From 95385dfd14e28d2706f37bfb1aff5eababa2716c Mon Sep 17 00:00:00 2001 From: hwillson Date: Fri, 13 Dec 2019 10:43:42 -0500 Subject: [PATCH 1/4] Provide an `@apollo/client/utilities` entry point for utilities --- config/prepareDist.js | 20 +++++++----- config/rollup.config.js | 18 +++++++++++ src/utilities/index.ts | 67 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/utilities/index.ts diff --git a/config/prepareDist.js b/config/prepareDist.js index 906baaa3b15..0b98eda2391 100644 --- a/config/prepareDist.js +++ b/config/prepareDist.js @@ -53,7 +53,7 @@ fs.copyFileSync(`${srcDir}/README.md`, `${destDir}/README.md`); fs.copyFileSync(`${srcDir}/LICENSE`, `${destDir}/LICENSE`); -/* @apollo/client/core, @apollo/client/cache */ +/* @apollo/client/core, @apollo/client/cache, @apollo/client/utilities */ function buildPackageJson(bundleName) { return JSON.stringify({ @@ -91,15 +91,21 @@ function writeCjsIndex(bundleName, exportNames, includeNames = true) { ].join('\n')); } -// Create `core` and `cache` bundle package.json files, storing them in their -// associated dist directory. This helps provide a way for the Apollo Client -// core to be used without React (via `@apollo/client/core`), and the cache -// to be used by itself (via `@apollo/client/cache`). Also create -// `core.cjs.js` and `cache.cjs.js` CommonJS entry point files that only -// include the exports needed for each bundle. +// Create `core`, `cache` and `utilities` bundle package.json files, storing +// them in their associated dist directory. This helps provide a way for the +// Apollo Client core to be used without React (via `@apollo/client/core`), +// and AC's cache and utilities to be used by themselves +// (`@apollo/client/cache` and `@apollo/client/utilities`), via the +// `core.cjs.js`, `cache.cjs.js` and `utilities.cjs.js` CommonJS entry point +// files that only include the exports needed for each bundle. fs.writeFileSync(`${distRoot}/core/package.json`, buildPackageJson('core')); writeCjsIndex('core', loadExportNames('react'), false); fs.writeFileSync(`${distRoot}/cache/package.json`, buildPackageJson('cache')); writeCjsIndex('cache', loadExportNames('cache')); + +fs.writeFileSync( + `${distRoot}/utilities/package.json`, + buildPackageJson('utilities') +); diff --git a/config/rollup.config.js b/config/rollup.config.js index db4419dc9b7..e219f8972eb 100644 --- a/config/rollup.config.js +++ b/config/rollup.config.js @@ -108,6 +108,23 @@ function prepareCJSMinified(input) { }; } +function prepareUtilities() { + const utilsDistDir = `${distDir}/utilities`; + return { + input: `${utilsDistDir}/index.js`, + external, + output: { + file: `${utilsDistDir}/utilities.cjs.js`, + format: 'cjs', + sourcemap: true, + exports: 'named', + }, + plugins: [ + nodeResolve(), + ], + }; +} + // Build a separate CJS only `testing.js` bundle, that includes React // testing utilities like `MockedProvider` (testing utilities are kept out of // the main `apollo-client` bundle). This bundle can be accessed directly @@ -144,6 +161,7 @@ function rollup() { prepareESM(packageJson.module, distDir), prepareCJS(packageJson.module, packageJson.main), prepareCJSMinified(packageJson.main), + prepareUtilities(), prepareTesting(), ]; } diff --git a/src/utilities/index.ts b/src/utilities/index.ts new file mode 100644 index 00000000000..0fda92429da --- /dev/null +++ b/src/utilities/index.ts @@ -0,0 +1,67 @@ +export { + DirectiveInfo, + InclusionDirectives, + + shouldInclude, + hasDirectives, + hasClientExports, + getDirectiveNames, + getInclusionDirectives, +} from './graphql/directives'; + +export { + FragmentMap, + + createFragmentMap, + getFragmentQueryDocument, + getFragmentFromSelection, +} from './graphql/fragments'; + +export { + checkDocument, + getOperationDefinition, + getOperationName, + getFragmentDefinitions, + getQueryDefinition, + getFragmentDefinition, + getMainDefinition, + getDefaultValues, +} from './graphql/getFromAST'; + +export { + Reference, + StoreValue, + Directives, + VariableValue, + + makeReference, + isReference, + isField, + isInlineFragment, + valueToObjectRepresentation, + storeKeyNameFromField, + argumentsObjectFromField, + resultKeyNameFromField, + getStoreKeyName, + getTypenameFromResult, +} from './graphql/storeUtils'; + +export { + RemoveNodeConfig, + GetNodeConfig, + RemoveDirectiveConfig, + GetDirectiveConfig, + RemoveArgumentsConfig, + GetFragmentSpreadConfig, + RemoveFragmentSpreadConfig, + RemoveFragmentDefinitionConfig, + RemoveVariableDefinitionConfig, + + addTypenameToDocument, + buildQueryFromSelectionSet, + removeDirectivesFromDocument, + removeConnectionDirectiveFromDocument, + removeArgumentsFromDocument, + removeFragmentSpreadFromDocument, + removeClientSetsFromDocument, +} from './graphql/transform'; From af271399d638bd1aec637e38446c63c338fc89bc Mon Sep 17 00:00:00 2001 From: hwillson Date: Fri, 13 Dec 2019 10:45:55 -0500 Subject: [PATCH 2/4] Changelog update --- CHANGELOG.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 099498866be..8a2fbba4f85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,9 +48,6 @@ - Removed `apollo-boost` since Apollo Client 3.0 provides a boost like getting started experience out of the box.
[@hwillson](https://github.com/hwillson) in [#5217](https://github.com/apollographql/apollo-client/pull/5217) -- The `@apollo/client` package is now published without a nested `@apollo/client/lib` directory.
- [@hwillson](https://github.com/hwillson) in [#5357](https://github.com/apollographql/apollo-client/pull/5357) - - The `queryManager` property of `ApolloClient` instances is now marked as `private`, paving the way for a more aggressive redesign of its API. @@ -68,9 +65,6 @@ - `ApolloClient` is now only available as a named export. The default `ApolloClient` export has been removed.
[@hwillson](https://github.com/hwillson) in [#5425](https://github.com/apollographql/apollo-client/pull/5425) -- Utilities that were previously externally available through the `apollo-utilities` package, are now internal only.
- [@hwillson](https://github.com/hwillson) in [#TODO](https://github.com/apollographql/apollo-client/pull/TODO) - - The `ObservableQuery#getCurrentResult` method no longer falls back to reading from the cache, so calling it immediately after `client.watchQuery` will consistently return a `loading: true` result. When the `fetchPolicy` permits cached results, those results will be delivered via the `next` method of the `ObservableQuery`, and can be obtained by `getCurrentResult` after they have been delivered. This change prevents race conditions where the initial behavior of one query could depend on the timing of cache writes associated with other queries.
[@benjamn](https://github.com/benjamn) in [#5565](https://github.com/apollographql/apollo-client/pull/5565) @@ -94,6 +88,9 @@ - Custom field `read` functions can read from neighboring fields using the `getFieldValue(fieldName)` helper, and may also read fields from other entities by calling `getFieldValue(fieldName, foreignReference)`.
[@benjamn](https://github.com/benjamn) in [#5651](https://github.com/apollographql/apollo-client/pull/5651) +- Utilities that were previously externally available through the `apollo-utilities` package are now only available by importing from `@apollo/client/utilities`.
+ [@hwillson](https://github.com/hwillson) in [#TODO](https://github.com/apollographql/apollo-client/pull/TODO) + ## Apollo Client (2.6.4) ### Apollo Client (2.6.4) From 87c7a2bcc285626dd666587ab2c2b4103af161bc Mon Sep 17 00:00:00 2001 From: hwillson Date: Fri, 13 Dec 2019 10:46:08 -0500 Subject: [PATCH 3/4] Remove utilities we no longer use internally --- src/cache/inmemory/entityStore.ts | 5 +- src/core/LocalState.ts | 4 +- src/core/ObservableQuery.ts | 12 +- src/data/queries.ts | 6 +- src/react/data/MutationData.ts | 4 +- src/react/data/OperationData.ts | 4 +- src/react/data/QueryData.ts | 12 +- src/react/data/SubscriptionData.ts | 4 +- src/react/hooks/utils/useDeepMemo.ts | 4 +- src/utilities/common/__tests__/isEqual.ts | 174 -------- src/utilities/common/capitalizeFirstLetter.ts | 3 - src/utilities/common/isEqual.ts | 1 - src/utilities/graphql/__tests__/getFromAST.ts | 42 -- src/utilities/graphql/__tests__/transform.ts | 403 ------------------ src/utilities/graphql/directives.ts | 20 - src/utilities/graphql/getFromAST.ts | 40 -- src/utilities/graphql/storeUtils.ts | 45 -- src/utilities/graphql/transform.ts | 42 -- src/utilities/index.ts | 4 - src/utilities/testing/mocking/mockLink.ts | 4 +- 20 files changed, 30 insertions(+), 803 deletions(-) delete mode 100644 src/utilities/common/__tests__/isEqual.ts delete mode 100644 src/utilities/common/capitalizeFirstLetter.ts delete mode 100644 src/utilities/common/isEqual.ts diff --git a/src/cache/inmemory/entityStore.ts b/src/cache/inmemory/entityStore.ts index df5bc670bb3..60f38a8b98c 100644 --- a/src/cache/inmemory/entityStore.ts +++ b/src/cache/inmemory/entityStore.ts @@ -1,11 +1,12 @@ import { dep, OptimisticDependencyFunction, KeyTrie } from 'optimism'; import { invariant } from 'ts-invariant'; +import { equal } from '@wry/equality'; + import { isReference, StoreValue } from '../../utilities/graphql/storeUtils'; import { DeepMerger, ReconcilerFunction, } from '../../utilities/common/mergeDeep'; -import { isEqual } from '../../utilities/common/isEqual'; import { canUseWeakMap } from '../../utilities/common/canUse'; import { NormalizedCache, NormalizedCacheObject, StoreObject } from './types'; import { @@ -482,7 +483,7 @@ const storeObjectReconciler: ReconcilerFunction<[EntityStore]> = function ( // returning incoming would be logically correct) because preserving // the referential identity of existing data can prevent needless // rereading and rerendering. - if (isEqual(existing, incoming)) { + if (equal(existing, incoming)) { return existing; } } diff --git a/src/core/LocalState.ts b/src/core/LocalState.ts index a250a89afa1..7ab0f0df6db 100644 --- a/src/core/LocalState.ts +++ b/src/core/LocalState.ts @@ -32,7 +32,6 @@ import { } from '../utilities/graphql/storeUtils'; import { ApolloClient } from '../ApolloClient'; import { Resolvers, OperationVariables } from './types'; -import { capitalizeFirstLetter } from '../utilities/common/capitalizeFirstLetter'; export type Resolver = ( rootValue?: any, @@ -278,7 +277,8 @@ export class LocalState { .operation; const defaultOperationType = definitionOperation - ? capitalizeFirstLetter(definitionOperation) + ? definitionOperation.charAt(0).toUpperCase() + + definitionOperation.slice(1) : 'Query'; const { cache, client } = this; diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts index 994f69518c7..7db43eaeceb 100644 --- a/src/core/ObservableQuery.ts +++ b/src/core/ObservableQuery.ts @@ -1,6 +1,6 @@ import { invariant, InvariantError } from 'ts-invariant'; +import { equal } from '@wry/equality'; -import { isEqual } from '../utilities/common/isEqual'; import { tryFunctionOrLogError } from '../utilities/common/errorHandling'; import { cloneDeep } from '../utilities/common/cloneDeep'; import { getOperationDefinition } from '../utilities/graphql/getFromAST'; @@ -207,7 +207,7 @@ export class ObservableQuery< newResult && snapshot.networkStatus === newResult.networkStatus && snapshot.stale === newResult.stale && - isEqual(snapshot.data, newResult.data) + equal(snapshot.data, newResult.data) ); } @@ -260,7 +260,7 @@ export class ObservableQuery< fetchPolicy = 'network-only'; } - if (!isEqual(this.variables, variables)) { + if (!equal(this.variables, variables)) { // update observable variables this.variables = { ...this.variables, @@ -268,7 +268,7 @@ export class ObservableQuery< }; } - if (!isEqual(this.options.variables, this.variables)) { + if (!equal(this.options.variables, this.variables)) { // Update the existing options with new variables this.options.variables = { ...this.options.variables, @@ -446,7 +446,7 @@ export class ObservableQuery< variables = variables || this.variables; - if (!tryFetch && isEqual(variables, this.variables)) { + if (!tryFetch && equal(variables, this.variables)) { // If we have no observers, then we don't actually want to make a network // request. As soon as someone observes the query, the request will kick // off. For now, we just store any changes. (See #1077) @@ -600,7 +600,7 @@ export class ObservableQuery< previousResult && fetchPolicy !== 'cache-only' && queryManager.transform(query).serverQuery && - !isEqual(previousVariables, variables) + !equal(previousVariables, variables) ) { this.refetch(); } else { diff --git a/src/data/queries.ts b/src/data/queries.ts index 5a67d0c9ead..47d07cbc6b5 100644 --- a/src/data/queries.ts +++ b/src/data/queries.ts @@ -1,7 +1,7 @@ import { DocumentNode, GraphQLError, ExecutionResult } from 'graphql'; import { invariant } from 'ts-invariant'; -import { isEqual } from '../utilities/common/isEqual'; +import { equal } from '@wry/equality'; import { NetworkStatus } from '../core/networkStatus'; import { isNonEmptyArray } from '../utilities/common/arrays'; @@ -44,7 +44,7 @@ export class QueryStore { invariant( !previousQuery || previousQuery.document === query.document || - isEqual(previousQuery.document, query.document), + equal(previousQuery.document, query.document), 'Internal Error: may not update existing query string in store', ); @@ -57,7 +57,7 @@ export class QueryStore { previousQuery.networkStatus !== NetworkStatus.loading // if the previous query was still loading, we don't want to remember it at all. ) { - if (!isEqual(previousQuery.variables, query.variables)) { + if (!equal(previousQuery.variables, query.variables)) { isSetVariables = true; previousVariables = previousQuery.variables; } diff --git a/src/react/data/MutationData.ts b/src/react/data/MutationData.ts index 78d20e568a7..6421f1ca6af 100644 --- a/src/react/data/MutationData.ts +++ b/src/react/data/MutationData.ts @@ -1,4 +1,4 @@ -import { equal as isEqual } from '@wry/equality'; +import { equal } from '@wry/equality'; import { DocumentType } from '../parser/parser'; import { ApolloError } from '../../errors/ApolloError'; @@ -175,7 +175,7 @@ export class MutationData< private updateResult(result: MutationResult) { if ( this.isMounted && - (!this.previousResult || !isEqual(this.previousResult, result)) + (!this.previousResult || !equal(this.previousResult, result)) ) { this.setResult(result); this.previousResult = result; diff --git a/src/react/data/OperationData.ts b/src/react/data/OperationData.ts index c046a45c81a..92e1f6fbe6d 100644 --- a/src/react/data/OperationData.ts +++ b/src/react/data/OperationData.ts @@ -1,5 +1,5 @@ import { DocumentNode } from 'graphql'; -import { equal as isEqual } from '@wry/equality'; +import { equal } from '@wry/equality'; import { invariant } from 'ts-invariant'; import { ApolloClient } from '../../ApolloClient'; @@ -29,7 +29,7 @@ export abstract class OperationData { newOptions: CommonOptions, storePrevious: boolean = false ) { - if (storePrevious && !isEqual(this.options, newOptions)) { + if (storePrevious && !equal(this.options, newOptions)) { this.previousOptions = this.options; } this.options = newOptions; diff --git a/src/react/data/QueryData.ts b/src/react/data/QueryData.ts index 84584b4cfdb..fe56dc4f3ef 100644 --- a/src/react/data/QueryData.ts +++ b/src/react/data/QueryData.ts @@ -1,4 +1,4 @@ -import { equal as isEqual } from '@wry/equality'; +import { equal } from '@wry/equality'; import { ApolloError } from '../../errors/ApolloError'; import { NetworkStatus } from '../../core/networkStatus'; @@ -245,7 +245,7 @@ export class QueryData extends OperationData { }; if ( - !isEqual( + !equal( newObservableQueryOptions, this.previousData.observableQueryOptions ) @@ -280,7 +280,7 @@ export class QueryData extends OperationData { previousResult && previousResult.loading === loading && previousResult.networkStatus === networkStatus && - isEqual(previousResult.data, data) + equal(previousResult.data, data) ) { return; } @@ -306,7 +306,7 @@ export class QueryData extends OperationData { const previousResult = this.previousData.result; if ( (previousResult && previousResult.loading) || - !isEqual(error, this.previousData.error) + !equal(error, this.previousData.error) ) { this.previousData.error = error; onNewData(); @@ -433,8 +433,8 @@ export class QueryData extends OperationData { if ( this.previousOptions && !this.previousData.loading && - isEqual(this.previousOptions.query, query) && - isEqual(this.previousOptions.variables, variables) + equal(this.previousOptions.query, query) && + equal(this.previousOptions.variables, variables) ) { return; } diff --git a/src/react/data/SubscriptionData.ts b/src/react/data/SubscriptionData.ts index 9128899de3c..de7b41a4e3e 100644 --- a/src/react/data/SubscriptionData.ts +++ b/src/react/data/SubscriptionData.ts @@ -1,4 +1,4 @@ -import { equal as isEqual } from '@wry/equality'; +import { equal } from '@wry/equality'; import { OperationData } from './OperationData'; import { @@ -54,7 +54,7 @@ export class SubscriptionData< this.previousOptions && Object.keys(this.previousOptions).length > 0 && (this.previousOptions.subscription !== this.getOptions().subscription || - !isEqual(this.previousOptions.variables, this.getOptions().variables) || + !equal(this.previousOptions.variables, this.getOptions().variables) || this.previousOptions.skip !== this.getOptions().skip) ) { this.cleanup(); diff --git a/src/react/hooks/utils/useDeepMemo.ts b/src/react/hooks/utils/useDeepMemo.ts index f6090d07cbf..a1f4882952a 100644 --- a/src/react/hooks/utils/useDeepMemo.ts +++ b/src/react/hooks/utils/useDeepMemo.ts @@ -1,4 +1,4 @@ -import { equal as isEqual } from '@wry/equality'; +import { equal } from '@wry/equality'; import { requireReactLazily } from '../../react'; @@ -17,7 +17,7 @@ export function useDeepMemo( const { useRef } = React; const ref = useRef<{ key: TKey; value: TValue }>(); - if (!ref.current || !isEqual(key, ref.current.key)) { + if (!ref.current || !equal(key, ref.current.key)) { ref.current = { key, value: memoFn() }; } diff --git a/src/utilities/common/__tests__/isEqual.ts b/src/utilities/common/__tests__/isEqual.ts deleted file mode 100644 index e621a837082..00000000000 --- a/src/utilities/common/__tests__/isEqual.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { isEqual } from '../isEqual'; - -describe('isEqual', () => { - it('should return true for equal primitive values', () => { - expect(isEqual(undefined, undefined)).toBe(true); - expect(isEqual(null, null)).toBe(true); - expect(isEqual(true, true)).toBe(true); - expect(isEqual(false, false)).toBe(true); - expect(isEqual(-1, -1)).toBe(true); - expect(isEqual(+1, +1)).toBe(true); - expect(isEqual(42, 42)).toBe(true); - expect(isEqual(0, 0)).toBe(true); - expect(isEqual(0.5, 0.5)).toBe(true); - expect(isEqual('hello', 'hello')).toBe(true); - expect(isEqual('world', 'world')).toBe(true); - }); - - it('should return false for not equal primitive values', () => { - expect(!isEqual(undefined, null)).toBe(true); - expect(!isEqual(null, undefined)).toBe(true); - expect(!isEqual(true, false)).toBe(true); - expect(!isEqual(false, true)).toBe(true); - expect(!isEqual(-1, +1)).toBe(true); - expect(!isEqual(+1, -1)).toBe(true); - expect(!isEqual(42, 42.00000000000001)).toBe(true); - expect(!isEqual(0, 0.5)).toBe(true); - expect(!isEqual('hello', 'world')).toBe(true); - expect(!isEqual('world', 'hello')).toBe(true); - }); - - it('should return false when comparing primitives with objects', () => { - expect(!isEqual({}, null)).toBe(true); - expect(!isEqual(null, {})).toBe(true); - expect(!isEqual({}, true)).toBe(true); - expect(!isEqual(true, {})).toBe(true); - expect(!isEqual({}, 42)).toBe(true); - expect(!isEqual(42, {})).toBe(true); - expect(!isEqual({}, 'hello')).toBe(true); - expect(!isEqual('hello', {})).toBe(true); - }); - - it('should correctly compare shallow objects', () => { - expect(isEqual({}, {})).toBe(true); - expect(isEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).toBe(true); - expect(!isEqual({ a: 1, b: 2, c: 3 }, { a: 3, b: 2, c: 1 })).toBe(true); - expect(!isEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 })).toBe(true); - expect(!isEqual({ a: 1, b: 2 }, { a: 1, b: 2, c: 3 })).toBe(true); - }); - - it('should correctly compare deep objects', () => { - expect(isEqual({ x: {} }, { x: {} })).toBe(true); - expect( - isEqual({ x: { a: 1, b: 2, c: 3 } }, { x: { a: 1, b: 2, c: 3 } }), - ).toBe(true); - expect( - !isEqual({ x: { a: 1, b: 2, c: 3 } }, { x: { a: 3, b: 2, c: 1 } }), - ).toBe(true); - expect(!isEqual({ x: { a: 1, b: 2, c: 3 } }, { x: { a: 1, b: 2 } })).toBe( - true, - ); - expect(!isEqual({ x: { a: 1, b: 2 } }, { x: { a: 1, b: 2, c: 3 } })).toBe( - true, - ); - }); - - it('should correctly compare deep objects without object prototype ', () => { - // Solves https://github.com/apollographql/apollo-client/issues/2132 - const objNoProto = Object.create(null); - objNoProto.a = { b: 2, c: [3, 4] }; - objNoProto.e = Object.create(null); - objNoProto.e.f = 5; - expect(isEqual(objNoProto, { a: { b: 2, c: [3, 4] }, e: { f: 5 } })).toBe( - true, - ); - expect(!isEqual(objNoProto, { a: { b: 2, c: [3, 4] }, e: { f: 6 } })).toBe( - true, - ); - expect(!isEqual(objNoProto, { a: { b: 2, c: [3, 4] }, e: null })).toBe( - true, - ); - expect(!isEqual(objNoProto, { a: { b: 2, c: [3] }, e: { f: 5 } })).toBe( - true, - ); - expect(!isEqual(objNoProto, null)).toBe(true); - }); - - it('should correctly handle modified prototypes', () => { - Array.prototype.foo = null; - expect(isEqual([1, 2, 3], [1, 2, 3])).toBe(true); - expect(!isEqual([1, 2, 3], [1, 2, 4])).toBe(true); - delete Array.prototype.foo; - }); - - describe('comparing objects with circular refs', () => { - // copied with slight modification from lodash test suite - it('should compare objects with circular references', () => { - const object1 = {}, - object2 = {}; - - object1.a = object1; - object2.a = object2; - - expect(isEqual(object1, object2)).toBe(true); - - object1.b = 0; - object2.b = Object(0); - - expect(isEqual(object1, object2)).toBe(true); - - object1.c = Object(1); - object2.c = Object(2); - - expect(isEqual(object1, object2)).toBe(false); - - object1 = { a: 1, b: 2, c: 3 }; - object1.b = object1; - object2 = { a: 1, b: { a: 1, b: 2, c: 3 }, c: 3 }; - - expect(isEqual(object1, object2)).toBe(false); - }); - - it('should have transitive equivalence for circular references of objects', () => { - const object1 = {}, - object2 = { a: object1 }, - object3 = { a: object2 }; - - object1.a = object1; - - expect(isEqual(object1, object2)).toBe(true); - expect(isEqual(object2, object3)).toBe(true); - expect(isEqual(object1, object3)).toBe(true); - }); - - it('should compare objects with multiple circular references', () => { - const array1 = [{}], - array2 = [{}]; - - (array1[0].a = array1).push(array1); - (array2[0].a = array2).push(array2); - - expect(isEqual(array1, array2)).toBe(true); - - array1[0].b = 0; - array2[0].b = Object(0); - - expect(isEqual(array1, array2)).toBe(true); - - array1[0].c = Object(1); - array2[0].c = Object(2); - - expect(isEqual(array1, array2)).toBe(false); - }); - - it('should compare objects with complex circular references', () => { - const object1 = { - foo: { b: { c: { d: {} } } }, - bar: { a: 2 }, - }; - - const object2 = { - foo: { b: { c: { d: {} } } }, - bar: { a: 2 }, - }; - - object1.foo.b.c.d = object1; - object1.bar.b = object1.foo.b; - - object2.foo.b.c.d = object2; - object2.bar.b = object2.foo.b; - - expect(isEqual(object1, object2)).toBe(true); - }); - }); -}); diff --git a/src/utilities/common/capitalizeFirstLetter.ts b/src/utilities/common/capitalizeFirstLetter.ts deleted file mode 100644 index 0eab4968185..00000000000 --- a/src/utilities/common/capitalizeFirstLetter.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function capitalizeFirstLetter(str: string) { - return str.charAt(0).toUpperCase() + str.slice(1); -} diff --git a/src/utilities/common/isEqual.ts b/src/utilities/common/isEqual.ts deleted file mode 100644 index debf7429c5e..00000000000 --- a/src/utilities/common/isEqual.ts +++ /dev/null @@ -1 +0,0 @@ -export { equal as isEqual } from '@wry/equality'; diff --git a/src/utilities/graphql/__tests__/getFromAST.ts b/src/utilities/graphql/__tests__/getFromAST.ts index 328aeb57d4a..575ecfca038 100644 --- a/src/utilities/graphql/__tests__/getFromAST.ts +++ b/src/utilities/graphql/__tests__/getFromAST.ts @@ -6,7 +6,6 @@ import { checkDocument, getFragmentDefinitions, getQueryDefinition, - getMutationDefinition, getDefaultValues, getOperationName, } from '../getFromAST'; @@ -162,32 +161,6 @@ describe('AST utility functions', () => { }).toThrow(); }); - it('should get the correct mutation definition out of a mutation with multiple fragments', () => { - const mutationWithFragments = gql` - mutation { - createAuthor(firstName: "John", lastName: "Smith") { - ...authorDetails - } - } - - fragment authorDetails on Author { - firstName - lastName - } - `; - const expectedDoc = gql` - mutation { - createAuthor(firstName: "John", lastName: "Smith") { - ...authorDetails - } - } - `; - const expectedResult: OperationDefinitionNode = expectedDoc - .definitions[0] as OperationDefinitionNode; - const actualResult = getMutationDefinition(mutationWithFragments); - expect(print(actualResult)).toEqual(print(expectedResult)); - }); - it('should get the operation name out of a query', () => { const query = gql` query nameOfQuery { @@ -268,24 +241,9 @@ describe('AST utility functions', () => { } `; - const complexMutation = gql` - mutation complexStuff( - $test: Input = { key1: ["value", "value2"], key2: { key3: 4 } } - ) { - complexStuff(test: $test) { - people { - name - } - } - } - `; - expect(getDefaultValues(getQueryDefinition(basicQuery))).toEqual({ first: 1, }); - expect(getDefaultValues(getMutationDefinition(complexMutation))).toEqual({ - test: { key1: ['value', 'value2'], key2: { key3: 4 } }, - }); }); }); }); diff --git a/src/utilities/graphql/__tests__/transform.ts b/src/utilities/graphql/__tests__/transform.ts index 26835f4cc4f..01a687dafc3 100644 --- a/src/utilities/graphql/__tests__/transform.ts +++ b/src/utilities/graphql/__tests__/transform.ts @@ -8,7 +8,6 @@ disableFragmentWarnings(); import { addTypenameToDocument, removeDirectivesFromDocument, - getDirectivesFromDocument, removeConnectionDirectiveFromDocument, removeArgumentsFromDocument, removeFragmentSpreadFromDocument, @@ -803,408 +802,6 @@ describe('query transforms', () => { }); }); -describe('getDirectivesFromDocument', () => { - it('should get query with fields of storage directive ', () => { - const query = gql` - query Simple { - field @storage(if: true) - } - `; - - const expected = gql` - query Simple { - field @storage(if: true) - } - `; - const doc = getDirectivesFromDocument([{ name: 'storage' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it('should get query with fields of storage directive [test function] ', () => { - const query = gql` - query Simple { - field @storage(if: true) - } - `; - - const expected = gql` - query Simple { - field @storage(if: true) - } - `; - const test = ({ name: { value } }: { name: any }) => value === 'storage'; - const doc = getDirectivesFromDocument([{ test }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it('should only get query with fields of storage directive ', () => { - const query = gql` - query Simple { - maybe @skip(if: false) - field @storage(if: true) - } - `; - - const expected = gql` - query Simple { - field @storage(if: true) - } - `; - const doc = getDirectivesFromDocument([{ name: 'storage' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it('should only get query with multiple fields of storage directive ', () => { - const query = gql` - query Simple { - maybe @skip(if: false) - field @storage(if: true) - other @storage - } - `; - - const expected = gql` - query Simple { - field @storage(if: true) - other @storage - } - `; - const doc = getDirectivesFromDocument([{ name: 'storage' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it('should get query with fields of both storage and client directives ', () => { - const query = gql` - query Simple { - maybe @skip(if: false) - field @storage(if: true) - user @client - } - `; - - const expected = gql` - query Simple { - field @storage(if: true) - user @client - } - `; - const doc = getDirectivesFromDocument( - [{ name: 'storage' }, { name: 'client' }], - query, - ); - expect(print(doc)).toBe(print(expected)); - }); - - it('should get query with different types of directive matchers ', () => { - const query = gql` - query Simple { - maybe @skip(if: false) - field @storage(if: true) - user @client - } - `; - - const expected = gql` - query Simple { - field @storage(if: true) - user @client - } - `; - const doc = getDirectivesFromDocument( - [ - { name: 'storage' }, - { test: directive => directive.name.value === 'client' }, - ], - query, - ); - - expect(print(doc)).toBe(print(expected)); - }); - - it('should get query with nested fields ', () => { - const query = gql` - query Simple { - user { - firstName @client - email - } - } - `; - - const expected = gql` - query Simple { - user { - firstName @client - } - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it('should include all the nested fields of field that has client directive ', () => { - const query = gql` - query Simple { - user @client { - firstName - email - } - } - `; - - const expected = gql` - query Simple { - user @client { - firstName - email - } - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it('should return null if the query is no longer valid', () => { - const query = gql` - query Simple { - field - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query); - expect(print(doc)).toBe(null); - }); - - it('should get query with client fields in fragment', function() { - const query = gql` - query Simple { - ...fragmentSpread - } - - fragment fragmentSpread on Thing { - field @client - other - } - `; - const expected = gql` - query Simple { - ...fragmentSpread - } - - fragment fragmentSpread on Thing { - field @client - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it('should get query with client fields in fragment with nested fields', function() { - const query = gql` - query Simple { - ...fragmentSpread - } - - fragment fragmentSpread on Thing { - user { - firstName @client - lastName - } - } - `; - const expected = gql` - query Simple { - ...fragmentSpread - } - - fragment fragmentSpread on Thing { - user { - firstName @client - } - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it('should get query with client fields in multiple fragments', function() { - const query = gql` - query Simple { - ...fragmentSpread - ...anotherFragmentSpread - } - - fragment fragmentSpread on Thing { - field @client - other - } - - fragment anotherFragmentSpread on AnotherThing { - user @client - product - } - `; - const expected = gql` - query Simple { - ...fragmentSpread - ...anotherFragmentSpread - } - - fragment fragmentSpread on Thing { - field @client - } - - fragment anotherFragmentSpread on AnotherThing { - user @client - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it("should return null if fragment didn't have client fields", function() { - const query = gql` - query Simple { - ...fragmentSpread - } - - fragment fragmentSpread on Thing { - field - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query); - expect(print(doc)).toBe(print(null)); - }); - - it('should get query with client fields when both fields and fragements are mixed', function() { - const query = gql` - query Simple { - user @client - product @storage - order - ...fragmentSpread - } - - fragment fragmentSpread on Thing { - field @client - other - } - `; - const expected = gql` - query Simple { - user @client - ...fragmentSpread - } - - fragment fragmentSpread on Thing { - field @client - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it('should get mutation with client fields', () => { - const query = gql` - mutation { - login @client - } - `; - - const expected = gql` - mutation { - login @client - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - it('should get mutation fields of client only', () => { - const query = gql` - mutation { - login @client - updateUser - } - `; - - const expected = gql` - mutation { - login @client - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query); - expect(print(doc)).toBe(print(expected)); - }); - - describe('includeAllFragments', () => { - it('= false: should remove the values without a client in fragment', () => { - const query = gql` - fragment client on ClientData { - hi @client - bye @storage - bar - } - - query Mixed { - foo @client { - ...client - } - bar { - baz - } - } - `; - - const expected = gql` - fragment client on ClientData { - hi @client - } - - query Mixed { - foo @client { - ...client - } - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query, false); - expect(print(doc)).toBe(print(expected)); - }); - - it('= true: should include the values without a client in fragment', () => { - const query = gql` - fragment client on ClientData { - hi @client - bye @storage - bar - } - - query Mixed { - foo @client { - ...client - } - bar { - baz - } - } - `; - - const expected = gql` - fragment client on ClientData { - hi @client - } - - query Mixed { - foo @client { - ...client - } - } - `; - const doc = getDirectivesFromDocument([{ name: 'client' }], query, true); - expect(print(doc)).toBe(print(expected)); - }); - }); -}); - describe('removeClientSetsFromDocument', () => { it('should remove @client fields from document', () => { const query = gql` diff --git a/src/utilities/graphql/directives.ts b/src/utilities/graphql/directives.ts index 84c14abaeaa..a7a20a01fb4 100644 --- a/src/utilities/graphql/directives.ts +++ b/src/utilities/graphql/directives.ts @@ -1,7 +1,6 @@ // Provides the methods that allow QueryManager to handle the `skip` and // `include` directives within GraphQL. import { - FieldNode, SelectionNode, VariableNode, BooleanValueNode, @@ -15,29 +14,10 @@ import { visit } from 'graphql/language/visitor'; import { invariant } from 'ts-invariant'; -import { argumentsObjectFromField } from './storeUtils'; - export type DirectiveInfo = { [fieldName: string]: { [argName: string]: any }; }; -export function getDirectiveInfoFromField( - field: FieldNode, - variables: Object, -): DirectiveInfo { - if (field.directives && field.directives.length) { - const directiveObj: DirectiveInfo = {}; - field.directives.forEach((directive: DirectiveNode) => { - directiveObj[directive.name.value] = argumentsObjectFromField( - directive, - variables, - ); - }); - return directiveObj; - } - return null; -} - export function shouldInclude( selection: SelectionNode, variables: { [name: string]: any } = {}, diff --git a/src/utilities/graphql/getFromAST.ts b/src/utilities/graphql/getFromAST.ts index d5aabba0609..3dad1fe3da7 100644 --- a/src/utilities/graphql/getFromAST.ts +++ b/src/utilities/graphql/getFromAST.ts @@ -11,22 +11,6 @@ import { assign } from '../common/assign'; import { valueToObjectRepresentation } from './storeUtils'; -export function getMutationDefinition( - doc: DocumentNode, -): OperationDefinitionNode { - checkDocument(doc); - - let mutationDef: OperationDefinitionNode | null = doc.definitions.filter( - definition => - definition.kind === 'OperationDefinition' && - definition.operation === 'mutation', - )[0] as OperationDefinitionNode; - - invariant(mutationDef, 'Must contain a mutation definition.'); - - return mutationDef; -} - // Checks the document for errors and throws an exception if there is an error. export function checkDocument(doc: DocumentNode) { invariant( @@ -65,14 +49,6 @@ export function getOperationDefinition( )[0] as OperationDefinitionNode; } -export function getOperationDefinitionOrDie( - document: DocumentNode, -): OperationDefinitionNode { - const def = getOperationDefinition(document); - invariant(def, `GraphQL document is missing an operation`); - return def; -} - export function getOperationName(doc: DocumentNode): string | null { return ( doc.definitions @@ -195,19 +171,3 @@ export function getDefaultValues( return {}; } - -/** - * Returns the names of all variables declared by the operation. - */ -export function variablesInOperation( - operation: OperationDefinitionNode, -): Set { - const names = new Set(); - if (operation.variableDefinitions) { - for (const definition of operation.variableDefinitions) { - names.add(definition.variable.name.value); - } - } - - return names; -} diff --git a/src/utilities/graphql/storeUtils.ts b/src/utilities/graphql/storeUtils.ts index a4178975702..5bf701f1a2c 100644 --- a/src/utilities/graphql/storeUtils.ts +++ b/src/utilities/graphql/storeUtils.ts @@ -44,18 +44,6 @@ export type StoreValue = | void | Object; -export type ScalarValue = StringValueNode | BooleanValueNode | EnumValueNode; - -export function isScalarValue(value: ValueNode): value is ScalarValue { - return ['StringValue', 'BooleanValue', 'EnumValue'].indexOf(value.kind) > -1; -} - -export type NumberValue = IntValueNode | FloatValueNode; - -export function isNumberValue(value: ValueNode): value is NumberValue { - return ['IntValue', 'FloatValue'].indexOf(value.kind) > -1; -} - function isStringValue(value: ValueNode): value is StringValueNode { return value.kind === 'StringValue'; } @@ -297,38 +285,5 @@ export function isInlineFragment( return selection.kind === 'InlineFragment'; } -function defaultValueFromVariable(node: VariableNode) { - throw new InvariantError(`Variable nodes are not supported by valueFromNode`); -} - export type VariableValue = (node: VariableNode) => any; -/** - * Evaluate a ValueNode and yield its value in its natural JS form. - */ -export function valueFromNode( - node: ValueNode, - onVariable: VariableValue = defaultValueFromVariable, -): any { - switch (node.kind) { - case 'Variable': - return onVariable(node); - case 'NullValue': - return null; - case 'IntValue': - return parseInt(node.value, 10); - case 'FloatValue': - return parseFloat(node.value); - case 'ListValue': - return node.values.map(v => valueFromNode(v, onVariable)); - case 'ObjectValue': { - const value: { [key: string]: any } = {}; - for (const field of node.fields) { - value[field.name.value] = valueFromNode(field.value, onVariable); - } - return value; - } - default: - return node.value; - } -} diff --git a/src/utilities/graphql/transform.ts b/src/utilities/graphql/transform.ts index b63ed2680d6..94ff1c04e21 100644 --- a/src/utilities/graphql/transform.ts +++ b/src/utilities/graphql/transform.ts @@ -325,48 +325,6 @@ function hasDirectivesInSelection( ); } -export function getDirectivesFromDocument( - directives: GetDirectiveConfig[], - doc: DocumentNode, -): DocumentNode { - checkDocument(doc); - - let parentPath: string; - - return nullIfDocIsEmpty( - visit(doc, { - SelectionSet: { - enter(node, _key, _parent, path) { - const currentPath = path.join('-'); - - if ( - !parentPath || - currentPath === parentPath || - !currentPath.startsWith(parentPath) - ) { - if (node.selections) { - const selectionsWithDirectives = node.selections.filter( - selection => hasDirectivesInSelection(directives, selection), - ); - - if (hasDirectivesInSelectionSet(directives, node, false)) { - parentPath = currentPath; - } - - return { - ...node, - selections: selectionsWithDirectives, - }; - } else { - return null; - } - } - }, - }, - }), - ); -} - function getArgumentMatcher(config: RemoveArgumentsConfig[]) { return function argumentMatcher(argument: ArgumentNode) { return config.some( diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 0fda92429da..320c73c5ee5 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -1,7 +1,6 @@ export { DirectiveInfo, InclusionDirectives, - shouldInclude, hasDirectives, hasClientExports, @@ -11,7 +10,6 @@ export { export { FragmentMap, - createFragmentMap, getFragmentQueryDocument, getFragmentFromSelection, @@ -33,7 +31,6 @@ export { StoreValue, Directives, VariableValue, - makeReference, isReference, isField, @@ -56,7 +53,6 @@ export { RemoveFragmentSpreadConfig, RemoveFragmentDefinitionConfig, RemoveVariableDefinitionConfig, - addTypenameToDocument, buildQueryFromSelectionSet, removeDirectivesFromDocument, diff --git a/src/utilities/testing/mocking/mockLink.ts b/src/utilities/testing/mocking/mockLink.ts index 4bc9699617c..a110bca193f 100644 --- a/src/utilities/testing/mocking/mockLink.ts +++ b/src/utilities/testing/mocking/mockLink.ts @@ -1,5 +1,6 @@ import { print } from 'graphql/language/printer'; import stringify from 'fast-json-stable-stringify'; +import { equal } from '@wry/equality'; import { Observable } from '../../../utilities/observables/Observable'; import { ApolloLink } from '../../../link/core/ApolloLink'; @@ -14,7 +15,6 @@ import { removeConnectionDirectiveFromDocument, } from '../../../utilities/graphql/transform'; import { cloneDeep } from '../../../utilities/common/cloneDeep'; -import { isEqual } from '../../../utilities/common/isEqual'; export type ResultFunction = () => T; @@ -77,7 +77,7 @@ export class MockLink extends ApolloLink { const requestVariables = operation.variables || {}; const mockedResponseVariables = res.request.variables || {}; if ( - !isEqual( + !equal( stringify(requestVariables), stringify(mockedResponseVariables) ) From 29a71a17f24a5b826e15dc78f393d55f21453d6f Mon Sep 17 00:00:00 2001 From: hwillson Date: Fri, 13 Dec 2019 21:07:04 -0500 Subject: [PATCH 4/4] Changelog update --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a2fbba4f85..fa28b2aaf14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,7 +89,7 @@ [@benjamn](https://github.com/benjamn) in [#5651](https://github.com/apollographql/apollo-client/pull/5651) - Utilities that were previously externally available through the `apollo-utilities` package are now only available by importing from `@apollo/client/utilities`.
- [@hwillson](https://github.com/hwillson) in [#TODO](https://github.com/apollographql/apollo-client/pull/TODO) + [@hwillson](https://github.com/hwillson) in [#5683](https://github.com/apollographql/apollo-client/pull/5683) ## Apollo Client (2.6.4)