From c5b0719c70d89c6a672705b6be98772097c8e9ab Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 31 Aug 2021 09:58:52 -0400 Subject: [PATCH] Some improvements and preparation for GraphQL v16 (#3443) * Test GraphQL v16 * Fix tests * Move collectFields to utils * v16< support * More * Fix tests for v14 and v15 * Fix * Naming * Use 15 in resolutions * Go * ... * Cleanup * Do not import an nested modules from graphql-js * Fix build * Changesets and more * Update graphql-js peerDep range * More * Fix * Improve DX * Some memoization * Fix memoize functions * More * Fix tests * Reuse functions * ??? * Less iterations * Remove object.assign at all * More * ... --- .changeset/config.json | 2 +- .changeset/few-chefs-bow.md | 6 + .changeset/funny-cobras-speak.md | 36 + .changeset/mean-coats-laugh.md | 9 + .changeset/spotty-queens-sniff.md | 7 + .changeset/strange-poems-flow.md | 5 + .github/workflows/tests.yml | 31 +- .vscode/settings.json | 6 +- README.md | 2 +- package.json | 4 - packages/batch-delegate/package.json | 2 +- packages/batch-delegate/src/getLoader.ts | 41 +- packages/batch-delegate/src/types.ts | 10 +- .../tests/basic.example.test.ts | 4 +- .../tests/withTransforms.test.ts | 2 +- packages/batch-execute/package.json | 2 +- .../src/createBatchingExecutor.ts | 5 +- .../batch-execute/src/getBatchingExecutor.ts | 3 +- packages/batch-execute/src/memoize.ts | 22 - packages/delegate/package.json | 2 +- .../delegate/src/defaultMergedResolver.ts | 2 +- packages/delegate/src/delegateToSchema.ts | 48 +- .../delegate/src/finalizeGatewayRequest.ts | 7 +- packages/delegate/src/memoize.ts | 69 -- packages/delegate/src/mergeFields.ts | 87 +-- .../delegate/src/prepareGatewayDocument.ts | 136 +--- packages/delegate/src/resolveExternalValue.ts | 9 +- packages/delegate/src/types.ts | 9 +- .../delegate/tests/batchExecution.test.ts | 20 +- packages/delegate/tests/errors.test.ts | 4 +- .../tests/finalizeGatewayRequest.test.ts | 2 +- packages/graphql-tag-pluck/package.json | 2 +- packages/graphql-tools/package.json | 2 +- packages/import/package.json | 2 +- packages/jest-transform/package.json | 2 +- packages/links/package.json | 2 +- packages/links/tests/upload.test.ts | 2 +- packages/load-files/package.json | 2 +- packages/load/package.json | 2 +- packages/load/src/load-typedefs/parse.ts | 2 - .../schema/schema-from-typedefs.spec.ts | 2 +- packages/loaders/apollo-engine/package.json | 2 +- packages/loaders/code-file/package.json | 2 +- packages/loaders/git/package.json | 2 +- packages/loaders/github/package.json | 2 +- packages/loaders/graphql-file/package.json | 2 +- packages/loaders/json-file/package.json | 2 +- packages/loaders/module/package.json | 2 +- packages/loaders/prisma/package.json | 2 +- packages/loaders/url/package.json | 2 +- packages/merge/package.json | 2 +- packages/merge/tests/merge-typedefs.spec.ts | 2 +- packages/mock/package.json | 2 +- .../mock/tests/mocking-compatibility.spec.ts | 7 +- packages/mock/tests/store.spec.ts | 4 +- packages/node-require/package.json | 2 +- .../node-require/test/node-require.spec.ts | 4 +- packages/optimize/package.json | 2 +- .../remove-description.spec.ts.snap | 3 +- .../optimize/tests/remove-description.spec.ts | 2 +- .../relay-operation-optimizer/package.json | 2 +- packages/resolvers-composition/package.json | 2 +- .../tests/resolvers-composition.spec.ts | 2 +- packages/schema/package.json | 2 +- packages/schema/src/addResolversToSchema.ts | 24 +- packages/schema/tests/merge-schemas.spec.ts | 2 +- packages/schema/tests/schemaGenerator.test.ts | 14 +- packages/stitch/package.json | 2 +- .../stitch/src/createDelegationPlanBuilder.ts | 30 +- .../stitch/src/createMergedTypeResolver.ts | 10 +- .../stitch/src/getFieldsNotInSubschema.ts | 47 +- packages/stitch/src/memoize.ts | 70 -- packages/stitch/src/stitchingInfo.ts | 33 +- packages/stitch/src/typeCandidates.ts | 6 +- .../tests/alternateStitchSchemas.test.ts | 6 +- packages/stitch/tests/errors.test.ts | 10 +- packages/stitch/tests/example.test.ts | 4 +- packages/stitch/tests/selectionSets.test.ts | 2 +- packages/stitch/tests/stitchSchemas.test.ts | 20 +- packages/stitch/tests/transforms.test.ts | 2 +- packages/stitch/tests/unknownType.test.ts | 2 +- packages/stitching-directives/package.json | 2 +- .../src/stitchingDirectives.ts | 8 +- .../stitchingDirectivesTransformer.test.ts | 42 +- .../stitchingDirectivesValidator.test.ts | 18 +- .../tests => testing}/fixtures/schemas.ts | 75 +- packages/testing/utils.ts | 22 +- packages/utils/package.json | 2 +- packages/utils/src/astFromType.ts | 5 +- packages/utils/src/collectFields.ts | 162 +++++ .../utils/src/create-schema-definition.ts | 44 -- packages/utils/src/declarations.d.ts | 3 - packages/utils/src/fix-schema-ast.ts | 26 - .../utils/src/get-user-types-from-schema.ts | 41 -- packages/utils/src/getArgumentValues.ts | 8 +- packages/utils/src/index.ts | 6 +- packages/utils/src/inspect.ts | 110 +++ packages/utils/src/memoize.ts | 202 +++++ .../utils/src/print-schema-with-directives.ts | 85 +-- packages/utils/src/rootTypes.ts | 58 +- packages/utils/src/visitResult.ts | 126 ++-- .../parse-graphql-sdl.spec.ts.snap | 6 +- packages/utils/tests/get-directives.spec.ts | 10 +- .../utils/tests/parse-graphql-sdl.spec.ts | 4 +- packages/utils/tests/prune.test.ts | 42 +- ...a.test.ts => valueMatchedCriteria.test.ts} | 0 packages/utils/tests/visitResult.test.ts | 8 +- packages/webpack-loader-runtime/package.json | 2 +- packages/webpack-loader/package.json | 2 +- packages/wrap/package.json | 2 +- .../transforms/TransformCompositeFields.ts | 5 +- .../transforms/TransformInputObjectFields.ts | 4 +- packages/wrap/tests/fixtures/schemas.ts | 687 ------------------ .../tests/makeRemoteExecutableSchema.test.ts | 14 +- .../wrap/tests/transformFilterTypes.test.ts | 2 +- .../wrap/tests/transformRenameTypes.test.ts | 2 +- website/docs/generate-schema.mdx | 2 +- website/docs/mocking.mdx | 12 +- website/docs/scalars.mdx | 2 +- website/docs/schema-directives.mdx | 2 +- .../stitch-directives-sdl.mdx | 2 +- .../stitch-schema-extensions.mdx | 10 +- 122 files changed, 1090 insertions(+), 1729 deletions(-) create mode 100644 .changeset/few-chefs-bow.md create mode 100644 .changeset/funny-cobras-speak.md create mode 100644 .changeset/mean-coats-laugh.md create mode 100644 .changeset/spotty-queens-sniff.md create mode 100644 .changeset/strange-poems-flow.md delete mode 100644 packages/batch-execute/src/memoize.ts delete mode 100644 packages/delegate/src/memoize.ts delete mode 100644 packages/stitch/src/memoize.ts rename packages/{stitch/tests => testing}/fixtures/schemas.ts (89%) create mode 100644 packages/utils/src/collectFields.ts delete mode 100644 packages/utils/src/create-schema-definition.ts delete mode 100644 packages/utils/src/fix-schema-ast.ts delete mode 100644 packages/utils/src/get-user-types-from-schema.ts create mode 100644 packages/utils/src/inspect.ts create mode 100644 packages/utils/src/memoize.ts rename packages/utils/tests/{valueMatchesCriteria.test.ts => valueMatchedCriteria.test.ts} (100%) delete mode 100644 packages/wrap/tests/fixtures/schemas.ts diff --git a/.changeset/config.json b/.changeset/config.json index 58aeffc5992..05450a29249 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -3,7 +3,7 @@ "changelog": "@changesets/cli/changelog", "commit": false, "linked": [], - "access": "restricted", + "access": "public", "baseBranch": "master", "updateInternalDependencies": "patch", "ignore": [ diff --git a/.changeset/few-chefs-bow.md b/.changeset/few-chefs-bow.md new file mode 100644 index 00000000000..d432b0c018e --- /dev/null +++ b/.changeset/few-chefs-bow.md @@ -0,0 +1,6 @@ +--- +'@graphql-tools/delegate': minor +'@graphql-tools/utils': minor +--- + +enhance(utils): copy inspect util from graphql-js diff --git a/.changeset/funny-cobras-speak.md b/.changeset/funny-cobras-speak.md new file mode 100644 index 00000000000..47e738c321d --- /dev/null +++ b/.changeset/funny-cobras-speak.md @@ -0,0 +1,36 @@ +--- +'@graphql-tools/batch-delegate': minor +'@graphql-tools/batch-execute': minor +'@graphql-tools/delegate': minor +'@graphql-tools/graphql-tag-pluck': minor +'graphql-tools': minor +'@graphql-tools/import': minor +'@graphql-tools/jest-transform': minor +'@graphql-tools/links': minor +'@graphql-tools/load': minor +'@graphql-tools/load-files': minor +'@graphql-tools/apollo-engine-loader': minor +'@graphql-tools/code-file-loader': minor +'@graphql-tools/git-loader': minor +'@graphql-tools/github-loader': minor +'@graphql-tools/graphql-file-loader': minor +'@graphql-tools/json-file-loader': minor +'@graphql-tools/module-loader': minor +'@graphql-tools/prisma-loader': minor +'@graphql-tools/url-loader': minor +'@graphql-tools/merge': minor +'@graphql-tools/mock': minor +'@graphql-tools/node-require': minor +'@graphql-tools/optimize': minor +'@graphql-tools/relay-operation-optimizer': minor +'@graphql-tools/resolvers-composition': minor +'@graphql-tools/schema': minor +'@graphql-tools/stitch': minor +'@graphql-tools/stitching-directives': minor +'@graphql-tools/utils': minor +'@graphql-tools/webpack-loader': minor +'@graphql-tools/webpack-loader-runtime': minor +'@graphql-tools/wrap': minor +--- + +feat: GraphQL v16 support diff --git a/.changeset/mean-coats-laugh.md b/.changeset/mean-coats-laugh.md new file mode 100644 index 00000000000..848108f7039 --- /dev/null +++ b/.changeset/mean-coats-laugh.md @@ -0,0 +1,9 @@ +--- +'@graphql-tools/batch-delegate': minor +'@graphql-tools/batch-execute': minor +'@graphql-tools/delegate': minor +'@graphql-tools/stitch': minor +'@graphql-tools/utils': minor +--- + +enhance(utils): move memoize functions to utils diff --git a/.changeset/spotty-queens-sniff.md b/.changeset/spotty-queens-sniff.md new file mode 100644 index 00000000000..5af87212fcb --- /dev/null +++ b/.changeset/spotty-queens-sniff.md @@ -0,0 +1,7 @@ +--- +'@graphql-tools/delegate': minor +'@graphql-tools/stitch': minor +'@graphql-tools/utils': minor +--- + +enhance(utils): copy collectFields from graphql-js@16 for backwards compat diff --git a/.changeset/strange-poems-flow.md b/.changeset/strange-poems-flow.md new file mode 100644 index 00000000000..9d6bf6563e7 --- /dev/null +++ b/.changeset/strange-poems-flow.md @@ -0,0 +1,5 @@ +--- +'@graphql-tools/utils': patch +--- + +enhance(utils): memoize root types utility functions diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d67b6c3910a..92a10900e93 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,7 +35,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - graphql_version: [14, 15] + graphql_version: + - 14 + - 15 + # - 16.0.0-rc.1 steps: - name: Checkout Master uses: actions/checkout@v2 @@ -63,7 +66,10 @@ jobs: matrix: os: [ubuntu-latest] # remove windows to speed up the tests node_version: [12, 16] - graphql_version: [14, 15] + graphql_version: + - 14 + - 15 + # - 16.0.0-rc.1 include: - node_version: 14 os: windows-latest @@ -98,33 +104,22 @@ jobs: env: CI: true test_esm: - name: ESM Test on Node ${{matrix.node_version}} (${{matrix.os}}) and GraphQL v${{matrix.graphql_version}} - runs-on: ${{matrix.os}} - strategy: - matrix: - os: [ubuntu-latest] # remove windows to speed up the tests - node_version: [12, 16] - graphql_version: [14, 15] - include: - - node_version: 14 - os: windows-latest - graphql_version: 15 + name: ESM Test + runs-on: ubuntu-latest steps: - name: Checkout Master uses: actions/checkout@v2 - name: Use Node uses: actions/setup-node@master with: - node-version: ${{ matrix.node_version }} + node-version: 16 - name: Cache Yarn uses: actions/cache@v2 with: path: '**/node_modules' - key: ${{ runner.os }}-${{matrix.node_version}}-${{matrix.graphql_version}}-yarn-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-16-15-yarn-${{ hashFiles('yarn.lock') }} restore-keys: | - ${{ runner.os }}-${{matrix.node_version}}-${{matrix.graphql_version}}-yarn - - name: Use GraphQL v${{matrix.graphql_version}} - run: node ./scripts/match-graphql.js ${{matrix.graphql_version}} + ${{ runner.os }}-16-15-yarn - name: Install Dependencies using Yarn run: yarn install --ignore-engines && git checkout yarn.lock - name: Build Packages diff --git a/.vscode/settings.json b/.vscode/settings.json index f3f1f765ec0..1cc0bba7e3a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,11 +12,13 @@ "files.exclude": { "**/.git": true, "**/.DS_Store": true, - "node_modules": false, + "**/node_modules": true, "test-lib": true, "lib": true, "coverage": true, - "npm": true + "npm": true, + "**/dist": true, + "**/dist-es5": true }, "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/README.md b/README.md index 1c07f3dd7ea..e836dcdc11f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ When using `graphql-tools`, you describe the schema as a GraphQL type language s ```js -const typeDefs = ` +const typeDefs = /* GraphQL */` type Author { id: ID! # the ! means that every author object _must_ have an id firstName: String diff --git a/package.json b/package.json index f2f0f0e0dd4..0bfda79acc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "name": "graphql-tools-monorepo", - "version": "6.2.3", "description": "Useful tools to create and manipulate GraphQL schemas.", "private": true, "scripts": { @@ -39,9 +38,6 @@ "url": "https://github.com/ardatan/graphql-tools/issues" }, "homepage": "https://github.com/ardatan/graphql-tools#readme", - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" - }, "devDependencies": { "@babel/core": "7.15.0", "@babel/plugin-proposal-class-properties": "7.14.5", diff --git a/packages/batch-delegate/package.json b/packages/batch-delegate/package.json index 1b2f550d479..3686bd69f1c 100644 --- a/packages/batch-delegate/package.json +++ b/packages/batch-delegate/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/batch-delegate/src/getLoader.ts b/packages/batch-delegate/src/getLoader.ts index 613841cead7..b3b910b71df 100644 --- a/packages/batch-delegate/src/getLoader.ts +++ b/packages/batch-delegate/src/getLoader.ts @@ -3,15 +3,10 @@ import { getNamedType, GraphQLOutputType, GraphQLList, GraphQLSchema, FieldNode import DataLoader from 'dataloader'; import { delegateToSchema, SubschemaConfig } from '@graphql-tools/delegate'; -import { relocatedError } from '@graphql-tools/utils'; +import { memoize2, relocatedError } from '@graphql-tools/utils'; import { BatchDelegateOptions } from './types'; -const cache1 = new WeakMap< - ReadonlyArray, - WeakMap, Map>> ->(); - function createBatchFn(options: BatchDelegateOptions) { const argsFromKeys = options.argsFromKeys ?? ((keys: ReadonlyArray) => ({ ids: keys })); const fieldName = options.fieldName ?? options.info.fieldName; @@ -58,10 +53,18 @@ function defaultCacheKeyFn(key: any) { return key; } +const getLoadersMap = memoize2(function getLoadersMap( + _fieldNodes: readonly FieldNode[], + _schema: GraphQLSchema | SubschemaConfig +) { + return new Map>(); +}); + export function getLoader(options: BatchDelegateOptions): DataLoader { const fieldName = options.fieldName ?? options.info.fieldName; + const loaders = getLoadersMap(options.info.fieldNodes, options.schema); - let cache2 = cache1.get(options.info.fieldNodes); + let loader = loaders.get(fieldName); // Prevents the keys to be passed with the same structure const dataLoaderOptions: DataLoader.Options = { @@ -69,30 +72,6 @@ export function getLoader(options: BatchDelegateOptions ...options.dataLoaderOptions, }; - if (cache2 === undefined) { - cache2 = new WeakMap(); - cache1.set(options.info.fieldNodes, cache2); - const loaders = new Map(); - cache2.set(options.schema, loaders); - const batchFn = createBatchFn(options); - const loader = new DataLoader(batchFn, dataLoaderOptions); - loaders.set(fieldName, loader); - return loader; - } - - let loaders = cache2.get(options.schema); - - if (loaders === undefined) { - loaders = new Map(); - cache2.set(options.schema, loaders); - const batchFn = createBatchFn(options); - const loader = new DataLoader(batchFn, dataLoaderOptions); - loaders.set(fieldName, loader); - return loader; - } - - let loader = loaders.get(fieldName); - if (loader === undefined) { const batchFn = createBatchFn(options); loader = new DataLoader(batchFn, dataLoaderOptions); diff --git a/packages/batch-delegate/src/types.ts b/packages/batch-delegate/src/types.ts index 8c30f74ecae..c9601b5541a 100644 --- a/packages/batch-delegate/src/types.ts +++ b/packages/batch-delegate/src/types.ts @@ -1,14 +1,6 @@ -import { FieldNode, GraphQLSchema } from 'graphql'; - import DataLoader from 'dataloader'; -import { IDelegateToSchemaOptions, SubschemaConfig } from '@graphql-tools/delegate'; - -// TODO: remove in next major release -export type DataLoaderCache = WeakMap< - ReadonlyArray, - WeakMap> ->; +import { IDelegateToSchemaOptions } from '@graphql-tools/delegate'; export type BatchDelegateFn, K = any> = ( batchDelegateOptions: BatchDelegateOptions diff --git a/packages/batch-delegate/tests/basic.example.test.ts b/packages/batch-delegate/tests/basic.example.test.ts index 4de71fe6bc1..e64acd2c66b 100644 --- a/packages/batch-delegate/tests/basic.example.test.ts +++ b/packages/batch-delegate/tests/basic.example.test.ts @@ -46,7 +46,7 @@ describe('batch delegation within basic stitching example', () => { } }); - const linkTypeDefs = ` + const linkTypeDefs = /* GraphQL */` extend type Chirp { chirpedAtUser: User } @@ -139,7 +139,7 @@ describe('batch delegation within basic stitching example', () => { } }); - const linkTypeDefs = ` + const linkTypeDefs = /* GraphQL */` extend type User { posts: [Post]! } diff --git a/packages/batch-delegate/tests/withTransforms.test.ts b/packages/batch-delegate/tests/withTransforms.test.ts index f4a39fa651a..dfea2463c06 100644 --- a/packages/batch-delegate/tests/withTransforms.test.ts +++ b/packages/batch-delegate/tests/withTransforms.test.ts @@ -57,7 +57,7 @@ describe('works with complex transforms', () => { } }); - const linkTypeDefs = ` + const linkTypeDefs = /* GraphQL */` extend type User { books: [Book!]! } diff --git a/packages/batch-execute/package.json b/packages/batch-execute/package.json index 0c080ed5ffc..20060fe0858 100644 --- a/packages/batch-execute/package.json +++ b/packages/batch-execute/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/batch-execute/src/createBatchingExecutor.ts b/packages/batch-execute/src/createBatchingExecutor.ts index 6897612cc39..87401414a11 100644 --- a/packages/batch-execute/src/createBatchingExecutor.ts +++ b/packages/batch-execute/src/createBatchingExecutor.ts @@ -13,8 +13,9 @@ export function createBatchingExecutor( request: ExecutionRequest ) => Record = defaultExtensionsReducer ): Executor { - const loader = new DataLoader(createLoadFn(executor, extensionsReducer), dataLoaderOptions); - return (request: ExecutionRequest) => { + const loadFn = createLoadFn(executor, extensionsReducer); + const loader = new DataLoader(loadFn, dataLoaderOptions); + return function batchingExecutor(request: ExecutionRequest) { return request.operationType === 'subscription' ? executor(request) : loader.load(request); }; } diff --git a/packages/batch-execute/src/getBatchingExecutor.ts b/packages/batch-execute/src/getBatchingExecutor.ts index 4bfda8d6ec7..a2f079e1d24 100644 --- a/packages/batch-execute/src/getBatchingExecutor.ts +++ b/packages/batch-execute/src/getBatchingExecutor.ts @@ -1,8 +1,7 @@ import DataLoader from 'dataloader'; -import { ExecutionRequest, Executor } from '@graphql-tools/utils'; +import { ExecutionRequest, Executor, memoize2of4 } from '@graphql-tools/utils'; import { createBatchingExecutor } from './createBatchingExecutor'; -import { memoize2of4 } from './memoize'; export const getBatchingExecutor = memoize2of4(function getBatchingExecutor( _context: Record, diff --git a/packages/batch-execute/src/memoize.ts b/packages/batch-execute/src/memoize.ts deleted file mode 100644 index 9b39529420b..00000000000 --- a/packages/batch-execute/src/memoize.ts +++ /dev/null @@ -1,22 +0,0 @@ -const memoize2of4cache: WeakMap, WeakMap, any>> = new WeakMap(); -export function memoize2of4 any>(fn: F): F { - return function memoized(a1: any, a2: any, a3: any, a4: any): any { - let cache2 = memoize2of4cache.get(a1); - if (!cache2) { - cache2 = new WeakMap(); - memoize2of4cache.set(a1, cache2); - const newValue = fn(a1, a2, a3, a4); - cache2.set(a2, newValue); - return newValue; - } - - const cachedValue = cache2.get(a2); - if (cachedValue === undefined) { - const newValue = fn(a1, a2, a3, a4); - cache2.set(a2, newValue); - return newValue; - } - - return cachedValue; - } as F; -} diff --git a/packages/delegate/package.json b/packages/delegate/package.json index d41f9398110..cfb8b7dbcb2 100644 --- a/packages/delegate/package.json +++ b/packages/delegate/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/delegate/src/defaultMergedResolver.ts b/packages/delegate/src/defaultMergedResolver.ts index da5559440d0..d80998d7ead 100644 --- a/packages/delegate/src/defaultMergedResolver.ts +++ b/packages/delegate/src/defaultMergedResolver.ts @@ -25,7 +25,7 @@ export function defaultMergedResolver( const responseKey = getResponseKeyFromInfo(info); // check to see if parent is not a proxied result, i.e. if parent resolver was manually overwritten - // See https://github.com/apollographql/graphql-tools/issues/967 + // See https://github.com/ardatan/graphql-tools/issues/967 if (!isExternalObject(parent)) { return defaultFieldResolver(parent, args, context, info); } diff --git a/packages/delegate/src/delegateToSchema.ts b/packages/delegate/src/delegateToSchema.ts index 300a9bda2da..55a7c446732 100644 --- a/packages/delegate/src/delegateToSchema.ts +++ b/packages/delegate/src/delegateToSchema.ts @@ -24,6 +24,7 @@ import { AggregateError, isAsyncIterable, getDefinedRootType, + memoize1, } from '@graphql-tools/utils'; import { @@ -206,33 +207,26 @@ function getExecutor(delegationContext: DelegationContext): return executor; } -const defaultExecutorCache = new WeakMap(); - -export function createDefaultExecutor(schema: GraphQLSchema): Executor { - let defaultExecutor = defaultExecutorCache.get(schema); - if (!defaultExecutor) { - defaultExecutor = function defaultExecutor({ +export const createDefaultExecutor = memoize1(function createDefaultExecutor(schema: GraphQLSchema): Executor { + return function defaultExecutor({ + document, + context, + variables, + rootValue, + operationName, + operationType, + }: ExecutionRequest) { + const executionArgs: ExecutionArgs = { + schema, document, - context, - variables, + contextValue: context, + variableValues: variables, rootValue, operationName, - operationType, - }: ExecutionRequest) { - const executionArgs: ExecutionArgs = { - schema, - document, - contextValue: context, - variableValues: variables, - rootValue, - operationName, - }; - if (operationType === 'subscription') { - return subscribe(executionArgs); - } - return execute(executionArgs); - } as Executor; - defaultExecutorCache.set(schema, defaultExecutor); - } - return defaultExecutor; -} + }; + if (operationType === 'subscription') { + return subscribe(executionArgs); + } + return execute(executionArgs); + } as Executor; +}); diff --git a/packages/delegate/src/finalizeGatewayRequest.ts b/packages/delegate/src/finalizeGatewayRequest.ts index 50c2ad1217e..7a5f8f8b57c 100644 --- a/packages/delegate/src/finalizeGatewayRequest.ts +++ b/packages/delegate/src/finalizeGatewayRequest.ts @@ -17,6 +17,7 @@ import { TypeInfo, TypeNameMetaFieldDef, VariableDefinitionNode, + versionInfo as graphqlVersionInfo, visit, VisitorKeyMap, visitWithTypeInfo, @@ -29,6 +30,7 @@ import { updateArgument, createVariableNameGenerator, implementsAbstractType, + inspect, } from '@graphql-tools/utils'; import { DelegationContext } from './types'; @@ -313,7 +315,8 @@ function finalizeSelectionSet( const usedFragments: Array = []; const usedVariables: Array = []; - const typeInfo = new TypeInfo(schema, undefined, type); + const typeInfo = + graphqlVersionInfo.major < 16 ? new TypeInfo(schema, undefined, type as any) : new TypeInfo(schema, type as any); const filteredSelectionSet = visit( selectionSet, visitWithTypeInfo(typeInfo, { @@ -351,7 +354,7 @@ function finalizeSelectionSet( leave: node => { const type = typeInfo.getType(); if (type == null) { - throw new Error(`No type was found for field node ${node}.`); + throw new Error(`No type was found for field node ${inspect(node)}.`); } const namedType = getNamedType(type); if (!schema.getType(namedType.name) == null) { diff --git a/packages/delegate/src/memoize.ts b/packages/delegate/src/memoize.ts deleted file mode 100644 index 271b6511fc8..00000000000 --- a/packages/delegate/src/memoize.ts +++ /dev/null @@ -1,69 +0,0 @@ -const memoize2cache: WeakMap, WeakMap, any>> = new WeakMap(); -export function memoize2 any>(fn: F): F { - return function memoized(a1: any, a2: any): any { - let cache2 = memoize2cache.get(a1); - if (!cache2) { - cache2 = new WeakMap(); - memoize2cache.set(a1, cache2); - const newValue = fn(a1, a2); - cache2.set(a2, newValue); - return newValue; - } - - const cachedValue = cache2.get(a2); - if (cachedValue === undefined) { - const newValue = fn(a1, a2); - cache2.set(a2, newValue); - return newValue; - } - - return cachedValue; - } as F; -} - -const memoize4Cache: WeakMap, WeakMap, any>> = new WeakMap(); -export function memoize4 any>(fn: F): F { - return function memoized(a1: any, a2: any, a3: any, a4: any) { - let cache2 = memoize4Cache.get(a1); - if (!cache2) { - cache2 = new WeakMap(); - memoize4Cache.set(a1, cache2); - const cache3 = new WeakMap(); - cache2.set(a2, cache3); - const cache4 = new WeakMap(); - cache3.set(a3, cache4); - const newValue = fn(a1, a2, a3, a4); - cache4.set(a4, newValue); - return newValue; - } - - let cache3 = cache2.get(a2); - if (!cache3) { - cache3 = new WeakMap(); - cache2.set(a2, cache3); - const cache4 = new WeakMap(); - cache3.set(a3, cache4); - const newValue = fn(a1, a2, a3, a4); - cache4.set(a4, newValue); - return newValue; - } - - const cache4 = cache3.get(a3); - if (!cache4) { - const cache4 = new WeakMap(); - cache3.set(a3, cache4); - const newValue = fn(a1, a2, a3, a4); - cache4.set(a4, newValue); - return newValue; - } - - const cachedValue = cache4.get(a4); - if (cachedValue === undefined) { - const newValue = fn(a1, a2, a3, a4); - cache4.set(a4, newValue); - return newValue; - } - - return cachedValue; - } as F; -} diff --git a/packages/delegate/src/mergeFields.ts b/packages/delegate/src/mergeFields.ts index d288c4e60b6..aed123555e7 100644 --- a/packages/delegate/src/mergeFields.ts +++ b/packages/delegate/src/mergeFields.ts @@ -6,16 +6,14 @@ import { GraphQLError, locatedError, GraphQLSchema, + FieldNode, } from 'graphql'; -import { collectFields, ExecutionContext } from 'graphql/execution/execute.js'; +import { collectFields, relocatedError } from '@graphql-tools/utils'; -import { relocatedError } from '@graphql-tools/utils'; - -import { ExternalObject, MergedTypeInfo, StitchingInfo, SubschemaConfig } from './types'; +import { ExternalObject, MergedTypeInfo, SubschemaConfig } from './types'; import { FIELD_SUBSCHEMA_MAP_SYMBOL, OBJECT_SUBSCHEMA_SYMBOL, UNPATHED_ERRORS_SYMBOL } from './symbols'; import { Subschema } from './Subschema'; -import { memoize4 } from './memoize'; export function isExternalObject(data: any): data is ExternalObject { return data[UNPATHED_ERRORS_SYMBOL] !== undefined; @@ -24,11 +22,12 @@ export function isExternalObject(data: any): data is ExternalObject { export function annotateExternalObject( object: any, errors: Array, - subschema: GraphQLSchema | SubschemaConfig | undefined + subschema: GraphQLSchema | SubschemaConfig | undefined, + subschemaMap: Record>> ): ExternalObject { Object.defineProperties(object, { [OBJECT_SUBSCHEMA_SYMBOL]: { value: subschema }, - [FIELD_SUBSCHEMA_MAP_SYMBOL]: { value: Object.create(null) }, + [FIELD_SUBSCHEMA_MAP_SYMBOL]: { value: subschemaMap }, [UNPATHED_ERRORS_SYMBOL]: { value: errors }, }); return object; @@ -42,18 +41,26 @@ export function getUnpathedErrors(object: ExternalObject): Array { return object[UNPATHED_ERRORS_SYMBOL]; } +const EMPTY_ARRAY: any[] = []; +const EMPTY_OBJECT = Object.create(null); + export async function mergeFields( mergedTypeInfo: MergedTypeInfo, object: any, sourceSubschema: Subschema, - targetSubschemas: Array>, context: any, info: GraphQLResolveInfo ): Promise { - const delegationMaps = buildDelegationPlanFromInfo(info, mergedTypeInfo, sourceSubschema, targetSubschemas); + const delegationMaps = mergedTypeInfo.delegationPlanBuilder( + info.schema, + sourceSubschema, + info.variableValues != null && Object.keys(info.variableValues).length > 0 ? info.variableValues : EMPTY_OBJECT, + info.fragments != null && Object.keys(info.fragments).length > 0 ? info.fragments : EMPTY_OBJECT, + info.fieldNodes?.length > 0 ? (info.fieldNodes as FieldNode[]) : EMPTY_ARRAY + ); for (const delegationMap of delegationMaps) { - object = await executeDelegationStage(mergedTypeInfo, delegationMap, object, context, info); + await executeDelegationStage(mergedTypeInfo, delegationMap, object, context, info); } return object; @@ -65,16 +72,16 @@ async function executeDelegationStage( object: ExternalObject, context: any, info: GraphQLResolveInfo -): Promise { - const combinedErrors = object[UNPATHED_ERRORS_SYMBOL] ?? []; +): Promise { + const combinedErrors = object[UNPATHED_ERRORS_SYMBOL]; const path = responsePathAsArray(info.path); - const newFieldSubschemaMap = object[FIELD_SUBSCHEMA_MAP_SYMBOL] ?? Object.create(null); + const combinedFieldSubschemaMap = object[FIELD_SUBSCHEMA_MAP_SYMBOL]; const type = info.schema.getType(object.__typename) as GraphQLObjectType; - const results = await Promise.all( + await Promise.all( [...delegationMap.entries()].map(async ([s, selectionSet]) => { const resolver = mergedTypeInfo.resolvers.get(s); if (resolver) { @@ -84,25 +91,15 @@ async function executeDelegationStage( } catch (error: any) { source = error; } - if (source instanceof Error || source === null) { - const fieldNodes = collectFields( - { - schema: info.schema, - variableValues: {}, - fragments: {}, - } as ExecutionContext, - type, - selectionSet, - Object.create(null), - Object.create(null) - ); + if (source instanceof Error || source == null) { + const fieldNodeResponseKeyMap = collectFields(info.schema, {}, {}, type, selectionSet, new Map(), new Set()); const nullResult = {}; - for (const responseKey in fieldNodes) { + for (const [responseKey, fieldNodes] of fieldNodeResponseKeyMap) { const combinedPath = [...path, responseKey]; if (source instanceof GraphQLError) { nullResult[responseKey] = relocatedError(source, combinedPath); } else if (source instanceof Error) { - nullResult[responseKey] = locatedError(source, fieldNodes[responseKey], combinedPath); + nullResult[responseKey] = locatedError(source, fieldNodes, combinedPath); } else { nullResult[responseKey] = null; } @@ -117,40 +114,10 @@ async function executeDelegationStage( const objectSubschema = source[OBJECT_SUBSCHEMA_SYMBOL]; const fieldSubschemaMap = source[FIELD_SUBSCHEMA_MAP_SYMBOL]; for (const responseKey in source) { - newFieldSubschemaMap[responseKey] = fieldSubschemaMap?.[responseKey] ?? objectSubschema; + object[responseKey] = source[responseKey]; + combinedFieldSubschemaMap[responseKey] = fieldSubschemaMap?.[responseKey] ?? objectSubschema; } - - return source; } }) ); - - const combinedResult: ExternalObject = Object.assign({}, object, ...results); - - combinedResult[FIELD_SUBSCHEMA_MAP_SYMBOL] = newFieldSubschemaMap; - combinedResult[OBJECT_SUBSCHEMA_SYMBOL] = object[OBJECT_SUBSCHEMA_SYMBOL]; - - combinedResult[UNPATHED_ERRORS_SYMBOL] = combinedErrors; - - return combinedResult; } - -const buildDelegationPlanFromInfo = memoize4(function buildDelegationPlanFromInfo( - info: GraphQLResolveInfo, - mergedTypeInfo: MergedTypeInfo, - sourceSubschema: Subschema, - targetSubschemas: Array> -): Array> { - const { schema, fragments, variableValues, fieldNodes } = info; - - return mergedTypeInfo.delegationPlanBuilder( - schema, - sourceSubschema, - fieldNodes, - fragments, - variableValues, - schema.extensions?.['stitchingInfo'] as StitchingInfo, - mergedTypeInfo, - targetSubschemas - ); -}); diff --git a/packages/delegate/src/prepareGatewayDocument.ts b/packages/delegate/src/prepareGatewayDocument.ts index a063bbc26c4..3601e3d55e5 100644 --- a/packages/delegate/src/prepareGatewayDocument.ts +++ b/packages/delegate/src/prepareGatewayDocument.ts @@ -16,11 +16,12 @@ import { GraphQLOutputType, isObjectType, FieldNode, + VisitorKeyMap, + ASTKindToNode, } from 'graphql'; -import { implementsAbstractType, getRootTypeNames } from '@graphql-tools/utils'; +import { implementsAbstractType, getRootTypeNames, memoize2 } from '@graphql-tools/utils'; -import { memoize2 } from './memoize'; import { getDocumentMetadata } from './getDocumentMetadata'; import { StitchingInfo } from './types'; @@ -56,6 +57,15 @@ export function prepareGatewayDocument( definitions: [...operations, ...fragments, ...expandedFragments], }; + const visitorKeyMap: Partial> = { + Document: ['definitions'], + OperationDefinition: ['selectionSet'], + SelectionSet: ['selections'], + Field: ['selectionSet'], + InlineFragment: ['selectionSet'], + FragmentDefinition: ['selectionSet'], + }; + return visit( expandedDocument, visitWithTypeInfo(typeInfo, { @@ -76,61 +86,7 @@ export function prepareGatewayDocument( // visitorKeys argument usage a la https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js // empty keys cannot be removed only because of typescript errors // will hopefully be fixed in future version of graphql-js to be optional - { - Name: [], - - Document: ['definitions'], - OperationDefinition: ['selectionSet'], - VariableDefinition: [], - Variable: [], - SelectionSet: ['selections'], - Field: ['selectionSet'], - Argument: [], - - FragmentSpread: [], - InlineFragment: ['selectionSet'], - FragmentDefinition: ['selectionSet'], - - IntValue: [], - FloatValue: [], - StringValue: [], - BooleanValue: [], - NullValue: [], - EnumValue: [], - ListValue: [], - ObjectValue: [], - ObjectField: [], - - Directive: [], - - NamedType: [], - ListType: [], - NonNullType: [], - - SchemaDefinition: [], - OperationTypeDefinition: [], - - ScalarTypeDefinition: [], - ObjectTypeDefinition: [], - FieldDefinition: [], - InputValueDefinition: [], - InterfaceTypeDefinition: [], - UnionTypeDefinition: [], - EnumTypeDefinition: [], - EnumValueDefinition: [], - InputObjectTypeDefinition: [], - - DirectiveDefinition: [], - - SchemaExtension: [], - - ScalarTypeExtension: [], - ObjectTypeExtension: [], - InterfaceTypeExtension: [], - UnionTypeExtension: [], - EnumTypeExtension: [], - InputObjectTypeExtension: [], - } + visitorKeyMap as any ); } @@ -435,6 +391,16 @@ function wrapConcreteTypes( const rootTypeNames = getRootTypeNames(targetSchema); const typeInfo = new TypeInfo(targetSchema); + + const visitorKeys: Partial> = { + Document: ['definitions'], + OperationDefinition: ['selectionSet'], + SelectionSet: ['selections'], + + InlineFragment: ['selectionSet'], + FragmentDefinition: ['selectionSet'], + }; + return visit( document, visitWithTypeInfo(typeInfo, { @@ -472,60 +438,6 @@ function wrapConcreteTypes( // visitorKeys argument usage a la https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js // empty keys cannot be removed only because of typescript errors // will hopefully be fixed in future version of graphql-js to be optional - { - Name: [], - - Document: ['definitions'], - OperationDefinition: ['selectionSet'], - VariableDefinition: [], - Variable: [], - SelectionSet: ['selections'], - Field: [], - Argument: [], - - FragmentSpread: [], - InlineFragment: ['selectionSet'], - FragmentDefinition: ['selectionSet'], - - IntValue: [], - FloatValue: [], - StringValue: [], - BooleanValue: [], - NullValue: [], - EnumValue: [], - ListValue: [], - ObjectValue: [], - ObjectField: [], - - Directive: [], - - NamedType: [], - ListType: [], - NonNullType: [], - - SchemaDefinition: [], - OperationTypeDefinition: [], - - ScalarTypeDefinition: [], - ObjectTypeDefinition: [], - FieldDefinition: [], - InputValueDefinition: [], - InterfaceTypeDefinition: [], - UnionTypeDefinition: [], - EnumTypeDefinition: [], - EnumValueDefinition: [], - InputObjectTypeDefinition: [], - - DirectiveDefinition: [], - - SchemaExtension: [], - - ScalarTypeExtension: [], - ObjectTypeExtension: [], - InterfaceTypeExtension: [], - UnionTypeExtension: [], - EnumTypeExtension: [], - InputObjectTypeExtension: [], - } + visitorKeys as any ); } diff --git a/packages/delegate/src/resolveExternalValue.ts b/packages/delegate/src/resolveExternalValue.ts index 710e07c0d1e..5139121ad0f 100644 --- a/packages/delegate/src/resolveExternalValue.ts +++ b/packages/delegate/src/resolveExternalValue.ts @@ -2,7 +2,6 @@ import { GraphQLResolveInfo, getNullableType, isCompositeType, - isLeafType, isListType, GraphQLError, GraphQLSchema, @@ -60,14 +59,14 @@ function resolveExternalObject( // if we have already resolved this object, for example, when the identical object appears twice // in a list, see https://github.com/ardatan/graphql-tools/issues/2304 if (!isExternalObject(object)) { - annotateExternalObject(object, unpathedErrors, subschema); + annotateExternalObject(object, unpathedErrors, subschema, Object.create(null)); } if (skipTypeMerging || info == null) { return object; } - const stitchingInfo: Maybe = info.schema.extensions?.['stitchingInfo']; + const stitchingInfo = info.schema.extensions?.['stitchingInfo'] as Maybe; if (stitchingInfo == null) { return object; @@ -101,7 +100,7 @@ function resolveExternalObject( return object; } - return mergeFields(mergedTypeInfo, object, subschema as Subschema, targetSubschemas, context, info); + return mergeFields(mergedTypeInfo, object, subschema as Subschema, context, info); } function resolveExternalList( @@ -143,7 +142,7 @@ function resolveExternalListMember( return reportUnpathedErrorsViaNull(unpathedErrors); } - if (isLeafType(type)) { + if ('parseValue' in type) { return type.parseValue(listMember); } else if (isCompositeType(type)) { return resolveExternalObject(type, listMember, unpathedErrors, subschema, context, info, skipTypeMerging); diff --git a/packages/delegate/src/types.ts b/packages/delegate/src/types.ts index 7e769dc43b3..7899ea8ee7c 100644 --- a/packages/delegate/src/types.ts +++ b/packages/delegate/src/types.ts @@ -114,12 +114,9 @@ export interface ICreateRequest { export type DelegationPlanBuilder = ( schema: GraphQLSchema, sourceSubschema: Subschema, - fieldNodes: ReadonlyArray, - fragments?: Record, - variableValues?: Record, - stitchingInfo?: StitchingInfo, - mergedTypeInfo?: MergedTypeInfo, - targetSubschemas?: Array + variableValues: Record, + fragments: Record, + fieldNodes: FieldNode[] ) => Array>; export interface MergedTypeInfo> { diff --git a/packages/delegate/tests/batchExecution.test.ts b/packages/delegate/tests/batchExecution.test.ts index 6706f15a615..9ce31a51236 100644 --- a/packages/delegate/tests/batchExecution.test.ts +++ b/packages/delegate/tests/batchExecution.test.ts @@ -1,10 +1,10 @@ -import { graphql, execute, ExecutionResult } from 'graphql'; +import { graphql } from 'graphql'; import { makeExecutableSchema } from '@graphql-tools/schema'; -import { delegateToSchema, SubschemaConfig } from '../src'; +import { createDefaultExecutor, delegateToSchema, SubschemaConfig } from '../src'; import { stitchSchemas } from '@graphql-tools/stitch'; import { FilterObjectFields } from '@graphql-tools/wrap'; -import { ExecutionRequest, Executor, SyncExecutor } from '@graphql-tools/utils'; +import { Executor } from '@graphql-tools/utils'; describe('batch execution', () => { it('should batch', async () => { @@ -25,13 +25,14 @@ describe('batch execution', () => { let executions = 0; + const defaultExecutor = createDefaultExecutor(innerSchema); const innerSubschemaConfig: SubschemaConfig = { schema: innerSchema, batch: true, - executor: ((request: ExecutionRequest): ExecutionResult => { + executor: request => { executions++; - return execute({ schema: innerSchema, document: request.document, contextValue: request.context, variableValues: request.variables}) as ExecutionResult; - }) as SyncExecutor + return defaultExecutor(request); + } } const outerSchema = makeExecutableSchema({ @@ -105,10 +106,11 @@ describe('batch execution', () => { let executions = 0; - const executor = ((request: ExecutionRequest): ExecutionResult => { + const defaultExecutor = createDefaultExecutor(innerSchemaA); + const executor: Executor = request => { executions++; - return execute({ schema: innerSchemaA, document: request.document, contextValue: request.context, variableValues: request.variables }) as ExecutionResult; - }) as Executor; + return defaultExecutor(request); + }; const innerSubschemaConfigA: Array = [{ schema: innerSchemaA, diff --git a/packages/delegate/tests/errors.test.ts b/packages/delegate/tests/errors.test.ts index 0f4253ad94b..1fd6edfa88a 100644 --- a/packages/delegate/tests/errors.test.ts +++ b/packages/delegate/tests/errors.test.ts @@ -110,7 +110,7 @@ describe('Errors', () => { // see https://github.com/ardatan/graphql-tools/issues/1641 describe('it proxies errors with invalid paths', () => { test('it works with bare delegation', async () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Object { field1: String field2: String @@ -175,7 +175,7 @@ describe('Errors', () => { }); test('it works with stitched schemas', async () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Object { field1: String field2: String diff --git a/packages/delegate/tests/finalizeGatewayRequest.test.ts b/packages/delegate/tests/finalizeGatewayRequest.test.ts index e7a0898d8e3..e312bc6699a 100644 --- a/packages/delegate/tests/finalizeGatewayRequest.test.ts +++ b/packages/delegate/tests/finalizeGatewayRequest.test.ts @@ -1,6 +1,6 @@ import { print, parse } from 'graphql'; import { DelegationContext } from '@graphql-tools/delegate'; -import { bookingSchema, propertySchema } from '../../wrap/tests/fixtures/schemas'; +import { bookingSchema, propertySchema } from '../../testing/fixtures/schemas'; import { finalizeGatewayRequest } from '../src/finalizeGatewayRequest'; describe('finalizeGatewayRequest', () => { diff --git a/packages/graphql-tag-pluck/package.json b/packages/graphql-tag-pluck/package.json index 665b16def1a..621f80718fd 100644 --- a/packages/graphql-tag-pluck/package.json +++ b/packages/graphql-tag-pluck/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { "@babel/parser": "7.15.3", diff --git a/packages/graphql-tools/package.json b/packages/graphql-tools/package.json index c75041632ca..6bf43555473 100644 --- a/packages/graphql-tools/package.json +++ b/packages/graphql-tools/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "optionalDependencies": { "@apollo/client": "~3.2.5 || ~3.3.0 || ~3.4.0" diff --git a/packages/import/package.json b/packages/import/package.json index 8fdcda48f04..f2f625c2afb 100644 --- a/packages/import/package.json +++ b/packages/import/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "publishConfig": { "access": "public", diff --git a/packages/jest-transform/package.json b/packages/jest-transform/package.json index 767b71307f3..2f9eaf70704 100644 --- a/packages/jest-transform/package.json +++ b/packages/jest-transform/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/links/package.json b/packages/links/package.json index e4b5881841f..8c9654e3959 100644 --- a/packages/links/package.json +++ b/packages/links/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0", + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", "@apollo/client": "~3.2.5 || ~3.3.0 || ~3.4.0" }, "buildOptions": { diff --git a/packages/links/tests/upload.test.ts b/packages/links/tests/upload.test.ts index b4d35642340..1a7fc60db79 100644 --- a/packages/links/tests/upload.test.ts +++ b/packages/links/tests/upload.test.ts @@ -94,7 +94,7 @@ describe('graphql upload', () => { remoteServer = await startServer(remoteApp); remotePort = (remoteServer.address() as AddressInfo).port; - const nonExecutableSchema = buildSchema(` + const nonExecutableSchema = buildSchema(/* GraphQL */` scalar Upload type Query { version: String diff --git a/packages/load-files/package.json b/packages/load-files/package.json index 9fa8c8c4497..eb236b01723 100644 --- a/packages/load-files/package.json +++ b/packages/load-files/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { "globby": "11.0.4", diff --git a/packages/load/package.json b/packages/load/package.json index a1650d415dc..996f6eea471 100644 --- a/packages/load/package.json +++ b/packages/load/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "devDependencies": { "graphql-tag": "2.12.5", diff --git a/packages/load/src/load-typedefs/parse.ts b/packages/load/src/load-typedefs/parse.ts index 60f386671d4..d1a201cefba 100644 --- a/packages/load/src/load-typedefs/parse.ts +++ b/packages/load/src/load-typedefs/parse.ts @@ -1,7 +1,6 @@ import { Source, printSchemaWithDirectives, - fixSchemaAst, parseGraphQLSDL, printWithComments, resetComments, @@ -67,7 +66,6 @@ function prepareInput({ function parseSchema(input: Input) { if (input.source.schema) { - input.source.schema = fixSchemaAst(input.source.schema, input.options); input.source.rawSDL = printSchemaWithDirectives(input.source.schema, input.options); } } diff --git a/packages/load/tests/loaders/schema/schema-from-typedefs.spec.ts b/packages/load/tests/loaders/schema/schema-from-typedefs.spec.ts index a5424d74e74..79001d22019 100644 --- a/packages/load/tests/loaders/schema/schema-from-typedefs.spec.ts +++ b/packages/load/tests/loaders/schema/schema-from-typedefs.spec.ts @@ -3,7 +3,7 @@ import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'; import { CodeFileLoader } from '@graphql-tools/code-file-loader'; import { runTests, useMonorepo } from '../../../../testing/utils'; import path from 'path'; -import { inspect } from 'util'; +import { inspect } from '@graphql-tools/utils'; const monorepo = useMonorepo({ dirname: __dirname diff --git a/packages/loaders/apollo-engine/package.json b/packages/loaders/apollo-engine/package.json index 93f1541d0c3..57f30f32621 100644 --- a/packages/loaders/apollo-engine/package.json +++ b/packages/loaders/apollo-engine/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { "@graphql-tools/utils": "^8.1.1", diff --git a/packages/loaders/code-file/package.json b/packages/loaders/code-file/package.json index 44d528176c6..e4f2fdbd49a 100644 --- a/packages/loaders/code-file/package.json +++ b/packages/loaders/code-file/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { "@graphql-tools/utils": "^8.1.2", diff --git a/packages/loaders/git/package.json b/packages/loaders/git/package.json index c1aa8907e2d..eaad5583b3b 100644 --- a/packages/loaders/git/package.json +++ b/packages/loaders/git/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { "@graphql-tools/graphql-tag-pluck": "^7.0.5", diff --git a/packages/loaders/github/package.json b/packages/loaders/github/package.json index 6ed47f8d9d2..71deb94806e 100644 --- a/packages/loaders/github/package.json +++ b/packages/loaders/github/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { "@graphql-tools/utils": "^8.1.1", diff --git a/packages/loaders/graphql-file/package.json b/packages/loaders/graphql-file/package.json index b9e4d710b3f..e335f2254aa 100644 --- a/packages/loaders/graphql-file/package.json +++ b/packages/loaders/graphql-file/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/loaders/json-file/package.json b/packages/loaders/json-file/package.json index f970b3d9f35..b017fe38fcb 100644 --- a/packages/loaders/json-file/package.json +++ b/packages/loaders/json-file/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { "@graphql-tools/utils": "^8.1.2", diff --git a/packages/loaders/module/package.json b/packages/loaders/module/package.json index 882ffbf429d..eea4d9c599c 100644 --- a/packages/loaders/module/package.json +++ b/packages/loaders/module/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { "@graphql-tools/utils": "^8.1.1", diff --git a/packages/loaders/prisma/package.json b/packages/loaders/prisma/package.json index d68eeb8ffb5..218b2df167f 100644 --- a/packages/loaders/prisma/package.json +++ b/packages/loaders/prisma/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { "@graphql-tools/url-loader": "^7.0.11", diff --git a/packages/loaders/url/package.json b/packages/loaders/url/package.json index 1917d415b0c..f32693db2e8 100644 --- a/packages/loaders/url/package.json +++ b/packages/loaders/url/package.json @@ -32,7 +32,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "devDependencies": { "@types/valid-url": "1.0.3", diff --git a/packages/merge/package.json b/packages/merge/package.json index be1b4741c8b..8afeec6dcbf 100644 --- a/packages/merge/package.json +++ b/packages/merge/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/merge/tests/merge-typedefs.spec.ts b/packages/merge/tests/merge-typedefs.spec.ts index 838d9ef58ce..1c0cd99dc94 100644 --- a/packages/merge/tests/merge-typedefs.spec.ts +++ b/packages/merge/tests/merge-typedefs.spec.ts @@ -137,7 +137,7 @@ const productType = /* GraphQL */ ` describe('Merge TypeDefs', () => { describe('AST Schema Fixing', () => { it('Should handle correctly schema without valid root AST node', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` type A { a: String } diff --git a/packages/mock/package.json b/packages/mock/package.json index fbc83a511d1..6ec887952e9 100644 --- a/packages/mock/package.json +++ b/packages/mock/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/mock/tests/mocking-compatibility.spec.ts b/packages/mock/tests/mocking-compatibility.spec.ts index c4b8c2459cc..8245fd2bc91 100644 --- a/packages/mock/tests/mocking-compatibility.spec.ts +++ b/packages/mock/tests/mocking-compatibility.spec.ts @@ -1,7 +1,6 @@ /* eslint-disable camelcase */ import { graphql, - GraphQLResolveInfo, GraphQLSchema, buildSchema, subscribe, parse } from 'graphql'; @@ -251,9 +250,9 @@ describe('Mock retro-compatibility', () => { let spy = 0; const resolvers = { BirdsAndBees: { - __resolveType(data: any, _context: any, info: GraphQLResolveInfo) { + __resolveType(data: any, _context: any) { ++spy; - return info.schema.getType(data.__typename); + return data.__typename; }, }, }; @@ -1187,7 +1186,7 @@ describe('Mock retro-compatibility', () => { }); test('works for resolvers returning javascript Dates', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` scalar Date type DateObject { diff --git a/packages/mock/tests/store.spec.ts b/packages/mock/tests/store.spec.ts index 869bd123359..4246d287c05 100644 --- a/packages/mock/tests/store.spec.ts +++ b/packages/mock/tests/store.spec.ts @@ -3,7 +3,7 @@ import { createMockStore } from '../src'; import { assertIsRef, Ref } from '../src/types'; import { makeRef } from '../src/utils'; -const typeDefs = ` +const typeDefs = /* GraphQL */` type User { id: ID! age: Int! @@ -364,7 +364,7 @@ describe('MockStore', () => { }); it('should support ID of type number', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type User { id: Int! name: String! diff --git a/packages/node-require/package.json b/packages/node-require/package.json index a83818d0b85..9be49d203c7 100644 --- a/packages/node-require/package.json +++ b/packages/node-require/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/node-require/test/node-require.spec.ts b/packages/node-require/test/node-require.spec.ts index d2dac6d6f2e..9c8cbbaf97d 100644 --- a/packages/node-require/test/node-require.spec.ts +++ b/packages/node-require/test/node-require.spec.ts @@ -9,8 +9,8 @@ describe('GraphQL Node Import', () => { const m: any = {}; handleModule(m, join(__dirname, filePath)); const typeDefs = m.exports; - expect(print(typeDefs).replace(/\s\s+/g, ' ')).toBe( - readFileSync(require.resolve(filePath), 'utf8').replace(/\s\s+/g, ' ') + expect(print(typeDefs).replace(/\s\s+/g, ' ').trim()).toBe( + readFileSync(require.resolve(filePath), 'utf8').replace(/\s\s+/g, ' ').trim() ); }); }); diff --git a/packages/optimize/package.json b/packages/optimize/package.json index d72ca52dfd8..034dc8d0034 100644 --- a/packages/optimize/package.json +++ b/packages/optimize/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/optimize/tests/__snapshots__/remove-description.spec.ts.snap b/packages/optimize/tests/__snapshots__/remove-description.spec.ts.snap index ef4f9dd4507..3880834a246 100644 --- a/packages/optimize/tests/__snapshots__/remove-description.spec.ts.snap +++ b/packages/optimize/tests/__snapshots__/remove-description.spec.ts.snap @@ -3,6 +3,5 @@ exports[`removeDescription Should remove description 1`] = ` "type Query { f: String -} -" +}" `; diff --git a/packages/optimize/tests/remove-description.spec.ts b/packages/optimize/tests/remove-description.spec.ts index 4020538a1d0..ae68926e552 100644 --- a/packages/optimize/tests/remove-description.spec.ts +++ b/packages/optimize/tests/remove-description.spec.ts @@ -11,6 +11,6 @@ describe('removeDescription', () => { } `); const out = removeDescriptions(doc); - expect(print(out)).toMatchSnapshot(); + expect(print(out).trim()).toMatchSnapshot(); }) }) diff --git a/packages/relay-operation-optimizer/package.json b/packages/relay-operation-optimizer/package.json index f89aeae7e4d..991b15945d1 100644 --- a/packages/relay-operation-optimizer/package.json +++ b/packages/relay-operation-optimizer/package.json @@ -38,7 +38,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "dependencies": { "@graphql-tools/utils": "^8.1.1", diff --git a/packages/resolvers-composition/package.json b/packages/resolvers-composition/package.json index d736279797b..7f5d3a81219 100644 --- a/packages/resolvers-composition/package.json +++ b/packages/resolvers-composition/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "devDependencies": { "@types/lodash": "4.14.172", diff --git a/packages/resolvers-composition/tests/resolvers-composition.spec.ts b/packages/resolvers-composition/tests/resolvers-composition.spec.ts index 3befe96c55b..38967cec221 100644 --- a/packages/resolvers-composition/tests/resolvers-composition.spec.ts +++ b/packages/resolvers-composition/tests/resolvers-composition.spec.ts @@ -2,7 +2,7 @@ import gql from 'graphql-tag'; import { composeResolvers, ResolversComposerMapping } from '../src'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { execute, GraphQLScalarType, Kind } from 'graphql'; -import { inspect } from 'util'; +import { inspect } from '@graphql-tools/utils'; function createAsyncIterator(array: T[]): AsyncIterator { let i = 0; diff --git a/packages/schema/package.json b/packages/schema/package.json index 815d357ab1a..6e6f02b14c7 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "devDependencies": { "graphql-scalars": "1.10.0" diff --git a/packages/schema/src/addResolversToSchema.ts b/packages/schema/src/addResolversToSchema.ts index 51427d538b7..64ce23f082a 100644 --- a/packages/schema/src/addResolversToSchema.ts +++ b/packages/schema/src/addResolversToSchema.ts @@ -180,7 +180,11 @@ function addResolversToExistingSchema( type.extensions != null && (resolverValue as GraphQLScalarType).extensions != null ) { - type.extensions = Object.assign({}, type.extensions, (resolverValue as GraphQLScalarType).extensions); + type.extensions = Object.assign( + Object.create(null), + type.extensions, + (resolverValue as GraphQLScalarType).extensions + ); } else { type[fieldName] = resolverValue[fieldName]; } @@ -209,7 +213,11 @@ function addResolversToExistingSchema( type.extensions != null && (resolverValue as GraphQLEnumType).extensions != null ) { - type.extensions = Object.assign({}, type.extensions, (resolverValue as GraphQLEnumType).extensions); + type.extensions = Object.assign( + Object.create(null), + type.extensions, + (resolverValue as GraphQLEnumType).extensions + ); } else if (enumValueConfigMap[fieldName]) { enumValueConfigMap[fieldName].value = resolverValue[fieldName]; } @@ -294,7 +302,11 @@ function createNewSchemaWithResolvers( config.extensions != null && (resolverValue as GraphQLScalarType).extensions != null ) { - config.extensions = Object.assign({}, type.extensions, (resolverValue as GraphQLScalarType).extensions); + config.extensions = Object.assign( + Object.create(null), + type.extensions, + (resolverValue as GraphQLScalarType).extensions + ); } else { config[fieldName] = resolverValue[fieldName]; } @@ -330,7 +342,11 @@ function createNewSchemaWithResolvers( config.extensions != null && (resolverValue as GraphQLEnumType).extensions != null ) { - config.extensions = Object.assign({}, type.extensions, (resolverValue as GraphQLEnumType).extensions); + config.extensions = Object.assign( + Object.create(null), + type.extensions, + (resolverValue as GraphQLEnumType).extensions + ); } else if (enumValueConfigMap[fieldName]) { enumValueConfigMap[fieldName].value = resolverValue[fieldName]; } diff --git a/packages/schema/tests/merge-schemas.spec.ts b/packages/schema/tests/merge-schemas.spec.ts index 172cf0ef453..90be07a78c5 100644 --- a/packages/schema/tests/merge-schemas.spec.ts +++ b/packages/schema/tests/merge-schemas.spec.ts @@ -365,7 +365,7 @@ describe('Merge Schemas', () => { }); it.only('should not duplicate directives of scalars', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` directive @sqlType(type: String!) on SCALAR scalar JSON @sqlType(type: "json") `); diff --git a/packages/schema/tests/schemaGenerator.test.ts b/packages/schema/tests/schemaGenerator.test.ts index 6e222911445..ef02cde8ee3 100644 --- a/packages/schema/tests/schemaGenerator.test.ts +++ b/packages/schema/tests/schemaGenerator.test.ts @@ -307,7 +307,7 @@ describe('generating schema from shorthand', () => { test('can generate a schema from an array of parsed and none parsed type definitions', () => { const typeDefSchema = [ - parse(/* GraphQL */` + parse(` type Query { foo: String }`), @@ -582,12 +582,12 @@ describe('generating schema from shorthand', () => { }, }, Searchable: { - __resolveType(data: any, _context: any, info: GraphQLResolveInfo) { + __resolveType(data: any, _context: any) { if (data.age) { - return info.schema.getType('Person'); + return 'Person'; } if (data.coordinates) { - return info.schema.getType('Location'); + return 'Location'; } return null; }, @@ -2224,7 +2224,7 @@ describe('interface resolver inheritance', () => { }); test('respects interface order and existing resolvers', async () => { - const testSchemaWithInterfaceResolvers = ` + const testSchemaWithInterfaceResolvers = /* GraphQL */` interface Node { id: ID! } @@ -2232,11 +2232,11 @@ describe('interface resolver inheritance', () => { id: ID! name: String! } - type Replicant implements Node, Person { + type Replicant implements Node & Person { id: ID! name: String! } - type Cyborg implements Person, Node { + type Cyborg implements Person & Node { id: ID! name: String! } diff --git a/packages/stitch/package.json b/packages/stitch/package.json index f73eaa28c64..415c9323eba 100644 --- a/packages/stitch/package.json +++ b/packages/stitch/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/stitch/src/createDelegationPlanBuilder.ts b/packages/stitch/src/createDelegationPlanBuilder.ts index 0637f9fd699..74bfd22b7ed 100644 --- a/packages/stitch/src/createDelegationPlanBuilder.ts +++ b/packages/stitch/src/createDelegationPlanBuilder.ts @@ -12,7 +12,7 @@ import { import { DelegationPlanBuilder, MergedTypeInfo, StitchingInfo, Subschema } from '@graphql-tools/delegate'; import { getFieldsNotInSubschema } from './getFieldsNotInSubschema'; -import { memoize1, memoize2, memoize3 } from './memoize'; +import { memoize1, memoize2, memoize3, memoize5 } from '@graphql-tools/utils'; function calculateDelegationStage( mergedTypeInfo: MergedTypeInfo, @@ -131,29 +131,21 @@ function getStitchingInfo(schema: GraphQLSchema): StitchingInfo { return stitchingInfo; } -function getMergedTypeInfo(stitchingInfo: StitchingInfo, typeName: string): MergedTypeInfo { - const mergedTypeInfo = stitchingInfo.mergedTypes[typeName]; - if (!mergedTypeInfo) { - throw new Error(`Type "${typeName}" is not a merged type.`); - } - return mergedTypeInfo; -} - -export function createDelegationPlanBuilder(typeName: string): DelegationPlanBuilder { - return ( +export function createDelegationPlanBuilder(mergedTypeInfo: MergedTypeInfo): DelegationPlanBuilder { + return memoize5(function delegationPlanBuilder( schema: GraphQLSchema, sourceSubschema: Subschema, - fieldNodes: ReadonlyArray, - fragments: Record = Object.create(null), - variableValues: Record = Object.create(null), - stitchingInfo = getStitchingInfo(schema), - mergedTypeInfo = getMergedTypeInfo(stitchingInfo, typeName), - targetSubschemas = mergedTypeInfo?.targetSubschemas.get(sourceSubschema) - ): Array> => { + variableValues: Record, + fragments: Record, + fieldNodes: FieldNode[] + ): Array> { + const stitchingInfo = getStitchingInfo(schema); + const targetSubschemas = mergedTypeInfo?.targetSubschemas.get(sourceSubschema); if (!targetSubschemas || !targetSubschemas.length) { return []; } + const typeName = mergedTypeInfo.typeName; const fieldsNotInSubschema = getFieldsNotInSubschema( schema, stitchingInfo, @@ -195,7 +187,7 @@ export function createDelegationPlanBuilder(typeName: string): DelegationPlanBui } return delegationMaps; - }; + }); } const createSubschemas = memoize1(function createSubschemas(sourceSubschema: Subschema): Array { diff --git a/packages/stitch/src/createMergedTypeResolver.ts b/packages/stitch/src/createMergedTypeResolver.ts index 3ceec4b3162..76735bf923b 100644 --- a/packages/stitch/src/createMergedTypeResolver.ts +++ b/packages/stitch/src/createMergedTypeResolver.ts @@ -8,8 +8,8 @@ export function createMergedTypeResolver( const { fieldName, argsFromKeys, valuesFromResults, args } = mergedTypeResolverOptions; if (argsFromKeys != null) { - return (originalResult, context, info, subschema, selectionSet, key) => - batchDelegateToSchema({ + return function mergedBatchedTypeResolver(originalResult, context, info, subschema, selectionSet, key) { + return batchDelegateToSchema({ schema: subschema, operation: 'query', fieldName, @@ -24,11 +24,12 @@ export function createMergedTypeResolver( info, skipTypeMerging: true, }); + }; } if (args != null) { - return (originalResult, context, info, subschema, selectionSet) => - delegateToSchema({ + return function mergedTypeResolver(originalResult, context, info, subschema, selectionSet) { + return delegateToSchema({ schema: subschema, operation: 'query', fieldName, @@ -41,6 +42,7 @@ export function createMergedTypeResolver( info, skipTypeMerging: true, }); + }; } return undefined; diff --git a/packages/stitch/src/getFieldsNotInSubschema.ts b/packages/stitch/src/getFieldsNotInSubschema.ts index 474a016499a..e7668cf80db 100644 --- a/packages/stitch/src/getFieldsNotInSubschema.ts +++ b/packages/stitch/src/getFieldsNotInSubschema.ts @@ -1,50 +1,18 @@ import { GraphQLSchema, FieldNode, GraphQLObjectType, FragmentDefinitionNode } from 'graphql'; -import { collectFields, ExecutionContext } from 'graphql/execution/execute.js'; - import { StitchingInfo } from '@graphql-tools/delegate'; - -function collectSubFields( - schema: GraphQLSchema, - type: GraphQLObjectType, - fieldNodes: ReadonlyArray, - fragments: Record, - variableValues: Record -): Record> { - let subFieldNodes: Record> = Object.create(null); - const visitedFragmentNames = Object.create(null); - - const partialExecutionContext = { - schema, - variableValues, - fragments, - } as ExecutionContext; - - for (const fieldNode of fieldNodes) { - if (fieldNode.selectionSet) { - subFieldNodes = collectFields( - partialExecutionContext, - type, - fieldNode.selectionSet, - subFieldNodes, - visitedFragmentNames - ); - } - } - - return subFieldNodes; -} +import { collectSubFields } from '@graphql-tools/utils'; export function getFieldsNotInSubschema( schema: GraphQLSchema, stitchingInfo: StitchingInfo, gatewayType: GraphQLObjectType, subschemaType: GraphQLObjectType, - fieldNodes: ReadonlyArray, + fieldNodes: FieldNode[], fragments: Record, variableValues: Record ): Array { - const subFieldNodes = collectSubFields(schema, gatewayType, fieldNodes, fragments, variableValues); + const subFieldNodesByResponseKey = collectSubFields(schema, fragments, variableValues, gatewayType, fieldNodes); // TODO: Verify whether it is safe that extensions always exists. const fieldNodesByField = stitchingInfo?.fieldNodesByField; @@ -52,12 +20,11 @@ export function getFieldsNotInSubschema( const fields = subschemaType.getFields(); const fieldsNotInSchema = new Set(); - for (const responseKey in subFieldNodes) { - const subFieldNodesForResponseKey = subFieldNodes[responseKey]; - const fieldName = subFieldNodesForResponseKey[0].name.value; + for (const [, subFieldNodes] of subFieldNodesByResponseKey) { + const fieldName = subFieldNodes[0].name.value; if (!fields[fieldName]) { - for (const subFieldNodeForResponseKey of subFieldNodesForResponseKey) { - fieldsNotInSchema.add(subFieldNodeForResponseKey); + for (const subFieldNode of subFieldNodes) { + fieldsNotInSchema.add(subFieldNode); } } const fieldNodesForField = fieldNodesByField?.[gatewayType.name]?.[fieldName]; diff --git a/packages/stitch/src/memoize.ts b/packages/stitch/src/memoize.ts deleted file mode 100644 index 498fdc8c6a3..00000000000 --- a/packages/stitch/src/memoize.ts +++ /dev/null @@ -1,70 +0,0 @@ -const memoize1cache: WeakMap, WeakMap, any>> = new WeakMap(); -export function memoize1 any>(fn: F): F { - return function memoized(a1: any): any { - const cachedValue = memoize1cache.get(a1); - if (cachedValue === undefined) { - const newValue = fn(a1); - memoize1cache.set(a1, newValue); - return newValue; - } - - return cachedValue; - } as F; -} - -const memoize2cache: WeakMap, WeakMap, any>> = new WeakMap(); -export function memoize2 any>(fn: F): F { - return function memoized(a1: any, a2: any): any { - let cache2 = memoize2cache.get(a1); - if (!cache2) { - cache2 = new WeakMap(); - memoize2cache.set(a1, cache2); - const newValue = fn(a1, a2); - cache2.set(a2, newValue); - return newValue; - } - - const cachedValue = cache2.get(a2); - if (cachedValue === undefined) { - const newValue = fn(a1, a2); - cache2.set(a2, newValue); - return newValue; - } - - return cachedValue; - } as F; -} - -const memoize3Cache: WeakMap, WeakMap, any>> = new WeakMap(); -export function memoize3 any>(fn: F): F { - return function memoized(a1: any, a2: any, a3: any) { - let cache2 = memoize3Cache.get(a1); - if (!cache2) { - cache2 = new WeakMap(); - memoize3Cache.set(a1, cache2); - const cache3 = new WeakMap(); - cache2.set(a2, cache3); - const newValue = fn(a1, a2, a3); - cache3.set(a3, newValue); - return newValue; - } - - let cache3 = cache2.get(a2); - if (!cache3) { - cache3 = new WeakMap(); - cache2.set(a2, cache3); - const newValue = fn(a1, a2, a3); - cache3.set(a3, newValue); - return newValue; - } - - const cachedValue = cache3.get(a3); - if (cachedValue === undefined) { - const newValue = fn(a1, a2, a3); - cache3.set(a3, newValue); - return newValue; - } - - return cachedValue; - } as F; -} diff --git a/packages/stitch/src/stitchingInfo.ts b/packages/stitch/src/stitchingInfo.ts index 3c01029747c..05f9b5fd1cb 100644 --- a/packages/stitch/src/stitchingInfo.ts +++ b/packages/stitch/src/stitchingInfo.ts @@ -15,14 +15,13 @@ import { GraphQLNamedType, } from 'graphql'; -import { parseSelectionSet, IResolvers, IFieldResolverOptions, isSome } from '@graphql-tools/utils'; +import { collectFields, parseSelectionSet, IResolvers, IFieldResolverOptions, isSome } from '@graphql-tools/utils'; import { MergedTypeResolver, Subschema, SubschemaConfig, MergedTypeInfo, StitchingInfo } from '@graphql-tools/delegate'; import { MergeTypeCandidate, MergeTypeFilter } from './types'; import { createMergedTypeResolver } from './createMergedTypeResolver'; -import { collectFields, ExecutionContext } from 'graphql/execution/execute.js'; import { createDelegationPlanBuilder } from './createDelegationPlanBuilder'; export function createStitchingInfo>( @@ -167,8 +166,11 @@ function createMergedTypes>( uniqueFields: Object.create({}), nonUniqueFields: Object.create({}), resolvers, - delegationPlanBuilder: createDelegationPlanBuilder(typeName), - }; + } as MergedTypeInfo; + + mergedTypes[typeName].delegationPlanBuilder = createDelegationPlanBuilder( + mergedTypes[typeName] as MergedTypeInfo + ); for (const fieldName in supportedBySubschemas) { if (supportedBySubschemas[fieldName].length === 1) { @@ -256,28 +258,27 @@ export function completeStitchingInfo>( } } - const partialExecutionContext = { - schema, - variableValues: Object.create(null), - fragments: Object.create(null), - } as ExecutionContext; + const variableValues = Object.create(null); + const fragments = Object.create(null); - const fieldNodeMap = Object.create(null); + const fieldNodeMap: Record = Object.create(null); for (const typeName in selectionSetsByField) { const type = schema.getType(typeName) as GraphQLObjectType; for (const fieldName in selectionSetsByField[typeName]) { for (const selectionSet of selectionSetsByField[typeName][fieldName]) { - const fieldNodes = collectFields( - partialExecutionContext, + const fieldNodesByResponseKey = collectFields( + schema, + fragments, + variableValues, type, selectionSet, - Object.create(null), - Object.create(null) + new Map(), + new Set() ); - for (const responseKey in fieldNodes) { - for (const fieldNode of fieldNodes[responseKey]) { + for (const [, fieldNodes] of fieldNodesByResponseKey) { + for (const fieldNode of fieldNodes) { const key = print(fieldNode); if (fieldNodeMap[key] == null) { fieldNodeMap[key] = fieldNode; diff --git a/packages/stitch/src/typeCandidates.ts b/packages/stitch/src/typeCandidates.ts index c1bee2413f9..667618e19a9 100644 --- a/packages/stitch/src/typeCandidates.ts +++ b/packages/stitch/src/typeCandidates.ts @@ -15,7 +15,7 @@ import { import { wrapSchema } from '@graphql-tools/wrap'; import { Subschema, SubschemaConfig, StitchingInfo } from '@graphql-tools/delegate'; -import { GraphQLParseOptions, TypeSource, rewireTypes, getRootTypeMap } from '@graphql-tools/utils'; +import { GraphQLParseOptions, TypeSource, rewireTypes, getRootTypeMap, inspect } from '@graphql-tools/utils'; import typeFromAST from './typeFromAST'; import { MergeTypeCandidate, MergeTypeFilter, OnTypeConflict, TypeMergingOptions } from './types'; @@ -114,7 +114,7 @@ export function buildTypeCandidates>({ for (const def of extraction.typeDefinitions) { const type = typeFromAST(def); if (!isNamedType(type)) { - throw new Error(`Expected to get named typed but got ${JSON.stringify(def)}`); + throw new Error(`Expected to get named typed but got ${inspect(def)}`); } if (type != null) { addTypeCandidate(typeCandidates, type.name, { type }); @@ -124,7 +124,7 @@ export function buildTypeCandidates>({ for (const def of extraction.directiveDefs) { const directive = typeFromAST(def); if (!isDirective(directive)) { - throw new Error(`Expected to get directive type but got ${JSON.stringify(def)}`); + throw new Error(`Expected to get directive type but got ${inspect(def)}`); } directiveMap[directive.name] = directive; } diff --git a/packages/stitch/tests/alternateStitchSchemas.test.ts b/packages/stitch/tests/alternateStitchSchemas.test.ts index 2de0ef98982..7cbd2e4ee5f 100644 --- a/packages/stitch/tests/alternateStitchSchemas.test.ts +++ b/packages/stitch/tests/alternateStitchSchemas.test.ts @@ -45,7 +45,7 @@ import { subscriptionSchema, subscriptionPubSub, subscriptionPubSubTrigger, -} from './fixtures/schemas'; +} from '../../testing/fixtures/schemas'; const linkSchema = /* GraphQL */` """ @@ -1504,7 +1504,7 @@ describe('schema transformation with wrapping of object fields', () => { }); describe('interface resolver inheritance', () => { - const testSchemaWithInterfaceResolvers = ` + const testSchemaWithInterfaceResolvers = /* GraphQL */` interface Node { id: ID! } @@ -2199,7 +2199,7 @@ describe('unidirectional type merging', () => { describe('stitchSchemas handles typeDefs with default values', () => { test('it works', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { foo(arg: String = "1"): String } diff --git a/packages/stitch/tests/errors.test.ts b/packages/stitch/tests/errors.test.ts index 55cec77d6ed..f8bf146d1b3 100644 --- a/packages/stitch/tests/errors.test.ts +++ b/packages/stitch/tests/errors.test.ts @@ -6,7 +6,7 @@ import { assertSome, ExecutionResult, Executor } from '@graphql-tools/utils'; describe('passes along errors for missing fields on list', () => { test('if non-null', async () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { getOuter: Outer } @@ -43,7 +43,7 @@ describe('passes along errors for missing fields on list', () => { }); test('even if nullable', async () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { getOuter: Outer } @@ -82,7 +82,7 @@ describe('passes along errors for missing fields on list', () => { describe('passes along errors when list field errors', () => { test('if non-null', async () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { getOuter: Outer } @@ -119,7 +119,7 @@ describe('passes along errors when list field errors', () => { }); test('even if nullable', async () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { getOuter: Outer } @@ -191,7 +191,7 @@ describe('passes along errors when list field errors', () => { describe('passes along errors for remote schemas', () => { it('it works', async () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Test { field: String! } diff --git a/packages/stitch/tests/example.test.ts b/packages/stitch/tests/example.test.ts index c8f89436106..2c0d6fc159e 100644 --- a/packages/stitch/tests/example.test.ts +++ b/packages/stitch/tests/example.test.ts @@ -41,7 +41,7 @@ describe('basic stitching example', () => { authorSchema = addMocksToSchema({ schema: authorSchema }); - const linkTypeDefs = ` + const linkTypeDefs = /* GraphQL */` extend type User { chirps: [Chirp] } @@ -159,7 +159,7 @@ describe('stitching to interfaces', () => { authorSchema = addMocksToSchema({ schema: authorSchema }); - const linkTypeDefs = ` + const linkTypeDefs = /* GraphQL */` extend type User { chirps: [Chirp] } diff --git a/packages/stitch/tests/selectionSets.test.ts b/packages/stitch/tests/selectionSets.test.ts index 448b21c8944..e5829a53c12 100644 --- a/packages/stitch/tests/selectionSets.test.ts +++ b/packages/stitch/tests/selectionSets.test.ts @@ -12,7 +12,7 @@ import { bookingSchema, sampleData, Property, -} from './fixtures/schemas'; +} from '../../testing/fixtures/schemas'; describe('delegateToSchema ', () => { test('should add selection sets for deep types', async () => { diff --git a/packages/stitch/tests/stitchSchemas.test.ts b/packages/stitch/tests/stitchSchemas.test.ts index 0d57b143c44..f34e1025a42 100644 --- a/packages/stitch/tests/stitchSchemas.test.ts +++ b/packages/stitch/tests/stitchSchemas.test.ts @@ -31,7 +31,7 @@ import { remoteProductSchema, subscriptionPubSub, subscriptionPubSubTrigger, -} from './fixtures/schemas'; +} from '../../testing/fixtures/schemas'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const removeLocations = ({ locations, ...rest }: any): any => ({ ...rest }); @@ -66,7 +66,7 @@ const testCombinations = [ }, ]; -const scalarTest = ` +const scalarTest = /* GraphQL */` """ Description of TestScalar. """ @@ -117,7 +117,7 @@ const scalarSchema = makeExecutableSchema({ }, }); -const enumTest = ` +const enumTest = /* GraphQL */` """ A type that uses an Enum. """ @@ -196,7 +196,7 @@ const enumSchema = makeExecutableSchema({ }, }); -const linkSchema = /* GraphQL */` +const linkSchema = ` """ A new type linking the Property type. """ @@ -259,14 +259,14 @@ const loneExtend = parse(/* GraphQL */` } `); -let interfaceExtensionTest = ` +let interfaceExtensionTest = /* GraphQL */` # No-op for older versions since this feature does not yet exist extend type DownloadableProduct { filesize: Int } `; -interfaceExtensionTest = ` +interfaceExtensionTest = /* GraphQL */` extend interface Downloadable { filesize: Int } @@ -278,7 +278,7 @@ interfaceExtensionTest = ` // Miscellaneous typeDefs that exercise uncommon branches for the sake of // code coverage. -const codeCoverageTypeDefs = ` +const codeCoverageTypeDefs = /* GraphQL */` interface SyntaxNode { type: String } @@ -306,7 +306,7 @@ const codeCoverageTypeDefs = ` } `; -const schemaDirectiveTypeDefs = ` +const schemaDirectiveTypeDefs = /* GraphQL */` directive @upper on FIELD_DEFINITION directive @withEnumArg(enumArg: DirectiveEnum = FOO) on FIELD_DEFINITION @@ -3030,7 +3030,7 @@ fragment BookingFragment on Booking { test('defaultMergedResolver should work with aliases if parent merged resolver is manually overwritten', async () => { // Source: https://github.com/apollographql/graphql-tools/issues/967 - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { book: Book } @@ -3152,7 +3152,7 @@ fragment BookingFragment on Booking { describe('empty typeDefs array', () => { test('works', async () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { book: Book } diff --git a/packages/stitch/tests/transforms.test.ts b/packages/stitch/tests/transforms.test.ts index bef497b54e6..154848591ed 100644 --- a/packages/stitch/tests/transforms.test.ts +++ b/packages/stitch/tests/transforms.test.ts @@ -10,7 +10,7 @@ import { addMocksToSchema } from '@graphql-tools/mock'; import { stitchSchemas } from '../src/stitchSchemas'; -import { propertySchema } from './fixtures/schemas'; +import { propertySchema } from '../../testing/fixtures/schemas'; describe('rename root type', () => { test('works with stitchSchemas', async () => { diff --git a/packages/stitch/tests/unknownType.test.ts b/packages/stitch/tests/unknownType.test.ts index 73433d59ecb..12f774f9a99 100644 --- a/packages/stitch/tests/unknownType.test.ts +++ b/packages/stitch/tests/unknownType.test.ts @@ -51,7 +51,7 @@ const serviceSchemaConfig = { describe('test delegateToSchema() with type renaming', () => { let stitchedSchema: GraphQLSchema; - const typeDefs = ` + const typeDefs = /* GraphQL */` enum Variant { A B diff --git a/packages/stitching-directives/package.json b/packages/stitching-directives/package.json index 12ea271d03f..2699ce73385 100644 --- a/packages/stitching-directives/package.json +++ b/packages/stitching-directives/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/stitching-directives/src/stitchingDirectives.ts b/packages/stitching-directives/src/stitchingDirectives.ts index c460c562bf2..64c40fbe97e 100644 --- a/packages/stitching-directives/src/stitchingDirectives.ts +++ b/packages/stitching-directives/src/stitchingDirectives.ts @@ -30,10 +30,10 @@ export function stitchingDirectives(options: StitchingDirectivesOptions = {}): { const { keyDirectiveName, computedDirectiveName, mergeDirectiveName, canonicalDirectiveName } = finalOptions; - const keyDirectiveTypeDefs = `directive @${keyDirectiveName}(selectionSet: String!) on OBJECT`; - const computedDirectiveTypeDefs = `directive @${computedDirectiveName}(selectionSet: String!) on FIELD_DEFINITION`; - const mergeDirectiveTypeDefs = `directive @${mergeDirectiveName}(argsExpr: String, keyArg: String, keyField: String, key: [String!], additionalArgs: String) on FIELD_DEFINITION`; - const canonicalDirectiveTypeDefs = `directive @${canonicalDirectiveName} on OBJECT | INTERFACE | INPUT_OBJECT | UNION | ENUM | SCALAR | FIELD_DEFINITION | INPUT_FIELD_DEFINITION`; + const keyDirectiveTypeDefs = /* GraphQL */ `directive @${keyDirectiveName}(selectionSet: String!) on OBJECT`; + const computedDirectiveTypeDefs = /* GraphQL */ `directive @${computedDirectiveName}(selectionSet: String!) on FIELD_DEFINITION`; + const mergeDirectiveTypeDefs = /* GraphQL */ `directive @${mergeDirectiveName}(argsExpr: String, keyArg: String, keyField: String, key: [String!], additionalArgs: String) on FIELD_DEFINITION`; + const canonicalDirectiveTypeDefs = /* GraphQL */ `directive @${canonicalDirectiveName} on OBJECT | INTERFACE | INPUT_OBJECT | UNION | ENUM | SCALAR | FIELD_DEFINITION | INPUT_FIELD_DEFINITION`; const keyDirective = new GraphQLDirective({ name: keyDirectiveName, diff --git a/packages/stitching-directives/tests/stitchingDirectivesTransformer.test.ts b/packages/stitching-directives/tests/stitchingDirectivesTransformer.test.ts index 29bf970afbd..72104b768d7 100644 --- a/packages/stitching-directives/tests/stitchingDirectivesTransformer.test.ts +++ b/packages/stitching-directives/tests/stitchingDirectivesTransformer.test.ts @@ -10,7 +10,7 @@ describe('type merging directives', () => { const { allStitchingDirectivesTypeDefs, stitchingDirectivesTransformer } = stitchingDirectives(); test('adds type selection sets', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -37,7 +37,7 @@ describe('type merging directives', () => { }); test('adds type selection sets when returns union', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -66,7 +66,7 @@ describe('type merging directives', () => { }); test('adds type selection sets when returns interface', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -97,7 +97,7 @@ describe('type merging directives', () => { }); test('adds type selection sets when returns list', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -148,7 +148,7 @@ describe('type merging directives', () => { }); test('adds type selection sets when returns multi-layered list', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -213,7 +213,7 @@ describe('type merging directives', () => { }); test('adds type selection sets when returns null', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -250,7 +250,7 @@ describe('type merging directives', () => { }); test('adds computed selection sets', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -278,7 +278,7 @@ describe('type merging directives', () => { }); test('adds args function when used without arguments', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -317,7 +317,7 @@ describe('type merging directives', () => { }); test('adds args function when used with argsExpr argument using an unqualified key', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -356,7 +356,7 @@ describe('type merging directives', () => { }); test('adds args function when used with argsExpr argument using a fully qualified key', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -395,7 +395,7 @@ describe('type merging directives', () => { }); test('adds args function when used with keyArg argument', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -434,7 +434,7 @@ describe('type merging directives', () => { }); test('adds args function when used with nested keyArg argument', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -480,7 +480,7 @@ describe('type merging directives', () => { }); test('adds args function when used with keyArg and additionalArgs arguments', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -520,7 +520,7 @@ describe('type merging directives', () => { }); test('adds key and args function when @merge is used with keyField argument', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} type Query { _user(id: ID): User @merge(keyField: "id") @@ -557,7 +557,7 @@ describe('type merging directives', () => { }); test('adds args function when used with key argument', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -603,7 +603,7 @@ describe('type merging directives', () => { }); test('adds key and argsFromKeys functions when used without arguments', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -647,7 +647,7 @@ describe('type merging directives', () => { }); test('adds key and argsFromKeys functions when used without arguments and returns union', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -696,7 +696,7 @@ describe('type merging directives', () => { }); test('adds key and argsFromKeys functions when used without arguments and returns interface', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -747,7 +747,7 @@ describe('type merging directives', () => { }); test('adds key and argsFromKeys functions with argsExpr argument using an unqualified key', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -791,7 +791,7 @@ describe('type merging directives', () => { }); test('adds key and argsFromKeys functions with argsExpr argument using a fully qualified key', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -835,7 +835,7 @@ describe('type merging directives', () => { }); test('applies canonical merge attributions', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} type User implements IUser @canonical { diff --git a/packages/stitching-directives/tests/stitchingDirectivesValidator.test.ts b/packages/stitching-directives/tests/stitchingDirectivesValidator.test.ts index 03988182170..6877ef5511f 100644 --- a/packages/stitching-directives/tests/stitchingDirectivesValidator.test.ts +++ b/packages/stitching-directives/tests/stitchingDirectivesValidator.test.ts @@ -6,7 +6,7 @@ describe('type merging directives', () => { const { allStitchingDirectivesTypeDefs, stitchingDirectivesValidator } = stitchingDirectives(); test('does not throw an error if no other typeDefs used', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} `; @@ -15,7 +15,7 @@ describe('type merging directives', () => { }); test('throws an error if type selectionSet invalid', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} type Query { _user: User @@ -32,7 +32,7 @@ describe('type merging directives', () => { }); test('does not throw an error if type selectionSet valid', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} type Query { _user: User @@ -49,7 +49,7 @@ describe('type merging directives', () => { }); test('throws an error if computed selectionSet invalid', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} type Query { _user: User @@ -66,7 +66,7 @@ describe('type merging directives', () => { }); test('does not throw an error if computed selectionSet valid', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} type Query { _user: User @@ -83,7 +83,7 @@ describe('type merging directives', () => { }); test('throws an error if merge argsExpr invalid', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -102,7 +102,7 @@ describe('type merging directives', () => { }); test('does not throw an error if merge argsExpr valid', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -121,7 +121,7 @@ describe('type merging directives', () => { }); test('does not throw an error when using merge with argsExpr on a multiple args endpoint', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} type Query { @@ -139,7 +139,7 @@ describe('type merging directives', () => { }); test('does not throw an error if merge used without arguments', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key diff --git a/packages/stitch/tests/fixtures/schemas.ts b/packages/testing/fixtures/schemas.ts similarity index 89% rename from packages/stitch/tests/fixtures/schemas.ts rename to packages/testing/fixtures/schemas.ts index a2e081c191e..a8a9c91fb04 100644 --- a/packages/stitch/tests/fixtures/schemas.ts +++ b/packages/testing/fixtures/schemas.ts @@ -10,25 +10,14 @@ import { } from 'graphql'; import { introspectSchema } from '@graphql-tools/wrap'; -import { - AsyncExecutor, - IResolvers, -} from '@graphql-tools/utils'; +import { AsyncExecutor, IResolvers } from '@graphql-tools/utils'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { SubschemaConfig, createDefaultExecutor } from '@graphql-tools/delegate'; export class CustomError extends GraphQLError { constructor(message: string, extensions: Record) { - super( - message, - undefined, - undefined, - undefined, - undefined, - undefined, - extensions, - ); + super(message, undefined, undefined, undefined, undefined, undefined, extensions); } } @@ -183,9 +172,7 @@ export const sampleData: { function coerceString(value: any): string { if (Array.isArray(value)) { - throw new TypeError( - `String cannot represent an array value: [${String(value)}]`, - ); + throw new TypeError(`String cannot represent an array value: [${String(value)}]`); } return String(value); } @@ -238,7 +225,7 @@ const GraphQLJSON = new GraphQLScalarType({ parseLiteral, }); -const addressTypeDef = ` +const addressTypeDef = /* GraphQL */ ` type Address { street: String city: String @@ -247,7 +234,7 @@ const addressTypeDef = ` } `; -const propertyAddressTypeDef = ` +const propertyAddressTypeDef = /* GraphQL */ ` type Property { id: ID! name: String! @@ -257,7 +244,7 @@ const propertyAddressTypeDef = ` } `; -const propertyRootTypeDefs = ` +const propertyRootTypeDefs = /* GraphQL */ ` type Location { name: String! } @@ -278,9 +265,8 @@ const propertyRootTypeDefs = ` foo: String } - ${ - 'getInterfaces' in GraphQLInterfaceType.prototype - ? `interface TestNestedInterface implements TestInterface { + ${'getInterfaces' in GraphQLInterfaceType.prototype + ? `interface TestNestedInterface implements TestInterface { kind: TestInterfaceKind testString: String } @@ -290,12 +276,11 @@ const propertyRootTypeDefs = ` testString: String bar: String }` - : `type TestImpl2 implements TestInterface { + : `type TestImpl2 implements TestInterface { kind: TestInterfaceKind testString: String bar: String - }` - } + }`} type UnionImpl { someField: String @@ -322,7 +307,7 @@ const propertyRootTypeDefs = ` } `; -const propertyAddressTypeDefs = ` +const propertyAddressTypeDefs = /* GraphQL */ ` scalar DateTime scalar JSON @@ -429,7 +414,7 @@ const SimpleProduct = `type SimpleProduct implements Product & Sellable { } `; -const productTypeDefs = ` +const productTypeDefs = /* GraphQL */ ` interface Product { id: ID! } @@ -465,7 +450,7 @@ const productResolvers: IResolvers = { }, }; -const customerAddressTypeDef = ` +const customerAddressTypeDef = /* GraphQL */ ` type Customer implements Person { id: ID! email: String! @@ -477,7 +462,7 @@ const customerAddressTypeDef = ` } `; -const bookingRootTypeDefs = ` +const bookingRootTypeDefs = /* GraphQL */ ` scalar DateTime type Booking { @@ -502,7 +487,7 @@ const bookingRootTypeDefs = ` bikeType: String } - type Car { + type Car { id: ID! licensePlate: String } @@ -527,7 +512,7 @@ const bookingRootTypeDefs = ` } `; -const bookingAddressTypeDefs = ` +const bookingAddressTypeDefs = /* GraphQL */ ` ${addressTypeDef} ${customerAddressTypeDef} ${bookingRootTypeDefs} @@ -539,9 +524,7 @@ const bookingResolvers: IResolvers = { return sampleData.Booking[id]; }, bookingsByPropertyId(_parent, { propertyId, limit }) { - const list = Object.values(sampleData.Booking).filter( - (booking: Booking) => booking.propertyId === propertyId, - ); + const list = Object.values(sampleData.Booking).filter((booking: Booking) => booking.propertyId === propertyId); return limit ? list.slice(0, limit) : list; }, customerById(_parent, { id }) { @@ -558,10 +541,7 @@ const bookingResolvers: IResolvers = { }, Mutation: { - addBooking( - _parent, - { input: { propertyId, customerId, startTime, endTime } }, - ) { + addBooking(_parent, { input: { propertyId, customerId, startTime, endTime } }) { return { id: 'newId', propertyId, @@ -589,9 +569,7 @@ const bookingResolvers: IResolvers = { Customer: { bookings(parent: Customer, { limit }) { - const list = Object.values(sampleData.Booking).filter( - (booking: Booking) => booking.customerId === parent.id, - ); + const list = Object.values(sampleData.Booking).filter((booking: Booking) => booking.customerId === parent.id); return limit ? list.slice(0, limit) : list; }, vehicle(parent: Customer) { @@ -617,17 +595,17 @@ const bookingResolvers: IResolvers = { DateTime, }; -const subscriptionTypeDefs = ` - type Notification{ +const subscriptionTypeDefs = /* GraphQL */ ` + type Notification { text: String throwError: String } - type Query{ + type Query { notifications: Notification } - type Subscription{ + type Subscription { notifications: Notification } `; @@ -641,8 +619,7 @@ const subscriptionResolvers: IResolvers = { }, Subscription: { notifications: { - subscribe: () => - subscriptionPubSub.asyncIterator(subscriptionPubSubTrigger), + subscribe: () => subscriptionPubSub.asyncIterator(subscriptionPubSubTrigger), }, }, Notification: { @@ -672,9 +649,7 @@ export const subscriptionSchema: GraphQLSchema = makeExecutableSchema({ resolvers: subscriptionResolvers, }); -export async function makeSchemaRemote( - schema: GraphQLSchema, -): Promise { +export async function makeSchemaRemote(schema: GraphQLSchema): Promise { const executor = createDefaultExecutor(schema); const clientSchema = await introspectSchema(executor as AsyncExecutor); return { diff --git a/packages/testing/utils.ts b/packages/testing/utils.ts index e391cb568e4..9bdbe778c8b 100644 --- a/packages/testing/utils.ts +++ b/packages/testing/utils.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -import { GraphQLSchema } from 'graphql'; +import { execute, GraphQLSchema, subscribe } from 'graphql'; import { resolve } from 'path'; import { existsSync } from 'fs'; import nock from 'nock'; @@ -136,6 +136,26 @@ export function mockGraphQLServer({ variables, request, schema, + execute: (schema, document, rootValue, contextValue, variableValues, operationName, fieldResolver) => + execute({ + schema, + document, + rootValue, + contextValue, + variableValues, + operationName, + fieldResolver, + }), + subscribe: (schema, document, rootValue, contextValue, variableValues, operationName, fieldResolver) => + subscribe({ + schema, + document, + rootValue, + contextValue, + variableValues, + operationName, + fieldResolver, + }), }); // processRequest returns one of three types of results depending on how the server should respond // 1) RESPONSE: a regular JSON payload diff --git a/packages/utils/package.json b/packages/utils/package.json index 01cbabd9bb5..61ace4b9b16 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -27,7 +27,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "devDependencies": { "@types/dateformat": "3.0.1", diff --git a/packages/utils/src/astFromType.ts b/packages/utils/src/astFromType.ts index 790dd056daa..f69d19d1049 100644 --- a/packages/utils/src/astFromType.ts +++ b/packages/utils/src/astFromType.ts @@ -1,12 +1,11 @@ import { isNonNullType, Kind, GraphQLType, TypeNode, isListType } from 'graphql'; +import { inspect } from './inspect'; export function astFromType(type: GraphQLType): TypeNode { if (isNonNullType(type)) { const innerType = astFromType(type.ofType); if (innerType.kind === Kind.NON_NULL_TYPE) { - throw new Error( - `Invalid type node ${JSON.stringify(type)}. Inner type of non-null type cannot be a non-null type.` - ); + throw new Error(`Invalid type node ${inspect(type)}. Inner type of non-null type cannot be a non-null type.`); } return { kind: Kind.NON_NULL_TYPE, diff --git a/packages/utils/src/collectFields.ts b/packages/utils/src/collectFields.ts new file mode 100644 index 00000000000..7d7ead8ecce --- /dev/null +++ b/packages/utils/src/collectFields.ts @@ -0,0 +1,162 @@ +import { memoize5 } from './memoize'; +import { + GraphQLSchema, + FragmentDefinitionNode, + GraphQLObjectType, + SelectionSetNode, + FieldNode, + Kind, + FragmentSpreadNode, + InlineFragmentNode, + getDirectiveValues, + GraphQLSkipDirective, + GraphQLIncludeDirective, + isAbstractType, + typeFromAST, +} from 'graphql'; + +// Taken from GraphQL-JS v16 for backwards compat +export function collectFields( + schema: GraphQLSchema, + fragments: Record, + variableValues: { [variable: string]: unknown }, + runtimeType: GraphQLObjectType, + selectionSet: SelectionSetNode, + fields: Map>, + visitedFragmentNames: Set +): Map> { + for (const selection of selectionSet.selections) { + switch (selection.kind) { + case Kind.FIELD: { + if (!shouldIncludeNode(variableValues, selection)) { + continue; + } + const name = getFieldEntryKey(selection); + const fieldList = fields.get(name); + if (fieldList !== undefined) { + fieldList.push(selection); + } else { + fields.set(name, [selection]); + } + break; + } + case Kind.INLINE_FRAGMENT: { + if ( + !shouldIncludeNode(variableValues, selection) || + !doesFragmentConditionMatch(schema, selection, runtimeType) + ) { + continue; + } + collectFields( + schema, + fragments, + variableValues, + runtimeType, + selection.selectionSet, + fields, + visitedFragmentNames + ); + break; + } + case Kind.FRAGMENT_SPREAD: { + const fragName = selection.name.value; + if (visitedFragmentNames.has(fragName) || !shouldIncludeNode(variableValues, selection)) { + continue; + } + visitedFragmentNames.add(fragName); + const fragment = fragments[fragName]; + if (!fragment || !doesFragmentConditionMatch(schema, fragment, runtimeType)) { + continue; + } + collectFields( + schema, + fragments, + variableValues, + runtimeType, + fragment.selectionSet, + fields, + visitedFragmentNames + ); + break; + } + } + } + return fields; +} + +/** + * Determines if a field should be included based on the `@include` and `@skip` + * directives, where `@skip` has higher precedence than `@include`. + */ +function shouldIncludeNode( + variableValues: { [variable: string]: unknown }, + node: FragmentSpreadNode | FieldNode | InlineFragmentNode +): boolean { + const skip = getDirectiveValues(GraphQLSkipDirective, node, variableValues); + if (skip?.['if'] === true) { + return false; + } + + const include = getDirectiveValues(GraphQLIncludeDirective, node, variableValues); + if (include?.['if'] === false) { + return false; + } + return true; +} + +/** + * Determines if a fragment is applicable to the given type. + */ +function doesFragmentConditionMatch( + schema: GraphQLSchema, + fragment: FragmentDefinitionNode | InlineFragmentNode, + type: GraphQLObjectType +): boolean { + const typeConditionNode = fragment.typeCondition; + if (!typeConditionNode) { + return true; + } + const conditionalType = typeFromAST(schema, typeConditionNode); + if (conditionalType === type) { + return true; + } + if (isAbstractType(conditionalType)) { + const possibleTypes = schema.getPossibleTypes(conditionalType); + return possibleTypes.includes(type); + } + return false; +} + +/** + * Implements the logic to compute the key of a given field's entry + */ +function getFieldEntryKey(node: FieldNode): string { + return node.alias ? node.alias.value : node.name.value; +} + +export const collectSubFields = memoize5(function collectSubFields( + schema: GraphQLSchema, + fragments: Record, + variableValues: Record, + type: GraphQLObjectType, + fieldNodes: Array +): Map> { + const subFieldNodes = new Map>(); + const visitedFragmentNames = new Set(); + + for (const fieldNode of fieldNodes) { + if (fieldNode.selectionSet) { + collectFields( + schema, + fragments, + variableValues, + type, + fieldNode.selectionSet, + subFieldNodes, + visitedFragmentNames + ); + } + } + + return subFieldNodes; +}); diff --git a/packages/utils/src/create-schema-definition.ts b/packages/utils/src/create-schema-definition.ts deleted file mode 100644 index a685aff021f..00000000000 --- a/packages/utils/src/create-schema-definition.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { GraphQLObjectType } from 'graphql'; - -export function createSchemaDefinition( - def: { - query: string | GraphQLObjectType | null; - mutation: string | GraphQLObjectType | null; - subscription: string | GraphQLObjectType | null; - }, - config?: { - force?: boolean; - } -): string | undefined { - const schemaRoot: { - query?: string; - mutation?: string; - subscription?: string; - } = {}; - - if (def.query) { - schemaRoot.query = def.query.toString(); - } - - if (def.mutation) { - schemaRoot.mutation = def.mutation.toString(); - } - - if (def.subscription) { - schemaRoot.subscription = def.subscription.toString(); - } - - const fields = Object.keys(schemaRoot) - .map(rootType => (schemaRoot[rootType] ? `${rootType}: ${schemaRoot[rootType]}` : null)) - .filter(a => a); - - if (fields.length) { - return `schema { ${fields.join('\n')} }`; - } - - if (config && config.force) { - return ` schema { query: Query } `; - } - - return undefined; -} diff --git a/packages/utils/src/declarations.d.ts b/packages/utils/src/declarations.d.ts index 284493da0ba..61a9bdd0ca6 100644 --- a/packages/utils/src/declarations.d.ts +++ b/packages/utils/src/declarations.d.ts @@ -11,6 +11,3 @@ declare global { } } declare module 'graphql-upload'; -declare module 'graphql/jsutils/inspect.js' { - export function inspect(obj: any): string; -} diff --git a/packages/utils/src/fix-schema-ast.ts b/packages/utils/src/fix-schema-ast.ts deleted file mode 100644 index acf7739a848..00000000000 --- a/packages/utils/src/fix-schema-ast.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { GraphQLSchema, BuildSchemaOptions, buildASTSchema } from 'graphql'; -import { SchemaPrintOptions } from './types'; -import { getDocumentNodeFromSchema } from './print-schema-with-directives'; - -function buildFixedSchema(schema: GraphQLSchema, options: BuildSchemaOptions & SchemaPrintOptions) { - const document = getDocumentNodeFromSchema(schema); - return buildASTSchema(document, { - ...(options || {}), - }); -} - -export function fixSchemaAst(schema: GraphQLSchema, options: BuildSchemaOptions & SchemaPrintOptions) { - // eslint-disable-next-line no-undef-init - let schemaWithValidAst: GraphQLSchema | undefined = undefined; - if (!schema.astNode || !schema.extensionASTNodes) { - schemaWithValidAst = buildFixedSchema(schema, options); - } - - if (!schema.astNode && schemaWithValidAst?.astNode) { - schema.astNode = schemaWithValidAst.astNode; - } - if (!schema.extensionASTNodes && schemaWithValidAst?.astNode) { - schema.extensionASTNodes = schemaWithValidAst.extensionASTNodes; - } - return schema; -} diff --git a/packages/utils/src/get-user-types-from-schema.ts b/packages/utils/src/get-user-types-from-schema.ts deleted file mode 100644 index d0b6f8e51c2..00000000000 --- a/packages/utils/src/get-user-types-from-schema.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { GraphQLSchema, GraphQLObjectType, isObjectType } from 'graphql'; - -/** - * Get all GraphQL types from schema without: - * - * - Query, Mutation, Subscription objects - * - Internal scalars added by parser - * - * @param schema - */ -export function getUserTypesFromSchema(schema: GraphQLSchema): GraphQLObjectType[] { - const allTypesMap = schema.getTypeMap(); - - // tslint:disable-next-line: no-unnecessary-local-variable - const modelTypes = Object.values(allTypesMap).filter((graphqlType): graphqlType is GraphQLObjectType => { - if (isObjectType(graphqlType)) { - // Filter out private types - if (graphqlType.name.startsWith('__')) { - return false; - } - const schemaMutationType = schema.getMutationType(); - if (schemaMutationType && graphqlType.name === schemaMutationType.name) { - return false; - } - const schemaQueryType = schema.getMutationType(); - if (schemaQueryType && graphqlType.name === schemaQueryType.name) { - return false; - } - const schemaSubscriptionType = schema.getMutationType(); - if (schemaSubscriptionType && graphqlType.name === schemaSubscriptionType.name) { - return false; - } - - return true; - } - - return false; - }); - - return modelTypes; -} diff --git a/packages/utils/src/getArgumentValues.ts b/packages/utils/src/getArgumentValues.ts index da5bb44e1f6..422f2ede9c5 100644 --- a/packages/utils/src/getArgumentValues.ts +++ b/packages/utils/src/getArgumentValues.ts @@ -11,7 +11,7 @@ import { ArgumentNode, } from 'graphql'; -import inspectUtils from 'graphql/jsutils/inspect.js'; +import { inspect } from './inspect'; /** * Prepares an object map of argument values given a list of argument @@ -54,7 +54,7 @@ export function getArgumentValues( coercedValues[name] = defaultValue; } else if (isNonNullType(argType)) { throw new GraphQLError( - `Argument "${name}" of required type "${inspectUtils.inspect(argType)}" ` + 'was not provided.', + `Argument "${name}" of required type "${inspect(argType)}" ` + 'was not provided.', node ); } @@ -71,7 +71,7 @@ export function getArgumentValues( coercedValues[name] = defaultValue; } else if (isNonNullType(argType)) { throw new GraphQLError( - `Argument "${name}" of required type "${inspectUtils.inspect(argType)}" ` + + `Argument "${name}" of required type "${inspect(argType)}" ` + `was provided the variable "$${variableName}" which was not provided a runtime value.`, valueNode ); @@ -83,7 +83,7 @@ export function getArgumentValues( if (isNull && isNonNullType(argType)) { throw new GraphQLError( - `Argument "${name}" of non-null type "${inspectUtils.inspect(argType)}" ` + 'must not be null.', + `Argument "${name}" of non-null type "${inspect(argType)}" ` + 'must not be null.', valueNode ); } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 684445e201b..0dac4a5399a 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -6,11 +6,8 @@ export * from './get-implementing-types'; export * from './print-schema-with-directives'; export * from './get-fields-with-directives'; export * from './validate-documents'; -export * from './fix-schema-ast'; export * from './parse-graphql-json'; export * from './parse-graphql-sdl'; -export * from './get-user-types-from-schema'; -export * from './create-schema-definition'; export * from './build-operation-for-field'; export * from './types'; export * from './filterSchema'; @@ -46,3 +43,6 @@ export * from './withCancel'; export * from './AggregateError'; export * from './rootTypes'; export * from './comments'; +export * from './collectFields'; +export * from './inspect'; +export * from './memoize'; diff --git a/packages/utils/src/inspect.ts b/packages/utils/src/inspect.ts new file mode 100644 index 00000000000..d91ffffba74 --- /dev/null +++ b/packages/utils/src/inspect.ts @@ -0,0 +1,110 @@ +// Taken from graphql-js +// https://github.com/graphql/graphql-js/blob/main/src/jsutils/inspect.ts +/* eslint-disable @typescript-eslint/ban-types */ +const MAX_ARRAY_LENGTH = 10; +const MAX_RECURSIVE_DEPTH = 2; + +/** + * Used to print values in error messages. + */ +export function inspect(value: unknown): string { + return formatValue(value, []); +} + +function formatValue(value: unknown, seenValues: ReadonlyArray): string { + switch (typeof value) { + case 'string': + return JSON.stringify(value); + case 'function': + return value.name ? `[function ${value.name}]` : '[function]'; + case 'object': + return formatObjectValue(value, seenValues); + default: + return String(value); + } +} + +function formatObjectValue(value: object | null, previouslySeenValues: ReadonlyArray): string { + if (value === null) { + return 'null'; + } + + if (previouslySeenValues.includes(value)) { + return '[Circular]'; + } + + const seenValues = [...previouslySeenValues, value]; + + if (isJSONable(value)) { + const jsonValue = value.toJSON(); + + // check for infinite recursion + if (jsonValue !== value) { + return typeof jsonValue === 'string' ? jsonValue : formatValue(jsonValue, seenValues); + } + } else if (Array.isArray(value)) { + return formatArray(value, seenValues); + } + + return formatObject(value, seenValues); +} + +function isJSONable(value: any): value is { toJSON: () => unknown } { + return typeof value.toJSON === 'function'; +} + +function formatObject(object: object, seenValues: ReadonlyArray): string { + const entries = Object.entries(object); + if (entries.length === 0) { + return '{}'; + } + + if (seenValues.length > MAX_RECURSIVE_DEPTH) { + return '[' + getObjectTag(object) + ']'; + } + + const properties = entries.map(([key, value]) => key + ': ' + formatValue(value, seenValues)); + return '{ ' + properties.join(', ') + ' }'; +} + +function formatArray(array: ReadonlyArray, seenValues: ReadonlyArray): string { + if (array.length === 0) { + return '[]'; + } + + if (seenValues.length > MAX_RECURSIVE_DEPTH) { + return '[Array]'; + } + + const len = Math.min(MAX_ARRAY_LENGTH, array.length); + const remaining = array.length - len; + const items = []; + + for (let i = 0; i < len; ++i) { + items.push(formatValue(array[i], seenValues)); + } + + if (remaining === 1) { + items.push('... 1 more item'); + } else if (remaining > 1) { + items.push(`... ${remaining} more items`); + } + + return '[' + items.join(', ') + ']'; +} + +function getObjectTag(object: object): string { + const tag = Object.prototype.toString + .call(object) + .replace(/^\[object /, '') + .replace(/]$/, ''); + + if (tag === 'Object' && typeof object.constructor === 'function') { + const name = object.constructor.name; + if (typeof name === 'string' && name !== '') { + return name; + } + } + + return tag; +} diff --git a/packages/utils/src/memoize.ts b/packages/utils/src/memoize.ts new file mode 100644 index 00000000000..7f68a4133d0 --- /dev/null +++ b/packages/utils/src/memoize.ts @@ -0,0 +1,202 @@ +export function memoize1 any>(fn: F): F { + const memoize1cache: WeakMap, WeakMap, any>> = new WeakMap(); + return function memoized(a1: any): any { + const cachedValue = memoize1cache.get(a1); + if (cachedValue === undefined) { + const newValue = fn(a1); + memoize1cache.set(a1, newValue); + return newValue; + } + + return cachedValue; + } as F; +} + +export function memoize2 any>(fn: F): F { + const memoize2cache: WeakMap, WeakMap, any>> = new WeakMap(); + return function memoized(a1: any, a2: any): any { + let cache2 = memoize2cache.get(a1); + if (!cache2) { + cache2 = new WeakMap(); + memoize2cache.set(a1, cache2); + const newValue = fn(a1, a2); + cache2.set(a2, newValue); + return newValue; + } + + const cachedValue = cache2.get(a2); + if (cachedValue === undefined) { + const newValue = fn(a1, a2); + cache2.set(a2, newValue); + return newValue; + } + + return cachedValue; + } as F; +} + +export function memoize3 any>(fn: F): F { + const memoize3Cache: WeakMap, WeakMap, any>> = new WeakMap(); + return function memoized(a1: any, a2: any, a3: any) { + let cache2 = memoize3Cache.get(a1); + if (!cache2) { + cache2 = new WeakMap(); + memoize3Cache.set(a1, cache2); + const cache3 = new WeakMap(); + cache2.set(a2, cache3); + const newValue = fn(a1, a2, a3); + cache3.set(a3, newValue); + return newValue; + } + + let cache3 = cache2.get(a2); + if (!cache3) { + cache3 = new WeakMap(); + cache2.set(a2, cache3); + const newValue = fn(a1, a2, a3); + cache3.set(a3, newValue); + return newValue; + } + + const cachedValue = cache3.get(a3); + if (cachedValue === undefined) { + const newValue = fn(a1, a2, a3); + cache3.set(a3, newValue); + return newValue; + } + + return cachedValue; + } as F; +} + +export function memoize4 any>(fn: F): F { + const memoize4Cache: WeakMap, WeakMap, any>> = new WeakMap(); + return function memoized(a1: any, a2: any, a3: any, a4: any) { + let cache2 = memoize4Cache.get(a1); + if (!cache2) { + cache2 = new WeakMap(); + memoize4Cache.set(a1, cache2); + const cache3 = new WeakMap(); + cache2.set(a2, cache3); + const cache4 = new WeakMap(); + cache3.set(a3, cache4); + const newValue = fn(a1, a2, a3, a4); + cache4.set(a4, newValue); + return newValue; + } + + let cache3 = cache2.get(a2); + if (!cache3) { + cache3 = new WeakMap(); + cache2.set(a2, cache3); + const cache4 = new WeakMap(); + cache3.set(a3, cache4); + const newValue = fn(a1, a2, a3, a4); + cache4.set(a4, newValue); + return newValue; + } + + const cache4 = cache3.get(a3); + if (!cache4) { + const cache4 = new WeakMap(); + cache3.set(a3, cache4); + const newValue = fn(a1, a2, a3, a4); + cache4.set(a4, newValue); + return newValue; + } + + const cachedValue = cache4.get(a4); + if (cachedValue === undefined) { + const newValue = fn(a1, a2, a3, a4); + cache4.set(a4, newValue); + return newValue; + } + + return cachedValue; + } as F; +} + +export function memoize5 any>(fn: F): F { + const memoize5Cache: WeakMap, WeakMap, any>> = new WeakMap(); + return function memoized(a1: any, a2: any, a3: any, a4: any, a5: any) { + let cache2 = memoize5Cache.get(a1); + if (!cache2) { + cache2 = new WeakMap(); + memoize5Cache.set(a1, cache2); + const cache3 = new WeakMap(); + cache2.set(a2, cache3); + const cache4 = new WeakMap(); + cache3.set(a3, cache4); + const cache5 = new WeakMap(); + cache4.set(a4, cache5); + const newValue = fn(a1, a2, a3, a4, a5); + cache5.set(a5, newValue); + return newValue; + } + + let cache3 = cache2.get(a2); + if (!cache3) { + cache3 = new WeakMap(); + cache2.set(a2, cache3); + const cache4 = new WeakMap(); + cache3.set(a3, cache4); + const cache5 = new WeakMap(); + cache4.set(a4, cache5); + const newValue = fn(a1, a2, a3, a4, a5); + cache5.set(a5, newValue); + return newValue; + } + + let cache4 = cache3.get(a3); + if (!cache4) { + cache4 = new WeakMap(); + cache3.set(a3, cache4); + const cache5 = new WeakMap(); + cache4.set(a4, cache5); + const newValue = fn(a1, a2, a3, a4, a5); + cache5.set(a5, newValue); + return newValue; + } + + let cache5 = cache4.get(a4); + if (!cache5) { + cache5 = new WeakMap(); + cache4.set(a4, cache5); + const newValue = fn(a1, a2, a3, a4, a5); + cache5.set(a5, newValue); + return newValue; + } + + const cachedValue = cache5.get(a5); + if (cachedValue === undefined) { + const newValue = fn(a1, a2, a3, a4, a5); + cache5.set(a5, newValue); + return newValue; + } + + return cachedValue; + } as F; +} + +const memoize2of4cache: WeakMap, WeakMap, any>> = new WeakMap(); +export function memoize2of4 any>(fn: F): F { + return function memoized(a1: any, a2: any, a3: any, a4: any): any { + let cache2 = memoize2of4cache.get(a1); + if (!cache2) { + cache2 = new WeakMap(); + memoize2of4cache.set(a1, cache2); + const newValue = fn(a1, a2, a3, a4); + cache2.set(a2, newValue); + return newValue; + } + + const cachedValue = cache2.get(a2); + if (cachedValue === undefined) { + const newValue = fn(a1, a2, a3, a4); + cache2.set(a2, newValue); + return newValue; + } + + return cachedValue; + } as F; +} diff --git a/packages/utils/src/print-schema-with-directives.ts b/packages/utils/src/print-schema-with-directives.ts index b12aaa136ab..bca3ab371bb 100644 --- a/packages/utils/src/print-schema-with-directives.ts +++ b/packages/utils/src/print-schema-with-directives.ts @@ -54,6 +54,7 @@ import { astFromType } from './astFromType'; import { getDirectivesInExtensions } from './get-directives'; import { astFromValueUntyped } from './astFromValueUntyped'; import { isSome } from './helpers'; +import { getRootTypeMap } from './rootTypes'; export function getDocumentNodeFromSchema( schema: GraphQLSchema, @@ -121,11 +122,11 @@ export function astFromSchema( schema: GraphQLSchema, pathToDirectivesInExtensions?: Array ): SchemaDefinitionNode | SchemaExtensionNode | null { - const operationTypeMap: Record> = { - query: undefined, - mutation: undefined, - subscription: undefined, - }; + const operationTypeMap = new Map([ + ['query', undefined], + ['mutation', undefined], + ['subscription', undefined], + ]); const nodes: Array = []; if (schema.astNode != null) { @@ -140,32 +141,30 @@ export function astFromSchema( for (const node of nodes) { if (node.operationTypes) { for (const operationTypeDefinitionNode of node.operationTypes) { - operationTypeMap[operationTypeDefinitionNode.operation] = operationTypeDefinitionNode; + operationTypeMap.set(operationTypeDefinitionNode.operation, operationTypeDefinitionNode); } } } - const rootTypeMap: Record> = { - query: schema.getQueryType(), - mutation: schema.getMutationType(), - subscription: schema.getSubscriptionType(), - }; + const rootTypeMap = getRootTypeMap(schema); - for (const operationTypeNode in operationTypeMap) { - if (rootTypeMap[operationTypeNode] != null) { - if (operationTypeMap[operationTypeNode] != null) { - operationTypeMap[operationTypeNode].type = astFromType(rootTypeMap[operationTypeNode]); + for (const [operationTypeNode, operationTypeDefinitionNode] of operationTypeMap) { + const rootType = rootTypeMap.get(operationTypeNode as OperationTypeNode); + if (rootType != null) { + const rootTypeAST = astFromType(rootType); + if (operationTypeDefinitionNode != null) { + (operationTypeDefinitionNode as any).type = rootTypeAST; } else { - operationTypeMap[operationTypeNode] = { + operationTypeMap.set(operationTypeNode, { kind: Kind.OPERATION_TYPE_DEFINITION, operation: operationTypeNode, - type: astFromType(rootTypeMap[operationTypeNode]), - }; + type: rootTypeAST, + } as OperationTypeDefinitionNode); } } } - const operationTypes = Object.values(operationTypeMap).filter(isSome); + const operationTypes = [...operationTypeMap.values()].filter(isSome); const directives = getDirectiveNodes(schema, schema, pathToDirectivesInExtensions); @@ -214,16 +213,13 @@ export function astFromDirective( kind: Kind.NAME, value: directive.name, }, - arguments: directive?.args - ? directive.args.map(arg => astFromArg(arg, schema, pathToDirectivesInExtensions)) - : undefined, + arguments: directive.args?.map(arg => astFromArg(arg, schema, pathToDirectivesInExtensions)), repeatable: directive.isRepeatable, - locations: directive?.locations - ? directive.locations.map(location => ({ - kind: Kind.NAME, - value: location, - })) - : [], + locations: + directive.locations?.map(location => ({ + kind: Kind.NAME, + value: location, + })) || [], }; } @@ -469,36 +465,19 @@ export function astFromScalarType( schema: GraphQLSchema, pathToDirectivesInExtensions?: Array ): ScalarTypeDefinitionNode { - let directiveNodesBesidesSpecifiedBy: Array = []; - let specifiedByDirectiveNode: Maybe = null; - const directivesInExtensions = getDirectivesInExtensions(type, pathToDirectivesInExtensions); - let allDirectives: Maybe>; - if (directivesInExtensions != null) { - allDirectives = makeDirectiveNodes(schema, directivesInExtensions); - } else { - allDirectives = type.astNode?.directives; - } - - if (allDirectives != null) { - directiveNodesBesidesSpecifiedBy = allDirectives.filter(directive => directive.name.value !== 'specifiedBy'); - if ((type as unknown as { specifiedByUrl: string }).specifiedByUrl != null) { - specifiedByDirectiveNode = allDirectives.filter(directive => directive.name.value === 'specifiedBy')?.[0]; - } - } + const directives: DirectiveNode[] = directivesInExtensions + ? makeDirectiveNodes(schema, directivesInExtensions) + : (type.astNode?.directives as DirectiveNode[]) || []; - if ((type as unknown as { specifiedByUrl: string }).specifiedByUrl != null && specifiedByDirectiveNode == null) { - specifiedByDirectiveNode = makeDirectiveNode('specifiedBy', { - url: (type as unknown as { specifiedByUrl: string }).specifiedByUrl, - }); + if ('specifiedBy' in type && !directives.some(directiveNode => directiveNode.name.value === 'specifiedBy')) { + const specifiedByArgs = { + url: (type as any)['specifiedByUrl'], + }; + directives.push(makeDirectiveNode('specifiedBy', specifiedByArgs)); } - const directives = - specifiedByDirectiveNode == null - ? directiveNodesBesidesSpecifiedBy - : [specifiedByDirectiveNode].concat(directiveNodesBesidesSpecifiedBy); - return { kind: Kind.SCALAR_TYPE_DEFINITION, description: diff --git a/packages/utils/src/rootTypes.ts b/packages/utils/src/rootTypes.ts index c7ab9676cd7..21daf3f01ef 100644 --- a/packages/utils/src/rootTypes.ts +++ b/packages/utils/src/rootTypes.ts @@ -1,19 +1,9 @@ import { GraphQLObjectType, GraphQLSchema, OperationTypeNode } from 'graphql'; -import { Maybe } from './types'; +import { memoize1 } from './memoize'; export function getDefinedRootType(schema: GraphQLSchema, operation: OperationTypeNode): GraphQLObjectType { - let rootType: Maybe; - if (operation === 'query') { - rootType = schema.getQueryType(); - } else if (operation === 'mutation') { - rootType = schema.getMutationType(); - } else if (operation === 'subscription') { - rootType = schema.getSubscriptionType(); - } else { - // Future proof against new operation types - throw new Error(`Unknown operation "${operation}", cannot get root type.`); - } - + const rootTypeMap = getRootTypeMap(schema); + const rootType = rootTypeMap.get(operation); if (rootType == null) { throw new Error(`Root type for operation "${operation}" not defined by the given schema.`); } @@ -21,37 +11,19 @@ export function getDefinedRootType(schema: GraphQLSchema, operation: OperationTy return rootType; } -export function getRootTypeNames(schema: GraphQLSchema): Set { - const rootTypeNames: Set = new Set(); - const queryType = schema.getQueryType(); - const mutationType = schema.getMutationType(); - const subscriptionType = schema.getSubscriptionType(); +export const getRootTypeNames = memoize1(function getRootTypeNames(schema: GraphQLSchema): Set { + const rootTypes = getRootTypes(schema); + return new Set([...rootTypes].map(type => type.name)); +}); - for (const rootType of [queryType, mutationType, subscriptionType]) { - if (rootType) { - rootTypeNames.add(rootType.name); - } - } - - return rootTypeNames; -} +export const getRootTypes = memoize1(function getRootTypes(schema: GraphQLSchema): Set { + const rootTypeMap = getRootTypeMap(schema); + return new Set(rootTypeMap.values()); +}); -export function getRootTypes(schema: GraphQLSchema): Set { - const rootTypes: Set = new Set(); - const queryType = schema.getQueryType(); - const mutationType = schema.getMutationType(); - const subscriptionType = schema.getSubscriptionType(); - - for (const rootType of [queryType, mutationType, subscriptionType]) { - if (rootType) { - rootTypes.add(rootType); - } - } - - return rootTypes; -} - -export function getRootTypeMap(schema: GraphQLSchema): Map { +export const getRootTypeMap = memoize1(function getRootTypeMap( + schema: GraphQLSchema +): Map { const rootTypeMap: Map = new Map(); const queryType = schema.getQueryType(); @@ -70,4 +42,4 @@ export function getRootTypeMap(schema: GraphQLSchema): Map any; @@ -82,16 +83,14 @@ export function visitResult( resultVisitorMap?: ResultVisitorMap, errorVisitorMap?: ErrorVisitorMap ): any { - const partialExecutionContext = { - schema, - fragments: request.document.definitions.reduce((acc, def) => { - if (def.kind === Kind.FRAGMENT_DEFINITION) { - acc[def.name.value] = def; - } - return acc; - }, {}), - variableValues: request.variables, - } as ExecutionContext; + const fragments = request.document.definitions.reduce((acc, def) => { + if (def.kind === Kind.FRAGMENT_DEFINITION) { + acc[def.name.value] = def; + } + return acc; + }, {}); + + const variableValues = request.variables || {}; const errorInfo: ErrorInfo = { segmentInfoMap: new Map>(), @@ -108,7 +107,9 @@ export function visitResult( result.data = visitRoot( data, operationDocumentNode, - partialExecutionContext, + schema, + fragments, + variableValues, resultVisitorMap, visitingErrors ? errors : undefined, errorInfo @@ -157,28 +158,45 @@ function visitErrorsByType( function visitRoot( root: any, operation: OperationDefinitionNode, - exeContext: ExecutionContext, + schema: GraphQLSchema, + fragments: Record, + variableValues: Record, resultVisitorMap: Maybe, errors: Maybe>, errorInfo: ErrorInfo ): any { - const operationRootType = getOperationRootType(exeContext.schema, operation); + const operationRootType = getOperationRootType(schema, operation); const collectedFields = collectFields( - exeContext, + schema, + fragments, + variableValues, operationRootType, operation.selectionSet, - Object.create(null), - Object.create(null) + new Map(), + new Set() ); - return visitObjectValue(root, operationRootType, collectedFields, exeContext, resultVisitorMap, 0, errors, errorInfo); + return visitObjectValue( + root, + operationRootType, + collectedFields, + schema, + fragments, + variableValues, + resultVisitorMap, + 0, + errors, + errorInfo + ); } function visitObjectValue( object: Record, type: GraphQLObjectType, - fieldNodeMap: Record>, - exeContext: ExecutionContext, + fieldNodeMap: Map, + schema: GraphQLSchema, + fragments: Record, + variableValues: Record, resultVisitorMap: Maybe, pathIndex: number, errors: Maybe>, @@ -200,8 +218,7 @@ function visitObjectValue( } } - for (const responseKey in fieldNodeMap) { - const subFieldNodes = fieldNodeMap[responseKey]; + for (const [responseKey, subFieldNodes] of fieldNodeMap) { const fieldName = subFieldNodes[0].name.value; const fieldType = fieldName === '__typename' ? TypeNameMetaFieldDef.type : fieldMap[fieldName].type; @@ -220,7 +237,9 @@ function visitObjectValue( object[responseKey], fieldType, subFieldNodes, - exeContext, + schema, + fragments, + variableValues, resultVisitorMap, newPathIndex, fieldErrors, @@ -236,7 +255,8 @@ function visitObjectValue( } if (errorMap) { - for (const errors of Object.values(errorMap)) { + for (const errorsKey in errorMap) { + const errors = errorMap[errorsKey]; for (const error of errors) { errorInfo.unpathedErrors.add(error); } @@ -279,14 +299,27 @@ function visitListValue( list: Array, returnType: GraphQLOutputType, fieldNodes: Array, - exeContext: ExecutionContext, + schema: GraphQLSchema, + fragments: Record, + variableValues: Record, resultVisitorMap: Maybe, pathIndex: number, errors: ReadonlyArray, errorInfo: ErrorInfo ): Array { return list.map(listMember => - visitFieldValue(listMember, returnType, fieldNodes, exeContext, resultVisitorMap, pathIndex + 1, errors, errorInfo) + visitFieldValue( + listMember, + returnType, + fieldNodes, + schema, + fragments, + variableValues, + resultVisitorMap, + pathIndex + 1, + errors, + errorInfo + ) ); } @@ -294,7 +327,9 @@ function visitFieldValue( value: any, returnType: GraphQLOutputType, fieldNodes: Array, - exeContext: ExecutionContext, + schema: GraphQLSchema, + fragments: Record, + variableValues: Record, resultVisitorMap: Maybe, pathIndex: number, errors: ReadonlyArray | undefined = [], @@ -310,32 +345,38 @@ function visitFieldValue( value as Array, nullableType.ofType, fieldNodes, - exeContext, + schema, + fragments, + variableValues, resultVisitorMap, pathIndex, errors, errorInfo ); } else if (isAbstractType(nullableType)) { - const finalType = exeContext.schema.getType(value.__typename) as GraphQLObjectType; - const collectedFields = collectSubFields(exeContext, finalType, fieldNodes); + const finalType = schema.getType(value.__typename) as GraphQLObjectType; + const collectedFields = collectSubFields(schema, fragments, variableValues, finalType, fieldNodes); return visitObjectValue( value, finalType, collectedFields, - exeContext, + schema, + fragments, + variableValues, resultVisitorMap, pathIndex, errors, errorInfo ); } else if (isObjectType(nullableType)) { - const collectedFields = collectSubFields(exeContext, nullableType, fieldNodes); + const collectedFields = collectSubFields(schema, fragments, variableValues, nullableType, fieldNodes); return visitObjectValue( value, nullableType, collectedFields, - exeContext, + schema, + fragments, + variableValues, resultVisitorMap, pathIndex, errors, @@ -396,20 +437,3 @@ function addPathSegmentInfo( } } } - -function collectSubFields( - exeContext: ExecutionContext, - type: GraphQLObjectType, - fieldNodes: Array -): Record> { - let subFieldNodes: Record> = Object.create(null); - const visitedFragmentNames = Object.create(null); - - for (const fieldNode of fieldNodes) { - if (fieldNode.selectionSet) { - subFieldNodes = collectFields(exeContext, type, fieldNode.selectionSet, subFieldNodes, visitedFragmentNames); - } - } - - return subFieldNodes; -} diff --git a/packages/utils/tests/__snapshots__/parse-graphql-sdl.spec.ts.snap b/packages/utils/tests/__snapshots__/parse-graphql-sdl.spec.ts.snap index 42acdf69740..6b707216cd0 100644 --- a/packages/utils/tests/__snapshots__/parse-graphql-sdl.spec.ts.snap +++ b/packages/utils/tests/__snapshots__/parse-graphql-sdl.spec.ts.snap @@ -49,8 +49,7 @@ interface Node { } \\"Custom scalar test\\" -scalar Date -" +scalar Date" `; exports[`parse sdl comment parsing should transform comments to descriptions correctly on all available nodes with noLocation=true 1`] = ` @@ -102,6 +101,5 @@ interface Node { } \\"Custom scalar test\\" -scalar Date -" +scalar Date" `; diff --git a/packages/utils/tests/get-directives.spec.ts b/packages/utils/tests/get-directives.spec.ts index 4058d9b4eed..92b46081680 100644 --- a/packages/utils/tests/get-directives.spec.ts +++ b/packages/utils/tests/get-directives.spec.ts @@ -5,7 +5,7 @@ import { GraphQLSchema } from 'graphql'; describe('getDirectives', () => { it('should return the correct directives when no directives specified', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { test: String } @@ -17,7 +17,7 @@ describe('getDirectives', () => { }); it('should return the correct directives built-in directive specified over FIELD_DEFINITION', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { test: String @deprecated } @@ -34,7 +34,7 @@ describe('getDirectives', () => { }); it('should return the correct directives when using custom directive without arguments', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { test: String @mydir } @@ -51,7 +51,7 @@ describe('getDirectives', () => { }); it('should return the correct directives when using custom directive with optional argument', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { test: String @mydir(f1: "test") } @@ -70,7 +70,7 @@ describe('getDirectives', () => { }); it('should return the correct directives when using custom directive with optional argument an no value', () => { - const typeDefs = ` + const typeDefs = /* GraphQL */` type Query { test: String @mydir } diff --git a/packages/utils/tests/parse-graphql-sdl.spec.ts b/packages/utils/tests/parse-graphql-sdl.spec.ts index ecbe6526984..b4e596c5553 100644 --- a/packages/utils/tests/parse-graphql-sdl.spec.ts +++ b/packages/utils/tests/parse-graphql-sdl.spec.ts @@ -69,7 +69,7 @@ describe('parse sdl', () => { it('should transform comments to descriptions correctly on all available nodes', () => { const transformed = transformCommentsToDescriptions(ast); - const printed = print(transformed); + const printed = print(transformed).trim(); expect(printed).toMatchSnapshot(); }); @@ -79,7 +79,7 @@ describe('parse sdl', () => { const type = transformed.document.definitions.find((d): d is ObjectTypeDefinitionNode => "name" in d && d.name?.value === 'Type'); expect(type?.description?.value).toBe('test type comment'); expect(type?.loc).not.toBeDefined(); - const printed = print(transformed.document); + const printed = print(transformed.document).trim(); expect(printed).toMatchSnapshot(); }); }); diff --git a/packages/utils/tests/prune.test.ts b/packages/utils/tests/prune.test.ts index b1d2aeca77a..4fc26070804 100644 --- a/packages/utils/tests/prune.test.ts +++ b/packages/utils/tests/prune.test.ts @@ -4,7 +4,7 @@ import { PruneSchemaFilter } from '../src'; describe('pruneSchema', () => { test('can handle recursive input types', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` input Input { moreInput: Input } @@ -17,7 +17,7 @@ describe('pruneSchema', () => { }); test('removes unused enums', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` enum Unused { VALUE } @@ -31,7 +31,7 @@ describe('pruneSchema', () => { }); test('removes unused objects', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` type Unused { value: String } @@ -45,7 +45,7 @@ describe('pruneSchema', () => { }); test('does not remove unused objects when skipUnusedTypesPruning is true', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` type Unused { value: String } @@ -61,7 +61,7 @@ describe('pruneSchema', () => { }); test('removes unused input objects', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` input Unused { value: String } @@ -75,7 +75,7 @@ describe('pruneSchema', () => { }); test('does not remove unused input objects when skipUnusedTypesPruning is true', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` input Unused { value: String } @@ -91,7 +91,7 @@ describe('pruneSchema', () => { }); test('removes unused unions', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` union Unused type Query { @@ -103,7 +103,7 @@ describe('pruneSchema', () => { }); test('does not remove unused unions when skipEmptyUnionPruning is true', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` union Unused type Query { @@ -118,7 +118,7 @@ describe('pruneSchema', () => { }); test('removes unused interfaces', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` interface Unused { value: String } @@ -132,7 +132,7 @@ describe('pruneSchema', () => { }); test('does not remove unused interfaces when skipUnimplementedInterfacesPruning is true', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` interface Unused { value: String } @@ -149,11 +149,11 @@ describe('pruneSchema', () => { }); test('removes top level objects with no fields', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` type Query { foo: Boolean } - + type Mutation `); const result = pruneSchema(schema); @@ -161,11 +161,11 @@ describe('pruneSchema', () => { }); test('does not removes objects with no fields when skipEmptyCompositeTypePruning is true', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` type Query { foo: Boolean } - + type Foo `); const result = pruneSchema(schema, { @@ -176,11 +176,11 @@ describe('pruneSchema', () => { }); test('removes unused interfaces when implementations are unused', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` interface UnusedInterface { value: String } - + type UnusedType implements UnusedInterface { value: String } @@ -195,9 +195,9 @@ describe('pruneSchema', () => { }); test('removes unused unions when implementations are unused', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` union UnusedUnion = UnusedType - + type UnusedType { value: String } @@ -212,7 +212,7 @@ describe('pruneSchema', () => { }); test('does not throw on pruning unimplemented interfaces', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` interface UnimplementedInterface { value: String } @@ -226,9 +226,9 @@ describe('pruneSchema', () => { }); test('does not prune types that match the filter', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` directive @bar on OBJECT - + type CustomType @bar { value: String } diff --git a/packages/utils/tests/valueMatchesCriteria.test.ts b/packages/utils/tests/valueMatchedCriteria.test.ts similarity index 100% rename from packages/utils/tests/valueMatchesCriteria.test.ts rename to packages/utils/tests/valueMatchedCriteria.test.ts diff --git a/packages/utils/tests/visitResult.test.ts b/packages/utils/tests/visitResult.test.ts index 1294530b73d..212ccbded4c 100644 --- a/packages/utils/tests/visitResult.test.ts +++ b/packages/utils/tests/visitResult.test.ts @@ -7,7 +7,7 @@ import { relocatedError } from '../src/errors'; import { visitResult } from '../src/visitResult'; describe('visiting results', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` interface TestInterface { field: String } @@ -205,7 +205,7 @@ describe('visiting results', () => { }); describe('visiting nested results', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` type User { name: String } @@ -273,7 +273,7 @@ describe('visiting nested results', () => { }); describe('visiting nested results', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` type User { name: String } @@ -341,7 +341,7 @@ describe('visiting nested results', () => { }); describe('visiting errors', () => { - const schema = buildSchema(` + const schema = buildSchema(/* GraphQL */` interface TestInterface { field: String } diff --git a/packages/webpack-loader-runtime/package.json b/packages/webpack-loader-runtime/package.json index 87c6d824576..3f826639c44 100644 --- a/packages/webpack-loader-runtime/package.json +++ b/packages/webpack-loader-runtime/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/webpack-loader/package.json b/packages/webpack-loader/package.json index 3d50a628ca2..52101a4d552 100644 --- a/packages/webpack-loader/package.json +++ b/packages/webpack-loader/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/wrap/package.json b/packages/wrap/package.json index ea6735097d4..c9d27872607 100644 --- a/packages/wrap/package.json +++ b/packages/wrap/package.json @@ -26,7 +26,7 @@ "definition": "dist/index.d.ts" }, "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0" + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "buildOptions": { "input": "./src/index.ts" diff --git a/packages/wrap/src/transforms/TransformCompositeFields.ts b/packages/wrap/src/transforms/TransformCompositeFields.ts index 10b2fc06f41..1ce093539c2 100644 --- a/packages/wrap/src/transforms/TransformCompositeFields.ts +++ b/packages/wrap/src/transforms/TransformCompositeFields.ts @@ -114,9 +114,8 @@ export default class TransformCompositeFields> im return visit( document, visitWithTypeInfo(this._getTypeInfo(), { - leave: { - [Kind.SELECTION_SET]: node => - this.transformSelectionSet(node, this._getTypeInfo(), fragments, transformationContext), + [Kind.SELECTION_SET]: { + leave: node => this.transformSelectionSet(node, this._getTypeInfo(), fragments, transformationContext), }, }) ); diff --git a/packages/wrap/src/transforms/TransformInputObjectFields.ts b/packages/wrap/src/transforms/TransformInputObjectFields.ts index 00bc5ea8098..c3ac9b46aeb 100644 --- a/packages/wrap/src/transforms/TransformInputObjectFields.ts +++ b/packages/wrap/src/transforms/TransformInputObjectFields.ts @@ -157,8 +157,8 @@ export default class TransformInputObjectFields implements Transform { const newDocument: DocumentNode = visit( document, visitWithTypeInfo(typeInfo, { - leave: { - [Kind.OBJECT]: (node: ObjectValueNode): ObjectValueNode | undefined => { + [Kind.OBJECT]: { + leave: (node: ObjectValueNode): ObjectValueNode | undefined => { // The casting is kind of legit here as we are in a visitor const parentType = typeInfo.getInputType() as Maybe; if (parentType != null) { diff --git a/packages/wrap/tests/fixtures/schemas.ts b/packages/wrap/tests/fixtures/schemas.ts deleted file mode 100644 index 66b536676a2..00000000000 --- a/packages/wrap/tests/fixtures/schemas.ts +++ /dev/null @@ -1,687 +0,0 @@ -import { PubSub } from 'graphql-subscriptions'; -import { - GraphQLSchema, - Kind, - GraphQLScalarType, - ValueNode, - GraphQLResolveInfo, - GraphQLError, - GraphQLInterfaceType, -} from 'graphql'; - - -import { introspectSchema } from '../../src/introspect'; -import { - IResolvers, - AsyncExecutor, -} from '@graphql-tools/utils'; -import { makeExecutableSchema } from '@graphql-tools/schema'; -import { createDefaultExecutor, SubschemaConfig } from '@graphql-tools/delegate'; - -export class CustomError extends GraphQLError { - constructor(message: string, extensions: Record) { - super( - message, - undefined, - undefined, - undefined, - undefined, - undefined, - extensions, - ); - } -} - -export type Location = { - name: string; - coordinates: string; -}; - -export type Property = { - id: string; - name: string; - location: Location; -}; - -export type Product = { - id: string; - price?: number; - url?: string; - type: string; -}; - -export type Booking = { - id: string; - propertyId: string; - customerId: string; - startTime: string; - endTime: string; -}; - -export type Customer = { - id: string; - email: string; - name: string; - address?: string; - vehicleId?: string; -}; - -export type Vehicle = { - id: string; - licensePlate?: string; - bikeType?: 'MOUNTAIN' | 'ROAD'; -}; - -export const sampleData: { - Property: Record; - Product: Record; - Booking: Record; - Customer: Record; - Vehicle: Record; -} = { - Product: { - pd1: { - id: 'pd1', - type: 'simple', - price: 100, - }, - pd2: { - id: 'pd2', - type: 'download', - url: 'https://graphql.org', - }, - }, - Property: { - p1: { - id: 'p1', - name: 'Super great hotel', - location: { - name: 'Helsinki', - coordinates: '60.1698° N, 24.9383° E', - }, - }, - p2: { - id: 'p2', - name: 'Another great hotel', - location: { - name: 'San Francisco', - coordinates: '37.7749° N, 122.4194° W', - }, - }, - p3: { - id: 'p3', - name: 'BedBugs - The Affordable Hostel', - location: { - name: 'Helsinki', - coordinates: '60.1699° N, 24.9384° E', - }, - }, - }, - Booking: { - b1: { - id: 'b1', - propertyId: 'p1', - customerId: 'c1', - startTime: '2016-05-04', - endTime: '2016-06-03', - }, - b2: { - id: 'b2', - propertyId: 'p1', - customerId: 'c2', - startTime: '2016-06-04', - endTime: '2016-07-03', - }, - b3: { - id: 'b3', - propertyId: 'p1', - customerId: 'c3', - startTime: '2016-08-04', - endTime: '2016-09-03', - }, - b4: { - id: 'b4', - propertyId: 'p2', - customerId: 'c1', - startTime: '2016-10-04', - endTime: '2016-10-03', - }, - }, - - Customer: { - c1: { - id: 'c1', - email: 'examplec1@example.com', - name: 'Exampler Customer', - vehicleId: 'v1', - }, - c2: { - id: 'c2', - email: 'examplec2@example.com', - name: 'Joe Doe', - vehicleId: 'v2', - }, - c3: { - id: 'c3', - email: 'examplec3@example.com', - name: 'Liisa Esimerki', - address: 'Esimerkikatu 1 A 77, 99999 Kyyjarvi', - }, - }, - - Vehicle: { - v1: { - id: 'v1', - bikeType: 'MOUNTAIN', - }, - v2: { - id: 'v2', - licensePlate: 'GRAPHQL', - }, - }, -}; - -function coerceString(value: any): string { - if (Array.isArray(value)) { - throw new TypeError( - `String cannot represent an array value: [${String(value)}]`, - ); - } - return String(value); -} - -const DateTime = new GraphQLScalarType({ - name: 'DateTime', - description: 'Simple fake datetime', - serialize: coerceString, - parseValue: coerceString, - parseLiteral(ast) { - return ast.kind === Kind.STRING ? ast.value : null; - }, -}); - -function identity(value: any): any { - return value; -} - -function parseLiteral(ast: ValueNode): any { - switch (ast.kind) { - case Kind.STRING: - case Kind.BOOLEAN: - return ast.value; - case Kind.INT: - case Kind.FLOAT: - return parseFloat(ast.value); - case Kind.OBJECT: { - const value = Object.create(null); - for (const field of ast.fields) { - value[field.name.value] = parseLiteral(field.value); - } - - return value; - } - case Kind.LIST: - return ast.values.map(parseLiteral); - default: - return null; - } -} - -const GraphQLJSON = new GraphQLScalarType({ - name: 'JSON', - description: - 'The `JSON` scalar type represents JSON values as specified by ' + - '[ECMA-404](http://www.ecma-international.org/' + - 'publications/files/ECMA-ST/ECMA-404.pdf).', - serialize: identity, - parseValue: identity, - parseLiteral, -}); - -const addressTypeDef = ` - type Address { - street: String - city: String - state: String - zip: String - } -`; - -const propertyAddressTypeDef = ` - type Property { - id: ID! - name: String! - location: Location - address: Address - error: String - } -`; - -const propertyRootTypeDefs = ` - type Location { - name: String! - } - - enum TestInterfaceKind { - ONE - TWO - } - - interface TestInterface { - kind: TestInterfaceKind - testString: String - } - - type TestImpl1 implements TestInterface { - kind: TestInterfaceKind - testString: String - foo: String - } - - ${'getInterfaces' in GraphQLInterfaceType.prototype - ? `interface TestNestedInterface implements TestInterface { - kind: TestInterfaceKind - testString: String - } - - type TestImpl2 implements TestNestedInterface & TestInterface { - kind: TestInterfaceKind - testString: String - bar: String - }` - : `type TestImpl2 implements TestInterface { - kind: TestInterfaceKind - testString: String - bar: String - }` - } - - type UnionImpl { - someField: String - } - - union TestUnion = TestImpl1 | UnionImpl - - input InputWithDefault { - test: String = "Foo" - } - - type Query { - propertyById(id: ID!): Property - properties(limit: Int): [Property!] - contextTest(key: String!): String - dateTimeTest: DateTime - jsonTest(input: JSON): JSON - interfaceTest(kind: TestInterfaceKind): TestInterface - unionTest(output: String): TestUnion - errorTest: String - errorTestNonNull: String! - relay: Query! - defaultInputTest(input: InputWithDefault!): String - } -`; - -const propertyAddressTypeDefs = ` - scalar DateTime - scalar JSON - - ${addressTypeDef} - ${propertyAddressTypeDef} - ${propertyRootTypeDefs} -`; - -const propertyResolvers: IResolvers = { - Query: { - propertyById(_root, { id }) { - return sampleData.Property[id]; - }, - - properties(_root, { limit }) { - const list = Object.values(sampleData.Property); - return limit ? list.slice(0, limit) : list; - }, - - contextTest(_root, args, context) { - return JSON.stringify(context[args.key]); - }, - - dateTimeTest() { - return '1987-09-25T12:00:00'; - }, - - jsonTest(_root, { input }) { - return input; - }, - - interfaceTest(_root, { kind }) { - return kind === 'ONE' - ? { - kind: 'ONE', - testString: 'test', - foo: 'foo', - } - : { - kind: 'TWO', - testString: 'test', - bar: 'bar', - }; - }, - - unionTest(_root, { output }) { - return output === 'Interface' - ? { - kind: 'ONE', - testString: 'test', - foo: 'foo', - } - : { - someField: 'Bar', - }; - }, - - errorTest() { - throw new Error('Sample error!'); - }, - - errorTestNonNull() { - throw new Error('Sample error non-null!'); - }, - - defaultInputTest(_parent, { input }) { - return input.test; - }, - }, - DateTime, - JSON: GraphQLJSON, - - TestInterface: { - __resolveType(obj: any) { - return obj.kind === 'ONE' ? 'TestImpl1' : 'TestImpl2'; - }, - }, - - TestUnion: { - __resolveType(obj: any) { - return obj.kind === 'ONE' ? 'TestImpl1' : 'UnionImpl'; - }, - }, - - Property: { - error() { - throw new CustomError('Property.error error', { - code: 'SOME_CUSTOM_CODE', - }); - }, - }, -}; - -const DownloadableProduct = ` - type DownloadableProduct implements Product & Downloadable { - id: ID! - url: String! - } -`; - -const SimpleProduct = `type SimpleProduct implements Product & Sellable { - id: ID! - price: Int! - } -`; - -const productTypeDefs = ` - interface Product { - id: ID! - } - - interface Sellable { - price: Int! - } - - interface Downloadable { - url: String! - } - - ${SimpleProduct} - ${DownloadableProduct} - - type Query { - products: [Product] - } -`; - -const productResolvers: IResolvers = { - Query: { - products(_root) { - const list = Object.values(sampleData.Product); - return list; - }, - }, - - Product: { - __resolveType(obj: any) { - return obj.type === 'simple' ? 'SimpleProduct' : 'DownloadableProduct'; - }, - }, -}; - -const customerAddressTypeDef = ` - type Customer implements Person { - id: ID! - email: String! - name: String! - address: Address - bookings(limit: Int): [Booking!] - vehicle: Vehicle - error: String - } -`; - -const bookingRootTypeDefs = ` - scalar DateTime - - type Booking { - id: ID! - propertyId: ID! - customer: Customer! - startTime: String! - endTime: String! - error: String - errorNonNull: String! - } - - interface Person { - id: ID! - name: String! - } - - union Vehicle = Bike | Car - - type Bike { - id: ID! - bikeType: String - } - - type Car { - id: ID! - licensePlate: String - } - - type Query { - bookingById(id: ID!): Booking - bookingsByPropertyId(propertyId: ID!, limit: Int): [Booking!] - customerById(id: ID!): Customer - bookings(limit: Int): [Booking!] - customers(limit: Int): [Customer!] - } - - input BookingInput { - propertyId: ID! - customerId: ID! - startTime: DateTime! - endTime: DateTime! - } - - type Mutation { - addBooking(input: BookingInput): Booking - } -`; - -const bookingAddressTypeDefs = ` - ${addressTypeDef} - ${customerAddressTypeDef} - ${bookingRootTypeDefs} -`; - -const bookingResolvers: IResolvers = { - Query: { - bookingById(_parent, { id }) { - return sampleData.Booking[id]; - }, - bookingsByPropertyId(_parent, { propertyId, limit }) { - const list = Object.values(sampleData.Booking).filter( - (booking: Booking) => booking.propertyId === propertyId, - ); - return limit ? list.slice(0, limit) : list; - }, - customerById(_parent, { id }) { - return sampleData.Customer[id]; - }, - bookings(_parent, { limit }) { - const list = Object.values(sampleData.Booking); - return limit ? list.slice(0, limit) : list; - }, - customers(_parent, { limit }) { - const list = Object.values(sampleData.Customer); - return limit ? list.slice(0, limit) : list; - }, - }, - - Mutation: { - addBooking( - _parent, - { input: { propertyId, customerId, startTime, endTime } }, - ) { - return { - id: 'newId', - propertyId, - customerId, - startTime, - endTime, - }; - }, - }, - - Booking: { - __isTypeOf(possibleBooking: any, _context: any, _info: GraphQLResolveInfo) { - return possibleBooking.id != null; - }, - customer(parent: Booking) { - return sampleData.Customer[parent.customerId]; - }, - error() { - throw new Error('Booking.error error'); - }, - errorNonNull() { - throw new Error('Booking.errorNoNull error'); - }, - }, - - Customer: { - bookings(parent: Customer, { limit }) { - const list = Object.values(sampleData.Booking).filter( - (booking: Booking) => booking.customerId === parent.id, - ); - return limit ? list.slice(0, limit) : list; - }, - vehicle(parent: Customer) { - return parent.vehicleId && sampleData.Vehicle[parent.vehicleId]; - }, - error() { - throw new Error('Customer.error error'); - }, - }, - - Vehicle: { - __resolveType(parent: any) { - if (parent.licensePlate) { - return 'Car'; - } else if (parent.bikeType) { - return 'Bike'; - } - - throw new Error('Could not resolve Vehicle type'); - }, - }, - - DateTime, -}; - -const subscriptionTypeDefs = ` - type Notification{ - text: String - throwError: String - } - - type Query{ - notifications: Notification - } - - type Subscription{ - notifications: Notification - } -`; - -export const subscriptionPubSub = new PubSub(); -export const subscriptionPubSubTrigger = 'pubSubTrigger'; - -const subscriptionResolvers: IResolvers = { - Query: { - notifications: (_root: any) => ({ text: 'Hello world' }), - }, - Subscription: { - notifications: { - subscribe: () => - subscriptionPubSub.asyncIterator(subscriptionPubSubTrigger), - }, - }, - Notification: { - throwError: () => { - throw new Error('subscription field error'); - }, - }, -}; - -export const propertySchema: GraphQLSchema = makeExecutableSchema({ - typeDefs: propertyAddressTypeDefs, - resolvers: propertyResolvers, -}); - -export const productSchema: GraphQLSchema = makeExecutableSchema({ - typeDefs: productTypeDefs, - resolvers: productResolvers, -}); - -export const bookingSchema: GraphQLSchema = makeExecutableSchema({ - typeDefs: bookingAddressTypeDefs, - resolvers: bookingResolvers, -}); - -export const subscriptionSchema: GraphQLSchema = makeExecutableSchema({ - typeDefs: subscriptionTypeDefs, - resolvers: subscriptionResolvers, -}); - -export async function makeSchemaRemote( - schema: GraphQLSchema, -): Promise { - const executor = createDefaultExecutor(schema); - const clientSchema = await introspectSchema(executor as AsyncExecutor); - return { - schema: clientSchema, - executor, - }; -} - -export const remotePropertySchema = makeSchemaRemote(propertySchema); -export const remoteProductSchema = makeSchemaRemote(productSchema); -export const remoteBookingSchema = makeSchemaRemote(bookingSchema); diff --git a/packages/wrap/tests/makeRemoteExecutableSchema.test.ts b/packages/wrap/tests/makeRemoteExecutableSchema.test.ts index 5e7b2812d5d..24f76795b79 100644 --- a/packages/wrap/tests/makeRemoteExecutableSchema.test.ts +++ b/packages/wrap/tests/makeRemoteExecutableSchema.test.ts @@ -16,7 +16,7 @@ import { subscriptionPubSubTrigger, subscriptionPubSub, makeSchemaRemote, -} from './fixtures/schemas'; +} from '../../testing/fixtures/schemas'; describe('remote queries', () => { let schema: GraphQLSchema; @@ -178,20 +178,20 @@ describe('when query for multiple fields', () => { }); expect(calls).toHaveLength(3); - expect(print(calls[0].document)).toEqual(`\ + expect(print(calls[0].document).trim()).toEqual(`\ { fieldA } -`); - expect(print(calls[1].document)).toEqual(`\ +`.trim()); + expect(print(calls[1].document).trim()).toEqual(`\ { fieldB } -`); - expect(print(calls[2].document)).toEqual(`\ +`.trim()); + expect(print(calls[2].document).trim()).toEqual(`\ { field3 } -`); +`.trim()); }); }); diff --git a/packages/wrap/tests/transformFilterTypes.test.ts b/packages/wrap/tests/transformFilterTypes.test.ts index 4c54ce616df..1543d25ee5a 100644 --- a/packages/wrap/tests/transformFilterTypes.test.ts +++ b/packages/wrap/tests/transformFilterTypes.test.ts @@ -1,7 +1,7 @@ import { wrapSchema, FilterTypes } from '@graphql-tools/wrap'; import { graphql, GraphQLSchema, GraphQLNamedType } from 'graphql'; import { assertSome } from '@graphql-tools/utils'; -import { bookingSchema } from './fixtures/schemas'; +import { bookingSchema } from '../../testing/fixtures/schemas'; describe('FilterTypes', () => { let schema: GraphQLSchema; diff --git a/packages/wrap/tests/transformRenameTypes.test.ts b/packages/wrap/tests/transformRenameTypes.test.ts index 77a061c2ba4..18816a1c2f5 100644 --- a/packages/wrap/tests/transformRenameTypes.test.ts +++ b/packages/wrap/tests/transformRenameTypes.test.ts @@ -1,6 +1,6 @@ import { wrapSchema, RenameTypes } from '@graphql-tools/wrap'; import { execute, GraphQLSchema, parse } from 'graphql'; -import { propertySchema } from './fixtures/schemas'; +import { propertySchema } from '../../testing/fixtures/schemas'; describe('RenameTypes', () => { describe('rename type', () => { diff --git a/website/docs/generate-schema.mdx b/website/docs/generate-schema.mdx index a3d80c88fc2..d57aae7dc0a 100644 --- a/website/docs/generate-schema.mdx +++ b/website/docs/generate-schema.mdx @@ -11,7 +11,7 @@ The graphql-tools package allows you to create a GraphQL.js GraphQLSchema instan When using `graphql-tools`, you describe the schema as a GraphQL type language string: ```js -const typeDefs = ` +const typeDefs = /* GraphQL */` type Author { id: Int! firstName: String diff --git a/website/docs/mocking.mdx b/website/docs/mocking.mdx index ac334d6b965..a1d10060df0 100644 --- a/website/docs/mocking.mdx +++ b/website/docs/mocking.mdx @@ -144,7 +144,7 @@ To define a mock for a list, simply return an empty array of the desired length If you'd like to provide a mock for an `Union` or `Interface` type, you need to provide the type with an extra `__typename`. ```ts -const typeDefs = ` +const typeDefs = /* GraphQL */` ... union Result = User | Book `; @@ -161,7 +161,7 @@ const mocks = { Use `resolvers` option of `addMocksToSchema` to implement custom resolvers that interact with the [`MockStore`](#mockstore), especially to mutate field values. ```ts -const typeDefs = ` +const typeDefs = /* GraphQL */` type User { id: Id! name: String! @@ -222,7 +222,7 @@ const result = await server.query(query, variables); By default, `*byId` (like `userById(id: ID!)`) field will return an entity that does not have the same `id` as the one queried. We can fix that: ```ts -const typeDefs = ` +const typeDefs = /* GraphQL */` type User { id: Id! name: String! @@ -250,7 +250,7 @@ Note that, by default, the `id` or `_id` field will be used as storage key and t The idea is that the [`MockStore`](#mockstore) contains the full list, as field value, and that the resolver queries the store and slice the result: ```ts -const typeDefs = ` +const typeDefs = /* GraphQL */` type User { id: Id! name: String! @@ -286,7 +286,7 @@ const schemaWithMocks = addMocksToSchema({ The principles stay the same than for basic pagination: ```ts -const typeDefs = ` +const typeDefs = /* GraphQL */` type User { id: Id! name: String! @@ -612,7 +612,7 @@ If you used `__resolveType` resolver for mocking interfaces and unions, rather u Example: ```js -const typeDefs = ` +const typeDefs = /* GraphQL */` type Query { fetchMore(listType: String!, amount: Int!, offset: Int!): List } diff --git a/website/docs/scalars.mdx b/website/docs/scalars.mdx index 48c7ee7d896..0d0d6f6345c 100644 --- a/website/docs/scalars.mdx +++ b/website/docs/scalars.mdx @@ -246,7 +246,7 @@ Putting it all together: ```js import { makeExecutableSchema } from '@graphql-tools/schema'; -const typeDefs = ` +const typeDefs = /* GraphQL */` enum AllowedColor { RED GREEN diff --git a/website/docs/schema-directives.mdx b/website/docs/schema-directives.mdx index 3a02b01f377..4711267b43e 100644 --- a/website/docs/schema-directives.mdx +++ b/website/docs/schema-directives.mdx @@ -33,7 +33,7 @@ However, the API we provide for _using_ a schema directive is extremely simple. import { makeExecutableSchema } from '@graphql-tools/schema'; import { renameDirective } from 'fake-rename-directive-package'; -const typeDefs = ` +const typeDefs = /* GraphQL */` type Person @rename(to: "Human") { name: String! currentDateMinusDateOfBirth: Int @rename(to: "age") diff --git a/website/docs/schema-stitching/stitch-directives-sdl.mdx b/website/docs/schema-stitching/stitch-directives-sdl.mdx index c06d5f3730b..a20bbc0c434 100644 --- a/website/docs/schema-stitching/stitch-directives-sdl.mdx +++ b/website/docs/schema-stitching/stitch-directives-sdl.mdx @@ -105,7 +105,7 @@ const { } = stitchingDirectives(); // 1. include directive type definitions... -const typeDefs = ` +const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} # schema here ... type Query { diff --git a/website/docs/schema-stitching/stitch-schema-extensions.mdx b/website/docs/schema-stitching/stitch-schema-extensions.mdx index 2da10616d61..2cd9554a902 100644 --- a/website/docs/schema-stitching/stitch-schema-extensions.mdx +++ b/website/docs/schema-stitching/stitch-schema-extensions.mdx @@ -221,7 +221,15 @@ Internally, `batchDelegateToSchema` wraps a single `delegateToSchema` call in a If the query you're delegating to doesn't conform to these expectations, you can provide a custom [valuesFromResults](https://www.graphql-tools.com/docs/api/interfaces/batch_delegate_src.createbatchdelegatefnoptions/#valuesfromresults) function to transform it appropriately. -Batch delegation is generally preferable over plain delegation because it eliminates the redundancy of requesting the same field across an array of parent objects. Even so, delegation costs can add up because there is still one subschema request made _per batched field_—for remote services, this may create many network requests sent to the same service. Consider enabling an additional layer of network-level batching with a package such as [apollo-link-batch-http](https://www.apollographql.com/docs/link/links/batch-http/) to consolidate requests per subschema. +Batch delegation is generally preferable over plain delegation because it eliminates the redundancy of requesting the same field across an array of parent objects. Even so, delegation costs can add up because there is still one subschema request made _per batched field_—for remote services, this may create many network requests sent to the same service. Consider enabling an additional layer of batching by enabling batch execution with `batch: true` in subschema configuration; + +```ts +const someSubschema = { + schema: someNonExecutableSchema, + executor: someExecutor + batch: true, +}; +``` ## Passing gateway arguments