diff --git a/.eslintrc.yml b/.eslintrc.yml
new file mode 100644
index 00000000000..5950ac406e1
--- /dev/null
+++ b/.eslintrc.yml
@@ -0,0 +1,507 @@
+parser: babel-eslint
+parserOptions:
+ sourceType: module
+env:
+ es6: true
+ node: true
+plugins:
+ - import
+
+rules:
+ ##############################################################################
+ # `eslint-plugin-import` rule list based on `v2.20.x`
+ ##############################################################################
+
+ # Static analysis
+ # https://github.com/benmosher/eslint-plugin-import#static-analysis
+ import/no-unresolved: error
+ import/named: error
+ import/default: error
+ import/namespace: error
+ import/no-restricted-paths: off
+ import/no-absolute-path: error
+ import/no-dynamic-require: error
+ import/no-internal-modules: off
+ import/no-webpack-loader-syntax: error
+ import/no-self-import: error
+ import/no-cycle: error
+ import/no-useless-path-segments: error
+ import/no-relative-parent-imports: off
+
+ # Helpful warnings
+ # https://github.com/benmosher/eslint-plugin-import#helpful-warnings
+ import/export: error
+ import/no-named-as-default: error
+ import/no-named-as-default-member: error
+ import/no-deprecated: error
+ import/no-extraneous-dependencies: [error, { devDependencies: false }]
+ import/no-mutable-exports: error
+ import/no-unused-modules: error
+
+ # Module systems
+ # https://github.com/benmosher/eslint-plugin-import#module-systems
+ import/unambiguous: error
+ import/no-commonjs: error
+ import/no-amd: error
+ import/no-nodejs-modules: error
+
+ # Style guide
+ # https://github.com/benmosher/eslint-plugin-import#style-guide
+ import/first: error
+ import/exports-last: off
+ import/no-duplicates: error
+ import/no-namespace: error
+ import/extensions: [error, never] # TODO: switch to ignorePackages
+ import/order: [error, { newlines-between: always-and-inside-groups }]
+ import/newline-after-import: error
+ import/prefer-default-export: off
+ import/max-dependencies: off
+ import/no-unassigned-import: error
+ import/no-named-default: error
+ import/no-default-export: off
+ import/no-named-export: off
+ import/no-anonymous-default-export: error
+ import/group-exports: off
+ import/dynamic-import-chunkname: off
+
+ ##############################################################################
+ # ESLint builtin rules list based on `v6.8.x`
+ ##############################################################################
+
+ # Possible Errors
+ # https://eslint.org/docs/rules/#possible-errors
+
+ for-direction: error
+ getter-return: error
+ no-async-promise-executor: error
+ no-await-in-loop: error
+ no-compare-neg-zero: error
+ no-cond-assign: error
+ no-console: warn
+ no-constant-condition: error
+ no-control-regex: error
+ no-debugger: warn
+ no-dupe-args: error
+ no-dupe-else-if: error
+ no-dupe-keys: error
+ no-duplicate-case: error
+ no-empty: error
+ no-empty-character-class: error
+ no-ex-assign: error
+ no-extra-boolean-cast: error
+ no-func-assign: error
+ no-import-assign: error
+ no-inner-declarations: [error, both]
+ no-invalid-regexp: error
+ no-irregular-whitespace: error
+ no-misleading-character-class: error
+ no-obj-calls: error
+ no-prototype-builtins: error
+ no-regex-spaces: error
+ no-setter-return: error
+ no-sparse-arrays: error
+ no-template-curly-in-string: error
+ no-unreachable: error
+ no-unsafe-finally: error
+ no-unsafe-negation: error
+ require-atomic-updates: error
+ use-isnan: error
+ valid-typeof: error
+
+ # Best Practices
+ # https://eslint.org/docs/rules/#best-practices
+
+ accessor-pairs: error
+ array-callback-return: error
+ block-scoped-var: error
+ class-methods-use-this: off
+ complexity: off
+ consistent-return: off
+ curly: error
+ default-case: off
+ default-param-last: error
+ dot-notation: off
+ eqeqeq: [error, smart]
+ grouped-accessor-pairs: error
+ guard-for-in: error
+ max-classes-per-file: off
+ no-alert: error
+ no-caller: error
+ no-case-declarations: error
+ no-constructor-return: error
+ no-div-regex: error
+ no-else-return: error
+ no-empty-function: error
+ no-empty-pattern: error
+ no-eq-null: off
+ no-eval: error
+ no-extend-native: error
+ no-extra-bind: error
+ no-extra-label: error
+ no-fallthrough: error
+ no-global-assign: error
+ no-implicit-coercion: error
+ no-implicit-globals: off
+ no-implied-eval: error
+ no-invalid-this: off
+ no-iterator: error
+ no-labels: error
+ no-lone-blocks: error
+ no-loop-func: error
+ no-magic-numbers: off
+ no-multi-str: error
+ no-new: error
+ no-new-func: error
+ no-new-wrappers: error
+ no-octal: error
+ no-octal-escape: error
+ no-param-reassign: error
+ no-proto: error
+ no-redeclare: error
+ no-restricted-properties: off
+ no-return-assign: error
+ no-return-await: error
+ no-script-url: error
+ no-self-assign: error
+ no-self-compare: off # TODO
+ no-sequences: error
+ no-throw-literal: error
+ no-unmodified-loop-condition: error
+ no-unused-expressions: error
+ no-unused-labels: error
+ no-useless-call: error
+ no-useless-catch: error
+ no-useless-concat: error
+ no-useless-escape: error
+ no-useless-return: error
+ no-void: error
+ no-warning-comments: off
+ no-with: error
+ prefer-named-capture-group: off # ES2018
+ prefer-promise-reject-errors: error
+ prefer-regex-literals: error
+ radix: error
+ require-await: error
+ require-unicode-regexp: off
+ vars-on-top: error
+ yoda: [error, never, { exceptRange: true }]
+
+ # Strict Mode
+ # https://eslint.org/docs/rules/#strict-mode
+
+ strict: error
+
+ # Variables
+ # https://eslint.org/docs/rules/#variables
+
+ init-declarations: off
+ no-delete-var: error
+ no-label-var: error
+ no-restricted-globals: off
+ no-shadow: error
+ no-shadow-restricted-names: error
+ no-undef: error
+ no-undef-init: error
+ no-undefined: off
+ no-unused-vars: [error, { vars: all, args: all, argsIgnorePattern: '^_' }]
+ no-use-before-define: off
+
+ # Node.js and CommonJS
+ # https://eslint.org/docs/rules/#nodejs-and-commonjs
+
+ callback-return: error
+ global-require: error
+ handle-callback-err: [error, error]
+ no-buffer-constructor: error
+ no-mixed-requires: error
+ no-new-require: error
+ no-path-concat: error
+ no-process-env: off
+ no-process-exit: off
+ no-restricted-modules: off
+ no-sync: error
+
+ # Stylistic Issues
+ # https://eslint.org/docs/rules/#stylistic-issues
+
+ camelcase: error
+ capitalized-comments: off # maybe
+ consistent-this: off
+ func-name-matching: off
+ func-names: off
+ func-style: off
+ id-blacklist: off
+ id-length: off
+ id-match: [error, '^(?:_?[a-zA-Z0-9]*)|[_A-Z0-9]+$']
+ line-comment-position: off
+ lines-around-comment: off
+ lines-between-class-members: [error, always, { exceptAfterSingleLine: true }]
+ max-depth: off
+ max-lines: off
+ max-lines-per-function: off
+ max-nested-callbacks: off
+ max-params: off
+ max-statements: off
+ max-statements-per-line: off
+ multiline-comment-style: off
+ new-cap: off # TODO
+ no-array-constructor: error
+ no-bitwise: off
+ no-continue: off
+ no-inline-comments: off
+ no-lonely-if: error
+ no-multi-assign: off
+ no-negated-condition: off
+ no-nested-ternary: off
+ no-new-object: error
+ no-plusplus: off
+ no-restricted-syntax:
+ - error
+ - selector: 'FunctionDeclaration[async=true]'
+ message: >
+ async functions are not allowed inside package source code because
+ older versions of NodeJS do not support them without additional
+ runtime dependencies. Instead, use explicit Promises.
+ no-tabs: error
+ no-ternary: off
+ no-underscore-dangle: off
+ no-unneeded-ternary: error
+ one-var: [error, never]
+ operator-assignment: error
+ padding-line-between-statements: off
+ prefer-exponentiation-operator: error
+ prefer-object-spread: error
+ quotes: [error, single, { avoidEscape: true }]
+ sort-keys: off
+ sort-vars: off
+ spaced-comment: error
+
+ # ECMAScript 6
+ # https://eslint.org/docs/rules/#ecmascript-6
+
+ arrow-body-style: error
+ constructor-super: error
+ no-class-assign: error
+ no-const-assign: error
+ no-dupe-class-members: error
+ no-duplicate-imports: error
+ no-new-symbol: error
+ no-restricted-imports: off
+ no-this-before-super: error
+ no-useless-computed-key: error
+ no-useless-constructor: error
+ no-useless-rename: error
+ no-var: error
+ object-shorthand: error
+ prefer-arrow-callback: error
+ prefer-const: error
+ prefer-destructuring: off
+ prefer-numeric-literals: error
+ prefer-rest-params: off # TODO
+ prefer-spread: error
+ prefer-template: off
+ require-yield: error
+ sort-imports: off
+ symbol-description: off
+
+ # Bellow rules are disabled because coflicts with Prettier, see:
+ # https://github.com/prettier/eslint-config-prettier/blob/master/index.js
+ array-bracket-newline: off
+ array-bracket-spacing: off
+ array-element-newline: off
+ arrow-parens: off
+ arrow-spacing: off
+ block-spacing: off
+ brace-style: off
+ comma-dangle: off
+ comma-spacing: off
+ comma-style: off
+ computed-property-spacing: off
+ dot-location: off
+ eol-last: off
+ func-call-spacing: off
+ function-call-argument-newline: off
+ function-paren-newline: off
+ generator-star-spacing: off
+ implicit-arrow-linebreak: off
+ indent: off
+ jsx-quotes: off
+ key-spacing: off
+ keyword-spacing: off
+ linebreak-style: off
+ max-len: off
+ multiline-ternary: off
+ newline-per-chained-call: off
+ new-parens: off
+ no-confusing-arrow: off
+ no-extra-parens: off
+ no-extra-semi: off
+ no-floating-decimal: off
+ no-mixed-operators: off
+ no-mixed-spaces-and-tabs: off
+ no-multi-spaces: off
+ no-multiple-empty-lines: off
+ no-trailing-spaces: off
+ no-unexpected-multiline: off
+ no-whitespace-before-property: off
+ nonblock-statement-body-position: off
+ object-curly-newline: off
+ object-curly-spacing: off
+ object-property-newline: off
+ one-var-declaration-per-line: off
+ operator-linebreak: off
+ padded-blocks: off
+ quote-props: off
+ rest-spread-spacing: off
+ semi: off
+ semi-spacing: off
+ semi-style: off
+ space-before-blocks: off
+ space-before-function-paren: off
+ space-in-parens: off
+ space-infix-ops: off
+ space-unary-ops: off
+ switch-colon-spacing: off
+ template-curly-spacing: off
+ template-tag-spacing: off
+ unicode-bom: off
+ wrap-iife: off
+ wrap-regex: off
+ yield-star-spacing: off
+
+overrides:
+ - files: '**/*.ts'
+ parser: '@typescript-eslint/parser'
+ parserOptions:
+ tsconfigRootDir: './'
+ project: ['tsconfig.json']
+ plugins:
+ - '@typescript-eslint'
+ rules:
+ import/named: off
+ import/namespace: off
+ import/default: off
+ import/no-named-as-default-member: off
+ import/no-named-as-default: off
+ import/no-cycle: off
+ import/no-unused-modules: off
+ import/no-deprecated: off
+ import/no-unresolved: off
+
+ ##########################################################################
+ # `@typescript-eslint/eslint-plugin` rule list based on `v2.17.x`
+ ##########################################################################
+
+ # Supported Rules
+ # https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules
+ '@typescript-eslint/adjacent-overload-signatures': error
+ '@typescript-eslint/array-type': [error, { default: generic }]
+ '@typescript-eslint/await-thenable': error
+ '@typescript-eslint/ban-ts-comment': error
+ '@typescript-eslint/ban-types': error
+ '@typescript-eslint/consistent-type-assertions':
+ [error, { assertionStyle: as, objectLiteralTypeAssertions: never }]
+ '@typescript-eslint/consistent-type-definitions': off # TODO consider
+ '@typescript-eslint/explicit-function-return-type': off # TODO consider
+ '@typescript-eslint/explicit-member-accessibility': off # TODO consider
+ '@typescript-eslint/explicit-module-boundary-types': off # TODO consider
+ '@typescript-eslint/member-ordering': off # TODO consider
+ '@typescript-eslint/naming-convention': off # TODO consider
+ '@typescript-eslint/no-dynamic-delete': off
+ '@typescript-eslint/no-empty-interface': error
+ '@typescript-eslint/no-explicit-any': off # TODO error
+ '@typescript-eslint/no-extra-non-null-assertion': error
+ '@typescript-eslint/no-extraneous-class': off # TODO consider
+ '@typescript-eslint/no-floating-promises': error
+ '@typescript-eslint/no-for-in-array': error
+ '@typescript-eslint/no-implied-eval': error
+ '@typescript-eslint/no-inferrable-types':
+ [error, { ignoreParameters: true, ignoreProperties: true }]
+ '@typescript-eslint/no-misused-new': error
+ '@typescript-eslint/no-misused-promises': error
+ '@typescript-eslint/no-namespace': error
+ '@typescript-eslint/no-non-null-asserted-optional-chain': error
+ '@typescript-eslint/no-non-null-assertion': error
+ '@typescript-eslint/no-parameter-properties': error
+ '@typescript-eslint/no-require-imports': error
+ '@typescript-eslint/no-this-alias': error
+ '@typescript-eslint/no-throw-literal': error
+ '@typescript-eslint/no-type-alias': off # TODO consider
+ '@typescript-eslint/no-unnecessary-boolean-literal-compare': error
+ '@typescript-eslint/no-unnecessary-condition': error
+ '@typescript-eslint/no-unnecessary-qualifier': error
+ '@typescript-eslint/no-unnecessary-type-arguments': error
+ '@typescript-eslint/no-unnecessary-type-assertion': error
+ '@typescript-eslint/no-unused-vars-experimental': off
+ '@typescript-eslint/no-var-requires': error
+ '@typescript-eslint/prefer-as-const': off # TODO consider
+ '@typescript-eslint/prefer-for-of': off # TODO switch to error after TS migration
+ '@typescript-eslint/prefer-function-type': error
+ '@typescript-eslint/prefer-includes': off # TODO switch to error after IE11 drop
+ '@typescript-eslint/prefer-namespace-keyword': error
+ '@typescript-eslint/prefer-nullish-coalescing': error
+ '@typescript-eslint/prefer-optional-chain': error
+ '@typescript-eslint/prefer-readonly': error
+ '@typescript-eslint/prefer-regexp-exec': error
+ '@typescript-eslint/prefer-string-starts-ends-with': off # TODO switch to error after IE11 drop
+ '@typescript-eslint/promise-function-async': off
+ '@typescript-eslint/require-array-sort-compare': error
+ '@typescript-eslint/restrict-plus-operands':
+ [error, { checkCompoundAssignments: true }]
+ '@typescript-eslint/restrict-template-expressions': error
+ '@typescript-eslint/strict-boolean-expressions': off # TODO consider
+ '@typescript-eslint/switch-exhaustiveness-check': error
+ '@typescript-eslint/triple-slash-reference': error
+ '@typescript-eslint/typedef': off
+ '@typescript-eslint/unbound-method': off # TODO consider
+ '@typescript-eslint/unified-signatures': error
+
+ # Extension Rules
+ # https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#extension-rules
+
+ # Disable conflicting ESLint rules and enable TS-compatible ones
+ default-param-last: off
+ no-array-constructor: off
+ no-dupe-class-members: off
+ no-empty-function: off
+ no-unused-expressions: off
+ no-unused-vars: off
+ no-useless-constructor: off
+ require-await: off
+ no-return-await: off
+ '@typescript-eslint/default-param-last': error
+ '@typescript-eslint/no-dupe-class-members': error
+ '@typescript-eslint/no-array-constructor': error
+ '@typescript-eslint/no-empty-function': error
+ '@typescript-eslint/no-unused-expressions': error
+ '@typescript-eslint/no-unused-vars':
+ [error, { vars: all, args: all, argsIgnorePattern: '^_' }]
+ '@typescript-eslint/no-useless-constructor': error
+ '@typescript-eslint/require-await': error
+ '@typescript-eslint/return-await': error
+
+ # Disable for JS and TS
+ '@typescript-eslint/no-magic-numbers': off
+ '@typescript-eslint/no-use-before-define': off
+
+ # Bellow rules are disabled because coflicts with Prettier, see:
+ # https://github.com/prettier/eslint-config-prettier/blob/master/%40typescript-eslint.js
+ '@typescript-eslint/quotes': off
+ '@typescript-eslint/brace-style': off
+ '@typescript-eslint/comma-spacing': off
+ '@typescript-eslint/func-call-spacing': off
+ '@typescript-eslint/indent': off
+ '@typescript-eslint/member-delimiter-style': off
+ '@typescript-eslint/no-extra-parens': off
+ '@typescript-eslint/no-extra-semi': off
+ '@typescript-eslint/semi': off
+ '@typescript-eslint/space-before-function-paren': off
+ '@typescript-eslint/type-annotation-spacing': off
+ overrides:
+ - files: 'src/test/**/*.ts'
+ env:
+ mocha: true
+ rules:
+ import/no-extraneous-dependencies: off
+ import/no-nodejs-modules: off
+ no-restricted-syntax: off
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000000..6313b56c578
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf
diff --git a/.gitignore b/.gitignore
index 7ac4c259186..f55623756b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,16 @@
-node_modules
-coverage
-npm-debug.log
-dist
+node_modules/
+coverage/
+dist/
+
*.tgz
.DS_Store
+
+yarn.lock
package-lock.json
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+.eslintcache
+.nyc_output
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index e0e4eaecc5a..00000000000
--- a/.npmignore
+++ /dev/null
@@ -1,9 +0,0 @@
-*
-!dist/
-!dist/*
-!dist/stitching/*
-!dist/transforms/*
-!dist/generate/*
-!package.json
-!*.md
-!*.png
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000000..1dab4ed4c30
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+save-exact = true
diff --git a/.travis.yml b/.travis.yml
index 62262c34e04..4a51eeaf7c5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,38 @@
language: node_js
node_js:
- - "8"
+ - "13"
+ - "12"
- "10"
+env:
+ - GRAPHQL_VERSION='0.12'
+ - GRAPHQL_VERSION='0.13'
+ - GRAPHQL_VERSION='14.0'
+ - GRAPHQL_VERSION='14.1'
+ - GRAPHQL_VERSION='14.2'
+ - GRAPHQL_VERSION='14.3'
+ - GRAPHQL_VERSION='14.4'
+ - GRAPHQL_VERSION='14.5'
+ - GRAPHQL_VERSION='14.6'
+ - GRAPHQL_VERSION='rc'
+
install:
- npm config set spin=false
- - npm install -g coveralls
- npm install
script:
- - npm test
- - npm run lint
+ - node_version=$(node -v); if [[ ${node_version:1:2} == "13" && $GRAPHQL_VERSION == "14.6" ]]; then
+ npm run lint;
+ fi
+ - node_version=$(node -v); if [[ ${node_version:1:2} == "13" && $GRAPHQL_VERSION == "14.6" ]]; then
+ npm run prettier:check;
+ fi
+ - npm run compile
+ - npm install graphql@$GRAPHQL_VERSION
+ - npm run testonly:cover
+
+after_success:
- npm run coverage
- - coveralls < ./coverage/lcov.info || true # if coveralls doesn't have it covered
# Allow Travis tests to run in containers.
sudo: false
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 65e6eb88a5c..c1a50609bf2 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -15,7 +15,6 @@
"node_modules": true,
"test-lib": true,
"lib": true,
- "dist": true,
"coverage": true,
"npm": true
},
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 545599261a0..1ced43a707d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,33 @@
# Change log
+### Next
+
+#### Features
+
+* Adds [graphql-upload](https://github.com/jaydenseric/graphql-upload) compatible scalar and link for proxying remote file uploads [#671](https://github.com/apollographql/graphql-tools/issues/671)
+* Add ability to merge fields from types from different schemas
+* Adds transforms to wrap, extract, and rename fields [#1183](https://github.com/apollographql/graphql-tools/issues/1183)
+* Adds transform to filter object fields [#819](https://github.com/apollographql/graphql-tools/issues/819)
+* Exports visitSchema, SchemaVisitor, healSchema, healTypes, cloneSchema, cloneType, cloneDirective to enable more custom transforms. [#1070](https://github.com/apollographql/graphql-tools/issues/1070)
+* Allows removing extra delegation layers by passing fetcher/link options directly to delegateToSchema, mergeSchemas, and transformSchema and by filtering directly with filterSchema without additional transformation round [#1165](https://github.com/apollographql/graphql-tools/issues/1165)
+
+#### Bug Fixes
+
+* Filter unused variables from map when proxying requests
+* Preserve subscription errors when using makeRemoteExecutableSchema
+* Preserve extensions when transforming schemas
+* Fix merging and transforming of custom scalars and enums [#501](https://github.com/apollographql/graphql-tools/issues/501), [#1056](https://github.com/apollographql/graphql-tools/issues/1056), [#1200](https://github.com/apollographql/graphql-tools/issues/1200)
+* Allow renaming of subscription root fields [#997](https://github.com/apollographql/graphql-tools/issues/997), [#1002](https://github.com/apollographql/graphql-tools/issues/1002)
+* Fix alias resolution to no longer incorrectly fallback to non-aliased field when null [#1171](https://github.com/apollographql/graphql-tools/issues/1171)
+* Do not remove default directives (skip, include, deprecated) when not merging custom directives [#1159](https://github.com/apollographql/graphql-tools/issues/1159)
+* Fixes errors support [#743](https://github.com/apollographql/graphql-tools/issues/743), [#1037](https://github.com/apollographql/graphql-tools/issues/1037), [#1046](https://github.com/apollographql/graphql-tools/issues/1046)
+* Fix mergeSchemas to allow resolvers to return fields defined as functions [#1061](https://github.com/apollographql/graphql-tools/issues/1061)
+* Fix default values with mergeSchemas and addResolveFunctionsToSchema [#1121](https://github.com/apollographql/graphql-tools/issues/1121)
+* Fix interface and union healing
+* Fix stitching unions of types with enums
+* Fix mocking to work when schema stitching
+* Fix lost directives when adding an enum resolver
+
### 4.0.7
* Filter `extensions` prior to passing them to `buildASTSchema`, in an effort to provide minimum compatibilty for `graphql@14`-compatible schemas with the upcoming `graphql@15` release. This PR does not, however, bring support for newer `graphql@15` features like interfaces implementing interfaces. [#1284](https://github.com/apollographql/graphql-tools/pull/1284)
@@ -18,6 +46,12 @@
[@freiksenet](https://github.com/freiksenet) in [#1003](https://github.com/apollographql/graphql-tools/pull/1003)
* Allow user-provided `buildSchema` options.
[@trevor-scheer](https://github.com/trevor-scheer) in [#1154](https://github.com/apollographql/graphql-tools/pull/1154)
+* Fix `delegateToSchema` to allow delegation to subscriptions with different root field names, allows
+ the use of the `RenameRootFields` transform with subscriptions,
+ pull request [#1104](https://github.com/apollographql/graphql-tools/pull/1104), fixes
+ [#997](https://github.com/apollographql/graphql-tools/issues/997).
+* Add transformers to rename, filter, and arbitrarily transform object fields.
+ Fixes [#819](https://github.com/apollographql/graphql-tools/issues/819).
### 4.0.4
diff --git a/designs/connectors.md b/designs/connectors.md
index a2764ffa1dd..e565c233bfc 100644
--- a/designs/connectors.md
+++ b/designs/connectors.md
@@ -9,7 +9,7 @@ This document is intended as a design document for people who want to write conn
This is a draft at the moment, and not the final document. Chances are that the spec will change as we learn about the better ways to build GraphQL servers. It should be pretty close to the final version though, so if you want to get started and build connectors for specific backends, this document is a good starting point.
-Technically you could write a GraphQL server without connectors and models by writing all your logic directly into the resolve functions, but in most cases that's not ideal. Connectors and models are a way of organizing code in a GraphQL server, and you should use them to keep your server modular. If the need arises, you can always write optimized queries directly in your resolvers or models.
+Technically you could write a GraphQL server without connectors and models by writing all your logic directly into the resolvers, but in most cases that's not ideal. Connectors and models are a way of organizing code in a GraphQL server, and you should use them to keep your server modular. If the need arises, you can always write optimized queries directly in your resolvers or models.
Let's use an example schema, because it's always easier to explain things with examples:
```
@@ -60,7 +60,7 @@ Both batching and caching are more important in GraphQL than in traditional endp
Models are the glue between connectors - which are backend-specific - and GraphQL types - which are app-specific. They are very similar to models in ORMs, such as Rails' Active Record.
-Let's say for example that you have two types, Author and Post, which are both stored in MySQL. Rather than calling the MySQL connector directly from your resolve functions, you should create models for Author and Post, which use the MySQL connector. This additional level of abstraction helps separate the data fetching logic from the GraphQL schema, which makes reusing and refactoring it easier.
+Let's say for example that you have two types, Author and Post, which are both stored in MySQL. Rather than calling the MySQL connector directly from your resolvers, you should create models for Author and Post, which use the MySQL connector. This additional level of abstraction helps separate the data fetching logic from the GraphQL schema, which makes reusing and refactoring it easier.
In the example schema above, the Authors model would have the following methods:
```
@@ -150,7 +150,7 @@ app.use('/graphql', apolloServer({
});
```
-Step 4: Calling models in resolve functions
+Step 4: Calling models in resolvers
```
function resolve(author, args, ctx){
return ctx.models.Author.getById(author.id, ctx);
diff --git a/designs/graphql-decorator-spec.md b/designs/graphql-decorator-spec.md
index ab6fd7f65f0..b663e8b7b95 100644
--- a/designs/graphql-decorator-spec.md
+++ b/designs/graphql-decorator-spec.md
@@ -73,9 +73,9 @@ Decorators can be selectively applied to:
* A specific field
* An argument
-Decorators can modify the behavior of the parts of the schema they are applied to. Sometimes that requires modifying other parts of the schema. For instance, the @validateRange decorator modifies the behavior of the containing field's resolve function.
+Decorators can modify the behavior of the parts of the schema they are applied to. Sometimes that requires modifying other parts of the schema. For instance, the @validateRange decorator modifies the behavior of the containing field's resolver.
-In general, decorators either add, remove or modify an attribute of the thing they wrap. The most common type of decorator (e.g. @adminOnly, @log, @connector) will wrap one or more field's resolve functions to alter the execution behavior of the GraphQL schema, but other decorators (e.g. @description) may add attributes to a type, field or argument. It is also possible for a type decorator to add a field to the type (e.g. @id(fields: ["uuid"]) can add the __id field).
+In general, decorators either add, remove or modify an attribute of the thing they wrap. The most common type of decorator (e.g. @adminOnly, @log, @connector) will wrap one or more field resolvers to alter the execution behavior of the GraphQL schema, but other decorators (e.g. @description) may add attributes to a type, field or argument. It is also possible for a type decorator to add a field to the type (e.g. @id(fields: ["uuid"]) can add the __id field).
## Schema decorator API
@@ -120,7 +120,7 @@ class SampleFieldDecorator extends SchemaDecorator {
return (wrappedThing, { schema, type, field, context }) => {
// use this.config ...
// use args
- // modify wrappedThing's properties, resolve functions, etc.
+ // modify wrappedThing's properties, resolvers, etc.
}
}
}
diff --git a/docs/package-lock.json b/docs/package-lock.json
index 84cd975d655..55a5cbcefcc 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -1,3 +1,4 @@
+
{
"requires": true,
"lockfileVersion": 1,
diff --git a/docs/source/directive-resolvers.md b/docs/source/directive-resolvers.md
index 1e7e7c1faf8..9c4e988398f 100644
--- a/docs/source/directive-resolvers.md
+++ b/docs/source/directive-resolvers.md
@@ -2,7 +2,6 @@
## Directive example
Let's take a look at how we can create `@upper` Directive to upper-case a string returned from resolve on Field
-[See a complete runnable example on Launchpad.](https://launchpad.graphql.com/p00rw37qx0)
To start, let's grab the schema definition string from the `makeExecutableSchema` example [in the "Generating a schema" article](/generate-schema/#example).
@@ -65,7 +64,6 @@ graphql(schema, query).then((result) => console.log('Got result', result));
## Multi-Directives example
Multi-Directives on a field will be apply with LTR order.
-[See a complete runnable example on Launchpad.](https://launchpad.graphql.com/nx945rq1x7)
```js
// graphql-tools combines a schema string with resolvers.
diff --git a/docs/source/generate-schema.md b/docs/source/generate-schema.md
index f95b952d5f7..3dd10bcb2ee 100644
--- a/docs/source/generate-schema.md
+++ b/docs/source/generate-schema.md
@@ -7,8 +7,6 @@ The graphql-tools package allows you to create a GraphQL.js GraphQLSchema instan
## Example
-[See the complete live example in Apollo Launchpad.](https://launchpad.graphql.com/1jzxrj179)
-
When using `graphql-tools`, you describe the schema as a GraphQL type language string:
```js
@@ -212,14 +210,14 @@ const jsSchema = makeExecutableSchema({
- `parseOptions` is an optional argument which allows customization of parse when specifying `typeDefs` as a string.
-- `allowUndefinedInResolve` is an optional argument, which is `true` by default. When set to `false`, causes your resolve functions to throw errors if they return undefined, which can help make debugging easier.
+- `allowUndefinedInResolve` is an optional argument, which is `true` by default. When set to `false`, causes your resolver to throw errors if they return undefined, which can help make debugging easier.
- `resolverValidationOptions` is an optional argument which accepts an `ResolverValidationOptions` object which has the following boolean properties:
- - `requireResolversForArgs` will cause `makeExecutableSchema` to throw an error if no resolve function is defined for a field that has arguments.
+ - `requireResolversForArgs` will cause `makeExecutableSchema` to throw an error if no resolver is defined for a field that has arguments.
- `requireResolversForNonScalar` will cause `makeExecutableSchema` to throw an error if a non-scalar field has no resolver defined. Setting this to `true` can be helpful in catching errors, but defaults to `false` to avoid confusing behavior for those coming from other GraphQL libraries.
- - `requireResolversForAllFields` asserts that *all* fields have a valid resolve function.
+ - `requireResolversForAllFields` asserts that *all* fields have valid resolvers.
- `requireResolversForResolveType` will require a `resolveType()` method for Interface and Union types. This can be passed in with the field resolvers as `__resolveType()`. False to disable the warning.
diff --git a/docs/source/index.mdx b/docs/source/index.mdx
index a232a2f641d..20f88979499 100644
--- a/docs/source/index.mdx
+++ b/docs/source/index.mdx
@@ -3,13 +3,13 @@ title: graphql-tools
description: A set of utilities to build your JavaScript GraphQL schema in a concise and powerful way.
---
-GraphQL Tools is an npm package and an opinionated structure for how to build a GraphQL schema and resolvers in JavaScript, following the GraphQL-first development workflow.
+GraphQL Tools is an npm package and an opinionated structure for how to build a GraphQL schema and resolvers in JavaScript, following the GraphQL-first development workflow, authored originally by the Apollo team.
```txt
npm install graphql-tools graphql
```
-Functions in the `graphql-tools` package are not just useful for building servers. They can also be used in the browser, for example to mock a backend during development or testing.
+Functions in the `graphql-tools` packages are not just useful for building servers. They can also be used in the browser, for example to mock a backend during development or testing.
Even though we recommend a specific way of building GraphQL servers, you can use these tools even if you don't follow our structure; they work with any GraphQL-JS schema, and each tool can be useful on its own.
@@ -24,5 +24,5 @@ JavaScript GraphQL servers are often developed with `graphql-tools` and `apollo-
This package enables a specific workflow for developing a GraphQL server, where the GraphQL schema is the first thing you design, and acts as the contract between your frontend and backend. It's not necessarily for everyone, but it can be a great way to get a server up and running with a very clear separation of concerns. These concerns are aligned with Facebook's direction about the best way to use GraphQL, and our own findings after thinking about the best way to architect a JavaScript GraphQL API codebase.
1. **Use the GraphQL schema language.** The [official GraphQL documentation](http://graphql.org/learn/schema/) explains schema concepts using a concise and easy to read language. The [getting started guide](http://graphql.org/graphql-js/) for GraphQL.js now uses the schema to introduce new developers to GraphQL. `graphql-tools` enables you to use this language alongside with all of the features of GraphQL including resolvers, interfaces, custom scalars, and more, so that you can have a seamless flow from design to mocking to implementation. For a more complete overview of the benefits, check out Nick Nance's talk, [Managing GraphQL Development at Scale](https://www.youtube.com/watch?v=XOM8J4LaYFg).
-2. **Separate business logic from the schema.** As Dan Schafer covered in his talk, [GraphQL at Facebook](https://medium.com/apollo-stack/graphql-at-facebook-by-dan-schafer-38d65ef075af#.jduhdwudr), it's a good idea to treat GraphQL as a thin API and routing layer. This means that your actual business logic, permissions, and other concerns should not be part of your GraphQL schema. For large apps, we suggest splitting your GraphQL server code into 4 components: Schema, Resolvers, Models, and Connectors, which each handle a specific part of the work.
+2. **Separate business logic from the schema.** As Dan Schafer covered in his talk, [GraphQL at Facebook](https://medium.com/apollo-stack/graphql-at-facebook-by-dan-schafer-38d65ef075af#.jduhdwudr), it's a good idea to treat GraphQL as a thin API and routing layer. This means that your actual business logic, permissions, and other concerns should not be part of your GraphQL schema. For large apps, we suggest splitting your GraphQL server code into 4 components: Schema, Resolvers, Models, and Connectors, which each handle a specific part of the work. You can see this in action in the server part of our [GitHunt example app](https://github.com/apollostack/GitHunt-API/blob/master/api/schema.js).
3. **Use standard libraries for auth and other special concerns.** There's no need to reinvent the login process in GraphQL. Every server framework already has a wealth of technologies for auth, file uploads, and more. It's prudent to use those standard solutions even if your data is being served through a GraphQL endpoint, and it is okay to have non-GraphQL endpoints on your server when it's the most practical solution.
diff --git a/docs/source/mocking.md b/docs/source/mocking.md
index 5ffdf97f825..fc6b1867ab9 100644
--- a/docs/source/mocking.md
+++ b/docs/source/mocking.md
@@ -14,7 +14,7 @@ Let's take a look at how we can mock a GraphQL schema with just one line of code
To start, let's grab the schema definition string from the `makeExecutableSchema` example [in the "Generating a schema" article](/generate-schema/#example).
```js
-import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
+import { makeExecutableSchema, addMocksToSchema } from 'graphql-tools';
import { graphql } from 'graphql';
// Fill this in with the schema string
@@ -24,7 +24,7 @@ const schemaString = `...`;
const schema = makeExecutableSchema({ typeDefs: schemaString });
// Add mocks, modifies schema in place
-addMockFunctionsToSchema({ schema });
+addMocksToSchema({ schema });
const query = `
query tasksForUser {
@@ -126,13 +126,13 @@ You can read some background and flavor on this approach in our blog post, ["Moc
## Mocking interfaces
-You will need resolvers to mock interfaces. By default [`addMockFunctionsToSchema`](#addmockfunctionstoschema) will overwrite resolver functions.
+You will need resolvers to mock interfaces. By default [`addMocksToSchema`](#addmockfunctionstoschema) will overwrite resolver functions.
By setting the property `preserveResolvers` on the options object to `true`, the type resolvers will be preserved.
```js
import {
makeExecutableSchema,
- addMockFunctionsToSchema
+ addMocksToSchema
} from 'graphql-tools'
import mocks from './mocks' // your mock functions
@@ -188,7 +188,7 @@ const schema = makeExecutableSchema({
typeResolvers
})
-addMockFunctionsToSchema({
+addMocksToSchema({
schema,
mocks,
preserveResolvers: true
@@ -209,24 +209,24 @@ import * as introspectionResult from 'schema.json';
const schema = buildClientSchema(introspectionResult);
-addMockFunctionsToSchema({schema});
+addMocksToSchema({schema});
```
## API
-### addMockFunctionsToSchema
+### addMocksToSchema
```js
-import { addMockFunctionsToSchema } from 'graphql-tools';
+import { addMocksToSchema } from 'graphql-tools';
-addMockFunctionsToSchema({
+addMocksToSchema({
schema,
mocks: {},
preserveResolvers: false,
});
```
-Given an instance of GraphQLSchema and a mock object, `addMockFunctionsToSchema` modifies the schema in place to return mock data for any valid query that is sent to the server. If `mocks` is not passed, the defaults will be used for each of the scalar types. If `preserveResolvers` is set to `true`, existing resolve functions will not be overwritten to provide mock data. This can be used to mock some parts of the server and not others.
+Given an instance of GraphQLSchema and a mock object, `addMocksToSchema` modifies the schema in place to return mock data for any valid query that is sent to the server. If `mocks` is not passed, the defaults will be used for each of the scalar types. If `preserveResolvers` is set to `true`, existing resolvers will not be overwritten to provide mock data. This can be used to mock some parts of the server and not others.
### MockList
@@ -247,7 +247,7 @@ import { mockServer } from 'graphql-tools';
// or a GraphQLSchema object (eg the result of `buildSchema` from `graphql`)
const schema = `...`
-// Same mocks object that `addMockFunctionsToSchema` takes above
+// Same mocks object that `addMocksToSchema` takes above
const mocks = {}
preserveResolvers = false
@@ -262,6 +262,6 @@ server.query(query, variables)
})
```
-`mockServer` is just a convenience wrapper on top of `addMockFunctionsToSchema`. It adds your mock resolvers to your schema and returns a client that will correctly execute
+`mockServer` is just a convenience wrapper on top of `addMocksToSchema`. It adds your mock resolvers to your schema and returns a client that will correctly execute
your query with variables. **Note**: when executing queries from the returned server,
`context` and `root` will both equal `{}`.
diff --git a/docs/source/resolvers.md b/docs/source/resolvers.md
index d5aef29311c..e70a47e4d9a 100644
--- a/docs/source/resolvers.md
+++ b/docs/source/resolvers.md
@@ -10,7 +10,7 @@ Keep in mind that GraphQL resolvers can return [promises](https://developer.mozi
## Resolver map
-In order to respond to queries, a schema needs to have resolve functions for all fields. Resolve functions cannot be included in the GraphQL schema language, so they must be added separately. This collection of functions is called the "resolver map".
+In order to respond to queries, a schema needs to have resolvers for all fields. Resolvers are per field functions that are given a parent object, arguments, and the execution context, and are responsible for returning a result for that field. Resolvers cannot be included in the GraphQL schema language, so they must be added separately. The collection of resolvers is called the "resolver map".
The `resolverMap` object (`IResolvers`) should have a map of resolvers for each relevant GraphQL Object Type. The following is an example of a valid `resolverMap` object:
@@ -28,7 +28,7 @@ const resolverMap = {
},
};
```
-> Note: If you are using mocking, the `preserveResolvers` argument of [`addMockFunctionsToSchema`](/mocking/#addmockfunctionstoschema) must be set to `true` if you don't want your resolvers to be overwritten by mock resolvers.
+> Note: If you are using mocking, the `preserveResolvers` argument of [`addMocksToSchema`](/mocking/#addmockfunctionstoschema) must be set to `true` if you don't want your resolvers to be overwritten by mock resolvers.
Note that you don't have to put all of your resolvers in one object. Refer to the ["modularizing the schema"](/generate-schema/) section to learn how to combine multiple resolver maps into one.
@@ -53,7 +53,7 @@ Resolvers in GraphQL can return different kinds of results which are treated dif
1. `null` or `undefined` - this indicates the object could not be found. If your schema says that field is _nullable_, then the result will have a `null` value at that position. If the field is `non-null`, the result will "bubble up" to the nearest nullable field and that result will be set to `null`. This is to ensure that the API consumer never gets a `null` value when they were expecting a result.
2. An array - this is only valid if the schema indicates that the result of a field should be a list. The sub-selection of the query will run once for every item in this array.
-3. A promise - resolvers often do asynchronous actions like fetching from a database or backend API, so they can return promises. This can be combined with arrays, so a resolver can return:
+3. A promise - resolvers often do asynchronous actions like fetching from a database or backend API, so they can return promises. This can be combined with arrays, so a resolver can return:
1. A promise that resolves an array
2. An array of promises
4. A scalar or object value - a resolver can also return any other kind of value, which doesn't have any special meaning but is simply passed down into any nested resolvers, as described in the next section.
@@ -144,13 +144,13 @@ const resolverMap = {
In addition to using a resolver map with `makeExecutableSchema`, you can use it with any GraphQL.js schema by importing the following function from `graphql-tools`:
-### addResolveFunctionsToSchema({ schema, resolvers, resolverValidationOptions?, inheritResolversFromInterfaces? })
+### addResolversToSchema({ schema, resolvers, resolverValidationOptions?, inheritResolversFromInterfaces? })
-`addResolveFunctionsToSchema` takes an options object of `IAddResolveFunctionsToSchemaOptions` and modifies the schema in place by attaching the resolvers to the relevant types.
+`addResolversToSchema` takes an options object of `IAddResolveFunctionsToSchemaOptions` and modifies the schema in place by attaching the resolvers to the relevant types.
```js
-import { addResolveFunctionsToSchema } from 'graphql-tools';
+import { addResolversToSchema } from 'graphql-tools';
const resolvers = {
RootQuery: {
@@ -162,7 +162,7 @@ const resolvers = {
},
};
-addResolveFunctionsToSchema({ schema, resolvers });
+addResolversToSchema({ schema, resolvers });
```
The `IAddResolveFunctionsToSchemaOptions` object has 4 properties that are described in [`makeExecutableSchema`](/generate-schema/#makeexecutableschemaoptions).
@@ -175,9 +175,9 @@ export interface IAddResolveFunctionsToSchemaOptions {
}
```
-### addSchemaLevelResolveFunction(schema, rootResolveFunction)
+### addSchemaLevelResolver(schema, rootResolveFunction)
-Some operations, such as authentication, need to be done only once per query. Logically, these operations belong in an obj resolve function, but unfortunately GraphQL-JS does not let you define one. `addSchemaLevelResolveFunction` solves this by modifying the GraphQLSchema that is passed as the first argument.
+Some operations, such as authentication, need to be done only once per query. Logically, these operations belong in a schema level resolver field resolver, but unfortunately GraphQL-JS does not let you define one. `addSchemaLevelResolver` solves this by modifying the GraphQLSchema that is passed as the first argument.
## Companion tools
diff --git a/docs/source/schema-delegation.md b/docs/source/schema-delegation.md
index 0a8949a8638..360c349fa82 100644
--- a/docs/source/schema-delegation.md
+++ b/docs/source/schema-delegation.md
@@ -3,7 +3,12 @@ title: Schema delegation
description: Forward queries to other schemas automatically
---
-Schema delegation is a way to automatically forward a query (or a part of a query) from a parent schema to another schema (called a _subschema_) that is able to execute the query. Delegation is useful when the parent schema shares a significant part of its data model with the subschema. For example, the parent schema might be powering a GraphQL gateway that connects multiple existing endpoints together, each with its own schema. This kind of architecture could be implemented using schema delegation.
+Schema delegation is a way to automatically forward a query (or a part of a query) from a parent schema to another schema (called a _subschema_) that is able to execute the query. Delegation is useful when the parent schema shares a significant part of its data model with the subschema. For example:
+
+* A GraphQL gateway that connects multiple existing endpoints together, each with its own schema, could be implemented as a parent schema that delegates portions of queries to the relevant subschemas.
+* Any local schema can directly wrap remote schemas and optionally extend them with additional fields. As long as schema delegation is unidirectional, no gateway is necessary. Simple examples are schemas that wrap other autogenerated schemas (e.g. Postgraphile, Hasura, Prisma) to add custom functionality.
+
+Delegation is performed by one function, `delegateToSchema`, called from within a resolver function of the parent schema. The `delegateToSchema` function sends the query subtree received by the parent resolver to the subschema that knows how to execute it. Fields for the merged types use the `defaultMergedResolver` resolver to extract the correct data from the query response.
The `graphql-tools` package provides several related tools for managing schema delegation:
@@ -11,8 +16,6 @@ The `graphql-tools` package provides several related tools for managing schema d
* [Schema transforms](/schema-transforms/) - modifying existing schemas to make delegation easier
* [Schema stitching](/schema-stitching/) - merging multiple schemas into one
-Delegation is performed by one function, `delegateToSchema`, called from within a resolver function of the parent schema. The `delegateToSchema` function sends the query subtree received by the parent resolver to a subschema that knows how to execute it, then returns the result as if the parent resolver had executed the query.
-
## Motivational example
Let's consider two schemas, a subschema and a parent schema that reuses parts of a subschema. While the parent schema reuses the *definitions* of the subschema, we want to keep the implementations separate, so that the subschema can be tested independently, or even used as a remote service.
@@ -102,11 +105,13 @@ query($id: ID!) {
Delegation also removes the fields that don't exist on the subschema, such as `user`. This field would be retrieved from the parent schema using normal GraphQL resolvers.
+Each field on the `Repository` and `Issue` types should use the `defaultMergedResolver` to properly extract data from the delegated response. Although in the simplest case, the default resolver can be used for the merged types, `defaultMergedResolver` resolves aliases, converts custom scalars and enums to their internal representations, and maps errors.
+
## API
### delegateToSchema
-The `delegateToSchema` method can be found on the `info.mergeInfo` object within any resolver function, and should be called with the following named options:
+The `delegateToSchema` method should be called with the following named options:
```
delegateToSchema(options: {
@@ -165,7 +170,7 @@ If we delegate at `User.bookings` to `Query.bookingsByUser`, we want to preserve
const resolvers = {
User: {
bookings(parent, args, context, info) {
- return info.mergeInfo.delegateToSchema({
+ return delegateToSchema({
schema: subschema,
operation: 'query',
fieldName: 'bookingsByUser',
@@ -190,14 +195,6 @@ GraphQL context that is going to be past to subschema execution or subsciption c
GraphQL resolve info of the current resolver. Provides access to the subquery that starts at the current resolver.
-Also provides the `info.mergeInfo.delegateToSchema` function discussed above.
-
#### transforms: Array
-[Transforms](/schema-transforms/) to apply to the query and results. Should be the same transforms that were used to transform the schema, if any. After transformation, `transformedSchema.transforms` contains the transforms that were applied.
-
-## Additional considerations
-
-### Aliases
-
-Delegation preserves aliases that are passed from the parent query. However that presents problems, because default GraphQL resolvers retrieve field from parent based on their name, not aliases. This way results with aliases will be missing from the delegated result. `mergeSchemas` and `transformSchemas` go around that by using `src/stitching/defaultMergedResolver` for all fields without explicit resolver. When building new libraries around delegation, one should consider how the aliases will be handled.
+Any additional operation [transforms](/schema-transforms/) to apply to the query and results. Could be the same operation transforms used in conjunction with schema transformation. For convenience, after schema transformation, `transformedSchema.transforms` contains the transforms that were applied.
diff --git a/docs/source/schema-directives.md b/docs/source/schema-directives.md
index d563fb0f212..f6bcd2e185d 100644
--- a/docs/source/schema-directives.md
+++ b/docs/source/schema-directives.md
@@ -460,11 +460,10 @@ class LengthDirective extends SchemaDirectiveVisitor {
// Replace field.type with a custom GraphQLScalarType that enforces the
// length restriction.
wrapType(field) {
- if (field.type instanceof GraphQLNonNull &&
- field.type.ofType instanceof GraphQLScalarType) {
+ if (isNonNullType(field.type) && isScalarType(field.type.ofType)) {
field.type = new GraphQLNonNull(
new LimitedLengthType(field.type.ofType, this.args.max));
- } else if (field.type instanceof GraphQLScalarType) {
+ } else if (isScalarType(field.type)) {
field.type = new LimitedLengthType(field.type, this.args.max);
} else {
throw new Error(`Not a scalar type: ${field.type}`);
diff --git a/docs/source/schema-stitching.md b/docs/source/schema-stitching.md
index 1770fa66bc3..f3320f10a63 100644
--- a/docs/source/schema-stitching.md
+++ b/docs/source/schema-stitching.md
@@ -1,5 +1,5 @@
---
-title: Schema stitching (deprecated)
+title: Schema stitching (still going strong)
description: Combining multiple GraphQL APIs into one
---
@@ -7,24 +7,18 @@ description: Combining multiple GraphQL APIs into one
Schema stitching is the process of creating a single GraphQL schema from multiple underlying GraphQL APIs.
-One of the main benefits of GraphQL is that we can query all of our data as part of one schema, and get everything we need in one request. But as the schema grows, it might become cumbersome to manage it all as one codebase, and it starts to make sense to split it into different modules. We may also want to decompose your schema into separate microservices, which can be developed and deployed independently.
+One of the main benefits of GraphQL is that we can query all of our data as part of one schema, and get everything we need in one request. But as the schema grows, it might become cumbersome to manage it all as one codebase, and it starts to make sense to split it into different modules. We may also want to decompose your schema into separate microservices, which can be developed and deployed independently. We may also want to integrate our own schema with remote schemas.
-In both cases, we use `mergeSchemas` to combine multiple GraphQL schemas together and produce a merged schema that knows how to delegate parts of the query to the relevant subschemas. These subschemas can be either local to the server, or running on a remote server. They can even be services offered by 3rd parties, allowing us to connect to external data and create mashups.
-
-## Working with remote schemas
-
-In order to merge with a remote schema, we first call [makeRemoteExecutableSchema](/remote-schemas/) to create a local proxy for the schema that knows how to call the remote endpoint. We then merge that local proxy schema the same way we would merge any other locally implemented schema.
+In these cases, we use `mergeSchemas` to combine multiple GraphQL schemas together and produce a new schema that knows how to delegate parts of the query to the relevant subschemas. These subschemas can be either local to the server, or running on a remote server. They can even be services offered by 3rd parties, allowing us to connect to external data and create mashups.
## Basic example
-In this example we'll stitch together two very simple schemas. It doesn't matter whether these are local or proxies created with `makeRemoteExecutableSchema`, because the merging itself would be the same.
-
-In this case, we're dealing with two schemas that implement a system with users and "chirps"—small snippets of text that users can post.
+In this example we'll stitch together two very simple schemas. In this case, we're dealing with two schemas that implement a system with users and "chirps"—small snippets of text that users can post.
```js
import {
makeExecutableSchema,
- addMockFunctionsToSchema,
+ addMocksToSchema,
mergeSchemas,
} from 'graphql-tools';
@@ -46,7 +40,7 @@ const chirpSchema = makeExecutableSchema({
`
});
-addMockFunctionsToSchema({ schema: chirpSchema });
+addMocksToSchema({ schema: chirpSchema });
// Mocked author schema
const authorSchema = makeExecutableSchema({
@@ -62,17 +56,19 @@ const authorSchema = makeExecutableSchema({
`
});
-addMockFunctionsToSchema({ schema: authorSchema });
+addMocksToSchema({ schema: authorSchema });
export const schema = mergeSchemas({
- schemas: [
- chirpSchema,
- authorSchema,
+ subschemas: [
+ { schema: chirpSchema, },
+ { schema: authorSchema, },
],
});
```
-[Run the above example on Launchpad.](https://launchpad.graphql.com/1nkk8vqj9)
+Note the new `subschemas` property with an array of subschema configuration objects. This syntax is a bit more verbose, but we shall see how it provides multiple benefits:
+1. transforms can be specified on the subschema config object, avoiding creation of a new schema with a new round of delegation in order to transform a schema prior to merging.
+2. remote schema configuration options can be specified, also avoiding an additional round of schema proxying.
This gives us a new schema with the root fields on `Query` from both schemas (along with the `User` and `Chirp` types):
@@ -107,38 +103,40 @@ const linkTypeDefs = `
We can now merge these three schemas together:
```js
-mergeSchemas({
- schemas: [
- chirpSchema,
- authorSchema,
- linkTypeDefs,
+export const schema = mergeSchemas({
+ subschemas: [
+ { schema: chirpSchema, },
+ { schema: authorSchema, },
],
+ typeDefs: linkTypeDefs,
});
```
+Note the new `typeDefs` option in parallel to the new `subschemas` option, which better expresses that these typeDefs are defined only within the outer gateway schemas.
+
We won't be able to query `User.chirps` or `Chirp.author` yet, however, because we still need to define resolvers for these new fields.
How should these resolvers be implemented? When we resolve `User.chirps` or `Chirp.author`, we want to _delegate_ to the relevant root fields. To get from a user to the user's chirps, for example, we'll want to use the `id` of the user to call `Query.chirpsByAuthorId`. And to get from a chirp to its author, we can use the chirp's `authorId` field to call the existing `Query.userById` field.
-Resolvers for fields in schemas created by `mergeSchema` have access to a handy `delegateToSchema` function (exposed via `info.mergeInfo.delegateToSchema`) that allows forwarding parts of queries (or even whole new queries) to one of the subschemas that was passed to `mergeSchemas`.
+Resolvers can use the `delegateToSchema` function to forward parts of queries (or even whole new queries) to one of the subschemas that was passed to `mergeSchemas` (or any other schema).
In order to delegate to these root fields, we'll need to make sure we've actually requested the `id` of the user or the `authorId` of the chirp. To avoid forcing users to add these fields to their queries manually, resolvers on a merged schema can define a `fragment` property that specifies the required fields, and they will be added to the query automatically.
A complete implementation of schema stitching for these schemas might look like this:
```js
-const mergedSchema = mergeSchemas({
- schemas: [
- chirpSchema,
- authorSchema,
- linkTypeDefs,
+const schema = mergeSchemas({
+ subschemas: [
+ { schema: chirpSchema, },
+ { schema: authorSchema, },
],
+ typeDefs: linkTypeDefs,
resolvers: {
User: {
chirps: {
fragment: `... on User { id }`,
resolve(user, args, context, info) {
- return info.mergeInfo.delegateToSchema({
+ return delegateToSchema({
schema: chirpSchema,
operation: 'query',
fieldName: 'chirpsByAuthorId',
@@ -155,7 +153,7 @@ const mergedSchema = mergeSchemas({
author: {
fragment: `... on Chirp { authorId }`,
resolve(chirp, args, context, info) {
- return info.mergeInfo.delegateToSchema({
+ return delegateToSchema({
schema: authorSchema,
operation: 'query',
fieldName: 'userById',
@@ -172,22 +170,17 @@ const mergedSchema = mergeSchemas({
});
```
-[Run the above example on Launchpad.](https://launchpad.graphql.com/8r11mk9jq)
-
## Using with Transforms
-Often, when creating a GraphQL gateway that combines multiple existing schemas, we might want to modify one of the schemas. The most common tasks include renaming some of the types, and filtering the root fields. By using [transforms](/schema-transforms/) with schema stitching, we can easily tweak the subschemas before merging them together.
-
-Before, when we were simply merging schemas without first transforming them, we would typically delegate directly to one of the merged schemas. Once we add transforms to the mix, there are times when we want to delegate to fields of the new, transformed schemas, and other times when we want to delegate to the original, untransformed schemas.
+Often, when creating a GraphQL gateway that combines multiple existing schemas, we might want to modify one of the schemas. The most common tasks include renaming some of the types, and filtering the root fields. By using [transforms](/schema-transforms/) with schema stitching, we can easily tweak the subschemas before merging them together. (In earlier versions of graphql-tools, this required an additional round of delegation prior to merging, but transforms can now be specifying directly when merging using the new subschema configuration objects.)
For example, suppose we transform the `chirpSchema` by removing the `chirpsByAuthorId` field and add a `Chirp_` prefix to all types and field names, in order to make it very clear which types and fields came from `chirpSchema`:
```ts
import {
makeExecutableSchema,
- addMockFunctionsToSchema,
+ addMocksToSchema,
mergeSchemas,
- transformSchema,
FilterRootFields,
RenameTypes,
RenameRootFields,
@@ -211,37 +204,43 @@ const chirpSchema = makeExecutableSchema({
`
});
-addMockFunctionsToSchema({ schema: chirpSchema });
+addMocksToSchema({ schema: chirpSchema });
-// create transform schema
+// create transforms
-const transformedChirpSchema = transformSchema(chirpSchema, [
+const chirpSchemaTransforms = [
new FilterRootFields(
(operation: string, rootField: string) => rootField !== 'chirpsByAuthorId'
),
new RenameTypes((name: string) => `Chirp_${name}`),
new RenameRootFields((operation: 'Query' | 'Mutation' | 'Subscription', name: string) => `Chirp_${name}`),
-]);
+];
```
-Now we have a schema that has all fields and types prefixed with `Chirp_` and has only the `chirpById` root field. Note that the original schema has not been modified, and remains fully functional. We've simply created a new, slightly different schema, which hopefully will be more convenient for merging with our other subschemas.
+We will now have a schema that has all fields and types prefixed with `Chirp_` and has only the `chirpById` root field.
Now let's implement the resolvers:
-```js
-const mergedSchema = mergeSchemas({
- schemas: [
- transformedChirpSchema,
- authorSchema,
- linkTypeDefs,
+```ts
+const chirpSubschema = {
+ schema: chirpSchema,
+ transforms: chirpSchemaTransforms,
+}
+
+export const schema = mergeSchemas({
+ subschemas: [
+ chirpSubschema,
+ { schema: authorSchema },
],
+ typeDefs: linkTypeDefs,
+
resolvers: {
User: {
chirps: {
fragment: `... on User { id }`,
resolve(user, args, context, info) {
- return info.mergeInfo.delegateToSchema({
- schema: chirpSchema,
+ return delegateToSchema({
+ schema: chirpSubschema,
operation: 'query',
fieldName: 'chirpsByAuthorId',
args: {
@@ -249,7 +248,6 @@ const mergedSchema = mergeSchemas({
},
context,
info,
- transforms: transformedChirpSchema.transforms,
});
},
},
@@ -258,7 +256,7 @@ const mergedSchema = mergeSchemas({
author: {
fragment: `... on Chirp { authorId }`,
resolve(chirp, args, context, info) {
- return info.mergeInfo.delegateToSchema({
+ return delegateToSchema({
schema: authorSchema,
operation: 'query',
fieldName: 'userById',
@@ -277,23 +275,56 @@ const mergedSchema = mergeSchemas({
Notice that `resolvers.Chirp_Chirp` has been renamed from just `Chirp`, but `resolvers.Chirp_Chirp.author.fragment` still refers to the original `Chirp` type and `authorId` field, rather than `Chirp_Chirp` and `Chirp_authorId`.
-Also, when we call `info.mergeInfo.delegateToSchema` in the `User.chirps` resolvers, we can delegate to the original `chirpsByAuthorId` field, even though it has been filtered out of the final schema. That's because we're delegating to the original `chirpSchema`, which has not been modified by the transforms.
+Also, when we call `delegateToSchema` in the `User.chirps` resolvers, we can delegate to the original `chirpsByAuthorId` field, even though it has been filtered out of the final schema.
-## Complex example
+## Working with remote schemas
-For a more complicated example involving properties and bookings, with implementations of all of the resolvers, check out the Launchpad links below:
+In order to merge with a remote schema, we specify different options within the subschema configuration object that describe how to connect to the remote schema. For example:
-* [Property schema](https://launchpad.graphql.com/v7l45qkw3)
-* [Booking schema](https://launchpad.graphql.com/41p4j4309)
-* [Merged schema](https://launchpad.graphql.com/q5kq9z15p)
+```ts
+ subschemas: [
+ {
+ schema: nonExecutableChirpSchema,
+ link: chirpSchemaLink
+ transforms: chirpSchemaTransforms,
+ },
+ { schema: authorSchema },
+ ],
+```
+
+The remote schema may be obtained either via introspection or any other source. A link is a generic ApolloLink method of connecting to a schema, also used by Apollo Client.
+
+Specifying the remote schema options within the `mergeSchemas` call itself allows for skipping an additional round of delegation. The old method of using [makeRemoteExecutableSchema](/remote-schemas/) to create a local proxy for the remote schema would still work, and the same arguments are supported. See the [remote schema](/remote-schemas/) docs for further description of the options available. Subschema configuration allows for specifying an ApolloLink `link`, any fetcher method (if not using subscriptions), or a dispatcher function that takes the graphql `context` object as an argument and dynamically returns a link object or fetcher method.
## API
-### mergeSchemas
+### schemas
```ts
+
+export type SubschemaConfig = {
+ schema: GraphQLSchema;
+ rootValue?: Record;
+ executor?: Delegator;
+ subscriber?: Delegator;
+ link?: ApolloLink;
+ fetcher?: Fetcher;
+ dispatcher?: Dispatcher;
+ transforms?: Array;
+};
+
+export type SchemaLikeObject =
+ SubschemaConfig |
+ GraphQLSchema |
+ string |
+ DocumentNode |
+ Array;
+
mergeSchemas({
- schemas: Array>;
+ subschemas: Array;
+ types: Array;
+ typeDefs: string | DocumentNode;
+ schemas: Array;
resolvers?: Array | IResolvers;
onTypeConflict?: (
left: GraphQLNamedType,
@@ -316,7 +347,7 @@ This is the main function that implements schema stitching. Read below for a des
#### schemas
-`schemas` is an array of `GraphQLSchema` objects, schema strings, or lists of `GraphQLNamedType`s. Strings can contain type extensions or GraphQL types, which will be added to resulting schema. Note that type extensions are always applied last, while types are defined in the order in which they are provided.
+`schemas` is an array of `GraphQLSchema` objects, schema strings, or lists of `GraphQLNamedType`s. Strings can contain type extensions or GraphQL types, which will be added to resulting schema. Note that type extensions are always applied last, while types are defined in the order in which they are provided. Using the `subschemas` and `typeDefs` parameters is preferred, as these parameter names better describe whether the includes types will be wrapped or will be imported directly into the outer schema.
#### resolvers
@@ -328,7 +359,7 @@ resolvers: {
property: {
fragment: '... on Booking { propertyId }',
resolve(parent, args, context, info) {
- return info.mergeInfo.delegateToSchema({
+ return delegateToSchema({
schema: bookingSchema,
operation: 'query',
fieldName: 'propertyById',
@@ -344,14 +375,12 @@ resolvers: {
}
```
-#### mergeInfo and delegateToSchema
+#### delegateToSchema
-The `info.mergeInfo` object provides the `delegateToSchema` method:
+The `delegateToSchema` method:
```js
-type MergeInfo = {
- delegateToSchema(options: IDelegateToSchemaOptions): any;
-}
+delegateToSchema(options: IDelegateToSchemaOptions): any;
interface IDelegateToSchemaOptions right;
+const onTypeConflict = (left, right) => left;
```
And here's how we might select the type whose schema has the latest `version`:
@@ -413,4 +442,4 @@ When using schema transforms, `onTypeConflict` is often unnecessary, since trans
#### inheritResolversFromInterfaces
-The `inheritResolversFromInterfaces` option is simply passed through to `addResolveFunctionsToSchema`, which is called when adding resolvers to the schema under the covers. See [`addResolveFunctionsToSchema`](/resolvers/#addresolvefunctionstoschema-schema-resolvers-resolvervalidationoptions-inheritresolversfrominterfaces-) for more info.
+The `inheritResolversFromInterfaces` option is simply passed through to `addResolversToSchema`, which is called when adding resolvers to the schema under the covers. See [`addResolversToSchema`](/resolvers/#addresolvefunctionstoschema-schema-resolvers-resolvervalidationoptions-inheritresolversfrominterfaces-) for more info.
diff --git a/docs/source/schema-transforms.md b/docs/source/schema-transforms.md
index 3102105c9a0..078ec4a2a54 100644
--- a/docs/source/schema-transforms.md
+++ b/docs/source/schema-transforms.md
@@ -3,13 +3,11 @@ title: Schema transforms
description: Automatically transforming schemas
---
-Schema transforms are a tool for making modified copies of `GraphQLSchema` objects, while preserving the possibility of delegating back to original schema.
+Schema transforms are a tool for making modified copies of `GraphQLSchema` objects, without changing the original schema implementation. This is especially useful when the original schema _cannot_ be changed, i.e. when using [remote schemas](/remote-schemas/).
-Transforms are useful when working with [remote schemas](/remote-schemas/), building GraphQL gateways that combine multiple schemas, and/or using [schema stitching](/schema-stitching/) to combine schemas together without conflicts between types or fields.
+Schema transforms can be useful when building GraphQL gateways that combine multiple schemas using [schema stitching](/schema-stitching/) to combine schemas together without conflicts between types or fields.
-While it's possible to modify a schema by hand, the manual approach requires a deep understanding of all the relationships between `GraphQLSchema` properties, which makes it error-prone and labor-intensive. Transforms provide a generic abstraction over all those details, which improves code quality and saves time, not only now but also in the future, because transforms are designed to be reused again and again.
-
-Each `Transform` may define three different kinds of transform functions:
+Schema transforms work by wrapping the original schema in a new 'gateway' schema that simply delegates all operations to the original subschema. Each schema transform includes a function that changes the gateway schema. It may also include an operation transform, i.e. functions that either modify the operation prior to delegation or modify the result prior to its return.
```ts
interface Transform = {
@@ -19,8 +17,6 @@ interface Transform = {
};
```
-The most commonly used transform function is `transformSchema`. However, some transforms require modifying incoming requests and/or outgoing results as well, especially if `transformSchema` adds or removes types or fields, since such changes require mapping new types/fields to the original types/fields at runtime.
-
For example, let's consider changing the name of the type in a simple schema. Imagine we've written a function that takes a `GraphQLSchema` and replaces all instances of type `Test` with `NewTest`.
```graphql
@@ -46,7 +42,7 @@ type Query {
}
```
-At runtime, we want the `NewTest` type to be automatically mapped to the old `Test` type.
+On delegation to the original subschema, we want the `NewTest` type to be automatically mapped to the old `Test` type.
At first glance, it might seem as though most queries work the same way as before:
@@ -102,11 +98,17 @@ type Result = ExecutionResult & {
};
```
-### transformSchema
+### wrapSchema
Given a `GraphQLSchema` and an array of `Transform` objects, produce a new schema with those transforms applied.
-Delegating resolvers will also be generated to map from new schema root fields to old schema root fields. Often these automatic resolvers are sufficient, so you don't have to implement your own.
+Delegating resolvers are generated to map from new schema root fields to old schema root fields. These automatic resolvers should be sufficient, so you don't have to implement your own.
+
+The delegating resolvers will apply the operation transforms defined by the `Transform` objects. Each provided `transformRequest` functions will be applies in reverse order, until the request matches the original schema. The `tranformResult` functions will be applied in the opposite order until the result matches the final gateway schema.
+
+### transformSchema
+
+For convenience, when using `transformSchema`, after schema transformation, the `transforms` property on a returned `transformedSchema` object will contains the operation transforms that were applied. This could be useful when manually delegating to the transformed schema, but has been deprecated in favor of specifying the transforms within a subschema configuration object. See the [schema stitching](/schema-stitching/) docs for further details.
## Built-in transforms
@@ -170,7 +172,57 @@ RenameRootFields(
)
```
-### Other
+### Modifying object fields
+
+* `TransformObjectFields(objectFieldTransformer: ObjectFieldTransformer, fieldNodeTransformer?: FieldNodeTransformer))`: Given an object field transformer, arbitrarily transform fields. The `objectFieldTransformer` can return a `GraphQLFieldConfig` definition, a object with new `name` and a `field`, `null` to remove the field, or `undefined` to leave the field unchanged. The optional `fieldNodeTransformer`, if specified, is called upon any field of that type in the request; result transformation can be specified by wrapping the field's resolver within the `objectFieldTransformer`. In this way, a field can be fully arbitrarily modified in place.
+
+```ts
+TransformObjectFields(objectFieldTransformer: ObjectFieldTransformer, fieldNodeTransformer: FieldNodeTransformer)
+
+type ObjectFieldTransformer = (
+ typeName: string,
+ fieldName: string,
+ field: GraphQLField,
+) =>
+ | GraphQLFieldConfig
+ | { name: string; field: GraphQLFieldConfig }
+ | null
+ | void;
+
+type FieldNodeTransformer = (
+ typeName: string,
+ fieldName: string,
+ fieldNode: FieldNode
+) => FieldNode;
+```
+
+* `FilterObjectFields(filter: ObjectFilter)`: Removes object fields for which the `filter` function returns `false`.
+
+```ts
+FilterObjectFields(filter: ObjectFilter)
+
+type ObjectFilter = (
+ typeName: string,
+ fieldName: string,
+ field: GraphQLField,
+) => boolean;
+```
+
+* `RenameObjectFields(renamer)`: Rename object fields, by applying the `renamer` function to their names.
+
+```ts
+RenameObjectFields(
+ renamer: (
+ typeName: string,
+ fieldName: string,
+ field: GraphQLField,
+ ) => string,
+)
+```
+
+### Additional Operation Transforms
+
+It may be sometimes useful to add additional transforms to manually change an operation request or result when using `delegateToSchema`. Common use cases may be move selections around or to wrap them. The following built-in transforms may be useful in those cases.
* `ExtractField({ from: Array, to: Array })` - move selection at `from` path to `to` path.
@@ -243,15 +295,19 @@ transforms: [
})
```
-* `ReplaceFieldWithFragment(targetSchema: GraphQLSchema, fragments: Array<{ field: string; fragment: string; }>)`: Replace the given fields with an inline fragment. Used by `mergeSchemas` to handle the `fragment` option.
-
-## delegateToSchema transforms
+## delegateToSchema (delegation) transforms
-The following transforms are automatically applied by `delegateToSchema` during schema delegation, to translate between new and old types and fields:
+The following transforms are automatically applied by `delegateToSchema` during schema delegation, to translate between source and target types and fields:
-* `AddArgumentsAsVariables`: Given a schema and arguments passed to a root field, make those arguments document variables.
-* `FilterToSchema`: Given a schema and document, remove all fields, variables and fragments for types that don't exist in that schema.
-* `AddTypenameToAbstract`: Add `__typename` to all abstract types in the document.
+* `ExpandAbstractTypes`: If an abstract type within a document does not exist within the target schema, expand the type to each and any of its implementations that do exist.
+* `FilterToSchema`: Remove all fields, variables and fragments for types that don't exist within the target schema.
+* `AddTypenameToAbstract`: Add `__typename` to all abstract types in the document, necessary for type resolution of interfaces within the source schema to work.
* `CheckResultAndHandleErrors`: Given a result from a subschema, propagate errors so that they match the correct subfield. Also provide the correct key if aliases are used.
-By passing a custom `transforms` array to `delegateToSchema`, it's possible to run additional transforms before these default transforms, though it is currently not possible to disable the default transforms.
+By passing a custom `transforms` array to `delegateToSchema`, it's possible to run additional operation (request/result) transforms before these default transforms.
+
+## mergeSchemas (gateway/stitching) transforms
+
+* `AddReplacementSelectionSets(schema: GraphQLSchema, mapping: ReplacementSelectionSetMapping)`: `mergeSchemas` adds selection sets on outgoing requests from the gateway, enabling delegation from fields specified on the gateway using fields obtained from the original requests. The selection sets can be added depending on the presence of fields within the request using the `selectionSet` option within the resolver map. `mergeSchemas` creates the mapping at gateway startup. Selection sets are used instead of fragments as the selections are added prior to transformation (in case type names are changed).
+* `AddMergedTypeSelectionSets(schema: GraphQLSchema, mapping: Record)`: `mergeSchemas` adds selection sets on outgoing requests from the gateway, enabling type merging from the initial result using any fields initially obtained. The mapping is created at gateway startup.
+* Deprecated: `ReplaceFieldWithFragment(targetSchema: GraphQLSchema, fragments: Array<{ field: string; fragment: string; }>)`: Replace the given fields with an inline fragment. Used by original `mergeSchemas` to add prespecified fragments to root fields, enabling delegation `fragment` option. Array was parsed at each delegation.
diff --git a/package.json b/package.json
index 3ec61534073..37224cd007d 100644
--- a/package.json
+++ b/package.json
@@ -1,32 +1,32 @@
{
"name": "graphql-tools",
- "version": "4.0.7",
+ "version": "5.0.0-alpha.0",
"description": "Useful tools to create and manipulate GraphQL schemas.",
"main": "dist/index.js",
- "typings": "dist/index.d.ts",
- "typescript": {
- "definition": "dist/index.d.ts"
- },
- "directories": {
- "test": "test"
- },
+ "types": "dist/index.d.ts",
+ "files": [
+ "/dist",
+ "!/dist/test"
+ ],
+ "sideEffects": false,
"scripts": {
"clean": "rimraf dist",
- "compile": "npx tsc",
- "typings": "typings install",
+ "build": "npm run compile",
+ "precompile": "npm run clean",
+ "compile": "tsc",
"pretest": "npm run clean && npm run compile",
- "test": "npm run testonly --",
- "posttest": "npm run lint",
- "lint": "tslint src/**/*.ts",
+ "test": "npm run testonly",
+ "posttest": "npm run lint && npm run prettier:check",
+ "lint": "eslint --ext .js,.ts src",
+ "lint:watch": "esw --watch --cache --ext .js,.ts src",
"watch": "tsc -w",
- "testonly": "mocha --reporter spec --full-trace ./dist/test/tests.js",
- "testonly:watch": "mocha -w --reporter spec --full-trace ./dist/test/tests.js",
- "coverage": "istanbul cover _mocha -- --reporter dot --full-trace ./dist/test/tests.js",
- "postcoverage": "remap-istanbul --input coverage/coverage.json --type lcovonly --output coverage/lcov.info",
+ "testonly": "mocha --reporter spec --full-trace ./dist/test/**.js --require source-map-support/register",
+ "testonly:cover": "nyc npm run testonly",
+ "testonly:watch": "mocha -w --reporter spec --full-trace ./dist/test/**.js --require source-map-support/register",
+ "coverage": "nyc report --reporter=text-lcov | coveralls",
"prepublishOnly": "npm run compile",
- "prerelease": "npm test",
- "prettier": "prettier --trailing-comma all --single-quote --write 'src/**/*.ts'",
- "release": "standard-version"
+ "prettier": "prettier --trailing-comma all --single-quote --write src/**/*.ts",
+ "prettier:check": "prettier --trailing-comma all --single-quote --check src/**/*.ts"
},
"repository": {
"type": "git",
@@ -49,36 +49,53 @@
},
"homepage": "https://github.com/apollostack/graphql-tools#readme",
"dependencies": {
- "apollo-link": "^1.2.3",
- "apollo-utilities": "^1.0.1",
+ "apollo-link": "^1.2.13",
+ "apollo-link-http-common": "^0.2.15",
"deprecated-decorator": "^0.1.6",
- "iterall": "^1.1.3",
- "uuid": "^3.1.0"
+ "extract-files": "^7.0.0",
+ "form-data": "^3.0.0",
+ "iterall": "^1.3.0",
+ "node-fetch": "^2.6.0",
+ "tslib": "^1.11.0",
+ "uuid": "^7.0.2"
},
"peerDependencies": {
- "graphql": "^0.13.0 || ^14.0.0"
+ "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc"
},
"devDependencies": {
- "@types/chai": "4.0.10",
- "@types/dateformat": "^1.0.1",
- "@types/mocha": "^2.2.44",
- "@types/node": "^8.0.47",
- "@types/uuid": "^3.4.3",
- "@types/zen-observable": "^0.5.3",
- "body-parser": "^1.18.2",
- "chai": "^4.1.2",
- "dateformat": "^3.0.3",
- "express": "^4.16.2",
- "graphql": "^14.5.8",
- "graphql-subscriptions": "^1.0.0",
- "graphql-type-json": "^0.1.4",
- "istanbul": "^0.4.5",
- "mocha": "^4.0.1",
- "prettier": "^1.7.4",
- "remap-istanbul": "0.9.6",
- "rimraf": "^2.6.2",
- "source-map-support": "^0.5.0",
- "tslint": "^5.8.0",
- "typescript": "^3.6.4"
+ "@types/chai": "4.2.11",
+ "@types/dateformat": "3.0.1",
+ "@types/express": "4.17.3",
+ "@types/extract-files": "3.1.0",
+ "@types/graphql-type-json": "0.3.2",
+ "@types/graphql-upload": "8.0.3",
+ "@types/mocha": "7.0.2",
+ "@types/node": "13.9.3",
+ "@types/node-fetch": "2.5.5",
+ "@types/uuid": "7.0.2",
+ "@typescript-eslint/eslint-plugin": "2.25.0",
+ "@typescript-eslint/parser": "2.25.0",
+ "babel-eslint": "10.1.0",
+ "body-parser": "1.19.0",
+ "chai": "4.2.0",
+ "coveralls": "3.0.11",
+ "dataloader": "2.0.0",
+ "dateformat": "3.0.3",
+ "eslint": "6.8.0",
+ "eslint-plugin-import": "2.20.1",
+ "eslint-watch": "6.0.1",
+ "express": "4.17.1",
+ "express-graphql": "0.9.0",
+ "graphql": "14.6.0",
+ "graphql-subscriptions": "1.1.0",
+ "graphql-type-json": "0.3.1",
+ "graphql-upload": "10.0.0",
+ "mocha": "7.1.1",
+ "nyc": "15.0.0",
+ "prettier": "2.0.2",
+ "rimraf": "3.0.2",
+ "source-map-support": "0.5.16",
+ "typescript": "3.8.3",
+ "zen-observable-ts": "0.8.20"
}
}
diff --git a/src/Interfaces.ts b/src/Interfaces.ts
index d5e513c0814..9a176068eeb 100644
--- a/src/Interfaces.ts
+++ b/src/Interfaces.ts
@@ -2,22 +2,40 @@ import {
GraphQLSchema,
GraphQLField,
ExecutionResult,
+ GraphQLInputType,
GraphQLType,
+ GraphQLNamedType,
GraphQLFieldResolver,
GraphQLResolveInfo,
GraphQLIsTypeOfFn,
GraphQLTypeResolver,
GraphQLScalarType,
- GraphQLNamedType,
DocumentNode,
- ASTNode,
+ FieldNode,
+ GraphQLEnumValue,
+ GraphQLEnumType,
+ GraphQLUnionType,
+ GraphQLArgument,
+ GraphQLInputField,
+ GraphQLInputObjectType,
+ GraphQLInterfaceType,
+ GraphQLObjectType,
+ InlineFragmentNode,
+ GraphQLOutputType,
+ SelectionSetNode,
+ GraphQLDirective,
+ GraphQLFieldConfig,
+ FragmentDefinitionNode,
+ SelectionNode,
+ VariableDefinitionNode,
} from 'graphql';
-import { SchemaDirectiveVisitor } from './schemaVisitor';
+import { TypeMap } from 'graphql/type/schema';
+import { ApolloLink } from 'apollo-link';
-/* TODO: Add documentation */
+import { SchemaVisitor } from './utils/SchemaVisitor';
+import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor';
-export type UnitOrList = Type | Array;
export interface IResolverValidationOptions {
requireResolversForArgs?: boolean;
requireResolversForNonScalar?: boolean;
@@ -26,9 +44,19 @@ export interface IResolverValidationOptions {
allowResolversNotInSchema?: boolean;
}
+// for backwards compatibility
export interface IAddResolveFunctionsToSchemaOptions {
schema: GraphQLSchema;
resolvers: IResolvers;
+ defaultFieldResolver: IFieldResolver;
+ resolverValidationOptions: IResolverValidationOptions;
+ inheritResolversFromInterfaces: boolean;
+}
+
+export interface IAddResolversToSchemaOptions {
+ schema: GraphQLSchema;
+ resolvers: IResolvers;
+ defaultFieldResolver?: IFieldResolver;
resolverValidationOptions?: IResolverValidationOptions;
inheritResolversFromInterfaces?: boolean;
}
@@ -41,28 +69,155 @@ export interface IResolverOptions {
__isTypeOf?: GraphQLIsTypeOfFn;
}
-export type Transform = {
+export interface Transform {
transformSchema?: (schema: GraphQLSchema) => GraphQLSchema;
transformRequest?: (originalRequest: Request) => Request;
transformResult?: (result: Result) => Result;
+}
+
+export type FieldTransformer = (
+ typeName: string,
+ fieldName: string,
+ field: GraphQLField,
+) => GraphQLFieldConfig | RenamedField | null | undefined;
+
+export type FieldNodeTransformer = (
+ typeName: string,
+ fieldName: string,
+ fieldNode: FieldNode,
+ fragments: Record,
+) => SelectionNode | Array;
+
+export type RenamedField = {
+ name: string;
+ field?: GraphQLFieldConfig;
};
+export type FieldFilter = (
+ typeName?: string,
+ fieldName?: string,
+ field?: GraphQLField,
+) => boolean;
+
+export type RootFieldFilter = (
+ operation?: 'Query' | 'Mutation' | 'Subscription',
+ rootFieldName?: string,
+ field?: GraphQLField,
+) => boolean;
+
export interface IGraphQLToolsResolveInfo extends GraphQLResolveInfo {
mergeInfo?: MergeInfo;
}
-export interface IDelegateToSchemaOptions {
+export type Fetcher = (
+ operation: IFetcherOperation,
+) => Promise;
+
+export interface IFetcherOperation {
+ query: DocumentNode;
+ operationName?: string;
+ variables?: { [key: string]: any };
+ context?: { [key: string]: any };
+}
+
+export type Dispatcher = (context: any) => ApolloLink | Fetcher;
+
+export interface SubschemaConfig {
schema: GraphQLSchema;
- operation: Operation;
- fieldName: string;
+ rootValue?: Record;
+ executor?: Delegator;
+ subscriber?: Delegator;
+ link?: ApolloLink;
+ fetcher?: Fetcher;
+ dispatcher?: Dispatcher;
+ transforms?: Array;
+ merge?: Record;
+}
+
+export interface MergedTypeConfig {
+ selectionSet?: string;
+ fieldName?: string;
+ args?: (originalResult: any) => Record;
+ resolve?: MergedTypeResolver;
+}
+
+export type MergedTypeResolver = (
+ originalResult: any,
+ context: Record,
+ info: IGraphQLToolsResolveInfo,
+ subschema: GraphQLSchema | SubschemaConfig,
+ selectionSet: SelectionSetNode,
+) => any;
+
+export interface GraphQLSchemaWithTransforms extends GraphQLSchema {
+ transforms?: Array;
+}
+
+export type SchemaLikeObject =
+ | SubschemaConfig
+ | GraphQLSchema
+ | string
+ | DocumentNode
+ | Array;
+
+export function isSubschemaConfig(
+ value: SchemaLikeObject,
+): value is SubschemaConfig {
+ return Boolean((value as SubschemaConfig).schema);
+}
+
+export interface IDelegateToSchemaOptions {
+ schema: GraphQLSchema | SubschemaConfig;
+ operation?: Operation;
+ fieldName?: string;
+ returnType?: GraphQLOutputType;
args?: { [key: string]: any };
- context: TContext;
+ selectionSet?: SelectionSetNode;
+ fieldNodes?: ReadonlyArray;
+ context?: TContext;
info: IGraphQLToolsResolveInfo;
+ rootValue?: Record;
transforms?: Array;
skipValidation?: boolean;
+ skipTypeMerging?: boolean;
}
-export type MergeInfo = {
+export interface ICreateRequestFromInfo {
+ info: IGraphQLToolsResolveInfo;
+ operation: Operation;
+ fieldName: string;
+ selectionSet?: SelectionSetNode;
+ fieldNodes?: ReadonlyArray;
+}
+
+export interface ICreateRequest {
+ sourceSchema: GraphQLSchema;
+ sourceParentType: GraphQLObjectType;
+ sourceFieldName: string;
+ fragments: Record;
+ variableDefinitions: ReadonlyArray;
+ variableValues: Record;
+ targetOperation: Operation;
+ targetFieldName: string;
+ selectionSet: SelectionSetNode;
+ fieldNodes: ReadonlyArray;
+}
+
+export interface IDelegateRequestOptions extends IDelegateToSchemaOptions {
+ request: Request;
+}
+
+export type Delegator = ({
+ document,
+ context,
+ variables,
+}: {
+ document: DocumentNode;
+ context?: { [key: string]: any };
+ variables?: { [key: string]: any };
+}) => any;
+
+export interface MergeInfo {
delegate: (
type: 'query' | 'mutation' | 'subscription',
fieldName: string,
@@ -71,29 +226,56 @@ export type MergeInfo = {
info: GraphQLResolveInfo,
transforms?: Array,
) => any;
- delegateToSchema(options: IDelegateToSchemaOptions): any;
fragments: Array<{
field: string;
fragment: string;
}>;
-};
+ replacementSelectionSets: ReplacementSelectionSetMapping;
+ replacementFragments: ReplacementFragmentMapping;
+ mergedTypes: Record;
+ delegateToSchema(options: IDelegateToSchemaOptions): any;
+}
+
+export interface ReplacementSelectionSetMapping {
+ [typeName: string]: { [fieldName: string]: SelectionSetNode };
+}
+
+export interface ReplacementFragmentMapping {
+ [typeName: string]: { [fieldName: string]: InlineFragmentNode };
+}
+
+export interface MergedTypeInfo {
+ subschemas: Array;
+ selectionSet?: SelectionSetNode;
+ uniqueFields: Record;
+ nonUniqueFields: Record>;
+ typeMaps: Map;
+ selectionSets: Map;
+ containsSelectionSet: Map>;
+}
export type IFieldResolver> = (
source: TSource,
args: TArgs,
context: TContext,
- info: GraphQLResolveInfo & { mergeInfo: MergeInfo },
+ info: IGraphQLToolsResolveInfo,
) => any;
-export type ITypedef = (() => ITypedef[]) | string | DocumentNode | ASTNode;
-export type ITypeDefinitions = ITypedef | ITypedef[];
-export type IResolverObject = {
+export type ITypedef = (() => Array) | string | DocumentNode;
+
+export type ITypeDefinitions = ITypedef | Array;
+
+export interface IResolverObject {
[key: string]:
| IFieldResolver
| IResolverOptions
| IResolverObject;
-};
-export type IEnumResolver = { [key: string]: string | number };
+}
+
+export interface IEnumResolver {
+ [key: string]: string | number;
+}
+
export interface IResolvers {
[key: string]:
| (() => any)
@@ -102,26 +284,27 @@ export interface IResolvers {
| GraphQLScalarType
| IEnumResolver;
}
+
export type IResolversParameter =
| Array IResolvers)>
| IResolvers
| ((mergeInfo: MergeInfo) => IResolvers);
export interface ILogger {
- log: (message: string | Error) => void;
+ log: (error: Error) => void;
}
-export interface IConnectorCls {
- new (context?: TContext): any;
-}
+export type IConnectorCls = new (context?: TContext) => any;
+
export type IConnectorFn = (context?: TContext) => any;
+
export type IConnector =
| IConnectorCls
| IConnectorFn;
-export type IConnectors = {
+export interface IConnectors {
[key: string]: IConnector;
-};
+}
export interface IExecutableSchemaDefinition {
typeDefs: ITypeDefinitions;
@@ -142,7 +325,13 @@ export type IFieldIteratorFn = (
fieldName: string,
) => void;
+export type IDefaultValueIteratorFn = (
+ type: GraphQLInputType,
+ value: any,
+) => void;
+
export type NextResolverFn = () => Promise;
+
export type DirectiveResolverFn = (
next: NextResolverFn,
source: TSource,
@@ -157,7 +346,11 @@ export interface IDirectiveResolvers {
/* XXX on mocks, args are optional, Not sure if a bug. */
export type IMockFn = GraphQLFieldResolver;
-export type IMocks = { [key: string]: IMockFn };
+
+export interface IMocks {
+ [key: string]: IMockFn;
+}
+
export type IMockTypeFn = (
type: GraphQLType,
typeName?: string,
@@ -165,7 +358,7 @@ export type IMockTypeFn = (
) => GraphQLFieldResolver;
export interface IMockOptions {
- schema: GraphQLSchema;
+ schema?: GraphQLSchema;
mocks?: IMocks;
preserveResolvers?: boolean;
}
@@ -177,40 +370,226 @@ export interface IMockServer {
) => Promise;
}
-export type MergeTypeCandidate = {
- schema?: GraphQLSchema;
- type: GraphQLNamedType;
-};
-
-export type TypeWithResolvers = {
- type: GraphQLNamedType;
- resolvers?: IResolvers;
-};
-
-export type VisitTypeResult = GraphQLNamedType | TypeWithResolvers | null;
-
-export type VisitType = (
- name: string,
- candidates: Array,
-) => VisitTypeResult;
+export type OnTypeConflict = (
+ left: GraphQLNamedType,
+ right: GraphQLNamedType,
+ info?: {
+ left: {
+ schema?: GraphQLSchema | SubschemaConfig;
+ };
+ right: {
+ schema?: GraphQLSchema | SubschemaConfig;
+ };
+ },
+) => GraphQLNamedType;
export type Operation = 'query' | 'mutation' | 'subscription';
-export type Request = {
+export interface Request {
document: DocumentNode;
variables: Record;
extensions?: Record;
-};
+}
-export type Result = ExecutionResult & {
+export interface Result extends ExecutionResult {
extensions?: Record;
-};
-
-export type ResolveType = (type: T) => T;
+}
-export type GraphQLParseOptions = {
+export interface GraphQLParseOptions {
noLocation?: boolean;
allowLegacySDLEmptyFields?: boolean;
allowLegacySDLImplementsInterfaces?: boolean;
experimentalFragmentVariables?: boolean;
-};
+}
+
+export type IndexedObject = { [key: string]: V } | ReadonlyArray;
+
+export type VisitableSchemaType =
+ | GraphQLSchema
+ | GraphQLObjectType
+ | GraphQLInterfaceType
+ | GraphQLInputObjectType
+ | GraphQLNamedType
+ | GraphQLScalarType
+ | GraphQLField
+ | GraphQLInputField
+ | GraphQLArgument
+ | GraphQLUnionType
+ | GraphQLEnumType
+ | GraphQLEnumValue;
+
+export type VisitorSelector = (
+ type: VisitableSchemaType,
+ methodName: string,
+) => Array;
+
+export enum VisitSchemaKind {
+ TYPE = 'VisitSchemaKind.TYPE',
+ SCALAR_TYPE = 'VisitSchemaKind.SCALAR_TYPE',
+ ENUM_TYPE = 'VisitSchemaKind.ENUM_TYPE',
+ COMPOSITE_TYPE = 'VisitSchemaKind.COMPOSITE_TYPE',
+ OBJECT_TYPE = 'VisitSchemaKind.OBJECT_TYPE',
+ INPUT_OBJECT_TYPE = 'VisitSchemaKind.INPUT_OBJECT_TYPE',
+ ABSTRACT_TYPE = 'VisitSchemaKind.ABSTRACT_TYPE',
+ UNION_TYPE = 'VisitSchemaKind.UNION_TYPE',
+ INTERFACE_TYPE = 'VisitSchemaKind.INTERFACE_TYPE',
+ ROOT_OBJECT = 'VisitSchemaKind.ROOT_OBJECT',
+ QUERY = 'VisitSchemaKind.QUERY',
+ MUTATION = 'VisitSchemaKind.MUTATION',
+ SUBSCRIPTION = 'VisitSchemaKind.SUBSCRIPTION',
+}
+
+export interface SchemaVisitorMap {
+ [VisitSchemaKind.TYPE]?: NamedTypeVisitor;
+ [VisitSchemaKind.SCALAR_TYPE]?: ScalarTypeVisitor;
+ [VisitSchemaKind.ENUM_TYPE]?: EnumTypeVisitor;
+ [VisitSchemaKind.COMPOSITE_TYPE]?: CompositeTypeVisitor;
+ [VisitSchemaKind.OBJECT_TYPE]?: ObjectTypeVisitor;
+ [VisitSchemaKind.INPUT_OBJECT_TYPE]?: InputObjectTypeVisitor;
+ [VisitSchemaKind.ABSTRACT_TYPE]?: AbstractTypeVisitor;
+ [VisitSchemaKind.UNION_TYPE]?: UnionTypeVisitor;
+ [VisitSchemaKind.INTERFACE_TYPE]?: InterfaceTypeVisitor;
+ [VisitSchemaKind.ROOT_OBJECT]?: ObjectTypeVisitor;
+ [VisitSchemaKind.QUERY]?: ObjectTypeVisitor;
+ [VisitSchemaKind.MUTATION]?: ObjectTypeVisitor;
+ [VisitSchemaKind.SUBSCRIPTION]?: ObjectTypeVisitor;
+}
+
+export type NamedTypeVisitor = (
+ type: GraphQLNamedType,
+ schema: GraphQLSchema,
+) => GraphQLNamedType | null | undefined;
+
+export type ScalarTypeVisitor = (
+ type: GraphQLScalarType,
+ schema: GraphQLSchema,
+) => GraphQLScalarType | null | undefined;
+
+export type EnumTypeVisitor = (
+ type: GraphQLEnumType,
+ schema: GraphQLSchema,
+) => GraphQLEnumType | null | undefined;
+
+export type CompositeTypeVisitor = (
+ type: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType,
+ schema: GraphQLSchema,
+) =>
+ | GraphQLObjectType
+ | GraphQLInterfaceType
+ | GraphQLUnionType
+ | null
+ | undefined;
+
+export type ObjectTypeVisitor = (
+ type: GraphQLObjectType,
+ schema: GraphQLSchema,
+) => GraphQLObjectType | null | undefined;
+
+export type InputObjectTypeVisitor = (
+ type: GraphQLInputObjectType,
+ schema: GraphQLSchema,
+) => GraphQLInputObjectType | null | undefined;
+
+export type AbstractTypeVisitor = (
+ type: GraphQLInterfaceType | GraphQLUnionType,
+ schema: GraphQLSchema,
+) => GraphQLInterfaceType | GraphQLUnionType | null | undefined;
+
+export type UnionTypeVisitor = (
+ type: GraphQLUnionType,
+ schema: GraphQLSchema,
+) => GraphQLUnionType | null | undefined;
+
+export type InterfaceTypeVisitor = (
+ type: GraphQLInterfaceType,
+ schema: GraphQLSchema,
+) => GraphQLInterfaceType | null | undefined;
+
+export enum MapperKind {
+ TYPE = 'MapperKind.TYPE',
+ SCALAR_TYPE = 'MapperKind.SCALAR_TYPE',
+ ENUM_TYPE = 'MapperKind.ENUM_TYPE',
+ COMPOSITE_TYPE = 'MapperKind.COMPOSITE_TYPE',
+ OBJECT_TYPE = 'MapperKind.OBJECT_TYPE',
+ INPUT_OBJECT_TYPE = 'MapperKind.INPUT_OBJECT_TYPE',
+ ABSTRACT_TYPE = 'MapperKind.ABSTRACT_TYPE',
+ UNION_TYPE = 'MapperKind.UNION_TYPE',
+ INTERFACE_TYPE = 'MapperKind.INTERFACE_TYPE',
+ ROOT_OBJECT = 'MapperKind.ROOT_OBJECT',
+ QUERY = 'MapperKind.QUERY',
+ MUTATION = 'MapperKind.MUTATION',
+ SUBSCRIPTION = 'MapperKind.SUBSCRIPTION',
+ DIRECTIVE = 'MapperKind.DIRECTIVE',
+}
+
+export interface SchemaMapper {
+ [MapperKind.TYPE]?: NamedTypeMapper;
+ [MapperKind.SCALAR_TYPE]?: ScalarTypeMapper;
+ [MapperKind.ENUM_TYPE]?: EnumTypeMapper;
+ [MapperKind.COMPOSITE_TYPE]?: CompositeTypeMapper;
+ [MapperKind.OBJECT_TYPE]?: ObjectTypeMapper;
+ [MapperKind.INPUT_OBJECT_TYPE]?: InputObjectTypeMapper;
+ [MapperKind.ABSTRACT_TYPE]?: AbstractTypeMapper;
+ [MapperKind.UNION_TYPE]?: UnionTypeMapper;
+ [MapperKind.INTERFACE_TYPE]?: InterfaceTypeMapper;
+ [MapperKind.ROOT_OBJECT]?: ObjectTypeMapper;
+ [MapperKind.QUERY]?: ObjectTypeMapper;
+ [MapperKind.MUTATION]?: ObjectTypeMapper;
+ [MapperKind.SUBSCRIPTION]?: ObjectTypeMapper;
+ [MapperKind.DIRECTIVE]?: DirectiveMapper;
+}
+
+export type NamedTypeMapper = (
+ type: GraphQLNamedType,
+ schema: GraphQLSchema,
+) => GraphQLNamedType | null | undefined;
+
+export type ScalarTypeMapper = (
+ type: GraphQLScalarType,
+ schema: GraphQLSchema,
+) => GraphQLScalarType | null | undefined;
+
+export type EnumTypeMapper = (
+ type: GraphQLEnumType,
+ schema: GraphQLSchema,
+) => GraphQLEnumType | null | undefined;
+
+export type CompositeTypeMapper = (
+ type: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType,
+ schema: GraphQLSchema,
+) =>
+ | GraphQLObjectType
+ | GraphQLInterfaceType
+ | GraphQLUnionType
+ | null
+ | undefined;
+
+export type ObjectTypeMapper = (
+ type: GraphQLObjectType,
+ schema: GraphQLSchema,
+) => GraphQLObjectType | null | undefined;
+
+export type InputObjectTypeMapper = (
+ type: GraphQLInputObjectType,
+ schema: GraphQLSchema,
+) => GraphQLInputObjectType | null | undefined;
+
+export type AbstractTypeMapper = (
+ type: GraphQLInterfaceType | GraphQLUnionType,
+ schema: GraphQLSchema,
+) => GraphQLInterfaceType | GraphQLUnionType | null | undefined;
+
+export type UnionTypeMapper = (
+ type: GraphQLUnionType,
+ schema: GraphQLSchema,
+) => GraphQLUnionType | null | undefined;
+
+export type InterfaceTypeMapper = (
+ type: GraphQLInterfaceType,
+ schema: GraphQLSchema,
+) => GraphQLInterfaceType | null | undefined;
+
+export type DirectiveMapper = (
+ directive: GraphQLDirective,
+ schema: GraphQLSchema,
+) => GraphQLDirective | null | undefined;
diff --git a/src/delegate/addTypenameToAbstract.ts b/src/delegate/addTypenameToAbstract.ts
new file mode 100644
index 00000000000..362cb9d7f2a
--- /dev/null
+++ b/src/delegate/addTypenameToAbstract.ts
@@ -0,0 +1,45 @@
+import {
+ GraphQLType,
+ DocumentNode,
+ TypeInfo,
+ visit,
+ visitWithTypeInfo,
+ SelectionSetNode,
+ Kind,
+ GraphQLSchema,
+ isAbstractType,
+} from 'graphql';
+
+export function addTypenameToAbstract(
+ targetSchema: GraphQLSchema,
+ document: DocumentNode,
+): DocumentNode {
+ const typeInfo = new TypeInfo(targetSchema);
+ return visit(
+ document,
+ visitWithTypeInfo(typeInfo, {
+ [Kind.SELECTION_SET](
+ node: SelectionSetNode,
+ ): SelectionSetNode | null | undefined {
+ const parentType: GraphQLType = typeInfo.getParentType();
+ let selections = node.selections;
+ if (parentType != null && isAbstractType(parentType)) {
+ selections = selections.concat({
+ kind: Kind.FIELD,
+ name: {
+ kind: Kind.NAME,
+ value: '__typename',
+ },
+ });
+ }
+
+ if (selections !== node.selections) {
+ return {
+ ...node,
+ selections,
+ };
+ }
+ },
+ }),
+ );
+}
diff --git a/src/delegate/checkResultAndHandleErrors.ts b/src/delegate/checkResultAndHandleErrors.ts
new file mode 100644
index 00000000000..99c2e9e06b7
--- /dev/null
+++ b/src/delegate/checkResultAndHandleErrors.ts
@@ -0,0 +1,317 @@
+import {
+ GraphQLResolveInfo,
+ responsePathAsArray,
+ getNullableType,
+ isCompositeType,
+ isLeafType,
+ isListType,
+ ExecutionResult,
+ GraphQLCompositeType,
+ GraphQLError,
+ GraphQLList,
+ GraphQLOutputType,
+ GraphQLType,
+ GraphQLSchema,
+ FieldNode,
+ isAbstractType,
+ GraphQLObjectType,
+} from 'graphql';
+import { collectFields, ExecutionContext } from 'graphql/execution/execute';
+
+import {
+ SubschemaConfig,
+ IGraphQLToolsResolveInfo,
+ isSubschemaConfig,
+ MergedTypeInfo,
+} from '../Interfaces';
+
+import {
+ relocatedError,
+ combineErrors,
+ getErrorsByPathSegment,
+} from '../stitch/errors';
+import { getResponseKeyFromInfo } from '../stitch/getResponseKeyFromInfo';
+import resolveFromParentTypename from '../stitch/resolveFromParentTypename';
+import { setErrors, setObjectSubschema } from '../stitch/proxiedResult';
+import { mergeFields } from '../stitch/mergeFields';
+
+export function checkResultAndHandleErrors(
+ result: ExecutionResult,
+ context: Record,
+ info: GraphQLResolveInfo,
+ responseKey: string = getResponseKeyFromInfo(info),
+ subschema?: GraphQLSchema | SubschemaConfig,
+ returnType: GraphQLOutputType = info.returnType,
+ skipTypeMerging?: boolean,
+): any {
+ const errors = result.errors != null ? result.errors : [];
+ const data = result.data != null ? result.data[responseKey] : undefined;
+
+ return handleResult(
+ data,
+ errors,
+ subschema,
+ context,
+ info,
+ returnType,
+ skipTypeMerging,
+ );
+}
+
+export function handleResult(
+ result: any,
+ errors: ReadonlyArray,
+ subschema: GraphQLSchema | SubschemaConfig,
+ context: Record,
+ info: IGraphQLToolsResolveInfo,
+ returnType = info.returnType,
+ skipTypeMerging?: boolean,
+): any {
+ const type = getNullableType(returnType);
+
+ if (result == null) {
+ return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors);
+ }
+
+ if (isLeafType(type)) {
+ return type.parseValue(result);
+ } else if (isCompositeType(type)) {
+ return handleObject(
+ type,
+ result,
+ errors,
+ subschema,
+ context,
+ info,
+ skipTypeMerging,
+ );
+ } else if (isListType(type)) {
+ return handleList(
+ type,
+ result,
+ errors,
+ subschema,
+ context,
+ info,
+ skipTypeMerging,
+ );
+ }
+}
+
+function handleList(
+ type: GraphQLList,
+ list: Array,
+ errors: ReadonlyArray,
+ subschema: GraphQLSchema | SubschemaConfig,
+ context: Record,
+ info: IGraphQLToolsResolveInfo,
+ skipTypeMerging?: boolean,
+) {
+ const childErrors = getErrorsByPathSegment(errors);
+
+ return list.map((listMember, index) =>
+ handleListMember(
+ getNullableType(type.ofType),
+ listMember,
+ index,
+ childErrors[index] != null ? childErrors[index] : [],
+ subschema,
+ context,
+ info,
+ skipTypeMerging,
+ ),
+ );
+}
+
+function handleListMember(
+ type: GraphQLType,
+ listMember: any,
+ index: number,
+ errors: ReadonlyArray,
+ subschema: GraphQLSchema | SubschemaConfig,
+ context: Record,
+ info: IGraphQLToolsResolveInfo,
+ skipTypeMerging?: boolean,
+): any {
+ if (listMember == null) {
+ return handleNull(
+ info.fieldNodes,
+ [...responsePathAsArray(info.path), index],
+ errors,
+ );
+ }
+
+ if (isLeafType(type)) {
+ return type.parseValue(listMember);
+ } else if (isCompositeType(type)) {
+ return handleObject(
+ type,
+ listMember,
+ errors,
+ subschema,
+ context,
+ info,
+ skipTypeMerging,
+ );
+ } else if (isListType(type)) {
+ return handleList(
+ type,
+ listMember,
+ errors,
+ subschema,
+ context,
+ info,
+ skipTypeMerging,
+ );
+ }
+}
+
+export function handleObject(
+ type: GraphQLCompositeType,
+ object: any,
+ errors: ReadonlyArray,
+ subschema: GraphQLSchema | SubschemaConfig,
+ context: Record,
+ info: IGraphQLToolsResolveInfo,
+ skipTypeMerging?: boolean,
+) {
+ setErrors(
+ object,
+ errors.map((error) =>
+ relocatedError(
+ error,
+ error.nodes,
+ error.path != null ? error.path.slice(1) : undefined,
+ ),
+ ),
+ );
+
+ setObjectSubschema(object, subschema);
+
+ if (skipTypeMerging || !info.mergeInfo) {
+ return object;
+ }
+
+ const typeName = isAbstractType(type)
+ ? info.schema.getTypeMap()[resolveFromParentTypename(object)].name
+ : type.name;
+ const mergedTypeInfo = info.mergeInfo.mergedTypes[typeName];
+ let targetSubschemas: Array;
+
+ if (mergedTypeInfo != null) {
+ targetSubschemas = mergedTypeInfo.subschemas;
+ }
+
+ if (!targetSubschemas) {
+ return object;
+ }
+
+ targetSubschemas = targetSubschemas.filter((s) => s !== subschema);
+ if (!targetSubschemas.length) {
+ return object;
+ }
+
+ const subFields = collectSubFields(info, object.__typename);
+
+ const selections = getFieldsNotInSubschema(
+ subFields,
+ subschema,
+ mergedTypeInfo,
+ object.__typename,
+ );
+
+ return mergeFields(
+ mergedTypeInfo,
+ typeName,
+ object,
+ selections,
+ [subschema as SubschemaConfig],
+ targetSubschemas,
+ context,
+ info,
+ );
+}
+
+function collectSubFields(info: IGraphQLToolsResolveInfo, typeName: string) {
+ let subFieldNodes: Record> = Object.create(null);
+ const visitedFragmentNames = Object.create(null);
+ info.fieldNodes.forEach((fieldNode) => {
+ subFieldNodes = collectFields(
+ ({
+ schema: info.schema,
+ variableValues: info.variableValues,
+ fragments: info.fragments,
+ } as unknown) as ExecutionContext,
+ info.schema.getType(typeName) as GraphQLObjectType,
+ fieldNode.selectionSet,
+ subFieldNodes,
+ visitedFragmentNames,
+ );
+ });
+ return subFieldNodes;
+}
+
+function getFieldsNotInSubschema(
+ subFieldNodes: Record>,
+ subschema: GraphQLSchema | SubschemaConfig,
+ mergedTypeInfo: MergedTypeInfo,
+ typeName: string,
+): Array {
+ const typeMap = isSubschemaConfig(subschema)
+ ? mergedTypeInfo.typeMaps.get(subschema)
+ : subschema.getTypeMap();
+ const fields = (typeMap[typeName] as GraphQLObjectType).getFields();
+
+ const fieldsNotInSchema: Array = [];
+ Object.keys(subFieldNodes).forEach((responseName) => {
+ subFieldNodes[responseName].forEach((subFieldNode) => {
+ if (!fields[subFieldNode.name.value]) {
+ fieldsNotInSchema.push(subFieldNode);
+ }
+ });
+ });
+
+ return fieldsNotInSchema;
+}
+
+export function handleNull(
+ fieldNodes: ReadonlyArray,
+ path: Array,
+ errors: ReadonlyArray,
+) {
+ if (errors.length) {
+ if (errors.some((error) => !error.path || error.path.length < 2)) {
+ return relocatedError(combineErrors(errors), fieldNodes, path);
+ } else if (errors.some((error) => typeof error.path[1] === 'string')) {
+ const childErrors = getErrorsByPathSegment(errors);
+
+ const result = Object.create(null);
+ Object.keys(childErrors).forEach((pathSegment) => {
+ result[pathSegment] = handleNull(
+ fieldNodes,
+ [...path, pathSegment],
+ childErrors[pathSegment],
+ );
+ });
+
+ return result;
+ }
+
+ const childErrors = getErrorsByPathSegment(errors);
+
+ const result: Array = [];
+ Object.keys(childErrors).forEach((pathSegment) => {
+ result.push(
+ handleNull(
+ fieldNodes,
+ [...path, parseInt(pathSegment, 10)],
+ childErrors[pathSegment],
+ ),
+ );
+ });
+
+ return result;
+ }
+
+ return null;
+}
diff --git a/src/delegate/createRequest.ts b/src/delegate/createRequest.ts
new file mode 100644
index 00000000000..28ef8504ba9
--- /dev/null
+++ b/src/delegate/createRequest.ts
@@ -0,0 +1,192 @@
+import {
+ ArgumentNode,
+ FieldNode,
+ FragmentDefinitionNode,
+ Kind,
+ OperationDefinitionNode,
+ SelectionNode,
+ GraphQLSchema,
+ GraphQLObjectType,
+ OperationTypeNode,
+ typeFromAST,
+ NamedTypeNode,
+ GraphQLInputType,
+ GraphQLArgument,
+ VariableDefinitionNode,
+ SelectionSetNode,
+} from 'graphql';
+
+import { ICreateRequestFromInfo, Request, ICreateRequest } from '../Interfaces';
+import { serializeInputValue } from '../utils/index';
+import { updateArgument } from '../utils/updateArgument';
+
+export function getDelegatingOperation(
+ parentType: GraphQLObjectType,
+ schema: GraphQLSchema,
+): OperationTypeNode {
+ if (parentType === schema.getMutationType()) {
+ return 'mutation';
+ } else if (parentType === schema.getSubscriptionType()) {
+ return 'subscription';
+ }
+
+ return 'query';
+}
+
+export function createRequestFromInfo({
+ info,
+ operation = getDelegatingOperation(info.parentType, info.schema),
+ fieldName = info.fieldName,
+ selectionSet,
+ fieldNodes,
+}: ICreateRequestFromInfo): Request {
+ return createRequest({
+ sourceSchema: info.schema,
+ sourceParentType: info.parentType,
+ sourceFieldName: info.fieldName,
+ fragments: info.fragments,
+ variableDefinitions: info.operation.variableDefinitions,
+ variableValues: info.variableValues,
+ targetOperation: operation,
+ targetFieldName: fieldName,
+ selectionSet,
+ fieldNodes:
+ selectionSet != null
+ ? undefined
+ : fieldNodes != null
+ ? fieldNodes
+ : info.fieldNodes,
+ });
+}
+
+export function createRequest({
+ sourceSchema,
+ sourceParentType,
+ sourceFieldName,
+ fragments,
+ variableDefinitions,
+ variableValues,
+ targetOperation,
+ targetFieldName,
+ selectionSet,
+ fieldNodes,
+}: ICreateRequest): Request {
+ let argumentNodes: ReadonlyArray;
+ let newSelectionSet: SelectionSetNode = selectionSet;
+ if (!selectionSet && fieldNodes != null) {
+ const selections: Array = fieldNodes.reduce(
+ (acc, fieldNode) =>
+ fieldNode.selectionSet != null
+ ? acc.concat(fieldNode.selectionSet.selections)
+ : acc,
+ [],
+ );
+
+ newSelectionSet = selections.length
+ ? {
+ kind: Kind.SELECTION_SET,
+ selections,
+ }
+ : undefined;
+
+ argumentNodes = fieldNodes[0].arguments;
+ } else {
+ argumentNodes = [];
+ }
+
+ const newVariables = {};
+ const variableDefinitionMap = {};
+ variableDefinitions.forEach((def) => {
+ const varName = def.variable.name.value;
+ variableDefinitionMap[varName] = def;
+ const varType = typeFromAST(
+ sourceSchema,
+ def.type as NamedTypeNode,
+ ) as GraphQLInputType;
+ newVariables[varName] = serializeInputValue(
+ varType,
+ variableValues[varName],
+ );
+ });
+
+ const argumentNodeMap: Record = {};
+ argumentNodes.forEach((argument: ArgumentNode) => {
+ argumentNodeMap[argument.name.value] = argument;
+ });
+
+ updateArgumentsWithDefaults(
+ sourceParentType,
+ sourceFieldName,
+ argumentNodeMap,
+ variableDefinitionMap,
+ newVariables,
+ );
+
+ const rootfieldNode: FieldNode = {
+ kind: Kind.FIELD,
+ alias: null,
+ arguments: Object.keys(argumentNodeMap).map(
+ (argName) => argumentNodeMap[argName],
+ ),
+ selectionSet: newSelectionSet,
+ name: {
+ kind: Kind.NAME,
+ value: targetFieldName || fieldNodes[0].name.value,
+ },
+ };
+
+ const operationDefinition: OperationDefinitionNode = {
+ kind: Kind.OPERATION_DEFINITION,
+ operation: targetOperation,
+ variableDefinitions: Object.keys(variableDefinitionMap).map(
+ (varName) => variableDefinitionMap[varName],
+ ),
+ selectionSet: {
+ kind: Kind.SELECTION_SET,
+ selections: [rootfieldNode],
+ },
+ };
+
+ const fragmentDefinitions: Array = Object.keys(
+ fragments,
+ ).map((fragmentName) => fragments[fragmentName]);
+
+ const document = {
+ kind: Kind.DOCUMENT,
+ definitions: [operationDefinition, ...fragmentDefinitions],
+ };
+
+ return {
+ document,
+ variables: newVariables,
+ };
+}
+
+function updateArgumentsWithDefaults(
+ sourceParentType: GraphQLObjectType,
+ sourceFieldName: string,
+ argumentNodeMap: Record,
+ variableDefinitionMap: Record,
+ variableValues: Record,
+): void {
+ const sourceField = sourceParentType.getFields()[sourceFieldName];
+ sourceField.args.forEach((argument: GraphQLArgument) => {
+ const argName = argument.name;
+ const sourceArgType = argument.type;
+
+ if (argumentNodeMap[argName] === undefined) {
+ const defaultValue = argument.defaultValue;
+
+ if (defaultValue !== undefined) {
+ updateArgument(
+ argName,
+ sourceArgType,
+ argumentNodeMap,
+ variableDefinitionMap,
+ variableValues,
+ serializeInputValue(sourceArgType, defaultValue),
+ );
+ }
+ }
+ });
+}
diff --git a/src/delegate/delegateToSchema.ts b/src/delegate/delegateToSchema.ts
new file mode 100644
index 00000000000..0fd2387483d
--- /dev/null
+++ b/src/delegate/delegateToSchema.ts
@@ -0,0 +1,347 @@
+import { isAsyncIterable } from 'iterall';
+import { ApolloLink, execute as executeLink } from 'apollo-link';
+import {
+ subscribe,
+ execute,
+ validate,
+ GraphQLSchema,
+ ExecutionResult,
+ GraphQLOutputType,
+ isSchema,
+} from 'graphql';
+
+import {
+ IDelegateToSchemaOptions,
+ IDelegateRequestOptions,
+ Fetcher,
+ Delegator,
+ SubschemaConfig,
+ isSubschemaConfig,
+ IGraphQLToolsResolveInfo,
+ Transform,
+} from '../Interfaces';
+import {
+ ExpandAbstractTypes,
+ FilterToSchema,
+ AddReplacementSelectionSets,
+ AddReplacementFragments,
+ AddMergedTypeSelectionSets,
+ AddTypenameToAbstract,
+ CheckResultAndHandleErrors,
+ applyRequestTransforms,
+ applyResultTransforms,
+ AddArgumentsAsVariables,
+} from '../wrap/index';
+
+import linkToFetcher from '../stitch/linkToFetcher';
+import { observableToAsyncIterable } from '../stitch/observableToAsyncIterable';
+import mapAsyncIterator from '../stitch/mapAsyncIterator';
+import { combineErrors } from '../stitch/errors';
+
+import { createRequestFromInfo, getDelegatingOperation } from './createRequest';
+
+export default function delegateToSchema(
+ options: IDelegateToSchemaOptions | GraphQLSchema,
+): any {
+ if (isSchema(options)) {
+ throw new Error(
+ 'Passing positional arguments to delegateToSchema is deprecated. ' +
+ 'Please pass named parameters instead.',
+ );
+ }
+
+ const {
+ info,
+ operation = getDelegatingOperation(info.parentType, info.schema),
+ fieldName = info.fieldName,
+ returnType = info.returnType,
+ selectionSet,
+ fieldNodes,
+ } = options;
+
+ const request = createRequestFromInfo({
+ info,
+ operation,
+ fieldName,
+ selectionSet,
+ fieldNodes,
+ });
+
+ return delegateRequest({
+ ...options,
+ request,
+ operation,
+ fieldName,
+ returnType,
+ });
+}
+
+function buildDelegationTransforms(
+ subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig,
+ info: IGraphQLToolsResolveInfo,
+ context: Record,
+ targetSchema: GraphQLSchema,
+ fieldName: string,
+ args: Record,
+ returnType: GraphQLOutputType,
+ transforms: Array,
+ skipTypeMerging: boolean,
+): Array {
+ let delegationTransforms: Array = [
+ new CheckResultAndHandleErrors(
+ info,
+ fieldName,
+ subschemaOrSubschemaConfig,
+ context,
+ returnType,
+ skipTypeMerging,
+ ),
+ ];
+
+ if (info.mergeInfo != null) {
+ delegationTransforms.push(
+ new AddReplacementSelectionSets(
+ info.schema,
+ info.mergeInfo.replacementSelectionSets,
+ ),
+ new AddMergedTypeSelectionSets(info.schema, info.mergeInfo.mergedTypes),
+ );
+ }
+
+ delegationTransforms = delegationTransforms.concat(transforms);
+
+ delegationTransforms.push(new ExpandAbstractTypes(info.schema, targetSchema));
+
+ if (info.mergeInfo != null) {
+ delegationTransforms.push(
+ new AddReplacementFragments(
+ targetSchema,
+ info.mergeInfo.replacementFragments,
+ ),
+ );
+ }
+
+ if (args != null) {
+ delegationTransforms.push(new AddArgumentsAsVariables(targetSchema, args));
+ }
+
+ delegationTransforms.push(
+ new FilterToSchema(targetSchema),
+ new AddTypenameToAbstract(targetSchema),
+ );
+
+ return delegationTransforms;
+}
+
+export function delegateRequest({
+ request,
+ schema: subschemaOrSubschemaConfig,
+ rootValue,
+ info,
+ operation = getDelegatingOperation(info.parentType, info.schema),
+ fieldName = info.fieldName,
+ args,
+ returnType = info.returnType,
+ context,
+ transforms = [],
+ skipValidation,
+ skipTypeMerging,
+}: IDelegateRequestOptions): any {
+ let targetSchema: GraphQLSchema;
+ let targetRootValue: Record;
+ let requestTransforms: Array = transforms.slice();
+ let subschemaConfig: SubschemaConfig;
+
+ if (isSubschemaConfig(subschemaOrSubschemaConfig)) {
+ subschemaConfig = subschemaOrSubschemaConfig;
+ targetSchema = subschemaConfig.schema;
+ targetRootValue =
+ rootValue != null
+ ? rootValue
+ : subschemaConfig.rootValue != null
+ ? subschemaConfig.rootValue
+ : info.rootValue;
+ if (subschemaConfig.transforms != null) {
+ requestTransforms = requestTransforms.concat(subschemaConfig.transforms);
+ }
+ } else {
+ targetSchema = subschemaOrSubschemaConfig;
+ targetRootValue = rootValue != null ? rootValue : info.rootValue;
+ }
+
+ const delegationTransforms = buildDelegationTransforms(
+ subschemaOrSubschemaConfig,
+ info,
+ context,
+ targetSchema,
+ fieldName,
+ args,
+ returnType,
+ requestTransforms.reverse(),
+ skipTypeMerging,
+ );
+
+ const processedRequest = applyRequestTransforms(
+ request,
+ delegationTransforms,
+ );
+
+ if (!skipValidation) {
+ const errors = validate(targetSchema, processedRequest.document);
+ if (errors.length > 0) {
+ const combinedError: Error = combineErrors(errors);
+ throw combinedError;
+ }
+ }
+
+ if (operation === 'query' || operation === 'mutation') {
+ const executor = createExecutor(
+ targetSchema,
+ targetRootValue,
+ context,
+ subschemaConfig,
+ );
+
+ const executionResult:
+ | ExecutionResult
+ | Promise = executor({
+ document: processedRequest.document,
+ context,
+ variables: processedRequest.variables,
+ });
+
+ if (executionResult instanceof Promise) {
+ return executionResult.then((originalResult: any) =>
+ applyResultTransforms(originalResult, delegationTransforms),
+ );
+ }
+ return applyResultTransforms(executionResult, delegationTransforms);
+ }
+
+ const subscriber = createSubscriber(
+ targetSchema,
+ targetRootValue,
+ context,
+ subschemaConfig,
+ );
+
+ return subscriber({
+ document: processedRequest.document,
+ context,
+ variables: processedRequest.variables,
+ }).then(
+ (
+ subscriptionResult:
+ | AsyncIterableIterator
+ | ExecutionResult,
+ ) => {
+ if (isAsyncIterable(subscriptionResult)) {
+ // "subscribe" to the subscription result and map the result through the transforms
+ return mapAsyncIterator(
+ subscriptionResult,
+ (result) => {
+ const transformedResult = applyResultTransforms(
+ result,
+ delegationTransforms,
+ );
+ // wrap with fieldName to return for an additional round of resolutioon
+ // with payload as rootValue
+ return {
+ [info.fieldName]: transformedResult,
+ };
+ },
+ );
+ }
+
+ return applyResultTransforms(subscriptionResult, delegationTransforms);
+ },
+ );
+}
+
+function createExecutor(
+ schema: GraphQLSchema,
+ rootValue: Record,
+ context: Record,
+ subschemaConfig?: SubschemaConfig,
+): Delegator {
+ let fetcher: Fetcher;
+ let targetRootValue: Record = rootValue;
+ if (subschemaConfig != null) {
+ if (subschemaConfig.dispatcher != null) {
+ const dynamicLinkOrFetcher = subschemaConfig.dispatcher(context);
+ fetcher =
+ typeof dynamicLinkOrFetcher === 'function'
+ ? dynamicLinkOrFetcher
+ : linkToFetcher(dynamicLinkOrFetcher);
+ } else if (subschemaConfig.link != null) {
+ fetcher = linkToFetcher(subschemaConfig.link);
+ } else if (subschemaConfig.fetcher != null) {
+ fetcher = subschemaConfig.fetcher;
+ }
+
+ if (!fetcher && !rootValue && subschemaConfig.rootValue != null) {
+ targetRootValue = subschemaConfig.rootValue;
+ }
+ }
+
+ if (fetcher != null) {
+ return ({ document, context: graphqlContext, variables }) =>
+ fetcher({
+ query: document,
+ variables,
+ context: { graphqlContext },
+ });
+ }
+
+ return ({ document, context: graphqlContext, variables }) =>
+ execute({
+ schema,
+ document,
+ rootValue: targetRootValue,
+ contextValue: graphqlContext,
+ variableValues: variables,
+ });
+}
+
+function createSubscriber(
+ schema: GraphQLSchema,
+ rootValue: Record,
+ context: Record,
+ subschemaConfig?: SubschemaConfig,
+): Delegator {
+ let link: ApolloLink;
+ let targetRootValue: Record = rootValue;
+
+ if (subschemaConfig != null) {
+ if (subschemaConfig.dispatcher != null) {
+ link = subschemaConfig.dispatcher(context) as ApolloLink;
+ } else if (subschemaConfig.link != null) {
+ link = subschemaConfig.link;
+ }
+
+ if (!link && !rootValue && subschemaConfig.rootValue != null) {
+ targetRootValue = subschemaConfig.rootValue;
+ }
+ }
+
+ if (link != null) {
+ return ({ document, context: graphqlContext, variables }) => {
+ const operation = {
+ query: document,
+ variables,
+ context: { graphqlContext },
+ };
+ const observable = executeLink(link, operation);
+ return observableToAsyncIterable(observable);
+ };
+ }
+
+ return ({ document, context: graphqlContext, variables }) =>
+ subscribe({
+ schema,
+ document,
+ rootValue: targetRootValue,
+ contextValue: graphqlContext,
+ variableValues: variables,
+ });
+}
diff --git a/src/delegate/index.ts b/src/delegate/index.ts
new file mode 100644
index 00000000000..9e3c80e66ff
--- /dev/null
+++ b/src/delegate/index.ts
@@ -0,0 +1,9 @@
+import delegateToSchema, { delegateRequest } from './delegateToSchema';
+import { createRequestFromInfo, createRequest } from './createRequest';
+
+export {
+ delegateToSchema,
+ createRequestFromInfo,
+ createRequest,
+ delegateRequest,
+};
diff --git a/src/Logger.ts b/src/generate/Logger.ts
similarity index 64%
rename from src/Logger.ts
rename to src/generate/Logger.ts
index 3b8c420ff3a..15d4f6ead9d 100644
--- a/src/Logger.ts
+++ b/src/generate/Logger.ts
@@ -2,12 +2,12 @@
* A very simple class for logging errors
*/
-import { ILogger } from './Interfaces';
+import { ILogger } from '../Interfaces';
export class Logger implements ILogger {
- public errors: Error[];
- public name: string;
- private callback: Function;
+ public errors: Array;
+ public name: string | undefined;
+ private readonly callback: Function | undefined;
constructor(name?: string, callback?: Function) {
this.name = name;
@@ -23,13 +23,13 @@ export class Logger implements ILogger {
}
}
- public printOneError(e: Error) {
- return e.stack;
+ public printOneError(e: Error): string {
+ return e.stack ? e.stack : '';
}
public printAllErrors() {
return this.errors.reduce(
- (agg, e) => `${agg}\n${this.printOneError(e)}`,
+ (agg: string, e: Error) => `${agg}\n${this.printOneError(e)}`,
'',
);
}
diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts
deleted file mode 100644
index 5e9741017af..00000000000
--- a/src/generate/addResolveFunctionsToSchema.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-import { SchemaError } from '.';
-
-import {
- GraphQLField,
- GraphQLEnumType,
- GraphQLScalarType,
- GraphQLType,
- GraphQLSchema,
- GraphQLObjectType,
- GraphQLInterfaceType,
- GraphQLFieldMap,
-} from 'graphql';
-
-import {
- IResolvers,
- IResolverValidationOptions,
- IAddResolveFunctionsToSchemaOptions,
-} from '../Interfaces';
-import { applySchemaTransforms } from '../transforms/transforms';
-import { checkForResolveTypeResolver, extendResolversFromInterfaces } from '.';
-import ConvertEnumValues from '../transforms/ConvertEnumValues';
-
-function addResolveFunctionsToSchema(
- options: IAddResolveFunctionsToSchemaOptions | GraphQLSchema,
- legacyInputResolvers?: IResolvers,
- legacyInputValidationOptions?: IResolverValidationOptions,
-) {
- if (options instanceof GraphQLSchema) {
- console.warn(
- 'The addResolveFunctionsToSchema function takes named options now; see IAddResolveFunctionsToSchemaOptions',
- );
- options = {
- schema: options,
- resolvers: legacyInputResolvers,
- resolverValidationOptions: legacyInputValidationOptions,
- };
- }
-
- const {
- schema,
- resolvers: inputResolvers,
- resolverValidationOptions = {},
- inheritResolversFromInterfaces = false,
- } = options;
-
- const {
- allowResolversNotInSchema = false,
- requireResolversForResolveType,
- } = resolverValidationOptions;
-
- const resolvers = inheritResolversFromInterfaces
- ? extendResolversFromInterfaces(schema, inputResolvers)
- : inputResolvers;
-
- // Used to map the external value of an enum to its internal value, when
- // that internal value is provided by a resolver.
- const enumValueMap = Object.create(null);
-
- Object.keys(resolvers).forEach(typeName => {
- const resolverValue = resolvers[typeName];
- const resolverType = typeof resolverValue;
-
- if (resolverType !== 'object' && resolverType !== 'function') {
- throw new SchemaError(
- `"${typeName}" defined in resolvers, but has invalid value "${resolverValue}". A resolver's value ` +
- `must be of type object or function.`,
- );
- }
-
- const type = schema.getType(typeName);
-
- if (!type && typeName !== '__schema') {
- if (allowResolversNotInSchema) {
- return;
- }
-
- throw new SchemaError(
- `"${typeName}" defined in resolvers, but not in schema`,
- );
- }
-
- Object.keys(resolverValue).forEach(fieldName => {
- if (fieldName.startsWith('__')) {
- // this is for isTypeOf and resolveType and all the other stuff.
- type[fieldName.substring(2)] = resolverValue[fieldName];
- return;
- }
-
- if (type instanceof GraphQLScalarType) {
- type[fieldName] = resolverValue[fieldName];
- return;
- }
-
- if (type instanceof GraphQLEnumType) {
- if (!type.getValue(fieldName)) {
- if (allowResolversNotInSchema) {
- return;
- }
- throw new SchemaError(
- `${typeName}.${fieldName} was defined in resolvers, but enum is not in schema`,
- );
- }
-
- // We've encountered an enum resolver that is being used to provide an
- // internal enum value.
- // Reference: https://www.apollographql.com/docs/graphql-tools/scalars.html#internal-values
- //
- // We're storing a map of the current enums external facing value to
- // its resolver provided internal value. This map is used to transform
- // the current schema to a new schema that includes enums with the new
- // internal value.
- enumValueMap[type.name] = enumValueMap[type.name] || {};
- enumValueMap[type.name][fieldName] = resolverValue[fieldName];
- return;
- }
-
- // object type
- const fields = getFieldsForType(type);
- if (!fields) {
- if (allowResolversNotInSchema) {
- return;
- }
-
- throw new SchemaError(
- `${typeName} was defined in resolvers, but it's not an object`,
- );
- }
-
- if (!fields[fieldName]) {
- if (allowResolversNotInSchema) {
- return;
- }
-
- throw new SchemaError(
- `${typeName}.${fieldName} defined in resolvers, but not in schema`,
- );
- }
- const field = fields[fieldName];
- const fieldResolve = resolverValue[fieldName];
- if (typeof fieldResolve === 'function') {
- // for convenience. Allows shorter syntax in resolver definition file
- setFieldProperties(field, { resolve: fieldResolve });
- } else {
- if (typeof fieldResolve !== 'object') {
- throw new SchemaError(
- `Resolver ${typeName}.${fieldName} must be object or function`,
- );
- }
- setFieldProperties(field, fieldResolve);
- }
- });
- });
-
- checkForResolveTypeResolver(schema, requireResolversForResolveType);
-
- // If there are any enum resolver functions (that are used to return
- // internal enum values), create a new schema that includes enums with the
- // new internal facing values.
- const updatedSchema = applySchemaTransforms(schema, [
- new ConvertEnumValues(enumValueMap),
- ]);
-
- return updatedSchema;
-}
-
-function getFieldsForType(type: GraphQLType): GraphQLFieldMap {
- if (
- type instanceof GraphQLObjectType ||
- type instanceof GraphQLInterfaceType
- ) {
- return type.getFields();
- } else {
- return undefined;
- }
-}
-
-function setFieldProperties(
- field: GraphQLField,
- propertiesObj: Object,
-) {
- Object.keys(propertiesObj).forEach(propertyName => {
- field[propertyName] = propertiesObj[propertyName];
- });
-}
-
-export default addResolveFunctionsToSchema;
diff --git a/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts
new file mode 100644
index 00000000000..b61a516c947
--- /dev/null
+++ b/src/generate/addResolversToSchema.ts
@@ -0,0 +1,213 @@
+import {
+ GraphQLField,
+ GraphQLEnumType,
+ GraphQLSchema,
+ isSchema,
+ isScalarType,
+ isEnumType,
+ isUnionType,
+ isInterfaceType,
+ isObjectType,
+} from 'graphql';
+
+import {
+ IResolvers,
+ IResolverValidationOptions,
+ IAddResolversToSchemaOptions,
+} from '../Interfaces';
+import {
+ parseInputValue,
+ serializeInputValue,
+ healSchema,
+ forEachField,
+ forEachDefaultValue,
+} from '../utils/index';
+import { toConfig } from '../polyfills/index';
+
+import SchemaError from './SchemaError';
+import checkForResolveTypeResolver from './checkForResolveTypeResolver';
+import extendResolversFromInterfaces from './extendResolversFromInterfaces';
+
+function addResolversToSchema(
+ schemaOrOptions: GraphQLSchema | IAddResolversToSchemaOptions,
+ legacyInputResolvers?: IResolvers,
+ legacyInputValidationOptions?: IResolverValidationOptions,
+): GraphQLSchema {
+ const options: IAddResolversToSchemaOptions = isSchema(schemaOrOptions)
+ ? {
+ schema: schemaOrOptions,
+ resolvers: legacyInputResolvers,
+ resolverValidationOptions: legacyInputValidationOptions,
+ }
+ : schemaOrOptions;
+
+ const {
+ schema,
+ resolvers: inputResolvers,
+ defaultFieldResolver,
+ resolverValidationOptions = {},
+ inheritResolversFromInterfaces = false,
+ } = options;
+
+ const {
+ allowResolversNotInSchema = false,
+ requireResolversForResolveType,
+ } = resolverValidationOptions;
+
+ const resolvers = inheritResolversFromInterfaces
+ ? extendResolversFromInterfaces(schema, inputResolvers)
+ : inputResolvers;
+
+ const typeMap = schema.getTypeMap();
+
+ Object.keys(resolvers).forEach((typeName) => {
+ const resolverValue = resolvers[typeName];
+ const resolverType = typeof resolverValue;
+
+ if (resolverType !== 'object' && resolverType !== 'function') {
+ throw new SchemaError(
+ `"${typeName}" defined in resolvers, but has invalid value "${
+ resolverValue as string
+ }". A resolver's value must be of type object or function.`,
+ );
+ }
+
+ const type = schema.getType(typeName);
+
+ if (!type && typeName !== '__schema') {
+ if (allowResolversNotInSchema) {
+ return;
+ }
+
+ throw new SchemaError(
+ `"${typeName}" defined in resolvers, but not in schema`,
+ );
+ }
+
+ if (isScalarType(type)) {
+ // Support -- without recommending -- overriding default scalar types
+ Object.keys(resolverValue).forEach((fieldName) => {
+ if (fieldName.startsWith('__')) {
+ type[fieldName.substring(2)] = resolverValue[fieldName];
+ } else {
+ type[fieldName] = resolverValue[fieldName];
+ }
+ });
+ } else if (isEnumType(type)) {
+ // We've encountered an enum resolver that is being used to provide an
+ // internal enum value.
+ // Reference: https://www.apollographql.com/docs/graphql-tools/scalars.html#internal-values
+ Object.keys(resolverValue).forEach((fieldName) => {
+ if (!type.getValue(fieldName)) {
+ if (allowResolversNotInSchema) {
+ return;
+ }
+ throw new SchemaError(
+ `${typeName}.${fieldName} was defined in resolvers, but enum is not in schema`,
+ );
+ }
+ });
+
+ const config = toConfig(type);
+
+ const values = type.getValues();
+ const newValues = {};
+ values.forEach((value) => {
+ const newValue = Object.keys(resolverValue).includes(value.name)
+ ? resolverValue[value.name]
+ : value.name;
+ newValues[value.name] = {
+ value: newValue,
+ deprecationReason: value.deprecationReason,
+ description: value.description,
+ astNode: value.astNode,
+ };
+ });
+
+ // healSchema called later to update all fields to new type
+ typeMap[typeName] = new GraphQLEnumType({
+ ...config,
+ values: newValues,
+ });
+ } else if (isUnionType(type)) {
+ Object.keys(resolverValue).forEach((fieldName) => {
+ if (fieldName.startsWith('__')) {
+ // this is for isTypeOf and resolveType and all the other stuff.
+ type[fieldName.substring(2)] = resolverValue[fieldName];
+ return;
+ }
+ if (allowResolversNotInSchema) {
+ return;
+ }
+
+ throw new SchemaError(
+ `${typeName} was defined in resolvers, but it's not an object`,
+ );
+ });
+ } else if (isObjectType(type) || isInterfaceType(type)) {
+ Object.keys(resolverValue).forEach((fieldName) => {
+ if (fieldName.startsWith('__')) {
+ // this is for isTypeOf and resolveType and all the other stuff.
+ type[fieldName.substring(2)] = resolverValue[fieldName];
+ return;
+ }
+
+ const fields = type.getFields();
+ const field = fields[fieldName];
+
+ if (field == null) {
+ if (allowResolversNotInSchema) {
+ return;
+ }
+
+ throw new SchemaError(
+ `${typeName}.${fieldName} defined in resolvers, but not in schema`,
+ );
+ }
+
+ const fieldResolve = resolverValue[fieldName];
+ if (typeof fieldResolve === 'function') {
+ // for convenience. Allows shorter syntax in resolver definition file
+ field.resolve = fieldResolve;
+ } else {
+ if (typeof fieldResolve !== 'object') {
+ throw new SchemaError(
+ `Resolver ${typeName}.${fieldName} must be object or function`,
+ );
+ }
+ setFieldProperties(field, fieldResolve);
+ }
+ });
+ }
+ });
+
+ checkForResolveTypeResolver(schema, requireResolversForResolveType);
+
+ // serialize all default values prior to healing fields with new scalar/enum types.
+ forEachDefaultValue(schema, serializeInputValue);
+ // schema may have new scalar/enum types that require healing
+ healSchema(schema);
+ // reparse all default values with new parsing functions.
+ forEachDefaultValue(schema, parseInputValue);
+
+ if (defaultFieldResolver != null) {
+ forEachField(schema, (field) => {
+ if (!field.resolve) {
+ field.resolve = defaultFieldResolver;
+ }
+ });
+ }
+
+ return schema;
+}
+
+function setFieldProperties(
+ field: GraphQLField,
+ propertiesObj: Record,
+) {
+ Object.keys(propertiesObj).forEach((propertyName) => {
+ field[propertyName] = propertiesObj[propertyName];
+ });
+}
+
+export default addResolversToSchema;
diff --git a/src/generate/addSchemaLevelResolveFunction.ts b/src/generate/addSchemaLevelResolver.ts
similarity index 56%
rename from src/generate/addSchemaLevelResolveFunction.ts
rename to src/generate/addSchemaLevelResolver.ts
index 048c7e0770e..b77bb5033e4 100644
--- a/src/generate/addSchemaLevelResolveFunction.ts
+++ b/src/generate/addSchemaLevelResolver.ts
@@ -4,9 +4,9 @@ import {
GraphQLFieldResolver,
} from 'graphql';
-// wraps all resolve functions of query, mutation or subscription fields
-// with the provided function to simulate a root schema level resolve funciton
-function addSchemaLevelResolveFunction(
+// wraps all resolvers of query, mutation or subscription fields
+// with the provided function to simulate a root schema level resolver
+function addSchemaLevelResolver(
schema: GraphQLSchema,
fn: GraphQLFieldResolver,
): void {
@@ -15,24 +15,29 @@ function addSchemaLevelResolveFunction(
schema.getQueryType(),
schema.getMutationType(),
schema.getSubscriptionType(),
- ].filter(x => !!x);
- rootTypes.forEach(type => {
- // XXX this should run at most once per request to simulate a true root resolver
- // for graphql-js this is an approximation that works with queries but not mutations
- const rootResolveFn = runAtMostOncePerRequest(fn);
- const fields = type.getFields();
- Object.keys(fields).forEach(fieldName => {
- // XXX if the type is a subscription, a same query AST will be ran multiple times so we
- // deactivate here the runOnce if it's a subscription. This may not be optimal though...
- if (type === schema.getSubscriptionType()) {
- fields[fieldName].resolve = wrapResolver(fields[fieldName].resolve, fn);
- } else {
- fields[fieldName].resolve = wrapResolver(
- fields[fieldName].resolve,
- rootResolveFn,
- );
- }
- });
+ ].filter((x) => Boolean(x));
+ rootTypes.forEach((type) => {
+ if (type != null) {
+ // XXX this should run at most once per request to simulate a true root resolver
+ // for graphql-js this is an approximation that works with queries but not mutations
+ const rootResolveFn = runAtMostOncePerRequest(fn);
+ const fields = type.getFields();
+ Object.keys(fields).forEach((fieldName) => {
+ // XXX if the type is a subscription, a same query AST will be ran multiple times so we
+ // deactivate here the runOnce if it's a subscription. This may not be optimal though...
+ if (type === schema.getSubscriptionType()) {
+ fields[fieldName].resolve = wrapResolver(
+ fields[fieldName].resolve,
+ fn,
+ );
+ } else {
+ fields[fieldName].resolve = wrapResolver(
+ fields[fieldName].resolve,
+ rootResolveFn,
+ );
+ }
+ });
+ }
});
}
@@ -41,14 +46,13 @@ function wrapResolver(
innerResolver: GraphQLFieldResolver | undefined,
outerResolver: GraphQLFieldResolver,
): GraphQLFieldResolver {
- return (obj, args, ctx, info) => {
- return Promise.resolve(outerResolver(obj, args, ctx, info)).then(root => {
- if (innerResolver) {
+ return (obj, args, ctx, info) =>
+ Promise.resolve(outerResolver(obj, args, ctx, info)).then((root) => {
+ if (innerResolver != null) {
return innerResolver(root, args, ctx, info);
}
return defaultFieldResolver(root, args, ctx, info);
});
- };
}
// XXX this function only works for resolvers
@@ -74,4 +78,4 @@ function runAtMostOncePerRequest(
};
}
-export default addSchemaLevelResolveFunction;
+export default addSchemaLevelResolver;
diff --git a/src/generate/assertResolveFunctionsPresent.ts b/src/generate/assertResolversPresent.ts
similarity index 61%
rename from src/generate/assertResolveFunctionsPresent.ts
rename to src/generate/assertResolversPresent.ts
index 013a2187790..3d579260a9d 100644
--- a/src/generate/assertResolveFunctionsPresent.ts
+++ b/src/generate/assertResolversPresent.ts
@@ -2,16 +2,18 @@ import {
GraphQLSchema,
GraphQLField,
getNamedType,
- GraphQLScalarType,
+ isScalarType,
} from 'graphql';
+
import { IResolverValidationOptions } from '../Interfaces';
+import { forEachField } from '../utils/index';
-import { forEachField, SchemaError } from '.';
+import SchemaError from './SchemaError';
-function assertResolveFunctionsPresent(
+function assertResolversPresent(
schema: GraphQLSchema,
resolverValidationOptions: IResolverValidationOptions = {},
-) {
+): void {
const {
requireResolversForArgs = false,
requireResolversForNonScalar = false,
@@ -30,35 +32,35 @@ function assertResolveFunctionsPresent(
}
forEachField(schema, (field, typeName, fieldName) => {
- // requires a resolve function for *every* field.
+ // requires a resolver for *every* field.
if (requireResolversForAllFields) {
- expectResolveFunction(field, typeName, fieldName);
+ expectResolver(field, typeName, fieldName);
}
- // requires a resolve function on every field that has arguments
+ // requires a resolver on every field that has arguments
if (requireResolversForArgs && field.args.length > 0) {
- expectResolveFunction(field, typeName, fieldName);
+ expectResolver(field, typeName, fieldName);
}
- // requires a resolve function on every field that returns a non-scalar type
+ // requires a resolver on every field that returns a non-scalar type
if (
requireResolversForNonScalar &&
- !(getNamedType(field.type) instanceof GraphQLScalarType)
+ !isScalarType(getNamedType(field.type))
) {
- expectResolveFunction(field, typeName, fieldName);
+ expectResolver(field, typeName, fieldName);
}
});
}
-function expectResolveFunction(
+function expectResolver(
field: GraphQLField,
typeName: string,
fieldName: string,
) {
if (!field.resolve) {
+ // eslint-disable-next-line no-console
console.warn(
- // tslint:disable-next-line: max-line-length
- `Resolve function missing for "${typeName}.${fieldName}". To disable this warning check https://github.com/apollostack/graphql-tools/issues/131`,
+ `Resolver missing for "${typeName}.${fieldName}". To disable this warning check https://github.com/apollostack/graphql-tools/issues/131`,
);
return;
}
@@ -69,4 +71,4 @@ function expectResolveFunction(
}
}
-export default assertResolveFunctionsPresent;
+export default assertResolversPresent;
diff --git a/src/generate/attachConnectorsToContext.ts b/src/generate/attachConnectorsToContext.ts
index a6274cff3c6..668f1b6c8e8 100644
--- a/src/generate/attachConnectorsToContext.ts
+++ b/src/generate/attachConnectorsToContext.ts
@@ -1,10 +1,9 @@
-import { GraphQLSchema, GraphQLFieldResolver } from 'graphql';
-
import { deprecated } from 'deprecated-decorator';
+import { GraphQLSchema, GraphQLFieldResolver, isSchema } from 'graphql';
import { IConnectors, IConnector, IConnectorCls } from '../Interfaces';
-import { addSchemaLevelResolveFunction } from '.';
+import addSchemaLevelResolver from './addSchemaLevelResolver';
// takes a GraphQL-JS schema and an object of connectors, then attaches
// the connectors to the context by wrapping each query or mutation resolve
@@ -15,8 +14,8 @@ const attachConnectorsToContext = deprecated(
version: '0.7.0',
url: 'https://github.com/apollostack/graphql-tools/issues/140',
},
- function(schema: GraphQLSchema, connectors: IConnectors): void {
- if (!schema || !(schema instanceof GraphQLSchema)) {
+ (schema: GraphQLSchema, connectors: IConnectors): void => {
+ if (!schema || !isSchema(schema)) {
throw new Error(
'schema must be an instance of GraphQLSchema. ' +
'This error could be caused by installing more than one version of GraphQL-JS',
@@ -42,9 +41,9 @@ const attachConnectorsToContext = deprecated(
}
schema['_apolloConnectorsAttached'] = true;
const attachconnectorFn: GraphQLFieldResolver = (
- root: any,
- args: { [key: string]: any },
- ctx: any,
+ root,
+ _args,
+ ctx,
) => {
if (typeof ctx !== 'object') {
// if in any way possible, we should throw an error when the attachconnectors
@@ -57,17 +56,17 @@ const attachConnectorsToContext = deprecated(
if (typeof ctx.connectors === 'undefined') {
ctx.connectors = {};
}
- Object.keys(connectors).forEach(connectorName => {
- let connector: IConnector = connectors[connectorName];
- if (!!connector.prototype) {
- ctx.connectors[connectorName] = new (connector)(ctx);
+ Object.keys(connectors).forEach((connectorName) => {
+ const connector: IConnector = connectors[connectorName];
+ if (connector.prototype != null) {
+ ctx.connectors[connectorName] = new (connector as IConnectorCls)(ctx);
} else {
- throw new Error(`Connector must be a function or an class`);
+ throw new Error('Connector must be a function or an class');
}
});
return root;
};
- addSchemaLevelResolveFunction(schema, attachconnectorFn);
+ addSchemaLevelResolver(schema, attachconnectorFn);
},
);
diff --git a/src/generate/attachDirectiveResolvers.ts b/src/generate/attachDirectiveResolvers.ts
index 3b6a3a50bad..fbe6ae73c15 100644
--- a/src/generate/attachDirectiveResolvers.ts
+++ b/src/generate/attachDirectiveResolvers.ts
@@ -1,10 +1,11 @@
import { GraphQLSchema, GraphQLField, defaultFieldResolver } from 'graphql';
+
import { IDirectiveResolvers } from '../Interfaces';
-import { SchemaDirectiveVisitor } from '../schemaVisitor';
+import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor';
function attachDirectiveResolvers(
schema: GraphQLSchema,
- directiveResolvers: IDirectiveResolvers,
+ directiveResolvers: IDirectiveResolvers,
) {
if (typeof directiveResolvers !== 'object') {
throw new Error(
@@ -20,16 +21,24 @@ function attachDirectiveResolvers(
const schemaDirectives = Object.create(null);
- Object.keys(directiveResolvers).forEach(directiveName => {
+ Object.keys(directiveResolvers).forEach((directiveName) => {
schemaDirectives[directiveName] = class extends SchemaDirectiveVisitor {
public visitFieldDefinition(field: GraphQLField) {
const resolver = directiveResolvers[directiveName];
- const originalResolver = field.resolve || defaultFieldResolver;
+ const originalResolver =
+ field.resolve != null ? field.resolve : defaultFieldResolver;
const directiveArgs = this.args;
- field.resolve = (...args: any[]) => {
+ field.resolve = (...args) => {
const [source /* original args */, , context, info] = args;
return resolver(
- async () => originalResolver.apply(field, args),
+ () =>
+ new Promise((resolve, reject) => {
+ const result = originalResolver.apply(field, args);
+ if (result instanceof Error) {
+ reject(result);
+ }
+ resolve(result);
+ }),
source,
directiveArgs,
context,
diff --git a/src/generate/buildSchemaFromTypeDefinitions.ts b/src/generate/buildSchemaFromTypeDefinitions.ts
index a122a993365..4493a002f49 100644
--- a/src/generate/buildSchemaFromTypeDefinitions.ts
+++ b/src/generate/buildSchemaFromTypeDefinitions.ts
@@ -4,15 +4,17 @@ import {
buildASTSchema,
GraphQLSchema,
DocumentNode,
+ ASTNode,
} from 'graphql';
+
import { ITypeDefinitions, GraphQLParseOptions } from '../Interfaces';
import {
extractExtensionDefinitions,
- concatenateTypeDefs,
- SchemaError,
-} from '.';
-import filterExtensionDefinitions from './filterExtensionDefinitions';
+ filterExtensionDefinitions,
+} from './extensionDefinitions';
+import concatenateTypeDefs from './concatenateTypeDefs';
+import SchemaError from './SchemaError';
function buildSchemaFromTypeDefinitions(
typeDefinitions: ITypeDefinitions,
@@ -38,19 +40,14 @@ function buildSchemaFromTypeDefinitions(
astDocument = parse(myDefinitions, parseOptions);
}
- const backcompatOptions = { commentDescriptions: true };
const typesAst = filterExtensionDefinitions(astDocument);
- // TODO fix types https://github.com/apollographql/graphql-tools/issues/542
- let schema: GraphQLSchema = (buildASTSchema as any)(
- typesAst,
- backcompatOptions,
- );
+ const backcompatOptions = { commentDescriptions: true };
+ let schema: GraphQLSchema = buildASTSchema(typesAst, backcompatOptions);
const extensionsAst = extractExtensionDefinitions(astDocument);
if (extensionsAst.definitions.length > 0) {
- // TODO fix types https://github.com/apollographql/graphql-tools/issues/542
- schema = (extendSchema as any)(schema, extensionsAst, backcompatOptions);
+ schema = extendSchema(schema, extensionsAst, backcompatOptions);
}
return schema;
@@ -59,7 +56,7 @@ function buildSchemaFromTypeDefinitions(
function isDocumentNode(
typeDefinitions: ITypeDefinitions,
): typeDefinitions is DocumentNode {
- return (typeDefinitions).kind !== undefined;
+ return (typeDefinitions as ASTNode).kind !== undefined;
}
export default buildSchemaFromTypeDefinitions;
diff --git a/src/generate/chainResolvers.ts b/src/generate/chainResolvers.ts
index 88d8d44def6..dd5cdfc30c2 100644
--- a/src/generate/chainResolvers.ts
+++ b/src/generate/chainResolvers.ts
@@ -1,13 +1,23 @@
-import { defaultFieldResolver, GraphQLResolveInfo, GraphQLFieldResolver } from 'graphql';
+import {
+ defaultFieldResolver,
+ GraphQLResolveInfo,
+ GraphQLFieldResolver,
+} from 'graphql';
-export function chainResolvers(resolvers: GraphQLFieldResolver[]) {
- return (root: any, args: { [argName: string]: any }, ctx: any, info: GraphQLResolveInfo) => {
- return resolvers.reduce((prev, curResolver) => {
- if (curResolver) {
+export function chainResolvers(
+ resolvers: Array>,
+) {
+ return (
+ root: any,
+ args: { [argName: string]: any },
+ ctx: any,
+ info: GraphQLResolveInfo,
+ ) =>
+ resolvers.reduce((prev, curResolver) => {
+ if (curResolver != null) {
return curResolver(prev, args, ctx, info);
}
return defaultFieldResolver(prev, args, ctx, info);
}, root);
- };
}
diff --git a/src/generate/checkForResolveTypeResolver.ts b/src/generate/checkForResolveTypeResolver.ts
index 139cbde349d..cf6f15758b3 100644
--- a/src/generate/checkForResolveTypeResolver.ts
+++ b/src/generate/checkForResolveTypeResolver.ts
@@ -1,6 +1,11 @@
-import { GraphQLInterfaceType, GraphQLUnionType, GraphQLSchema } from 'graphql';
+import {
+ GraphQLInterfaceType,
+ GraphQLUnionType,
+ GraphQLSchema,
+ isAbstractType,
+} from 'graphql';
-import { SchemaError } from '.';
+import SchemaError from './SchemaError';
// If we have any union or interface types throw if no there is no resolveType or isTypeOf resolvers
function checkForResolveTypeResolver(
@@ -8,31 +13,18 @@ function checkForResolveTypeResolver(
requireResolversForResolveType?: boolean,
) {
Object.keys(schema.getTypeMap())
- .map(typeName => schema.getType(typeName))
+ .map((typeName) => schema.getType(typeName))
.forEach((type: GraphQLUnionType | GraphQLInterfaceType) => {
- if (
- !(
- type instanceof GraphQLUnionType ||
- type instanceof GraphQLInterfaceType
- )
- ) {
+ if (!isAbstractType(type)) {
return;
}
if (!type.resolveType) {
- if (requireResolversForResolveType === false) {
+ if (!requireResolversForResolveType) {
return;
}
- if (requireResolversForResolveType === true) {
- throw new SchemaError(
- `Type "${type.name}" is missing a "resolveType" resolver`,
- );
- }
- // tslint:disable-next-line:max-line-length
- console.warn(
- `Type "${
- type.name
- }" is missing a "__resolveType" resolver. Pass false into ` +
- `"resolverValidationOptions.requireResolversForResolveType" to disable this warning.`,
+ throw new SchemaError(
+ `Type "${type.name}" is missing a "__resolveType" resolver. Pass false into ` +
+ '"resolverValidationOptions.requireResolversForResolveType" to disable this error.',
);
}
});
diff --git a/src/generate/concatenateTypeDefs.ts b/src/generate/concatenateTypeDefs.ts
index c72784fc976..31863b5f738 100644
--- a/src/generate/concatenateTypeDefs.ts
+++ b/src/generate/concatenateTypeDefs.ts
@@ -1,18 +1,15 @@
-import { print, DocumentNode, ASTNode } from 'graphql';
+import { print, ASTNode } from 'graphql';
+
import { ITypedef } from '../Interfaces';
-import { SchemaError } from '.';
+import SchemaError from './SchemaError';
function concatenateTypeDefs(
- typeDefinitionsAry: ITypedef[],
+ typeDefinitionsAry: Array,
calledFunctionRefs = [] as any,
): string {
- let resolvedTypeDefinitions: string[] = [];
+ let resolvedTypeDefinitions: Array = [];
typeDefinitionsAry.forEach((typeDef: ITypedef) => {
- if ((typeDef).kind !== undefined) {
- typeDef = print(typeDef as ASTNode);
- }
-
if (typeof typeDef === 'function') {
if (calledFunctionRefs.indexOf(typeDef) === -1) {
calledFunctionRefs.push(typeDef);
@@ -22,6 +19,8 @@ function concatenateTypeDefs(
}
} else if (typeof typeDef === 'string') {
resolvedTypeDefinitions.push(typeDef.trim());
+ } else if ((typeDef as ASTNode).kind !== undefined) {
+ resolvedTypeDefinitions.push(print(typeDef).trim());
} else {
const type = typeof typeDef;
throw new SchemaError(
@@ -29,15 +28,17 @@ function concatenateTypeDefs(
);
}
});
- return uniq(resolvedTypeDefinitions.map(x => x.trim())).join('\n');
+ return uniq(resolvedTypeDefinitions.map((x) => x.trim())).join('\n');
}
function uniq(array: Array): Array {
- return array.reduce((accumulator, currentValue) => {
- return accumulator.indexOf(currentValue) === -1
- ? [...accumulator, currentValue]
- : accumulator;
- }, []);
+ return array.reduce(
+ (accumulator, currentValue) =>
+ accumulator.indexOf(currentValue) === -1
+ ? [...accumulator, currentValue]
+ : accumulator,
+ [],
+ );
}
export default concatenateTypeDefs;
diff --git a/src/generate/decorateWithLogger.ts b/src/generate/decorateWithLogger.ts
index eedf45264fe..7f48e0f8f34 100644
--- a/src/generate/decorateWithLogger.ts
+++ b/src/generate/decorateWithLogger.ts
@@ -1,4 +1,5 @@
import { defaultFieldResolver, GraphQLFieldResolver } from 'graphql';
+
import { ILogger } from '../Interfaces';
/*
@@ -7,13 +8,11 @@ import { ILogger } from '../Interfaces';
* hint: an optional hint to add to the error's message
*/
function decorateWithLogger(
- fn: GraphQLFieldResolver | undefined,
+ fn: GraphQLFieldResolver,
logger: ILogger,
hint: string,
): GraphQLFieldResolver {
- if (typeof fn === 'undefined') {
- fn = defaultFieldResolver;
- }
+ const resolver = fn != null ? fn : defaultFieldResolver;
const logError = (e: Error) => {
// TODO: clone the error properly
@@ -29,8 +28,8 @@ function decorateWithLogger(
return (root, args, ctx, info) => {
try {
- const result = fn(root, args, ctx, info);
- // If the resolve function returns a Promise log any Promise rejects.
+ const result = resolver(root, args, ctx, info);
+ // If the resolver returns a Promise log any Promise rejects.
if (
result &&
typeof result.then === 'function' &&
diff --git a/src/generate/extendResolversFromInterfaces.ts b/src/generate/extendResolversFromInterfaces.ts
index aee6d0efb5c..20665eb4318 100644
--- a/src/generate/extendResolversFromInterfaces.ts
+++ b/src/generate/extendResolversFromInterfaces.ts
@@ -1,6 +1,12 @@
-import { GraphQLObjectType, GraphQLSchema } from 'graphql';
+import {
+ GraphQLObjectType,
+ GraphQLSchema,
+ isObjectType,
+ isInterfaceType,
+} from 'graphql';
import { IResolvers } from '../Interfaces';
+import { graphqlVersion } from '../utils/index';
function extendResolversFromInterfaces(
schema: GraphQLSchema,
@@ -12,22 +18,23 @@ function extendResolversFromInterfaces(
});
const extendedResolvers: IResolvers = {};
- typeNames.forEach(typeName => {
+ typeNames.forEach((typeName) => {
const typeResolvers = resolvers[typeName];
const type = schema.getType(typeName);
- if (type instanceof GraphQLObjectType) {
- const interfaceResolvers = type
+ if (
+ isObjectType(type) ||
+ (graphqlVersion() >= 15 && isInterfaceType(type))
+ ) {
+ const interfaceResolvers = (type as GraphQLObjectType)
.getInterfaces()
- .map(iFace => resolvers[iFace.name]);
+ .map((iFace) => resolvers[iFace.name]);
extendedResolvers[typeName] = Object.assign(
{},
...interfaceResolvers,
typeResolvers,
);
- } else {
- if (typeResolvers) {
- extendedResolvers[typeName] = typeResolvers;
- }
+ } else if (typeResolvers != null) {
+ extendedResolvers[typeName] = typeResolvers;
}
});
diff --git a/src/generate/extensionDefinitions.ts b/src/generate/extensionDefinitions.ts
new file mode 100644
index 00000000000..5adce4a7288
--- /dev/null
+++ b/src/generate/extensionDefinitions.ts
@@ -0,0 +1,39 @@
+import { DocumentNode, DefinitionNode, Kind } from 'graphql';
+
+import { graphqlVersion } from '../utils/index';
+
+export function extractExtensionDefinitions(ast: DocumentNode) {
+ const extensionDefs = ast.definitions.filter(
+ (def: DefinitionNode) =>
+ def.kind === Kind.OBJECT_TYPE_EXTENSION ||
+ (graphqlVersion() >= 13 && def.kind === Kind.INTERFACE_TYPE_EXTENSION) ||
+ def.kind === Kind.INPUT_OBJECT_TYPE_EXTENSION ||
+ def.kind === Kind.UNION_TYPE_EXTENSION ||
+ def.kind === Kind.ENUM_TYPE_EXTENSION ||
+ def.kind === Kind.SCALAR_TYPE_EXTENSION ||
+ def.kind === Kind.SCHEMA_EXTENSION,
+ );
+
+ return {
+ ...ast,
+ definitions: extensionDefs,
+ };
+}
+
+export function filterExtensionDefinitions(ast: DocumentNode) {
+ const extensionDefs = ast.definitions.filter(
+ (def: DefinitionNode) =>
+ def.kind !== Kind.OBJECT_TYPE_EXTENSION &&
+ def.kind !== Kind.INTERFACE_TYPE_EXTENSION &&
+ def.kind !== Kind.INPUT_OBJECT_TYPE_EXTENSION &&
+ def.kind !== Kind.UNION_TYPE_EXTENSION &&
+ def.kind !== Kind.ENUM_TYPE_EXTENSION &&
+ def.kind !== Kind.SCALAR_TYPE_EXTENSION &&
+ def.kind !== Kind.SCHEMA_EXTENSION,
+ );
+
+ return {
+ ...ast,
+ definitions: extensionDefs,
+ };
+}
diff --git a/src/generate/extractExtensionDefinitions.ts b/src/generate/extractExtensionDefinitions.ts
deleted file mode 100644
index 69b8d072a5d..00000000000
--- a/src/generate/extractExtensionDefinitions.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { DocumentNode, DefinitionNode } from 'graphql';
-
-const newExtensionDefinitionKind = 'ObjectTypeExtension';
-const interfaceExtensionDefinitionKind = 'InterfaceTypeExtension';
-const inputObjectExtensionDefinitionKind = 'InputObjectTypeExtension';
-const unionExtensionDefinitionKind = 'UnionTypeExtension';
-const enumExtensionDefinitionKind = 'EnumTypeExtension';
-
-export default function extractExtensionDefinitions(ast: DocumentNode) {
- const extensionDefs = ast.definitions.filter(
- (def: DefinitionNode) =>
- (def.kind as any) === newExtensionDefinitionKind ||
- (def.kind as any) === interfaceExtensionDefinitionKind ||
- (def.kind as any) === inputObjectExtensionDefinitionKind ||
- (def.kind as any) === unionExtensionDefinitionKind ||
- (def.kind as any) === enumExtensionDefinitionKind,
- );
-
- return Object.assign({}, ast, {
- definitions: extensionDefs,
- });
-}
diff --git a/src/generate/filterExtensionDefinitions.ts b/src/generate/filterExtensionDefinitions.ts
deleted file mode 100644
index e53a43a0640..00000000000
--- a/src/generate/filterExtensionDefinitions.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { DocumentNode, DefinitionNode, Kind } from 'graphql';
-
-export default function filterExtensionDefinitions(ast: DocumentNode) {
- const extensionDefs = ast.definitions.filter(
- (def: DefinitionNode) =>
- def.kind !== Kind.OBJECT_TYPE_EXTENSION &&
- def.kind !== Kind.INTERFACE_TYPE_EXTENSION &&
- def.kind !== Kind.INPUT_OBJECT_TYPE_EXTENSION &&
- def.kind !== Kind.UNION_TYPE_EXTENSION &&
- def.kind !== Kind.ENUM_TYPE_EXTENSION &&
- def.kind !== Kind.SCALAR_TYPE_EXTENSION &&
- def.kind !== Kind.SCHEMA_EXTENSION,
- );
-
- return {
- ...ast,
- definitions: extensionDefs,
- };
-}
diff --git a/src/generate/forEachField.ts b/src/generate/forEachField.ts
deleted file mode 100644
index f48da53a4b7..00000000000
--- a/src/generate/forEachField.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { getNamedType, GraphQLObjectType, GraphQLSchema } from 'graphql';
-import { IFieldIteratorFn } from '../Interfaces';
-
-function forEachField(schema: GraphQLSchema, fn: IFieldIteratorFn): void {
- const typeMap = schema.getTypeMap();
- Object.keys(typeMap).forEach(typeName => {
- const type = typeMap[typeName];
-
- // TODO: maybe have an option to include these?
- if (
- !getNamedType(type).name.startsWith('__') &&
- type instanceof GraphQLObjectType
- ) {
- const fields = type.getFields();
- Object.keys(fields).forEach(fieldName => {
- const field = fields[fieldName];
- fn(field, typeName, fieldName);
- });
- }
- });
-}
-
-export default forEachField;
diff --git a/src/generate/index.ts b/src/generate/index.ts
index 1cabbca287d..e32f284566b 100644
--- a/src/generate/index.ts
+++ b/src/generate/index.ts
@@ -1,6 +1,16 @@
-export { default as addResolveFunctionsToSchema } from './addResolveFunctionsToSchema';
-export { default as addSchemaLevelResolveFunction } from './addSchemaLevelResolveFunction';
-export { default as assertResolveFunctionsPresent } from './assertResolveFunctionsPresent';
+import { GraphQLSchema, GraphQLFieldResolver } from 'graphql';
+
+import {
+ IAddResolversToSchemaOptions,
+ IResolvers,
+ IResolverValidationOptions,
+} from '../Interfaces';
+
+import addResolversToSchema from './addResolversToSchema';
+import addSchemaLevelResolver from './addSchemaLevelResolver';
+import assertResolversPresent from './assertResolversPresent';
+
+export { addResolversToSchema, addSchemaLevelResolver, assertResolversPresent };
export { default as attachDirectiveResolvers } from './attachDirectiveResolvers';
export { default as attachConnectorsToContext } from './attachConnectorsToContext';
export { default as buildSchemaFromTypeDefinitions } from './buildSchemaFromTypeDefinitions';
@@ -9,6 +19,38 @@ export { default as checkForResolveTypeResolver } from './checkForResolveTypeRes
export { default as concatenateTypeDefs } from './concatenateTypeDefs';
export { default as decorateWithLogger } from './decorateWithLogger';
export { default as extendResolversFromInterfaces } from './extendResolversFromInterfaces';
-export { default as extractExtensionDefinitions } from './extractExtensionDefinitions';
-export { default as forEachField } from './forEachField';
+export {
+ extractExtensionDefinitions,
+ filterExtensionDefinitions,
+} from './extensionDefinitions';
export { default as SchemaError } from './SchemaError';
+export * from './makeExecutableSchema';
+
+// These functions are preserved for backwards compatibility.
+// They are not simply rexported with new (old) names so as to allow
+// typedoc to annotate them.
+export function addResolveFunctionsToSchema(
+ schemaOrOptions: GraphQLSchema | IAddResolversToSchemaOptions,
+ legacyInputResolvers?: IResolvers,
+ legacyInputValidationOptions?: IResolverValidationOptions,
+): GraphQLSchema {
+ return addResolversToSchema(
+ schemaOrOptions,
+ legacyInputResolvers,
+ legacyInputValidationOptions,
+ );
+}
+
+export function addSchemaLevelResolveFunction(
+ schema: GraphQLSchema,
+ fn: GraphQLFieldResolver,
+): void {
+ addSchemaLevelResolver(schema, fn);
+}
+
+export function assertResolveFunctionsPresent(
+ schema: GraphQLSchema,
+ resolverValidationOptions: IResolverValidationOptions = {},
+): void {
+ assertResolversPresent(schema, resolverValidationOptions);
+}
diff --git a/src/makeExecutableSchema.ts b/src/generate/makeExecutableSchema.ts
similarity index 56%
rename from src/makeExecutableSchema.ts
rename to src/generate/makeExecutableSchema.ts
index ab9f3debed3..e8188e37795 100644
--- a/src/makeExecutableSchema.ts
+++ b/src/generate/makeExecutableSchema.ts
@@ -1,21 +1,24 @@
-import { defaultFieldResolver, GraphQLSchema, GraphQLFieldResolver } from 'graphql';
-
-import { IExecutableSchemaDefinition, ILogger } from './Interfaces';
-
-import { SchemaDirectiveVisitor } from './schemaVisitor';
-import mergeDeep from './mergeDeep';
+import {
+ defaultFieldResolver,
+ GraphQLSchema,
+ GraphQLFieldResolver,
+} from 'graphql';
+import { IExecutableSchemaDefinition, ILogger } from '../Interfaces';
import {
- attachDirectiveResolvers,
- assertResolveFunctionsPresent,
- addResolveFunctionsToSchema,
- attachConnectorsToContext,
- addSchemaLevelResolveFunction,
- buildSchemaFromTypeDefinitions,
- decorateWithLogger,
+ SchemaDirectiveVisitor,
forEachField,
- SchemaError
-} from './generate';
+ mergeDeep,
+} from '../utils/index';
+
+import attachDirectiveResolvers from './attachDirectiveResolvers';
+import assertResolversPresent from './assertResolversPresent';
+import addResolversToSchema from './addResolversToSchema';
+import attachConnectorsToContext from './attachConnectorsToContext';
+import addSchemaLevelResolver from './addSchemaLevelResolver';
+import buildSchemaFromTypeDefinitions from './buildSchemaFromTypeDefinitions';
+import decorateWithLogger from './decorateWithLogger';
+import SchemaError from './SchemaError';
export function makeExecutableSchema({
typeDefs,
@@ -24,67 +27,70 @@ export function makeExecutableSchema({
logger,
allowUndefinedInResolve = true,
resolverValidationOptions = {},
- directiveResolvers = null,
- schemaDirectives = null,
+ directiveResolvers,
+ schemaDirectives,
parseOptions = {},
- inheritResolversFromInterfaces = false
+ inheritResolversFromInterfaces = false,
}: IExecutableSchemaDefinition) {
// Validate and clean up arguments
if (typeof resolverValidationOptions !== 'object') {
- throw new SchemaError('Expected `resolverValidationOptions` to be an object');
+ throw new SchemaError(
+ 'Expected `resolverValidationOptions` to be an object',
+ );
}
if (!typeDefs) {
throw new SchemaError('Must provide typeDefs');
}
- if (!resolvers) {
- throw new SchemaError('Must provide resolvers');
- }
-
// We allow passing in an array of resolver maps, in which case we merge them
const resolverMap = Array.isArray(resolvers)
- ? resolvers.filter(resolverObj => typeof resolverObj === 'object').reduce(mergeDeep, {})
+ ? resolvers
+ .filter((resolverObj) => typeof resolverObj === 'object')
+ .reduce(mergeDeep, {})
: resolvers;
// Arguments are now validated and cleaned up
- let schema = buildSchemaFromTypeDefinitions(typeDefs, parseOptions);
+ const schema = buildSchemaFromTypeDefinitions(typeDefs, parseOptions);
- schema = addResolveFunctionsToSchema({
+ addResolversToSchema({
schema,
resolvers: resolverMap,
resolverValidationOptions,
- inheritResolversFromInterfaces
+ inheritResolversFromInterfaces,
});
- assertResolveFunctionsPresent(schema, resolverValidationOptions);
+ assertResolversPresent(schema, resolverValidationOptions);
if (!allowUndefinedInResolve) {
addCatchUndefinedToSchema(schema);
}
- if (logger) {
+ if (logger != null) {
addErrorLoggingToSchema(schema, logger);
}
if (typeof resolvers['__schema'] === 'function') {
// TODO a bit of a hack now, better rewrite generateSchema to attach it there.
// not doing that now, because I'd have to rewrite a lot of tests.
- addSchemaLevelResolveFunction(schema, resolvers['__schema'] as GraphQLFieldResolver);
+ addSchemaLevelResolver(
+ schema,
+ resolvers['__schema'] as GraphQLFieldResolver,
+ );
}
- if (connectors) {
+ if (connectors != null) {
// connectors are optional, at least for now. That means you can just import them in the resolve
// function if you want.
attachConnectorsToContext(schema, connectors);
}
- if (directiveResolvers) {
+ if (directiveResolvers != null) {
attachDirectiveResolvers(schema, directiveResolvers);
}
- if (schemaDirectives) {
+ if (schemaDirectives != null) {
SchemaDirectiveVisitor.visitSchemaDirectives(schema, schemaDirectives);
}
@@ -93,15 +99,13 @@ export function makeExecutableSchema({
function decorateToCatchUndefined(
fn: GraphQLFieldResolver,
- hint: string
+ hint: string,
): GraphQLFieldResolver {
- if (typeof fn === 'undefined') {
- fn = defaultFieldResolver;
- }
+ const resolve = fn == null ? defaultFieldResolver : fn;
return (root, args, ctx, info) => {
- const result = fn(root, args, ctx, info);
+ const result = resolve(root, args, ctx, info);
if (typeof result === 'undefined') {
- throw new Error(`Resolve function for "${hint}" returned undefined`);
+ throw new Error(`Resolver for "${hint}" returned undefined`);
}
return result;
};
@@ -114,7 +118,10 @@ export function addCatchUndefinedToSchema(schema: GraphQLSchema): void {
});
}
-export function addErrorLoggingToSchema(schema: GraphQLSchema, logger: ILogger): void {
+export function addErrorLoggingToSchema(
+ schema: GraphQLSchema,
+ logger?: ILogger,
+): void {
if (!logger) {
throw new Error('Must provide a logger');
}
@@ -126,5 +133,3 @@ export function addErrorLoggingToSchema(schema: GraphQLSchema, logger: ILogger):
field.resolve = decorateWithLogger(field.resolve, logger, errorHint);
});
}
-
-export * from './generate';
diff --git a/src/index.ts b/src/index.ts
index a8bf617019c..55dd06de2c1 100755
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,10 @@
export * from './Interfaces';
-export * from './makeExecutableSchema';
+export * from './delegate';
+export * from './generate';
+export * from './links';
export * from './mock';
-export * from './stitching';
-export * from './transforms';
-export { SchemaDirectiveVisitor } from './schemaVisitor';
+export * from './polyfills';
+export * from './scalars';
+export * from './stitch';
+export * from './wrap';
+export * from './utils';
diff --git a/src/links/createServerHttpLink.ts b/src/links/createServerHttpLink.ts
new file mode 100644
index 00000000000..f1272a4758a
--- /dev/null
+++ b/src/links/createServerHttpLink.ts
@@ -0,0 +1,404 @@
+/* eslint-disable import/no-nodejs-modules */
+
+import { Readable } from 'stream';
+
+import { ApolloLink, Observable, RequestHandler, fromError } from 'apollo-link';
+import {
+ serializeFetchParameter,
+ selectURI,
+ parseAndCheckHttpResponse,
+ selectHttpOptionsAndBody,
+ createSignalIfSupported,
+ fallbackHttpConfig,
+ Body,
+ HttpOptions,
+ UriFunction,
+} from 'apollo-link-http-common';
+import { DefinitionNode } from 'graphql';
+import {
+ extractFiles,
+ isExtractableFile as defaultIsExtractableFile,
+} from 'extract-files';
+import KnownLengthFormData, { AppendOptions } from 'form-data';
+import fetch from 'node-fetch';
+
+const hasOwn = Object.prototype.hasOwnProperty;
+
+class FormData extends KnownLengthFormData {
+ private hasUnknowableLength: boolean;
+
+ constructor(options?: any) {
+ super(options);
+ this.hasUnknowableLength = false;
+ }
+
+ public append(
+ key: string,
+ value: any,
+ optionsOrFilename: AppendOptions | string = {},
+ ): void {
+ // allow filename as single option
+ const options: AppendOptions =
+ typeof optionsOrFilename === 'string'
+ ? { filename: optionsOrFilename }
+ : optionsOrFilename;
+
+ // empty or either doesn't have path or not an http response
+ if (
+ !options.knownLength &&
+ !Buffer.isBuffer(value) &&
+ typeof value !== 'string' &&
+ !value.path &&
+ !(value.readable && hasOwn.call(value, 'httpVersion'))
+ ) {
+ this.hasUnknowableLength = true;
+ }
+
+ super.append(key, value, options);
+ }
+
+ public getLength(
+ callback: (err: Error | null, length: number) => void,
+ ): void {
+ if (this.hasUnknowableLength) {
+ return null;
+ }
+
+ return super.getLength(callback);
+ }
+
+ public getLengthSync(): number {
+ if (this.hasUnknowableLength) {
+ return null;
+ }
+
+ // eslint-disable-next-line no-sync
+ return super.getLengthSync();
+ }
+}
+
+export type Function = UriFunction;
+export type Options = HttpOptions & {
+ /**
+ * If set to true, use the HTTP GET method for query operations. Mutations
+ * will still use the method specified in fetchOptions.method (which defaults
+ * to POST).
+ */
+ useGETForQueries?: boolean;
+ serializer?: (method: string) => any;
+ appendFile?: (form: FormData, index: string, file: File) => void;
+};
+// For backwards compatibility.
+export { HttpOptions as FetchOptions };
+
+interface File {
+ createReadStream?: () => Readable;
+ filename?: string;
+ mimetype?: string;
+ name?: string;
+}
+
+export const createServerHttpLink = (linkOptions: Options = {}) => {
+ const {
+ uri = '/graphql',
+ fetch: customFetch = (fetch as unknown) as WindowOrWorkerGlobalScope['fetch'],
+ serializer: customSerializer = defaultSerializer,
+ appendFile: customAppendFile = defaultAppendFile,
+ includeExtensions,
+ useGETForQueries,
+ ...requestOptions
+ } = linkOptions;
+
+ const linkConfig = {
+ http: { includeExtensions },
+ options: requestOptions.fetchOptions,
+ credentials: requestOptions.credentials,
+ headers: requestOptions.headers,
+ };
+
+ return new ApolloLink((operation) => {
+ let chosenURI = selectURI(operation, uri);
+
+ const context = operation.getContext();
+
+ // `apollographql-client-*` headers are automatically set if a
+ // `clientAwareness` object is found in the context. These headers are
+ // set first, followed by the rest of the headers pulled from
+ // `context.headers`. If desired, `apollographql-client-*` headers set by
+ // the `clientAwareness` object can be overridden by
+ // `apollographql-client-*` headers set in `context.headers`.
+ const clientAwarenessHeaders = {};
+ if (context.clientAwareness) {
+ const { name, version } = context.clientAwareness;
+ if (name) {
+ clientAwarenessHeaders['apollographql-client-name'] = name;
+ }
+ if (version) {
+ clientAwarenessHeaders['apollographql-client-version'] = version;
+ }
+ }
+
+ const contextHeaders = { ...clientAwarenessHeaders, ...context.headers };
+
+ const contextConfig = {
+ http: context.http,
+ options: context.fetchOptions,
+ credentials: context.credentials,
+ headers: contextHeaders,
+ };
+
+ // uses fallback, link, and then context to build options
+ const { options, body } = selectHttpOptionsAndBody(
+ operation,
+ fallbackHttpConfig,
+ linkConfig,
+ contextConfig,
+ );
+
+ let controller: AbortController;
+ if (!(options as any).signal) {
+ const { controller: _controller, signal } = createSignalIfSupported();
+ controller = _controller;
+ if ((controller as unknown) as boolean) {
+ (options as any).signal = signal;
+ }
+ }
+
+ // If requested, set method to GET if there are no mutations.
+ const definitionIsMutation = (d: DefinitionNode) =>
+ d.kind === 'OperationDefinition' && d.operation === 'mutation';
+
+ if (
+ useGETForQueries &&
+ !operation.query.definitions.some(definitionIsMutation)
+ ) {
+ options.method = 'GET';
+ }
+
+ if (options.method === 'GET') {
+ const { newURI, parseError } = rewriteURIForGET(chosenURI, body);
+ if (parseError) {
+ return fromError(parseError);
+ }
+ chosenURI = newURI;
+ }
+
+ return new Observable((observer) => {
+ getFinalPromise(body)
+ .then((resolvedBody) => {
+ if (options.method !== 'GET') {
+ options.body = customSerializer(resolvedBody, customAppendFile);
+ if (options.body instanceof FormData) {
+ // Automatically set by fetch when the body is a FormData instance.
+ delete options.headers['content-type'];
+ }
+ }
+ return options;
+ })
+ .then((newOptions) => customFetch(chosenURI, newOptions))
+ .then((response) => {
+ operation.setContext({ response });
+ return response;
+ })
+ .then(parseAndCheckHttpResponse(operation))
+ .then((result) => {
+ // we have data and can send it to back up the link chain
+ observer.next(result);
+ observer.complete();
+ return result;
+ })
+ .catch((err) => {
+ // fetch was cancelled so it's already been cleaned up in the unsubscribe
+ if (err.name === 'AbortError') {
+ return;
+ }
+ // if it is a network error, BUT there is graphql result info
+ // fire the next observer before calling error
+ // this gives apollo-client (and react-apollo) the `graphqlErrors` and `networErrors`
+ // to pass to UI
+ // this should only happen if we *also* have data as part of the response key per
+ // the spec
+ if (err.result && err.result.errors && err.result.data) {
+ // if we don't call next, the UI can only show networkError because AC didn't
+ // get any graphqlErrors
+ // this is graphql execution result info (i.e errors and possibly data)
+ // this is because there is no formal spec how errors should translate to
+ // http status codes. So an auth error (401) could have both data
+ // from a public field, errors from a private field, and a status of 401
+ // {
+ // user { // this will have errors
+ // firstName
+ // }
+ // products { // this is public so will have data
+ // cost
+ // }
+ // }
+ //
+ // the result of above *could* look like this:
+ // {
+ // data: { products: [{ cost: "$10" }] },
+ // errors: [{
+ // message: 'your session has timed out',
+ // path: []
+ // }]
+ // }
+ // status code of above would be a 401
+ // in the UI you want to show data where you can, errors as data where you can
+ // and use correct http status codes
+ observer.next(err.result);
+ }
+ observer.error(err);
+ });
+
+ return () => {
+ // XXX support canceling this request
+ // https://developers.google.com/web/updates/2017/09/abortable-fetch
+ if ((controller as unknown) as boolean) {
+ controller.abort();
+ }
+ };
+ });
+ });
+};
+
+// For GET operations, returns the given URI rewritten with parameters, or a
+// parse error.
+function rewriteURIForGET(chosenURI: string, body: Body) {
+ // Implement the standard HTTP GET serialization, plus 'extensions'. Note
+ // the extra level of JSON serialization!
+ const queryParams: Array = [];
+ const addQueryParam = (key: string, value: string) => {
+ queryParams.push(`${key}=${encodeURIComponent(value)}`);
+ };
+
+ if ('query' in body) {
+ addQueryParam('query', body.query);
+ }
+ if (body.operationName) {
+ addQueryParam('operationName', body.operationName);
+ }
+ if (body.variables != null) {
+ let serializedVariables;
+ try {
+ serializedVariables = serializeFetchParameter(
+ body.variables,
+ 'Variables map',
+ );
+ } catch (parseError) {
+ return { parseError };
+ }
+ addQueryParam('variables', serializedVariables);
+ }
+ if (body.extensions != null) {
+ let serializedExtensions;
+ try {
+ serializedExtensions = serializeFetchParameter(
+ body.extensions,
+ 'Extensions map',
+ );
+ } catch (parseError) {
+ return { parseError };
+ }
+ addQueryParam('extensions', serializedExtensions);
+ }
+
+ // Reconstruct the URI with added query params.
+ // XXX This assumes that the URI is well-formed and that it doesn't
+ // already contain any of these query params. We could instead use the
+ // URL API and take a polyfill (whatwg-url@6) for older browsers that
+ // don't support URLSearchParams. Note that some browsers (and
+ // versions of whatwg-url) support URL but not URLSearchParams!
+ let fragment = '';
+ let preFragment = chosenURI;
+ const fragmentStart = chosenURI.indexOf('#');
+ if (fragmentStart !== -1) {
+ fragment = chosenURI.substr(fragmentStart);
+ preFragment = chosenURI.substr(0, fragmentStart);
+ }
+ const queryParamsPrefix = preFragment.indexOf('?') === -1 ? '?' : '&';
+ const newURI =
+ preFragment + queryParamsPrefix + queryParams.join('&') + fragment;
+ return { newURI };
+}
+
+function getFinalPromise(object: any): Promise {
+ return Promise.resolve(object).then((resolvedObject) => {
+ if (resolvedObject == null) {
+ return resolvedObject;
+ }
+
+ if (Array.isArray(resolvedObject)) {
+ return Promise.all(resolvedObject.map((o) => getFinalPromise(o)));
+ } else if (typeof resolvedObject === 'object') {
+ const keys = Object.keys(resolvedObject);
+ return Promise.all(
+ keys.map((key) => getFinalPromise(resolvedObject[key])),
+ ).then((awaitedValues) => {
+ for (let i = 0; i < keys.length; i++) {
+ resolvedObject[keys[i]] = awaitedValues[i];
+ }
+ return resolvedObject;
+ });
+ }
+
+ return resolvedObject;
+ });
+}
+
+function defaultSerializer(
+ body: any,
+ appendFile: (form: FormData, index: string, file: File) => void,
+): any {
+ const { clone, files } = extractFiles(
+ body,
+ undefined,
+ (value: any) => defaultIsExtractableFile(value) || value?.createReadStream,
+ );
+
+ const payload = serializeFetchParameter(clone, 'Payload');
+
+ if (!files.size) {
+ return payload;
+ }
+
+ // GraphQL multipart request spec:
+ // https://github.com/jaydenseric/graphql-multipart-request-spec
+
+ const form = new FormData();
+
+ form.append('operations', payload);
+
+ const map = {};
+ let i = 0;
+
+ files.forEach((paths: Array) => {
+ map[++i] = paths;
+ });
+
+ form.append('map', JSON.stringify(map));
+
+ i = 0;
+ files.forEach((_paths: Array, file: File) => {
+ appendFile(form, (++i).toString(), file);
+ });
+
+ return form;
+}
+
+function defaultAppendFile(form: FormData, index: string, file: File) {
+ if (file.createReadStream != null) {
+ form.append(index, file.createReadStream(), {
+ filename: file.filename,
+ contentType: file.mimetype,
+ });
+ } else {
+ form.append(index, file, file.name);
+ }
+}
+
+export class ServerHttpLink extends ApolloLink {
+ public requester: RequestHandler;
+ constructor(opts?: HttpOptions) {
+ super(createServerHttpLink(opts).request);
+ }
+}
diff --git a/src/links/index.ts b/src/links/index.ts
new file mode 100644
index 00000000000..a34c83329b9
--- /dev/null
+++ b/src/links/index.ts
@@ -0,0 +1,3 @@
+import { createServerHttpLink } from './createServerHttpLink';
+
+export { createServerHttpLink };
diff --git a/src/mergeDeep.ts b/src/mergeDeep.ts
deleted file mode 100644
index 599ab656773..00000000000
--- a/src/mergeDeep.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-export default function mergeDeep(target: any, source: any): any {
- let output = Object.assign({}, target);
- if (isObject(target) && isObject(source)) {
- Object.keys(source).forEach(key => {
- if (isObject(source[key])) {
- if (!(key in target)) {
- Object.assign(output, { [key]: source[key] });
- } else {
- output[key] = mergeDeep(target[key], source[key]);
- }
- } else {
- Object.assign(output, { [key]: source[key] });
- }
- });
- }
- return output;
-}
-
-function isObject(item: any): boolean {
- return item && typeof item === 'object' && !Array.isArray(item);
-}
diff --git a/src/mock.ts b/src/mock/index.ts
similarity index 75%
rename from src/mock.ts
rename to src/mock/index.ts
index 9a53a2beb2b..e87e47a4c82 100644
--- a/src/mock.ts
+++ b/src/mock/index.ts
@@ -2,9 +2,6 @@ import {
graphql,
GraphQLSchema,
GraphQLObjectType,
- GraphQLEnumType,
- GraphQLUnionType,
- GraphQLInterfaceType,
GraphQLList,
GraphQLType,
GraphQLField,
@@ -13,14 +10,19 @@ import {
getNamedType,
GraphQLNamedType,
GraphQLFieldResolver,
- GraphQLNonNull,
GraphQLNullableType,
+ isSchema,
+ isObjectType,
+ isUnionType,
+ isInterfaceType,
+ isListType,
+ isEnumType,
+ isAbstractType,
} from 'graphql';
-import * as uuid from 'uuid';
-import {
- forEachField,
- buildSchemaFromTypeDefinitions,
-} from './makeExecutableSchema';
+import { v4 as uuid } from 'uuid';
+
+import { buildSchemaFromTypeDefinitions } from '../generate/index';
+import { forEachField } from '../utils/index';
import {
IMocks,
@@ -29,23 +31,25 @@ import {
IMockFn,
IMockTypeFn,
ITypeDefinitions,
-} from './Interfaces';
+} from '../Interfaces';
-// This function wraps addMockFunctionsToSchema for more convenience
+/**
+ * This function wraps addMocksToSchema for more convenience
+ */
function mockServer(
schema: GraphQLSchema | ITypeDefinitions,
mocks: IMocks,
preserveResolvers: boolean = false,
): IMockServer {
let mySchema: GraphQLSchema;
- if (!(schema instanceof GraphQLSchema)) {
+ if (!isSchema(schema)) {
// TODO: provide useful error messages here if this fails
mySchema = buildSchemaFromTypeDefinitions(schema);
} else {
mySchema = schema;
}
- addMockFunctionsToSchema({ schema: mySchema, mocks, preserveResolvers });
+ addMocksToSchema({ schema: mySchema, mocks, preserveResolvers });
return { query: (query, vars) => graphql(mySchema, query, {}, {}, vars) };
}
@@ -55,12 +59,12 @@ defaultMockMap.set('Int', () => Math.round(Math.random() * 200) - 100);
defaultMockMap.set('Float', () => Math.random() * 200 - 100);
defaultMockMap.set('String', () => 'Hello World');
defaultMockMap.set('Boolean', () => Math.random() > 0.5);
-defaultMockMap.set('ID', () => uuid.v4());
+defaultMockMap.set('ID', () => uuid());
// TODO allow providing a seed such that lengths of list could be deterministic
// this could be done by using casual to get a random list length if the casual
// object is global.
-function addMockFunctionsToSchema({
+function addMocksToSchema({
schema,
mocks = {},
preserveResolvers = false,
@@ -68,7 +72,7 @@ function addMockFunctionsToSchema({
if (!schema) {
throw new Error('Must provide schema to mock');
}
- if (!(schema instanceof GraphQLSchema)) {
+ if (!isSchema(schema)) {
throw new Error('Value at "schema" must be of type GraphQLSchema');
}
if (!isObject(mocks)) {
@@ -77,7 +81,7 @@ function addMockFunctionsToSchema({
// use Map internally, because that API is nicer.
const mockFunctionMap: Map = new Map();
- Object.keys(mocks).forEach(typeName => {
+ Object.keys(mocks).forEach((typeName) => {
mockFunctionMap.set(typeName, mocks[typeName]);
});
@@ -87,9 +91,9 @@ function addMockFunctionsToSchema({
}
});
- const mockType = function(
+ const mockType = function (
type: GraphQLType,
- typeName?: string,
+ _typeName?: string,
fieldName?: string,
): GraphQLFieldResolver {
// order of precendence for mocking:
@@ -109,7 +113,7 @@ function addMockFunctionsToSchema({
const fieldType = getNullableType(type) as GraphQLNullableType;
const namedFieldType = getNamedType(fieldType);
- if (root && typeof root[fieldName] !== 'undefined') {
+ if (fieldName && root && typeof root[fieldName] !== 'undefined') {
let result: any;
// if we're here, the field is already defined
@@ -132,53 +136,38 @@ function addMockFunctionsToSchema({
// Now we merge the result with the default mock for this type.
// This allows overriding defaults while writing very little code.
if (mockFunctionMap.has(namedFieldType.name)) {
+ const mock = mockFunctionMap.get(namedFieldType.name);
+
result = mergeMocks(
- mockFunctionMap
- .get(namedFieldType.name)
- .bind(null, root, args, context, info),
+ mock.bind(null, root, args, context, info),
result,
);
}
return result;
}
- if (
- fieldType instanceof GraphQLList ||
- fieldType instanceof GraphQLNonNull
- ) {
+ if (isListType(fieldType)) {
return [
mockType(fieldType.ofType)(root, args, context, info),
mockType(fieldType.ofType)(root, args, context, info),
];
}
- if (
- mockFunctionMap.has(fieldType.name) &&
- !(
- fieldType instanceof GraphQLUnionType ||
- fieldType instanceof GraphQLInterfaceType
- )
- ) {
+ if (mockFunctionMap.has(fieldType.name) && !isAbstractType(fieldType)) {
// the object passed doesn't have this field, so we apply the default mock
- return mockFunctionMap.get(fieldType.name)(root, args, context, info);
+ const mock = mockFunctionMap.get(fieldType.name);
+ return mock(root, args, context, info);
}
- if (fieldType instanceof GraphQLObjectType) {
+ if (isObjectType(fieldType)) {
// objects don't return actual data, we only need to mock scalars!
return {};
}
// if a mock function is provided for unionType or interfaceType, execute it to resolve the concrete type
// otherwise randomly pick a type from all implementation types
- if (
- fieldType instanceof GraphQLUnionType ||
- fieldType instanceof GraphQLInterfaceType
- ) {
+ if (isAbstractType(fieldType)) {
let implementationType;
if (mockFunctionMap.has(fieldType.name)) {
- const interfaceMockObj = mockFunctionMap.get(fieldType.name)(
- root,
- args,
- context,
- info,
- );
+ const mock = mockFunctionMap.get(fieldType.name);
+ const interfaceMockObj = mock(root, args, context, info);
if (!interfaceMockObj || !interfaceMockObj.__typename) {
return Error(`Please return a __typename in "${fieldType.name}"`);
}
@@ -187,18 +176,19 @@ function addMockFunctionsToSchema({
const possibleTypes = schema.getPossibleTypes(fieldType);
implementationType = getRandomElement(possibleTypes);
}
- return Object.assign(
- { __typename: implementationType },
- mockType(implementationType)(root, args, context, info),
- );
+ return {
+ __typename: implementationType,
+ ...mockType(implementationType)(root, args, context, info),
+ };
}
- if (fieldType instanceof GraphQLEnumType) {
+ if (isEnumType(fieldType)) {
return getRandomElement(fieldType.getValues()).value;
}
if (defaultMockMap.has(fieldType.name)) {
- return defaultMockMap.get(fieldType.name)(root, args, context, info);
+ const defaultMock = defaultMockMap.get(fieldType.name);
+ return defaultMock(root, args, context, info);
}
// if we get to here, we don't have a value, and we don't have a mock for this type,
@@ -212,34 +202,44 @@ function addMockFunctionsToSchema({
schema,
(field: GraphQLField, typeName: string, fieldName: string) => {
assignResolveType(field.type, preserveResolvers);
- let mockResolver: GraphQLFieldResolver;
+ let mockResolver: GraphQLFieldResolver = mockType(
+ field.type,
+ typeName,
+ fieldName,
+ );
// we have to handle the root mutation and root query types differently,
// because no resolver is called at the root.
- /* istanbul ignore next: Must provide schema DefinitionNode with query type or a type named Query. */
- const isOnQueryType: boolean = schema.getQueryType() && schema.getQueryType().name === typeName
- const isOnMutationType: boolean = schema.getMutationType() && schema.getMutationType().name === typeName
+ const queryType = schema.getQueryType();
+ const isOnQueryType = queryType != null && queryType.name === typeName;
+
+ const mutationType = schema.getMutationType();
+ const isOnMutationType =
+ mutationType != null && mutationType.name === typeName;
if (isOnQueryType || isOnMutationType) {
if (mockFunctionMap.has(typeName)) {
const rootMock = mockFunctionMap.get(typeName);
// XXX: BUG in here, need to provide proper signature for rootMock.
- if (typeof rootMock(undefined, {}, {}, {} as any)[fieldName] === 'function') {
+ if (
+ typeof rootMock(undefined, {}, {}, {} as any)[fieldName] ===
+ 'function'
+ ) {
mockResolver = (
root: any,
args: { [key: string]: any },
context: any,
info: GraphQLResolveInfo,
) => {
- const updatedRoot = root || {}; // TODO: should we clone instead?
+ const updatedRoot = root ?? {}; // TODO: should we clone instead?
updatedRoot[fieldName] = rootMock(root, args, context, info)[
fieldName
];
// XXX this is a bit of a hack to still use mockType, which
// lets you mock lists etc. as well
// otherwise we could just set field.resolve to rootMock()[fieldName]
- // it's like pretending there was a resolve function that ran before
- // the root resolve function.
+ // it's like pretending there was a resolver that ran before
+ // the root resolver.
return mockType(field.type, typeName, fieldName)(
updatedRoot,
args,
@@ -250,23 +250,20 @@ function addMockFunctionsToSchema({
}
}
}
- if (!mockResolver) {
- mockResolver = mockType(field.type, typeName, fieldName);
- }
if (!preserveResolvers || !field.resolve) {
field.resolve = mockResolver;
} else {
const oldResolver = field.resolve;
field.resolve = (
- rootObject?: any,
- args?: { [key: string]: any },
- context?: any,
- info?: GraphQLResolveInfo,
+ rootObject: any,
+ args: { [key: string]: any },
+ context: any,
+ info: GraphQLResolveInfo,
) =>
Promise.all([
mockResolver(rootObject, args, context, info),
oldResolver(rootObject, args, context, info),
- ]).then(values => {
+ ]).then((values) => {
const [mockedValue, resolvedValue] = values;
// In case we couldn't mock
@@ -307,26 +304,33 @@ function getRandomElement(ary: ReadonlyArray) {
return ary[sample];
}
-function mergeObjects(a: Object, b: Object) {
+function mergeObjects(a: Record, b: Record) {
return Object.assign(a, b);
}
-function copyOwnPropsIfNotPresent(target: Object, source: Object) {
- Object.getOwnPropertyNames(source).forEach(prop => {
+function copyOwnPropsIfNotPresent(
+ target: Record,
+ source: Record,
+) {
+ Object.getOwnPropertyNames(source).forEach((prop) => {
if (!Object.getOwnPropertyDescriptor(target, prop)) {
+ const propertyDescriptor = Object.getOwnPropertyDescriptor(source, prop);
Object.defineProperty(
target,
prop,
- Object.getOwnPropertyDescriptor(source, prop),
+ propertyDescriptor == null ? {} : propertyDescriptor,
);
}
});
}
-function copyOwnProps(target: Object, ...sources: Object[]) {
- sources.forEach(source => {
+function copyOwnProps(
+ target: Record,
+ ...sources: Array>
+) {
+ sources.forEach((source) => {
let chain = source;
- while (chain) {
+ while (chain != null) {
copyOwnPropsIfNotPresent(target, chain);
chain = Object.getPrototypeOf(chain);
}
@@ -349,13 +353,8 @@ function mergeMocks(genericMockFunction: () => any, customMock: any): any {
}
function getResolveType(namedFieldType: GraphQLNamedType) {
- if (
- namedFieldType instanceof GraphQLInterfaceType ||
- namedFieldType instanceof GraphQLUnionType
- ) {
+ if (isAbstractType(namedFieldType)) {
return namedFieldType.resolveType;
- } else {
- return undefined;
}
}
@@ -364,33 +363,28 @@ function assignResolveType(type: GraphQLType, preserveResolvers: boolean) {
const namedFieldType = getNamedType(fieldType);
const oldResolveType = getResolveType(namedFieldType);
- if (preserveResolvers && oldResolveType && oldResolveType.length) {
+ if (preserveResolvers && oldResolveType != null && oldResolveType.length) {
return;
}
- if (
- namedFieldType instanceof GraphQLUnionType ||
- namedFieldType instanceof GraphQLInterfaceType
- ) {
+ if (isInterfaceType(namedFieldType) || isUnionType(namedFieldType)) {
// the default `resolveType` always returns null. We add a fallback
// resolution that works with how unions and interface are mocked
namedFieldType.resolveType = (
data: any,
- context: any,
+ _context: any,
info: GraphQLResolveInfo,
- ) => {
- return info.schema.getType(data.__typename) as GraphQLObjectType;
- };
+ ) => info.schema.getType(data.__typename) as GraphQLObjectType;
}
}
class MockList {
- private len: number | number[];
- private wrappedFunction: GraphQLFieldResolver;
+ private readonly len: number | Array;
+ private readonly wrappedFunction: GraphQLFieldResolver | undefined;
// wrappedFunction can return another MockList or a value
constructor(
- len: number | number[],
+ len: number | Array,
wrappedFunction?: GraphQLFieldResolver,
) {
this.len = len;
@@ -412,7 +406,7 @@ class MockList {
fieldType: GraphQLList,
mockTypeFunc: IMockTypeFn,
) {
- let arr: any[];
+ let arr: Array;
if (Array.isArray(this.len)) {
arr = new Array(this.randint(this.len[0], this.len[1]));
} else {
@@ -449,4 +443,14 @@ class MockList {
}
}
-export { addMockFunctionsToSchema, MockList, mockServer };
+// retain addMockFunctionsToSchema for backwards compatibility
+
+function addMockFunctionsToSchema({
+ schema,
+ mocks = {},
+ preserveResolvers = false,
+}: IMockOptions): void {
+ addMocksToSchema({ schema, mocks, preserveResolvers });
+}
+
+export { addMocksToSchema, addMockFunctionsToSchema, MockList, mockServer };
diff --git a/src/polyfills/buildSchema.ts b/src/polyfills/buildSchema.ts
new file mode 100644
index 00000000000..ae8c3c05c0f
--- /dev/null
+++ b/src/polyfills/buildSchema.ts
@@ -0,0 +1,9 @@
+import { Source, buildASTSchema, parse, BuildSchemaOptions } from 'graphql';
+
+// polyfill for graphql prior to v13 which do not pass options to buildASTSchema
+export function buildSchema(
+ ast: string | Source,
+ buildSchemaOptions: BuildSchemaOptions,
+) {
+ return buildASTSchema(parse(ast), buildSchemaOptions);
+}
diff --git a/src/polyfills/extendSchema.ts b/src/polyfills/extendSchema.ts
new file mode 100644
index 00000000000..89a67aae6cc
--- /dev/null
+++ b/src/polyfills/extendSchema.ts
@@ -0,0 +1,36 @@
+import {
+ DocumentNode,
+ GraphQLSchema,
+ extendSchema as graphqlExtendSchema,
+} from 'graphql';
+
+import { getResolversFromSchema } from '../utils/index';
+import { IResolverOptions } from '../Interfaces';
+
+// polyfill for graphql < v14.2 which does not support subscriptions
+export function extendSchema(
+ schema: GraphQLSchema,
+ extension: DocumentNode,
+ options: any,
+): GraphQLSchema {
+ const subscriptionType = schema.getSubscriptionType();
+ if (subscriptionType == null) {
+ return graphqlExtendSchema(schema, extension, options);
+ }
+
+ const resolvers = getResolversFromSchema(schema);
+
+ const subscriptionTypeName = subscriptionType.name;
+ const subscriptionResolvers = resolvers[
+ subscriptionTypeName
+ ] as IResolverOptions;
+
+ const extendedSchema = graphqlExtendSchema(schema, extension, options);
+
+ const fields = extendedSchema.getSubscriptionType().getFields();
+ Object.keys(subscriptionResolvers).forEach((fieldName) => {
+ fields[fieldName].subscribe = subscriptionResolvers[fieldName].subscribe;
+ });
+
+ return extendedSchema;
+}
diff --git a/src/polyfills/index.ts b/src/polyfills/index.ts
new file mode 100644
index 00000000000..054341ba434
--- /dev/null
+++ b/src/polyfills/index.ts
@@ -0,0 +1,24 @@
+export { isSpecifiedScalarType } from './isSpecifiedScalarType';
+
+export { buildSchema } from './buildSchema';
+
+export { extendSchema } from './extendSchema';
+
+export {
+ toConfig,
+ schemaToConfig,
+ typeToConfig,
+ objectTypeToConfig,
+ interfaceTypeToConfig,
+ unionTypeToConfig,
+ enumTypeToConfig,
+ scalarTypeToConfig,
+ inputObjectTypeToConfig,
+ directiveToConfig,
+ inputFieldMapToConfig,
+ inputFieldToConfig,
+ fieldMapToConfig,
+ fieldToConfig,
+ argumentMapToConfig,
+ argumentToConfig,
+} from './toConfig';
diff --git a/src/isSpecifiedScalarType.ts b/src/polyfills/isSpecifiedScalarType.ts
similarity index 85%
rename from src/isSpecifiedScalarType.ts
rename to src/polyfills/isSpecifiedScalarType.ts
index 0fec7d93544..9305fcb53ae 100644
--- a/src/isSpecifiedScalarType.ts
+++ b/src/polyfills/isSpecifiedScalarType.ts
@@ -9,6 +9,7 @@ import {
} from 'graphql';
// FIXME: Replace with https://github.com/graphql/graphql-js/blob/master/src/type/scalars.js#L139
+// Blocked by https://github.com/graphql/graphql-js/issues/2153
export const specifiedScalarTypes: Array = [
GraphQLString,
@@ -18,7 +19,7 @@ export const specifiedScalarTypes: Array = [
GraphQLID,
];
-export default function isSpecifiedScalarType(type: any): boolean {
+export function isSpecifiedScalarType(type: any): boolean {
return (
isNamedType(type) &&
// Would prefer to use specifiedScalarTypes.some(), however %checks needs
diff --git a/src/polyfills/toConfig.ts b/src/polyfills/toConfig.ts
new file mode 100644
index 00000000000..bdb83b01fc2
--- /dev/null
+++ b/src/polyfills/toConfig.ts
@@ -0,0 +1,441 @@
+// graphql = [];
+
+ const types = schema.getTypeMap();
+ Object.keys(types).forEach((typeName) => {
+ newTypes.push(types[typeName]);
+ });
+
+ const schemaConfig = {
+ query: schema.getQueryType(),
+ mutation: schema.getMutationType(),
+ subscription: schema.getSubscriptionType(),
+ types: newTypes,
+ directives: schema.getDirectives().slice(),
+ extensions: schema.extensions,
+ astNode: schema.astNode,
+ extensionASTNodes:
+ schema.extensionASTNodes != null ? schema.extensionASTNodes : [],
+ assumeValid:
+ (schema as { __validationErrors?: boolean }).__validationErrors !==
+ undefined,
+ };
+
+ if (graphqlVersion() >= 15) {
+ (schemaConfig as {
+ description?: string;
+ }).description = (schema as {
+ description?: string;
+ }).description;
+ }
+
+ return schemaConfig;
+}
+
+export function toConfig(graphqlObject: GraphQLSchema): GraphQLSchemaConfig;
+export function toConfig(
+ graphqlObject: GraphQLObjectTypeConfig & {
+ interfaces: Array;
+ fields: GraphQLFieldConfigMap;
+ },
+): GraphQLObjectTypeConfig;
+export function toConfig(
+ graphqlObject: GraphQLInterfaceType,
+): GraphQLInterfaceTypeConfig & {
+ fields: GraphQLFieldConfigMap;
+};
+export function toConfig(
+ graphqlObject: GraphQLUnionType,
+): GraphQLUnionTypeConfig & {
+ types: Array;
+};
+export function toConfig(graphqlObject: GraphQLEnumType): GraphQLEnumTypeConfig;
+export function toConfig(
+ graphqlObject: GraphQLScalarType,
+): GraphQLScalarTypeConfig;
+export function toConfig(
+ graphqlObject: GraphQLInputObjectType,
+): GraphQLInputObjectTypeConfig & {
+ fields: GraphQLInputFieldConfigMap;
+};
+export function toConfig(
+ graphqlObject: GraphQLDirective,
+): GraphQLDirectiveConfig;
+export function toConfig(
+ graphqlObject: GraphQLInputField,
+): GraphQLInputFieldConfig;
+export function toConfig(
+ graphqlObject: GraphQLField,
+): GraphQLFieldConfig;
+export function toConfig(graphqlObject: any): any;
+export function toConfig(graphqlObject: any) {
+ if (isSchema(graphqlObject)) {
+ return schemaToConfig(graphqlObject);
+ } else if (isDirective(graphqlObject)) {
+ return directiveToConfig(graphqlObject);
+ } else if (isNamedType(graphqlObject)) {
+ return typeToConfig(graphqlObject);
+ }
+
+ // Input and output fields do not have predicates defined, but using duck typing,
+ // type is defined for input and output fields
+ if (graphqlObject.type != null) {
+ if (
+ graphqlObject.args != null ||
+ graphqlObject.resolve != null ||
+ graphqlObject.subscribe != null
+ ) {
+ return fieldToConfig(graphqlObject);
+ } else if (graphqlObject.defaultValue !== undefined) {
+ return inputFieldToConfig(graphqlObject);
+ }
+
+ // Not all input and output fields can be checked by above in older versions
+ // of graphql, but almost all properties on the field and config are identical.
+ // In particular, just name and isDeprecated should be removed.
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { name, isDeprecated, ...rest } = graphqlObject;
+ return {
+ ...rest,
+ };
+ }
+
+ throw new Error(`Unknown graphql object ${graphqlObject as string}`);
+}
+
+export function typeToConfig(
+ type: GraphQLObjectType,
+): GraphQLObjectTypeConfig;
+export function typeToConfig(
+ type: GraphQLInterfaceType,
+): GraphQLInterfaceTypeConfig;
+export function typeToConfig(
+ type: GraphQLUnionType,
+): GraphQLUnionTypeConfig;
+export function typeToConfig(type: GraphQLEnumType): GraphQLEnumTypeConfig;
+export function typeToConfig(
+ type: GraphQLScalarType,
+): GraphQLScalarTypeConfig;
+export function typeToConfig(
+ type: GraphQLInputObjectType,
+): GraphQLInputObjectTypeConfig;
+export function typeToConfig(type: any): any;
+export function typeToConfig(type: any) {
+ if (isObjectType(type)) {
+ return objectTypeToConfig(type);
+ } else if (isInterfaceType(type)) {
+ return interfaceTypeToConfig(type);
+ } else if (isUnionType(type)) {
+ return unionTypeToConfig(type);
+ } else if (isEnumType(type)) {
+ return enumTypeToConfig(type);
+ } else if (isScalarType(type)) {
+ return scalarTypeToConfig(type);
+ } else if (isInputObjectType(type)) {
+ return inputObjectTypeToConfig(type);
+ }
+
+ throw new Error(`Unknown type ${type as string}`);
+}
+
+export function objectTypeToConfig(
+ type: GraphQLObjectType,
+): GraphQLObjectTypeConfig {
+ if (type.toConfig != null) {
+ return type.toConfig();
+ }
+
+ const typeConfig = {
+ name: type.name,
+ description: type.description,
+ interfaces: type.getInterfaces(),
+ fields: fieldMapToConfig(type.getFields()),
+ isTypeOf: type.isTypeOf,
+ extensions: type.extensions,
+ astNode: type.astNode,
+ extensionASTNodes:
+ type.extensionASTNodes != null ? type.extensionASTNodes : [],
+ };
+
+ return typeConfig;
+}
+
+export function interfaceTypeToConfig(
+ type: GraphQLInterfaceType,
+): GraphQLInterfaceTypeConfig {
+ if (type.toConfig != null) {
+ return type.toConfig();
+ }
+
+ const typeConfig = {
+ name: type.name,
+ description: type.description,
+ fields: fieldMapToConfig(type.getFields()),
+ resolveType: type.resolveType,
+ extensions: type.extensions,
+ astNode: type.astNode,
+ extensionASTNodes:
+ type.extensionASTNodes != null ? type.extensionASTNodes : [],
+ };
+
+ if (graphqlVersion() >= 15) {
+ ((typeConfig as unknown) as GraphQLObjectTypeConfig<
+ any,
+ any
+ >).interfaces = ((type as unknown) as GraphQLObjectType).getInterfaces();
+ }
+
+ return typeConfig;
+}
+
+export function unionTypeToConfig(
+ type: GraphQLUnionType,
+): GraphQLUnionTypeConfig {
+ if (type.toConfig != null) {
+ return type.toConfig();
+ }
+
+ const typeConfig = {
+ name: type.name,
+ description: type.description,
+ types: type.getTypes(),
+ resolveType: type.resolveType,
+ extensions: type.extensions,
+ astNode: type.astNode,
+ extensionASTNodes:
+ type.extensionASTNodes != null ? type.extensionASTNodes : [],
+ };
+
+ return typeConfig;
+}
+
+export function enumTypeToConfig(type: GraphQLEnumType): GraphQLEnumTypeConfig {
+ if (type.toConfig != null) {
+ return type.toConfig();
+ }
+
+ const newValues = {};
+
+ type.getValues().forEach((value) => {
+ newValues[value.name] = {
+ description: value.description,
+ value: value.value,
+ deprecationReason: value.deprecationReason,
+ extensions: value.extensions,
+ astNode: value.astNode,
+ };
+ });
+
+ const typeConfig = {
+ name: type.name,
+ description: type.description,
+ values: newValues,
+ extensions: type.extensions,
+ astNode: type.astNode,
+ extensionASTNodes:
+ type.extensionASTNodes != null ? type.extensionASTNodes : [],
+ };
+
+ return typeConfig;
+}
+
+const hasOwn = Object.prototype.hasOwnProperty;
+
+export function scalarTypeToConfig(
+ type: GraphQLScalarType,
+): GraphQLScalarTypeConfig {
+ if (type.toConfig != null) {
+ return type.toConfig();
+ }
+
+ const typeConfig = {
+ name: type.name,
+ description: type.description,
+ serialize:
+ graphqlVersion() >= 14 || hasOwn.call(type, 'serialize')
+ ? type.serialize
+ : ((type as unknown) as {
+ _scalarConfig: GraphQLScalarTypeConfig;
+ })._scalarConfig.serialize,
+ parseValue:
+ graphqlVersion() >= 14 || hasOwn.call(type, 'parseValue')
+ ? type.parseValue
+ : ((type as unknown) as {
+ _scalarConfig: GraphQLScalarTypeConfig;
+ })._scalarConfig.parseValue,
+ parseLiteral:
+ graphqlVersion() >= 14 || hasOwn.call(type, 'parseLiteral')
+ ? type.parseLiteral
+ : ((type as unknown) as {
+ _scalarConfig: GraphQLScalarTypeConfig;
+ })._scalarConfig.parseLiteral,
+ extensions: type.extensions,
+ astNode: type.astNode,
+ extensionASTNodes:
+ type.extensionASTNodes != null ? type.extensionASTNodes : [],
+ };
+
+ return typeConfig;
+}
+
+export function inputObjectTypeToConfig(
+ type: GraphQLInputObjectType,
+): GraphQLInputObjectTypeConfig {
+ if (type.toConfig != null) {
+ return type.toConfig();
+ }
+
+ const typeConfig = {
+ name: type.name,
+ description: type.description,
+ fields: inputFieldMapToConfig(type.getFields()),
+ extensions: type.extensions,
+ astNode: type.astNode,
+ extensionASTNodes:
+ type.extensionASTNodes != null ? type.extensionASTNodes : [],
+ };
+
+ return typeConfig;
+}
+
+export function inputFieldMapToConfig(
+ fields: GraphQLInputFieldMap,
+): GraphQLInputFieldConfigMap {
+ const newFields = {};
+ Object.keys(fields).forEach((fieldName) => {
+ const field = fields[fieldName];
+ newFields[fieldName] = toConfig(field);
+ });
+
+ return newFields;
+}
+
+export function inputFieldToConfig(
+ field: GraphQLInputField,
+): GraphQLInputFieldConfig {
+ return {
+ description: field.description,
+ type: field.type,
+ defaultValue: field.defaultValue,
+ extensions: field.extensions,
+ astNode: field.astNode,
+ };
+}
+
+export function directiveToConfig(
+ directive: GraphQLDirective,
+): GraphQLDirectiveConfig {
+ if (directive.toConfig != null) {
+ return directive.toConfig();
+ }
+
+ const directiveConfig = {
+ name: directive.name,
+ description: directive.description,
+ locations: directive.locations,
+ args: argumentMapToConfig(directive.args),
+ isRepeatable: ((directive as unknown) as { isRepeatable: boolean })
+ .isRepeatable,
+ extensions: directive.extensions,
+ astNode: directive.astNode,
+ };
+
+ return directiveConfig;
+}
+
+export function fieldMapToConfig(
+ fields: GraphQLFieldMap,
+): GraphQLFieldConfigMap {
+ const newFields = {};
+
+ Object.keys(fields).forEach((fieldName) => {
+ const field = fields[fieldName];
+ newFields[fieldName] = toConfig(field);
+ });
+
+ return newFields;
+}
+
+export function fieldToConfig(
+ field: GraphQLField,
+): GraphQLFieldConfig {
+ return {
+ description: field.description,
+ type: field.type,
+ args: argumentMapToConfig(field.args),
+ resolve: field.resolve,
+ subscribe: field.subscribe,
+ deprecationReason: field.deprecationReason,
+ extensions: field.extensions,
+ astNode: field.astNode,
+ };
+}
+
+export function argumentMapToConfig(
+ args: ReadonlyArray,
+): GraphQLFieldConfigArgumentMap {
+ const newArguments = {};
+ args.forEach((arg) => {
+ newArguments[arg.name] = argumentToConfig(arg);
+ });
+
+ return newArguments;
+}
+
+export function argumentToConfig(arg: GraphQLArgument): GraphQLArgumentConfig {
+ return {
+ description: arg.description,
+ type: arg.type,
+ defaultValue: arg.defaultValue,
+ extensions: arg.extensions,
+ astNode: arg.astNode,
+ };
+}
diff --git a/src/scalars/GraphQLUpload.ts b/src/scalars/GraphQLUpload.ts
new file mode 100644
index 00000000000..a903244057b
--- /dev/null
+++ b/src/scalars/GraphQLUpload.ts
@@ -0,0 +1,23 @@
+import { GraphQLScalarType, GraphQLError } from 'graphql';
+
+const GraphQLUpload = new GraphQLScalarType({
+ name: 'Upload',
+ description: 'The `Upload` scalar type represents a file upload.',
+ parseValue: (value) => {
+ if (value != null && value.promise instanceof Promise) {
+ // graphql-upload v10
+ return value.promise;
+ } else if (value instanceof Promise) {
+ // graphql-upload v9
+ return value;
+ }
+ throw new GraphQLError('Upload value invalid.');
+ },
+ // serialization requires to support schema stitching
+ serialize: (value) => value,
+ parseLiteral: (ast) => {
+ throw new GraphQLError('Upload literal unsupported.', ast);
+ },
+});
+
+export { GraphQLUpload };
diff --git a/src/scalars/index.ts b/src/scalars/index.ts
new file mode 100644
index 00000000000..593c28f6641
--- /dev/null
+++ b/src/scalars/index.ts
@@ -0,0 +1,3 @@
+import { GraphQLUpload } from './GraphQLUpload';
+
+export { GraphQLUpload };
diff --git a/src/schemaVisitor.ts b/src/schemaVisitor.ts
deleted file mode 100644
index 5769e132b0c..00000000000
--- a/src/schemaVisitor.ts
+++ /dev/null
@@ -1,766 +0,0 @@
-import {
- GraphQLArgument,
- GraphQLDirective,
- GraphQLEnumType,
- GraphQLEnumValue,
- GraphQLField,
- GraphQLInputField,
- GraphQLInputObjectType,
- GraphQLInterfaceType,
- GraphQLNamedType,
- GraphQLObjectType,
- GraphQLScalarType,
- GraphQLSchema,
- GraphQLUnionType,
- Kind,
- ValueNode,
- DirectiveLocationEnum,
- GraphQLType,
- GraphQLList,
- GraphQLNonNull,
- isNamedType,
-} from 'graphql';
-
-import {
- getArgumentValues,
-} from 'graphql/execution/values';
-
-export type VisitableSchemaType =
- GraphQLSchema
- | GraphQLObjectType
- | GraphQLInterfaceType
- | GraphQLInputObjectType
- | GraphQLNamedType
- | GraphQLScalarType
- | GraphQLField
- | GraphQLArgument
- | GraphQLUnionType
- | GraphQLEnumType
- | GraphQLEnumValue
- | GraphQLInputField;
-
-const hasOwn = Object.prototype.hasOwnProperty;
-
-// Abstract base class of any visitor implementation, defining the available
-// visitor methods along with their parameter types, and providing a static
-// helper function for determining whether a subclass implements a given
-// visitor method, as opposed to inheriting one of the stubs defined here.
-export abstract class SchemaVisitor {
- // All SchemaVisitor instances are created while visiting a specific
- // GraphQLSchema object, so this property holds a reference to that object,
- // in case a visitor method needs to refer to this.schema.
- public schema: GraphQLSchema;
-
- // Determine if this SchemaVisitor (sub)class implements a particular
- // visitor method.
- public static implementsVisitorMethod(methodName: string) {
- if (! methodName.startsWith('visit')) {
- return false;
- }
-
- const method = this.prototype[methodName];
- if (typeof method !== 'function') {
- return false;
- }
-
- if (this === SchemaVisitor) {
- // The SchemaVisitor class implements every visitor method.
- return true;
- }
-
- const stub = SchemaVisitor.prototype[methodName];
- if (method === stub) {
- // If this.prototype[methodName] was just inherited from SchemaVisitor,
- // then this class does not really implement the method.
- return false;
- }
-
- return true;
- }
-
- // Concrete subclasses of SchemaVisitor should override one or more of these
- // visitor methods, in order to express their interest in handling certain
- // schema types/locations. Each method may return null to remove the given
- // type from the schema, a non-null value of the same type to update the
- // type in the schema, or nothing to leave the type as it was.
-
- /* tslint:disable:no-empty */
- public visitSchema(schema: GraphQLSchema): void {}
- public visitScalar(scalar: GraphQLScalarType): GraphQLScalarType | void | null {}
- public visitObject(object: GraphQLObjectType): GraphQLObjectType | void | null {}
- public visitFieldDefinition(field: GraphQLField, details: {
- objectType: GraphQLObjectType | GraphQLInterfaceType,
- }): GraphQLField | void | null {}
- public visitArgumentDefinition(argument: GraphQLArgument, details: {
- field: GraphQLField,
- objectType: GraphQLObjectType | GraphQLInterfaceType,
- }): GraphQLArgument | void | null {}
- public visitInterface(iface: GraphQLInterfaceType): GraphQLInterfaceType | void | null {}
- public visitUnion(union: GraphQLUnionType): GraphQLUnionType | void | null {}
- public visitEnum(type: GraphQLEnumType): GraphQLEnumType | void | null {}
- public visitEnumValue(value: GraphQLEnumValue, details: {
- enumType: GraphQLEnumType,
- }): GraphQLEnumValue | void | null {}
- public visitInputObject(object: GraphQLInputObjectType): GraphQLInputObjectType | void |Â null {}
- public visitInputFieldDefinition(field: GraphQLInputField, details: {
- objectType: GraphQLInputObjectType,
- }): GraphQLInputField | void | null {}
- /* tslint:enable:no-empty */
-}
-
-// Generic function for visiting GraphQLSchema objects.
-export function visitSchema(
- schema: GraphQLSchema,
- // To accommodate as many different visitor patterns as possible, the
- // visitSchema function does not simply accept a single instance of the
- // SchemaVisitor class, but instead accepts a function that takes the
- // current VisitableSchemaType object and the name of a visitor method and
- // returns an array of SchemaVisitor instances that implement the visitor
- // method and have an interest in handling the given VisitableSchemaType
- // object. In the simplest case, this function can always return an array
- // containing a single visitor object, without even looking at the type or
- // methodName parameters. In other cases, this function might sometimes
- // return an empty array to indicate there are no visitors that should be
- // applied to the given VisitableSchemaType object. For an example of a
- // visitor pattern that benefits from this abstraction, see the
- // SchemaDirectiveVisitor class below.
- visitorSelector: (
- type: VisitableSchemaType,
- methodName: string,
- ) => SchemaVisitor[],
-): GraphQLSchema {
- // Helper function that calls visitorSelector and applies the resulting
- // visitors to the given type, with arguments [type, ...args].
- function callMethod(
- methodName: string,
- type: T,
- ...args: any[]
- ): T {
- visitorSelector(type, methodName).every(visitor => {
- const newType = visitor[methodName](type, ...args);
-
- if (typeof newType === 'undefined') {
- // Keep going without modifying type.
- return true;
- }
-
- if (methodName === 'visitSchema' ||
- type instanceof GraphQLSchema) {
- throw new Error(`Method ${methodName} cannot replace schema with ${newType}`);
- }
-
- if (newType === null) {
- // Stop the loop and return null form callMethod, which will cause
- // the type to be removed from the schema.
- type = null;
- return false;
- }
-
- // Update type to the new type returned by the visitor method, so that
- // later directives will see the new type, and callMethod will return
- // the final type.
- type = newType;
- return true;
- });
-
- // If there were no directives for this type object, or if all visitor
- // methods returned nothing, type will be returned unmodified.
- return type;
- }
-
- // Recursive helper function that calls any appropriate visitor methods for
- // each object in the schema, then traverses the object's children (if any).
- function visit(type: T): T {
- if (type instanceof GraphQLSchema) {
- // Unlike the other types, the root GraphQLSchema object cannot be
- // replaced by visitor methods, because that would make life very hard
- // for SchemaVisitor subclasses that rely on the original schema object.
- callMethod('visitSchema', type);
-
- updateEachKey(type.getTypeMap(), (namedType, typeName) => {
- if (! typeName.startsWith('__')) {
- // Call visit recursively to let it determine which concrete
- // subclass of GraphQLNamedType we found in the type map. Because
- // we're using updateEachKey, the result of visit(namedType) may
- // cause the type to be removed or replaced.
- return visit(namedType);
- }
- });
-
- return type;
- }
-
- if (type instanceof GraphQLObjectType) {
- // Note that callMethod('visitObject', type) may not actually call any
- // methods, if there are no @directive annotations associated with this
- // type, or if this SchemaDirectiveVisitor subclass does not override
- // the visitObject method.
- const newObject = callMethod('visitObject', type);
- if (newObject) {
- visitFields(newObject);
- }
- return newObject;
- }
-
- if (type instanceof GraphQLInterfaceType) {
- const newInterface = callMethod('visitInterface', type);
- if (newInterface) {
- visitFields(newInterface);
- }
- return newInterface;
- }
-
- if (type instanceof GraphQLInputObjectType) {
- const newInputObject = callMethod('visitInputObject', type);
-
- if (newInputObject) {
- updateEachKey(newInputObject.getFields(), field => {
- // Since we call a different method for input object fields, we
- // can't reuse the visitFields function here.
- return callMethod('visitInputFieldDefinition', field, {
- objectType: newInputObject,
- });
- });
- }
-
- return newInputObject;
- }
-
- if (type instanceof GraphQLScalarType) {
- return callMethod('visitScalar', type);
- }
-
- if (type instanceof GraphQLUnionType) {
- return callMethod('visitUnion', type);
- }
-
- if (type instanceof GraphQLEnumType) {
- const newEnum = callMethod('visitEnum', type);
-
- if (newEnum) {
- updateEachKey(newEnum.getValues(), value => {
- return callMethod('visitEnumValue', value, {
- enumType: newEnum,
- });
- });
- }
-
- return newEnum;
- }
-
- throw new Error(`Unexpected schema type: ${type}`);
- }
-
- function visitFields(type: GraphQLObjectType | GraphQLInterfaceType) {
- updateEachKey(type.getFields(), field => {
- // It would be nice if we could call visit(field) recursively here, but
- // GraphQLField is merely a type, not a value that can be detected using
- // an instanceof check, so we have to visit the fields in this lexical
- // context, so that TypeScript can validate the call to
- // visitFieldDefinition.
- const newField = callMethod('visitFieldDefinition', field, {
- // While any field visitor needs a reference to the field object, some
- // field visitors may also need to know the enclosing (parent) type,
- // perhaps to determine if the parent is a GraphQLObjectType or a
- // GraphQLInterfaceType. To obtain a reference to the parent, a
- // visitor method can have a second parameter, which will be an object
- // with an .objectType property referring to the parent.
- objectType: type,
- });
-
- if (newField && newField.args) {
- updateEachKey(newField.args, arg => {
- return callMethod('visitArgumentDefinition', arg, {
- // Like visitFieldDefinition, visitArgumentDefinition takes a
- // second parameter that provides additional context, namely the
- // parent .field and grandparent .objectType. Remember that the
- // current GraphQLSchema is always available via this.schema.
- field: newField,
- objectType: type,
- });
- });
- }
-
- return newField;
- });
- }
-
- visit(schema);
-
- // Return the original schema for convenience, even though it cannot have
- // been replaced or removed by the code above.
- return schema;
-}
-
-type NamedTypeMap = {
- [key: string]: GraphQLNamedType;
-};
-
-// Update any references to named schema types that disagree with the named
-// types found in schema.getTypeMap().
-export function healSchema(schema: GraphQLSchema) {
- heal(schema);
- return schema;
-
- function heal(type: VisitableSchemaType) {
- if (type instanceof GraphQLSchema) {
- const originalTypeMap: NamedTypeMap = type.getTypeMap();
- const actualNamedTypeMap: NamedTypeMap = Object.create(null);
-
- // If any of the .name properties of the GraphQLNamedType objects in
- // schema.getTypeMap() have changed, the keys of the type map need to
- // be updated accordingly.
-
- each(originalTypeMap, (namedType, typeName) => {
- if (typeName.startsWith('__')) {
- return;
- }
-
- const actualName = namedType.name;
- if (actualName.startsWith('__')) {
- return;
- }
-
- if (hasOwn.call(actualNamedTypeMap, actualName)) {
- throw new Error(`Duplicate schema type name ${actualName}`);
- }
-
- actualNamedTypeMap[actualName] = namedType;
-
- // Note: we are deliberately leaving namedType in the schema by its
- // original name (which might be different from actualName), so that
- // references by that name can be healed.
- });
-
- // Now add back every named type by its actual name.
- each(actualNamedTypeMap, (namedType, typeName) => {
- originalTypeMap[typeName] = namedType;
- });
-
- // Directive declaration argument types can refer to named types.
- each(type.getDirectives(), (decl: GraphQLDirective) => {
- if (decl.args) {
- each(decl.args, arg => {
- arg.type = healType(arg.type);
- });
- }
- });
-
- each(originalTypeMap, (namedType, typeName) => {
- if (! typeName.startsWith('__')) {
- heal(namedType);
- }
- });
-
- updateEachKey(originalTypeMap, (namedType, typeName) => {
- // Dangling references to renamed types should remain in the schema
- // during healing, but must be removed now, so that the following
- // invariant holds for all names: schema.getType(name).name === name
- if (! typeName.startsWith('__') &&
- ! hasOwn.call(actualNamedTypeMap, typeName)) {
- return null;
- }
- });
-
- } else if (type instanceof GraphQLObjectType) {
- healFields(type);
- each(type.getInterfaces(), iface => heal(iface));
-
- } else if (type instanceof GraphQLInterfaceType) {
- healFields(type);
-
- } else if (type instanceof GraphQLInputObjectType) {
- each(type.getFields(), field => {
- field.type = healType(field.type);
- });
-
- } else if (type instanceof GraphQLScalarType) {
- // Nothing to do.
-
- } else if (type instanceof GraphQLUnionType) {
- updateEachKey(type.getTypes(), t => healType(t));
-
- } else if (type instanceof GraphQLEnumType) {
- // Nothing to do.
-
- } else {
- throw new Error(`Unexpected schema type: ${type}`);
- }
- }
-
- function healFields(type: GraphQLObjectType | GraphQLInterfaceType) {
- each(type.getFields(), field => {
- field.type = healType(field.type);
- if (field.args) {
- each(field.args, arg => {
- arg.type = healType(arg.type);
- });
- }
- });
- }
-
- function healType(type: T): T {
- // Unwrap the two known wrapper types
- if (type instanceof GraphQLList) {
- type = new GraphQLList(healType(type.ofType)) as T;
- } else if (type instanceof GraphQLNonNull) {
- type = new GraphQLNonNull(healType(type.ofType)) as T;
- } else if (isNamedType(type)) {
- // If a type annotation on a field or an argument or a union member is
- // any `GraphQLNamedType` with a `name`, then it must end up identical
- // to `schema.getType(name)`, since `schema.getTypeMap()` is the source
- // of truth for all named schema types.
- const namedType = type as GraphQLNamedType;
- const officialType = schema.getType(namedType.name);
- if (officialType && namedType !== officialType) {
- return officialType as T;
- }
- }
- return type;
- }
-}
-
-// This class represents a reusable implementation of a @directive that may
-// appear in a GraphQL schema written in Schema Definition Language.
-//
-// By overriding one or more visit{Object,Union,...} methods, a subclass
-// registers interest in certain schema types, such as GraphQLObjectType,
-// GraphQLUnionType, etc. When SchemaDirectiveVisitor.visitSchemaDirectives is
-// called with a GraphQLSchema object and a map of visitor subclasses, the
-// overidden methods of those subclasses allow the visitors to obtain
-// references to any type objects that have @directives attached to them,
-// enabling visitors to inspect or modify the schema as appropriate.
-//
-// For example, if a directive called @rest(url: "...") appears after a field
-// definition, a SchemaDirectiveVisitor subclass could provide meaning to that
-// directive by overriding the visitFieldDefinition method (which receives a
-// GraphQLField parameter), and then the body of that visitor method could
-// manipulate the field's resolver function to fetch data from a REST endpoint
-// described by the url argument passed to the @rest directive:
-//
-// const typeDefs = `
-// type Query {
-// people: [Person] @rest(url: "/api/v1/people")
-// }`;
-//
-// const schema = makeExecutableSchema({ typeDefs });
-//
-// SchemaDirectiveVisitor.visitSchemaDirectives(schema, {
-// rest: class extends SchemaDirectiveVisitor {
-// public visitFieldDefinition(field: GraphQLField) {
-// const { url } = this.args;
-// field.resolve = () => fetch(url);
-// }
-// }
-// });
-//
-// The subclass in this example is defined as an anonymous class expression,
-// for brevity. A truly reusable SchemaDirectiveVisitor would most likely be
-// defined in a library using a named class declaration, and then exported for
-// consumption by other modules and packages.
-//
-// See below for a complete list of overridable visitor methods, their
-// parameter types, and more details about the properties exposed by instances
-// of the SchemaDirectiveVisitor class.
-
-export class SchemaDirectiveVisitor extends SchemaVisitor {
- // The name of the directive this visitor is allowed to visit (that is, the
- // identifier that appears after the @ character in the schema). Note that
- // this property is per-instance rather than static because subclasses of
- // SchemaDirectiveVisitor can be instantiated multiple times to visit
- // directives of different names. In other words, SchemaDirectiveVisitor
- // implementations are effectively anonymous, and it's up to the caller of
- // SchemaDirectiveVisitor.visitSchemaDirectives to assign names to them.
- public name: string;
-
- // A map from parameter names to argument values, as obtained from a
- // specific occurrence of a @directive(arg1: value1, arg2: value2, ...) in
- // the schema. Visitor methods may refer to this object via this.args.
- public args: { [name: string]: any };
-
- // A reference to the type object that this visitor was created to visit.
- public visitedType: VisitableSchemaType;
-
- // A shared object that will be available to all visitor instances via
- // this.context. Callers of visitSchemaDirectives can provide their own
- // object, or just use the default empty object.
- public context: { [key: string]: any };
-
- // Override this method to return a custom GraphQLDirective (or modify one
- // already present in the schema) to enforce argument types, provide default
- // argument values, or specify schema locations where this @directive may
- // appear. By default, any declaration found in the schema will be returned.
- public static getDirectiveDeclaration(
- directiveName: string,
- schema: GraphQLSchema,
- ): GraphQLDirective {
- return schema.getDirective(directiveName);
- }
-
- // Call SchemaDirectiveVisitor.visitSchemaDirectives to visit every
- // @directive in the schema and create an appropriate SchemaDirectiveVisitor
- // instance to visit the object decorated by the @directive.
- public static visitSchemaDirectives(
- schema: GraphQLSchema,
- directiveVisitors: {
- // The keys of this object correspond to directive names as they appear
- // in the schema, and the values should be subclasses (not instances!)
- // of the SchemaDirectiveVisitor class. This distinction is important
- // because a new SchemaDirectiveVisitor instance will be created each
- // time a matching directive is found in the schema AST, with arguments
- // and other metadata specific to that occurrence. To help prevent the
- // mistake of passing instances, the SchemaDirectiveVisitor constructor
- // method is marked as protected.
- [directiveName: string]: typeof SchemaDirectiveVisitor
- },
- // Optional context object that will be available to all visitor instances
- // via this.context. Defaults to an empty null-prototype object.
- context: {
- [key: string]: any
- } = Object.create(null),
- ): {
- // The visitSchemaDirectives method returns a map from directive names to
- // lists of SchemaDirectiveVisitors created while visiting the schema.
- [directiveName: string]: SchemaDirectiveVisitor[],
- } {
- // If the schema declares any directives for public consumption, record
- // them here so that we can properly coerce arguments when/if we encounter
- // an occurrence of the directive while walking the schema below.
- const declaredDirectives =
- this.getDeclaredDirectives(schema, directiveVisitors);
-
- // Map from directive names to lists of SchemaDirectiveVisitor instances
- // created while visiting the schema.
- const createdVisitors: {
- [directiveName: string]: SchemaDirectiveVisitor[]
- } = Object.create(null);
- Object.keys(directiveVisitors).forEach(directiveName => {
- createdVisitors[directiveName] = [];
- });
-
- function visitorSelector(
- type: VisitableSchemaType,
- methodName: string,
- ): SchemaDirectiveVisitor[] {
- const visitors: SchemaDirectiveVisitor[] = [];
- const directiveNodes = type.astNode && type.astNode.directives;
- if (! directiveNodes) {
- return visitors;
- }
-
- directiveNodes.forEach(directiveNode => {
- const directiveName = directiveNode.name.value;
- if (! hasOwn.call(directiveVisitors, directiveName)) {
- return;
- }
-
- const visitorClass = directiveVisitors[directiveName];
-
- // Avoid creating visitor objects if visitorClass does not override
- // the visitor method named by methodName.
- if (! visitorClass.implementsVisitorMethod(methodName)) {
- return;
- }
-
- const decl = declaredDirectives[directiveName];
- let args: { [key: string]: any };
-
- if (decl) {
- // If this directive was explicitly declared, use the declared
- // argument types (and any default values) to check, coerce, and/or
- // supply default values for the given arguments.
- args = getArgumentValues(decl, directiveNode);
- } else {
- // If this directive was not explicitly declared, just convert the
- // argument nodes to their corresponding JavaScript values.
- args = Object.create(null);
- directiveNode.arguments.forEach(arg => {
- args[arg.name.value] = valueFromASTUntyped(arg.value);
- });
- }
-
- // As foretold in comments near the top of the visitSchemaDirectives
- // method, this is where instances of the SchemaDirectiveVisitor class
- // get created and assigned names. While subclasses could override the
- // constructor method, the constructor is marked as protected, so
- // these are the only arguments that will ever be passed.
- visitors.push(new visitorClass({
- name: directiveName,
- args,
- visitedType: type,
- schema,
- context,
- }));
- });
-
- if (visitors.length > 0) {
- visitors.forEach(visitor => {
- createdVisitors[visitor.name].push(visitor);
- });
- }
-
- return visitors;
- }
-
- visitSchema(schema, visitorSelector);
-
- // Automatically update any references to named schema types replaced
- // during the traversal, so implementors don't have to worry about that.
- healSchema(schema);
-
- return createdVisitors;
- }
-
- protected static getDeclaredDirectives(
- schema: GraphQLSchema,
- directiveVisitors: {
- [directiveName: string]: typeof SchemaDirectiveVisitor
- },
- ) {
- const declaredDirectives: {
- [directiveName: string]: GraphQLDirective,
- } = Object.create(null);
-
- each(schema.getDirectives(), (decl: GraphQLDirective) => {
- declaredDirectives[decl.name] = decl;
- });
-
- // If the visitor subclass overrides getDirectiveDeclaration, and it
- // returns a non-null GraphQLDirective, use that instead of any directive
- // declared in the schema itself. Reasoning: if a SchemaDirectiveVisitor
- // goes to the trouble of implementing getDirectiveDeclaration, it should
- // be able to rely on that implementation.
- each(directiveVisitors, (visitorClass, directiveName) => {
- const decl = visitorClass.getDirectiveDeclaration(directiveName, schema);
- if (decl) {
- declaredDirectives[directiveName] = decl;
- }
- });
-
- each(declaredDirectives, (decl, name) => {
- if (! hasOwn.call(directiveVisitors, name)) {
- // SchemaDirectiveVisitors.visitSchemaDirectives might be called
- // multiple times with partial directiveVisitors maps, so it's not
- // necessarily an error for directiveVisitors to be missing an
- // implementation of a directive that was declared in the schema.
- return;
- }
- const visitorClass = directiveVisitors[name];
-
- each(decl.locations, loc => {
- const visitorMethodName = directiveLocationToVisitorMethodName(loc);
- if (SchemaVisitor.implementsVisitorMethod(visitorMethodName) &&
- ! visitorClass.implementsVisitorMethod(visitorMethodName)) {
- // While visitor subclasses may implement extra visitor methods,
- // it's definitely a mistake if the GraphQLDirective declares itself
- // applicable to certain schema locations, and the visitor subclass
- // does not implement all the corresponding methods.
- throw new Error(
- `SchemaDirectiveVisitor for @${name} must implement ${visitorMethodName} method`
- );
- }
- });
- });
-
- return declaredDirectives;
- }
-
- // Mark the constructor protected to enforce passing SchemaDirectiveVisitor
- // subclasses (not instances) to visitSchemaDirectives.
- protected constructor(config: {
- name: string
- args: { [name: string]: any }
- visitedType: VisitableSchemaType
- schema: GraphQLSchema
- context: { [key: string]: any }
- }) {
- super();
- this.name = config.name;
- this.args = config.args;
- this.visitedType = config.visitedType;
- this.schema = config.schema;
- this.context = config.context;
- }
-}
-
-// Convert a string like "FIELD_DEFINITION" to "visitFieldDefinition".
-function directiveLocationToVisitorMethodName(loc: DirectiveLocationEnum) {
- return 'visit' + loc.replace(/([^_]*)_?/g, (wholeMatch, part) => {
- return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
- });
-}
-
-type IndexedObject = { [key: string]: V } | ReadonlyArray;
-
-function each(
- arrayOrObject: IndexedObject,
- callback: (value: V, key: string) => void,
-) {
- Object.keys(arrayOrObject).forEach(key => {
- callback(arrayOrObject[key], key);
- });
-}
-
-// A more powerful version of each that has the ability to replace or remove
-// array or object keys.
-function updateEachKey(
- arrayOrObject: IndexedObject,
- // The callback can return nothing to leave the key untouched, null to remove
- // the key from the array or object, or a non-null V to replace the value.
- callback: (value: V, key: string) => V | void,
-) {
- let deletedCount = 0;
-
- Object.keys(arrayOrObject).forEach(key => {
- const result = callback(arrayOrObject[key], key);
-
- if (typeof result === 'undefined') {
- return;
- }
-
- if (result === null) {
- delete arrayOrObject[key];
- deletedCount++;
- return;
- }
-
- arrayOrObject[key] = result;
- });
-
- if (deletedCount > 0 && Array.isArray(arrayOrObject)) {
- // Remove any holes from the array due to deleted elements.
- arrayOrObject.splice(0).forEach(elem => {
- arrayOrObject.push(elem);
- });
- }
-}
-
-// Similar to the graphql-js function of the same name, slightly simplified:
-// https://github.com/graphql/graphql-js/blob/master/src/utilities/valueFromASTUntyped.js
-function valueFromASTUntyped(
- valueNode: ValueNode,
-): any {
- switch (valueNode.kind) {
- case Kind.NULL:
- return null;
- case Kind.INT:
- return parseInt(valueNode.value, 10);
- case Kind.FLOAT:
- return parseFloat(valueNode.value);
- case Kind.STRING:
- case Kind.ENUM:
- case Kind.BOOLEAN:
- return valueNode.value;
- case Kind.LIST:
- return valueNode.values.map(valueFromASTUntyped);
- case Kind.OBJECT:
- const obj = Object.create(null);
- valueNode.fields.forEach(field => {
- obj[field.name.value] = valueFromASTUntyped(field.value);
- });
- return obj;
- /* istanbul ignore next */
- default:
- throw new Error('Unexpected value kind: ' + valueNode.kind);
- }
-}
diff --git a/src/stitch/createMergedResolver.ts b/src/stitch/createMergedResolver.ts
new file mode 100644
index 00000000000..940a5d130b3
--- /dev/null
+++ b/src/stitch/createMergedResolver.ts
@@ -0,0 +1,54 @@
+import { IFieldResolver } from '../Interfaces';
+
+import { unwrapResult, dehoistResult } from './proxiedResult';
+import defaultMergedResolver from './defaultMergedResolver';
+
+export function createMergedResolver({
+ fromPath,
+ dehoist,
+ delimeter = '__gqltf__',
+}: {
+ fromPath?: Array;
+ dehoist?: boolean;
+ delimeter?: string;
+}): IFieldResolver {
+ const parentErrorResolver: IFieldResolver = (
+ parent,
+ args,
+ context,
+ info,
+ ) =>
+ parent instanceof Error
+ ? parent
+ : defaultMergedResolver(parent, args, context, info);
+
+ const unwrappingResolver: IFieldResolver =
+ fromPath != null
+ ? (parent, args, context, info) =>
+ parentErrorResolver(
+ unwrapResult(parent, info, fromPath),
+ args,
+ context,
+ info,
+ )
+ : parentErrorResolver;
+
+ const dehoistingResolver: IFieldResolver = dehoist
+ ? (parent, args, context, info) =>
+ unwrappingResolver(
+ dehoistResult(parent, delimeter),
+ args,
+ context,
+ info,
+ )
+ : unwrappingResolver;
+
+ const noParentResolver: IFieldResolver = (
+ parent,
+ args,
+ context,
+ info,
+ ) => (parent ? dehoistingResolver(parent, args, context, info) : {});
+
+ return noParentResolver;
+}
diff --git a/src/stitch/defaultMergedResolver.ts b/src/stitch/defaultMergedResolver.ts
new file mode 100644
index 00000000000..5a55cd40491
--- /dev/null
+++ b/src/stitch/defaultMergedResolver.ts
@@ -0,0 +1,38 @@
+import { defaultFieldResolver } from 'graphql';
+
+import { IGraphQLToolsResolveInfo } from '../Interfaces';
+import { handleResult } from '../delegate/checkResultAndHandleErrors';
+
+import { getErrors, getSubschema } from './proxiedResult';
+import { getResponseKeyFromInfo } from './getResponseKeyFromInfo';
+
+/**
+ * Resolver that knows how to:
+ * a) handle aliases for proxied schemas
+ * b) handle errors from proxied schemas
+ * c) handle external to internal enum coversion
+ */
+export default function defaultMergedResolver(
+ parent: Record