From da25bfcc5b14fa0d2e726dc4e41e58d3b478562f Mon Sep 17 00:00:00 2001 From: Kamil Kisiela Date: Thu, 26 Mar 2020 11:14:35 +0100 Subject: [PATCH 001/250] Prepare for v5 --- .gitignore | 14 ++++++++++---- CHANGELOG.md | 2 ++ package.json | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 7ac4c259186..5c899fd7571 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,13 @@ -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* diff --git a/CHANGELOG.md b/CHANGELOG.md index 545599261a0..1a4e2ec26c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Change log +### Next + ### 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) diff --git a/package.json b/package.json index 3ec61534073..ddd8c1fbd00 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "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", From 4e02f179cf41e917a2c4182eb39508cc749bf5fb Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 26 Mar 2020 10:24:26 +0000 Subject: [PATCH 002/250] Update dependency gatsby-theme-apollo-docs to v4 --- docs/package-lock.json | 3231 +++++++++++++++++++++++++++++----------- docs/package.json | 2 +- 2 files changed, 2392 insertions(+), 841 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 1e85f243d92..f6fc68504c0 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -23,20 +23,31 @@ "@babel/highlight": "^7.0.0" } }, - "@babel/core": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.4.tgz", - "integrity": "sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ==", + "@babel/compat-data": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz", + "integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==", "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helpers": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4", + "browserslist": "^4.9.1", + "invariant": "^2.2.4", + "semver": "^5.5.0" + } + }, + "@babel/core": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz", + "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helpers": "^7.8.4", + "@babel/parser": "^7.8.4", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.4", + "@babel/types": "^7.8.3", "convert-source-map": "^1.7.0", "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", "json5": "^2.1.0", "lodash": "^4.17.13", "resolve": "^1.3.2", @@ -45,11 +56,99 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", "requires": { - "@babel/highlight": "^7.0.0" + "@babel/highlight": "^7.8.3" + } + }, + "@babel/generator": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", + "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", + "requires": { + "@babel/types": "^7.9.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==" + }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/traverse": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", + "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" } }, "debug": { @@ -103,12 +202,70 @@ } }, "@babel/helper-builder-react-jsx": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.7.4.tgz", - "integrity": "sha512-kvbfHJNN9dg4rkEM4xn1s8d1/h6TYNvajy9L1wx4qLn9HFg0IkTsQi4rfBe92nxrPUFcMsHoMV+8rU7MJb3fCA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz", + "integrity": "sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw==", "requires": { - "@babel/types": "^7.7.4", - "esutils": "^2.0.0" + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/types": "^7.9.0" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", + "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-builder-react-jsx-experimental": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.0.tgz", + "integrity": "sha512-3xJEiyuYU4Q/Ar9BsHisgdxZsRlsShMe90URZ0e6przL26CCs8NJbDoxH94kKT17PcxlMhsCAwZd90evCo26VQ==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-module-imports": "^7.8.3", + "@babel/types": "^7.9.0" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", + "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-imports": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-call-delegate": { @@ -217,124 +374,274 @@ } } }, - "@babel/helper-create-class-features-plugin": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.7.4.tgz", - "integrity": "sha512-l+OnKACG4uiDHQ/aJT8dwpR+LhCJALxL0mJ6nzjB25e5IPwqV1VOsY7ah6UB1DG+VOXAIMtuC54rFJGiHkxjgA==", - "requires": { - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-member-expression-to-functions": "^7.7.4", - "@babel/helper-optimise-call-expression": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz", - "integrity": "sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A==", - "requires": { - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.6.0" - } - }, - "@babel/helper-define-map": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.7.4.tgz", - "integrity": "sha512-v5LorqOa0nVQUvAUTUF3KPastvUt/HzByXNamKQ6RdJRTV7j8rLL+WB5C/MzzWAwOomxDhYFb1wLLxHqox86lg==", - "requires": { - "@babel/helper-function-name": "^7.7.4", - "@babel/types": "^7.7.4", - "lodash": "^4.17.13" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.4.tgz", - "integrity": "sha512-2/SicuFrNSXsZNBxe5UGdLr+HZg+raWBLE9vC98bdYOKX/U6PY0mdGlYUJdtTDPSU0Lw0PNbKKDpwYHJLn2jLg==", + "@babel/helper-compilation-targets": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", + "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", "requires": { - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/compat-data": "^7.8.6", + "browserslist": "^4.9.1", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" } }, - "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "@babel/helper-create-class-features-plugin": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz", + "integrity": "sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg==", "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-split-export-declaration": "^7.8.3" }, "dependencies": { - "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" + "@babel/highlight": "^7.8.3" } - } - } - }, - "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", - "requires": { - "@babel/types": "^7.7.4" - }, - "dependencies": { - "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + }, + "@babel/generator": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", + "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "requires": { - "esutils": "^2.0.2", + "@babel/types": "^7.9.0", + "jsesc": "^2.5.1", "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" + "source-map": "^0.5.0" } - } - } - }, - "@babel/helper-hoist-variables": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.4.tgz", - "integrity": "sha512-wQC4xyvc1Jo/FnLirL6CEgPgPCa8M74tOdjWpRhQYapz5JC7u3NYU1zCVoVAGCE3EaIP9T1A3iW0WLJ+reZlpQ==", - "requires": { - "@babel/types": "^7.7.4" - }, - "dependencies": { - "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } - } - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz", - "integrity": "sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw==", - "requires": { - "@babel/types": "^7.7.4" - }, - "dependencies": { - "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", + "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", + "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + }, + "@babel/helper-replace-supers": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", + "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==" + }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/traverse": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", + "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz", + "integrity": "sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A==", + "requires": { + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + } + }, + "@babel/helper-define-map": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.7.4.tgz", + "integrity": "sha512-v5LorqOa0nVQUvAUTUF3KPastvUt/HzByXNamKQ6RdJRTV7j8rLL+WB5C/MzzWAwOomxDhYFb1wLLxHqox86lg==", + "requires": { + "@babel/helper-function-name": "^7.7.4", + "@babel/types": "^7.7.4", + "lodash": "^4.17.13" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.4.tgz", + "integrity": "sha512-2/SicuFrNSXsZNBxe5UGdLr+HZg+raWBLE9vC98bdYOKX/U6PY0mdGlYUJdtTDPSU0Lw0PNbKKDpwYHJLn2jLg==", + "requires": { + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "requires": { + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-hoist-variables": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.4.tgz", + "integrity": "sha512-wQC4xyvc1Jo/FnLirL6CEgPgPCa8M74tOdjWpRhQYapz5JC7u3NYU1zCVoVAGCE3EaIP9T1A3iW0WLJ+reZlpQ==", + "requires": { + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz", + "integrity": "sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw==", + "requires": { + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } } @@ -576,6 +883,11 @@ } } }, + "@babel/helper-validator-identifier": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==" + }, "@babel/helper-wrap-function": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.7.4.tgz", @@ -686,99 +998,107 @@ } }, "@babel/helpers": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.7.4.tgz", - "integrity": "sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", + "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", "requires": { - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.9.0", + "@babel/types": "^7.9.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", + "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "requires": { - "@babel/types": "^7.7.4", + "@babel/types": "^7.9.0", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.4.tgz", - "integrity": "sha512-jIwvLO0zCL+O/LmEJQjWA75MQTWwx3c3u2JOTDK5D3/9egrWRRA0/0hk9XXywYnXZVVpzrBYeIQTmhwUaePI9g==" + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==" }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", - "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", + "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.0", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "requires": { - "@babel/highlight": "^7.0.0" - } - } } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", "requires": { - "esutils": "^2.0.2", + "@babel/helper-validator-identifier": "^7.9.0", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } @@ -1032,13 +1352,44 @@ } } }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", + "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } + } + }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.4.tgz", - "integrity": "sha512-rnpnZR3/iWKmiQyJ3LKJpSwLDcX/nSXhdLk4Aq/tXOApIvyu7qoabrige0ylsAJffaUC51WiBu209Q0U+86OWQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA==", "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.7.4" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + } } }, "@babel/plugin-proposal-optional-catch-binding": { @@ -1100,11 +1451,18 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.7.4.tgz", - "integrity": "sha512-wuy6fiMe9y7HeZBWXYCGt2RGxZOj0BImZ9EyXJVnVGBKO/Br592rbR3rtIQn0eQhAk9vqaKP5n8tVqEFBQMfLg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", + "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } } }, "@babel/plugin-syntax-nullish-coalescing-operator": { @@ -1122,6 +1480,21 @@ } } }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", + "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } + } + }, "@babel/plugin-syntax-object-rest-spread": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz", @@ -1162,11 +1535,18 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.7.4.tgz", - "integrity": "sha512-77blgY18Hud4NM1ggTA8xVT/dBENQf17OpiToSa2jSmEY3fWXD2jwrdVlO4kq5yzUTeF15WSQ6b4fByNvJcjpQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.8.3.tgz", + "integrity": "sha512-GO1MQ/SGGGoiEXY0e0bSpHimJvxqB7lktLLIq2pv8xG7WZ8IMEle74jIe1FhprHBWjwjZtXHkycDLZXIWM5Wfg==", "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } } }, "@babel/plugin-transform-arrow-functions": { @@ -1463,22 +1843,100 @@ } }, "@babel/plugin-transform-react-constant-elements": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.7.4.tgz", - "integrity": "sha512-U6XkHZ8RnmeEb8jBUOpeo6oFka5RhLgxAVvK4/fBbwoYlsHQYLb8I37ymTPDVsrWjqb94+hueuWQA/1OAA4rAQ==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.9.0.tgz", + "integrity": "sha512-wXMXsToAUOxJuBBEHajqKLFWcCkOSLshTI2ChCFFj1zDd7od4IOxiwLCOObNUvOpkxLpjIuaIdBMmNt6ocCPAw==", "requires": { - "@babel/helper-annotate-as-pure": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz", + "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } } }, "@babel/plugin-transform-react-jsx": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.7.7.tgz", - "integrity": "sha512-SlPjWPbva2+7/ZJbGcoqjl4LsQaLpKEzxW9hcxU7675s24JmdotJOSJ4cgAbV82W3FcZpHIGmRZIlUL8ayMvjw==", + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.4.tgz", + "integrity": "sha512-Mjqf3pZBNLt854CK0C/kRuXAnE6H/bo7xYojP+WGtX8glDGSibcwnsWwhwoSuRg0+EBnxPC1ouVnuetUIlPSAw==", "requires": { - "@babel/helper-builder-react-jsx": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.7.4" + "@babel/helper-builder-react-jsx": "^7.9.0", + "@babel/helper-builder-react-jsx-experimental": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.9.0.tgz", + "integrity": "sha512-tK8hWKrQncVvrhvtOiPpKrQjfNX3DtkNLSX4ObuGcpS9p0QrGetKmlySIGR07y48Zft8WVgPakqd/bk46JrMSw==", + "requires": { + "@babel/helper-builder-react-jsx-experimental": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.9.0.tgz", + "integrity": "sha512-K2ObbWPKT7KUTAoyjCsFilOkEgMvFG+y0FqOl6Lezd0/13kMkkjHskVsZvblRPj1PHA44PrToaZANrryppzTvQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.9.0.tgz", + "integrity": "sha512-K6m3LlSnTSfRkM6FcRk8saNEeaeyG5k7AVkBU2bZK3+1zdkSED3qNdsWrUgQBeTVD2Tp3VMmerxVO2yM5iITmw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } } }, "@babel/plugin-transform-regenerator": { @@ -1576,13 +2034,20 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.7.4.tgz", - "integrity": "sha512-X8e3tcPEKnwwPVG+vP/vSqEShkwODOEeyQGod82qrIuidwIrfnsGn11qPM1jBLF4MqguTXXYzm58d0dY+/wdpg==", + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.4.tgz", + "integrity": "sha512-yeWeUkKx2auDbSxRe8MusAG+n4m9BFY/v+lPjmQDgOFX5qnySkUY5oXzkp6FwPdsYqnKay6lorXYdC0n3bZO7w==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-typescript": "^7.7.4" + "@babel/helper-create-class-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-typescript": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } } }, "@babel/plugin-transform-unicode-regex": { @@ -1718,6 +2183,18 @@ } } }, + "@babel/preset-modules": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", + "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, "@babel/preset-react": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.7.4.tgz", @@ -1796,12 +2273,19 @@ } }, "@babel/preset-typescript": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.7.7.tgz", - "integrity": "sha512-Apg0sCTovsSA+pEaI8efnA44b9x4X/7z4P8vsWMiN8rSUaM4y4+Shl5NMWnMl6njvt96+CEb6jwpXAKYAVCSQA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.9.0.tgz", + "integrity": "sha512-S4cueFnGrIbvYJgwsVFKdvOmpiL0XGw9MFW9D0vgRys5g36PBhZRL8NX8Gr2akz8XRtzq6HuDXPD/1nniagNUg==", "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.7.4" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-transform-typescript": "^7.9.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } } }, "@babel/runtime": { @@ -1929,19 +2413,24 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" } } }, "@emotion/cache": { - "version": "10.0.27", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.27.tgz", - "integrity": "sha512-Zp8BEpbMunFsTcqAK4D7YTm3MvCp1SekflSLJH8lze2fCcSZ/yMkXHo8kb3t1/1Tdd3hAqf3Fb7z9VZ+FMiC9w==", + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", + "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", "requires": { "@emotion/sheet": "0.9.4", "@emotion/stylis": "0.8.5", @@ -1950,9 +2439,9 @@ } }, "@emotion/core": { - "version": "10.0.27", - "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.27.tgz", - "integrity": "sha512-XbD5R36pVbohQMnKfajHv43g8EbN4NHdF6Zh9zg/C0nr0jqwOw3gYnC07Xj3yG43OYSRyrGsoQ5qPwc8ycvLZw==", + "version": "10.0.28", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.28.tgz", + "integrity": "sha512-pH8UueKYO5jgg0Iq+AmCLxBsvuGtvlmiDCOuv8fGNYn3cowFpLN98L8zO56U0H1PjDIyAlXymgL3Wu7u7v6hbA==", "requires": { "@babel/runtime": "^7.5.5", "@emotion/cache": "^10.0.27", @@ -1963,12 +2452,17 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" } } }, @@ -1983,14 +2477,14 @@ } }, "@emotion/hash": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.4.tgz", - "integrity": "sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A==" + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, "@emotion/is-prop-valid": { - "version": "0.8.6", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.6.tgz", - "integrity": "sha512-mnZMho3Sq8BfzkYYRVc8ilQTnc8U02Ytp6J1AwM6taQStZ3AhsEJBX2LzhA/LJirNCwM2VtHL3VFIZ+sNJUgUQ==", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", "requires": { "@emotion/memoize": "0.7.4" } @@ -2001,11 +2495,11 @@ "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" }, "@emotion/serialize": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.15.tgz", - "integrity": "sha512-YE+qnrmGwyR+XB5j7Bi+0GT1JWsdcjM/d4POu+TXkcnrRs4RFCCsi3d/Ebf+wSStHqAlTT2+dfd+b9N9EO2KBg==", + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", + "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", "requires": { - "@emotion/hash": "0.7.4", + "@emotion/hash": "0.8.0", "@emotion/memoize": "0.7.4", "@emotion/unitless": "0.7.5", "@emotion/utils": "0.11.3", @@ -2027,23 +2521,28 @@ } }, "@emotion/styled-base": { - "version": "10.0.27", - "resolved": "https://registry.npmjs.org/@emotion/styled-base/-/styled-base-10.0.27.tgz", - "integrity": "sha512-ufHM/HhE3nr309hJG9jxuFt71r6aHn7p+bwXduFxcwPFEfBIqvmZUMtZ9YxIsY61PVwK3bp4G1XhaCzy9smVvw==", + "version": "10.0.31", + "resolved": "https://registry.npmjs.org/@emotion/styled-base/-/styled-base-10.0.31.tgz", + "integrity": "sha512-wTOE1NcXmqMWlyrtwdkqg87Mu6Rj1MaukEoEmEkHirO5IoHDJ8LgCQL4MjJODgxWxXibGR3opGp1p7YvkNEdXQ==", "requires": { "@babel/runtime": "^7.5.5", - "@emotion/is-prop-valid": "0.8.6", + "@emotion/is-prop-valid": "0.8.8", "@emotion/serialize": "^0.11.15", "@emotion/utils": "0.11.3" }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" } } }, @@ -2120,39 +2619,54 @@ } }, "@mdx-js/mdx": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.5.3.tgz", - "integrity": "sha512-XxnOvyCQKri52tgaCXbV5NWnZGqgRsRifa/yJrxwWa6QG3vdFiEi/xokBHBf/62RCKRK4+QmbM4dSl0fgWIRNA==", - "requires": { - "@babel/core": "7.7.4", - "@babel/plugin-syntax-jsx": "7.7.4", - "@babel/plugin-syntax-object-rest-spread": "7.7.4", - "@mdx-js/util": "^1.5.3", - "babel-plugin-apply-mdx-type-prop": "^1.5.3", - "babel-plugin-extract-import-names": "^1.5.3", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.5.7.tgz", + "integrity": "sha512-db1E3P0HCgSUX768Y/jIcr5h41VR5AsvaOmPTydltNM4R8Uh863IqDvnkpa7l829bY/tp6wrMBWM2NH0oLuxHw==", + "requires": { + "@babel/core": "7.8.4", + "@babel/plugin-syntax-jsx": "7.8.3", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@mdx-js/util": "^1.5.7", + "babel-plugin-apply-mdx-type-prop": "^1.5.7", + "babel-plugin-extract-import-names": "^1.5.7", "camelcase-css": "2.0.1", - "detab": "2.0.2", + "detab": "2.0.3", "hast-util-raw": "5.0.1", "lodash.uniq": "4.5.0", - "mdast-util-to-hast": "6.0.2", - "remark-mdx": "^1.5.3", + "mdast-util-to-hast": "7.0.0", + "remark-mdx": "^1.5.7", "remark-parse": "7.0.2", "remark-squeeze-paragraphs": "3.0.4", "style-to-object": "0.3.0", "unified": "8.4.2", - "unist-builder": "1.0.4", - "unist-util-visit": "2.0.1" + "unist-builder": "2.0.3", + "unist-util-visit": "2.0.2" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + } } }, "@mdx-js/react": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.5.3.tgz", - "integrity": "sha512-5bVLUsZybjmeYL8l4Uh/ysE8vMn0Vb0GKzki/LicaDHJvXr/N4Tjj0gT4tk1OzhcC5nGQAQGIyQMW5pvIjp9XQ==" + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.5.7.tgz", + "integrity": "sha512-OxX/GKyVlqY7WqyRcsIA/qr7i1Xq3kAVNUhSSnL1mfKKNKO+hwMWcZX4WS2OItLtoavA2/8TVDHpV/MWKWyfvw==" }, "@mdx-js/util": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.5.3.tgz", - "integrity": "sha512-OXeOtHO+eN50QlIkm4Vj4vqNGtowv4FH9L21WvcbEM0eeZrb7aANiFTN70lBQEXcucxCMRkd/6IA9LxhotZEQw==" + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.5.7.tgz", + "integrity": "sha512-SV+V8A+Y33pmVT/LWk/2y51ixIyA/QH1XL+nrWAhoqre1rFtxOEZ4jr0W+bKZpeahOvkn/BQTheK+dRty9o/ig==" }, "@mikaelkristiansson/domready": { "version": "1.0.10", @@ -2456,9 +2970,9 @@ } }, "@types/classnames": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.9.tgz", - "integrity": "sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A==" + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz", + "integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==" }, "@types/color-name": { "version": "1.1.1", @@ -2481,9 +2995,9 @@ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==" }, "@types/estree": { - "version": "0.0.42", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.42.tgz", - "integrity": "sha512-K1DPVvnBCPxzD+G51/cxVIoc2X8uUVl1zpJeE6iKcgHMj4+tbat5Xu4TjV7v2QSDbIeAfLi2hIk+u2+s0MlpUQ==" + "version": "0.0.44", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", + "integrity": "sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==" }, "@types/events": { "version": "3.0.0", @@ -2912,9 +3426,9 @@ } }, "acorn": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", - "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==" + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==" }, "acorn-jsx": { "version": "5.2.0", @@ -3185,9 +3699,9 @@ } }, "array-iterate": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-1.1.3.tgz", - "integrity": "sha512-7MIv7HE9MuzfK6B2UnWv07oSHBLOaY1UUXAxZ07bIeRM+4IkPTlveMDs9MY//qvxPZPSvCn2XV4bmtQgSkVodg==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-1.1.4.tgz", + "integrity": "sha512-sNRaPGh9nnmdC8Zf+pT3UqP8rnWj5Hf9wiFGsX3wUQ2yVSIhO2ShFwCoceIPpB41QF6i2OEmrHmCo36xronCVA==" }, "array-map": { "version": "0.0.0", @@ -3432,9 +3946,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", - "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" }, "axios": { "version": "0.19.2", @@ -3564,12 +4078,19 @@ } }, "babel-plugin-apply-mdx-type-prop": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.5.3.tgz", - "integrity": "sha512-9G+V0R8Jx56nHdEnWvRmSN//rFXMDiBZynu9JPuu3KVUhZhaJMgx5CTiXcdR2P//c85Q/IuwPbH0vIGrjdSq8A==", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.5.7.tgz", + "integrity": "sha512-SUDwTmMmxzaAZ1YfAPnL2UI3q/JEs+fekx/QTZYEgK+cVGMwS/PrCeK9UDlTHOYJr9b4mieR+iLhm43jrav2WA==", "requires": { - "@babel/helper-plugin-utils": "7.0.0", - "@mdx-js/util": "^1.5.3" + "@babel/helper-plugin-utils": "7.8.3", + "@mdx-js/util": "^1.5.7" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } } }, "babel-plugin-dynamic-import-node": { @@ -3581,14 +4102,14 @@ } }, "babel-plugin-emotion": { - "version": "10.0.27", - "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.27.tgz", - "integrity": "sha512-SUNYcT4FqhOqvwv0z1oeYhqgheU8qrceLojuHyX17ngo7WtWqN5I9l3IGHzf21Xraj465CVzF4IvOlAF+3ed0A==", + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.29.tgz", + "integrity": "sha512-7Jpi1OCxjyz0k163lKtqP+LHMg5z3S6A7vMBfHnF06l2unmtsOmFDzZBpGf0CWo1G4m8UACfVcDJiSiRuu/cSw==", "requires": { "@babel/helper-module-imports": "^7.0.0", - "@emotion/hash": "0.7.4", + "@emotion/hash": "0.8.0", "@emotion/memoize": "0.7.4", - "@emotion/serialize": "^0.11.15", + "@emotion/serialize": "^0.11.16", "babel-plugin-macros": "^2.0.0", "babel-plugin-syntax-jsx": "^6.18.0", "convert-source-map": "^1.5.0", @@ -3598,13 +4119,20 @@ } }, "babel-plugin-extract-import-names": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.5.3.tgz", - "integrity": "sha512-UPgDHjNb4hr2xYRWO8C8JPX7GO+q3gluKd3pkcmVcd1gn9bdO7/yE5FKnYe1UkCPY7PhEUOpEzHCSuIy3GMpsQ==", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.5.7.tgz", + "integrity": "sha512-kZX4g9ehTyxjdbq2rb8wW307+jNu5z3KllYs8cnbapSwclT9wBErJoqvKKZAkuiaufp0r+7WaIvjhKtJ7QlG3A==", "requires": { - "@babel/helper-plugin-utils": "7.0.0" - } - }, + "@babel/helper-plugin-utils": "7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } + } + }, "babel-plugin-macros": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", @@ -3740,9 +4268,9 @@ "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, "bail": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.4.tgz", - "integrity": "sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==" }, "balanced-match": { "version": "1.0.0", @@ -4259,6 +4787,11 @@ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, "buffer-es6": { "version": "4.9.3", "resolved": "https://registry.npmjs.org/buffer-es6/-/buffer-es6-4.9.3.tgz", @@ -4485,9 +5018,9 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "ccount": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.4.tgz", - "integrity": "sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz", + "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==" }, "chalk": { "version": "2.4.2", @@ -4530,24 +5063,24 @@ "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" }, "character-entities": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.3.tgz", - "integrity": "sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w==" + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" }, "character-entities-html4": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.3.tgz", - "integrity": "sha512-SwnyZ7jQBCRHELk9zf2CN5AnGEc2nA+uKMZLHvcqhpPprjkYhiLn0DywMHgN5ttFZuITMATbh68M6VIVKwJbcg==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==" }, "character-entities-legacy": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz", - "integrity": "sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" }, "character-reference-invalid": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz", - "integrity": "sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" }, "chardet": { "version": "0.7.0", @@ -4731,9 +5264,9 @@ "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" }, "clean-css": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", + "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", "requires": { "source-map": "~0.6.0" }, @@ -4877,9 +5410,9 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" }, "clipboard": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", - "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", + "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", "optional": true, "requires": { "good-listener": "^1.2.2", @@ -5007,9 +5540,9 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collapse-white-space": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.5.tgz", - "integrity": "sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==" }, "collection-visit": { "version": "1.0.0", @@ -5066,9 +5599,9 @@ } }, "comma-separated-tokens": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz", - "integrity": "sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==" }, "command-exists": { "version": "1.2.8", @@ -5282,9 +5815,9 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "copy-to-clipboard": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz", - "integrity": "sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", "requires": { "toggle-selection": "^1.0.6" } @@ -6354,9 +6887,9 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "detab": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.2.tgz", - "integrity": "sha512-Q57yPrxScy816TTE1P/uLRXLDKjXhvYTbfxS/e6lPD+YrqghbsMlGB9nQzj/zVtSPaF0DFPSdO916EWO4sQUyQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.3.tgz", + "integrity": "sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A==", "requires": { "repeat-string": "^1.5.4" } @@ -8005,14 +8538,14 @@ } }, "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" }, "dependencies": { "debug": { @@ -8023,6 +8556,19 @@ "ms": "2.0.0" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "requires": { + "minimist": "^1.2.5" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -8111,9 +8657,9 @@ } }, "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "requires": { "pend": "~1.2.0" } @@ -9640,55 +10186,65 @@ } }, "gatsby-plugin-emotion": { - "version": "4.1.21", - "resolved": "https://registry.npmjs.org/gatsby-plugin-emotion/-/gatsby-plugin-emotion-4.1.21.tgz", - "integrity": "sha512-jFb+3AqSrGZpN3o0Q2mhVN70YTAENCcNL/EE6RjUP+UwtPhaj8e+WHDI0IEq4IYKooa3DtCW4y95bmcd/5WDXQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/gatsby-plugin-emotion/-/gatsby-plugin-emotion-4.2.1.tgz", + "integrity": "sha512-ygXxkpnWJdDOAgb1XA9TbVCRLkaAYTFLTsqVQXMBhnrknb5iPNO+MP0fZ5LRqWgBALyJ629nxs0efUpnT/RSWw==", "requires": { - "@babel/runtime": "^7.7.6", - "@emotion/babel-preset-css-prop": "^10.0.23" + "@babel/runtime": "^7.8.7", + "@emotion/babel-preset-css-prop": "^10.0.27" }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" } } }, "gatsby-plugin-less": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/gatsby-plugin-less/-/gatsby-plugin-less-3.0.18.tgz", - "integrity": "sha512-41/OcNeAdHPgQMCkKQtxV+23NPdLv/ZyvGGzKR4Ij/nLXMP097ECAJTZSSXTxuFMquWYla81GVcyUnlRoMbDWw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gatsby-plugin-less/-/gatsby-plugin-less-3.1.1.tgz", + "integrity": "sha512-H0LQXy2DpwD1UW3bEuE8RHk2yNfOcZGFjzYH9URPUAzxuOo6bhPyNxKJ8tLcvarqwYiJIWFFivDMv0IqgBJ16Q==", "requires": { - "@babel/runtime": "^7.7.6", + "@babel/runtime": "^7.8.7", "less-loader": "^5.0.0" }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" } } }, "gatsby-plugin-mdx": { - "version": "1.0.67", - "resolved": "https://registry.npmjs.org/gatsby-plugin-mdx/-/gatsby-plugin-mdx-1.0.67.tgz", - "integrity": "sha512-PG1j8voD1hCBrj1blw1CqiJlXMv+QATVllvqClKl47GfCOZAuVj+VMN6O9WbImc5sVUw/SuvfCTOpk9H7gHlPw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gatsby-plugin-mdx/-/gatsby-plugin-mdx-1.1.3.tgz", + "integrity": "sha512-6U0dMsEFxiGGdd8k9j7EL8lSlMCrsTytrqDCGNdpKEyyPsPjPCroVmjuHFwG3nNeKwi0m/bPaVSOoIbkIcmPaw==", "requires": { - "@babel/core": "^7.7.5", - "@babel/generator": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.7.4", - "@babel/preset-env": "^7.7.6", - "@babel/preset-react": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/core": "^7.8.7", + "@babel/generator": "^7.8.8", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.8.3", + "@babel/preset-env": "^7.8.7", + "@babel/preset-react": "^7.8.3", + "@babel/types": "^7.8.7", "camelcase-css": "^2.0.1", "change-case": "^3.1.0", "core-js": "2", @@ -9697,12 +10253,12 @@ "escape-string-regexp": "^1.0.5", "eval": "^0.1.4", "fs-extra": "^8.1.0", - "gatsby-core-utils": "^1.0.26", + "gatsby-core-utils": "^1.1.1", "gray-matter": "^4.0.2", - "json5": "^2.1.1", - "loader-utils": "^1.2.3", + "json5": "^2.1.2", + "loader-utils": "^1.4.0", "lodash": "^4.17.15", - "mdast-util-to-string": "^1.0.7", + "mdast-util-to-string": "^1.1.0", "mdast-util-toc": "^3.1.0", "mime": "^2.4.4", "p-queue": "^5.0.0", @@ -9720,77 +10276,865 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/core": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", + "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.0", + "@babel/parser": "^7.9.0", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.0", + "@babel/types": "^7.9.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", + "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", + "requires": { + "@babel/types": "^7.9.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", + "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", + "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", + "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-regex": "^7.8.3", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-define-map": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", + "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", + "requires": { + "@babel/helper-function-name": "^7.8.3", + "@babel/types": "^7.8.3", + "lodash": "^4.17.13" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", + "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", + "requires": { + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", + "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", + "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-imports": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-transforms": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", + "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "requires": { + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.9.0", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", + "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + }, + "@babel/helper-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", + "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", + "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-wrap-function": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-replace-supers": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", + "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/helper-simple-access": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", + "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "requires": { + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-wrap-function": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", + "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", + "requires": { + "@babel/helper-function-name": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", + "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-remap-async-to-generator": "^7.8.3", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", + "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", + "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", + "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", + "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.8.8", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", + "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", + "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", + "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", + "requires": { + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-remap-async-to-generator": "^7.8.3" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", + "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", + "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz", + "integrity": "sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-define-map": "^7.8.3", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-split-export-declaration": "^7.8.3", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", + "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz", + "integrity": "sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", + "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", + "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", + "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz", + "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", + "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", + "requires": { + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", + "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", + "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz", + "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==", + "requires": { + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz", + "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==", + "requires": { + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-simple-access": "^7.8.3", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz", + "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==", + "requires": { + "@babel/helper-hoist-variables": "^7.8.3", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz", + "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==", + "requires": { + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", + "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.8.3" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", + "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", + "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.3" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz", + "integrity": "sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg==", + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", + "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", + "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", + "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", + "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", + "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", + "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-regex": "^7.8.3" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", + "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", + "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", + "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/preset-env": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", + "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==", + "requires": { + "@babel/compat-data": "^7.9.0", + "@babel/helper-compilation-targets": "^7.8.7", + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-proposal-async-generator-functions": "^7.8.3", + "@babel/plugin-proposal-dynamic-import": "^7.8.3", + "@babel/plugin-proposal-json-strings": "^7.8.3", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-numeric-separator": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.9.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", + "@babel/plugin-proposal-optional-chaining": "^7.9.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.8.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.8.3", + "@babel/plugin-transform-async-to-generator": "^7.8.3", + "@babel/plugin-transform-block-scoped-functions": "^7.8.3", + "@babel/plugin-transform-block-scoping": "^7.8.3", + "@babel/plugin-transform-classes": "^7.9.0", + "@babel/plugin-transform-computed-properties": "^7.8.3", + "@babel/plugin-transform-destructuring": "^7.8.3", + "@babel/plugin-transform-dotall-regex": "^7.8.3", + "@babel/plugin-transform-duplicate-keys": "^7.8.3", + "@babel/plugin-transform-exponentiation-operator": "^7.8.3", + "@babel/plugin-transform-for-of": "^7.9.0", + "@babel/plugin-transform-function-name": "^7.8.3", + "@babel/plugin-transform-literals": "^7.8.3", + "@babel/plugin-transform-member-expression-literals": "^7.8.3", + "@babel/plugin-transform-modules-amd": "^7.9.0", + "@babel/plugin-transform-modules-commonjs": "^7.9.0", + "@babel/plugin-transform-modules-systemjs": "^7.9.0", + "@babel/plugin-transform-modules-umd": "^7.9.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", + "@babel/plugin-transform-new-target": "^7.8.3", + "@babel/plugin-transform-object-super": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.8.7", + "@babel/plugin-transform-property-literals": "^7.8.3", + "@babel/plugin-transform-regenerator": "^7.8.7", + "@babel/plugin-transform-reserved-words": "^7.8.3", + "@babel/plugin-transform-shorthand-properties": "^7.8.3", + "@babel/plugin-transform-spread": "^7.8.3", + "@babel/plugin-transform-sticky-regex": "^7.8.3", + "@babel/plugin-transform-template-literals": "^7.8.3", + "@babel/plugin-transform-typeof-symbol": "^7.8.4", + "@babel/plugin-transform-unicode-regex": "^7.8.3", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.9.0", + "browserslist": "^4.9.1", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + }, + "dependencies": { + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz", + "integrity": "sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0" + } + } + } + }, + "@babel/preset-react": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.9.4.tgz", + "integrity": "sha512-AxylVB3FXeOTQXNXyiuAQJSvss62FEotbX2Pzx3K/7c+MKJMdSg6Ose6QYllkdCFA8EInCJVw7M/o5QbLuA4ZQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-transform-react-display-name": "^7.8.3", + "@babel/plugin-transform-react-jsx": "^7.9.4", + "@babel/plugin-transform-react-jsx-development": "^7.9.0", + "@babel/plugin-transform-react-jsx-self": "^7.9.0", + "@babel/plugin-transform-react-jsx-source": "^7.9.0" + } + }, + "@babel/runtime": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/traverse": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", + "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "gatsby-core-utils": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-1.1.1.tgz", + "integrity": "sha512-EboPcBx37YQVUKN9JH753S54nDxjRmOefbR0i08KTmaVgQ1lZnDXJr8JfrImmMqupZlOkPQX1mWlXfp+r1jGhA==", + "requires": { + "ci-info": "2.0.0", + "configstore": "^5.0.1", + "node-object-hash": "^2.0.0" + } + }, + "json5": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", + "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", "requires": { - "@babel/highlight": "^7.0.0" + "minimist": "^1.2.5" } }, - "@babel/core": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.7.tgz", - "integrity": "sha512-jlSjuj/7z138NLZALxVgrx13AOtqip42ATZP7+kYl53GvDV6+4dCek1mVUo8z8c8Xnw/mx2q3d9HWh3griuesQ==", + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.7", - "@babel/helpers": "^7.7.4", - "@babel/parser": "^7.7.7", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "json5": "^2.1.0", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" }, "dependencies": { - "@babel/generator": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.7.tgz", - "integrity": "sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ==", + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "requires": { - "@babel/types": "^7.7.4", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" + "minimist": "^1.2.0" } } } }, - "@babel/parser": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.7.tgz", - "integrity": "sha512-WtTZMZAZLbeymhkd/sEaPD8IQyGAhmuTuvTzLiCFM7iXiVdY0gc0IaI+cW0fh1BnSMbJSzXX6/fHllgHKwHhXw==" + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", "requires": { - "ms": "^2.1.1" + "regenerate": "^1.4.0" } }, - "gatsby-core-utils": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-1.0.26.tgz", - "integrity": "sha512-NPflmXmyTcg3x2mp6cqp/51QeAHRKepfbf0X4erDsnVlewFJuGTe+25ZJvWkkwU2g1cPAxuwzlPe0jOL92iU4A==", + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, + "regenerator-transform": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", + "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", "requires": { - "ci-info": "2.0.0", - "node-object-hash": "^2.0.0" + "@babel/runtime": "^7.8.4", + "private": "^0.1.8" } }, - "json5": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", - "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "regexpu-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", + "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", "requires": { - "minimist": "^1.2.0" + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } } }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" + }, "unist-util-visit": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", @@ -9867,20 +11211,25 @@ } }, "gatsby-plugin-react-helmet": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/gatsby-plugin-react-helmet/-/gatsby-plugin-react-helmet-3.1.21.tgz", - "integrity": "sha512-6LZ2LEYTwqD+ZqyCH55mVpk2xEXbQoCTfijP1W4ZCQsKtpWGJP+vyd6b96FWVyEb2k5LsQ1u+jk4R8xXULSX+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/gatsby-plugin-react-helmet/-/gatsby-plugin-react-helmet-3.2.1.tgz", + "integrity": "sha512-5oarZdVvp3k3keG26eVFagVHLYw7wCGs/MXRYQg8MEyJewU3X4Uc0eo7qu4TM5EIuZ2ekaL14r86RB6RM5TORA==", "requires": { - "@babel/runtime": "^7.7.6" + "@babel/runtime": "^7.8.7" }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" } } }, @@ -9928,25 +11277,30 @@ } }, "gatsby-remark-autolink-headers": { - "version": "2.1.23", - "resolved": "https://registry.npmjs.org/gatsby-remark-autolink-headers/-/gatsby-remark-autolink-headers-2.1.23.tgz", - "integrity": "sha512-OXZi/XlA8o0NmZLkPoMqM03cXNIymugCRfuqpZxZTXqGObjjbYb+YbxLVSbogbJAVJdjw2xVEjYMG1ca0YMS1w==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/gatsby-remark-autolink-headers/-/gatsby-remark-autolink-headers-2.2.1.tgz", + "integrity": "sha512-FqTq9rh9fRxdlX1V3InXSAoZQyBcZ3mI5zNiNagO+DRNZCSve3YVKTDmMZ7a7GXx5Bz7QTPBB993wk2OcRSIFg==", "requires": { - "@babel/runtime": "^7.7.6", - "github-slugger": "^1.2.1", + "@babel/runtime": "^7.8.7", + "github-slugger": "^1.3.0", "lodash": "^4.17.15", - "mdast-util-to-string": "^1.0.7", + "mdast-util-to-string": "^1.1.0", "unist-util-visit": "^1.4.1" }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, "unist-util-visit": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", @@ -9975,12 +11329,50 @@ } } }, + "gatsby-remark-code-titles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gatsby-remark-code-titles/-/gatsby-remark-code-titles-1.1.0.tgz", + "integrity": "sha512-RuNqziXi99eBIj5NJP0TgdzAxzWFL+ArGRb3961Ff9Tto/nCvmyqR1qySaWKXtkOgeqoVUlqAFNUCyEAyNuc8w==", + "requires": { + "query-string": "~6.0.0", + "unist-util-visit": "~1.3.0" + }, + "dependencies": { + "query-string": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.0.0.tgz", + "integrity": "sha1-i485RHtz6CkNb141gXeSGOkXEUI=", + "requires": { + "decode-uri-component": "^0.2.0", + "strict-uri-encode": "^2.0.0" + } + }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" + }, + "unist-util-is": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.3.tgz", + "integrity": "sha512-4WbQX2iwfr/+PfM4U3zd2VNXY+dWtZsN1fLnWEi2QQXA4qyDYAZcDMfXUX0Cu6XZUHHAO9q4nyxxLT4Awk1qUA==" + }, + "unist-util-visit": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.3.1.tgz", + "integrity": "sha512-0fdB9EQJU0tho5tK0VzOJzAQpPv2LyLZ030b10GxuzAWEfvd54mpY7BMjQ1L69k2YNvL+SvxRzH0yUIehOO8aA==", + "requires": { + "unist-util-is": "^2.1.1" + } + } + } + }, "gatsby-remark-copy-linked-files": { - "version": "2.1.36", - "resolved": "https://registry.npmjs.org/gatsby-remark-copy-linked-files/-/gatsby-remark-copy-linked-files-2.1.36.tgz", - "integrity": "sha512-WEvdRLvWHr8ftrHUfq/77N0AbraGN62fkWe4E+p8/SKPCXb7WV7sY67Z/npiBAh6bRMNWsy/UZpKEkOk/oz04A==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/gatsby-remark-copy-linked-files/-/gatsby-remark-copy-linked-files-2.2.1.tgz", + "integrity": "sha512-xTy52n0K+fF4aXCNYkpH1HdhYy47GwLG2tE5H+xIisyEQiCr5XA555yQdS0U4MRtDZEyfX4TB+XTwaNhOgTPgw==", "requires": { - "@babel/runtime": "^7.7.6", + "@babel/runtime": "^7.8.7", "cheerio": "^1.0.0-rc.3", "fs-extra": "^8.1.0", "is-relative-url": "^3.0.0", @@ -9991,11 +11383,11 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, "cheerio": { @@ -10019,6 +11411,11 @@ "@types/node": "*" } }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, "unist-util-visit": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", @@ -10050,41 +11447,28 @@ } }, "gatsby-remark-prismjs": { - "version": "3.3.30", - "resolved": "https://registry.npmjs.org/gatsby-remark-prismjs/-/gatsby-remark-prismjs-3.3.30.tgz", - "integrity": "sha512-DFcFAcvYHmx9FM5P/CRkCbQH4nSPmZ9DRaocAUxH7ZJtF2nMGYp1suYa8BEJKmM/EwvFxLdGoRoN+5FzO56qZQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/gatsby-remark-prismjs/-/gatsby-remark-prismjs-3.4.1.tgz", + "integrity": "sha512-DPg4PjalrElXXZ3KZRiWiJiHIsXaee51nN2hCoGC2hfaXW8VdSjXhpBSSps9OWuB+QNmdTp/EP3FDiiwImjpUw==", "requires": { - "@babel/runtime": "^7.7.6", + "@babel/runtime": "^7.8.7", "parse-numeric-range": "^0.0.2", "unist-util-visit": "^1.4.1" }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, - "unist-util-visit": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", - "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", - "requires": { - "unist-util-visit-parents": "^2.0.0" - } - } - } - }, - "gatsby-remark-prismjs-title": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gatsby-remark-prismjs-title/-/gatsby-remark-prismjs-title-1.0.0.tgz", - "integrity": "sha512-VKAw7LGAbzyDlztUfhOri+jDTjLyOPCJCNgkdt2+61+SP8M9wYzzma8NvVyzHP7J9hx0jcYq8F50XQH5dE42ow==", - "requires": { - "unist-util-visit": "~1.4.0" - }, - "dependencies": { + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, "unist-util-visit": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", @@ -10104,69 +11488,178 @@ } }, "gatsby-source-filesystem": { - "version": "2.1.46", - "resolved": "https://registry.npmjs.org/gatsby-source-filesystem/-/gatsby-source-filesystem-2.1.46.tgz", - "integrity": "sha512-5LC90+qMKK+/hJzZxKcazx5JvvOO1wHH+ZE7JDSHSzZ1QB+RKWnkvG4a7n6dyiFybo1HN3ql5YQXQLkBEiIfMg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/gatsby-source-filesystem/-/gatsby-source-filesystem-2.2.2.tgz", + "integrity": "sha512-uHHCiTp8/q9JF0Yr14Q5aJZ07jUJSV6HJSnrSVnEIF4PfRQkVJG5FHQULmxJUXWQhIoy17EGuzqVjxMsFY69QA==", "requires": { - "@babel/runtime": "^7.7.6", + "@babel/runtime": "^7.8.7", "better-queue": "^3.8.10", "bluebird": "^3.7.2", - "chokidar": "3.3.0", - "file-type": "^12.4.0", + "chokidar": "3.3.1", + "file-type": "^12.4.2", "fs-extra": "^8.1.0", - "gatsby-core-utils": "^1.0.26", - "got": "^7.1.0", + "gatsby-core-utils": "^1.1.1", + "got": "^9.6.0", "md5-file": "^3.2.3", "mime": "^2.4.4", "pretty-bytes": "^5.3.0", "progress": "^2.0.3", "read-chunk": "^3.2.0", "valid-url": "^1.0.9", - "xstate": "^4.7.2" + "xstate": "^4.8.0" }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" } }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", + "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.3.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "optional": true + }, "gatsby-core-utils": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-1.0.26.tgz", - "integrity": "sha512-NPflmXmyTcg3x2mp6cqp/51QeAHRKepfbf0X4erDsnVlewFJuGTe+25ZJvWkkwU2g1cPAxuwzlPe0jOL92iU4A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-1.1.1.tgz", + "integrity": "sha512-EboPcBx37YQVUKN9JH753S54nDxjRmOefbR0i08KTmaVgQ1lZnDXJr8JfrImmMqupZlOkPQX1mWlXfp+r1jGhA==", "requires": { "ci-info": "2.0.0", + "configstore": "^5.0.1", "node-object-hash": "^2.0.0" } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, "got": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", "requires": { - "decompress-response": "^3.2.0", + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-plain-obj": "^1.1.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "p-cancelable": "^0.3.0", - "p-timeout": "^1.1.1", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "url-parse-lax": "^1.0.0", - "url-to-options": "^1.0.1" + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "readdirp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", + "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "requires": { + "picomatch": "^2.0.7" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" } + }, + "xstate": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-4.8.0.tgz", + "integrity": "sha512-xHSYQtCHLkcrFRxa5lK4Lp1rnKt00a80jcKFMQiMBuE+6MvTYv7twwqYpzjsJoKFjGZB3GGEpZAuY1dmlPTh/g==" } } }, @@ -10273,9 +11766,9 @@ } }, "gatsby-theme-apollo-core": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/gatsby-theme-apollo-core/-/gatsby-theme-apollo-core-3.0.3.tgz", - "integrity": "sha512-eYd4QQb4TbIawM02qMonPy5zdIuXMhPYN9xevZrcoJMBJgKhzkYpENJVX8OnLCthhrTQ4XZzGH1mg3Kqqrcl8g==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/gatsby-theme-apollo-core/-/gatsby-theme-apollo-core-3.0.11.tgz", + "integrity": "sha512-dWpSi35pbNASs6/6flvlAP2qmOhaLrhDv9CqDyEYajG1yvH7qMrKHP8XLKVObyZ2BU3Y6Zzw+OKdoZVtlo/5Ig==", "requires": { "@apollo/space-kit": "2.15.0", "@emotion/core": "^10.0.7", @@ -10294,9 +11787,9 @@ } }, "gatsby-theme-apollo-docs": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gatsby-theme-apollo-docs/-/gatsby-theme-apollo-docs-3.2.0.tgz", - "integrity": "sha512-98v1NUt/6vBdOgyecK/ONv6mad9GAYZ0jqAVCIUvkP6K5Hm4VvivMYFZhffEQnaL/SevZ0TshY2CbTCrk5j5EA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/gatsby-theme-apollo-docs/-/gatsby-theme-apollo-docs-4.1.1.tgz", + "integrity": "sha512-ZV83sYtJHapHgdu3TIR7dejWGwgVMafLyOt3fIQ7XFxU3PGztGWwwoeL4kZ3i8M505+I0hMF8OtL1EO+oehkaw==", "requires": { "@mdx-js/mdx": "^1.1.0", "@mdx-js/react": "^1.0.27", @@ -10305,14 +11798,14 @@ "gatsby-plugin-segment-js": "^3.0.1", "gatsby-remark-autolink-headers": "^2.0.16", "gatsby-remark-check-links": "^2.1.0", + "gatsby-remark-code-titles": "^1.1.0", "gatsby-remark-copy-linked-files": "^2.0.12", "gatsby-remark-mermaid": "^1.2.0", "gatsby-remark-prismjs": "^3.2.8", - "gatsby-remark-prismjs-title": "^1.0.0", "gatsby-remark-rewrite-relative-links": "^1.0.7", "gatsby-source-filesystem": "^2.0.29", "gatsby-source-git": "^1.0.1", - "gatsby-theme-apollo-core": "^3.0.3", + "gatsby-theme-apollo-core": "^3.0.11", "gatsby-transformer-remark": "^2.6.30", "js-yaml": "^3.13.1", "prismjs": "^1.15.0", @@ -10327,26 +11820,26 @@ } }, "gatsby-transformer-remark": { - "version": "2.6.48", - "resolved": "https://registry.npmjs.org/gatsby-transformer-remark/-/gatsby-transformer-remark-2.6.48.tgz", - "integrity": "sha512-VvfNGN7L+V+Lg6Lsfqh+SFxLPcMGEjEBpy7cDjhRwUq+AEbv5kZ4lt+6+ICUD7QitsSER1AgnZz0Yp61t9YEag==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/gatsby-transformer-remark/-/gatsby-transformer-remark-2.7.1.tgz", + "integrity": "sha512-9geE8itjePDvaa0uWmyRgi2emPt9ut420YyjaNJ1/4eZw9Yj8zAuCdancw7j1buhL0UAxgQ2YseO6+MWTHEoMw==", "requires": { - "@babel/runtime": "^7.7.6", + "@babel/runtime": "^7.8.7", "bluebird": "^3.7.2", - "gatsby-core-utils": "^1.0.26", + "gatsby-core-utils": "^1.1.1", "gray-matter": "^4.0.2", "hast-util-raw": "^4.0.0", "hast-util-to-html": "^4.0.1", "lodash": "^4.17.15", "mdast-util-to-hast": "^3.0.4", - "mdast-util-to-string": "^1.0.7", + "mdast-util-to-string": "^1.1.0", "mdast-util-toc": "^5.0", "remark": "^10.0.1", "remark-parse": "^6.0.3", "remark-retext": "^3.1.3", "remark-stringify": "6.0.4", "retext-english": "^3.0.4", - "sanitize-html": "^1.20.1", + "sanitize-html": "^1.22.1", "underscore.string": "^3.3.5", "unified": "^6.2.0", "unist-util-remove-position": "^1.1.4", @@ -10355,11 +11848,11 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", - "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, "bluebird": { @@ -10368,11 +11861,12 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "gatsby-core-utils": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-1.0.26.tgz", - "integrity": "sha512-NPflmXmyTcg3x2mp6cqp/51QeAHRKepfbf0X4erDsnVlewFJuGTe+25ZJvWkkwU2g1cPAxuwzlPe0jOL92iU4A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/gatsby-core-utils/-/gatsby-core-utils-1.1.1.tgz", + "integrity": "sha512-EboPcBx37YQVUKN9JH753S54nDxjRmOefbR0i08KTmaVgQ1lZnDXJr8JfrImmMqupZlOkPQX1mWlXfp+r1jGhA==", "requires": { "ci-info": "2.0.0", + "configstore": "^5.0.1", "node-object-hash": "^2.0.0" } }, @@ -10458,9 +11952,9 @@ } }, "mdast-util-toc": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-5.0.0.tgz", - "integrity": "sha512-1ExJKC+85/+Q2LfuASOjdGTB7n/ikQperjiv+7OEVCpRbabr/DGZzEXEZfsZr/k4Pd3g/Gim9DV44/rPjczMAw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-5.0.2.tgz", + "integrity": "sha512-IeihbQLXrnCs/427dVzCp3ffvSPpdx/Mc2WWYAdVaS+MFqdKZHlJylGWAA1cGPewhEVyITsWrlXJ/b2d80Wsnw==", "requires": { "@types/mdast": "^3.0.3", "@types/unist": "^2.0.3", @@ -10472,14 +11966,14 @@ }, "dependencies": { "unist-util-is": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.1.tgz", - "integrity": "sha512-7NYjErP4LJtkEptPR22wO5RsCPnHZZrop7t2SoQzjvpFedCFer4WW8ujj9GI5DkUX7yVcffXLjoURf6h2QUv6Q==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" }, "unist-util-visit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.1.tgz", - "integrity": "sha512-bEDa5S/O8WRDeI1mLaMoKuFFi89AjF+UAoMNxO+bbVdo06q+53Vhq4iiv1PenL6Rx1ZxIpXIzqZoc5HD2I1oMA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz", + "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==", "requires": { "@types/unist": "^2.0.0", "unist-util-is": "^4.0.0", @@ -10487,9 +11981,9 @@ } }, "unist-util-visit-parents": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.1.tgz", - "integrity": "sha512-umEOTkm6/y1gIqPrqet55mYqlvGXCia/v1FSc5AveLAI7jFmOAIbqiwcHcviLcusAkEQt1bq2hixCKO9ltMb2Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz", + "integrity": "sha512-yJEfuZtzFpQmg1OSCyS9M5NJRrln/9FbYosH3iW0MG402QbdbaB8ZESwUv9RO6nRfLAKvWcMxCwdLWOov36x/g==", "requires": { "@types/unist": "^2.0.0", "unist-util-is": "^4.0.0" @@ -10505,6 +11999,11 @@ "xtend": "^4.0.1" } }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, "remark-parse": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz", @@ -10548,6 +12047,14 @@ "x-is-string": "^0.1.0" } }, + "unist-builder": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-1.0.4.tgz", + "integrity": "sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg==", + "requires": { + "object-assign": "^4.1.0" + } + }, "unist-util-is": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.3.tgz", @@ -10638,9 +12145,9 @@ } }, "github-slugger": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.2.1.tgz", - "integrity": "sha512-SsZUjg/P03KPzQBt7OxJPasGw6NRO5uOgiZ5RGXVud5iSIZ0eNZeNp5rTwCxtavrRUa/A77j8mePVc5lEvk0KQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz", + "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==", "requires": { "emoji-regex": ">=6.0.0 <=6.1.1" }, @@ -11156,9 +12663,9 @@ } }, "hast-util-from-parse5": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.2.tgz", - "integrity": "sha512-YXFjoRS7ES7PEoLx6uihtSfKTO1s3z/tzGiV5cVpsUiihduogFXubNRCzTIW3yOOGO1nws9CxPq4MbwD39Uo+w==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", + "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", "requires": { "ccount": "^1.0.3", "hastscript": "^5.0.0", @@ -11168,14 +12675,14 @@ } }, "hast-util-is-element": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.0.3.tgz", - "integrity": "sha512-C62CVn7jbjp89yOhhy7vrkSaB7Vk906Gtcw/Ihd+Iufnq+2pwOZjdPmpzpKLWJXPJBMDX3wXg4FqmdOayPcewA==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.0.4.tgz", + "integrity": "sha512-NFR6ljJRvDcyPP5SbV7MyPBgF47X3BsskLnmw1U34yL+X6YC0MoBx9EyMg8Jtx4FzGH95jw8+c1VPLHaRA0wDQ==" }, "hast-util-parse-selector": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.3.tgz", - "integrity": "sha512-nxbeqjQNxsvo/uYYAw9kij6td05YVUlf1qti09rVfbWSLT5H6wo3c+USIwX6nzXWk5kFZzXnEqO82856r0aM2Q==" + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.4.tgz", + "integrity": "sha512-gW3sxfynIvZApL4L07wryYF4+C9VvH3AUi7LAnVXV4MneGEgwOByXvFo18BgmTWnm7oHAe874jKbIB1YhHSIzA==" }, "hast-util-raw": { "version": "5.0.1", @@ -11233,26 +12740,26 @@ } }, "hast-util-to-parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-5.1.1.tgz", - "integrity": "sha512-ivCeAd5FCXr7bapJIVsWMnx/EmbjkkW2TU2hd1prq+jGwiaUoK+FcpjyPNwsC5ogzCwWO669tOqIovGeLc/ntg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-5.1.2.tgz", + "integrity": "sha512-ZgYLJu9lYknMfsBY0rBV4TJn2xiwF1fXFFjbP6EE7S0s5mS8LIKBVWzhA1MeIs1SWW6GnnE4In6c3kPb+CWhog==", "requires": { "hast-to-hyperscript": "^7.0.0", "property-information": "^5.0.0", "web-namespaces": "^1.0.0", - "xtend": "^4.0.1", + "xtend": "^4.0.0", "zwitch": "^1.0.0" } }, "hast-util-whitespace": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.3.tgz", - "integrity": "sha512-AlkYiLTTwPOyxZ8axq2/bCwRUPjIPBfrHkXuCR92B38b3lSdU22R5F/Z4DL6a2kxWpekWq1w6Nj48tWat6GeRA==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz", + "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==" }, "hastscript": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.1.tgz", - "integrity": "sha512-xHo1Hkcqd0LlWNuDL3/BxwhgAGp3d7uEvCMgCTrBY+zsOooPPH+8KAvW8PCgl+GB8H3H44nfSaF0A4BQ+4xlYg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", + "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", "requires": { "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", @@ -11356,9 +12863,9 @@ } }, "html-void-elements": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.4.tgz", - "integrity": "sha512-yMk3naGPLrfvUV9TdDbuYXngh/TpHbA6TrOw3HL9kS8yhwx7i309BReNg7CbAJXGE+UMJ6je5OqJ7lC63o6YuQ==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", + "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==" }, "htmlparser2": { "version": "3.10.1", @@ -12029,9 +13536,9 @@ } }, "is-alphabetical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.3.tgz", - "integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" }, "is-alphanumeric": { "version": "1.0.0", @@ -12039,9 +13546,9 @@ "integrity": "sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=" }, "is-alphanumerical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz", - "integrity": "sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", "requires": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" @@ -12128,9 +13635,9 @@ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-decimal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.3.tgz", - "integrity": "sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" }, "is-descriptor": { "version": "0.1.6", @@ -12183,9 +13690,9 @@ } }, "is-hexadecimal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz", - "integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" }, "is-installed-globally": { "version": "0.1.0", @@ -12444,9 +13951,9 @@ } }, "is-whitespace-character": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz", - "integrity": "sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==" }, "is-windows": { "version": "1.0.2", @@ -12454,9 +13961,9 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" }, "is-word-character": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.3.tgz", - "integrity": "sha512-0wfcrFgOOOBdgRNT9H33xe6Zi6yhX/uoc4U8NBZGeQQB0ctU1dnlNTyL9JM2646bHDTpsDm1Brb3VPoCIMrd/A==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==" }, "is-wsl": { "version": "2.1.1", @@ -12699,9 +14206,9 @@ } }, "less": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/less/-/less-3.10.3.tgz", - "integrity": "sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow==", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/less/-/less-3.11.1.tgz", + "integrity": "sha512-tlWX341RECuTOvoDIvtFqXsKj072hm3+9ymRBe76/mD6O5ZZecnlAOVDlWAleF2+aohFrxNidXhv2773f6kY7g==", "requires": { "clone": "^2.1.2", "errno": "^0.1.1", @@ -12711,7 +14218,8 @@ "mkdirp": "^0.5.0", "promise": "^7.1.1", "request": "^2.83.0", - "source-map": "~0.6.0" + "source-map": "~0.6.0", + "tslib": "^1.10.0" }, "dependencies": { "clone": { @@ -12962,6 +14470,14 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "requires": { + "leven": "^3.1.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -13248,9 +14764,9 @@ "integrity": "sha512-D8E3TBrY35o1ELnonp2MF8b3wKu2tVNl2TqRjvS+95oPMMe7OoIAxNY1qr+5BEZwnWn2V4ErAjVt000DonM+FA==" }, "longest-streak": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.3.tgz", - "integrity": "sha512-9lz5IVdpwsKLMzQi0MQ+oD9EA0mIGcWYP7jXMTZVXP8D42PwuAk+M/HBFYQoxt1G5OR8m7aSIgb1UymfWGBWEw==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==" }, "loose-envify": { "version": "1.4.0", @@ -13302,9 +14818,9 @@ "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" }, "magic-string": { - "version": "0.25.6", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.6.tgz", - "integrity": "sha512-3a5LOMSGoCTH5rbqobC2HuDNRtE2glHZ8J7pK+QZYppyWA36yuNpsX994rIY2nCuyP7CZYy7lQq/X2jygiZ89g==", + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", "requires": { "sourcemap-codec": "^1.4.4" } @@ -13352,9 +14868,9 @@ } }, "markdown-escapes": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.3.tgz", - "integrity": "sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" }, "markdown-table": { "version": "1.1.3", @@ -13434,31 +14950,19 @@ } }, "mdast-util-to-hast": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-6.0.2.tgz", - "integrity": "sha512-GjcOimC9qHI0yNFAQdBesrZXzUkRdFleQlcoU8+TVNfDW6oLUazUx8MgUoTaUyCJzBOnE5AOgqhpURrSlf0QwQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-7.0.0.tgz", + "integrity": "sha512-vxnXKSZgvPG2grZM3kxaF052pxsLtq8TPAkiMkqYj1nFTOazYUPXt3LFYIEB6Ws/IX7Uyvljzk64kD6DwZl/wQ==", "requires": { "collapse-white-space": "^1.0.0", "detab": "^2.0.0", "mdast-util-definitions": "^1.2.0", "mdurl": "^1.0.1", - "trim": "0.0.1", "trim-lines": "^1.0.0", - "unist-builder": "^1.0.1", - "unist-util-generated": "^1.1.0", + "unist-builder": "^2.0.0", + "unist-util-generated": "^1.0.0", "unist-util-position": "^3.0.0", - "unist-util-visit": "^1.1.0", - "xtend": "^4.0.1" - }, - "dependencies": { - "unist-util-visit": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", - "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", - "requires": { - "unist-util-visit-parents": "^2.0.0" - } - } + "unist-util-visit": "^2.0.0" } }, "mdast-util-to-nlcst": { @@ -13473,9 +14977,9 @@ } }, "mdast-util-to-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.0.7.tgz", - "integrity": "sha512-P+gdtssCoHOX+eJUrrC30Sixqao86ZPlVjR5NEAoy0U79Pfxb1Y0Gntei0+GrnQD4T04X9xA8tcugp90cSmNow==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", + "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==" }, "mdast-util-toc": { "version": "3.1.0", @@ -13585,9 +15089,9 @@ "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==" }, "mermaid": { - "version": "8.4.4", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.4.4.tgz", - "integrity": "sha512-exidY40uwtYZ8564Z1YtdElhfqUj/JTYNgbVs1fD1Zf7gIoIpSQke5U4bnSYe9ErMU1NO2Xt+C2VlfiV1har+A==", + "version": "8.4.8", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.4.8.tgz", + "integrity": "sha512-sumTNBFwMX7oMQgogdr3NhgTeQOiwcEsm23rQ4KHGW7tpmvMwER1S+1gjCSSnqlmM/zw7Ga7oesYCYicKboRwQ==", "requires": { "@braintree/sanitize-url": "^3.1.0", "crypto-random-string": "^3.0.1", @@ -13603,11 +15107,11 @@ }, "dependencies": { "crypto-random-string": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.0.1.tgz", - "integrity": "sha512-dUL0cJ4PBLanJGJQBHQUkvZ3C4q13MXzl54oRqAIiJGiNkOZ4JDwkg/SBo7daGghzlJv16yW1p/4lIQukmbedA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.2.0.tgz", + "integrity": "sha512-8vPu5bsKaq2uKRy3OL7h1Oo7RayAWB8sYexLKAqvCXVib8SxgbmoF1IN4QMKjBv8uI8mp5gPPMbiRah25GMrVQ==", "requires": { - "type-fest": "^0.5.2" + "type-fest": "^0.8.1" } } } @@ -13863,9 +15367,9 @@ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "moment-mini": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.22.1.tgz", - "integrity": "sha512-OUCkHOz7ehtNMYuZjNciXUfwTuz8vmF1MTbAy59ebf+ZBYZO5/tZKuChVWCX+uDo+4idJBpGltNfV8st+HwsGw==" + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.24.0.tgz", + "integrity": "sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==" }, "move-concurrently": { "version": "1.0.1", @@ -13916,9 +15420,9 @@ "optional": true }, "nano-css": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/nano-css/-/nano-css-5.2.1.tgz", - "integrity": "sha512-T54okxMAha0+de+W8o3qFtuWhTxYvqQh2ku1cYEqTTP9mR62nWV2lLK9qRuAGWmoaYWhU7K4evT9Lc1iF65wuw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/nano-css/-/nano-css-5.3.0.tgz", + "integrity": "sha512-uM/9NGK9/E9/sTpbIZ/bQ9xOLOIHZwrrb/CRlbDHBU/GFS7Gshl24v/WJhwsVViWkpOXUmiZ66XO7fSB4Wd92Q==", "requires": { "css-tree": "^1.0.0-alpha.28", "csstype": "^2.5.5", @@ -13964,9 +15468,9 @@ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" }, "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "nice-try": { "version": "1.0.5", @@ -13974,9 +15478,9 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "nlcst-to-string": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-2.0.3.tgz", - "integrity": "sha512-OY2QhGdf6jpYfHqS4vJwqF7aIBZkaMjMUkcHcskMPitvXLuYNGdQvgVWI/5yKwkmIdmhft3ounSJv+Re2yydng==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-2.0.4.tgz", + "integrity": "sha512-3x3jwTd6UPG7vi5k4GEzvxJ5rDA7hVUIRNHPblKuMVP9Z3xmlsd9cgLcpAMkc5uPOBna82EeshROFhsPkbnTZg==" }, "no-case": { "version": "2.3.2", @@ -14554,9 +16058,9 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" }, "p-defer": { "version": "3.0.0", @@ -14613,14 +16117,6 @@ "retry": "^0.12.0" } }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "requires": { - "p-finally": "^1.0.0" - } - }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -14740,9 +16236,9 @@ } }, "parse-english": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/parse-english/-/parse-english-4.1.2.tgz", - "integrity": "sha512-+PBf+1ifxqJlOpisODiKX4A8wBEgWm4goMvDB5O9zx/cQI58vzHTZeWFbAgCF9fUXRl8/YdINv1cfmfIRR1acg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/parse-english/-/parse-english-4.1.3.tgz", + "integrity": "sha512-IQl1v/ik9gw437T8083coohMihae0rozpc7JYC/9h6hi9xKBSxFwh5HWRpzVC2ZhEs2nUlze2aAktpNBJXdJKA==", "requires": { "nlcst-to-string": "^2.0.0", "parse-latin": "^4.0.0", @@ -14775,9 +16271,9 @@ } }, "parse-latin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-4.2.0.tgz", - "integrity": "sha512-b8PvsA1Ohh7hIQwDDy6kSjx3EbcuR3oKYm5lC1/l/zIB6mVVV5ESEoS1+Qr5+QgEGmp+aEZzc+D145FIPJUszw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-4.2.1.tgz", + "integrity": "sha512-7T9g6mIsFFpLlo0Zzb2jLWdCt+H9Qtf/hRmMYFi/Mq6Ovi+YKo+AyDFX3OhFfu0vXX5Nid9FKJGKSSzNcTkWiA==", "requires": { "nlcst-to-string": "^2.0.0", "unist-util-modify-children": "^1.0.0", @@ -15760,9 +17256,9 @@ } }, "prismjs": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.18.0.tgz", - "integrity": "sha512-N0r3i/Cto516V8+GKKamhsPVZSFcO0TMUBtIDW6uq6BVqoC3FNtZVZ+cmH16N2XtGQlgRN+sFUTjOdCsEP51qw==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.19.0.tgz", + "integrity": "sha512-IVFtbW9mCWm9eOIaEkNyo2Vl4NnEifis2GQ7/MLRG5TQe6t+4Sj9J5QWI9i3v+SS43uZBlCAOn+zYTVYQcPXJw==", "requires": { "clipboard": "^2.0.0" } @@ -15838,11 +17334,11 @@ } }, "property-information": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.3.0.tgz", - "integrity": "sha512-IslotQn1hBCZDY7SaJ3zmCjVea219VTwmOk6Pu3z9haU9m4+T8GwaDubur+6NMHEU+Fjs/6/p66z6QULPkcL1w==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.4.0.tgz", + "integrity": "sha512-nmMWAm/3vKFGmmOWOcdLjgq/Hlxa+hsuR/px1Lp/UGEyc5A22A6l78Shc2C0E71sPmAqglni+HrS7L7VJ7AUCA==", "requires": { - "xtend": "^4.0.1" + "xtend": "^4.0.0" } }, "protocols": { @@ -15860,9 +17356,9 @@ } }, "proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "prr": { "version": "1.0.1", @@ -15875,9 +17371,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, "public-encrypt": { "version": "4.0.3", @@ -16889,18 +18385,25 @@ } }, "remark-mdx": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.5.3.tgz", - "integrity": "sha512-7WqfwdyER3k0gNiikzw9y+AQskAm6PX2qEF97vhuZ9y8/MatVKoWGCPX4VCYAN0qlM1X6ty761rbMWMy5OmgyA==", - "requires": { - "@babel/core": "7.7.4", - "@babel/helper-plugin-utils": "7.0.0", - "@babel/plugin-proposal-object-rest-spread": "7.7.4", - "@babel/plugin-syntax-jsx": "7.7.4", - "@mdx-js/util": "^1.5.3", - "is-alphabetical": "1.0.3", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.5.7.tgz", + "integrity": "sha512-f13ot+zaByDXYuOC4FWTpQCGP/rNbaxdhs2mLlW7ZBipm3JYR2ASFSL7RC3R7ytzm3n8v6hhcFxDKU+CwC2f4g==", + "requires": { + "@babel/core": "7.8.4", + "@babel/helper-plugin-utils": "7.8.3", + "@babel/plugin-proposal-object-rest-spread": "7.8.3", + "@babel/plugin-syntax-jsx": "7.8.3", + "@mdx-js/util": "^1.5.7", + "is-alphabetical": "1.0.4", "remark-parse": "7.0.2", "unified": "8.4.2" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } } }, "remark-parse": { @@ -16975,6 +18478,14 @@ "inline-style-parser": "0.1.1" } }, + "unist-builder": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-1.0.4.tgz", + "integrity": "sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg==", + "requires": { + "object-assign": "^4.1.0" + } + }, "unist-util-is": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.3.tgz", @@ -17081,9 +18592,9 @@ "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -17092,7 +18603,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -17102,7 +18613,7 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" } @@ -17270,9 +18781,9 @@ } }, "rollup-plugin-babel": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.3.3.tgz", - "integrity": "sha512-tKzWOCmIJD/6aKNz0H1GMM+lW1q9KyFubbWzGiOG540zxPPifnEAHTZwjo0g991Y+DyOZcLqBgqOdqazYE5fkw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz", + "integrity": "sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw==", "requires": { "@babel/helper-module-imports": "^7.0.0", "rollup-pluginutils": "^2.8.1" @@ -17291,9 +18802,9 @@ }, "dependencies": { "resolve": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", - "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "requires": { "path-parse": "^1.0.6" } @@ -17325,9 +18836,9 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" }, "estree-walker": { "version": "0.5.2", @@ -17357,9 +18868,9 @@ }, "dependencies": { "resolve": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", - "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "requires": { "path-parse": "^1.0.6" } @@ -17457,20 +18968,93 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sanitize-html": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.1.tgz", - "integrity": "sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.22.1.tgz", + "integrity": "sha512-++IMC00KfMQc45UWZJlhWOlS9eMrME38sFG9GXfR+k6oBo9JXSYQgTOZCl9j3v/smFTRNT9XNwz5DseFdMY+2Q==", "requires": { "chalk": "^2.4.1", - "htmlparser2": "^3.10.0", + "htmlparser2": "^4.1.0", "lodash.clonedeep": "^4.5.0", "lodash.escaperegexp": "^4.1.2", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", - "lodash.mergewith": "^4.6.1", - "postcss": "^7.0.5", - "srcset": "^1.0.0", + "lodash.mergewith": "^4.6.2", + "postcss": "^7.0.27", + "srcset": "^2.0.1", "xtend": "^4.0.1" + }, + "dependencies": { + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" + }, + "domhandler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", + "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz", + "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==", + "requires": { + "dom-serializer": "^0.2.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0" + } + }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" + }, + "htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, + "postcss": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", + "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } } }, "sax": { @@ -17856,9 +19440,9 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "simple-git": { - "version": "1.129.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.129.0.tgz", - "integrity": "sha512-XbzNmugMTeV2crZnPl+b1ZJn+nqXCUNyrZxDXpLM0kHL3B85sbPlpd8q9I4qtAHI9D2FxTB6w4BuiAGKYtyzKw==", + "version": "1.132.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.132.0.tgz", + "integrity": "sha512-xauHm1YqCTom1sC9eOjfq3/9RKiUA9iPnxBbrY2DdL8l4ADMu0jjM5l5lphQP5YWNqAL2aXC/OeuQ76vHtW5fg==", "requires": { "debug": "^4.0.1" }, @@ -18234,14 +19818,14 @@ "integrity": "sha512-C1RFUGu+YASuqpgDRInTM7Y6OwqeWNOuKn7v0P/4Kh66epTI4PYWwPWP5kdA4l/VqzBAWiqoz5dk0trof73R7w==" }, "sourcemap-codec": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.7.tgz", - "integrity": "sha512-RuN23NzhAOuUtaivhcrjXx1OPXsFeH9m5sI373/U7+tGLKihjUyboZAzOadytMjnqHp1f45RGk1IzDKCpDpSYA==" + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "space-separated-tokens": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz", - "integrity": "sha512-UyhMSmeIqZrQn2UdjYpxEkwY9JUrn8pP+7L4f91zRzOQuI8MF1FGLfYU9DKCYeLdo7LXMxwrX5zKFy7eeeVHuA==" + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==" }, "spdx-correct": { "version": "3.1.0", @@ -18340,13 +19924,9 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "srcset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", - "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", - "requires": { - "array-uniq": "^1.0.2", - "number-is-nan": "^1.0.0" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-2.0.1.tgz", + "integrity": "sha512-00kZI87TdRKwt+P8jj8UZxbfp7mK2ufxcIMWvhAOZNJTRROimpHeruWrGvCZneiuVDLqdyHefVp748ECTnyUBQ==" }, "sshpk": { "version": "1.16.1", @@ -18383,13 +19963,6 @@ "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", "requires": { "stackframe": "^1.1.1" - }, - "dependencies": { - "stackframe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.1.1.tgz", - "integrity": "sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ==" - } } }, "stack-trace": { @@ -18420,11 +19993,6 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" - }, - "stackframe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.1.1.tgz", - "integrity": "sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ==" } } }, @@ -18436,27 +20004,12 @@ "error-stack-parser": "^2.0.6", "stack-generator": "^2.0.5", "stacktrace-gps": "^3.0.4" - }, - "dependencies": { - "error-stack-parser": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", - "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", - "requires": { - "stackframe": "^1.1.1" - } - }, - "stackframe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.1.1.tgz", - "integrity": "sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ==" - } } }, "state-toggle": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.2.tgz", - "integrity": "sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==" }, "static-extend": { "version": "0.1.2", @@ -18862,9 +20415,9 @@ } }, "svg-parser": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.2.tgz", - "integrity": "sha512-1gtApepKFweigFZj3sGO8KT8LvVZK8io146EzXrpVuWCDAbISz/yMucco3hWTkpZNoPabM+dnMOpy6Swue68Zg==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" }, "svgo": { "version": "1.2.2", @@ -19193,19 +20746,12 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "trim": { @@ -19214,19 +20760,19 @@ "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" }, "trim-lines": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-1.1.2.tgz", - "integrity": "sha512-3GOuyNeTqk3FAqc3jOJtw7FTjYl94XBR5aD9QnDbK/T4CA9sW/J0l9RoaRPE9wyPP7NF331qnHnvJFBJ+IDkmQ==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-1.1.3.tgz", + "integrity": "sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA==" }, "trim-trailing-lines": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz", - "integrity": "sha512-MUjYItdrqqj2zpcHFTkMa9WAv4JHTI6gnRQGPFLrt5L9a6tRMiDnIqYl8JBvu2d2Tc3lWJKQwlGCp0K8AvCM+Q==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", + "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==" }, "trough": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.4.tgz", - "integrity": "sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==" }, "true-case-path": { "version": "2.2.1", @@ -19293,9 +20839,9 @@ } }, "type-fest": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", - "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" }, "type-is": { "version": "1.6.18", @@ -19340,14 +20886,14 @@ } }, "ua-parser-js": { - "version": "0.7.19", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", - "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" + "version": "0.7.21", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", + "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==" }, "uglify-js": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.4.tgz", - "integrity": "sha512-tinYWE8X1QfCHxS1lBS8yiDekyhSXOO6R66yNOCdUJeojxxw+PX2BHAz/BWyW7PQ7pkiWVxJfIEbiDxyLWvUGg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz", + "integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==", "requires": { "commander": "~2.20.3", "source-map": "~0.6.1" @@ -19375,12 +20921,12 @@ } }, "unherit": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.2.tgz", - "integrity": "sha512-W3tMnpaMG7ZY6xe/moK04U9fBhi6wEiCYHUW5Mop/wQHf12+79EQGwxYejNdhEz2mkqkBlGwm7pxmgBKMVUj0w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", "requires": { - "inherits": "^2.0.1", - "xtend": "^4.0.1" + "inherits": "^2.0.0", + "xtend": "^4.0.0" } }, "unicode-canonical-property-names-ecmascript": { @@ -19420,9 +20966,9 @@ }, "dependencies": { "is-plain-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.0.0.tgz", - "integrity": "sha512-EYisGhpgSCwspmIuRHGjROWTon2Xp8Z7U03Wubk/bTL5TTRC5R1rGVgyjzBrk9+ULdH6cRD06KRcw/xfqhVYKQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" } } }, @@ -19472,12 +21018,9 @@ } }, "unist-builder": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-1.0.4.tgz", - "integrity": "sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg==", - "requires": { - "object-assign": "^4.1.0" - } + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", + "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==" }, "unist-util-generated": { "version": "1.1.5", @@ -19498,17 +21041,17 @@ } }, "unist-util-modify-children": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-1.1.5.tgz", - "integrity": "sha512-XeL5qqyoS3TEueCKEzHusWXE9JBDJPE4rl6LmcLOwlzv0RIZrcMNqKx02GSK3Ms4v45ldu+ltPxG42FBMVdPZw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-1.1.6.tgz", + "integrity": "sha512-TOA6W9QLil+BrHqIZNR4o6IA5QwGOveMbnQxnWYq+7EFORx9vz/CHrtzF36zWrW61E2UKw7sM1KPtIgeceVwXw==", "requires": { "array-iterate": "^1.0.0" } }, "unist-util-position": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.0.4.tgz", - "integrity": "sha512-tWvIbV8goayTjobxDIr4zVTyG+Q7ragMSMeKC3xnPl9xzIc0+she8mxXLM3JVNDDsfARPbCd3XdzkyLdo7fF3g==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", + "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==" }, "unist-util-remove": { "version": "1.0.3", @@ -19562,17 +21105,17 @@ } }, "unist-util-stringify-position": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.2.tgz", - "integrity": "sha512-nK5n8OGhZ7ZgUwoUbL8uiVRwAbZyzBsB/Ddrlbu6jwwubFza4oe15KlyEaLNMXQW1svOQq4xesUeqA85YrIUQA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", "requires": { "@types/unist": "^2.0.2" } }, "unist-util-visit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.1.tgz", - "integrity": "sha512-bEDa5S/O8WRDeI1mLaMoKuFFi89AjF+UAoMNxO+bbVdo06q+53Vhq4iiv1PenL6Rx1ZxIpXIzqZoc5HD2I1oMA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz", + "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==", "requires": { "@types/unist": "^2.0.0", "unist-util-is": "^4.0.0", @@ -19580,14 +21123,14 @@ }, "dependencies": { "unist-util-is": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.1.tgz", - "integrity": "sha512-7NYjErP4LJtkEptPR22wO5RsCPnHZZrop7t2SoQzjvpFedCFer4WW8ujj9GI5DkUX7yVcffXLjoURf6h2QUv6Q==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" }, "unist-util-visit-parents": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.1.tgz", - "integrity": "sha512-umEOTkm6/y1gIqPrqet55mYqlvGXCia/v1FSc5AveLAI7jFmOAIbqiwcHcviLcusAkEQt1bq2hixCKO9ltMb2Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz", + "integrity": "sha512-yJEfuZtzFpQmg1OSCyS9M5NJRrln/9FbYosH3iW0MG402QbdbaB8ZESwUv9RO6nRfLAKvWcMxCwdLWOov36x/g==", "requires": { "@types/unist": "^2.0.0", "unist-util-is": "^4.0.0" @@ -19596,9 +21139,9 @@ } }, "unist-util-visit-children": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-1.1.3.tgz", - "integrity": "sha512-/GQ8KNRrG+qD30H76FZNc6Ok+8XTu8lxJByN5LnQ4eQfqxda2gP0CPsCX63BRB26ZRMNf6i1c+jlvNlqysEoFg==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-1.1.4.tgz", + "integrity": "sha512-sA/nXwYRCQVRwZU2/tQWUqJ9JSFM1X3x7JIOsIgSzrFHcfVt6NkzDtKzyxg2cZWkCwGF9CO8x4QNZRJRMK8FeQ==" }, "unist-util-visit-parents": { "version": "2.1.2", @@ -19942,11 +21485,18 @@ } }, "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", "requires": { - "prepend-http": "^1.0.1" + "prepend-http": "^2.0.0" + }, + "dependencies": { + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + } } }, "url-to-options": { @@ -20036,9 +21586,9 @@ } }, "vfile": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.0.2.tgz", - "integrity": "sha512-yhoTU5cDMSsaeaMfJ5g0bUKYkYmZhAh9fn9TZicxqn+Cw4Z439il2v3oT9S0yjlpqlI74aFOQCt3nOV+pxzlkw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.0.3.tgz", + "integrity": "sha512-lREgT5sF05TQk68LO6APy0In+TkFGnFEgKChK2+PHIaTrFQ9oHCKXznZ7VILwgYVBcl0gv4lGATFZBLhi2kVQg==", "requires": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", @@ -20060,9 +21610,9 @@ "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==" }, "vfile-message": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.2.tgz", - "integrity": "sha512-gNV2Y2fDvDOOqq8bEe7cF3DXU6QgV4uA9zMR2P8tix11l1r7zju3zry3wZ8sx+BEfuO6WQ7z2QzfWTvqHQiwsA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.3.tgz", + "integrity": "sha512-qQg/2z8qnnBHL0psXyF72kCjb9YioIynvyltuNKFaUhRtqTIcIMP3xnBaPzirVZNuBrUe1qwFciSx2yApa4byw==", "requires": { "@types/unist": "^2.0.0", "unist-util-stringify-position": "^2.0.0" @@ -20131,9 +21681,9 @@ } }, "web-namespaces": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.3.tgz", - "integrity": "sha512-r8sAtNmgR0WKOKOxzuSgk09JsHlpKlB+uHi937qypOu3PZ17UxPrierFKDye/uNHjNTTEshu5PId8rojIPj/tA==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz", + "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==" }, "webpack": { "version": "4.41.6", @@ -20808,11 +22358,12 @@ } }, "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "requires": { - "fd-slicer": "~1.0.1" + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } }, "yeast": { @@ -21061,9 +22612,9 @@ } }, "zwitch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.4.tgz", - "integrity": "sha512-YO803/X+13GNaZB7fVopjvHH0uWQKgJkgKnU1YCjxShjKGVuN9PPHHW8g+uFDpkHpSTNi3rCMKMewIcbC1BAYg==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==" } } } diff --git a/docs/package.json b/docs/package.json index 87ce042475e..754f07ddfc9 100644 --- a/docs/package.json +++ b/docs/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "gatsby": "2.19.43", - "gatsby-theme-apollo-docs": "3.2.0", + "gatsby-theme-apollo-docs": "4.1.1", "react": "16.12.0", "react-dom": "16.12.0" } From 91b150e31a80516ab6faff5faf7d606acf30950c Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 14 Apr 2019 07:33:06 -0400 Subject: [PATCH 003/250] Fix tests graphql-subscriptions 1.1.0 only adds listeners after first call to next(), see https://github.com/apollographql/graphql-subscriptions/pull/148 --- src/test/testMakeRemoteExecutableSchema.ts | 25 +++++++++++----------- src/test/testMergeSchemas.ts | 24 ++++++--------------- src/test/testResolution.ts | 9 +++----- 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index 795fb32762f..9eb425a315b 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -33,15 +33,13 @@ describe('remote subscriptions', () => { `); let notificationCnt = 0; - subscribe(schema, subscription).then(results => + subscribe(schema, subscription).then(results => { forAwaitEach(results as AsyncIterable, (result: ExecutionResult) => { expect(result).to.have.property('data'); expect(result.data).to.deep.equal(mockNotification); !notificationCnt++ ? done() : null; - }) - ); - - setTimeout(() => { + }); + }).then(() => { subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification); }); }); @@ -62,28 +60,29 @@ describe('remote subscriptions', () => { `); let notificationCnt = 0; - subscribe(schema, subscription).then(results => + const sub1 = subscribe(schema, subscription).then(results => { forAwaitEach(results as AsyncIterable, (result: ExecutionResult) => { expect(result).to.have.property('data'); expect(result.data).to.deep.equal(mockNotification); notificationCnt++; - }) - ); + }); + }); - subscribe(schema, subscription).then(results => + const sub2 = subscribe(schema, subscription).then(results => { forAwaitEach(results as AsyncIterable, (result: ExecutionResult) => { expect(result).to.have.property('data'); expect(result.data).to.deep.equal(mockNotification); - }) - ); + }); + }); - setTimeout(() => { + Promise.all([sub1, sub2]).then(() => { subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification); subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification); + setTimeout(() => { expect(notificationCnt).to.eq(2); done(); - }); + }, 0); }); }); }); diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 8e86a160dd3..d1d24f56873 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -736,15 +736,9 @@ bookingById(id: "b1") { !notificationCnt++ ? done() : null; }, ).catch(done); - }) - .catch(done); - - setTimeout(() => { - subscriptionPubSub.publish( - subscriptionPubSubTrigger, - mockNotification - ); - }); + }).then(() => { + subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification); + }).catch(done); }); it('subscription errors are working correctly in merged schema', done => { @@ -798,15 +792,9 @@ bookingById(id: "b1") { !notificationCnt++ ? done() : null; }, ).catch(done); - }) - .catch(done); - - setTimeout(() => { - subscriptionPubSub.publish( - subscriptionPubSubTrigger, - mockNotification - ); - }); + }).then(() => { + subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification); + }).catch(done); }); it('links in queries', async () => { diff --git a/src/test/testResolution.ts b/src/test/testResolution.ts index 473b8e4f766..f4196cb048c 100644 --- a/src/test/testResolution.ts +++ b/src/test/testResolution.ts @@ -119,12 +119,9 @@ describe('Resolve', () => { done(new Error('Too many subscription fired')); }, ).catch(done); - }) - .catch(done); - }); - - setTimeout(() => { - pubsub.publish('printRootChannel', { printRoot: subscriptionRoot }); + }).then(() => { + pubsub.publish('printRootChannel', { printRoot: subscriptionRoot }); + }).catch(done); }); firstSubsTriggered From 8eb035911f4538dbb0ee9a2fad9581b0afbf103c Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 15 Apr 2019 11:58:28 -0400 Subject: [PATCH 004/250] [fix] allow renaming of subscription root fields --- CHANGELOG.md | 4 ++ src/stitching/delegateToSchema.ts | 7 ++- src/test/testAlternateMergeSchemas.ts | 64 ++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a4e2ec26c4..7be9add19fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ [@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).
### 4.0.4 diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index ff6bab772e9..9f38315968b 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -125,12 +125,11 @@ async function delegateToSchemaImplementation( // "subscribe" to the subscription result and map the result through the transforms return mapAsyncIterator(executionResult, result => { const transformedResult = applyResultTransforms(result, transforms); - const subscriptionKey = Object.keys(result.data)[0]; - // for some reason the returned transformedResult needs to be nested inside the root subscription field - // does not work otherwise... + // wrap with fieldName to return for an additional round of resolutioon + // with payload as rootValue return { - [subscriptionKey]: transformedResult, + [info.fieldName]: transformedResult, }; }); } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index e593d0028e0..a7f4b3a422a 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -1,7 +1,13 @@ /* tslint:disable:no-unused-expression */ import { expect } from 'chai'; -import { graphql, GraphQLSchema } from 'graphql'; +import { + graphql, + GraphQLSchema, + ExecutionResult, + subscribe, + parse, +} from 'graphql'; import mergeSchemas from '../stitching/mergeSchemas'; import { transformSchema, @@ -9,7 +15,14 @@ import { RenameTypes, RenameRootFields, } from '../transforms'; -import { propertySchema, bookingSchema } from './testingSchemas'; +import { + propertySchema, + bookingSchema, + subscriptionSchema, + subscriptionPubSub, + subscriptionPubSubTrigger, +} from './testingSchemas'; +import { forAwaitEach } from 'iterall'; let linkSchema = ` """ @@ -78,11 +91,23 @@ describe('merge schemas through transforms', () => { (operation: string, name: string) => `Bookings_${name}`, ), ]); + const transformedSubscriptionSchema = transformSchema(subscriptionSchema, [ + new FilterRootFields( + (operation: string, rootField: string) => + // must include a Query type otherwise graphql will error + 'Query.notifications' === `${operation}.${rootField}` || + 'Subscription.notifications' === `${operation}.${rootField}`, + ), + new RenameTypes((name: string) => `Subscriptions_${name}`), + new RenameRootFields( + (operation: string, name: string) => `Subscriptions_${name}`), + ]); mergedSchema = mergeSchemas({ schemas: [ transformedPropertySchema, transformedBookingSchema, + transformedSubscriptionSchema, linkSchema, ], resolvers: { @@ -234,6 +259,41 @@ describe('merge schemas through transforms', () => { }, }); }); + + it('local subscriptions should work even if root fields are renamed', done => { + const originalNotification = { + notifications: { + text: 'Hello world', + }, + }; + + const transformedNotification = { + Subscriptions_notifications: originalNotification.notifications + }; + + const subscription = parse(` + subscription Subscription { + Subscriptions_notifications { + text + } + } + `); + + let notificationCnt = 0; + subscribe(mergedSchema, subscription) + .then(results => { + forAwaitEach( + results as AsyncIterable, + (result: ExecutionResult) => { + expect(result).to.have.property('data'); + expect(result.data).to.deep.equal(transformedNotification); + !notificationCnt++ ? done() : null; + }, + ).catch(done); + }).then(() => { + subscriptionPubSub.publish(subscriptionPubSubTrigger, originalNotification); + }).catch(done); + }); }); describe('interface resolver inheritance', () => { From d783d785ab90a44e9f72263202e1e863a77cecf2 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 21 May 2019 09:32:42 -0400 Subject: [PATCH 005/250] [feat] Add transformers to rename, filter, and arbitrarily transform object fields, fixes #819. --- CHANGELOG.md | 2 + docs/source/schema-transforms.md | 48 +++++++ src/test/testAlternateMergeSchemas.ts | 128 +++++++++++++++++- src/transforms/FilterObjectFields.ts | 25 ++++ src/transforms/RenameObjectFields.ts | 29 ++++ src/transforms/TransformObjectFields.ts | 168 ++++++++++++++++++++++++ src/transforms/index.ts | 3 + 7 files changed, 399 insertions(+), 4 deletions(-) create mode 100644 src/transforms/FilterObjectFields.ts create mode 100644 src/transforms/RenameObjectFields.ts create mode 100644 src/transforms/TransformObjectFields.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be9add19fc..0fd04ec70d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ 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/docs/source/schema-transforms.md b/docs/source/schema-transforms.md index 3102105c9a0..389368f51f7 100644 --- a/docs/source/schema-transforms.md +++ b/docs/source/schema-transforms.md @@ -170,6 +170,54 @@ RenameRootFields( ) ``` +### 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 resolve function 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, +) +``` + ### Other * `ExtractField({ from: Array, to: Array })` - move selection at `from` path to `to` path. diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index a7f4b3a422a..8b4c4deef24 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -7,6 +7,9 @@ import { ExecutionResult, subscribe, parse, + GraphQLField, + GraphQLNamedType, + FieldNode } from 'graphql'; import mergeSchemas from '../stitching/mergeSchemas'; import { @@ -14,6 +17,9 @@ import { FilterRootFields, RenameTypes, RenameRootFields, + RenameObjectFields, + FilterObjectFields, + TransformObjectFields, } from '../transforms'; import { propertySchema, @@ -23,6 +29,7 @@ import { subscriptionPubSubTrigger, } from './testingSchemas'; import { forAwaitEach } from 'iterall'; +import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; let linkSchema = ` """ @@ -79,7 +86,7 @@ describe('merge schemas through transforms', () => { 'Query.properties' === `${operation}.${rootField}`, ), new RenameTypes((name: string) => `Properties_${name}`), - new RenameRootFields((name: string) => `Properties_${name}`), + new RenameRootFields((operation: string, name: string) => `Properties_${name}`), ]); const transformedBookingSchema = transformSchema(bookingSchema, [ new FilterRootFields( @@ -87,9 +94,7 @@ describe('merge schemas through transforms', () => { 'Query.bookings' === `${operation}.${rootField}`, ), new RenameTypes((name: string) => `Bookings_${name}`), - new RenameRootFields( - (operation: string, name: string) => `Bookings_${name}`, - ), + new RenameRootFields((operation: string, name: string) => `Bookings_${name}`), ]); const transformedSubscriptionSchema = transformSchema(subscriptionSchema, [ new FilterRootFields( @@ -296,6 +301,121 @@ describe('merge schemas through transforms', () => { }); }); +describe('transform object fields', () => { + let transformedPropertySchema: GraphQLSchema; + + before(async () => { + const resolveType = createResolveType((name: string, type: GraphQLNamedType): GraphQLNamedType => type); + transformedPropertySchema = transformSchema(propertySchema, [ + new TransformObjectFields( + (typeName: string, fieldName: string, field: GraphQLField) => { + const fieldConfig = fieldToFieldConfig(field, resolveType, true); + if (typeName !== 'Property' || fieldName !== 'name') { + return fieldConfig; + } + fieldConfig.resolve = () => 'test'; + return fieldConfig; + }, + (typeName: string, fieldName: string, fieldNode: FieldNode) => { + if (typeName !== 'Property' || fieldName !== 'name') { + return fieldNode; + } + const newFieldNode = { + ...fieldNode, + name: { + ...fieldNode.name, + value: 'id' + } + }; + return newFieldNode; + } + ) + ]); + }); + + it('should work', async () => { + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + id + name + location { + name + } + } + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + expect(result).to.deep.equal({ + data: { + propertyById: { + id: 'p1', + name: 'test', + location: { + name: 'Helsinki', + }, + }, + }, + }); + }); +}); + +describe('filter and rename object fields', () => { + let transformedPropertySchema: GraphQLSchema; + + before(async () => { + transformedPropertySchema = transformSchema(propertySchema, [ + new RenameTypes((name: string) => `New_${name}`), + new FilterObjectFields((typeName: string, fieldName: string) => + (typeName !== 'NewProperty' || fieldName === 'id' || fieldName === 'name' || fieldName === 'location') + ), + new RenameObjectFields((typeName: string, fieldName: string) => (typeName === 'New_Property' ? `new_${fieldName}` : fieldName)) + ]); + }); + + it('should work', async () => { + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + new_id + new_name + new_location { + name + } + } + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + expect(result).to.deep.equal({ + data: { + propertyById: { + new_id: 'p1', + new_name: 'Super great hotel', + new_location: { + name: 'Helsinki', + }, + }, + }, + }); + }); +}); + describe('interface resolver inheritance', () => { const testSchemaWithInterfaceResolvers = ` interface Node { diff --git a/src/transforms/FilterObjectFields.ts b/src/transforms/FilterObjectFields.ts new file mode 100644 index 00000000000..cd0ca806f94 --- /dev/null +++ b/src/transforms/FilterObjectFields.ts @@ -0,0 +1,25 @@ +import { GraphQLField, GraphQLSchema } from 'graphql'; +import { Transform } from './transforms'; +import TransformObjectFields from './TransformObjectFields'; + +export type ObjectFilter = (typeName: string, fieldName: string, field: GraphQLField) => boolean; + +export default class FilterObjectFields implements Transform { + private transformer: TransformObjectFields; + + constructor(filter: ObjectFilter) { + this.transformer = new TransformObjectFields( + (typeName: string, fieldName: string, field: GraphQLField) => { + if (filter(typeName, fieldName, field)) { + return undefined; + } else { + return null; + } + } + ); + } + + public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { + return this.transformer.transformSchema(originalSchema); + } +} diff --git a/src/transforms/RenameObjectFields.ts b/src/transforms/RenameObjectFields.ts new file mode 100644 index 00000000000..01426897e05 --- /dev/null +++ b/src/transforms/RenameObjectFields.ts @@ -0,0 +1,29 @@ +import { GraphQLNamedType, GraphQLField, GraphQLSchema } from 'graphql'; +import { Transform } from './transforms'; +import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; +import { Request } from '../Interfaces'; +import TransformObjectFields from './TransformObjectFields'; + +export default class RenameObjectFields implements Transform { + private transformer: TransformObjectFields; + + constructor(renamer: (typeName: string, fieldName: string, field: GraphQLField) => string) { + const resolveType = createResolveType((name: string, type: GraphQLNamedType): GraphQLNamedType => type); + this.transformer = new TransformObjectFields( + (typeName: string, fieldName: string, field: GraphQLField) => { + return { + name: renamer(typeName, fieldName, field), + field: fieldToFieldConfig(field, resolveType, true) + }; + } + ); + } + + public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { + return this.transformer.transformSchema(originalSchema); + } + + public transformRequest(originalRequest: Request): Request { + return this.transformer.transformRequest(originalRequest); + } +} diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts new file mode 100644 index 00000000000..08dbc0af9fc --- /dev/null +++ b/src/transforms/TransformObjectFields.ts @@ -0,0 +1,168 @@ +import { + GraphQLObjectType, + GraphQLSchema, + GraphQLNamedType, + GraphQLField, + GraphQLFieldConfig, + GraphQLType, + DocumentNode, + FieldNode, + TypeInfo, + visit, + visitWithTypeInfo, + Kind +} from 'graphql'; +import isEmptyObject from '../isEmptyObject'; +import { Request } from '../Interfaces'; +import { Transform } from './transforms'; +import { visitSchema, VisitSchemaKind } from './visitSchema'; +import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; + +export type ObjectFieldTransformer = ( + typeName: string, + fieldName: string, + field: GraphQLField +) => GraphQLFieldConfig | { name: string; field: GraphQLFieldConfig } | null | undefined; + +export type FieldNodeTransformer = ( + typeName: string, + fieldName: string, + fieldNode: FieldNode +) => FieldNode; + +type FieldMapping = { + [typeName: string]: { + [newFieldName: string]: string; + }; +}; + +export default class TransformObjectFields implements Transform { + private objectFieldTransformer: ObjectFieldTransformer; + private fieldNodeTransformer: FieldNodeTransformer; + private schema: GraphQLSchema; + private mapping: FieldMapping; + + constructor(objectFieldTransformer: ObjectFieldTransformer, fieldNodeTransformer?: FieldNodeTransformer) { + this.objectFieldTransformer = objectFieldTransformer; + this.fieldNodeTransformer = fieldNodeTransformer; + this.mapping = {}; + } + + public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { + this.schema = originalSchema; + return visitSchema(originalSchema, { + [VisitSchemaKind.ROOT_OBJECT]: () => { + return undefined; + }, + [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => { + return this.transformFields(type, this.objectFieldTransformer); + } + }); + } + + public transformRequest(originalRequest: Request): Request { + const document = this.reverseMapping(originalRequest.document, this.mapping, this.fieldNodeTransformer); + return { + ...originalRequest, + document + }; + } + + private transformFields( + type: GraphQLObjectType, + objectFieldTransformer: ObjectFieldTransformer + ): GraphQLObjectType { + const resolveType = createResolveType( + (name: string, originalType: GraphQLNamedType): GraphQLNamedType => originalType + ); + const fields = type.getFields(); + const newFields = {}; + + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + const transformedField = objectFieldTransformer(type.name, fieldName, field); + + if (typeof transformedField === 'undefined') { + newFields[fieldName] = fieldToFieldConfig(field, resolveType, true); + } else if (transformedField !== null) { + const newName = (transformedField as { name: string; field: GraphQLFieldConfig }).name; + + if (newName) { + newFields[newName] = (transformedField as { + name: string; + field: GraphQLFieldConfig; + }).field; + if (newName !== fieldName) { + const typeName = type.name; + if (!this.mapping[typeName]) { + this.mapping[typeName] = {}; + } + this.mapping[typeName][newName] = fieldName; + + const originalResolver = (transformedField as { + name: string; + field: GraphQLFieldConfig; + }).field.resolve; + (newFields[newName] as GraphQLFieldConfig).resolve = (parent, args, context, info) => + originalResolver(parent, args, context, { + ...info, + fieldName + }); + } + } else { + newFields[fieldName] = transformedField; + } + } + }); + if (isEmptyObject(newFields)) { + return null; + } else { + return new GraphQLObjectType({ + name: type.name, + description: type.description, + astNode: type.astNode, + isTypeOf: type.isTypeOf, + fields: newFields, + interfaces: () => type.getInterfaces().map(iface => resolveType(iface)) + }); + } + } + + private reverseMapping( + document: DocumentNode, + mapping: FieldMapping, + fieldNodeTransformer?: FieldNodeTransformer + ): DocumentNode { + const typeInfo = new TypeInfo(this.schema); + const newDocument: DocumentNode = visit( + document, + visitWithTypeInfo(typeInfo, { + [Kind.FIELD](node: FieldNode): FieldNode | null | undefined { + const parentType: GraphQLType = typeInfo.getParentType(); + if (parentType) { + const parentTypeName = parentType.name; + const newName = node.name.value; + const transformedNode = fieldNodeTransformer + ? fieldNodeTransformer(parentTypeName, newName, node) + : node; + let transformedName = transformedNode.name.value; + if (mapping[parentTypeName]) { + const originalName = mapping[parentTypeName][newName]; + if (originalName) { + transformedName = originalName; + } + } + return { + ...transformedNode, + name: { + ...node.name, + value: transformedName + } + }; + } + } + }) + ); + return newDocument; + } +} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index db76efbc221..8ffe9f238f2 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -17,6 +17,9 @@ export { default as FilterTypes } from './FilterTypes'; export { default as TransformRootFields } from './TransformRootFields'; export { default as RenameRootFields } from './RenameRootFields'; export { default as FilterRootFields } from './FilterRootFields'; +export { default as TransformObjectFields } from './TransformObjectFields'; +export { default as RenameObjectFields } from './RenameObjectFields'; +export { default as FilterObjectFields } from './FilterObjectFields'; export { default as ExpandAbstractTypes } from './ExpandAbstractTypes'; export { default as ExtractField } from './ExtractField'; export { default as WrapQuery } from './WrapQuery'; From d7ce4e8e94432da9020e67126b3296d7a7ba0de2 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 2 Jun 2019 00:35:41 -0400 Subject: [PATCH 006/250] lint --- src/Interfaces.ts | 2 +- src/mock.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index d5e513c0814..a9cca8d74d6 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -71,11 +71,11 @@ export type MergeInfo = { info: GraphQLResolveInfo, transforms?: Array, ) => any; - delegateToSchema(options: IDelegateToSchemaOptions): any; fragments: Array<{ field: string; fragment: string; }>; + delegateToSchema(options: IDelegateToSchemaOptions): any; }; export type IFieldResolver> = ( diff --git a/src/mock.ts b/src/mock.ts index 9a53a2beb2b..adcfb41eb84 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -217,8 +217,8 @@ function addMockFunctionsToSchema({ // 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 isOnQueryType: boolean = schema.getQueryType() && schema.getQueryType().name === typeName; + const isOnMutationType: boolean = schema.getMutationType() && schema.getMutationType().name === typeName; if (isOnQueryType || isOnMutationType) { if (mockFunctionMap.has(typeName)) { From 97d8fe6751dab428951ee0d818fbebdb8823b2b0 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Fri, 8 Mar 2019 21:28:38 -0500 Subject: [PATCH 007/250] feat(errors): Pass through all possible errors. Use new relocatedError function to update the original GraphQLErrors with the new path. Addresses #743, #1037, #1046, apollographql/apollo-server#1582. --- src/stitching/defaultMergedResolver.ts | 42 +++-- src/stitching/errors.ts | 142 +++++++++------ src/test/testErrors.ts | 63 +++---- src/test/testMergeSchemas.ts | 233 ++++++++++++------------- src/test/testingSchemas.ts | 6 +- 5 files changed, 257 insertions(+), 229 deletions(-) diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index cbf754a03a8..ce5e65156f0 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,6 +1,16 @@ -import { GraphQLFieldResolver, responsePathAsArray } from 'graphql'; -import { locatedError } from 'graphql/error'; -import { getErrorsFromParent, annotateWithChildrenErrors } from './errors'; +import { + GraphQLFieldResolver, + responsePathAsArray, + getNullableType, + isObjectType, + isListType +} from 'graphql'; +import { + getErrorsFromParent, + annotateWithChildrenErrors, + combineErrors, + relocatedError +} from './errors'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; // Resolver that knows how to: @@ -12,26 +22,30 @@ const defaultMergedResolver: GraphQLFieldResolver = (parent, args, con } const responseKey = getResponseKeyFromInfo(info); - const errorResult = getErrorsFromParent(parent, responseKey); + const errors = getErrorsFromParent(parent, responseKey); - if (errorResult.kind === 'OWN') { - throw locatedError(new Error(errorResult.error.message), info.fieldNodes, responsePathAsArray(info.path)); + // check to see if parent is not a proxied result, i.e. if parent resolver was manually overwritten + // See https://github.com/apollographql/graphql-tools/issues/967 + if (!Array.isArray(errors)) { + return parent[info.fieldName]; } let result = parent[responseKey]; - if (result == null) { - result = parent[info.fieldName]; + // if null, throw all possible errors + if (!result && errors.length) { + throw relocatedError( + combineErrors(errors), + info.fieldNodes, + responsePathAsArray(info.path) + ); } - // subscription result mapping - if (!result && parent.data && parent.data[responseKey]) { - result = parent.data[responseKey]; + const nullableType = getNullableType(info.returnType); + if (isObjectType(nullableType) || isListType(nullableType)) { + annotateWithChildrenErrors(result, errors); } - if (errorResult.errors) { - result = annotateWithChildrenErrors(result, errorResult.errors); - } return result; }; diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 94ba6851a25..e927f9f2477 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -1,11 +1,13 @@ import { GraphQLResolveInfo, responsePathAsArray, + getNullableType, + isObjectType, + isListType, ExecutionResult, - GraphQLFormattedError, GraphQLError, + ASTNode } from 'graphql'; -import { locatedError } from 'graphql/error'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; export let ERROR_SYMBOL: any; @@ -18,9 +20,36 @@ if ( ERROR_SYMBOL = '@@__subSchemaErrors'; } -export function annotateWithChildrenErrors(object: any, childrenErrors: ReadonlyArray): any { - if (!childrenErrors || childrenErrors.length === 0) { - // Nothing to see here, move along +export function relocatedError( + originalError: Error | GraphQLError, + nodes: ReadonlyArray, + path: ReadonlyArray +): GraphQLError { + if (Array.isArray((originalError as GraphQLError).path)) { + return new GraphQLError( + (originalError as GraphQLError).message, + (originalError as GraphQLError).nodes, + (originalError as GraphQLError).source, + (originalError as GraphQLError).positions, + path ? path : (originalError as GraphQLError).path, + (originalError as GraphQLError).originalError, + (originalError as GraphQLError).extensions + ); + } + + return new GraphQLError( + originalError && originalError.message, + (originalError && (originalError as any).nodes) || nodes, + originalError && (originalError as any).source, + originalError && (originalError as any).positions, + path, + originalError, + ); +} + +export function annotateWithChildrenErrors(object: any, childrenErrors: ReadonlyArray): any { + if (!Array.isArray(childrenErrors)) { + object[ERROR_SYMBOL] = []; return object; } @@ -33,55 +62,50 @@ export function annotateWithChildrenErrors(object: any, childrenErrors: Readonly } const index = error.path[1]; const current = byIndex[index] || []; - current.push({ - ...error, - path: error.path.slice(1) - }); + current.push( + relocatedError( + error, + error.nodes, + error.path ? error.path.slice(1) : undefined + ) + ); byIndex[index] = current; }); return object.map((item, index) => annotateWithChildrenErrors(item, byIndex[index])); } - return { - ...object, - [ERROR_SYMBOL]: childrenErrors.map(error => ({ - ...error, - ...(error.path ? { path: error.path.slice(1) } : {}) - })) - }; + object[ERROR_SYMBOL] = childrenErrors.map(error => { + const newError = relocatedError( + error, + error.nodes, + error.path ? error.path.slice(1) : undefined + ); + return newError; + }); + + return object; } export function getErrorsFromParent( object: any, fieldName: string -): - | { - kind: 'OWN'; - error: any; - } - | { - kind: 'CHILDREN'; - errors?: Array; - } { - const errors = (object && object[ERROR_SYMBOL]) || []; - const childrenErrors: Array = []; +): Array { + const errors = object && object[ERROR_SYMBOL]; + + if (!Array.isArray(errors)) { + return null; + } + + const childrenErrors = []; for (const error of errors) { - if (!error.path || (error.path.length === 1 && error.path[0] === fieldName)) { - return { - kind: 'OWN', - error - }; - } else if (error.path[0] === fieldName) { + if (!error.path || error.path[0] === fieldName) { childrenErrors.push(error); } } - return { - kind: 'CHILDREN', - errors: childrenErrors - }; + return childrenErrors; } class CombinedError extends Error { @@ -101,28 +125,38 @@ export function checkResultAndHandleErrors( responseKey = getResponseKeyFromInfo(info); } - if (result.errors && (!result.data || result.data[responseKey] == null)) { - // apollo-link-http & http-link-dataloader need the - // result property to be passed through for better error handling. - // If there is only one error, which contains a result property, pass the error through - const newError = - result.errors.length === 1 && hasResult(result.errors[0]) - ? result.errors[0] - : new CombinedError(concatErrors(result.errors), result.errors); - throw locatedError(newError, info.fieldNodes, responsePathAsArray(info.path)); + if (!result.data || !result.data[responseKey]) { + if (result.errors) { + throw relocatedError( + combineErrors(result.errors), + info.fieldNodes, + responsePathAsArray(info.path) + ); + } } + result.errors = result.errors || []; + let resultObject = result.data[responseKey]; - if (result.errors) { - resultObject = annotateWithChildrenErrors(resultObject, result.errors as ReadonlyArray); + const nullableType = getNullableType(info.returnType); + if (isObjectType(nullableType) || isListType(nullableType)) { + annotateWithChildrenErrors(resultObject, result.errors); } return resultObject; } -function concatErrors(errors: ReadonlyArray) { - return errors.map(error => error.message).join('\n'); -} - -function hasResult(error: any) { - return error.result || error.extensions || (error.originalError && error.originalError.result); +export function combineErrors(errors: ReadonlyArray): GraphQLError | CombinedError { + if (errors.length === 1) { + return new GraphQLError( + errors[0].message, + errors[0].nodes, + errors[0].source, + errors[0].positions, + errors[0].path, + errors[0].originalError, + errors[0].extensions + ); + } else { + return new CombinedError(errors.map(error => error.message).join('\n'), errors); + } } diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index 05bcf69c292..5758f0460a0 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -1,17 +1,14 @@ import { assert } from 'chai'; import { GraphQLResolveInfo, GraphQLError } from 'graphql'; -import { checkResultAndHandleErrors, getErrorsFromParent, ERROR_SYMBOL } from '../stitching/errors'; +import { + relocatedError, + checkResultAndHandleErrors, + getErrorsFromParent, + ERROR_SYMBOL +} from '../stitching/errors'; import 'mocha'; -class ErrorWithResult extends GraphQLError { - public result: any; - constructor(message: string, result: any) { - super(message); - this.result = result; - } -} - class ErrorWithExtensions extends GraphQLError { constructor(message: string, code: string) { super(message, null, null, null, null, null, { code }); @@ -19,28 +16,43 @@ class ErrorWithExtensions extends GraphQLError { } describe('Errors', () => { + describe('relocatedError', () => { + it('should adjust the path of a GraphqlError', () => { + const originalError = new GraphQLError('test', null, null, null, ['test']); + const newError = relocatedError(originalError, null, ['test', 1]); + const expectedError = new GraphQLError('test', null, null, null, ['test', 1]); + assert.deepEqual(newError, expectedError); + }); + + it('should also locate a non GraphQLError', () => { + const originalError = new Error('test'); + const newError = relocatedError(originalError, null, ['test', 1]); + const expectedError = new GraphQLError('test', null, null, null, ['test', 1]); + assert.deepEqual(newError, expectedError); + }); + }); + describe('getErrorsFromParent', () => { - it('should return OWN error kind if path is not defined', () => { + it('should return all errors including if path is not defined', () => { const mockErrors = { responseKey: '', [ERROR_SYMBOL]: [ { message: 'Test error without path' - } + } as GraphQLError ] }; - assert.deepEqual(getErrorsFromParent(mockErrors, 'responseKey'), { - kind: 'OWN', - error: mockErrors[ERROR_SYMBOL][0] - }); + assert.deepEqual(getErrorsFromParent(mockErrors, 'responseKey'), + [mockErrors[ERROR_SYMBOL][0]] + ); }); }); describe('checkResultAndHandleErrors', () => { - it('persists single error with a result', () => { + it('persists single error', () => { const result = { - errors: [new ErrorWithResult('Test error', 'result')] + errors: [new GraphQLError('Test error')] }; try { checkResultAndHandleErrors(result, {} as GraphQLResolveInfo, 'responseKey'); @@ -63,23 +75,6 @@ describe('Errors', () => { } }); - it('persists original errors without a result', () => { - const result = { - errors: [new GraphQLError('Test error')] - }; - try { - checkResultAndHandleErrors(result, {} as GraphQLResolveInfo, 'responseKey'); - } catch (e) { - assert.equal(e.message, 'Test error'); - assert.isNotEmpty(e.originalError); - assert.isNotEmpty(e.originalError.errors); - assert.lengthOf(e.originalError.errors, result.errors.length); - result.errors.forEach((error, i) => { - assert.deepEqual(e.originalError.errors[i], error); - }); - } - }); - it('combines errors and persists the original errors', () => { const result = { errors: [ diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index d1d24f56873..6bd1ded30f2 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -30,6 +30,8 @@ import { forAwaitEach } from 'iterall'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { IResolvers } from '../Interfaces'; +const removeLocations = ({ locations, ...rest }: any): any => ({ ...rest }); + const testCombinations = [ { name: 'local', @@ -2152,13 +2154,12 @@ fragment BookingFragment on Booking { ${bookingFragment} }`, ); - expect(mergedResult).to.deep.equal({ - errors: propertyResult.errors, - data: { - ...propertyResult.data, - ...bookingResult.data, - }, + expect(mergedResult.data).to.deep.equal({ + ...propertyResult.data, + ...bookingResult.data, }); + expect(mergedResult.errors.map(removeLocations)).to.deep.equal( + propertyResult.errors.map(removeLocations)); const mergedResult2 = await graphql( mergedSchema, @@ -2170,21 +2171,13 @@ fragment BookingFragment on Booking { `, ); - expect(mergedResult2).to.deep.equal({ - errors: [ - { - locations: [ - { - column: 19, - line: 3, - }, - ], - message: 'Sample error non-null!', - path: ['errorTestNonNull'], - }, - ], - data: null, - }); + expect(mergedResult2.data).to.equal(null); + expect(mergedResult2.errors.map(removeLocations)).to.deep.equal([ + { + message: 'Sample error non-null!', + path: ['errorTestNonNull'], + }, + ]); }); it('nested errors', async () => { @@ -2205,114 +2198,102 @@ fragment BookingFragment on Booking { `, ); - expect(result).to.deep.equal({ - data: { - propertyById: { - bookings: [ - { - bookingErrorAlias: null, - error: null, - id: 'b1', - }, - { - bookingErrorAlias: null, - error: null, - id: 'b2', - }, - { - bookingErrorAlias: null, - error: null, - id: 'b3', - }, - ], - error: null, - errorAlias: null, + expect(result.data).to.deep.equal({ + propertyById: { + bookings: [ + { + bookingErrorAlias: null, + error: null, + id: 'b1', + }, + { + bookingErrorAlias: null, + error: null, + id: 'b2', + }, + { + bookingErrorAlias: null, + error: null, + id: 'b3', + }, + ], + error: null, + errorAlias: null, + } + }); + expect(result.errors.map(removeLocations)).to.deep.equal([ + { + extensions: { + code: 'SOME_CUSTOM_CODE' }, + message: 'Property.error error', + path: ['propertyById', 'error'], }, - errors: [ - { - locations: [ - { - column: 17, - line: 4, - }, - ], - message: 'Property.error error', - path: ['propertyById', 'error'], - }, - { - locations: [ - { - column: 17, - line: 5, - }, - ], - message: 'Property.error error', - path: ['propertyById', 'errorAlias'], - }, - { - locations: [ - { - column: 19, - line: 8, - }, - ], - message: 'Booking.error error', - path: ['propertyById', 'bookings', 0, 'error'], - }, - { - locations: [ - { - column: 19, - line: 9, - }, - ], - message: 'Booking.error error', - path: ['propertyById', 'bookings', 0, 'bookingErrorAlias'], - }, - { - locations: [ - { - column: 19, - line: 8, - }, - ], - message: 'Booking.error error', - path: ['propertyById', 'bookings', 1, 'error'], - }, - { - locations: [ - { - column: 19, - line: 9, - }, - ], - message: 'Booking.error error', - path: ['propertyById', 'bookings', 1, 'bookingErrorAlias'], - }, - { - locations: [ - { - column: 19, - line: 8, - }, - ], - message: 'Booking.error error', - path: ['propertyById', 'bookings', 2, 'error'], - }, - { - locations: [ - { - column: 19, - line: 9, - }, - ], - message: 'Booking.error error', - path: ['propertyById', 'bookings', 2, 'bookingErrorAlias'], + { + extensions: { + code: 'SOME_CUSTOM_CODE' }, - ], - }); + message: 'Property.error error', + path: ['propertyById', 'errorAlias'], + }, + { + message: 'Booking.error error', + path: ['propertyById', 'bookings', 0, 'error'], + }, + { + message: 'Booking.error error', + path: ['propertyById', 'bookings', 0, 'bookingErrorAlias'], + }, + { + message: 'Booking.error error', + path: ['propertyById', 'bookings', 1, 'error'], + }, + { + message: 'Booking.error error', + path: ['propertyById', 'bookings', 1, 'bookingErrorAlias'], + }, + { + message: 'Booking.error error', + path: ['propertyById', 'bookings', 2, 'error'], + }, + { + message: 'Booking.error error', + path: ['propertyById', 'bookings', 2, 'bookingErrorAlias'], + } + ]); }); + + it( + 'should preserve custom error extensions from the original schema, ' + + 'when merging schemas', + async () => { + const propertyQuery = ` + query { + properties(limit: 1) { + error + } + } + `; + + const propertyResult = await graphql( + propertySchema, + propertyQuery, + ); + + const mergedResult = await graphql( + mergedSchema, + propertyQuery, + ); + + [propertyResult, mergedResult].forEach((result) => { + expect(result.errors).to.exist; + expect(result.errors.length > 0).to.be.true; + const error = result.errors[0]; + expect(error.extensions).to.exist; + expect(error.extensions.code).to.equal('SOME_CUSTOM_CODE'); + }); + } + ); }); describe('types in schema extensions', () => { @@ -2729,7 +2710,7 @@ fragment BookingFragment on Booking { }); }); - it('defaultMergedResolver should work with non-root aliases', async () => { + it('defaultMergedResolver should work with aliases if parent merged resolver is manually overwritten', async () => { // Source: https://github.com/apollographql/graphql-tools/issues/967 const typeDefs = ` type Query { diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index 5e428cb6784..ddac48b88a4 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -407,7 +407,11 @@ const propertyResolvers: IResolvers = { Property: { error() { - throw new Error('Property.error error'); + const error = new Error('Property.error error'); + (error as any).extensions = { + code: 'SOME_CUSTOM_CODE', + }; + throw error; }, }, }; From 9fda49909eb0cb74d70dbfdf92e4da529b7e24db Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 13 Jun 2019 00:05:52 -0400 Subject: [PATCH 008/250] fix(stitching): fix regression Refactoring in v5.1.0 introduced a regression when nullable root fields returned null without errors. --- src/stitching/errors.ts | 2 ++ src/test/testAlternateMergeSchemas.ts | 28 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index e927f9f2477..534f599ea5d 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -132,6 +132,8 @@ export function checkResultAndHandleErrors( info.fieldNodes, responsePathAsArray(info.path) ); + } else { + return null; } } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 8b4c4deef24..81fb9aaf699 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -30,6 +30,7 @@ import { } from './testingSchemas'; import { forAwaitEach } from 'iterall'; import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; +import { makeExecutableSchema } from '../makeExecutableSchema'; let linkSchema = ` """ @@ -509,3 +510,30 @@ async () => { }); }); +describe('mergeSchemas', () => { + it('can merge null root fields', async () => { + const schema = makeExecutableSchema({ + typeDefs: ` + type Query { + test: Test + } + type Test { + field: String + } + `, + resolvers: { + Query: { + test: () => null + } + } + }); + const mergedSchema = mergeSchemas({ + schemas: [schema] + }); + + const query = `{ test { field } }`; + const response = await graphql(mergedSchema, query); + expect(response.data.test).to.be.null; + expect(response.errors).to.be.undefined; + }); +}); From c7f82acf73a7ceff813da1b08cf43b1cff3a739a Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 16 Jun 2019 07:41:27 -0400 Subject: [PATCH 009/250] feat(stitching): restore onTypeConflict option to mergeSchemas --- docs/source/schema-stitching.md | 6 +- src/stitching/mergeSchemas.ts | 36 ++++++++++- src/test/testAlternateMergeSchemas.ts | 87 +++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 6 deletions(-) diff --git a/docs/source/schema-stitching.md b/docs/source/schema-stitching.md index 1770fa66bc3..878b4810fac 100644 --- a/docs/source/schema-stitching.md +++ b/docs/source/schema-stitching.md @@ -389,12 +389,12 @@ type OnTypeConflict = ( The `onTypeConflict` option to `mergeSchemas` allows customization of type resolving logic. -The default behavior of `mergeSchemas` is to take the first encountered type of all the types with the same name. If there are conflicts, `onTypeConflict` enables explicit selection of the winning type. +The default behavior of `mergeSchemas` is to take the *last* encountered type of all the types with the same name, with a warning that type conflicts have been encountered. If specified, `onTypeConflict` enables explicit selection of the winning type. -For example, here's how we could select the last type among multiple types with the same name: +For example, here's how we could select the *first* type among multiple types with the same name: ```js -const onTypeConflict = (left, right) => right; +const onTypeConflict = (left, right) => left; ``` And here's how we might select the type whose schema has the latest `version`: diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index d704262f11c..d936887e3f9 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -56,6 +56,10 @@ export type OnTypeConflict = ( }, ) => GraphQLNamedType; +type CandidateSelector = ( + candidates: Array, +) => MergeTypeCandidate; + export default function mergeSchemas({ schemas, onTypeConflict, @@ -76,6 +80,7 @@ export default function mergeSchemas({ }): GraphQLSchema { return mergeSchemasImplementation({ schemas, + onTypeConflict, resolvers, schemaDirectives, inheritResolversFromInterfaces, @@ -85,6 +90,7 @@ export default function mergeSchemas({ function mergeSchemasImplementation({ schemas, + onTypeConflict, resolvers, schemaDirectives, inheritResolversFromInterfaces, @@ -93,6 +99,7 @@ function mergeSchemasImplementation({ schemas: Array< string | GraphQLSchema | DocumentNode | Array >; + onTypeConflict?: OnTypeConflict; resolvers?: IResolversParameter; schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor }; inheritResolversFromInterfaces?: boolean; @@ -225,6 +232,7 @@ function mergeSchemasImplementation({ const resultType: VisitTypeResult = defaultVisitType( typeName, typeCandidates[typeName], + onTypeConflict ? onTypeConflictToCandidateSelector(onTypeConflict) : undefined ); if (resultType === null) { types[typeName] = null; @@ -441,12 +449,34 @@ function addTypeCandidate( typeCandidates[name].push(typeCandidate); } +function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): CandidateSelector { + return cands => + cands.reduce((prev, next) => { + const type = onTypeConflict(prev.type, next.type, { + left: { + schema: prev.schema, + }, + right: { + schema: next.schema, + }, + }); + if (prev.type === type) { + return prev; + } else if (next.type === type) { + return next; + } else { + return { + schemaName: 'unknown', + type + }; + } + }); +} + function defaultVisitType( name: string, candidates: Array, - candidateSelector?: ( - candidates: Array, - ) => MergeTypeCandidate, + candidateSelector?: CandidateSelector ) { if (!candidateSelector) { candidateSelector = cands => cands[cands.length - 1]; diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 81fb9aaf699..7db04074e04 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -537,3 +537,90 @@ describe('mergeSchemas', () => { expect(response.errors).to.be.undefined; }); }); + +describe('onTypeConflict', () => { + let schema1: GraphQLSchema; + let schema2: GraphQLSchema; + + beforeEach(() => { + const typeDefs1 = ` + type Query { + test1: Test + } + + type Test { + fieldA: String + fieldB: String + } + `; + + const typeDefs2 = ` + type Query { + test2: Test + } + + type Test { + fieldA: String + fieldC: String + } + `; + + schema1 = makeExecutableSchema({ + typeDefs: typeDefs1, + resolvers: { + Query: { + test1: () => ({}) + }, + Test: { + fieldA: () => 'A', + fieldB: () => 'B' + } + } + }); + + schema2 = makeExecutableSchema({ + typeDefs: typeDefs2, + resolvers: { + Query: { + test2: () => ({}) + }, + Test: { + fieldA: () => 'A', + fieldC: () => 'C' + } + } + }); + }) + + it('by default takes last type', async () => { + const mergedSchema = mergeSchemas({ + schemas: [schema1, schema2] + }); + const result1 = await graphql(mergedSchema, `{ test2 { fieldC } }`); + expect(result1.data.test2.fieldC).to.equal('C'); + const result2 = await graphql(mergedSchema, `{ test2 { fieldB } }`); + expect(result2.data).to.be.undefined; + }); + + it('can use onTypeConflict to select last type', async () => { + const mergedSchema = mergeSchemas({ + schemas: [schema1, schema2], + onTypeConflict: (left, right) => right + }); + const result1 = await graphql(mergedSchema, `{ test2 { fieldC } }`); + expect(result1.data.test2.fieldC).to.equal('C'); + const result2 = await graphql(mergedSchema, `{ test2 { fieldB } }`); + expect(result2.data).to.be.undefined; + }); + + it('can use onTypeConflict to select first type', async () => { + const mergedSchema = mergeSchemas({ + schemas: [schema1, schema2], + onTypeConflict: (left) => left + }); + const result1 = await graphql(mergedSchema, `{ test1 { fieldB } }`); + expect(result1.data.test1.fieldB).to.equal('B'); + const result2 = await graphql(mergedSchema, `{ test1 { fieldC } }`); + expect(result2.data).to.be.undefined; + }); +}); From c4ddfcc97ce53cf956be0a2b22f1752f1b113858 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 16 Jun 2019 14:18:01 -0400 Subject: [PATCH 010/250] fix(stitching): nested enums --- src/stitching/checkResultAndHandleErrors.ts | 71 ++++++++++++++++++++ src/stitching/defaultMergedResolver.ts | 35 ++-------- src/stitching/delegateToSchema.ts | 8 --- src/stitching/errors.ts | 38 ----------- src/stitching/makeRemoteExecutableSchema.ts | 2 +- src/test/testErrors.ts | 2 +- src/test/testMergeSchemas.ts | 45 +++++++------ src/transforms/CheckResultAndHandleErrors.ts | 2 +- src/transforms/ConvertEnumResponse.ts | 18 ----- 9 files changed, 104 insertions(+), 117 deletions(-) create mode 100644 src/stitching/checkResultAndHandleErrors.ts delete mode 100644 src/transforms/ConvertEnumResponse.ts diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts new file mode 100644 index 00000000000..18231f0fb52 --- /dev/null +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -0,0 +1,71 @@ +import { + GraphQLResolveInfo, + responsePathAsArray, + getNullableType, + isObjectType, + isListType, + isEnumType, + ExecutionResult, + GraphQLError, +} from 'graphql'; +import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; +import { + relocatedError, + combineErrors, + annotateWithChildrenErrors +} from './errors'; + +export function checkResultAndHandleErrors( + result: ExecutionResult, + info: GraphQLResolveInfo, + responseKey?: string +): any { + if (!responseKey) { + responseKey = getResponseKeyFromInfo(info); + } + + if (!result.data) { + if (result.errors) { + throw relocatedError( + combineErrors(result.errors), + info.fieldNodes, + responsePathAsArray(info.path) + ); + } else { + return null; + } + } + + return handleResult(info, result.data[responseKey], result.errors || []); +} + +export function handleResult( + info: GraphQLResolveInfo, + resultObject: any, + errors: ReadonlyArray +): any { + if (!resultObject) { + if (errors.length) { + throw relocatedError( + combineErrors(errors), + info.fieldNodes, + responsePathAsArray(info.path) + ); + } else { + return null; + } + } + + const nullableType = getNullableType(info.returnType); + + if (isObjectType(nullableType) || isListType(nullableType)) { + annotateWithChildrenErrors(resultObject, errors); + } else if (isEnumType(nullableType)) { + const value = nullableType.getValue(resultObject); + if (value) { + return value.value; + } + } + + return resultObject; +} diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index ce5e65156f0..f95c725edc3 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,21 +1,12 @@ -import { - GraphQLFieldResolver, - responsePathAsArray, - getNullableType, - isObjectType, - isListType -} from 'graphql'; -import { - getErrorsFromParent, - annotateWithChildrenErrors, - combineErrors, - relocatedError -} from './errors'; +import { GraphQLFieldResolver } from 'graphql'; +import { getErrorsFromParent } from './errors'; +import { handleResult } from './checkResultAndHandleErrors'; 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 const defaultMergedResolver: GraphQLFieldResolver = (parent, args, context, info) => { if (!parent) { return null; @@ -30,23 +21,7 @@ const defaultMergedResolver: GraphQLFieldResolver = (parent, args, con return parent[info.fieldName]; } - let result = parent[responseKey]; - - // if null, throw all possible errors - if (!result && errors.length) { - throw relocatedError( - combineErrors(errors), - info.fieldNodes, - responsePathAsArray(info.path) - ); - } - - const nullableType = getNullableType(info.returnType); - if (isObjectType(nullableType) || isListType(nullableType)) { - annotateWithChildrenErrors(result, errors); - } - - return result; + return handleResult(info, parent[responseKey], errors); }; export default defaultMergedResolver; diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 9f38315968b..b2c1ced76ec 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -14,7 +14,6 @@ import { GraphQLSchema, ExecutionResult, NameNode, - isEnumType, } from 'graphql'; import { Operation, Request, IDelegateToSchemaOptions } from '../Interfaces'; @@ -31,7 +30,6 @@ import CheckResultAndHandleErrors from '../transforms/CheckResultAndHandleErrors import mapAsyncIterator from './mapAsyncIterator'; import ExpandAbstractTypes from '../transforms/ExpandAbstractTypes'; import ReplaceFieldWithFragment from '../transforms/ReplaceFieldWithFragment'; -import ConvertEnumResponse from '../transforms/ConvertEnumResponse'; export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, @@ -85,12 +83,6 @@ async function delegateToSchemaImplementation( new CheckResultAndHandleErrors(info, options.fieldName), ]); - if (isEnumType(options.info.returnType)) { - transforms = transforms.concat( - new ConvertEnumResponse(options.info.returnType), - ); - } - const processedRequest = applyRequestTransforms(rawRequest, transforms); if (!options.skipValidation) { diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 534f599ea5d..221004422dc 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -1,14 +1,7 @@ import { - GraphQLResolveInfo, - responsePathAsArray, - getNullableType, - isObjectType, - isListType, - ExecutionResult, GraphQLError, ASTNode } from 'graphql'; -import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; export let ERROR_SYMBOL: any; if ( @@ -116,37 +109,6 @@ class CombinedError extends Error { } } -export function checkResultAndHandleErrors( - result: ExecutionResult, - info: GraphQLResolveInfo, - responseKey?: string -): any { - if (!responseKey) { - responseKey = getResponseKeyFromInfo(info); - } - - if (!result.data || !result.data[responseKey]) { - if (result.errors) { - throw relocatedError( - combineErrors(result.errors), - info.fieldNodes, - responsePathAsArray(info.path) - ); - } else { - return null; - } - } - - result.errors = result.errors || []; - - let resultObject = result.data[responseKey]; - const nullableType = getNullableType(info.returnType); - if (isObjectType(nullableType) || isListType(nullableType)) { - annotateWithChildrenErrors(resultObject, result.errors); - } - return resultObject; -} - export function combineErrors(errors: ReadonlyArray): GraphQLError | CombinedError { if (errors.length === 1) { return new GraphQLError( diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 5ea5c7789a0..06e318e40ee 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -29,7 +29,7 @@ import { makeExecutableSchema } from '../makeExecutableSchema'; import { recreateType } from './schemaRecreation'; import resolveParentFromTypename from './resolveFromParentTypename'; import defaultMergedResolver from './defaultMergedResolver'; -import { checkResultAndHandleErrors } from './errors'; +import { checkResultAndHandleErrors } from './checkResultAndHandleErrors'; import { observableToAsyncIterable } from './observableToAsyncIterable'; export type ResolverFn = ( diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index 5758f0460a0..3a53429f6ff 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -2,10 +2,10 @@ import { assert } from 'chai'; import { GraphQLResolveInfo, GraphQLError } from 'graphql'; import { relocatedError, - checkResultAndHandleErrors, getErrorsFromParent, ERROR_SYMBOL } from '../stitching/errors'; +import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; import 'mocha'; diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 6bd1ded30f2..c8a23245987 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -97,6 +97,11 @@ let enumTest = ` TEST @deprecated(reason: "This is deprecated") } + type EnumWrapper { + color: Color + numericEnum: NumericEnum + } + schema { query: Query } @@ -104,6 +109,7 @@ let enumTest = ` type Query { color: Color numericEnum: NumericEnum + wrappedEnum: EnumWrapper } `; @@ -125,6 +131,12 @@ enumSchema = makeExecutableSchema({ numericEnum() { return 1; }, + wrappedEnum() { + return { + color: '#EA3232', + numericEnum: 1 + }; + }, }, }, }); @@ -532,27 +544,8 @@ testCombinations.forEach(async combination => { }); it('works with custom enums', async () => { - const localSchema = makeExecutableSchema({ - typeDefs: enumTest, - resolvers: { - Color: { - RED: '#EA3232', - }, - NumericEnum: { - TEST: 1, - }, - Query: { - color() { - return '#EA3232'; - }, - numericEnum() { - return 1; - }, - }, - }, - }); const enumResult = await graphql( - localSchema, + enumSchema, ` query { color @@ -571,6 +564,10 @@ testCombinations.forEach(async combination => { description } } + wrappedEnum { + color + numericEnum + } } `, ); @@ -595,6 +592,10 @@ testCombinations.forEach(async combination => { description } } + wrappedEnum { + color + numericEnum + } } `, ); @@ -621,6 +622,10 @@ testCombinations.forEach(async combination => { }, ], }, + wrappedEnum: { + color: 'RED', + numericEnum: 'TEST', + }, }, }); expect(mergedResult).to.deep.equal(enumResult); diff --git a/src/transforms/CheckResultAndHandleErrors.ts b/src/transforms/CheckResultAndHandleErrors.ts index 73532674825..5738af15968 100644 --- a/src/transforms/CheckResultAndHandleErrors.ts +++ b/src/transforms/CheckResultAndHandleErrors.ts @@ -1,5 +1,5 @@ import { GraphQLResolveInfo } from 'graphql'; -import { checkResultAndHandleErrors } from '../stitching/errors'; +import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; import { Transform } from './transforms'; export default class CheckResultAndHandleErrors implements Transform { diff --git a/src/transforms/ConvertEnumResponse.ts b/src/transforms/ConvertEnumResponse.ts deleted file mode 100644 index 55975ae5933..00000000000 --- a/src/transforms/ConvertEnumResponse.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Transform } from './transforms'; -import { GraphQLEnumType } from 'graphql'; - -export default class ConvertEnumResponse implements Transform { - private enumNode: GraphQLEnumType; - - constructor(enumNode: GraphQLEnumType) { - this.enumNode = enumNode; - } - - public transformResult(result: any) { - const value = this.enumNode.getValue(result); - if (value) { - return value.value; - } - return result; - } -} From c662bea77e3025c76c6f12d93730392f5026d9cf Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 16 Jun 2019 14:20:04 -0400 Subject: [PATCH 011/250] perf(stitching): remove unnecessary map --- src/stitching/errors.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 221004422dc..6e56f2ba72e 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -2,6 +2,7 @@ import { GraphQLError, ASTNode } from 'graphql'; +import { forEach } from 'iterall'; export let ERROR_SYMBOL: any; if ( @@ -65,7 +66,9 @@ export function annotateWithChildrenErrors(object: any, childrenErrors: Readonly byIndex[index] = current; }); - return object.map((item, index) => annotateWithChildrenErrors(item, byIndex[index])); + object.forEach((item, index) => annotateWithChildrenErrors(item, byIndex[index])); + + return object; } object[ERROR_SYMBOL] = childrenErrors.map(error => { From 21c1486535c32264ec10369d5b4fae3a4c6d9887 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 16 Jun 2019 14:26:46 -0400 Subject: [PATCH 012/250] chore(lint) --- src/stitching/errors.ts | 1 - src/test/testAlternateMergeSchemas.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 6e56f2ba72e..90079f570d6 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -2,7 +2,6 @@ import { GraphQLError, ASTNode } from 'graphql'; -import { forEach } from 'iterall'; export let ERROR_SYMBOL: any; if ( diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 7db04074e04..0d31b2b3c70 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -590,7 +590,7 @@ describe('onTypeConflict', () => { } } }); - }) + }); it('by default takes last type', async () => { const mergedSchema = mergeSchemas({ From ddbeb9ac18034bcceb0f68b6df2e6f2c014ee020 Mon Sep 17 00:00:00 2001 From: Stefan Probst Date: Sat, 9 Mar 2019 16:33:56 +0100 Subject: [PATCH 013/250] fix(stitching): serialize/deserialize enum/custom scalar values BREAKING CHANGE: This change allows enums and custom scalars to be used as arguments within merged schemas. It also fixes seralization and deserialization more generally within merged schemas. If an implementation is available for a custom scalar within a merged schema (i.e., the schema is local), the internal representation will be available for use with stitching. Previously, the merged schema internally used the serialized version. --- src/stitching/checkResultAndHandleErrors.ts | 3 + src/stitching/schemaRecreation.ts | 14 ++-- src/test/testMergeSchemas.ts | 71 ++++++++++++++++++--- src/transforms/AddArgumentsAsVariables.ts | 41 ++++++++++-- 4 files changed, 106 insertions(+), 23 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 18231f0fb52..cd9fca996f1 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -5,6 +5,7 @@ import { isObjectType, isListType, isEnumType, + isScalarType, ExecutionResult, GraphQLError, } from 'graphql'; @@ -65,6 +66,8 @@ export function handleResult( if (value) { return value.value; } + } else if (isScalarType(nullableType)) { + return nullableType.parseValue(resultObject); } return resultObject; diff --git a/src/stitching/schemaRecreation.ts b/src/stitching/schemaRecreation.ts index 3c51530d93f..8c794b8e863 100644 --- a/src/stitching/schemaRecreation.ts +++ b/src/stitching/schemaRecreation.ts @@ -107,22 +107,16 @@ export function recreateType( values: newValues, }); } else if (type instanceof GraphQLScalarType) { - if (keepResolvers || isSpecifiedScalarType(type)) { + if (isSpecifiedScalarType(type)) { return type; } else { return new GraphQLScalarType({ name: type.name, description: type.description, astNode: type.astNode, - serialize(value: any) { - return value; - }, - parseValue(value: any) { - return value; - }, - parseLiteral(ast: ValueNode) { - return parseLiteral(ast); - }, + serialize: type.serialize ? type.serialize : (value: any) => value, + parseValue: type.parseValue ? type.parseValue : (value: any) => value, + parseLiteral: type.parseLiteral ? type.parseLiteral : (ast: any) => parseLiteral(ast), }); } } else { diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index c8a23245987..4343d6c9c8c 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -4,13 +4,13 @@ import { expect } from 'chai'; import { graphql, GraphQLSchema, + GraphQLField, GraphQLObjectType, GraphQLScalarType, subscribe, parse, ExecutionResult, defaultFieldResolver, - GraphQLField, findDeprecatedUsages, } from 'graphql'; import mergeSchemas from '../stitching/mergeSchemas'; @@ -72,10 +72,32 @@ let scalarTest = ` } type Query { - testingScalar: TestingScalar + testingScalar(input: TestScalar): TestingScalar } `; +let scalarSchema: GraphQLSchema; + +scalarSchema = makeExecutableSchema({ + typeDefs: scalarTest, + resolvers: { + TestScalar: new GraphQLScalarType({ + name: 'TestScalar', + description: undefined, + serialize: value => (value as string).slice(1), + parseValue: value => `_${value}`, + parseLiteral: (ast: any) => `_${ast.value}`, + }), + Query: { + testingScalar(parent, args) { + return { + value: args.input[0] === '_' ? args.input : null + }; + }, + }, + }, +}); + let enumTest = ` """ A type that uses an Enum. @@ -107,7 +129,7 @@ let enumTest = ` } type Query { - color: Color + color(input: Color): Color numericEnum: NumericEnum wrappedEnum: EnumWrapper } @@ -125,8 +147,8 @@ enumSchema = makeExecutableSchema({ TEST: 1, }, Query: { - color() { - return '#EA3232'; + color(parent, args) { + return args.input === '#EA3232' ? args.input : null; }, numericEnum() { return 1; @@ -289,8 +311,8 @@ testCombinations.forEach(async combination => { propertySchema, bookingSchema, productSchema, - scalarTest, interfaceExtensionTest, + scalarSchema, enumSchema, linkSchema, loneExtend, @@ -543,12 +565,45 @@ testCombinations.forEach(async combination => { expect(mergedResult).to.deep.equal(propertyResult); }); + it('works with custom scalars', async () => { + const scalarResult = await graphql( + scalarSchema, + ` + query { + testingScalar(input: "test") { + value + } + } + `, + ); + + const mergedResult = await graphql( + mergedSchema, + ` + query { + testingScalar(input: "test") { + value + } + } + `, + ); + + expect(scalarResult).to.deep.equal({ + data: { + testingScalar: { + value: 'test' + } + }, + }); + expect(mergedResult).to.deep.equal(scalarResult); + }); + it('works with custom enums', async () => { const enumResult = await graphql( enumSchema, ` query { - color + color(input: RED) numericEnum numericEnumInfo: __type(name: "NumericEnum") { enumValues(includeDeprecated: true) { @@ -576,7 +631,7 @@ testCombinations.forEach(async combination => { mergedSchema, ` query { - color + color(input: RED) numericEnum numericEnumInfo: __type(name: "NumericEnum") { enumValues(includeDeprecated: true) { diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index b2378ff8b64..3904c945c8d 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -3,17 +3,21 @@ import { DocumentNode, FragmentDefinitionNode, GraphQLArgument, + GraphQLEnumType, + GraphQLInputObjectType, GraphQLInputType, GraphQLList, GraphQLField, GraphQLNonNull, GraphQLObjectType, + GraphQLScalarType, GraphQLSchema, Kind, OperationDefinitionNode, SelectionNode, TypeNode, VariableDefinitionNode, + getNullableType, } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; @@ -62,6 +66,7 @@ function addVariablesToRootField( ) as Array; const variableNames = {}; + const newVariables = {}; const newOperations = operations.map((operation: OperationDefinitionNode) => { let existingVariables = operation.variableDefinitions.map( @@ -130,6 +135,10 @@ function addVariablesToRootField( }, type: typeToAst(argument.type), }; + newVariables[variableName] = serializeArgumentValue( + argument.type, + args[argument.name], + ); } }); @@ -154,11 +163,6 @@ function addVariablesToRootField( }; }); - const newVariables = {}; - Object.keys(variableNames).forEach(name => { - newVariables[variableNames[name]] = args[name]; - }); - return { document: { ...document, @@ -197,3 +201,30 @@ function typeToAst(type: GraphQLInputType): TypeNode { }; } } + +function serializeArgumentValue(type: GraphQLInputType, value: any): any { + if (value == null) { + return null; + } + + const nullableType = getNullableType(type); + + if (nullableType instanceof GraphQLEnumType || nullableType instanceof GraphQLScalarType) { + return nullableType.serialize(value); + } + + if (nullableType instanceof GraphQLList) { + return value.map((listMember: any) => serializeArgumentValue(nullableType.ofType, listMember)); + } + + if (nullableType instanceof GraphQLInputObjectType) { + const fields = nullableType.getFields(); + const newValue = {}; + Object.keys(value).forEach(key => { + newValue[key] = serializeArgumentValue(fields[key].type, value[key]); + }); + return newValue; + } + + return value; +} From 59418ed556968b6caa1dfc0f5c13255ce1b211ee Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 17 Jun 2019 23:09:25 -0400 Subject: [PATCH 014/250] docs: update index --- docs/source/index.mdx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/source/index.mdx b/docs/source/index.mdx index a232a2f641d..fbb2438d881 100644 --- a/docs/source/index.mdx +++ b/docs/source/index.mdx @@ -1,15 +1,17 @@ --- -title: graphql-tools +title: graphql-tools-fork 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. + +`graphql-tools-fork` is a fork of the original package with the goal of more active development and engagement with the community. ```txt -npm install graphql-tools graphql +npm install graphql-tools-fork 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` and `graphql-tools-fork` 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. @@ -17,12 +19,12 @@ Even though we recommend a specific way of building GraphQL servers, you can use If you want to bind your JavaScript GraphQL schema to an HTTP server, we recommend using [Apollo Server](https://www.apollographql.com/docs/apollo-server/), which supports every popular Node HTTP server library including Express, Koa, Hapi, and more. -JavaScript GraphQL servers are often developed with `graphql-tools` and `apollo-server-express` together: One to write the schema and resolver code, and the other to connect it to a web server. +JavaScript GraphQL servers are often developed with `graphql-tools`/`graphql-tools-fork` and `apollo-server-express` together: One to write the schema and resolver code, and the other to connect it to a web server. ## The GraphQL-first philosophy 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. +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`/`graphql-tools-fork` 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. 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. From d627b3c6c91c56f5590ee23da2f9b2cfca371b3d Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 18 Jun 2019 06:22:30 -0400 Subject: [PATCH 015/250] docs(dedeprecate) --- docs/source/schema-stitching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/schema-stitching.md b/docs/source/schema-stitching.md index 878b4810fac..7a4d3f3b9e8 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 --- From c6567e75100d8829d10caa381086cacea2822328 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 27 Jun 2019 16:12:32 -0400 Subject: [PATCH 016/250] fix(stitching): add default value support fixes #1121 --- src/Interfaces.ts | 18 ---- src/generate/addResolveFunctionsToSchema.ts | 89 ++++++++-------- src/stitching/mergeSchemas.ts | 82 +++++---------- src/stitching/schemaRecreation.ts | 28 ++++- src/stitching/typeFromAST.ts | 9 +- src/test/testAlternateMergeSchemas.ts | 111 +++++++++++++++++++- src/transformInputValue.ts | 57 ++++++++++ src/transforms/AddArgumentsAsVariables.ts | 34 +----- src/transforms/AddEnumAndScalarResolvers.ts | 89 ++++++++++++++++ 9 files changed, 363 insertions(+), 154 deletions(-) create mode 100644 src/transformInputValue.ts create mode 100644 src/transforms/AddEnumAndScalarResolvers.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index a9cca8d74d6..34a5e8c4438 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -8,7 +8,6 @@ import { GraphQLIsTypeOfFn, GraphQLTypeResolver, GraphQLScalarType, - GraphQLNamedType, DocumentNode, ASTNode, } from 'graphql'; @@ -177,23 +176,6 @@ 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 Operation = 'query' | 'mutation' | 'subscription'; export type Request = { diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts index 5e9741017af..0cb6f729cec 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolveFunctionsToSchema.ts @@ -18,7 +18,7 @@ import { } from '../Interfaces'; import { applySchemaTransforms } from '../transforms/transforms'; import { checkForResolveTypeResolver, extendResolversFromInterfaces } from '.'; -import ConvertEnumValues from '../transforms/ConvertEnumValues'; +import AddEnumAndScalarResolvers from '../transforms/AddEnumAndScalarResolvers'; function addResolveFunctionsToSchema( options: IAddResolveFunctionsToSchemaOptions | GraphQLSchema, @@ -55,6 +55,8 @@ function addResolveFunctionsToSchema( // 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); + // Used to store custom scalar implementations. + const scalarTypeMap = Object.create(null); Object.keys(resolvers).forEach(typeName => { const resolverValue = resolvers[typeName]; @@ -63,7 +65,7 @@ function addResolveFunctionsToSchema( 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.`, + `must be of type object or function.`, ); } @@ -79,19 +81,10 @@ function addResolveFunctionsToSchema( ); } - 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 instanceof GraphQLScalarType) { + scalarTypeMap[type.name] = resolverValue; + } else if (type instanceof GraphQLEnumType) { + Object.keys(resolverValue).forEach(fieldName => { if (!type.getValue(fieldName)) { if (allowResolversNotInSchema) { return; @@ -111,44 +104,51 @@ function addResolveFunctionsToSchema( // internal value. enumValueMap[type.name] = enumValueMap[type.name] || {}; enumValueMap[type.name][fieldName] = resolverValue[fieldName]; - return; - } - + }); + } else { // object type - const fields = getFieldsForType(type); - if (!fields) { - if (allowResolversNotInSchema) { + 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; } - throw new SchemaError( - `${typeName} was defined in resolvers, but it's not an object`, - ); - } + const fields = getFieldsForType(type); + if (!fields) { + if (allowResolversNotInSchema) { + return; + } - if (!fields[fieldName]) { - if (allowResolversNotInSchema) { - return; + throw new SchemaError( + `${typeName} was defined in resolvers, but it's not an object`, + ); } - 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') { + if (!fields[fieldName]) { + if (allowResolversNotInSchema) { + return; + } + throw new SchemaError( - `Resolver ${typeName}.${fieldName} must be object or function`, + `${typeName}.${fieldName} defined in resolvers, but not in schema`, ); } - setFieldProperties(field, fieldResolve); - } - }); + 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); @@ -156,8 +156,9 @@ function addResolveFunctionsToSchema( // 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. + // also parse all defaultValues in all input fields to use internal values for enums/scalars const updatedSchema = applySchemaTransforms(schema, [ - new ConvertEnumValues(enumValueMap), + new AddEnumAndScalarResolvers(enumValueMap, scalarTypeMap), ]); return updatedSchema; diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index d936887e3f9..5b0371ae62b 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -18,9 +18,6 @@ import { IFieldResolver, IResolvers, MergeInfo, - MergeTypeCandidate, - TypeWithResolvers, - VisitTypeResult, IResolversParameter, } from '../Interfaces'; import { @@ -43,7 +40,18 @@ import { import mergeDeep from '../mergeDeep'; import { SchemaDirectiveVisitor } from '../schemaVisitor'; -export type OnTypeConflict = ( +type MergeTypeCandidate = { + schema?: GraphQLSchema; + type: GraphQLNamedType; +}; + +type MergeTypeCandidatesResult = { + type?: GraphQLNamedType; + resolvers?: IResolvers; + candidate?: MergeTypeCandidate; +}; + +type OnTypeConflict = ( left: GraphQLNamedType, right: GraphQLNamedType, info?: { @@ -76,35 +84,6 @@ export default function mergeSchemas({ schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor }; inheritResolversFromInterfaces?: boolean; mergeDirectives?: boolean, - -}): GraphQLSchema { - return mergeSchemasImplementation({ - schemas, - onTypeConflict, - resolvers, - schemaDirectives, - inheritResolversFromInterfaces, - mergeDirectives, - }); -} - -function mergeSchemasImplementation({ - schemas, - onTypeConflict, - resolvers, - schemaDirectives, - inheritResolversFromInterfaces, - mergeDirectives, -}: { - schemas: Array< - string | GraphQLSchema | DocumentNode | Array - >; - onTypeConflict?: OnTypeConflict; - resolvers?: IResolversParameter; - schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor }; - inheritResolversFromInterfaces?: boolean; - mergeDirectives?: boolean, - }): GraphQLSchema { const allSchemas: Array = []; const typeCandidates: { [name: string]: Array } = {}; @@ -229,28 +208,22 @@ function mergeSchemasImplementation({ let generatedResolvers = {}; Object.keys(typeCandidates).forEach(typeName => { - const resultType: VisitTypeResult = defaultVisitType( + const mergeResult: MergeTypeCandidatesResult = mergeTypeCandidates( typeName, typeCandidates[typeName], onTypeConflict ? onTypeConflictToCandidateSelector(onTypeConflict) : undefined ); - if (resultType === null) { - types[typeName] = null; + let type: GraphQLNamedType; + let typeResolvers: IResolvers; + if (mergeResult.type) { + type = mergeResult.type; + typeResolvers = mergeResult.resolvers; } else { - let type: GraphQLNamedType; - let typeResolvers: IResolvers; - if (isNamedType(resultType)) { - type = resultType; - } else if ((resultType).type) { - type = (resultType).type; - typeResolvers = (resultType).resolvers; - } else { - throw new Error(`Invalid visitType result for type ${typeName}`); - } - types[typeName] = recreateType(type, resolveType, false); - if (typeResolvers) { - generatedResolvers[typeName] = typeResolvers; - } + throw new Error(`Invalid mergeTypeCandidates result for type ${typeName}`); + } + types[typeName] = recreateType(type, resolveType, false); + if (typeResolvers) { + generatedResolvers[typeName] = typeResolvers; } }); @@ -473,11 +446,11 @@ function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): Cand }); } -function defaultVisitType( +function mergeTypeCandidates( name: string, candidates: Array, candidateSelector?: CandidateSelector -) { +): MergeTypeCandidatesResult { if (!candidateSelector) { candidateSelector = cands => cands[cands.length - 1]; } @@ -524,6 +497,9 @@ function defaultVisitType( }; } else { const candidate = candidateSelector(candidates); - return candidate.type; + return { + type: candidate.type, + candidate + }; } } diff --git a/src/stitching/schemaRecreation.ts b/src/stitching/schemaRecreation.ts index 8c794b8e863..7f37c4b5601 100644 --- a/src/stitching/schemaRecreation.ts +++ b/src/stitching/schemaRecreation.ts @@ -9,6 +9,7 @@ import { GraphQLFieldMap, GraphQLInputField, GraphQLInputFieldConfig, + GraphQLInputType, GraphQLInputFieldConfigMap, GraphQLInputFieldMap, GraphQLInputObjectType, @@ -35,6 +36,12 @@ import isSpecifiedScalarType from '../isSpecifiedScalarType'; import { ResolveType } from '../Interfaces'; import resolveFromParentTypename from './resolveFromParentTypename'; import defaultMergedResolver from './defaultMergedResolver'; +import { isStub } from './typeFromAST'; +import { + serializeInputValue, + parseInputValue, + parseInputValueLiteral +} from '../transformInputValue'; export function recreateType( type: GraphQLNamedType, @@ -265,9 +272,10 @@ export function argumentToArgumentConfig( return [ argument.name, { - type: type, - defaultValue: argument.defaultValue, + type, + defaultValue: reparseDefaultValue(argument.defaultValue, argument.type, type), description: argument.description, + astNode: argument.astNode, }, ]; } @@ -292,10 +300,22 @@ export function inputFieldToFieldConfig( field: GraphQLInputField, resolveType: ResolveType, ): GraphQLInputFieldConfig { + const type = resolveType(field.type); return { - type: resolveType(field.type), - defaultValue: field.defaultValue, + type, + defaultValue: reparseDefaultValue(field.defaultValue, field.type, type), description: field.description, astNode: field.astNode, }; } + +function reparseDefaultValue( + originalDefaultValue: any, + originalType: GraphQLInputType, + newType: GraphQLInputType, +) { + if (originalType instanceof GraphQLInputObjectType && isStub(originalType)) { + return parseInputValueLiteral(newType, originalDefaultValue); + } + return parseInputValue(newType, serializeInputValue(originalType, originalDefaultValue)); +} diff --git a/src/stitching/typeFromAST.ts b/src/stitching/typeFromAST.ts index 69367beefb3..878487fe2e2 100644 --- a/src/stitching/typeFromAST.ts +++ b/src/stitching/typeFromAST.ts @@ -21,7 +21,6 @@ import { ScalarTypeDefinitionNode, TypeNode, UnionTypeDefinitionNode, - valueFromAST, getDescription, GraphQLString, GraphQLDirective, @@ -183,7 +182,7 @@ function makeValues(nodes: ReadonlyArray) { const type = resolveType(node.type, 'input') as GraphQLInputType; result[node.name.value] = { type, - defaultValue: valueFromAST(node.defaultValue, type), + defaultValue: node.defaultValue, description: getDescription(node, backcompatOptions), }; }); @@ -227,6 +226,12 @@ function createNamedStub( }); } +export function isStub(type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType): boolean { + const fields = type.getFields(); + const fieldNames = Object.keys(fields); + return fieldNames.length === 1 && fields[fieldNames[0]].name === '__fake'; +} + function makeDirective(node: DirectiveDefinitionNode): GraphQLDirective { const locations: Array = []; node.locations.forEach(location => { diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 0d31b2b3c70..dd613cae8c6 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -9,7 +9,9 @@ import { parse, GraphQLField, GraphQLNamedType, - FieldNode + GraphQLScalarType, + FieldNode, + printSchema } from 'graphql'; import mergeSchemas from '../stitching/mergeSchemas'; import { @@ -536,6 +538,113 @@ describe('mergeSchemas', () => { expect(response.data.test).to.be.null; expect(response.errors).to.be.undefined; }); + + it('can merge default input types', async () => { + const schema = makeExecutableSchema({ + typeDefs: ` + input InputWithDefault { + field: String = "test" + } + type Query { + getInput(input: InputWithDefault!): String + } + `, + resolvers: { + Query: { + getInput: (root, args) => args.input.field + } + } + }); + const mergedSchema = mergeSchemas({ + schemas: [schema] + }); + + const query = `{ getInput(input: {}) }`; + const response = await graphql(mergedSchema, query); + + expect(printSchema(schema)).to.equal(printSchema(mergedSchema)); + expect(response.data.getInput).to.equal('test'); + }); + + it('can override scalars with new internal values', async () => { + const schema = makeExecutableSchema({ + typeDefs: ` + scalar TestScalar + type Query { + getTestScalar: TestScalar + } + `, + resolvers: { + TestScalar: new GraphQLScalarType({ + name: 'TestScalar', + description: undefined, + serialize: value => (value as string).slice(1), + parseValue: value => `_${value}`, + parseLiteral: (ast: any) => `_${ast.value}`, + }), + Query: { + getTestScalar: () => '_test' + } + } + }); + const mergedSchema = mergeSchemas({ + schemas: [schema], + resolvers: { + TestScalar: new GraphQLScalarType({ + name: 'TestScalar', + description: undefined, + serialize: value => (value as string).slice(2), + parseValue: value => `__${value}`, + parseLiteral: (ast: any) => `__${ast.value}`, + }) + } + }); + + const query = `{ getTestScalar }`; + const response = await graphql(mergedSchema, query); + + expect(response.data.getTestScalar).to.equal('test'); + }); + + it('can override scalars with new internal values when using default input types', async () => { + const schema = makeExecutableSchema({ + typeDefs: ` + scalar TestScalar + type Query { + getTestScalar(input: TestScalar = "test"): TestScalar + } + `, + resolvers: { + TestScalar: new GraphQLScalarType({ + name: 'TestScalar', + description: undefined, + serialize: value => (value as string).slice(1), + parseValue: value => `_${value}`, + parseLiteral: (ast: any) => `_${ast.value}`, + }), + Query: { + getTestScalar: (root, args) => '_test' + } + } + }); + const mergedSchema = mergeSchemas({ + schemas: [schema], + resolvers: { + TestScalar: new GraphQLScalarType({ + name: 'TestScalar', + description: undefined, + serialize: value => (value as string).slice(2), + parseValue: value => `__${value}`, + parseLiteral: (ast: any) => `__${ast.value}`, + }) + } + }); + + const query = `{ getTestScalar }`; + const response = await graphql(mergedSchema, query); + + expect(response.data.getTestScalar).to.equal('test'); + }); }); describe('onTypeConflict', () => { diff --git a/src/transformInputValue.ts b/src/transformInputValue.ts new file mode 100644 index 00000000000..f293d625c8e --- /dev/null +++ b/src/transformInputValue.ts @@ -0,0 +1,57 @@ +import { + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLInputType, + GraphQLList, + GraphQLScalarType, + getNullableType, +} from 'graphql'; + +type InputValueTransformer = (type: GraphQLEnumType | GraphQLScalarType, originalValue: any) => any; + +export function transformInputValue(type: GraphQLInputType, value: any, transformer: InputValueTransformer) { + if (value == null) { + return null; + } + + const nullableType = getNullableType(type); + + if (nullableType instanceof GraphQLEnumType || nullableType instanceof GraphQLScalarType) { + return transformer(nullableType, value); + } else if (nullableType instanceof GraphQLList) { + return value.map((listMember: any) => transformInputValue(nullableType.ofType, listMember, transformer)); + } else if (nullableType instanceof GraphQLInputObjectType) { + const fields = nullableType.getFields(); + const newValue = {}; + Object.keys(value).forEach(key => { + newValue[key] = transformInputValue(fields[key].type, value[key], transformer); + }); + return newValue; + } + + //unreachable, no other possible return value +} + +export function serializeInputValue(type: GraphQLInputType, value: any) { + return transformInputValue( + type, + value, + (t, v) => t.serialize(v) + ); +} + +export function parseInputValue(type: GraphQLInputType, value: any) { + return transformInputValue( + type, + value, + (t, v) => t.parseValue(v) + ); +} + +export function parseInputValueLiteral(type: GraphQLInputType, value: any) { + return transformInputValue( + type, + value, + (t, v) => t.parseLiteral(v, {}) + ); +} diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index 3904c945c8d..61bdd442b27 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -3,24 +3,21 @@ import { DocumentNode, FragmentDefinitionNode, GraphQLArgument, - GraphQLEnumType, - GraphQLInputObjectType, GraphQLInputType, GraphQLList, GraphQLField, GraphQLNonNull, GraphQLObjectType, - GraphQLScalarType, GraphQLSchema, Kind, OperationDefinitionNode, SelectionNode, TypeNode, VariableDefinitionNode, - getNullableType, } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; +import { serializeInputValue } from '../transformInputValue'; export default class AddArgumentsAsVariablesTransform implements Transform { private schema: GraphQLSchema; @@ -135,7 +132,7 @@ function addVariablesToRootField( }, type: typeToAst(argument.type), }; - newVariables[variableName] = serializeArgumentValue( + newVariables[variableName] = serializeInputValue( argument.type, args[argument.name], ); @@ -201,30 +198,3 @@ function typeToAst(type: GraphQLInputType): TypeNode { }; } } - -function serializeArgumentValue(type: GraphQLInputType, value: any): any { - if (value == null) { - return null; - } - - const nullableType = getNullableType(type); - - if (nullableType instanceof GraphQLEnumType || nullableType instanceof GraphQLScalarType) { - return nullableType.serialize(value); - } - - if (nullableType instanceof GraphQLList) { - return value.map((listMember: any) => serializeArgumentValue(nullableType.ofType, listMember)); - } - - if (nullableType instanceof GraphQLInputObjectType) { - const fields = nullableType.getFields(); - const newValue = {}; - Object.keys(value).forEach(key => { - newValue[key] = serializeArgumentValue(fields[key].type, value[key]); - }); - return newValue; - } - - return value; -} diff --git a/src/transforms/AddEnumAndScalarResolvers.ts b/src/transforms/AddEnumAndScalarResolvers.ts new file mode 100644 index 00000000000..c7b6c7ae383 --- /dev/null +++ b/src/transforms/AddEnumAndScalarResolvers.ts @@ -0,0 +1,89 @@ +import { + GraphQLSchema, + GraphQLEnumType, + GraphQLScalarType, + GraphQLScalarTypeConfig +} from 'graphql'; +import { Transform } from './transforms'; +import { visitSchema, VisitSchemaKind } from './visitSchema'; + +export default class AddEnumAndScalarResolvers implements Transform { + private enumValueMap: object; + private scalarTypeMap: object; + + constructor(enumValueMap: object, scalarTypeMap: object) { + this.enumValueMap = enumValueMap; + this.scalarTypeMap = scalarTypeMap; + } + + public transformSchema(schema: GraphQLSchema): GraphQLSchema { + const { enumValueMap, scalarTypeMap } = this; + const enumTypeMap = Object.create(null); + + if (!Object.keys(enumValueMap).length && !Object.keys(scalarTypeMap).length) { + return schema; + } + + // Build enum types from the resolver map. + Object.keys(enumValueMap).forEach(typeName => { + const enumType: GraphQLEnumType = schema.getType(typeName) as GraphQLEnumType; + const externalToInternalValueMap = enumValueMap[enumType.name]; + + if (externalToInternalValueMap) { + const values = enumType.getValues(); + const newValues = {}; + values.forEach(value => { + const newValue = Object.keys(externalToInternalValueMap).includes( + value.name, + ) + ? externalToInternalValueMap[value.name] + : value.name; + newValues[value.name] = { + value: newValue, + deprecationReason: value.deprecationReason, + description: value.description, + astNode: value.astNode, + }; + }); + + enumTypeMap[typeName] = new GraphQLEnumType({ + name: enumType.name, + description: enumType.description, + astNode: enumType.astNode, + values: newValues, + }); + } + }); + + //Build scalar types from resolver map (if necessary, see below). + Object.keys(scalarTypeMap).forEach(typeName => { + const type = scalarTypeMap[typeName]; + + // Below is necessary as legacy code for scalar type specification allowed + // hardcoding within the resolver an object with fields 'serialize', + // 'parse', and '__parseLiteral', see examples in testMocking.ts. + if (!(type instanceof GraphQLScalarType)) { + const scalarTypeConfig = {}; + Object.keys(type).forEach(key => { + scalarTypeConfig[key.slice(2)] = type[key]; + }); + scalarTypeMap[typeName] = new GraphQLScalarType({ + name: typeName, + ...scalarTypeConfig + } as GraphQLScalarTypeConfig); + } + }); + + // type recreation within visitSchema will automatically adjust default + // values on fields. + const transformedSchema = visitSchema(schema, { + [VisitSchemaKind.SCALAR_TYPE](type: GraphQLScalarType) { + return scalarTypeMap[type.name]; + }, + [VisitSchemaKind.ENUM_TYPE](type: GraphQLEnumType) { + return enumTypeMap[type.name]; }, + }); + + return transformedSchema; + } +} From 169fad42a9031a8fcc95aba4e74716ee37bbe07b Mon Sep 17 00:00:00 2001 From: Kamil Kisiela Date: Wed, 12 Jun 2019 11:27:12 +0200 Subject: [PATCH 017/250] fix(stitching): Directive disappears when enum has resolvers --- src/stitching/schemaRecreation.ts | 1 - src/test/testDirectives.ts | 36 +++++++++++++++++++++++++++++++ src/transforms/visitSchema.ts | 7 ++++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/stitching/schemaRecreation.ts b/src/stitching/schemaRecreation.ts index 7f37c4b5601..bb52f1bf9ba 100644 --- a/src/stitching/schemaRecreation.ts +++ b/src/stitching/schemaRecreation.ts @@ -92,7 +92,6 @@ export function recreateType( name: type.name, description: type.description, astNode: type.astNode, - fields: () => inputFieldMapToFieldConfigMap(type.getFields(), resolveType), }); diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index b4d65a14172..2cbd00c2ce6 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -94,6 +94,13 @@ describe('@directives', () => { it('are included in the schema AST', () => { const schema = makeExecutableSchema({ typeDefs, + resolvers: { + Gender: { + NONBINARY: 'NB', + FEMALE: 'F', + MALE: 'M' + } + } }); function checkDirectives( @@ -167,6 +174,35 @@ describe('@directives', () => { checkDirectives(schema.getType('WhateverUnion'), ['unionDirective']); }); + it('works with enum and its resolvers', () => { + const schema = makeExecutableSchema({ + typeDefs: ` + enum DateFormat { + LOCAL + ISO + } + + directive @date(format: DateFormat) on FIELD_DEFINITION + + scalar Date + + type Query { + today: Date @date(format: LOCAL) + } + `, + resolvers: { + DateFormat: { + LOCAL: 'local', + ISO: 'iso' + } + } + }); + + assert.exists(schema.getType('DateFormat')); + assert.lengthOf(schema.getDirectives(), 4); + assert.exists(schema.getDirective('date')); + }); + it('can be implemented with SchemaDirectiveVisitor', () => { const visited: Set = new Set; const schema = makeExecutableSchema({ typeDefs }); diff --git a/src/transforms/visitSchema.ts b/src/transforms/visitSchema.ts index 8d454fd25c2..78f62fb4b95 100644 --- a/src/transforms/visitSchema.ts +++ b/src/transforms/visitSchema.ts @@ -11,7 +11,7 @@ import { isNamedType, getNamedType, } from 'graphql'; -import { recreateType, createResolveType } from '../stitching/schemaRecreation'; +import { recreateType, recreateDirective, createResolveType } from '../stitching/schemaRecreation'; export enum VisitSchemaKind { TYPE = 'VisitSchemaKind.TYPE', @@ -40,7 +40,7 @@ export function visitSchema( visitor: SchemaVisitor, stripResolvers?: boolean, ) { - const types = {}; + const types: {[key: string]: GraphQLNamedType} = {}; const resolveType = createResolveType(name => { if (typeof types[name] === 'undefined') { throw new Error(`Can't find type ${name}.`); @@ -73,6 +73,7 @@ export function visitSchema( } } }); + return new GraphQLSchema({ query: queryType ? (types[queryType.name] as GraphQLObjectType) : null, mutation: mutationType @@ -82,6 +83,8 @@ export function visitSchema( ? (types[subscriptionType.name] as GraphQLObjectType) : null, types: Object.keys(types).map(name => types[name]), + directives: [...schema.getDirectives().map(d => recreateDirective(d, resolveType))], + astNode: schema.astNode, }); } From 5abdaf0f96d78c248bfe38a697671acde20c6d0b Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 27 Jun 2019 21:44:24 -0400 Subject: [PATCH 018/250] fix(stitching): fix legacy custom scalar recreation to more closely match pre-v6.0.1 functionality. --- src/transforms/AddEnumAndScalarResolvers.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/transforms/AddEnumAndScalarResolvers.ts b/src/transforms/AddEnumAndScalarResolvers.ts index c7b6c7ae383..25985f35c41 100644 --- a/src/transforms/AddEnumAndScalarResolvers.ts +++ b/src/transforms/AddEnumAndScalarResolvers.ts @@ -57,18 +57,25 @@ export default class AddEnumAndScalarResolvers implements Transform { //Build scalar types from resolver map (if necessary, see below). Object.keys(scalarTypeMap).forEach(typeName => { - const type = scalarTypeMap[typeName]; + const resolverValue = scalarTypeMap[typeName]; // Below is necessary as legacy code for scalar type specification allowed - // hardcoding within the resolver an object with fields 'serialize', - // 'parse', and '__parseLiteral', see examples in testMocking.ts. - if (!(type instanceof GraphQLScalarType)) { + // hardcoding within the resolver an object with fields '__serialize', + // '__parse', and '__parseLiteral', see examples in testMocking.ts. + if (!(resolverValue instanceof GraphQLScalarType)) { + const scalarType: GraphQLScalarType = schema.getType(typeName) as GraphQLScalarType; + const scalarTypeConfig = {}; - Object.keys(type).forEach(key => { - scalarTypeConfig[key.slice(2)] = type[key]; + Object.keys(resolverValue).forEach(key => { + if (key.startsWith('__')) { + scalarTypeConfig[key.substring(2)] = resolverValue[key]; + } else { + scalarTypeConfig[key] = resolverValue[key]; + } }); + scalarTypeMap[typeName] = new GraphQLScalarType({ - name: typeName, + ...scalarType, ...scalarTypeConfig } as GraphQLScalarTypeConfig); } From 4e6616e52cacb758897efe718a68ade44313b13c Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 27 Jun 2019 22:30:22 -0400 Subject: [PATCH 019/250] feat(deps): upgrade dependencies to latest minor version. --- package.json | 50 +++++++++++---------- src/generate/concatenateTypeDefs.ts | 6 +-- src/stitching/makeRemoteExecutableSchema.ts | 2 +- src/test/testSchemaGenerator.ts | 2 +- src/test/testingSchemas.ts | 4 +- 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index ddd8c1fbd00..c9cf5dbc6b6 100644 --- a/package.json +++ b/package.json @@ -49,36 +49,38 @@ }, "homepage": "https://github.com/apollostack/graphql-tools#readme", "dependencies": { - "apollo-link": "^1.2.3", - "apollo-utilities": "^1.0.1", + "apollo-link": "^1.2.12", + "apollo-utilities": "^1.3.2", "deprecated-decorator": "^0.1.6", - "iterall": "^1.1.3", - "uuid": "^3.1.0" + "iterall": "^1.2.2", + "uuid": "^3.3.2" }, "peerDependencies": { - "graphql": "^0.13.0 || ^14.0.0" + "graphql": "^14.4.0" }, "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", + "@types/chai": "4.1.7", + "@types/dateformat": "^3.0.0", + "@types/graphql": "14.2.2", + "@types/mocha": "^5.2.7", + "@types/node": "^12.0.10", + "@types/uuid": "^3.4.4", + "@types/zen-observable": "^0.8.0", + "body-parser": "^1.19.0", + "chai": "^4.2.0", "dateformat": "^3.0.3", - "express": "^4.16.2", - "graphql": "^14.5.8", - "graphql-subscriptions": "^1.0.0", - "graphql-type-json": "^0.1.4", + "express": "^4.17.1", + "graphql": "^14.0.2", + "graphql-subscriptions": "^1.1.0", + "graphql-type-json": "^0.3.0", "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" + "mocha": "^6.1.4", + "prettier": "^1.18.2", + "remap-istanbul": "0.13.0", + "rimraf": "^2.6.3", + "source-map-support": "^0.5.12", + "standard-version": "^6.0.1", + "tslint": "^5.18.0", + "typescript": "3.5.2" } } diff --git a/src/generate/concatenateTypeDefs.ts b/src/generate/concatenateTypeDefs.ts index c72784fc976..8b5dd5222a7 100644 --- a/src/generate/concatenateTypeDefs.ts +++ b/src/generate/concatenateTypeDefs.ts @@ -9,10 +9,6 @@ function concatenateTypeDefs( ): string { let resolvedTypeDefinitions: string[] = []; 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 +18,8 @@ function concatenateTypeDefs( } } else if (typeof typeDef === 'string') { resolvedTypeDefinitions.push(typeDef.trim()); + } else if ((typeDef).kind !== undefined) { + resolvedTypeDefinitions.push(print(typeDef).trim()); } else { const type = typeof typeDef; throw new SchemaError( diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 06e318e40ee..dbb5d3d8a0a 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -144,7 +144,7 @@ export default function makeRemoteExecutableSchema({ for (const type of types) { if (type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) { resolvers[type.name] = { - __resolveType(parent: any, context: any, info: any) { + __resolveType(parent: any, context: any, info: GraphQLResolveInfo) { return resolveParentFromTypename(parent, info.schema); } }; diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index f77ac22e374..9c03d75880e 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -14,7 +14,7 @@ import { GraphQLEnumType, } from 'graphql'; // import { printSchema } from 'graphql'; -const GraphQLJSON = require('graphql-type-json'); +const { GraphQLJSON } = require('graphql-type-json'); import { Logger } from '../Logger'; import TypeA from './circularSchemaA'; import { diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index ddac48b88a4..6dafdd21e29 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -8,6 +8,8 @@ import { ValueNode, ExecutionResult, DocumentNode, + Source, + GraphQLResolveInfo, } from 'graphql'; import { ExecutionResultDataDefault } from 'graphql/execution/execute'; import { @@ -589,7 +591,7 @@ const bookingResolvers: IResolvers = { }, Booking: { - __isTypeOf(source: any, context: any, info: any) { + __isTypeOf(source: Source, context: any, info: GraphQLResolveInfo) { return Object.prototype.hasOwnProperty.call(source, 'id'); }, customer(parent: Booking) { From 773f765cb4bedd4d03afd8b29f81d673a7da499e Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 30 Jun 2019 09:54:18 -0400 Subject: [PATCH 020/250] fix(stitching): include specified directives even when merging of directives is disabled --- src/stitching/mergeSchemas.ts | 4 +- src/test/testAlternateMergeSchemas.ts | 53 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 5b0371ae62b..81a1de99f34 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -232,7 +232,9 @@ export default function mergeSchemas({ mutation: types.Mutation as GraphQLObjectType, subscription: types.Subscription as GraphQLObjectType, types: Object.keys(types).map(key => types[key]), - directives: directives.map((directive) => recreateDirective(directive, resolveType)) + directives: directives.length ? + directives.map((directive) => recreateDirective(directive, resolveType)) : + undefined }); extensions.forEach(extension => { diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index dd613cae8c6..bd46a4b4847 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -33,6 +33,7 @@ import { import { forAwaitEach } from 'iterall'; import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; import { makeExecutableSchema } from '../makeExecutableSchema'; +import { delegateToSchema } from '../stitching'; let linkSchema = ` """ @@ -645,6 +646,58 @@ describe('mergeSchemas', () => { expect(response.data.getTestScalar).to.equal('test'); }); + + it('can use @include directives', async () => { + const schema = makeExecutableSchema({ + typeDefs: ` + type WrappingType { + subfield: String + } + type Query { + get1: WrappingType + } + `, + resolvers: { + Query: { + get1: () => ({ subfield: 'test'}) + } + } + }); + const mergedSchema = mergeSchemas({ + schemas: [ + schema, + ` + type Query { + get2: WrappingType + } + ` + ], + resolvers: { + Query: { + get2: (root, args, context, info) => { + return delegateToSchema({ + schema: schema, + operation: 'query', + fieldName: 'get1', + context, + info + }) + } + } + } + }); + + const query = ` + { + get2 @include(if: true) { + subfield + } + } + `; + const response = await graphql(mergedSchema, query); + console.log(JSON.stringify(response, null, 2)); + expect(response.data.get2.subfield).to.equal('test'); + }); }); describe('onTypeConflict', () => { From 6600d5d05e11e87376d74061811c9cd2ed65abbb Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 30 Jun 2019 10:51:27 -0400 Subject: [PATCH 021/250] fix(stitching): fix reparsing of lists --- src/stitching/schemaRecreation.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/stitching/schemaRecreation.ts b/src/stitching/schemaRecreation.ts index bb52f1bf9ba..414e37dd14d 100644 --- a/src/stitching/schemaRecreation.ts +++ b/src/stitching/schemaRecreation.ts @@ -313,7 +313,10 @@ function reparseDefaultValue( originalType: GraphQLInputType, newType: GraphQLInputType, ) { - if (originalType instanceof GraphQLInputObjectType && isStub(originalType)) { + if ( + originalType instanceof GraphQLInputObjectType && + isStub(getNamedType(originalType) as GraphQLInputObjectType) + ) { return parseInputValueLiteral(newType, originalDefaultValue); } return parseInputValue(newType, serializeInputValue(originalType, originalDefaultValue)); From b41896fa09b9e47a546e4906590e0d9714c82f19 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 30 Jun 2019 10:53:51 -0400 Subject: [PATCH 022/250] chore(lint) --- src/test/testAlternateMergeSchemas.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index bd46a4b4847..655aa6e851b 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -681,7 +681,7 @@ describe('mergeSchemas', () => { fieldName: 'get1', context, info - }) + }); } } } @@ -695,7 +695,6 @@ describe('mergeSchemas', () => { } `; const response = await graphql(mergedSchema, query); - console.log(JSON.stringify(response, null, 2)); expect(response.data.get2.subfield).to.equal('test'); }); }); From 46083a266b2326921523e5ab9b8274811cd33cdd Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 1 Jul 2019 09:55:41 -0400 Subject: [PATCH 023/250] fix(stitching): fix default resolver to execute field if specified as function. --- src/stitching/defaultMergedResolver.ts | 3 +++ src/test/testAlternateMergeSchemas.ts | 28 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index f95c725edc3..010d01979c0 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -18,6 +18,9 @@ const defaultMergedResolver: GraphQLFieldResolver = (parent, args, con // check to see if parent is not a proxied result, i.e. if parent resolver was manually overwritten // See https://github.com/apollographql/graphql-tools/issues/967 if (!Array.isArray(errors)) { + if (typeof parent[info.fieldName] === 'function') { + return parent[info.fieldName](parent, args, context, info); + } return parent[info.fieldName]; } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 655aa6e851b..fa23364a22b 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -697,6 +697,34 @@ describe('mergeSchemas', () => { const response = await graphql(mergedSchema, query); expect(response.data.get2.subfield).to.equal('test'); }); + + it('can use functions in subfields', async () => { + const schema = makeExecutableSchema({ + typeDefs: ` + type WrappingObject { + functionField: Int! + } + type Query { + wrappingObject: WrappingObject + } + ` + }); + + const mergedSchema = mergeSchemas({ + schemas: [schema], + resolvers: { + Query: { + wrappingObject: () => ({ + functionField: () => 8 + }) + }, + } + }); + + const query = `{ wrappingObject { functionField } }`; + const response = await graphql(mergedSchema, query); + expect(response.data.wrappingObject.functionField).to.equal(8); + }); }); describe('onTypeConflict', () => { From 5f6a5f3e5b342bc0501dea4343de0d164f8dbdec Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 2 Jul 2019 22:55:12 -0400 Subject: [PATCH 024/250] fix(stitching): fix lists of enum and custom scalars, closes #9 --- src/stitching/checkResultAndHandleErrors.ts | 19 +++++++++------- src/test/testMergeSchemas.ts | 24 ++++++++++++++++++++- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index cd9fca996f1..cc409dfa703 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -8,6 +8,7 @@ import { isScalarType, ExecutionResult, GraphQLError, + GraphQLType, } from 'graphql'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; import { @@ -61,14 +62,16 @@ export function handleResult( if (isObjectType(nullableType) || isListType(nullableType)) { annotateWithChildrenErrors(resultObject, errors); - } else if (isEnumType(nullableType)) { - const value = nullableType.getValue(resultObject); - if (value) { - return value.value; - } - } else if (isScalarType(nullableType)) { - return nullableType.parseValue(resultObject); } - return resultObject; + return parseOutputValue(nullableType, resultObject); +} + +function parseOutputValue(type: GraphQLType, value: any) { + if (isListType(type)) { + return value.map((v: any) => parseOutputValue(getNullableType(type.ofType), v)); + } else if (isEnumType(type) || isScalarType(type)) { + return type.parseValue(value); + } + return value; } diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 4343d6c9c8c..1c3f7f85097 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -73,6 +73,7 @@ let scalarTest = ` type Query { testingScalar(input: TestScalar): TestingScalar + listTestingScalar(input: TestScalar): [TestingScalar] } `; @@ -94,6 +95,11 @@ scalarSchema = makeExecutableSchema({ value: args.input[0] === '_' ? args.input : null }; }, + listTestingScalar(parent, args) { + return [{ + value: args.input[0] === '_' ? args.input : null + }]; + }, }, }, }); @@ -131,6 +137,7 @@ let enumTest = ` type Query { color(input: Color): Color numericEnum: NumericEnum + listNumericEnum: [NumericEnum] wrappedEnum: EnumWrapper } `; @@ -153,6 +160,9 @@ enumSchema = makeExecutableSchema({ numericEnum() { return 1; }, + listNumericEnum() { + return [1]; + }, wrappedEnum() { return { color: '#EA3232', @@ -573,6 +583,9 @@ testCombinations.forEach(async combination => { testingScalar(input: "test") { value } + listTestingScalar(input: "test") { + value + } } `, ); @@ -584,6 +597,9 @@ testCombinations.forEach(async combination => { testingScalar(input: "test") { value } + listTestingScalar(input: "test") { + value + } } `, ); @@ -592,7 +608,10 @@ testCombinations.forEach(async combination => { data: { testingScalar: { value: 'test' - } + }, + listTestingScalar: [{ + value: 'test' + }] }, }); expect(mergedResult).to.deep.equal(scalarResult); @@ -605,6 +624,7 @@ testCombinations.forEach(async combination => { query { color(input: RED) numericEnum + listNumericEnum numericEnumInfo: __type(name: "NumericEnum") { enumValues(includeDeprecated: true) { name @@ -633,6 +653,7 @@ testCombinations.forEach(async combination => { query { color(input: RED) numericEnum + listNumericEnum numericEnumInfo: __type(name: "NumericEnum") { enumValues(includeDeprecated: true) { name @@ -659,6 +680,7 @@ testCombinations.forEach(async combination => { data: { color: 'RED', numericEnum: 'TEST', + listNumericEnum: ['TEST'], numericEnumInfo: { enumValues: [ { From dd5abab3b8eef6901c159ccc109bdba3b717e135 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 4 Jul 2019 21:20:41 -0400 Subject: [PATCH 025/250] fix(stitching): do not convert falsy values to null. Closes #10. Renamed resultObject to result to remind code/coders that the result may be a scalrar, not an object, and may be falsy without being equivalent to null. --- src/stitching/checkResultAndHandleErrors.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index cc409dfa703..f4ecf4f5e65 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -43,10 +43,10 @@ export function checkResultAndHandleErrors( export function handleResult( info: GraphQLResolveInfo, - resultObject: any, + result: any, errors: ReadonlyArray ): any { - if (!resultObject) { + if (result == null) { if (errors.length) { throw relocatedError( combineErrors(errors), @@ -61,10 +61,10 @@ export function handleResult( const nullableType = getNullableType(info.returnType); if (isObjectType(nullableType) || isListType(nullableType)) { - annotateWithChildrenErrors(resultObject, errors); + annotateWithChildrenErrors(result, errors); } - return parseOutputValue(nullableType, resultObject); + return parseOutputValue(nullableType, result); } function parseOutputValue(type: GraphQLType, value: any) { From 39fc00784562a5acabc4d2696b1f99590e3ad8e0 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 8 Jul 2019 22:35:25 -0400 Subject: [PATCH 026/250] fix(mocking): to work with schema stitching --- src/stitching/mergeSchemas.ts | 6 +- src/test/testIntegration.ts | 144 ++++++++++++++++++++++++++++++++++ src/test/tests.ts | 1 + 3 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 src/test/testIntegration.ts diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 81a1de99f34..2731307f31a 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -222,7 +222,7 @@ export default function mergeSchemas({ throw new Error(`Invalid mergeTypeCandidates result for type ${typeName}`); } types[typeName] = recreateType(type, resolveType, false); - if (typeResolvers) { + if (typeResolvers !== undefined) { generatedResolvers[typeName] = typeResolvers; } }); @@ -481,11 +481,11 @@ function mergeTypeCandidates( fields = { ...fields, ...candidateFields }; Object.keys(candidateFields).forEach(fieldName => { resolvers[fieldName] = { - [resolverKey]: createDelegatingResolver( + [resolverKey]: schema ? createDelegatingResolver( schema, operationName, fieldName, - ), + ) : null, }; }); }); diff --git a/src/test/testIntegration.ts b/src/test/testIntegration.ts new file mode 100644 index 00000000000..776401890d9 --- /dev/null +++ b/src/test/testIntegration.ts @@ -0,0 +1,144 @@ +/* tslint:disable:no-unused-expression */ + +// The below is meant to be an alternative canonical schema stitching example +// which intermingles local (mocked) resolvers and stitched schemas and does +// not require use of the fragment field, because it follows best practices of +// always returning the necessary object fields: +// https://medium.com/paypal-engineering/graphql-resolvers-best-practices-cd36fdbcef55 + +// The fragment field is still necessary when working with a remote schema +// where this is not possible. + +import { expect } from 'chai'; +import { graphql } from 'graphql'; +import { delegateToSchema, mergeSchemas } from '../index'; +import { addMockFunctionsToSchema } from '../mock'; + +const chirpTypeDefs = ` + type Chirp { + id: ID! + text: String + authorId: ID! + author: User + } +`; + +const authorTypeDefs = ` + type User { + id: ID! + email: String + chirps: [Chirp] + } +`; + +const schemas = {}; +const getSchema = (name: string) => schemas[name]; + +const chirpSchema = mergeSchemas({ + schemas: [ + chirpTypeDefs, + authorTypeDefs, + ` + type Query { + chirpById(id: ID!): Chirp + chirpsByAuthorId(authorId: ID!): [Chirp] + } + ` + ], + resolvers: { + Chirp: { + author: (chirp, args, context, info) => { + return delegateToSchema({ + schema: getSchema('authorSchema'), + operation: 'query', + fieldName: 'userById', + args: { + id: chirp.authorId + }, + context, + info + }); + } + } + } +}); + +addMockFunctionsToSchema({ + schema: chirpSchema, + mocks: { + Chirp: () => ({ + authorId: '1' + }), + }, + preserveResolvers: true +}); + +const authorSchema = mergeSchemas({ + schemas: [ + chirpTypeDefs, + authorTypeDefs, + ` + type Query { + userById(id: ID!): User + } + ` + ], + resolvers: { + User: { + chirps: (user, args, context, info) => { + return delegateToSchema({ + schema: getSchema('chirpSchema'), + operation: 'query', + fieldName: 'chirpsByAuthorId', + args: { + authorId: user.id + }, + context, + info + }); + } + } + } +}); + +addMockFunctionsToSchema({ + schema: authorSchema, + mocks: { + User: () => ({ + id: '1' + }), + }, + preserveResolvers: true +}); + +schemas['chirpSchema'] = chirpSchema; +schemas['authorSchema'] = authorSchema; + +const mergedSchema = mergeSchemas({ + schemas: Object.keys(schemas).map(schemaName => schemas[schemaName]) +}); + +describe('merging without specifying fragments', () => { + it('works', async () => { + const query = ` + query { + userById(id: 5) { + chirps { + id + textAlias: text + author { + email + } + } + } + } + `; + + const result = await graphql(mergedSchema, query); + + expect(result.errors).to.be.undefined; + expect(result.data.userById.chirps[1].id).to.not.be.null; + expect(result.data.userById.chirps[1].text).to.not.be.null; + expect(result.data.userById.chirps[1].author.email).to.not.be.null; + }); +}); diff --git a/src/test/tests.ts b/src/test/tests.ts index 7da887e0719..7febbb10f6f 100755 --- a/src/test/tests.ts +++ b/src/test/tests.ts @@ -13,3 +13,4 @@ import './testResolution'; import './testSchemaGenerator'; import './testTransforms'; import './testExtensionExtraction'; +import './testIntegration'; From 230af75322b7600e5c881da366e7884470f3e3dd Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Fri, 19 Jul 2019 01:04:06 -0400 Subject: [PATCH 027/250] fix(stitching): support stitching unions of types with enums Closes #13. --- src/stitching/checkResultAndHandleErrors.ts | 9 +++---- src/test/testMergeSchemas.ts | 28 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index f4ecf4f5e65..f472bc1dda5 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -2,10 +2,9 @@ import { GraphQLResolveInfo, responsePathAsArray, getNullableType, - isObjectType, + isCompositeType, + isLeafType, isListType, - isEnumType, - isScalarType, ExecutionResult, GraphQLError, GraphQLType, @@ -60,7 +59,7 @@ export function handleResult( const nullableType = getNullableType(info.returnType); - if (isObjectType(nullableType) || isListType(nullableType)) { + if (isCompositeType(nullableType) || isListType(nullableType)) { annotateWithChildrenErrors(result, errors); } @@ -70,7 +69,7 @@ export function handleResult( function parseOutputValue(type: GraphQLType, value: any) { if (isListType(type)) { return value.map((v: any) => parseOutputValue(getNullableType(type.ofType), v)); - } else if (isEnumType(type) || isScalarType(type)) { + } else if (isLeafType(type)) { return type.parseValue(value); } return value; diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 1c3f7f85097..d8c247415ca 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -130,6 +130,8 @@ let enumTest = ` numericEnum: NumericEnum } + union UnionWithEnum = EnumWrapper + schema { query: Query } @@ -139,6 +141,7 @@ let enumTest = ` numericEnum: NumericEnum listNumericEnum: [NumericEnum] wrappedEnum: EnumWrapper + unionWithEnum: UnionWithEnum } `; @@ -153,6 +156,9 @@ enumSchema = makeExecutableSchema({ NumericEnum: { TEST: 1, }, + UnionWithEnum: { + __resolveType: () => 'EnumWrapper' + }, Query: { color(parent, args) { return args.input === '#EA3232' ? args.input : null; @@ -169,6 +175,12 @@ enumSchema = makeExecutableSchema({ numericEnum: 1 }; }, + unionWithEnum() { + return { + color: '#EA3232', + numericEnum: 1 + }; + }, }, }, }); @@ -643,6 +655,12 @@ testCombinations.forEach(async combination => { color numericEnum } + unionWithEnum { + ... on EnumWrapper { + color + numericEnum + } + } } `, ); @@ -672,6 +690,12 @@ testCombinations.forEach(async combination => { color numericEnum } + unionWithEnum { + ... on EnumWrapper { + color + numericEnum + } + } } `, ); @@ -703,6 +727,10 @@ testCombinations.forEach(async combination => { color: 'RED', numericEnum: 'TEST', }, + unionWithEnum: { + color: 'RED', + numericEnum: 'TEST', + }, }, }); expect(mergedResult).to.deep.equal(enumResult); From 490e4331ffccdeb9cde4210ab31b5065fc90edc0 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 23 Jul 2019 00:09:20 -0400 Subject: [PATCH 028/250] feat(stitching): allow delegateToSchema, mergeSchemas and transformSchema to take remote schema configurations as parameters This removes the need for makeRemoteExecutableSchema, removing an unnecessary layer of schema delegation. This change introduces a RemoteGraphQLSchema type which consists of a GraphQLSchema object annotated with link, fetcher, or dispatcher properties that can be used by delegateToSchema to access the remote schema. The dispatcher function takes the graphql context eventually passed to delegateToSchema as an argument and returns a link or fetcher function. --- src/Interfaces.ts | 51 +++++++ src/stitching/delegateToRemoteSchema.ts | 60 ++++++++ src/stitching/delegateToSchema.ts | 52 ++++--- src/stitching/index.ts | 2 + src/stitching/introspectSchema.ts | 2 +- src/stitching/linkToFetcher.ts | 4 +- src/stitching/makeRemoteExecutableSchema.ts | 34 +---- src/stitching/mergeSchemas.ts | 62 +++++++-- src/stitching/resolvers.ts | 25 +++- src/test/testAlternateMergeSchemas.ts | 22 +-- src/test/testMakeRemoteExecutableSchema.ts | 6 +- src/test/testMergeSchemas.ts | 145 +++++++++++++------- src/test/testingSchemas.ts | 43 ++++-- src/transforms/transformSchema.ts | 11 +- 14 files changed, 373 insertions(+), 146 deletions(-) create mode 100644 src/stitching/delegateToRemoteSchema.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 34a5e8c4438..2c174c87324 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -3,6 +3,7 @@ import { GraphQLField, ExecutionResult, GraphQLType, + GraphQLNamedType, GraphQLFieldResolver, GraphQLResolveInfo, GraphQLIsTypeOfFn, @@ -14,6 +15,8 @@ import { import { SchemaDirectiveVisitor } from './schemaVisitor'; +import { ApolloLink } from 'apollo-link'; + /* TODO: Add documentation */ export type UnitOrList = Type | Array; @@ -50,6 +53,46 @@ export interface IGraphQLToolsResolveInfo extends GraphQLResolveInfo { mergeInfo?: MergeInfo; } +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 type SchemaExecutionConfig = { + schema: GraphQLSchemaWithTransforms; +}; + +export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array }; + +export type RemoteSchemaExecutionConfig = { + schema: GraphQLSchemaWithTransforms; + link?: ApolloLink; + fetcher?: Fetcher; + dispatcher?: Dispatcher; +}; + +export function isSchemaExecutionConfig( + schema: string | GraphQLSchema | SchemaExecutionConfig | DocumentNode | Array +): schema is SchemaExecutionConfig { + return !!(schema as SchemaExecutionConfig).schema; +} + +export function isRemoteSchemaExecutionConfig( + schema: GraphQLSchema | SchemaExecutionConfig +): schema is RemoteSchemaExecutionConfig { + return ( + !!(schema as RemoteSchemaExecutionConfig).dispatcher || + !!(schema as RemoteSchemaExecutionConfig).link || + !!(schema as RemoteSchemaExecutionConfig).fetcher + ); +} + export interface IDelegateToSchemaOptions { schema: GraphQLSchema; operation: Operation; @@ -59,8 +102,16 @@ export interface IDelegateToSchemaOptions { info: IGraphQLToolsResolveInfo; transforms?: Array; skipValidation?: boolean; + executor?: Delegator; + subscriber?: Delegator; } +export type Delegator = ({ document, context, variables }: { + document: DocumentNode; + context?: { [key: string]: any }; + variables?: { [key: string]: any }; +}) => any; + export type MergeInfo = { delegate: ( type: 'query' | 'mutation' | 'subscription', diff --git a/src/stitching/delegateToRemoteSchema.ts b/src/stitching/delegateToRemoteSchema.ts new file mode 100644 index 00000000000..ffc596ce4f4 --- /dev/null +++ b/src/stitching/delegateToRemoteSchema.ts @@ -0,0 +1,60 @@ +import { + IDelegateToSchemaOptions, + RemoteSchemaExecutionConfig, + Fetcher +} from '../Interfaces'; +import { ApolloLink } from 'apollo-link'; +import { observableToAsyncIterable } from './observableToAsyncIterable'; +import linkToFetcher, { execute } from './linkToFetcher'; +import delegateToSchema from './delegateToSchema'; + +export default function delegateToRemoteSchema( + options: IDelegateToSchemaOptions & RemoteSchemaExecutionConfig +): Promise { + if (options.operation === 'query' || options.operation === 'mutation') { + let fetcher: Fetcher; + if (options.dispatcher) { + const dynamicLinkOrFetcher = options.dispatcher(context); + fetcher = (typeof dynamicLinkOrFetcher === 'function') ? + dynamicLinkOrFetcher : + linkToFetcher(dynamicLinkOrFetcher); + } else if (options.link) { + fetcher = linkToFetcher(options.link); + } else { + fetcher = options.fetcher; + } + + if (!options.executor) { + options.executor = ({ document, context, variables }) => fetcher({ + query: document, + variables, + context: { graphqlContext: context } + }); + } + + return delegateToSchema(options); + } + + if (options.operation === 'subscription') { + let link: ApolloLink; + if (options.dispatcher) { + link = options.dispatcher(context) as ApolloLink; + } else { + link = options.link; + } + + if (!options.subscriber) { + options.subscriber = ({ document, context, variables }) => { + const operation = { + query: document, + variables, + context: { graphqlContext: context } + }; + const observable = execute(link, operation); + return observableToAsyncIterable(observable); + }; + } + + return delegateToSchema(options); + } +} diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index b2c1ced76ec..3eb5c30434c 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -16,7 +16,11 @@ import { NameNode, } from 'graphql'; -import { Operation, Request, IDelegateToSchemaOptions } from '../Interfaces'; +import { + IDelegateToSchemaOptions, + Operation, + Request, +} from '../Interfaces'; import { applyRequestTransforms, @@ -93,29 +97,45 @@ async function delegateToSchemaImplementation( } if (operation === 'query' || operation === 'mutation') { + if (!options.executor) { + options.executor = ({ document, context, variables }) => execute({ + schema: options.schema, + document, + rootValue: info.rootValue, + contextValue: context, + variableValues: variables, + }); + } + return applyResultTransforms( - await execute( - options.schema, - processedRequest.document, - info.rootValue, - options.context, - processedRequest.variables, - ), + await options.executor({ + document: processedRequest.document, + context: options.context, + variables: processedRequest.variables + }), transforms, ); } if (operation === 'subscription') { - const executionResult = (await subscribe( - options.schema, - processedRequest.document, - info.rootValue, - options.context, - processedRequest.variables, - )) as AsyncIterator; + if (!options.subscriber) { + options.subscriber = ({ document, context, variables }) => subscribe({ + schema: options.schema, + document, + rootValue: info.rootValue, + contextValue: context, + variableValues: variables, + }); + } + + const originalAsyncIterator = (await options.subscriber({ + document: processedRequest.document, + context: options.context, + variables: processedRequest.variables, + })) as AsyncIterator; // "subscribe" to the subscription result and map the result through the transforms - return mapAsyncIterator(executionResult, result => { + return mapAsyncIterator(originalAsyncIterator, result => { const transformedResult = applyResultTransforms(result, transforms); // wrap with fieldName to return for an additional round of resolutioon diff --git a/src/stitching/index.ts b/src/stitching/index.ts index 72724c6fb99..2896edfb94b 100644 --- a/src/stitching/index.ts +++ b/src/stitching/index.ts @@ -2,6 +2,7 @@ import makeRemoteExecutableSchema, { createResolver as defaultCreateRemoteResolv import introspectSchema from './introspectSchema'; import mergeSchemas from './mergeSchemas'; import delegateToSchema from './delegateToSchema'; +import delegateToRemoteSchema from './delegateToRemoteSchema'; import defaultMergedResolver from './defaultMergedResolver'; export { @@ -11,6 +12,7 @@ export { // Those are currently undocumented and not part of official API, // but exposed for the community use delegateToSchema, + delegateToRemoteSchema, defaultMergedResolver, defaultCreateRemoteResolver }; diff --git a/src/stitching/introspectSchema.ts b/src/stitching/introspectSchema.ts index b4708bad958..0f12bc4da4e 100644 --- a/src/stitching/introspectSchema.ts +++ b/src/stitching/introspectSchema.ts @@ -2,7 +2,7 @@ import { GraphQLSchema, DocumentNode } from 'graphql'; import { buildClientSchema, parse } from 'graphql'; import { getIntrospectionQuery } from 'graphql/utilities'; import { ApolloLink } from 'apollo-link'; -import { Fetcher } from './makeRemoteExecutableSchema'; +import { Fetcher } from '../Interfaces'; import linkToFetcher from './linkToFetcher'; const parsedIntrospectionQuery: DocumentNode = parse(getIntrospectionQuery()); diff --git a/src/stitching/linkToFetcher.ts b/src/stitching/linkToFetcher.ts index 9a6f75c9d8e..98361b4f7ee 100644 --- a/src/stitching/linkToFetcher.ts +++ b/src/stitching/linkToFetcher.ts @@ -1,4 +1,4 @@ -import { Fetcher, FetcherOperation } from './makeRemoteExecutableSchema'; +import { Fetcher, IFetcherOperation } from '../Interfaces'; import { ApolloLink, // This import doesn't actually import code - only the types. @@ -10,7 +10,7 @@ import { export { execute } from 'apollo-link'; export default function linkToFetcher(link: ApolloLink): Fetcher { - return (fetcherOperation: FetcherOperation) => { + return (fetcherOperation: IFetcherOperation) => { return makePromise(execute(link, fetcherOperation as GraphQLRequest)); }; } diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index dbb5d3d8a0a..53a1d7afa09 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -14,17 +14,15 @@ import { GraphQLBoolean, GraphQLInt, GraphQLScalarType, - ExecutionResult, buildSchema, printSchema, Kind, GraphQLResolveInfo, - DocumentNode, BuildSchemaOptions } from 'graphql'; import linkToFetcher, { execute } from './linkToFetcher'; import isEmptyObject from '../isEmptyObject'; -import { IResolvers, IResolverObject } from '../Interfaces'; +import { IResolvers, IResolverObject, Fetcher } from '../Interfaces'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { recreateType } from './schemaRecreation'; import resolveParentFromTypename from './resolveFromParentTypename'; @@ -39,36 +37,6 @@ export type ResolverFn = ( info?: GraphQLResolveInfo ) => AsyncIterator; -export type Fetcher = (operation: FetcherOperation) => Promise; - -export type FetcherOperation = { - query: DocumentNode; - operationName?: string; - variables?: { [key: string]: any }; - context?: { [key: string]: any }; -}; - -/** - * This type has been copied inline from its source on `@types/graphql`: - * - * https://git.io/Jv8NX - * - * Previously, it was imported from `graphql/utilities/schemaPrinter`, however - * that module has been removed in `graphql@15`. Furthermore, the sole property - * on this type is due to be deprecated in `graphql@16`. - */ -interface PrintSchemaOptions { - /** - * Descriptions are defined as preceding string literals, however an older - * experimental version of the SDL supported preceding comments as - * descriptions. Set to true to enable this deprecated behavior. - * This option is provided to ease adoption and will be removed in v16. - * - * Default: false - */ - commentDescriptions?: boolean; -} - export default function makeRemoteExecutableSchema({ schema, link, diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 2731307f31a..ca686f9c8bc 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -19,6 +19,9 @@ import { IResolvers, MergeInfo, IResolversParameter, + SchemaExecutionConfig, + isSchemaExecutionConfig, + isRemoteSchemaExecutionConfig, } from '../Interfaces'; import { extractExtensionDefinitions, @@ -31,6 +34,7 @@ import { createResolveType, } from './schemaRecreation'; import delegateToSchema from './delegateToSchema'; +import delegateToRemoteSchema from './delegateToRemoteSchema'; import typeFromAST from './typeFromAST'; import { Transform, @@ -42,6 +46,7 @@ import { SchemaDirectiveVisitor } from '../schemaVisitor'; type MergeTypeCandidate = { schema?: GraphQLSchema; + executionConfig?: SchemaExecutionConfig; type: GraphQLNamedType; }; @@ -77,7 +82,7 @@ export default function mergeSchemas({ mergeDirectives, }: { schemas: Array< - string | GraphQLSchema | DocumentNode | Array + string | GraphQLSchema | SchemaExecutionConfig | DocumentNode | Array >; onTypeConflict?: OnTypeConflict; resolvers?: IResolversParameter; @@ -102,7 +107,16 @@ export default function mergeSchemas({ return types[name]; }); - schemas.forEach(schema => { + schemas.forEach(schemaOrSchemaExecutionConfig => { + let schema: string | GraphQLSchema | SchemaExecutionConfig | DocumentNode | Array; + let executionConfig: SchemaExecutionConfig; + if (isSchemaExecutionConfig(schemaOrSchemaExecutionConfig)) { + executionConfig = schemaOrSchemaExecutionConfig; + schema = schemaOrSchemaExecutionConfig.schema; + } else { + schema = schemaOrSchemaExecutionConfig; + } + if (schema instanceof GraphQLSchema) { allSchemas.push(schema); const queryType = schema.getQueryType(); @@ -111,18 +125,21 @@ export default function mergeSchemas({ if (queryType) { addTypeCandidate(typeCandidates, 'Query', { schema, + executionConfig, type: queryType, }); } if (mutationType) { addTypeCandidate(typeCandidates, 'Mutation', { schema, + executionConfig, type: mutationType, }); } if (subscriptionType) { addTypeCandidate(typeCandidates, 'Subscription', { schema, + executionConfig, type: subscriptionType, }); } @@ -145,8 +162,9 @@ export default function mergeSchemas({ type !== subscriptionType ) { addTypeCandidate(typeCandidates, type.name, { - schema, - type: type, + schema: schema as GraphQLSchema, + executionConfig, + type, }); } }); @@ -162,7 +180,7 @@ export default function mergeSchemas({ directives.push(type); } else if (type && !(type instanceof GraphQLDirective)) { addTypeCandidate(typeCandidates, type.name, { - type: type, + type, }); } }); @@ -176,7 +194,7 @@ export default function mergeSchemas({ } else if (Array.isArray(schema)) { schema.forEach(type => { addTypeCandidate(typeCandidates, type.name, { - type: type, + type, }); }); } else { @@ -372,11 +390,30 @@ function guessSchemaByRootField( ); } -function createDelegatingResolver( +function createDelegatingResolver({ + schema, + executionConfig, + operation, + fieldName, +}: { schema: GraphQLSchema, + executionConfig?: SchemaExecutionConfig, operation: 'query' | 'mutation' | 'subscription', fieldName: string, -): IFieldResolver { +}): IFieldResolver { + if (executionConfig && isRemoteSchemaExecutionConfig(executionConfig)) { + return (root, args, context, info) => { + return delegateToRemoteSchema({ + ...executionConfig, + operation, + fieldName, + args, + context, + info, + }); + }; + } + return (root, args, context, info) => { return info.mergeInfo.delegateToSchema({ schema, @@ -476,16 +513,17 @@ function mergeTypeCandidates( const resolvers = {}; const resolverKey = operationName === 'subscription' ? 'subscribe' : 'resolve'; - candidates.forEach(({ type: candidateType, schema }) => { + candidates.forEach(({ type: candidateType, schema, executionConfig }) => { const candidateFields = (candidateType as GraphQLObjectType).getFields(); fields = { ...fields, ...candidateFields }; Object.keys(candidateFields).forEach(fieldName => { resolvers[fieldName] = { - [resolverKey]: schema ? createDelegatingResolver( + [resolverKey]: schema ? createDelegatingResolver({ schema, - operationName, + executionConfig, + operation: operationName, fieldName, - ) : null, + }) : null, }; }); }); diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 9a41972c468..784387fb33b 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -3,7 +3,12 @@ import { GraphQLFieldResolver, GraphQLObjectType, } from 'graphql'; -import { IResolvers, Operation } from '../Interfaces'; +import { + IResolvers, + Operation, + SchemaExecutionConfig, + isRemoteSchemaExecutionConfig, +} from '../Interfaces'; import delegateToSchema from './delegateToSchema'; import { Transform } from '../transforms/index'; @@ -17,7 +22,7 @@ export type Mapping = { }; export function generateProxyingResolvers( - targetSchema: GraphQLSchema, + targetSchema: GraphQLSchema | SchemaExecutionConfig, transforms: Array, mapping: Mapping, ): IResolvers { @@ -85,13 +90,25 @@ export function generateMappingFromObjectType( } function createProxyingResolver( - schema: GraphQLSchema, + schema: GraphQLSchema | SchemaExecutionConfig, operation: Operation, fieldName: string, transforms: Array, ): GraphQLFieldResolver { + if (isRemoteSchemaExecutionConfig(schema)) { + return (parent, args, context, info) => delegateToSchema({ + ...schema, + operation, + fieldName, + args: {}, + context, + info, + transforms, + }); + } + return (parent, args, context, info) => delegateToSchema({ - schema, + schema: schema as GraphQLSchema, operation, fieldName, args: {}, diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index fa23364a22b..480eeef192b 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -25,7 +25,7 @@ import { } from '../transforms'; import { propertySchema, - bookingSchema, + remoteBookingSchema, subscriptionSchema, subscriptionPubSub, subscriptionPubSubTrigger, @@ -33,7 +33,8 @@ import { import { forAwaitEach } from 'iterall'; import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import { delegateToSchema } from '../stitching'; +import { delegateToSchema, delegateToRemoteSchema } from '../stitching'; +import { SchemaExecutionConfig } from '../Interfaces'; let linkSchema = ` """ @@ -80,9 +81,12 @@ let linkSchema = ` `; describe('merge schemas through transforms', () => { + let bookingSchemaExecConfig: SchemaExecutionConfig; let mergedSchema: GraphQLSchema; before(async () => { + bookingSchemaExecConfig = await remoteBookingSchema; + // namespace and strip schemas const transformedPropertySchema = transformSchema(propertySchema, [ new FilterRootFields( @@ -92,7 +96,7 @@ describe('merge schemas through transforms', () => { new RenameTypes((name: string) => `Properties_${name}`), new RenameRootFields((operation: string, name: string) => `Properties_${name}`), ]); - const transformedBookingSchema = transformSchema(bookingSchema, [ + const transformedBookingSchema = transformSchema(bookingSchemaExecConfig, [ new FilterRootFields( (operation: string, rootField: string) => 'Query.bookings' === `${operation}.${rootField}`, @@ -134,8 +138,8 @@ describe('merge schemas through transforms', () => { transforms: transformedPropertySchema.transforms, }); } else if (args.id.startsWith('b')) { - return info.mergeInfo.delegateToSchema({ - schema: bookingSchema, + return delegateToRemoteSchema({ + ...bookingSchemaExecConfig, operation: 'query', fieldName: 'bookingById', args, @@ -144,8 +148,8 @@ describe('merge schemas through transforms', () => { transforms: transformedBookingSchema.transforms, }); } else if (args.id.startsWith('c')) { - return info.mergeInfo.delegateToSchema({ - schema: bookingSchema, + return delegateToRemoteSchema({ + ...bookingSchemaExecConfig, operation: 'query', fieldName: 'customerById', args, @@ -162,8 +166,8 @@ describe('merge schemas through transforms', () => { bookings: { fragment: 'fragment PropertyFragment on Property { id }', resolve(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ - schema: bookingSchema, + return delegateToRemoteSchema({ + ...bookingSchemaExecConfig, operation: 'query', fieldName: 'bookingsByPropertyId', args: { diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index 9eb425a315b..e17ad9c0b92 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -14,7 +14,11 @@ import { makeRemoteExecutableSchema } from '../stitching'; describe('remote subscriptions', () => { let schema: GraphQLSchema; before(async () => { - schema = await makeSchemaRemoteFromLink(subscriptionSchema); + const remoteSchemaExecConfig = await makeSchemaRemoteFromLink(subscriptionSchema); + schema = makeRemoteExecutableSchema({ + schema: remoteSchemaExecConfig.schema, + link: remoteSchemaExecConfig.link + }); }); it('should work', done => { diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index d8c247415ca..abe2f529efe 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -12,6 +12,7 @@ import { ExecutionResult, defaultFieldResolver, findDeprecatedUsages, + GraphQLResolveInfo, } from 'graphql'; import mergeSchemas from '../stitching/mergeSchemas'; import { @@ -28,7 +29,14 @@ import { import { SchemaDirectiveVisitor } from '../schemaVisitor'; import { forAwaitEach } from 'iterall'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import { IResolvers } from '../Interfaces'; +import { + IResolvers, + SchemaExecutionConfig, + isRemoteSchemaExecutionConfig, + Operation, +} from '../Interfaces'; +import { delegateToSchema, delegateToRemoteSchema } from '../stitching'; +import { Transform } from '../transforms'; const removeLocations = ({ locations, ...rest }: any): any => ({ ...rest }); @@ -316,12 +324,33 @@ let schemaDirectiveTypeDefs = ` } `; +function delegateToLocalOrRemote(options: { + schema: GraphQLSchema | SchemaExecutionConfig; + operation: Operation; + fieldName: string; + args?: { [key: string]: any }; + context: any; + info: GraphQLResolveInfo; + transforms?: Array; +}) { + if (isRemoteSchemaExecutionConfig(options.schema)) { + return delegateToRemoteSchema({ + ...options, + ...options.schema, + }); + } + return delegateToSchema({ + ...options, + schema: options.schema as GraphQLSchema + }); +} + testCombinations.forEach(async combination => { describe('merging ' + combination.name, () => { let mergedSchema: GraphQLSchema, - propertySchema: GraphQLSchema, - productSchema: GraphQLSchema, - bookingSchema: GraphQLSchema; + propertySchema: GraphQLSchema | SchemaExecutionConfig, + productSchema: GraphQLSchema | SchemaExecutionConfig, + bookingSchema: GraphQLSchema | SchemaExecutionConfig; before(async () => { propertySchema = await combination.property; @@ -362,18 +391,32 @@ testCombinations.forEach(async combination => { bookings: { fragment: '... on Property { id }', resolve(parent, args, context, info) { - // Use the old mergeInfo.delegate API just this once, to make - // sure it continues to work. - return info.mergeInfo.delegate( - 'query', - 'bookingsByPropertyId', - { - propertyId: parent.id, - limit: args.limit ? args.limit : null, - }, - context, - info, - ); + if (combination.name === 'local') { + // Use the old mergeInfo.delegate API just this once, to make + // sure it continues to work. + return info.mergeInfo.delegate( + 'query', + 'bookingsByPropertyId', + { + propertyId: parent.id, + limit: args.limit ? args.limit : null, + }, + context, + info, + ); + } else { + return delegateToLocalOrRemote({ + schema: bookingSchema, + operation: 'query', + fieldName: 'bookingsByPropertyId', + args: { + propertyId: parent.id, + limit: args.limit ? args.limit : null, + }, + context, + info, + }); + } }, }, someField: { @@ -386,7 +429,7 @@ testCombinations.forEach(async combination => { property: { fragment: 'fragment BookingFragment on Booking { propertyId }', resolve(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -413,7 +456,7 @@ testCombinations.forEach(async combination => { LinkType: { property: { resolve(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -428,7 +471,7 @@ testCombinations.forEach(async combination => { }, Query: { delegateInterfaceTest(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'interfaceTest', @@ -440,7 +483,7 @@ testCombinations.forEach(async combination => { }); }, delegateArgumentTest(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -461,7 +504,7 @@ testCombinations.forEach(async combination => { fragment: '... on Node { id }', resolve(parent, args, context, info) { if (args.id.startsWith('p')) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -470,7 +513,7 @@ testCombinations.forEach(async combination => { info, }); } else if (args.id.startsWith('b')) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: bookingSchema, operation: 'query', fieldName: 'bookingById', @@ -479,7 +522,7 @@ testCombinations.forEach(async combination => { info, }); } else if (args.id.startsWith('c')) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: bookingSchema, operation: 'query', fieldName: 'customerById', @@ -493,14 +536,14 @@ testCombinations.forEach(async combination => { }, }, async nodes(parent, args, context, info) { - const bookings = await info.mergeInfo.delegateToSchema({ + const bookings = await delegateToLocalOrRemote({ schema: bookingSchema, operation: 'query', fieldName: 'bookings', context, info, }); - const properties = await info.mergeInfo.delegateToSchema({ + const properties = await delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'properties', @@ -517,7 +560,7 @@ testCombinations.forEach(async combination => { describe('basic', () => { it('works with context', async () => { const propertyResult = await graphql( - propertySchema, + localPropertySchema, ` query { contextTest(key: "test") @@ -553,7 +596,7 @@ testCombinations.forEach(async combination => { it('works with custom scalars', async () => { const propertyResult = await graphql( - propertySchema, + localPropertySchema, ` query { dateTimeTest @@ -755,12 +798,12 @@ bookingById(id: "b1") { `; const propertyResult = await graphql( - propertySchema, + localPropertySchema, `query { ${propertyFragment} }`, ); const bookingResult = await graphql( - bookingSchema, + localBookingSchema, `query { ${bookingFragment} }`, ); @@ -801,7 +844,7 @@ bookingById(id: "b1") { }; const bookingResult = await graphql( - bookingSchema, + localBookingSchema, mutationFragment, {}, {}, @@ -1012,7 +1055,7 @@ bookingById(id: "b1") { } } `; - const propertyResult = await graphql(propertySchema, query); + const propertyResult = await graphql(localPropertySchema, query); const mergedResult = await graphql(mergedSchema, query); expect(propertyResult).to.deep.equal({ @@ -1382,7 +1425,7 @@ bookingById(id: "b1") { bookings: { fragment: 'fragment PropertyFragment on Property { id }', resolve(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: bookingSchema, operation: 'query', fieldName: 'bookingsByPropertyId', @@ -1402,7 +1445,7 @@ bookingById(id: "b1") { property: { fragment: 'fragment BookingFragment on Booking { propertyId }', resolve(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -1429,7 +1472,7 @@ bookingById(id: "b1") { const Query2: IResolvers = { Query: { delegateInterfaceTest(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'interfaceTest', @@ -1441,7 +1484,7 @@ bookingById(id: "b1") { }); }, delegateArgumentTest(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -1462,7 +1505,7 @@ bookingById(id: "b1") { fragment: 'fragment NodeFragment on Node { id }', resolve(parent, args, context, info) { if (args.id.startsWith('p')) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -1471,7 +1514,7 @@ bookingById(id: "b1") { info, }); } else if (args.id.startsWith('b')) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: bookingSchema, operation: 'query', fieldName: 'bookingById', @@ -1480,7 +1523,7 @@ bookingById(id: "b1") { info, }); } else if (args.id.startsWith('c')) { - return info.mergeInfo.delegateToSchema({ + return delegateToLocalOrRemote({ schema: bookingSchema, operation: 'query', fieldName: 'customerById', @@ -1499,14 +1542,14 @@ bookingById(id: "b1") { const AsyncQuery: IResolvers = { Query: { async nodes(parent, args, context, info) { - const bookings = await info.mergeInfo.delegateToSchema({ + const bookings = await delegateToLocalOrRemote({ schema: bookingSchema, operation: 'query', fieldName: 'bookings', context, info, }); - const properties = await info.mergeInfo.delegateToSchema({ + const properties = await delegateToLocalOrRemote({ schema: propertySchema, operation: 'query', fieldName: 'properties', @@ -1585,7 +1628,7 @@ fragment BookingFragment on Booking { `; const propertyResult = await graphql( - propertySchema, + localPropertySchema, ` ${propertyFragment} query { @@ -1597,7 +1640,7 @@ fragment BookingFragment on Booking { ); const bookingResult = await graphql( - bookingSchema, + localBookingSchema, ` ${bookingFragment} query { @@ -1656,12 +1699,12 @@ bookingById(id: "b1") { `; const propertyResult = await graphql( - propertySchema, + localPropertySchema, `query { ${propertyFragment} }`, ); const bookingResult = await graphql( - bookingSchema, + localBookingSchema, `query { ${bookingFragment} }`, ); @@ -1767,7 +1810,7 @@ fragment BookingFragment on Booking { `; const propertyResult = await graphql( - propertySchema, + localPropertySchema, ` ${propertyFragment1} ${propertyFragment2} @@ -1781,7 +1824,7 @@ fragment BookingFragment on Booking { ); const bookingResult = await graphql( - bookingSchema, + localBookingSchema, ` ${bookingFragment} query { @@ -2005,7 +2048,7 @@ fragment BookingFragment on Booking { `; const propertyResult = await graphql( - propertySchema, + localPropertySchema, `query($p1: ID!) { ${propertyFragment} }`, {}, {}, @@ -2015,7 +2058,7 @@ fragment BookingFragment on Booking { ); const bookingResult = await graphql( - bookingSchema, + localBookingSchema, `query($b1: ID!) { ${bookingFragment} }`, {}, {}, @@ -2244,14 +2287,14 @@ fragment BookingFragment on Booking { `; const propertyResult = await graphql( - propertySchema, + localPropertySchema, `query { ${propertyFragment} }`, ); const bookingResult = await graphql( - bookingSchema, + localBookingSchema, `query { ${bookingFragment} }`, @@ -2386,7 +2429,7 @@ fragment BookingFragment on Booking { `; const propertyResult = await graphql( - propertySchema, + localPropertySchema, propertyQuery, ); diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index 6dafdd21e29..ce6a94cad5a 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -18,10 +18,11 @@ import { ExecutionResult as LinkExecutionResult, } from 'apollo-link'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import { IResolvers } from '../Interfaces'; -import makeRemoteExecutableSchema, { +import { + IResolvers, Fetcher, -} from '../stitching/makeRemoteExecutableSchema'; + RemoteSchemaExecutionConfig, +} from '../Interfaces'; import introspectSchema from '../stitching/introspectSchema'; import { PubSub } from 'graphql-subscriptions'; @@ -706,9 +707,8 @@ const hasSubscriptionOperation = ({ query }: { query: any }): boolean => { return false; }; -// Pretend this schema is remote -export async function makeSchemaRemoteFromLink(schema: GraphQLSchema) { - const link = new ApolloLink(operation => { +function makeLinkFromSchema(schema: GraphQLSchema) { + return new ApolloLink(operation => { return new Observable(observer => { (async () => { const { query, operationName, variables } = operation; @@ -759,16 +759,31 @@ export async function makeSchemaRemoteFromLink(schema: GraphQLSchema) { })(); }); }); +} +export async function makeSchemaRemoteFromLink(schema: GraphQLSchema) + : Promise { + const link = makeLinkFromSchema(schema); const clientSchema = await introspectSchema(link); - return makeRemoteExecutableSchema({ + return { schema: clientSchema, link, - }); + }; +} + +export async function makeSchemaRemoteFromDispatchedLink(schema: GraphQLSchema) + : Promise { + const link = makeLinkFromSchema(schema); + const clientSchema = await introspectSchema(link); + return { + schema: clientSchema, + dispatcher: () => link, + }; } // ensure fetcher support exists from the 2.0 api -async function makeExecutableSchemaFromFetcher(schema: GraphQLSchema) { +async function makeExecutableSchemaFromDispatchedFetcher(schema: GraphQLSchema) + : Promise { const fetcher: Fetcher = ({ query, operationName, variables, context }) => { return graphql( schema, @@ -781,14 +796,12 @@ async function makeExecutableSchemaFromFetcher(schema: GraphQLSchema) { }; const clientSchema = await introspectSchema(fetcher); - return makeRemoteExecutableSchema({ + return { schema: clientSchema, fetcher, - }); + }; } export const remotePropertySchema = makeSchemaRemoteFromLink(propertySchema); -export const remoteProductSchema = makeSchemaRemoteFromLink(productSchema); -export const remoteBookingSchema = makeExecutableSchemaFromFetcher( - bookingSchema, -); +export const remoteProductSchema = makeSchemaRemoteFromDispatchedLink(productSchema); +export const remoteBookingSchema = makeExecutableSchemaFromDispatchedFetcher(bookingSchema); diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index 3d9f8c7eaa8..a6e70ca5d1a 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -7,15 +7,22 @@ import { generateProxyingResolvers, generateSimpleMapping, } from '../stitching/resolvers'; +import { + SchemaExecutionConfig, + isSchemaExecutionConfig, +} from '../Interfaces'; export default function transformSchema( - targetSchema: GraphQLSchema, + schemaOrSchemaExecutionConfig: GraphQLSchema | SchemaExecutionConfig, transforms: Array, ): GraphQLSchema & { transforms: Array } { + const targetSchema: GraphQLSchema = isSchemaExecutionConfig(schemaOrSchemaExecutionConfig) ? + schemaOrSchemaExecutionConfig.schema : schemaOrSchemaExecutionConfig; + let schema = visitSchema(targetSchema, {}, true); const mapping = generateSimpleMapping(targetSchema); const resolvers = generateProxyingResolvers( - targetSchema, + schemaOrSchemaExecutionConfig, transforms, mapping, ); From c5571fb49d5f23801d136d84690451cef13aaab8 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 25 Jul 2019 01:39:57 -0400 Subject: [PATCH 029/250] fix(types): export OnTypeConflict type --- src/Interfaces.ts | 13 +++++++++++++ src/stitching/mergeSchemas.ts | 14 +------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 2c174c87324..7e92e29a214 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -227,6 +227,19 @@ export interface IMockServer { ) => Promise; } +export type OnTypeConflict = ( + left: GraphQLNamedType, + right: GraphQLNamedType, + info?: { + left: { + schema?: GraphQLSchema; + }; + right: { + schema?: GraphQLSchema; + }; + }, +) => GraphQLNamedType; + export type Operation = 'query' | 'mutation' | 'subscription'; export type Request = { diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index ca686f9c8bc..2f79312af27 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -18,6 +18,7 @@ import { IFieldResolver, IResolvers, MergeInfo, + OnTypeConflict, IResolversParameter, SchemaExecutionConfig, isSchemaExecutionConfig, @@ -56,19 +57,6 @@ type MergeTypeCandidatesResult = { candidate?: MergeTypeCandidate; }; -type OnTypeConflict = ( - left: GraphQLNamedType, - right: GraphQLNamedType, - info?: { - left: { - schema?: GraphQLSchema; - }; - right: { - schema?: GraphQLSchema; - }; - }, -) => GraphQLNamedType; - type CandidateSelector = ( candidates: Array, ) => MergeTypeCandidate; From 194e467a411e123b759b92ab46f266130c4ec2e0 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 29 Jul 2019 00:09:32 -0400 Subject: [PATCH 030/250] feat(transformers): TransformQuery transformer adds errorPathTransformer property to properly return errors from transformed queries. --- src/test/testTransforms.ts | 245 ++++++++++++++++++++++++++++++- src/transforms/TransformQuery.ts | 135 +++++++++++++++++ src/transforms/index.ts | 1 + 3 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 src/transforms/TransformQuery.ts diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 5dc7c6d3ef4..8378959c996 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -12,7 +12,10 @@ import { } from 'graphql'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { propertySchema, bookingSchema } from './testingSchemas'; -import delegateToSchema from '../stitching/delegateToSchema'; +import { + delegateToSchema, + defaultMergedResolver +} from '../stitching'; import { transformSchema, RenameTypes, @@ -21,6 +24,7 @@ import { ExtractField, ReplaceFieldWithFragment, FilterToSchema, + TransformQuery, } from '../transforms'; describe('transforms', () => { @@ -703,6 +707,245 @@ describe('transforms', () => { }); }); + describe('TransformQuery', () => { + let data: any; + let subSchema: GraphQLSchema; + let schema: GraphQLSchema; + before(() => { + data = { + u1: { + id: 'u1', + username: 'alice', + address: { + streetAddress: 'Windy Shore 21 A 7', + zip: '12345', + }, + }, + u2: { + id: 'u2', + username: 'bob', + address: { + streetAddress: 'Snowy Mountain 5 B 77', + zip: '54321', + }, + }, + }; + subSchema = makeExecutableSchema({ + typeDefs: ` + type User { + id: ID! + username: String + address: Address + errorTest: Address + } + + type Address { + streetAddress: String + zip: String + errorTest: String + } + + type Query { + userById(id: ID!): User + } + `, + resolvers: { + User: { + errorTest: () => { + throw new Error('Test Error!'); + } + }, + Address: { + errorTest: () => { + throw new Error('Test Error!'); + } + }, + Query: { + userById(parent, { id }) { + return data[id]; + }, + }, + }, + }); + schema = makeExecutableSchema({ + typeDefs: ` + type Address { + streetAddress: String + zip: String + errorTest: String + } + + type Query { + addressByUser(id: ID!): Address + errorTest(id: ID!): Address + } + `, + resolvers: { + Query: { + addressByUser(parent, { id }, context, info) { + return delegateToSchema({ + schema: subSchema, + operation: 'query', + fieldName: 'userById', + args: { id }, + context, + info, + transforms: [ + // Wrap document takes a subtree as an AST node + new TransformQuery({ + // path at which to apply wrapping and extracting + path: ['userById'], + queryTransformer: (subtree: SelectionSetNode) => ({ + kind: Kind.SELECTION_SET, + selections: [{ + // we create a wrapping AST Field + kind: Kind.FIELD, + name: { + kind: Kind.NAME, + // that field is `address` + value: 'address', + }, + // Inside the field selection + selectionSet: subtree, + }], + }), + // how to process the data result at path + resultTransformer: result => result && result.address, + errorPathTransformer: path => path.slice(1), + }), + ], + }); + }, + errorTest(parent, { id }, context, info) { + return delegateToSchema({ + schema: subSchema, + operation: 'query', + fieldName: 'userById', + args: { id }, + context, + info, + transforms: [ + new TransformQuery({ + path: ['userById'], + queryTransformer: (subtree: SelectionSetNode) => ({ + kind: Kind.SELECTION_SET, + selections: [{ + kind: Kind.FIELD, + name: { + kind: Kind.NAME, + value: 'errorTest', + }, + selectionSet: subtree, + }], + }), + resultTransformer: result => result && result.address, + errorPathTransformer: path => path.slice(1), + }), + ], + }); + }, + }, + }, + }); + }); + + it('wrapping delegation', async () => { + const result = await graphql( + schema, + ` + query { + addressByUser(id: "u1") { + streetAddress + zip + } + } + `, + ); + + expect(result).to.deep.equal({ + data: { + addressByUser: { + streetAddress: 'Windy Shore 21 A 7', + zip: '12345', + }, + }, + }); + }); + + it('preserves errors from underlying fields', async () => { + const result = await graphql( + schema, + ` + query { + addressByUser(id: "u1") { + errorTest + } + } + `, + {}, + {}, + {}, + undefined, + defaultMergedResolver, + ); + + expect(result).to.deep.equal({ + data: { + addressByUser: { + errorTest: null, + }, + }, + errors: [ + { + locations: [ + { + column: 15, + line: 4, + }, + ], + message: 'Test Error!', + path: [ + 'addressByUser', + 'errorTest', + ], + } + ] + }); + }); + + it('preserves errors from the wrapping field', async () => { + const result = await graphql( + schema, + ` + query { + errorTest(id: "u1") { + errorTest + } + } + `, + {}, + {}, + {}, + undefined, + defaultMergedResolver, + ); + + expect(result).to.deep.equal({ + data: { + errorTest: null, + }, + errors: [ + { + locations: [], + message: 'Test Error!', + path: [ + 'errorTest', + ], + } + ] + }); + }); + }); describe('replaces field with fragments', () => { let data: any; diff --git a/src/transforms/TransformQuery.ts b/src/transforms/TransformQuery.ts new file mode 100644 index 00000000000..af41383371f --- /dev/null +++ b/src/transforms/TransformQuery.ts @@ -0,0 +1,135 @@ +import { visit, Kind, SelectionSetNode, FragmentDefinitionNode, GraphQLError } from 'graphql'; +import { Transform } from './transforms'; +import { Request, Result } from '../Interfaces'; + +export type QueryTransformer = ( + selectionSet: SelectionSetNode, + fragments: Record +) => SelectionSetNode; + +export type ResultTransformer = (result: any) => any; + +export type ErrorPathTransformer = (path: ReadonlyArray) => Array; + +export default class TransformQuery implements Transform { + private path: Array; + private queryTransformer: QueryTransformer; + private resultTransformer: ResultTransformer; + private errorPathTransformer: ErrorPathTransformer; + private fragments: Record; + + constructor({ + path, + queryTransformer, + resultTransformer = result => result, + errorPathTransformer = errorPath => [].concat(errorPath), + fragments = {}, + }: { + path: Array; + queryTransformer: QueryTransformer; + resultTransformer?: ResultTransformer; + errorPathTransformer?: ErrorPathTransformer; + fragments?: Record; + }) { + this.path = path; + this.queryTransformer = queryTransformer; + this.resultTransformer = resultTransformer; + this.errorPathTransformer = errorPathTransformer; + this.fragments = fragments; + } + + public transformRequest(originalRequest: Request): Request { + const document = originalRequest.document; + + const pathLength = this.path.length; + let index = 0; + const newDocument = visit(document, { + [Kind.FIELD]: { + enter: node => { + if (index === pathLength || node.name.value !== this.path[index]) { + return false; + } + + index++; + + if (index === pathLength) { + const selectionSet = this.queryTransformer( + node.selectionSet, + this.fragments + ); + + return { + ...node, + selectionSet + }; + } + }, + leave: () => { + index--; + } + } + }); + return { + ...originalRequest, + document: newDocument + }; + } + + public transformResult(originalResult: Result): Result { + const data = this.transformData(originalResult.data); + const errors = originalResult.errors; + return { + data, + errors: errors ? this.transformErrors(errors) : undefined + }; + } + + private transformData(data: any): any { + let index = 0; + let leafIndex = this.path.length - 1; + if (data) { + let next = this.path[index]; + while (index < leafIndex) { + if (data[next]) { + data = data[next]; + } else { + break; + } + index++; + next = this.path[index]; + } + data[next] = this.resultTransformer(data[next]); + } + return data; + } + + private transformErrors(errors: ReadonlyArray): ReadonlyArray { + return errors.map(error => { + let path: ReadonlyArray = error.path; + + let match = true; + let index = 0; + while (index < this.path.length) { + if (path[index] !== this.path[index]) { + match = false; + break; + } + index++; + } + + const newPath = match ? + path.slice(0, index).concat(this.errorPathTransformer(path.slice(index))) : + path; + + return new GraphQLError( + error.message, + error.nodes, + error.source, + error.positions, + newPath, + error.originalError, + error.extensions + ); + }); + } +} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 8ffe9f238f2..f597770dcd8 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -23,3 +23,4 @@ export { default as FilterObjectFields } from './FilterObjectFields'; export { default as ExpandAbstractTypes } from './ExpandAbstractTypes'; export { default as ExtractField } from './ExtractField'; export { default as WrapQuery } from './WrapQuery'; +export { default as TransformQuery } from './TransformQuery'; From c3d7cae74855808116abd816604e9be0110d7116 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 31 Jul 2019 00:50:19 -0400 Subject: [PATCH 031/250] feat(transforms): add dedicated filterSchema function to allow schema filtering without a layer of delegation --- src/test/testAlternateMergeSchemas.ts | 93 +++++++++++-------- src/transforms/filterSchema.ts | 124 ++++++++++++++++++++++++++ src/transforms/index.ts | 1 + 3 files changed, 183 insertions(+), 35 deletions(-) create mode 100644 src/transforms/filterSchema.ts diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 480eeef192b..58a04c4c72a 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -16,11 +16,10 @@ import { import mergeSchemas from '../stitching/mergeSchemas'; import { transformSchema, - FilterRootFields, + filterSchema, RenameTypes, RenameRootFields, RenameObjectFields, - FilterObjectFields, TransformObjectFields, } from '../transforms'; import { @@ -88,33 +87,33 @@ describe('merge schemas through transforms', () => { bookingSchemaExecConfig = await remoteBookingSchema; // namespace and strip schemas - const transformedPropertySchema = transformSchema(propertySchema, [ - new FilterRootFields( - (operation: string, rootField: string) => - 'Query.properties' === `${operation}.${rootField}`, - ), - new RenameTypes((name: string) => `Properties_${name}`), - new RenameRootFields((operation: string, name: string) => `Properties_${name}`), - ]); - const transformedBookingSchema = transformSchema(bookingSchemaExecConfig, [ - new FilterRootFields( - (operation: string, rootField: string) => - 'Query.bookings' === `${operation}.${rootField}`, - ), - new RenameTypes((name: string) => `Bookings_${name}`), - new RenameRootFields((operation: string, name: string) => `Bookings_${name}`), - ]); - const transformedSubscriptionSchema = transformSchema(subscriptionSchema, [ - new FilterRootFields( - (operation: string, rootField: string) => + const transformedPropertySchema = filterSchema({ + schema: transformSchema(propertySchema, [ + new RenameTypes((name: string) => `Properties_${name}`), + new RenameRootFields((operation: string, name: string) => `Properties_${name}`), + ]), + rootFieldFilter: (operation: string, rootField: string) => + 'Query.Properties_properties' === `${operation}.${rootField}`, + }); + const transformedBookingSchema = filterSchema({ + schema: transformSchema(bookingSchemaExecConfig, [ + new RenameTypes((name: string) => `Bookings_${name}`), + new RenameRootFields((operation: string, name: string) => `Bookings_${name}`), + ]), + rootFieldFilter: (operation: string, rootField: string) => + 'Query.Bookings_bookings' === `${operation}.${rootField}` + }); + const transformedSubscriptionSchema = filterSchema({ + schema: transformSchema(subscriptionSchema, [ + new RenameTypes((name: string) => `Subscriptions_${name}`), + new RenameRootFields( + (operation: string, name: string) => `Subscriptions_${name}`), + ]), + rootFieldFilter: (operation: string, rootField: string) => // must include a Query type otherwise graphql will error - 'Query.notifications' === `${operation}.${rootField}` || - 'Subscription.notifications' === `${operation}.${rootField}`, - ), - new RenameTypes((name: string) => `Subscriptions_${name}`), - new RenameRootFields( - (operation: string, name: string) => `Subscriptions_${name}`), - ]); + 'Query.Subscriptions_notifications' === `${operation}.${rootField}` || + 'Subscription.Subscriptions_notifications' === `${operation}.${rootField}`, + }); mergedSchema = mergeSchemas({ schemas: [ @@ -380,13 +379,37 @@ describe('filter and rename object fields', () => { let transformedPropertySchema: GraphQLSchema; before(async () => { - transformedPropertySchema = transformSchema(propertySchema, [ - new RenameTypes((name: string) => `New_${name}`), - new FilterObjectFields((typeName: string, fieldName: string) => - (typeName !== 'NewProperty' || fieldName === 'id' || fieldName === 'name' || fieldName === 'location') - ), - new RenameObjectFields((typeName: string, fieldName: string) => (typeName === 'New_Property' ? `new_${fieldName}` : fieldName)) - ]); + transformedPropertySchema = filterSchema({ + schema: transformSchema(propertySchema, [ + new RenameTypes((name: string) => `New_${name}`), + new RenameObjectFields((typeName: string, fieldName: string) => (typeName === 'New_Property' ? `new_${fieldName}` : fieldName)) + ]), + rootFieldFilter: (operation: string, fieldName: string) => + 'Query.propertyById' === `${operation}.${fieldName}`, + fieldFilter: (typeName: string, fieldName: string) => + (typeName === 'New_Property' || fieldName === 'name'), + typeFilter: (typeName: string) => + (typeName === 'New_Property' || typeName === 'New_Location') + }); + }); + + it('should filter', () => { + expect(printSchema(transformedPropertySchema)).to.equal(`type New_Location { + name: String! +} + +type New_Property { + new_id: ID! + new_name: String! + new_location: New_Location + new_error: String +} + +type Query { + propertyById(id: ID!): New_Property +} +` + ); }); it('should work', async () => { diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts new file mode 100644 index 00000000000..aef5b0ce730 --- /dev/null +++ b/src/transforms/filterSchema.ts @@ -0,0 +1,124 @@ +import { + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLInterfaceType, + GraphQLObjectType, + GraphQLScalarType, + GraphQLUnionType, +} from 'graphql'; +import { GraphQLSchemaWithTransforms } from '../Interfaces'; +import { visitSchema, VisitSchemaKind } from './visitSchema'; +import { fieldToFieldConfig, createResolveType } from '../stitching/schemaRecreation'; +import isEmptyObject from '../isEmptyObject'; + +export type RootFieldFilter = ( + operation: 'Query' | 'Mutation' | 'Subscription', + rootFieldName: string +) => boolean; + +export type FieldFilter = ( + typeName: string, + rootFieldName: string +) => boolean; + +export default function filterSchema({ + schema, + rootFieldFilter = () => true, + typeFilter = () => true, + fieldFilter = () => true, +}: { + schema: GraphQLSchemaWithTransforms; + rootFieldFilter?: RootFieldFilter; + typeFilter?: (typeName: string) => boolean; + fieldFilter?: (typeName: string, fieldName: string) => boolean; +}): GraphQLSchemaWithTransforms { + const filteredSchema: GraphQLSchemaWithTransforms = visitSchema(schema, { + [VisitSchemaKind.QUERY]: (type: GraphQLObjectType) => { + return rootFieldFilter ? filterRootFields(type, 'Query', rootFieldFilter) : undefined; + }, + [VisitSchemaKind.MUTATION]: (type: GraphQLObjectType) => { + return rootFieldFilter ? filterRootFields(type, 'Mutation', rootFieldFilter) : undefined; + }, + [VisitSchemaKind.SUBSCRIPTION]: (type: GraphQLObjectType) => { + return rootFieldFilter ? filterRootFields(type, 'Subscription', rootFieldFilter) : undefined; + }, + [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => { + return (!typeFilter || typeFilter(type.name)) ? + (filterObjectFields ? + filterObjectFields(type, fieldFilter) : + undefined) : + null; + }, + [VisitSchemaKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) => { + return (!typeFilter || typeFilter(type.name)) ? undefined : null; + }, + [VisitSchemaKind.UNION_TYPE]: (type: GraphQLUnionType) => { + return (!typeFilter || typeFilter(type.name)) ? undefined : null; + }, + [VisitSchemaKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) => { + return (!typeFilter || typeFilter(type.name)) ? undefined : null; + }, + [VisitSchemaKind.ENUM_TYPE]: (type: GraphQLEnumType) => { + return (!typeFilter || typeFilter(type.name)) ? undefined : null; + }, + [VisitSchemaKind.SCALAR_TYPE]: (type: GraphQLScalarType) => { + return (!typeFilter || typeFilter(type.name)) ? undefined : null; + }, + }); + + filteredSchema.transforms = schema.transforms; + + return filteredSchema; +} + +function filterRootFields( + type: GraphQLObjectType, + operation: 'Query' | 'Mutation' | 'Subscription', + rootFieldFilter: RootFieldFilter, +): GraphQLObjectType { + const resolveType = createResolveType((_, t) => t); + const fields = type.getFields(); + const newFields = {}; + Object.keys(fields).forEach(fieldName => { + if (rootFieldFilter(operation, fieldName)) { + newFields[fieldName] = fieldToFieldConfig(fields[fieldName], resolveType, true); + } + }); + if (isEmptyObject(newFields)) { + return null; + } else { + return new GraphQLObjectType({ + name: type.name, + description: type.description, + astNode: type.astNode, + fields: newFields, + }); + } +} + +function filterObjectFields( + type: GraphQLObjectType, + fieldFilter: FieldFilter, +): GraphQLObjectType { + const resolveType = createResolveType((_, t) => t); + const fields = type.getFields(); + const interfaces = type.getInterfaces(); + const newFields = {}; + Object.keys(fields).forEach(fieldName => { + if (fieldFilter(type.name, fieldName)) { + newFields[fieldName] = fieldToFieldConfig(fields[fieldName], resolveType, true); + } + }); + if (isEmptyObject(newFields)) { + return null; + } else { + return new GraphQLObjectType({ + name: type.name, + description: type.description, + astNode: type.astNode, + isTypeOf: type.isTypeOf, + fields: newFields, + interfaces: () => interfaces.map(iface => resolveType(iface)), + }); + } +} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index f597770dcd8..d08b48adfd1 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -1,6 +1,7 @@ import { Transform } from './transforms'; export { Transform }; +export { default as filterSchema } from './filterSchema'; export { default as transformSchema } from './transformSchema'; export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; From 8c82178d8cd8e9b808e1f6fb9fa1e4cbeb7e0cb3 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 7 Aug 2019 10:42:16 -0400 Subject: [PATCH 032/250] feat(transforms): map object fields to new structures Provides building blocks to allow transforming object types by wrapping a field with a new object type or by extracting subfields from a field of a certain object type into the parent: = Adds new ExtendSchema transformer to extend the wrapping schema within transformSchema with new fields. = Adds new functions that create resolvers that can wrap, extract, and rename merged fields, for use within the ExtendSchema transformer = Adds new MapFields transformer to allow transformation of field nodes within a request by object type and field name. --- src/Interfaces.ts | 1 + src/generate/addResolveFunctionsToSchema.ts | 12 +- src/stitching/defaultMergedResolver.ts | 7 +- src/test/testAlternateMergeSchemas.ts | 344 +++++++++++++++++++- src/transforms/AddDefaultResolver.ts | 61 ++++ src/transforms/ExtendSchema.ts | 40 +++ src/transforms/MapFields.ts | 111 +++++++ src/transforms/TransformObjectFields.ts | 85 +++-- src/transforms/collectFields.ts | 44 +++ src/transforms/index.ts | 17 +- src/transforms/mergeResolvers.ts | 44 +++ 11 files changed, 723 insertions(+), 43 deletions(-) create mode 100644 src/transforms/AddDefaultResolver.ts create mode 100644 src/transforms/ExtendSchema.ts create mode 100644 src/transforms/MapFields.ts create mode 100644 src/transforms/collectFields.ts create mode 100644 src/transforms/mergeResolvers.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 7e92e29a214..cb4c21f2786 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -31,6 +31,7 @@ export interface IResolverValidationOptions { export interface IAddResolveFunctionsToSchemaOptions { schema: GraphQLSchema; resolvers: IResolvers; + defaultFieldResolver?: IFieldResolver; resolverValidationOptions?: IResolverValidationOptions; inheritResolversFromInterfaces?: boolean; } diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts index 0cb6f729cec..a1434094434 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolveFunctionsToSchema.ts @@ -18,7 +18,7 @@ import { } from '../Interfaces'; import { applySchemaTransforms } from '../transforms/transforms'; import { checkForResolveTypeResolver, extendResolversFromInterfaces } from '.'; -import AddEnumAndScalarResolvers from '../transforms/AddEnumAndScalarResolvers'; +import { AddDefaultResolver, AddEnumAndScalarResolvers } from '../transforms/index'; function addResolveFunctionsToSchema( options: IAddResolveFunctionsToSchemaOptions | GraphQLSchema, @@ -39,6 +39,7 @@ function addResolveFunctionsToSchema( const { schema, resolvers: inputResolvers, + defaultFieldResolver, resolverValidationOptions = {}, inheritResolversFromInterfaces = false, } = options; @@ -153,12 +154,13 @@ function addResolveFunctionsToSchema( 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. - // also parse all defaultValues in all input fields to use internal values for enums/scalars const updatedSchema = applySchemaTransforms(schema, [ + // 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. + // also parse all defaultValues in all input fields to use internal values for enums/scalars new AddEnumAndScalarResolvers(enumValueMap, scalarTypeMap), + new AddDefaultResolver(defaultFieldResolver), ]); return updatedSchema; diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index 010d01979c0..0c14aea5cd4 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,4 +1,4 @@ -import { GraphQLFieldResolver } from 'graphql'; +import { GraphQLFieldResolver, defaultFieldResolver } from 'graphql'; import { getErrorsFromParent } from './errors'; import { handleResult } from './checkResultAndHandleErrors'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; @@ -18,10 +18,7 @@ const defaultMergedResolver: GraphQLFieldResolver = (parent, args, con // check to see if parent is not a proxied result, i.e. if parent resolver was manually overwritten // See https://github.com/apollographql/graphql-tools/issues/967 if (!Array.isArray(errors)) { - if (typeof parent[info.fieldName] === 'function') { - return parent[info.fieldName](parent, args, context, info); - } - return parent[info.fieldName]; + return defaultFieldResolver(parent, args, context, info); } return handleResult(info, parent[responseKey], errors); diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 58a04c4c72a..26fa9bef437 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -11,9 +11,9 @@ import { GraphQLNamedType, GraphQLScalarType, FieldNode, - printSchema + printSchema, + ExecutableDefinitionNode, } from 'graphql'; -import mergeSchemas from '../stitching/mergeSchemas'; import { transformSchema, filterSchema, @@ -21,6 +21,12 @@ import { RenameRootFields, RenameObjectFields, TransformObjectFields, + ExtendSchema, + wrapField, + extractField, + renameField, + MapFields, + collectFields, } from '../transforms'; import { propertySchema, @@ -32,7 +38,12 @@ import { import { forAwaitEach } from 'iterall'; import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import { delegateToSchema, delegateToRemoteSchema } from '../stitching'; +import { + delegateToSchema, + delegateToRemoteSchema, + defaultMergedResolver, + mergeSchemas, +} from '../stitching'; import { SchemaExecutionConfig } from '../Interfaces'; let linkSchema = ` @@ -447,6 +458,333 @@ type Query { }); }); +describe('ExtendSchema transform', () => { + let transformedPropertySchema: GraphQLSchema; + + before(async () => { + transformedPropertySchema = transformSchema(propertySchema, [ + new ExtendSchema({ + typeDefs: ` + extend type Property { + locationName: String + wrap: Wrap + } + + type Wrap { + id: ID + name: String + } + `, + defaultFieldResolver: defaultMergedResolver, + }), + ]); + }); + + it('should work', () => { + /* tslint:disable:max-line-length */ + expect(printSchema(transformedPropertySchema)).to.equal(`type Address { + street: String + city: String + state: String + zip: String +} + +"""Simple fake datetime""" +scalar DateTime + +input InputWithDefault { + test: String = "Foo" +} + +""" +The \`JSON\` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). +""" +scalar JSON + +type Location { + name: String! +} + +type Property { + id: ID! + name: String! + location: Location + address: Address + error: String + locationName: String + wrap: Wrap +} + +type Query { + propertyById(id: ID!): Property + properties(limit: Int = null): [Property!] + contextTest(key: String!): String + dateTimeTest: DateTime + jsonTest(input: JSON = null): JSON + interfaceTest(kind: TestInterfaceKind = null): TestInterface + unionTest(output: String = null): TestUnion + errorTest: String + errorTestNonNull: String! + relay: Query! + defaultInputTest(input: InputWithDefault!): String +} + +type TestImpl1 implements TestInterface { + kind: TestInterfaceKind + testString: String + foo: String +} + +type TestImpl2 implements TestInterface { + kind: TestInterfaceKind + testString: String + bar: String +} + +interface TestInterface { + kind: TestInterfaceKind + testString: String +} + +enum TestInterfaceKind { + ONE + TWO +} + +union TestUnion = TestImpl1 | UnionImpl + +type UnionImpl { + someField: String +} + +type Wrap { + id: ID + name: String +} +` + /* tslint:enable:max-line-length */ + ); + }); +}); + +describe('extract object field example', () => { + let transformedPropertySchema: GraphQLSchema; + + before(async () => { + transformedPropertySchema = transformSchema(propertySchema, [ + new ExtendSchema({ + typeDefs: ` + extend type Property { + locationName: String + } + `, + resolvers: { + Property: { + locationName: wrapField('location', 'name'), + }, + }, + }), + new MapFields({ + 'Property': { + 'locationName': () => { + return (parse(` + { + location { + name + } + } + `).definitions[0] as ExecutableDefinitionNode).selectionSet.selections[0]; + }, + }, + }), + ]); + }); + + it('should work to extract a field', async () => { + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + id + test1: locationName + test2: locationName + name + } + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + expect(result).to.deep.equal({ + data: { + propertyById: { + id: 'p1', + test1: 'Helsinki', + test2: 'Helsinki', + name: 'Super great hotel', + }, + }, + }); + }); +}); + +describe('wrap object field example', () => { + let transformedPropertySchema: GraphQLSchema; + + before(async () => { + transformedPropertySchema = transformSchema(propertySchema, [ + new ExtendSchema({ + typeDefs: ` + extend type Property { + wrap: Wrap + } + + type Wrap { + id: ID + name: String + } + `, + resolvers: { + Property: { + wrap: (parent, args, context, info) => ({ + id: extractField('id')(parent, args, context, info), + name: extractField('name')(parent, args, context, info), + }), + }, + }, + }), + new MapFields({ + 'Property': { + 'wrap': (fieldNode, fragments) => collectFields(fieldNode.selectionSet, fragments), + }, + }), + ]); + }); + + it('should work to wrap a field even with aliases', async () => { + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + test1: wrap { + ...W1 + } + test2: wrap { + ...W2 + } + } + } + fragment W1 on Wrap { + one: id + } + fragment W2 on Wrap { + two: name + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + expect(result).to.deep.equal({ + data: { + propertyById: { + test1: { + one: 'p1', + }, + test2: { + two: 'Super great hotel', + }, + }, + }, + }); + }); +}); + +describe('rename field while preserving errors', () => { + let transformedPropertySchema: GraphQLSchema; + + before(async () => { + transformedPropertySchema = transformSchema(propertySchema, [ + new ExtendSchema({ + typeDefs: ` + extend type Property { + new_error: String + } + `, + resolvers: { + Property: { + new_error: renameField('error'), + }, + }, + }), + new MapFields({ + 'Property': { + 'new_error': () => { + return (parse(` + { + error + } + `).definitions[0] as ExecutableDefinitionNode).selectionSet.selections[0]; + }, + }, + }), + ]); + }); + + it('should work to rename an error field even with aliases', async () => { + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + new_error + } + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + expect(result).to.deep.equal({ + data: { + propertyById: { + new_error: null, + }, + }, + errors: [ + { + extensions: { + code: 'SOME_CUSTOM_CODE', + }, + locations: [ + { + column: 17, + line: 3, + }, + ], + message: 'Property.error error', + path: [ + 'propertyById', + 'new_error', + ], + } + ], + }); + }); +}); + describe('interface resolver inheritance', () => { const testSchemaWithInterfaceResolvers = ` interface Node { diff --git a/src/transforms/AddDefaultResolver.ts b/src/transforms/AddDefaultResolver.ts new file mode 100644 index 00000000000..a75866be8d0 --- /dev/null +++ b/src/transforms/AddDefaultResolver.ts @@ -0,0 +1,61 @@ +import { + GraphQLSchema, + GraphQLFieldResolver, + GraphQLObjectType, + GraphQLFieldConfigMap +} from 'graphql'; +import { Transform } from './transforms'; +import { visitSchema, VisitSchemaKind } from './visitSchema'; +import { createResolveType, argsToFieldConfigArgumentMap } from '../stitching/schemaRecreation'; + +export default class AddDefaultResolver implements Transform { + private defaultFieldResolver: GraphQLFieldResolver; + + constructor(defaultFieldResolver: GraphQLFieldResolver) { + this.defaultFieldResolver = defaultFieldResolver; + } + + public transformSchema(schema: GraphQLSchema): GraphQLSchema { + const { defaultFieldResolver } = this; + + if (!defaultFieldResolver) { + return schema; + } + + const resolveType = createResolveType((name, type) => type); + const transformedSchema = visitSchema(schema, { + [VisitSchemaKind.OBJECT_TYPE](type: GraphQLObjectType) { + const fields = type.getFields(); + const interfaces = type.getInterfaces(); + + const newFields: GraphQLFieldConfigMap = {}; + Object.keys(fields).forEach(name => { + const field = fields[name]; + const resolvedType = resolveType(field.type); + if (resolvedType !== null) { + newFields[name] = { + type: resolvedType, + args: argsToFieldConfigArgumentMap(field.args, resolveType), + resolve: field.resolve ? field.resolve : defaultFieldResolver, + subscribe: field.subscribe, + description: field.description, + deprecationReason: field.deprecationReason, + astNode: field.astNode, + }; + } + }); + + return new GraphQLObjectType({ + name: type.name, + description: type.description, + astNode: type.astNode, + isTypeOf: type.isTypeOf, + fields: () => newFields, + interfaces: () => interfaces.map(iface => resolveType(iface)), + }); + }, + }); + + return transformedSchema; + } +} diff --git a/src/transforms/ExtendSchema.ts b/src/transforms/ExtendSchema.ts new file mode 100644 index 00000000000..a99d741796b --- /dev/null +++ b/src/transforms/ExtendSchema.ts @@ -0,0 +1,40 @@ +/* tslint:disable:no-unused-expression */ + +import { GraphQLSchema, extendSchema, parse, } from 'graphql'; +import { IFieldResolver, IResolvers } from '../Interfaces'; +import { Transform } from './transforms'; +import { addResolveFunctionsToSchema } from '../generate'; + +export default class ExtendSchema implements Transform { + private typeDefs: string; + private resolvers: IResolvers; + private defaultFieldResolver: IFieldResolver; + + constructor({ + typeDefs, + resolvers = {}, + defaultFieldResolver, + }: { + typeDefs?: string; + resolvers?: IResolvers; + defaultFieldResolver?: IFieldResolver, + }) { + this.typeDefs = typeDefs; + this.resolvers = resolvers, + this.defaultFieldResolver = defaultFieldResolver; + } + + public transformSchema(schema: GraphQLSchema): GraphQLSchema { + let newSchema: GraphQLSchema; + + if (this.typeDefs) { + newSchema = extendSchema(schema, parse(this.typeDefs)); + } + + return addResolveFunctionsToSchema({ + schema: newSchema || schema, + resolvers: this.resolvers, + defaultFieldResolver: this.defaultFieldResolver, + }); + } +} diff --git a/src/transforms/MapFields.ts b/src/transforms/MapFields.ts new file mode 100644 index 00000000000..5f858e96448 --- /dev/null +++ b/src/transforms/MapFields.ts @@ -0,0 +1,111 @@ +import { + GraphQLSchema, + GraphQLType, + DocumentNode, + FieldNode, + TypeInfo, + visit, + visitWithTypeInfo, + Kind, + SelectionSetNode, + SelectionNode, + FragmentDefinitionNode +} from 'graphql'; +import { Request } from '../Interfaces'; +import { Transform } from './transforms'; + +export type FieldNodeTransformer = ( + fieldNode: FieldNode, + fragments: Record +) => SelectionNode | Array; + +export type FieldNodeTransformerMap = { + [typeName: string]: { + [fieldName: string]: FieldNodeTransformer + } +}; + +export default class MapFields implements Transform { + private schema: GraphQLSchema; + private fieldNodeTransformerMap: FieldNodeTransformerMap; + + constructor( + fieldNodeTransformerMap: FieldNodeTransformerMap, + ) { + this.fieldNodeTransformerMap = fieldNodeTransformerMap; + } + + public transformSchema(schema: GraphQLSchema): GraphQLSchema { + this.schema = schema; + return schema; + } + + public transformRequest(originalRequest: Request): Request { + const fragments = {}; + originalRequest.document.definitions.filter( + def => def.kind === Kind.FRAGMENT_DEFINITION + ).forEach(def => { + fragments[(def as FragmentDefinitionNode).name.value] = def; + }); + const document = transformDocument( + originalRequest.document, + this.schema, + this.fieldNodeTransformerMap, + fragments + ); + return { + ...originalRequest, + document + }; + } +} + +function transformDocument( + document: DocumentNode, + schema: GraphQLSchema, + fieldNodeTransformerMap: FieldNodeTransformerMap, + fragments: Record = {}, +): DocumentNode { + const typeInfo = new TypeInfo(schema); + const newDocument: DocumentNode = visit( + document, + visitWithTypeInfo(typeInfo, { + [Kind.SELECTION_SET](node: SelectionSetNode): SelectionSetNode { + const parentType: GraphQLType = typeInfo.getParentType(); + if (parentType) { + const parentTypeName = parentType.name; + let newSelections: Array = []; + + node.selections.forEach(selection => { + if (selection.kind === Kind.FIELD) { + const fieldName = selection.name.value; + + const fieldNodeTransformer = + fieldNodeTransformerMap[parentTypeName] && fieldNodeTransformerMap[parentTypeName][fieldName]; + + const transformedSelection = fieldNodeTransformer + ? fieldNodeTransformer(selection, fragments) + : selection; + + if (Array.isArray(transformedSelection)) { + newSelections = newSelections.concat(transformedSelection); + } else if (transformedSelection.kind === Kind.FIELD) { + newSelections.push(transformedSelection); + } else { + newSelections.push(selection); + } + } else { + newSelections.push(selection); + } + }); + + return { + ...node, + selections: newSelections, + }; + } + } + }) + ); + return newDocument; +} diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index 08dbc0af9fc..6d9700b1d26 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -10,7 +10,10 @@ import { TypeInfo, visit, visitWithTypeInfo, - Kind + Kind, + SelectionSetNode, + SelectionNode, + FragmentDefinitionNode } from 'graphql'; import isEmptyObject from '../isEmptyObject'; import { Request } from '../Interfaces'; @@ -21,14 +24,15 @@ import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecrea export type ObjectFieldTransformer = ( typeName: string, fieldName: string, - field: GraphQLField + field: GraphQLField, ) => GraphQLFieldConfig | { name: string; field: GraphQLFieldConfig } | null | undefined; export type FieldNodeTransformer = ( typeName: string, fieldName: string, - fieldNode: FieldNode -) => FieldNode; + fieldNode: FieldNode, + fragments: Record +) => SelectionNode | Array; type FieldMapping = { [typeName: string]: { @@ -42,7 +46,10 @@ export default class TransformObjectFields implements Transform { private schema: GraphQLSchema; private mapping: FieldMapping; - constructor(objectFieldTransformer: ObjectFieldTransformer, fieldNodeTransformer?: FieldNodeTransformer) { + constructor( + objectFieldTransformer: ObjectFieldTransformer, + fieldNodeTransformer?: FieldNodeTransformer, + ) { this.objectFieldTransformer = objectFieldTransformer; this.fieldNodeTransformer = fieldNodeTransformer; this.mapping = {}; @@ -61,7 +68,18 @@ export default class TransformObjectFields implements Transform { } public transformRequest(originalRequest: Request): Request { - const document = this.reverseMapping(originalRequest.document, this.mapping, this.fieldNodeTransformer); + const fragments = {}; + originalRequest.document.definitions.filter( + def => def.kind === Kind.FRAGMENT_DEFINITION + ).forEach(def => { + fragments[(def as FragmentDefinitionNode).name.value] = def; + }); + const document = this.transformDocument( + originalRequest.document, + this.mapping, + this.fieldNodeTransformer, + fragments + ); return { ...originalRequest, document @@ -128,36 +146,55 @@ export default class TransformObjectFields implements Transform { } } - private reverseMapping( + private transformDocument( document: DocumentNode, mapping: FieldMapping, - fieldNodeTransformer?: FieldNodeTransformer + fieldNodeTransformer?: FieldNodeTransformer, + fragments: Record = {}, ): DocumentNode { const typeInfo = new TypeInfo(this.schema); const newDocument: DocumentNode = visit( document, visitWithTypeInfo(typeInfo, { - [Kind.FIELD](node: FieldNode): FieldNode | null | undefined { + [Kind.SELECTION_SET](node: SelectionSetNode): SelectionSetNode { const parentType: GraphQLType = typeInfo.getParentType(); if (parentType) { const parentTypeName = parentType.name; - const newName = node.name.value; - const transformedNode = fieldNodeTransformer - ? fieldNodeTransformer(parentTypeName, newName, node) - : node; - let transformedName = transformedNode.name.value; - if (mapping[parentTypeName]) { - const originalName = mapping[parentTypeName][newName]; - if (originalName) { - transformedName = originalName; + let newSelections: Array = []; + + node.selections.forEach(selection => { + if (selection.kind === Kind.FIELD) { + const newName = selection.name.value; + + const transformedSelection = fieldNodeTransformer + ? fieldNodeTransformer(parentTypeName, newName, selection, fragments) + : selection; + + if (Array.isArray(transformedSelection)) { + newSelections = newSelections.concat(transformedSelection); + } else if (transformedSelection.kind === Kind.FIELD) { + let originalName; + if (mapping[parentTypeName]) { + originalName = mapping[parentTypeName][newName]; + } + newSelections.push({ + ...transformedSelection, + name: { + ...transformedSelection.name, + value: originalName || transformedSelection.name.value + } + }); + } else { + newSelections.push(selection); + } + } else { + newSelections.push(selection); } - } + }); + return { - ...transformedNode, - name: { - ...node.name, - value: transformedName - } + ...node, + selections: newSelections, }; } } diff --git a/src/transforms/collectFields.ts b/src/transforms/collectFields.ts new file mode 100644 index 00000000000..9a6bd7fe584 --- /dev/null +++ b/src/transforms/collectFields.ts @@ -0,0 +1,44 @@ +import { + FieldNode, + FragmentDefinitionNode, + Kind, + SelectionSetNode, +} from 'graphql'; + +export function collectFields( + selectionSet: SelectionSetNode, + fragments: Record, + fields: Array = [], + visitedFragmentNames = {} +): Array { + selectionSet.selections.forEach(selection => { + switch (selection.kind) { + case Kind.FIELD: + fields.push(selection); + break; + case Kind.INLINE_FRAGMENT: + collectFields( + selection.selectionSet, + fragments, + fields, + visitedFragmentNames + ); + break; + case Kind.FRAGMENT_SPREAD: + const fragmentName = selection.name.value; + if (!visitedFragmentNames[fragmentName]) { + collectFields( + fragments[fragmentName].selectionSet, + fragments, + fields, + visitedFragmentNames + ); + } + break; + default: // unreachable + break; + } + }); + + return fields; +} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index d08b48adfd1..08b6c0b0915 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -4,15 +4,15 @@ export { Transform }; export { default as filterSchema } from './filterSchema'; export { default as transformSchema } from './transformSchema'; +export { default as AddEnumAndScalarResolvers } from './AddEnumAndScalarResolvers'; +export { default as AddDefaultResolver } from './AddDefaultResolver'; + export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; -export { - default as CheckResultAndHandleErrors, -} from './CheckResultAndHandleErrors'; -export { - default as ReplaceFieldWithFragment, -} from './ReplaceFieldWithFragment'; +export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErrors'; +export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; export { default as FilterToSchema } from './FilterToSchema'; + export { default as RenameTypes } from './RenameTypes'; export { default as FilterTypes } from './FilterTypes'; export { default as TransformRootFields } from './TransformRootFields'; @@ -25,3 +25,8 @@ export { default as ExpandAbstractTypes } from './ExpandAbstractTypes'; export { default as ExtractField } from './ExtractField'; export { default as WrapQuery } from './WrapQuery'; export { default as TransformQuery } from './TransformQuery'; + +export { default as ExtendSchema } from './ExtendSchema'; +export { wrapField, extractField, renameField } from './mergeResolvers'; +export { default as MapFields } from './MapFields'; +export { collectFields } from './collectFields'; diff --git a/src/transforms/mergeResolvers.ts b/src/transforms/mergeResolvers.ts new file mode 100644 index 00000000000..ca6f2e1ef6f --- /dev/null +++ b/src/transforms/mergeResolvers.ts @@ -0,0 +1,44 @@ +import { IFieldResolver } from '../Interfaces'; +import { defaultMergedResolver } from '../stitching'; +import { FieldNode } from 'graphql'; +import { collectFields } from './collectFields'; + +export function wrapField(wrapper: string, fieldName: string): IFieldResolver { + return (parent, args, context, info) => + defaultMergedResolver(parent[wrapper], args, context, { ...info, fieldName }); +} + +export function extractField(fieldName: string): IFieldResolver { + return (parent, args, context, info) => { + const newFieldNodes: Array = []; + + let noMatchingFields = true; + info.fieldNodes.forEach(fieldNode => { + collectFields(fieldNode.selectionSet, info.fragments).forEach(selection => { + if (selection.name.value === fieldName) { + newFieldNodes.push(selection); + noMatchingFields = false; + } + }); + }); + + if (noMatchingFields) { + return null; + } + + return defaultMergedResolver(parent, args, context, { + ...info, + fieldName, + fieldNodes: newFieldNodes, + }); + }; +} + +export function renameField(fieldName: string): IFieldResolver { + return (parent, args, context, info) => { + return defaultMergedResolver(parent, args, context, { + ...info, + fieldName, + }); + }; +} From 1c25e1224c5ee9f254ddb8a01928f62799aea355 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Fri, 16 Aug 2019 15:57:39 -0400 Subject: [PATCH 033/250] fix(stitching): fixes error mapping with ExtendSchema transform. Also, refactors! = uses createMergedResolver instead of individual wrapFields, extractFields, and renameFields functions. These functionss likely should be deprecated or at least renamed, as their function (creating specialized resolvers for merging) is not clear from current names. createMergedResolver works with multiple layers of wrapping, extracting, or even a combination thereof. = exports new extractFields function for use within a fieldNodeTransformerMap when wrapping fields. = allows specification of the fieldNodeTransformerMap directly on ExtendSchema transform so that separate MapFields transform not required. Under the hood, the ExtendSchema transform calls its own MapFields transform. = fixes file structure; as most of the recent new functionality is stitching functionality rather than transform functionality, even though it relies on the new ExtendSchema and MapFields transforms. --- .../collectFields.ts | 0 src/stitching/createMergedResolver.ts | 64 ++++++++ src/stitching/extractFields.ts | 34 ++++ src/stitching/index.ts | 18 ++- src/test/testAlternateMergeSchemas.ts | 153 ++++++++++++------ src/transforms/ExtendSchema.ts | 15 +- src/transforms/index.ts | 2 - src/transforms/mergeResolvers.ts | 44 ----- 8 files changed, 230 insertions(+), 100 deletions(-) rename src/{transforms => stitching}/collectFields.ts (100%) create mode 100644 src/stitching/createMergedResolver.ts create mode 100644 src/stitching/extractFields.ts delete mode 100644 src/transforms/mergeResolvers.ts diff --git a/src/transforms/collectFields.ts b/src/stitching/collectFields.ts similarity index 100% rename from src/transforms/collectFields.ts rename to src/stitching/collectFields.ts diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts new file mode 100644 index 00000000000..5a38b648098 --- /dev/null +++ b/src/stitching/createMergedResolver.ts @@ -0,0 +1,64 @@ +import { IFieldResolver } from '../Interfaces'; +import { defaultMergedResolver } from '../stitching'; +import { GraphQLObjectType } from 'graphql'; +import { extractOneLevelOfFields } from './extractFields'; + +export function wrapField(wrapper: string, fieldName: string): IFieldResolver { + return createMergedResolver({ fromPath: [wrapper, fieldName] }); +} + +export function extractField(fieldName: string): IFieldResolver { + return createMergedResolver({ toPath: [fieldName] }); +} + +export function renameField(fieldName: string): IFieldResolver { + return createMergedResolver({ fromPath: [fieldName] }); +} + +export function createMergedResolver({ + fromPath = [], + toPath = [], +}: { + toPath?: Array; + fromPath?: Array; +}): IFieldResolver { + return (parent, args, context, info) => { + + let fieldNodes = info.fieldNodes; + let returnType = info.returnType; + let parentType = info.parentType; + let path = info.path; + + toPath.forEach(pathSegment => { + fieldNodes = extractOneLevelOfFields(fieldNodes, pathSegment, info.fragments); + parentType = returnType as GraphQLObjectType; + returnType = (returnType as GraphQLObjectType).getFields()[pathSegment].type; + path = { prev: path, key: pathSegment }; + }); + + if (!fieldNodes.length) { + return null; + } + + let fieldName; + + const fromPathLength = fromPath.length; + if (fromPathLength) { + parent = fromPath.slice(0, -1).reduce((p, pathSegment) => p[pathSegment], parent); + fieldName = fromPath[fromPathLength - 1]; + } + + if (!fieldName) { + fieldName = toPath[toPath.length - 1]; + } + + return defaultMergedResolver(parent, args, context, { + ...info, + fieldName, + fieldNodes, + returnType, + parentType, + path, + }); + }; +} diff --git a/src/stitching/extractFields.ts b/src/stitching/extractFields.ts new file mode 100644 index 00000000000..c70443bc715 --- /dev/null +++ b/src/stitching/extractFields.ts @@ -0,0 +1,34 @@ +import { FieldNode, FragmentDefinitionNode } from 'graphql'; +import { collectFields } from './collectFields'; + +export function extractFields({ + fieldNode, + path = [], + fragments = {}, +}: { + fieldNode: FieldNode; + path?: Array; + fragments?: Record; +}) { + const fieldNodes = collectFields(fieldNode.selectionSet, fragments); + return path.length ? path.reduce( + (acc, pathSegment) => extractOneLevelOfFields(acc, pathSegment, fragments), + fieldNodes, + ) : fieldNodes; +} + +export function extractOneLevelOfFields( + fieldNodes: ReadonlyArray, + fieldName: string, + fragments: Record, +) { + const newFieldNodes: Array = []; + fieldNodes.forEach(fieldNode => { + collectFields(fieldNode.selectionSet, fragments).forEach(selection => { + if (selection.name.value === fieldName) { + newFieldNodes.push(selection); + } + }); + }); + return newFieldNodes; +} diff --git a/src/stitching/index.ts b/src/stitching/index.ts index 2896edfb94b..b124e850341 100644 --- a/src/stitching/index.ts +++ b/src/stitching/index.ts @@ -4,15 +4,29 @@ import mergeSchemas from './mergeSchemas'; import delegateToSchema from './delegateToSchema'; import delegateToRemoteSchema from './delegateToRemoteSchema'; import defaultMergedResolver from './defaultMergedResolver'; +import { wrapField, extractField, renameField, createMergedResolver } from './createMergedResolver'; +import { extractFields } from './extractFields'; +import { collectFields } from './collectFields'; + export { makeRemoteExecutableSchema, introspectSchema, mergeSchemas, - // Those are currently undocumented and not part of official API, + + // These are currently undocumented and not part of official API, // but exposed for the community use delegateToSchema, delegateToRemoteSchema, + defaultCreateRemoteResolver, defaultMergedResolver, - defaultCreateRemoteResolver + createMergedResolver, + collectFields, + extractFields, + + // TBD: deprecate in favor of createMergedResolver? + // OR: fix naming to clarify that these functions return resolvers? + wrapField, + extractField, + renameField, }; diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 26fa9bef437..05392dccd74 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -22,11 +22,6 @@ import { RenameObjectFields, TransformObjectFields, ExtendSchema, - wrapField, - extractField, - renameField, - MapFields, - collectFields, } from '../transforms'; import { propertySchema, @@ -43,9 +38,18 @@ import { delegateToRemoteSchema, defaultMergedResolver, mergeSchemas, + wrapField, + extractField, + renameField, + createMergedResolver, + extractFields, } from '../stitching'; import { SchemaExecutionConfig } from '../Interfaces'; +const toFieldNode = + (raw: string) => + ((parse(`{ ${raw} }`).definitions[0] as ExecutableDefinitionNode).selectionSet.selections[0]); + let linkSchema = ` """ A new type linking the Property type. @@ -567,7 +571,7 @@ type Wrap { }); }); -describe('extract object field example', () => { +describe('schema transformation with extraction of nested fields', () => { let transformedPropertySchema: GraphQLSchema; before(async () => { @@ -576,24 +580,20 @@ describe('extract object field example', () => { typeDefs: ` extend type Property { locationName: String + locationName2: String } `, resolvers: { Property: { - locationName: wrapField('location', 'name'), + locationName: createMergedResolver({ fromPath: ['location', 'name'] }), + //deprecated wrapField shorthand + locationName2: wrapField('location', 'name'), }, }, - }), - new MapFields({ - 'Property': { - 'locationName': () => { - return (parse(` - { - location { - name - } - } - `).definitions[0] as ExecutableDefinitionNode).selectionSet.selections[0]; + fieldNodeTransformerMap: { + 'Property': { + 'locationName': () => toFieldNode('location { name }'), + 'locationName2': () => toFieldNode('location { name }'), }, }, }), @@ -608,7 +608,7 @@ describe('extract object field example', () => { propertyById(id: $pid) { id test1: locationName - test2: locationName + test2: locationName2 name } } @@ -633,7 +633,7 @@ describe('extract object field example', () => { }); }); -describe('wrap object field example', () => { +describe('schema transformation with wrapping of object fields', () => { let transformedPropertySchema: GraphQLSchema; before(async () => { @@ -641,49 +641,70 @@ describe('wrap object field example', () => { new ExtendSchema({ typeDefs: ` extend type Property { - wrap: Wrap + outerWrap: OuterWrap + singleWrap: InnerWrap } - type Wrap { + type OuterWrap { + innerWrap: InnerWrap + } + + type InnerWrap { id: ID name: String } `, resolvers: { Property: { - wrap: (parent, args, context, info) => ({ + outerWrap: (parent, args, context, info) => ({ + innerWrap: { + id: createMergedResolver({ toPath: ['innerWrap', 'id'] })(parent, args, context, info), + name: createMergedResolver({ toPath: ['innerWrap', 'name'] })(parent, args, context, info), + }, + }), + //deprecated extractField shorthand + singleWrap: (parent, args, context, info) => ({ id: extractField('id')(parent, args, context, info), name: extractField('name')(parent, args, context, info), }), }, }, - }), - new MapFields({ - 'Property': { - 'wrap': (fieldNode, fragments) => collectFields(fieldNode.selectionSet, fragments), + fieldNodeTransformerMap: { + 'Property': { + 'outerWrap': (fieldNode, fragments) => extractFields({ fieldNode, path: ['innerWrap'], fragments }), + 'singleWrap': (fieldNode, fragments) => extractFields({ fieldNode, fragments }), + }, }, }), ]); }); - it('should work to wrap a field even with aliases', async () => { + it('should work, even with aliases', async () => { const result = await graphql( transformedPropertySchema, ` query($pid: ID!) { propertyById(id: $pid) { - test1: wrap { - ...W1 + test1: outerWrap { + innerWrap { + ...W1 + } + } + test2: outerWrap { + innerWrap { + ...W2 + } } - test2: wrap { + singleWrap { + ...W1 ...W2 } } } - fragment W1 on Wrap { + fragment W1 on InnerWrap { one: id } - fragment W2 on Wrap { + fragment W2 on InnerWrap { two: name } `, @@ -698,18 +719,26 @@ describe('wrap object field example', () => { data: { propertyById: { test1: { - one: 'p1', + innerWrap: { + one: 'p1', + }, }, test2: { - two: 'Super great hotel', + innerWrap: { + two: 'Super great hotel', + }, }, + singleWrap: { + one: 'p1', + two: 'Super great hotel', + } }, }, }); }); }); -describe('rename field while preserving errors', () => { +describe('schema transformation with renaming of object fields', () => { let transformedPropertySchema: GraphQLSchema; before(async () => { @@ -718,35 +747,34 @@ describe('rename field while preserving errors', () => { typeDefs: ` extend type Property { new_error: String + new_error2: String } `, resolvers: { Property: { - new_error: renameField('error'), + new_error: createMergedResolver({ fromPath: ['error'] }), + //deprecated renameField shorthand + new_error2: renameField('error'), }, }, - }), - new MapFields({ - 'Property': { - 'new_error': () => { - return (parse(` - { - error - } - `).definitions[0] as ExecutableDefinitionNode).selectionSet.selections[0]; + fieldNodeTransformerMap: { + 'Property': { + 'new_error': () => toFieldNode('error'), + 'new_error2': () => toFieldNode('error'), }, }, }), ]); }); - it('should work to rename an error field even with aliases', async () => { + it('should work, even with aliases, and should preserve errors', async () => { const result = await graphql( transformedPropertySchema, ` query($pid: ID!) { propertyById(id: $pid) { new_error + new_error2 } } `, @@ -761,6 +789,7 @@ describe('rename field while preserving errors', () => { data: { propertyById: { new_error: null, + new_error2: null, }, }, errors: [ @@ -770,8 +799,12 @@ describe('rename field while preserving errors', () => { }, locations: [ { - column: 17, - line: 3, + column: 3, + line: 1, + }, + { + column: 3, + line: 1, }, ], message: 'Property.error error', @@ -779,7 +812,27 @@ describe('rename field while preserving errors', () => { 'propertyById', 'new_error', ], - } + }, + { + extensions: { + code: 'SOME_CUSTOM_CODE', + }, + locations: [ + { + column: 3, + line: 1, + }, + { + column: 3, + line: 1, + }, + ], + message: 'Property.error error', + path: [ + 'propertyById', + 'new_error2', + ], + }, ], }); }); diff --git a/src/transforms/ExtendSchema.ts b/src/transforms/ExtendSchema.ts index a99d741796b..f848d15091a 100644 --- a/src/transforms/ExtendSchema.ts +++ b/src/transforms/ExtendSchema.ts @@ -1,30 +1,37 @@ /* tslint:disable:no-unused-expression */ import { GraphQLSchema, extendSchema, parse, } from 'graphql'; -import { IFieldResolver, IResolvers } from '../Interfaces'; +import { IFieldResolver, IResolvers, Request } from '../Interfaces'; import { Transform } from './transforms'; import { addResolveFunctionsToSchema } from '../generate'; +import { default as MapFields, FieldNodeTransformerMap } from './MapFields'; export default class ExtendSchema implements Transform { private typeDefs: string; private resolvers: IResolvers; private defaultFieldResolver: IFieldResolver; + private transformer: Transform; constructor({ typeDefs, resolvers = {}, defaultFieldResolver, + fieldNodeTransformerMap, }: { typeDefs?: string; resolvers?: IResolvers; - defaultFieldResolver?: IFieldResolver, + defaultFieldResolver?: IFieldResolver; + fieldNodeTransformerMap?: FieldNodeTransformerMap; }) { this.typeDefs = typeDefs; this.resolvers = resolvers, this.defaultFieldResolver = defaultFieldResolver; + this.transformer = new MapFields(fieldNodeTransformerMap); } public transformSchema(schema: GraphQLSchema): GraphQLSchema { + this.transformer.transformSchema(schema); + let newSchema: GraphQLSchema; if (this.typeDefs) { @@ -37,4 +44,8 @@ export default class ExtendSchema implements Transform { defaultFieldResolver: this.defaultFieldResolver, }); } + + public transformRequest(originalRequest: Request): Request { + return this.transformer.transformRequest(originalRequest); + } } diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 08b6c0b0915..86b51b8fb67 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -27,6 +27,4 @@ export { default as WrapQuery } from './WrapQuery'; export { default as TransformQuery } from './TransformQuery'; export { default as ExtendSchema } from './ExtendSchema'; -export { wrapField, extractField, renameField } from './mergeResolvers'; export { default as MapFields } from './MapFields'; -export { collectFields } from './collectFields'; diff --git a/src/transforms/mergeResolvers.ts b/src/transforms/mergeResolvers.ts deleted file mode 100644 index ca6f2e1ef6f..00000000000 --- a/src/transforms/mergeResolvers.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { IFieldResolver } from '../Interfaces'; -import { defaultMergedResolver } from '../stitching'; -import { FieldNode } from 'graphql'; -import { collectFields } from './collectFields'; - -export function wrapField(wrapper: string, fieldName: string): IFieldResolver { - return (parent, args, context, info) => - defaultMergedResolver(parent[wrapper], args, context, { ...info, fieldName }); -} - -export function extractField(fieldName: string): IFieldResolver { - return (parent, args, context, info) => { - const newFieldNodes: Array = []; - - let noMatchingFields = true; - info.fieldNodes.forEach(fieldNode => { - collectFields(fieldNode.selectionSet, info.fragments).forEach(selection => { - if (selection.name.value === fieldName) { - newFieldNodes.push(selection); - noMatchingFields = false; - } - }); - }); - - if (noMatchingFields) { - return null; - } - - return defaultMergedResolver(parent, args, context, { - ...info, - fieldName, - fieldNodes: newFieldNodes, - }); - }; -} - -export function renameField(fieldName: string): IFieldResolver { - return (parent, args, context, info) => { - return defaultMergedResolver(parent, args, context, { - ...info, - fieldName, - }); - }; -} From 9af1ec5c987fee8bfda74b65666d8a3f3c030aae Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 17 Aug 2019 23:30:01 -0400 Subject: [PATCH 034/250] fix(stitching): map errors along schema transformation path when extracting fields --- src/stitching/createMergedResolver.ts | 36 +++++++-- src/test/testAlternateMergeSchemas.ts | 111 ++++++++++++++++++++++---- 2 files changed, 126 insertions(+), 21 deletions(-) diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 5a38b648098..f0062cf9498 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -1,6 +1,12 @@ +import { GraphQLObjectType, getNamedType, responsePathAsArray } from 'graphql'; import { IFieldResolver } from '../Interfaces'; -import { defaultMergedResolver } from '../stitching'; -import { GraphQLObjectType } from 'graphql'; +import { + relocatedError, + combineErrors, + getErrorsFromParent, + annotateWithChildrenErrors, +} from './errors'; +import defaultMergedResolver from './defaultMergedResolver'; import { extractOneLevelOfFields } from './extractFields'; export function wrapField(wrapper: string, fieldName: string): IFieldResolver { @@ -31,8 +37,8 @@ export function createMergedResolver({ toPath.forEach(pathSegment => { fieldNodes = extractOneLevelOfFields(fieldNodes, pathSegment, info.fragments); - parentType = returnType as GraphQLObjectType; - returnType = (returnType as GraphQLObjectType).getFields()[pathSegment].type; + parentType = getNamedType(returnType) as GraphQLObjectType; + returnType = (parentType as GraphQLObjectType).getFields()[pathSegment].type; path = { prev: path, key: pathSegment }; }); @@ -44,7 +50,27 @@ export function createMergedResolver({ const fromPathLength = fromPath.length; if (fromPathLength) { - parent = fromPath.slice(0, -1).reduce((p, pathSegment) => p[pathSegment], parent); + const fromParentPathLength = fromPathLength - 1; + + for (let i = 0; i < fromParentPathLength; i++) { + const responseKey = fromPath[i]; + const errors = getErrorsFromParent(parent, responseKey); + const result = parent[responseKey]; + if (result == null) { + if (errors.length) { + throw relocatedError( + combineErrors(errors), + fieldNodes, + responsePathAsArray(path) + ); + } else { + return null; + } + } + annotateWithChildrenErrors(result, errors); + parent = result; + } + fieldName = fromPath[fromPathLength - 1]; } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 05392dccd74..641a50a5926 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -12,7 +12,7 @@ import { GraphQLScalarType, FieldNode, printSchema, - ExecutableDefinitionNode, + Kind, } from 'graphql'; import { transformSchema, @@ -46,9 +46,35 @@ import { } from '../stitching'; import { SchemaExecutionConfig } from '../Interfaces'; -const toFieldNode = - (raw: string) => - ((parse(`{ ${raw} }`).definitions[0] as ExecutableDefinitionNode).selectionSet.selections[0]); +function renameFieldNode(fieldNode: FieldNode, name: string): FieldNode { + return { + ...fieldNode, + name: { + ...fieldNode.name, + value: name, + } + }; +} + +function wrapFieldNode(fieldNode: FieldNode, path: Array): FieldNode { + let newFieldNode = fieldNode; + path.forEach(fieldName => { + newFieldNode = { + kind: Kind.FIELD, + name: { + kind: Kind.NAME, + value: fieldName, + }, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [ + fieldNode, + ] + } + }; + }); + return newFieldNode; +} let linkSchema = ` """ @@ -581,6 +607,7 @@ describe('schema transformation with extraction of nested fields', () => { extend type Property { locationName: String locationName2: String + pseudoWrappedError: String } `, resolvers: { @@ -588,18 +615,70 @@ describe('schema transformation with extraction of nested fields', () => { locationName: createMergedResolver({ fromPath: ['location', 'name'] }), //deprecated wrapField shorthand locationName2: wrapField('location', 'name'), + pseudoWrappedError: createMergedResolver({ fromPath: ['error', 'name'] }), }, }, fieldNodeTransformerMap: { 'Property': { - 'locationName': () => toFieldNode('location { name }'), - 'locationName2': () => toFieldNode('location { name }'), + 'locationName': + fieldNode => wrapFieldNode(renameFieldNode(fieldNode, 'name'), ['location']), + 'locationName2': + fieldNode => wrapFieldNode(renameFieldNode(fieldNode, 'name'), ['location']), + 'pseudoWrappedError': fieldNode => renameFieldNode(fieldNode, 'error'), }, }, }), ]); }); + it('should work to extract a field', async () => { + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + test1: locationName + test2: locationName2 + pseudoWrappedError + } + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + expect(result).to.deep.equal({ + data: { + propertyById: { + test1: 'Helsinki', + test2: 'Helsinki', + pseudoWrappedError: null, + }, + }, + errors: [ + { + extensions: { + code: 'SOME_CUSTOM_CODE', + }, + locations: [ + { + column: 13, + line: 6, + }, + ], + message: 'Property.error error', + path: [ + 'propertyById', + 'pseudoWrappedError', + ], + }, + ] + }); + }); + it('should work to extract a field', async () => { const result = await graphql( transformedPropertySchema, @@ -759,8 +838,8 @@ describe('schema transformation with renaming of object fields', () => { }, fieldNodeTransformerMap: { 'Property': { - 'new_error': () => toFieldNode('error'), - 'new_error2': () => toFieldNode('error'), + 'new_error': fieldNode => renameFieldNode(fieldNode, 'error'), + 'new_error2': fieldNode => renameFieldNode(fieldNode, 'error'), }, }, }), @@ -799,12 +878,12 @@ describe('schema transformation with renaming of object fields', () => { }, locations: [ { - column: 3, - line: 1, + column: 13, + line: 4, }, { - column: 3, - line: 1, + column: 13, + line: 5, }, ], message: 'Property.error error', @@ -819,12 +898,12 @@ describe('schema transformation with renaming of object fields', () => { }, locations: [ { - column: 3, - line: 1, + column: 13, + line: 4, }, { - column: 3, - line: 1, + column: 13, + line: 5, }, ], message: 'Property.error error', From d5e54ad2184b41f72d0a28b009257dbead7a44c0 Mon Sep 17 00:00:00 2001 From: Connor White Date: Sun, 18 Aug 2019 21:10:47 -0500 Subject: [PATCH 035/250] fix(stitching): input fields without default value Input fields without default values should not have default values of null after transformation -- they should still have no default values. Closes #15. --- src/test/testAlternateMergeSchemas.ts | 8 ++++---- src/transformInputValue.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 641a50a5926..6b728b5297a 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -547,12 +547,12 @@ type Property { type Query { propertyById(id: ID!): Property - properties(limit: Int = null): [Property!] + properties(limit: Int): [Property!] contextTest(key: String!): String dateTimeTest: DateTime - jsonTest(input: JSON = null): JSON - interfaceTest(kind: TestInterfaceKind = null): TestInterface - unionTest(output: String = null): TestUnion + jsonTest(input: JSON): JSON + interfaceTest(kind: TestInterfaceKind): TestInterface + unionTest(output: String): TestUnion errorTest: String errorTestNonNull: String! relay: Query! diff --git a/src/transformInputValue.ts b/src/transformInputValue.ts index f293d625c8e..26ea921753e 100644 --- a/src/transformInputValue.ts +++ b/src/transformInputValue.ts @@ -11,7 +11,7 @@ type InputValueTransformer = (type: GraphQLEnumType | GraphQLScalarType, origina export function transformInputValue(type: GraphQLInputType, value: any, transformer: InputValueTransformer) { if (value == null) { - return null; + return value; } const nullableType = getNullableType(type); From a88f667bc7656dd48b6d37b1f3c800dac0d307bc Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 26 Aug 2019 02:29:11 -0400 Subject: [PATCH 036/250] fix(transforms): support custom scalar input variables on root fields when transforming. Fixes #18. --- src/stitching/resolvers.ts | 2 +- src/test/testTransforms.ts | 66 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 784387fb33b..3363ca6d30c 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -111,7 +111,7 @@ function createProxyingResolver( schema: schema as GraphQLSchema, operation, fieldName, - args: {}, + args, context, info, transforms, diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 8378959c996..0dba395c698 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -4,6 +4,7 @@ import { expect } from 'chai'; import { GraphQLSchema, GraphQLNamedType, + GraphQLScalarType, graphql, Kind, SelectionSetNode, @@ -28,6 +29,71 @@ import { } from '../transforms'; describe('transforms', () => { + describe('base transform function', () => { + let schema: GraphQLSchema; + let scalarTest = ` + scalar TestScalar + type TestingScalar { + value: TestScalar + } + + type Query { + testingScalar(input: TestScalar): TestingScalar + } + `; + + let scalarSchema: GraphQLSchema; + + scalarSchema = makeExecutableSchema({ + typeDefs: scalarTest, + resolvers: { + TestScalar: new GraphQLScalarType({ + name: 'TestScalar', + description: undefined, + serialize: value => (value as string).slice(1), + parseValue: value => `_${value}`, + parseLiteral: (ast: any) => `_${ast.value}`, + }), + Query: { + testingScalar(parent, args) { + return { + value: args.input[0] === '_' ? args.input : null + }; + }, + }, + }, + }); + + before(() => { + schema = transformSchema(scalarSchema, []); + }); + it('should work', async () => { + const result = await graphql( + schema, + ` + query($input: TestScalar) { + testingScalar(input: $input) { + value + } + } + `, + {}, + {}, + { + input: 'test', + }, + ); + + expect(result).to.deep.equal({ + data: { + testingScalar: { + value: 'test', + }, + }, + }); + }); + }); + describe('rename type', () => { let schema: GraphQLSchema; before(() => { From 339c569ea20d2eb87e49612ccd38e4f5246549cb Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 26 Aug 2019 21:02:53 -0400 Subject: [PATCH 037/250] chore(deps): upgrade dependencies. Required changes to comply with new type definitions as graphql changes from flow to typescript. --- package.json | 25 ++++++++++++------------- src/schemaVisitor.ts | 1 + src/test/testDirectives.ts | 13 +++++++++---- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index c9cf5dbc6b6..77f089b5612 100644 --- a/package.json +++ b/package.json @@ -53,34 +53,33 @@ "apollo-utilities": "^1.3.2", "deprecated-decorator": "^0.1.6", "iterall": "^1.2.2", - "uuid": "^3.3.2" + "uuid": "^3.3.3" }, "peerDependencies": { - "graphql": "^14.4.0" + "graphql": "^14.0.0" }, "devDependencies": { - "@types/chai": "4.1.7", + "@types/chai": "4.2.0", "@types/dateformat": "^3.0.0", - "@types/graphql": "14.2.2", "@types/mocha": "^5.2.7", - "@types/node": "^12.0.10", - "@types/uuid": "^3.4.4", + "@types/node": "^12.7.2", + "@types/uuid": "^3.4.5", "@types/zen-observable": "^0.8.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "dateformat": "^3.0.3", "express": "^4.17.1", - "graphql": "^14.0.2", + "graphql": "^14.5.3", "graphql-subscriptions": "^1.1.0", "graphql-type-json": "^0.3.0", "istanbul": "^0.4.5", - "mocha": "^6.1.4", + "mocha": "^6.2.0", "prettier": "^1.18.2", "remap-istanbul": "0.13.0", - "rimraf": "^2.6.3", - "source-map-support": "^0.5.12", - "standard-version": "^6.0.1", - "tslint": "^5.18.0", - "typescript": "3.5.2" + "rimraf": "^3.0.0", + "source-map-support": "^0.5.13", + "standard-version": "^7.0.0", + "tslint": "^5.19.0", + "typescript": "3.5.3" } } diff --git a/src/schemaVisitor.ts b/src/schemaVisitor.ts index 5769e132b0c..4293c9bd84b 100644 --- a/src/schemaVisitor.ts +++ b/src/schemaVisitor.ts @@ -33,6 +33,7 @@ export type VisitableSchemaType = | GraphQLNamedType | GraphQLScalarType | GraphQLField + | GraphQLInputField | GraphQLArgument | GraphQLUnionType | GraphQLEnumType diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 2cbd00c2ce6..144cf311e05 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -597,8 +597,12 @@ describe('@directives', () => { field.args.push({ name: 'format', - type: GraphQLString - } as any); + type: GraphQLString, + description: undefined, + defaultValue: undefined, + extensions: undefined, + astNode: undefined, + }); field.type = GraphQLString; field.resolve = async function (source, { format, ...args }, context, info) { @@ -1030,8 +1034,9 @@ describe('@directives', () => { hash.update(String(object[fieldName])); }); return hash.digest('hex'); - } - } as any; + }, + extensions: undefined, + }; } } }, From bde8e539ab9ddd59148b63ce9616dcc78c2e7f56 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 1 Sep 2019 02:58:42 -0400 Subject: [PATCH 038/250] chore(style): do not import from index --- src/generate/addResolveFunctionsToSchema.ts | 8 +++++--- src/generate/assertResolveFunctionsPresent.ts | 4 ++-- src/generate/attachConnectorsToContext.ts | 2 +- src/generate/buildSchemaFromTypeDefinitions.ts | 8 +++----- src/generate/checkForResolveTypeResolver.ts | 2 +- src/generate/concatenateTypeDefs.ts | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts index a1434094434..6313fed0527 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolveFunctionsToSchema.ts @@ -1,5 +1,3 @@ -import { SchemaError } from '.'; - import { GraphQLField, GraphQLEnumType, @@ -16,8 +14,12 @@ import { IResolverValidationOptions, IAddResolveFunctionsToSchemaOptions, } from '../Interfaces'; + +import SchemaError from './SchemaError'; +import checkForResolveTypeResolver from './checkForResolveTypeResolver'; +import extendResolversFromInterfaces from './extendResolversFromInterfaces'; + import { applySchemaTransforms } from '../transforms/transforms'; -import { checkForResolveTypeResolver, extendResolversFromInterfaces } from '.'; import { AddDefaultResolver, AddEnumAndScalarResolvers } from '../transforms/index'; function addResolveFunctionsToSchema( diff --git a/src/generate/assertResolveFunctionsPresent.ts b/src/generate/assertResolveFunctionsPresent.ts index 013a2187790..9d170f237a6 100644 --- a/src/generate/assertResolveFunctionsPresent.ts +++ b/src/generate/assertResolveFunctionsPresent.ts @@ -5,8 +5,8 @@ import { GraphQLScalarType, } from 'graphql'; import { IResolverValidationOptions } from '../Interfaces'; - -import { forEachField, SchemaError } from '.'; +import forEachField from './forEachField'; +import SchemaError from './SchemaError'; function assertResolveFunctionsPresent( schema: GraphQLSchema, diff --git a/src/generate/attachConnectorsToContext.ts b/src/generate/attachConnectorsToContext.ts index a6274cff3c6..56a6ce74249 100644 --- a/src/generate/attachConnectorsToContext.ts +++ b/src/generate/attachConnectorsToContext.ts @@ -4,7 +4,7 @@ import { deprecated } from 'deprecated-decorator'; import { IConnectors, IConnector, IConnectorCls } from '../Interfaces'; -import { addSchemaLevelResolveFunction } from '.'; +import addSchemaLevelResolveFunction from './addSchemaLevelResolveFunction'; // takes a GraphQL-JS schema and an object of connectors, then attaches // the connectors to the context by wrapping each query or mutation resolve diff --git a/src/generate/buildSchemaFromTypeDefinitions.ts b/src/generate/buildSchemaFromTypeDefinitions.ts index a122a993365..8e852f85c0e 100644 --- a/src/generate/buildSchemaFromTypeDefinitions.ts +++ b/src/generate/buildSchemaFromTypeDefinitions.ts @@ -7,12 +7,10 @@ import { } from 'graphql'; import { ITypeDefinitions, GraphQLParseOptions } from '../Interfaces'; -import { - extractExtensionDefinitions, - concatenateTypeDefs, - SchemaError, -} from '.'; import filterExtensionDefinitions from './filterExtensionDefinitions'; +import extractExtensionDefinitions from './extractExtensionDefinitions'; +import concatenateTypeDefs from './concatenateTypeDefs'; +import SchemaError from './SchemaError'; function buildSchemaFromTypeDefinitions( typeDefinitions: ITypeDefinitions, diff --git a/src/generate/checkForResolveTypeResolver.ts b/src/generate/checkForResolveTypeResolver.ts index 139cbde349d..8b50f99455c 100644 --- a/src/generate/checkForResolveTypeResolver.ts +++ b/src/generate/checkForResolveTypeResolver.ts @@ -1,6 +1,6 @@ import { GraphQLInterfaceType, GraphQLUnionType, GraphQLSchema } 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( diff --git a/src/generate/concatenateTypeDefs.ts b/src/generate/concatenateTypeDefs.ts index 8b5dd5222a7..b80f765443d 100644 --- a/src/generate/concatenateTypeDefs.ts +++ b/src/generate/concatenateTypeDefs.ts @@ -1,7 +1,7 @@ import { print, DocumentNode, ASTNode } from 'graphql'; import { ITypedef } from '../Interfaces'; -import { SchemaError } from '.'; +import SchemaError from './SchemaError'; function concatenateTypeDefs( typeDefinitionsAry: ITypedef[], From 80d196c7b6700a34ca2853c449279ddfbba406d4 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 1 Sep 2019 11:20:56 -0400 Subject: [PATCH 039/250] fix(generate): addResolversToSchema should update enums and scalars of original schema. Closes #19. --- src/Interfaces.ts | 6 ++ src/generate/addResolveFunctionsToSchema.ts | 86 ++++++++++++++------- src/generate/forEachDefaultValue.ts | 31 ++++++++ src/makeExecutableSchema.ts | 2 +- src/stitching/mergeSchemas.ts | 2 +- src/test/testAlternateMergeSchemas.ts | 2 - src/transforms/transformSchema.ts | 2 +- 7 files changed, 98 insertions(+), 33 deletions(-) create mode 100644 src/generate/forEachDefaultValue.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index cb4c21f2786..a30a87d3d90 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -2,6 +2,7 @@ import { GraphQLSchema, GraphQLField, ExecutionResult, + GraphQLInputType, GraphQLType, GraphQLNamedType, GraphQLFieldResolver, @@ -193,6 +194,11 @@ export type IFieldIteratorFn = ( fieldName: string, ) => void; +export type IDefaultValueIteratorFn = ( + type: GraphQLInputType, + value: any, +) => void; + export type NextResolverFn = () => Promise; export type DirectiveResolverFn = ( next: NextResolverFn, diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts index 6313fed0527..db5ca7d4d24 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolveFunctionsToSchema.ts @@ -18,9 +18,11 @@ import { import SchemaError from './SchemaError'; import checkForResolveTypeResolver from './checkForResolveTypeResolver'; import extendResolversFromInterfaces from './extendResolversFromInterfaces'; +import forEachField from './forEachField'; +import forEachDefaultValue from './forEachDefaultValue'; -import { applySchemaTransforms } from '../transforms/transforms'; -import { AddDefaultResolver, AddEnumAndScalarResolvers } from '../transforms/index'; +import { serializeInputValue, parseInputValue } from '../transformInputValue'; +import { healSchema } from '../schemaVisitor'; function addResolveFunctionsToSchema( options: IAddResolveFunctionsToSchemaOptions | GraphQLSchema, @@ -55,11 +57,9 @@ function addResolveFunctionsToSchema( ? 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); - // Used to store custom scalar implementations. - const scalarTypeMap = Object.create(null); + // serialize all default values prior to addition of scalar/enum types. + // default values will be parsed via new defs after addition of the new types. + forEachDefaultValue(schema, serializeInputValue); Object.keys(resolvers).forEach(typeName => { const resolverValue = resolvers[typeName]; @@ -85,8 +85,20 @@ function addResolveFunctionsToSchema( } if (type instanceof GraphQLScalarType) { - scalarTypeMap[type.name] = resolverValue; + Object.keys(resolverValue).forEach(fieldName => { + // Below is necessary as legacy code for scalar type specification allowed + // hardcoding within the resolver an object with fields '__serialize', + // '__parse', and '__parseLiteral', see examples in testMocking.ts. + if (fieldName.startsWith('__')) { + type[fieldName.substring(2)] = resolverValue[fieldName]; + } else { + type[fieldName] = resolverValue[fieldName]; + } + }); } else if (type instanceof GraphQLEnumType) { + // 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) { @@ -96,17 +108,31 @@ function addResolveFunctionsToSchema( `${typeName}.${fieldName} was defined in resolvers, but enum is not in schema`, ); } + }); + + 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, + }; + }); - // 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]; + const typeMap = schema.getTypeMap(); + // healSchema called later to update fields to new type + typeMap[type.name] = new GraphQLEnumType({ + name: type.name, + description: type.description, + astNode: type.astNode, + values: newValues, }); } else { // object type @@ -141,7 +167,7 @@ function addResolveFunctionsToSchema( const fieldResolve = resolverValue[fieldName]; if (typeof fieldResolve === 'function') { // for convenience. Allows shorter syntax in resolver definition file - setFieldProperties(field, { resolve: fieldResolve }); + field.resolve = fieldResolve; } else { if (typeof fieldResolve !== 'object') { throw new SchemaError( @@ -156,16 +182,20 @@ function addResolveFunctionsToSchema( checkForResolveTypeResolver(schema, requireResolversForResolveType); - const updatedSchema = applySchemaTransforms(schema, [ - // 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. - // also parse all defaultValues in all input fields to use internal values for enums/scalars - new AddEnumAndScalarResolvers(enumValueMap, scalarTypeMap), - new AddDefaultResolver(defaultFieldResolver), - ]); + // schema may have new enum types that require healing + healSchema(schema); + // reparse all default values with new parsing functions. + forEachDefaultValue(schema, parseInputValue); + + if (defaultFieldResolver) { + forEachField(schema, field => { + if (!field.resolve) { + field.resolve = defaultFieldResolver; + } + }); + } - return updatedSchema; + return schema; } function getFieldsForType(type: GraphQLType): GraphQLFieldMap { diff --git a/src/generate/forEachDefaultValue.ts b/src/generate/forEachDefaultValue.ts new file mode 100644 index 00000000000..ef7655f7f87 --- /dev/null +++ b/src/generate/forEachDefaultValue.ts @@ -0,0 +1,31 @@ +import { getNamedType, GraphQLInputObjectType, GraphQLSchema, GraphQLObjectType } from 'graphql'; +import { IDefaultValueIteratorFn } from '../Interfaces'; + +function forEachDefaultValue(schema: GraphQLSchema, fn: IDefaultValueIteratorFn): void { + + const typeMap = schema.getTypeMap(); + Object.keys(typeMap).forEach(typeName => { + const type = typeMap[typeName]; + + if (!getNamedType(type).name.startsWith('__')) { + if (type instanceof GraphQLObjectType) { + const fields = type.getFields(); + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + + field.args.forEach(arg => { + arg.defaultValue = fn(arg.type, arg.defaultValue); + }); + }); + } else if (type instanceof GraphQLInputObjectType) { + const fields = type.getFields(); + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + field.defaultValue = fn(field.type, field.defaultValue); + }); + } + } + }); +} + +export default forEachDefaultValue; diff --git a/src/makeExecutableSchema.ts b/src/makeExecutableSchema.ts index ab9f3debed3..7a844142aea 100644 --- a/src/makeExecutableSchema.ts +++ b/src/makeExecutableSchema.ts @@ -51,7 +51,7 @@ export function makeExecutableSchema({ let schema = buildSchemaFromTypeDefinitions(typeDefs, parseOptions); - schema = addResolveFunctionsToSchema({ + addResolveFunctionsToSchema({ schema, resolvers: resolverMap, resolverValidationOptions, diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 2f79312af27..43dbd1b5ea7 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -271,7 +271,7 @@ export default function mergeSchemas({ }); }); - mergedSchema = addResolveFunctionsToSchema({ + addResolveFunctionsToSchema({ schema: mergedSchema, resolvers: mergeDeep(generatedResolvers, resolvers), inheritResolversFromInterfaces diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 6b728b5297a..903395e16d9 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -36,7 +36,6 @@ import { makeExecutableSchema } from '../makeExecutableSchema'; import { delegateToSchema, delegateToRemoteSchema, - defaultMergedResolver, mergeSchemas, wrapField, extractField, @@ -505,7 +504,6 @@ describe('ExtendSchema transform', () => { name: String } `, - defaultFieldResolver: defaultMergedResolver, }), ]); }); diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index a6e70ca5d1a..b74e41973a8 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -26,7 +26,7 @@ export default function transformSchema( transforms, mapping, ); - schema = addResolveFunctionsToSchema({ + addResolveFunctionsToSchema({ schema, resolvers, resolverValidationOptions: { From e3b04d0f905eef14cdefc976af51cfae96656436 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 2 Sep 2019 13:37:16 -0400 Subject: [PATCH 040/250] fix(healSchema): healing schema requires reparsing default field values as types are updated. --- src/generate/addResolveFunctionsToSchema.ts | 31 ++++--- src/generate/forEachDefaultValue.ts | 31 ------- src/schemaVisitor.ts | 26 +++++- src/test/testSchemaGenerator.ts | 96 +++++++++++++++++++++ 4 files changed, 134 insertions(+), 50 deletions(-) delete mode 100644 src/generate/forEachDefaultValue.ts diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts index db5ca7d4d24..c4715dfa233 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolveFunctionsToSchema.ts @@ -19,9 +19,6 @@ import SchemaError from './SchemaError'; import checkForResolveTypeResolver from './checkForResolveTypeResolver'; import extendResolversFromInterfaces from './extendResolversFromInterfaces'; import forEachField from './forEachField'; -import forEachDefaultValue from './forEachDefaultValue'; - -import { serializeInputValue, parseInputValue } from '../transformInputValue'; import { healSchema } from '../schemaVisitor'; function addResolveFunctionsToSchema( @@ -57,9 +54,7 @@ function addResolveFunctionsToSchema( ? extendResolversFromInterfaces(schema, inputResolvers) : inputResolvers; - // serialize all default values prior to addition of scalar/enum types. - // default values will be parsed via new defs after addition of the new types. - forEachDefaultValue(schema, serializeInputValue); + const typeMap = schema.getTypeMap(); Object.keys(resolvers).forEach(typeName => { const resolverValue = resolvers[typeName]; @@ -85,16 +80,23 @@ function addResolveFunctionsToSchema( } if (type instanceof GraphQLScalarType) { + const config = type.toConfig(); + Object.keys(resolverValue).forEach(fieldName => { // Below is necessary as legacy code for scalar type specification allowed // hardcoding within the resolver an object with fields '__serialize', // '__parse', and '__parseLiteral', see examples in testMocking.ts. + // Luckily, the fields on GraphQLScalarType and GraphQLScalarTypeConfig + // are named the same. if (fieldName.startsWith('__')) { - type[fieldName.substring(2)] = resolverValue[fieldName]; + config[fieldName.substring(2)] = resolverValue[fieldName]; } else { - type[fieldName] = resolverValue[fieldName]; + config[fieldName] = resolverValue[fieldName]; } }); + + // healSchema called later to update all fields to new type + typeMap[type.name] = new GraphQLScalarType(config); } else if (type instanceof GraphQLEnumType) { // We've encountered an enum resolver that is being used to provide an // internal enum value. @@ -110,6 +112,8 @@ function addResolveFunctionsToSchema( } }); + const config = type.toConfig(); + const values = type.getValues(); const newValues = {}; values.forEach(value => { @@ -126,12 +130,9 @@ function addResolveFunctionsToSchema( }; }); - const typeMap = schema.getTypeMap(); - // healSchema called later to update fields to new type + // healSchema called later to update all fields to new type typeMap[type.name] = new GraphQLEnumType({ - name: type.name, - description: type.description, - astNode: type.astNode, + ...config, values: newValues, }); } else { @@ -182,10 +183,8 @@ function addResolveFunctionsToSchema( checkForResolveTypeResolver(schema, requireResolversForResolveType); - // schema may have new enum types that require healing + // schema may have new scalar or enum types that require healing healSchema(schema); - // reparse all default values with new parsing functions. - forEachDefaultValue(schema, parseInputValue); if (defaultFieldResolver) { forEachField(schema, field => { diff --git a/src/generate/forEachDefaultValue.ts b/src/generate/forEachDefaultValue.ts deleted file mode 100644 index ef7655f7f87..00000000000 --- a/src/generate/forEachDefaultValue.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { getNamedType, GraphQLInputObjectType, GraphQLSchema, GraphQLObjectType } from 'graphql'; -import { IDefaultValueIteratorFn } from '../Interfaces'; - -function forEachDefaultValue(schema: GraphQLSchema, fn: IDefaultValueIteratorFn): void { - - const typeMap = schema.getTypeMap(); - Object.keys(typeMap).forEach(typeName => { - const type = typeMap[typeName]; - - if (!getNamedType(type).name.startsWith('__')) { - if (type instanceof GraphQLObjectType) { - const fields = type.getFields(); - Object.keys(fields).forEach(fieldName => { - const field = fields[fieldName]; - - field.args.forEach(arg => { - arg.defaultValue = fn(arg.type, arg.defaultValue); - }); - }); - } else if (type instanceof GraphQLInputObjectType) { - const fields = type.getFields(); - Object.keys(fields).forEach(fieldName => { - const field = fields[fieldName]; - field.defaultValue = fn(field.type, field.defaultValue); - }); - } - } - }); -} - -export default forEachDefaultValue; diff --git a/src/schemaVisitor.ts b/src/schemaVisitor.ts index 4293c9bd84b..40bf7f536c0 100644 --- a/src/schemaVisitor.ts +++ b/src/schemaVisitor.ts @@ -19,11 +19,13 @@ import { GraphQLList, GraphQLNonNull, isNamedType, + getNamedType, } from 'graphql'; import { getArgumentValues, } from 'graphql/execution/values'; +import { serializeInputValue, parseInputValue } from './transformInputValue'; export type VisitableSchemaType = GraphQLSchema @@ -371,9 +373,7 @@ export function healSchema(schema: GraphQLSchema) { healFields(type); } else if (type instanceof GraphQLInputObjectType) { - each(type.getFields(), field => { - field.type = healType(field.type); - }); + healInputFields(type); } else if (type instanceof GraphQLScalarType) { // Nothing to do. @@ -394,12 +394,32 @@ export function healSchema(schema: GraphQLSchema) { field.type = healType(field.type); if (field.args) { each(field.args, arg => { + const originalType = arg.type; arg.type = healType(arg.type); + if (getNamedType(arg.type) !== getNamedType(originalType)) { + arg.defaultValue = parseInputValue( + arg.type, + serializeInputValue(originalType, arg.defaultValue) + ); + } }); } }); } + function healInputFields(type: GraphQLInputObjectType) { + each(type.getFields(), field => { + const originalType = field.type; + field.type = healType(field.type); + if (getNamedType(field.type) !== getNamedType(originalType)) { + field.defaultValue = parseInputValue( + field.type, + serializeInputValue(originalType, field.defaultValue) + ); + } +}); + } + function healType(type: T): T { // Unwrap the two known wrapper types if (type instanceof GraphQLList) { diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 9c03d75880e..c843a32e10a 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -36,6 +36,7 @@ import { } from '../Interfaces'; import 'mocha'; import { VisitSchemaKind, visitSchema } from '../transforms/visitSchema'; +import { addResolveFunctionsToSchema } from '../generate'; interface Bird { name: string; @@ -1176,6 +1177,101 @@ describe('generating schema from shorthand', () => { }); }); + describe('default value support', () => { + it('supports default field values', () => { + const shorthand = ` + enum Color { + RED + } + + schema { + query: Query + } + + type Query { + colorTest(color: Color = RED): String + } + `; + + const testQuery = `{ + red: colorTest + }`; + + const resolveFunctions = { + Color: { + RED: '#EA3232', + }, + Query: { + colorTest(root: any, args: { color: string }) { + return args.color; + }, + }, + }; + + const jsSchema = makeExecutableSchema({ + typeDefs: shorthand, + resolvers: resolveFunctions, + }); + + const resultPromise = graphql(jsSchema, testQuery); + return resultPromise.then(result => { + assert.equal(result.data['red'], resolveFunctions.Color.RED); + assert.equal(result.errors, undefined); + }); + }); + + it('supports changing default field values', () => { + const shorthand = ` + enum Color { + RED + } + + schema { + query: Query + } + + type Query { + colorTest(color: Color = RED): String + } + `; + + const testQuery = `{ + red: colorTest + }`; + + const resolveFunctions = { + Color: { + RED: '#EA3232', + }, + Query: { + colorTest(root: any, args: { color: string }) { + return args.color; + }, + }, + }; + + const jsSchema = makeExecutableSchema({ + typeDefs: shorthand, + resolvers: resolveFunctions, + }); + + addResolveFunctionsToSchema({ + schema: jsSchema, + resolvers: { + Color: { + RED: 'override', + }, + } + }); + + const resultPromise = graphql(jsSchema, testQuery); + return resultPromise.then(result => { + assert.equal(result.data['red'], 'override'); + assert.equal(result.errors, undefined); + }); + }); + }); + it('can set description and deprecation reason', () => { const shorthand = ` type BirdSpecies { From 71e24599791d81fa33c8db5f92c73536d602f5eb Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 2 Sep 2019 20:50:04 -0400 Subject: [PATCH 041/250] fix(healSchema): revert most recent change healSchema cannot automatically include reparsing of default values. Automatic reparsing assumes that the default value corresponds to the old type, but that may not always be the case, and when it is not the case, the default value will be lost if automatic reparsing is implemented as in 6.3.6. --- src/generate/addResolveFunctionsToSchema.ts | 8 +++++- src/generate/forEachDefaultValue.ts | 31 +++++++++++++++++++++ src/schemaVisitor.ts | 18 +----------- 3 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 src/generate/forEachDefaultValue.ts diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts index c4715dfa233..432c09b73ab 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolveFunctionsToSchema.ts @@ -19,6 +19,8 @@ import SchemaError from './SchemaError'; import checkForResolveTypeResolver from './checkForResolveTypeResolver'; import extendResolversFromInterfaces from './extendResolversFromInterfaces'; import forEachField from './forEachField'; +import forEachDefaultValue from './forEachDefaultValue'; +import { parseInputValue, serializeInputValue } from '../transformInputValue'; import { healSchema } from '../schemaVisitor'; function addResolveFunctionsToSchema( @@ -183,8 +185,12 @@ function addResolveFunctionsToSchema( checkForResolveTypeResolver(schema, requireResolversForResolveType); - // schema may have new scalar or enum types that require healing + // 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) { forEachField(schema, field => { diff --git a/src/generate/forEachDefaultValue.ts b/src/generate/forEachDefaultValue.ts new file mode 100644 index 00000000000..ef7655f7f87 --- /dev/null +++ b/src/generate/forEachDefaultValue.ts @@ -0,0 +1,31 @@ +import { getNamedType, GraphQLInputObjectType, GraphQLSchema, GraphQLObjectType } from 'graphql'; +import { IDefaultValueIteratorFn } from '../Interfaces'; + +function forEachDefaultValue(schema: GraphQLSchema, fn: IDefaultValueIteratorFn): void { + + const typeMap = schema.getTypeMap(); + Object.keys(typeMap).forEach(typeName => { + const type = typeMap[typeName]; + + if (!getNamedType(type).name.startsWith('__')) { + if (type instanceof GraphQLObjectType) { + const fields = type.getFields(); + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + + field.args.forEach(arg => { + arg.defaultValue = fn(arg.type, arg.defaultValue); + }); + }); + } else if (type instanceof GraphQLInputObjectType) { + const fields = type.getFields(); + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + field.defaultValue = fn(field.type, field.defaultValue); + }); + } + } + }); +} + +export default forEachDefaultValue; diff --git a/src/schemaVisitor.ts b/src/schemaVisitor.ts index 40bf7f536c0..fc89f878ec5 100644 --- a/src/schemaVisitor.ts +++ b/src/schemaVisitor.ts @@ -19,13 +19,11 @@ import { GraphQLList, GraphQLNonNull, isNamedType, - getNamedType, } from 'graphql'; import { getArgumentValues, } from 'graphql/execution/values'; -import { serializeInputValue, parseInputValue } from './transformInputValue'; export type VisitableSchemaType = GraphQLSchema @@ -394,14 +392,7 @@ export function healSchema(schema: GraphQLSchema) { field.type = healType(field.type); if (field.args) { each(field.args, arg => { - const originalType = arg.type; arg.type = healType(arg.type); - if (getNamedType(arg.type) !== getNamedType(originalType)) { - arg.defaultValue = parseInputValue( - arg.type, - serializeInputValue(originalType, arg.defaultValue) - ); - } }); } }); @@ -409,15 +400,8 @@ export function healSchema(schema: GraphQLSchema) { function healInputFields(type: GraphQLInputObjectType) { each(type.getFields(), field => { - const originalType = field.type; field.type = healType(field.type); - if (getNamedType(field.type) !== getNamedType(originalType)) { - field.defaultValue = parseInputValue( - field.type, - serializeInputValue(originalType, field.defaultValue) - ); - } -}); + }); } function healType(type: T): T { From 49ee5363c0738c92d8847f9a368d49888fa90ba5 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 3 Sep 2019 21:33:51 -0400 Subject: [PATCH 042/250] fix(transforms): VisitSchemaKind.TYPE should have greatest priority --- src/transforms/visitSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transforms/visitSchema.ts b/src/transforms/visitSchema.ts index 78f62fb4b95..0bb12702a19 100644 --- a/src/transforms/visitSchema.ts +++ b/src/transforms/visitSchema.ts @@ -94,7 +94,7 @@ function getTypeSpecifiers( ): Array { const specifiers = [VisitSchemaKind.TYPE]; if (type instanceof GraphQLObjectType) { - specifiers.unshift( + specifiers.push( VisitSchemaKind.COMPOSITE_TYPE, VisitSchemaKind.OBJECT_TYPE, ); From bd2363bcce4247644febbc62913b8fb9250740d8 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 8 Sep 2019 11:08:51 -0400 Subject: [PATCH 043/250] fix(visitSchema): new interfaces should not break schema. --- src/schemaVisitor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schemaVisitor.ts b/src/schemaVisitor.ts index fc89f878ec5..67a5cbfa793 100644 --- a/src/schemaVisitor.ts +++ b/src/schemaVisitor.ts @@ -365,7 +365,7 @@ export function healSchema(schema: GraphQLSchema) { } else if (type instanceof GraphQLObjectType) { healFields(type); - each(type.getInterfaces(), iface => heal(iface)); + updateEachKey(type.getInterfaces(), iface => healType(iface)); } else if (type instanceof GraphQLInterfaceType) { healFields(type); From ad4e23abeb6ea43da4f8d039a36328db077e5051 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 8 Sep 2019 11:29:27 -0400 Subject: [PATCH 044/250] refactor: create utils folder --- src/generate/addResolveFunctionsToSchema.ts | 2 +- src/makeExecutableSchema.ts | 2 +- src/stitching/makeRemoteExecutableSchema.ts | 2 +- src/stitching/mergeSchemas.ts | 2 +- src/stitching/schemaRecreation.ts | 4 ++-- src/transforms/AddArgumentsAsVariables.ts | 2 +- src/transforms/ExpandAbstractTypes.ts | 2 +- src/transforms/FilterToSchema.ts | 2 +- src/transforms/RenameTypes.ts | 2 +- src/transforms/TransformObjectFields.ts | 2 +- src/transforms/TransformRootFields.ts | 2 +- src/transforms/filterSchema.ts | 2 +- src/{ => utils}/implementsAbstractType.ts | 0 src/{ => utils}/isEmptyObject.ts | 0 src/{ => utils}/isSpecifiedScalarType.ts | 0 src/{ => utils}/mergeDeep.ts | 0 src/{ => utils}/transformInputValue.ts | 0 17 files changed, 13 insertions(+), 13 deletions(-) rename src/{ => utils}/implementsAbstractType.ts (100%) rename src/{ => utils}/isEmptyObject.ts (100%) rename src/{ => utils}/isSpecifiedScalarType.ts (100%) rename src/{ => utils}/mergeDeep.ts (100%) rename src/{ => utils}/transformInputValue.ts (100%) diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts index 432c09b73ab..164a061f7ce 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolveFunctionsToSchema.ts @@ -20,7 +20,7 @@ import checkForResolveTypeResolver from './checkForResolveTypeResolver'; import extendResolversFromInterfaces from './extendResolversFromInterfaces'; import forEachField from './forEachField'; import forEachDefaultValue from './forEachDefaultValue'; -import { parseInputValue, serializeInputValue } from '../transformInputValue'; +import { parseInputValue, serializeInputValue } from '../utils/transformInputValue'; import { healSchema } from '../schemaVisitor'; function addResolveFunctionsToSchema( diff --git a/src/makeExecutableSchema.ts b/src/makeExecutableSchema.ts index 7a844142aea..166e73ffb02 100644 --- a/src/makeExecutableSchema.ts +++ b/src/makeExecutableSchema.ts @@ -3,7 +3,7 @@ import { defaultFieldResolver, GraphQLSchema, GraphQLFieldResolver } from 'graph import { IExecutableSchemaDefinition, ILogger } from './Interfaces'; import { SchemaDirectiveVisitor } from './schemaVisitor'; -import mergeDeep from './mergeDeep'; +import mergeDeep from './utils/mergeDeep'; import { attachDirectiveResolvers, diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 53a1d7afa09..199acf2eb14 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -21,7 +21,7 @@ import { BuildSchemaOptions } from 'graphql'; import linkToFetcher, { execute } from './linkToFetcher'; -import isEmptyObject from '../isEmptyObject'; +import isEmptyObject from '../utils/isEmptyObject'; import { IResolvers, IResolverObject, Fetcher } from '../Interfaces'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { recreateType } from './schemaRecreation'; diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 43dbd1b5ea7..36eecb981e5 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -42,7 +42,7 @@ import { ExpandAbstractTypes, ReplaceFieldWithFragment, } from '../transforms'; -import mergeDeep from '../mergeDeep'; +import mergeDeep from '../utils/mergeDeep'; import { SchemaDirectiveVisitor } from '../schemaVisitor'; type MergeTypeCandidate = { diff --git a/src/stitching/schemaRecreation.ts b/src/stitching/schemaRecreation.ts index 414e37dd14d..a068b9babe6 100644 --- a/src/stitching/schemaRecreation.ts +++ b/src/stitching/schemaRecreation.ts @@ -32,7 +32,7 @@ import { GraphQLBoolean, GraphQLID, } from 'graphql'; -import isSpecifiedScalarType from '../isSpecifiedScalarType'; +import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; import { ResolveType } from '../Interfaces'; import resolveFromParentTypename from './resolveFromParentTypename'; import defaultMergedResolver from './defaultMergedResolver'; @@ -41,7 +41,7 @@ import { serializeInputValue, parseInputValue, parseInputValueLiteral -} from '../transformInputValue'; +} from '../utils/transformInputValue'; export function recreateType( type: GraphQLNamedType, diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index 61bdd442b27..d2defe1a9cf 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -17,7 +17,7 @@ import { } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; -import { serializeInputValue } from '../transformInputValue'; +import { serializeInputValue } from '../utils/transformInputValue'; export default class AddArgumentsAsVariablesTransform implements Transform { private schema: GraphQLSchema; diff --git a/src/transforms/ExpandAbstractTypes.ts b/src/transforms/ExpandAbstractTypes.ts index 41e8665e3da..ddc5aafeadc 100644 --- a/src/transforms/ExpandAbstractTypes.ts +++ b/src/transforms/ExpandAbstractTypes.ts @@ -13,7 +13,7 @@ import { visit, visitWithTypeInfo, } from 'graphql'; -import implementsAbstractType from '../implementsAbstractType'; +import implementsAbstractType from '../utils/implementsAbstractType'; import { Transform, Request } from '../Interfaces'; type TypeMapping = { [key: string]: Array }; diff --git a/src/transforms/FilterToSchema.ts b/src/transforms/FilterToSchema.ts index 4142e623d90..9c055239b55 100644 --- a/src/transforms/FilterToSchema.ts +++ b/src/transforms/FilterToSchema.ts @@ -22,7 +22,7 @@ import { visit, } from 'graphql'; import { Request } from '../Interfaces'; -import implementsAbstractType from '../implementsAbstractType'; +import implementsAbstractType from '../utils/implementsAbstractType'; import { Transform } from './transforms'; export default class FilterToSchema implements Transform { diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index 84b1d1dbfd6..ab2d5956d50 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -6,7 +6,7 @@ import { GraphQLNamedType, GraphQLScalarType, } from 'graphql'; -import isSpecifiedScalarType from '../isSpecifiedScalarType'; +import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; import { Request, Result } from '../Interfaces'; import { Transform } from '../transforms/transforms'; import { visitSchema, VisitSchemaKind } from '../transforms/visitSchema'; diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index 6d9700b1d26..5a54738ba8b 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -15,7 +15,7 @@ import { SelectionNode, FragmentDefinitionNode } from 'graphql'; -import isEmptyObject from '../isEmptyObject'; +import isEmptyObject from '../utils/isEmptyObject'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; import { visitSchema, VisitSchemaKind } from './visitSchema'; diff --git a/src/transforms/TransformRootFields.ts b/src/transforms/TransformRootFields.ts index cc47ef3adb4..92b46e29cc9 100644 --- a/src/transforms/TransformRootFields.ts +++ b/src/transforms/TransformRootFields.ts @@ -5,7 +5,7 @@ import { GraphQLField, GraphQLFieldConfig, } from 'graphql'; -import isEmptyObject from '../isEmptyObject'; +import isEmptyObject from '../utils/isEmptyObject'; import { Transform } from './transforms'; import { visitSchema, VisitSchemaKind } from './visitSchema'; import { diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index aef5b0ce730..ff554f847a3 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -9,7 +9,7 @@ import { import { GraphQLSchemaWithTransforms } from '../Interfaces'; import { visitSchema, VisitSchemaKind } from './visitSchema'; import { fieldToFieldConfig, createResolveType } from '../stitching/schemaRecreation'; -import isEmptyObject from '../isEmptyObject'; +import isEmptyObject from '../utils/isEmptyObject'; export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', diff --git a/src/implementsAbstractType.ts b/src/utils/implementsAbstractType.ts similarity index 100% rename from src/implementsAbstractType.ts rename to src/utils/implementsAbstractType.ts diff --git a/src/isEmptyObject.ts b/src/utils/isEmptyObject.ts similarity index 100% rename from src/isEmptyObject.ts rename to src/utils/isEmptyObject.ts diff --git a/src/isSpecifiedScalarType.ts b/src/utils/isSpecifiedScalarType.ts similarity index 100% rename from src/isSpecifiedScalarType.ts rename to src/utils/isSpecifiedScalarType.ts diff --git a/src/mergeDeep.ts b/src/utils/mergeDeep.ts similarity index 100% rename from src/mergeDeep.ts rename to src/utils/mergeDeep.ts diff --git a/src/transformInputValue.ts b/src/utils/transformInputValue.ts similarity index 100% rename from src/transformInputValue.ts rename to src/utils/transformInputValue.ts From ef14f8d62ba6784842795782d9b0faffe86daaf6 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 9 Sep 2019 07:31:18 -0400 Subject: [PATCH 045/250] refactor: move underlying healSchema implementation to utils --- src/Interfaces.ts | 2 + src/schemaVisitor.ts | 213 +------------------------------ src/utils/cloneSchema.ts | 73 +++++++++++ src/utils/each.ts | 10 ++ src/utils/healTypeMap.ts | 144 +++++++++++++++++++++ src/utils/updateEachKey.ts | 35 +++++ src/utils/valueFromASTUntyped.ts | 31 +++++ 7 files changed, 302 insertions(+), 206 deletions(-) create mode 100644 src/utils/cloneSchema.ts create mode 100644 src/utils/each.ts create mode 100644 src/utils/healTypeMap.ts create mode 100644 src/utils/updateEachKey.ts create mode 100644 src/utils/valueFromASTUntyped.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index a30a87d3d90..f58e401ebfb 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -267,3 +267,5 @@ export type GraphQLParseOptions = { allowLegacySDLImplementsInterfaces?: boolean; experimentalFragmentVariables?: boolean; }; + +export type IndexedObject = { [key: string]: V } | ReadonlyArray; diff --git a/src/schemaVisitor.ts b/src/schemaVisitor.ts index 67a5cbfa793..16ff81cf55d 100644 --- a/src/schemaVisitor.ts +++ b/src/schemaVisitor.ts @@ -12,18 +12,16 @@ import { GraphQLScalarType, GraphQLSchema, GraphQLUnionType, - Kind, - ValueNode, DirectiveLocationEnum, - GraphQLType, - GraphQLList, - GraphQLNonNull, - isNamedType, } from 'graphql'; import { getArgumentValues, } from 'graphql/execution/values'; +import each from './utils/each'; +import valueFromASTUntyped from './utils/valueFromASTUntyped'; +import { healTypeMap } from './utils/healTypeMap'; +import updateEachKey from './utils/updateEachKey'; export type VisitableSchemaType = GraphQLSchema @@ -293,138 +291,14 @@ export function visitSchema( 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); +export function healSchema(schema: GraphQLSchema): GraphQLSchema { + healTypeMap(schema.getTypeMap(), schema.getDirectives()); 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); - updateEachKey(type.getInterfaces(), iface => healType(iface)); - - } else if (type instanceof GraphQLInterfaceType) { - healFields(type); - - } else if (type instanceof GraphQLInputObjectType) { - healInputFields(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 healInputFields(type: GraphQLInputObjectType) { - each(type.getFields(), field => { - field.type = healType(field.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. // @@ -695,77 +569,4 @@ function directiveLocationToVisitorMethodName(loc: DirectiveLocationEnum) { }); } -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/utils/cloneSchema.ts b/src/utils/cloneSchema.ts new file mode 100644 index 00000000000..92c3ac680cc --- /dev/null +++ b/src/utils/cloneSchema.ts @@ -0,0 +1,73 @@ +import { + GraphQLDirective, + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLInterfaceType, + GraphQLObjectType, + GraphQLNamedType, + GraphQLScalarType, + GraphQLSchema, + GraphQLUnionType, +} from 'graphql'; +import { healTypeMap } from './healTypeMap'; +import isSpecifiedScalarType from './isSpecifiedScalarType'; + +function cloneDirective(directive: GraphQLDirective): GraphQLDirective { + return new GraphQLDirective(directive.toConfig()); +} + +function cloneType(type: GraphQLNamedType): GraphQLNamedType { + if (type instanceof GraphQLObjectType) { + return new GraphQLObjectType(type.toConfig()); + } else if (type instanceof GraphQLInterfaceType) { + return new GraphQLInterfaceType(type.toConfig()); + } else if (type instanceof GraphQLUnionType) { + return new GraphQLUnionType(type.toConfig()); + } else if (type instanceof GraphQLInputObjectType) { + return new GraphQLInputObjectType(type.toConfig()); + } else if (type instanceof GraphQLEnumType) { + return new GraphQLEnumType(type.toConfig()); + } else if (type instanceof GraphQLScalarType) { + return isSpecifiedScalarType(type) ? type : new GraphQLScalarType(type.toConfig()); + } else { + throw new Error(`Invalid type ${type}`); + } +} + +export function cloneSchema(schema: GraphQLSchema): GraphQLSchema { + const newDirectives = schema.getDirectives().map(directive => cloneDirective(directive)); + + const originalTypeMap = schema.getTypeMap(); + const newTypeMap = {}; + + Object.keys(originalTypeMap).forEach(typeName => { + if (!typeName.startsWith('__')) { + newTypeMap[typeName] = cloneType(originalTypeMap[typeName]); + } + }); + + healTypeMap(newTypeMap, newDirectives); + + const selectors = { + query: 'getQueryType', + mutation: 'getMutationType', + subscription: 'getSubscriptionType', + }; + + const rootTypes = Object.create(null); + + Object.keys(selectors).forEach(op => { + const rootType = schema[selectors[op]](); + if (rootType) { + rootTypes[op] = newTypeMap[rootType.name]; + } + }); + + return new GraphQLSchema({ + query: rootTypes.query, + mutation: rootTypes.mutation, + subscription: rootTypes.subscription, + types: Object.keys(newTypeMap).map(typeName => newTypeMap[typeName]), + directives: newDirectives, + }); +} diff --git a/src/utils/each.ts b/src/utils/each.ts new file mode 100644 index 00000000000..312af123c22 --- /dev/null +++ b/src/utils/each.ts @@ -0,0 +1,10 @@ +import { IndexedObject } from '../Interfaces'; + +export default function each( + arrayOrObject: IndexedObject, + callback: (value: V, key: string) => void, +) { + Object.keys(arrayOrObject).forEach(key => { + callback(arrayOrObject[key], key); + }); +} diff --git a/src/utils/healTypeMap.ts b/src/utils/healTypeMap.ts new file mode 100644 index 00000000000..cc1a391f83f --- /dev/null +++ b/src/utils/healTypeMap.ts @@ -0,0 +1,144 @@ +import { + GraphQLDirective, + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLInterfaceType, + GraphQLList, + GraphQLObjectType, + GraphQLNamedType, + GraphQLNonNull, + GraphQLScalarType, + GraphQLType, + GraphQLUnionType, + isNamedType +} from 'graphql'; +import each from './each'; +import updateEachKey from './updateEachKey'; +import { VisitableSchemaType } from '../schemaVisitor'; + +type NamedTypeMap = { + [key: string]: GraphQLNamedType; +}; + +const hasOwn = Object.prototype.hasOwnProperty; + +export function healTypeMap(originalTypeMap: NamedTypeMap, directives: ReadonlyArray) { + 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(directives, (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; + } + }); + + function heal(type: VisitableSchemaType) { + if (type instanceof GraphQLObjectType) { + healFields(type); + updateEachKey(type.getInterfaces(), iface => healType(iface)); + + } else if (type instanceof GraphQLInterfaceType) { + healFields(type); + + } else if (type instanceof GraphQLInputObjectType) { + healInputFields(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 healInputFields(type: GraphQLInputObjectType) { + each(type.getFields(), field => { + field.type = healType(field.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 = originalTypeMap[namedType.name]; + if (officialType && namedType !== officialType) { + return officialType as T; + } + } + return type; + } +} diff --git a/src/utils/updateEachKey.ts b/src/utils/updateEachKey.ts new file mode 100644 index 00000000000..f03ced53116 --- /dev/null +++ b/src/utils/updateEachKey.ts @@ -0,0 +1,35 @@ +import { IndexedObject } from '../Interfaces'; + +// A more powerful version of each that has the ability to replace or remove +// array or object keys. +export default 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); + }); + } +} diff --git a/src/utils/valueFromASTUntyped.ts b/src/utils/valueFromASTUntyped.ts new file mode 100644 index 00000000000..6e725786d27 --- /dev/null +++ b/src/utils/valueFromASTUntyped.ts @@ -0,0 +1,31 @@ +import { ValueNode, Kind } from 'graphql'; + +// Similar to the graphql-js function of the same name, slightly simplified: +// https://github.com/graphql/graphql-js/blob/master/src/utilities/valueFromASTUntyped.js +export default 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); + } +} From 6f313bd484b93d07ed4f1126dc4a1ab7a57f9b82 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 9 Sep 2019 07:34:35 -0400 Subject: [PATCH 046/250] refactor(visitSchema): remove transforms visitSchema reference --- src/transforms/transformSchema.ts | 49 +++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index b74e41973a8..e262a56534d 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -1,7 +1,11 @@ -import { GraphQLSchema } from 'graphql'; +import { + GraphQLInterfaceType, + GraphQLObjectType, + GraphQLSchema, + GraphQLUnionType, +} from 'graphql'; import { addResolveFunctionsToSchema } from '../makeExecutableSchema'; -import { visitSchema } from '../transforms/visitSchema'; import { Transform, applySchemaTransforms } from '../transforms/transforms'; import { generateProxyingResolvers, @@ -11,15 +15,42 @@ import { SchemaExecutionConfig, isSchemaExecutionConfig, } from '../Interfaces'; +import resolveFromParentTypename from '../stitching/resolveFromParentTypename'; +import { defaultMergedResolver } from '../stitching'; +import { cloneSchema } from '../utils/cloneSchema'; -export default function transformSchema( +function stripResolvers(schema: GraphQLSchema): void { + const typeMap = schema.getTypeMap(); + Object.keys(typeMap).forEach(typeName => { + if (typeName.startsWith('__')) { + return; + } + + const type = typeMap[typeName]; + if (type instanceof GraphQLObjectType) { + type.isTypeOf = undefined; + + const fieldMap = type.getFields(); + Object.keys(fieldMap).forEach(fieldName => { + fieldMap[fieldName].resolve = defaultMergedResolver; + fieldMap[fieldName].subscribe = null; + }); + } else if (type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) { + type.resolveType = (parent, context, info) => resolveFromParentTypename(parent, info.schema); + } + }); +} + +export function wrapSchema( schemaOrSchemaExecutionConfig: GraphQLSchema | SchemaExecutionConfig, transforms: Array, -): GraphQLSchema & { transforms: Array } { +): GraphQLSchema { const targetSchema: GraphQLSchema = isSchemaExecutionConfig(schemaOrSchemaExecutionConfig) ? schemaOrSchemaExecutionConfig.schema : schemaOrSchemaExecutionConfig; - let schema = visitSchema(targetSchema, {}, true); + const schema = cloneSchema(targetSchema); + stripResolvers(schema); + const mapping = generateSimpleMapping(targetSchema); const resolvers = generateProxyingResolvers( schemaOrSchemaExecutionConfig, @@ -33,6 +64,14 @@ export default function transformSchema( allowResolversNotInSchema: true, }, }); + return schema; +} + +export default function transformSchema( + schemaOrSchemaExecutionConfig: GraphQLSchema | SchemaExecutionConfig, + transforms: Array, +): GraphQLSchema & { transforms: Array } { + let schema = wrapSchema(schemaOrSchemaExecutionConfig, transforms); schema = applySchemaTransforms(schema, transforms); (schema as any).transforms = transforms; return schema as GraphQLSchema & { transforms: Array }; From a2f407f5ae02015e46ff92f0ebb1f978900fec66 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 9 Sep 2019 17:11:56 -0400 Subject: [PATCH 047/250] chore: label blocking issue --- src/utils/isSpecifiedScalarType.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/isSpecifiedScalarType.ts b/src/utils/isSpecifiedScalarType.ts index 0fec7d93544..dda61f8cac0 100644 --- a/src/utils/isSpecifiedScalarType.ts +++ b/src/utils/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, From 11104f08e03c94a59c1598198c5a61cb13f52ded Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 9 Sep 2019 17:49:16 -0400 Subject: [PATCH 048/250] feat(cloning): export methods to shallow clone types and directives and deep clone schemas --- src/index.ts | 1 + src/utils/cloneSchema.ts | 4 ++-- src/utils/index.ts | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 src/utils/index.ts diff --git a/src/index.ts b/src/index.ts index a8bf617019c..aa658b2e8c6 100755 --- a/src/index.ts +++ b/src/index.ts @@ -3,4 +3,5 @@ export * from './makeExecutableSchema'; export * from './mock'; export * from './stitching'; export * from './transforms'; +export * from './utils'; export { SchemaDirectiveVisitor } from './schemaVisitor'; diff --git a/src/utils/cloneSchema.ts b/src/utils/cloneSchema.ts index 92c3ac680cc..db66b07f08b 100644 --- a/src/utils/cloneSchema.ts +++ b/src/utils/cloneSchema.ts @@ -12,11 +12,11 @@ import { import { healTypeMap } from './healTypeMap'; import isSpecifiedScalarType from './isSpecifiedScalarType'; -function cloneDirective(directive: GraphQLDirective): GraphQLDirective { +export function cloneDirective(directive: GraphQLDirective): GraphQLDirective { return new GraphQLDirective(directive.toConfig()); } -function cloneType(type: GraphQLNamedType): GraphQLNamedType { +export function cloneType(type: GraphQLNamedType): GraphQLNamedType { if (type instanceof GraphQLObjectType) { return new GraphQLObjectType(type.toConfig()); } else if (type instanceof GraphQLInterfaceType) { diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000000..bffcaa10452 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,2 @@ +export { cloneSchema, cloneDirective, cloneType } from './cloneSchema'; +export { healTypeMap } from './healTypeMap'; From 771f310c1d8d402c0828c2253f5a3249f9804c45 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Sep 2019 07:40:02 -0400 Subject: [PATCH 049/250] chore(deps): drop support for graphql 14.0 and 14.1 as refactoring relies heavily on toConfig --- .travis.yml | 9 +++++++++ package.json | 2 +- src/test/testDirectives.ts | 13 ++++--------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 62262c34e04..cc99d50a220 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,8 @@ install: - npm config set spin=false - npm install -g coveralls - npm install + - npm install graphql@$GRAPHQL_VERSION + - npm install @types/graphql@$TYPES_GRAPHQL_VERSION script: - npm test @@ -16,3 +18,10 @@ script: # Allow Travis tests to run in containers. sudo: false + +env: + - GRAPHQL_VERSION='^14.0' TYPES_GRAPHQL_VERSION='^14.0' + - GRAPHQL_VERSION='~14.5' TYPES_GRAPHQL_VERSION='~14.5' + - GRAPHQL_VERSION='~14.4' TYPES_GRAPHQL_VERSION='~14.2' + - GRAPHQL_VERSION='~14.3' TYPES_GRAPHQL_VERSION='~14.2' + - GRAPHQL_VERSION='~14.2' TYPES_GRAPHQL_VERSION='~14.2' diff --git a/package.json b/package.json index 77f089b5612..16bf9cc1a03 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "uuid": "^3.3.3" }, "peerDependencies": { - "graphql": "^14.0.0" + "graphql": "^14.2.0" }, "devDependencies": { "@types/chai": "4.2.0", diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 144cf311e05..891a456f9a4 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -595,14 +595,10 @@ describe('@directives', () => { const { resolve = defaultFieldResolver } = field; const { defaultFormat } = this.args; - field.args.push({ + field.args.push(Object.create({ name: 'format', type: GraphQLString, - description: undefined, - defaultValue: undefined, - extensions: undefined, - astNode: undefined, - }); + })); field.type = GraphQLString; field.resolve = async function (source, { format, ...args }, context, info) { @@ -1022,7 +1018,7 @@ describe('@directives', () => { uniqueID: class extends SchemaDirectiveVisitor { public visitObject(type: GraphQLObjectType) { const { name, from } = this.args; - type.getFields()[name] = { + type.getFields()[name] = Object.create({ name: name, type: GraphQLID, description: 'Unique ID', @@ -1035,8 +1031,7 @@ describe('@directives', () => { }); return hash.digest('hex'); }, - extensions: undefined, - }; + }); } } }, From faad819e50d72b5146bb41ff1efbfa69ca42802f Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Sep 2019 21:46:48 -0400 Subject: [PATCH 050/250] refactor(transforms): export wrapSchema wrapSchema better describes what transformSchema does, it wraps requests to initial schema with request and response transforms, including any outer schema modifications that themselves can modify the response. --- src/transforms/index.ts | 2 +- src/transforms/transformSchema.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 86b51b8fb67..05126bcf25a 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -2,7 +2,7 @@ import { Transform } from './transforms'; export { Transform }; export { default as filterSchema } from './filterSchema'; -export { default as transformSchema } from './transformSchema'; +export { default as transformSchema, wrapSchema } from './transformSchema'; export { default as AddEnumAndScalarResolvers } from './AddEnumAndScalarResolvers'; export { default as AddDefaultResolver } from './AddDefaultResolver'; diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index e262a56534d..8ef8450ec71 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -64,15 +64,15 @@ export function wrapSchema( allowResolversNotInSchema: true, }, }); - return schema; + + return applySchemaTransforms(schema, transforms); } export default function transformSchema( schemaOrSchemaExecutionConfig: GraphQLSchema | SchemaExecutionConfig, transforms: Array, ): GraphQLSchema & { transforms: Array } { - let schema = wrapSchema(schemaOrSchemaExecutionConfig, transforms); - schema = applySchemaTransforms(schema, transforms); + const schema = wrapSchema(schemaOrSchemaExecutionConfig, transforms); (schema as any).transforms = transforms; return schema as GraphQLSchema & { transforms: Array }; } From c6b547004beee5a647f62871722cb180d42e94d0 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 12 Sep 2019 14:43:02 -0400 Subject: [PATCH 051/250] feat(filterSchema): provide type argument to type filter also removes unnecessary checks considering defaults provides lint: default values obviate checks --- src/test/testAlternateMergeSchemas.ts | 5 +++-- src/transforms/filterSchema.ts | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 903395e16d9..f41a1dd48e9 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -44,6 +44,7 @@ import { extractFields, } from '../stitching'; import { SchemaExecutionConfig } from '../Interfaces'; +import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; function renameFieldNode(fieldNode: FieldNode, name: string): FieldNode { return { @@ -428,8 +429,8 @@ describe('filter and rename object fields', () => { 'Query.propertyById' === `${operation}.${fieldName}`, fieldFilter: (typeName: string, fieldName: string) => (typeName === 'New_Property' || fieldName === 'name'), - typeFilter: (typeName: string) => - (typeName === 'New_Property' || typeName === 'New_Location') + typeFilter: (typeName: string, type) => + (typeName === 'New_Property' || typeName === 'New_Location' || isSpecifiedScalarType(type)) }); }); diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index ff554f847a3..8b71f4bf356 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -5,6 +5,7 @@ import { GraphQLObjectType, GraphQLScalarType, GraphQLUnionType, + GraphQLType, } from 'graphql'; import { GraphQLSchemaWithTransforms } from '../Interfaces'; import { visitSchema, VisitSchemaKind } from './visitSchema'; @@ -29,40 +30,38 @@ export default function filterSchema({ }: { schema: GraphQLSchemaWithTransforms; rootFieldFilter?: RootFieldFilter; - typeFilter?: (typeName: string) => boolean; + typeFilter?: (typeName: string, type: GraphQLType) => boolean; fieldFilter?: (typeName: string, fieldName: string) => boolean; }): GraphQLSchemaWithTransforms { const filteredSchema: GraphQLSchemaWithTransforms = visitSchema(schema, { [VisitSchemaKind.QUERY]: (type: GraphQLObjectType) => { - return rootFieldFilter ? filterRootFields(type, 'Query', rootFieldFilter) : undefined; + return filterRootFields(type, 'Query', rootFieldFilter); }, [VisitSchemaKind.MUTATION]: (type: GraphQLObjectType) => { - return rootFieldFilter ? filterRootFields(type, 'Mutation', rootFieldFilter) : undefined; + return filterRootFields(type, 'Mutation', rootFieldFilter); }, [VisitSchemaKind.SUBSCRIPTION]: (type: GraphQLObjectType) => { - return rootFieldFilter ? filterRootFields(type, 'Subscription', rootFieldFilter) : undefined; + return filterRootFields(type, 'Subscription', rootFieldFilter); }, [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => { - return (!typeFilter || typeFilter(type.name)) ? - (filterObjectFields ? - filterObjectFields(type, fieldFilter) : - undefined) : + return (typeFilter(type.name, type)) ? + filterObjectFields(type, fieldFilter) : null; }, [VisitSchemaKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) => { - return (!typeFilter || typeFilter(type.name)) ? undefined : null; + return typeFilter(type.name, type) ? undefined : null; }, [VisitSchemaKind.UNION_TYPE]: (type: GraphQLUnionType) => { - return (!typeFilter || typeFilter(type.name)) ? undefined : null; + return typeFilter(type.name, type) ? undefined : null; }, [VisitSchemaKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) => { - return (!typeFilter || typeFilter(type.name)) ? undefined : null; + return typeFilter(type.name, type) ? undefined : null; }, [VisitSchemaKind.ENUM_TYPE]: (type: GraphQLEnumType) => { - return (!typeFilter || typeFilter(type.name)) ? undefined : null; + return typeFilter(type.name, type) ? undefined : null; }, [VisitSchemaKind.SCALAR_TYPE]: (type: GraphQLScalarType) => { - return (!typeFilter || typeFilter(type.name)) ? undefined : null; + return typeFilter(type.name, type) ? undefined : null; }, }); From c7f3a1360474a65a236ac73bf87b91ea8863ca45 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 12 Sep 2019 15:53:24 -0400 Subject: [PATCH 052/250] refactor: move stub type methods to utils --- src/stitching/schemaRecreation.ts | 2 +- src/stitching/typeFromAST.ts | 31 +-------------------------- src/utils/stub.ts | 35 +++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 31 deletions(-) create mode 100644 src/utils/stub.ts diff --git a/src/stitching/schemaRecreation.ts b/src/stitching/schemaRecreation.ts index a068b9babe6..64183d0d4ca 100644 --- a/src/stitching/schemaRecreation.ts +++ b/src/stitching/schemaRecreation.ts @@ -36,7 +36,7 @@ import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; import { ResolveType } from '../Interfaces'; import resolveFromParentTypename from './resolveFromParentTypename'; import defaultMergedResolver from './defaultMergedResolver'; -import { isStub } from './typeFromAST'; +import { isStub } from '../utils/stub'; import { serializeInputValue, parseInputValue, diff --git a/src/stitching/typeFromAST.ts b/src/stitching/typeFromAST.ts index 878487fe2e2..300fd933f38 100644 --- a/src/stitching/typeFromAST.ts +++ b/src/stitching/typeFromAST.ts @@ -22,7 +22,6 @@ import { TypeNode, UnionTypeDefinitionNode, getDescription, - GraphQLString, GraphQLDirective, DirectiveDefinitionNode, DirectiveLocationEnum, @@ -31,6 +30,7 @@ import { StringValueNode, } from 'graphql'; import resolveFromParentType from './resolveFromParentTypename'; +import { createNamedStub } from '../utils/stub'; const backcompatOptions = { commentDescriptions: true }; @@ -203,35 +203,6 @@ function resolveType( } } -function createNamedStub( - name: string, - type: 'object' | 'interface' | 'input' -): GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType { - let constructor: any; - if (type === 'object') { - constructor = GraphQLObjectType; - } else if (type === 'interface') { - constructor = GraphQLInterfaceType; - } else { - constructor = GraphQLInputObjectType; - } - - return new constructor({ - name, - fields: { - __fake: { - type: GraphQLString, - }, - }, - }); -} - -export function isStub(type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType): boolean { - const fields = type.getFields(); - const fieldNames = Object.keys(fields); - return fieldNames.length === 1 && fields[fieldNames[0]].name === '__fake'; -} - function makeDirective(node: DirectiveDefinitionNode): GraphQLDirective { const locations: Array = []; node.locations.forEach(location => { diff --git a/src/utils/stub.ts b/src/utils/stub.ts new file mode 100644 index 00000000000..09c21cd5e4e --- /dev/null +++ b/src/utils/stub.ts @@ -0,0 +1,35 @@ +import { + GraphQLObjectType, + GraphQLInterfaceType, + GraphQLInputObjectType, + GraphQLString, +} from 'graphql'; + +export function createNamedStub( + name: string, + type: 'object' | 'interface' | 'input' +): GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType { + let constructor: any; + if (type === 'object') { + constructor = GraphQLObjectType; + } else if (type === 'interface') { + constructor = GraphQLInterfaceType; + } else { + constructor = GraphQLInputObjectType; + } + + return new constructor({ + name, + fields: { + __fake: { + type: GraphQLString, + }, + }, + }); +} + +export function isStub(type: GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType): boolean { + const fields = type.getFields(); + const fieldNames = Object.keys(fields); + return fieldNames.length === 1 && fields[fieldNames[0]].name === '__fake'; +} From 538461e6668d8d54f72ab0b1be3a068fd8e6ca81 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 12 Sep 2019 17:15:30 -0400 Subject: [PATCH 053/250] fix(cloneSchema): healSchema requires a new array for object interfaces and union types. toConfig provides a new map for fields, but not new arrays. --- src/utils/cloneSchema.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/utils/cloneSchema.ts b/src/utils/cloneSchema.ts index db66b07f08b..00df9cd68e8 100644 --- a/src/utils/cloneSchema.ts +++ b/src/utils/cloneSchema.ts @@ -18,11 +18,19 @@ export function cloneDirective(directive: GraphQLDirective): GraphQLDirective { export function cloneType(type: GraphQLNamedType): GraphQLNamedType { if (type instanceof GraphQLObjectType) { - return new GraphQLObjectType(type.toConfig()); + const config = type.toConfig(); + return new GraphQLObjectType({ + ...config, + interfaces: config.interfaces.slice(), + }); } else if (type instanceof GraphQLInterfaceType) { return new GraphQLInterfaceType(type.toConfig()); } else if (type instanceof GraphQLUnionType) { - return new GraphQLUnionType(type.toConfig()); + const config = type.toConfig(); + return new GraphQLUnionType({ + ...config, + types: config.types.slice(), + }); } else if (type instanceof GraphQLInputObjectType) { return new GraphQLInputObjectType(type.toConfig()); } else if (type instanceof GraphQLEnumType) { From b55bc823e2bdd27094fa4cf72c9fe60e13305494 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 12 Sep 2019 17:30:36 -0400 Subject: [PATCH 054/250] refactor: rewrite transforms visitSchema to use cloneSchema and healTypeMap. Required changes to healTypeMap to handle removed types and/or fields. --- src/transforms/transformSchema.ts | 6 +- src/transforms/visitSchema.ts | 61 +++++++++--------- src/utils/healTypeMap.ts | 104 ++++++++++++++++++++++++------ 3 files changed, 118 insertions(+), 53 deletions(-) diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index 8ef8450ec71..64a58870242 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -18,8 +18,9 @@ import { import resolveFromParentTypename from '../stitching/resolveFromParentTypename'; import { defaultMergedResolver } from '../stitching'; import { cloneSchema } from '../utils/cloneSchema'; +import { visitSchema } from './visitSchema'; -function stripResolvers(schema: GraphQLSchema): void { +export function stripResolvers(schema: GraphQLSchema): void { const typeMap = schema.getTypeMap(); Object.keys(typeMap).forEach(typeName => { if (typeName.startsWith('__')) { @@ -48,8 +49,7 @@ export function wrapSchema( const targetSchema: GraphQLSchema = isSchemaExecutionConfig(schemaOrSchemaExecutionConfig) ? schemaOrSchemaExecutionConfig.schema : schemaOrSchemaExecutionConfig; - const schema = cloneSchema(targetSchema); - stripResolvers(schema); + const schema = visitSchema(targetSchema, {}, true); const mapping = generateSimpleMapping(targetSchema); const resolvers = generateProxyingResolvers( diff --git a/src/transforms/visitSchema.ts b/src/transforms/visitSchema.ts index 0bb12702a19..486ce25b8f1 100644 --- a/src/transforms/visitSchema.ts +++ b/src/transforms/visitSchema.ts @@ -9,9 +9,13 @@ import { GraphQLUnionType, GraphQLNamedType, isNamedType, - getNamedType, } from 'graphql'; -import { recreateType, recreateDirective, createResolveType } from '../stitching/schemaRecreation'; +import { + cloneType, + cloneDirective, + healTypeMap, +} from '../utils'; +import { stripResolvers } from './transformSchema'; export enum VisitSchemaKind { TYPE = 'VisitSchemaKind.TYPE', @@ -33,59 +37,58 @@ export type SchemaVisitor = { [key: string]: TypeVisitor }; export type TypeVisitor = ( type: GraphQLType, schema: GraphQLSchema, -) => GraphQLNamedType; +) => GraphQLNamedType | null | undefined; export function visitSchema( schema: GraphQLSchema, visitor: SchemaVisitor, - stripResolvers?: boolean, + wrap?: boolean, ) { - const types: {[key: string]: GraphQLNamedType} = {}; - const resolveType = createResolveType(name => { - if (typeof types[name] === 'undefined') { - throw new Error(`Can't find type ${name}.`); - } - return types[name]; - }); + const types: { [key: string]: GraphQLNamedType } = {}; + const queryType = schema.getQueryType(); const mutationType = schema.getMutationType(); const subscriptionType = schema.getSubscriptionType(); + const typeMap = schema.getTypeMap(); Object.keys(typeMap).map((typeName: string) => { const type = typeMap[typeName]; - if (isNamedType(type) && getNamedType(type).name.slice(0, 2) !== '__') { + if (isNamedType(type) && type.name.slice(0, 2) !== '__') { const specifiers = getTypeSpecifiers(type, schema); const typeVisitor = getVisitor(visitor, specifiers); if (typeVisitor) { - const result: GraphQLNamedType | null | undefined = typeVisitor( - type, - schema, - ); + const result = typeVisitor(type, schema); if (typeof result === 'undefined') { - types[typeName] = recreateType(type, resolveType, !stripResolvers); + types[typeName] = cloneType(type); } else if (result === null) { types[typeName] = null; } else { - types[typeName] = recreateType(result, resolveType, !stripResolvers); + types[typeName] = cloneType(result); } - } else { - types[typeName] = recreateType(type, resolveType, !stripResolvers); + } else { + types[typeName] = cloneType(type); } } }); - return new GraphQLSchema({ + const directives = schema.getDirectives().map(d => cloneDirective(d)); + + healTypeMap(types, directives); + + const newSchema = new GraphQLSchema({ + ...schema.toConfig(), query: queryType ? (types[queryType.name] as GraphQLObjectType) : null, - mutation: mutationType - ? (types[mutationType.name] as GraphQLObjectType) - : null, - subscription: subscriptionType - ? (types[subscriptionType.name] as GraphQLObjectType) - : null, + mutation: mutationType ? (types[mutationType.name] as GraphQLObjectType) : null, + subscription: subscriptionType ? (types[subscriptionType.name] as GraphQLObjectType) : null, types: Object.keys(types).map(name => types[name]), - directives: [...schema.getDirectives().map(d => recreateDirective(d, resolveType))], - astNode: schema.astNode, + directives, }); + + if (wrap) { + stripResolvers(newSchema); + } + + return newSchema; } function getTypeSpecifiers( diff --git a/src/utils/healTypeMap.ts b/src/utils/healTypeMap.ts index cc1a391f83f..89c8edf7ae2 100644 --- a/src/utils/healTypeMap.ts +++ b/src/utils/healTypeMap.ts @@ -30,7 +30,7 @@ export function healTypeMap(originalTypeMap: NamedTypeMap, directives: ReadonlyA // be updated accordingly. each(originalTypeMap, (namedType, typeName) => { - if (typeName.startsWith('__')) { + if (!namedType || typeName.startsWith('__')) { return; } @@ -58,14 +58,17 @@ export function healTypeMap(originalTypeMap: NamedTypeMap, directives: ReadonlyA // Directive declaration argument types can refer to named types. each(directives, (decl: GraphQLDirective) => { if (decl.args) { - each(decl.args, arg => { + updateEachKey(decl.args, arg => { arg.type = healType(arg.type); + return arg.type === null ? null : arg; }); } }); each(originalTypeMap, (namedType, typeName) => { - if (! typeName.startsWith('__')) { + // Heal all named types, except for dangling references, kept only to redirect. + if (! typeName.startsWith('__') && + hasOwn.call(actualNamedTypeMap, typeName)) { heal(namedType); } }); @@ -80,24 +83,23 @@ export function healTypeMap(originalTypeMap: NamedTypeMap, directives: ReadonlyA } }); + pruneTypeMap(originalTypeMap, directives); + function heal(type: VisitableSchemaType) { if (type instanceof GraphQLObjectType) { healFields(type); - updateEachKey(type.getInterfaces(), iface => healType(iface)); + healInterfaces(type); } else if (type instanceof GraphQLInterfaceType) { healFields(type); + } else if (type instanceof GraphQLUnionType) { + healUnderlyingTypes(type); + } else if (type instanceof GraphQLInputObjectType) { healInputFields(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) { + } else if (type instanceof GraphQLScalarType || GraphQLEnumType) { // Nothing to do. } else { @@ -106,39 +108,99 @@ export function healTypeMap(originalTypeMap: NamedTypeMap, directives: ReadonlyA } function healFields(type: GraphQLObjectType | GraphQLInterfaceType) { - each(type.getFields(), field => { - field.type = healType(field.type); + updateEachKey(type.getFields(), field => { if (field.args) { - each(field.args, arg => { + updateEachKey(field.args, arg => { arg.type = healType(arg.type); + return arg.type === null ? null : arg; }); } + field.type = healType(field.type); + return field.type === null ? null : field; + }); + } + + function healInterfaces(type: GraphQLObjectType) { + updateEachKey(type.getInterfaces(), iface => { + const healedType = healType(iface); + return healedType; }); } function healInputFields(type: GraphQLInputObjectType) { - each(type.getFields(), field => { + updateEachKey(type.getFields(), field => { field.type = healType(field.type); + return field.type === null ? null : field; + }); + } + + function healUnderlyingTypes(type: GraphQLUnionType) { + updateEachKey(type.getTypes(), t => { + const healedType = healType(t); + return healedType; }); } function healType(type: T): T { // Unwrap the two known wrapper types if (type instanceof GraphQLList) { - type = new GraphQLList(healType(type.ofType)) as T; + const healedType = healType(type.ofType); + return healedType ? new GraphQLList(healedType) as T : null; } else if (type instanceof GraphQLNonNull) { - type = new GraphQLNonNull(healType(type.ofType)) as T; + const healedType = healType(type.ofType); + return healedType ? new GraphQLNonNull(healedType) as T : null; } 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 = originalTypeMap[namedType.name]; - if (officialType && namedType !== officialType) { + // Note that new types can still be simply added by adding a field, as + // the official type will be undefined, not null. + const officialType = originalTypeMap[type.name]; + if (officialType === undefined) { + originalTypeMap[type.name] = type; + return type; + } else { return officialType as T; } + } else { + return null; } - return type; + } +} + +function pruneTypeMap(typeMap: NamedTypeMap, directives: ReadonlyArray) { + const implementedInterfaces = {}; + each(typeMap, (namedType, typeName) => { + if (namedType instanceof GraphQLObjectType) { + each(namedType.getInterfaces(), iface => { + implementedInterfaces[iface.name] = true; + }); + } + }); + + let prunedTypeMap = false; + updateEachKey(typeMap, (type, typeName) => { + let shouldPrune: boolean = false; + if (type instanceof GraphQLObjectType) { + // prune types with no fields + shouldPrune = !Object.keys(type.getFields()).length; + } else if (type instanceof GraphQLUnionType) { + // prune unions without underlying types + shouldPrune = !type.getTypes().length; + } else if (type instanceof GraphQLInterfaceType) { + // prune interfaces without implementations + shouldPrune = !implementedInterfaces[type.name]; + } + + if (shouldPrune) { + prunedTypeMap = true; + return null; + } + }); + + // every prune requires another round of healing + if (prunedTypeMap) { + healTypeMap(typeMap, directives); } } From cf69bd8bd444f41a06dd2d419477718bb5a3a06e Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 12 Sep 2019 17:34:50 -0400 Subject: [PATCH 055/250] refactor: deprecate stripResolvers argument for transforms visitSchema use wrapSchema instead --- src/transforms/transformSchema.ts | 4 ++-- src/transforms/visitSchema.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index 64a58870242..a6b11ff3d6f 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -18,7 +18,6 @@ import { import resolveFromParentTypename from '../stitching/resolveFromParentTypename'; import { defaultMergedResolver } from '../stitching'; import { cloneSchema } from '../utils/cloneSchema'; -import { visitSchema } from './visitSchema'; export function stripResolvers(schema: GraphQLSchema): void { const typeMap = schema.getTypeMap(); @@ -49,7 +48,8 @@ export function wrapSchema( const targetSchema: GraphQLSchema = isSchemaExecutionConfig(schemaOrSchemaExecutionConfig) ? schemaOrSchemaExecutionConfig.schema : schemaOrSchemaExecutionConfig; - const schema = visitSchema(targetSchema, {}, true); + const schema = cloneSchema(targetSchema); + stripResolvers(schema); const mapping = generateSimpleMapping(targetSchema); const resolvers = generateProxyingResolvers( diff --git a/src/transforms/visitSchema.ts b/src/transforms/visitSchema.ts index 486ce25b8f1..9ecc0e9f498 100644 --- a/src/transforms/visitSchema.ts +++ b/src/transforms/visitSchema.ts @@ -42,7 +42,7 @@ export type TypeVisitor = ( export function visitSchema( schema: GraphQLSchema, visitor: SchemaVisitor, - wrap?: boolean, + wrap?: boolean, // deprecated, use wrapSchema ) { const types: { [key: string]: GraphQLNamedType } = {}; From ce77e4d190d0b22bc55d1fede6230ea441dd743e Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 12 Sep 2019 18:14:38 -0400 Subject: [PATCH 056/250] refactor: use graphql toConfig() instead of fieldToFieldConfig toConfig does recreate the field map even though it does not recreate object interfaces and union types arrays. --- src/transforms/filterSchema.ts | 47 ++++++++-------------------------- 1 file changed, 10 insertions(+), 37 deletions(-) diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index 8b71f4bf356..d1cab70e3e7 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -9,8 +9,6 @@ import { } from 'graphql'; import { GraphQLSchemaWithTransforms } from '../Interfaces'; import { visitSchema, VisitSchemaKind } from './visitSchema'; -import { fieldToFieldConfig, createResolveType } from '../stitching/schemaRecreation'; -import isEmptyObject from '../utils/isEmptyObject'; export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -75,49 +73,24 @@ function filterRootFields( operation: 'Query' | 'Mutation' | 'Subscription', rootFieldFilter: RootFieldFilter, ): GraphQLObjectType { - const resolveType = createResolveType((_, t) => t); - const fields = type.getFields(); - const newFields = {}; - Object.keys(fields).forEach(fieldName => { - if (rootFieldFilter(operation, fieldName)) { - newFields[fieldName] = fieldToFieldConfig(fields[fieldName], resolveType, true); + const config = type.toConfig(); + Object.keys(config.fields).forEach(fieldName => { + if (!rootFieldFilter(operation, fieldName)) { + delete config.fields[fieldName]; } }); - if (isEmptyObject(newFields)) { - return null; - } else { - return new GraphQLObjectType({ - name: type.name, - description: type.description, - astNode: type.astNode, - fields: newFields, - }); - } + return new GraphQLObjectType(config); } function filterObjectFields( type: GraphQLObjectType, fieldFilter: FieldFilter, ): GraphQLObjectType { - const resolveType = createResolveType((_, t) => t); - const fields = type.getFields(); - const interfaces = type.getInterfaces(); - const newFields = {}; - Object.keys(fields).forEach(fieldName => { - if (fieldFilter(type.name, fieldName)) { - newFields[fieldName] = fieldToFieldConfig(fields[fieldName], resolveType, true); + const config = type.toConfig(); + Object.keys(config.fields).forEach(fieldName => { + if (!fieldFilter(type.name, fieldName)) { + delete config.fields[fieldName]; } }); - if (isEmptyObject(newFields)) { - return null; - } else { - return new GraphQLObjectType({ - name: type.name, - description: type.description, - astNode: type.astNode, - isTypeOf: type.isTypeOf, - fields: newFields, - interfaces: () => interfaces.map(iface => resolveType(iface)), - }); - } + return new GraphQLObjectType(config); } From ecf3066945d4b076c35fbe5570008ca4241f1757 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Fri, 13 Sep 2019 10:21:28 -0400 Subject: [PATCH 057/250] refactor: use cloneType and healTypeMap within mergeSchemas instead of recreateType. Requires modifying healTypeMap to handle stub types. --- src/stitching/makeMergedType.ts | 22 ++++++++++++++++++ src/stitching/mergeSchemas.ts | 34 ++++++++++++--------------- src/transforms/transformSchema.ts | 28 ++++------------------- src/utils/healTypeMap.ts | 34 +++++++++++++++++++-------- src/utils/stub.ts | 38 +++++++++++++++++++++++++++---- 5 files changed, 99 insertions(+), 57 deletions(-) create mode 100644 src/stitching/makeMergedType.ts diff --git a/src/stitching/makeMergedType.ts b/src/stitching/makeMergedType.ts new file mode 100644 index 00000000000..3cef8c21cf4 --- /dev/null +++ b/src/stitching/makeMergedType.ts @@ -0,0 +1,22 @@ +import { + GraphQLType, + GraphQLObjectType, + GraphQLInterfaceType, + GraphQLUnionType, +} from 'graphql'; +import defaultMergedResolver from './defaultMergedResolver'; +import resolveFromParentTypename from './resolveFromParentTypename'; + +export function makeMergedType(type: GraphQLType): void { + if (type instanceof GraphQLObjectType) { + type.isTypeOf = undefined; + + const fieldMap = type.getFields(); + Object.keys(fieldMap).forEach(fieldName => { + fieldMap[fieldName].resolve = defaultMergedResolver; + fieldMap[fieldName].subscribe = null; + }); + } else if (type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) { + type.resolveType = (parent, context, info) => resolveFromParentTypename(parent, info.schema); + } +} diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 36eecb981e5..22685529b33 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -28,12 +28,6 @@ import { extractExtensionDefinitions, addResolveFunctionsToSchema, } from '../makeExecutableSchema'; -import { - recreateType, - recreateDirective, - fieldMapToFieldConfigMap, - createResolveType, -} from './schemaRecreation'; import delegateToSchema from './delegateToSchema'; import delegateToRemoteSchema from './delegateToRemoteSchema'; import typeFromAST from './typeFromAST'; @@ -43,7 +37,9 @@ import { ReplaceFieldWithFragment, } from '../transforms'; import mergeDeep from '../utils/mergeDeep'; -import { SchemaDirectiveVisitor } from '../schemaVisitor'; +import { SchemaDirectiveVisitor, healSchema } from '../schemaVisitor'; +import { cloneDirective, cloneType, healTypeMap } from '../utils'; +import { makeMergedType } from './makeMergedType'; type MergeTypeCandidate = { schema?: GraphQLSchema; @@ -88,13 +84,6 @@ export default function mergeSchemas({ fragment: string; }> = []; - const resolveType = createResolveType(name => { - if (types[name] === undefined) { - throw new Error(`Can't find type ${name}.`); - } - return types[name]; - }); - schemas.forEach(schemaOrSchemaExecutionConfig => { let schema: string | GraphQLSchema | SchemaExecutionConfig | DocumentNode | Array; let executionConfig: SchemaExecutionConfig; @@ -227,19 +216,21 @@ export default function mergeSchemas({ } else { throw new Error(`Invalid mergeTypeCandidates result for type ${typeName}`); } - types[typeName] = recreateType(type, resolveType, false); + types[typeName] = type; if (typeResolvers !== undefined) { generatedResolvers[typeName] = typeResolvers; } }); + healTypeMap(types, directives, { skipPruning: true }); + let mergedSchema = new GraphQLSchema({ query: types.Query as GraphQLObjectType, mutation: types.Mutation as GraphQLObjectType, subscription: types.Subscription as GraphQLObjectType, types: Object.keys(types).map(key => types[key]), directives: directives.length ? - directives.map((directive) => recreateDirective(directive, resolveType)) : + directives.map((directive) => cloneDirective(directive)) : undefined }); @@ -301,6 +292,8 @@ export default function mergeSchemas({ ); } + healSchema(mergedSchema); + return mergedSchema; } @@ -481,7 +474,6 @@ function mergeTypeCandidates( if (!candidateSelector) { candidateSelector = cands => cands[cands.length - 1]; } - const resolveType = createResolveType((_, type) => type); if (name === 'Query' || name === 'Mutation' || name === 'Subscription') { let fields = {}; let operationName: 'query' | 'mutation' | 'subscription'; @@ -502,7 +494,7 @@ function mergeTypeCandidates( const resolverKey = operationName === 'subscription' ? 'subscribe' : 'resolve'; candidates.forEach(({ type: candidateType, schema, executionConfig }) => { - const candidateFields = (candidateType as GraphQLObjectType).getFields(); + const candidateFields = (candidateType as GraphQLObjectType).toConfig().fields; fields = { ...fields, ...candidateFields }; Object.keys(candidateFields).forEach(fieldName => { resolvers[fieldName] = { @@ -517,7 +509,7 @@ function mergeTypeCandidates( }); const type = new GraphQLObjectType({ name, - fields: fieldMapToFieldConfigMap(fields, resolveType, false), + fields, }); return { type, @@ -525,8 +517,10 @@ function mergeTypeCandidates( }; } else { const candidate = candidateSelector(candidates); + const type = cloneType(candidate.type); + makeMergedType(type); return { - type: candidate.type, + type, candidate }; } diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index a6b11ff3d6f..4330501817b 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -1,9 +1,4 @@ -import { - GraphQLInterfaceType, - GraphQLObjectType, - GraphQLSchema, - GraphQLUnionType, -} from 'graphql'; +import { GraphQLSchema } from 'graphql'; import { addResolveFunctionsToSchema } from '../makeExecutableSchema'; import { Transform, applySchemaTransforms } from '../transforms/transforms'; @@ -15,28 +10,15 @@ import { SchemaExecutionConfig, isSchemaExecutionConfig, } from '../Interfaces'; -import resolveFromParentTypename from '../stitching/resolveFromParentTypename'; -import { defaultMergedResolver } from '../stitching'; + import { cloneSchema } from '../utils/cloneSchema'; +import { makeMergedType } from '../stitching/makeMergedType'; export function stripResolvers(schema: GraphQLSchema): void { const typeMap = schema.getTypeMap(); Object.keys(typeMap).forEach(typeName => { - if (typeName.startsWith('__')) { - return; - } - - const type = typeMap[typeName]; - if (type instanceof GraphQLObjectType) { - type.isTypeOf = undefined; - - const fieldMap = type.getFields(); - Object.keys(fieldMap).forEach(fieldName => { - fieldMap[fieldName].resolve = defaultMergedResolver; - fieldMap[fieldName].subscribe = null; - }); - } else if (type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) { - type.resolveType = (parent, context, info) => resolveFromParentTypename(parent, info.schema); + if (!typeName.startsWith('__')) { + makeMergedType(typeMap[typeName]); } }); } diff --git a/src/utils/healTypeMap.ts b/src/utils/healTypeMap.ts index 89c8edf7ae2..27b048cec10 100644 --- a/src/utils/healTypeMap.ts +++ b/src/utils/healTypeMap.ts @@ -10,11 +10,12 @@ import { GraphQLScalarType, GraphQLType, GraphQLUnionType, - isNamedType + isNamedType, } from 'graphql'; import each from './each'; import updateEachKey from './updateEachKey'; import { VisitableSchemaType } from '../schemaVisitor'; +import { isStub, getBuiltInForStub } from './stub'; type NamedTypeMap = { [key: string]: GraphQLNamedType; @@ -22,7 +23,15 @@ type NamedTypeMap = { const hasOwn = Object.prototype.hasOwnProperty; -export function healTypeMap(originalTypeMap: NamedTypeMap, directives: ReadonlyArray) { +export function healTypeMap( + originalTypeMap: NamedTypeMap, + directives: ReadonlyArray, + config: { + skipPruning: boolean; + } = { + skipPruning: false, + } +) { const actualNamedTypeMap: NamedTypeMap = Object.create(null); // If any of the .name properties of the GraphQLNamedType objects in @@ -83,7 +92,9 @@ export function healTypeMap(originalTypeMap: NamedTypeMap, directives: ReadonlyA } }); - pruneTypeMap(originalTypeMap, directives); + if (!config.skipPruning) { + pruneTypeMap(originalTypeMap, directives); + } function heal(type: VisitableSchemaType) { if (type instanceof GraphQLObjectType) { @@ -156,13 +167,16 @@ export function healTypeMap(originalTypeMap: NamedTypeMap, directives: ReadonlyA // of truth for all named schema types. // Note that new types can still be simply added by adding a field, as // the official type will be undefined, not null. - const officialType = originalTypeMap[type.name]; + let officialType = originalTypeMap[type.name]; if (officialType === undefined) { - originalTypeMap[type.name] = type; - return type; - } else { - return officialType as T; + if (isStub(type)) { + officialType = getBuiltInForStub(type); + } else { + officialType = type; + } + originalTypeMap[type.name] = officialType; } + return officialType as T; } else { return null; } @@ -189,8 +203,8 @@ function pruneTypeMap(typeMap: NamedTypeMap, directives: ReadonlyArray Date: Sat, 14 Sep 2019 17:54:10 -0400 Subject: [PATCH 058/250] refactor: rename healTypeMap to healTypes As does not just heal typeMap, also heals types within directives. --- src/schemaVisitor.ts | 4 ++-- src/stitching/mergeSchemas.ts | 4 ++-- src/transforms/visitSchema.ts | 4 ++-- src/utils/cloneSchema.ts | 4 ++-- src/utils/{healTypeMap.ts => healTypes.ts} | 4 ++-- src/utils/index.ts | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) rename src/utils/{healTypeMap.ts => healTypes.ts} (99%) diff --git a/src/schemaVisitor.ts b/src/schemaVisitor.ts index 16ff81cf55d..c2dc981b6a4 100644 --- a/src/schemaVisitor.ts +++ b/src/schemaVisitor.ts @@ -20,7 +20,7 @@ import { } from 'graphql/execution/values'; import each from './utils/each'; import valueFromASTUntyped from './utils/valueFromASTUntyped'; -import { healTypeMap } from './utils/healTypeMap'; +import { healTypes } from './utils/healTypes'; import updateEachKey from './utils/updateEachKey'; export type VisitableSchemaType = @@ -294,7 +294,7 @@ export function visitSchema( // Update any references to named schema types that disagree with the named // types found in schema.getTypeMap(). export function healSchema(schema: GraphQLSchema): GraphQLSchema { - healTypeMap(schema.getTypeMap(), schema.getDirectives()); + healTypes(schema.getTypeMap(), schema.getDirectives()); return schema; } diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 22685529b33..069b448e69b 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -38,7 +38,7 @@ import { } from '../transforms'; import mergeDeep from '../utils/mergeDeep'; import { SchemaDirectiveVisitor, healSchema } from '../schemaVisitor'; -import { cloneDirective, cloneType, healTypeMap } from '../utils'; +import { cloneDirective, cloneType, healTypes } from '../utils'; import { makeMergedType } from './makeMergedType'; type MergeTypeCandidate = { @@ -222,7 +222,7 @@ export default function mergeSchemas({ } }); - healTypeMap(types, directives, { skipPruning: true }); + healTypes(types, directives, { skipPruning: true }); let mergedSchema = new GraphQLSchema({ query: types.Query as GraphQLObjectType, diff --git a/src/transforms/visitSchema.ts b/src/transforms/visitSchema.ts index 9ecc0e9f498..89a70545681 100644 --- a/src/transforms/visitSchema.ts +++ b/src/transforms/visitSchema.ts @@ -13,7 +13,7 @@ import { import { cloneType, cloneDirective, - healTypeMap, + healTypes, } from '../utils'; import { stripResolvers } from './transformSchema'; @@ -73,7 +73,7 @@ export function visitSchema( const directives = schema.getDirectives().map(d => cloneDirective(d)); - healTypeMap(types, directives); + healTypes(types, directives); const newSchema = new GraphQLSchema({ ...schema.toConfig(), diff --git a/src/utils/cloneSchema.ts b/src/utils/cloneSchema.ts index 00df9cd68e8..26f5358ea08 100644 --- a/src/utils/cloneSchema.ts +++ b/src/utils/cloneSchema.ts @@ -9,7 +9,7 @@ import { GraphQLSchema, GraphQLUnionType, } from 'graphql'; -import { healTypeMap } from './healTypeMap'; +import { healTypes } from './healTypes'; import isSpecifiedScalarType from './isSpecifiedScalarType'; export function cloneDirective(directive: GraphQLDirective): GraphQLDirective { @@ -54,7 +54,7 @@ export function cloneSchema(schema: GraphQLSchema): GraphQLSchema { } }); - healTypeMap(newTypeMap, newDirectives); + healTypes(newTypeMap, newDirectives); const selectors = { query: 'getQueryType', diff --git a/src/utils/healTypeMap.ts b/src/utils/healTypes.ts similarity index 99% rename from src/utils/healTypeMap.ts rename to src/utils/healTypes.ts index 27b048cec10..eac746769e3 100644 --- a/src/utils/healTypeMap.ts +++ b/src/utils/healTypes.ts @@ -23,7 +23,7 @@ type NamedTypeMap = { const hasOwn = Object.prototype.hasOwnProperty; -export function healTypeMap( +export function healTypes( originalTypeMap: NamedTypeMap, directives: ReadonlyArray, config: { @@ -215,6 +215,6 @@ function pruneTypeMap(typeMap: NamedTypeMap, directives: ReadonlyArray Date: Sat, 14 Sep 2019 19:39:09 -0400 Subject: [PATCH 059/250] refactor: organize utils rename cloneSchema to clone move healSchema to new file heal break out SchemaDirectiveVisitor and visitSchema from SchemaVisitor --- src/Interfaces.ts | 27 +- src/generate/addResolveFunctionsToSchema.ts | 2 +- src/generate/attachDirectiveResolvers.ts | 2 +- src/index.ts | 1 - src/makeExecutableSchema.ts | 2 +- src/stitching/mergeSchemas.ts | 5 +- src/test/testDirectives.ts | 14 +- src/test/testMergeSchemas.ts | 2 +- src/transforms/transformSchema.ts | 2 +- .../SchemaDirectiveVisitor.ts} | 301 +----------------- src/utils/SchemaVisitor.ts | 80 +++++ src/utils/{cloneSchema.ts => clone.ts} | 2 +- src/utils/{healTypes.ts => heal.ts} | 10 +- src/utils/index.ts | 6 +- src/utils/visitSchema.ts | 197 ++++++++++++ 15 files changed, 335 insertions(+), 318 deletions(-) rename src/{schemaVisitor.ts => utils/SchemaDirectiveVisitor.ts} (51%) create mode 100644 src/utils/SchemaVisitor.ts rename src/utils/{cloneSchema.ts => clone.ts} (98%) rename src/utils/{healTypes.ts => heal.ts} (95%) create mode 100644 src/utils/visitSchema.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index f58e401ebfb..285bed64e19 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -11,10 +11,17 @@ import { GraphQLTypeResolver, GraphQLScalarType, DocumentNode, - ASTNode, + GraphQLEnumValue, + GraphQLEnumType, + GraphQLUnionType, + GraphQLArgument, + GraphQLInputField, + GraphQLInputObjectType, + GraphQLInterfaceType, + GraphQLObjectType, } from 'graphql'; -import { SchemaDirectiveVisitor } from './schemaVisitor'; +import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; import { ApolloLink } from 'apollo-link'; @@ -137,7 +144,7 @@ export type IFieldResolver> = ( info: GraphQLResolveInfo & { mergeInfo: MergeInfo }, ) => any; -export type ITypedef = (() => ITypedef[]) | string | DocumentNode | ASTNode; +export type ITypedef = (() => ITypedef[]) | string | DocumentNode; export type ITypeDefinitions = ITypedef | ITypedef[]; export type IResolverObject = { [key: string]: @@ -269,3 +276,17 @@ export type GraphQLParseOptions = { }; export type IndexedObject = { [key: string]: V } | ReadonlyArray; + +export type VisitableSchemaType = + GraphQLSchema + | GraphQLObjectType + | GraphQLInterfaceType + | GraphQLInputObjectType + | GraphQLNamedType + | GraphQLScalarType + | GraphQLField + | GraphQLInputField + | GraphQLArgument + | GraphQLUnionType + | GraphQLEnumType + | GraphQLEnumValue; diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts index 164a061f7ce..c9fe386a72c 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolveFunctionsToSchema.ts @@ -21,7 +21,7 @@ import extendResolversFromInterfaces from './extendResolversFromInterfaces'; import forEachField from './forEachField'; import forEachDefaultValue from './forEachDefaultValue'; import { parseInputValue, serializeInputValue } from '../utils/transformInputValue'; -import { healSchema } from '../schemaVisitor'; +import { healSchema } from '../utils/heal'; function addResolveFunctionsToSchema( options: IAddResolveFunctionsToSchemaOptions | GraphQLSchema, diff --git a/src/generate/attachDirectiveResolvers.ts b/src/generate/attachDirectiveResolvers.ts index 3b6a3a50bad..ecd7b64a87f 100644 --- a/src/generate/attachDirectiveResolvers.ts +++ b/src/generate/attachDirectiveResolvers.ts @@ -1,6 +1,6 @@ import { GraphQLSchema, GraphQLField, defaultFieldResolver } from 'graphql'; import { IDirectiveResolvers } from '../Interfaces'; -import { SchemaDirectiveVisitor } from '../schemaVisitor'; +import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; function attachDirectiveResolvers( schema: GraphQLSchema, diff --git a/src/index.ts b/src/index.ts index aa658b2e8c6..cecba32033e 100755 --- a/src/index.ts +++ b/src/index.ts @@ -4,4 +4,3 @@ export * from './mock'; export * from './stitching'; export * from './transforms'; export * from './utils'; -export { SchemaDirectiveVisitor } from './schemaVisitor'; diff --git a/src/makeExecutableSchema.ts b/src/makeExecutableSchema.ts index 166e73ffb02..deedacd8378 100644 --- a/src/makeExecutableSchema.ts +++ b/src/makeExecutableSchema.ts @@ -2,7 +2,7 @@ import { defaultFieldResolver, GraphQLSchema, GraphQLFieldResolver } from 'graph import { IExecutableSchemaDefinition, ILogger } from './Interfaces'; -import { SchemaDirectiveVisitor } from './schemaVisitor'; +import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; import mergeDeep from './utils/mergeDeep'; import { diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 069b448e69b..b29ee76e639 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -37,8 +37,9 @@ import { ReplaceFieldWithFragment, } from '../transforms'; import mergeDeep from '../utils/mergeDeep'; -import { SchemaDirectiveVisitor, healSchema } from '../schemaVisitor'; -import { cloneDirective, cloneType, healTypes } from '../utils'; +import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; +import { cloneDirective, cloneType } from '../utils/clone'; +import { healSchema, healTypes } from '../utils/heal'; import { makeMergedType } from './makeMergedType'; type MergeTypeCandidate = { diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 891a456f9a4..6cb76719cda 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -1,13 +1,9 @@ import { assert } from 'chai'; -import { - makeExecutableSchema, -} from '../makeExecutableSchema'; -import { - VisitableSchemaType, - SchemaDirectiveVisitor, - SchemaVisitor, - visitSchema, -} from '../schemaVisitor'; +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { VisitableSchemaType } from '../Interfaces'; +import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; +import { SchemaVisitor } from '../utils/SchemaVisitor'; +import { visitSchema } from '../utils/visitSchema'; import { ExecutionResult, GraphQLArgument, diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index abe2f529efe..182a393f0d6 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -26,7 +26,7 @@ import { subscriptionPubSub, subscriptionPubSubTrigger, } from './testingSchemas'; -import { SchemaDirectiveVisitor } from '../schemaVisitor'; +import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; import { forAwaitEach } from 'iterall'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index 4330501817b..32ecfe00217 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -11,7 +11,7 @@ import { isSchemaExecutionConfig, } from '../Interfaces'; -import { cloneSchema } from '../utils/cloneSchema'; +import { cloneSchema } from '../utils/clone'; import { makeMergedType } from '../stitching/makeMergedType'; export function stripResolvers(schema: GraphQLSchema): void { diff --git a/src/schemaVisitor.ts b/src/utils/SchemaDirectiveVisitor.ts similarity index 51% rename from src/schemaVisitor.ts rename to src/utils/SchemaDirectiveVisitor.ts index c2dc981b6a4..9796ae6f2e3 100644 --- a/src/schemaVisitor.ts +++ b/src/utils/SchemaDirectiveVisitor.ts @@ -1,304 +1,19 @@ import { - GraphQLArgument, GraphQLDirective, - GraphQLEnumType, - GraphQLEnumValue, - GraphQLField, - GraphQLInputField, - GraphQLInputObjectType, - GraphQLInterfaceType, - GraphQLNamedType, - GraphQLObjectType, - GraphQLScalarType, GraphQLSchema, - GraphQLUnionType, DirectiveLocationEnum, } from 'graphql'; -import { - getArgumentValues, -} from 'graphql/execution/values'; -import each from './utils/each'; -import valueFromASTUntyped from './utils/valueFromASTUntyped'; -import { healTypes } from './utils/healTypes'; -import updateEachKey from './utils/updateEachKey'; - -export type VisitableSchemaType = - GraphQLSchema - | GraphQLObjectType - | GraphQLInterfaceType - | GraphQLInputObjectType - | GraphQLNamedType - | GraphQLScalarType - | GraphQLField - | GraphQLInputField - | GraphQLArgument - | GraphQLUnionType - | GraphQLEnumType - | GraphQLEnumValue - | GraphQLInputField; +import { getArgumentValues } from 'graphql/execution/values'; +import each from './each'; +import valueFromASTUntyped from './valueFromASTUntyped'; +import { healSchema } from './heal'; +import { VisitableSchemaType } from '../Interfaces'; +import { SchemaVisitor } from './SchemaVisitor'; +import { visitSchema } from './visitSchema'; 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; -} - -// Update any references to named schema types that disagree with the named -// types found in schema.getTypeMap(). -export function healSchema(schema: GraphQLSchema): GraphQLSchema { - healTypes(schema.getTypeMap(), schema.getDirectives()); - return schema; -} - - // This class represents a reusable implementation of a @directive that may // appear in a GraphQL schema written in Schema Definition Language. // @@ -568,5 +283,3 @@ function directiveLocationToVisitorMethodName(loc: DirectiveLocationEnum) { return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(); }); } - - diff --git a/src/utils/SchemaVisitor.ts b/src/utils/SchemaVisitor.ts new file mode 100644 index 00000000000..6c75637e212 --- /dev/null +++ b/src/utils/SchemaVisitor.ts @@ -0,0 +1,80 @@ +import { + GraphQLArgument, + GraphQLEnumType, + GraphQLEnumValue, + GraphQLField, + GraphQLInputField, + GraphQLInputObjectType, + GraphQLInterfaceType, + GraphQLObjectType, + GraphQLScalarType, + GraphQLSchema, + GraphQLUnionType, +} from 'graphql'; + +// 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 */ +} diff --git a/src/utils/cloneSchema.ts b/src/utils/clone.ts similarity index 98% rename from src/utils/cloneSchema.ts rename to src/utils/clone.ts index 26f5358ea08..88ba3b366d8 100644 --- a/src/utils/cloneSchema.ts +++ b/src/utils/clone.ts @@ -9,7 +9,7 @@ import { GraphQLSchema, GraphQLUnionType, } from 'graphql'; -import { healTypes } from './healTypes'; +import { healTypes } from './heal'; import isSpecifiedScalarType from './isSpecifiedScalarType'; export function cloneDirective(directive: GraphQLDirective): GraphQLDirective { diff --git a/src/utils/healTypes.ts b/src/utils/heal.ts similarity index 95% rename from src/utils/healTypes.ts rename to src/utils/heal.ts index eac746769e3..5ac31e16fab 100644 --- a/src/utils/healTypes.ts +++ b/src/utils/heal.ts @@ -11,10 +11,11 @@ import { GraphQLType, GraphQLUnionType, isNamedType, + GraphQLSchema, } from 'graphql'; import each from './each'; import updateEachKey from './updateEachKey'; -import { VisitableSchemaType } from '../schemaVisitor'; +import { VisitableSchemaType } from '../Interfaces'; import { isStub, getBuiltInForStub } from './stub'; type NamedTypeMap = { @@ -23,6 +24,13 @@ type NamedTypeMap = { const hasOwn = Object.prototype.hasOwnProperty; +// Update any references to named schema types that disagree with the named +// types found in schema.getTypeMap(). +export function healSchema(schema: GraphQLSchema): GraphQLSchema { + healTypes(schema.getTypeMap(), schema.getDirectives()); + return schema; +} + export function healTypes( originalTypeMap: NamedTypeMap, directives: ReadonlyArray, diff --git a/src/utils/index.ts b/src/utils/index.ts index f94fd56b15b..aa2f3f712c0 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,4 @@ -export { cloneSchema, cloneDirective, cloneType } from './cloneSchema'; -export { healTypes } from './healTypes'; +export { cloneSchema, cloneDirective, cloneType } from './clone'; +export { healSchema, healTypes } from './heal'; +export { SchemaVisitor } from './SchemaVisitor'; +export { SchemaDirectiveVisitor } from './SchemaDirectiveVisitor'; diff --git a/src/utils/visitSchema.ts b/src/utils/visitSchema.ts new file mode 100644 index 00000000000..5be6f9805ce --- /dev/null +++ b/src/utils/visitSchema.ts @@ -0,0 +1,197 @@ +import { + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLInterfaceType, + GraphQLObjectType, + GraphQLScalarType, + GraphQLSchema, + GraphQLUnionType, +} from 'graphql'; + +import updateEachKey from './updateEachKey'; +import { VisitableSchemaType } from '../Interfaces'; +import { SchemaVisitor } from './SchemaVisitor'; + +// 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; +} From 0854ed5b1d7b5c4874941c654cc007bc2b2721fa Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 15 Sep 2019 02:32:54 -0400 Subject: [PATCH 060/250] refactor: rename pruneTypeMap to fit with healTypes --- src/utils/heal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/heal.ts b/src/utils/heal.ts index 5ac31e16fab..ea9632a1720 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -101,7 +101,7 @@ export function healTypes( }); if (!config.skipPruning) { - pruneTypeMap(originalTypeMap, directives); + pruneTypes(originalTypeMap, directives); } function heal(type: VisitableSchemaType) { @@ -191,7 +191,7 @@ export function healTypes( } } -function pruneTypeMap(typeMap: NamedTypeMap, directives: ReadonlyArray) { +function pruneTypes(typeMap: NamedTypeMap, directives: ReadonlyArray) { const implementedInterfaces = {}; each(typeMap, (namedType, typeName) => { if (namedType instanceof GraphQLObjectType) { From cfc3db7e98ba39aad843b00faaf1f157c5cde270 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 15 Sep 2019 02:34:49 -0400 Subject: [PATCH 061/250] refactor: all visiting requires healing --- src/utils/SchemaDirectiveVisitor.ts | 5 ----- src/utils/visitSchema.ts | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/SchemaDirectiveVisitor.ts b/src/utils/SchemaDirectiveVisitor.ts index 9796ae6f2e3..ddd91afcae1 100644 --- a/src/utils/SchemaDirectiveVisitor.ts +++ b/src/utils/SchemaDirectiveVisitor.ts @@ -7,7 +7,6 @@ import { import { getArgumentValues } from 'graphql/execution/values'; import each from './each'; import valueFromASTUntyped from './valueFromASTUntyped'; -import { healSchema } from './heal'; import { VisitableSchemaType } from '../Interfaces'; import { SchemaVisitor } from './SchemaVisitor'; import { visitSchema } from './visitSchema'; @@ -198,10 +197,6 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { 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; } diff --git a/src/utils/visitSchema.ts b/src/utils/visitSchema.ts index 5be6f9805ce..de997e62629 100644 --- a/src/utils/visitSchema.ts +++ b/src/utils/visitSchema.ts @@ -11,6 +11,7 @@ import { import updateEachKey from './updateEachKey'; import { VisitableSchemaType } from '../Interfaces'; import { SchemaVisitor } from './SchemaVisitor'; +import { healSchema } from './heal'; // Generic function for visiting GraphQLSchema objects. export function visitSchema( @@ -191,6 +192,10 @@ export function visitSchema( visit(schema); + // Automatically update any references to named schema types replaced + // during the traversal, so implementors don't have to worry about that. + healSchema(schema); + // Return the original schema for convenience, even though it cannot have // been replaced or removed by the code above. return schema; From 68bf8cb0bc1be6e1d1dbcfd072a1d5130c371183 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 15 Sep 2019 04:28:19 -0400 Subject: [PATCH 062/250] feat(visitSchema): export consolidated visitSchema method Takes an object of type SchemaVisitorDef, a SchemaVisitor class instance, an array of either, or a function that returns the schema visitor spec. --- src/Interfaces.ts | 29 ++++ src/test/testSchemaGenerator.ts | 3 +- src/transforms/AddDefaultResolver.ts | 3 +- src/transforms/AddEnumAndScalarResolvers.ts | 3 +- src/transforms/ConvertEnumValues.ts | 3 +- src/transforms/FilterTypes.ts | 3 +- src/transforms/RenameTypes.ts | 4 +- src/transforms/TransformObjectFields.ts | 4 +- src/transforms/TransformRootFields.ts | 3 +- src/transforms/filterSchema.ts | 4 +- src/transforms/visitSchema.ts | 152 -------------------- src/utils/index.ts | 1 + src/utils/visitSchema.ts | 122 +++++++++++++--- 13 files changed, 154 insertions(+), 180 deletions(-) delete mode 100644 src/transforms/visitSchema.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 285bed64e19..4ae89fee1f6 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -22,6 +22,7 @@ import { } from 'graphql'; import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; +import { SchemaVisitor } from './utils/SchemaVisitor'; import { ApolloLink } from 'apollo-link'; @@ -290,3 +291,31 @@ export type VisitableSchemaType = | 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', +} + +// I couldn't make keys to be forced to be enum values +export type SchemaVisitorMap = { [key: string]: TypeVisitor }; +export type TypeVisitor = ( + type: GraphQLType, + schema: GraphQLSchema, +) => GraphQLNamedType | null | undefined; diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index c843a32e10a..2fb98824457 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -33,9 +33,10 @@ import { IExecutableSchemaDefinition, IDirectiveResolvers, NextResolverFn, + VisitSchemaKind, } from '../Interfaces'; import 'mocha'; -import { VisitSchemaKind, visitSchema } from '../transforms/visitSchema'; +import { visitSchema } from '../utils/visitSchema'; import { addResolveFunctionsToSchema } from '../generate'; interface Bird { diff --git a/src/transforms/AddDefaultResolver.ts b/src/transforms/AddDefaultResolver.ts index a75866be8d0..554c460e075 100644 --- a/src/transforms/AddDefaultResolver.ts +++ b/src/transforms/AddDefaultResolver.ts @@ -5,7 +5,8 @@ import { GraphQLFieldConfigMap } from 'graphql'; import { Transform } from './transforms'; -import { visitSchema, VisitSchemaKind } from './visitSchema'; +import { visitSchema } from '../utils/visitSchema'; +import { VisitSchemaKind } from '../Interfaces'; import { createResolveType, argsToFieldConfigArgumentMap } from '../stitching/schemaRecreation'; export default class AddDefaultResolver implements Transform { diff --git a/src/transforms/AddEnumAndScalarResolvers.ts b/src/transforms/AddEnumAndScalarResolvers.ts index 25985f35c41..df466052841 100644 --- a/src/transforms/AddEnumAndScalarResolvers.ts +++ b/src/transforms/AddEnumAndScalarResolvers.ts @@ -5,7 +5,8 @@ import { GraphQLScalarTypeConfig } from 'graphql'; import { Transform } from './transforms'; -import { visitSchema, VisitSchemaKind } from './visitSchema'; +import { visitSchema } from '../utils/visitSchema'; +import { VisitSchemaKind } from '../Interfaces'; export default class AddEnumAndScalarResolvers implements Transform { private enumValueMap: object; diff --git a/src/transforms/ConvertEnumValues.ts b/src/transforms/ConvertEnumValues.ts index e923477d268..740aed7a268 100644 --- a/src/transforms/ConvertEnumValues.ts +++ b/src/transforms/ConvertEnumValues.ts @@ -1,6 +1,7 @@ import { GraphQLSchema, GraphQLEnumType } from 'graphql'; import { Transform } from '../transforms/transforms'; -import { visitSchema, VisitSchemaKind } from '../transforms/visitSchema'; +import { visitSchema } from '../utils/visitSchema'; +import { VisitSchemaKind } from '../Interfaces'; // Transformation used to modifiy `GraphQLEnumType` values in a schema. export default class ConvertEnumValues implements Transform { diff --git a/src/transforms/FilterTypes.ts b/src/transforms/FilterTypes.ts index 8d8196bd98d..6f436ca9480 100644 --- a/src/transforms/FilterTypes.ts +++ b/src/transforms/FilterTypes.ts @@ -2,7 +2,8 @@ import { GraphQLSchema, GraphQLNamedType } from 'graphql'; import { Transform } from '../transforms/transforms'; -import { visitSchema, VisitSchemaKind } from '../transforms/visitSchema'; +import { visitSchema } from '../utils/visitSchema'; +import { VisitSchemaKind } from '../Interfaces'; export default class FilterTypes implements Transform { private filter: (type: GraphQLNamedType) => boolean; diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index ab2d5956d50..f68d73a6dff 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -7,9 +7,9 @@ import { GraphQLScalarType, } from 'graphql'; import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; -import { Request, Result } from '../Interfaces'; +import { Request, Result, VisitSchemaKind } from '../Interfaces'; import { Transform } from '../transforms/transforms'; -import { visitSchema, VisitSchemaKind } from '../transforms/visitSchema'; +import { visitSchema } from '../utils/visitSchema'; export type RenameOptions = { renameBuiltins: boolean; diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index 5a54738ba8b..fa1d3fe5e5f 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -16,9 +16,9 @@ import { FragmentDefinitionNode } from 'graphql'; import isEmptyObject from '../utils/isEmptyObject'; -import { Request } from '../Interfaces'; +import { Request, VisitSchemaKind } from '../Interfaces'; import { Transform } from './transforms'; -import { visitSchema, VisitSchemaKind } from './visitSchema'; +import { visitSchema } from '../utils/visitSchema'; import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; export type ObjectFieldTransformer = ( diff --git a/src/transforms/TransformRootFields.ts b/src/transforms/TransformRootFields.ts index 92b46e29cc9..db603c8369b 100644 --- a/src/transforms/TransformRootFields.ts +++ b/src/transforms/TransformRootFields.ts @@ -7,7 +7,8 @@ import { } from 'graphql'; import isEmptyObject from '../utils/isEmptyObject'; import { Transform } from './transforms'; -import { visitSchema, VisitSchemaKind } from './visitSchema'; +import { visitSchema } from '../utils/visitSchema'; +import { VisitSchemaKind } from '../Interfaces'; import { createResolveType, fieldToFieldConfig, diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index d1cab70e3e7..952d113ad44 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -7,8 +7,8 @@ import { GraphQLUnionType, GraphQLType, } from 'graphql'; -import { GraphQLSchemaWithTransforms } from '../Interfaces'; -import { visitSchema, VisitSchemaKind } from './visitSchema'; +import { GraphQLSchemaWithTransforms, VisitSchemaKind } from '../Interfaces'; +import { visitSchema } from '../utils/visitSchema'; export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', diff --git a/src/transforms/visitSchema.ts b/src/transforms/visitSchema.ts deleted file mode 100644 index 89a70545681..00000000000 --- a/src/transforms/visitSchema.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { - GraphQLEnumType, - GraphQLInputObjectType, - GraphQLInterfaceType, - GraphQLObjectType, - GraphQLScalarType, - GraphQLSchema, - GraphQLType, - GraphQLUnionType, - GraphQLNamedType, - isNamedType, -} from 'graphql'; -import { - cloneType, - cloneDirective, - healTypes, -} from '../utils'; -import { stripResolvers } from './transformSchema'; - -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', -} -// I couldn't make keys to be forced to be enum values -export type SchemaVisitor = { [key: string]: TypeVisitor }; -export type TypeVisitor = ( - type: GraphQLType, - schema: GraphQLSchema, -) => GraphQLNamedType | null | undefined; - -export function visitSchema( - schema: GraphQLSchema, - visitor: SchemaVisitor, - wrap?: boolean, // deprecated, use wrapSchema -) { - const types: { [key: string]: GraphQLNamedType } = {}; - - const queryType = schema.getQueryType(); - const mutationType = schema.getMutationType(); - const subscriptionType = schema.getSubscriptionType(); - - const typeMap = schema.getTypeMap(); - Object.keys(typeMap).map((typeName: string) => { - const type = typeMap[typeName]; - if (isNamedType(type) && type.name.slice(0, 2) !== '__') { - const specifiers = getTypeSpecifiers(type, schema); - const typeVisitor = getVisitor(visitor, specifiers); - if (typeVisitor) { - const result = typeVisitor(type, schema); - if (typeof result === 'undefined') { - types[typeName] = cloneType(type); - } else if (result === null) { - types[typeName] = null; - } else { - types[typeName] = cloneType(result); - } - } else { - types[typeName] = cloneType(type); - } - } - }); - - const directives = schema.getDirectives().map(d => cloneDirective(d)); - - healTypes(types, directives); - - const newSchema = new GraphQLSchema({ - ...schema.toConfig(), - query: queryType ? (types[queryType.name] as GraphQLObjectType) : null, - mutation: mutationType ? (types[mutationType.name] as GraphQLObjectType) : null, - subscription: subscriptionType ? (types[subscriptionType.name] as GraphQLObjectType) : null, - types: Object.keys(types).map(name => types[name]), - directives, - }); - - if (wrap) { - stripResolvers(newSchema); - } - - return newSchema; -} - -function getTypeSpecifiers( - type: GraphQLType, - schema: GraphQLSchema, -): Array { - const specifiers = [VisitSchemaKind.TYPE]; - if (type instanceof GraphQLObjectType) { - specifiers.push( - VisitSchemaKind.COMPOSITE_TYPE, - VisitSchemaKind.OBJECT_TYPE, - ); - const query = schema.getQueryType(); - const mutation = schema.getMutationType(); - const subscription = schema.getSubscriptionType(); - if (type === query) { - specifiers.push(VisitSchemaKind.ROOT_OBJECT, VisitSchemaKind.QUERY); - } else if (type === mutation) { - specifiers.push(VisitSchemaKind.ROOT_OBJECT, VisitSchemaKind.MUTATION); - } else if (type === subscription) { - specifiers.push( - VisitSchemaKind.ROOT_OBJECT, - VisitSchemaKind.SUBSCRIPTION, - ); - } - } else if (type instanceof GraphQLInputObjectType) { - specifiers.push(VisitSchemaKind.INPUT_OBJECT_TYPE); - } else if (type instanceof GraphQLInterfaceType) { - specifiers.push( - VisitSchemaKind.COMPOSITE_TYPE, - VisitSchemaKind.ABSTRACT_TYPE, - VisitSchemaKind.INTERFACE_TYPE, - ); - } else if (type instanceof GraphQLUnionType) { - specifiers.push( - VisitSchemaKind.COMPOSITE_TYPE, - VisitSchemaKind.ABSTRACT_TYPE, - VisitSchemaKind.UNION_TYPE, - ); - } else if (type instanceof GraphQLEnumType) { - specifiers.push(VisitSchemaKind.ENUM_TYPE); - } else if (type instanceof GraphQLScalarType) { - specifiers.push(VisitSchemaKind.SCALAR_TYPE); - } - - return specifiers; -} - -function getVisitor( - visitor: SchemaVisitor, - specifiers: Array, -): TypeVisitor | null { - let typeVisitor = null; - const stack = [...specifiers]; - while (!typeVisitor && stack.length > 0) { - const next = stack.pop(); - typeVisitor = visitor[next]; - } - - return typeVisitor; -} diff --git a/src/utils/index.ts b/src/utils/index.ts index aa2f3f712c0..2b01b5c4ea1 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,3 +2,4 @@ export { cloneSchema, cloneDirective, cloneType } from './clone'; export { healSchema, healTypes } from './heal'; export { SchemaVisitor } from './SchemaVisitor'; export { SchemaDirectiveVisitor } from './SchemaDirectiveVisitor'; +export { visitSchema } from './visitSchema'; diff --git a/src/utils/visitSchema.ts b/src/utils/visitSchema.ts index de997e62629..4e3e5393eed 100644 --- a/src/utils/visitSchema.ts +++ b/src/utils/visitSchema.ts @@ -6,12 +6,16 @@ import { GraphQLScalarType, GraphQLSchema, GraphQLUnionType, + isNamedType, + GraphQLType, } from 'graphql'; import updateEachKey from './updateEachKey'; -import { VisitableSchemaType } from '../Interfaces'; -import { SchemaVisitor } from './SchemaVisitor'; +import { VisitableSchemaType, VisitorSelector, VisitSchemaKind, SchemaVisitorMap, TypeVisitor } from '../Interfaces'; import { healSchema } from './heal'; +import { SchemaVisitor } from './SchemaVisitor'; +import each from './each'; +import { cloneSchema } from './clone'; // Generic function for visiting GraphQLSchema objects. export function visitSchema( @@ -29,11 +33,17 @@ export function visitSchema( // 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[], + visitorOrVisitorSelector: + VisitorSelector | + Array | + SchemaVisitorMap | + SchemaVisitorMap, ): GraphQLSchema { + const visitorSelector = + typeof visitorOrVisitorSelector === 'function' ? + visitorOrVisitorSelector : + () => visitorOrVisitorSelector; + // Helper function that calls visitorSelector and applies the resulting // visitors to the given type, with arguments [type, ...args]. function callMethod( @@ -41,8 +51,26 @@ export function visitSchema( type: T, ...args: any[] ): T { - visitorSelector(type, methodName).every(visitor => { - const newType = visitor[methodName](type, ...args); + let visitors = visitorSelector(type, methodName); + visitors = Array.isArray(visitors) ? visitors : [visitors]; + + visitors.every(visitorOrVisitorDef => { + let newType; + if (visitorOrVisitorDef instanceof SchemaVisitor) { + newType = visitorOrVisitorDef[methodName](type, ...args); + } else if ( + isNamedType(type) && ( + methodName === 'visitScalar' || + methodName === 'visitEnum' || + methodName === 'visitObject' || + methodName === 'visitInputObject' || + methodName === 'visitUnion' || + methodName === 'visitInterface' + )) { + const specifiers = getTypeSpecifiers(type, schema); + const typeVisitor = getVisitor(visitorOrVisitorDef, specifiers); + newType = typeVisitor ? typeVisitor(type, schema) : undefined; + } if (typeof newType === 'undefined') { // Keep going without modifying type. @@ -82,13 +110,14 @@ export function visitSchema( // for SchemaVisitor subclasses that rely on the original schema object. callMethod('visitSchema', type); - updateEachKey(type.getTypeMap(), (namedType, typeName) => { + each(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); + // subclass of GraphQLNamedType we found in the type map. + // We do not use updateEachKey because we want to preserve + // deleted types in the typeMap so that other types that reference + // the deleted types can be healed. + type.getTypeMap()[typeName] = visit(namedType); } }); @@ -196,7 +225,68 @@ export function visitSchema( // during the traversal, so implementors don't have to worry about that. healSchema(schema); - // Return the original schema for convenience, even though it cannot have - // been replaced or removed by the code above. - return schema; + // Return a cloned version of the schema as a workaround for constructor + // initiliazation not currently healed, e.g. the stored implementation map. + return cloneSchema(schema); +} + + +function getTypeSpecifiers( + type: GraphQLType, + schema: GraphQLSchema, +): Array { + const specifiers = [VisitSchemaKind.TYPE]; + if (type instanceof GraphQLObjectType) { + specifiers.push( + VisitSchemaKind.COMPOSITE_TYPE, + VisitSchemaKind.OBJECT_TYPE, + ); + const query = schema.getQueryType(); + const mutation = schema.getMutationType(); + const subscription = schema.getSubscriptionType(); + if (type === query) { + specifiers.push(VisitSchemaKind.ROOT_OBJECT, VisitSchemaKind.QUERY); + } else if (type === mutation) { + specifiers.push(VisitSchemaKind.ROOT_OBJECT, VisitSchemaKind.MUTATION); + } else if (type === subscription) { + specifiers.push( + VisitSchemaKind.ROOT_OBJECT, + VisitSchemaKind.SUBSCRIPTION, + ); + } + } else if (type instanceof GraphQLInputObjectType) { + specifiers.push(VisitSchemaKind.INPUT_OBJECT_TYPE); + } else if (type instanceof GraphQLInterfaceType) { + specifiers.push( + VisitSchemaKind.COMPOSITE_TYPE, + VisitSchemaKind.ABSTRACT_TYPE, + VisitSchemaKind.INTERFACE_TYPE, + ); + } else if (type instanceof GraphQLUnionType) { + specifiers.push( + VisitSchemaKind.COMPOSITE_TYPE, + VisitSchemaKind.ABSTRACT_TYPE, + VisitSchemaKind.UNION_TYPE, + ); + } else if (type instanceof GraphQLEnumType) { + specifiers.push(VisitSchemaKind.ENUM_TYPE); + } else if (type instanceof GraphQLScalarType) { + specifiers.push(VisitSchemaKind.SCALAR_TYPE); + } + + return specifiers; +} + +function getVisitor( + visitorDef: SchemaVisitorMap, + specifiers: Array, +): TypeVisitor { + let typeVisitor = null; + const stack = [...specifiers]; + while (!typeVisitor && stack.length > 0) { + const next = stack.pop(); + typeVisitor = visitorDef[next]; + } + + return typeVisitor; } From 1b49371fed272d28401bc873ca9df19fe741e1ba Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 15 Sep 2019 04:31:39 -0400 Subject: [PATCH 063/250] refactor: remove unnecessary transforms --- src/transforms/AddDefaultResolver.ts | 62 ------------- src/transforms/AddEnumAndScalarResolvers.ts | 97 --------------------- src/transforms/ConvertEnumValues.ts | 61 ------------- src/transforms/index.ts | 3 - 4 files changed, 223 deletions(-) delete mode 100644 src/transforms/AddDefaultResolver.ts delete mode 100644 src/transforms/AddEnumAndScalarResolvers.ts delete mode 100644 src/transforms/ConvertEnumValues.ts diff --git a/src/transforms/AddDefaultResolver.ts b/src/transforms/AddDefaultResolver.ts deleted file mode 100644 index 554c460e075..00000000000 --- a/src/transforms/AddDefaultResolver.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - GraphQLSchema, - GraphQLFieldResolver, - GraphQLObjectType, - GraphQLFieldConfigMap -} from 'graphql'; -import { Transform } from './transforms'; -import { visitSchema } from '../utils/visitSchema'; -import { VisitSchemaKind } from '../Interfaces'; -import { createResolveType, argsToFieldConfigArgumentMap } from '../stitching/schemaRecreation'; - -export default class AddDefaultResolver implements Transform { - private defaultFieldResolver: GraphQLFieldResolver; - - constructor(defaultFieldResolver: GraphQLFieldResolver) { - this.defaultFieldResolver = defaultFieldResolver; - } - - public transformSchema(schema: GraphQLSchema): GraphQLSchema { - const { defaultFieldResolver } = this; - - if (!defaultFieldResolver) { - return schema; - } - - const resolveType = createResolveType((name, type) => type); - const transformedSchema = visitSchema(schema, { - [VisitSchemaKind.OBJECT_TYPE](type: GraphQLObjectType) { - const fields = type.getFields(); - const interfaces = type.getInterfaces(); - - const newFields: GraphQLFieldConfigMap = {}; - Object.keys(fields).forEach(name => { - const field = fields[name]; - const resolvedType = resolveType(field.type); - if (resolvedType !== null) { - newFields[name] = { - type: resolvedType, - args: argsToFieldConfigArgumentMap(field.args, resolveType), - resolve: field.resolve ? field.resolve : defaultFieldResolver, - subscribe: field.subscribe, - description: field.description, - deprecationReason: field.deprecationReason, - astNode: field.astNode, - }; - } - }); - - return new GraphQLObjectType({ - name: type.name, - description: type.description, - astNode: type.astNode, - isTypeOf: type.isTypeOf, - fields: () => newFields, - interfaces: () => interfaces.map(iface => resolveType(iface)), - }); - }, - }); - - return transformedSchema; - } -} diff --git a/src/transforms/AddEnumAndScalarResolvers.ts b/src/transforms/AddEnumAndScalarResolvers.ts deleted file mode 100644 index df466052841..00000000000 --- a/src/transforms/AddEnumAndScalarResolvers.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { - GraphQLSchema, - GraphQLEnumType, - GraphQLScalarType, - GraphQLScalarTypeConfig -} from 'graphql'; -import { Transform } from './transforms'; -import { visitSchema } from '../utils/visitSchema'; -import { VisitSchemaKind } from '../Interfaces'; - -export default class AddEnumAndScalarResolvers implements Transform { - private enumValueMap: object; - private scalarTypeMap: object; - - constructor(enumValueMap: object, scalarTypeMap: object) { - this.enumValueMap = enumValueMap; - this.scalarTypeMap = scalarTypeMap; - } - - public transformSchema(schema: GraphQLSchema): GraphQLSchema { - const { enumValueMap, scalarTypeMap } = this; - const enumTypeMap = Object.create(null); - - if (!Object.keys(enumValueMap).length && !Object.keys(scalarTypeMap).length) { - return schema; - } - - // Build enum types from the resolver map. - Object.keys(enumValueMap).forEach(typeName => { - const enumType: GraphQLEnumType = schema.getType(typeName) as GraphQLEnumType; - const externalToInternalValueMap = enumValueMap[enumType.name]; - - if (externalToInternalValueMap) { - const values = enumType.getValues(); - const newValues = {}; - values.forEach(value => { - const newValue = Object.keys(externalToInternalValueMap).includes( - value.name, - ) - ? externalToInternalValueMap[value.name] - : value.name; - newValues[value.name] = { - value: newValue, - deprecationReason: value.deprecationReason, - description: value.description, - astNode: value.astNode, - }; - }); - - enumTypeMap[typeName] = new GraphQLEnumType({ - name: enumType.name, - description: enumType.description, - astNode: enumType.astNode, - values: newValues, - }); - } - }); - - //Build scalar types from resolver map (if necessary, see below). - Object.keys(scalarTypeMap).forEach(typeName => { - const resolverValue = scalarTypeMap[typeName]; - - // Below is necessary as legacy code for scalar type specification allowed - // hardcoding within the resolver an object with fields '__serialize', - // '__parse', and '__parseLiteral', see examples in testMocking.ts. - if (!(resolverValue instanceof GraphQLScalarType)) { - const scalarType: GraphQLScalarType = schema.getType(typeName) as GraphQLScalarType; - - const scalarTypeConfig = {}; - Object.keys(resolverValue).forEach(key => { - if (key.startsWith('__')) { - scalarTypeConfig[key.substring(2)] = resolverValue[key]; - } else { - scalarTypeConfig[key] = resolverValue[key]; - } - }); - - scalarTypeMap[typeName] = new GraphQLScalarType({ - ...scalarType, - ...scalarTypeConfig - } as GraphQLScalarTypeConfig); - } - }); - - // type recreation within visitSchema will automatically adjust default - // values on fields. - const transformedSchema = visitSchema(schema, { - [VisitSchemaKind.SCALAR_TYPE](type: GraphQLScalarType) { - return scalarTypeMap[type.name]; - }, - [VisitSchemaKind.ENUM_TYPE](type: GraphQLEnumType) { - return enumTypeMap[type.name]; }, - }); - - return transformedSchema; - } -} diff --git a/src/transforms/ConvertEnumValues.ts b/src/transforms/ConvertEnumValues.ts deleted file mode 100644 index 740aed7a268..00000000000 --- a/src/transforms/ConvertEnumValues.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { GraphQLSchema, GraphQLEnumType } from 'graphql'; -import { Transform } from '../transforms/transforms'; -import { visitSchema } from '../utils/visitSchema'; -import { VisitSchemaKind } from '../Interfaces'; - -// Transformation used to modifiy `GraphQLEnumType` values in a schema. -export default class ConvertEnumValues implements Transform { - // Maps current enum values to their new values. - // e.g. { Color: { 'RED': '#EA3232' } } - private enumValueMap: object; - - constructor(enumValueMap: object) { - this.enumValueMap = enumValueMap; - } - - // Walk a schema looking for `GraphQLEnumType` types. If found, and - // matching types have been identified in `this.enumValueMap`, create new - // `GraphQLEnumType` types using the `this.enumValueMap` specified new - // values, and return them in the new schema. - public transformSchema(schema: GraphQLSchema): GraphQLSchema { - const { enumValueMap } = this; - if (!enumValueMap || Object.keys(enumValueMap).length === 0) { - return schema; - } - - const transformedSchema = visitSchema(schema, { - [VisitSchemaKind.ENUM_TYPE](enumType: GraphQLEnumType) { - const externalToInternalValueMap = enumValueMap[enumType.name]; - - if (externalToInternalValueMap) { - const values = enumType.getValues(); - const newValues = {}; - values.forEach(value => { - const newValue = Object.keys(externalToInternalValueMap).includes( - value.name, - ) - ? externalToInternalValueMap[value.name] - : value.name; - newValues[value.name] = { - value: newValue, - deprecationReason: value.deprecationReason, - description: value.description, - astNode: value.astNode, - }; - }); - - return new GraphQLEnumType({ - name: enumType.name, - description: enumType.description, - astNode: enumType.astNode, - values: newValues, - }); - } - - return enumType; - }, - }); - - return transformedSchema; - } -} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 05126bcf25a..b97636c0551 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -4,9 +4,6 @@ export { Transform }; export { default as filterSchema } from './filterSchema'; export { default as transformSchema, wrapSchema } from './transformSchema'; -export { default as AddEnumAndScalarResolvers } from './AddEnumAndScalarResolvers'; -export { default as AddDefaultResolver } from './AddDefaultResolver'; - export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErrors'; export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; From bf39855f6fbc8ff4aa302b635aa8dafb85c7fdcd Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 15 Sep 2019 14:30:16 -0400 Subject: [PATCH 064/250] fix(cloneSchema): cloneSchema should preserve extensions --- src/utils/clone.ts | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/utils/clone.ts b/src/utils/clone.ts index 88ba3b366d8..fd34ea98ae8 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -56,25 +56,15 @@ export function cloneSchema(schema: GraphQLSchema): GraphQLSchema { healTypes(newTypeMap, newDirectives); - const selectors = { - query: 'getQueryType', - mutation: 'getMutationType', - subscription: 'getSubscriptionType', - }; - - const rootTypes = Object.create(null); - - Object.keys(selectors).forEach(op => { - const rootType = schema[selectors[op]](); - if (rootType) { - rootTypes[op] = newTypeMap[rootType.name]; - } - }); + const query = schema.getQueryType(); + const mutation = schema.getMutationType(); + const subscription = schema.getSubscriptionType(); return new GraphQLSchema({ - query: rootTypes.query, - mutation: rootTypes.mutation, - subscription: rootTypes.subscription, + ...schema.toConfig(), + query: query ? newTypeMap[query.name] : undefined, + mutation: mutation ? newTypeMap[mutation.name] : undefined, + subscription: subscription ? newTypeMap[subscription.name] : undefined, types: Object.keys(newTypeMap).map(typeName => newTypeMap[typeName]), directives: newDirectives, }); From 0150e02ba74228be0ce74c743bf3fb8f2e9798ec Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 16 Sep 2019 21:34:23 -0400 Subject: [PATCH 065/250] fix(visitSchema): visitSchema must reinitialize private variables when updating interfaces. --- src/test/testDirectives.ts | 23 +++++++---------------- src/utils/visitSchema.ts | 9 ++++++--- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 6cb76719cda..3a190e91c57 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -24,6 +24,7 @@ import { GraphQLList, GraphQLUnionType, GraphQLInt, + GraphQLOutputType, } from 'graphql'; import formatDate = require('dateformat'); @@ -202,24 +203,19 @@ describe('@directives', () => { it('can be implemented with SchemaDirectiveVisitor', () => { const visited: Set = new Set; const schema = makeExecutableSchema({ typeDefs }); - let visitCount = 0; SchemaDirectiveVisitor.visitSchemaDirectives(schema, { // The directive subclass can be defined anonymously inline! queryTypeDirective: class extends SchemaDirectiveVisitor { public static description = 'A @directive for query object types'; public visitObject(object: GraphQLObjectType) { + assert.strictEqual(object, schema.getQueryType()); visited.add(object); - visitCount++; } }, }); assert.strictEqual(visited.size, 1); - assert.strictEqual(visitCount, 1); - visited.forEach(object => { - assert.strictEqual(object, schema.getType('Query')); - }); }); it('can visit the schema itself', () => { @@ -1081,14 +1077,12 @@ describe('@directives', () => { }); it('automatically updates references to changed types', () => { - let HumanType: GraphQLObjectType = null; - const schema = makeExecutableSchema({ typeDefs, schemaDirectives: { objectTypeDirective: class extends SchemaDirectiveVisitor { public visitObject(object: GraphQLObjectType) { - return HumanType = Object.create(object, { + return Object.create(object, { name: { value: 'Human' } }); } @@ -1099,19 +1093,19 @@ describe('@directives', () => { const Query = schema.getType('Query') as GraphQLObjectType; const peopleType = Query.getFields().people.type; if (peopleType instanceof GraphQLList) { - assert.strictEqual(peopleType.ofType, HumanType); + assert.strictEqual(peopleType.ofType, schema.getType('Human')); } else { throw new Error('Query.people not a GraphQLList type'); } const Mutation = schema.getType('Mutation') as GraphQLObjectType; const addPersonResultType = Mutation.getFields().addPerson.type; - assert.strictEqual(addPersonResultType, HumanType); + assert.strictEqual(addPersonResultType, schema.getType('Human') as GraphQLOutputType); const WhateverUnion = schema.getType('WhateverUnion') as GraphQLUnionType; const found = WhateverUnion.getTypes().some(type => { if (type.name === 'Human') { - assert.strictEqual(type, HumanType); + assert.strictEqual(type, schema.getType('Human')); return true; } }); @@ -1209,7 +1203,7 @@ describe('@directives', () => { it('does not enforce query directive locations (issue #680)', () => { const visited = new Set(); - const schema = makeExecutableSchema({ + makeExecutableSchema({ typeDefs: ` directive @hasScope(scope: [String]) on QUERY | FIELD | OBJECT @@ -1228,9 +1222,6 @@ describe('@directives', () => { }); assert.strictEqual(visited.size, 1); - visited.forEach(object => { - assert.strictEqual(schema.getType('Query'), object); - }); }); it('allows multiple directives when first replaces type (issue #851)', () => { diff --git a/src/utils/visitSchema.ts b/src/utils/visitSchema.ts index 4e3e5393eed..8f8668a2e28 100644 --- a/src/utils/visitSchema.ts +++ b/src/utils/visitSchema.ts @@ -225,9 +225,12 @@ export function visitSchema( // during the traversal, so implementors don't have to worry about that. healSchema(schema); - // Return a cloned version of the schema as a workaround for constructor - // initiliazation not currently healed, e.g. the stored implementation map. - return cloneSchema(schema); + // Reconstruct the schema to reinitialize private variables + // e.g. the stored implementation map. + Object.assign(schema, cloneSchema(schema)); + + // Return schema for convenience, even though schema parameter has all updated types. + return schema; } From 0b2ddb8e285b5cea76e752354ded170694a1b653 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 16 Sep 2019 21:34:42 -0400 Subject: [PATCH 066/250] fix(filterSchema): should not modify original schema. --- src/transforms/filterSchema.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index 952d113ad44..9a341e71326 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -9,6 +9,7 @@ import { } from 'graphql'; import { GraphQLSchemaWithTransforms, VisitSchemaKind } from '../Interfaces'; import { visitSchema } from '../utils/visitSchema'; +import { cloneSchema } from '../utils/clone'; export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -31,7 +32,7 @@ export default function filterSchema({ typeFilter?: (typeName: string, type: GraphQLType) => boolean; fieldFilter?: (typeName: string, fieldName: string) => boolean; }): GraphQLSchemaWithTransforms { - const filteredSchema: GraphQLSchemaWithTransforms = visitSchema(schema, { + const filteredSchema: GraphQLSchemaWithTransforms = visitSchema(cloneSchema(schema), { [VisitSchemaKind.QUERY]: (type: GraphQLObjectType) => { return filterRootFields(type, 'Query', rootFieldFilter); }, From 0eec7b5f3d44fa7d50ef49843d4704cd44bbf224 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 17 Sep 2019 22:01:11 -0400 Subject: [PATCH 067/250] fix(stitching): observabeToAsyncIterator should preserve graphql errors --- src/stitching/makeRemoteExecutableSchema.ts | 8 ++++++-- src/stitching/observableToAsyncIterable.ts | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 199acf2eb14..c76d8898087 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -29,6 +29,8 @@ import resolveParentFromTypename from './resolveFromParentTypename'; import defaultMergedResolver from './defaultMergedResolver'; import { checkResultAndHandleErrors } from './checkResultAndHandleErrors'; import { observableToAsyncIterable } from './observableToAsyncIterable'; +import mapAsyncIterator from './mapAsyncIterator'; +import { Options as PrintSchemaOptions } from 'graphql/utilities/schemaPrinter'; export type ResolverFn = ( rootValue?: any, @@ -180,7 +182,9 @@ function createSubscriptionResolver(name: string, link: ApolloLink): ResolverFn }; const observable = execute(link, operation); - - return observableToAsyncIterable(observable); + const originalAsyncIterator = observableToAsyncIterable(observable); + return mapAsyncIterator(originalAsyncIterator, result => ({ + [info.fieldName]: checkResultAndHandleErrors(result, info), + })); }; } diff --git a/src/stitching/observableToAsyncIterable.ts b/src/stitching/observableToAsyncIterable.ts index 7b0cd203e02..02b27447479 100644 --- a/src/stitching/observableToAsyncIterable.ts +++ b/src/stitching/observableToAsyncIterable.ts @@ -12,11 +12,11 @@ export function observableToAsyncIterable( let listening = true; - const pushValue = ({ data }: any) => { + const pushValue = (value: any) => { if (pullQueue.length !== 0) { - pullQueue.shift()({ value: data, done: false }); + pullQueue.shift()({ value, done: false }); } else { - pushQueue.push({ value: data }); + pushQueue.push({ value }); } }; From aa5728b78f90ae9a2f8c97e31a46eb9efd89d860 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 18 Sep 2019 20:37:17 -0400 Subject: [PATCH 068/250] fix(release): broken npmignore due to incomplete npmignore whitelist --- .npmignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.npmignore b/.npmignore index e0e4eaecc5a..6ed2c45714a 100644 --- a/.npmignore +++ b/.npmignore @@ -1,9 +1,10 @@ * !dist/ !dist/* +!dist/generate/* !dist/stitching/* !dist/transforms/* -!dist/generate/* +!dist/utils/* !package.json !*.md !*.png From 3965da54452f16f7a63624dec884ee9a9c8b5c1e Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 19 Sep 2019 00:25:49 -0400 Subject: [PATCH 069/250] refactor(delegateToSchema): refactor away delegateToRemoteSchema Allow delegateToSchema to take remote execution options or a schema execution config containg remote execution options. Remove unnecessary RemoteSchemaExecutionConfig type. --- src/Interfaces.ts | 23 ++--- src/stitching/delegateToRemoteSchema.ts | 60 ------------- src/stitching/delegateToSchema.ts | 109 ++++++++++++++++++------ src/stitching/index.ts | 2 - src/stitching/mergeSchemas.ts | 31 +++---- src/stitching/resolvers.ts | 15 +--- src/test/testAlternateMergeSchemas.ts | 7 +- src/test/testMergeSchemas.ts | 65 +++++--------- src/test/testingSchemas.ts | 8 +- 9 files changed, 132 insertions(+), 188 deletions(-) delete mode 100644 src/stitching/delegateToRemoteSchema.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 4ae89fee1f6..b54742a69fb 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -76,35 +76,24 @@ export type Dispatcher = (context: any) => ApolloLink | Fetcher; export type SchemaExecutionConfig = { schema: GraphQLSchemaWithTransforms; -}; - -export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array }; - -export type RemoteSchemaExecutionConfig = { - schema: GraphQLSchemaWithTransforms; link?: ApolloLink; fetcher?: Fetcher; dispatcher?: Dispatcher; }; +export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array }; + export function isSchemaExecutionConfig( schema: string | GraphQLSchema | SchemaExecutionConfig | DocumentNode | Array ): schema is SchemaExecutionConfig { return !!(schema as SchemaExecutionConfig).schema; } -export function isRemoteSchemaExecutionConfig( - schema: GraphQLSchema | SchemaExecutionConfig -): schema is RemoteSchemaExecutionConfig { - return ( - !!(schema as RemoteSchemaExecutionConfig).dispatcher || - !!(schema as RemoteSchemaExecutionConfig).link || - !!(schema as RemoteSchemaExecutionConfig).fetcher - ); -} - export interface IDelegateToSchemaOptions { - schema: GraphQLSchema; + schema: GraphQLSchema | SchemaExecutionConfig; + link?: ApolloLink; + fetcher?: Fetcher; + dispatcher?: Dispatcher; operation: Operation; fieldName: string; args?: { [key: string]: any }; diff --git a/src/stitching/delegateToRemoteSchema.ts b/src/stitching/delegateToRemoteSchema.ts deleted file mode 100644 index ffc596ce4f4..00000000000 --- a/src/stitching/delegateToRemoteSchema.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - IDelegateToSchemaOptions, - RemoteSchemaExecutionConfig, - Fetcher -} from '../Interfaces'; -import { ApolloLink } from 'apollo-link'; -import { observableToAsyncIterable } from './observableToAsyncIterable'; -import linkToFetcher, { execute } from './linkToFetcher'; -import delegateToSchema from './delegateToSchema'; - -export default function delegateToRemoteSchema( - options: IDelegateToSchemaOptions & RemoteSchemaExecutionConfig -): Promise { - if (options.operation === 'query' || options.operation === 'mutation') { - let fetcher: Fetcher; - if (options.dispatcher) { - const dynamicLinkOrFetcher = options.dispatcher(context); - fetcher = (typeof dynamicLinkOrFetcher === 'function') ? - dynamicLinkOrFetcher : - linkToFetcher(dynamicLinkOrFetcher); - } else if (options.link) { - fetcher = linkToFetcher(options.link); - } else { - fetcher = options.fetcher; - } - - if (!options.executor) { - options.executor = ({ document, context, variables }) => fetcher({ - query: document, - variables, - context: { graphqlContext: context } - }); - } - - return delegateToSchema(options); - } - - if (options.operation === 'subscription') { - let link: ApolloLink; - if (options.dispatcher) { - link = options.dispatcher(context) as ApolloLink; - } else { - link = options.link; - } - - if (!options.subscriber) { - options.subscriber = ({ document, context, variables }) => { - const operation = { - query: document, - variables, - context: { graphqlContext: context } - }; - const observable = execute(link, operation); - return observableToAsyncIterable(observable); - }; - } - - return delegateToSchema(options); - } -} diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 3eb5c30434c..4354566fb50 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -20,6 +20,9 @@ import { IDelegateToSchemaOptions, Operation, Request, + Fetcher, + Delegator, + isSchemaExecutionConfig, } from '../Interfaces'; import { @@ -35,6 +38,10 @@ import mapAsyncIterator from './mapAsyncIterator'; import ExpandAbstractTypes from '../transforms/ExpandAbstractTypes'; import ReplaceFieldWithFragment from '../transforms/ReplaceFieldWithFragment'; +import { ApolloLink, execute as executeLink } from 'apollo-link'; +import linkToFetcher from './linkToFetcher'; +import { observableToAsyncIterable } from './observableToAsyncIterable'; + export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, ...args: any[] @@ -51,6 +58,17 @@ export default function delegateToSchema( async function delegateToSchemaImplementation( options: IDelegateToSchemaOptions, ): Promise { + const { schema: schemaOrSchemaConfig } = options; + let targetSchema; + if (isSchemaExecutionConfig(schemaOrSchemaConfig)) { + targetSchema = schemaOrSchemaConfig.schema; + options.link = schemaOrSchemaConfig.link; + options.fetcher = schemaOrSchemaConfig.fetcher; + options.dispatcher = schemaOrSchemaConfig.dispatcher; + } else { + targetSchema = schemaOrSchemaConfig; + } + const { info, args = {} } = options; const operation = options.operation || info.operation.operation; const rawDocument: DocumentNode = createDocument( @@ -71,41 +89,33 @@ async function delegateToSchemaImplementation( let transforms = [ ...(options.transforms || []), - new ExpandAbstractTypes(info.schema, options.schema), + new ExpandAbstractTypes(info.schema, targetSchema), ]; if (info.mergeInfo && info.mergeInfo.fragments) { transforms.push( - new ReplaceFieldWithFragment(options.schema, info.mergeInfo.fragments), + new ReplaceFieldWithFragment(targetSchema, info.mergeInfo.fragments), ); } transforms = transforms.concat([ - new AddArgumentsAsVariables(options.schema, args), - new FilterToSchema(options.schema), - new AddTypenameToAbstract(options.schema), + new AddArgumentsAsVariables(targetSchema, args), + new FilterToSchema(targetSchema), + new AddTypenameToAbstract(targetSchema), new CheckResultAndHandleErrors(info, options.fieldName), ]); const processedRequest = applyRequestTransforms(rawRequest, transforms); if (!options.skipValidation) { - const errors = validate(options.schema, processedRequest.document); + const errors = validate(targetSchema, processedRequest.document); if (errors.length > 0) { throw errors; } } if (operation === 'query' || operation === 'mutation') { - if (!options.executor) { - options.executor = ({ document, context, variables }) => execute({ - schema: options.schema, - document, - rootValue: info.rootValue, - contextValue: context, - variableValues: variables, - }); - } + options.executor = options.executor || getExecutor(targetSchema, options); return applyResultTransforms( await options.executor({ @@ -118,15 +128,7 @@ async function delegateToSchemaImplementation( } if (operation === 'subscription') { - if (!options.subscriber) { - options.subscriber = ({ document, context, variables }) => subscribe({ - schema: options.schema, - document, - rootValue: info.rootValue, - contextValue: context, - variableValues: variables, - }); - } + options.subscriber = options.subscriber || getSubscriber(targetSchema, options); const originalAsyncIterator = (await options.subscriber({ document: processedRequest.document, @@ -202,3 +204,62 @@ function createDocument( definitions: [operationDefinition, ...fragments], }; } + +function getExecutor(schema: GraphQLSchema, options: IDelegateToSchemaOptions): Delegator { + let fetcher: Fetcher; + if (options.dispatcher) { + const dynamicLinkOrFetcher = options.dispatcher(context); + fetcher = (typeof dynamicLinkOrFetcher === 'function') ? + dynamicLinkOrFetcher : + linkToFetcher(dynamicLinkOrFetcher); + } else if (options.link) { + fetcher = linkToFetcher(options.link); + } else if (options.fetcher) { + fetcher = options.fetcher; + } + + if (fetcher) { + return ({ document, context, variables }) => fetcher({ + query: document, + variables, + context: { graphqlContext: context } + }); + } else { + return ({ document, context, variables }) => execute({ + schema, + document, + rootValue: options.info.rootValue, + contextValue: context, + variableValues: variables, + }); + } +} + +function getSubscriber(schema: GraphQLSchema, options: IDelegateToSchemaOptions): Delegator { + let link: ApolloLink; + if (options.dispatcher) { + link = options.dispatcher(context) as ApolloLink; + } else if (options.link) { + link = options.link; + } + + if (link) { + return ({ document, context, variables }) => { + const operation = { + query: document, + variables, + context: { graphqlContext: context } + }; + const observable = executeLink(link, operation); + return observableToAsyncIterable(observable); + }; + } else { + return ({ document, context, variables }) => subscribe({ + schema, + document, + rootValue: options.info.rootValue, + contextValue: context, + variableValues: variables, + }); + } +} diff --git a/src/stitching/index.ts b/src/stitching/index.ts index b124e850341..1adfe996eac 100644 --- a/src/stitching/index.ts +++ b/src/stitching/index.ts @@ -2,7 +2,6 @@ import makeRemoteExecutableSchema, { createResolver as defaultCreateRemoteResolv import introspectSchema from './introspectSchema'; import mergeSchemas from './mergeSchemas'; import delegateToSchema from './delegateToSchema'; -import delegateToRemoteSchema from './delegateToRemoteSchema'; import defaultMergedResolver from './defaultMergedResolver'; import { wrapField, extractField, renameField, createMergedResolver } from './createMergedResolver'; import { extractFields } from './extractFields'; @@ -17,7 +16,6 @@ export { // These are currently undocumented and not part of official API, // but exposed for the community use delegateToSchema, - delegateToRemoteSchema, defaultCreateRemoteResolver, defaultMergedResolver, createMergedResolver, diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index b29ee76e639..4ce7621c6b8 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -22,14 +22,12 @@ import { IResolversParameter, SchemaExecutionConfig, isSchemaExecutionConfig, - isRemoteSchemaExecutionConfig, } from '../Interfaces'; import { extractExtensionDefinitions, addResolveFunctionsToSchema, } from '../makeExecutableSchema'; import delegateToSchema from './delegateToSchema'; -import delegateToRemoteSchema from './delegateToRemoteSchema'; import typeFromAST from './typeFromAST'; import { Transform, @@ -383,24 +381,21 @@ function createDelegatingResolver({ operation: 'query' | 'mutation' | 'subscription', fieldName: string, }): IFieldResolver { - if (executionConfig && isRemoteSchemaExecutionConfig(executionConfig)) { - return (root, args, context, info) => { - return delegateToRemoteSchema({ - ...executionConfig, - operation, - fieldName, - args, - context, - info, - }); - }; - } + let options = Object.create(null); + + options = executionConfig ? { + operation, + fieldName, + ...executionConfig, + } : { + operation, + fieldName, + schema, + }; return (root, args, context, info) => { - return info.mergeInfo.delegateToSchema({ - schema, - operation, - fieldName, + return delegateToSchema({ + ...options, args, context, info, diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 3363ca6d30c..b1da6797905 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -7,7 +7,6 @@ import { IResolvers, Operation, SchemaExecutionConfig, - isRemoteSchemaExecutionConfig, } from '../Interfaces'; import delegateToSchema from './delegateToSchema'; import { Transform } from '../transforms/index'; @@ -95,20 +94,8 @@ function createProxyingResolver( fieldName: string, transforms: Array, ): GraphQLFieldResolver { - if (isRemoteSchemaExecutionConfig(schema)) { - return (parent, args, context, info) => delegateToSchema({ - ...schema, - operation, - fieldName, - args: {}, - context, - info, - transforms, - }); - } - return (parent, args, context, info) => delegateToSchema({ - schema: schema as GraphQLSchema, + schema, operation, fieldName, args, diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index f41a1dd48e9..2c2e2b056fb 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -35,7 +35,6 @@ import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecrea import { makeExecutableSchema } from '../makeExecutableSchema'; import { delegateToSchema, - delegateToRemoteSchema, mergeSchemas, wrapField, extractField, @@ -178,7 +177,7 @@ describe('merge schemas through transforms', () => { transforms: transformedPropertySchema.transforms, }); } else if (args.id.startsWith('b')) { - return delegateToRemoteSchema({ + return delegateToSchema({ ...bookingSchemaExecConfig, operation: 'query', fieldName: 'bookingById', @@ -188,7 +187,7 @@ describe('merge schemas through transforms', () => { transforms: transformedBookingSchema.transforms, }); } else if (args.id.startsWith('c')) { - return delegateToRemoteSchema({ + return delegateToSchema({ ...bookingSchemaExecConfig, operation: 'query', fieldName: 'customerById', @@ -206,7 +205,7 @@ describe('merge schemas through transforms', () => { bookings: { fragment: 'fragment PropertyFragment on Property { id }', resolve(parent, args, context, info) { - return delegateToRemoteSchema({ + return delegateToSchema({ ...bookingSchemaExecConfig, operation: 'query', fieldName: 'bookingsByPropertyId', diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 182a393f0d6..026a991b8fd 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -12,7 +12,6 @@ import { ExecutionResult, defaultFieldResolver, findDeprecatedUsages, - GraphQLResolveInfo, } from 'graphql'; import mergeSchemas from '../stitching/mergeSchemas'; import { @@ -32,11 +31,8 @@ import { makeExecutableSchema } from '../makeExecutableSchema'; import { IResolvers, SchemaExecutionConfig, - isRemoteSchemaExecutionConfig, - Operation, } from '../Interfaces'; -import { delegateToSchema, delegateToRemoteSchema } from '../stitching'; -import { Transform } from '../transforms'; +import { delegateToSchema } from '../stitching'; const removeLocations = ({ locations, ...rest }: any): any => ({ ...rest }); @@ -324,27 +320,6 @@ let schemaDirectiveTypeDefs = ` } `; -function delegateToLocalOrRemote(options: { - schema: GraphQLSchema | SchemaExecutionConfig; - operation: Operation; - fieldName: string; - args?: { [key: string]: any }; - context: any; - info: GraphQLResolveInfo; - transforms?: Array; -}) { - if (isRemoteSchemaExecutionConfig(options.schema)) { - return delegateToRemoteSchema({ - ...options, - ...options.schema, - }); - } - return delegateToSchema({ - ...options, - schema: options.schema as GraphQLSchema - }); -} - testCombinations.forEach(async combination => { describe('merging ' + combination.name, () => { let mergedSchema: GraphQLSchema, @@ -405,7 +380,7 @@ testCombinations.forEach(async combination => { info, ); } else { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: bookingSchema, operation: 'query', fieldName: 'bookingsByPropertyId', @@ -429,7 +404,7 @@ testCombinations.forEach(async combination => { property: { fragment: 'fragment BookingFragment on Booking { propertyId }', resolve(parent, args, context, info) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -456,7 +431,7 @@ testCombinations.forEach(async combination => { LinkType: { property: { resolve(parent, args, context, info) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -471,7 +446,7 @@ testCombinations.forEach(async combination => { }, Query: { delegateInterfaceTest(parent, args, context, info) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'interfaceTest', @@ -483,7 +458,7 @@ testCombinations.forEach(async combination => { }); }, delegateArgumentTest(parent, args, context, info) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -504,7 +479,7 @@ testCombinations.forEach(async combination => { fragment: '... on Node { id }', resolve(parent, args, context, info) { if (args.id.startsWith('p')) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -513,7 +488,7 @@ testCombinations.forEach(async combination => { info, }); } else if (args.id.startsWith('b')) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: bookingSchema, operation: 'query', fieldName: 'bookingById', @@ -522,7 +497,7 @@ testCombinations.forEach(async combination => { info, }); } else if (args.id.startsWith('c')) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: bookingSchema, operation: 'query', fieldName: 'customerById', @@ -536,14 +511,14 @@ testCombinations.forEach(async combination => { }, }, async nodes(parent, args, context, info) { - const bookings = await delegateToLocalOrRemote({ + const bookings = await delegateToSchema({ schema: bookingSchema, operation: 'query', fieldName: 'bookings', context, info, }); - const properties = await delegateToLocalOrRemote({ + const properties = await delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'properties', @@ -1425,7 +1400,7 @@ bookingById(id: "b1") { bookings: { fragment: 'fragment PropertyFragment on Property { id }', resolve(parent, args, context, info) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: bookingSchema, operation: 'query', fieldName: 'bookingsByPropertyId', @@ -1445,7 +1420,7 @@ bookingById(id: "b1") { property: { fragment: 'fragment BookingFragment on Booking { propertyId }', resolve(parent, args, context, info) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -1472,7 +1447,7 @@ bookingById(id: "b1") { const Query2: IResolvers = { Query: { delegateInterfaceTest(parent, args, context, info) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'interfaceTest', @@ -1484,7 +1459,7 @@ bookingById(id: "b1") { }); }, delegateArgumentTest(parent, args, context, info) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -1505,7 +1480,7 @@ bookingById(id: "b1") { fragment: 'fragment NodeFragment on Node { id }', resolve(parent, args, context, info) { if (args.id.startsWith('p')) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -1514,7 +1489,7 @@ bookingById(id: "b1") { info, }); } else if (args.id.startsWith('b')) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: bookingSchema, operation: 'query', fieldName: 'bookingById', @@ -1523,7 +1498,7 @@ bookingById(id: "b1") { info, }); } else if (args.id.startsWith('c')) { - return delegateToLocalOrRemote({ + return delegateToSchema({ schema: bookingSchema, operation: 'query', fieldName: 'customerById', @@ -1542,14 +1517,14 @@ bookingById(id: "b1") { const AsyncQuery: IResolvers = { Query: { async nodes(parent, args, context, info) { - const bookings = await delegateToLocalOrRemote({ + const bookings = await delegateToSchema({ schema: bookingSchema, operation: 'query', fieldName: 'bookings', context, info, }); - const properties = await delegateToLocalOrRemote({ + const properties = await delegateToSchema({ schema: propertySchema, operation: 'query', fieldName: 'properties', diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index ce6a94cad5a..f5d0c085642 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -21,7 +21,7 @@ import { makeExecutableSchema } from '../makeExecutableSchema'; import { IResolvers, Fetcher, - RemoteSchemaExecutionConfig, + SchemaExecutionConfig, } from '../Interfaces'; import introspectSchema from '../stitching/introspectSchema'; import { PubSub } from 'graphql-subscriptions'; @@ -762,7 +762,7 @@ function makeLinkFromSchema(schema: GraphQLSchema) { } export async function makeSchemaRemoteFromLink(schema: GraphQLSchema) - : Promise { + : Promise { const link = makeLinkFromSchema(schema); const clientSchema = await introspectSchema(link); return { @@ -772,7 +772,7 @@ export async function makeSchemaRemoteFromLink(schema: GraphQLSchema) } export async function makeSchemaRemoteFromDispatchedLink(schema: GraphQLSchema) - : Promise { + : Promise { const link = makeLinkFromSchema(schema); const clientSchema = await introspectSchema(link); return { @@ -783,7 +783,7 @@ export async function makeSchemaRemoteFromDispatchedLink(schema: GraphQLSchema) // ensure fetcher support exists from the 2.0 api async function makeExecutableSchemaFromDispatchedFetcher(schema: GraphQLSchema) - : Promise { + : Promise { const fetcher: Fetcher = ({ query, operationName, variables, context }) => { return graphql( schema, From 39fe30b54e60b883154b7d61392882815784127c Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 19 Sep 2019 00:47:50 -0400 Subject: [PATCH 070/250] refactor(mergeSchemas): simplify createDelegatingResolver --- src/stitching/mergeSchemas.ts | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 4ce7621c6b8..d79c30d5650 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -372,30 +372,18 @@ function guessSchemaByRootField( function createDelegatingResolver({ schema, - executionConfig, operation, fieldName, }: { - schema: GraphQLSchema, - executionConfig?: SchemaExecutionConfig, + schema: GraphQLSchema | SchemaExecutionConfig, operation: 'query' | 'mutation' | 'subscription', fieldName: string, }): IFieldResolver { - let options = Object.create(null); - - options = executionConfig ? { - operation, - fieldName, - ...executionConfig, - } : { - operation, - fieldName, - schema, - }; - return (root, args, context, info) => { return delegateToSchema({ - ...options, + schema, + operation, + fieldName, args, context, info, @@ -495,8 +483,7 @@ function mergeTypeCandidates( Object.keys(candidateFields).forEach(fieldName => { resolvers[fieldName] = { [resolverKey]: schema ? createDelegatingResolver({ - schema, - executionConfig, + schema: executionConfig ? executionConfig : schema, operation: operationName, fieldName, }) : null, From 4c78947c2fe04fe221a3437e5a8c60c23b8b8b4d Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 19 Sep 2019 00:53:40 -0400 Subject: [PATCH 071/250] refactor(delegateToSchema): do not add empty args --- src/stitching/delegateToSchema.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 4354566fb50..da1ef093601 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -69,7 +69,7 @@ async function delegateToSchemaImplementation( targetSchema = schemaOrSchemaConfig; } - const { info, args = {} } = options; + const { info } = options; const operation = options.operation || info.operation.operation; const rawDocument: DocumentNode = createDocument( options.fieldName, @@ -94,12 +94,18 @@ async function delegateToSchemaImplementation( if (info.mergeInfo && info.mergeInfo.fragments) { transforms.push( - new ReplaceFieldWithFragment(targetSchema, info.mergeInfo.fragments), + new ReplaceFieldWithFragment(targetSchema, info.mergeInfo.fragments) + ); + } + + const { args } = options; + if (args) { + transforms.push( + new AddArgumentsAsVariables(targetSchema, args) ); } transforms = transforms.concat([ - new AddArgumentsAsVariables(targetSchema, args), new FilterToSchema(targetSchema), new AddTypenameToAbstract(targetSchema), new CheckResultAndHandleErrors(info, options.fieldName), From e8ee6bbf38a68a3815543916cc7d6fa2f40c396d Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 21 Sep 2019 23:25:54 -0400 Subject: [PATCH 072/250] chore(tests): mocha not found with graphql 14.2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cc99d50a220..72d3c92e505 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ node_js: install: - npm config set spin=false - - npm install -g coveralls + - npm install -g mocha coveralls - npm install - npm install graphql@$GRAPHQL_VERSION - npm install @types/graphql@$TYPES_GRAPHQL_VERSION From 01d99f45eafefc473d38fbb4677d291d60d37405 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 22 Sep 2019 11:28:21 -0400 Subject: [PATCH 073/250] refactor(schemaRecreation): deprecate most of schema recreation now that healTypes operational --- src/stitching/makeRemoteExecutableSchema.ts | 131 ++------- src/stitching/resolvers.ts | 29 +- src/stitching/schemaRecreation.ts | 284 ++------------------ src/test/testAlternateMergeSchemas.ts | 6 +- src/transforms/RenameObjectFields.ts | 7 +- src/transforms/RenameRootFields.ts | 12 +- src/transforms/TransformObjectFields.ts | 14 +- src/transforms/TransformRootFields.ts | 12 +- src/transforms/transformSchema.ts | 14 +- 9 files changed, 84 insertions(+), 425 deletions(-) diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index c76d8898087..66d5304564e 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -3,34 +3,22 @@ import { ApolloLink } from 'apollo-link'; import { - GraphQLObjectType, GraphQLFieldResolver, GraphQLSchema, - GraphQLInterfaceType, - GraphQLUnionType, - GraphQLID, - GraphQLString, - GraphQLFloat, - GraphQLBoolean, - GraphQLInt, - GraphQLScalarType, buildSchema, - printSchema, Kind, GraphQLResolveInfo, BuildSchemaOptions } from 'graphql'; import linkToFetcher, { execute } from './linkToFetcher'; -import isEmptyObject from '../utils/isEmptyObject'; -import { IResolvers, IResolverObject, Fetcher } from '../Interfaces'; -import { makeExecutableSchema } from '../makeExecutableSchema'; -import { recreateType } from './schemaRecreation'; -import resolveParentFromTypename from './resolveFromParentTypename'; -import defaultMergedResolver from './defaultMergedResolver'; +import { Fetcher, Operation } from '../Interfaces'; import { checkResultAndHandleErrors } from './checkResultAndHandleErrors'; import { observableToAsyncIterable } from './observableToAsyncIterable'; import mapAsyncIterator from './mapAsyncIterator'; import { Options as PrintSchemaOptions } from 'graphql/utilities/schemaPrinter'; +import { cloneSchema } from '../utils'; +import { stripResolvers, generateProxyingResolvers } from './resolvers'; +import { addResolveFunctionsToSchema } from '../generate'; export type ResolverFn = ( rootValue?: any, @@ -40,7 +28,7 @@ export type ResolverFn = ( ) => AsyncIterator; export default function makeRemoteExecutableSchema({ - schema, + schema: targetSchema, link, fetcher, createResolver: customCreateResolver = createResolver, @@ -58,97 +46,34 @@ export default function makeRemoteExecutableSchema({ fetcher = linkToFetcher(link); } - let typeDefs: string; - - if (typeof schema === 'string') { - typeDefs = schema; - schema = buildSchema(typeDefs, buildSchemaOptions); - } else { - typeDefs = printSchema(schema, printSchemaOptions); - } - - // prepare query resolvers - const queryResolvers: IResolverObject = {}; - const queryType = schema.getQueryType(); - const queries = queryType.getFields(); - Object.keys(queries).forEach(key => { - queryResolvers[key] = customCreateResolver(fetcher); - }); - - // prepare mutation resolvers - const mutationResolvers: IResolverObject = {}; - const mutationType = schema.getMutationType(); - if (mutationType) { - const mutations = mutationType.getFields(); - Object.keys(mutations).forEach(key => { - mutationResolvers[key] = customCreateResolver(fetcher); - }); + if (typeof targetSchema === 'string') { + targetSchema = buildSchema(targetSchema, buildSchemaOptions); } - // prepare subscription resolvers - const subscriptionResolvers: IResolverObject = {}; - const subscriptionType = schema.getSubscriptionType(); - if (subscriptionType) { - const subscriptions = subscriptionType.getFields(); - Object.keys(subscriptions).forEach(key => { - subscriptionResolvers[key] = { - subscribe: createSubscriptionResolver(key, link) - }; - }); - } - - // merge resolvers into resolver map - const resolvers: IResolvers = { [queryType.name]: queryResolvers }; - - if (!isEmptyObject(mutationResolvers)) { - resolvers[mutationType.name] = mutationResolvers; - } - - if (!isEmptyObject(subscriptionResolvers)) { - resolvers[subscriptionType.name] = subscriptionResolvers; - } - - // add missing abstract resolvers (scalar, unions, interfaces) - const typeMap = schema.getTypeMap(); - const types = Object.keys(typeMap).map(name => typeMap[name]); - for (const type of types) { - if (type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) { - resolvers[type.name] = { - __resolveType(parent: any, context: any, info: GraphQLResolveInfo) { - return resolveParentFromTypename(parent, info.schema); - } - }; - } else if (type instanceof GraphQLScalarType) { - if ( - !( - type === GraphQLID || - type === GraphQLString || - type === GraphQLFloat || - type === GraphQLBoolean || - type === GraphQLInt - ) - ) { - resolvers[type.name] = recreateType(type, (name: string) => null, false) as GraphQLScalarType; - } - } else if ( - type instanceof GraphQLObjectType && - type.name.slice(0, 2) !== '__' && - type !== queryType && - type !== mutationType && - type !== subscriptionType - ) { - const resolver = {}; - Object.keys(type.getFields()).forEach(field => { - resolver[field] = defaultMergedResolver; - }); - resolvers[type.name] = resolver; + const schema = cloneSchema(targetSchema); + stripResolvers(schema); + + function createProxyingResolver( + schema: GraphQLSchema, + operation: Operation, + ): GraphQLFieldResolver { + if (operation === 'query' || operation === 'mutation') { + return customCreateResolver(fetcher); + } else { + return createSubscriptionResolver(link); } } - return makeExecutableSchema({ - typeDefs, - resolvers + const resolvers = generateProxyingResolvers(schema, [], createProxyingResolver); + addResolveFunctionsToSchema({ + schema, + resolvers, + resolverValidationOptions: { + allowResolversNotInSchema: true, + }, }); + + return schema; } export function createResolver(fetcher: Fetcher): GraphQLFieldResolver { @@ -167,7 +92,7 @@ export function createResolver(fetcher: Fetcher): GraphQLFieldResolver }; } -function createSubscriptionResolver(name: string, link: ApolloLink): ResolverFn { +function createSubscriptionResolver(link: ApolloLink): ResolverFn { return (root, args, context, info) => { const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]); const document = { diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index b1da6797905..9e93f3c4bac 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -7,8 +7,10 @@ import { IResolvers, Operation, SchemaExecutionConfig, + isSchemaExecutionConfig, } from '../Interfaces'; import delegateToSchema from './delegateToSchema'; +import { makeMergedType } from './makeMergedType'; import { Transform } from '../transforms/index'; export type Mapping = { @@ -21,10 +23,20 @@ export type Mapping = { }; export function generateProxyingResolvers( - targetSchema: GraphQLSchema | SchemaExecutionConfig, - transforms: Array, - mapping: Mapping, + schemaOrSchemaExecutionConfig: GraphQLSchema | SchemaExecutionConfig, + transforms: Array = [], + createProxyingResolver: ( + schema: GraphQLSchema | SchemaExecutionConfig, + operation: Operation, + fieldName: string, + transforms: Array, + ) => GraphQLFieldResolver = defaultCreateProxyingResolver, ): IResolvers { + const targetSchema: GraphQLSchema = isSchemaExecutionConfig(schemaOrSchemaExecutionConfig) ? + schemaOrSchemaExecutionConfig.schema : schemaOrSchemaExecutionConfig; + + const mapping = generateSimpleMapping(targetSchema); + const result = {}; Object.keys(mapping).forEach(name => { result[name] = {}; @@ -88,7 +100,7 @@ export function generateMappingFromObjectType( return result; } -function createProxyingResolver( +function defaultCreateProxyingResolver( schema: GraphQLSchema | SchemaExecutionConfig, operation: Operation, fieldName: string, @@ -104,3 +116,12 @@ function createProxyingResolver( transforms, }); } + +export function stripResolvers(schema: GraphQLSchema): void { + const typeMap = schema.getTypeMap(); + Object.keys(typeMap).forEach(typeName => { + if (!typeName.startsWith('__')) { + makeMergedType(typeMap[typeName]); + } + }); +} diff --git a/src/stitching/schemaRecreation.ts b/src/stitching/schemaRecreation.ts index 64183d0d4ca..12780ea3e35 100644 --- a/src/stitching/schemaRecreation.ts +++ b/src/stitching/schemaRecreation.ts @@ -1,245 +1,23 @@ import { GraphQLArgument, GraphQLArgumentConfig, - GraphQLEnumType, GraphQLField, GraphQLFieldConfig, GraphQLFieldConfigArgumentMap, - GraphQLFieldConfigMap, - GraphQLFieldMap, GraphQLInputField, GraphQLInputFieldConfig, - GraphQLInputType, GraphQLInputFieldConfigMap, GraphQLInputFieldMap, - GraphQLInputObjectType, - GraphQLInterfaceType, - GraphQLList, - GraphQLNamedType, - GraphQLNonNull, - GraphQLObjectType, - GraphQLScalarType, - GraphQLType, - GraphQLUnionType, - GraphQLDirective, - Kind, - ValueNode, - getNamedType, - isNamedType, - GraphQLInt, - GraphQLFloat, - GraphQLString, - GraphQLBoolean, - GraphQLID, } from 'graphql'; -import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; -import { ResolveType } from '../Interfaces'; -import resolveFromParentTypename from './resolveFromParentTypename'; -import defaultMergedResolver from './defaultMergedResolver'; -import { isStub } from '../utils/stub'; -import { - serializeInputValue, - parseInputValue, - parseInputValueLiteral -} from '../utils/transformInputValue'; - -export function recreateType( - type: GraphQLNamedType, - resolveType: ResolveType, - keepResolvers: boolean, -): GraphQLNamedType { - if (type instanceof GraphQLObjectType) { - const fields = type.getFields(); - const interfaces = type.getInterfaces(); - - return new GraphQLObjectType({ - name: type.name, - description: type.description, - astNode: type.astNode, - isTypeOf: keepResolvers ? type.isTypeOf : undefined, - fields: () => - fieldMapToFieldConfigMap(fields, resolveType, keepResolvers), - interfaces: () => interfaces.map(iface => resolveType(iface)), - }); - } else if (type instanceof GraphQLInterfaceType) { - const fields = type.getFields(); - - return new GraphQLInterfaceType({ - name: type.name, - description: type.description, - astNode: type.astNode, - fields: () => - fieldMapToFieldConfigMap(fields, resolveType, keepResolvers), - resolveType: keepResolvers - ? type.resolveType - : (parent, context, info) => - resolveFromParentTypename(parent, info.schema), - }); - } else if (type instanceof GraphQLUnionType) { - return new GraphQLUnionType({ - name: type.name, - description: type.description, - astNode: type.astNode, - - types: () => type.getTypes().map(unionMember => resolveType(unionMember)), - resolveType: keepResolvers - ? type.resolveType - : (parent, context, info) => - resolveFromParentTypename(parent, info.schema), - }); - } else if (type instanceof GraphQLInputObjectType) { - return new GraphQLInputObjectType({ - name: type.name, - description: type.description, - astNode: type.astNode, - fields: () => - inputFieldMapToFieldConfigMap(type.getFields(), resolveType), - }); - } else if (type instanceof GraphQLEnumType) { - const values = type.getValues(); - const newValues = {}; - values.forEach(value => { - newValues[value.name] = { - value: value.value, - deprecationReason: value.deprecationReason, - description: value.description, - astNode: value.astNode, - }; - }); - return new GraphQLEnumType({ - name: type.name, - description: type.description, - astNode: type.astNode, - values: newValues, - }); - } else if (type instanceof GraphQLScalarType) { - if (isSpecifiedScalarType(type)) { - return type; - } else { - return new GraphQLScalarType({ - name: type.name, - description: type.description, - astNode: type.astNode, - serialize: type.serialize ? type.serialize : (value: any) => value, - parseValue: type.parseValue ? type.parseValue : (value: any) => value, - parseLiteral: type.parseLiteral ? type.parseLiteral : (ast: any) => parseLiteral(ast), - }); - } - } else { - throw new Error(`Invalid type ${type}`); - } -} - -export function recreateDirective( - directive: GraphQLDirective, - resolveType: ResolveType, -): GraphQLDirective { - return new GraphQLDirective({ - name: directive.name, - description: directive.description, - locations: directive.locations, - args: argsToFieldConfigArgumentMap(directive.args, resolveType), - astNode: directive.astNode, - }); -} - -function parseLiteral(ast: ValueNode): any { - switch (ast.kind) { - case Kind.STRING: - case Kind.BOOLEAN: { - return ast.value; - } - case Kind.INT: - case Kind.FLOAT: { - return parseFloat(ast.value); - } - case Kind.OBJECT: { - const value = Object.create(null); - ast.fields.forEach(field => { - value[field.name.value] = parseLiteral(field.value); - }); - - return value; - } - case Kind.LIST: { - return ast.values.map(parseLiteral); - } - default: - return null; - } -} - -export function fieldMapToFieldConfigMap( - fields: GraphQLFieldMap, - resolveType: ResolveType, - keepResolvers: boolean, -): GraphQLFieldConfigMap { - const result: GraphQLFieldConfigMap = {}; - Object.keys(fields).forEach(name => { - const field = fields[name]; - const type = resolveType(field.type); - if (type !== null) { - result[name] = fieldToFieldConfig( - fields[name], - resolveType, - keepResolvers, - ); - } - }); - return result; -} - -export function createResolveType( - getType: (name: string, type: GraphQLType) => GraphQLType | null, -): ResolveType { - const resolveType = (type: T): T | GraphQLType => { - if (type instanceof GraphQLList) { - const innerType = resolveType(type.ofType); - if (innerType === null) { - return null; - } else { - return new GraphQLList(innerType) as T; - } - } else if (type instanceof GraphQLNonNull) { - const innerType = resolveType(type.ofType); - if (innerType === null) { - return null; - } else { - return new GraphQLNonNull(innerType) as T; - } - } else if (isNamedType(type)) { - const typeName = getNamedType(type).name; - switch (typeName) { - case GraphQLInt.name: - return GraphQLInt; - case GraphQLFloat.name: - return GraphQLFloat; - case GraphQLString.name: - return GraphQLString; - case GraphQLBoolean.name: - return GraphQLBoolean; - case GraphQLID.name: - return GraphQLID; - default: - return getType(typeName, type); - } - } else { - return type; - } - }; - return resolveType; -} export function fieldToFieldConfig( field: GraphQLField, - resolveType: ResolveType, - keepResolvers: boolean, ): GraphQLFieldConfig { return { - type: resolveType(field.type), - args: argsToFieldConfigArgumentMap(field.args, resolveType), - resolve: keepResolvers ? field.resolve : defaultMergedResolver, - subscribe: keepResolvers ? field.subscribe : null, + type: field.type, + args: argsToFieldConfigArgumentMap(field.args), + resolve: field.resolve, + subscribe: field.subscribe, description: field.description, deprecationReason: field.deprecationReason, astNode: field.astNode, @@ -248,11 +26,10 @@ export function fieldToFieldConfig( export function argsToFieldConfigArgumentMap( args: Array, - resolveType: ResolveType, ): GraphQLFieldConfigArgumentMap { const result: GraphQLFieldConfigArgumentMap = {}; args.forEach(arg => { - const newArg = argumentToArgumentConfig(arg, resolveType); + const newArg = argumentToArgumentConfig(arg); if (newArg) { result[newArg[0]] = newArg[1]; } @@ -262,62 +39,35 @@ export function argsToFieldConfigArgumentMap( export function argumentToArgumentConfig( argument: GraphQLArgument, - resolveType: ResolveType, ): [string, GraphQLArgumentConfig] | null { - const type = resolveType(argument.type); - if (type === null) { - return null; - } else { - return [ - argument.name, - { - type, - defaultValue: reparseDefaultValue(argument.defaultValue, argument.type, type), - description: argument.description, - astNode: argument.astNode, - }, - ]; - } + return [ + argument.name, + { + type: argument.type, + defaultValue: argument.defaultValue, + description: argument.description, + astNode: argument.astNode, + }, + ]; } export function inputFieldMapToFieldConfigMap( fields: GraphQLInputFieldMap, - resolveType: ResolveType, ): GraphQLInputFieldConfigMap { const result: GraphQLInputFieldConfigMap = {}; Object.keys(fields).forEach(name => { - const field = fields[name]; - const type = resolveType(field.type); - if (type !== null) { - result[name] = inputFieldToFieldConfig(fields[name], resolveType); - } + result[name] = inputFieldToFieldConfig(fields[name]); }); return result; } export function inputFieldToFieldConfig( field: GraphQLInputField, - resolveType: ResolveType, ): GraphQLInputFieldConfig { - const type = resolveType(field.type); return { - type, - defaultValue: reparseDefaultValue(field.defaultValue, field.type, type), + type: field.type, + defaultValue: field.defaultValue, description: field.description, astNode: field.astNode, }; } - -function reparseDefaultValue( - originalDefaultValue: any, - originalType: GraphQLInputType, - newType: GraphQLInputType, -) { - if ( - originalType instanceof GraphQLInputObjectType && - isStub(getNamedType(originalType) as GraphQLInputObjectType) - ) { - return parseInputValueLiteral(newType, originalDefaultValue); - } - return parseInputValue(newType, serializeInputValue(originalType, originalDefaultValue)); -} diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 2c2e2b056fb..c83827303fa 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -8,7 +8,6 @@ import { subscribe, parse, GraphQLField, - GraphQLNamedType, GraphQLScalarType, FieldNode, printSchema, @@ -31,7 +30,7 @@ import { subscriptionPubSubTrigger, } from './testingSchemas'; import { forAwaitEach } from 'iterall'; -import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; +import { fieldToFieldConfig } from '../stitching/schemaRecreation'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { delegateToSchema, @@ -352,11 +351,10 @@ describe('transform object fields', () => { let transformedPropertySchema: GraphQLSchema; before(async () => { - const resolveType = createResolveType((name: string, type: GraphQLNamedType): GraphQLNamedType => type); transformedPropertySchema = transformSchema(propertySchema, [ new TransformObjectFields( (typeName: string, fieldName: string, field: GraphQLField) => { - const fieldConfig = fieldToFieldConfig(field, resolveType, true); + const fieldConfig = fieldToFieldConfig(field); if (typeName !== 'Property' || fieldName !== 'name') { return fieldConfig; } diff --git a/src/transforms/RenameObjectFields.ts b/src/transforms/RenameObjectFields.ts index 01426897e05..f814a73177e 100644 --- a/src/transforms/RenameObjectFields.ts +++ b/src/transforms/RenameObjectFields.ts @@ -1,6 +1,6 @@ -import { GraphQLNamedType, GraphQLField, GraphQLSchema } from 'graphql'; +import { GraphQLField, GraphQLSchema } from 'graphql'; import { Transform } from './transforms'; -import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; +import { fieldToFieldConfig } from '../stitching/schemaRecreation'; import { Request } from '../Interfaces'; import TransformObjectFields from './TransformObjectFields'; @@ -8,12 +8,11 @@ export default class RenameObjectFields implements Transform { private transformer: TransformObjectFields; constructor(renamer: (typeName: string, fieldName: string, field: GraphQLField) => string) { - const resolveType = createResolveType((name: string, type: GraphQLNamedType): GraphQLNamedType => type); this.transformer = new TransformObjectFields( (typeName: string, fieldName: string, field: GraphQLField) => { return { name: renamer(typeName, fieldName, field), - field: fieldToFieldConfig(field, resolveType, true) + field: fieldToFieldConfig(field) }; } ); diff --git a/src/transforms/RenameRootFields.ts b/src/transforms/RenameRootFields.ts index 30fdbf4fe67..6ddebff8662 100644 --- a/src/transforms/RenameRootFields.ts +++ b/src/transforms/RenameRootFields.ts @@ -1,9 +1,6 @@ -import { GraphQLNamedType, GraphQLField, GraphQLSchema } from 'graphql'; +import { GraphQLField, GraphQLSchema } from 'graphql'; import { Transform } from './transforms'; -import { - createResolveType, - fieldToFieldConfig, -} from '../stitching/schemaRecreation'; +import { fieldToFieldConfig } from '../stitching/schemaRecreation'; import TransformRootFields from './TransformRootFields'; export default class RenameRootFields implements Transform { @@ -16,9 +13,6 @@ export default class RenameRootFields implements Transform { field: GraphQLField, ) => string, ) { - const resolveType = createResolveType( - (name: string, type: GraphQLNamedType): GraphQLNamedType => type, - ); this.transformer = new TransformRootFields( ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -27,7 +21,7 @@ export default class RenameRootFields implements Transform { ) => { return { name: renamer(operation, fieldName, field), - field: fieldToFieldConfig(field, resolveType, true), + field: fieldToFieldConfig(field), }; }, ); diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index fa1d3fe5e5f..46b9e3cdd4d 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -1,7 +1,6 @@ import { GraphQLObjectType, GraphQLSchema, - GraphQLNamedType, GraphQLField, GraphQLFieldConfig, GraphQLType, @@ -19,7 +18,7 @@ import isEmptyObject from '../utils/isEmptyObject'; import { Request, VisitSchemaKind } from '../Interfaces'; import { Transform } from './transforms'; import { visitSchema } from '../utils/visitSchema'; -import { createResolveType, fieldToFieldConfig } from '../stitching/schemaRecreation'; +import { fieldToFieldConfig } from '../stitching/schemaRecreation'; export type ObjectFieldTransformer = ( typeName: string, @@ -90,9 +89,6 @@ export default class TransformObjectFields implements Transform { type: GraphQLObjectType, objectFieldTransformer: ObjectFieldTransformer ): GraphQLObjectType { - const resolveType = createResolveType( - (name: string, originalType: GraphQLNamedType): GraphQLNamedType => originalType - ); const fields = type.getFields(); const newFields = {}; @@ -101,7 +97,7 @@ export default class TransformObjectFields implements Transform { const transformedField = objectFieldTransformer(type.name, fieldName, field); if (typeof transformedField === 'undefined') { - newFields[fieldName] = fieldToFieldConfig(field, resolveType, true); + newFields[fieldName] = fieldToFieldConfig(field); } else if (transformedField !== null) { const newName = (transformedField as { name: string; field: GraphQLFieldConfig }).name; @@ -136,12 +132,8 @@ export default class TransformObjectFields implements Transform { return null; } else { return new GraphQLObjectType({ - name: type.name, - description: type.description, - astNode: type.astNode, - isTypeOf: type.isTypeOf, + ...type.toConfig(), fields: newFields, - interfaces: () => type.getInterfaces().map(iface => resolveType(iface)) }); } } diff --git a/src/transforms/TransformRootFields.ts b/src/transforms/TransformRootFields.ts index db603c8369b..d1d5b4e4576 100644 --- a/src/transforms/TransformRootFields.ts +++ b/src/transforms/TransformRootFields.ts @@ -1,7 +1,6 @@ import { GraphQLObjectType, GraphQLSchema, - GraphQLNamedType, GraphQLField, GraphQLFieldConfig, } from 'graphql'; @@ -9,10 +8,7 @@ import isEmptyObject from '../utils/isEmptyObject'; import { Transform } from './transforms'; import { visitSchema } from '../utils/visitSchema'; import { VisitSchemaKind } from '../Interfaces'; -import { - createResolveType, - fieldToFieldConfig, -} from '../stitching/schemaRecreation'; +import { fieldToFieldConfig } from '../stitching/schemaRecreation'; export type RootTransformer = ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -69,17 +65,13 @@ function transformFields( | null | undefined, ): GraphQLObjectType { - const resolveType = createResolveType( - (name: string, originalType: GraphQLNamedType): GraphQLNamedType => - originalType, - ); const fields = type.getFields(); const newFields = {}; Object.keys(fields).forEach(fieldName => { const field = fields[fieldName]; const newField = transformer(fieldName, field); if (typeof newField === 'undefined') { - newFields[fieldName] = fieldToFieldConfig(field, resolveType, true); + newFields[fieldName] = fieldToFieldConfig(field); } else if (newField !== null) { if ( (<{ name: string; field: GraphQLFieldConfig }>newField).name diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index 32ecfe00217..a63bc06ce79 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -4,7 +4,7 @@ import { addResolveFunctionsToSchema } from '../makeExecutableSchema'; import { Transform, applySchemaTransforms } from '../transforms/transforms'; import { generateProxyingResolvers, - generateSimpleMapping, + stripResolvers, } from '../stitching/resolvers'; import { SchemaExecutionConfig, @@ -12,16 +12,6 @@ import { } from '../Interfaces'; import { cloneSchema } from '../utils/clone'; -import { makeMergedType } from '../stitching/makeMergedType'; - -export function stripResolvers(schema: GraphQLSchema): void { - const typeMap = schema.getTypeMap(); - Object.keys(typeMap).forEach(typeName => { - if (!typeName.startsWith('__')) { - makeMergedType(typeMap[typeName]); - } - }); -} export function wrapSchema( schemaOrSchemaExecutionConfig: GraphQLSchema | SchemaExecutionConfig, @@ -33,11 +23,9 @@ export function wrapSchema( const schema = cloneSchema(targetSchema); stripResolvers(schema); - const mapping = generateSimpleMapping(targetSchema); const resolvers = generateProxyingResolvers( schemaOrSchemaExecutionConfig, transforms, - mapping, ); addResolveFunctionsToSchema({ schema, From f2d9b1c87982e9567b95e763da34686ff301e55a Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 22 Sep 2019 19:15:19 -0400 Subject: [PATCH 074/250] chore(lint) --- src/stitching/makeRemoteExecutableSchema.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 66d5304564e..b4ac0506443 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -50,8 +50,8 @@ export default function makeRemoteExecutableSchema({ targetSchema = buildSchema(targetSchema, buildSchemaOptions); } - const schema = cloneSchema(targetSchema); - stripResolvers(schema); + const remoteSchema = cloneSchema(targetSchema); + stripResolvers(remoteSchema); function createProxyingResolver( schema: GraphQLSchema, @@ -64,16 +64,16 @@ export default function makeRemoteExecutableSchema({ } } - const resolvers = generateProxyingResolvers(schema, [], createProxyingResolver); + const resolvers = generateProxyingResolvers(remoteSchema, [], createProxyingResolver); addResolveFunctionsToSchema({ - schema, + schema: remoteSchema, resolvers, resolverValidationOptions: { allowResolversNotInSchema: true, }, }); - return schema; + return remoteSchema; } export function createResolver(fetcher: Fetcher): GraphQLFieldResolver { From 52ca30fbc1278c6afe39826670f528e1334a9fe9 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 24 Sep 2019 03:30:16 -0400 Subject: [PATCH 075/250] chore(experimentalFragmentVariables): fix test, clarify use case. --- src/test/testSchemaGenerator.ts | 92 +++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 21 deletions(-) diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 2fb98824457..26ac8af9021 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -12,6 +12,9 @@ import { ExecutionResult, GraphQLError, GraphQLEnumType, + execute, + VariableDefinitionNode, + DocumentNode, } from 'graphql'; // import { printSchema } from 'graphql'; const { GraphQLJSON } = require('graphql-type-json'); @@ -2684,32 +2687,16 @@ describe('can specify lexical parser options', () => { expect(schema.astNode.loc).to.equal(undefined); }); - xit("can specify 'experimentalFragmentVariables' option", () => { + it("can specify 'experimentalFragmentVariables' option", () => { const typeDefs = ` - type Hello { - world(phrase: String): String - } - - fragment hello($phrase: String = "world") on Hello { - world(phrase: $phrase) - } - - type RootQuery { - hello: Hello - } - - schema { - query: RootQuery + type Query { + version: Int } `; const resolvers = { - RootQuery: { - hello() { - return { - world: (phrase: string) => `hello ${phrase}`, - }; - }, + Query: { + version: () => 1, }, }; @@ -2723,6 +2710,69 @@ describe('can specify lexical parser options', () => { }); }).to.not.throw(); }); + + // Note that the experimentalFragmentVariables option requires a client side transform + // to hoist the parsed variables into queries, see https://github.com/graphql/graphql-js/pull/1141 + // and so this really has nothing to do with schema creation or execution. + it("can use 'experimentalFragmentVariables' option", async () => { + const typeDefs = ` + type Query { + hello(phrase: String): String + } + `; + + const resolvers = { + Query: { + hello: (root: any, args: any) => { + return `hello ${args.phrase}`; + } + }, + }; + + const jsSchema = makeExecutableSchema({ + typeDefs, + resolvers, + }); + + const query = ` + fragment Hello($phrase: String = "world") on Query { + hello(phrase: $phrase) + } + query { + ...Hello + } + `; + + const parsedQuery = parse(query, { experimentalFragmentVariables: true }); + + const hoist = (document: DocumentNode) => { + let variableDefs: Array = []; + + document.definitions.forEach(def => { + if (def.kind === Kind.FRAGMENT_DEFINITION) { + variableDefs = variableDefs.concat(def.variableDefinitions); + } + }); + + return { + kind: Kind.DOCUMENT, + definitions: parsedQuery.definitions.map(def => { + return { + ...def, + variableDefinitions: variableDefs, + }; + }), + }; + }; + + const hoistedQuery = hoist(parsedQuery); + + const result = await execute(jsSchema, hoistedQuery); + expect(result.data).to.deep.equal({ hello: 'hello world', }); + + const result2 = await execute(jsSchema, hoistedQuery, null, null, { phrase: 'world again!' }); + expect(result2.data).to.deep.equal({ hello: 'hello world again!', }); + }); }); describe('interfaces', () => { From 3f79614c28a2246d1e23d0358ed6ce40fa7cdfc0 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 24 Sep 2019 14:45:05 -0400 Subject: [PATCH 076/250] fix(remote schemas): must add __typename to remote query to properly resolve interfaces. Error will occur only when querying directly on the remote schema, as delegateToSchema automatically adds type names. addTypenameToAbstract is necessary for resolveType to work in the parent schema just as checkResultAndHandleErrors is necessary for resolve to work! --- src/stitching/addTypenameToAbstract.ts | 56 ++++++++++++++++++++ src/stitching/makeRemoteExecutableSchema.ts | 17 ++++-- src/test/testMakeRemoteExecutableSchema.ts | 44 +++++++++++++++- src/transforms/AddTypenameToAbstract.ts | 58 +-------------------- 4 files changed, 113 insertions(+), 62 deletions(-) create mode 100644 src/stitching/addTypenameToAbstract.ts diff --git a/src/stitching/addTypenameToAbstract.ts b/src/stitching/addTypenameToAbstract.ts new file mode 100644 index 00000000000..706574b1b32 --- /dev/null +++ b/src/stitching/addTypenameToAbstract.ts @@ -0,0 +1,56 @@ +import { + GraphQLType, + DocumentNode, + TypeInfo, + visit, + visitWithTypeInfo, + SelectionSetNode, + Kind, + FieldNode, + GraphQLInterfaceType, + GraphQLUnionType, +} from 'graphql'; +import { GraphQLSchemaWithTransforms } from '../Interfaces'; + +export function addTypenameToAbstract( + targetSchema: GraphQLSchemaWithTransforms, + 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 && + (parentType instanceof GraphQLInterfaceType || + parentType instanceof GraphQLUnionType) && + !selections.find( + _ => + (_ as FieldNode).kind === Kind.FIELD && + (_ as FieldNode).name.value === '__typename', + ) + ) { + selections = selections.concat({ + kind: Kind.FIELD, + name: { + kind: Kind.NAME, + value: '__typename', + }, + }); + } + + if (selections !== node.selections) { + return { + ...node, + selections, + }; + } + }, + }), + ); +} diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index b4ac0506443..77f6031a5c5 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -8,10 +8,12 @@ import { buildSchema, Kind, GraphQLResolveInfo, - BuildSchemaOptions + BuildSchemaOptions, + DocumentNode, } from 'graphql'; import linkToFetcher, { execute } from './linkToFetcher'; import { Fetcher, Operation } from '../Interfaces'; +import { addTypenameToAbstract } from './addTypenameToAbstract'; import { checkResultAndHandleErrors } from './checkResultAndHandleErrors'; import { observableToAsyncIterable } from './observableToAsyncIterable'; import mapAsyncIterator from './mapAsyncIterator'; @@ -79,12 +81,15 @@ export default function makeRemoteExecutableSchema({ export function createResolver(fetcher: Fetcher): GraphQLFieldResolver { return async (root, args, context, info) => { const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]); - const document = { + let query: DocumentNode = { kind: Kind.DOCUMENT, definitions: [info.operation, ...fragments] }; + + query = addTypenameToAbstract(info.schema, query); + const result = await fetcher({ - query: document, + query, variables: info.variableValues, context: { graphqlContext: context } }); @@ -95,13 +100,15 @@ export function createResolver(fetcher: Fetcher): GraphQLFieldResolver function createSubscriptionResolver(link: ApolloLink): ResolverFn { return (root, args, context, info) => { const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]); - const document = { + let query: DocumentNode = { kind: Kind.DOCUMENT, definitions: [info.operation, ...fragments] }; + query = addTypenameToAbstract(info.schema, query); + const operation = { - query: document, + query, variables: info.variableValues, context: { graphqlContext: context } }; diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index e17ad9c0b92..0e30c64172a 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { forAwaitEach } from 'iterall'; -import { GraphQLSchema, ExecutionResult, subscribe, parse } from 'graphql'; +import { GraphQLSchema, ExecutionResult, subscribe, parse, graphql } from 'graphql'; import { + propertySchema, subscriptionSchema, subscriptionPubSubTrigger, subscriptionPubSub, @@ -11,6 +12,47 @@ import { } from '../test/testingSchemas'; import { makeRemoteExecutableSchema } from '../stitching'; +describe('remote queries', () => { + let schema: GraphQLSchema; + before(async () => { + const remoteSchemaExecConfig = await makeSchemaRemoteFromLink(propertySchema); + schema = makeRemoteExecutableSchema({ + schema: remoteSchemaExecConfig.schema, + link: remoteSchemaExecConfig.link + }); + }); + + it('should work', async () => { + const query = ` + { + interfaceTest(kind: ONE) { + kind + testString + ...on TestImpl1 { + foo + } + ...on TestImpl2 { + bar + } + } + } + `; + + const expected = { + data: { + interfaceTest: { + foo: 'foo', + kind: 'ONE', + testString: 'test', + }, + }, + }; + + const result = await graphql(schema, query); + expect(result).to.deep.equal(expected); + }); +}); + describe('remote subscriptions', () => { let schema: GraphQLSchema; before(async () => { diff --git a/src/transforms/AddTypenameToAbstract.ts b/src/transforms/AddTypenameToAbstract.ts index ef790062219..9e9bf75ca82 100644 --- a/src/transforms/AddTypenameToAbstract.ts +++ b/src/transforms/AddTypenameToAbstract.ts @@ -1,18 +1,7 @@ -import { - DocumentNode, - FieldNode, - GraphQLInterfaceType, - GraphQLSchema, - GraphQLType, - GraphQLUnionType, - Kind, - SelectionSetNode, - TypeInfo, - visit, - visitWithTypeInfo, -} from 'graphql'; +import { GraphQLSchema } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; +import { addTypenameToAbstract } from '../stitching/addTypeNameToAbstract'; export default class AddTypenameToAbstract implements Transform { private targetSchema: GraphQLSchema; @@ -32,46 +21,3 @@ export default class AddTypenameToAbstract implements Transform { }; } } - -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 && - (parentType instanceof GraphQLInterfaceType || - parentType instanceof GraphQLUnionType) && - !selections.find( - _ => - (_ as FieldNode).kind === Kind.FIELD && - (_ as FieldNode).name.value === '__typename', - ) - ) { - selections = selections.concat({ - kind: Kind.FIELD, - name: { - kind: Kind.NAME, - value: '__typename', - }, - }); - } - - if (selections !== node.selections) { - return { - ...node, - selections, - }; - } - }, - }), - ); -} From b4df9ade96d44d301bd0412bcc34ec9385fcabce Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 24 Sep 2019 19:52:10 -0400 Subject: [PATCH 077/250] fix(delegateToSchema): fix typo breaking refactoring --- src/transforms/AddTypenameToAbstract.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transforms/AddTypenameToAbstract.ts b/src/transforms/AddTypenameToAbstract.ts index 9e9bf75ca82..be773a65b6a 100644 --- a/src/transforms/AddTypenameToAbstract.ts +++ b/src/transforms/AddTypenameToAbstract.ts @@ -1,7 +1,7 @@ import { GraphQLSchema } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; -import { addTypenameToAbstract } from '../stitching/addTypeNameToAbstract'; +import { addTypenameToAbstract } from '../stitching/addTypenameToAbstract'; export default class AddTypenameToAbstract implements Transform { private targetSchema: GraphQLSchema; From 3352229c4686aa99b7ffef0e521d75a032201a18 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 24 Sep 2019 16:03:56 -0400 Subject: [PATCH 078/250] docs(delegateToSchema): update use cases and defaultMergedResolver usage --- docs/source/schema-delegation.md | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/docs/source/schema-delegation.md b/docs/source/schema-delegation.md index 0a8949a8638..5ccf5ad9602 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: { @@ -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. +[Transforms](/schema-transforms/) to apply to the query and results. Should be the same transforms that were used to transform the schema, if any. For convenience, after transformation, `transformedSchema.transforms` contains the transforms that were applied. From 19295f3e1bbca74b8a832ca8faa01be18fce1951 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 25 Sep 2019 19:36:03 -0400 Subject: [PATCH 079/250] chore(tests): add tests for cloneSchema --- src/test/testMergeSchemas.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 026a991b8fd..72b32ae9864 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -33,6 +33,7 @@ import { SchemaExecutionConfig, } from '../Interfaces'; import { delegateToSchema } from '../stitching'; +import { cloneSchema } from '../utils'; const removeLocations = ({ locations, ...rest }: any): any => ({ ...rest }); @@ -55,6 +56,12 @@ const testCombinations = [ property: remotePropertySchema, product: localProductSchema, }, + { + name: 'recreated', + booking: cloneSchema(localBookingSchema), + property: cloneSchema(localPropertySchema), + product: cloneSchema(localProductSchema), + } ]; let scalarTest = ` From 9cb29df465610fd8460c4855f1e4ec838d905874 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 26 Sep 2019 05:42:59 -0400 Subject: [PATCH 080/250] feat(utils): getResolversFromSchema add function to get graphql-tools style resolvers map from an existing schema, parallels graphql-js toConfig. --- src/test/testMergeSchemas.ts | 7 +++- src/utils/getResolversFromSchema.ts | 64 +++++++++++++++++++++++++++++ src/utils/index.ts | 1 + 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/utils/getResolversFromSchema.ts diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 72b32ae9864..648a8dcccf9 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -12,6 +12,7 @@ import { ExecutionResult, defaultFieldResolver, findDeprecatedUsages, + printSchema, } from 'graphql'; import mergeSchemas from '../stitching/mergeSchemas'; import { @@ -34,6 +35,7 @@ import { } from '../Interfaces'; import { delegateToSchema } from '../stitching'; import { cloneSchema } from '../utils'; +import { getResolversFromSchema } from '../utils/getResolversFromSchema'; const removeLocations = ({ locations, ...rest }: any): any => ({ ...rest }); @@ -59,7 +61,10 @@ const testCombinations = [ { name: 'recreated', booking: cloneSchema(localBookingSchema), - property: cloneSchema(localPropertySchema), + property: makeExecutableSchema({ + typeDefs: printSchema(localPropertySchema), + resolvers: getResolversFromSchema(localPropertySchema), + }), product: cloneSchema(localProductSchema), } ]; diff --git a/src/utils/getResolversFromSchema.ts b/src/utils/getResolversFromSchema.ts new file mode 100644 index 00000000000..5b467b75c51 --- /dev/null +++ b/src/utils/getResolversFromSchema.ts @@ -0,0 +1,64 @@ +import { + GraphQLSchema, + GraphQLScalarType, + GraphQLEnumType, + GraphQLObjectType, + GraphQLInterfaceType, + GraphQLUnionType, +} from 'graphql'; +import { IResolvers } from '../Interfaces'; +import isSpecifiedScalarType from './isSpecifiedScalarType'; +import { cloneType } from './clone'; + +export function getResolversFromSchema(schema: GraphQLSchema): IResolvers { + const resolvers = Object.create({}); + + const typeMap = schema.getTypeMap(); + + Object.keys(typeMap).forEach(typeName => { + const type = typeMap[typeName]; + + if (type instanceof GraphQLScalarType) { + if (!isSpecifiedScalarType(type)) { + resolvers[typeName] = cloneType(type); + } + } else if (type instanceof GraphQLEnumType) { + resolvers[typeName] = {}; + + const values = type.getValues(); + values.forEach(value => { + resolvers[typeName][value.name] = value.value; + }); + } else if (type instanceof GraphQLInterfaceType) { + if (type.resolveType) { + resolvers[typeName] = { + __resolveType: type.resolveType, + }; + } + } else if (type instanceof GraphQLUnionType) { + if (type.resolveType) { + resolvers[typeName] = { + __resolveType: type.resolveType, + }; + } + } else if (type instanceof GraphQLObjectType) { + resolvers[typeName] = {}; + + if (type.isTypeOf) { + resolvers[typeName].__isTypeOf = type.isTypeOf; + } + + const fields = type.getFields(); + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + + resolvers[typeName][fieldName] = { + resolve: field.resolve, + subscribe: field.subscribe, + }; + }); + } + }); + + return resolvers; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 2b01b5c4ea1..cabd47a08d5 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,3 +3,4 @@ export { healSchema, healTypes } from './heal'; export { SchemaVisitor } from './SchemaVisitor'; export { SchemaDirectiveVisitor } from './SchemaDirectiveVisitor'; export { visitSchema } from './visitSchema'; +export { getResolversFromSchema } from './getResolversFromSchema'; From 686d9d5efe3e48e603c9e482b71beca92a5e743d Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 3 Oct 2019 08:39:31 -0400 Subject: [PATCH 081/250] fix(transforms): should work with remote schema execution config. --- src/stitching/resolvers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 9e93f3c4bac..353b848205f 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -47,7 +47,7 @@ export function generateProxyingResolvers( to.operation === 'subscription' ? 'subscribe' : 'resolve'; result[name][from] = { [resolverType]: createProxyingResolver( - targetSchema, + schemaOrSchemaExecutionConfig, to.operation, to.name, transforms, From 65725684c127a208f59c5242e8758f055977f163 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 3 Oct 2019 08:54:49 -0400 Subject: [PATCH 082/250] lint(resolveFromParentTypename): use same name even for default import --- src/stitching/typeFromAST.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stitching/typeFromAST.ts b/src/stitching/typeFromAST.ts index 300fd933f38..2026f8f6189 100644 --- a/src/stitching/typeFromAST.ts +++ b/src/stitching/typeFromAST.ts @@ -29,7 +29,7 @@ import { GraphQLFieldConfig, StringValueNode, } from 'graphql'; -import resolveFromParentType from './resolveFromParentTypename'; +import resolveFromParentTypename from './resolveFromParentTypename'; import { createNamedStub } from '../utils/stub'; const backcompatOptions = { commentDescriptions: true }; @@ -85,7 +85,7 @@ function makeInterfaceType( fields: () => makeFields(node.fields), description: getDescription(node, backcompatOptions), resolveType: (parent, context, info) => - resolveFromParentType(parent, info.schema), + resolveFromParentTypename(parent, info.schema), }); } @@ -116,7 +116,7 @@ function makeUnionType( ), description: getDescription(node, backcompatOptions), resolveType: (parent, context, info) => - resolveFromParentType(parent, info.schema), + resolveFromParentTypename(parent, info.schema), }); } From 50d825050595b62e54281695ca8ba94cb8c24ffe Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 3 Oct 2019 11:20:49 -0400 Subject: [PATCH 083/250] refactor(healSchema): schema recreation should be part of all healing --- src/utils/heal.ts | 6 ++++++ src/utils/visitSchema.ts | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/utils/heal.ts b/src/utils/heal.ts index ea9632a1720..63d68a48bd8 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -17,6 +17,7 @@ import each from './each'; import updateEachKey from './updateEachKey'; import { VisitableSchemaType } from '../Interfaces'; import { isStub, getBuiltInForStub } from './stub'; +import { cloneSchema } from './clone'; type NamedTypeMap = { [key: string]: GraphQLNamedType; @@ -28,6 +29,11 @@ const hasOwn = Object.prototype.hasOwnProperty; // types found in schema.getTypeMap(). export function healSchema(schema: GraphQLSchema): GraphQLSchema { healTypes(schema.getTypeMap(), schema.getDirectives()); + + // Reconstruct the schema to reinitialize private variables + // e.g. the stored implementation map and the proper root types. + Object.assign(schema, cloneSchema(schema)); + return schema; } diff --git a/src/utils/visitSchema.ts b/src/utils/visitSchema.ts index 8f8668a2e28..a8b6b7d9f14 100644 --- a/src/utils/visitSchema.ts +++ b/src/utils/visitSchema.ts @@ -15,7 +15,6 @@ import { VisitableSchemaType, VisitorSelector, VisitSchemaKind, SchemaVisitorMap import { healSchema } from './heal'; import { SchemaVisitor } from './SchemaVisitor'; import each from './each'; -import { cloneSchema } from './clone'; // Generic function for visiting GraphQLSchema objects. export function visitSchema( @@ -225,10 +224,6 @@ export function visitSchema( // during the traversal, so implementors don't have to worry about that. healSchema(schema); - // Reconstruct the schema to reinitialize private variables - // e.g. the stored implementation map. - Object.assign(schema, cloneSchema(schema)); - // Return schema for convenience, even though schema parameter has all updated types. return schema; } From 9e87082ec4de2d2792dc33026f10bda43d6f1f78 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 3 Oct 2019 11:44:59 -0400 Subject: [PATCH 084/250] feat(WrapType): add WrapType transform --- src/test/testAlternateMergeSchemas.ts | 147 ++++++++++++++++++++++++++ src/transforms/WrapType.ts | 63 +++++++++++ src/transforms/index.ts | 1 + 3 files changed, 211 insertions(+) create mode 100644 src/transforms/WrapType.ts diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index c83827303fa..9b291dc3b76 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -21,6 +21,7 @@ import { RenameObjectFields, TransformObjectFields, ExtendSchema, + WrapType, } from '../transforms'; import { propertySchema, @@ -485,6 +486,152 @@ type Query { }); }); +describe('WrapType transform', () => { + let transformedPropertySchema: GraphQLSchema; + + before(async () => { + transformedPropertySchema = transformSchema(propertySchema, [ + new WrapType('Query', 'Namespace_Query', 'namespace'), + ]); + }); + + it('should modify the schema', () => { + /* tslint:disable:max-line-length */ + expect(printSchema(transformedPropertySchema)).to.equal(`type Address { + street: String + city: String + state: String + zip: String +} + +"""Simple fake datetime""" +scalar DateTime + +input InputWithDefault { + test: String = "Foo" +} + +""" +The \`JSON\` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). +""" +scalar JSON + +type Location { + name: String! +} + +type Namespace_Query { + propertyById(id: ID!): Property + properties(limit: Int): [Property!] + contextTest(key: String!): String + dateTimeTest: DateTime + jsonTest(input: JSON): JSON + interfaceTest(kind: TestInterfaceKind): TestInterface + unionTest(output: String): TestUnion + errorTest: String + errorTestNonNull: String! + relay: Query! + defaultInputTest(input: InputWithDefault!): String +} + +type Property { + id: ID! + name: String! + location: Location + address: Address + error: String +} + +type Query { + namespace: Namespace_Query +} + +type TestImpl1 implements TestInterface { + kind: TestInterfaceKind + testString: String + foo: String +} + +type TestImpl2 implements TestInterface { + kind: TestInterfaceKind + testString: String + bar: String +} + +interface TestInterface { + kind: TestInterfaceKind + testString: String +} + +enum TestInterfaceKind { + ONE + TWO +} + +union TestUnion = TestImpl1 | UnionImpl + +type UnionImpl { + someField: String +} +` + /* tslint:enable:max-line-length */ + ); + }); + + it('should work', async () => { + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + namespace { + propertyById(id: $pid) { + id + name + error + } + } + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + expect(result).to.deep.equal({ + data: { + namespace: { + propertyById: { + id: 'p1', + name: 'Super great hotel', + error: null, + }, + }, + }, + errors: [ + { + extensions: { + code: 'SOME_CUSTOM_CODE', + }, + locations: [ + { + column: 15, + line: 7, + }, + ], + message: 'Property.error error', + path: [ + 'namespace', + 'propertyById', + 'error', + ], + }, + ] + }); + }); +}); + describe('ExtendSchema transform', () => { let transformedPropertySchema: GraphQLSchema; diff --git a/src/transforms/WrapType.ts b/src/transforms/WrapType.ts new file mode 100644 index 00000000000..3fa8ce945b6 --- /dev/null +++ b/src/transforms/WrapType.ts @@ -0,0 +1,63 @@ +/* tslint:disable:no-unused-expression */ + +import { GraphQLSchema, GraphQLObjectType } from 'graphql'; +import { Request } from '../Interfaces'; +import { Transform } from './transforms'; +import { cloneType, healSchema } from '../utils'; +import { extractFields } from '../stitching'; +import { default as ExtendSchema } from './ExtendSchema'; + +export default class WrapType implements Transform { + private outerTypeName: string; + private innerTypeName: string; + private fieldName: string; + private transformer: Transform; + + constructor( + outerTypeName: string, + innerTypeName: string, + fieldName: string + ) { + this.outerTypeName = outerTypeName; + this.innerTypeName = innerTypeName; + this.fieldName = fieldName; + this.transformer = new ExtendSchema({ + resolvers: { + [outerTypeName]: { + [fieldName]: parent => parent, + }, + }, + fieldNodeTransformerMap: { + [outerTypeName]: { + [fieldName]: (fieldNode, fragments) => extractFields({ fieldNode, fragments }), + }, + } + }); + } + + public transformSchema(schema: GraphQLSchema): GraphQLSchema { + const typeMap = schema.getTypeMap(); + + // Clone the outer type before modification. + // When healing, changing the type name of a root type changes the root type name. + const innerType = cloneType(typeMap[this.outerTypeName]); + innerType.name = this.innerTypeName; + + typeMap[this.innerTypeName] = innerType; + + typeMap[this.outerTypeName] = new GraphQLObjectType({ + name: this.outerTypeName, + fields: { + [this.fieldName]: { + type: typeMap[this.innerTypeName] as GraphQLObjectType, + }, + }, + }); + + return this.transformer.transformSchema(healSchema(schema)); + } + + public transformRequest(originalRequest: Request): Request { + return this.transformer.transformRequest(originalRequest); + } +} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index b97636c0551..fd72dfffdec 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -24,4 +24,5 @@ export { default as WrapQuery } from './WrapQuery'; export { default as TransformQuery } from './TransformQuery'; export { default as ExtendSchema } from './ExtendSchema'; +export { default as WrapType } from './WrapType'; export { default as MapFields } from './MapFields'; From a9a6ff6aa8448e3376310d612991219c8e68a936 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 6 Oct 2019 05:59:05 -0400 Subject: [PATCH 085/250] fix(transforms): to properly allow chaining. BREAKING CHANGE: Result transforms should be reversed, so that multiple request and result transforms can be properly executed. Request and result transforms must be inverted when transforming a schema, as these transforms work to move from the final schema back to the original schema. These CHANGES have the potential to be BREAKING. --- src/stitching/delegateToSchema.ts | 2 +- src/test/testTransforms.ts | 3 ++- src/transforms/transformSchema.ts | 4 ++-- src/transforms/transforms.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index da1ef093601..73cdddc4983 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -88,6 +88,7 @@ async function delegateToSchemaImplementation( }; let transforms = [ + new CheckResultAndHandleErrors(info, options.fieldName), ...(options.transforms || []), new ExpandAbstractTypes(info.schema, targetSchema), ]; @@ -108,7 +109,6 @@ async function delegateToSchemaImplementation( transforms = transforms.concat([ new FilterToSchema(targetSchema), new AddTypenameToAbstract(targetSchema), - new CheckResultAndHandleErrors(info, options.fieldName), ]); const processedRequest = applyRequestTransforms(rawRequest, transforms); diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 0dba395c698..7d5049fda89 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -160,7 +160,8 @@ describe('transforms', () => { let schema: GraphQLSchema; before(() => { const transforms = [ - new RenameTypes((name: string) => `Property_${name}`), + new RenameTypes((name: string) => `_${name}`), + new RenameTypes((name: string) => `Property${name}`), ]; schema = transformSchema(propertySchema, transforms); }); diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index a63bc06ce79..781150855c0 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -25,7 +25,7 @@ export function wrapSchema( const resolvers = generateProxyingResolvers( schemaOrSchemaExecutionConfig, - transforms, + transforms.slice().reverse(), ); addResolveFunctionsToSchema({ schema, @@ -43,6 +43,6 @@ export default function transformSchema( transforms: Array, ): GraphQLSchema & { transforms: Array } { const schema = wrapSchema(schemaOrSchemaExecutionConfig, transforms); - (schema as any).transforms = transforms; + (schema as any).transforms = transforms.slice().reverse(); return schema as GraphQLSchema & { transforms: Array }; } diff --git a/src/transforms/transforms.ts b/src/transforms/transforms.ts index 7a84cb1bfe1..2b1a1d3504e 100644 --- a/src/transforms/transforms.ts +++ b/src/transforms/transforms.ts @@ -32,7 +32,7 @@ export function applyResultTransforms( originalResult: any, transforms: Array, ): any { - return transforms.reduce( + return transforms.reduceRight( (result: any, transform: Transform) => transform.transformResult ? transform.transformResult(result) : result, originalResult, From c3abc073db499a4ebec08c433c55c9c718b9a504 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 6 Oct 2019 06:02:20 -0400 Subject: [PATCH 086/250] refactor: remove unused composeTransforms function --- src/transforms/transforms.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/transforms/transforms.ts b/src/transforms/transforms.ts index 2b1a1d3504e..1ac56354d08 100644 --- a/src/transforms/transforms.ts +++ b/src/transforms/transforms.ts @@ -1,5 +1,5 @@ import { GraphQLSchema } from 'graphql'; -import { Request, Result, Transform } from '../Interfaces'; +import { Request, Transform } from '../Interfaces'; export { Transform }; @@ -38,18 +38,3 @@ export function applyResultTransforms( originalResult, ); } - -export function composeTransforms(...transforms: Array): Transform { - const reverseTransforms = [...transforms].reverse(); - return { - transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { - return applySchemaTransforms(originalSchema, transforms); - }, - transformRequest(originalRequest: Request): Request { - return applyRequestTransforms(originalRequest, reverseTransforms); - }, - transformResult(result: Result): Result { - return applyResultTransforms(result, reverseTransforms); - }, - }; -} From 52128a3d800fec150f15ca1ca58b2ec794aba4ad Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 10 Oct 2019 20:10:56 -0400 Subject: [PATCH 087/250] fix(types): enforce VisitSchemaKind enum --- src/Interfaces.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index b54742a69fb..75c71f6c5c9 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -302,8 +302,7 @@ export enum VisitSchemaKind { SUBSCRIPTION = 'VisitSchemaKind.SUBSCRIPTION', } -// I couldn't make keys to be forced to be enum values -export type SchemaVisitorMap = { [key: string]: TypeVisitor }; +export type SchemaVisitorMap = { [key in VisitSchemaKind]?: TypeVisitor }; export type TypeVisitor = ( type: GraphQLType, schema: GraphQLSchema, From 9a5b06a46049fc668e89ef9eab6662fc989c2542 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 3 Oct 2019 09:09:33 -0400 Subject: [PATCH 088/250] feat(RenameTypes): remove extra result traversal RenameTypes transform now relies on wrapping the resolveType methods within the outer schema instead of using transformResult. Required changing the resolveFromParentTypename to return a string instead of an object, as the string may need to be renamed before it corresponds to a type in the outer schema. So that the transform can be used as a operation transform as well, the transformResult function is preserved, but a new flag is used to skip result transformation when the resolvers will be responsible for transforming the result, i.e. when used with transformSchema/wrapSchema. --- src/Interfaces.ts | 1 + src/stitching/errors.ts | 4 ++++ src/stitching/resolveFromParentTypename.ts | 12 ++--------- src/transforms/RenameTypes.ts | 25 ++++++++++++++++++---- src/transforms/transformSchema.ts | 8 ++++++- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 75c71f6c5c9..bdd8aa944c6 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -57,6 +57,7 @@ export type Transform = { transformSchema?: (schema: GraphQLSchema) => GraphQLSchema; transformRequest?: (originalRequest: Request) => Request; transformResult?: (result: Result) => Result; + resolversTransformResult?: boolean; }; export interface IGraphQLToolsResolveInfo extends GraphQLResolveInfo { diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 90079f570d6..261b8045bb7 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -82,6 +82,10 @@ export function annotateWithChildrenErrors(object: any, childrenErrors: Readonly return object; } +export function isParentProxiedResult(parent: any) { + return parent && parent[ERROR_SYMBOL]; +} + export function getErrorsFromParent( object: any, fieldName: string diff --git a/src/stitching/resolveFromParentTypename.ts b/src/stitching/resolveFromParentTypename.ts index 88776b6838b..06bfbec722d 100644 --- a/src/stitching/resolveFromParentTypename.ts +++ b/src/stitching/resolveFromParentTypename.ts @@ -1,4 +1,4 @@ -import { GraphQLObjectType, GraphQLSchema } from 'graphql'; +import { GraphQLSchema } from 'graphql'; export default function resolveFromParentTypename( parent: any, @@ -11,13 +11,5 @@ export default function resolveFromParentTypename( ); } - const resolvedType = schema.getType(parentTypename); - - if (!(resolvedType instanceof GraphQLObjectType)) { - throw new Error( - '__typename did not match an object type: ' + parentTypename, - ); - } - - return resolvedType; + return parentTypename; } diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index f68d73a6dff..e984231464d 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -5,11 +5,13 @@ import { Kind, GraphQLNamedType, GraphQLScalarType, + GraphQLAbstractType, } from 'graphql'; import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; import { Request, Result, VisitSchemaKind } from '../Interfaces'; import { Transform } from '../transforms/transforms'; -import { visitSchema } from '../utils/visitSchema'; +import { isParentProxiedResult } from '../stitching/errors'; +import { visitSchema, cloneType } from '../utils'; export type RenameOptions = { renameBuiltins: boolean; @@ -17,6 +19,8 @@ export type RenameOptions = { }; export default class RenameTypes implements Transform { + public readonly resolversTransformResult = true; + private renamer: (name: string) => string | undefined; private reverseMap: { [key: string]: string }; private renameBuiltins: boolean; @@ -34,7 +38,7 @@ export default class RenameTypes implements Transform { } public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { - return visitSchema(originalSchema, { + return visitSchema(originalSchema, [{ [VisitSchemaKind.TYPE]: (type: GraphQLNamedType) => { if (isSpecifiedScalarType(type) && !this.renameBuiltins) { return undefined; @@ -45,7 +49,7 @@ export default class RenameTypes implements Transform { const newName = this.renamer(type.name); if (newName && newName !== type.name) { this.reverseMap[newName] = type.name; - const newType = Object.assign(Object.create(type), type); + const newType = cloneType(type); newType.name = newName; return newType; } @@ -54,7 +58,20 @@ export default class RenameTypes implements Transform { [VisitSchemaKind.ROOT_OBJECT](type: GraphQLNamedType) { return undefined; }, - }); + }, { + [VisitSchemaKind.ABSTRACT_TYPE]: (type: GraphQLAbstractType) => { + const originalResolveType = type.resolveType; + type.resolveType = (value, info, context) => { + if (isParentProxiedResult(value)) { + const oldName = originalResolveType(value, info, context) as string; + const newName = this.renamer(oldName); + return newName ? newName : oldName; + } + return originalResolveType(value, info, context); + }; + return type; + }, + }]); } public transformRequest(originalRequest: Request): Request { diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index 781150855c0..b50dbcf51bf 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -25,7 +25,13 @@ export function wrapSchema( const resolvers = generateProxyingResolvers( schemaOrSchemaExecutionConfig, - transforms.slice().reverse(), + transforms.slice().reverse().map(transform => { + return transform.resolversTransformResult + ? { + transformRequest: originalRequest => transform.transformRequest(originalRequest) + } : + transform; + }), ); addResolveFunctionsToSchema({ schema, From eca442f20e45915afd78b1424b313fdf15c27d05 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 12 Oct 2019 22:17:53 -0400 Subject: [PATCH 089/250] fix(docs): use delegateToSchema instead of info.mergeInfo.delegateToSchema --- docs/source/schema-delegation.md | 2 +- docs/source/schema-stitching.md | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/schema-delegation.md b/docs/source/schema-delegation.md index 5ccf5ad9602..e1ba6c1f39d 100644 --- a/docs/source/schema-delegation.md +++ b/docs/source/schema-delegation.md @@ -170,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', diff --git a/docs/source/schema-stitching.md b/docs/source/schema-stitching.md index 7a4d3f3b9e8..da1ef8cf60d 100644 --- a/docs/source/schema-stitching.md +++ b/docs/source/schema-stitching.md @@ -120,7 +120,7 @@ We won't be able to query `User.chirps` or `Chirp.author` yet, however, because 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 for fields in schemas created by `mergeSchema` 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. @@ -138,7 +138,7 @@ const mergedSchema = mergeSchemas({ chirps: { fragment: `... on User { id }`, resolve(user, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToSchema({ schema: chirpSchema, operation: 'query', fieldName: 'chirpsByAuthorId', @@ -155,7 +155,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', @@ -240,7 +240,7 @@ const mergedSchema = mergeSchemas({ chirps: { fragment: `... on User { id }`, resolve(user, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToSchema({ schema: chirpSchema, operation: 'query', fieldName: 'chirpsByAuthorId', @@ -258,7 +258,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,7 +277,7 @@ 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. That's because we're delegating to the original `chirpSchema`, which has not been modified by the transforms. ## Complex example @@ -328,7 +328,7 @@ resolvers: { property: { fragment: '... on Booking { propertyId }', resolve(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ + return delegateToSchema({ schema: bookingSchema, operation: 'query', fieldName: 'propertyById', @@ -368,7 +368,7 @@ interface IDelegateToSchemaOptions Date: Sat, 12 Oct 2019 22:58:48 -0400 Subject: [PATCH 090/250] fix(docs): expands transforms docs --- docs/source/schema-delegation.md | 2 +- docs/source/schema-transforms.md | 29 ++++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/source/schema-delegation.md b/docs/source/schema-delegation.md index e1ba6c1f39d..360c349fa82 100644 --- a/docs/source/schema-delegation.md +++ b/docs/source/schema-delegation.md @@ -197,4 +197,4 @@ GraphQL resolve info of the current resolver. Provides access to the subquery th #### 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. For convenience, after transformation, `transformedSchema.transforms` contains the transforms that were applied. +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-transforms.md b/docs/source/schema-transforms.md index 389368f51f7..01a31b20112 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 and/or 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 outer schema that simply delegates all operations to the original inner schema. Each schema transform includes a function that changes the outer wrapping 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 inner, original schema, 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,15 @@ type Result = ExecutionResult & { }; ``` -### transformSchema +### wrapSchema / transformSchema 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 outer schema. + +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 original schema from an outer schema. ## Built-in transforms @@ -218,7 +218,9 @@ RenameObjectFields( ) ``` -### Other +### 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. @@ -298,8 +300,9 @@ transforms: [ The following transforms are automatically applied by `delegateToSchema` during schema delegation, to translate between new and old types and fields: * `AddArgumentsAsVariables`: Given a schema and arguments passed to a root field, make those arguments document variables. +* `ExpandAbstractTypes`: If an abstract type within a document does not exist in the inner schema, expand the type to each and any of its implementations that do exist in the inner schema. * `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. +* `AddTypenameToAbstract`: Add `__typename` to all abstract types in the document, necessary for type resolution of interfaces within the outer 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. From 70fceca9af04a859388ba193a7acea97aa57e8f8 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 13 Oct 2019 07:49:46 -0400 Subject: [PATCH 091/250] fix(RenameTypes): add required arg --- package.json | 10 +++++----- src/transforms/RenameTypes.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 16bf9cc1a03..e320b6f9d2c 100644 --- a/package.json +++ b/package.json @@ -56,13 +56,13 @@ "uuid": "^3.3.3" }, "peerDependencies": { - "graphql": "^14.2.0" + "graphql": "^14.5.8" }, "devDependencies": { "@types/chai": "4.2.0", "@types/dateformat": "^3.0.0", "@types/mocha": "^5.2.7", - "@types/node": "^12.7.2", + "@types/node": "^12.7.12", "@types/uuid": "^3.4.5", "@types/zen-observable": "^0.8.0", "body-parser": "^1.19.0", @@ -73,13 +73,13 @@ "graphql-subscriptions": "^1.1.0", "graphql-type-json": "^0.3.0", "istanbul": "^0.4.5", - "mocha": "^6.2.0", + "mocha": "^6.2.1", "prettier": "^1.18.2", "remap-istanbul": "0.13.0", "rimraf": "^3.0.0", "source-map-support": "^0.5.13", "standard-version": "^7.0.0", - "tslint": "^5.19.0", - "typescript": "3.5.3" + "tslint": "^5.20.0", + "typescript": "3.6.4" } } diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index e984231464d..c3afab33583 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -61,13 +61,13 @@ export default class RenameTypes implements Transform { }, { [VisitSchemaKind.ABSTRACT_TYPE]: (type: GraphQLAbstractType) => { const originalResolveType = type.resolveType; - type.resolveType = (value, info, context) => { + type.resolveType = (value, info, context, abstractType) => { if (isParentProxiedResult(value)) { - const oldName = originalResolveType(value, info, context) as string; + const oldName = originalResolveType(value, info, context, abstractType) as string; const newName = this.renamer(oldName); return newName ? newName : oldName; } - return originalResolveType(value, info, context); + return originalResolveType(value, info, context, abstractType); }; return type; }, From 47cbda7d43fb0f5fd492cb9eab80284791bfd122 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 19 Oct 2019 22:43:11 -0400 Subject: [PATCH 092/250] fix(deps): correct graphql peer dependency. Last npm-check-updates update upgraded the peer dependency instead of dev dependency. --- .travis.yml | 4 ---- package.json | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 72d3c92e505..99171bb7f88 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,3 @@ sudo: false env: - GRAPHQL_VERSION='^14.0' TYPES_GRAPHQL_VERSION='^14.0' - - GRAPHQL_VERSION='~14.5' TYPES_GRAPHQL_VERSION='~14.5' - - GRAPHQL_VERSION='~14.4' TYPES_GRAPHQL_VERSION='~14.2' - - GRAPHQL_VERSION='~14.3' TYPES_GRAPHQL_VERSION='~14.2' - - GRAPHQL_VERSION='~14.2' TYPES_GRAPHQL_VERSION='~14.2' diff --git a/package.json b/package.json index e320b6f9d2c..f49ea52a7db 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "uuid": "^3.3.3" }, "peerDependencies": { - "graphql": "^14.5.8" + "graphql": "^14.2.0" }, "devDependencies": { "@types/chai": "4.2.0", @@ -69,7 +69,7 @@ "chai": "^4.2.0", "dateformat": "^3.0.3", "express": "^4.17.1", - "graphql": "^14.5.3", + "graphql": "^14.5.8", "graphql-subscriptions": "^1.1.0", "graphql-type-json": "^0.3.0", "istanbul": "^0.4.5", From 24d17bb0103a0da0168877a9d987d60d17729960 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 23 Oct 2019 23:04:44 -0400 Subject: [PATCH 093/250] refactor(mergeSchemas): consolidate code Remove unnecessary MergeTypeCandidatesResult type. Use new SchemaLikeObject type. --- src/Interfaces.ts | 13 ++- src/stitching/mergeSchemas.ts | 213 +++++++++++++++------------------- 2 files changed, 101 insertions(+), 125 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index bdd8aa944c6..c99d5a1f9dd 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -84,10 +84,15 @@ export type SchemaExecutionConfig = { export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array }; -export function isSchemaExecutionConfig( - schema: string | GraphQLSchema | SchemaExecutionConfig | DocumentNode | Array -): schema is SchemaExecutionConfig { - return !!(schema as SchemaExecutionConfig).schema; +export type SchemaLikeObject = + SchemaExecutionConfig | + GraphQLSchema | + string | + DocumentNode | + Array; + +export function isSchemaExecutionConfig(value: SchemaLikeObject): value is SchemaExecutionConfig { + return !!(value as SchemaExecutionConfig).schema; } export interface IDelegateToSchemaOptions { diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index d79c30d5650..19ecd41366d 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -16,12 +16,14 @@ import { import { IDelegateToSchemaOptions, IFieldResolver, - IResolvers, MergeInfo, OnTypeConflict, IResolversParameter, SchemaExecutionConfig, isSchemaExecutionConfig, + SchemaLikeObject, + GraphQLSchemaWithTransforms, + IResolvers, } from '../Interfaces'; import { extractExtensionDefinitions, @@ -46,12 +48,6 @@ type MergeTypeCandidate = { type: GraphQLNamedType; }; -type MergeTypeCandidatesResult = { - type?: GraphQLNamedType; - resolvers?: IResolvers; - candidate?: MergeTypeCandidate; -}; - type CandidateSelector = ( candidates: Array, ) => MergeTypeCandidate; @@ -64,9 +60,7 @@ export default function mergeSchemas({ inheritResolversFromInterfaces, mergeDirectives, }: { - schemas: Array< - string | GraphQLSchema | SchemaExecutionConfig | DocumentNode | Array - >; + schemas: Array; onTypeConflict?: OnTypeConflict; resolvers?: IResolversParameter; schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor }; @@ -83,42 +77,34 @@ export default function mergeSchemas({ fragment: string; }> = []; - schemas.forEach(schemaOrSchemaExecutionConfig => { - let schema: string | GraphQLSchema | SchemaExecutionConfig | DocumentNode | Array; - let executionConfig: SchemaExecutionConfig; - if (isSchemaExecutionConfig(schemaOrSchemaExecutionConfig)) { - executionConfig = schemaOrSchemaExecutionConfig; - schema = schemaOrSchemaExecutionConfig.schema; - } else { - schema = schemaOrSchemaExecutionConfig; - } + schemas.forEach(schemaLikeObject => { + if (schemaLikeObject instanceof GraphQLSchema || isSchemaExecutionConfig(schemaLikeObject)) { + let schema: GraphQLSchemaWithTransforms; + let executionConfig: SchemaExecutionConfig; + if (isSchemaExecutionConfig(schemaLikeObject)) { + executionConfig = schemaLikeObject; + schema = schemaLikeObject.schema; + } else { + schema = schemaLikeObject; + } - if (schema instanceof GraphQLSchema) { allSchemas.push(schema); - const queryType = schema.getQueryType(); - const mutationType = schema.getMutationType(); - const subscriptionType = schema.getSubscriptionType(); - if (queryType) { - addTypeCandidate(typeCandidates, 'Query', { - schema, - executionConfig, - type: queryType, - }); - } - if (mutationType) { - addTypeCandidate(typeCandidates, 'Mutation', { - schema, - executionConfig, - type: mutationType, - }); - } - if (subscriptionType) { - addTypeCandidate(typeCandidates, 'Subscription', { - schema, - executionConfig, - type: subscriptionType, - }); - } + + const operationTypes = { + Query: schema.getQueryType(), + Mutation: schema.getMutationType(), + Subscription: schema.getSubscriptionType(), + }; + + Object.keys(operationTypes).forEach(typeName => { + if (operationTypes[typeName]) { + addTypeCandidate(typeCandidates, typeName, { + schema, + executionConfig, + type: operationTypes[typeName], + }); + } + }); if (mergeDirectives) { const directiveInstances = schema.getDirectives(); @@ -133,23 +119,23 @@ export default function mergeSchemas({ if ( isNamedType(type) && getNamedType(type).name.slice(0, 2) !== '__' && - type !== queryType && - type !== mutationType && - type !== subscriptionType + type !== operationTypes.Query && + type !== operationTypes.Mutation && + type !== operationTypes.Subscription ) { addTypeCandidate(typeCandidates, type.name, { - schema: schema as GraphQLSchema, + schema, executionConfig, type, }); } }); } else if ( - typeof schema === 'string' || - (schema && (schema as DocumentNode).kind === Kind.DOCUMENT) + typeof schemaLikeObject === 'string' || + (schemaLikeObject && (schemaLikeObject as DocumentNode).kind === Kind.DOCUMENT) ) { let parsedSchemaDocument = - typeof schema === 'string' ? parse(schema) : (schema as DocumentNode); + typeof schemaLikeObject === 'string' ? parse(schemaLikeObject) : (schemaLikeObject as DocumentNode); parsedSchemaDocument.definitions.forEach(def => { const type = typeFromAST(def); if (type instanceof GraphQLDirective && mergeDirectives) { @@ -167,8 +153,8 @@ export default function mergeSchemas({ if (extensionsDocument.definitions.length > 0) { extensions.push(extensionsDocument); } - } else if (Array.isArray(schema)) { - schema.forEach(type => { + } else if (Array.isArray(schemaLikeObject)) { + schemaLikeObject.forEach(type => { addTypeCandidate(typeCandidates, type.name, { type, }); @@ -199,26 +185,13 @@ export default function mergeSchemas({ }, {}); } - let generatedResolvers = {}; - Object.keys(typeCandidates).forEach(typeName => { - const mergeResult: MergeTypeCandidatesResult = mergeTypeCandidates( + const mergeResult = mergeTypeCandidates( typeName, typeCandidates[typeName], onTypeConflict ? onTypeConflictToCandidateSelector(onTypeConflict) : undefined ); - let type: GraphQLNamedType; - let typeResolvers: IResolvers; - if (mergeResult.type) { - type = mergeResult.type; - typeResolvers = mergeResult.resolvers; - } else { - throw new Error(`Invalid mergeTypeCandidates result for type ${typeName}`); - } - types[typeName] = type; - if (typeResolvers !== undefined) { - generatedResolvers[typeName] = typeResolvers; - } + types[typeName] = mergeResult; }); healTypes(types, directives, { skipPruning: true }); @@ -263,7 +236,7 @@ export default function mergeSchemas({ addResolveFunctionsToSchema({ schema: mergedSchema, - resolvers: mergeDeep(generatedResolvers, resolvers), + resolvers: resolvers as IResolvers, inheritResolversFromInterfaces }); @@ -350,14 +323,7 @@ function guessSchemaByRootField( fieldName: string, ): GraphQLSchema { for (const schema of schemas) { - let rootObject: GraphQLObjectType; - if (operation === 'subscription') { - rootObject = schema.getSubscriptionType(); - } else if (operation === 'mutation') { - rootObject = schema.getMutationType(); - } else { - rootObject = schema.getQueryType(); - } + let rootObject = operationToRootType(operation, schema); if (rootObject) { const fields = rootObject.getFields(); if (fields[fieldName]) { @@ -450,61 +416,66 @@ function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): Cand }); } + +function rootTypeNameToOperation( + name: 'Query' | 'Mutation' | 'Subscription' +): 'query' | 'mutation' | 'subscription' { + return name.toLowerCase() as 'query' | 'mutation' | 'subscription'; +} + +function operationToRootType( + operation: 'query' | 'mutation' | 'subscription', + schema: GraphQLSchema, +): GraphQLObjectType { + if (operation === 'subscription') { + return schema.getSubscriptionType(); + } else if (operation === 'mutation') { + return schema.getMutationType(); + } else { + return schema.getQueryType(); + } +} + function mergeTypeCandidates( name: string, candidates: Array, candidateSelector?: CandidateSelector -): MergeTypeCandidatesResult { +): GraphQLNamedType { if (!candidateSelector) { candidateSelector = cands => cands[cands.length - 1]; } if (name === 'Query' || name === 'Mutation' || name === 'Subscription') { - let fields = {}; - let operationName: 'query' | 'mutation' | 'subscription'; - switch (name) { - case 'Query': - operationName = 'query'; - break; - case 'Mutation': - operationName = 'mutation'; - break; - case 'Subscription': - operationName = 'subscription'; - break; - default: - break; - } - const resolvers = {}; - const resolverKey = - operationName === 'subscription' ? 'subscribe' : 'resolve'; - candidates.forEach(({ type: candidateType, schema, executionConfig }) => { - const candidateFields = (candidateType as GraphQLObjectType).toConfig().fields; - fields = { ...fields, ...candidateFields }; - Object.keys(candidateFields).forEach(fieldName => { - resolvers[fieldName] = { - [resolverKey]: schema ? createDelegatingResolver({ - schema: executionConfig ? executionConfig : schema, - operation: operationName, - fieldName, - }) : null, - }; - }); - }); - const type = new GraphQLObjectType({ - name, - fields, - }); - return { - type, - resolvers, - }; + return mergeRootTypeCandidates(name, candidates); } else { const candidate = candidateSelector(candidates); const type = cloneType(candidate.type); makeMergedType(type); - return { - type, - candidate - }; + return type; } } + +function mergeRootTypeCandidates( + name: 'Query' | 'Mutation' | 'Subscription', + candidates: Array +): GraphQLNamedType { + let operation = rootTypeNameToOperation(name); + let fields = {}; + const resolverKey = operation === 'subscription' ? 'subscribe' : 'resolve'; + candidates.forEach(candidate => { + const { type: candidateType, schema, executionConfig } = candidate; + const candidateFields = (candidateType as GraphQLObjectType).toConfig().fields; + Object.keys(candidateFields).forEach(fieldName => { + candidateFields[fieldName][resolverKey] = + schema ? createDelegatingResolver({ + schema: executionConfig ? executionConfig : schema, + operation, + fieldName, + }) : null; + }); + fields = { ...fields, ...candidateFields }; + }); + return new GraphQLObjectType({ + name, + fields, + }); +} From a1296f37e535abf8f3139fd27271a1189835c438 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 24 Oct 2019 19:39:11 -0400 Subject: [PATCH 094/250] refactor(mergeSchemas): remove variable Remove unnecessary variable. --- src/stitching/mergeSchemas.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 19ecd41366d..7ee934b6abc 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -186,12 +186,11 @@ export default function mergeSchemas({ } Object.keys(typeCandidates).forEach(typeName => { - const mergeResult = mergeTypeCandidates( + types[typeName] = mergeTypeCandidates( typeName, typeCandidates[typeName], onTypeConflict ? onTypeConflictToCandidateSelector(onTypeConflict) : undefined ); - types[typeName] = mergeResult; }); healTypes(types, directives, { skipPruning: true }); From 775747ec0d557d2c177e713e992c1e1f07914e51 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 24 Oct 2019 19:45:48 -0400 Subject: [PATCH 095/250] refactor(utils): consolidate more utils. Consolidate forEachField functions. Move forEachField and forEachDefaultValue to utils folder. Change forEachField, forEachDefaultValue and mergeDeep to no longer use default exports to fit style of other utils files. TODO: streamline use/nonuse of default exports within entire library. --- src/generate/addResolveFunctionsToSchema.ts | 11 ++++-- src/generate/assertResolveFunctionsPresent.ts | 2 +- src/generate/index.ts | 1 - src/makeExecutableSchema.ts | 8 ++-- src/mock.ts | 6 +-- src/stitching/mergeSchemas.ts | 38 +++++-------------- .../forEachDefaultValue.ts | 4 +- src/{generate => utils}/forEachField.ts | 4 +- src/utils/index.ts | 9 +++++ src/utils/mergeDeep.ts | 2 +- 10 files changed, 36 insertions(+), 49 deletions(-) rename src/{generate => utils}/forEachDefaultValue.ts (88%) rename src/{generate => utils}/forEachField.ts (85%) diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts index c9fe386a72c..1b0dc06a37b 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolveFunctionsToSchema.ts @@ -18,10 +18,13 @@ import { import SchemaError from './SchemaError'; import checkForResolveTypeResolver from './checkForResolveTypeResolver'; import extendResolversFromInterfaces from './extendResolversFromInterfaces'; -import forEachField from './forEachField'; -import forEachDefaultValue from './forEachDefaultValue'; -import { parseInputValue, serializeInputValue } from '../utils/transformInputValue'; -import { healSchema } from '../utils/heal'; +import { + parseInputValue, + serializeInputValue, + healSchema, + forEachField, + forEachDefaultValue, +} from '../utils'; function addResolveFunctionsToSchema( options: IAddResolveFunctionsToSchemaOptions | GraphQLSchema, diff --git a/src/generate/assertResolveFunctionsPresent.ts b/src/generate/assertResolveFunctionsPresent.ts index 9d170f237a6..ec51b3d0c90 100644 --- a/src/generate/assertResolveFunctionsPresent.ts +++ b/src/generate/assertResolveFunctionsPresent.ts @@ -5,7 +5,7 @@ import { GraphQLScalarType, } from 'graphql'; import { IResolverValidationOptions } from '../Interfaces'; -import forEachField from './forEachField'; +import { forEachField } from '../utils'; import SchemaError from './SchemaError'; function assertResolveFunctionsPresent( diff --git a/src/generate/index.ts b/src/generate/index.ts index 1cabbca287d..f447f981e1d 100644 --- a/src/generate/index.ts +++ b/src/generate/index.ts @@ -10,5 +10,4 @@ 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 { default as SchemaError } from './SchemaError'; diff --git a/src/makeExecutableSchema.ts b/src/makeExecutableSchema.ts index deedacd8378..e8fb4120e18 100644 --- a/src/makeExecutableSchema.ts +++ b/src/makeExecutableSchema.ts @@ -2,8 +2,11 @@ import { defaultFieldResolver, GraphQLSchema, GraphQLFieldResolver } from 'graph import { IExecutableSchemaDefinition, ILogger } from './Interfaces'; -import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; -import mergeDeep from './utils/mergeDeep'; +import { + SchemaDirectiveVisitor, + forEachField, + mergeDeep +} from './utils'; import { attachDirectiveResolvers, @@ -13,7 +16,6 @@ import { addSchemaLevelResolveFunction, buildSchemaFromTypeDefinitions, decorateWithLogger, - forEachField, SchemaError } from './generate'; diff --git a/src/mock.ts b/src/mock.ts index adcfb41eb84..a2a26bc12f0 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -17,10 +17,8 @@ import { GraphQLNullableType, } from 'graphql'; import * as uuid from 'uuid'; -import { - forEachField, - buildSchemaFromTypeDefinitions, -} from './makeExecutableSchema'; +import { buildSchemaFromTypeDefinitions } from './makeExecutableSchema'; +import { forEachField } from './utils'; import { IMocks, diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 7ee934b6abc..a6d5977360f 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -1,6 +1,5 @@ import { DocumentNode, - GraphQLField, GraphQLNamedType, GraphQLObjectType, GraphQLResolveInfo, @@ -36,10 +35,15 @@ import { ExpandAbstractTypes, ReplaceFieldWithFragment, } from '../transforms'; -import mergeDeep from '../utils/mergeDeep'; -import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; -import { cloneDirective, cloneType } from '../utils/clone'; -import { healSchema, healTypes } from '../utils/heal'; +import { + SchemaDirectiveVisitor, + cloneDirective, + cloneType, + healSchema, + healTypes, + forEachField, + mergeDeep, +} from '../utils'; import { makeMergedType } from './makeMergedType'; type MergeTypeCandidate = { @@ -356,30 +360,6 @@ function createDelegatingResolver({ }; } -type FieldIteratorFn = ( - fieldDef: GraphQLField, - typeName: string, - fieldName: string, -) => void; - -function forEachField(schema: GraphQLSchema, fn: FieldIteratorFn): void { - const typeMap = schema.getTypeMap(); - Object.keys(typeMap).forEach(typeName => { - const type = typeMap[typeName]; - - 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); - }); - } - }); -} - function addTypeCandidate( typeCandidates: { [name: string]: Array }, name: string, diff --git a/src/generate/forEachDefaultValue.ts b/src/utils/forEachDefaultValue.ts similarity index 88% rename from src/generate/forEachDefaultValue.ts rename to src/utils/forEachDefaultValue.ts index ef7655f7f87..189ced660fb 100644 --- a/src/generate/forEachDefaultValue.ts +++ b/src/utils/forEachDefaultValue.ts @@ -1,7 +1,7 @@ import { getNamedType, GraphQLInputObjectType, GraphQLSchema, GraphQLObjectType } from 'graphql'; import { IDefaultValueIteratorFn } from '../Interfaces'; -function forEachDefaultValue(schema: GraphQLSchema, fn: IDefaultValueIteratorFn): void { +export function forEachDefaultValue(schema: GraphQLSchema, fn: IDefaultValueIteratorFn): void { const typeMap = schema.getTypeMap(); Object.keys(typeMap).forEach(typeName => { @@ -27,5 +27,3 @@ function forEachDefaultValue(schema: GraphQLSchema, fn: IDefaultValueIteratorFn) } }); } - -export default forEachDefaultValue; diff --git a/src/generate/forEachField.ts b/src/utils/forEachField.ts similarity index 85% rename from src/generate/forEachField.ts rename to src/utils/forEachField.ts index f48da53a4b7..a75f866244b 100644 --- a/src/generate/forEachField.ts +++ b/src/utils/forEachField.ts @@ -1,7 +1,7 @@ import { getNamedType, GraphQLObjectType, GraphQLSchema } from 'graphql'; import { IFieldIteratorFn } from '../Interfaces'; -function forEachField(schema: GraphQLSchema, fn: IFieldIteratorFn): void { +export function forEachField(schema: GraphQLSchema, fn: IFieldIteratorFn): void { const typeMap = schema.getTypeMap(); Object.keys(typeMap).forEach(typeName => { const type = typeMap[typeName]; @@ -19,5 +19,3 @@ function forEachField(schema: GraphQLSchema, fn: IFieldIteratorFn): void { } }); } - -export default forEachField; diff --git a/src/utils/index.ts b/src/utils/index.ts index cabd47a08d5..7303b6f68fb 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,3 +4,12 @@ export { SchemaVisitor } from './SchemaVisitor'; export { SchemaDirectiveVisitor } from './SchemaDirectiveVisitor'; export { visitSchema } from './visitSchema'; export { getResolversFromSchema } from './getResolversFromSchema'; +export { forEachField } from './forEachField'; +export { forEachDefaultValue } from './forEachDefaultValue'; +export { + transformInputValue, + parseInputValue, + parseInputValueLiteral, + serializeInputValue, +} from './transformInputValue'; +export { mergeDeep } from './mergeDeep'; diff --git a/src/utils/mergeDeep.ts b/src/utils/mergeDeep.ts index 599ab656773..37ae2782a46 100644 --- a/src/utils/mergeDeep.ts +++ b/src/utils/mergeDeep.ts @@ -1,4 +1,4 @@ -export default function mergeDeep(target: any, source: any): any { +export function mergeDeep(target: any, source: any): any { let output = Object.assign({}, target); if (isObject(target) && isObject(source)) { Object.keys(source).forEach(key => { From 15c24eb98062042fc07e217149385e6f51851a3e Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 24 Oct 2019 23:02:35 -0400 Subject: [PATCH 096/250] feat(mergeSchemas): allow transform specification Allow specification of transforms directly within mergeSchemas. Allows for removal of an additional round of delegationg when merging transformed schemas. Requires schema wrapping prior to registering a type candidate for merge. --- src/Interfaces.ts | 9 ++- src/stitching/mergeSchemas.ts | 92 +++++---------------------- src/test/testAlternateMergeSchemas.ts | 78 +++++++++++++---------- 3 files changed, 68 insertions(+), 111 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index c99d5a1f9dd..6c12b6e0626 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -82,10 +82,14 @@ export type SchemaExecutionConfig = { dispatcher?: Dispatcher; }; +export type SubSchemaConfig = { + transforms?: Array; +} & SchemaExecutionConfig; + export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array }; export type SchemaLikeObject = - SchemaExecutionConfig | + SubSchemaConfig | GraphQLSchema | string | DocumentNode | @@ -95,6 +99,9 @@ export function isSchemaExecutionConfig(value: SchemaLikeObject): value is Schem return !!(value as SchemaExecutionConfig).schema; } +export function isSubSchemaConfig(value: SchemaLikeObject): value is SubSchemaConfig { + return !!(value as SubSchemaConfig).schema; +} export interface IDelegateToSchemaOptions { schema: GraphQLSchema | SchemaExecutionConfig; link?: ApolloLink; diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index a6d5977360f..990a24bb527 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -14,14 +14,11 @@ import { } from 'graphql'; import { IDelegateToSchemaOptions, - IFieldResolver, MergeInfo, OnTypeConflict, IResolversParameter, - SchemaExecutionConfig, - isSchemaExecutionConfig, + isSubSchemaConfig, SchemaLikeObject, - GraphQLSchemaWithTransforms, IResolvers, } from '../Interfaces'; import { @@ -34,22 +31,20 @@ import { Transform, ExpandAbstractTypes, ReplaceFieldWithFragment, + wrapSchema, } from '../transforms'; import { SchemaDirectiveVisitor, cloneDirective, - cloneType, healSchema, healTypes, forEachField, mergeDeep, } from '../utils'; -import { makeMergedType } from './makeMergedType'; type MergeTypeCandidate = { - schema?: GraphQLSchema; - executionConfig?: SchemaExecutionConfig; type: GraphQLNamedType; + schema?: GraphQLSchema; }; type CandidateSelector = ( @@ -82,14 +77,12 @@ export default function mergeSchemas({ }> = []; schemas.forEach(schemaLikeObject => { - if (schemaLikeObject instanceof GraphQLSchema || isSchemaExecutionConfig(schemaLikeObject)) { - let schema: GraphQLSchemaWithTransforms; - let executionConfig: SchemaExecutionConfig; - if (isSchemaExecutionConfig(schemaLikeObject)) { - executionConfig = schemaLikeObject; - schema = schemaLikeObject.schema; + if (schemaLikeObject instanceof GraphQLSchema || isSubSchemaConfig(schemaLikeObject)) { + let schema: GraphQLSchema; + if (isSubSchemaConfig(schemaLikeObject)) { + schema = wrapSchema(schemaLikeObject, schemaLikeObject.transforms || []); } else { - schema = schemaLikeObject; + schema = wrapSchema(schemaLikeObject, []); } allSchemas.push(schema); @@ -104,7 +97,6 @@ export default function mergeSchemas({ if (operationTypes[typeName]) { addTypeCandidate(typeCandidates, typeName, { schema, - executionConfig, type: operationTypes[typeName], }); } @@ -129,7 +121,6 @@ export default function mergeSchemas({ ) { addTypeCandidate(typeCandidates, type.name, { schema, - executionConfig, type, }); } @@ -339,27 +330,6 @@ function guessSchemaByRootField( ); } -function createDelegatingResolver({ - schema, - operation, - fieldName, -}: { - schema: GraphQLSchema | SchemaExecutionConfig, - operation: 'query' | 'mutation' | 'subscription', - fieldName: string, -}): IFieldResolver { - return (root, args, context, info) => { - return delegateToSchema({ - schema, - operation, - fieldName, - args, - context, - info, - }); - }; -} - function addTypeCandidate( typeCandidates: { [name: string]: Array }, name: string, @@ -395,13 +365,6 @@ function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): Cand }); } - -function rootTypeNameToOperation( - name: 'Query' | 'Mutation' | 'Subscription' -): 'query' | 'mutation' | 'subscription' { - return name.toLowerCase() as 'query' | 'mutation' | 'subscription'; -} - function operationToRootType( operation: 'query' | 'mutation' | 'subscription', schema: GraphQLSchema, @@ -424,37 +387,14 @@ function mergeTypeCandidates( candidateSelector = cands => cands[cands.length - 1]; } if (name === 'Query' || name === 'Mutation' || name === 'Subscription') { - return mergeRootTypeCandidates(name, candidates); + return new GraphQLObjectType({ + name, + fields: candidates.reduce((acc, candidate) => ({ + ...acc, + ...(candidate.type as GraphQLObjectType).toConfig().fields, + }), {}), + }); } else { - const candidate = candidateSelector(candidates); - const type = cloneType(candidate.type); - makeMergedType(type); - return type; + return candidateSelector(candidates).type; } } - -function mergeRootTypeCandidates( - name: 'Query' | 'Mutation' | 'Subscription', - candidates: Array -): GraphQLNamedType { - let operation = rootTypeNameToOperation(name); - let fields = {}; - const resolverKey = operation === 'subscription' ? 'subscribe' : 'resolve'; - candidates.forEach(candidate => { - const { type: candidateType, schema, executionConfig } = candidate; - const candidateFields = (candidateType as GraphQLObjectType).toConfig().fields; - Object.keys(candidateFields).forEach(fieldName => { - candidateFields[fieldName][resolverKey] = - schema ? createDelegatingResolver({ - schema: executionConfig ? executionConfig : schema, - operation, - fieldName, - }) : null; - }); - fields = { ...fields, ...candidateFields }; - }); - return new GraphQLObjectType({ - name, - fields, - }); -} diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 9b291dc3b76..68312cbca16 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -22,6 +22,7 @@ import { TransformObjectFields, ExtendSchema, WrapType, + FilterRootFields, } from '../transforms'; import { propertySchema, @@ -127,39 +128,48 @@ describe('merge schemas through transforms', () => { bookingSchemaExecConfig = await remoteBookingSchema; // namespace and strip schemas - const transformedPropertySchema = filterSchema({ - schema: transformSchema(propertySchema, [ - new RenameTypes((name: string) => `Properties_${name}`), - new RenameRootFields((operation: string, name: string) => `Properties_${name}`), - ]), - rootFieldFilter: (operation: string, rootField: string) => - 'Query.Properties_properties' === `${operation}.${rootField}`, - }); - const transformedBookingSchema = filterSchema({ - schema: transformSchema(bookingSchemaExecConfig, [ - new RenameTypes((name: string) => `Bookings_${name}`), - new RenameRootFields((operation: string, name: string) => `Bookings_${name}`), - ]), - rootFieldFilter: (operation: string, rootField: string) => - 'Query.Bookings_bookings' === `${operation}.${rootField}` - }); - const transformedSubscriptionSchema = filterSchema({ - schema: transformSchema(subscriptionSchema, [ - new RenameTypes((name: string) => `Subscriptions_${name}`), - new RenameRootFields( - (operation: string, name: string) => `Subscriptions_${name}`), - ]), - rootFieldFilter: (operation: string, rootField: string) => + const propertySchemaTransforms = [ + new FilterRootFields( + (operation: string, rootField: string) => + 'Query.properties' === `${operation}.${rootField}` + ), + new RenameTypes((name: string) => `Properties_${name}`), + new RenameRootFields((operation: string, name: string) => `Properties_${name}`), + ]; + const bookingSchemaTransforms = [ + new FilterRootFields( + (operation: string, rootField: string) => + 'Query.bookings' === `${operation}.${rootField}` + ), + new RenameTypes((name: string) => `Bookings_${name}`), + new RenameRootFields((operation: string, name: string) => `Bookings_${name}`), + ]; + const subScriptionSchemaTransforms = [ + new FilterRootFields( + (operation: string, rootField: string) => // must include a Query type otherwise graphql will error - 'Query.Subscriptions_notifications' === `${operation}.${rootField}` || - 'Subscription.Subscriptions_notifications' === `${operation}.${rootField}`, - }); + 'Query.notifications' === `${operation}.${rootField}` || + 'Subscription.notifications' === `${operation}.${rootField}` + ), + new RenameTypes((name: string) => `Subscriptions_${name}`), + new RenameRootFields( + (operation: string, name: string) => `Subscriptions_${name}`), + ]; mergedSchema = mergeSchemas({ schemas: [ - transformedPropertySchema, - transformedBookingSchema, - transformedSubscriptionSchema, + { + schema: propertySchema, + transforms: propertySchemaTransforms, + }, + { + ...bookingSchemaExecConfig, + transforms: bookingSchemaTransforms, + }, + { + schema: subscriptionSchema, + transforms: subScriptionSchemaTransforms, + }, linkSchema, ], resolvers: { @@ -174,7 +184,7 @@ describe('merge schemas through transforms', () => { args, context, info, - transforms: transformedPropertySchema.transforms, + transforms: propertySchemaTransforms, }); } else if (args.id.startsWith('b')) { return delegateToSchema({ @@ -184,7 +194,7 @@ describe('merge schemas through transforms', () => { args, context, info, - transforms: transformedBookingSchema.transforms, + transforms: bookingSchemaTransforms, }); } else if (args.id.startsWith('c')) { return delegateToSchema({ @@ -194,7 +204,7 @@ describe('merge schemas through transforms', () => { args, context, info, - transforms: transformedBookingSchema.transforms, + transforms: bookingSchemaTransforms, }); } else { throw new Error('invalid id'); @@ -215,7 +225,7 @@ describe('merge schemas through transforms', () => { }, context, info, - transforms: transformedBookingSchema.transforms, + transforms: bookingSchemaTransforms, }); }, }, @@ -233,7 +243,7 @@ describe('merge schemas through transforms', () => { }, context, info, - transforms: transformedPropertySchema.transforms, + transforms: propertySchemaTransforms, }); }, }, From d978ef6e349b502a4e7e9012a3e8a2272b3955e6 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 4 Nov 2019 14:47:08 -0500 Subject: [PATCH 097/250] refactor(subschemas): improve delegation workflow - refine SubschemaConfig type to allow using identical objects to merge different schemas and then delegate to those subschemas. - consolidate SchemaExecutionConfig type into SubschemaConfig type. - add separate subschemas, types, and typeDefs options to mergeSchemas to highlight different things that are being merged -- schemas options still works. - allow wrapSchema to take just a subschemaConfig as an argument, will apply the subschemas execution configuration and transforms to yield the appropriate delegating outer schema. - add rootValue to delegateToSchema options and move all other newish delegateToSchema options within the subSchemaConfig. rootValue is parallel in some ways to context, so ok to have at top level. - rename SubSchemaConfig => SubschemaConfig - - remove support for renaming types based on modification of an outer schema, this breaks when inner type is renamed and outer schema has interface not defined in inner schema. ExpandAbstractTypes currently handles this use case, but works only when the delegated result contains the correct typename. An alternative workflow would be to add the outer interface prior to renaming to each subschema and then merge interfaces. - remove resolversTransformResult property from transforms, no longer being used. - update docs! This change paves the way for annotating a result with a reference to the delegated schema. With a modified merged resolver, an outer merged schema could check to see if it received a partial result of a merged type and then merge the partial result with the necessary additional schemas defining the type. --- docs/source/directive-resolvers.md | 2 - docs/source/generate-schema.md | 2 - docs/source/schema-stitching.md | 141 ++++++++++++-------- docs/source/schema-transforms.md | 8 +- src/Interfaces.ts | 29 ++-- src/stitching/addTypenameToAbstract.ts | 4 +- src/stitching/delegateToSchema.ts | 119 ++++++++++------- src/stitching/makeRemoteExecutableSchema.ts | 3 +- src/stitching/mergeSchemas.ts | 52 +++++--- src/stitching/resolvers.ts | 22 +-- src/test/testAlternateMergeSchemas.ts | 58 ++++---- src/test/testMakeRemoteExecutableSchema.ts | 12 +- src/test/testMergeSchemas.ts | 8 +- src/test/testingSchemas.ts | 8 +- src/transforms/RenameTypes.ts | 21 +-- src/transforms/transformSchema.ts | 42 +++--- 16 files changed, 282 insertions(+), 249 deletions(-) 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..be469554e59 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 diff --git a/docs/source/schema-stitching.md b/docs/source/schema-stitching.md index da1ef8cf60d..df8415ff5d6 100644 --- a/docs/source/schema-stitching.md +++ b/docs/source/schema-stitching.md @@ -7,19 +7,13 @@ 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 { @@ -65,14 +59,16 @@ const authorSchema = makeExecutableSchema({ addMockFunctionsToSchema({ 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,32 +103,34 @@ 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` 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). +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: { @@ -172,13 +170,9 @@ 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`: @@ -187,7 +181,6 @@ import { makeExecutableSchema, addMockFunctionsToSchema, mergeSchemas, - transformSchema, FilterRootFields, RenameTypes, RenameRootFields, @@ -213,35 +206,41 @@ const chirpSchema = makeExecutableSchema({ addMockFunctionsToSchema({ 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 delegateToSchema({ - schema: chirpSchema, + schema: chirpSubschema, operation: 'query', fieldName: 'chirpsByAuthorId', args: { @@ -249,7 +248,6 @@ const mergedSchema = mergeSchemas({ }, context, info, - transforms: transformedChirpSchema.transforms, }); }, }, @@ -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 `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 @@ -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 GraphQLSchema; transformRequest?: (originalRequest: Request) => Request; transformResult?: (result: Result) => Result; - resolversTransformResult?: boolean; }; export interface IGraphQLToolsResolveInfo extends GraphQLResolveInfo { @@ -75,47 +74,39 @@ export interface IFetcherOperation { export type Dispatcher = (context: any) => ApolloLink | Fetcher; -export type SchemaExecutionConfig = { +export type SubschemaConfig = { schema: GraphQLSchemaWithTransforms; + rootValue?: Record; + executor?: Delegator; + subscriber?: Delegator; link?: ApolloLink; fetcher?: Fetcher; dispatcher?: Dispatcher; -}; - -export type SubSchemaConfig = { transforms?: Array; -} & SchemaExecutionConfig; +}; export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array }; export type SchemaLikeObject = - SubSchemaConfig | + SubschemaConfig | GraphQLSchema | string | DocumentNode | Array; -export function isSchemaExecutionConfig(value: SchemaLikeObject): value is SchemaExecutionConfig { - return !!(value as SchemaExecutionConfig).schema; -} - -export function isSubSchemaConfig(value: SchemaLikeObject): value is SubSchemaConfig { - return !!(value as SubSchemaConfig).schema; +export function isSubschemaConfig(value: SchemaLikeObject): value is SubschemaConfig { + return !!(value as SubschemaConfig).schema; } export interface IDelegateToSchemaOptions { - schema: GraphQLSchema | SchemaExecutionConfig; - link?: ApolloLink; - fetcher?: Fetcher; - dispatcher?: Dispatcher; + schema: GraphQLSchema | SubschemaConfig; operation: Operation; fieldName: string; args?: { [key: string]: any }; context: TContext; info: IGraphQLToolsResolveInfo; + rootValue?: Record; transforms?: Array; skipValidation?: boolean; - executor?: Delegator; - subscriber?: Delegator; } export type Delegator = ({ document, context, variables }: { diff --git a/src/stitching/addTypenameToAbstract.ts b/src/stitching/addTypenameToAbstract.ts index 706574b1b32..9b957f6ba0b 100644 --- a/src/stitching/addTypenameToAbstract.ts +++ b/src/stitching/addTypenameToAbstract.ts @@ -9,11 +9,11 @@ import { FieldNode, GraphQLInterfaceType, GraphQLUnionType, + GraphQLSchema, } from 'graphql'; -import { GraphQLSchemaWithTransforms } from '../Interfaces'; export function addTypenameToAbstract( - targetSchema: GraphQLSchemaWithTransforms, + targetSchema: GraphQLSchema, document: DocumentNode, ): DocumentNode { const typeInfo = new TypeInfo(targetSchema); diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 73cdddc4983..f875012d15d 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -22,7 +22,8 @@ import { Request, Fetcher, Delegator, - isSchemaExecutionConfig, + SubschemaConfig, + isSubschemaConfig, } from '../Interfaces'; import { @@ -55,24 +56,33 @@ export default function delegateToSchema( return delegateToSchemaImplementation(options); } -async function delegateToSchemaImplementation( - options: IDelegateToSchemaOptions, +async function delegateToSchemaImplementation({ + schema: schemaOrSubschemaConfig, + rootValue, + info, + operation = info.operation.operation, + fieldName, + args, + context, + transforms = [], + skipValidation, +}: IDelegateToSchemaOptions, ): Promise { - const { schema: schemaOrSchemaConfig } = options; - let targetSchema; - if (isSchemaExecutionConfig(schemaOrSchemaConfig)) { - targetSchema = schemaOrSchemaConfig.schema; - options.link = schemaOrSchemaConfig.link; - options.fetcher = schemaOrSchemaConfig.fetcher; - options.dispatcher = schemaOrSchemaConfig.dispatcher; + let targetSchema: GraphQLSchema; + let subSchemaConfig: SubschemaConfig; + + if (isSubschemaConfig(schemaOrSubschemaConfig)) { + subSchemaConfig = schemaOrSubschemaConfig; + targetSchema = subSchemaConfig.schema; + rootValue = rootValue || subSchemaConfig.rootValue || info.rootValue; + transforms = transforms.concat((subSchemaConfig.transforms || []).slice().reverse()); } else { - targetSchema = schemaOrSchemaConfig; + targetSchema = schemaOrSubschemaConfig; + rootValue = rootValue || info.rootValue; } - const { info } = options; - const operation = options.operation || info.operation.operation; const rawDocument: DocumentNode = createDocument( - options.fieldName, + fieldName, operation, info.fieldNodes, Object.keys(info.fragments).map( @@ -87,9 +97,9 @@ async function delegateToSchemaImplementation( variables: info.variableValues as Record, }; - let transforms = [ - new CheckResultAndHandleErrors(info, options.fieldName), - ...(options.transforms || []), + transforms = [ + new CheckResultAndHandleErrors(info, fieldName), + ...transforms, new ExpandAbstractTypes(info.schema, targetSchema), ]; @@ -99,7 +109,6 @@ async function delegateToSchemaImplementation( ); } - const { args } = options; if (args) { transforms.push( new AddArgumentsAsVariables(targetSchema, args) @@ -113,7 +122,7 @@ async function delegateToSchemaImplementation( const processedRequest = applyRequestTransforms(rawRequest, transforms); - if (!options.skipValidation) { + if (!skipValidation) { const errors = validate(targetSchema, processedRequest.document); if (errors.length > 0) { throw errors; @@ -121,24 +130,23 @@ async function delegateToSchemaImplementation( } if (operation === 'query' || operation === 'mutation') { - options.executor = options.executor || getExecutor(targetSchema, options); + const executor = createExecutor(targetSchema, rootValue, subSchemaConfig); return applyResultTransforms( - await options.executor({ + await executor({ document: processedRequest.document, - context: options.context, + context, variables: processedRequest.variables }), transforms, ); - } - if (operation === 'subscription') { - options.subscriber = options.subscriber || getSubscriber(targetSchema, options); + } else if (operation === 'subscription') { + const subscriber = createSubscriber(targetSchema, rootValue, subSchemaConfig); - const originalAsyncIterator = (await options.subscriber({ + const originalAsyncIterator = (await subscriber({ document: processedRequest.document, - context: options.context, + context, variables: processedRequest.variables, })) as AsyncIterator; @@ -211,17 +219,27 @@ function createDocument( }; } -function getExecutor(schema: GraphQLSchema, options: IDelegateToSchemaOptions): Delegator { +function createExecutor( + schema: GraphQLSchema, + rootValue: Record, + subSchemaConfig?: SubschemaConfig +): Delegator { let fetcher: Fetcher; - if (options.dispatcher) { - const dynamicLinkOrFetcher = options.dispatcher(context); - fetcher = (typeof dynamicLinkOrFetcher === 'function') ? - dynamicLinkOrFetcher : - linkToFetcher(dynamicLinkOrFetcher); - } else if (options.link) { - fetcher = linkToFetcher(options.link); - } else if (options.fetcher) { - fetcher = options.fetcher; + if (subSchemaConfig) { + if (subSchemaConfig.dispatcher) { + const dynamicLinkOrFetcher = subSchemaConfig.dispatcher(context); + fetcher = (typeof dynamicLinkOrFetcher === 'function') ? + dynamicLinkOrFetcher : + linkToFetcher(dynamicLinkOrFetcher); + } else if (subSchemaConfig.link) { + fetcher = linkToFetcher(subSchemaConfig.link); + } else if (subSchemaConfig.fetcher) { + fetcher = subSchemaConfig.fetcher; + } + + if (!fetcher && !rootValue && subSchemaConfig.rootValue) { + rootValue = subSchemaConfig.rootValue; + } } if (fetcher) { @@ -234,19 +252,30 @@ function getExecutor(schema: GraphQLSchema, options: IDelegateToSchemaOptions): return ({ document, context, variables }) => execute({ schema, document, - rootValue: options.info.rootValue, + rootValue, contextValue: context, variableValues: variables, }); } } -function getSubscriber(schema: GraphQLSchema, options: IDelegateToSchemaOptions): Delegator { +function createSubscriber( + schema: GraphQLSchema, + rootValue: Record, + subSchemaConfig?: SubschemaConfig +): Delegator { let link: ApolloLink; - if (options.dispatcher) { - link = options.dispatcher(context) as ApolloLink; - } else if (options.link) { - link = options.link; + + if (subSchemaConfig) { + if (subSchemaConfig.dispatcher) { + link = subSchemaConfig.dispatcher(context) as ApolloLink; + } else if (subSchemaConfig.link) { + link = subSchemaConfig.link; + } + + if (!link && !rootValue && subSchemaConfig.rootValue) { + rootValue = subSchemaConfig.rootValue; + } } if (link) { @@ -263,9 +292,9 @@ function getSubscriber(schema: GraphQLSchema, options: IDelegateToSchemaOptions) return ({ document, context, variables }) => subscribe({ schema, document, - rootValue: options.info.rootValue, + rootValue, contextValue: context, variableValues: variables, }); - } + } } diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 77f6031a5c5..59af15335d6 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -66,10 +66,9 @@ export default function makeRemoteExecutableSchema({ } } - const resolvers = generateProxyingResolvers(remoteSchema, [], createProxyingResolver); addResolveFunctionsToSchema({ schema: remoteSchema, - resolvers, + resolvers: generateProxyingResolvers({ schema: remoteSchema }, createProxyingResolver), resolverValidationOptions: { allowResolversNotInSchema: true, }, diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 990a24bb527..8ec23b71ae1 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -17,9 +17,10 @@ import { MergeInfo, OnTypeConflict, IResolversParameter, - isSubSchemaConfig, + isSubschemaConfig, SchemaLikeObject, IResolvers, + SubschemaConfig, } from '../Interfaces'; import { extractExtensionDefinitions, @@ -52,14 +53,20 @@ type CandidateSelector = ( ) => MergeTypeCandidate; export default function mergeSchemas({ - schemas, + subschemas = [], + types = [], + typeDefs, + schemas: schemaLikeObjects = [], onTypeConflict, resolvers, schemaDirectives, inheritResolversFromInterfaces, mergeDirectives, }: { - schemas: Array; + subschemas?: Array; + types?: Array; + typeDefs?: string | DocumentNode; + schemas?: Array; onTypeConflict?: OnTypeConflict; resolvers?: IResolversParameter; schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor }; @@ -68,22 +75,25 @@ export default function mergeSchemas({ }): GraphQLSchema { const allSchemas: Array = []; const typeCandidates: { [name: string]: Array } = {}; - const types: { [name: string]: GraphQLNamedType } = {}; + const typeMap: { [name: string]: GraphQLNamedType } = {}; const extensions: Array = []; const directives: Array = []; const fragments: Array<{ field: string; fragment: string; }> = []; + let schemas: Array = [...subschemas]; + if (typeDefs) { + schemas.push(typeDefs); + } + if (types) { + schemas.push(types); + } + schemas = [...schemas, ...schemaLikeObjects]; schemas.forEach(schemaLikeObject => { - if (schemaLikeObject instanceof GraphQLSchema || isSubSchemaConfig(schemaLikeObject)) { - let schema: GraphQLSchema; - if (isSubSchemaConfig(schemaLikeObject)) { - schema = wrapSchema(schemaLikeObject, schemaLikeObject.transforms || []); - } else { - schema = wrapSchema(schemaLikeObject, []); - } + if (schemaLikeObject instanceof GraphQLSchema || isSubschemaConfig(schemaLikeObject)) { + const schema = wrapSchema(schemaLikeObject); allSchemas.push(schema); @@ -109,9 +119,9 @@ export default function mergeSchemas({ }); } - const typeMap = schema.getTypeMap(); - Object.keys(typeMap).forEach(typeName => { - const type: GraphQLNamedType = typeMap[typeName]; + const originalTypeMap = schema.getTypeMap(); + Object.keys(originalTypeMap).forEach(typeName => { + const type: GraphQLNamedType = originalTypeMap[typeName]; if ( isNamedType(type) && getNamedType(type).name.slice(0, 2) !== '__' && @@ -130,7 +140,7 @@ export default function mergeSchemas({ (schemaLikeObject && (schemaLikeObject as DocumentNode).kind === Kind.DOCUMENT) ) { let parsedSchemaDocument = - typeof schemaLikeObject === 'string' ? parse(schemaLikeObject) : (schemaLikeObject as DocumentNode); + typeof schemaLikeObject === 'string' ? parse(schemaLikeObject) : (schemaLikeObject as DocumentNode); parsedSchemaDocument.definitions.forEach(def => { const type = typeFromAST(def); if (type instanceof GraphQLDirective && mergeDirectives) { @@ -181,20 +191,20 @@ export default function mergeSchemas({ } Object.keys(typeCandidates).forEach(typeName => { - types[typeName] = mergeTypeCandidates( + typeMap[typeName] = mergeTypeCandidates( typeName, typeCandidates[typeName], onTypeConflict ? onTypeConflictToCandidateSelector(onTypeConflict) : undefined ); }); - healTypes(types, directives, { skipPruning: true }); + healTypes(typeMap, directives, { skipPruning: true }); let mergedSchema = new GraphQLSchema({ - query: types.Query as GraphQLObjectType, - mutation: types.Mutation as GraphQLObjectType, - subscription: types.Subscription as GraphQLObjectType, - types: Object.keys(types).map(key => types[key]), + query: typeMap.Query as GraphQLObjectType, + mutation: typeMap.Mutation as GraphQLObjectType, + subscription: typeMap.Subscription as GraphQLObjectType, + types: Object.keys(typeMap).map(key => typeMap[key]), directives: directives.length ? directives.map((directive) => cloneDirective(directive)) : undefined diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 353b848205f..c8dbf5d1f1a 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -6,12 +6,10 @@ import { import { IResolvers, Operation, - SchemaExecutionConfig, - isSchemaExecutionConfig, + SubschemaConfig, } from '../Interfaces'; import delegateToSchema from './delegateToSchema'; import { makeMergedType } from './makeMergedType'; -import { Transform } from '../transforms/index'; export type Mapping = { [typeName: string]: { @@ -23,17 +21,14 @@ export type Mapping = { }; export function generateProxyingResolvers( - schemaOrSchemaExecutionConfig: GraphQLSchema | SchemaExecutionConfig, - transforms: Array = [], + subschemaConfig: SubschemaConfig, createProxyingResolver: ( - schema: GraphQLSchema | SchemaExecutionConfig, + schema: GraphQLSchema | SubschemaConfig, operation: Operation, fieldName: string, - transforms: Array, ) => GraphQLFieldResolver = defaultCreateProxyingResolver, ): IResolvers { - const targetSchema: GraphQLSchema = isSchemaExecutionConfig(schemaOrSchemaExecutionConfig) ? - schemaOrSchemaExecutionConfig.schema : schemaOrSchemaExecutionConfig; + const targetSchema = subschemaConfig.schema; const mapping = generateSimpleMapping(targetSchema); @@ -47,10 +42,9 @@ export function generateProxyingResolvers( to.operation === 'subscription' ? 'subscribe' : 'resolve'; result[name][from] = { [resolverType]: createProxyingResolver( - schemaOrSchemaExecutionConfig, + subschemaConfig, to.operation, to.name, - transforms, ), }; }); @@ -101,19 +95,17 @@ export function generateMappingFromObjectType( } function defaultCreateProxyingResolver( - schema: GraphQLSchema | SchemaExecutionConfig, + subschemaConfig: SubschemaConfig, operation: Operation, fieldName: string, - transforms: Array, ): GraphQLFieldResolver { return (parent, args, context, info) => delegateToSchema({ - schema, + schema: subschemaConfig, operation, fieldName, args, context, info, - transforms, }); } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 68312cbca16..7bd4fb2536e 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -43,7 +43,7 @@ import { createMergedResolver, extractFields, } from '../stitching'; -import { SchemaExecutionConfig } from '../Interfaces'; +import { SubschemaConfig } from '../Interfaces'; import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; function renameFieldNode(fieldNode: FieldNode, name: string): FieldNode { @@ -121,11 +121,11 @@ let linkSchema = ` `; describe('merge schemas through transforms', () => { - let bookingSchemaExecConfig: SchemaExecutionConfig; + let bookingSubschemaConfig: SubschemaConfig; let mergedSchema: GraphQLSchema; before(async () => { - bookingSchemaExecConfig = await remoteBookingSchema; + bookingSubschemaConfig = await remoteBookingSchema; // namespace and strip schemas const propertySchemaTransforms = [ @@ -144,7 +144,7 @@ describe('merge schemas through transforms', () => { new RenameTypes((name: string) => `Bookings_${name}`), new RenameRootFields((operation: string, name: string) => `Bookings_${name}`), ]; - const subScriptionSchemaTransforms = [ + const subscriptionSchemaTransforms = [ new FilterRootFields( (operation: string, rootField: string) => // must include a Query type otherwise graphql will error @@ -156,55 +156,59 @@ describe('merge schemas through transforms', () => { (operation: string, name: string) => `Subscriptions_${name}`), ]; + const propertySubschema = { + schema: propertySchema, + transforms: propertySchemaTransforms, + }; + const bookingSubschema = { + ...bookingSubschemaConfig, + transforms: bookingSchemaTransforms, + }; + const subscriptionSubschema = { + schema: subscriptionSchema, + transforms: subscriptionSchemaTransforms, + }; + mergedSchema = mergeSchemas({ - schemas: [ - { - schema: propertySchema, - transforms: propertySchemaTransforms, - }, - { - ...bookingSchemaExecConfig, - transforms: bookingSchemaTransforms, - }, - { - schema: subscriptionSchema, - transforms: subScriptionSchemaTransforms, - }, - linkSchema, + subschemas: [ + propertySubschema, + bookingSubschema, + subscriptionSubschema, ], + typeDefs: linkSchema, resolvers: { Query: { // delegating directly, no subschemas or mergeInfo node(parent, args, context, info) { if (args.id.startsWith('p')) { return info.mergeInfo.delegateToSchema({ - schema: propertySchema, + schema: propertySubschema, operation: 'query', fieldName: 'propertyById', args, context, info, - transforms: propertySchemaTransforms, + transforms: [], }); } else if (args.id.startsWith('b')) { return delegateToSchema({ - ...bookingSchemaExecConfig, + schema: bookingSubschema, operation: 'query', fieldName: 'bookingById', args, context, info, - transforms: bookingSchemaTransforms, + transforms: [], }); } else if (args.id.startsWith('c')) { return delegateToSchema({ - ...bookingSchemaExecConfig, + schema: bookingSubschema, operation: 'query', fieldName: 'customerById', args, context, info, - transforms: bookingSchemaTransforms, + transforms: [], }); } else { throw new Error('invalid id'); @@ -216,7 +220,7 @@ describe('merge schemas through transforms', () => { fragment: 'fragment PropertyFragment on Property { id }', resolve(parent, args, context, info) { return delegateToSchema({ - ...bookingSchemaExecConfig, + schema: bookingSubschema, operation: 'query', fieldName: 'bookingsByPropertyId', args: { @@ -225,7 +229,6 @@ describe('merge schemas through transforms', () => { }, context, info, - transforms: bookingSchemaTransforms, }); }, }, @@ -235,7 +238,7 @@ describe('merge schemas through transforms', () => { fragment: 'fragment BookingFragment on Booking { propertyId }', resolve(parent, args, context, info) { return info.mergeInfo.delegateToSchema({ - schema: propertySchema, + schema: propertySubschema, operation: 'query', fieldName: 'propertyById', args: { @@ -243,7 +246,6 @@ describe('merge schemas through transforms', () => { }, context, info, - transforms: propertySchemaTransforms, }); }, }, diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index 0e30c64172a..79fe440cd6b 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -15,10 +15,10 @@ import { makeRemoteExecutableSchema } from '../stitching'; describe('remote queries', () => { let schema: GraphQLSchema; before(async () => { - const remoteSchemaExecConfig = await makeSchemaRemoteFromLink(propertySchema); + const remoteSubschemaConfig = await makeSchemaRemoteFromLink(propertySchema); schema = makeRemoteExecutableSchema({ - schema: remoteSchemaExecConfig.schema, - link: remoteSchemaExecConfig.link + schema: remoteSubschemaConfig.schema, + link: remoteSubschemaConfig.link }); }); @@ -56,10 +56,10 @@ describe('remote queries', () => { describe('remote subscriptions', () => { let schema: GraphQLSchema; before(async () => { - const remoteSchemaExecConfig = await makeSchemaRemoteFromLink(subscriptionSchema); + const remoteSubschemaConfig = await makeSchemaRemoteFromLink(subscriptionSchema); schema = makeRemoteExecutableSchema({ - schema: remoteSchemaExecConfig.schema, - link: remoteSchemaExecConfig.link + schema: remoteSubschemaConfig.schema, + link: remoteSubschemaConfig.link }); }); diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 648a8dcccf9..9d4ca781721 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -31,7 +31,7 @@ import { forAwaitEach } from 'iterall'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { IResolvers, - SchemaExecutionConfig, + SubschemaConfig, } from '../Interfaces'; import { delegateToSchema } from '../stitching'; import { cloneSchema } from '../utils'; @@ -335,9 +335,9 @@ let schemaDirectiveTypeDefs = ` testCombinations.forEach(async combination => { describe('merging ' + combination.name, () => { let mergedSchema: GraphQLSchema, - propertySchema: GraphQLSchema | SchemaExecutionConfig, - productSchema: GraphQLSchema | SchemaExecutionConfig, - bookingSchema: GraphQLSchema | SchemaExecutionConfig; + propertySchema: GraphQLSchema | SubschemaConfig, + productSchema: GraphQLSchema | SubschemaConfig, + bookingSchema: GraphQLSchema | SubschemaConfig; before(async () => { propertySchema = await combination.property; diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index f5d0c085642..4d24f36f957 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -21,7 +21,7 @@ import { makeExecutableSchema } from '../makeExecutableSchema'; import { IResolvers, Fetcher, - SchemaExecutionConfig, + SubschemaConfig, } from '../Interfaces'; import introspectSchema from '../stitching/introspectSchema'; import { PubSub } from 'graphql-subscriptions'; @@ -762,7 +762,7 @@ function makeLinkFromSchema(schema: GraphQLSchema) { } export async function makeSchemaRemoteFromLink(schema: GraphQLSchema) - : Promise { + : Promise { const link = makeLinkFromSchema(schema); const clientSchema = await introspectSchema(link); return { @@ -772,7 +772,7 @@ export async function makeSchemaRemoteFromLink(schema: GraphQLSchema) } export async function makeSchemaRemoteFromDispatchedLink(schema: GraphQLSchema) - : Promise { + : Promise { const link = makeLinkFromSchema(schema); const clientSchema = await introspectSchema(link); return { @@ -783,7 +783,7 @@ export async function makeSchemaRemoteFromDispatchedLink(schema: GraphQLSchema) // ensure fetcher support exists from the 2.0 api async function makeExecutableSchemaFromDispatchedFetcher(schema: GraphQLSchema) - : Promise { + : Promise { const fetcher: Fetcher = ({ query, operationName, variables, context }) => { return graphql( schema, diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index c3afab33583..ab1bd5e180b 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -5,12 +5,10 @@ import { Kind, GraphQLNamedType, GraphQLScalarType, - GraphQLAbstractType, } from 'graphql'; import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; import { Request, Result, VisitSchemaKind } from '../Interfaces'; import { Transform } from '../transforms/transforms'; -import { isParentProxiedResult } from '../stitching/errors'; import { visitSchema, cloneType } from '../utils'; export type RenameOptions = { @@ -19,8 +17,6 @@ export type RenameOptions = { }; export default class RenameTypes implements Transform { - public readonly resolversTransformResult = true; - private renamer: (name: string) => string | undefined; private reverseMap: { [key: string]: string }; private renameBuiltins: boolean; @@ -38,7 +34,7 @@ export default class RenameTypes implements Transform { } public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { - return visitSchema(originalSchema, [{ + return visitSchema(originalSchema, { [VisitSchemaKind.TYPE]: (type: GraphQLNamedType) => { if (isSpecifiedScalarType(type) && !this.renameBuiltins) { return undefined; @@ -58,20 +54,7 @@ export default class RenameTypes implements Transform { [VisitSchemaKind.ROOT_OBJECT](type: GraphQLNamedType) { return undefined; }, - }, { - [VisitSchemaKind.ABSTRACT_TYPE]: (type: GraphQLAbstractType) => { - const originalResolveType = type.resolveType; - type.resolveType = (value, info, context, abstractType) => { - if (isParentProxiedResult(value)) { - const oldName = originalResolveType(value, info, context, abstractType) as string; - const newName = this.renamer(oldName); - return newName ? newName : oldName; - } - return originalResolveType(value, info, context, abstractType); - }; - return type; - }, - }]); + }); } public transformRequest(originalRequest: Request): Request { diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index b50dbcf51bf..59589f40827 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -7,48 +7,48 @@ import { stripResolvers, } from '../stitching/resolvers'; import { - SchemaExecutionConfig, - isSchemaExecutionConfig, + SubschemaConfig, + isSubschemaConfig, } from '../Interfaces'; import { cloneSchema } from '../utils/clone'; export function wrapSchema( - schemaOrSchemaExecutionConfig: GraphQLSchema | SchemaExecutionConfig, - transforms: Array, + schemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, + transforms: Array = [], ): GraphQLSchema { - const targetSchema: GraphQLSchema = isSchemaExecutionConfig(schemaOrSchemaExecutionConfig) ? - schemaOrSchemaExecutionConfig.schema : schemaOrSchemaExecutionConfig; + let subschemaConfig: SubschemaConfig; + if (isSubschemaConfig(schemaOrSubschemaConfig)) { + subschemaConfig = { + ...schemaOrSubschemaConfig, + transforms: (schemaOrSubschemaConfig.transforms || []).concat(transforms), + }; + } else { + subschemaConfig = { + schema: schemaOrSubschemaConfig, + transforms, + }; + } - const schema = cloneSchema(targetSchema); + const schema = cloneSchema(subschemaConfig.schema); stripResolvers(schema); - const resolvers = generateProxyingResolvers( - schemaOrSchemaExecutionConfig, - transforms.slice().reverse().map(transform => { - return transform.resolversTransformResult - ? { - transformRequest: originalRequest => transform.transformRequest(originalRequest) - } : - transform; - }), - ); addResolveFunctionsToSchema({ schema, - resolvers, + resolvers: generateProxyingResolvers(subschemaConfig), resolverValidationOptions: { allowResolversNotInSchema: true, }, }); - return applySchemaTransforms(schema, transforms); + return applySchemaTransforms(schema, subschemaConfig.transforms); } export default function transformSchema( - schemaOrSchemaExecutionConfig: GraphQLSchema | SchemaExecutionConfig, + schemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, transforms: Array, ): GraphQLSchema & { transforms: Array } { - const schema = wrapSchema(schemaOrSchemaExecutionConfig, transforms); + const schema = wrapSchema(schemaOrSubschemaConfig, transforms); (schema as any).transforms = transforms.slice().reverse(); return schema as GraphQLSchema & { transforms: Array }; } From d7e7b8b36a8b3cf94fcbb05ccb1cb70f6e32de36 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 6 Nov 2019 10:56:01 -0500 Subject: [PATCH 098/250] fix(stitching): pass along more errors partially addresses #26. --- src/stitching/errors.ts | 4 ++ src/test/testErrors.ts | 90 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 261b8045bb7..1b3fe0ca444 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -41,6 +41,10 @@ export function relocatedError( } export function annotateWithChildrenErrors(object: any, childrenErrors: ReadonlyArray): any { + if (!object) { + return object; + } + if (!Array.isArray(childrenErrors)) { object[ERROR_SYMBOL] = []; return object; diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index 3a53429f6ff..f44724aceab 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -1,5 +1,5 @@ -import { assert } from 'chai'; -import { GraphQLResolveInfo, GraphQLError } from 'graphql'; +import { expect, assert } from 'chai'; +import { GraphQLResolveInfo, GraphQLError, graphql } from 'graphql'; import { relocatedError, getErrorsFromParent, @@ -8,6 +8,8 @@ import { import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; import 'mocha'; +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { mergeSchemas } from '../stitching'; class ErrorWithExtensions extends GraphQLError { constructor(message: string, code: string) { @@ -96,3 +98,87 @@ describe('Errors', () => { }); }); }); + +describe('passes along errors for missing fields on list', () => { + it('hides error if null allowed', async () => { + const typeDefs = ` + type Query { + getOuter: Outer + } + type Outer { + innerList: [Inner]! + } + type Inner { + mandatoryField: String! + } + `; + + const schema = makeExecutableSchema({ + typeDefs, + resolvers: { + Query: { + getOuter: () => ({ + innerList: [{}] + }) + }, + } + }); + + const mergedSchema = mergeSchemas({ + schemas: [schema] + }); + const result = await graphql(mergedSchema, `{ getOuter { innerList { mandatoryField } } }`); + expect(result).to.deep.equal({ + data: { + getOuter: { + innerList: [null], + }, + }, + }); + }); + + it('if non-null', async () => { + const typeDefs = ` + type Query { + getOuter: Outer + } + type Outer { + innerList: [Inner!]! + } + type Inner { + mandatoryField: String! + } + `; + + const schema = makeExecutableSchema({ + typeDefs, + resolvers: { + Query: { + getOuter: () => ({ + innerList: [{}] + }) + }, + } + }); + + const mergedSchema = mergeSchemas({ + schemas: [schema] + }); + const result = await graphql(mergedSchema, `{ getOuter { innerList { mandatoryField } } }`); + expect(result).to.deep.equal({ + data: { + getOuter: null, + }, + errors: [{ + locations: [{ + column: 26, + line: 1, + }], + message: 'Cannot return null for non-nullable field Inner.mandatoryField.', + path: [ + 'getOuter', + ], + }] + }); + }); +}); From d2173f393128175f000eed026a4257f0e8e9a6b4 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 7 Nov 2019 11:39:27 -0500 Subject: [PATCH 099/250] fix(stitching): change merged result format Merged results that are null may carry errors from deeper within the query tree. Previous merged result format carries this metadata within a property on the result, but null has no properties. New result format differs only in that a null result is transformed to an object with a special property signifying that the result was null, so that metadata can be added in the same way. When merging within defaultMergedResolver, all null checks must check for this property as well. Because a result may be changed during annotation, the function is essentially no longer annotating only/i.e. modifying the object in place, and so it has been renamed to reflect that, with forEach changed to map when processing a list. Fixes #26. --- src/stitching/checkResultAndHandleErrors.ts | 35 ++++++++++++--------- src/stitching/createMergedResolver.ts | 5 ++- src/stitching/defaultMergedResolver.ts | 12 +++++-- src/stitching/errors.ts | 23 +++++++++----- src/test/testErrors.ts | 29 ++++++++++++----- 5 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index f472bc1dda5..77fd767f866 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -13,7 +13,7 @@ import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; import { relocatedError, combineErrors, - annotateWithChildrenErrors + createMergedResult } from './errors'; export function checkResultAndHandleErrors( @@ -37,6 +37,10 @@ export function checkResultAndHandleErrors( } } + if (result.data[responseKey] == null) { + return handleNull(info, result.errors || []); + } + return handleResult(info, result.data[responseKey], result.errors || []); } @@ -45,22 +49,10 @@ export function handleResult( result: any, errors: ReadonlyArray ): any { - if (result == null) { - if (errors.length) { - throw relocatedError( - combineErrors(errors), - info.fieldNodes, - responsePathAsArray(info.path) - ); - } else { - return null; - } - } - const nullableType = getNullableType(info.returnType); if (isCompositeType(nullableType) || isListType(nullableType)) { - annotateWithChildrenErrors(result, errors); + result = createMergedResult(result, errors); } return parseOutputValue(nullableType, result); @@ -74,3 +66,18 @@ function parseOutputValue(type: GraphQLType, value: any) { } return value; } + +export function handleNull( + info: GraphQLResolveInfo, + errors: ReadonlyArray, +): null { + if (errors.length) { + throw relocatedError( + combineErrors(errors), + info.fieldNodes, + responsePathAsArray(info.path) + ); + } else { + return null; + } +} diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index f0062cf9498..93444d2635f 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -4,7 +4,7 @@ import { relocatedError, combineErrors, getErrorsFromParent, - annotateWithChildrenErrors, + createMergedResult, } from './errors'; import defaultMergedResolver from './defaultMergedResolver'; import { extractOneLevelOfFields } from './extractFields'; @@ -67,8 +67,7 @@ export function createMergedResolver({ return null; } } - annotateWithChildrenErrors(result, errors); - parent = result; + parent = createMergedResult(result, errors); } fieldName = fromPath[fromPathLength - 1]; diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index 0c14aea5cd4..8af9d8a8bbf 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,6 +1,6 @@ import { GraphQLFieldResolver, defaultFieldResolver } from 'graphql'; -import { getErrorsFromParent } from './errors'; -import { handleResult } from './checkResultAndHandleErrors'; +import { getErrorsFromParent, MERGED_NULL_SYMBOL } from './errors'; +import { handleResult, handleNull } from './checkResultAndHandleErrors'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; // Resolver that knows how to: @@ -21,7 +21,13 @@ const defaultMergedResolver: GraphQLFieldResolver = (parent, args, con return defaultFieldResolver(parent, args, context, info); } - return handleResult(info, parent[responseKey], errors); + const result = parent[responseKey]; + + if (result == null || result[MERGED_NULL_SYMBOL]) { + return handleNull(info, errors); + } + + return handleResult(info, result, errors); }; export default defaultMergedResolver; diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 1b3fe0ca444..c61ba9127d9 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -40,14 +40,21 @@ export function relocatedError( ); } -export function annotateWithChildrenErrors(object: any, childrenErrors: ReadonlyArray): any { - if (!object) { - return object; - } +export let MERGED_NULL_SYMBOL: any; +if ( + (typeof global !== 'undefined' && 'Symbol' in global) || + (typeof window !== 'undefined' && 'Symbol' in window) +) { + MERGED_NULL_SYMBOL = Symbol('mergedNull'); +} else { + MERGED_NULL_SYMBOL = '@@__mergedNull'; +} - if (!Array.isArray(childrenErrors)) { - object[ERROR_SYMBOL] = []; - return object; +export function createMergedResult(object: any, childrenErrors: ReadonlyArray = []): any { + if (!object) { + object = { + [MERGED_NULL_SYMBOL]: true, + }; } if (Array.isArray(object)) { @@ -69,7 +76,7 @@ export function annotateWithChildrenErrors(object: any, childrenErrors: Readonly byIndex[index] = current; }); - object.forEach((item, index) => annotateWithChildrenErrors(item, byIndex[index])); + object = object.map((item, index) => createMergedResult(item, byIndex[index])); return object; } diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index f44724aceab..03598407bee 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -100,13 +100,13 @@ describe('Errors', () => { }); describe('passes along errors for missing fields on list', () => { - it('hides error if null allowed', async () => { + it('if non-null', async () => { const typeDefs = ` type Query { getOuter: Outer } type Outer { - innerList: [Inner]! + innerList: [Inner!]! } type Inner { mandatoryField: String! @@ -130,20 +130,28 @@ describe('passes along errors for missing fields on list', () => { const result = await graphql(mergedSchema, `{ getOuter { innerList { mandatoryField } } }`); expect(result).to.deep.equal({ data: { - getOuter: { - innerList: [null], - }, + getOuter: null, }, + errors: [{ + locations: [{ + column: 26, + line: 1, + }], + message: 'Cannot return null for non-nullable field Inner.mandatoryField.', + path: [ + 'getOuter', + ], + }] }); }); - it('if non-null', async () => { + it('even if nullable', async () => { const typeDefs = ` type Query { getOuter: Outer } type Outer { - innerList: [Inner!]! + innerList: [Inner]! } type Inner { mandatoryField: String! @@ -167,7 +175,9 @@ describe('passes along errors for missing fields on list', () => { const result = await graphql(mergedSchema, `{ getOuter { innerList { mandatoryField } } }`); expect(result).to.deep.equal({ data: { - getOuter: null, + getOuter: { + innerList: [null], + }, }, errors: [{ locations: [{ @@ -177,6 +187,9 @@ describe('passes along errors for missing fields on list', () => { message: 'Cannot return null for non-nullable field Inner.mandatoryField.', path: [ 'getOuter', + 'innerList', + 0, + 'mandatoryField', ], }] }); From 300f86df7125922af151c414ae6bc86624aee176 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 7 Nov 2019 11:55:14 -0500 Subject: [PATCH 100/250] refactor(handleResult): remove some extra logic checks. Streamlines code to skip a few extra checks with full recursive version, i.e. composite types do not require parsing. --- src/stitching/checkResultAndHandleErrors.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 77fd767f866..eaf1dae253b 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -51,20 +51,25 @@ export function handleResult( ): any { const nullableType = getNullableType(info.returnType); - if (isCompositeType(nullableType) || isListType(nullableType)) { - result = createMergedResult(result, errors); + if (isLeafType(nullableType)) { + return nullableType.parseValue(result); + } else if (isCompositeType(nullableType)) { + return createMergedResult(result, errors); + } else if (isListType(nullableType)) { + return createMergedResult(result, errors).map( + (r: any) => parseOutputValue(getNullableType(nullableType.ofType), r) + ); } - - return parseOutputValue(nullableType, result); } function parseOutputValue(type: GraphQLType, value: any) { - if (isListType(type)) { - return value.map((v: any) => parseOutputValue(getNullableType(type.ofType), v)); - } else if (isLeafType(type)) { + if (isLeafType(type)) { return type.parseValue(value); + } else if (isCompositeType(type)) { + return value; + } else if (isListType(type)) { + return value.map((v: any) => parseOutputValue(getNullableType(type.ofType), v)); } - return value; } export function handleNull( From 743bc30fc2de77cbca064fc8d628273efb5162d6 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 7 Nov 2019 12:09:33 -0500 Subject: [PATCH 101/250] refactor(handleNull): refactor to handleErrors Consolidates a bit more code within checkResultAndHandleErrors. --- src/stitching/checkResultAndHandleErrors.ts | 34 ++++++--------------- src/stitching/defaultMergedResolver.ts | 4 +-- src/stitching/errors.ts | 13 ++------ 3 files changed, 14 insertions(+), 37 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index eaf1dae253b..751fc98967e 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -25,20 +25,8 @@ export function checkResultAndHandleErrors( responseKey = getResponseKeyFromInfo(info); } - if (!result.data) { - if (result.errors) { - throw relocatedError( - combineErrors(result.errors), - info.fieldNodes, - responsePathAsArray(info.path) - ); - } else { - return null; - } - } - - if (result.data[responseKey] == null) { - return handleNull(info, result.errors || []); + if (!result.data || result.data[responseKey] == null) { + return (result.errors) ? handleErrors(info, result.errors) : null; } return handleResult(info, result.data[responseKey], result.errors || []); @@ -72,17 +60,13 @@ function parseOutputValue(type: GraphQLType, value: any) { } } -export function handleNull( +export function handleErrors( info: GraphQLResolveInfo, errors: ReadonlyArray, -): null { - if (errors.length) { - throw relocatedError( - combineErrors(errors), - info.fieldNodes, - responsePathAsArray(info.path) - ); - } else { - return null; - } +) { + throw relocatedError( + combineErrors(errors), + info.fieldNodes, + responsePathAsArray(info.path) + ); } diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index 8af9d8a8bbf..81e8ce11268 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,6 +1,6 @@ import { GraphQLFieldResolver, defaultFieldResolver } from 'graphql'; import { getErrorsFromParent, MERGED_NULL_SYMBOL } from './errors'; -import { handleResult, handleNull } from './checkResultAndHandleErrors'; +import { handleResult, handleErrors } from './checkResultAndHandleErrors'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; // Resolver that knows how to: @@ -24,7 +24,7 @@ const defaultMergedResolver: GraphQLFieldResolver = (parent, args, con const result = parent[responseKey]; if (result == null || result[MERGED_NULL_SYMBOL]) { - return handleNull(info, errors); + return (errors.length) ? handleErrors(info, errors) : null; } return handleResult(info, result, errors); diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index c61ba9127d9..3d5f41c1066 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -3,13 +3,16 @@ import { ASTNode } from 'graphql'; +export let MERGED_NULL_SYMBOL: any; export let ERROR_SYMBOL: any; if ( (typeof global !== 'undefined' && 'Symbol' in global) || (typeof window !== 'undefined' && 'Symbol' in window) ) { + MERGED_NULL_SYMBOL = Symbol('mergedNull'); ERROR_SYMBOL = Symbol('subSchemaErrors'); } else { + MERGED_NULL_SYMBOL = '@@__mergedNull'; ERROR_SYMBOL = '@@__subSchemaErrors'; } @@ -40,16 +43,6 @@ export function relocatedError( ); } -export let MERGED_NULL_SYMBOL: any; -if ( - (typeof global !== 'undefined' && 'Symbol' in global) || - (typeof window !== 'undefined' && 'Symbol' in window) -) { - MERGED_NULL_SYMBOL = Symbol('mergedNull'); -} else { - MERGED_NULL_SYMBOL = '@@__mergedNull'; -} - export function createMergedResult(object: any, childrenErrors: ReadonlyArray = []): any { if (!object) { object = { From 18c03b8404cdf6852fcd172846a8cf3749258bb3 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 7 Nov 2019 14:00:32 -0500 Subject: [PATCH 102/250] fix(stitching): latest fix breaks zeros --- src/stitching/errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 3d5f41c1066..79b0449f41c 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -44,7 +44,7 @@ export function relocatedError( } export function createMergedResult(object: any, childrenErrors: ReadonlyArray = []): any { - if (!object) { + if (object == null) { object = { [MERGED_NULL_SYMBOL]: true, }; From fb45bbb80a5362feaa0ba13f5b56dbc520edf249 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 7 Nov 2019 14:20:41 -0500 Subject: [PATCH 103/250] fix(stitching): don't annotate primitives --- src/stitching/errors.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 79b0449f41c..4ae49eb2ba5 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -48,6 +48,8 @@ export function createMergedResult(object: any, childrenErrors: ReadonlyArray Date: Fri, 8 Nov 2019 15:26:09 -0500 Subject: [PATCH 104/250] feat(mergeTypes): initial version --- src/Interfaces.ts | 19 ++- src/stitching/checkResultAndHandleErrors.ts | 17 ++- src/stitching/defaultMergedResolver.ts | 39 +++++-- src/stitching/delegateToSchema.ts | 10 +- src/stitching/errors.ts | 24 +++- src/stitching/mergeSchemas.ts | 67 ++++++----- src/test/testAlternateMergeSchemas.ts | 117 ++++++++++++++++++- src/transforms/CheckResultAndHandleErrors.ts | 18 ++- src/transforms/transformSchema.ts | 26 ++--- 9 files changed, 269 insertions(+), 68 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 1ee7c5da0bf..788450adc91 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -83,8 +83,22 @@ export type SubschemaConfig = { fetcher?: Fetcher; dispatcher?: Dispatcher; transforms?: Array; + mergedTypeConfigs?: Record; }; +export type MergedTypeConfig = { + fragment?: string; + mergedTypeResolver: MergedTypeResolver; +}; + +export type MergedTypeResolver = ( + subschema: GraphQLSchema | SubschemaConfig, + parent: any, + args: Record, + context: Record, + info: IGraphQLToolsResolveInfo, +) => any; + export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array }; export type SchemaLikeObject = @@ -128,6 +142,7 @@ export type MergeInfo = { field: string; fragment: string; }>; + mergedTypes: Record>, delegateToSchema(options: IDelegateToSchemaOptions): any; }; @@ -240,10 +255,10 @@ export type OnTypeConflict = ( right: GraphQLNamedType, info?: { left: { - schema?: GraphQLSchema; + schema?: GraphQLSchema | SubschemaConfig; }; right: { - schema?: GraphQLSchema; + schema?: GraphQLSchema | SubschemaConfig; }; }, ) => GraphQLNamedType; diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 751fc98967e..bb8cbaaf213 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -8,6 +8,7 @@ import { ExecutionResult, GraphQLError, GraphQLType, + GraphQLSchema, } from 'graphql'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; import { @@ -15,11 +16,16 @@ import { combineErrors, createMergedResult } from './errors'; +import { + SubschemaConfig, + IGraphQLToolsResolveInfo, +} from '../Interfaces'; export function checkResultAndHandleErrors( result: ExecutionResult, info: GraphQLResolveInfo, - responseKey?: string + responseKey?: string, + subschema?: GraphQLSchema | SubschemaConfig, ): any { if (!responseKey) { responseKey = getResponseKeyFromInfo(info); @@ -29,20 +35,21 @@ export function checkResultAndHandleErrors( return (result.errors) ? handleErrors(info, result.errors) : null; } - return handleResult(info, result.data[responseKey], result.errors || []); + return handleResult(info, result.data[responseKey], result.errors || [], [subschema]); } export function handleResult( - info: GraphQLResolveInfo, + info: IGraphQLToolsResolveInfo, result: any, - errors: ReadonlyArray + errors: ReadonlyArray, + subschemas: Array, ): any { const nullableType = getNullableType(info.returnType); if (isLeafType(nullableType)) { return nullableType.parseValue(result); } else if (isCompositeType(nullableType)) { - return createMergedResult(result, errors); + return createMergedResult(result, errors, subschemas); } else if (isListType(nullableType)) { return createMergedResult(result, errors).map( (r: any) => parseOutputValue(getNullableType(nullableType.ofType), r) diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index 81e8ce11268..1f6842fae8e 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,13 +1,19 @@ -import { GraphQLFieldResolver, defaultFieldResolver } from 'graphql'; -import { getErrorsFromParent, MERGED_NULL_SYMBOL } from './errors'; +import { defaultFieldResolver, getNamedType, ExecutionResult } from 'graphql'; +import { getErrorsFromParent, getSubschemasFromParent, MERGED_NULL_SYMBOL } from './errors'; import { handleResult, handleErrors } from './checkResultAndHandleErrors'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; +import { IGraphQLToolsResolveInfo } from '../Interfaces'; // Resolver that knows how to: // a) handle aliases for proxied schemas // b) handle errors from proxied schemas // c) handle external to internal enum coversion -const defaultMergedResolver: GraphQLFieldResolver = (parent, args, context, info) => { +export default async function defaultMergedResolver( + parent: Record, + args: Record, + context: Record, + info: IGraphQLToolsResolveInfo, +) { if (!parent) { return null; } @@ -17,7 +23,7 @@ const defaultMergedResolver: GraphQLFieldResolver = (parent, args, con // check to see if parent is not a proxied result, i.e. if parent resolver was manually overwritten // See https://github.com/apollographql/graphql-tools/issues/967 - if (!Array.isArray(errors)) { + if (!errors) { return defaultFieldResolver(parent, args, context, info); } @@ -27,7 +33,26 @@ const defaultMergedResolver: GraphQLFieldResolver = (parent, args, con return (errors.length) ? handleErrors(info, errors) : null; } - return handleResult(info, result, errors); -}; + const parentSubschemas = getSubschemasFromParent(parent); + const mergedResult = handleResult(info, result, errors, parentSubschemas); + if (info.mergeInfo) { + const typeName = getNamedType(info.returnType).name; + const initialSubschemas = info.mergeInfo.mergedTypes[typeName]; + if (initialSubschemas) { + const remainingSubschemas = info.mergeInfo.mergedTypes[typeName].filter( + subschema => !parentSubschemas.includes(subschema) + ); + if (remainingSubschemas.length) { + const additionalResults = await Promise.all(remainingSubschemas.map(subschema => { + const mergedTypeResolver = subschema.mergedTypeConfigs[typeName].mergedTypeResolver; + return mergedTypeResolver(subschema, parent, args, context, info); + })); + additionalResults.forEach((additionalResult: ExecutionResult) => { + Object.assign(result, additionalResult); + }); + } + } + } -export default defaultMergedResolver; + return mergedResult; +} diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index f875012d15d..65653cf3461 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -57,7 +57,7 @@ export default function delegateToSchema( } async function delegateToSchemaImplementation({ - schema: schemaOrSubschemaConfig, + schema: subschema, rootValue, info, operation = info.operation.operation, @@ -71,13 +71,13 @@ async function delegateToSchemaImplementation({ let targetSchema: GraphQLSchema; let subSchemaConfig: SubschemaConfig; - if (isSubschemaConfig(schemaOrSubschemaConfig)) { - subSchemaConfig = schemaOrSubschemaConfig; + if (isSubschemaConfig(subschema)) { + subSchemaConfig = subschema; targetSchema = subSchemaConfig.schema; rootValue = rootValue || subSchemaConfig.rootValue || info.rootValue; transforms = transforms.concat((subSchemaConfig.transforms || []).slice().reverse()); } else { - targetSchema = schemaOrSubschemaConfig; + targetSchema = subschema; rootValue = rootValue || info.rootValue; } @@ -98,7 +98,7 @@ async function delegateToSchemaImplementation({ }; transforms = [ - new CheckResultAndHandleErrors(info, fieldName), + new CheckResultAndHandleErrors(info, fieldName, subschema), ...transforms, new ExpandAbstractTypes(info.schema, targetSchema), ]; diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 4ae49eb2ba5..ad895d572e9 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -1,19 +1,24 @@ import { GraphQLError, - ASTNode + ASTNode, + GraphQLSchema } from 'graphql'; +import { SubschemaConfig } from '../Interfaces'; export let MERGED_NULL_SYMBOL: any; +export let SUBSCHEMAS_SYMBOL: any; export let ERROR_SYMBOL: any; if ( (typeof global !== 'undefined' && 'Symbol' in global) || (typeof window !== 'undefined' && 'Symbol' in window) ) { MERGED_NULL_SYMBOL = Symbol('mergedNull'); - ERROR_SYMBOL = Symbol('subSchemaErrors'); + SUBSCHEMAS_SYMBOL = Symbol('subschemas'); + ERROR_SYMBOL = Symbol('subschemaErrors'); } else { MERGED_NULL_SYMBOL = '@@__mergedNull'; - ERROR_SYMBOL = '@@__subSchemaErrors'; + SUBSCHEMAS_SYMBOL = Symbol('subschemas'); + ERROR_SYMBOL = '@@__subschemaErrors'; } export function relocatedError( @@ -43,7 +48,11 @@ export function relocatedError( ); } -export function createMergedResult(object: any, childrenErrors: ReadonlyArray = []): any { +export function createMergedResult( + object: any, + childrenErrors: ReadonlyArray = [], + subschemas: Array = [], +): any { if (object == null) { object = { [MERGED_NULL_SYMBOL]: true, @@ -71,7 +80,7 @@ export function createMergedResult(object: any, childrenErrors: ReadonlyArray createMergedResult(item, byIndex[index])); + object = object.map((item, index) => createMergedResult(item, byIndex[index]), subschemas); return object; } @@ -84,6 +93,7 @@ export function createMergedResult(object: any, childrenErrors: ReadonlyArray { + return object && object[SUBSCHEMAS_SYMBOL]; +} + export function getErrorsFromParent( object: any, fieldName: string diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 8ec23b71ae1..bd5d6aca976 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -46,6 +46,7 @@ import { type MergeTypeCandidate = { type: GraphQLNamedType; schema?: GraphQLSchema; + subschema?: GraphQLSchema | SubschemaConfig; }; type CandidateSelector = ( @@ -57,6 +58,7 @@ export default function mergeSchemas({ types = [], typeDefs, schemas: schemaLikeObjects = [], + mergeTypes = [], onTypeConflict, resolvers, schemaDirectives, @@ -67,6 +69,7 @@ export default function mergeSchemas({ types?: Array; typeDefs?: string | DocumentNode; schemas?: Array; + mergeTypes?: Array; onTypeConflict?: OnTypeConflict; resolvers?: IResolversParameter; schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor }; @@ -108,6 +111,7 @@ export default function mergeSchemas({ addTypeCandidate(typeCandidates, typeName, { schema, type: operationTypes[typeName], + subschema: schemaLikeObject, }); } }); @@ -132,6 +136,7 @@ export default function mergeSchemas({ addTypeCandidate(typeCandidates, type.name, { schema, type, + subschema: schemaLikeObject, }); } }); @@ -169,7 +174,18 @@ export default function mergeSchemas({ } }); - const mergeInfo = createMergeInfo(allSchemas, fragments); + const mergedTypes = {}; + + mergeTypes.forEach(typeName => { + if (typeCandidates[typeName]) { + mergedTypes[typeName] = + typeCandidates[typeName].map(typeCandidate => typeCandidate.subschema); + } else { + throw new Error(`Cannot merge type '${typeName}', type not found.`); + } + }); + + const mergeInfo = createMergeInfo(allSchemas, fragments, mergedTypes); if (!resolvers) { resolvers = {}; @@ -191,11 +207,19 @@ export default function mergeSchemas({ } Object.keys(typeCandidates).forEach(typeName => { - typeMap[typeName] = mergeTypeCandidates( - typeName, - typeCandidates[typeName], - onTypeConflict ? onTypeConflictToCandidateSelector(onTypeConflict) : undefined - ); + if ( + typeName === 'Query' || + typeName === 'Mutation' || + typeName === 'Subscription' || + mergeTypes.includes(typeName) + ) { + typeMap[typeName] = mergeFields(typeName, typeCandidates[typeName]); + } else { + const candidateSelector = onTypeConflict ? + onTypeConflictToCandidateSelector(onTypeConflict) : + (cands: Array) => cands[cands.length - 1]; + typeMap[typeName] = candidateSelector(typeCandidates[typeName]).type; + } }); healTypes(typeMap, directives, { skipPruning: true }); @@ -279,6 +303,7 @@ function createMergeInfo( field: string; fragment: string; }>, + mergedTypes: Record>, ): MergeInfo { return { delegate( @@ -317,7 +342,8 @@ function createMergeInfo( transforms: options.transforms }); }, - fragments + fragments, + mergedTypes, }; } @@ -388,23 +414,12 @@ function operationToRootType( } } -function mergeTypeCandidates( - name: string, - candidates: Array, - candidateSelector?: CandidateSelector -): GraphQLNamedType { - if (!candidateSelector) { - candidateSelector = cands => cands[cands.length - 1]; - } - if (name === 'Query' || name === 'Mutation' || name === 'Subscription') { - return new GraphQLObjectType({ - name, - fields: candidates.reduce((acc, candidate) => ({ - ...acc, - ...(candidate.type as GraphQLObjectType).toConfig().fields, - }), {}), - }); - } else { - return candidateSelector(candidates).type; - } +function mergeFields(typeName: string, candidates: Array): GraphQLNamedType { + return new GraphQLObjectType({ + name: typeName, + fields: candidates.reduce((acc, candidate) => ({ + ...acc, + ...(candidate.type as GraphQLObjectType).toConfig().fields, + }), {}), + }); } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 7bd4fb2536e..48679c92848 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -43,7 +43,7 @@ import { createMergedResolver, extractFields, } from '../stitching'; -import { SubschemaConfig } from '../Interfaces'; +import { SubschemaConfig, MergedTypeConfig } from '../Interfaces'; import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; function renameFieldNode(fieldNode: FieldNode, name: string): FieldNode { @@ -1465,3 +1465,118 @@ describe('onTypeConflict', () => { expect(result2.data).to.be.undefined; }); }); + +describe('mergeTypes', () => { + let schema1: GraphQLSchema; + let schema2: GraphQLSchema; + + beforeEach(() => { + const typeDefs1 = ` + type Query { + rootField1: Wrapper + getTest: Test + } + + type Wrapper { + test: Test + } + + type Test { + field1: String + } + `; + + const typeDefs2 = ` + type Query { + rootField2: Wrapper + getTest: Test + } + + type Wrapper { + test: Test + } + + type Test { + field2: String + } + `; + + schema1 = makeExecutableSchema({ + typeDefs: typeDefs1, + resolvers: { + Query: { + rootField1: () => ({ test: {} }), + getTest: () => ({}), + }, + Test: { + field1: () => '1', + } + } + }); + + schema2 = makeExecutableSchema({ + typeDefs: typeDefs2, + resolvers: { + Query: { + rootField2: () => ({ test: {} }), + getTest: () => ({}), + }, + Test: { + field2: () => '2', + } + } + }); + }); + + it('can merge types', async () => { + const subschemaConfig1: SubschemaConfig = { schema: schema1 }; + const subschemaConfig2: SubschemaConfig = { schema: schema2 }; + + const mergedTypeConfigs1: Record = { + Test: { + mergedTypeResolver: (subschema, parent, args, context, info) => { + return delegateToSchema({ + schema: subschemaConfig1, + operation: 'query', + fieldName: 'getTest', + context, + info, + }); + } + } + }; + + const mergedTypeConfigs2: Record = { + Test: { + mergedTypeResolver: (subschema, parent, args, context, info) => { + return delegateToSchema({ + schema: subschemaConfig2, + operation: 'query', + fieldName: 'getTest', + context, + info, + }); + } + } + }; + + subschemaConfig1.mergedTypeConfigs = mergedTypeConfigs1; + subschemaConfig2.mergedTypeConfigs = mergedTypeConfigs2; + + const mergedSchema = mergeSchemas({ + subschemas: [subschemaConfig1, subschemaConfig2], + mergeTypes: ['Test'], + }); + const result1 = await graphql(mergedSchema, `{ rootField1 { test { field1 field2 } } }`); + expect(result1).to.deep.equal({ + data: { + rootField1: { + test: { + field1: '1', + field2: '2', + } + } + } + }); + }); +}); diff --git a/src/transforms/CheckResultAndHandleErrors.ts b/src/transforms/CheckResultAndHandleErrors.ts index 5738af15968..aaff1af000c 100644 --- a/src/transforms/CheckResultAndHandleErrors.ts +++ b/src/transforms/CheckResultAndHandleErrors.ts @@ -1,17 +1,29 @@ -import { GraphQLResolveInfo } from 'graphql'; +import { GraphQLResolveInfo, GraphQLSchema } from 'graphql'; import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; import { Transform } from './transforms'; +import { SubschemaConfig } from '../Interfaces'; export default class CheckResultAndHandleErrors implements Transform { private info: GraphQLResolveInfo; private fieldName?: string; + private subschema?: GraphQLSchema | SubschemaConfig; - constructor(info: GraphQLResolveInfo, fieldName?: string) { + constructor( + info: GraphQLResolveInfo, + fieldName?: string, + subschema?: GraphQLSchema | SubschemaConfig + ) { this.info = info; this.fieldName = fieldName; + this.subschema = subschema; } public transformResult(result: any): any { - return checkResultAndHandleErrors(result, this.info, this.fieldName); + return checkResultAndHandleErrors( + result, + this.info, + this.fieldName, + this.subschema + ); } } diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index 59589f40827..c6a69044685 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -14,41 +14,39 @@ import { import { cloneSchema } from '../utils/clone'; export function wrapSchema( - schemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, + subschema: GraphQLSchema | SubschemaConfig, transforms: Array = [], ): GraphQLSchema { - let subschemaConfig: SubschemaConfig; - if (isSubschemaConfig(schemaOrSubschemaConfig)) { - subschemaConfig = { - ...schemaOrSubschemaConfig, - transforms: (schemaOrSubschemaConfig.transforms || []).concat(transforms), - }; + if (isSubschemaConfig(subschema)) { + if (transforms) { + subschema.transforms = (subschema.transforms || []).concat(transforms); + } } else { - subschemaConfig = { - schema: schemaOrSubschemaConfig, + subschema = { + schema: subschema, transforms, }; } - const schema = cloneSchema(subschemaConfig.schema); + const schema = cloneSchema(subschema.schema); stripResolvers(schema); addResolveFunctionsToSchema({ schema, - resolvers: generateProxyingResolvers(subschemaConfig), + resolvers: generateProxyingResolvers(subschema), resolverValidationOptions: { allowResolversNotInSchema: true, }, }); - return applySchemaTransforms(schema, subschemaConfig.transforms); + return applySchemaTransforms(schema, subschema.transforms); } export default function transformSchema( - schemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, + subschema: GraphQLSchema | SubschemaConfig, transforms: Array, ): GraphQLSchema & { transforms: Array } { - const schema = wrapSchema(schemaOrSubschemaConfig, transforms); + const schema = wrapSchema(subschema, transforms); (schema as any).transforms = transforms.slice().reverse(); return schema as GraphQLSchema & { transforms: Array }; } From 16fa01516c011acf5b4151bf25c082f56922fe1a Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 19 Nov 2019 09:31:49 -0500 Subject: [PATCH 105/250] refactor(stitching): resolveFromParentTypename streamline arguments --- src/stitching/makeMergedType.ts | 2 +- src/stitching/resolveFromParentTypename.ts | 3 --- src/stitching/typeFromAST.ts | 6 ++---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/stitching/makeMergedType.ts b/src/stitching/makeMergedType.ts index 3cef8c21cf4..e659a739004 100644 --- a/src/stitching/makeMergedType.ts +++ b/src/stitching/makeMergedType.ts @@ -17,6 +17,6 @@ export function makeMergedType(type: GraphQLType): void { fieldMap[fieldName].subscribe = null; }); } else if (type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) { - type.resolveType = (parent, context, info) => resolveFromParentTypename(parent, info.schema); + type.resolveType = parent => resolveFromParentTypename(parent); } } diff --git a/src/stitching/resolveFromParentTypename.ts b/src/stitching/resolveFromParentTypename.ts index 06bfbec722d..c81434e53fd 100644 --- a/src/stitching/resolveFromParentTypename.ts +++ b/src/stitching/resolveFromParentTypename.ts @@ -1,8 +1,5 @@ -import { GraphQLSchema } from 'graphql'; - export default function resolveFromParentTypename( parent: any, - schema: GraphQLSchema, ) { const parentTypename: string = parent['__typename']; if (!parentTypename) { diff --git a/src/stitching/typeFromAST.ts b/src/stitching/typeFromAST.ts index 2026f8f6189..d76f99a5d5e 100644 --- a/src/stitching/typeFromAST.ts +++ b/src/stitching/typeFromAST.ts @@ -84,8 +84,7 @@ function makeInterfaceType( name: node.name.value, fields: () => makeFields(node.fields), description: getDescription(node, backcompatOptions), - resolveType: (parent, context, info) => - resolveFromParentTypename(parent, info.schema), + resolveType: parent => resolveFromParentTypename(parent), }); } @@ -115,8 +114,7 @@ function makeUnionType( type => resolveType(type, 'object') as GraphQLObjectType, ), description: getDescription(node, backcompatOptions), - resolveType: (parent, context, info) => - resolveFromParentTypename(parent, info.schema), + resolveType: parent => resolveFromParentTypename(parent), }); } From 7b3398832fb67a23c5003a8c9afcb83890f36edf Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 19 Nov 2019 10:33:50 -0500 Subject: [PATCH 106/250] feat(stitching): precompile fragment replacements --- src/Interfaces.ts | 6 + src/stitching/delegateToSchema.ts | 6 +- src/stitching/mergeSchemas.ts | 49 +++++++- src/test/testTransforms.ts | 110 +++++++++++++++++ src/transforms/AddReplacementFragments.ts | 78 ++++++++++++ src/transforms/ReplaceFieldWithFragment.ts | 101 +--------------- src/transforms/index.ts | 3 +- src/utils/fragments.ts | 134 +++++++++++++++++++++ src/utils/index.ts | 1 + 9 files changed, 380 insertions(+), 108 deletions(-) create mode 100644 src/transforms/AddReplacementFragments.ts create mode 100644 src/utils/fragments.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 788450adc91..eb91b3ad5f8 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -19,6 +19,7 @@ import { GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType, + InlineFragmentNode, } from 'graphql'; import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; @@ -142,10 +143,15 @@ export type MergeInfo = { field: string; fragment: string; }>; + replacementFragments: ReplacementFragmentMapping, mergedTypes: Record>, delegateToSchema(options: IDelegateToSchemaOptions): any; }; +export type ReplacementFragmentMapping = { + [typeName: string]: { [fieldName: string]: InlineFragmentNode }; +}; + export type IFieldResolver> = ( source: TSource, args: TArgs, diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 65653cf3461..7164bdcb166 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -37,7 +37,7 @@ import AddTypenameToAbstract from '../transforms/AddTypenameToAbstract'; import CheckResultAndHandleErrors from '../transforms/CheckResultAndHandleErrors'; import mapAsyncIterator from './mapAsyncIterator'; import ExpandAbstractTypes from '../transforms/ExpandAbstractTypes'; -import ReplaceFieldWithFragment from '../transforms/ReplaceFieldWithFragment'; +import AddReplacementFragments from '../transforms/AddReplacementFragments'; import { ApolloLink, execute as executeLink } from 'apollo-link'; import linkToFetcher from './linkToFetcher'; @@ -103,9 +103,9 @@ async function delegateToSchemaImplementation({ new ExpandAbstractTypes(info.schema, targetSchema), ]; - if (info.mergeInfo && info.mergeInfo.fragments) { + if (info.mergeInfo) { transforms.push( - new ReplaceFieldWithFragment(targetSchema, info.mergeInfo.fragments) + new AddReplacementFragments(targetSchema, info.mergeInfo.replacementFragments) ); } diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index bd5d6aca976..58ad8344b4f 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -2,7 +2,6 @@ import { DocumentNode, GraphQLNamedType, GraphQLObjectType, - GraphQLResolveInfo, GraphQLScalarType, GraphQLSchema, extendSchema, @@ -21,6 +20,8 @@ import { SchemaLikeObject, IResolvers, SubschemaConfig, + ReplacementFragmentMapping, + IGraphQLToolsResolveInfo, } from '../Interfaces'; import { extractExtensionDefinitions, @@ -31,8 +32,8 @@ import typeFromAST from './typeFromAST'; import { Transform, ExpandAbstractTypes, - ReplaceFieldWithFragment, wrapSchema, + AddReplacementFragments, } from '../transforms'; import { SchemaDirectiveVisitor, @@ -41,6 +42,8 @@ import { healTypes, forEachField, mergeDeep, + parseFragmentToInlineFragment, + concatInlineFragments, } from '../utils'; type MergeTypeCandidate = { @@ -262,6 +265,8 @@ export default function mergeSchemas({ }); }); + mergeInfo.replacementFragments = parseReplacementFragments(fragments); + addResolveFunctionsToSchema({ schema: mergedSchema, resolvers: resolvers as IResolvers, @@ -311,7 +316,7 @@ function createMergeInfo( fieldName: string, args: { [key: string]: any }, context: { [key: string]: any }, - info: GraphQLResolveInfo, + info: IGraphQLToolsResolveInfo, transforms?: Array, ) { console.warn( @@ -320,7 +325,7 @@ function createMergeInfo( ); const schema = guessSchemaByRootField(allSchemas, operation, fieldName); const expandTransforms = new ExpandAbstractTypes(info.schema, schema); - const fragmentTransform = new ReplaceFieldWithFragment(schema, fragments); + const fragmentTransform = new AddReplacementFragments(schema, info.mergeInfo.replacementFragments); return delegateToSchema({ schema, operation, @@ -343,10 +348,46 @@ function createMergeInfo( }); }, fragments, + replacementFragments: undefined, mergedTypes, }; } +function parseReplacementFragments( + fragments: Array<{ + field: string; + fragment: string; + }> +): ReplacementFragmentMapping { + const mapping = {}; + for (const { field, fragment } of fragments) { + const parsedFragment = parseFragmentToInlineFragment(fragment); + const actualTypeName = parsedFragment.typeCondition.name.value; + mapping[actualTypeName] = mapping[actualTypeName] || {}; + + if (mapping[actualTypeName][field]) { + mapping[actualTypeName][field].push(parsedFragment); + } else { + mapping[actualTypeName][field] = [parsedFragment]; + } + } + + const replacementFragments = Object.create({}); + Object.keys(mapping).forEach(typeName => { + Object.keys(mapping[typeName]).forEach(field => { + replacementFragments[typeName] = mapping[typeName] || {}; + if (mapping[typeName][field]) { + replacementFragments[typeName][field] = concatInlineFragments( + typeName, + mapping[typeName][field], + ); + } + }); + }); + + return replacementFragments; +} + function guessSchemaByRootField( schemas: Array, operation: 'query' | 'mutation' | 'subscription', diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 7d5049fda89..8223e3c2a6d 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -26,7 +26,12 @@ import { ReplaceFieldWithFragment, FilterToSchema, TransformQuery, + AddReplacementFragments, } from '../transforms'; +import { + concatInlineFragments, + parseFragmentToInlineFragment +} from '../utils'; describe('transforms', () => { describe('base transform function', () => { @@ -1118,3 +1123,108 @@ describe('transforms', () => { }); }); }); + +describe('replaces field with processed fragment node', () => { + let data: any; + let schema: GraphQLSchema; + let subSchema: GraphQLSchema; + before(() => { + data = { + u1: { + id: 'u1', + name: 'joh', + surname: 'gats', + }, + }; + + subSchema = makeExecutableSchema({ + typeDefs: ` + type User { + id: ID! + name: String! + surname: String! + } + + type Query { + userById(id: ID!): User + } + `, + resolvers: { + Query: { + userById(parent, { id }) { + return data[id]; + }, + }, + }, + }); + + schema = makeExecutableSchema({ + typeDefs: ` + type User { + id: ID! + name: String! + surname: String! + fullname: String! + } + + type Query { + userById(id: ID!): User + } + `, + resolvers: { + Query: { + userById(parent, { id }, context, info) { + return delegateToSchema({ + schema: subSchema, + operation: 'query', + fieldName: 'userById', + args: { id }, + context, + info, + transforms: [ + new AddReplacementFragments(subSchema, { + User: { + fullname: concatInlineFragments( + 'User', + [ + parseFragmentToInlineFragment(`fragment UserName on User { name }`), + parseFragmentToInlineFragment(`fragment UserSurname on User { surname }`), + ], + ), + } + }), + ], + }); + }, + }, + User: { + fullname(parent, args, context, info) { + return `${parent.name} ${parent.surname}`; + }, + }, + }, + }); + }); + it('should work', async () => { + const result = await graphql( + schema, + ` + query { + userById(id: "u1") { + id + fullname + } + } + `, + ); + + expect(result).to.deep.equal({ + data: { + userById: { + id: 'u1', + fullname: 'joh gats', + }, + }, + }); + }); +}); diff --git a/src/transforms/AddReplacementFragments.ts b/src/transforms/AddReplacementFragments.ts new file mode 100644 index 00000000000..33c27cb190c --- /dev/null +++ b/src/transforms/AddReplacementFragments.ts @@ -0,0 +1,78 @@ +import { + DocumentNode, + GraphQLSchema, + GraphQLType, + Kind, + SelectionSetNode, + TypeInfo, + visit, + visitWithTypeInfo, +} from 'graphql'; +import { Request, ReplacementFragmentMapping } from '../Interfaces'; +import { Transform } from './transforms'; + +export default class AddReplacementFragments implements Transform { + private targetSchema: GraphQLSchema; + private fragments: ReplacementFragmentMapping; + + constructor( + targetSchema: GraphQLSchema, + fragments: ReplacementFragmentMapping, + ) { + this.targetSchema = targetSchema; + this.fragments = fragments; + } + + public transformRequest(originalRequest: Request): Request { + const document = replaceFieldsWithFragments( + this.targetSchema, + originalRequest.document, + this.fragments, + ); + return { + ...originalRequest, + document, + }; + } +} + +function replaceFieldsWithFragments( + targetSchema: GraphQLSchema, + document: DocumentNode, + fragments: ReplacementFragmentMapping, +): DocumentNode { + const typeInfo = new TypeInfo(targetSchema); + return visit( + document, + visitWithTypeInfo(typeInfo, { + [Kind.SELECTION_SET]( + node: SelectionSetNode, + ): SelectionSetNode | null | undefined { + const parentType: GraphQLType = typeInfo.getParentType(); + if (parentType) { + const parentTypeName = parentType.name; + let selections = node.selections; + + if (fragments[parentTypeName]) { + node.selections.forEach(selection => { + if (selection.kind === Kind.FIELD) { + const name = selection.name.value; + const fragment = fragments[parentTypeName][name]; + if (fragment) { + selections = selections.concat(fragment); + } + } + }); + } + + if (selections !== node.selections) { + return { + ...node, + selections, + }; + } + } + }, + }), + ); +} diff --git a/src/transforms/ReplaceFieldWithFragment.ts b/src/transforms/ReplaceFieldWithFragment.ts index 41b472d9629..62d6b32ac89 100644 --- a/src/transforms/ReplaceFieldWithFragment.ts +++ b/src/transforms/ReplaceFieldWithFragment.ts @@ -10,10 +10,10 @@ import { parse, visit, visitWithTypeInfo, - SelectionNode, } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; +import { concatInlineFragments } from '../utils'; export default class ReplaceFieldWithFragment implements Transform { private targetSchema: GraphQLSchema; @@ -130,102 +130,3 @@ function parseFragmentToInlineFragment( throw new Error('Could not parse fragment'); } -function concatInlineFragments( - type: string, - fragments: InlineFragmentNode[], -): InlineFragmentNode { - const fragmentSelections: SelectionNode[] = fragments.reduce( - (selections, fragment) => { - return selections.concat(fragment.selectionSet.selections); - }, - [], - ); - - const deduplicatedFragmentSelection: SelectionNode[] = deduplicateSelection( - fragmentSelections, - ); - - return { - kind: Kind.INLINE_FRAGMENT, - typeCondition: { - kind: Kind.NAMED_TYPE, - name: { - kind: Kind.NAME, - value: type, - }, - }, - selectionSet: { - kind: Kind.SELECTION_SET, - selections: deduplicatedFragmentSelection, - }, - }; -} - -function deduplicateSelection(nodes: SelectionNode[]): SelectionNode[] { - const selectionMap = nodes.reduce<{ [key: string]: SelectionNode }>( - (map, node) => { - switch (node.kind) { - case 'Field': { - if (node.alias) { - if (map.hasOwnProperty(node.alias.value)) { - return map; - } else { - return { - ...map, - [node.alias.value]: node, - }; - } - } else { - if (map.hasOwnProperty(node.name.value)) { - return map; - } else { - return { - ...map, - [node.name.value]: node, - }; - } - } - } - case 'FragmentSpread': { - if (map.hasOwnProperty(node.name.value)) { - return map; - } else { - return { - ...map, - [node.name.value]: node, - }; - } - } - case 'InlineFragment': { - if (map.__fragment) { - const fragment = map.__fragment as InlineFragmentNode; - - return { - ...map, - __fragment: concatInlineFragments( - fragment.typeCondition.name.value, - [fragment, node], - ), - }; - } else { - return { - ...map, - __fragment: node, - }; - } - } - default: { - return map; - } - } - }, - {}, - ); - - const selection = Object.keys(selectionMap).reduce( - (selectionList, node) => selectionList.concat(selectionMap[node]), - [], - ); - - return selection; -} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index fd72dfffdec..9d562338e31 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -6,7 +6,7 @@ export { default as transformSchema, wrapSchema } from './transformSchema'; export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErrors'; -export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; +export { default as AddReplacementFragments } from './AddReplacementFragments'; export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; export { default as FilterToSchema } from './FilterToSchema'; @@ -26,3 +26,4 @@ export { default as TransformQuery } from './TransformQuery'; export { default as ExtendSchema } from './ExtendSchema'; export { default as WrapType } from './WrapType'; export { default as MapFields } from './MapFields'; +export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; diff --git a/src/utils/fragments.ts b/src/utils/fragments.ts new file mode 100644 index 00000000000..303374b7ebf --- /dev/null +++ b/src/utils/fragments.ts @@ -0,0 +1,134 @@ +import { + InlineFragmentNode, + SelectionNode, + Kind, + parse, + OperationDefinitionNode, +} from 'graphql'; + +export function concatInlineFragments( + type: string, + fragments: InlineFragmentNode[], +): InlineFragmentNode { + const fragmentSelections: SelectionNode[] = fragments.reduce( + (selections, fragment) => { + return selections.concat(fragment.selectionSet.selections); + }, + [], + ); + + const deduplicatedFragmentSelection: SelectionNode[] = deduplicateSelection( + fragmentSelections, + ); + + return { + kind: Kind.INLINE_FRAGMENT, + typeCondition: { + kind: Kind.NAMED_TYPE, + name: { + kind: Kind.NAME, + value: type, + }, + }, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: deduplicatedFragmentSelection, + }, + }; +} + +function deduplicateSelection(nodes: SelectionNode[]): SelectionNode[] { + const selectionMap = nodes.reduce<{ [key: string]: SelectionNode }>( + (map, node) => { + switch (node.kind) { + case 'Field': { + if (node.alias) { + if (map.hasOwnProperty(node.alias.value)) { + return map; + } else { + return { + ...map, + [node.alias.value]: node, + }; + } + } else { + if (map.hasOwnProperty(node.name.value)) { + return map; + } else { + return { + ...map, + [node.name.value]: node, + }; + } + } + } + case 'FragmentSpread': { + if (map.hasOwnProperty(node.name.value)) { + return map; + } else { + return { + ...map, + [node.name.value]: node, + }; + } + } + case 'InlineFragment': { + if (map.__fragment) { + const fragment = map.__fragment as InlineFragmentNode; + + return { + ...map, + __fragment: concatInlineFragments( + fragment.typeCondition.name.value, + [fragment, node], + ), + }; + } else { + return { + ...map, + __fragment: node, + }; + } + } + default: { + return map; + } + } + }, + {}, + ); + + const selection = Object.keys(selectionMap).reduce( + (selectionList, node) => selectionList.concat(selectionMap[node]), + [], + ); + + return selection; +} + +export function parseFragmentToInlineFragment( + definitions: string, +): InlineFragmentNode { + if (definitions.trim().startsWith('fragment')) { + const document = parse(definitions); + for (const definition of document.definitions) { + if (definition.kind === Kind.FRAGMENT_DEFINITION) { + return { + kind: Kind.INLINE_FRAGMENT, + typeCondition: definition.typeCondition, + selectionSet: definition.selectionSet, + }; + } + } + } + + const query = parse(`{${definitions}}`) + .definitions[0] as OperationDefinitionNode; + for (const selection of query.selectionSet.selections) { + if (selection.kind === Kind.INLINE_FRAGMENT) { + return selection; + } + } + + throw new Error('Could not parse fragment'); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 7303b6f68fb..53feba20d77 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -12,4 +12,5 @@ export { parseInputValueLiteral, serializeInputValue, } from './transformInputValue'; +export { concatInlineFragments, parseFragmentToInlineFragment } from './fragments'; export { mergeDeep } from './mergeDeep'; From 1ad6015571ec9be238e61a73b7a940a199739caf Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 20 Nov 2019 11:00:50 -0500 Subject: [PATCH 107/250] feat(mergeTypes): adds abiliity to merge types --- src/Interfaces.ts | 15 +- src/stitching/checkResultAndHandleErrors.ts | 123 +++++++++++--- src/stitching/createMergedResolver.ts | 17 +- src/stitching/defaultMergedResolver.ts | 28 +--- src/stitching/delegateToSchema.ts | 6 +- src/stitching/errors.ts | 26 ++- src/stitching/makeRemoteExecutableSchema.ts | 4 +- src/stitching/mergeSchemas.ts | 165 +++++++++---------- src/test/testAlternateMergeSchemas.ts | 28 ++-- src/test/testErrors.ts | 9 +- src/transforms/AddMergedTypeFragments.ts | 72 ++++++++ src/transforms/AddReplacementFragments.ts | 14 +- src/transforms/CheckResultAndHandleErrors.ts | 14 +- src/transforms/index.ts | 1 + 14 files changed, 336 insertions(+), 186 deletions(-) create mode 100644 src/transforms/AddMergedTypeFragments.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index eb91b3ad5f8..3843ec440d5 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -94,8 +94,7 @@ export type MergedTypeConfig = { export type MergedTypeResolver = ( subschema: GraphQLSchema | SubschemaConfig, - parent: any, - args: Record, + originalResult: any, context: Record, info: IGraphQLToolsResolveInfo, ) => any; @@ -144,7 +143,7 @@ export type MergeInfo = { fragment: string; }>; replacementFragments: ReplacementFragmentMapping, - mergedTypes: Record>, + mergedTypes: MergedTypeMapping, delegateToSchema(options: IDelegateToSchemaOptions): any; }; @@ -152,6 +151,11 @@ export type ReplacementFragmentMapping = { [typeName: string]: { [fieldName: string]: InlineFragmentNode }; }; +export type MergedTypeMapping = Record, +}>; + export type IFieldResolver> = ( source: TSource, args: TArgs, @@ -332,3 +336,8 @@ export type TypeVisitor = ( type: GraphQLType, schema: GraphQLSchema, ) => GraphQLNamedType | null | undefined; + +export type Path = { + prev: Path; + key: string | number; +}; diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index bb8cbaaf213..1af39f6fcc5 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -6,9 +6,12 @@ import { isLeafType, isListType, ExecutionResult, + GraphQLCompositeType, GraphQLError, GraphQLType, GraphQLSchema, + FieldNode, + isAbstractType, } from 'graphql'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; import { @@ -19,10 +22,13 @@ import { import { SubschemaConfig, IGraphQLToolsResolveInfo, + Path, } from '../Interfaces'; +import resolveFromParentTypename from './resolveFromParentTypename'; export function checkResultAndHandleErrors( result: ExecutionResult, + context: Record, info: GraphQLResolveInfo, responseKey?: string, subschema?: GraphQLSchema | SubschemaConfig, @@ -32,48 +38,127 @@ export function checkResultAndHandleErrors( } if (!result.data || result.data[responseKey] == null) { - return (result.errors) ? handleErrors(info, result.errors) : null; + return (result.errors) ? handleErrors(info.fieldNodes, info.path, result.errors) : null; } - return handleResult(info, result.data[responseKey], result.errors || [], [subschema]); + return handleResult( + getNullableType(info.returnType), + result.data[responseKey], + result.errors || [], + [subschema], + context, + info, + ); } export function handleResult( - info: IGraphQLToolsResolveInfo, + type: GraphQLType, result: any, errors: ReadonlyArray, subschemas: Array, + context: Record, + info: IGraphQLToolsResolveInfo, ): any { - const nullableType = getNullableType(info.returnType); - - if (isLeafType(nullableType)) { - return nullableType.parseValue(result); - } else if (isCompositeType(nullableType)) { - return createMergedResult(result, errors, subschemas); - } else if (isListType(nullableType)) { - return createMergedResult(result, errors).map( - (r: any) => parseOutputValue(getNullableType(nullableType.ofType), r) + if (isLeafType(type)) { + return type.parseValue(result); + } else if (isCompositeType(type)) { + return mergeResultsFromOtherSubschemas( + type, + createMergedResult(result, errors, subschemas), + subschemas, + context, + info, + ); + } else if (isListType(type)) { + return createMergedResult(result, errors, subschemas).map( + (r: any) => handleListResult( + getNullableType(type.ofType), + r, + subschemas, + context, + info, + ) ); } } -function parseOutputValue(type: GraphQLType, value: any) { +function handleListResult( + type: GraphQLType, + result: any, + subschemas: Array, + context: Record, + info: IGraphQLToolsResolveInfo, +) { if (isLeafType(type)) { - return type.parseValue(value); + return type.parseValue(result); } else if (isCompositeType(type)) { - return value; + return mergeResultsFromOtherSubschemas( + type, + result, + subschemas, + context, + info + ); } else if (isListType(type)) { - return value.map((v: any) => parseOutputValue(getNullableType(type.ofType), v)); + return result.map((r: any) => handleListResult( + getNullableType(type.ofType), + r, + subschemas, + context, + info, + )); } } +async function mergeResultsFromOtherSubschemas( + type: GraphQLCompositeType, + result: any, + subschemas: Array, + context: Record, + info: IGraphQLToolsResolveInfo, +): Promise { + if (info.mergeInfo) { + let typeName: string; + if (isAbstractType(type)) { + typeName = info.schema.getTypeMap()[resolveFromParentTypename(result)].name; + } else { + typeName = type.name; + } + + const initialSchemas = + info.mergeInfo.mergedTypes[typeName] && + info.mergeInfo.mergedTypes[typeName].subschemas; + if (initialSchemas) { + const remainingSubschemas = initialSchemas.filter( + subschema => !subschemas.includes(subschema) + ); + if (remainingSubschemas.length) { + const results = await Promise.all(remainingSubschemas.map(subschema => { + const mergedTypeResolver = subschema.mergedTypeConfigs[typeName].mergedTypeResolver; + return mergedTypeResolver(subschema, result, context, { + ...info, + mergeInfo: { + ...info.mergeInfo, + mergedTypes: {}, + }, + }); + })); + results.forEach((r: ExecutionResult) => Object.assign(result, r)); + } + } + } + + return result; +} + export function handleErrors( - info: GraphQLResolveInfo, + fieldNodes: ReadonlyArray, + path: Path, errors: ReadonlyArray, ) { throw relocatedError( combineErrors(errors), - info.fieldNodes, - responsePathAsArray(info.path) + fieldNodes, + responsePathAsArray(path) ); } diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 93444d2635f..5e64921ed75 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -1,13 +1,13 @@ -import { GraphQLObjectType, getNamedType, responsePathAsArray } from 'graphql'; +import { GraphQLObjectType, getNamedType } from 'graphql'; import { IFieldResolver } from '../Interfaces'; import { - relocatedError, - combineErrors, getErrorsFromParent, createMergedResult, + getSubschemasFromParent, } from './errors'; import defaultMergedResolver from './defaultMergedResolver'; import { extractOneLevelOfFields } from './extractFields'; +import { handleErrors } from './checkResultAndHandleErrors'; export function wrapField(wrapper: string, fieldName: string): IFieldResolver { return createMergedResolver({ fromPath: [wrapper, fieldName] }); @@ -28,7 +28,7 @@ export function createMergedResolver({ toPath?: Array; fromPath?: Array; }): IFieldResolver { - return (parent, args, context, info) => { + return async (parent, args, context, info) => { let fieldNodes = info.fieldNodes; let returnType = info.returnType; @@ -55,19 +55,16 @@ export function createMergedResolver({ for (let i = 0; i < fromParentPathLength; i++) { const responseKey = fromPath[i]; const errors = getErrorsFromParent(parent, responseKey); + const subschemas = getSubschemasFromParent(parent); const result = parent[responseKey]; if (result == null) { if (errors.length) { - throw relocatedError( - combineErrors(errors), - fieldNodes, - responsePathAsArray(path) - ); + handleErrors(fieldNodes, path, errors); } else { return null; } } - parent = createMergedResult(result, errors); + parent = createMergedResult(result, errors, subschemas); } fieldName = fromPath[fromPathLength - 1]; diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index 1f6842fae8e..53a06d82862 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,4 +1,4 @@ -import { defaultFieldResolver, getNamedType, ExecutionResult } from 'graphql'; +import { defaultFieldResolver, getNullableType } from 'graphql'; import { getErrorsFromParent, getSubschemasFromParent, MERGED_NULL_SYMBOL } from './errors'; import { handleResult, handleErrors } from './checkResultAndHandleErrors'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; @@ -8,7 +8,7 @@ import { IGraphQLToolsResolveInfo } from '../Interfaces'; // a) handle aliases for proxied schemas // b) handle errors from proxied schemas // c) handle external to internal enum coversion -export default async function defaultMergedResolver( +export default function defaultMergedResolver( parent: Record, args: Record, context: Record, @@ -30,29 +30,9 @@ export default async function defaultMergedResolver( const result = parent[responseKey]; if (result == null || result[MERGED_NULL_SYMBOL]) { - return (errors.length) ? handleErrors(info, errors) : null; + return (errors.length) ? handleErrors(info.fieldNodes, info.path, errors) : null; } const parentSubschemas = getSubschemasFromParent(parent); - const mergedResult = handleResult(info, result, errors, parentSubschemas); - if (info.mergeInfo) { - const typeName = getNamedType(info.returnType).name; - const initialSubschemas = info.mergeInfo.mergedTypes[typeName]; - if (initialSubschemas) { - const remainingSubschemas = info.mergeInfo.mergedTypes[typeName].filter( - subschema => !parentSubschemas.includes(subschema) - ); - if (remainingSubschemas.length) { - const additionalResults = await Promise.all(remainingSubschemas.map(subschema => { - const mergedTypeResolver = subschema.mergedTypeConfigs[typeName].mergedTypeResolver; - return mergedTypeResolver(subschema, parent, args, context, info); - })); - additionalResults.forEach((additionalResult: ExecutionResult) => { - Object.assign(result, additionalResult); - }); - } - } - } - - return mergedResult; + return handleResult(getNullableType(info.returnType), result, errors, parentSubschemas, context, info); } diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 7164bdcb166..ba4bfd92e88 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -42,6 +42,7 @@ import AddReplacementFragments from '../transforms/AddReplacementFragments'; import { ApolloLink, execute as executeLink } from 'apollo-link'; import linkToFetcher from './linkToFetcher'; import { observableToAsyncIterable } from './observableToAsyncIterable'; +import { AddMergedTypeFragments } from '../transforms'; export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, @@ -98,14 +99,15 @@ async function delegateToSchemaImplementation({ }; transforms = [ - new CheckResultAndHandleErrors(info, fieldName, subschema), + new CheckResultAndHandleErrors(info, fieldName, subschema, context), ...transforms, new ExpandAbstractTypes(info.schema, targetSchema), ]; if (info.mergeInfo) { transforms.push( - new AddReplacementFragments(targetSchema, info.mergeInfo.replacementFragments) + new AddReplacementFragments(targetSchema, info.mergeInfo.replacementFragments), + new AddMergedTypeFragments(targetSchema, info.mergeInfo.mergedTypes), ); } diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index ad895d572e9..f6258bf80c4 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -49,22 +49,22 @@ export function relocatedError( } export function createMergedResult( - object: any, - childrenErrors: ReadonlyArray = [], + result: any, + errors: ReadonlyArray = [], subschemas: Array = [], ): any { - if (object == null) { - object = { + if (result == null) { + result = { [MERGED_NULL_SYMBOL]: true, }; - } else if (typeof object !== 'object') { - return object; + } else if (typeof result !== 'object') { + return result; } - if (Array.isArray(object)) { + if (Array.isArray(result)) { const byIndex = {}; - childrenErrors.forEach(error => { + errors.forEach((error: GraphQLError) => { if (!error.path) { return; } @@ -80,12 +80,10 @@ export function createMergedResult( byIndex[index] = current; }); - object = object.map((item, index) => createMergedResult(item, byIndex[index]), subschemas); - - return object; + return result.map((item, index) => createMergedResult(item, byIndex[index], subschemas)); } - object[ERROR_SYMBOL] = childrenErrors.map(error => { + result[ERROR_SYMBOL] = errors.map(error => { const newError = relocatedError( error, error.nodes, @@ -93,9 +91,9 @@ export function createMergedResult( ); return newError; }); - object[SUBSCHEMAS_SYMBOL] = subschemas; + result[SUBSCHEMAS_SYMBOL] = subschemas; - return object; + return result; } export function isParentProxiedResult(parent: any) { diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 59af15335d6..41c05817e96 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -92,7 +92,7 @@ export function createResolver(fetcher: Fetcher): GraphQLFieldResolver variables: info.variableValues, context: { graphqlContext: context } }); - return checkResultAndHandleErrors(result, info); + return checkResultAndHandleErrors(result, context, info); }; } @@ -115,7 +115,7 @@ function createSubscriptionResolver(link: ApolloLink): ResolverFn { const observable = execute(link, operation); const originalAsyncIterator = observableToAsyncIterable(observable); return mapAsyncIterator(originalAsyncIterator, result => ({ - [info.fieldName]: checkResultAndHandleErrors(result, info), + [info.fieldName]: checkResultAndHandleErrors(result, context, info), })); }; } diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 58ad8344b4f..c09980ee8f8 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -20,8 +20,8 @@ import { SchemaLikeObject, IResolvers, SubschemaConfig, - ReplacementFragmentMapping, IGraphQLToolsResolveInfo, + MergedTypeMapping, } from '../Interfaces'; import { extractExtensionDefinitions, @@ -63,7 +63,7 @@ export default function mergeSchemas({ schemas: schemaLikeObjects = [], mergeTypes = [], onTypeConflict, - resolvers, + resolvers = {}, schemaDirectives, inheritResolversFromInterfaces, mergeDirectives, @@ -84,10 +84,7 @@ export default function mergeSchemas({ const typeMap: { [name: string]: GraphQLNamedType } = {}; const extensions: Array = []; const directives: Array = []; - const fragments: Array<{ - field: string; - fragment: string; - }> = []; + let schemas: Array = [...subschemas]; if (typeDefs) { schemas.push(typeDefs); @@ -177,38 +174,6 @@ export default function mergeSchemas({ } }); - const mergedTypes = {}; - - mergeTypes.forEach(typeName => { - if (typeCandidates[typeName]) { - mergedTypes[typeName] = - typeCandidates[typeName].map(typeCandidate => typeCandidate.subschema); - } else { - throw new Error(`Cannot merge type '${typeName}', type not found.`); - } - }); - - const mergeInfo = createMergeInfo(allSchemas, fragments, mergedTypes); - - if (!resolvers) { - resolvers = {}; - } else if (typeof resolvers === 'function') { - console.warn( - 'Passing functions as resolver parameter is deprecated. Use `info.mergeInfo` instead.', - ); - resolvers = resolvers(mergeInfo); - } else if (Array.isArray(resolvers)) { - resolvers = resolvers.reduce((left, right) => { - if (typeof right === 'function') { - console.warn( - 'Passing functions as resolver parameter is deprecated. Use `info.mergeInfo` instead.', - ); - right = right(mergeInfo); - } - return mergeDeep(left, right); - }, {}); - } - Object.keys(typeCandidates).forEach(typeName => { if ( typeName === 'Query' || @@ -243,29 +208,31 @@ export default function mergeSchemas({ }); }); - if (!resolvers) { - resolvers = {}; - } else if (Array.isArray(resolvers)) { - resolvers = resolvers.reduce(mergeDeep, {}); - } + let mergeInfo = createMergeInfo(allSchemas, mergeTypes, typeCandidates); - Object.keys(resolvers).forEach(typeName => { - const type = resolvers[typeName]; - if (type instanceof GraphQLScalarType) { - return; - } - Object.keys(type).forEach(fieldName => { - const field = type[fieldName]; - if (field.fragment) { - fragments.push({ - field: fieldName, - fragment: field.fragment, - }); + if (typeof resolvers === 'function') { + console.warn( + 'Passing functions as resolver parameter is deprecated. Use `info.mergeInfo` instead.', + ); + resolvers = resolvers(mergeInfo) || {}; + } else if (Array.isArray(resolvers)) { + resolvers = resolvers.reduce((left, right) => { + if (typeof right === 'function') { + console.warn( + 'Passing functions as resolver parameter is deprecated. Use `info.mergeInfo` instead.', + ); + right = right(mergeInfo); } - }); - }); + return mergeDeep(left, right); + }, {}) || {}; + if (!resolvers) { + resolvers = {}; + } else if (Array.isArray(resolvers)) { + resolvers = resolvers.reduce(mergeDeep, {}); + } + } - mergeInfo.replacementFragments = parseReplacementFragments(fragments); + mergeInfo = completeMergeInfo(mergeInfo, resolvers); addResolveFunctionsToSchema({ schema: mergedSchema, @@ -304,12 +271,30 @@ export default function mergeSchemas({ function createMergeInfo( allSchemas: Array, - fragments: Array<{ - field: string; - fragment: string; - }>, - mergedTypes: Record>, + mergeTypes: Array, + typeCandidates: { [name: string]: Array }, ): MergeInfo { + const mergedTypes: MergedTypeMapping = {}; + + mergeTypes.forEach(typeName => { + if (typeCandidates[typeName]) { + const subschemaConfigs: Array = + typeCandidates[typeName] + .filter(typeCandidate => isSubschemaConfig(typeCandidate.subschema)) + .map(typeCandidate => typeCandidate.subschema as SubschemaConfig); + const inlineFragments = subschemaConfigs + .filter(subschemaConfig => subschemaConfig.mergedTypeConfigs[typeName].fragment) + .map(subschemaConfig => subschemaConfig.mergedTypeConfigs[typeName].fragment) + .map(fragment => parseFragmentToInlineFragment(fragment)); + mergedTypes[typeName] = { + fragment: concatInlineFragments(typeName, inlineFragments), + subschemas: subschemaConfigs, + }; + } else { + throw new Error(`Cannot merge type '${typeName}', type not found.`); + } + }); + return { delegate( operation: 'query' | 'mutation' | 'subscription', @@ -347,45 +332,55 @@ function createMergeInfo( transforms: options.transforms }); }, - fragments, + fragments: [], replacementFragments: undefined, mergedTypes, }; } -function parseReplacementFragments( - fragments: Array<{ - field: string; - fragment: string; - }> -): ReplacementFragmentMapping { +function completeMergeInfo( + mergeInfo: MergeInfo, + resolvers: IResolversParameter, +): MergeInfo { + Object.keys(resolvers).forEach(typeName => { + const type = resolvers[typeName]; + if (type instanceof GraphQLScalarType) { + return; + } + Object.keys(type).forEach(fieldName => { + const field = type[fieldName]; + if (field.fragment) { + mergeInfo.fragments.push({ + field: fieldName, + fragment: field.fragment, + }); + } + }); + }); + const mapping = {}; - for (const { field, fragment } of fragments) { + mergeInfo.fragments.forEach(({ field, fragment }) => { const parsedFragment = parseFragmentToInlineFragment(fragment); const actualTypeName = parsedFragment.typeCondition.name.value; mapping[actualTypeName] = mapping[actualTypeName] || {}; - - if (mapping[actualTypeName][field]) { - mapping[actualTypeName][field].push(parsedFragment); - } else { - mapping[actualTypeName][field] = [parsedFragment]; - } - } + mapping[actualTypeName][field] = mapping[actualTypeName][field] || []; + mapping[actualTypeName][field].push(parsedFragment); + }); const replacementFragments = Object.create({}); Object.keys(mapping).forEach(typeName => { Object.keys(mapping[typeName]).forEach(field => { replacementFragments[typeName] = mapping[typeName] || {}; - if (mapping[typeName][field]) { - replacementFragments[typeName][field] = concatInlineFragments( - typeName, - mapping[typeName][field], - ); - } + replacementFragments[typeName][field] = concatInlineFragments( + typeName, + mapping[typeName][field], + ); }); }); - return replacementFragments; + mergeInfo.replacementFragments = replacementFragments; + + return mergeInfo; } function guessSchemaByRootField( diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 48679c92848..6feb4d769a8 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -1474,7 +1474,7 @@ describe('mergeTypes', () => { const typeDefs1 = ` type Query { rootField1: Wrapper - getTest: Test + getTest(id: ID): Test } type Wrapper { @@ -1482,6 +1482,7 @@ describe('mergeTypes', () => { } type Test { + id: ID field1: String } `; @@ -1489,7 +1490,7 @@ describe('mergeTypes', () => { const typeDefs2 = ` type Query { rootField2: Wrapper - getTest: Test + getTest(id: ID): Test } type Wrapper { @@ -1497,6 +1498,7 @@ describe('mergeTypes', () => { } type Test { + id: ID field2: String } `; @@ -1505,11 +1507,11 @@ describe('mergeTypes', () => { typeDefs: typeDefs1, resolvers: { Query: { - rootField1: () => ({ test: {} }), - getTest: () => ({}), + rootField1: () => ({ test: { id: '1' } }), + getTest: (parent, { id }) => ({ id }), }, Test: { - field1: () => '1', + field1: parent => parent.id, } } }); @@ -1518,11 +1520,11 @@ describe('mergeTypes', () => { typeDefs: typeDefs2, resolvers: { Query: { - rootField2: () => ({ test: {} }), - getTest: () => ({}), + rootField2: () => ({ test: { id: '2' } }), + getTest: (parent, { id }) => ({ id }), }, Test: { - field2: () => '2', + field2: parent => parent.id, } } }); @@ -1534,11 +1536,13 @@ describe('mergeTypes', () => { const mergedTypeConfigs1: Record = { Test: { - mergedTypeResolver: (subschema, parent, args, context, info) => { + fragment: 'fragment TestFragment on Test { id }', + mergedTypeResolver: (subschema, originalResult, context, info) => { return delegateToSchema({ schema: subschemaConfig1, operation: 'query', fieldName: 'getTest', + args: { id: originalResult.id }, context, info, }); @@ -1548,11 +1552,13 @@ describe('mergeTypes', () => { const mergedTypeConfigs2: Record = { Test: { - mergedTypeResolver: (subschema, parent, args, context, info) => { + fragment: 'fragment TestFragment on Test { id }', + mergedTypeResolver: async (subschema, originalResult, context, info) => { return delegateToSchema({ schema: subschemaConfig2, operation: 'query', fieldName: 'getTest', + args: { id: originalResult.id }, context, info, }); @@ -1573,7 +1579,7 @@ describe('mergeTypes', () => { rootField1: { test: { field1: '1', - field2: '2', + field2: '1', } } } diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index 03598407bee..a48aa38ae9e 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -1,5 +1,5 @@ import { expect, assert } from 'chai'; -import { GraphQLResolveInfo, GraphQLError, graphql } from 'graphql'; +import { GraphQLError, graphql } from 'graphql'; import { relocatedError, getErrorsFromParent, @@ -10,6 +10,7 @@ import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErr import 'mocha'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { mergeSchemas } from '../stitching'; +import { IGraphQLToolsResolveInfo } from '../Interfaces'; class ErrorWithExtensions extends GraphQLError { constructor(message: string, code: string) { @@ -57,7 +58,7 @@ describe('Errors', () => { errors: [new GraphQLError('Test error')] }; try { - checkResultAndHandleErrors(result, {} as GraphQLResolveInfo, 'responseKey'); + checkResultAndHandleErrors(result, {}, {} as IGraphQLToolsResolveInfo, 'responseKey'); } catch (e) { assert.equal(e.message, 'Test error'); assert.isUndefined(e.originalError.errors); @@ -69,7 +70,7 @@ describe('Errors', () => { errors: [new ErrorWithExtensions('Test error', 'UNAUTHENTICATED')] }; try { - checkResultAndHandleErrors(result, {} as GraphQLResolveInfo, 'responseKey'); + checkResultAndHandleErrors(result, {}, {} as IGraphQLToolsResolveInfo, 'responseKey'); } catch (e) { assert.equal(e.message, 'Test error'); assert.equal(e.extensions && e.extensions.code, 'UNAUTHENTICATED'); @@ -85,7 +86,7 @@ describe('Errors', () => { ] }; try { - checkResultAndHandleErrors(result, {} as GraphQLResolveInfo, 'responseKey'); + checkResultAndHandleErrors(result, {}, {} as IGraphQLToolsResolveInfo, 'responseKey'); } catch (e) { assert.equal(e.message, 'Error1\nError2'); assert.isNotEmpty(e.originalError); diff --git a/src/transforms/AddMergedTypeFragments.ts b/src/transforms/AddMergedTypeFragments.ts new file mode 100644 index 00000000000..6ef732145db --- /dev/null +++ b/src/transforms/AddMergedTypeFragments.ts @@ -0,0 +1,72 @@ +import { + DocumentNode, + GraphQLSchema, + GraphQLType, + Kind, + SelectionSetNode, + TypeInfo, + visit, + visitWithTypeInfo, +} from 'graphql'; +import { Request, MergedTypeMapping } from '../Interfaces'; +import { Transform } from './transforms'; + +export default class AddMergedTypeFragments implements Transform { + private targetSchema: GraphQLSchema; + private mapping: MergedTypeMapping; + + constructor( + targetSchema: GraphQLSchema, + mapping: MergedTypeMapping, + ) { + this.targetSchema = targetSchema; + this.mapping = mapping; + } + + public transformRequest(originalRequest: Request): Request { + const document = addMergedTypeFragments( + this.targetSchema, + originalRequest.document, + this.mapping, + ); + return { + ...originalRequest, + document, + }; + } +} + +function addMergedTypeFragments( + targetSchema: GraphQLSchema, + document: DocumentNode, + mapping: MergedTypeMapping, +): DocumentNode { + const typeInfo = new TypeInfo(targetSchema); + return visit( + document, + visitWithTypeInfo(typeInfo, { + leave: { + [Kind.SELECTION_SET]( + node: SelectionSetNode, + ): SelectionSetNode | null | undefined { + const parentType: GraphQLType = typeInfo.getParentType(); + if (parentType) { + const parentTypeName = parentType.name; + let selections = node.selections; + + if (mapping[parentTypeName]) { + selections = selections.concat(mapping[parentTypeName].fragment); + } + + if (selections !== node.selections) { + return { + ...node, + selections, + }; + } + } + }, + }, + }), + ); +} diff --git a/src/transforms/AddReplacementFragments.ts b/src/transforms/AddReplacementFragments.ts index 33c27cb190c..b0c131cb101 100644 --- a/src/transforms/AddReplacementFragments.ts +++ b/src/transforms/AddReplacementFragments.ts @@ -13,21 +13,21 @@ import { Transform } from './transforms'; export default class AddReplacementFragments implements Transform { private targetSchema: GraphQLSchema; - private fragments: ReplacementFragmentMapping; + private mapping: ReplacementFragmentMapping; constructor( targetSchema: GraphQLSchema, - fragments: ReplacementFragmentMapping, + mapping: ReplacementFragmentMapping, ) { this.targetSchema = targetSchema; - this.fragments = fragments; + this.mapping = mapping; } public transformRequest(originalRequest: Request): Request { const document = replaceFieldsWithFragments( this.targetSchema, originalRequest.document, - this.fragments, + this.mapping, ); return { ...originalRequest, @@ -39,7 +39,7 @@ export default class AddReplacementFragments implements Transform { function replaceFieldsWithFragments( targetSchema: GraphQLSchema, document: DocumentNode, - fragments: ReplacementFragmentMapping, + mapping: ReplacementFragmentMapping, ): DocumentNode { const typeInfo = new TypeInfo(targetSchema); return visit( @@ -53,11 +53,11 @@ function replaceFieldsWithFragments( const parentTypeName = parentType.name; let selections = node.selections; - if (fragments[parentTypeName]) { + if (mapping[parentTypeName]) { node.selections.forEach(selection => { if (selection.kind === Kind.FIELD) { const name = selection.name.value; - const fragment = fragments[parentTypeName][name]; + const fragment = mapping[parentTypeName][name]; if (fragment) { selections = selections.concat(fragment); } diff --git a/src/transforms/CheckResultAndHandleErrors.ts b/src/transforms/CheckResultAndHandleErrors.ts index aaff1af000c..c54c2b78723 100644 --- a/src/transforms/CheckResultAndHandleErrors.ts +++ b/src/transforms/CheckResultAndHandleErrors.ts @@ -1,18 +1,21 @@ -import { GraphQLResolveInfo, GraphQLSchema } from 'graphql'; +import { GraphQLSchema } from 'graphql'; import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; import { Transform } from './transforms'; -import { SubschemaConfig } from '../Interfaces'; +import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; export default class CheckResultAndHandleErrors implements Transform { - private info: GraphQLResolveInfo; + private context?: Record; + private info: IGraphQLToolsResolveInfo; private fieldName?: string; private subschema?: GraphQLSchema | SubschemaConfig; constructor( - info: GraphQLResolveInfo, + info: IGraphQLToolsResolveInfo, fieldName?: string, - subschema?: GraphQLSchema | SubschemaConfig + subschema?: GraphQLSchema | SubschemaConfig, + context?: Record, ) { + this.context = context; this.info = info; this.fieldName = fieldName; this.subschema = subschema; @@ -21,6 +24,7 @@ export default class CheckResultAndHandleErrors implements Transform { public transformResult(result: any): any { return checkResultAndHandleErrors( result, + this.context, this.info, this.fieldName, this.subschema diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 9d562338e31..14f20c22eaf 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -7,6 +7,7 @@ export { default as transformSchema, wrapSchema } from './transformSchema'; export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErrors'; export { default as AddReplacementFragments } from './AddReplacementFragments'; +export { default as AddMergedTypeFragments } from './AddMergedTypeFragments'; export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; export { default as FilterToSchema } from './FilterToSchema'; From a4ec8822ecb839bff8ece0184109e7c2fb926d2b Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 27 Nov 2019 04:53:46 -0500 Subject: [PATCH 108/250] fix(stitching): add test RenameObjectFields and RenameRootFields should work when used together. --- src/test/testAlternateMergeSchemas.ts | 96 +++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 6feb4d769a8..9af388b5504 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -426,6 +426,102 @@ describe('transform object fields', () => { }); }); +describe('rename object fields', () => { + it('should work', async () => { + const ITEM = { + id: '123', + camel_case: "I'm a camel!", + }; + + const itemSchema = makeExecutableSchema({ + typeDefs: ` + type Item { + id: ID! + camel_case: String + } + type ItemConnection { + edges: [ItemEdge!]! + } + type ItemEdge { + node: Item! + } + type Query { + item: Item + allItems: ItemConnection! + } + `, + resolvers: { + Query: { + item: () => ITEM, + allItems: () => ({ + edges: [ + { + node: ITEM + } + ] + }) + } + } + }); + + const schema = transformSchema(itemSchema, [ + new RenameObjectFields((_typeName, fieldName) => { + if (fieldName === 'camel_case') { + return 'camelCase'; + } + return fieldName; + }), + new RenameRootFields((_operation, fieldName) => { + if (fieldName === 'allItems') { + return 'items'; + } + return fieldName; + }), + ]); + + const result = await graphql( + schema, + ` + query { + item { + id + camelCase + } + items { + edges { + node { + id + camelCase + } + } + } + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + const TRANSFORMED_ITEM = { + id: '123', + camelCase: "I'm a camel!", + }; + + expect(result).to.deep.equal({ + data: { + item: TRANSFORMED_ITEM, + items: { + edges: [{ + node: TRANSFORMED_ITEM, + }], + }, + }, + }); + }); +}); + describe('filter and rename object fields', () => { let transformedPropertySchema: GraphQLSchema; From a5a9a1a00df8abad399fbfb1728053b56d57e1e9 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 27 Nov 2019 05:07:00 -0500 Subject: [PATCH 109/250] fix(transforms): cloneSchema prior to transformSchema This allows individual schema transforms to save intermediate schema representations even if other schema transformations modify them in place. --- src/transforms/transforms.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transforms/transforms.ts b/src/transforms/transforms.ts index 1ac56354d08..a0ddf9cb854 100644 --- a/src/transforms/transforms.ts +++ b/src/transforms/transforms.ts @@ -1,5 +1,6 @@ import { GraphQLSchema } from 'graphql'; import { Request, Transform } from '../Interfaces'; +import { cloneSchema } from '../utils'; export { Transform }; @@ -9,7 +10,7 @@ export function applySchemaTransforms( ): GraphQLSchema { return transforms.reduce( (schema: GraphQLSchema, transform: Transform) => - transform.transformSchema ? transform.transformSchema(schema) : schema, + transform.transformSchema ? transform.transformSchema(cloneSchema(schema)) : schema, originalSchema, ); } From 5b1790d97294d341a8de12ec5cd1aa51f1741751 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 27 Nov 2019 05:09:40 -0500 Subject: [PATCH 110/250] refactor(TransformObjectFields) visitSchema in this fork modifies schemas in place, but this change makes code more explicit that final schema is being used as base for transformRequest. --- src/transforms/TransformObjectFields.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index 46b9e3cdd4d..cd4881758e6 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -42,7 +42,7 @@ type FieldMapping = { export default class TransformObjectFields implements Transform { private objectFieldTransformer: ObjectFieldTransformer; private fieldNodeTransformer: FieldNodeTransformer; - private schema: GraphQLSchema; + private transformedSchema: GraphQLSchema; private mapping: FieldMapping; constructor( @@ -55,8 +55,7 @@ export default class TransformObjectFields implements Transform { } public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { - this.schema = originalSchema; - return visitSchema(originalSchema, { + this.transformedSchema = visitSchema(originalSchema, { [VisitSchemaKind.ROOT_OBJECT]: () => { return undefined; }, @@ -64,6 +63,8 @@ export default class TransformObjectFields implements Transform { return this.transformFields(type, this.objectFieldTransformer); } }); + + return this.transformedSchema; } public transformRequest(originalRequest: Request): Request { @@ -144,7 +145,7 @@ export default class TransformObjectFields implements Transform { fieldNodeTransformer?: FieldNodeTransformer, fragments: Record = {}, ): DocumentNode { - const typeInfo = new TypeInfo(this.schema); + const typeInfo = new TypeInfo(this.transformedSchema); const newDocument: DocumentNode = visit( document, visitWithTypeInfo(typeInfo, { From 8d5e29fb674e0a4cb9b95d888de5aa1ad7c73dc0 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 1 Dec 2019 14:14:36 -0500 Subject: [PATCH 111/250] refactor(Object Transform tests) --- src/test/testAlternateMergeSchemas.ts | 52 +++++++++++++++++++++------ 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 9af388b5504..3cc3c1dfd07 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -23,6 +23,7 @@ import { ExtendSchema, WrapType, FilterRootFields, + FilterObjectFields, } from '../transforms'; import { propertySchema, @@ -426,8 +427,10 @@ describe('transform object fields', () => { }); }); -describe('rename object fields', () => { - it('should work', async () => { +describe.only('transform object fields', () => { + let schema: GraphQLSchema; + + before(() => { const ITEM = { id: '123', camel_case: "I'm a camel!", @@ -464,7 +467,13 @@ describe('rename object fields', () => { } }); - const schema = transformSchema(itemSchema, [ + schema = transformSchema(itemSchema, [ + new FilterObjectFields((_typeName, fieldName) => { + if (fieldName === 'id') { + return false; + } + return true; + }), new RenameObjectFields((_typeName, fieldName) => { if (fieldName === 'camel_case') { return 'camelCase'; @@ -478,34 +487,28 @@ describe('rename object fields', () => { return fieldName; }), ]); + }); + it('renaming should work', async () => { const result = await graphql( schema, ` query { item { - id camelCase } items { edges { node { - id camelCase } } } } `, - {}, - {}, - { - pid: 'p1', - }, ); const TRANSFORMED_ITEM = { - id: '123', camelCase: "I'm a camel!", }; @@ -520,6 +523,33 @@ describe('rename object fields', () => { }, }); }); + + it('filtering should work', async () => { + const result = await graphql( + schema, + ` + query { + items { + edges { + node { + id + } + } + } + } + `, + ); + + expect(result).to.deep.equal({ + errors: [{ + locations: [{ + column: 17, + line: 6, + }], + message: 'Cannot query field \"id\" on type \"Item\".', + }], + }); + }); }); describe('filter and rename object fields', () => { From aa79488eac3384f2f149824c6fe61c6efb4d01c4 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 2 Dec 2019 21:55:21 -0500 Subject: [PATCH 112/250] refactor(tests): run all tests --- src/test/testAlternateMergeSchemas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 3cc3c1dfd07..c62597b5c94 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -427,7 +427,7 @@ describe('transform object fields', () => { }); }); -describe.only('transform object fields', () => { +describe('transform object fields', () => { let schema: GraphQLSchema; before(() => { From 8dbc18887c9847e81a2ea01961422ef43c0234c3 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 2 Dec 2019 21:56:27 -0500 Subject: [PATCH 113/250] refactor(addTypenameToAbstract) Remove unnecessary search. Duplicate typename fields are also included elsewhere! --- src/stitching/addTypenameToAbstract.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/stitching/addTypenameToAbstract.ts b/src/stitching/addTypenameToAbstract.ts index 9b957f6ba0b..76783d9785b 100644 --- a/src/stitching/addTypenameToAbstract.ts +++ b/src/stitching/addTypenameToAbstract.ts @@ -6,7 +6,6 @@ import { visitWithTypeInfo, SelectionSetNode, Kind, - FieldNode, GraphQLInterfaceType, GraphQLUnionType, GraphQLSchema, @@ -28,12 +27,7 @@ export function addTypenameToAbstract( if ( parentType && (parentType instanceof GraphQLInterfaceType || - parentType instanceof GraphQLUnionType) && - !selections.find( - _ => - (_ as FieldNode).kind === Kind.FIELD && - (_ as FieldNode).name.value === '__typename', - ) + parentType instanceof GraphQLUnionType) ) { selections = selections.concat({ kind: Kind.FIELD, From 06f9d5e6da75032a4deac4bbf9561e43af46504f Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 2 Dec 2019 21:57:13 -0500 Subject: [PATCH 114/250] refactor(FilterToSchema) Use graphql TypeInfo and visitWithTypeInfo to keep track of types during traversal. --- src/transforms/FilterToSchema.ts | 56 ++++++++------------------------ 1 file changed, 13 insertions(+), 43 deletions(-) diff --git a/src/transforms/FilterToSchema.ts b/src/transforms/FilterToSchema.ts index 9c055239b55..4ac65da7aef 100644 --- a/src/transforms/FilterToSchema.ts +++ b/src/transforms/FilterToSchema.ts @@ -5,13 +5,9 @@ import { FragmentDefinitionNode, FragmentSpreadNode, GraphQLInterfaceType, - GraphQLList, - GraphQLNamedType, - GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLType, - GraphQLUnionType, InlineFragmentNode, Kind, OperationDefinitionNode, @@ -20,6 +16,9 @@ import { VariableDefinitionNode, VariableNode, visit, + TypeInfo, + visitWithTypeInfo, + getNamedType, } from 'graphql'; import { Request } from '../Interfaces'; import implementsAbstractType from '../utils/implementsAbstractType'; @@ -199,15 +198,12 @@ function filterSelectionSet( ) { const usedFragments: Array = []; const usedVariables: Array = []; - const typeStack: Array = [type]; - // Should be rewritten using visitWithSchema - const filteredSelectionSet = visit(selectionSet, { + const typeInfo = new TypeInfo(schema, undefined, type); + const filteredSelectionSet = visit(selectionSet, visitWithTypeInfo(typeInfo, { [Kind.FIELD]: { enter(node: FieldNode): null | undefined | FieldNode { - let parentType: GraphQLNamedType = resolveType( - typeStack[typeStack.length - 1], - ); + const parentType = typeInfo.getParentType(); if ( parentType instanceof GraphQLObjectType || parentType instanceof GraphQLInterfaceType @@ -219,8 +215,6 @@ function filterSelectionSet( : fields[node.name.value]; if (!field) { return null; - } else { - typeStack.push(field.type); } const argNames = (field.args || []).map(arg => arg.name); @@ -235,16 +229,10 @@ function filterSelectionSet( }; } } - } else if ( - parentType instanceof GraphQLUnionType && - node.name.value === '__typename' - ) { - typeStack.push(TypeNameMetaFieldDef.type); } }, leave(node: FieldNode): null | undefined | FieldNode { - const currentType = typeStack.pop(); - const resolvedType = resolveType(currentType); + const resolvedType = getNamedType(typeInfo.getType()); if ( resolvedType instanceof GraphQLObjectType || resolvedType instanceof GraphQLInterfaceType @@ -268,9 +256,7 @@ function filterSelectionSet( }, [Kind.FRAGMENT_SPREAD](node: FragmentSpreadNode): null | undefined { if (node.name.value in validFragments) { - const parentType: GraphQLNamedType = resolveType( - typeStack[typeStack.length - 1], - ); + const parentType = typeInfo.getParentType(); const innerType = validFragments[node.name.value]; if (!implementsAbstractType(schema, parentType, innerType)) { return null; @@ -285,25 +271,20 @@ function filterSelectionSet( [Kind.INLINE_FRAGMENT]: { enter(node: InlineFragmentNode): null | undefined { if (node.typeCondition) { + const parentType = typeInfo.getParentType(); const innerType = schema.getType(node.typeCondition.name.value); - const parentType: GraphQLNamedType = resolveType( - typeStack[typeStack.length - 1], - ); - if (implementsAbstractType(schema, parentType, innerType)) { - typeStack.push(innerType); - } else { + if (!implementsAbstractType(schema, parentType, innerType)) { return null; + } else { + return; } } }, - leave(node: InlineFragmentNode) { - typeStack.pop(); - }, }, [Kind.VARIABLE](node: VariableNode) { usedVariables.push(node.name.value); }, - }); + })); return { selectionSet: filteredSelectionSet, @@ -312,17 +293,6 @@ function filterSelectionSet( }; } -function resolveType(type: GraphQLType): GraphQLNamedType { - let lastType = type; - while ( - lastType instanceof GraphQLNonNull || - lastType instanceof GraphQLList - ) { - lastType = lastType.ofType; - } - return lastType; -} - function union(...arrays: Array>): Array { const cache: { [key: string]: boolean } = {}; const result: Array = []; From ca8382ab5f4fe7cbd26cbe97c967ffcdb53d9592 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 4 Dec 2019 22:03:23 -0500 Subject: [PATCH 115/250] lint(types): lint type definition. --- src/stitching/createMergedResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 5e64921ed75..8f91abf9f58 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -25,8 +25,8 @@ export function createMergedResolver({ fromPath = [], toPath = [], }: { - toPath?: Array; fromPath?: Array; + toPath?: Array; }): IFieldResolver { return async (parent, args, context, info) => { From ba609d25a27521d667bb672c4d56b3c02c40e5bb Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 4 Dec 2019 22:25:24 -0500 Subject: [PATCH 116/250] refactor(fieldNodes) Move renameFieldNode, wrapFieldNode, and collectFields into utils. --- src/stitching/extractFields.ts | 2 +- src/stitching/index.ts | 2 -- src/test/testAlternateMergeSchemas.ts | 32 +------------------ .../collectFields.ts => utils/fieldNodes.ts} | 32 ++++++++++++++++++- src/utils/index.ts | 5 +++ 5 files changed, 38 insertions(+), 35 deletions(-) rename src/{stitching/collectFields.ts => utils/fieldNodes.ts} (63%) diff --git a/src/stitching/extractFields.ts b/src/stitching/extractFields.ts index c70443bc715..c34f9e5b328 100644 --- a/src/stitching/extractFields.ts +++ b/src/stitching/extractFields.ts @@ -1,5 +1,5 @@ import { FieldNode, FragmentDefinitionNode } from 'graphql'; -import { collectFields } from './collectFields'; +import { collectFields } from '../utils'; export function extractFields({ fieldNode, diff --git a/src/stitching/index.ts b/src/stitching/index.ts index 1adfe996eac..9e685e4d7b4 100644 --- a/src/stitching/index.ts +++ b/src/stitching/index.ts @@ -5,7 +5,6 @@ import delegateToSchema from './delegateToSchema'; import defaultMergedResolver from './defaultMergedResolver'; import { wrapField, extractField, renameField, createMergedResolver } from './createMergedResolver'; import { extractFields } from './extractFields'; -import { collectFields } from './collectFields'; export { @@ -19,7 +18,6 @@ export { defaultCreateRemoteResolver, defaultMergedResolver, createMergedResolver, - collectFields, extractFields, // TBD: deprecate in favor of createMergedResolver? diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index c62597b5c94..a3e98d021cb 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -11,7 +11,6 @@ import { GraphQLScalarType, FieldNode, printSchema, - Kind, } from 'graphql'; import { transformSchema, @@ -46,36 +45,7 @@ import { } from '../stitching'; import { SubschemaConfig, MergedTypeConfig } from '../Interfaces'; import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; - -function renameFieldNode(fieldNode: FieldNode, name: string): FieldNode { - return { - ...fieldNode, - name: { - ...fieldNode.name, - value: name, - } - }; -} - -function wrapFieldNode(fieldNode: FieldNode, path: Array): FieldNode { - let newFieldNode = fieldNode; - path.forEach(fieldName => { - newFieldNode = { - kind: Kind.FIELD, - name: { - kind: Kind.NAME, - value: fieldName, - }, - selectionSet: { - kind: Kind.SELECTION_SET, - selections: [ - fieldNode, - ] - } - }; - }); - return newFieldNode; -} +import { wrapFieldNode, renameFieldNode } from '../utils/fieldNodes'; let linkSchema = ` """ diff --git a/src/stitching/collectFields.ts b/src/utils/fieldNodes.ts similarity index 63% rename from src/stitching/collectFields.ts rename to src/utils/fieldNodes.ts index 9a6bd7fe584..c2846273e60 100644 --- a/src/stitching/collectFields.ts +++ b/src/utils/fieldNodes.ts @@ -1,10 +1,40 @@ import { FieldNode, - FragmentDefinitionNode, Kind, + FragmentDefinitionNode, SelectionSetNode, } from 'graphql'; +export function renameFieldNode(fieldNode: FieldNode, name: string): FieldNode { + return { + ...fieldNode, + name: { + ...fieldNode.name, + value: name, + } + }; +} + +export function wrapFieldNode(fieldNode: FieldNode, path: Array): FieldNode { + let newFieldNode = fieldNode; + path.forEach(fieldName => { + newFieldNode = { + kind: Kind.FIELD, + name: { + kind: Kind.NAME, + value: fieldName, + }, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [ + fieldNode, + ] + } + }; + }); + return newFieldNode; +} + export function collectFields( selectionSet: SelectionSetNode, fragments: Record, diff --git a/src/utils/index.ts b/src/utils/index.ts index 53feba20d77..486df347611 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -14,3 +14,8 @@ export { } from './transformInputValue'; export { concatInlineFragments, parseFragmentToInlineFragment } from './fragments'; export { mergeDeep } from './mergeDeep'; +export { + collectFields, + wrapFieldNode, + renameFieldNode, +} from './fieldNodes'; From cf12fdf3eaac1a9a443396fdad9c4340236924a9 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 9 Dec 2019 13:29:39 -0500 Subject: [PATCH 117/250] fix(stitching): improve error proxying for lists required significant refactoring of error handling --- src/Interfaces.ts | 5 - src/stitching/checkResultAndHandleErrors.ts | 161 +++++++++++++------- src/stitching/createMergedResolver.ts | 23 ++- src/stitching/defaultMergedResolver.ts | 16 +- src/stitching/errors.ts | 105 +++---------- src/stitching/proxiedResult.ts | 55 +++++++ src/test/testErrors.ts | 120 +++++++++++++-- 7 files changed, 307 insertions(+), 178 deletions(-) create mode 100644 src/stitching/proxiedResult.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 3843ec440d5..18240280c21 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -336,8 +336,3 @@ export type TypeVisitor = ( type: GraphQLType, schema: GraphQLSchema, ) => GraphQLNamedType | null | undefined; - -export type Path = { - prev: Path; - key: string | number; -}; diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 1af39f6fcc5..79128d95e03 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -8,6 +8,7 @@ import { ExecutionResult, GraphQLCompositeType, GraphQLError, + GraphQLList, GraphQLType, GraphQLSchema, FieldNode, @@ -17,14 +18,14 @@ import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; import { relocatedError, combineErrors, - createMergedResult + getErrorsByPathSegment, } from './errors'; import { SubschemaConfig, IGraphQLToolsResolveInfo, - Path, } from '../Interfaces'; import resolveFromParentTypename from './resolveFromParentTypename'; +import { setErrors, setSubschemas } from './proxiedResult'; export function checkResultAndHandleErrors( result: ExecutionResult, @@ -37,82 +38,114 @@ export function checkResultAndHandleErrors( responseKey = getResponseKeyFromInfo(info); } - if (!result.data || result.data[responseKey] == null) { - return (result.errors) ? handleErrors(info.fieldNodes, info.path, result.errors) : null; - } + const errors = result.errors || []; + const data = result.data && result.data[responseKey]; + const subschemas = [subschema]; - return handleResult( - getNullableType(info.returnType), - result.data[responseKey], - result.errors || [], - [subschema], - context, - info, - ); + return handleResult(data, errors, subschemas, context, info); } export function handleResult( - type: GraphQLType, result: any, errors: ReadonlyArray, subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, ): any { + const type = getNullableType(info.returnType); + + if (result == null) { + return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors); + } + if (isLeafType(type)) { return type.parseValue(result); } else if (isCompositeType(type)) { - return mergeResultsFromOtherSubschemas( + const object = handleObject(result, errors, subschemas); + return mergeFields( type, - createMergedResult(result, errors, subschemas), + object, subschemas, context, info, ); } else if (isListType(type)) { - return createMergedResult(result, errors, subschemas).map( - (r: any) => handleListResult( - getNullableType(type.ofType), - r, - subschemas, - context, - info, - ) - ); + return handleList(type, result, errors, subschemas, info); } } -function handleListResult( +export function handleObject ( + object: any, + errors: ReadonlyArray, + subschemas: Array, +) { + setErrors(object, errors.map(error => { + return relocatedError( + error, + error.nodes, + error.path ? error.path.slice(1) : undefined + ); + })); + setSubschemas(object, subschemas); + + return object; +} + +function handleList( + type: GraphQLList, + list: Array, + errors: ReadonlyArray, + subschemas: Array, + info: IGraphQLToolsResolveInfo, +) { + + const childErrors = getErrorsByPathSegment(errors); + + list = list.map((listMember, index) => handleListMember( + getNullableType(type.ofType), + listMember, + index, + childErrors[index] || [], + subschemas, + context, + info, + )); + + return list; +} + +function handleListMember( type: GraphQLType, - result: any, + listMember: any, + index: number, + errors: ReadonlyArray, subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, -) { +): any { + if (listMember == null) { + return handleNull(info.fieldNodes, [...responsePathAsArray(info.path), index], errors); + } + if (isLeafType(type)) { - return type.parseValue(result); + return type.parseValue(listMember); } else if (isCompositeType(type)) { - return mergeResultsFromOtherSubschemas( + const object = handleObject(listMember, errors, subschemas); + return mergeFields( type, - result, + object, subschemas, context, info ); } else if (isListType(type)) { - return result.map((r: any) => handleListResult( - getNullableType(type.ofType), - r, - subschemas, - context, - info, - )); + return handleList(type, listMember, errors, subschemas, info); } } -async function mergeResultsFromOtherSubschemas( +async function mergeFields( type: GraphQLCompositeType, - result: any, + object: any, subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, @@ -120,7 +153,7 @@ async function mergeResultsFromOtherSubschemas( if (info.mergeInfo) { let typeName: string; if (isAbstractType(type)) { - typeName = info.schema.getTypeMap()[resolveFromParentTypename(result)].name; + typeName = info.schema.getTypeMap()[resolveFromParentTypename(object)].name; } else { typeName = type.name; } @@ -135,7 +168,7 @@ async function mergeResultsFromOtherSubschemas( if (remainingSubschemas.length) { const results = await Promise.all(remainingSubschemas.map(subschema => { const mergedTypeResolver = subschema.mergedTypeConfigs[typeName].mergedTypeResolver; - return mergedTypeResolver(subschema, result, context, { + return mergedTypeResolver(subschema, object, context, { ...info, mergeInfo: { ...info.mergeInfo, @@ -143,22 +176,48 @@ async function mergeResultsFromOtherSubschemas( }, }); })); - results.forEach((r: ExecutionResult) => Object.assign(result, r)); + results.forEach((r: ExecutionResult) => Object.assign(object, r)); } } } - return result; + return object; } -export function handleErrors( +export function handleNull( fieldNodes: ReadonlyArray, - path: Path, + path: Array, errors: ReadonlyArray, ) { - throw relocatedError( - combineErrors(errors), - fieldNodes, - responsePathAsArray(path) - ); + 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; + + } else { + const childErrors = getErrorsByPathSegment(errors); + + const result = new Array; + Object.keys(childErrors).forEach(pathSegment => { + result.push(handleNull(fieldNodes, [...path, parseInt(pathSegment, 10)], childErrors[pathSegment])); + }); + + return result; + } + } else { + return null; + } } diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 8f91abf9f58..72d437b58f1 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -1,13 +1,12 @@ -import { GraphQLObjectType, getNamedType } from 'graphql'; +import { GraphQLObjectType, getNamedType, responsePathAsArray } from 'graphql'; import { IFieldResolver } from '../Interfaces'; import { - getErrorsFromParent, - createMergedResult, - getSubschemasFromParent, -} from './errors'; + getErrors, + getSubschemas, +} from './proxiedResult'; import defaultMergedResolver from './defaultMergedResolver'; import { extractOneLevelOfFields } from './extractFields'; -import { handleErrors } from './checkResultAndHandleErrors'; +import { handleNull, handleObject } from './checkResultAndHandleErrors'; export function wrapField(wrapper: string, fieldName: string): IFieldResolver { return createMergedResolver({ fromPath: [wrapper, fieldName] }); @@ -54,17 +53,13 @@ export function createMergedResolver({ for (let i = 0; i < fromParentPathLength; i++) { const responseKey = fromPath[i]; - const errors = getErrorsFromParent(parent, responseKey); - const subschemas = getSubschemasFromParent(parent); + const errors = getErrors(parent, responseKey); + const subschemas = getSubschemas(parent); const result = parent[responseKey]; if (result == null) { - if (errors.length) { - handleErrors(fieldNodes, path, errors); - } else { - return null; - } + return handleNull(fieldNodes, responsePathAsArray(path), errors); } - parent = createMergedResult(result, errors, subschemas); + parent = handleObject(result, errors, subschemas); } fieldName = fromPath[fromPathLength - 1]; diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index 53a06d82862..bec11fd249b 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,6 +1,6 @@ -import { defaultFieldResolver, getNullableType } from 'graphql'; -import { getErrorsFromParent, getSubschemasFromParent, MERGED_NULL_SYMBOL } from './errors'; -import { handleResult, handleErrors } from './checkResultAndHandleErrors'; +import { defaultFieldResolver } from 'graphql'; +import { getErrors, getSubschemas } from './proxiedResult'; +import { handleResult } from './checkResultAndHandleErrors'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; @@ -19,7 +19,7 @@ export default function defaultMergedResolver( } const responseKey = getResponseKeyFromInfo(info); - const errors = getErrorsFromParent(parent, responseKey); + const errors = getErrors(parent, responseKey); // check to see if parent is not a proxied result, i.e. if parent resolver was manually overwritten // See https://github.com/apollographql/graphql-tools/issues/967 @@ -28,11 +28,7 @@ export default function defaultMergedResolver( } const result = parent[responseKey]; + const subschemas = getSubschemas(parent); - if (result == null || result[MERGED_NULL_SYMBOL]) { - return (errors.length) ? handleErrors(info.fieldNodes, info.path, errors) : null; - } - - const parentSubschemas = getSubschemasFromParent(parent); - return handleResult(getNullableType(info.returnType), result, errors, parentSubschemas, context, info); + return handleResult(result, errors, subschemas, context, info); } diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index f6258bf80c4..51bf72c06b8 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -1,25 +1,7 @@ import { GraphQLError, ASTNode, - GraphQLSchema } from 'graphql'; -import { SubschemaConfig } from '../Interfaces'; - -export let MERGED_NULL_SYMBOL: any; -export let SUBSCHEMAS_SYMBOL: any; -export let ERROR_SYMBOL: any; -if ( - (typeof global !== 'undefined' && 'Symbol' in global) || - (typeof window !== 'undefined' && 'Symbol' in window) -) { - MERGED_NULL_SYMBOL = Symbol('mergedNull'); - SUBSCHEMAS_SYMBOL = Symbol('subschemas'); - ERROR_SYMBOL = Symbol('subschemaErrors'); -} else { - MERGED_NULL_SYMBOL = '@@__mergedNull'; - SUBSCHEMAS_SYMBOL = Symbol('subschemas'); - ERROR_SYMBOL = '@@__subschemaErrors'; -} export function relocatedError( originalError: Error | GraphQLError, @@ -48,81 +30,30 @@ export function relocatedError( ); } -export function createMergedResult( - result: any, - errors: ReadonlyArray = [], - subschemas: Array = [], -): any { - if (result == null) { - result = { - [MERGED_NULL_SYMBOL]: true, - }; - } else if (typeof result !== 'object') { - return result; - } - - if (Array.isArray(result)) { - const byIndex = {}; - - errors.forEach((error: GraphQLError) => { - if (!error.path) { - return; - } - const index = error.path[1]; - const current = byIndex[index] || []; - current.push( - relocatedError( - error, - error.nodes, - error.path ? error.path.slice(1) : undefined - ) - ); - byIndex[index] = current; - }); - - return result.map((item, index) => createMergedResult(item, byIndex[index], subschemas)); - } - - result[ERROR_SYMBOL] = errors.map(error => { - const newError = relocatedError( - error, - error.nodes, - error.path ? error.path.slice(1) : undefined - ); - return newError; - }); - result[SUBSCHEMAS_SYMBOL] = subschemas; - - return result; -} - -export function isParentProxiedResult(parent: any) { - return parent && parent[ERROR_SYMBOL]; -} - -export function getSubschemasFromParent(object: any): Array { - return object && object[SUBSCHEMAS_SYMBOL]; +export function slicedError(originalError: GraphQLError) { + return relocatedError( + originalError, + originalError.nodes, + originalError.path ? originalError.path.slice(1) : undefined + ); } -export function getErrorsFromParent( - object: any, - fieldName: string -): Array { - const errors = object && object[ERROR_SYMBOL]; - if (!Array.isArray(errors)) { - return null; - } +export function getErrorsByPathSegment(errors: ReadonlyArray): Record> { + const record = Object.create(null); + errors.forEach(error => { + if (!error.path || error.path.length < 2) { + return; + } - const childrenErrors = []; + const pathSegment = error.path[1]; - for (const error of errors) { - if (!error.path || error.path[0] === fieldName) { - childrenErrors.push(error); - } - } + const current = record[pathSegment] || []; + current.push(slicedError(error)); + record[pathSegment] = current; + }); - return childrenErrors; + return record; } class CombinedError extends Error { diff --git a/src/stitching/proxiedResult.ts b/src/stitching/proxiedResult.ts new file mode 100644 index 00000000000..0e85f5b4b4c --- /dev/null +++ b/src/stitching/proxiedResult.ts @@ -0,0 +1,55 @@ +import { + GraphQLError, + GraphQLSchema, +} from 'graphql'; +import { SubschemaConfig } from '../Interfaces'; + +export let SUBSCHEMAS_SYMBOL: any; +export let ERROR_SYMBOL: any; +if ( + (typeof global !== 'undefined' && 'Symbol' in global) || + (typeof window !== 'undefined' && 'Symbol' in window) +) { + SUBSCHEMAS_SYMBOL = Symbol('subschemas'); + ERROR_SYMBOL = Symbol('subschemaErrors'); +} else { + SUBSCHEMAS_SYMBOL = Symbol('subschemas'); + ERROR_SYMBOL = '@@__subschemaErrors'; +} + +export function isProxiedResult(result: any) { + return result && result[ERROR_SYMBOL]; +} + +export function getSubschemas(result: any): Array { + return result && result[SUBSCHEMAS_SYMBOL]; +} + +export function setSubschemas(result: any, subschemas: Array) { + result[SUBSCHEMAS_SYMBOL] = subschemas; +} + +export function setErrors(result: any, errors: Array) { + result[ERROR_SYMBOL] = errors; +} + +export function getErrors( + result: any, + pathSegment: string +): Array { + const errors = result && result[ERROR_SYMBOL]; + + if (!Array.isArray(errors)) { + return null; + } + + const fieldErrors = []; + + for (const error of errors) { + if (!error.path || error.path[0] === pathSegment) { + fieldErrors.push(error); + } + } + + return fieldErrors; +} diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index a48aa38ae9e..e38707bc4e2 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -1,10 +1,7 @@ import { expect, assert } from 'chai'; import { GraphQLError, graphql } from 'graphql'; -import { - relocatedError, - getErrorsFromParent, - ERROR_SYMBOL -} from '../stitching/errors'; +import { relocatedError } from '../stitching/errors'; +import { getErrors, ERROR_SYMBOL } from '../stitching/proxiedResult'; import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; import 'mocha'; @@ -35,7 +32,7 @@ describe('Errors', () => { }); }); - describe('getErrorsFromParent', () => { + describe('getErrors', () => { it('should return all errors including if path is not defined', () => { const mockErrors = { responseKey: '', @@ -46,7 +43,7 @@ describe('Errors', () => { ] }; - assert.deepEqual(getErrorsFromParent(mockErrors, 'responseKey'), + assert.deepEqual(getErrors(mockErrors, 'responseKey'), [mockErrors[ERROR_SYMBOL][0]] ); }); @@ -119,7 +116,7 @@ describe('passes along errors for missing fields on list', () => { resolvers: { Query: { getOuter: () => ({ - innerList: [{}] + innerList: [{ mandatoryField: 'test'}, {}] }) }, } @@ -141,6 +138,9 @@ describe('passes along errors for missing fields on list', () => { message: 'Cannot return null for non-nullable field Inner.mandatoryField.', path: [ 'getOuter', + 'innerList', + 1, + 'mandatoryField', ], }] }); @@ -164,7 +164,7 @@ describe('passes along errors for missing fields on list', () => { resolvers: { Query: { getOuter: () => ({ - innerList: [{}] + innerList: [{ mandatoryField: 'test' }, {}] }) }, } @@ -177,7 +177,7 @@ describe('passes along errors for missing fields on list', () => { expect(result).to.deep.equal({ data: { getOuter: { - innerList: [null], + innerList: [{ mandatoryField: 'test'}, null], }, }, errors: [{ @@ -189,10 +189,108 @@ describe('passes along errors for missing fields on list', () => { path: [ 'getOuter', 'innerList', - 0, + 1, 'mandatoryField', ], }] }); }); }); + +describe('passes along errors when list field errors', () => { + it('if non-null', async () => { + const typeDefs = ` + type Query { + getOuter: Outer + } + type Outer { + innerList: [Inner!]! + } + type Inner { + mandatoryField: String! + } + `; + + const schema = makeExecutableSchema({ + typeDefs, + resolvers: { + Query: { + getOuter: () => ({ + innerList: [{ mandatoryField: 'test' }, new Error('test')], + }), + }, + } + }); + + const mergedSchema = mergeSchemas({ + schemas: [schema] + }); + const result = await graphql(mergedSchema, `{ getOuter { innerList { mandatoryField } } }`); + expect(result).to.deep.equal({ + data: { + getOuter: null, + }, + errors: [{ + locations: [{ + column: 14, + line: 1, + }], + message: 'test', + path: [ + 'getOuter', + 'innerList', + 1, + ], + }] + }); + }); + + it('even if nullable', async () => { + const typeDefs = ` + type Query { + getOuter: Outer + } + type Outer { + innerList: [Inner]! + } + type Inner { + mandatoryField: String! + } + `; + + const schema = makeExecutableSchema({ + typeDefs, + resolvers: { + Query: { + getOuter: () => ({ + innerList: [{ mandatoryField: 'test' }, new Error('test')], + }), + }, + } + }); + + const mergedSchema = mergeSchemas({ + schemas: [schema] + }); + const result = await graphql(mergedSchema, `{ getOuter { innerList { mandatoryField } } }`); + expect(result).to.deep.equal({ + data: { + getOuter: { + innerList: [{ mandatoryField: 'test'}, null], + }, + }, + errors: [{ + locations: [{ + column: 14, + line: 1, + }], + message: 'test', + path: [ + 'getOuter', + 'innerList', + 1, + ], + }] + }); + }); +}); From db10787ee3b53ed86d0f1807cfc160d42226ec7b Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 9 Dec 2019 13:42:50 -0500 Subject: [PATCH 118/250] fix(WrapType): fix root type edge case Allow wrapping root types even when a root type value is not defined for the query. --- src/test/testAlternateMergeSchemas.ts | 4 ++-- src/transforms/WrapType.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index a3e98d021cb..9cb29269063 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -700,8 +700,8 @@ type UnionImpl { } } `, - {}, - {}, + undefined, + undefined, { pid: 'p1', }, diff --git a/src/transforms/WrapType.ts b/src/transforms/WrapType.ts index 3fa8ce945b6..3a8488659b9 100644 --- a/src/transforms/WrapType.ts +++ b/src/transforms/WrapType.ts @@ -24,7 +24,7 @@ export default class WrapType implements Transform { this.transformer = new ExtendSchema({ resolvers: { [outerTypeName]: { - [fieldName]: parent => parent, + [fieldName]: parent => parent ? parent : {}, }, }, fieldNodeTransformerMap: { From 378e8b5bd5c1ce6a4b65c7b04a43d3e57a7eb508 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Dec 2019 06:17:52 -0500 Subject: [PATCH 119/250] fix(handleList): missing context linter thought it referred to mocha context... --- src/stitching/checkResultAndHandleErrors.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 79128d95e03..8af506957a5 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -70,7 +70,7 @@ export function handleResult( info, ); } else if (isListType(type)) { - return handleList(type, result, errors, subschemas, info); + return handleList(type, result, errors, subschemas, context, info); } } @@ -96,6 +96,7 @@ function handleList( list: Array, errors: ReadonlyArray, subschemas: Array, + context: Record, info: IGraphQLToolsResolveInfo, ) { @@ -139,7 +140,7 @@ function handleListMember( info ); } else if (isListType(type)) { - return handleList(type, listMember, errors, subschemas, info); + return handleList(type, listMember, errors, subschemas, context, info); } } From 332c758540fb927f62206f61738ecf250d66d45b Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 14 Dec 2019 23:44:02 -0500 Subject: [PATCH 120/250] fix(stitching): wrapping and hoisting field transforms BREAKING CHANGE: Previous version of createMergedResolver did not work with multiple layers of field wrapping. extractFields was not working, deprecated in favor of hoistFieldNodes. createMergedResolver now relies on two helper functions, dehoistResult, complement to hoistFieldNoes, and unwrapResult, complement to wrapFieldNodes. --- src/stitching/createMergedResolver.ts | 86 ++++----------- src/stitching/extractFields.ts | 34 ------ src/stitching/index.ts | 14 +-- src/stitching/proxiedResult.ts | 58 ++++++++++- src/test/testAlternateMergeSchemas.ts | 144 +++++++------------------- src/transforms/ExtendSchema.ts | 3 +- src/transforms/WrapType.ts | 5 +- src/utils/fieldNodes.ts | 54 ++++++++++ src/utils/index.ts | 1 + 9 files changed, 180 insertions(+), 219 deletions(-) delete mode 100644 src/stitching/extractFields.ts diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 72d437b58f1..68d4793d36c 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -1,81 +1,35 @@ -import { GraphQLObjectType, getNamedType, responsePathAsArray } from 'graphql'; import { IFieldResolver } from '../Interfaces'; -import { - getErrors, - getSubschemas, -} from './proxiedResult'; +import { unwrapResult, dehoistResult } from './proxiedResult'; import defaultMergedResolver from './defaultMergedResolver'; -import { extractOneLevelOfFields } from './extractFields'; -import { handleNull, handleObject } from './checkResultAndHandleErrors'; - -export function wrapField(wrapper: string, fieldName: string): IFieldResolver { - return createMergedResolver({ fromPath: [wrapper, fieldName] }); -} - -export function extractField(fieldName: string): IFieldResolver { - return createMergedResolver({ toPath: [fieldName] }); -} - -export function renameField(fieldName: string): IFieldResolver { - return createMergedResolver({ fromPath: [fieldName] }); -} export function createMergedResolver({ - fromPath = [], - toPath = [], + fromPath, + fromField, + dehoist, }: { fromPath?: Array; - toPath?: Array; + fromField?: string; + dehoist?: string; }): IFieldResolver { - return async (parent, args, context, info) => { - - let fieldNodes = info.fieldNodes; - let returnType = info.returnType; - let parentType = info.parentType; - let path = info.path; - - toPath.forEach(pathSegment => { - fieldNodes = extractOneLevelOfFields(fieldNodes, pathSegment, info.fragments); - parentType = getNamedType(returnType) as GraphQLObjectType; - returnType = (parentType as GraphQLObjectType).getFields()[pathSegment].type; - path = { prev: path, key: pathSegment }; - }); - - if (!fieldNodes.length) { - return null; + return (parent, args, context, info) => { + if (dehoist) { + parent = dehoistResult(parent, dehoist); } - let fieldName; - - const fromPathLength = fromPath.length; - if (fromPathLength) { - const fromParentPathLength = fromPathLength - 1; - - for (let i = 0; i < fromParentPathLength; i++) { - const responseKey = fromPath[i]; - const errors = getErrors(parent, responseKey); - const subschemas = getSubschemas(parent); - const result = parent[responseKey]; - if (result == null) { - return handleNull(fieldNodes, responsePathAsArray(path), errors); - } - parent = handleObject(result, errors, subschemas); - } - - fieldName = fromPath[fromPathLength - 1]; + if (fromPath) { + parent = unwrapResult(parent, info, fromPath); } - if (!fieldName) { - fieldName = toPath[toPath.length - 1]; + if (!parent) { + parent = {}; } - return defaultMergedResolver(parent, args, context, { - ...info, - fieldName, - fieldNodes, - returnType, - parentType, - path, - }); + return parent instanceof Error ? + parent : fromField ? + defaultMergedResolver(parent, args, context, { + ...info, + fieldName: fromField, + }) : + defaultMergedResolver(parent, args, context, info); }; } diff --git a/src/stitching/extractFields.ts b/src/stitching/extractFields.ts deleted file mode 100644 index c34f9e5b328..00000000000 --- a/src/stitching/extractFields.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { FieldNode, FragmentDefinitionNode } from 'graphql'; -import { collectFields } from '../utils'; - -export function extractFields({ - fieldNode, - path = [], - fragments = {}, -}: { - fieldNode: FieldNode; - path?: Array; - fragments?: Record; -}) { - const fieldNodes = collectFields(fieldNode.selectionSet, fragments); - return path.length ? path.reduce( - (acc, pathSegment) => extractOneLevelOfFields(acc, pathSegment, fragments), - fieldNodes, - ) : fieldNodes; -} - -export function extractOneLevelOfFields( - fieldNodes: ReadonlyArray, - fieldName: string, - fragments: Record, -) { - const newFieldNodes: Array = []; - fieldNodes.forEach(fieldNode => { - collectFields(fieldNode.selectionSet, fragments).forEach(selection => { - if (selection.name.value === fieldName) { - newFieldNodes.push(selection); - } - }); - }); - return newFieldNodes; -} diff --git a/src/stitching/index.ts b/src/stitching/index.ts index 9e685e4d7b4..66e9851258a 100644 --- a/src/stitching/index.ts +++ b/src/stitching/index.ts @@ -3,9 +3,8 @@ import introspectSchema from './introspectSchema'; import mergeSchemas from './mergeSchemas'; import delegateToSchema from './delegateToSchema'; import defaultMergedResolver from './defaultMergedResolver'; -import { wrapField, extractField, renameField, createMergedResolver } from './createMergedResolver'; -import { extractFields } from './extractFields'; - +import { createMergedResolver } from './createMergedResolver'; +import { dehoistResult, unwrapResult } from './proxiedResult'; export { makeRemoteExecutableSchema, @@ -18,11 +17,6 @@ export { defaultCreateRemoteResolver, defaultMergedResolver, createMergedResolver, - extractFields, - - // TBD: deprecate in favor of createMergedResolver? - // OR: fix naming to clarify that these functions return resolvers? - wrapField, - extractField, - renameField, + dehoistResult, + unwrapResult, }; diff --git a/src/stitching/proxiedResult.ts b/src/stitching/proxiedResult.ts index 0e85f5b4b4c..1a384c43763 100644 --- a/src/stitching/proxiedResult.ts +++ b/src/stitching/proxiedResult.ts @@ -1,8 +1,11 @@ import { GraphQLError, GraphQLSchema, + responsePathAsArray, } from 'graphql'; -import { SubschemaConfig } from '../Interfaces'; +import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; +import { handleNull, handleObject } from './checkResultAndHandleErrors'; +import { relocatedError } from './errors'; export let SUBSCHEMAS_SYMBOL: any; export let ERROR_SYMBOL: any; @@ -53,3 +56,56 @@ export function getErrors( return fieldErrors; } + +export function unwrapResult( + parent: any, + info: IGraphQLToolsResolveInfo, + path: Array = [] +): any { + const pathLength = path.length; + + for (let i = 0; i < pathLength; i++) { + const responseKey = path[i]; + const errors = getErrors(parent, responseKey); + const subschemas = getSubschemas(parent); + + const result = parent[responseKey]; + if (result == null) { + return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors); + } + parent = handleObject(result, errors, subschemas); + } + + return parent; +} + +export function dehoistResult(parent: any, delimeter: string): any { + const result = Object.create(null); + + Object.keys(parent).forEach(alias => { + let obj = result; + + const fieldNames = alias.split(delimeter); + const fieldName = fieldNames.pop(); + fieldNames.forEach(key => { + obj = obj[key] = obj[key] || Object.create(null); + }); + obj[fieldName] = parent[alias]; + + }); + + result[ERROR_SYMBOL] = parent[ERROR_SYMBOL].map((error: GraphQLError) => { + if (error.path) { + let path = error.path.slice(); + const pathSegment = path.shift(); + const expandedPathSegment: Array = (pathSegment as string).split(delimeter); + return relocatedError(error, error.nodes, expandedPathSegment.concat(path)); + } else { + return error; + } + }); + + result[SUBSCHEMAS_SYMBOL] = parent[SUBSCHEMAS_SYMBOL]; + + return result; +} diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 9cb29269063..e9753eb1379 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -37,15 +37,11 @@ import { makeExecutableSchema } from '../makeExecutableSchema'; import { delegateToSchema, mergeSchemas, - wrapField, - extractField, - renameField, createMergedResolver, - extractFields, } from '../stitching'; import { SubschemaConfig, MergedTypeConfig } from '../Interfaces'; import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; -import { wrapFieldNode, renameFieldNode } from '../utils/fieldNodes'; +import { wrapFieldNode, renameFieldNode, hoistFieldNodes } from '../utils/fieldNodes'; let linkSchema = ` """ @@ -857,24 +853,19 @@ describe('schema transformation with extraction of nested fields', () => { typeDefs: ` extend type Property { locationName: String - locationName2: String pseudoWrappedError: String } `, resolvers: { Property: { - locationName: createMergedResolver({ fromPath: ['location', 'name'] }), - //deprecated wrapField shorthand - locationName2: wrapField('location', 'name'), - pseudoWrappedError: createMergedResolver({ fromPath: ['error', 'name'] }), + locationName: createMergedResolver({ fromPath: ['location'], fromField: 'name' }), + pseudoWrappedError: createMergedResolver({ fromField: 'error' }), }, }, fieldNodeTransformerMap: { 'Property': { 'locationName': fieldNode => wrapFieldNode(renameFieldNode(fieldNode, 'name'), ['location']), - 'locationName2': - fieldNode => wrapFieldNode(renameFieldNode(fieldNode, 'name'), ['location']), 'pseudoWrappedError': fieldNode => renameFieldNode(fieldNode, 'error'), }, }, @@ -888,8 +879,9 @@ describe('schema transformation with extraction of nested fields', () => { ` query($pid: ID!) { propertyById(id: $pid) { - test1: locationName - test2: locationName2 + id + name + test: locationName pseudoWrappedError } } @@ -904,8 +896,9 @@ describe('schema transformation with extraction of nested fields', () => { expect(result).to.deep.equal({ data: { propertyById: { - test1: 'Helsinki', - test2: 'Helsinki', + id: 'p1', + name: 'Super great hotel', + test: 'Helsinki', pseudoWrappedError: null, }, }, @@ -917,7 +910,7 @@ describe('schema transformation with extraction of nested fields', () => { locations: [ { column: 13, - line: 6, + line: 7, }, ], message: 'Property.error error', @@ -929,38 +922,6 @@ describe('schema transformation with extraction of nested fields', () => { ] }); }); - - it('should work to extract a field', async () => { - const result = await graphql( - transformedPropertySchema, - ` - query($pid: ID!) { - propertyById(id: $pid) { - id - test1: locationName - test2: locationName2 - name - } - } - `, - {}, - {}, - { - pid: 'p1', - }, - ); - - expect(result).to.deep.equal({ - data: { - propertyById: { - id: 'p1', - test1: 'Helsinki', - test2: 'Helsinki', - name: 'Super great hotel', - }, - }, - }); - }); }); describe('schema transformation with wrapping of object fields', () => { @@ -972,7 +933,6 @@ describe('schema transformation with wrapping of object fields', () => { typeDefs: ` extend type Property { outerWrap: OuterWrap - singleWrap: InnerWrap } type OuterWrap { @@ -982,27 +942,23 @@ describe('schema transformation with wrapping of object fields', () => { type InnerWrap { id: ID name: String + error: String } `, resolvers: { Property: { - outerWrap: (parent, args, context, info) => ({ - innerWrap: { - id: createMergedResolver({ toPath: ['innerWrap', 'id'] })(parent, args, context, info), - name: createMergedResolver({ toPath: ['innerWrap', 'name'] })(parent, args, context, info), - }, - }), - //deprecated extractField shorthand - singleWrap: (parent, args, context, info) => ({ - id: extractField('id')(parent, args, context, info), - name: extractField('name')(parent, args, context, info), - }), + outerWrap: createMergedResolver({ dehoist: '__gqltf__' }), }, }, fieldNodeTransformerMap: { 'Property': { - 'outerWrap': (fieldNode, fragments) => extractFields({ fieldNode, path: ['innerWrap'], fragments }), - 'singleWrap': (fieldNode, fragments) => extractFields({ fieldNode, fragments }), + 'outerWrap': (fieldNode, fragments) => hoistFieldNodes({ + fieldNode, + fieldNames: ['id', 'name', 'error'], + path: ['innerWrap'], + delimeter: '__gqltf__', + fragments, + }), }, }, }), @@ -1025,17 +981,14 @@ describe('schema transformation with wrapping of object fields', () => { ...W2 } } - singleWrap { - ...W1 - ...W2 - } } } fragment W1 on InnerWrap { one: id + two: error } fragment W2 on InnerWrap { - two: name + one: name } `, {}, @@ -1051,19 +1004,32 @@ describe('schema transformation with wrapping of object fields', () => { test1: { innerWrap: { one: 'p1', + two: null, }, }, test2: { innerWrap: { - two: 'Super great hotel', + one: 'Super great hotel', }, }, - singleWrap: { - one: 'p1', - two: 'Super great hotel', - } }, }, + 'errors': [{ + 'extensions': { + code: 'SOME_CUSTOM_CODE' + }, + 'locations': [{ + column: 11, + line: 18 + }], + message: 'Property.error error', + path: [ + 'propertyById', + 'test1', + 'innerWrap', + 'two', + ], + }] }); }); }); @@ -1077,20 +1043,16 @@ describe('schema transformation with renaming of object fields', () => { typeDefs: ` extend type Property { new_error: String - new_error2: String } `, resolvers: { Property: { - new_error: createMergedResolver({ fromPath: ['error'] }), - //deprecated renameField shorthand - new_error2: renameField('error'), + new_error: createMergedResolver({ fromField: 'error' }), }, }, fieldNodeTransformerMap: { 'Property': { 'new_error': fieldNode => renameFieldNode(fieldNode, 'error'), - 'new_error2': fieldNode => renameFieldNode(fieldNode, 'error'), }, }, }), @@ -1104,7 +1066,6 @@ describe('schema transformation with renaming of object fields', () => { query($pid: ID!) { propertyById(id: $pid) { new_error - new_error2 } } `, @@ -1119,7 +1080,6 @@ describe('schema transformation with renaming of object fields', () => { data: { propertyById: { new_error: null, - new_error2: null, }, }, errors: [ @@ -1132,10 +1092,6 @@ describe('schema transformation with renaming of object fields', () => { column: 13, line: 4, }, - { - column: 13, - line: 5, - }, ], message: 'Property.error error', path: [ @@ -1143,26 +1099,6 @@ describe('schema transformation with renaming of object fields', () => { 'new_error', ], }, - { - extensions: { - code: 'SOME_CUSTOM_CODE', - }, - locations: [ - { - column: 13, - line: 4, - }, - { - column: 13, - line: 5, - }, - ], - message: 'Property.error error', - path: [ - 'propertyById', - 'new_error2', - ], - }, ], }); }); diff --git a/src/transforms/ExtendSchema.ts b/src/transforms/ExtendSchema.ts index f848d15091a..9dd0f1707d9 100644 --- a/src/transforms/ExtendSchema.ts +++ b/src/transforms/ExtendSchema.ts @@ -4,6 +4,7 @@ import { GraphQLSchema, extendSchema, parse, } from 'graphql'; import { IFieldResolver, IResolvers, Request } from '../Interfaces'; import { Transform } from './transforms'; import { addResolveFunctionsToSchema } from '../generate'; +import { defaultMergedResolver } from '../stitching'; import { default as MapFields, FieldNodeTransformerMap } from './MapFields'; export default class ExtendSchema implements Transform { @@ -25,7 +26,7 @@ export default class ExtendSchema implements Transform { }) { this.typeDefs = typeDefs; this.resolvers = resolvers, - this.defaultFieldResolver = defaultFieldResolver; + this.defaultFieldResolver = defaultFieldResolver || defaultMergedResolver; this.transformer = new MapFields(fieldNodeTransformerMap); } diff --git a/src/transforms/WrapType.ts b/src/transforms/WrapType.ts index 3a8488659b9..e8dbdca4535 100644 --- a/src/transforms/WrapType.ts +++ b/src/transforms/WrapType.ts @@ -3,8 +3,7 @@ import { GraphQLSchema, GraphQLObjectType } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; -import { cloneType, healSchema } from '../utils'; -import { extractFields } from '../stitching'; +import { cloneType, healSchema, collectFields } from '../utils'; import { default as ExtendSchema } from './ExtendSchema'; export default class WrapType implements Transform { @@ -29,7 +28,7 @@ export default class WrapType implements Transform { }, fieldNodeTransformerMap: { [outerTypeName]: { - [fieldName]: (fieldNode, fragments) => extractFields({ fieldNode, fragments }), + [fieldName]: (fieldNode, fragments) => collectFields(fieldNode.selectionSet, fragments), }, } }); diff --git a/src/utils/fieldNodes.ts b/src/utils/fieldNodes.ts index c2846273e60..0c6c0f9456d 100644 --- a/src/utils/fieldNodes.ts +++ b/src/utils/fieldNodes.ts @@ -15,6 +15,16 @@ export function renameFieldNode(fieldNode: FieldNode, name: string): FieldNode { }; } +export function preAliasFieldNode(fieldNode: FieldNode, str: string): FieldNode { + return { + ...fieldNode, + alias: { + ...fieldNode.name, + value: `${str}${fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value}`, + } + }; +} + export function wrapFieldNode(fieldNode: FieldNode, path: Array): FieldNode { let newFieldNode = fieldNode; path.forEach(fieldName => { @@ -72,3 +82,47 @@ export function collectFields( return fields; } + +export function hoistFieldNodes({ + fieldNode, + fieldNames, + path, + delimeter, + fragments, +}: { + fieldNode: FieldNode; + fieldNames: Array; + path: Array; + delimeter: string; + fragments: Record; +}): Array { + const newFieldNode = path.reduce((acc, pathSegment) => { + const alias = acc.alias ? acc.alias.value : acc.name.value; + const newFieldNodes: Array = []; + collectFields(acc.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { + if (possibleFieldNode.name.value === pathSegment) { + newFieldNodes.push(preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`)); + } + }); + + return { + ...acc, + selectionSet: { + ...acc.selectionSet, + selections: newFieldNodes, + } + }; + }, fieldNode); + + const finalFieldNodes: Array = []; + collectFields(newFieldNode.selectionSet, fragments).forEach((finalfieldNode: FieldNode) => { + const alias = finalfieldNode.alias ? finalfieldNode.alias.value : finalfieldNode.name.value; + collectFields(finalfieldNode.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { + if (fieldNames.includes(possibleFieldNode.name.value)) { + finalFieldNodes.push(preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`)); + } + }); + }); + + return finalFieldNodes; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 486df347611..9b66237a785 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -18,4 +18,5 @@ export { collectFields, wrapFieldNode, renameFieldNode, + hoistFieldNodes, } from './fieldNodes'; From c95345a1cefb4120c43319a101c3c2c8aec5b079 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 15 Dec 2019 00:46:39 -0500 Subject: [PATCH 121/250] refactor(transforms): streamline with upstream remove all uses of fieldToFieldConfig in favor of type.toConfig() will support extensions for all graphql objects allow renaming of root and object fields without requiring field argument if field unchanged. --- src/stitching/schemaRecreation.ts | 73 ------------------------- src/test/testAlternateMergeSchemas.ts | 11 ++-- src/transforms/RenameObjectFields.ts | 2 - src/transforms/RenameRootFields.ts | 2 - src/transforms/TransformObjectFields.ts | 23 ++++---- src/transforms/TransformRootFields.ts | 28 ++++------ 6 files changed, 29 insertions(+), 110 deletions(-) delete mode 100644 src/stitching/schemaRecreation.ts diff --git a/src/stitching/schemaRecreation.ts b/src/stitching/schemaRecreation.ts deleted file mode 100644 index 12780ea3e35..00000000000 --- a/src/stitching/schemaRecreation.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - GraphQLArgument, - GraphQLArgumentConfig, - GraphQLField, - GraphQLFieldConfig, - GraphQLFieldConfigArgumentMap, - GraphQLInputField, - GraphQLInputFieldConfig, - GraphQLInputFieldConfigMap, - GraphQLInputFieldMap, -} from 'graphql'; - -export function fieldToFieldConfig( - field: GraphQLField, -): GraphQLFieldConfig { - return { - type: field.type, - args: argsToFieldConfigArgumentMap(field.args), - resolve: field.resolve, - subscribe: field.subscribe, - description: field.description, - deprecationReason: field.deprecationReason, - astNode: field.astNode, - }; -} - -export function argsToFieldConfigArgumentMap( - args: Array, -): GraphQLFieldConfigArgumentMap { - const result: GraphQLFieldConfigArgumentMap = {}; - args.forEach(arg => { - const newArg = argumentToArgumentConfig(arg); - if (newArg) { - result[newArg[0]] = newArg[1]; - } - }); - return result; -} - -export function argumentToArgumentConfig( - argument: GraphQLArgument, -): [string, GraphQLArgumentConfig] | null { - return [ - argument.name, - { - type: argument.type, - defaultValue: argument.defaultValue, - description: argument.description, - astNode: argument.astNode, - }, - ]; -} - -export function inputFieldMapToFieldConfigMap( - fields: GraphQLInputFieldMap, -): GraphQLInputFieldConfigMap { - const result: GraphQLInputFieldConfigMap = {}; - Object.keys(fields).forEach(name => { - result[name] = inputFieldToFieldConfig(fields[name]); - }); - return result; -} - -export function inputFieldToFieldConfig( - field: GraphQLInputField, -): GraphQLInputFieldConfig { - return { - type: field.type, - defaultValue: field.defaultValue, - description: field.description, - astNode: field.astNode, - }; -} diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index e9753eb1379..c6a78d3ecc8 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -7,10 +7,11 @@ import { ExecutionResult, subscribe, parse, - GraphQLField, GraphQLScalarType, FieldNode, printSchema, + GraphQLObjectTypeConfig, + GraphQLFieldConfig, } from 'graphql'; import { transformSchema, @@ -32,7 +33,6 @@ import { subscriptionPubSubTrigger, } from './testingSchemas'; import { forAwaitEach } from 'iterall'; -import { fieldToFieldConfig } from '../stitching/schemaRecreation'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { delegateToSchema, @@ -333,11 +333,12 @@ describe('transform object fields', () => { before(async () => { transformedPropertySchema = transformSchema(propertySchema, [ new TransformObjectFields( - (typeName: string, fieldName: string, field: GraphQLField) => { - const fieldConfig = fieldToFieldConfig(field); + (typeName: string, fieldName: string) => { if (typeName !== 'Property' || fieldName !== 'name') { - return fieldConfig; + return undefined; } + const typeConfig = propertySchema.getType(typeName).toConfig() as GraphQLObjectTypeConfig; + const fieldConfig = typeConfig.fields[fieldName] as GraphQLFieldConfig; fieldConfig.resolve = () => 'test'; return fieldConfig; }, diff --git a/src/transforms/RenameObjectFields.ts b/src/transforms/RenameObjectFields.ts index f814a73177e..901cd6e070e 100644 --- a/src/transforms/RenameObjectFields.ts +++ b/src/transforms/RenameObjectFields.ts @@ -1,6 +1,5 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; import { Transform } from './transforms'; -import { fieldToFieldConfig } from '../stitching/schemaRecreation'; import { Request } from '../Interfaces'; import TransformObjectFields from './TransformObjectFields'; @@ -12,7 +11,6 @@ export default class RenameObjectFields implements Transform { (typeName: string, fieldName: string, field: GraphQLField) => { return { name: renamer(typeName, fieldName, field), - field: fieldToFieldConfig(field) }; } ); diff --git a/src/transforms/RenameRootFields.ts b/src/transforms/RenameRootFields.ts index 6ddebff8662..c2fb9b39e19 100644 --- a/src/transforms/RenameRootFields.ts +++ b/src/transforms/RenameRootFields.ts @@ -1,6 +1,5 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; import { Transform } from './transforms'; -import { fieldToFieldConfig } from '../stitching/schemaRecreation'; import TransformRootFields from './TransformRootFields'; export default class RenameRootFields implements Transform { @@ -21,7 +20,6 @@ export default class RenameRootFields implements Transform { ) => { return { name: renamer(operation, fieldName, field), - field: fieldToFieldConfig(field), }; }, ); diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index cd4881758e6..06e5cc6027e 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -18,13 +18,12 @@ import isEmptyObject from '../utils/isEmptyObject'; import { Request, VisitSchemaKind } from '../Interfaces'; import { Transform } from './transforms'; import { visitSchema } from '../utils/visitSchema'; -import { fieldToFieldConfig } from '../stitching/schemaRecreation'; export type ObjectFieldTransformer = ( typeName: string, fieldName: string, field: GraphQLField, -) => GraphQLFieldConfig | { name: string; field: GraphQLFieldConfig } | null | undefined; +) => GraphQLFieldConfig | RenamedField | null | undefined; export type FieldNodeTransformer = ( typeName: string, @@ -39,6 +38,8 @@ type FieldMapping = { }; }; +type RenamedField = { name: string; field?: GraphQLFieldConfig }; + export default class TransformObjectFields implements Transform { private objectFieldTransformer: ObjectFieldTransformer; private fieldNodeTransformer: FieldNodeTransformer; @@ -90,6 +91,7 @@ export default class TransformObjectFields implements Transform { type: GraphQLObjectType, objectFieldTransformer: ObjectFieldTransformer ): GraphQLObjectType { + const typeConfig = type.toConfig(); const fields = type.getFields(); const newFields = {}; @@ -98,15 +100,15 @@ export default class TransformObjectFields implements Transform { const transformedField = objectFieldTransformer(type.name, fieldName, field); if (typeof transformedField === 'undefined') { - newFields[fieldName] = fieldToFieldConfig(field); + newFields[fieldName] = typeConfig.fields[fieldName]; } else if (transformedField !== null) { - const newName = (transformedField as { name: string; field: GraphQLFieldConfig }).name; + const newName = (transformedField as RenamedField).name; if (newName) { - newFields[newName] = (transformedField as { - name: string; - field: GraphQLFieldConfig; - }).field; + newFields[newName] = (transformedField as RenamedField).field ? + (transformedField as RenamedField).field : + typeConfig.fields[fieldName]; + if (newName !== fieldName) { const typeName = type.name; if (!this.mapping[typeName]) { @@ -114,10 +116,7 @@ export default class TransformObjectFields implements Transform { } this.mapping[typeName][newName] = fieldName; - const originalResolver = (transformedField as { - name: string; - field: GraphQLFieldConfig; - }).field.resolve; + const originalResolver = newFields[newName].resolve; (newFields[newName] as GraphQLFieldConfig).resolve = (parent, args, context, info) => originalResolver(parent, args, context, { ...info, diff --git a/src/transforms/TransformRootFields.ts b/src/transforms/TransformRootFields.ts index d1d5b4e4576..eea4d067e93 100644 --- a/src/transforms/TransformRootFields.ts +++ b/src/transforms/TransformRootFields.ts @@ -8,7 +8,6 @@ import isEmptyObject from '../utils/isEmptyObject'; import { Transform } from './transforms'; import { visitSchema } from '../utils/visitSchema'; import { VisitSchemaKind } from '../Interfaces'; -import { fieldToFieldConfig } from '../stitching/schemaRecreation'; export type RootTransformer = ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -16,10 +15,12 @@ export type RootTransformer = ( field: GraphQLField, ) => | GraphQLFieldConfig - | { name: string; field: GraphQLFieldConfig } + | RenamedField | null | undefined; +type RenamedField = { name: string; field?: GraphQLFieldConfig }; + export default class TransformRootFields implements Transform { private transform: RootTransformer; @@ -61,27 +62,24 @@ function transformFields( field: GraphQLField, ) => | GraphQLFieldConfig - | { name: string; field: GraphQLFieldConfig } + | RenamedField | null | undefined, ): GraphQLObjectType { + const typeConfig = type.toConfig(); const fields = type.getFields(); const newFields = {}; Object.keys(fields).forEach(fieldName => { const field = fields[fieldName]; const newField = transformer(fieldName, field); if (typeof newField === 'undefined') { - newFields[fieldName] = fieldToFieldConfig(field); + newFields[fieldName] = typeConfig.fields[fieldName]; } else if (newField !== null) { - if ( - (<{ name: string; field: GraphQLFieldConfig }>newField).name - ) { - newFields[ - (<{ name: string; field: GraphQLFieldConfig }>newField).name - ] = (<{ - name: string; - field: GraphQLFieldConfig; - }>newField).field; + if ((newField as RenamedField).name) { + newFields[(newField as RenamedField).name] = + (newField as RenamedField).field ? + (newField as RenamedField).field : + typeConfig.fields[fieldName]; } else { newFields[fieldName] = newField; } @@ -91,9 +89,7 @@ function transformFields( return null; } else { return new GraphQLObjectType({ - name: type.name, - description: type.description, - astNode: type.astNode, + ...type, fields: newFields, }); } From 738f12925b2b2b75ab91b0587088c92010589e55 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 16 Dec 2019 19:57:49 -0500 Subject: [PATCH 122/250] refactor(createMergedResolver) Precompile resolver at build time. Default delimeter to '__gqtlf__'. --- src/stitching/createMergedResolver.ts | 41 ++++++++++++++------------- src/test/testAlternateMergeSchemas.ts | 3 +- src/utils/fieldNodes.ts | 8 +++--- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 68d4793d36c..241ec1fd914 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -9,27 +9,30 @@ export function createMergedResolver({ }: { fromPath?: Array; fromField?: string; - dehoist?: string; + dehoist?: boolean | string; }): IFieldResolver { - return (parent, args, context, info) => { - if (dehoist) { - parent = dehoistResult(parent, dehoist); - } + const fromFieldResolver: IFieldResolver = fromField ? + (parent, args, context, info) => defaultMergedResolver(parent, args, context, { + ...info, + fieldName: fromField, + }) : + defaultMergedResolver; - if (fromPath) { - parent = unwrapResult(parent, info, fromPath); - } + const noParentResolver: IFieldResolver = + (parent, args, context, info) => parent ? + parent instanceof Error ? + parent : + fromFieldResolver(parent, args, context, info) + : {}; - if (!parent) { - parent = {}; - } + const unwrappingResolver: IFieldResolver = fromPath ? + (parent, args, context, info) => + noParentResolver(unwrapResult(parent, info, fromPath), args, context, info) : + noParentResolver; - return parent instanceof Error ? - parent : fromField ? - defaultMergedResolver(parent, args, context, { - ...info, - fieldName: fromField, - }) : - defaultMergedResolver(parent, args, context, info); - }; + const delimeter = dehoist === 'string' ? dehoist : '__gqltf__'; + return dehoist ? + (parent, args, context, info) => + unwrappingResolver(dehoistResult(parent, delimeter), args, context, info) : + unwrappingResolver; } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index c6a78d3ecc8..9c83495e506 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -948,7 +948,7 @@ describe('schema transformation with wrapping of object fields', () => { `, resolvers: { Property: { - outerWrap: createMergedResolver({ dehoist: '__gqltf__' }), + outerWrap: createMergedResolver({ dehoist: true }), }, }, fieldNodeTransformerMap: { @@ -957,7 +957,6 @@ describe('schema transformation with wrapping of object fields', () => { fieldNode, fieldNames: ['id', 'name', 'error'], path: ['innerWrap'], - delimeter: '__gqltf__', fragments, }), }, diff --git a/src/utils/fieldNodes.ts b/src/utils/fieldNodes.ts index 0c6c0f9456d..18d276a6927 100644 --- a/src/utils/fieldNodes.ts +++ b/src/utils/fieldNodes.ts @@ -87,13 +87,13 @@ export function hoistFieldNodes({ fieldNode, fieldNames, path, - delimeter, + delimeter = '__gqltf__', fragments, }: { fieldNode: FieldNode; - fieldNames: Array; + fieldNames?: Array; path: Array; - delimeter: string; + delimeter?: string; fragments: Record; }): Array { const newFieldNode = path.reduce((acc, pathSegment) => { @@ -118,7 +118,7 @@ export function hoistFieldNodes({ collectFields(newFieldNode.selectionSet, fragments).forEach((finalfieldNode: FieldNode) => { const alias = finalfieldNode.alias ? finalfieldNode.alias.value : finalfieldNode.name.value; collectFields(finalfieldNode.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { - if (fieldNames.includes(possibleFieldNode.name.value)) { + if (!fieldNames || fieldNames.includes(possibleFieldNode.name.value)) { finalFieldNodes.push(preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`)); } }); From 2d6e9c950ec54368a6d30bfd2830a1284145a3cb Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 16 Dec 2019 19:59:18 -0500 Subject: [PATCH 123/250] refactor(tests) Update field name in tests to better reflect what is being tests. --- src/test/testAlternateMergeSchemas.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 9c83495e506..b0c469e0433 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -854,20 +854,20 @@ describe('schema transformation with extraction of nested fields', () => { typeDefs: ` extend type Property { locationName: String - pseudoWrappedError: String + renamedError: String } `, resolvers: { Property: { locationName: createMergedResolver({ fromPath: ['location'], fromField: 'name' }), - pseudoWrappedError: createMergedResolver({ fromField: 'error' }), + renamedError: createMergedResolver({ fromField: 'error' }), }, }, fieldNodeTransformerMap: { 'Property': { 'locationName': fieldNode => wrapFieldNode(renameFieldNode(fieldNode, 'name'), ['location']), - 'pseudoWrappedError': fieldNode => renameFieldNode(fieldNode, 'error'), + 'renamedError': fieldNode => renameFieldNode(fieldNode, 'error'), }, }, }), @@ -883,7 +883,7 @@ describe('schema transformation with extraction of nested fields', () => { id name test: locationName - pseudoWrappedError + renamedError } } `, @@ -900,7 +900,7 @@ describe('schema transformation with extraction of nested fields', () => { id: 'p1', name: 'Super great hotel', test: 'Helsinki', - pseudoWrappedError: null, + renamedError: null, }, }, errors: [ @@ -917,7 +917,7 @@ describe('schema transformation with extraction of nested fields', () => { message: 'Property.error error', path: [ 'propertyById', - 'pseudoWrappedError', + 'renamedError', ], }, ] From e00d6932019c87678c73f4406b42d4375413d12b Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 18 Dec 2019 23:39:06 -0500 Subject: [PATCH 124/250] feat(WrapFields): add new transform --WrapFields can wrap a subset of a types fields with any number of wrapping fields and types. --fixes createMergedResolver and hoistFields. --refactors WrapType to use the underlying more generic WrapFields transform. --- src/stitching/createMergedResolver.ts | 23 ++-- src/test/testAlternateMergeSchemas.ts | 160 +++++++++++++++++++++++++- src/transforms/WrapFields.ts | 144 +++++++++++++++++++++++ src/transforms/WrapType.ts | 44 +------ src/transforms/index.ts | 1 + src/utils/fieldNodes.ts | 49 ++++---- 6 files changed, 346 insertions(+), 75 deletions(-) create mode 100644 src/transforms/WrapFields.ts diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 241ec1fd914..53240d9234d 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -18,21 +18,26 @@ export function createMergedResolver({ }) : defaultMergedResolver; - const noParentResolver: IFieldResolver = - (parent, args, context, info) => parent ? - parent instanceof Error ? - parent : - fromFieldResolver(parent, args, context, info) - : {}; + const parentErrorResolver: IFieldResolver = + (parent, args, context, info) => parent instanceof Error ? + parent : + fromFieldResolver(parent, args, context, info); const unwrappingResolver: IFieldResolver = fromPath ? (parent, args, context, info) => - noParentResolver(unwrapResult(parent, info, fromPath), args, context, info) : - noParentResolver; + parentErrorResolver(unwrapResult(parent, info, fromPath), args, context, info) : + parentErrorResolver; const delimeter = dehoist === 'string' ? dehoist : '__gqltf__'; - return dehoist ? + 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/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index b0c469e0433..5ed99435d4c 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -22,6 +22,7 @@ import { TransformObjectFields, ExtendSchema, WrapType, + WrapFields, FilterRootFields, FilterObjectFields, } from '../transforms'; @@ -703,7 +704,7 @@ type UnionImpl { pid: 'p1', }, ); - +//console.log(result.errors[0]) expect(result).to.deep.equal({ data: { namespace: { @@ -1034,6 +1035,163 @@ describe('schema transformation with wrapping of object fields', () => { }); }); +describe('WrapFields transform', () => { + let transformedPropertySchema: GraphQLSchema; + + before(async () => { + transformedPropertySchema = transformSchema(propertySchema, [ + new WrapFields( + 'Property', + ['outerWrap'], + ['OuterWrap'], + ['id', 'name', 'error'], + ), + ]); + }); + + it('should work, even with aliases', async () => { + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + test1: outerWrap { + ...W1 + } + test2: outerWrap { + ...W2 + } + } + } + fragment W1 on OuterWrap { + one: id + two: error + } + fragment W2 on OuterWrap { + one: name + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + expect(result).to.deep.equal({ + data: { + propertyById: { + test1: { + one: 'p1', + two: null, + }, + test2: { + one: 'Super great hotel', + }, + }, + }, + 'errors': [{ + 'extensions': { + code: 'SOME_CUSTOM_CODE' + }, + 'locations': [{ + column: 11, + line: 14 + }], + message: 'Property.error error', + path: [ + 'propertyById', + 'test1', + 'two', + ], + }] + }); + }); +}); + +describe('WrapFields transform with multiple fields', () => { + let transformedPropertySchema: GraphQLSchema; + + before(async () => { + transformedPropertySchema = transformSchema(propertySchema, [ + new WrapFields( + 'Property', + ['outerWrap', 'innerWrap'], + ['OuterWrap', 'InnerWrap'], + ['id', 'name', 'error'], + ), + ]); + }); + + it('should work, even with aliases', async () => { + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + test1: outerWrap { + innerWrap { + ...W1 + } + } + test2: outerWrap { + innerWrap { + ...W2 + } + } + } + } + fragment W1 on InnerWrap { + one: id + two: error + } + fragment W2 on InnerWrap { + one: name + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + expect(result).to.deep.equal({ + data: { + propertyById: { + test1: { + innerWrap: { + one: 'p1', + two: null, + }, + }, + test2: { + innerWrap: { + one: 'Super great hotel', + }, + }, + }, + }, + 'errors': [{ + 'extensions': { + code: 'SOME_CUSTOM_CODE' + }, + 'locations': [{ + column: 11, + line: 18 + }], + message: 'Property.error error', + path: [ + 'propertyById', + 'test1', + 'innerWrap', + 'two', + ], + }] + }); + }); +}); + describe('schema transformation with renaming of object fields', () => { let transformedPropertySchema: GraphQLSchema; diff --git a/src/transforms/WrapFields.ts b/src/transforms/WrapFields.ts new file mode 100644 index 00000000000..3d63d4b6805 --- /dev/null +++ b/src/transforms/WrapFields.ts @@ -0,0 +1,144 @@ +/* tslint:disable:no-unused-expression */ + +import { + GraphQLSchema, + GraphQLObjectType, + GraphQLObjectTypeConfig, + GraphQLFieldConfigMap, + GraphQLFieldConfig, +} from 'graphql'; +import { Request } from '../Interfaces'; +import { Transform } from './transforms'; +import { hoistFieldNodes, healSchema } from '../utils'; +import { defaultMergedResolver, createMergedResolver } from '../stitching'; +import { default as MapFields } from './MapFields'; +import { TypeMap } from 'graphql/type/schema'; + +export default class WrapFields implements Transform { + private outerTypeName: string; + private wrappingFieldNames: Array; + private wrappingTypeNames: Array; + private numWraps: number; + private fieldNames: Array; + private transformer: Transform; + + constructor( + outerTypeName: string, + wrappingFieldNames: Array, + wrappingTypeNames: Array, + fieldNames?: Array, + ) { + this.outerTypeName = outerTypeName; + this.wrappingFieldNames = wrappingFieldNames; + this.wrappingTypeNames = wrappingTypeNames; + this.numWraps = wrappingFieldNames.length; + this.fieldNames = fieldNames; + + const remainingWrappingFieldNames = this.wrappingFieldNames.slice(); + const outerMostWrappingFieldName = remainingWrappingFieldNames.shift(); + this.transformer = new MapFields({ + [outerTypeName]: { + [outerMostWrappingFieldName]: (fieldNode, fragments) => hoistFieldNodes({ + fieldNode, + path: remainingWrappingFieldNames, + fieldNames: this.fieldNames, + fragments, + }), + }, + }); + } + + public transformSchema(schema: GraphQLSchema): GraphQLSchema { + const typeMap = schema.getTypeMap(); + + const targetFields = this.filterFields( + typeMap, + this.outerTypeName, + !this.fieldNames ? () => true : fieldName => this.fieldNames.includes(fieldName) + ); + + let wrapIndex = this.numWraps - 1; + + const innerMostWrappingTypeName = this.wrappingTypeNames[wrapIndex]; + this.appendFields(typeMap, innerMostWrappingTypeName, targetFields); + + for (wrapIndex--; wrapIndex > -1; wrapIndex--) { + this.appendFields(typeMap, this.wrappingTypeNames[wrapIndex], { + [this.wrappingFieldNames[wrapIndex + 1]]: { + type: typeMap[this.wrappingTypeNames[wrapIndex + 1]] as GraphQLObjectType, + resolve: defaultMergedResolver, + } + }); + } + + this.appendFields(typeMap, this.outerTypeName, { + [this.wrappingFieldNames[0]]: { + type: typeMap[this.wrappingTypeNames[0]] as GraphQLObjectType, + resolve: createMergedResolver({ dehoist: true }), + }, + }); + + healSchema(schema); + + return this.transformer.transformSchema(schema); + } + + public transformRequest(originalRequest: Request): Request { + return this.transformer.transformRequest(originalRequest); + } + + private appendFields( + typeMap: TypeMap, + typeName: string, + fields: GraphQLFieldConfigMap, + ) { + let type = typeMap[typeName]; + if (type) { + const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; + const originalFields = typeConfig.fields; + const newFields = {}; + Object.keys(originalFields).forEach(fieldName => { + newFields[fieldName] = originalFields[fieldName]; + }); + Object.keys(fields).forEach(fieldName => { + newFields[fieldName] = fields[fieldName]; + }); + type = new GraphQLObjectType({ + ...typeConfig, + fields: newFields, + }); + } else { + type = new GraphQLObjectType({ + name: typeName, + fields, + }); + } + typeMap[typeName] = type; + } + + private filterFields( + typeMap: TypeMap, + typeName: string, + filter: (fieldName: string, field: GraphQLFieldConfig) => boolean, + ): GraphQLFieldConfigMap { + let type = typeMap[typeName]; + const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; + const originalFields = typeConfig.fields; + const newFields = {}; + const filteredFields = {}; + Object.keys(originalFields).forEach(fieldName => { + if (filter(fieldName, originalFields[fieldName])) { + filteredFields[fieldName] = originalFields[fieldName]; + } else { + newFields[fieldName] = originalFields[fieldName]; + } + }); + type = new GraphQLObjectType({ + ...typeConfig, + fields: newFields, + }); + typeMap[typeName] = type; + + return filteredFields; + } +} diff --git a/src/transforms/WrapType.ts b/src/transforms/WrapType.ts index e8dbdca4535..d2d2113d5ef 100644 --- a/src/transforms/WrapType.ts +++ b/src/transforms/WrapType.ts @@ -1,15 +1,11 @@ /* tslint:disable:no-unused-expression */ -import { GraphQLSchema, GraphQLObjectType } from 'graphql'; +import { GraphQLSchema } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; -import { cloneType, healSchema, collectFields } from '../utils'; -import { default as ExtendSchema } from './ExtendSchema'; +import { default as WrapFields } from './WrapFields'; export default class WrapType implements Transform { - private outerTypeName: string; - private innerTypeName: string; - private fieldName: string; private transformer: Transform; constructor( @@ -17,43 +13,11 @@ export default class WrapType implements Transform { innerTypeName: string, fieldName: string ) { - this.outerTypeName = outerTypeName; - this.innerTypeName = innerTypeName; - this.fieldName = fieldName; - this.transformer = new ExtendSchema({ - resolvers: { - [outerTypeName]: { - [fieldName]: parent => parent ? parent : {}, - }, - }, - fieldNodeTransformerMap: { - [outerTypeName]: { - [fieldName]: (fieldNode, fragments) => collectFields(fieldNode.selectionSet, fragments), - }, - } - }); + this.transformer = new WrapFields(outerTypeName, [fieldName], [innerTypeName]); } public transformSchema(schema: GraphQLSchema): GraphQLSchema { - const typeMap = schema.getTypeMap(); - - // Clone the outer type before modification. - // When healing, changing the type name of a root type changes the root type name. - const innerType = cloneType(typeMap[this.outerTypeName]); - innerType.name = this.innerTypeName; - - typeMap[this.innerTypeName] = innerType; - - typeMap[this.outerTypeName] = new GraphQLObjectType({ - name: this.outerTypeName, - fields: { - [this.fieldName]: { - type: typeMap[this.innerTypeName] as GraphQLObjectType, - }, - }, - }); - - return this.transformer.transformSchema(healSchema(schema)); + return this.transformer.transformSchema(schema); } public transformRequest(originalRequest: Request): Request { diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 14f20c22eaf..2ab795023e5 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -26,5 +26,6 @@ export { default as TransformQuery } from './TransformQuery'; export { default as ExtendSchema } from './ExtendSchema'; export { default as WrapType } from './WrapType'; +export { default as WrapFields } from './WrapFields'; export { default as MapFields } from './MapFields'; export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; diff --git a/src/utils/fieldNodes.ts b/src/utils/fieldNodes.ts index 18d276a6927..85a7f49d557 100644 --- a/src/utils/fieldNodes.ts +++ b/src/utils/fieldNodes.ts @@ -86,43 +86,42 @@ export function collectFields( export function hoistFieldNodes({ fieldNode, fieldNames, - path, + path = [], delimeter = '__gqltf__', fragments, }: { fieldNode: FieldNode; fieldNames?: Array; - path: Array; + path?: Array; delimeter?: string; fragments: Record; }): Array { - const newFieldNode = path.reduce((acc, pathSegment) => { - const alias = acc.alias ? acc.alias.value : acc.name.value; - const newFieldNodes: Array = []; - collectFields(acc.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { - if (possibleFieldNode.name.value === pathSegment) { - newFieldNodes.push(preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`)); - } - }); + const alias = fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value; - return { - ...acc, - selectionSet: { - ...acc.selectionSet, - selections: newFieldNodes, - } - }; - }, fieldNode); + let newFieldNodes: Array = []; - const finalFieldNodes: Array = []; - collectFields(newFieldNode.selectionSet, fragments).forEach((finalfieldNode: FieldNode) => { - const alias = finalfieldNode.alias ? finalfieldNode.alias.value : finalfieldNode.name.value; - collectFields(finalfieldNode.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { + if (path && path.length) { + const remainingPathSegments = path.slice(); + const initialPathSegment = remainingPathSegments.shift(); + + collectFields(fieldNode.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { + if (possibleFieldNode.name.value === initialPathSegment) { + newFieldNodes = newFieldNodes.concat(hoistFieldNodes({ + fieldNode: preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`), + fieldNames, + path: remainingPathSegments, + delimeter, + fragments, + })); + } + }); + } else { + collectFields(fieldNode.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { if (!fieldNames || fieldNames.includes(possibleFieldNode.name.value)) { - finalFieldNodes.push(preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`)); + newFieldNodes.push(preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`)); } }); - }); + } - return finalFieldNodes; + return newFieldNodes; } From 83d00ac303410622b579f1ebd5ab2389918b8aef Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 18 Dec 2019 23:45:08 -0500 Subject: [PATCH 125/250] chore(deps): update minor versions --- package.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index f49ea52a7db..1db65a4568c 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,8 @@ }, "homepage": "https://github.com/apollostack/graphql-tools#readme", "dependencies": { - "apollo-link": "^1.2.12", - "apollo-utilities": "^1.3.2", + "apollo-link": "^1.2.13", + "apollo-utilities": "^1.3.3", "deprecated-decorator": "^0.1.6", "iterall": "^1.2.2", "uuid": "^3.3.3" @@ -59,11 +59,11 @@ "graphql": "^14.2.0" }, "devDependencies": { - "@types/chai": "4.2.0", - "@types/dateformat": "^3.0.0", + "@types/chai": "^4.2.7", + "@types/dateformat": "^3.0.1", "@types/mocha": "^5.2.7", - "@types/node": "^12.7.12", - "@types/uuid": "^3.4.5", + "@types/node": "^12.12.21", + "@types/uuid": "^3.4.6", "@types/zen-observable": "^0.8.0", "body-parser": "^1.19.0", "chai": "^4.2.0", @@ -71,15 +71,15 @@ "express": "^4.17.1", "graphql": "^14.5.8", "graphql-subscriptions": "^1.1.0", - "graphql-type-json": "^0.3.0", + "graphql-type-json": "^0.3.1", "istanbul": "^0.4.5", - "mocha": "^6.2.1", - "prettier": "^1.18.2", + "mocha": "^6.2.2", + "prettier": "^1.19.1", "remap-istanbul": "0.13.0", "rimraf": "^3.0.0", - "source-map-support": "^0.5.13", - "standard-version": "^7.0.0", - "tslint": "^5.20.0", - "typescript": "3.6.4" + "source-map-support": "^0.5.16", + "standard-version": "^7.0.1", + "tslint": "^5.20.1", + "typescript": "3.7.3" } } From 90c356508577fa5d2c51abba8a433cffca2ca0f5 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 24 Dec 2019 20:41:19 -0500 Subject: [PATCH 126/250] fix(stitching): filter unused variables from map https://github.com/gatsbyjs/gatsby/issues/20280 --- src/transforms/FilterToSchema.ts | 33 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/transforms/FilterToSchema.ts b/src/transforms/FilterToSchema.ts index 4ac65da7aef..f2a7efd6511 100644 --- a/src/transforms/FilterToSchema.ts +++ b/src/transforms/FilterToSchema.ts @@ -32,21 +32,22 @@ export default class FilterToSchema implements Transform { } public transformRequest(originalRequest: Request): Request { - const document = filterDocumentToSchema( - this.targetSchema, - originalRequest.document, - ); return { ...originalRequest, - document, + ...filterToSchema( + this.targetSchema, + originalRequest.document, + originalRequest.variables, + ), }; } } -function filterDocumentToSchema( +function filterToSchema( targetSchema: GraphQLSchema, document: DocumentNode, -): DocumentNode { + variables: Record, +): { document: DocumentNode; variables: Record } { const operations: Array< OperationDefinitionNode > = document.definitions.filter( @@ -56,6 +57,7 @@ function filterDocumentToSchema( def => def.kind === Kind.FRAGMENT_DEFINITION, ) as Array; + let usedVariables: Array = []; let usedFragments: Array = []; const newOperations: Array = []; let newFragments: Array = []; @@ -110,14 +112,15 @@ function filterDocumentToSchema( validFragmentsWithType, usedFragments, ); - const fullUsedVariables = + const operationOrFragmentVariables = union(operationUsedVariables, collectedUsedVariables); + usedVariables = union(usedVariables, operationOrFragmentVariables); newFragments = collectedNewFragments; fragmentSet = collectedFragmentSet; const variableDefinitions = operation.variableDefinitions.filter( (variable: VariableDefinitionNode) => - fullUsedVariables.indexOf(variable.variable.name.value) !== -1, + operationOrFragmentVariables.indexOf(variable.variable.name.value) !== -1, ); newOperations.push({ @@ -130,9 +133,17 @@ function filterDocumentToSchema( }); }); + const newVariables: Record = Object.create(null); + usedVariables.forEach(variableName => { + newVariables[variableName] = variables[variableName]; + }); + return { - kind: Kind.DOCUMENT, - definitions: [...newOperations, ...newFragments], + document: { + kind: Kind.DOCUMENT, + definitions: [...newOperations, ...newFragments], + }, + variables: newVariables, }; } From bb6c4432621aa1d3b2fdbf8940e545773393d562 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 24 Dec 2019 23:57:40 -0500 Subject: [PATCH 127/250] lint --- src/test/testAlternateMergeSchemas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 5ed99435d4c..17838e83734 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -704,7 +704,7 @@ type UnionImpl { pid: 'p1', }, ); -//console.log(result.errors[0]) + expect(result).to.deep.equal({ data: { namespace: { From e2862057a27b30de5aff7797424c6e6d2952c850 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 25 Dec 2019 20:31:13 -0500 Subject: [PATCH 128/250] fix(filterToSchema): remove map in favor of plain object some links (e.g. apollo-upload-client) may expect a plain object with constructor.name === 'Object' --- src/transforms/FilterToSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transforms/FilterToSchema.ts b/src/transforms/FilterToSchema.ts index f2a7efd6511..85339d78b7e 100644 --- a/src/transforms/FilterToSchema.ts +++ b/src/transforms/FilterToSchema.ts @@ -133,7 +133,7 @@ function filterToSchema( }); }); - const newVariables: Record = Object.create(null); + const newVariables: Record = {}; usedVariables.forEach(variableName => { newVariables[variableName] = variables[variableName]; }); From 6a25db34072b03f8c56fca3fd5409b5774c30053 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 25 Dec 2019 20:26:05 -0500 Subject: [PATCH 129/250] feat(stitching): enable proxying uploads Adds a new scalar for the gateway and a new server to server link that allows proxying of remote file uploads using the graphql-upload format. --- .npmignore | 2 + package.json | 17 +- src/index.ts | 2 + src/links/createServerHttpLink.ts | 403 ++++++++++++++++++++++++++++++ src/links/index.ts | 5 + src/scalars/GraphQLUpload.ts | 15 ++ src/scalars/index.ts | 5 + src/test/testUpload.ts | 149 +++++++++++ src/test/tests.ts | 1 + tsconfig.json | 1 + typings.d.ts | 1 + 11 files changed, 599 insertions(+), 2 deletions(-) create mode 100644 src/links/createServerHttpLink.ts create mode 100644 src/links/index.ts create mode 100644 src/scalars/GraphQLUpload.ts create mode 100644 src/scalars/index.ts create mode 100644 src/test/testUpload.ts diff --git a/.npmignore b/.npmignore index 6ed2c45714a..6ac52e9721e 100644 --- a/.npmignore +++ b/.npmignore @@ -2,6 +2,8 @@ !dist/ !dist/* !dist/generate/* +!dist/links/* +!dist/scalars/* !dist/stitching/* !dist/transforms/* !dist/utils/* diff --git a/package.json b/package.json index 1db65a4568c..08936d4ee56 100644 --- a/package.json +++ b/package.json @@ -50,28 +50,40 @@ "homepage": "https://github.com/apollostack/graphql-tools#readme", "dependencies": { "apollo-link": "^1.2.13", + "apollo-link-http-common": "^0.2.15", "apollo-utilities": "^1.3.3", "deprecated-decorator": "^0.1.6", + "extract-files": "^6.0.0", + "form-data": "^3.0.0", "iterall": "^1.2.2", + "node-fetch": "^2.6.0", "uuid": "^3.3.3" }, "peerDependencies": { "graphql": "^14.2.0" }, "devDependencies": { + "@types/apollo-upload-client": "^8.1.3", "@types/chai": "^4.2.7", "@types/dateformat": "^3.0.1", + "@types/express": "^4.17.2", + "@types/extract-files": "^3.1.0", + "@types/graphql-upload": "^8.0.3", "@types/mocha": "^5.2.7", "@types/node": "^12.12.21", + "@types/node-fetch": "^2.5.4", + "@types/supertest": "^2.0.8", "@types/uuid": "^3.4.6", - "@types/zen-observable": "^0.8.0", + "apollo-upload-client": "^12.1.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "dateformat": "^3.0.3", "express": "^4.17.1", + "express-graphql": "^0.9.0", "graphql": "^14.5.8", "graphql-subscriptions": "^1.1.0", "graphql-type-json": "^0.3.1", + "graphql-upload": "^9.0.0", "istanbul": "^0.4.5", "mocha": "^6.2.2", "prettier": "^1.19.1", @@ -80,6 +92,7 @@ "source-map-support": "^0.5.16", "standard-version": "^7.0.1", "tslint": "^5.20.1", - "typescript": "3.7.3" + "typescript": "3.7.3", + "zen-observable-ts": "^0.8.20" } } diff --git a/src/index.ts b/src/index.ts index cecba32033e..b918078b972 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ export * from './Interfaces'; +export * from './links'; export * from './makeExecutableSchema'; export * from './mock'; +export * from './scalars'; export * from './stitching'; export * from './transforms'; export * from './utils'; diff --git a/src/links/createServerHttpLink.ts b/src/links/createServerHttpLink.ts new file mode 100644 index 00000000000..ab07646d098 --- /dev/null +++ b/src/links/createServerHttpLink.ts @@ -0,0 +1,403 @@ +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'; + +class FormData extends KnownLengthFormData { + private hasUnknowableLength: boolean; + + constructor(options?: any) { + super(options); + this.hasUnknowableLength = false; + } + + public append(key: string, value: any, options?: AppendOptions | string): void { + options = options || {}; + + // allow filename as single option + if (typeof options === 'string') { + options = {filename: options}; + } + + // 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 && value.hasOwnProperty('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; + } + + return super.getLengthSync(); + } +} + +export namespace HttpLink { + //TODO Would much rather be able to export directly + // tslint:disable-next-line: no-shadowed-variable + export interface Function extends UriFunction {} + export interface Options extends 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 import FetchOptions = HttpLink.Options; +export import UriFunction = HttpLink.Function; +import { Readable } from 'stream'; + +interface File { + createReadStream?: () => Readable; + filename?: string; + mimetype?: string; + name?: string; +} + +export const createServerHttpLink = (linkOptions: HttpLink.Options = {}) => { + let { + 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) { + (options as any).signal = signal; + } + } + + // If requested, set method to GET if there are no mutations. + const definitionIsMutation = (d: DefinitionNode) => { + return 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 => { + resolvePromises(body, async (object) => { + if (object instanceof Promise) { + object = await object; + } + return object; + }).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) { + 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) { + let serializedVariables; + try { + serializedVariables = serializeFetchParameter( + body.variables, + 'Variables map', + ); + } catch (parseError) { + return { parseError }; + } + addQueryParam('variables', serializedVariables); + } + if (body.extensions) { + 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 = '', + 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 }; +} + +async function resolvePromises(object: any, resolver: ((o: any) => any)): Promise { + if (!object) { + return object; + } + + if (resolver) { + object = await resolver(object); + } + + if (Array.isArray(object)) { + return object.map(async o => await resolvePromises(o, resolver)); + } else if (typeof object === 'object') { + const keys = Object.keys(object); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + object[key] = await resolvePromises(object[key], resolver); + } + return object; + } + + return object; +} + +function defaultSerializer( + body: any, + appendFile: (form: FormData, index: string, file: File) => void, +): any { + const { clone, files } = extractFiles(body, undefined, (value: any) => { + if ( + defaultIsExtractableFile(value) || + (value && value.createReadStream) + ) { + return true; + } + return false; + }); + + 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) { + 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?: HttpLink.Options) { + super(createServerHttpLink(opts).request); + } +} diff --git a/src/links/index.ts b/src/links/index.ts new file mode 100644 index 00000000000..c60c87c1bf4 --- /dev/null +++ b/src/links/index.ts @@ -0,0 +1,5 @@ +import { createServerHttpLink } from './createServerHttpLink'; + +export { + createServerHttpLink, +}; diff --git a/src/scalars/GraphQLUpload.ts b/src/scalars/GraphQLUpload.ts new file mode 100644 index 00000000000..e745e3de421 --- /dev/null +++ b/src/scalars/GraphQLUpload.ts @@ -0,0 +1,15 @@ +import { GraphQLScalarType } from 'graphql'; + +const GraphQLUpload = new GraphQLScalarType({ + name: 'Upload', + description: 'The `Upload` scalar type represents a file upload.', + parseValue: value => value, + parseLiteral: () => { + throw new Error('‘Upload’ scalar literal unsupported.'); + }, + serialize: value => value, +}); + +export { + GraphQLUpload, +}; diff --git a/src/scalars/index.ts b/src/scalars/index.ts new file mode 100644 index 00000000000..c65b605ccae --- /dev/null +++ b/src/scalars/index.ts @@ -0,0 +1,5 @@ +import { GraphQLUpload } from './GraphQLUpload'; + +export { + GraphQLUpload, +}; diff --git a/src/test/testUpload.ts b/src/test/testUpload.ts new file mode 100644 index 00000000000..2e5f104b76f --- /dev/null +++ b/src/test/testUpload.ts @@ -0,0 +1,149 @@ +/* tslint:disable:no-unused-expression */ + +import { expect } from 'chai'; + +import { Server } from 'http'; +import { AddressInfo } from 'net'; +import { Readable } from 'stream'; +import express, { Express } from 'express'; +import graphqlHTTP from 'express-graphql'; +import { graphqlUploadExpress } from 'graphql-upload'; +import FormData from 'form-data'; +import fetch from 'node-fetch'; +import { buildSchema } from 'graphql'; +import { SubschemaConfig } from '../Interfaces'; +import { createServerHttpLink } from '../links'; +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { GraphQLUpload } from '../scalars'; +import { mergeSchemas, delegateToSchema } from '../stitching'; + +function streamToString(stream: Readable) { + const chunks: Array = []; + return new Promise((resolve, reject) => { + stream.on('data', chunk => chunks.push(chunk)); + stream.on('error', reject); + stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); + }); +} + +function startServer(e: Express): Promise { + return new Promise((resolve, reject) => { + e.listen(undefined, 'localhost', function (error) { + if (error) { + reject(error); + } else { + resolve(this); + } + }); + }); +} + +function testGraphqlMultipartRequest(query: string, port: number) { + const body = new FormData(); + + body.append('operations', JSON.stringify({ + query, + variables: { + file: null, + }, + })); + body.append('map', '{ "1": ["variables.file"] }'); + body.append('1', 'abc', { filename: __filename }); + + return fetch(`http://localhost:${port}`, { method: 'POST', body }); +} + +describe('graphql upload', () => { + it('should return a file after uploading one', async () => { + const remoteSchema = makeExecutableSchema({ + typeDefs: ` + scalar Upload + type Query { + version: String + } + type Mutation { + upload(file: Upload): String + } + `, + resolvers: { + Mutation: { + upload: async (root, { file }) => { + const { createReadStream } = await file; + const stream = createReadStream(); + const s = await streamToString(stream); + return s; + } + }, + Upload: GraphQLUpload, + }, + }); + + const remoteApp = express().use( + graphqlUploadExpress(), + graphqlHTTP({ schema: remoteSchema }), + ); + + const remoteServer = await startServer(remoteApp); + const remotePort = (remoteServer.address() as AddressInfo).port; + + const nonExecutableSchema = buildSchema(` + scalar Upload + type Query { + version: String + } + type Mutation { + upload(file: Upload): String + } + `); + + const subSchema: SubschemaConfig = { + schema: nonExecutableSchema, + link: createServerHttpLink({ + uri: `http://localhost:${remotePort}`, + }), + }; + + const gatewaySchema = mergeSchemas({ + schemas: [subSchema], + resolvers: { + Mutation: { + upload: async (root, args, context, info) => { + const result = await delegateToSchema({ + schema: subSchema, + operation: 'mutation', + fieldName: 'upload', + args, + context, + info, + }); + return result; + } + }, + Upload: GraphQLUpload, + }, + }); + + const gatewayApp = express().use( + graphqlUploadExpress(), + graphqlHTTP({ schema: gatewaySchema }), + ); + + const gatewayServer = await startServer(gatewayApp); + const gatewayPort = (gatewayServer.address() as AddressInfo).port; + const query = ` + mutation upload($file: Upload!) { + upload(file: $file) + } + `; + const res = await testGraphqlMultipartRequest(query, gatewayPort); + + expect(await res.json()).to.deep.equal({ + data: { + upload: 'abc', + }, + }); + + remoteServer.close(); + gatewayServer.close(); + }); +}); diff --git a/src/test/tests.ts b/src/test/tests.ts index 7febbb10f6f..7cf01779f52 100755 --- a/src/test/tests.ts +++ b/src/test/tests.ts @@ -14,3 +14,4 @@ import './testSchemaGenerator'; import './testTransforms'; import './testExtensionExtraction'; import './testIntegration'; +import './testUpload'; diff --git a/tsconfig.json b/tsconfig.json index 767de85424b..0e73e0a674b 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "outDir": "./dist", "removeComments": false, "noImplicitUseStrict": true, + "esModuleInterop": true, }, "exclude": ["node_modules", "dist"] } diff --git a/typings.d.ts b/typings.d.ts index e69de29bb2d..e2b0b1d1555 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -0,0 +1 @@ +declare module 'extract-files' From 6252261a7b9ba991606f2f30333a129ab296dc00 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 30 Dec 2019 23:26:31 -0500 Subject: [PATCH 130/250] chore(changelog): update vNext --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fd04ec70d2..b3c43bdd77e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ ### Next +#### Features + +* Adds [graphql-upload](https://github.com/jaydenseric/graphql-upload) compatible scalar and link for proxying remote file uploads #671 +* Add ability to merge fields from types from different schemas +* Adds transforms to wrap, extract, and rename fields #1183 +* Adds transform to filter object fields #819 +* Exports visitSchema, SchemaVisitor, healSchema, healTypes, cloneSchema, cloneType, cloneDirective to enable more custom transforms. #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 + +#### 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, #1056, #1200 +* Allow renaming of subscription root fields #997, #1002 +* Fix alias resolution to no longer incorrectly fallback to non-aliased field when null #1171 +* Do not remove default directives (skip, include, deprecated) when not merging custom directives #1159 +* Fixes errors support #743, #1037, #1046 +* Fix mergeSchemas to allow resolvers to return fields defined as functions #1061 +* Fix default values with mergeSchemas and addResolveFunctionsToSchema #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) From d13bd2bf9353054ee75f810a51f5b915390bb8d2 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 30 Dec 2019 23:28:37 -0500 Subject: [PATCH 131/250] lint --- src/generate/concatenateTypeDefs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generate/concatenateTypeDefs.ts b/src/generate/concatenateTypeDefs.ts index b80f765443d..96e046d928d 100644 --- a/src/generate/concatenateTypeDefs.ts +++ b/src/generate/concatenateTypeDefs.ts @@ -1,4 +1,4 @@ -import { print, DocumentNode, ASTNode } from 'graphql'; +import { print, DocumentNode } from 'graphql'; import { ITypedef } from '../Interfaces'; import SchemaError from './SchemaError'; From 00925ccadf746f340ed14130aeb9fa15c03b439c Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 31 Dec 2019 07:38:22 -0500 Subject: [PATCH 132/250] refactor(tests): group tests wrapping fields --- src/test/testAlternateMergeSchemas.ts | 274 ++++++++++++-------------- 1 file changed, 130 insertions(+), 144 deletions(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 17838e83734..7b3c11b55f2 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -927,10 +927,8 @@ describe('schema transformation with extraction of nested fields', () => { }); describe('schema transformation with wrapping of object fields', () => { - let transformedPropertySchema: GraphQLSchema; - - before(async () => { - transformedPropertySchema = transformSchema(propertySchema, [ + it('should work via ExtendSchema transform', async () => { + const transformedPropertySchema = transformSchema(propertySchema, [ new ExtendSchema({ typeDefs: ` extend type Property { @@ -964,9 +962,7 @@ describe('schema transformation with wrapping of object fields', () => { }, }), ]); - }); - it('should work, even with aliases', async () => { const result = await graphql( transformedPropertySchema, ` @@ -1033,161 +1029,151 @@ describe('schema transformation with wrapping of object fields', () => { }] }); }); -}); - -describe('WrapFields transform', () => { - let transformedPropertySchema: GraphQLSchema; - - before(async () => { - transformedPropertySchema = transformSchema(propertySchema, [ - new WrapFields( - 'Property', - ['outerWrap'], - ['OuterWrap'], - ['id', 'name', 'error'], - ), - ]); - }); - it('should work, even with aliases', async () => { - const result = await graphql( - transformedPropertySchema, - ` - query($pid: ID!) { - propertyById(id: $pid) { - test1: outerWrap { - ...W1 - } - test2: outerWrap { - ...W2 + describe('WrapFields transform', () => { + it('should work', async () => { + const transformedPropertySchema = transformSchema(propertySchema, [ + new WrapFields( + 'Property', + ['outerWrap'], + ['OuterWrap'], + ['id', 'name', 'error'], + ), + ]); + + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + test1: outerWrap { + ...W1 + } + test2: outerWrap { + ...W2 + } } } - } - fragment W1 on OuterWrap { - one: id - two: error - } - fragment W2 on OuterWrap { - one: name - } - `, - {}, - {}, - { - pid: 'p1', - }, - ); + fragment W1 on OuterWrap { + one: id + two: error + } + fragment W2 on OuterWrap { + one: name + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); - expect(result).to.deep.equal({ - data: { - propertyById: { - test1: { - one: 'p1', - two: null, - }, - test2: { - one: 'Super great hotel', + expect(result).to.deep.equal({ + data: { + propertyById: { + test1: { + one: 'p1', + two: null, + }, + test2: { + one: 'Super great hotel', + }, }, }, - }, - 'errors': [{ - 'extensions': { - code: 'SOME_CUSTOM_CODE' - }, - 'locations': [{ - column: 11, - line: 14 - }], - message: 'Property.error error', - path: [ - 'propertyById', - 'test1', - 'two', - ], - }] + 'errors': [{ + 'extensions': { + code: 'SOME_CUSTOM_CODE' + }, + 'locations': [{ + column: 13, + line: 14 + }], + message: 'Property.error error', + path: [ + 'propertyById', + 'test1', + 'two', + ], + }] + }); }); - }); -}); - -describe('WrapFields transform with multiple fields', () => { - let transformedPropertySchema: GraphQLSchema; - - before(async () => { - transformedPropertySchema = transformSchema(propertySchema, [ - new WrapFields( - 'Property', - ['outerWrap', 'innerWrap'], - ['OuterWrap', 'InnerWrap'], - ['id', 'name', 'error'], - ), - ]); - }); - it('should work, even with aliases', async () => { - const result = await graphql( - transformedPropertySchema, - ` - query($pid: ID!) { - propertyById(id: $pid) { - test1: outerWrap { - innerWrap { - ...W1 + it('should work, even with multiple fields', async () => { + const transformedPropertySchema = transformSchema(propertySchema, [ + new WrapFields( + 'Property', + ['outerWrap', 'innerWrap'], + ['OuterWrap', 'InnerWrap'], + ['id', 'name', 'error'], + ), + ]); + + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + test1: outerWrap { + innerWrap { + ...W1 + } } - } - test2: outerWrap { - innerWrap { - ...W2 + test2: outerWrap { + innerWrap { + ...W2 + } } } } - } - fragment W1 on InnerWrap { - one: id - two: error - } - fragment W2 on InnerWrap { - one: name - } - `, - {}, - {}, - { - pid: 'p1', - }, - ); + fragment W1 on InnerWrap { + one: id + two: error + } + fragment W2 on InnerWrap { + one: name + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); - expect(result).to.deep.equal({ - data: { - propertyById: { - test1: { - innerWrap: { - one: 'p1', - two: null, + expect(result).to.deep.equal({ + data: { + propertyById: { + test1: { + innerWrap: { + one: 'p1', + two: null, + }, }, - }, - test2: { - innerWrap: { - one: 'Super great hotel', + test2: { + innerWrap: { + one: 'Super great hotel', + }, }, }, }, - }, - 'errors': [{ - 'extensions': { - code: 'SOME_CUSTOM_CODE' - }, - 'locations': [{ - column: 11, - line: 18 - }], - message: 'Property.error error', - path: [ - 'propertyById', - 'test1', - 'innerWrap', - 'two', - ], - }] + 'errors': [{ + 'extensions': { + code: 'SOME_CUSTOM_CODE' + }, + 'locations': [{ + column: 13, + line: 18 + }], + message: 'Property.error error', + path: [ + 'propertyById', + 'test1', + 'innerWrap', + 'two', + ], + }] + }); }); }); }); From 472ad972183a18af789d118b70d6fb2a5608a4a2 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 2 Jan 2020 10:30:51 -0500 Subject: [PATCH 133/250] refactor(FilterObjectFields) questionable improvement in brevity vs clarity --- src/transforms/FilterObjectFields.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/transforms/FilterObjectFields.ts b/src/transforms/FilterObjectFields.ts index cd0ca806f94..2511ea9b9a5 100644 --- a/src/transforms/FilterObjectFields.ts +++ b/src/transforms/FilterObjectFields.ts @@ -9,13 +9,8 @@ export default class FilterObjectFields implements Transform { constructor(filter: ObjectFilter) { this.transformer = new TransformObjectFields( - (typeName: string, fieldName: string, field: GraphQLField) => { - if (filter(typeName, fieldName, field)) { - return undefined; - } else { - return null; - } - } + (typeName: string, fieldName: string, field: GraphQLField) => + filter(typeName, fieldName, field) ? undefined : null ); } From 0d4e8eb0e9e127103f5d2e0b72b443d7c0479ff9 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 2 Jan 2020 14:26:53 -0500 Subject: [PATCH 134/250] fix(TransformObjectFields/MapFields): allow more transformations --- src/transforms/MapFields.ts | 2 +- src/transforms/TransformObjectFields.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transforms/MapFields.ts b/src/transforms/MapFields.ts index 5f858e96448..3575d6832dc 100644 --- a/src/transforms/MapFields.ts +++ b/src/transforms/MapFields.ts @@ -92,7 +92,7 @@ function transformDocument( } else if (transformedSelection.kind === Kind.FIELD) { newSelections.push(transformedSelection); } else { - newSelections.push(selection); + newSelections.push(transformedSelection); } } else { newSelections.push(selection); diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index 06e5cc6027e..ebc425f8037 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -177,7 +177,7 @@ export default class TransformObjectFields implements Transform { } }); } else { - newSelections.push(selection); + newSelections.push(transformedSelection); } } else { newSelections.push(selection); From 4483f00fa3c719c31e9b200cd9a5be058c702357 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 2 Jan 2020 10:31:13 -0500 Subject: [PATCH 135/250] refactor(TransformObjectFields): do not modify info object --- src/stitching/createMergedResolver.ts | 9 +------ src/test/testAlternateMergeSchemas.ts | 20 ++++++++++++++++ src/transforms/TransformObjectFields.ts | 32 ++++++++++++------------- src/utils/fieldNodes.ts | 8 +++++-- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 53240d9234d..2d24a15fac9 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -11,17 +11,10 @@ export function createMergedResolver({ fromField?: string; dehoist?: boolean | string; }): IFieldResolver { - const fromFieldResolver: IFieldResolver = fromField ? - (parent, args, context, info) => defaultMergedResolver(parent, args, context, { - ...info, - fieldName: fromField, - }) : - defaultMergedResolver; - const parentErrorResolver: IFieldResolver = (parent, args, context, info) => parent instanceof Error ? parent : - fromFieldResolver(parent, args, context, info); + defaultMergedResolver(parent, args, context, info); const unwrappingResolver: IFieldResolver = fromPath ? (parent, args, context, info) => diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 7b3c11b55f2..b368dbc6cd4 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -568,6 +568,7 @@ type Query { new_location { name } + new_error } } `, @@ -586,8 +587,27 @@ type Query { new_location: { name: 'Helsinki', }, + new_error: null, }, }, + errors: [ + { + extensions: { + code: 'SOME_CUSTOM_CODE', + }, + locations: [ + { + column: 13, + line: 9, + }, + ], + message: 'Property.error error', + path: [ + 'propertyById', + 'new_error', + ], + }, + ], }); }); }); diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index ebc425f8037..cb3c622a291 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -115,13 +115,6 @@ export default class TransformObjectFields implements Transform { this.mapping[typeName] = {}; } this.mapping[typeName][newName] = fieldName; - - const originalResolver = newFields[newName].resolve; - (newFields[newName] as GraphQLFieldConfig).resolve = (parent, args, context, info) => - originalResolver(parent, args, context, { - ...info, - fieldName - }); } } else { newFields[fieldName] = transformedField; @@ -165,17 +158,22 @@ export default class TransformObjectFields implements Transform { if (Array.isArray(transformedSelection)) { newSelections = newSelections.concat(transformedSelection); } else if (transformedSelection.kind === Kind.FIELD) { - let originalName; - if (mapping[parentTypeName]) { - originalName = mapping[parentTypeName][newName]; + const oldName = mapping[parentTypeName] && mapping[parentTypeName][newName]; + if (oldName) { + newSelections.push({ + ...transformedSelection, + name: { + kind: Kind.NAME, + value: oldName + }, + alias: { + kind: Kind.NAME, + value: newName + } + }); + } else { + newSelections.push(transformedSelection); } - newSelections.push({ - ...transformedSelection, - name: { - ...transformedSelection.name, - value: originalName || transformedSelection.name.value - } - }); } else { newSelections.push(transformedSelection); } diff --git a/src/utils/fieldNodes.ts b/src/utils/fieldNodes.ts index 85a7f49d557..c56afa56e3b 100644 --- a/src/utils/fieldNodes.ts +++ b/src/utils/fieldNodes.ts @@ -8,8 +8,12 @@ import { export function renameFieldNode(fieldNode: FieldNode, name: string): FieldNode { return { ...fieldNode, + alias: { + kind: Kind.NAME, + value: fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value, + }, name: { - ...fieldNode.name, + kind: Kind.NAME, value: name, } }; @@ -19,7 +23,7 @@ export function preAliasFieldNode(fieldNode: FieldNode, str: string): FieldNode return { ...fieldNode, alias: { - ...fieldNode.name, + kind: Kind.NAME, value: `${str}${fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value}`, } }; From 348cc1b1fc150bb416eab1ff65eba98a7ddc6f3f Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 1 Jan 2020 23:56:31 -0500 Subject: [PATCH 136/250] refactor(RenameTypes): simplify recursive method --- src/transforms/RenameTypes.ts | 60 +++++++++++++++-------------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index ab1bd5e180b..4eab6af5b67 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -18,6 +18,7 @@ export type RenameOptions = { export default class RenameTypes implements Transform { private renamer: (name: string) => string | undefined; + private map: { [key: string]: string }; private reverseMap: { [key: string]: string }; private renameBuiltins: boolean; private renameScalars: boolean; @@ -27,6 +28,7 @@ export default class RenameTypes implements Transform { options?: RenameOptions, ) { this.renamer = renamer; + this.map = {}; this.reverseMap = {}; const { renameBuiltins = false, renameScalars = true } = options || {}; this.renameBuiltins = renameBuiltins; @@ -42,9 +44,11 @@ export default class RenameTypes implements Transform { if (type instanceof GraphQLScalarType && !this.renameScalars) { return undefined; } - const newName = this.renamer(type.name); - if (newName && newName !== type.name) { - this.reverseMap[newName] = type.name; + const oldName = type.name; + const newName = this.renamer(oldName); + if (newName && newName !== oldName) { + this.map[oldName] = type.name; + this.reverseMap[newName] = oldName; const newType = cloneType(type); newType.name = newName; return newType; @@ -79,42 +83,28 @@ export default class RenameTypes implements Transform { } public transformResult(result: Result): Result { - if (result.data) { - const data = this.renameTypes(result.data, 'data'); - if (data !== result.data) { - return { ...result, data }; - } - } - - return result; + return { + ...result, + data: this.renameTypes(result.data) + }; } - private renameTypes(value: any, name?: string) { - if (name === '__typename') { - return this.renamer(value); - } - - if (value && typeof value === 'object') { - const newValue = Array.isArray(value) ? [] - // Create a new object with the same prototype. - : Object.create(Object.getPrototypeOf(value)); - - let returnNewValue = false; - + private renameTypes(value: any): any { + if (value == null) { + return value; + } else if (Array.isArray(value)) { + value.forEach((v, index) => { + value[index] = this.renameTypes(v); + }); + return value; + } else if (typeof value === 'object') { Object.keys(value).forEach(key => { - const oldChild = value[key]; - const newChild = this.renameTypes(oldChild, key); - newValue[key] = newChild; - if (newChild !== oldChild) { - returnNewValue = true; - } + value[key] = key === '__typename' ? + this.renamer(value[key]) : this.renameTypes(value[key]); }); - - if (returnNewValue) { - return newValue; - } + return value; + } else { + return value; } - - return value; } } From dffc7eb2274e3220f4b4265dc57037eee8665d48 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 4 Jan 2020 22:13:03 -0500 Subject: [PATCH 137/250] fix(wrapping): wrapped field names must be unique. ...so they can be combined from multiple schemas. This change also adds use of a delimeter when wrapping fields and fixes specification of the delimeter more broadly, which was not set up appropriately within the dehoist property. The delimeter can now be specified when using the WrapFields and WrapType transforms. This change also removes the now unnecessary fromField property, as renaming no longer requires a dedicated resolver now that it just uses aliases. --- src/stitching/createMergedResolver.ts | 11 +++++---- src/stitching/proxiedResult.ts | 32 ++++++++++++++++++--------- src/test/testAlternateMergeSchemas.ts | 8 +------ src/transforms/WrapFields.ts | 6 ++++- src/transforms/WrapType.ts | 11 +++++++-- src/utils/fieldNodes.ts | 13 ++++++++--- 6 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 2d24a15fac9..39552b2c03c 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -4,24 +4,23 @@ import defaultMergedResolver from './defaultMergedResolver'; export function createMergedResolver({ fromPath, - fromField, dehoist, + delimeter = '__gqltf__', }: { fromPath?: Array; - fromField?: string; - dehoist?: boolean | string; + 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 ? + const unwrappingResolver: IFieldResolver = fromPath && fromPath.length ? (parent, args, context, info) => - parentErrorResolver(unwrapResult(parent, info, fromPath), args, context, info) : + parentErrorResolver(unwrapResult(parent, info, fromPath, delimeter), args, context, info) : parentErrorResolver; - const delimeter = dehoist === 'string' ? dehoist : '__gqltf__'; const dehoistingResolver: IFieldResolver = dehoist ? (parent, args, context, info) => unwrappingResolver(dehoistResult(parent, delimeter), args, context, info) : diff --git a/src/stitching/proxiedResult.ts b/src/stitching/proxiedResult.ts index 1a384c43763..e01909ebf64 100644 --- a/src/stitching/proxiedResult.ts +++ b/src/stitching/proxiedResult.ts @@ -60,12 +60,16 @@ export function getErrors( export function unwrapResult( parent: any, info: IGraphQLToolsResolveInfo, - path: Array = [] + path: Array, + delimeter: string = '__gqltf__', ): any { - const pathLength = path.length; + let responseKey = Object.keys(parent).find(key => { + const splitKey = key.split(delimeter); + return (splitKey.length === 3 && splitKey[0] === 'wrapped' && splitKey[2] === path[0]); + }); + const pathLength = path.length; for (let i = 0; i < pathLength; i++) { - const responseKey = path[i]; const errors = getErrors(parent, responseKey); const subschemas = getSubschemas(parent); @@ -74,24 +78,27 @@ export function unwrapResult( return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors); } parent = handleObject(result, errors, subschemas); + responseKey = path[i + 1]; } return parent; } -export function dehoistResult(parent: any, delimeter: string): any { +export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): any { const result = Object.create(null); Object.keys(parent).forEach(alias => { let obj = result; const fieldNames = alias.split(delimeter); - const fieldName = fieldNames.pop(); - fieldNames.forEach(key => { - obj = obj[key] = obj[key] || Object.create(null); - }); - obj[fieldName] = parent[alias]; - + const prefix = fieldNames.shift(); + if (prefix === 'hoisted') { + const fieldName = fieldNames.pop(); + fieldNames.forEach(key => { + obj = obj[key] = obj[key] || Object.create(null); + }); + obj[fieldName] = parent[alias]; + } }); result[ERROR_SYMBOL] = parent[ERROR_SYMBOL].map((error: GraphQLError) => { @@ -99,7 +106,10 @@ export function dehoistResult(parent: any, delimeter: string): any { let path = error.path.slice(); const pathSegment = path.shift(); const expandedPathSegment: Array = (pathSegment as string).split(delimeter); - return relocatedError(error, error.nodes, expandedPathSegment.concat(path)); + const prefix = expandedPathSegment.shift(); + return (prefix === 'hoisted') ? + relocatedError(error, error.nodes, expandedPathSegment.concat(path)) : + error; } else { return error; } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index b368dbc6cd4..84d217c6a92 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -880,8 +880,7 @@ describe('schema transformation with extraction of nested fields', () => { `, resolvers: { Property: { - locationName: createMergedResolver({ fromPath: ['location'], fromField: 'name' }), - renamedError: createMergedResolver({ fromField: 'error' }), + locationName: createMergedResolver({ fromPath: ['location'] }), }, }, fieldNodeTransformerMap: { @@ -1209,11 +1208,6 @@ describe('schema transformation with renaming of object fields', () => { new_error: String } `, - resolvers: { - Property: { - new_error: createMergedResolver({ fromField: 'error' }), - }, - }, fieldNodeTransformerMap: { 'Property': { 'new_error': fieldNode => renameFieldNode(fieldNode, 'error'), diff --git a/src/transforms/WrapFields.ts b/src/transforms/WrapFields.ts index 3d63d4b6805..63fbbfacd58 100644 --- a/src/transforms/WrapFields.ts +++ b/src/transforms/WrapFields.ts @@ -20,6 +20,7 @@ export default class WrapFields implements Transform { private wrappingTypeNames: Array; private numWraps: number; private fieldNames: Array; + private delimeter: string; private transformer: Transform; constructor( @@ -27,12 +28,14 @@ export default class WrapFields implements Transform { wrappingFieldNames: Array, wrappingTypeNames: Array, fieldNames?: Array, + delimeter: string = '__gqltf__', ) { this.outerTypeName = outerTypeName; this.wrappingFieldNames = wrappingFieldNames; this.wrappingTypeNames = wrappingTypeNames; this.numWraps = wrappingFieldNames.length; this.fieldNames = fieldNames; + this.delimeter = delimeter; const remainingWrappingFieldNames = this.wrappingFieldNames.slice(); const outerMostWrappingFieldName = remainingWrappingFieldNames.shift(); @@ -43,6 +46,7 @@ export default class WrapFields implements Transform { path: remainingWrappingFieldNames, fieldNames: this.fieldNames, fragments, + delimeter: this.delimeter, }), }, }); @@ -74,7 +78,7 @@ export default class WrapFields implements Transform { this.appendFields(typeMap, this.outerTypeName, { [this.wrappingFieldNames[0]]: { type: typeMap[this.wrappingTypeNames[0]] as GraphQLObjectType, - resolve: createMergedResolver({ dehoist: true }), + resolve: createMergedResolver({ dehoist: true, delimeter: this.delimeter }), }, }); diff --git a/src/transforms/WrapType.ts b/src/transforms/WrapType.ts index d2d2113d5ef..91a4f8c1c11 100644 --- a/src/transforms/WrapType.ts +++ b/src/transforms/WrapType.ts @@ -11,9 +11,16 @@ export default class WrapType implements Transform { constructor( outerTypeName: string, innerTypeName: string, - fieldName: string + fieldName: string, + delimeter: string = '__gqltf__', ) { - this.transformer = new WrapFields(outerTypeName, [fieldName], [innerTypeName]); + this.transformer = new WrapFields( + outerTypeName, + [fieldName], + [innerTypeName], + undefined, + delimeter + ); } public transformSchema(schema: GraphQLSchema): GraphQLSchema { diff --git a/src/utils/fieldNodes.ts b/src/utils/fieldNodes.ts index c56afa56e3b..f8532b4db32 100644 --- a/src/utils/fieldNodes.ts +++ b/src/utils/fieldNodes.ts @@ -29,7 +29,13 @@ export function preAliasFieldNode(fieldNode: FieldNode, str: string): FieldNode }; } -export function wrapFieldNode(fieldNode: FieldNode, path: Array): FieldNode { +export function wrapFieldNode( + fieldNode: FieldNode, + path: Array, + delimeter: string = '__gqltf__', +): FieldNode { + const alias = fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value; + let newFieldNode = fieldNode; path.forEach(fieldName => { newFieldNode = { @@ -46,7 +52,8 @@ export function wrapFieldNode(fieldNode: FieldNode, path: Array): FieldN } }; }); - return newFieldNode; + + return preAliasFieldNode(newFieldNode, `wrapped${delimeter}${alias}__gqltf__`); } export function collectFields( @@ -122,7 +129,7 @@ export function hoistFieldNodes({ } else { collectFields(fieldNode.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { if (!fieldNames || fieldNames.includes(possibleFieldNode.name.value)) { - newFieldNodes.push(preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`)); + newFieldNodes.push(preAliasFieldNode(possibleFieldNode, `hoisted${delimeter}${alias}${delimeter}`)); } }); } From 898d421e43da36b446db75e8eeef8cf46280bd93 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 6 Jan 2020 12:05:45 -0500 Subject: [PATCH 138/250] feat(transforms): add HoistField transform New HoistField transform allows moving a field on a remote type into a higher level object within the wrapping schema. The appendFields and filterFields methods from the WrapFIelds transform have been moved to the utils directory and exported; filterFields has been renamed to removeFields to better describe what it does. --- src/test/testAlternateMergeSchemas.ts | 39 +++++++++++-- src/transforms/HoistField.ts | 80 +++++++++++++++++++++++++++ src/transforms/WrapFields.ts | 68 ++--------------------- src/transforms/index.ts | 1 + src/utils/fields.ts | 62 +++++++++++++++++++++ src/utils/index.ts | 1 + 6 files changed, 182 insertions(+), 69 deletions(-) create mode 100644 src/transforms/HoistField.ts create mode 100644 src/utils/fields.ts diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 84d217c6a92..d082f1ff4d3 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -23,6 +23,7 @@ import { ExtendSchema, WrapType, WrapFields, + HoistField, FilterRootFields, FilterObjectFields, } from '../transforms'; @@ -867,10 +868,8 @@ type Wrap { }); describe('schema transformation with extraction of nested fields', () => { - let transformedPropertySchema: GraphQLSchema; - - before(async () => { - transformedPropertySchema = transformSchema(propertySchema, [ + it('should work via ExtendSchema transform', async () => { + const transformedPropertySchema = transformSchema(propertySchema, [ new ExtendSchema({ typeDefs: ` extend type Property { @@ -892,9 +891,7 @@ describe('schema transformation with extraction of nested fields', () => { }, }), ]); - }); - it('should work to extract a field', async () => { const result = await graphql( transformedPropertySchema, ` @@ -943,6 +940,36 @@ describe('schema transformation with extraction of nested fields', () => { ] }); }); + + it('should work via HoistField transform', async () => { + const transformedPropertySchema = transformSchema(propertySchema, [ + new HoistField('Property', ['location', 'name'], 'locationName'), + ]); + + const result = await graphql( + transformedPropertySchema, + ` + query($pid: ID!) { + propertyById(id: $pid) { + test: locationName + } + } + `, + {}, + {}, + { + pid: 'p1', + }, + ); + + expect(result).to.deep.equal({ + data: { + propertyById: { + test: 'Helsinki', + }, + }, + }); + }); }); describe('schema transformation with wrapping of object fields', () => { diff --git a/src/transforms/HoistField.ts b/src/transforms/HoistField.ts new file mode 100644 index 00000000000..028ea5da6f2 --- /dev/null +++ b/src/transforms/HoistField.ts @@ -0,0 +1,80 @@ +/* tslint:disable:no-unused-expression */ + +import { + GraphQLSchema, + GraphQLObjectType, + getNullableType, +} from 'graphql'; +import { Request } from '../Interfaces'; +import { Transform } from './transforms'; +import { healSchema, wrapFieldNode, renameFieldNode } from '../utils'; +import { createMergedResolver } from '../stitching'; +import { default as MapFields } from './MapFields'; +import { appendFields, removeFields } from '../utils/fields'; + +export default class HoistField implements Transform { + private typeName: string; + private path: Array; + private newFieldName: string; + private pathToField: Array; + private oldFieldName: string; + private delimeter: string; + private transformer: Transform; + + constructor( + typeName: string, + path: Array, + newFieldName: string, + delimeter: string = '__gqltf__', + ) { + this.typeName = typeName; + this.path = path; + this.newFieldName = newFieldName; + this.delimeter = delimeter; + + this.pathToField = this.path.slice(); + this.oldFieldName = this.pathToField.pop(); + this.transformer = new MapFields({ + [typeName]: { + [newFieldName]: fieldNode => wrapFieldNode( + renameFieldNode(fieldNode, this.oldFieldName), + this.pathToField, + this.delimeter + ), + }, + }); + } + + public transformSchema(schema: GraphQLSchema): GraphQLSchema { + const typeMap = schema.getTypeMap(); + + const innerType: GraphQLObjectType = this.pathToField.reduce( + (acc, pathSegment) => + getNullableType(acc.getFields()[pathSegment].type) as GraphQLObjectType, + typeMap[this.typeName] as GraphQLObjectType + ); + + const targetField = removeFields( + typeMap, + innerType.name, + fieldName => fieldName === this.oldFieldName, + )[this.oldFieldName]; + + const targetType = (targetField.type as GraphQLObjectType); + + appendFields(typeMap, this.typeName, { + [this.newFieldName]: { + type: targetType, + resolve: createMergedResolver({ fromPath: this.pathToField, delimeter: this.delimeter }), + }, + }); + + healSchema(schema); + + return this.transformer.transformSchema(schema); + } + + public transformRequest(originalRequest: Request): Request { + return this.transformer.transformRequest(originalRequest); + } +} diff --git a/src/transforms/WrapFields.ts b/src/transforms/WrapFields.ts index 63fbbfacd58..10bbdeea4c4 100644 --- a/src/transforms/WrapFields.ts +++ b/src/transforms/WrapFields.ts @@ -3,16 +3,13 @@ import { GraphQLSchema, GraphQLObjectType, - GraphQLObjectTypeConfig, - GraphQLFieldConfigMap, - GraphQLFieldConfig, } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; import { hoistFieldNodes, healSchema } from '../utils'; import { defaultMergedResolver, createMergedResolver } from '../stitching'; import { default as MapFields } from './MapFields'; -import { TypeMap } from 'graphql/type/schema'; +import { appendFields, removeFields } from '../utils/fields'; export default class WrapFields implements Transform { private outerTypeName: string; @@ -55,7 +52,7 @@ export default class WrapFields implements Transform { public transformSchema(schema: GraphQLSchema): GraphQLSchema { const typeMap = schema.getTypeMap(); - const targetFields = this.filterFields( + const targetFields = removeFields( typeMap, this.outerTypeName, !this.fieldNames ? () => true : fieldName => this.fieldNames.includes(fieldName) @@ -64,10 +61,10 @@ export default class WrapFields implements Transform { let wrapIndex = this.numWraps - 1; const innerMostWrappingTypeName = this.wrappingTypeNames[wrapIndex]; - this.appendFields(typeMap, innerMostWrappingTypeName, targetFields); + appendFields(typeMap, innerMostWrappingTypeName, targetFields); for (wrapIndex--; wrapIndex > -1; wrapIndex--) { - this.appendFields(typeMap, this.wrappingTypeNames[wrapIndex], { + appendFields(typeMap, this.wrappingTypeNames[wrapIndex], { [this.wrappingFieldNames[wrapIndex + 1]]: { type: typeMap[this.wrappingTypeNames[wrapIndex + 1]] as GraphQLObjectType, resolve: defaultMergedResolver, @@ -75,7 +72,7 @@ export default class WrapFields implements Transform { }); } - this.appendFields(typeMap, this.outerTypeName, { + appendFields(typeMap, this.outerTypeName, { [this.wrappingFieldNames[0]]: { type: typeMap[this.wrappingTypeNames[0]] as GraphQLObjectType, resolve: createMergedResolver({ dehoist: true, delimeter: this.delimeter }), @@ -90,59 +87,4 @@ export default class WrapFields implements Transform { public transformRequest(originalRequest: Request): Request { return this.transformer.transformRequest(originalRequest); } - - private appendFields( - typeMap: TypeMap, - typeName: string, - fields: GraphQLFieldConfigMap, - ) { - let type = typeMap[typeName]; - if (type) { - const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; - const originalFields = typeConfig.fields; - const newFields = {}; - Object.keys(originalFields).forEach(fieldName => { - newFields[fieldName] = originalFields[fieldName]; - }); - Object.keys(fields).forEach(fieldName => { - newFields[fieldName] = fields[fieldName]; - }); - type = new GraphQLObjectType({ - ...typeConfig, - fields: newFields, - }); - } else { - type = new GraphQLObjectType({ - name: typeName, - fields, - }); - } - typeMap[typeName] = type; - } - - private filterFields( - typeMap: TypeMap, - typeName: string, - filter: (fieldName: string, field: GraphQLFieldConfig) => boolean, - ): GraphQLFieldConfigMap { - let type = typeMap[typeName]; - const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; - const originalFields = typeConfig.fields; - const newFields = {}; - const filteredFields = {}; - Object.keys(originalFields).forEach(fieldName => { - if (filter(fieldName, originalFields[fieldName])) { - filteredFields[fieldName] = originalFields[fieldName]; - } else { - newFields[fieldName] = originalFields[fieldName]; - } - }); - type = new GraphQLObjectType({ - ...typeConfig, - fields: newFields, - }); - typeMap[typeName] = type; - - return filteredFields; - } } diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 2ab795023e5..0f714300df2 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -27,5 +27,6 @@ export { default as TransformQuery } from './TransformQuery'; export { default as ExtendSchema } from './ExtendSchema'; export { default as WrapType } from './WrapType'; export { default as WrapFields } from './WrapFields'; +export { default as HoistField } from './HoistField'; export { default as MapFields } from './MapFields'; export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; diff --git a/src/utils/fields.ts b/src/utils/fields.ts new file mode 100644 index 00000000000..f9b0268b122 --- /dev/null +++ b/src/utils/fields.ts @@ -0,0 +1,62 @@ +import { + GraphQLFieldConfigMap, + GraphQLObjectTypeConfig, + GraphQLObjectType, + GraphQLFieldConfig, +} from 'graphql'; +import { TypeMap } from 'graphql/type/schema'; + +export function appendFields( + typeMap: TypeMap, + typeName: string, + fields: GraphQLFieldConfigMap, +): void { + let type = typeMap[typeName]; + if (type) { + const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; + const originalFields = typeConfig.fields; + const newFields = {}; + Object.keys(originalFields).forEach(fieldName => { + newFields[fieldName] = originalFields[fieldName]; + }); + Object.keys(fields).forEach(fieldName => { + newFields[fieldName] = fields[fieldName]; + }); + type = new GraphQLObjectType({ + ...typeConfig, + fields: newFields, + }); + } else { + type = new GraphQLObjectType({ + name: typeName, + fields, + }); + } + typeMap[typeName] = type; +} + +export function removeFields( + typeMap: TypeMap, + typeName: string, + testFn: (fieldName: string, field: GraphQLFieldConfig) => boolean, +): GraphQLFieldConfigMap { + let type = typeMap[typeName]; + const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; + const originalFields = typeConfig.fields; + const newFields = {}; + const removedFields = {}; + Object.keys(originalFields).forEach(fieldName => { + if (testFn(fieldName, originalFields[fieldName])) { + removedFields[fieldName] = originalFields[fieldName]; + } else { + newFields[fieldName] = originalFields[fieldName]; + } + }); + type = new GraphQLObjectType({ + ...typeConfig, + fields: newFields, + }); + typeMap[typeName] = type; + + return removedFields; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 9b66237a785..1fe3920c316 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -20,3 +20,4 @@ export { renameFieldNode, hoistFieldNodes, } from './fieldNodes'; +export { appendFields, removeFields } from './fields'; From 144548f6a8c5120d0902209075ea838df3002b4c Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 6 Jan 2020 12:38:59 -0500 Subject: [PATCH 139/250] refactor(addResolveFunctionsToSchema) remove function hiding type checks --- src/generate/addResolveFunctionsToSchema.ts | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolveFunctionsToSchema.ts index 1b0dc06a37b..0a91cb96dbe 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolveFunctionsToSchema.ts @@ -2,11 +2,9 @@ import { GraphQLField, GraphQLEnumType, GraphQLScalarType, - GraphQLType, GraphQLSchema, GraphQLObjectType, GraphQLInterfaceType, - GraphQLFieldMap, } from 'graphql'; import { @@ -149,8 +147,10 @@ function addResolveFunctionsToSchema( return; } - const fields = getFieldsForType(type); - if (!fields) { + if (!( + type instanceof GraphQLObjectType || + type instanceof GraphQLInterfaceType + )) { if (allowResolversNotInSchema) { return; } @@ -160,6 +160,7 @@ function addResolveFunctionsToSchema( ); } + const fields = type.getFields(); if (!fields[fieldName]) { if (allowResolversNotInSchema) { return; @@ -206,17 +207,6 @@ function addResolveFunctionsToSchema( return schema; } -function getFieldsForType(type: GraphQLType): GraphQLFieldMap { - if ( - type instanceof GraphQLObjectType || - type instanceof GraphQLInterfaceType - ) { - return type.getFields(); - } else { - return undefined; - } -} - function setFieldProperties( field: GraphQLField, propertiesObj: Object, From 415b5d498e63db3e923a9992730a45b3056b8b69 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 6 Jan 2020 13:13:42 -0500 Subject: [PATCH 140/250] refactor(resolvers) Remove references to resolve functions in favor of the generic term "resolver." Retain old exports for backwards compatibility. --- designs/connectors.md | 6 +-- designs/graphql-decorator-spec.md | 6 +-- docs/source/generate-schema.md | 6 +-- docs/source/mocking.md | 2 +- docs/source/resolvers.md | 16 +++---- docs/source/schema-stitching.md | 2 +- docs/source/schema-transforms.md | 2 +- ...onsToSchema.ts => addResolversToSchema.ts} | 6 +-- ...eFunction.ts => addSchemaLevelResolver.ts} | 8 ++-- ...nsPresent.ts => assertResolversPresent.ts} | 12 +++--- src/generate/attachConnectorsToContext.ts | 4 +- src/generate/decorateWithLogger.ts | 2 +- src/generate/index.ts | 11 +++-- src/makeExecutableSchema.ts | 14 +++---- src/mock.ts | 4 +- src/stitching/makeRemoteExecutableSchema.ts | 4 +- src/stitching/mergeSchemas.ts | 4 +- src/test/testMocking.ts | 32 +++++++------- src/test/testResolution.ts | 6 +-- src/test/testSchemaGenerator.ts | 42 +++++++++---------- src/transforms/ExtendSchema.ts | 4 +- src/transforms/transformSchema.ts | 4 +- 22 files changed, 101 insertions(+), 96 deletions(-) rename src/generate/{addResolveFunctionsToSchema.ts => addResolversToSchema.ts} (97%) rename src/generate/{addSchemaLevelResolveFunction.ts => addSchemaLevelResolver.ts} (93%) rename src/generate/{assertResolveFunctionsPresent.ts => assertResolversPresent.ts} (79%) 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/source/generate-schema.md b/docs/source/generate-schema.md index be469554e59..3dd10bcb2ee 100644 --- a/docs/source/generate-schema.md +++ b/docs/source/generate-schema.md @@ -210,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/mocking.md b/docs/source/mocking.md index 5ffdf97f825..660590a787d 100644 --- a/docs/source/mocking.md +++ b/docs/source/mocking.md @@ -226,7 +226,7 @@ addMockFunctionsToSchema({ }); ``` -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, `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 resolvers will not be overwritten to provide mock data. This can be used to mock some parts of the server and not others. ### MockList diff --git a/docs/source/resolvers.md b/docs/source/resolvers.md index d5aef29311c..2f48f99dbff 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: @@ -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-stitching.md b/docs/source/schema-stitching.md index df8415ff5d6..dcdaaa73aac 100644 --- a/docs/source/schema-stitching.md +++ b/docs/source/schema-stitching.md @@ -442,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 3467d6464c5..33105f6cdf0 100644 --- a/docs/source/schema-transforms.md +++ b/docs/source/schema-transforms.md @@ -174,7 +174,7 @@ RenameRootFields( ### 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 resolve function within the `objectFieldTransformer`. In this way, a field can be fully arbitrarily modified in place. +* `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) diff --git a/src/generate/addResolveFunctionsToSchema.ts b/src/generate/addResolversToSchema.ts similarity index 97% rename from src/generate/addResolveFunctionsToSchema.ts rename to src/generate/addResolversToSchema.ts index 0a91cb96dbe..78096deb12c 100644 --- a/src/generate/addResolveFunctionsToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -24,14 +24,14 @@ import { forEachDefaultValue, } from '../utils'; -function addResolveFunctionsToSchema( +function addResolversToSchema( options: IAddResolveFunctionsToSchemaOptions | GraphQLSchema, legacyInputResolvers?: IResolvers, legacyInputValidationOptions?: IResolverValidationOptions, ) { if (options instanceof GraphQLSchema) { console.warn( - 'The addResolveFunctionsToSchema function takes named options now; see IAddResolveFunctionsToSchemaOptions', + 'The addResolversToSchema function takes named options now; see IAddResolveFunctionsToSchemaOptions', ); options = { schema: options, @@ -216,4 +216,4 @@ function setFieldProperties( }); } -export default addResolveFunctionsToSchema; +export default addResolversToSchema; diff --git a/src/generate/addSchemaLevelResolveFunction.ts b/src/generate/addSchemaLevelResolver.ts similarity index 93% rename from src/generate/addSchemaLevelResolveFunction.ts rename to src/generate/addSchemaLevelResolver.ts index 048c7e0770e..229d0a87f77 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 { @@ -74,4 +74,4 @@ function runAtMostOncePerRequest( }; } -export default addSchemaLevelResolveFunction; +export default addSchemaLevelResolver; diff --git a/src/generate/assertResolveFunctionsPresent.ts b/src/generate/assertResolversPresent.ts similarity index 79% rename from src/generate/assertResolveFunctionsPresent.ts rename to src/generate/assertResolversPresent.ts index ec51b3d0c90..7879dfbf734 100644 --- a/src/generate/assertResolveFunctionsPresent.ts +++ b/src/generate/assertResolversPresent.ts @@ -8,7 +8,7 @@ import { IResolverValidationOptions } from '../Interfaces'; import { forEachField } from '../utils'; import SchemaError from './SchemaError'; -function assertResolveFunctionsPresent( +function assertResolversPresent( schema: GraphQLSchema, resolverValidationOptions: IResolverValidationOptions = {}, ) { @@ -30,17 +30,17 @@ 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); } - // 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); } - // 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) @@ -58,7 +58,7 @@ function expectResolveFunction( if (!field.resolve) { 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 +69,4 @@ function expectResolveFunction( } } -export default assertResolveFunctionsPresent; +export default assertResolversPresent; diff --git a/src/generate/attachConnectorsToContext.ts b/src/generate/attachConnectorsToContext.ts index 56a6ce74249..6c25574dc82 100644 --- a/src/generate/attachConnectorsToContext.ts +++ b/src/generate/attachConnectorsToContext.ts @@ -4,7 +4,7 @@ import { deprecated } from 'deprecated-decorator'; import { IConnectors, IConnector, IConnectorCls } from '../Interfaces'; -import addSchemaLevelResolveFunction from './addSchemaLevelResolveFunction'; +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 @@ -67,7 +67,7 @@ const attachConnectorsToContext = deprecated( }); return root; }; - addSchemaLevelResolveFunction(schema, attachconnectorFn); + addSchemaLevelResolver(schema, attachconnectorFn); }, ); diff --git a/src/generate/decorateWithLogger.ts b/src/generate/decorateWithLogger.ts index eedf45264fe..88ed6f97a4d 100644 --- a/src/generate/decorateWithLogger.ts +++ b/src/generate/decorateWithLogger.ts @@ -30,7 +30,7 @@ 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. + // If the resolver returns a Promise log any Promise rejects. if ( result && typeof result.then === 'function' && diff --git a/src/generate/index.ts b/src/generate/index.ts index f447f981e1d..6f3520b991b 100644 --- a/src/generate/index.ts +++ b/src/generate/index.ts @@ -1,6 +1,6 @@ -export { default as addResolveFunctionsToSchema } from './addResolveFunctionsToSchema'; -export { default as addSchemaLevelResolveFunction } from './addSchemaLevelResolveFunction'; -export { default as assertResolveFunctionsPresent } from './assertResolveFunctionsPresent'; +export { default as addResolversToSchema } from './addResolversToSchema'; +export { default as addSchemaLevelResolver } from './addSchemaLevelResolver'; +export { default as assertResolversPresent } from './assertResolversPresent'; export { default as attachDirectiveResolvers } from './attachDirectiveResolvers'; export { default as attachConnectorsToContext } from './attachConnectorsToContext'; export { default as buildSchemaFromTypeDefinitions } from './buildSchemaFromTypeDefinitions'; @@ -11,3 +11,8 @@ export { default as decorateWithLogger } from './decorateWithLogger'; export { default as extendResolversFromInterfaces } from './extendResolversFromInterfaces'; export { default as extractExtensionDefinitions } from './extractExtensionDefinitions'; export { default as SchemaError } from './SchemaError'; + +// for backwards compatibility +export { default as addResolveFunctionsToSchema } from './addResolversToSchema'; +export { default as addSchemaLevelResolveFunction } from './addSchemaLevelResolver'; +export { default as assertResolveFunctionsPresent } from './assertResolversPresent'; diff --git a/src/makeExecutableSchema.ts b/src/makeExecutableSchema.ts index e8fb4120e18..96b6c7c5e35 100644 --- a/src/makeExecutableSchema.ts +++ b/src/makeExecutableSchema.ts @@ -10,10 +10,10 @@ import { import { attachDirectiveResolvers, - assertResolveFunctionsPresent, - addResolveFunctionsToSchema, + assertResolversPresent, + addResolversToSchema, attachConnectorsToContext, - addSchemaLevelResolveFunction, + addSchemaLevelResolver, buildSchemaFromTypeDefinitions, decorateWithLogger, SchemaError @@ -53,14 +53,14 @@ export function makeExecutableSchema({ let schema = buildSchemaFromTypeDefinitions(typeDefs, parseOptions); - addResolveFunctionsToSchema({ + addResolversToSchema({ schema, resolvers: resolverMap, resolverValidationOptions, inheritResolversFromInterfaces }); - assertResolveFunctionsPresent(schema, resolverValidationOptions); + assertResolversPresent(schema, resolverValidationOptions); if (!allowUndefinedInResolve) { addCatchUndefinedToSchema(schema); @@ -73,7 +73,7 @@ export function makeExecutableSchema({ 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) { @@ -103,7 +103,7 @@ function decorateToCatchUndefined( return (root, args, ctx, info) => { const result = fn(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; }; diff --git a/src/mock.ts b/src/mock.ts index a2a26bc12f0..4d7a0ebd6ef 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -236,8 +236,8 @@ function addMockFunctionsToSchema({ // 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, diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 41c05817e96..001d03903bf 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -20,7 +20,7 @@ import mapAsyncIterator from './mapAsyncIterator'; import { Options as PrintSchemaOptions } from 'graphql/utilities/schemaPrinter'; import { cloneSchema } from '../utils'; import { stripResolvers, generateProxyingResolvers } from './resolvers'; -import { addResolveFunctionsToSchema } from '../generate'; +import { addResolversToSchema } from '../generate'; export type ResolverFn = ( rootValue?: any, @@ -66,7 +66,7 @@ export default function makeRemoteExecutableSchema({ } } - addResolveFunctionsToSchema({ + addResolversToSchema({ schema: remoteSchema, resolvers: generateProxyingResolvers({ schema: remoteSchema }, createProxyingResolver), resolverValidationOptions: { diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index c09980ee8f8..ac7d6566d3b 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -25,7 +25,7 @@ import { } from '../Interfaces'; import { extractExtensionDefinitions, - addResolveFunctionsToSchema, + addResolversToSchema, } from '../makeExecutableSchema'; import delegateToSchema from './delegateToSchema'; import typeFromAST from './typeFromAST'; @@ -234,7 +234,7 @@ export default function mergeSchemas({ mergeInfo = completeMergeInfo(mergeInfo, resolvers); - addResolveFunctionsToSchema({ + addResolversToSchema({ schema: mergedSchema, resolvers: resolvers as IResolvers, inheritResolversFromInterfaces diff --git a/src/test/testMocking.ts b/src/test/testMocking.ts index 01c217796ae..69e0c228d35 100644 --- a/src/test/testMocking.ts +++ b/src/test/testMocking.ts @@ -3,7 +3,7 @@ import { graphql, GraphQLResolveInfo } from 'graphql'; import { addMockFunctionsToSchema, MockList, mockServer } from '../mock'; import { buildSchemaFromTypeDefinitions, - addResolveFunctionsToSchema, + addResolversToSchema, makeExecutableSchema, } from '../makeExecutableSchema'; import 'mocha'; @@ -177,7 +177,7 @@ describe('Mock', () => { returnString: () => 'someString', }, }; - addResolveFunctionsToSchema(jsSchema, resolvers); + addResolversToSchema(jsSchema, resolvers); const testQuery = `{ returnInt returnString @@ -259,7 +259,7 @@ describe('Mock', () => { }, }, }; - addResolveFunctionsToSchema(jsSchema, resolvers); + addResolversToSchema(jsSchema, resolvers); addMockFunctionsToSchema({ schema: jsSchema, mocks: {}, @@ -298,7 +298,7 @@ describe('Mock', () => { it('can mock Unions', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); - addResolveFunctionsToSchema(jsSchema, resolveFunctions); + addResolversToSchema(jsSchema, resolveFunctions); const mockMap = { Int: () => 10, String: () => 'aha', @@ -335,7 +335,7 @@ describe('Mock', () => { it('can mock Interfaces by default', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); - addResolveFunctionsToSchema(jsSchema, resolveFunctions); + addResolversToSchema(jsSchema, resolveFunctions); const mockMap = { Int: () => 10, String: () => 'aha', @@ -375,7 +375,7 @@ describe('Mock', () => { it('can support explicit Interface mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); - addResolveFunctionsToSchema(jsSchema, resolveFunctions); + addResolversToSchema(jsSchema, resolveFunctions); let spy = 0; const mockMap = { Bird: (root: any, args: any) => ({ @@ -418,7 +418,7 @@ describe('Mock', () => { it('can support explicit UnionType mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); - addResolveFunctionsToSchema(jsSchema, resolveFunctions); + addResolversToSchema(jsSchema, resolveFunctions); let spy = 0; const mockMap = { Bird: (root: any, args: any) => ({ @@ -463,7 +463,7 @@ describe('Mock', () => { it('throws an error when __typename is not returned within an explicit interface mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); - addResolveFunctionsToSchema(jsSchema, resolveFunctions); + addResolversToSchema(jsSchema, resolveFunctions); const mockMap = { Bird: (root: any, args: any) => ({ id: args.id, @@ -515,7 +515,7 @@ describe('Mock', () => { returnMockError: () => undefined, }, }; - addResolveFunctionsToSchema(jsSchema, resolvers); + addResolversToSchema(jsSchema, resolvers); const mockMap = {}; addMockFunctionsToSchema({ @@ -544,7 +544,7 @@ describe('Mock', () => { returnMockError: () => '10-11-2012', }, }; - addResolveFunctionsToSchema(jsSchema, resolvers); + addResolversToSchema(jsSchema, resolvers); const mockMap = {}; addMockFunctionsToSchema({ @@ -714,7 +714,7 @@ describe('Mock', () => { }); }); - it('does not mask resolve functions if you tell it not to', () => { + it('does not mask resolvers if you tell it not to', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { RootQuery: () => ({ @@ -730,7 +730,7 @@ describe('Mock', () => { returnString: () => Promise.resolve('bar'), // see c) }, }; - addResolveFunctionsToSchema(jsSchema, resolvers); + addResolversToSchema(jsSchema, resolvers); addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap, @@ -791,7 +791,7 @@ describe('Mock', () => { }), }, }; - addResolveFunctionsToSchema(jsSchema, resolvers); + addResolversToSchema(jsSchema, resolvers); const mockMap = { returnListOfInt: () => [5, 6, 7], Bird: () => ({ @@ -834,7 +834,7 @@ describe('Mock', () => { }), }, }; - addResolveFunctionsToSchema(jsSchema, resolvers); + addResolversToSchema(jsSchema, resolvers); const mockMap = { Bird: () => ({ returnInt: 3, // see a) @@ -876,7 +876,7 @@ describe('Mock', () => { returnObject: () => objProxy, }, }; - addResolveFunctionsToSchema(jsSchema, resolvers); + addResolversToSchema(jsSchema, resolvers); const mockMap = { Bird: () => ({ returnInt: 3, // see a) @@ -1046,7 +1046,7 @@ describe('Mock', () => { returnString: (): string => null, // a) resolve of a string }, }; - addResolveFunctionsToSchema(jsSchema, resolvers); + addResolversToSchema(jsSchema, resolvers); const mockMap = { Int: () => 666, // b) mock of Int. }; diff --git a/src/test/testResolution.ts b/src/test/testResolution.ts index f4196cb048c..d0d3c470eac 100644 --- a/src/test/testResolution.ts +++ b/src/test/testResolution.ts @@ -1,11 +1,11 @@ import { assert } from 'chai'; -import { makeExecutableSchema, addSchemaLevelResolveFunction } from '..'; +import { makeExecutableSchema, addSchemaLevelResolver } from '..'; import { parse, graphql, subscribe, ExecutionResult } from 'graphql'; import { PubSub } from 'graphql-subscriptions'; import { forAwaitEach } from 'iterall'; describe('Resolve', () => { - describe('addSchemaLevelResolveFunction', () => { + describe('addSchemaLevelResolver', () => { const pubsub = new PubSub(); const typeDefs = ` type RootQuery { @@ -44,7 +44,7 @@ describe('Resolve', () => { }; const schema = makeExecutableSchema({ typeDefs, resolvers }); let schemaLevelResolveFunctionCalls = 0; - addSchemaLevelResolveFunction(schema, root => { + addSchemaLevelResolver(schema, root => { schemaLevelResolveFunctionCalls += 1; return root; }); diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 26ac8af9021..6d395996bce 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -24,7 +24,7 @@ import { makeExecutableSchema, SchemaError, addErrorLoggingToSchema, - addSchemaLevelResolveFunction, + addSchemaLevelResolver, attachConnectorsToContext, attachDirectiveResolvers, chainResolvers, @@ -40,7 +40,7 @@ import { } from '../Interfaces'; import 'mocha'; import { visitSchema } from '../utils/visitSchema'; -import { addResolveFunctionsToSchema } from '../generate'; +import { addResolversToSchema } from '../generate'; interface Bird { name: string; @@ -448,7 +448,7 @@ describe('generating schema from shorthand', () => { expect(jsSchema.getQueryType().name).to.equal('Query'); }); - it('can generate a schema with resolve functions', () => { + it('can generate a schema with resolvers', () => { const shorthand = ` type BirdSpecies { name: String!, @@ -1259,7 +1259,7 @@ describe('generating schema from shorthand', () => { resolvers: resolveFunctions, }); - addResolveFunctionsToSchema({ + addResolversToSchema({ schema: jsSchema, resolvers: { Color: { @@ -1341,7 +1341,7 @@ describe('generating schema from shorthand', () => { ); }); - it('shows a warning if a field has arguments but no resolve func', () => { + it('shows a warning if a field has arguments but no resolver', () => { const short = ` type Query{ bird(id: ID): String @@ -1360,7 +1360,7 @@ describe('generating schema from shorthand', () => { requireResolversForArgs: true, }, }); - }, 'Resolve function missing for "Query.bird"'); + }, 'Resolver missing for "Query.bird"'); }); // tslint:disable-next-line: max-line-length @@ -1401,7 +1401,7 @@ describe('generating schema from shorthand', () => { ).to.throw('Resolver Query.bird must be object or function'); }); - it('shows a warning if a field is not scalar, but has no resolve func', () => { + it('shows a warning if a field is not scalar, but has no resolver', () => { const short = ` type Bird{ id: ID @@ -1425,11 +1425,11 @@ describe('generating schema from shorthand', () => { resolvers: rf, resolverValidationOptions, }); - }, 'Resolve function missing for "Query.bird"'); + }, 'Resolver missing for "Query.bird"'); }); // tslint:disable-next-line: max-line-length - it('allows non-scalar field to use default resolve func if `resolverValidationOptions.requireResolversForNonScalar` = false', () => { + it('allows non-scalar field to use default resolver if `resolverValidationOptions.requireResolversForNonScalar` = false', () => { const short = ` type Bird{ id: ID @@ -1815,8 +1815,8 @@ describe('generating schema from shorthand', () => { }); }); -describe('providing useful errors from resolve functions', () => { - it('logs an error if a resolve function fails', () => { +describe('providing useful errors from resolvers', () => { + it('logs an error if a resolver fails', () => { const shorthand = ` type RootQuery { species(name: String): String @@ -1874,7 +1874,7 @@ describe('providing useful errors from resolve functions', () => { allowUndefinedInResolve: false, }); const testQuery = '{ species, stuff }'; - const expectedErr = /Resolve function for "RootQuery.species" returned undefined/; + const expectedErr = /Resolver for "RootQuery.species" returned undefined/; const expectedResData = { species: null, stuff: 'stuff' }; return graphql(jsSchema, testQuery).then(res => { assert.equal(logger.errors.length, 1); @@ -1955,7 +1955,7 @@ describe('providing useful errors from resolve functions', () => { }`; return graphql(jsSchema, testQuery).then(res => { expect((res.errors[0]).originalError.message).to.equal( - 'Resolve function for "Thread.name" returned undefined', + 'Resolver for "Thread.name" returned undefined', ); }); }); @@ -2049,14 +2049,14 @@ describe('Add error logging to schema', () => { }); describe('Attaching connectors to schema', () => { - describe('Schema level resolve function', () => { + describe('Schema level resolver', () => { it('actually runs', () => { const jsSchema = makeExecutableSchema({ typeDefs: testSchema, resolvers: testResolvers, }); const rootResolver = () => ({ species: 'ROOT' }); - addSchemaLevelResolveFunction(jsSchema, rootResolver); + addSchemaLevelResolver(jsSchema, rootResolver); const query = `{ species(name: "strix") }`; @@ -2071,7 +2071,7 @@ describe('Attaching connectors to schema', () => { resolvers: testResolvers, }); const rootResolver = () => ({ stuff: 'stuff' }); - addSchemaLevelResolveFunction(jsSchema, rootResolver); + addSchemaLevelResolver(jsSchema, rootResolver); const query = `{ stuff }`; @@ -2105,7 +2105,7 @@ describe('Attaching connectors to schema', () => { } return { stuff: 'EEE', species: 'EEE' }; }; - addSchemaLevelResolveFunction(jsSchema, rootResolver); + addSchemaLevelResolver(jsSchema, rootResolver); const query = `{ species(name: "strix") stuff @@ -2148,7 +2148,7 @@ describe('Attaching connectors to schema', () => { } return { stuff: 'EEE', species: 'EEE' }; }; - addSchemaLevelResolveFunction(jsSchema, rootResolver); + addSchemaLevelResolver(jsSchema, rootResolver); const query = `{ species(name: "strix") stuff @@ -2177,7 +2177,7 @@ describe('Attaching connectors to schema', () => { const rootResolver = (o: any, a: { [key: string]: any }, ctx: any) => { ctx['usecontext'] = 'ABC'; }; - addSchemaLevelResolveFunction(jsSchema, rootResolver); + addSchemaLevelResolver(jsSchema, rootResolver); const query = `{ usecontext }`; @@ -2313,13 +2313,13 @@ describe('Attaching connectors to schema', () => { }); }); - it('does not interfere with schema level resolve function', () => { + it('does not interfere with schema level resolver', () => { const jsSchema = makeExecutableSchema({ typeDefs: testSchema, resolvers: testResolvers, }); const rootResolver = () => ({ stuff: 'stuff', species: 'ROOT' }); - addSchemaLevelResolveFunction(jsSchema, rootResolver); + addSchemaLevelResolver(jsSchema, rootResolver); attachConnectorsToContext(jsSchema, testConnectors); const query = `{ species(name: "strix") diff --git a/src/transforms/ExtendSchema.ts b/src/transforms/ExtendSchema.ts index 9dd0f1707d9..00bfb346b59 100644 --- a/src/transforms/ExtendSchema.ts +++ b/src/transforms/ExtendSchema.ts @@ -3,7 +3,7 @@ import { GraphQLSchema, extendSchema, parse, } from 'graphql'; import { IFieldResolver, IResolvers, Request } from '../Interfaces'; import { Transform } from './transforms'; -import { addResolveFunctionsToSchema } from '../generate'; +import { addResolversToSchema } from '../generate'; import { defaultMergedResolver } from '../stitching'; import { default as MapFields, FieldNodeTransformerMap } from './MapFields'; @@ -39,7 +39,7 @@ export default class ExtendSchema implements Transform { newSchema = extendSchema(schema, parse(this.typeDefs)); } - return addResolveFunctionsToSchema({ + return addResolversToSchema({ schema: newSchema || schema, resolvers: this.resolvers, defaultFieldResolver: this.defaultFieldResolver, diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index c6a69044685..6278a0576c4 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -1,5 +1,5 @@ import { GraphQLSchema } from 'graphql'; -import { addResolveFunctionsToSchema } from '../makeExecutableSchema'; +import { addResolversToSchema } from '../makeExecutableSchema'; import { Transform, applySchemaTransforms } from '../transforms/transforms'; import { @@ -31,7 +31,7 @@ export function wrapSchema( const schema = cloneSchema(subschema.schema); stripResolvers(schema); - addResolveFunctionsToSchema({ + addResolversToSchema({ schema, resolvers: generateProxyingResolvers(subschema), resolverValidationOptions: { From bea9d690002486bbae945866c601d016c07a286d Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 6 Jan 2020 14:26:16 -0500 Subject: [PATCH 141/250] chore(types) new version of typescript and iterall don't play nice with this code --- package.json | 10 +++++----- src/stitching/observableToAsyncIterable.ts | 6 ++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 08936d4ee56..bb5ab0fe1c1 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,9 @@ "apollo-link-http-common": "^0.2.15", "apollo-utilities": "^1.3.3", "deprecated-decorator": "^0.1.6", - "extract-files": "^6.0.0", + "extract-files": "^7.0.0", "form-data": "^3.0.0", - "iterall": "^1.2.2", + "iterall": "^1.3.0", "node-fetch": "^2.6.0", "uuid": "^3.3.3" }, @@ -70,7 +70,7 @@ "@types/extract-files": "^3.1.0", "@types/graphql-upload": "^8.0.3", "@types/mocha": "^5.2.7", - "@types/node": "^12.12.21", + "@types/node": "^13.1.4", "@types/node-fetch": "^2.5.4", "@types/supertest": "^2.0.8", "@types/uuid": "^3.4.6", @@ -85,14 +85,14 @@ "graphql-type-json": "^0.3.1", "graphql-upload": "^9.0.0", "istanbul": "^0.4.5", - "mocha": "^6.2.2", + "mocha": "^7.0.0", "prettier": "^1.19.1", "remap-istanbul": "0.13.0", "rimraf": "^3.0.0", "source-map-support": "^0.5.16", "standard-version": "^7.0.1", "tslint": "^5.20.1", - "typescript": "3.7.3", + "typescript": "3.7.4", "zen-observable-ts": "^0.8.20" } } diff --git a/src/stitching/observableToAsyncIterable.ts b/src/stitching/observableToAsyncIterable.ts index 02b27447479..e57633695b9 100644 --- a/src/stitching/observableToAsyncIterable.ts +++ b/src/stitching/observableToAsyncIterable.ts @@ -2,10 +2,8 @@ import { Observable } from 'apollo-link'; import { $$asyncIterator } from 'iterall'; type Callback = (value?: any) => any; -export function observableToAsyncIterable( - observable: Observable -): AsyncIterator & { - [$$asyncIterator]: () => AsyncIterator; +export function observableToAsyncIterable(observable: Observable): AsyncIterator & { + [$$asyncIterator]: () => AsyncIterator, } { const pullQueue: Callback[] = []; const pushQueue: any[] = []; From 6033344ddf70d60927af43f584c18344f15b04ee Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 7 Jan 2020 22:37:02 -0500 Subject: [PATCH 142/250] refactor(handleObject): consolidate --- src/stitching/checkResultAndHandleErrors.ts | 95 +++++++++++---------- src/stitching/proxiedResult.ts | 11 ++- 2 files changed, 57 insertions(+), 49 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 8af506957a5..ac016e78ea0 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -61,20 +61,13 @@ export function handleResult( if (isLeafType(type)) { return type.parseValue(result); } else if (isCompositeType(type)) { - const object = handleObject(result, errors, subschemas); - return mergeFields( - type, - object, - subschemas, - context, - info, - ); + return handleObject(type, result, errors, subschemas, context, info); } else if (isListType(type)) { return handleList(type, result, errors, subschemas, context, info); } } -export function handleObject ( +export function makeObjectProxiedResult( object: any, errors: ReadonlyArray, subschemas: Array, @@ -87,8 +80,29 @@ export function handleObject ( ); })); setSubschemas(object, subschemas); +} - return object; +export function handleObject( + type: GraphQLCompositeType, + object: any, + errors: ReadonlyArray, + subschemas: Array, + context: Record, + info: IGraphQLToolsResolveInfo, +) { + makeObjectProxiedResult(object, errors, subschemas); + + if (info.mergeInfo) { + return mergeFields( + type, + object, + subschemas, + context, + info, + ); + } else { + return object; + } } function handleList( @@ -131,14 +145,7 @@ function handleListMember( if (isLeafType(type)) { return type.parseValue(listMember); } else if (isCompositeType(type)) { - const object = handleObject(listMember, errors, subschemas); - return mergeFields( - type, - object, - subschemas, - context, - info - ); + return handleObject(type, listMember, errors, subschemas, context, info); } else if (isListType(type)) { return handleList(type, listMember, errors, subschemas, context, info); } @@ -151,34 +158,32 @@ async function mergeFields( context: Record, info: IGraphQLToolsResolveInfo, ): Promise { - if (info.mergeInfo) { - let typeName: string; - if (isAbstractType(type)) { - typeName = info.schema.getTypeMap()[resolveFromParentTypename(object)].name; - } else { - typeName = type.name; - } + let typeName: string; + if (isAbstractType(type)) { + typeName = info.schema.getTypeMap()[resolveFromParentTypename(object)].name; + } else { + typeName = type.name; + } - const initialSchemas = - info.mergeInfo.mergedTypes[typeName] && - info.mergeInfo.mergedTypes[typeName].subschemas; - if (initialSchemas) { - const remainingSubschemas = initialSchemas.filter( - subschema => !subschemas.includes(subschema) - ); - if (remainingSubschemas.length) { - const results = await Promise.all(remainingSubschemas.map(subschema => { - const mergedTypeResolver = subschema.mergedTypeConfigs[typeName].mergedTypeResolver; - return mergedTypeResolver(subschema, object, context, { - ...info, - mergeInfo: { - ...info.mergeInfo, - mergedTypes: {}, - }, - }); - })); - results.forEach((r: ExecutionResult) => Object.assign(object, r)); - } + const initialSchemas = + info.mergeInfo.mergedTypes[typeName] && + info.mergeInfo.mergedTypes[typeName].subschemas; + if (initialSchemas) { + const remainingSubschemas = initialSchemas.filter( + subschema => !subschemas.includes(subschema) + ); + if (remainingSubschemas.length) { + const results = await Promise.all(remainingSubschemas.map(subschema => { + const mergedTypeResolver = subschema.mergedTypeConfigs[typeName].mergedTypeResolver; + return mergedTypeResolver(subschema, object, context, { + ...info, + mergeInfo: { + ...info.mergeInfo, + mergedTypes: {}, + }, + }); + })); + results.forEach((r: ExecutionResult) => Object.assign(object, r)); } } diff --git a/src/stitching/proxiedResult.ts b/src/stitching/proxiedResult.ts index e01909ebf64..f9c3215fe66 100644 --- a/src/stitching/proxiedResult.ts +++ b/src/stitching/proxiedResult.ts @@ -4,7 +4,7 @@ import { responsePathAsArray, } from 'graphql'; import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; -import { handleNull, handleObject } from './checkResultAndHandleErrors'; +import { handleNull, makeObjectProxiedResult } from './checkResultAndHandleErrors'; import { relocatedError } from './errors'; export let SUBSCHEMAS_SYMBOL: any; @@ -73,11 +73,14 @@ export function unwrapResult( const errors = getErrors(parent, responseKey); const subschemas = getSubschemas(parent); - const result = parent[responseKey]; - if (result == null) { + const object = parent[responseKey]; + if (object == null) { return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors); } - parent = handleObject(result, errors, subschemas); + + makeObjectProxiedResult(object, errors, subschemas); + parent = object; + responseKey = path[i + 1]; } From 285274d2415f12beed91454f86a70cbc2d088274 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 7 Jan 2020 22:38:36 -0500 Subject: [PATCH 143/250] refactor(hoisting) No need to prealias when wrapping if using deep merge instead of assign to merge types from multiple schemas, as the query tree should always be unique. If not prealiasing when wrapping, wrapping does not require a delimeter, and so hoisting does not require a prefix either as all delimeters are used only when hoisting. --- src/stitching/checkResultAndHandleErrors.ts | 3 ++- src/stitching/createMergedResolver.ts | 2 +- src/stitching/proxiedResult.ts | 28 ++++++--------------- src/transforms/HoistField.ts | 6 +---- src/transforms/WrapFields.ts | 6 +---- src/transforms/WrapType.ts | 2 -- src/utils/fieldNodes.ts | 7 ++---- 7 files changed, 14 insertions(+), 40 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index ac016e78ea0..b203e33c537 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -26,6 +26,7 @@ import { } from '../Interfaces'; import resolveFromParentTypename from './resolveFromParentTypename'; import { setErrors, setSubschemas } from './proxiedResult'; +import { mergeDeep } from '../utils'; export function checkResultAndHandleErrors( result: ExecutionResult, @@ -183,7 +184,7 @@ async function mergeFields( }, }); })); - results.forEach((r: ExecutionResult) => Object.assign(object, r)); + object = results.reduce((acc: any, r: ExecutionResult) => mergeDeep(acc, r), object); } } diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 39552b2c03c..e71b99155cf 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -18,7 +18,7 @@ export function createMergedResolver({ const unwrappingResolver: IFieldResolver = fromPath && fromPath.length ? (parent, args, context, info) => - parentErrorResolver(unwrapResult(parent, info, fromPath, delimeter), args, context, info) : + parentErrorResolver(unwrapResult(parent, info, fromPath), args, context, info) : parentErrorResolver; const dehoistingResolver: IFieldResolver = dehoist ? diff --git a/src/stitching/proxiedResult.ts b/src/stitching/proxiedResult.ts index f9c3215fe66..d5b9bf9dec8 100644 --- a/src/stitching/proxiedResult.ts +++ b/src/stitching/proxiedResult.ts @@ -61,15 +61,10 @@ export function unwrapResult( parent: any, info: IGraphQLToolsResolveInfo, path: Array, - delimeter: string = '__gqltf__', ): any { - let responseKey = Object.keys(parent).find(key => { - const splitKey = key.split(delimeter); - return (splitKey.length === 3 && splitKey[0] === 'wrapped' && splitKey[2] === path[0]); - }); - const pathLength = path.length; for (let i = 0; i < pathLength; i++) { + const responseKey = path[i]; const errors = getErrors(parent, responseKey); const subschemas = getSubschemas(parent); @@ -77,11 +72,8 @@ export function unwrapResult( if (object == null) { return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors); } - makeObjectProxiedResult(object, errors, subschemas); parent = object; - - responseKey = path[i + 1]; } return parent; @@ -94,14 +86,11 @@ export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): any let obj = result; const fieldNames = alias.split(delimeter); - const prefix = fieldNames.shift(); - if (prefix === 'hoisted') { - const fieldName = fieldNames.pop(); - fieldNames.forEach(key => { - obj = obj[key] = obj[key] || Object.create(null); - }); - obj[fieldName] = parent[alias]; - } + const fieldName = fieldNames.pop(); + fieldNames.forEach(key => { + obj = obj[key] = obj[key] || Object.create(null); + }); + obj[fieldName] = parent[alias]; }); result[ERROR_SYMBOL] = parent[ERROR_SYMBOL].map((error: GraphQLError) => { @@ -109,10 +98,7 @@ export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): any let path = error.path.slice(); const pathSegment = path.shift(); const expandedPathSegment: Array = (pathSegment as string).split(delimeter); - const prefix = expandedPathSegment.shift(); - return (prefix === 'hoisted') ? - relocatedError(error, error.nodes, expandedPathSegment.concat(path)) : - error; + return relocatedError(error, error.nodes, expandedPathSegment.concat(path)); } else { return error; } diff --git a/src/transforms/HoistField.ts b/src/transforms/HoistField.ts index 028ea5da6f2..595b09b94e7 100644 --- a/src/transforms/HoistField.ts +++ b/src/transforms/HoistField.ts @@ -18,19 +18,16 @@ export default class HoistField implements Transform { private newFieldName: string; private pathToField: Array; private oldFieldName: string; - private delimeter: string; private transformer: Transform; constructor( typeName: string, path: Array, newFieldName: string, - delimeter: string = '__gqltf__', ) { this.typeName = typeName; this.path = path; this.newFieldName = newFieldName; - this.delimeter = delimeter; this.pathToField = this.path.slice(); this.oldFieldName = this.pathToField.pop(); @@ -39,7 +36,6 @@ export default class HoistField implements Transform { [newFieldName]: fieldNode => wrapFieldNode( renameFieldNode(fieldNode, this.oldFieldName), this.pathToField, - this.delimeter ), }, }); @@ -65,7 +61,7 @@ export default class HoistField implements Transform { appendFields(typeMap, this.typeName, { [this.newFieldName]: { type: targetType, - resolve: createMergedResolver({ fromPath: this.pathToField, delimeter: this.delimeter }), + resolve: createMergedResolver({ fromPath: this.pathToField }), }, }); diff --git a/src/transforms/WrapFields.ts b/src/transforms/WrapFields.ts index 10bbdeea4c4..2f2fc09371c 100644 --- a/src/transforms/WrapFields.ts +++ b/src/transforms/WrapFields.ts @@ -17,7 +17,6 @@ export default class WrapFields implements Transform { private wrappingTypeNames: Array; private numWraps: number; private fieldNames: Array; - private delimeter: string; private transformer: Transform; constructor( @@ -25,14 +24,12 @@ export default class WrapFields implements Transform { wrappingFieldNames: Array, wrappingTypeNames: Array, fieldNames?: Array, - delimeter: string = '__gqltf__', ) { this.outerTypeName = outerTypeName; this.wrappingFieldNames = wrappingFieldNames; this.wrappingTypeNames = wrappingTypeNames; this.numWraps = wrappingFieldNames.length; this.fieldNames = fieldNames; - this.delimeter = delimeter; const remainingWrappingFieldNames = this.wrappingFieldNames.slice(); const outerMostWrappingFieldName = remainingWrappingFieldNames.shift(); @@ -43,7 +40,6 @@ export default class WrapFields implements Transform { path: remainingWrappingFieldNames, fieldNames: this.fieldNames, fragments, - delimeter: this.delimeter, }), }, }); @@ -75,7 +71,7 @@ export default class WrapFields implements Transform { appendFields(typeMap, this.outerTypeName, { [this.wrappingFieldNames[0]]: { type: typeMap[this.wrappingTypeNames[0]] as GraphQLObjectType, - resolve: createMergedResolver({ dehoist: true, delimeter: this.delimeter }), + resolve: createMergedResolver({ dehoist: true }), }, }); diff --git a/src/transforms/WrapType.ts b/src/transforms/WrapType.ts index 91a4f8c1c11..c23f3130fc1 100644 --- a/src/transforms/WrapType.ts +++ b/src/transforms/WrapType.ts @@ -12,14 +12,12 @@ export default class WrapType implements Transform { outerTypeName: string, innerTypeName: string, fieldName: string, - delimeter: string = '__gqltf__', ) { this.transformer = new WrapFields( outerTypeName, [fieldName], [innerTypeName], undefined, - delimeter ); } diff --git a/src/utils/fieldNodes.ts b/src/utils/fieldNodes.ts index f8532b4db32..52f2dcdb340 100644 --- a/src/utils/fieldNodes.ts +++ b/src/utils/fieldNodes.ts @@ -32,10 +32,7 @@ export function preAliasFieldNode(fieldNode: FieldNode, str: string): FieldNode export function wrapFieldNode( fieldNode: FieldNode, path: Array, - delimeter: string = '__gqltf__', ): FieldNode { - const alias = fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value; - let newFieldNode = fieldNode; path.forEach(fieldName => { newFieldNode = { @@ -53,7 +50,7 @@ export function wrapFieldNode( }; }); - return preAliasFieldNode(newFieldNode, `wrapped${delimeter}${alias}__gqltf__`); + return newFieldNode; } export function collectFields( @@ -129,7 +126,7 @@ export function hoistFieldNodes({ } else { collectFields(fieldNode.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { if (!fieldNames || fieldNames.includes(possibleFieldNode.name.value)) { - newFieldNodes.push(preAliasFieldNode(possibleFieldNode, `hoisted${delimeter}${alias}${delimeter}`)); + newFieldNodes.push(preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`)); } }); } From 0db56d7122b4b15d8eab8226c47276d04383de0f Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 8 Jan 2020 21:55:45 -0500 Subject: [PATCH 144/250] fix(stitching): custom scalars/enums Provide support for overriding scalars or enums used within input objects. --- src/stitching/delegateToSchema.ts | 6 +++- src/test/testMergeSchemas.ts | 5 +++ src/transforms/AddArgumentsAsVariables.ts | 38 +++++++++++++++++------ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index ba4bfd92e88..ab7032feea5 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -113,7 +113,11 @@ async function delegateToSchemaImplementation({ if (args) { transforms.push( - new AddArgumentsAsVariables(targetSchema, args) + new AddArgumentsAsVariables(targetSchema, args, info.schema) + ); + } else { + console.warn( + '"args" undefined. "args" argument may be required in a future version. Custom scalars or enums may not be properly serialized prior to delegation.' ); } diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 9d4ca781721..f39970cb786 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -456,6 +456,11 @@ testCombinations.forEach(async combination => { }, }, }, + TestScalar: new GraphQLScalarType({ + name: 'TestScalar', + description: undefined, + serialize: value => value, + }), Query: { delegateInterfaceTest(parent, args, context, info) { return delegateToSchema({ diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index d2defe1a9cf..220fc6ba04b 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -14,25 +14,30 @@ import { SelectionNode, TypeNode, VariableDefinitionNode, + GraphQLEnumType, + GraphQLScalarType, } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; -import { serializeInputValue } from '../utils/transformInputValue'; +import { transformInputValue } from '../utils'; export default class AddArgumentsAsVariablesTransform implements Transform { - private schema: GraphQLSchema; + private targetSchema: GraphQLSchema; private args: { [key: string]: any }; + private newSchema: GraphQLSchema; - constructor(schema: GraphQLSchema, args: { [key: string]: any }) { - this.schema = schema; + constructor(targetSchema: GraphQLSchema, args: { [key: string]: any }, newSchema: GraphQLSchema) { + this.targetSchema = targetSchema; this.args = args; + this.newSchema = newSchema; } public transformRequest(originalRequest: Request): Request { const { document, newVariables } = addVariablesToRootField( - this.schema, + this.targetSchema, originalRequest.document, this.args, + this.newSchema, ); const variables = { ...originalRequest.variables, @@ -49,10 +54,11 @@ function addVariablesToRootField( targetSchema: GraphQLSchema, document: DocumentNode, args: { [key: string]: any }, + newSchema: GraphQLSchema, ): { document: DocumentNode; newVariables: { [key: string]: any }; -} { + } { const operations: Array< OperationDefinitionNode > = document.definitions.filter( @@ -132,10 +138,22 @@ function addVariablesToRootField( }, type: typeToAst(argument.type), }; - newVariables[variableName] = serializeInputValue( - argument.type, - args[argument.name], - ); + if (newSchema) { + newVariables[variableName] = transformInputValue( + argument.type, + args[argument.name], + (t, v) => { + const newType = newSchema.getType(t.name) as GraphQLEnumType | GraphQLScalarType; + return newType ? newType.serialize(v) : v; + } + ); + } else { + // tslint:disable-next-line:max-line-length + console.warn( + 'AddArgumentsAsVariables should be passed the wrapping schema so that arguments can be properly serialized prior to delegation.' + ); + newVariables[variableName] = args[argument.name]; + } } }); From 9e5fc5bc8aeceaf2ac8fe229bafd39cd03bb1be9 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 8 Jan 2020 22:07:25 -0500 Subject: [PATCH 145/250] refactor(testUpload) Simply example usage within test. Inclusion of the GraphQLUpload scalar within the resolver map is only necessary when merging an executable "local" subschema, as if the subschema has the original GraphqlUpload scalar defined, it will throw when attempting to serialize after it is merged into the gateway. The custom GraphqlUpload scalar can be used in that case to avoid that either within the local subschema or within the gateway. In this case, the subschema is remote, and so the subschema custom scalar is imported with the default serialize method that does not throw. --- src/test/testUpload.ts | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/test/testUpload.ts b/src/test/testUpload.ts index 2e5f104b76f..35e3298a19a 100644 --- a/src/test/testUpload.ts +++ b/src/test/testUpload.ts @@ -7,15 +7,14 @@ import { AddressInfo } from 'net'; import { Readable } from 'stream'; import express, { Express } from 'express'; import graphqlHTTP from 'express-graphql'; -import { graphqlUploadExpress } from 'graphql-upload'; +import { GraphQLUpload, graphqlUploadExpress } from 'graphql-upload'; import FormData from 'form-data'; import fetch from 'node-fetch'; import { buildSchema } from 'graphql'; import { SubschemaConfig } from '../Interfaces'; import { createServerHttpLink } from '../links'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import { GraphQLUpload } from '../scalars'; -import { mergeSchemas, delegateToSchema } from '../stitching'; +import { mergeSchemas} from '../stitching'; function streamToString(stream: Readable) { const chunks: Array = []; @@ -105,22 +104,6 @@ describe('graphql upload', () => { const gatewaySchema = mergeSchemas({ schemas: [subSchema], - resolvers: { - Mutation: { - upload: async (root, args, context, info) => { - const result = await delegateToSchema({ - schema: subSchema, - operation: 'mutation', - fieldName: 'upload', - args, - context, - info, - }); - return result; - } - }, - Upload: GraphQLUpload, - }, }); const gatewayApp = express().use( From e51d11fc31e416de3c1e2fe384fcbd4ac1f6f669 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 12 Jan 2020 22:50:15 -0500 Subject: [PATCH 146/250] refactor(stitching): type merging simplifies logic around type merging to automatically merge types that have merging resolvers specified. --- src/stitching/checkResultAndHandleErrors.ts | 4 +- src/stitching/mergeSchemas.ts | 84 ++++++++++----------- src/test/testAlternateMergeSchemas.ts | 44 +++++------ 3 files changed, 64 insertions(+), 68 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index b203e33c537..3d504589ba9 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -175,8 +175,8 @@ async function mergeFields( ); if (remainingSubschemas.length) { const results = await Promise.all(remainingSubschemas.map(subschema => { - const mergedTypeResolver = subschema.mergedTypeConfigs[typeName].mergedTypeResolver; - return mergedTypeResolver(subschema, object, context, { + const resolver = subschema.mergedTypeConfigs[typeName].mergedTypeResolver; + return resolver(subschema, object, context, { ...info, mergeInfo: { ...info.mergeInfo, diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index ac7d6566d3b..20242d510a2 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -61,7 +61,6 @@ export default function mergeSchemas({ types = [], typeDefs, schemas: schemaLikeObjects = [], - mergeTypes = [], onTypeConflict, resolvers = {}, schemaDirectives, @@ -72,7 +71,6 @@ export default function mergeSchemas({ types?: Array; typeDefs?: string | DocumentNode; schemas?: Array; - mergeTypes?: Array; onTypeConflict?: OnTypeConflict; resolvers?: IResolversParameter; schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor }; @@ -174,12 +172,38 @@ export default function mergeSchemas({ } }); + let mergeInfo = createMergeInfo(allSchemas, typeCandidates); + + if (typeof resolvers === 'function') { + console.warn( + 'Passing functions as resolver parameter is deprecated. Use `info.mergeInfo` instead.', + ); + resolvers = resolvers(mergeInfo) || {}; + } else if (Array.isArray(resolvers)) { + resolvers = resolvers.reduce((left, right) => { + if (typeof right === 'function') { + console.warn( + 'Passing functions as resolver parameter is deprecated. Use `info.mergeInfo` instead.', + ); + right = right(mergeInfo); + } + return mergeDeep(left, right); + }, {}) || {}; + if (!resolvers) { + resolvers = {}; + } else if (Array.isArray(resolvers)) { + resolvers = resolvers.reduce(mergeDeep, {}); + } + } + + mergeInfo = completeMergeInfo(mergeInfo, resolvers); + Object.keys(typeCandidates).forEach(typeName => { if ( typeName === 'Query' || typeName === 'Mutation' || typeName === 'Subscription' || - mergeTypes.includes(typeName) + mergeInfo.mergedTypes[typeName] ) { typeMap[typeName] = mergeFields(typeName, typeCandidates[typeName]); } else { @@ -208,32 +232,6 @@ export default function mergeSchemas({ }); }); - let mergeInfo = createMergeInfo(allSchemas, mergeTypes, typeCandidates); - - if (typeof resolvers === 'function') { - console.warn( - 'Passing functions as resolver parameter is deprecated. Use `info.mergeInfo` instead.', - ); - resolvers = resolvers(mergeInfo) || {}; - } else if (Array.isArray(resolvers)) { - resolvers = resolvers.reduce((left, right) => { - if (typeof right === 'function') { - console.warn( - 'Passing functions as resolver parameter is deprecated. Use `info.mergeInfo` instead.', - ); - right = right(mergeInfo); - } - return mergeDeep(left, right); - }, {}) || {}; - if (!resolvers) { - resolvers = {}; - } else if (Array.isArray(resolvers)) { - resolvers = resolvers.reduce(mergeDeep, {}); - } - } - - mergeInfo = completeMergeInfo(mergeInfo, resolvers); - addResolversToSchema({ schema: mergedSchema, resolvers: resolvers as IResolvers, @@ -271,27 +269,29 @@ export default function mergeSchemas({ function createMergeInfo( allSchemas: Array, - mergeTypes: Array, typeCandidates: { [name: string]: Array }, ): MergeInfo { const mergedTypes: MergedTypeMapping = {}; - mergeTypes.forEach(typeName => { - if (typeCandidates[typeName]) { - const subschemaConfigs: Array = - typeCandidates[typeName] - .filter(typeCandidate => isSubschemaConfig(typeCandidate.subschema)) - .map(typeCandidate => typeCandidate.subschema as SubschemaConfig); - const inlineFragments = subschemaConfigs - .filter(subschemaConfig => subschemaConfig.mergedTypeConfigs[typeName].fragment) - .map(subschemaConfig => subschemaConfig.mergedTypeConfigs[typeName].fragment) - .map(fragment => parseFragmentToInlineFragment(fragment)); + Object.keys(typeCandidates).forEach(typeName => { + const subschemaConfigs: Array = + typeCandidates[typeName] + .filter(typeCandidate => typeCandidate.subschema && isSubschemaConfig(typeCandidate.subschema)) + .map(typeCandidate => typeCandidate.subschema as SubschemaConfig); + + const mergeTypeConfigs = subschemaConfigs + .filter(subschemaConfig => + subschemaConfig.mergedTypeConfigs && subschemaConfig.mergedTypeConfigs[typeName]) + .map(subschemaConfig => subschemaConfig.mergedTypeConfigs[typeName]); + + if (mergeTypeConfigs.length) { + const inlineFragments = mergeTypeConfigs + .filter(mergeTypeConfig => mergeTypeConfig.fragment) + .map(mergeTypeConfig => parseFragmentToInlineFragment(mergeTypeConfig.fragment)); mergedTypes[typeName] = { fragment: concatInlineFragments(typeName, inlineFragments), subschemas: subschemaConfigs, }; - } else { - throw new Error(`Cannot merge type '${typeName}', type not found.`); } }); diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index d082f1ff4d3..184ce93aa15 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -41,7 +41,7 @@ import { mergeSchemas, createMergedResolver, } from '../stitching'; -import { SubschemaConfig, MergedTypeConfig } from '../Interfaces'; +import { SubschemaConfig } from '../Interfaces'; import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; import { wrapFieldNode, renameFieldNode, hoistFieldNodes } from '../utils/fieldNodes'; @@ -1748,48 +1748,44 @@ describe('mergeTypes', () => { }); it('can merge types', async () => { - const subschemaConfig1: SubschemaConfig = { schema: schema1 }; - const subschemaConfig2: SubschemaConfig = { schema: schema2 }; - - const mergedTypeConfigs1: Record = { - Test: { - fragment: 'fragment TestFragment on Test { id }', - mergedTypeResolver: (subschema, originalResult, context, info) => { - return delegateToSchema({ - schema: subschemaConfig1, + const subschemaConfig1: SubschemaConfig = { + schema: schema1, + mergedTypeConfigs: { + Test: { + fragment: 'fragment TestFragment on Test { id }', + mergedTypeResolver: (subschema, originalResult, context, info) => delegateToSchema({ + schema: subschema, operation: 'query', fieldName: 'getTest', args: { id: originalResult.id }, context, info, - }); + }) } - } + }, }; - const mergedTypeConfigs2: Record = { - Test: { - fragment: 'fragment TestFragment on Test { id }', - mergedTypeResolver: async (subschema, originalResult, context, info) => { - return delegateToSchema({ - schema: subschemaConfig2, + const subschemaConfig2: SubschemaConfig = { + schema: schema2, + mergedTypeConfigs: { + Test: { + fragment: 'fragment TestFragment on Test { id }', + mergedTypeResolver: (subschema, originalResult, context, info) => delegateToSchema({ + schema: subschema, operation: 'query', fieldName: 'getTest', args: { id: originalResult.id }, context, info, - }); + }) } - } + }, }; - subschemaConfig1.mergedTypeConfigs = mergedTypeConfigs1; - subschemaConfig2.mergedTypeConfigs = mergedTypeConfigs2; - const mergedSchema = mergeSchemas({ subschemas: [subschemaConfig1, subschemaConfig2], - mergeTypes: ['Test'], }); + const result1 = await graphql(mergedSchema, `{ rootField1 { test { field1 field2 } } }`); expect(result1).to.deep.equal({ data: { From 0bd452860481ec5f0eb79f38482c58c5b3510625 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 12 Jan 2020 22:53:43 -0500 Subject: [PATCH 147/250] refactor(subSchema): rename to subschema --- src/stitching/delegateToSchema.ts | 50 +++++++++++++++---------------- src/test/testTransforms.ts | 40 ++++++++++++------------- src/test/testUpload.ts | 4 +-- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index ab7032feea5..92fd59a6930 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -70,13 +70,13 @@ async function delegateToSchemaImplementation({ }: IDelegateToSchemaOptions, ): Promise { let targetSchema: GraphQLSchema; - let subSchemaConfig: SubschemaConfig; + let subschemaConfig: SubschemaConfig; if (isSubschemaConfig(subschema)) { - subSchemaConfig = subschema; - targetSchema = subSchemaConfig.schema; - rootValue = rootValue || subSchemaConfig.rootValue || info.rootValue; - transforms = transforms.concat((subSchemaConfig.transforms || []).slice().reverse()); + subschemaConfig = subschema; + targetSchema = subschemaConfig.schema; + rootValue = rootValue || subschemaConfig.rootValue || info.rootValue; + transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse()); } else { targetSchema = subschema; rootValue = rootValue || info.rootValue; @@ -136,7 +136,7 @@ async function delegateToSchemaImplementation({ } if (operation === 'query' || operation === 'mutation') { - const executor = createExecutor(targetSchema, rootValue, subSchemaConfig); + const executor = createExecutor(targetSchema, rootValue, subschemaConfig); return applyResultTransforms( await executor({ @@ -148,7 +148,7 @@ async function delegateToSchemaImplementation({ ); } else if (operation === 'subscription') { - const subscriber = createSubscriber(targetSchema, rootValue, subSchemaConfig); + const subscriber = createSubscriber(targetSchema, rootValue, subschemaConfig); const originalAsyncIterator = (await subscriber({ document: processedRequest.document, @@ -228,23 +228,23 @@ function createDocument( function createExecutor( schema: GraphQLSchema, rootValue: Record, - subSchemaConfig?: SubschemaConfig + subschemaConfig?: SubschemaConfig ): Delegator { let fetcher: Fetcher; - if (subSchemaConfig) { - if (subSchemaConfig.dispatcher) { - const dynamicLinkOrFetcher = subSchemaConfig.dispatcher(context); + if (subschemaConfig) { + if (subschemaConfig.dispatcher) { + const dynamicLinkOrFetcher = subschemaConfig.dispatcher(context); fetcher = (typeof dynamicLinkOrFetcher === 'function') ? dynamicLinkOrFetcher : linkToFetcher(dynamicLinkOrFetcher); - } else if (subSchemaConfig.link) { - fetcher = linkToFetcher(subSchemaConfig.link); - } else if (subSchemaConfig.fetcher) { - fetcher = subSchemaConfig.fetcher; + } else if (subschemaConfig.link) { + fetcher = linkToFetcher(subschemaConfig.link); + } else if (subschemaConfig.fetcher) { + fetcher = subschemaConfig.fetcher; } - if (!fetcher && !rootValue && subSchemaConfig.rootValue) { - rootValue = subSchemaConfig.rootValue; + if (!fetcher && !rootValue && subschemaConfig.rootValue) { + rootValue = subschemaConfig.rootValue; } } @@ -268,19 +268,19 @@ function createExecutor( function createSubscriber( schema: GraphQLSchema, rootValue: Record, - subSchemaConfig?: SubschemaConfig + subschemaConfig?: SubschemaConfig ): Delegator { let link: ApolloLink; - if (subSchemaConfig) { - if (subSchemaConfig.dispatcher) { - link = subSchemaConfig.dispatcher(context) as ApolloLink; - } else if (subSchemaConfig.link) { - link = subSchemaConfig.link; + if (subschemaConfig) { + if (subschemaConfig.dispatcher) { + link = subschemaConfig.dispatcher(context) as ApolloLink; + } else if (subschemaConfig.link) { + link = subschemaConfig.link; } - if (!link && !rootValue && subSchemaConfig.rootValue) { - rootValue = subSchemaConfig.rootValue; + if (!link && !rootValue && subschemaConfig.rootValue) { + rootValue = subschemaConfig.rootValue; } } diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 8223e3c2a6d..642659d7b46 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -387,7 +387,7 @@ describe('transforms', () => { describe('tree operations', () => { let data: any; - let subSchema: GraphQLSchema; + let subschema: GraphQLSchema; let schema: GraphQLSchema; before(() => { data = { @@ -408,7 +408,7 @@ describe('transforms', () => { }, }, }; - subSchema = makeExecutableSchema({ + subschema = makeExecutableSchema({ typeDefs: ` type User { id: ID! @@ -499,7 +499,7 @@ describe('transforms', () => { Query: { addressByUser(parent, { id }, context, info) { return delegateToSchema({ - schema: subSchema, + schema: subschema, operation: 'query', fieldName: 'userById', args: { id }, @@ -531,7 +531,7 @@ describe('transforms', () => { Mutation: { async setUserAndAddress(parent, { input }, context, info) { const addressResult = await delegateToSchema({ - schema: subSchema, + schema: subschema, operation: 'mutation', fieldName: 'setAddress', args: { @@ -553,7 +553,7 @@ describe('transforms', () => { ], }); const userResult = await delegateToSchema({ - schema: subSchema, + schema: subschema, operation: 'mutation', fieldName: 'setUser', args: { @@ -649,7 +649,7 @@ describe('transforms', () => { }); describe('WrapQuery', () => { let data: any; - let subSchema: GraphQLSchema; + let subschema: GraphQLSchema; let schema: GraphQLSchema; before(() => { data = { @@ -659,7 +659,7 @@ describe('transforms', () => { addressZip: '12345' } }; - subSchema = makeExecutableSchema({ + subschema = makeExecutableSchema({ typeDefs: ` type User { id: ID! @@ -699,7 +699,7 @@ describe('transforms', () => { Query: { addressByUser(parent, { id }, context, info) { return delegateToSchema({ - schema: subSchema, + schema: subschema, operation: 'query', fieldName: 'userById', args: { id }, @@ -781,7 +781,7 @@ describe('transforms', () => { describe('TransformQuery', () => { let data: any; - let subSchema: GraphQLSchema; + let subschema: GraphQLSchema; let schema: GraphQLSchema; before(() => { data = { @@ -802,7 +802,7 @@ describe('transforms', () => { }, }, }; - subSchema = makeExecutableSchema({ + subschema = makeExecutableSchema({ typeDefs: ` type User { id: ID! @@ -856,7 +856,7 @@ describe('transforms', () => { Query: { addressByUser(parent, { id }, context, info) { return delegateToSchema({ - schema: subSchema, + schema: subschema, operation: 'query', fieldName: 'userById', args: { id }, @@ -890,7 +890,7 @@ describe('transforms', () => { }, errorTest(parent, { id }, context, info) { return delegateToSchema({ - schema: subSchema, + schema: subschema, operation: 'query', fieldName: 'userById', args: { id }, @@ -1022,7 +1022,7 @@ describe('transforms', () => { describe('replaces field with fragments', () => { let data: any; let schema: GraphQLSchema; - let subSchema: GraphQLSchema; + let subschema: GraphQLSchema; before(() => { data = { u1: { @@ -1032,7 +1032,7 @@ describe('transforms', () => { }, }; - subSchema = makeExecutableSchema({ + subschema = makeExecutableSchema({ typeDefs: ` type User { id: ID! @@ -1070,14 +1070,14 @@ describe('transforms', () => { Query: { userById(parent, { id }, context, info) { return delegateToSchema({ - schema: subSchema, + schema: subschema, operation: 'query', fieldName: 'userById', args: { id }, context, info, transforms: [ - new ReplaceFieldWithFragment(subSchema, [ + new ReplaceFieldWithFragment(subschema, [ { field: `fullname`, fragment: `fragment UserName on User { name }`, @@ -1127,7 +1127,7 @@ describe('transforms', () => { describe('replaces field with processed fragment node', () => { let data: any; let schema: GraphQLSchema; - let subSchema: GraphQLSchema; + let subschema: GraphQLSchema; before(() => { data = { u1: { @@ -1137,7 +1137,7 @@ describe('replaces field with processed fragment node', () => { }, }; - subSchema = makeExecutableSchema({ + subschema = makeExecutableSchema({ typeDefs: ` type User { id: ID! @@ -1175,14 +1175,14 @@ describe('replaces field with processed fragment node', () => { Query: { userById(parent, { id }, context, info) { return delegateToSchema({ - schema: subSchema, + schema: subschema, operation: 'query', fieldName: 'userById', args: { id }, context, info, transforms: [ - new AddReplacementFragments(subSchema, { + new AddReplacementFragments(subschema, { User: { fullname: concatInlineFragments( 'User', diff --git a/src/test/testUpload.ts b/src/test/testUpload.ts index 35e3298a19a..72af506afcd 100644 --- a/src/test/testUpload.ts +++ b/src/test/testUpload.ts @@ -95,7 +95,7 @@ describe('graphql upload', () => { } `); - const subSchema: SubschemaConfig = { + const subschema: SubschemaConfig = { schema: nonExecutableSchema, link: createServerHttpLink({ uri: `http://localhost:${remotePort}`, @@ -103,7 +103,7 @@ describe('graphql upload', () => { }; const gatewaySchema = mergeSchemas({ - schemas: [subSchema], + schemas: [subschema], }); const gatewayApp = express().use( From 491c9dd8de017828aa016f560a0b452c06d9d25f Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 14 Jan 2020 23:16:31 -0500 Subject: [PATCH 148/250] fix(stitching): do not make sync delegation async delegateToSchema now returns a synchronous result when possible rather than always returning a promise. merging types is also performed synchronously when possible. Fixes #36. --- package.json | 1 + src/stitching/checkResultAndHandleErrors.ts | 27 +++-- src/stitching/delegateToSchema.ts | 54 +++++---- src/test/testDataloader.ts | 125 ++++++++++++++++++++ src/test/tests.ts | 1 + 5 files changed, 178 insertions(+), 30 deletions(-) create mode 100644 src/test/testDataloader.ts diff --git a/package.json b/package.json index bb5ab0fe1c1..8a36ee04093 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "apollo-upload-client": "^12.1.0", "body-parser": "^1.19.0", "chai": "^4.2.0", + "dataloader": "^2.0.0", "dateformat": "^3.0.3", "express": "^4.17.1", "express-graphql": "^0.9.0", diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 3d504589ba9..e13096248c5 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -152,13 +152,13 @@ function handleListMember( } } -async function mergeFields( +function mergeFields( type: GraphQLCompositeType, object: any, subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, -): Promise { +): any { let typeName: string; if (isAbstractType(type)) { typeName = info.schema.getTypeMap()[resolveFromParentTypename(object)].name; @@ -174,17 +174,30 @@ async function mergeFields( subschema => !subschemas.includes(subschema) ); if (remainingSubschemas.length) { - const results = await Promise.all(remainingSubschemas.map(subschema => { - const resolver = subschema.mergedTypeConfigs[typeName].mergedTypeResolver; - return resolver(subschema, object, context, { + const maybePromises = remainingSubschemas.map(subschema => { + return subschema.mergedTypeConfigs[typeName].mergedTypeResolver(subschema, object, context, { ...info, mergeInfo: { ...info.mergeInfo, mergedTypes: {}, }, }); - })); - object = results.reduce((acc: any, r: ExecutionResult) => mergeDeep(acc, r), object); + }); + + let containsPromises = false; { + for (const maybePromise of maybePromises) { + if (maybePromise instanceof Promise) { + containsPromises = true; + break; + } + } + } + if (containsPromises) { + return Promise.all(maybePromises). + then(results => results.reduce((acc: any, r: ExecutionResult) => mergeDeep(acc, r), object)); + } else { + return maybePromises.reduce((acc: any, r: ExecutionResult) => mergeDeep(acc, r), object); + } } } diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 92fd59a6930..78dd203ce99 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -44,10 +44,12 @@ import linkToFetcher from './linkToFetcher'; import { observableToAsyncIterable } from './observableToAsyncIterable'; import { AddMergedTypeFragments } from '../transforms'; +import { isAsyncIterable } from 'iterall'; + export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, ...args: any[] -): Promise { +): any { if (options instanceof GraphQLSchema) { throw new Error( 'Passing positional arguments to delegateToSchema is a deprecated. ' + @@ -57,7 +59,7 @@ export default function delegateToSchema( return delegateToSchemaImplementation(options); } -async function delegateToSchemaImplementation({ +function delegateToSchemaImplementation({ schema: subschema, rootValue, info, @@ -68,7 +70,7 @@ async function delegateToSchemaImplementation({ transforms = [], skipValidation, }: IDelegateToSchemaOptions, -): Promise { +): any { let targetSchema: GraphQLSchema; let subschemaConfig: SubschemaConfig; @@ -138,33 +140,39 @@ async function delegateToSchemaImplementation({ if (operation === 'query' || operation === 'mutation') { const executor = createExecutor(targetSchema, rootValue, subschemaConfig); - return applyResultTransforms( - await executor({ - document: processedRequest.document, - context, - variables: processedRequest.variables - }), - transforms, - ); + const executionResult: ExecutionResult | Promise = executor({ + document: processedRequest.document, + context, + variables: processedRequest.variables + }); + if (executionResult instanceof Promise) { + return executionResult.then((originalResult: any) => applyResultTransforms(originalResult, transforms)); + } else { + return applyResultTransforms(executionResult, transforms); + } } else if (operation === 'subscription') { const subscriber = createSubscriber(targetSchema, rootValue, subschemaConfig); - const originalAsyncIterator = (await subscriber({ + return subscriber({ document: processedRequest.document, context, variables: processedRequest.variables, - })) as AsyncIterator; - - // "subscribe" to the subscription result and map the result through the transforms - return mapAsyncIterator(originalAsyncIterator, result => { - const transformedResult = applyResultTransforms(result, transforms); - - // wrap with fieldName to return for an additional round of resolutioon - // with payload as rootValue - return { - [info.fieldName]: transformedResult, - }; + }).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, transforms); + + // wrap with fieldName to return for an additional round of resolutioon + // with payload as rootValue + return { + [info.fieldName]: transformedResult, + }; + }); + } else { + return applyResultTransforms(subscriptionResult, transforms); + } }); } } diff --git a/src/test/testDataloader.ts b/src/test/testDataloader.ts new file mode 100644 index 00000000000..173b927de14 --- /dev/null +++ b/src/test/testDataloader.ts @@ -0,0 +1,125 @@ +/* tslint:disable:no-unused-expression */ + +import { expect } from 'chai'; + +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { + mergeSchemas, + delegateToSchema +} from '../stitching'; +import { graphql, GraphQLList } from 'graphql'; +import DataLoader from 'dataloader'; +import { IGraphQLToolsResolveInfo } from '../Interfaces'; + +describe('dataloader', () => { + it('should work', async () => { + const taskSchema = makeExecutableSchema({ + typeDefs: ` + type Task { + id: ID! + text: String + userId: ID! + } + type Query { + task(id: ID!): Task + } + `, + resolvers: { + Query: { + task: (root, { id }) => ({ id, text: `task ${id}`, userId: id }), + } + }, + }); + + const userSchema = makeExecutableSchema({ + typeDefs: ` + type User { + id: ID! + email: String! + } + type Query { + usersByIds(ids: [ID!]!): [User]! + } + `, + resolvers: { + Query: { + usersByIds: (root, { ids }) => { + return ids.map((id: string) => ({ id, email: `${id}@tasks.com` })); + }, + } + }, + }); + + const schema = mergeSchemas({ + schemas: [ + taskSchema, + userSchema + ], + typeDefs: ` + extend type Task { + user: User! + } + `, + resolvers: { + Task: { + user: { + fragment: `... on Task { userId }`, + resolve(task, args, context, info) { + return context.usersLoader.load({ id: task.userId, info }); + } + } + }, + } + }); + + const usersLoader = new DataLoader(async (keys: Array<{ id: any, info: IGraphQLToolsResolveInfo }>) => { + const users = delegateToSchema({ + schema: userSchema, + operation: 'query', + fieldName: 'usersByIds', + args: { + ids: keys.map((k: { id: any }) => k.id) + }, + context: null, + info: { + ...keys[0].info, + returnType: new GraphQLList(keys[0].info.returnType), + } + }); + + expect(users).to.deep.equal([{ + id: '1', + email: '1@tasks.com', + }]); + + return users; + }); + + const query = `{ + task(id: "1") { + id + text + user { + id + email + } + } + }`; + + const result = await graphql(schema, query, null, { usersLoader }); + + expect(result).to.deep.equal({ + data: + { + task: { + id: '1', + text: 'task 1', + user: { + id: '1', + email: '1@tasks.com', + } + } + } + }); + }); +}); diff --git a/src/test/tests.ts b/src/test/tests.ts index 7cf01779f52..2d8869e723f 100755 --- a/src/test/tests.ts +++ b/src/test/tests.ts @@ -2,6 +2,7 @@ require('source-map-support').install(); import './testAlternateMergeSchemas'; import './testDelegateToSchema'; +import './testDataloader'; import './testDirectives'; import './testErrors'; import './testFragmentsAreNotDuplicated'; From 4f3a0b7cca1e429b0220c0aea50c169310bbb206 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 15 Jan 2020 19:51:39 -0500 Subject: [PATCH 149/250] fix(healing): empty types should be pruned even if fields of those types still exist Fixes #37. --- src/test/testUtils.ts | 35 +++++++++++++++++++++++++++++++++++ src/test/tests.ts | 2 ++ src/utils/heal.ts | 25 ++++++++++++++----------- 3 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 src/test/testUtils.ts diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts new file mode 100644 index 00000000000..78127740089 --- /dev/null +++ b/src/test/testUtils.ts @@ -0,0 +1,35 @@ +/* tslint:disable:no-unused-expression */ + +import { expect } from 'chai'; + +import { healSchema } from '../utils'; +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { GraphQLObjectType, GraphQLObjectTypeConfig } from 'graphql'; + +describe('heal', () => { + it('should prune empty types', () => { + const schema = makeExecutableSchema({ + typeDefs: ` + type WillBeEmptyObject { + willBeRemoved: String + } + + type Query { + someQuery: WillBeEmptyObject + } + ` + }); + const originalTypeMap = schema.getTypeMap(); + + const config = originalTypeMap['WillBeEmptyObject'].toConfig() as GraphQLObjectTypeConfig; + originalTypeMap['WillBeEmptyObject'] = new GraphQLObjectType({ + ...config, + fields: {}, + }); + + healSchema(schema); + + const healedTypeMap = schema.getTypeMap(); + expect(healedTypeMap).not.to.haveOwnProperty('WillBeEmptyObject'); + }); +}); diff --git a/src/test/tests.ts b/src/test/tests.ts index 2d8869e723f..17c3f222d13 100755 --- a/src/test/tests.ts +++ b/src/test/tests.ts @@ -16,3 +16,5 @@ import './testTransforms'; import './testExtensionExtraction'; import './testIntegration'; import './testUpload'; +import './testUtils'; + diff --git a/src/utils/heal.ts b/src/utils/heal.ts index 63d68a48bd8..ac97f161410 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -208,22 +208,25 @@ function pruneTypes(typeMap: NamedTypeMap, directives: ReadonlyArray { - let shouldPrune: boolean = false; - if (type instanceof GraphQLObjectType) { + each(typeMap, (type, typeName) => { + if (type instanceof GraphQLObjectType || type instanceof GraphQLInputObjectType) { // prune types with no fields - shouldPrune = !Object.keys(type.getFields()).length; + if (!Object.keys(type.getFields()).length) { + typeMap[typeName] = null; + prunedTypeMap = true; + } } else if (type instanceof GraphQLUnionType) { // prune unions without underlying types - shouldPrune = !type.getTypes().length; + if (!type.getTypes().length) { + typeMap[typeName] = null; + prunedTypeMap = true; + } } else if (type instanceof GraphQLInterfaceType) { // prune interfaces without fields or without implementations - shouldPrune = !Object.keys(type.getFields()).length || !implementedInterfaces[type.name]; - } - - if (shouldPrune) { - prunedTypeMap = true; - return null; + if (!Object.keys(type.getFields()).length || !implementedInterfaces[type.name]) { + typeMap[typeName] = null; + prunedTypeMap = true; + } } }); From bcb04baa5960cee98683ca17e7b68d88d256bf0a Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 15 Jan 2020 20:02:25 -0500 Subject: [PATCH 150/250] feat(stitching): add returnType option to delegateToSchema facilitates proxying from objects to lists or possibly otherwise incompatible schemas, see #33. --- src/Interfaces.ts | 2 ++ src/stitching/checkResultAndHandleErrors.ts | 7 +++++-- src/stitching/delegateToSchema.ts | 3 ++- src/test/testDataloader.ts | 6 ++---- src/transforms/CheckResultAndHandleErrors.ts | 8 ++++++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 18240280c21..73446f5d737 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -20,6 +20,7 @@ import { GraphQLInterfaceType, GraphQLObjectType, InlineFragmentNode, + GraphQLOutputType, } from 'graphql'; import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; @@ -115,6 +116,7 @@ export interface IDelegateToSchemaOptions { schema: GraphQLSchema | SubschemaConfig; operation: Operation; fieldName: string; + returnType?: GraphQLOutputType; args?: { [key: string]: any }; context: TContext; info: IGraphQLToolsResolveInfo; diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index e13096248c5..d956ae255d5 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -9,6 +9,7 @@ import { GraphQLCompositeType, GraphQLError, GraphQLList, + GraphQLOutputType, GraphQLType, GraphQLSchema, FieldNode, @@ -34,6 +35,7 @@ export function checkResultAndHandleErrors( info: GraphQLResolveInfo, responseKey?: string, subschema?: GraphQLSchema | SubschemaConfig, + returnType: GraphQLOutputType = info.returnType, ): any { if (!responseKey) { responseKey = getResponseKeyFromInfo(info); @@ -43,7 +45,7 @@ export function checkResultAndHandleErrors( const data = result.data && result.data[responseKey]; const subschemas = [subschema]; - return handleResult(data, errors, subschemas, context, info); + return handleResult(data, errors, subschemas, context, info, returnType); } export function handleResult( @@ -52,8 +54,9 @@ export function handleResult( subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, + returnType = info.returnType, ): any { - const type = getNullableType(info.returnType); + const type = getNullableType(returnType); if (result == null) { return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors); diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 78dd203ce99..44c83eb6b1e 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -65,6 +65,7 @@ function delegateToSchemaImplementation({ info, operation = info.operation.operation, fieldName, + returnType = info.returnType, args, context, transforms = [], @@ -101,7 +102,7 @@ function delegateToSchemaImplementation({ }; transforms = [ - new CheckResultAndHandleErrors(info, fieldName, subschema, context), + new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType), ...transforms, new ExpandAbstractTypes(info.schema, targetSchema), ]; diff --git a/src/test/testDataloader.ts b/src/test/testDataloader.ts index 173b927de14..7b2e90e38dc 100644 --- a/src/test/testDataloader.ts +++ b/src/test/testDataloader.ts @@ -81,10 +81,8 @@ describe('dataloader', () => { ids: keys.map((k: { id: any }) => k.id) }, context: null, - info: { - ...keys[0].info, - returnType: new GraphQLList(keys[0].info.returnType), - } + info: keys[0].info, + returnType: new GraphQLList(keys[0].info.returnType), }); expect(users).to.deep.equal([{ diff --git a/src/transforms/CheckResultAndHandleErrors.ts b/src/transforms/CheckResultAndHandleErrors.ts index c54c2b78723..259000aab7a 100644 --- a/src/transforms/CheckResultAndHandleErrors.ts +++ b/src/transforms/CheckResultAndHandleErrors.ts @@ -1,4 +1,4 @@ -import { GraphQLSchema } from 'graphql'; +import { GraphQLSchema, GraphQLOutputType } from 'graphql'; import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; import { Transform } from './transforms'; import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; @@ -8,17 +8,20 @@ export default class CheckResultAndHandleErrors implements Transform { private info: IGraphQLToolsResolveInfo; private fieldName?: string; private subschema?: GraphQLSchema | SubschemaConfig; + private returnType?: GraphQLOutputType; constructor( info: IGraphQLToolsResolveInfo, fieldName?: string, subschema?: GraphQLSchema | SubschemaConfig, context?: Record, + returnType: GraphQLOutputType = info.returnType, ) { this.context = context; this.info = info; this.fieldName = fieldName; this.subschema = subschema; + this.returnType = returnType; } public transformResult(result: any): any { @@ -27,7 +30,8 @@ export default class CheckResultAndHandleErrors implements Transform { this.context, this.info, this.fieldName, - this.subschema + this.subschema, + this.returnType, ); } } From 3fecf4e341cef590082f1f9be1286bf0d9bdee42 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 16 Jan 2020 00:09:49 -0500 Subject: [PATCH 151/250] feat(stitching): export createDelegatingRequest and delegateRequest methods. May be useful for use with dataloaders or memoization. See https://github.com/apollographql/graphql-tools/pull/724 --- src/Interfaces.ts | 25 ++++- src/stitching/delegateToSchema.ts | 148 ++++++++++++++++++++---------- src/stitching/index.ts | 4 +- 3 files changed, 129 insertions(+), 48 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 73446f5d737..3e001bfdd11 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -112,19 +112,42 @@ export type SchemaLikeObject = export function isSubschemaConfig(value: SchemaLikeObject): value is SubschemaConfig { return !!(value as SubschemaConfig).schema; } + export interface IDelegateToSchemaOptions { schema: GraphQLSchema | SubschemaConfig; operation: Operation; fieldName: string; returnType?: GraphQLOutputType; args?: { [key: string]: any }; - context: TContext; + context?: TContext; info: IGraphQLToolsResolveInfo; rootValue?: Record; transforms?: Array; skipValidation?: boolean; } +export interface ICreateDelegatingRequestOptions { + schema: GraphQLSchema | SubschemaConfig; + info: IGraphQLToolsResolveInfo; + operation: Operation; + fieldName: string; + args?: { [key: string]: any }; + transforms?: Array; + skipValidation?: boolean; +} + +export interface IDelegateRequestOptions { + request: Request; + schema: GraphQLSchema | SubschemaConfig; + rootValue?: Record; + info: IGraphQLToolsResolveInfo; + operation: Operation; + fieldName: string; + returnType?: GraphQLOutputType; + context?: TContext; + transforms?: Array; +} + export type Delegator = ({ document, context, variables }: { document: DocumentNode; context?: { [key: string]: any }; diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 44c83eb6b1e..d3f2c99b95d 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -1,6 +1,5 @@ import { ArgumentNode, - DocumentNode, FieldNode, FragmentDefinitionNode, Kind, @@ -10,20 +9,21 @@ import { subscribe, execute, validate, - VariableDefinitionNode, GraphQLSchema, ExecutionResult, - NameNode, } from 'graphql'; import { IDelegateToSchemaOptions, + ICreateDelegatingRequestOptions, + IDelegateRequestOptions, Operation, Request, Fetcher, Delegator, SubschemaConfig, isSubschemaConfig, + IGraphQLToolsResolveInfo, } from '../Interfaces'; import { @@ -48,7 +48,6 @@ import { isAsyncIterable } from 'iterall'; export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, - ...args: any[] ): any { if (options instanceof GraphQLSchema) { throw new Error( @@ -56,53 +55,66 @@ export default function delegateToSchema( 'Please pass named parameters instead.', ); } - return delegateToSchemaImplementation(options); + + const { + schema: subschema, + rootValue, + info, + operation = info.operation.operation, + fieldName, + returnType = info.returnType, + args, + context, + transforms = [], + skipValidation, + } = options; + + const request = createDelegatingRequest({ + schema: subschema, + info, + operation, + fieldName, + args, + transforms, + skipValidation, + }); + + return delegateRequest({ + request, + schema: subschema, + rootValue, + info, + operation, + fieldName, + returnType, + context, + transforms, + }); } -function delegateToSchemaImplementation({ +export function createDelegatingRequest({ schema: subschema, - rootValue, info, operation = info.operation.operation, fieldName, - returnType = info.returnType, args, - context, transforms = [], skipValidation, -}: IDelegateToSchemaOptions, -): any { +}: ICreateDelegatingRequestOptions): any { let targetSchema: GraphQLSchema; let subschemaConfig: SubschemaConfig; if (isSubschemaConfig(subschema)) { subschemaConfig = subschema; targetSchema = subschemaConfig.schema; - rootValue = rootValue || subschemaConfig.rootValue || info.rootValue; transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse()); } else { targetSchema = subschema; - rootValue = rootValue || info.rootValue; } - const rawDocument: DocumentNode = createDocument( - fieldName, - operation, - info.fieldNodes, - Object.keys(info.fragments).map( - fragmentName => info.fragments[fragmentName], - ), - info.operation.variableDefinitions, - info.operation.name, - ); - - const rawRequest: Request = { - document: rawDocument, - variables: info.variableValues as Record, - }; + const initialRequest = createInitialRequest(fieldName, operation, info); transforms = [ - new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType), ...transforms, new ExpandAbstractTypes(info.schema, targetSchema), ]; @@ -124,27 +136,60 @@ function delegateToSchemaImplementation({ ); } - transforms = transforms.concat([ + transforms.push( new FilterToSchema(targetSchema), new AddTypenameToAbstract(targetSchema), - ]); + ); - const processedRequest = applyRequestTransforms(rawRequest, transforms); + const delegatingRequest = applyRequestTransforms(initialRequest, transforms); if (!skipValidation) { - const errors = validate(targetSchema, processedRequest.document); + const errors = validate(targetSchema, delegatingRequest.document); if (errors.length > 0) { throw errors; } } + return delegatingRequest; +} + +export function delegateRequest({ + request, + schema: subschema, + rootValue, + info, + operation = info.operation.operation, + fieldName, + returnType = info.returnType, + context, + transforms = [], +}: IDelegateRequestOptions): any { + let targetSchema: GraphQLSchema; + let subschemaConfig: SubschemaConfig; + + if (isSubschemaConfig(subschema)) { + subschemaConfig = subschema; + targetSchema = subschemaConfig.schema; + rootValue = rootValue || subschemaConfig.rootValue || info.rootValue; + transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse()); + } else { + targetSchema = subschema; + rootValue = rootValue || info.rootValue; + } + + transforms = [ + new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType), + ...transforms, + ]; + if (operation === 'query' || operation === 'mutation') { + const executor = createExecutor(targetSchema, rootValue, subschemaConfig); const executionResult: ExecutionResult | Promise = executor({ - document: processedRequest.document, + document: request.document, context, - variables: processedRequest.variables + variables: request.variables }); if (executionResult instanceof Promise) { @@ -152,13 +197,15 @@ function delegateToSchemaImplementation({ } else { return applyResultTransforms(executionResult, transforms); } + } else if (operation === 'subscription') { + const subscriber = createSubscriber(targetSchema, rootValue, subschemaConfig); return subscriber({ - document: processedRequest.document, + document: request.document, context, - variables: processedRequest.variables, + variables: request.variables, }).then((subscriptionResult: AsyncIterableIterator | ExecutionResult) => { if (isAsyncIterable(subscriptionResult)) { // "subscribe" to the subscription result and map the result through the transforms @@ -175,20 +222,19 @@ function delegateToSchemaImplementation({ return applyResultTransforms(subscriptionResult, transforms); } }); + } } -function createDocument( +function createInitialRequest( targetField: string, targetOperation: Operation, - originalSelections: ReadonlyArray, - fragments: Array, - variables: ReadonlyArray, - operationName: NameNode, -): DocumentNode { + info: IGraphQLToolsResolveInfo, +): Request { let selections: Array = []; let args: Array = []; + const originalSelections: ReadonlyArray = info.fieldNodes; originalSelections.forEach((field: FieldNode) => { const fieldSelections = field.selectionSet ? field.selectionSet.selections @@ -215,6 +261,7 @@ function createDocument( value: targetField, }, }; + const rootSelectionSet: SelectionSetNode = { kind: Kind.SELECTION_SET, selections: [rootField], @@ -223,15 +270,24 @@ function createDocument( const operationDefinition: OperationDefinitionNode = { kind: Kind.OPERATION_DEFINITION, operation: targetOperation, - variableDefinitions: variables, + variableDefinitions: info.operation.variableDefinitions, selectionSet: rootSelectionSet, - name: operationName, + name: info.operation.name, }; - return { + const fragments: Array = Object.keys(info.fragments).map( + fragmentName => info.fragments[fragmentName], + ); + + const document = { kind: Kind.DOCUMENT, definitions: [operationDefinition, ...fragments], }; + + return { + document, + variables: info.variableValues, + }; } function createExecutor( diff --git a/src/stitching/index.ts b/src/stitching/index.ts index 66e9851258a..02440022bcc 100644 --- a/src/stitching/index.ts +++ b/src/stitching/index.ts @@ -1,7 +1,7 @@ import makeRemoteExecutableSchema, { createResolver as defaultCreateRemoteResolver } from './makeRemoteExecutableSchema'; import introspectSchema from './introspectSchema'; import mergeSchemas from './mergeSchemas'; -import delegateToSchema from './delegateToSchema'; +import { default as delegateToSchema, createDelegatingRequest, delegateRequest } from './delegateToSchema'; import defaultMergedResolver from './defaultMergedResolver'; import { createMergedResolver } from './createMergedResolver'; import { dehoistResult, unwrapResult } from './proxiedResult'; @@ -14,6 +14,8 @@ export { // These are currently undocumented and not part of official API, // but exposed for the community use delegateToSchema, + createDelegatingRequest, + delegateRequest, defaultCreateRemoteResolver, defaultMergedResolver, createMergedResolver, From 2b3896f14799a59631c607ec7043f602faba27b5 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 16 Jan 2020 00:17:43 -0500 Subject: [PATCH 152/250] refactor(ResolveFunctions) complete rename to Resolvers --- src/Interfaces.ts | 9 +++++++++ src/generate/addResolversToSchema.ts | 4 ++-- src/generate/assertResolversPresent.ts | 8 ++++---- src/test/testResolution.ts | 18 +++++++++--------- src/test/testSchemaGenerator.ts | 6 +++--- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 3e001bfdd11..1921c53c36d 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -39,6 +39,7 @@ export interface IResolverValidationOptions { allowResolversNotInSchema?: boolean; } +// for backwards compatibility export interface IAddResolveFunctionsToSchemaOptions { schema: GraphQLSchema; resolvers: IResolvers; @@ -47,6 +48,14 @@ export interface IAddResolveFunctionsToSchemaOptions { inheritResolversFromInterfaces?: boolean; } +export interface IAddResolversToSchemaOptions { + schema: GraphQLSchema; + resolvers: IResolvers; + defaultFieldResolver?: IFieldResolver; + resolverValidationOptions?: IResolverValidationOptions; + inheritResolversFromInterfaces?: boolean; +} + export interface IResolverOptions { fragment?: string; resolve?: IFieldResolver; diff --git a/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index 78096deb12c..9dd5e234271 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -10,7 +10,7 @@ import { import { IResolvers, IResolverValidationOptions, - IAddResolveFunctionsToSchemaOptions, + IAddResolversToSchemaOptions, } from '../Interfaces'; import SchemaError from './SchemaError'; @@ -25,7 +25,7 @@ import { } from '../utils'; function addResolversToSchema( - options: IAddResolveFunctionsToSchemaOptions | GraphQLSchema, + options: IAddResolversToSchemaOptions | GraphQLSchema, legacyInputResolvers?: IResolvers, legacyInputValidationOptions?: IResolverValidationOptions, ) { diff --git a/src/generate/assertResolversPresent.ts b/src/generate/assertResolversPresent.ts index 7879dfbf734..fac561175a1 100644 --- a/src/generate/assertResolversPresent.ts +++ b/src/generate/assertResolversPresent.ts @@ -32,12 +32,12 @@ function assertResolversPresent( forEachField(schema, (field, typeName, fieldName) => { // requires a resolver for *every* field. if (requireResolversForAllFields) { - expectResolveFunction(field, typeName, fieldName); + expectResolver(field, typeName, fieldName); } // 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 resolver on every field that returns a non-scalar type @@ -45,12 +45,12 @@ function assertResolversPresent( requireResolversForNonScalar && !(getNamedType(field.type) instanceof GraphQLScalarType) ) { - expectResolveFunction(field, typeName, fieldName); + expectResolver(field, typeName, fieldName); } }); } -function expectResolveFunction( +function expectResolver( field: GraphQLField, typeName: string, fieldName: string, diff --git a/src/test/testResolution.ts b/src/test/testResolution.ts index d0d3c470eac..7918d94c616 100644 --- a/src/test/testResolution.ts +++ b/src/test/testResolution.ts @@ -43,14 +43,14 @@ describe('Resolve', () => { }, }; const schema = makeExecutableSchema({ typeDefs, resolvers }); - let schemaLevelResolveFunctionCalls = 0; + let schemaLevelResolverCalls = 0; addSchemaLevelResolver(schema, root => { - schemaLevelResolveFunctionCalls += 1; + schemaLevelResolverCalls += 1; return root; }); it('should run the schema level resolver once in a same query', () => { - schemaLevelResolveFunctionCalls = 0; + schemaLevelResolverCalls = 0; const root = 'queryRoot'; return graphql( schema, @@ -66,12 +66,12 @@ describe('Resolve', () => { printRoot: root, printRootAgain: root, }); - assert.equal(schemaLevelResolveFunctionCalls, 1); + assert.equal(schemaLevelResolverCalls, 1); }); }); it('should isolate roots from the different operation types', done => { - schemaLevelResolveFunctionCalls = 0; + schemaLevelResolverCalls = 0; const queryRoot = 'queryRoot'; const mutationRoot = 'mutationRoot'; const subscriptionRoot = 'subscriptionRoot'; @@ -103,11 +103,11 @@ describe('Resolve', () => { subsCbkCalls++; try { if (subsCbkCalls === 1) { - assert.equal(schemaLevelResolveFunctionCalls, 1); + assert.equal(schemaLevelResolverCalls, 1); assert.deepEqual(subsData, { printRoot: subscriptionRoot }); return resolveFirst(); } else if (subsCbkCalls === 2) { - assert.equal(schemaLevelResolveFunctionCalls, 4); + assert.equal(schemaLevelResolverCalls, 4); assert.deepEqual(subsData, { printRoot: subscriptionRoot2, }); @@ -137,7 +137,7 @@ describe('Resolve', () => { ), ) .then(({ data }) => { - assert.equal(schemaLevelResolveFunctionCalls, 2); + assert.equal(schemaLevelResolverCalls, 2); assert.deepEqual(data, { printRoot: queryRoot }); return graphql( schema, @@ -150,7 +150,7 @@ describe('Resolve', () => { ); }) .then(({ data: mutationData }) => { - assert.equal(schemaLevelResolveFunctionCalls, 3); + assert.equal(schemaLevelResolverCalls, 3); assert.deepEqual(mutationData, { printRoot: mutationRoot }); pubsub.publish('printRootChannel', { printRoot: subscriptionRoot2 }); }) diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 6d395996bce..8dc68076a39 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -669,7 +669,7 @@ describe('generating schema from shorthand', () => { } `; - const resolveFunctions = { + const resolvers = { RootQuery: { species: (root: any, { name }: { name: string }) => [ { @@ -681,7 +681,7 @@ describe('generating schema from shorthand', () => { }, }; - const otherResolveFunctions = { + const otherResolvers = { BirdSpecies: { name: (bird: Bird) => bird.name, wingspan: (bird: Bird) => bird.wingspan, @@ -717,7 +717,7 @@ describe('generating schema from shorthand', () => { }; const jsSchema = makeExecutableSchema({ typeDefs: shorthand, - resolvers: [resolveFunctions, otherResolveFunctions], + resolvers: [resolvers, otherResolvers], }); const resultPromise = graphql(jsSchema, testQuery); return resultPromise.then(result => From 9d74412ddc9d5e297e907b65c229d8003388fe72 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 16 Jan 2020 18:29:26 -0500 Subject: [PATCH 153/250] refactor(mergeTypes): provide flag Allows specification that type merging is only performed in a wrapping schema without overriding info object. delegateToSchema will likely be used to obtain additional fields from subschemas, but only the initial wrapping delegateToSchema call should mergeTypes, otherwise type merging will never conclude. Previously this was done by creating a new info object. --- src/Interfaces.ts | 2 ++ src/stitching/checkResultAndHandleErrors.ts | 26 ++++++++++---------- src/stitching/defaultMergedResolver.ts | 2 +- src/stitching/delegateToSchema.ts | 5 +++- src/stitching/resolvers.ts | 1 + src/transforms/CheckResultAndHandleErrors.ts | 4 +++ 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 1921c53c36d..bf1a6c4ba44 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -133,6 +133,7 @@ export interface IDelegateToSchemaOptions { rootValue?: Record; transforms?: Array; skipValidation?: boolean; + mergeTypes?: boolean; } export interface ICreateDelegatingRequestOptions { @@ -155,6 +156,7 @@ export interface IDelegateRequestOptions { returnType?: GraphQLOutputType; context?: TContext; transforms?: Array; + mergeTypes?: boolean; } export type Delegator = ({ document, context, variables }: { diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index d956ae255d5..504464deb4f 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -36,6 +36,7 @@ export function checkResultAndHandleErrors( responseKey?: string, subschema?: GraphQLSchema | SubschemaConfig, returnType: GraphQLOutputType = info.returnType, + mergeTypes?: boolean, ): any { if (!responseKey) { responseKey = getResponseKeyFromInfo(info); @@ -45,7 +46,7 @@ export function checkResultAndHandleErrors( const data = result.data && result.data[responseKey]; const subschemas = [subschema]; - return handleResult(data, errors, subschemas, context, info, returnType); + return handleResult(data, errors, subschemas, context, info, returnType, mergeTypes); } export function handleResult( @@ -55,6 +56,7 @@ export function handleResult( context: Record, info: IGraphQLToolsResolveInfo, returnType = info.returnType, + mergeTypes?: boolean, ): any { const type = getNullableType(returnType); @@ -65,9 +67,9 @@ export function handleResult( if (isLeafType(type)) { return type.parseValue(result); } else if (isCompositeType(type)) { - return handleObject(type, result, errors, subschemas, context, info); + return handleObject(type, result, errors, subschemas, context, info, mergeTypes); } else if (isListType(type)) { - return handleList(type, result, errors, subschemas, context, info); + return handleList(type, result, errors, subschemas, context, info, mergeTypes); } } @@ -93,10 +95,11 @@ export function handleObject( subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, + mergeTypes?: boolean, ) { makeObjectProxiedResult(object, errors, subschemas); - if (info.mergeInfo) { + if (mergeTypes && info.mergeInfo) { return mergeFields( type, object, @@ -116,6 +119,7 @@ function handleList( subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, + mergeTypes?: boolean, ) { const childErrors = getErrorsByPathSegment(errors); @@ -128,6 +132,7 @@ function handleList( subschemas, context, info, + mergeTypes, )); return list; @@ -141,6 +146,7 @@ function handleListMember( subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, + mergeTypes?: boolean, ): any { if (listMember == null) { return handleNull(info.fieldNodes, [...responsePathAsArray(info.path), index], errors); @@ -149,9 +155,9 @@ function handleListMember( if (isLeafType(type)) { return type.parseValue(listMember); } else if (isCompositeType(type)) { - return handleObject(type, listMember, errors, subschemas, context, info); + return handleObject(type, listMember, errors, subschemas, context, info, mergeTypes); } else if (isListType(type)) { - return handleList(type, listMember, errors, subschemas, context, info); + return handleList(type, listMember, errors, subschemas, context, info, mergeTypes); } } @@ -178,13 +184,7 @@ function mergeFields( ); if (remainingSubschemas.length) { const maybePromises = remainingSubschemas.map(subschema => { - return subschema.mergedTypeConfigs[typeName].mergedTypeResolver(subschema, object, context, { - ...info, - mergeInfo: { - ...info.mergeInfo, - mergedTypes: {}, - }, - }); + return subschema.mergedTypeConfigs[typeName].mergedTypeResolver(subschema, object, context, info); }); let containsPromises = false; { diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index bec11fd249b..6f98e80a15b 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -30,5 +30,5 @@ export default function defaultMergedResolver( const result = parent[responseKey]; const subschemas = getSubschemas(parent); - return handleResult(result, errors, subschemas, context, info); + return handleResult(result, errors, subschemas, context, info, undefined, true); } diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index d3f2c99b95d..2cf4e0d1cba 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -67,6 +67,7 @@ export default function delegateToSchema( context, transforms = [], skipValidation, + mergeTypes, } = options; const request = createDelegatingRequest({ @@ -89,6 +90,7 @@ export default function delegateToSchema( returnType, context, transforms, + mergeTypes, }); } @@ -163,6 +165,7 @@ export function delegateRequest({ returnType = info.returnType, context, transforms = [], + mergeTypes, }: IDelegateRequestOptions): any { let targetSchema: GraphQLSchema; let subschemaConfig: SubschemaConfig; @@ -178,7 +181,7 @@ export function delegateRequest({ } transforms = [ - new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType), + new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType, mergeTypes), ...transforms, ]; diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index c8dbf5d1f1a..20abeadda63 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -106,6 +106,7 @@ function defaultCreateProxyingResolver( args, context, info, + mergeTypes: true, }); } diff --git a/src/transforms/CheckResultAndHandleErrors.ts b/src/transforms/CheckResultAndHandleErrors.ts index 259000aab7a..b9c7f71f523 100644 --- a/src/transforms/CheckResultAndHandleErrors.ts +++ b/src/transforms/CheckResultAndHandleErrors.ts @@ -9,6 +9,7 @@ export default class CheckResultAndHandleErrors implements Transform { private fieldName?: string; private subschema?: GraphQLSchema | SubschemaConfig; private returnType?: GraphQLOutputType; + private mergeTypes?: boolean; constructor( info: IGraphQLToolsResolveInfo, @@ -16,12 +17,14 @@ export default class CheckResultAndHandleErrors implements Transform { subschema?: GraphQLSchema | SubschemaConfig, context?: Record, returnType: GraphQLOutputType = info.returnType, + mergeTypes?: boolean, ) { this.context = context; this.info = info; this.fieldName = fieldName; this.subschema = subschema; this.returnType = returnType; + this.mergeTypes = mergeTypes; } public transformResult(result: any): any { @@ -32,6 +35,7 @@ export default class CheckResultAndHandleErrors implements Transform { this.fieldName, this.subschema, this.returnType, + this.mergeTypes, ); } } From 3a23a633351094ffbb58d925f1bde43847e3b5dd Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 19 Jan 2020 01:20:50 -0500 Subject: [PATCH 154/250] refactor(stitching) - Default delegateToSchema operation to the original operation for root fields and query otherwise. - Change mergeTypes option to skipTypeMerging to reverse default so that skipTypeMerging should be specified for mergeTypeResolvers but can be omitted for stitching on gateway - serialize original variable values so that they can be ommited by default --- src/Interfaces.ts | 6 +-- src/stitching/checkResultAndHandleErrors.ts | 30 ++++++------ src/stitching/defaultMergedResolver.ts | 2 +- src/stitching/delegateToSchema.ts | 51 ++++++++++++++------ src/stitching/resolvers.ts | 3 -- src/test/testAlternateMergeSchemas.ts | 2 + src/transforms/CheckResultAndHandleErrors.ts | 8 +-- 7 files changed, 61 insertions(+), 41 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index bf1a6c4ba44..8786b2f4884 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -124,7 +124,7 @@ export function isSubschemaConfig(value: SchemaLikeObject): value is SubschemaCo export interface IDelegateToSchemaOptions { schema: GraphQLSchema | SubschemaConfig; - operation: Operation; + operation?: Operation; fieldName: string; returnType?: GraphQLOutputType; args?: { [key: string]: any }; @@ -133,7 +133,7 @@ export interface IDelegateToSchemaOptions { rootValue?: Record; transforms?: Array; skipValidation?: boolean; - mergeTypes?: boolean; + skipTypeMerging?: boolean; } export interface ICreateDelegatingRequestOptions { @@ -156,7 +156,7 @@ export interface IDelegateRequestOptions { returnType?: GraphQLOutputType; context?: TContext; transforms?: Array; - mergeTypes?: boolean; + skipTypeMerging?: boolean; } export type Delegator = ({ document, context, variables }: { diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 504464deb4f..ecf6b3a3681 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -36,7 +36,7 @@ export function checkResultAndHandleErrors( responseKey?: string, subschema?: GraphQLSchema | SubschemaConfig, returnType: GraphQLOutputType = info.returnType, - mergeTypes?: boolean, + skipTypeMerging?: boolean, ): any { if (!responseKey) { responseKey = getResponseKeyFromInfo(info); @@ -46,7 +46,7 @@ export function checkResultAndHandleErrors( const data = result.data && result.data[responseKey]; const subschemas = [subschema]; - return handleResult(data, errors, subschemas, context, info, returnType, mergeTypes); + return handleResult(data, errors, subschemas, context, info, returnType, skipTypeMerging); } export function handleResult( @@ -56,7 +56,7 @@ export function handleResult( context: Record, info: IGraphQLToolsResolveInfo, returnType = info.returnType, - mergeTypes?: boolean, + skipTypeMerging?: boolean, ): any { const type = getNullableType(returnType); @@ -67,9 +67,9 @@ export function handleResult( if (isLeafType(type)) { return type.parseValue(result); } else if (isCompositeType(type)) { - return handleObject(type, result, errors, subschemas, context, info, mergeTypes); + return handleObject(type, result, errors, subschemas, context, info, skipTypeMerging); } else if (isListType(type)) { - return handleList(type, result, errors, subschemas, context, info, mergeTypes); + return handleList(type, result, errors, subschemas, context, info, skipTypeMerging); } } @@ -95,11 +95,12 @@ export function handleObject( subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, - mergeTypes?: boolean, + skipTypeMerging?: boolean, ) { makeObjectProxiedResult(object, errors, subschemas); - - if (mergeTypes && info.mergeInfo) { + if (skipTypeMerging || !info.mergeInfo) { + return object; + } else { return mergeFields( type, object, @@ -107,8 +108,6 @@ export function handleObject( context, info, ); - } else { - return object; } } @@ -119,9 +118,8 @@ function handleList( subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, - mergeTypes?: boolean, + skipTypeMerging?: boolean, ) { - const childErrors = getErrorsByPathSegment(errors); list = list.map((listMember, index) => handleListMember( @@ -132,7 +130,7 @@ function handleList( subschemas, context, info, - mergeTypes, + skipTypeMerging, )); return list; @@ -146,7 +144,7 @@ function handleListMember( subschemas: Array, context: Record, info: IGraphQLToolsResolveInfo, - mergeTypes?: boolean, + skipTypeMerging?: boolean, ): any { if (listMember == null) { return handleNull(info.fieldNodes, [...responsePathAsArray(info.path), index], errors); @@ -155,9 +153,9 @@ function handleListMember( if (isLeafType(type)) { return type.parseValue(listMember); } else if (isCompositeType(type)) { - return handleObject(type, listMember, errors, subschemas, context, info, mergeTypes); + return handleObject(type, listMember, errors, subschemas, context, info, skipTypeMerging); } else if (isListType(type)) { - return handleList(type, listMember, errors, subschemas, context, info, mergeTypes); + return handleList(type, listMember, errors, subschemas, context, info, skipTypeMerging); } } diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index 6f98e80a15b..bec11fd249b 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -30,5 +30,5 @@ export default function defaultMergedResolver( const result = parent[responseKey]; const subschemas = getSubschemas(parent); - return handleResult(result, errors, subschemas, context, info, undefined, true); + return handleResult(result, errors, subschemas, context, info); } diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 2cf4e0d1cba..0d52be6c394 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -11,6 +11,11 @@ import { validate, GraphQLSchema, ExecutionResult, + GraphQLObjectType, + OperationTypeNode, + typeFromAST, + NamedTypeNode, + GraphQLInputType, } from 'graphql'; import { @@ -45,6 +50,20 @@ import { observableToAsyncIterable } from './observableToAsyncIterable'; import { AddMergedTypeFragments } from '../transforms'; import { isAsyncIterable } from 'iterall'; +import { serializeInputValue } from '../utils'; + +function getDelegatingOperation( + parentType: GraphQLObjectType, + schema: GraphQLSchema +): OperationTypeNode { + if (parentType === schema.getMutationType()) { + return 'mutation'; + } else if (parentType === schema.getSubscriptionType()) { + return 'subscription'; + } else { + return 'query'; + } +} export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, @@ -60,14 +79,14 @@ export default function delegateToSchema( schema: subschema, rootValue, info, - operation = info.operation.operation, + operation = getDelegatingOperation(info.parentType, info.schema), fieldName, returnType = info.returnType, args, context, transforms = [], skipValidation, - mergeTypes, + skipTypeMerging, } = options; const request = createDelegatingRequest({ @@ -90,14 +109,14 @@ export default function delegateToSchema( returnType, context, transforms, - mergeTypes, + skipTypeMerging, }); } export function createDelegatingRequest({ schema: subschema, info, - operation = info.operation.operation, + operation = getDelegatingOperation(info.parentType, info.schema), fieldName, args, transforms = [], @@ -132,10 +151,6 @@ export function createDelegatingRequest({ transforms.push( new AddArgumentsAsVariables(targetSchema, args, info.schema) ); - } else { - console.warn( - '"args" undefined. "args" argument may be required in a future version. Custom scalars or enums may not be properly serialized prior to delegation.' - ); } transforms.push( @@ -160,12 +175,12 @@ export function delegateRequest({ schema: subschema, rootValue, info, - operation = info.operation.operation, + operation, fieldName, returnType = info.returnType, context, transforms = [], - mergeTypes, + skipTypeMerging, }: IDelegateRequestOptions): any { let targetSchema: GraphQLSchema; let subschemaConfig: SubschemaConfig; @@ -181,7 +196,7 @@ export function delegateRequest({ } transforms = [ - new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType, mergeTypes), + new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType, skipTypeMerging), ...transforms, ]; @@ -261,7 +276,7 @@ function createInitialRequest( selectionSet, name: { kind: Kind.NAME, - value: targetField, + value: targetField || info.fieldNodes[0].name.value, }, }; @@ -272,7 +287,7 @@ function createInitialRequest( const operationDefinition: OperationDefinitionNode = { kind: Kind.OPERATION_DEFINITION, - operation: targetOperation, + operation: targetOperation || getDelegatingOperation(info.parentType, info.schema), variableDefinitions: info.operation.variableDefinitions, selectionSet: rootSelectionSet, name: info.operation.name, @@ -287,9 +302,17 @@ function createInitialRequest( definitions: [operationDefinition, ...fragments], }; + const variableValues = info.variableValues; + const variables = {}; + for (const variableDefinition of info.operation.variableDefinitions) { + const varName = variableDefinition.variable.name.value; + const varType = typeFromAST(info.schema, (variableDefinition.type as NamedTypeNode)) as GraphQLInputType; + variables[varName] = serializeInputValue(varType, variableValues[varName]); + } + return { document, - variables: info.variableValues, + variables, }; } diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 20abeadda63..dae0007cfac 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -101,12 +101,9 @@ function defaultCreateProxyingResolver( ): GraphQLFieldResolver { return (parent, args, context, info) => delegateToSchema({ schema: subschemaConfig, - operation, fieldName, - args, context, info, - mergeTypes: true, }); } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 184ce93aa15..03f625cf2be 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -1760,6 +1760,7 @@ describe('mergeTypes', () => { args: { id: originalResult.id }, context, info, + skipTypeMerging: true, }) } }, @@ -1777,6 +1778,7 @@ describe('mergeTypes', () => { args: { id: originalResult.id }, context, info, + skipTypeMerging: true, }) } }, diff --git a/src/transforms/CheckResultAndHandleErrors.ts b/src/transforms/CheckResultAndHandleErrors.ts index b9c7f71f523..3d8d5b0cdd9 100644 --- a/src/transforms/CheckResultAndHandleErrors.ts +++ b/src/transforms/CheckResultAndHandleErrors.ts @@ -9,7 +9,7 @@ export default class CheckResultAndHandleErrors implements Transform { private fieldName?: string; private subschema?: GraphQLSchema | SubschemaConfig; private returnType?: GraphQLOutputType; - private mergeTypes?: boolean; + private typeMerge?: boolean; constructor( info: IGraphQLToolsResolveInfo, @@ -17,14 +17,14 @@ export default class CheckResultAndHandleErrors implements Transform { subschema?: GraphQLSchema | SubschemaConfig, context?: Record, returnType: GraphQLOutputType = info.returnType, - mergeTypes?: boolean, + typeMerge?: boolean, ) { this.context = context; this.info = info; this.fieldName = fieldName; this.subschema = subschema; this.returnType = returnType; - this.mergeTypes = mergeTypes; + this.typeMerge = typeMerge; } public transformResult(result: any): any { @@ -35,7 +35,7 @@ export default class CheckResultAndHandleErrors implements Transform { this.fieldName, this.subschema, this.returnType, - this.mergeTypes, + this.typeMerge, ); } } From 8754452b49cec6a1b85a3adf5d0ba78cc834c6bc Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 20 Jan 2020 13:13:33 -0500 Subject: [PATCH 155/250] fix(transforms): refactor TransformRootFields to allow flexible transform ordering Fixes #27, now possible because info object is not modified, see https://github.com/yaacovCR/graphql-tools-fork/commit/1676ab010204ab2c50ae07d13ecf560b73ff5f76 --- src/Interfaces.ts | 2 +- src/stitching/delegateToSchema.ts | 9 ++- src/stitching/resolvers.ts | 1 - src/test/testAlternateMergeSchemas.ts | 12 ++-- src/transforms/RenameRootFields.ts | 5 ++ src/transforms/TransformObjectFields.ts | 3 - src/transforms/TransformRootFields.ts | 88 ++++++------------------- 7 files changed, 36 insertions(+), 84 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 8786b2f4884..9a2a4bf5580 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -125,7 +125,7 @@ export function isSubschemaConfig(value: SchemaLikeObject): value is SubschemaCo export interface IDelegateToSchemaOptions { schema: GraphQLSchema | SubschemaConfig; operation?: Operation; - fieldName: string; + fieldName?: string; returnType?: GraphQLOutputType; args?: { [key: string]: any }; context?: TContext; diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 0d52be6c394..25738ada262 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -80,7 +80,7 @@ export default function delegateToSchema( rootValue, info, operation = getDelegatingOperation(info.parentType, info.schema), - fieldName, + fieldName = info.fieldName, returnType = info.returnType, args, context, @@ -117,7 +117,7 @@ export function createDelegatingRequest({ schema: subschema, info, operation = getDelegatingOperation(info.parentType, info.schema), - fieldName, + fieldName = info.fieldName, args, transforms = [], skipValidation, @@ -175,8 +175,8 @@ export function delegateRequest({ schema: subschema, rootValue, info, - operation, - fieldName, + operation = getDelegatingOperation(info.parentType, info.schema), + fieldName = info.fieldName, returnType = info.returnType, context, transforms = [], @@ -229,7 +229,6 @@ export function delegateRequest({ // "subscribe" to the subscription result and map the result through the transforms return mapAsyncIterator(subscriptionResult, result => { const transformedResult = applyResultTransforms(result, transforms); - // wrap with fieldName to return for an additional round of resolutioon // with payload as rootValue return { diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index dae0007cfac..086f0b3009e 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -101,7 +101,6 @@ function defaultCreateProxyingResolver( ): GraphQLFieldResolver { return (parent, args, context, info) => delegateToSchema({ schema: subschemaConfig, - fieldName, context, info, }); diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 03f625cf2be..e51232528cc 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -443,18 +443,18 @@ describe('transform object fields', () => { } return true; }), - new RenameObjectFields((_typeName, fieldName) => { - if (fieldName === 'camel_case') { - return 'camelCase'; - } - return fieldName; - }), new RenameRootFields((_operation, fieldName) => { if (fieldName === 'allItems') { return 'items'; } return fieldName; }), + new RenameObjectFields((_typeName, fieldName) => { + if (fieldName === 'camel_case') { + return 'camelCase'; + } + return fieldName; + }), ]); }); diff --git a/src/transforms/RenameRootFields.ts b/src/transforms/RenameRootFields.ts index c2fb9b39e19..6fb7d9e60b6 100644 --- a/src/transforms/RenameRootFields.ts +++ b/src/transforms/RenameRootFields.ts @@ -1,4 +1,5 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; +import { Request } from '../Interfaces'; import { Transform } from './transforms'; import TransformRootFields from './TransformRootFields'; @@ -28,4 +29,8 @@ export default class RenameRootFields implements Transform { public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { return this.transformer.transformSchema(originalSchema); } + + public transformRequest(originalRequest: Request): Request { + return this.transformer.transformRequest(originalRequest); + } } diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index cb3c622a291..b395c452987 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -57,9 +57,6 @@ export default class TransformObjectFields implements Transform { public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { this.transformedSchema = visitSchema(originalSchema, { - [VisitSchemaKind.ROOT_OBJECT]: () => { - return undefined; - }, [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => { return this.transformFields(type, this.objectFieldTransformer); } diff --git a/src/transforms/TransformRootFields.ts b/src/transforms/TransformRootFields.ts index eea4d067e93..44f5c39ec84 100644 --- a/src/transforms/TransformRootFields.ts +++ b/src/transforms/TransformRootFields.ts @@ -1,13 +1,12 @@ import { - GraphQLObjectType, GraphQLSchema, GraphQLField, GraphQLFieldConfig, } from 'graphql'; -import isEmptyObject from '../utils/isEmptyObject'; +import { Request } from '../Interfaces'; import { Transform } from './transforms'; -import { visitSchema } from '../utils/visitSchema'; -import { VisitSchemaKind } from '../Interfaces'; +import { TransformObjectFields } from '.'; +import { FieldNodeTransformer } from './TransformObjectFields'; export type RootTransformer = ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -22,75 +21,28 @@ export type RootTransformer = ( type RenamedField = { name: string; field?: GraphQLFieldConfig }; export default class TransformRootFields implements Transform { - private transform: RootTransformer; + private transformer: TransformObjectFields; - constructor(transform: RootTransformer) { - this.transform = transform; + constructor(rootFieldTransformer: RootTransformer, fieldNodeTransformer?: FieldNodeTransformer) { + const rootToObjectFieldTransformer = + (typeName: string, fieldName: string, field: GraphQLField) => { + if (typeName === 'Query' || typeName === 'Mutation' || typeName === 'Subscription') { + return rootFieldTransformer(typeName, fieldName, field); + } else { + return undefined; + } + }; + this.transformer = new TransformObjectFields( + rootToObjectFieldTransformer, + fieldNodeTransformer, + ); } public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { - return visitSchema(originalSchema, { - [VisitSchemaKind.QUERY]: (type: GraphQLObjectType) => { - return transformFields( - type, - (fieldName: string, field: GraphQLField) => - this.transform('Query', fieldName, field), - ); - }, - [VisitSchemaKind.MUTATION]: (type: GraphQLObjectType) => { - return transformFields( - type, - (fieldName: string, field: GraphQLField) => - this.transform('Mutation', fieldName, field), - ); - }, - [VisitSchemaKind.SUBSCRIPTION]: (type: GraphQLObjectType) => { - return transformFields( - type, - (fieldName: string, field: GraphQLField) => - this.transform('Subscription', fieldName, field), - ); - }, - }); + return this.transformer.transformSchema(originalSchema); } -} -function transformFields( - type: GraphQLObjectType, - transformer: ( - fieldName: string, - field: GraphQLField, - ) => - | GraphQLFieldConfig - | RenamedField - | null - | undefined, -): GraphQLObjectType { - const typeConfig = type.toConfig(); - const fields = type.getFields(); - const newFields = {}; - Object.keys(fields).forEach(fieldName => { - const field = fields[fieldName]; - const newField = transformer(fieldName, field); - if (typeof newField === 'undefined') { - newFields[fieldName] = typeConfig.fields[fieldName]; - } else if (newField !== null) { - if ((newField as RenamedField).name) { - newFields[(newField as RenamedField).name] = - (newField as RenamedField).field ? - (newField as RenamedField).field : - typeConfig.fields[fieldName]; - } else { - newFields[fieldName] = newField; - } - } - }); - if (isEmptyObject(newFields)) { - return null; - } else { - return new GraphQLObjectType({ - ...type, - fields: newFields, - }); + public transformRequest(originalRequest: Request): Request { + return this.transformer.transformRequest(originalRequest); } } From 60e04d64694417d9d32a4374203b5c5d28863a3c Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 20 Jan 2020 13:19:11 -0500 Subject: [PATCH 156/250] refactor(transforms): standardize sourceSchema argument --- src/transforms/AddArgumentsAsVariables.ts | 16 ++++++++-------- src/transforms/ExpandAbstractTypes.ts | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index 220fc6ba04b..34d29b03979 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -24,12 +24,12 @@ import { transformInputValue } from '../utils'; export default class AddArgumentsAsVariablesTransform implements Transform { private targetSchema: GraphQLSchema; private args: { [key: string]: any }; - private newSchema: GraphQLSchema; + private sourceSchema: GraphQLSchema; - constructor(targetSchema: GraphQLSchema, args: { [key: string]: any }, newSchema: GraphQLSchema) { + constructor(targetSchema: GraphQLSchema, args: { [key: string]: any }, sourceSchema: GraphQLSchema) { this.targetSchema = targetSchema; this.args = args; - this.newSchema = newSchema; + this.sourceSchema = sourceSchema; } public transformRequest(originalRequest: Request): Request { @@ -37,7 +37,7 @@ export default class AddArgumentsAsVariablesTransform implements Transform { this.targetSchema, originalRequest.document, this.args, - this.newSchema, + this.sourceSchema, ); const variables = { ...originalRequest.variables, @@ -54,7 +54,7 @@ function addVariablesToRootField( targetSchema: GraphQLSchema, document: DocumentNode, args: { [key: string]: any }, - newSchema: GraphQLSchema, + sourceSchema: GraphQLSchema, ): { document: DocumentNode; newVariables: { [key: string]: any }; @@ -138,13 +138,13 @@ function addVariablesToRootField( }, type: typeToAst(argument.type), }; - if (newSchema) { + if (sourceSchema) { newVariables[variableName] = transformInputValue( argument.type, args[argument.name], (t, v) => { - const newType = newSchema.getType(t.name) as GraphQLEnumType | GraphQLScalarType; - return newType ? newType.serialize(v) : v; + const type = sourceSchema.getType(t.name) as GraphQLEnumType | GraphQLScalarType; + return type ? type.serialize(v) : v; } ); } else { diff --git a/src/transforms/ExpandAbstractTypes.ts b/src/transforms/ExpandAbstractTypes.ts index ddc5aafeadc..1ca923383a2 100644 --- a/src/transforms/ExpandAbstractTypes.ts +++ b/src/transforms/ExpandAbstractTypes.ts @@ -23,9 +23,9 @@ export default class ExpandAbstractTypes implements Transform { private mapping: TypeMapping; private reverseMapping: TypeMapping; - constructor(transformedSchema: GraphQLSchema, targetSchema: GraphQLSchema) { + constructor(sourceSchema: GraphQLSchema, targetSchema: GraphQLSchema) { this.targetSchema = targetSchema; - this.mapping = extractPossibleTypes(transformedSchema, targetSchema); + this.mapping = extractPossibleTypes(sourceSchema, targetSchema); this.reverseMapping = flipMapping(this.mapping); } @@ -44,17 +44,17 @@ export default class ExpandAbstractTypes implements Transform { } function extractPossibleTypes( - transformedSchema: GraphQLSchema, + sourceSchema: GraphQLSchema, targetSchema: GraphQLSchema, ) { - const typeMap = transformedSchema.getTypeMap(); + const typeMap = sourceSchema.getTypeMap(); const mapping: TypeMapping = {}; Object.keys(typeMap).forEach(typeName => { const type = typeMap[typeName]; if (isAbstractType(type)) { const targetType = targetSchema.getType(typeName); if (!isAbstractType(targetType)) { - const implementations = transformedSchema.getPossibleTypes(type) || []; + const implementations = sourceSchema.getPossibleTypes(type) || []; mapping[typeName] = implementations .filter(impl => targetSchema.getType(impl.name)) .map(impl => impl.name); From 54b6a9c4d61da17a2b86a84211120481bee407d0 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 20 Jan 2020 13:24:11 -0500 Subject: [PATCH 157/250] fix(delegateToSchema): standardize args format now that all directly proxied args are serialized automatically, including variables, appropriate to standardize args passed to delegateToSchema. Previously, the typeName would be inferred from the proxy target, but serialization would be according to the gateway schema if the type names matched. This would introduce a lot of edge cases and was only necessary as a workaround to facilitate serialization of variables. Now we can standardize as follows: args format is the internal representation of the proxy target schema. By default, local schemas internal representation will be imported, so that local schema custom enums/scalars do not require manual serialization. For remote schemas should, the internal and external representation is the same, so this is the same as the external representation. If the gateway introduces a new internal representation, args must be converted to the old internal representation manually. addresses #34 reverts https://github.com/yaacovCR/graphql-tools-fork/commit/fd1bd8fb55a357258e04d026bad661d39b316752#diff-06b180be3290dd53bea4c1c98c9dfdd2 --- src/stitching/delegateToSchema.ts | 2 +- src/transforms/AddArgumentsAsVariables.ts | 30 +++++------------------ 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 25738ada262..210496e0f4b 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -149,7 +149,7 @@ export function createDelegatingRequest({ if (args) { transforms.push( - new AddArgumentsAsVariables(targetSchema, args, info.schema) + new AddArgumentsAsVariables(targetSchema, args) ); } diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index 34d29b03979..15b57639ea9 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -14,22 +14,18 @@ import { SelectionNode, TypeNode, VariableDefinitionNode, - GraphQLEnumType, - GraphQLScalarType, } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; -import { transformInputValue } from '../utils'; +import { serializeInputValue } from '../utils'; export default class AddArgumentsAsVariablesTransform implements Transform { private targetSchema: GraphQLSchema; private args: { [key: string]: any }; - private sourceSchema: GraphQLSchema; - constructor(targetSchema: GraphQLSchema, args: { [key: string]: any }, sourceSchema: GraphQLSchema) { + constructor(targetSchema: GraphQLSchema, args: { [key: string]: any }) { this.targetSchema = targetSchema; this.args = args; - this.sourceSchema = sourceSchema; } public transformRequest(originalRequest: Request): Request { @@ -37,7 +33,6 @@ export default class AddArgumentsAsVariablesTransform implements Transform { this.targetSchema, originalRequest.document, this.args, - this.sourceSchema, ); const variables = { ...originalRequest.variables, @@ -54,7 +49,6 @@ function addVariablesToRootField( targetSchema: GraphQLSchema, document: DocumentNode, args: { [key: string]: any }, - sourceSchema: GraphQLSchema, ): { document: DocumentNode; newVariables: { [key: string]: any }; @@ -138,22 +132,10 @@ function addVariablesToRootField( }, type: typeToAst(argument.type), }; - if (sourceSchema) { - newVariables[variableName] = transformInputValue( - argument.type, - args[argument.name], - (t, v) => { - const type = sourceSchema.getType(t.name) as GraphQLEnumType | GraphQLScalarType; - return type ? type.serialize(v) : v; - } - ); - } else { - // tslint:disable-next-line:max-line-length - console.warn( - 'AddArgumentsAsVariables should be passed the wrapping schema so that arguments can be properly serialized prior to delegation.' - ); - newVariables[variableName] = args[argument.name]; - } + newVariables[variableName] = serializeInputValue( + argument.type, + args[argument.name], + ); } }); From dedb1ae869a931779121643c88e58ea4db8d7137 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 20 Jan 2020 21:11:33 -0500 Subject: [PATCH 158/250] chore(deps): remove unused dependency --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 8a36ee04093..03d63a57623 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "dependencies": { "apollo-link": "^1.2.13", "apollo-link-http-common": "^0.2.15", - "apollo-utilities": "^1.3.3", "deprecated-decorator": "^0.1.6", "extract-files": "^7.0.0", "form-data": "^3.0.0", From cefa4dbb1931cae0277bdf9ffd37fdf7bdf1fe30 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 20 Jan 2020 21:13:24 -0500 Subject: [PATCH 159/250] chore(deps): upgrade deps --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 03d63a57623..3d72a68eeb1 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "form-data": "^3.0.0", "iterall": "^1.3.0", "node-fetch": "^2.6.0", - "uuid": "^3.3.3" + "uuid": "^3.4.0" }, "peerDependencies": { "graphql": "^14.2.0" @@ -69,7 +69,7 @@ "@types/extract-files": "^3.1.0", "@types/graphql-upload": "^8.0.3", "@types/mocha": "^5.2.7", - "@types/node": "^13.1.4", + "@types/node": "^13.1.8", "@types/node-fetch": "^2.5.4", "@types/supertest": "^2.0.8", "@types/uuid": "^3.4.6", @@ -92,7 +92,7 @@ "source-map-support": "^0.5.16", "standard-version": "^7.0.1", "tslint": "^5.20.1", - "typescript": "3.7.4", + "typescript": "3.7.5", "zen-observable-ts": "^0.8.20" } } From d5f1a9a7077966848dd43ca051031de9f205a61e Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 20 Jan 2020 22:30:40 -0500 Subject: [PATCH 160/250] refactor(AddArgumentsAsVariables): to AddArguments Why add arguments as variables when you can just add as an AST with graphql utility method? --- docs/source/schema-transforms.md | 2 +- src/stitching/delegateToSchema.ts | 4 +- src/transforms/AddArguments.ts | 108 ++++++++++++++++++++++++++++++ src/transforms/index.ts | 15 +++-- 4 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 src/transforms/AddArguments.ts diff --git a/docs/source/schema-transforms.md b/docs/source/schema-transforms.md index 33105f6cdf0..9e04189b463 100644 --- a/docs/source/schema-transforms.md +++ b/docs/source/schema-transforms.md @@ -301,7 +301,7 @@ transforms: [ The following transforms are automatically applied by `delegateToSchema` during schema delegation, to translate between new and old types and fields: -* `AddArgumentsAsVariables`: Given a schema and arguments passed to a root field, make those arguments document variables. +* `AddArguments`: Given a schema and arguments passed to a root field, add arguments to request. * `ExpandAbstractTypes`: If an abstract type within a document does not exist in the inner schema, expand the type to each and any of its implementations that do exist in the inner schema. * `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, necessary for type resolution of interfaces within the outer schema to work. diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 210496e0f4b..43c5cd9749d 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -36,7 +36,7 @@ import { applyResultTransforms, } from '../transforms/transforms'; -import AddArgumentsAsVariables from '../transforms/AddArgumentsAsVariables'; +import AddArguments from '../transforms/AddArguments'; import FilterToSchema from '../transforms/FilterToSchema'; import AddTypenameToAbstract from '../transforms/AddTypenameToAbstract'; import CheckResultAndHandleErrors from '../transforms/CheckResultAndHandleErrors'; @@ -149,7 +149,7 @@ export function createDelegatingRequest({ if (args) { transforms.push( - new AddArgumentsAsVariables(targetSchema, args) + new AddArguments(targetSchema, args) ); } diff --git a/src/transforms/AddArguments.ts b/src/transforms/AddArguments.ts new file mode 100644 index 00000000000..746c400ca63 --- /dev/null +++ b/src/transforms/AddArguments.ts @@ -0,0 +1,108 @@ +import { + ArgumentNode, + DocumentNode, + FragmentDefinitionNode, + GraphQLArgument, + GraphQLField, + GraphQLObjectType, + GraphQLSchema, + Kind, + OperationDefinitionNode, + SelectionNode, + astFromValue, +} from 'graphql'; +import { Request } from '../Interfaces'; +import { Transform } from './transforms'; + +export default class AddArgumentsTransform implements Transform { + private targetSchema: GraphQLSchema; + private args: { [key: string]: any }; + + constructor(targetSchema: GraphQLSchema, args: { [key: string]: any }) { + this.targetSchema = targetSchema; + this.args = args; + } + + public transformRequest(originalRequest: Request): Request { + const document = addVariablesToRootField( + this.targetSchema, + originalRequest.document, + this.args, + ); + return { + ...originalRequest, + document, + }; + } +} + +function addVariablesToRootField( + targetSchema: GraphQLSchema, + document: DocumentNode, + args: { [key: string]: any }, +): DocumentNode { + const operations: Array< + OperationDefinitionNode + > = document.definitions.filter( + def => def.kind === Kind.OPERATION_DEFINITION, + ) as Array; + const fragments: Array = document.definitions.filter( + def => def.kind === Kind.FRAGMENT_DEFINITION, + ) as Array; + + const newOperations = operations.map((operation: OperationDefinitionNode) => { + let type: GraphQLObjectType; + if (operation.operation === 'subscription') { + type = targetSchema.getSubscriptionType(); + } else if (operation.operation === 'mutation') { + type = targetSchema.getMutationType(); + } else { + type = targetSchema.getQueryType(); + } + + const newSelectionSet: Array = []; + + operation.selectionSet.selections.forEach((selection: SelectionNode) => { + if (selection.kind === Kind.FIELD) { + let newArgs: { [name: string]: ArgumentNode } = {}; + selection.arguments.forEach((argument: ArgumentNode) => { + newArgs[argument.name.value] = argument; + }); + const name: string = selection.name.value; + const field: GraphQLField = type.getFields()[name]; + field.args.forEach((argument: GraphQLArgument) => { + if (argument.name in args) { + newArgs[argument.name] = { + kind: Kind.ARGUMENT, + name: { + kind: Kind.NAME, + value: argument.name, + }, + value: astFromValue(args[argument.name], argument.type), + }; + } + }); + + newSelectionSet.push({ + ...selection, + arguments: Object.keys(newArgs).map(argName => newArgs[argName]), + }); + } else { + newSelectionSet.push(selection); + } + }); + + return { + ...operation, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: newSelectionSet, + }, + }; + }); + + return { + ...document, + definitions: [...newOperations, ...fragments], + }; +} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 0f714300df2..50d63bf43f0 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -4,12 +4,13 @@ export { Transform }; export { default as filterSchema } from './filterSchema'; export { default as transformSchema, wrapSchema } from './transformSchema'; -export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErrors'; +export { default as ExpandAbstractTypes } from './ExpandAbstractTypes'; export { default as AddReplacementFragments } from './AddReplacementFragments'; export { default as AddMergedTypeFragments } from './AddMergedTypeFragments'; -export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; +export { default as AddArguments } from './AddArguments'; export { default as FilterToSchema } from './FilterToSchema'; +export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; export { default as RenameTypes } from './RenameTypes'; export { default as FilterTypes } from './FilterTypes'; @@ -19,9 +20,6 @@ export { default as FilterRootFields } from './FilterRootFields'; export { default as TransformObjectFields } from './TransformObjectFields'; export { default as RenameObjectFields } from './RenameObjectFields'; export { default as FilterObjectFields } from './FilterObjectFields'; -export { default as ExpandAbstractTypes } from './ExpandAbstractTypes'; -export { default as ExtractField } from './ExtractField'; -export { default as WrapQuery } from './WrapQuery'; export { default as TransformQuery } from './TransformQuery'; export { default as ExtendSchema } from './ExtendSchema'; @@ -29,4 +27,11 @@ export { default as WrapType } from './WrapType'; export { default as WrapFields } from './WrapFields'; export { default as HoistField } from './HoistField'; export { default as MapFields } from './MapFields'; + +// superseded by AddReplacementFragments export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; +// superseded by AddArguments +export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; +// superseded by TransformQuery +export { default as WrapQuery } from './WrapQuery'; +export { default as ExtractField } from './ExtractField'; From bd907f9863a3e4ac1cb3ea4f251832235c041186 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 21 Jan 2020 21:46:40 -0500 Subject: [PATCH 161/250] refactor(AddArguments): transform not necessary Additional arguments can be handled within initial proxy query creation just like a change in operation or fieldName. --- docs/source/schema-transforms.md | 1 - src/stitching/delegateToSchema.ts | 63 +++++++++++++---- src/transforms/AddArguments.ts | 108 ------------------------------ src/transforms/index.ts | 5 +- 4 files changed, 51 insertions(+), 126 deletions(-) delete mode 100644 src/transforms/AddArguments.ts diff --git a/docs/source/schema-transforms.md b/docs/source/schema-transforms.md index 9e04189b463..0c47ce6c83f 100644 --- a/docs/source/schema-transforms.md +++ b/docs/source/schema-transforms.md @@ -301,7 +301,6 @@ transforms: [ The following transforms are automatically applied by `delegateToSchema` during schema delegation, to translate between new and old types and fields: -* `AddArguments`: Given a schema and arguments passed to a root field, add arguments to request. * `ExpandAbstractTypes`: If an abstract type within a document does not exist in the inner schema, expand the type to each and any of its implementations that do exist in the inner schema. * `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, necessary for type resolution of interfaces within the outer schema to work. diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 43c5cd9749d..baad38b1f39 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -16,6 +16,9 @@ import { typeFromAST, NamedTypeNode, GraphQLInputType, + GraphQLField, + GraphQLArgument, + astFromValue, } from 'graphql'; import { @@ -36,7 +39,6 @@ import { applyResultTransforms, } from '../transforms/transforms'; -import AddArguments from '../transforms/AddArguments'; import FilterToSchema from '../transforms/FilterToSchema'; import AddTypenameToAbstract from '../transforms/AddTypenameToAbstract'; import CheckResultAndHandleErrors from '../transforms/CheckResultAndHandleErrors'; @@ -133,7 +135,7 @@ export function createDelegatingRequest({ targetSchema = subschema; } - const initialRequest = createInitialRequest(fieldName, operation, info); + const initialRequest = createInitialRequest(info, operation, fieldName, targetSchema, args); transforms = [ ...transforms, @@ -147,12 +149,6 @@ export function createDelegatingRequest({ ); } - if (args) { - transforms.push( - new AddArguments(targetSchema, args) - ); - } - transforms.push( new FilterToSchema(targetSchema), new AddTypenameToAbstract(targetSchema), @@ -244,12 +240,14 @@ export function delegateRequest({ } function createInitialRequest( - targetField: string, - targetOperation: Operation, info: IGraphQLToolsResolveInfo, + targetOperation: Operation, + targetField: string, + targetSchema: GraphQLSchema, + newArgsMap: Record, ): Request { let selections: Array = []; - let args: Array = []; + let args: ReadonlyArray = info.fieldNodes[0].arguments || []; const originalSelections: ReadonlyArray = info.fieldNodes; originalSelections.forEach((field: FieldNode) => { @@ -257,10 +255,9 @@ function createInitialRequest( ? field.selectionSet.selections : []; selections = selections.concat(fieldSelections); - args = args.concat(field.arguments || []); }); - let selectionSet = null; + let selectionSet = undefined; if (selections.length > 0) { selectionSet = { kind: Kind.SELECTION_SET, @@ -271,7 +268,7 @@ function createInitialRequest( const rootField: FieldNode = { kind: Kind.FIELD, alias: null, - arguments: args, + arguments: newArgsMap ? updateArguments(targetSchema, targetOperation, targetField, args, newArgsMap) : args, selectionSet, name: { kind: Kind.NAME, @@ -315,6 +312,44 @@ function createInitialRequest( }; } +function updateArguments( + schema: GraphQLSchema, + operation: OperationTypeNode, + fieldName: string, + argumentNodes: ReadonlyArray, + newArgsMap: Record, +): Array { + let type: GraphQLObjectType; + if (operation === 'subscription') { + type = schema.getSubscriptionType(); + } else if (operation === 'mutation') { + type = schema.getMutationType(); + } else { + type = schema.getQueryType(); + } + + const newArgs: Record = {}; + argumentNodes.forEach((argument: ArgumentNode) => { + newArgs[argument.name.value] = argument; + }); + + const field: GraphQLField = type.getFields()[fieldName]; + field.args.forEach((argument: GraphQLArgument) => { + if (newArgsMap[argument.name]) { + newArgs[argument.name] = { + kind: Kind.ARGUMENT, + name: { + kind: Kind.NAME, + value: argument.name, + }, + value: astFromValue(newArgsMap[argument.name], argument.type), + }; + } + }); + + return Object.keys(newArgs).map(argName => newArgs[argName]); +} + function createExecutor( schema: GraphQLSchema, rootValue: Record, diff --git a/src/transforms/AddArguments.ts b/src/transforms/AddArguments.ts deleted file mode 100644 index 746c400ca63..00000000000 --- a/src/transforms/AddArguments.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { - ArgumentNode, - DocumentNode, - FragmentDefinitionNode, - GraphQLArgument, - GraphQLField, - GraphQLObjectType, - GraphQLSchema, - Kind, - OperationDefinitionNode, - SelectionNode, - astFromValue, -} from 'graphql'; -import { Request } from '../Interfaces'; -import { Transform } from './transforms'; - -export default class AddArgumentsTransform implements Transform { - private targetSchema: GraphQLSchema; - private args: { [key: string]: any }; - - constructor(targetSchema: GraphQLSchema, args: { [key: string]: any }) { - this.targetSchema = targetSchema; - this.args = args; - } - - public transformRequest(originalRequest: Request): Request { - const document = addVariablesToRootField( - this.targetSchema, - originalRequest.document, - this.args, - ); - return { - ...originalRequest, - document, - }; - } -} - -function addVariablesToRootField( - targetSchema: GraphQLSchema, - document: DocumentNode, - args: { [key: string]: any }, -): DocumentNode { - const operations: Array< - OperationDefinitionNode - > = document.definitions.filter( - def => def.kind === Kind.OPERATION_DEFINITION, - ) as Array; - const fragments: Array = document.definitions.filter( - def => def.kind === Kind.FRAGMENT_DEFINITION, - ) as Array; - - const newOperations = operations.map((operation: OperationDefinitionNode) => { - let type: GraphQLObjectType; - if (operation.operation === 'subscription') { - type = targetSchema.getSubscriptionType(); - } else if (operation.operation === 'mutation') { - type = targetSchema.getMutationType(); - } else { - type = targetSchema.getQueryType(); - } - - const newSelectionSet: Array = []; - - operation.selectionSet.selections.forEach((selection: SelectionNode) => { - if (selection.kind === Kind.FIELD) { - let newArgs: { [name: string]: ArgumentNode } = {}; - selection.arguments.forEach((argument: ArgumentNode) => { - newArgs[argument.name.value] = argument; - }); - const name: string = selection.name.value; - const field: GraphQLField = type.getFields()[name]; - field.args.forEach((argument: GraphQLArgument) => { - if (argument.name in args) { - newArgs[argument.name] = { - kind: Kind.ARGUMENT, - name: { - kind: Kind.NAME, - value: argument.name, - }, - value: astFromValue(args[argument.name], argument.type), - }; - } - }); - - newSelectionSet.push({ - ...selection, - arguments: Object.keys(newArgs).map(argName => newArgs[argName]), - }); - } else { - newSelectionSet.push(selection); - } - }); - - return { - ...operation, - selectionSet: { - kind: Kind.SELECTION_SET, - selections: newSelectionSet, - }, - }; - }); - - return { - ...document, - definitions: [...newOperations, ...fragments], - }; -} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 50d63bf43f0..86a02b31940 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -8,7 +8,6 @@ export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErr export { default as ExpandAbstractTypes } from './ExpandAbstractTypes'; export { default as AddReplacementFragments } from './AddReplacementFragments'; export { default as AddMergedTypeFragments } from './AddMergedTypeFragments'; -export { default as AddArguments } from './AddArguments'; export { default as FilterToSchema } from './FilterToSchema'; export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; @@ -28,10 +27,10 @@ export { default as WrapFields } from './WrapFields'; export { default as HoistField } from './HoistField'; export { default as MapFields } from './MapFields'; +// implemented directly within initial query proxy creation +export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; // superseded by AddReplacementFragments export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; -// superseded by AddArguments -export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; // superseded by TransformQuery export { default as WrapQuery } from './WrapQuery'; export { default as ExtractField } from './ExtractField'; From 2a142e5637771e8639502587addde54a9b9e97bb Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 21 Jan 2020 21:53:21 -0500 Subject: [PATCH 162/250] refactor(delegateToSchema): organize imports Also export useful transform application methods. --- src/stitching/delegateToSchema.ts | 19 +++++++++---------- src/transforms/index.ts | 8 ++++++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index baad38b1f39..e1071410c86 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -35,24 +35,23 @@ import { } from '../Interfaces'; import { + ExpandAbstractTypes, + FilterToSchema, + AddReplacementFragments, + AddMergedTypeFragments, + AddTypenameToAbstract, + CheckResultAndHandleErrors, applyRequestTransforms, applyResultTransforms, -} from '../transforms/transforms'; +} from '../transforms'; -import FilterToSchema from '../transforms/FilterToSchema'; -import AddTypenameToAbstract from '../transforms/AddTypenameToAbstract'; -import CheckResultAndHandleErrors from '../transforms/CheckResultAndHandleErrors'; -import mapAsyncIterator from './mapAsyncIterator'; -import ExpandAbstractTypes from '../transforms/ExpandAbstractTypes'; -import AddReplacementFragments from '../transforms/AddReplacementFragments'; +import { serializeInputValue } from '../utils'; import { ApolloLink, execute as executeLink } from 'apollo-link'; import linkToFetcher from './linkToFetcher'; import { observableToAsyncIterable } from './observableToAsyncIterable'; -import { AddMergedTypeFragments } from '../transforms'; - import { isAsyncIterable } from 'iterall'; -import { serializeInputValue } from '../utils'; +import mapAsyncIterator from './mapAsyncIterator'; function getDelegatingOperation( parentType: GraphQLObjectType, diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 86a02b31940..91bd8595690 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -1,5 +1,9 @@ -import { Transform } from './transforms'; -export { Transform }; +export { + Transform, + applySchemaTransforms, + applyRequestTransforms, + applyResultTransforms, +} from './transforms'; export { default as filterSchema } from './filterSchema'; export { default as transformSchema, wrapSchema } from './transformSchema'; From 284eca34668bbc17698a721e5e5726722433d061 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 21 Jan 2020 22:12:39 -0500 Subject: [PATCH 163/250] fix(delegateRequest) delegateRequest should take a query created for the targetSchema prior to application of request transforms. If request batching/caching/manipulation is to be performed, it should be possible to do this before or after request transforms are applied. Now createRequestFromInfo and the underlying createRequest methods create requests for the targetSchema prior to application of request transforms, and so that allows for a hook there. Using an Apollo link within the subschema config passed to delegateRequest allows for manipulation after request transforms are applied. --- src/Interfaces.ts | 24 ++--- src/stitching/delegateToSchema.ts | 172 ++++++++++++++---------------- src/stitching/index.ts | 10 +- 3 files changed, 98 insertions(+), 108 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 9a2a4bf5580..364b48b86cf 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -11,6 +11,7 @@ import { GraphQLTypeResolver, GraphQLScalarType, DocumentNode, + FieldNode, GraphQLEnumValue, GraphQLEnumType, GraphQLUnionType, @@ -127,6 +128,7 @@ export interface IDelegateToSchemaOptions { operation?: Operation; fieldName?: string; returnType?: GraphQLOutputType; + fieldNodes?: ReadonlyArray; args?: { [key: string]: any }; context?: TContext; info: IGraphQLToolsResolveInfo; @@ -136,28 +138,18 @@ export interface IDelegateToSchemaOptions { skipTypeMerging?: boolean; } -export interface ICreateDelegatingRequestOptions { - schema: GraphQLSchema | SubschemaConfig; +export interface ICreateRequestFromInfo { info: IGraphQLToolsResolveInfo; + schema: GraphQLSchema | SubschemaConfig; operation: Operation; fieldName: string; - args?: { [key: string]: any }; - transforms?: Array; - skipValidation?: boolean; + additionalArgs: Record; + fieldNodes: ReadonlyArray; } -export interface IDelegateRequestOptions { +export type IDelegateRequestOptions = { request: Request; - schema: GraphQLSchema | SubschemaConfig; - rootValue?: Record; - info: IGraphQLToolsResolveInfo; - operation: Operation; - fieldName: string; - returnType?: GraphQLOutputType; - context?: TContext; - transforms?: Array; - skipTypeMerging?: boolean; -} +} & IDelegateToSchemaOptions; export type Delegator = ({ document, context, variables }: { document: DocumentNode; diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index e1071410c86..f19a3aafd33 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -4,7 +4,6 @@ import { FragmentDefinitionNode, Kind, OperationDefinitionNode, - SelectionSetNode, SelectionNode, subscribe, execute, @@ -19,11 +18,12 @@ import { GraphQLField, GraphQLArgument, astFromValue, + VariableDefinitionNode, } from 'graphql'; import { IDelegateToSchemaOptions, - ICreateDelegatingRequestOptions, + ICreateRequestFromInfo, IDelegateRequestOptions, Operation, Request, @@ -31,7 +31,6 @@ import { Delegator, SubschemaConfig, isSubschemaConfig, - IGraphQLToolsResolveInfo, } from '../Interfaces'; import { @@ -71,72 +70,68 @@ export default function delegateToSchema( ): any { if (options instanceof GraphQLSchema) { throw new Error( - 'Passing positional arguments to delegateToSchema is a deprecated. ' + + 'Passing positional arguments to delegateToSchema is deprecated. ' + 'Please pass named parameters instead.', ); } const { - schema: subschema, - rootValue, + schema: subschemaOrSubschemaConfig, info, operation = getDelegatingOperation(info.parentType, info.schema), fieldName = info.fieldName, returnType = info.returnType, args, - context, - transforms = [], - skipValidation, - skipTypeMerging, + fieldNodes = info.fieldNodes, } = options; - const request = createDelegatingRequest({ - schema: subschema, + const request = createRequestFromInfo({ info, + schema: subschemaOrSubschemaConfig, operation, fieldName, - args, - transforms, - skipValidation, + additionalArgs: args, + fieldNodes, }); return delegateRequest({ + ...options, request, - schema: subschema, - rootValue, - info, operation, fieldName, returnType, - context, - transforms, - skipTypeMerging, }); } -export function createDelegatingRequest({ +export function delegateRequest({ + request, schema: subschema, + rootValue, info, operation = getDelegatingOperation(info.parentType, info.schema), fieldName = info.fieldName, - args, + fieldNodes = info.fieldNodes, + returnType = info.returnType, + context, transforms = [], skipValidation, -}: ICreateDelegatingRequestOptions): any { + skipTypeMerging, +}: IDelegateRequestOptions): any { let targetSchema: GraphQLSchema; let subschemaConfig: SubschemaConfig; if (isSubschemaConfig(subschema)) { subschemaConfig = subschema; targetSchema = subschemaConfig.schema; + rootValue = rootValue || subschemaConfig.rootValue || info.rootValue; transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse()); } else { targetSchema = subschema; + rootValue = rootValue || info.rootValue; } - const initialRequest = createInitialRequest(info, operation, fieldName, targetSchema, args); - transforms = [ + new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType, skipTypeMerging), ...transforms, new ExpandAbstractTypes(info.schema, targetSchema), ]; @@ -153,48 +148,15 @@ export function createDelegatingRequest({ new AddTypenameToAbstract(targetSchema), ); - const delegatingRequest = applyRequestTransforms(initialRequest, transforms); + request = applyRequestTransforms(request, transforms); if (!skipValidation) { - const errors = validate(targetSchema, delegatingRequest.document); + const errors = validate(targetSchema, request.document); if (errors.length > 0) { throw errors; } } - return delegatingRequest; -} - -export function delegateRequest({ - request, - schema: subschema, - rootValue, - info, - operation = getDelegatingOperation(info.parentType, info.schema), - fieldName = info.fieldName, - returnType = info.returnType, - context, - transforms = [], - skipTypeMerging, -}: IDelegateRequestOptions): any { - let targetSchema: GraphQLSchema; - let subschemaConfig: SubschemaConfig; - - if (isSubschemaConfig(subschema)) { - subschemaConfig = subschema; - targetSchema = subschemaConfig.schema; - rootValue = rootValue || subschemaConfig.rootValue || info.rootValue; - transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse()); - } else { - targetSchema = subschema; - rootValue = rootValue || info.rootValue; - } - - transforms = [ - new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType, skipTypeMerging), - ...transforms, - ]; - if (operation === 'query' || operation === 'mutation') { const executor = createExecutor(targetSchema, rootValue, subschemaConfig); @@ -238,17 +200,40 @@ export function delegateRequest({ } } -function createInitialRequest( - info: IGraphQLToolsResolveInfo, +export function createRequestFromInfo({ + info, + schema, + operation = getDelegatingOperation(info.parentType, info.schema), + fieldName = info.fieldName, + additionalArgs, + fieldNodes = info.fieldNodes, +}: ICreateRequestFromInfo): Request { + return createRequest( + info.schema, + info.fragments, + info.operation.variableDefinitions, + info.variableValues, + schema, + operation, + fieldName, + additionalArgs, + fieldNodes, + ); +} + +export function createRequest( + sourceSchema: GraphQLSchema, + fragments: Record, + variableDefinitions: ReadonlyArray, + variableValues: Record, + targetSchemaOrSchemaConfig: GraphQLSchema | SubschemaConfig, targetOperation: Operation, targetField: string, - targetSchema: GraphQLSchema, - newArgsMap: Record, + additionalArgs: Record, + fieldNodes: ReadonlyArray, ): Request { let selections: Array = []; - let args: ReadonlyArray = info.fieldNodes[0].arguments || []; - - const originalSelections: ReadonlyArray = info.fieldNodes; + const originalSelections: ReadonlyArray = fieldNodes; originalSelections.forEach((field: FieldNode) => { const fieldSelections = field.selectionSet ? field.selectionSet.selections @@ -264,44 +249,46 @@ function createInitialRequest( }; } - const rootField: FieldNode = { + const fieldNode: FieldNode = { kind: Kind.FIELD, alias: null, - arguments: newArgsMap ? updateArguments(targetSchema, targetOperation, targetField, args, newArgsMap) : args, + arguments: additionalArgs ? updateArguments( + targetSchemaOrSchemaConfig, + targetOperation, + targetField, + fieldNodes[0].arguments, + additionalArgs, + ) : fieldNodes[0].arguments, selectionSet, name: { kind: Kind.NAME, - value: targetField || info.fieldNodes[0].name.value, + value: targetField || fieldNodes[0].name.value, }, }; - const rootSelectionSet: SelectionSetNode = { - kind: Kind.SELECTION_SET, - selections: [rootField], - }; - const operationDefinition: OperationDefinitionNode = { kind: Kind.OPERATION_DEFINITION, - operation: targetOperation || getDelegatingOperation(info.parentType, info.schema), - variableDefinitions: info.operation.variableDefinitions, - selectionSet: rootSelectionSet, - name: info.operation.name, + operation: targetOperation, + variableDefinitions, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [fieldNode], + }, }; - const fragments: Array = Object.keys(info.fragments).map( - fragmentName => info.fragments[fragmentName], + const fragmentDefinitions: Array = Object.keys(fragments).map( + fragmentName => fragments[fragmentName], ); const document = { kind: Kind.DOCUMENT, - definitions: [operationDefinition, ...fragments], + definitions: [operationDefinition, ...fragmentDefinitions], }; - const variableValues = info.variableValues; const variables = {}; - for (const variableDefinition of info.operation.variableDefinitions) { + for (const variableDefinition of variableDefinitions) { const varName = variableDefinition.variable.name.value; - const varType = typeFromAST(info.schema, (variableDefinition.type as NamedTypeNode)) as GraphQLInputType; + const varType = typeFromAST(sourceSchema, (variableDefinition.type as NamedTypeNode)) as GraphQLInputType; variables[varName] = serializeInputValue(varType, variableValues[varName]); } @@ -312,12 +299,15 @@ function createInitialRequest( } function updateArguments( - schema: GraphQLSchema, + subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, operation: OperationTypeNode, fieldName: string, argumentNodes: ReadonlyArray, newArgsMap: Record, ): Array { + const schema = isSubschemaConfig(subschemaOrSubschemaConfig) ? + subschemaOrSubschemaConfig.schema : subschemaOrSubschemaConfig; + let type: GraphQLObjectType; if (operation === 'subscription') { type = schema.getSubscriptionType(); @@ -328,9 +318,11 @@ function updateArguments( } const newArgs: Record = {}; - argumentNodes.forEach((argument: ArgumentNode) => { - newArgs[argument.name.value] = argument; - }); + if (argumentNodes) { + argumentNodes.forEach((argument: ArgumentNode) => { + newArgs[argument.name.value] = argument; + }); + } const field: GraphQLField = type.getFields()[fieldName]; field.args.forEach((argument: GraphQLArgument) => { diff --git a/src/stitching/index.ts b/src/stitching/index.ts index 02440022bcc..02bdb87fa3b 100644 --- a/src/stitching/index.ts +++ b/src/stitching/index.ts @@ -1,7 +1,12 @@ import makeRemoteExecutableSchema, { createResolver as defaultCreateRemoteResolver } from './makeRemoteExecutableSchema'; import introspectSchema from './introspectSchema'; import mergeSchemas from './mergeSchemas'; -import { default as delegateToSchema, createDelegatingRequest, delegateRequest } from './delegateToSchema'; +import { + default as delegateToSchema, + createRequestFromInfo, + createRequest, + delegateRequest, +} from './delegateToSchema'; import defaultMergedResolver from './defaultMergedResolver'; import { createMergedResolver } from './createMergedResolver'; import { dehoistResult, unwrapResult } from './proxiedResult'; @@ -14,7 +19,8 @@ export { // These are currently undocumented and not part of official API, // but exposed for the community use delegateToSchema, - createDelegatingRequest, + createRequestFromInfo, + createRequest, delegateRequest, defaultCreateRemoteResolver, defaultMergedResolver, From aecf495fc0c6534e437bcf2519c022bf73702727 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 23 Jan 2020 16:16:32 -0500 Subject: [PATCH 164/250] feat(stitching): support advanced type merging Also fixes algorithmic error in type merging in which previously not recorded the correct originating subschema for fields. Nested type merging is now enabled, with tests (hopefully!) to follow. --- src/Interfaces.ts | 19 ++- src/stitching/checkResultAndHandleErrors.ts | 143 +++++++++----------- src/stitching/defaultMergedResolver.ts | 6 +- src/stitching/mergeFields.ts | 135 ++++++++++++++++++ src/stitching/mergeSchemas.ts | 76 ++++++++--- src/stitching/proxiedResult.ts | 56 ++++++-- src/test/testAlternateMergeSchemas.ts | 6 +- src/transforms/AddMergedTypeFragments.ts | 8 +- src/utils/fragments.ts | 23 ++++ src/utils/index.ts | 6 +- src/utils/mergeDeep.ts | 26 ++-- 11 files changed, 367 insertions(+), 137 deletions(-) create mode 100644 src/stitching/mergeFields.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 364b48b86cf..c382fef3e92 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -24,6 +24,8 @@ import { GraphQLOutputType, } from 'graphql'; +import { TypeMap } from 'graphql/type/schema'; + import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; import { SchemaVisitor } from './utils/SchemaVisitor'; @@ -100,14 +102,16 @@ export type SubschemaConfig = { export type MergedTypeConfig = { fragment?: string; - mergedTypeResolver: MergedTypeResolver; + parsedFragment?: InlineFragmentNode, + merge: MergedTypeResolver; }; export type MergedTypeResolver = ( - subschema: GraphQLSchema | SubschemaConfig, originalResult: any, context: Record, info: IGraphQLToolsResolveInfo, + subschema: GraphQLSchema | SubschemaConfig, + fieldNodes: Array, ) => any; export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array }; @@ -171,7 +175,7 @@ export type MergeInfo = { fragment: string; }>; replacementFragments: ReplacementFragmentMapping, - mergedTypes: MergedTypeMapping, + mergedTypes: Record, delegateToSchema(options: IDelegateToSchemaOptions): any; }; @@ -179,10 +183,13 @@ export type ReplacementFragmentMapping = { [typeName: string]: { [fieldName: string]: InlineFragmentNode }; }; -export type MergedTypeMapping = Record, -}>; + fragment?: InlineFragmentNode, + uniqueFields: Record, + nonUniqueFields: Record>, + typeMaps: Map, +}; export type IFieldResolver> = ( source: TSource, diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index ecf6b3a3681..3b1bf7e8561 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -14,6 +14,7 @@ import { GraphQLSchema, FieldNode, isAbstractType, + GraphQLObjectType, } from 'graphql'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; import { @@ -24,10 +25,12 @@ import { import { SubschemaConfig, IGraphQLToolsResolveInfo, + isSubschemaConfig, } from '../Interfaces'; import resolveFromParentTypename from './resolveFromParentTypename'; -import { setErrors, setSubschemas } from './proxiedResult'; -import { mergeDeep } from '../utils'; +import { setErrors, setObjectSubschema } from './proxiedResult'; +import { collectFields } from '../utils'; +import { mergeFields } from './mergeFields'; export function checkResultAndHandleErrors( result: ExecutionResult, @@ -44,15 +47,14 @@ export function checkResultAndHandleErrors( const errors = result.errors || []; const data = result.data && result.data[responseKey]; - const subschemas = [subschema]; - return handleResult(data, errors, subschemas, context, info, returnType, skipTypeMerging); + return handleResult(data, errors, subschema, context, info, returnType, skipTypeMerging); } export function handleResult( result: any, errors: ReadonlyArray, - subschemas: Array, + subschema: GraphQLSchema | SubschemaConfig, context: Record, info: IGraphQLToolsResolveInfo, returnType = info.returnType, @@ -67,47 +69,9 @@ export function handleResult( if (isLeafType(type)) { return type.parseValue(result); } else if (isCompositeType(type)) { - return handleObject(type, result, errors, subschemas, context, info, skipTypeMerging); + return handleObject(type, result, errors, subschema, context, info, skipTypeMerging); } else if (isListType(type)) { - return handleList(type, result, errors, subschemas, context, info, skipTypeMerging); - } -} - -export function makeObjectProxiedResult( - object: any, - errors: ReadonlyArray, - subschemas: Array, -) { - setErrors(object, errors.map(error => { - return relocatedError( - error, - error.nodes, - error.path ? error.path.slice(1) : undefined - ); - })); - setSubschemas(object, subschemas); -} - -export function handleObject( - type: GraphQLCompositeType, - object: any, - errors: ReadonlyArray, - subschemas: Array, - context: Record, - info: IGraphQLToolsResolveInfo, - skipTypeMerging?: boolean, -) { - makeObjectProxiedResult(object, errors, subschemas); - if (skipTypeMerging || !info.mergeInfo) { - return object; - } else { - return mergeFields( - type, - object, - subschemas, - context, - info, - ); + return handleList(type, result, errors, subschema, context, info, skipTypeMerging); } } @@ -115,7 +79,7 @@ function handleList( type: GraphQLList, list: Array, errors: ReadonlyArray, - subschemas: Array, + subschema: GraphQLSchema | SubschemaConfig, context: Record, info: IGraphQLToolsResolveInfo, skipTypeMerging?: boolean, @@ -127,7 +91,7 @@ function handleList( listMember, index, childErrors[index] || [], - subschemas, + subschema, context, info, skipTypeMerging, @@ -141,7 +105,7 @@ function handleListMember( listMember: any, index: number, errors: ReadonlyArray, - subschemas: Array, + subschema: GraphQLSchema | SubschemaConfig, context: Record, info: IGraphQLToolsResolveInfo, skipTypeMerging?: boolean, @@ -153,19 +117,35 @@ function handleListMember( if (isLeafType(type)) { return type.parseValue(listMember); } else if (isCompositeType(type)) { - return handleObject(type, listMember, errors, subschemas, context, info, skipTypeMerging); + return handleObject(type, listMember, errors, subschema, context, info, skipTypeMerging); } else if (isListType(type)) { - return handleList(type, listMember, errors, subschemas, context, info, skipTypeMerging); + return handleList(type, listMember, errors, subschema, context, info, skipTypeMerging); } } -function mergeFields( +export function handleObject( type: GraphQLCompositeType, object: any, - subschemas: Array, + errors: ReadonlyArray, + subschema: GraphQLSchema | SubschemaConfig, context: Record, info: IGraphQLToolsResolveInfo, -): any { + skipTypeMerging?: boolean, +) { + setErrors(object, errors.map(error => { + return relocatedError( + error, + error.nodes, + error.path ? error.path.slice(1) : undefined + ); + })); + + setObjectSubschema(object, subschema); + + if (skipTypeMerging || !info.mergeInfo) { + return object; + } + let typeName: string; if (isAbstractType(type)) { typeName = info.schema.getTypeMap()[resolveFromParentTypename(object)].name; @@ -173,36 +153,39 @@ function mergeFields( typeName = type.name; } - const initialSchemas = - info.mergeInfo.mergedTypes[typeName] && - info.mergeInfo.mergedTypes[typeName].subschemas; - if (initialSchemas) { - const remainingSubschemas = initialSchemas.filter( - subschema => !subschemas.includes(subschema) - ); - if (remainingSubschemas.length) { - const maybePromises = remainingSubschemas.map(subschema => { - return subschema.mergedTypeConfigs[typeName].mergedTypeResolver(subschema, object, context, info); - }); + const mergedTypeInfo = info.mergeInfo.mergedTypes[typeName]; + let subschemas = mergedTypeInfo && mergedTypeInfo.subschemas; - let containsPromises = false; { - for (const maybePromise of maybePromises) { - if (maybePromise instanceof Promise) { - containsPromises = true; - break; - } - } - } - if (containsPromises) { - return Promise.all(maybePromises). - then(results => results.reduce((acc: any, r: ExecutionResult) => mergeDeep(acc, r), object)); - } else { - return maybePromises.reduce((acc: any, r: ExecutionResult) => mergeDeep(acc, r), object); - } - } + if (!subschemas) { + return object; } - return object; + subschemas = subschemas.filter(s => s !== subschema); + if (!subschemas.length) { + return object; + } + + const typeMap = isSubschemaConfig(subschema) ? + mergedTypeInfo.typeMaps.get(subschema) : subschema.getTypeMap(); + const fields = (typeMap[typeName] as GraphQLObjectType).getFields(); + const selections: Array = []; + info.fieldNodes.forEach(fieldNode => { + collectFields(fieldNode.selectionSet, info.fragments).forEach(s => { + if (!fields[s.name.value]) { + selections.push(s); + } + }); + }); + + return mergeFields( + mergedTypeInfo, + typeName, + object, + selections, + subschemas, + context, + info, + ); } export function handleNull( diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index bec11fd249b..5008ab4c07c 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,5 +1,5 @@ import { defaultFieldResolver } from 'graphql'; -import { getErrors, getSubschemas } from './proxiedResult'; +import { getErrors, getSubschema } from './proxiedResult'; import { handleResult } from './checkResultAndHandleErrors'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; @@ -28,7 +28,7 @@ export default function defaultMergedResolver( } const result = parent[responseKey]; - const subschemas = getSubschemas(parent); + const subschema = getSubschema(parent, responseKey); - return handleResult(result, errors, subschemas, context, info); + return handleResult(result, errors, subschema, context, info); } diff --git a/src/stitching/mergeFields.ts b/src/stitching/mergeFields.ts new file mode 100644 index 00000000000..4981a1a3cc8 --- /dev/null +++ b/src/stitching/mergeFields.ts @@ -0,0 +1,135 @@ +import { + FieldNode, + SelectionNode, + Kind, +} from 'graphql'; +import { + SubschemaConfig, + IGraphQLToolsResolveInfo, + MergedTypeInfo, +} from '../Interfaces'; +import { mergeProxiedResults } from './proxiedResult'; +import { objectContainsInlineFragment } from '../utils'; + +export function mergeFields( + mergedType: MergedTypeInfo, + typeName: string, + object: any, + originalSelections: Array, + subschemas: Array, + context: Record, + info: IGraphQLToolsResolveInfo, +): any { + // 1. use fragment contained within the map and existing result to calculate + // if possible to delegate to given subschema + + const proxiableSubschemas: Array = []; + const nonProxiableSubschemas: Array = []; + subschemas.forEach(s => { + if (objectContainsInlineFragment(object, s.mergedTypeConfigs[typeName].parsedFragment)) { + proxiableSubschemas.push(s); + } else { + nonProxiableSubschemas.push(s); + } + }); + + // 3. use uniqueFields map to assign fields to subschema if one of possible subschemas + + const uniqueFields = mergedType.uniqueFields; + const nonUniqueFields = mergedType.nonUniqueFields; + const remainingSelections: Array = []; + + const delegationMap: Map> = new Map(); + originalSelections.forEach(selection => { + const uniqueSubschema: SubschemaConfig = uniqueFields[selection.name.value]; + if (uniqueSubschema && proxiableSubschemas.includes(uniqueSubschema)) { + const selections = delegationMap.get(uniqueSubschema); + if (selections) { + selections.push(selection); + } else { + delegationMap.set(uniqueSubschema, [selection]); + } + } else { + remainingSelections.push(selection); + } + }); + + // 4. use nonUniqueFields to assign to a possible subschema, + // preferring one of the subschemas already targets of delegation + + const unproxiableSelections: Array = []; + remainingSelections.forEach(selection => { + let nonUniqueSubschemas: Array = nonUniqueFields[selection.name.value]; + nonUniqueSubschemas = nonUniqueSubschemas.filter(s => proxiableSubschemas.includes(s)); + if (nonUniqueSubschemas) { + const existingSelections = nonUniqueSubschemas.map(s => delegationMap.get(s)).find(selections => selections); + if (existingSelections) { + existingSelections.push(selection); + } else { + delegationMap.set(nonUniqueSubschemas[0], [selection]); + } + } else { + unproxiableSelections.push(selection); + } + }); + + // 5. terminate if no delegations actually possible. + + if (!delegationMap.size) { + return object; + } + + // 6. delegate! + + const maybePromises: Promise | any = []; + delegationMap.forEach((selections: Array, s: SubschemaConfig) => { + const newFieldNodes = [{ + ...info.fieldNodes[0], + selectionSet: { + kind: Kind.SELECTION_SET, + selections, + } + }]; + const maybePromise = s.mergedTypeConfigs[typeName].merge( + object, + context, + info, + s, + newFieldNodes, + ); + maybePromises.push(maybePromise); + }); + + let containsPromises = false; + for (const maybePromise of maybePromises) { + if (maybePromise instanceof Promise) { + containsPromises = true; + break; + } + } + + // 7. repeat if necessary + + const mergeRemaining = (o: any) => { + if (unproxiableSelections) { + return mergeFields( + mergedType, + typeName, + o, + unproxiableSelections, + nonProxiableSubschemas, + context, + info + ); + } else { + return o; + } + }; + + if (containsPromises) { + return Promise.all(maybePromises). + then(results => mergeRemaining(mergeProxiedResults(object, ...results))); + } else { + return mergeRemaining(mergeProxiedResults(object, ...maybePromises)); + } +} diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 20242d510a2..14963b395f8 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -10,6 +10,7 @@ import { parse, Kind, GraphQLDirective, + InlineFragmentNode, } from 'graphql'; import { IDelegateToSchemaOptions, @@ -21,7 +22,6 @@ import { IResolvers, SubschemaConfig, IGraphQLToolsResolveInfo, - MergedTypeMapping, } from '../Interfaces'; import { extractExtensionDefinitions, @@ -50,6 +50,7 @@ type MergeTypeCandidate = { type: GraphQLNamedType; schema?: GraphQLSchema; subschema?: GraphQLSchema | SubschemaConfig; + transformedSubschema?: GraphQLSchema; }; type CandidateSelector = ( @@ -110,6 +111,7 @@ export default function mergeSchemas({ schema, type: operationTypes[typeName], subschema: schemaLikeObject, + transformedSubschema: schema, }); } }); @@ -135,6 +137,7 @@ export default function mergeSchemas({ schema, type, subschema: schemaLikeObject, + transformedSubschema: schema, }); } }); @@ -271,27 +274,64 @@ function createMergeInfo( allSchemas: Array, typeCandidates: { [name: string]: Array }, ): MergeInfo { - const mergedTypes: MergedTypeMapping = {}; + const mergedTypes = {}; Object.keys(typeCandidates).forEach(typeName => { - const subschemaConfigs: Array = + const mergedTypeCandidates = typeCandidates[typeName] - .filter(typeCandidate => typeCandidate.subschema && isSubschemaConfig(typeCandidate.subschema)) - .map(typeCandidate => typeCandidate.subschema as SubschemaConfig); - - const mergeTypeConfigs = subschemaConfigs - .filter(subschemaConfig => - subschemaConfig.mergedTypeConfigs && subschemaConfig.mergedTypeConfigs[typeName]) - .map(subschemaConfig => subschemaConfig.mergedTypeConfigs[typeName]); - - if (mergeTypeConfigs.length) { - const inlineFragments = mergeTypeConfigs - .filter(mergeTypeConfig => mergeTypeConfig.fragment) - .map(mergeTypeConfig => parseFragmentToInlineFragment(mergeTypeConfig.fragment)); + .filter(typeCandidate => + typeCandidate.subschema && + isSubschemaConfig(typeCandidate.subschema) && + typeCandidate.subschema.mergedTypeConfigs && + typeCandidate.subschema.mergedTypeConfigs[typeName] + ); + + if (mergedTypeCandidates.length) { + const subschemas: Array = []; + const parsedFragments: Array = []; + const fields = Object.create({}); + const typeMaps = new Map(); + + mergedTypeCandidates.forEach(typeCandidate => { + const subschemaConfig = typeCandidate.subschema as SubschemaConfig; + const transformedSubschema = typeCandidate.transformedSubschema; + typeMaps.set(subschemaConfig, transformedSubschema.getTypeMap()); + const type = transformedSubschema.getType(typeName) as GraphQLObjectType; + const fieldMap = type.getFields(); + Object.keys(fieldMap).forEach(fieldName => { + fields[fieldName] = fields[fieldName] || []; + fields[fieldName].push(subschemaConfig); + }); + + const mergedTypeConfig = subschemaConfig.mergedTypeConfigs[typeName]; + if (mergedTypeConfig.fragment) { + const parsedFragment = parseFragmentToInlineFragment(mergedTypeConfig.fragment); + parsedFragments.push(parsedFragment); + mergedTypeConfig.parsedFragment = parsedFragment; + } + + subschemas.push(subschemaConfig); + }); + mergedTypes[typeName] = { - fragment: concatInlineFragments(typeName, inlineFragments), - subschemas: subschemaConfigs, + subschemas, + typeMaps, + uniqueFields: Object.create({}), + nonUniqueFields: Object.create({}), }; + + Object.keys(fields).forEach(fieldName => { + const supportedBySubschemas = fields[fieldName]; + if (supportedBySubschemas.length === 1) { + mergedTypes[typeName].uniqueFields[fieldName] = supportedBySubschemas[0]; + } else { + mergedTypes[typeName].nonUniqueFields[fieldName] = supportedBySubschemas; + } + }); + + if (parsedFragments.length) { + mergedTypes[typeName].fragment = concatInlineFragments(typeName, parsedFragments); + } } }); @@ -367,7 +407,7 @@ function completeMergeInfo( mapping[actualTypeName][field].push(parsedFragment); }); - const replacementFragments = Object.create({}); + const replacementFragments = Object.create(null); Object.keys(mapping).forEach(typeName => { Object.keys(mapping[typeName]).forEach(field => { replacementFragments[typeName] = mapping[typeName] || {}; diff --git a/src/stitching/proxiedResult.ts b/src/stitching/proxiedResult.ts index d5b9bf9dec8..085f6a2cdb5 100644 --- a/src/stitching/proxiedResult.ts +++ b/src/stitching/proxiedResult.ts @@ -4,19 +4,23 @@ import { responsePathAsArray, } from 'graphql'; import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; -import { handleNull, makeObjectProxiedResult } from './checkResultAndHandleErrors'; +import { handleNull } from './checkResultAndHandleErrors'; import { relocatedError } from './errors'; +import { mergeDeep } from '../utils'; -export let SUBSCHEMAS_SYMBOL: any; +export let OBJECT_SUBSCHEMA_SYMBOL: any; +export let SUBSCHEMA_MAP_SYMBOL: any; export let ERROR_SYMBOL: any; if ( (typeof global !== 'undefined' && 'Symbol' in global) || (typeof window !== 'undefined' && 'Symbol' in window) ) { - SUBSCHEMAS_SYMBOL = Symbol('subschemas'); + OBJECT_SUBSCHEMA_SYMBOL = Symbol('initialSubschema'); + SUBSCHEMA_MAP_SYMBOL = Symbol('subschemaMap'); ERROR_SYMBOL = Symbol('subschemaErrors'); } else { - SUBSCHEMAS_SYMBOL = Symbol('subschemas'); + OBJECT_SUBSCHEMA_SYMBOL = Symbol('@@__initialSubschema'); + SUBSCHEMA_MAP_SYMBOL = Symbol('@@__subschemaMap'); ERROR_SYMBOL = '@@__subschemaErrors'; } @@ -24,12 +28,18 @@ export function isProxiedResult(result: any) { return result && result[ERROR_SYMBOL]; } -export function getSubschemas(result: any): Array { - return result && result[SUBSCHEMAS_SYMBOL]; +export function getSubschema(result: any, responseKey: string): GraphQLSchema | SubschemaConfig { + const subschema = result[SUBSCHEMA_MAP_SYMBOL] && result[SUBSCHEMA_MAP_SYMBOL][responseKey]; + return subschema ? subschema : result[OBJECT_SUBSCHEMA_SYMBOL]; } -export function setSubschemas(result: any, subschemas: Array) { - result[SUBSCHEMAS_SYMBOL] = subschemas; +export function setObjectSubschema(result: any, subschema: GraphQLSchema | SubschemaConfig) { + result[OBJECT_SUBSCHEMA_SYMBOL] = subschema; +} + +export function setSubschemaForKey(result: any, responseKey: string, subschema: GraphQLSchema | SubschemaConfig) { + result[SUBSCHEMA_MAP_SYMBOL] = result[SUBSCHEMA_MAP_SYMBOL] || Object.create(null); + result[SUBSCHEMA_MAP_SYMBOL][responseKey] = subschema; } export function setErrors(result: any, errors: Array) { @@ -66,13 +76,22 @@ export function unwrapResult( for (let i = 0; i < pathLength; i++) { const responseKey = path[i]; const errors = getErrors(parent, responseKey); - const subschemas = getSubschemas(parent); + const subschema = getSubschema(parent, responseKey); const object = parent[responseKey]; if (object == null) { return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors); } - makeObjectProxiedResult(object, errors, subschemas); + + setErrors(object, errors.map(error => { + return relocatedError( + error, + error.nodes, + error.path ? error.path.slice(1) : undefined + ); + })); + setObjectSubschema(object, subschema); + parent = object; } @@ -104,7 +123,22 @@ export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): any } }); - result[SUBSCHEMAS_SYMBOL] = parent[SUBSCHEMAS_SYMBOL]; + result[OBJECT_SUBSCHEMA_SYMBOL] = parent[OBJECT_SUBSCHEMA_SYMBOL]; return result; } + +export function mergeProxiedResults(target: any, ...sources: any): any { + const errors = target[ERROR_SYMBOL].concat(sources.map((source: any) => source[ERROR_SYMBOL])); + const subschemaMap = sources.reduce((acc: Record, source: any) => { + const subschema = source[OBJECT_SUBSCHEMA_SYMBOL]; + Object.keys(source).forEach(key => { + acc[key] = subschema; + }); + return acc; + }, {}); + return mergeDeep(target, ...sources, { + [ERROR_SYMBOL]: errors, + [SUBSCHEMA_MAP_SYMBOL]: subschemaMap, + }); +} diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index e51232528cc..1fb8ed56201 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -1753,11 +1753,12 @@ describe('mergeTypes', () => { mergedTypeConfigs: { Test: { fragment: 'fragment TestFragment on Test { id }', - mergedTypeResolver: (subschema, originalResult, context, info) => delegateToSchema({ + merge: (originalResult, context, info, subschema, fieldNodes) => delegateToSchema({ schema: subschema, operation: 'query', fieldName: 'getTest', args: { id: originalResult.id }, + fieldNodes, context, info, skipTypeMerging: true, @@ -1771,11 +1772,12 @@ describe('mergeTypes', () => { mergedTypeConfigs: { Test: { fragment: 'fragment TestFragment on Test { id }', - mergedTypeResolver: (subschema, originalResult, context, info) => delegateToSchema({ + merge: (originalResult, context, info, subschema, fieldNodes) => delegateToSchema({ schema: subschema, operation: 'query', fieldName: 'getTest', args: { id: originalResult.id }, + fieldNodes, context, info, skipTypeMerging: true, diff --git a/src/transforms/AddMergedTypeFragments.ts b/src/transforms/AddMergedTypeFragments.ts index 6ef732145db..98021a86f4a 100644 --- a/src/transforms/AddMergedTypeFragments.ts +++ b/src/transforms/AddMergedTypeFragments.ts @@ -8,16 +8,16 @@ import { visit, visitWithTypeInfo, } from 'graphql'; -import { Request, MergedTypeMapping } from '../Interfaces'; +import { Request, MergedTypeInfo } from '../Interfaces'; import { Transform } from './transforms'; export default class AddMergedTypeFragments implements Transform { private targetSchema: GraphQLSchema; - private mapping: MergedTypeMapping; + private mapping: Record; constructor( targetSchema: GraphQLSchema, - mapping: MergedTypeMapping, + mapping: Record, ) { this.targetSchema = targetSchema; this.mapping = mapping; @@ -39,7 +39,7 @@ export default class AddMergedTypeFragments implements Transform { function addMergedTypeFragments( targetSchema: GraphQLSchema, document: DocumentNode, - mapping: MergedTypeMapping, + mapping: Record, ): DocumentNode { const typeInfo = new TypeInfo(targetSchema); return visit( diff --git a/src/utils/fragments.ts b/src/utils/fragments.ts index 303374b7ebf..ed4bfc5cfbc 100644 --- a/src/utils/fragments.ts +++ b/src/utils/fragments.ts @@ -132,3 +132,26 @@ export function parseFragmentToInlineFragment( throw new Error('Could not parse fragment'); } + +export function objectContainsInlineFragment(object: any, fragment: InlineFragmentNode): boolean { + for (const selection of fragment.selectionSet.selections) { + if (selection.kind === Kind.FIELD) { + if (selection.alias) { + if (!object[selection.alias.value]) { + return false; + } + } else { + if (!object[selection.name.value]) { + return false; + } + } + } else if (selection.kind === Kind.INLINE_FRAGMENT) { + const containsFragment = objectContainsInlineFragment(object, selection); + if (!containsFragment) { + return false; + } + } + } + + return true; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 1fe3920c316..712a9d0205b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -12,7 +12,11 @@ export { parseInputValueLiteral, serializeInputValue, } from './transformInputValue'; -export { concatInlineFragments, parseFragmentToInlineFragment } from './fragments'; +export { + concatInlineFragments, + parseFragmentToInlineFragment, + objectContainsInlineFragment, +} from './fragments'; export { mergeDeep } from './mergeDeep'; export { collectFields, diff --git a/src/utils/mergeDeep.ts b/src/utils/mergeDeep.ts index 37ae2782a46..352ba57d818 100644 --- a/src/utils/mergeDeep.ts +++ b/src/utils/mergeDeep.ts @@ -1,18 +1,20 @@ -export function mergeDeep(target: any, source: any): any { +export function mergeDeep(target: any, ...sources: 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] }); + sources.forEach((source: any) => { + 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 { - output[key] = mergeDeep(target[key], source[key]); + Object.assign(output, { [key]: source[key] }); } - } else { - Object.assign(output, { [key]: source[key] }); - } - }); - } + }); + } + }); return output; } From 5790c6b5d1ed0e937fd6345bb0a4a460b8cdb952 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 26 Jan 2020 06:35:19 -0500 Subject: [PATCH 165/250] fix(stitching): add arguments as variables Reverts 3144e41. Adding args as variables can be useful if using only known queries. --- src/stitching/delegateToSchema.ts | 128 ++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 17 deletions(-) diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index f19a3aafd33..c31f8310896 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -17,8 +17,10 @@ import { GraphQLInputType, GraphQLField, GraphQLArgument, - astFromValue, VariableDefinitionNode, + GraphQLList, + GraphQLNonNull, + TypeNode, } from 'graphql'; import { @@ -249,16 +251,37 @@ export function createRequest( }; } - const fieldNode: FieldNode = { - kind: Kind.FIELD, - alias: null, - arguments: additionalArgs ? updateArguments( + let variables = {}; + for (const variableDefinition of variableDefinitions) { + const varName = variableDefinition.variable.name.value; + const varType = typeFromAST(sourceSchema, (variableDefinition.type as NamedTypeNode)) as GraphQLInputType; + variables[varName] = serializeInputValue(varType, variableValues[varName]); + } + + let args = fieldNodes[0].arguments; + if (additionalArgs) { + const { + arguments: updatedArguments, + variableDefinitions: updatedVariableDefinitions, + variableValues: updatedVariableValues + } = updateArguments( targetSchemaOrSchemaConfig, targetOperation, targetField, - fieldNodes[0].arguments, + args, + variableDefinitions, + variables, additionalArgs, - ) : fieldNodes[0].arguments, + ); + args = updatedArguments; + variableDefinitions = updatedVariableDefinitions; + variables = updatedVariableValues; + } + + const fieldNode: FieldNode = { + kind: Kind.FIELD, + alias: null, + arguments: args, selectionSet, name: { kind: Kind.NAME, @@ -285,13 +308,6 @@ export function createRequest( definitions: [operationDefinition, ...fragmentDefinitions], }; - const variables = {}; - for (const variableDefinition of variableDefinitions) { - const varName = variableDefinition.variable.name.value; - const varType = typeFromAST(sourceSchema, (variableDefinition.type as NamedTypeNode)) as GraphQLInputType; - variables[varName] = serializeInputValue(varType, variableValues[varName]); - } - return { document, variables, @@ -303,8 +319,14 @@ function updateArguments( operation: OperationTypeNode, fieldName: string, argumentNodes: ReadonlyArray, + variableDefinitions: ReadonlyArray, + variableValues: Record, newArgsMap: Record, -): Array { +): { + arguments: Array, + variableDefinitions: Array, + variableValues: Record +} { const schema = isSubschemaConfig(subschemaOrSubschemaConfig) ? subschemaOrSubschemaConfig.schema : subschemaOrSubschemaConfig; @@ -324,21 +346,93 @@ function updateArguments( }); } + let varNames = variableDefinitions.reduce((acc, def) => { + acc[def.variable.name.value] = true; + return acc; + }, {}); + + const variables = {}; + let numGeneratedVariables = 0; + const field: GraphQLField = type.getFields()[fieldName]; field.args.forEach((argument: GraphQLArgument) => { if (newArgsMap[argument.name]) { + const argName = argument.name; + let varName; + do { + varName = `_v${numGeneratedVariables++}_${argName}`; + } while (varNames[varName]); + newArgs[argument.name] = { kind: Kind.ARGUMENT, name: { kind: Kind.NAME, value: argument.name, }, - value: astFromValue(newArgsMap[argument.name], argument.type), + value: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: varName, + }, + }, }; + varNames[varName] = true; + variables[varName] = { + kind: Kind.VARIABLE_DEFINITION, + variable: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: varName, + }, + }, + type: typeToAst(argument.type), + }; + variableValues[varName] = serializeInputValue( + argument.type, + newArgsMap[argument.name], + ); } }); - return Object.keys(newArgs).map(argName => newArgs[argName]); + return { + arguments: Object.keys(newArgs).map(argName => newArgs[argName]), + variableDefinitions: variableDefinitions.concat( + Object.keys(variables).map(varName => variables[varName]), + ), + variableValues, + }; +} + +function typeToAst(type: GraphQLInputType): TypeNode { + if (type instanceof GraphQLNonNull) { + const innerType = typeToAst(type.ofType); + if ( + innerType.kind === Kind.LIST_TYPE || + innerType.kind === Kind.NAMED_TYPE + ) { + return { + kind: Kind.NON_NULL_TYPE, + type: innerType, + }; + } else { + throw new Error('Incorrent inner non-null type'); + } + } else if (type instanceof GraphQLList) { + return { + kind: Kind.LIST_TYPE, + type: typeToAst(type.ofType), + }; + } else { + return { + kind: Kind.NAMED_TYPE, + name: { + kind: Kind.NAME, + value: type.toString(), + }, + }; + } } function createExecutor( From fb23115814d2240da89cd976289d94667efea284 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 26 Jan 2020 06:40:57 -0500 Subject: [PATCH 166/250] refactor(createRequest): split into separate file --- src/stitching/createRequest.ts | 276 ++++++++++++++++++++++++++++++ src/stitching/delegateToSchema.ts | 270 +---------------------------- src/stitching/index.ts | 8 +- 3 files changed, 282 insertions(+), 272 deletions(-) create mode 100644 src/stitching/createRequest.ts diff --git a/src/stitching/createRequest.ts b/src/stitching/createRequest.ts new file mode 100644 index 00000000000..7c4b3dfe38f --- /dev/null +++ b/src/stitching/createRequest.ts @@ -0,0 +1,276 @@ +import { + ArgumentNode, + FieldNode, + FragmentDefinitionNode, + Kind, + OperationDefinitionNode, + SelectionNode, + GraphQLSchema, + GraphQLObjectType, + OperationTypeNode, + typeFromAST, + NamedTypeNode, + GraphQLInputType, + GraphQLField, + GraphQLArgument, + VariableDefinitionNode, + GraphQLList, + GraphQLNonNull, + TypeNode, +} from 'graphql'; + +import { + ICreateRequestFromInfo, + Operation, + Request, + SubschemaConfig, + isSubschemaConfig, +} from '../Interfaces'; + +import { serializeInputValue } from '../utils'; + +export function getDelegatingOperation( + parentType: GraphQLObjectType, + schema: GraphQLSchema +): OperationTypeNode { + if (parentType === schema.getMutationType()) { + return 'mutation'; + } else if (parentType === schema.getSubscriptionType()) { + return 'subscription'; + } else { + return 'query'; + } +} + +export function createRequestFromInfo({ + info, + schema, + operation = getDelegatingOperation(info.parentType, info.schema), + fieldName = info.fieldName, + additionalArgs, + fieldNodes = info.fieldNodes, +}: ICreateRequestFromInfo): Request { + return createRequest( + info.schema, + info.fragments, + info.operation.variableDefinitions, + info.variableValues, + schema, + operation, + fieldName, + additionalArgs, + fieldNodes, + ); +} + +export function createRequest( + sourceSchema: GraphQLSchema, + fragments: Record, + variableDefinitions: ReadonlyArray, + variableValues: Record, + targetSchemaOrSchemaConfig: GraphQLSchema | SubschemaConfig, + targetOperation: Operation, + targetField: string, + additionalArgs: Record, + fieldNodes: ReadonlyArray, +): Request { + let selections: Array = []; + const originalSelections: ReadonlyArray = fieldNodes; + originalSelections.forEach((field: FieldNode) => { + const fieldSelections = field.selectionSet + ? field.selectionSet.selections + : []; + selections = selections.concat(fieldSelections); + }); + + let selectionSet = undefined; + if (selections.length > 0) { + selectionSet = { + kind: Kind.SELECTION_SET, + selections: selections, + }; + } + + let variables = {}; + for (const variableDefinition of variableDefinitions) { + const varName = variableDefinition.variable.name.value; + const varType = typeFromAST(sourceSchema, (variableDefinition.type as NamedTypeNode)) as GraphQLInputType; + variables[varName] = serializeInputValue(varType, variableValues[varName]); + } + + let args = fieldNodes[0].arguments; + if (additionalArgs) { + const { + arguments: updatedArguments, + variableDefinitions: updatedVariableDefinitions, + variableValues: updatedVariableValues + } = updateArguments( + targetSchemaOrSchemaConfig, + targetOperation, + targetField, + args, + variableDefinitions, + variables, + additionalArgs, + ); + args = updatedArguments; + variableDefinitions = updatedVariableDefinitions; + variables = updatedVariableValues; + } + + const fieldNode: FieldNode = { + kind: Kind.FIELD, + alias: null, + arguments: args, + selectionSet, + name: { + kind: Kind.NAME, + value: targetField || fieldNodes[0].name.value, + }, + }; + + const operationDefinition: OperationDefinitionNode = { + kind: Kind.OPERATION_DEFINITION, + operation: targetOperation, + variableDefinitions, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [fieldNode], + }, + }; + + const fragmentDefinitions: Array = Object.keys(fragments).map( + fragmentName => fragments[fragmentName], + ); + + const document = { + kind: Kind.DOCUMENT, + definitions: [operationDefinition, ...fragmentDefinitions], + }; + + return { + document, + variables, + }; +} + +function updateArguments( + subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, + operation: OperationTypeNode, + fieldName: string, + argumentNodes: ReadonlyArray, + variableDefinitions: ReadonlyArray, + variableValues: Record, + newArgsMap: Record, +): { + arguments: Array, + variableDefinitions: Array, + variableValues: Record +} { + const schema = isSubschemaConfig(subschemaOrSubschemaConfig) ? + subschemaOrSubschemaConfig.schema : subschemaOrSubschemaConfig; + + let type: GraphQLObjectType; + if (operation === 'subscription') { + type = schema.getSubscriptionType(); + } else if (operation === 'mutation') { + type = schema.getMutationType(); + } else { + type = schema.getQueryType(); + } + + const newArgs: Record = {}; + if (argumentNodes) { + argumentNodes.forEach((argument: ArgumentNode) => { + newArgs[argument.name.value] = argument; + }); + } + + let varNames = variableDefinitions.reduce((acc, def) => { + acc[def.variable.name.value] = true; + return acc; + }, {}); + + const variables = {}; + let numGeneratedVariables = 0; + + const field: GraphQLField = type.getFields()[fieldName]; + field.args.forEach((argument: GraphQLArgument) => { + if (newArgsMap[argument.name]) { + const argName = argument.name; + let varName; + do { + varName = `_v${numGeneratedVariables++}_${argName}`; + } while (varNames[varName]); + + newArgs[argument.name] = { + kind: Kind.ARGUMENT, + name: { + kind: Kind.NAME, + value: argument.name, + }, + value: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: varName, + }, + }, + }; + varNames[varName] = true; + variables[varName] = { + kind: Kind.VARIABLE_DEFINITION, + variable: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: varName, + }, + }, + type: typeToAst(argument.type), + }; + variableValues[varName] = serializeInputValue( + argument.type, + newArgsMap[argument.name], + ); + } + }); + + return { + arguments: Object.keys(newArgs).map(argName => newArgs[argName]), + variableDefinitions: variableDefinitions.concat( + Object.keys(variables).map(varName => variables[varName]), + ), + variableValues, + }; +} + +function typeToAst(type: GraphQLInputType): TypeNode { + if (type instanceof GraphQLNonNull) { + const innerType = typeToAst(type.ofType); + if ( + innerType.kind === Kind.LIST_TYPE || + innerType.kind === Kind.NAMED_TYPE + ) { + return { + kind: Kind.NON_NULL_TYPE, + type: innerType, + }; + } else { + throw new Error('Incorrent inner non-null type'); + } + } else if (type instanceof GraphQLList) { + return { + kind: Kind.LIST_TYPE, + type: typeToAst(type.ofType), + }; + } else { + return { + kind: Kind.NAMED_TYPE, + name: { + kind: Kind.NAME, + value: type.toString(), + }, + }; + } +} diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index c31f8310896..5694f47f2aa 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -1,34 +1,14 @@ import { - ArgumentNode, - FieldNode, - FragmentDefinitionNode, - Kind, - OperationDefinitionNode, - SelectionNode, subscribe, execute, validate, GraphQLSchema, ExecutionResult, - GraphQLObjectType, - OperationTypeNode, - typeFromAST, - NamedTypeNode, - GraphQLInputType, - GraphQLField, - GraphQLArgument, - VariableDefinitionNode, - GraphQLList, - GraphQLNonNull, - TypeNode, } from 'graphql'; import { IDelegateToSchemaOptions, - ICreateRequestFromInfo, IDelegateRequestOptions, - Operation, - Request, Fetcher, Delegator, SubschemaConfig, @@ -46,7 +26,10 @@ import { applyResultTransforms, } from '../transforms'; -import { serializeInputValue } from '../utils'; +import { + createRequestFromInfo, + getDelegatingOperation, +} from './createRequest'; import { ApolloLink, execute as executeLink } from 'apollo-link'; import linkToFetcher from './linkToFetcher'; @@ -54,19 +37,6 @@ import { observableToAsyncIterable } from './observableToAsyncIterable'; import { isAsyncIterable } from 'iterall'; import mapAsyncIterator from './mapAsyncIterator'; -function getDelegatingOperation( - parentType: GraphQLObjectType, - schema: GraphQLSchema -): OperationTypeNode { - if (parentType === schema.getMutationType()) { - return 'mutation'; - } else if (parentType === schema.getSubscriptionType()) { - return 'subscription'; - } else { - return 'query'; - } -} - export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, ): any { @@ -202,238 +172,6 @@ export function delegateRequest({ } } -export function createRequestFromInfo({ - info, - schema, - operation = getDelegatingOperation(info.parentType, info.schema), - fieldName = info.fieldName, - additionalArgs, - fieldNodes = info.fieldNodes, -}: ICreateRequestFromInfo): Request { - return createRequest( - info.schema, - info.fragments, - info.operation.variableDefinitions, - info.variableValues, - schema, - operation, - fieldName, - additionalArgs, - fieldNodes, - ); -} - -export function createRequest( - sourceSchema: GraphQLSchema, - fragments: Record, - variableDefinitions: ReadonlyArray, - variableValues: Record, - targetSchemaOrSchemaConfig: GraphQLSchema | SubschemaConfig, - targetOperation: Operation, - targetField: string, - additionalArgs: Record, - fieldNodes: ReadonlyArray, -): Request { - let selections: Array = []; - const originalSelections: ReadonlyArray = fieldNodes; - originalSelections.forEach((field: FieldNode) => { - const fieldSelections = field.selectionSet - ? field.selectionSet.selections - : []; - selections = selections.concat(fieldSelections); - }); - - let selectionSet = undefined; - if (selections.length > 0) { - selectionSet = { - kind: Kind.SELECTION_SET, - selections: selections, - }; - } - - let variables = {}; - for (const variableDefinition of variableDefinitions) { - const varName = variableDefinition.variable.name.value; - const varType = typeFromAST(sourceSchema, (variableDefinition.type as NamedTypeNode)) as GraphQLInputType; - variables[varName] = serializeInputValue(varType, variableValues[varName]); - } - - let args = fieldNodes[0].arguments; - if (additionalArgs) { - const { - arguments: updatedArguments, - variableDefinitions: updatedVariableDefinitions, - variableValues: updatedVariableValues - } = updateArguments( - targetSchemaOrSchemaConfig, - targetOperation, - targetField, - args, - variableDefinitions, - variables, - additionalArgs, - ); - args = updatedArguments; - variableDefinitions = updatedVariableDefinitions; - variables = updatedVariableValues; - } - - const fieldNode: FieldNode = { - kind: Kind.FIELD, - alias: null, - arguments: args, - selectionSet, - name: { - kind: Kind.NAME, - value: targetField || fieldNodes[0].name.value, - }, - }; - - const operationDefinition: OperationDefinitionNode = { - kind: Kind.OPERATION_DEFINITION, - operation: targetOperation, - variableDefinitions, - selectionSet: { - kind: Kind.SELECTION_SET, - selections: [fieldNode], - }, - }; - - const fragmentDefinitions: Array = Object.keys(fragments).map( - fragmentName => fragments[fragmentName], - ); - - const document = { - kind: Kind.DOCUMENT, - definitions: [operationDefinition, ...fragmentDefinitions], - }; - - return { - document, - variables, - }; -} - -function updateArguments( - subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, - operation: OperationTypeNode, - fieldName: string, - argumentNodes: ReadonlyArray, - variableDefinitions: ReadonlyArray, - variableValues: Record, - newArgsMap: Record, -): { - arguments: Array, - variableDefinitions: Array, - variableValues: Record -} { - const schema = isSubschemaConfig(subschemaOrSubschemaConfig) ? - subschemaOrSubschemaConfig.schema : subschemaOrSubschemaConfig; - - let type: GraphQLObjectType; - if (operation === 'subscription') { - type = schema.getSubscriptionType(); - } else if (operation === 'mutation') { - type = schema.getMutationType(); - } else { - type = schema.getQueryType(); - } - - const newArgs: Record = {}; - if (argumentNodes) { - argumentNodes.forEach((argument: ArgumentNode) => { - newArgs[argument.name.value] = argument; - }); - } - - let varNames = variableDefinitions.reduce((acc, def) => { - acc[def.variable.name.value] = true; - return acc; - }, {}); - - const variables = {}; - let numGeneratedVariables = 0; - - const field: GraphQLField = type.getFields()[fieldName]; - field.args.forEach((argument: GraphQLArgument) => { - if (newArgsMap[argument.name]) { - const argName = argument.name; - let varName; - do { - varName = `_v${numGeneratedVariables++}_${argName}`; - } while (varNames[varName]); - - newArgs[argument.name] = { - kind: Kind.ARGUMENT, - name: { - kind: Kind.NAME, - value: argument.name, - }, - value: { - kind: Kind.VARIABLE, - name: { - kind: Kind.NAME, - value: varName, - }, - }, - }; - varNames[varName] = true; - variables[varName] = { - kind: Kind.VARIABLE_DEFINITION, - variable: { - kind: Kind.VARIABLE, - name: { - kind: Kind.NAME, - value: varName, - }, - }, - type: typeToAst(argument.type), - }; - variableValues[varName] = serializeInputValue( - argument.type, - newArgsMap[argument.name], - ); - } - }); - - return { - arguments: Object.keys(newArgs).map(argName => newArgs[argName]), - variableDefinitions: variableDefinitions.concat( - Object.keys(variables).map(varName => variables[varName]), - ), - variableValues, - }; -} - -function typeToAst(type: GraphQLInputType): TypeNode { - if (type instanceof GraphQLNonNull) { - const innerType = typeToAst(type.ofType); - if ( - innerType.kind === Kind.LIST_TYPE || - innerType.kind === Kind.NAMED_TYPE - ) { - return { - kind: Kind.NON_NULL_TYPE, - type: innerType, - }; - } else { - throw new Error('Incorrent inner non-null type'); - } - } else if (type instanceof GraphQLList) { - return { - kind: Kind.LIST_TYPE, - type: typeToAst(type.ofType), - }; - } else { - return { - kind: Kind.NAMED_TYPE, - name: { - kind: Kind.NAME, - value: type.toString(), - }, - }; - } -} function createExecutor( schema: GraphQLSchema, diff --git a/src/stitching/index.ts b/src/stitching/index.ts index 02bdb87fa3b..8d084ca7fdd 100644 --- a/src/stitching/index.ts +++ b/src/stitching/index.ts @@ -1,12 +1,8 @@ import makeRemoteExecutableSchema, { createResolver as defaultCreateRemoteResolver } from './makeRemoteExecutableSchema'; import introspectSchema from './introspectSchema'; import mergeSchemas from './mergeSchemas'; -import { - default as delegateToSchema, - createRequestFromInfo, - createRequest, - delegateRequest, -} from './delegateToSchema'; +import delegateToSchema, { delegateRequest } from './delegateToSchema'; +import { createRequestFromInfo, createRequest } from './createRequest'; import defaultMergedResolver from './defaultMergedResolver'; import { createMergedResolver } from './createMergedResolver'; import { dehoistResult, unwrapResult } from './proxiedResult'; From 6e8cad0682d3726c077d8fe569515af141168bfd Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 26 Jan 2020 06:55:18 -0500 Subject: [PATCH 167/250] refactor(updateArguments): linting --- src/stitching/createRequest.ts | 66 +++++++++++++++------------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/src/stitching/createRequest.ts b/src/stitching/createRequest.ts index 7c4b3dfe38f..61773bdb520 100644 --- a/src/stitching/createRequest.ts +++ b/src/stitching/createRequest.ts @@ -17,6 +17,7 @@ import { GraphQLList, GraphQLNonNull, TypeNode, + GraphQLType, } from 'graphql'; import { @@ -158,10 +159,10 @@ function updateArguments( subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, operation: OperationTypeNode, fieldName: string, - argumentNodes: ReadonlyArray, - variableDefinitions: ReadonlyArray, - variableValues: Record, - newArgsMap: Record, + argumentNodes: ReadonlyArray = [], + variableDefinitions: ReadonlyArray = [], + variableValues: Record = {}, + newArgsMap: Record = {}, ): { arguments: Array, variableDefinitions: Array, @@ -179,21 +180,18 @@ function updateArguments( type = schema.getQueryType(); } - const newArgs: Record = {}; - if (argumentNodes) { - argumentNodes.forEach((argument: ArgumentNode) => { - newArgs[argument.name.value] = argument; - }); - } - let varNames = variableDefinitions.reduce((acc, def) => { acc[def.variable.name.value] = true; return acc; }, {}); - - const variables = {}; let numGeneratedVariables = 0; + const updatedArgs: Record = {}; + argumentNodes.forEach((argument: ArgumentNode) => { + updatedArgs[argument.name.value] = argument; + }); + const newVariableDefinitions: Array = []; + const field: GraphQLField = type.getFields()[fieldName]; field.args.forEach((argument: GraphQLArgument) => { if (newArgsMap[argument.name]) { @@ -203,11 +201,11 @@ function updateArguments( varName = `_v${numGeneratedVariables++}_${argName}`; } while (varNames[varName]); - newArgs[argument.name] = { + updatedArgs[argument.name] = { kind: Kind.ARGUMENT, name: { kind: Kind.NAME, - value: argument.name, + value: argName, }, value: { kind: Kind.VARIABLE, @@ -218,7 +216,7 @@ function updateArguments( }, }; varNames[varName] = true; - variables[varName] = { + newVariableDefinitions.push({ kind: Kind.VARIABLE_DEFINITION, variable: { kind: Kind.VARIABLE, @@ -227,49 +225,43 @@ function updateArguments( value: varName, }, }, - type: typeToAst(argument.type), - }; + type: astFromType(argument.type), + }); variableValues[varName] = serializeInputValue( argument.type, - newArgsMap[argument.name], + newArgsMap[argName], ); } }); return { - arguments: Object.keys(newArgs).map(argName => newArgs[argName]), - variableDefinitions: variableDefinitions.concat( - Object.keys(variables).map(varName => variables[varName]), - ), + arguments: Object.keys(updatedArgs).map(argName => updatedArgs[argName]), + variableDefinitions: newVariableDefinitions, variableValues, }; } -function typeToAst(type: GraphQLInputType): TypeNode { +function astFromType(type: GraphQLType): TypeNode { if (type instanceof GraphQLNonNull) { - const innerType = typeToAst(type.ofType); - if ( - innerType.kind === Kind.LIST_TYPE || - innerType.kind === Kind.NAMED_TYPE - ) { - return { - kind: Kind.NON_NULL_TYPE, - type: innerType, - }; - } else { - throw new Error('Incorrent inner non-null type'); + const innerType = astFromType(type.ofType); + if (innerType.kind === Kind.NON_NULL_TYPE) { + throw new Error(`Invalid type node ${JSON.stringify(type)}. Inner type of non-null type cannot be a non-null type.`); } + return { + kind: Kind.NON_NULL_TYPE, + type: innerType, + }; } else if (type instanceof GraphQLList) { return { kind: Kind.LIST_TYPE, - type: typeToAst(type.ofType), + type: astFromType(type.ofType), }; } else { return { kind: Kind.NAMED_TYPE, name: { kind: Kind.NAME, - value: type.toString(), + value: type.name, }, }; } From 5831e92a2259f57af01f6a1411474df161a0b6f5 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 26 Jan 2020 22:20:16 -0500 Subject: [PATCH 168/250] fix(merging): use proper collectFields when possible --- src/stitching/checkResultAndHandleErrors.ts | 23 ++++++++++++++++----- src/stitching/mergeSchemas.ts | 5 ++--- src/utils/fieldNodes.ts | 1 + 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 3b1bf7e8561..e741521e446 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -29,8 +29,8 @@ import { } from '../Interfaces'; import resolveFromParentTypename from './resolveFromParentTypename'; import { setErrors, setObjectSubschema } from './proxiedResult'; -import { collectFields } from '../utils'; import { mergeFields } from './mergeFields'; +import { collectFields, ExecutionContext } from 'graphql/execution/execute'; export function checkResultAndHandleErrors( result: ExecutionResult, @@ -165,14 +165,27 @@ export function handleObject( return object; } + 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 ExecutionContext, + info.schema.getType(object.__typename) as GraphQLObjectType, + fieldNode.selectionSet, + subFieldNodes, + visitedFragmentNames, + ); + }); + const typeMap = isSubschemaConfig(subschema) ? mergedTypeInfo.typeMaps.get(subschema) : subschema.getTypeMap(); const fields = (typeMap[typeName] as GraphQLObjectType).getFields(); + const selections: Array = []; - info.fieldNodes.forEach(fieldNode => { - collectFields(fieldNode.selectionSet, info.fragments).forEach(s => { - if (!fields[s.name.value]) { - selections.push(s); + Object.keys(subFieldNodes).forEach(responseName => { + subFieldNodes[responseName].forEach(subFieldNode => { + if (!fields[subFieldNode.name.value]) { + selections.push(subFieldNode); } }); }); diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 14963b395f8..e4e2e97d772 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -329,9 +329,8 @@ function createMergeInfo( } }); - if (parsedFragments.length) { - mergedTypes[typeName].fragment = concatInlineFragments(typeName, parsedFragments); - } + parsedFragments.push(parseFragmentToInlineFragment(`... on ${typeName} { __typename }`)); + mergedTypes[typeName].fragment = concatInlineFragments(typeName, parsedFragments); } }); diff --git a/src/utils/fieldNodes.ts b/src/utils/fieldNodes.ts index 52f2dcdb340..efb08c5f17e 100644 --- a/src/utils/fieldNodes.ts +++ b/src/utils/fieldNodes.ts @@ -75,6 +75,7 @@ export function collectFields( case Kind.FRAGMENT_SPREAD: const fragmentName = selection.name.value; if (!visitedFragmentNames[fragmentName]) { + visitedFragmentNames[fragmentName] = true; collectFields( fragments[fragmentName].selectionSet, fragments, From cd357bef64aa885bbdd49bbc624a229b223569eb Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 27 Jan 2020 08:57:19 -0500 Subject: [PATCH 169/250] feat(merging): check subschema rather than fragment for merging allows for memoization as delegation plan no longer dependent on object includes additional refactoring and code cleanup drawback is that keys for fields cannot be spread across subschemas --- src/Interfaces.ts | 1 + src/stitching/checkResultAndHandleErrors.ts | 62 +++++--- src/stitching/mergeFields.ts | 159 ++++++++++++-------- src/stitching/mergeSchemas.ts | 17 ++- src/test/testAlternateMergeSchemas.ts | 11 +- src/utils/fragments.ts | 14 +- src/utils/index.ts | 2 +- 7 files changed, 173 insertions(+), 93 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index c382fef3e92..bd6cc145281 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -189,6 +189,7 @@ export type MergedTypeInfo = { uniqueFields: Record, nonUniqueFields: Record>, typeMaps: Map, + containsFragment: Map>, }; export type IFieldResolver> = ( diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index e741521e446..90b88847dcb 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -26,6 +26,7 @@ import { SubschemaConfig, IGraphQLToolsResolveInfo, isSubschemaConfig, + MergedTypeInfo, } from '../Interfaces'; import resolveFromParentTypename from './resolveFromParentTypename'; import { setErrors, setObjectSubschema } from './proxiedResult'; @@ -146,59 +147,74 @@ export function handleObject( return object; } - let typeName: string; - if (isAbstractType(type)) { - typeName = info.schema.getTypeMap()[resolveFromParentTypename(object)].name; - } else { - typeName = type.name; - } - + const typeName = + isAbstractType(type) ? + info.schema.getTypeMap()[resolveFromParentTypename(object)].name : + type.name; const mergedTypeInfo = info.mergeInfo.mergedTypes[typeName]; - let subschemas = mergedTypeInfo && mergedTypeInfo.subschemas; + let targetSubschemas = mergedTypeInfo && mergedTypeInfo.subschemas; - if (!subschemas) { + if (!targetSubschemas) { return object; } - subschemas = subschemas.filter(s => s !== subschema); - if (!subschemas.length) { + targetSubschemas = targetSubschemas.filter(s => s !== subschema); + if (!targetSubschemas.length) { return object; } + return mergeFields( + mergedTypeInfo, + typeName, + object, + getFieldsNotInSubschema( + collectSubFields(info, object.__typename), + subschema, + mergedTypeInfo, + object.__typename, + ), + [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 ExecutionContext, - info.schema.getType(object.__typename) as GraphQLObjectType, + 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 selections: Array = []; + const fieldsNotInSchema: Array = []; Object.keys(subFieldNodes).forEach(responseName => { subFieldNodes[responseName].forEach(subFieldNode => { if (!fields[subFieldNode.name.value]) { - selections.push(subFieldNode); + fieldsNotInSchema.push(subFieldNode); } }); }); - return mergeFields( - mergedTypeInfo, - typeName, - object, - selections, - subschemas, - context, - info, - ); + return fieldsNotInSchema; } export function handleNull( diff --git a/src/stitching/mergeFields.ts b/src/stitching/mergeFields.ts index 4981a1a3cc8..aceab19b885 100644 --- a/src/stitching/mergeFields.ts +++ b/src/stitching/mergeFields.ts @@ -9,77 +9,112 @@ import { MergedTypeInfo, } from '../Interfaces'; import { mergeProxiedResults } from './proxiedResult'; -import { objectContainsInlineFragment } from '../utils'; -export function mergeFields( - mergedType: MergedTypeInfo, +function buildDelegationPlan( + mergedTypeInfo: MergedTypeInfo, typeName: string, - object: any, originalSelections: Array, - subschemas: Array, - context: Record, - info: IGraphQLToolsResolveInfo, -): any { - // 1. use fragment contained within the map and existing result to calculate - // if possible to delegate to given subschema + sourceSubschemas: Array, + targetSubschemas: Array, +): { + delegationMap: Map>, + unproxiableSelections: Array, + proxiableSubschemas: Array, + nonProxiableSubschemas: Array, +} { + // 1. use fragment and source subschemas to calculate if possible to delegate to given subschema + // TODO: change logic so that fragment can be spread across multiple subschemas? const proxiableSubschemas: Array = []; - const nonProxiableSubschemas: Array = []; - subschemas.forEach(s => { - if (objectContainsInlineFragment(object, s.mergedTypeConfigs[typeName].parsedFragment)) { - proxiableSubschemas.push(s); + const nonProxiableSubschemas: Array = []; + + targetSubschemas.forEach(t => { + if (sourceSubschemas.some(s => + mergedTypeInfo.containsFragment.get(s).get(t.mergedTypeConfigs[typeName].parsedFragment) + )) { + proxiableSubschemas.push(t); } else { - nonProxiableSubschemas.push(s); + nonProxiableSubschemas.push(t); } }); - // 3. use uniqueFields map to assign fields to subschema if one of possible subschemas + const { uniqueFields, nonUniqueFields } = mergedTypeInfo; + const unproxiableSelections: Array = []; - const uniqueFields = mergedType.uniqueFields; - const nonUniqueFields = mergedType.nonUniqueFields; - const remainingSelections: Array = []; + // 2. for each selection: const delegationMap: Map> = new Map(); originalSelections.forEach(selection => { + + // 2a. use uniqueFields map to assign fields to subschema if one of possible subschemas + const uniqueSubschema: SubschemaConfig = uniqueFields[selection.name.value]; - if (uniqueSubschema && proxiableSubschemas.includes(uniqueSubschema)) { - const selections = delegationMap.get(uniqueSubschema); - if (selections) { - selections.push(selection); + if (uniqueSubschema) { + if (proxiableSubschemas.includes(uniqueSubschema)) { + const existingSubschema = delegationMap.get(uniqueSubschema); + if (existingSubschema) { + existingSubschema.push(selection); + } else { + delegationMap.set(uniqueSubschema, [selection]); + } } else { - delegationMap.set(uniqueSubschema, [selection]); + unproxiableSelections.push(selection); } + } else { - remainingSelections.push(selection); - } - }); - // 4. use nonUniqueFields to assign to a possible subschema, - // preferring one of the subschemas already targets of delegation + // 2b. use nonUniqueFields to assign to a possible subschema, + // preferring one of the subschemas already targets of delegation - const unproxiableSelections: Array = []; - remainingSelections.forEach(selection => { - let nonUniqueSubschemas: Array = nonUniqueFields[selection.name.value]; - nonUniqueSubschemas = nonUniqueSubschemas.filter(s => proxiableSubschemas.includes(s)); - if (nonUniqueSubschemas) { - const existingSelections = nonUniqueSubschemas.map(s => delegationMap.get(s)).find(selections => selections); - if (existingSelections) { - existingSelections.push(selection); + let nonUniqueSubschemas: Array = nonUniqueFields[selection.name.value]; + nonUniqueSubschemas = nonUniqueSubschemas.filter(s => proxiableSubschemas.includes(s)); + if (nonUniqueSubschemas) { + const subschemas: Array = Array.from(delegationMap.keys()); + const existingSubschema = nonUniqueSubschemas.find(s => subschemas.includes(s)); + if (existingSubschema) { + delegationMap.get(existingSubschema).push(selection); + } else { + delegationMap.set(nonUniqueSubschemas[0], [selection]); + } } else { - delegationMap.set(nonUniqueSubschemas[0], [selection]); + unproxiableSelections.push(selection); } - } else { - unproxiableSelections.push(selection); } }); - // 5. terminate if no delegations actually possible. + return { + delegationMap, + unproxiableSelections, + proxiableSubschemas, + nonProxiableSubschemas, + }; +} - if (!delegationMap.size) { +export function mergeFields( + mergedTypeInfo: MergedTypeInfo, + typeName: string, + object: any, + originalSelections: Array, + sourceSubschemas: Array, + targetSubschemas: Array, + context: Record, + info: IGraphQLToolsResolveInfo, +): any { + + if (!originalSelections.length) { return object; } - // 6. delegate! + const { + delegationMap, + unproxiableSelections, + proxiableSubschemas, + nonProxiableSubschemas, + } = buildDelegationPlan(mergedTypeInfo, typeName, originalSelections, sourceSubschemas, targetSubschemas); + + if (!delegationMap.size) { + return object; + } const maybePromises: Promise | any = []; delegationMap.forEach((selections: Array, s: SubschemaConfig) => { @@ -108,28 +143,26 @@ export function mergeFields( } } - // 7. repeat if necessary - - const mergeRemaining = (o: any) => { - if (unproxiableSelections) { - return mergeFields( - mergedType, + return containsPromises ? + Promise.all(maybePromises). + then(results => mergeFields( + mergedTypeInfo, typeName, - o, + mergeProxiedResults(object, ...results), unproxiableSelections, + sourceSubschemas.concat(proxiableSubschemas), nonProxiableSubschemas, context, - info - ); - } else { - return o; - } - }; - - if (containsPromises) { - return Promise.all(maybePromises). - then(results => mergeRemaining(mergeProxiedResults(object, ...results))); - } else { - return mergeRemaining(mergeProxiedResults(object, ...maybePromises)); - } + info, + )) : + mergeFields( + mergedTypeInfo, + typeName, + mergeProxiedResults(object, ...maybePromises), + unproxiableSelections, + sourceSubschemas.concat(proxiableSubschemas), + nonProxiableSubschemas, + context, + info, + ); } diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index e4e2e97d772..0e4e4a6e4c8 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -44,7 +44,9 @@ import { mergeDeep, parseFragmentToInlineFragment, concatInlineFragments, + typeContainsInlineFragment, } from '../utils'; +import { TypeMap } from 'graphql/type/schema'; type MergeTypeCandidate = { type: GraphQLNamedType; @@ -290,7 +292,7 @@ function createMergeInfo( const subschemas: Array = []; const parsedFragments: Array = []; const fields = Object.create({}); - const typeMaps = new Map(); + const typeMaps: Map = new Map(); mergedTypeCandidates.forEach(typeCandidate => { const subschemaConfig = typeCandidate.subschema as SubschemaConfig; @@ -316,10 +318,23 @@ function createMergeInfo( mergedTypes[typeName] = { subschemas, typeMaps, + containsFragment: new Map(), uniqueFields: Object.create({}), nonUniqueFields: Object.create({}), }; + subschemas.forEach(subschema => { + const type = typeMaps.get(subschema)[typeName] as GraphQLObjectType; + let subschemaMap = new Map(); + subschemas.filter(s => s !== subschema).forEach(s => { + const fragment = s.mergedTypeConfigs[typeName].parsedFragment; + if (fragment && typeContainsInlineFragment(type, fragment)) { + subschemaMap.set(fragment, true); + } + }); + mergedTypes[typeName].containsFragment.set(subschema, subschemaMap); + }); + Object.keys(fields).forEach(fieldName => { const supportedBySubschemas = fields[fieldName]; if (supportedBySubschemas.length === 1) { diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 1fb8ed56201..ce97124a212 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -1790,7 +1790,16 @@ describe('mergeTypes', () => { subschemas: [subschemaConfig1, subschemaConfig2], }); - const result1 = await graphql(mergedSchema, `{ rootField1 { test { field1 field2 } } }`); + const result1 = await graphql(mergedSchema, `{ + rootField1 { + test { + field1 + ... on Test { + field2 + } + } + } + }`); expect(result1).to.deep.equal({ data: { rootField1: { diff --git a/src/utils/fragments.ts b/src/utils/fragments.ts index ed4bfc5cfbc..ebfc3e065dd 100644 --- a/src/utils/fragments.ts +++ b/src/utils/fragments.ts @@ -4,6 +4,7 @@ import { Kind, parse, OperationDefinitionNode, + GraphQLObjectType, } from 'graphql'; export function concatInlineFragments( @@ -133,20 +134,25 @@ export function parseFragmentToInlineFragment( throw new Error('Could not parse fragment'); } -export function objectContainsInlineFragment(object: any, fragment: InlineFragmentNode): boolean { +export function typeContainsInlineFragment(type: GraphQLObjectType, fragment: InlineFragmentNode): boolean { + const fields = type.getFields(); + for (const selection of fragment.selectionSet.selections) { if (selection.kind === Kind.FIELD) { if (selection.alias) { - if (!object[selection.alias.value]) { + if (!fields[selection.alias.value]) { return false; } } else { - if (!object[selection.name.value]) { + if (!fields[selection.name.value]) { return false; } } + + // TODO: check that all subfields are also included. + } else if (selection.kind === Kind.INLINE_FRAGMENT) { - const containsFragment = objectContainsInlineFragment(object, selection); + const containsFragment = typeContainsInlineFragment(type, selection); if (!containsFragment) { return false; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 712a9d0205b..f65151f70b2 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -15,7 +15,7 @@ export { export { concatInlineFragments, parseFragmentToInlineFragment, - objectContainsInlineFragment, + typeContainsInlineFragment, } from './fragments'; export { mergeDeep } from './mergeDeep'; export { From be89138d5956331543a2a2c4cda3fc21ca276990 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 29 Jan 2020 16:18:01 -0500 Subject: [PATCH 170/250] feat(delegation): selectionSet option Specifying the selection Set option can indicate that there are no existing arguments. Use selectionSets instead of fragments for type merging. --- src/Interfaces.ts | 24 ++++-- src/stitching/createRequest.ts | 56 +++++++------ src/stitching/delegateToSchema.ts | 44 +++++++---- src/stitching/mergeFields.ts | 22 +++--- src/stitching/mergeSchemas.ts | 49 ++++++++---- src/test/testAlternateMergeSchemas.ts | 12 +-- ...ments.ts => AddMergedTypeSelectionSets.ts} | 6 +- src/transforms/AddReplacementSelectionSets.ts | 78 +++++++++++++++++++ src/transforms/index.ts | 6 +- src/utils/fragments.ts | 29 ------- src/utils/index.ts | 5 +- src/utils/selectionSets.ts | 40 ++++++++++ 12 files changed, 256 insertions(+), 115 deletions(-) rename src/transforms/{AddMergedTypeFragments.ts => AddMergedTypeSelectionSets.ts} (93%) create mode 100644 src/transforms/AddReplacementSelectionSets.ts create mode 100644 src/utils/selectionSets.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index bd6cc145281..1964395d11e 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -22,6 +22,7 @@ import { GraphQLObjectType, InlineFragmentNode, GraphQLOutputType, + SelectionSetNode, } from 'graphql'; import { TypeMap } from 'graphql/type/schema'; @@ -101,8 +102,7 @@ export type SubschemaConfig = { }; export type MergedTypeConfig = { - fragment?: string; - parsedFragment?: InlineFragmentNode, + selectionSet?: string; merge: MergedTypeResolver; }; @@ -111,7 +111,7 @@ export type MergedTypeResolver = ( context: Record, info: IGraphQLToolsResolveInfo, subschema: GraphQLSchema | SubschemaConfig, - fieldNodes: Array, + selectionSet: SelectionSetNode, ) => any; export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array }; @@ -132,8 +132,9 @@ export interface IDelegateToSchemaOptions { operation?: Operation; fieldName?: string; returnType?: GraphQLOutputType; - fieldNodes?: ReadonlyArray; args?: { [key: string]: any }; + selectionSet?: SelectionSetNode; + fieldNodes?: ReadonlyArray; context?: TContext; info: IGraphQLToolsResolveInfo; rootValue?: Record; @@ -147,8 +148,9 @@ export interface ICreateRequestFromInfo { schema: GraphQLSchema | SubschemaConfig; operation: Operation; fieldName: string; - additionalArgs: Record; - fieldNodes: ReadonlyArray; + args?: Record; + selectionSet?: SelectionSetNode; + fieldNodes?: ReadonlyArray; } export type IDelegateRequestOptions = { @@ -174,22 +176,28 @@ export type MergeInfo = { field: string; fragment: string; }>; + replacementSelectionSets: ReplacementSelectionSetMapping, replacementFragments: ReplacementFragmentMapping, mergedTypes: Record, delegateToSchema(options: IDelegateToSchemaOptions): any; }; +export type ReplacementSelectionSetMapping = { + [typeName: string]: { [fieldName: string]: SelectionSetNode }; +}; + export type ReplacementFragmentMapping = { [typeName: string]: { [fieldName: string]: InlineFragmentNode }; }; export type MergedTypeInfo = { subschemas: Array, - fragment?: InlineFragmentNode, + selectionSet?: SelectionSetNode, uniqueFields: Record, nonUniqueFields: Record>, typeMaps: Map, - containsFragment: Map>, + selectionSets: Map, + containsSelectionSet: Map>, }; export type IFieldResolver> = ( diff --git a/src/stitching/createRequest.ts b/src/stitching/createRequest.ts index 61773bdb520..d3aa9938f46 100644 --- a/src/stitching/createRequest.ts +++ b/src/stitching/createRequest.ts @@ -18,6 +18,7 @@ import { GraphQLNonNull, TypeNode, GraphQLType, + SelectionSetNode, } from 'graphql'; import { @@ -48,8 +49,9 @@ export function createRequestFromInfo({ schema, operation = getDelegatingOperation(info.parentType, info.schema), fieldName = info.fieldName, - additionalArgs, - fieldNodes = info.fieldNodes, + args, + selectionSet, + fieldNodes, }: ICreateRequestFromInfo): Request { return createRequest( info.schema, @@ -59,8 +61,9 @@ export function createRequestFromInfo({ schema, operation, fieldName, - additionalArgs, - fieldNodes, + args, + selectionSet, + selectionSet ? undefined : (fieldNodes ? fieldNodes : info.fieldNodes), ); } @@ -72,24 +75,28 @@ export function createRequest( targetSchemaOrSchemaConfig: GraphQLSchema | SubschemaConfig, targetOperation: Operation, targetField: string, - additionalArgs: Record, + args: Record, + selectionSet: SelectionSetNode, fieldNodes: ReadonlyArray, ): Request { - let selections: Array = []; - const originalSelections: ReadonlyArray = fieldNodes; - originalSelections.forEach((field: FieldNode) => { - const fieldSelections = field.selectionSet - ? field.selectionSet.selections - : []; - selections = selections.concat(fieldSelections); - }); + let argumentNodes: ReadonlyArray; - let selectionSet = undefined; - if (selections.length > 0) { - selectionSet = { + if (!selectionSet && fieldNodes) { + const selections: Array = fieldNodes.reduce( + (acc, fieldNode) => fieldNode.selectionSet ? + acc.concat(fieldNode.selectionSet.selections) : + acc, + [], + ); + + selectionSet = selections.length ? { kind: Kind.SELECTION_SET, selections: selections, - }; + } : undefined; + + argumentNodes = fieldNodes[0].arguments; + } else { + argumentNodes = []; } let variables = {}; @@ -99,8 +106,7 @@ export function createRequest( variables[varName] = serializeInputValue(varType, variableValues[varName]); } - let args = fieldNodes[0].arguments; - if (additionalArgs) { + if (args) { const { arguments: updatedArguments, variableDefinitions: updatedVariableDefinitions, @@ -109,20 +115,20 @@ export function createRequest( targetSchemaOrSchemaConfig, targetOperation, targetField, - args, + argumentNodes, variableDefinitions, variables, - additionalArgs, + args, ); - args = updatedArguments; + argumentNodes = updatedArguments; variableDefinitions = updatedVariableDefinitions; variables = updatedVariableValues; } - const fieldNode: FieldNode = { + const rootfieldNode: FieldNode = { kind: Kind.FIELD, alias: null, - arguments: args, + arguments: argumentNodes, selectionSet, name: { kind: Kind.NAME, @@ -136,7 +142,7 @@ export function createRequest( variableDefinitions, selectionSet: { kind: Kind.SELECTION_SET, - selections: [fieldNode], + selections: [rootfieldNode], }, }; diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 5694f47f2aa..146e69a0867 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -18,12 +18,14 @@ import { import { ExpandAbstractTypes, FilterToSchema, + AddReplacementSelectionSets, AddReplacementFragments, - AddMergedTypeFragments, + AddMergedTypeSelectionSets, AddTypenameToAbstract, CheckResultAndHandleErrors, applyRequestTransforms, applyResultTransforms, + Transform, } from '../transforms'; import { @@ -54,7 +56,8 @@ export default function delegateToSchema( fieldName = info.fieldName, returnType = info.returnType, args, - fieldNodes = info.fieldNodes, + selectionSet, + fieldNodes, } = options; const request = createRequestFromInfo({ @@ -62,7 +65,8 @@ export default function delegateToSchema( schema: subschemaOrSubschemaConfig, operation, fieldName, - additionalArgs: args, + args, + selectionSet, fieldNodes, }); @@ -82,7 +86,6 @@ export function delegateRequest({ info, operation = getDelegatingOperation(info.parentType, info.schema), fieldName = info.fieldName, - fieldNodes = info.fieldNodes, returnType = info.returnType, context, transforms = [], @@ -102,25 +105,35 @@ export function delegateRequest({ rootValue = rootValue || info.rootValue; } - transforms = [ + let delegationTransforms: Array = [ new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType, skipTypeMerging), - ...transforms, - new ExpandAbstractTypes(info.schema, targetSchema), ]; if (info.mergeInfo) { - transforms.push( + 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) { + delegationTransforms.push( new AddReplacementFragments(targetSchema, info.mergeInfo.replacementFragments), - new AddMergedTypeFragments(targetSchema, info.mergeInfo.mergedTypes), ); } - transforms.push( + delegationTransforms.push( new FilterToSchema(targetSchema), new AddTypenameToAbstract(targetSchema), ); - request = applyRequestTransforms(request, transforms); + request = applyRequestTransforms(request, delegationTransforms); if (!skipValidation) { const errors = validate(targetSchema, request.document); @@ -140,9 +153,10 @@ export function delegateRequest({ }); if (executionResult instanceof Promise) { - return executionResult.then((originalResult: any) => applyResultTransforms(originalResult, transforms)); + return executionResult.then((originalResult: any) => + applyResultTransforms(originalResult, delegationTransforms)); } else { - return applyResultTransforms(executionResult, transforms); + return applyResultTransforms(executionResult, delegationTransforms); } } else if (operation === 'subscription') { @@ -157,7 +171,7 @@ export function delegateRequest({ if (isAsyncIterable(subscriptionResult)) { // "subscribe" to the subscription result and map the result through the transforms return mapAsyncIterator(subscriptionResult, result => { - const transformedResult = applyResultTransforms(result, transforms); + const transformedResult = applyResultTransforms(result, delegationTransforms); // wrap with fieldName to return for an additional round of resolutioon // with payload as rootValue return { @@ -165,7 +179,7 @@ export function delegateRequest({ }; }); } else { - return applyResultTransforms(subscriptionResult, transforms); + return applyResultTransforms(subscriptionResult, delegationTransforms); } }); diff --git a/src/stitching/mergeFields.ts b/src/stitching/mergeFields.ts index aceab19b885..3a3d1137df2 100644 --- a/src/stitching/mergeFields.ts +++ b/src/stitching/mergeFields.ts @@ -22,15 +22,17 @@ function buildDelegationPlan( proxiableSubschemas: Array, nonProxiableSubschemas: Array, } { - // 1. use fragment and source subschemas to calculate if possible to delegate to given subschema - // TODO: change logic so that fragment can be spread across multiple subschemas? + // 1. calculate if possible to delegate to given subschema + // TODO: change logic so that required selection set can be spread across multiple subschemas? const proxiableSubschemas: Array = []; const nonProxiableSubschemas: Array = []; targetSubschemas.forEach(t => { - if (sourceSubschemas.some(s => - mergedTypeInfo.containsFragment.get(s).get(t.mergedTypeConfigs[typeName].parsedFragment) + if (sourceSubschemas.some(s => { + const selectionSet = mergedTypeInfo.selectionSets.get(t); + return mergedTypeInfo.containsSelectionSet.get(s).get(selectionSet); + } )) { proxiableSubschemas.push(t); } else { @@ -118,19 +120,15 @@ export function mergeFields( const maybePromises: Promise | any = []; delegationMap.forEach((selections: Array, s: SubschemaConfig) => { - const newFieldNodes = [{ - ...info.fieldNodes[0], - selectionSet: { - kind: Kind.SELECTION_SET, - selections, - } - }]; const maybePromise = s.mergedTypeConfigs[typeName].merge( object, context, info, s, - newFieldNodes, + { + kind: Kind.SELECTION_SET, + selections, + }, ); maybePromises.push(maybePromise); }); diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 0e4e4a6e4c8..d5032d561ec 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -10,7 +10,8 @@ import { parse, Kind, GraphQLDirective, - InlineFragmentNode, + SelectionNode, + SelectionSetNode, } from 'graphql'; import { IDelegateToSchemaOptions, @@ -44,7 +45,8 @@ import { mergeDeep, parseFragmentToInlineFragment, concatInlineFragments, - typeContainsInlineFragment, + typeContainsSelectionSet, + parseSelectionSet, } from '../utils'; import { TypeMap } from 'graphql/type/schema'; @@ -290,9 +292,10 @@ function createMergeInfo( if (mergedTypeCandidates.length) { const subschemas: Array = []; - const parsedFragments: Array = []; + let requiredSelections: Array = []; const fields = Object.create({}); const typeMaps: Map = new Map(); + const selectionSets: Map = new Map(); mergedTypeCandidates.forEach(typeCandidate => { const subschemaConfig = typeCandidate.subschema as SubschemaConfig; @@ -306,10 +309,10 @@ function createMergeInfo( }); const mergedTypeConfig = subschemaConfig.mergedTypeConfigs[typeName]; - if (mergedTypeConfig.fragment) { - const parsedFragment = parseFragmentToInlineFragment(mergedTypeConfig.fragment); - parsedFragments.push(parsedFragment); - mergedTypeConfig.parsedFragment = parsedFragment; + if (mergedTypeConfig.selectionSet) { + const selectionSet = parseSelectionSet(mergedTypeConfig.selectionSet); + requiredSelections = requiredSelections.concat(selectionSet.selections); + selectionSets.set(subschemaConfig, selectionSet); } subschemas.push(subschemaConfig); @@ -318,7 +321,8 @@ function createMergeInfo( mergedTypes[typeName] = { subschemas, typeMaps, - containsFragment: new Map(), + selectionSets, + containsSelectionSet: new Map(), uniqueFields: Object.create({}), nonUniqueFields: Object.create({}), }; @@ -327,12 +331,12 @@ function createMergeInfo( const type = typeMaps.get(subschema)[typeName] as GraphQLObjectType; let subschemaMap = new Map(); subschemas.filter(s => s !== subschema).forEach(s => { - const fragment = s.mergedTypeConfigs[typeName].parsedFragment; - if (fragment && typeContainsInlineFragment(type, fragment)) { - subschemaMap.set(fragment, true); + const selectionSet = selectionSets.get(s); + if (selectionSet && typeContainsSelectionSet(type, selectionSet)) { + subschemaMap.set(selectionSet, true); } }); - mergedTypes[typeName].containsFragment.set(subschema, subschemaMap); + mergedTypes[typeName].containsSelectionSet.set(subschema, subschemaMap); }); Object.keys(fields).forEach(fieldName => { @@ -344,8 +348,11 @@ function createMergeInfo( } }); - parsedFragments.push(parseFragmentToInlineFragment(`... on ${typeName} { __typename }`)); - mergedTypes[typeName].fragment = concatInlineFragments(typeName, parsedFragments); + requiredSelections.push(parseSelectionSet(`{ __typename }`).selections[0]); + mergedTypes[typeName].selectionSet = { + kind: Kind.SELECTION_SET, + selections: requiredSelections, + }; } }); @@ -387,6 +394,7 @@ function createMergeInfo( }); }, fragments: [], + replacementSelectionSets: undefined, replacementFragments: undefined, mergedTypes, }; @@ -396,6 +404,8 @@ function completeMergeInfo( mergeInfo: MergeInfo, resolvers: IResolversParameter, ): MergeInfo { + const replacementSelectionSets = Object.create(null); + Object.keys(resolvers).forEach(typeName => { const type = resolvers[typeName]; if (type instanceof GraphQLScalarType) { @@ -403,6 +413,16 @@ function completeMergeInfo( } Object.keys(type).forEach(fieldName => { const field = type[fieldName]; + if (field.selectionSet) { + const selectionSet = parseSelectionSet(field.selectionSet); + replacementSelectionSets[typeName] = replacementSelectionSets[typeName] || {}; + replacementSelectionSets[typeName][fieldName] = replacementSelectionSets[typeName][fieldName] || { + kind: Kind.SELECTION_SET, + selections: [], + }; + replacementSelectionSets[typeName][fieldName].selections = + replacementSelectionSets[typeName][fieldName].selections.concat(selectionSet.selections); + } if (field.fragment) { mergeInfo.fragments.push({ field: fieldName, @@ -432,6 +452,7 @@ function completeMergeInfo( }); }); + mergeInfo.replacementSelectionSets = replacementSelectionSets; mergeInfo.replacementFragments = replacementFragments; return mergeInfo; diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index ce97124a212..177a7091566 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -1752,13 +1752,13 @@ describe('mergeTypes', () => { schema: schema1, mergedTypeConfigs: { Test: { - fragment: 'fragment TestFragment on Test { id }', - merge: (originalResult, context, info, subschema, fieldNodes) => delegateToSchema({ + selectionSet: '{ id }', + merge: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ schema: subschema, operation: 'query', fieldName: 'getTest', args: { id: originalResult.id }, - fieldNodes, + selectionSet, context, info, skipTypeMerging: true, @@ -1771,13 +1771,13 @@ describe('mergeTypes', () => { schema: schema2, mergedTypeConfigs: { Test: { - fragment: 'fragment TestFragment on Test { id }', - merge: (originalResult, context, info, subschema, fieldNodes) => delegateToSchema({ + selectionSet: '{ id }', + merge: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ schema: subschema, operation: 'query', fieldName: 'getTest', args: { id: originalResult.id }, - fieldNodes, + selectionSet, context, info, skipTypeMerging: true, diff --git a/src/transforms/AddMergedTypeFragments.ts b/src/transforms/AddMergedTypeSelectionSets.ts similarity index 93% rename from src/transforms/AddMergedTypeFragments.ts rename to src/transforms/AddMergedTypeSelectionSets.ts index 98021a86f4a..8f330052da3 100644 --- a/src/transforms/AddMergedTypeFragments.ts +++ b/src/transforms/AddMergedTypeSelectionSets.ts @@ -24,7 +24,7 @@ export default class AddMergedTypeFragments implements Transform { } public transformRequest(originalRequest: Request): Request { - const document = addMergedTypeFragments( + const document = addMergedTypeSelectionSets( this.targetSchema, originalRequest.document, this.mapping, @@ -36,7 +36,7 @@ export default class AddMergedTypeFragments implements Transform { } } -function addMergedTypeFragments( +function addMergedTypeSelectionSets( targetSchema: GraphQLSchema, document: DocumentNode, mapping: Record, @@ -55,7 +55,7 @@ function addMergedTypeFragments( let selections = node.selections; if (mapping[parentTypeName]) { - selections = selections.concat(mapping[parentTypeName].fragment); + selections = selections.concat(mapping[parentTypeName].selectionSet.selections); } if (selections !== node.selections) { diff --git a/src/transforms/AddReplacementSelectionSets.ts b/src/transforms/AddReplacementSelectionSets.ts new file mode 100644 index 00000000000..7afe04dac4b --- /dev/null +++ b/src/transforms/AddReplacementSelectionSets.ts @@ -0,0 +1,78 @@ +import { + DocumentNode, + GraphQLSchema, + GraphQLType, + Kind, + SelectionSetNode, + TypeInfo, + visit, + visitWithTypeInfo, +} from 'graphql'; +import { Request, ReplacementSelectionSetMapping } from '../Interfaces'; +import { Transform } from './transforms'; + +export default class AddReplacementSelectionSets implements Transform { + private schema: GraphQLSchema; + private mapping: ReplacementSelectionSetMapping; + + constructor( + schema: GraphQLSchema, + mapping: ReplacementSelectionSetMapping, + ) { + this.schema = schema; + this.mapping = mapping; + } + + public transformRequest(originalRequest: Request): Request { + const document = replaceFieldsWithSelectionSet( + this.schema, + originalRequest.document, + this.mapping, + ); + return { + ...originalRequest, + document, + }; + } +} + +function replaceFieldsWithSelectionSet( + schema: GraphQLSchema, + document: DocumentNode, + mapping: ReplacementSelectionSetMapping, +): DocumentNode { + const typeInfo = new TypeInfo(schema); + return visit( + document, + visitWithTypeInfo(typeInfo, { + [Kind.SELECTION_SET]( + node: SelectionSetNode, + ): SelectionSetNode | null | undefined { + const parentType: GraphQLType = typeInfo.getParentType(); + if (parentType) { + const parentTypeName = parentType.name; + let selections = node.selections; + + if (mapping[parentTypeName]) { + node.selections.forEach(selection => { + if (selection.kind === Kind.FIELD) { + const name = selection.name.value; + const selectionSet = mapping[parentTypeName][name]; + if (selectionSet) { + selections = selections.concat(selectionSet.selections); + } + } + }); + } + + if (selections !== node.selections) { + return { + ...node, + selections, + }; + } + } + }, + }), + ); +} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 91bd8595690..fcdff45b7b2 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -10,8 +10,8 @@ export { default as transformSchema, wrapSchema } from './transformSchema'; export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErrors'; export { default as ExpandAbstractTypes } from './ExpandAbstractTypes'; -export { default as AddReplacementFragments } from './AddReplacementFragments'; -export { default as AddMergedTypeFragments } from './AddMergedTypeFragments'; +export { default as AddReplacementSelectionSets } from './AddReplacementSelectionSets'; +export { default as AddMergedTypeSelectionSets } from './AddMergedTypeSelectionSets'; export { default as FilterToSchema } from './FilterToSchema'; export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; @@ -35,6 +35,8 @@ export { default as MapFields } from './MapFields'; export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; // superseded by AddReplacementFragments export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; +// superseded by AddReplacementSelectionSets +export { default as AddReplacementFragments } from './AddReplacementFragments'; // superseded by TransformQuery export { default as WrapQuery } from './WrapQuery'; export { default as ExtractField } from './ExtractField'; diff --git a/src/utils/fragments.ts b/src/utils/fragments.ts index ebfc3e065dd..303374b7ebf 100644 --- a/src/utils/fragments.ts +++ b/src/utils/fragments.ts @@ -4,7 +4,6 @@ import { Kind, parse, OperationDefinitionNode, - GraphQLObjectType, } from 'graphql'; export function concatInlineFragments( @@ -133,31 +132,3 @@ export function parseFragmentToInlineFragment( throw new Error('Could not parse fragment'); } - -export function typeContainsInlineFragment(type: GraphQLObjectType, fragment: InlineFragmentNode): boolean { - const fields = type.getFields(); - - for (const selection of fragment.selectionSet.selections) { - if (selection.kind === Kind.FIELD) { - if (selection.alias) { - if (!fields[selection.alias.value]) { - return false; - } - } else { - if (!fields[selection.name.value]) { - return false; - } - } - - // TODO: check that all subfields are also included. - - } else if (selection.kind === Kind.INLINE_FRAGMENT) { - const containsFragment = typeContainsInlineFragment(type, selection); - if (!containsFragment) { - return false; - } - } - } - - return true; -} diff --git a/src/utils/index.ts b/src/utils/index.ts index f65151f70b2..2f7b787f07d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -15,8 +15,11 @@ export { export { concatInlineFragments, parseFragmentToInlineFragment, - typeContainsInlineFragment, } from './fragments'; +export { + parseSelectionSet, + typeContainsSelectionSet, +} from './selectionSets'; export { mergeDeep } from './mergeDeep'; export { collectFields, diff --git a/src/utils/selectionSets.ts b/src/utils/selectionSets.ts new file mode 100644 index 00000000000..b8e1706bdcd --- /dev/null +++ b/src/utils/selectionSets.ts @@ -0,0 +1,40 @@ +import { + OperationDefinitionNode, + SelectionSetNode, + parse, + Kind, + GraphQLObjectType, +} from 'graphql'; + +export function parseSelectionSet(selectionSet: string): SelectionSetNode { + const query = (parse(selectionSet).definitions[0] as OperationDefinitionNode); + return query.selectionSet; +} + +export function typeContainsSelectionSet(type: GraphQLObjectType, selectionSet: SelectionSetNode): boolean { + const fields = type.getFields(); + + for (const selection of selectionSet.selections) { + if (selection.kind === Kind.FIELD) { + if (selection.alias) { + if (!fields[selection.alias.value]) { + return false; + } + } else { + if (!fields[selection.name.value]) { + return false; + } + } + + // TODO: check that all subfields are also included. + + } else if (selection.kind === Kind.INLINE_FRAGMENT) { + const containsSelectionSet = typeContainsSelectionSet(type, selection.selectionSet); + if (!containsSelectionSet) { + return false; + } + } + } + + return true; +} From 603050715f4ff81df4be3529c9de5e857af3483c Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 29 Jan 2020 20:17:28 -0500 Subject: [PATCH 171/250] feat(merging): allow keys for merged types to also include subfields --- src/utils/selectionSets.ts | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/utils/selectionSets.ts b/src/utils/selectionSets.ts index b8e1706bdcd..dc3183615a9 100644 --- a/src/utils/selectionSets.ts +++ b/src/utils/selectionSets.ts @@ -4,6 +4,7 @@ import { parse, Kind, GraphQLObjectType, + getNamedType, } from 'graphql'; export function parseSelectionSet(selectionSet: string): SelectionSetNode { @@ -11,22 +12,26 @@ export function parseSelectionSet(selectionSet: string): SelectionSetNode { return query.selectionSet; } -export function typeContainsSelectionSet(type: GraphQLObjectType, selectionSet: SelectionSetNode): boolean { +export function typeContainsSelectionSet( + type: GraphQLObjectType, + selectionSet: SelectionSetNode, +): boolean { const fields = type.getFields(); for (const selection of selectionSet.selections) { if (selection.kind === Kind.FIELD) { - if (selection.alias) { - if (!fields[selection.alias.value]) { - return false; - } - } else { - if (!fields[selection.name.value]) { - return false; - } + const field = fields[selection.name.value]; + + if (!field) { + return false; } - // TODO: check that all subfields are also included. + if (selection.selectionSet) { + return typeContainsSelectionSet( + getNamedType(field.type) as GraphQLObjectType, + selection.selectionSet, + ); + } } else if (selection.kind === Kind.INLINE_FRAGMENT) { const containsSelectionSet = typeContainsSelectionSet(type, selection.selectionSet); From 3a1560f388d7cdfbed4ebec94f2fd0232038ea03 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 29 Jan 2020 21:16:39 -0500 Subject: [PATCH 172/250] fix(merging): fixes merging for non root types adds a canonical test --- src/stitching/checkResultAndHandleErrors.ts | 16 ++- src/stitching/proxiedResult.ts | 25 ++-- ...tion.ts => testStitchingFromSubschemas.ts} | 11 +- src/test/testTypeMerging.ts | 110 ++++++++++++++++++ src/test/tests.ts | 3 +- 5 files changed, 142 insertions(+), 23 deletions(-) rename src/test/{testIntegration.ts => testStitchingFromSubschemas.ts} (90%) create mode 100644 src/test/testTypeMerging.ts diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 90b88847dcb..7f13d2f8483 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -163,16 +163,20 @@ export function handleObject( return object; } + const subFields = collectSubFields(info, object.__typename); + + const selections = getFieldsNotInSubschema( + subFields, + subschema, + mergedTypeInfo, + object.__typename, + ); + return mergeFields( mergedTypeInfo, typeName, object, - getFieldsNotInSubschema( - collectSubFields(info, object.__typename), - subschema, - mergedTypeInfo, - object.__typename, - ), + selections, [subschema as SubschemaConfig], targetSubschemas, context, diff --git a/src/stitching/proxiedResult.ts b/src/stitching/proxiedResult.ts index 085f6a2cdb5..a6e5dbd8bce 100644 --- a/src/stitching/proxiedResult.ts +++ b/src/stitching/proxiedResult.ts @@ -9,18 +9,18 @@ import { relocatedError } from './errors'; import { mergeDeep } from '../utils'; export let OBJECT_SUBSCHEMA_SYMBOL: any; -export let SUBSCHEMA_MAP_SYMBOL: any; +export let FIELD_SUBSCHEMA_MAP_SYMBOL: any; export let ERROR_SYMBOL: any; if ( (typeof global !== 'undefined' && 'Symbol' in global) || (typeof window !== 'undefined' && 'Symbol' in window) ) { OBJECT_SUBSCHEMA_SYMBOL = Symbol('initialSubschema'); - SUBSCHEMA_MAP_SYMBOL = Symbol('subschemaMap'); + FIELD_SUBSCHEMA_MAP_SYMBOL = Symbol('subschemaMap'); ERROR_SYMBOL = Symbol('subschemaErrors'); } else { OBJECT_SUBSCHEMA_SYMBOL = Symbol('@@__initialSubschema'); - SUBSCHEMA_MAP_SYMBOL = Symbol('@@__subschemaMap'); + FIELD_SUBSCHEMA_MAP_SYMBOL = Symbol('@@__subschemaMap'); ERROR_SYMBOL = '@@__subschemaErrors'; } @@ -29,7 +29,7 @@ export function isProxiedResult(result: any) { } export function getSubschema(result: any, responseKey: string): GraphQLSchema | SubschemaConfig { - const subschema = result[SUBSCHEMA_MAP_SYMBOL] && result[SUBSCHEMA_MAP_SYMBOL][responseKey]; + const subschema = result[FIELD_SUBSCHEMA_MAP_SYMBOL] && result[FIELD_SUBSCHEMA_MAP_SYMBOL][responseKey]; return subschema ? subschema : result[OBJECT_SUBSCHEMA_SYMBOL]; } @@ -37,11 +37,6 @@ export function setObjectSubschema(result: any, subschema: GraphQLSchema | Subsc result[OBJECT_SUBSCHEMA_SYMBOL] = subschema; } -export function setSubschemaForKey(result: any, responseKey: string, subschema: GraphQLSchema | SubschemaConfig) { - result[SUBSCHEMA_MAP_SYMBOL] = result[SUBSCHEMA_MAP_SYMBOL] || Object.create(null); - result[SUBSCHEMA_MAP_SYMBOL][responseKey] = subschema; -} - export function setErrors(result: any, errors: Array) { result[ERROR_SYMBOL] = errors; } @@ -130,15 +125,17 @@ export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): any export function mergeProxiedResults(target: any, ...sources: any): any { const errors = target[ERROR_SYMBOL].concat(sources.map((source: any) => source[ERROR_SYMBOL])); - const subschemaMap = sources.reduce((acc: Record, source: any) => { + const fieldSubschemaMap = sources.reduce((acc: Record, source: any) => { const subschema = source[OBJECT_SUBSCHEMA_SYMBOL]; Object.keys(source).forEach(key => { acc[key] = subschema; }); return acc; }, {}); - return mergeDeep(target, ...sources, { - [ERROR_SYMBOL]: errors, - [SUBSCHEMA_MAP_SYMBOL]: subschemaMap, - }); + const result = mergeDeep(target, ...sources); + result[ERROR_SYMBOL] = errors; + result[FIELD_SUBSCHEMA_MAP_SYMBOL] = target[FIELD_SUBSCHEMA_MAP_SYMBOL] ? + mergeDeep(target[FIELD_SUBSCHEMA_MAP_SYMBOL], fieldSubschemaMap) : + fieldSubschemaMap; + return result; } diff --git a/src/test/testIntegration.ts b/src/test/testStitchingFromSubschemas.ts similarity index 90% rename from src/test/testIntegration.ts rename to src/test/testStitchingFromSubschemas.ts index 776401890d9..21abcbbdcc4 100644 --- a/src/test/testIntegration.ts +++ b/src/test/testStitchingFromSubschemas.ts @@ -6,13 +6,20 @@ // always returning the necessary object fields: // https://medium.com/paypal-engineering/graphql-resolvers-best-practices-cd36fdbcef55 +// This is achieved at the considerable cost of moving all of the delegation +// logic from the gateway to each subschema so that each subschema imports all +// the required types and performs all delegation. + // The fragment field is still necessary when working with a remote schema // where this is not possible. import { expect } from 'chai'; import { graphql } from 'graphql'; -import { delegateToSchema, mergeSchemas } from '../index'; -import { addMockFunctionsToSchema } from '../mock'; +import { + delegateToSchema, + mergeSchemas, + addMockFunctionsToSchema, +} from '../index'; const chirpTypeDefs = ` type Chirp { diff --git a/src/test/testTypeMerging.ts b/src/test/testTypeMerging.ts new file mode 100644 index 00000000000..cd764b34df6 --- /dev/null +++ b/src/test/testTypeMerging.ts @@ -0,0 +1,110 @@ +/* tslint:disable:no-unused-expression */ + +// The below is meant to be an alternative canonical schema stitching example +// which relies on type merging. + +import { expect } from 'chai'; +import { graphql } from 'graphql'; +import { + delegateToSchema, + mergeSchemas, + addMockFunctionsToSchema, + makeExecutableSchema, +} from '../index'; + +const chirpSchema = makeExecutableSchema({ + typeDefs: ` + type Chirp { + id: ID! + text: String + author: User + } + + type User { + id: ID! + chirps: [Chirp] + } + type Query { + userById(id: ID!): User + } + `, +}); + +addMockFunctionsToSchema({ schema: chirpSchema }); + +const authorSchema = makeExecutableSchema({ + typeDefs: ` + type User { + id: ID! + email: String + } + type Query { + userById(id: ID!): User + } + `, +}); + +addMockFunctionsToSchema({ schema: authorSchema }); + +const mergedSchema = mergeSchemas({ + subschemas: [{ + schema: chirpSchema, + mergedTypeConfigs: { + User: { + selectionSet: '{ id }', + merge: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ + schema: subschema, + operation: 'query', + fieldName: 'userById', + args: { id: originalResult.id }, + selectionSet, + context, + info, + skipTypeMerging: true, + }), + } + }, + }, { + schema: authorSchema, + mergedTypeConfigs: { + User: { + selectionSet: '{ id }', + merge: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ + schema: subschema, + operation: 'query', + fieldName: 'userById', + args: { id: originalResult.id }, + selectionSet, + context, + info, + skipTypeMerging: true, + }), + } + }, + }], +}); + +describe('merging using type merging', () => { + it('works', async () => { + const query = ` + query { + userById(id: 5) { + chirps { + id + textAlias: text + author { + email + } + } + } + } + `; + + const result = await graphql(mergedSchema, query); + + expect(result.errors).to.be.undefined; + expect(result.data.userById.chirps[1].id).to.not.be.null; + expect(result.data.userById.chirps[1].text).to.not.be.null; + expect(result.data.userById.chirps[1].author.email).to.not.be.null; + }); +}); diff --git a/src/test/tests.ts b/src/test/tests.ts index 17c3f222d13..a65618dbdb5 100755 --- a/src/test/tests.ts +++ b/src/test/tests.ts @@ -14,7 +14,8 @@ import './testResolution'; import './testSchemaGenerator'; import './testTransforms'; import './testExtensionExtraction'; -import './testIntegration'; +import './testStitchingFromSubschemas'; +import './testTypeMerging'; import './testUpload'; import './testUtils'; From 86fa0996449ecf465cade87f844976db41a2d447 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 29 Jan 2020 21:26:33 -0500 Subject: [PATCH 173/250] refactor(addMockFunctionsToSchema) shorten name, retain old name for backwards compatibility --- docs/source/mocking.md | 24 +++--- docs/source/resolvers.md | 2 +- docs/source/schema-stitching.md | 10 +-- src/mock.ts | 9 ++- src/test/testFragmentsAreNotDuplicated.ts | 4 +- src/test/testMocking.ts | 94 +++++++++++------------ src/test/testStitchingFromSubschemas.ts | 6 +- src/test/testTypeMerging.ts | 6 +- 8 files changed, 78 insertions(+), 77 deletions(-) diff --git a/docs/source/mocking.md b/docs/source/mocking.md index 660590a787d..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 resolvers 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 2f48f99dbff..e70a47e4d9a 100644 --- a/docs/source/resolvers.md +++ b/docs/source/resolvers.md @@ -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. diff --git a/docs/source/schema-stitching.md b/docs/source/schema-stitching.md index dcdaaa73aac..f3320f10a63 100644 --- a/docs/source/schema-stitching.md +++ b/docs/source/schema-stitching.md @@ -18,7 +18,7 @@ In this example we'll stitch together two very simple schemas. In this case, we' ```js import { makeExecutableSchema, - addMockFunctionsToSchema, + addMocksToSchema, mergeSchemas, } from 'graphql-tools'; @@ -40,7 +40,7 @@ const chirpSchema = makeExecutableSchema({ ` }); -addMockFunctionsToSchema({ schema: chirpSchema }); +addMocksToSchema({ schema: chirpSchema }); // Mocked author schema const authorSchema = makeExecutableSchema({ @@ -56,7 +56,7 @@ const authorSchema = makeExecutableSchema({ ` }); -addMockFunctionsToSchema({ schema: authorSchema }); +addMocksToSchema({ schema: authorSchema }); export const schema = mergeSchemas({ subschemas: [ @@ -179,7 +179,7 @@ For example, suppose we transform the `chirpSchema` by removing the `chirpsByAut ```ts import { makeExecutableSchema, - addMockFunctionsToSchema, + addMocksToSchema, mergeSchemas, FilterRootFields, RenameTypes, @@ -204,7 +204,7 @@ const chirpSchema = makeExecutableSchema({ ` }); -addMockFunctionsToSchema({ schema: chirpSchema }); +addMocksToSchema({ schema: chirpSchema }); // create transforms diff --git a/src/mock.ts b/src/mock.ts index 4d7a0ebd6ef..766b771a398 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -29,7 +29,7 @@ import { ITypeDefinitions, } from './Interfaces'; -// This function wraps addMockFunctionsToSchema for more convenience +// This function wraps addMocksToSchema for more convenience function mockServer( schema: GraphQLSchema | ITypeDefinitions, mocks: IMocks, @@ -43,7 +43,7 @@ function mockServer( mySchema = schema; } - addMockFunctionsToSchema({ schema: mySchema, mocks, preserveResolvers }); + addMocksToSchema({ schema: mySchema, mocks, preserveResolvers }); return { query: (query, vars) => graphql(mySchema, query, {}, {}, vars) }; } @@ -58,7 +58,7 @@ defaultMockMap.set('ID', () => uuid.v4()); // 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, @@ -447,4 +447,5 @@ class MockList { } } -export { addMockFunctionsToSchema, MockList, mockServer }; +// retain addMockFunctionsToSchema for backwards compatibility +export { addMocksToSchema, addMocksToSchema as addMockFunctionsToSchema, MockList, mockServer }; diff --git a/src/test/testFragmentsAreNotDuplicated.ts b/src/test/testFragmentsAreNotDuplicated.ts index 7b45c605dd5..c7f02b8f40f 100644 --- a/src/test/testFragmentsAreNotDuplicated.ts +++ b/src/test/testFragmentsAreNotDuplicated.ts @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {ExecutionResult, graphql} from 'graphql'; import { - addMockFunctionsToSchema, + addMocksToSchema, makeExecutableSchema, transformSchema, } from '..'; @@ -12,7 +12,7 @@ describe('Merging schemas', () => { typeDefs: rawSchema, }); - addMockFunctionsToSchema({schema: originalSchema}); + addMocksToSchema({schema: originalSchema}); const originalResult = await graphql( originalSchema, diff --git a/src/test/testMocking.ts b/src/test/testMocking.ts index 69e0c228d35..2b09990f7c5 100644 --- a/src/test/testMocking.ts +++ b/src/test/testMocking.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { graphql, GraphQLResolveInfo } from 'graphql'; -import { addMockFunctionsToSchema, MockList, mockServer } from '../mock'; +import { addMocksToSchema, MockList, mockServer } from '../mock'; import { buildSchemaFromTypeDefinitions, addResolversToSchema, @@ -84,13 +84,13 @@ describe('Mock', () => { }; it('throws an error if you forget to pass schema', () => { - expect(() => (addMockFunctionsToSchema)({})).to.throw( + expect(() => (addMocksToSchema)({})).to.throw( 'Must provide schema to mock', ); }); it('throws an error if the property "schema" on the first argument is not of type GraphQLSchema', () => { - expect(() => (addMockFunctionsToSchema)({ schema: {} })).to.throw( + expect(() => (addMocksToSchema)({ schema: {} })).to.throw( 'Value at "schema" must be of type GraphQLSchema', ); }); @@ -98,7 +98,7 @@ describe('Mock', () => { it('throws an error if second argument is not a Map', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); expect(() => - (addMockFunctionsToSchema)({ schema: jsSchema, mocks: ['a'] }), + (addMocksToSchema)({ schema: jsSchema, mocks: ['a'] }), ).to.throw('mocks must be of type Object'); }); @@ -106,14 +106,14 @@ describe('Mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { Int: 55 }; expect(() => - (addMockFunctionsToSchema)({ schema: jsSchema, mocks: mockMap }), + (addMocksToSchema)({ schema: jsSchema, mocks: mockMap }), ).to.throw('mockFunctionMap[Int] must be a function'); }); it('mocks the default types for you', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = {}; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnInt returnFloat @@ -260,7 +260,7 @@ describe('Mock', () => { }, }; addResolversToSchema(jsSchema, resolvers); - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: {}, preserveResolvers: true, @@ -287,7 +287,7 @@ describe('Mock', () => { it('can mock Enum', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = {}; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnEnum }`; @@ -307,7 +307,7 @@ describe('Mock', () => { returnBirdsAndBees: () => new MockList(40), }), }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnBirdsAndBees { ... on Bird { @@ -344,7 +344,7 @@ describe('Mock', () => { returnFlying: () => new MockList(40), }), }; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -395,7 +395,7 @@ describe('Mock', () => { return { __typename }; }, }; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -438,7 +438,7 @@ describe('Mock', () => { }; }, }; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -477,7 +477,7 @@ describe('Mock', () => { return; }, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ node(id:"bee:123456"){ id, @@ -493,7 +493,7 @@ describe('Mock', () => { it('throws an error in resolve if mock type is not defined', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = {}; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnMockError }`; @@ -518,7 +518,7 @@ describe('Mock', () => { addResolversToSchema(jsSchema, resolvers); const mockMap = {}; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -547,7 +547,7 @@ describe('Mock', () => { addResolversToSchema(jsSchema, resolvers); const mockMap = {}; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -567,7 +567,7 @@ describe('Mock', () => { it('can mock an Int', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { Int: () => 55 }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnInt }`; @@ -579,7 +579,7 @@ describe('Mock', () => { it('can mock a Float', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { Float: () => 55.5 }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnFloat }`; @@ -590,7 +590,7 @@ describe('Mock', () => { it('can mock a String', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { String: () => 'a string' }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnString }`; @@ -601,7 +601,7 @@ describe('Mock', () => { it('can mock a Boolean', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { Boolean: () => true }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnBoolean }`; @@ -612,7 +612,7 @@ describe('Mock', () => { it('can mock an ID', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { ID: () => 'ea5bdc19' }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnID }`; @@ -623,7 +623,7 @@ describe('Mock', () => { it('nullable type is nullable', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { String: (): null => null }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnNullableString }`; @@ -634,7 +634,7 @@ describe('Mock', () => { it('can mock a nonNull type', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { String: () => 'nonnull' }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnNonNullString }`; @@ -645,7 +645,7 @@ describe('Mock', () => { it('nonNull type is not nullable', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { String: (): null => null }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnNonNullString }`; @@ -660,7 +660,7 @@ describe('Mock', () => { String: () => 'abc', Int: () => 123, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnObject { returnInt, returnString } }`; @@ -675,7 +675,7 @@ describe('Mock', () => { it('can mock a list of ints', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { Int: () => 123 }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnListOfInt }`; @@ -693,7 +693,7 @@ describe('Mock', () => { String: () => 'a', Int: () => 1, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnListOfListOfObject { returnInt, returnString } }`; @@ -731,7 +731,7 @@ describe('Mock', () => { }, }; addResolversToSchema(jsSchema, resolvers); - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -760,7 +760,7 @@ describe('Mock', () => { }), Int: () => 15, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnObject{ returnInt @@ -799,7 +799,7 @@ describe('Mock', () => { returnString: 'woot!?', // b) another part of a Bird }), }; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -841,7 +841,7 @@ describe('Mock', () => { returnString: 'woot!?', // b) another part of a Bird }), }; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -883,7 +883,7 @@ describe('Mock', () => { returnString: 'woot!?', // b) another part of a Bird }), }; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -924,7 +924,7 @@ describe('Mock', () => { const mockMap = { Int: () => 123, // b) mock of Int. }; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -969,7 +969,7 @@ describe('Mock', () => { const mockMap = { Int: () => 123, // b) mock of Int. }; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -1015,7 +1015,7 @@ describe('Mock', () => { const mockMap = { Int: () => 123, // b) mock of Int. }; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -1050,7 +1050,7 @@ describe('Mock', () => { const mockMap = { Int: () => 666, // b) mock of Int. }; - addMockFunctionsToSchema({ + addMocksToSchema({ schema: jsSchema, mocks: mockMap, preserveResolvers: true, @@ -1081,7 +1081,7 @@ describe('Mock', () => { returnStringArgument: (o: any, a: { [key: string]: any }) => a['s'], }), }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnStringArgument(s: "adieu") }`; @@ -1100,7 +1100,7 @@ describe('Mock', () => { returnStringArgument: (o: any, a: { [key: string]: any }) => a['s'], }), }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `mutation { returnStringArgument(s: "adieu") }`; @@ -1118,7 +1118,7 @@ describe('Mock', () => { RootQuery: () => ({ returnListOfInt: () => new MockList(3) }), Int: () => 12, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnListOfInt }`; @@ -1136,7 +1136,7 @@ describe('Mock', () => { RootQuery: () => ({ returnListOfInt: () => new MockList([10, 20]) }), Int: () => 12, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnListOfInt }`; @@ -1155,7 +1155,7 @@ describe('Mock', () => { }), Int: () => 12, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ l3: returnListOfIntArg(l: 3) l5: returnListOfIntArg(l: 5) @@ -1174,7 +1174,7 @@ describe('Mock', () => { }), Int: () => 12, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnListOfInt }`; @@ -1200,7 +1200,7 @@ describe('Mock', () => { }), Int: () => 12, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnListOfListOfInt }`; @@ -1224,7 +1224,7 @@ describe('Mock', () => { }), Int: () => 12, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ returnListOfListOfIntArg(l: 1) }`; @@ -1291,7 +1291,7 @@ describe('Mock', () => { }), Int: () => 123, }; - addMockFunctionsToSchema({ schema: jsSchema, mocks: mockMap }); + addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `query abc{ thread(id: "67"){ id @@ -1356,7 +1356,7 @@ describe('Mock', () => { resolvers, }); - addMockFunctionsToSchema({ + addMocksToSchema({ schema, mocks: { Date: () => new Date('2016-05-04'), @@ -1438,7 +1438,7 @@ describe('Mock', () => { resolvers, }); - addMockFunctionsToSchema({ + addMocksToSchema({ schema, preserveResolvers: true, }); diff --git a/src/test/testStitchingFromSubschemas.ts b/src/test/testStitchingFromSubschemas.ts index 21abcbbdcc4..62a994f6171 100644 --- a/src/test/testStitchingFromSubschemas.ts +++ b/src/test/testStitchingFromSubschemas.ts @@ -18,7 +18,7 @@ import { graphql } from 'graphql'; import { delegateToSchema, mergeSchemas, - addMockFunctionsToSchema, + addMocksToSchema, } from '../index'; const chirpTypeDefs = ` @@ -70,7 +70,7 @@ const chirpSchema = mergeSchemas({ } }); -addMockFunctionsToSchema({ +addMocksToSchema({ schema: chirpSchema, mocks: { Chirp: () => ({ @@ -108,7 +108,7 @@ const authorSchema = mergeSchemas({ } }); -addMockFunctionsToSchema({ +addMocksToSchema({ schema: authorSchema, mocks: { User: () => ({ diff --git a/src/test/testTypeMerging.ts b/src/test/testTypeMerging.ts index cd764b34df6..954c88a49f5 100644 --- a/src/test/testTypeMerging.ts +++ b/src/test/testTypeMerging.ts @@ -8,7 +8,7 @@ import { graphql } from 'graphql'; import { delegateToSchema, mergeSchemas, - addMockFunctionsToSchema, + addMocksToSchema, makeExecutableSchema, } from '../index'; @@ -30,7 +30,7 @@ const chirpSchema = makeExecutableSchema({ `, }); -addMockFunctionsToSchema({ schema: chirpSchema }); +addMocksToSchema({ schema: chirpSchema }); const authorSchema = makeExecutableSchema({ typeDefs: ` @@ -44,7 +44,7 @@ const authorSchema = makeExecutableSchema({ `, }); -addMockFunctionsToSchema({ schema: authorSchema }); +addMocksToSchema({ schema: authorSchema }); const mergedSchema = mergeSchemas({ subschemas: [{ From 33647bf7a09175500458ba46501e0700d488d564 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 29 Jan 2020 21:40:47 -0500 Subject: [PATCH 174/250] refactor(delegationTransforms) separate out function, some of these can be precompiled. --- src/stitching/delegateToSchema.ts | 78 ++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 146e69a0867..0df210bb05b 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -4,6 +4,7 @@ import { validate, GraphQLSchema, ExecutionResult, + GraphQLOutputType, } from 'graphql'; import { @@ -13,6 +14,7 @@ import { Delegator, SubschemaConfig, isSubschemaConfig, + IGraphQLToolsResolveInfo, } from '../Interfaces'; import { @@ -79,34 +81,17 @@ export default function delegateToSchema( }); } -export function delegateRequest({ - request, - schema: subschema, - rootValue, - info, - operation = getDelegatingOperation(info.parentType, info.schema), - fieldName = info.fieldName, - returnType = info.returnType, - context, - transforms = [], - skipValidation, - skipTypeMerging, -}: IDelegateRequestOptions): any { - let targetSchema: GraphQLSchema; - let subschemaConfig: SubschemaConfig; - - if (isSubschemaConfig(subschema)) { - subschemaConfig = subschema; - targetSchema = subschemaConfig.schema; - rootValue = rootValue || subschemaConfig.rootValue || info.rootValue; - transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse()); - } else { - targetSchema = subschema; - rootValue = rootValue || info.rootValue; - } - +function buildDelegationTransforms( + subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, + info: IGraphQLToolsResolveInfo, + targetSchema: GraphQLSchema, + fieldName: string, + returnType: GraphQLOutputType, + transforms: Array, + skipTypeMerging: boolean, +): Array { let delegationTransforms: Array = [ - new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType, skipTypeMerging), + new CheckResultAndHandleErrors(info, fieldName, subschemaOrSubschemaConfig, context, returnType, skipTypeMerging), ]; if (info.mergeInfo) { @@ -133,6 +118,45 @@ export function delegateRequest({ new AddTypenameToAbstract(targetSchema), ); + return delegationTransforms; +} + +export function delegateRequest({ + request, + schema: subschemaOrSubschemaConfig, + rootValue, + info, + operation = getDelegatingOperation(info.parentType, info.schema), + fieldName = info.fieldName, + returnType = info.returnType, + context, + transforms = [], + skipValidation, + skipTypeMerging, +}: IDelegateRequestOptions): any { + let targetSchema: GraphQLSchema; + let subschemaConfig: SubschemaConfig; + + if (isSubschemaConfig(subschemaOrSubschemaConfig)) { + subschemaConfig = subschemaOrSubschemaConfig; + targetSchema = subschemaConfig.schema; + rootValue = rootValue || subschemaConfig.rootValue || info.rootValue; + transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse()); + } else { + targetSchema = subschemaOrSubschemaConfig; + rootValue = rootValue || info.rootValue; + } + + const delegationTransforms = buildDelegationTransforms( + subschemaOrSubschemaConfig, + info, + targetSchema, + fieldName, + returnType, + transforms, + skipTypeMerging, + ); + request = applyRequestTransforms(request, delegationTransforms); if (!skipValidation) { From ce834ff42168f026192c9b8eb6f85966784b20ec Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 29 Jan 2020 21:59:49 -0500 Subject: [PATCH 175/250] docs(transforms): add info about gateway and delegation transforms --- docs/source/schema-transforms.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/source/schema-transforms.md b/docs/source/schema-transforms.md index 0c47ce6c83f..078ec4a2a54 100644 --- a/docs/source/schema-transforms.md +++ b/docs/source/schema-transforms.md @@ -7,7 +7,7 @@ Schema transforms are a tool for making modified copies of `GraphQLSchema` objec 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. -Schema transforms work by wrapping the original schema in a new outer schema that simply delegates all operations to the original inner schema. Each schema transform includes a function that changes the outer wrapping 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. +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 = { @@ -42,7 +42,7 @@ type Query { } ``` -On delegation to the inner, original schema, 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: @@ -104,11 +104,11 @@ Given a `GraphQLSchema` and an array of `Transform` objects, produce a new schem 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 outer schema. +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 original schema from an outer schema when [schema stitching](/schema-stitching/), but has been deprecated in favor of specifying subschema ids. See the [schema stitching](/schema-stitching/) docs for further details. +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 @@ -295,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 (delegation) transforms -## delegateToSchema transforms +The following transforms are automatically applied by `delegateToSchema` during schema delegation, to translate between source and target types and fields: -The following transforms are automatically applied by `delegateToSchema` during schema delegation, to translate between new and old types and fields: - -* `ExpandAbstractTypes`: If an abstract type within a document does not exist in the inner schema, expand the type to each and any of its implementations that do exist in the inner schema. -* `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, necessary for type resolution of interfaces within the outer schema to work. +* `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 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. From 9e3048c88f483df5b1637fa252a2f99bc726a255 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 29 Jan 2020 22:32:19 -0500 Subject: [PATCH 176/250] feat(merging): set up default mergeTypeResolver mergeType resolver functions can also simply be named resolve. --- src/Interfaces.ts | 6 ++++-- src/stitching/mergeFields.ts | 2 +- src/stitching/mergeSchemas.ts | 20 +++++++++++++++--- src/test/testAlternateMergeSchemas.ts | 8 ++++---- src/test/testTypeMerging.ts | 29 ++++++--------------------- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 1964395d11e..231c087652c 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -98,12 +98,14 @@ export type SubschemaConfig = { fetcher?: Fetcher; dispatcher?: Dispatcher; transforms?: Array; - mergedTypeConfigs?: Record; + merge?: Record; }; export type MergedTypeConfig = { selectionSet?: string; - merge: MergedTypeResolver; + fieldName?: string; + args?: (originalResult: any) => Record; + resolve?: MergedTypeResolver; }; export type MergedTypeResolver = ( diff --git a/src/stitching/mergeFields.ts b/src/stitching/mergeFields.ts index 3a3d1137df2..e155963f993 100644 --- a/src/stitching/mergeFields.ts +++ b/src/stitching/mergeFields.ts @@ -120,7 +120,7 @@ export function mergeFields( const maybePromises: Promise | any = []; delegationMap.forEach((selections: Array, s: SubschemaConfig) => { - const maybePromise = s.mergedTypeConfigs[typeName].merge( + const maybePromise = s.merge[typeName].resolve( object, context, info, diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index d5032d561ec..87d7a732a1b 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -286,8 +286,8 @@ function createMergeInfo( .filter(typeCandidate => typeCandidate.subschema && isSubschemaConfig(typeCandidate.subschema) && - typeCandidate.subschema.mergedTypeConfigs && - typeCandidate.subschema.mergedTypeConfigs[typeName] + typeCandidate.subschema.merge && + typeCandidate.subschema.merge[typeName] ); if (mergedTypeCandidates.length) { @@ -308,13 +308,27 @@ function createMergeInfo( fields[fieldName].push(subschemaConfig); }); - const mergedTypeConfig = subschemaConfig.mergedTypeConfigs[typeName]; + const mergedTypeConfig = subschemaConfig.merge[typeName]; + if (mergedTypeConfig.selectionSet) { const selectionSet = parseSelectionSet(mergedTypeConfig.selectionSet); requiredSelections = requiredSelections.concat(selectionSet.selections); selectionSets.set(subschemaConfig, selectionSet); } + if (!mergedTypeConfig.resolve) { + mergedTypeConfig.resolve = (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ + schema: subschema, + operation: 'query', + fieldName: mergedTypeConfig.fieldName, + args: mergedTypeConfig.args(originalResult), + selectionSet, + context, + info, + skipTypeMerging: true, + }); + } + subschemas.push(subschemaConfig); }); diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 177a7091566..cc5051efcb7 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -1750,10 +1750,10 @@ describe('mergeTypes', () => { it('can merge types', async () => { const subschemaConfig1: SubschemaConfig = { schema: schema1, - mergedTypeConfigs: { + merge: { Test: { selectionSet: '{ id }', - merge: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ + resolve: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ schema: subschema, operation: 'query', fieldName: 'getTest', @@ -1769,10 +1769,10 @@ describe('mergeTypes', () => { const subschemaConfig2: SubschemaConfig = { schema: schema2, - mergedTypeConfigs: { + merge: { Test: { selectionSet: '{ id }', - merge: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ + resolve: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ schema: subschema, operation: 'query', fieldName: 'getTest', diff --git a/src/test/testTypeMerging.ts b/src/test/testTypeMerging.ts index 954c88a49f5..05d160b1186 100644 --- a/src/test/testTypeMerging.ts +++ b/src/test/testTypeMerging.ts @@ -6,7 +6,6 @@ import { expect } from 'chai'; import { graphql } from 'graphql'; import { - delegateToSchema, mergeSchemas, addMocksToSchema, makeExecutableSchema, @@ -49,36 +48,20 @@ addMocksToSchema({ schema: authorSchema }); const mergedSchema = mergeSchemas({ subschemas: [{ schema: chirpSchema, - mergedTypeConfigs: { + merge: { User: { + fieldName: 'userById', + args: originalResult => ({ id: originalResult.id }), selectionSet: '{ id }', - merge: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ - schema: subschema, - operation: 'query', - fieldName: 'userById', - args: { id: originalResult.id }, - selectionSet, - context, - info, - skipTypeMerging: true, - }), } }, }, { schema: authorSchema, - mergedTypeConfigs: { + merge: { User: { + fieldName: 'userById', + args: originalResult => ({ id: originalResult.id }), selectionSet: '{ id }', - merge: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ - schema: subschema, - operation: 'query', - fieldName: 'userById', - args: { id: originalResult.id }, - selectionSet, - context, - info, - skipTypeMerging: true, - }), } }, }], From 6a58a18a3026b5031abd1241705932ac872044f8 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 1 Feb 2020 18:20:13 -0500 Subject: [PATCH 177/250] feat(merging): merge additional types besides GraphQLObjectTypes --- src/stitching/mergeInfo.ts | 285 +++++++++++++++++++++++++++++ src/stitching/mergeSchemas.ts | 325 +++++++--------------------------- src/test/testTypeMerging.ts | 1 + 3 files changed, 351 insertions(+), 260 deletions(-) create mode 100644 src/stitching/mergeInfo.ts diff --git a/src/stitching/mergeInfo.ts b/src/stitching/mergeInfo.ts new file mode 100644 index 00000000000..8a0cd84cb8d --- /dev/null +++ b/src/stitching/mergeInfo.ts @@ -0,0 +1,285 @@ +import { + GraphQLNamedType, + GraphQLObjectType, + GraphQLScalarType, + GraphQLSchema, + Kind, + SelectionNode, + SelectionSetNode, +} from 'graphql'; +import { + IDelegateToSchemaOptions, + MergeInfo, + IResolversParameter, + isSubschemaConfig, + SubschemaConfig, + IGraphQLToolsResolveInfo, + MergedTypeInfo, +} from '../Interfaces'; +import delegateToSchema from './delegateToSchema'; +import { + Transform, + ExpandAbstractTypes, + AddReplacementFragments, +} from '../transforms'; +import { + parseFragmentToInlineFragment, + concatInlineFragments, + typeContainsSelectionSet, + parseSelectionSet, +} from '../utils'; +import { TypeMap } from 'graphql/type/schema'; + +type MergeTypeCandidate = { + type: GraphQLNamedType; + schema?: GraphQLSchema; + subschema?: GraphQLSchema | SubschemaConfig; + transformedSubschema?: GraphQLSchema; +}; + +export function createMergeInfo( + allSchemas: Array, + typeCandidates: { [name: string]: Array }, + mergeTypes?: boolean | Array | + ((typeName: string, mergeTypeCandidates: Array) => boolean), +): MergeInfo { + return { + delegate( + operation: 'query' | 'mutation' | 'subscription', + fieldName: string, + args: { [key: string]: any }, + context: { [key: string]: any }, + info: IGraphQLToolsResolveInfo, + transforms?: Array, + ) { + console.warn( + '`mergeInfo.delegate` is deprecated. ' + + 'Use `mergeInfo.delegateToSchema and pass explicit schema instances.', + ); + const schema = guessSchemaByRootField(allSchemas, operation, fieldName); + const expandTransforms = new ExpandAbstractTypes(info.schema, schema); + const fragmentTransform = new AddReplacementFragments(schema, info.mergeInfo.replacementFragments); + return delegateToSchema({ + schema, + operation, + fieldName, + args, + context, + info, + transforms: [ + ...(transforms || []), + expandTransforms, + fragmentTransform, + ], + }); + }, + + delegateToSchema(options: IDelegateToSchemaOptions) { + return delegateToSchema({ + ...options, + transforms: options.transforms + }); + }, + fragments: [], + replacementSelectionSets: undefined, + replacementFragments: undefined, + mergedTypes: createMergedTypes(typeCandidates, mergeTypes), + }; +} + +function createMergedTypes( + typeCandidates: { [name: string]: Array }, + mergeTypes?: boolean | Array | + ((typeName: string, mergeTypeCandidates: Array) => boolean) +): Record { + const mergedTypes: Record = {}; + + Object.keys(typeCandidates).forEach(typeName => { + if (typeCandidates[typeName][0].type instanceof GraphQLObjectType) { + const mergedTypeCandidates = typeCandidates[typeName] + .filter(typeCandidate => + typeCandidate.subschema && + isSubschemaConfig(typeCandidate.subschema) && + typeCandidate.subschema.merge && + typeCandidate.subschema.merge[typeName] + ); + + if ( + mergeTypes === true || + (typeof mergeTypes === 'function') && mergeTypes(typeName, typeCandidates[typeName]) || + Array.isArray(mergeTypes) && mergeTypes.includes(typeName) || + mergedTypeCandidates.length + ) { + const subschemas: Array = []; + + let requiredSelections: Array = [parseSelectionSet(`{ __typename }`).selections[0]]; + const fields = Object.create({}); + const typeMaps: Map = new Map(); + const selectionSets: Map = new Map(); + + mergedTypeCandidates.forEach(typeCandidate => { + const subschemaConfig = typeCandidate.subschema as SubschemaConfig; + const transformedSubschema = typeCandidate.transformedSubschema; + typeMaps.set(subschemaConfig, transformedSubschema.getTypeMap()); + const type = transformedSubschema.getType(typeName) as GraphQLObjectType; + const fieldMap = type.getFields(); + Object.keys(fieldMap).forEach(fieldName => { + fields[fieldName] = fields[fieldName] || []; + fields[fieldName].push(subschemaConfig); + }); + + const mergedTypeConfig = subschemaConfig.merge[typeName]; + + if (mergedTypeConfig.selectionSet) { + const selectionSet = parseSelectionSet(mergedTypeConfig.selectionSet); + requiredSelections = requiredSelections.concat(selectionSet.selections); + selectionSets.set(subschemaConfig, selectionSet); + } + + if (!mergedTypeConfig.resolve) { + mergedTypeConfig.resolve = (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ + schema: subschema, + operation: 'query', + fieldName: mergedTypeConfig.fieldName, + args: mergedTypeConfig.args(originalResult), + selectionSet, + context, + info, + skipTypeMerging: true, + }); + } + + subschemas.push(subschemaConfig); + }); + + mergedTypes[typeName] = { + subschemas, + typeMaps, + selectionSets, + containsSelectionSet: new Map(), + uniqueFields: Object.create({}), + nonUniqueFields: Object.create({}), + }; + + subschemas.forEach(subschema => { + const type = typeMaps.get(subschema)[typeName] as GraphQLObjectType; + let subschemaMap = new Map(); + subschemas.filter(s => s !== subschema).forEach(s => { + const selectionSet = selectionSets.get(s); + if (selectionSet && typeContainsSelectionSet(type, selectionSet)) { + subschemaMap.set(selectionSet, true); + } + }); + mergedTypes[typeName].containsSelectionSet.set(subschema, subschemaMap); + }); + + Object.keys(fields).forEach(fieldName => { + const supportedBySubschemas = fields[fieldName]; + if (supportedBySubschemas.length === 1) { + mergedTypes[typeName].uniqueFields[fieldName] = supportedBySubschemas[0]; + } else { + mergedTypes[typeName].nonUniqueFields[fieldName] = supportedBySubschemas; + } + }); + + mergedTypes[typeName].selectionSet = { + kind: Kind.SELECTION_SET, + selections: requiredSelections, + }; + } + + } + }); + + return mergedTypes; +} + +export function completeMergeInfo( + mergeInfo: MergeInfo, + resolvers: IResolversParameter, +): MergeInfo { + const replacementSelectionSets = Object.create(null); + + Object.keys(resolvers).forEach(typeName => { + const type = resolvers[typeName]; + if (type instanceof GraphQLScalarType) { + return; + } + Object.keys(type).forEach(fieldName => { + const field = type[fieldName]; + if (field.selectionSet) { + const selectionSet = parseSelectionSet(field.selectionSet); + replacementSelectionSets[typeName] = replacementSelectionSets[typeName] || {}; + replacementSelectionSets[typeName][fieldName] = replacementSelectionSets[typeName][fieldName] || { + kind: Kind.SELECTION_SET, + selections: [], + }; + replacementSelectionSets[typeName][fieldName].selections = + replacementSelectionSets[typeName][fieldName].selections.concat(selectionSet.selections); + } + if (field.fragment) { + mergeInfo.fragments.push({ + field: fieldName, + fragment: field.fragment, + }); + } + }); + }); + + const mapping = {}; + mergeInfo.fragments.forEach(({ field, fragment }) => { + const parsedFragment = parseFragmentToInlineFragment(fragment); + const actualTypeName = parsedFragment.typeCondition.name.value; + mapping[actualTypeName] = mapping[actualTypeName] || {}; + mapping[actualTypeName][field] = mapping[actualTypeName][field] || []; + mapping[actualTypeName][field].push(parsedFragment); + }); + + const replacementFragments = Object.create(null); + Object.keys(mapping).forEach(typeName => { + Object.keys(mapping[typeName]).forEach(field => { + replacementFragments[typeName] = mapping[typeName] || {}; + replacementFragments[typeName][field] = concatInlineFragments( + typeName, + mapping[typeName][field], + ); + }); + }); + + mergeInfo.replacementSelectionSets = replacementSelectionSets; + mergeInfo.replacementFragments = replacementFragments; + + return mergeInfo; +} + +function operationToRootType( + operation: 'query' | 'mutation' | 'subscription', + schema: GraphQLSchema, +): GraphQLObjectType { + if (operation === 'subscription') { + return schema.getSubscriptionType(); + } else if (operation === 'mutation') { + return schema.getMutationType(); + } else { + return schema.getQueryType(); + } +} + +function guessSchemaByRootField( + schemas: Array, + operation: 'query' | 'mutation' | 'subscription', + fieldName: string, +): GraphQLSchema { + for (const schema of schemas) { + let rootObject = operationToRootType(operation, schema); + if (rootObject) { + const fields = rootObject.getFields(); + if (fields[fieldName]) { + return schema; + } + } + } + throw new Error( + `Could not find subschema with field \`${operation}.${fieldName}\``, + ); +} diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 87d7a732a1b..d3f18552f83 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -10,32 +10,24 @@ import { parse, Kind, GraphQLDirective, - SelectionNode, - SelectionSetNode, + GraphQLInterfaceType, + GraphQLUnionType, + GraphQLEnumType, } from 'graphql'; import { - IDelegateToSchemaOptions, - MergeInfo, OnTypeConflict, IResolversParameter, isSubschemaConfig, SchemaLikeObject, IResolvers, SubschemaConfig, - IGraphQLToolsResolveInfo, } from '../Interfaces'; import { extractExtensionDefinitions, addResolversToSchema, } from '../makeExecutableSchema'; -import delegateToSchema from './delegateToSchema'; import typeFromAST from './typeFromAST'; -import { - Transform, - ExpandAbstractTypes, - wrapSchema, - AddReplacementFragments, -} from '../transforms'; +import { wrapSchema } from '../transforms'; import { SchemaDirectiveVisitor, cloneDirective, @@ -43,12 +35,8 @@ import { healTypes, forEachField, mergeDeep, - parseFragmentToInlineFragment, - concatInlineFragments, - typeContainsSelectionSet, - parseSelectionSet, } from '../utils'; -import { TypeMap } from 'graphql/type/schema'; +import { createMergeInfo, completeMergeInfo } from './mergeInfo'; type MergeTypeCandidate = { type: GraphQLNamedType; @@ -70,6 +58,7 @@ export default function mergeSchemas({ resolvers = {}, schemaDirectives, inheritResolversFromInterfaces, + mergeTypes = false, mergeDirectives, }: { subschemas?: Array; @@ -80,7 +69,9 @@ export default function mergeSchemas({ resolvers?: IResolversParameter; schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor }; inheritResolversFromInterfaces?: boolean; - mergeDirectives?: boolean, + mergeTypes?: boolean | Array | + ((typeName: string, mergeTypeCandidates: Array) => boolean); + mergeDirectives?: boolean; }): GraphQLSchema { const allSchemas: Array = []; const typeCandidates: { [name: string]: Array } = {}; @@ -179,7 +170,7 @@ export default function mergeSchemas({ } }); - let mergeInfo = createMergeInfo(allSchemas, typeCandidates); + let mergeInfo = createMergeInfo(allSchemas, typeCandidates, mergeTypes); if (typeof resolvers === 'function') { console.warn( @@ -207,12 +198,17 @@ export default function mergeSchemas({ Object.keys(typeCandidates).forEach(typeName => { if ( - typeName === 'Query' || - typeName === 'Mutation' || - typeName === 'Subscription' || - mergeInfo.mergedTypes[typeName] + ( + typeName === 'Query' || + typeName === 'Mutation' || + typeName === 'Subscription' || + (mergeTypes === true && !(typeCandidates[typeName][0].type instanceof GraphQLScalarType)) || + (typeof mergeTypes === 'function') && mergeTypes(typeName, typeCandidates[typeName]) || + Array.isArray(mergeTypes) && mergeTypes.includes(typeName) || + mergeInfo.mergedTypes[typeName] + ) ) { - typeMap[typeName] = mergeFields(typeName, typeCandidates[typeName]); + typeMap[typeName] = merge(typeName, typeCandidates[typeName]); } else { const candidateSelector = onTypeConflict ? onTypeConflictToCandidateSelector(onTypeConflict) : @@ -274,223 +270,6 @@ export default function mergeSchemas({ return mergedSchema; } -function createMergeInfo( - allSchemas: Array, - typeCandidates: { [name: string]: Array }, -): MergeInfo { - const mergedTypes = {}; - - Object.keys(typeCandidates).forEach(typeName => { - const mergedTypeCandidates = - typeCandidates[typeName] - .filter(typeCandidate => - typeCandidate.subschema && - isSubschemaConfig(typeCandidate.subschema) && - typeCandidate.subschema.merge && - typeCandidate.subschema.merge[typeName] - ); - - if (mergedTypeCandidates.length) { - const subschemas: Array = []; - let requiredSelections: Array = []; - const fields = Object.create({}); - const typeMaps: Map = new Map(); - const selectionSets: Map = new Map(); - - mergedTypeCandidates.forEach(typeCandidate => { - const subschemaConfig = typeCandidate.subschema as SubschemaConfig; - const transformedSubschema = typeCandidate.transformedSubschema; - typeMaps.set(subschemaConfig, transformedSubschema.getTypeMap()); - const type = transformedSubschema.getType(typeName) as GraphQLObjectType; - const fieldMap = type.getFields(); - Object.keys(fieldMap).forEach(fieldName => { - fields[fieldName] = fields[fieldName] || []; - fields[fieldName].push(subschemaConfig); - }); - - const mergedTypeConfig = subschemaConfig.merge[typeName]; - - if (mergedTypeConfig.selectionSet) { - const selectionSet = parseSelectionSet(mergedTypeConfig.selectionSet); - requiredSelections = requiredSelections.concat(selectionSet.selections); - selectionSets.set(subschemaConfig, selectionSet); - } - - if (!mergedTypeConfig.resolve) { - mergedTypeConfig.resolve = (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ - schema: subschema, - operation: 'query', - fieldName: mergedTypeConfig.fieldName, - args: mergedTypeConfig.args(originalResult), - selectionSet, - context, - info, - skipTypeMerging: true, - }); - } - - subschemas.push(subschemaConfig); - }); - - mergedTypes[typeName] = { - subschemas, - typeMaps, - selectionSets, - containsSelectionSet: new Map(), - uniqueFields: Object.create({}), - nonUniqueFields: Object.create({}), - }; - - subschemas.forEach(subschema => { - const type = typeMaps.get(subschema)[typeName] as GraphQLObjectType; - let subschemaMap = new Map(); - subschemas.filter(s => s !== subschema).forEach(s => { - const selectionSet = selectionSets.get(s); - if (selectionSet && typeContainsSelectionSet(type, selectionSet)) { - subschemaMap.set(selectionSet, true); - } - }); - mergedTypes[typeName].containsSelectionSet.set(subschema, subschemaMap); - }); - - Object.keys(fields).forEach(fieldName => { - const supportedBySubschemas = fields[fieldName]; - if (supportedBySubschemas.length === 1) { - mergedTypes[typeName].uniqueFields[fieldName] = supportedBySubschemas[0]; - } else { - mergedTypes[typeName].nonUniqueFields[fieldName] = supportedBySubschemas; - } - }); - - requiredSelections.push(parseSelectionSet(`{ __typename }`).selections[0]); - mergedTypes[typeName].selectionSet = { - kind: Kind.SELECTION_SET, - selections: requiredSelections, - }; - } - }); - - return { - delegate( - operation: 'query' | 'mutation' | 'subscription', - fieldName: string, - args: { [key: string]: any }, - context: { [key: string]: any }, - info: IGraphQLToolsResolveInfo, - transforms?: Array, - ) { - console.warn( - '`mergeInfo.delegate` is deprecated. ' + - 'Use `mergeInfo.delegateToSchema and pass explicit schema instances.', - ); - const schema = guessSchemaByRootField(allSchemas, operation, fieldName); - const expandTransforms = new ExpandAbstractTypes(info.schema, schema); - const fragmentTransform = new AddReplacementFragments(schema, info.mergeInfo.replacementFragments); - return delegateToSchema({ - schema, - operation, - fieldName, - args, - context, - info, - transforms: [ - ...(transforms || []), - expandTransforms, - fragmentTransform, - ], - }); - }, - - delegateToSchema(options: IDelegateToSchemaOptions) { - return delegateToSchema({ - ...options, - transforms: options.transforms - }); - }, - fragments: [], - replacementSelectionSets: undefined, - replacementFragments: undefined, - mergedTypes, - }; -} - -function completeMergeInfo( - mergeInfo: MergeInfo, - resolvers: IResolversParameter, -): MergeInfo { - const replacementSelectionSets = Object.create(null); - - Object.keys(resolvers).forEach(typeName => { - const type = resolvers[typeName]; - if (type instanceof GraphQLScalarType) { - return; - } - Object.keys(type).forEach(fieldName => { - const field = type[fieldName]; - if (field.selectionSet) { - const selectionSet = parseSelectionSet(field.selectionSet); - replacementSelectionSets[typeName] = replacementSelectionSets[typeName] || {}; - replacementSelectionSets[typeName][fieldName] = replacementSelectionSets[typeName][fieldName] || { - kind: Kind.SELECTION_SET, - selections: [], - }; - replacementSelectionSets[typeName][fieldName].selections = - replacementSelectionSets[typeName][fieldName].selections.concat(selectionSet.selections); - } - if (field.fragment) { - mergeInfo.fragments.push({ - field: fieldName, - fragment: field.fragment, - }); - } - }); - }); - - const mapping = {}; - mergeInfo.fragments.forEach(({ field, fragment }) => { - const parsedFragment = parseFragmentToInlineFragment(fragment); - const actualTypeName = parsedFragment.typeCondition.name.value; - mapping[actualTypeName] = mapping[actualTypeName] || {}; - mapping[actualTypeName][field] = mapping[actualTypeName][field] || []; - mapping[actualTypeName][field].push(parsedFragment); - }); - - const replacementFragments = Object.create(null); - Object.keys(mapping).forEach(typeName => { - Object.keys(mapping[typeName]).forEach(field => { - replacementFragments[typeName] = mapping[typeName] || {}; - replacementFragments[typeName][field] = concatInlineFragments( - typeName, - mapping[typeName][field], - ); - }); - }); - - mergeInfo.replacementSelectionSets = replacementSelectionSets; - mergeInfo.replacementFragments = replacementFragments; - - return mergeInfo; -} - -function guessSchemaByRootField( - schemas: Array, - operation: 'query' | 'mutation' | 'subscription', - fieldName: string, -): GraphQLSchema { - for (const schema of schemas) { - let rootObject = operationToRootType(operation, schema); - if (rootObject) { - const fields = rootObject.getFields(); - if (fields[fieldName]) { - return schema; - } - } - } - throw new Error( - `Could not find subschema with field \`${operation}.${fieldName}\``, - ); -} - function addTypeCandidate( typeCandidates: { [name: string]: Array }, name: string, @@ -526,25 +305,51 @@ function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): Cand }); } -function operationToRootType( - operation: 'query' | 'mutation' | 'subscription', - schema: GraphQLSchema, -): GraphQLObjectType { - if (operation === 'subscription') { - return schema.getSubscriptionType(); - } else if (operation === 'mutation') { - return schema.getMutationType(); +function merge(typeName: string, candidates: Array): GraphQLNamedType { + const initialCandidateType = candidates[0].type; + if (candidates.some(candidate => candidate.type.constructor !== initialCandidateType.constructor)) { + throw new Error(`Cannot merge different type categories into common type ${typeName}.`); + } + if (initialCandidateType instanceof GraphQLObjectType) { + return new GraphQLObjectType({ + name: typeName, + fields: candidates.reduce((acc, candidate) => ({ + ...acc, + ...(candidate.type as GraphQLObjectType).toConfig().fields, + }), {}), + interfaces: candidates.reduce((acc, candidate) => { + const interfaces = (candidate.type as GraphQLObjectType).toConfig().interfaces; + return interfaces ? acc.concat(interfaces) : acc; + }, []), + }); + } else if (initialCandidateType instanceof GraphQLInterfaceType) { + return new GraphQLInterfaceType({ + name: typeName, + fields: candidates.reduce((acc, candidate) => ({ + ...acc, + ...(candidate.type as GraphQLObjectType).toConfig().fields, + }), {}), + }); + } else if (initialCandidateType instanceof GraphQLUnionType) { + return new GraphQLUnionType({ + name: typeName, + types: candidates.reduce( + (acc, candidate) => (candidate.type as GraphQLUnionType).toConfig().types, + [], + ), + }); + } else if (initialCandidateType instanceof GraphQLEnumType) { + return new GraphQLEnumType({ + name: typeName, + values: candidates.reduce((acc, candidate) => ({ + ...acc, + ...(candidate.type as GraphQLEnumType).toConfig().values, + }), {}), + }); + } else if (initialCandidateType instanceof GraphQLScalarType) { + throw new Error(`Cannot merge type ${typeName}. Merging not supported for GraphQLScalarType.`); } else { - return schema.getQueryType(); + // not reachable. + throw new Error(`Type ${typeName} has unknown GraphQL type.`); } } - -function mergeFields(typeName: string, candidates: Array): GraphQLNamedType { - return new GraphQLObjectType({ - name: typeName, - fields: candidates.reduce((acc, candidate) => ({ - ...acc, - ...(candidate.type as GraphQLObjectType).toConfig().fields, - }), {}), - }); -} diff --git a/src/test/testTypeMerging.ts b/src/test/testTypeMerging.ts index 05d160b1186..70e16cc509d 100644 --- a/src/test/testTypeMerging.ts +++ b/src/test/testTypeMerging.ts @@ -65,6 +65,7 @@ const mergedSchema = mergeSchemas({ } }, }], + mergeTypes: true, }); describe('merging using type merging', () => { From 89fa13f45c4170e63dbaabce6d4cd65825915d31 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 3 Feb 2020 21:56:53 -0500 Subject: [PATCH 178/250] fix(stitching): pass context Closes #30. Again. Not sure if eslint will work better than tslint in terms of this. --- src/stitching/delegateToSchema.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 0df210bb05b..7870909efc3 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -84,6 +84,7 @@ export default function delegateToSchema( function buildDelegationTransforms( subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, info: IGraphQLToolsResolveInfo, + context: Record, targetSchema: GraphQLSchema, fieldName: string, returnType: GraphQLOutputType, @@ -150,6 +151,7 @@ export function delegateRequest({ const delegationTransforms = buildDelegationTransforms( subschemaOrSubschemaConfig, info, + context, targetSchema, fieldName, returnType, From 1c0a27db7c8a724551956a69c81d5e82f8a69a78 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 6 Feb 2020 14:32:01 -0500 Subject: [PATCH 179/250] fix(generation): allow modification of default scalar types Add back support, although still not recommended. --- src/generate/addResolversToSchema.ts | 47 +++++++++++++------- src/test/testSchemaGenerator.ts | 66 ++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index 9dd5e234271..321a0b8a9fb 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -5,6 +5,7 @@ import { GraphQLSchema, GraphQLObjectType, GraphQLInterfaceType, + isSpecifiedScalarType, } from 'graphql'; import { @@ -83,23 +84,35 @@ function addResolversToSchema( } if (type instanceof GraphQLScalarType) { - const config = type.toConfig(); - - Object.keys(resolverValue).forEach(fieldName => { - // Below is necessary as legacy code for scalar type specification allowed - // hardcoding within the resolver an object with fields '__serialize', - // '__parse', and '__parseLiteral', see examples in testMocking.ts. - // Luckily, the fields on GraphQLScalarType and GraphQLScalarTypeConfig - // are named the same. - if (fieldName.startsWith('__')) { - config[fieldName.substring(2)] = resolverValue[fieldName]; - } else { - config[fieldName] = resolverValue[fieldName]; - } - }); + if (isSpecifiedScalarType(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 { + // Otherwise the existing schema types are not changed, just replaced. + const config = type.toConfig(); + + Object.keys(resolverValue).forEach(fieldName => { + // Below is necessary as legacy code for scalar type specification allowed + // hardcoding within the resolver an object with fields '__serialize', + // '__parse', and '__parseLiteral', see examples in testMocking.ts. + // Luckily, the fields on GraphQLScalarType and GraphQLScalarTypeConfig + // are named the same. + if (fieldName.startsWith('__')) { + config[fieldName.substring(2)] = resolverValue[fieldName]; + } else { + config[fieldName] = resolverValue[fieldName]; + } + }); - // healSchema called later to update all fields to new type - typeMap[type.name] = new GraphQLScalarType(config); + // healSchema called later to update all fields to new type + typeMap[typeName] = new GraphQLScalarType(config); + } } else if (type instanceof GraphQLEnumType) { // We've encountered an enum resolver that is being used to provide an // internal enum value. @@ -134,7 +147,7 @@ function addResolversToSchema( }); // healSchema called later to update all fields to new type - typeMap[type.name] = new GraphQLEnumType({ + typeMap[typeName] = new GraphQLEnumType({ ...config, values: newValues, }); diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 8dc68076a39..ad4c351babd 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -15,6 +15,8 @@ import { execute, VariableDefinitionNode, DocumentNode, + GraphQLBoolean, + graphqlSync, } from 'graphql'; // import { printSchema } from 'graphql'; const { GraphQLJSON } = require('graphql-type-json'); @@ -754,6 +756,70 @@ describe('generating schema from shorthand', () => { expect(jsSchema.getType('JSON')['description']).to.have.length.above(0); }); + it('supports passing a default scalar type', () => { + const shorthand = ` + type Foo { + aField: Boolean + } + + type Query { + foo: Foo + } + `; + const resolveFunctions = { + Boolean: GraphQLBoolean, + }; + const jsSchema = makeExecutableSchema({ + typeDefs: shorthand, + resolvers: resolveFunctions, + }); + expect(jsSchema.getQueryType().name).to.equal('Query'); + expect(jsSchema.getType('Boolean')).to.equal(GraphQLBoolean); + }); + + it('allow overriding default scalar type fields', () => { + const originalSerialize = GraphQLBoolean.serialize; + const shorthand = ` + type Foo { + aField: Boolean + } + + type Query { + foo: Foo + } + `; + const resolveFunctions = { + Boolean: new GraphQLScalarType({ + name: 'Boolean', + serialize: () => false, + }), + Query: { + foo: () => ({ aField: true }), + } + }; + const jsSchema = makeExecutableSchema({ + typeDefs: shorthand, + resolvers: resolveFunctions, + }); + const testQuery = ` + { + foo { + aField + } + } + `; + const result = graphqlSync(jsSchema, testQuery); + expect(result.data.foo.aField).to.equal(false); + addResolversToSchema({ + schema: jsSchema, + resolvers: { + Boolean: { + serialize: originalSerialize, + } + } + }); + }); + it('retains scalars after walking/recreating the schema', () => { const shorthand = ` scalar Test From f89df7fb02842e92587b1199a928af6c1347fb21 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 13 Feb 2020 01:43:10 -0500 Subject: [PATCH 180/250] chore(linting): switch from tslint to eslint permanently fixes #30 aligns style rules with upstream graphql --- .eslintrc.yml | 507 ++++++++++++++++++ .gitignore | 2 + package.json | 26 +- src/Interfaces.ts | 89 ++- src/Logger.ts | 12 +- src/generate/addResolversToSchema.ts | 85 ++- src/generate/addSchemaLevelResolver.ts | 43 +- src/generate/assertResolversPresent.ts | 10 +- src/generate/attachConnectorsToContext.ts | 23 +- src/generate/attachDirectiveResolvers.ts | 17 +- .../buildSchemaFromTypeDefinitions.ts | 4 +- src/generate/chainResolvers.ts | 9 +- src/generate/checkForResolveTypeResolver.ts | 16 +- src/generate/concatenateTypeDefs.ts | 16 +- src/generate/decorateWithLogger.ts | 11 +- src/generate/extendResolversFromInterfaces.ts | 10 +- src/generate/extractExtensionDefinitions.ts | 5 +- src/links/createServerHttpLink.ts | 133 +++-- src/makeExecutableSchema.ts | 34 +- src/mock.ts | 111 ++-- src/stitching/addTypenameToAbstract.ts | 2 +- src/stitching/checkResultAndHandleErrors.ts | 90 ++-- src/stitching/createMergedResolver.ts | 3 +- src/stitching/createRequest.ts | 64 +-- src/stitching/defaultMergedResolver.ts | 6 +- src/stitching/delegateToSchema.ts | 176 +++--- src/stitching/errors.ts | 29 +- src/stitching/getResponseKeyFromInfo.ts | 2 +- src/stitching/introspectSchema.ts | 55 +- src/stitching/linkToFetcher.ts | 11 +- src/stitching/makeMergedType.ts | 5 +- src/stitching/makeRemoteExecutableSchema.ts | 61 ++- src/stitching/mergeFields.ts | 23 +- src/stitching/mergeInfo.ts | 86 +-- src/stitching/mergeSchemas.ts | 105 ++-- src/stitching/observableToAsyncIterable.ts | 33 +- src/stitching/proxiedResult.ts | 67 ++- src/stitching/resolvers.ts | 71 ++- src/stitching/typeFromAST.ts | 32 +- src/test/circularSchemaA.ts | 4 +- src/test/circularSchemaB.ts | 4 +- src/test/testAlternateMergeSchemas.ts | 259 ++++----- src/test/testDataloader.ts | 22 +- src/test/testDelegateToSchema.ts | 22 +- src/test/testDirectives.ts | 105 ++-- src/test/testErrors.ts | 29 +- src/test/testExtensionExtraction.ts | 4 +- src/test/testFragmentsAreNotDuplicated.ts | 2 +- src/test/testLogger.ts | 57 +- src/test/testMakeRemoteExecutableSchema.ts | 35 +- src/test/testMergeSchemas.ts | 176 +++--- src/test/testMocking.ts | 99 ++-- src/test/testResolution.ts | 12 +- src/test/testSchemaGenerator.ts | 335 ++++++------ src/test/testStitchingFromSubschemas.ts | 43 +- src/test/testTransforms.ts | 102 ++-- src/test/testTypeMerging.ts | 15 +- src/test/testUpload.ts | 21 +- src/test/testUtils.ts | 6 +- src/test/testingSchemas.ts | 267 ++++----- src/test/tests.ts | 21 - src/transforms/AddArgumentsAsVariables.ts | 103 ++-- src/transforms/AddMergedTypeSelectionSets.ts | 21 +- src/transforms/AddReplacementFragments.ts | 18 +- src/transforms/AddReplacementSelectionSets.ts | 18 +- src/transforms/AddTypenameToAbstract.ts | 8 +- src/transforms/CheckResultAndHandleErrors.ts | 20 +- src/transforms/ExpandAbstractTypes.ts | 134 ++--- src/transforms/ExtendSchema.ts | 39 +- src/transforms/ExtractField.ts | 21 +- src/transforms/FilterObjectFields.ts | 5 +- src/transforms/FilterRootFields.ts | 9 +- src/transforms/FilterToSchema.ts | 50 +- src/transforms/FilterTypes.ts | 11 +- src/transforms/HoistField.ts | 34 +- src/transforms/MapFields.ts | 39 +- src/transforms/RenameObjectFields.ts | 14 +- src/transforms/RenameRootFields.ts | 12 +- src/transforms/RenameTypes.ts | 23 +- src/transforms/ReplaceFieldWithFragment.ts | 30 +- src/transforms/TransformObjectFields.ts | 105 ++-- src/transforms/TransformQuery.ts | 37 +- src/transforms/TransformRootFields.ts | 15 +- src/transforms/WrapFields.ts | 26 +- src/transforms/WrapQuery.ts | 15 +- src/transforms/WrapType.ts | 10 +- src/transforms/filterSchema.ts | 54 +- src/transforms/transformSchema.ts | 50 +- src/transforms/transforms.ts | 9 +- src/utils/SchemaDirectiveVisitor.ts | 46 +- src/utils/SchemaVisitor.ts | 51 +- src/utils/clone.ts | 15 +- src/utils/fieldNodes.ts | 59 +- src/utils/fields.ts | 2 +- src/utils/forEachDefaultValue.ts | 8 +- src/utils/forEachField.ts | 7 +- src/utils/fragments.ts | 70 +-- src/utils/getResolversFromSchema.ts | 14 +- src/utils/heal.ts | 94 ++-- src/utils/implementsAbstractType.ts | 4 +- src/utils/isEmptyObject.ts | 4 +- src/utils/mergeDeep.ts | 4 +- src/utils/selectionSets.ts | 4 +- src/utils/transformInputValue.ts | 2 +- src/utils/updateEachKey.ts | 4 +- src/utils/valueFromASTUntyped.ts | 3 +- src/utils/visitSchema.ts | 96 ++-- tsconfig.json | 2 +- 108 files changed, 2881 insertions(+), 2262 deletions(-) create mode 100644 .eslintrc.yml delete mode 100755 src/test/tests.ts 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/.gitignore b/.gitignore index 5c899fd7571..5782c81d5c9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ package-lock.json npm-debug.log* yarn-debug.log* yarn-error.log* + +.eslintcache diff --git a/package.json b/package.json index 3d72a68eeb1..ed6a43d60c2 100644 --- a/package.json +++ b/package.json @@ -3,24 +3,19 @@ "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", + "sideEffects": false, "scripts": { "clean": "rimraf dist", - "compile": "npx tsc", - "typings": "typings install", + "compile": "tsc", "pretest": "npm run clean && npm run compile", "test": "npm run testonly --", "posttest": "npm run lint", - "lint": "tslint src/**/*.ts", + "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", + "testonly": "mocha --reporter spec --full-trace ./dist/test/**.js --require source-map-support/register", + "testonly:watch": "mocha -w --reporter spec --full-trace ./dist/test/**.js --require source-map-support/register", "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", "prepublishOnly": "npm run compile", @@ -67,17 +62,24 @@ "@types/dateformat": "^3.0.1", "@types/express": "^4.17.2", "@types/extract-files": "^3.1.0", + "@types/graphql-type-json": "^0.3.2", "@types/graphql-upload": "^8.0.3", "@types/mocha": "^5.2.7", "@types/node": "^13.1.8", "@types/node-fetch": "^2.5.4", "@types/supertest": "^2.0.8", "@types/uuid": "^3.4.6", + "@typescript-eslint/eslint-plugin": "^2.19.0", + "@typescript-eslint/parser": "^2.19.0", "apollo-upload-client": "^12.1.0", + "babel-eslint": "^10.0.3", "body-parser": "^1.19.0", "chai": "^4.2.0", "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.5.8", diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 231c087652c..5c2416b52d9 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -1,3 +1,6 @@ +import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; +import { SchemaVisitor } from './utils/SchemaVisitor'; + import { GraphQLSchema, GraphQLField, @@ -27,9 +30,6 @@ import { import { TypeMap } from 'graphql/type/schema'; -import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; -import { SchemaVisitor } from './utils/SchemaVisitor'; - import { ApolloLink } from 'apollo-link'; /* TODO: Add documentation */ @@ -47,9 +47,9 @@ export interface IResolverValidationOptions { export interface IAddResolveFunctionsToSchemaOptions { schema: GraphQLSchema; resolvers: IResolvers; - defaultFieldResolver?: IFieldResolver; - resolverValidationOptions?: IResolverValidationOptions; - inheritResolversFromInterfaces?: boolean; + defaultFieldResolver: IFieldResolver; + resolverValidationOptions: IResolverValidationOptions; + inheritResolversFromInterfaces: boolean; } export interface IAddResolversToSchemaOptions { @@ -126,7 +126,7 @@ export type SchemaLikeObject = Array; export function isSubschemaConfig(value: SchemaLikeObject): value is SubschemaConfig { - return !!(value as SubschemaConfig).schema; + return Boolean((value as SubschemaConfig).schema); } export interface IDelegateToSchemaOptions { @@ -206,11 +206,11 @@ export type IFieldResolver> = ( source: TSource, args: TArgs, context: TContext, - info: GraphQLResolveInfo & { mergeInfo: MergeInfo }, + info: IGraphQLToolsResolveInfo, ) => any; -export type ITypedef = (() => ITypedef[]) | string | DocumentNode; -export type ITypeDefinitions = ITypedef | ITypedef[]; +export type ITypedef = (() => Array) | string | DocumentNode; +export type ITypeDefinitions = ITypedef | Array; export type IResolverObject = { [key: string]: | IFieldResolver @@ -232,12 +232,10 @@ export type IResolversParameter = | ((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 @@ -294,7 +292,7 @@ export type IMockTypeFn = ( ) => GraphQLFieldResolver; export interface IMockOptions { - schema: GraphQLSchema; + schema?: GraphQLSchema; mocks?: IMocks; preserveResolvers?: boolean; } @@ -377,8 +375,63 @@ export enum VisitSchemaKind { SUBSCRIPTION = 'VisitSchemaKind.SUBSCRIPTION', } -export type SchemaVisitorMap = { [key in VisitSchemaKind]?: TypeVisitor }; -export type TypeVisitor = ( - type: GraphQLType, +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; diff --git a/src/Logger.ts b/src/Logger.ts index 3b8c420ff3a..f7014601872 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -5,9 +5,9 @@ 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/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index 321a0b8a9fb..1dc4edbd2b6 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -1,22 +1,9 @@ -import { - GraphQLField, - GraphQLEnumType, - GraphQLScalarType, - GraphQLSchema, - GraphQLObjectType, - GraphQLInterfaceType, - isSpecifiedScalarType, -} from 'graphql'; - import { IResolvers, IResolverValidationOptions, IAddResolversToSchemaOptions, } from '../Interfaces'; -import SchemaError from './SchemaError'; -import checkForResolveTypeResolver from './checkForResolveTypeResolver'; -import extendResolversFromInterfaces from './extendResolversFromInterfaces'; import { parseInputValue, serializeInputValue, @@ -25,21 +12,31 @@ import { forEachDefaultValue, } from '../utils'; +import SchemaError from './SchemaError'; +import checkForResolveTypeResolver from './checkForResolveTypeResolver'; +import extendResolversFromInterfaces from './extendResolversFromInterfaces'; + +import { + GraphQLField, + GraphQLEnumType, + GraphQLScalarType, + GraphQLSchema, + GraphQLObjectType, + GraphQLInterfaceType, + isSpecifiedScalarType, + GraphQLUnionType, +} from 'graphql'; + function addResolversToSchema( - options: IAddResolversToSchemaOptions | GraphQLSchema, + schemaOrOptions: GraphQLSchema | IAddResolversToSchemaOptions, legacyInputResolvers?: IResolvers, legacyInputValidationOptions?: IResolverValidationOptions, ) { - if (options instanceof GraphQLSchema) { - console.warn( - 'The addResolversToSchema function takes named options now; see IAddResolveFunctionsToSchemaOptions', - ); - options = { - schema: options, - resolvers: legacyInputResolvers, - resolverValidationOptions: legacyInputValidationOptions, - }; - } + const options: IAddResolversToSchemaOptions = (schemaOrOptions instanceof GraphQLSchema) ? { + schema: schemaOrOptions, + resolvers: legacyInputResolvers, + resolverValidationOptions: legacyInputValidationOptions, + } : schemaOrOptions; const { schema, @@ -66,8 +63,7 @@ function addResolversToSchema( 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.`, + `"${typeName}" defined in resolvers, but has invalid value "${resolverValue as string}". A resolver's value must be of type object or function.`, ); } @@ -151,30 +147,33 @@ function addResolversToSchema( ...config, values: newValues, }); - } else { - // object type + } else if (type instanceof GraphQLUnionType) { 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; + } - if (!( - type instanceof GraphQLObjectType || - type instanceof GraphQLInterfaceType - )) { - if (allowResolversNotInSchema) { - return; - } - - throw new SchemaError( - `${typeName} was defined in resolvers, but it's not an object`, - ); + throw new SchemaError( + `${typeName} was defined in resolvers, but it's not an object`, + ); + }); + } else if (type instanceof GraphQLInterfaceType || type instanceof GraphQLObjectType) { + 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(); - if (!fields[fieldName]) { + const field = fields[fieldName]; + + if (field == null) { if (allowResolversNotInSchema) { return; } @@ -183,7 +182,7 @@ function addResolversToSchema( `${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 @@ -209,7 +208,7 @@ function addResolversToSchema( // reparse all default values with new parsing functions. forEachDefaultValue(schema, parseInputValue); - if (defaultFieldResolver) { + if (defaultFieldResolver != null) { forEachField(schema, field => { if (!field.resolve) { field.resolve = defaultFieldResolver; @@ -222,7 +221,7 @@ function addResolversToSchema( function setFieldProperties( field: GraphQLField, - propertiesObj: Object, + propertiesObj: Record, ) { Object.keys(propertiesObj).forEach(propertyName => { field[propertyName] = propertiesObj[propertyName]; diff --git a/src/generate/addSchemaLevelResolver.ts b/src/generate/addSchemaLevelResolver.ts index 229d0a87f77..058fae93cd2 100644 --- a/src/generate/addSchemaLevelResolver.ts +++ b/src/generate/addSchemaLevelResolver.ts @@ -15,24 +15,26 @@ function addSchemaLevelResolver( schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType(), - ].filter(x => !!x); + ].filter(x => Boolean(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, - ); - } - }); + 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 +43,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 diff --git a/src/generate/assertResolversPresent.ts b/src/generate/assertResolversPresent.ts index fac561175a1..4dc860225be 100644 --- a/src/generate/assertResolversPresent.ts +++ b/src/generate/assertResolversPresent.ts @@ -1,12 +1,14 @@ +import { IResolverValidationOptions } from '../Interfaces'; +import { forEachField } from '../utils'; + +import SchemaError from './SchemaError'; + import { GraphQLSchema, GraphQLField, getNamedType, GraphQLScalarType, } from 'graphql'; -import { IResolverValidationOptions } from '../Interfaces'; -import { forEachField } from '../utils'; -import SchemaError from './SchemaError'; function assertResolversPresent( schema: GraphQLSchema, @@ -56,8 +58,8 @@ function expectResolver( fieldName: string, ) { if (!field.resolve) { + // eslint-disable-next-line no-console console.warn( - // tslint:disable-next-line: max-line-length `Resolver missing for "${typeName}.${fieldName}". To disable this warning check https://github.com/apollostack/graphql-tools/issues/131`, ); return; diff --git a/src/generate/attachConnectorsToContext.ts b/src/generate/attachConnectorsToContext.ts index 6c25574dc82..589b06d92d8 100644 --- a/src/generate/attachConnectorsToContext.ts +++ b/src/generate/attachConnectorsToContext.ts @@ -1,11 +1,10 @@ -import { GraphQLSchema, GraphQLFieldResolver } from 'graphql'; - -import { deprecated } from 'deprecated-decorator'; - import { IConnectors, IConnector, IConnectorCls } from '../Interfaces'; import addSchemaLevelResolver from './addSchemaLevelResolver'; +import { deprecated } from 'deprecated-decorator'; +import { GraphQLSchema, GraphQLFieldResolver } from 'graphql'; + // takes a GraphQL-JS schema and an object of connectors, then attaches // the connectors to the context by wrapping each query or mutation resolve // function with a function that attaches connectors if they don't exist. @@ -15,7 +14,7 @@ const attachConnectorsToContext = deprecated( version: '0.7.0', url: 'https://github.com/apollostack/graphql-tools/issues/140', }, - function(schema: GraphQLSchema, connectors: IConnectors): void { + (schema: GraphQLSchema, connectors: IConnectors): void => { if (!schema || !(schema instanceof GraphQLSchema)) { throw new Error( 'schema must be an instance of GraphQLSchema. ' + @@ -41,11 +40,7 @@ const attachConnectorsToContext = deprecated( ); } schema['_apolloConnectorsAttached'] = true; - const attachconnectorFn: GraphQLFieldResolver = ( - root: any, - args: { [key: string]: any }, - ctx: any, - ) => { + const attachconnectorFn: GraphQLFieldResolver = (root, _args, ctx) => { if (typeof ctx !== 'object') { // if in any way possible, we should throw an error when the attachconnectors // function is called, not when a query is executed. @@ -58,11 +53,11 @@ const attachConnectorsToContext = deprecated( ctx.connectors = {}; } Object.keys(connectors).forEach(connectorName => { - let connector: IConnector = connectors[connectorName]; - if (!!connector.prototype) { - ctx.connectors[connectorName] = new (connector)(ctx); + 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; diff --git a/src/generate/attachDirectiveResolvers.ts b/src/generate/attachDirectiveResolvers.ts index ecd7b64a87f..ef2e7236ff6 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 '../utils/SchemaDirectiveVisitor'; +import { GraphQLSchema, GraphQLField, defaultFieldResolver } from 'graphql'; + function attachDirectiveResolvers( schema: GraphQLSchema, - directiveResolvers: IDirectiveResolvers, + directiveResolvers: IDirectiveResolvers, ) { if (typeof directiveResolvers !== 'object') { throw new Error( @@ -24,12 +25,18 @@ function attachDirectiveResolvers( 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 8e852f85c0e..d9f9d9a3c77 100644 --- a/src/generate/buildSchemaFromTypeDefinitions.ts +++ b/src/generate/buildSchemaFromTypeDefinitions.ts @@ -4,7 +4,9 @@ import { buildASTSchema, GraphQLSchema, DocumentNode, + ASTNode, } from 'graphql'; + import { ITypeDefinitions, GraphQLParseOptions } from '../Interfaces'; import filterExtensionDefinitions from './filterExtensionDefinitions'; @@ -57,7 +59,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..903382e6db4 100644 --- a/src/generate/chainResolvers.ts +++ b/src/generate/chainResolvers.ts @@ -1,13 +1,12 @@ 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 8b50f99455c..9e85b86cd67 100644 --- a/src/generate/checkForResolveTypeResolver.ts +++ b/src/generate/checkForResolveTypeResolver.ts @@ -1,7 +1,7 @@ -import { GraphQLInterfaceType, GraphQLUnionType, GraphQLSchema } from 'graphql'; - import SchemaError from './SchemaError'; +import { GraphQLInterfaceType, GraphQLUnionType, GraphQLSchema } from 'graphql'; + // If we have any union or interface types throw if no there is no resolveType or isTypeOf resolvers function checkForResolveTypeResolver( schema: GraphQLSchema, @@ -19,20 +19,14 @@ function checkForResolveTypeResolver( 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( + throw new SchemaError( `Type "${ type.name }" is missing a "__resolveType" resolver. Pass false into ` + - `"resolverValidationOptions.requireResolversForResolveType" to disable this warning.`, + '"resolverValidationOptions.requireResolversForResolveType" to disable this error.', ); } }); diff --git a/src/generate/concatenateTypeDefs.ts b/src/generate/concatenateTypeDefs.ts index 96e046d928d..91abe980fd1 100644 --- a/src/generate/concatenateTypeDefs.ts +++ b/src/generate/concatenateTypeDefs.ts @@ -1,13 +1,14 @@ -import { print, DocumentNode } from 'graphql'; import { ITypedef } from '../Interfaces'; import SchemaError from './SchemaError'; +import { print, ASTNode } from 'graphql'; + function concatenateTypeDefs( - typeDefinitionsAry: ITypedef[], + typeDefinitionsAry: Array, calledFunctionRefs = [] as any, ): string { - let resolvedTypeDefinitions: string[] = []; + let resolvedTypeDefinitions: Array = []; typeDefinitionsAry.forEach((typeDef: ITypedef) => { if (typeof typeDef === 'function') { if (calledFunctionRefs.indexOf(typeDef) === -1) { @@ -18,7 +19,7 @@ function concatenateTypeDefs( } } else if (typeof typeDef === 'string') { resolvedTypeDefinitions.push(typeDef.trim()); - } else if ((typeDef).kind !== undefined) { + } else if ((typeDef as ASTNode).kind !== undefined) { resolvedTypeDefinitions.push(print(typeDef).trim()); } else { const type = typeof typeDef; @@ -31,11 +32,10 @@ function concatenateTypeDefs( } function uniq(array: Array): Array { - return array.reduce((accumulator, currentValue) => { - return accumulator.indexOf(currentValue) === -1 + return array.reduce((accumulator, currentValue) => + accumulator.indexOf(currentValue) === -1 ? [...accumulator, currentValue] - : accumulator; - }, []); + : accumulator, []); } export default concatenateTypeDefs; diff --git a/src/generate/decorateWithLogger.ts b/src/generate/decorateWithLogger.ts index 88ed6f97a4d..6809333c456 100644 --- a/src/generate/decorateWithLogger.ts +++ b/src/generate/decorateWithLogger.ts @@ -1,19 +1,18 @@ -import { defaultFieldResolver, GraphQLFieldResolver } from 'graphql'; import { ILogger } from '../Interfaces'; +import { defaultFieldResolver, GraphQLFieldResolver } from 'graphql'; + /* * fn: The function to decorate with the logger * logger: an object instance of type Logger * 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,7 +28,7 @@ function decorateWithLogger( return (root, args, ctx, info) => { try { - const result = fn(root, args, ctx, info); + const result = resolver(root, args, ctx, info); // If the resolver returns a Promise log any Promise rejects. if ( result && diff --git a/src/generate/extendResolversFromInterfaces.ts b/src/generate/extendResolversFromInterfaces.ts index aee6d0efb5c..c6947d38968 100644 --- a/src/generate/extendResolversFromInterfaces.ts +++ b/src/generate/extendResolversFromInterfaces.ts @@ -1,7 +1,7 @@ -import { GraphQLObjectType, GraphQLSchema } from 'graphql'; - import { IResolvers } from '../Interfaces'; +import { GraphQLObjectType, GraphQLSchema } from 'graphql'; + function extendResolversFromInterfaces( schema: GraphQLSchema, resolvers: IResolvers, @@ -24,10 +24,8 @@ function extendResolversFromInterfaces( ...interfaceResolvers, typeResolvers, ); - } else { - if (typeResolvers) { - extendedResolvers[typeName] = typeResolvers; - } + } else if (typeResolvers != null) { + extendedResolvers[typeName] = typeResolvers; } }); diff --git a/src/generate/extractExtensionDefinitions.ts b/src/generate/extractExtensionDefinitions.ts index 69b8d072a5d..c7b4c473fe2 100644 --- a/src/generate/extractExtensionDefinitions.ts +++ b/src/generate/extractExtensionDefinitions.ts @@ -16,7 +16,8 @@ export default function extractExtensionDefinitions(ast: DocumentNode) { (def.kind as any) === enumExtensionDefinitionKind, ); - return Object.assign({}, ast, { + return { + ...ast, definitions: extensionDefs, - }); + }; } diff --git a/src/links/createServerHttpLink.ts b/src/links/createServerHttpLink.ts index ab07646d098..d593a06cd67 100644 --- a/src/links/createServerHttpLink.ts +++ b/src/links/createServerHttpLink.ts @@ -1,3 +1,7 @@ +/* eslint-disable import/no-nodejs-modules */ + +import { Readable } from 'stream'; + import { ApolloLink, Observable, RequestHandler, fromError } from 'apollo-link'; import { serializeFetchParameter, @@ -15,6 +19,8 @@ import { extractFiles, isExtractableFile as defaultIsExtractableFile } from 'ext import KnownLengthFormData, { AppendOptions } from 'form-data'; import fetch from 'node-fetch'; +const hasOwn = Object.prototype.hasOwnProperty; + class FormData extends KnownLengthFormData { private hasUnknowableLength: boolean; @@ -23,13 +29,11 @@ class FormData extends KnownLengthFormData { this.hasUnknowableLength = false; } - public append(key: string, value: any, options?: AppendOptions | string): void { - options = options || {}; - + public append(key: string, value: any, optionsOrFilename: AppendOptions | string = {}): void { // allow filename as single option - if (typeof options === 'string') { - options = {filename: options}; - } + const options: AppendOptions = (typeof optionsOrFilename === 'string') ? + { filename: optionsOrFilename } : + optionsOrFilename; // empty or either doesn't have path or not an http response if ( @@ -37,7 +41,7 @@ class FormData extends KnownLengthFormData { !Buffer.isBuffer(value) && typeof value !== 'string' && !value.path && - !(value.readable && value.hasOwnProperty('httpVersion')) + !(value.readable && hasOwn.call(value, 'httpVersion')) ) { this.hasUnknowableLength = true; } @@ -58,30 +62,24 @@ class FormData extends KnownLengthFormData { return null; } + // eslint-disable-next-line no-sync return super.getLengthSync(); } } -export namespace HttpLink { - //TODO Would much rather be able to export directly - // tslint:disable-next-line: no-shadowed-variable - export interface Function extends UriFunction {} - export interface Options extends 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; - } +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 import FetchOptions = HttpLink.Options; -export import UriFunction = HttpLink.Function; -import { Readable } from 'stream'; +export { HttpOptions as FetchOptions }; interface File { createReadStream?: () => Readable; @@ -90,8 +88,8 @@ interface File { name?: string; } -export const createServerHttpLink = (linkOptions: HttpLink.Options = {}) => { - let { +export const createServerHttpLink = (linkOptions: Options = {}) => { + const { uri = '/graphql', fetch: customFetch = fetch as unknown as WindowOrWorkerGlobalScope['fetch'], serializer: customSerializer = defaultSerializer, @@ -139,7 +137,7 @@ export const createServerHttpLink = (linkOptions: HttpLink.Options = {}) => { headers: contextHeaders, }; - //uses fallback, link, and then context to build options + // uses fallback, link, and then context to build options const { options, body } = selectHttpOptionsAndBody( operation, fallbackHttpConfig, @@ -151,15 +149,15 @@ export const createServerHttpLink = (linkOptions: HttpLink.Options = {}) => { if (!(options as any).signal) { const { controller: _controller, signal } = createSignalIfSupported(); controller = _controller; - if (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) => { - return d.kind === 'OperationDefinition' && d.operation === 'mutation'; - }; + const definitionIsMutation = (d: DefinitionNode) => + d.kind === 'OperationDefinition' && d.operation === 'mutation'; + if ( useGETForQueries && !operation.query.definitions.some(definitionIsMutation) @@ -176,12 +174,7 @@ export const createServerHttpLink = (linkOptions: HttpLink.Options = {}) => { } return new Observable(observer => { - resolvePromises(body, async (object) => { - if (object instanceof Promise) { - object = await object; - } - return object; - }).then(resolvedBody => { + getFinalPromise(body).then(resolvedBody => { if (options.method !== 'GET') { options.body = customSerializer(resolvedBody, customAppendFile); if (options.body instanceof FormData) { @@ -249,7 +242,7 @@ export const createServerHttpLink = (linkOptions: HttpLink.Options = {}) => { return () => { // XXX support canceling this request // https://developers.google.com/web/updates/2017/09/abortable-fetch - if (controller) { + if (controller as unknown as boolean) { controller.abort(); } }; @@ -273,7 +266,7 @@ function rewriteURIForGET(chosenURI: string, body: Body) { if (body.operationName) { addQueryParam('operationName', body.operationName); } - if (body.variables) { + if (body.variables != null) { let serializedVariables; try { serializedVariables = serializeFetchParameter( @@ -285,7 +278,7 @@ function rewriteURIForGET(chosenURI: string, body: Body) { } addQueryParam('variables', serializedVariables); } - if (body.extensions) { + if (body.extensions != null) { let serializedExtensions; try { serializedExtensions = serializeFetchParameter( @@ -304,8 +297,8 @@ function rewriteURIForGET(chosenURI: string, body: Body) { // 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 = '', - preFragment = chosenURI; + let fragment = ''; + let preFragment = chosenURI; const fragmentStart = chosenURI.indexOf('#'); if (fragmentStart !== -1) { fragment = chosenURI.substr(fragmentStart); @@ -317,42 +310,38 @@ function rewriteURIForGET(chosenURI: string, body: Body) { return { newURI }; } -async function resolvePromises(object: any, resolver: ((o: any) => any)): Promise { - if (!object) { - return object; - } +function getFinalPromise(object: any): Promise { + return Promise.resolve(object).then(resolvedObject => { + if (resolvedObject == null) { + return resolvedObject; + } - if (resolver) { - object = await resolver(object); - } + 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; + }); - if (Array.isArray(object)) { - return object.map(async o => await resolvePromises(o, resolver)); - } else if (typeof object === 'object') { - const keys = Object.keys(object); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - object[key] = await resolvePromises(object[key], resolver); } - return object; - } - return object; + return resolvedObject; + }); } function defaultSerializer( body: any, appendFile: (form: FormData, index: string, file: File) => void, ): any { - const { clone, files } = extractFiles(body, undefined, (value: any) => { - if ( - defaultIsExtractableFile(value) || - (value && value.createReadStream) - ) { - return true; - } - return false; - }); + const { clone, files } = extractFiles(body, undefined, (value: any) => + defaultIsExtractableFile(value) || value?.createReadStream); const payload = serializeFetchParameter(clone, 'Payload'); @@ -377,7 +366,7 @@ function defaultSerializer( form.append('map', JSON.stringify(map)); i = 0; - files.forEach((paths: Array, file: File) => { + files.forEach((_paths: Array, file: File) => { appendFile(form, (++i).toString(), file); }); @@ -385,7 +374,7 @@ function defaultSerializer( } function defaultAppendFile(form: FormData, index: string, file: File) { - if (file.createReadStream) { + if (file.createReadStream != null) { form.append(index, file.createReadStream(), { filename: file.filename, contentType: file.mimetype, @@ -397,7 +386,7 @@ function defaultAppendFile(form: FormData, index: string, file: File) { export class ServerHttpLink extends ApolloLink { public requester: RequestHandler; - constructor(opts?: HttpLink.Options) { + constructor(opts?: HttpOptions) { super(createServerHttpLink(opts).request); } } diff --git a/src/makeExecutableSchema.ts b/src/makeExecutableSchema.ts index 96b6c7c5e35..6c23352d4f9 100644 --- a/src/makeExecutableSchema.ts +++ b/src/makeExecutableSchema.ts @@ -1,5 +1,3 @@ -import { defaultFieldResolver, GraphQLSchema, GraphQLFieldResolver } from 'graphql'; - import { IExecutableSchemaDefinition, ILogger } from './Interfaces'; import { @@ -19,6 +17,12 @@ import { SchemaError } from './generate'; +import { + defaultFieldResolver, + GraphQLSchema, + GraphQLFieldResolver, +} from 'graphql'; + export function makeExecutableSchema({ typeDefs, resolvers = {}, @@ -26,8 +30,8 @@ export function makeExecutableSchema({ logger, allowUndefinedInResolve = true, resolverValidationOptions = {}, - directiveResolvers = null, - schemaDirectives = null, + directiveResolvers, + schemaDirectives, parseOptions = {}, inheritResolversFromInterfaces = false }: IExecutableSchemaDefinition) { @@ -40,10 +44,6 @@ export function makeExecutableSchema({ 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, {}) @@ -51,7 +51,7 @@ export function makeExecutableSchema({ // Arguments are now validated and cleaned up - let schema = buildSchemaFromTypeDefinitions(typeDefs, parseOptions); + const schema = buildSchemaFromTypeDefinitions(typeDefs, parseOptions); addResolversToSchema({ schema, @@ -66,7 +66,7 @@ export function makeExecutableSchema({ addCatchUndefinedToSchema(schema); } - if (logger) { + if (logger != null) { addErrorLoggingToSchema(schema, logger); } @@ -76,17 +76,17 @@ export function makeExecutableSchema({ 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); } @@ -97,11 +97,9 @@ function decorateToCatchUndefined( fn: GraphQLFieldResolver, 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(`Resolver for "${hint}" returned undefined`); } @@ -116,7 +114,7 @@ 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'); } diff --git a/src/mock.ts b/src/mock.ts index 766b771a398..46bd5d20a63 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -1,3 +1,15 @@ +import { buildSchemaFromTypeDefinitions } from './makeExecutableSchema'; +import { forEachField } from './utils'; + +import { + IMocks, + IMockServer, + IMockOptions, + IMockFn, + IMockTypeFn, + ITypeDefinitions, +} from './Interfaces'; + import { graphql, GraphQLSchema, @@ -16,18 +28,7 @@ import { GraphQLNonNull, GraphQLNullableType, } from 'graphql'; -import * as uuid from 'uuid'; -import { buildSchemaFromTypeDefinitions } from './makeExecutableSchema'; -import { forEachField } from './utils'; - -import { - IMocks, - IMockServer, - IMockOptions, - IMockFn, - IMockTypeFn, - ITypeDefinitions, -} from './Interfaces'; +import { v4 as uuid } from 'uuid'; // This function wraps addMocksToSchema for more convenience function mockServer( @@ -53,7 +54,7 @@ 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 @@ -87,7 +88,7 @@ function addMocksToSchema({ const mockType = function( type: GraphQLType, - typeName?: string, + _typeName?: string, fieldName?: string, ): GraphQLFieldResolver { // order of precendence for mocking: @@ -107,7 +108,7 @@ function addMocksToSchema({ 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 @@ -130,10 +131,10 @@ function addMocksToSchema({ // 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, ); } @@ -157,7 +158,8 @@ function addMocksToSchema({ ) ) { // 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) { // objects don't return actual data, we only need to mock scalars! @@ -171,12 +173,8 @@ function addMocksToSchema({ ) { 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}"`); } @@ -185,10 +183,10 @@ function addMocksToSchema({ 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) { @@ -196,7 +194,8 @@ function addMocksToSchema({ } 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, @@ -210,13 +209,15 @@ function addMocksToSchema({ 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)) { @@ -229,7 +230,7 @@ function addMocksToSchema({ 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 ]; @@ -248,18 +249,15 @@ function addMocksToSchema({ } } } - 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), @@ -305,26 +303,27 @@ 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) { +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[]) { +function copyOwnProps(target: Record, ...sources: Array>) { sources.forEach(source => { let chain = source; - while (chain) { + while (chain != null) { copyOwnPropsIfNotPresent(target, chain); chain = Object.getPrototypeOf(chain); } @@ -352,8 +351,6 @@ function getResolveType(namedFieldType: GraphQLNamedType) { namedFieldType instanceof GraphQLUnionType ) { return namedFieldType.resolveType; - } else { - return undefined; } } @@ -362,7 +359,7 @@ 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; } @@ -374,21 +371,19 @@ function assignResolveType(type: GraphQLType, preserveResolvers: boolean) { // 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; @@ -410,7 +405,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 { diff --git a/src/stitching/addTypenameToAbstract.ts b/src/stitching/addTypenameToAbstract.ts index 76783d9785b..989e91bfe67 100644 --- a/src/stitching/addTypenameToAbstract.ts +++ b/src/stitching/addTypenameToAbstract.ts @@ -25,7 +25,7 @@ export function addTypenameToAbstract( const parentType: GraphQLType = typeInfo.getParentType(); let selections = node.selections; if ( - parentType && + parentType != null && (parentType instanceof GraphQLInterfaceType || parentType instanceof GraphQLUnionType) ) { diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 7f13d2f8483..3458821f73d 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -1,3 +1,20 @@ +import { + SubschemaConfig, + IGraphQLToolsResolveInfo, + isSubschemaConfig, + MergedTypeInfo, +} from '../Interfaces'; + +import { + relocatedError, + combineErrors, + getErrorsByPathSegment, +} from './errors'; +import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; +import resolveFromParentTypename from './resolveFromParentTypename'; +import { setErrors, setObjectSubschema } from './proxiedResult'; +import { mergeFields } from './mergeFields'; + import { GraphQLResolveInfo, responsePathAsArray, @@ -16,38 +33,19 @@ import { isAbstractType, GraphQLObjectType, } from 'graphql'; -import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; -import { - relocatedError, - combineErrors, - getErrorsByPathSegment, -} from './errors'; -import { - SubschemaConfig, - IGraphQLToolsResolveInfo, - isSubschemaConfig, - MergedTypeInfo, -} from '../Interfaces'; -import resolveFromParentTypename from './resolveFromParentTypename'; -import { setErrors, setObjectSubschema } from './proxiedResult'; -import { mergeFields } from './mergeFields'; import { collectFields, ExecutionContext } from 'graphql/execution/execute'; export function checkResultAndHandleErrors( result: ExecutionResult, context: Record, info: GraphQLResolveInfo, - responseKey?: string, + responseKey: string = getResponseKeyFromInfo(info), subschema?: GraphQLSchema | SubschemaConfig, returnType: GraphQLOutputType = info.returnType, skipTypeMerging?: boolean, ): any { - if (!responseKey) { - responseKey = getResponseKeyFromInfo(info); - } - - const errors = result.errors || []; - const data = result.data && result.data[responseKey]; + 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); } @@ -87,18 +85,16 @@ function handleList( ) { const childErrors = getErrorsByPathSegment(errors); - list = list.map((listMember, index) => handleListMember( + return list.map((listMember, index) => handleListMember( getNullableType(type.ofType), listMember, index, - childErrors[index] || [], + childErrors[index] != null ? childErrors[index] : [], subschema, context, info, skipTypeMerging, )); - - return list; } function handleListMember( @@ -133,13 +129,11 @@ export function handleObject( info: IGraphQLToolsResolveInfo, skipTypeMerging?: boolean, ) { - setErrors(object, errors.map(error => { - return relocatedError( - error, - error.nodes, - error.path ? error.path.slice(1) : undefined - ); - })); + setErrors(object, errors.map(error => relocatedError( + error, + error.nodes, + error.path != null ? error.path.slice(1) : undefined, + ))); setObjectSubschema(object, subschema); @@ -152,7 +146,11 @@ export function handleObject( info.schema.getTypeMap()[resolveFromParentTypename(object)].name : type.name; const mergedTypeInfo = info.mergeInfo.mergedTypes[typeName]; - let targetSubschemas = mergedTypeInfo && mergedTypeInfo.subschemas; + let targetSubschemas: Array; + + if (mergedTypeInfo != null) { + targetSubschemas = mergedTypeInfo.subschemas; + }; if (!targetSubschemas) { return object; @@ -189,8 +187,8 @@ function collectSubFields(info: IGraphQLToolsResolveInfo, typeName: string) { const visitedFragmentNames = Object.create(null); info.fieldNodes.forEach(fieldNode => { subFieldNodes = collectFields( - { schema: info.schema, variableValues: info.variableValues, fragments: info.fragments } as ExecutionContext, - info.schema.getType(typeName) as GraphQLObjectType, + { schema: info.schema, variableValues: info.variableValues, fragments: info.fragments } as unknown as ExecutionContext, + info.schema.getType(typeName) as GraphQLObjectType, fieldNode.selectionSet, subFieldNodes, visitedFragmentNames, @@ -244,17 +242,17 @@ export function handleNull( return result; - } else { - const childErrors = getErrorsByPathSegment(errors); + } - const result = new Array; - Object.keys(childErrors).forEach(pathSegment => { - result.push(handleNull(fieldNodes, [...path, parseInt(pathSegment, 10)], childErrors[pathSegment])); - }); + const childErrors = getErrorsByPathSegment(errors); - return result; - } - } else { - return null; + 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/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index e71b99155cf..33939de2494 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -1,4 +1,5 @@ import { IFieldResolver } from '../Interfaces'; + import { unwrapResult, dehoistResult } from './proxiedResult'; import defaultMergedResolver from './defaultMergedResolver'; @@ -16,7 +17,7 @@ export function createMergedResolver({ parent : defaultMergedResolver(parent, args, context, info); - const unwrappingResolver: IFieldResolver = fromPath && fromPath.length ? + const unwrappingResolver: IFieldResolver = fromPath != null ? (parent, args, context, info) => parentErrorResolver(unwrapResult(parent, info, fromPath), args, context, info) : parentErrorResolver; diff --git a/src/stitching/createRequest.ts b/src/stitching/createRequest.ts index d3aa9938f46..27aa1ae55db 100644 --- a/src/stitching/createRequest.ts +++ b/src/stitching/createRequest.ts @@ -1,3 +1,12 @@ +import { + ICreateRequestFromInfo, + Operation, + Request, + SubschemaConfig, + isSubschemaConfig, +} from '../Interfaces'; +import { serializeInputValue } from '../utils'; + import { ArgumentNode, FieldNode, @@ -21,16 +30,6 @@ import { SelectionSetNode, } from 'graphql'; -import { - ICreateRequestFromInfo, - Operation, - Request, - SubschemaConfig, - isSubschemaConfig, -} from '../Interfaces'; - -import { serializeInputValue } from '../utils'; - export function getDelegatingOperation( parentType: GraphQLObjectType, schema: GraphQLSchema @@ -39,9 +38,9 @@ export function getDelegatingOperation( return 'mutation'; } else if (parentType === schema.getSubscriptionType()) { return 'subscription'; - } else { - return 'query'; } + + return 'query'; } export function createRequestFromInfo({ @@ -63,7 +62,7 @@ export function createRequestFromInfo({ fieldName, args, selectionSet, - selectionSet ? undefined : (fieldNodes ? fieldNodes : info.fieldNodes), + selectionSet != null ? undefined : (fieldNodes != null ? fieldNodes: info.fieldNodes), ); } @@ -81,17 +80,20 @@ export function createRequest( ): Request { let argumentNodes: ReadonlyArray; - if (!selectionSet && fieldNodes) { + let newSelectionSet: SelectionSetNode = selectionSet; + let newVariableDefinitions: ReadonlyArray = variableDefinitions; + + if (!selectionSet && fieldNodes != null) { const selections: Array = fieldNodes.reduce( - (acc, fieldNode) => fieldNode.selectionSet ? + (acc, fieldNode) => fieldNode.selectionSet != null ? acc.concat(fieldNode.selectionSet.selections) : acc, [], ); - selectionSet = selections.length ? { + newSelectionSet = selections.length ? { kind: Kind.SELECTION_SET, - selections: selections, + selections, } : undefined; argumentNodes = fieldNodes[0].arguments; @@ -106,7 +108,7 @@ export function createRequest( variables[varName] = serializeInputValue(varType, variableValues[varName]); } - if (args) { + if (args != null) { const { arguments: updatedArguments, variableDefinitions: updatedVariableDefinitions, @@ -121,7 +123,7 @@ export function createRequest( args, ); argumentNodes = updatedArguments; - variableDefinitions = updatedVariableDefinitions; + newVariableDefinitions = updatedVariableDefinitions; variables = updatedVariableValues; } @@ -129,7 +131,7 @@ export function createRequest( kind: Kind.FIELD, alias: null, arguments: argumentNodes, - selectionSet, + selectionSet: newSelectionSet, name: { kind: Kind.NAME, value: targetField || fieldNodes[0].name.value, @@ -139,7 +141,7 @@ export function createRequest( const operationDefinition: OperationDefinitionNode = { kind: Kind.OPERATION_DEFINITION, operation: targetOperation, - variableDefinitions, + variableDefinitions: newVariableDefinitions, selectionSet: { kind: Kind.SELECTION_SET, selections: [rootfieldNode], @@ -186,7 +188,7 @@ function updateArguments( type = schema.getQueryType(); } - let varNames = variableDefinitions.reduce((acc, def) => { + const varNames = variableDefinitions.reduce((acc, def) => { acc[def.variable.name.value] = true; return acc; }, {}); @@ -204,7 +206,7 @@ function updateArguments( const argName = argument.name; let varName; do { - varName = `_v${numGeneratedVariables++}_${argName}`; + varName = `_v${(numGeneratedVariables++).toString()}_${argName}`; } while (varNames[varName]); updatedArgs[argument.name] = { @@ -262,13 +264,13 @@ function astFromType(type: GraphQLType): TypeNode { kind: Kind.LIST_TYPE, type: astFromType(type.ofType), }; - } else { - return { - kind: Kind.NAMED_TYPE, - name: { - kind: Kind.NAME, - value: type.name, - }, - }; } + + return { + kind: Kind.NAMED_TYPE, + name: { + kind: Kind.NAME, + value: type.name, + }, + }; } diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index 5008ab4c07c..b58bd6e3829 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,8 +1,10 @@ -import { defaultFieldResolver } from 'graphql'; +import { IGraphQLToolsResolveInfo } from '../Interfaces'; + import { getErrors, getSubschema } from './proxiedResult'; import { handleResult } from './checkResultAndHandleErrors'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; -import { IGraphQLToolsResolveInfo } from '../Interfaces'; + +import { defaultFieldResolver } from 'graphql'; // Resolver that knows how to: // a) handle aliases for proxied schemas diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 7870909efc3..d32a2a44158 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -1,12 +1,3 @@ -import { - subscribe, - execute, - validate, - GraphQLSchema, - ExecutionResult, - GraphQLOutputType, -} from 'graphql'; - import { IDelegateToSchemaOptions, IDelegateRequestOptions, @@ -16,7 +7,6 @@ import { isSubschemaConfig, IGraphQLToolsResolveInfo, } from '../Interfaces'; - import { ExpandAbstractTypes, FilterToSchema, @@ -34,12 +24,21 @@ import { createRequestFromInfo, getDelegatingOperation, } from './createRequest'; - -import { ApolloLink, execute as executeLink } from 'apollo-link'; import linkToFetcher from './linkToFetcher'; import { observableToAsyncIterable } from './observableToAsyncIterable'; -import { isAsyncIterable } from 'iterall'; import mapAsyncIterator from './mapAsyncIterator'; +import { combineErrors } from './errors'; + +import { isAsyncIterable } from 'iterall'; +import { ApolloLink, execute as executeLink } from 'apollo-link'; +import { + subscribe, + execute, + validate, + GraphQLSchema, + ExecutionResult, + GraphQLOutputType, +} from 'graphql'; export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, @@ -95,7 +94,7 @@ function buildDelegationTransforms( new CheckResultAndHandleErrors(info, fieldName, subschemaOrSubschemaConfig, context, returnType, skipTypeMerging), ]; - if (info.mergeInfo) { + if (info.mergeInfo != null) { delegationTransforms.push( new AddReplacementSelectionSets(info.schema, info.mergeInfo.replacementSelectionSets), new AddMergedTypeSelectionSets(info.schema, info.mergeInfo.mergedTypes), @@ -108,7 +107,7 @@ function buildDelegationTransforms( new ExpandAbstractTypes(info.schema, targetSchema), ); - if (info.mergeInfo) { + if (info.mergeInfo != null) { delegationTransforms.push( new AddReplacementFragments(targetSchema, info.mergeInfo.replacementFragments), ); @@ -136,16 +135,22 @@ export function delegateRequest({ 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; - rootValue = rootValue || subschemaConfig.rootValue || info.rootValue; - transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse()); + targetRootValue = rootValue != null ? + rootValue : + (subschemaConfig.rootValue != null ? subschemaConfig.rootValue : info.rootValue); + if (subschemaConfig.transforms != null) { + requestTransforms = requestTransforms.concat(subschemaConfig.transforms) + } } else { targetSchema = subschemaOrSubschemaConfig; - rootValue = rootValue || info.rootValue; + targetRootValue = rootValue != null ? rootValue : info.rootValue; } const delegationTransforms = buildDelegationTransforms( @@ -155,140 +160,143 @@ export function delegateRequest({ targetSchema, fieldName, returnType, - transforms, + requestTransforms.reverse(), skipTypeMerging, ); - request = applyRequestTransforms(request, delegationTransforms); + const processedRequest = applyRequestTransforms(request, delegationTransforms); if (!skipValidation) { - const errors = validate(targetSchema, request.document); + const errors = validate(targetSchema, processedRequest.document); if (errors.length > 0) { - throw errors; + const combinedError: Error = combineErrors(errors); + throw combinedError; } } if (operation === 'query' || operation === 'mutation') { - const executor = createExecutor(targetSchema, rootValue, subschemaConfig); + const executor = createExecutor(targetSchema, targetRootValue, context, subschemaConfig); const executionResult: ExecutionResult | Promise = executor({ - document: request.document, + document: processedRequest.document, context, - variables: request.variables + variables: processedRequest.variables }); if (executionResult instanceof Promise) { return executionResult.then((originalResult: any) => applyResultTransforms(originalResult, delegationTransforms)); - } else { - return applyResultTransforms(executionResult, delegationTransforms); } + return applyResultTransforms(executionResult, delegationTransforms); - } else if (operation === 'subscription') { + } - const subscriber = createSubscriber(targetSchema, rootValue, subschemaConfig); + const subscriber = createSubscriber(targetSchema, targetRootValue, context, subschemaConfig); - return subscriber({ - document: request.document, - context, - variables: request.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, - }; - }); - } else { - return applyResultTransforms(subscriptionResult, delegationTransforms); - } - }); + 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, - subschemaConfig?: SubschemaConfig + context: Record, + subschemaConfig?: SubschemaConfig, ): Delegator { let fetcher: Fetcher; - if (subschemaConfig) { - if (subschemaConfig.dispatcher) { + 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) { + } else if (subschemaConfig.link != null) { fetcher = linkToFetcher(subschemaConfig.link); - } else if (subschemaConfig.fetcher) { + } else if (subschemaConfig.fetcher != null) { fetcher = subschemaConfig.fetcher; } - if (!fetcher && !rootValue && subschemaConfig.rootValue) { - rootValue = subschemaConfig.rootValue; + if (!fetcher && !rootValue && subschemaConfig.rootValue != null) { + targetRootValue = subschemaConfig.rootValue; } } - if (fetcher) { - return ({ document, context, variables }) => fetcher({ + if (fetcher != null) { + return ({ document, context: graphqlContext, variables }) => fetcher({ query: document, variables, - context: { graphqlContext: context } - }); - } else { - return ({ document, context, variables }) => execute({ - schema, - document, - rootValue, - contextValue: context, - variableValues: 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) { - if (subschemaConfig.dispatcher) { + if (subschemaConfig != null) { + if (subschemaConfig.dispatcher != null) { link = subschemaConfig.dispatcher(context) as ApolloLink; - } else if (subschemaConfig.link) { + } else if (subschemaConfig.link != null) { link = subschemaConfig.link; } - if (!link && !rootValue && subschemaConfig.rootValue) { - rootValue = subschemaConfig.rootValue; + if (!link && !rootValue && subschemaConfig.rootValue != null) { + targetRootValue = subschemaConfig.rootValue; } } - if (link) { - return ({ document, context, variables }) => { + if (link != null) { + return ({ document, context: graphqlContext, variables }) => { const operation = { query: document, variables, - context: { graphqlContext: context } + context: { graphqlContext } }; const observable = executeLink(link, operation); return observableToAsyncIterable(observable); }; - } else { - return ({ document, context, variables }) => subscribe({ - schema, - document, - rootValue, - contextValue: context, - variableValues: variables, - }); - } + } + + return ({ document, context: graphqlContext, variables }) => subscribe({ + schema, + document, + rootValue: targetRootValue, + contextValue: graphqlContext, + variableValues: variables, + }); } diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 51bf72c06b8..403ce9d5662 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -14,17 +14,28 @@ export function relocatedError( (originalError as GraphQLError).nodes, (originalError as GraphQLError).source, (originalError as GraphQLError).positions, - path ? path : (originalError as GraphQLError).path, + path != null ? path : (originalError as GraphQLError).path, (originalError as GraphQLError).originalError, (originalError as GraphQLError).extensions ); } + if (originalError == null) { + return new GraphQLError( + undefined, + nodes, + undefined, + undefined, + path, + originalError, + ); + } + return new GraphQLError( - originalError && originalError.message, - (originalError && (originalError as any).nodes) || nodes, - originalError && (originalError as any).source, - originalError && (originalError as any).positions, + originalError.message, + ((originalError as GraphQLError).nodes != null) ? (originalError as GraphQLError).nodes : nodes, + (originalError as GraphQLError).source, + (originalError as GraphQLError).positions, path, originalError, ); @@ -34,7 +45,7 @@ export function slicedError(originalError: GraphQLError) { return relocatedError( originalError, originalError.nodes, - originalError.path ? originalError.path.slice(1) : undefined + (originalError.path != null) ? originalError.path.slice(1) : undefined ); } @@ -48,7 +59,7 @@ export function getErrorsByPathSegment(errors: ReadonlyArray): Rec const pathSegment = error.path[1]; - const current = record[pathSegment] || []; + const current = (record[pathSegment] != null) ? record[pathSegment] : []; current.push(slicedError(error)); record[pathSegment] = current; }); @@ -75,7 +86,7 @@ export function combineErrors(errors: ReadonlyArray): GraphQLError errors[0].originalError, errors[0].extensions ); - } else { - return new CombinedError(errors.map(error => error.message).join('\n'), errors); } + + return new CombinedError(errors.map(error => error.message).join('\n'), errors); } diff --git a/src/stitching/getResponseKeyFromInfo.ts b/src/stitching/getResponseKeyFromInfo.ts index d342a4ca56b..4f1277a0a7f 100644 --- a/src/stitching/getResponseKeyFromInfo.ts +++ b/src/stitching/getResponseKeyFromInfo.ts @@ -6,5 +6,5 @@ import { GraphQLResolveInfo } from 'graphql'; * @param info The info argument to the resolver. */ export function getResponseKeyFromInfo(info: GraphQLResolveInfo) { - return info.fieldNodes[0].alias ? info.fieldNodes[0].alias.value : info.fieldName; + return info.fieldNodes[0].alias != null ? info.fieldNodes[0].alias.value : info.fieldName; } diff --git a/src/stitching/introspectSchema.ts b/src/stitching/introspectSchema.ts index 0f12bc4da4e..e35c5955ab1 100644 --- a/src/stitching/introspectSchema.ts +++ b/src/stitching/introspectSchema.ts @@ -1,35 +1,46 @@ -import { GraphQLSchema, DocumentNode } from 'graphql'; -import { buildClientSchema, parse } from 'graphql'; -import { getIntrospectionQuery } from 'graphql/utilities'; import { ApolloLink } from 'apollo-link'; +import { + GraphQLSchema, + DocumentNode, + getIntrospectionQuery, + buildClientSchema, + parse, +} from 'graphql'; + import { Fetcher } from '../Interfaces'; + +import { combineErrors } from './errors'; import linkToFetcher from './linkToFetcher'; const parsedIntrospectionQuery: DocumentNode = parse(getIntrospectionQuery()); -export default async function introspectSchema( - fetcher: ApolloLink | Fetcher, +export default function introspectSchema( + linkOrFetcher: ApolloLink | Fetcher, linkContext?: { [key: string]: any }, ): Promise { - // Convert link to fetcher - if ((fetcher as ApolloLink).request) { - fetcher = linkToFetcher(fetcher as ApolloLink); - } + const fetcher = linkOrFetcher instanceof ApolloLink ? + linkToFetcher(linkOrFetcher) : + linkOrFetcher; - const introspectionResult = await (fetcher as Fetcher)({ + return fetcher({ query: parsedIntrospectionQuery, context: linkContext, + }).then(introspectionResult => { + if ( + (Array.isArray(introspectionResult.errors) && introspectionResult.errors.length) || + !introspectionResult.data.__schema + ) { + if (Array.isArray(introspectionResult.errors)) { + const combinedError: Error = combineErrors(introspectionResult.errors); + throw combinedError; + } else { + throw new Error('Could not obtain introspection result, received: ' + JSON.stringify(introspectionResult)); + } + } else { + const schema = buildClientSchema(introspectionResult.data as { + __schema: any; + }); + return schema; + } }); - - if ( - (introspectionResult.errors && introspectionResult.errors.length) || - !introspectionResult.data.__schema - ) { - throw introspectionResult.errors; - } else { - const schema = buildClientSchema(introspectionResult.data as { - __schema: any; - }); - return schema; - } } diff --git a/src/stitching/linkToFetcher.ts b/src/stitching/linkToFetcher.ts index 98361b4f7ee..4d253f9fc8c 100644 --- a/src/stitching/linkToFetcher.ts +++ b/src/stitching/linkToFetcher.ts @@ -1,16 +1,15 @@ import { Fetcher, IFetcherOperation } from '../Interfaces'; import { - ApolloLink, // This import doesn't actually import code - only the types. - makePromise, + ApolloLink, + toPromise, execute, - GraphQLRequest, + ExecutionResult, } from 'apollo-link'; export { execute } from 'apollo-link'; export default function linkToFetcher(link: ApolloLink): Fetcher { - return (fetcherOperation: IFetcherOperation) => { - return makePromise(execute(link, fetcherOperation as GraphQLRequest)); - }; + return (fetcherOperation: IFetcherOperation): Promise => + toPromise(execute(link, fetcherOperation)); } diff --git a/src/stitching/makeMergedType.ts b/src/stitching/makeMergedType.ts index e659a739004..769fefc10c8 100644 --- a/src/stitching/makeMergedType.ts +++ b/src/stitching/makeMergedType.ts @@ -1,11 +1,12 @@ +import defaultMergedResolver from './defaultMergedResolver'; +import resolveFromParentTypename from './resolveFromParentTypename'; + import { GraphQLType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, } from 'graphql'; -import defaultMergedResolver from './defaultMergedResolver'; -import resolveFromParentTypename from './resolveFromParentTypename'; export function makeMergedType(type: GraphQLType): void { if (type instanceof GraphQLObjectType) { diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 001d03903bf..9c3edbc5f0f 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -1,7 +1,15 @@ -// This import doesn't actually import code - only the types. -// Don't use ApolloLink to actually construct a link here. -import { ApolloLink } from 'apollo-link'; +import { addResolversToSchema } from '../generate'; +import { Fetcher, Operation } from '../Interfaces'; +import { cloneSchema } from '../utils'; + +import linkToFetcher, { execute } from './linkToFetcher'; +import { addTypenameToAbstract } from './addTypenameToAbstract'; +import { checkResultAndHandleErrors } from './checkResultAndHandleErrors'; +import { observableToAsyncIterable } from './observableToAsyncIterable'; +import mapAsyncIterator from './mapAsyncIterator'; +import { stripResolvers, generateProxyingResolvers } from './resolvers'; +import { ApolloLink } from 'apollo-link'; import { GraphQLFieldResolver, GraphQLSchema, @@ -11,16 +19,7 @@ import { BuildSchemaOptions, DocumentNode, } from 'graphql'; -import linkToFetcher, { execute } from './linkToFetcher'; -import { Fetcher, Operation } from '../Interfaces'; -import { addTypenameToAbstract } from './addTypenameToAbstract'; -import { checkResultAndHandleErrors } from './checkResultAndHandleErrors'; -import { observableToAsyncIterable } from './observableToAsyncIterable'; -import mapAsyncIterator from './mapAsyncIterator'; import { Options as PrintSchemaOptions } from 'graphql/utilities/schemaPrinter'; -import { cloneSchema } from '../utils'; -import { stripResolvers, generateProxyingResolvers } from './resolvers'; -import { addResolversToSchema } from '../generate'; export type ResolverFn = ( rootValue?: any, @@ -30,12 +29,11 @@ export type ResolverFn = ( ) => AsyncIterator; export default function makeRemoteExecutableSchema({ - schema: targetSchema, + schema: schemaOrTypeDefs, link, fetcher, createResolver: customCreateResolver = createResolver, buildSchemaOptions, - printSchemaOptions = { commentDescriptions: true } }: { schema: GraphQLSchema | string; link?: ApolloLink; @@ -44,31 +42,36 @@ export default function makeRemoteExecutableSchema({ buildSchemaOptions?: BuildSchemaOptions; printSchemaOptions?: PrintSchemaOptions; }): GraphQLSchema { - if (!fetcher && link) { - fetcher = linkToFetcher(link); - } + let finalFetcher: Fetcher = fetcher; - if (typeof targetSchema === 'string') { - targetSchema = buildSchema(targetSchema, buildSchemaOptions); + if (finalFetcher == null && link != null) { + finalFetcher = linkToFetcher(link); } + const targetSchema = typeof schemaOrTypeDefs === 'string' ? + buildSchema(schemaOrTypeDefs, buildSchemaOptions) : + schemaOrTypeDefs; + const remoteSchema = cloneSchema(targetSchema); stripResolvers(remoteSchema); - function createProxyingResolver( - schema: GraphQLSchema, - operation: Operation, - ): GraphQLFieldResolver { + function createProxyingResolver({ + operation, + }: { + operation: Operation; + }): GraphQLFieldResolver { if (operation === 'query' || operation === 'mutation') { - return customCreateResolver(fetcher); - } else { - return createSubscriptionResolver(link); + return customCreateResolver(finalFetcher); } + return createSubscriptionResolver(link); } addResolversToSchema({ schema: remoteSchema, - resolvers: generateProxyingResolvers({ schema: remoteSchema }, createProxyingResolver), + resolvers: generateProxyingResolvers({ + subschemaConfig: { schema: remoteSchema }, + createProxyingResolver, + }), resolverValidationOptions: { allowResolversNotInSchema: true, }, @@ -78,7 +81,7 @@ export default function makeRemoteExecutableSchema({ } export function createResolver(fetcher: Fetcher): GraphQLFieldResolver { - return async (root, args, context, info) => { + return async (_root, _args, context, info) => { const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]); let query: DocumentNode = { kind: Kind.DOCUMENT, @@ -97,7 +100,7 @@ export function createResolver(fetcher: Fetcher): GraphQLFieldResolver } function createSubscriptionResolver(link: ApolloLink): ResolverFn { - return (root, args, context, info) => { + return (_root, _args, context, info) => { const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]); let query: DocumentNode = { kind: Kind.DOCUMENT, diff --git a/src/stitching/mergeFields.ts b/src/stitching/mergeFields.ts index e155963f993..08f09039faa 100644 --- a/src/stitching/mergeFields.ts +++ b/src/stitching/mergeFields.ts @@ -1,18 +1,19 @@ -import { - FieldNode, - SelectionNode, - Kind, -} from 'graphql'; import { SubschemaConfig, IGraphQLToolsResolveInfo, MergedTypeInfo, } from '../Interfaces'; + import { mergeProxiedResults } from './proxiedResult'; +import { + FieldNode, + SelectionNode, + Kind, +} from 'graphql'; + function buildDelegationPlan( mergedTypeInfo: MergedTypeInfo, - typeName: string, originalSelections: Array, sourceSubschemas: Array, targetSubschemas: Array, @@ -51,10 +52,10 @@ function buildDelegationPlan( // 2a. use uniqueFields map to assign fields to subschema if one of possible subschemas const uniqueSubschema: SubschemaConfig = uniqueFields[selection.name.value]; - if (uniqueSubschema) { + if (uniqueSubschema != null) { if (proxiableSubschemas.includes(uniqueSubschema)) { const existingSubschema = delegationMap.get(uniqueSubschema); - if (existingSubschema) { + if (existingSubschema != null) { existingSubschema.push(selection); } else { delegationMap.set(uniqueSubschema, [selection]); @@ -70,10 +71,10 @@ function buildDelegationPlan( let nonUniqueSubschemas: Array = nonUniqueFields[selection.name.value]; nonUniqueSubschemas = nonUniqueSubschemas.filter(s => proxiableSubschemas.includes(s)); - if (nonUniqueSubschemas) { + if (nonUniqueSubschemas != null) { const subschemas: Array = Array.from(delegationMap.keys()); const existingSubschema = nonUniqueSubschemas.find(s => subschemas.includes(s)); - if (existingSubschema) { + if (existingSubschema != null) { delegationMap.get(existingSubschema).push(selection); } else { delegationMap.set(nonUniqueSubschemas[0], [selection]); @@ -112,7 +113,7 @@ export function mergeFields( unproxiableSelections, proxiableSubschemas, nonProxiableSubschemas, - } = buildDelegationPlan(mergedTypeInfo, typeName, originalSelections, sourceSubschemas, targetSubschemas); + } = buildDelegationPlan(mergedTypeInfo, originalSelections, sourceSubschemas, targetSubschemas); if (!delegationMap.size) { return object; diff --git a/src/stitching/mergeInfo.ts b/src/stitching/mergeInfo.ts index 8a0cd84cb8d..215d6551eae 100644 --- a/src/stitching/mergeInfo.ts +++ b/src/stitching/mergeInfo.ts @@ -1,12 +1,3 @@ -import { - GraphQLNamedType, - GraphQLObjectType, - GraphQLScalarType, - GraphQLSchema, - Kind, - SelectionNode, - SelectionSetNode, -} from 'graphql'; import { IDelegateToSchemaOptions, MergeInfo, @@ -16,7 +7,6 @@ import { IGraphQLToolsResolveInfo, MergedTypeInfo, } from '../Interfaces'; -import delegateToSchema from './delegateToSchema'; import { Transform, ExpandAbstractTypes, @@ -28,6 +18,18 @@ import { typeContainsSelectionSet, parseSelectionSet, } from '../utils'; + +import delegateToSchema from './delegateToSchema'; + +import { + GraphQLNamedType, + GraphQLObjectType, + GraphQLScalarType, + GraphQLSchema, + Kind, + SelectionNode, + SelectionSetNode, +} from 'graphql'; import { TypeMap } from 'graphql/type/schema'; type MergeTypeCandidate = { @@ -50,12 +52,8 @@ export function createMergeInfo( args: { [key: string]: any }, context: { [key: string]: any }, info: IGraphQLToolsResolveInfo, - transforms?: Array, + transforms: Array = [], ) { - console.warn( - '`mergeInfo.delegate` is deprecated. ' + - 'Use `mergeInfo.delegateToSchema and pass explicit schema instances.', - ); const schema = guessSchemaByRootField(allSchemas, operation, fieldName); const expandTransforms = new ExpandAbstractTypes(info.schema, schema); const fragmentTransform = new AddReplacementFragments(schema, info.mergeInfo.replacementFragments); @@ -67,7 +65,7 @@ export function createMergeInfo( context, info, transforms: [ - ...(transforms || []), + ...transforms, expandTransforms, fragmentTransform, ], @@ -98,21 +96,21 @@ function createMergedTypes( if (typeCandidates[typeName][0].type instanceof GraphQLObjectType) { const mergedTypeCandidates = typeCandidates[typeName] .filter(typeCandidate => - typeCandidate.subschema && + typeCandidate.subschema != null && isSubschemaConfig(typeCandidate.subschema) && - typeCandidate.subschema.merge && - typeCandidate.subschema.merge[typeName] + typeCandidate.subschema.merge != null && + typeCandidate.subschema.merge[typeName] != null ); if ( mergeTypes === true || (typeof mergeTypes === 'function') && mergeTypes(typeName, typeCandidates[typeName]) || - Array.isArray(mergeTypes) && mergeTypes.includes(typeName) || + (Array.isArray(mergeTypes) && mergeTypes.includes(typeName)) || mergedTypeCandidates.length ) { const subschemas: Array = []; - let requiredSelections: Array = [parseSelectionSet(`{ __typename }`).selections[0]]; + let requiredSelections: Array = [parseSelectionSet('{ __typename }').selections[0]]; const fields = Object.create({}); const typeMaps: Map = new Map(); const selectionSets: Map = new Map(); @@ -124,7 +122,9 @@ function createMergedTypes( const type = transformedSubschema.getType(typeName) as GraphQLObjectType; const fieldMap = type.getFields(); Object.keys(fieldMap).forEach(fieldName => { - fields[fieldName] = fields[fieldName] || []; + if (fields[fieldName] == null) { + fields[fieldName] = []; + } fields[fieldName].push(subschemaConfig); }); @@ -162,11 +162,11 @@ function createMergedTypes( }; subschemas.forEach(subschema => { - const type = typeMaps.get(subschema)[typeName] as GraphQLObjectType; - let subschemaMap = new Map(); + const type = typeMaps.get(subschema)[typeName] as GraphQLObjectType; + const subschemaMap = new Map(); subschemas.filter(s => s !== subschema).forEach(s => { const selectionSet = selectionSets.get(s); - if (selectionSet && typeContainsSelectionSet(type, selectionSet)) { + if (selectionSet != null && typeContainsSelectionSet(type, selectionSet)) { subschemaMap.set(selectionSet, true); } }); @@ -209,11 +209,15 @@ export function completeMergeInfo( const field = type[fieldName]; if (field.selectionSet) { const selectionSet = parseSelectionSet(field.selectionSet); - replacementSelectionSets[typeName] = replacementSelectionSets[typeName] || {}; - replacementSelectionSets[typeName][fieldName] = replacementSelectionSets[typeName][fieldName] || { - kind: Kind.SELECTION_SET, - selections: [], - }; + if (replacementSelectionSets[typeName] == null) { + replacementSelectionSets[typeName] = {}; + } + if (replacementSelectionSets[typeName][fieldName] == null) { + replacementSelectionSets[typeName][fieldName] = { + kind: Kind.SELECTION_SET, + selections: [], + }; + } replacementSelectionSets[typeName][fieldName].selections = replacementSelectionSets[typeName][fieldName].selections.concat(selectionSet.selections); } @@ -230,15 +234,21 @@ export function completeMergeInfo( mergeInfo.fragments.forEach(({ field, fragment }) => { const parsedFragment = parseFragmentToInlineFragment(fragment); const actualTypeName = parsedFragment.typeCondition.name.value; - mapping[actualTypeName] = mapping[actualTypeName] || {}; - mapping[actualTypeName][field] = mapping[actualTypeName][field] || []; + if (mapping[actualTypeName] == null) { + mapping[actualTypeName] = {}; + } + if (mapping[actualTypeName][field] == null) { + mapping[actualTypeName][field] = []; + } mapping[actualTypeName][field].push(parsedFragment); }); const replacementFragments = Object.create(null); Object.keys(mapping).forEach(typeName => { Object.keys(mapping[typeName]).forEach(field => { - replacementFragments[typeName] = mapping[typeName] || {}; + if (replacementFragments[typeName] == null) { + replacementFragments[typeName] = {}; + } replacementFragments[typeName][field] = concatInlineFragments( typeName, mapping[typeName][field], @@ -260,9 +270,9 @@ function operationToRootType( return schema.getSubscriptionType(); } else if (operation === 'mutation') { return schema.getMutationType(); - } else { - return schema.getQueryType(); } + + return schema.getQueryType(); } function guessSchemaByRootField( @@ -271,10 +281,10 @@ function guessSchemaByRootField( fieldName: string, ): GraphQLSchema { for (const schema of schemas) { - let rootObject = operationToRootType(operation, schema); - if (rootObject) { + const rootObject = operationToRootType(operation, schema); + if (rootObject != null) { const fields = rootObject.getFields(); - if (fields[fieldName]) { + if (fields[fieldName] != null) { return schema; } } diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index d3f18552f83..f2b004c4b75 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -1,19 +1,3 @@ -import { - DocumentNode, - GraphQLNamedType, - GraphQLObjectType, - GraphQLScalarType, - GraphQLSchema, - extendSchema, - getNamedType, - isNamedType, - parse, - Kind, - GraphQLDirective, - GraphQLInterfaceType, - GraphQLUnionType, - GraphQLEnumType, -} from 'graphql'; import { OnTypeConflict, IResolversParameter, @@ -26,7 +10,6 @@ import { extractExtensionDefinitions, addResolversToSchema, } from '../makeExecutableSchema'; -import typeFromAST from './typeFromAST'; import { wrapSchema } from '../transforms'; import { SchemaDirectiveVisitor, @@ -36,8 +19,28 @@ import { forEachField, mergeDeep, } from '../utils'; + +import typeFromAST from './typeFromAST'; import { createMergeInfo, completeMergeInfo } from './mergeInfo'; +import { + DocumentNode, + GraphQLNamedType, + GraphQLObjectType, + GraphQLScalarType, + GraphQLSchema, + extendSchema, + getNamedType, + isNamedType, + parse, + Kind, + GraphQLDirective, + GraphQLInterfaceType, + GraphQLUnionType, + GraphQLEnumType, + ASTNode, +} from 'graphql'; + type MergeTypeCandidate = { type: GraphQLNamedType; schema?: GraphQLSchema; @@ -83,7 +86,7 @@ export default function mergeSchemas({ if (typeDefs) { schemas.push(typeDefs); } - if (types) { + if (types != null) { schemas.push(types); } schemas = [...schemas, ...schemaLikeObjects]; @@ -138,15 +141,15 @@ export default function mergeSchemas({ }); } else if ( typeof schemaLikeObject === 'string' || - (schemaLikeObject && (schemaLikeObject as DocumentNode).kind === Kind.DOCUMENT) + (schemaLikeObject != null && (schemaLikeObject as ASTNode).kind === Kind.DOCUMENT) ) { - let parsedSchemaDocument = + const parsedSchemaDocument = typeof schemaLikeObject === 'string' ? parse(schemaLikeObject) : (schemaLikeObject as DocumentNode); parsedSchemaDocument.definitions.forEach(def => { const type = typeFromAST(def); if (type instanceof GraphQLDirective && mergeDirectives) { directives.push(type); - } else if (type && !(type instanceof GraphQLDirective)) { + } else if (type != null && !(type instanceof GraphQLDirective)) { addTypeCandidate(typeCandidates, type.name, { type, }); @@ -166,35 +169,30 @@ export default function mergeSchemas({ }); }); } else { - throw new Error(`Invalid schema passed`); + throw new Error('Invalid schema passed'); } }); let mergeInfo = createMergeInfo(allSchemas, typeCandidates, mergeTypes); + let finalResolvers: IResolvers; if (typeof resolvers === 'function') { - console.warn( - 'Passing functions as resolver parameter is deprecated. Use `info.mergeInfo` instead.', - ); - resolvers = resolvers(mergeInfo) || {}; + finalResolvers = resolvers(mergeInfo); } else if (Array.isArray(resolvers)) { - resolvers = resolvers.reduce((left, right) => { - if (typeof right === 'function') { - console.warn( - 'Passing functions as resolver parameter is deprecated. Use `info.mergeInfo` instead.', - ); - right = right(mergeInfo); - } - return mergeDeep(left, right); - }, {}) || {}; - if (!resolvers) { - resolvers = {}; - } else if (Array.isArray(resolvers)) { - resolvers = resolvers.reduce(mergeDeep, {}); + finalResolvers = resolvers.reduce((left, right) => + mergeDeep(left, (typeof right === 'function') ? right(mergeInfo) : right), {}); + if (Array.isArray(resolvers)) { + finalResolvers = resolvers.reduce(mergeDeep, {}); } + } else { + finalResolvers = resolvers; } - mergeInfo = completeMergeInfo(mergeInfo, resolvers); + if (finalResolvers == null) { + finalResolvers = {}; + } + + mergeInfo = completeMergeInfo(mergeInfo, finalResolvers); Object.keys(typeCandidates).forEach(typeName => { if ( @@ -204,13 +202,13 @@ export default function mergeSchemas({ typeName === 'Subscription' || (mergeTypes === true && !(typeCandidates[typeName][0].type instanceof GraphQLScalarType)) || (typeof mergeTypes === 'function') && mergeTypes(typeName, typeCandidates[typeName]) || - Array.isArray(mergeTypes) && mergeTypes.includes(typeName) || - mergeInfo.mergedTypes[typeName] + (Array.isArray(mergeTypes) && mergeTypes.includes(typeName)) || + mergeInfo.mergedTypes[typeName] != null ) ) { typeMap[typeName] = merge(typeName, typeCandidates[typeName]); } else { - const candidateSelector = onTypeConflict ? + const candidateSelector = onTypeConflict != null ? onTypeConflictToCandidateSelector(onTypeConflict) : (cands: Array) => cands[cands.length - 1]; typeMap[typeName] = candidateSelector(typeCandidates[typeName]).type; @@ -237,19 +235,19 @@ export default function mergeSchemas({ addResolversToSchema({ schema: mergedSchema, - resolvers: resolvers as IResolvers, + resolvers: finalResolvers, inheritResolversFromInterfaces }); forEachField(mergedSchema, field => { - if (field.resolve) { + if (field.resolve != null) { const fieldResolver = field.resolve; field.resolve = (parent, args, context, info) => { const newInfo = { ...info, mergeInfo }; return fieldResolver(parent, args, context, newInfo); }; } - if (field.subscribe) { + if (field.subscribe != null) { const fieldResolver = field.subscribe; field.subscribe = (parent, args, context, info) => { const newInfo = { ...info, mergeInfo }; @@ -258,7 +256,7 @@ export default function mergeSchemas({ } }); - if (schemaDirectives) { + if (schemaDirectives != null) { SchemaDirectiveVisitor.visitSchemaDirectives( mergedSchema, schemaDirectives, @@ -296,12 +294,11 @@ function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): Cand return prev; } else if (next.type === type) { return next; - } else { - return { - schemaName: 'unknown', - type - }; } + return { + schemaName: 'unknown', + type + }; }); } @@ -319,7 +316,7 @@ function merge(typeName: string, candidates: Array): GraphQL }), {}), interfaces: candidates.reduce((acc, candidate) => { const interfaces = (candidate.type as GraphQLObjectType).toConfig().interfaces; - return interfaces ? acc.concat(interfaces) : acc; + return (interfaces != null) ? acc.concat(interfaces) : acc; }, []), }); } else if (initialCandidateType instanceof GraphQLInterfaceType) { @@ -334,7 +331,7 @@ function merge(typeName: string, candidates: Array): GraphQL return new GraphQLUnionType({ name: typeName, types: candidates.reduce( - (acc, candidate) => (candidate.type as GraphQLUnionType).toConfig().types, + (acc, candidate) => acc.concat((candidate.type as GraphQLUnionType).toConfig().types), [], ), }); diff --git a/src/stitching/observableToAsyncIterable.ts b/src/stitching/observableToAsyncIterable.ts index e57633695b9..f181d1a91ec 100644 --- a/src/stitching/observableToAsyncIterable.ts +++ b/src/stitching/observableToAsyncIterable.ts @@ -1,12 +1,13 @@ import { Observable } from 'apollo-link'; import { $$asyncIterator } from 'iterall'; + type Callback = (value?: any) => any; export function observableToAsyncIterable(observable: Observable): AsyncIterator & { [$$asyncIterator]: () => AsyncIterator, } { - const pullQueue: Callback[] = []; - const pushQueue: any[] = []; + const pullQueue: Array = []; + const pushQueue: Array = []; let listening = true; @@ -26,20 +27,18 @@ export function observableToAsyncIterable(observable: Observable): AsyncIt } }; - const pullValue = () => { - return new Promise(resolve => { - if (pushQueue.length !== 0) { - const element = pushQueue.shift(); - // either {value: {errors: [...]}} or {value: ...} - resolve({ - ...element, - done: false, - }); - } else { - pullQueue.push(resolve); - } - }); - }; + const pullValue = () => new Promise(resolve => { + if (pushQueue.length !== 0) { + const element = pushQueue.shift(); + // either {value: {errors: [...]}} or {value: ...} + resolve({ + ...element, + done: false, + }); + } else { + pullQueue.push(resolve); + } + }); const subscription = observable.subscribe({ next(value: any) { @@ -61,7 +60,7 @@ export function observableToAsyncIterable(observable: Observable): AsyncIt }; return { - async next() { + next() { return listening ? pullValue() : this.return(); }, return() { diff --git a/src/stitching/proxiedResult.ts b/src/stitching/proxiedResult.ts index a6e5dbd8bce..b8267984dc5 100644 --- a/src/stitching/proxiedResult.ts +++ b/src/stitching/proxiedResult.ts @@ -1,31 +1,26 @@ +import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; +import { mergeDeep } from '../utils'; + +import { handleNull } from './checkResultAndHandleErrors'; +import { relocatedError } from './errors'; + import { GraphQLError, GraphQLSchema, responsePathAsArray, } from 'graphql'; -import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; -import { handleNull } from './checkResultAndHandleErrors'; -import { relocatedError } from './errors'; -import { mergeDeep } from '../utils'; -export let OBJECT_SUBSCHEMA_SYMBOL: any; -export let FIELD_SUBSCHEMA_MAP_SYMBOL: any; -export let ERROR_SYMBOL: any; -if ( +const hasSymbol = (typeof global !== 'undefined' && 'Symbol' in global) || - (typeof window !== 'undefined' && 'Symbol' in window) -) { - OBJECT_SUBSCHEMA_SYMBOL = Symbol('initialSubschema'); - FIELD_SUBSCHEMA_MAP_SYMBOL = Symbol('subschemaMap'); - ERROR_SYMBOL = Symbol('subschemaErrors'); -} else { - OBJECT_SUBSCHEMA_SYMBOL = Symbol('@@__initialSubschema'); - FIELD_SUBSCHEMA_MAP_SYMBOL = Symbol('@@__subschemaMap'); - ERROR_SYMBOL = '@@__subschemaErrors'; -} + // eslint-disable-next-line no-undef + (typeof window !== 'undefined' && 'Symbol' in window); + +export const OBJECT_SUBSCHEMA_SYMBOL = hasSymbol ? Symbol('initialSubschema') : '@@__initialSubschema'; +export const FIELD_SUBSCHEMA_MAP_SYMBOL = hasSymbol ? Symbol('subschemaMap') : '@@__subschemaMap'; +export const ERROR_SYMBOL = hasSymbol ? Symbol('subschemaErrors') : '@@__subschemaErrors'; export function isProxiedResult(result: any) { - return result && result[ERROR_SYMBOL]; + return (result != null) ? result[ERROR_SYMBOL] : result; } export function getSubschema(result: any, responseKey: string): GraphQLSchema | SubschemaConfig { @@ -45,7 +40,7 @@ export function getErrors( result: any, pathSegment: string ): Array { - const errors = result && result[ERROR_SYMBOL]; + const errors = (result != null) ? result[ERROR_SYMBOL] : result; if (!Array.isArray(errors)) { return null; @@ -67,30 +62,30 @@ export function unwrapResult( info: IGraphQLToolsResolveInfo, path: Array, ): any { + + let newParent: any = parent; const pathLength = path.length; for (let i = 0; i < pathLength; i++) { const responseKey = path[i]; - const errors = getErrors(parent, responseKey); - const subschema = getSubschema(parent, responseKey); + const errors = getErrors(newParent, responseKey); + const subschema = getSubschema(newParent, responseKey); - const object = parent[responseKey]; + const object = newParent[responseKey]; if (object == null) { return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors); } - setErrors(object, errors.map(error => { - return relocatedError( - error, - error.nodes, - error.path ? error.path.slice(1) : undefined - ); - })); + setErrors(object, errors.map(error => relocatedError( + error, + error.nodes, + error.path != null ? error.path.slice(1) : undefined + ))); setObjectSubschema(object, subschema); - parent = object; + newParent = object; } - return parent; + return newParent; } export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): any { @@ -108,14 +103,14 @@ export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): any }); result[ERROR_SYMBOL] = parent[ERROR_SYMBOL].map((error: GraphQLError) => { - if (error.path) { - let path = error.path.slice(); + if (error.path != null) { + const path = error.path.slice(); const pathSegment = path.shift(); const expandedPathSegment: Array = (pathSegment as string).split(delimeter); return relocatedError(error, error.nodes, expandedPathSegment.concat(path)); - } else { - return error; } + + return error; }); result[OBJECT_SUBSCHEMA_SYMBOL] = parent[OBJECT_SUBSCHEMA_SYMBOL]; diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 086f0b3009e..828ade698e5 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -1,16 +1,19 @@ -import { - GraphQLSchema, - GraphQLFieldResolver, - GraphQLObjectType, -} from 'graphql'; import { IResolvers, Operation, SubschemaConfig, } from '../Interfaces'; +import { Transform } from '../transforms'; + import delegateToSchema from './delegateToSchema'; import { makeMergedType } from './makeMergedType'; +import { + GraphQLSchema, + GraphQLFieldResolver, + GraphQLObjectType, +} from 'graphql'; + export type Mapping = { [typeName: string]: { [fieldName: string]: { @@ -20,14 +23,25 @@ export type Mapping = { }; }; -export function generateProxyingResolvers( - subschemaConfig: SubschemaConfig, - createProxyingResolver: ( - schema: GraphQLSchema | SubschemaConfig, - operation: Operation, - fieldName: string, - ) => GraphQLFieldResolver = defaultCreateProxyingResolver, -): IResolvers { +export function generateProxyingResolvers({ + subschemaConfig, + transforms, + createProxyingResolver = defaultCreateProxyingResolver, +}: { + subschemaConfig: SubschemaConfig; + transforms?: Array; + createProxyingResolver?: ({ + schema, + transforms, + operation, + fieldName, + }: { + schema?: GraphQLSchema | SubschemaConfig; + transforms?: Array; + operation?: Operation; + fieldName?: string; + }) => GraphQLFieldResolver; +}): IResolvers { const targetSchema = subschemaConfig.schema; const mapping = generateSimpleMapping(targetSchema); @@ -41,11 +55,12 @@ export function generateProxyingResolvers( const resolverType = to.operation === 'subscription' ? 'subscribe' : 'resolve'; result[name][from] = { - [resolverType]: createProxyingResolver( - subschemaConfig, - to.operation, - to.name, - ), + [resolverType]: createProxyingResolver({ + schema: subschemaConfig, + transforms, + operation: to.operation, + fieldName: to.name, + }), }; }); }); @@ -58,13 +73,13 @@ export function generateSimpleMapping(targetSchema: GraphQLSchema): Mapping { const subscription = targetSchema.getSubscriptionType(); const result: Mapping = {}; - if (query) { + if (query != null) { result[query.name] = generateMappingFromObjectType(query, 'query'); } - if (mutation) { + if (mutation != null) { result[mutation.name] = generateMappingFromObjectType(mutation, 'mutation'); } - if (subscription) { + if (subscription != null) { result[subscription.name] = generateMappingFromObjectType( subscription, 'subscription', @@ -94,13 +109,13 @@ export function generateMappingFromObjectType( return result; } -function defaultCreateProxyingResolver( - subschemaConfig: SubschemaConfig, - operation: Operation, - fieldName: string, -): GraphQLFieldResolver { - return (parent, args, context, info) => delegateToSchema({ - schema: subschemaConfig, +function defaultCreateProxyingResolver({ + schema, +}: { + schema: SubschemaConfig; +}): GraphQLFieldResolver { + return (_parent, _args, context, info) => delegateToSchema({ + schema, context, info, }); diff --git a/src/stitching/typeFromAST.ts b/src/stitching/typeFromAST.ts index d76f99a5d5e..0c2314e53cb 100644 --- a/src/stitching/typeFromAST.ts +++ b/src/stitching/typeFromAST.ts @@ -1,3 +1,7 @@ +import { createNamedStub } from '../utils/stub'; + +import resolveFromParentTypename from './resolveFromParentTypename'; + import { DefinitionNode, EnumTypeDefinitionNode, @@ -29,8 +33,6 @@ import { GraphQLFieldConfig, StringValueNode, } from 'graphql'; -import resolveFromParentTypename from './resolveFromParentTypename'; -import { createNamedStub } from '../utils/stub'; const backcompatOptions = { commentDescriptions: true }; @@ -150,19 +152,17 @@ function makeFields( const result: Record> = {}; nodes.forEach((node) => { const deprecatedDirective = node.directives.find( - (directive) => - directive && directive.name && directive.name.value === 'deprecated', + directive => directive.name.value === 'deprecated', ); - const deprecatedArgument = - deprecatedDirective && - deprecatedDirective.arguments && - deprecatedDirective.arguments.find( - (arg) => arg && arg.name && arg.name.value === 'reason', + + let deprecationReason; + + if (deprecatedDirective != null) { + const deprecatedArgument = deprecatedDirective.arguments.find( + arg => arg.name.value === 'reason', ); - const deprecationReason = - deprecatedArgument && - deprecatedArgument.value && - (deprecatedArgument.value as StringValueNode).value; + deprecationReason = (deprecatedArgument.value as StringValueNode).value; + } result[node.name.value] = { type: resolveType(node.type, 'object') as GraphQLObjectType, @@ -204,13 +204,13 @@ function resolveType( function makeDirective(node: DirectiveDefinitionNode): GraphQLDirective { const locations: Array = []; node.locations.forEach(location => { - if (location.value in DirectiveLocation) { - locations.push(location.value); + if (location.value in DirectiveLocation) { + locations.push(location.value as DirectiveLocationEnum); } }); return new GraphQLDirective({ name: node.name.value, - description: node.description ? node.description.value : null, + description: node.description != null ? node.description.value : null, args: makeValues(node.arguments), locations, }); diff --git a/src/test/circularSchemaA.ts b/src/test/circularSchemaA.ts index 5642b5078c8..12c1bb403f6 100644 --- a/src/test/circularSchemaA.ts +++ b/src/test/circularSchemaA.ts @@ -1,6 +1,6 @@ import TypeB from './circularSchemaB'; -export default () => [ +const TypeA = () => [ ` type TypeA { id: ID @@ -8,3 +8,5 @@ type TypeA { }`, TypeB, ]; + +export default TypeA; diff --git a/src/test/circularSchemaB.ts b/src/test/circularSchemaB.ts index 5b564266722..e417fecc203 100644 --- a/src/test/circularSchemaB.ts +++ b/src/test/circularSchemaB.ts @@ -1,6 +1,6 @@ import TypeA from './circularSchemaA'; -export default () => [ +const TypeB = () => [ ` type TypeB { id: ID @@ -8,3 +8,5 @@ type TypeB { }`, TypeA, ]; + +export default TypeB; diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index cc5051efcb7..04c41438ced 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -1,18 +1,3 @@ -/* tslint:disable:no-unused-expression */ - -import { expect } from 'chai'; -import { - graphql, - GraphQLSchema, - ExecutionResult, - subscribe, - parse, - GraphQLScalarType, - FieldNode, - printSchema, - GraphQLObjectTypeConfig, - GraphQLFieldConfig, -} from 'graphql'; import { transformSchema, filterSchema, @@ -27,14 +12,7 @@ import { FilterRootFields, FilterObjectFields, } from '../transforms'; -import { - propertySchema, - remoteBookingSchema, - subscriptionSchema, - subscriptionPubSub, - subscriptionPubSubTrigger, -} from './testingSchemas'; -import { forAwaitEach } from 'iterall'; + import { makeExecutableSchema } from '../makeExecutableSchema'; import { delegateToSchema, @@ -43,9 +21,33 @@ import { } from '../stitching'; import { SubschemaConfig } from '../Interfaces'; import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; -import { wrapFieldNode, renameFieldNode, hoistFieldNodes } from '../utils/fieldNodes'; +import { wrapFieldNode, renameFieldNode, hoistFieldNodes } from '../utils'; -let linkSchema = ` +import { + propertySchema, + remoteBookingSchema, + subscriptionSchema, + subscriptionPubSub, + subscriptionPubSubTrigger, +} from './testingSchemas'; + +import { + graphql, + GraphQLSchema, + ExecutionResult, + subscribe, + parse, + GraphQLScalarType, + FieldNode, + printSchema, + GraphQLObjectTypeConfig, + GraphQLFieldConfig, + GraphQLObjectType, +} from 'graphql'; +import { forAwaitEach } from 'iterall'; +import { expect } from 'chai'; + +const linkSchema = ` """ A new type linking the Property type. """ @@ -100,29 +102,29 @@ describe('merge schemas through transforms', () => { const propertySchemaTransforms = [ new FilterRootFields( (operation: string, rootField: string) => - 'Query.properties' === `${operation}.${rootField}` + `${operation}.${rootField}` === 'Query.properties' ), new RenameTypes((name: string) => `Properties_${name}`), - new RenameRootFields((operation: string, name: string) => `Properties_${name}`), + new RenameRootFields((_operation: string, name: string) => `Properties_${name}`), ]; const bookingSchemaTransforms = [ new FilterRootFields( (operation: string, rootField: string) => - 'Query.bookings' === `${operation}.${rootField}` + `${operation}.${rootField}` === 'Query.bookings' ), new RenameTypes((name: string) => `Bookings_${name}`), - new RenameRootFields((operation: string, name: string) => `Bookings_${name}`), + new RenameRootFields((_operation: string, name: string) => `Bookings_${name}`), ]; const subscriptionSchemaTransforms = [ new FilterRootFields( (operation: string, rootField: string) => // must include a Query type otherwise graphql will error - 'Query.notifications' === `${operation}.${rootField}` || - 'Subscription.notifications' === `${operation}.${rootField}` + `${operation}.${rootField}` === 'Query.notifications' || + `${operation}.${rootField}` === 'Subscription.notifications' ), new RenameTypes((name: string) => `Subscriptions_${name}`), new RenameRootFields( - (operation: string, name: string) => `Subscriptions_${name}`), + (_operation: string, name: string) => `Subscriptions_${name}`), ]; const propertySubschema = { @@ -148,7 +150,7 @@ describe('merge schemas through transforms', () => { resolvers: { Query: { // delegating directly, no subschemas or mergeInfo - node(parent, args, context, info) { + node: (_parent, args, context, info) => { if (args.id.startsWith('p')) { return info.mergeInfo.delegateToSchema({ schema: propertySubschema, @@ -179,44 +181,41 @@ describe('merge schemas through transforms', () => { info, transforms: [], }); - } else { - throw new Error('invalid id'); } + throw new Error('invalid id'); }, }, + // eslint-disable-next-line camelcase Properties_Property: { bookings: { fragment: 'fragment PropertyFragment on Property { id }', - resolve(parent, args, context, info) { - return delegateToSchema({ - schema: bookingSubschema, - operation: 'query', - fieldName: 'bookingsByPropertyId', - args: { - propertyId: parent.id, - limit: args.limit ? args.limit : null, - }, - context, - info, - }); - }, + resolve: (parent, args, context, info) => delegateToSchema({ + schema: bookingSubschema, + operation: 'query', + fieldName: 'bookingsByPropertyId', + args: { + propertyId: parent.id, + limit: args.limit ? args.limit : null, + }, + context, + info, + }), }, }, + // eslint-disable-next-line camelcase Bookings_Booking: { property: { fragment: 'fragment BookingFragment on Booking { propertyId }', - resolve(parent, args, context, info) { - return info.mergeInfo.delegateToSchema({ - schema: propertySubschema, - operation: 'query', - fieldName: 'propertyById', - args: { - id: parent.propertyId, - }, - context, - info, - }); - }, + resolve: (parent, _args, context, info) => info.mergeInfo.delegateToSchema({ + schema: propertySubschema, + operation: 'query', + fieldName: 'propertyById', + args: { + id: parent.propertyId, + }, + context, + info, + }), }, }, }, @@ -301,6 +300,7 @@ describe('merge schemas through transforms', () => { }; const transformedNotification = { + // eslint-disable-next-line camelcase Subscriptions_notifications: originalNotification.notifications }; @@ -320,26 +320,29 @@ describe('merge schemas through transforms', () => { (result: ExecutionResult) => { expect(result).to.have.property('data'); expect(result.data).to.deep.equal(transformedNotification); - !notificationCnt++ ? done() : null; + if (!notificationCnt++) { + return done(); + } }, ).catch(done); - }).then(() => { - subscriptionPubSub.publish(subscriptionPubSubTrigger, originalNotification); - }).catch(done); + }).then(() => + subscriptionPubSub.publish(subscriptionPubSubTrigger, originalNotification) + ).catch(done); }); }); describe('transform object fields', () => { let transformedPropertySchema: GraphQLSchema; - before(async () => { + before(() => { transformedPropertySchema = transformSchema(propertySchema, [ new TransformObjectFields( (typeName: string, fieldName: string) => { if (typeName !== 'Property' || fieldName !== 'name') { return undefined; } - const typeConfig = propertySchema.getType(typeName).toConfig() as GraphQLObjectTypeConfig; + const type = propertySchema.getType(typeName) as GraphQLObjectType; + const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; const fieldConfig = typeConfig.fields[fieldName] as GraphQLFieldConfig; fieldConfig.resolve = () => 'test'; return fieldConfig; @@ -402,6 +405,7 @@ describe('transform object fields', () => { before(() => { const ITEM = { id: '123', + // eslint-disable-next-line camelcase camel_case: "I'm a camel!", }; @@ -515,7 +519,7 @@ describe('transform object fields', () => { column: 17, line: 6, }], - message: 'Cannot query field \"id\" on type \"Item\".', + message: 'Cannot query field "id" on type "Item".', }], }); }); @@ -524,14 +528,14 @@ describe('transform object fields', () => { describe('filter and rename object fields', () => { let transformedPropertySchema: GraphQLSchema; - before(async () => { + before(() => { transformedPropertySchema = filterSchema({ schema: transformSchema(propertySchema, [ new RenameTypes((name: string) => `New_${name}`), new RenameObjectFields((typeName: string, fieldName: string) => (typeName === 'New_Property' ? `new_${fieldName}` : fieldName)) ]), rootFieldFilter: (operation: string, fieldName: string) => - 'Query.propertyById' === `${operation}.${fieldName}`, + `${operation}.${fieldName}` === 'Query.propertyById', fieldFilter: (typeName: string, fieldName: string) => (typeName === 'New_Property' || fieldName === 'name'), typeFilter: (typeName: string, type) => @@ -583,11 +587,15 @@ type Query { expect(result).to.deep.equal({ data: { propertyById: { + // eslint-disable-next-line camelcase new_id: 'p1', + // eslint-disable-next-line camelcase new_name: 'Super great hotel', + // eslint-disable-next-line camelcase new_location: { name: 'Helsinki', }, + // eslint-disable-next-line camelcase new_error: null, }, }, @@ -616,14 +624,13 @@ type Query { describe('WrapType transform', () => { let transformedPropertySchema: GraphQLSchema; - before(async () => { + before(() => { transformedPropertySchema = transformSchema(propertySchema, [ new WrapType('Query', 'Namespace_Query', 'namespace'), ]); }); it('should modify the schema', () => { - /* tslint:disable:max-line-length */ expect(printSchema(transformedPropertySchema)).to.equal(`type Address { street: String city: String @@ -701,7 +708,6 @@ type UnionImpl { someField: String } ` - /* tslint:enable:max-line-length */ ); }); @@ -762,7 +768,7 @@ type UnionImpl { describe('ExtendSchema transform', () => { let transformedPropertySchema: GraphQLSchema; - before(async () => { + before(() => { transformedPropertySchema = transformSchema(propertySchema, [ new ExtendSchema({ typeDefs: ` @@ -781,7 +787,6 @@ describe('ExtendSchema transform', () => { }); it('should work', () => { - /* tslint:disable:max-line-length */ expect(printSchema(transformedPropertySchema)).to.equal(`type Address { street: String city: String @@ -862,7 +867,6 @@ type Wrap { name: String } ` - /* tslint:enable:max-line-length */ ); }); }); @@ -1227,7 +1231,7 @@ describe('schema transformation with wrapping of object fields', () => { describe('schema transformation with renaming of object fields', () => { let transformedPropertySchema: GraphQLSchema; - before(async () => { + before(() => { transformedPropertySchema = transformSchema(propertySchema, [ new ExtendSchema({ typeDefs: ` @@ -1264,6 +1268,7 @@ describe('schema transformation with renaming of object fields', () => { expect(result).to.deep.equal({ data: { propertyById: { + // eslint-disable-next-line camelcase new_error: null, }, }, @@ -1309,7 +1314,7 @@ describe('interface resolver inheritance', () => { const resolvers = { Node: { __resolveType: ({ type }: { type: string }) => type, - id: ({ _id }: { _id: number }) => `Node:${_id}`, + id: ({ _id }: { _id: number }) => `Node:${_id.toString()}`, }, User: { name: ({ name }: { name: string}) => `User:${name}` @@ -1330,13 +1335,13 @@ describe('interface resolver inheritance', () => { resolvers, inheritResolversFromInterfaces: true }); - const query = `{ user { id name } }`; + const query = '{ user { id name } }'; const response = await graphql(mergedSchema, query); expect(response).to.deep.equal({ data: { user: { - id: `Node:1`, - name: `User:Ada` + id: 'Node:1', + name: 'User:Ada' } } }); @@ -1354,7 +1359,7 @@ async () => { resolvers, inheritResolversFromInterfaces: false }); - const query = `{ user { id name } }`; + const query = '{ user { id name } }'; const response = await graphql(mergedSchema, query); expect(response.errors.length).to.equal(1); expect(response.errors[0].message).to.equal('Cannot return null for ' + @@ -1373,7 +1378,7 @@ async () => { ], resolvers }); - const query = `{ user { id name } }`; + const query = '{ user { id name } }'; const response = await graphql(mergedSchema, query); expect(response.errors.length).to.equal(1); expect(response.errors[0].message).to.equal('Cannot return null for ' + @@ -1403,10 +1408,10 @@ describe('mergeSchemas', () => { schemas: [schema] }); - const query = `{ test { field } }`; + const query = '{ test { field } }'; const response = await graphql(mergedSchema, query); - expect(response.data.test).to.be.null; - expect(response.errors).to.be.undefined; + expect(response.data.test).to.equal(null); + expect(response.errors).to.equal(undefined); }); it('can merge default input types', async () => { @@ -1421,7 +1426,7 @@ describe('mergeSchemas', () => { `, resolvers: { Query: { - getInput: (root, args) => args.input.field + getInput: (_root, args) => args.input.field } } }); @@ -1429,11 +1434,11 @@ describe('mergeSchemas', () => { schemas: [schema] }); - const query = `{ getInput(input: {}) }`; + const query = '{ getInput(input: {}) }'; const response = await graphql(mergedSchema, query); expect(printSchema(schema)).to.equal(printSchema(mergedSchema)); - expect(response.data.getInput).to.equal('test'); + expect(response.data?.getInput).to.equal('test'); }); it('can override scalars with new internal values', async () => { @@ -1449,8 +1454,8 @@ describe('mergeSchemas', () => { name: 'TestScalar', description: undefined, serialize: value => (value as string).slice(1), - parseValue: value => `_${value}`, - parseLiteral: (ast: any) => `_${ast.value}`, + parseValue: value => `_${value as string}`, + parseLiteral: (ast: any) => `_${ast.value as string}`, }), Query: { getTestScalar: () => '_test' @@ -1464,16 +1469,16 @@ describe('mergeSchemas', () => { name: 'TestScalar', description: undefined, serialize: value => (value as string).slice(2), - parseValue: value => `__${value}`, - parseLiteral: (ast: any) => `__${ast.value}`, + parseValue: value => `__${value as string}`, + parseLiteral: (ast: any) => `__${ast.value as string}`, }) } }); - const query = `{ getTestScalar }`; + const query = '{ getTestScalar }'; const response = await graphql(mergedSchema, query); - expect(response.data.getTestScalar).to.equal('test'); + expect(response.data?.getTestScalar).to.equal('test'); }); it('can override scalars with new internal values when using default input types', async () => { @@ -1489,11 +1494,11 @@ describe('mergeSchemas', () => { name: 'TestScalar', description: undefined, serialize: value => (value as string).slice(1), - parseValue: value => `_${value}`, - parseLiteral: (ast: any) => `_${ast.value}`, + parseValue: value => `_${value as string}`, + parseLiteral: (ast: any) => `_${ast.value as string}`, }), Query: { - getTestScalar: (root, args) => '_test' + getTestScalar: () => '_test' } } }); @@ -1504,16 +1509,16 @@ describe('mergeSchemas', () => { name: 'TestScalar', description: undefined, serialize: value => (value as string).slice(2), - parseValue: value => `__${value}`, - parseLiteral: (ast: any) => `__${ast.value}`, + parseValue: value => `__${value as string}`, + parseLiteral: (ast: any) => `__${ast.value as string}`, }) } }); - const query = `{ getTestScalar }`; + const query = '{ getTestScalar }'; const response = await graphql(mergedSchema, query); - expect(response.data.getTestScalar).to.equal('test'); + expect(response.data?.getTestScalar).to.equal('test'); }); it('can use @include directives', async () => { @@ -1543,15 +1548,13 @@ describe('mergeSchemas', () => { ], resolvers: { Query: { - get2: (root, args, context, info) => { - return delegateToSchema({ - schema: schema, - operation: 'query', - fieldName: 'get1', - context, - info - }); - } + get2: (_root, _args, context, info) => delegateToSchema({ + schema, + operation: 'query', + fieldName: 'get1', + context, + info + }) } } }); @@ -1564,7 +1567,7 @@ describe('mergeSchemas', () => { } `; const response = await graphql(mergedSchema, query); - expect(response.data.get2.subfield).to.equal('test'); + expect(response.data?.get2.subfield).to.equal('test'); }); it('can use functions in subfields', async () => { @@ -1590,9 +1593,9 @@ describe('mergeSchemas', () => { } }); - const query = `{ wrappingObject { functionField } }`; + const query = '{ wrappingObject { functionField } }'; const response = await graphql(mergedSchema, query); - expect(response.data.wrappingObject.functionField).to.equal(8); + expect(response.data?.wrappingObject.functionField).to.equal(8); }); }); @@ -1654,21 +1657,21 @@ describe('onTypeConflict', () => { const mergedSchema = mergeSchemas({ schemas: [schema1, schema2] }); - const result1 = await graphql(mergedSchema, `{ test2 { fieldC } }`); - expect(result1.data.test2.fieldC).to.equal('C'); - const result2 = await graphql(mergedSchema, `{ test2 { fieldB } }`); - expect(result2.data).to.be.undefined; + const result1 = await graphql(mergedSchema, '{ test2 { fieldC } }'); + expect(result1.data?.test2.fieldC).to.equal('C'); + const result2 = await graphql(mergedSchema, '{ test2 { fieldB } }'); + expect(result2.data).to.equal(undefined); }); it('can use onTypeConflict to select last type', async () => { const mergedSchema = mergeSchemas({ schemas: [schema1, schema2], - onTypeConflict: (left, right) => right + onTypeConflict: (_left, right) => right }); - const result1 = await graphql(mergedSchema, `{ test2 { fieldC } }`); - expect(result1.data.test2.fieldC).to.equal('C'); - const result2 = await graphql(mergedSchema, `{ test2 { fieldB } }`); - expect(result2.data).to.be.undefined; + const result1 = await graphql(mergedSchema, '{ test2 { fieldC } }'); + expect(result1.data?.test2.fieldC).to.equal('C'); + const result2 = await graphql(mergedSchema, '{ test2 { fieldB } }'); + expect(result2.data).to.equal(undefined); }); it('can use onTypeConflict to select first type', async () => { @@ -1676,10 +1679,10 @@ describe('onTypeConflict', () => { schemas: [schema1, schema2], onTypeConflict: (left) => left }); - const result1 = await graphql(mergedSchema, `{ test1 { fieldB } }`); - expect(result1.data.test1.fieldB).to.equal('B'); - const result2 = await graphql(mergedSchema, `{ test1 { fieldC } }`); - expect(result2.data).to.be.undefined; + const result1 = await graphql(mergedSchema, '{ test1 { fieldB } }'); + expect(result1.data?.test1.fieldB).to.equal('B'); + const result2 = await graphql(mergedSchema, '{ test1 { fieldC } }'); + expect(result2.data).to.equal(undefined); }); }); @@ -1725,7 +1728,7 @@ describe('mergeTypes', () => { resolvers: { Query: { rootField1: () => ({ test: { id: '1' } }), - getTest: (parent, { id }) => ({ id }), + getTest: (_parent, { id }) => ({ id }), }, Test: { field1: parent => parent.id, @@ -1738,7 +1741,7 @@ describe('mergeTypes', () => { resolvers: { Query: { rootField2: () => ({ test: { id: '2' } }), - getTest: (parent, { id }) => ({ id }), + getTest: (_parent, { id }) => ({ id }), }, Test: { field2: parent => parent.id, diff --git a/src/test/testDataloader.ts b/src/test/testDataloader.ts index 7b2e90e38dc..dcb8bc58a36 100644 --- a/src/test/testDataloader.ts +++ b/src/test/testDataloader.ts @@ -1,16 +1,14 @@ -/* tslint:disable:no-unused-expression */ - -import { expect } from 'chai'; - import { makeExecutableSchema } from '../makeExecutableSchema'; import { mergeSchemas, delegateToSchema } from '../stitching'; -import { graphql, GraphQLList } from 'graphql'; -import DataLoader from 'dataloader'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; +import DataLoader from 'dataloader'; +import { graphql, GraphQLList } from 'graphql'; +import { expect } from 'chai'; + describe('dataloader', () => { it('should work', async () => { const taskSchema = makeExecutableSchema({ @@ -26,7 +24,7 @@ describe('dataloader', () => { `, resolvers: { Query: { - task: (root, { id }) => ({ id, text: `task ${id}`, userId: id }), + task: (_root, { id }) => ({ id, text: `task ${id as string}`, userId: id }), } }, }); @@ -43,9 +41,7 @@ describe('dataloader', () => { `, resolvers: { Query: { - usersByIds: (root, { ids }) => { - return ids.map((id: string) => ({ id, email: `${id}@tasks.com` })); - }, + usersByIds: (_root, { ids }) => ids.map((id: string) => ({ id, email: `${id}@tasks.com` })), } }, }); @@ -63,8 +59,8 @@ describe('dataloader', () => { resolvers: { Task: { user: { - fragment: `... on Task { userId }`, - resolve(task, args, context, info) { + fragment: '... on Task { userId }', + resolve(task, _args, context, info) { return context.usersLoader.load({ id: task.userId, info }); } } @@ -73,7 +69,7 @@ describe('dataloader', () => { }); const usersLoader = new DataLoader(async (keys: Array<{ id: any, info: IGraphQLToolsResolveInfo }>) => { - const users = delegateToSchema({ + const users = await delegateToSchema({ schema: userSchema, operation: 'query', fieldName: 'usersByIds', diff --git a/src/test/testDelegateToSchema.ts b/src/test/testDelegateToSchema.ts index 0ff3ecb9b99..d3a0e0d6229 100644 --- a/src/test/testDelegateToSchema.ts +++ b/src/test/testDelegateToSchema.ts @@ -1,19 +1,19 @@ -/* tslint:disable:no-unused-expression */ +import delegateToSchema from '../stitching/delegateToSchema'; +import mergeSchemas from '../stitching/mergeSchemas'; +import { IResolvers } from '../Interfaces'; + +import { propertySchema, bookingSchema, sampleData, Property } from './testingSchemas'; -import { expect } from 'chai'; import { GraphQLSchema, graphql } from 'graphql'; -import { propertySchema, bookingSchema, sampleData, Property } from './testingSchemas'; -import delegateToSchema from '../stitching/delegateToSchema'; -import mergeSchemas from '../stitching/mergeSchemas'; -import { IResolvers } from '../Interfaces'; +import { expect } from 'chai'; function findPropertyByLocationName ( properties: { [key: string]: Property }, name: string -): Property { +): Property | undefined { for (const key of Object.keys(properties)) { const property = properties[key]; if (property.location.name === name) { @@ -39,10 +39,10 @@ function proxyResolvers (spec: string): IResolvers { Booking: { property: { fragment: '... on Booking { propertyId }', - resolve (booking, args, context, info) { + resolve (booking, _args, context, info) { const delegateFn = spec === 'standalone' ? delegateToSchema : info.mergeInfo.delegateToSchema; - return delegateFn({ + return delegateFn?.({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', @@ -56,7 +56,7 @@ function proxyResolvers (spec: string): IResolvers { Location: { coordinates: { fragment: '... on Location { name }', - resolve (location, args, context, info) { + resolve: location => { const name = location.name; return findPropertyByLocationName(sampleData.Property, name) .location.coordinates; @@ -78,7 +78,7 @@ const proxyTypeDefs = ` describe('stitching', () => { describe('delegateToSchema', () => { ['standalone', 'info.mergeInfo'].forEach(spec => { - context(spec, () => { + describe(spec, () => { let schema: GraphQLSchema; before(() => { schema = mergeSchemas({ diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 3a190e91c57..4009eda0b44 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -1,9 +1,11 @@ -import { assert } from 'chai'; +import crypto from 'crypto'; + import { makeExecutableSchema } from '../makeExecutableSchema'; import { VisitableSchemaType } from '../Interfaces'; import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; import { SchemaVisitor } from '../utils/SchemaVisitor'; import { visitSchema } from '../utils/visitSchema'; + import { ExecutionResult, GraphQLArgument, @@ -27,7 +29,9 @@ import { GraphQLOutputType, } from 'graphql'; -import formatDate = require('dateformat'); +import { assert } from 'chai'; + +import formatDate from 'dateformat'; const typeDefs = ` directive @schemaDirective(role: String) on SCHEMA @@ -103,7 +107,7 @@ describe('@directives', () => { function checkDirectives( type: VisitableSchemaType, typeDirectiveNames: [string], - fieldDirectiveMap: { [key: string]: string[] } = {}, + fieldDirectiveMap: { [key: string]: Array } = {}, ) { assert.deepEqual( getDirectiveNames(type), @@ -120,7 +124,7 @@ describe('@directives', () => { function getDirectiveNames( type: VisitableSchemaType, - ): string[] { + ): Array { return type.astNode.directives.map(d => d.name.value); } @@ -144,13 +148,13 @@ describe('@directives', () => { ['enumValueDirective'], ); - checkDirectives(schema.getType('Date'), ['dateDirective']); + checkDirectives(schema.getType('Date') as GraphQLObjectType, ['dateDirective']); - checkDirectives(schema.getType('Named'), ['interfaceDirective'], { + checkDirectives(schema.getType('Named') as GraphQLObjectType, ['interfaceDirective'], { name: ['interfaceFieldDirective'], }); - checkDirectives(schema.getType('PersonInput'), ['inputTypeDirective'], { + checkDirectives(schema.getType('PersonInput') as GraphQLObjectType, ['inputTypeDirective'], { name: ['inputFieldDirective'], gender: [], }); @@ -219,7 +223,7 @@ describe('@directives', () => { }); it('can visit the schema itself', () => { - const visited: GraphQLSchema[] = []; + const visited: Array = []; const schema = makeExecutableSchema({ typeDefs }); SchemaDirectiveVisitor.visitSchemaDirectives(schema, { schemaDirective: class extends SchemaDirectiveVisitor { @@ -315,9 +319,8 @@ describe('@directives', () => { it('can check if a visitor method is implemented', () => { class Visitor extends SchemaVisitor { - public notVisitorMethod() { - return; // Just to keep the tslint:no-empty rule satisfied. - } + // eslint-disable-next-line @typescript-eslint/no-empty-function + public notVisitorMethod() {} public visitObject(object: GraphQLObjectType) { return object; @@ -348,7 +351,7 @@ describe('@directives', () => { it('can use visitSchema for simple visitor patterns', () => { class SimpleVisitor extends SchemaVisitor { public visitCount = 0; - public names: string[] = []; + public names: Array = []; constructor(s: GraphQLSchema) { super(); @@ -371,7 +374,7 @@ describe('@directives', () => { const schema = makeExecutableSchema({ typeDefs }); const visitor = new SimpleVisitor(schema); visitor.visit(); - assert.deepEqual(visitor.names.sort(), [ + assert.deepEqual(visitor.names.sort((a, b) => a.localeCompare(b)), [ 'Mutation', 'Person', 'Query', @@ -387,7 +390,8 @@ describe('@directives', () => { // Pretend this class implements all visitor methods. This is safe // because the SchemaVisitor base class provides empty stubs for all // the visitor methods that might be called. - return methodNamesEncountered[name] = true; + methodNamesEncountered[name] = true; + return methodNamesEncountered[name]; } } @@ -411,10 +415,10 @@ describe('@directives', () => { }); assert.deepEqual( - Object.keys(methodNamesEncountered).sort(), + Object.keys(methodNamesEncountered).sort((a, b) => a.localeCompare(b)), Object.keys(SchemaVisitor.prototype) .filter(name => name.startsWith('visit')) - .sort() + .sort((a, b) => a.localeCompare(b)) ); }); @@ -461,11 +465,12 @@ describe('@directives', () => { arg.defaultValue = 3; return true; } + return false; }); return prev; } - public visitObject(object: GraphQLObjectType) { + public visitObject() { ++this.context.objectCount; assert.strictEqual(this.args.times, 3); } @@ -487,9 +492,7 @@ describe('@directives', () => { assert.deepEqual(Object.keys(visitors), ['oyez']); assert.deepEqual( - visitors.oyez.map(v => { - return (v.visitedType as GraphQLObjectType | GraphQLField).name; - }), + visitors.oyez.map(v => (v.visitedType as GraphQLObjectType | GraphQLField).name), ['Courtroom', 'judge', 'marshall'], ); }); @@ -506,7 +509,7 @@ describe('@directives', () => { upper: class extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const { resolve = defaultFieldResolver } = field; - field.resolve = async function (...args: any[]) { + field.resolve = async function (...args) { const result = await resolve.apply(this, args); if (typeof result === 'string') { return result.toUpperCase(); @@ -553,7 +556,7 @@ describe('@directives', () => { const { resolve = defaultFieldResolver } = field; const { format } = this.args; field.type = GraphQLString; - field.resolve = async function (...args: any[]) { + field.resolve = async function (...args) { const date = await resolve.apply(this, args); return formatDate(date, format, true); }; @@ -594,9 +597,9 @@ describe('@directives', () => { field.type = GraphQLString; field.resolve = async function (source, { format, ...args }, context, info) { - format = format || defaultFormat; + const newFormat = format || defaultFormat; const date = await resolve.call(this, source, args, context, info); - return formatDate(date, format, true); + return formatDate(date, newFormat, true); }; } } @@ -626,9 +629,9 @@ describe('@directives', () => { } }); - const resultNoArg = await graphql(schema, `query { today }`); + const resultNoArg = await graphql(schema, 'query { today }'); - if (resultNoArg.errors) { + if (resultNoArg.errors != null) { assert.deepEqual(resultNoArg.errors, []); } @@ -642,7 +645,7 @@ describe('@directives', () => { today(format: "dd mmm yyyy") }`); - if (resultWithArg.errors) { + if (resultWithArg.errors != null) { assert.deepEqual(resultWithArg.errors, []); } @@ -655,7 +658,7 @@ describe('@directives', () => { it('can be used to implement the @intl example', () => { function translate( text: string, - path: string[], + path: Array, locale: string, ) { assert.strictEqual(text, 'hello'); @@ -682,7 +685,7 @@ describe('@directives', () => { objectType: GraphQLObjectType, }) { const { resolve = defaultFieldResolver } = field; - field.resolve = async function (...args: any[]) { + field.resolve = async function (...args: Array) { const defaultText = await resolve.apply(this, args); // In this example, path would be ["Query", "greeting"]: const path = [details.objectType.name, field.name]; @@ -736,6 +739,7 @@ describe('@directives', () => { this.ensureFieldsWrapped(type); (type as any)._requiredAuthRole = this.args.requires; } + // Visitor methods for nested types like fields and arguments // also receive a details object that provides information about // the parent and grandparent types. @@ -759,7 +763,7 @@ describe('@directives', () => { Object.keys(fields).forEach(fieldName => { const field = fields[fieldName]; const { resolve = defaultFieldResolver } = field; - field.resolve = async function (...args: any[]) { + field.resolve = function (...args: Array) { // Get the required Role from the field first, falling back // to the objectType if no Role is required by the field: const requiredRole = @@ -771,7 +775,7 @@ describe('@directives', () => { } const context = args[2]; - const user = await getUser(context.headers.authToken); + const user = getUser(context.headers.authToken); if (! user.hasRole(requiredRole)) { throw new Error('not authorized'); } @@ -840,18 +844,18 @@ describe('@directives', () => { function checkErrors( expectedCount: number, - ...expectedNames: string[] + ...expectedNames: Array ) { return function ({ errors = [], data }: { - errors: any[], + errors: Array, data: any, }) { assert.strictEqual(errors.length, expectedCount); assert(errors.every(error => error.message === 'not authorized')); const actualNames = errors.map(error => error.path.slice(-1)[0]); assert.deepEqual( - expectedNames.sort(), - actualNames.sort(), + expectedNames.sort((a, b) => a.localeCompare(b)), + actualNames.sort((a, b) => a.localeCompare(b)), ); return data; }; @@ -874,13 +878,13 @@ describe('@directives', () => { class LimitedLengthType extends GraphQLScalarType { constructor(type: GraphQLScalarType, maxLength: number) { super({ - name: `LengthAtMost${maxLength}`, + name: `LengthAtMost${maxLength.toString()}`, serialize(value: string) { - value = type.serialize(value); - assert.strictEqual(typeof value.length, 'number'); - assert.isAtMost(value.length, maxLength); - return value; + const newValue = type.serialize(value); + assert.strictEqual(typeof newValue.length, 'number'); + assert.isAtMost(newValue.length, maxLength); + return newValue; }, parseValue(value: string) { @@ -932,7 +936,7 @@ describe('@directives', () => { } else if (field.type instanceof GraphQLScalarType) { field.type = new LimitedLengthType(field.type, this.args.max); } else { - throw new Error(`Not a scalar type: ${field.type}`); + throw new Error(`Not a scalar type: ${field.type.toString()}`); } } } @@ -947,7 +951,7 @@ describe('@directives', () => { } }, Mutation: { - createBook(parent, args) { + createBook(_parent, args) { return args.book; } } @@ -975,7 +979,7 @@ describe('@directives', () => { } `); - if (result.errors) { + if (result.errors != null) { assert.deepEqual(result.errors, []); } @@ -1011,12 +1015,12 @@ describe('@directives', () => { public visitObject(type: GraphQLObjectType) { const { name, from } = this.args; type.getFields()[name] = Object.create({ - name: name, + name, type: GraphQLID, description: 'Unique ID', args: [], resolve(object: any) { - const hash = require('crypto').createHash('sha1'); + const hash = crypto.createHash('sha1'); hash.update(type.name); from.forEach((fieldName: string) => { hash.update(String(object[fieldName])); @@ -1030,13 +1034,13 @@ describe('@directives', () => { resolvers: { Query: { - people(...args: any[]) { + people() { return [{ personID: 1, name: 'Ben', }]; }, - locations(...args: any[]) { + locations() { return [{ locationID: 1, address: '140 10th St', @@ -1108,6 +1112,7 @@ describe('@directives', () => { assert.strictEqual(type, schema.getType('Human')); return true; } + return false; }); assert.strictEqual(found, true); @@ -1135,7 +1140,7 @@ describe('@directives', () => { schemaDirectives: { remove: class extends SchemaDirectiveVisitor { - public visitEnumValue(value: GraphQLEnumValue): null { + public visitEnumValue(): null { if (this.args.if) { return null; } @@ -1239,7 +1244,7 @@ describe('@directives', () => { const { resolve = defaultFieldResolver } = field; const newField = {...field}; - newField.resolve = async function(...args: any[]) { + newField.resolve = async function(...args: Array) { const result = await resolve.apply(this, args); if (typeof result === 'string') { return result.toUpperCase(); @@ -1253,7 +1258,7 @@ describe('@directives', () => { reverse: class extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const { resolve = defaultFieldResolver } = field; - field.resolve = async function(...args: any[]) { + field.resolve = async function(...args: Array) { const result = await resolve.apply(this, args); if (typeof result === 'string') { return result diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index e38707bc4e2..8c95eb25c5e 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -1,14 +1,13 @@ -import { expect, assert } from 'chai'; -import { GraphQLError, graphql } from 'graphql'; import { relocatedError } from '../stitching/errors'; import { getErrors, ERROR_SYMBOL } from '../stitching/proxiedResult'; import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; - -import 'mocha'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { mergeSchemas } from '../stitching'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; +import { expect, assert } from 'chai'; +import { GraphQLError, graphql } from 'graphql'; + class ErrorWithExtensions extends GraphQLError { constructor(message: string, code: string) { super(message, null, null, null, null, null, { code }); @@ -34,13 +33,12 @@ describe('Errors', () => { describe('getErrors', () => { it('should return all errors including if path is not defined', () => { - const mockErrors = { + const error = { + message: 'Test error without path' + }; + const mockErrors: any = { responseKey: '', - [ERROR_SYMBOL]: [ - { - message: 'Test error without path' - } as GraphQLError - ] + [ERROR_SYMBOL]: [error], }; assert.deepEqual(getErrors(mockErrors, 'responseKey'), @@ -55,6 +53,7 @@ describe('Errors', () => { errors: [new GraphQLError('Test error')] }; try { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions checkResultAndHandleErrors(result, {}, {} as IGraphQLToolsResolveInfo, 'responseKey'); } catch (e) { assert.equal(e.message, 'Test error'); @@ -67,6 +66,7 @@ describe('Errors', () => { errors: [new ErrorWithExtensions('Test error', 'UNAUTHENTICATED')] }; try { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions checkResultAndHandleErrors(result, {}, {} as IGraphQLToolsResolveInfo, 'responseKey'); } catch (e) { assert.equal(e.message, 'Test error'); @@ -83,6 +83,7 @@ describe('Errors', () => { ] }; try { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions checkResultAndHandleErrors(result, {}, {} as IGraphQLToolsResolveInfo, 'responseKey'); } catch (e) { assert.equal(e.message, 'Error1\nError2'); @@ -125,7 +126,7 @@ describe('passes along errors for missing fields on list', () => { const mergedSchema = mergeSchemas({ schemas: [schema] }); - const result = await graphql(mergedSchema, `{ getOuter { innerList { mandatoryField } } }`); + const result = await graphql(mergedSchema, '{ getOuter { innerList { mandatoryField } } }'); expect(result).to.deep.equal({ data: { getOuter: null, @@ -173,7 +174,7 @@ describe('passes along errors for missing fields on list', () => { const mergedSchema = mergeSchemas({ schemas: [schema] }); - const result = await graphql(mergedSchema, `{ getOuter { innerList { mandatoryField } } }`); + const result = await graphql(mergedSchema, '{ getOuter { innerList { mandatoryField } } }'); expect(result).to.deep.equal({ data: { getOuter: { @@ -225,7 +226,7 @@ describe('passes along errors when list field errors', () => { const mergedSchema = mergeSchemas({ schemas: [schema] }); - const result = await graphql(mergedSchema, `{ getOuter { innerList { mandatoryField } } }`); + const result = await graphql(mergedSchema, '{ getOuter { innerList { mandatoryField } } }'); expect(result).to.deep.equal({ data: { getOuter: null, @@ -272,7 +273,7 @@ describe('passes along errors when list field errors', () => { const mergedSchema = mergeSchemas({ schemas: [schema] }); - const result = await graphql(mergedSchema, `{ getOuter { innerList { mandatoryField } } }`); + const result = await graphql(mergedSchema, '{ getOuter { innerList { mandatoryField } } }'); expect(result).to.deep.equal({ data: { getOuter: { diff --git a/src/test/testExtensionExtraction.ts b/src/test/testExtensionExtraction.ts index 89320c85231..a7621874604 100644 --- a/src/test/testExtensionExtraction.ts +++ b/src/test/testExtensionExtraction.ts @@ -1,7 +1,7 @@ +import extractExtensionDefinitons from '../generate/extractExtensionDefinitions'; + import { expect } from 'chai'; import { parse } from 'graphql'; -import extractExtensionDefinitons from '../generate/extractExtensionDefinitions'; -import 'mocha'; describe('Extension extraction', () => { it('extracts extended inputs', () => { diff --git a/src/test/testFragmentsAreNotDuplicated.ts b/src/test/testFragmentsAreNotDuplicated.ts index c7f02b8f40f..409faccdd29 100644 --- a/src/test/testFragmentsAreNotDuplicated.ts +++ b/src/test/testFragmentsAreNotDuplicated.ts @@ -83,7 +83,7 @@ const variables = { function assertNoDuplicateFragmentErrors(result: ExecutionResult) { // Run assertion against each array element for better test failure output. - if (result.errors) { + if (result.errors != null) { result.errors.forEach(error => expect(error.message).to.equal('')); } } diff --git a/src/test/testLogger.ts b/src/test/testLogger.ts index e226e62b1e9..5668b10136e 100644 --- a/src/test/testLogger.ts +++ b/src/test/testLogger.ts @@ -1,11 +1,11 @@ -import { assert } from 'chai'; -import { graphql } from 'graphql'; import { Logger } from '../Logger'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import 'mocha'; + +import { assert } from 'chai'; +import { graphql } from 'graphql'; describe('Logger', () => { - it('logs the errors', done => { + it('logs the errors', () => { const shorthand = ` type RootQuery { just_a_field: Int @@ -39,15 +39,14 @@ describe('Logger', () => { const testQuery = 'mutation { species, stuff }'; const expected0 = 'Error in resolver RootMutation.species\noops!'; const expected1 = 'Error in resolver RootMutation.stuff\noh noes!'; - graphql(jsSchema, testQuery).then(() => { + return graphql(jsSchema, testQuery).then(() => { assert.equal(logger.errors.length, 2); assert.equal(logger.errors[0].message, expected0); assert.equal(logger.errors[1].message, expected1); - done(); }); }); - it('also forwards the errors when you tell it to', done => { + it('also forwards the errors when you tell it to', () => { const shorthand = ` type RootQuery { species(name: String): String @@ -73,13 +72,12 @@ describe('Logger', () => { logger, }); const testQuery = '{ species }'; - graphql(jsSchema, testQuery).then(() => { + return graphql(jsSchema, testQuery).then(() => { assert.equal(loggedErr, logger.errors[0]); - done(); }); }); - it('prints the errors when you want it to', done => { + it('prints the errors when you want it to', () => { const shorthand = ` type RootQuery { species(name: String): String @@ -90,7 +88,7 @@ describe('Logger', () => { `; const resolve = { RootQuery: { - species: (root: any, { name }: { name: string }) => { + species: (_root: any, { name }: { name: string }) => { if (name) { throw new Error(name); } @@ -105,15 +103,14 @@ describe('Logger', () => { logger, }); const testQuery = '{ q: species, p: species(name: "Peter") }'; - graphql(jsSchema, testQuery).then(() => { + return graphql(jsSchema, testQuery).then(() => { const allErrors = logger.printAllErrors(); assert.match(allErrors, /oops/); assert.match(allErrors, /Peter/); - done(); }); }); - it('logs any Promise reject errors', done => { + it('logs any Promise reject errors', () => { const shorthand = ` type RootQuery { just_a_field: Int @@ -129,16 +126,12 @@ describe('Logger', () => { `; const resolve = { RootMutation: { - species: () => { - return new Promise((_, reject) => { - reject(new Error('oops!')); - }); - }, - stuff: () => { - return new Promise((_, reject) => { - reject(new Error('oh noes!')); - }); - }, + species: () => new Promise((_, reject) => { + reject(new Error('oops!')); + }), + stuff: () => new Promise((_, reject) => { + reject(new Error('oh noes!')); + }), }, }; const logger = new Logger(); @@ -151,15 +144,14 @@ describe('Logger', () => { const testQuery = 'mutation { species, stuff }'; const expected0 = 'Error in resolver RootMutation.species\noops!'; const expected1 = 'Error in resolver RootMutation.stuff\noh noes!'; - graphql(jsSchema, testQuery).then(() => { + return graphql(jsSchema, testQuery).then(() => { assert.equal(logger.errors.length, 2); assert.equal(logger.errors[0].message, expected0); assert.equal(logger.errors[1].message, expected1); - done(); }); }); - it('all Promise rejects will log an Error', done => { + it('all Promise rejects will log an Error', () => { const shorthand = ` type RootQuery { species(name: String): String @@ -170,11 +162,9 @@ describe('Logger', () => { `; const resolve = { RootQuery: { - species: () => { - return new Promise((_, reject) => { - reject('oops!'); - }); - }, + species: () => new Promise((_, reject) => { + reject(new Error('oops!')); + }), }, }; @@ -189,9 +179,8 @@ describe('Logger', () => { }); const testQuery = '{ species }'; - graphql(jsSchema, testQuery).then(() => { + return graphql(jsSchema, testQuery).then(() => { assert.equal(loggedErr, logger.errors[0]); - done(); }); }); }); diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index 79fe440cd6b..8a63e866162 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -1,8 +1,3 @@ -/* tslint:disable:no-unused-expression */ - -import { expect } from 'chai'; -import { forAwaitEach } from 'iterall'; -import { GraphQLSchema, ExecutionResult, subscribe, parse, graphql } from 'graphql'; import { propertySchema, subscriptionSchema, @@ -12,6 +7,16 @@ import { } from '../test/testingSchemas'; import { makeRemoteExecutableSchema } from '../stitching'; +import { expect } from 'chai'; +import { forAwaitEach } from 'iterall'; +import { + GraphQLSchema, + ExecutionResult, + subscribe, + parse, + graphql, +} from 'graphql'; + describe('remote queries', () => { let schema: GraphQLSchema; before(async () => { @@ -83,11 +88,11 @@ describe('remote subscriptions', () => { forAwaitEach(results as AsyncIterable, (result: ExecutionResult) => { expect(result).to.have.property('data'); expect(result.data).to.deep.equal(mockNotification); - !notificationCnt++ ? done() : null; - }); - }).then(() => { - subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification); - }); + if (!notificationCnt++) { + done(); + } + }).catch(done); + }).then(() => subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification)).catch(done); }); it('should work without triggering multiple times per notification', done => { @@ -111,25 +116,25 @@ describe('remote subscriptions', () => { expect(result).to.have.property('data'); expect(result.data).to.deep.equal(mockNotification); notificationCnt++; - }); + }).catch(done); }); const sub2 = subscribe(schema, subscription).then(results => { forAwaitEach(results as AsyncIterable, (result: ExecutionResult) => { expect(result).to.have.property('data'); expect(result.data).to.deep.equal(mockNotification); - }); + }).catch(done); }); Promise.all([sub1, sub2]).then(() => { - subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification); - subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification); + subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification).catch(done); + subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification).catch(done); setTimeout(() => { expect(notificationCnt).to.eq(2); done(); }, 0); - }); + }).catch(done); }); }); diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index f39970cb786..39863e45440 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -1,5 +1,27 @@ -/* tslint:disable:no-unused-expression */ +import mergeSchemas from '../stitching/mergeSchemas'; +import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { + IResolvers, + SubschemaConfig, +} from '../Interfaces'; +import { delegateToSchema } from '../stitching'; +import { cloneSchema } from '../utils'; +import { getResolversFromSchema } from '../utils/getResolversFromSchema'; +import { + propertySchema as localPropertySchema, + productSchema as localProductSchema, + bookingSchema as localBookingSchema, + subscriptionSchema as localSubscriptionSchema, + remoteBookingSchema, + remotePropertySchema, + remoteProductSchema, + subscriptionPubSub, + subscriptionPubSubTrigger, +} from './testingSchemas'; + +import { forAwaitEach } from 'iterall'; import { expect } from 'chai'; import { graphql, @@ -14,29 +36,8 @@ import { findDeprecatedUsages, printSchema, } from 'graphql'; -import mergeSchemas from '../stitching/mergeSchemas'; -import { - propertySchema as localPropertySchema, - productSchema as localProductSchema, - bookingSchema as localBookingSchema, - subscriptionSchema as localSubscriptionSchema, - remoteBookingSchema, - remotePropertySchema, - remoteProductSchema, - subscriptionPubSub, - subscriptionPubSubTrigger, -} from './testingSchemas'; -import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; -import { forAwaitEach } from 'iterall'; -import { makeExecutableSchema } from '../makeExecutableSchema'; -import { - IResolvers, - SubschemaConfig, -} from '../Interfaces'; -import { delegateToSchema } from '../stitching'; -import { cloneSchema } from '../utils'; -import { getResolversFromSchema } from '../utils/getResolversFromSchema'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars const removeLocations = ({ locations, ...rest }: any): any => ({ ...rest }); const testCombinations = [ @@ -69,7 +70,7 @@ const testCombinations = [ } ]; -let scalarTest = ` +const scalarTest = ` """ Description of TestScalar. """ @@ -93,25 +94,23 @@ let scalarTest = ` } `; -let scalarSchema: GraphQLSchema; - -scalarSchema = makeExecutableSchema({ +const scalarSchema = makeExecutableSchema({ typeDefs: scalarTest, resolvers: { TestScalar: new GraphQLScalarType({ name: 'TestScalar', description: undefined, serialize: value => (value as string).slice(1), - parseValue: value => `_${value}`, - parseLiteral: (ast: any) => `_${ast.value}`, + parseValue: value => `_${value as string}`, + parseLiteral: (ast: any) => `_${ast.value as string}`, }), Query: { - testingScalar(parent, args) { + testingScalar(_parent, args) { return { value: args.input[0] === '_' ? args.input : null }; }, - listTestingScalar(parent, args) { + listTestingScalar(_parent, args) { return [{ value: args.input[0] === '_' ? args.input : null }]; @@ -120,7 +119,7 @@ scalarSchema = makeExecutableSchema({ }, }); -let enumTest = ` +const enumTest = ` """ A type that uses an Enum. """ @@ -161,9 +160,7 @@ let enumTest = ` } `; -let enumSchema: GraphQLSchema; - -enumSchema = makeExecutableSchema({ +const enumSchema = makeExecutableSchema({ typeDefs: enumTest, resolvers: { Color: { @@ -176,7 +173,7 @@ enumSchema = makeExecutableSchema({ __resolveType: () => 'EnumWrapper' }, Query: { - color(parent, args) { + color(_parent, args) { return args.input === '#EA3232' ? args.input : null; }, numericEnum() { @@ -201,7 +198,7 @@ enumSchema = makeExecutableSchema({ }, }); -let linkSchema = ` +const linkSchema = ` """ A new type linking the Property type. """ @@ -315,7 +312,7 @@ const codeCoverageTypeDefs = ` } `; -let schemaDirectiveTypeDefs = ` +const schemaDirectiveTypeDefs = ` directive @upper on FIELD_DEFINITION directive @withEnumArg(enumArg: DirectiveEnum = FOO) on FIELD_DEFINITION @@ -332,12 +329,12 @@ let schemaDirectiveTypeDefs = ` } `; -testCombinations.forEach(async combination => { +testCombinations.forEach(combination => { describe('merging ' + combination.name, () => { - let mergedSchema: GraphQLSchema, - propertySchema: GraphQLSchema | SubschemaConfig, - productSchema: GraphQLSchema | SubschemaConfig, - bookingSchema: GraphQLSchema | SubschemaConfig; + let mergedSchema: GraphQLSchema; + let propertySchema: GraphQLSchema | SubschemaConfig; + let productSchema: GraphQLSchema | SubschemaConfig; + let bookingSchema: GraphQLSchema | SubschemaConfig; before(async () => { propertySchema = await combination.property; @@ -362,7 +359,7 @@ testCombinations.forEach(async combination => { upper: class extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const { resolve = defaultFieldResolver } = field; - field.resolve = async function(...args: any[]) { + field.resolve = async function(...args) { const result = await resolve.apply(this, args); if (typeof result === 'string') { return result.toUpperCase(); @@ -391,19 +388,19 @@ testCombinations.forEach(async combination => { context, info, ); - } else { - return delegateToSchema({ - schema: bookingSchema, - operation: 'query', - fieldName: 'bookingsByPropertyId', - args: { - propertyId: parent.id, - limit: args.limit ? args.limit : null, - }, - context, - info, - }); } + + return delegateToSchema({ + schema: bookingSchema, + operation: 'query', + fieldName: 'bookingsByPropertyId', + args: { + propertyId: parent.id, + limit: args.limit ? args.limit : null, + }, + context, + info, + }); }, }, someField: { @@ -415,7 +412,7 @@ testCombinations.forEach(async combination => { Booking: { property: { fragment: 'fragment BookingFragment on Booking { propertyId }', - resolve(parent, args, context, info) { + resolve(parent, _args, context, info) { return delegateToSchema({ schema: propertySchema, operation: 'query', @@ -430,8 +427,8 @@ testCombinations.forEach(async combination => { }, textDescription: { fragment: '... on Booking { id }', - resolve(parent, args, context, info) { - return `Booking #${parent.id}`; + resolve(parent, _args, _context, _info) { + return `Booking #${parent.id as string}`; }, }, }, @@ -442,7 +439,7 @@ testCombinations.forEach(async combination => { }, LinkType: { property: { - resolve(parent, args, context, info) { + resolve(_parent, _args, context, info) { return delegateToSchema({ schema: propertySchema, operation: 'query', @@ -462,7 +459,7 @@ testCombinations.forEach(async combination => { serialize: value => value, }), Query: { - delegateInterfaceTest(parent, args, context, info) { + delegateInterfaceTest(_parent, _args, context, info) { return delegateToSchema({ schema: propertySchema, operation: 'query', @@ -474,7 +471,7 @@ testCombinations.forEach(async combination => { info, }); }, - delegateArgumentTest(parent, args, context, info) { + delegateArgumentTest(_parent, _args, context, info) { return delegateToSchema({ schema: propertySchema, operation: 'query', @@ -494,7 +491,7 @@ testCombinations.forEach(async combination => { node: { // fragment doesn't work fragment: '... on Node { id }', - resolve(parent, args, context, info) { + resolve(_parent, args, context, info) { if (args.id.startsWith('p')) { return delegateToSchema({ schema: propertySchema, @@ -522,12 +519,12 @@ testCombinations.forEach(async combination => { context, info, }); - } else { - throw new Error('invalid id'); } + + throw new Error('invalid id'); }, }, - async nodes(parent, args, context, info) { + async nodes(_parent, _args, context, info) { const bookings = await delegateToSchema({ schema: bookingSchema, operation: 'query', @@ -880,12 +877,14 @@ bookingById(id: "b1") { (result: ExecutionResult) => { expect(result).to.have.property('data'); expect(result.data).to.deep.equal(mockNotification); - !notificationCnt++ ? done() : null; + if (!notificationCnt++) { + done(); + } }, ).catch(done); - }).then(() => { - subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification); - }).catch(done); + }) + .then(() => subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification)) + .catch(done); }); it('subscription errors are working correctly in merged schema', done => { @@ -936,12 +935,14 @@ bookingById(id: "b1") { expect(result.errors).to.have.lengthOf(1); expect(result.errors).to.deep.equal(expectedResult.errors); expect(result.data).to.deep.equal(expectedResult.data); - !notificationCnt++ ? done() : null; + if (!notificationCnt++) { + done(); + } }, ).catch(done); - }).then(() => { - subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification); - }).catch(done); + }) + .then(() => subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification)) + .catch(done); }); it('links in queries', async () => { @@ -1436,7 +1437,7 @@ bookingById(id: "b1") { Booking: { property: { fragment: 'fragment BookingFragment on Booking { propertyId }', - resolve(parent, args, context, info) { + resolve(parent, _args, context, info) { return delegateToSchema({ schema: propertySchema, operation: 'query', @@ -1463,7 +1464,7 @@ bookingById(id: "b1") { }; const Query2: IResolvers = { Query: { - delegateInterfaceTest(parent, args, context, info) { + delegateInterfaceTest(_parent, _args, context, info) { return delegateToSchema({ schema: propertySchema, operation: 'query', @@ -1475,7 +1476,7 @@ bookingById(id: "b1") { info, }); }, - delegateArgumentTest(parent, args, context, info) { + delegateArgumentTest(_parent, _args, context, info) { return delegateToSchema({ schema: propertySchema, operation: 'query', @@ -1495,7 +1496,7 @@ bookingById(id: "b1") { node: { // fragment doesn't work fragment: 'fragment NodeFragment on Node { id }', - resolve(parent, args, context, info) { + resolve(_parent, args, context, info) { if (args.id.startsWith('p')) { return delegateToSchema({ schema: propertySchema, @@ -1523,9 +1524,9 @@ bookingById(id: "b1") { context, info, }); - } else { - throw new Error('invalid id'); } + + throw new Error('invalid id'); }, }, }, @@ -1533,7 +1534,7 @@ bookingById(id: "b1") { const AsyncQuery: IResolvers = { Query: { - async nodes(parent, args, context, info) { + async nodes(_parent, _args, context, info) { const bookings = await delegateToSchema({ schema: bookingSchema, operation: 'query', @@ -2431,10 +2432,10 @@ fragment BookingFragment on Booking { ); [propertyResult, mergedResult].forEach((result) => { - expect(result.errors).to.exist; - expect(result.errors.length > 0).to.be.true; + expect(result.errors).to.not.equal(undefined); + expect(result.errors.length > 0).to.equal(true); const error = result.errors[0]; - expect(error.extensions).to.exist; + expect(error.extensions).to.not.equal(undefined); expect(error.extensions.code).to.equal('SOME_CUSTOM_CODE'); }); } @@ -2880,7 +2881,7 @@ fragment BookingFragment on Booking { const result = await graphql( schema, - `{ book { cat: category } }`, + '{ book { cat: category } }', ); expect(result.data.book.cat).to.equal('Test'); @@ -2888,7 +2889,7 @@ fragment BookingFragment on Booking { }); describe('deprecation', () => { - it('should retain deprecation information', async () => { + it('should retain deprecation information', () => { const typeDefs = ` type Query { book: Book @@ -2916,9 +2917,8 @@ fragment BookingFragment on Booking { }); const deprecatedUsages = findDeprecatedUsages(schema, parse(query)); - expect(deprecatedUsages).not.empty; expect(deprecatedUsages.length).to.equal(1); - expect(deprecatedUsages.find(error => Boolean(error && error.message.match(/deprecated/) && error.message.match(/yolo/)))); + expect(deprecatedUsages.find(error => (error.message.match(/deprecated/g) != null) && (error.message.match(/yolo/g) != null))).to.not.equal(undefined); }); }); }); diff --git a/src/test/testMocking.ts b/src/test/testMocking.ts index 2b09990f7c5..0d8afcd010b 100644 --- a/src/test/testMocking.ts +++ b/src/test/testMocking.ts @@ -1,12 +1,13 @@ -import { expect } from 'chai'; -import { graphql, GraphQLResolveInfo } from 'graphql'; import { addMocksToSchema, MockList, mockServer } from '../mock'; import { buildSchemaFromTypeDefinitions, addResolversToSchema, makeExecutableSchema, } from '../makeExecutableSchema'; -import 'mocha'; +import { IMocks } from '../Interfaces'; + +import { expect } from 'chai'; +import { graphql, GraphQLResolveInfo, GraphQLSchema, GraphQLFieldResolver } from 'graphql'; describe('Mock', () => { const shorthand = ` @@ -72,25 +73,25 @@ describe('Mock', () => { const resolveFunctions = { BirdsAndBees: { - __resolveType(data: any, context: any, info: GraphQLResolveInfo) { + __resolveType(data: any, _context: any, info: GraphQLResolveInfo) { return info.schema.getType(data.__typename); }, }, Flying: { - __resolveType(data: any, context: any, info: GraphQLResolveInfo) { + __resolveType(data: any, _context: any, info: GraphQLResolveInfo) { return info.schema.getType(data.__typename); }, }, }; it('throws an error if you forget to pass schema', () => { - expect(() => (addMocksToSchema)({})).to.throw( + expect(() => addMocksToSchema({})).to.throw( 'Must provide schema to mock', ); }); it('throws an error if the property "schema" on the first argument is not of type GraphQLSchema', () => { - expect(() => (addMocksToSchema)({ schema: {} })).to.throw( + expect(() => addMocksToSchema({ schema: {} as unknown as GraphQLSchema })).to.throw( 'Value at "schema" must be of type GraphQLSchema', ); }); @@ -98,7 +99,7 @@ describe('Mock', () => { it('throws an error if second argument is not a Map', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); expect(() => - (addMocksToSchema)({ schema: jsSchema, mocks: ['a'] }), + addMocksToSchema({ schema: jsSchema, mocks: ['a'] as unknown as IMocks }), ).to.throw('mocks must be of type Object'); }); @@ -106,7 +107,7 @@ describe('Mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { Int: 55 }; expect(() => - (addMocksToSchema)({ schema: jsSchema, mocks: mockMap }), + addMocksToSchema({ schema: jsSchema, mocks: mockMap as unknown as IMocks }), ).to.throw('mockFunctionMap[Int] must be a function'); }); @@ -253,7 +254,7 @@ describe('Mock', () => { let spy = 0; const resolvers = { BirdsAndBees: { - __resolveType(data: any, context: any, info: GraphQLResolveInfo) { + __resolveType(data: any, _context: any, info: GraphQLResolveInfo) { ++spy; return info.schema.getType(data.__typename); }, @@ -277,7 +278,7 @@ describe('Mock', () => { } } }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then(_res => { // the resolveType has been called twice expect(spy).to.equal(2); }); @@ -378,19 +379,18 @@ describe('Mock', () => { addResolversToSchema(jsSchema, resolveFunctions); let spy = 0; const mockMap = { - Bird: (root: any, args: any) => ({ + Bird: (_root: any, args: any) => ({ id: args.id, returnInt: 100, }), - Bee: (root: any, args: any) => ({ + Bee: (_root: any, args: any) => ({ id: args.id, returnInt: 200, }), - Flying: (root: any, args: any) => { + Flying: (_root: any, args: any) => { spy++; const { id } = args; const type = id.split(':')[0]; - // tslint:disable-next-line const __typename = ['Bird', 'Bee'].find(r => r.toLowerCase() === type); return { __typename }; }, @@ -421,15 +421,15 @@ describe('Mock', () => { addResolversToSchema(jsSchema, resolveFunctions); let spy = 0; const mockMap = { - Bird: (root: any, args: any) => ({ + Bird: (_root: any, args: any) => ({ id: args.id, returnInt: 100, }), - Bee: (root: any, args: any) => ({ + Bee: (_root: any, args: any) => ({ id: args.id, returnEnum: 'A', }), - BirdsAndBees: (root: any, args: any) => { + BirdsAndBees: (_root: any, args: any) => { spy++; const { id } = args; const type = id.split(':')[0]; @@ -465,17 +465,16 @@ describe('Mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); addResolversToSchema(jsSchema, resolveFunctions); const mockMap = { - Bird: (root: any, args: any) => ({ + Bird: (_root: any, args: any) => ({ id: args.id, returnInt: 100, }), - Bee: (root: any, args: any) => ({ + Bee: (_root: any, args: any) => ({ id: args.id, returnInt: 100, }), - Flying: (root: any, args: any): void => { - return; - }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + Flying: (_root: any, _args: any): void => {}, }; addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ @@ -486,7 +485,7 @@ describe('Mock', () => { }`; const expected = 'Please return a __typename in "Flying"'; return graphql(jsSchema, testQuery).then(res => { - expect((res.errors[0]).originalError.message).to.equal(expected); + expect(res.errors[0].originalError.message).to.equal(expected); }); }); @@ -499,7 +498,7 @@ describe('Mock', () => { }`; const expected = 'No mock defined for type "MissingMockType"'; return graphql(jsSchema, testQuery).then(res => { - expect((res.errors[0]).originalError.message).to.equal(expected); + expect(res.errors[0].originalError.message).to.equal(expected); }); }); @@ -512,7 +511,7 @@ describe('Mock', () => { __parseLiteral: (val: string) => val, }, RootQuery: { - returnMockError: () => undefined, + returnMockError: (): string => undefined, }, }; addResolversToSchema(jsSchema, resolvers); @@ -528,7 +527,7 @@ describe('Mock', () => { }`; const expected = 'No mock defined for type "MissingMockType"'; return graphql(jsSchema, testQuery).then(res => { - expect((res.errors[0]).originalError.message).to.equal(expected); + expect(res.errors[0].originalError.message).to.equal(expected); }); }); @@ -718,9 +717,9 @@ describe('Mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { RootQuery: () => ({ - returnInt: (root: any, args: { [key: string]: any }) => 42, // a) in resolvers, will not be used - returnFloat: (root: any, args: { [key: string]: any }) => 1.3, // b) not in resolvers, will be used - returnString: (root: any, args: { [key: string]: any }) => + returnInt: (_root: any, _args: { [key: string]: any }) => 42, // a) in resolvers, will not be used + returnFloat: (_root: any, _args: { [key: string]: any }) => 1.3, // b) not in resolvers, will be used + returnString: (_root: any, _args: { [key: string]: any }) => Promise.resolve('foo'), // c) in resolvers, will not be used }), }; @@ -1067,7 +1066,7 @@ describe('Mock', () => { returnInt: 666, // from the mock, see b) returnString: 'Hello World', // from mock default values. }, - returnString: null as string, /// from the mock, see a) + returnString: null as string, // from the mock, see a) }; return graphql(jsSchema, testQuery, undefined, {}).then(res => { expect(res.data).to.deep.equal(expected); @@ -1078,7 +1077,7 @@ describe('Mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { RootQuery: () => ({ - returnStringArgument: (o: any, a: { [key: string]: any }) => a['s'], + returnStringArgument: (_o: any, a: { [key: string]: any }) => a['s'], }), }; addMocksToSchema({ schema: jsSchema, mocks: mockMap }); @@ -1097,7 +1096,7 @@ describe('Mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { RootMutation: () => ({ - returnStringArgument: (o: any, a: { [key: string]: any }) => a['s'], + returnStringArgument: (_o: any, a: { [key: string]: any }) => a['s'], }), }; addMocksToSchema({ schema: jsSchema, mocks: mockMap }); @@ -1150,7 +1149,7 @@ describe('Mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { RootQuery: () => ({ - returnListOfIntArg: (o: any, a: { [key: string]: any }) => + returnListOfIntArg: (_o: any, a: { [key: string]: any }) => new MockList(a['l']), }), Int: () => 12, @@ -1187,7 +1186,7 @@ describe('Mock', () => { }); it('throws an error if the second argument to MockList is not a function', () => { - expect(() => new (MockList)(5, 'abc')).to.throw( + expect(() => new MockList(5, 'abc' as unknown as GraphQLFieldResolver)).to.throw( 'Second argument to MockList must be a function or undefined', ); }); @@ -1219,7 +1218,7 @@ describe('Mock', () => { returnListOfListOfIntArg: () => new MockList( 2, - (o: any, a: { [key: string]: any }) => new MockList(a['l']), + (_o: any, a: { [key: string]: any }) => new MockList(a['l']), ), }), Int: () => 12, @@ -1273,16 +1272,16 @@ describe('Mock', () => { // unintuitive corner-cases const mockMap = { RootQuery: () => ({ - thread: (o: any, a: { [key: string]: any }) => ({ id: a['id'] }), - threads: (o: any, a: { [key: string]: any }) => + thread: (_o: any, a: { [key: string]: any }) => ({ id: a['id'] }), + threads: (_o: any, a: { [key: string]: any }) => new MockList(ITEMS_PER_PAGE * a['num']), }), Thread: () => ({ name: 'Lorem Ipsum', - posts: (o: any, a: { [key: string]: any }) => + posts: (_o: any, a: { [key: string]: any }) => new MockList( ITEMS_PER_PAGE * a['num'], - (oi: any, ai: { [key: string]: any }) => ({ id: ai['num'] }), + (_oi: any, ai: { [key: string]: any }) => ({ id: ai['num'] }), ), }), Post: () => ({ @@ -1321,15 +1320,15 @@ describe('Mock', () => { it('works for resolvers returning javascript Dates', () => { const typeDefs = ` - scalar Date + scalar Date type DateObject { start: Date! } type Query { - date1: DateObject - date2: Date + date1: DateObject + date2: Date date3: Date } `; @@ -1398,9 +1397,9 @@ describe('Mock', () => { } const typeDefs = ` - interface Node { - id: ID! - } + interface Node { + id: ID! + } type Account implements Node { id: ID! @@ -1418,17 +1417,15 @@ describe('Mock', () => { const resolvers = { Query: { - node: () => { - return new Account(); - }, + node: () => new Account(), }, Node: { __resolveType: (obj: any) => { if (obj instanceof Account) { return 'Account'; - } else { - return null; } + + return null; }, }, }; diff --git a/src/test/testResolution.ts b/src/test/testResolution.ts index 7918d94c616..83e85dc499f 100644 --- a/src/test/testResolution.ts +++ b/src/test/testResolution.ts @@ -91,10 +91,10 @@ describe('Resolve', () => { forAwaitEach( results as AsyncIterable, (result: ExecutionResult) => { - if (result.errors) { + if (result.errors != null) { return done( new Error( - `Unexpected errors in GraphQL result: ${result.errors}`, + `Unexpected errors in GraphQL result: ${JSON.stringify(result.errors)}`, ), ); } @@ -119,9 +119,9 @@ describe('Resolve', () => { done(new Error('Too many subscription fired')); }, ).catch(done); - }).then(() => { - pubsub.publish('printRootChannel', { printRoot: subscriptionRoot }); - }).catch(done); + }).then(() => + pubsub.publish('printRootChannel', { printRoot: subscriptionRoot }) + ).catch(done); }); firstSubsTriggered @@ -152,7 +152,7 @@ describe('Resolve', () => { .then(({ data: mutationData }) => { assert.equal(schemaLevelResolverCalls, 3); assert.deepEqual(mutationData, { printRoot: mutationRoot }); - pubsub.publish('printRootChannel', { printRoot: subscriptionRoot2 }); + return pubsub.publish('printRootChannel', { printRoot: subscriptionRoot2 }); }) .catch(done); }); diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index ad4c351babd..78ed7cef031 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -1,27 +1,7 @@ // TODO: reduce code repetition in this file. // see https://github.com/apollostack/graphql-tools/issues/26 -import { assert, expect } from 'chai'; -import { - graphql, - GraphQLResolveInfo, - GraphQLScalarType, - Kind, - IntValueNode, - parse, - ExecutionResult, - GraphQLError, - GraphQLEnumType, - execute, - VariableDefinitionNode, - DocumentNode, - GraphQLBoolean, - graphqlSync, -} from 'graphql'; -// import { printSchema } from 'graphql'; -const { GraphQLJSON } = require('graphql-type-json'); import { Logger } from '../Logger'; -import TypeA from './circularSchemaA'; import { makeExecutableSchema, SchemaError, @@ -35,25 +15,49 @@ import { import { IResolverValidationOptions, IResolvers, - IExecutableSchemaDefinition, IDirectiveResolvers, NextResolverFn, VisitSchemaKind, + ITypeDefinitions, + ILogger, } from '../Interfaces'; -import 'mocha'; import { visitSchema } from '../utils/visitSchema'; import { addResolversToSchema } from '../generate'; +import TypeA from './circularSchemaA'; + +import { GraphQLJSON } from 'graphql-type-json'; +import { assert, expect } from 'chai'; +import { + graphql, + GraphQLResolveInfo, + GraphQLScalarType, + Kind, + IntValueNode, + parse, + ExecutionResult, + GraphQLError, + GraphQLEnumType, + execute, + VariableDefinitionNode, + DocumentNode, + GraphQLBoolean, + graphqlSync, + GraphQLSchema, +} from 'graphql'; + interface Bird { name: string; wingspan?: number; } function expectWarning(fn: () => void, warnMatcher?: string) { - let originalWarn = console.warn; + // eslint-disable-next-line no-console + const originalWarn = console.warn; let warning: string = null; try { + // eslint-disable-next-line no-console console.warn = function warn(message: string) { warning = message; }; @@ -66,6 +70,7 @@ function expectWarning(fn: () => void, warnMatcher?: string) { expect(warning).to.contain(warnMatcher); } } finally { + // eslint-disable-next-line no-console console.warn = originalWarn; } } @@ -85,12 +90,12 @@ const testSchema = ` const testResolvers = { __schema: () => ({ stuff: 'stuff', species: 'ROOT' }), RootQuery: { - usecontext: (r: any, a: { [key: string]: any }, ctx: any) => ctx.usecontext, - useTestConnector: (r: any, a: { [key: string]: any }, ctx: any) => + usecontext: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.usecontext, + useTestConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.connectors.TestConnector.get(), - useContextConnector: (r: any, a: { [key: string]: any }, ctx: any) => + useContextConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.connectors.ContextConnector.get(), - species: (root: any, { name }: { name: string }) => root.species + name, + species: (root: any, { name }: { name: string }) => root.species as string + name, }, }; class TestConnector { @@ -100,11 +105,12 @@ class TestConnector { } class ContextConnector { - private str: string; + private readonly str: string; constructor(ctx: any) { this.str = ctx.str; } + public get() { return this.str; } @@ -116,47 +122,46 @@ const testConnectors = { describe('generating schema from shorthand', () => { it('throws an error if no schema is provided', () => { - expect(() => (makeExecutableSchema)()).to.throw('undefined'); + expect(() => makeExecutableSchema(undefined)).to.throw('undefined'); }); it('throws an error if typeDefinitionNodes are not provided', () => { expect(() => - (makeExecutableSchema)({ typeDefs: undefined, resolvers: {} }), + makeExecutableSchema({ typeDefs: undefined, resolvers: {} }), ).to.throw('Must provide typeDefs'); }); it('throws an error if no resolveFunctions are provided', () => { expect(() => - (makeExecutableSchema)({ typeDefs: 'blah', resolvers: {} }), + makeExecutableSchema({ typeDefs: 'blah', resolvers: {} }), ).to.throw(GraphQLError); }); it('throws an error if typeDefinitionNodes is neither string nor array nor schema AST', () => { expect(() => - (makeExecutableSchema)({ typeDefs: {}, resolvers: {} }), + makeExecutableSchema({ typeDefs: {} as unknown as ITypeDefinitions, resolvers: {} }), ).to.throw('typeDefs must be a string, array or schema AST, got object'); }); it('throws an error if typeDefinitionNode array contains not only functions and strings', () => { expect(() => - (makeExecutableSchema)({ typeDefs: [17], resolvers: {} }), + makeExecutableSchema({ typeDefs: [17] as unknown as ITypeDefinitions, resolvers: {} }), ).to.throw( 'typeDef array must contain only strings and functions, got number', ); }); it('throws an error if resolverValidationOptions is not an object', () => { - expect(() => - makeExecutableSchema({ - typeDefs: 'blah', - resolvers: {}, - resolverValidationOptions: 'string', - } as IExecutableSchemaDefinition), - ).to.throw('Expected `resolverValidationOptions` to be an object'); + const options = { + typeDefs: 'blah', + resolvers: {}, + resolverValidationOptions: 'string' as unknown as IResolverValidationOptions, + }; + expect(() => makeExecutableSchema(options)).to.throw('Expected `resolverValidationOptions` to be an object'); }); it('can generate a schema', () => { - let shorthand = ` + const shorthand = ` """ A bird species """ @@ -175,9 +180,8 @@ describe('generating schema from shorthand', () => { const resolve = { RootQuery: { - species() { - return; - }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + species() {} }, }; @@ -232,7 +236,7 @@ describe('generating schema from shorthand', () => { name: 'name', type: { kind: 'NON_NULL', - name: null, + name: null as string, ofType: { name: 'String', }, @@ -256,7 +260,7 @@ describe('generating schema from shorthand', () => { name: 'species', type: { kind: 'LIST', - name: null, + name: null as string, ofType: { name: 'BirdSpecies', }, @@ -265,7 +269,7 @@ describe('generating schema from shorthand', () => { { name: 'name', type: { - name: null, + name: null as string, kind: 'NON_NULL', ofType: { name: 'String', @@ -466,7 +470,7 @@ describe('generating schema from shorthand', () => { const resolveFunctions = { RootQuery: { - species: (root: any, { name }: { name: string }) => [ + species: (_root: any, { name }: { name: string }) => [ { name: `Hello ${name}!`, wingspan: 200, @@ -521,7 +525,7 @@ describe('generating schema from shorthand', () => { const resolveFunctions = { RootQuery: { - species: (root: any, { name }: { name: string }) => [ + species: (_root: any, { name }: { name: string }) => [ { name: `Hello ${name}!`, wingspan: 200, @@ -587,7 +591,7 @@ describe('generating schema from shorthand', () => { const resolveFunctions = { RootQuery: { search: { - resolve(root: any, { name }: { name: string }) { + resolve(_root: any, { name }: { name: string }) { return [ { name: `Tom ${name}`, @@ -602,14 +606,13 @@ describe('generating schema from shorthand', () => { }, }, Searchable: { - __resolveType(data: any, context: any, info: GraphQLResolveInfo) { + __resolveType(data: any, _context: any, info: GraphQLResolveInfo) { if (data.age) { return info.schema.getType('Person'); } if (data.coordinates) { return info.schema.getType('Location'); } - console.error('no type!'); return null; }, }, @@ -673,7 +676,7 @@ describe('generating schema from shorthand', () => { const resolvers = { RootQuery: { - species: (root: any, { name }: { name: string }) => [ + species: (_root: any, { name }: { name: string }) => [ { name: `Hello ${name}!`, wingspan: 200, @@ -839,18 +842,18 @@ describe('generating schema from shorthand', () => { description: 'Test resolver', serialize(value) { if (typeof value !== 'string' || value.indexOf('scalar:') !== 0) { - return `scalar:${value}`; + return `scalar:${value as string}`; } return value; }, parseValue(value) { - return `scalar:${value}`; + return `scalar:${value as string}`; }, parseLiteral(ast: any) { switch (ast.kind) { case Kind.STRING: case Kind.INT: - return `scalar:${ast.value}`; + return `scalar:${ast.value as string}`; default: return null; } @@ -943,9 +946,7 @@ describe('generating schema from shorthand', () => { }); it('should work with an Odd custom scalar type', () => { - const oddValue = (value: number) => { - return value % 2 === 1 ? value : null; - }; + const oddValue = (value: number) => value % 2 === 1 ? value : null; const OddType = new GraphQLScalarType({ name: 'Odd', @@ -954,7 +955,7 @@ describe('generating schema from shorthand', () => { serialize: oddValue, parseLiteral(ast) { if (ast.kind === Kind.INT) { - const intValue: IntValueNode = ast; + const intValue: IntValueNode = ast; return oddValue(parseInt(intValue.value, 10)); } return null; @@ -994,8 +995,8 @@ describe('generating schema from shorthand', () => { }; const jsSchema = makeExecutableSchema({ - typeDefs: typeDefs, - resolvers: resolvers, + typeDefs, + resolvers, }); const testQuery = ` { @@ -1023,7 +1024,7 @@ describe('generating schema from shorthand', () => { }, parseLiteral(ast) { if (ast.kind === Kind.INT) { - const intValue: IntValueNode = ast; + const intValue: IntValueNode = ast; return parseInt(intValue.value, 10); } return null; @@ -1064,8 +1065,8 @@ describe('generating schema from shorthand', () => { }; const jsSchema = makeExecutableSchema({ - typeDefs: typeDefs, - resolvers: resolvers, + typeDefs, + resolvers, }); const testQuery = ` { @@ -1223,10 +1224,10 @@ describe('generating schema from shorthand', () => { TEST: 1, }, Query: { - colorTest(root: any, args: { color: string }) { + colorTest(_root: any, args: { color: string }) { return args.color; }, - numericTest(root: any, args: { num: number }) { + numericTest(_root: any, args: { num: number }) { return args.num; }, }, @@ -1272,7 +1273,7 @@ describe('generating schema from shorthand', () => { RED: '#EA3232', }, Query: { - colorTest(root: any, args: { color: string }) { + colorTest(_root: any, args: { color: string }) { return args.color; }, }, @@ -1314,7 +1315,7 @@ describe('generating schema from shorthand', () => { RED: '#EA3232', }, Query: { - colorTest(root: any, args: { color: string }) { + colorTest(_root: any, args: { color: string }) { return args.color; }, }, @@ -1361,7 +1362,7 @@ describe('generating schema from shorthand', () => { species: { description: 'A species', deprecationReason: 'Just because', - resolve: (root: any, { name }: { name: string }) => [ + resolve: (_root: any, { name }: { name: string }) => [ { name: `Hello ${name}!`, wingspan: 200, @@ -1429,7 +1430,6 @@ describe('generating schema from shorthand', () => { }, 'Resolver missing for "Query.bird"'); }); - // tslint:disable-next-line: max-line-length it('does not throw an error if `resolverValidationOptions.requireResolversForArgs` is false', () => { const short = ` type Query{ @@ -1441,7 +1441,6 @@ describe('generating schema from shorthand', () => { const rf = { Query: {} }; - // tslint:disable-next-line: max-line-length assert.doesNotThrow( makeExecutableSchema.bind(null, { typeDefs: short, resolvers: rf }), SchemaError, @@ -1463,7 +1462,7 @@ describe('generating schema from shorthand', () => { makeExecutableSchema({ typeDefs: short, resolvers: rf, - } as IExecutableSchemaDefinition), + }), ).to.throw('Resolver Query.bird must be object or function'); }); @@ -1494,7 +1493,6 @@ describe('generating schema from shorthand', () => { }, 'Resolver missing for "Query.bird"'); }); - // tslint:disable-next-line: max-line-length it('allows non-scalar field to use default resolver if `resolverValidationOptions.requireResolversForNonScalar` = false', () => { const short = ` type Bird{ @@ -1509,7 +1507,6 @@ describe('generating schema from shorthand', () => { const rf = {}; - // tslint:disable-next-line: max-line-length assert.doesNotThrow( makeExecutableSchema.bind(null, { typeDefs: short, @@ -1547,7 +1544,7 @@ describe('generating schema from shorthand', () => { expect(() => makeExecutableSchema({ typeDefs: short, resolvers: rf }), - ).to.throw(`Searchable was defined in resolvers, but it's not an object`); + ).to.throw('Searchable was defined in resolvers, but it\'s not an object'); expect(() => makeExecutableSchema({ @@ -1582,7 +1579,7 @@ describe('generating schema from shorthand', () => { expect(() => makeExecutableSchema({ typeDefs: short, resolvers: rf }), - ).to.throw(`"Searchable" defined in resolvers, but not in schema`); + ).to.throw('"Searchable" defined in resolvers, but not in schema'); expect(() => makeExecutableSchema({ @@ -1616,8 +1613,8 @@ describe('generating schema from shorthand', () => { expect(() => makeExecutableSchema({ typeDefs: short, resolvers: rf }), ).to.throw( - `"Searchable" defined in resolvers, but has invalid value "undefined". A resolver's value ` + - `must be of type object or function.`, + '"Searchable" defined in resolvers, but has invalid value "undefined". A resolver\'s value ' + + 'must be of type object or function.', ); }); @@ -1643,7 +1640,7 @@ describe('generating schema from shorthand', () => { expect(() => makeExecutableSchema({ typeDefs: short, resolvers: rf }), - ).to.throw(`RootQuery.name defined in resolvers, but not in schema`); + ).to.throw('RootQuery.name defined in resolvers, but not in schema'); expect(() => makeExecutableSchema({ @@ -1689,7 +1686,7 @@ describe('generating schema from shorthand', () => { expect(() => makeExecutableSchema({ typeDefs: short, resolvers: rf }), ).to.throw( - `Color.NO_RESOLVER was defined in resolvers, but enum is not in schema`, + 'Color.NO_RESOLVER was defined in resolvers, but enum is not in schema', ); expect(() => @@ -1719,7 +1716,6 @@ describe('generating schema from shorthand', () => { function assertOptionsError( resolverValidationOptions: IResolverValidationOptions, ) { - // tslint:disable-next-line: max-line-length assert.throws( () => makeExecutableSchema({ @@ -1746,7 +1742,6 @@ describe('generating schema from shorthand', () => { }); }); - // tslint:disable-next-line: max-line-length it('throws for any missing field if `resolverValidationOptions.requireResolversForAllFields` = true', () => { const typeDefs = ` type Bird { @@ -1784,7 +1779,6 @@ describe('generating schema from shorthand', () => { }); }); - // tslint:disable-next-line: max-line-length it('does not throw if all fields are satisfied when `resolverValidationOptions.requireResolversForAllFields` = true', () => { const typeDefs = ` type Bird { @@ -1806,7 +1800,6 @@ describe('generating schema from shorthand', () => { }, }; - // tslint:disable-next-line: max-line-length assert.doesNotThrow(() => makeExecutableSchema({ typeDefs, @@ -1832,7 +1825,7 @@ describe('generating schema from shorthand', () => { const resolveFunctions = { RootQuery: { - speciez: (root: any, { name }: { name: string }) => [ + speciez: (_root: any, { name }: { name: string }) => [ { name: `Hello ${name}!`, wingspan: 200, @@ -1863,7 +1856,7 @@ describe('generating schema from shorthand', () => { `; const resolveFunctions = { BootQuery: { - species: (root: any, { name }: { name: string }) => [ + species: (_root: any, { name }: { name: string }) => [ { name: `Hello ${name}!`, wingspan: 200, @@ -1909,7 +1902,7 @@ describe('providing useful errors from resolvers', () => { }); const testQuery = '{ species }'; const expected = 'Error in resolver RootQuery.species\noops!'; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then(_res => { assert.equal(logger.errors.length, 1); assert.equal(logger.errors[0].message, expected); }); @@ -1941,7 +1934,7 @@ describe('providing useful errors from resolvers', () => { }); const testQuery = '{ species, stuff }'; const expectedErr = /Resolver for "RootQuery.species" returned undefined/; - const expectedResData = { species: null, stuff: 'stuff' }; + const expectedResData = { species: null as string, stuff: 'stuff' }; return graphql(jsSchema, testQuery).then(res => { assert.equal(logger.errors.length, 1); assert.match(logger.errors[0].message, expectedErr); @@ -1963,7 +1956,7 @@ describe('providing useful errors from resolvers', () => { `; const resolve = { RootQuery: { - thread(root: any, args: { [key: string]: any }) { + thread(_root: any, args: { [key: string]: any }) { return args; }, }, @@ -2003,7 +1996,7 @@ describe('providing useful errors from resolvers', () => { `; const resolve = { RootQuery: { - thread(root: any, args: { [key: string]: any }) { + thread(_root: any, _args: { [key: string]: any }) { return { name: (): any => undefined }; }, }, @@ -2020,7 +2013,7 @@ describe('providing useful errors from resolvers', () => { } }`; return graphql(jsSchema, testQuery).then(res => { - expect((res.errors[0]).originalError.message).to.equal( + expect(res.errors[0].originalError.message).to.equal( 'Resolver for "Thread.name" returned undefined', ); }); @@ -2040,7 +2033,7 @@ describe('providing useful errors from resolvers', () => { `; const resolve = { RootQuery: { - thread(root: any, args: { [key: string]: any }) { + thread(_root: any, args: { [key: string]: any }) { return { name: () => args['name'] }; }, }, @@ -2066,7 +2059,7 @@ describe('providing useful errors from resolvers', () => { }); }); - it('will not throw errors on undefined by default', done => { + it('will not throw errors on undefined by default', () => { const shorthand = ` type RootQuery { species(name: String): String @@ -2090,11 +2083,10 @@ describe('providing useful errors from resolvers', () => { logger, }); const testQuery = '{ species, stuff }'; - const expectedResData = { species: null, stuff: 'stuff' }; - graphql(jsSchema, testQuery).then(res => { + const expectedResData = { species: null as string, stuff: 'stuff' }; + return graphql(jsSchema, testQuery).then(res => { assert.equal(logger.errors.length, 0); assert.deepEqual(res.data, expectedResData); - done(); }); }); }); @@ -2102,13 +2094,13 @@ describe('providing useful errors from resolvers', () => { describe('Add error logging to schema', () => { it('throws an error if no logger is provided', () => { assert.throw( - () => (addErrorLoggingToSchema)({}), + () => addErrorLoggingToSchema({} as unknown as GraphQLSchema), 'Must provide a logger', ); }); it('throws an error if logger.log is not a function', () => { assert.throw( - () => (addErrorLoggingToSchema)({}, { log: '1' }), + () => addErrorLoggingToSchema({} as unknown as GraphQLSchema, { log: '1' } as unknown as ILogger), 'Logger.log must be a function', ); }); @@ -2149,14 +2141,14 @@ describe('Attaching connectors to schema', () => { it('runs only once per query', () => { const simpleResolvers = { RootQuery: { - usecontext: (r: any, a: { [key: string]: any }, ctx: any) => + usecontext: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.usecontext, - useTestConnector: (r: any, a: { [key: string]: any }, ctx: any) => + useTestConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.connectors.TestConnector.get(), - useContextConnector: (r: any, a: { [key: string]: any }, ctx: any) => + useContextConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.connectors.ContextConnector.get(), species: (root: any, { name }: { name: string }) => - root.species + name, + root.species as string + name, }, }; const jsSchema = makeExecutableSchema({ @@ -2188,14 +2180,14 @@ describe('Attaching connectors to schema', () => { it('runs twice for two queries', () => { const simpleResolvers = { RootQuery: { - usecontext: (r: any, a: { [key: string]: any }, ctx: any) => + usecontext: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.usecontext, - useTestConnector: (r: any, a: { [key: string]: any }, ctx: any) => + useTestConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.connectors.TestConnector.get(), - useContextConnector: (r: any, a: { [key: string]: any }, ctx: any) => + useContextConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.connectors.ContextConnector.get(), species: (root: any, { name }: { name: string }) => - root.species + name, + root.species as string + name, }, }; const jsSchema = makeExecutableSchema({ @@ -2240,7 +2232,7 @@ describe('Attaching connectors to schema', () => { typeDefs: testSchema, resolvers: testResolvers, }); - const rootResolver = (o: any, a: { [key: string]: any }, ctx: any) => { + const rootResolver = (_o: any, _a: { [key: string]: any }, ctx: any) => { ctx['usecontext'] = 'ABC'; }; addSchemaLevelResolver(jsSchema, rootResolver); @@ -2258,12 +2250,12 @@ describe('Attaching connectors to schema', () => { it('can attach with existing static connectors', () => { const resolvers = { RootQuery: { - testString(root: any, args: { [key: string]: any }, ctx: any) { + testString(_root: any, _args: { [key: string]: any }, ctx: any) { return ctx.connectors.staticString; }, }, }; - const typeDef = ` + const typeDefs = ` type RootQuery { testString: String } @@ -2273,8 +2265,8 @@ describe('Attaching connectors to schema', () => { } `; const jsSchema = makeExecutableSchema({ - typeDefs: typeDef, - resolvers: resolvers, + typeDefs, + resolvers, connectors: testConnectors, }); const query = `{ @@ -2350,12 +2342,12 @@ describe('Attaching connectors to schema', () => { typeDefs: testSchema, resolvers: testResolvers, }); - (attachConnectorsToContext)(jsSchema, { someConnector: {} }); + attachConnectorsToContext(jsSchema, { someConnector: {} }); const query = `{ useTestConnector }`; return graphql(jsSchema, query, {}, 'notObject').then(res => { - expect((res.errors[0]).originalError.message).to.equal( + expect(res.errors[0].originalError.message).to.equal( 'Cannot attach connector because context is not an object: string', ); }); @@ -2366,14 +2358,14 @@ describe('Attaching connectors to schema', () => { typeDefs: testSchema, resolvers: testResolvers, }); - (attachConnectorsToContext)(jsSchema, { testString: 'a' }); + attachConnectorsToContext(jsSchema, { testString: 'a' }); const query = `{ species(name: "strix") stuff useTestConnector }`; return graphql(jsSchema, query, undefined, {}).then(res => { - expect((res.errors[0]).originalError.message).to.equal( + expect(res.errors[0].originalError.message).to.equal( 'Connector must be a function or an class', ); }); @@ -2405,14 +2397,14 @@ describe('Attaching connectors to schema', () => { // TODO test attachConnectors with wrong arguments it('throws error if no schema is passed', () => { - expect(() => (attachConnectorsToContext)()).to.throw( + expect(() => attachConnectorsToContext()).to.throw( 'schema must be an instance of GraphQLSchema. ' + 'This error could be caused by installing more than one version of GraphQL-JS', ); }); it('throws error if schema is not an instance of GraphQLSchema', () => { - expect(() => (attachConnectorsToContext)({})).to.throw( + expect(() => attachConnectorsToContext({})).to.throw( 'schema must be an instance of GraphQLSchema. ' + 'This error could be caused by installing more than one version of GraphQL-JS', ); @@ -2423,7 +2415,7 @@ describe('Attaching connectors to schema', () => { typeDefs: testSchema, resolvers: testResolvers, }); - expect(() => (attachConnectorsToContext)(jsSchema, [1])).to.throw( + expect(() => attachConnectorsToContext(jsSchema, [1])).to.throw( 'Expected connectors to be of type object, got Array', ); }); @@ -2444,7 +2436,7 @@ describe('Attaching connectors to schema', () => { resolvers: testResolvers, }); return expect(() => - (attachConnectorsToContext)(jsSchema, 'a'), + attachConnectorsToContext(jsSchema, 'a'), ).to.throw('Expected connectors to be of type object, got string'); }); }); @@ -2484,16 +2476,17 @@ describe('chainResolvers', () => { }); it('uses default resolver when a resolver is undefined', () => { - const r1 = (root: any, { name }: { name: string }) => ({ + const r1 = (_root: any, { name }: { name: string }) => ({ person: { name }, }); const r3 = (root: any) => root['name']; const rChained = chainResolvers([r1, undefined, r3]); // faking the resolve info here. + const info: GraphQLResolveInfo = { + fieldName: 'person', + } as unknown as GraphQLResolveInfo; expect( - rChained(0, { name: 'tony' }, null, { - fieldName: 'person', - } as GraphQLResolveInfo), + rChained(0, { name: 'tony' }, null, info), ).to.equals('tony'); }); }); @@ -2529,7 +2522,7 @@ describe('attachDirectiveResolvers on field', () => { RootQuery: { hello: () => 'giau. tran minh', object: () => testObject, - asyncResolver: async () => 'giau. tran minh', + asyncResolver: async () => Promise.resolve('giau. tran minh'), multiDirectives: () => 'Giau. Tran Minh', throwError: () => { throw new Error('This error for testing'); @@ -2537,12 +2530,12 @@ describe('attachDirectiveResolvers on field', () => { }, }; - const directiveResolvers: IDirectiveResolvers = { + const directiveResolvers: IDirectiveResolvers = { lower( next: NextResolverFn, - src: any, - args: { [argName: string]: any }, - context: any, + _src: any, + _args: { [argName: string]: any }, + _context: any, ) { return next().then(str => { if (typeof str === 'string') { @@ -2553,9 +2546,9 @@ describe('attachDirectiveResolvers on field', () => { }, upper( next: NextResolverFn, - src: any, - args: { [argName: string]: any }, - context: any, + _src: any, + _args: { [argName: string]: any }, + _context: any, ) { return next().then(str => { if (typeof str === 'string') { @@ -2566,9 +2559,9 @@ describe('attachDirectiveResolvers on field', () => { }, default( next: NextResolverFn, - src: any, + _src: any, args: { [argName: string]: any }, - context: any, + _context: any, ) { return next().then(res => { if (undefined === res) { @@ -2579,13 +2572,11 @@ describe('attachDirectiveResolvers on field', () => { }, catchError( next: NextResolverFn, - src: any, - args: { [argName: string]: any }, - context: any, + _src: any, + _args: { [argName: string]: any }, + _context: any, ) { - return next().catch(error => { - return error.message; - }); + return next().catch(error => error.message); }, }; @@ -2594,7 +2585,7 @@ describe('attachDirectiveResolvers on field', () => { typeDefs: testSchema, resolvers: testResolvers, }); - expect(() => (attachDirectiveResolvers)(jsSchema, [1])).to.throw( + expect(() => attachDirectiveResolvers(jsSchema, [1] as unknown as IDirectiveResolvers)).to.throw( 'Expected directiveResolvers to be of type object, got Array', ); }); @@ -2605,7 +2596,7 @@ describe('attachDirectiveResolvers on field', () => { resolvers: testResolvers, }); return expect(() => - (attachDirectiveResolvers)(jsSchema, 'a'), + attachDirectiveResolvers(jsSchema, 'a' as unknown as IDirectiveResolvers), ).to.throw('Expected directiveResolvers to be of type object, got string'); }); @@ -2613,7 +2604,7 @@ describe('attachDirectiveResolvers on field', () => { const schema = makeExecutableSchema({ typeDefs: testSchemaWithDirectives, resolvers: testResolversDirectives, - directiveResolvers: directiveResolvers, + directiveResolvers, }); const query = `{ hello @@ -2630,7 +2621,7 @@ describe('attachDirectiveResolvers on field', () => { const schema = makeExecutableSchema({ typeDefs: testSchemaWithDirectives, resolvers: testResolversDirectives, - directiveResolvers: directiveResolvers, + directiveResolvers, }); const query = `{ object { @@ -2651,7 +2642,7 @@ describe('attachDirectiveResolvers on field', () => { const schema = makeExecutableSchema({ typeDefs: testSchemaWithDirectives, resolvers: testResolversDirectives, - directiveResolvers: directiveResolvers, + directiveResolvers, }); const query = `{ withDefault @@ -2685,7 +2676,7 @@ describe('attachDirectiveResolvers on field', () => { const schema = makeExecutableSchema({ typeDefs: testSchemaWithDirectives, resolvers: testResolversDirectives, - directiveResolvers: directiveResolvers, + directiveResolvers, }); const query = `{ asyncResolver @@ -2702,7 +2693,7 @@ describe('attachDirectiveResolvers on field', () => { const schema = makeExecutableSchema({ typeDefs: testSchemaWithDirectives, resolvers: testResolversDirectives, - directiveResolvers: directiveResolvers, + directiveResolvers, }); const query = `{ multiDirectives @@ -2719,7 +2710,7 @@ describe('attachDirectiveResolvers on field', () => { const schema = makeExecutableSchema({ typeDefs: testSchemaWithDirectives, resolvers: testResolversDirectives, - directiveResolvers: directiveResolvers, + directiveResolvers, }); const query = `{ throwError @@ -2789,9 +2780,7 @@ describe('can specify lexical parser options', () => { const resolvers = { Query: { - hello: (root: any, args: any) => { - return `hello ${args.phrase}`; - } + hello: (_root: any, args: any) => `hello ${args.phrase as string}` }, }; @@ -2822,12 +2811,10 @@ describe('can specify lexical parser options', () => { return { kind: Kind.DOCUMENT, - definitions: parsedQuery.definitions.map(def => { - return { + definitions: parsedQuery.definitions.map(def => ({ ...def, variableDefinitions: variableDefs, - }; - }), + })), }; }; @@ -2868,7 +2855,7 @@ describe('interfaces', () => { user { id name } }`; - it('throws if there is no interface resolveType resolver', async () => { + it('throws if there is no interface resolveType resolver', () => { const resolvers = { Query: queryResolver, }; @@ -2881,7 +2868,7 @@ describe('interfaces', () => { } catch (error) { assert.equal( error.message, - 'Type "Node" is missing a "resolveType" resolver', + 'Type "Node" is missing a "__resolveType" resolver. Pass false into "resolverValidationOptions.requireResolversForResolveType" to disable this error.', ); return; } @@ -2891,7 +2878,7 @@ describe('interfaces', () => { const resolvers = { Query: queryResolver, Node: { - __resolveType: ({ type }: { type: String }) => type, + __resolveType: ({ type }: { type: string }) => type, }, }; const schema = makeExecutableSchema({ @@ -2902,7 +2889,7 @@ describe('interfaces', () => { const response = await graphql(schema, query); assert.isUndefined(response.errors); }); - it('does not warn if requireResolversForResolveType is disabled and there are missing resolvers', async () => { + it('does not warn if requireResolversForResolveType is disabled and there are missing resolvers', () => { const resolvers = { Query: queryResolver, }; @@ -2935,7 +2922,7 @@ describe('interface resolver inheritance', () => { const resolvers = { Node: { __resolveType: ({ type }: { type: string }) => type, - id: ({ id }: { id: number }) => `Node:${id}`, + id: ({ id }: { id: number }) => `Node:${id.toString()}`, }, User: { name: ({ name }: { name: string }) => `User:${name}`, @@ -2953,13 +2940,13 @@ describe('interface resolver inheritance', () => { requireResolversForResolveType: true, }, }); - const query = `{ user { id name } }`; + const query = '{ user { id name } }'; const response = await graphql(schema, query); assert.deepEqual(response, { data: { user: { - id: `Node:1`, - name: `User:Ada`, + id: 'Node:1', + name: 'User:Ada', }, }, }); @@ -2995,11 +2982,11 @@ describe('interface resolver inheritance', () => { const resolvers = { Node: { __resolveType: ({ type }: { type: string }) => type, - id: ({ id }: { id: number }) => `Node:${id}`, + id: ({ id }: { id: number }) => `Node:${id.toString()}`, }, Person: { __resolveType: ({ type }: { type: string }) => type, - id: ({ id }: { id: number }) => `Person:${id}`, + id: ({ id }: { id: number }) => `Person:${id.toString()}`, name: ({ name }: { name: string }) => `Person:${name}`, }, Query: { @@ -3017,17 +3004,17 @@ describe('interface resolver inheritance', () => { requireResolversForResolveType: true, }, }); - const query = `{ cyborg { id name } replicant { id name }}`; + const query = '{ cyborg { id name } replicant { id name }}'; const response = await graphql(schema, query); assert.deepEqual(response, { data: { cyborg: { - id: `Node:1`, - name: `Person:Alex Murphy`, + id: 'Node:1', + name: 'Person:Alex Murphy', }, replicant: { - id: `Person:2`, - name: `Person:Rachael Tyrell`, + id: 'Person:2', + name: 'Person:Rachael Tyrell', }, }, }); @@ -3068,7 +3055,7 @@ describe('unions', () => { } }`; - it('throws if there is no union resolveType resolver', async () => { + it('throws if there is no union resolveType resolver', () => { const resolvers = { Query: queryResolver, }; @@ -3081,7 +3068,7 @@ describe('unions', () => { } catch (error) { assert.equal( error.message, - 'Type "Displayable" is missing a "resolveType" resolver', + 'Type "Displayable" is missing a "__resolveType" resolver. Pass false into "resolverValidationOptions.requireResolversForResolveType" to disable this error.' , ); return; } @@ -3091,7 +3078,7 @@ describe('unions', () => { const resolvers = { Query: queryResolver, Displayable: { - __resolveType: ({ type }: { type: String }) => type, + __resolveType: ({ type }: { type: string }) => type, }, }; const schema = makeExecutableSchema({ @@ -3102,7 +3089,7 @@ describe('unions', () => { const response = await graphql(schema, query); assert.isUndefined(response.errors); }); - it('does not warn if requireResolversForResolveType is disabled', async () => { + it('does not warn if requireResolversForResolveType is disabled', () => { const resolvers = { Query: queryResolver, }; diff --git a/src/test/testStitchingFromSubschemas.ts b/src/test/testStitchingFromSubschemas.ts index 62a994f6171..dba307cb9b6 100644 --- a/src/test/testStitchingFromSubschemas.ts +++ b/src/test/testStitchingFromSubschemas.ts @@ -1,5 +1,3 @@ -/* tslint:disable:no-unused-expression */ - // The below is meant to be an alternative canonical schema stitching example // which intermingles local (mocked) resolvers and stitched schemas and does // not require use of the fragment field, because it follows best practices of @@ -13,14 +11,15 @@ // The fragment field is still necessary when working with a remote schema // where this is not possible. -import { expect } from 'chai'; -import { graphql } from 'graphql'; import { delegateToSchema, mergeSchemas, addMocksToSchema, } from '../index'; +import { expect } from 'chai'; +import { graphql } from 'graphql'; + const chirpTypeDefs = ` type Chirp { id: ID! @@ -54,18 +53,16 @@ const chirpSchema = mergeSchemas({ ], resolvers: { Chirp: { - author: (chirp, args, context, info) => { - return delegateToSchema({ - schema: getSchema('authorSchema'), - operation: 'query', - fieldName: 'userById', - args: { - id: chirp.authorId - }, - context, - info - }); - } + author: (chirp, _args, context, info) => delegateToSchema({ + schema: getSchema('authorSchema'), + operation: 'query', + fieldName: 'userById', + args: { + id: chirp.authorId + }, + context, + info + }) } } }); @@ -92,8 +89,7 @@ const authorSchema = mergeSchemas({ ], resolvers: { User: { - chirps: (user, args, context, info) => { - return delegateToSchema({ + chirps: (user, _args, context, info) => delegateToSchema({ schema: getSchema('chirpSchema'), operation: 'query', fieldName: 'chirpsByAuthorId', @@ -102,8 +98,7 @@ const authorSchema = mergeSchemas({ }, context, info - }); - } + }) } } }); @@ -143,9 +138,9 @@ describe('merging without specifying fragments', () => { const result = await graphql(mergedSchema, query); - expect(result.errors).to.be.undefined; - expect(result.data.userById.chirps[1].id).to.not.be.null; - expect(result.data.userById.chirps[1].text).to.not.be.null; - expect(result.data.userById.chirps[1].author.email).to.not.be.null; + expect(result.errors).to.equal(undefined); + expect(result.data.userById.chirps[1].id).to.not.equal(null); + expect(result.data.userById.chirps[1].text).to.not.equal(null); + expect(result.data.userById.chirps[1].author.email).to.not.equal(null); }); }); diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 642659d7b46..34f6501f4e2 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -1,18 +1,4 @@ -/* tslint:disable:no-unused-expression */ - -import { expect } from 'chai'; -import { - GraphQLSchema, - GraphQLNamedType, - GraphQLScalarType, - graphql, - Kind, - SelectionSetNode, - print, - parse, -} from 'graphql'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import { propertySchema, bookingSchema } from './testingSchemas'; import { delegateToSchema, defaultMergedResolver @@ -33,10 +19,24 @@ import { parseFragmentToInlineFragment } from '../utils'; +import { propertySchema, bookingSchema } from './testingSchemas'; + +import { expect } from 'chai'; +import { + GraphQLSchema, + GraphQLNamedType, + GraphQLScalarType, + graphql, + Kind, + SelectionSetNode, + print, + parse, +} from 'graphql'; + describe('transforms', () => { describe('base transform function', () => { let schema: GraphQLSchema; - let scalarTest = ` + const scalarTest = ` scalar TestScalar type TestingScalar { value: TestScalar @@ -47,20 +47,18 @@ describe('transforms', () => { } `; - let scalarSchema: GraphQLSchema; - - scalarSchema = makeExecutableSchema({ + const scalarSchema = makeExecutableSchema({ typeDefs: scalarTest, resolvers: { TestScalar: new GraphQLScalarType({ name: 'TestScalar', description: undefined, serialize: value => (value as string).slice(1), - parseValue: value => `_${value}`, - parseLiteral: (ast: any) => `_${ast.value}`, + parseValue: value => `_${value as string}`, + parseLiteral: (ast: any) => `_${ast.value as string}`, }), Query: { - testingScalar(parent, args) { + testingScalar(_parent, args) { return { value: args.input[0] === '_' ? args.input : null }; @@ -229,7 +227,7 @@ describe('transforms', () => { filter = new FilterToSchema(bookingSchema); }); - it('should remove empty selection sets on objects', async () => { + it('should remove empty selection sets on objects', () => { const query = parse(` query customerQuery($id: ID!) { customerById(id: $id) { @@ -259,7 +257,7 @@ describe('transforms', () => { expect(print(filteredQuery.document)).to.equal(print(expected)); }); - it('should also remove variables when removing empty selection sets', async () => { + it('should also remove variables when removing empty selection sets', () => { const query = parse(` query customerQuery($id: ID!, $limit: Int) { customerById(id: $id) { @@ -290,7 +288,7 @@ describe('transforms', () => { expect(print(filteredQuery.document)).to.equal(print(expected)); }); - it('should remove empty selection sets on wrapped objects (non-nullable/lists)', async () => { + it('should remove empty selection sets on wrapped objects (non-nullable/lists)', () => { const query = parse(` query bookingQuery($id: ID!) { bookingById(id: $id) { @@ -377,7 +375,7 @@ describe('transforms', () => { } `, ); - expect(result.errors).not.to.be.empty; + expect(result.errors).to.not.equal(undefined); expect(result.errors.length).to.equal(1); expect(result.errors[0].message).to.equal( 'Cannot query field "customer" on type "Booking".' @@ -443,12 +441,12 @@ describe('transforms', () => { `, resolvers: { Query: { - userById(parent, { id }) { + userById(_parent, { id }) { return data[id]; }, }, Mutation: { - setUser(parent, { input }) { + setUser(_parent, { input }) { if (data[input.id]) { return { ...data[input.id], @@ -456,7 +454,7 @@ describe('transforms', () => { }; } }, - setAddress(parent, { input }) { + setAddress(_parent, { input }) { if (data[input.id]) { return { ...data[input.id].address, @@ -497,7 +495,7 @@ describe('transforms', () => { `, resolvers: { Query: { - addressByUser(parent, { id }, context, info) { + addressByUser(_parent, { id }, context, info) { return delegateToSchema({ schema: subschema, operation: 'query', @@ -522,14 +520,14 @@ describe('transforms', () => { selectionSet: subtree, }), // how to process the data result at path - result => result && result.address, + result => result?.address, ), ], }); }, }, Mutation: { - async setUserAndAddress(parent, { input }, context, info) { + async setUserAndAddress(_parent, { input }, context, info) { const addressResult = await delegateToSchema({ schema: subschema, operation: 'mutation', @@ -673,7 +671,7 @@ describe('transforms', () => { `, resolvers: { Query: { - userById(parent, { id }) { + userById(_parent, { id }) { return data[id]; }, } @@ -697,7 +695,7 @@ describe('transforms', () => { `, resolvers: { Query: { - addressByUser(parent, { id }, context, info) { + addressByUser(_parent, { id }, context, info) { return delegateToSchema({ schema: subschema, operation: 'query', @@ -833,7 +831,7 @@ describe('transforms', () => { } }, Query: { - userById(parent, { id }) { + userById(_parent, { id }) { return data[id]; }, }, @@ -854,7 +852,7 @@ describe('transforms', () => { `, resolvers: { Query: { - addressByUser(parent, { id }, context, info) { + addressByUser(_parent, { id }, context, info) { return delegateToSchema({ schema: subschema, operation: 'query', @@ -882,13 +880,13 @@ describe('transforms', () => { }], }), // how to process the data result at path - resultTransformer: result => result && result.address, + resultTransformer: result => result?.address, errorPathTransformer: path => path.slice(1), }), ], }); }, - errorTest(parent, { id }, context, info) { + errorTest(_parent, { id }, context, info) { return delegateToSchema({ schema: subschema, operation: 'query', @@ -910,7 +908,7 @@ describe('transforms', () => { selectionSet: subtree, }], }), - resultTransformer: result => result && result.address, + resultTransformer: result => result?.address, errorPathTransformer: path => path.slice(1), }), ], @@ -1046,7 +1044,7 @@ describe('transforms', () => { `, resolvers: { Query: { - userById(parent, { id }) { + userById(_parent, { id }) { return data[id]; }, }, @@ -1068,7 +1066,7 @@ describe('transforms', () => { `, resolvers: { Query: { - userById(parent, { id }, context, info) { + userById(_parent, { id }, context, info) { return delegateToSchema({ schema: subschema, operation: 'query', @@ -1079,12 +1077,12 @@ describe('transforms', () => { transforms: [ new ReplaceFieldWithFragment(subschema, [ { - field: `fullname`, - fragment: `fragment UserName on User { name }`, + field: 'fullname', + fragment: 'fragment UserName on User { name }', }, { - field: `fullname`, - fragment: `fragment UserSurname on User { surname }`, + field: 'fullname', + fragment: 'fragment UserSurname on User { surname }', }, ]), ], @@ -1092,8 +1090,8 @@ describe('transforms', () => { }, }, User: { - fullname(parent, args, context, info) { - return `${parent.name} ${parent.surname}`; + fullname(parent, _args, _context, _info) { + return `${parent.name as string} ${parent.surname as string}`; }, }, }, @@ -1151,7 +1149,7 @@ describe('replaces field with processed fragment node', () => { `, resolvers: { Query: { - userById(parent, { id }) { + userById(_parent, { id }) { return data[id]; }, }, @@ -1173,7 +1171,7 @@ describe('replaces field with processed fragment node', () => { `, resolvers: { Query: { - userById(parent, { id }, context, info) { + userById(_parent, { id }, context, info) { return delegateToSchema({ schema: subschema, operation: 'query', @@ -1187,8 +1185,8 @@ describe('replaces field with processed fragment node', () => { fullname: concatInlineFragments( 'User', [ - parseFragmentToInlineFragment(`fragment UserName on User { name }`), - parseFragmentToInlineFragment(`fragment UserSurname on User { surname }`), + parseFragmentToInlineFragment('fragment UserName on User { name }'), + parseFragmentToInlineFragment('fragment UserSurname on User { surname }'), ], ), } @@ -1198,8 +1196,8 @@ describe('replaces field with processed fragment node', () => { }, }, User: { - fullname(parent, args, context, info) { - return `${parent.name} ${parent.surname}`; + fullname(parent, _args, _context, _info) { + return `${parent.name as string} ${parent.surname as string}`; }, }, }, diff --git a/src/test/testTypeMerging.ts b/src/test/testTypeMerging.ts index 70e16cc509d..39024b8feca 100644 --- a/src/test/testTypeMerging.ts +++ b/src/test/testTypeMerging.ts @@ -1,16 +1,15 @@ -/* tslint:disable:no-unused-expression */ - // The below is meant to be an alternative canonical schema stitching example // which relies on type merging. -import { expect } from 'chai'; -import { graphql } from 'graphql'; import { mergeSchemas, addMocksToSchema, makeExecutableSchema, } from '../index'; +import { expect } from 'chai'; +import { graphql } from 'graphql'; + const chirpSchema = makeExecutableSchema({ typeDefs: ` type Chirp { @@ -86,9 +85,9 @@ describe('merging using type merging', () => { const result = await graphql(mergedSchema, query); - expect(result.errors).to.be.undefined; - expect(result.data.userById.chirps[1].id).to.not.be.null; - expect(result.data.userById.chirps[1].text).to.not.be.null; - expect(result.data.userById.chirps[1].author.email).to.not.be.null; + expect(result.errors).to.equal(undefined); + expect(result.data.userById.chirps[1].id).to.not.equal(null); + expect(result.data.userById.chirps[1].text).to.not.equal(null); + expect(result.data.userById.chirps[1].author.email).to.not.equal(null); }); }); diff --git a/src/test/testUpload.ts b/src/test/testUpload.ts index 72af506afcd..8c5a38b5a51 100644 --- a/src/test/testUpload.ts +++ b/src/test/testUpload.ts @@ -1,20 +1,19 @@ -/* tslint:disable:no-unused-expression */ - -import { expect } from 'chai'; - import { Server } from 'http'; import { AddressInfo } from 'net'; import { Readable } from 'stream'; + +import { SubschemaConfig } from '../Interfaces'; +import { createServerHttpLink } from '../links'; +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { mergeSchemas} from '../stitching'; + +import { expect } from 'chai'; import express, { Express } from 'express'; import graphqlHTTP from 'express-graphql'; import { GraphQLUpload, graphqlUploadExpress } from 'graphql-upload'; import FormData from 'form-data'; import fetch from 'node-fetch'; import { buildSchema } from 'graphql'; -import { SubschemaConfig } from '../Interfaces'; -import { createServerHttpLink } from '../links'; -import { makeExecutableSchema } from '../makeExecutableSchema'; -import { mergeSchemas} from '../stitching'; function streamToString(stream: Readable) { const chunks: Array = []; @@ -49,7 +48,7 @@ function testGraphqlMultipartRequest(query: string, port: number) { body.append('map', '{ "1": ["variables.file"] }'); body.append('1', 'abc', { filename: __filename }); - return fetch(`http://localhost:${port}`, { method: 'POST', body }); + return fetch(`http://localhost:${port.toString()}`, { method: 'POST', body }); } describe('graphql upload', () => { @@ -66,7 +65,7 @@ describe('graphql upload', () => { `, resolvers: { Mutation: { - upload: async (root, { file }) => { + upload: async (_root, { file }) => { const { createReadStream } = await file; const stream = createReadStream(); const s = await streamToString(stream); @@ -98,7 +97,7 @@ describe('graphql upload', () => { const subschema: SubschemaConfig = { schema: nonExecutableSchema, link: createServerHttpLink({ - uri: `http://localhost:${remotePort}`, + uri: `http://localhost:${remotePort.toString()}`, }), }; diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 78127740089..9f6dfc2165a 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -1,9 +1,7 @@ -/* tslint:disable:no-unused-expression */ - -import { expect } from 'chai'; - import { healSchema } from '../utils'; import { makeExecutableSchema } from '../makeExecutableSchema'; + +import { expect } from 'chai'; import { GraphQLObjectType, GraphQLObjectTypeConfig } from 'graphql'; describe('heal', () => { diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index 4d24f36f957..1d0f9ad9133 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -1,3 +1,17 @@ +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { + IResolvers, + Fetcher, + SubschemaConfig, +} from '../Interfaces'; +import introspectSchema from '../stitching/introspectSchema'; + +import { PubSub } from 'graphql-subscriptions'; +import { + ApolloLink, + Observable, + ExecutionResult as LinkExecutionResult, +} from 'apollo-link'; import { GraphQLSchema, graphql, @@ -7,24 +21,10 @@ import { GraphQLScalarType, ValueNode, ExecutionResult, - DocumentNode, Source, GraphQLResolveInfo, } from 'graphql'; -import { ExecutionResultDataDefault } from 'graphql/execution/execute'; -import { - ApolloLink, - Observable, - ExecutionResult as LinkExecutionResult, -} from 'apollo-link'; -import { makeExecutableSchema } from '../makeExecutableSchema'; -import { - IResolvers, - Fetcher, - SubschemaConfig, -} from '../Interfaces'; -import introspectSchema from '../stitching/introspectSchema'; -import { PubSub } from 'graphql-subscriptions'; +import { forAwaitEach } from 'iterall'; export type Location = { name: string; @@ -175,7 +175,7 @@ export const sampleData: { }, }; -function values(o: { [s: string]: T }): T[] { +function values(o: { [s: string]: T }): Array { return Object.keys(o).map(k => o[k]); } @@ -318,20 +318,16 @@ const propertyAddressTypeDefs = ` const propertyResolvers: IResolvers = { Query: { - propertyById(root, { id }) { + propertyById(_root, { id }) { return sampleData.Property[id]; }, - properties(root, { limit }) { + properties(_root, { limit }) { const list = values(sampleData.Property); - if (limit) { - return list.slice(0, limit); - } else { - return list; - } + return limit ? list.slice(0, limit) : list; }, - contextTest(root, args, context) { + contextTest(_root, args, context) { return JSON.stringify(context[args.key]); }, @@ -339,38 +335,30 @@ const propertyResolvers: IResolvers = { return '1987-09-25T12:00:00'; }, - jsonTest(root, { input }) { + jsonTest(_root, { input }) { return input; }, - interfaceTest(root, { kind }) { - if (kind === 'ONE') { - return { - kind: 'ONE', - testString: 'test', - foo: 'foo', - }; - } else { - return { - kind: 'TWO', - testString: 'test', - bar: 'bar', - }; - } + interfaceTest(_root, { kind }) { + return (kind === 'ONE') ? { + kind: 'ONE', + testString: 'test', + foo: 'foo', + } : { + kind: 'TWO', + testString: 'test', + bar: 'bar', + }; }, - unionTest(root, { output }) { - if (output === 'Interface') { - return { - kind: 'ONE', - testString: 'test', - foo: 'foo', - }; - } else { - return { - someField: 'Bar', - }; - } + unionTest(_root, { output }) { + return (output === 'Interface') ? { + kind: 'ONE', + testString: 'test', + foo: 'foo', + } : { + someField: 'Bar', + }; }, errorTest() { @@ -381,7 +369,7 @@ const propertyResolvers: IResolvers = { throw new Error('Sample error non-null!'); }, - defaultInputTest(parent, { input }) { + defaultInputTest(_parent, { input }) { return input.test; }, }, @@ -390,21 +378,13 @@ const propertyResolvers: IResolvers = { TestInterface: { __resolveType(obj: any) { - if (obj.kind === 'ONE') { - return 'TestImpl1'; - } else { - return 'TestImpl2'; - } + return (obj.kind === 'ONE') ? 'TestImpl1' : 'TestImpl2'; }, }, TestUnion: { __resolveType(obj: any) { - if (obj.kind === 'ONE') { - return 'TestImpl1'; - } else { - return 'UnionImpl'; - } + return (obj.kind === 'ONE') ? 'TestImpl1' : 'UnionImpl'; }, }, @@ -419,14 +399,14 @@ const propertyResolvers: IResolvers = { }, }; -let DownloadableProduct = ` +const DownloadableProduct = ` type DownloadableProduct implements Product & Downloadable { id: ID! url: String! } `; -let SimpleProduct = `type SimpleProduct implements Product & Sellable { +const SimpleProduct = `type SimpleProduct implements Product & Sellable { id: ID! price: Int! } @@ -455,7 +435,7 @@ const productTypeDefs = ` const productResolvers: IResolvers = { Query: { - products(root) { + products(_root) { const list = values(sampleData.Product); return list; }, @@ -463,11 +443,7 @@ const productResolvers: IResolvers = { Product: { __resolveType(obj: any) { - if (obj.type === 'simple') { - return 'SimpleProduct'; - } else { - return 'DownloadableProduct'; - } + return (obj.type === 'simple') ? 'SimpleProduct' : 'DownloadableProduct'; }, }, }; @@ -542,43 +518,31 @@ const bookingAddressTypeDefs = ` const bookingResolvers: IResolvers = { Query: { - bookingById(parent, { id }) { + bookingById(_parent, { id }) { return sampleData.Booking[id]; }, - bookingsByPropertyId(parent, { propertyId, limit }) { + bookingsByPropertyId(_parent, { propertyId, limit }) { const list = values(sampleData.Booking).filter( (booking: Booking) => booking.propertyId === propertyId, ); - if (limit) { - return list.slice(0, limit); - } else { - return list; - } + return limit ? list.slice(0, limit) : list; }, - customerById(parent, { id }) { + customerById(_parent, { id }) { return sampleData.Customer[id]; }, - bookings(parent, { limit }) { + bookings(_parent, { limit }) { const list = values(sampleData.Booking); - if (limit) { - return list.slice(0, limit); - } else { - return list; - } + return limit ? list.slice(0, limit) : list; }, - customers(parent, { limit }) { + customers(_parent, { limit }) { const list = values(sampleData.Customer); - if (limit) { - return list.slice(0, limit); - } else { - return list; - } + return limit ? list.slice(0, limit) : list; }, }, Mutation: { addBooking( - parent, + _parent, { input: { propertyId, customerId, startTime, endTime } }, ) { return { @@ -592,7 +556,7 @@ const bookingResolvers: IResolvers = { }, Booking: { - __isTypeOf(source: Source, context: any, info: GraphQLResolveInfo) { + __isTypeOf(source: Source, _context: any, _info: GraphQLResolveInfo) { return Object.prototype.hasOwnProperty.call(source, 'id'); }, customer(parent: Booking) { @@ -611,11 +575,7 @@ const bookingResolvers: IResolvers = { const list = values(sampleData.Booking).filter( (booking: Booking) => booking.customerId === parent.id, ); - if (limit) { - return list.slice(0, limit); - } else { - return list; - } + return limit ? list.slice(0, limit) : list; }, vehicle(parent: Customer) { return sampleData.Vehicle[parent.vehicleId]; @@ -631,9 +591,9 @@ const bookingResolvers: IResolvers = { return 'Car'; } else if (parent.bikeType) { return 'Bike'; - } else { - throw new Error('Could not resolve Vehicle type'); } + + throw new Error('Could not resolve Vehicle type'); }, }, @@ -660,7 +620,7 @@ export const subscriptionPubSubTrigger = 'pubSubTrigger'; const subscriptionResolvers: IResolvers = { Query: { - notifications: (root: any) => ({ text: 'Hello world' }), + notifications: (_root: any) => ({ text: 'Hello world' }), }, Subscription: { notifications: { @@ -696,7 +656,7 @@ export const subscriptionSchema: GraphQLSchema = makeExecutableSchema({ }); const hasSubscriptionOperation = ({ query }: { query: any }): boolean => { - for (let definition of query.definitions) { + for (const definition of query.definitions) { if (definition.kind === 'OperationDefinition') { const operation = definition.operation; if (operation === 'subscription') { @@ -708,57 +668,46 @@ const hasSubscriptionOperation = ({ query }: { query: any }): boolean => { }; function makeLinkFromSchema(schema: GraphQLSchema) { - return new ApolloLink(operation => { - return new Observable(observer => { - (async () => { - const { query, operationName, variables } = operation; - const { graphqlContext } = operation.getContext(); - try { - if (!hasSubscriptionOperation(operation)) { - const result: ExecutionResultDataDefault = await graphql( - schema, - print(query), - null, - graphqlContext, - variables, - operationName, - ); - observer.next(result as LinkExecutionResult); - observer.complete(); + return new ApolloLink(operation => new Observable(observer => { + const { query, operationName, variables } = operation; + const { graphqlContext } = operation.getContext(); + if (!hasSubscriptionOperation(operation)) { + graphql( + schema, + print(query), + null, + graphqlContext, + variables, + operationName, + ).then(result => { + observer.next(result); + observer.complete(); + }).catch(err => { + observer.error(err); + }); + } else { + subscribe( + schema, + query, + null, + graphqlContext, + variables, + operationName, + ).then(results => { + if (typeof (results as AsyncIterator).next === 'function') { + forAwaitEach( + (results as AsyncIterable), + result => observer.next(result) + ).then(() => observer.complete()).catch(err => observer.error(err)) } else { - const result = await subscribe( - schema, - query as DocumentNode, - null, - graphqlContext, - variables, - operationName, - ); - if ( - typeof (>result).next === - 'function' - ) { - while (true) { - const next = await (>( - result - )).next(); - observer.next(next.value as LinkExecutionResult); - if (next.done) { - observer.complete(); - break; - } - } - } else { - observer.next(result as LinkExecutionResult); - observer.complete(); - } + observer.next(results as LinkExecutionResult); + observer.complete(); } - } catch (error) { - observer.error.bind(observer); - } - })(); - }); - }); + }).catch(err => { + observer.error(err); + }); + } + })); } export async function makeSchemaRemoteFromLink(schema: GraphQLSchema) @@ -784,16 +733,14 @@ export async function makeSchemaRemoteFromDispatchedLink(schema: GraphQLSchema) // ensure fetcher support exists from the 2.0 api async function makeExecutableSchemaFromDispatchedFetcher(schema: GraphQLSchema) : Promise { - const fetcher: Fetcher = ({ query, operationName, variables, context }) => { - return graphql( - schema, - print(query), - null, - context, - variables, - operationName, - ); - }; + const fetcher: Fetcher = ({ query, operationName, variables, context }) => graphql( + schema, + print(query), + null, + context, + variables, + operationName, + ); const clientSchema = await introspectSchema(fetcher); return { diff --git a/src/test/tests.ts b/src/test/tests.ts deleted file mode 100755 index a65618dbdb5..00000000000 --- a/src/test/tests.ts +++ /dev/null @@ -1,21 +0,0 @@ -require('source-map-support').install(); - -import './testAlternateMergeSchemas'; -import './testDelegateToSchema'; -import './testDataloader'; -import './testDirectives'; -import './testErrors'; -import './testFragmentsAreNotDuplicated'; -import './testLogger'; -import './testMakeRemoteExecutableSchema'; -import './testMergeSchemas'; -import './testMocking'; -import './testResolution'; -import './testSchemaGenerator'; -import './testTransforms'; -import './testExtensionExtraction'; -import './testStitchingFromSubschemas'; -import './testTypeMerging'; -import './testUpload'; -import './testUtils'; - diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index 15b57639ea9..a0d761fe0bd 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -1,3 +1,8 @@ +import { Request } from '../Interfaces'; +import { serializeInputValue } from '../utils'; + +import { Transform } from './transforms'; + import { ArgumentNode, DocumentNode, @@ -15,13 +20,10 @@ import { TypeNode, VariableDefinitionNode, } from 'graphql'; -import { Request } from '../Interfaces'; -import { Transform } from './transforms'; -import { serializeInputValue } from '../utils'; export default class AddArgumentsAsVariablesTransform implements Transform { - private targetSchema: GraphQLSchema; - private args: { [key: string]: any }; + private readonly targetSchema: GraphQLSchema; + private readonly args: { [key: string]: any }; constructor(targetSchema: GraphQLSchema, args: { [key: string]: any }) { this.targetSchema = targetSchema; @@ -66,7 +68,8 @@ function addVariablesToRootField( const newVariables = {}; const newOperations = operations.map((operation: OperationDefinitionNode) => { - let existingVariables = operation.variableDefinitions.map( + const originalVariableDefinitions = operation.variableDefinitions; + const existingVariables = originalVariableDefinitions.map( (variableDefinition: VariableDefinitionNode) => variableDefinition.variable.name.value, ); @@ -77,13 +80,13 @@ function addVariablesToRootField( const generateVariableName = (argName: string) => { let varName; do { - varName = `_v${variableCounter}_${argName}`; + varName = `_v${variableCounter.toString()}_${argName}`; variableCounter++; } while (existingVariables.indexOf(varName) !== -1); return varName; }; - let type: GraphQLObjectType; + let type: GraphQLObjectType | null | undefined; if (operation.operation === 'subscription') { type = targetSchema.getSubscriptionType(); } else if (operation.operation === 'mutation') { @@ -96,48 +99,53 @@ function addVariablesToRootField( operation.selectionSet.selections.forEach((selection: SelectionNode) => { if (selection.kind === Kind.FIELD) { - let newArgs: { [name: string]: ArgumentNode } = {}; - selection.arguments.forEach((argument: ArgumentNode) => { - newArgs[argument.name.value] = argument; - }); + const newArgs: { [name: string]: ArgumentNode } = {}; + if (selection.arguments != null) { + selection.arguments.forEach((argument: ArgumentNode) => { + newArgs[argument.name.value] = argument; + }); + } const name: string = selection.name.value; - const field: GraphQLField = type.getFields()[name]; - field.args.forEach((argument: GraphQLArgument) => { - if (argument.name in args) { - const variableName = generateVariableName(argument.name); - variableNames[argument.name] = variableName; - newArgs[argument.name] = { - kind: Kind.ARGUMENT, - name: { - kind: Kind.NAME, - value: argument.name, - }, - value: { - kind: Kind.VARIABLE, + + if (type != null) { + const field: GraphQLField = type.getFields()[name]; + field.args.forEach((argument: GraphQLArgument) => { + if (argument.name in args) { + const variableName = generateVariableName(argument.name); + variableNames[argument.name] = variableName; + newArgs[argument.name] = { + kind: Kind.ARGUMENT, name: { kind: Kind.NAME, - value: variableName, + value: argument.name, }, - }, - }; - existingVariables.push(variableName); - variables[variableName] = { - kind: Kind.VARIABLE_DEFINITION, - variable: { - kind: Kind.VARIABLE, - name: { - kind: Kind.NAME, - value: variableName, + value: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: variableName, + }, }, - }, - type: typeToAst(argument.type), - }; - newVariables[variableName] = serializeInputValue( - argument.type, - args[argument.name], - ); - } - }); + }; + existingVariables.push(variableName); + variables[variableName] = { + kind: Kind.VARIABLE_DEFINITION, + variable: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: variableName, + }, + }, + type: typeToAst(argument.type), + }; + newVariables[variableName] = serializeInputValue( + argument.type, + args[argument.name], + ); + } + }); + } newSelectionSet.push({ ...selection, @@ -150,7 +158,7 @@ function addVariablesToRootField( return { ...operation, - variableDefinitions: operation.variableDefinitions.concat( + variableDefinitions: originalVariableDefinitions.concat( Object.keys(variables).map(varName => variables[varName]), ), selectionSet: { @@ -180,9 +188,8 @@ function typeToAst(type: GraphQLInputType): TypeNode { kind: Kind.NON_NULL_TYPE, type: innerType, }; - } else { - throw new Error('Incorrent inner non-null type'); } + throw new Error('Incorrect inner non-null type'); } else if (type instanceof GraphQLList) { return { kind: Kind.LIST_TYPE, diff --git a/src/transforms/AddMergedTypeSelectionSets.ts b/src/transforms/AddMergedTypeSelectionSets.ts index 8f330052da3..3ea058965d2 100644 --- a/src/transforms/AddMergedTypeSelectionSets.ts +++ b/src/transforms/AddMergedTypeSelectionSets.ts @@ -1,3 +1,7 @@ +import { Request, MergedTypeInfo } from '../Interfaces'; + +import { Transform } from './transforms'; + import { DocumentNode, GraphQLSchema, @@ -8,12 +12,10 @@ import { visit, visitWithTypeInfo, } from 'graphql'; -import { Request, MergedTypeInfo } from '../Interfaces'; -import { Transform } from './transforms'; export default class AddMergedTypeFragments implements Transform { - private targetSchema: GraphQLSchema; - private mapping: Record; + private readonly targetSchema: GraphQLSchema; + private readonly mapping: Record; constructor( targetSchema: GraphQLSchema, @@ -49,13 +51,16 @@ function addMergedTypeSelectionSets( [Kind.SELECTION_SET]( node: SelectionSetNode, ): SelectionSetNode | null | undefined { - const parentType: GraphQLType = typeInfo.getParentType(); - if (parentType) { + const parentType: GraphQLType | null | undefined = typeInfo.getParentType(); + if (parentType != null) { const parentTypeName = parentType.name; let selections = node.selections; - if (mapping[parentTypeName]) { - selections = selections.concat(mapping[parentTypeName].selectionSet.selections); + if (mapping[parentTypeName] != null) { + const selectionSet = mapping[parentTypeName].selectionSet; + if (selectionSet != null) { + selections = selections.concat(selectionSet.selections); + } } if (selections !== node.selections) { diff --git a/src/transforms/AddReplacementFragments.ts b/src/transforms/AddReplacementFragments.ts index b0c131cb101..9b31ac0b9d3 100644 --- a/src/transforms/AddReplacementFragments.ts +++ b/src/transforms/AddReplacementFragments.ts @@ -1,3 +1,7 @@ +import { Request, ReplacementFragmentMapping } from '../Interfaces'; + +import { Transform } from './transforms'; + import { DocumentNode, GraphQLSchema, @@ -8,12 +12,10 @@ import { visit, visitWithTypeInfo, } from 'graphql'; -import { Request, ReplacementFragmentMapping } from '../Interfaces'; -import { Transform } from './transforms'; export default class AddReplacementFragments implements Transform { - private targetSchema: GraphQLSchema; - private mapping: ReplacementFragmentMapping; + private readonly targetSchema: GraphQLSchema; + private readonly mapping: ReplacementFragmentMapping; constructor( targetSchema: GraphQLSchema, @@ -48,17 +50,17 @@ function replaceFieldsWithFragments( [Kind.SELECTION_SET]( node: SelectionSetNode, ): SelectionSetNode | null | undefined { - const parentType: GraphQLType = typeInfo.getParentType(); - if (parentType) { + const parentType: GraphQLType | null | undefined = typeInfo.getParentType(); + if (parentType != null) { const parentTypeName = parentType.name; let selections = node.selections; - if (mapping[parentTypeName]) { + if (mapping[parentTypeName] != null) { node.selections.forEach(selection => { if (selection.kind === Kind.FIELD) { const name = selection.name.value; const fragment = mapping[parentTypeName][name]; - if (fragment) { + if (fragment != null) { selections = selections.concat(fragment); } } diff --git a/src/transforms/AddReplacementSelectionSets.ts b/src/transforms/AddReplacementSelectionSets.ts index 7afe04dac4b..5e8d25b6a0a 100644 --- a/src/transforms/AddReplacementSelectionSets.ts +++ b/src/transforms/AddReplacementSelectionSets.ts @@ -1,3 +1,7 @@ +import { Request, ReplacementSelectionSetMapping } from '../Interfaces'; + +import { Transform } from './transforms'; + import { DocumentNode, GraphQLSchema, @@ -8,12 +12,10 @@ import { visit, visitWithTypeInfo, } from 'graphql'; -import { Request, ReplacementSelectionSetMapping } from '../Interfaces'; -import { Transform } from './transforms'; export default class AddReplacementSelectionSets implements Transform { - private schema: GraphQLSchema; - private mapping: ReplacementSelectionSetMapping; + private readonly schema: GraphQLSchema; + private readonly mapping: ReplacementSelectionSetMapping; constructor( schema: GraphQLSchema, @@ -48,17 +50,17 @@ function replaceFieldsWithSelectionSet( [Kind.SELECTION_SET]( node: SelectionSetNode, ): SelectionSetNode | null | undefined { - const parentType: GraphQLType = typeInfo.getParentType(); - if (parentType) { + const parentType: GraphQLType | null | undefined = typeInfo.getParentType(); + if (parentType != null) { const parentTypeName = parentType.name; let selections = node.selections; - if (mapping[parentTypeName]) { + if (mapping[parentTypeName] != null) { node.selections.forEach(selection => { if (selection.kind === Kind.FIELD) { const name = selection.name.value; const selectionSet = mapping[parentTypeName][name]; - if (selectionSet) { + if (selectionSet != null) { selections = selections.concat(selectionSet.selections); } } diff --git a/src/transforms/AddTypenameToAbstract.ts b/src/transforms/AddTypenameToAbstract.ts index be773a65b6a..f700bdde441 100644 --- a/src/transforms/AddTypenameToAbstract.ts +++ b/src/transforms/AddTypenameToAbstract.ts @@ -1,10 +1,12 @@ -import { GraphQLSchema } from 'graphql'; import { Request } from '../Interfaces'; -import { Transform } from './transforms'; import { addTypenameToAbstract } from '../stitching/addTypenameToAbstract'; +import { Transform } from './transforms'; + +import { GraphQLSchema } from 'graphql'; + export default class AddTypenameToAbstract implements Transform { - private targetSchema: GraphQLSchema; + private readonly targetSchema: GraphQLSchema; constructor(targetSchema: GraphQLSchema) { this.targetSchema = targetSchema; diff --git a/src/transforms/CheckResultAndHandleErrors.ts b/src/transforms/CheckResultAndHandleErrors.ts index 3d8d5b0cdd9..a49c8325fa6 100644 --- a/src/transforms/CheckResultAndHandleErrors.ts +++ b/src/transforms/CheckResultAndHandleErrors.ts @@ -1,15 +1,17 @@ -import { GraphQLSchema, GraphQLOutputType } from 'graphql'; import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; -import { Transform } from './transforms'; import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; +import { Transform } from './transforms'; + +import { GraphQLSchema, GraphQLOutputType } from 'graphql'; + export default class CheckResultAndHandleErrors implements Transform { - private context?: Record; - private info: IGraphQLToolsResolveInfo; - private fieldName?: string; - private subschema?: GraphQLSchema | SubschemaConfig; - private returnType?: GraphQLOutputType; - private typeMerge?: boolean; + private readonly context?: Record; + private readonly info: IGraphQLToolsResolveInfo; + private readonly fieldName?: string; + private readonly subschema?: GraphQLSchema | SubschemaConfig; + private readonly returnType?: GraphQLOutputType; + private readonly typeMerge?: boolean; constructor( info: IGraphQLToolsResolveInfo, @@ -30,7 +32,7 @@ export default class CheckResultAndHandleErrors implements Transform { public transformResult(result: any): any { return checkResultAndHandleErrors( result, - this.context, + this.context != null ? this.context : {}, this.info, this.fieldName, this.subschema, diff --git a/src/transforms/ExpandAbstractTypes.ts b/src/transforms/ExpandAbstractTypes.ts index 1ca923383a2..37e2fd0645a 100644 --- a/src/transforms/ExpandAbstractTypes.ts +++ b/src/transforms/ExpandAbstractTypes.ts @@ -1,3 +1,6 @@ +import implementsAbstractType from '../utils/implementsAbstractType'; +import { Transform, Request } from '../Interfaces'; + import { DocumentNode, FragmentDefinitionNode, @@ -13,15 +16,13 @@ import { visit, visitWithTypeInfo, } from 'graphql'; -import implementsAbstractType from '../utils/implementsAbstractType'; -import { Transform, Request } from '../Interfaces'; type TypeMapping = { [key: string]: Array }; export default class ExpandAbstractTypes implements Transform { - private targetSchema: GraphQLSchema; - private mapping: TypeMapping; - private reverseMapping: TypeMapping; + private readonly targetSchema: GraphQLSchema; + private readonly mapping: TypeMapping; + private readonly reverseMapping: TypeMapping; constructor(sourceSchema: GraphQLSchema, targetSchema: GraphQLSchema) { this.targetSchema = targetSchema; @@ -54,7 +55,7 @@ function extractPossibleTypes( if (isAbstractType(type)) { const targetType = targetSchema.getType(typeName); if (!isAbstractType(targetType)) { - const implementations = sourceSchema.getPossibleTypes(type) || []; + const implementations = sourceSchema.getPossibleTypes(type); mapping[typeName] = implementations .filter(impl => targetSchema.getType(impl.name)) .map(impl => impl.name); @@ -69,7 +70,7 @@ function flipMapping(mapping: TypeMapping): TypeMapping { Object.keys(mapping).forEach(typeName => { const toTypeNames = mapping[typeName]; toTypeNames.forEach(toTypeName => { - if (!result[toTypeName]) { + if (result[toTypeName] == null) { result[toTypeName] = []; } result[toTypeName].push(typeName); @@ -98,7 +99,7 @@ function expandAbstractTypes( const generateFragmentName = (typeName: string) => { let fragmentName; do { - fragmentName = `_${typeName}_Fragment${fragmentCounter}`; + fragmentName = `_${typeName}_Fragment${fragmentCounter.toString()}`; fragmentCounter++; } while (existingFragmentNames.indexOf(fragmentName) !== -1); return fragmentName; @@ -112,7 +113,7 @@ function expandAbstractTypes( fragments.forEach((fragment: FragmentDefinitionNode) => { newFragments.push(fragment); const possibleTypes = mapping[fragment.typeCondition.name.value]; - if (possibleTypes) { + if (possibleTypes != null) { fragmentReplacements[fragment.name.value] = []; possibleTypes.forEach(possibleTypeName => { const name = generateFragmentName(possibleTypeName); @@ -152,69 +153,68 @@ function expandAbstractTypes( visitWithTypeInfo(typeInfo, { [Kind.SELECTION_SET](node: SelectionSetNode) { const newSelections = [...node.selections]; - const parentType: GraphQLNamedType = getNamedType( - typeInfo.getParentType(), - ); - node.selections.forEach((selection: SelectionNode) => { - if (selection.kind === Kind.INLINE_FRAGMENT) { - const possibleTypes = mapping[selection.typeCondition.name.value]; - if (possibleTypes) { - possibleTypes.forEach(possibleType => { - if ( - implementsAbstractType( - targetSchema, - parentType, - targetSchema.getType(possibleType), - ) - ) { - newSelections.push({ - kind: Kind.INLINE_FRAGMENT, - typeCondition: { - kind: Kind.NAMED_TYPE, + const maybeType = typeInfo.getParentType(); + if (maybeType != null) { + const parentType: GraphQLNamedType = getNamedType(maybeType); + node.selections.forEach((selection: SelectionNode) => { + if (selection.kind === Kind.INLINE_FRAGMENT) { + if (selection.typeCondition != null) { + const possibleTypes = mapping[selection.typeCondition.name.value]; + if (possibleTypes != null) { + possibleTypes.forEach(possibleType => { + const maybePossibleType = targetSchema.getType(possibleType); + if ( + maybePossibleType != null && + implementsAbstractType(targetSchema, parentType, maybePossibleType) + ) { + newSelections.push({ + kind: Kind.INLINE_FRAGMENT, + typeCondition: { + kind: Kind.NAMED_TYPE, + name: { + kind: Kind.NAME, + value: possibleType, + }, + }, + selectionSet: selection.selectionSet, + }); + } + }); + } + } + } else if (selection.kind === Kind.FRAGMENT_SPREAD) { + const fragmentName = selection.name.value; + const replacements = fragmentReplacements[fragmentName]; + if (replacements != null) { + replacements.forEach(replacement => { + const typeName = replacement.typeName; + const maybeReplacementType = targetSchema.getType(typeName); + if ( + maybeReplacementType != null && + implementsAbstractType(targetSchema, parentType, maybeType) + ) { + newSelections.push({ + kind: Kind.FRAGMENT_SPREAD, name: { kind: Kind.NAME, - value: possibleType, + value: replacement.fragmentName, }, - }, - selectionSet: selection.selectionSet, - }); - } - }); - } - } else if (selection.kind === Kind.FRAGMENT_SPREAD) { - const fragmentName = selection.name.value; - const replacements = fragmentReplacements[fragmentName]; - if (replacements) { - replacements.forEach(replacement => { - const typeName = replacement.typeName; - if ( - implementsAbstractType( - targetSchema, - parentType, - targetSchema.getType(typeName), - ) - ) { - newSelections.push({ - kind: Kind.FRAGMENT_SPREAD, - name: { - kind: Kind.NAME, - value: replacement.fragmentName, - }, - }); - } - }); + }); + } + }); + } } - } - }); - - if (parentType && reverseMapping[parentType.name]) { - newSelections.push({ - kind: Kind.FIELD, - name: { - kind: Kind.NAME, - value: '__typename', - }, }); + + if (reverseMapping[parentType.name] != null) { + newSelections.push({ + kind: Kind.FIELD, + name: { + kind: Kind.NAME, + value: '__typename', + }, + }); + } } if (newSelections.length !== node.selections.length) { diff --git a/src/transforms/ExtendSchema.ts b/src/transforms/ExtendSchema.ts index 00bfb346b59..623df9a0671 100644 --- a/src/transforms/ExtendSchema.ts +++ b/src/transforms/ExtendSchema.ts @@ -1,17 +1,22 @@ -/* tslint:disable:no-unused-expression */ - -import { GraphQLSchema, extendSchema, parse, } from 'graphql'; import { IFieldResolver, IResolvers, Request } from '../Interfaces'; -import { Transform } from './transforms'; + import { addResolversToSchema } from '../generate'; import { defaultMergedResolver } from '../stitching'; -import { default as MapFields, FieldNodeTransformerMap } from './MapFields'; + +import { Transform } from './transforms'; +import MapFields, { FieldNodeTransformerMap } from './MapFields'; + +import { + GraphQLSchema, + extendSchema, + parse, +} from 'graphql'; export default class ExtendSchema implements Transform { - private typeDefs: string; - private resolvers: IResolvers; - private defaultFieldResolver: IFieldResolver; - private transformer: Transform; + private readonly typeDefs: string | undefined; + private readonly resolvers: IResolvers | undefined; + private readonly defaultFieldResolver: IFieldResolver | undefined; + private readonly transformer: MapFields; constructor({ typeDefs, @@ -25,23 +30,17 @@ export default class ExtendSchema implements Transform { fieldNodeTransformerMap?: FieldNodeTransformerMap; }) { this.typeDefs = typeDefs; - this.resolvers = resolvers, - this.defaultFieldResolver = defaultFieldResolver || defaultMergedResolver; - this.transformer = new MapFields(fieldNodeTransformerMap); + this.resolvers = resolvers; + this.defaultFieldResolver = defaultFieldResolver != null ? defaultFieldResolver : defaultMergedResolver; + this.transformer = new MapFields(fieldNodeTransformerMap != null ? fieldNodeTransformerMap : {}); } public transformSchema(schema: GraphQLSchema): GraphQLSchema { this.transformer.transformSchema(schema); - let newSchema: GraphQLSchema; - - if (this.typeDefs) { - newSchema = extendSchema(schema, parse(this.typeDefs)); - } - return addResolversToSchema({ - schema: newSchema || schema, - resolvers: this.resolvers, + schema: this.typeDefs ? extendSchema(schema, parse(this.typeDefs)) : schema, + resolvers: this.resolvers != null ? this.resolvers : {}, defaultFieldResolver: this.defaultFieldResolver, }); } diff --git a/src/transforms/ExtractField.ts b/src/transforms/ExtractField.ts index 216d8f61e7c..15d13f817c6 100644 --- a/src/transforms/ExtractField.ts +++ b/src/transforms/ExtractField.ts @@ -1,9 +1,16 @@ -import { visit, Kind, SelectionSetNode, BREAK, FieldNode } from 'graphql'; import { Transform, Request } from '../Interfaces'; +import { + visit, + Kind, + SelectionSetNode, + BREAK, + FieldNode, +} from 'graphql'; + export default class ExtractField implements Transform { - private from: Array; - private to: Array; + private readonly from: Array; + private readonly to: Array; constructor({ from, to }: { from: Array; to: Array }) { this.from = from; @@ -11,7 +18,7 @@ export default class ExtractField implements Transform { } public transformRequest(originalRequest: Request): Request { - let fromSelection: SelectionSetNode; + let fromSelection: SelectionSetNode | undefined; const ourPathFrom = JSON.stringify(this.from); const ourPathTo = JSON.stringify(this.to); let fieldPath: Array = []; @@ -24,7 +31,7 @@ export default class ExtractField implements Transform { return BREAK; } }, - leave: (node: FieldNode) => { + leave: () => { fieldPath.pop(); }, }, @@ -35,14 +42,14 @@ export default class ExtractField implements Transform { [Kind.FIELD]: { enter: (node: FieldNode) => { fieldPath.push(node.name.value); - if (ourPathTo === JSON.stringify(fieldPath) && fromSelection) { + if (ourPathTo === JSON.stringify(fieldPath) && fromSelection != null) { return { ...node, selectionSet: fromSelection, }; } }, - leave: (node: FieldNode) => { + leave: () => { fieldPath.pop(); }, }, diff --git a/src/transforms/FilterObjectFields.ts b/src/transforms/FilterObjectFields.ts index 2511ea9b9a5..f47b66fcf8f 100644 --- a/src/transforms/FilterObjectFields.ts +++ b/src/transforms/FilterObjectFields.ts @@ -1,11 +1,12 @@ -import { GraphQLField, GraphQLSchema } from 'graphql'; import { Transform } from './transforms'; import TransformObjectFields from './TransformObjectFields'; +import { GraphQLField, GraphQLSchema } from 'graphql'; + export type ObjectFilter = (typeName: string, fieldName: string, field: GraphQLField) => boolean; export default class FilterObjectFields implements Transform { - private transformer: TransformObjectFields; + private readonly transformer: TransformObjectFields; constructor(filter: ObjectFilter) { this.transformer = new TransformObjectFields( diff --git a/src/transforms/FilterRootFields.ts b/src/transforms/FilterRootFields.ts index bc7a2c1979b..4e316912ce5 100644 --- a/src/transforms/FilterRootFields.ts +++ b/src/transforms/FilterRootFields.ts @@ -1,7 +1,8 @@ -import { GraphQLField, GraphQLSchema } from 'graphql'; import { Transform } from './transforms'; import TransformRootFields from './TransformRootFields'; +import { GraphQLField, GraphQLSchema } from 'graphql'; + export type RootFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', fieldName: string, @@ -9,7 +10,7 @@ export type RootFilter = ( ) => boolean; export default class FilterRootFields implements Transform { - private transformer: TransformRootFields; + private readonly transformer: TransformRootFields; constructor(filter: RootFilter) { this.transformer = new TransformRootFields( @@ -20,9 +21,9 @@ export default class FilterRootFields implements Transform { ) => { if (filter(operation, fieldName, field)) { return undefined; - } else { - return null; } + + return null; }, ); } diff --git a/src/transforms/FilterToSchema.ts b/src/transforms/FilterToSchema.ts index 85339d78b7e..2ae2ee615bf 100644 --- a/src/transforms/FilterToSchema.ts +++ b/src/transforms/FilterToSchema.ts @@ -1,3 +1,8 @@ +import { Request } from '../Interfaces'; +import implementsAbstractType from '../utils/implementsAbstractType'; + +import { Transform } from './transforms'; + import { ArgumentNode, DocumentNode, @@ -20,12 +25,9 @@ import { visitWithTypeInfo, getNamedType, } from 'graphql'; -import { Request } from '../Interfaces'; -import implementsAbstractType from '../utils/implementsAbstractType'; -import { Transform } from './transforms'; export default class FilterToSchema implements Transform { - private targetSchema: GraphQLSchema; + private readonly targetSchema: GraphQLSchema; constructor(targetSchema: GraphQLSchema) { this.targetSchema = targetSchema; @@ -149,20 +151,22 @@ function filterToSchema( function collectFragmentVariables( targetSchema: GraphQLSchema, - fragmentSet: Object, + fragmentSet: object, validFragments: Array, validFragmentsWithType: { [name: string]: GraphQLType }, usedFragments: Array, ) { + let remainingFragments = usedFragments.slice(); + let usedVariables: Array = []; - let newFragments: Array = []; + const newFragments: Array = []; - while (usedFragments.length !== 0) { - const nextFragmentName = usedFragments.pop(); + while (remainingFragments.length !== 0) { + const nextFragmentName = remainingFragments.pop(); const fragment = validFragments.find( fr => fr.name.value === nextFragmentName, ); - if (fragment) { + if (fragment != null) { const name = nextFragmentName; const typeName = fragment.typeCondition.name.value; const type = targetSchema.getType(typeName); @@ -176,7 +180,7 @@ function collectFragmentVariables( validFragmentsWithType, fragment.selectionSet, ); - usedFragments = union(usedFragments, fragmentUsedFragments); + remainingFragments = union(remainingFragments, fragmentUsedFragments); usedVariables = union(usedVariables, fragmentUsedVariables); if (!fragmentSet[name]) { @@ -228,11 +232,9 @@ function filterSelectionSet( return null; } - const argNames = (field.args || []).map(arg => arg.name); - if (node.arguments) { - let args = node.arguments.filter((arg: ArgumentNode) => { - return argNames.indexOf(arg.name.value) !== -1; - }); + const argNames = (field.args != null ? field.args : []).map(arg => arg.name); + if (node.arguments != null) { + const args = node.arguments.filter((arg: ArgumentNode) => argNames.indexOf(arg.name.value) !== -1); if (args.length !== node.arguments.length) { return { ...node, @@ -248,8 +250,8 @@ function filterSelectionSet( resolvedType instanceof GraphQLObjectType || resolvedType instanceof GraphQLInterfaceType ) { - const selections = node.selectionSet && node.selectionSet.selections || null; - if (!selections || selections.length === 0) { + const selections = node.selectionSet != null ? node.selectionSet.selections : null; + if (selections == null || selections.length === 0) { // need to remove any added variables. Is there a better way to do this? visit(node, { [Kind.VARIABLE](variableNode: VariableNode) { @@ -271,23 +273,21 @@ function filterSelectionSet( const innerType = validFragments[node.name.value]; if (!implementsAbstractType(schema, parentType, innerType)) { return null; - } else { - usedFragments.push(node.name.value); - return; } - } else { - return null; + + usedFragments.push(node.name.value); + return; } + + return null; }, [Kind.INLINE_FRAGMENT]: { enter(node: InlineFragmentNode): null | undefined { - if (node.typeCondition) { + if (node.typeCondition != null) { const parentType = typeInfo.getParentType(); const innerType = schema.getType(node.typeCondition.name.value); if (!implementsAbstractType(schema, parentType, innerType)) { return null; - } else { - return; } } }, diff --git a/src/transforms/FilterTypes.ts b/src/transforms/FilterTypes.ts index 6f436ca9480..6500f5d4bb4 100644 --- a/src/transforms/FilterTypes.ts +++ b/src/transforms/FilterTypes.ts @@ -1,12 +1,11 @@ -/* tslint:disable:no-unused-expression */ - -import { GraphQLSchema, GraphQLNamedType } from 'graphql'; import { Transform } from '../transforms/transforms'; import { visitSchema } from '../utils/visitSchema'; import { VisitSchemaKind } from '../Interfaces'; +import { GraphQLSchema, GraphQLNamedType } from 'graphql'; + export default class FilterTypes implements Transform { - private filter: (type: GraphQLNamedType) => boolean; + private readonly filter: (type: GraphQLNamedType) => boolean; constructor(filter: (type: GraphQLNamedType) => boolean) { this.filter = filter; @@ -17,9 +16,9 @@ export default class FilterTypes implements Transform { [VisitSchemaKind.TYPE]: (type: GraphQLNamedType) => { if (this.filter(type)) { return undefined; - } else { - return null; } + + return null; }, }); } diff --git a/src/transforms/HoistField.ts b/src/transforms/HoistField.ts index 595b09b94e7..2f4ed8f4ba0 100644 --- a/src/transforms/HoistField.ts +++ b/src/transforms/HoistField.ts @@ -1,24 +1,24 @@ -/* tslint:disable:no-unused-expression */ +import { healSchema, wrapFieldNode, renameFieldNode } from '../utils'; +import { createMergedResolver } from '../stitching'; +import { appendFields, removeFields } from '../utils/fields'; +import { Request } from '../Interfaces'; + +import { Transform } from './transforms'; +import MapFields from './MapFields'; import { GraphQLSchema, GraphQLObjectType, getNullableType, } from 'graphql'; -import { Request } from '../Interfaces'; -import { Transform } from './transforms'; -import { healSchema, wrapFieldNode, renameFieldNode } from '../utils'; -import { createMergedResolver } from '../stitching'; -import { default as MapFields } from './MapFields'; -import { appendFields, removeFields } from '../utils/fields'; export default class HoistField implements Transform { - private typeName: string; - private path: Array; - private newFieldName: string; - private pathToField: Array; - private oldFieldName: string; - private transformer: Transform; + private readonly typeName: string; + private readonly path: Array; + private readonly newFieldName: string; + private readonly pathToField: Array; + private readonly oldFieldName: string; + private readonly transformer: Transform; constructor( typeName: string, @@ -44,10 +44,10 @@ export default class HoistField implements Transform { public transformSchema(schema: GraphQLSchema): GraphQLSchema { const typeMap = schema.getTypeMap(); - const innerType: GraphQLObjectType = this.pathToField.reduce( + const innerType: GraphQLObjectType = this.pathToField.reduce( (acc, pathSegment) => - getNullableType(acc.getFields()[pathSegment].type) as GraphQLObjectType, - typeMap[this.typeName] as GraphQLObjectType + getNullableType(acc.getFields()[pathSegment].type) as GraphQLObjectType, + typeMap[this.typeName] as GraphQLObjectType ); const targetField = removeFields( @@ -56,7 +56,7 @@ export default class HoistField implements Transform { fieldName => fieldName === this.oldFieldName, )[this.oldFieldName]; - const targetType = (targetField.type as GraphQLObjectType); + const targetType = (targetField.type as GraphQLObjectType); appendFields(typeMap, this.typeName, { [this.newFieldName]: { diff --git a/src/transforms/MapFields.ts b/src/transforms/MapFields.ts index 3575d6832dc..86be806cb0c 100644 --- a/src/transforms/MapFields.ts +++ b/src/transforms/MapFields.ts @@ -1,3 +1,7 @@ +import { Request } from '../Interfaces'; + +import { Transform } from './transforms'; + import { GraphQLSchema, GraphQLType, @@ -7,12 +11,9 @@ import { visit, visitWithTypeInfo, Kind, - SelectionSetNode, SelectionNode, FragmentDefinitionNode } from 'graphql'; -import { Request } from '../Interfaces'; -import { Transform } from './transforms'; export type FieldNodeTransformer = ( fieldNode: FieldNode, @@ -26,8 +27,8 @@ export type FieldNodeTransformerMap = { }; export default class MapFields implements Transform { - private schema: GraphQLSchema; - private fieldNodeTransformerMap: FieldNodeTransformerMap; + private schema: GraphQLSchema | undefined; + private readonly fieldNodeTransformerMap: FieldNodeTransformerMap; constructor( fieldNodeTransformerMap: FieldNodeTransformerMap, @@ -41,6 +42,10 @@ export default class MapFields implements Transform { } public transformRequest(originalRequest: Request): Request { + if (!this.schema) { + throw new Error('MapFields transform required initialization with target schema within the transformSchema method.') + } + const fragments = {}; originalRequest.document.definitions.filter( def => def.kind === Kind.FRAGMENT_DEFINITION @@ -70,22 +75,28 @@ function transformDocument( const newDocument: DocumentNode = visit( document, visitWithTypeInfo(typeInfo, { - [Kind.SELECTION_SET](node: SelectionSetNode): SelectionSetNode { - const parentType: GraphQLType = typeInfo.getParentType(); - if (parentType) { + [Kind.SELECTION_SET]: node => { + const parentType: GraphQLType | null | undefined = typeInfo.getParentType(); + if (parentType != null) { const parentTypeName = parentType.name; + const fieldNodeTransformers = fieldNodeTransformerMap[parentTypeName]; let newSelections: Array = []; node.selections.forEach(selection => { if (selection.kind === Kind.FIELD) { const fieldName = selection.name.value; - const fieldNodeTransformer = - fieldNodeTransformerMap[parentTypeName] && fieldNodeTransformerMap[parentTypeName][fieldName]; - - const transformedSelection = fieldNodeTransformer - ? fieldNodeTransformer(selection, fragments) - : selection; + let transformedSelection; + if (fieldNodeTransformers != null) { + const fieldNodeTransformer = fieldNodeTransformers[fieldName]; + if (fieldNodeTransformer != null) { + transformedSelection = fieldNodeTransformer(selection, fragments); + } else { + transformedSelection = selection; + } + } else { + transformedSelection = selection; + } if (Array.isArray(transformedSelection)) { newSelections = newSelections.concat(transformedSelection); diff --git a/src/transforms/RenameObjectFields.ts b/src/transforms/RenameObjectFields.ts index 901cd6e070e..4e56b37a02a 100644 --- a/src/transforms/RenameObjectFields.ts +++ b/src/transforms/RenameObjectFields.ts @@ -1,18 +1,18 @@ -import { GraphQLField, GraphQLSchema } from 'graphql'; -import { Transform } from './transforms'; import { Request } from '../Interfaces'; + import TransformObjectFields from './TransformObjectFields'; +import { Transform } from './transforms'; + +import { GraphQLField, GraphQLSchema } from 'graphql'; export default class RenameObjectFields implements Transform { - private transformer: TransformObjectFields; + private readonly transformer: TransformObjectFields; constructor(renamer: (typeName: string, fieldName: string, field: GraphQLField) => string) { this.transformer = new TransformObjectFields( - (typeName: string, fieldName: string, field: GraphQLField) => { - return { + (typeName: string, fieldName: string, field: GraphQLField) => ({ name: renamer(typeName, fieldName, field), - }; - } + }) ); } diff --git a/src/transforms/RenameRootFields.ts b/src/transforms/RenameRootFields.ts index 6fb7d9e60b6..e8f934fc32a 100644 --- a/src/transforms/RenameRootFields.ts +++ b/src/transforms/RenameRootFields.ts @@ -1,10 +1,12 @@ -import { GraphQLField, GraphQLSchema } from 'graphql'; import { Request } from '../Interfaces'; + import { Transform } from './transforms'; import TransformRootFields from './TransformRootFields'; +import { GraphQLField, GraphQLSchema } from 'graphql'; + export default class RenameRootFields implements Transform { - private transformer: TransformRootFields; + private readonly transformer: TransformRootFields; constructor( renamer: ( @@ -18,11 +20,9 @@ export default class RenameRootFields implements Transform { operation: 'Query' | 'Mutation' | 'Subscription', fieldName: string, field: GraphQLField, - ) => { - return { + ) => ({ name: renamer(operation, fieldName, field), - }; - }, + }), ); } diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index 4eab6af5b67..3dd542003b6 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -1,3 +1,8 @@ +import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; +import { Request, Result, VisitSchemaKind } from '../Interfaces'; +import { Transform } from '../transforms/transforms'; +import { visitSchema, cloneType } from '../utils'; + import { visit, GraphQLSchema, @@ -6,10 +11,6 @@ import { GraphQLNamedType, GraphQLScalarType, } from 'graphql'; -import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; -import { Request, Result, VisitSchemaKind } from '../Interfaces'; -import { Transform } from '../transforms/transforms'; -import { visitSchema, cloneType } from '../utils'; export type RenameOptions = { renameBuiltins: boolean; @@ -17,11 +18,11 @@ export type RenameOptions = { }; export default class RenameTypes implements Transform { - private renamer: (name: string) => string | undefined; + private readonly renamer: (name: string) => string | undefined; private map: { [key: string]: string }; private reverseMap: { [key: string]: string }; - private renameBuiltins: boolean; - private renameScalars: boolean; + private readonly renameBuiltins: boolean; + private readonly renameScalars: boolean; constructor( renamer: (name: string) => string | undefined, @@ -30,7 +31,7 @@ export default class RenameTypes implements Transform { this.renamer = renamer; this.map = {}; this.reverseMap = {}; - const { renameBuiltins = false, renameScalars = true } = options || {}; + const { renameBuiltins = false, renameScalars = true } = (options != null) ? options : {}; this.renameBuiltins = renameBuiltins; this.renameScalars = renameScalars; } @@ -55,7 +56,7 @@ export default class RenameTypes implements Transform { } }, - [VisitSchemaKind.ROOT_OBJECT](type: GraphQLNamedType) { + [VisitSchemaKind.ROOT_OBJECT]() { return undefined; }, }); @@ -103,8 +104,8 @@ export default class RenameTypes implements Transform { this.renamer(value[key]) : this.renameTypes(value[key]); }); return value; - } else { - return value; } + + return value; } } diff --git a/src/transforms/ReplaceFieldWithFragment.ts b/src/transforms/ReplaceFieldWithFragment.ts index 62d6b32ac89..c91f4ce05f7 100644 --- a/src/transforms/ReplaceFieldWithFragment.ts +++ b/src/transforms/ReplaceFieldWithFragment.ts @@ -1,3 +1,8 @@ +import { concatInlineFragments } from '../utils'; +import { Request } from '../Interfaces'; + +import { Transform } from './transforms'; + import { DocumentNode, GraphQLSchema, @@ -11,13 +16,10 @@ import { visit, visitWithTypeInfo, } from 'graphql'; -import { Request } from '../Interfaces'; -import { Transform } from './transforms'; -import { concatInlineFragments } from '../utils'; export default class ReplaceFieldWithFragment implements Transform { - private targetSchema: GraphQLSchema; - private mapping: FieldToFragmentMapping; + private readonly targetSchema: GraphQLSchema; + private readonly mapping: FieldToFragmentMapping; constructor( targetSchema: GraphQLSchema, @@ -31,12 +33,14 @@ export default class ReplaceFieldWithFragment implements Transform { for (const { field, fragment } of fragments) { const parsedFragment = parseFragmentToInlineFragment(fragment); const actualTypeName = parsedFragment.typeCondition.name.value; - this.mapping[actualTypeName] = this.mapping[actualTypeName] || {}; + if (this.mapping[actualTypeName] == null) { + this.mapping[actualTypeName] = {}; + } - if (this.mapping[actualTypeName][field]) { - this.mapping[actualTypeName][field].push(parsedFragment); - } else { + if (this.mapping[actualTypeName][field] == null) { this.mapping[actualTypeName][field] = [parsedFragment]; + } else { + this.mapping[actualTypeName][field].push(parsedFragment); } } } @@ -55,7 +59,7 @@ export default class ReplaceFieldWithFragment implements Transform { } type FieldToFragmentMapping = { - [typeName: string]: { [fieldName: string]: InlineFragmentNode[] }; + [typeName: string]: { [fieldName: string]: Array }; }; function replaceFieldsWithFragments( @@ -71,16 +75,16 @@ function replaceFieldsWithFragments( node: SelectionSetNode, ): SelectionSetNode | null | undefined { const parentType: GraphQLType = typeInfo.getParentType(); - if (parentType) { + if (parentType != null) { const parentTypeName = parentType.name; let selections = node.selections; - if (mapping[parentTypeName]) { + if (mapping[parentTypeName] != null) { node.selections.forEach(selection => { if (selection.kind === Kind.FIELD) { const name = selection.name.value; const fragments = mapping[parentTypeName][name]; - if (fragments && fragments.length > 0) { + if (fragments != null && fragments.length > 0) { const fragment = concatInlineFragments( parentTypeName, fragments, diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index b395c452987..bbf98f55a38 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -1,3 +1,9 @@ +import isEmptyObject from '../utils/isEmptyObject'; +import { Request, VisitSchemaKind } from '../Interfaces'; +import { visitSchema } from '../utils/visitSchema'; + +import { Transform } from './transforms'; + import { GraphQLObjectType, GraphQLSchema, @@ -14,10 +20,6 @@ import { SelectionNode, FragmentDefinitionNode } from 'graphql'; -import isEmptyObject from '../utils/isEmptyObject'; -import { Request, VisitSchemaKind } from '../Interfaces'; -import { Transform } from './transforms'; -import { visitSchema } from '../utils/visitSchema'; export type ObjectFieldTransformer = ( typeName: string, @@ -41,8 +43,8 @@ type FieldMapping = { type RenamedField = { name: string; field?: GraphQLFieldConfig }; export default class TransformObjectFields implements Transform { - private objectFieldTransformer: ObjectFieldTransformer; - private fieldNodeTransformer: FieldNodeTransformer; + private readonly objectFieldTransformer: ObjectFieldTransformer; + private readonly fieldNodeTransformer: FieldNodeTransformer; private transformedSchema: GraphQLSchema; private mapping: FieldMapping; @@ -57,9 +59,7 @@ export default class TransformObjectFields implements Transform { public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { this.transformedSchema = visitSchema(originalSchema, { - [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => { - return this.transformFields(type, this.objectFieldTransformer); - } + [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => this.transformFields(type, this.objectFieldTransformer) }); return this.transformedSchema; @@ -102,7 +102,7 @@ export default class TransformObjectFields implements Transform { const newName = (transformedField as RenamedField).name; if (newName) { - newFields[newName] = (transformedField as RenamedField).field ? + newFields[newName] = (transformedField as RenamedField).field != null ? (transformedField as RenamedField).field : typeConfig.fields[fieldName]; @@ -118,14 +118,15 @@ export default class TransformObjectFields implements Transform { } } }); + if (isEmptyObject(newFields)) { return null; - } else { - return new GraphQLObjectType({ - ...type.toConfig(), - fields: newFields, - }); } + + return new GraphQLObjectType({ + ...type.toConfig(), + fields: newFields, + }); } private transformDocument( @@ -140,43 +141,55 @@ export default class TransformObjectFields implements Transform { visitWithTypeInfo(typeInfo, { [Kind.SELECTION_SET](node: SelectionSetNode): SelectionSetNode { const parentType: GraphQLType = typeInfo.getParentType(); - if (parentType) { + if (parentType != null) { const parentTypeName = parentType.name; let newSelections: Array = []; node.selections.forEach(selection => { - if (selection.kind === Kind.FIELD) { - const newName = selection.name.value; - - const transformedSelection = fieldNodeTransformer - ? fieldNodeTransformer(parentTypeName, newName, selection, fragments) - : selection; - - if (Array.isArray(transformedSelection)) { - newSelections = newSelections.concat(transformedSelection); - } else if (transformedSelection.kind === Kind.FIELD) { - const oldName = mapping[parentTypeName] && mapping[parentTypeName][newName]; - if (oldName) { - newSelections.push({ - ...transformedSelection, - name: { - kind: Kind.NAME, - value: oldName - }, - alias: { - kind: Kind.NAME, - value: newName - } - }); - } else { - newSelections.push(transformedSelection); - } - } else { - newSelections.push(transformedSelection); - } - } else { + if (selection.kind !== Kind.FIELD) { newSelections.push(selection); + return; + } + + const newName = selection.name.value; + + const transformedSelection = fieldNodeTransformer != null + ? fieldNodeTransformer(parentTypeName, newName, selection, fragments) + : selection; + + if (Array.isArray(transformedSelection)) { + newSelections = newSelections.concat(transformedSelection); + return; + } + + if (transformedSelection.kind !== Kind.FIELD) { + newSelections.push(transformedSelection); + return; } + + const typeMapping = mapping[parentTypeName]; + if (typeMapping == null) { + newSelections.push(transformedSelection); + return; + } + + const oldName = mapping[parentTypeName][newName]; + if (oldName == null) { + newSelections.push(transformedSelection); + return; + } + + newSelections.push({ + ...transformedSelection, + name: { + kind: Kind.NAME, + value: oldName + }, + alias: { + kind: Kind.NAME, + value: newName + } + }); }); return { diff --git a/src/transforms/TransformQuery.ts b/src/transforms/TransformQuery.ts index af41383371f..63e6161265b 100644 --- a/src/transforms/TransformQuery.ts +++ b/src/transforms/TransformQuery.ts @@ -1,7 +1,15 @@ -import { visit, Kind, SelectionSetNode, FragmentDefinitionNode, GraphQLError } from 'graphql'; -import { Transform } from './transforms'; import { Request, Result } from '../Interfaces'; +import { Transform } from './transforms'; + +import { + visit, + Kind, + SelectionSetNode, + FragmentDefinitionNode, + GraphQLError, +} from 'graphql'; + export type QueryTransformer = ( selectionSet: SelectionSetNode, fragments: Record @@ -12,11 +20,11 @@ export type ResultTransformer = (result: any) => any; export type ErrorPathTransformer = (path: ReadonlyArray) => Array; export default class TransformQuery implements Transform { - private path: Array; - private queryTransformer: QueryTransformer; - private resultTransformer: ResultTransformer; - private errorPathTransformer: ErrorPathTransformer; - private fragments: Record; + private readonly path: Array; + private readonly queryTransformer: QueryTransformer; + private readonly resultTransformer: ResultTransformer; + private readonly errorPathTransformer: ErrorPathTransformer; + private readonly fragments: Record; constructor({ path, @@ -80,32 +88,33 @@ export default class TransformQuery implements Transform { const errors = originalResult.errors; return { data, - errors: errors ? this.transformErrors(errors) : undefined + errors: errors != null ? this.transformErrors(errors) : undefined }; } private transformData(data: any): any { + const leafIndex = this.path.length - 1; let index = 0; - let leafIndex = this.path.length - 1; - if (data) { + let newData = data; + if (newData) { let next = this.path[index]; while (index < leafIndex) { if (data[next]) { - data = data[next]; + newData = newData[next]; } else { break; } index++; next = this.path[index]; } - data[next] = this.resultTransformer(data[next]); + newData[next] = this.resultTransformer(newData[next]); } - return data; + return newData; } private transformErrors(errors: ReadonlyArray): ReadonlyArray { return errors.map(error => { - let path: ReadonlyArray = error.path; + const path: ReadonlyArray = error.path; let match = true; let index = 0; diff --git a/src/transforms/TransformRootFields.ts b/src/transforms/TransformRootFields.ts index 44f5c39ec84..a123c539407 100644 --- a/src/transforms/TransformRootFields.ts +++ b/src/transforms/TransformRootFields.ts @@ -1,12 +1,13 @@ +import { Request } from '../Interfaces'; + +import { Transform } from './transforms'; +import TransformObjectFields, { FieldNodeTransformer } from './TransformObjectFields'; + import { GraphQLSchema, GraphQLField, GraphQLFieldConfig, } from 'graphql'; -import { Request } from '../Interfaces'; -import { Transform } from './transforms'; -import { TransformObjectFields } from '.'; -import { FieldNodeTransformer } from './TransformObjectFields'; export type RootTransformer = ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -21,16 +22,16 @@ export type RootTransformer = ( type RenamedField = { name: string; field?: GraphQLFieldConfig }; export default class TransformRootFields implements Transform { - private transformer: TransformObjectFields; + private readonly transformer: TransformObjectFields; constructor(rootFieldTransformer: RootTransformer, fieldNodeTransformer?: FieldNodeTransformer) { const rootToObjectFieldTransformer = (typeName: string, fieldName: string, field: GraphQLField) => { if (typeName === 'Query' || typeName === 'Mutation' || typeName === 'Subscription') { return rootFieldTransformer(typeName, fieldName, field); - } else { - return undefined; } + + return undefined; }; this.transformer = new TransformObjectFields( rootToObjectFieldTransformer, diff --git a/src/transforms/WrapFields.ts b/src/transforms/WrapFields.ts index 2f2fc09371c..d6d26064279 100644 --- a/src/transforms/WrapFields.ts +++ b/src/transforms/WrapFields.ts @@ -1,23 +1,23 @@ -/* tslint:disable:no-unused-expression */ +import { Request } from '../Interfaces'; +import { appendFields, removeFields } from '../utils/fields'; +import { hoistFieldNodes, healSchema } from '../utils'; +import { defaultMergedResolver, createMergedResolver } from '../stitching'; + +import { Transform } from './transforms'; +import MapFields from './MapFields'; import { GraphQLSchema, GraphQLObjectType, } from 'graphql'; -import { Request } from '../Interfaces'; -import { Transform } from './transforms'; -import { hoistFieldNodes, healSchema } from '../utils'; -import { defaultMergedResolver, createMergedResolver } from '../stitching'; -import { default as MapFields } from './MapFields'; -import { appendFields, removeFields } from '../utils/fields'; export default class WrapFields implements Transform { - private outerTypeName: string; - private wrappingFieldNames: Array; - private wrappingTypeNames: Array; - private numWraps: number; - private fieldNames: Array; - private transformer: Transform; + private readonly outerTypeName: string; + private readonly wrappingFieldNames: Array; + private readonly wrappingTypeNames: Array; + private readonly numWraps: number; + private readonly fieldNames: Array; + private readonly transformer: Transform; constructor( outerTypeName: string, diff --git a/src/transforms/WrapQuery.ts b/src/transforms/WrapQuery.ts index 7878e7cf9f8..bf78bcd6da6 100644 --- a/src/transforms/WrapQuery.ts +++ b/src/transforms/WrapQuery.ts @@ -1,12 +1,13 @@ -import { FieldNode, visit, Kind, SelectionNode, SelectionSetNode } from 'graphql'; import { Transform, Request, Result } from '../Interfaces'; +import { FieldNode, visit, Kind, SelectionNode, SelectionSetNode } from 'graphql'; + export type QueryWrapper = (subtree: SelectionSetNode) => SelectionNode | SelectionSetNode; export default class WrapQuery implements Transform { - private wrapper: QueryWrapper; - private extractor: (result: any) => any; - private path: Array; + private readonly wrapper: QueryWrapper; + private readonly extractor: (result: any) => any; + private readonly path: Array; constructor(path: Array, wrapper: QueryWrapper, extractor: (result: any) => any) { this.path = path; @@ -28,7 +29,7 @@ export default class WrapQuery implements Transform { // Selection can be either a single selection or a selection set. If it's just one selection, // let's wrap it in a selection set. Otherwise, keep it as is. const selectionSet = - wrapResult.kind === Kind.SELECTION_SET + wrapResult != null && wrapResult.kind === Kind.SELECTION_SET ? wrapResult : { kind: Kind.SELECTION_SET, @@ -41,7 +42,7 @@ export default class WrapQuery implements Transform { }; } }, - leave: (node: FieldNode) => { + leave: () => { fieldPath.pop(); } } @@ -54,7 +55,7 @@ export default class WrapQuery implements Transform { public transformResult(originalResult: Result): Result { const rootData = originalResult.data; - if (rootData) { + if (rootData != null) { let data = rootData; const path = [...this.path]; while (path.length > 1) { diff --git a/src/transforms/WrapType.ts b/src/transforms/WrapType.ts index c23f3130fc1..bb8e2e05266 100644 --- a/src/transforms/WrapType.ts +++ b/src/transforms/WrapType.ts @@ -1,12 +1,12 @@ -/* tslint:disable:no-unused-expression */ - -import { GraphQLSchema } from 'graphql'; import { Request } from '../Interfaces'; + import { Transform } from './transforms'; -import { default as WrapFields } from './WrapFields'; +import WrapFields from './WrapFields'; + +import { GraphQLSchema } from 'graphql'; export default class WrapType implements Transform { - private transformer: Transform; + private readonly transformer: Transform; constructor( outerTypeName: string, diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index 9a341e71326..eaa3950de46 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -1,3 +1,7 @@ +import { GraphQLSchemaWithTransforms, VisitSchemaKind } from '../Interfaces'; +import { visitSchema } from '../utils/visitSchema'; +import { cloneSchema } from '../utils/clone'; + import { GraphQLEnumType, GraphQLInputObjectType, @@ -7,9 +11,6 @@ import { GraphQLUnionType, GraphQLType, } from 'graphql'; -import { GraphQLSchemaWithTransforms, VisitSchemaKind } from '../Interfaces'; -import { visitSchema } from '../utils/visitSchema'; -import { cloneSchema } from '../utils/clone'; export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -33,35 +34,26 @@ export default function filterSchema({ fieldFilter?: (typeName: string, fieldName: string) => boolean; }): GraphQLSchemaWithTransforms { const filteredSchema: GraphQLSchemaWithTransforms = visitSchema(cloneSchema(schema), { - [VisitSchemaKind.QUERY]: (type: GraphQLObjectType) => { - return filterRootFields(type, 'Query', rootFieldFilter); - }, - [VisitSchemaKind.MUTATION]: (type: GraphQLObjectType) => { - return filterRootFields(type, 'Mutation', rootFieldFilter); - }, - [VisitSchemaKind.SUBSCRIPTION]: (type: GraphQLObjectType) => { - return filterRootFields(type, 'Subscription', rootFieldFilter); - }, - [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => { - return (typeFilter(type.name, type)) ? + [VisitSchemaKind.QUERY]: (type: GraphQLObjectType) => + filterRootFields(type, 'Query', rootFieldFilter), + [VisitSchemaKind.MUTATION]: (type: GraphQLObjectType) => + filterRootFields(type, 'Mutation', rootFieldFilter), + [VisitSchemaKind.SUBSCRIPTION]: (type: GraphQLObjectType) => + filterRootFields(type, 'Subscription', rootFieldFilter), + [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => + (typeFilter(type.name, type)) ? filterObjectFields(type, fieldFilter) : - null; - }, - [VisitSchemaKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) => { - return typeFilter(type.name, type) ? undefined : null; - }, - [VisitSchemaKind.UNION_TYPE]: (type: GraphQLUnionType) => { - return typeFilter(type.name, type) ? undefined : null; - }, - [VisitSchemaKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) => { - return typeFilter(type.name, type) ? undefined : null; - }, - [VisitSchemaKind.ENUM_TYPE]: (type: GraphQLEnumType) => { - return typeFilter(type.name, type) ? undefined : null; - }, - [VisitSchemaKind.SCALAR_TYPE]: (type: GraphQLScalarType) => { - return typeFilter(type.name, type) ? undefined : null; - }, + null, + [VisitSchemaKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) => + typeFilter(type.name, type) ? undefined : null, + [VisitSchemaKind.UNION_TYPE]: (type: GraphQLUnionType) => + typeFilter(type.name, type) ? undefined : null, + [VisitSchemaKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) => + typeFilter(type.name, type) ? undefined : null, + [VisitSchemaKind.ENUM_TYPE]: (type: GraphQLEnumType) => + typeFilter(type.name, type) ? undefined : null, + [VisitSchemaKind.SCALAR_TYPE]: (type: GraphQLScalarType) => + typeFilter(type.name, type) ? undefined : null, }); filteredSchema.transforms = schema.transforms; diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index 6278a0576c4..c5c6b3263e3 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -1,6 +1,4 @@ -import { GraphQLSchema } from 'graphql'; import { addResolversToSchema } from '../makeExecutableSchema'; - import { Transform, applySchemaTransforms } from '../transforms/transforms'; import { generateProxyingResolvers, @@ -9,44 +7,52 @@ import { import { SubschemaConfig, isSubschemaConfig, + GraphQLSchemaWithTransforms, } from '../Interfaces'; import { cloneSchema } from '../utils/clone'; +import { GraphQLSchema } from 'graphql'; + export function wrapSchema( - subschema: GraphQLSchema | SubschemaConfig, - transforms: Array = [], + subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, + transforms?: Array, ): GraphQLSchema { - if (isSubschemaConfig(subschema)) { - if (transforms) { - subschema.transforms = (subschema.transforms || []).concat(transforms); - } - } else { - subschema = { - schema: subschema, - transforms, - }; - } + const subschemaConfig: SubschemaConfig = isSubschemaConfig(subschemaOrSubschemaConfig) ? + subschemaOrSubschemaConfig : + { schema: subschemaOrSubschemaConfig }; - const schema = cloneSchema(subschema.schema); + const schema = cloneSchema(subschemaConfig.schema); stripResolvers(schema); addResolversToSchema({ schema, - resolvers: generateProxyingResolvers(subschema), + resolvers: generateProxyingResolvers({ subschemaConfig, transforms }), resolverValidationOptions: { allowResolversNotInSchema: true, }, }); - return applySchemaTransforms(schema, subschema.transforms); + let schemaTransforms: Array = []; + if (subschemaConfig.transforms != null) { + schemaTransforms = schemaTransforms.concat(subschemaConfig.transforms); + } + if (transforms != null) { + schemaTransforms = schemaTransforms.concat(transforms); + } + + return applySchemaTransforms(schema, schemaTransforms); } export default function transformSchema( - subschema: GraphQLSchema | SubschemaConfig, + subschema: GraphQLSchema, transforms: Array, -): GraphQLSchema & { transforms: Array } { - const schema = wrapSchema(subschema, transforms); - (schema as any).transforms = transforms.slice().reverse(); - return schema as GraphQLSchema & { transforms: Array }; +): GraphQLSchemaWithTransforms { + const schema: GraphQLSchemaWithTransforms = wrapSchema({ + schema: subschema, + transforms, + }); + + schema.transforms = transforms.slice().reverse(); + return schema; } diff --git a/src/transforms/transforms.ts b/src/transforms/transforms.ts index a0ddf9cb854..72e9930d478 100644 --- a/src/transforms/transforms.ts +++ b/src/transforms/transforms.ts @@ -1,7 +1,8 @@ -import { GraphQLSchema } from 'graphql'; import { Request, Transform } from '../Interfaces'; import { cloneSchema } from '../utils'; +import { GraphQLSchema } from 'graphql'; + export { Transform }; export function applySchemaTransforms( @@ -10,7 +11,7 @@ export function applySchemaTransforms( ): GraphQLSchema { return transforms.reduce( (schema: GraphQLSchema, transform: Transform) => - transform.transformSchema ? transform.transformSchema(cloneSchema(schema)) : schema, + transform.transformSchema != null ? transform.transformSchema(cloneSchema(schema)) : schema, originalSchema, ); } @@ -21,7 +22,7 @@ export function applyRequestTransforms( ): Request { return transforms.reduce( (request: Request, transform: Transform) => - transform.transformRequest + transform.transformRequest != null ? transform.transformRequest(request) : request, @@ -35,7 +36,7 @@ export function applyResultTransforms( ): any { return transforms.reduceRight( (result: any, transform: Transform) => - transform.transformResult ? transform.transformResult(result) : result, + transform.transformResult != null ? transform.transformResult(result) : result, originalResult, ); } diff --git a/src/utils/SchemaDirectiveVisitor.ts b/src/utils/SchemaDirectiveVisitor.ts index ddd91afcae1..c00de99b8a6 100644 --- a/src/utils/SchemaDirectiveVisitor.ts +++ b/src/utils/SchemaDirectiveVisitor.ts @@ -1,16 +1,19 @@ -import { - GraphQLDirective, - GraphQLSchema, - DirectiveLocationEnum, -} from 'graphql'; +import { VisitableSchemaType } from '../Interfaces'; -import { getArgumentValues } from 'graphql/execution/values'; import each from './each'; import valueFromASTUntyped from './valueFromASTUntyped'; -import { VisitableSchemaType } from '../Interfaces'; + import { SchemaVisitor } from './SchemaVisitor'; import { visitSchema } from './visitSchema'; +import { getArgumentValues } from 'graphql/execution/values'; + +import { + GraphQLDirective, + GraphQLSchema, + DirectiveLocationEnum, +} from 'graphql'; + const hasOwn = Object.prototype.hasOwnProperty; // This class represents a reusable implementation of a @directive that may @@ -86,7 +89,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { public static getDirectiveDeclaration( directiveName: string, schema: GraphQLSchema, - ): GraphQLDirective { + ): GraphQLDirective | null | undefined { return schema.getDirective(directiveName); } @@ -114,7 +117,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { ): { // The visitSchemaDirectives method returns a map from directive names to // lists of SchemaDirectiveVisitors created while visiting the schema. - [directiveName: string]: SchemaDirectiveVisitor[], + [directiveName: string]: Array, } { // If the schema declares any directives for public consumption, record // them here so that we can properly coerce arguments when/if we encounter @@ -125,7 +128,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { // Map from directive names to lists of SchemaDirectiveVisitor instances // created while visiting the schema. const createdVisitors: { - [directiveName: string]: SchemaDirectiveVisitor[] + [directiveName: string]: Array } = Object.create(null); Object.keys(directiveVisitors).forEach(directiveName => { createdVisitors[directiveName] = []; @@ -134,9 +137,9 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { function visitorSelector( type: VisitableSchemaType, methodName: string, - ): SchemaDirectiveVisitor[] { - const visitors: SchemaDirectiveVisitor[] = []; - const directiveNodes = type.astNode && type.astNode.directives; + ): Array { + const visitors: Array = []; + const directiveNodes = (type.astNode != null) ? type.astNode.directives : null; if (! directiveNodes) { return visitors; } @@ -158,7 +161,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { const decl = declaredDirectives[directiveName]; let args: { [key: string]: any }; - if (decl) { + if (decl != null) { // 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. @@ -167,9 +170,11 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { // 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); - }); + if (directiveNode.arguments != null) { + directiveNode.arguments.forEach(arg => { + args[arg.name.value] = valueFromASTUntyped(arg.value); + }); + } } // As foretold in comments near the top of the visitSchemaDirectives @@ -221,7 +226,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { // be able to rely on that implementation. each(directiveVisitors, (visitorClass, directiveName) => { const decl = visitorClass.getDirectiveDeclaration(directiveName, schema); - if (decl) { + if (decl != null) { declaredDirectives[directiveName] = decl; } }); @@ -274,7 +279,6 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { // 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(); - }); + return 'visit' + loc.replace(/([^_]*)_?/g, (_wholeMatch, part: string) => + part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()); } diff --git a/src/utils/SchemaVisitor.ts b/src/utils/SchemaVisitor.ts index 6c75637e212..085b7e69e21 100644 --- a/src/utils/SchemaVisitor.ts +++ b/src/utils/SchemaVisitor.ts @@ -20,7 +20,7 @@ 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; + public schema!: GraphQLSchema; // Determine if this SchemaVisitor (sub)class implements a particular // visitor method. @@ -55,26 +55,47 @@ export abstract class SchemaVisitor { // 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: { + // eslint-disable-next-line @typescript-eslint/no-empty-function + public visitSchema(_schema: GraphQLSchema): void {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public visitScalar(_scalar: GraphQLScalarType): GraphQLScalarType | void | null {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public visitObject(_object: GraphQLObjectType): GraphQLObjectType | void | null {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + + public visitFieldDefinition(_field: GraphQLField, _details: { objectType: GraphQLObjectType | GraphQLInterfaceType, - }): GraphQLField | void | null {} - public visitArgumentDefinition(argument: GraphQLArgument, details: { + // eslint-disable-next-line @typescript-eslint/no-empty-function + }): GraphQLField | void | null { } + + public visitArgumentDefinition(_argument: GraphQLArgument, _details: { field: GraphQLField, objectType: GraphQLObjectType | GraphQLInterfaceType, + // eslint-disable-next-line @typescript-eslint/no-empty-function }): 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: { + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public visitInterface(_iface: GraphQLInterfaceType): GraphQLInterfaceType | void | null { } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public visitUnion(_union: GraphQLUnionType): GraphQLUnionType | void | null { } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public visitEnum(_type: GraphQLEnumType): GraphQLEnumType | void | null {} + + public visitEnumValue(_value: GraphQLEnumValue, _details: { enumType: GraphQLEnumType, + // eslint-disable-next-line @typescript-eslint/no-empty-function }): GraphQLEnumValue | void | null {} - public visitInputObject(object: GraphQLInputObjectType): GraphQLInputObjectType | void | null {} - public visitInputFieldDefinition(field: GraphQLInputField, details: { + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public visitInputObject(_object: GraphQLInputObjectType): GraphQLInputObjectType | void | null {} + + public visitInputFieldDefinition(_field: GraphQLInputField, _details: { objectType: GraphQLInputObjectType, + // eslint-disable-next-line @typescript-eslint/no-empty-function }): GraphQLInputField | void | null {} - /* tslint:enable:no-empty */ } diff --git a/src/utils/clone.ts b/src/utils/clone.ts index fd34ea98ae8..a2520948bd0 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -1,3 +1,6 @@ +import { healTypes } from './heal'; +import isSpecifiedScalarType from './isSpecifiedScalarType'; + import { GraphQLDirective, GraphQLEnumType, @@ -9,8 +12,6 @@ import { GraphQLSchema, GraphQLUnionType, } from 'graphql'; -import { healTypes } from './heal'; -import isSpecifiedScalarType from './isSpecifiedScalarType'; export function cloneDirective(directive: GraphQLDirective): GraphQLDirective { return new GraphQLDirective(directive.toConfig()); @@ -37,9 +38,9 @@ export function cloneType(type: GraphQLNamedType): GraphQLNamedType { return new GraphQLEnumType(type.toConfig()); } else if (type instanceof GraphQLScalarType) { return isSpecifiedScalarType(type) ? type : new GraphQLScalarType(type.toConfig()); - } else { - throw new Error(`Invalid type ${type}`); } + + throw new Error(`Invalid type ${type as string}`); } export function cloneSchema(schema: GraphQLSchema): GraphQLSchema { @@ -62,9 +63,9 @@ export function cloneSchema(schema: GraphQLSchema): GraphQLSchema { return new GraphQLSchema({ ...schema.toConfig(), - query: query ? newTypeMap[query.name] : undefined, - mutation: mutation ? newTypeMap[mutation.name] : undefined, - subscription: subscription ? newTypeMap[subscription.name] : undefined, + query: query != null ? newTypeMap[query.name] : undefined, + mutation: mutation != null ? newTypeMap[mutation.name] : undefined, + subscription: subscription != null ? newTypeMap[subscription.name] : undefined, types: Object.keys(newTypeMap).map(typeName => newTypeMap[typeName]), directives: newDirectives, }); diff --git a/src/utils/fieldNodes.ts b/src/utils/fieldNodes.ts index efb08c5f17e..5daa3652631 100644 --- a/src/utils/fieldNodes.ts +++ b/src/utils/fieldNodes.ts @@ -10,7 +10,7 @@ export function renameFieldNode(fieldNode: FieldNode, name: string): FieldNode { ...fieldNode, alias: { kind: Kind.NAME, - value: fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value, + value: fieldNode.alias != null ? fieldNode.alias.value : fieldNode.name.value, }, name: { kind: Kind.NAME, @@ -24,7 +24,7 @@ export function preAliasFieldNode(fieldNode: FieldNode, str: string): FieldNode ...fieldNode, alias: { kind: Kind.NAME, - value: `${str}${fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value}`, + value: `${str}${fieldNode.alias != null ? fieldNode.alias.value : fieldNode.name.value}`, } }; } @@ -54,40 +54,43 @@ export function wrapFieldNode( } export function collectFields( - selectionSet: SelectionSetNode, + selectionSet: SelectionSetNode | undefined, fragments: Record, fields: Array = [], visitedFragmentNames = {} ): Array { - selectionSet.selections.forEach(selection => { - switch (selection.kind) { - case Kind.FIELD: - fields.push(selection); - break; - case Kind.INLINE_FRAGMENT: - collectFields( - selection.selectionSet, - fragments, - fields, - visitedFragmentNames - ); - break; - case Kind.FRAGMENT_SPREAD: - const fragmentName = selection.name.value; - if (!visitedFragmentNames[fragmentName]) { - visitedFragmentNames[fragmentName] = true; + if (selectionSet != null) { + selectionSet.selections.forEach(selection => { + switch (selection.kind) { + case Kind.FIELD: + fields.push(selection); + break; + case Kind.INLINE_FRAGMENT: collectFields( - fragments[fragmentName].selectionSet, + selection.selectionSet, fragments, fields, visitedFragmentNames ); + break; + case Kind.FRAGMENT_SPREAD: { + const fragmentName = selection.name.value; + if (!visitedFragmentNames[fragmentName]) { + visitedFragmentNames[fragmentName] = true; + collectFields( + fragments[fragmentName].selectionSet, + fragments, + fields, + visitedFragmentNames + ); + } + break; } - break; - default: // unreachable - break; - } - }); + default: // unreachable + break; + } + }); + } return fields; } @@ -105,11 +108,11 @@ export function hoistFieldNodes({ delimeter?: string; fragments: Record; }): Array { - const alias = fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value; + const alias = fieldNode.alias != null ? fieldNode.alias.value : fieldNode.name.value; let newFieldNodes: Array = []; - if (path && path.length) { + if (path.length) { const remainingPathSegments = path.slice(); const initialPathSegment = remainingPathSegments.shift(); diff --git a/src/utils/fields.ts b/src/utils/fields.ts index f9b0268b122..44ca1773cca 100644 --- a/src/utils/fields.ts +++ b/src/utils/fields.ts @@ -12,7 +12,7 @@ export function appendFields( fields: GraphQLFieldConfigMap, ): void { let type = typeMap[typeName]; - if (type) { + if (type != null) { const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; const originalFields = typeConfig.fields; const newFields = {}; diff --git a/src/utils/forEachDefaultValue.ts b/src/utils/forEachDefaultValue.ts index 189ced660fb..b54e452124a 100644 --- a/src/utils/forEachDefaultValue.ts +++ b/src/utils/forEachDefaultValue.ts @@ -1,6 +1,12 @@ -import { getNamedType, GraphQLInputObjectType, GraphQLSchema, GraphQLObjectType } from 'graphql'; import { IDefaultValueIteratorFn } from '../Interfaces'; +import { + getNamedType, + GraphQLInputObjectType, + GraphQLSchema, + GraphQLObjectType, +} from 'graphql'; + export function forEachDefaultValue(schema: GraphQLSchema, fn: IDefaultValueIteratorFn): void { const typeMap = schema.getTypeMap(); diff --git a/src/utils/forEachField.ts b/src/utils/forEachField.ts index a75f866244b..12f7f565d47 100644 --- a/src/utils/forEachField.ts +++ b/src/utils/forEachField.ts @@ -1,6 +1,11 @@ -import { getNamedType, GraphQLObjectType, GraphQLSchema } from 'graphql'; import { IFieldIteratorFn } from '../Interfaces'; +import { + getNamedType, + GraphQLObjectType, + GraphQLSchema, +} from 'graphql'; + export function forEachField(schema: GraphQLSchema, fn: IFieldIteratorFn): void { const typeMap = schema.getTypeMap(); Object.keys(typeMap).forEach(typeName => { diff --git a/src/utils/fragments.ts b/src/utils/fragments.ts index 303374b7ebf..b3ea3270bd9 100644 --- a/src/utils/fragments.ts +++ b/src/utils/fragments.ts @@ -8,16 +8,14 @@ import { export function concatInlineFragments( type: string, - fragments: InlineFragmentNode[], + fragments: Array, ): InlineFragmentNode { - const fragmentSelections: SelectionNode[] = fragments.reduce( - (selections, fragment) => { - return selections.concat(fragment.selectionSet.selections); - }, + const fragmentSelections: Array = fragments.reduce( + (selections, fragment) => selections.concat(fragment.selectionSet.selections), [], ); - const deduplicatedFragmentSelection: SelectionNode[] = deduplicateSelection( + const deduplicatedFragmentSelection: Array = deduplicateSelection( fragmentSelections, ); @@ -37,43 +35,45 @@ export function concatInlineFragments( }; } -function deduplicateSelection(nodes: SelectionNode[]): SelectionNode[] { +const hasOwn = Object.prototype.hasOwnProperty; + +function deduplicateSelection(nodes: Array): Array { const selectionMap = nodes.reduce<{ [key: string]: SelectionNode }>( (map, node) => { switch (node.kind) { case 'Field': { - if (node.alias) { - if (map.hasOwnProperty(node.alias.value)) { + if (node.alias != null) { + if (hasOwn.call(map, node.alias.value)) { return map; - } else { - return { - ...map, - [node.alias.value]: node, - }; - } - } else { - if (map.hasOwnProperty(node.name.value)) { - return map; - } else { - return { - ...map, - [node.name.value]: node, - }; } + + return { + ...map, + [node.alias.value]: node, + }; + } + + if (hasOwn.call(map, node.name.value)) { + return map; } + + return { + ...map, + [node.name.value]: node, + }; } case 'FragmentSpread': { - if (map.hasOwnProperty(node.name.value)) { + if (hasOwn.call(map, node.name.value)) { return map; - } else { - return { - ...map, - [node.name.value]: node, - }; } + + return { + ...map, + [node.name.value]: node, + }; } case 'InlineFragment': { - if (map.__fragment) { + if (map.__fragment != null) { const fragment = map.__fragment as InlineFragmentNode; return { @@ -83,12 +83,12 @@ function deduplicateSelection(nodes: SelectionNode[]): SelectionNode[] { [fragment, node], ), }; - } else { - return { - ...map, - __fragment: node, - }; } + + return { + ...map, + __fragment: node, + }; } default: { return map; diff --git a/src/utils/getResolversFromSchema.ts b/src/utils/getResolversFromSchema.ts index 5b467b75c51..7d505b62036 100644 --- a/src/utils/getResolversFromSchema.ts +++ b/src/utils/getResolversFromSchema.ts @@ -1,3 +1,8 @@ +import { IResolvers } from '../Interfaces'; + +import isSpecifiedScalarType from './isSpecifiedScalarType'; +import { cloneType } from './clone'; + import { GraphQLSchema, GraphQLScalarType, @@ -6,9 +11,6 @@ import { GraphQLInterfaceType, GraphQLUnionType, } from 'graphql'; -import { IResolvers } from '../Interfaces'; -import isSpecifiedScalarType from './isSpecifiedScalarType'; -import { cloneType } from './clone'; export function getResolversFromSchema(schema: GraphQLSchema): IResolvers { const resolvers = Object.create({}); @@ -30,13 +32,13 @@ export function getResolversFromSchema(schema: GraphQLSchema): IResolvers { resolvers[typeName][value.name] = value.value; }); } else if (type instanceof GraphQLInterfaceType) { - if (type.resolveType) { + if (type.resolveType != null) { resolvers[typeName] = { __resolveType: type.resolveType, }; } } else if (type instanceof GraphQLUnionType) { - if (type.resolveType) { + if (type.resolveType != null) { resolvers[typeName] = { __resolveType: type.resolveType, }; @@ -44,7 +46,7 @@ export function getResolversFromSchema(schema: GraphQLSchema): IResolvers { } else if (type instanceof GraphQLObjectType) { resolvers[typeName] = {}; - if (type.isTypeOf) { + if (type.isTypeOf != null) { resolvers[typeName].__isTypeOf = type.isTypeOf; } diff --git a/src/utils/heal.ts b/src/utils/heal.ts index ac97f161410..1090bb27bde 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -1,3 +1,8 @@ +import each from './each'; +import updateEachKey from './updateEachKey'; +import { isStub, getBuiltInForStub } from './stub'; +import { cloneSchema } from './clone'; + import { GraphQLDirective, GraphQLEnumType, @@ -12,12 +17,9 @@ import { GraphQLUnionType, isNamedType, GraphQLSchema, + GraphQLInputType, + GraphQLOutputType, } from 'graphql'; -import each from './each'; -import updateEachKey from './updateEachKey'; -import { VisitableSchemaType } from '../Interfaces'; -import { isStub, getBuiltInForStub } from './stub'; -import { cloneSchema } from './clone'; type NamedTypeMap = { [key: string]: GraphQLNamedType; @@ -38,7 +40,7 @@ export function healSchema(schema: GraphQLSchema): GraphQLSchema { } export function healTypes( - originalTypeMap: NamedTypeMap, + originalTypeMap: Record, directives: ReadonlyArray, config: { skipPruning: boolean; @@ -53,7 +55,7 @@ export function healTypes( // be updated accordingly. each(originalTypeMap, (namedType, typeName) => { - if (!namedType || typeName.startsWith('__')) { + if (namedType == null || typeName.startsWith('__')) { return; } @@ -80,23 +82,23 @@ export function healTypes( // Directive declaration argument types can refer to named types. each(directives, (decl: GraphQLDirective) => { - if (decl.args) { - updateEachKey(decl.args, arg => { - arg.type = healType(arg.type); - return arg.type === null ? null : arg; - }); - } + updateEachKey(decl.args, arg => { + arg.type = healType(arg.type) as GraphQLInputType; + return arg.type === null ? null : arg; + }); }); each(originalTypeMap, (namedType, typeName) => { // Heal all named types, except for dangling references, kept only to redirect. if (! typeName.startsWith('__') && hasOwn.call(actualNamedTypeMap, typeName)) { - heal(namedType); + if (namedType != null) { + healNamedType(namedType); + } } }); - updateEachKey(originalTypeMap, (namedType, typeName) => { + 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 @@ -110,70 +112,67 @@ export function healTypes( pruneTypes(originalTypeMap, directives); } - function heal(type: VisitableSchemaType) { + function healNamedType(type: GraphQLNamedType) { if (type instanceof GraphQLObjectType) { healFields(type); healInterfaces(type); - + return; } else if (type instanceof GraphQLInterfaceType) { healFields(type); - + return; } else if (type instanceof GraphQLUnionType) { healUnderlyingTypes(type); - + return; } else if (type instanceof GraphQLInputObjectType) { healInputFields(type); - - } else if (type instanceof GraphQLScalarType || GraphQLEnumType) { - // Nothing to do. - - } else { - throw new Error(`Unexpected schema type: ${type}`); + return; + } else if (type instanceof GraphQLScalarType || type instanceof GraphQLEnumType) { + return; } + + throw new Error(`Unexpected schema type: ${type as unknown as string}`); } function healFields(type: GraphQLObjectType | GraphQLInterfaceType) { updateEachKey(type.getFields(), field => { - if (field.args) { - updateEachKey(field.args, arg => { - arg.type = healType(arg.type); - return arg.type === null ? null : arg; - }); - } - field.type = healType(field.type); + updateEachKey(field.args, arg => { + arg.type = healType(arg.type) as GraphQLInputType; + return arg.type === null ? null : arg; + }); + field.type = healType(field.type) as GraphQLOutputType; return field.type === null ? null : field; }); } function healInterfaces(type: GraphQLObjectType) { updateEachKey(type.getInterfaces(), iface => { - const healedType = healType(iface); + const healedType = healType(iface) as GraphQLInterfaceType; return healedType; }); } function healInputFields(type: GraphQLInputObjectType) { updateEachKey(type.getFields(), field => { - field.type = healType(field.type); + field.type = healType(field.type) as GraphQLInputType; return field.type === null ? null : field; }); } function healUnderlyingTypes(type: GraphQLUnionType) { - updateEachKey(type.getTypes(), t => { - const healedType = healType(t); + updateEachKey(type.getTypes(), (t: GraphQLOutputType) => { + const healedType = healType(t) as GraphQLOutputType; return healedType; }); } - function healType(type: T): T { + function healType(type: T): GraphQLType | null { // Unwrap the two known wrapper types if (type instanceof GraphQLList) { const healedType = healType(type.ofType); - return healedType ? new GraphQLList(healedType) as T : null; + return (healedType != null) ? new GraphQLList(healedType) : null; } else if (type instanceof GraphQLNonNull) { const healedType = healType(type.ofType); - return healedType ? new GraphQLNonNull(healedType) as T : null; + return (healedType != null) ? new GraphQLNonNull(healedType) : null; } 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 @@ -190,16 +189,16 @@ export function healTypes( } originalTypeMap[type.name] = officialType; } - return officialType as T; - } else { - return null; + return officialType; } + + return null; } } -function pruneTypes(typeMap: NamedTypeMap, directives: ReadonlyArray) { +function pruneTypes(typeMap: Record, directives: ReadonlyArray) { const implementedInterfaces = {}; - each(typeMap, (namedType, typeName) => { + each(typeMap, namedType => { if (namedType instanceof GraphQLObjectType) { each(namedType.getInterfaces(), iface => { implementedInterfaces[iface.name] = true; @@ -208,7 +207,10 @@ function pruneTypes(typeMap: NamedTypeMap, directives: ReadonlyArray { + const typeNames = Object.keys(typeMap); + for (let i = 0; i < typeNames.length; i++) { + const typeName = typeNames[i]; + const type = typeMap[typeName]; if (type instanceof GraphQLObjectType || type instanceof GraphQLInputObjectType) { // prune types with no fields if (!Object.keys(type.getFields()).length) { @@ -228,7 +230,7 @@ function pruneTypes(typeMap: NamedTypeMap, directives: ReadonlyArray): boolean { + if (obj == null) { return true; } diff --git a/src/utils/mergeDeep.ts b/src/utils/mergeDeep.ts index 352ba57d818..ae5a389ec9a 100644 --- a/src/utils/mergeDeep.ts +++ b/src/utils/mergeDeep.ts @@ -1,5 +1,7 @@ export function mergeDeep(target: any, ...sources: any): any { - let output = Object.assign({}, target); + const output = { + ...target, + }; sources.forEach((source: any) => { if (isObject(target) && isObject(source)) { Object.keys(source).forEach(key => { diff --git a/src/utils/selectionSets.ts b/src/utils/selectionSets.ts index dc3183615a9..66bcbe7463b 100644 --- a/src/utils/selectionSets.ts +++ b/src/utils/selectionSets.ts @@ -22,11 +22,11 @@ export function typeContainsSelectionSet( if (selection.kind === Kind.FIELD) { const field = fields[selection.name.value]; - if (!field) { + if (field == null) { return false; } - if (selection.selectionSet) { + if (selection.selectionSet != null) { return typeContainsSelectionSet( getNamedType(field.type) as GraphQLObjectType, selection.selectionSet, diff --git a/src/utils/transformInputValue.ts b/src/utils/transformInputValue.ts index 26ea921753e..28fa455ac07 100644 --- a/src/utils/transformInputValue.ts +++ b/src/utils/transformInputValue.ts @@ -29,7 +29,7 @@ export function transformInputValue(type: GraphQLInputType, value: any, transfor return newValue; } - //unreachable, no other possible return value + // unreachable, no other possible return value } export function serializeInputValue(type: GraphQLInputType, value: any) { diff --git a/src/utils/updateEachKey.ts b/src/utils/updateEachKey.ts index f03ced53116..5d3172e3e8d 100644 --- a/src/utils/updateEachKey.ts +++ b/src/utils/updateEachKey.ts @@ -6,12 +6,12 @@ export default 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, + updater: (value: V, key: string) => void | null | V, ) { let deletedCount = 0; Object.keys(arrayOrObject).forEach(key => { - const result = callback(arrayOrObject[key], key); + const result = updater(arrayOrObject[key], key); if (typeof result === 'undefined') { return; diff --git a/src/utils/valueFromASTUntyped.ts b/src/utils/valueFromASTUntyped.ts index 6e725786d27..aa26752796e 100644 --- a/src/utils/valueFromASTUntyped.ts +++ b/src/utils/valueFromASTUntyped.ts @@ -18,12 +18,13 @@ export default function valueFromASTUntyped( return valueNode.value; case Kind.LIST: return valueNode.values.map(valueFromASTUntyped); - case Kind.OBJECT: + 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/utils/visitSchema.ts b/src/utils/visitSchema.ts index a8b6b7d9f14..a569b011a10 100644 --- a/src/utils/visitSchema.ts +++ b/src/utils/visitSchema.ts @@ -1,3 +1,16 @@ +import { + VisitableSchemaType, + VisitorSelector, + VisitSchemaKind, + NamedTypeVisitor, + SchemaVisitorMap, +} from '../Interfaces'; + +import updateEachKey from './updateEachKey'; +import { healSchema } from './heal'; +import { SchemaVisitor } from './SchemaVisitor'; +import each from './each'; + import { GraphQLEnumType, GraphQLInputObjectType, @@ -8,14 +21,10 @@ import { GraphQLUnionType, isNamedType, GraphQLType, + GraphQLNamedType, + GraphQLInputField, } from 'graphql'; -import updateEachKey from './updateEachKey'; -import { VisitableSchemaType, VisitorSelector, VisitSchemaKind, SchemaVisitorMap, TypeVisitor } from '../Interfaces'; -import { healSchema } from './heal'; -import { SchemaVisitor } from './SchemaVisitor'; -import each from './each'; - // Generic function for visiting GraphQLSchema objects. export function visitSchema( schema: GraphQLSchema, @@ -35,7 +44,7 @@ export function visitSchema( visitorOrVisitorSelector: VisitorSelector | Array | - SchemaVisitorMap | + SchemaVisitor | SchemaVisitorMap, ): GraphQLSchema { const visitorSelector = @@ -48,17 +57,18 @@ export function visitSchema( function callMethod( methodName: string, type: T, - ...args: any[] - ): T { + ...args: Array + ): T | null { let visitors = visitorSelector(type, methodName); visitors = Array.isArray(visitors) ? visitors : [visitors]; + let finalType: T | null = type; visitors.every(visitorOrVisitorDef => { let newType; if (visitorOrVisitorDef instanceof SchemaVisitor) { - newType = visitorOrVisitorDef[methodName](type, ...args); + newType = visitorOrVisitorDef[methodName](finalType, ...args); } else if ( - isNamedType(type) && ( + isNamedType(finalType) && ( methodName === 'visitScalar' || methodName === 'visitEnum' || methodName === 'visitObject' || @@ -66,9 +76,9 @@ export function visitSchema( methodName === 'visitUnion' || methodName === 'visitInterface' )) { - const specifiers = getTypeSpecifiers(type, schema); + const specifiers = getTypeSpecifiers(finalType, schema); const typeVisitor = getVisitor(visitorOrVisitorDef, specifiers); - newType = typeVisitor ? typeVisitor(type, schema) : undefined; + newType = typeVisitor != null ? typeVisitor(finalType, schema) : undefined; } if (typeof newType === 'undefined') { @@ -77,46 +87,47 @@ export function visitSchema( } if (methodName === 'visitSchema' || - type instanceof GraphQLSchema) { - throw new Error(`Method ${methodName} cannot replace schema with ${newType}`); + finalType instanceof GraphQLSchema) { + throw new Error(`Method ${methodName} cannot replace schema with ${newType as string}`); } if (newType === null) { // Stop the loop and return null form callMethod, which will cause // the type to be removed from the schema. - type = null; + finalType = 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; + finalType = 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; + return finalType; } // 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 { + function visit(type: T): T | null { 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); - each(type.getTypeMap(), (namedType, typeName) => { - if (! typeName.startsWith('__')) { + const typeMap: Record = type.getTypeMap(); + each(typeMap, (namedType, typeName) => { + if (!typeName.startsWith('__') && namedType != null) { // Call visit recursively to let it determine which concrete // subclass of GraphQLNamedType we found in the type map. // We do not use updateEachKey because we want to preserve // deleted types in the typeMap so that other types that reference // the deleted types can be healed. - type.getTypeMap()[typeName] = visit(namedType); + typeMap[typeName] = visit(namedType); } }); @@ -129,7 +140,7 @@ export function visitSchema( // type, or if this SchemaDirectiveVisitor subclass does not override // the visitObject method. const newObject = callMethod('visitObject', type); - if (newObject) { + if (newObject != null) { visitFields(newObject); } return newObject; @@ -137,7 +148,7 @@ export function visitSchema( if (type instanceof GraphQLInterfaceType) { const newInterface = callMethod('visitInterface', type); - if (newInterface) { + if (newInterface != null) { visitFields(newInterface); } return newInterface; @@ -146,14 +157,14 @@ export function visitSchema( if (type instanceof GraphQLInputObjectType) { const newInputObject = callMethod('visitInputObject', type); - if (newInputObject) { - updateEachKey(newInputObject.getFields(), field => { + if (newInputObject != null) { + const fieldMap = newInputObject.getFields() as Record; + updateEachKey(fieldMap, field => callMethod('visitInputFieldDefinition', 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; @@ -170,18 +181,17 @@ export function visitSchema( if (type instanceof GraphQLEnumType) { const newEnum = callMethod('visitEnum', type); - if (newEnum) { - updateEachKey(newEnum.getValues(), value => { - return callMethod('visitEnumValue', value, { + if (newEnum != null) { + updateEachKey(newEnum.getValues(), value => + callMethod('visitEnumValue', value, { enumType: newEnum, - }); - }); + })); } return newEnum; } - throw new Error(`Unexpected schema type: ${type}`); + throw new Error(`Unexpected schema type: ${type as unknown as string}`); } function visitFields(type: GraphQLObjectType | GraphQLInterfaceType) { @@ -201,17 +211,15 @@ export function visitSchema( objectType: type, }); - if (newField && newField.args) { - updateEachKey(newField.args, arg => { - return callMethod('visitArgumentDefinition', arg, { + if (newField.args != null) { + updateEachKey(newField.args, arg => 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; @@ -278,13 +286,13 @@ function getTypeSpecifiers( function getVisitor( visitorDef: SchemaVisitorMap, specifiers: Array, -): TypeVisitor { - let typeVisitor = null; +): NamedTypeVisitor | null { + let typeVisitor: NamedTypeVisitor | undefined; const stack = [...specifiers]; while (!typeVisitor && stack.length > 0) { const next = stack.pop(); - typeVisitor = visitorDef[next]; + typeVisitor = visitorDef[next] as NamedTypeVisitor; } - return typeVisitor; + return (typeVisitor != null) ? typeVisitor : null; } diff --git a/tsconfig.json b/tsconfig.json index 0e73e0a674b..0e391487814 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "module": "commonjs", "target": "es5", "noImplicitAny": true, + "noImplicitUseStrict": true, "suppressImplicitAnyIndexErrors": true, "moduleResolution": "node", "emitDecoratorMetadata": true, @@ -14,7 +15,6 @@ "rootDir": "./src", "outDir": "./dist", "removeComments": false, - "noImplicitUseStrict": true, "esModuleInterop": true, }, "exclude": ["node_modules", "dist"] From 93674435ede6a4e0e49cc43fcfa348bad939f0df Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 13 Feb 2020 02:10:24 -0500 Subject: [PATCH 181/250] chore(deps): upgrade dependencies remove tslint --- package.json | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index ed6a43d60c2..8b1d375022e 100644 --- a/package.json +++ b/package.json @@ -58,19 +58,19 @@ }, "devDependencies": { "@types/apollo-upload-client": "^8.1.3", - "@types/chai": "^4.2.7", + "@types/chai": "^4.2.9", "@types/dateformat": "^3.0.1", "@types/express": "^4.17.2", "@types/extract-files": "^3.1.0", "@types/graphql-type-json": "^0.3.2", "@types/graphql-upload": "^8.0.3", - "@types/mocha": "^5.2.7", - "@types/node": "^13.1.8", + "@types/mocha": "^7.0.1", + "@types/node": "^13.7.1", "@types/node-fetch": "^2.5.4", "@types/supertest": "^2.0.8", - "@types/uuid": "^3.4.6", - "@typescript-eslint/eslint-plugin": "^2.19.0", - "@typescript-eslint/parser": "^2.19.0", + "@types/uuid": "^3.4.7", + "@typescript-eslint/eslint-plugin": "^2.19.2", + "@typescript-eslint/parser": "^2.19.2", "apollo-upload-client": "^12.1.0", "babel-eslint": "^10.0.3", "body-parser": "^1.19.0", @@ -87,13 +87,12 @@ "graphql-type-json": "^0.3.1", "graphql-upload": "^9.0.0", "istanbul": "^0.4.5", - "mocha": "^7.0.0", + "mocha": "^7.0.1", "prettier": "^1.19.1", "remap-istanbul": "0.13.0", - "rimraf": "^3.0.0", + "rimraf": "^3.0.2", "source-map-support": "^0.5.16", - "standard-version": "^7.0.1", - "tslint": "^5.20.1", + "standard-version": "^7.1.0", "typescript": "3.7.5", "zen-observable-ts": "^0.8.20" } From 6fdb6f636f93813f94a3b12ea199845fa6c7f448 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 13 Feb 2020 02:27:35 -0500 Subject: [PATCH 182/250] chore(remove) tslint --- tslint.json | 131 ---------------------------------------------------- 1 file changed, 131 deletions(-) delete mode 100755 tslint.json diff --git a/tslint.json b/tslint.json deleted file mode 100755 index 727d23d408e..00000000000 --- a/tslint.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "rules": { - "align": [ - false, - "parameters", - "arguments", - "statements" - ], - "ban": false, - "class-name": true, - "curly": true, - "eofline": true, - "forin": true, - "indent": [ - true, - "spaces" - ], - "interface-name": false, - "jsdoc-format": true, - "label-position": true, - "max-line-length": [ - true, - 140 - ], - "member-access": true, - "member-ordering": [ - true, - "public-before-private", - "static-before-instance", - "variables-before-functions" - ], - "no-any": false, - "no-arg": true, - "no-bitwise": true, - "no-conditional-assignment": true, - "no-consecutive-blank-lines": false, - "no-console": [ - true, - "log", - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-construct": true, - "no-parameter-properties": true, - "no-debugger": true, - "no-duplicate-variable": true, - "no-empty": true, - "no-eval": true, - "no-inferrable-types": false, - "no-internal-module": true, - "no-null-keyword": false, - "no-require-imports": false, - "no-shadowed-variable": true, - "no-switch-case-fall-through": true, - "no-trailing-whitespace": true, - "no-unused-expression": true, - "no-use-before-declare": false, - "no-var-keyword": true, - "object-literal-sort-keys": false, - "one-line": [ - true, - "check-open-brace", - "check-catch", - "check-else", - "check-finally", - "check-whitespace" - ], - "quotemark": [ - true, - "single", - "avoid-escape" - ], - "radix": true, - "semicolon": [ - true, - "always" - ], - "switch-default": true, - "trailing-comma": [ - false - ], - "triple-equals": [ - true, - "allow-null-check" - ], - "typedef": [ - false, - "call-signature", - "parameter", - "arrow-parameter", - "property-declaration", - "variable-declaration", - "member-variable-declaration" - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - }, - { - "call-signature": "space", - "index-signature": "space", - "parameter": "space", - "property-declaration": "space", - "variable-declaration": "space" - } - ], - "variable-name": [ - true, - "check-format", - "allow-leading-underscore", - "ban-keywords", - "allow-pascal-case" - ], - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ] - } -} From dde3be73cae2f37b7a690275bb06b5a8a057ec5b Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 13 Feb 2020 16:34:20 -0500 Subject: [PATCH 183/250] chore(lint): pin eslint-plugin-import to 2.22.0 To avoid windows import/order error, see: https://github.com/benmosher/eslint-plugin-import/issues/1643 --- package.json | 2 +- src/Interfaces.ts | 7 ++-- src/generate/addResolversToSchema.ts | 23 +++++------ src/generate/assertResolversPresent.ts | 10 ++--- src/generate/attachConnectorsToContext.ts | 6 +-- src/generate/attachDirectiveResolvers.ts | 4 +- src/generate/checkForResolveTypeResolver.ts | 4 +- src/generate/concatenateTypeDefs.ts | 4 +- src/generate/decorateWithLogger.ts | 4 +- src/generate/extendResolversFromInterfaces.ts | 4 +- src/makeExecutableSchema.ts | 13 +++--- src/mock.ts | 24 +++++------ src/stitching/checkResultAndHandleErrors.ts | 35 ++++++++-------- src/stitching/createRequest.ts | 18 ++++----- src/stitching/defaultMergedResolver.ts | 4 +- src/stitching/delegateToSchema.ts | 22 +++++----- src/stitching/linkToFetcher.ts | 4 +- src/stitching/makeMergedType.ts | 6 +-- src/stitching/makeRemoteExecutableSchema.ts | 22 +++++----- src/stitching/mergeFields.ts | 12 +++--- src/stitching/mergeInfo.ts | 22 +++++----- src/stitching/mergeSchemas.ts | 36 ++++++++--------- src/stitching/proxiedResult.ts | 12 +++--- src/stitching/resolvers.ts | 12 +++--- src/stitching/typeFromAST.ts | 8 ++-- src/test/testAlternateMergeSchemas.ts | 31 +++++++------- src/test/testDataloader.ts | 8 ++-- src/test/testDelegateToSchema.ts | 12 +++--- src/test/testDirectives.ts | 14 +++---- src/test/testErrors.ts | 6 +-- src/test/testExtensionExtraction.ts | 4 +- src/test/testFragmentsAreNotDuplicated.ts | 1 + src/test/testLogger.ts | 6 +-- src/test/testMakeRemoteExecutableSchema.ts | 18 ++++----- src/test/testMergeSchemas.ts | 31 +++++++------- src/test/testMocking.ts | 6 +-- src/test/testResolution.ts | 3 +- src/test/testSchemaGenerator.ts | 40 +++++++++---------- src/test/testStitchingFromSubschemas.ts | 6 +-- src/test/testTransforms.ts | 23 ++++++----- src/test/testTypeMerging.ts | 6 +-- src/test/testUpload.ts | 10 ++--- src/test/testUtils.ts | 6 +-- src/test/testingSchemas.ts | 16 ++++---- src/transforms/AddArgumentsAsVariables.ts | 10 ++--- src/transforms/AddMergedTypeSelectionSets.ts | 8 ++-- src/transforms/AddReplacementFragments.ts | 8 ++-- src/transforms/AddReplacementSelectionSets.ts | 8 ++-- src/transforms/AddTypenameToAbstract.ts | 4 +- src/transforms/CheckResultAndHandleErrors.ts | 4 +- src/transforms/ExpandAbstractTypes.ts | 6 +-- src/transforms/ExtendSchema.ts | 13 +++--- src/transforms/ExtractField.ts | 4 +- src/transforms/FilterObjectFields.ts | 4 +- src/transforms/FilterRootFields.ts | 4 +- src/transforms/FilterToSchema.ts | 10 ++--- src/transforms/FilterTypes.ts | 4 +- src/transforms/HoistField.ts | 12 +++--- src/transforms/MapFields.ts | 8 ++-- src/transforms/RenameObjectFields.ts | 4 +- src/transforms/RenameRootFields.ts | 4 +- src/transforms/RenameTypes.ts | 10 ++--- src/transforms/ReplaceFieldWithFragment.ts | 10 ++--- src/transforms/TransformObjectFields.ts | 12 +++--- src/transforms/TransformQuery.ts | 8 ++-- src/transforms/TransformRootFields.ts | 10 ++--- src/transforms/WrapFields.ts | 10 ++--- src/transforms/WrapQuery.ts | 4 +- src/transforms/WrapType.ts | 4 +- src/transforms/filterSchema.ts | 8 ++-- src/transforms/transformSchema.ts | 5 +-- src/transforms/transforms.ts | 4 +- src/utils/SchemaDirectiveVisitor.ts | 16 ++++---- src/utils/clone.ts | 6 +-- src/utils/forEachDefaultValue.ts | 4 +- src/utils/forEachField.ts | 4 +- src/utils/getResolversFromSchema.ts | 10 ++--- src/utils/heal.ts | 10 ++--- src/utils/visitSchema.ts | 26 ++++++------ 79 files changed, 419 insertions(+), 422 deletions(-) diff --git a/package.json b/package.json index 8b1d375022e..3e29ac4f931 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "dataloader": "^2.0.0", "dateformat": "^3.0.3", "eslint": "^6.8.0", - "eslint-plugin-import": "^2.20.1", + "eslint-plugin-import": "2.20.0", "eslint-watch": "^6.0.1", "express": "^4.17.1", "express-graphql": "^0.9.0", diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 5c2416b52d9..c049d65e057 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -1,6 +1,3 @@ -import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; -import { SchemaVisitor } from './utils/SchemaVisitor'; - import { GraphQLSchema, GraphQLField, @@ -29,9 +26,11 @@ import { } from 'graphql'; import { TypeMap } from 'graphql/type/schema'; - import { ApolloLink } from 'apollo-link'; +import { SchemaVisitor } from './utils/SchemaVisitor'; +import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; + /* TODO: Add documentation */ export type UnitOrList = Type | Array; diff --git a/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index 1dc4edbd2b6..2292dc6be50 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -1,9 +1,19 @@ +import { + GraphQLField, + GraphQLEnumType, + GraphQLScalarType, + GraphQLSchema, + GraphQLObjectType, + GraphQLInterfaceType, + isSpecifiedScalarType, + GraphQLUnionType, +} from 'graphql'; + import { IResolvers, IResolverValidationOptions, IAddResolversToSchemaOptions, } from '../Interfaces'; - import { parseInputValue, serializeInputValue, @@ -16,17 +26,6 @@ import SchemaError from './SchemaError'; import checkForResolveTypeResolver from './checkForResolveTypeResolver'; import extendResolversFromInterfaces from './extendResolversFromInterfaces'; -import { - GraphQLField, - GraphQLEnumType, - GraphQLScalarType, - GraphQLSchema, - GraphQLObjectType, - GraphQLInterfaceType, - isSpecifiedScalarType, - GraphQLUnionType, -} from 'graphql'; - function addResolversToSchema( schemaOrOptions: GraphQLSchema | IAddResolversToSchemaOptions, legacyInputResolvers?: IResolvers, diff --git a/src/generate/assertResolversPresent.ts b/src/generate/assertResolversPresent.ts index 4dc860225be..06173157188 100644 --- a/src/generate/assertResolversPresent.ts +++ b/src/generate/assertResolversPresent.ts @@ -1,8 +1,3 @@ -import { IResolverValidationOptions } from '../Interfaces'; -import { forEachField } from '../utils'; - -import SchemaError from './SchemaError'; - import { GraphQLSchema, GraphQLField, @@ -10,6 +5,11 @@ import { GraphQLScalarType, } from 'graphql'; +import { IResolverValidationOptions } from '../Interfaces'; +import { forEachField } from '../utils'; + +import SchemaError from './SchemaError'; + function assertResolversPresent( schema: GraphQLSchema, resolverValidationOptions: IResolverValidationOptions = {}, diff --git a/src/generate/attachConnectorsToContext.ts b/src/generate/attachConnectorsToContext.ts index 589b06d92d8..dd0cb6c21fd 100644 --- a/src/generate/attachConnectorsToContext.ts +++ b/src/generate/attachConnectorsToContext.ts @@ -1,10 +1,10 @@ +import { deprecated } from 'deprecated-decorator'; +import { GraphQLSchema, GraphQLFieldResolver } from 'graphql'; + import { IConnectors, IConnector, IConnectorCls } from '../Interfaces'; import addSchemaLevelResolver from './addSchemaLevelResolver'; -import { deprecated } from 'deprecated-decorator'; -import { GraphQLSchema, GraphQLFieldResolver } from 'graphql'; - // takes a GraphQL-JS schema and an object of connectors, then attaches // the connectors to the context by wrapping each query or mutation resolve // function with a function that attaches connectors if they don't exist. diff --git a/src/generate/attachDirectiveResolvers.ts b/src/generate/attachDirectiveResolvers.ts index ef2e7236ff6..a6d01cb39e4 100644 --- a/src/generate/attachDirectiveResolvers.ts +++ b/src/generate/attachDirectiveResolvers.ts @@ -1,8 +1,8 @@ +import { GraphQLSchema, GraphQLField, defaultFieldResolver } from 'graphql'; + import { IDirectiveResolvers } from '../Interfaces'; import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; -import { GraphQLSchema, GraphQLField, defaultFieldResolver } from 'graphql'; - function attachDirectiveResolvers( schema: GraphQLSchema, directiveResolvers: IDirectiveResolvers, diff --git a/src/generate/checkForResolveTypeResolver.ts b/src/generate/checkForResolveTypeResolver.ts index 9e85b86cd67..75bf947291e 100644 --- a/src/generate/checkForResolveTypeResolver.ts +++ b/src/generate/checkForResolveTypeResolver.ts @@ -1,7 +1,7 @@ -import SchemaError from './SchemaError'; - import { GraphQLInterfaceType, GraphQLUnionType, GraphQLSchema } from 'graphql'; +import SchemaError from './SchemaError'; + // If we have any union or interface types throw if no there is no resolveType or isTypeOf resolvers function checkForResolveTypeResolver( schema: GraphQLSchema, diff --git a/src/generate/concatenateTypeDefs.ts b/src/generate/concatenateTypeDefs.ts index 91abe980fd1..61face52f7a 100644 --- a/src/generate/concatenateTypeDefs.ts +++ b/src/generate/concatenateTypeDefs.ts @@ -1,9 +1,9 @@ +import { print, ASTNode } from 'graphql'; + import { ITypedef } from '../Interfaces'; import SchemaError from './SchemaError'; -import { print, ASTNode } from 'graphql'; - function concatenateTypeDefs( typeDefinitionsAry: Array, calledFunctionRefs = [] as any, diff --git a/src/generate/decorateWithLogger.ts b/src/generate/decorateWithLogger.ts index 6809333c456..9e08aeaa291 100644 --- a/src/generate/decorateWithLogger.ts +++ b/src/generate/decorateWithLogger.ts @@ -1,7 +1,7 @@ -import { ILogger } from '../Interfaces'; - import { defaultFieldResolver, GraphQLFieldResolver } from 'graphql'; +import { ILogger } from '../Interfaces'; + /* * fn: The function to decorate with the logger * logger: an object instance of type Logger diff --git a/src/generate/extendResolversFromInterfaces.ts b/src/generate/extendResolversFromInterfaces.ts index c6947d38968..5783c7b72e4 100644 --- a/src/generate/extendResolversFromInterfaces.ts +++ b/src/generate/extendResolversFromInterfaces.ts @@ -1,7 +1,7 @@ -import { IResolvers } from '../Interfaces'; - import { GraphQLObjectType, GraphQLSchema } from 'graphql'; +import { IResolvers } from '../Interfaces'; + function extendResolversFromInterfaces( schema: GraphQLSchema, resolvers: IResolvers, diff --git a/src/makeExecutableSchema.ts b/src/makeExecutableSchema.ts index 6c23352d4f9..f73d6d0419c 100644 --- a/src/makeExecutableSchema.ts +++ b/src/makeExecutableSchema.ts @@ -1,11 +1,15 @@ -import { IExecutableSchemaDefinition, ILogger } from './Interfaces'; +import { + defaultFieldResolver, + GraphQLSchema, + GraphQLFieldResolver, +} from 'graphql'; +import { IExecutableSchemaDefinition, ILogger } from './Interfaces'; import { SchemaDirectiveVisitor, forEachField, mergeDeep } from './utils'; - import { attachDirectiveResolvers, assertResolversPresent, @@ -17,11 +21,6 @@ import { SchemaError } from './generate'; -import { - defaultFieldResolver, - GraphQLSchema, - GraphQLFieldResolver, -} from 'graphql'; export function makeExecutableSchema({ typeDefs, diff --git a/src/mock.ts b/src/mock.ts index 46bd5d20a63..e0b02e27e32 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -1,15 +1,3 @@ -import { buildSchemaFromTypeDefinitions } from './makeExecutableSchema'; -import { forEachField } from './utils'; - -import { - IMocks, - IMockServer, - IMockOptions, - IMockFn, - IMockTypeFn, - ITypeDefinitions, -} from './Interfaces'; - import { graphql, GraphQLSchema, @@ -30,6 +18,18 @@ import { } from 'graphql'; import { v4 as uuid } from 'uuid'; +import { buildSchemaFromTypeDefinitions } from './makeExecutableSchema'; +import { forEachField } from './utils'; + +import { + IMocks, + IMockServer, + IMockOptions, + IMockFn, + IMockTypeFn, + ITypeDefinitions, +} from './Interfaces'; + // This function wraps addMocksToSchema for more convenience function mockServer( schema: GraphQLSchema | ITypeDefinitions, diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 3458821f73d..3100851e7ae 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -1,20 +1,3 @@ -import { - SubschemaConfig, - IGraphQLToolsResolveInfo, - isSubschemaConfig, - MergedTypeInfo, -} from '../Interfaces'; - -import { - relocatedError, - combineErrors, - getErrorsByPathSegment, -} from './errors'; -import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; -import resolveFromParentTypename from './resolveFromParentTypename'; -import { setErrors, setObjectSubschema } from './proxiedResult'; -import { mergeFields } from './mergeFields'; - import { GraphQLResolveInfo, responsePathAsArray, @@ -35,6 +18,24 @@ import { } from 'graphql'; import { collectFields, ExecutionContext } from 'graphql/execution/execute'; +import { + SubschemaConfig, + IGraphQLToolsResolveInfo, + isSubschemaConfig, + MergedTypeInfo, +} from '../Interfaces'; + +import { + relocatedError, + combineErrors, + getErrorsByPathSegment, +} from './errors'; +import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; +import resolveFromParentTypename from './resolveFromParentTypename'; +import { setErrors, setObjectSubschema } from './proxiedResult'; +import { mergeFields } from './mergeFields'; + + export function checkResultAndHandleErrors( result: ExecutionResult, context: Record, diff --git a/src/stitching/createRequest.ts b/src/stitching/createRequest.ts index 27aa1ae55db..22178b97f38 100644 --- a/src/stitching/createRequest.ts +++ b/src/stitching/createRequest.ts @@ -1,12 +1,3 @@ -import { - ICreateRequestFromInfo, - Operation, - Request, - SubschemaConfig, - isSubschemaConfig, -} from '../Interfaces'; -import { serializeInputValue } from '../utils'; - import { ArgumentNode, FieldNode, @@ -30,6 +21,15 @@ import { SelectionSetNode, } from 'graphql'; +import { + ICreateRequestFromInfo, + Operation, + Request, + SubschemaConfig, + isSubschemaConfig, +} from '../Interfaces'; +import { serializeInputValue } from '../utils'; + export function getDelegatingOperation( parentType: GraphQLObjectType, schema: GraphQLSchema diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitching/defaultMergedResolver.ts index b58bd6e3829..3c45099be3c 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitching/defaultMergedResolver.ts @@ -1,11 +1,11 @@ +import { defaultFieldResolver } from 'graphql'; + import { IGraphQLToolsResolveInfo } from '../Interfaces'; import { getErrors, getSubschema } from './proxiedResult'; import { handleResult } from './checkResultAndHandleErrors'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; -import { defaultFieldResolver } from 'graphql'; - // Resolver that knows how to: // a) handle aliases for proxied schemas // b) handle errors from proxied schemas diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index d32a2a44158..b86128e5526 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -1,3 +1,14 @@ +import { isAsyncIterable } from 'iterall'; +import { ApolloLink, execute as executeLink } from 'apollo-link'; +import { + subscribe, + execute, + validate, + GraphQLSchema, + ExecutionResult, + GraphQLOutputType, +} from 'graphql'; + import { IDelegateToSchemaOptions, IDelegateRequestOptions, @@ -29,17 +40,6 @@ import { observableToAsyncIterable } from './observableToAsyncIterable'; import mapAsyncIterator from './mapAsyncIterator'; import { combineErrors } from './errors'; -import { isAsyncIterable } from 'iterall'; -import { ApolloLink, execute as executeLink } from 'apollo-link'; -import { - subscribe, - execute, - validate, - GraphQLSchema, - ExecutionResult, - GraphQLOutputType, -} from 'graphql'; - export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, ): any { diff --git a/src/stitching/linkToFetcher.ts b/src/stitching/linkToFetcher.ts index 4d253f9fc8c..9c4082dac42 100644 --- a/src/stitching/linkToFetcher.ts +++ b/src/stitching/linkToFetcher.ts @@ -1,5 +1,3 @@ -import { Fetcher, IFetcherOperation } from '../Interfaces'; - import { ApolloLink, toPromise, @@ -7,6 +5,8 @@ import { ExecutionResult, } from 'apollo-link'; +import { Fetcher, IFetcherOperation } from '../Interfaces'; + export { execute } from 'apollo-link'; export default function linkToFetcher(link: ApolloLink): Fetcher { diff --git a/src/stitching/makeMergedType.ts b/src/stitching/makeMergedType.ts index 769fefc10c8..003a9c14c58 100644 --- a/src/stitching/makeMergedType.ts +++ b/src/stitching/makeMergedType.ts @@ -1,6 +1,3 @@ -import defaultMergedResolver from './defaultMergedResolver'; -import resolveFromParentTypename from './resolveFromParentTypename'; - import { GraphQLType, GraphQLObjectType, @@ -8,6 +5,9 @@ import { GraphQLUnionType, } from 'graphql'; +import defaultMergedResolver from './defaultMergedResolver'; +import resolveFromParentTypename from './resolveFromParentTypename'; + export function makeMergedType(type: GraphQLType): void { if (type instanceof GraphQLObjectType) { type.isTypeOf = undefined; diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 9c3edbc5f0f..10ecbe7ad20 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -1,14 +1,3 @@ -import { addResolversToSchema } from '../generate'; -import { Fetcher, Operation } from '../Interfaces'; -import { cloneSchema } from '../utils'; - -import linkToFetcher, { execute } from './linkToFetcher'; -import { addTypenameToAbstract } from './addTypenameToAbstract'; -import { checkResultAndHandleErrors } from './checkResultAndHandleErrors'; -import { observableToAsyncIterable } from './observableToAsyncIterable'; -import mapAsyncIterator from './mapAsyncIterator'; -import { stripResolvers, generateProxyingResolvers } from './resolvers'; - import { ApolloLink } from 'apollo-link'; import { GraphQLFieldResolver, @@ -21,6 +10,17 @@ import { } from 'graphql'; import { Options as PrintSchemaOptions } from 'graphql/utilities/schemaPrinter'; +import { addResolversToSchema } from '../generate'; +import { Fetcher, Operation } from '../Interfaces'; +import { cloneSchema } from '../utils'; + +import linkToFetcher, { execute } from './linkToFetcher'; +import { addTypenameToAbstract } from './addTypenameToAbstract'; +import { checkResultAndHandleErrors } from './checkResultAndHandleErrors'; +import { observableToAsyncIterable } from './observableToAsyncIterable'; +import mapAsyncIterator from './mapAsyncIterator'; +import { stripResolvers, generateProxyingResolvers } from './resolvers'; + export type ResolverFn = ( rootValue?: any, args?: any, diff --git a/src/stitching/mergeFields.ts b/src/stitching/mergeFields.ts index 08f09039faa..374f23e6251 100644 --- a/src/stitching/mergeFields.ts +++ b/src/stitching/mergeFields.ts @@ -1,3 +1,9 @@ +import { + FieldNode, + SelectionNode, + Kind, +} from 'graphql'; + import { SubschemaConfig, IGraphQLToolsResolveInfo, @@ -6,12 +12,6 @@ import { import { mergeProxiedResults } from './proxiedResult'; -import { - FieldNode, - SelectionNode, - Kind, -} from 'graphql'; - function buildDelegationPlan( mergedTypeInfo: MergedTypeInfo, originalSelections: Array, diff --git a/src/stitching/mergeInfo.ts b/src/stitching/mergeInfo.ts index 215d6551eae..e071b3add86 100644 --- a/src/stitching/mergeInfo.ts +++ b/src/stitching/mergeInfo.ts @@ -1,3 +1,14 @@ +import { + GraphQLNamedType, + GraphQLObjectType, + GraphQLScalarType, + GraphQLSchema, + Kind, + SelectionNode, + SelectionSetNode, +} from 'graphql'; +import { TypeMap } from 'graphql/type/schema'; + import { IDelegateToSchemaOptions, MergeInfo, @@ -21,17 +32,6 @@ import { import delegateToSchema from './delegateToSchema'; -import { - GraphQLNamedType, - GraphQLObjectType, - GraphQLScalarType, - GraphQLSchema, - Kind, - SelectionNode, - SelectionSetNode, -} from 'graphql'; -import { TypeMap } from 'graphql/type/schema'; - type MergeTypeCandidate = { type: GraphQLNamedType; schema?: GraphQLSchema; diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index f2b004c4b75..830d846c6e5 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -1,3 +1,21 @@ +import { + DocumentNode, + GraphQLNamedType, + GraphQLObjectType, + GraphQLScalarType, + GraphQLSchema, + extendSchema, + getNamedType, + isNamedType, + parse, + Kind, + GraphQLDirective, + GraphQLInterfaceType, + GraphQLUnionType, + GraphQLEnumType, + ASTNode, +} from 'graphql'; + import { OnTypeConflict, IResolversParameter, @@ -23,24 +41,6 @@ import { import typeFromAST from './typeFromAST'; import { createMergeInfo, completeMergeInfo } from './mergeInfo'; -import { - DocumentNode, - GraphQLNamedType, - GraphQLObjectType, - GraphQLScalarType, - GraphQLSchema, - extendSchema, - getNamedType, - isNamedType, - parse, - Kind, - GraphQLDirective, - GraphQLInterfaceType, - GraphQLUnionType, - GraphQLEnumType, - ASTNode, -} from 'graphql'; - type MergeTypeCandidate = { type: GraphQLNamedType; schema?: GraphQLSchema; diff --git a/src/stitching/proxiedResult.ts b/src/stitching/proxiedResult.ts index b8267984dc5..5bdf82bb8e4 100644 --- a/src/stitching/proxiedResult.ts +++ b/src/stitching/proxiedResult.ts @@ -1,15 +1,15 @@ -import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; -import { mergeDeep } from '../utils'; - -import { handleNull } from './checkResultAndHandleErrors'; -import { relocatedError } from './errors'; - import { GraphQLError, GraphQLSchema, responsePathAsArray, } from 'graphql'; +import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; +import { mergeDeep } from '../utils'; + +import { handleNull } from './checkResultAndHandleErrors'; +import { relocatedError } from './errors'; + const hasSymbol = (typeof global !== 'undefined' && 'Symbol' in global) || // eslint-disable-next-line no-undef diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 828ade698e5..511ad3f3541 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -1,3 +1,9 @@ +import { + GraphQLSchema, + GraphQLFieldResolver, + GraphQLObjectType, +} from 'graphql'; + import { IResolvers, Operation, @@ -8,12 +14,6 @@ import { Transform } from '../transforms'; import delegateToSchema from './delegateToSchema'; import { makeMergedType } from './makeMergedType'; -import { - GraphQLSchema, - GraphQLFieldResolver, - GraphQLObjectType, -} from 'graphql'; - export type Mapping = { [typeName: string]: { [fieldName: string]: { diff --git a/src/stitching/typeFromAST.ts b/src/stitching/typeFromAST.ts index 0c2314e53cb..d73ef220055 100644 --- a/src/stitching/typeFromAST.ts +++ b/src/stitching/typeFromAST.ts @@ -1,7 +1,3 @@ -import { createNamedStub } from '../utils/stub'; - -import resolveFromParentTypename from './resolveFromParentTypename'; - import { DefinitionNode, EnumTypeDefinitionNode, @@ -34,6 +30,10 @@ import { StringValueNode, } from 'graphql'; +import { createNamedStub } from '../utils/stub'; + +import resolveFromParentTypename from './resolveFromParentTypename'; + const backcompatOptions = { commentDescriptions: true }; export type GetType = ( diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 04c41438ced..83171e4fd22 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -1,3 +1,19 @@ +import { + graphql, + GraphQLSchema, + ExecutionResult, + subscribe, + parse, + GraphQLScalarType, + FieldNode, + printSchema, + GraphQLObjectTypeConfig, + GraphQLFieldConfig, + GraphQLObjectType, +} from 'graphql'; +import { forAwaitEach } from 'iterall'; +import { expect } from 'chai'; + import { transformSchema, filterSchema, @@ -31,21 +47,6 @@ import { subscriptionPubSubTrigger, } from './testingSchemas'; -import { - graphql, - GraphQLSchema, - ExecutionResult, - subscribe, - parse, - GraphQLScalarType, - FieldNode, - printSchema, - GraphQLObjectTypeConfig, - GraphQLFieldConfig, - GraphQLObjectType, -} from 'graphql'; -import { forAwaitEach } from 'iterall'; -import { expect } from 'chai'; const linkSchema = ` """ diff --git a/src/test/testDataloader.ts b/src/test/testDataloader.ts index dcb8bc58a36..89bde4208bc 100644 --- a/src/test/testDataloader.ts +++ b/src/test/testDataloader.ts @@ -1,3 +1,7 @@ +import DataLoader from 'dataloader'; +import { graphql, GraphQLList } from 'graphql'; +import { expect } from 'chai'; + import { makeExecutableSchema } from '../makeExecutableSchema'; import { mergeSchemas, @@ -5,10 +9,6 @@ import { } from '../stitching'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; -import DataLoader from 'dataloader'; -import { graphql, GraphQLList } from 'graphql'; -import { expect } from 'chai'; - describe('dataloader', () => { it('should work', async () => { const taskSchema = makeExecutableSchema({ diff --git a/src/test/testDelegateToSchema.ts b/src/test/testDelegateToSchema.ts index d3a0e0d6229..9114c8560be 100644 --- a/src/test/testDelegateToSchema.ts +++ b/src/test/testDelegateToSchema.ts @@ -1,15 +1,15 @@ -import delegateToSchema from '../stitching/delegateToSchema'; -import mergeSchemas from '../stitching/mergeSchemas'; -import { IResolvers } from '../Interfaces'; - -import { propertySchema, bookingSchema, sampleData, Property } from './testingSchemas'; - import { GraphQLSchema, graphql } from 'graphql'; import { expect } from 'chai'; +import delegateToSchema from '../stitching/delegateToSchema'; +import mergeSchemas from '../stitching/mergeSchemas'; +import { IResolvers } from '../Interfaces'; + +import { propertySchema, bookingSchema, sampleData, Property } from './testingSchemas'; + function findPropertyByLocationName ( properties: { [key: string]: Property }, name: string diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 4009eda0b44..1127a547099 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -1,11 +1,5 @@ import crypto from 'crypto'; -import { makeExecutableSchema } from '../makeExecutableSchema'; -import { VisitableSchemaType } from '../Interfaces'; -import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; -import { SchemaVisitor } from '../utils/SchemaVisitor'; -import { visitSchema } from '../utils/visitSchema'; - import { ExecutionResult, GraphQLArgument, @@ -28,11 +22,15 @@ import { GraphQLInt, GraphQLOutputType, } from 'graphql'; - import { assert } from 'chai'; - import formatDate from 'dateformat'; +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { VisitableSchemaType } from '../Interfaces'; +import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; +import { SchemaVisitor } from '../utils/SchemaVisitor'; +import { visitSchema } from '../utils/visitSchema'; + const typeDefs = ` directive @schemaDirective(role: String) on SCHEMA directive @queryTypeDirective on OBJECT diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index 8c95eb25c5e..367ff8037ed 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -1,3 +1,6 @@ +import { expect, assert } from 'chai'; +import { GraphQLError, graphql } from 'graphql'; + import { relocatedError } from '../stitching/errors'; import { getErrors, ERROR_SYMBOL } from '../stitching/proxiedResult'; import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; @@ -5,9 +8,6 @@ import { makeExecutableSchema } from '../makeExecutableSchema'; import { mergeSchemas } from '../stitching'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; -import { expect, assert } from 'chai'; -import { GraphQLError, graphql } from 'graphql'; - class ErrorWithExtensions extends GraphQLError { constructor(message: string, code: string) { super(message, null, null, null, null, null, { code }); diff --git a/src/test/testExtensionExtraction.ts b/src/test/testExtensionExtraction.ts index a7621874604..234205a5774 100644 --- a/src/test/testExtensionExtraction.ts +++ b/src/test/testExtensionExtraction.ts @@ -1,8 +1,8 @@ -import extractExtensionDefinitons from '../generate/extractExtensionDefinitions'; - import { expect } from 'chai'; import { parse } from 'graphql'; +import extractExtensionDefinitons from '../generate/extractExtensionDefinitions'; + describe('Extension extraction', () => { it('extracts extended inputs', () => { const typeDefs = ` diff --git a/src/test/testFragmentsAreNotDuplicated.ts b/src/test/testFragmentsAreNotDuplicated.ts index 409faccdd29..ab7d70c9ec0 100644 --- a/src/test/testFragmentsAreNotDuplicated.ts +++ b/src/test/testFragmentsAreNotDuplicated.ts @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {ExecutionResult, graphql} from 'graphql'; + import { addMocksToSchema, makeExecutableSchema, diff --git a/src/test/testLogger.ts b/src/test/testLogger.ts index 5668b10136e..b6176fb4299 100644 --- a/src/test/testLogger.ts +++ b/src/test/testLogger.ts @@ -1,9 +1,9 @@ -import { Logger } from '../Logger'; -import { makeExecutableSchema } from '../makeExecutableSchema'; - import { assert } from 'chai'; import { graphql } from 'graphql'; +import { Logger } from '../Logger'; +import { makeExecutableSchema } from '../makeExecutableSchema'; + describe('Logger', () => { it('logs the errors', () => { const shorthand = ` diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index 8a63e866162..b47f742a92b 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -1,12 +1,3 @@ -import { - propertySchema, - subscriptionSchema, - subscriptionPubSubTrigger, - subscriptionPubSub, - makeSchemaRemoteFromLink -} from '../test/testingSchemas'; -import { makeRemoteExecutableSchema } from '../stitching'; - import { expect } from 'chai'; import { forAwaitEach } from 'iterall'; import { @@ -17,6 +8,15 @@ import { graphql, } from 'graphql'; +import { makeRemoteExecutableSchema } from '../stitching'; +import { + propertySchema, + subscriptionSchema, + subscriptionPubSubTrigger, + subscriptionPubSub, + makeSchemaRemoteFromLink +} from '../test/testingSchemas'; + describe('remote queries', () => { let schema: GraphQLSchema; before(async () => { diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 39863e45440..dad277839bc 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -1,3 +1,19 @@ +import { forAwaitEach } from 'iterall'; +import { expect } from 'chai'; +import { + graphql, + GraphQLSchema, + GraphQLField, + GraphQLObjectType, + GraphQLScalarType, + subscribe, + parse, + ExecutionResult, + defaultFieldResolver, + findDeprecatedUsages, + printSchema, +} from 'graphql'; + import mergeSchemas from '../stitching/mergeSchemas'; import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; import { makeExecutableSchema } from '../makeExecutableSchema'; @@ -21,21 +37,6 @@ import { subscriptionPubSubTrigger, } from './testingSchemas'; -import { forAwaitEach } from 'iterall'; -import { expect } from 'chai'; -import { - graphql, - GraphQLSchema, - GraphQLField, - GraphQLObjectType, - GraphQLScalarType, - subscribe, - parse, - ExecutionResult, - defaultFieldResolver, - findDeprecatedUsages, - printSchema, -} from 'graphql'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const removeLocations = ({ locations, ...rest }: any): any => ({ ...rest }); diff --git a/src/test/testMocking.ts b/src/test/testMocking.ts index 0d8afcd010b..4d25f611f36 100644 --- a/src/test/testMocking.ts +++ b/src/test/testMocking.ts @@ -1,3 +1,6 @@ +import { expect } from 'chai'; +import { graphql, GraphQLResolveInfo, GraphQLSchema, GraphQLFieldResolver } from 'graphql'; + import { addMocksToSchema, MockList, mockServer } from '../mock'; import { buildSchemaFromTypeDefinitions, @@ -6,9 +9,6 @@ import { } from '../makeExecutableSchema'; import { IMocks } from '../Interfaces'; -import { expect } from 'chai'; -import { graphql, GraphQLResolveInfo, GraphQLSchema, GraphQLFieldResolver } from 'graphql'; - describe('Mock', () => { const shorthand = ` scalar MissingMockType diff --git a/src/test/testResolution.ts b/src/test/testResolution.ts index 83e85dc499f..e3359f3b5d0 100644 --- a/src/test/testResolution.ts +++ b/src/test/testResolution.ts @@ -1,9 +1,10 @@ import { assert } from 'chai'; -import { makeExecutableSchema, addSchemaLevelResolver } from '..'; import { parse, graphql, subscribe, ExecutionResult } from 'graphql'; import { PubSub } from 'graphql-subscriptions'; import { forAwaitEach } from 'iterall'; +import { makeExecutableSchema, addSchemaLevelResolver } from '..'; + describe('Resolve', () => { describe('addSchemaLevelResolver', () => { const pubsub = new PubSub(); diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 78ed7cef031..3ea69ec74c9 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -1,6 +1,26 @@ // TODO: reduce code repetition in this file. // see https://github.com/apollostack/graphql-tools/issues/26 +import { GraphQLJSON } from 'graphql-type-json'; +import { assert, expect } from 'chai'; +import { + graphql, + GraphQLResolveInfo, + GraphQLScalarType, + Kind, + IntValueNode, + parse, + ExecutionResult, + GraphQLError, + GraphQLEnumType, + execute, + VariableDefinitionNode, + DocumentNode, + GraphQLBoolean, + graphqlSync, + GraphQLSchema, +} from 'graphql'; + import { Logger } from '../Logger'; import { makeExecutableSchema, @@ -26,26 +46,6 @@ import { addResolversToSchema } from '../generate'; import TypeA from './circularSchemaA'; -import { GraphQLJSON } from 'graphql-type-json'; -import { assert, expect } from 'chai'; -import { - graphql, - GraphQLResolveInfo, - GraphQLScalarType, - Kind, - IntValueNode, - parse, - ExecutionResult, - GraphQLError, - GraphQLEnumType, - execute, - VariableDefinitionNode, - DocumentNode, - GraphQLBoolean, - graphqlSync, - GraphQLSchema, -} from 'graphql'; - interface Bird { name: string; wingspan?: number; diff --git a/src/test/testStitchingFromSubschemas.ts b/src/test/testStitchingFromSubschemas.ts index dba307cb9b6..7f49a29e208 100644 --- a/src/test/testStitchingFromSubschemas.ts +++ b/src/test/testStitchingFromSubschemas.ts @@ -11,15 +11,15 @@ // The fragment field is still necessary when working with a remote schema // where this is not possible. +import { expect } from 'chai'; +import { graphql } from 'graphql'; + import { delegateToSchema, mergeSchemas, addMocksToSchema, } from '../index'; -import { expect } from 'chai'; -import { graphql } from 'graphql'; - const chirpTypeDefs = ` type Chirp { id: ID! diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 34f6501f4e2..6e38bdaa9b1 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -1,3 +1,15 @@ +import { expect } from 'chai'; +import { + GraphQLSchema, + GraphQLNamedType, + GraphQLScalarType, + graphql, + Kind, + SelectionSetNode, + print, + parse, +} from 'graphql'; + import { makeExecutableSchema } from '../makeExecutableSchema'; import { delegateToSchema, @@ -21,17 +33,6 @@ import { import { propertySchema, bookingSchema } from './testingSchemas'; -import { expect } from 'chai'; -import { - GraphQLSchema, - GraphQLNamedType, - GraphQLScalarType, - graphql, - Kind, - SelectionSetNode, - print, - parse, -} from 'graphql'; describe('transforms', () => { describe('base transform function', () => { diff --git a/src/test/testTypeMerging.ts b/src/test/testTypeMerging.ts index 39024b8feca..156eb6ec238 100644 --- a/src/test/testTypeMerging.ts +++ b/src/test/testTypeMerging.ts @@ -1,15 +1,15 @@ // The below is meant to be an alternative canonical schema stitching example // which relies on type merging. +import { expect } from 'chai'; +import { graphql } from 'graphql'; + import { mergeSchemas, addMocksToSchema, makeExecutableSchema, } from '../index'; -import { expect } from 'chai'; -import { graphql } from 'graphql'; - const chirpSchema = makeExecutableSchema({ typeDefs: ` type Chirp { diff --git a/src/test/testUpload.ts b/src/test/testUpload.ts index 8c5a38b5a51..e3046c801db 100644 --- a/src/test/testUpload.ts +++ b/src/test/testUpload.ts @@ -2,11 +2,6 @@ import { Server } from 'http'; import { AddressInfo } from 'net'; import { Readable } from 'stream'; -import { SubschemaConfig } from '../Interfaces'; -import { createServerHttpLink } from '../links'; -import { makeExecutableSchema } from '../makeExecutableSchema'; -import { mergeSchemas} from '../stitching'; - import { expect } from 'chai'; import express, { Express } from 'express'; import graphqlHTTP from 'express-graphql'; @@ -15,6 +10,11 @@ import FormData from 'form-data'; import fetch from 'node-fetch'; import { buildSchema } from 'graphql'; +import { mergeSchemas} from '../stitching'; +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { createServerHttpLink } from '../links'; +import { SubschemaConfig } from '../Interfaces'; + function streamToString(stream: Readable) { const chunks: Array = []; return new Promise((resolve, reject) => { diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 9f6dfc2165a..12107a6bede 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -1,9 +1,9 @@ -import { healSchema } from '../utils'; -import { makeExecutableSchema } from '../makeExecutableSchema'; - import { expect } from 'chai'; import { GraphQLObjectType, GraphQLObjectTypeConfig } from 'graphql'; +import { healSchema } from '../utils'; +import { makeExecutableSchema } from '../makeExecutableSchema'; + describe('heal', () => { it('should prune empty types', () => { const schema = makeExecutableSchema({ diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index 1d0f9ad9133..aee75b331ea 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -1,11 +1,3 @@ -import { makeExecutableSchema } from '../makeExecutableSchema'; -import { - IResolvers, - Fetcher, - SubschemaConfig, -} from '../Interfaces'; -import introspectSchema from '../stitching/introspectSchema'; - import { PubSub } from 'graphql-subscriptions'; import { ApolloLink, @@ -26,6 +18,14 @@ import { } from 'graphql'; import { forAwaitEach } from 'iterall'; +import introspectSchema from '../stitching/introspectSchema'; +import { + IResolvers, + Fetcher, + SubschemaConfig, +} from '../Interfaces'; +import { makeExecutableSchema } from '../makeExecutableSchema'; + export type Location = { name: string; coordinates: string; diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index a0d761fe0bd..6f7986075c3 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -1,8 +1,3 @@ -import { Request } from '../Interfaces'; -import { serializeInputValue } from '../utils'; - -import { Transform } from './transforms'; - import { ArgumentNode, DocumentNode, @@ -21,6 +16,11 @@ import { VariableDefinitionNode, } from 'graphql'; +import { Request } from '../Interfaces'; +import { serializeInputValue } from '../utils'; + +import { Transform } from './transforms'; + export default class AddArgumentsAsVariablesTransform implements Transform { private readonly targetSchema: GraphQLSchema; private readonly args: { [key: string]: any }; diff --git a/src/transforms/AddMergedTypeSelectionSets.ts b/src/transforms/AddMergedTypeSelectionSets.ts index 3ea058965d2..45b7c3f39e0 100644 --- a/src/transforms/AddMergedTypeSelectionSets.ts +++ b/src/transforms/AddMergedTypeSelectionSets.ts @@ -1,7 +1,3 @@ -import { Request, MergedTypeInfo } from '../Interfaces'; - -import { Transform } from './transforms'; - import { DocumentNode, GraphQLSchema, @@ -13,6 +9,10 @@ import { visitWithTypeInfo, } from 'graphql'; +import { Request, MergedTypeInfo } from '../Interfaces'; + +import { Transform } from './transforms'; + export default class AddMergedTypeFragments implements Transform { private readonly targetSchema: GraphQLSchema; private readonly mapping: Record; diff --git a/src/transforms/AddReplacementFragments.ts b/src/transforms/AddReplacementFragments.ts index 9b31ac0b9d3..251d3962943 100644 --- a/src/transforms/AddReplacementFragments.ts +++ b/src/transforms/AddReplacementFragments.ts @@ -1,7 +1,3 @@ -import { Request, ReplacementFragmentMapping } from '../Interfaces'; - -import { Transform } from './transforms'; - import { DocumentNode, GraphQLSchema, @@ -13,6 +9,10 @@ import { visitWithTypeInfo, } from 'graphql'; +import { Request, ReplacementFragmentMapping } from '../Interfaces'; + +import { Transform } from './transforms'; + export default class AddReplacementFragments implements Transform { private readonly targetSchema: GraphQLSchema; private readonly mapping: ReplacementFragmentMapping; diff --git a/src/transforms/AddReplacementSelectionSets.ts b/src/transforms/AddReplacementSelectionSets.ts index 5e8d25b6a0a..1b09837f8e5 100644 --- a/src/transforms/AddReplacementSelectionSets.ts +++ b/src/transforms/AddReplacementSelectionSets.ts @@ -1,7 +1,3 @@ -import { Request, ReplacementSelectionSetMapping } from '../Interfaces'; - -import { Transform } from './transforms'; - import { DocumentNode, GraphQLSchema, @@ -13,6 +9,10 @@ import { visitWithTypeInfo, } from 'graphql'; +import { Request, ReplacementSelectionSetMapping } from '../Interfaces'; + +import { Transform } from './transforms'; + export default class AddReplacementSelectionSets implements Transform { private readonly schema: GraphQLSchema; private readonly mapping: ReplacementSelectionSetMapping; diff --git a/src/transforms/AddTypenameToAbstract.ts b/src/transforms/AddTypenameToAbstract.ts index f700bdde441..d7f3c4a7f7d 100644 --- a/src/transforms/AddTypenameToAbstract.ts +++ b/src/transforms/AddTypenameToAbstract.ts @@ -1,10 +1,10 @@ +import { GraphQLSchema } from 'graphql'; + import { Request } from '../Interfaces'; import { addTypenameToAbstract } from '../stitching/addTypenameToAbstract'; import { Transform } from './transforms'; -import { GraphQLSchema } from 'graphql'; - export default class AddTypenameToAbstract implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/transforms/CheckResultAndHandleErrors.ts b/src/transforms/CheckResultAndHandleErrors.ts index a49c8325fa6..ab8aa0e34a9 100644 --- a/src/transforms/CheckResultAndHandleErrors.ts +++ b/src/transforms/CheckResultAndHandleErrors.ts @@ -1,10 +1,10 @@ +import { GraphQLSchema, GraphQLOutputType } from 'graphql'; + import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; import { Transform } from './transforms'; -import { GraphQLSchema, GraphQLOutputType } from 'graphql'; - export default class CheckResultAndHandleErrors implements Transform { private readonly context?: Record; private readonly info: IGraphQLToolsResolveInfo; diff --git a/src/transforms/ExpandAbstractTypes.ts b/src/transforms/ExpandAbstractTypes.ts index 37e2fd0645a..7761d51494f 100644 --- a/src/transforms/ExpandAbstractTypes.ts +++ b/src/transforms/ExpandAbstractTypes.ts @@ -1,6 +1,3 @@ -import implementsAbstractType from '../utils/implementsAbstractType'; -import { Transform, Request } from '../Interfaces'; - import { DocumentNode, FragmentDefinitionNode, @@ -17,6 +14,9 @@ import { visitWithTypeInfo, } from 'graphql'; +import implementsAbstractType from '../utils/implementsAbstractType'; +import { Transform, Request } from '../Interfaces'; + type TypeMapping = { [key: string]: Array }; export default class ExpandAbstractTypes implements Transform { diff --git a/src/transforms/ExtendSchema.ts b/src/transforms/ExtendSchema.ts index 623df9a0671..a6661c346f0 100644 --- a/src/transforms/ExtendSchema.ts +++ b/src/transforms/ExtendSchema.ts @@ -1,17 +1,16 @@ -import { IFieldResolver, IResolvers, Request } from '../Interfaces'; +import { + GraphQLSchema, + extendSchema, + parse, +} from 'graphql'; +import { IFieldResolver, IResolvers, Request } from '../Interfaces'; import { addResolversToSchema } from '../generate'; import { defaultMergedResolver } from '../stitching'; import { Transform } from './transforms'; import MapFields, { FieldNodeTransformerMap } from './MapFields'; -import { - GraphQLSchema, - extendSchema, - parse, -} from 'graphql'; - export default class ExtendSchema implements Transform { private readonly typeDefs: string | undefined; private readonly resolvers: IResolvers | undefined; diff --git a/src/transforms/ExtractField.ts b/src/transforms/ExtractField.ts index 15d13f817c6..7d8003f408f 100644 --- a/src/transforms/ExtractField.ts +++ b/src/transforms/ExtractField.ts @@ -1,5 +1,3 @@ -import { Transform, Request } from '../Interfaces'; - import { visit, Kind, @@ -8,6 +6,8 @@ import { FieldNode, } from 'graphql'; +import { Transform, Request } from '../Interfaces'; + export default class ExtractField implements Transform { private readonly from: Array; private readonly to: Array; diff --git a/src/transforms/FilterObjectFields.ts b/src/transforms/FilterObjectFields.ts index f47b66fcf8f..4577c01ac66 100644 --- a/src/transforms/FilterObjectFields.ts +++ b/src/transforms/FilterObjectFields.ts @@ -1,8 +1,8 @@ +import { GraphQLField, GraphQLSchema } from 'graphql'; + import { Transform } from './transforms'; import TransformObjectFields from './TransformObjectFields'; -import { GraphQLField, GraphQLSchema } from 'graphql'; - export type ObjectFilter = (typeName: string, fieldName: string, field: GraphQLField) => boolean; export default class FilterObjectFields implements Transform { diff --git a/src/transforms/FilterRootFields.ts b/src/transforms/FilterRootFields.ts index 4e316912ce5..196a6cbf68c 100644 --- a/src/transforms/FilterRootFields.ts +++ b/src/transforms/FilterRootFields.ts @@ -1,8 +1,8 @@ +import { GraphQLField, GraphQLSchema } from 'graphql'; + import { Transform } from './transforms'; import TransformRootFields from './TransformRootFields'; -import { GraphQLField, GraphQLSchema } from 'graphql'; - export type RootFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', fieldName: string, diff --git a/src/transforms/FilterToSchema.ts b/src/transforms/FilterToSchema.ts index 2ae2ee615bf..b60f9d8982f 100644 --- a/src/transforms/FilterToSchema.ts +++ b/src/transforms/FilterToSchema.ts @@ -1,8 +1,3 @@ -import { Request } from '../Interfaces'; -import implementsAbstractType from '../utils/implementsAbstractType'; - -import { Transform } from './transforms'; - import { ArgumentNode, DocumentNode, @@ -26,6 +21,11 @@ import { getNamedType, } from 'graphql'; +import { Request } from '../Interfaces'; +import implementsAbstractType from '../utils/implementsAbstractType'; + +import { Transform } from './transforms'; + export default class FilterToSchema implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/transforms/FilterTypes.ts b/src/transforms/FilterTypes.ts index 6500f5d4bb4..3584c1fba63 100644 --- a/src/transforms/FilterTypes.ts +++ b/src/transforms/FilterTypes.ts @@ -1,9 +1,9 @@ +import { GraphQLSchema, GraphQLNamedType } from 'graphql'; + import { Transform } from '../transforms/transforms'; import { visitSchema } from '../utils/visitSchema'; import { VisitSchemaKind } from '../Interfaces'; -import { GraphQLSchema, GraphQLNamedType } from 'graphql'; - export default class FilterTypes implements Transform { private readonly filter: (type: GraphQLNamedType) => boolean; diff --git a/src/transforms/HoistField.ts b/src/transforms/HoistField.ts index 2f4ed8f4ba0..3c9dd77eae9 100644 --- a/src/transforms/HoistField.ts +++ b/src/transforms/HoistField.ts @@ -1,3 +1,9 @@ +import { + GraphQLSchema, + GraphQLObjectType, + getNullableType, +} from 'graphql'; + import { healSchema, wrapFieldNode, renameFieldNode } from '../utils'; import { createMergedResolver } from '../stitching'; import { appendFields, removeFields } from '../utils/fields'; @@ -6,12 +12,6 @@ import { Request } from '../Interfaces'; import { Transform } from './transforms'; import MapFields from './MapFields'; -import { - GraphQLSchema, - GraphQLObjectType, - getNullableType, -} from 'graphql'; - export default class HoistField implements Transform { private readonly typeName: string; private readonly path: Array; diff --git a/src/transforms/MapFields.ts b/src/transforms/MapFields.ts index 86be806cb0c..bee993374e2 100644 --- a/src/transforms/MapFields.ts +++ b/src/transforms/MapFields.ts @@ -1,7 +1,3 @@ -import { Request } from '../Interfaces'; - -import { Transform } from './transforms'; - import { GraphQLSchema, GraphQLType, @@ -15,6 +11,10 @@ import { FragmentDefinitionNode } from 'graphql'; +import { Request } from '../Interfaces'; + +import { Transform } from './transforms'; + export type FieldNodeTransformer = ( fieldNode: FieldNode, fragments: Record diff --git a/src/transforms/RenameObjectFields.ts b/src/transforms/RenameObjectFields.ts index 4e56b37a02a..e14b0769105 100644 --- a/src/transforms/RenameObjectFields.ts +++ b/src/transforms/RenameObjectFields.ts @@ -1,10 +1,10 @@ +import { GraphQLField, GraphQLSchema } from 'graphql'; + import { Request } from '../Interfaces'; import TransformObjectFields from './TransformObjectFields'; import { Transform } from './transforms'; -import { GraphQLField, GraphQLSchema } from 'graphql'; - export default class RenameObjectFields implements Transform { private readonly transformer: TransformObjectFields; diff --git a/src/transforms/RenameRootFields.ts b/src/transforms/RenameRootFields.ts index e8f934fc32a..8b993b78db8 100644 --- a/src/transforms/RenameRootFields.ts +++ b/src/transforms/RenameRootFields.ts @@ -1,10 +1,10 @@ +import { GraphQLField, GraphQLSchema } from 'graphql'; + import { Request } from '../Interfaces'; import { Transform } from './transforms'; import TransformRootFields from './TransformRootFields'; -import { GraphQLField, GraphQLSchema } from 'graphql'; - export default class RenameRootFields implements Transform { private readonly transformer: TransformRootFields; diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index 3dd542003b6..4caf9088935 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -1,8 +1,3 @@ -import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; -import { Request, Result, VisitSchemaKind } from '../Interfaces'; -import { Transform } from '../transforms/transforms'; -import { visitSchema, cloneType } from '../utils'; - import { visit, GraphQLSchema, @@ -12,6 +7,11 @@ import { GraphQLScalarType, } from 'graphql'; +import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; +import { Request, Result, VisitSchemaKind } from '../Interfaces'; +import { Transform } from '../transforms/transforms'; +import { visitSchema, cloneType } from '../utils'; + export type RenameOptions = { renameBuiltins: boolean; renameScalars: boolean; diff --git a/src/transforms/ReplaceFieldWithFragment.ts b/src/transforms/ReplaceFieldWithFragment.ts index c91f4ce05f7..7fbce45ddda 100644 --- a/src/transforms/ReplaceFieldWithFragment.ts +++ b/src/transforms/ReplaceFieldWithFragment.ts @@ -1,8 +1,3 @@ -import { concatInlineFragments } from '../utils'; -import { Request } from '../Interfaces'; - -import { Transform } from './transforms'; - import { DocumentNode, GraphQLSchema, @@ -17,6 +12,11 @@ import { visitWithTypeInfo, } from 'graphql'; +import { concatInlineFragments } from '../utils'; +import { Request } from '../Interfaces'; + +import { Transform } from './transforms'; + export default class ReplaceFieldWithFragment implements Transform { private readonly targetSchema: GraphQLSchema; private readonly mapping: FieldToFragmentMapping; diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index bbf98f55a38..aaab2f0368e 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -1,9 +1,3 @@ -import isEmptyObject from '../utils/isEmptyObject'; -import { Request, VisitSchemaKind } from '../Interfaces'; -import { visitSchema } from '../utils/visitSchema'; - -import { Transform } from './transforms'; - import { GraphQLObjectType, GraphQLSchema, @@ -21,6 +15,12 @@ import { FragmentDefinitionNode } from 'graphql'; +import isEmptyObject from '../utils/isEmptyObject'; +import { Request, VisitSchemaKind } from '../Interfaces'; +import { visitSchema } from '../utils/visitSchema'; + +import { Transform } from './transforms'; + export type ObjectFieldTransformer = ( typeName: string, fieldName: string, diff --git a/src/transforms/TransformQuery.ts b/src/transforms/TransformQuery.ts index 63e6161265b..f0c967a4a65 100644 --- a/src/transforms/TransformQuery.ts +++ b/src/transforms/TransformQuery.ts @@ -1,7 +1,3 @@ -import { Request, Result } from '../Interfaces'; - -import { Transform } from './transforms'; - import { visit, Kind, @@ -10,6 +6,10 @@ import { GraphQLError, } from 'graphql'; +import { Request, Result } from '../Interfaces'; + +import { Transform } from './transforms'; + export type QueryTransformer = ( selectionSet: SelectionSetNode, fragments: Record diff --git a/src/transforms/TransformRootFields.ts b/src/transforms/TransformRootFields.ts index a123c539407..94c3e2acac1 100644 --- a/src/transforms/TransformRootFields.ts +++ b/src/transforms/TransformRootFields.ts @@ -1,14 +1,14 @@ -import { Request } from '../Interfaces'; - -import { Transform } from './transforms'; -import TransformObjectFields, { FieldNodeTransformer } from './TransformObjectFields'; - import { GraphQLSchema, GraphQLField, GraphQLFieldConfig, } from 'graphql'; +import { Request } from '../Interfaces'; + +import { Transform } from './transforms'; +import TransformObjectFields, { FieldNodeTransformer } from './TransformObjectFields'; + export type RootTransformer = ( operation: 'Query' | 'Mutation' | 'Subscription', fieldName: string, diff --git a/src/transforms/WrapFields.ts b/src/transforms/WrapFields.ts index d6d26064279..d597795390b 100644 --- a/src/transforms/WrapFields.ts +++ b/src/transforms/WrapFields.ts @@ -1,3 +1,8 @@ +import { + GraphQLSchema, + GraphQLObjectType, +} from 'graphql'; + import { Request } from '../Interfaces'; import { appendFields, removeFields } from '../utils/fields'; import { hoistFieldNodes, healSchema } from '../utils'; @@ -6,11 +11,6 @@ import { defaultMergedResolver, createMergedResolver } from '../stitching'; import { Transform } from './transforms'; import MapFields from './MapFields'; -import { - GraphQLSchema, - GraphQLObjectType, -} from 'graphql'; - export default class WrapFields implements Transform { private readonly outerTypeName: string; private readonly wrappingFieldNames: Array; diff --git a/src/transforms/WrapQuery.ts b/src/transforms/WrapQuery.ts index bf78bcd6da6..b3dc3aa8abe 100644 --- a/src/transforms/WrapQuery.ts +++ b/src/transforms/WrapQuery.ts @@ -1,7 +1,7 @@ -import { Transform, Request, Result } from '../Interfaces'; - import { FieldNode, visit, Kind, SelectionNode, SelectionSetNode } from 'graphql'; +import { Transform, Request, Result } from '../Interfaces'; + export type QueryWrapper = (subtree: SelectionSetNode) => SelectionNode | SelectionSetNode; export default class WrapQuery implements Transform { diff --git a/src/transforms/WrapType.ts b/src/transforms/WrapType.ts index bb8e2e05266..ddff33b58a5 100644 --- a/src/transforms/WrapType.ts +++ b/src/transforms/WrapType.ts @@ -1,10 +1,10 @@ +import { GraphQLSchema } from 'graphql'; + import { Request } from '../Interfaces'; import { Transform } from './transforms'; import WrapFields from './WrapFields'; -import { GraphQLSchema } from 'graphql'; - export default class WrapType implements Transform { private readonly transformer: Transform; diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index eaa3950de46..37684ee4a02 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -1,7 +1,3 @@ -import { GraphQLSchemaWithTransforms, VisitSchemaKind } from '../Interfaces'; -import { visitSchema } from '../utils/visitSchema'; -import { cloneSchema } from '../utils/clone'; - import { GraphQLEnumType, GraphQLInputObjectType, @@ -12,6 +8,10 @@ import { GraphQLType, } from 'graphql'; +import { GraphQLSchemaWithTransforms, VisitSchemaKind } from '../Interfaces'; +import { visitSchema } from '../utils/visitSchema'; +import { cloneSchema } from '../utils/clone'; + export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', rootFieldName: string diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index c5c6b3263e3..ff9bd34e671 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -1,3 +1,5 @@ +import { GraphQLSchema } from 'graphql'; + import { addResolversToSchema } from '../makeExecutableSchema'; import { Transform, applySchemaTransforms } from '../transforms/transforms'; import { @@ -9,11 +11,8 @@ import { isSubschemaConfig, GraphQLSchemaWithTransforms, } from '../Interfaces'; - import { cloneSchema } from '../utils/clone'; -import { GraphQLSchema } from 'graphql'; - export function wrapSchema( subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, transforms?: Array, diff --git a/src/transforms/transforms.ts b/src/transforms/transforms.ts index 72e9930d478..44fc62b4c33 100644 --- a/src/transforms/transforms.ts +++ b/src/transforms/transforms.ts @@ -1,8 +1,8 @@ +import { GraphQLSchema } from 'graphql'; + import { Request, Transform } from '../Interfaces'; import { cloneSchema } from '../utils'; -import { GraphQLSchema } from 'graphql'; - export { Transform }; export function applySchemaTransforms( diff --git a/src/utils/SchemaDirectiveVisitor.ts b/src/utils/SchemaDirectiveVisitor.ts index c00de99b8a6..dcf3bf594f3 100644 --- a/src/utils/SchemaDirectiveVisitor.ts +++ b/src/utils/SchemaDirectiveVisitor.ts @@ -1,19 +1,17 @@ +import { + GraphQLDirective, + GraphQLSchema, + DirectiveLocationEnum, +} from 'graphql'; +import { getArgumentValues } from 'graphql/execution/values'; + import { VisitableSchemaType } from '../Interfaces'; import each from './each'; import valueFromASTUntyped from './valueFromASTUntyped'; - import { SchemaVisitor } from './SchemaVisitor'; import { visitSchema } from './visitSchema'; -import { getArgumentValues } from 'graphql/execution/values'; - -import { - GraphQLDirective, - GraphQLSchema, - DirectiveLocationEnum, -} from 'graphql'; - const hasOwn = Object.prototype.hasOwnProperty; // This class represents a reusable implementation of a @directive that may diff --git a/src/utils/clone.ts b/src/utils/clone.ts index a2520948bd0..662d27b3f3d 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -1,6 +1,3 @@ -import { healTypes } from './heal'; -import isSpecifiedScalarType from './isSpecifiedScalarType'; - import { GraphQLDirective, GraphQLEnumType, @@ -13,6 +10,9 @@ import { GraphQLUnionType, } from 'graphql'; +import { healTypes } from './heal'; +import isSpecifiedScalarType from './isSpecifiedScalarType'; + export function cloneDirective(directive: GraphQLDirective): GraphQLDirective { return new GraphQLDirective(directive.toConfig()); } diff --git a/src/utils/forEachDefaultValue.ts b/src/utils/forEachDefaultValue.ts index b54e452124a..8bda359a78d 100644 --- a/src/utils/forEachDefaultValue.ts +++ b/src/utils/forEachDefaultValue.ts @@ -1,5 +1,3 @@ -import { IDefaultValueIteratorFn } from '../Interfaces'; - import { getNamedType, GraphQLInputObjectType, @@ -7,6 +5,8 @@ import { GraphQLObjectType, } from 'graphql'; +import { IDefaultValueIteratorFn } from '../Interfaces'; + export function forEachDefaultValue(schema: GraphQLSchema, fn: IDefaultValueIteratorFn): void { const typeMap = schema.getTypeMap(); diff --git a/src/utils/forEachField.ts b/src/utils/forEachField.ts index 12f7f565d47..09a94f29ab5 100644 --- a/src/utils/forEachField.ts +++ b/src/utils/forEachField.ts @@ -1,11 +1,11 @@ -import { IFieldIteratorFn } from '../Interfaces'; - import { getNamedType, GraphQLObjectType, GraphQLSchema, } from 'graphql'; +import { IFieldIteratorFn } from '../Interfaces'; + export function forEachField(schema: GraphQLSchema, fn: IFieldIteratorFn): void { const typeMap = schema.getTypeMap(); Object.keys(typeMap).forEach(typeName => { diff --git a/src/utils/getResolversFromSchema.ts b/src/utils/getResolversFromSchema.ts index 7d505b62036..00ef7ed457c 100644 --- a/src/utils/getResolversFromSchema.ts +++ b/src/utils/getResolversFromSchema.ts @@ -1,8 +1,3 @@ -import { IResolvers } from '../Interfaces'; - -import isSpecifiedScalarType from './isSpecifiedScalarType'; -import { cloneType } from './clone'; - import { GraphQLSchema, GraphQLScalarType, @@ -12,6 +7,11 @@ import { GraphQLUnionType, } from 'graphql'; +import { IResolvers } from '../Interfaces'; + +import isSpecifiedScalarType from './isSpecifiedScalarType'; +import { cloneType } from './clone'; + export function getResolversFromSchema(schema: GraphQLSchema): IResolvers { const resolvers = Object.create({}); diff --git a/src/utils/heal.ts b/src/utils/heal.ts index 1090bb27bde..7ce80dc74ad 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -1,8 +1,3 @@ -import each from './each'; -import updateEachKey from './updateEachKey'; -import { isStub, getBuiltInForStub } from './stub'; -import { cloneSchema } from './clone'; - import { GraphQLDirective, GraphQLEnumType, @@ -21,6 +16,11 @@ import { GraphQLOutputType, } from 'graphql'; +import each from './each'; +import updateEachKey from './updateEachKey'; +import { isStub, getBuiltInForStub } from './stub'; +import { cloneSchema } from './clone'; + type NamedTypeMap = { [key: string]: GraphQLNamedType; }; diff --git a/src/utils/visitSchema.ts b/src/utils/visitSchema.ts index a569b011a10..b59fe6a4708 100644 --- a/src/utils/visitSchema.ts +++ b/src/utils/visitSchema.ts @@ -1,16 +1,3 @@ -import { - VisitableSchemaType, - VisitorSelector, - VisitSchemaKind, - NamedTypeVisitor, - SchemaVisitorMap, -} from '../Interfaces'; - -import updateEachKey from './updateEachKey'; -import { healSchema } from './heal'; -import { SchemaVisitor } from './SchemaVisitor'; -import each from './each'; - import { GraphQLEnumType, GraphQLInputObjectType, @@ -25,6 +12,19 @@ import { GraphQLInputField, } from 'graphql'; +import { + VisitableSchemaType, + VisitorSelector, + VisitSchemaKind, + NamedTypeVisitor, + SchemaVisitorMap, +} from '../Interfaces'; + +import updateEachKey from './updateEachKey'; +import { healSchema } from './heal'; +import { SchemaVisitor } from './SchemaVisitor'; +import each from './each'; + // Generic function for visiting GraphQLSchema objects. export function visitSchema( schema: GraphQLSchema, From a99ae19f35d3cc5a0b17b9fff4fa1669ec2989f5 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 13 Feb 2020 16:49:13 -0500 Subject: [PATCH 184/250] chore(prettier): use prettier! Run prettier on codebase. Add prettier-check to ci workflow. --- package.json | 7 +- src/Interfaces.ts | 59 +- src/generate/addResolversToSchema.ts | 22 +- src/generate/addSchemaLevelResolver.ts | 5 +- src/generate/attachConnectorsToContext.ts | 6 +- src/generate/attachDirectiveResolvers.ts | 18 +- src/generate/chainResolvers.ts | 17 +- src/generate/checkForResolveTypeResolver.ts | 6 +- src/generate/concatenateTypeDefs.ts | 11 +- src/generate/decorateWithLogger.ts | 2 +- src/links/createServerHttpLink.ts | 64 +- src/links/index.ts | 4 +- src/makeExecutableSchema.ts | 35 +- src/mock.ts | 37 +- src/scalars/GraphQLUpload.ts | 4 +- src/scalars/index.ts | 4 +- src/stitching/checkResultAndHandleErrors.ts | 136 ++-- src/stitching/createMergedResolver.ts | 50 +- src/stitching/createRequest.ts | 57 +- src/stitching/delegateToSchema.ts | 165 +++-- src/stitching/errors.ts | 33 +- src/stitching/getResponseKeyFromInfo.ts | 4 +- src/stitching/index.ts | 5 +- src/stitching/introspectSchema.ts | 23 +- src/stitching/linkToFetcher.ts | 7 +- src/stitching/makeMergedType.ts | 5 +- src/stitching/makeRemoteExecutableSchema.ts | 29 +- src/stitching/mapAsyncIterator.ts | 4 +- src/stitching/mergeFields.ts | 100 +-- src/stitching/mergeInfo.ts | 118 ++-- src/stitching/mergeSchemas.ts | 128 ++-- src/stitching/observableToAsyncIterable.ts | 31 +- src/stitching/proxiedResult.ts | 102 ++- src/stitching/resolveFromParentTypename.ts | 4 +- src/stitching/resolvers.ts | 17 +- src/stitching/typeFromAST.ts | 28 +- src/test/testAlternateMergeSchemas.ts | 522 +++++++------- src/test/testDataloader.ts | 78 +-- src/test/testDelegateToSchema.ts | 63 +- src/test/testDirectives.ts | 639 ++++++++++-------- src/test/testErrors.ts | 206 +++--- src/test/testExtensionExtraction.ts | 5 +- src/test/testFragmentsAreNotDuplicated.ts | 12 +- src/test/testLogger.ts | 17 +- src/test/testMakeRemoteExecutableSchema.ts | 98 ++- src/test/testMergeSchemas.ts | 95 +-- src/test/testMocking.ts | 39 +- src/test/testResolution.ts | 16 +- src/test/testSchemaGenerator.ts | 148 ++-- src/test/testStitchingFromSubschemas.ts | 58 +- src/test/testTransforms.ts | 132 ++-- src/test/testTypeMerging.ts | 43 +- src/test/testUpload.ts | 21 +- src/test/testUtils.ts | 6 +- src/test/testingSchemas.ts | 183 ++--- src/transforms/AddArgumentsAsVariables.ts | 6 +- src/transforms/AddMergedTypeSelectionSets.ts | 5 +- src/transforms/AddReplacementFragments.ts | 5 +- src/transforms/AddReplacementSelectionSets.ts | 10 +- src/transforms/ExpandAbstractTypes.ts | 17 +- src/transforms/ExtendSchema.ts | 21 +- src/transforms/ExtractField.ts | 13 +- src/transforms/FilterObjectFields.ts | 8 +- src/transforms/FilterToSchema.ts | 166 ++--- src/transforms/HoistField.ts | 25 +- src/transforms/MapFields.ts | 44 +- src/transforms/RenameObjectFields.ts | 12 +- src/transforms/RenameRootFields.ts | 4 +- src/transforms/RenameTypes.ts | 11 +- src/transforms/ReplaceFieldWithFragment.ts | 1 - src/transforms/TransformObjectFields.ts | 58 +- src/transforms/TransformQuery.ts | 42 +- src/transforms/TransformRootFields.ts | 44 +- src/transforms/WrapFields.ts | 28 +- src/transforms/WrapQuery.ts | 30 +- src/transforms/WrapType.ts | 6 +- src/transforms/filterSchema.ts | 54 +- src/transforms/transformSchema.ts | 8 +- src/transforms/transforms.ts | 8 +- src/utils/SchemaDirectiveVisitor.ts | 73 +- src/utils/SchemaVisitor.ts | 88 ++- src/utils/clone.ts | 11 +- src/utils/fieldNodes.ts | 77 ++- src/utils/forEachDefaultValue.ts | 6 +- src/utils/forEachField.ts | 11 +- src/utils/fragments.ts | 7 +- src/utils/heal.ts | 40 +- src/utils/index.ts | 5 +- src/utils/selectionSets.ts | 8 +- src/utils/stub.ts | 4 +- src/utils/transformInputValue.ts | 46 +- src/utils/valueFromASTUntyped.ts | 48 +- src/utils/visitSchema.ts | 65 +- 93 files changed, 2763 insertions(+), 2080 deletions(-) diff --git a/package.json b/package.json index 3e29ac4f931..78bc4adec77 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "clean": "rimraf dist", "compile": "tsc", "pretest": "npm run clean && npm run compile", - "test": "npm run testonly --", - "posttest": "npm run lint", + "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", @@ -20,7 +20,8 @@ "postcoverage": "remap-istanbul --input coverage/coverage.json --type lcovonly --output coverage/lcov.info", "prepublishOnly": "npm run compile", "prerelease": "npm test", - "prettier": "prettier --trailing-comma all --single-quote --write 'src/**/*.ts'", + "prettier": "prettier --trailing-comma all --single-quote --write src/**/*.ts", + "prettier:check": "prettier --trailing-comma all --single-quote --check src/**/*.ts", "release": "standard-version" }, "repository": { diff --git a/src/Interfaces.ts b/src/Interfaces.ts index c049d65e057..e96e836fd29 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -77,7 +77,9 @@ export interface IGraphQLToolsResolveInfo extends GraphQLResolveInfo { mergeInfo?: MergeInfo; } -export type Fetcher = (operation: IFetcherOperation) => Promise; +export type Fetcher = ( + operation: IFetcherOperation, +) => Promise; export interface IFetcherOperation { query: DocumentNode; @@ -115,16 +117,20 @@ export type MergedTypeResolver = ( selectionSet: SelectionSetNode, ) => any; -export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array }; +export type GraphQLSchemaWithTransforms = GraphQLSchema & { + transforms?: Array; +}; export type SchemaLikeObject = - SubschemaConfig | - GraphQLSchema | - string | - DocumentNode | - Array; - -export function isSubschemaConfig(value: SchemaLikeObject): value is SubschemaConfig { + | SubschemaConfig + | GraphQLSchema + | string + | DocumentNode + | Array; + +export function isSubschemaConfig( + value: SchemaLikeObject, +): value is SubschemaConfig { return Boolean((value as SubschemaConfig).schema); } @@ -158,7 +164,11 @@ export type IDelegateRequestOptions = { request: Request; } & IDelegateToSchemaOptions; -export type Delegator = ({ document, context, variables }: { +export type Delegator = ({ + document, + context, + variables, +}: { document: DocumentNode; context?: { [key: string]: any }; variables?: { [key: string]: any }; @@ -177,9 +187,9 @@ export type MergeInfo = { field: string; fragment: string; }>; - replacementSelectionSets: ReplacementSelectionSetMapping, - replacementFragments: ReplacementFragmentMapping, - mergedTypes: Record, + replacementSelectionSets: ReplacementSelectionSetMapping; + replacementFragments: ReplacementFragmentMapping; + mergedTypes: Record; delegateToSchema(options: IDelegateToSchemaOptions): any; }; @@ -192,13 +202,13 @@ export type ReplacementFragmentMapping = { }; export type MergedTypeInfo = { - subschemas: Array, - selectionSet?: SelectionSetNode, - uniqueFields: Record, - nonUniqueFields: Record>, - typeMaps: Map, - selectionSets: Map, - containsSelectionSet: Map>, + subschemas: Array; + selectionSet?: SelectionSetNode; + uniqueFields: Record; + nonUniqueFields: Record>; + typeMaps: Map; + selectionSets: Map; + containsSelectionSet: Map>; }; export type IFieldResolver> = ( @@ -340,7 +350,7 @@ export type GraphQLParseOptions = { export type IndexedObject = { [key: string]: V } | ReadonlyArray; export type VisitableSchemaType = - GraphQLSchema + | GraphQLSchema | GraphQLObjectType | GraphQLInterfaceType | GraphQLInputObjectType @@ -408,7 +418,12 @@ export type EnumTypeVisitor = ( export type CompositeTypeVisitor = ( type: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType, schema: GraphQLSchema, -) => GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType | null | undefined; +) => + | GraphQLObjectType + | GraphQLInterfaceType + | GraphQLUnionType + | null + | undefined; export type ObjectTypeVisitor = ( type: GraphQLObjectType, diff --git a/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index 2292dc6be50..b5821db6a6b 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -31,11 +31,14 @@ function addResolversToSchema( legacyInputResolvers?: IResolvers, legacyInputValidationOptions?: IResolverValidationOptions, ) { - const options: IAddResolversToSchemaOptions = (schemaOrOptions instanceof GraphQLSchema) ? { - schema: schemaOrOptions, - resolvers: legacyInputResolvers, - resolverValidationOptions: legacyInputValidationOptions, - } : schemaOrOptions; + const options: IAddResolversToSchemaOptions = + schemaOrOptions instanceof GraphQLSchema + ? { + schema: schemaOrOptions, + resolvers: legacyInputResolvers, + resolverValidationOptions: legacyInputValidationOptions, + } + : schemaOrOptions; const { schema, @@ -128,9 +131,7 @@ function addResolversToSchema( const values = type.getValues(); const newValues = {}; values.forEach(value => { - const newValue = Object.keys(resolverValue).includes( - value.name, - ) + const newValue = Object.keys(resolverValue).includes(value.name) ? resolverValue[value.name] : value.name; newValues[value.name] = { @@ -161,7 +162,10 @@ function addResolversToSchema( `${typeName} was defined in resolvers, but it's not an object`, ); }); - } else if (type instanceof GraphQLInterfaceType || type instanceof GraphQLObjectType) { + } else if ( + type instanceof GraphQLInterfaceType || + type instanceof GraphQLObjectType + ) { Object.keys(resolverValue).forEach(fieldName => { if (fieldName.startsWith('__')) { // this is for isTypeOf and resolveType and all the other stuff. diff --git a/src/generate/addSchemaLevelResolver.ts b/src/generate/addSchemaLevelResolver.ts index 058fae93cd2..23a366e2312 100644 --- a/src/generate/addSchemaLevelResolver.ts +++ b/src/generate/addSchemaLevelResolver.ts @@ -26,7 +26,10 @@ function addSchemaLevelResolver( // 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); + fields[fieldName].resolve = wrapResolver( + fields[fieldName].resolve, + fn, + ); } else { fields[fieldName].resolve = wrapResolver( fields[fieldName].resolve, diff --git a/src/generate/attachConnectorsToContext.ts b/src/generate/attachConnectorsToContext.ts index dd0cb6c21fd..5f53c1efe35 100644 --- a/src/generate/attachConnectorsToContext.ts +++ b/src/generate/attachConnectorsToContext.ts @@ -40,7 +40,11 @@ const attachConnectorsToContext = deprecated( ); } schema['_apolloConnectorsAttached'] = true; - const attachconnectorFn: GraphQLFieldResolver = (root, _args, ctx) => { + const attachconnectorFn: GraphQLFieldResolver = ( + root, + _args, + ctx, + ) => { if (typeof ctx !== 'object') { // if in any way possible, we should throw an error when the attachconnectors // function is called, not when a query is executed. diff --git a/src/generate/attachDirectiveResolvers.ts b/src/generate/attachDirectiveResolvers.ts index a6d01cb39e4..8d9bd7c695c 100644 --- a/src/generate/attachDirectiveResolvers.ts +++ b/src/generate/attachDirectiveResolvers.ts @@ -25,18 +25,20 @@ function attachDirectiveResolvers( schemaDirectives[directiveName] = class extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const resolver = directiveResolvers[directiveName]; - const originalResolver = (field.resolve != null) ? field.resolve : defaultFieldResolver; + const originalResolver = + field.resolve != null ? field.resolve : defaultFieldResolver; const directiveArgs = this.args; field.resolve = (...args) => { const [source /* original args */, , context, info] = args; return resolver( - () => new Promise((resolve, reject) => { - const result = originalResolver.apply(field, args); - if (result instanceof Error) { - reject(result); - } - resolve(result); - }), + () => + 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/chainResolvers.ts b/src/generate/chainResolvers.ts index 903382e6db4..dd5cdfc30c2 100644 --- a/src/generate/chainResolvers.ts +++ b/src/generate/chainResolvers.ts @@ -1,7 +1,18 @@ -import { defaultFieldResolver, GraphQLResolveInfo, GraphQLFieldResolver } from 'graphql'; +import { + defaultFieldResolver, + GraphQLResolveInfo, + GraphQLFieldResolver, +} from 'graphql'; -export function chainResolvers(resolvers: Array>) { - return (root: any, args: { [argName: string]: any }, ctx: any, info: GraphQLResolveInfo) => +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); diff --git a/src/generate/checkForResolveTypeResolver.ts b/src/generate/checkForResolveTypeResolver.ts index 75bf947291e..64cb759f163 100644 --- a/src/generate/checkForResolveTypeResolver.ts +++ b/src/generate/checkForResolveTypeResolver.ts @@ -23,10 +23,8 @@ function checkForResolveTypeResolver( return; } throw new SchemaError( - `Type "${ - type.name - }" is missing a "__resolveType" resolver. Pass false into ` + - '"resolverValidationOptions.requireResolversForResolveType" to disable this error.', + `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 61face52f7a..207398edeb9 100644 --- a/src/generate/concatenateTypeDefs.ts +++ b/src/generate/concatenateTypeDefs.ts @@ -32,10 +32,13 @@ function concatenateTypeDefs( } function uniq(array: Array): Array { - return array.reduce((accumulator, currentValue) => - 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 9e08aeaa291..7f48e0f8f34 100644 --- a/src/generate/decorateWithLogger.ts +++ b/src/generate/decorateWithLogger.ts @@ -12,7 +12,7 @@ function decorateWithLogger( logger: ILogger, hint: string, ): GraphQLFieldResolver { - const resolver = (fn != null) ? fn : defaultFieldResolver; + const resolver = fn != null ? fn : defaultFieldResolver; const logError = (e: Error) => { // TODO: clone the error properly diff --git a/src/links/createServerHttpLink.ts b/src/links/createServerHttpLink.ts index d593a06cd67..3b85c676e25 100644 --- a/src/links/createServerHttpLink.ts +++ b/src/links/createServerHttpLink.ts @@ -15,7 +15,10 @@ import { UriFunction, } from 'apollo-link-http-common'; import { DefinitionNode } from 'graphql'; -import { extractFiles, isExtractableFile as defaultIsExtractableFile } from 'extract-files'; +import { + extractFiles, + isExtractableFile as defaultIsExtractableFile, +} from 'extract-files'; import KnownLengthFormData, { AppendOptions } from 'form-data'; import fetch from 'node-fetch'; @@ -29,11 +32,16 @@ class FormData extends KnownLengthFormData { this.hasUnknowableLength = false; } - public append(key: string, value: any, optionsOrFilename: AppendOptions | string = {}): void { + public append( + key: string, + value: any, + optionsOrFilename: AppendOptions | string = {}, + ): void { // allow filename as single option - const options: AppendOptions = (typeof optionsOrFilename === 'string') ? - { filename: optionsOrFilename } : - optionsOrFilename; + const options: AppendOptions = + typeof optionsOrFilename === 'string' + ? { filename: optionsOrFilename } + : optionsOrFilename; // empty or either doesn't have path or not an http response if ( @@ -49,7 +57,9 @@ class FormData extends KnownLengthFormData { super.append(key, value, options); } - public getLength(callback: (err: Error | null, length: number) => void): void { + public getLength( + callback: (err: Error | null, length: number) => void, + ): void { if (this.hasUnknowableLength) { return null; } @@ -77,7 +87,7 @@ export type Options = HttpOptions & { useGETForQueries?: boolean; serializer?: (method: string) => any; appendFile?: (form: FormData, index: string, file: File) => void; -} +}; // For backwards compatibility. export { HttpOptions as FetchOptions }; @@ -91,7 +101,7 @@ interface File { export const createServerHttpLink = (linkOptions: Options = {}) => { const { uri = '/graphql', - fetch: customFetch = fetch as unknown as WindowOrWorkerGlobalScope['fetch'], + fetch: customFetch = (fetch as unknown) as WindowOrWorkerGlobalScope['fetch'], serializer: customSerializer = defaultSerializer, appendFile: customAppendFile = defaultAppendFile, includeExtensions, @@ -149,7 +159,7 @@ export const createServerHttpLink = (linkOptions: Options = {}) => { if (!(options as any).signal) { const { controller: _controller, signal } = createSignalIfSupported(); controller = _controller; - if (controller as unknown as boolean) { + if ((controller as unknown) as boolean) { (options as any).signal = signal; } } @@ -174,16 +184,17 @@ export const createServerHttpLink = (linkOptions: Options = {}) => { } 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']; + 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; - }) + return options; + }) .then(newOptions => customFetch(chosenURI, newOptions)) .then(response => { operation.setContext({ response }); @@ -242,7 +253,7 @@ export const createServerHttpLink = (linkOptions: Options = {}) => { return () => { // XXX support canceling this request // https://developers.google.com/web/updates/2017/09/abortable-fetch - if (controller as unknown as boolean) { + if ((controller as unknown) as boolean) { controller.abort(); } }; @@ -317,19 +328,17 @@ function getFinalPromise(object: any): Promise { } 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 => { + 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; @@ -340,8 +349,11 @@ 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 { clone, files } = extractFiles( + body, + undefined, + (value: any) => defaultIsExtractableFile(value) || value?.createReadStream, + ); const payload = serializeFetchParameter(clone, 'Payload'); diff --git a/src/links/index.ts b/src/links/index.ts index c60c87c1bf4..a34c83329b9 100644 --- a/src/links/index.ts +++ b/src/links/index.ts @@ -1,5 +1,3 @@ import { createServerHttpLink } from './createServerHttpLink'; -export { - createServerHttpLink, -}; +export { createServerHttpLink }; diff --git a/src/makeExecutableSchema.ts b/src/makeExecutableSchema.ts index f73d6d0419c..d54a1b28cf9 100644 --- a/src/makeExecutableSchema.ts +++ b/src/makeExecutableSchema.ts @@ -5,11 +5,7 @@ import { } from 'graphql'; import { IExecutableSchemaDefinition, ILogger } from './Interfaces'; -import { - SchemaDirectiveVisitor, - forEachField, - mergeDeep -} from './utils'; +import { SchemaDirectiveVisitor, forEachField, mergeDeep } from './utils'; import { attachDirectiveResolvers, assertResolversPresent, @@ -18,10 +14,9 @@ import { addSchemaLevelResolver, buildSchemaFromTypeDefinitions, decorateWithLogger, - SchemaError + SchemaError, } from './generate'; - export function makeExecutableSchema({ typeDefs, resolvers = {}, @@ -32,11 +27,13 @@ export function makeExecutableSchema({ 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) { @@ -45,7 +42,9 @@ export function makeExecutableSchema({ // 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 @@ -56,7 +55,7 @@ export function makeExecutableSchema({ schema, resolvers: resolverMap, resolverValidationOptions, - inheritResolversFromInterfaces + inheritResolversFromInterfaces, }); assertResolversPresent(schema, resolverValidationOptions); @@ -72,7 +71,10 @@ export function makeExecutableSchema({ 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. - addSchemaLevelResolver(schema, resolvers['__schema'] as GraphQLFieldResolver); + addSchemaLevelResolver( + schema, + resolvers['__schema'] as GraphQLFieldResolver, + ); } if (connectors != null) { @@ -94,9 +96,9 @@ export function makeExecutableSchema({ function decorateToCatchUndefined( fn: GraphQLFieldResolver, - hint: string + hint: string, ): GraphQLFieldResolver { - const resolve = (fn == null) ? defaultFieldResolver : fn; + const resolve = fn == null ? defaultFieldResolver : fn; return (root, args, ctx, info) => { const result = resolve(root, args, ctx, info); if (typeof result === 'undefined') { @@ -113,7 +115,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'); } diff --git a/src/mock.ts b/src/mock.ts index e0b02e27e32..acda40d476f 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -209,21 +209,29 @@ function addMocksToSchema({ schema, (field: GraphQLField, typeName: string, fieldName: string) => { assignResolveType(field.type, preserveResolvers); - let mockResolver: GraphQLFieldResolver = mockType(field.type, typeName, fieldName); + 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. const queryType = schema.getQueryType(); - const isOnQueryType = (queryType != null) && (queryType.name === typeName); + const isOnQueryType = queryType != null && queryType.name === typeName; const mutationType = schema.getMutationType(); - const isOnMutationType = (mutationType != null) && (mutationType.name === typeName); + 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 }, @@ -307,20 +315,26 @@ function mergeObjects(a: Record, b: Record) { return Object.assign(a, b); } -function copyOwnPropsIfNotPresent(target: Record, source: Record) { +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, - (propertyDescriptor == null) ? {} : propertyDescriptor, + propertyDescriptor == null ? {} : propertyDescriptor, ); } }); } -function copyOwnProps(target: Record, ...sources: Array>) { +function copyOwnProps( + target: Record, + ...sources: Array> +) { sources.forEach(source => { let chain = source; while (chain != null) { @@ -359,7 +373,7 @@ function assignResolveType(type: GraphQLType, preserveResolvers: boolean) { const namedFieldType = getNamedType(fieldType); const oldResolveType = getResolveType(namedFieldType); - if (preserveResolvers && (oldResolveType != null) && oldResolveType.length) { + if (preserveResolvers && oldResolveType != null && oldResolveType.length) { return; } @@ -443,4 +457,9 @@ class MockList { } // retain addMockFunctionsToSchema for backwards compatibility -export { addMocksToSchema, addMocksToSchema as addMockFunctionsToSchema, MockList, mockServer }; +export { + addMocksToSchema, + addMocksToSchema as addMockFunctionsToSchema, + MockList, + mockServer, +}; diff --git a/src/scalars/GraphQLUpload.ts b/src/scalars/GraphQLUpload.ts index e745e3de421..5bc3d3152ed 100644 --- a/src/scalars/GraphQLUpload.ts +++ b/src/scalars/GraphQLUpload.ts @@ -10,6 +10,4 @@ const GraphQLUpload = new GraphQLScalarType({ serialize: value => value, }); -export { - GraphQLUpload, -}; +export { GraphQLUpload }; diff --git a/src/scalars/index.ts b/src/scalars/index.ts index c65b605ccae..593c28f6641 100644 --- a/src/scalars/index.ts +++ b/src/scalars/index.ts @@ -1,5 +1,3 @@ import { GraphQLUpload } from './GraphQLUpload'; -export { - GraphQLUpload, -}; +export { GraphQLUpload }; diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 3100851e7ae..b75ea558961 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -35,7 +35,6 @@ import resolveFromParentTypename from './resolveFromParentTypename'; import { setErrors, setObjectSubschema } from './proxiedResult'; import { mergeFields } from './mergeFields'; - export function checkResultAndHandleErrors( result: ExecutionResult, context: Record, @@ -48,7 +47,15 @@ export function checkResultAndHandleErrors( 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); + return handleResult( + data, + errors, + subschema, + context, + info, + returnType, + skipTypeMerging, + ); } export function handleResult( @@ -69,9 +76,25 @@ export function handleResult( if (isLeafType(type)) { return type.parseValue(result); } else if (isCompositeType(type)) { - return handleObject(type, result, errors, subschema, context, info, skipTypeMerging); + return handleObject( + type, + result, + errors, + subschema, + context, + info, + skipTypeMerging, + ); } else if (isListType(type)) { - return handleList(type, result, errors, subschema, context, info, skipTypeMerging); + return handleList( + type, + result, + errors, + subschema, + context, + info, + skipTypeMerging, + ); } } @@ -86,16 +109,18 @@ function handleList( ) { const childErrors = getErrorsByPathSegment(errors); - return list.map((listMember, index) => handleListMember( - getNullableType(type.ofType), - listMember, - index, - childErrors[index] != null ? childErrors[index] : [], - subschema, - context, - info, - skipTypeMerging, - )); + return list.map((listMember, index) => + handleListMember( + getNullableType(type.ofType), + listMember, + index, + childErrors[index] != null ? childErrors[index] : [], + subschema, + context, + info, + skipTypeMerging, + ), + ); } function handleListMember( @@ -109,15 +134,35 @@ function handleListMember( skipTypeMerging?: boolean, ): any { if (listMember == null) { - return handleNull(info.fieldNodes, [...responsePathAsArray(info.path), index], errors); + 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); + return handleObject( + type, + listMember, + errors, + subschema, + context, + info, + skipTypeMerging, + ); } else if (isListType(type)) { - return handleList(type, listMember, errors, subschema, context, info, skipTypeMerging); + return handleList( + type, + listMember, + errors, + subschema, + context, + info, + skipTypeMerging, + ); } } @@ -130,11 +175,16 @@ export function handleObject( info: IGraphQLToolsResolveInfo, skipTypeMerging?: boolean, ) { - setErrors(object, errors.map(error => relocatedError( - error, - error.nodes, - error.path != null ? error.path.slice(1) : undefined, - ))); + setErrors( + object, + errors.map(error => + relocatedError( + error, + error.nodes, + error.path != null ? error.path.slice(1) : undefined, + ), + ), + ); setObjectSubschema(object, subschema); @@ -142,16 +192,15 @@ export function handleObject( return object; } - const typeName = - isAbstractType(type) ? - info.schema.getTypeMap()[resolveFromParentTypename(object)].name : - type.name; + 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; @@ -188,7 +237,11 @@ function collectSubFields(info: IGraphQLToolsResolveInfo, typeName: string) { const visitedFragmentNames = Object.create(null); info.fieldNodes.forEach(fieldNode => { subFieldNodes = collectFields( - { schema: info.schema, variableValues: info.variableValues, fragments: info.fragments } as unknown as ExecutionContext, + ({ + schema: info.schema, + variableValues: info.variableValues, + fragments: info.fragments, + } as unknown) as ExecutionContext, info.schema.getType(typeName) as GraphQLObjectType, fieldNode.selectionSet, subFieldNodes, @@ -204,8 +257,9 @@ function getFieldsNotInSubschema( mergedTypeInfo: MergedTypeInfo, typeName: string, ): Array { - const typeMap = isSubschemaConfig(subschema) ? - mergedTypeInfo.typeMaps.get(subschema) : subschema.getTypeMap(); + const typeMap = isSubschemaConfig(subschema) + ? mergedTypeInfo.typeMaps.get(subschema) + : subschema.getTypeMap(); const fields = (typeMap[typeName] as GraphQLObjectType).getFields(); const fieldsNotInSchema: Array = []; @@ -227,29 +281,33 @@ export function handleNull( ) { if (errors.length) { if (errors.some(error => !error.path || error.path.length < 2)) { - return relocatedError( - combineErrors(errors), - fieldNodes, - path, - ); - + 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]); + 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])); + result.push( + handleNull( + fieldNodes, + [...path, parseInt(pathSegment, 10)], + childErrors[pathSegment], + ), + ); }); return result; diff --git a/src/stitching/createMergedResolver.ts b/src/stitching/createMergedResolver.ts index 33939de2494..940a5d130b3 100644 --- a/src/stitching/createMergedResolver.ts +++ b/src/stitching/createMergedResolver.ts @@ -12,25 +12,43 @@ export function createMergedResolver({ dehoist?: boolean; delimeter?: string; }): IFieldResolver { - const parentErrorResolver: IFieldResolver = - (parent, args, context, info) => parent instanceof Error ? - parent : - defaultMergedResolver(parent, args, context, info); + 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 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 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) - : {}; + const noParentResolver: IFieldResolver = ( + parent, + args, + context, + info, + ) => (parent ? dehoistingResolver(parent, args, context, info) : {}); return noParentResolver; } diff --git a/src/stitching/createRequest.ts b/src/stitching/createRequest.ts index 22178b97f38..3b1f4fbe68c 100644 --- a/src/stitching/createRequest.ts +++ b/src/stitching/createRequest.ts @@ -32,7 +32,7 @@ import { serializeInputValue } from '../utils'; export function getDelegatingOperation( parentType: GraphQLObjectType, - schema: GraphQLSchema + schema: GraphQLSchema, ): OperationTypeNode { if (parentType === schema.getMutationType()) { return 'mutation'; @@ -62,7 +62,11 @@ export function createRequestFromInfo({ fieldName, args, selectionSet, - selectionSet != null ? undefined : (fieldNodes != null ? fieldNodes: info.fieldNodes), + selectionSet != null + ? undefined + : fieldNodes != null + ? fieldNodes + : info.fieldNodes, ); } @@ -85,16 +89,19 @@ export function createRequest( if (!selectionSet && fieldNodes != null) { const selections: Array = fieldNodes.reduce( - (acc, fieldNode) => fieldNode.selectionSet != null ? - acc.concat(fieldNode.selectionSet.selections) : - acc, + (acc, fieldNode) => + fieldNode.selectionSet != null + ? acc.concat(fieldNode.selectionSet.selections) + : acc, [], ); - newSelectionSet = selections.length ? { - kind: Kind.SELECTION_SET, - selections, - } : undefined; + newSelectionSet = selections.length + ? { + kind: Kind.SELECTION_SET, + selections, + } + : undefined; argumentNodes = fieldNodes[0].arguments; } else { @@ -104,7 +111,10 @@ export function createRequest( let variables = {}; for (const variableDefinition of variableDefinitions) { const varName = variableDefinition.variable.name.value; - const varType = typeFromAST(sourceSchema, (variableDefinition.type as NamedTypeNode)) as GraphQLInputType; + const varType = typeFromAST( + sourceSchema, + variableDefinition.type as NamedTypeNode, + ) as GraphQLInputType; variables[varName] = serializeInputValue(varType, variableValues[varName]); } @@ -112,7 +122,7 @@ export function createRequest( const { arguments: updatedArguments, variableDefinitions: updatedVariableDefinitions, - variableValues: updatedVariableValues + variableValues: updatedVariableValues, } = updateArguments( targetSchemaOrSchemaConfig, targetOperation, @@ -121,7 +131,7 @@ export function createRequest( variableDefinitions, variables, args, - ); + ); argumentNodes = updatedArguments; newVariableDefinitions = updatedVariableDefinitions; variables = updatedVariableValues; @@ -148,9 +158,9 @@ export function createRequest( }, }; - const fragmentDefinitions: Array = Object.keys(fragments).map( - fragmentName => fragments[fragmentName], - ); + const fragmentDefinitions: Array = Object.keys( + fragments, + ).map(fragmentName => fragments[fragmentName]); const document = { kind: Kind.DOCUMENT, @@ -172,12 +182,13 @@ function updateArguments( variableValues: Record = {}, newArgsMap: Record = {}, ): { - arguments: Array, - variableDefinitions: Array, - variableValues: Record + arguments: Array; + variableDefinitions: Array; + variableValues: Record; } { - const schema = isSubschemaConfig(subschemaOrSubschemaConfig) ? - subschemaOrSubschemaConfig.schema : subschemaOrSubschemaConfig; + const schema = isSubschemaConfig(subschemaOrSubschemaConfig) + ? subschemaOrSubschemaConfig.schema + : subschemaOrSubschemaConfig; let type: GraphQLObjectType; if (operation === 'subscription') { @@ -253,7 +264,11 @@ function astFromType(type: GraphQLType): TypeNode { if (type instanceof GraphQLNonNull) { const innerType = astFromType(type.ofType); if (innerType.kind === Kind.NON_NULL_TYPE) { - throw new Error(`Invalid type node ${JSON.stringify(type)}. Inner type of non-null type cannot be a non-null type.`); + throw new Error( + `Invalid type node ${JSON.stringify( + type, + )}. Inner type of non-null type cannot be a non-null type.`, + ); } return { kind: Kind.NON_NULL_TYPE, diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index b86128e5526..b41b43e1935 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -31,10 +31,7 @@ import { Transform, } from '../transforms'; -import { - createRequestFromInfo, - getDelegatingOperation, -} from './createRequest'; +import { createRequestFromInfo, getDelegatingOperation } from './createRequest'; import linkToFetcher from './linkToFetcher'; import { observableToAsyncIterable } from './observableToAsyncIterable'; import mapAsyncIterator from './mapAsyncIterator'; @@ -91,25 +88,36 @@ function buildDelegationTransforms( skipTypeMerging: boolean, ): Array { let delegationTransforms: Array = [ - new CheckResultAndHandleErrors(info, fieldName, subschemaOrSubschemaConfig, context, returnType, skipTypeMerging), + new CheckResultAndHandleErrors( + info, + fieldName, + subschemaOrSubschemaConfig, + context, + returnType, + skipTypeMerging, + ), ]; if (info.mergeInfo != null) { delegationTransforms.push( - new AddReplacementSelectionSets(info.schema, info.mergeInfo.replacementSelectionSets), + 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), - ); + delegationTransforms.push(new ExpandAbstractTypes(info.schema, targetSchema)); if (info.mergeInfo != null) { delegationTransforms.push( - new AddReplacementFragments(targetSchema, info.mergeInfo.replacementFragments), + new AddReplacementFragments( + targetSchema, + info.mergeInfo.replacementFragments, + ), ); } @@ -142,11 +150,14 @@ export function delegateRequest({ if (isSubschemaConfig(subschemaOrSubschemaConfig)) { subschemaConfig = subschemaOrSubschemaConfig; targetSchema = subschemaConfig.schema; - targetRootValue = rootValue != null ? - rootValue : - (subschemaConfig.rootValue != null ? subschemaConfig.rootValue : info.rootValue); + targetRootValue = + rootValue != null + ? rootValue + : subschemaConfig.rootValue != null + ? subschemaConfig.rootValue + : info.rootValue; if (subschemaConfig.transforms != null) { - requestTransforms = requestTransforms.concat(subschemaConfig.transforms) + requestTransforms = requestTransforms.concat(subschemaConfig.transforms); } } else { targetSchema = subschemaOrSubschemaConfig; @@ -164,7 +175,10 @@ export function delegateRequest({ skipTypeMerging, ); - const processedRequest = applyRequestTransforms(request, delegationTransforms); + const processedRequest = applyRequestTransforms( + request, + delegationTransforms, + ); if (!skipValidation) { const errors = validate(targetSchema, processedRequest.document); @@ -175,48 +189,69 @@ export function delegateRequest({ } if (operation === 'query' || operation === 'mutation') { + const executor = createExecutor( + targetSchema, + targetRootValue, + context, + subschemaConfig, + ); - const executor = createExecutor(targetSchema, targetRootValue, context, subschemaConfig); - - const executionResult: ExecutionResult | Promise = executor({ + const executionResult: + | ExecutionResult + | Promise = executor({ document: processedRequest.document, context, - variables: processedRequest.variables + variables: processedRequest.variables, }); if (executionResult instanceof Promise) { return executionResult.then((originalResult: any) => - applyResultTransforms(originalResult, delegationTransforms)); + applyResultTransforms(originalResult, delegationTransforms), + ); } return applyResultTransforms(executionResult, delegationTransforms); - } - const subscriber = createSubscriber(targetSchema, targetRootValue, context, subschemaConfig); + 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); - }); - + }).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, @@ -228,9 +263,10 @@ function createExecutor( if (subschemaConfig != null) { if (subschemaConfig.dispatcher != null) { const dynamicLinkOrFetcher = subschemaConfig.dispatcher(context); - fetcher = (typeof dynamicLinkOrFetcher === 'function') ? - dynamicLinkOrFetcher : - linkToFetcher(dynamicLinkOrFetcher); + fetcher = + typeof dynamicLinkOrFetcher === 'function' + ? dynamicLinkOrFetcher + : linkToFetcher(dynamicLinkOrFetcher); } else if (subschemaConfig.link != null) { fetcher = linkToFetcher(subschemaConfig.link); } else if (subschemaConfig.fetcher != null) { @@ -243,27 +279,29 @@ function createExecutor( } if (fetcher != null) { - return ({ document, context: graphqlContext, variables }) => fetcher({ - query: document, - variables, - context: { graphqlContext } - }); + 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, - }); + return ({ document, context: graphqlContext, variables }) => + execute({ + schema, + document, + rootValue: targetRootValue, + contextValue: graphqlContext, + variableValues: variables, + }); } function createSubscriber( schema: GraphQLSchema, rootValue: Record, context: Record, - subschemaConfig?: SubschemaConfig + subschemaConfig?: SubschemaConfig, ): Delegator { let link: ApolloLink; let targetRootValue: Record = rootValue; @@ -285,18 +323,19 @@ function createSubscriber( const operation = { query: document, variables, - context: { graphqlContext } + context: { graphqlContext }, }; const observable = executeLink(link, operation); return observableToAsyncIterable(observable); }; } - return ({ document, context: graphqlContext, variables }) => subscribe({ - schema, - document, - rootValue: targetRootValue, - contextValue: graphqlContext, - variableValues: variables, - }); + return ({ document, context: graphqlContext, variables }) => + subscribe({ + schema, + document, + rootValue: targetRootValue, + contextValue: graphqlContext, + variableValues: variables, + }); } diff --git a/src/stitching/errors.ts b/src/stitching/errors.ts index 403ce9d5662..89adc5756a0 100644 --- a/src/stitching/errors.ts +++ b/src/stitching/errors.ts @@ -1,12 +1,9 @@ -import { - GraphQLError, - ASTNode, -} from 'graphql'; +import { GraphQLError, ASTNode } from 'graphql'; export function relocatedError( originalError: Error | GraphQLError, nodes: ReadonlyArray, - path: ReadonlyArray + path: ReadonlyArray, ): GraphQLError { if (Array.isArray((originalError as GraphQLError).path)) { return new GraphQLError( @@ -16,7 +13,7 @@ export function relocatedError( (originalError as GraphQLError).positions, path != null ? path : (originalError as GraphQLError).path, (originalError as GraphQLError).originalError, - (originalError as GraphQLError).extensions + (originalError as GraphQLError).extensions, ); } @@ -33,7 +30,9 @@ export function relocatedError( return new GraphQLError( originalError.message, - ((originalError as GraphQLError).nodes != null) ? (originalError as GraphQLError).nodes : nodes, + (originalError as GraphQLError).nodes != null + ? (originalError as GraphQLError).nodes + : nodes, (originalError as GraphQLError).source, (originalError as GraphQLError).positions, path, @@ -45,12 +44,13 @@ export function slicedError(originalError: GraphQLError) { return relocatedError( originalError, originalError.nodes, - (originalError.path != null) ? originalError.path.slice(1) : undefined + originalError.path != null ? originalError.path.slice(1) : undefined, ); } - -export function getErrorsByPathSegment(errors: ReadonlyArray): Record> { +export function getErrorsByPathSegment( + errors: ReadonlyArray, +): Record> { const record = Object.create(null); errors.forEach(error => { if (!error.path || error.path.length < 2) { @@ -59,7 +59,7 @@ export function getErrorsByPathSegment(errors: ReadonlyArray): Rec const pathSegment = error.path[1]; - const current = (record[pathSegment] != null) ? record[pathSegment] : []; + const current = record[pathSegment] != null ? record[pathSegment] : []; current.push(slicedError(error)); record[pathSegment] = current; }); @@ -75,7 +75,9 @@ class CombinedError extends Error { } } -export function combineErrors(errors: ReadonlyArray): GraphQLError | CombinedError { +export function combineErrors( + errors: ReadonlyArray, +): GraphQLError | CombinedError { if (errors.length === 1) { return new GraphQLError( errors[0].message, @@ -84,9 +86,12 @@ export function combineErrors(errors: ReadonlyArray): GraphQLError errors[0].positions, errors[0].path, errors[0].originalError, - errors[0].extensions + errors[0].extensions, ); } - return new CombinedError(errors.map(error => error.message).join('\n'), errors); + return new CombinedError( + errors.map(error => error.message).join('\n'), + errors, + ); } diff --git a/src/stitching/getResponseKeyFromInfo.ts b/src/stitching/getResponseKeyFromInfo.ts index 4f1277a0a7f..822cc2f7501 100644 --- a/src/stitching/getResponseKeyFromInfo.ts +++ b/src/stitching/getResponseKeyFromInfo.ts @@ -6,5 +6,7 @@ import { GraphQLResolveInfo } from 'graphql'; * @param info The info argument to the resolver. */ export function getResponseKeyFromInfo(info: GraphQLResolveInfo) { - return info.fieldNodes[0].alias != null ? info.fieldNodes[0].alias.value : info.fieldName; + return info.fieldNodes[0].alias != null + ? info.fieldNodes[0].alias.value + : info.fieldName; } diff --git a/src/stitching/index.ts b/src/stitching/index.ts index 8d084ca7fdd..c0c59d69a0e 100644 --- a/src/stitching/index.ts +++ b/src/stitching/index.ts @@ -1,4 +1,6 @@ -import makeRemoteExecutableSchema, { createResolver as defaultCreateRemoteResolver } from './makeRemoteExecutableSchema'; +import makeRemoteExecutableSchema, { + createResolver as defaultCreateRemoteResolver, +} from './makeRemoteExecutableSchema'; import introspectSchema from './introspectSchema'; import mergeSchemas from './mergeSchemas'; import delegateToSchema, { delegateRequest } from './delegateToSchema'; @@ -11,7 +13,6 @@ export { makeRemoteExecutableSchema, introspectSchema, mergeSchemas, - // These are currently undocumented and not part of official API, // but exposed for the community use delegateToSchema, diff --git a/src/stitching/introspectSchema.ts b/src/stitching/introspectSchema.ts index e35c5955ab1..06fd7c5d181 100644 --- a/src/stitching/introspectSchema.ts +++ b/src/stitching/introspectSchema.ts @@ -18,28 +18,35 @@ export default function introspectSchema( linkOrFetcher: ApolloLink | Fetcher, linkContext?: { [key: string]: any }, ): Promise { - const fetcher = linkOrFetcher instanceof ApolloLink ? - linkToFetcher(linkOrFetcher) : - linkOrFetcher; + const fetcher = + linkOrFetcher instanceof ApolloLink + ? linkToFetcher(linkOrFetcher) + : linkOrFetcher; return fetcher({ query: parsedIntrospectionQuery, context: linkContext, }).then(introspectionResult => { if ( - (Array.isArray(introspectionResult.errors) && introspectionResult.errors.length) || + (Array.isArray(introspectionResult.errors) && + introspectionResult.errors.length) || !introspectionResult.data.__schema ) { if (Array.isArray(introspectionResult.errors)) { const combinedError: Error = combineErrors(introspectionResult.errors); throw combinedError; } else { - throw new Error('Could not obtain introspection result, received: ' + JSON.stringify(introspectionResult)); + throw new Error( + 'Could not obtain introspection result, received: ' + + JSON.stringify(introspectionResult), + ); } } else { - const schema = buildClientSchema(introspectionResult.data as { - __schema: any; - }); + const schema = buildClientSchema( + introspectionResult.data as { + __schema: any; + }, + ); return schema; } }); diff --git a/src/stitching/linkToFetcher.ts b/src/stitching/linkToFetcher.ts index 9c4082dac42..e64e9ff19cf 100644 --- a/src/stitching/linkToFetcher.ts +++ b/src/stitching/linkToFetcher.ts @@ -1,9 +1,4 @@ -import { - ApolloLink, - toPromise, - execute, - ExecutionResult, -} from 'apollo-link'; +import { ApolloLink, toPromise, execute, ExecutionResult } from 'apollo-link'; import { Fetcher, IFetcherOperation } from '../Interfaces'; diff --git a/src/stitching/makeMergedType.ts b/src/stitching/makeMergedType.ts index 003a9c14c58..fd342680579 100644 --- a/src/stitching/makeMergedType.ts +++ b/src/stitching/makeMergedType.ts @@ -17,7 +17,10 @@ export function makeMergedType(type: GraphQLType): void { fieldMap[fieldName].resolve = defaultMergedResolver; fieldMap[fieldName].subscribe = null; }); - } else if (type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) { + } else if ( + type instanceof GraphQLInterfaceType || + type instanceof GraphQLUnionType + ) { type.resolveType = parent => resolveFromParentTypename(parent); } } diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index 10ecbe7ad20..e5e6c4533bb 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -25,7 +25,7 @@ export type ResolverFn = ( rootValue?: any, args?: any, context?: any, - info?: GraphQLResolveInfo + info?: GraphQLResolveInfo, ) => AsyncIterator; export default function makeRemoteExecutableSchema({ @@ -48,9 +48,10 @@ export default function makeRemoteExecutableSchema({ finalFetcher = linkToFetcher(link); } - const targetSchema = typeof schemaOrTypeDefs === 'string' ? - buildSchema(schemaOrTypeDefs, buildSchemaOptions) : - schemaOrTypeDefs; + const targetSchema = + typeof schemaOrTypeDefs === 'string' + ? buildSchema(schemaOrTypeDefs, buildSchemaOptions) + : schemaOrTypeDefs; const remoteSchema = cloneSchema(targetSchema); stripResolvers(remoteSchema); @@ -80,12 +81,16 @@ export default function makeRemoteExecutableSchema({ return remoteSchema; } -export function createResolver(fetcher: Fetcher): GraphQLFieldResolver { +export function createResolver( + fetcher: Fetcher, +): GraphQLFieldResolver { return async (_root, _args, context, info) => { - const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]); + const fragments = Object.keys(info.fragments).map( + fragment => info.fragments[fragment], + ); let query: DocumentNode = { kind: Kind.DOCUMENT, - definitions: [info.operation, ...fragments] + definitions: [info.operation, ...fragments], }; query = addTypenameToAbstract(info.schema, query); @@ -93,7 +98,7 @@ export function createResolver(fetcher: Fetcher): GraphQLFieldResolver const result = await fetcher({ query, variables: info.variableValues, - context: { graphqlContext: context } + context: { graphqlContext: context }, }); return checkResultAndHandleErrors(result, context, info); }; @@ -101,10 +106,12 @@ export function createResolver(fetcher: Fetcher): GraphQLFieldResolver function createSubscriptionResolver(link: ApolloLink): ResolverFn { return (_root, _args, context, info) => { - const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]); + const fragments = Object.keys(info.fragments).map( + fragment => info.fragments[fragment], + ); let query: DocumentNode = { kind: Kind.DOCUMENT, - definitions: [info.operation, ...fragments] + definitions: [info.operation, ...fragments], }; query = addTypenameToAbstract(info.schema, query); @@ -112,7 +119,7 @@ function createSubscriptionResolver(link: ApolloLink): ResolverFn { const operation = { query, variables: info.variableValues, - context: { graphqlContext: context } + context: { graphqlContext: context }, }; const observable = execute(link, operation); diff --git a/src/stitching/mapAsyncIterator.ts b/src/stitching/mapAsyncIterator.ts index 1aeaa1cb41b..5af28dc8d7b 100644 --- a/src/stitching/mapAsyncIterator.ts +++ b/src/stitching/mapAsyncIterator.ts @@ -34,7 +34,7 @@ export default function mapAsyncIterator( asyncMapValue(error, reject).then(iteratorResult, abruptClose); } - return ({ + return { next() { return iterator.next().then(mapResult, mapReject); }, @@ -52,7 +52,7 @@ export default function mapAsyncIterator( [$$asyncIterator]() { return this; }, - } as any); + } as any; } function asyncMapValue( diff --git a/src/stitching/mergeFields.ts b/src/stitching/mergeFields.ts index 374f23e6251..f0e499256cc 100644 --- a/src/stitching/mergeFields.ts +++ b/src/stitching/mergeFields.ts @@ -1,8 +1,4 @@ -import { - FieldNode, - SelectionNode, - Kind, -} from 'graphql'; +import { FieldNode, SelectionNode, Kind } from 'graphql'; import { SubschemaConfig, @@ -18,10 +14,10 @@ function buildDelegationPlan( sourceSubschemas: Array, targetSubschemas: Array, ): { - delegationMap: Map>, - unproxiableSelections: Array, - proxiableSubschemas: Array, - nonProxiableSubschemas: Array, + delegationMap: Map>; + unproxiableSelections: Array; + proxiableSubschemas: Array; + nonProxiableSubschemas: Array; } { // 1. calculate if possible to delegate to given subschema // TODO: change logic so that required selection set can be spread across multiple subschemas? @@ -30,11 +26,12 @@ function buildDelegationPlan( const nonProxiableSubschemas: Array = []; targetSubschemas.forEach(t => { - if (sourceSubschemas.some(s => { - const selectionSet = mergedTypeInfo.selectionSets.get(t); - return mergedTypeInfo.containsSelectionSet.get(s).get(selectionSet); - } - )) { + if ( + sourceSubschemas.some(s => { + const selectionSet = mergedTypeInfo.selectionSets.get(t); + return mergedTypeInfo.containsSelectionSet.get(s).get(selectionSet); + }) + ) { proxiableSubschemas.push(t); } else { nonProxiableSubschemas.push(t); @@ -48,7 +45,6 @@ function buildDelegationPlan( const delegationMap: Map> = new Map(); originalSelections.forEach(selection => { - // 2a. use uniqueFields map to assign fields to subschema if one of possible subschemas const uniqueSubschema: SubschemaConfig = uniqueFields[selection.name.value]; @@ -63,17 +59,22 @@ function buildDelegationPlan( } else { unproxiableSelections.push(selection); } - } else { - // 2b. use nonUniqueFields to assign to a possible subschema, // preferring one of the subschemas already targets of delegation - let nonUniqueSubschemas: Array = nonUniqueFields[selection.name.value]; - nonUniqueSubschemas = nonUniqueSubschemas.filter(s => proxiableSubschemas.includes(s)); + let nonUniqueSubschemas: Array = + nonUniqueFields[selection.name.value]; + nonUniqueSubschemas = nonUniqueSubschemas.filter(s => + proxiableSubschemas.includes(s), + ); if (nonUniqueSubschemas != null) { - const subschemas: Array = Array.from(delegationMap.keys()); - const existingSubschema = nonUniqueSubschemas.find(s => subschemas.includes(s)); + const subschemas: Array = Array.from( + delegationMap.keys(), + ); + const existingSubschema = nonUniqueSubschemas.find(s => + subschemas.includes(s), + ); if (existingSubschema != null) { delegationMap.get(existingSubschema).push(selection); } else { @@ -103,7 +104,6 @@ export function mergeFields( context: Record, info: IGraphQLToolsResolveInfo, ): any { - if (!originalSelections.length) { return object; } @@ -113,26 +113,27 @@ export function mergeFields( unproxiableSelections, proxiableSubschemas, nonProxiableSubschemas, - } = buildDelegationPlan(mergedTypeInfo, originalSelections, sourceSubschemas, targetSubschemas); + } = buildDelegationPlan( + mergedTypeInfo, + originalSelections, + sourceSubschemas, + targetSubschemas, + ); if (!delegationMap.size) { return object; } const maybePromises: Promise | any = []; - delegationMap.forEach((selections: Array, s: SubschemaConfig) => { - const maybePromise = s.merge[typeName].resolve( - object, - context, - info, - s, - { + delegationMap.forEach( + (selections: Array, s: SubschemaConfig) => { + const maybePromise = s.merge[typeName].resolve(object, context, info, s, { kind: Kind.SELECTION_SET, selections, - }, - ); - maybePromises.push(maybePromise); - }); + }); + maybePromises.push(maybePromise); + }, + ); let containsPromises = false; for (const maybePromise of maybePromises) { @@ -142,26 +143,27 @@ export function mergeFields( } } - return containsPromises ? - Promise.all(maybePromises). - then(results => mergeFields( + return containsPromises + ? Promise.all(maybePromises).then(results => + mergeFields( + mergedTypeInfo, + typeName, + mergeProxiedResults(object, ...results), + unproxiableSelections, + sourceSubschemas.concat(proxiableSubschemas), + nonProxiableSubschemas, + context, + info, + ), + ) + : mergeFields( mergedTypeInfo, typeName, - mergeProxiedResults(object, ...results), + mergeProxiedResults(object, ...maybePromises), unproxiableSelections, sourceSubschemas.concat(proxiableSubschemas), nonProxiableSubschemas, context, info, - )) : - mergeFields( - mergedTypeInfo, - typeName, - mergeProxiedResults(object, ...maybePromises), - unproxiableSelections, - sourceSubschemas.concat(proxiableSubschemas), - nonProxiableSubschemas, - context, - info, - ); + ); } diff --git a/src/stitching/mergeInfo.ts b/src/stitching/mergeInfo.ts index e071b3add86..f40ff0847dd 100644 --- a/src/stitching/mergeInfo.ts +++ b/src/stitching/mergeInfo.ts @@ -42,8 +42,13 @@ type MergeTypeCandidate = { export function createMergeInfo( allSchemas: Array, typeCandidates: { [name: string]: Array }, - mergeTypes?: boolean | Array | - ((typeName: string, mergeTypeCandidates: Array) => boolean), + mergeTypes?: + | boolean + | Array + | (( + typeName: string, + mergeTypeCandidates: Array, + ) => boolean), ): MergeInfo { return { delegate( @@ -56,7 +61,10 @@ export function createMergeInfo( ) { const schema = guessSchemaByRootField(allSchemas, operation, fieldName); const expandTransforms = new ExpandAbstractTypes(info.schema, schema); - const fragmentTransform = new AddReplacementFragments(schema, info.mergeInfo.replacementFragments); + const fragmentTransform = new AddReplacementFragments( + schema, + info.mergeInfo.replacementFragments, + ); return delegateToSchema({ schema, operation, @@ -64,18 +72,14 @@ export function createMergeInfo( args, context, info, - transforms: [ - ...transforms, - expandTransforms, - fragmentTransform, - ], + transforms: [...transforms, expandTransforms, fragmentTransform], }); }, delegateToSchema(options: IDelegateToSchemaOptions) { return delegateToSchema({ ...options, - transforms: options.transforms + transforms: options.transforms, }); }, fragments: [], @@ -87,30 +91,38 @@ export function createMergeInfo( function createMergedTypes( typeCandidates: { [name: string]: Array }, - mergeTypes?: boolean | Array | - ((typeName: string, mergeTypeCandidates: Array) => boolean) + mergeTypes?: + | boolean + | Array + | (( + typeName: string, + mergeTypeCandidates: Array, + ) => boolean), ): Record { const mergedTypes: Record = {}; Object.keys(typeCandidates).forEach(typeName => { if (typeCandidates[typeName][0].type instanceof GraphQLObjectType) { - const mergedTypeCandidates = typeCandidates[typeName] - .filter(typeCandidate => + const mergedTypeCandidates = typeCandidates[typeName].filter( + typeCandidate => typeCandidate.subschema != null && isSubschemaConfig(typeCandidate.subschema) && typeCandidate.subschema.merge != null && - typeCandidate.subschema.merge[typeName] != null - ); + typeCandidate.subschema.merge[typeName] != null, + ); if ( mergeTypes === true || - (typeof mergeTypes === 'function') && mergeTypes(typeName, typeCandidates[typeName]) || + (typeof mergeTypes === 'function' && + mergeTypes(typeName, typeCandidates[typeName])) || (Array.isArray(mergeTypes) && mergeTypes.includes(typeName)) || mergedTypeCandidates.length ) { const subschemas: Array = []; - let requiredSelections: Array = [parseSelectionSet('{ __typename }').selections[0]]; + let requiredSelections: Array = [ + parseSelectionSet('{ __typename }').selections[0], + ]; const fields = Object.create({}); const typeMaps: Map = new Map(); const selectionSets: Map = new Map(); @@ -119,7 +131,9 @@ function createMergedTypes( const subschemaConfig = typeCandidate.subschema as SubschemaConfig; const transformedSubschema = typeCandidate.transformedSubschema; typeMaps.set(subschemaConfig, transformedSubschema.getTypeMap()); - const type = transformedSubschema.getType(typeName) as GraphQLObjectType; + const type = transformedSubschema.getType( + typeName, + ) as GraphQLObjectType; const fieldMap = type.getFields(); Object.keys(fieldMap).forEach(fieldName => { if (fields[fieldName] == null) { @@ -131,22 +145,33 @@ function createMergedTypes( const mergedTypeConfig = subschemaConfig.merge[typeName]; if (mergedTypeConfig.selectionSet) { - const selectionSet = parseSelectionSet(mergedTypeConfig.selectionSet); - requiredSelections = requiredSelections.concat(selectionSet.selections); + const selectionSet = parseSelectionSet( + mergedTypeConfig.selectionSet, + ); + requiredSelections = requiredSelections.concat( + selectionSet.selections, + ); selectionSets.set(subschemaConfig, selectionSet); } if (!mergedTypeConfig.resolve) { - mergedTypeConfig.resolve = (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ - schema: subschema, - operation: 'query', - fieldName: mergedTypeConfig.fieldName, - args: mergedTypeConfig.args(originalResult), - selectionSet, + mergedTypeConfig.resolve = ( + originalResult, context, info, - skipTypeMerging: true, - }); + subschema, + selectionSet, + ) => + delegateToSchema({ + schema: subschema, + operation: 'query', + fieldName: mergedTypeConfig.fieldName, + args: mergedTypeConfig.args(originalResult), + selectionSet, + context, + info, + skipTypeMerging: true, + }); } subschemas.push(subschemaConfig); @@ -164,21 +189,32 @@ function createMergedTypes( subschemas.forEach(subschema => { const type = typeMaps.get(subschema)[typeName] as GraphQLObjectType; const subschemaMap = new Map(); - subschemas.filter(s => s !== subschema).forEach(s => { - const selectionSet = selectionSets.get(s); - if (selectionSet != null && typeContainsSelectionSet(type, selectionSet)) { - subschemaMap.set(selectionSet, true); - } - }); - mergedTypes[typeName].containsSelectionSet.set(subschema, subschemaMap); + subschemas + .filter(s => s !== subschema) + .forEach(s => { + const selectionSet = selectionSets.get(s); + if ( + selectionSet != null && + typeContainsSelectionSet(type, selectionSet) + ) { + subschemaMap.set(selectionSet, true); + } + }); + mergedTypes[typeName].containsSelectionSet.set( + subschema, + subschemaMap, + ); }); Object.keys(fields).forEach(fieldName => { const supportedBySubschemas = fields[fieldName]; if (supportedBySubschemas.length === 1) { - mergedTypes[typeName].uniqueFields[fieldName] = supportedBySubschemas[0]; + mergedTypes[typeName].uniqueFields[fieldName] = + supportedBySubschemas[0]; } else { - mergedTypes[typeName].nonUniqueFields[fieldName] = supportedBySubschemas; + mergedTypes[typeName].nonUniqueFields[ + fieldName + ] = supportedBySubschemas; } }); @@ -187,7 +223,6 @@ function createMergedTypes( selections: requiredSelections, }; } - } }); @@ -218,8 +253,11 @@ export function completeMergeInfo( selections: [], }; } - replacementSelectionSets[typeName][fieldName].selections = - replacementSelectionSets[typeName][fieldName].selections.concat(selectionSet.selections); + replacementSelectionSets[typeName][ + fieldName + ].selections = replacementSelectionSets[typeName][ + fieldName + ].selections.concat(selectionSet.selections); } if (field.fragment) { mergeInfo.fragments.push({ diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 830d846c6e5..2048aa81856 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -72,8 +72,13 @@ export default function mergeSchemas({ resolvers?: IResolversParameter; schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor }; inheritResolversFromInterfaces?: boolean; - mergeTypes?: boolean | Array | - ((typeName: string, mergeTypeCandidates: Array) => boolean); + mergeTypes?: + | boolean + | Array + | (( + typeName: string, + mergeTypeCandidates: Array, + ) => boolean); mergeDirectives?: boolean; }): GraphQLSchema { const allSchemas: Array = []; @@ -92,7 +97,10 @@ export default function mergeSchemas({ schemas = [...schemas, ...schemaLikeObjects]; schemas.forEach(schemaLikeObject => { - if (schemaLikeObject instanceof GraphQLSchema || isSubschemaConfig(schemaLikeObject)) { + if ( + schemaLikeObject instanceof GraphQLSchema || + isSubschemaConfig(schemaLikeObject) + ) { const schema = wrapSchema(schemaLikeObject); allSchemas.push(schema); @@ -141,10 +149,13 @@ export default function mergeSchemas({ }); } else if ( typeof schemaLikeObject === 'string' || - (schemaLikeObject != null && (schemaLikeObject as ASTNode).kind === Kind.DOCUMENT) + (schemaLikeObject != null && + (schemaLikeObject as ASTNode).kind === Kind.DOCUMENT) ) { const parsedSchemaDocument = - typeof schemaLikeObject === 'string' ? parse(schemaLikeObject) : (schemaLikeObject as DocumentNode); + typeof schemaLikeObject === 'string' + ? parse(schemaLikeObject) + : (schemaLikeObject as DocumentNode); parsedSchemaDocument.definitions.forEach(def => { const type = typeFromAST(def); if (type instanceof GraphQLDirective && mergeDirectives) { @@ -179,8 +190,11 @@ export default function mergeSchemas({ if (typeof resolvers === 'function') { finalResolvers = resolvers(mergeInfo); } else if (Array.isArray(resolvers)) { - finalResolvers = resolvers.reduce((left, right) => - mergeDeep(left, (typeof right === 'function') ? right(mergeInfo) : right), {}); + finalResolvers = resolvers.reduce( + (left, right) => + mergeDeep(left, typeof right === 'function' ? right(mergeInfo) : right), + {}, + ); if (Array.isArray(resolvers)) { finalResolvers = resolvers.reduce(mergeDeep, {}); } @@ -196,21 +210,22 @@ export default function mergeSchemas({ Object.keys(typeCandidates).forEach(typeName => { if ( - ( - typeName === 'Query' || - typeName === 'Mutation' || - typeName === 'Subscription' || - (mergeTypes === true && !(typeCandidates[typeName][0].type instanceof GraphQLScalarType)) || - (typeof mergeTypes === 'function') && mergeTypes(typeName, typeCandidates[typeName]) || - (Array.isArray(mergeTypes) && mergeTypes.includes(typeName)) || - mergeInfo.mergedTypes[typeName] != null - ) + typeName === 'Query' || + typeName === 'Mutation' || + typeName === 'Subscription' || + (mergeTypes === true && + !(typeCandidates[typeName][0].type instanceof GraphQLScalarType)) || + (typeof mergeTypes === 'function' && + mergeTypes(typeName, typeCandidates[typeName])) || + (Array.isArray(mergeTypes) && mergeTypes.includes(typeName)) || + mergeInfo.mergedTypes[typeName] != null ) { typeMap[typeName] = merge(typeName, typeCandidates[typeName]); } else { - const candidateSelector = onTypeConflict != null ? - onTypeConflictToCandidateSelector(onTypeConflict) : - (cands: Array) => cands[cands.length - 1]; + const candidateSelector = + onTypeConflict != null + ? onTypeConflictToCandidateSelector(onTypeConflict) + : (cands: Array) => cands[cands.length - 1]; typeMap[typeName] = candidateSelector(typeCandidates[typeName]).type; } }); @@ -222,9 +237,9 @@ export default function mergeSchemas({ mutation: typeMap.Mutation as GraphQLObjectType, subscription: typeMap.Subscription as GraphQLObjectType, types: Object.keys(typeMap).map(key => typeMap[key]), - directives: directives.length ? - directives.map((directive) => cloneDirective(directive)) : - undefined + directives: directives.length + ? directives.map(directive => cloneDirective(directive)) + : undefined, }); extensions.forEach(extension => { @@ -236,7 +251,7 @@ export default function mergeSchemas({ addResolversToSchema({ schema: mergedSchema, resolvers: finalResolvers, - inheritResolversFromInterfaces + inheritResolversFromInterfaces, }); forEachField(mergedSchema, field => { @@ -279,7 +294,9 @@ function addTypeCandidate( typeCandidates[name].push(typeCandidate); } -function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): CandidateSelector { +function onTypeConflictToCandidateSelector( + onTypeConflict: OnTypeConflict, +): CandidateSelector { return cands => cands.reduce((prev, next) => { const type = onTypeConflict(prev.type, next.type, { @@ -297,54 +314,77 @@ function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): Cand } return { schemaName: 'unknown', - type + type, }; }); } -function merge(typeName: string, candidates: Array): GraphQLNamedType { +function merge( + typeName: string, + candidates: Array, +): GraphQLNamedType { const initialCandidateType = candidates[0].type; - if (candidates.some(candidate => candidate.type.constructor !== initialCandidateType.constructor)) { - throw new Error(`Cannot merge different type categories into common type ${typeName}.`); + if ( + candidates.some( + candidate => + candidate.type.constructor !== initialCandidateType.constructor, + ) + ) { + throw new Error( + `Cannot merge different type categories into common type ${typeName}.`, + ); } if (initialCandidateType instanceof GraphQLObjectType) { return new GraphQLObjectType({ name: typeName, - fields: candidates.reduce((acc, candidate) => ({ - ...acc, - ...(candidate.type as GraphQLObjectType).toConfig().fields, - }), {}), + fields: candidates.reduce( + (acc, candidate) => ({ + ...acc, + ...(candidate.type as GraphQLObjectType).toConfig().fields, + }), + {}, + ), interfaces: candidates.reduce((acc, candidate) => { - const interfaces = (candidate.type as GraphQLObjectType).toConfig().interfaces; - return (interfaces != null) ? acc.concat(interfaces) : acc; + const interfaces = (candidate.type as GraphQLObjectType).toConfig() + .interfaces; + return interfaces != null ? acc.concat(interfaces) : acc; }, []), }); } else if (initialCandidateType instanceof GraphQLInterfaceType) { return new GraphQLInterfaceType({ name: typeName, - fields: candidates.reduce((acc, candidate) => ({ - ...acc, - ...(candidate.type as GraphQLObjectType).toConfig().fields, - }), {}), + fields: candidates.reduce( + (acc, candidate) => ({ + ...acc, + ...(candidate.type as GraphQLObjectType).toConfig().fields, + }), + {}, + ), }); } else if (initialCandidateType instanceof GraphQLUnionType) { return new GraphQLUnionType({ name: typeName, types: candidates.reduce( - (acc, candidate) => acc.concat((candidate.type as GraphQLUnionType).toConfig().types), + (acc, candidate) => + acc.concat((candidate.type as GraphQLUnionType).toConfig().types), [], ), }); } else if (initialCandidateType instanceof GraphQLEnumType) { return new GraphQLEnumType({ name: typeName, - values: candidates.reduce((acc, candidate) => ({ - ...acc, - ...(candidate.type as GraphQLEnumType).toConfig().values, - }), {}), + values: candidates.reduce( + (acc, candidate) => ({ + ...acc, + ...(candidate.type as GraphQLEnumType).toConfig().values, + }), + {}, + ), }); } else if (initialCandidateType instanceof GraphQLScalarType) { - throw new Error(`Cannot merge type ${typeName}. Merging not supported for GraphQLScalarType.`); + throw new Error( + `Cannot merge type ${typeName}. Merging not supported for GraphQLScalarType.`, + ); } else { // not reachable. throw new Error(`Type ${typeName} has unknown GraphQL type.`); diff --git a/src/stitching/observableToAsyncIterable.ts b/src/stitching/observableToAsyncIterable.ts index f181d1a91ec..23466912389 100644 --- a/src/stitching/observableToAsyncIterable.ts +++ b/src/stitching/observableToAsyncIterable.ts @@ -3,8 +3,10 @@ import { $$asyncIterator } from 'iterall'; type Callback = (value?: any) => any; -export function observableToAsyncIterable(observable: Observable): AsyncIterator & { - [$$asyncIterator]: () => AsyncIterator, +export function observableToAsyncIterable( + observable: Observable, +): AsyncIterator & { + [$$asyncIterator]: () => AsyncIterator; } { const pullQueue: Array = []; const pushQueue: Array = []; @@ -27,18 +29,19 @@ export function observableToAsyncIterable(observable: Observable): AsyncIt } }; - const pullValue = () => new Promise(resolve => { - if (pushQueue.length !== 0) { - const element = pushQueue.shift(); - // either {value: {errors: [...]}} or {value: ...} - resolve({ - ...element, - done: false, - }); - } else { - pullQueue.push(resolve); - } - }); + const pullValue = () => + new Promise(resolve => { + if (pushQueue.length !== 0) { + const element = pushQueue.shift(); + // either {value: {errors: [...]}} or {value: ...} + resolve({ + ...element, + done: false, + }); + } else { + pullQueue.push(resolve); + } + }); const subscription = observable.subscribe({ next(value: any) { diff --git a/src/stitching/proxiedResult.ts b/src/stitching/proxiedResult.ts index 5bdf82bb8e4..8e35991016d 100644 --- a/src/stitching/proxiedResult.ts +++ b/src/stitching/proxiedResult.ts @@ -1,8 +1,4 @@ -import { - GraphQLError, - GraphQLSchema, - responsePathAsArray, -} from 'graphql'; +import { GraphQLError, GraphQLSchema, responsePathAsArray } from 'graphql'; import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; import { mergeDeep } from '../utils'; @@ -15,20 +11,34 @@ const hasSymbol = // eslint-disable-next-line no-undef (typeof window !== 'undefined' && 'Symbol' in window); -export const OBJECT_SUBSCHEMA_SYMBOL = hasSymbol ? Symbol('initialSubschema') : '@@__initialSubschema'; -export const FIELD_SUBSCHEMA_MAP_SYMBOL = hasSymbol ? Symbol('subschemaMap') : '@@__subschemaMap'; -export const ERROR_SYMBOL = hasSymbol ? Symbol('subschemaErrors') : '@@__subschemaErrors'; +export const OBJECT_SUBSCHEMA_SYMBOL = hasSymbol + ? Symbol('initialSubschema') + : '@@__initialSubschema'; +export const FIELD_SUBSCHEMA_MAP_SYMBOL = hasSymbol + ? Symbol('subschemaMap') + : '@@__subschemaMap'; +export const ERROR_SYMBOL = hasSymbol + ? Symbol('subschemaErrors') + : '@@__subschemaErrors'; export function isProxiedResult(result: any) { - return (result != null) ? result[ERROR_SYMBOL] : result; + return result != null ? result[ERROR_SYMBOL] : result; } -export function getSubschema(result: any, responseKey: string): GraphQLSchema | SubschemaConfig { - const subschema = result[FIELD_SUBSCHEMA_MAP_SYMBOL] && result[FIELD_SUBSCHEMA_MAP_SYMBOL][responseKey]; +export function getSubschema( + result: any, + responseKey: string, +): GraphQLSchema | SubschemaConfig { + const subschema = + result[FIELD_SUBSCHEMA_MAP_SYMBOL] && + result[FIELD_SUBSCHEMA_MAP_SYMBOL][responseKey]; return subschema ? subschema : result[OBJECT_SUBSCHEMA_SYMBOL]; } -export function setObjectSubschema(result: any, subschema: GraphQLSchema | SubschemaConfig) { +export function setObjectSubschema( + result: any, + subschema: GraphQLSchema | SubschemaConfig, +) { result[OBJECT_SUBSCHEMA_SYMBOL] = subschema; } @@ -38,9 +48,9 @@ export function setErrors(result: any, errors: Array) { export function getErrors( result: any, - pathSegment: string + pathSegment: string, ): Array { - const errors = (result != null) ? result[ERROR_SYMBOL] : result; + const errors = result != null ? result[ERROR_SYMBOL] : result; if (!Array.isArray(errors)) { return null; @@ -62,7 +72,6 @@ export function unwrapResult( info: IGraphQLToolsResolveInfo, path: Array, ): any { - let newParent: any = parent; const pathLength = path.length; for (let i = 0; i < pathLength; i++) { @@ -72,14 +81,23 @@ export function unwrapResult( const object = newParent[responseKey]; if (object == null) { - return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors); + return handleNull( + info.fieldNodes, + responsePathAsArray(info.path), + errors, + ); } - setErrors(object, errors.map(error => relocatedError( - error, - error.nodes, - error.path != null ? error.path.slice(1) : undefined - ))); + setErrors( + object, + errors.map(error => + relocatedError( + error, + error.nodes, + error.path != null ? error.path.slice(1) : undefined, + ), + ), + ); setObjectSubschema(object, subschema); newParent = object; @@ -88,7 +106,10 @@ export function unwrapResult( return newParent; } -export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): any { +export function dehoistResult( + parent: any, + delimeter: string = '__gqltf__', +): any { const result = Object.create(null); Object.keys(parent).forEach(alias => { @@ -106,8 +127,14 @@ export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): any if (error.path != null) { const path = error.path.slice(); const pathSegment = path.shift(); - const expandedPathSegment: Array = (pathSegment as string).split(delimeter); - return relocatedError(error, error.nodes, expandedPathSegment.concat(path)); + const expandedPathSegment: Array< + string | number + > = (pathSegment as string).split(delimeter); + return relocatedError( + error, + error.nodes, + expandedPathSegment.concat(path), + ); } return error; @@ -119,18 +146,23 @@ export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): any } export function mergeProxiedResults(target: any, ...sources: any): any { - const errors = target[ERROR_SYMBOL].concat(sources.map((source: any) => source[ERROR_SYMBOL])); - const fieldSubschemaMap = sources.reduce((acc: Record, source: any) => { - const subschema = source[OBJECT_SUBSCHEMA_SYMBOL]; - Object.keys(source).forEach(key => { - acc[key] = subschema; - }); - return acc; - }, {}); + const errors = target[ERROR_SYMBOL].concat( + sources.map((source: any) => source[ERROR_SYMBOL]), + ); + const fieldSubschemaMap = sources.reduce( + (acc: Record, source: any) => { + const subschema = source[OBJECT_SUBSCHEMA_SYMBOL]; + Object.keys(source).forEach(key => { + acc[key] = subschema; + }); + return acc; + }, + {}, + ); const result = mergeDeep(target, ...sources); result[ERROR_SYMBOL] = errors; - result[FIELD_SUBSCHEMA_MAP_SYMBOL] = target[FIELD_SUBSCHEMA_MAP_SYMBOL] ? - mergeDeep(target[FIELD_SUBSCHEMA_MAP_SYMBOL], fieldSubschemaMap) : - fieldSubschemaMap; + result[FIELD_SUBSCHEMA_MAP_SYMBOL] = target[FIELD_SUBSCHEMA_MAP_SYMBOL] + ? mergeDeep(target[FIELD_SUBSCHEMA_MAP_SYMBOL], fieldSubschemaMap) + : fieldSubschemaMap; return result; } diff --git a/src/stitching/resolveFromParentTypename.ts b/src/stitching/resolveFromParentTypename.ts index c81434e53fd..bb3abdc52bf 100644 --- a/src/stitching/resolveFromParentTypename.ts +++ b/src/stitching/resolveFromParentTypename.ts @@ -1,6 +1,4 @@ -export default function resolveFromParentTypename( - parent: any, -) { +export default function resolveFromParentTypename(parent: any) { const parentTypename: string = parent['__typename']; if (!parentTypename) { throw new Error( diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 511ad3f3541..fe9270ef84a 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -4,11 +4,7 @@ import { GraphQLObjectType, } from 'graphql'; -import { - IResolvers, - Operation, - SubschemaConfig, -} from '../Interfaces'; +import { IResolvers, Operation, SubschemaConfig } from '../Interfaces'; import { Transform } from '../transforms'; import delegateToSchema from './delegateToSchema'; @@ -114,11 +110,12 @@ function defaultCreateProxyingResolver({ }: { schema: SubschemaConfig; }): GraphQLFieldResolver { - return (_parent, _args, context, info) => delegateToSchema({ - schema, - context, - info, - }); + return (_parent, _args, context, info) => + delegateToSchema({ + schema, + context, + info, + }); } export function stripResolvers(schema: GraphQLSchema): void { diff --git a/src/stitching/typeFromAST.ts b/src/stitching/typeFromAST.ts index d73ef220055..414c5b040d0 100644 --- a/src/stitching/typeFromAST.ts +++ b/src/stitching/typeFromAST.ts @@ -65,15 +65,17 @@ export default function typeFromAST( } } -function makeObjectType( - node: ObjectTypeDefinitionNode, -): GraphQLObjectType { +function makeObjectType(node: ObjectTypeDefinitionNode): GraphQLObjectType { return new GraphQLObjectType({ name: node.name.value, fields: () => makeFields(node.fields), interfaces: () => node.interfaces.map( - iface => createNamedStub(iface.name.value, 'interface') as GraphQLInterfaceType, + iface => + createNamedStub( + iface.name.value, + 'interface', + ) as GraphQLInterfaceType, ), description: getDescription(node, backcompatOptions), }); @@ -90,9 +92,7 @@ function makeInterfaceType( }); } -function makeEnumType( - node: EnumTypeDefinitionNode, -): GraphQLEnumType { +function makeEnumType(node: EnumTypeDefinitionNode): GraphQLEnumType { const values = {}; node.values.forEach(value => { values[value.name.value] = { @@ -106,23 +106,17 @@ function makeEnumType( }); } -function makeUnionType( - node: UnionTypeDefinitionNode, -): GraphQLUnionType { +function makeUnionType(node: UnionTypeDefinitionNode): GraphQLUnionType { return new GraphQLUnionType({ name: node.name.value, types: () => - node.types.map( - type => resolveType(type, 'object') as GraphQLObjectType, - ), + node.types.map(type => resolveType(type, 'object') as GraphQLObjectType), description: getDescription(node, backcompatOptions), resolveType: parent => resolveFromParentTypename(parent), }); } -function makeScalarType( - node: ScalarTypeDefinitionNode, -): GraphQLScalarType { +function makeScalarType(node: ScalarTypeDefinitionNode): GraphQLScalarType { return new GraphQLScalarType({ name: node.name.value, description: getDescription(node, backcompatOptions), @@ -150,7 +144,7 @@ function makeFields( nodes: ReadonlyArray, ): Record> { const result: Record> = {}; - nodes.forEach((node) => { + nodes.forEach(node => { const deprecatedDirective = node.directives.find( directive => directive.name.value === 'deprecated', ); diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 83171e4fd22..b59c3f28ddb 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -47,7 +47,6 @@ import { subscriptionPubSubTrigger, } from './testingSchemas'; - const linkSchema = ` """ A new type linking the Property type. @@ -103,29 +102,34 @@ describe('merge schemas through transforms', () => { const propertySchemaTransforms = [ new FilterRootFields( (operation: string, rootField: string) => - `${operation}.${rootField}` === 'Query.properties' + `${operation}.${rootField}` === 'Query.properties', ), new RenameTypes((name: string) => `Properties_${name}`), - new RenameRootFields((_operation: string, name: string) => `Properties_${name}`), + new RenameRootFields( + (_operation: string, name: string) => `Properties_${name}`, + ), ]; const bookingSchemaTransforms = [ new FilterRootFields( (operation: string, rootField: string) => - `${operation}.${rootField}` === 'Query.bookings' + `${operation}.${rootField}` === 'Query.bookings', ), new RenameTypes((name: string) => `Bookings_${name}`), - new RenameRootFields((_operation: string, name: string) => `Bookings_${name}`), + new RenameRootFields( + (_operation: string, name: string) => `Bookings_${name}`, + ), ]; const subscriptionSchemaTransforms = [ new FilterRootFields( (operation: string, rootField: string) => // must include a Query type otherwise graphql will error `${operation}.${rootField}` === 'Query.notifications' || - `${operation}.${rootField}` === 'Subscription.notifications' + `${operation}.${rootField}` === 'Subscription.notifications', ), new RenameTypes((name: string) => `Subscriptions_${name}`), new RenameRootFields( - (_operation: string, name: string) => `Subscriptions_${name}`), + (_operation: string, name: string) => `Subscriptions_${name}`, + ), ]; const propertySubschema = { @@ -142,11 +146,7 @@ describe('merge schemas through transforms', () => { }; mergedSchema = mergeSchemas({ - subschemas: [ - propertySubschema, - bookingSubschema, - subscriptionSubschema, - ], + subschemas: [propertySubschema, bookingSubschema, subscriptionSubschema], typeDefs: linkSchema, resolvers: { Query: { @@ -190,33 +190,35 @@ describe('merge schemas through transforms', () => { Properties_Property: { bookings: { fragment: 'fragment PropertyFragment on Property { id }', - resolve: (parent, args, context, info) => delegateToSchema({ - schema: bookingSubschema, - operation: 'query', - fieldName: 'bookingsByPropertyId', - args: { - propertyId: parent.id, - limit: args.limit ? args.limit : null, - }, - context, - info, - }), + resolve: (parent, args, context, info) => + delegateToSchema({ + schema: bookingSubschema, + operation: 'query', + fieldName: 'bookingsByPropertyId', + args: { + propertyId: parent.id, + limit: args.limit ? args.limit : null, + }, + context, + info, + }), }, }, // eslint-disable-next-line camelcase Bookings_Booking: { property: { fragment: 'fragment BookingFragment on Booking { propertyId }', - resolve: (parent, _args, context, info) => info.mergeInfo.delegateToSchema({ - schema: propertySubschema, - operation: 'query', - fieldName: 'propertyById', - args: { - id: parent.propertyId, - }, - context, - info, - }), + resolve: (parent, _args, context, info) => + info.mergeInfo.delegateToSchema({ + schema: propertySubschema, + operation: 'query', + fieldName: 'propertyById', + args: { + id: parent.propertyId, + }, + context, + info, + }), }, }, }, @@ -302,7 +304,7 @@ describe('merge schemas through transforms', () => { const transformedNotification = { // eslint-disable-next-line camelcase - Subscriptions_notifications: originalNotification.notifications + Subscriptions_notifications: originalNotification.notifications, }; const subscription = parse(` @@ -326,9 +328,14 @@ describe('merge schemas through transforms', () => { } }, ).catch(done); - }).then(() => - subscriptionPubSub.publish(subscriptionPubSubTrigger, originalNotification) - ).catch(done); + }) + .then(() => + subscriptionPubSub.publish( + subscriptionPubSubTrigger, + originalNotification, + ), + ) + .catch(done); }); }); @@ -343,8 +350,13 @@ describe('transform object fields', () => { return undefined; } const type = propertySchema.getType(typeName) as GraphQLObjectType; - const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; - const fieldConfig = typeConfig.fields[fieldName] as GraphQLFieldConfig; + const typeConfig = type.toConfig() as GraphQLObjectTypeConfig< + any, + any + >; + const fieldConfig = typeConfig.fields[ + fieldName + ] as GraphQLFieldConfig; fieldConfig.resolve = () => 'test'; return fieldConfig; }, @@ -356,12 +368,12 @@ describe('transform object fields', () => { ...fieldNode, name: { ...fieldNode.name, - value: 'id' - } + value: 'id', + }, }; return newFieldNode; - } - ) + }, + ), ]); }); @@ -433,12 +445,12 @@ describe('transform object fields', () => { allItems: () => ({ edges: [ { - node: ITEM - } - ] - }) - } - } + node: ITEM, + }, + ], + }), + }, + }, }); schema = transformSchema(itemSchema, [ @@ -475,7 +487,7 @@ describe('transform object fields', () => { edges { node { camelCase - } + } } } } @@ -490,9 +502,11 @@ describe('transform object fields', () => { data: { item: TRANSFORMED_ITEM, items: { - edges: [{ - node: TRANSFORMED_ITEM, - }], + edges: [ + { + node: TRANSFORMED_ITEM, + }, + ], }, }, }); @@ -507,7 +521,7 @@ describe('transform object fields', () => { edges { node { id - } + } } } } @@ -515,13 +529,17 @@ describe('transform object fields', () => { ); expect(result).to.deep.equal({ - errors: [{ - locations: [{ - column: 17, - line: 6, - }], - message: 'Cannot query field "id" on type "Item".', - }], + errors: [ + { + locations: [ + { + column: 17, + line: 6, + }, + ], + message: 'Cannot query field "id" on type "Item".', + }, + ], }); }); }); @@ -533,14 +551,18 @@ describe('filter and rename object fields', () => { transformedPropertySchema = filterSchema({ schema: transformSchema(propertySchema, [ new RenameTypes((name: string) => `New_${name}`), - new RenameObjectFields((typeName: string, fieldName: string) => (typeName === 'New_Property' ? `new_${fieldName}` : fieldName)) + new RenameObjectFields((typeName: string, fieldName: string) => + typeName === 'New_Property' ? `new_${fieldName}` : fieldName, + ), ]), rootFieldFilter: (operation: string, fieldName: string) => - `${operation}.${fieldName}` === 'Query.propertyById', + `${operation}.${fieldName}` === 'Query.propertyById', fieldFilter: (typeName: string, fieldName: string) => - (typeName === 'New_Property' || fieldName === 'name'), + typeName === 'New_Property' || fieldName === 'name', typeFilter: (typeName: string, type) => - (typeName === 'New_Property' || typeName === 'New_Location' || isSpecifiedScalarType(type)) + typeName === 'New_Property' || + typeName === 'New_Location' || + isSpecifiedScalarType(type), }); }); @@ -559,8 +581,7 @@ type New_Property { type Query { propertyById(id: ID!): New_Property } -` - ); +`); }); it('should work', async () => { @@ -612,10 +633,7 @@ type Query { }, ], message: 'Property.error error', - path: [ - 'propertyById', - 'new_error', - ], + path: ['propertyById', 'new_error'], }, ], }); @@ -708,8 +726,7 @@ union TestUnion = TestImpl1 | UnionImpl type UnionImpl { someField: String } -` - ); +`); }); it('should work', async () => { @@ -755,13 +772,9 @@ type UnionImpl { }, ], message: 'Property.error error', - path: [ - 'namespace', - 'propertyById', - 'error', - ], + path: ['namespace', 'propertyById', 'error'], }, - ] + ], }); }); }); @@ -867,8 +880,7 @@ type Wrap { id: ID name: String } -` - ); +`); }); }); @@ -888,10 +900,10 @@ describe('schema transformation with extraction of nested fields', () => { }, }, fieldNodeTransformerMap: { - 'Property': { - 'locationName': - fieldNode => wrapFieldNode(renameFieldNode(fieldNode, 'name'), ['location']), - 'renamedError': fieldNode => renameFieldNode(fieldNode, 'error'), + Property: { + locationName: fieldNode => + wrapFieldNode(renameFieldNode(fieldNode, 'name'), ['location']), + renamedError: fieldNode => renameFieldNode(fieldNode, 'error'), }, }, }), @@ -937,12 +949,9 @@ describe('schema transformation with extraction of nested fields', () => { }, ], message: 'Property.error error', - path: [ - 'propertyById', - 'renamedError', - ], + path: ['propertyById', 'renamedError'], }, - ] + ], }); }); @@ -1002,13 +1011,14 @@ describe('schema transformation with wrapping of object fields', () => { }, }, fieldNodeTransformerMap: { - 'Property': { - 'outerWrap': (fieldNode, fragments) => hoistFieldNodes({ - fieldNode, - fieldNames: ['id', 'name', 'error'], - path: ['innerWrap'], - fragments, - }), + Property: { + outerWrap: (fieldNode, fragments) => + hoistFieldNodes({ + fieldNode, + fieldNames: ['id', 'name', 'error'], + path: ['innerWrap'], + fragments, + }), }, }, }), @@ -1038,7 +1048,7 @@ describe('schema transformation with wrapping of object fields', () => { fragment W2 on InnerWrap { one: name } - `, + `, {}, {}, { @@ -1062,22 +1072,21 @@ describe('schema transformation with wrapping of object fields', () => { }, }, }, - 'errors': [{ - 'extensions': { - code: 'SOME_CUSTOM_CODE' + errors: [ + { + extensions: { + code: 'SOME_CUSTOM_CODE', + }, + locations: [ + { + column: 11, + line: 18, + }, + ], + message: 'Property.error error', + path: ['propertyById', 'test1', 'innerWrap', 'two'], }, - 'locations': [{ - column: 11, - line: 18 - }], - message: 'Property.error error', - path: [ - 'propertyById', - 'test1', - 'innerWrap', - 'two', - ], - }] + ], }); }); @@ -1112,7 +1121,7 @@ describe('schema transformation with wrapping of object fields', () => { fragment W2 on OuterWrap { one: name } - `, + `, {}, {}, { @@ -1132,21 +1141,21 @@ describe('schema transformation with wrapping of object fields', () => { }, }, }, - 'errors': [{ - 'extensions': { - code: 'SOME_CUSTOM_CODE' + errors: [ + { + extensions: { + code: 'SOME_CUSTOM_CODE', + }, + locations: [ + { + column: 13, + line: 14, + }, + ], + message: 'Property.error error', + path: ['propertyById', 'test1', 'two'], }, - 'locations': [{ - column: 13, - line: 14 - }], - message: 'Property.error error', - path: [ - 'propertyById', - 'test1', - 'two', - ], - }] + ], }); }); @@ -1184,7 +1193,7 @@ describe('schema transformation with wrapping of object fields', () => { fragment W2 on InnerWrap { one: name } - `, + `, {}, {}, { @@ -1208,22 +1217,21 @@ describe('schema transformation with wrapping of object fields', () => { }, }, }, - 'errors': [{ - 'extensions': { - code: 'SOME_CUSTOM_CODE' + errors: [ + { + extensions: { + code: 'SOME_CUSTOM_CODE', + }, + locations: [ + { + column: 13, + line: 18, + }, + ], + message: 'Property.error error', + path: ['propertyById', 'test1', 'innerWrap', 'two'], }, - 'locations': [{ - column: 13, - line: 18 - }], - message: 'Property.error error', - path: [ - 'propertyById', - 'test1', - 'innerWrap', - 'two', - ], - }] + ], }); }); }); @@ -1241,8 +1249,9 @@ describe('schema transformation with renaming of object fields', () => { } `, fieldNodeTransformerMap: { - 'Property': { - 'new_error': fieldNode => renameFieldNode(fieldNode, 'error'), + Property: { + // eslint-disable-next-line camelcase + new_error: fieldNode => renameFieldNode(fieldNode, 'error'), }, }, }), @@ -1258,7 +1267,7 @@ describe('schema transformation with renaming of object fields', () => { new_error } } - `, + `, {}, {}, { @@ -1285,10 +1294,7 @@ describe('schema transformation with renaming of object fields', () => { }, ], message: 'Property.error error', - path: [ - 'propertyById', - 'new_error', - ], + path: ['propertyById', 'new_error'], }, ], }); @@ -1318,11 +1324,11 @@ describe('interface resolver inheritance', () => { id: ({ _id }: { _id: number }) => `Node:${_id.toString()}`, }, User: { - name: ({ name }: { name: string}) => `User:${name}` + name: ({ name }: { name: string }) => `User:${name}`, }, Query: { - user: () => user - } + user: () => user, + }, }; it('copies resolvers from interface', async () => { @@ -1331,10 +1337,10 @@ describe('interface resolver inheritance', () => { // pull in an executable schema just so mergeSchema doesn't complain // about not finding default types (e.g. ID) propertySchema, - testSchemaWithInterfaceResolvers + testSchemaWithInterfaceResolvers, ], resolvers, - inheritResolversFromInterfaces: true + inheritResolversFromInterfaces: true, }); const query = '{ user { id name } }'; const response = await graphql(mergedSchema, query); @@ -1342,48 +1348,48 @@ describe('interface resolver inheritance', () => { data: { user: { id: 'Node:1', - name: 'User:Ada' - } - } + name: 'User:Ada', + }, + }, }); }); - it('does not copy resolvers from interface when flag is false', -async () => { + it('does not copy resolvers from interface when flag is false', async () => { const mergedSchema = mergeSchemas({ schemas: [ // pull in an executable schema just so mergeSchema doesn't complain // about not finding default types (e.g. ID) propertySchema, - testSchemaWithInterfaceResolvers + testSchemaWithInterfaceResolvers, ], resolvers, - inheritResolversFromInterfaces: false + inheritResolversFromInterfaces: false, }); const query = '{ user { id name } }'; const response = await graphql(mergedSchema, query); expect(response.errors.length).to.equal(1); - expect(response.errors[0].message).to.equal('Cannot return null for ' + - 'non-nullable field User.id.'); + expect(response.errors[0].message).to.equal( + 'Cannot return null for non-nullable field User.id.', + ); expect(response.errors[0].path).to.deep.equal(['user', 'id']); }); - it('does not copy resolvers from interface when flag is not provided', -async () => { + it('does not copy resolvers from interface when flag is not provided', async () => { const mergedSchema = mergeSchemas({ schemas: [ // pull in an executable schema just so mergeSchema doesn't complain // about not finding default types (e.g. ID) propertySchema, - testSchemaWithInterfaceResolvers + testSchemaWithInterfaceResolvers, ], - resolvers + resolvers, }); const query = '{ user { id name } }'; const response = await graphql(mergedSchema, query); expect(response.errors.length).to.equal(1); - expect(response.errors[0].message).to.equal('Cannot return null for ' + - 'non-nullable field User.id.'); + expect(response.errors[0].message).to.equal( + 'Cannot return null for non-nullable field User.id.', + ); expect(response.errors[0].path).to.deep.equal(['user', 'id']); }); }); @@ -1401,12 +1407,12 @@ describe('mergeSchemas', () => { `, resolvers: { Query: { - test: () => null - } - } + test: () => null, + }, + }, }); const mergedSchema = mergeSchemas({ - schemas: [schema] + schemas: [schema], }); const query = '{ test { field } }'; @@ -1427,12 +1433,12 @@ describe('mergeSchemas', () => { `, resolvers: { Query: { - getInput: (_root, args) => args.input.field - } - } + getInput: (_root, args) => args.input.field, + }, + }, }); const mergedSchema = mergeSchemas({ - schemas: [schema] + schemas: [schema], }); const query = '{ getInput(input: {}) }'; @@ -1459,9 +1465,9 @@ describe('mergeSchemas', () => { parseLiteral: (ast: any) => `_${ast.value as string}`, }), Query: { - getTestScalar: () => '_test' - } - } + getTestScalar: () => '_test', + }, + }, }); const mergedSchema = mergeSchemas({ schemas: [schema], @@ -1472,8 +1478,8 @@ describe('mergeSchemas', () => { serialize: value => (value as string).slice(2), parseValue: value => `__${value as string}`, parseLiteral: (ast: any) => `__${ast.value as string}`, - }) - } + }), + }, }); const query = '{ getTestScalar }'; @@ -1499,9 +1505,9 @@ describe('mergeSchemas', () => { parseLiteral: (ast: any) => `_${ast.value as string}`, }), Query: { - getTestScalar: () => '_test' - } - } + getTestScalar: () => '_test', + }, + }, }); const mergedSchema = mergeSchemas({ schemas: [schema], @@ -1512,8 +1518,8 @@ describe('mergeSchemas', () => { serialize: value => (value as string).slice(2), parseValue: value => `__${value as string}`, parseLiteral: (ast: any) => `__${ast.value as string}`, - }) - } + }), + }, }); const query = '{ getTestScalar }'; @@ -1534,9 +1540,9 @@ describe('mergeSchemas', () => { `, resolvers: { Query: { - get1: () => ({ subfield: 'test'}) - } - } + get1: () => ({ subfield: 'test' }), + }, + }, }); const mergedSchema = mergeSchemas({ schemas: [ @@ -1545,19 +1551,20 @@ describe('mergeSchemas', () => { type Query { get2: WrappingType } - ` + `, ], resolvers: { Query: { - get2: (_root, _args, context, info) => delegateToSchema({ - schema, - operation: 'query', - fieldName: 'get1', - context, - info - }) - } - } + get2: (_root, _args, context, info) => + delegateToSchema({ + schema, + operation: 'query', + fieldName: 'get1', + context, + info, + }), + }, + }, }); const query = ` @@ -1580,7 +1587,7 @@ describe('mergeSchemas', () => { type Query { wrappingObject: WrappingObject } - ` + `, }); const mergedSchema = mergeSchemas({ @@ -1588,10 +1595,10 @@ describe('mergeSchemas', () => { resolvers: { Query: { wrappingObject: () => ({ - functionField: () => 8 - }) + functionField: () => 8, + }), }, - } + }, }); const query = '{ wrappingObject { functionField } }'; @@ -1631,32 +1638,32 @@ describe('onTypeConflict', () => { typeDefs: typeDefs1, resolvers: { Query: { - test1: () => ({}) + test1: () => ({}), }, Test: { fieldA: () => 'A', - fieldB: () => 'B' - } - } + fieldB: () => 'B', + }, + }, }); schema2 = makeExecutableSchema({ typeDefs: typeDefs2, resolvers: { Query: { - test2: () => ({}) + test2: () => ({}), }, Test: { fieldA: () => 'A', - fieldC: () => 'C' - } - } + fieldC: () => 'C', + }, + }, }); }); it('by default takes last type', async () => { const mergedSchema = mergeSchemas({ - schemas: [schema1, schema2] + schemas: [schema1, schema2], }); const result1 = await graphql(mergedSchema, '{ test2 { fieldC } }'); expect(result1.data?.test2.fieldC).to.equal('C'); @@ -1667,7 +1674,7 @@ describe('onTypeConflict', () => { it('can use onTypeConflict to select last type', async () => { const mergedSchema = mergeSchemas({ schemas: [schema1, schema2], - onTypeConflict: (_left, right) => right + onTypeConflict: (_left, right) => right, }); const result1 = await graphql(mergedSchema, '{ test2 { fieldC } }'); expect(result1.data?.test2.fieldC).to.equal('C'); @@ -1678,7 +1685,7 @@ describe('onTypeConflict', () => { it('can use onTypeConflict to select first type', async () => { const mergedSchema = mergeSchemas({ schemas: [schema1, schema2], - onTypeConflict: (left) => left + onTypeConflict: left => left, }); const result1 = await graphql(mergedSchema, '{ test1 { fieldB } }'); expect(result1.data?.test1.fieldB).to.equal('B'); @@ -1733,8 +1740,8 @@ describe('mergeTypes', () => { }, Test: { field1: parent => parent.id, - } - } + }, + }, }); schema2 = makeExecutableSchema({ @@ -1746,8 +1753,8 @@ describe('mergeTypes', () => { }, Test: { field2: parent => parent.id, - } - } + }, + }, }); }); @@ -1757,17 +1764,18 @@ describe('mergeTypes', () => { merge: { Test: { selectionSet: '{ id }', - resolve: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ - schema: subschema, - operation: 'query', - fieldName: 'getTest', - args: { id: originalResult.id }, - selectionSet, - context, - info, - skipTypeMerging: true, - }) - } + resolve: (originalResult, context, info, subschema, selectionSet) => + delegateToSchema({ + schema: subschema, + operation: 'query', + fieldName: 'getTest', + args: { id: originalResult.id }, + selectionSet, + context, + info, + skipTypeMerging: true, + }), + }, }, }; @@ -1776,17 +1784,18 @@ describe('mergeTypes', () => { merge: { Test: { selectionSet: '{ id }', - resolve: (originalResult, context, info, subschema, selectionSet) => delegateToSchema({ - schema: subschema, - operation: 'query', - fieldName: 'getTest', - args: { id: originalResult.id }, - selectionSet, - context, - info, - skipTypeMerging: true, - }) - } + resolve: (originalResult, context, info, subschema, selectionSet) => + delegateToSchema({ + schema: subschema, + operation: 'query', + fieldName: 'getTest', + args: { id: originalResult.id }, + selectionSet, + context, + info, + skipTypeMerging: true, + }), + }, }, }; @@ -1794,25 +1803,30 @@ describe('mergeTypes', () => { subschemas: [subschemaConfig1, subschemaConfig2], }); - const result1 = await graphql(mergedSchema, `{ - rootField1 { - test { - field1 - ... on Test { - field2 + const result1 = await graphql( + mergedSchema, + ` + { + rootField1 { + test { + field1 + ... on Test { + field2 + } + } } } - } - }`); + `, + ); expect(result1).to.deep.equal({ data: { rootField1: { test: { field1: '1', field2: '1', - } - } - } + }, + }, + }, }); }); }); diff --git a/src/test/testDataloader.ts b/src/test/testDataloader.ts index 89bde4208bc..29cd626bfa0 100644 --- a/src/test/testDataloader.ts +++ b/src/test/testDataloader.ts @@ -3,10 +3,7 @@ import { graphql, GraphQLList } from 'graphql'; import { expect } from 'chai'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import { - mergeSchemas, - delegateToSchema -} from '../stitching'; +import { mergeSchemas, delegateToSchema } from '../stitching'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; describe('dataloader', () => { @@ -24,8 +21,12 @@ describe('dataloader', () => { `, resolvers: { Query: { - task: (_root, { id }) => ({ id, text: `task ${id as string}`, userId: id }), - } + task: (_root, { id }) => ({ + id, + text: `task ${id as string}`, + userId: id, + }), + }, }, }); @@ -41,16 +42,14 @@ describe('dataloader', () => { `, resolvers: { Query: { - usersByIds: (_root, { ids }) => ids.map((id: string) => ({ id, email: `${id}@tasks.com` })), - } + usersByIds: (_root, { ids }) => + ids.map((id: string) => ({ id, email: `${id}@tasks.com` })), + }, }, }); const schema = mergeSchemas({ - schemas: [ - taskSchema, - userSchema - ], + schemas: [taskSchema, userSchema], typeDefs: ` extend type Task { user: User! @@ -62,32 +61,36 @@ describe('dataloader', () => { fragment: '... on Task { userId }', resolve(task, _args, context, info) { return context.usersLoader.load({ id: task.userId, info }); - } - } + }, + }, }, - } + }, }); - const usersLoader = new DataLoader(async (keys: Array<{ id: any, info: IGraphQLToolsResolveInfo }>) => { - const users = await delegateToSchema({ - schema: userSchema, - operation: 'query', - fieldName: 'usersByIds', - args: { - ids: keys.map((k: { id: any }) => k.id) - }, - context: null, - info: keys[0].info, - returnType: new GraphQLList(keys[0].info.returnType), - }); + const usersLoader = new DataLoader( + async (keys: Array<{ id: any; info: IGraphQLToolsResolveInfo }>) => { + const users = await delegateToSchema({ + schema: userSchema, + operation: 'query', + fieldName: 'usersByIds', + args: { + ids: keys.map((k: { id: any }) => k.id), + }, + context: null, + info: keys[0].info, + returnType: new GraphQLList(keys[0].info.returnType), + }); - expect(users).to.deep.equal([{ - id: '1', - email: '1@tasks.com', - }]); + expect(users).to.deep.equal([ + { + id: '1', + email: '1@tasks.com', + }, + ]); - return users; - }); + return users; + }, + ); const query = `{ task(id: "1") { @@ -103,17 +106,16 @@ describe('dataloader', () => { const result = await graphql(schema, query, null, { usersLoader }); expect(result).to.deep.equal({ - data: - { + data: { task: { id: '1', text: 'task 1', user: { id: '1', email: '1@tasks.com', - } - } - } + }, + }, + }, }); }); }); diff --git a/src/test/testDelegateToSchema.ts b/src/test/testDelegateToSchema.ts index 9114c8560be..fe0e638a06f 100644 --- a/src/test/testDelegateToSchema.ts +++ b/src/test/testDelegateToSchema.ts @@ -1,18 +1,20 @@ -import { - GraphQLSchema, - graphql -} from 'graphql'; +import { GraphQLSchema, graphql } from 'graphql'; import { expect } from 'chai'; import delegateToSchema from '../stitching/delegateToSchema'; import mergeSchemas from '../stitching/mergeSchemas'; import { IResolvers } from '../Interfaces'; -import { propertySchema, bookingSchema, sampleData, Property } from './testingSchemas'; +import { + propertySchema, + bookingSchema, + sampleData, + Property, +} from './testingSchemas'; -function findPropertyByLocationName ( +function findPropertyByLocationName( properties: { [key: string]: Property }, - name: string + name: string, ): Property | undefined { for (const key of Object.keys(properties)) { const property = properties[key]; @@ -34,35 +36,37 @@ const COORDINATES_QUERY = ` } `; -function proxyResolvers (spec: string): IResolvers { +function proxyResolvers(spec: string): IResolvers { return { Booking: { property: { fragment: '... on Booking { propertyId }', - resolve (booking, _args, context, info) { - const delegateFn = spec === 'standalone' ? delegateToSchema : - info.mergeInfo.delegateToSchema; + resolve(booking, _args, context, info) { + const delegateFn = + spec === 'standalone' + ? delegateToSchema + : info.mergeInfo.delegateToSchema; return delegateFn?.({ schema: propertySchema, operation: 'query', fieldName: 'propertyById', args: { id: booking.propertyId }, context, - info + info, }); - } - } + }, + }, }, Location: { coordinates: { fragment: '... on Location { name }', resolve: location => { const name = location.name; - return findPropertyByLocationName(sampleData.Property, name) - .location.coordinates; - } - } - } + return findPropertyByLocationName(sampleData.Property, name).location + .coordinates; + }, + }, + }, }; } @@ -83,23 +87,28 @@ describe('stitching', () => { before(() => { schema = mergeSchemas({ schemas: [bookingSchema, propertySchema, proxyTypeDefs], - resolvers: proxyResolvers(spec) + resolvers: proxyResolvers(spec), }); }); it('should add fragments for deep types', async () => { - const result = await graphql(schema, COORDINATES_QUERY, - {}, {}, { bookingId: 'b1' }); + const result = await graphql( + schema, + COORDINATES_QUERY, + {}, + {}, + { bookingId: 'b1' }, + ); expect(result).to.deep.equal({ data: { bookingById: { property: { location: { - coordinates: sampleData.Property.p1.location.coordinates - } - } - } - } + coordinates: sampleData.Property.p1.location.coordinates, + }, + }, + }, + }, }); }); }); diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 1127a547099..5f3ba41b5fb 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -97,9 +97,9 @@ describe('@directives', () => { Gender: { NONBINARY: 'NB', FEMALE: 'F', - MALE: 'M' - } - } + MALE: 'M', + }, + }, }); function checkDirectives( @@ -107,10 +107,7 @@ describe('@directives', () => { typeDirectiveNames: [string], fieldDirectiveMap: { [key: string]: Array } = {}, ) { - assert.deepEqual( - getDirectiveNames(type), - typeDirectiveNames, - ); + assert.deepEqual(getDirectiveNames(type), typeDirectiveNames); Object.keys(fieldDirectiveMap).forEach(key => { assert.deepEqual( @@ -120,42 +117,45 @@ describe('@directives', () => { }); } - function getDirectiveNames( - type: VisitableSchemaType, - ): Array { + function getDirectiveNames(type: VisitableSchemaType): Array { return type.astNode.directives.map(d => d.name.value); } - assert.deepEqual( - getDirectiveNames(schema), - ['schemaDirective'], - ); + assert.deepEqual(getDirectiveNames(schema), ['schemaDirective']); checkDirectives(schema.getQueryType(), ['queryTypeDirective'], { people: ['queryFieldDirective'], }); - assert.deepEqual( - getDirectiveNames(schema.getType('Gender')), - ['enumTypeDirective'], - ); + assert.deepEqual(getDirectiveNames(schema.getType('Gender')), [ + 'enumTypeDirective', + ]); - const nonBinary = (schema.getType('Gender') as GraphQLEnumType).getValues()[0]; - assert.deepEqual( - getDirectiveNames(nonBinary), - ['enumValueDirective'], - ); + const nonBinary = (schema.getType( + 'Gender', + ) as GraphQLEnumType).getValues()[0]; + assert.deepEqual(getDirectiveNames(nonBinary), ['enumValueDirective']); - checkDirectives(schema.getType('Date') as GraphQLObjectType, ['dateDirective']); + checkDirectives(schema.getType('Date') as GraphQLObjectType, [ + 'dateDirective', + ]); - checkDirectives(schema.getType('Named') as GraphQLObjectType, ['interfaceDirective'], { - name: ['interfaceFieldDirective'], - }); + checkDirectives( + schema.getType('Named') as GraphQLObjectType, + ['interfaceDirective'], + { + name: ['interfaceFieldDirective'], + }, + ); - checkDirectives(schema.getType('PersonInput') as GraphQLObjectType, ['inputTypeDirective'], { - name: ['inputFieldDirective'], - gender: [], - }); + checkDirectives( + schema.getType('PersonInput') as GraphQLObjectType, + ['inputTypeDirective'], + { + name: ['inputFieldDirective'], + gender: [], + }, + ); checkDirectives(schema.getMutationType(), ['mutationTypeDirective'], { addPerson: ['mutationMethodDirective'], @@ -192,9 +192,9 @@ describe('@directives', () => { resolvers: { DateFormat: { LOCAL: 'local', - ISO: 'iso' - } - } + ISO: 'iso', + }, + }, }); assert.exists(schema.getType('DateFormat')); @@ -203,7 +203,7 @@ describe('@directives', () => { }); it('can be implemented with SchemaDirectiveVisitor', () => { - const visited: Set = new Set; + const visited: Set = new Set(); const schema = makeExecutableSchema({ typeDefs }); SchemaDirectiveVisitor.visitSchemaDirectives(schema, { @@ -228,7 +228,7 @@ describe('@directives', () => { public visitSchema(s: GraphQLSchema) { visited.push(s); } - } + }, }); assert.strictEqual(visited.length, 1); assert.strictEqual(visited[0], schema); @@ -252,9 +252,12 @@ describe('@directives', () => { }, mutationMethodDirective: class extends SchemaDirectiveVisitor { - public visitFieldDefinition(field: GraphQLField, details: { - objectType: GraphQLObjectType, - }) { + public visitFieldDefinition( + field: GraphQLField, + details: { + objectType: GraphQLObjectType; + }, + ) { assert.strictEqual(this.visitedType, field); assert.strictEqual(field.name, 'addPerson'); assert.strictEqual(details.objectType, mutationObjectType); @@ -264,10 +267,13 @@ describe('@directives', () => { }, mutationArgumentDirective: class extends SchemaDirectiveVisitor { - public visitArgumentDefinition(arg: GraphQLArgument, details: { - field: GraphQLField, - objectType: GraphQLObjectType, - }) { + public visitArgumentDefinition( + arg: GraphQLArgument, + details: { + field: GraphQLField; + objectType: GraphQLObjectType; + }, + ) { assert.strictEqual(this.visitedType, arg); assert.strictEqual(arg.name, 'input'); assert.strictEqual(details.field, mutationField); @@ -285,9 +291,12 @@ describe('@directives', () => { }, enumValueDirective: class extends SchemaDirectiveVisitor { - public visitEnumValue(value: GraphQLEnumValue, details: { - enumType: GraphQLEnumType, - }) { + public visitEnumValue( + value: GraphQLEnumValue, + details: { + enumType: GraphQLEnumType; + }, + ) { assert.strictEqual(this.visitedType, value); assert.strictEqual(value.name, 'NONBINARY'); assert.strictEqual(value.value, 'NONBINARY'); @@ -304,14 +313,17 @@ describe('@directives', () => { }, inputFieldDirective: class extends SchemaDirectiveVisitor { - public visitInputFieldDefinition(field: GraphQLInputField, details: { - objectType: GraphQLInputObjectType, - }) { + public visitInputFieldDefinition( + field: GraphQLInputField, + details: { + objectType: GraphQLInputObjectType; + }, + ) { assert.strictEqual(this.visitedType, field); assert.strictEqual(field.name, 'name'); assert.strictEqual(details.objectType, inputObjectType); } - } + }, }); }); @@ -330,10 +342,7 @@ describe('@directives', () => { false, ); - assert.strictEqual( - Visitor.implementsVisitorMethod('visitObject'), - true, - ); + assert.strictEqual(Visitor.implementsVisitorMethod('visitObject'), true); assert.strictEqual( Visitor.implementsVisitorMethod('visitInputFieldDefinition'), @@ -372,11 +381,10 @@ describe('@directives', () => { const schema = makeExecutableSchema({ typeDefs }); const visitor = new SimpleVisitor(schema); visitor.visit(); - assert.deepEqual(visitor.names.sort((a, b) => a.localeCompare(b)), [ - 'Mutation', - 'Person', - 'Query', - ]); + assert.deepEqual( + visitor.names.sort((a, b) => a.localeCompare(b)), + ['Mutation', 'Person', 'Query'], + ); }); it('can use SchemaDirectiveVisitor as a no-op visitor', () => { @@ -415,8 +423,8 @@ describe('@directives', () => { assert.deepEqual( Object.keys(methodNamesEncountered).sort((a, b) => a.localeCompare(b)), Object.keys(SchemaVisitor.prototype) - .filter(name => name.startsWith('visit')) - .sort((a, b) => a.localeCompare(b)) + .filter(name => name.startsWith('visit')) + .sort((a, b) => a.localeCompare(b)), ); }); @@ -448,49 +456,55 @@ describe('@directives', () => { fieldCount: 0, }; - const visitors = SchemaDirectiveVisitor.visitSchemaDirectives(schema, { - oyez: class extends SchemaDirectiveVisitor { - public static getDirectiveDeclaration( - name: string, - theSchema: GraphQLSchema, - ) { - assert.strictEqual(theSchema, schema); - const prev = schema.getDirective(name); - prev.args.some(arg => { - if (arg.name === 'times') { - // Override the default value of the times argument to be 3 - // instead of 5. - arg.defaultValue = 3; - return true; - } - return false; - }); - return prev; - } - - public visitObject() { - ++this.context.objectCount; - assert.strictEqual(this.args.times, 3); - } + const visitors = SchemaDirectiveVisitor.visitSchemaDirectives( + schema, + { + oyez: class extends SchemaDirectiveVisitor { + public static getDirectiveDeclaration( + name: string, + theSchema: GraphQLSchema, + ) { + assert.strictEqual(theSchema, schema); + const prev = schema.getDirective(name); + prev.args.some(arg => { + if (arg.name === 'times') { + // Override the default value of the times argument to be 3 + // instead of 5. + arg.defaultValue = 3; + return true; + } + return false; + }); + return prev; + } - public visitFieldDefinition(field: GraphQLField) { - ++this.context.fieldCount; - if (field.name === 'judge') { - assert.strictEqual(this.args.times, 0); - } else if (field.name === 'marshall') { + public visitObject() { + ++this.context.objectCount; assert.strictEqual(this.args.times, 3); } - assert.strictEqual(this.args.party, 'IMPARTIAL'); - } - } - }, context); + + public visitFieldDefinition(field: GraphQLField) { + ++this.context.fieldCount; + if (field.name === 'judge') { + assert.strictEqual(this.args.times, 0); + } else if (field.name === 'marshall') { + assert.strictEqual(this.args.times, 3); + } + assert.strictEqual(this.args.party, 'IMPARTIAL'); + } + }, + }, + context, + ); assert.strictEqual(context.objectCount, 1); assert.strictEqual(context.fieldCount, 2); assert.deepEqual(Object.keys(visitors), ['oyez']); assert.deepEqual( - visitors.oyez.map(v => (v.visitedType as GraphQLObjectType | GraphQLField).name), + visitors.oyez.map( + v => (v.visitedType as GraphQLObjectType | GraphQLField).name, + ), ['Courtroom', 'judge', 'marshall'], ); }); @@ -507,7 +521,7 @@ describe('@directives', () => { upper: class extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const { resolve = defaultFieldResolver } = field; - field.resolve = async function (...args) { + field.resolve = async function(...args) { const result = await resolve.apply(this, args); if (typeof result === 'string') { return result.toUpperCase(); @@ -515,24 +529,27 @@ describe('@directives', () => { return result; }; } - } + }, }, resolvers: { Query: { hello() { return 'hello world'; - } - } - } + }, + }, + }, }); - return graphql(schema, ` - query { - hello - } - `).then(({ data }) => { + return graphql( + schema, + ` + query { + hello + } + `, + ).then(({ data }) => { assert.deepEqual(data, { - hello: 'HELLO WORLD' + hello: 'HELLO WORLD', }); }); }); @@ -554,30 +571,33 @@ describe('@directives', () => { const { resolve = defaultFieldResolver } = field; const { format } = this.args; field.type = GraphQLString; - field.resolve = async function (...args) { + field.resolve = async function(...args) { const date = await resolve.apply(this, args); return formatDate(date, format, true); }; } - } + }, }, resolvers: { Query: { today() { return new Date(1519688273858).toUTCString(); - } - } - } + }, + }, + }, }); - return graphql(schema, ` - query { - today - } - `).then(({ data }) => { + return graphql( + schema, + ` + query { + today + } + `, + ).then(({ data }) => { assert.deepEqual(data, { - today: 'February 26, 2018' + today: 'February 26, 2018', }); }); }); @@ -588,13 +608,20 @@ describe('@directives', () => { const { resolve = defaultFieldResolver } = field; const { defaultFormat } = this.args; - field.args.push(Object.create({ - name: 'format', - type: GraphQLString, - })); + field.args.push( + Object.create({ + name: 'format', + type: GraphQLString, + }), + ); field.type = GraphQLString; - field.resolve = async function (source, { format, ...args }, context, info) { + field.resolve = async function( + source, + { format, ...args }, + context, + info, + ) { const newFormat = format || defaultFormat; const date = await resolve.call(this, source, args, context, info); return formatDate(date, newFormat, true); @@ -615,16 +642,16 @@ describe('@directives', () => { }`, schemaDirectives: { - date: FormattableDateDirective + date: FormattableDateDirective, }, resolvers: { Query: { today() { return new Date(1521131357195); - } - } - } + }, + }, + }, }); const resultNoArg = await graphql(schema, 'query { today }'); @@ -633,32 +660,26 @@ describe('@directives', () => { assert.deepEqual(resultNoArg.errors, []); } - assert.deepEqual( - resultNoArg.data, - { today: 'March 15, 2018' } - ); + assert.deepEqual(resultNoArg.data, { today: 'March 15, 2018' }); - const resultWithArg = await graphql(schema, ` - query { - today(format: "dd mmm yyyy") - }`); + const resultWithArg = await graphql( + schema, + ` + query { + today(format: "dd mmm yyyy") + } + `, + ); if (resultWithArg.errors != null) { assert.deepEqual(resultWithArg.errors, []); } - assert.deepEqual( - resultWithArg.data, - { today: '15 Mar 2018' } - ); + assert.deepEqual(resultWithArg.data, { today: '15 Mar 2018' }); }); it('can be used to implement the @intl example', () => { - function translate( - text: string, - path: Array, - locale: string, - ) { + function translate(text: string, path: Array, locale: string) { assert.strictEqual(text, 'hello'); assert.deepEqual(path, ['Query', 'greeting']); assert.strictEqual(locale, 'fr'); @@ -666,7 +687,7 @@ describe('@directives', () => { } const context = { - locale: 'fr' + locale: 'fr', }; const schema = makeExecutableSchema({ @@ -679,11 +700,14 @@ describe('@directives', () => { schemaDirectives: { intl: class extends SchemaDirectiveVisitor { - public visitFieldDefinition(field: GraphQLField, details: { - objectType: GraphQLObjectType, - }) { + public visitFieldDefinition( + field: GraphQLField, + details: { + objectType: GraphQLObjectType; + }, + ) { const { resolve = defaultFieldResolver } = field; - field.resolve = async function (...args: Array) { + field.resolve = async function(...args: Array) { const defaultText = await resolve.apply(this, args); // In this example, path would be ["Query", "greeting"]: const path = [details.objectType.name, field.name]; @@ -691,36 +715,36 @@ describe('@directives', () => { return translate(defaultText, path, context.locale); }; } - } + }, }, resolvers: { Query: { greeting() { return 'hello'; - } - } - } + }, + }, + }, }); - return graphql(schema, ` - query { - greeting - } - `, null, context).then(({ data }) => { + return graphql( + schema, + ` + query { + greeting + } + `, + null, + context, + ).then(({ data }) => { assert.deepEqual(data, { - greeting: 'bonjour' + greeting: 'bonjour', }); }); }); it('can be used to implement the @auth example', async () => { - const roles = [ - 'UNKNOWN', - 'USER', - 'REVIEWER', - 'ADMIN', - ]; + const roles = ['UNKNOWN', 'USER', 'REVIEWER', 'ADMIN']; function getUser(token: string) { return { @@ -728,7 +752,7 @@ describe('@directives', () => { const tokenIndex = roles.indexOf(token); const roleIndex = roles.indexOf(role); return roleIndex >= 0 && tokenIndex >= roleIndex; - } + }, }; } @@ -761,20 +785,20 @@ describe('@directives', () => { Object.keys(fields).forEach(fieldName => { const field = fields[fieldName]; const { resolve = defaultFieldResolver } = field; - field.resolve = function (...args: Array) { + field.resolve = function(...args: Array) { // Get the required Role from the field first, falling back // to the objectType if no Role is required by the field: const requiredRole = (field as any)._requiredAuthRole || (objectType as any)._requiredAuthRole; - if (! requiredRole) { + if (!requiredRole) { return resolve.apply(this, args); } const context = args[2]; const user = getUser(context.headers.authToken); - if (! user.hasRole(requiredRole)) { + if (!user.hasRole(requiredRole)) { throw new Error('not authorized'); } @@ -808,45 +832,55 @@ describe('@directives', () => { }`, schemaDirectives: { - auth: AuthDirective + auth: AuthDirective, }, resolvers: { Query: { users() { - return [{ - banned: true, - canPost: false, - name: 'Ben' - }]; - } - } - } + return [ + { + banned: true, + canPost: false, + name: 'Ben', + }, + ]; + }, + }, + }, }); function execWithRole(role: string): Promise { - return graphql(schema, ` - query { - users { - name - banned - canPost - } - } - `, null, { - headers: { - authToken: role, - } - }); + return graphql( + schema, + ` + query { + users { + name + banned + canPost + } + } + `, + null, + { + headers: { + authToken: role, + }, + }, + ); } function checkErrors( expectedCount: number, ...expectedNames: Array ) { - return function ({ errors = [], data }: { - errors: Array, - data: any, + return function({ + errors = [], + data, + }: { + errors: Array; + data: any; }) { assert.strictEqual(errors.length, expectedCount); assert(errors.every(error => error.message === 'not authorized')); @@ -863,12 +897,14 @@ describe('@directives', () => { execWithRole('UNKNOWN').then(checkErrors(3, 'banned', 'canPost', 'name')), execWithRole('USER').then(checkErrors(2, 'banned', 'canPost')), execWithRole('REVIEWER').then(checkErrors(1, 'banned')), - execWithRole('ADMIN').then(checkErrors(0)).then(data => { - assert.strictEqual(data.users.length, 1); - assert.strictEqual(data.users[0].banned, true); - assert.strictEqual(data.users[0].canPost, false); - assert.strictEqual(data.users[0].name, 'Ben'); - }), + execWithRole('ADMIN') + .then(checkErrors(0)) + .then(data => { + assert.strictEqual(data.users.length, 1); + assert.strictEqual(data.users[0].banned, true); + assert.strictEqual(data.users[0].canPost, false); + assert.strictEqual(data.users[0].name, 'Ben'); + }), ]); }); @@ -891,7 +927,7 @@ describe('@directives', () => { parseLiteral(ast: StringValueNode) { return type.parseLiteral(ast, {}); - } + }, }); } } @@ -927,55 +963,63 @@ describe('@directives', () => { } private wrapType(field: GraphQLInputField | GraphQLField) { - if (field.type instanceof GraphQLNonNull && - field.type.ofType instanceof GraphQLScalarType) { + if ( + field.type instanceof GraphQLNonNull && + field.type.ofType instanceof GraphQLScalarType + ) { field.type = new GraphQLNonNull( - new LimitedLengthType(field.type.ofType, this.args.max)); + new LimitedLengthType(field.type.ofType, this.args.max), + ); } else if (field.type instanceof GraphQLScalarType) { field.type = new LimitedLengthType(field.type, this.args.max); } else { throw new Error(`Not a scalar type: ${field.type.toString()}`); } } - } + }, }, resolvers: { Query: { books() { - return [{ - title: 'abcdefghijklmnopqrstuvwxyz' - }]; - } + return [ + { + title: 'abcdefghijklmnopqrstuvwxyz', + }, + ]; + }, }, Mutation: { createBook(_parent, args) { return args.book; - } - } - } + }, + }, + }, }); - const { errors } = await graphql(schema, ` - query { - books { - title - } - } - `); - assert.strictEqual(errors.length, 1); - assert.strictEqual( - errors[0].message, - 'expected 26 to be at most 10', + const { errors } = await graphql( + schema, + ` + query { + books { + title + } + } + `, ); + assert.strictEqual(errors.length, 1); + assert.strictEqual(errors[0].message, 'expected 26 to be at most 10'); - const result = await graphql(schema, ` - mutation { - createBook(book: { title: "safe title" }) { - title - } - } - `); + const result = await graphql( + schema, + ` + mutation { + createBook(book: { title: "safe title" }) { + title + } + } + `, + ); if (result.errors != null) { assert.deepEqual(result.errors, []); @@ -983,8 +1027,8 @@ describe('@directives', () => { assert.deepEqual(result.data, { createBook: { - title: 'safe title' - } + title: 'safe title', + }, }); }); @@ -1027,54 +1071,67 @@ describe('@directives', () => { }, }); } - } + }, }, resolvers: { Query: { people() { - return [{ - personID: 1, - name: 'Ben', - }]; + return [ + { + personID: 1, + name: 'Ben', + }, + ]; }, locations() { - return [{ - locationID: 1, - address: '140 10th St', - }]; - } - } - } + return [ + { + locationID: 1, + address: '140 10th St', + }, + ]; + }, + }, + }, }); - return graphql(schema, ` - query { - people { - uid - personID - name - } - locations { - uid - locationID - address - } - } - `, null, context).then(result => { + return graphql( + schema, + ` + query { + people { + uid + personID + name + } + locations { + uid + locationID + address + } + } + `, + null, + context, + ).then(result => { const { data } = result; - assert.deepEqual(data.people, [{ - uid: '580a207c8e94f03b93a2b01217c3cc218490571a', - personID: 1, - name: 'Ben', - }]); - - assert.deepEqual(data.locations, [{ - uid: 'c31b71e6e23a7ae527f94341da333590dd7cba96', - locationID: 1, - address: '140 10th St', - }]); + assert.deepEqual(data.people, [ + { + uid: '580a207c8e94f03b93a2b01217c3cc218490571a', + personID: 1, + name: 'Ben', + }, + ]); + + assert.deepEqual(data.locations, [ + { + uid: 'c31b71e6e23a7ae527f94341da333590dd7cba96', + locationID: 1, + address: '140 10th St', + }, + ]); }); }); @@ -1085,11 +1142,11 @@ describe('@directives', () => { objectTypeDirective: class extends SchemaDirectiveVisitor { public visitObject(object: GraphQLObjectType) { return Object.create(object, { - name: { value: 'Human' } + name: { value: 'Human' }, }); } - } - } + }, + }, }); const Query = schema.getType('Query') as GraphQLObjectType; @@ -1102,7 +1159,10 @@ describe('@directives', () => { const Mutation = schema.getType('Mutation') as GraphQLObjectType; const addPersonResultType = Mutation.getFields().addPerson.type; - assert.strictEqual(addPersonResultType, schema.getType('Human') as GraphQLOutputType); + assert.strictEqual( + addPersonResultType, + schema.getType('Human') as GraphQLOutputType, + ); const WhateverUnion = schema.getType('WhateverUnion') as GraphQLUnionType; const found = WhateverUnion.getTypes().some(type => { @@ -1115,10 +1175,7 @@ describe('@directives', () => { assert.strictEqual(found, true); // Make sure that the Person type was actually removed. - assert.strictEqual( - typeof schema.getType('Person'), - 'undefined' - ); + assert.strictEqual(typeof schema.getType('Person'), 'undefined'); }); it('can remove enum values', () => { @@ -1143,14 +1200,14 @@ describe('@directives', () => { return null; } } - } - } + }, + }, }); const AgeUnit = schema.getType('AgeUnit') as GraphQLEnumType; assert.deepEqual( AgeUnit.getValues().map(value => value.name), - ['DOG_YEARS', 'PERSON_YEARS'] + ['DOG_YEARS', 'PERSON_YEARS'], ); }); @@ -1178,16 +1235,13 @@ describe('@directives', () => { public visitObject(object: GraphQLObjectType) { object.name = this.args.to; } - } - } + }, + }, }); const Human = schema.getType('Human') as GraphQLObjectType; assert.strictEqual(Human.name, 'Human'); - assert.strictEqual( - Human.getFields().heightInInches.type, - GraphQLInt, - ); + assert.strictEqual(Human.getFields().heightInInches.type, GraphQLInt); const Person = schema.getType('Person') as GraphQLObjectType; assert.strictEqual(Person.name, 'Person'); @@ -1197,11 +1251,10 @@ describe('@directives', () => { ); const Query = schema.getType('Query') as GraphQLObjectType; - const peopleType = Query.getFields().people.type as GraphQLList; - assert.strictEqual( - peopleType.ofType, - Human - ); + const peopleType = Query.getFields().people.type as GraphQLList< + GraphQLObjectType + >; + assert.strictEqual(peopleType.ofType, Human); }); it('does not enforce query directive locations (issue #680)', () => { @@ -1220,8 +1273,8 @@ describe('@directives', () => { assert.strictEqual(object.name, 'Query'); visited.add(object); } - } - } + }, + }, }); assert.strictEqual(visited.size, 1); @@ -1240,7 +1293,7 @@ describe('@directives', () => { upper: class extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const { resolve = defaultFieldResolver } = field; - const newField = {...field}; + const newField = { ...field }; newField.resolve = async function(...args: Array) { const result = await resolve.apply(this, args); diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index 367ff8037ed..eea4b4876f4 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -17,16 +17,24 @@ class ErrorWithExtensions extends GraphQLError { describe('Errors', () => { describe('relocatedError', () => { it('should adjust the path of a GraphqlError', () => { - const originalError = new GraphQLError('test', null, null, null, ['test']); + const originalError = new GraphQLError('test', null, null, null, [ + 'test', + ]); const newError = relocatedError(originalError, null, ['test', 1]); - const expectedError = new GraphQLError('test', null, null, null, ['test', 1]); + const expectedError = new GraphQLError('test', null, null, null, [ + 'test', + 1, + ]); assert.deepEqual(newError, expectedError); }); it('should also locate a non GraphQLError', () => { const originalError = new Error('test'); const newError = relocatedError(originalError, null, ['test', 1]); - const expectedError = new GraphQLError('test', null, null, null, ['test', 1]); + const expectedError = new GraphQLError('test', null, null, null, [ + 'test', + 1, + ]); assert.deepEqual(newError, expectedError); }); }); @@ -34,27 +42,32 @@ describe('Errors', () => { describe('getErrors', () => { it('should return all errors including if path is not defined', () => { const error = { - message: 'Test error without path' + message: 'Test error without path', }; const mockErrors: any = { responseKey: '', [ERROR_SYMBOL]: [error], }; - assert.deepEqual(getErrors(mockErrors, 'responseKey'), - [mockErrors[ERROR_SYMBOL][0]] - ); + assert.deepEqual(getErrors(mockErrors, 'responseKey'), [ + mockErrors[ERROR_SYMBOL][0], + ]); }); }); describe('checkResultAndHandleErrors', () => { it('persists single error', () => { const result = { - errors: [new GraphQLError('Test error')] + errors: [new GraphQLError('Test error')], }; try { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - checkResultAndHandleErrors(result, {}, {} as IGraphQLToolsResolveInfo, 'responseKey'); + checkResultAndHandleErrors( + result, + {}, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + {} as IGraphQLToolsResolveInfo, + 'responseKey', + ); } catch (e) { assert.equal(e.message, 'Test error'); assert.isUndefined(e.originalError.errors); @@ -63,11 +76,16 @@ describe('Errors', () => { it('persists single error with extensions', () => { const result = { - errors: [new ErrorWithExtensions('Test error', 'UNAUTHENTICATED')] + errors: [new ErrorWithExtensions('Test error', 'UNAUTHENTICATED')], }; try { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - checkResultAndHandleErrors(result, {}, {} as IGraphQLToolsResolveInfo, 'responseKey'); + checkResultAndHandleErrors( + result, + {}, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + {} as IGraphQLToolsResolveInfo, + 'responseKey', + ); } catch (e) { assert.equal(e.message, 'Test error'); assert.equal(e.extensions && e.extensions.code, 'UNAUTHENTICATED'); @@ -77,14 +95,16 @@ describe('Errors', () => { it('combines errors and persists the original errors', () => { const result = { - errors: [ - new GraphQLError('Error1'), - new GraphQLError('Error2'), - ] + errors: [new GraphQLError('Error1'), new GraphQLError('Error2')], }; try { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - checkResultAndHandleErrors(result, {}, {} as IGraphQLToolsResolveInfo, 'responseKey'); + checkResultAndHandleErrors( + result, + {}, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + {} as IGraphQLToolsResolveInfo, + 'responseKey', + ); } catch (e) { assert.equal(e.message, 'Error1\nError2'); assert.isNotEmpty(e.originalError); @@ -117,33 +137,36 @@ describe('passes along errors for missing fields on list', () => { resolvers: { Query: { getOuter: () => ({ - innerList: [{ mandatoryField: 'test'}, {}] - }) + innerList: [{ mandatoryField: 'test' }, {}], + }), }, - } + }, }); const mergedSchema = mergeSchemas({ - schemas: [schema] + schemas: [schema], }); - const result = await graphql(mergedSchema, '{ getOuter { innerList { mandatoryField } } }'); + const result = await graphql( + mergedSchema, + '{ getOuter { innerList { mandatoryField } } }', + ); expect(result).to.deep.equal({ data: { getOuter: null, }, - errors: [{ - locations: [{ - column: 26, - line: 1, - }], - message: 'Cannot return null for non-nullable field Inner.mandatoryField.', - path: [ - 'getOuter', - 'innerList', - 1, - 'mandatoryField', - ], - }] + errors: [ + { + locations: [ + { + column: 26, + line: 1, + }, + ], + message: + 'Cannot return null for non-nullable field Inner.mandatoryField.', + path: ['getOuter', 'innerList', 1, 'mandatoryField'], + }, + ], }); }); @@ -165,35 +188,38 @@ describe('passes along errors for missing fields on list', () => { resolvers: { Query: { getOuter: () => ({ - innerList: [{ mandatoryField: 'test' }, {}] - }) + innerList: [{ mandatoryField: 'test' }, {}], + }), }, - } + }, }); const mergedSchema = mergeSchemas({ - schemas: [schema] + schemas: [schema], }); - const result = await graphql(mergedSchema, '{ getOuter { innerList { mandatoryField } } }'); + const result = await graphql( + mergedSchema, + '{ getOuter { innerList { mandatoryField } } }', + ); expect(result).to.deep.equal({ data: { getOuter: { - innerList: [{ mandatoryField: 'test'}, null], + innerList: [{ mandatoryField: 'test' }, null], }, }, - errors: [{ - locations: [{ - column: 26, - line: 1, - }], - message: 'Cannot return null for non-nullable field Inner.mandatoryField.', - path: [ - 'getOuter', - 'innerList', - 1, - 'mandatoryField', - ], - }] + errors: [ + { + locations: [ + { + column: 26, + line: 1, + }, + ], + message: + 'Cannot return null for non-nullable field Inner.mandatoryField.', + path: ['getOuter', 'innerList', 1, 'mandatoryField'], + }, + ], }); }); }); @@ -220,29 +246,32 @@ describe('passes along errors when list field errors', () => { innerList: [{ mandatoryField: 'test' }, new Error('test')], }), }, - } + }, }); const mergedSchema = mergeSchemas({ - schemas: [schema] + schemas: [schema], }); - const result = await graphql(mergedSchema, '{ getOuter { innerList { mandatoryField } } }'); + const result = await graphql( + mergedSchema, + '{ getOuter { innerList { mandatoryField } } }', + ); expect(result).to.deep.equal({ data: { getOuter: null, }, - errors: [{ - locations: [{ - column: 14, - line: 1, - }], - message: 'test', - path: [ - 'getOuter', - 'innerList', - 1, - ], - }] + errors: [ + { + locations: [ + { + column: 14, + line: 1, + }, + ], + message: 'test', + path: ['getOuter', 'innerList', 1], + }, + ], }); }); @@ -267,31 +296,34 @@ describe('passes along errors when list field errors', () => { innerList: [{ mandatoryField: 'test' }, new Error('test')], }), }, - } + }, }); const mergedSchema = mergeSchemas({ - schemas: [schema] + schemas: [schema], }); - const result = await graphql(mergedSchema, '{ getOuter { innerList { mandatoryField } } }'); + const result = await graphql( + mergedSchema, + '{ getOuter { innerList { mandatoryField } } }', + ); expect(result).to.deep.equal({ data: { getOuter: { - innerList: [{ mandatoryField: 'test'}, null], + innerList: [{ mandatoryField: 'test' }, null], }, }, - errors: [{ - locations: [{ - column: 14, - line: 1, - }], - message: 'test', - path: [ - 'getOuter', - 'innerList', - 1, - ], - }] + errors: [ + { + locations: [ + { + column: 14, + line: 1, + }, + ], + message: 'test', + path: ['getOuter', 'innerList', 1], + }, + ], }); }); }); diff --git a/src/test/testExtensionExtraction.ts b/src/test/testExtensionExtraction.ts index 234205a5774..9cac63cf502 100644 --- a/src/test/testExtensionExtraction.ts +++ b/src/test/testExtensionExtraction.ts @@ -19,7 +19,9 @@ describe('Extension extraction', () => { const extensionAst = extractExtensionDefinitons(astDocument); expect(extensionAst.definitions).to.have.length(1); - expect(extensionAst.definitions[0].kind).to.equal('InputObjectTypeExtension'); + expect(extensionAst.definitions[0].kind).to.equal( + 'InputObjectTypeExtension', + ); }); it('extracts extended unions', () => { @@ -64,4 +66,3 @@ describe('Extension extraction', () => { expect(extensionAst.definitions[0].kind).to.equal('EnumTypeExtension'); }); }); - diff --git a/src/test/testFragmentsAreNotDuplicated.ts b/src/test/testFragmentsAreNotDuplicated.ts index ab7d70c9ec0..91d7baecea2 100644 --- a/src/test/testFragmentsAreNotDuplicated.ts +++ b/src/test/testFragmentsAreNotDuplicated.ts @@ -1,11 +1,7 @@ -import {expect} from 'chai'; -import {ExecutionResult, graphql} from 'graphql'; +import { expect } from 'chai'; +import { ExecutionResult, graphql } from 'graphql'; -import { - addMocksToSchema, - makeExecutableSchema, - transformSchema, -} from '..'; +import { addMocksToSchema, makeExecutableSchema, transformSchema } from '..'; describe('Merging schemas', () => { it('should not throw `There can be only one fragment named "FieldName"` errors', async () => { @@ -13,7 +9,7 @@ describe('Merging schemas', () => { typeDefs: rawSchema, }); - addMocksToSchema({schema: originalSchema}); + addMocksToSchema({ schema: originalSchema }); const originalResult = await graphql( originalSchema, diff --git a/src/test/testLogger.ts b/src/test/testLogger.ts index b6176fb4299..3b30d4d3ede 100644 --- a/src/test/testLogger.ts +++ b/src/test/testLogger.ts @@ -126,12 +126,14 @@ describe('Logger', () => { `; const resolve = { RootMutation: { - species: () => new Promise((_, reject) => { - reject(new Error('oops!')); - }), - stuff: () => new Promise((_, reject) => { - reject(new Error('oh noes!')); - }), + species: () => + new Promise((_, reject) => { + reject(new Error('oops!')); + }), + stuff: () => + new Promise((_, reject) => { + reject(new Error('oh noes!')); + }), }, }; const logger = new Logger(); @@ -162,7 +164,8 @@ describe('Logger', () => { `; const resolve = { RootQuery: { - species: () => new Promise((_, reject) => { + species: () => + new Promise((_, reject) => { reject(new Error('oops!')); }), }, diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index b47f742a92b..96e5a79ea7e 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -14,16 +14,18 @@ import { subscriptionSchema, subscriptionPubSubTrigger, subscriptionPubSub, - makeSchemaRemoteFromLink + makeSchemaRemoteFromLink, } from '../test/testingSchemas'; describe('remote queries', () => { let schema: GraphQLSchema; before(async () => { - const remoteSubschemaConfig = await makeSchemaRemoteFromLink(propertySchema); + const remoteSubschemaConfig = await makeSchemaRemoteFromLink( + propertySchema, + ); schema = makeRemoteExecutableSchema({ schema: remoteSubschemaConfig.schema, - link: remoteSubschemaConfig.link + link: remoteSubschemaConfig.link, }); }); @@ -61,18 +63,20 @@ describe('remote queries', () => { describe('remote subscriptions', () => { let schema: GraphQLSchema; before(async () => { - const remoteSubschemaConfig = await makeSchemaRemoteFromLink(subscriptionSchema); + const remoteSubschemaConfig = await makeSchemaRemoteFromLink( + subscriptionSchema, + ); schema = makeRemoteExecutableSchema({ schema: remoteSubschemaConfig.schema, - link: remoteSubschemaConfig.link + link: remoteSubschemaConfig.link, }); }); it('should work', done => { const mockNotification = { notifications: { - text: 'Hello world' - } + text: 'Hello world', + }, }; const subscription = parse(` @@ -84,22 +88,30 @@ describe('remote subscriptions', () => { `); let notificationCnt = 0; - subscribe(schema, subscription).then(results => { - forAwaitEach(results as AsyncIterable, (result: ExecutionResult) => { - expect(result).to.have.property('data'); - expect(result.data).to.deep.equal(mockNotification); - if (!notificationCnt++) { - done(); - } - }).catch(done); - }).then(() => subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification)).catch(done); + subscribe(schema, subscription) + .then(results => { + forAwaitEach( + results as AsyncIterable, + (result: ExecutionResult) => { + expect(result).to.have.property('data'); + expect(result.data).to.deep.equal(mockNotification); + if (!notificationCnt++) { + done(); + } + }, + ).catch(done); + }) + .then(() => + subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification), + ) + .catch(done); }); it('should work without triggering multiple times per notification', done => { const mockNotification = { notifications: { - text: 'Hello world' - } + text: 'Hello world', + }, }; const subscription = parse(` @@ -112,29 +124,41 @@ describe('remote subscriptions', () => { let notificationCnt = 0; const sub1 = subscribe(schema, subscription).then(results => { - forAwaitEach(results as AsyncIterable, (result: ExecutionResult) => { - expect(result).to.have.property('data'); - expect(result.data).to.deep.equal(mockNotification); - notificationCnt++; - }).catch(done); + forAwaitEach( + results as AsyncIterable, + (result: ExecutionResult) => { + expect(result).to.have.property('data'); + expect(result.data).to.deep.equal(mockNotification); + notificationCnt++; + }, + ).catch(done); }); const sub2 = subscribe(schema, subscription).then(results => { - forAwaitEach(results as AsyncIterable, (result: ExecutionResult) => { - expect(result).to.have.property('data'); - expect(result.data).to.deep.equal(mockNotification); - }).catch(done); + forAwaitEach( + results as AsyncIterable, + (result: ExecutionResult) => { + expect(result).to.have.property('data'); + expect(result.data).to.deep.equal(mockNotification); + }, + ).catch(done); }); - Promise.all([sub1, sub2]).then(() => { - subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification).catch(done); - subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification).catch(done); - - setTimeout(() => { - expect(notificationCnt).to.eq(2); - done(); - }, 0); - }).catch(done); + Promise.all([sub1, sub2]) + .then(() => { + subscriptionPubSub + .publish(subscriptionPubSubTrigger, mockNotification) + .catch(done); + subscriptionPubSub + .publish(subscriptionPubSubTrigger, mockNotification) + .catch(done); + + setTimeout(() => { + expect(notificationCnt).to.eq(2); + done(); + }, 0); + }) + .catch(done); }); }); @@ -159,7 +183,7 @@ describe('respects buildSchema options', () => { it('with comment descriptions', () => { const remoteSchema = makeRemoteExecutableSchema({ schema, - buildSchemaOptions: { commentDescriptions: true } + buildSchemaOptions: { commentDescriptions: true }, }); const field = remoteSchema.getQueryType().getFields()['custom']; diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index dad277839bc..1138b286bdc 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -17,10 +17,7 @@ import { import mergeSchemas from '../stitching/mergeSchemas'; import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import { - IResolvers, - SubschemaConfig, -} from '../Interfaces'; +import { IResolvers, SubschemaConfig } from '../Interfaces'; import { delegateToSchema } from '../stitching'; import { cloneSchema } from '../utils'; import { getResolversFromSchema } from '../utils/getResolversFromSchema'; @@ -37,7 +34,6 @@ import { subscriptionPubSubTrigger, } from './testingSchemas'; - // eslint-disable-next-line @typescript-eslint/no-unused-vars const removeLocations = ({ locations, ...rest }: any): any => ({ ...rest }); @@ -68,7 +64,7 @@ const testCombinations = [ resolvers: getResolversFromSchema(localPropertySchema), }), product: cloneSchema(localProductSchema), - } + }, ]; const scalarTest = ` @@ -108,13 +104,15 @@ const scalarSchema = makeExecutableSchema({ Query: { testingScalar(_parent, args) { return { - value: args.input[0] === '_' ? args.input : null + value: args.input[0] === '_' ? args.input : null, }; }, listTestingScalar(_parent, args) { - return [{ - value: args.input[0] === '_' ? args.input : null - }]; + return [ + { + value: args.input[0] === '_' ? args.input : null, + }, + ]; }, }, }, @@ -171,7 +169,7 @@ const enumSchema = makeExecutableSchema({ TEST: 1, }, UnionWithEnum: { - __resolveType: () => 'EnumWrapper' + __resolveType: () => 'EnumWrapper', }, Query: { color(_parent, args) { @@ -186,13 +184,13 @@ const enumSchema = makeExecutableSchema({ wrappedEnum() { return { color: '#EA3232', - numericEnum: 1 + numericEnum: 1, }; }, unionWithEnum() { return { color: '#EA3232', - numericEnum: 1 + numericEnum: 1, }; }, }, @@ -652,11 +650,13 @@ testCombinations.forEach(combination => { expect(scalarResult).to.deep.equal({ data: { testingScalar: { - value: 'test' + value: 'test', }, - listTestingScalar: [{ - value: 'test' - }] + listTestingScalar: [ + { + value: 'test', + }, + ], }, }); expect(mergedResult).to.deep.equal(scalarResult); @@ -884,7 +884,12 @@ bookingById(id: "b1") { }, ).catch(done); }) - .then(() => subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification)) + .then(() => + subscriptionPubSub.publish( + subscriptionPubSubTrigger, + mockNotification, + ), + ) .catch(done); }); @@ -942,7 +947,12 @@ bookingById(id: "b1") { }, ).catch(done); }) - .then(() => subscriptionPubSub.publish(subscriptionPubSubTrigger, mockNotification)) + .then(() => + subscriptionPubSub.publish( + subscriptionPubSubTrigger, + mockNotification, + ), + ) .catch(done); }); @@ -2306,7 +2316,8 @@ fragment BookingFragment on Booking { ...bookingResult.data, }); expect(mergedResult.errors.map(removeLocations)).to.deep.equal( - propertyResult.errors.map(removeLocations)); + propertyResult.errors.map(removeLocations), + ); const mergedResult2 = await graphql( mergedSchema, @@ -2366,19 +2377,19 @@ fragment BookingFragment on Booking { ], error: null, errorAlias: null, - } + }, }); expect(result.errors.map(removeLocations)).to.deep.equal([ { extensions: { - code: 'SOME_CUSTOM_CODE' + code: 'SOME_CUSTOM_CODE', }, message: 'Property.error error', path: ['propertyById', 'error'], }, { extensions: { - code: 'SOME_CUSTOM_CODE' + code: 'SOME_CUSTOM_CODE', }, message: 'Property.error error', path: ['propertyById', 'errorAlias'], @@ -2406,13 +2417,13 @@ fragment BookingFragment on Booking { { message: 'Booking.error error', path: ['propertyById', 'bookings', 2, 'bookingErrorAlias'], - } + }, ]); }); it( 'should preserve custom error extensions from the original schema, ' + - 'when merging schemas', + 'when merging schemas', async () => { const propertyQuery = ` query { @@ -2427,19 +2438,16 @@ fragment BookingFragment on Booking { propertyQuery, ); - const mergedResult = await graphql( - mergedSchema, - propertyQuery, - ); + const mergedResult = await graphql(mergedSchema, propertyQuery); - [propertyResult, mergedResult].forEach((result) => { + [propertyResult, mergedResult].forEach(result => { expect(result.errors).to.not.equal(undefined); expect(result.errors.length > 0).to.equal(true); const error = result.errors[0]; expect(error.extensions).to.not.equal(undefined); expect(error.extensions.code).to.equal('SOME_CUSTOM_CODE'); }); - } + }, ); }); @@ -2871,19 +2879,16 @@ fragment BookingFragment on Booking { const resolvers = { Query: { - book: () => ({ category: 'Test' }) - } + book: () => ({ category: 'Test' }), + }, }; schema = mergeSchemas({ schemas: [schema], - resolvers + resolvers, }); - const result = await graphql( - schema, - '{ book { cat: category } }', - ); + const result = await graphql(schema, '{ book { cat: category } }'); expect(result.data.book.cat).to.equal('Test'); }); @@ -2908,18 +2913,24 @@ fragment BookingFragment on Booking { const resolvers = { Query: { - book: () => ({ category: 'Test' }) - } + book: () => ({ category: 'Test' }), + }, }; const schema = mergeSchemas({ schemas: [propertySchema, typeDefs], - resolvers + resolvers, }); const deprecatedUsages = findDeprecatedUsages(schema, parse(query)); expect(deprecatedUsages.length).to.equal(1); - expect(deprecatedUsages.find(error => (error.message.match(/deprecated/g) != null) && (error.message.match(/yolo/g) != null))).to.not.equal(undefined); + expect( + deprecatedUsages.find( + error => + error.message.match(/deprecated/g) != null && + error.message.match(/yolo/g) != null, + ), + ).to.not.equal(undefined); }); }); }); diff --git a/src/test/testMocking.ts b/src/test/testMocking.ts index 4d25f611f36..e5ab9f295bf 100644 --- a/src/test/testMocking.ts +++ b/src/test/testMocking.ts @@ -1,5 +1,10 @@ import { expect } from 'chai'; -import { graphql, GraphQLResolveInfo, GraphQLSchema, GraphQLFieldResolver } from 'graphql'; +import { + graphql, + GraphQLResolveInfo, + GraphQLSchema, + GraphQLFieldResolver, +} from 'graphql'; import { addMocksToSchema, MockList, mockServer } from '../mock'; import { @@ -85,21 +90,22 @@ describe('Mock', () => { }; it('throws an error if you forget to pass schema', () => { - expect(() => addMocksToSchema({})).to.throw( - 'Must provide schema to mock', - ); + expect(() => addMocksToSchema({})).to.throw('Must provide schema to mock'); }); it('throws an error if the property "schema" on the first argument is not of type GraphQLSchema', () => { - expect(() => addMocksToSchema({ schema: {} as unknown as GraphQLSchema })).to.throw( - 'Value at "schema" must be of type GraphQLSchema', - ); + expect(() => + addMocksToSchema({ schema: ({} as unknown) as GraphQLSchema }), + ).to.throw('Value at "schema" must be of type GraphQLSchema'); }); it('throws an error if second argument is not a Map', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); expect(() => - addMocksToSchema({ schema: jsSchema, mocks: ['a'] as unknown as IMocks }), + addMocksToSchema({ + schema: jsSchema, + mocks: (['a'] as unknown) as IMocks, + }), ).to.throw('mocks must be of type Object'); }); @@ -107,7 +113,10 @@ describe('Mock', () => { const jsSchema = buildSchemaFromTypeDefinitions(shorthand); const mockMap = { Int: 55 }; expect(() => - addMocksToSchema({ schema: jsSchema, mocks: mockMap as unknown as IMocks }), + addMocksToSchema({ + schema: jsSchema, + mocks: (mockMap as unknown) as IMocks, + }), ).to.throw('mockFunctionMap[Int] must be a function'); }); @@ -1186,9 +1195,10 @@ describe('Mock', () => { }); it('throws an error if the second argument to MockList is not a function', () => { - expect(() => new MockList(5, 'abc' as unknown as GraphQLFieldResolver)).to.throw( - 'Second argument to MockList must be a function or undefined', - ); + expect( + () => + new MockList(5, ('abc' as unknown) as GraphQLFieldResolver), + ).to.throw('Second argument to MockList must be a function or undefined'); }); it('lets you nest MockList in MockList', () => { @@ -1204,7 +1214,10 @@ describe('Mock', () => { returnListOfListOfInt }`; const expected = { - returnListOfListOfInt: [[12, 12, 12], [12, 12, 12]], + returnListOfListOfInt: [ + [12, 12, 12], + [12, 12, 12], + ], }; return graphql(jsSchema, testQuery).then(res => { expect(res.data).to.deep.equal(expected); diff --git a/src/test/testResolution.ts b/src/test/testResolution.ts index e3359f3b5d0..3e3dc1af6df 100644 --- a/src/test/testResolution.ts +++ b/src/test/testResolution.ts @@ -95,7 +95,9 @@ describe('Resolve', () => { if (result.errors != null) { return done( new Error( - `Unexpected errors in GraphQL result: ${JSON.stringify(result.errors)}`, + `Unexpected errors in GraphQL result: ${JSON.stringify( + result.errors, + )}`, ), ); } @@ -120,9 +122,11 @@ describe('Resolve', () => { done(new Error('Too many subscription fired')); }, ).catch(done); - }).then(() => - pubsub.publish('printRootChannel', { printRoot: subscriptionRoot }) - ).catch(done); + }) + .then(() => + pubsub.publish('printRootChannel', { printRoot: subscriptionRoot }), + ) + .catch(done); }); firstSubsTriggered @@ -153,7 +157,9 @@ describe('Resolve', () => { .then(({ data: mutationData }) => { assert.equal(schemaLevelResolverCalls, 3); assert.deepEqual(mutationData, { printRoot: mutationRoot }); - return pubsub.publish('printRootChannel', { printRoot: subscriptionRoot2 }); + return pubsub.publish('printRootChannel', { + printRoot: subscriptionRoot2, + }); }) .catch(done); }); diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 3ea69ec74c9..2794a8ce077 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -90,12 +90,14 @@ const testSchema = ` const testResolvers = { __schema: () => ({ stuff: 'stuff', species: 'ROOT' }), RootQuery: { - usecontext: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.usecontext, + usecontext: (_r: any, _a: { [key: string]: any }, ctx: any) => + ctx.usecontext, useTestConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.connectors.TestConnector.get(), useContextConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.connectors.ContextConnector.get(), - species: (root: any, { name }: { name: string }) => root.species as string + name, + species: (root: any, { name }: { name: string }) => + (root.species as string) + name, }, }; class TestConnector { @@ -139,13 +141,19 @@ describe('generating schema from shorthand', () => { it('throws an error if typeDefinitionNodes is neither string nor array nor schema AST', () => { expect(() => - makeExecutableSchema({ typeDefs: {} as unknown as ITypeDefinitions, resolvers: {} }), + makeExecutableSchema({ + typeDefs: ({} as unknown) as ITypeDefinitions, + resolvers: {}, + }), ).to.throw('typeDefs must be a string, array or schema AST, got object'); }); it('throws an error if typeDefinitionNode array contains not only functions and strings', () => { expect(() => - makeExecutableSchema({ typeDefs: [17] as unknown as ITypeDefinitions, resolvers: {} }), + makeExecutableSchema({ + typeDefs: ([17] as unknown) as ITypeDefinitions, + resolvers: {}, + }), ).to.throw( 'typeDef array must contain only strings and functions, got number', ); @@ -155,9 +163,11 @@ describe('generating schema from shorthand', () => { const options = { typeDefs: 'blah', resolvers: {}, - resolverValidationOptions: 'string' as unknown as IResolverValidationOptions, + resolverValidationOptions: ('string' as unknown) as IResolverValidationOptions, }; - expect(() => makeExecutableSchema(options)).to.throw('Expected `resolverValidationOptions` to be an object'); + expect(() => makeExecutableSchema(options)).to.throw( + 'Expected `resolverValidationOptions` to be an object', + ); }); it('can generate a schema', () => { @@ -181,7 +191,7 @@ describe('generating schema from shorthand', () => { const resolve = { RootQuery: { // eslint-disable-next-line @typescript-eslint/no-empty-function - species() {} + species() {}, }, }; @@ -798,7 +808,7 @@ describe('generating schema from shorthand', () => { }), Query: { foo: () => ({ aField: true }), - } + }, }; const jsSchema = makeExecutableSchema({ typeDefs: shorthand, @@ -818,8 +828,8 @@ describe('generating schema from shorthand', () => { resolvers: { Boolean: { serialize: originalSerialize, - } - } + }, + }, }); }); @@ -857,7 +867,7 @@ describe('generating schema from shorthand', () => { default: return null; } - } + }, }), Query: { testIn(_: any, { input }: any) { @@ -866,18 +876,23 @@ describe('generating schema from shorthand', () => { }, test() { return 42; - } - } + }, + }, }; - const walkedSchema = visitSchema(makeExecutableSchema({ - typeDefs: shorthand, - resolvers: resolveFunctions, - }), { - [VisitSchemaKind.ENUM_TYPE](type: GraphQLEnumType) { - return type; - } - }); - expect(walkedSchema.getType('Test')).to.be.an.instanceof(GraphQLScalarType); + const walkedSchema = visitSchema( + makeExecutableSchema({ + typeDefs: shorthand, + resolvers: resolveFunctions, + }), + { + [VisitSchemaKind.ENUM_TYPE](type: GraphQLEnumType) { + return type; + }, + }, + ); + expect(walkedSchema.getType('Test')).to.be.an.instanceof( + GraphQLScalarType, + ); expect(walkedSchema.getType('Test')) .to.have.property('description') .that.equals('Test resolver'); @@ -887,10 +902,12 @@ describe('generating schema from shorthand', () => { testIn(input: 1) }`; const resultPromise = graphql(walkedSchema, testQuery); - return resultPromise.then(result => expect(result.data).to.deep.equal({ - test: 'scalar:42', - testIn: 'scalar:1' - })); + return resultPromise.then(result => + expect(result.data).to.deep.equal({ + test: 'scalar:42', + testIn: 'scalar:1', + }), + ); }); it('should support custom scalar usage on client-side query execution', () => { @@ -946,7 +963,7 @@ describe('generating schema from shorthand', () => { }); it('should work with an Odd custom scalar type', () => { - const oddValue = (value: number) => value % 2 === 1 ? value : null; + const oddValue = (value: number) => (value % 2 === 1 ? value : null); const OddType = new GraphQLScalarType({ name: 'Odd', @@ -1332,7 +1349,7 @@ describe('generating schema from shorthand', () => { Color: { RED: 'override', }, - } + }, }); const resultPromise = graphql(jsSchema, testQuery); @@ -1544,7 +1561,7 @@ describe('generating schema from shorthand', () => { expect(() => makeExecutableSchema({ typeDefs: short, resolvers: rf }), - ).to.throw('Searchable was defined in resolvers, but it\'s not an object'); + ).to.throw("Searchable was defined in resolvers, but it's not an object"); expect(() => makeExecutableSchema({ @@ -2094,13 +2111,17 @@ describe('providing useful errors from resolvers', () => { describe('Add error logging to schema', () => { it('throws an error if no logger is provided', () => { assert.throw( - () => addErrorLoggingToSchema({} as unknown as GraphQLSchema), + () => addErrorLoggingToSchema(({} as unknown) as GraphQLSchema), 'Must provide a logger', ); }); it('throws an error if logger.log is not a function', () => { assert.throw( - () => addErrorLoggingToSchema({} as unknown as GraphQLSchema, { log: '1' } as unknown as ILogger), + () => + addErrorLoggingToSchema( + ({} as unknown) as GraphQLSchema, + ({ log: '1' } as unknown) as ILogger, + ), 'Logger.log must be a function', ); }); @@ -2145,10 +2166,13 @@ describe('Attaching connectors to schema', () => { ctx.usecontext, useTestConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.connectors.TestConnector.get(), - useContextConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => - ctx.connectors.ContextConnector.get(), + useContextConnector: ( + _r: any, + _a: { [key: string]: any }, + ctx: any, + ) => ctx.connectors.ContextConnector.get(), species: (root: any, { name }: { name: string }) => - root.species as string + name, + (root.species as string) + name, }, }; const jsSchema = makeExecutableSchema({ @@ -2184,10 +2208,13 @@ describe('Attaching connectors to schema', () => { ctx.usecontext, useTestConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => ctx.connectors.TestConnector.get(), - useContextConnector: (_r: any, _a: { [key: string]: any }, ctx: any) => - ctx.connectors.ContextConnector.get(), + useContextConnector: ( + _r: any, + _a: { [key: string]: any }, + ctx: any, + ) => ctx.connectors.ContextConnector.get(), species: (root: any, { name }: { name: string }) => - root.species as string + name, + (root.species as string) + name, }, }; const jsSchema = makeExecutableSchema({ @@ -2435,9 +2462,9 @@ describe('Attaching connectors to schema', () => { typeDefs: testSchema, resolvers: testResolvers, }); - return expect(() => - attachConnectorsToContext(jsSchema, 'a'), - ).to.throw('Expected connectors to be of type object, got string'); + return expect(() => attachConnectorsToContext(jsSchema, 'a')).to.throw( + 'Expected connectors to be of type object, got string', + ); }); }); @@ -2482,12 +2509,10 @@ describe('chainResolvers', () => { const r3 = (root: any) => root['name']; const rChained = chainResolvers([r1, undefined, r3]); // faking the resolve info here. - const info: GraphQLResolveInfo = { + const info: GraphQLResolveInfo = ({ fieldName: 'person', - } as unknown as GraphQLResolveInfo; - expect( - rChained(0, { name: 'tony' }, null, info), - ).to.equals('tony'); + } as unknown) as GraphQLResolveInfo; + expect(rChained(0, { name: 'tony' }, null, info)).to.equals('tony'); }); }); @@ -2585,9 +2610,11 @@ describe('attachDirectiveResolvers on field', () => { typeDefs: testSchema, resolvers: testResolvers, }); - expect(() => attachDirectiveResolvers(jsSchema, [1] as unknown as IDirectiveResolvers)).to.throw( - 'Expected directiveResolvers to be of type object, got Array', - ); + expect(() => + attachDirectiveResolvers(jsSchema, ([ + 1, + ] as unknown) as IDirectiveResolvers), + ).to.throw('Expected directiveResolvers to be of type object, got Array'); }); it('throws error if directiveResolvers argument is not an object', () => { @@ -2596,7 +2623,10 @@ describe('attachDirectiveResolvers on field', () => { resolvers: testResolvers, }); return expect(() => - attachDirectiveResolvers(jsSchema, 'a' as unknown as IDirectiveResolvers), + attachDirectiveResolvers( + jsSchema, + ('a' as unknown) as IDirectiveResolvers, + ), ).to.throw('Expected directiveResolvers to be of type object, got string'); }); @@ -2772,7 +2802,7 @@ describe('can specify lexical parser options', () => { // to hoist the parsed variables into queries, see https://github.com/graphql/graphql-js/pull/1141 // and so this really has nothing to do with schema creation or execution. it("can use 'experimentalFragmentVariables' option", async () => { - const typeDefs = ` + const typeDefs = ` type Query { hello(phrase: String): String } @@ -2780,7 +2810,7 @@ describe('can specify lexical parser options', () => { const resolvers = { Query: { - hello: (_root: any, args: any) => `hello ${args.phrase as string}` + hello: (_root: any, args: any) => `hello ${args.phrase as string}`, }, }; @@ -2812,19 +2842,21 @@ describe('can specify lexical parser options', () => { return { kind: Kind.DOCUMENT, definitions: parsedQuery.definitions.map(def => ({ - ...def, - variableDefinitions: variableDefs, - })), + ...def, + variableDefinitions: variableDefs, + })), }; }; const hoistedQuery = hoist(parsedQuery); const result = await execute(jsSchema, hoistedQuery); - expect(result.data).to.deep.equal({ hello: 'hello world', }); + expect(result.data).to.deep.equal({ hello: 'hello world' }); - const result2 = await execute(jsSchema, hoistedQuery, null, null, { phrase: 'world again!' }); - expect(result2.data).to.deep.equal({ hello: 'hello world again!', }); + const result2 = await execute(jsSchema, hoistedQuery, null, null, { + phrase: 'world again!', + }); + expect(result2.data).to.deep.equal({ hello: 'hello world again!' }); }); }); @@ -3068,7 +3100,7 @@ describe('unions', () => { } catch (error) { assert.equal( error.message, - 'Type "Displayable" is missing a "__resolveType" resolver. Pass false into "resolverValidationOptions.requireResolversForResolveType" to disable this error.' , + 'Type "Displayable" is missing a "__resolveType" resolver. Pass false into "resolverValidationOptions.requireResolversForResolveType" to disable this error.', ); return; } diff --git a/src/test/testStitchingFromSubschemas.ts b/src/test/testStitchingFromSubschemas.ts index 7f49a29e208..10c4c7d5070 100644 --- a/src/test/testStitchingFromSubschemas.ts +++ b/src/test/testStitchingFromSubschemas.ts @@ -14,11 +14,7 @@ import { expect } from 'chai'; import { graphql } from 'graphql'; -import { - delegateToSchema, - mergeSchemas, - addMocksToSchema, -} from '../index'; +import { delegateToSchema, mergeSchemas, addMocksToSchema } from '../index'; const chirpTypeDefs = ` type Chirp { @@ -49,32 +45,33 @@ const chirpSchema = mergeSchemas({ chirpById(id: ID!): Chirp chirpsByAuthorId(authorId: ID!): [Chirp] } - ` + `, ], resolvers: { Chirp: { - author: (chirp, _args, context, info) => delegateToSchema({ - schema: getSchema('authorSchema'), - operation: 'query', - fieldName: 'userById', - args: { - id: chirp.authorId - }, - context, - info - }) - } - } + author: (chirp, _args, context, info) => + delegateToSchema({ + schema: getSchema('authorSchema'), + operation: 'query', + fieldName: 'userById', + args: { + id: chirp.authorId, + }, + context, + info, + }), + }, + }, }); addMocksToSchema({ schema: chirpSchema, mocks: { Chirp: () => ({ - authorId: '1' + authorId: '1', }), }, - preserveResolvers: true + preserveResolvers: true, }); const authorSchema = mergeSchemas({ @@ -85,39 +82,40 @@ const authorSchema = mergeSchemas({ type Query { userById(id: ID!): User } - ` + `, ], resolvers: { User: { - chirps: (user, _args, context, info) => delegateToSchema({ + chirps: (user, _args, context, info) => + delegateToSchema({ schema: getSchema('chirpSchema'), operation: 'query', fieldName: 'chirpsByAuthorId', args: { - authorId: user.id + authorId: user.id, }, context, - info - }) - } - } + info, + }), + }, + }, }); addMocksToSchema({ schema: authorSchema, mocks: { User: () => ({ - id: '1' + id: '1', }), }, - preserveResolvers: true + preserveResolvers: true, }); schemas['chirpSchema'] = chirpSchema; schemas['authorSchema'] = authorSchema; const mergedSchema = mergeSchemas({ - schemas: Object.keys(schemas).map(schemaName => schemas[schemaName]) + schemas: Object.keys(schemas).map(schemaName => schemas[schemaName]), }); describe('merging without specifying fragments', () => { diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 6e38bdaa9b1..f6a9b8f3549 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -11,10 +11,7 @@ import { } from 'graphql'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import { - delegateToSchema, - defaultMergedResolver -} from '../stitching'; +import { delegateToSchema, defaultMergedResolver } from '../stitching'; import { transformSchema, RenameTypes, @@ -26,14 +23,10 @@ import { TransformQuery, AddReplacementFragments, } from '../transforms'; -import { - concatInlineFragments, - parseFragmentToInlineFragment -} from '../utils'; +import { concatInlineFragments, parseFragmentToInlineFragment } from '../utils'; import { propertySchema, bookingSchema } from './testingSchemas'; - describe('transforms', () => { describe('base transform function', () => { let schema: GraphQLSchema; @@ -61,7 +54,7 @@ describe('transforms', () => { Query: { testingScalar(_parent, args) { return { - value: args.input[0] === '_' ? args.input : null + value: args.input[0] === '_' ? args.input : null, }; }, }, @@ -243,8 +236,8 @@ describe('transforms', () => { const filteredQuery = filter.transformRequest({ document: query, variables: { - id: 'c1' - } + id: 'c1', + }, }); const expected = parse(` @@ -274,8 +267,8 @@ describe('transforms', () => { document: query, variables: { id: 'c1', - limit: 10 - } + limit: 10, + }, }); const expected = parse(` @@ -304,8 +297,8 @@ describe('transforms', () => { const filteredQuery = filter.transformRequest({ document: query, variables: { - id: 'b1' - } + id: 'b1', + }, }); const expected = parse(` @@ -379,7 +372,7 @@ describe('transforms', () => { expect(result.errors).to.not.equal(undefined); expect(result.errors.length).to.equal(1); expect(result.errors[0].message).to.equal( - 'Cannot query field "customer" on type "Booking".' + 'Cannot query field "customer" on type "Booking".', ); }); }); @@ -655,8 +648,8 @@ describe('transforms', () => { u1: { id: 'user1', addressStreetAddress: 'Windy Shore 21 A 7', - addressZip: '12345' - } + addressZip: '12345', + }, }; subschema = makeExecutableSchema({ typeDefs: ` @@ -675,7 +668,7 @@ describe('transforms', () => { userById(_parent, { id }) { return data[id]; }, - } + }, }, }); schema = makeExecutableSchema({ @@ -715,8 +708,10 @@ describe('transforms', () => { selections: subtree.selections.map(selection => { // just append fragments, not interesting for this // test - if (selection.kind === Kind.INLINE_FRAGMENT || - selection.kind === Kind.FRAGMENT_SPREAD) { + if ( + selection.kind === Kind.INLINE_FRAGMENT || + selection.kind === Kind.FRAGMENT_SPREAD + ) { return selection; } // prepend `address` to name and camelCase @@ -725,27 +720,28 @@ describe('transforms', () => { kind: Kind.FIELD, name: { kind: Kind.NAME, - value: 'address' + + value: + 'address' + oldFieldName.charAt(0).toUpperCase() + - oldFieldName.slice(1) - } + oldFieldName.slice(1), + }, }; - }) + }), }; return newSelectionSet; }, // how to process the data result at path result => ({ streetAddress: result.addressStreetAddress, - zip: result.addressZip - }) + zip: result.addressZip, + }), ), // Wrap a second level field new WrapQuery( ['userById', 'zip'], (subtree: SelectionSetNode) => subtree, - result => result - ) + result => result, + ), ], }); }, @@ -824,12 +820,12 @@ describe('transforms', () => { User: { errorTest: () => { throw new Error('Test Error!'); - } + }, }, Address: { errorTest: () => { throw new Error('Test Error!'); - } + }, }, Query: { userById(_parent, { id }) { @@ -868,17 +864,19 @@ describe('transforms', () => { path: ['userById'], queryTransformer: (subtree: SelectionSetNode) => ({ kind: Kind.SELECTION_SET, - selections: [{ - // we create a wrapping AST Field - kind: Kind.FIELD, - name: { - kind: Kind.NAME, - // that field is `address` - value: 'address', + selections: [ + { + // we create a wrapping AST Field + kind: Kind.FIELD, + name: { + kind: Kind.NAME, + // that field is `address` + value: 'address', + }, + // Inside the field selection + selectionSet: subtree, }, - // Inside the field selection - selectionSet: subtree, - }], + ], }), // how to process the data result at path resultTransformer: result => result?.address, @@ -900,14 +898,16 @@ describe('transforms', () => { path: ['userById'], queryTransformer: (subtree: SelectionSetNode) => ({ kind: Kind.SELECTION_SET, - selections: [{ - kind: Kind.FIELD, - name: { - kind: Kind.NAME, - value: 'errorTest', + selections: [ + { + kind: Kind.FIELD, + name: { + kind: Kind.NAME, + value: 'errorTest', + }, + selectionSet: subtree, }, - selectionSet: subtree, - }], + ], }), resultTransformer: result => result?.address, errorPathTransformer: path => path.slice(1), @@ -975,12 +975,9 @@ describe('transforms', () => { }, ], message: 'Test Error!', - path: [ - 'addressByUser', - 'errorTest', - ], - } - ] + path: ['addressByUser', 'errorTest'], + }, + ], }); }); @@ -1009,11 +1006,9 @@ describe('transforms', () => { { locations: [], message: 'Test Error!', - path: [ - 'errorTest', - ], - } - ] + path: ['errorTest'], + }, + ], }); }); }); @@ -1183,14 +1178,15 @@ describe('replaces field with processed fragment node', () => { transforms: [ new AddReplacementFragments(subschema, { User: { - fullname: concatInlineFragments( - 'User', - [ - parseFragmentToInlineFragment('fragment UserName on User { name }'), - parseFragmentToInlineFragment('fragment UserSurname on User { surname }'), - ], - ), - } + fullname: concatInlineFragments('User', [ + parseFragmentToInlineFragment( + 'fragment UserName on User { name }', + ), + parseFragmentToInlineFragment( + 'fragment UserSurname on User { surname }', + ), + ]), + }, }), ], }); diff --git a/src/test/testTypeMerging.ts b/src/test/testTypeMerging.ts index 156eb6ec238..3efa7ffb1c0 100644 --- a/src/test/testTypeMerging.ts +++ b/src/test/testTypeMerging.ts @@ -4,11 +4,7 @@ import { expect } from 'chai'; import { graphql } from 'graphql'; -import { - mergeSchemas, - addMocksToSchema, - makeExecutableSchema, -} from '../index'; +import { mergeSchemas, addMocksToSchema, makeExecutableSchema } from '../index'; const chirpSchema = makeExecutableSchema({ typeDefs: ` @@ -45,25 +41,28 @@ const authorSchema = makeExecutableSchema({ addMocksToSchema({ schema: authorSchema }); const mergedSchema = mergeSchemas({ - subschemas: [{ - schema: chirpSchema, - merge: { - User: { - fieldName: 'userById', - args: originalResult => ({ id: originalResult.id }), - selectionSet: '{ id }', - } + subschemas: [ + { + schema: chirpSchema, + merge: { + User: { + fieldName: 'userById', + args: originalResult => ({ id: originalResult.id }), + selectionSet: '{ id }', + }, + }, }, - }, { - schema: authorSchema, - merge: { - User: { - fieldName: 'userById', - args: originalResult => ({ id: originalResult.id }), - selectionSet: '{ id }', - } + { + schema: authorSchema, + merge: { + User: { + fieldName: 'userById', + args: originalResult => ({ id: originalResult.id }), + selectionSet: '{ id }', + }, + }, }, - }], + ], mergeTypes: true, }); diff --git a/src/test/testUpload.ts b/src/test/testUpload.ts index e3046c801db..81cdef01219 100644 --- a/src/test/testUpload.ts +++ b/src/test/testUpload.ts @@ -10,7 +10,7 @@ import FormData from 'form-data'; import fetch from 'node-fetch'; import { buildSchema } from 'graphql'; -import { mergeSchemas} from '../stitching'; +import { mergeSchemas } from '../stitching'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { createServerHttpLink } from '../links'; import { SubschemaConfig } from '../Interfaces'; @@ -26,7 +26,7 @@ function streamToString(stream: Readable) { function startServer(e: Express): Promise { return new Promise((resolve, reject) => { - e.listen(undefined, 'localhost', function (error) { + e.listen(undefined, 'localhost', function(error) { if (error) { reject(error); } else { @@ -39,12 +39,15 @@ function startServer(e: Express): Promise { function testGraphqlMultipartRequest(query: string, port: number) { const body = new FormData(); - body.append('operations', JSON.stringify({ - query, - variables: { - file: null, - }, - })); + body.append( + 'operations', + JSON.stringify({ + query, + variables: { + file: null, + }, + }), + ); body.append('map', '{ "1": ["variables.file"] }'); body.append('1', 'abc', { filename: __filename }); @@ -70,7 +73,7 @@ describe('graphql upload', () => { const stream = createReadStream(); const s = await streamToString(stream); return s; - } + }, }, Upload: GraphQLUpload, }, diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 12107a6bede..3f9970296b7 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -15,11 +15,13 @@ describe('heal', () => { type Query { someQuery: WillBeEmptyObject } - ` + `, }); const originalTypeMap = schema.getTypeMap(); - const config = originalTypeMap['WillBeEmptyObject'].toConfig() as GraphQLObjectTypeConfig; + const config = originalTypeMap[ + 'WillBeEmptyObject' + ].toConfig() as GraphQLObjectTypeConfig; originalTypeMap['WillBeEmptyObject'] = new GraphQLObjectType({ ...config, fields: {}, diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index aee75b331ea..dd9a7cb0a7c 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -19,11 +19,7 @@ import { import { forAwaitEach } from 'iterall'; import introspectSchema from '../stitching/introspectSchema'; -import { - IResolvers, - Fetcher, - SubschemaConfig, -} from '../Interfaces'; +import { IResolvers, Fetcher, SubschemaConfig } from '../Interfaces'; import { makeExecutableSchema } from '../makeExecutableSchema'; export type Location = { @@ -91,7 +87,7 @@ export const sampleData: { name: 'Super great hotel', location: { name: 'Helsinki', - coordinates: '60.1698° N, 24.9383° E' + coordinates: '60.1698° N, 24.9383° E', }, }, p2: { @@ -99,7 +95,7 @@ export const sampleData: { name: 'Another great hotel', location: { name: 'San Francisco', - coordinates: '37.7749° N, 122.4194° W' + coordinates: '37.7749° N, 122.4194° W', }, }, p3: { @@ -107,7 +103,7 @@ export const sampleData: { name: 'BedBugs - The Affordable Hostel', location: { name: 'Helsinki', - coordinates: '60.1699° N, 24.9384° E' + coordinates: '60.1699° N, 24.9384° E', }, }, }, @@ -340,25 +336,29 @@ const propertyResolvers: IResolvers = { }, interfaceTest(_root, { kind }) { - return (kind === 'ONE') ? { - kind: 'ONE', - testString: 'test', - foo: 'foo', - } : { - kind: 'TWO', - testString: 'test', - bar: 'bar', - }; + return kind === 'ONE' + ? { + kind: 'ONE', + testString: 'test', + foo: 'foo', + } + : { + kind: 'TWO', + testString: 'test', + bar: 'bar', + }; }, unionTest(_root, { output }) { - return (output === 'Interface') ? { - kind: 'ONE', - testString: 'test', - foo: 'foo', - } : { - someField: 'Bar', - }; + return output === 'Interface' + ? { + kind: 'ONE', + testString: 'test', + foo: 'foo', + } + : { + someField: 'Bar', + }; }, errorTest() { @@ -378,13 +378,13 @@ const propertyResolvers: IResolvers = { TestInterface: { __resolveType(obj: any) { - return (obj.kind === 'ONE') ? 'TestImpl1' : 'TestImpl2'; + return obj.kind === 'ONE' ? 'TestImpl1' : 'TestImpl2'; }, }, TestUnion: { __resolveType(obj: any) { - return (obj.kind === 'ONE') ? 'TestImpl1' : 'UnionImpl'; + return obj.kind === 'ONE' ? 'TestImpl1' : 'UnionImpl'; }, }, @@ -443,7 +443,7 @@ const productResolvers: IResolvers = { Product: { __resolveType(obj: any) { - return (obj.type === 'simple') ? 'SimpleProduct' : 'DownloadableProduct'; + return obj.type === 'simple' ? 'SimpleProduct' : 'DownloadableProduct'; }, }, }; @@ -631,8 +631,8 @@ const subscriptionResolvers: IResolvers = { Notification: { throwError: () => { throw new Error('subscription field error'); - } - } + }, + }, }; export const propertySchema: GraphQLSchema = makeExecutableSchema({ @@ -668,50 +668,63 @@ const hasSubscriptionOperation = ({ query }: { query: any }): boolean => { }; function makeLinkFromSchema(schema: GraphQLSchema) { - return new ApolloLink(operation => new Observable(observer => { - const { query, operationName, variables } = operation; - const { graphqlContext } = operation.getContext(); - if (!hasSubscriptionOperation(operation)) { - graphql( - schema, - print(query), - null, - graphqlContext, - variables, - operationName, - ).then(result => { - observer.next(result); - observer.complete(); - }).catch(err => { - observer.error(err); - }); - } else { - subscribe( - schema, - query, - null, - graphqlContext, - variables, - operationName, - ).then(results => { - if (typeof (results as AsyncIterator).next === 'function') { - forAwaitEach( - (results as AsyncIterable), - result => observer.next(result) - ).then(() => observer.complete()).catch(err => observer.error(err)) - } else { - observer.next(results as LinkExecutionResult); - observer.complete(); - } - }).catch(err => { - observer.error(err); - }); - } - })); + return new ApolloLink( + operation => + new Observable(observer => { + const { query, operationName, variables } = operation; + const { graphqlContext } = operation.getContext(); + if (!hasSubscriptionOperation(operation)) { + graphql( + schema, + print(query), + null, + graphqlContext, + variables, + operationName, + ) + .then(result => { + observer.next(result); + observer.complete(); + }) + .catch(err => { + observer.error(err); + }); + } else { + subscribe( + schema, + query, + null, + graphqlContext, + variables, + operationName, + ) + .then(results => { + if ( + typeof (results as AsyncIterator).next === + 'function' + ) { + forAwaitEach( + results as AsyncIterable, + result => observer.next(result), + ) + .then(() => observer.complete()) + .catch(err => observer.error(err)); + } else { + observer.next(results as LinkExecutionResult); + observer.complete(); + } + }) + .catch(err => { + observer.error(err); + }); + } + }), + ); } -export async function makeSchemaRemoteFromLink(schema: GraphQLSchema) - : Promise { +export async function makeSchemaRemoteFromLink( + schema: GraphQLSchema, +): Promise { const link = makeLinkFromSchema(schema); const clientSchema = await introspectSchema(link); return { @@ -720,8 +733,9 @@ export async function makeSchemaRemoteFromLink(schema: GraphQLSchema) }; } -export async function makeSchemaRemoteFromDispatchedLink(schema: GraphQLSchema) - : Promise { +export async function makeSchemaRemoteFromDispatchedLink( + schema: GraphQLSchema, +): Promise { const link = makeLinkFromSchema(schema); const clientSchema = await introspectSchema(link); return { @@ -731,16 +745,11 @@ export async function makeSchemaRemoteFromDispatchedLink(schema: GraphQLSchema) } // ensure fetcher support exists from the 2.0 api -async function makeExecutableSchemaFromDispatchedFetcher(schema: GraphQLSchema) - : Promise { - const fetcher: Fetcher = ({ query, operationName, variables, context }) => graphql( - schema, - print(query), - null, - context, - variables, - operationName, - ); +async function makeExecutableSchemaFromDispatchedFetcher( + schema: GraphQLSchema, +): Promise { + const fetcher: Fetcher = ({ query, operationName, variables, context }) => + graphql(schema, print(query), null, context, variables, operationName); const clientSchema = await introspectSchema(fetcher); return { @@ -750,5 +759,9 @@ async function makeExecutableSchemaFromDispatchedFetcher(schema: GraphQLSchema) } export const remotePropertySchema = makeSchemaRemoteFromLink(propertySchema); -export const remoteProductSchema = makeSchemaRemoteFromDispatchedLink(productSchema); -export const remoteBookingSchema = makeExecutableSchemaFromDispatchedFetcher(bookingSchema); +export const remoteProductSchema = makeSchemaRemoteFromDispatchedLink( + productSchema, +); +export const remoteBookingSchema = makeExecutableSchemaFromDispatchedFetcher( + bookingSchema, +); diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index 6f7986075c3..90a19507724 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -54,10 +54,8 @@ function addVariablesToRootField( ): { document: DocumentNode; newVariables: { [key: string]: any }; - } { - const operations: Array< - OperationDefinitionNode - > = document.definitions.filter( +} { + const operations: Array = document.definitions.filter( def => def.kind === Kind.OPERATION_DEFINITION, ) as Array; const fragments: Array = document.definitions.filter( diff --git a/src/transforms/AddMergedTypeSelectionSets.ts b/src/transforms/AddMergedTypeSelectionSets.ts index 45b7c3f39e0..31f26ae0512 100644 --- a/src/transforms/AddMergedTypeSelectionSets.ts +++ b/src/transforms/AddMergedTypeSelectionSets.ts @@ -51,7 +51,10 @@ function addMergedTypeSelectionSets( [Kind.SELECTION_SET]( node: SelectionSetNode, ): SelectionSetNode | null | undefined { - const parentType: GraphQLType | null | undefined = typeInfo.getParentType(); + const parentType: + | GraphQLType + | null + | undefined = typeInfo.getParentType(); if (parentType != null) { const parentTypeName = parentType.name; let selections = node.selections; diff --git a/src/transforms/AddReplacementFragments.ts b/src/transforms/AddReplacementFragments.ts index 251d3962943..fa71d77eb97 100644 --- a/src/transforms/AddReplacementFragments.ts +++ b/src/transforms/AddReplacementFragments.ts @@ -50,7 +50,10 @@ function replaceFieldsWithFragments( [Kind.SELECTION_SET]( node: SelectionSetNode, ): SelectionSetNode | null | undefined { - const parentType: GraphQLType | null | undefined = typeInfo.getParentType(); + const parentType: + | GraphQLType + | null + | undefined = typeInfo.getParentType(); if (parentType != null) { const parentTypeName = parentType.name; let selections = node.selections; diff --git a/src/transforms/AddReplacementSelectionSets.ts b/src/transforms/AddReplacementSelectionSets.ts index 1b09837f8e5..692664adfba 100644 --- a/src/transforms/AddReplacementSelectionSets.ts +++ b/src/transforms/AddReplacementSelectionSets.ts @@ -17,10 +17,7 @@ export default class AddReplacementSelectionSets implements Transform { private readonly schema: GraphQLSchema; private readonly mapping: ReplacementSelectionSetMapping; - constructor( - schema: GraphQLSchema, - mapping: ReplacementSelectionSetMapping, - ) { + constructor(schema: GraphQLSchema, mapping: ReplacementSelectionSetMapping) { this.schema = schema; this.mapping = mapping; } @@ -50,7 +47,10 @@ function replaceFieldsWithSelectionSet( [Kind.SELECTION_SET]( node: SelectionSetNode, ): SelectionSetNode | null | undefined { - const parentType: GraphQLType | null | undefined = typeInfo.getParentType(); + const parentType: + | GraphQLType + | null + | undefined = typeInfo.getParentType(); if (parentType != null) { const parentTypeName = parentType.name; let selections = node.selections; diff --git a/src/transforms/ExpandAbstractTypes.ts b/src/transforms/ExpandAbstractTypes.ts index 7761d51494f..c26371a8ae0 100644 --- a/src/transforms/ExpandAbstractTypes.ts +++ b/src/transforms/ExpandAbstractTypes.ts @@ -85,9 +85,7 @@ function expandAbstractTypes( reverseMapping: TypeMapping, document: DocumentNode, ): DocumentNode { - const operations: Array< - OperationDefinitionNode - > = document.definitions.filter( + const operations: Array = document.definitions.filter( def => def.kind === Kind.OPERATION_DEFINITION, ) as Array; const fragments: Array = document.definitions.filter( @@ -159,13 +157,20 @@ function expandAbstractTypes( node.selections.forEach((selection: SelectionNode) => { if (selection.kind === Kind.INLINE_FRAGMENT) { if (selection.typeCondition != null) { - const possibleTypes = mapping[selection.typeCondition.name.value]; + const possibleTypes = + mapping[selection.typeCondition.name.value]; if (possibleTypes != null) { possibleTypes.forEach(possibleType => { - const maybePossibleType = targetSchema.getType(possibleType); + const maybePossibleType = targetSchema.getType( + possibleType, + ); if ( maybePossibleType != null && - implementsAbstractType(targetSchema, parentType, maybePossibleType) + implementsAbstractType( + targetSchema, + parentType, + maybePossibleType, + ) ) { newSelections.push({ kind: Kind.INLINE_FRAGMENT, diff --git a/src/transforms/ExtendSchema.ts b/src/transforms/ExtendSchema.ts index a6661c346f0..34a7e443b63 100644 --- a/src/transforms/ExtendSchema.ts +++ b/src/transforms/ExtendSchema.ts @@ -1,8 +1,4 @@ -import { - GraphQLSchema, - extendSchema, - parse, -} from 'graphql'; +import { GraphQLSchema, extendSchema, parse } from 'graphql'; import { IFieldResolver, IResolvers, Request } from '../Interfaces'; import { addResolversToSchema } from '../generate'; @@ -27,18 +23,25 @@ export default class ExtendSchema implements Transform { resolvers?: IResolvers; defaultFieldResolver?: IFieldResolver; fieldNodeTransformerMap?: FieldNodeTransformerMap; - }) { + }) { this.typeDefs = typeDefs; this.resolvers = resolvers; - this.defaultFieldResolver = defaultFieldResolver != null ? defaultFieldResolver : defaultMergedResolver; - this.transformer = new MapFields(fieldNodeTransformerMap != null ? fieldNodeTransformerMap : {}); + this.defaultFieldResolver = + defaultFieldResolver != null + ? defaultFieldResolver + : defaultMergedResolver; + this.transformer = new MapFields( + fieldNodeTransformerMap != null ? fieldNodeTransformerMap : {}, + ); } public transformSchema(schema: GraphQLSchema): GraphQLSchema { this.transformer.transformSchema(schema); return addResolversToSchema({ - schema: this.typeDefs ? extendSchema(schema, parse(this.typeDefs)) : schema, + schema: this.typeDefs + ? extendSchema(schema, parse(this.typeDefs)) + : schema, resolvers: this.resolvers != null ? this.resolvers : {}, defaultFieldResolver: this.defaultFieldResolver, }); diff --git a/src/transforms/ExtractField.ts b/src/transforms/ExtractField.ts index 7d8003f408f..f89c38b314b 100644 --- a/src/transforms/ExtractField.ts +++ b/src/transforms/ExtractField.ts @@ -1,10 +1,4 @@ -import { - visit, - Kind, - SelectionSetNode, - BREAK, - FieldNode, -} from 'graphql'; +import { visit, Kind, SelectionSetNode, BREAK, FieldNode } from 'graphql'; import { Transform, Request } from '../Interfaces'; @@ -42,7 +36,10 @@ export default class ExtractField implements Transform { [Kind.FIELD]: { enter: (node: FieldNode) => { fieldPath.push(node.name.value); - if (ourPathTo === JSON.stringify(fieldPath) && fromSelection != null) { + if ( + ourPathTo === JSON.stringify(fieldPath) && + fromSelection != null + ) { return { ...node, selectionSet: fromSelection, diff --git a/src/transforms/FilterObjectFields.ts b/src/transforms/FilterObjectFields.ts index 4577c01ac66..b9fccdd3b04 100644 --- a/src/transforms/FilterObjectFields.ts +++ b/src/transforms/FilterObjectFields.ts @@ -3,7 +3,11 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; import { Transform } from './transforms'; import TransformObjectFields from './TransformObjectFields'; -export type ObjectFilter = (typeName: string, fieldName: string, field: GraphQLField) => boolean; +export type ObjectFilter = ( + typeName: string, + fieldName: string, + field: GraphQLField, +) => boolean; export default class FilterObjectFields implements Transform { private readonly transformer: TransformObjectFields; @@ -11,7 +15,7 @@ export default class FilterObjectFields implements Transform { constructor(filter: ObjectFilter) { this.transformer = new TransformObjectFields( (typeName: string, fieldName: string, field: GraphQLField) => - filter(typeName, fieldName, field) ? undefined : null + filter(typeName, fieldName, field) ? undefined : null, ); } diff --git a/src/transforms/FilterToSchema.ts b/src/transforms/FilterToSchema.ts index b60f9d8982f..30ab26ca033 100644 --- a/src/transforms/FilterToSchema.ts +++ b/src/transforms/FilterToSchema.ts @@ -50,11 +50,9 @@ function filterToSchema( document: DocumentNode, variables: Record, ): { document: DocumentNode; variables: Record } { - const operations: Array< - OperationDefinitionNode - > = document.definitions.filter( - def => def.kind === Kind.OPERATION_DEFINITION, - ) as Array; + const operations: Array = document.definitions.filter( + def => def.kind === Kind.OPERATION_DEFINITION, + ) as Array; const fragments: Array = document.definitions.filter( def => def.kind === Kind.FRAGMENT_DEFINITION, ) as Array; @@ -98,7 +96,7 @@ function filterToSchema( targetSchema, type, validFragmentsWithType, - operation.selectionSet + operation.selectionSet, ); usedFragments = union(usedFragments, operationUsedFragments); @@ -114,15 +112,18 @@ function filterToSchema( validFragmentsWithType, usedFragments, ); - const operationOrFragmentVariables = - union(operationUsedVariables, collectedUsedVariables); + const operationOrFragmentVariables = union( + operationUsedVariables, + collectedUsedVariables, + ); usedVariables = union(usedVariables, operationOrFragmentVariables); newFragments = collectedNewFragments; fragmentSet = collectedFragmentSet; const variableDefinitions = operation.variableDefinitions.filter( (variable: VariableDefinitionNode) => - operationOrFragmentVariables.indexOf(variable.variable.name.value) !== -1, + operationOrFragmentVariables.indexOf(variable.variable.name.value) !== + -1, ); newOperations.push({ @@ -179,7 +180,7 @@ function collectFragmentVariables( type, validFragmentsWithType, fragment.selectionSet, - ); + ); remainingFragments = union(remainingFragments, fragmentUsedFragments); usedVariables = union(usedVariables, fragmentUsedVariables); @@ -215,87 +216,94 @@ function filterSelectionSet( const usedVariables: Array = []; const typeInfo = new TypeInfo(schema, undefined, type); - const filteredSelectionSet = visit(selectionSet, visitWithTypeInfo(typeInfo, { - [Kind.FIELD]: { - enter(node: FieldNode): null | undefined | FieldNode { - const parentType = typeInfo.getParentType(); - if ( - parentType instanceof GraphQLObjectType || - parentType instanceof GraphQLInterfaceType - ) { - const fields = parentType.getFields(); - const field = - node.name.value === '__typename' - ? TypeNameMetaFieldDef - : fields[node.name.value]; - if (!field) { - return null; - } + const filteredSelectionSet = visit( + selectionSet, + visitWithTypeInfo(typeInfo, { + [Kind.FIELD]: { + enter(node: FieldNode): null | undefined | FieldNode { + const parentType = typeInfo.getParentType(); + if ( + parentType instanceof GraphQLObjectType || + parentType instanceof GraphQLInterfaceType + ) { + const fields = parentType.getFields(); + const field = + node.name.value === '__typename' + ? TypeNameMetaFieldDef + : fields[node.name.value]; + if (!field) { + return null; + } - const argNames = (field.args != null ? field.args : []).map(arg => arg.name); - if (node.arguments != null) { - const args = node.arguments.filter((arg: ArgumentNode) => argNames.indexOf(arg.name.value) !== -1); - if (args.length !== node.arguments.length) { - return { - ...node, - arguments: args, - }; + const argNames = (field.args != null ? field.args : []).map( + arg => arg.name, + ); + if (node.arguments != null) { + const args = node.arguments.filter( + (arg: ArgumentNode) => argNames.indexOf(arg.name.value) !== -1, + ); + if (args.length !== node.arguments.length) { + return { + ...node, + arguments: args, + }; + } } } - } - }, - leave(node: FieldNode): null | undefined | FieldNode { - const resolvedType = getNamedType(typeInfo.getType()); - if ( - resolvedType instanceof GraphQLObjectType || - resolvedType instanceof GraphQLInterfaceType - ) { - const selections = node.selectionSet != null ? node.selectionSet.selections : null; - if (selections == null || selections.length === 0) { - // need to remove any added variables. Is there a better way to do this? - visit(node, { - [Kind.VARIABLE](variableNode: VariableNode) { - const index = usedVariables.indexOf(variableNode.name.value); - if (index !== -1) { - usedVariables.splice(index, 1); - } - } + }, + leave(node: FieldNode): null | undefined | FieldNode { + const resolvedType = getNamedType(typeInfo.getType()); + if ( + resolvedType instanceof GraphQLObjectType || + resolvedType instanceof GraphQLInterfaceType + ) { + const selections = + node.selectionSet != null ? node.selectionSet.selections : null; + if (selections == null || selections.length === 0) { + // need to remove any added variables. Is there a better way to do this? + visit(node, { + [Kind.VARIABLE](variableNode: VariableNode) { + const index = usedVariables.indexOf(variableNode.name.value); + if (index !== -1) { + usedVariables.splice(index, 1); + } + }, + }); + return null; } - ); - return null; } - } + }, }, - }, - [Kind.FRAGMENT_SPREAD](node: FragmentSpreadNode): null | undefined { - if (node.name.value in validFragments) { - const parentType = typeInfo.getParentType(); - const innerType = validFragments[node.name.value]; - if (!implementsAbstractType(schema, parentType, innerType)) { - return null; - } - - usedFragments.push(node.name.value); - return; - } - - return null; - }, - [Kind.INLINE_FRAGMENT]: { - enter(node: InlineFragmentNode): null | undefined { - if (node.typeCondition != null) { + [Kind.FRAGMENT_SPREAD](node: FragmentSpreadNode): null | undefined { + if (node.name.value in validFragments) { const parentType = typeInfo.getParentType(); - const innerType = schema.getType(node.typeCondition.name.value); + const innerType = validFragments[node.name.value]; if (!implementsAbstractType(schema, parentType, innerType)) { return null; } + + usedFragments.push(node.name.value); + return; } + + return null; }, - }, - [Kind.VARIABLE](node: VariableNode) { - usedVariables.push(node.name.value); - }, - })); + [Kind.INLINE_FRAGMENT]: { + enter(node: InlineFragmentNode): null | undefined { + if (node.typeCondition != null) { + const parentType = typeInfo.getParentType(); + const innerType = schema.getType(node.typeCondition.name.value); + if (!implementsAbstractType(schema, parentType, innerType)) { + return null; + } + } + }, + }, + [Kind.VARIABLE](node: VariableNode) { + usedVariables.push(node.name.value); + }, + }), + ); return { selectionSet: filteredSelectionSet, diff --git a/src/transforms/HoistField.ts b/src/transforms/HoistField.ts index 3c9dd77eae9..dd4a0f1bbfb 100644 --- a/src/transforms/HoistField.ts +++ b/src/transforms/HoistField.ts @@ -1,8 +1,4 @@ -import { - GraphQLSchema, - GraphQLObjectType, - getNullableType, -} from 'graphql'; +import { GraphQLSchema, GraphQLObjectType, getNullableType } from 'graphql'; import { healSchema, wrapFieldNode, renameFieldNode } from '../utils'; import { createMergedResolver } from '../stitching'; @@ -20,11 +16,7 @@ export default class HoistField implements Transform { private readonly oldFieldName: string; private readonly transformer: Transform; - constructor( - typeName: string, - path: Array, - newFieldName: string, - ) { + constructor(typeName: string, path: Array, newFieldName: string) { this.typeName = typeName; this.path = path; this.newFieldName = newFieldName; @@ -33,10 +25,11 @@ export default class HoistField implements Transform { this.oldFieldName = this.pathToField.pop(); this.transformer = new MapFields({ [typeName]: { - [newFieldName]: fieldNode => wrapFieldNode( - renameFieldNode(fieldNode, this.oldFieldName), - this.pathToField, - ), + [newFieldName]: fieldNode => + wrapFieldNode( + renameFieldNode(fieldNode, this.oldFieldName), + this.pathToField, + ), }, }); } @@ -47,7 +40,7 @@ export default class HoistField implements Transform { const innerType: GraphQLObjectType = this.pathToField.reduce( (acc, pathSegment) => getNullableType(acc.getFields()[pathSegment].type) as GraphQLObjectType, - typeMap[this.typeName] as GraphQLObjectType + typeMap[this.typeName] as GraphQLObjectType, ); const targetField = removeFields( @@ -56,7 +49,7 @@ export default class HoistField implements Transform { fieldName => fieldName === this.oldFieldName, )[this.oldFieldName]; - const targetType = (targetField.type as GraphQLObjectType); + const targetType = targetField.type as GraphQLObjectType; appendFields(typeMap, this.typeName, { [this.newFieldName]: { diff --git a/src/transforms/MapFields.ts b/src/transforms/MapFields.ts index bee993374e2..56f191963fd 100644 --- a/src/transforms/MapFields.ts +++ b/src/transforms/MapFields.ts @@ -8,7 +8,7 @@ import { visitWithTypeInfo, Kind, SelectionNode, - FragmentDefinitionNode + FragmentDefinitionNode, } from 'graphql'; import { Request } from '../Interfaces'; @@ -17,22 +17,20 @@ import { Transform } from './transforms'; export type FieldNodeTransformer = ( fieldNode: FieldNode, - fragments: Record + fragments: Record, ) => SelectionNode | Array; export type FieldNodeTransformerMap = { [typeName: string]: { - [fieldName: string]: FieldNodeTransformer - } + [fieldName: string]: FieldNodeTransformer; + }; }; export default class MapFields implements Transform { private schema: GraphQLSchema | undefined; private readonly fieldNodeTransformerMap: FieldNodeTransformerMap; - constructor( - fieldNodeTransformerMap: FieldNodeTransformerMap, - ) { + constructor(fieldNodeTransformerMap: FieldNodeTransformerMap) { this.fieldNodeTransformerMap = fieldNodeTransformerMap; } @@ -43,24 +41,26 @@ export default class MapFields implements Transform { public transformRequest(originalRequest: Request): Request { if (!this.schema) { - throw new Error('MapFields transform required initialization with target schema within the transformSchema method.') + throw new Error( + 'MapFields transform required initialization with target schema within the transformSchema method.', + ); } const fragments = {}; - originalRequest.document.definitions.filter( - def => def.kind === Kind.FRAGMENT_DEFINITION - ).forEach(def => { - fragments[(def as FragmentDefinitionNode).name.value] = def; - }); + originalRequest.document.definitions + .filter(def => def.kind === Kind.FRAGMENT_DEFINITION) + .forEach(def => { + fragments[(def as FragmentDefinitionNode).name.value] = def; + }); const document = transformDocument( originalRequest.document, this.schema, this.fieldNodeTransformerMap, - fragments + fragments, ); return { ...originalRequest, - document + document, }; } } @@ -76,7 +76,10 @@ function transformDocument( document, visitWithTypeInfo(typeInfo, { [Kind.SELECTION_SET]: node => { - const parentType: GraphQLType | null | undefined = typeInfo.getParentType(); + const parentType: + | GraphQLType + | null + | undefined = typeInfo.getParentType(); if (parentType != null) { const parentTypeName = parentType.name; const fieldNodeTransformers = fieldNodeTransformerMap[parentTypeName]; @@ -90,7 +93,10 @@ function transformDocument( if (fieldNodeTransformers != null) { const fieldNodeTransformer = fieldNodeTransformers[fieldName]; if (fieldNodeTransformer != null) { - transformedSelection = fieldNodeTransformer(selection, fragments); + transformedSelection = fieldNodeTransformer( + selection, + fragments, + ); } else { transformedSelection = selection; } @@ -115,8 +121,8 @@ function transformDocument( selections: newSelections, }; } - } - }) + }, + }), ); return newDocument; } diff --git a/src/transforms/RenameObjectFields.ts b/src/transforms/RenameObjectFields.ts index e14b0769105..ce35122e536 100644 --- a/src/transforms/RenameObjectFields.ts +++ b/src/transforms/RenameObjectFields.ts @@ -8,11 +8,17 @@ import { Transform } from './transforms'; export default class RenameObjectFields implements Transform { private readonly transformer: TransformObjectFields; - constructor(renamer: (typeName: string, fieldName: string, field: GraphQLField) => string) { + constructor( + renamer: ( + typeName: string, + fieldName: string, + field: GraphQLField, + ) => string, + ) { this.transformer = new TransformObjectFields( (typeName: string, fieldName: string, field: GraphQLField) => ({ - name: renamer(typeName, fieldName, field), - }) + name: renamer(typeName, fieldName, field), + }), ); } diff --git a/src/transforms/RenameRootFields.ts b/src/transforms/RenameRootFields.ts index 8b993b78db8..752e49c9e5d 100644 --- a/src/transforms/RenameRootFields.ts +++ b/src/transforms/RenameRootFields.ts @@ -21,8 +21,8 @@ export default class RenameRootFields implements Transform { fieldName: string, field: GraphQLField, ) => ({ - name: renamer(operation, fieldName, field), - }), + name: renamer(operation, fieldName, field), + }), ); } diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index 4caf9088935..1df800ac2c0 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -31,7 +31,8 @@ export default class RenameTypes implements Transform { this.renamer = renamer; this.map = {}; this.reverseMap = {}; - const { renameBuiltins = false, renameScalars = true } = (options != null) ? options : {}; + const { renameBuiltins = false, renameScalars = true } = + options != null ? options : {}; this.renameBuiltins = renameBuiltins; this.renameScalars = renameScalars; } @@ -86,7 +87,7 @@ export default class RenameTypes implements Transform { public transformResult(result: Result): Result { return { ...result, - data: this.renameTypes(result.data) + data: this.renameTypes(result.data), }; } @@ -100,8 +101,10 @@ export default class RenameTypes implements Transform { return value; } else if (typeof value === 'object') { Object.keys(value).forEach(key => { - value[key] = key === '__typename' ? - this.renamer(value[key]) : this.renameTypes(value[key]); + value[key] = + key === '__typename' + ? this.renamer(value[key]) + : this.renameTypes(value[key]); }); return value; } diff --git a/src/transforms/ReplaceFieldWithFragment.ts b/src/transforms/ReplaceFieldWithFragment.ts index 7fbce45ddda..fd3696ada5e 100644 --- a/src/transforms/ReplaceFieldWithFragment.ts +++ b/src/transforms/ReplaceFieldWithFragment.ts @@ -133,4 +133,3 @@ function parseFragmentToInlineFragment( throw new Error('Could not parse fragment'); } - diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index aaab2f0368e..74925431425 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -12,7 +12,7 @@ import { Kind, SelectionSetNode, SelectionNode, - FragmentDefinitionNode + FragmentDefinitionNode, } from 'graphql'; import isEmptyObject from '../utils/isEmptyObject'; @@ -31,7 +31,7 @@ export type FieldNodeTransformer = ( typeName: string, fieldName: string, fieldNode: FieldNode, - fragments: Record + fragments: Record, ) => SelectionNode | Array; type FieldMapping = { @@ -59,7 +59,8 @@ export default class TransformObjectFields implements Transform { public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { this.transformedSchema = visitSchema(originalSchema, { - [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => this.transformFields(type, this.objectFieldTransformer) + [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => + this.transformFields(type, this.objectFieldTransformer), }); return this.transformedSchema; @@ -67,26 +68,26 @@ export default class TransformObjectFields implements Transform { public transformRequest(originalRequest: Request): Request { const fragments = {}; - originalRequest.document.definitions.filter( - def => def.kind === Kind.FRAGMENT_DEFINITION - ).forEach(def => { - fragments[(def as FragmentDefinitionNode).name.value] = def; - }); + originalRequest.document.definitions + .filter(def => def.kind === Kind.FRAGMENT_DEFINITION) + .forEach(def => { + fragments[(def as FragmentDefinitionNode).name.value] = def; + }); const document = this.transformDocument( originalRequest.document, this.mapping, this.fieldNodeTransformer, - fragments + fragments, ); return { ...originalRequest, - document + document, }; } private transformFields( type: GraphQLObjectType, - objectFieldTransformer: ObjectFieldTransformer + objectFieldTransformer: ObjectFieldTransformer, ): GraphQLObjectType { const typeConfig = type.toConfig(); const fields = type.getFields(); @@ -94,7 +95,11 @@ export default class TransformObjectFields implements Transform { Object.keys(fields).forEach(fieldName => { const field = fields[fieldName]; - const transformedField = objectFieldTransformer(type.name, fieldName, field); + const transformedField = objectFieldTransformer( + type.name, + fieldName, + field, + ); if (typeof transformedField === 'undefined') { newFields[fieldName] = typeConfig.fields[fieldName]; @@ -102,9 +107,10 @@ export default class TransformObjectFields implements Transform { const newName = (transformedField as RenamedField).name; if (newName) { - newFields[newName] = (transformedField as RenamedField).field != null ? - (transformedField as RenamedField).field : - typeConfig.fields[fieldName]; + newFields[newName] = + (transformedField as RenamedField).field != null + ? (transformedField as RenamedField).field + : typeConfig.fields[fieldName]; if (newName !== fieldName) { const typeName = type.name; @@ -153,9 +159,15 @@ export default class TransformObjectFields implements Transform { const newName = selection.name.value; - const transformedSelection = fieldNodeTransformer != null - ? fieldNodeTransformer(parentTypeName, newName, selection, fragments) - : selection; + const transformedSelection = + fieldNodeTransformer != null + ? fieldNodeTransformer( + parentTypeName, + newName, + selection, + fragments, + ) + : selection; if (Array.isArray(transformedSelection)) { newSelections = newSelections.concat(transformedSelection); @@ -183,12 +195,12 @@ export default class TransformObjectFields implements Transform { ...transformedSelection, name: { kind: Kind.NAME, - value: oldName + value: oldName, }, alias: { kind: Kind.NAME, - value: newName - } + value: newName, + }, }); }); @@ -197,8 +209,8 @@ export default class TransformObjectFields implements Transform { selections: newSelections, }; } - } - }) + }, + }), ); return newDocument; } diff --git a/src/transforms/TransformQuery.ts b/src/transforms/TransformQuery.ts index f0c967a4a65..d94f66529d2 100644 --- a/src/transforms/TransformQuery.ts +++ b/src/transforms/TransformQuery.ts @@ -12,12 +12,14 @@ import { Transform } from './transforms'; export type QueryTransformer = ( selectionSet: SelectionSetNode, - fragments: Record + fragments: Record, ) => SelectionSetNode; export type ResultTransformer = (result: any) => any; -export type ErrorPathTransformer = (path: ReadonlyArray) => Array; +export type ErrorPathTransformer = ( + path: ReadonlyArray, +) => Array; export default class TransformQuery implements Transform { private readonly path: Array; @@ -33,11 +35,11 @@ export default class TransformQuery implements Transform { errorPathTransformer = errorPath => [].concat(errorPath), fragments = {}, }: { - path: Array; - queryTransformer: QueryTransformer; - resultTransformer?: ResultTransformer; - errorPathTransformer?: ErrorPathTransformer; - fragments?: Record; + path: Array; + queryTransformer: QueryTransformer; + resultTransformer?: ResultTransformer; + errorPathTransformer?: ErrorPathTransformer; + fragments?: Record; }) { this.path = path; this.queryTransformer = queryTransformer; @@ -63,23 +65,23 @@ export default class TransformQuery implements Transform { if (index === pathLength) { const selectionSet = this.queryTransformer( node.selectionSet, - this.fragments + this.fragments, ); return { ...node, - selectionSet + selectionSet, }; } }, leave: () => { index--; - } - } + }, + }, }); return { ...originalRequest, - document: newDocument + document: newDocument, }; } @@ -88,7 +90,7 @@ export default class TransformQuery implements Transform { const errors = originalResult.errors; return { data, - errors: errors != null ? this.transformErrors(errors) : undefined + errors: errors != null ? this.transformErrors(errors) : undefined, }; } @@ -112,7 +114,9 @@ export default class TransformQuery implements Transform { return newData; } - private transformErrors(errors: ReadonlyArray): ReadonlyArray { + private transformErrors( + errors: ReadonlyArray, + ): ReadonlyArray { return errors.map(error => { const path: ReadonlyArray = error.path; @@ -126,9 +130,11 @@ export default class TransformQuery implements Transform { index++; } - const newPath = match ? - path.slice(0, index).concat(this.errorPathTransformer(path.slice(index))) : - path; + const newPath = match + ? path + .slice(0, index) + .concat(this.errorPathTransformer(path.slice(index))) + : path; return new GraphQLError( error.message, @@ -137,7 +143,7 @@ export default class TransformQuery implements Transform { error.positions, newPath, error.originalError, - error.extensions + error.extensions, ); }); } diff --git a/src/transforms/TransformRootFields.ts b/src/transforms/TransformRootFields.ts index 94c3e2acac1..6de4fddd225 100644 --- a/src/transforms/TransformRootFields.ts +++ b/src/transforms/TransformRootFields.ts @@ -1,38 +1,42 @@ -import { - GraphQLSchema, - GraphQLField, - GraphQLFieldConfig, -} from 'graphql'; +import { GraphQLSchema, GraphQLField, GraphQLFieldConfig } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; -import TransformObjectFields, { FieldNodeTransformer } from './TransformObjectFields'; +import TransformObjectFields, { + FieldNodeTransformer, +} from './TransformObjectFields'; export type RootTransformer = ( operation: 'Query' | 'Mutation' | 'Subscription', fieldName: string, field: GraphQLField, -) => - | GraphQLFieldConfig - | RenamedField - | null - | undefined; +) => GraphQLFieldConfig | RenamedField | null | undefined; type RenamedField = { name: string; field?: GraphQLFieldConfig }; export default class TransformRootFields implements Transform { private readonly transformer: TransformObjectFields; - constructor(rootFieldTransformer: RootTransformer, fieldNodeTransformer?: FieldNodeTransformer) { - const rootToObjectFieldTransformer = - (typeName: string, fieldName: string, field: GraphQLField) => { - if (typeName === 'Query' || typeName === 'Mutation' || typeName === 'Subscription') { - return rootFieldTransformer(typeName, fieldName, field); - } - - return undefined; - }; + constructor( + rootFieldTransformer: RootTransformer, + fieldNodeTransformer?: FieldNodeTransformer, + ) { + const rootToObjectFieldTransformer = ( + typeName: string, + fieldName: string, + field: GraphQLField, + ) => { + if ( + typeName === 'Query' || + typeName === 'Mutation' || + typeName === 'Subscription' + ) { + return rootFieldTransformer(typeName, fieldName, field); + } + + return undefined; + }; this.transformer = new TransformObjectFields( rootToObjectFieldTransformer, fieldNodeTransformer, diff --git a/src/transforms/WrapFields.ts b/src/transforms/WrapFields.ts index d597795390b..01be37eaa4e 100644 --- a/src/transforms/WrapFields.ts +++ b/src/transforms/WrapFields.ts @@ -1,7 +1,4 @@ -import { - GraphQLSchema, - GraphQLObjectType, -} from 'graphql'; +import { GraphQLSchema, GraphQLObjectType } from 'graphql'; import { Request } from '../Interfaces'; import { appendFields, removeFields } from '../utils/fields'; @@ -35,12 +32,13 @@ export default class WrapFields implements Transform { const outerMostWrappingFieldName = remainingWrappingFieldNames.shift(); this.transformer = new MapFields({ [outerTypeName]: { - [outerMostWrappingFieldName]: (fieldNode, fragments) => hoistFieldNodes({ - fieldNode, - path: remainingWrappingFieldNames, - fieldNames: this.fieldNames, - fragments, - }), + [outerMostWrappingFieldName]: (fieldNode, fragments) => + hoistFieldNodes({ + fieldNode, + path: remainingWrappingFieldNames, + fieldNames: this.fieldNames, + fragments, + }), }, }); } @@ -51,7 +49,9 @@ export default class WrapFields implements Transform { const targetFields = removeFields( typeMap, this.outerTypeName, - !this.fieldNames ? () => true : fieldName => this.fieldNames.includes(fieldName) + !this.fieldNames + ? () => true + : fieldName => this.fieldNames.includes(fieldName), ); let wrapIndex = this.numWraps - 1; @@ -62,9 +62,11 @@ export default class WrapFields implements Transform { for (wrapIndex--; wrapIndex > -1; wrapIndex--) { appendFields(typeMap, this.wrappingTypeNames[wrapIndex], { [this.wrappingFieldNames[wrapIndex + 1]]: { - type: typeMap[this.wrappingTypeNames[wrapIndex + 1]] as GraphQLObjectType, + type: typeMap[ + this.wrappingTypeNames[wrapIndex + 1] + ] as GraphQLObjectType, resolve: defaultMergedResolver, - } + }, }); } diff --git a/src/transforms/WrapQuery.ts b/src/transforms/WrapQuery.ts index b3dc3aa8abe..68021949e36 100644 --- a/src/transforms/WrapQuery.ts +++ b/src/transforms/WrapQuery.ts @@ -1,15 +1,27 @@ -import { FieldNode, visit, Kind, SelectionNode, SelectionSetNode } from 'graphql'; +import { + FieldNode, + visit, + Kind, + SelectionNode, + SelectionSetNode, +} from 'graphql'; import { Transform, Request, Result } from '../Interfaces'; -export type QueryWrapper = (subtree: SelectionSetNode) => SelectionNode | SelectionSetNode; +export type QueryWrapper = ( + subtree: SelectionSetNode, +) => SelectionNode | SelectionSetNode; export default class WrapQuery implements Transform { private readonly wrapper: QueryWrapper; private readonly extractor: (result: any) => any; private readonly path: Array; - constructor(path: Array, wrapper: QueryWrapper, extractor: (result: any) => any) { + constructor( + path: Array, + wrapper: QueryWrapper, + extractor: (result: any) => any, + ) { this.path = path; this.wrapper = wrapper; this.extractor = extractor; @@ -33,23 +45,23 @@ export default class WrapQuery implements Transform { ? wrapResult : { kind: Kind.SELECTION_SET, - selections: [wrapResult] + selections: [wrapResult], }; return { ...node, - selectionSet + selectionSet, }; } }, leave: () => { fieldPath.pop(); - } - } + }, + }, }); return { ...originalRequest, - document: newDocument + document: newDocument, }; } @@ -69,7 +81,7 @@ export default class WrapQuery implements Transform { return { data: rootData, - errors: originalResult.errors + errors: originalResult.errors, }; } } diff --git a/src/transforms/WrapType.ts b/src/transforms/WrapType.ts index ddff33b58a5..a4feadf5084 100644 --- a/src/transforms/WrapType.ts +++ b/src/transforms/WrapType.ts @@ -8,11 +8,7 @@ import WrapFields from './WrapFields'; export default class WrapType implements Transform { private readonly transformer: Transform; - constructor( - outerTypeName: string, - innerTypeName: string, - fieldName: string, - ) { + constructor(outerTypeName: string, innerTypeName: string, fieldName: string) { this.transformer = new WrapFields( outerTypeName, [fieldName], diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index 37684ee4a02..e380bed9061 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -14,13 +14,10 @@ import { cloneSchema } from '../utils/clone'; export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', - rootFieldName: string + rootFieldName: string, ) => boolean; -export type FieldFilter = ( - typeName: string, - rootFieldName: string -) => boolean; +export type FieldFilter = (typeName: string, rootFieldName: string) => boolean; export default function filterSchema({ schema, @@ -33,28 +30,31 @@ export default function filterSchema({ typeFilter?: (typeName: string, type: GraphQLType) => boolean; fieldFilter?: (typeName: string, fieldName: string) => boolean; }): GraphQLSchemaWithTransforms { - const filteredSchema: GraphQLSchemaWithTransforms = visitSchema(cloneSchema(schema), { - [VisitSchemaKind.QUERY]: (type: GraphQLObjectType) => - filterRootFields(type, 'Query', rootFieldFilter), - [VisitSchemaKind.MUTATION]: (type: GraphQLObjectType) => - filterRootFields(type, 'Mutation', rootFieldFilter), - [VisitSchemaKind.SUBSCRIPTION]: (type: GraphQLObjectType) => - filterRootFields(type, 'Subscription', rootFieldFilter), - [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => - (typeFilter(type.name, type)) ? - filterObjectFields(type, fieldFilter) : - null, - [VisitSchemaKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) => - typeFilter(type.name, type) ? undefined : null, - [VisitSchemaKind.UNION_TYPE]: (type: GraphQLUnionType) => - typeFilter(type.name, type) ? undefined : null, - [VisitSchemaKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) => - typeFilter(type.name, type) ? undefined : null, - [VisitSchemaKind.ENUM_TYPE]: (type: GraphQLEnumType) => - typeFilter(type.name, type) ? undefined : null, - [VisitSchemaKind.SCALAR_TYPE]: (type: GraphQLScalarType) => - typeFilter(type.name, type) ? undefined : null, - }); + const filteredSchema: GraphQLSchemaWithTransforms = visitSchema( + cloneSchema(schema), + { + [VisitSchemaKind.QUERY]: (type: GraphQLObjectType) => + filterRootFields(type, 'Query', rootFieldFilter), + [VisitSchemaKind.MUTATION]: (type: GraphQLObjectType) => + filterRootFields(type, 'Mutation', rootFieldFilter), + [VisitSchemaKind.SUBSCRIPTION]: (type: GraphQLObjectType) => + filterRootFields(type, 'Subscription', rootFieldFilter), + [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => + typeFilter(type.name, type) + ? filterObjectFields(type, fieldFilter) + : null, + [VisitSchemaKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) => + typeFilter(type.name, type) ? undefined : null, + [VisitSchemaKind.UNION_TYPE]: (type: GraphQLUnionType) => + typeFilter(type.name, type) ? undefined : null, + [VisitSchemaKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) => + typeFilter(type.name, type) ? undefined : null, + [VisitSchemaKind.ENUM_TYPE]: (type: GraphQLEnumType) => + typeFilter(type.name, type) ? undefined : null, + [VisitSchemaKind.SCALAR_TYPE]: (type: GraphQLScalarType) => + typeFilter(type.name, type) ? undefined : null, + }, + ); filteredSchema.transforms = schema.transforms; diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index ff9bd34e671..7caa0e14041 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -17,9 +17,11 @@ export function wrapSchema( subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, transforms?: Array, ): GraphQLSchema { - const subschemaConfig: SubschemaConfig = isSubschemaConfig(subschemaOrSubschemaConfig) ? - subschemaOrSubschemaConfig : - { schema: subschemaOrSubschemaConfig }; + const subschemaConfig: SubschemaConfig = isSubschemaConfig( + subschemaOrSubschemaConfig, + ) + ? subschemaOrSubschemaConfig + : { schema: subschemaOrSubschemaConfig }; const schema = cloneSchema(subschemaConfig.schema); stripResolvers(schema); diff --git a/src/transforms/transforms.ts b/src/transforms/transforms.ts index 44fc62b4c33..b131d680d46 100644 --- a/src/transforms/transforms.ts +++ b/src/transforms/transforms.ts @@ -11,7 +11,9 @@ export function applySchemaTransforms( ): GraphQLSchema { return transforms.reduce( (schema: GraphQLSchema, transform: Transform) => - transform.transformSchema != null ? transform.transformSchema(cloneSchema(schema)) : schema, + transform.transformSchema != null + ? transform.transformSchema(cloneSchema(schema)) + : schema, originalSchema, ); } @@ -36,7 +38,9 @@ export function applyResultTransforms( ): any { return transforms.reduceRight( (result: any, transform: Transform) => - transform.transformResult != null ? transform.transformResult(result) : result, + transform.transformResult != null + ? transform.transformResult(result) + : result, originalResult, ); } diff --git a/src/utils/SchemaDirectiveVisitor.ts b/src/utils/SchemaDirectiveVisitor.ts index dcf3bf594f3..06f54b715f0 100644 --- a/src/utils/SchemaDirectiveVisitor.ts +++ b/src/utils/SchemaDirectiveVisitor.ts @@ -105,28 +105,30 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { // 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 + [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 + [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]: Array, + [directiveName: string]: Array; } { // 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); + const declaredDirectives = this.getDeclaredDirectives( + schema, + directiveVisitors, + ); // Map from directive names to lists of SchemaDirectiveVisitor instances // created while visiting the schema. const createdVisitors: { - [directiveName: string]: Array + [directiveName: string]: Array; } = Object.create(null); Object.keys(directiveVisitors).forEach(directiveName => { createdVisitors[directiveName] = []; @@ -137,14 +139,15 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { methodName: string, ): Array { const visitors: Array = []; - const directiveNodes = (type.astNode != null) ? type.astNode.directives : null; - if (! directiveNodes) { + const directiveNodes = + type.astNode != null ? type.astNode.directives : null; + if (!directiveNodes) { return visitors; } directiveNodes.forEach(directiveNode => { const directiveName = directiveNode.name.value; - if (! hasOwn.call(directiveVisitors, directiveName)) { + if (!hasOwn.call(directiveVisitors, directiveName)) { return; } @@ -152,7 +155,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { // Avoid creating visitor objects if visitorClass does not override // the visitor method named by methodName. - if (! visitorClass.implementsVisitorMethod(methodName)) { + if (!visitorClass.implementsVisitorMethod(methodName)) { return; } @@ -180,13 +183,15 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { // 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, - })); + visitors.push( + new visitorClass({ + name: directiveName, + args, + visitedType: type, + schema, + context, + }), + ); }); if (visitors.length > 0) { @@ -206,11 +211,11 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { protected static getDeclaredDirectives( schema: GraphQLSchema, directiveVisitors: { - [directiveName: string]: typeof SchemaDirectiveVisitor + [directiveName: string]: typeof SchemaDirectiveVisitor; }, ) { const declaredDirectives: { - [directiveName: string]: GraphQLDirective, + [directiveName: string]: GraphQLDirective; } = Object.create(null); each(schema.getDirectives(), (decl: GraphQLDirective) => { @@ -230,7 +235,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { }); each(declaredDirectives, (decl, name) => { - if (! hasOwn.call(directiveVisitors, 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 @@ -241,14 +246,16 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { each(decl.locations, loc => { const visitorMethodName = directiveLocationToVisitorMethodName(loc); - if (SchemaVisitor.implementsVisitorMethod(visitorMethodName) && - ! visitorClass.implementsVisitorMethod(visitorMethodName)) { + 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` + `SchemaDirectiveVisitor for @${name} must implement ${visitorMethodName} method`, ); } }); @@ -260,11 +267,11 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { // 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 } + name: string; + args: { [name: string]: any }; + visitedType: VisitableSchemaType; + schema: GraphQLSchema; + context: { [key: string]: any }; }) { super(); this.name = config.name; @@ -277,6 +284,12 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { // Convert a string like "FIELD_DEFINITION" to "visitFieldDefinition". function directiveLocationToVisitorMethodName(loc: DirectiveLocationEnum) { - return 'visit' + loc.replace(/([^_]*)_?/g, (_wholeMatch, part: string) => - part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()); + return ( + 'visit' + + loc.replace( + /([^_]*)_?/g, + (_wholeMatch, part: string) => + part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(), + ) + ); } diff --git a/src/utils/SchemaVisitor.ts b/src/utils/SchemaVisitor.ts index 085b7e69e21..84808616fb3 100644 --- a/src/utils/SchemaVisitor.ts +++ b/src/utils/SchemaVisitor.ts @@ -25,7 +25,7 @@ export abstract class SchemaVisitor { // Determine if this SchemaVisitor (sub)class implements a particular // visitor method. public static implementsVisitorMethod(methodName: string) { - if (! methodName.startsWith('visit')) { + if (!methodName.startsWith('visit')) { return false; } @@ -58,44 +58,62 @@ export abstract class SchemaVisitor { // eslint-disable-next-line @typescript-eslint/no-empty-function public visitSchema(_schema: GraphQLSchema): void {} - // eslint-disable-next-line @typescript-eslint/no-empty-function - public visitScalar(_scalar: GraphQLScalarType): GraphQLScalarType | void | null {} - - // eslint-disable-next-line @typescript-eslint/no-empty-function - public visitObject(_object: GraphQLObjectType): GraphQLObjectType | void | null {} - - // eslint-disable-next-line @typescript-eslint/no-empty-function - - public visitFieldDefinition(_field: GraphQLField, _details: { - objectType: GraphQLObjectType | GraphQLInterfaceType, - // eslint-disable-next-line @typescript-eslint/no-empty-function - }): GraphQLField | void | null { } - - public visitArgumentDefinition(_argument: GraphQLArgument, _details: { - field: GraphQLField, - objectType: GraphQLObjectType | GraphQLInterfaceType, - // eslint-disable-next-line @typescript-eslint/no-empty-function - }): GraphQLArgument | void | null {} - - // eslint-disable-next-line @typescript-eslint/no-empty-function - public visitInterface(_iface: GraphQLInterfaceType): GraphQLInterfaceType | void | null { } + public visitScalar( + _scalar: GraphQLScalarType, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): GraphQLScalarType | void | null {} + + public visitObject( + _object: GraphQLObjectType, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): GraphQLObjectType | void | null {} + + public visitFieldDefinition( + _field: GraphQLField, + _details: { + objectType: GraphQLObjectType | GraphQLInterfaceType; + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): GraphQLField | void | null {} + + public visitArgumentDefinition( + _argument: GraphQLArgument, + _details: { + field: GraphQLField; + objectType: GraphQLObjectType | GraphQLInterfaceType; + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): GraphQLArgument | void | null {} + + public visitInterface( + _iface: GraphQLInterfaceType, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): GraphQLInterfaceType | void | null {} // eslint-disable-next-line @typescript-eslint/no-empty-function - public visitUnion(_union: GraphQLUnionType): GraphQLUnionType | void | null { } + public visitUnion(_union: GraphQLUnionType): GraphQLUnionType | void | null {} // eslint-disable-next-line @typescript-eslint/no-empty-function public visitEnum(_type: GraphQLEnumType): GraphQLEnumType | void | null {} - public visitEnumValue(_value: GraphQLEnumValue, _details: { - enumType: GraphQLEnumType, - // eslint-disable-next-line @typescript-eslint/no-empty-function - }): GraphQLEnumValue | void | null {} - - // eslint-disable-next-line @typescript-eslint/no-empty-function - public visitInputObject(_object: GraphQLInputObjectType): GraphQLInputObjectType | void | null {} - - public visitInputFieldDefinition(_field: GraphQLInputField, _details: { - objectType: GraphQLInputObjectType, - // eslint-disable-next-line @typescript-eslint/no-empty-function - }): GraphQLInputField | void | null {} + public visitEnumValue( + _value: GraphQLEnumValue, + _details: { + enumType: GraphQLEnumType; + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): GraphQLEnumValue | void | null {} + + public visitInputObject( + _object: GraphQLInputObjectType, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): GraphQLInputObjectType | void | null {} + + public visitInputFieldDefinition( + _field: GraphQLInputField, + _details: { + objectType: GraphQLInputObjectType; + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + ): GraphQLInputField | void | null {} } diff --git a/src/utils/clone.ts b/src/utils/clone.ts index 662d27b3f3d..c6e7afb3e21 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -37,14 +37,18 @@ export function cloneType(type: GraphQLNamedType): GraphQLNamedType { } else if (type instanceof GraphQLEnumType) { return new GraphQLEnumType(type.toConfig()); } else if (type instanceof GraphQLScalarType) { - return isSpecifiedScalarType(type) ? type : new GraphQLScalarType(type.toConfig()); + return isSpecifiedScalarType(type) + ? type + : new GraphQLScalarType(type.toConfig()); } throw new Error(`Invalid type ${type as string}`); } export function cloneSchema(schema: GraphQLSchema): GraphQLSchema { - const newDirectives = schema.getDirectives().map(directive => cloneDirective(directive)); + const newDirectives = schema + .getDirectives() + .map(directive => cloneDirective(directive)); const originalTypeMap = schema.getTypeMap(); const newTypeMap = {}; @@ -65,7 +69,8 @@ export function cloneSchema(schema: GraphQLSchema): GraphQLSchema { ...schema.toConfig(), query: query != null ? newTypeMap[query.name] : undefined, mutation: mutation != null ? newTypeMap[mutation.name] : undefined, - subscription: subscription != null ? newTypeMap[subscription.name] : undefined, + subscription: + subscription != null ? newTypeMap[subscription.name] : undefined, types: Object.keys(newTypeMap).map(typeName => newTypeMap[typeName]), directives: newDirectives, }); diff --git a/src/utils/fieldNodes.ts b/src/utils/fieldNodes.ts index 5daa3652631..f2408646ae3 100644 --- a/src/utils/fieldNodes.ts +++ b/src/utils/fieldNodes.ts @@ -10,22 +10,28 @@ export function renameFieldNode(fieldNode: FieldNode, name: string): FieldNode { ...fieldNode, alias: { kind: Kind.NAME, - value: fieldNode.alias != null ? fieldNode.alias.value : fieldNode.name.value, + value: + fieldNode.alias != null ? fieldNode.alias.value : fieldNode.name.value, }, name: { kind: Kind.NAME, value: name, - } + }, }; } -export function preAliasFieldNode(fieldNode: FieldNode, str: string): FieldNode { +export function preAliasFieldNode( + fieldNode: FieldNode, + str: string, +): FieldNode { return { ...fieldNode, alias: { kind: Kind.NAME, - value: `${str}${fieldNode.alias != null ? fieldNode.alias.value : fieldNode.name.value}`, - } + value: `${str}${ + fieldNode.alias != null ? fieldNode.alias.value : fieldNode.name.value + }`, + }, }; } @@ -43,10 +49,8 @@ export function wrapFieldNode( }, selectionSet: { kind: Kind.SELECTION_SET, - selections: [ - fieldNode, - ] - } + selections: [fieldNode], + }, }; }); @@ -57,7 +61,7 @@ export function collectFields( selectionSet: SelectionSetNode | undefined, fragments: Record, fields: Array = [], - visitedFragmentNames = {} + visitedFragmentNames = {}, ): Array { if (selectionSet != null) { selectionSet.selections.forEach(selection => { @@ -70,7 +74,7 @@ export function collectFields( selection.selectionSet, fragments, fields, - visitedFragmentNames + visitedFragmentNames, ); break; case Kind.FRAGMENT_SPREAD: { @@ -81,12 +85,13 @@ export function collectFields( fragments[fragmentName].selectionSet, fragments, fields, - visitedFragmentNames + visitedFragmentNames, ); } break; } - default: // unreachable + default: + // unreachable break; } }); @@ -108,7 +113,8 @@ export function hoistFieldNodes({ delimeter?: string; fragments: Record; }): Array { - const alias = fieldNode.alias != null ? fieldNode.alias.value : fieldNode.name.value; + const alias = + fieldNode.alias != null ? fieldNode.alias.value : fieldNode.name.value; let newFieldNodes: Array = []; @@ -116,23 +122,34 @@ export function hoistFieldNodes({ const remainingPathSegments = path.slice(); const initialPathSegment = remainingPathSegments.shift(); - collectFields(fieldNode.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { - if (possibleFieldNode.name.value === initialPathSegment) { - newFieldNodes = newFieldNodes.concat(hoistFieldNodes({ - fieldNode: preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`), - fieldNames, - path: remainingPathSegments, - delimeter, - fragments, - })); - } - }); + collectFields(fieldNode.selectionSet, fragments).forEach( + (possibleFieldNode: FieldNode) => { + if (possibleFieldNode.name.value === initialPathSegment) { + newFieldNodes = newFieldNodes.concat( + hoistFieldNodes({ + fieldNode: preAliasFieldNode( + possibleFieldNode, + `${alias}${delimeter}`, + ), + fieldNames, + path: remainingPathSegments, + delimeter, + fragments, + }), + ); + } + }, + ); } else { - collectFields(fieldNode.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => { - if (!fieldNames || fieldNames.includes(possibleFieldNode.name.value)) { - newFieldNodes.push(preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`)); - } - }); + collectFields(fieldNode.selectionSet, fragments).forEach( + (possibleFieldNode: FieldNode) => { + if (!fieldNames || fieldNames.includes(possibleFieldNode.name.value)) { + newFieldNodes.push( + preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`), + ); + } + }, + ); } return newFieldNodes; diff --git a/src/utils/forEachDefaultValue.ts b/src/utils/forEachDefaultValue.ts index 8bda359a78d..b1267e7b984 100644 --- a/src/utils/forEachDefaultValue.ts +++ b/src/utils/forEachDefaultValue.ts @@ -7,8 +7,10 @@ import { import { IDefaultValueIteratorFn } from '../Interfaces'; -export function forEachDefaultValue(schema: GraphQLSchema, fn: IDefaultValueIteratorFn): void { - +export function forEachDefaultValue( + schema: GraphQLSchema, + fn: IDefaultValueIteratorFn, +): void { const typeMap = schema.getTypeMap(); Object.keys(typeMap).forEach(typeName => { const type = typeMap[typeName]; diff --git a/src/utils/forEachField.ts b/src/utils/forEachField.ts index 09a94f29ab5..15fd023fb32 100644 --- a/src/utils/forEachField.ts +++ b/src/utils/forEachField.ts @@ -1,12 +1,11 @@ -import { - getNamedType, - GraphQLObjectType, - GraphQLSchema, -} from 'graphql'; +import { getNamedType, GraphQLObjectType, GraphQLSchema } from 'graphql'; import { IFieldIteratorFn } from '../Interfaces'; -export function forEachField(schema: GraphQLSchema, fn: IFieldIteratorFn): void { +export function forEachField( + schema: GraphQLSchema, + fn: IFieldIteratorFn, +): void { const typeMap = schema.getTypeMap(); Object.keys(typeMap).forEach(typeName => { const type = typeMap[typeName]; diff --git a/src/utils/fragments.ts b/src/utils/fragments.ts index b3ea3270bd9..84bc9434d79 100644 --- a/src/utils/fragments.ts +++ b/src/utils/fragments.ts @@ -11,7 +11,8 @@ export function concatInlineFragments( fragments: Array, ): InlineFragmentNode { const fragmentSelections: Array = fragments.reduce( - (selections, fragment) => selections.concat(fragment.selectionSet.selections), + (selections, fragment) => + selections.concat(fragment.selectionSet.selections), [], ); @@ -37,7 +38,9 @@ export function concatInlineFragments( const hasOwn = Object.prototype.hasOwnProperty; -function deduplicateSelection(nodes: Array): Array { +function deduplicateSelection( + nodes: Array, +): Array { const selectionMap = nodes.reduce<{ [key: string]: SelectionNode }>( (map, node) => { switch (node.kind) { diff --git a/src/utils/heal.ts b/src/utils/heal.ts index 7ce80dc74ad..fda52090c05 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -46,7 +46,7 @@ export function healTypes( skipPruning: boolean; } = { skipPruning: false, - } + }, ) { const actualNamedTypeMap: NamedTypeMap = Object.create(null); @@ -90,8 +90,10 @@ export function healTypes( each(originalTypeMap, (namedType, typeName) => { // Heal all named types, except for dangling references, kept only to redirect. - if (! typeName.startsWith('__') && - hasOwn.call(actualNamedTypeMap, typeName)) { + if ( + !typeName.startsWith('__') && + hasOwn.call(actualNamedTypeMap, typeName) + ) { if (namedType != null) { healNamedType(namedType); } @@ -102,8 +104,10 @@ export function healTypes( // 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)) { + if ( + !typeName.startsWith('__') && + !hasOwn.call(actualNamedTypeMap, typeName) + ) { return null; } }); @@ -126,11 +130,14 @@ export function healTypes( } else if (type instanceof GraphQLInputObjectType) { healInputFields(type); return; - } else if (type instanceof GraphQLScalarType || type instanceof GraphQLEnumType) { + } else if ( + type instanceof GraphQLScalarType || + type instanceof GraphQLEnumType + ) { return; } - throw new Error(`Unexpected schema type: ${type as unknown as string}`); + throw new Error(`Unexpected schema type: ${(type as unknown) as string}`); } function healFields(type: GraphQLObjectType | GraphQLInterfaceType) { @@ -169,10 +176,10 @@ export function healTypes( // Unwrap the two known wrapper types if (type instanceof GraphQLList) { const healedType = healType(type.ofType); - return (healedType != null) ? new GraphQLList(healedType) : null; + return healedType != null ? new GraphQLList(healedType) : null; } else if (type instanceof GraphQLNonNull) { const healedType = healType(type.ofType); - return (healedType != null) ? new GraphQLNonNull(healedType) : null; + return healedType != null ? new GraphQLNonNull(healedType) : null; } 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 @@ -196,7 +203,10 @@ export function healTypes( } } -function pruneTypes(typeMap: Record, directives: ReadonlyArray) { +function pruneTypes( + typeMap: Record, + directives: ReadonlyArray, +) { const implementedInterfaces = {}; each(typeMap, namedType => { if (namedType instanceof GraphQLObjectType) { @@ -211,7 +221,10 @@ function pruneTypes(typeMap: Record, directives for (let i = 0; i < typeNames.length; i++) { const typeName = typeNames[i]; const type = typeMap[typeName]; - if (type instanceof GraphQLObjectType || type instanceof GraphQLInputObjectType) { + if ( + type instanceof GraphQLObjectType || + type instanceof GraphQLInputObjectType + ) { // prune types with no fields if (!Object.keys(type.getFields()).length) { typeMap[typeName] = null; @@ -225,7 +238,10 @@ function pruneTypes(typeMap: Record, directives } } else if (type instanceof GraphQLInterfaceType) { // prune interfaces without fields or without implementations - if (!Object.keys(type.getFields()).length || !implementedInterfaces[type.name]) { + if ( + !Object.keys(type.getFields()).length || + !implementedInterfaces[type.name] + ) { typeMap[typeName] = null; prunedTypeMap = true; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 2f7b787f07d..dd9cf263339 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -16,10 +16,7 @@ export { concatInlineFragments, parseFragmentToInlineFragment, } from './fragments'; -export { - parseSelectionSet, - typeContainsSelectionSet, -} from './selectionSets'; +export { parseSelectionSet, typeContainsSelectionSet } from './selectionSets'; export { mergeDeep } from './mergeDeep'; export { collectFields, diff --git a/src/utils/selectionSets.ts b/src/utils/selectionSets.ts index 66bcbe7463b..665b4440741 100644 --- a/src/utils/selectionSets.ts +++ b/src/utils/selectionSets.ts @@ -8,7 +8,7 @@ import { } from 'graphql'; export function parseSelectionSet(selectionSet: string): SelectionSetNode { - const query = (parse(selectionSet).definitions[0] as OperationDefinitionNode); + const query = parse(selectionSet).definitions[0] as OperationDefinitionNode; return query.selectionSet; } @@ -32,9 +32,11 @@ export function typeContainsSelectionSet( selection.selectionSet, ); } - } else if (selection.kind === Kind.INLINE_FRAGMENT) { - const containsSelectionSet = typeContainsSelectionSet(type, selection.selectionSet); + const containsSelectionSet = typeContainsSelectionSet( + type, + selection.selectionSet, + ); if (!containsSelectionSet) { return false; } diff --git a/src/utils/stub.ts b/src/utils/stub.ts index d56204c314c..7beff74490e 100644 --- a/src/utils/stub.ts +++ b/src/utils/stub.ts @@ -12,7 +12,7 @@ import { export function createNamedStub( name: string, - type: 'object' | 'interface' | 'input' + type: 'object' | 'interface' | 'input', ): GraphQLObjectType | GraphQLInputObjectType | GraphQLInterfaceType { let constructor: any; if (type === 'object') { @@ -61,5 +61,5 @@ export function getBuiltInForStub(type: GraphQLNamedType): GraphQLNamedType { return GraphQLID; default: return type; - } + } } diff --git a/src/utils/transformInputValue.ts b/src/utils/transformInputValue.ts index 28fa455ac07..3024148073a 100644 --- a/src/utils/transformInputValue.ts +++ b/src/utils/transformInputValue.ts @@ -7,24 +7,40 @@ import { getNullableType, } from 'graphql'; -type InputValueTransformer = (type: GraphQLEnumType | GraphQLScalarType, originalValue: any) => any; - -export function transformInputValue(type: GraphQLInputType, value: any, transformer: InputValueTransformer) { +type InputValueTransformer = ( + type: GraphQLEnumType | GraphQLScalarType, + originalValue: any, +) => any; + +export function transformInputValue( + type: GraphQLInputType, + value: any, + transformer: InputValueTransformer, +) { if (value == null) { return value; } const nullableType = getNullableType(type); - if (nullableType instanceof GraphQLEnumType || nullableType instanceof GraphQLScalarType) { + if ( + nullableType instanceof GraphQLEnumType || + nullableType instanceof GraphQLScalarType + ) { return transformer(nullableType, value); } else if (nullableType instanceof GraphQLList) { - return value.map((listMember: any) => transformInputValue(nullableType.ofType, listMember, transformer)); + return value.map((listMember: any) => + transformInputValue(nullableType.ofType, listMember, transformer), + ); } else if (nullableType instanceof GraphQLInputObjectType) { const fields = nullableType.getFields(); const newValue = {}; Object.keys(value).forEach(key => { - newValue[key] = transformInputValue(fields[key].type, value[key], transformer); + newValue[key] = transformInputValue( + fields[key].type, + value[key], + transformer, + ); }); return newValue; } @@ -33,25 +49,13 @@ export function transformInputValue(type: GraphQLInputType, value: any, transfor } export function serializeInputValue(type: GraphQLInputType, value: any) { - return transformInputValue( - type, - value, - (t, v) => t.serialize(v) - ); + return transformInputValue(type, value, (t, v) => t.serialize(v)); } export function parseInputValue(type: GraphQLInputType, value: any) { - return transformInputValue( - type, - value, - (t, v) => t.parseValue(v) - ); + return transformInputValue(type, value, (t, v) => t.parseValue(v)); } export function parseInputValueLiteral(type: GraphQLInputType, value: any) { - return transformInputValue( - type, - value, - (t, v) => t.parseLiteral(v, {}) - ); + return transformInputValue(type, value, (t, v) => t.parseLiteral(v, {})); } diff --git a/src/utils/valueFromASTUntyped.ts b/src/utils/valueFromASTUntyped.ts index aa26752796e..198ad65a2e0 100644 --- a/src/utils/valueFromASTUntyped.ts +++ b/src/utils/valueFromASTUntyped.ts @@ -2,31 +2,29 @@ import { ValueNode, Kind } from 'graphql'; // Similar to the graphql-js function of the same name, slightly simplified: // https://github.com/graphql/graphql-js/blob/master/src/utilities/valueFromASTUntyped.js -export default function valueFromASTUntyped( - valueNode: ValueNode, -): any { +export default 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); + 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/utils/visitSchema.ts b/src/utils/visitSchema.ts index b59fe6a4708..6ad883953af 100644 --- a/src/utils/visitSchema.ts +++ b/src/utils/visitSchema.ts @@ -42,15 +42,15 @@ export function visitSchema( // visitor pattern that benefits from this abstraction, see the // SchemaDirectiveVisitor class below. visitorOrVisitorSelector: - VisitorSelector | - Array | - SchemaVisitor | - SchemaVisitorMap, + | VisitorSelector + | Array + | SchemaVisitor + | SchemaVisitorMap, ): GraphQLSchema { const visitorSelector = - typeof visitorOrVisitorSelector === 'function' ? - visitorOrVisitorSelector : - () => visitorOrVisitorSelector; + typeof visitorOrVisitorSelector === 'function' + ? visitorOrVisitorSelector + : () => visitorOrVisitorSelector; // Helper function that calls visitorSelector and applies the resulting // visitors to the given type, with arguments [type, ...args]. @@ -68,17 +68,18 @@ export function visitSchema( if (visitorOrVisitorDef instanceof SchemaVisitor) { newType = visitorOrVisitorDef[methodName](finalType, ...args); } else if ( - isNamedType(finalType) && ( - methodName === 'visitScalar' || + isNamedType(finalType) && + (methodName === 'visitScalar' || methodName === 'visitEnum' || methodName === 'visitObject' || methodName === 'visitInputObject' || methodName === 'visitUnion' || - methodName === 'visitInterface' - )) { + methodName === 'visitInterface') + ) { const specifiers = getTypeSpecifiers(finalType, schema); const typeVisitor = getVisitor(visitorOrVisitorDef, specifiers); - newType = typeVisitor != null ? typeVisitor(finalType, schema) : undefined; + newType = + typeVisitor != null ? typeVisitor(finalType, schema) : undefined; } if (typeof newType === 'undefined') { @@ -86,9 +87,10 @@ export function visitSchema( return true; } - if (methodName === 'visitSchema' || - finalType instanceof GraphQLSchema) { - throw new Error(`Method ${methodName} cannot replace schema with ${newType as string}`); + if (methodName === 'visitSchema' || finalType instanceof GraphQLSchema) { + throw new Error( + `Method ${methodName} cannot replace schema with ${newType as string}`, + ); } if (newType === null) { @@ -119,7 +121,10 @@ export function visitSchema( // for SchemaVisitor subclasses that rely on the original schema object. callMethod('visitSchema', type); - const typeMap: Record = type.getTypeMap(); + const typeMap: Record< + string, + GraphQLNamedType | null + > = type.getTypeMap(); each(typeMap, (namedType, typeName) => { if (!typeName.startsWith('__') && namedType != null) { // Call visit recursively to let it determine which concrete @@ -158,12 +163,16 @@ export function visitSchema( const newInputObject = callMethod('visitInputObject', type); if (newInputObject != null) { - const fieldMap = newInputObject.getFields() as Record; - updateEachKey(fieldMap, field => callMethod('visitInputFieldDefinition', field, { - // Since we call a different method for input object fields, we - // can't reuse the visitFields function here. + const fieldMap = newInputObject.getFields() as Record< + string, + GraphQLInputField + >; + updateEachKey(fieldMap, field => + callMethod('visitInputFieldDefinition', field, { + // Since we call a different method for input object fields, we + // can't reuse the visitFields function here. objectType: newInputObject, - }) + }), ); } @@ -185,13 +194,14 @@ export function visitSchema( updateEachKey(newEnum.getValues(), value => callMethod('visitEnumValue', value, { enumType: newEnum, - })); + }), + ); } return newEnum; } - throw new Error(`Unexpected schema type: ${type as unknown as string}`); + throw new Error(`Unexpected schema type: ${(type as unknown) as string}`); } function visitFields(type: GraphQLObjectType | GraphQLInterfaceType) { @@ -212,14 +222,16 @@ export function visitSchema( }); if (newField.args != null) { - updateEachKey(newField.args, arg => callMethod('visitArgumentDefinition', arg, { + updateEachKey(newField.args, arg => + 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; @@ -236,7 +248,6 @@ export function visitSchema( return schema; } - function getTypeSpecifiers( type: GraphQLType, schema: GraphQLSchema, @@ -294,5 +305,5 @@ function getVisitor( typeVisitor = visitorDef[next] as NamedTypeVisitor; } - return (typeVisitor != null) ? typeVisitor : null; + return typeVisitor != null ? typeVisitor : null; } From ac51b575794b8cdde2c133bbc9550bf33882d374 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 13 Feb 2020 16:53:32 -0500 Subject: [PATCH 185/250] chore(ci): attempt to fix coverage and ci workflow --- .travis.yml | 1 - package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 99171bb7f88..5645bedb37f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ install: script: - npm test - - npm run lint - npm run coverage - coveralls < ./coverage/lcov.info || true # if coveralls doesn't have it covered diff --git a/package.json b/package.json index 78bc4adec77..a3a87d1d2f1 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "watch": "tsc -w", "testonly": "mocha --reporter spec --full-trace ./dist/test/**.js --require source-map-support/register", "testonly:watch": "mocha -w --reporter spec --full-trace ./dist/test/**.js --require source-map-support/register", - "coverage": "istanbul cover _mocha -- --reporter dot --full-trace ./dist/test/tests.js", + "coverage": "istanbul cover _mocha -- --reporter dot --full-trace ./dist/test/**.js", "postcoverage": "remap-istanbul --input coverage/coverage.json --type lcovonly --output coverage/lcov.info", "prepublishOnly": "npm run compile", "prerelease": "npm test", From cd944d0228ac16408af6dee92e1884142e325e0a Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 13 Feb 2020 17:00:49 -0500 Subject: [PATCH 186/250] chore(ci): add releaseonly option for manually releasing immediately after testing! --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a3a87d1d2f1..39d5b2747aa 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "sideEffects": false, "scripts": { "clean": "rimraf dist", + "precompile": "npm run clean", "compile": "tsc", "pretest": "npm run clean && npm run compile", "test": "npm run testonly", @@ -22,7 +23,8 @@ "prerelease": "npm test", "prettier": "prettier --trailing-comma all --single-quote --write src/**/*.ts", "prettier:check": "prettier --trailing-comma all --single-quote --check src/**/*.ts", - "release": "standard-version" + "release": "npm run releaseonly", + "releaseonly": "standard-version" }, "repository": { "type": "git", From 55a15efd822528be52d617e1d8f1c344e1d787b0 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 15 Feb 2020 19:20:29 -0500 Subject: [PATCH 187/250] fix(transformSchema): handle schemaConfig objects Regression caused by https://github.com/yaacovCR/graphql-tools-fork/commit/3c3a256feddca731be25315cb7a5ab0f45bd5c4a. Fixes https://github.com/gatsbyjs/gatsby/issues/21443 --- src/stitching/resolvers.ts | 3 +++ src/test/testTransforms.ts | 35 +++++++++++++++++++++++++++---- src/transforms/transformSchema.ts | 8 +++---- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index fe9270ef84a..1123c2cbc14 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -107,14 +107,17 @@ export function generateMappingFromObjectType( function defaultCreateProxyingResolver({ schema, + transforms, }: { schema: SubschemaConfig; + transforms: Array; }): GraphQLFieldResolver { return (_parent, _args, context, info) => delegateToSchema({ schema, context, info, + transforms, }); } diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index f6a9b8f3549..312428e2234 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -29,7 +29,6 @@ import { propertySchema, bookingSchema } from './testingSchemas'; describe('transforms', () => { describe('base transform function', () => { - let schema: GraphQLSchema; const scalarTest = ` scalar TestScalar type TestingScalar { @@ -61,10 +60,38 @@ describe('transforms', () => { }, }); - before(() => { - schema = transformSchema(scalarSchema, []); - }); it('should work', async () => { + const schema = transformSchema(scalarSchema, []); + const result = await graphql( + schema, + ` + query($input: TestScalar) { + testingScalar(input: $input) { + value + } + } + `, + {}, + {}, + { + input: 'test', + }, + ); + + expect(result).to.deep.equal({ + data: { + testingScalar: { + value: 'test', + }, + }, + }); + }); + + it('should work when specified as a schema configuration object', async () => { + const schema = transformSchema( + { schema: scalarSchema, transforms: [] }, + [], + ); const result = await graphql( schema, ` diff --git a/src/transforms/transformSchema.ts b/src/transforms/transformSchema.ts index 7caa0e14041..4f1adfbd63e 100644 --- a/src/transforms/transformSchema.ts +++ b/src/transforms/transformSchema.ts @@ -46,13 +46,13 @@ export function wrapSchema( } export default function transformSchema( - subschema: GraphQLSchema, + subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, transforms: Array, ): GraphQLSchemaWithTransforms { - const schema: GraphQLSchemaWithTransforms = wrapSchema({ - schema: subschema, + const schema: GraphQLSchemaWithTransforms = wrapSchema( + subschemaOrSubschemaConfig, transforms, - }); + ); schema.transforms = transforms.slice().reverse(); return schema; From 8ff2fda90356b9d89053f69c03a5a1099a4733a7 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 15 Feb 2020 19:41:31 -0500 Subject: [PATCH 188/250] chore(lint): fix a few eslint issues instead of disabling a few rules --- src/test/testErrors.ts | 9 +++------ src/test/testMocking.ts | 3 +-- src/test/testSchemaGenerator.ts | 9 +-------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index eea4b4876f4..f992ed98e51 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -64,8 +64,7 @@ describe('Errors', () => { checkResultAndHandleErrors( result, {}, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - {} as IGraphQLToolsResolveInfo, + ({} as unknown) as IGraphQLToolsResolveInfo, 'responseKey', ); } catch (e) { @@ -82,8 +81,7 @@ describe('Errors', () => { checkResultAndHandleErrors( result, {}, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - {} as IGraphQLToolsResolveInfo, + ({} as unknown) as IGraphQLToolsResolveInfo, 'responseKey', ); } catch (e) { @@ -101,8 +99,7 @@ describe('Errors', () => { checkResultAndHandleErrors( result, {}, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - {} as IGraphQLToolsResolveInfo, + ({} as unknown) as IGraphQLToolsResolveInfo, 'responseKey', ); } catch (e) { diff --git a/src/test/testMocking.ts b/src/test/testMocking.ts index e5ab9f295bf..f5cbdd12250 100644 --- a/src/test/testMocking.ts +++ b/src/test/testMocking.ts @@ -482,8 +482,7 @@ describe('Mock', () => { id: args.id, returnInt: 100, }), - // eslint-disable-next-line @typescript-eslint/no-empty-function - Flying: (_root: any, _args: any): void => {}, + Flying: (_root: any, _args: any) => ({}), }; addMocksToSchema({ schema: jsSchema, mocks: mockMap }); const testQuery = `{ diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 2794a8ce077..62938666768 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -188,13 +188,6 @@ describe('generating schema from shorthand', () => { } `; - const resolve = { - RootQuery: { - // eslint-disable-next-line @typescript-eslint/no-empty-function - species() {}, - }, - }; - const introspectionQuery = `{ species: __type(name: "BirdSpecies"){ name, @@ -295,7 +288,7 @@ describe('generating schema from shorthand', () => { const jsSchema = makeExecutableSchema({ typeDefs: shorthand, - resolvers: resolve, + resolvers: {}, }); const resultPromise = graphql(jsSchema, introspectionQuery); return resultPromise.then(result => From a47550ab4ea072fc024b55f269d71e2769fd86ca Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 16 Feb 2020 01:22:16 -0500 Subject: [PATCH 189/250] feat(graphql): initial v15 support. Does not yet support v15 specific features that require graphql version checking, i.e. interfaces that implement interfaces, but existing tests should pass. A few unnecessary verbose tests deleted. --- .travis.yml | 9 +- package.json | 4 +- .../buildSchemaFromTypeDefinitions.ts | 17 +- src/generate/extensionDefinitions.ts | 37 +++ src/generate/extractExtensionDefinitions.ts | 23 -- src/generate/filterExtensionDefinitions.ts | 19 -- src/generate/index.ts | 5 +- src/stitching/makeRemoteExecutableSchema.ts | 2 - src/stitching/mergeSchemas.ts | 1 + src/test/testAlternateMergeSchemas.ts | 229 +++--------------- src/test/testExtensionExtraction.ts | 8 +- src/test/testSchemaGenerator.ts | 5 +- 12 files changed, 98 insertions(+), 261 deletions(-) create mode 100644 src/generate/extensionDefinitions.ts delete mode 100644 src/generate/extractExtensionDefinitions.ts delete mode 100644 src/generate/filterExtensionDefinitions.ts diff --git a/.travis.yml b/.travis.yml index 5645bedb37f..78a74a2b13a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,14 @@ language: node_js node_js: - - "8" + - "13" + - "12" - "10" install: - npm config set spin=false - npm install -g mocha coveralls - npm install - - npm install graphql@$GRAPHQL_VERSION - - npm install @types/graphql@$TYPES_GRAPHQL_VERSION + - npm install graphql@$GRAPHQL_CHANNEL script: - npm test @@ -19,4 +19,5 @@ script: sudo: false env: - - GRAPHQL_VERSION='^14.0' TYPES_GRAPHQL_VERSION='^14.0' + - GRAPHQL_CHANNEL='latest' + - GRAPHQL_CHANNEL='rc' diff --git a/package.json b/package.json index 39d5b2747aa..3f094339d75 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "uuid": "^3.4.0" }, "peerDependencies": { - "graphql": "^14.2.0" + "graphql": "^14.2.0 || ^15.0.0-rc" }, "devDependencies": { "@types/apollo-upload-client": "^8.1.3", @@ -85,7 +85,7 @@ "eslint-watch": "^6.0.1", "express": "^4.17.1", "express-graphql": "^0.9.0", - "graphql": "^14.5.8", + "graphql": "^14.6.0", "graphql-subscriptions": "^1.1.0", "graphql-type-json": "^0.3.1", "graphql-upload": "^9.0.0", diff --git a/src/generate/buildSchemaFromTypeDefinitions.ts b/src/generate/buildSchemaFromTypeDefinitions.ts index d9f9d9a3c77..4493a002f49 100644 --- a/src/generate/buildSchemaFromTypeDefinitions.ts +++ b/src/generate/buildSchemaFromTypeDefinitions.ts @@ -9,8 +9,10 @@ import { import { ITypeDefinitions, GraphQLParseOptions } from '../Interfaces'; -import filterExtensionDefinitions from './filterExtensionDefinitions'; -import extractExtensionDefinitions from './extractExtensionDefinitions'; +import { + extractExtensionDefinitions, + filterExtensionDefinitions, +} from './extensionDefinitions'; import concatenateTypeDefs from './concatenateTypeDefs'; import SchemaError from './SchemaError'; @@ -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; diff --git a/src/generate/extensionDefinitions.ts b/src/generate/extensionDefinitions.ts new file mode 100644 index 00000000000..0cf29eb9b57 --- /dev/null +++ b/src/generate/extensionDefinitions.ts @@ -0,0 +1,37 @@ +import { DocumentNode, DefinitionNode, Kind } from 'graphql'; + +export function extractExtensionDefinitions(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, + }; +} + +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 c7b4c473fe2..00000000000 --- a/src/generate/extractExtensionDefinitions.ts +++ /dev/null @@ -1,23 +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 { - ...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/index.ts b/src/generate/index.ts index 6f3520b991b..ffe2efa28fe 100644 --- a/src/generate/index.ts +++ b/src/generate/index.ts @@ -9,7 +9,10 @@ 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 { + extractExtensionDefinitions, + filterExtensionDefinitions, +} from './extensionDefinitions'; export { default as SchemaError } from './SchemaError'; // for backwards compatibility diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index e5e6c4533bb..b7c86031271 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -8,7 +8,6 @@ import { BuildSchemaOptions, DocumentNode, } from 'graphql'; -import { Options as PrintSchemaOptions } from 'graphql/utilities/schemaPrinter'; import { addResolversToSchema } from '../generate'; import { Fetcher, Operation } from '../Interfaces'; @@ -40,7 +39,6 @@ export default function makeRemoteExecutableSchema({ fetcher?: Fetcher; createResolver?: (fetcher: Fetcher) => GraphQLFieldResolver; buildSchemaOptions?: BuildSchemaOptions; - printSchemaOptions?: PrintSchemaOptions; }): GraphQLSchema { let finalFetcher: Fetcher = fetcher; diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 2048aa81856..b35a287805d 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -156,6 +156,7 @@ export default function mergeSchemas({ typeof schemaLikeObject === 'string' ? parse(schemaLikeObject) : (schemaLikeObject as DocumentNode); + parsedSchemaDocument.definitions.forEach(def => { const type = typeFromAST(def); if (type instanceof GraphQLDirective && mergeDirectives) { diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index b59c3f28ddb..75ad1ff5d95 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -10,6 +10,7 @@ import { GraphQLObjectTypeConfig, GraphQLFieldConfig, GraphQLObjectType, + versionInfo, } from 'graphql'; import { forAwaitEach } from 'iterall'; import { expect } from 'chai'; @@ -567,7 +568,25 @@ describe('filter and rename object fields', () => { }); it('should filter', () => { - expect(printSchema(transformedPropertySchema)).to.equal(`type New_Location { + if (versionInfo.major >= 15) { + expect(printSchema(transformedPropertySchema)).to.equal(`type Query { + propertyById(id: ID!): New_Property +} + +type New_Property { + new_id: ID! + new_name: String! + new_location: New_Location + new_error: String +} + +type New_Location { + name: String! +} +`); + } else { + expect(printSchema(transformedPropertySchema)).to + .equal(`type New_Location { name: String! } @@ -582,6 +601,7 @@ type Query { propertyById(id: ID!): New_Property } `); + } }); it('should work', async () => { @@ -641,95 +661,10 @@ type Query { }); describe('WrapType transform', () => { - let transformedPropertySchema: GraphQLSchema; - - before(() => { - transformedPropertySchema = transformSchema(propertySchema, [ + it('should work', async () => { + const transformedPropertySchema = transformSchema(propertySchema, [ new WrapType('Query', 'Namespace_Query', 'namespace'), ]); - }); - - it('should modify the schema', () => { - expect(printSchema(transformedPropertySchema)).to.equal(`type Address { - street: String - city: String - state: String - zip: String -} - -"""Simple fake datetime""" -scalar DateTime - -input InputWithDefault { - test: String = "Foo" -} - -""" -The \`JSON\` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). -""" -scalar JSON - -type Location { - name: String! -} - -type Namespace_Query { - propertyById(id: ID!): Property - properties(limit: Int): [Property!] - contextTest(key: String!): String - dateTimeTest: DateTime - jsonTest(input: JSON): JSON - interfaceTest(kind: TestInterfaceKind): TestInterface - unionTest(output: String): TestUnion - errorTest: String - errorTestNonNull: String! - relay: Query! - defaultInputTest(input: InputWithDefault!): String -} - -type Property { - id: ID! - name: String! - location: Location - address: Address - error: String -} - -type Query { - namespace: Namespace_Query -} - -type TestImpl1 implements TestInterface { - kind: TestInterfaceKind - testString: String - foo: String -} - -type TestImpl2 implements TestInterface { - kind: TestInterfaceKind - testString: String - bar: String -} - -interface TestInterface { - kind: TestInterfaceKind - testString: String -} - -enum TestInterfaceKind { - ONE - TWO -} - -union TestUnion = TestImpl1 | UnionImpl - -type UnionImpl { - someField: String -} -`); - }); - - it('should work', async () => { const result = await graphql( transformedPropertySchema, ` @@ -779,111 +714,6 @@ type UnionImpl { }); }); -describe('ExtendSchema transform', () => { - let transformedPropertySchema: GraphQLSchema; - - before(() => { - transformedPropertySchema = transformSchema(propertySchema, [ - new ExtendSchema({ - typeDefs: ` - extend type Property { - locationName: String - wrap: Wrap - } - - type Wrap { - id: ID - name: String - } - `, - }), - ]); - }); - - it('should work', () => { - expect(printSchema(transformedPropertySchema)).to.equal(`type Address { - street: String - city: String - state: String - zip: String -} - -"""Simple fake datetime""" -scalar DateTime - -input InputWithDefault { - test: String = "Foo" -} - -""" -The \`JSON\` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). -""" -scalar JSON - -type Location { - name: String! -} - -type Property { - id: ID! - name: String! - location: Location - address: Address - error: String - locationName: String - wrap: Wrap -} - -type Query { - propertyById(id: ID!): Property - properties(limit: Int): [Property!] - contextTest(key: String!): String - dateTimeTest: DateTime - jsonTest(input: JSON): JSON - interfaceTest(kind: TestInterfaceKind): TestInterface - unionTest(output: String): TestUnion - errorTest: String - errorTestNonNull: String! - relay: Query! - defaultInputTest(input: InputWithDefault!): String -} - -type TestImpl1 implements TestInterface { - kind: TestInterfaceKind - testString: String - foo: String -} - -type TestImpl2 implements TestInterface { - kind: TestInterfaceKind - testString: String - bar: String -} - -interface TestInterface { - kind: TestInterfaceKind - testString: String -} - -enum TestInterfaceKind { - ONE - TWO -} - -union TestUnion = TestImpl1 | UnionImpl - -type UnionImpl { - someField: String -} - -type Wrap { - id: ID - name: String -} -`); - }); -}); - describe('schema transformation with extraction of nested fields', () => { it('should work via ExtendSchema transform', async () => { const transformedPropertySchema = transformSchema(propertySchema, [ @@ -1444,7 +1274,18 @@ describe('mergeSchemas', () => { const query = '{ getInput(input: {}) }'; const response = await graphql(mergedSchema, query); - expect(printSchema(schema)).to.equal(printSchema(mergedSchema)); + if (versionInfo.major >= 15) { + expect(printSchema(schema)).to.equal(`input InputWithDefault { + field: String = "test" +} + +type Query { + getInput(input: InputWithDefault!): String +} +`); + } else { + expect(printSchema(schema)).to.equal(printSchema(mergedSchema)); + } expect(response.data?.getInput).to.equal('test'); }); diff --git a/src/test/testExtensionExtraction.ts b/src/test/testExtensionExtraction.ts index 9cac63cf502..ae726991f92 100644 --- a/src/test/testExtensionExtraction.ts +++ b/src/test/testExtensionExtraction.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { parse } from 'graphql'; -import extractExtensionDefinitons from '../generate/extractExtensionDefinitions'; +import { extractExtensionDefinitions } from '../generate/extensionDefinitions'; describe('Extension extraction', () => { it('extracts extended inputs', () => { @@ -16,7 +16,7 @@ describe('Extension extraction', () => { `; const astDocument = parse(typeDefs); - const extensionAst = extractExtensionDefinitons(astDocument); + const extensionAst = extractExtensionDefinitions(astDocument); expect(extensionAst.definitions).to.have.length(1); expect(extensionAst.definitions[0].kind).to.equal( @@ -41,7 +41,7 @@ describe('Extension extraction', () => { `; const astDocument = parse(typeDefs); - const extensionAst = extractExtensionDefinitons(astDocument); + const extensionAst = extractExtensionDefinitions(astDocument); expect(extensionAst.definitions).to.have.length(1); expect(extensionAst.definitions[0].kind).to.equal('UnionTypeExtension'); @@ -60,7 +60,7 @@ describe('Extension extraction', () => { `; const astDocument = parse(typeDefs); - const extensionAst = extractExtensionDefinitons(astDocument); + const extensionAst = extractExtensionDefinitions(astDocument); expect(extensionAst.definitions).to.have.length(1); expect(extensionAst.definitions[0].kind).to.equal('EnumTypeExtension'); diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 62938666768..64a7d7607f4 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -19,6 +19,7 @@ import { GraphQLBoolean, graphqlSync, GraphQLSchema, + versionInfo, } from 'graphql'; import { Logger } from '../Logger'; @@ -257,7 +258,7 @@ describe('generating schema from shorthand', () => { }, query: { name: 'RootQuery', - description: '', + description: null as string, fields: [ { name: 'species', @@ -1776,7 +1777,7 @@ describe('generating schema from shorthand', () => { }, errorMatcher); } - assertFieldError('Bird.id', {}); + assertFieldError(versionInfo.major >= 15 ? 'Query.bird' : 'Bird.id', {}); assertFieldError('Query.bird', { Bird: { id: (bird: { id: string }) => bird.id, From 9a0186c6ca0f6aa2ee14ccece1845509d7f28247 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 16 Feb 2020 14:59:53 -0500 Subject: [PATCH 190/250] chore(test): fix graphql v14 test --- src/test/testSchemaGenerator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 64a7d7607f4..eb33f571694 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -258,7 +258,7 @@ describe('generating schema from shorthand', () => { }, query: { name: 'RootQuery', - description: null as string, + description: versionInfo.major >= 15 ? (null as string) : '', fields: [ { name: 'species', From e05dec7a6b60a2f967b64e17f514a7df6baa0321 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 16 Feb 2020 16:59:17 -0500 Subject: [PATCH 191/250] feat(graphql): interfaces implementing interfaces --- .travis.yml | 5 ++++- package.json | 5 ++--- src/generate/extendResolversFromInterfaces.ts | 12 ++++++++++-- src/stitching/mergeSchemas.ts | 9 +++++++++ src/stitching/typeFromAST.ts | 12 ++++++++++++ src/test/testingSchemas.ts | 11 ++++++++++- src/utils/clone.ts | 8 +++++++- src/utils/heal.ts | 11 +++++++++-- 8 files changed, 63 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 78a74a2b13a..5276c394ae8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,10 @@ install: - npm install graphql@$GRAPHQL_CHANNEL script: - - npm test + - if [ $GRAPHQL_CHANNEL == 'rc' ]; + then npm test && npm run lint && npm run prettier-check; + else npm test; + fi - npm run coverage - coveralls < ./coverage/lcov.info || true # if coveralls doesn't have it covered diff --git a/package.json b/package.json index 3f094339d75..fc5ace01c2c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "compile": "tsc", "pretest": "npm run clean && npm run compile", "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", @@ -20,9 +19,9 @@ "coverage": "istanbul cover _mocha -- --reporter dot --full-trace ./dist/test/**.js", "postcoverage": "remap-istanbul --input coverage/coverage.json --type lcovonly --output coverage/lcov.info", "prepublishOnly": "npm run compile", - "prerelease": "npm test", "prettier": "prettier --trailing-comma all --single-quote --write src/**/*.ts", "prettier:check": "prettier --trailing-comma all --single-quote --check src/**/*.ts", + "prerelease": "npm test && npm run lint && npm run prettier:check", "release": "npm run releaseonly", "releaseonly": "standard-version" }, @@ -85,7 +84,7 @@ "eslint-watch": "^6.0.1", "express": "^4.17.1", "express-graphql": "^0.9.0", - "graphql": "^14.6.0", + "graphql": "^15.0.0-rc.2", "graphql-subscriptions": "^1.1.0", "graphql-type-json": "^0.3.1", "graphql-upload": "^9.0.0", diff --git a/src/generate/extendResolversFromInterfaces.ts b/src/generate/extendResolversFromInterfaces.ts index 5783c7b72e4..d0684274cdf 100644 --- a/src/generate/extendResolversFromInterfaces.ts +++ b/src/generate/extendResolversFromInterfaces.ts @@ -1,4 +1,9 @@ -import { GraphQLObjectType, GraphQLSchema } from 'graphql'; +import { + GraphQLObjectType, + GraphQLSchema, + versionInfo, + GraphQLInterfaceType, +} from 'graphql'; import { IResolvers } from '../Interfaces'; @@ -15,7 +20,10 @@ function extendResolversFromInterfaces( typeNames.forEach(typeName => { const typeResolvers = resolvers[typeName]; const type = schema.getType(typeName); - if (type instanceof GraphQLObjectType) { + if ( + type instanceof GraphQLObjectType || + (versionInfo.major >= 15 && type instanceof GraphQLInterfaceType) + ) { const interfaceResolvers = type .getInterfaces() .map(iFace => resolvers[iFace.name]); diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index b35a287805d..350a4a4a25b 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -14,6 +14,7 @@ import { GraphQLUnionType, GraphQLEnumType, ASTNode, + versionInfo, } from 'graphql'; import { @@ -361,6 +362,14 @@ function merge( }), {}, ), + interfaces: + versionInfo.major >= 15 + ? candidates.reduce((acc, candidate) => { + const interfaces = (candidate.type as GraphQLInterfaceType).toConfig() + .interfaces; + return interfaces != null ? acc.concat(interfaces) : acc; + }, []) + : undefined, }); } else if (initialCandidateType instanceof GraphQLUnionType) { return new GraphQLUnionType({ diff --git a/src/stitching/typeFromAST.ts b/src/stitching/typeFromAST.ts index 414c5b040d0..c27b7bf1b99 100644 --- a/src/stitching/typeFromAST.ts +++ b/src/stitching/typeFromAST.ts @@ -28,6 +28,7 @@ import { DirectiveLocation, GraphQLFieldConfig, StringValueNode, + versionInfo, } from 'graphql'; import { createNamedStub } from '../utils/stub'; @@ -87,6 +88,17 @@ function makeInterfaceType( return new GraphQLInterfaceType({ name: node.name.value, fields: () => makeFields(node.fields), + interfaces: + versionInfo.major >= 15 + ? () => + node.interfaces.map( + iface => + createNamedStub( + iface.name.value, + 'interface', + ) as GraphQLInterfaceType, + ) + : undefined, description: getDescription(node, backcompatOptions), resolveType: parent => resolveFromParentTypename(parent), }); diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index dd9a7cb0a7c..178223be098 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -15,6 +15,7 @@ import { ExecutionResult, Source, GraphQLResolveInfo, + versionInfo, } from 'graphql'; import { forAwaitEach } from 'iterall'; @@ -272,7 +273,15 @@ const propertyRootTypeDefs = ` foo: String } - type TestImpl2 implements TestInterface { + ${ + versionInfo.major >= 15 + ? `interface TestNestedInterface implements TestInterface { + kind: TestInterfaceKind + testString: String + } + type TestImpl2 implements TestNestedInterface & TestInterface {` + : 'type TestImpl2 implements TestInterface' + } kind: TestInterfaceKind testString: String bar: String diff --git a/src/utils/clone.ts b/src/utils/clone.ts index c6e7afb3e21..c02447aa749 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -8,6 +8,7 @@ import { GraphQLScalarType, GraphQLSchema, GraphQLUnionType, + versionInfo, } from 'graphql'; import { healTypes } from './heal'; @@ -25,7 +26,12 @@ export function cloneType(type: GraphQLNamedType): GraphQLNamedType { interfaces: config.interfaces.slice(), }); } else if (type instanceof GraphQLInterfaceType) { - return new GraphQLInterfaceType(type.toConfig()); + const config = type.toConfig(); + return new GraphQLInterfaceType({ + ...config, + interfaces: + versionInfo.major >= 15 ? config.interfaces.slice() : undefined, + }); } else if (type instanceof GraphQLUnionType) { const config = type.toConfig(); return new GraphQLUnionType({ diff --git a/src/utils/heal.ts b/src/utils/heal.ts index fda52090c05..d9dbe834192 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -14,6 +14,7 @@ import { GraphQLSchema, GraphQLInputType, GraphQLOutputType, + versionInfo, } from 'graphql'; import each from './each'; @@ -123,6 +124,9 @@ export function healTypes( return; } else if (type instanceof GraphQLInterfaceType) { healFields(type); + if (versionInfo.major >= 15) { + healInterfaces(type); + } return; } else if (type instanceof GraphQLUnionType) { healUnderlyingTypes(type); @@ -151,7 +155,7 @@ export function healTypes( }); } - function healInterfaces(type: GraphQLObjectType) { + function healInterfaces(type: GraphQLObjectType | GraphQLInterfaceType) { updateEachKey(type.getInterfaces(), iface => { const healedType = healType(iface) as GraphQLInterfaceType; return healedType; @@ -209,7 +213,10 @@ function pruneTypes( ) { const implementedInterfaces = {}; each(typeMap, namedType => { - if (namedType instanceof GraphQLObjectType) { + if ( + namedType instanceof GraphQLObjectType || + (versionInfo.major >= 15 && namedType instanceof GraphQLInterfaceType) + ) { each(namedType.getInterfaces(), iface => { implementedInterfaces[iface.name] = true; }); From dcf18bd57ff27b15f2713bd4c22aecfa6829e0d5 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 16 Feb 2020 20:13:01 -0500 Subject: [PATCH 192/250] fix(ci): fix types tsc must run successfully for both graphql v14 and v15 --- .travis.yml | 5 +---- package.json | 5 +++-- src/generate/extendResolversFromInterfaces.ts | 2 +- src/stitching/mergeSchemas.ts | 7 ++++--- src/stitching/typeFromAST.ts | 14 ++++++++------ src/utils/clone.ts | 10 +++++++--- src/utils/heal.ts | 4 ++-- 7 files changed, 26 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5276c394ae8..78a74a2b13a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,10 +11,7 @@ install: - npm install graphql@$GRAPHQL_CHANNEL script: - - if [ $GRAPHQL_CHANNEL == 'rc' ]; - then npm test && npm run lint && npm run prettier-check; - else npm test; - fi + - npm test - npm run coverage - coveralls < ./coverage/lcov.info || true # if coveralls doesn't have it covered diff --git a/package.json b/package.json index fc5ace01c2c..4afb9b94201 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "compile": "tsc", "pretest": "npm run clean && npm run compile", "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", @@ -21,7 +22,7 @@ "prepublishOnly": "npm run compile", "prettier": "prettier --trailing-comma all --single-quote --write src/**/*.ts", "prettier:check": "prettier --trailing-comma all --single-quote --check src/**/*.ts", - "prerelease": "npm test && npm run lint && npm run prettier:check", + "prerelease": "npm test", "release": "npm run releaseonly", "releaseonly": "standard-version" }, @@ -84,7 +85,7 @@ "eslint-watch": "^6.0.1", "express": "^4.17.1", "express-graphql": "^0.9.0", - "graphql": "^15.0.0-rc.2", + "graphql": "^14.6.0", "graphql-subscriptions": "^1.1.0", "graphql-type-json": "^0.3.1", "graphql-upload": "^9.0.0", diff --git a/src/generate/extendResolversFromInterfaces.ts b/src/generate/extendResolversFromInterfaces.ts index d0684274cdf..b0ef7d02061 100644 --- a/src/generate/extendResolversFromInterfaces.ts +++ b/src/generate/extendResolversFromInterfaces.ts @@ -24,7 +24,7 @@ function extendResolversFromInterfaces( type instanceof GraphQLObjectType || (versionInfo.major >= 15 && type instanceof GraphQLInterfaceType) ) { - const interfaceResolvers = type + const interfaceResolvers = (type as GraphQLObjectType) .getInterfaces() .map(iFace => resolvers[iFace.name]); extendedResolvers[typeName] = Object.assign( diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 350a4a4a25b..de0ab71e89b 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -353,7 +353,7 @@ function merge( }, []), }); } else if (initialCandidateType instanceof GraphQLInterfaceType) { - return new GraphQLInterfaceType({ + const config = { name: typeName, fields: candidates.reduce( (acc, candidate) => ({ @@ -365,12 +365,13 @@ function merge( interfaces: versionInfo.major >= 15 ? candidates.reduce((acc, candidate) => { - const interfaces = (candidate.type as GraphQLInterfaceType).toConfig() + const interfaces = (candidate.type as GraphQLObjectType).toConfig() .interfaces; return interfaces != null ? acc.concat(interfaces) : acc; }, []) : undefined, - }); + }; + return new GraphQLInterfaceType(config); } else if (initialCandidateType instanceof GraphQLUnionType) { return new GraphQLUnionType({ name: typeName, diff --git a/src/stitching/typeFromAST.ts b/src/stitching/typeFromAST.ts index c27b7bf1b99..70d300f0ee4 100644 --- a/src/stitching/typeFromAST.ts +++ b/src/stitching/typeFromAST.ts @@ -67,7 +67,7 @@ export default function typeFromAST( } function makeObjectType(node: ObjectTypeDefinitionNode): GraphQLObjectType { - return new GraphQLObjectType({ + const config = { name: node.name.value, fields: () => makeFields(node.fields), interfaces: () => @@ -79,19 +79,20 @@ function makeObjectType(node: ObjectTypeDefinitionNode): GraphQLObjectType { ) as GraphQLInterfaceType, ), description: getDescription(node, backcompatOptions), - }); + }; + return new GraphQLObjectType(config); } function makeInterfaceType( node: InterfaceTypeDefinitionNode, ): GraphQLInterfaceType { - return new GraphQLInterfaceType({ + const config = { name: node.name.value, fields: () => makeFields(node.fields), interfaces: versionInfo.major >= 15 ? () => - node.interfaces.map( + ((node as unknown) as ObjectTypeDefinitionNode).interfaces.map( iface => createNamedStub( iface.name.value, @@ -100,8 +101,9 @@ function makeInterfaceType( ) : undefined, description: getDescription(node, backcompatOptions), - resolveType: parent => resolveFromParentTypename(parent), - }); + resolveType: (parent: any) => resolveFromParentTypename(parent), + }; + return new GraphQLInterfaceType(config); } function makeEnumType(node: EnumTypeDefinitionNode): GraphQLEnumType { diff --git a/src/utils/clone.ts b/src/utils/clone.ts index c02447aa749..34837f2e2c2 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -9,6 +9,7 @@ import { GraphQLSchema, GraphQLUnionType, versionInfo, + GraphQLInterfaceTypeConfig, } from 'graphql'; import { healTypes } from './heal'; @@ -26,12 +27,15 @@ export function cloneType(type: GraphQLNamedType): GraphQLNamedType { interfaces: config.interfaces.slice(), }); } else if (type instanceof GraphQLInterfaceType) { - const config = type.toConfig(); - return new GraphQLInterfaceType({ + const config = ((type as unknown) as GraphQLObjectType).toConfig(); + const newConfig = { ...config, interfaces: versionInfo.major >= 15 ? config.interfaces.slice() : undefined, - }); + }; + return new GraphQLInterfaceType( + (newConfig as unknown) as GraphQLInterfaceTypeConfig, + ); } else if (type instanceof GraphQLUnionType) { const config = type.toConfig(); return new GraphQLUnionType({ diff --git a/src/utils/heal.ts b/src/utils/heal.ts index d9dbe834192..778a95b1ea4 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -156,7 +156,7 @@ export function healTypes( } function healInterfaces(type: GraphQLObjectType | GraphQLInterfaceType) { - updateEachKey(type.getInterfaces(), iface => { + updateEachKey((type as GraphQLObjectType).getInterfaces(), iface => { const healedType = healType(iface) as GraphQLInterfaceType; return healedType; }); @@ -217,7 +217,7 @@ function pruneTypes( namedType instanceof GraphQLObjectType || (versionInfo.major >= 15 && namedType instanceof GraphQLInterfaceType) ) { - each(namedType.getInterfaces(), iface => { + each((namedType as GraphQLObjectType).getInterfaces(), iface => { implementedInterfaces[iface.name] = true; }); } From 1f1ec072655313411afd287591f3880b00bd71d7 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 16 Feb 2020 20:35:18 -0500 Subject: [PATCH 193/250] fix(typo): caused by prettier? --- src/test/testingSchemas.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index 178223be098..6dc96a5b89c 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -279,12 +279,17 @@ const propertyRootTypeDefs = ` kind: TestInterfaceKind testString: String } - type TestImpl2 implements TestNestedInterface & TestInterface {` - : 'type TestImpl2 implements TestInterface' - } + + type TestImpl2 implements TestNestedInterface & TestInterface { + kind: TestInterfaceKind + testString: String + bar: String + }` + : `type TestImpl2 implements TestInterface { kind: TestInterfaceKind testString: String bar: String + }` } type UnionImpl { From a509d3e67be517c77fde64a26313ee2ecdd8f4c9 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 17 Feb 2020 11:02:11 -0500 Subject: [PATCH 194/250] fix(typescript): remove dom requirement --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 0e391487814..2e4b27f2603 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "experimentalDecorators": true, - "lib": ["es7", "dom", "esnext.asynciterable"], + "lib": ["es7", "esnext.asynciterable"], "module": "commonjs", "target": "es5", "noImplicitAny": true, From 3b46556643bf9cb507795599f774d110d78abd72 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 18 Feb 2020 21:07:43 -0500 Subject: [PATCH 195/250] chore(deps): remove unnecessary dependency @types/supertest was also hiding need for explicit inclusion of dom within typescript libs to avoid apollo-link-http-common related errors on node, see https://github.com/yaacovCR/graphql-tools-fork/issues/41 and https://github.com/apollographql/apollo-link/issues/544 --- package.json | 3 +-- tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 4afb9b94201..7c4a801baba 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,6 @@ "@types/mocha": "^7.0.1", "@types/node": "^13.7.1", "@types/node-fetch": "^2.5.4", - "@types/supertest": "^2.0.8", "@types/uuid": "^3.4.7", "@typescript-eslint/eslint-plugin": "^2.19.2", "@typescript-eslint/parser": "^2.19.2", @@ -96,7 +95,7 @@ "rimraf": "^3.0.2", "source-map-support": "^0.5.16", "standard-version": "^7.1.0", - "typescript": "3.7.5", + "typescript": "^3.7.5", "zen-observable-ts": "^0.8.20" } } diff --git a/tsconfig.json b/tsconfig.json index 2e4b27f2603..5ca070bef67 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "experimentalDecorators": true, - "lib": ["es7", "esnext.asynciterable"], + "lib": ["es7", "esnext.asynciterable", "dom"], "module": "commonjs", "target": "es5", "noImplicitAny": true, From e00003a34783ed8b834108312078b97672b0aa5b Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 23 Feb 2020 20:59:25 -0500 Subject: [PATCH 196/250] chore(deps): remove dev dependency @types/apollo-upload-client not necessary --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 7c4a801baba..bedaa783bb7 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "graphql": "^14.2.0 || ^15.0.0-rc" }, "devDependencies": { - "@types/apollo-upload-client": "^8.1.3", "@types/chai": "^4.2.9", "@types/dateformat": "^3.0.1", "@types/express": "^4.17.2", From 74ba001f51d42febc36f8f0ba12e2b9cb4634bfb Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 26 Feb 2020 23:21:36 -0500 Subject: [PATCH 197/250] chore(deps): support graphql v0.12 and above --- .travis.yml | 15 +- package.json | 2 +- src/generate/addResolversToSchema.ts | 41 +-- src/generate/extendResolversFromInterfaces.ts | 4 +- src/generate/extensionDefinitions.ts | 4 +- src/stitching/makeRemoteExecutableSchema.ts | 5 +- src/stitching/mergeSchemas.ts | 46 ++- src/stitching/typeFromAST.ts | 112 ++++++- src/test/testAlternateMergeSchemas.ts | 141 +++++--- src/test/testMergeSchemas.ts | 90 ++--- src/test/testSchemaGenerator.ts | 7 +- src/test/testUtils.ts | 10 +- src/test/testingSchemas.ts | 16 +- src/transforms/TransformObjectFields.ts | 6 +- src/transforms/filterSchema.ts | 7 +- src/utils/clone.ts | 30 +- src/utils/fields.ts | 7 +- src/utils/graphqlVersion.ts | 24 ++ src/utils/heal.ts | 6 +- src/utils/index.ts | 13 + src/utils/toConfig.ts | 310 ++++++++++++++++++ 21 files changed, 722 insertions(+), 174 deletions(-) create mode 100644 src/utils/graphqlVersion.ts create mode 100644 src/utils/toConfig.ts diff --git a/.travis.yml b/.travis.yml index 78a74a2b13a..b46d812edfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,14 +4,17 @@ node_js: - "12" - "10" -install: +install: - npm config set spin=false - npm install -g mocha coveralls - npm install - - npm install graphql@$GRAPHQL_CHANNEL script: - - npm test + - node_version=$(node -v); if [[ ${node_version:1:2} == 13 && $GRAPHQL_VERSION == 14 ]]; then npm run lint; fi + - node_version=$(node -v); if [[ ${node_version:1:2} == 13 && $GRAPHQL_VERSION == 14 ]]; then npm run prettier:check; fi + - npm run compile + - npm install graphql@$GRAPHQL_VERSION + - npm run testonly - npm run coverage - coveralls < ./coverage/lcov.info || true # if coveralls doesn't have it covered @@ -19,5 +22,7 @@ script: sudo: false env: - - GRAPHQL_CHANNEL='latest' - - GRAPHQL_CHANNEL='rc' + - GRAPHQL_VERSION='0.12' + - GRAPHQL_VERSION='0.13' + - GRAPHQL_VERSION='14' + - GRAPHQL_VERSION='rc' diff --git a/package.json b/package.json index bedaa783bb7..ac0516c5cdd 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "uuid": "^3.4.0" }, "peerDependencies": { - "graphql": "^14.2.0 || ^15.0.0-rc" + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc" }, "devDependencies": { "@types/chai": "^4.2.9", diff --git a/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index b5821db6a6b..2d003c79314 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -5,7 +5,6 @@ import { GraphQLSchema, GraphQLObjectType, GraphQLInterfaceType, - isSpecifiedScalarType, GraphQLUnionType, } from 'graphql'; @@ -20,6 +19,7 @@ import { healSchema, forEachField, forEachDefaultValue, + typeToConfig, } from '../utils'; import SchemaError from './SchemaError'; @@ -82,35 +82,14 @@ function addResolversToSchema( } if (type instanceof GraphQLScalarType) { - if (isSpecifiedScalarType(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 { - // Otherwise the existing schema types are not changed, just replaced. - const config = type.toConfig(); - - Object.keys(resolverValue).forEach(fieldName => { - // Below is necessary as legacy code for scalar type specification allowed - // hardcoding within the resolver an object with fields '__serialize', - // '__parse', and '__parseLiteral', see examples in testMocking.ts. - // Luckily, the fields on GraphQLScalarType and GraphQLScalarTypeConfig - // are named the same. - if (fieldName.startsWith('__')) { - config[fieldName.substring(2)] = resolverValue[fieldName]; - } else { - config[fieldName] = resolverValue[fieldName]; - } - }); - - // healSchema called later to update all fields to new type - typeMap[typeName] = new GraphQLScalarType(config); - } + // 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 (type instanceof GraphQLEnumType) { // We've encountered an enum resolver that is being used to provide an // internal enum value. @@ -126,7 +105,7 @@ function addResolversToSchema( } }); - const config = type.toConfig(); + const config = typeToConfig(type); const values = type.getValues(); const newValues = {}; diff --git a/src/generate/extendResolversFromInterfaces.ts b/src/generate/extendResolversFromInterfaces.ts index b0ef7d02061..e6f9af1d3bd 100644 --- a/src/generate/extendResolversFromInterfaces.ts +++ b/src/generate/extendResolversFromInterfaces.ts @@ -1,11 +1,11 @@ import { GraphQLObjectType, GraphQLSchema, - versionInfo, GraphQLInterfaceType, } from 'graphql'; import { IResolvers } from '../Interfaces'; +import { graphqlVersion } from '../utils'; function extendResolversFromInterfaces( schema: GraphQLSchema, @@ -22,7 +22,7 @@ function extendResolversFromInterfaces( const type = schema.getType(typeName); if ( type instanceof GraphQLObjectType || - (versionInfo.major >= 15 && type instanceof GraphQLInterfaceType) + (graphqlVersion() >= 15 && type instanceof GraphQLInterfaceType) ) { const interfaceResolvers = (type as GraphQLObjectType) .getInterfaces() diff --git a/src/generate/extensionDefinitions.ts b/src/generate/extensionDefinitions.ts index 0cf29eb9b57..d491589aa14 100644 --- a/src/generate/extensionDefinitions.ts +++ b/src/generate/extensionDefinitions.ts @@ -1,10 +1,12 @@ import { DocumentNode, DefinitionNode, Kind } from 'graphql'; +import { graphqlVersion } from '../utils'; + export function extractExtensionDefinitions(ast: DocumentNode) { const extensionDefs = ast.definitions.filter( (def: DefinitionNode) => def.kind === Kind.OBJECT_TYPE_EXTENSION || - def.kind === Kind.INTERFACE_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 || diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index b7c86031271..f1b426f3b8f 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -2,11 +2,12 @@ import { ApolloLink } from 'apollo-link'; import { GraphQLFieldResolver, GraphQLSchema, - buildSchema, Kind, GraphQLResolveInfo, BuildSchemaOptions, DocumentNode, + buildASTSchema, + parse, } from 'graphql'; import { addResolversToSchema } from '../generate'; @@ -48,7 +49,7 @@ export default function makeRemoteExecutableSchema({ const targetSchema = typeof schemaOrTypeDefs === 'string' - ? buildSchema(schemaOrTypeDefs, buildSchemaOptions) + ? buildASTSchema(parse(schemaOrTypeDefs), buildSchemaOptions) : schemaOrTypeDefs; const remoteSchema = cloneSchema(targetSchema); diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index de0ab71e89b..d5f3978ce83 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -14,7 +14,6 @@ import { GraphQLUnionType, GraphQLEnumType, ASTNode, - versionInfo, } from 'graphql'; import { @@ -37,6 +36,9 @@ import { healTypes, forEachField, mergeDeep, + typeToConfig, + graphqlVersion, + getResolversFromSchema, } from '../utils'; import typeFromAST from './typeFromAST'; @@ -244,11 +246,28 @@ export default function mergeSchemas({ : undefined, }); - extensions.forEach(extension => { - mergedSchema = (extendSchema as any)(mergedSchema, extension, { - commentDescriptions: true, + let proxyingResolvers: IResolvers; + if (graphqlVersion() >= 14) { + extensions.forEach(extension => { + mergedSchema = extendSchema(mergedSchema, extension, { + commentDescriptions: true, + }); }); - }); + } else { + // extendSchema in graphql < v14 does not support subscriptions? + proxyingResolvers = getResolversFromSchema(mergedSchema); + + extensions.forEach(extension => { + mergedSchema = extendSchema(mergedSchema, extension, { + commentDescriptions: true, + }); + }); + + addResolversToSchema({ + schema: mergedSchema, + resolvers: proxyingResolvers, + }); + } addResolversToSchema({ schema: mergedSchema, @@ -342,12 +361,12 @@ function merge( fields: candidates.reduce( (acc, candidate) => ({ ...acc, - ...(candidate.type as GraphQLObjectType).toConfig().fields, + ...typeToConfig(candidate.type as GraphQLObjectType).fields, }), {}, ), interfaces: candidates.reduce((acc, candidate) => { - const interfaces = (candidate.type as GraphQLObjectType).toConfig() + const interfaces = typeToConfig(candidate.type as GraphQLObjectType) .interfaces; return interfaces != null ? acc.concat(interfaces) : acc; }, []), @@ -358,15 +377,16 @@ function merge( fields: candidates.reduce( (acc, candidate) => ({ ...acc, - ...(candidate.type as GraphQLObjectType).toConfig().fields, + ...typeToConfig(candidate.type as GraphQLObjectType).fields, }), {}, ), interfaces: - versionInfo.major >= 15 + graphqlVersion() >= 15 ? candidates.reduce((acc, candidate) => { - const interfaces = (candidate.type as GraphQLObjectType).toConfig() - .interfaces; + const interfaces = typeToConfig( + candidate.type as GraphQLObjectType, + ).interfaces; return interfaces != null ? acc.concat(interfaces) : acc; }, []) : undefined, @@ -377,7 +397,7 @@ function merge( name: typeName, types: candidates.reduce( (acc, candidate) => - acc.concat((candidate.type as GraphQLUnionType).toConfig().types), + acc.concat(typeToConfig(candidate.type as GraphQLUnionType).types), [], ), }); @@ -387,7 +407,7 @@ function merge( values: candidates.reduce( (acc, candidate) => ({ ...acc, - ...(candidate.type as GraphQLEnumType).toConfig().values, + ...typeToConfig(candidate.type as GraphQLEnumType).values, }), {}, ), diff --git a/src/stitching/typeFromAST.ts b/src/stitching/typeFromAST.ts index 70d300f0ee4..56f1ac992c4 100644 --- a/src/stitching/typeFromAST.ts +++ b/src/stitching/typeFromAST.ts @@ -21,17 +21,17 @@ import { ScalarTypeDefinitionNode, TypeNode, UnionTypeDefinitionNode, - getDescription, GraphQLDirective, DirectiveDefinitionNode, DirectiveLocationEnum, DirectiveLocation, GraphQLFieldConfig, StringValueNode, - versionInfo, + Location, + TokenKind, } from 'graphql'; -import { createNamedStub } from '../utils/stub'; +import { createNamedStub, graphqlVersion } from '../utils'; import resolveFromParentTypename from './resolveFromParentTypename'; @@ -90,7 +90,7 @@ function makeInterfaceType( name: node.name.value, fields: () => makeFields(node.fields), interfaces: - versionInfo.major >= 15 + graphqlVersion() >= 15 ? () => ((node as unknown) as ObjectTypeDefinitionNode).interfaces.map( iface => @@ -223,3 +223,107 @@ function makeDirective(node: DirectiveDefinitionNode): GraphQLDirective { locations, }); } + +// graphql < v13 does not export getDescription + +function getDescription( + node: { description?: StringValueNode; loc?: Location }, + options?: { commentDescriptions?: boolean }, +): string { + if (node.description != null) { + return node.description.value; + } + if (options.commentDescriptions) { + const rawValue = getLeadingCommentBlock(node); + if (rawValue !== undefined) { + return dedentBlockStringValue(`\n${rawValue as string}`); + } + } +} + +function getLeadingCommentBlock(node: { + description?: StringValueNode; + loc?: Location; +}): void | string { + const loc = node.loc; + if (!loc) { + return; + } + const comments = []; + let token = loc.startToken.prev; + while ( + token != null && + token.kind === TokenKind.COMMENT && + token.next != null && + token.prev != null && + token.line + 1 === token.next.line && + token.line !== token.prev.line + ) { + const value = String(token.value); + comments.push(value); + token = token.prev; + } + return comments.length > 0 ? comments.reverse().join('\n') : undefined; +} + +function dedentBlockStringValue(rawString: string): string { + // Expand a block string's raw value into independent lines. + const lines = rawString.split(/\r\n|[\n\r]/g); + + // Remove common indentation from all lines but first. + const commonIndent = getBlockStringIndentation(lines); + + if (commonIndent !== 0) { + for (let i = 1; i < lines.length; i++) { + lines[i] = lines[i].slice(commonIndent); + } + } + + // Remove leading and trailing blank lines. + while (lines.length > 0 && isBlank(lines[0])) { + lines.shift(); + } + while (lines.length > 0 && isBlank(lines[lines.length - 1])) { + lines.pop(); + } + + // Return a string of the lines joined with U+000A. + return lines.join('\n'); +} +/** + * @internal + */ +export function getBlockStringIndentation( + lines: ReadonlyArray, +): number { + let commonIndent = null; + + for (let i = 1; i < lines.length; i++) { + const line = lines[i]; + const indent = leadingWhitespace(line); + if (indent === line.length) { + continue; // skip empty lines + } + + if (commonIndent === null || indent < commonIndent) { + commonIndent = indent; + if (commonIndent === 0) { + break; + } + } + } + + return commonIndent === null ? 0 : commonIndent; +} + +function leadingWhitespace(str: string) { + let i = 0; + while (i < str.length && (str[i] === ' ' || str[i] === '\t')) { + i++; + } + return i; +} + +function isBlank(str: string) { + return leadingWhitespace(str) === str.length; +} diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 75ad1ff5d95..45e457b8992 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -7,10 +7,8 @@ import { GraphQLScalarType, FieldNode, printSchema, - GraphQLObjectTypeConfig, GraphQLFieldConfig, GraphQLObjectType, - versionInfo, } from 'graphql'; import { forAwaitEach } from 'iterall'; import { expect } from 'chai'; @@ -38,7 +36,13 @@ import { } from '../stitching'; import { SubschemaConfig } from '../Interfaces'; import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; -import { wrapFieldNode, renameFieldNode, hoistFieldNodes } from '../utils'; +import { + wrapFieldNode, + renameFieldNode, + hoistFieldNodes, + typeToConfig, + graphqlVersion, +} from '../utils'; import { propertySchema, @@ -351,10 +355,7 @@ describe('transform object fields', () => { return undefined; } const type = propertySchema.getType(typeName) as GraphQLObjectType; - const typeConfig = type.toConfig() as GraphQLObjectTypeConfig< - any, - any - >; + const typeConfig = typeToConfig(type); const fieldConfig = typeConfig.fields[ fieldName ] as GraphQLFieldConfig; @@ -529,7 +530,7 @@ describe('transform object fields', () => { `, ); - expect(result).to.deep.equal({ + const expectedResult: any = { errors: [ { locations: [ @@ -541,7 +542,13 @@ describe('transform object fields', () => { message: 'Cannot query field "id" on type "Item".', }, ], - }); + }; + + if (graphqlVersion() < 14) { + expectedResult.errors[0].path = undefined; + } + + expect(result).to.deep.equal(expectedResult); }); }); @@ -568,7 +575,7 @@ describe('filter and rename object fields', () => { }); it('should filter', () => { - if (versionInfo.major >= 15) { + if (graphqlVersion() >= 15) { expect(printSchema(transformedPropertySchema)).to.equal(`type Query { propertyById(id: ID!): New_Property } @@ -626,7 +633,7 @@ type Query { }, ); - expect(result).to.deep.equal({ + const expectedResult: any = { data: { propertyById: { // eslint-disable-next-line camelcase @@ -643,9 +650,6 @@ type Query { }, errors: [ { - extensions: { - code: 'SOME_CUSTOM_CODE', - }, locations: [ { column: 13, @@ -656,6 +660,17 @@ type Query { path: ['propertyById', 'new_error'], }, ], + }; + + if (graphqlVersion() >= 14) { + expectedResult.errors[0].extensions = { + code: 'SOME_CUSTOM_CODE', + }; + } + + expect(result).to.deep.equal(expectedResult); + expect(result.errors[0].extensions).to.deep.equal({ + code: 'SOME_CUSTOM_CODE', }); }); }); @@ -685,7 +700,7 @@ describe('WrapType transform', () => { }, ); - expect(result).to.deep.equal({ + const expectedResult: any = { data: { namespace: { propertyById: { @@ -697,9 +712,6 @@ describe('WrapType transform', () => { }, errors: [ { - extensions: { - code: 'SOME_CUSTOM_CODE', - }, locations: [ { column: 15, @@ -710,6 +722,17 @@ describe('WrapType transform', () => { path: ['namespace', 'propertyById', 'error'], }, ], + }; + + if (graphqlVersion() >= 14) { + expectedResult.errors[0].extensions = { + code: 'SOME_CUSTOM_CODE', + }; + } + + expect(result).to.deep.equal(expectedResult); + expect(result.errors[0].extensions).to.deep.equal({ + code: 'SOME_CUSTOM_CODE', }); }); }); @@ -758,7 +781,7 @@ describe('schema transformation with extraction of nested fields', () => { }, ); - expect(result).to.deep.equal({ + const expectedResult: any = { data: { propertyById: { id: 'p1', @@ -769,9 +792,6 @@ describe('schema transformation with extraction of nested fields', () => { }, errors: [ { - extensions: { - code: 'SOME_CUSTOM_CODE', - }, locations: [ { column: 13, @@ -782,6 +802,17 @@ describe('schema transformation with extraction of nested fields', () => { path: ['propertyById', 'renamedError'], }, ], + }; + + if (graphqlVersion() >= 14) { + expectedResult.errors[0].extensions = { + code: 'SOME_CUSTOM_CODE', + }; + } + + expect(result).to.deep.equal(expectedResult); + expect(result.errors[0].extensions).to.deep.equal({ + code: 'SOME_CUSTOM_CODE', }); }); @@ -886,7 +917,7 @@ describe('schema transformation with wrapping of object fields', () => { }, ); - expect(result).to.deep.equal({ + const expectedResult: any = { data: { propertyById: { test1: { @@ -904,9 +935,6 @@ describe('schema transformation with wrapping of object fields', () => { }, errors: [ { - extensions: { - code: 'SOME_CUSTOM_CODE', - }, locations: [ { column: 11, @@ -917,6 +945,17 @@ describe('schema transformation with wrapping of object fields', () => { path: ['propertyById', 'test1', 'innerWrap', 'two'], }, ], + }; + + if (graphqlVersion() >= 14) { + expectedResult.errors[0].extensions = { + code: 'SOME_CUSTOM_CODE', + }; + } + + expect(result).to.deep.equal(expectedResult); + expect(result.errors[0].extensions).to.deep.equal({ + code: 'SOME_CUSTOM_CODE', }); }); @@ -959,7 +998,7 @@ describe('schema transformation with wrapping of object fields', () => { }, ); - expect(result).to.deep.equal({ + const expectedResult: any = { data: { propertyById: { test1: { @@ -973,9 +1012,6 @@ describe('schema transformation with wrapping of object fields', () => { }, errors: [ { - extensions: { - code: 'SOME_CUSTOM_CODE', - }, locations: [ { column: 13, @@ -986,6 +1022,17 @@ describe('schema transformation with wrapping of object fields', () => { path: ['propertyById', 'test1', 'two'], }, ], + }; + + if (graphqlVersion() >= 14) { + expectedResult.errors[0].extensions = { + code: 'SOME_CUSTOM_CODE', + }; + } + + expect(result).to.deep.equal(expectedResult); + expect(result.errors[0].extensions).to.deep.equal({ + code: 'SOME_CUSTOM_CODE', }); }); @@ -1031,7 +1078,7 @@ describe('schema transformation with wrapping of object fields', () => { }, ); - expect(result).to.deep.equal({ + const expectedResult: any = { data: { propertyById: { test1: { @@ -1049,9 +1096,6 @@ describe('schema transformation with wrapping of object fields', () => { }, errors: [ { - extensions: { - code: 'SOME_CUSTOM_CODE', - }, locations: [ { column: 13, @@ -1062,6 +1106,17 @@ describe('schema transformation with wrapping of object fields', () => { path: ['propertyById', 'test1', 'innerWrap', 'two'], }, ], + }; + + if (graphqlVersion() >= 14) { + expectedResult.errors[0].extensions = { + code: 'SOME_CUSTOM_CODE', + }; + } + + expect(result).to.deep.equal(expectedResult); + expect(result.errors[0].extensions).to.deep.equal({ + code: 'SOME_CUSTOM_CODE', }); }); }); @@ -1105,7 +1160,7 @@ describe('schema transformation with renaming of object fields', () => { }, ); - expect(result).to.deep.equal({ + const expectedResult: any = { data: { propertyById: { // eslint-disable-next-line camelcase @@ -1114,9 +1169,6 @@ describe('schema transformation with renaming of object fields', () => { }, errors: [ { - extensions: { - code: 'SOME_CUSTOM_CODE', - }, locations: [ { column: 13, @@ -1127,6 +1179,17 @@ describe('schema transformation with renaming of object fields', () => { path: ['propertyById', 'new_error'], }, ], + }; + + if (graphqlVersion() >= 14) { + expectedResult.errors[0].extensions = { + code: 'SOME_CUSTOM_CODE', + }; + } + + expect(result).to.deep.equal(expectedResult); + expect(result.errors[0].extensions).to.deep.equal({ + code: 'SOME_CUSTOM_CODE', }); }); }); @@ -1274,7 +1337,7 @@ describe('mergeSchemas', () => { const query = '{ getInput(input: {}) }'; const response = await graphql(mergedSchema, query); - if (versionInfo.major >= 15) { + if (graphqlVersion() >= 15) { expect(printSchema(schema)).to.equal(`input InputWithDefault { field: String = "test" } diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 1138b286bdc..feb8df65fa4 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -19,7 +19,7 @@ import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { IResolvers, SubschemaConfig } from '../Interfaces'; import { delegateToSchema } from '../stitching'; -import { cloneSchema } from '../utils'; +import { cloneSchema, graphqlVersion } from '../utils'; import { getResolversFromSchema } from '../utils/getResolversFromSchema'; import { @@ -2379,18 +2379,15 @@ fragment BookingFragment on Booking { errorAlias: null, }, }); - expect(result.errors.map(removeLocations)).to.deep.equal([ + + const errorsWithoutLocations = result.errors.map(removeLocations); + + const expectedErrors: Array = [ { - extensions: { - code: 'SOME_CUSTOM_CODE', - }, message: 'Property.error error', path: ['propertyById', 'error'], }, { - extensions: { - code: 'SOME_CUSTOM_CODE', - }, message: 'Property.error error', path: ['propertyById', 'errorAlias'], }, @@ -2418,7 +2415,24 @@ fragment BookingFragment on Booking { message: 'Booking.error error', path: ['propertyById', 'bookings', 2, 'bookingErrorAlias'], }, - ]); + ]; + + if (graphqlVersion() >= 14) { + expectedErrors[0].extensions = { + code: 'SOME_CUSTOM_CODE', + }; + expectedErrors[1].extensions = { + code: 'SOME_CUSTOM_CODE', + }; + } + + expect(errorsWithoutLocations).to.deep.equal(expectedErrors); + expect(result.errors[0].extensions).to.deep.equal({ + code: 'SOME_CUSTOM_CODE', + }); + expect(result.errors[1].extensions).to.deep.equal({ + code: 'SOME_CUSTOM_CODE', + }); }); it( @@ -2727,38 +2741,40 @@ fragment BookingFragment on Booking { }); }); - it('interface extensions', async () => { - const result = await graphql( - mergedSchema, - ` - query { - products { - id - __typename - ... on Downloadable { - filesize + if (graphqlVersion() >= 13) { + it('interface extensions', async () => { + const result = await graphql( + mergedSchema, + ` + query { + products { + id + __typename + ... on Downloadable { + filesize + } } } - } - `, - ); + `, + ); - expect(result).to.deep.equal({ - data: { - products: [ - { - id: 'pd1', - __typename: 'SimpleProduct', - }, - { - id: 'pd2', - __typename: 'DownloadableProduct', - filesize: 1024, - }, - ], - }, + expect(result).to.deep.equal({ + data: { + products: [ + { + id: 'pd1', + __typename: 'SimpleProduct', + }, + { + id: 'pd2', + __typename: 'DownloadableProduct', + filesize: 1024, + }, + ], + }, + }); }); - }); + } it('arbitrary transforms that return interfaces', async () => { const result = await graphql( diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index eb33f571694..0c9d50cb1bf 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -19,7 +19,6 @@ import { GraphQLBoolean, graphqlSync, GraphQLSchema, - versionInfo, } from 'graphql'; import { Logger } from '../Logger'; @@ -42,7 +41,7 @@ import { ITypeDefinitions, ILogger, } from '../Interfaces'; -import { visitSchema } from '../utils/visitSchema'; +import { visitSchema, graphqlVersion } from '../utils'; import { addResolversToSchema } from '../generate'; import TypeA from './circularSchemaA'; @@ -258,7 +257,7 @@ describe('generating schema from shorthand', () => { }, query: { name: 'RootQuery', - description: versionInfo.major >= 15 ? (null as string) : '', + description: graphqlVersion() >= 15 ? (null as string) : '', fields: [ { name: 'species', @@ -1777,7 +1776,7 @@ describe('generating schema from shorthand', () => { }, errorMatcher); } - assertFieldError(versionInfo.major >= 15 ? 'Query.bird' : 'Bird.id', {}); + assertFieldError(graphqlVersion() >= 15 ? 'Query.bird' : 'Bird.id', {}); assertFieldError('Query.bird', { Bird: { id: (bird: { id: string }) => bird.id, diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 3f9970296b7..f427ca649ed 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import { GraphQLObjectType, GraphQLObjectTypeConfig } from 'graphql'; +import { GraphQLObjectType } from 'graphql'; -import { healSchema } from '../utils'; +import { healSchema, typeToConfig } from '../utils'; import { makeExecutableSchema } from '../makeExecutableSchema'; describe('heal', () => { @@ -19,9 +19,9 @@ describe('heal', () => { }); const originalTypeMap = schema.getTypeMap(); - const config = originalTypeMap[ - 'WillBeEmptyObject' - ].toConfig() as GraphQLObjectTypeConfig; + const config = typeToConfig( + originalTypeMap['WillBeEmptyObject'] as GraphQLObjectType, + ); originalTypeMap['WillBeEmptyObject'] = new GraphQLObjectType({ ...config, fields: {}, diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index 6dc96a5b89c..81f046899f2 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -15,13 +15,13 @@ import { ExecutionResult, Source, GraphQLResolveInfo, - versionInfo, } from 'graphql'; import { forAwaitEach } from 'iterall'; import introspectSchema from '../stitching/introspectSchema'; import { IResolvers, Fetcher, SubschemaConfig } from '../Interfaces'; import { makeExecutableSchema } from '../makeExecutableSchema'; +import { graphqlVersion } from '../utils'; export type Location = { name: string; @@ -274,13 +274,15 @@ const propertyRootTypeDefs = ` } ${ - versionInfo.major >= 15 + graphqlVersion() >= 15 ? `interface TestNestedInterface implements TestInterface { kind: TestInterfaceKind testString: String } - type TestImpl2 implements TestNestedInterface & TestInterface { + type TestImpl2 implements TestNestedInterface${ + graphqlVersion() >= 13 ? ' &' : ', ' + } TestInterface { kind: TestInterfaceKind testString: String bar: String @@ -414,13 +416,17 @@ const propertyResolvers: IResolvers = { }; const DownloadableProduct = ` - type DownloadableProduct implements Product & Downloadable { + type DownloadableProduct implements Product${ + graphqlVersion() >= 13 ? ' &' : ', ' + } Downloadable { id: ID! url: String! } `; -const SimpleProduct = `type SimpleProduct implements Product & Sellable { +const SimpleProduct = `type SimpleProduct implements Product${ + graphqlVersion() >= 13 ? ' &' : ', ' +} Sellable { id: ID! price: Int! } diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index 74925431425..d084fb20efb 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -17,7 +17,7 @@ import { import isEmptyObject from '../utils/isEmptyObject'; import { Request, VisitSchemaKind } from '../Interfaces'; -import { visitSchema } from '../utils/visitSchema'; +import { visitSchema, typeToConfig } from '../utils'; import { Transform } from './transforms'; @@ -89,7 +89,7 @@ export default class TransformObjectFields implements Transform { type: GraphQLObjectType, objectFieldTransformer: ObjectFieldTransformer, ): GraphQLObjectType { - const typeConfig = type.toConfig(); + const typeConfig = typeToConfig(type); const fields = type.getFields(); const newFields = {}; @@ -130,7 +130,7 @@ export default class TransformObjectFields implements Transform { } return new GraphQLObjectType({ - ...type.toConfig(), + ...typeToConfig(type), fields: newFields, }); } diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index e380bed9061..30d55f641f9 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -9,8 +9,7 @@ import { } from 'graphql'; import { GraphQLSchemaWithTransforms, VisitSchemaKind } from '../Interfaces'; -import { visitSchema } from '../utils/visitSchema'; -import { cloneSchema } from '../utils/clone'; +import { visitSchema, cloneSchema, typeToConfig } from '../utils'; export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -66,7 +65,7 @@ function filterRootFields( operation: 'Query' | 'Mutation' | 'Subscription', rootFieldFilter: RootFieldFilter, ): GraphQLObjectType { - const config = type.toConfig(); + const config = typeToConfig(type); Object.keys(config.fields).forEach(fieldName => { if (!rootFieldFilter(operation, fieldName)) { delete config.fields[fieldName]; @@ -79,7 +78,7 @@ function filterObjectFields( type: GraphQLObjectType, fieldFilter: FieldFilter, ): GraphQLObjectType { - const config = type.toConfig(); + const config = typeToConfig(type); Object.keys(config.fields).forEach(fieldName => { if (!fieldFilter(type.name, fieldName)) { delete config.fields[fieldName]; diff --git a/src/utils/clone.ts b/src/utils/clone.ts index 34837f2e2c2..62c00321018 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -8,48 +8,54 @@ import { GraphQLScalarType, GraphQLSchema, GraphQLUnionType, - versionInfo, GraphQLInterfaceTypeConfig, } from 'graphql'; import { healTypes } from './heal'; import isSpecifiedScalarType from './isSpecifiedScalarType'; +import { directiveToConfig, typeToConfig, schemaToConfig } from './toConfig'; +import { graphqlVersion } from './graphqlVersion'; export function cloneDirective(directive: GraphQLDirective): GraphQLDirective { - return new GraphQLDirective(directive.toConfig()); + return new GraphQLDirective(directiveToConfig(directive)); } export function cloneType(type: GraphQLNamedType): GraphQLNamedType { if (type instanceof GraphQLObjectType) { - const config = type.toConfig(); + const config = typeToConfig(type); return new GraphQLObjectType({ ...config, - interfaces: config.interfaces.slice(), + interfaces: + typeof config.interfaces === 'function' + ? config.interfaces + : (config.interfaces as ReadonlyArray).slice(), }); } else if (type instanceof GraphQLInterfaceType) { - const config = ((type as unknown) as GraphQLObjectType).toConfig(); + const config = typeToConfig((type as unknown) as GraphQLObjectType); const newConfig = { ...config, interfaces: - versionInfo.major >= 15 ? config.interfaces.slice() : undefined, + graphqlVersion() >= 15 + ? (config.interfaces as ReadonlyArray).slice() + : undefined, }; return new GraphQLInterfaceType( (newConfig as unknown) as GraphQLInterfaceTypeConfig, ); } else if (type instanceof GraphQLUnionType) { - const config = type.toConfig(); + const config = typeToConfig(type); return new GraphQLUnionType({ ...config, - types: config.types.slice(), + types: (config.types as ReadonlyArray).slice(), }); } else if (type instanceof GraphQLInputObjectType) { - return new GraphQLInputObjectType(type.toConfig()); + return new GraphQLInputObjectType(typeToConfig(type)); } else if (type instanceof GraphQLEnumType) { - return new GraphQLEnumType(type.toConfig()); + return new GraphQLEnumType(typeToConfig(type)); } else if (type instanceof GraphQLScalarType) { return isSpecifiedScalarType(type) ? type - : new GraphQLScalarType(type.toConfig()); + : new GraphQLScalarType(typeToConfig(type)); } throw new Error(`Invalid type ${type as string}`); @@ -76,7 +82,7 @@ export function cloneSchema(schema: GraphQLSchema): GraphQLSchema { const subscription = schema.getSubscriptionType(); return new GraphQLSchema({ - ...schema.toConfig(), + ...schemaToConfig(schema), query: query != null ? newTypeMap[query.name] : undefined, mutation: mutation != null ? newTypeMap[mutation.name] : undefined, subscription: diff --git a/src/utils/fields.ts b/src/utils/fields.ts index 44ca1773cca..94bed621368 100644 --- a/src/utils/fields.ts +++ b/src/utils/fields.ts @@ -1,11 +1,12 @@ import { GraphQLFieldConfigMap, - GraphQLObjectTypeConfig, GraphQLObjectType, GraphQLFieldConfig, } from 'graphql'; import { TypeMap } from 'graphql/type/schema'; +import { typeToConfig } from './toConfig'; + export function appendFields( typeMap: TypeMap, typeName: string, @@ -13,7 +14,7 @@ export function appendFields( ): void { let type = typeMap[typeName]; if (type != null) { - const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; + const typeConfig = typeToConfig(type as GraphQLObjectType); const originalFields = typeConfig.fields; const newFields = {}; Object.keys(originalFields).forEach(fieldName => { @@ -41,7 +42,7 @@ export function removeFields( testFn: (fieldName: string, field: GraphQLFieldConfig) => boolean, ): GraphQLFieldConfigMap { let type = typeMap[typeName]; - const typeConfig = type.toConfig() as GraphQLObjectTypeConfig; + const typeConfig = typeToConfig(type as GraphQLObjectType); const originalFields = typeConfig.fields; const newFields = {}; const removedFields = {}; diff --git a/src/utils/graphqlVersion.ts b/src/utils/graphqlVersion.ts new file mode 100644 index 00000000000..857b27643e7 --- /dev/null +++ b/src/utils/graphqlVersion.ts @@ -0,0 +1,24 @@ +import { + versionInfo, + getOperationRootType, + lexicographicSortSchema, + printError, +} from 'graphql'; + +let version: number; + +if (versionInfo != null && versionInfo.major >= 15) { + version = 15; +} else if (getOperationRootType != null) { + version = 14; +} else if (lexicographicSortSchema != null) { + version = 13; +} else if (printError != null) { + version = 12; +} else { + version = 11; +} + +export function graphqlVersion() { + return version; +} diff --git a/src/utils/heal.ts b/src/utils/heal.ts index 778a95b1ea4..f608a22d6c9 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -14,13 +14,13 @@ import { GraphQLSchema, GraphQLInputType, GraphQLOutputType, - versionInfo, } from 'graphql'; import each from './each'; import updateEachKey from './updateEachKey'; import { isStub, getBuiltInForStub } from './stub'; import { cloneSchema } from './clone'; +import { graphqlVersion } from './graphqlVersion'; type NamedTypeMap = { [key: string]: GraphQLNamedType; @@ -124,7 +124,7 @@ export function healTypes( return; } else if (type instanceof GraphQLInterfaceType) { healFields(type); - if (versionInfo.major >= 15) { + if (graphqlVersion() >= 15) { healInterfaces(type); } return; @@ -215,7 +215,7 @@ function pruneTypes( each(typeMap, namedType => { if ( namedType instanceof GraphQLObjectType || - (versionInfo.major >= 15 && namedType instanceof GraphQLInterfaceType) + (graphqlVersion() >= 15 && namedType instanceof GraphQLInterfaceType) ) { each((namedType as GraphQLObjectType).getInterfaces(), iface => { implementedInterfaces[iface.name] = true; diff --git a/src/utils/index.ts b/src/utils/index.ts index dd9cf263339..58195da0aad 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -25,3 +25,16 @@ export { hoistFieldNodes, } from './fieldNodes'; export { appendFields, removeFields } from './fields'; +export { + schemaToConfig, + typeToConfig, + objectTypeToConfig, + interfaceTypeToConfig, + unionTypeToConfig, + enumTypeToConfig, + scalarTypeToConfig, + inputObjectTypeToConfig, + directiveToConfig, +} from './toConfig'; +export { createNamedStub } from './stub'; +export { graphqlVersion } from './graphqlVersion'; diff --git a/src/utils/toConfig.ts b/src/utils/toConfig.ts new file mode 100644 index 00000000000..1a814f7545f --- /dev/null +++ b/src/utils/toConfig.ts @@ -0,0 +1,310 @@ +// 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 unknown) as { __validationErrors: boolean }) + .__validationErrors !== undefined, + }; + + if (graphqlVersion() >= 15) { + ((schemaConfig as unknown) as { + description: string; + }).description = ((schema as unknown) as { + description: string; + }).description; + } + + return schemaConfig; +} + +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) { + 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 unknown) as string}`); +} + +export function objectTypeToConfig( + type: GraphQLObjectType, +): GraphQLObjectTypeConfig { + const typeConfig = { + name: type.name, + description: type.description, + interfaces: type.getInterfaces(), + fields: fieldsToFieldsConfig(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 { + const typeConfig = { + name: type.name, + description: type.description, + fields: fieldsToFieldsConfig(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 { + 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 { + 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 { + 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 { + const newFields = {}; + const fields = type.getFields(); + + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + + newFields[fieldName] = { + description: field.description, + type: field.type, + defaultValue: field.defaultValue, + extensions: field.extensions, + astNode: field.astNode, + }; + }); + + const typeConfig = { + name: type.name, + description: type.description, + fields: newFields, + extensions: type.extensions, + astNode: type.astNode, + extensionASTNodes: + type.extensionASTNodes != null ? type.extensionASTNodes : [], + }; + + return typeConfig; +} + +export function directiveToConfig( + directive: GraphQLDirective, +): GraphQLDirectiveConfig { + const directiveConfig = { + name: directive.name, + description: directive.description, + locations: directive.locations, + args: argsToArgsConfig(directive.args), + isRepeatable: ((directive as unknown) as { isRepeatable: boolean }) + .isRepeatable, + extensions: directive.extensions, + astNode: directive.astNode, + }; + + return directiveConfig; +} + +function fieldsToFieldsConfig( + fields: GraphQLFieldMap, +): GraphQLFieldConfigMap { + const newFields = {}; + + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + + newFields[fieldName] = { + description: field.description, + type: field.type, + args: argsToArgsConfig(field.args), + resolve: field.resolve, + subscribe: field.subscribe, + deprecationReason: field.deprecationReason, + extensions: field.extensions, + astNode: field.astNode, + }; + }); + + return newFields; +} + +function argsToArgsConfig( + args: ReadonlyArray, +): GraphQLFieldConfigArgumentMap { + const newArguments = {}; + + args.forEach(arg => { + newArguments[arg.name] = { + description: arg.description, + type: arg.type, + defaultValue: arg.defaultValue, + extensions: arg.extensions, + astNode: arg.astNode, + }; + }); + + return newArguments; +} From 0f502403b7cd50307e00a93069d5544ee87c19fc Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 27 Feb 2020 11:30:14 -0500 Subject: [PATCH 198/250] chore(deps): upgrade deps provide cross-compatibility for graphql upload v9 and v10 in exported GraphQLUpload scalar --- package.json | 21 ++++++++++----------- src/scalars/GraphQLUpload.ts | 18 ++++++++++++++---- src/test/testUpload.ts | 4 ++++ 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index ac0516c5cdd..bcdfd76fabf 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "form-data": "^3.0.0", "iterall": "^1.3.0", "node-fetch": "^2.6.0", - "uuid": "^3.4.0" + "uuid": "^7.0.1" }, "peerDependencies": { "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc" @@ -67,13 +67,12 @@ "@types/graphql-type-json": "^0.3.2", "@types/graphql-upload": "^8.0.3", "@types/mocha": "^7.0.1", - "@types/node": "^13.7.1", - "@types/node-fetch": "^2.5.4", - "@types/uuid": "^3.4.7", - "@typescript-eslint/eslint-plugin": "^2.19.2", - "@typescript-eslint/parser": "^2.19.2", - "apollo-upload-client": "^12.1.0", - "babel-eslint": "^10.0.3", + "@types/node": "^13.7.6", + "@types/node-fetch": "^2.5.5", + "@types/uuid": "^7.0.0", + "@typescript-eslint/eslint-plugin": "^2.21.0", + "@typescript-eslint/parser": "^2.21.0", + "babel-eslint": "^10.1.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "dataloader": "^2.0.0", @@ -86,15 +85,15 @@ "graphql": "^14.6.0", "graphql-subscriptions": "^1.1.0", "graphql-type-json": "^0.3.1", - "graphql-upload": "^9.0.0", + "graphql-upload": "^10.0.0", "istanbul": "^0.4.5", - "mocha": "^7.0.1", + "mocha": "^7.1.0", "prettier": "^1.19.1", "remap-istanbul": "0.13.0", "rimraf": "^3.0.2", "source-map-support": "^0.5.16", "standard-version": "^7.1.0", - "typescript": "^3.7.5", + "typescript": "^3.8.2", "zen-observable-ts": "^0.8.20" } } diff --git a/src/scalars/GraphQLUpload.ts b/src/scalars/GraphQLUpload.ts index 5bc3d3152ed..62493f4bac4 100644 --- a/src/scalars/GraphQLUpload.ts +++ b/src/scalars/GraphQLUpload.ts @@ -1,13 +1,23 @@ -import { GraphQLScalarType } from 'graphql'; +import { GraphQLScalarType, GraphQLError } from 'graphql'; const GraphQLUpload = new GraphQLScalarType({ name: 'Upload', description: 'The `Upload` scalar type represents a file upload.', - parseValue: value => value, - parseLiteral: () => { - throw new Error('‘Upload’ scalar literal unsupported.'); + 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/test/testUpload.ts b/src/test/testUpload.ts index 81cdef01219..e4e2c45f453 100644 --- a/src/test/testUpload.ts +++ b/src/test/testUpload.ts @@ -13,6 +13,7 @@ import { buildSchema } from 'graphql'; import { mergeSchemas } from '../stitching'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { createServerHttpLink } from '../links'; +import { GraphQLUpload as ServerGraphQLUpload } from '../scalars'; import { SubschemaConfig } from '../Interfaces'; function streamToString(stream: Readable) { @@ -106,6 +107,9 @@ describe('graphql upload', () => { const gatewaySchema = mergeSchemas({ schemas: [subschema], + resolvers: { + Upload: ServerGraphQLUpload, + }, }); const gatewayApp = express().use( From 21f9509f3470adf380bbd3833ab93c36d59a5c1b Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 27 Feb 2020 12:08:10 -0500 Subject: [PATCH 199/250] chore(ci): upgrade to nyc --- .gitignore | 1 + .travis.yml | 28 ++++++++++++++++------------ package.json | 8 ++++---- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 5782c81d5c9..f55623756b5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ yarn-debug.log* yarn-error.log* .eslintcache +.nyc_output diff --git a/.travis.yml b/.travis.yml index b46d812edfa..12586476a3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,25 +4,29 @@ node_js: - "12" - "10" -install: +env: + - GRAPHQL_VERSION='0.12' + - GRAPHQL_VERSION='0.13' + - GRAPHQL_VERSION='14' + - GRAPHQL_VERSION='rc' + +install: - npm config set spin=false - - npm install -g mocha coveralls - npm install script: - - node_version=$(node -v); if [[ ${node_version:1:2} == 13 && $GRAPHQL_VERSION == 14 ]]; then npm run lint; fi - - node_version=$(node -v); if [[ ${node_version:1:2} == 13 && $GRAPHQL_VERSION == 14 ]]; then npm run prettier:check; fi + - node_version=$(node -v); if [[ ${node_version:1:2} == 13 && $GRAPHQL_VERSION == 14 ]]; then + npm run lint; + fi + - node_version=$(node -v); if [[ ${node_version:1:2} == 13 && $GRAPHQL_VERSION == 14 ]]; then + npm run prettier:check; + fi - npm run compile - npm install graphql@$GRAPHQL_VERSION - - npm run testonly + - 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 - -env: - - GRAPHQL_VERSION='0.12' - - GRAPHQL_VERSION='0.13' - - GRAPHQL_VERSION='14' - - GRAPHQL_VERSION='rc' diff --git a/package.json b/package.json index bcdfd76fabf..dd127999ed6 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ "lint:watch": "esw --watch --cache --ext .js,.ts src", "watch": "tsc -w", "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": "istanbul cover _mocha -- --reporter dot --full-trace ./dist/test/**.js", - "postcoverage": "remap-istanbul --input coverage/coverage.json --type lcovonly --output coverage/lcov.info", + "coverage": "nyc report --reporter=text-lcov | coveralls", "prepublishOnly": "npm run compile", "prettier": "prettier --trailing-comma all --single-quote --write src/**/*.ts", "prettier:check": "prettier --trailing-comma all --single-quote --check src/**/*.ts", @@ -75,6 +75,7 @@ "babel-eslint": "^10.1.0", "body-parser": "^1.19.0", "chai": "^4.2.0", + "coveralls": "^3.0.9", "dataloader": "^2.0.0", "dateformat": "^3.0.3", "eslint": "^6.8.0", @@ -86,10 +87,9 @@ "graphql-subscriptions": "^1.1.0", "graphql-type-json": "^0.3.1", "graphql-upload": "^10.0.0", - "istanbul": "^0.4.5", "mocha": "^7.1.0", + "nyc": "^15.0.0", "prettier": "^1.19.1", - "remap-istanbul": "0.13.0", "rimraf": "^3.0.2", "source-map-support": "^0.5.16", "standard-version": "^7.1.0", From 5180bfb49080a825d950c4f6b0bd61e2ca2e0e89 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Fri, 28 Feb 2020 10:00:22 -0500 Subject: [PATCH 200/250] chore(ci): explicitly test minor versions --- .travis.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 12586476a3c..b532ed85309 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,12 @@ node_js: env: - GRAPHQL_VERSION='0.12' - GRAPHQL_VERSION='0.13' - - GRAPHQL_VERSION='14' + - 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: @@ -15,10 +20,10 @@ install: - npm install script: - - node_version=$(node -v); if [[ ${node_version:1:2} == 13 && $GRAPHQL_VERSION == 14 ]]; then + - 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 ]]; then + - node_version=$(node -v); if [[ ${node_version:1:2} == "13" && $GRAPHQL_VERSION == "14.6" ]]; then npm run prettier:check; fi - npm run compile From 5fdb8d2a5170b27fd5e976e560daa48b619f2a44 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Fri, 28 Feb 2020 12:27:21 -0500 Subject: [PATCH 201/250] fix(ci): properly polyfill graphql v14.1 and 14.2 to properly support all graphql versions >= v0.12 --- .npmignore | 1 + .travis.yml | 1 + src/generate/addResolversToSchema.ts | 4 +- src/index.ts | 1 + src/polyfills/buildSchema.ts | 9 ++++ src/polyfills/extendSchema.ts | 37 +++++++++++++++ src/polyfills/index.ts | 18 ++++++++ .../isSpecifiedScalarType.ts | 2 +- src/{utils => polyfills}/toConfig.ts | 45 ++++++++++++++++++- src/stitching/makeRemoteExecutableSchema.ts | 5 +-- src/stitching/mergeSchemas.ts | 44 +++++------------- src/test/testAlternateMergeSchemas.ts | 5 +-- src/test/testUtils.ts | 5 ++- src/transforms/RenameTypes.ts | 2 +- src/transforms/TransformObjectFields.ts | 7 +-- src/transforms/filterSchema.ts | 7 +-- src/utils/clone.ts | 20 ++++----- src/utils/fields.ts | 6 +-- src/utils/getResolversFromSchema.ts | 2 +- src/utils/index.ts | 11 ----- 20 files changed, 156 insertions(+), 76 deletions(-) create mode 100644 src/polyfills/buildSchema.ts create mode 100644 src/polyfills/extendSchema.ts create mode 100644 src/polyfills/index.ts rename src/{utils => polyfills}/isSpecifiedScalarType.ts (92%) rename src/{utils => polyfills}/toConfig.ts (84%) diff --git a/.npmignore b/.npmignore index 6ac52e9721e..5078d8deef1 100644 --- a/.npmignore +++ b/.npmignore @@ -3,6 +3,7 @@ !dist/* !dist/generate/* !dist/links/* +!dist/polyfills/* !dist/scalars/* !dist/stitching/* !dist/transforms/* diff --git a/.travis.yml b/.travis.yml index b532ed85309..4a51eeaf7c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ node_js: 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' diff --git a/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index 2d003c79314..480f8f5257e 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -19,8 +19,8 @@ import { healSchema, forEachField, forEachDefaultValue, - typeToConfig, } from '../utils'; +import { toConfig } from '../polyfills'; import SchemaError from './SchemaError'; import checkForResolveTypeResolver from './checkForResolveTypeResolver'; @@ -105,7 +105,7 @@ function addResolversToSchema( } }); - const config = typeToConfig(type); + const config = toConfig(type); const values = type.getValues(); const newValues = {}; diff --git a/src/index.ts b/src/index.ts index b918078b972..4ed810be1d3 100755 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export * from './Interfaces'; export * from './links'; export * from './makeExecutableSchema'; export * from './mock'; +export * from './polyfills'; export * from './scalars'; export * from './stitching'; export * from './transforms'; 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..a0e86a09954 --- /dev/null +++ b/src/polyfills/extendSchema.ts @@ -0,0 +1,37 @@ +import { + DocumentNode, + GraphQLSchema, + extendSchema as graphqlExtendSchema, +} from 'graphql'; + +import { getResolversFromSchema } from '../utils'; +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].resolve = subscriptionResolvers[fieldName].resolve; + 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..d28d35039b8 --- /dev/null +++ b/src/polyfills/index.ts @@ -0,0 +1,18 @@ +export { isSpecifiedScalarType } from './isSpecifiedScalarType'; + +export { buildSchema } from './buildSchema'; + +export { extendSchema } from './extendSchema'; + +export { + toConfig, + schemaToConfig, + typeToConfig, + objectTypeToConfig, + interfaceTypeToConfig, + unionTypeToConfig, + enumTypeToConfig, + scalarTypeToConfig, + inputObjectTypeToConfig, + directiveToConfig, +} from './toConfig'; diff --git a/src/utils/isSpecifiedScalarType.ts b/src/polyfills/isSpecifiedScalarType.ts similarity index 92% rename from src/utils/isSpecifiedScalarType.ts rename to src/polyfills/isSpecifiedScalarType.ts index dda61f8cac0..9305fcb53ae 100644 --- a/src/utils/isSpecifiedScalarType.ts +++ b/src/polyfills/isSpecifiedScalarType.ts @@ -19,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/utils/toConfig.ts b/src/polyfills/toConfig.ts similarity index 84% rename from src/utils/toConfig.ts rename to src/polyfills/toConfig.ts index 1a814f7545f..679081c17bb 100644 --- a/src/utils/toConfig.ts +++ b/src/polyfills/toConfig.ts @@ -28,9 +28,12 @@ import { isEnumType, isScalarType, isInputObjectType, + isSchema, + isDirective, + isNamedType, } from 'graphql'; -import { graphqlVersion } from './graphqlVersion'; +import { graphqlVersion } from '../utils/graphqlVersion'; export function schemaToConfig(schema: GraphQLSchema): GraphQLSchemaConfig { const newTypes: Array = []; @@ -66,6 +69,45 @@ export function schemaToConfig(schema: GraphQLSchema): GraphQLSchemaConfig { return schemaConfig; } +export function toConfig( + schemaOrTypeOrDirective: GraphQLSchema, +): GraphQLSchemaConfig; +export function toConfig( + schemaOrTypeOrDirective: GraphQLObjectType, +): GraphQLObjectTypeConfig; +export function toConfig( + schemaOrTypeOrDirective: GraphQLInterfaceType, +): GraphQLInterfaceTypeConfig; +export function toConfig( + schemaOrTypeOrDirective: GraphQLUnionType, +): GraphQLUnionTypeConfig; +export function toConfig( + schemaOrTypeOrDirective: GraphQLEnumType, +): GraphQLEnumTypeConfig; +export function toConfig( + schemaOrTypeOrDirective: GraphQLScalarType, +): GraphQLScalarTypeConfig; +export function toConfig( + schemaOrTypeOrDirective: GraphQLInputObjectType, +): GraphQLInputObjectTypeConfig; +export function toConfig( + schemaOrTypeOrDirective: GraphQLDirective, +): GraphQLDirectiveConfig; +export function toConfig(schemaOrTypeOrDirective: any): any; +export function toConfig(schemaOrTypeOrDirective: any) { + if (isSchema(schemaOrTypeOrDirective)) { + return schemaToConfig(schemaOrTypeOrDirective); + } else if (isDirective(schemaOrTypeOrDirective)) { + return directiveToConfig(schemaOrTypeOrDirective); + } else if (isNamedType(schemaOrTypeOrDirective)) { + return typeToConfig(schemaOrTypeOrDirective); + } + + throw new Error( + `Unknown object ${(schemaOrTypeOrDirective as unknown) as string}`, + ); +} + export function typeToConfig( type: GraphQLObjectType, ): GraphQLObjectTypeConfig; @@ -82,6 +124,7 @@ export function typeToConfig( export function typeToConfig( type: GraphQLInputObjectType, ): GraphQLInputObjectTypeConfig; +export function typeToConfig(type: any): any; export function typeToConfig(type: any) { if (isObjectType(type)) { return objectTypeToConfig(type); diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/stitching/makeRemoteExecutableSchema.ts index f1b426f3b8f..64427e1befc 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/stitching/makeRemoteExecutableSchema.ts @@ -6,13 +6,12 @@ import { GraphQLResolveInfo, BuildSchemaOptions, DocumentNode, - buildASTSchema, - parse, } from 'graphql'; import { addResolversToSchema } from '../generate'; import { Fetcher, Operation } from '../Interfaces'; import { cloneSchema } from '../utils'; +import { buildSchema } from '../polyfills'; import linkToFetcher, { execute } from './linkToFetcher'; import { addTypenameToAbstract } from './addTypenameToAbstract'; @@ -49,7 +48,7 @@ export default function makeRemoteExecutableSchema({ const targetSchema = typeof schemaOrTypeDefs === 'string' - ? buildASTSchema(parse(schemaOrTypeDefs), buildSchemaOptions) + ? buildSchema(schemaOrTypeDefs, buildSchemaOptions) : schemaOrTypeDefs; const remoteSchema = cloneSchema(targetSchema); diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index d5f3978ce83..bd44b90d092 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -4,7 +4,6 @@ import { GraphQLObjectType, GraphQLScalarType, GraphQLSchema, - extendSchema, getNamedType, isNamedType, parse, @@ -36,10 +35,9 @@ import { healTypes, forEachField, mergeDeep, - typeToConfig, graphqlVersion, - getResolversFromSchema, } from '../utils'; +import { toConfig, extendSchema } from '../polyfills'; import typeFromAST from './typeFromAST'; import { createMergeInfo, completeMergeInfo } from './mergeInfo'; @@ -246,28 +244,11 @@ export default function mergeSchemas({ : undefined, }); - let proxyingResolvers: IResolvers; - if (graphqlVersion() >= 14) { - extensions.forEach(extension => { - mergedSchema = extendSchema(mergedSchema, extension, { - commentDescriptions: true, - }); - }); - } else { - // extendSchema in graphql < v14 does not support subscriptions? - proxyingResolvers = getResolversFromSchema(mergedSchema); - - extensions.forEach(extension => { - mergedSchema = extendSchema(mergedSchema, extension, { - commentDescriptions: true, - }); + extensions.forEach(extension => { + mergedSchema = extendSchema(mergedSchema, extension, { + commentDescriptions: true, }); - - addResolversToSchema({ - schema: mergedSchema, - resolvers: proxyingResolvers, - }); - } + }); addResolversToSchema({ schema: mergedSchema, @@ -361,12 +342,12 @@ function merge( fields: candidates.reduce( (acc, candidate) => ({ ...acc, - ...typeToConfig(candidate.type as GraphQLObjectType).fields, + ...toConfig(candidate.type as GraphQLObjectType).fields, }), {}, ), interfaces: candidates.reduce((acc, candidate) => { - const interfaces = typeToConfig(candidate.type as GraphQLObjectType) + const interfaces = toConfig(candidate.type as GraphQLObjectType) .interfaces; return interfaces != null ? acc.concat(interfaces) : acc; }, []), @@ -377,16 +358,15 @@ function merge( fields: candidates.reduce( (acc, candidate) => ({ ...acc, - ...typeToConfig(candidate.type as GraphQLObjectType).fields, + ...toConfig(candidate.type as GraphQLObjectType).fields, }), {}, ), interfaces: graphqlVersion() >= 15 ? candidates.reduce((acc, candidate) => { - const interfaces = typeToConfig( - candidate.type as GraphQLObjectType, - ).interfaces; + const interfaces = toConfig(candidate.type as GraphQLObjectType) + .interfaces; return interfaces != null ? acc.concat(interfaces) : acc; }, []) : undefined, @@ -397,7 +377,7 @@ function merge( name: typeName, types: candidates.reduce( (acc, candidate) => - acc.concat(typeToConfig(candidate.type as GraphQLUnionType).types), + acc.concat(toConfig(candidate.type as GraphQLUnionType).types), [], ), }); @@ -407,7 +387,7 @@ function merge( values: candidates.reduce( (acc, candidate) => ({ ...acc, - ...typeToConfig(candidate.type as GraphQLEnumType).values, + ...toConfig(candidate.type as GraphQLEnumType).values, }), {}, ), diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 45e457b8992..b178d4e03d9 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -27,6 +27,7 @@ import { FilterRootFields, FilterObjectFields, } from '../transforms'; +import { isSpecifiedScalarType, toConfig } from '../polyfills'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { @@ -35,12 +36,10 @@ import { createMergedResolver, } from '../stitching'; import { SubschemaConfig } from '../Interfaces'; -import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; import { wrapFieldNode, renameFieldNode, hoistFieldNodes, - typeToConfig, graphqlVersion, } from '../utils'; @@ -355,7 +354,7 @@ describe('transform object fields', () => { return undefined; } const type = propertySchema.getType(typeName) as GraphQLObjectType; - const typeConfig = typeToConfig(type); + const typeConfig = toConfig(type); const fieldConfig = typeConfig.fields[ fieldName ] as GraphQLFieldConfig; diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index f427ca649ed..8500cd8b8d9 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -1,7 +1,8 @@ import { expect } from 'chai'; import { GraphQLObjectType } from 'graphql'; -import { healSchema, typeToConfig } from '../utils'; +import { healSchema } from '../utils'; +import { toConfig } from '../polyfills'; import { makeExecutableSchema } from '../makeExecutableSchema'; describe('heal', () => { @@ -19,7 +20,7 @@ describe('heal', () => { }); const originalTypeMap = schema.getTypeMap(); - const config = typeToConfig( + const config = toConfig( originalTypeMap['WillBeEmptyObject'] as GraphQLObjectType, ); originalTypeMap['WillBeEmptyObject'] = new GraphQLObjectType({ diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index 1df800ac2c0..57e065ff0e2 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -7,7 +7,7 @@ import { GraphQLScalarType, } from 'graphql'; -import isSpecifiedScalarType from '../utils/isSpecifiedScalarType'; +import { isSpecifiedScalarType } from '../polyfills'; import { Request, Result, VisitSchemaKind } from '../Interfaces'; import { Transform } from '../transforms/transforms'; import { visitSchema, cloneType } from '../utils'; diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index d084fb20efb..73bc7ce40c9 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -17,7 +17,8 @@ import { import isEmptyObject from '../utils/isEmptyObject'; import { Request, VisitSchemaKind } from '../Interfaces'; -import { visitSchema, typeToConfig } from '../utils'; +import { visitSchema } from '../utils'; +import { toConfig } from '../polyfills'; import { Transform } from './transforms'; @@ -89,7 +90,7 @@ export default class TransformObjectFields implements Transform { type: GraphQLObjectType, objectFieldTransformer: ObjectFieldTransformer, ): GraphQLObjectType { - const typeConfig = typeToConfig(type); + const typeConfig = toConfig(type); const fields = type.getFields(); const newFields = {}; @@ -130,7 +131,7 @@ export default class TransformObjectFields implements Transform { } return new GraphQLObjectType({ - ...typeToConfig(type), + ...toConfig(type), fields: newFields, }); } diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index 30d55f641f9..13d6ca6eaa5 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -9,7 +9,8 @@ import { } from 'graphql'; import { GraphQLSchemaWithTransforms, VisitSchemaKind } from '../Interfaces'; -import { visitSchema, cloneSchema, typeToConfig } from '../utils'; +import { visitSchema, cloneSchema } from '../utils'; +import { toConfig } from '../polyfills'; export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -65,7 +66,7 @@ function filterRootFields( operation: 'Query' | 'Mutation' | 'Subscription', rootFieldFilter: RootFieldFilter, ): GraphQLObjectType { - const config = typeToConfig(type); + const config = toConfig(type); Object.keys(config.fields).forEach(fieldName => { if (!rootFieldFilter(operation, fieldName)) { delete config.fields[fieldName]; @@ -78,7 +79,7 @@ function filterObjectFields( type: GraphQLObjectType, fieldFilter: FieldFilter, ): GraphQLObjectType { - const config = typeToConfig(type); + const config = toConfig(type); Object.keys(config.fields).forEach(fieldName => { if (!fieldFilter(type.name, fieldName)) { delete config.fields[fieldName]; diff --git a/src/utils/clone.ts b/src/utils/clone.ts index 62c00321018..c09bb36e6be 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -11,18 +11,18 @@ import { GraphQLInterfaceTypeConfig, } from 'graphql'; +import { isSpecifiedScalarType, toConfig } from '../polyfills'; + import { healTypes } from './heal'; -import isSpecifiedScalarType from './isSpecifiedScalarType'; -import { directiveToConfig, typeToConfig, schemaToConfig } from './toConfig'; import { graphqlVersion } from './graphqlVersion'; export function cloneDirective(directive: GraphQLDirective): GraphQLDirective { - return new GraphQLDirective(directiveToConfig(directive)); + return new GraphQLDirective(toConfig(directive)); } export function cloneType(type: GraphQLNamedType): GraphQLNamedType { if (type instanceof GraphQLObjectType) { - const config = typeToConfig(type); + const config = toConfig(type); return new GraphQLObjectType({ ...config, interfaces: @@ -31,7 +31,7 @@ export function cloneType(type: GraphQLNamedType): GraphQLNamedType { : (config.interfaces as ReadonlyArray).slice(), }); } else if (type instanceof GraphQLInterfaceType) { - const config = typeToConfig((type as unknown) as GraphQLObjectType); + const config = toConfig((type as unknown) as GraphQLObjectType); const newConfig = { ...config, interfaces: @@ -43,19 +43,19 @@ export function cloneType(type: GraphQLNamedType): GraphQLNamedType { (newConfig as unknown) as GraphQLInterfaceTypeConfig, ); } else if (type instanceof GraphQLUnionType) { - const config = typeToConfig(type); + const config = toConfig(type); return new GraphQLUnionType({ ...config, types: (config.types as ReadonlyArray).slice(), }); } else if (type instanceof GraphQLInputObjectType) { - return new GraphQLInputObjectType(typeToConfig(type)); + return new GraphQLInputObjectType(toConfig(type)); } else if (type instanceof GraphQLEnumType) { - return new GraphQLEnumType(typeToConfig(type)); + return new GraphQLEnumType(toConfig(type)); } else if (type instanceof GraphQLScalarType) { return isSpecifiedScalarType(type) ? type - : new GraphQLScalarType(typeToConfig(type)); + : new GraphQLScalarType(toConfig(type)); } throw new Error(`Invalid type ${type as string}`); @@ -82,7 +82,7 @@ export function cloneSchema(schema: GraphQLSchema): GraphQLSchema { const subscription = schema.getSubscriptionType(); return new GraphQLSchema({ - ...schemaToConfig(schema), + ...toConfig(schema), query: query != null ? newTypeMap[query.name] : undefined, mutation: mutation != null ? newTypeMap[mutation.name] : undefined, subscription: diff --git a/src/utils/fields.ts b/src/utils/fields.ts index 94bed621368..ce4bb112e1d 100644 --- a/src/utils/fields.ts +++ b/src/utils/fields.ts @@ -5,7 +5,7 @@ import { } from 'graphql'; import { TypeMap } from 'graphql/type/schema'; -import { typeToConfig } from './toConfig'; +import { toConfig } from '../polyfills'; export function appendFields( typeMap: TypeMap, @@ -14,7 +14,7 @@ export function appendFields( ): void { let type = typeMap[typeName]; if (type != null) { - const typeConfig = typeToConfig(type as GraphQLObjectType); + const typeConfig = toConfig(type as GraphQLObjectType); const originalFields = typeConfig.fields; const newFields = {}; Object.keys(originalFields).forEach(fieldName => { @@ -42,7 +42,7 @@ export function removeFields( testFn: (fieldName: string, field: GraphQLFieldConfig) => boolean, ): GraphQLFieldConfigMap { let type = typeMap[typeName]; - const typeConfig = typeToConfig(type as GraphQLObjectType); + const typeConfig = toConfig(type as GraphQLObjectType); const originalFields = typeConfig.fields; const newFields = {}; const removedFields = {}; diff --git a/src/utils/getResolversFromSchema.ts b/src/utils/getResolversFromSchema.ts index 00ef7ed457c..5beb4451b26 100644 --- a/src/utils/getResolversFromSchema.ts +++ b/src/utils/getResolversFromSchema.ts @@ -8,8 +8,8 @@ import { } from 'graphql'; import { IResolvers } from '../Interfaces'; +import { isSpecifiedScalarType } from '../polyfills'; -import isSpecifiedScalarType from './isSpecifiedScalarType'; import { cloneType } from './clone'; export function getResolversFromSchema(schema: GraphQLSchema): IResolvers { diff --git a/src/utils/index.ts b/src/utils/index.ts index 58195da0aad..0134ffebb0d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -25,16 +25,5 @@ export { hoistFieldNodes, } from './fieldNodes'; export { appendFields, removeFields } from './fields'; -export { - schemaToConfig, - typeToConfig, - objectTypeToConfig, - interfaceTypeToConfig, - unionTypeToConfig, - enumTypeToConfig, - scalarTypeToConfig, - inputObjectTypeToConfig, - directiveToConfig, -} from './toConfig'; export { createNamedStub } from './stub'; export { graphqlVersion } from './graphqlVersion'; From 02098e07101dc850d2836562702dc238b46cf02d Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Fri, 28 Feb 2020 14:12:46 -0500 Subject: [PATCH 202/250] chore(graphql-js): use toConfig if available --- src/polyfills/toConfig.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/polyfills/toConfig.ts b/src/polyfills/toConfig.ts index 679081c17bb..2bb490db358 100644 --- a/src/polyfills/toConfig.ts +++ b/src/polyfills/toConfig.ts @@ -36,6 +36,10 @@ import { import { graphqlVersion } from '../utils/graphqlVersion'; export function schemaToConfig(schema: GraphQLSchema): GraphQLSchemaConfig { + if (schema.toConfig != null) { + return schema.toConfig(); + } + const newTypes: Array = []; const types = schema.getTypeMap(); @@ -146,6 +150,10 @@ export function typeToConfig(type: any) { export function objectTypeToConfig( type: GraphQLObjectType, ): GraphQLObjectTypeConfig { + if (type.toConfig != null) { + return type.toConfig(); + } + const typeConfig = { name: type.name, description: type.description, @@ -164,6 +172,10 @@ export function objectTypeToConfig( export function interfaceTypeToConfig( type: GraphQLInterfaceType, ): GraphQLInterfaceTypeConfig { + if (type.toConfig != null) { + return type.toConfig(); + } + const typeConfig = { name: type.name, description: type.description, @@ -188,6 +200,10 @@ export function interfaceTypeToConfig( export function unionTypeToConfig( type: GraphQLUnionType, ): GraphQLUnionTypeConfig { + if (type.toConfig != null) { + return type.toConfig(); + } + const typeConfig = { name: type.name, description: type.description, @@ -203,6 +219,10 @@ export function unionTypeToConfig( } export function enumTypeToConfig(type: GraphQLEnumType): GraphQLEnumTypeConfig { + if (type.toConfig != null) { + return type.toConfig(); + } + const newValues = {}; type.getValues().forEach(value => { @@ -233,6 +253,10 @@ 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, @@ -266,6 +290,10 @@ export function scalarTypeToConfig( export function inputObjectTypeToConfig( type: GraphQLInputObjectType, ): GraphQLInputObjectTypeConfig { + if (type.toConfig != null) { + return type.toConfig(); + } + const newFields = {}; const fields = type.getFields(); @@ -297,6 +325,10 @@ export function inputObjectTypeToConfig( export function directiveToConfig( directive: GraphQLDirective, ): GraphQLDirectiveConfig { + if (directive.toConfig != null) { + return directive.toConfig(); + } + const directiveConfig = { name: directive.name, description: directive.description, From 97c584acfd458c69f5d5a1520b71aa7932d8d890 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 29 Feb 2020 22:02:46 -0500 Subject: [PATCH 203/250] fix(instanceof): use graphql predicates --- docs/source/schema-directives.md | 5 +- src/generate/addResolversToSchema.ts | 36 +++++++------- src/generate/assertResolversPresent.ts | 4 +- src/generate/attachConnectorsToContext.ts | 4 +- src/generate/checkForResolveTypeResolver.ts | 14 +++--- src/generate/extendResolversFromInterfaces.ts | 7 +-- src/mock.ts | 47 +++++++------------ src/stitching/addTypenameToAbstract.ts | 9 +--- src/stitching/createRequest.ts | 8 ++-- src/stitching/delegateToSchema.ts | 3 +- src/stitching/makeMergedType.ts | 14 ++---- src/stitching/mergeInfo.ts | 7 +-- src/stitching/mergeSchemas.ts | 29 +++++++----- src/test/testDirectives.ts | 12 ++--- src/transforms/AddArgumentsAsVariables.ts | 8 ++-- src/transforms/FilterToSchema.ts | 14 ++---- src/transforms/RenameTypes.ts | 4 +- src/utils/clone.ts | 18 ++++--- src/utils/forEachDefaultValue.ts | 8 ++-- src/utils/forEachField.ts | 7 +-- src/utils/getResolversFromSchema.ts | 20 ++++---- src/utils/heal.ts | 39 ++++++++------- src/utils/stub.ts | 9 ++-- src/utils/transformInputValue.ts | 14 +++--- src/utils/visitSchema.ts | 40 +++++++++------- 25 files changed, 176 insertions(+), 204 deletions(-) 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/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index 480f8f5257e..34e211beb2b 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -1,11 +1,13 @@ import { GraphQLField, GraphQLEnumType, - GraphQLScalarType, GraphQLSchema, - GraphQLObjectType, - GraphQLInterfaceType, - GraphQLUnionType, + isSchema, + isScalarType, + isEnumType, + isUnionType, + isInterfaceType, + isObjectType, } from 'graphql'; import { @@ -31,14 +33,13 @@ function addResolversToSchema( legacyInputResolvers?: IResolvers, legacyInputValidationOptions?: IResolverValidationOptions, ) { - const options: IAddResolversToSchemaOptions = - schemaOrOptions instanceof GraphQLSchema - ? { - schema: schemaOrOptions, - resolvers: legacyInputResolvers, - resolverValidationOptions: legacyInputValidationOptions, - } - : schemaOrOptions; + const options: IAddResolversToSchemaOptions = isSchema(schemaOrOptions) + ? { + schema: schemaOrOptions, + resolvers: legacyInputResolvers, + resolverValidationOptions: legacyInputValidationOptions, + } + : schemaOrOptions; const { schema, @@ -81,7 +82,7 @@ function addResolversToSchema( ); } - if (type instanceof GraphQLScalarType) { + if (isScalarType(type)) { // Support -- without recommending -- overriding default scalar types Object.keys(resolverValue).forEach(fieldName => { if (fieldName.startsWith('__')) { @@ -90,7 +91,7 @@ function addResolversToSchema( type[fieldName] = resolverValue[fieldName]; } }); - } else if (type instanceof GraphQLEnumType) { + } 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 @@ -126,7 +127,7 @@ function addResolversToSchema( ...config, values: newValues, }); - } else if (type instanceof GraphQLUnionType) { + } else if (isUnionType(type)) { Object.keys(resolverValue).forEach(fieldName => { if (fieldName.startsWith('__')) { // this is for isTypeOf and resolveType and all the other stuff. @@ -141,10 +142,7 @@ function addResolversToSchema( `${typeName} was defined in resolvers, but it's not an object`, ); }); - } else if ( - type instanceof GraphQLInterfaceType || - type instanceof GraphQLObjectType - ) { + } 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. diff --git a/src/generate/assertResolversPresent.ts b/src/generate/assertResolversPresent.ts index 06173157188..20ce5efc375 100644 --- a/src/generate/assertResolversPresent.ts +++ b/src/generate/assertResolversPresent.ts @@ -2,7 +2,7 @@ import { GraphQLSchema, GraphQLField, getNamedType, - GraphQLScalarType, + isScalarType, } from 'graphql'; import { IResolverValidationOptions } from '../Interfaces'; @@ -45,7 +45,7 @@ function assertResolversPresent( // requires a resolver on every field that returns a non-scalar type if ( requireResolversForNonScalar && - !(getNamedType(field.type) instanceof GraphQLScalarType) + !isScalarType(getNamedType(field.type)) ) { expectResolver(field, typeName, fieldName); } diff --git a/src/generate/attachConnectorsToContext.ts b/src/generate/attachConnectorsToContext.ts index 5f53c1efe35..60d3363d738 100644 --- a/src/generate/attachConnectorsToContext.ts +++ b/src/generate/attachConnectorsToContext.ts @@ -1,5 +1,5 @@ import { deprecated } from 'deprecated-decorator'; -import { GraphQLSchema, GraphQLFieldResolver } from 'graphql'; +import { GraphQLSchema, GraphQLFieldResolver, isSchema } from 'graphql'; import { IConnectors, IConnector, IConnectorCls } from '../Interfaces'; @@ -15,7 +15,7 @@ const attachConnectorsToContext = deprecated( url: 'https://github.com/apollostack/graphql-tools/issues/140', }, (schema: GraphQLSchema, connectors: IConnectors): void => { - if (!schema || !(schema instanceof GraphQLSchema)) { + 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', diff --git a/src/generate/checkForResolveTypeResolver.ts b/src/generate/checkForResolveTypeResolver.ts index 64cb759f163..b643d59de6a 100644 --- a/src/generate/checkForResolveTypeResolver.ts +++ b/src/generate/checkForResolveTypeResolver.ts @@ -1,4 +1,9 @@ -import { GraphQLInterfaceType, GraphQLUnionType, GraphQLSchema } from 'graphql'; +import { + GraphQLInterfaceType, + GraphQLUnionType, + GraphQLSchema, + isAbstractType, +} from 'graphql'; import SchemaError from './SchemaError'; @@ -10,12 +15,7 @@ function checkForResolveTypeResolver( Object.keys(schema.getTypeMap()) .map(typeName => schema.getType(typeName)) .forEach((type: GraphQLUnionType | GraphQLInterfaceType) => { - if ( - !( - type instanceof GraphQLUnionType || - type instanceof GraphQLInterfaceType - ) - ) { + if (!isAbstractType(type)) { return; } if (!type.resolveType) { diff --git a/src/generate/extendResolversFromInterfaces.ts b/src/generate/extendResolversFromInterfaces.ts index e6f9af1d3bd..b0047c27db3 100644 --- a/src/generate/extendResolversFromInterfaces.ts +++ b/src/generate/extendResolversFromInterfaces.ts @@ -1,7 +1,8 @@ import { GraphQLObjectType, GraphQLSchema, - GraphQLInterfaceType, + isObjectType, + isInterfaceType, } from 'graphql'; import { IResolvers } from '../Interfaces'; @@ -21,8 +22,8 @@ function extendResolversFromInterfaces( const typeResolvers = resolvers[typeName]; const type = schema.getType(typeName); if ( - type instanceof GraphQLObjectType || - (graphqlVersion() >= 15 && type instanceof GraphQLInterfaceType) + isObjectType(type) || + (graphqlVersion() >= 15 && isInterfaceType(type)) ) { const interfaceResolvers = (type as GraphQLObjectType) .getInterfaces() diff --git a/src/mock.ts b/src/mock.ts index acda40d476f..edc064dda44 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -2,9 +2,6 @@ import { graphql, GraphQLSchema, GraphQLObjectType, - GraphQLEnumType, - GraphQLUnionType, - GraphQLInterfaceType, GraphQLList, GraphQLType, GraphQLField, @@ -13,8 +10,14 @@ import { getNamedType, GraphQLNamedType, GraphQLFieldResolver, - GraphQLNonNull, GraphQLNullableType, + isSchema, + isObjectType, + isUnionType, + isInterfaceType, + isListType, + isEnumType, + isAbstractType, } from 'graphql'; import { v4 as uuid } from 'uuid'; @@ -37,7 +40,7 @@ function mockServer( 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 { @@ -67,7 +70,7 @@ function addMocksToSchema({ 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)) { @@ -141,36 +144,24 @@ function addMocksToSchema({ 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 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 mock = mockFunctionMap.get(fieldType.name); @@ -189,7 +180,7 @@ function addMocksToSchema({ }; } - if (fieldType instanceof GraphQLEnumType) { + if (isEnumType(fieldType)) { return getRandomElement(fieldType.getValues()).value; } @@ -360,10 +351,7 @@ function mergeMocks(genericMockFunction: () => any, customMock: any): any { } function getResolveType(namedFieldType: GraphQLNamedType) { - if ( - namedFieldType instanceof GraphQLInterfaceType || - namedFieldType instanceof GraphQLUnionType - ) { + if (isAbstractType(namedFieldType)) { return namedFieldType.resolveType; } } @@ -377,10 +365,7 @@ function assignResolveType(type: GraphQLType, preserveResolvers: boolean) { 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 = ( diff --git a/src/stitching/addTypenameToAbstract.ts b/src/stitching/addTypenameToAbstract.ts index 989e91bfe67..362cb9d7f2a 100644 --- a/src/stitching/addTypenameToAbstract.ts +++ b/src/stitching/addTypenameToAbstract.ts @@ -6,9 +6,8 @@ import { visitWithTypeInfo, SelectionSetNode, Kind, - GraphQLInterfaceType, - GraphQLUnionType, GraphQLSchema, + isAbstractType, } from 'graphql'; export function addTypenameToAbstract( @@ -24,11 +23,7 @@ export function addTypenameToAbstract( ): SelectionSetNode | null | undefined { const parentType: GraphQLType = typeInfo.getParentType(); let selections = node.selections; - if ( - parentType != null && - (parentType instanceof GraphQLInterfaceType || - parentType instanceof GraphQLUnionType) - ) { + if (parentType != null && isAbstractType(parentType)) { selections = selections.concat({ kind: Kind.FIELD, name: { diff --git a/src/stitching/createRequest.ts b/src/stitching/createRequest.ts index 3b1f4fbe68c..7f855733b87 100644 --- a/src/stitching/createRequest.ts +++ b/src/stitching/createRequest.ts @@ -14,11 +14,11 @@ import { GraphQLField, GraphQLArgument, VariableDefinitionNode, - GraphQLList, - GraphQLNonNull, TypeNode, GraphQLType, SelectionSetNode, + isNonNullType, + isListType, } from 'graphql'; import { @@ -261,7 +261,7 @@ function updateArguments( } function astFromType(type: GraphQLType): TypeNode { - if (type instanceof GraphQLNonNull) { + if (isNonNullType(type)) { const innerType = astFromType(type.ofType); if (innerType.kind === Kind.NON_NULL_TYPE) { throw new Error( @@ -274,7 +274,7 @@ function astFromType(type: GraphQLType): TypeNode { kind: Kind.NON_NULL_TYPE, type: innerType, }; - } else if (type instanceof GraphQLList) { + } else if (isListType(type)) { return { kind: Kind.LIST_TYPE, type: astFromType(type.ofType), diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index b41b43e1935..7fc8f569c9d 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -7,6 +7,7 @@ import { GraphQLSchema, ExecutionResult, GraphQLOutputType, + isSchema, } from 'graphql'; import { @@ -40,7 +41,7 @@ import { combineErrors } from './errors'; export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, ): any { - if (options instanceof GraphQLSchema) { + if (isSchema(options)) { throw new Error( 'Passing positional arguments to delegateToSchema is deprecated. ' + 'Please pass named parameters instead.', diff --git a/src/stitching/makeMergedType.ts b/src/stitching/makeMergedType.ts index fd342680579..15d9657cf2e 100644 --- a/src/stitching/makeMergedType.ts +++ b/src/stitching/makeMergedType.ts @@ -1,15 +1,10 @@ -import { - GraphQLType, - GraphQLObjectType, - GraphQLInterfaceType, - GraphQLUnionType, -} from 'graphql'; +import { GraphQLType, isAbstractType, isObjectType } from 'graphql'; import defaultMergedResolver from './defaultMergedResolver'; import resolveFromParentTypename from './resolveFromParentTypename'; export function makeMergedType(type: GraphQLType): void { - if (type instanceof GraphQLObjectType) { + if (isObjectType(type)) { type.isTypeOf = undefined; const fieldMap = type.getFields(); @@ -17,10 +12,7 @@ export function makeMergedType(type: GraphQLType): void { fieldMap[fieldName].resolve = defaultMergedResolver; fieldMap[fieldName].subscribe = null; }); - } else if ( - type instanceof GraphQLInterfaceType || - type instanceof GraphQLUnionType - ) { + } else if (isAbstractType(type)) { type.resolveType = parent => resolveFromParentTypename(parent); } } diff --git a/src/stitching/mergeInfo.ts b/src/stitching/mergeInfo.ts index f40ff0847dd..1f2067b9e51 100644 --- a/src/stitching/mergeInfo.ts +++ b/src/stitching/mergeInfo.ts @@ -1,11 +1,12 @@ import { GraphQLNamedType, GraphQLObjectType, - GraphQLScalarType, GraphQLSchema, Kind, SelectionNode, SelectionSetNode, + isObjectType, + isScalarType, } from 'graphql'; import { TypeMap } from 'graphql/type/schema'; @@ -102,7 +103,7 @@ function createMergedTypes( const mergedTypes: Record = {}; Object.keys(typeCandidates).forEach(typeName => { - if (typeCandidates[typeName][0].type instanceof GraphQLObjectType) { + if (isObjectType(typeCandidates[typeName][0].type)) { const mergedTypeCandidates = typeCandidates[typeName].filter( typeCandidate => typeCandidate.subschema != null && @@ -237,7 +238,7 @@ export function completeMergeInfo( Object.keys(resolvers).forEach(typeName => { const type = resolvers[typeName]; - if (type instanceof GraphQLScalarType) { + if (isScalarType(type)) { return; } Object.keys(type).forEach(fieldName => { diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index bd44b90d092..d6df04bd5d1 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -2,7 +2,6 @@ import { DocumentNode, GraphQLNamedType, GraphQLObjectType, - GraphQLScalarType, GraphQLSchema, getNamedType, isNamedType, @@ -13,6 +12,13 @@ import { GraphQLUnionType, GraphQLEnumType, ASTNode, + isSchema, + isDirective, + isScalarType, + isObjectType, + isInterfaceType, + isUnionType, + isEnumType, } from 'graphql'; import { @@ -98,10 +104,7 @@ export default function mergeSchemas({ schemas = [...schemas, ...schemaLikeObjects]; schemas.forEach(schemaLikeObject => { - if ( - schemaLikeObject instanceof GraphQLSchema || - isSubschemaConfig(schemaLikeObject) - ) { + if (isSchema(schemaLikeObject) || isSubschemaConfig(schemaLikeObject)) { const schema = wrapSchema(schemaLikeObject); allSchemas.push(schema); @@ -160,9 +163,9 @@ export default function mergeSchemas({ parsedSchemaDocument.definitions.forEach(def => { const type = typeFromAST(def); - if (type instanceof GraphQLDirective && mergeDirectives) { + if (isDirective(type) && mergeDirectives) { directives.push(type); - } else if (type != null && !(type instanceof GraphQLDirective)) { + } else if (type != null && !isDirective(type)) { addTypeCandidate(typeCandidates, type.name, { type, }); @@ -216,7 +219,7 @@ export default function mergeSchemas({ typeName === 'Mutation' || typeName === 'Subscription' || (mergeTypes === true && - !(typeCandidates[typeName][0].type instanceof GraphQLScalarType)) || + !isScalarType(typeCandidates[typeName][0].type)) || (typeof mergeTypes === 'function' && mergeTypes(typeName, typeCandidates[typeName])) || (Array.isArray(mergeTypes) && mergeTypes.includes(typeName)) || @@ -336,7 +339,7 @@ function merge( `Cannot merge different type categories into common type ${typeName}.`, ); } - if (initialCandidateType instanceof GraphQLObjectType) { + if (isObjectType(initialCandidateType)) { return new GraphQLObjectType({ name: typeName, fields: candidates.reduce( @@ -352,7 +355,7 @@ function merge( return interfaces != null ? acc.concat(interfaces) : acc; }, []), }); - } else if (initialCandidateType instanceof GraphQLInterfaceType) { + } else if (isInterfaceType(initialCandidateType)) { const config = { name: typeName, fields: candidates.reduce( @@ -372,7 +375,7 @@ function merge( : undefined, }; return new GraphQLInterfaceType(config); - } else if (initialCandidateType instanceof GraphQLUnionType) { + } else if (isUnionType(initialCandidateType)) { return new GraphQLUnionType({ name: typeName, types: candidates.reduce( @@ -381,7 +384,7 @@ function merge( [], ), }); - } else if (initialCandidateType instanceof GraphQLEnumType) { + } else if (isEnumType(initialCandidateType)) { return new GraphQLEnumType({ name: typeName, values: candidates.reduce( @@ -392,7 +395,7 @@ function merge( {}, ), }); - } else if (initialCandidateType instanceof GraphQLScalarType) { + } else if (isScalarType(initialCandidateType)) { throw new Error( `Cannot merge type ${typeName}. Merging not supported for GraphQLScalarType.`, ); diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 5f3ba41b5fb..e03ec283f12 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -21,6 +21,9 @@ import { GraphQLUnionType, GraphQLInt, GraphQLOutputType, + isNonNullType, + isScalarType, + isListType, } from 'graphql'; import { assert } from 'chai'; import formatDate from 'dateformat'; @@ -963,14 +966,11 @@ describe('@directives', () => { } private wrapType(field: GraphQLInputField | GraphQLField) { - 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.toString()}`); @@ -1151,7 +1151,7 @@ describe('@directives', () => { const Query = schema.getType('Query') as GraphQLObjectType; const peopleType = Query.getFields().people.type; - if (peopleType instanceof GraphQLList) { + if (isListType(peopleType)) { assert.strictEqual(peopleType.ofType, schema.getType('Human')); } else { throw new Error('Query.people not a GraphQLList type'); diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index 90a19507724..1a286834955 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -4,9 +4,7 @@ import { FragmentDefinitionNode, GraphQLArgument, GraphQLInputType, - GraphQLList, GraphQLField, - GraphQLNonNull, GraphQLObjectType, GraphQLSchema, Kind, @@ -14,6 +12,8 @@ import { SelectionNode, TypeNode, VariableDefinitionNode, + isNonNullType, + isListType, } from 'graphql'; import { Request } from '../Interfaces'; @@ -176,7 +176,7 @@ function addVariablesToRootField( } function typeToAst(type: GraphQLInputType): TypeNode { - if (type instanceof GraphQLNonNull) { + if (isNonNullType(type)) { const innerType = typeToAst(type.ofType); if ( innerType.kind === Kind.LIST_TYPE || @@ -188,7 +188,7 @@ function typeToAst(type: GraphQLInputType): TypeNode { }; } throw new Error('Incorrect inner non-null type'); - } else if (type instanceof GraphQLList) { + } else if (isListType(type)) { return { kind: Kind.LIST_TYPE, type: typeToAst(type.ofType), diff --git a/src/transforms/FilterToSchema.ts b/src/transforms/FilterToSchema.ts index 30ab26ca033..9f9a13f5873 100644 --- a/src/transforms/FilterToSchema.ts +++ b/src/transforms/FilterToSchema.ts @@ -4,8 +4,6 @@ import { FieldNode, FragmentDefinitionNode, FragmentSpreadNode, - GraphQLInterfaceType, - GraphQLObjectType, GraphQLSchema, GraphQLType, InlineFragmentNode, @@ -19,6 +17,8 @@ import { TypeInfo, visitWithTypeInfo, getNamedType, + isObjectType, + isInterfaceType, } from 'graphql'; import { Request } from '../Interfaces'; @@ -222,10 +222,7 @@ function filterSelectionSet( [Kind.FIELD]: { enter(node: FieldNode): null | undefined | FieldNode { const parentType = typeInfo.getParentType(); - if ( - parentType instanceof GraphQLObjectType || - parentType instanceof GraphQLInterfaceType - ) { + if (isObjectType(parentType) || isInterfaceType(parentType)) { const fields = parentType.getFields(); const field = node.name.value === '__typename' @@ -253,10 +250,7 @@ function filterSelectionSet( }, leave(node: FieldNode): null | undefined | FieldNode { const resolvedType = getNamedType(typeInfo.getType()); - if ( - resolvedType instanceof GraphQLObjectType || - resolvedType instanceof GraphQLInterfaceType - ) { + if (isObjectType(resolvedType) || isInterfaceType(resolvedType)) { const selections = node.selectionSet != null ? node.selectionSet.selections : null; if (selections == null || selections.length === 0) { diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index 57e065ff0e2..f321c7997cb 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -4,7 +4,7 @@ import { NamedTypeNode, Kind, GraphQLNamedType, - GraphQLScalarType, + isScalarType, } from 'graphql'; import { isSpecifiedScalarType } from '../polyfills'; @@ -43,7 +43,7 @@ export default class RenameTypes implements Transform { if (isSpecifiedScalarType(type) && !this.renameBuiltins) { return undefined; } - if (type instanceof GraphQLScalarType && !this.renameScalars) { + if (isScalarType(type) && !this.renameScalars) { return undefined; } const oldName = type.name; diff --git a/src/utils/clone.ts b/src/utils/clone.ts index c09bb36e6be..c59df3d4232 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -9,6 +9,12 @@ import { GraphQLSchema, GraphQLUnionType, GraphQLInterfaceTypeConfig, + isObjectType, + isInterfaceType, + isUnionType, + isInputObjectType, + isEnumType, + isScalarType, } from 'graphql'; import { isSpecifiedScalarType, toConfig } from '../polyfills'; @@ -21,7 +27,7 @@ export function cloneDirective(directive: GraphQLDirective): GraphQLDirective { } export function cloneType(type: GraphQLNamedType): GraphQLNamedType { - if (type instanceof GraphQLObjectType) { + if (isObjectType(type)) { const config = toConfig(type); return new GraphQLObjectType({ ...config, @@ -30,7 +36,7 @@ export function cloneType(type: GraphQLNamedType): GraphQLNamedType { ? config.interfaces : (config.interfaces as ReadonlyArray).slice(), }); - } else if (type instanceof GraphQLInterfaceType) { + } else if (isInterfaceType(type)) { const config = toConfig((type as unknown) as GraphQLObjectType); const newConfig = { ...config, @@ -42,17 +48,17 @@ export function cloneType(type: GraphQLNamedType): GraphQLNamedType { return new GraphQLInterfaceType( (newConfig as unknown) as GraphQLInterfaceTypeConfig, ); - } else if (type instanceof GraphQLUnionType) { + } else if (isUnionType(type)) { const config = toConfig(type); return new GraphQLUnionType({ ...config, types: (config.types as ReadonlyArray).slice(), }); - } else if (type instanceof GraphQLInputObjectType) { + } else if (isInputObjectType(type)) { return new GraphQLInputObjectType(toConfig(type)); - } else if (type instanceof GraphQLEnumType) { + } else if (isEnumType(type)) { return new GraphQLEnumType(toConfig(type)); - } else if (type instanceof GraphQLScalarType) { + } else if (isScalarType(type)) { return isSpecifiedScalarType(type) ? type : new GraphQLScalarType(toConfig(type)); diff --git a/src/utils/forEachDefaultValue.ts b/src/utils/forEachDefaultValue.ts index b1267e7b984..e1001b76d67 100644 --- a/src/utils/forEachDefaultValue.ts +++ b/src/utils/forEachDefaultValue.ts @@ -1,8 +1,8 @@ import { getNamedType, - GraphQLInputObjectType, GraphQLSchema, - GraphQLObjectType, + isObjectType, + isInputObjectType, } from 'graphql'; import { IDefaultValueIteratorFn } from '../Interfaces'; @@ -16,7 +16,7 @@ export function forEachDefaultValue( const type = typeMap[typeName]; if (!getNamedType(type).name.startsWith('__')) { - if (type instanceof GraphQLObjectType) { + if (isObjectType(type)) { const fields = type.getFields(); Object.keys(fields).forEach(fieldName => { const field = fields[fieldName]; @@ -25,7 +25,7 @@ export function forEachDefaultValue( arg.defaultValue = fn(arg.type, arg.defaultValue); }); }); - } else if (type instanceof GraphQLInputObjectType) { + } else if (isInputObjectType(type)) { const fields = type.getFields(); Object.keys(fields).forEach(fieldName => { const field = fields[fieldName]; diff --git a/src/utils/forEachField.ts b/src/utils/forEachField.ts index 15fd023fb32..ab66a17be97 100644 --- a/src/utils/forEachField.ts +++ b/src/utils/forEachField.ts @@ -1,4 +1,4 @@ -import { getNamedType, GraphQLObjectType, GraphQLSchema } from 'graphql'; +import { getNamedType, GraphQLSchema, isObjectType } from 'graphql'; import { IFieldIteratorFn } from '../Interfaces'; @@ -11,10 +11,7 @@ export function forEachField( const type = typeMap[typeName]; // TODO: maybe have an option to include these? - if ( - !getNamedType(type).name.startsWith('__') && - type instanceof GraphQLObjectType - ) { + if (!getNamedType(type).name.startsWith('__') && isObjectType(type)) { const fields = type.getFields(); Object.keys(fields).forEach(fieldName => { const field = fields[fieldName]; diff --git a/src/utils/getResolversFromSchema.ts b/src/utils/getResolversFromSchema.ts index 5beb4451b26..471a2c7424c 100644 --- a/src/utils/getResolversFromSchema.ts +++ b/src/utils/getResolversFromSchema.ts @@ -1,10 +1,10 @@ import { GraphQLSchema, - GraphQLScalarType, - GraphQLEnumType, - GraphQLObjectType, - GraphQLInterfaceType, - GraphQLUnionType, + isScalarType, + isEnumType, + isInterfaceType, + isUnionType, + isObjectType, } from 'graphql'; import { IResolvers } from '../Interfaces'; @@ -20,30 +20,30 @@ export function getResolversFromSchema(schema: GraphQLSchema): IResolvers { Object.keys(typeMap).forEach(typeName => { const type = typeMap[typeName]; - if (type instanceof GraphQLScalarType) { + if (isScalarType(type)) { if (!isSpecifiedScalarType(type)) { resolvers[typeName] = cloneType(type); } - } else if (type instanceof GraphQLEnumType) { + } else if (isEnumType(type)) { resolvers[typeName] = {}; const values = type.getValues(); values.forEach(value => { resolvers[typeName][value.name] = value.value; }); - } else if (type instanceof GraphQLInterfaceType) { + } else if (isInterfaceType(type)) { if (type.resolveType != null) { resolvers[typeName] = { __resolveType: type.resolveType, }; } - } else if (type instanceof GraphQLUnionType) { + } else if (isUnionType(type)) { if (type.resolveType != null) { resolvers[typeName] = { __resolveType: type.resolveType, }; } - } else if (type instanceof GraphQLObjectType) { + } else if (isObjectType(type)) { resolvers[typeName] = {}; if (type.isTypeOf != null) { diff --git a/src/utils/heal.ts b/src/utils/heal.ts index f608a22d6c9..0a206c82c2e 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -1,19 +1,24 @@ import { GraphQLDirective, - GraphQLEnumType, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, GraphQLObjectType, GraphQLNamedType, GraphQLNonNull, - GraphQLScalarType, GraphQLType, GraphQLUnionType, isNamedType, GraphQLSchema, GraphQLInputType, GraphQLOutputType, + isObjectType, + isInterfaceType, + isUnionType, + isInputObjectType, + isLeafType, + isListType, + isNonNullType, } from 'graphql'; import each from './each'; @@ -118,26 +123,23 @@ export function healTypes( } function healNamedType(type: GraphQLNamedType) { - if (type instanceof GraphQLObjectType) { + if (isObjectType(type)) { healFields(type); healInterfaces(type); return; - } else if (type instanceof GraphQLInterfaceType) { + } else if (isInterfaceType(type)) { healFields(type); if (graphqlVersion() >= 15) { healInterfaces(type); } return; - } else if (type instanceof GraphQLUnionType) { + } else if (isUnionType(type)) { healUnderlyingTypes(type); return; - } else if (type instanceof GraphQLInputObjectType) { + } else if (isInputObjectType(type)) { healInputFields(type); return; - } else if ( - type instanceof GraphQLScalarType || - type instanceof GraphQLEnumType - ) { + } else if (isLeafType(type)) { return; } @@ -178,10 +180,10 @@ export function healTypes( function healType(type: T): GraphQLType | null { // Unwrap the two known wrapper types - if (type instanceof GraphQLList) { + if (isListType(type)) { const healedType = healType(type.ofType); return healedType != null ? new GraphQLList(healedType) : null; - } else if (type instanceof GraphQLNonNull) { + } else if (isNonNullType(type)) { const healedType = healType(type.ofType); return healedType != null ? new GraphQLNonNull(healedType) : null; } else if (isNamedType(type)) { @@ -214,8 +216,8 @@ function pruneTypes( const implementedInterfaces = {}; each(typeMap, namedType => { if ( - namedType instanceof GraphQLObjectType || - (graphqlVersion() >= 15 && namedType instanceof GraphQLInterfaceType) + isObjectType(namedType) || + (graphqlVersion() >= 15 && isInterfaceType(namedType)) ) { each((namedType as GraphQLObjectType).getInterfaces(), iface => { implementedInterfaces[iface.name] = true; @@ -228,22 +230,19 @@ function pruneTypes( for (let i = 0; i < typeNames.length; i++) { const typeName = typeNames[i]; const type = typeMap[typeName]; - if ( - type instanceof GraphQLObjectType || - type instanceof GraphQLInputObjectType - ) { + if (isObjectType(type) || isInputObjectType(type)) { // prune types with no fields if (!Object.keys(type.getFields()).length) { typeMap[typeName] = null; prunedTypeMap = true; } - } else if (type instanceof GraphQLUnionType) { + } else if (isUnionType(type)) { // prune unions without underlying types if (!type.getTypes().length) { typeMap[typeName] = null; prunedTypeMap = true; } - } else if (type instanceof GraphQLInterfaceType) { + } else if (isInterfaceType(type)) { // prune interfaces without fields or without implementations if ( !Object.keys(type.getFields()).length || diff --git a/src/utils/stub.ts b/src/utils/stub.ts index 7beff74490e..be84a09cf60 100644 --- a/src/utils/stub.ts +++ b/src/utils/stub.ts @@ -8,6 +8,9 @@ import { GraphQLFloat, GraphQLBoolean, GraphQLID, + isObjectType, + isInterfaceType, + isInputObjectType, } from 'graphql'; export function createNamedStub( @@ -34,11 +37,7 @@ export function createNamedStub( } export function isStub(type: GraphQLNamedType): boolean { - if ( - type instanceof GraphQLObjectType || - type instanceof GraphQLInterfaceType || - type instanceof GraphQLInterfaceType - ) { + if (isObjectType(type) || isInterfaceType(type) || isInputObjectType(type)) { const fields = type.getFields(); const fieldNames = Object.keys(fields); return fieldNames.length === 1 && fields[fieldNames[0]].name === '__fake'; diff --git a/src/utils/transformInputValue.ts b/src/utils/transformInputValue.ts index 3024148073a..ed9e1e6a982 100644 --- a/src/utils/transformInputValue.ts +++ b/src/utils/transformInputValue.ts @@ -1,10 +1,11 @@ import { GraphQLEnumType, - GraphQLInputObjectType, GraphQLInputType, - GraphQLList, GraphQLScalarType, getNullableType, + isLeafType, + isListType, + isInputObjectType, } from 'graphql'; type InputValueTransformer = ( @@ -23,16 +24,13 @@ export function transformInputValue( const nullableType = getNullableType(type); - if ( - nullableType instanceof GraphQLEnumType || - nullableType instanceof GraphQLScalarType - ) { + if (isLeafType(nullableType)) { return transformer(nullableType, value); - } else if (nullableType instanceof GraphQLList) { + } else if (isListType(nullableType)) { return value.map((listMember: any) => transformInputValue(nullableType.ofType, listMember, transformer), ); - } else if (nullableType instanceof GraphQLInputObjectType) { + } else if (isInputObjectType(nullableType)) { const fields = nullableType.getFields(); const newValue = {}; Object.keys(value).forEach(key => { diff --git a/src/utils/visitSchema.ts b/src/utils/visitSchema.ts index 6ad883953af..45198d7d49d 100644 --- a/src/utils/visitSchema.ts +++ b/src/utils/visitSchema.ts @@ -1,15 +1,19 @@ import { - GraphQLEnumType, - GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType, - GraphQLScalarType, GraphQLSchema, - GraphQLUnionType, isNamedType, GraphQLType, GraphQLNamedType, GraphQLInputField, + isSchema, + isObjectType, + isInterfaceType, + isInputObjectType, + isScalarType, + isUnionType, + isEnumType, + isInputType, } from 'graphql'; import { @@ -87,7 +91,7 @@ export function visitSchema( return true; } - if (methodName === 'visitSchema' || finalType instanceof GraphQLSchema) { + if (methodName === 'visitSchema' || isSchema(finalType)) { throw new Error( `Method ${methodName} cannot replace schema with ${newType as string}`, ); @@ -115,7 +119,7 @@ export function visitSchema( // 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 | null { - if (type instanceof GraphQLSchema) { + if (isSchema(type)) { // 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. @@ -139,7 +143,7 @@ export function visitSchema( return type; } - if (type instanceof GraphQLObjectType) { + if (isObjectType(type)) { // 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 @@ -151,7 +155,7 @@ export function visitSchema( return newObject; } - if (type instanceof GraphQLInterfaceType) { + if (isInterfaceType(type)) { const newInterface = callMethod('visitInterface', type); if (newInterface != null) { visitFields(newInterface); @@ -159,7 +163,7 @@ export function visitSchema( return newInterface; } - if (type instanceof GraphQLInputObjectType) { + if (isInputObjectType(type)) { const newInputObject = callMethod('visitInputObject', type); if (newInputObject != null) { @@ -179,15 +183,15 @@ export function visitSchema( return newInputObject; } - if (type instanceof GraphQLScalarType) { + if (isScalarType(type)) { return callMethod('visitScalar', type); } - if (type instanceof GraphQLUnionType) { + if (isUnionType(type)) { return callMethod('visitUnion', type); } - if (type instanceof GraphQLEnumType) { + if (isEnumType(type)) { const newEnum = callMethod('visitEnum', type); if (newEnum != null) { @@ -253,7 +257,7 @@ function getTypeSpecifiers( schema: GraphQLSchema, ): Array { const specifiers = [VisitSchemaKind.TYPE]; - if (type instanceof GraphQLObjectType) { + if (isObjectType(type)) { specifiers.push( VisitSchemaKind.COMPOSITE_TYPE, VisitSchemaKind.OBJECT_TYPE, @@ -271,23 +275,23 @@ function getTypeSpecifiers( VisitSchemaKind.SUBSCRIPTION, ); } - } else if (type instanceof GraphQLInputObjectType) { + } else if (isInputType(type)) { specifiers.push(VisitSchemaKind.INPUT_OBJECT_TYPE); - } else if (type instanceof GraphQLInterfaceType) { + } else if (isInterfaceType(type)) { specifiers.push( VisitSchemaKind.COMPOSITE_TYPE, VisitSchemaKind.ABSTRACT_TYPE, VisitSchemaKind.INTERFACE_TYPE, ); - } else if (type instanceof GraphQLUnionType) { + } else if (isUnionType(type)) { specifiers.push( VisitSchemaKind.COMPOSITE_TYPE, VisitSchemaKind.ABSTRACT_TYPE, VisitSchemaKind.UNION_TYPE, ); - } else if (type instanceof GraphQLEnumType) { + } else if (isEnumType(type)) { specifiers.push(VisitSchemaKind.ENUM_TYPE); - } else if (type instanceof GraphQLScalarType) { + } else if (isScalarType(type)) { specifiers.push(VisitSchemaKind.SCALAR_TYPE); } From 2be5d5307ec76a36fcc4e8372945885a441d2146 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 29 Feb 2020 22:07:18 -0500 Subject: [PATCH 204/250] fix(mergeSchemas): remove unnecessarily healing --- src/stitching/mergeSchemas.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index d6df04bd5d1..b98faecee21 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -37,7 +37,6 @@ import { wrapSchema } from '../transforms'; import { SchemaDirectiveVisitor, cloneDirective, - healSchema, healTypes, forEachField, mergeDeep, @@ -283,8 +282,6 @@ export default function mergeSchemas({ ); } - healSchema(mergedSchema); - return mergedSchema; } From 8d0e666dde0abba0ff6483fb38be519769434981 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 29 Feb 2020 22:10:12 -0500 Subject: [PATCH 205/250] fix(extendSchema): adjust polyfill --- src/polyfills/extendSchema.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/polyfills/extendSchema.ts b/src/polyfills/extendSchema.ts index a0e86a09954..58f767f336e 100644 --- a/src/polyfills/extendSchema.ts +++ b/src/polyfills/extendSchema.ts @@ -29,7 +29,6 @@ export function extendSchema( const fields = extendedSchema.getSubscriptionType().getFields(); Object.keys(subscriptionResolvers).forEach(fieldName => { - fields[fieldName].resolve = subscriptionResolvers[fieldName].resolve; fields[fieldName].subscribe = subscriptionResolvers[fieldName].subscribe; }); From ac81da89ac1a3e27bf68af90ce67629ebb72ecea Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Fri, 6 Mar 2020 11:55:03 -0500 Subject: [PATCH 206/250] feat(RenameRootTypes): add new transform Fixes healing, which previously ignored changes in root types. --- src/test/testTransforms.ts | 59 +++++++++++++++++++++ src/transforms/RenameRootTypes.ts | 88 +++++++++++++++++++++++++++++++ src/transforms/index.ts | 1 + src/utils/heal.ts | 56 ++++++++++++++++++-- 4 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 src/transforms/RenameRootTypes.ts diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 312428e2234..22784389173 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -15,6 +15,7 @@ import { delegateToSchema, defaultMergedResolver } from '../stitching'; import { transformSchema, RenameTypes, + RenameRootTypes, FilterTypes, WrapQuery, ExtractField, @@ -24,6 +25,7 @@ import { AddReplacementFragments, } from '../transforms'; import { concatInlineFragments, parseFragmentToInlineFragment } from '../utils'; +import { addMocksToSchema } from '../mock'; import { propertySchema, bookingSchema } from './testingSchemas'; @@ -180,6 +182,63 @@ describe('transforms', () => { }); }); + describe('rename root type', () => { + let schema: GraphQLSchema; + before(() => { + const subschema = makeExecutableSchema({ + typeDefs: ` + schema { + query: QueryRoot + mutation: MutationRoot + } + + type QueryRoot { + foo: String! + } + + type MutationRoot { + doSomething: DoSomethingPayload! + } + + type DoSomethingPayload { + query: QueryRoot! + } + `, + }); + + addMocksToSchema({ schema: subschema }); + + schema = transformSchema(subschema, [ + new RenameRootTypes(name => (name === 'QueryRoot' ? 'Query' : name)), + ]); + }); + + it('should work', async () => { + const result = await graphql( + schema, + ` + mutation { + doSomething { + query { + foo + } + } + } + `, + ); + + expect(result).to.deep.equal({ + data: { + doSomething: { + query: { + foo: 'Hello World', + }, + }, + }, + }); + }); + }); + describe('namespace', () => { let schema: GraphQLSchema; before(() => { diff --git a/src/transforms/RenameRootTypes.ts b/src/transforms/RenameRootTypes.ts new file mode 100644 index 00000000000..519e70afec4 --- /dev/null +++ b/src/transforms/RenameRootTypes.ts @@ -0,0 +1,88 @@ +import { + visit, + GraphQLSchema, + NamedTypeNode, + Kind, + GraphQLObjectType, +} from 'graphql'; + +import { Request, Result, VisitSchemaKind } from '../Interfaces'; +import { Transform } from '../transforms/transforms'; +import { visitSchema, cloneType } from '../utils'; + +export default class RenameRootTypes implements Transform { + private readonly renamer: (name: string) => string | undefined; + private map: { [key: string]: string }; + private reverseMap: { [key: string]: string }; + + constructor(renamer: (name: string) => string | undefined) { + this.renamer = renamer; + this.map = {}; + this.reverseMap = {}; + } + + public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { + return visitSchema(originalSchema, { + [VisitSchemaKind.ROOT_OBJECT]: type => { + const oldName = type.name; + const newName = this.renamer(oldName); + if (newName && newName !== oldName) { + this.map[oldName] = type.name; + this.reverseMap[newName] = oldName; + const newType = cloneType(type) as GraphQLObjectType; + newType.name = newName; + return newType; + } + }, + }); + } + + public transformRequest(originalRequest: Request): Request { + const newDocument = visit(originalRequest.document, { + [Kind.NAMED_TYPE]: (node: NamedTypeNode) => { + const name = node.name.value; + if (name in this.reverseMap) { + return { + ...node, + name: { + kind: Kind.NAME, + value: this.reverseMap[name], + }, + }; + } + }, + }); + return { + document: newDocument, + variables: originalRequest.variables, + }; + } + + public transformResult(result: Result): Result { + return { + ...result, + data: this.renameTypes(result.data), + }; + } + + private renameTypes(value: any): any { + if (value == null) { + return value; + } else if (Array.isArray(value)) { + value.forEach((v, index) => { + value[index] = this.renameTypes(v); + }); + return value; + } else if (typeof value === 'object') { + Object.keys(value).forEach(key => { + value[key] = + key === '__typename' + ? this.renamer(value[key]) + : this.renameTypes(value[key]); + }); + return value; + } + + return value; + } +} diff --git a/src/transforms/index.ts b/src/transforms/index.ts index fcdff45b7b2..052153c0a73 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -16,6 +16,7 @@ export { default as FilterToSchema } from './FilterToSchema'; export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; export { default as RenameTypes } from './RenameTypes'; +export { default as RenameRootTypes } from './RenameRootTypes'; export { default as FilterTypes } from './FilterTypes'; export { default as TransformRootFields } from './TransformRootFields'; export { default as RenameRootFields } from './RenameRootFields'; diff --git a/src/utils/heal.ts b/src/utils/heal.ts index 0a206c82c2e..c1954d3cccf 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -21,10 +21,11 @@ import { isNonNullType, } from 'graphql'; +import { toConfig } from '../polyfills'; + import each from './each'; import updateEachKey from './updateEachKey'; import { isStub, getBuiltInForStub } from './stub'; -import { cloneSchema } from './clone'; import { graphqlVersion } from './graphqlVersion'; type NamedTypeMap = { @@ -36,11 +37,60 @@ const hasOwn = Object.prototype.hasOwnProperty; // Update any references to named schema types that disagree with the named // types found in schema.getTypeMap(). export function healSchema(schema: GraphQLSchema): GraphQLSchema { - healTypes(schema.getTypeMap(), schema.getDirectives()); + const typeMap = schema.getTypeMap(); + const directives = schema.getDirectives(); + + const queryType = schema.getQueryType(); + const mutationType = schema.getMutationType(); + const subscriptionType = schema.getSubscriptionType(); + + const newQueryTypeName = + queryType != null + ? typeMap[queryType.name] != null + ? typeMap[queryType.name].name + : undefined + : undefined; + const newMutationTypeName = + mutationType != null + ? typeMap[mutationType.name] != null + ? typeMap[mutationType.name].name + : undefined + : undefined; + const newSubscriptionTypeName = + subscriptionType != null + ? typeMap[subscriptionType.name] != null + ? typeMap[subscriptionType.name].name + : undefined + : undefined; + + healTypes(typeMap, directives); + + const filteredTypeMap = {}; + + Object.keys(typeMap).forEach(typeName => { + if (!typeName.startsWith('__')) { + filteredTypeMap[typeName] = typeMap[typeName]; + } + }); + + const healedSchema = new GraphQLSchema({ + ...toConfig(schema), + query: newQueryTypeName ? filteredTypeMap[newQueryTypeName] : undefined, + mutation: newMutationTypeName + ? filteredTypeMap[newMutationTypeName] + : undefined, + subscription: newSubscriptionTypeName + ? filteredTypeMap[newSubscriptionTypeName] + : undefined, + types: Object.keys(filteredTypeMap).map( + typeName => filteredTypeMap[typeName], + ), + directives: directives.slice(), + }); // Reconstruct the schema to reinitialize private variables // e.g. the stored implementation map and the proper root types. - Object.assign(schema, cloneSchema(schema)); + Object.assign(schema, healedSchema); return schema; } From bad75aae38db69ca174b56fb7c52a4e614ccb4b8 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Fri, 6 Mar 2020 16:15:41 -0500 Subject: [PATCH 207/250] fix(stitching): do not reproxy a nested root field --- src/stitching/resolvers.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 1123c2cbc14..634bb2f7224 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -9,6 +9,9 @@ import { Transform } from '../transforms'; import delegateToSchema from './delegateToSchema'; import { makeMergedType } from './makeMergedType'; +import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; +import { getErrors, getSubschema } from './proxiedResult'; +import { handleResult } from './checkResultAndHandleErrors'; export type Mapping = { [typeName: string]: { @@ -112,13 +115,27 @@ function defaultCreateProxyingResolver({ schema: SubschemaConfig; transforms: Array; }): GraphQLFieldResolver { - return (_parent, _args, context, info) => - delegateToSchema({ + return (parent, _args, context, info) => { + if (parent != null) { + const responseKey = getResponseKeyFromInfo(info); + const errors = getErrors(parent, responseKey); + + // check to see if parent is a proxied result, possible if root types are also nested + if (errors != null) { + const result = parent[responseKey]; + const subschema = getSubschema(parent, responseKey); + + return handleResult(result, errors, subschema, context, info); + } + } + + return delegateToSchema({ schema, context, info, transforms, }); + }; } export function stripResolvers(schema: GraphQLSchema): void { From 210662c93061f82c10633bbeca9c8c11aaffad4b Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Fri, 6 Mar 2020 16:32:04 -0500 Subject: [PATCH 208/250] feat(stitching): allow specification of custom root object names --- src/stitching/mergeSchemas.ts | 26 ++++++++----- src/test/testMergeSchemas.ts | 69 ++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 15 deletions(-) diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index b98faecee21..23685a29092 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -69,6 +69,9 @@ export default function mergeSchemas({ inheritResolversFromInterfaces, mergeTypes = false, mergeDirectives, + queryTypeName = 'Query', + mutationTypeName = 'Mutation', + subscriptionTypeName = 'Subscription', }: { subschemas?: Array; types?: Array; @@ -86,6 +89,9 @@ export default function mergeSchemas({ mergeTypeCandidates: Array, ) => boolean); mergeDirectives?: boolean; + queryTypeName?: string; + mutationTypeName?: string; + subscriptionTypeName?: string; }): GraphQLSchema { const allSchemas: Array = []; const typeCandidates: { [name: string]: Array } = {}; @@ -109,13 +115,13 @@ export default function mergeSchemas({ allSchemas.push(schema); const operationTypes = { - Query: schema.getQueryType(), - Mutation: schema.getMutationType(), - Subscription: schema.getSubscriptionType(), + [queryTypeName]: schema.getQueryType(), + [mutationTypeName]: schema.getMutationType(), + [subscriptionTypeName]: schema.getSubscriptionType(), }; Object.keys(operationTypes).forEach(typeName => { - if (operationTypes[typeName]) { + if (operationTypes[typeName] != null) { addTypeCandidate(typeCandidates, typeName, { schema, type: operationTypes[typeName], @@ -214,9 +220,9 @@ export default function mergeSchemas({ Object.keys(typeCandidates).forEach(typeName => { if ( - typeName === 'Query' || - typeName === 'Mutation' || - typeName === 'Subscription' || + typeName === queryTypeName || + typeName === mutationTypeName || + typeName === subscriptionTypeName || (mergeTypes === true && !isScalarType(typeCandidates[typeName][0].type)) || (typeof mergeTypes === 'function' && @@ -237,9 +243,9 @@ export default function mergeSchemas({ healTypes(typeMap, directives, { skipPruning: true }); let mergedSchema = new GraphQLSchema({ - query: typeMap.Query as GraphQLObjectType, - mutation: typeMap.Mutation as GraphQLObjectType, - subscription: typeMap.Subscription as GraphQLObjectType, + query: typeMap[queryTypeName] as GraphQLObjectType, + mutation: typeMap[mutationTypeName] as GraphQLObjectType, + subscription: typeMap[subscriptionTypeName] as GraphQLObjectType, types: Object.keys(typeMap).map(key => typeMap[key]), directives: directives.length ? directives.map(directive => cloneDirective(directive)) diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index feb8df65fa4..5cb8e2b19ac 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -14,13 +14,16 @@ import { printSchema, } from 'graphql'; -import mergeSchemas from '../stitching/mergeSchemas'; -import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { IResolvers, SubschemaConfig } from '../Interfaces'; -import { delegateToSchema } from '../stitching'; -import { cloneSchema, graphqlVersion } from '../utils'; -import { getResolversFromSchema } from '../utils/getResolversFromSchema'; +import { delegateToSchema, mergeSchemas } from '../stitching'; +import { + cloneSchema, + getResolversFromSchema, + graphqlVersion, + SchemaDirectiveVisitor, +} from '../utils'; +import { addMocksToSchema } from '../mock'; import { propertySchema as localPropertySchema, @@ -3011,4 +3014,60 @@ fragment BookingFragment on Booking { }); }); }); + + describe('new root type name', () => { + it('works', async () => { + const bookSchema = makeExecutableSchema({ + typeDefs: ` + type Query { + book: Book + } + type Book { + name: String + } + `, + }); + + const movieSchema = makeExecutableSchema({ + typeDefs: ` + type Query { + movie: Movie + } + + type Movie { + name: String + } + `, + }); + + addMocksToSchema({ schema: bookSchema }); + addMocksToSchema({ schema: movieSchema }); + + const mergedSchema = mergeSchemas({ + schemas: [bookSchema, movieSchema], + queryTypeName: 'RootQuery', + }); + + const result = await graphql( + mergedSchema, + ` + query { + ... on RootQuery { + book { + name + } + } + } + `, + ); + + expect(result).to.deep.equal({ + data: { + book: { + name: 'Hello World', + }, + }, + }); + }); + }); }); From 86d0a6c88ed1d098bf2b271ea5b0e9a28e778f07 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 8 Mar 2020 13:36:24 -0400 Subject: [PATCH 209/250] fix(RenameRootTypes): to work when stitching --- src/stitching/resolvers.ts | 8 ++-- src/test/testTransforms.ts | 95 +++++++++++++++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/src/stitching/resolvers.ts b/src/stitching/resolvers.ts index 634bb2f7224..6e8d78e2ca2 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitching/resolvers.ts @@ -120,12 +120,14 @@ function defaultCreateProxyingResolver({ const responseKey = getResponseKeyFromInfo(info); const errors = getErrors(parent, responseKey); - // check to see if parent is a proxied result, possible if root types are also nested if (errors != null) { - const result = parent[responseKey]; const subschema = getSubschema(parent, responseKey); - return handleResult(result, errors, subschema, context, info); + // if parent contains a proxied result from this subschema, can return that result + if (schema === subschema) { + const result = parent[responseKey]; + return handleResult(result, errors, subschema, context, info); + } } } diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 22784389173..280dfe88e00 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -11,7 +11,11 @@ import { } from 'graphql'; import { makeExecutableSchema } from '../makeExecutableSchema'; -import { delegateToSchema, defaultMergedResolver } from '../stitching'; +import { + delegateToSchema, + defaultMergedResolver, + mergeSchemas, +} from '../stitching'; import { transformSchema, RenameTypes, @@ -183,8 +187,7 @@ describe('transforms', () => { }); describe('rename root type', () => { - let schema: GraphQLSchema; - before(() => { + it('should work', async () => { const subschema = makeExecutableSchema({ typeDefs: ` schema { @@ -208,12 +211,10 @@ describe('transforms', () => { addMocksToSchema({ schema: subschema }); - schema = transformSchema(subschema, [ + const schema = transformSchema(subschema, [ new RenameRootTypes(name => (name === 'QueryRoot' ? 'Query' : name)), ]); - }); - it('should work', async () => { const result = await graphql( schema, ` @@ -237,6 +238,88 @@ describe('transforms', () => { }, }); }); + + it('works with mergeSchemas', async () => { + const schemaWithCustomRootTypeNames = makeExecutableSchema({ + typeDefs: ` + schema { + query: QueryRoot + mutation: MutationRoot + } + + type QueryRoot { + foo: String! + } + + type MutationRoot { + doSomething: DoSomethingPayload! + } + + type DoSomethingPayload { + somethingChanged: Boolean! + query: QueryRoot! + } + `, + }); + + addMocksToSchema({ schema: schemaWithCustomRootTypeNames }); + + const schemaWithDefaultRootTypeNames = makeExecutableSchema({ + typeDefs: ` + type Query { + bar: String! + } + + type Mutation { + doSomethingElse: DoSomethingElsePayload! + } + + type DoSomethingElsePayload { + somethingElseChanged: Boolean! + query: Query! + } + `, + }); + + addMocksToSchema({ schema: schemaWithDefaultRootTypeNames }); + + const mergedSchema = mergeSchemas({ + subschemas: [ + schemaWithCustomRootTypeNames, + { + schema: schemaWithDefaultRootTypeNames, + transforms: [new RenameRootTypes(name => `${name}Root`)], + }, + ], + queryTypeName: 'QueryRoot', + mutationTypeName: 'MutationRoot', + }); + + const result = await graphql( + mergedSchema, + ` + mutation { + doSomething { + query { + foo + bar + } + } + } + `, + ); + + expect(result).to.deep.equal({ + data: { + doSomething: { + query: { + foo: 'Hello World', + bar: 'Hello World', + }, + }, + }, + }); + }); }); describe('namespace', () => { From 98964d0b7cbbb4c3332d7fba23eb50db7d19e037 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 8 Mar 2020 23:01:38 -0400 Subject: [PATCH 210/250] feat(mapSchema): initial version mapSchema enables transformation of schema objects without modification of the original schema. See https://github.com/graphql/graphql-js/pull/1199 --- src/test/testMapSchema.ts | 64 +++++ src/utils/index.ts | 1 + src/utils/map.ts | 480 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 545 insertions(+) create mode 100644 src/test/testMapSchema.ts create mode 100644 src/utils/map.ts diff --git a/src/test/testMapSchema.ts b/src/test/testMapSchema.ts new file mode 100644 index 00000000000..7248cc17167 --- /dev/null +++ b/src/test/testMapSchema.ts @@ -0,0 +1,64 @@ +import { expect } from 'chai'; +import { GraphQLSchema, GraphQLObjectType, graphqlSync } from 'graphql'; + +import { makeExecutableSchema, mapSchema } from '../index'; +import { MapperKind } from '../utils/map'; + +describe('mapSchema', () => { + it('does not throw', () => { + const schema = makeExecutableSchema({ + typeDefs: ` + type Query { + version: String + } + `, + }); + + const newSchema = mapSchema(schema, {}); + expect(newSchema).to.be.instanceOf(GraphQLSchema); + }); + + it('can add a resolver', () => { + const schema = makeExecutableSchema({ + typeDefs: ` + type Query { + version: Int + } + `, + }); + + const newSchema = mapSchema(schema, { + [MapperKind.QUERY]: type => { + const queryConfig = type.toConfig(); + queryConfig.fields.version.resolve = () => 1; + return new GraphQLObjectType(queryConfig); + }, + }); + + expect(newSchema).to.be.instanceOf(GraphQLSchema); + + const result = graphqlSync(newSchema, '{ version }'); + expect(result.data.version).to.equal(1); + }); + + it('can change the root query name', () => { + const schema = makeExecutableSchema({ + typeDefs: ` + type Query { + version: Int + } + `, + }); + + const newSchema = mapSchema(schema, { + [MapperKind.QUERY]: type => { + const queryConfig = type.toConfig(); + queryConfig.name = 'RootQuery'; + return new GraphQLObjectType(queryConfig); + }, + }); + + expect(newSchema).to.be.instanceOf(GraphQLSchema); + expect(newSchema.getQueryType().name).to.equal('RootQuery'); + }); +}); diff --git a/src/utils/index.ts b/src/utils/index.ts index 0134ffebb0d..a296287e458 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -27,3 +27,4 @@ export { export { appendFields, removeFields } from './fields'; export { createNamedStub } from './stub'; export { graphqlVersion } from './graphqlVersion'; +export { mapSchema } from './map'; diff --git a/src/utils/map.ts b/src/utils/map.ts new file mode 100644 index 00000000000..7a8dd3c4a8b --- /dev/null +++ b/src/utils/map.ts @@ -0,0 +1,480 @@ +import { + GraphQLDirective, + GraphQLEnumType, + GraphQLFieldConfigArgumentMap, + GraphQLFieldConfigMap, + GraphQLInputFieldConfigMap, + GraphQLInputObjectType, + GraphQLInterfaceType, + GraphQLList, + GraphQLObjectType, + GraphQLObjectTypeConfig, + GraphQLNamedType, + GraphQLNonNull, + GraphQLScalarType, + GraphQLSchema, + GraphQLType, + GraphQLUnionType, + isInterfaceType, + isEnumType, + isInputType, + isInputObjectType, + isListType, + isNamedType, + isNonNullType, + isObjectType, + isScalarType, + isUnionType, +} from 'graphql'; + +import { toConfig, isSpecifiedScalarType } from '../polyfills'; +import { graphqlVersion } from '../utils'; + +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; + +export function mapSchema( + schema: GraphQLSchema, + schemaMapper: SchemaMapper, +): GraphQLSchema { + const originalTypeMap = schema.getTypeMap(); + const newTypeMap = {}; + Object.keys(originalTypeMap).forEach(typeName => { + if (!typeName.startsWith('__')) { + const specifiers = getTypeSpecifiers(originalTypeMap[typeName], schema); + const typeMapper = getMapper(schemaMapper, specifiers); + + newTypeMap[typeName] = + typeMapper != null + ? typeMapper(originalTypeMap[typeName], schema) + : originalTypeMap[typeName]; + } + }); + + const queryType = schema.getQueryType(); + const mutationType = schema.getMutationType(); + const subscriptionType = schema.getSubscriptionType(); + + const newQueryTypeName = + queryType != null + ? newTypeMap[queryType.name] != null + ? newTypeMap[queryType.name].name + : undefined + : undefined; + const newMutationTypeName = + mutationType != null + ? newTypeMap[mutationType.name] != null + ? newTypeMap[mutationType.name].name + : undefined + : undefined; + const newSubscriptionTypeName = + subscriptionType != null + ? newTypeMap[subscriptionType.name] != null + ? newTypeMap[subscriptionType.name].name + : undefined + : undefined; + + const originalDirectives = schema.getDirectives(); + let newDirectives: Array; + const directiveMapper = schemaMapper[MapperKind.DIRECTIVE]; + if (directiveMapper != null) { + newDirectives = []; + originalDirectives.forEach(directive => { + const newDirective = directiveMapper(directive, schema); + if (newDirective != null) { + newDirectives.push(newDirective); + } + }); + } else { + newDirectives = originalDirectives.slice(); + } + + const { typeMap, directives } = rewireTypes(newTypeMap, newDirectives); + + return new GraphQLSchema({ + ...toConfig(schema), + query: newQueryTypeName + ? (typeMap[newQueryTypeName] as GraphQLObjectType) + : undefined, + mutation: newMutationTypeName + ? (typeMap[newMutationTypeName] as GraphQLObjectType) + : undefined, + subscription: + newSubscriptionTypeName != null + ? (typeMap[newSubscriptionTypeName] as GraphQLObjectType) + : undefined, + types: Object.keys(typeMap).map(typeName => typeMap[typeName]), + directives, + }); +} + +function getTypeSpecifiers( + type: GraphQLType | GraphQLDirective, + schema: GraphQLSchema, +): Array { + const specifiers = [MapperKind.TYPE]; + if (isObjectType(type)) { + specifiers.push(MapperKind.COMPOSITE_TYPE, MapperKind.OBJECT_TYPE); + const query = schema.getQueryType(); + const mutation = schema.getMutationType(); + const subscription = schema.getSubscriptionType(); + if (type === query) { + specifiers.push(MapperKind.ROOT_OBJECT, MapperKind.QUERY); + } else if (type === mutation) { + specifiers.push(MapperKind.ROOT_OBJECT, MapperKind.MUTATION); + } else if (type === subscription) { + specifiers.push(MapperKind.ROOT_OBJECT, MapperKind.SUBSCRIPTION); + } + } else if (isInputType(type)) { + specifiers.push(MapperKind.INPUT_OBJECT_TYPE); + } else if (isInterfaceType(type)) { + specifiers.push( + MapperKind.COMPOSITE_TYPE, + MapperKind.ABSTRACT_TYPE, + MapperKind.INTERFACE_TYPE, + ); + } else if (isUnionType(type)) { + specifiers.push( + MapperKind.COMPOSITE_TYPE, + MapperKind.ABSTRACT_TYPE, + MapperKind.UNION_TYPE, + ); + } else if (isEnumType(type)) { + specifiers.push(MapperKind.ENUM_TYPE); + } else if (isScalarType(type)) { + specifiers.push(MapperKind.SCALAR_TYPE); + } + + return specifiers; +} + +function getMapper( + schemaMapper: SchemaMapper, + specifiers: Array, +): NamedTypeMapper | null { + let typeMapper: NamedTypeMapper | undefined; + const stack = [...specifiers]; + while (!typeMapper && stack.length > 0) { + const next = stack.pop(); + typeMapper = schemaMapper[next] as NamedTypeMapper; + } + + return typeMapper != null ? typeMapper : null; +} + +export function rewireTypes( + originalTypeMap: Record, + directives: ReadonlyArray, +): { + typeMap: Record; + directives: Array; +} { + const newTypeMap: Record = Object.create(null); + + Object.keys(originalTypeMap).forEach(typeName => { + const namedType = originalTypeMap[typeName]; + + if (namedType == null || typeName.startsWith('__')) { + return; + } + + const newName = namedType.name; + if (newName.startsWith('__')) { + return; + } + + if (newTypeMap[newName] != null) { + throw new Error(`Duplicate schema type name ${newName}`); + } + + newTypeMap[newName] = namedType; + }); + + const newDirectives = directives.map(directive => rewireDirective(directive)); + + Object.keys(newTypeMap).forEach(typeName => { + const namedType = newTypeMap[typeName]; + if (!typeName.startsWith('__')) { + newTypeMap[typeName] = rewireNamedType(namedType); + } + }); + + return pruneTypes(newTypeMap, newDirectives); + + function rewireDirective(directive: GraphQLDirective): GraphQLDirective { + const directiveConfig = toConfig(directive); + directiveConfig.args = rewireArgs(directiveConfig.args); + return new GraphQLDirective(directiveConfig); + } + + function rewireArgs( + args: GraphQLFieldConfigArgumentMap, + ): GraphQLFieldConfigArgumentMap { + const rewiredArgs = {}; + Object.keys(args).forEach(argName => { + const arg = args[argName]; + const rewiredArgType = rewireType(arg.type); + if (rewiredArgType != null) { + arg.type = rewiredArgType; + rewiredArgs[argName] = arg; + } + }); + return rewiredArgs; + } + + function rewireNamedType(type: T) { + if (isObjectType(type)) { + const objectConfig = toConfig(type); + objectConfig.fields = rewireFields( + objectConfig.fields as GraphQLFieldConfigMap, + ); + objectConfig.interfaces = rewireNamedTypes( + objectConfig.interfaces as Array, + ); + return new GraphQLObjectType(objectConfig); + } else if (isInterfaceType(type)) { + const interfaceConfig = toConfig(type); + interfaceConfig.fields = rewireFields( + interfaceConfig.fields as GraphQLFieldConfigMap, + ); + if (graphqlVersion() >= 15) { + ((interfaceConfig as unknown) as GraphQLObjectTypeConfig< + any, + any + >).interfaces = rewireNamedTypes( + ((interfaceConfig as unknown) as GraphQLObjectTypeConfig) + .interfaces as Array, + ); + } + return new GraphQLInterfaceType(interfaceConfig); + } else if (isUnionType(type)) { + const unionConfig = toConfig(type); + unionConfig.types = rewireNamedTypes( + unionConfig.types as Array, + ); + return new GraphQLUnionType(unionConfig); + } else if (isInputObjectType(type)) { + const inputObjectConfig = toConfig(type); + inputObjectConfig.fields = rewireInputFields( + inputObjectConfig.fields as GraphQLInputFieldConfigMap, + ); + return new GraphQLInputObjectType(inputObjectConfig); + } else if (isEnumType(type)) { + const enumConfig = toConfig(type); + return new GraphQLEnumType(enumConfig); + } else if (isScalarType(type)) { + if (isSpecifiedScalarType(type)) { + return type; + } + const scalarConfig = toConfig(type); + return new GraphQLScalarType(scalarConfig); + } + + throw new Error(`Unexpected schema type: ${(type as unknown) as string}`); + } + + function rewireFields( + fields: GraphQLFieldConfigMap, + ): GraphQLFieldConfigMap { + const rewiredFields = {}; + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + const rewiredFieldType = rewireType(field.type); + if (rewiredFieldType != null) { + field.type = rewiredFieldType; + field.args = rewireArgs(field.args); + rewiredFields[fieldName] = field; + } + }); + return rewiredFields; + } + + function rewireInputFields( + fields: GraphQLInputFieldConfigMap, + ): GraphQLInputFieldConfigMap { + const rewiredFields = {}; + Object.keys(fields).forEach(fieldName => { + const field = fields[fieldName]; + const rewiredFieldType = rewireType(field.type); + if (rewiredFieldType != null) { + field.type = rewiredFieldType; + rewiredFields[fieldName] = field; + } + }); + return rewiredFields; + } + + function rewireNamedTypes(namedTypes: Array) { + const rewiredTypes: Array = []; + namedTypes.forEach(namedType => { + const rewiredType = rewireType(namedType); + if (rewiredType != null) { + rewiredTypes.push(rewiredType); + } + }); + return rewiredTypes; + } + + function rewireType(type: T): T | null { + if (isListType(type)) { + const rewiredType = rewireType(type.ofType); + return rewiredType != null ? (new GraphQLList(rewiredType) as T) : null; + } else if (isNonNullType(type)) { + const rewiredType = rewireType(type.ofType); + return rewiredType != null + ? (new GraphQLNonNull(rewiredType) as T) + : null; + } else if (isNamedType(type)) { + return type; + } + + return null; + } +} + +function pruneTypes( + typeMap: Record, + directives: Array, +): { + typeMap: Record; + directives: Array; +} { + const newTypeMap = {}; + + const implementedInterfaces = {}; + Object.keys(typeMap).forEach(typeName => { + const namedType = typeMap[typeName]; + + if ( + isObjectType(namedType) || + (graphqlVersion() >= 15 && isInterfaceType(namedType)) + ) { + (namedType as GraphQLObjectType).getInterfaces().forEach(iface => { + implementedInterfaces[iface.name] = true; + }); + } + }); + + let prunedTypeMap = false; + const typeNames = Object.keys(typeMap); + for (let i = 0; i < typeNames.length; i++) { + const typeName = typeNames[i]; + const type = typeMap[typeName]; + if (isObjectType(type) || isInputObjectType(type)) { + // prune types with no fields + if (Object.keys(type.getFields()).length) { + newTypeMap[typeName] = type; + } else { + prunedTypeMap = true; + } + } else if (isUnionType(type)) { + // prune unions without underlying types + if (type.getTypes().length) { + newTypeMap[typeName] = type; + } else { + prunedTypeMap = true; + } + } else if (isInterfaceType(type)) { + // prune interfaces without fields or without implementations + if ( + Object.keys(type.getFields()).length && + implementedInterfaces[type.name] + ) { + newTypeMap[typeName] = type; + } else { + prunedTypeMap = true; + } + } + } + + // every prune requires another round of healing + return prunedTypeMap + ? rewireTypes(newTypeMap, directives) + : { typeMap, directives }; +} From 5f85c76bcf60ca5a7089b3cfdcaba240ddae5c6d Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 8 Mar 2020 23:16:38 -0400 Subject: [PATCH 211/250] fix(tests): use toConfig polyfill for all tests --- src/test/testMapSchema.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/test/testMapSchema.ts b/src/test/testMapSchema.ts index 7248cc17167..f793bf0245d 100644 --- a/src/test/testMapSchema.ts +++ b/src/test/testMapSchema.ts @@ -1,8 +1,14 @@ import { expect } from 'chai'; -import { GraphQLSchema, GraphQLObjectType, graphqlSync } from 'graphql'; +import { + GraphQLFieldConfig, + GraphQLObjectType, + GraphQLSchema, + graphqlSync, +} from 'graphql'; import { makeExecutableSchema, mapSchema } from '../index'; import { MapperKind } from '../utils/map'; +import { toConfig } from '../polyfills'; describe('mapSchema', () => { it('does not throw', () => { @@ -29,8 +35,10 @@ describe('mapSchema', () => { const newSchema = mapSchema(schema, { [MapperKind.QUERY]: type => { - const queryConfig = type.toConfig(); - queryConfig.fields.version.resolve = () => 1; + const queryConfig = toConfig(type); + (queryConfig.fields as { + version: GraphQLFieldConfig; + }).version.resolve = () => 1; return new GraphQLObjectType(queryConfig); }, }); @@ -52,7 +60,7 @@ describe('mapSchema', () => { const newSchema = mapSchema(schema, { [MapperKind.QUERY]: type => { - const queryConfig = type.toConfig(); + const queryConfig = toConfig(type); queryConfig.name = 'RootQuery'; return new GraphQLObjectType(queryConfig); }, From 39f2a28b9e1424ac11b476f2bb54dc1291ab7e2c Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 9 Mar 2020 22:26:12 -0400 Subject: [PATCH 212/250] fix(toConfig): touch up types toConfig returns an actual configuration, rather than a thunk. --- src/polyfills/toConfig.ts | 18 ++++++++++++++---- src/stitching/mergeSchemas.ts | 15 ++++++--------- src/test/testMapSchema.ts | 11 ++--------- src/test/testUtils.ts | 4 +--- src/utils/clone.ts | 20 ++++++++++++-------- src/utils/fields.ts | 4 ++-- src/utils/map.ts | 25 +++++++++---------------- 7 files changed, 46 insertions(+), 51 deletions(-) diff --git a/src/polyfills/toConfig.ts b/src/polyfills/toConfig.ts index 2bb490db358..ea7c00e115b 100644 --- a/src/polyfills/toConfig.ts +++ b/src/polyfills/toConfig.ts @@ -8,6 +8,7 @@ import { GraphQLObjectType, GraphQLObjectTypeConfig, GraphQLFieldConfigMap, + GraphQLInputFieldConfigMap, GraphQLInterfaceType, GraphQLInterfaceTypeConfig, GraphQLUnionType, @@ -77,14 +78,21 @@ export function toConfig( schemaOrTypeOrDirective: GraphQLSchema, ): GraphQLSchemaConfig; export function toConfig( - schemaOrTypeOrDirective: GraphQLObjectType, + schemaOrTypeOrDirective: GraphQLObjectTypeConfig & { + interfaces: Array; + fields: GraphQLFieldConfigMap; + }, ): GraphQLObjectTypeConfig; export function toConfig( schemaOrTypeOrDirective: GraphQLInterfaceType, -): GraphQLInterfaceTypeConfig; +): GraphQLInterfaceTypeConfig & { + fields: GraphQLFieldConfigMap; +}; export function toConfig( schemaOrTypeOrDirective: GraphQLUnionType, -): GraphQLUnionTypeConfig; +): GraphQLUnionTypeConfig & { + types: Array; +}; export function toConfig( schemaOrTypeOrDirective: GraphQLEnumType, ): GraphQLEnumTypeConfig; @@ -93,7 +101,9 @@ export function toConfig( ): GraphQLScalarTypeConfig; export function toConfig( schemaOrTypeOrDirective: GraphQLInputObjectType, -): GraphQLInputObjectTypeConfig; +): GraphQLInputObjectTypeConfig & { + fields: GraphQLInputFieldConfigMap; +}; export function toConfig( schemaOrTypeOrDirective: GraphQLDirective, ): GraphQLDirectiveConfig; diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index 23685a29092..31a71e83308 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -348,13 +348,12 @@ function merge( fields: candidates.reduce( (acc, candidate) => ({ ...acc, - ...toConfig(candidate.type as GraphQLObjectType).fields, + ...toConfig(candidate.type).fields, }), {}, ), interfaces: candidates.reduce((acc, candidate) => { - const interfaces = toConfig(candidate.type as GraphQLObjectType) - .interfaces; + const interfaces = toConfig(candidate.type).interfaces; return interfaces != null ? acc.concat(interfaces) : acc; }, []), }); @@ -364,15 +363,14 @@ function merge( fields: candidates.reduce( (acc, candidate) => ({ ...acc, - ...toConfig(candidate.type as GraphQLObjectType).fields, + ...toConfig(candidate.type).fields, }), {}, ), interfaces: graphqlVersion() >= 15 ? candidates.reduce((acc, candidate) => { - const interfaces = toConfig(candidate.type as GraphQLObjectType) - .interfaces; + const interfaces = toConfig(candidate.type).interfaces; return interfaces != null ? acc.concat(interfaces) : acc; }, []) : undefined, @@ -382,8 +380,7 @@ function merge( return new GraphQLUnionType({ name: typeName, types: candidates.reduce( - (acc, candidate) => - acc.concat(toConfig(candidate.type as GraphQLUnionType).types), + (acc, candidate) => acc.concat(toConfig(candidate.type).types), [], ), }); @@ -393,7 +390,7 @@ function merge( values: candidates.reduce( (acc, candidate) => ({ ...acc, - ...toConfig(candidate.type as GraphQLEnumType).values, + ...toConfig(candidate.type).values, }), {}, ), diff --git a/src/test/testMapSchema.ts b/src/test/testMapSchema.ts index f793bf0245d..a00478ebfa1 100644 --- a/src/test/testMapSchema.ts +++ b/src/test/testMapSchema.ts @@ -1,10 +1,5 @@ import { expect } from 'chai'; -import { - GraphQLFieldConfig, - GraphQLObjectType, - GraphQLSchema, - graphqlSync, -} from 'graphql'; +import { GraphQLObjectType, GraphQLSchema, graphqlSync } from 'graphql'; import { makeExecutableSchema, mapSchema } from '../index'; import { MapperKind } from '../utils/map'; @@ -36,9 +31,7 @@ describe('mapSchema', () => { const newSchema = mapSchema(schema, { [MapperKind.QUERY]: type => { const queryConfig = toConfig(type); - (queryConfig.fields as { - version: GraphQLFieldConfig; - }).version.resolve = () => 1; + queryConfig.fields.version.resolve = () => 1; return new GraphQLObjectType(queryConfig); }, }); diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 8500cd8b8d9..e3586fdc3c4 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -20,9 +20,7 @@ describe('heal', () => { }); const originalTypeMap = schema.getTypeMap(); - const config = toConfig( - originalTypeMap['WillBeEmptyObject'] as GraphQLObjectType, - ); + const config = toConfig(originalTypeMap['WillBeEmptyObject']); originalTypeMap['WillBeEmptyObject'] = new GraphQLObjectType({ ...config, fields: {}, diff --git a/src/utils/clone.ts b/src/utils/clone.ts index c59df3d4232..b3058f82562 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -4,11 +4,11 @@ import { GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType, + GraphQLObjectTypeConfig, GraphQLNamedType, GraphQLScalarType, GraphQLSchema, GraphQLUnionType, - GraphQLInterfaceTypeConfig, isObjectType, isInterfaceType, isUnionType, @@ -34,25 +34,29 @@ export function cloneType(type: GraphQLNamedType): GraphQLNamedType { interfaces: typeof config.interfaces === 'function' ? config.interfaces - : (config.interfaces as ReadonlyArray).slice(), + : config.interfaces.slice(), }); } else if (isInterfaceType(type)) { - const config = toConfig((type as unknown) as GraphQLObjectType); + const config = toConfig(type); const newConfig = { ...config, interfaces: graphqlVersion() >= 15 - ? (config.interfaces as ReadonlyArray).slice() + ? typeof ((config as unknown) as GraphQLObjectTypeConfig) + .interfaces === 'function' + ? ((config as unknown) as GraphQLObjectTypeConfig) + .interfaces + : ((config as unknown) as { + interfaces: Array; + }).interfaces.slice() : undefined, }; - return new GraphQLInterfaceType( - (newConfig as unknown) as GraphQLInterfaceTypeConfig, - ); + return new GraphQLInterfaceType(newConfig); } else if (isUnionType(type)) { const config = toConfig(type); return new GraphQLUnionType({ ...config, - types: (config.types as ReadonlyArray).slice(), + types: config.types.slice(), }); } else if (isInputObjectType(type)) { return new GraphQLInputObjectType(toConfig(type)); diff --git a/src/utils/fields.ts b/src/utils/fields.ts index ce4bb112e1d..ca22b1b884b 100644 --- a/src/utils/fields.ts +++ b/src/utils/fields.ts @@ -14,7 +14,7 @@ export function appendFields( ): void { let type = typeMap[typeName]; if (type != null) { - const typeConfig = toConfig(type as GraphQLObjectType); + const typeConfig = toConfig(type); const originalFields = typeConfig.fields; const newFields = {}; Object.keys(originalFields).forEach(fieldName => { @@ -42,7 +42,7 @@ export function removeFields( testFn: (fieldName: string, field: GraphQLFieldConfig) => boolean, ): GraphQLFieldConfigMap { let type = typeMap[typeName]; - const typeConfig = toConfig(type as GraphQLObjectType); + const typeConfig = toConfig(type); const originalFields = typeConfig.fields; const newFields = {}; const removedFields = {}; diff --git a/src/utils/map.ts b/src/utils/map.ts index 7a8dd3c4a8b..eed392a2f1c 100644 --- a/src/utils/map.ts +++ b/src/utils/map.ts @@ -8,7 +8,6 @@ import { GraphQLInterfaceType, GraphQLList, GraphQLObjectType, - GraphQLObjectTypeConfig, GraphQLNamedType, GraphQLNonNull, GraphQLScalarType, @@ -311,25 +310,19 @@ export function rewireTypes( function rewireNamedType(type: T) { if (isObjectType(type)) { const objectConfig = toConfig(type); - objectConfig.fields = rewireFields( - objectConfig.fields as GraphQLFieldConfigMap, - ); - objectConfig.interfaces = rewireNamedTypes( - objectConfig.interfaces as Array, - ); + objectConfig.fields = rewireFields(objectConfig.fields); + objectConfig.interfaces = rewireNamedTypes(objectConfig.interfaces); return new GraphQLObjectType(objectConfig); } else if (isInterfaceType(type)) { const interfaceConfig = toConfig(type); - interfaceConfig.fields = rewireFields( - interfaceConfig.fields as GraphQLFieldConfigMap, - ); + interfaceConfig.fields = rewireFields(interfaceConfig.fields); if (graphqlVersion() >= 15) { - ((interfaceConfig as unknown) as GraphQLObjectTypeConfig< - any, - any - >).interfaces = rewireNamedTypes( - ((interfaceConfig as unknown) as GraphQLObjectTypeConfig) - .interfaces as Array, + ((interfaceConfig as unknown) as { + interfaces: Array; + }).interfaces = rewireNamedTypes( + ((interfaceConfig as unknown) as { + interfaces: Array; + }).interfaces, ); } return new GraphQLInterfaceType(interfaceConfig); From fcbb6743b5fb26f1455cd2778d419798bc55ee04 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 9 Mar 2020 22:48:43 -0400 Subject: [PATCH 213/250] fix(mapSchema): fix rewiring --- src/utils/map.ts | 67 ++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/src/utils/map.ts b/src/utils/map.ts index eed392a2f1c..7166f5d8585 100644 --- a/src/utils/map.ts +++ b/src/utils/map.ts @@ -24,6 +24,7 @@ import { isObjectType, isScalarType, isUnionType, + GraphQLObjectTypeConfig, } from 'graphql'; import { toConfig, isSpecifiedScalarType } from '../polyfills'; @@ -120,7 +121,7 @@ export type DirectiveMapper = ( export function mapSchema( schema: GraphQLSchema, - schemaMapper: SchemaMapper, + schemaMapper: SchemaMapper = {}, ): GraphQLSchema { const originalTypeMap = schema.getTypeMap(); const newTypeMap = {}; @@ -278,10 +279,7 @@ export function rewireTypes( const newDirectives = directives.map(directive => rewireDirective(directive)); Object.keys(newTypeMap).forEach(typeName => { - const namedType = newTypeMap[typeName]; - if (!typeName.startsWith('__')) { - newTypeMap[typeName] = rewireNamedType(namedType); - } + newTypeMap[typeName] = rewireNamedType(newTypeMap[typeName]); }); return pruneTypes(newTypeMap, newDirectives); @@ -309,35 +307,44 @@ export function rewireTypes( function rewireNamedType(type: T) { if (isObjectType(type)) { - const objectConfig = toConfig(type); - objectConfig.fields = rewireFields(objectConfig.fields); - objectConfig.interfaces = rewireNamedTypes(objectConfig.interfaces); - return new GraphQLObjectType(objectConfig); + const config = toConfig(type); + const newConfig = { + ...config, + fields: () => rewireFields(config.fields), + interfaces: () => rewireNamedTypes(config.interfaces), + }; + return new GraphQLObjectType(newConfig); } else if (isInterfaceType(type)) { - const interfaceConfig = toConfig(type); - interfaceConfig.fields = rewireFields(interfaceConfig.fields); + const config = toConfig(type); + const newConfig = { + ...config, + fields: () => rewireFields(config.fields), + }; if (graphqlVersion() >= 15) { - ((interfaceConfig as unknown) as { - interfaces: Array; - }).interfaces = rewireNamedTypes( - ((interfaceConfig as unknown) as { - interfaces: Array; - }).interfaces, - ); + ((newConfig as unknown) as GraphQLObjectTypeConfig< + any, + any + >).interfaces = () => + rewireNamedTypes( + ((config as unknown) as { interfaces: Array }) + .interfaces, + ); } - return new GraphQLInterfaceType(interfaceConfig); + return new GraphQLInterfaceType(newConfig); } else if (isUnionType(type)) { - const unionConfig = toConfig(type); - unionConfig.types = rewireNamedTypes( - unionConfig.types as Array, - ); - return new GraphQLUnionType(unionConfig); + const config = toConfig(type); + const newConfig = { + ...config, + types: () => rewireNamedTypes(config.types), + }; + return new GraphQLUnionType(newConfig); } else if (isInputObjectType(type)) { - const inputObjectConfig = toConfig(type); - inputObjectConfig.fields = rewireInputFields( - inputObjectConfig.fields as GraphQLInputFieldConfigMap, - ); - return new GraphQLInputObjectType(inputObjectConfig); + const config = toConfig(type); + const newConfig = { + ...config, + fields: () => rewireInputFields(config.fields), + }; + return new GraphQLInputObjectType(newConfig); } else if (isEnumType(type)) { const enumConfig = toConfig(type); return new GraphQLEnumType(enumConfig); @@ -404,7 +411,7 @@ export function rewireTypes( ? (new GraphQLNonNull(rewiredType) as T) : null; } else if (isNamedType(type)) { - return type; + return newTypeMap[type.name] as T; } return null; From ed5d610cc408f65b358164938fdd3d42519a228a Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 9 Mar 2020 22:49:37 -0400 Subject: [PATCH 214/250] refactor(cloneSchema): to use mapSchema without any mappers --- src/utils/clone.ts | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/utils/clone.ts b/src/utils/clone.ts index b3058f82562..69aceaa4756 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -19,8 +19,8 @@ import { import { isSpecifiedScalarType, toConfig } from '../polyfills'; -import { healTypes } from './heal'; import { graphqlVersion } from './graphqlVersion'; +import { mapSchema } from './map'; export function cloneDirective(directive: GraphQLDirective): GraphQLDirective { return new GraphQLDirective(toConfig(directive)); @@ -72,32 +72,5 @@ export function cloneType(type: GraphQLNamedType): GraphQLNamedType { } export function cloneSchema(schema: GraphQLSchema): GraphQLSchema { - const newDirectives = schema - .getDirectives() - .map(directive => cloneDirective(directive)); - - const originalTypeMap = schema.getTypeMap(); - const newTypeMap = {}; - - Object.keys(originalTypeMap).forEach(typeName => { - if (!typeName.startsWith('__')) { - newTypeMap[typeName] = cloneType(originalTypeMap[typeName]); - } - }); - - healTypes(newTypeMap, newDirectives); - - const query = schema.getQueryType(); - const mutation = schema.getMutationType(); - const subscription = schema.getSubscriptionType(); - - return new GraphQLSchema({ - ...toConfig(schema), - query: query != null ? newTypeMap[query.name] : undefined, - mutation: mutation != null ? newTypeMap[mutation.name] : undefined, - subscription: - subscription != null ? newTypeMap[subscription.name] : undefined, - types: Object.keys(newTypeMap).map(typeName => newTypeMap[typeName]), - directives: newDirectives, - }); + return mapSchema(schema); } From 041d2ab3ed38250c103c82506b7eca94e8bd953a Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Mar 2020 07:09:00 -0400 Subject: [PATCH 215/250] fix(MapperKind): export MapperKind --- src/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index a296287e458..16e5d0eb5b0 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -27,4 +27,4 @@ export { export { appendFields, removeFields } from './fields'; export { createNamedStub } from './stub'; export { graphqlVersion } from './graphqlVersion'; -export { mapSchema } from './map'; +export { mapSchema, MapperKind } from './map'; From 54c04e03432b65dd0cc5dc386e429b0d0b25eb40 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Mar 2020 07:11:55 -0400 Subject: [PATCH 216/250] fix(mapSchema): fix rewiring --- src/utils/map.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/utils/map.ts b/src/utils/map.ts index 7166f5d8585..0b4d5ede437 100644 --- a/src/utils/map.ts +++ b/src/utils/map.ts @@ -130,10 +130,13 @@ export function mapSchema( const specifiers = getTypeSpecifiers(originalTypeMap[typeName], schema); const typeMapper = getMapper(schemaMapper, specifiers); - newTypeMap[typeName] = - typeMapper != null - ? typeMapper(originalTypeMap[typeName], schema) - : originalTypeMap[typeName]; + if (typeMapper != null) { + const newType = typeMapper(originalTypeMap[typeName], schema); + newTypeMap[typeName] = + newType !== undefined ? newType : originalTypeMap[typeName]; + } else { + newTypeMap[typeName] = originalTypeMap[typeName]; + } } }); @@ -411,7 +414,8 @@ export function rewireTypes( ? (new GraphQLNonNull(rewiredType) as T) : null; } else if (isNamedType(type)) { - return newTypeMap[type.name] as T; + const originalType = originalTypeMap[type.name]; + return originalType != null ? (newTypeMap[originalType.name] as T) : null; } return null; @@ -470,6 +474,8 @@ function pruneTypes( } else { prunedTypeMap = true; } + } else { + newTypeMap[typeName] = type; } } From a59345b72cbba921c912219ceb5c6a9628b9cea5 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Mar 2020 07:12:14 -0400 Subject: [PATCH 217/250] refactor(filterSchema): to use mapSchema --- src/transforms/filterSchema.ts | 52 ++++++++++++++++------------------ 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/transforms/filterSchema.ts b/src/transforms/filterSchema.ts index 13d6ca6eaa5..593683e474f 100644 --- a/src/transforms/filterSchema.ts +++ b/src/transforms/filterSchema.ts @@ -8,9 +8,10 @@ import { GraphQLType, } from 'graphql'; -import { GraphQLSchemaWithTransforms, VisitSchemaKind } from '../Interfaces'; -import { visitSchema, cloneSchema } from '../utils'; +import { GraphQLSchemaWithTransforms } from '../Interfaces'; +import { mapSchema } from '../utils'; import { toConfig } from '../polyfills'; +import { MapperKind } from '../utils/map'; export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', @@ -30,31 +31,28 @@ export default function filterSchema({ typeFilter?: (typeName: string, type: GraphQLType) => boolean; fieldFilter?: (typeName: string, fieldName: string) => boolean; }): GraphQLSchemaWithTransforms { - const filteredSchema: GraphQLSchemaWithTransforms = visitSchema( - cloneSchema(schema), - { - [VisitSchemaKind.QUERY]: (type: GraphQLObjectType) => - filterRootFields(type, 'Query', rootFieldFilter), - [VisitSchemaKind.MUTATION]: (type: GraphQLObjectType) => - filterRootFields(type, 'Mutation', rootFieldFilter), - [VisitSchemaKind.SUBSCRIPTION]: (type: GraphQLObjectType) => - filterRootFields(type, 'Subscription', rootFieldFilter), - [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => - typeFilter(type.name, type) - ? filterObjectFields(type, fieldFilter) - : null, - [VisitSchemaKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) => - typeFilter(type.name, type) ? undefined : null, - [VisitSchemaKind.UNION_TYPE]: (type: GraphQLUnionType) => - typeFilter(type.name, type) ? undefined : null, - [VisitSchemaKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) => - typeFilter(type.name, type) ? undefined : null, - [VisitSchemaKind.ENUM_TYPE]: (type: GraphQLEnumType) => - typeFilter(type.name, type) ? undefined : null, - [VisitSchemaKind.SCALAR_TYPE]: (type: GraphQLScalarType) => - typeFilter(type.name, type) ? undefined : null, - }, - ); + const filteredSchema: GraphQLSchemaWithTransforms = mapSchema(schema, { + [MapperKind.QUERY]: (type: GraphQLObjectType) => + filterRootFields(type, 'Query', rootFieldFilter), + [MapperKind.MUTATION]: (type: GraphQLObjectType) => + filterRootFields(type, 'Mutation', rootFieldFilter), + [MapperKind.SUBSCRIPTION]: (type: GraphQLObjectType) => + filterRootFields(type, 'Subscription', rootFieldFilter), + [MapperKind.OBJECT_TYPE]: (type: GraphQLObjectType) => + typeFilter(type.name, type) + ? filterObjectFields(type, fieldFilter) + : null, + [MapperKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) => + typeFilter(type.name, type) ? undefined : null, + [MapperKind.UNION_TYPE]: (type: GraphQLUnionType) => + typeFilter(type.name, type) ? undefined : null, + [MapperKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) => + typeFilter(type.name, type) ? undefined : null, + [MapperKind.ENUM_TYPE]: (type: GraphQLEnumType) => + typeFilter(type.name, type) ? undefined : null, + [MapperKind.SCALAR_TYPE]: (type: GraphQLScalarType) => + typeFilter(type.name, type) ? undefined : null, + }); filteredSchema.transforms = schema.transforms; From 61047fd1be28332216b057fb8ddfe83f4c88635f Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Mar 2020 11:32:51 -0400 Subject: [PATCH 218/250] refactor(FilterTypes): to use mapSchema --- src/transforms/FilterTypes.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/transforms/FilterTypes.ts b/src/transforms/FilterTypes.ts index 3584c1fba63..852ddadb591 100644 --- a/src/transforms/FilterTypes.ts +++ b/src/transforms/FilterTypes.ts @@ -1,8 +1,7 @@ import { GraphQLSchema, GraphQLNamedType } from 'graphql'; -import { Transform } from '../transforms/transforms'; -import { visitSchema } from '../utils/visitSchema'; -import { VisitSchemaKind } from '../Interfaces'; +import { Transform } from '../transforms'; +import { MapperKind, mapSchema } from '../utils'; export default class FilterTypes implements Transform { private readonly filter: (type: GraphQLNamedType) => boolean; @@ -12,8 +11,8 @@ export default class FilterTypes implements Transform { } public transformSchema(schema: GraphQLSchema): GraphQLSchema { - return visitSchema(schema, { - [VisitSchemaKind.TYPE]: (type: GraphQLNamedType) => { + return mapSchema(schema, { + [MapperKind.TYPE]: (type: GraphQLNamedType) => { if (this.filter(type)) { return undefined; } From 62d8554b1a97cd7d3b015f384105d8df95c27c95 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Mar 2020 11:33:09 -0400 Subject: [PATCH 219/250] refactor(RenameRootTypes): to use mapSchema --- src/transforms/RenameRootTypes.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/transforms/RenameRootTypes.ts b/src/transforms/RenameRootTypes.ts index 519e70afec4..d31710dd031 100644 --- a/src/transforms/RenameRootTypes.ts +++ b/src/transforms/RenameRootTypes.ts @@ -6,9 +6,10 @@ import { GraphQLObjectType, } from 'graphql'; -import { Request, Result, VisitSchemaKind } from '../Interfaces'; +import { Request, Result } from '../Interfaces'; import { Transform } from '../transforms/transforms'; -import { visitSchema, cloneType } from '../utils'; +import { mapSchema, MapperKind } from '../utils'; +import { toConfig } from '../polyfills'; export default class RenameRootTypes implements Transform { private readonly renamer: (name: string) => string | undefined; @@ -22,16 +23,17 @@ export default class RenameRootTypes implements Transform { } public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { - return visitSchema(originalSchema, { - [VisitSchemaKind.ROOT_OBJECT]: type => { + return mapSchema(originalSchema, { + [MapperKind.ROOT_OBJECT]: type => { const oldName = type.name; const newName = this.renamer(oldName); if (newName && newName !== oldName) { this.map[oldName] = type.name; this.reverseMap[newName] = oldName; - const newType = cloneType(type) as GraphQLObjectType; - newType.name = newName; - return newType; + return new GraphQLObjectType({ + ...toConfig(type), + name: newName, + }); } }, }); From d8eec056374f117a0972be8cd4d31fd49342d88a Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Mar 2020 12:10:02 -0400 Subject: [PATCH 220/250] refactor(TransformObjectFields): to use mapSchema --- src/transforms/TransformObjectFields.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/transforms/TransformObjectFields.ts b/src/transforms/TransformObjectFields.ts index 73bc7ce40c9..05ebb3cfb47 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/transforms/TransformObjectFields.ts @@ -16,8 +16,8 @@ import { } from 'graphql'; import isEmptyObject from '../utils/isEmptyObject'; -import { Request, VisitSchemaKind } from '../Interfaces'; -import { visitSchema } from '../utils'; +import { Request } from '../Interfaces'; +import { mapSchema, MapperKind } from '../utils'; import { toConfig } from '../polyfills'; import { Transform } from './transforms'; @@ -59,8 +59,8 @@ export default class TransformObjectFields implements Transform { } public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { - this.transformedSchema = visitSchema(originalSchema, { - [VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => + this.transformedSchema = mapSchema(originalSchema, { + [MapperKind.OBJECT_TYPE]: (type: GraphQLObjectType) => this.transformFields(type, this.objectFieldTransformer), }); From 121b3e12c48ef09818cadad20b93c01fa4d06bf1 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Mar 2020 12:13:01 -0400 Subject: [PATCH 221/250] chore(RenameTypes): add comment for visitSchema --- src/transforms/RenameTypes.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index f321c7997cb..610861ce092 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -38,6 +38,9 @@ export default class RenameTypes implements Transform { } public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { + // Modification of specified scalar types is not recommended, but is supported. + // mapSchema does not support modification of specified scalar types, + // so must use visitSchema instead. return visitSchema(originalSchema, { [VisitSchemaKind.TYPE]: (type: GraphQLNamedType) => { if (isSpecifiedScalarType(type) && !this.renameBuiltins) { From a1a2129ca229161acaf8373fe2736f404582c011 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Mar 2020 22:10:31 -0400 Subject: [PATCH 222/250] chore(tests): add failing test --- package.json | 1 + src/test/testGatsbyTransforms.ts | 133 +++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 src/test/testGatsbyTransforms.ts diff --git a/package.json b/package.json index dd127999ed6..cad5e4ebc97 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@types/uuid": "^7.0.0", "@typescript-eslint/eslint-plugin": "^2.21.0", "@typescript-eslint/parser": "^2.21.0", + "apollo-link-http": "^1.5.16", "babel-eslint": "^10.1.0", "body-parser": "^1.19.0", "chai": "^4.2.0", diff --git a/src/test/testGatsbyTransforms.ts b/src/test/testGatsbyTransforms.ts new file mode 100644 index 00000000000..833d1ba5402 --- /dev/null +++ b/src/test/testGatsbyTransforms.ts @@ -0,0 +1,133 @@ +import { expect } from 'chai'; +import { + GraphQLObjectType, + GraphQLSchema, + GraphQLFieldResolver, + GraphQLNonNull, + graphql, +} from 'graphql'; +import { createHttpLink } from 'apollo-link-http'; +import fetch from 'node-fetch'; + +import { VisitSchemaKind } from '../Interfaces'; +import { transformSchema, RenameTypes } from '../transforms'; +import { introspectSchema } from '../stitching'; +import { cloneType, healSchema, visitSchema } from '../utils'; + +// see https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/transforms.js +// and https://github.com/gatsbyjs/gatsby/issues/22128 + +class NamespaceUnderFieldTransform { + private readonly typeName: string; + private readonly fieldName: string; + private readonly resolver: GraphQLFieldResolver; + + constructor({ + typeName, + fieldName, + resolver, + }: { + typeName: string; + fieldName: string; + resolver: GraphQLFieldResolver; + }) { + this.typeName = typeName; + this.fieldName = fieldName; + this.resolver = resolver; + } + + transformSchema(schema: GraphQLSchema) { + const query = schema.getQueryType(); + + const nestedType = cloneType(query); + nestedType.name = this.typeName; + + const typeMap = schema.getTypeMap(); + typeMap[this.typeName] = nestedType; + + const newQuery = new GraphQLObjectType({ + name: query.name, + fields: { + [this.fieldName]: { + type: new GraphQLNonNull(nestedType), + resolve: (parent, args, context, info) => { + if (this.resolver != null) { + return this.resolver(parent, args, context, info); + } + + return {}; + }, + }, + }, + }); + typeMap[query.name] = newQuery; + + return healSchema(schema); + } +} + +class StripNonQueryTransform { + transformSchema(schema: GraphQLSchema) { + return visitSchema(schema, { + [VisitSchemaKind.MUTATION]() { + return null; + }, + [VisitSchemaKind.SUBSCRIPTION]() { + return null; + }, + }); + } +} + +describe('Gatsby transforms', () => { + it('work', async () => { + const link = createHttpLink({ + uri: 'https://countries.trevorblades.com/', + fetch: (fetch as unknown) as WindowOrWorkerGlobalScope['fetch'], + }); + const introspectionSchema = await introspectSchema(link); + const typeName = 'CountriesQuery'; + const fieldName = 'countries'; + const resolver = () => ({}); + + const schema = transformSchema( + { + schema: introspectionSchema, + link, + }, + [ + new StripNonQueryTransform(), + new RenameTypes(name => `${typeName}_${name}`), + new NamespaceUnderFieldTransform({ + typeName, + fieldName, + resolver, + }), + ], + ); + + expect(schema).to.be.instanceOf(GraphQLSchema); + + const result = await graphql( + schema, + ` + { + countries { + language(code: "en") { + name + } + } + } + `, + ); + expect(result).to.deep.equal({ + data: { + countries: { + language: { + name: 'English', + }, + }, + }, + }); + }); +}); From 17229f5767a333f3fe083af20a31fa88769c2872 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 10 Mar 2020 22:12:34 -0400 Subject: [PATCH 223/250] fix(mapSchema): rewire directives last As they do not take a thunk. --- src/utils/map.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/map.ts b/src/utils/map.ts index 0b4d5ede437..38e2ea22a79 100644 --- a/src/utils/map.ts +++ b/src/utils/map.ts @@ -279,12 +279,12 @@ export function rewireTypes( newTypeMap[newName] = namedType; }); - const newDirectives = directives.map(directive => rewireDirective(directive)); - Object.keys(newTypeMap).forEach(typeName => { newTypeMap[typeName] = rewireNamedType(newTypeMap[typeName]); }); + const newDirectives = directives.map(directive => rewireDirective(directive)); + return pruneTypes(newTypeMap, newDirectives); function rewireDirective(directive: GraphQLDirective): GraphQLDirective { From fa2a1d41abc4bf669c033a4c4c9a14770c5f7b1f Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 11 Mar 2020 22:38:34 -0400 Subject: [PATCH 224/250] fix(SchemaDirectiveVisitor): visit directives added via extend --- src/test/testDirectives.ts | 130 ++++++++++++++++++++++++---- src/utils/SchemaDirectiveVisitor.ts | 17 ++-- 2 files changed, 125 insertions(+), 22 deletions(-) diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index e03ec283f12..dfebeb62780 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -24,6 +24,7 @@ import { isNonNullType, isScalarType, isListType, + TypeSystemExtensionNode, } from 'graphql'; import { assert } from 'chai'; import formatDate from 'dateformat'; @@ -36,60 +37,87 @@ import { visitSchema } from '../utils/visitSchema'; const typeDefs = ` directive @schemaDirective(role: String) on SCHEMA +directive @schemaExtensionDirective(role: String) on SCHEMA directive @queryTypeDirective on OBJECT +directive @queryTypeExtensionDirective on OBJECT directive @queryFieldDirective on FIELD_DEFINITION directive @enumTypeDirective on ENUM +directive @enumTypeExtensionDirective on ENUM directive @enumValueDirective on ENUM_VALUE directive @dateDirective(tz: String) on SCALAR +directive @dateExtensionDirective(tz: String) on SCALAR directive @interfaceDirective on INTERFACE +directive @interfaceExtensionDirective on INTERFACE directive @interfaceFieldDirective on FIELD_DEFINITION directive @inputTypeDirective on INPUT_OBJECT +directive @inputTypeExtensionDirective on INPUT_OBJECT directive @inputFieldDirective on INPUT_FIELD_DEFINITION directive @mutationTypeDirective on OBJECT +directive @mutationTypeExtensionDirective on OBJECT directive @mutationArgumentDirective on ARGUMENT_DEFINITION directive @mutationMethodDirective on FIELD_DEFINITION directive @objectTypeDirective on OBJECT +directive @objectTypeExtensionDirective on OBJECT directive @objectFieldDirective on FIELD_DEFINITION directive @unionDirective on UNION +directive @unionExtensionDirective on UNION schema @schemaDirective(role: "admin") { query: Query mutation: Mutation } +extend schema @schemaExtensionDirective(role: "admin") + type Query @queryTypeDirective { people: [Person] @queryFieldDirective } +extend type Query @queryTypeExtensionDirective + enum Gender @enumTypeDirective { NONBINARY @enumValueDirective FEMALE MALE } +extend enum Gender @enumTypeExtensionDirective + scalar Date @dateDirective(tz: "utc") +extend scalar Date @dateExtensionDirective(tz: "utc") + interface Named @interfaceDirective { name: String! @interfaceFieldDirective } +extend interface Named @interfaceExtensionDirective + input PersonInput @inputTypeDirective { name: String! @inputFieldDirective gender: Gender } +extend input PersonInput @inputTypeExtensionDirective + type Mutation @mutationTypeDirective { addPerson( input: PersonInput @mutationArgumentDirective ): Person @mutationMethodDirective } +extend type Mutation @mutationTypeExtensionDirective + type Person implements Named @objectTypeDirective { id: ID! @objectFieldDirective name: String! } +extend type Person @objectTypeExtensionDirective + union WhateverUnion @unionDirective = Person | Query | Mutation + +extend union WhateverUnion @unionExtensionDirective `; describe('@directives', () => { @@ -107,7 +135,7 @@ describe('@directives', () => { function checkDirectives( type: VisitableSchemaType, - typeDirectiveNames: [string], + typeDirectiveNames: Array, fieldDirectiveMap: { [key: string]: Array } = {}, ) { assert.deepEqual(getDirectiveNames(type), typeDirectiveNames); @@ -121,17 +149,36 @@ describe('@directives', () => { } function getDirectiveNames(type: VisitableSchemaType): Array { - return type.astNode.directives.map(d => d.name.value); + let directives = type.astNode.directives.map(d => d.name.value); + const extensionASTNodes = (type as { + extensionASTNodes?: Array; + }).extensionASTNodes; + if (extensionASTNodes != null) { + extensionASTNodes.forEach(extensionASTNode => { + directives = directives.concat( + extensionASTNode.directives.map(d => d.name.value), + ); + }); + } + return directives; } - assert.deepEqual(getDirectiveNames(schema), ['schemaDirective']); + assert.deepEqual(getDirectiveNames(schema), [ + 'schemaDirective', + 'schemaExtensionDirective', + ]); - checkDirectives(schema.getQueryType(), ['queryTypeDirective'], { - people: ['queryFieldDirective'], - }); + checkDirectives( + schema.getQueryType(), + ['queryTypeDirective', 'queryTypeExtensionDirective'], + { + people: ['queryFieldDirective'], + }, + ); assert.deepEqual(getDirectiveNames(schema.getType('Gender')), [ 'enumTypeDirective', + 'enumTypeExtensionDirective', ]); const nonBinary = (schema.getType( @@ -141,11 +188,12 @@ describe('@directives', () => { checkDirectives(schema.getType('Date') as GraphQLObjectType, [ 'dateDirective', + 'dateExtensionDirective', ]); checkDirectives( schema.getType('Named') as GraphQLObjectType, - ['interfaceDirective'], + ['interfaceDirective', 'interfaceExtensionDirective'], { name: ['interfaceFieldDirective'], }, @@ -153,27 +201,38 @@ describe('@directives', () => { checkDirectives( schema.getType('PersonInput') as GraphQLObjectType, - ['inputTypeDirective'], + ['inputTypeDirective', 'inputTypeExtensionDirective'], { name: ['inputFieldDirective'], gender: [], }, ); - checkDirectives(schema.getMutationType(), ['mutationTypeDirective'], { - addPerson: ['mutationMethodDirective'], - }); + checkDirectives( + schema.getMutationType(), + ['mutationTypeDirective', 'mutationTypeExtensionDirective'], + { + addPerson: ['mutationMethodDirective'], + }, + ); assert.deepEqual( getDirectiveNames(schema.getMutationType().getFields().addPerson.args[0]), ['mutationArgumentDirective'], ); - checkDirectives(schema.getType('Person'), ['objectTypeDirective'], { - id: ['objectFieldDirective'], - name: [], - }); + checkDirectives( + schema.getType('Person'), + ['objectTypeDirective', 'objectTypeExtensionDirective'], + { + id: ['objectFieldDirective'], + name: [], + }, + ); - checkDirectives(schema.getType('WhateverUnion'), ['unionDirective']); + checkDirectives(schema.getType('WhateverUnion'), [ + 'unionDirective', + 'unionExtensionDirective', + ]); }); it('works with enum and its resolvers', () => { @@ -218,6 +277,13 @@ describe('@directives', () => { visited.add(object); } }, + queryTypeExtensionDirective: class extends SchemaDirectiveVisitor { + public static description = 'A @directive for query object types'; + public visitObject(object: GraphQLObjectType) { + assert.strictEqual(object, schema.getQueryType()); + visited.add(object); + } + }, }); assert.strictEqual(visited.size, 1); @@ -232,9 +298,15 @@ describe('@directives', () => { visited.push(s); } }, + schemaExtensionDirective: class extends SchemaDirectiveVisitor { + public visitSchema(s: GraphQLSchema) { + visited.push(s); + } + }, }); - assert.strictEqual(visited.length, 1); + assert.strictEqual(visited.length, 2); assert.strictEqual(visited[0], schema); + assert.strictEqual(visited[1], schema); }); it('can visit fields within object types', () => { @@ -254,6 +326,14 @@ describe('@directives', () => { } }, + mutationTypeExtensionDirective: class extends SchemaDirectiveVisitor { + public visitObject(object: GraphQLObjectType) { + mutationObjectType = object; + assert.strictEqual(this.visitedType, object); + assert.strictEqual(object.name, 'Mutation'); + } + }, + mutationMethodDirective: class extends SchemaDirectiveVisitor { public visitFieldDefinition( field: GraphQLField, @@ -293,6 +373,14 @@ describe('@directives', () => { } }, + enumTypeExtensionDirective: class extends SchemaDirectiveVisitor { + public visitEnum(enumType: GraphQLEnumType) { + assert.strictEqual(this.visitedType, enumType); + assert.strictEqual(enumType.name, 'Gender'); + enumObjectType = enumType; + } + }, + enumValueDirective: class extends SchemaDirectiveVisitor { public visitEnumValue( value: GraphQLEnumValue, @@ -315,6 +403,14 @@ describe('@directives', () => { } }, + inputTypeExtensionDirective: class extends SchemaDirectiveVisitor { + public visitInputObject(object: GraphQLInputObjectType) { + inputObjectType = object; + assert.strictEqual(this.visitedType, object); + assert.strictEqual(object.name, 'PersonInput'); + } + }, + inputFieldDirective: class extends SchemaDirectiveVisitor { public visitInputFieldDefinition( field: GraphQLInputField, diff --git a/src/utils/SchemaDirectiveVisitor.ts b/src/utils/SchemaDirectiveVisitor.ts index 06f54b715f0..850c380e448 100644 --- a/src/utils/SchemaDirectiveVisitor.ts +++ b/src/utils/SchemaDirectiveVisitor.ts @@ -2,6 +2,7 @@ import { GraphQLDirective, GraphQLSchema, DirectiveLocationEnum, + TypeSystemExtensionNode, } from 'graphql'; import { getArgumentValues } from 'graphql/execution/values'; @@ -138,13 +139,19 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { type: VisitableSchemaType, methodName: string, ): Array { - const visitors: Array = []; - const directiveNodes = - type.astNode != null ? type.astNode.directives : null; - if (!directiveNodes) { - return visitors; + let directiveNodes = type.astNode != null ? type.astNode.directives : []; + + const extensionASTNodes: ReadonlyArray = (type as { + extensionASTNodes?: Array; + }).extensionASTNodes; + + if (extensionASTNodes != null) { + extensionASTNodes.forEach(extensionASTNode => { + directiveNodes = directiveNodes.concat(extensionASTNode.directives); + }); } + const visitors: Array = []; directiveNodes.forEach(directiveNode => { const directiveName = directiveNode.name.value; if (!hasOwn.call(directiveVisitors, directiveName)) { From 78d454b7f5ba5c5d4a8962e3c76a0187db5fd60d Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 11 Mar 2020 22:50:18 -0400 Subject: [PATCH 225/250] refactor(types): remove a few unknown types --- src/polyfills/toConfig.ts | 18 ++++++++---------- src/utils/heal.ts | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/polyfills/toConfig.ts b/src/polyfills/toConfig.ts index ea7c00e115b..535f03b5e8d 100644 --- a/src/polyfills/toConfig.ts +++ b/src/polyfills/toConfig.ts @@ -59,15 +59,15 @@ export function schemaToConfig(schema: GraphQLSchema): GraphQLSchemaConfig { extensionASTNodes: schema.extensionASTNodes != null ? schema.extensionASTNodes : [], assumeValid: - ((schema as unknown) as { __validationErrors: boolean }) - .__validationErrors !== undefined, + (schema as { __validationErrors?: boolean }).__validationErrors !== + undefined, }; if (graphqlVersion() >= 15) { - ((schemaConfig as unknown) as { - description: string; - }).description = ((schema as unknown) as { - description: string; + (schemaConfig as { + description?: string; + }).description = (schema as { + description?: string; }).description; } @@ -117,9 +117,7 @@ export function toConfig(schemaOrTypeOrDirective: any) { return typeToConfig(schemaOrTypeOrDirective); } - throw new Error( - `Unknown object ${(schemaOrTypeOrDirective as unknown) as string}`, - ); + throw new Error(`Unknown object ${schemaOrTypeOrDirective as string}`); } export function typeToConfig( @@ -154,7 +152,7 @@ export function typeToConfig(type: any) { return inputObjectTypeToConfig(type); } - throw new Error(`Unknown type ${(type as unknown) as string}`); + throw new Error(`Unknown type ${type as string}`); } export function objectTypeToConfig( diff --git a/src/utils/heal.ts b/src/utils/heal.ts index c1954d3cccf..c09aa57a75b 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -193,7 +193,7 @@ export function healTypes( return; } - throw new Error(`Unexpected schema type: ${(type as unknown) as string}`); + throw new Error(`Unexpected schema type: ${type as string}`); } function healFields(type: GraphQLObjectType | GraphQLInterfaceType) { From f0cee574fef4d6883545e14bae0e6f3e37d0d745 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 11 Mar 2020 23:05:08 -0400 Subject: [PATCH 226/250] refactor(mapSchema) Streamline mapper selection for types and directives. Lays groundwork for additional SchemaMapper object formats, i.e. the resolver map format with specification of individual types/directives by name. --- src/utils/map.ts | 62 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/src/utils/map.ts b/src/utils/map.ts index 38e2ea22a79..8c7d80189f8 100644 --- a/src/utils/map.ts +++ b/src/utils/map.ts @@ -14,6 +14,7 @@ import { GraphQLSchema, GraphQLType, GraphQLUnionType, + isDirective, isInterfaceType, isEnumType, isInputType, @@ -127,8 +128,11 @@ export function mapSchema( const newTypeMap = {}; Object.keys(originalTypeMap).forEach(typeName => { if (!typeName.startsWith('__')) { - const specifiers = getTypeSpecifiers(originalTypeMap[typeName], schema); - const typeMapper = getMapper(schemaMapper, specifiers); + const typeMapper = getMapper( + schema, + schemaMapper, + originalTypeMap[typeName], + ); if (typeMapper != null) { const newType = typeMapper(originalTypeMap[typeName], schema); @@ -164,19 +168,18 @@ export function mapSchema( : undefined; const originalDirectives = schema.getDirectives(); - let newDirectives: Array; - const directiveMapper = schemaMapper[MapperKind.DIRECTIVE]; - if (directiveMapper != null) { - newDirectives = []; - originalDirectives.forEach(directive => { + const newDirectives: Array = []; + originalDirectives.forEach(directive => { + const directiveMapper = getMapper(schema, schemaMapper, directive); + if (directiveMapper != null) { const newDirective = directiveMapper(directive, schema); if (newDirective != null) { newDirectives.push(newDirective); } - }); - } else { - newDirectives = originalDirectives.slice(); - } + } else { + newDirectives.push(directive); + } + }); const { typeMap, directives } = rewireTypes(newTypeMap, newDirectives); @@ -198,7 +201,7 @@ export function mapSchema( } function getTypeSpecifiers( - type: GraphQLType | GraphQLDirective, + type: GraphQLType, schema: GraphQLSchema, ): Array { const specifiers = [MapperKind.TYPE]; @@ -238,17 +241,34 @@ function getTypeSpecifiers( } function getMapper( + schema: GraphQLSchema, schemaMapper: SchemaMapper, - specifiers: Array, -): NamedTypeMapper | null { - let typeMapper: NamedTypeMapper | undefined; - const stack = [...specifiers]; - while (!typeMapper && stack.length > 0) { - const next = stack.pop(); - typeMapper = schemaMapper[next] as NamedTypeMapper; - } + typeOrDirective: GraphQLNamedType, +): NamedTypeMapper | null; +function getMapper( + schema: GraphQLSchema, + schemaMapper: SchemaMapper, + typeOrDirective: GraphQLDirective, +): DirectiveMapper | null; +function getMapper( + schema: GraphQLSchema, + schemaMapper: SchemaMapper, + typeOrDirective: any, +): any { + if (isNamedType(typeOrDirective)) { + const specifiers = getTypeSpecifiers(typeOrDirective, schema); + let typeMapper: NamedTypeMapper | undefined; + const stack = [...specifiers]; + while (!typeMapper && stack.length > 0) { + const next = stack.pop(); + typeMapper = schemaMapper[next] as NamedTypeMapper; + } - return typeMapper != null ? typeMapper : null; + return typeMapper != null ? typeMapper : null; + } else if (isDirective(typeOrDirective)) { + const directiveMapper = schemaMapper[MapperKind.DIRECTIVE]; + return directiveMapper != null ? directiveMapper : null; + } } export function rewireTypes( From 1a6da664b26c82d29da8e69bb30d460e14ba2ed0 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 11 Mar 2020 23:13:37 -0400 Subject: [PATCH 227/250] refactor(tests): to use mocks rather than remote schema --- package.json | 1 - src/test/testGatsbyTransforms.ts | 98 ++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index cad5e4ebc97..dd127999ed6 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "@types/uuid": "^7.0.0", "@typescript-eslint/eslint-plugin": "^2.21.0", "@typescript-eslint/parser": "^2.21.0", - "apollo-link-http": "^1.5.16", "babel-eslint": "^10.1.0", "body-parser": "^1.19.0", "chai": "^4.2.0", diff --git a/src/test/testGatsbyTransforms.ts b/src/test/testGatsbyTransforms.ts index 833d1ba5402..5d769342da1 100644 --- a/src/test/testGatsbyTransforms.ts +++ b/src/test/testGatsbyTransforms.ts @@ -6,13 +6,12 @@ import { GraphQLNonNull, graphql, } from 'graphql'; -import { createHttpLink } from 'apollo-link-http'; -import fetch from 'node-fetch'; import { VisitSchemaKind } from '../Interfaces'; import { transformSchema, RenameTypes } from '../transforms'; -import { introspectSchema } from '../stitching'; import { cloneType, healSchema, visitSchema } from '../utils'; +import { makeExecutableSchema } from '../makeExecutableSchema'; +import { addMocksToSchema } from '../mock'; // see https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/transforms.js // and https://github.com/gatsbyjs/gatsby/issues/22128 @@ -81,35 +80,76 @@ class StripNonQueryTransform { describe('Gatsby transforms', () => { it('work', async () => { - const link = createHttpLink({ - uri: 'https://countries.trevorblades.com/', - fetch: (fetch as unknown) as WindowOrWorkerGlobalScope['fetch'], + const schema = makeExecutableSchema({ + typeDefs: ` + directive @cacheControl(maxAge: Int, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT | INTERFACE + + enum CacheControlScope { + PUBLIC + PRIVATE + } + + type Continent { + code: String + name: String + countries: [Country] + } + + type Country { + code: String + name: String + native: String + phone: String + continent: Continent + currency: String + languages: [Language] + emoji: String + emojiU: String + states: [State] + } + + type Language { + code: String + name: String + native: String + rtl: Int + } + + type Query { + continents: [Continent] + continent(code: String): Continent + countries: [Country] + country(code: String): Country + languages: [Language] + language(code: String): Language + } + + type State { + code: String + name: String + country: Country + } + + scalar Upload + `, }); - const introspectionSchema = await introspectSchema(link); - const typeName = 'CountriesQuery'; - const fieldName = 'countries'; - const resolver = () => ({}); - - const schema = transformSchema( - { - schema: introspectionSchema, - link, - }, - [ - new StripNonQueryTransform(), - new RenameTypes(name => `${typeName}_${name}`), - new NamespaceUnderFieldTransform({ - typeName, - fieldName, - resolver, - }), - ], - ); - expect(schema).to.be.instanceOf(GraphQLSchema); + addMocksToSchema({ schema }); + + const transformedSchema = transformSchema(schema, [ + new StripNonQueryTransform(), + new RenameTypes(name => `CountriesQuery_${name}`), + new NamespaceUnderFieldTransform({ + typeName: 'CountriesQuery', + fieldName: 'countries', + resolver: () => ({}), + }), + ]); + + expect(transformedSchema).to.be.instanceOf(GraphQLSchema); const result = await graphql( - schema, + transformedSchema, ` { countries { @@ -124,7 +164,7 @@ describe('Gatsby transforms', () => { data: { countries: { language: { - name: 'English', + name: 'Hello World', }, }, }, From d0c5b780294ce902dbc780f384d5e474a731dae6 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 11 Mar 2020 23:50:09 -0400 Subject: [PATCH 228/250] fix(tests): v12 and v13 extensionASTNode support v12 does not support extensionASTNodes v13 supports extensionASTNodes for query, mutation, and interface types --- src/test/testDirectives.ts | 125 ++++++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 35 deletions(-) diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index dfebeb62780..2af534c0e86 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -31,9 +31,12 @@ import formatDate from 'dateformat'; import { makeExecutableSchema } from '../makeExecutableSchema'; import { VisitableSchemaType } from '../Interfaces'; -import { SchemaDirectiveVisitor } from '../utils/SchemaDirectiveVisitor'; -import { SchemaVisitor } from '../utils/SchemaVisitor'; -import { visitSchema } from '../utils/visitSchema'; +import { + SchemaDirectiveVisitor, + SchemaVisitor, + visitSchema, + graphqlVersion, +} from '../utils'; const typeDefs = ` directive @schemaDirective(role: String) on SCHEMA @@ -67,13 +70,19 @@ schema @schemaDirective(role: "admin") { mutation: Mutation } -extend schema @schemaExtensionDirective(role: "admin") +${ + graphqlVersion() >= 14 + ? 'extend schema @schemaExtensionDirective(role: "admin")' + : '' +} type Query @queryTypeDirective { people: [Person] @queryFieldDirective } -extend type Query @queryTypeExtensionDirective +${ + graphqlVersion() >= 13 ? 'extend type Query @queryTypeExtensionDirective' : '' +} enum Gender @enumTypeDirective { NONBINARY @enumValueDirective @@ -81,24 +90,38 @@ enum Gender @enumTypeDirective { MALE } -extend enum Gender @enumTypeExtensionDirective +${ + graphqlVersion() >= 14 ? 'extend enum Gender @enumTypeExtensionDirective' : '' +} scalar Date @dateDirective(tz: "utc") -extend scalar Date @dateExtensionDirective(tz: "utc") +${ + graphqlVersion() >= 14 + ? 'extend scalar Date @dateExtensionDirective(tz: "utc")' + : '' +} interface Named @interfaceDirective { name: String! @interfaceFieldDirective } -extend interface Named @interfaceExtensionDirective +${ + graphqlVersion() >= 13 + ? 'extend interface Named @interfaceExtensionDirective' + : '' +} input PersonInput @inputTypeDirective { name: String! @inputFieldDirective gender: Gender } -extend input PersonInput @inputTypeExtensionDirective +${ + graphqlVersion() >= 14 + ? 'extend input PersonInput @inputTypeExtensionDirective' + : '' +} type Mutation @mutationTypeDirective { addPerson( @@ -106,18 +129,30 @@ type Mutation @mutationTypeDirective { ): Person @mutationMethodDirective } -extend type Mutation @mutationTypeExtensionDirective +${ + graphqlVersion() >= 13 + ? 'extend type Mutation @mutationTypeExtensionDirective' + : '' +} type Person implements Named @objectTypeDirective { id: ID! @objectFieldDirective name: String! } -extend type Person @objectTypeExtensionDirective +${ + graphqlVersion() >= 14 + ? 'extend type Person @objectTypeExtensionDirective' + : '' +} union WhateverUnion @unionDirective = Person | Query | Mutation -extend union WhateverUnion @unionExtensionDirective +${ + graphqlVersion() >= 14 + ? 'extend union WhateverUnion @unionExtensionDirective' + : '' +} `; describe('@directives', () => { @@ -163,37 +198,47 @@ describe('@directives', () => { return directives; } - assert.deepEqual(getDirectiveNames(schema), [ - 'schemaDirective', - 'schemaExtensionDirective', - ]); + assert.deepEqual( + getDirectiveNames(schema), + graphqlVersion() >= 14 + ? ['schemaDirective', 'schemaExtensionDirective'] + : ['schemaDirective'], + ); checkDirectives( schema.getQueryType(), - ['queryTypeDirective', 'queryTypeExtensionDirective'], + graphqlVersion() >= 13 + ? ['queryTypeDirective', 'queryTypeExtensionDirective'] + : ['queryTypeDirective'], { people: ['queryFieldDirective'], }, ); - assert.deepEqual(getDirectiveNames(schema.getType('Gender')), [ - 'enumTypeDirective', - 'enumTypeExtensionDirective', - ]); + assert.deepEqual( + getDirectiveNames(schema.getType('Gender')), + graphqlVersion() >= 14 + ? ['enumTypeDirective', 'enumTypeExtensionDirective'] + : ['enumTypeDirective'], + ); const nonBinary = (schema.getType( 'Gender', ) as GraphQLEnumType).getValues()[0]; assert.deepEqual(getDirectiveNames(nonBinary), ['enumValueDirective']); - checkDirectives(schema.getType('Date') as GraphQLObjectType, [ - 'dateDirective', - 'dateExtensionDirective', - ]); + checkDirectives( + schema.getType('Date') as GraphQLObjectType, + graphqlVersion() >= 14 + ? ['dateDirective', 'dateExtensionDirective'] + : ['dateDirective'], + ); checkDirectives( schema.getType('Named') as GraphQLObjectType, - ['interfaceDirective', 'interfaceExtensionDirective'], + graphqlVersion() >= 13 + ? ['interfaceDirective', 'interfaceExtensionDirective'] + : ['interfaceDirective'], { name: ['interfaceFieldDirective'], }, @@ -201,7 +246,9 @@ describe('@directives', () => { checkDirectives( schema.getType('PersonInput') as GraphQLObjectType, - ['inputTypeDirective', 'inputTypeExtensionDirective'], + graphqlVersion() >= 14 + ? ['inputTypeDirective', 'inputTypeExtensionDirective'] + : ['inputTypeDirective'], { name: ['inputFieldDirective'], gender: [], @@ -210,7 +257,9 @@ describe('@directives', () => { checkDirectives( schema.getMutationType(), - ['mutationTypeDirective', 'mutationTypeExtensionDirective'], + graphqlVersion() >= 13 + ? ['mutationTypeDirective', 'mutationTypeExtensionDirective'] + : ['mutationTypeDirective'], { addPerson: ['mutationMethodDirective'], }, @@ -222,17 +271,21 @@ describe('@directives', () => { checkDirectives( schema.getType('Person'), - ['objectTypeDirective', 'objectTypeExtensionDirective'], + graphqlVersion() >= 14 + ? ['objectTypeDirective', 'objectTypeExtensionDirective'] + : ['objectTypeDirective'], { id: ['objectFieldDirective'], name: [], }, ); - checkDirectives(schema.getType('WhateverUnion'), [ - 'unionDirective', - 'unionExtensionDirective', - ]); + checkDirectives( + schema.getType('WhateverUnion'), + graphqlVersion() >= 14 + ? ['unionDirective', 'unionExtensionDirective'] + : ['unionDirective'], + ); }); it('works with enum and its resolvers', () => { @@ -304,9 +357,11 @@ describe('@directives', () => { } }, }); - assert.strictEqual(visited.length, 2); + assert.strictEqual(visited.length, graphqlVersion() >= 14 ? 2 : 1); assert.strictEqual(visited[0], schema); - assert.strictEqual(visited[1], schema); + if (graphqlVersion() >= 14) { + assert.strictEqual(visited[1], schema); + } }); it('can visit fields within object types', () => { From 0317114a08db1807f8026d498e9a62c11db84581 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 12 Mar 2020 00:24:23 -0400 Subject: [PATCH 229/250] refactor(RenameTypes): to use mapSchema MapSchema actually can be used to rename scalars -- even to rename specified types -- because rewiring does not modify the original types (as opposed to healing). --- src/transforms/RenameTypes.ts | 56 +++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/transforms/RenameTypes.ts b/src/transforms/RenameTypes.ts index 610861ce092..ce6c92a63f3 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/transforms/RenameTypes.ts @@ -1,16 +1,27 @@ import { - visit, + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLInterfaceType, + GraphQLNamedType, + GraphQLObjectType, GraphQLSchema, - NamedTypeNode, + GraphQLScalarType, + GraphQLUnionType, Kind, - GraphQLNamedType, + NamedTypeNode, + isEnumType, + isInputObjectType, + isInterfaceType, + isObjectType, isScalarType, + isUnionType, + visit, } from 'graphql'; -import { isSpecifiedScalarType } from '../polyfills'; -import { Request, Result, VisitSchemaKind } from '../Interfaces'; +import { isSpecifiedScalarType, toConfig } from '../polyfills'; +import { Request, Result } from '../Interfaces'; import { Transform } from '../transforms/transforms'; -import { visitSchema, cloneType } from '../utils'; +import { mapSchema, MapperKind } from '../utils'; export type RenameOptions = { renameBuiltins: boolean; @@ -38,11 +49,8 @@ export default class RenameTypes implements Transform { } public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { - // Modification of specified scalar types is not recommended, but is supported. - // mapSchema does not support modification of specified scalar types, - // so must use visitSchema instead. - return visitSchema(originalSchema, { - [VisitSchemaKind.TYPE]: (type: GraphQLNamedType) => { + return mapSchema(originalSchema, { + [MapperKind.TYPE]: (type: GraphQLNamedType) => { if (isSpecifiedScalarType(type) && !this.renameBuiltins) { return undefined; } @@ -54,13 +62,31 @@ export default class RenameTypes implements Transform { if (newName && newName !== oldName) { this.map[oldName] = type.name; this.reverseMap[newName] = oldName; - const newType = cloneType(type); - newType.name = newName; - return newType; + + const newConfig = { + ...toConfig(type), + name: newName, + }; + + if (isObjectType(type)) { + return new GraphQLObjectType(newConfig); + } else if (isInterfaceType(type)) { + return new GraphQLInterfaceType(newConfig); + } else if (isUnionType(type)) { + return new GraphQLUnionType(newConfig); + } else if (isInputObjectType(type)) { + return new GraphQLInputObjectType(newConfig); + } else if (isEnumType(type)) { + return new GraphQLEnumType(newConfig); + } else if (isScalarType(type)) { + return new GraphQLScalarType(newConfig); + } + + throw new Error(`Unknown type ${type as string}.`); } }, - [VisitSchemaKind.ROOT_OBJECT]() { + [MapperKind.ROOT_OBJECT]() { return undefined; }, }); From b8f145a8b8c7f9306448f457e54f8524242a8d98 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Fri, 20 Mar 2020 00:09:26 -0400 Subject: [PATCH 230/250] refactor(organize): folders and types Use interfaces over tpes whenever possible as per official TypeScript recommendation, so as to support declaration merging. Separate delegation methods from stitching. Rename folders to verbs when possible for consistency. Rename transforms folder => wrap. Move makeRemoteExecutableSchema to wrap folder, as it wraps a remote schema. --- .npmignore | 5 +- src/Interfaces.ts | 141 +++++++++++++++--- .../addTypenameToAbstract.ts | 0 .../checkResultAndHandleErrors.ts | 10 +- src/{stitching => delegate}/createRequest.ts | 0 .../delegateToSchema.ts | 13 +- src/delegate/index.ts | 9 ++ src/{ => generate}/Logger.ts | 2 +- src/generate/index.ts | 1 + src/{ => generate}/makeExecutableSchema.ts | 25 ++-- src/index.ts | 7 +- src/{mock.ts => mock/index.ts} | 26 ++-- .../createMergedResolver.ts | 0 .../defaultMergedResolver.ts | 12 +- src/{stitching => stitch}/errors.ts | 0 .../getResponseKeyFromInfo.ts | 0 src/stitch/index.ts | 14 ++ src/{stitching => stitch}/introspectSchema.ts | 0 src/{stitching => stitch}/linkToFetcher.ts | 0 src/{stitching => stitch}/makeMergedType.ts | 0 src/{stitching => stitch}/mapAsyncIterator.ts | 0 src/{stitching => stitch}/mergeFields.ts | 0 src/{stitching => stitch}/mergeInfo.ts | 6 +- src/{stitching => stitch}/mergeSchemas.ts | 4 +- .../observableToAsyncIterable.ts | 0 src/{stitching => stitch}/proxiedResult.ts | 2 +- .../resolveFromParentTypename.ts | 0 src/{stitching => stitch}/resolvers.ts | 7 +- src/{stitching => stitch}/typeFromAST.ts | 0 src/stitching/index.ts | 27 ---- src/test/testAlternateMergeSchemas.ts | 10 +- src/test/testDataloader.ts | 5 +- src/test/testDelegateToSchema.ts | 4 +- src/test/testDirectives.ts | 2 +- src/test/testErrors.ts | 10 +- src/test/testGatsbyTransforms.ts | 4 +- src/test/testLogger.ts | 4 +- src/test/testMakeRemoteExecutableSchema.ts | 2 +- src/test/testMapSchema.ts | 2 +- src/test/testMergeSchemas.ts | 5 +- src/test/testMocking.ts | 2 +- src/test/testSchemaGenerator.ts | 6 +- src/test/testTransforms.ts | 11 +- src/test/testUpload.ts | 4 +- src/test/testUtils.ts | 2 +- src/test/testingSchemas.ts | 4 +- src/{transforms => utils}/filterSchema.ts | 6 +- src/utils/index.ts | 3 +- src/utils/map.ts | 95 +----------- .../AddArgumentsAsVariables.ts | 4 +- .../AddMergedTypeSelectionSets.ts | 4 +- .../AddReplacementFragments.ts | 4 +- .../AddReplacementSelectionSets.ts | 4 +- .../AddTypenameToAbstract.ts | 6 +- .../CheckResultAndHandleErrors.ts | 6 +- .../ExpandAbstractTypes.ts | 0 src/{transforms => wrap}/ExtendSchema.ts | 5 +- src/{transforms => wrap}/ExtractField.ts | 0 .../FilterObjectFields.ts | 3 +- src/{transforms => wrap}/FilterRootFields.ts | 3 +- src/{transforms => wrap}/FilterToSchema.ts | 4 +- src/{transforms => wrap}/FilterTypes.ts | 4 +- src/{transforms => wrap}/HoistField.ts | 5 +- src/{transforms => wrap}/MapFields.ts | 4 +- .../RenameObjectFields.ts | 3 +- src/{transforms => wrap}/RenameRootFields.ts | 3 +- src/{transforms => wrap}/RenameRootTypes.ts | 5 +- src/{transforms => wrap}/RenameTypes.ts | 5 +- .../ReplaceFieldWithFragment.ts | 4 +- .../TransformObjectFields.ts | 6 +- src/{transforms => wrap}/TransformQuery.ts | 4 +- .../TransformRootFields.ts | 3 +- src/{transforms => wrap}/WrapFields.ts | 13 +- src/{transforms => wrap}/WrapQuery.ts | 0 src/{transforms => wrap}/WrapType.ts | 3 +- src/{transforms => wrap}/index.ts | 7 +- .../makeRemoteExecutableSchema.ts | 12 +- src/{transforms => wrap}/transformSchema.ts | 10 +- src/{transforms => wrap}/transforms.ts | 2 - 79 files changed, 311 insertions(+), 317 deletions(-) rename src/{stitching => delegate}/addTypenameToAbstract.ts (100%) rename src/{stitching => delegate}/checkResultAndHandleErrors.ts (95%) rename src/{stitching => delegate}/createRequest.ts (100%) rename src/{stitching => delegate}/delegateToSchema.ts (97%) create mode 100644 src/delegate/index.ts rename src/{ => generate}/Logger.ts (94%) rename src/{ => generate}/makeExecutableSchema.ts (85%) rename src/{mock.ts => mock/index.ts} (96%) rename src/{stitching => stitch}/createMergedResolver.ts (100%) rename src/{stitching => stitch}/defaultMergedResolver.ts (80%) rename src/{stitching => stitch}/errors.ts (100%) rename src/{stitching => stitch}/getResponseKeyFromInfo.ts (100%) create mode 100644 src/stitch/index.ts rename src/{stitching => stitch}/introspectSchema.ts (100%) rename src/{stitching => stitch}/linkToFetcher.ts (100%) rename src/{stitching => stitch}/makeMergedType.ts (100%) rename src/{stitching => stitch}/mapAsyncIterator.ts (100%) rename src/{stitching => stitch}/mergeFields.ts (100%) rename src/{stitching => stitch}/mergeInfo.ts (99%) rename src/{stitching => stitch}/mergeSchemas.ts (99%) rename src/{stitching => stitch}/observableToAsyncIterable.ts (100%) rename src/{stitching => stitch}/proxiedResult.ts (98%) rename src/{stitching => stitch}/resolveFromParentTypename.ts (100%) rename src/{stitching => stitch}/resolvers.ts (94%) rename src/{stitching => stitch}/typeFromAST.ts (100%) delete mode 100644 src/stitching/index.ts rename src/{transforms => utils}/filterSchema.ts (95%) rename src/{transforms => wrap}/AddArgumentsAsVariables.ts (98%) rename src/{transforms => wrap}/AddMergedTypeSelectionSets.ts (94%) rename src/{transforms => wrap}/AddReplacementFragments.ts (94%) rename src/{transforms => wrap}/AddReplacementSelectionSets.ts (94%) rename src/{transforms => wrap}/AddTypenameToAbstract.ts (75%) rename src/{transforms => wrap}/CheckResultAndHandleErrors.ts (84%) rename src/{transforms => wrap}/ExpandAbstractTypes.ts (100%) rename src/{transforms => wrap}/ExtendSchema.ts (90%) rename src/{transforms => wrap}/ExtractField.ts (100%) rename src/{transforms => wrap}/FilterObjectFields.ts (94%) rename src/{transforms => wrap}/FilterRootFields.ts (95%) rename src/{transforms => wrap}/FilterToSchema.ts (99%) rename src/{transforms => wrap}/FilterTypes.ts (85%) rename src/{transforms => wrap}/HoistField.ts (93%) rename src/{transforms => wrap}/MapFields.ts (97%) rename src/{transforms => wrap}/RenameObjectFields.ts (91%) rename src/{transforms => wrap}/RenameRootFields.ts (91%) rename src/{transforms => wrap}/RenameRootTypes.ts (93%) rename src/{transforms => wrap}/RenameTypes.ts (96%) rename src/{transforms => wrap}/ReplaceFieldWithFragment.ts (97%) rename src/{transforms => wrap}/TransformObjectFields.ts (97%) rename src/{transforms => wrap}/TransformQuery.ts (97%) rename src/{transforms => wrap}/TransformRootFields.ts (94%) rename src/{transforms => wrap}/WrapFields.ts (92%) rename src/{transforms => wrap}/WrapQuery.ts (100%) rename src/{transforms => wrap}/WrapType.ts (88%) rename src/{transforms => wrap}/index.ts (93%) rename src/{stitching => wrap}/makeRemoteExecutableSchema.ts (87%) rename src/{transforms => wrap}/transformSchema.ts (87%) rename src/{transforms => wrap}/transforms.ts (97%) diff --git a/.npmignore b/.npmignore index 5078d8deef1..668489db5e1 100644 --- a/.npmignore +++ b/.npmignore @@ -1,13 +1,14 @@ * !dist/ !dist/* +!dist/delegate/* !dist/generate/* !dist/links/* !dist/polyfills/* !dist/scalars/* -!dist/stitching/* -!dist/transforms/* +!dist/stitch/* !dist/utils/* +!dist/wrap/* !package.json !*.md !*.png diff --git a/src/Interfaces.ts b/src/Interfaces.ts index e96e836fd29..128fdaa26e6 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -23,6 +23,7 @@ import { InlineFragmentNode, GraphQLOutputType, SelectionSetNode, + GraphQLDirective, } from 'graphql'; import { TypeMap } from 'graphql/type/schema'; @@ -31,9 +32,6 @@ import { ApolloLink } from 'apollo-link'; import { SchemaVisitor } from './utils/SchemaVisitor'; import { SchemaDirectiveVisitor } from './utils/SchemaDirectiveVisitor'; -/* TODO: Add documentation */ - -export type UnitOrList = Type | Array; export interface IResolverValidationOptions { requireResolversForArgs?: boolean; requireResolversForNonScalar?: boolean; @@ -67,7 +65,7 @@ export interface IResolverOptions { __isTypeOf?: GraphQLIsTypeOfFn; } -export type Transform = { +export interface Transform { transformSchema?: (schema: GraphQLSchema) => GraphQLSchema; transformRequest?: (originalRequest: Request) => Request; transformResult?: (result: Result) => Result; @@ -90,8 +88,8 @@ export interface IFetcherOperation { export type Dispatcher = (context: any) => ApolloLink | Fetcher; -export type SubschemaConfig = { - schema: GraphQLSchemaWithTransforms; +export interface SubschemaConfig { + schema: GraphQLSchema; rootValue?: Record; executor?: Delegator; subscriber?: Delegator; @@ -102,7 +100,7 @@ export type SubschemaConfig = { merge?: Record; }; -export type MergedTypeConfig = { +export interface MergedTypeConfig { selectionSet?: string; fieldName?: string; args?: (originalResult: any) => Record; @@ -117,7 +115,7 @@ export type MergedTypeResolver = ( selectionSet: SelectionSetNode, ) => any; -export type GraphQLSchemaWithTransforms = GraphQLSchema & { +export interface GraphQLSchemaWithTransforms extends GraphQLSchema { transforms?: Array; }; @@ -160,9 +158,9 @@ export interface ICreateRequestFromInfo { fieldNodes?: ReadonlyArray; } -export type IDelegateRequestOptions = { +export interface IDelegateRequestOptions extends IDelegateToSchemaOptions { request: Request; -} & IDelegateToSchemaOptions; +}; export type Delegator = ({ document, @@ -174,7 +172,7 @@ export type Delegator = ({ variables?: { [key: string]: any }; }) => any; -export type MergeInfo = { +export interface MergeInfo { delegate: ( type: 'query' | 'mutation' | 'subscription', fieldName: string, @@ -193,15 +191,15 @@ export type MergeInfo = { delegateToSchema(options: IDelegateToSchemaOptions): any; }; -export type ReplacementSelectionSetMapping = { +export interface ReplacementSelectionSetMapping { [typeName: string]: { [fieldName: string]: SelectionSetNode }; }; -export type ReplacementFragmentMapping = { +export interface ReplacementFragmentMapping { [typeName: string]: { [fieldName: string]: InlineFragmentNode }; }; -export type MergedTypeInfo = { +export interface MergedTypeInfo { subschemas: Array; selectionSet?: SelectionSetNode; uniqueFields: Record; @@ -219,14 +217,18 @@ export type IFieldResolver> = ( ) => any; export type ITypedef = (() => Array) | string | DocumentNode; + export type ITypeDefinitions = ITypedef | Array; -export type IResolverObject = { + +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) @@ -235,6 +237,7 @@ export interface IResolvers { | GraphQLScalarType | IEnumResolver; } + export type IResolversParameter = | Array IResolvers)> | IResolvers @@ -245,12 +248,14 @@ export interface ILogger { } 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; }; @@ -279,6 +284,7 @@ export type IDefaultValueIteratorFn = ( ) => void; export type NextResolverFn = () => Promise; + export type DirectiveResolverFn = ( next: NextResolverFn, source: TSource, @@ -293,7 +299,9 @@ 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, @@ -328,19 +336,17 @@ export type OnTypeConflict = ( 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; @@ -449,3 +455,92 @@ 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/stitching/addTypenameToAbstract.ts b/src/delegate/addTypenameToAbstract.ts similarity index 100% rename from src/stitching/addTypenameToAbstract.ts rename to src/delegate/addTypenameToAbstract.ts diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/delegate/checkResultAndHandleErrors.ts similarity index 95% rename from src/stitching/checkResultAndHandleErrors.ts rename to src/delegate/checkResultAndHandleErrors.ts index b75ea558961..808f9dd4c8d 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/delegate/checkResultAndHandleErrors.ts @@ -29,11 +29,11 @@ import { relocatedError, combineErrors, getErrorsByPathSegment, -} from './errors'; -import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; -import resolveFromParentTypename from './resolveFromParentTypename'; -import { setErrors, setObjectSubschema } from './proxiedResult'; -import { mergeFields } from './mergeFields'; +} 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, diff --git a/src/stitching/createRequest.ts b/src/delegate/createRequest.ts similarity index 100% rename from src/stitching/createRequest.ts rename to src/delegate/createRequest.ts diff --git a/src/stitching/delegateToSchema.ts b/src/delegate/delegateToSchema.ts similarity index 97% rename from src/stitching/delegateToSchema.ts rename to src/delegate/delegateToSchema.ts index 7fc8f569c9d..eaff8687a73 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/delegate/delegateToSchema.ts @@ -18,6 +18,7 @@ import { SubschemaConfig, isSubschemaConfig, IGraphQLToolsResolveInfo, + Transform, } from '../Interfaces'; import { ExpandAbstractTypes, @@ -29,14 +30,14 @@ import { CheckResultAndHandleErrors, applyRequestTransforms, applyResultTransforms, - Transform, -} from '../transforms'; +} from '../wrap'; + +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'; -import linkToFetcher from './linkToFetcher'; -import { observableToAsyncIterable } from './observableToAsyncIterable'; -import mapAsyncIterator from './mapAsyncIterator'; -import { combineErrors } from './errors'; export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, 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 94% rename from src/Logger.ts rename to src/generate/Logger.ts index f7014601872..15d4f6ead9d 100644 --- a/src/Logger.ts +++ b/src/generate/Logger.ts @@ -2,7 +2,7 @@ * A very simple class for logging errors */ -import { ILogger } from './Interfaces'; +import { ILogger } from '../Interfaces'; export class Logger implements ILogger { public errors: Array; diff --git a/src/generate/index.ts b/src/generate/index.ts index ffe2efa28fe..98477695825 100644 --- a/src/generate/index.ts +++ b/src/generate/index.ts @@ -14,6 +14,7 @@ export { filterExtensionDefinitions, } from './extensionDefinitions'; export { default as SchemaError } from './SchemaError'; +export * from './makeExecutableSchema'; // for backwards compatibility export { default as addResolveFunctionsToSchema } from './addResolversToSchema'; diff --git a/src/makeExecutableSchema.ts b/src/generate/makeExecutableSchema.ts similarity index 85% rename from src/makeExecutableSchema.ts rename to src/generate/makeExecutableSchema.ts index d54a1b28cf9..924c480bf07 100644 --- a/src/makeExecutableSchema.ts +++ b/src/generate/makeExecutableSchema.ts @@ -4,18 +4,17 @@ import { GraphQLFieldResolver, } from 'graphql'; -import { IExecutableSchemaDefinition, ILogger } from './Interfaces'; -import { SchemaDirectiveVisitor, forEachField, mergeDeep } from './utils'; -import { - attachDirectiveResolvers, - assertResolversPresent, - addResolversToSchema, - attachConnectorsToContext, - addSchemaLevelResolver, - buildSchemaFromTypeDefinitions, - decorateWithLogger, - SchemaError, -} from './generate'; +import { IExecutableSchemaDefinition, ILogger } from '../Interfaces'; +import { SchemaDirectiveVisitor, forEachField, mergeDeep } from '../utils'; + +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, @@ -130,5 +129,3 @@ export function addErrorLoggingToSchema( field.resolve = decorateWithLogger(field.resolve, logger, errorHint); }); } - -export * from './generate'; diff --git a/src/index.ts b/src/index.ts index 4ed810be1d3..55dd06de2c1 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,10 @@ export * from './Interfaces'; +export * from './delegate'; +export * from './generate'; export * from './links'; -export * from './makeExecutableSchema'; export * from './mock'; export * from './polyfills'; export * from './scalars'; -export * from './stitching'; -export * from './transforms'; +export * from './stitch'; +export * from './wrap'; export * from './utils'; diff --git a/src/mock.ts b/src/mock/index.ts similarity index 96% rename from src/mock.ts rename to src/mock/index.ts index edc064dda44..152fb3be174 100644 --- a/src/mock.ts +++ b/src/mock/index.ts @@ -21,8 +21,8 @@ import { } from 'graphql'; import { v4 as uuid } from 'uuid'; -import { buildSchemaFromTypeDefinitions } from './makeExecutableSchema'; -import { forEachField } from './utils'; +import { buildSchemaFromTypeDefinitions } from '../generate'; +import { forEachField } from '../utils'; import { IMocks, @@ -31,9 +31,11 @@ import { IMockFn, IMockTypeFn, ITypeDefinitions, -} from './Interfaces'; +} from '../Interfaces'; -// This function wraps addMocksToSchema for more convenience +/** + * This function wraps addMocksToSchema for more convenience + */ function mockServer( schema: GraphQLSchema | ITypeDefinitions, mocks: IMocks, @@ -442,9 +444,13 @@ class MockList { } // retain addMockFunctionsToSchema for backwards compatibility -export { - addMocksToSchema, - addMocksToSchema as addMockFunctionsToSchema, - MockList, - mockServer, -}; + +function addMockFunctionsToSchema({ + schema, + mocks = {}, + preserveResolvers = false, +}: IMockOptions): void { + addMocksToSchema({ schema, mocks, preserveResolvers }); +} + +export { addMocksToSchema, addMockFunctionsToSchema, MockList, mockServer }; diff --git a/src/stitching/createMergedResolver.ts b/src/stitch/createMergedResolver.ts similarity index 100% rename from src/stitching/createMergedResolver.ts rename to src/stitch/createMergedResolver.ts diff --git a/src/stitching/defaultMergedResolver.ts b/src/stitch/defaultMergedResolver.ts similarity index 80% rename from src/stitching/defaultMergedResolver.ts rename to src/stitch/defaultMergedResolver.ts index 3c45099be3c..5a55cd40491 100644 --- a/src/stitching/defaultMergedResolver.ts +++ b/src/stitch/defaultMergedResolver.ts @@ -1,15 +1,17 @@ import { defaultFieldResolver } from 'graphql'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; +import { handleResult } from '../delegate/checkResultAndHandleErrors'; import { getErrors, getSubschema } from './proxiedResult'; -import { handleResult } from './checkResultAndHandleErrors'; 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 +/** + * 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, args: Record, diff --git a/src/stitching/errors.ts b/src/stitch/errors.ts similarity index 100% rename from src/stitching/errors.ts rename to src/stitch/errors.ts diff --git a/src/stitching/getResponseKeyFromInfo.ts b/src/stitch/getResponseKeyFromInfo.ts similarity index 100% rename from src/stitching/getResponseKeyFromInfo.ts rename to src/stitch/getResponseKeyFromInfo.ts diff --git a/src/stitch/index.ts b/src/stitch/index.ts new file mode 100644 index 00000000000..2da2ce29c2e --- /dev/null +++ b/src/stitch/index.ts @@ -0,0 +1,14 @@ +import introspectSchema from './introspectSchema'; +import mergeSchemas from './mergeSchemas'; +import defaultMergedResolver from './defaultMergedResolver'; +import { createMergedResolver } from './createMergedResolver'; +import { dehoistResult, unwrapResult } from './proxiedResult'; + +export { + introspectSchema, + mergeSchemas, + defaultMergedResolver, + createMergedResolver, + dehoistResult, + unwrapResult, +}; diff --git a/src/stitching/introspectSchema.ts b/src/stitch/introspectSchema.ts similarity index 100% rename from src/stitching/introspectSchema.ts rename to src/stitch/introspectSchema.ts diff --git a/src/stitching/linkToFetcher.ts b/src/stitch/linkToFetcher.ts similarity index 100% rename from src/stitching/linkToFetcher.ts rename to src/stitch/linkToFetcher.ts diff --git a/src/stitching/makeMergedType.ts b/src/stitch/makeMergedType.ts similarity index 100% rename from src/stitching/makeMergedType.ts rename to src/stitch/makeMergedType.ts diff --git a/src/stitching/mapAsyncIterator.ts b/src/stitch/mapAsyncIterator.ts similarity index 100% rename from src/stitching/mapAsyncIterator.ts rename to src/stitch/mapAsyncIterator.ts diff --git a/src/stitching/mergeFields.ts b/src/stitch/mergeFields.ts similarity index 100% rename from src/stitching/mergeFields.ts rename to src/stitch/mergeFields.ts diff --git a/src/stitching/mergeInfo.ts b/src/stitch/mergeInfo.ts similarity index 99% rename from src/stitching/mergeInfo.ts rename to src/stitch/mergeInfo.ts index 1f2067b9e51..c7a626d5b7a 100644 --- a/src/stitching/mergeInfo.ts +++ b/src/stitch/mergeInfo.ts @@ -18,12 +18,12 @@ import { SubschemaConfig, IGraphQLToolsResolveInfo, MergedTypeInfo, + Transform, } from '../Interfaces'; import { - Transform, ExpandAbstractTypes, AddReplacementFragments, -} from '../transforms'; +} from '../wrap'; import { parseFragmentToInlineFragment, concatInlineFragments, @@ -31,7 +31,7 @@ import { parseSelectionSet, } from '../utils'; -import delegateToSchema from './delegateToSchema'; +import delegateToSchema from '../delegate/delegateToSchema'; type MergeTypeCandidate = { type: GraphQLNamedType; diff --git a/src/stitching/mergeSchemas.ts b/src/stitch/mergeSchemas.ts similarity index 99% rename from src/stitching/mergeSchemas.ts rename to src/stitch/mergeSchemas.ts index 31a71e83308..b4ff6ef67e3 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitch/mergeSchemas.ts @@ -32,8 +32,8 @@ import { import { extractExtensionDefinitions, addResolversToSchema, -} from '../makeExecutableSchema'; -import { wrapSchema } from '../transforms'; +} from '../generate'; +import { wrapSchema } from '../wrap'; import { SchemaDirectiveVisitor, cloneDirective, diff --git a/src/stitching/observableToAsyncIterable.ts b/src/stitch/observableToAsyncIterable.ts similarity index 100% rename from src/stitching/observableToAsyncIterable.ts rename to src/stitch/observableToAsyncIterable.ts diff --git a/src/stitching/proxiedResult.ts b/src/stitch/proxiedResult.ts similarity index 98% rename from src/stitching/proxiedResult.ts rename to src/stitch/proxiedResult.ts index 8e35991016d..36945a37b2a 100644 --- a/src/stitching/proxiedResult.ts +++ b/src/stitch/proxiedResult.ts @@ -2,8 +2,8 @@ import { GraphQLError, GraphQLSchema, responsePathAsArray } from 'graphql'; import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; import { mergeDeep } from '../utils'; +import { handleNull } from '../delegate/checkResultAndHandleErrors'; -import { handleNull } from './checkResultAndHandleErrors'; import { relocatedError } from './errors'; const hasSymbol = diff --git a/src/stitching/resolveFromParentTypename.ts b/src/stitch/resolveFromParentTypename.ts similarity index 100% rename from src/stitching/resolveFromParentTypename.ts rename to src/stitch/resolveFromParentTypename.ts diff --git a/src/stitching/resolvers.ts b/src/stitch/resolvers.ts similarity index 94% rename from src/stitching/resolvers.ts rename to src/stitch/resolvers.ts index 6e8d78e2ca2..adf0206bd3d 100644 --- a/src/stitching/resolvers.ts +++ b/src/stitch/resolvers.ts @@ -4,14 +4,13 @@ import { GraphQLObjectType, } from 'graphql'; -import { IResolvers, Operation, SubschemaConfig } from '../Interfaces'; -import { Transform } from '../transforms'; +import { Transform, IResolvers, Operation, SubschemaConfig } from '../Interfaces'; +import delegateToSchema from '../delegate/delegateToSchema'; +import { handleResult } from '../delegate/checkResultAndHandleErrors'; -import delegateToSchema from './delegateToSchema'; import { makeMergedType } from './makeMergedType'; import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; import { getErrors, getSubschema } from './proxiedResult'; -import { handleResult } from './checkResultAndHandleErrors'; export type Mapping = { [typeName: string]: { diff --git a/src/stitching/typeFromAST.ts b/src/stitch/typeFromAST.ts similarity index 100% rename from src/stitching/typeFromAST.ts rename to src/stitch/typeFromAST.ts diff --git a/src/stitching/index.ts b/src/stitching/index.ts deleted file mode 100644 index c0c59d69a0e..00000000000 --- a/src/stitching/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import makeRemoteExecutableSchema, { - createResolver as defaultCreateRemoteResolver, -} from './makeRemoteExecutableSchema'; -import introspectSchema from './introspectSchema'; -import mergeSchemas from './mergeSchemas'; -import delegateToSchema, { delegateRequest } from './delegateToSchema'; -import { createRequestFromInfo, createRequest } from './createRequest'; -import defaultMergedResolver from './defaultMergedResolver'; -import { createMergedResolver } from './createMergedResolver'; -import { dehoistResult, unwrapResult } from './proxiedResult'; - -export { - makeRemoteExecutableSchema, - introspectSchema, - mergeSchemas, - // These are currently undocumented and not part of official API, - // but exposed for the community use - delegateToSchema, - createRequestFromInfo, - createRequest, - delegateRequest, - defaultCreateRemoteResolver, - defaultMergedResolver, - createMergedResolver, - dehoistResult, - unwrapResult, -}; diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index b178d4e03d9..464256640ba 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -15,7 +15,6 @@ import { expect } from 'chai'; import { transformSchema, - filterSchema, RenameTypes, RenameRootFields, RenameObjectFields, @@ -26,17 +25,18 @@ import { HoistField, FilterRootFields, FilterObjectFields, -} from '../transforms'; +} from '../wrap'; import { isSpecifiedScalarType, toConfig } from '../polyfills'; -import { makeExecutableSchema } from '../makeExecutableSchema'; +import { delegateToSchema } from '../delegate'; +import { makeExecutableSchema } from '../generate'; import { - delegateToSchema, mergeSchemas, createMergedResolver, -} from '../stitching'; +} from '../stitch'; import { SubschemaConfig } from '../Interfaces'; import { + filterSchema, wrapFieldNode, renameFieldNode, hoistFieldNodes, diff --git a/src/test/testDataloader.ts b/src/test/testDataloader.ts index 29cd626bfa0..593c56803fd 100644 --- a/src/test/testDataloader.ts +++ b/src/test/testDataloader.ts @@ -2,8 +2,9 @@ import DataLoader from 'dataloader'; import { graphql, GraphQLList } from 'graphql'; import { expect } from 'chai'; -import { makeExecutableSchema } from '../makeExecutableSchema'; -import { mergeSchemas, delegateToSchema } from '../stitching'; +import { delegateToSchema } from '../delegate'; +import { makeExecutableSchema } from '../generate'; +import { mergeSchemas } from '../stitch'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; describe('dataloader', () => { diff --git a/src/test/testDelegateToSchema.ts b/src/test/testDelegateToSchema.ts index fe0e638a06f..4154e7a2ebe 100644 --- a/src/test/testDelegateToSchema.ts +++ b/src/test/testDelegateToSchema.ts @@ -1,8 +1,8 @@ import { GraphQLSchema, graphql } from 'graphql'; import { expect } from 'chai'; -import delegateToSchema from '../stitching/delegateToSchema'; -import mergeSchemas from '../stitching/mergeSchemas'; +import delegateToSchema from '../delegate/delegateToSchema'; +import mergeSchemas from '../stitch/mergeSchemas'; import { IResolvers } from '../Interfaces'; import { diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 2af534c0e86..56268e688ff 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -29,7 +29,7 @@ import { import { assert } from 'chai'; import formatDate from 'dateformat'; -import { makeExecutableSchema } from '../makeExecutableSchema'; +import { makeExecutableSchema } from '../generate'; import { VisitableSchemaType } from '../Interfaces'; import { SchemaDirectiveVisitor, diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index f992ed98e51..5dcf9b8052d 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -1,11 +1,11 @@ import { expect, assert } from 'chai'; import { GraphQLError, graphql } from 'graphql'; -import { relocatedError } from '../stitching/errors'; -import { getErrors, ERROR_SYMBOL } from '../stitching/proxiedResult'; -import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; -import { makeExecutableSchema } from '../makeExecutableSchema'; -import { mergeSchemas } from '../stitching'; +import { relocatedError } from '../stitch/errors'; +import { getErrors, ERROR_SYMBOL } from '../stitch/proxiedResult'; +import { checkResultAndHandleErrors } from '../delegate/checkResultAndHandleErrors'; +import { makeExecutableSchema } from '../generate'; +import { mergeSchemas } from '../stitch'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; class ErrorWithExtensions extends GraphQLError { diff --git a/src/test/testGatsbyTransforms.ts b/src/test/testGatsbyTransforms.ts index 5d769342da1..ca7f7cbb9bc 100644 --- a/src/test/testGatsbyTransforms.ts +++ b/src/test/testGatsbyTransforms.ts @@ -8,9 +8,9 @@ import { } from 'graphql'; import { VisitSchemaKind } from '../Interfaces'; -import { transformSchema, RenameTypes } from '../transforms'; +import { transformSchema, RenameTypes } from '../wrap'; import { cloneType, healSchema, visitSchema } from '../utils'; -import { makeExecutableSchema } from '../makeExecutableSchema'; +import { makeExecutableSchema } from '../generate'; import { addMocksToSchema } from '../mock'; // see https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/transforms.js diff --git a/src/test/testLogger.ts b/src/test/testLogger.ts index 3b30d4d3ede..c2873336e03 100644 --- a/src/test/testLogger.ts +++ b/src/test/testLogger.ts @@ -1,8 +1,8 @@ import { assert } from 'chai'; import { graphql } from 'graphql'; -import { Logger } from '../Logger'; -import { makeExecutableSchema } from '../makeExecutableSchema'; +import { makeExecutableSchema } from '../generate'; +import { Logger } from '../generate/Logger'; describe('Logger', () => { it('logs the errors', () => { diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index 96e5a79ea7e..2b0c8d6be3b 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -8,7 +8,7 @@ import { graphql, } from 'graphql'; -import { makeRemoteExecutableSchema } from '../stitching'; +import { makeRemoteExecutableSchema } from '../wrap'; import { propertySchema, subscriptionSchema, diff --git a/src/test/testMapSchema.ts b/src/test/testMapSchema.ts index a00478ebfa1..96970f50210 100644 --- a/src/test/testMapSchema.ts +++ b/src/test/testMapSchema.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { GraphQLObjectType, GraphQLSchema, graphqlSync } from 'graphql'; import { makeExecutableSchema, mapSchema } from '../index'; -import { MapperKind } from '../utils/map'; +import { MapperKind } from '../Interfaces'; import { toConfig } from '../polyfills'; describe('mapSchema', () => { diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index 5cb8e2b19ac..b62636eab1c 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -14,9 +14,10 @@ import { printSchema, } from 'graphql'; -import { makeExecutableSchema } from '../makeExecutableSchema'; +import { delegateToSchema } from '../delegate'; +import { makeExecutableSchema } from '../generate'; import { IResolvers, SubschemaConfig } from '../Interfaces'; -import { delegateToSchema, mergeSchemas } from '../stitching'; +import { mergeSchemas } from '../stitch'; import { cloneSchema, getResolversFromSchema, diff --git a/src/test/testMocking.ts b/src/test/testMocking.ts index f5cbdd12250..3f9a6942fcb 100644 --- a/src/test/testMocking.ts +++ b/src/test/testMocking.ts @@ -11,7 +11,7 @@ import { buildSchemaFromTypeDefinitions, addResolversToSchema, makeExecutableSchema, -} from '../makeExecutableSchema'; +} from '../generate'; import { IMocks } from '../Interfaces'; describe('Mock', () => { diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 0c9d50cb1bf..f29061e11bc 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -21,7 +21,7 @@ import { GraphQLSchema, } from 'graphql'; -import { Logger } from '../Logger'; +import { Logger } from '../generate/Logger'; import { makeExecutableSchema, SchemaError, @@ -31,7 +31,8 @@ import { attachDirectiveResolvers, chainResolvers, concatenateTypeDefs, -} from '../makeExecutableSchema'; + addResolversToSchema, +} from '../generate'; import { IResolverValidationOptions, IResolvers, @@ -42,7 +43,6 @@ import { ILogger, } from '../Interfaces'; import { visitSchema, graphqlVersion } from '../utils'; -import { addResolversToSchema } from '../generate'; import TypeA from './circularSchemaA'; diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 280dfe88e00..50d36e8f3a8 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -10,12 +10,9 @@ import { parse, } from 'graphql'; -import { makeExecutableSchema } from '../makeExecutableSchema'; -import { - delegateToSchema, - defaultMergedResolver, - mergeSchemas, -} from '../stitching'; +import { delegateToSchema } from '../delegate'; +import { makeExecutableSchema } from '../generate'; +import { defaultMergedResolver, mergeSchemas } from '../stitch'; import { transformSchema, RenameTypes, @@ -27,7 +24,7 @@ import { FilterToSchema, TransformQuery, AddReplacementFragments, -} from '../transforms'; +} from '../wrap'; import { concatInlineFragments, parseFragmentToInlineFragment } from '../utils'; import { addMocksToSchema } from '../mock'; diff --git a/src/test/testUpload.ts b/src/test/testUpload.ts index e4e2c45f453..f81ea47b06a 100644 --- a/src/test/testUpload.ts +++ b/src/test/testUpload.ts @@ -10,8 +10,8 @@ import FormData from 'form-data'; import fetch from 'node-fetch'; import { buildSchema } from 'graphql'; -import { mergeSchemas } from '../stitching'; -import { makeExecutableSchema } from '../makeExecutableSchema'; +import { mergeSchemas } from '../stitch'; +import { makeExecutableSchema } from '../generate'; import { createServerHttpLink } from '../links'; import { GraphQLUpload as ServerGraphQLUpload } from '../scalars'; import { SubschemaConfig } from '../Interfaces'; diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index e3586fdc3c4..629d5291735 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -3,7 +3,7 @@ import { GraphQLObjectType } from 'graphql'; import { healSchema } from '../utils'; import { toConfig } from '../polyfills'; -import { makeExecutableSchema } from '../makeExecutableSchema'; +import { makeExecutableSchema } from '../generate'; describe('heal', () => { it('should prune empty types', () => { diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index 81f046899f2..0d2896505df 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -18,9 +18,9 @@ import { } from 'graphql'; import { forAwaitEach } from 'iterall'; -import introspectSchema from '../stitching/introspectSchema'; +import introspectSchema from '../stitch/introspectSchema'; import { IResolvers, Fetcher, SubschemaConfig } from '../Interfaces'; -import { makeExecutableSchema } from '../makeExecutableSchema'; +import { makeExecutableSchema } from '../generate'; import { graphqlVersion } from '../utils'; export type Location = { diff --git a/src/transforms/filterSchema.ts b/src/utils/filterSchema.ts similarity index 95% rename from src/transforms/filterSchema.ts rename to src/utils/filterSchema.ts index 593683e474f..9b80050c9f3 100644 --- a/src/transforms/filterSchema.ts +++ b/src/utils/filterSchema.ts @@ -8,10 +8,10 @@ import { GraphQLType, } from 'graphql'; -import { GraphQLSchemaWithTransforms } from '../Interfaces'; -import { mapSchema } from '../utils'; +import { GraphQLSchemaWithTransforms, MapperKind } from '../Interfaces'; import { toConfig } from '../polyfills'; -import { MapperKind } from '../utils/map'; + +import { mapSchema } from './map'; export type RootFieldFilter = ( operation: 'Query' | 'Mutation' | 'Subscription', diff --git a/src/utils/index.ts b/src/utils/index.ts index 16e5d0eb5b0..c7d38cba3da 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ +export { default as filterSchema } from './filterSchema'; export { cloneSchema, cloneDirective, cloneType } from './clone'; export { healSchema, healTypes } from './heal'; export { SchemaVisitor } from './SchemaVisitor'; @@ -27,4 +28,4 @@ export { export { appendFields, removeFields } from './fields'; export { createNamedStub } from './stub'; export { graphqlVersion } from './graphqlVersion'; -export { mapSchema, MapperKind } from './map'; +export { mapSchema } from './map'; diff --git a/src/utils/map.ts b/src/utils/map.ts index 8c7d80189f8..6e1ada6b65b 100644 --- a/src/utils/map.ts +++ b/src/utils/map.ts @@ -30,95 +30,12 @@ import { import { toConfig, isSpecifiedScalarType } from '../polyfills'; import { graphqlVersion } from '../utils'; - -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; +import { + SchemaMapper, + MapperKind, + NamedTypeMapper, + DirectiveMapper, +} from '../Interfaces'; export function mapSchema( schema: GraphQLSchema, diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/wrap/AddArgumentsAsVariables.ts similarity index 98% rename from src/transforms/AddArgumentsAsVariables.ts rename to src/wrap/AddArgumentsAsVariables.ts index 1a286834955..c12928f85cc 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/wrap/AddArgumentsAsVariables.ts @@ -16,11 +16,9 @@ import { isListType, } from 'graphql'; -import { Request } from '../Interfaces'; +import { Transform, Request } from '../Interfaces'; import { serializeInputValue } from '../utils'; -import { Transform } from './transforms'; - export default class AddArgumentsAsVariablesTransform implements Transform { private readonly targetSchema: GraphQLSchema; private readonly args: { [key: string]: any }; diff --git a/src/transforms/AddMergedTypeSelectionSets.ts b/src/wrap/AddMergedTypeSelectionSets.ts similarity index 94% rename from src/transforms/AddMergedTypeSelectionSets.ts rename to src/wrap/AddMergedTypeSelectionSets.ts index 31f26ae0512..6cb2705623b 100644 --- a/src/transforms/AddMergedTypeSelectionSets.ts +++ b/src/wrap/AddMergedTypeSelectionSets.ts @@ -9,9 +9,7 @@ import { visitWithTypeInfo, } from 'graphql'; -import { Request, MergedTypeInfo } from '../Interfaces'; - -import { Transform } from './transforms'; +import { Transform, Request, MergedTypeInfo } from '../Interfaces'; export default class AddMergedTypeFragments implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/transforms/AddReplacementFragments.ts b/src/wrap/AddReplacementFragments.ts similarity index 94% rename from src/transforms/AddReplacementFragments.ts rename to src/wrap/AddReplacementFragments.ts index fa71d77eb97..1e64f1be76a 100644 --- a/src/transforms/AddReplacementFragments.ts +++ b/src/wrap/AddReplacementFragments.ts @@ -9,9 +9,7 @@ import { visitWithTypeInfo, } from 'graphql'; -import { Request, ReplacementFragmentMapping } from '../Interfaces'; - -import { Transform } from './transforms'; +import { Transform, Request, ReplacementFragmentMapping } from '../Interfaces'; export default class AddReplacementFragments implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/transforms/AddReplacementSelectionSets.ts b/src/wrap/AddReplacementSelectionSets.ts similarity index 94% rename from src/transforms/AddReplacementSelectionSets.ts rename to src/wrap/AddReplacementSelectionSets.ts index 692664adfba..b9e46f53ccc 100644 --- a/src/transforms/AddReplacementSelectionSets.ts +++ b/src/wrap/AddReplacementSelectionSets.ts @@ -9,9 +9,7 @@ import { visitWithTypeInfo, } from 'graphql'; -import { Request, ReplacementSelectionSetMapping } from '../Interfaces'; - -import { Transform } from './transforms'; +import { Transform, Request, ReplacementSelectionSetMapping } from '../Interfaces'; export default class AddReplacementSelectionSets implements Transform { private readonly schema: GraphQLSchema; diff --git a/src/transforms/AddTypenameToAbstract.ts b/src/wrap/AddTypenameToAbstract.ts similarity index 75% rename from src/transforms/AddTypenameToAbstract.ts rename to src/wrap/AddTypenameToAbstract.ts index d7f3c4a7f7d..f3cfaba2174 100644 --- a/src/transforms/AddTypenameToAbstract.ts +++ b/src/wrap/AddTypenameToAbstract.ts @@ -1,9 +1,7 @@ import { GraphQLSchema } from 'graphql'; -import { Request } from '../Interfaces'; -import { addTypenameToAbstract } from '../stitching/addTypenameToAbstract'; - -import { Transform } from './transforms'; +import { Transform, Request } from '../Interfaces'; +import { addTypenameToAbstract } from '../delegate/addTypenameToAbstract'; export default class AddTypenameToAbstract implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/transforms/CheckResultAndHandleErrors.ts b/src/wrap/CheckResultAndHandleErrors.ts similarity index 84% rename from src/transforms/CheckResultAndHandleErrors.ts rename to src/wrap/CheckResultAndHandleErrors.ts index ab8aa0e34a9..ae549c3ea90 100644 --- a/src/transforms/CheckResultAndHandleErrors.ts +++ b/src/wrap/CheckResultAndHandleErrors.ts @@ -1,9 +1,7 @@ import { GraphQLSchema, GraphQLOutputType } from 'graphql'; -import { checkResultAndHandleErrors } from '../stitching/checkResultAndHandleErrors'; -import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; - -import { Transform } from './transforms'; +import { checkResultAndHandleErrors } from '../delegate/checkResultAndHandleErrors'; +import { Transform, SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; export default class CheckResultAndHandleErrors implements Transform { private readonly context?: Record; diff --git a/src/transforms/ExpandAbstractTypes.ts b/src/wrap/ExpandAbstractTypes.ts similarity index 100% rename from src/transforms/ExpandAbstractTypes.ts rename to src/wrap/ExpandAbstractTypes.ts diff --git a/src/transforms/ExtendSchema.ts b/src/wrap/ExtendSchema.ts similarity index 90% rename from src/transforms/ExtendSchema.ts rename to src/wrap/ExtendSchema.ts index 34a7e443b63..d235ba08837 100644 --- a/src/transforms/ExtendSchema.ts +++ b/src/wrap/ExtendSchema.ts @@ -1,10 +1,9 @@ import { GraphQLSchema, extendSchema, parse } from 'graphql'; -import { IFieldResolver, IResolvers, Request } from '../Interfaces'; +import { Transform, IFieldResolver, IResolvers, Request } from '../Interfaces'; import { addResolversToSchema } from '../generate'; -import { defaultMergedResolver } from '../stitching'; +import { defaultMergedResolver } from '../stitch'; -import { Transform } from './transforms'; import MapFields, { FieldNodeTransformerMap } from './MapFields'; export default class ExtendSchema implements Transform { diff --git a/src/transforms/ExtractField.ts b/src/wrap/ExtractField.ts similarity index 100% rename from src/transforms/ExtractField.ts rename to src/wrap/ExtractField.ts diff --git a/src/transforms/FilterObjectFields.ts b/src/wrap/FilterObjectFields.ts similarity index 94% rename from src/transforms/FilterObjectFields.ts rename to src/wrap/FilterObjectFields.ts index b9fccdd3b04..30c1d10570d 100644 --- a/src/transforms/FilterObjectFields.ts +++ b/src/wrap/FilterObjectFields.ts @@ -1,6 +1,7 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; -import { Transform } from './transforms'; +import { Transform } from '../Interfaces'; + import TransformObjectFields from './TransformObjectFields'; export type ObjectFilter = ( diff --git a/src/transforms/FilterRootFields.ts b/src/wrap/FilterRootFields.ts similarity index 95% rename from src/transforms/FilterRootFields.ts rename to src/wrap/FilterRootFields.ts index 196a6cbf68c..8a0aa787137 100644 --- a/src/transforms/FilterRootFields.ts +++ b/src/wrap/FilterRootFields.ts @@ -1,6 +1,7 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; -import { Transform } from './transforms'; +import { Transform } from '../Interfaces'; + import TransformRootFields from './TransformRootFields'; export type RootFilter = ( diff --git a/src/transforms/FilterToSchema.ts b/src/wrap/FilterToSchema.ts similarity index 99% rename from src/transforms/FilterToSchema.ts rename to src/wrap/FilterToSchema.ts index 9f9a13f5873..50095c05119 100644 --- a/src/transforms/FilterToSchema.ts +++ b/src/wrap/FilterToSchema.ts @@ -21,11 +21,9 @@ import { isInterfaceType, } from 'graphql'; -import { Request } from '../Interfaces'; +import { Transform, Request } from '../Interfaces'; import implementsAbstractType from '../utils/implementsAbstractType'; -import { Transform } from './transforms'; - export default class FilterToSchema implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/transforms/FilterTypes.ts b/src/wrap/FilterTypes.ts similarity index 85% rename from src/transforms/FilterTypes.ts rename to src/wrap/FilterTypes.ts index 852ddadb591..e3dbbaf50ac 100644 --- a/src/transforms/FilterTypes.ts +++ b/src/wrap/FilterTypes.ts @@ -1,7 +1,7 @@ import { GraphQLSchema, GraphQLNamedType } from 'graphql'; -import { Transform } from '../transforms'; -import { MapperKind, mapSchema } from '../utils'; +import { mapSchema } from '../utils'; +import { Transform, MapperKind } from '../Interfaces'; export default class FilterTypes implements Transform { private readonly filter: (type: GraphQLNamedType) => boolean; diff --git a/src/transforms/HoistField.ts b/src/wrap/HoistField.ts similarity index 93% rename from src/transforms/HoistField.ts rename to src/wrap/HoistField.ts index dd4a0f1bbfb..12ea8e94455 100644 --- a/src/transforms/HoistField.ts +++ b/src/wrap/HoistField.ts @@ -1,11 +1,10 @@ import { GraphQLSchema, GraphQLObjectType, getNullableType } from 'graphql'; import { healSchema, wrapFieldNode, renameFieldNode } from '../utils'; -import { createMergedResolver } from '../stitching'; +import { createMergedResolver } from '../stitch'; import { appendFields, removeFields } from '../utils/fields'; -import { Request } from '../Interfaces'; +import { Transform, Request } from '../Interfaces'; -import { Transform } from './transforms'; import MapFields from './MapFields'; export default class HoistField implements Transform { diff --git a/src/transforms/MapFields.ts b/src/wrap/MapFields.ts similarity index 97% rename from src/transforms/MapFields.ts rename to src/wrap/MapFields.ts index 56f191963fd..677c298f7d9 100644 --- a/src/transforms/MapFields.ts +++ b/src/wrap/MapFields.ts @@ -11,9 +11,7 @@ import { FragmentDefinitionNode, } from 'graphql'; -import { Request } from '../Interfaces'; - -import { Transform } from './transforms'; +import { Transform, Request } from '../Interfaces'; export type FieldNodeTransformer = ( fieldNode: FieldNode, diff --git a/src/transforms/RenameObjectFields.ts b/src/wrap/RenameObjectFields.ts similarity index 91% rename from src/transforms/RenameObjectFields.ts rename to src/wrap/RenameObjectFields.ts index ce35122e536..08e8eed03c6 100644 --- a/src/transforms/RenameObjectFields.ts +++ b/src/wrap/RenameObjectFields.ts @@ -1,9 +1,8 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; -import { Request } from '../Interfaces'; +import { Transform, Request } from '../Interfaces'; import TransformObjectFields from './TransformObjectFields'; -import { Transform } from './transforms'; export default class RenameObjectFields implements Transform { private readonly transformer: TransformObjectFields; diff --git a/src/transforms/RenameRootFields.ts b/src/wrap/RenameRootFields.ts similarity index 91% rename from src/transforms/RenameRootFields.ts rename to src/wrap/RenameRootFields.ts index 752e49c9e5d..c8a14e8ccca 100644 --- a/src/transforms/RenameRootFields.ts +++ b/src/wrap/RenameRootFields.ts @@ -1,8 +1,7 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; -import { Request } from '../Interfaces'; +import { Transform, Request } from '../Interfaces'; -import { Transform } from './transforms'; import TransformRootFields from './TransformRootFields'; export default class RenameRootFields implements Transform { diff --git a/src/transforms/RenameRootTypes.ts b/src/wrap/RenameRootTypes.ts similarity index 93% rename from src/transforms/RenameRootTypes.ts rename to src/wrap/RenameRootTypes.ts index d31710dd031..61c6b1b966e 100644 --- a/src/transforms/RenameRootTypes.ts +++ b/src/wrap/RenameRootTypes.ts @@ -6,9 +6,8 @@ import { GraphQLObjectType, } from 'graphql'; -import { Request, Result } from '../Interfaces'; -import { Transform } from '../transforms/transforms'; -import { mapSchema, MapperKind } from '../utils'; +import { Request, Result, MapperKind, Transform } from '../Interfaces'; +import { mapSchema } from '../utils'; import { toConfig } from '../polyfills'; export default class RenameRootTypes implements Transform { diff --git a/src/transforms/RenameTypes.ts b/src/wrap/RenameTypes.ts similarity index 96% rename from src/transforms/RenameTypes.ts rename to src/wrap/RenameTypes.ts index ce6c92a63f3..680948a1b98 100644 --- a/src/transforms/RenameTypes.ts +++ b/src/wrap/RenameTypes.ts @@ -19,9 +19,8 @@ import { } from 'graphql'; import { isSpecifiedScalarType, toConfig } from '../polyfills'; -import { Request, Result } from '../Interfaces'; -import { Transform } from '../transforms/transforms'; -import { mapSchema, MapperKind } from '../utils'; +import { Transform, Request, Result, MapperKind } from '../Interfaces'; +import { mapSchema } from '../utils'; export type RenameOptions = { renameBuiltins: boolean; diff --git a/src/transforms/ReplaceFieldWithFragment.ts b/src/wrap/ReplaceFieldWithFragment.ts similarity index 97% rename from src/transforms/ReplaceFieldWithFragment.ts rename to src/wrap/ReplaceFieldWithFragment.ts index fd3696ada5e..00bb657f078 100644 --- a/src/transforms/ReplaceFieldWithFragment.ts +++ b/src/wrap/ReplaceFieldWithFragment.ts @@ -13,9 +13,7 @@ import { } from 'graphql'; import { concatInlineFragments } from '../utils'; -import { Request } from '../Interfaces'; - -import { Transform } from './transforms'; +import { Transform, Request } from '../Interfaces'; export default class ReplaceFieldWithFragment implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/transforms/TransformObjectFields.ts b/src/wrap/TransformObjectFields.ts similarity index 97% rename from src/transforms/TransformObjectFields.ts rename to src/wrap/TransformObjectFields.ts index 05ebb3cfb47..dc71e3a1924 100644 --- a/src/transforms/TransformObjectFields.ts +++ b/src/wrap/TransformObjectFields.ts @@ -16,12 +16,10 @@ import { } from 'graphql'; import isEmptyObject from '../utils/isEmptyObject'; -import { Request } from '../Interfaces'; -import { mapSchema, MapperKind } from '../utils'; +import { Transform, Request, MapperKind } from '../Interfaces'; +import { mapSchema } from '../utils'; import { toConfig } from '../polyfills'; -import { Transform } from './transforms'; - export type ObjectFieldTransformer = ( typeName: string, fieldName: string, diff --git a/src/transforms/TransformQuery.ts b/src/wrap/TransformQuery.ts similarity index 97% rename from src/transforms/TransformQuery.ts rename to src/wrap/TransformQuery.ts index d94f66529d2..6144b1d7a61 100644 --- a/src/transforms/TransformQuery.ts +++ b/src/wrap/TransformQuery.ts @@ -6,9 +6,7 @@ import { GraphQLError, } from 'graphql'; -import { Request, Result } from '../Interfaces'; - -import { Transform } from './transforms'; +import { Transform, Request, Result } from '../Interfaces'; export type QueryTransformer = ( selectionSet: SelectionSetNode, diff --git a/src/transforms/TransformRootFields.ts b/src/wrap/TransformRootFields.ts similarity index 94% rename from src/transforms/TransformRootFields.ts rename to src/wrap/TransformRootFields.ts index 6de4fddd225..219f4c3e650 100644 --- a/src/transforms/TransformRootFields.ts +++ b/src/wrap/TransformRootFields.ts @@ -1,8 +1,7 @@ import { GraphQLSchema, GraphQLField, GraphQLFieldConfig } from 'graphql'; -import { Request } from '../Interfaces'; +import { Transform, Request } from '../Interfaces'; -import { Transform } from './transforms'; import TransformObjectFields, { FieldNodeTransformer, } from './TransformObjectFields'; diff --git a/src/transforms/WrapFields.ts b/src/wrap/WrapFields.ts similarity index 92% rename from src/transforms/WrapFields.ts rename to src/wrap/WrapFields.ts index 01be37eaa4e..d116ddb676d 100644 --- a/src/transforms/WrapFields.ts +++ b/src/wrap/WrapFields.ts @@ -1,11 +1,14 @@ import { GraphQLSchema, GraphQLObjectType } from 'graphql'; -import { Request } from '../Interfaces'; -import { appendFields, removeFields } from '../utils/fields'; -import { hoistFieldNodes, healSchema } from '../utils'; -import { defaultMergedResolver, createMergedResolver } from '../stitching'; +import { Transform, Request } from '../Interfaces'; +import { + hoistFieldNodes, + healSchema, + appendFields, + removeFields, +} from '../utils'; +import { defaultMergedResolver, createMergedResolver } from '../stitch'; -import { Transform } from './transforms'; import MapFields from './MapFields'; export default class WrapFields implements Transform { diff --git a/src/transforms/WrapQuery.ts b/src/wrap/WrapQuery.ts similarity index 100% rename from src/transforms/WrapQuery.ts rename to src/wrap/WrapQuery.ts diff --git a/src/transforms/WrapType.ts b/src/wrap/WrapType.ts similarity index 88% rename from src/transforms/WrapType.ts rename to src/wrap/WrapType.ts index a4feadf5084..51794c08797 100644 --- a/src/transforms/WrapType.ts +++ b/src/wrap/WrapType.ts @@ -1,8 +1,7 @@ import { GraphQLSchema } from 'graphql'; -import { Request } from '../Interfaces'; +import { Transform, Request } from '../Interfaces'; -import { Transform } from './transforms'; import WrapFields from './WrapFields'; export default class WrapType implements Transform { diff --git a/src/transforms/index.ts b/src/wrap/index.ts similarity index 93% rename from src/transforms/index.ts rename to src/wrap/index.ts index 052153c0a73..3f87fb355cf 100644 --- a/src/transforms/index.ts +++ b/src/wrap/index.ts @@ -1,11 +1,9 @@ export { - Transform, applySchemaTransforms, applyRequestTransforms, applyResultTransforms, } from './transforms'; -export { default as filterSchema } from './filterSchema'; export { default as transformSchema, wrapSchema } from './transformSchema'; export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErrors'; @@ -32,6 +30,11 @@ export { default as WrapFields } from './WrapFields'; export { default as HoistField } from './HoistField'; export { default as MapFields } from './MapFields'; +export { + default as makeRemoteExecutableSchema, + createResolver as defaultCreateRemoteResolver, +} from './makeRemoteExecutableSchema'; + // implemented directly within initial query proxy creation export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; // superseded by AddReplacementFragments diff --git a/src/stitching/makeRemoteExecutableSchema.ts b/src/wrap/makeRemoteExecutableSchema.ts similarity index 87% rename from src/stitching/makeRemoteExecutableSchema.ts rename to src/wrap/makeRemoteExecutableSchema.ts index 64427e1befc..7f1ce08f86a 100644 --- a/src/stitching/makeRemoteExecutableSchema.ts +++ b/src/wrap/makeRemoteExecutableSchema.ts @@ -12,13 +12,13 @@ import { addResolversToSchema } from '../generate'; import { Fetcher, Operation } from '../Interfaces'; import { cloneSchema } from '../utils'; import { buildSchema } from '../polyfills'; +import { addTypenameToAbstract } from '../delegate/addTypenameToAbstract'; +import { checkResultAndHandleErrors } from '../delegate/checkResultAndHandleErrors'; -import linkToFetcher, { execute } from './linkToFetcher'; -import { addTypenameToAbstract } from './addTypenameToAbstract'; -import { checkResultAndHandleErrors } from './checkResultAndHandleErrors'; -import { observableToAsyncIterable } from './observableToAsyncIterable'; -import mapAsyncIterator from './mapAsyncIterator'; -import { stripResolvers, generateProxyingResolvers } from './resolvers'; +import linkToFetcher, { execute } from '../stitch/linkToFetcher'; +import { observableToAsyncIterable } from '../stitch/observableToAsyncIterable'; +import mapAsyncIterator from '../stitch/mapAsyncIterator'; +import { stripResolvers, generateProxyingResolvers } from '../stitch/resolvers'; export type ResolverFn = ( rootValue?: any, diff --git a/src/transforms/transformSchema.ts b/src/wrap/transformSchema.ts similarity index 87% rename from src/transforms/transformSchema.ts rename to src/wrap/transformSchema.ts index 4f1adfbd63e..6c6b5c463c6 100644 --- a/src/transforms/transformSchema.ts +++ b/src/wrap/transformSchema.ts @@ -1,17 +1,19 @@ import { GraphQLSchema } from 'graphql'; -import { addResolversToSchema } from '../makeExecutableSchema'; -import { Transform, applySchemaTransforms } from '../transforms/transforms'; +import { addResolversToSchema } from '../generate'; import { generateProxyingResolvers, stripResolvers, -} from '../stitching/resolvers'; +} from '../stitch/resolvers'; import { + Transform, SubschemaConfig, isSubschemaConfig, GraphQLSchemaWithTransforms, } from '../Interfaces'; -import { cloneSchema } from '../utils/clone'; +import { cloneSchema } from '../utils'; + +import { applySchemaTransforms } from './transforms'; export function wrapSchema( subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, diff --git a/src/transforms/transforms.ts b/src/wrap/transforms.ts similarity index 97% rename from src/transforms/transforms.ts rename to src/wrap/transforms.ts index b131d680d46..3c8bfcab920 100644 --- a/src/transforms/transforms.ts +++ b/src/wrap/transforms.ts @@ -3,8 +3,6 @@ import { GraphQLSchema } from 'graphql'; import { Request, Transform } from '../Interfaces'; import { cloneSchema } from '../utils'; -export { Transform }; - export function applySchemaTransforms( originalSchema: GraphQLSchema, transforms: Array, From 9ccb8251513107d64ee72d1e3d39b81c77f33fe8 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Fri, 20 Mar 2020 00:35:51 -0400 Subject: [PATCH 231/250] refactor(npmignore): into package.json Use files property within package json to white-list files in straightforward fashion. Do not publish extra markdown/images. --- .npmignore | 14 -------------- package.json | 4 ++++ 2 files changed, 4 insertions(+), 14 deletions(-) delete mode 100644 .npmignore diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 668489db5e1..00000000000 --- a/.npmignore +++ /dev/null @@ -1,14 +0,0 @@ -* -!dist/ -!dist/* -!dist/delegate/* -!dist/generate/* -!dist/links/* -!dist/polyfills/* -!dist/scalars/* -!dist/stitch/* -!dist/utils/* -!dist/wrap/* -!package.json -!*.md -!*.png diff --git a/package.json b/package.json index dd127999ed6..4f6e927c0f0 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,10 @@ "description": "Useful tools to create and manipulate GraphQL schemas.", "main": "dist/index.js", "types": "dist/index.d.ts", + "files": [ + "/dist", + "!/dist/test" + ], "sideEffects": false, "scripts": { "clean": "rimraf dist", From d994577724fe457bc1f4f314d31c25a6faec960e Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 21 Mar 2020 21:18:30 -0400 Subject: [PATCH 232/250] fix(tests): v15 test failure using mapSchema rather than visitSchema changes print(schema) order --- src/test/testAlternateMergeSchemas.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 464256640ba..13ecc1aa376 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -575,11 +575,7 @@ describe('filter and rename object fields', () => { it('should filter', () => { if (graphqlVersion() >= 15) { - expect(printSchema(transformedPropertySchema)).to.equal(`type Query { - propertyById(id: ID!): New_Property -} - -type New_Property { + expect(printSchema(transformedPropertySchema)).to.equal(`type New_Property { new_id: ID! new_name: String! new_location: New_Location @@ -589,6 +585,10 @@ type New_Property { type New_Location { name: String! } + +type Query { + propertyById(id: ID!): New_Property +} `); } else { expect(printSchema(transformedPropertySchema)).to From d09b778e16b96eef421eaa93bf052dbbd36c6e49 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sat, 21 Mar 2020 22:49:11 -0400 Subject: [PATCH 233/250] chore(prettier) --- src/Interfaces.ts | 36 ++++++++++++++----------- src/stitch/mergeInfo.ts | 5 +--- src/stitch/mergeSchemas.ts | 5 +--- src/stitch/resolvers.ts | 7 ++++- src/test/testAlternateMergeSchemas.ts | 8 +++--- src/wrap/AddReplacementSelectionSets.ts | 6 ++++- src/wrap/CheckResultAndHandleErrors.ts | 6 ++++- src/wrap/transformSchema.ts | 5 +--- 8 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 128fdaa26e6..c9ab749046d 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -69,7 +69,7 @@ export interface Transform { transformSchema?: (schema: GraphQLSchema) => GraphQLSchema; transformRequest?: (originalRequest: Request) => Request; transformResult?: (result: Result) => Result; -}; +} export interface IGraphQLToolsResolveInfo extends GraphQLResolveInfo { mergeInfo?: MergeInfo; @@ -98,14 +98,14 @@ export interface SubschemaConfig { dispatcher?: Dispatcher; transforms?: Array; merge?: Record; -}; +} export interface MergedTypeConfig { selectionSet?: string; fieldName?: string; args?: (originalResult: any) => Record; resolve?: MergedTypeResolver; -}; +} export type MergedTypeResolver = ( originalResult: any, @@ -117,7 +117,7 @@ export type MergedTypeResolver = ( export interface GraphQLSchemaWithTransforms extends GraphQLSchema { transforms?: Array; -}; +} export type SchemaLikeObject = | SubschemaConfig @@ -160,7 +160,7 @@ export interface ICreateRequestFromInfo { export interface IDelegateRequestOptions extends IDelegateToSchemaOptions { request: Request; -}; +} export type Delegator = ({ document, @@ -189,15 +189,15 @@ export interface MergeInfo { 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; @@ -207,7 +207,7 @@ export interface MergedTypeInfo { typeMaps: Map; selectionSets: Map; containsSelectionSet: Map>; -}; +} export type IFieldResolver> = ( source: TSource, @@ -225,9 +225,11 @@ export interface IResolverObject { | IFieldResolver | IResolverOptions | IResolverObject; -}; +} -export interface IEnumResolver { [key: string]: string | number }; +export interface IEnumResolver { + [key: string]: string | number; +} export interface IResolvers { [key: string]: @@ -257,7 +259,7 @@ export type IConnector = export interface IConnectors { [key: string]: IConnector; -}; +} export interface IExecutableSchemaDefinition { typeDefs: ITypeDefinitions; @@ -300,7 +302,9 @@ export interface IDirectiveResolvers { /* XXX on mocks, args are optional, Not sure if a bug. */ export type IMockFn = GraphQLFieldResolver; -export interface IMocks { [key: string]: IMockFn }; +export interface IMocks { + [key: string]: IMockFn; +} export type IMockTypeFn = ( type: GraphQLType, @@ -340,18 +344,18 @@ export interface Request { document: DocumentNode; variables: Record; extensions?: Record; -}; +} export interface Result extends ExecutionResult { extensions?: Record; -}; +} export interface GraphQLParseOptions { noLocation?: boolean; allowLegacySDLEmptyFields?: boolean; allowLegacySDLImplementsInterfaces?: boolean; experimentalFragmentVariables?: boolean; -}; +} export type IndexedObject = { [key: string]: V } | ReadonlyArray; diff --git a/src/stitch/mergeInfo.ts b/src/stitch/mergeInfo.ts index c7a626d5b7a..df355bc7792 100644 --- a/src/stitch/mergeInfo.ts +++ b/src/stitch/mergeInfo.ts @@ -20,10 +20,7 @@ import { MergedTypeInfo, Transform, } from '../Interfaces'; -import { - ExpandAbstractTypes, - AddReplacementFragments, -} from '../wrap'; +import { ExpandAbstractTypes, AddReplacementFragments } from '../wrap'; import { parseFragmentToInlineFragment, concatInlineFragments, diff --git a/src/stitch/mergeSchemas.ts b/src/stitch/mergeSchemas.ts index b4ff6ef67e3..539234aaf62 100644 --- a/src/stitch/mergeSchemas.ts +++ b/src/stitch/mergeSchemas.ts @@ -29,10 +29,7 @@ import { IResolvers, SubschemaConfig, } from '../Interfaces'; -import { - extractExtensionDefinitions, - addResolversToSchema, -} from '../generate'; +import { extractExtensionDefinitions, addResolversToSchema } from '../generate'; import { wrapSchema } from '../wrap'; import { SchemaDirectiveVisitor, diff --git a/src/stitch/resolvers.ts b/src/stitch/resolvers.ts index adf0206bd3d..920abdf3d94 100644 --- a/src/stitch/resolvers.ts +++ b/src/stitch/resolvers.ts @@ -4,7 +4,12 @@ import { GraphQLObjectType, } from 'graphql'; -import { Transform, IResolvers, Operation, SubschemaConfig } from '../Interfaces'; +import { + Transform, + IResolvers, + Operation, + SubschemaConfig, +} from '../Interfaces'; import delegateToSchema from '../delegate/delegateToSchema'; import { handleResult } from '../delegate/checkResultAndHandleErrors'; diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 13ecc1aa376..29578eecaaa 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -30,10 +30,7 @@ import { isSpecifiedScalarType, toConfig } from '../polyfills'; import { delegateToSchema } from '../delegate'; import { makeExecutableSchema } from '../generate'; -import { - mergeSchemas, - createMergedResolver, -} from '../stitch'; +import { mergeSchemas, createMergedResolver } from '../stitch'; import { SubschemaConfig } from '../Interfaces'; import { filterSchema, @@ -575,7 +572,8 @@ describe('filter and rename object fields', () => { it('should filter', () => { if (graphqlVersion() >= 15) { - expect(printSchema(transformedPropertySchema)).to.equal(`type New_Property { + expect(printSchema(transformedPropertySchema)).to + .equal(`type New_Property { new_id: ID! new_name: String! new_location: New_Location diff --git a/src/wrap/AddReplacementSelectionSets.ts b/src/wrap/AddReplacementSelectionSets.ts index b9e46f53ccc..324a5cb0063 100644 --- a/src/wrap/AddReplacementSelectionSets.ts +++ b/src/wrap/AddReplacementSelectionSets.ts @@ -9,7 +9,11 @@ import { visitWithTypeInfo, } from 'graphql'; -import { Transform, Request, ReplacementSelectionSetMapping } from '../Interfaces'; +import { + Transform, + Request, + ReplacementSelectionSetMapping, +} from '../Interfaces'; export default class AddReplacementSelectionSets implements Transform { private readonly schema: GraphQLSchema; diff --git a/src/wrap/CheckResultAndHandleErrors.ts b/src/wrap/CheckResultAndHandleErrors.ts index ae549c3ea90..904895f11f2 100644 --- a/src/wrap/CheckResultAndHandleErrors.ts +++ b/src/wrap/CheckResultAndHandleErrors.ts @@ -1,7 +1,11 @@ import { GraphQLSchema, GraphQLOutputType } from 'graphql'; import { checkResultAndHandleErrors } from '../delegate/checkResultAndHandleErrors'; -import { Transform, SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; +import { + Transform, + SubschemaConfig, + IGraphQLToolsResolveInfo, +} from '../Interfaces'; export default class CheckResultAndHandleErrors implements Transform { private readonly context?: Record; diff --git a/src/wrap/transformSchema.ts b/src/wrap/transformSchema.ts index 6c6b5c463c6..cb4085ac171 100644 --- a/src/wrap/transformSchema.ts +++ b/src/wrap/transformSchema.ts @@ -1,10 +1,7 @@ import { GraphQLSchema } from 'graphql'; import { addResolversToSchema } from '../generate'; -import { - generateProxyingResolvers, - stripResolvers, -} from '../stitch/resolvers'; +import { generateProxyingResolvers, stripResolvers } from '../stitch/resolvers'; import { Transform, SubschemaConfig, From 2abda1b5dfc94fdd0338195cf80704f065dde9b6 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 22 Mar 2020 15:27:11 -0400 Subject: [PATCH 234/250] fix(toConfig): accept field or inputField as arguments now that fieldToFieldConfig has been removed, closes #44 --- src/polyfills/index.ts | 6 ++ src/polyfills/toConfig.ts | 160 +++++++++++++++++++++++--------------- 2 files changed, 105 insertions(+), 61 deletions(-) diff --git a/src/polyfills/index.ts b/src/polyfills/index.ts index d28d35039b8..054341ba434 100644 --- a/src/polyfills/index.ts +++ b/src/polyfills/index.ts @@ -15,4 +15,10 @@ export { scalarTypeToConfig, inputObjectTypeToConfig, directiveToConfig, + inputFieldMapToConfig, + inputFieldToConfig, + fieldMapToConfig, + fieldToConfig, + argumentMapToConfig, + argumentToConfig, } from './toConfig'; diff --git a/src/polyfills/toConfig.ts b/src/polyfills/toConfig.ts index 535f03b5e8d..25b8d2c7249 100644 --- a/src/polyfills/toConfig.ts +++ b/src/polyfills/toConfig.ts @@ -23,6 +23,12 @@ import { GraphQLDirectiveConfig, GraphQLSchemaConfig, GraphQLNamedType, + GraphQLField, + GraphQLInputField, + GraphQLInputFieldConfig, + GraphQLInputFieldMap, + GraphQLArgumentConfig, + GraphQLFieldConfig, isObjectType, isInterfaceType, isUnionType, @@ -74,50 +80,65 @@ export function schemaToConfig(schema: GraphQLSchema): GraphQLSchemaConfig { return schemaConfig; } +export function toConfig(graphqlObject: GraphQLSchema): GraphQLSchemaConfig; export function toConfig( - schemaOrTypeOrDirective: GraphQLSchema, -): GraphQLSchemaConfig; -export function toConfig( - schemaOrTypeOrDirective: GraphQLObjectTypeConfig & { + graphqlObject: GraphQLObjectTypeConfig & { interfaces: Array; fields: GraphQLFieldConfigMap; }, ): GraphQLObjectTypeConfig; export function toConfig( - schemaOrTypeOrDirective: GraphQLInterfaceType, + graphqlObject: GraphQLInterfaceType, ): GraphQLInterfaceTypeConfig & { fields: GraphQLFieldConfigMap; }; export function toConfig( - schemaOrTypeOrDirective: GraphQLUnionType, + graphqlObject: GraphQLUnionType, ): GraphQLUnionTypeConfig & { types: Array; }; +export function toConfig(graphqlObject: GraphQLEnumType): GraphQLEnumTypeConfig; export function toConfig( - schemaOrTypeOrDirective: GraphQLEnumType, -): GraphQLEnumTypeConfig; -export function toConfig( - schemaOrTypeOrDirective: GraphQLScalarType, + graphqlObject: GraphQLScalarType, ): GraphQLScalarTypeConfig; export function toConfig( - schemaOrTypeOrDirective: GraphQLInputObjectType, + graphqlObject: GraphQLInputObjectType, ): GraphQLInputObjectTypeConfig & { fields: GraphQLInputFieldConfigMap; }; export function toConfig( - schemaOrTypeOrDirective: GraphQLDirective, + graphqlObject: GraphQLDirective, ): GraphQLDirectiveConfig; -export function toConfig(schemaOrTypeOrDirective: any): any; -export function toConfig(schemaOrTypeOrDirective: any) { - if (isSchema(schemaOrTypeOrDirective)) { - return schemaToConfig(schemaOrTypeOrDirective); - } else if (isDirective(schemaOrTypeOrDirective)) { - return directiveToConfig(schemaOrTypeOrDirective); - } else if (isNamedType(schemaOrTypeOrDirective)) { - return typeToConfig(schemaOrTypeOrDirective); +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); } - throw new Error(`Unknown object ${schemaOrTypeOrDirective as string}`); + const ofType = graphqlObject.type; + if (ofType != null) { + if (ofType.defaultValue !== undefined) { + return inputFieldToConfig(graphqlObject); + } else if ( + ofType.resolve !== undefined || + ofType.subscribe !== undefined || + ofType.args !== undefined + ) { + return fieldToConfig(graphqlObject); + } + } + + throw new Error(`Unknown graphql object ${graphqlObject as string}`); } export function typeToConfig( @@ -166,7 +187,7 @@ export function objectTypeToConfig( name: type.name, description: type.description, interfaces: type.getInterfaces(), - fields: fieldsToFieldsConfig(type.getFields()), + fields: fieldMapToConfig(type.getFields()), isTypeOf: type.isTypeOf, extensions: type.extensions, astNode: type.astNode, @@ -187,7 +208,7 @@ export function interfaceTypeToConfig( const typeConfig = { name: type.name, description: type.description, - fields: fieldsToFieldsConfig(type.getFields()), + fields: fieldMapToConfig(type.getFields()), resolveType: type.resolveType, extensions: type.extensions, astNode: type.astNode, @@ -302,25 +323,10 @@ export function inputObjectTypeToConfig( return type.toConfig(); } - const newFields = {}; - const fields = type.getFields(); - - Object.keys(fields).forEach(fieldName => { - const field = fields[fieldName]; - - newFields[fieldName] = { - description: field.description, - type: field.type, - defaultValue: field.defaultValue, - extensions: field.extensions, - astNode: field.astNode, - }; - }); - const typeConfig = { name: type.name, description: type.description, - fields: newFields, + fields: inputFieldMapToConfig(type.getFields()), extensions: type.extensions, astNode: type.astNode, extensionASTNodes: @@ -330,6 +336,30 @@ export function inputObjectTypeToConfig( 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 { @@ -341,7 +371,7 @@ export function directiveToConfig( name: directive.name, description: directive.description, locations: directive.locations, - args: argsToArgsConfig(directive.args), + args: argumentMapToConfig(directive.args), isRepeatable: ((directive as unknown) as { isRepeatable: boolean }) .isRepeatable, extensions: directive.extensions, @@ -351,43 +381,51 @@ export function directiveToConfig( return directiveConfig; } -function fieldsToFieldsConfig( +export function fieldMapToConfig( fields: GraphQLFieldMap, ): GraphQLFieldConfigMap { const newFields = {}; Object.keys(fields).forEach(fieldName => { const field = fields[fieldName]; - - newFields[fieldName] = { - description: field.description, - type: field.type, - args: argsToArgsConfig(field.args), - resolve: field.resolve, - subscribe: field.subscribe, - deprecationReason: field.deprecationReason, - extensions: field.extensions, - astNode: field.astNode, - }; + newFields[fieldName] = toConfig(field); }); return newFields; } -function argsToArgsConfig( +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] = { - description: arg.description, - type: arg.type, - defaultValue: arg.defaultValue, - extensions: arg.extensions, - astNode: arg.astNode, - }; + 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, + }; +} From f53371fef13398c85fdd0ad88759c02600f760be Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 22 Mar 2020 21:25:43 -0400 Subject: [PATCH 235/250] fix(transforms): fix nested field name changes closes #43 --- src/test/testAlternateMergeSchemas.ts | 107 +++++++++++++++++++++ src/wrap/AddMergedTypeSelectionSets.ts | 44 +++++---- src/wrap/MapFields.ts | 76 +++++++-------- src/wrap/TransformObjectFields.ts | 126 +++++++++++++------------ 4 files changed, 231 insertions(+), 122 deletions(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 29578eecaaa..35067072b06 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -9,6 +9,7 @@ import { printSchema, GraphQLFieldConfig, GraphQLObjectType, + graphqlSync, } from 'graphql'; import { forAwaitEach } from 'iterall'; import { expect } from 'chai'; @@ -672,6 +673,112 @@ type Query { }); }); +describe('rename nested object fields with interfaces', () => { + it('should work', () => { + const originalNode = { + aList: [ + { + anInnerObject: { + _linkType: 'linkedItem', + aString: 'Hello, world' + } + } + ] + }; + + const transformedNode = { + ALIST: [ + { + ANINNEROBJECT: { + _linkType: 'linkedItem', + ASTRING: 'Hello, world' + } + } + ] + }; + + const originalSchema = makeExecutableSchema({ + typeDefs: ` + interface _Linkable { + _linkType: String! + } + type linkedItem implements _Linkable { + _linkType: String! + aString: String! + } + type aLink { + anInnerObject: _Linkable + } + type aObject { + aList: [aLink!] + } + type Query { + node: aObject + } + `, + resolvers: { + _Linkable: { + __resolveType: (linkable: { _linkType: string }) => linkable._linkType + }, + Query: { + node: () => originalNode, + } + } + }); + + const transformedSchema = transformSchema(originalSchema, [ + new RenameObjectFields((typeName, fieldName) => { + if (typeName === 'Query') { + return fieldName; + } + + // Remote uses leading underscores for special fields. Leave them alone. + if (fieldName[0] === '_') { + return fieldName; + }; + + return fieldName.toUpperCase(); + }) + ]); + + const originalQuery = ` + query { + node { + aList { + anInnerObject { + _linkType + ... on linkedItem { + aString + } + } + } + } + } + `; + + const transformedQuery = ` + query { + node { + ALIST { + ANINNEROBJECT { + _linkType + ... on linkedItem { + ASTRING + } + } + } + } + } + `; + + const originalResult = graphqlSync(originalSchema, originalQuery); + const transformedResult = graphqlSync(transformedSchema, transformedQuery); + + expect(originalResult).to.deep.equal({ data: { node: originalNode }}); + expect(transformedResult).to.deep.equal({ data: { node: transformedNode }}); + }); +}); + describe('WrapType transform', () => { it('should work', async () => { const transformedPropertySchema = transformSchema(propertySchema, [ diff --git a/src/wrap/AddMergedTypeSelectionSets.ts b/src/wrap/AddMergedTypeSelectionSets.ts index 6cb2705623b..5382b7e730f 100644 --- a/src/wrap/AddMergedTypeSelectionSets.ts +++ b/src/wrap/AddMergedTypeSelectionSets.ts @@ -45,33 +45,31 @@ function addMergedTypeSelectionSets( return visit( document, visitWithTypeInfo(typeInfo, { - leave: { - [Kind.SELECTION_SET]( - node: SelectionSetNode, - ): SelectionSetNode | null | undefined { - const parentType: - | GraphQLType - | null - | undefined = typeInfo.getParentType(); - if (parentType != null) { - const parentTypeName = parentType.name; - let selections = node.selections; + [Kind.SELECTION_SET]( + node: SelectionSetNode, + ): SelectionSetNode | null | undefined { + const parentType: + | GraphQLType + | null + | undefined = typeInfo.getParentType(); + if (parentType != null) { + const parentTypeName = parentType.name; + let selections = node.selections; - if (mapping[parentTypeName] != null) { - const selectionSet = mapping[parentTypeName].selectionSet; - if (selectionSet != null) { - selections = selections.concat(selectionSet.selections); - } + if (mapping[parentTypeName] != null) { + const selectionSet = mapping[parentTypeName].selectionSet; + if (selectionSet != null) { + selections = selections.concat(selectionSet.selections); } + } - if (selections !== node.selections) { - return { - ...node, - selections, - }; - } + if (selections !== node.selections) { + return { + ...node, + selections, + }; } - }, + } }, }), ); diff --git a/src/wrap/MapFields.ts b/src/wrap/MapFields.ts index 677c298f7d9..abf3abd2d1d 100644 --- a/src/wrap/MapFields.ts +++ b/src/wrap/MapFields.ts @@ -73,52 +73,54 @@ function transformDocument( const newDocument: DocumentNode = visit( document, visitWithTypeInfo(typeInfo, { - [Kind.SELECTION_SET]: node => { - const parentType: - | GraphQLType - | null - | undefined = typeInfo.getParentType(); - if (parentType != null) { - const parentTypeName = parentType.name; - const fieldNodeTransformers = fieldNodeTransformerMap[parentTypeName]; - let newSelections: Array = []; + leave: { + [Kind.SELECTION_SET]: node => { + const parentType: + | GraphQLType + | null + | undefined = typeInfo.getParentType(); + if (parentType != null) { + const parentTypeName = parentType.name; + const fieldNodeTransformers = fieldNodeTransformerMap[parentTypeName]; + let newSelections: Array = []; - node.selections.forEach(selection => { - if (selection.kind === Kind.FIELD) { - const fieldName = selection.name.value; + node.selections.forEach(selection => { + if (selection.kind === Kind.FIELD) { + const fieldName = selection.name.value; - let transformedSelection; - if (fieldNodeTransformers != null) { - const fieldNodeTransformer = fieldNodeTransformers[fieldName]; - if (fieldNodeTransformer != null) { - transformedSelection = fieldNodeTransformer( - selection, - fragments, - ); + let transformedSelection; + if (fieldNodeTransformers != null) { + const fieldNodeTransformer = fieldNodeTransformers[fieldName]; + if (fieldNodeTransformer != null) { + transformedSelection = fieldNodeTransformer( + selection, + fragments, + ); + } else { + transformedSelection = selection; + } } else { transformedSelection = selection; } - } else { - transformedSelection = selection; - } - if (Array.isArray(transformedSelection)) { - newSelections = newSelections.concat(transformedSelection); - } else if (transformedSelection.kind === Kind.FIELD) { - newSelections.push(transformedSelection); + if (Array.isArray(transformedSelection)) { + newSelections = newSelections.concat(transformedSelection); + } else if (transformedSelection.kind === Kind.FIELD) { + newSelections.push(transformedSelection); + } else { + newSelections.push(transformedSelection); + } } else { - newSelections.push(transformedSelection); + newSelections.push(selection); } - } else { - newSelections.push(selection); - } - }); + }); - return { - ...node, - selections: newSelections, - }; - } + return { + ...node, + selections: newSelections, + }; + } + }, }, }), ); diff --git a/src/wrap/TransformObjectFields.ts b/src/wrap/TransformObjectFields.ts index dc71e3a1924..77f4c3f1192 100644 --- a/src/wrap/TransformObjectFields.ts +++ b/src/wrap/TransformObjectFields.ts @@ -144,70 +144,72 @@ export default class TransformObjectFields implements Transform { const newDocument: DocumentNode = visit( document, visitWithTypeInfo(typeInfo, { - [Kind.SELECTION_SET](node: SelectionSetNode): SelectionSetNode { - const parentType: GraphQLType = typeInfo.getParentType(); - if (parentType != null) { - const parentTypeName = parentType.name; - let newSelections: Array = []; - - node.selections.forEach(selection => { - if (selection.kind !== Kind.FIELD) { - newSelections.push(selection); - return; - } - - const newName = selection.name.value; - - const transformedSelection = - fieldNodeTransformer != null - ? fieldNodeTransformer( - parentTypeName, - newName, - selection, - fragments, - ) - : selection; - - if (Array.isArray(transformedSelection)) { - newSelections = newSelections.concat(transformedSelection); - return; - } - - if (transformedSelection.kind !== Kind.FIELD) { - newSelections.push(transformedSelection); - return; - } - - const typeMapping = mapping[parentTypeName]; - if (typeMapping == null) { - newSelections.push(transformedSelection); - return; - } - - const oldName = mapping[parentTypeName][newName]; - if (oldName == null) { - newSelections.push(transformedSelection); - return; - } - - newSelections.push({ - ...transformedSelection, - name: { - kind: Kind.NAME, - value: oldName, - }, - alias: { - kind: Kind.NAME, - value: newName, - }, + leave: { + [Kind.SELECTION_SET]: (node: SelectionSetNode): SelectionSetNode => { + const parentType: GraphQLType = typeInfo.getParentType(); + if (parentType != null) { + const parentTypeName = parentType.name; + let newSelections: Array = []; + + node.selections.forEach(selection => { + if (selection.kind !== Kind.FIELD) { + newSelections.push(selection); + return; + } + + const newName = selection.name.value; + + const transformedSelection = + fieldNodeTransformer != null + ? fieldNodeTransformer( + parentTypeName, + newName, + selection, + fragments, + ) + : selection; + + if (Array.isArray(transformedSelection)) { + newSelections = newSelections.concat(transformedSelection); + return; + } + + if (transformedSelection.kind !== Kind.FIELD) { + newSelections.push(transformedSelection); + return; + } + + const typeMapping = mapping[parentTypeName]; + if (typeMapping == null) { + newSelections.push(transformedSelection); + return; + } + + const oldName = mapping[parentTypeName][newName]; + if (oldName == null) { + newSelections.push(transformedSelection); + return; + } + + newSelections.push({ + ...transformedSelection, + name: { + kind: Kind.NAME, + value: oldName, + }, + alias: { + kind: Kind.NAME, + value: newName, + }, + }); }); - }); - return { - ...node, - selections: newSelections, - }; - } + return { + ...node, + selections: newSelections, + }; + } + }, }, }), ); From 279aa2762e5490420f9f75676c4e40a9a321f0b1 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 22 Mar 2020 21:51:50 -0400 Subject: [PATCH 236/250] fix(toConfig): in older versions of graphql --- src/polyfills/toConfig.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/polyfills/toConfig.ts b/src/polyfills/toConfig.ts index 25b8d2c7249..d3d377caf02 100644 --- a/src/polyfills/toConfig.ts +++ b/src/polyfills/toConfig.ts @@ -125,16 +125,28 @@ export function toConfig(graphqlObject: any) { return typeToConfig(graphqlObject); } - const ofType = graphqlObject.type; - if (ofType != null) { - if (ofType.defaultValue !== undefined) { - return inputFieldToConfig(graphqlObject); - } else if ( - ofType.resolve !== undefined || - ofType.subscribe !== undefined || - ofType.args !== undefined + // 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 != null + ) { + 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, } } From 06bc62dd9fd4d47a96fd2a1fa8ec335dea1eba8f Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Sun, 22 Mar 2020 22:02:26 -0400 Subject: [PATCH 237/250] chore(prettier) --- src/polyfills/toConfig.ts | 6 ++--- src/test/testAlternateMergeSchemas.ts | 33 +++++++++++++++------------ src/wrap/MapFields.ts | 3 ++- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/polyfills/toConfig.ts b/src/polyfills/toConfig.ts index d3d377caf02..295a22a113f 100644 --- a/src/polyfills/toConfig.ts +++ b/src/polyfills/toConfig.ts @@ -134,9 +134,7 @@ export function toConfig(graphqlObject: any) { graphqlObject.subscribe != null ) { return fieldToConfig(graphqlObject); - } else if ( - graphqlObject.defaultValue != null - ) { + } else if (graphqlObject.defaultValue != null) { return inputFieldToConfig(graphqlObject); } @@ -147,7 +145,7 @@ export function toConfig(graphqlObject: any) { const { name, isDeprecated, ...rest } = graphqlObject; return { ...rest, - } + }; } throw new Error(`Unknown graphql object ${graphqlObject as string}`); diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 35067072b06..98d48696d62 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -680,10 +680,10 @@ describe('rename nested object fields with interfaces', () => { { anInnerObject: { _linkType: 'linkedItem', - aString: 'Hello, world' - } - } - ] + aString: 'Hello, world', + }, + }, + ], }; const transformedNode = { @@ -691,10 +691,10 @@ describe('rename nested object fields with interfaces', () => { { ANINNEROBJECT: { _linkType: 'linkedItem', - ASTRING: 'Hello, world' - } - } - ] + ASTRING: 'Hello, world', + }, + }, + ], }; const originalSchema = makeExecutableSchema({ @@ -718,12 +718,13 @@ describe('rename nested object fields with interfaces', () => { `, resolvers: { _Linkable: { - __resolveType: (linkable: { _linkType: string }) => linkable._linkType + __resolveType: (linkable: { _linkType: string }) => + linkable._linkType, }, Query: { node: () => originalNode, - } - } + }, + }, }); const transformedSchema = transformSchema(originalSchema, [ @@ -735,10 +736,10 @@ describe('rename nested object fields with interfaces', () => { // Remote uses leading underscores for special fields. Leave them alone. if (fieldName[0] === '_') { return fieldName; - }; + } return fieldName.toUpperCase(); - }) + }), ]); const originalQuery = ` @@ -774,8 +775,10 @@ describe('rename nested object fields with interfaces', () => { const originalResult = graphqlSync(originalSchema, originalQuery); const transformedResult = graphqlSync(transformedSchema, transformedQuery); - expect(originalResult).to.deep.equal({ data: { node: originalNode }}); - expect(transformedResult).to.deep.equal({ data: { node: transformedNode }}); + expect(originalResult).to.deep.equal({ data: { node: originalNode } }); + expect(transformedResult).to.deep.equal({ + data: { node: transformedNode }, + }); }); }); diff --git a/src/wrap/MapFields.ts b/src/wrap/MapFields.ts index abf3abd2d1d..b059e225977 100644 --- a/src/wrap/MapFields.ts +++ b/src/wrap/MapFields.ts @@ -81,7 +81,8 @@ function transformDocument( | undefined = typeInfo.getParentType(); if (parentType != null) { const parentTypeName = parentType.name; - const fieldNodeTransformers = fieldNodeTransformerMap[parentTypeName]; + const fieldNodeTransformers = + fieldNodeTransformerMap[parentTypeName]; let newSelections: Array = []; node.selections.forEach(selection => { From 390f699980d3acf6fdd14610d050706b8c00a1f3 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 23 Mar 2020 07:53:07 -0400 Subject: [PATCH 238/250] fix(addResolveFunctionsToSchema): return modified schema for backwards compatibility See https://github.com/ardatan/graphql-toolkit/commit/46ce3562230e538a2a1472ab0c45fa5267d68ddc#commitcomment-37987581 --- src/generate/addResolversToSchema.ts | 2 +- src/generate/assertResolversPresent.ts | 2 +- src/generate/index.ts | 48 ++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index 34e211beb2b..7e7a4019e8f 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -32,7 +32,7 @@ function addResolversToSchema( schemaOrOptions: GraphQLSchema | IAddResolversToSchemaOptions, legacyInputResolvers?: IResolvers, legacyInputValidationOptions?: IResolverValidationOptions, -) { +): GraphQLSchema { const options: IAddResolversToSchemaOptions = isSchema(schemaOrOptions) ? { schema: schemaOrOptions, diff --git a/src/generate/assertResolversPresent.ts b/src/generate/assertResolversPresent.ts index 20ce5efc375..f3701c48ab5 100644 --- a/src/generate/assertResolversPresent.ts +++ b/src/generate/assertResolversPresent.ts @@ -13,7 +13,7 @@ import SchemaError from './SchemaError'; function assertResolversPresent( schema: GraphQLSchema, resolverValidationOptions: IResolverValidationOptions = {}, -) { +): void { const { requireResolversForArgs = false, requireResolversForNonScalar = false, diff --git a/src/generate/index.ts b/src/generate/index.ts index 98477695825..e32f284566b 100644 --- a/src/generate/index.ts +++ b/src/generate/index.ts @@ -1,6 +1,16 @@ -export { default as addResolversToSchema } from './addResolversToSchema'; -export { default as addSchemaLevelResolver } from './addSchemaLevelResolver'; -export { default as assertResolversPresent } from './assertResolversPresent'; +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'; @@ -16,7 +26,31 @@ export { export { default as SchemaError } from './SchemaError'; export * from './makeExecutableSchema'; -// for backwards compatibility -export { default as addResolveFunctionsToSchema } from './addResolversToSchema'; -export { default as addSchemaLevelResolveFunction } from './addSchemaLevelResolver'; -export { default as assertResolveFunctionsPresent } from './assertResolversPresent'; +// 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); +} From 0116a1b266c86a3bf758885ffbe5e70cf97853bc Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 23 Mar 2020 14:44:15 -0400 Subject: [PATCH 239/250] refactor(MapFields): using TransformObjectFields --- src/wrap/MapFields.ts | 122 ++++++++---------------------------------- 1 file changed, 23 insertions(+), 99 deletions(-) diff --git a/src/wrap/MapFields.ts b/src/wrap/MapFields.ts index b059e225977..8c4f0239a38 100644 --- a/src/wrap/MapFields.ts +++ b/src/wrap/MapFields.ts @@ -1,17 +1,14 @@ import { GraphQLSchema, - GraphQLType, - DocumentNode, FieldNode, - TypeInfo, - visit, - visitWithTypeInfo, - Kind, SelectionNode, FragmentDefinitionNode, } from 'graphql'; import { Transform, Request } from '../Interfaces'; +import { toConfig } from '../polyfills'; + +import TransformObjectFields from './TransformObjectFields'; export type FieldNodeTransformer = ( fieldNode: FieldNode, @@ -25,105 +22,32 @@ export type FieldNodeTransformerMap = { }; export default class MapFields implements Transform { - private schema: GraphQLSchema | undefined; - private readonly fieldNodeTransformerMap: FieldNodeTransformerMap; + private readonly transformer: TransformObjectFields; constructor(fieldNodeTransformerMap: FieldNodeTransformerMap) { - this.fieldNodeTransformerMap = fieldNodeTransformerMap; + this.transformer = new TransformObjectFields( + (_typeName, _fieldName, field) => toConfig(field), + (typeName, fieldName, fieldNode, fragments) => { + const typeTransformers = fieldNodeTransformerMap[typeName]; + if (typeTransformers == null) { + return fieldNode; + } + + const fieldNodeTransformer = typeTransformers[fieldName]; + if (fieldNodeTransformer == null) { + return fieldNode; + } + + return fieldNodeTransformer(fieldNode, fragments); + }, + ); } public transformSchema(schema: GraphQLSchema): GraphQLSchema { - this.schema = schema; - return schema; + return this.transformer.transformSchema(schema); } - public transformRequest(originalRequest: Request): Request { - if (!this.schema) { - throw new Error( - 'MapFields transform required initialization with target schema within the transformSchema method.', - ); - } - - const fragments = {}; - originalRequest.document.definitions - .filter(def => def.kind === Kind.FRAGMENT_DEFINITION) - .forEach(def => { - fragments[(def as FragmentDefinitionNode).name.value] = def; - }); - const document = transformDocument( - originalRequest.document, - this.schema, - this.fieldNodeTransformerMap, - fragments, - ); - return { - ...originalRequest, - document, - }; + public transformRequest(request: Request): Request { + return this.transformer.transformRequest(request); } } - -function transformDocument( - document: DocumentNode, - schema: GraphQLSchema, - fieldNodeTransformerMap: FieldNodeTransformerMap, - fragments: Record = {}, -): DocumentNode { - const typeInfo = new TypeInfo(schema); - const newDocument: DocumentNode = visit( - document, - visitWithTypeInfo(typeInfo, { - leave: { - [Kind.SELECTION_SET]: node => { - const parentType: - | GraphQLType - | null - | undefined = typeInfo.getParentType(); - if (parentType != null) { - const parentTypeName = parentType.name; - const fieldNodeTransformers = - fieldNodeTransformerMap[parentTypeName]; - let newSelections: Array = []; - - node.selections.forEach(selection => { - if (selection.kind === Kind.FIELD) { - const fieldName = selection.name.value; - - let transformedSelection; - if (fieldNodeTransformers != null) { - const fieldNodeTransformer = fieldNodeTransformers[fieldName]; - if (fieldNodeTransformer != null) { - transformedSelection = fieldNodeTransformer( - selection, - fragments, - ); - } else { - transformedSelection = selection; - } - } else { - transformedSelection = selection; - } - - if (Array.isArray(transformedSelection)) { - newSelections = newSelections.concat(transformedSelection); - } else if (transformedSelection.kind === Kind.FIELD) { - newSelections.push(transformedSelection); - } else { - newSelections.push(transformedSelection); - } - } else { - newSelections.push(selection); - } - }); - - return { - ...node, - selections: newSelections, - }; - } - }, - }, - }), - ); - return newDocument; -} From 96be7919bd11074c16e5180cc31ebcac57d2c58e Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 23 Mar 2020 15:31:35 -0400 Subject: [PATCH 240/250] refactor(transforms): collect wrapping transforms move wrapping transforms into separate subfolder --- src/wrap/index.ts | 34 ++++--------------- .../AddArgumentsAsVariables.ts | 4 +-- .../AddMergedTypeSelectionSets.ts | 2 +- .../AddReplacementFragments.ts | 2 +- .../AddReplacementSelectionSets.ts | 2 +- .../{ => transforms}/AddTypenameToAbstract.ts | 4 +-- .../CheckResultAndHandleErrors.ts | 4 +-- .../{ => transforms}/ExpandAbstractTypes.ts | 4 +-- src/wrap/{ => transforms}/ExtendSchema.ts | 6 ++-- src/wrap/{ => transforms}/ExtractField.ts | 2 +- .../{ => transforms}/FilterObjectFields.ts | 2 +- src/wrap/{ => transforms}/FilterRootFields.ts | 2 +- src/wrap/{ => transforms}/FilterToSchema.ts | 4 +-- src/wrap/{ => transforms}/FilterTypes.ts | 4 +-- src/wrap/{ => transforms}/HoistField.ts | 8 ++--- src/wrap/{ => transforms}/MapFields.ts | 4 +-- .../{ => transforms}/RenameObjectFields.ts | 2 +- src/wrap/{ => transforms}/RenameRootFields.ts | 2 +- src/wrap/{ => transforms}/RenameRootTypes.ts | 6 ++-- src/wrap/{ => transforms}/RenameTypes.ts | 6 ++-- .../ReplaceFieldWithFragment.ts | 4 +-- .../{ => transforms}/TransformObjectFields.ts | 8 ++--- src/wrap/{ => transforms}/TransformQuery.ts | 2 +- .../{ => transforms}/TransformRootFields.ts | 2 +- src/wrap/{ => transforms}/WrapFields.ts | 6 ++-- src/wrap/{ => transforms}/WrapQuery.ts | 2 +- src/wrap/{ => transforms}/WrapType.ts | 2 +- src/wrap/transforms/index.ts | 33 ++++++++++++++++++ 28 files changed, 87 insertions(+), 76 deletions(-) rename src/wrap/{ => transforms}/AddArgumentsAsVariables.ts (98%) rename src/wrap/{ => transforms}/AddMergedTypeSelectionSets.ts (96%) rename src/wrap/{ => transforms}/AddReplacementFragments.ts (99%) rename src/wrap/{ => transforms}/AddReplacementSelectionSets.ts (98%) rename src/wrap/{ => transforms}/AddTypenameToAbstract.ts (78%) rename src/wrap/{ => transforms}/CheckResultAndHandleErrors.ts (91%) rename src/wrap/{ => transforms}/ExpandAbstractTypes.ts (98%) rename src/wrap/{ => transforms}/ExtendSchema.ts (92%) rename src/wrap/{ => transforms}/ExtractField.ts (96%) rename src/wrap/{ => transforms}/FilterObjectFields.ts (94%) rename src/wrap/{ => transforms}/FilterRootFields.ts (95%) rename src/wrap/{ => transforms}/FilterToSchema.ts (98%) rename src/wrap/{ => transforms}/FilterTypes.ts (84%) rename src/wrap/{ => transforms}/HoistField.ts (88%) rename src/wrap/{ => transforms}/MapFields.ts (93%) rename src/wrap/{ => transforms}/RenameObjectFields.ts (93%) rename src/wrap/{ => transforms}/RenameRootFields.ts (94%) rename src/wrap/{ => transforms}/RenameRootTypes.ts (93%) rename src/wrap/{ => transforms}/RenameTypes.ts (95%) rename src/wrap/{ => transforms}/ReplaceFieldWithFragment.ts (97%) rename src/wrap/{ => transforms}/TransformObjectFields.ts (96%) rename src/wrap/{ => transforms}/TransformQuery.ts (98%) rename src/wrap/{ => transforms}/TransformRootFields.ts (96%) rename src/wrap/{ => transforms}/WrapFields.ts (96%) rename src/wrap/{ => transforms}/WrapQuery.ts (97%) rename src/wrap/{ => transforms}/WrapType.ts (92%) create mode 100644 src/wrap/transforms/index.ts diff --git a/src/wrap/index.ts b/src/wrap/index.ts index 3f87fb355cf..5914d23ed35 100644 --- a/src/wrap/index.ts +++ b/src/wrap/index.ts @@ -6,29 +6,7 @@ export { export { default as transformSchema, wrapSchema } from './transformSchema'; -export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErrors'; -export { default as ExpandAbstractTypes } from './ExpandAbstractTypes'; -export { default as AddReplacementSelectionSets } from './AddReplacementSelectionSets'; -export { default as AddMergedTypeSelectionSets } from './AddMergedTypeSelectionSets'; -export { default as FilterToSchema } from './FilterToSchema'; -export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; - -export { default as RenameTypes } from './RenameTypes'; -export { default as RenameRootTypes } from './RenameRootTypes'; -export { default as FilterTypes } from './FilterTypes'; -export { default as TransformRootFields } from './TransformRootFields'; -export { default as RenameRootFields } from './RenameRootFields'; -export { default as FilterRootFields } from './FilterRootFields'; -export { default as TransformObjectFields } from './TransformObjectFields'; -export { default as RenameObjectFields } from './RenameObjectFields'; -export { default as FilterObjectFields } from './FilterObjectFields'; -export { default as TransformQuery } from './TransformQuery'; - -export { default as ExtendSchema } from './ExtendSchema'; -export { default as WrapType } from './WrapType'; -export { default as WrapFields } from './WrapFields'; -export { default as HoistField } from './HoistField'; -export { default as MapFields } from './MapFields'; +export * from './transforms/index'; export { default as makeRemoteExecutableSchema, @@ -36,11 +14,11 @@ export { } from './makeRemoteExecutableSchema'; // implemented directly within initial query proxy creation -export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; +export { default as AddArgumentsAsVariables } from './transforms/AddArgumentsAsVariables'; // superseded by AddReplacementFragments -export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; +export { default as ReplaceFieldWithFragment } from './transforms/ReplaceFieldWithFragment'; // superseded by AddReplacementSelectionSets -export { default as AddReplacementFragments } from './AddReplacementFragments'; +export { default as AddReplacementFragments } from './transforms/AddReplacementFragments'; // superseded by TransformQuery -export { default as WrapQuery } from './WrapQuery'; -export { default as ExtractField } from './ExtractField'; +export { default as WrapQuery } from './transforms/WrapQuery'; +export { default as ExtractField } from './transforms/ExtractField'; diff --git a/src/wrap/AddArgumentsAsVariables.ts b/src/wrap/transforms/AddArgumentsAsVariables.ts similarity index 98% rename from src/wrap/AddArgumentsAsVariables.ts rename to src/wrap/transforms/AddArgumentsAsVariables.ts index c12928f85cc..ce63aa5b4a9 100644 --- a/src/wrap/AddArgumentsAsVariables.ts +++ b/src/wrap/transforms/AddArgumentsAsVariables.ts @@ -16,8 +16,8 @@ import { isListType, } from 'graphql'; -import { Transform, Request } from '../Interfaces'; -import { serializeInputValue } from '../utils'; +import { Transform, Request } from '../../Interfaces'; +import { serializeInputValue } from '../../utils'; export default class AddArgumentsAsVariablesTransform implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/wrap/AddMergedTypeSelectionSets.ts b/src/wrap/transforms/AddMergedTypeSelectionSets.ts similarity index 96% rename from src/wrap/AddMergedTypeSelectionSets.ts rename to src/wrap/transforms/AddMergedTypeSelectionSets.ts index 5382b7e730f..261a9a64404 100644 --- a/src/wrap/AddMergedTypeSelectionSets.ts +++ b/src/wrap/transforms/AddMergedTypeSelectionSets.ts @@ -9,7 +9,7 @@ import { visitWithTypeInfo, } from 'graphql'; -import { Transform, Request, MergedTypeInfo } from '../Interfaces'; +import { Transform, Request, MergedTypeInfo } from '../../Interfaces'; export default class AddMergedTypeFragments implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/wrap/AddReplacementFragments.ts b/src/wrap/transforms/AddReplacementFragments.ts similarity index 99% rename from src/wrap/AddReplacementFragments.ts rename to src/wrap/transforms/AddReplacementFragments.ts index 1e64f1be76a..05ce122ea26 100644 --- a/src/wrap/AddReplacementFragments.ts +++ b/src/wrap/transforms/AddReplacementFragments.ts @@ -9,7 +9,7 @@ import { visitWithTypeInfo, } from 'graphql'; -import { Transform, Request, ReplacementFragmentMapping } from '../Interfaces'; +import { Transform, Request, ReplacementFragmentMapping } from '../../Interfaces'; export default class AddReplacementFragments implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/wrap/AddReplacementSelectionSets.ts b/src/wrap/transforms/AddReplacementSelectionSets.ts similarity index 98% rename from src/wrap/AddReplacementSelectionSets.ts rename to src/wrap/transforms/AddReplacementSelectionSets.ts index 324a5cb0063..4df0908fac4 100644 --- a/src/wrap/AddReplacementSelectionSets.ts +++ b/src/wrap/transforms/AddReplacementSelectionSets.ts @@ -13,7 +13,7 @@ import { Transform, Request, ReplacementSelectionSetMapping, -} from '../Interfaces'; +} from '../../Interfaces'; export default class AddReplacementSelectionSets implements Transform { private readonly schema: GraphQLSchema; diff --git a/src/wrap/AddTypenameToAbstract.ts b/src/wrap/transforms/AddTypenameToAbstract.ts similarity index 78% rename from src/wrap/AddTypenameToAbstract.ts rename to src/wrap/transforms/AddTypenameToAbstract.ts index f3cfaba2174..a6a1582c9e1 100644 --- a/src/wrap/AddTypenameToAbstract.ts +++ b/src/wrap/transforms/AddTypenameToAbstract.ts @@ -1,7 +1,7 @@ import { GraphQLSchema } from 'graphql'; -import { Transform, Request } from '../Interfaces'; -import { addTypenameToAbstract } from '../delegate/addTypenameToAbstract'; +import { Transform, Request } from '../../Interfaces'; +import { addTypenameToAbstract } from '../../delegate/addTypenameToAbstract'; export default class AddTypenameToAbstract implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/wrap/CheckResultAndHandleErrors.ts b/src/wrap/transforms/CheckResultAndHandleErrors.ts similarity index 91% rename from src/wrap/CheckResultAndHandleErrors.ts rename to src/wrap/transforms/CheckResultAndHandleErrors.ts index 904895f11f2..9775024ff2c 100644 --- a/src/wrap/CheckResultAndHandleErrors.ts +++ b/src/wrap/transforms/CheckResultAndHandleErrors.ts @@ -1,11 +1,11 @@ import { GraphQLSchema, GraphQLOutputType } from 'graphql'; -import { checkResultAndHandleErrors } from '../delegate/checkResultAndHandleErrors'; +import { checkResultAndHandleErrors } from '../../delegate/checkResultAndHandleErrors'; import { Transform, SubschemaConfig, IGraphQLToolsResolveInfo, -} from '../Interfaces'; +} from '../../Interfaces'; export default class CheckResultAndHandleErrors implements Transform { private readonly context?: Record; diff --git a/src/wrap/ExpandAbstractTypes.ts b/src/wrap/transforms/ExpandAbstractTypes.ts similarity index 98% rename from src/wrap/ExpandAbstractTypes.ts rename to src/wrap/transforms/ExpandAbstractTypes.ts index c26371a8ae0..4afe325a51f 100644 --- a/src/wrap/ExpandAbstractTypes.ts +++ b/src/wrap/transforms/ExpandAbstractTypes.ts @@ -14,8 +14,8 @@ import { visitWithTypeInfo, } from 'graphql'; -import implementsAbstractType from '../utils/implementsAbstractType'; -import { Transform, Request } from '../Interfaces'; +import implementsAbstractType from '../../utils/implementsAbstractType'; +import { Transform, Request } from '../../Interfaces'; type TypeMapping = { [key: string]: Array }; diff --git a/src/wrap/ExtendSchema.ts b/src/wrap/transforms/ExtendSchema.ts similarity index 92% rename from src/wrap/ExtendSchema.ts rename to src/wrap/transforms/ExtendSchema.ts index d235ba08837..f430f3ce095 100644 --- a/src/wrap/ExtendSchema.ts +++ b/src/wrap/transforms/ExtendSchema.ts @@ -1,8 +1,8 @@ import { GraphQLSchema, extendSchema, parse } from 'graphql'; -import { Transform, IFieldResolver, IResolvers, Request } from '../Interfaces'; -import { addResolversToSchema } from '../generate'; -import { defaultMergedResolver } from '../stitch'; +import { Transform, IFieldResolver, IResolvers, Request } from '../../Interfaces'; +import { addResolversToSchema } from '../../generate'; +import { defaultMergedResolver } from '../../stitch'; import MapFields, { FieldNodeTransformerMap } from './MapFields'; diff --git a/src/wrap/ExtractField.ts b/src/wrap/transforms/ExtractField.ts similarity index 96% rename from src/wrap/ExtractField.ts rename to src/wrap/transforms/ExtractField.ts index f89c38b314b..a8c27f09676 100644 --- a/src/wrap/ExtractField.ts +++ b/src/wrap/transforms/ExtractField.ts @@ -1,6 +1,6 @@ import { visit, Kind, SelectionSetNode, BREAK, FieldNode } from 'graphql'; -import { Transform, Request } from '../Interfaces'; +import { Transform, Request } from '../../Interfaces'; export default class ExtractField implements Transform { private readonly from: Array; diff --git a/src/wrap/FilterObjectFields.ts b/src/wrap/transforms/FilterObjectFields.ts similarity index 94% rename from src/wrap/FilterObjectFields.ts rename to src/wrap/transforms/FilterObjectFields.ts index 30c1d10570d..f8d9d3854c6 100644 --- a/src/wrap/FilterObjectFields.ts +++ b/src/wrap/transforms/FilterObjectFields.ts @@ -1,6 +1,6 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; -import { Transform } from '../Interfaces'; +import { Transform } from '../../Interfaces'; import TransformObjectFields from './TransformObjectFields'; diff --git a/src/wrap/FilterRootFields.ts b/src/wrap/transforms/FilterRootFields.ts similarity index 95% rename from src/wrap/FilterRootFields.ts rename to src/wrap/transforms/FilterRootFields.ts index 8a0aa787137..19fe474e1de 100644 --- a/src/wrap/FilterRootFields.ts +++ b/src/wrap/transforms/FilterRootFields.ts @@ -1,6 +1,6 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; -import { Transform } from '../Interfaces'; +import { Transform } from '../../Interfaces'; import TransformRootFields from './TransformRootFields'; diff --git a/src/wrap/FilterToSchema.ts b/src/wrap/transforms/FilterToSchema.ts similarity index 98% rename from src/wrap/FilterToSchema.ts rename to src/wrap/transforms/FilterToSchema.ts index 50095c05119..57448cf5ddc 100644 --- a/src/wrap/FilterToSchema.ts +++ b/src/wrap/transforms/FilterToSchema.ts @@ -21,8 +21,8 @@ import { isInterfaceType, } from 'graphql'; -import { Transform, Request } from '../Interfaces'; -import implementsAbstractType from '../utils/implementsAbstractType'; +import { Transform, Request } from '../../Interfaces'; +import implementsAbstractType from '../../utils/implementsAbstractType'; export default class FilterToSchema implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/wrap/FilterTypes.ts b/src/wrap/transforms/FilterTypes.ts similarity index 84% rename from src/wrap/FilterTypes.ts rename to src/wrap/transforms/FilterTypes.ts index e3dbbaf50ac..254b6868c5e 100644 --- a/src/wrap/FilterTypes.ts +++ b/src/wrap/transforms/FilterTypes.ts @@ -1,7 +1,7 @@ import { GraphQLSchema, GraphQLNamedType } from 'graphql'; -import { mapSchema } from '../utils'; -import { Transform, MapperKind } from '../Interfaces'; +import { mapSchema } from '../../utils'; +import { Transform, MapperKind } from '../../Interfaces'; export default class FilterTypes implements Transform { private readonly filter: (type: GraphQLNamedType) => boolean; diff --git a/src/wrap/HoistField.ts b/src/wrap/transforms/HoistField.ts similarity index 88% rename from src/wrap/HoistField.ts rename to src/wrap/transforms/HoistField.ts index 12ea8e94455..50147b5ed6a 100644 --- a/src/wrap/HoistField.ts +++ b/src/wrap/transforms/HoistField.ts @@ -1,9 +1,9 @@ import { GraphQLSchema, GraphQLObjectType, getNullableType } from 'graphql'; -import { healSchema, wrapFieldNode, renameFieldNode } from '../utils'; -import { createMergedResolver } from '../stitch'; -import { appendFields, removeFields } from '../utils/fields'; -import { Transform, Request } from '../Interfaces'; +import { healSchema, wrapFieldNode, renameFieldNode } from '../../utils'; +import { createMergedResolver } from '../../stitch'; +import { appendFields, removeFields } from '../../utils/fields'; +import { Transform, Request } from '../../Interfaces'; import MapFields from './MapFields'; diff --git a/src/wrap/MapFields.ts b/src/wrap/transforms/MapFields.ts similarity index 93% rename from src/wrap/MapFields.ts rename to src/wrap/transforms/MapFields.ts index 8c4f0239a38..80cc0f0780e 100644 --- a/src/wrap/MapFields.ts +++ b/src/wrap/transforms/MapFields.ts @@ -5,8 +5,8 @@ import { FragmentDefinitionNode, } from 'graphql'; -import { Transform, Request } from '../Interfaces'; -import { toConfig } from '../polyfills'; +import { Transform, Request } from '../../Interfaces'; +import { toConfig } from '../../polyfills'; import TransformObjectFields from './TransformObjectFields'; diff --git a/src/wrap/RenameObjectFields.ts b/src/wrap/transforms/RenameObjectFields.ts similarity index 93% rename from src/wrap/RenameObjectFields.ts rename to src/wrap/transforms/RenameObjectFields.ts index 08e8eed03c6..1ca613cbab2 100644 --- a/src/wrap/RenameObjectFields.ts +++ b/src/wrap/transforms/RenameObjectFields.ts @@ -1,6 +1,6 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; -import { Transform, Request } from '../Interfaces'; +import { Transform, Request } from '../../Interfaces'; import TransformObjectFields from './TransformObjectFields'; diff --git a/src/wrap/RenameRootFields.ts b/src/wrap/transforms/RenameRootFields.ts similarity index 94% rename from src/wrap/RenameRootFields.ts rename to src/wrap/transforms/RenameRootFields.ts index c8a14e8ccca..1a9f5bfe468 100644 --- a/src/wrap/RenameRootFields.ts +++ b/src/wrap/transforms/RenameRootFields.ts @@ -1,6 +1,6 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; -import { Transform, Request } from '../Interfaces'; +import { Transform, Request } from '../../Interfaces'; import TransformRootFields from './TransformRootFields'; diff --git a/src/wrap/RenameRootTypes.ts b/src/wrap/transforms/RenameRootTypes.ts similarity index 93% rename from src/wrap/RenameRootTypes.ts rename to src/wrap/transforms/RenameRootTypes.ts index 61c6b1b966e..e37ab541402 100644 --- a/src/wrap/RenameRootTypes.ts +++ b/src/wrap/transforms/RenameRootTypes.ts @@ -6,9 +6,9 @@ import { GraphQLObjectType, } from 'graphql'; -import { Request, Result, MapperKind, Transform } from '../Interfaces'; -import { mapSchema } from '../utils'; -import { toConfig } from '../polyfills'; +import { Request, Result, MapperKind, Transform } from '../../Interfaces'; +import { mapSchema } from '../../utils'; +import { toConfig } from '../../polyfills'; export default class RenameRootTypes implements Transform { private readonly renamer: (name: string) => string | undefined; diff --git a/src/wrap/RenameTypes.ts b/src/wrap/transforms/RenameTypes.ts similarity index 95% rename from src/wrap/RenameTypes.ts rename to src/wrap/transforms/RenameTypes.ts index 680948a1b98..36b1dee08d9 100644 --- a/src/wrap/RenameTypes.ts +++ b/src/wrap/transforms/RenameTypes.ts @@ -18,9 +18,9 @@ import { visit, } from 'graphql'; -import { isSpecifiedScalarType, toConfig } from '../polyfills'; -import { Transform, Request, Result, MapperKind } from '../Interfaces'; -import { mapSchema } from '../utils'; +import { isSpecifiedScalarType, toConfig } from '../../polyfills'; +import { Transform, Request, Result, MapperKind } from '../../Interfaces'; +import { mapSchema } from '../../utils'; export type RenameOptions = { renameBuiltins: boolean; diff --git a/src/wrap/ReplaceFieldWithFragment.ts b/src/wrap/transforms/ReplaceFieldWithFragment.ts similarity index 97% rename from src/wrap/ReplaceFieldWithFragment.ts rename to src/wrap/transforms/ReplaceFieldWithFragment.ts index 00bb657f078..e8fcab537d6 100644 --- a/src/wrap/ReplaceFieldWithFragment.ts +++ b/src/wrap/transforms/ReplaceFieldWithFragment.ts @@ -12,8 +12,8 @@ import { visitWithTypeInfo, } from 'graphql'; -import { concatInlineFragments } from '../utils'; -import { Transform, Request } from '../Interfaces'; +import { concatInlineFragments } from '../../utils'; +import { Transform, Request } from '../../Interfaces'; export default class ReplaceFieldWithFragment implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/wrap/TransformObjectFields.ts b/src/wrap/transforms/TransformObjectFields.ts similarity index 96% rename from src/wrap/TransformObjectFields.ts rename to src/wrap/transforms/TransformObjectFields.ts index 77f4c3f1192..72c274aa28d 100644 --- a/src/wrap/TransformObjectFields.ts +++ b/src/wrap/transforms/TransformObjectFields.ts @@ -15,10 +15,10 @@ import { FragmentDefinitionNode, } from 'graphql'; -import isEmptyObject from '../utils/isEmptyObject'; -import { Transform, Request, MapperKind } from '../Interfaces'; -import { mapSchema } from '../utils'; -import { toConfig } from '../polyfills'; +import isEmptyObject from '../../utils/isEmptyObject'; +import { Transform, Request, MapperKind } from '../../Interfaces'; +import { mapSchema } from '../../utils'; +import { toConfig } from '../../polyfills'; export type ObjectFieldTransformer = ( typeName: string, diff --git a/src/wrap/TransformQuery.ts b/src/wrap/transforms/TransformQuery.ts similarity index 98% rename from src/wrap/TransformQuery.ts rename to src/wrap/transforms/TransformQuery.ts index 6144b1d7a61..6204355f800 100644 --- a/src/wrap/TransformQuery.ts +++ b/src/wrap/transforms/TransformQuery.ts @@ -6,7 +6,7 @@ import { GraphQLError, } from 'graphql'; -import { Transform, Request, Result } from '../Interfaces'; +import { Transform, Request, Result } from '../../Interfaces'; export type QueryTransformer = ( selectionSet: SelectionSetNode, diff --git a/src/wrap/TransformRootFields.ts b/src/wrap/transforms/TransformRootFields.ts similarity index 96% rename from src/wrap/TransformRootFields.ts rename to src/wrap/transforms/TransformRootFields.ts index 219f4c3e650..94426b4f40c 100644 --- a/src/wrap/TransformRootFields.ts +++ b/src/wrap/transforms/TransformRootFields.ts @@ -1,6 +1,6 @@ import { GraphQLSchema, GraphQLField, GraphQLFieldConfig } from 'graphql'; -import { Transform, Request } from '../Interfaces'; +import { Transform, Request } from '../../Interfaces'; import TransformObjectFields, { FieldNodeTransformer, diff --git a/src/wrap/WrapFields.ts b/src/wrap/transforms/WrapFields.ts similarity index 96% rename from src/wrap/WrapFields.ts rename to src/wrap/transforms/WrapFields.ts index d116ddb676d..0d85004e2ab 100644 --- a/src/wrap/WrapFields.ts +++ b/src/wrap/transforms/WrapFields.ts @@ -1,13 +1,13 @@ import { GraphQLSchema, GraphQLObjectType } from 'graphql'; -import { Transform, Request } from '../Interfaces'; +import { Transform, Request } from '../../Interfaces'; import { hoistFieldNodes, healSchema, appendFields, removeFields, -} from '../utils'; -import { defaultMergedResolver, createMergedResolver } from '../stitch'; +} from '../../utils'; +import { defaultMergedResolver, createMergedResolver } from '../../stitch'; import MapFields from './MapFields'; diff --git a/src/wrap/WrapQuery.ts b/src/wrap/transforms/WrapQuery.ts similarity index 97% rename from src/wrap/WrapQuery.ts rename to src/wrap/transforms/WrapQuery.ts index 68021949e36..8ddacfa4143 100644 --- a/src/wrap/WrapQuery.ts +++ b/src/wrap/transforms/WrapQuery.ts @@ -6,7 +6,7 @@ import { SelectionSetNode, } from 'graphql'; -import { Transform, Request, Result } from '../Interfaces'; +import { Transform, Request, Result } from '../../Interfaces'; export type QueryWrapper = ( subtree: SelectionSetNode, diff --git a/src/wrap/WrapType.ts b/src/wrap/transforms/WrapType.ts similarity index 92% rename from src/wrap/WrapType.ts rename to src/wrap/transforms/WrapType.ts index 51794c08797..ebd7c9f0430 100644 --- a/src/wrap/WrapType.ts +++ b/src/wrap/transforms/WrapType.ts @@ -1,6 +1,6 @@ import { GraphQLSchema } from 'graphql'; -import { Transform, Request } from '../Interfaces'; +import { Transform, Request } from '../../Interfaces'; import WrapFields from './WrapFields'; diff --git a/src/wrap/transforms/index.ts b/src/wrap/transforms/index.ts new file mode 100644 index 00000000000..1e4c85368e8 --- /dev/null +++ b/src/wrap/transforms/index.ts @@ -0,0 +1,33 @@ +export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErrors'; +export { default as ExpandAbstractTypes } from './ExpandAbstractTypes'; +export { default as AddReplacementSelectionSets } from './AddReplacementSelectionSets'; +export { default as AddMergedTypeSelectionSets } from './AddMergedTypeSelectionSets'; +export { default as FilterToSchema } from './FilterToSchema'; +export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; + +export { default as RenameTypes } from './RenameTypes'; +export { default as RenameRootTypes } from './RenameRootTypes'; +export { default as FilterTypes } from './FilterTypes'; +export { default as TransformRootFields } from './TransformRootFields'; +export { default as RenameRootFields } from './RenameRootFields'; +export { default as FilterRootFields } from './FilterRootFields'; +export { default as TransformObjectFields } from './TransformObjectFields'; +export { default as RenameObjectFields } from './RenameObjectFields'; +export { default as FilterObjectFields } from './FilterObjectFields'; +export { default as TransformQuery } from './TransformQuery'; + +export { default as ExtendSchema } from './ExtendSchema'; +export { default as WrapType } from './WrapType'; +export { default as WrapFields } from './WrapFields'; +export { default as HoistField } from './HoistField'; +export { default as MapFields } from './MapFields'; + +// implemented directly within initial query proxy creation +export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; +// superseded by AddReplacementFragments +export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; +// superseded by AddReplacementSelectionSets +export { default as AddReplacementFragments } from './AddReplacementFragments'; +// superseded by TransformQuery +export { default as WrapQuery } from './WrapQuery'; +export { default as ExtractField } from './ExtractField'; From 41e427101dd0fd8cf7e2dbc1944ed6c3dc77989f Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 23 Mar 2020 15:42:45 -0400 Subject: [PATCH 241/250] refactor(imports): do not import from folder --- src/delegate/createRequest.ts | 2 +- src/delegate/delegateToSchema.ts | 2 +- src/generate/addResolversToSchema.ts | 4 ++-- src/generate/assertResolversPresent.ts | 2 +- src/generate/extendResolversFromInterfaces.ts | 2 +- src/generate/extensionDefinitions.ts | 2 +- src/generate/makeExecutableSchema.ts | 6 +++++- src/mock/index.ts | 4 ++-- src/polyfills/extendSchema.ts | 2 +- src/stitch/mergeInfo.ts | 4 ++-- src/stitch/mergeSchemas.ts | 11 +++++++---- src/stitch/proxiedResult.ts | 2 +- src/stitch/typeFromAST.ts | 2 +- src/test/testAlternateMergeSchemas.ts | 12 ++++++------ src/test/testDataloader.ts | 6 +++--- src/test/testDirectives.ts | 4 ++-- src/test/testErrors.ts | 4 ++-- src/test/testGatsbyTransforms.ts | 8 ++++---- src/test/testLogger.ts | 2 +- src/test/testMakeRemoteExecutableSchema.ts | 2 +- src/test/testMapSchema.ts | 2 +- src/test/testMergeSchemas.ts | 10 +++++----- src/test/testMocking.ts | 4 ++-- src/test/testSchemaGenerator.ts | 4 ++-- src/test/testTransforms.ts | 15 +++++++++------ src/test/testUpload.ts | 8 ++++---- src/test/testUtils.ts | 6 +++--- src/test/testingSchemas.ts | 4 ++-- src/utils/clone.ts | 2 +- src/utils/fields.ts | 2 +- src/utils/filterSchema.ts | 2 +- src/utils/getResolversFromSchema.ts | 2 +- src/utils/heal.ts | 2 +- src/utils/map.ts | 4 ++-- src/wrap/makeRemoteExecutableSchema.ts | 6 +++--- src/wrap/transformSchema.ts | 4 ++-- src/wrap/transforms.ts | 2 +- src/wrap/transforms/AddArgumentsAsVariables.ts | 2 +- src/wrap/transforms/AddReplacementFragments.ts | 6 +++++- src/wrap/transforms/ExtendSchema.ts | 11 ++++++++--- src/wrap/transforms/FilterTypes.ts | 2 +- src/wrap/transforms/HoistField.ts | 4 ++-- src/wrap/transforms/MapFields.ts | 2 +- src/wrap/transforms/RenameRootTypes.ts | 4 ++-- src/wrap/transforms/RenameTypes.ts | 4 ++-- src/wrap/transforms/ReplaceFieldWithFragment.ts | 2 +- src/wrap/transforms/TransformObjectFields.ts | 4 ++-- src/wrap/transforms/WrapFields.ts | 7 +++++-- 48 files changed, 116 insertions(+), 94 deletions(-) diff --git a/src/delegate/createRequest.ts b/src/delegate/createRequest.ts index 7f855733b87..ef3daaf09fc 100644 --- a/src/delegate/createRequest.ts +++ b/src/delegate/createRequest.ts @@ -28,7 +28,7 @@ import { SubschemaConfig, isSubschemaConfig, } from '../Interfaces'; -import { serializeInputValue } from '../utils'; +import { serializeInputValue } from '../utils/index'; export function getDelegatingOperation( parentType: GraphQLObjectType, diff --git a/src/delegate/delegateToSchema.ts b/src/delegate/delegateToSchema.ts index eaff8687a73..cedc334b3f3 100644 --- a/src/delegate/delegateToSchema.ts +++ b/src/delegate/delegateToSchema.ts @@ -30,7 +30,7 @@ import { CheckResultAndHandleErrors, applyRequestTransforms, applyResultTransforms, -} from '../wrap'; +} from '../wrap/index'; import linkToFetcher from '../stitch/linkToFetcher'; import { observableToAsyncIterable } from '../stitch/observableToAsyncIterable'; diff --git a/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index 7e7a4019e8f..13416d6255c 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -21,8 +21,8 @@ import { healSchema, forEachField, forEachDefaultValue, -} from '../utils'; -import { toConfig } from '../polyfills'; +} from '../utils/index'; +import { toConfig } from '../polyfills/index'; import SchemaError from './SchemaError'; import checkForResolveTypeResolver from './checkForResolveTypeResolver'; diff --git a/src/generate/assertResolversPresent.ts b/src/generate/assertResolversPresent.ts index f3701c48ab5..3d579260a9d 100644 --- a/src/generate/assertResolversPresent.ts +++ b/src/generate/assertResolversPresent.ts @@ -6,7 +6,7 @@ import { } from 'graphql'; import { IResolverValidationOptions } from '../Interfaces'; -import { forEachField } from '../utils'; +import { forEachField } from '../utils/index'; import SchemaError from './SchemaError'; diff --git a/src/generate/extendResolversFromInterfaces.ts b/src/generate/extendResolversFromInterfaces.ts index b0047c27db3..d9222bcf65d 100644 --- a/src/generate/extendResolversFromInterfaces.ts +++ b/src/generate/extendResolversFromInterfaces.ts @@ -6,7 +6,7 @@ import { } from 'graphql'; import { IResolvers } from '../Interfaces'; -import { graphqlVersion } from '../utils'; +import { graphqlVersion } from '../utils/index'; function extendResolversFromInterfaces( schema: GraphQLSchema, diff --git a/src/generate/extensionDefinitions.ts b/src/generate/extensionDefinitions.ts index d491589aa14..5adce4a7288 100644 --- a/src/generate/extensionDefinitions.ts +++ b/src/generate/extensionDefinitions.ts @@ -1,6 +1,6 @@ import { DocumentNode, DefinitionNode, Kind } from 'graphql'; -import { graphqlVersion } from '../utils'; +import { graphqlVersion } from '../utils/index'; export function extractExtensionDefinitions(ast: DocumentNode) { const extensionDefs = ast.definitions.filter( diff --git a/src/generate/makeExecutableSchema.ts b/src/generate/makeExecutableSchema.ts index 924c480bf07..a389305c25c 100644 --- a/src/generate/makeExecutableSchema.ts +++ b/src/generate/makeExecutableSchema.ts @@ -5,7 +5,11 @@ import { } from 'graphql'; import { IExecutableSchemaDefinition, ILogger } from '../Interfaces'; -import { SchemaDirectiveVisitor, forEachField, mergeDeep } from '../utils'; +import { + SchemaDirectiveVisitor, + forEachField, + mergeDeep, +} from '../utils/index'; import attachDirectiveResolvers from './attachDirectiveResolvers'; import assertResolversPresent from './assertResolversPresent'; diff --git a/src/mock/index.ts b/src/mock/index.ts index 152fb3be174..c7e3cfab1fc 100644 --- a/src/mock/index.ts +++ b/src/mock/index.ts @@ -21,8 +21,8 @@ import { } from 'graphql'; import { v4 as uuid } from 'uuid'; -import { buildSchemaFromTypeDefinitions } from '../generate'; -import { forEachField } from '../utils'; +import { buildSchemaFromTypeDefinitions } from '../generate/index'; +import { forEachField } from '../utils/index'; import { IMocks, diff --git a/src/polyfills/extendSchema.ts b/src/polyfills/extendSchema.ts index 58f767f336e..f5c48ae6cbb 100644 --- a/src/polyfills/extendSchema.ts +++ b/src/polyfills/extendSchema.ts @@ -4,7 +4,7 @@ import { extendSchema as graphqlExtendSchema, } from 'graphql'; -import { getResolversFromSchema } from '../utils'; +import { getResolversFromSchema } from '../utils/index'; import { IResolverOptions } from '../Interfaces'; // polyfill for graphql < v14.2 which does not support subscriptions diff --git a/src/stitch/mergeInfo.ts b/src/stitch/mergeInfo.ts index df355bc7792..18431607426 100644 --- a/src/stitch/mergeInfo.ts +++ b/src/stitch/mergeInfo.ts @@ -20,13 +20,13 @@ import { MergedTypeInfo, Transform, } from '../Interfaces'; -import { ExpandAbstractTypes, AddReplacementFragments } from '../wrap'; +import { ExpandAbstractTypes, AddReplacementFragments } from '../wrap/index'; import { parseFragmentToInlineFragment, concatInlineFragments, typeContainsSelectionSet, parseSelectionSet, -} from '../utils'; +} from '../utils/index'; import delegateToSchema from '../delegate/delegateToSchema'; diff --git a/src/stitch/mergeSchemas.ts b/src/stitch/mergeSchemas.ts index 539234aaf62..713b927a4d4 100644 --- a/src/stitch/mergeSchemas.ts +++ b/src/stitch/mergeSchemas.ts @@ -29,8 +29,11 @@ import { IResolvers, SubschemaConfig, } from '../Interfaces'; -import { extractExtensionDefinitions, addResolversToSchema } from '../generate'; -import { wrapSchema } from '../wrap'; +import { + extractExtensionDefinitions, + addResolversToSchema, +} from '../generate/index'; +import { wrapSchema } from '../wrap/index'; import { SchemaDirectiveVisitor, cloneDirective, @@ -38,8 +41,8 @@ import { forEachField, mergeDeep, graphqlVersion, -} from '../utils'; -import { toConfig, extendSchema } from '../polyfills'; +} from '../utils/index'; +import { toConfig, extendSchema } from '../polyfills/index'; import typeFromAST from './typeFromAST'; import { createMergeInfo, completeMergeInfo } from './mergeInfo'; diff --git a/src/stitch/proxiedResult.ts b/src/stitch/proxiedResult.ts index 36945a37b2a..c0424f0a0d8 100644 --- a/src/stitch/proxiedResult.ts +++ b/src/stitch/proxiedResult.ts @@ -1,7 +1,7 @@ import { GraphQLError, GraphQLSchema, responsePathAsArray } from 'graphql'; import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces'; -import { mergeDeep } from '../utils'; +import { mergeDeep } from '../utils/index'; import { handleNull } from '../delegate/checkResultAndHandleErrors'; import { relocatedError } from './errors'; diff --git a/src/stitch/typeFromAST.ts b/src/stitch/typeFromAST.ts index 56f1ac992c4..56566dcd659 100644 --- a/src/stitch/typeFromAST.ts +++ b/src/stitch/typeFromAST.ts @@ -31,7 +31,7 @@ import { TokenKind, } from 'graphql'; -import { createNamedStub, graphqlVersion } from '../utils'; +import { createNamedStub, graphqlVersion } from '../utils/index'; import resolveFromParentTypename from './resolveFromParentTypename'; diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 98d48696d62..8a7745b930e 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -26,12 +26,12 @@ import { HoistField, FilterRootFields, FilterObjectFields, -} from '../wrap'; -import { isSpecifiedScalarType, toConfig } from '../polyfills'; +} from '../wrap/index'; +import { isSpecifiedScalarType, toConfig } from '../polyfills/index'; -import { delegateToSchema } from '../delegate'; -import { makeExecutableSchema } from '../generate'; -import { mergeSchemas, createMergedResolver } from '../stitch'; +import { delegateToSchema } from '../delegate/index'; +import { makeExecutableSchema } from '../generate/index'; +import { mergeSchemas, createMergedResolver } from '../stitch/index'; import { SubschemaConfig } from '../Interfaces'; import { filterSchema, @@ -39,7 +39,7 @@ import { renameFieldNode, hoistFieldNodes, graphqlVersion, -} from '../utils'; +} from '../utils/index'; import { propertySchema, diff --git a/src/test/testDataloader.ts b/src/test/testDataloader.ts index 593c56803fd..307f79a6f49 100644 --- a/src/test/testDataloader.ts +++ b/src/test/testDataloader.ts @@ -2,9 +2,9 @@ import DataLoader from 'dataloader'; import { graphql, GraphQLList } from 'graphql'; import { expect } from 'chai'; -import { delegateToSchema } from '../delegate'; -import { makeExecutableSchema } from '../generate'; -import { mergeSchemas } from '../stitch'; +import { delegateToSchema } from '../delegate/index'; +import { makeExecutableSchema } from '../generate/index'; +import { mergeSchemas } from '../stitch/index'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; describe('dataloader', () => { diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 56268e688ff..98e1a2ada56 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -29,14 +29,14 @@ import { import { assert } from 'chai'; import formatDate from 'dateformat'; -import { makeExecutableSchema } from '../generate'; +import { makeExecutableSchema } from '../generate/index'; import { VisitableSchemaType } from '../Interfaces'; import { SchemaDirectiveVisitor, SchemaVisitor, visitSchema, graphqlVersion, -} from '../utils'; +} from '../utils/index'; const typeDefs = ` directive @schemaDirective(role: String) on SCHEMA diff --git a/src/test/testErrors.ts b/src/test/testErrors.ts index 5dcf9b8052d..c43e77dc3ae 100644 --- a/src/test/testErrors.ts +++ b/src/test/testErrors.ts @@ -4,8 +4,8 @@ import { GraphQLError, graphql } from 'graphql'; import { relocatedError } from '../stitch/errors'; import { getErrors, ERROR_SYMBOL } from '../stitch/proxiedResult'; import { checkResultAndHandleErrors } from '../delegate/checkResultAndHandleErrors'; -import { makeExecutableSchema } from '../generate'; -import { mergeSchemas } from '../stitch'; +import { makeExecutableSchema } from '../generate/index'; +import { mergeSchemas } from '../stitch/index'; import { IGraphQLToolsResolveInfo } from '../Interfaces'; class ErrorWithExtensions extends GraphQLError { diff --git a/src/test/testGatsbyTransforms.ts b/src/test/testGatsbyTransforms.ts index ca7f7cbb9bc..a4a0e714e14 100644 --- a/src/test/testGatsbyTransforms.ts +++ b/src/test/testGatsbyTransforms.ts @@ -8,10 +8,10 @@ import { } from 'graphql'; import { VisitSchemaKind } from '../Interfaces'; -import { transformSchema, RenameTypes } from '../wrap'; -import { cloneType, healSchema, visitSchema } from '../utils'; -import { makeExecutableSchema } from '../generate'; -import { addMocksToSchema } from '../mock'; +import { transformSchema, RenameTypes } from '../wrap/index'; +import { cloneType, healSchema, visitSchema } from '../utils/index'; +import { makeExecutableSchema } from '../generate/index'; +import { addMocksToSchema } from '../mock/index'; // see https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/transforms.js // and https://github.com/gatsbyjs/gatsby/issues/22128 diff --git a/src/test/testLogger.ts b/src/test/testLogger.ts index c2873336e03..a4c09f0e50c 100644 --- a/src/test/testLogger.ts +++ b/src/test/testLogger.ts @@ -1,7 +1,7 @@ import { assert } from 'chai'; import { graphql } from 'graphql'; -import { makeExecutableSchema } from '../generate'; +import { makeExecutableSchema } from '../generate/index'; import { Logger } from '../generate/Logger'; describe('Logger', () => { diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index 2b0c8d6be3b..b6f0b3e6447 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -8,7 +8,7 @@ import { graphql, } from 'graphql'; -import { makeRemoteExecutableSchema } from '../wrap'; +import { makeRemoteExecutableSchema } from '../wrap/index'; import { propertySchema, subscriptionSchema, diff --git a/src/test/testMapSchema.ts b/src/test/testMapSchema.ts index 96970f50210..81ab8b558f1 100644 --- a/src/test/testMapSchema.ts +++ b/src/test/testMapSchema.ts @@ -3,7 +3,7 @@ import { GraphQLObjectType, GraphQLSchema, graphqlSync } from 'graphql'; import { makeExecutableSchema, mapSchema } from '../index'; import { MapperKind } from '../Interfaces'; -import { toConfig } from '../polyfills'; +import { toConfig } from '../polyfills/index'; describe('mapSchema', () => { it('does not throw', () => { diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index b62636eab1c..ea5cd21f78c 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -14,17 +14,17 @@ import { printSchema, } from 'graphql'; -import { delegateToSchema } from '../delegate'; -import { makeExecutableSchema } from '../generate'; +import { delegateToSchema } from '../delegate/index'; +import { makeExecutableSchema } from '../generate/index'; import { IResolvers, SubschemaConfig } from '../Interfaces'; -import { mergeSchemas } from '../stitch'; +import { mergeSchemas } from '../stitch/index'; import { cloneSchema, getResolversFromSchema, graphqlVersion, SchemaDirectiveVisitor, -} from '../utils'; -import { addMocksToSchema } from '../mock'; +} from '../utils/index'; +import { addMocksToSchema } from '../mock/index'; import { propertySchema as localPropertySchema, diff --git a/src/test/testMocking.ts b/src/test/testMocking.ts index 3f9a6942fcb..777206480f7 100644 --- a/src/test/testMocking.ts +++ b/src/test/testMocking.ts @@ -6,12 +6,12 @@ import { GraphQLFieldResolver, } from 'graphql'; -import { addMocksToSchema, MockList, mockServer } from '../mock'; +import { addMocksToSchema, MockList, mockServer } from '../mock/index'; import { buildSchemaFromTypeDefinitions, addResolversToSchema, makeExecutableSchema, -} from '../generate'; +} from '../generate/index'; import { IMocks } from '../Interfaces'; describe('Mock', () => { diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index f29061e11bc..2bc885ae3f0 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -32,7 +32,7 @@ import { chainResolvers, concatenateTypeDefs, addResolversToSchema, -} from '../generate'; +} from '../generate/index'; import { IResolverValidationOptions, IResolvers, @@ -42,7 +42,7 @@ import { ITypeDefinitions, ILogger, } from '../Interfaces'; -import { visitSchema, graphqlVersion } from '../utils'; +import { visitSchema, graphqlVersion } from '../utils/index'; import TypeA from './circularSchemaA'; diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 50d36e8f3a8..488424fc886 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -10,9 +10,9 @@ import { parse, } from 'graphql'; -import { delegateToSchema } from '../delegate'; -import { makeExecutableSchema } from '../generate'; -import { defaultMergedResolver, mergeSchemas } from '../stitch'; +import { delegateToSchema } from '../delegate/index'; +import { makeExecutableSchema } from '../generate/index'; +import { defaultMergedResolver, mergeSchemas } from '../stitch/index'; import { transformSchema, RenameTypes, @@ -24,9 +24,12 @@ import { FilterToSchema, TransformQuery, AddReplacementFragments, -} from '../wrap'; -import { concatInlineFragments, parseFragmentToInlineFragment } from '../utils'; -import { addMocksToSchema } from '../mock'; +} from '../wrap/index'; +import { + concatInlineFragments, + parseFragmentToInlineFragment, +} from '../utils/index'; +import { addMocksToSchema } from '../mock/index'; import { propertySchema, bookingSchema } from './testingSchemas'; diff --git a/src/test/testUpload.ts b/src/test/testUpload.ts index f81ea47b06a..2f7c3776e51 100644 --- a/src/test/testUpload.ts +++ b/src/test/testUpload.ts @@ -10,10 +10,10 @@ import FormData from 'form-data'; import fetch from 'node-fetch'; import { buildSchema } from 'graphql'; -import { mergeSchemas } from '../stitch'; -import { makeExecutableSchema } from '../generate'; -import { createServerHttpLink } from '../links'; -import { GraphQLUpload as ServerGraphQLUpload } from '../scalars'; +import { mergeSchemas } from '../stitch/index'; +import { makeExecutableSchema } from '../generate/index'; +import { createServerHttpLink } from '../links/index'; +import { GraphQLUpload as ServerGraphQLUpload } from '../scalars/index'; import { SubschemaConfig } from '../Interfaces'; function streamToString(stream: Readable) { diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 629d5291735..27d36a84aac 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -1,9 +1,9 @@ import { expect } from 'chai'; import { GraphQLObjectType } from 'graphql'; -import { healSchema } from '../utils'; -import { toConfig } from '../polyfills'; -import { makeExecutableSchema } from '../generate'; +import { healSchema } from '../utils/index'; +import { toConfig } from '../polyfills/index'; +import { makeExecutableSchema } from '../generate/index'; describe('heal', () => { it('should prune empty types', () => { diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index 0d2896505df..d263cfa893c 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -20,8 +20,8 @@ import { forAwaitEach } from 'iterall'; import introspectSchema from '../stitch/introspectSchema'; import { IResolvers, Fetcher, SubschemaConfig } from '../Interfaces'; -import { makeExecutableSchema } from '../generate'; -import { graphqlVersion } from '../utils'; +import { makeExecutableSchema } from '../generate/index'; +import { graphqlVersion } from '../utils/index'; export type Location = { name: string; diff --git a/src/utils/clone.ts b/src/utils/clone.ts index 69aceaa4756..3154d692e6b 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -17,7 +17,7 @@ import { isScalarType, } from 'graphql'; -import { isSpecifiedScalarType, toConfig } from '../polyfills'; +import { isSpecifiedScalarType, toConfig } from '../polyfills/index'; import { graphqlVersion } from './graphqlVersion'; import { mapSchema } from './map'; diff --git a/src/utils/fields.ts b/src/utils/fields.ts index ca22b1b884b..4d946c85e67 100644 --- a/src/utils/fields.ts +++ b/src/utils/fields.ts @@ -5,7 +5,7 @@ import { } from 'graphql'; import { TypeMap } from 'graphql/type/schema'; -import { toConfig } from '../polyfills'; +import { toConfig } from '../polyfills/index'; export function appendFields( typeMap: TypeMap, diff --git a/src/utils/filterSchema.ts b/src/utils/filterSchema.ts index 9b80050c9f3..d23ff3ebba8 100644 --- a/src/utils/filterSchema.ts +++ b/src/utils/filterSchema.ts @@ -9,7 +9,7 @@ import { } from 'graphql'; import { GraphQLSchemaWithTransforms, MapperKind } from '../Interfaces'; -import { toConfig } from '../polyfills'; +import { toConfig } from '../polyfills/index'; import { mapSchema } from './map'; diff --git a/src/utils/getResolversFromSchema.ts b/src/utils/getResolversFromSchema.ts index 471a2c7424c..73b45fcc025 100644 --- a/src/utils/getResolversFromSchema.ts +++ b/src/utils/getResolversFromSchema.ts @@ -8,7 +8,7 @@ import { } from 'graphql'; import { IResolvers } from '../Interfaces'; -import { isSpecifiedScalarType } from '../polyfills'; +import { isSpecifiedScalarType } from '../polyfills/index'; import { cloneType } from './clone'; diff --git a/src/utils/heal.ts b/src/utils/heal.ts index c09aa57a75b..c9b18b60414 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -21,7 +21,7 @@ import { isNonNullType, } from 'graphql'; -import { toConfig } from '../polyfills'; +import { toConfig } from '../polyfills/index'; import each from './each'; import updateEachKey from './updateEachKey'; diff --git a/src/utils/map.ts b/src/utils/map.ts index 6e1ada6b65b..2b4f72872a3 100644 --- a/src/utils/map.ts +++ b/src/utils/map.ts @@ -28,8 +28,8 @@ import { GraphQLObjectTypeConfig, } from 'graphql'; -import { toConfig, isSpecifiedScalarType } from '../polyfills'; -import { graphqlVersion } from '../utils'; +import { toConfig, isSpecifiedScalarType } from '../polyfills/index'; +import { graphqlVersion } from '../utils/index'; import { SchemaMapper, MapperKind, diff --git a/src/wrap/makeRemoteExecutableSchema.ts b/src/wrap/makeRemoteExecutableSchema.ts index 7f1ce08f86a..84af5626e71 100644 --- a/src/wrap/makeRemoteExecutableSchema.ts +++ b/src/wrap/makeRemoteExecutableSchema.ts @@ -8,10 +8,10 @@ import { DocumentNode, } from 'graphql'; -import { addResolversToSchema } from '../generate'; +import { addResolversToSchema } from '../generate/index'; import { Fetcher, Operation } from '../Interfaces'; -import { cloneSchema } from '../utils'; -import { buildSchema } from '../polyfills'; +import { cloneSchema } from '../utils/index'; +import { buildSchema } from '../polyfills/index'; import { addTypenameToAbstract } from '../delegate/addTypenameToAbstract'; import { checkResultAndHandleErrors } from '../delegate/checkResultAndHandleErrors'; diff --git a/src/wrap/transformSchema.ts b/src/wrap/transformSchema.ts index cb4085ac171..315234d6215 100644 --- a/src/wrap/transformSchema.ts +++ b/src/wrap/transformSchema.ts @@ -1,6 +1,6 @@ import { GraphQLSchema } from 'graphql'; -import { addResolversToSchema } from '../generate'; +import { addResolversToSchema } from '../generate/index'; import { generateProxyingResolvers, stripResolvers } from '../stitch/resolvers'; import { Transform, @@ -8,7 +8,7 @@ import { isSubschemaConfig, GraphQLSchemaWithTransforms, } from '../Interfaces'; -import { cloneSchema } from '../utils'; +import { cloneSchema } from '../utils/index'; import { applySchemaTransforms } from './transforms'; diff --git a/src/wrap/transforms.ts b/src/wrap/transforms.ts index 3c8bfcab920..354a1fc555d 100644 --- a/src/wrap/transforms.ts +++ b/src/wrap/transforms.ts @@ -1,7 +1,7 @@ import { GraphQLSchema } from 'graphql'; import { Request, Transform } from '../Interfaces'; -import { cloneSchema } from '../utils'; +import { cloneSchema } from '../utils/index'; export function applySchemaTransforms( originalSchema: GraphQLSchema, diff --git a/src/wrap/transforms/AddArgumentsAsVariables.ts b/src/wrap/transforms/AddArgumentsAsVariables.ts index ce63aa5b4a9..fdfeb68e876 100644 --- a/src/wrap/transforms/AddArgumentsAsVariables.ts +++ b/src/wrap/transforms/AddArgumentsAsVariables.ts @@ -17,7 +17,7 @@ import { } from 'graphql'; import { Transform, Request } from '../../Interfaces'; -import { serializeInputValue } from '../../utils'; +import { serializeInputValue } from '../../utils/index'; export default class AddArgumentsAsVariablesTransform implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/wrap/transforms/AddReplacementFragments.ts b/src/wrap/transforms/AddReplacementFragments.ts index 05ce122ea26..f134691a1ac 100644 --- a/src/wrap/transforms/AddReplacementFragments.ts +++ b/src/wrap/transforms/AddReplacementFragments.ts @@ -9,7 +9,11 @@ import { visitWithTypeInfo, } from 'graphql'; -import { Transform, Request, ReplacementFragmentMapping } from '../../Interfaces'; +import { + Transform, + Request, + ReplacementFragmentMapping, +} from '../../Interfaces'; export default class AddReplacementFragments implements Transform { private readonly targetSchema: GraphQLSchema; diff --git a/src/wrap/transforms/ExtendSchema.ts b/src/wrap/transforms/ExtendSchema.ts index f430f3ce095..7c6b7a6f052 100644 --- a/src/wrap/transforms/ExtendSchema.ts +++ b/src/wrap/transforms/ExtendSchema.ts @@ -1,8 +1,13 @@ import { GraphQLSchema, extendSchema, parse } from 'graphql'; -import { Transform, IFieldResolver, IResolvers, Request } from '../../Interfaces'; -import { addResolversToSchema } from '../../generate'; -import { defaultMergedResolver } from '../../stitch'; +import { + Transform, + IFieldResolver, + IResolvers, + Request, +} from '../../Interfaces'; +import { addResolversToSchema } from '../../generate/index'; +import { defaultMergedResolver } from '../../stitch/index'; import MapFields, { FieldNodeTransformerMap } from './MapFields'; diff --git a/src/wrap/transforms/FilterTypes.ts b/src/wrap/transforms/FilterTypes.ts index 254b6868c5e..da8def470f8 100644 --- a/src/wrap/transforms/FilterTypes.ts +++ b/src/wrap/transforms/FilterTypes.ts @@ -1,6 +1,6 @@ import { GraphQLSchema, GraphQLNamedType } from 'graphql'; -import { mapSchema } from '../../utils'; +import { mapSchema } from '../../utils/index'; import { Transform, MapperKind } from '../../Interfaces'; export default class FilterTypes implements Transform { diff --git a/src/wrap/transforms/HoistField.ts b/src/wrap/transforms/HoistField.ts index 50147b5ed6a..f290e7fdd41 100644 --- a/src/wrap/transforms/HoistField.ts +++ b/src/wrap/transforms/HoistField.ts @@ -1,7 +1,7 @@ import { GraphQLSchema, GraphQLObjectType, getNullableType } from 'graphql'; -import { healSchema, wrapFieldNode, renameFieldNode } from '../../utils'; -import { createMergedResolver } from '../../stitch'; +import { healSchema, wrapFieldNode, renameFieldNode } from '../../utils/index'; +import { createMergedResolver } from '../../stitch/index'; import { appendFields, removeFields } from '../../utils/fields'; import { Transform, Request } from '../../Interfaces'; diff --git a/src/wrap/transforms/MapFields.ts b/src/wrap/transforms/MapFields.ts index 80cc0f0780e..7d204c70f55 100644 --- a/src/wrap/transforms/MapFields.ts +++ b/src/wrap/transforms/MapFields.ts @@ -6,7 +6,7 @@ import { } from 'graphql'; import { Transform, Request } from '../../Interfaces'; -import { toConfig } from '../../polyfills'; +import { toConfig } from '../../polyfills/index'; import TransformObjectFields from './TransformObjectFields'; diff --git a/src/wrap/transforms/RenameRootTypes.ts b/src/wrap/transforms/RenameRootTypes.ts index e37ab541402..377e449d721 100644 --- a/src/wrap/transforms/RenameRootTypes.ts +++ b/src/wrap/transforms/RenameRootTypes.ts @@ -7,8 +7,8 @@ import { } from 'graphql'; import { Request, Result, MapperKind, Transform } from '../../Interfaces'; -import { mapSchema } from '../../utils'; -import { toConfig } from '../../polyfills'; +import { mapSchema } from '../../utils/index'; +import { toConfig } from '../../polyfills/index'; export default class RenameRootTypes implements Transform { private readonly renamer: (name: string) => string | undefined; diff --git a/src/wrap/transforms/RenameTypes.ts b/src/wrap/transforms/RenameTypes.ts index 36b1dee08d9..92e1e2cf1c3 100644 --- a/src/wrap/transforms/RenameTypes.ts +++ b/src/wrap/transforms/RenameTypes.ts @@ -18,9 +18,9 @@ import { visit, } from 'graphql'; -import { isSpecifiedScalarType, toConfig } from '../../polyfills'; +import { isSpecifiedScalarType, toConfig } from '../../polyfills/index'; import { Transform, Request, Result, MapperKind } from '../../Interfaces'; -import { mapSchema } from '../../utils'; +import { mapSchema } from '../../utils/index'; export type RenameOptions = { renameBuiltins: boolean; diff --git a/src/wrap/transforms/ReplaceFieldWithFragment.ts b/src/wrap/transforms/ReplaceFieldWithFragment.ts index e8fcab537d6..f395a0edb3f 100644 --- a/src/wrap/transforms/ReplaceFieldWithFragment.ts +++ b/src/wrap/transforms/ReplaceFieldWithFragment.ts @@ -12,7 +12,7 @@ import { visitWithTypeInfo, } from 'graphql'; -import { concatInlineFragments } from '../../utils'; +import { concatInlineFragments } from '../../utils/index'; import { Transform, Request } from '../../Interfaces'; export default class ReplaceFieldWithFragment implements Transform { diff --git a/src/wrap/transforms/TransformObjectFields.ts b/src/wrap/transforms/TransformObjectFields.ts index 72c274aa28d..00850b428e0 100644 --- a/src/wrap/transforms/TransformObjectFields.ts +++ b/src/wrap/transforms/TransformObjectFields.ts @@ -17,8 +17,8 @@ import { import isEmptyObject from '../../utils/isEmptyObject'; import { Transform, Request, MapperKind } from '../../Interfaces'; -import { mapSchema } from '../../utils'; -import { toConfig } from '../../polyfills'; +import { mapSchema } from '../../utils/index'; +import { toConfig } from '../../polyfills/index'; export type ObjectFieldTransformer = ( typeName: string, diff --git a/src/wrap/transforms/WrapFields.ts b/src/wrap/transforms/WrapFields.ts index 0d85004e2ab..fc516fc6c82 100644 --- a/src/wrap/transforms/WrapFields.ts +++ b/src/wrap/transforms/WrapFields.ts @@ -6,8 +6,11 @@ import { healSchema, appendFields, removeFields, -} from '../../utils'; -import { defaultMergedResolver, createMergedResolver } from '../../stitch'; +} from '../../utils/index'; +import { + defaultMergedResolver, + createMergedResolver, +} from '../../stitch/index'; import MapFields from './MapFields'; From e8b68e72c579c2501115b07509de5ec4812dacec Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 23 Mar 2020 15:56:42 -0400 Subject: [PATCH 242/250] refactor(wrap): move wrapping resolvers move wrapping resolvers to wrap folder, was placed in old stitching folder because makeRemoteExecutableSchema was in stitching, but makeRemoteExecutableSchema now belongs in the wrap folder, an older method besides wrapSchema/transformSchema of wrapping a schema, specifically for remote schema. --- src/wrap/makeRemoteExecutableSchema.ts | 3 ++- src/{stitch => wrap}/resolvers.ts | 6 +++--- src/wrap/transformSchema.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) rename src/{stitch => wrap}/resolvers.ts (94%) diff --git a/src/wrap/makeRemoteExecutableSchema.ts b/src/wrap/makeRemoteExecutableSchema.ts index 84af5626e71..9691bc51705 100644 --- a/src/wrap/makeRemoteExecutableSchema.ts +++ b/src/wrap/makeRemoteExecutableSchema.ts @@ -18,7 +18,8 @@ import { checkResultAndHandleErrors } from '../delegate/checkResultAndHandleErro import linkToFetcher, { execute } from '../stitch/linkToFetcher'; import { observableToAsyncIterable } from '../stitch/observableToAsyncIterable'; import mapAsyncIterator from '../stitch/mapAsyncIterator'; -import { stripResolvers, generateProxyingResolvers } from '../stitch/resolvers'; + +import { stripResolvers, generateProxyingResolvers } from './resolvers'; export type ResolverFn = ( rootValue?: any, diff --git a/src/stitch/resolvers.ts b/src/wrap/resolvers.ts similarity index 94% rename from src/stitch/resolvers.ts rename to src/wrap/resolvers.ts index 920abdf3d94..28730ab353b 100644 --- a/src/stitch/resolvers.ts +++ b/src/wrap/resolvers.ts @@ -13,9 +13,9 @@ import { import delegateToSchema from '../delegate/delegateToSchema'; import { handleResult } from '../delegate/checkResultAndHandleErrors'; -import { makeMergedType } from './makeMergedType'; -import { getResponseKeyFromInfo } from './getResponseKeyFromInfo'; -import { getErrors, getSubschema } from './proxiedResult'; +import { makeMergedType } from '../stitch/makeMergedType'; +import { getResponseKeyFromInfo } from '../stitch/getResponseKeyFromInfo'; +import { getErrors, getSubschema } from '../stitch/proxiedResult'; export type Mapping = { [typeName: string]: { diff --git a/src/wrap/transformSchema.ts b/src/wrap/transformSchema.ts index 315234d6215..51c9c962d4e 100644 --- a/src/wrap/transformSchema.ts +++ b/src/wrap/transformSchema.ts @@ -1,7 +1,6 @@ import { GraphQLSchema } from 'graphql'; import { addResolversToSchema } from '../generate/index'; -import { generateProxyingResolvers, stripResolvers } from '../stitch/resolvers'; import { Transform, SubschemaConfig, @@ -10,6 +9,7 @@ import { } from '../Interfaces'; import { cloneSchema } from '../utils/index'; +import { generateProxyingResolvers, stripResolvers } from './resolvers'; import { applySchemaTransforms } from './transforms'; export function wrapSchema( From 1f0c77f5519bda7ec23da2e67d33da53fd6fdc74 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Mon, 23 Mar 2020 18:39:52 -0400 Subject: [PATCH 243/250] chore(deps): upgrade deps and run prettier --- package.json | 26 ++--- src/delegate/checkResultAndHandleErrors.ts | 18 ++-- src/delegate/createRequest.ts | 4 +- src/delegate/delegateToSchema.ts | 2 +- src/generate/addResolversToSchema.ts | 20 ++-- src/generate/addSchemaLevelResolver.ts | 8 +- src/generate/attachConnectorsToContext.ts | 2 +- src/generate/attachDirectiveResolvers.ts | 2 +- src/generate/checkForResolveTypeResolver.ts | 2 +- src/generate/concatenateTypeDefs.ts | 2 +- src/generate/extendResolversFromInterfaces.ts | 4 +- src/generate/makeExecutableSchema.ts | 2 +- src/links/createServerHttpLink.ts | 22 ++--- src/mock/index.ts | 10 +- src/polyfills/extendSchema.ts | 2 +- src/polyfills/toConfig.ts | 10 +- src/scalars/GraphQLUpload.ts | 6 +- src/stitch/errors.ts | 4 +- src/stitch/introspectSchema.ts | 2 +- src/stitch/makeMergedType.ts | 4 +- src/stitch/mapAsyncIterator.ts | 2 +- src/stitch/mergeFields.ts | 12 +-- src/stitch/mergeInfo.ts | 24 ++--- src/stitch/mergeSchemas.ts | 26 ++--- src/stitch/observableToAsyncIterable.ts | 4 +- src/stitch/proxiedResult.ts | 8 +- src/stitch/typeFromAST.ts | 22 +++-- src/test/testAlternateMergeSchemas.ts | 32 +++--- src/test/testDelegateToSchema.ts | 4 +- src/test/testDirectives.ts | 50 +++++----- src/test/testFragmentsAreNotDuplicated.ts | 2 +- src/test/testGatsbyTransforms.ts | 2 +- src/test/testMakeRemoteExecutableSchema.ts | 10 +- src/test/testMapSchema.ts | 4 +- src/test/testMergeSchemas.ts | 26 ++--- src/test/testMocking.ts | 98 +++++++++---------- src/test/testResolution.ts | 8 +- src/test/testSchemaGenerator.ts | 98 +++++++++---------- src/test/testStitchingFromSubschemas.ts | 2 +- src/test/testTransforms.ts | 24 ++--- src/test/testTypeMerging.ts | 4 +- src/test/testUpload.ts | 4 +- src/test/testingSchemas.ts | 20 ++-- src/utils/SchemaDirectiveVisitor.ts | 12 +-- src/utils/each.ts | 2 +- src/utils/fieldNodes.ts | 4 +- src/utils/fields.ts | 6 +- src/utils/filterSchema.ts | 4 +- src/utils/forEachDefaultValue.ts | 8 +- src/utils/forEachField.ts | 4 +- src/utils/getResolversFromSchema.ts | 6 +- src/utils/heal.ts | 18 ++-- src/utils/map.ts | 26 ++--- src/utils/mergeDeep.ts | 2 +- src/utils/transformInputValue.ts | 2 +- src/utils/updateEachKey.ts | 4 +- src/utils/valueFromASTUntyped.ts | 2 +- src/utils/visitSchema.ts | 14 +-- src/wrap/makeRemoteExecutableSchema.ts | 6 +- src/wrap/resolvers.ts | 8 +- .../transforms/AddArgumentsAsVariables.ts | 8 +- .../transforms/AddReplacementFragments.ts | 2 +- .../transforms/AddReplacementSelectionSets.ts | 2 +- src/wrap/transforms/ExpandAbstractTypes.ts | 24 ++--- src/wrap/transforms/FilterToSchema.ts | 14 +-- src/wrap/transforms/HoistField.ts | 4 +- src/wrap/transforms/RenameRootTypes.ts | 4 +- src/wrap/transforms/RenameTypes.ts | 2 +- .../transforms/ReplaceFieldWithFragment.ts | 2 +- src/wrap/transforms/TransformObjectFields.ts | 8 +- src/wrap/transforms/TransformQuery.ts | 8 +- src/wrap/transforms/WrapFields.ts | 2 +- 72 files changed, 426 insertions(+), 420 deletions(-) diff --git a/package.json b/package.json index 4f6e927c0f0..4fb407616ae 100644 --- a/package.json +++ b/package.json @@ -58,32 +58,32 @@ "form-data": "^3.0.0", "iterall": "^1.3.0", "node-fetch": "^2.6.0", - "uuid": "^7.0.1" + "uuid": "^7.0.2" }, "peerDependencies": { "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0-rc" }, "devDependencies": { - "@types/chai": "^4.2.9", + "@types/chai": "^4.2.11", "@types/dateformat": "^3.0.1", - "@types/express": "^4.17.2", + "@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.1", - "@types/node": "^13.7.6", + "@types/mocha": "^7.0.2", + "@types/node": "^13.9.3", "@types/node-fetch": "^2.5.5", - "@types/uuid": "^7.0.0", - "@typescript-eslint/eslint-plugin": "^2.21.0", - "@typescript-eslint/parser": "^2.21.0", + "@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.9", + "coveralls": "^3.0.11", "dataloader": "^2.0.0", "dateformat": "^3.0.3", "eslint": "^6.8.0", - "eslint-plugin-import": "2.20.0", + "eslint-plugin-import": "2.20.1", "eslint-watch": "^6.0.1", "express": "^4.17.1", "express-graphql": "^0.9.0", @@ -91,13 +91,13 @@ "graphql-subscriptions": "^1.1.0", "graphql-type-json": "^0.3.1", "graphql-upload": "^10.0.0", - "mocha": "^7.1.0", + "mocha": "^7.1.1", "nyc": "^15.0.0", - "prettier": "^1.19.1", + "prettier": "^2.0.2", "rimraf": "^3.0.2", "source-map-support": "^0.5.16", "standard-version": "^7.1.0", - "typescript": "^3.8.2", + "typescript": "^3.8.3", "zen-observable-ts": "^0.8.20" } } diff --git a/src/delegate/checkResultAndHandleErrors.ts b/src/delegate/checkResultAndHandleErrors.ts index 808f9dd4c8d..99c2e9e06b7 100644 --- a/src/delegate/checkResultAndHandleErrors.ts +++ b/src/delegate/checkResultAndHandleErrors.ts @@ -177,7 +177,7 @@ export function handleObject( ) { setErrors( object, - errors.map(error => + errors.map((error) => relocatedError( error, error.nodes, @@ -206,7 +206,7 @@ export function handleObject( return object; } - targetSubschemas = targetSubschemas.filter(s => s !== subschema); + targetSubschemas = targetSubschemas.filter((s) => s !== subschema); if (!targetSubschemas.length) { return object; } @@ -235,7 +235,7 @@ export function handleObject( function collectSubFields(info: IGraphQLToolsResolveInfo, typeName: string) { let subFieldNodes: Record> = Object.create(null); const visitedFragmentNames = Object.create(null); - info.fieldNodes.forEach(fieldNode => { + info.fieldNodes.forEach((fieldNode) => { subFieldNodes = collectFields( ({ schema: info.schema, @@ -263,8 +263,8 @@ function getFieldsNotInSubschema( const fields = (typeMap[typeName] as GraphQLObjectType).getFields(); const fieldsNotInSchema: Array = []; - Object.keys(subFieldNodes).forEach(responseName => { - subFieldNodes[responseName].forEach(subFieldNode => { + Object.keys(subFieldNodes).forEach((responseName) => { + subFieldNodes[responseName].forEach((subFieldNode) => { if (!fields[subFieldNode.name.value]) { fieldsNotInSchema.push(subFieldNode); } @@ -280,13 +280,13 @@ export function handleNull( errors: ReadonlyArray, ) { if (errors.length) { - if (errors.some(error => !error.path || error.path.length < 2)) { + 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')) { + } else if (errors.some((error) => typeof error.path[1] === 'string')) { const childErrors = getErrorsByPathSegment(errors); const result = Object.create(null); - Object.keys(childErrors).forEach(pathSegment => { + Object.keys(childErrors).forEach((pathSegment) => { result[pathSegment] = handleNull( fieldNodes, [...path, pathSegment], @@ -300,7 +300,7 @@ export function handleNull( const childErrors = getErrorsByPathSegment(errors); const result: Array = []; - Object.keys(childErrors).forEach(pathSegment => { + Object.keys(childErrors).forEach((pathSegment) => { result.push( handleNull( fieldNodes, diff --git a/src/delegate/createRequest.ts b/src/delegate/createRequest.ts index ef3daaf09fc..db0eb17a452 100644 --- a/src/delegate/createRequest.ts +++ b/src/delegate/createRequest.ts @@ -160,7 +160,7 @@ export function createRequest( const fragmentDefinitions: Array = Object.keys( fragments, - ).map(fragmentName => fragments[fragmentName]); + ).map((fragmentName) => fragments[fragmentName]); const document = { kind: Kind.DOCUMENT, @@ -254,7 +254,7 @@ function updateArguments( }); return { - arguments: Object.keys(updatedArgs).map(argName => updatedArgs[argName]), + arguments: Object.keys(updatedArgs).map((argName) => updatedArgs[argName]), variableDefinitions: newVariableDefinitions, variableValues, }; diff --git a/src/delegate/delegateToSchema.ts b/src/delegate/delegateToSchema.ts index cedc334b3f3..6dadeacd382 100644 --- a/src/delegate/delegateToSchema.ts +++ b/src/delegate/delegateToSchema.ts @@ -235,7 +235,7 @@ export function delegateRequest({ // "subscribe" to the subscription result and map the result through the transforms return mapAsyncIterator( subscriptionResult, - result => { + (result) => { const transformedResult = applyResultTransforms( result, delegationTransforms, diff --git a/src/generate/addResolversToSchema.ts b/src/generate/addResolversToSchema.ts index 13416d6255c..b61a516c947 100644 --- a/src/generate/addResolversToSchema.ts +++ b/src/generate/addResolversToSchema.ts @@ -60,13 +60,15 @@ function addResolversToSchema( const typeMap = schema.getTypeMap(); - Object.keys(resolvers).forEach(typeName => { + 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.`, + `"${typeName}" defined in resolvers, but has invalid value "${ + resolverValue as string + }". A resolver's value must be of type object or function.`, ); } @@ -84,7 +86,7 @@ function addResolversToSchema( if (isScalarType(type)) { // Support -- without recommending -- overriding default scalar types - Object.keys(resolverValue).forEach(fieldName => { + Object.keys(resolverValue).forEach((fieldName) => { if (fieldName.startsWith('__')) { type[fieldName.substring(2)] = resolverValue[fieldName]; } else { @@ -95,7 +97,7 @@ function addResolversToSchema( // 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 => { + Object.keys(resolverValue).forEach((fieldName) => { if (!type.getValue(fieldName)) { if (allowResolversNotInSchema) { return; @@ -110,7 +112,7 @@ function addResolversToSchema( const values = type.getValues(); const newValues = {}; - values.forEach(value => { + values.forEach((value) => { const newValue = Object.keys(resolverValue).includes(value.name) ? resolverValue[value.name] : value.name; @@ -128,7 +130,7 @@ function addResolversToSchema( values: newValues, }); } else if (isUnionType(type)) { - Object.keys(resolverValue).forEach(fieldName => { + 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]; @@ -143,7 +145,7 @@ function addResolversToSchema( ); }); } else if (isObjectType(type) || isInterfaceType(type)) { - Object.keys(resolverValue).forEach(fieldName => { + 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]; @@ -189,7 +191,7 @@ function addResolversToSchema( forEachDefaultValue(schema, parseInputValue); if (defaultFieldResolver != null) { - forEachField(schema, field => { + forEachField(schema, (field) => { if (!field.resolve) { field.resolve = defaultFieldResolver; } @@ -203,7 +205,7 @@ function setFieldProperties( field: GraphQLField, propertiesObj: Record, ) { - Object.keys(propertiesObj).forEach(propertyName => { + Object.keys(propertiesObj).forEach((propertyName) => { field[propertyName] = propertiesObj[propertyName]; }); } diff --git a/src/generate/addSchemaLevelResolver.ts b/src/generate/addSchemaLevelResolver.ts index 23a366e2312..b77bb5033e4 100644 --- a/src/generate/addSchemaLevelResolver.ts +++ b/src/generate/addSchemaLevelResolver.ts @@ -15,14 +15,14 @@ function addSchemaLevelResolver( schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType(), - ].filter(x => Boolean(x)); - rootTypes.forEach(type => { + ].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 => { + 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()) { @@ -47,7 +47,7 @@ function wrapResolver( outerResolver: GraphQLFieldResolver, ): GraphQLFieldResolver { return (obj, args, ctx, info) => - Promise.resolve(outerResolver(obj, args, ctx, info)).then(root => { + Promise.resolve(outerResolver(obj, args, ctx, info)).then((root) => { if (innerResolver != null) { return innerResolver(root, args, ctx, info); } diff --git a/src/generate/attachConnectorsToContext.ts b/src/generate/attachConnectorsToContext.ts index 60d3363d738..668f1b6c8e8 100644 --- a/src/generate/attachConnectorsToContext.ts +++ b/src/generate/attachConnectorsToContext.ts @@ -56,7 +56,7 @@ const attachConnectorsToContext = deprecated( if (typeof ctx.connectors === 'undefined') { ctx.connectors = {}; } - Object.keys(connectors).forEach(connectorName => { + Object.keys(connectors).forEach((connectorName) => { const connector: IConnector = connectors[connectorName]; if (connector.prototype != null) { ctx.connectors[connectorName] = new (connector as IConnectorCls)(ctx); diff --git a/src/generate/attachDirectiveResolvers.ts b/src/generate/attachDirectiveResolvers.ts index 8d9bd7c695c..fbe6ae73c15 100644 --- a/src/generate/attachDirectiveResolvers.ts +++ b/src/generate/attachDirectiveResolvers.ts @@ -21,7 +21,7 @@ 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]; diff --git a/src/generate/checkForResolveTypeResolver.ts b/src/generate/checkForResolveTypeResolver.ts index b643d59de6a..cf6f15758b3 100644 --- a/src/generate/checkForResolveTypeResolver.ts +++ b/src/generate/checkForResolveTypeResolver.ts @@ -13,7 +13,7 @@ function checkForResolveTypeResolver( requireResolversForResolveType?: boolean, ) { Object.keys(schema.getTypeMap()) - .map(typeName => schema.getType(typeName)) + .map((typeName) => schema.getType(typeName)) .forEach((type: GraphQLUnionType | GraphQLInterfaceType) => { if (!isAbstractType(type)) { return; diff --git a/src/generate/concatenateTypeDefs.ts b/src/generate/concatenateTypeDefs.ts index 207398edeb9..31863b5f738 100644 --- a/src/generate/concatenateTypeDefs.ts +++ b/src/generate/concatenateTypeDefs.ts @@ -28,7 +28,7 @@ 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 { diff --git a/src/generate/extendResolversFromInterfaces.ts b/src/generate/extendResolversFromInterfaces.ts index d9222bcf65d..20665eb4318 100644 --- a/src/generate/extendResolversFromInterfaces.ts +++ b/src/generate/extendResolversFromInterfaces.ts @@ -18,7 +18,7 @@ function extendResolversFromInterfaces( }); const extendedResolvers: IResolvers = {}; - typeNames.forEach(typeName => { + typeNames.forEach((typeName) => { const typeResolvers = resolvers[typeName]; const type = schema.getType(typeName); if ( @@ -27,7 +27,7 @@ function extendResolversFromInterfaces( ) { const interfaceResolvers = (type as GraphQLObjectType) .getInterfaces() - .map(iFace => resolvers[iFace.name]); + .map((iFace) => resolvers[iFace.name]); extendedResolvers[typeName] = Object.assign( {}, ...interfaceResolvers, diff --git a/src/generate/makeExecutableSchema.ts b/src/generate/makeExecutableSchema.ts index a389305c25c..e8188e37795 100644 --- a/src/generate/makeExecutableSchema.ts +++ b/src/generate/makeExecutableSchema.ts @@ -46,7 +46,7 @@ export function makeExecutableSchema({ // 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') + .filter((resolverObj) => typeof resolverObj === 'object') .reduce(mergeDeep, {}) : resolvers; diff --git a/src/links/createServerHttpLink.ts b/src/links/createServerHttpLink.ts index 3b85c676e25..f1272a4758a 100644 --- a/src/links/createServerHttpLink.ts +++ b/src/links/createServerHttpLink.ts @@ -116,7 +116,7 @@ export const createServerHttpLink = (linkOptions: Options = {}) => { headers: requestOptions.headers, }; - return new ApolloLink(operation => { + return new ApolloLink((operation) => { let chosenURI = selectURI(operation, uri); const context = operation.getContext(); @@ -183,9 +183,9 @@ export const createServerHttpLink = (linkOptions: Options = {}) => { chosenURI = newURI; } - return new Observable(observer => { + return new Observable((observer) => { getFinalPromise(body) - .then(resolvedBody => { + .then((resolvedBody) => { if (options.method !== 'GET') { options.body = customSerializer(resolvedBody, customAppendFile); if (options.body instanceof FormData) { @@ -195,19 +195,19 @@ export const createServerHttpLink = (linkOptions: Options = {}) => { } return options; }) - .then(newOptions => customFetch(chosenURI, newOptions)) - .then(response => { + .then((newOptions) => customFetch(chosenURI, newOptions)) + .then((response) => { operation.setContext({ response }); return response; }) .then(parseAndCheckHttpResponse(operation)) - .then(result => { + .then((result) => { // we have data and can send it to back up the link chain observer.next(result); observer.complete(); return result; }) - .catch(err => { + .catch((err) => { // fetch was cancelled so it's already been cleaned up in the unsubscribe if (err.name === 'AbortError') { return; @@ -322,18 +322,18 @@ function rewriteURIForGET(chosenURI: string, body: Body) { } function getFinalPromise(object: any): Promise { - return Promise.resolve(object).then(resolvedObject => { + return Promise.resolve(object).then((resolvedObject) => { if (resolvedObject == null) { return resolvedObject; } if (Array.isArray(resolvedObject)) { - return Promise.all(resolvedObject.map(o => getFinalPromise(o))); + 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 => { + keys.map((key) => getFinalPromise(resolvedObject[key])), + ).then((awaitedValues) => { for (let i = 0; i < keys.length; i++) { resolvedObject[keys[i]] = awaitedValues[i]; } diff --git a/src/mock/index.ts b/src/mock/index.ts index c7e3cfab1fc..e87e47a4c82 100644 --- a/src/mock/index.ts +++ b/src/mock/index.ts @@ -81,7 +81,7 @@ function addMocksToSchema({ // 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]); }); @@ -91,7 +91,7 @@ function addMocksToSchema({ } }); - const mockType = function( + const mockType = function ( type: GraphQLType, _typeName?: string, fieldName?: string, @@ -263,7 +263,7 @@ function addMocksToSchema({ 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 @@ -312,7 +312,7 @@ function copyOwnPropsIfNotPresent( target: Record, source: Record, ) { - Object.getOwnPropertyNames(source).forEach(prop => { + Object.getOwnPropertyNames(source).forEach((prop) => { if (!Object.getOwnPropertyDescriptor(target, prop)) { const propertyDescriptor = Object.getOwnPropertyDescriptor(source, prop); Object.defineProperty( @@ -328,7 +328,7 @@ function copyOwnProps( target: Record, ...sources: Array> ) { - sources.forEach(source => { + sources.forEach((source) => { let chain = source; while (chain != null) { copyOwnPropsIfNotPresent(target, chain); diff --git a/src/polyfills/extendSchema.ts b/src/polyfills/extendSchema.ts index f5c48ae6cbb..89a67aae6cc 100644 --- a/src/polyfills/extendSchema.ts +++ b/src/polyfills/extendSchema.ts @@ -28,7 +28,7 @@ export function extendSchema( const extendedSchema = graphqlExtendSchema(schema, extension, options); const fields = extendedSchema.getSubscriptionType().getFields(); - Object.keys(subscriptionResolvers).forEach(fieldName => { + Object.keys(subscriptionResolvers).forEach((fieldName) => { fields[fieldName].subscribe = subscriptionResolvers[fieldName].subscribe; }); diff --git a/src/polyfills/toConfig.ts b/src/polyfills/toConfig.ts index 295a22a113f..741744fe583 100644 --- a/src/polyfills/toConfig.ts +++ b/src/polyfills/toConfig.ts @@ -50,7 +50,7 @@ export function schemaToConfig(schema: GraphQLSchema): GraphQLSchemaConfig { const newTypes: Array = []; const types = schema.getTypeMap(); - Object.keys(types).forEach(typeName => { + Object.keys(types).forEach((typeName) => { newTypes.push(types[typeName]); }); @@ -264,7 +264,7 @@ export function enumTypeToConfig(type: GraphQLEnumType): GraphQLEnumTypeConfig { const newValues = {}; - type.getValues().forEach(value => { + type.getValues().forEach((value) => { newValues[value.name] = { description: value.description, value: value.value, @@ -350,7 +350,7 @@ export function inputFieldMapToConfig( fields: GraphQLInputFieldMap, ): GraphQLInputFieldConfigMap { const newFields = {}; - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; newFields[fieldName] = toConfig(field); }); @@ -396,7 +396,7 @@ export function fieldMapToConfig( ): GraphQLFieldConfigMap { const newFields = {}; - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; newFields[fieldName] = toConfig(field); }); @@ -423,7 +423,7 @@ export function argumentMapToConfig( args: ReadonlyArray, ): GraphQLFieldConfigArgumentMap { const newArguments = {}; - args.forEach(arg => { + args.forEach((arg) => { newArguments[arg.name] = argumentToConfig(arg); }); diff --git a/src/scalars/GraphQLUpload.ts b/src/scalars/GraphQLUpload.ts index 62493f4bac4..a903244057b 100644 --- a/src/scalars/GraphQLUpload.ts +++ b/src/scalars/GraphQLUpload.ts @@ -3,7 +3,7 @@ import { GraphQLScalarType, GraphQLError } from 'graphql'; const GraphQLUpload = new GraphQLScalarType({ name: 'Upload', description: 'The `Upload` scalar type represents a file upload.', - parseValue: value => { + parseValue: (value) => { if (value != null && value.promise instanceof Promise) { // graphql-upload v10 return value.promise; @@ -14,8 +14,8 @@ const GraphQLUpload = new GraphQLScalarType({ throw new GraphQLError('Upload value invalid.'); }, // serialization requires to support schema stitching - serialize: value => value, - parseLiteral: ast => { + serialize: (value) => value, + parseLiteral: (ast) => { throw new GraphQLError('Upload literal unsupported.', ast); }, }); diff --git a/src/stitch/errors.ts b/src/stitch/errors.ts index 89adc5756a0..3076d7b0487 100644 --- a/src/stitch/errors.ts +++ b/src/stitch/errors.ts @@ -52,7 +52,7 @@ export function getErrorsByPathSegment( errors: ReadonlyArray, ): Record> { const record = Object.create(null); - errors.forEach(error => { + errors.forEach((error) => { if (!error.path || error.path.length < 2) { return; } @@ -91,7 +91,7 @@ export function combineErrors( } return new CombinedError( - errors.map(error => error.message).join('\n'), + errors.map((error) => error.message).join('\n'), errors, ); } diff --git a/src/stitch/introspectSchema.ts b/src/stitch/introspectSchema.ts index 06fd7c5d181..2bb3da4450d 100644 --- a/src/stitch/introspectSchema.ts +++ b/src/stitch/introspectSchema.ts @@ -26,7 +26,7 @@ export default function introspectSchema( return fetcher({ query: parsedIntrospectionQuery, context: linkContext, - }).then(introspectionResult => { + }).then((introspectionResult) => { if ( (Array.isArray(introspectionResult.errors) && introspectionResult.errors.length) || diff --git a/src/stitch/makeMergedType.ts b/src/stitch/makeMergedType.ts index 15d9657cf2e..7fd70acfc1b 100644 --- a/src/stitch/makeMergedType.ts +++ b/src/stitch/makeMergedType.ts @@ -8,11 +8,11 @@ export function makeMergedType(type: GraphQLType): void { type.isTypeOf = undefined; const fieldMap = type.getFields(); - Object.keys(fieldMap).forEach(fieldName => { + Object.keys(fieldMap).forEach((fieldName) => { fieldMap[fieldName].resolve = defaultMergedResolver; fieldMap[fieldName].subscribe = null; }); } else if (isAbstractType(type)) { - type.resolveType = parent => resolveFromParentTypename(parent); + type.resolveType = (parent) => resolveFromParentTypename(parent); } } diff --git a/src/stitch/mapAsyncIterator.ts b/src/stitch/mapAsyncIterator.ts index 5af28dc8d7b..34ecee64a91 100644 --- a/src/stitch/mapAsyncIterator.ts +++ b/src/stitch/mapAsyncIterator.ts @@ -59,7 +59,7 @@ function asyncMapValue( value: T, callback: (value: T) => Promise | U, ): Promise { - return new Promise(resolve => resolve(callback(value))); + return new Promise((resolve) => resolve(callback(value))); } function iteratorResult(value: T): IteratorResult { diff --git a/src/stitch/mergeFields.ts b/src/stitch/mergeFields.ts index f0e499256cc..1f3a616c6d4 100644 --- a/src/stitch/mergeFields.ts +++ b/src/stitch/mergeFields.ts @@ -25,9 +25,9 @@ function buildDelegationPlan( const proxiableSubschemas: Array = []; const nonProxiableSubschemas: Array = []; - targetSubschemas.forEach(t => { + targetSubschemas.forEach((t) => { if ( - sourceSubschemas.some(s => { + sourceSubschemas.some((s) => { const selectionSet = mergedTypeInfo.selectionSets.get(t); return mergedTypeInfo.containsSelectionSet.get(s).get(selectionSet); }) @@ -44,7 +44,7 @@ function buildDelegationPlan( // 2. for each selection: const delegationMap: Map> = new Map(); - originalSelections.forEach(selection => { + originalSelections.forEach((selection) => { // 2a. use uniqueFields map to assign fields to subschema if one of possible subschemas const uniqueSubschema: SubschemaConfig = uniqueFields[selection.name.value]; @@ -65,14 +65,14 @@ function buildDelegationPlan( let nonUniqueSubschemas: Array = nonUniqueFields[selection.name.value]; - nonUniqueSubschemas = nonUniqueSubschemas.filter(s => + nonUniqueSubschemas = nonUniqueSubschemas.filter((s) => proxiableSubschemas.includes(s), ); if (nonUniqueSubschemas != null) { const subschemas: Array = Array.from( delegationMap.keys(), ); - const existingSubschema = nonUniqueSubschemas.find(s => + const existingSubschema = nonUniqueSubschemas.find((s) => subschemas.includes(s), ); if (existingSubschema != null) { @@ -144,7 +144,7 @@ export function mergeFields( } return containsPromises - ? Promise.all(maybePromises).then(results => + ? Promise.all(maybePromises).then((results) => mergeFields( mergedTypeInfo, typeName, diff --git a/src/stitch/mergeInfo.ts b/src/stitch/mergeInfo.ts index 18431607426..dd83913ecab 100644 --- a/src/stitch/mergeInfo.ts +++ b/src/stitch/mergeInfo.ts @@ -99,10 +99,10 @@ function createMergedTypes( ): Record { const mergedTypes: Record = {}; - Object.keys(typeCandidates).forEach(typeName => { + Object.keys(typeCandidates).forEach((typeName) => { if (isObjectType(typeCandidates[typeName][0].type)) { const mergedTypeCandidates = typeCandidates[typeName].filter( - typeCandidate => + (typeCandidate) => typeCandidate.subschema != null && isSubschemaConfig(typeCandidate.subschema) && typeCandidate.subschema.merge != null && @@ -125,7 +125,7 @@ function createMergedTypes( const typeMaps: Map = new Map(); const selectionSets: Map = new Map(); - mergedTypeCandidates.forEach(typeCandidate => { + mergedTypeCandidates.forEach((typeCandidate) => { const subschemaConfig = typeCandidate.subschema as SubschemaConfig; const transformedSubschema = typeCandidate.transformedSubschema; typeMaps.set(subschemaConfig, transformedSubschema.getTypeMap()); @@ -133,7 +133,7 @@ function createMergedTypes( typeName, ) as GraphQLObjectType; const fieldMap = type.getFields(); - Object.keys(fieldMap).forEach(fieldName => { + Object.keys(fieldMap).forEach((fieldName) => { if (fields[fieldName] == null) { fields[fieldName] = []; } @@ -184,12 +184,12 @@ function createMergedTypes( nonUniqueFields: Object.create({}), }; - subschemas.forEach(subschema => { + subschemas.forEach((subschema) => { const type = typeMaps.get(subschema)[typeName] as GraphQLObjectType; const subschemaMap = new Map(); subschemas - .filter(s => s !== subschema) - .forEach(s => { + .filter((s) => s !== subschema) + .forEach((s) => { const selectionSet = selectionSets.get(s); if ( selectionSet != null && @@ -204,7 +204,7 @@ function createMergedTypes( ); }); - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const supportedBySubschemas = fields[fieldName]; if (supportedBySubschemas.length === 1) { mergedTypes[typeName].uniqueFields[fieldName] = @@ -233,12 +233,12 @@ export function completeMergeInfo( ): MergeInfo { const replacementSelectionSets = Object.create(null); - Object.keys(resolvers).forEach(typeName => { + Object.keys(resolvers).forEach((typeName) => { const type = resolvers[typeName]; if (isScalarType(type)) { return; } - Object.keys(type).forEach(fieldName => { + Object.keys(type).forEach((fieldName) => { const field = type[fieldName]; if (field.selectionSet) { const selectionSet = parseSelectionSet(field.selectionSet); @@ -280,8 +280,8 @@ export function completeMergeInfo( }); const replacementFragments = Object.create(null); - Object.keys(mapping).forEach(typeName => { - Object.keys(mapping[typeName]).forEach(field => { + Object.keys(mapping).forEach((typeName) => { + Object.keys(mapping[typeName]).forEach((field) => { if (replacementFragments[typeName] == null) { replacementFragments[typeName] = {}; } diff --git a/src/stitch/mergeSchemas.ts b/src/stitch/mergeSchemas.ts index 713b927a4d4..b8c6bd39457 100644 --- a/src/stitch/mergeSchemas.ts +++ b/src/stitch/mergeSchemas.ts @@ -108,7 +108,7 @@ export default function mergeSchemas({ } schemas = [...schemas, ...schemaLikeObjects]; - schemas.forEach(schemaLikeObject => { + schemas.forEach((schemaLikeObject) => { if (isSchema(schemaLikeObject) || isSubschemaConfig(schemaLikeObject)) { const schema = wrapSchema(schemaLikeObject); @@ -120,7 +120,7 @@ export default function mergeSchemas({ [subscriptionTypeName]: schema.getSubscriptionType(), }; - Object.keys(operationTypes).forEach(typeName => { + Object.keys(operationTypes).forEach((typeName) => { if (operationTypes[typeName] != null) { addTypeCandidate(typeCandidates, typeName, { schema, @@ -133,13 +133,13 @@ export default function mergeSchemas({ if (mergeDirectives) { const directiveInstances = schema.getDirectives(); - directiveInstances.forEach(directive => { + directiveInstances.forEach((directive) => { directives.push(directive); }); } const originalTypeMap = schema.getTypeMap(); - Object.keys(originalTypeMap).forEach(typeName => { + Object.keys(originalTypeMap).forEach((typeName) => { const type: GraphQLNamedType = originalTypeMap[typeName]; if ( isNamedType(type) && @@ -166,7 +166,7 @@ export default function mergeSchemas({ ? parse(schemaLikeObject) : (schemaLikeObject as DocumentNode); - parsedSchemaDocument.definitions.forEach(def => { + parsedSchemaDocument.definitions.forEach((def) => { const type = typeFromAST(def); if (isDirective(type) && mergeDirectives) { directives.push(type); @@ -184,7 +184,7 @@ export default function mergeSchemas({ extensions.push(extensionsDocument); } } else if (Array.isArray(schemaLikeObject)) { - schemaLikeObject.forEach(type => { + schemaLikeObject.forEach((type) => { addTypeCandidate(typeCandidates, type.name, { type, }); @@ -218,7 +218,7 @@ export default function mergeSchemas({ mergeInfo = completeMergeInfo(mergeInfo, finalResolvers); - Object.keys(typeCandidates).forEach(typeName => { + Object.keys(typeCandidates).forEach((typeName) => { if ( typeName === queryTypeName || typeName === mutationTypeName || @@ -246,13 +246,13 @@ export default function mergeSchemas({ query: typeMap[queryTypeName] as GraphQLObjectType, mutation: typeMap[mutationTypeName] as GraphQLObjectType, subscription: typeMap[subscriptionTypeName] as GraphQLObjectType, - types: Object.keys(typeMap).map(key => typeMap[key]), + types: Object.keys(typeMap).map((key) => typeMap[key]), directives: directives.length - ? directives.map(directive => cloneDirective(directive)) + ? directives.map((directive) => cloneDirective(directive)) : undefined, }); - extensions.forEach(extension => { + extensions.forEach((extension) => { mergedSchema = extendSchema(mergedSchema, extension, { commentDescriptions: true, }); @@ -264,7 +264,7 @@ export default function mergeSchemas({ inheritResolversFromInterfaces, }); - forEachField(mergedSchema, field => { + forEachField(mergedSchema, (field) => { if (field.resolve != null) { const fieldResolver = field.resolve; field.resolve = (parent, args, context, info) => { @@ -305,7 +305,7 @@ function addTypeCandidate( function onTypeConflictToCandidateSelector( onTypeConflict: OnTypeConflict, ): CandidateSelector { - return cands => + return (cands) => cands.reduce((prev, next) => { const type = onTypeConflict(prev.type, next.type, { left: { @@ -334,7 +334,7 @@ function merge( const initialCandidateType = candidates[0].type; if ( candidates.some( - candidate => + (candidate) => candidate.type.constructor !== initialCandidateType.constructor, ) ) { diff --git a/src/stitch/observableToAsyncIterable.ts b/src/stitch/observableToAsyncIterable.ts index 23466912389..f9098ae67a0 100644 --- a/src/stitch/observableToAsyncIterable.ts +++ b/src/stitch/observableToAsyncIterable.ts @@ -30,7 +30,7 @@ export function observableToAsyncIterable( }; const pullValue = () => - new Promise(resolve => { + new Promise((resolve) => { if (pushQueue.length !== 0) { const element = pushQueue.shift(); // either {value: {errors: [...]}} or {value: ...} @@ -56,7 +56,7 @@ export function observableToAsyncIterable( if (listening) { listening = false; subscription.unsubscribe(); - pullQueue.forEach(resolve => resolve({ value: undefined, done: true })); + pullQueue.forEach((resolve) => resolve({ value: undefined, done: true })); pullQueue.length = 0; pushQueue.length = 0; } diff --git a/src/stitch/proxiedResult.ts b/src/stitch/proxiedResult.ts index c0424f0a0d8..a1972731a41 100644 --- a/src/stitch/proxiedResult.ts +++ b/src/stitch/proxiedResult.ts @@ -90,7 +90,7 @@ export function unwrapResult( setErrors( object, - errors.map(error => + errors.map((error) => relocatedError( error, error.nodes, @@ -112,12 +112,12 @@ export function dehoistResult( ): any { const result = Object.create(null); - Object.keys(parent).forEach(alias => { + Object.keys(parent).forEach((alias) => { let obj = result; const fieldNames = alias.split(delimeter); const fieldName = fieldNames.pop(); - fieldNames.forEach(key => { + fieldNames.forEach((key) => { obj = obj[key] = obj[key] || Object.create(null); }); obj[fieldName] = parent[alias]; @@ -152,7 +152,7 @@ export function mergeProxiedResults(target: any, ...sources: any): any { const fieldSubschemaMap = sources.reduce( (acc: Record, source: any) => { const subschema = source[OBJECT_SUBSCHEMA_SYMBOL]; - Object.keys(source).forEach(key => { + Object.keys(source).forEach((key) => { acc[key] = subschema; }); return acc; diff --git a/src/stitch/typeFromAST.ts b/src/stitch/typeFromAST.ts index 56566dcd659..4a2e2e75f32 100644 --- a/src/stitch/typeFromAST.ts +++ b/src/stitch/typeFromAST.ts @@ -72,7 +72,7 @@ function makeObjectType(node: ObjectTypeDefinitionNode): GraphQLObjectType { fields: () => makeFields(node.fields), interfaces: () => node.interfaces.map( - iface => + (iface) => createNamedStub( iface.name.value, 'interface', @@ -93,7 +93,7 @@ function makeInterfaceType( graphqlVersion() >= 15 ? () => ((node as unknown) as ObjectTypeDefinitionNode).interfaces.map( - iface => + (iface) => createNamedStub( iface.name.value, 'interface', @@ -108,7 +108,7 @@ function makeInterfaceType( function makeEnumType(node: EnumTypeDefinitionNode): GraphQLEnumType { const values = {}; - node.values.forEach(value => { + node.values.forEach((value) => { values[value.name.value] = { description: getDescription(value, backcompatOptions), }; @@ -124,9 +124,11 @@ function makeUnionType(node: UnionTypeDefinitionNode): GraphQLUnionType { return new GraphQLUnionType({ name: node.name.value, types: () => - node.types.map(type => resolveType(type, 'object') as GraphQLObjectType), + node.types.map( + (type) => resolveType(type, 'object') as GraphQLObjectType, + ), description: getDescription(node, backcompatOptions), - resolveType: parent => resolveFromParentTypename(parent), + resolveType: (parent) => resolveFromParentTypename(parent), }); } @@ -158,16 +160,16 @@ function makeFields( nodes: ReadonlyArray, ): Record> { const result: Record> = {}; - nodes.forEach(node => { + nodes.forEach((node) => { const deprecatedDirective = node.directives.find( - directive => directive.name.value === 'deprecated', + (directive) => directive.name.value === 'deprecated', ); let deprecationReason; if (deprecatedDirective != null) { const deprecatedArgument = deprecatedDirective.arguments.find( - arg => arg.name.value === 'reason', + (arg) => arg.name.value === 'reason', ); deprecationReason = (deprecatedArgument.value as StringValueNode).value; } @@ -184,7 +186,7 @@ function makeFields( function makeValues(nodes: ReadonlyArray) { const result = {}; - nodes.forEach(node => { + nodes.forEach((node) => { const type = resolveType(node.type, 'input') as GraphQLInputType; result[node.name.value] = { type, @@ -211,7 +213,7 @@ function resolveType( function makeDirective(node: DirectiveDefinitionNode): GraphQLDirective { const locations: Array = []; - node.locations.forEach(location => { + node.locations.forEach((location) => { if (location.value in DirectiveLocation) { locations.push(location.value as DirectiveLocationEnum); } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 8a7745b930e..14336ad1eec 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -297,7 +297,7 @@ describe('merge schemas through transforms', () => { }); }); - it('local subscriptions should work even if root fields are renamed', done => { + it('local subscriptions should work even if root fields are renamed', (done) => { const originalNotification = { notifications: { text: 'Hello world', @@ -319,7 +319,7 @@ describe('merge schemas through transforms', () => { let notificationCnt = 0; subscribe(mergedSchema, subscription) - .then(results => { + .then((results) => { forAwaitEach( results as AsyncIterable, (result: ExecutionResult) => { @@ -861,9 +861,9 @@ describe('schema transformation with extraction of nested fields', () => { }, fieldNodeTransformerMap: { Property: { - locationName: fieldNode => + locationName: (fieldNode) => wrapFieldNode(renameFieldNode(fieldNode, 'name'), ['location']), - renamedError: fieldNode => renameFieldNode(fieldNode, 'error'), + renamedError: (fieldNode) => renameFieldNode(fieldNode, 'error'), }, }, }), @@ -1243,7 +1243,7 @@ describe('schema transformation with renaming of object fields', () => { fieldNodeTransformerMap: { Property: { // eslint-disable-next-line camelcase - new_error: fieldNode => renameFieldNode(fieldNode, 'error'), + new_error: (fieldNode) => renameFieldNode(fieldNode, 'error'), }, }, }), @@ -1471,8 +1471,8 @@ type Query { TestScalar: new GraphQLScalarType({ name: 'TestScalar', description: undefined, - serialize: value => (value as string).slice(1), - parseValue: value => `_${value as string}`, + serialize: (value) => (value as string).slice(1), + parseValue: (value) => `_${value as string}`, parseLiteral: (ast: any) => `_${ast.value as string}`, }), Query: { @@ -1486,8 +1486,8 @@ type Query { TestScalar: new GraphQLScalarType({ name: 'TestScalar', description: undefined, - serialize: value => (value as string).slice(2), - parseValue: value => `__${value as string}`, + serialize: (value) => (value as string).slice(2), + parseValue: (value) => `__${value as string}`, parseLiteral: (ast: any) => `__${ast.value as string}`, }), }, @@ -1511,8 +1511,8 @@ type Query { TestScalar: new GraphQLScalarType({ name: 'TestScalar', description: undefined, - serialize: value => (value as string).slice(1), - parseValue: value => `_${value as string}`, + serialize: (value) => (value as string).slice(1), + parseValue: (value) => `_${value as string}`, parseLiteral: (ast: any) => `_${ast.value as string}`, }), Query: { @@ -1526,8 +1526,8 @@ type Query { TestScalar: new GraphQLScalarType({ name: 'TestScalar', description: undefined, - serialize: value => (value as string).slice(2), - parseValue: value => `__${value as string}`, + serialize: (value) => (value as string).slice(2), + parseValue: (value) => `__${value as string}`, parseLiteral: (ast: any) => `__${ast.value as string}`, }), }, @@ -1696,7 +1696,7 @@ describe('onTypeConflict', () => { it('can use onTypeConflict to select first type', async () => { const mergedSchema = mergeSchemas({ schemas: [schema1, schema2], - onTypeConflict: left => left, + onTypeConflict: (left) => left, }); const result1 = await graphql(mergedSchema, '{ test1 { fieldB } }'); expect(result1.data?.test1.fieldB).to.equal('B'); @@ -1750,7 +1750,7 @@ describe('mergeTypes', () => { getTest: (_parent, { id }) => ({ id }), }, Test: { - field1: parent => parent.id, + field1: (parent) => parent.id, }, }, }); @@ -1763,7 +1763,7 @@ describe('mergeTypes', () => { getTest: (_parent, { id }) => ({ id }), }, Test: { - field2: parent => parent.id, + field2: (parent) => parent.id, }, }, }); diff --git a/src/test/testDelegateToSchema.ts b/src/test/testDelegateToSchema.ts index 4154e7a2ebe..3119ed5b5e3 100644 --- a/src/test/testDelegateToSchema.ts +++ b/src/test/testDelegateToSchema.ts @@ -60,7 +60,7 @@ function proxyResolvers(spec: string): IResolvers { Location: { coordinates: { fragment: '... on Location { name }', - resolve: location => { + resolve: (location) => { const name = location.name; return findPropertyByLocationName(sampleData.Property, name).location .coordinates; @@ -81,7 +81,7 @@ const proxyTypeDefs = ` describe('stitching', () => { describe('delegateToSchema', () => { - ['standalone', 'info.mergeInfo'].forEach(spec => { + ['standalone', 'info.mergeInfo'].forEach((spec) => { describe(spec, () => { let schema: GraphQLSchema; before(() => { diff --git a/src/test/testDirectives.ts b/src/test/testDirectives.ts index 98e1a2ada56..276aa0b4715 100644 --- a/src/test/testDirectives.ts +++ b/src/test/testDirectives.ts @@ -175,7 +175,7 @@ describe('@directives', () => { ) { assert.deepEqual(getDirectiveNames(type), typeDirectiveNames); - Object.keys(fieldDirectiveMap).forEach(key => { + Object.keys(fieldDirectiveMap).forEach((key) => { assert.deepEqual( getDirectiveNames((type as GraphQLObjectType).getFields()[key]), fieldDirectiveMap[key], @@ -184,14 +184,14 @@ describe('@directives', () => { } function getDirectiveNames(type: VisitableSchemaType): Array { - let directives = type.astNode.directives.map(d => d.name.value); + let directives = type.astNode.directives.map((d) => d.name.value); const extensionASTNodes = (type as { extensionASTNodes?: Array; }).extensionASTNodes; if (extensionASTNodes != null) { - extensionASTNodes.forEach(extensionASTNode => { + extensionASTNodes.forEach((extensionASTNode) => { directives = directives.concat( - extensionASTNode.directives.map(d => d.name.value), + extensionASTNode.directives.map((d) => d.name.value), ); }); } @@ -577,7 +577,7 @@ describe('@directives', () => { assert.deepEqual( Object.keys(methodNamesEncountered).sort((a, b) => a.localeCompare(b)), Object.keys(SchemaVisitor.prototype) - .filter(name => name.startsWith('visit')) + .filter((name) => name.startsWith('visit')) .sort((a, b) => a.localeCompare(b)), ); }); @@ -620,7 +620,7 @@ describe('@directives', () => { ) { assert.strictEqual(theSchema, schema); const prev = schema.getDirective(name); - prev.args.some(arg => { + prev.args.some((arg) => { if (arg.name === 'times') { // Override the default value of the times argument to be 3 // instead of 5. @@ -657,7 +657,8 @@ describe('@directives', () => { assert.deepEqual(Object.keys(visitors), ['oyez']); assert.deepEqual( visitors.oyez.map( - v => (v.visitedType as GraphQLObjectType | GraphQLField).name, + (v) => + (v.visitedType as GraphQLObjectType | GraphQLField).name, ), ['Courtroom', 'judge', 'marshall'], ); @@ -675,7 +676,7 @@ describe('@directives', () => { upper: class extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const { resolve = defaultFieldResolver } = field; - field.resolve = async function(...args) { + field.resolve = async function (...args) { const result = await resolve.apply(this, args); if (typeof result === 'string') { return result.toUpperCase(); @@ -725,7 +726,7 @@ describe('@directives', () => { const { resolve = defaultFieldResolver } = field; const { format } = this.args; field.type = GraphQLString; - field.resolve = async function(...args) { + field.resolve = async function (...args) { const date = await resolve.apply(this, args); return formatDate(date, format, true); }; @@ -770,7 +771,7 @@ describe('@directives', () => { ); field.type = GraphQLString; - field.resolve = async function( + field.resolve = async function ( source, { format, ...args }, context, @@ -861,7 +862,7 @@ describe('@directives', () => { }, ) { const { resolve = defaultFieldResolver } = field; - field.resolve = async function(...args: Array) { + field.resolve = async function (...args: Array) { const defaultText = await resolve.apply(this, args); // In this example, path would be ["Query", "greeting"]: const path = [details.objectType.name, field.name]; @@ -936,10 +937,10 @@ describe('@directives', () => { const fields = objectType.getFields(); - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; const { resolve = defaultFieldResolver } = field; - field.resolve = function(...args: Array) { + field.resolve = function (...args: Array) { // Get the required Role from the field first, falling back // to the objectType if no Role is required by the field: const requiredRole = @@ -1029,7 +1030,7 @@ describe('@directives', () => { expectedCount: number, ...expectedNames: Array ) { - return function({ + return function ({ errors = [], data, }: { @@ -1037,8 +1038,8 @@ describe('@directives', () => { data: any; }) { assert.strictEqual(errors.length, expectedCount); - assert(errors.every(error => error.message === 'not authorized')); - const actualNames = errors.map(error => error.path.slice(-1)[0]); + assert(errors.every((error) => error.message === 'not authorized')); + const actualNames = errors.map((error) => error.path.slice(-1)[0]); assert.deepEqual( expectedNames.sort((a, b) => a.localeCompare(b)), actualNames.sort((a, b) => a.localeCompare(b)), @@ -1053,7 +1054,7 @@ describe('@directives', () => { execWithRole('REVIEWER').then(checkErrors(1, 'banned')), execWithRole('ADMIN') .then(checkErrors(0)) - .then(data => { + .then((data) => { assert.strictEqual(data.users.length, 1); assert.strictEqual(data.users[0].banned, true); assert.strictEqual(data.users[0].canPost, false); @@ -1265,7 +1266,7 @@ describe('@directives', () => { `, null, context, - ).then(result => { + ).then((result) => { const { data } = result; assert.deepEqual(data.people, [ @@ -1316,7 +1317,7 @@ describe('@directives', () => { ); const WhateverUnion = schema.getType('WhateverUnion') as GraphQLUnionType; - const found = WhateverUnion.getTypes().some(type => { + const found = WhateverUnion.getTypes().some((type) => { if (type.name === 'Human') { assert.strictEqual(type, schema.getType('Human')); return true; @@ -1357,7 +1358,7 @@ describe('@directives', () => { const AgeUnit = schema.getType('AgeUnit') as GraphQLEnumType; assert.deepEqual( - AgeUnit.getValues().map(value => value.name), + AgeUnit.getValues().map((value) => value.name), ['DOG_YEARS', 'PERSON_YEARS'], ); }); @@ -1446,7 +1447,7 @@ describe('@directives', () => { const { resolve = defaultFieldResolver } = field; const newField = { ...field }; - newField.resolve = async function(...args: Array) { + newField.resolve = async function (...args: Array) { const result = await resolve.apply(this, args); if (typeof result === 'string') { return result.toUpperCase(); @@ -1460,13 +1461,10 @@ describe('@directives', () => { reverse: class extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const { resolve = defaultFieldResolver } = field; - field.resolve = async function(...args: Array) { + field.resolve = async function (...args: Array) { const result = await resolve.apply(this, args); if (typeof result === 'string') { - return result - .split('') - .reverse() - .join(''); + return result.split('').reverse().join(''); } return result; }; diff --git a/src/test/testFragmentsAreNotDuplicated.ts b/src/test/testFragmentsAreNotDuplicated.ts index 91d7baecea2..97ff3086fef 100644 --- a/src/test/testFragmentsAreNotDuplicated.ts +++ b/src/test/testFragmentsAreNotDuplicated.ts @@ -81,6 +81,6 @@ const variables = { function assertNoDuplicateFragmentErrors(result: ExecutionResult) { // Run assertion against each array element for better test failure output. if (result.errors != null) { - result.errors.forEach(error => expect(error.message).to.equal('')); + result.errors.forEach((error) => expect(error.message).to.equal('')); } } diff --git a/src/test/testGatsbyTransforms.ts b/src/test/testGatsbyTransforms.ts index a4a0e714e14..a4d802bc47b 100644 --- a/src/test/testGatsbyTransforms.ts +++ b/src/test/testGatsbyTransforms.ts @@ -138,7 +138,7 @@ describe('Gatsby transforms', () => { const transformedSchema = transformSchema(schema, [ new StripNonQueryTransform(), - new RenameTypes(name => `CountriesQuery_${name}`), + new RenameTypes((name) => `CountriesQuery_${name}`), new NamespaceUnderFieldTransform({ typeName: 'CountriesQuery', fieldName: 'countries', diff --git a/src/test/testMakeRemoteExecutableSchema.ts b/src/test/testMakeRemoteExecutableSchema.ts index b6f0b3e6447..d263724627f 100644 --- a/src/test/testMakeRemoteExecutableSchema.ts +++ b/src/test/testMakeRemoteExecutableSchema.ts @@ -72,7 +72,7 @@ describe('remote subscriptions', () => { }); }); - it('should work', done => { + it('should work', (done) => { const mockNotification = { notifications: { text: 'Hello world', @@ -89,7 +89,7 @@ describe('remote subscriptions', () => { let notificationCnt = 0; subscribe(schema, subscription) - .then(results => { + .then((results) => { forAwaitEach( results as AsyncIterable, (result: ExecutionResult) => { @@ -107,7 +107,7 @@ describe('remote subscriptions', () => { .catch(done); }); - it('should work without triggering multiple times per notification', done => { + it('should work without triggering multiple times per notification', (done) => { const mockNotification = { notifications: { text: 'Hello world', @@ -123,7 +123,7 @@ describe('remote subscriptions', () => { `); let notificationCnt = 0; - const sub1 = subscribe(schema, subscription).then(results => { + const sub1 = subscribe(schema, subscription).then((results) => { forAwaitEach( results as AsyncIterable, (result: ExecutionResult) => { @@ -134,7 +134,7 @@ describe('remote subscriptions', () => { ).catch(done); }); - const sub2 = subscribe(schema, subscription).then(results => { + const sub2 = subscribe(schema, subscription).then((results) => { forAwaitEach( results as AsyncIterable, (result: ExecutionResult) => { diff --git a/src/test/testMapSchema.ts b/src/test/testMapSchema.ts index 81ab8b558f1..6ae1aeb58a9 100644 --- a/src/test/testMapSchema.ts +++ b/src/test/testMapSchema.ts @@ -29,7 +29,7 @@ describe('mapSchema', () => { }); const newSchema = mapSchema(schema, { - [MapperKind.QUERY]: type => { + [MapperKind.QUERY]: (type) => { const queryConfig = toConfig(type); queryConfig.fields.version.resolve = () => 1; return new GraphQLObjectType(queryConfig); @@ -52,7 +52,7 @@ describe('mapSchema', () => { }); const newSchema = mapSchema(schema, { - [MapperKind.QUERY]: type => { + [MapperKind.QUERY]: (type) => { const queryConfig = toConfig(type); queryConfig.name = 'RootQuery'; return new GraphQLObjectType(queryConfig); diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index ea5cd21f78c..f14258446e8 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -101,8 +101,8 @@ const scalarSchema = makeExecutableSchema({ TestScalar: new GraphQLScalarType({ name: 'TestScalar', description: undefined, - serialize: value => (value as string).slice(1), - parseValue: value => `_${value as string}`, + serialize: (value) => (value as string).slice(1), + parseValue: (value) => `_${value as string}`, parseLiteral: (ast: any) => `_${ast.value as string}`, }), Query: { @@ -332,7 +332,7 @@ const schemaDirectiveTypeDefs = ` } `; -testCombinations.forEach(combination => { +testCombinations.forEach((combination) => { describe('merging ' + combination.name, () => { let mergedSchema: GraphQLSchema; let propertySchema: GraphQLSchema | SubschemaConfig; @@ -362,7 +362,7 @@ testCombinations.forEach(combination => { upper: class extends SchemaDirectiveVisitor { public visitFieldDefinition(field: GraphQLField) { const { resolve = defaultFieldResolver } = field; - field.resolve = async function(...args) { + field.resolve = async function (...args) { const result = await resolve.apply(this, args); if (typeof result === 'string') { return result.toUpperCase(); @@ -459,7 +459,7 @@ testCombinations.forEach(combination => { TestScalar: new GraphQLScalarType({ name: 'TestScalar', description: undefined, - serialize: value => value, + serialize: (value) => value, }), Query: { delegateInterfaceTest(_parent, _args, context, info) { @@ -859,7 +859,7 @@ bookingById(id: "b1") { expect(mergedResult).to.deep.equal(bookingResult); }); - it('local subscriptions working in merged schema', done => { + it('local subscriptions working in merged schema', (done) => { const mockNotification = { notifications: { text: 'Hello world', @@ -876,7 +876,7 @@ bookingById(id: "b1") { let notificationCnt = 0; subscribe(mergedSchema, subscription) - .then(results => { + .then((results) => { forAwaitEach( results as AsyncIterable, (result: ExecutionResult) => { @@ -897,7 +897,7 @@ bookingById(id: "b1") { .catch(done); }); - it('subscription errors are working correctly in merged schema', done => { + it('subscription errors are working correctly in merged schema', (done) => { const mockNotification = { notifications: { text: 'Hello world', @@ -936,7 +936,7 @@ bookingById(id: "b1") { let notificationCnt = 0; subscribe(mergedSchema, subscription) - .then(results => { + .then((results) => { forAwaitEach( results as AsyncIterable, (result: ExecutionResult) => { @@ -1415,8 +1415,8 @@ bookingById(id: "b1") { TestScalar: new GraphQLScalarType({ name: 'TestScalar', description: undefined, - serialize: value => value, - parseValue: value => value, + serialize: (value) => value, + parseValue: (value) => value, parseLiteral: () => null, }), }; @@ -2458,7 +2458,7 @@ fragment BookingFragment on Booking { const mergedResult = await graphql(mergedSchema, propertyQuery); - [propertyResult, mergedResult].forEach(result => { + [propertyResult, mergedResult].forEach((result) => { expect(result.errors).to.not.equal(undefined); expect(result.errors.length > 0).to.equal(true); const error = result.errors[0]; @@ -2946,7 +2946,7 @@ fragment BookingFragment on Booking { expect(deprecatedUsages.length).to.equal(1); expect( deprecatedUsages.find( - error => + (error) => error.message.match(/deprecated/g) != null && error.message.match(/yolo/g) != null, ), diff --git a/src/test/testMocking.ts b/src/test/testMocking.ts index 777206480f7..fab9ece9eaa 100644 --- a/src/test/testMocking.ts +++ b/src/test/testMocking.ts @@ -131,7 +131,7 @@ describe('Mock', () => { returnString returnID }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnInt']).to.be.within(-1000, 1000); expect(res.data['returnFloat']).to.be.within(-1000, 1000); expect(res.data['returnBoolean']).to.be.a('boolean'); @@ -167,9 +167,7 @@ describe('Mock', () => { .query(testQuery) .then((res: any) => { expect(res.data.returnInt).to.equal(12345); - expect(res.data.returnFloat) - .to.be.a('number') - .within(-1000, 1000); + expect(res.data.returnFloat).to.be.a('number').within(-1000, 1000); expect(res.data.returnBoolean).to.be.a('boolean'); expect(res.data.returnString).to.be.a('string'); expect(res.data.returnID).to.be.a('string'); @@ -245,9 +243,7 @@ describe('Mock', () => { .query(testQuery) .then((res: any) => { expect(res.data.returnInt).to.equal(12345); - expect(res.data.returnFloat) - .to.be.a('number') - .within(-1000, 1000); + expect(res.data.returnFloat).to.be.a('number').within(-1000, 1000); expect(res.data.returnBoolean).to.be.a('boolean'); expect(res.data.returnString).to.be.a('string'); expect(res.data.returnID).to.be.a('string'); @@ -287,7 +283,7 @@ describe('Mock', () => { } } }`; - return graphql(jsSchema, testQuery).then(_res => { + return graphql(jsSchema, testQuery).then((_res) => { // the resolveType has been called twice expect(spy).to.equal(2); }); @@ -301,7 +297,7 @@ describe('Mock', () => { const testQuery = `{ returnEnum }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnEnum']).to.be.oneOf(['A', 'B', 'C']); }); }); @@ -330,7 +326,7 @@ describe('Mock', () => { } } }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { // XXX this test is expected to fail once every 2^40 times ;-) expect(res.data['returnBirdsAndBees']).to.deep.include({ returnInt: 10, @@ -371,7 +367,7 @@ describe('Mock', () => { } } }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnFlying']).to.deep.include({ returnInt: 10, returnString: 'aha', @@ -400,7 +396,9 @@ describe('Mock', () => { spy++; const { id } = args; const type = id.split(':')[0]; - const __typename = ['Bird', 'Bee'].find(r => r.toLowerCase() === type); + const __typename = ['Bird', 'Bee'].find( + (r) => r.toLowerCase() === type, + ); return { __typename }; }, }; @@ -416,7 +414,7 @@ describe('Mock', () => { } }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(spy).to.equal(1); // to make sure that Flying possible types are not randomly selected expect(res.data['node']).to.include({ id: 'bee:123456', @@ -443,7 +441,7 @@ describe('Mock', () => { const { id } = args; const type = id.split(':')[0]; return { - __typename: ['Bird', 'Bee'].find(r => r.toLowerCase() === type), + __typename: ['Bird', 'Bee'].find((r) => r.toLowerCase() === type), }; }, }; @@ -461,7 +459,7 @@ describe('Mock', () => { } }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(spy).to.equal(1); expect(res.data['node2']).to.include({ id: 'bee:123456', @@ -492,7 +490,7 @@ describe('Mock', () => { } }`; const expected = 'Please return a __typename in "Flying"'; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.errors[0].originalError.message).to.equal(expected); }); }); @@ -505,7 +503,7 @@ describe('Mock', () => { returnMockError }`; const expected = 'No mock defined for type "MissingMockType"'; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.errors[0].originalError.message).to.equal(expected); }); }); @@ -534,7 +532,7 @@ describe('Mock', () => { returnMockError }`; const expected = 'No mock defined for type "MissingMockType"'; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.errors[0].originalError.message).to.equal(expected); }); }); @@ -565,7 +563,7 @@ describe('Mock', () => { const expected = { returnMockError: '10-11-2012', }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); expect(res.errors).to.equal(undefined); }); @@ -578,7 +576,7 @@ describe('Mock', () => { const testQuery = `{ returnInt }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnInt']).to.equal(55); }); }); @@ -590,7 +588,7 @@ describe('Mock', () => { const testQuery = `{ returnFloat }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnFloat']).to.equal(55.5); }); }); @@ -601,7 +599,7 @@ describe('Mock', () => { const testQuery = `{ returnString }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnString']).to.equal('a string'); }); }); @@ -612,7 +610,7 @@ describe('Mock', () => { const testQuery = `{ returnBoolean }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnBoolean']).to.equal(true); }); }); @@ -623,7 +621,7 @@ describe('Mock', () => { const testQuery = `{ returnID }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnID']).to.equal('ea5bdc19'); }); }); @@ -634,7 +632,7 @@ describe('Mock', () => { const testQuery = `{ returnNullableString }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnNullableString']).to.equal(null); }); }); @@ -645,7 +643,7 @@ describe('Mock', () => { const testQuery = `{ returnNonNullString }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnNonNullString']).to.equal('nonnull'); }); }); @@ -656,7 +654,7 @@ describe('Mock', () => { const testQuery = `{ returnNonNullString }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.equal(null); expect(res.errors.length).to.equal(1); }); @@ -674,7 +672,7 @@ describe('Mock', () => { const expected = { returnObject: { returnInt: 123, returnString: 'abc' }, }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -689,7 +687,7 @@ describe('Mock', () => { const expected = { returnListOfInt: [123, 123], }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -716,7 +714,7 @@ describe('Mock', () => { ], ], }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -753,7 +751,7 @@ describe('Mock', () => { returnFloat: 1.3, // b) from mock returnString: 'bar', // c) from resolvers, not masked by mock (and promise) }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -782,7 +780,7 @@ describe('Mock', () => { }, returnInt: 15, }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -825,7 +823,7 @@ describe('Mock', () => { returnString: 'woot!?', // from the mock, see b) }, }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -865,7 +863,7 @@ describe('Mock', () => { returnString: 'woot!?', // from the mock, see b) }, }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -907,7 +905,7 @@ describe('Mock', () => { returnString: 'woot!?', // from the mock, see b) }, }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -950,7 +948,7 @@ describe('Mock', () => { }, returnString: 'woot!?', // from the mock, see a) }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -995,7 +993,7 @@ describe('Mock', () => { }, returnString: 'woot!?', // from the mock, see a) }; - return graphql(jsSchema, testQuery, undefined, {}).then(res => { + return graphql(jsSchema, testQuery, undefined, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1041,7 +1039,7 @@ describe('Mock', () => { }, returnString: 'woot!?', // from the mock, see a) }; - return graphql(jsSchema, testQuery, undefined, {}).then(res => { + return graphql(jsSchema, testQuery, undefined, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1076,7 +1074,7 @@ describe('Mock', () => { }, returnString: null as string, // from the mock, see a) }; - return graphql(jsSchema, testQuery, undefined, {}).then(res => { + return graphql(jsSchema, testQuery, undefined, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1095,7 +1093,7 @@ describe('Mock', () => { const expected = { returnStringArgument: 'adieu', }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1114,7 +1112,7 @@ describe('Mock', () => { const expected = { returnStringArgument: 'adieu', }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1132,7 +1130,7 @@ describe('Mock', () => { const expected = { returnListOfInt: [12, 12, 12], }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1147,7 +1145,7 @@ describe('Mock', () => { const testQuery = `{ returnListOfInt }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['returnListOfInt']).to.have.length.within(10, 20); expect(res.data['returnListOfInt'][0]).to.equal(12); }); @@ -1167,7 +1165,7 @@ describe('Mock', () => { l3: returnListOfIntArg(l: 3) l5: returnListOfIntArg(l: 5) }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data['l3'].length).to.equal(3); expect(res.data['l5'].length).to.equal(5); }); @@ -1188,7 +1186,7 @@ describe('Mock', () => { const expected = { returnListOfInt: [33, 33], }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1218,7 +1216,7 @@ describe('Mock', () => { [12, 12, 12], ], }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1242,7 +1240,7 @@ describe('Mock', () => { const expected = { returnListOfListOfIntArg: [[12], [12]], }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1325,7 +1323,7 @@ describe('Mock', () => { ], }, }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1392,7 +1390,7 @@ describe('Mock', () => { date2: '2016-01-01T00:00:00.000Z', date3: '2016-05-04T00:00:00.000Z', }; - return graphql(schema, query).then(res => { + return graphql(schema, query).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -1471,7 +1469,7 @@ describe('Mock', () => { }, }, }; - return graphql(schema, query).then(res => { + return graphql(schema, query).then((res) => { expect(res).to.deep.equal(expected); }); }); diff --git a/src/test/testResolution.ts b/src/test/testResolution.ts index 3e3dc1af6df..74dc8278f94 100644 --- a/src/test/testResolution.ts +++ b/src/test/testResolution.ts @@ -45,7 +45,7 @@ describe('Resolve', () => { }; const schema = makeExecutableSchema({ typeDefs, resolvers }); let schemaLevelResolverCalls = 0; - addSchemaLevelResolver(schema, root => { + addSchemaLevelResolver(schema, (root) => { schemaLevelResolverCalls += 1; return root; }); @@ -71,7 +71,7 @@ describe('Resolve', () => { }); }); - it('should isolate roots from the different operation types', done => { + it('should isolate roots from the different operation types', (done) => { schemaLevelResolverCalls = 0; const queryRoot = 'queryRoot'; const mutationRoot = 'mutationRoot'; @@ -79,7 +79,7 @@ describe('Resolve', () => { const subscriptionRoot2 = 'subscriptionRoot2'; let subsCbkCalls = 0; - const firstSubsTriggered = new Promise(resolveFirst => { + const firstSubsTriggered = new Promise((resolveFirst) => { subscribe( schema, parse(` @@ -88,7 +88,7 @@ describe('Resolve', () => { } `), ) - .then(results => { + .then((results) => { forAwaitEach( results as AsyncIterable, (result: ExecutionResult) => { diff --git a/src/test/testSchemaGenerator.ts b/src/test/testSchemaGenerator.ts index 2bc885ae3f0..76d033a9ba8 100644 --- a/src/test/testSchemaGenerator.ts +++ b/src/test/testSchemaGenerator.ts @@ -291,7 +291,7 @@ describe('generating schema from shorthand', () => { resolvers: {}, }); const resultPromise = graphql(jsSchema, introspectionQuery); - return resultPromise.then(result => + return resultPromise.then((result) => assert.deepEqual(result, solution as ExecutionResult), ); }); @@ -388,7 +388,7 @@ describe('generating schema from shorthand', () => { const typeD = { typeDefs: () => ['type TypeD { bar: String }'] }; function combineTypeDefs(...args: Array): any { - return { typeDefs: () => args.map(o => o.typeDefs) }; + return { typeDefs: () => args.map((o) => o.typeDefs) }; } const combinedAandB = combineTypeDefs(typeA, typeB); @@ -504,7 +504,7 @@ describe('generating schema from shorthand', () => { resolvers: resolveFunctions, }); const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => + return resultPromise.then((result) => assert.deepEqual(result, solution as ExecutionResult), ); }); @@ -567,7 +567,7 @@ describe('generating schema from shorthand', () => { resolvers: resolveFunctions, }); const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => + return resultPromise.then((result) => assert.deepEqual(result, solution as ExecutionResult), ); }); @@ -654,7 +654,7 @@ describe('generating schema from shorthand', () => { resolvers: resolveFunctions, }); const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => + return resultPromise.then((result) => assert.deepEqual(result, solution as ExecutionResult), ); }); @@ -728,7 +728,7 @@ describe('generating schema from shorthand', () => { resolvers: [resolvers, otherResolvers], }); const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => + return resultPromise.then((result) => assert.deepEqual(result, solution as ExecutionResult), ); }); @@ -895,7 +895,7 @@ describe('generating schema from shorthand', () => { testIn(input: 1) }`; const resultPromise = graphql(walkedSchema, testQuery); - return resultPromise.then(result => + return resultPromise.then((result) => expect(result.data).to.deep.equal({ test: 'scalar:42', testIn: 'scalar:1', @@ -952,7 +952,7 @@ describe('generating schema from shorthand', () => { resolvers: resolveFunctions, }); const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => expect(result.errors).to.not.exist); + return resultPromise.then((result) => expect(result.errors).to.not.exist); }); it('should work with an Odd custom scalar type', () => { @@ -1016,7 +1016,7 @@ describe('generating schema from shorthand', () => { } `; const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => { + return resultPromise.then((result) => { assert.equal(result.data['post'].something, testValue); assert.equal(result.errors, undefined); }); @@ -1086,7 +1086,7 @@ describe('generating schema from shorthand', () => { } `; const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => { + return resultPromise.then((result) => { assert.equal(result.data['post'].something, testDate.getTime()); assert.equal(result.errors, undefined); }); @@ -1190,7 +1190,7 @@ describe('generating schema from shorthand', () => { }); const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => { + return resultPromise.then((result) => { assert.equal(result.data['redColor'], 'RED'); assert.equal(result.data['blueColor'], 'BLUE'); assert.equal(result.data['numericEnum'], 'TEST'); @@ -1249,7 +1249,7 @@ describe('generating schema from shorthand', () => { }); const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => { + return resultPromise.then((result) => { assert.equal(result.data['red'], resolveFunctions.Color.RED); assert.equal(result.data['blue'], resolveFunctions.Color.BLUE); assert.equal(result.data['num'], resolveFunctions.NumericEnum.TEST); @@ -1295,7 +1295,7 @@ describe('generating schema from shorthand', () => { }); const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => { + return resultPromise.then((result) => { assert.equal(result.data['red'], resolveFunctions.Color.RED); assert.equal(result.errors, undefined); }); @@ -1346,7 +1346,7 @@ describe('generating schema from shorthand', () => { }); const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => { + return resultPromise.then((result) => { assert.equal(result.data['red'], 'override'); assert.equal(result.errors, undefined); }); @@ -1413,7 +1413,7 @@ describe('generating schema from shorthand', () => { resolvers: resolveFunctions, }); const resultPromise = graphql(jsSchema, testQuery); - return resultPromise.then(result => + return resultPromise.then((result) => assert.deepEqual(result, solution as ExecutionResult), ); }); @@ -1819,7 +1819,7 @@ describe('generating schema from shorthand', () => { ); }); - it('throws an error if a resolve field cannot be used', done => { + it('throws an error if a resolve field cannot be used', (done) => { const shorthand = ` type BirdSpecies { name: String!, @@ -1851,7 +1851,7 @@ describe('generating schema from shorthand', () => { ).to.throw('RootQuery.speciez defined in resolvers, but not in schema'); done(); }); - it('throws an error if a resolve type is not in schema', done => { + it('throws an error if a resolve type is not in schema', (done) => { const shorthand = ` type BirdSpecies { name: String!, @@ -1912,7 +1912,7 @@ describe('providing useful errors from resolvers', () => { }); const testQuery = '{ species }'; const expected = 'Error in resolver RootQuery.species\noops!'; - return graphql(jsSchema, testQuery).then(_res => { + return graphql(jsSchema, testQuery).then((_res) => { assert.equal(logger.errors.length, 1); assert.equal(logger.errors[0].message, expected); }); @@ -1945,7 +1945,7 @@ describe('providing useful errors from resolvers', () => { const testQuery = '{ species, stuff }'; const expectedErr = /Resolver for "RootQuery.species" returned undefined/; const expectedResData = { species: null as string, stuff: 'stuff' }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { assert.equal(logger.errors.length, 1); assert.match(logger.errors[0].message, expectedErr); assert.deepEqual(res.data, expectedResData); @@ -1987,7 +1987,7 @@ describe('providing useful errors from resolvers', () => { name: 'SomeThread', }, }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { assert.deepEqual(res.data, expectedResData); }); }); @@ -2022,7 +2022,7 @@ describe('providing useful errors from resolvers', () => { name } }`; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { expect(res.errors[0].originalError.message).to.equal( 'Resolver for "Thread.name" returned undefined', ); @@ -2064,7 +2064,7 @@ describe('providing useful errors from resolvers', () => { name: 'SomeThread', }, }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { assert.deepEqual(res.data, expectedResData); }); }); @@ -2094,7 +2094,7 @@ describe('providing useful errors from resolvers', () => { }); const testQuery = '{ species, stuff }'; const expectedResData = { species: null as string, stuff: 'stuff' }; - return graphql(jsSchema, testQuery).then(res => { + return graphql(jsSchema, testQuery).then((res) => { assert.equal(logger.errors.length, 0); assert.deepEqual(res.data, expectedResData); }); @@ -2132,7 +2132,7 @@ describe('Attaching connectors to schema', () => { const query = `{ species(name: "strix") }`; - return graphql(jsSchema, query).then(res => { + return graphql(jsSchema, query).then((res) => { expect(res.data['species']).to.equal('ROOTstrix'); }); }); @@ -2147,7 +2147,7 @@ describe('Attaching connectors to schema', () => { const query = `{ stuff }`; - return graphql(jsSchema, query).then(res => { + return graphql(jsSchema, query).then((res) => { expect(res.data['stuff']).to.equal('stuff'); }); }); @@ -2189,7 +2189,7 @@ describe('Attaching connectors to schema', () => { species: 'some strix', stuff: 'stuff', }; - return graphql(jsSchema, query).then(res => { + return graphql(jsSchema, query).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2239,9 +2239,9 @@ describe('Attaching connectors to schema', () => { species: 'species2 strix', stuff: 'stuff2', }; - return graphql(jsSchema, query).then(res => { + return graphql(jsSchema, query).then((res) => { expect(res.data).to.deep.equal(expected); - return graphql(jsSchema, query).then(res2 => + return graphql(jsSchema, query).then((res2) => expect(res2.data).to.deep.equal(expected2), ); }); @@ -2262,7 +2262,7 @@ describe('Attaching connectors to schema', () => { const expected = { usecontext: 'ABC', }; - return graphql(jsSchema, query, {}, {}).then(res => { + return graphql(jsSchema, query, {}, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2304,7 +2304,7 @@ describe('Attaching connectors to schema', () => { staticString: 'Hi You!', }, }, - ).then(res => { + ).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2322,7 +2322,7 @@ describe('Attaching connectors to schema', () => { const expected = { useTestConnector: 'works', }; - return graphql(jsSchema, query, {}, {}).then(res => { + return graphql(jsSchema, query, {}, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2339,7 +2339,7 @@ describe('Attaching connectors to schema', () => { const expected = { useContextConnector: 'YOYO', }; - return graphql(jsSchema, query, {}, { str: 'YOYO' }).then(res => { + return graphql(jsSchema, query, {}, { str: 'YOYO' }).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2366,7 +2366,7 @@ describe('Attaching connectors to schema', () => { const query = `{ useTestConnector }`; - return graphql(jsSchema, query, {}, 'notObject').then(res => { + return graphql(jsSchema, query, {}, 'notObject').then((res) => { expect(res.errors[0].originalError.message).to.equal( 'Cannot attach connector because context is not an object: string', ); @@ -2384,7 +2384,7 @@ describe('Attaching connectors to schema', () => { stuff useTestConnector }`; - return graphql(jsSchema, query, undefined, {}).then(res => { + return graphql(jsSchema, query, undefined, {}).then((res) => { expect(res.errors[0].originalError.message).to.equal( 'Connector must be a function or an class', ); @@ -2409,7 +2409,7 @@ describe('Attaching connectors to schema', () => { stuff: 'stuff', useTestConnector: 'works', }; - return graphql(jsSchema, query, {}, {}).then(res => { + return graphql(jsSchema, query, {}, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); // TODO test schemaLevelResolve function with wrong arguments @@ -2480,7 +2480,7 @@ describe('Generating a full graphQL schema with resolvers and connectors', () => useTestConnector: 'works', usecontext: 'ABC', }; - return graphql(schema, query, {}, { usecontext: 'ABC' }).then(res => { + return graphql(schema, query, {}, { usecontext: 'ABC' }).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2555,7 +2555,7 @@ describe('attachDirectiveResolvers on field', () => { _args: { [argName: string]: any }, _context: any, ) { - return next().then(str => { + return next().then((str) => { if (typeof str === 'string') { return str.toLowerCase(); } @@ -2568,7 +2568,7 @@ describe('attachDirectiveResolvers on field', () => { _args: { [argName: string]: any }, _context: any, ) { - return next().then(str => { + return next().then((str) => { if (typeof str === 'string') { return str.toUpperCase(); } @@ -2581,7 +2581,7 @@ describe('attachDirectiveResolvers on field', () => { args: { [argName: string]: any }, _context: any, ) { - return next().then(res => { + return next().then((res) => { if (undefined === res) { return args.value; } @@ -2594,7 +2594,7 @@ describe('attachDirectiveResolvers on field', () => { _args: { [argName: string]: any }, _context: any, ) { - return next().catch(error => error.message); + return next().catch((error) => error.message); }, }; @@ -2635,7 +2635,7 @@ describe('attachDirectiveResolvers on field', () => { const expected = { hello: 'GIAU. TRAN MINH', }; - return graphql(schema, query, {}, {}).then(res => { + return graphql(schema, query, {}, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2656,7 +2656,7 @@ describe('attachDirectiveResolvers on field', () => { hello: 'GIAU. TRAN MINH', }, }; - return graphql(schema, query, {}, {}).then(res => { + return graphql(schema, query, {}, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2673,7 +2673,7 @@ describe('attachDirectiveResolvers on field', () => { const expected = { withDefault: 'some default_value', }; - return graphql(schema, query, {}, {}).then(res => { + return graphql(schema, query, {}, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2690,7 +2690,7 @@ describe('attachDirectiveResolvers on field', () => { const expected = { hello: 'giau. tran minh', }; - return graphql(schema, query, {}, {}).then(res => { + return graphql(schema, query, {}, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2707,7 +2707,7 @@ describe('attachDirectiveResolvers on field', () => { const expected = { asyncResolver: 'GIAU. TRAN MINH', }; - return graphql(schema, query, {}, {}).then(res => { + return graphql(schema, query, {}, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2724,7 +2724,7 @@ describe('attachDirectiveResolvers on field', () => { const expected = { multiDirectives: 'giau. tran minh', }; - return graphql(schema, query, {}, {}).then(res => { + return graphql(schema, query, {}, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2741,7 +2741,7 @@ describe('attachDirectiveResolvers on field', () => { const expected = { throwError: 'This error for testing', }; - return graphql(schema, query, {}, {}).then(res => { + return graphql(schema, query, {}, {}).then((res) => { expect(res.data).to.deep.equal(expected); }); }); @@ -2826,7 +2826,7 @@ describe('can specify lexical parser options', () => { const hoist = (document: DocumentNode) => { let variableDefs: Array = []; - document.definitions.forEach(def => { + document.definitions.forEach((def) => { if (def.kind === Kind.FRAGMENT_DEFINITION) { variableDefs = variableDefs.concat(def.variableDefinitions); } @@ -2834,7 +2834,7 @@ describe('can specify lexical parser options', () => { return { kind: Kind.DOCUMENT, - definitions: parsedQuery.definitions.map(def => ({ + definitions: parsedQuery.definitions.map((def) => ({ ...def, variableDefinitions: variableDefs, })), diff --git a/src/test/testStitchingFromSubschemas.ts b/src/test/testStitchingFromSubschemas.ts index 10c4c7d5070..df2ddd71885 100644 --- a/src/test/testStitchingFromSubschemas.ts +++ b/src/test/testStitchingFromSubschemas.ts @@ -115,7 +115,7 @@ schemas['chirpSchema'] = chirpSchema; schemas['authorSchema'] = authorSchema; const mergedSchema = mergeSchemas({ - schemas: Object.keys(schemas).map(schemaName => schemas[schemaName]), + schemas: Object.keys(schemas).map((schemaName) => schemas[schemaName]), }); describe('merging without specifying fragments', () => { diff --git a/src/test/testTransforms.ts b/src/test/testTransforms.ts index 488424fc886..21d50c3d60a 100644 --- a/src/test/testTransforms.ts +++ b/src/test/testTransforms.ts @@ -52,8 +52,8 @@ describe('transforms', () => { TestScalar: new GraphQLScalarType({ name: 'TestScalar', description: undefined, - serialize: value => (value as string).slice(1), - parseValue: value => `_${value as string}`, + serialize: (value) => (value as string).slice(1), + parseValue: (value) => `_${value as string}`, parseLiteral: (ast: any) => `_${ast.value as string}`, }), Query: { @@ -212,7 +212,7 @@ describe('transforms', () => { addMocksToSchema({ schema: subschema }); const schema = transformSchema(subschema, [ - new RenameRootTypes(name => (name === 'QueryRoot' ? 'Query' : name)), + new RenameRootTypes((name) => (name === 'QueryRoot' ? 'Query' : name)), ]); const result = await graphql( @@ -288,7 +288,7 @@ describe('transforms', () => { schemaWithCustomRootTypeNames, { schema: schemaWithDefaultRootTypeNames, - transforms: [new RenameRootTypes(name => `${name}Root`)], + transforms: [new RenameRootTypes((name) => `${name}Root`)], }, ], queryTypeName: 'QueryRoot', @@ -683,7 +683,7 @@ describe('transforms', () => { selectionSet: subtree, }), // how to process the data result at path - result => result?.address, + (result) => result?.address, ), ], }); @@ -874,7 +874,7 @@ describe('transforms', () => { (subtree: SelectionSetNode) => { const newSelectionSet = { kind: Kind.SELECTION_SET, - selections: subtree.selections.map(selection => { + selections: subtree.selections.map((selection) => { // just append fragments, not interesting for this // test if ( @@ -900,7 +900,7 @@ describe('transforms', () => { return newSelectionSet; }, // how to process the data result at path - result => ({ + (result) => ({ streetAddress: result.addressStreetAddress, zip: result.addressZip, }), @@ -909,7 +909,7 @@ describe('transforms', () => { new WrapQuery( ['userById', 'zip'], (subtree: SelectionSetNode) => subtree, - result => result, + (result) => result, ), ], }); @@ -1048,8 +1048,8 @@ describe('transforms', () => { ], }), // how to process the data result at path - resultTransformer: result => result?.address, - errorPathTransformer: path => path.slice(1), + resultTransformer: (result) => result?.address, + errorPathTransformer: (path) => path.slice(1), }), ], }); @@ -1078,8 +1078,8 @@ describe('transforms', () => { }, ], }), - resultTransformer: result => result?.address, - errorPathTransformer: path => path.slice(1), + resultTransformer: (result) => result?.address, + errorPathTransformer: (path) => path.slice(1), }), ], }); diff --git a/src/test/testTypeMerging.ts b/src/test/testTypeMerging.ts index 3efa7ffb1c0..594878dcfaa 100644 --- a/src/test/testTypeMerging.ts +++ b/src/test/testTypeMerging.ts @@ -47,7 +47,7 @@ const mergedSchema = mergeSchemas({ merge: { User: { fieldName: 'userById', - args: originalResult => ({ id: originalResult.id }), + args: (originalResult) => ({ id: originalResult.id }), selectionSet: '{ id }', }, }, @@ -57,7 +57,7 @@ const mergedSchema = mergeSchemas({ merge: { User: { fieldName: 'userById', - args: originalResult => ({ id: originalResult.id }), + args: (originalResult) => ({ id: originalResult.id }), selectionSet: '{ id }', }, }, diff --git a/src/test/testUpload.ts b/src/test/testUpload.ts index 2f7c3776e51..6ba6c0ce479 100644 --- a/src/test/testUpload.ts +++ b/src/test/testUpload.ts @@ -19,7 +19,7 @@ import { SubschemaConfig } from '../Interfaces'; function streamToString(stream: Readable) { const chunks: Array = []; return new Promise((resolve, reject) => { - stream.on('data', chunk => chunks.push(chunk)); + stream.on('data', (chunk) => chunks.push(chunk)); stream.on('error', reject); stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); }); @@ -27,7 +27,7 @@ function streamToString(stream: Readable) { function startServer(e: Express): Promise { return new Promise((resolve, reject) => { - e.listen(undefined, 'localhost', function(error) { + e.listen(undefined, 'localhost', function (error) { if (error) { reject(error); } else { diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index d263cfa893c..e161de0248f 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -173,7 +173,7 @@ export const sampleData: { }; function values(o: { [s: string]: T }): Array { - return Object.keys(o).map(k => o[k]); + return Object.keys(o).map((k) => o[k]); } function coerceString(value: any): string { @@ -209,7 +209,7 @@ function parseLiteral(ast: ValueNode): any { return parseFloat(ast.value); case Kind.OBJECT: { const value = Object.create(null); - ast.fields.forEach(field => { + ast.fields.forEach((field) => { value[field.name.value] = parseLiteral(field.value); }); @@ -689,8 +689,8 @@ const hasSubscriptionOperation = ({ query }: { query: any }): boolean => { function makeLinkFromSchema(schema: GraphQLSchema) { return new ApolloLink( - operation => - new Observable(observer => { + (operation) => + new Observable((observer) => { const { query, operationName, variables } = operation; const { graphqlContext } = operation.getContext(); if (!hasSubscriptionOperation(operation)) { @@ -702,11 +702,11 @@ function makeLinkFromSchema(schema: GraphQLSchema) { variables, operationName, ) - .then(result => { + .then((result) => { observer.next(result); observer.complete(); }) - .catch(err => { + .catch((err) => { observer.error(err); }); } else { @@ -718,23 +718,23 @@ function makeLinkFromSchema(schema: GraphQLSchema) { variables, operationName, ) - .then(results => { + .then((results) => { if ( typeof (results as AsyncIterator).next === 'function' ) { forAwaitEach( results as AsyncIterable, - result => observer.next(result), + (result) => observer.next(result), ) .then(() => observer.complete()) - .catch(err => observer.error(err)); + .catch((err) => observer.error(err)); } else { observer.next(results as LinkExecutionResult); observer.complete(); } }) - .catch(err => { + .catch((err) => { observer.error(err); }); } diff --git a/src/utils/SchemaDirectiveVisitor.ts b/src/utils/SchemaDirectiveVisitor.ts index 850c380e448..e744ce652c9 100644 --- a/src/utils/SchemaDirectiveVisitor.ts +++ b/src/utils/SchemaDirectiveVisitor.ts @@ -131,7 +131,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { const createdVisitors: { [directiveName: string]: Array; } = Object.create(null); - Object.keys(directiveVisitors).forEach(directiveName => { + Object.keys(directiveVisitors).forEach((directiveName) => { createdVisitors[directiveName] = []; }); @@ -146,13 +146,13 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { }).extensionASTNodes; if (extensionASTNodes != null) { - extensionASTNodes.forEach(extensionASTNode => { + extensionASTNodes.forEach((extensionASTNode) => { directiveNodes = directiveNodes.concat(extensionASTNode.directives); }); } const visitors: Array = []; - directiveNodes.forEach(directiveNode => { + directiveNodes.forEach((directiveNode) => { const directiveName = directiveNode.name.value; if (!hasOwn.call(directiveVisitors, directiveName)) { return; @@ -179,7 +179,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { // argument nodes to their corresponding JavaScript values. args = Object.create(null); if (directiveNode.arguments != null) { - directiveNode.arguments.forEach(arg => { + directiveNode.arguments.forEach((arg) => { args[arg.name.value] = valueFromASTUntyped(arg.value); }); } @@ -202,7 +202,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { }); if (visitors.length > 0) { - visitors.forEach(visitor => { + visitors.forEach((visitor) => { createdVisitors[visitor.name].push(visitor); }); } @@ -251,7 +251,7 @@ export class SchemaDirectiveVisitor extends SchemaVisitor { } const visitorClass = directiveVisitors[name]; - each(decl.locations, loc => { + each(decl.locations, (loc) => { const visitorMethodName = directiveLocationToVisitorMethodName(loc); if ( SchemaVisitor.implementsVisitorMethod(visitorMethodName) && diff --git a/src/utils/each.ts b/src/utils/each.ts index 312af123c22..97a55c8131e 100644 --- a/src/utils/each.ts +++ b/src/utils/each.ts @@ -4,7 +4,7 @@ export default function each( arrayOrObject: IndexedObject, callback: (value: V, key: string) => void, ) { - Object.keys(arrayOrObject).forEach(key => { + Object.keys(arrayOrObject).forEach((key) => { callback(arrayOrObject[key], key); }); } diff --git a/src/utils/fieldNodes.ts b/src/utils/fieldNodes.ts index f2408646ae3..c81b70e93a5 100644 --- a/src/utils/fieldNodes.ts +++ b/src/utils/fieldNodes.ts @@ -40,7 +40,7 @@ export function wrapFieldNode( path: Array, ): FieldNode { let newFieldNode = fieldNode; - path.forEach(fieldName => { + path.forEach((fieldName) => { newFieldNode = { kind: Kind.FIELD, name: { @@ -64,7 +64,7 @@ export function collectFields( visitedFragmentNames = {}, ): Array { if (selectionSet != null) { - selectionSet.selections.forEach(selection => { + selectionSet.selections.forEach((selection) => { switch (selection.kind) { case Kind.FIELD: fields.push(selection); diff --git a/src/utils/fields.ts b/src/utils/fields.ts index 4d946c85e67..12114f46bbe 100644 --- a/src/utils/fields.ts +++ b/src/utils/fields.ts @@ -17,10 +17,10 @@ export function appendFields( const typeConfig = toConfig(type); const originalFields = typeConfig.fields; const newFields = {}; - Object.keys(originalFields).forEach(fieldName => { + Object.keys(originalFields).forEach((fieldName) => { newFields[fieldName] = originalFields[fieldName]; }); - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { newFields[fieldName] = fields[fieldName]; }); type = new GraphQLObjectType({ @@ -46,7 +46,7 @@ export function removeFields( const originalFields = typeConfig.fields; const newFields = {}; const removedFields = {}; - Object.keys(originalFields).forEach(fieldName => { + Object.keys(originalFields).forEach((fieldName) => { if (testFn(fieldName, originalFields[fieldName])) { removedFields[fieldName] = originalFields[fieldName]; } else { diff --git a/src/utils/filterSchema.ts b/src/utils/filterSchema.ts index d23ff3ebba8..efe848795a8 100644 --- a/src/utils/filterSchema.ts +++ b/src/utils/filterSchema.ts @@ -65,7 +65,7 @@ function filterRootFields( rootFieldFilter: RootFieldFilter, ): GraphQLObjectType { const config = toConfig(type); - Object.keys(config.fields).forEach(fieldName => { + Object.keys(config.fields).forEach((fieldName) => { if (!rootFieldFilter(operation, fieldName)) { delete config.fields[fieldName]; } @@ -78,7 +78,7 @@ function filterObjectFields( fieldFilter: FieldFilter, ): GraphQLObjectType { const config = toConfig(type); - Object.keys(config.fields).forEach(fieldName => { + Object.keys(config.fields).forEach((fieldName) => { if (!fieldFilter(type.name, fieldName)) { delete config.fields[fieldName]; } diff --git a/src/utils/forEachDefaultValue.ts b/src/utils/forEachDefaultValue.ts index e1001b76d67..e6cf8692ed7 100644 --- a/src/utils/forEachDefaultValue.ts +++ b/src/utils/forEachDefaultValue.ts @@ -12,22 +12,22 @@ export function forEachDefaultValue( fn: IDefaultValueIteratorFn, ): void { const typeMap = schema.getTypeMap(); - Object.keys(typeMap).forEach(typeName => { + Object.keys(typeMap).forEach((typeName) => { const type = typeMap[typeName]; if (!getNamedType(type).name.startsWith('__')) { if (isObjectType(type)) { const fields = type.getFields(); - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; - field.args.forEach(arg => { + field.args.forEach((arg) => { arg.defaultValue = fn(arg.type, arg.defaultValue); }); }); } else if (isInputObjectType(type)) { const fields = type.getFields(); - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; field.defaultValue = fn(field.type, field.defaultValue); }); diff --git a/src/utils/forEachField.ts b/src/utils/forEachField.ts index ab66a17be97..9b71b72d4d2 100644 --- a/src/utils/forEachField.ts +++ b/src/utils/forEachField.ts @@ -7,13 +7,13 @@ export function forEachField( fn: IFieldIteratorFn, ): void { const typeMap = schema.getTypeMap(); - Object.keys(typeMap).forEach(typeName => { + Object.keys(typeMap).forEach((typeName) => { const type = typeMap[typeName]; // TODO: maybe have an option to include these? if (!getNamedType(type).name.startsWith('__') && isObjectType(type)) { const fields = type.getFields(); - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; fn(field, typeName, fieldName); }); diff --git a/src/utils/getResolversFromSchema.ts b/src/utils/getResolversFromSchema.ts index 73b45fcc025..05383782ca9 100644 --- a/src/utils/getResolversFromSchema.ts +++ b/src/utils/getResolversFromSchema.ts @@ -17,7 +17,7 @@ export function getResolversFromSchema(schema: GraphQLSchema): IResolvers { const typeMap = schema.getTypeMap(); - Object.keys(typeMap).forEach(typeName => { + Object.keys(typeMap).forEach((typeName) => { const type = typeMap[typeName]; if (isScalarType(type)) { @@ -28,7 +28,7 @@ export function getResolversFromSchema(schema: GraphQLSchema): IResolvers { resolvers[typeName] = {}; const values = type.getValues(); - values.forEach(value => { + values.forEach((value) => { resolvers[typeName][value.name] = value.value; }); } else if (isInterfaceType(type)) { @@ -51,7 +51,7 @@ export function getResolversFromSchema(schema: GraphQLSchema): IResolvers { } const fields = type.getFields(); - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; resolvers[typeName][fieldName] = { diff --git a/src/utils/heal.ts b/src/utils/heal.ts index c9b18b60414..53fd8e01367 100644 --- a/src/utils/heal.ts +++ b/src/utils/heal.ts @@ -67,7 +67,7 @@ export function healSchema(schema: GraphQLSchema): GraphQLSchema { const filteredTypeMap = {}; - Object.keys(typeMap).forEach(typeName => { + Object.keys(typeMap).forEach((typeName) => { if (!typeName.startsWith('__')) { filteredTypeMap[typeName] = typeMap[typeName]; } @@ -83,7 +83,7 @@ export function healSchema(schema: GraphQLSchema): GraphQLSchema { ? filteredTypeMap[newSubscriptionTypeName] : undefined, types: Object.keys(filteredTypeMap).map( - typeName => filteredTypeMap[typeName], + (typeName) => filteredTypeMap[typeName], ), directives: directives.slice(), }); @@ -138,7 +138,7 @@ export function healTypes( // Directive declaration argument types can refer to named types. each(directives, (decl: GraphQLDirective) => { - updateEachKey(decl.args, arg => { + updateEachKey(decl.args, (arg) => { arg.type = healType(arg.type) as GraphQLInputType; return arg.type === null ? null : arg; }); @@ -197,8 +197,8 @@ export function healTypes( } function healFields(type: GraphQLObjectType | GraphQLInterfaceType) { - updateEachKey(type.getFields(), field => { - updateEachKey(field.args, arg => { + updateEachKey(type.getFields(), (field) => { + updateEachKey(field.args, (arg) => { arg.type = healType(arg.type) as GraphQLInputType; return arg.type === null ? null : arg; }); @@ -208,14 +208,14 @@ export function healTypes( } function healInterfaces(type: GraphQLObjectType | GraphQLInterfaceType) { - updateEachKey((type as GraphQLObjectType).getInterfaces(), iface => { + updateEachKey((type as GraphQLObjectType).getInterfaces(), (iface) => { const healedType = healType(iface) as GraphQLInterfaceType; return healedType; }); } function healInputFields(type: GraphQLInputObjectType) { - updateEachKey(type.getFields(), field => { + updateEachKey(type.getFields(), (field) => { field.type = healType(field.type) as GraphQLInputType; return field.type === null ? null : field; }); @@ -264,12 +264,12 @@ function pruneTypes( directives: ReadonlyArray, ) { const implementedInterfaces = {}; - each(typeMap, namedType => { + each(typeMap, (namedType) => { if ( isObjectType(namedType) || (graphqlVersion() >= 15 && isInterfaceType(namedType)) ) { - each((namedType as GraphQLObjectType).getInterfaces(), iface => { + each((namedType as GraphQLObjectType).getInterfaces(), (iface) => { implementedInterfaces[iface.name] = true; }); } diff --git a/src/utils/map.ts b/src/utils/map.ts index 2b4f72872a3..6e4c18b09ed 100644 --- a/src/utils/map.ts +++ b/src/utils/map.ts @@ -43,7 +43,7 @@ export function mapSchema( ): GraphQLSchema { const originalTypeMap = schema.getTypeMap(); const newTypeMap = {}; - Object.keys(originalTypeMap).forEach(typeName => { + Object.keys(originalTypeMap).forEach((typeName) => { if (!typeName.startsWith('__')) { const typeMapper = getMapper( schema, @@ -86,7 +86,7 @@ export function mapSchema( const originalDirectives = schema.getDirectives(); const newDirectives: Array = []; - originalDirectives.forEach(directive => { + originalDirectives.forEach((directive) => { const directiveMapper = getMapper(schema, schemaMapper, directive); if (directiveMapper != null) { const newDirective = directiveMapper(directive, schema); @@ -112,7 +112,7 @@ export function mapSchema( newSubscriptionTypeName != null ? (typeMap[newSubscriptionTypeName] as GraphQLObjectType) : undefined, - types: Object.keys(typeMap).map(typeName => typeMap[typeName]), + types: Object.keys(typeMap).map((typeName) => typeMap[typeName]), directives, }); } @@ -197,7 +197,7 @@ export function rewireTypes( } { const newTypeMap: Record = Object.create(null); - Object.keys(originalTypeMap).forEach(typeName => { + Object.keys(originalTypeMap).forEach((typeName) => { const namedType = originalTypeMap[typeName]; if (namedType == null || typeName.startsWith('__')) { @@ -216,11 +216,13 @@ export function rewireTypes( newTypeMap[newName] = namedType; }); - Object.keys(newTypeMap).forEach(typeName => { + Object.keys(newTypeMap).forEach((typeName) => { newTypeMap[typeName] = rewireNamedType(newTypeMap[typeName]); }); - const newDirectives = directives.map(directive => rewireDirective(directive)); + const newDirectives = directives.map((directive) => + rewireDirective(directive), + ); return pruneTypes(newTypeMap, newDirectives); @@ -234,7 +236,7 @@ export function rewireTypes( args: GraphQLFieldConfigArgumentMap, ): GraphQLFieldConfigArgumentMap { const rewiredArgs = {}; - Object.keys(args).forEach(argName => { + Object.keys(args).forEach((argName) => { const arg = args[argName]; const rewiredArgType = rewireType(arg.type); if (rewiredArgType != null) { @@ -303,7 +305,7 @@ export function rewireTypes( fields: GraphQLFieldConfigMap, ): GraphQLFieldConfigMap { const rewiredFields = {}; - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; const rewiredFieldType = rewireType(field.type); if (rewiredFieldType != null) { @@ -319,7 +321,7 @@ export function rewireTypes( fields: GraphQLInputFieldConfigMap, ): GraphQLInputFieldConfigMap { const rewiredFields = {}; - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; const rewiredFieldType = rewireType(field.type); if (rewiredFieldType != null) { @@ -332,7 +334,7 @@ export function rewireTypes( function rewireNamedTypes(namedTypes: Array) { const rewiredTypes: Array = []; - namedTypes.forEach(namedType => { + namedTypes.forEach((namedType) => { const rewiredType = rewireType(namedType); if (rewiredType != null) { rewiredTypes.push(rewiredType); @@ -369,14 +371,14 @@ function pruneTypes( const newTypeMap = {}; const implementedInterfaces = {}; - Object.keys(typeMap).forEach(typeName => { + Object.keys(typeMap).forEach((typeName) => { const namedType = typeMap[typeName]; if ( isObjectType(namedType) || (graphqlVersion() >= 15 && isInterfaceType(namedType)) ) { - (namedType as GraphQLObjectType).getInterfaces().forEach(iface => { + (namedType as GraphQLObjectType).getInterfaces().forEach((iface) => { implementedInterfaces[iface.name] = true; }); } diff --git a/src/utils/mergeDeep.ts b/src/utils/mergeDeep.ts index ae5a389ec9a..0b5cd0abd4c 100644 --- a/src/utils/mergeDeep.ts +++ b/src/utils/mergeDeep.ts @@ -4,7 +4,7 @@ export function mergeDeep(target: any, ...sources: any): any { }; sources.forEach((source: any) => { if (isObject(target) && isObject(source)) { - Object.keys(source).forEach(key => { + Object.keys(source).forEach((key) => { if (isObject(source[key])) { if (!(key in target)) { Object.assign(output, { [key]: source[key] }); diff --git a/src/utils/transformInputValue.ts b/src/utils/transformInputValue.ts index ed9e1e6a982..6f40ac303d0 100644 --- a/src/utils/transformInputValue.ts +++ b/src/utils/transformInputValue.ts @@ -33,7 +33,7 @@ export function transformInputValue( } else if (isInputObjectType(nullableType)) { const fields = nullableType.getFields(); const newValue = {}; - Object.keys(value).forEach(key => { + Object.keys(value).forEach((key) => { newValue[key] = transformInputValue( fields[key].type, value[key], diff --git a/src/utils/updateEachKey.ts b/src/utils/updateEachKey.ts index 5d3172e3e8d..7582573ec99 100644 --- a/src/utils/updateEachKey.ts +++ b/src/utils/updateEachKey.ts @@ -10,7 +10,7 @@ export default function updateEachKey( ) { let deletedCount = 0; - Object.keys(arrayOrObject).forEach(key => { + Object.keys(arrayOrObject).forEach((key) => { const result = updater(arrayOrObject[key], key); if (typeof result === 'undefined') { @@ -28,7 +28,7 @@ export default function updateEachKey( if (deletedCount > 0 && Array.isArray(arrayOrObject)) { // Remove any holes from the array due to deleted elements. - arrayOrObject.splice(0).forEach(elem => { + arrayOrObject.splice(0).forEach((elem) => { arrayOrObject.push(elem); }); } diff --git a/src/utils/valueFromASTUntyped.ts b/src/utils/valueFromASTUntyped.ts index 198ad65a2e0..eb027962f8a 100644 --- a/src/utils/valueFromASTUntyped.ts +++ b/src/utils/valueFromASTUntyped.ts @@ -18,7 +18,7 @@ export default function valueFromASTUntyped(valueNode: ValueNode): any { return valueNode.values.map(valueFromASTUntyped); case Kind.OBJECT: { const obj = Object.create(null); - valueNode.fields.forEach(field => { + valueNode.fields.forEach((field) => { obj[field.name.value] = valueFromASTUntyped(field.value); }); return obj; diff --git a/src/utils/visitSchema.ts b/src/utils/visitSchema.ts index 45198d7d49d..eaa4f4bbb12 100644 --- a/src/utils/visitSchema.ts +++ b/src/utils/visitSchema.ts @@ -67,7 +67,7 @@ export function visitSchema( visitors = Array.isArray(visitors) ? visitors : [visitors]; let finalType: T | null = type; - visitors.every(visitorOrVisitorDef => { + visitors.every((visitorOrVisitorDef) => { let newType; if (visitorOrVisitorDef instanceof SchemaVisitor) { newType = visitorOrVisitorDef[methodName](finalType, ...args); @@ -93,7 +93,9 @@ export function visitSchema( if (methodName === 'visitSchema' || isSchema(finalType)) { throw new Error( - `Method ${methodName} cannot replace schema with ${newType as string}`, + `Method ${methodName} cannot replace schema with ${ + newType as string + }`, ); } @@ -171,7 +173,7 @@ export function visitSchema( string, GraphQLInputField >; - updateEachKey(fieldMap, field => + updateEachKey(fieldMap, (field) => callMethod('visitInputFieldDefinition', field, { // Since we call a different method for input object fields, we // can't reuse the visitFields function here. @@ -195,7 +197,7 @@ export function visitSchema( const newEnum = callMethod('visitEnum', type); if (newEnum != null) { - updateEachKey(newEnum.getValues(), value => + updateEachKey(newEnum.getValues(), (value) => callMethod('visitEnumValue', value, { enumType: newEnum, }), @@ -209,7 +211,7 @@ export function visitSchema( } function visitFields(type: GraphQLObjectType | GraphQLInterfaceType) { - updateEachKey(type.getFields(), field => { + 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 @@ -226,7 +228,7 @@ export function visitSchema( }); if (newField.args != null) { - updateEachKey(newField.args, arg => + updateEachKey(newField.args, (arg) => callMethod('visitArgumentDefinition', arg, { // Like visitFieldDefinition, visitArgumentDefinition takes a // second parameter that provides additional context, namely the diff --git a/src/wrap/makeRemoteExecutableSchema.ts b/src/wrap/makeRemoteExecutableSchema.ts index 9691bc51705..9147de94b20 100644 --- a/src/wrap/makeRemoteExecutableSchema.ts +++ b/src/wrap/makeRemoteExecutableSchema.ts @@ -85,7 +85,7 @@ export function createResolver( ): GraphQLFieldResolver { return async (_root, _args, context, info) => { const fragments = Object.keys(info.fragments).map( - fragment => info.fragments[fragment], + (fragment) => info.fragments[fragment], ); let query: DocumentNode = { kind: Kind.DOCUMENT, @@ -106,7 +106,7 @@ export function createResolver( function createSubscriptionResolver(link: ApolloLink): ResolverFn { return (_root, _args, context, info) => { const fragments = Object.keys(info.fragments).map( - fragment => info.fragments[fragment], + (fragment) => info.fragments[fragment], ); let query: DocumentNode = { kind: Kind.DOCUMENT, @@ -123,7 +123,7 @@ function createSubscriptionResolver(link: ApolloLink): ResolverFn { const observable = execute(link, operation); const originalAsyncIterator = observableToAsyncIterable(observable); - return mapAsyncIterator(originalAsyncIterator, result => ({ + return mapAsyncIterator(originalAsyncIterator, (result) => ({ [info.fieldName]: checkResultAndHandleErrors(result, context, info), })); }; diff --git a/src/wrap/resolvers.ts b/src/wrap/resolvers.ts index 28730ab353b..9967bafc3a8 100644 --- a/src/wrap/resolvers.ts +++ b/src/wrap/resolvers.ts @@ -50,10 +50,10 @@ export function generateProxyingResolvers({ const mapping = generateSimpleMapping(targetSchema); const result = {}; - Object.keys(mapping).forEach(name => { + Object.keys(mapping).forEach((name) => { result[name] = {}; const innerMapping = mapping[name]; - Object.keys(innerMapping).forEach(from => { + Object.keys(innerMapping).forEach((from) => { const to = innerMapping[from]; const resolverType = to.operation === 'subscription' ? 'subscribe' : 'resolve'; @@ -103,7 +103,7 @@ export function generateMappingFromObjectType( } { const result = {}; const fields = type.getFields(); - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { result[fieldName] = { name: fieldName, operation, @@ -146,7 +146,7 @@ function defaultCreateProxyingResolver({ export function stripResolvers(schema: GraphQLSchema): void { const typeMap = schema.getTypeMap(); - Object.keys(typeMap).forEach(typeName => { + Object.keys(typeMap).forEach((typeName) => { if (!typeName.startsWith('__')) { makeMergedType(typeMap[typeName]); } diff --git a/src/wrap/transforms/AddArgumentsAsVariables.ts b/src/wrap/transforms/AddArgumentsAsVariables.ts index fdfeb68e876..47cf6ab2049 100644 --- a/src/wrap/transforms/AddArgumentsAsVariables.ts +++ b/src/wrap/transforms/AddArgumentsAsVariables.ts @@ -54,10 +54,10 @@ function addVariablesToRootField( newVariables: { [key: string]: any }; } { const operations: Array = document.definitions.filter( - def => def.kind === Kind.OPERATION_DEFINITION, + (def) => def.kind === Kind.OPERATION_DEFINITION, ) as Array; const fragments: Array = document.definitions.filter( - def => def.kind === Kind.FRAGMENT_DEFINITION, + (def) => def.kind === Kind.FRAGMENT_DEFINITION, ) as Array; const variableNames = {}; @@ -145,7 +145,7 @@ function addVariablesToRootField( newSelectionSet.push({ ...selection, - arguments: Object.keys(newArgs).map(argName => newArgs[argName]), + arguments: Object.keys(newArgs).map((argName) => newArgs[argName]), }); } else { newSelectionSet.push(selection); @@ -155,7 +155,7 @@ function addVariablesToRootField( return { ...operation, variableDefinitions: originalVariableDefinitions.concat( - Object.keys(variables).map(varName => variables[varName]), + Object.keys(variables).map((varName) => variables[varName]), ), selectionSet: { kind: Kind.SELECTION_SET, diff --git a/src/wrap/transforms/AddReplacementFragments.ts b/src/wrap/transforms/AddReplacementFragments.ts index f134691a1ac..47ebbfdce31 100644 --- a/src/wrap/transforms/AddReplacementFragments.ts +++ b/src/wrap/transforms/AddReplacementFragments.ts @@ -61,7 +61,7 @@ function replaceFieldsWithFragments( let selections = node.selections; if (mapping[parentTypeName] != null) { - node.selections.forEach(selection => { + node.selections.forEach((selection) => { if (selection.kind === Kind.FIELD) { const name = selection.name.value; const fragment = mapping[parentTypeName][name]; diff --git a/src/wrap/transforms/AddReplacementSelectionSets.ts b/src/wrap/transforms/AddReplacementSelectionSets.ts index 4df0908fac4..d2c10895753 100644 --- a/src/wrap/transforms/AddReplacementSelectionSets.ts +++ b/src/wrap/transforms/AddReplacementSelectionSets.ts @@ -58,7 +58,7 @@ function replaceFieldsWithSelectionSet( let selections = node.selections; if (mapping[parentTypeName] != null) { - node.selections.forEach(selection => { + node.selections.forEach((selection) => { if (selection.kind === Kind.FIELD) { const name = selection.name.value; const selectionSet = mapping[parentTypeName][name]; diff --git a/src/wrap/transforms/ExpandAbstractTypes.ts b/src/wrap/transforms/ExpandAbstractTypes.ts index 4afe325a51f..5b9abfdf7e8 100644 --- a/src/wrap/transforms/ExpandAbstractTypes.ts +++ b/src/wrap/transforms/ExpandAbstractTypes.ts @@ -50,15 +50,15 @@ function extractPossibleTypes( ) { const typeMap = sourceSchema.getTypeMap(); const mapping: TypeMapping = {}; - Object.keys(typeMap).forEach(typeName => { + Object.keys(typeMap).forEach((typeName) => { const type = typeMap[typeName]; if (isAbstractType(type)) { const targetType = targetSchema.getType(typeName); if (!isAbstractType(targetType)) { const implementations = sourceSchema.getPossibleTypes(type); mapping[typeName] = implementations - .filter(impl => targetSchema.getType(impl.name)) - .map(impl => impl.name); + .filter((impl) => targetSchema.getType(impl.name)) + .map((impl) => impl.name); } } }); @@ -67,9 +67,9 @@ function extractPossibleTypes( function flipMapping(mapping: TypeMapping): TypeMapping { const result: TypeMapping = {}; - Object.keys(mapping).forEach(typeName => { + Object.keys(mapping).forEach((typeName) => { const toTypeNames = mapping[typeName]; - toTypeNames.forEach(toTypeName => { + toTypeNames.forEach((toTypeName) => { if (result[toTypeName] == null) { result[toTypeName] = []; } @@ -86,13 +86,15 @@ function expandAbstractTypes( document: DocumentNode, ): DocumentNode { const operations: Array = document.definitions.filter( - def => def.kind === Kind.OPERATION_DEFINITION, + (def) => def.kind === Kind.OPERATION_DEFINITION, ) as Array; const fragments: Array = document.definitions.filter( - def => def.kind === Kind.FRAGMENT_DEFINITION, + (def) => def.kind === Kind.FRAGMENT_DEFINITION, ) as Array; - const existingFragmentNames = fragments.map(fragment => fragment.name.value); + const existingFragmentNames = fragments.map( + (fragment) => fragment.name.value, + ); let fragmentCounter = 0; const generateFragmentName = (typeName: string) => { let fragmentName; @@ -113,7 +115,7 @@ function expandAbstractTypes( const possibleTypes = mapping[fragment.typeCondition.name.value]; if (possibleTypes != null) { fragmentReplacements[fragment.name.value] = []; - possibleTypes.forEach(possibleTypeName => { + possibleTypes.forEach((possibleTypeName) => { const name = generateFragmentName(possibleTypeName); existingFragmentNames.push(name); const newFragment: FragmentDefinitionNode = { @@ -160,7 +162,7 @@ function expandAbstractTypes( const possibleTypes = mapping[selection.typeCondition.name.value]; if (possibleTypes != null) { - possibleTypes.forEach(possibleType => { + possibleTypes.forEach((possibleType) => { const maybePossibleType = targetSchema.getType( possibleType, ); @@ -191,7 +193,7 @@ function expandAbstractTypes( const fragmentName = selection.name.value; const replacements = fragmentReplacements[fragmentName]; if (replacements != null) { - replacements.forEach(replacement => { + replacements.forEach((replacement) => { const typeName = replacement.typeName; const maybeReplacementType = targetSchema.getType(typeName); if ( diff --git a/src/wrap/transforms/FilterToSchema.ts b/src/wrap/transforms/FilterToSchema.ts index 57448cf5ddc..7a77dd359b2 100644 --- a/src/wrap/transforms/FilterToSchema.ts +++ b/src/wrap/transforms/FilterToSchema.ts @@ -49,10 +49,10 @@ function filterToSchema( variables: Record, ): { document: DocumentNode; variables: Record } { const operations: Array = document.definitions.filter( - def => def.kind === Kind.OPERATION_DEFINITION, + (def) => def.kind === Kind.OPERATION_DEFINITION, ) as Array; const fragments: Array = document.definitions.filter( - def => def.kind === Kind.FRAGMENT_DEFINITION, + (def) => def.kind === Kind.FRAGMENT_DEFINITION, ) as Array; let usedVariables: Array = []; @@ -135,7 +135,7 @@ function filterToSchema( }); const newVariables: Record = {}; - usedVariables.forEach(variableName => { + usedVariables.forEach((variableName) => { newVariables[variableName] = variables[variableName]; }); @@ -163,7 +163,7 @@ function collectFragmentVariables( while (remainingFragments.length !== 0) { const nextFragmentName = remainingFragments.pop(); const fragment = validFragments.find( - fr => fr.name.value === nextFragmentName, + (fr) => fr.name.value === nextFragmentName, ); if (fragment != null) { const name = nextFragmentName; @@ -231,7 +231,7 @@ function filterSelectionSet( } const argNames = (field.args != null ? field.args : []).map( - arg => arg.name, + (arg) => arg.name, ); if (node.arguments != null) { const args = node.arguments.filter( @@ -307,8 +307,8 @@ function filterSelectionSet( function union(...arrays: Array>): Array { const cache: { [key: string]: boolean } = {}; const result: Array = []; - arrays.forEach(array => { - array.forEach(item => { + arrays.forEach((array) => { + array.forEach((item) => { if (!cache[item]) { cache[item] = true; result.push(item); diff --git a/src/wrap/transforms/HoistField.ts b/src/wrap/transforms/HoistField.ts index f290e7fdd41..c2d1eae9902 100644 --- a/src/wrap/transforms/HoistField.ts +++ b/src/wrap/transforms/HoistField.ts @@ -24,7 +24,7 @@ export default class HoistField implements Transform { this.oldFieldName = this.pathToField.pop(); this.transformer = new MapFields({ [typeName]: { - [newFieldName]: fieldNode => + [newFieldName]: (fieldNode) => wrapFieldNode( renameFieldNode(fieldNode, this.oldFieldName), this.pathToField, @@ -45,7 +45,7 @@ export default class HoistField implements Transform { const targetField = removeFields( typeMap, innerType.name, - fieldName => fieldName === this.oldFieldName, + (fieldName) => fieldName === this.oldFieldName, )[this.oldFieldName]; const targetType = targetField.type as GraphQLObjectType; diff --git a/src/wrap/transforms/RenameRootTypes.ts b/src/wrap/transforms/RenameRootTypes.ts index 377e449d721..db09e38d7dc 100644 --- a/src/wrap/transforms/RenameRootTypes.ts +++ b/src/wrap/transforms/RenameRootTypes.ts @@ -23,7 +23,7 @@ export default class RenameRootTypes implements Transform { public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { return mapSchema(originalSchema, { - [MapperKind.ROOT_OBJECT]: type => { + [MapperKind.ROOT_OBJECT]: (type) => { const oldName = type.name; const newName = this.renamer(oldName); if (newName && newName !== oldName) { @@ -75,7 +75,7 @@ export default class RenameRootTypes implements Transform { }); return value; } else if (typeof value === 'object') { - Object.keys(value).forEach(key => { + Object.keys(value).forEach((key) => { value[key] = key === '__typename' ? this.renamer(value[key]) diff --git a/src/wrap/transforms/RenameTypes.ts b/src/wrap/transforms/RenameTypes.ts index 92e1e2cf1c3..1891267d29f 100644 --- a/src/wrap/transforms/RenameTypes.ts +++ b/src/wrap/transforms/RenameTypes.ts @@ -128,7 +128,7 @@ export default class RenameTypes implements Transform { }); return value; } else if (typeof value === 'object') { - Object.keys(value).forEach(key => { + Object.keys(value).forEach((key) => { value[key] = key === '__typename' ? this.renamer(value[key]) diff --git a/src/wrap/transforms/ReplaceFieldWithFragment.ts b/src/wrap/transforms/ReplaceFieldWithFragment.ts index f395a0edb3f..a8aa3d87d26 100644 --- a/src/wrap/transforms/ReplaceFieldWithFragment.ts +++ b/src/wrap/transforms/ReplaceFieldWithFragment.ts @@ -78,7 +78,7 @@ function replaceFieldsWithFragments( let selections = node.selections; if (mapping[parentTypeName] != null) { - node.selections.forEach(selection => { + node.selections.forEach((selection) => { if (selection.kind === Kind.FIELD) { const name = selection.name.value; const fragments = mapping[parentTypeName][name]; diff --git a/src/wrap/transforms/TransformObjectFields.ts b/src/wrap/transforms/TransformObjectFields.ts index 00850b428e0..b2b2510943c 100644 --- a/src/wrap/transforms/TransformObjectFields.ts +++ b/src/wrap/transforms/TransformObjectFields.ts @@ -68,8 +68,8 @@ export default class TransformObjectFields implements Transform { public transformRequest(originalRequest: Request): Request { const fragments = {}; originalRequest.document.definitions - .filter(def => def.kind === Kind.FRAGMENT_DEFINITION) - .forEach(def => { + .filter((def) => def.kind === Kind.FRAGMENT_DEFINITION) + .forEach((def) => { fragments[(def as FragmentDefinitionNode).name.value] = def; }); const document = this.transformDocument( @@ -92,7 +92,7 @@ export default class TransformObjectFields implements Transform { const fields = type.getFields(); const newFields = {}; - Object.keys(fields).forEach(fieldName => { + Object.keys(fields).forEach((fieldName) => { const field = fields[fieldName]; const transformedField = objectFieldTransformer( type.name, @@ -151,7 +151,7 @@ export default class TransformObjectFields implements Transform { const parentTypeName = parentType.name; let newSelections: Array = []; - node.selections.forEach(selection => { + node.selections.forEach((selection) => { if (selection.kind !== Kind.FIELD) { newSelections.push(selection); return; diff --git a/src/wrap/transforms/TransformQuery.ts b/src/wrap/transforms/TransformQuery.ts index 6204355f800..83cd1caa917 100644 --- a/src/wrap/transforms/TransformQuery.ts +++ b/src/wrap/transforms/TransformQuery.ts @@ -29,8 +29,8 @@ export default class TransformQuery implements Transform { constructor({ path, queryTransformer, - resultTransformer = result => result, - errorPathTransformer = errorPath => [].concat(errorPath), + resultTransformer = (result) => result, + errorPathTransformer = (errorPath) => [].concat(errorPath), fragments = {}, }: { path: Array; @@ -53,7 +53,7 @@ export default class TransformQuery implements Transform { let index = 0; const newDocument = visit(document, { [Kind.FIELD]: { - enter: node => { + enter: (node) => { if (index === pathLength || node.name.value !== this.path[index]) { return false; } @@ -115,7 +115,7 @@ export default class TransformQuery implements Transform { private transformErrors( errors: ReadonlyArray, ): ReadonlyArray { - return errors.map(error => { + return errors.map((error) => { const path: ReadonlyArray = error.path; let match = true; diff --git a/src/wrap/transforms/WrapFields.ts b/src/wrap/transforms/WrapFields.ts index fc516fc6c82..8da0f32744b 100644 --- a/src/wrap/transforms/WrapFields.ts +++ b/src/wrap/transforms/WrapFields.ts @@ -57,7 +57,7 @@ export default class WrapFields implements Transform { this.outerTypeName, !this.fieldNames ? () => true - : fieldName => this.fieldNames.includes(fieldName), + : (fieldName) => this.fieldNames.includes(fieldName), ); let wrapIndex = this.numWraps - 1; From 641424252892a68c6964700c00deca4fa0a2d75a Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 24 Mar 2020 00:34:29 -0400 Subject: [PATCH 244/250] chore(git): line endings --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..6313b56c578 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf From 29fbb0df52a299fa15e8f8157c552b8fef62dc0b Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 24 Mar 2020 00:45:54 -0400 Subject: [PATCH 245/250] feat(transforms): add interface transforms - adds TransformCompositeFields as generic transform that can function on fields belonging to objects and interfaces - refactoring TransformObjectFields to use TransformCompositeFIelds - adds TransformInterfaceFields along similar lines - adds RenameInterfaceFields and FilterInterfaceFields in parallel to RenameObjectFields and FilterObjectFields - make supporting types more generic and export them - add test for RenameInterfaceFields --- src/Interfaces.ts | 33 +++ src/test/testAlternateMergeSchemas.ts | 80 +++++++ src/utils/filterSchema.ts | 18 +- src/wrap/transforms/FilterInterfaceFields.ts | 20 ++ src/wrap/transforms/FilterObjectFields.ts | 10 +- src/wrap/transforms/RenameInterfaceFields.ts | 31 +++ .../transforms/TransformCompositeFields.ts | 222 ++++++++++++++++++ .../transforms/TransformInterfaceFields.ts | 49 ++++ src/wrap/transforms/TransformObjectFields.ts | 221 ++--------------- src/wrap/transforms/TransformRootFields.ts | 6 +- src/wrap/transforms/index.ts | 6 +- 11 files changed, 478 insertions(+), 218 deletions(-) create mode 100644 src/wrap/transforms/FilterInterfaceFields.ts create mode 100644 src/wrap/transforms/RenameInterfaceFields.ts create mode 100644 src/wrap/transforms/TransformCompositeFields.ts create mode 100644 src/wrap/transforms/TransformInterfaceFields.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index c9ab749046d..b055ef811d6 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -24,6 +24,9 @@ import { GraphQLOutputType, SelectionSetNode, GraphQLDirective, + GraphQLFieldConfig, + FragmentDefinitionNode, + SelectionNode, } from 'graphql'; import { TypeMap } from 'graphql/type/schema'; @@ -71,6 +74,36 @@ export interface Transform { 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; } diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 14336ad1eec..8c2118d85b2 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -16,6 +16,7 @@ import { expect } from 'chai'; import { transformSchema, + wrapSchema, RenameTypes, RenameRootFields, RenameObjectFields, @@ -26,6 +27,7 @@ import { HoistField, FilterRootFields, FilterObjectFields, + RenameInterfaceFields, } from '../wrap/index'; import { isSpecifiedScalarType, toConfig } from '../polyfills/index'; @@ -411,6 +413,84 @@ describe('transform object fields', () => { }); }); +describe('rename fields that implement interface fields', () => { + it('should work', () => { + const originalItem = { + id: '123', + camel: "I'm a camel!", + }; + + const originalSchema = makeExecutableSchema({ + typeDefs: ` + interface Node { + id: ID! + } + interface Item { + node: Node + } + type Camel implements Node { + id: ID! + camel: String! + } + type Query implements Item { + node: Node + } + `, + resolvers: { + Query: { + node: () => originalItem, + }, + Node: { + __resolveType: () => 'Camel', + }, + }, + }); + + const wrappedSchema = wrapSchema(originalSchema, [ + new RenameRootFields((_operation, fieldName) => { + if (fieldName === 'node') { + return '_node'; + } + return fieldName; + }), + new RenameInterfaceFields((typeName, fieldName) => { + if (typeName === 'Item' && fieldName === 'node') { + return '_node'; + } + return fieldName; + }), + ]); + + const originalQuery = ` + query { + node { + id + ... on Camel { + camel + } + } + } + `; + + const newQuery = ` + query { + _node { + id + ... on Camel { + camel + } + } + } + `; + + const originalResult = graphqlSync(originalSchema, originalQuery); + expect(originalResult).to.deep.equal({ data: { node: originalItem } }); + + const newResult = graphqlSync(wrappedSchema, newQuery); + expect(newResult).to.deep.equal({ data: { _node: originalItem } }); + }); +}); + describe('transform object fields', () => { let schema: GraphQLSchema; diff --git a/src/utils/filterSchema.ts b/src/utils/filterSchema.ts index efe848795a8..060e7009387 100644 --- a/src/utils/filterSchema.ts +++ b/src/utils/filterSchema.ts @@ -8,18 +8,16 @@ import { GraphQLType, } from 'graphql'; -import { GraphQLSchemaWithTransforms, MapperKind } from '../Interfaces'; +import { + GraphQLSchemaWithTransforms, + MapperKind, + FieldFilter, + RootFieldFilter, +} from '../Interfaces'; import { toConfig } from '../polyfills/index'; import { mapSchema } from './map'; -export type RootFieldFilter = ( - operation: 'Query' | 'Mutation' | 'Subscription', - rootFieldName: string, -) => boolean; - -export type FieldFilter = (typeName: string, rootFieldName: string) => boolean; - export default function filterSchema({ schema, rootFieldFilter = () => true, @@ -66,7 +64,7 @@ function filterRootFields( ): GraphQLObjectType { const config = toConfig(type); Object.keys(config.fields).forEach((fieldName) => { - if (!rootFieldFilter(operation, fieldName)) { + if (!rootFieldFilter(operation, fieldName, config.fields[fieldName])) { delete config.fields[fieldName]; } }); @@ -79,7 +77,7 @@ function filterObjectFields( ): GraphQLObjectType { const config = toConfig(type); Object.keys(config.fields).forEach((fieldName) => { - if (!fieldFilter(type.name, fieldName)) { + if (!fieldFilter(type.name, fieldName, config.fields[fieldName])) { delete config.fields[fieldName]; } }); diff --git a/src/wrap/transforms/FilterInterfaceFields.ts b/src/wrap/transforms/FilterInterfaceFields.ts new file mode 100644 index 00000000000..550376cae9e --- /dev/null +++ b/src/wrap/transforms/FilterInterfaceFields.ts @@ -0,0 +1,20 @@ +import { GraphQLField, GraphQLSchema } from 'graphql'; + +import { Transform, FieldFilter } from '../../Interfaces'; + +import TransformInterfaceFields from './TransformInterfaceFields'; + +export default class FilterInterfaceFields implements Transform { + private readonly transformer: TransformInterfaceFields; + + constructor(filter: FieldFilter) { + this.transformer = new TransformInterfaceFields( + (typeName: string, fieldName: string, field: GraphQLField) => + filter(typeName, fieldName, field) ? undefined : null, + ); + } + + public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { + return this.transformer.transformSchema(originalSchema); + } +} diff --git a/src/wrap/transforms/FilterObjectFields.ts b/src/wrap/transforms/FilterObjectFields.ts index f8d9d3854c6..1d55b7e0981 100644 --- a/src/wrap/transforms/FilterObjectFields.ts +++ b/src/wrap/transforms/FilterObjectFields.ts @@ -1,19 +1,13 @@ import { GraphQLField, GraphQLSchema } from 'graphql'; -import { Transform } from '../../Interfaces'; +import { Transform, FieldFilter } from '../../Interfaces'; import TransformObjectFields from './TransformObjectFields'; -export type ObjectFilter = ( - typeName: string, - fieldName: string, - field: GraphQLField, -) => boolean; - export default class FilterObjectFields implements Transform { private readonly transformer: TransformObjectFields; - constructor(filter: ObjectFilter) { + constructor(filter: FieldFilter) { this.transformer = new TransformObjectFields( (typeName: string, fieldName: string, field: GraphQLField) => filter(typeName, fieldName, field) ? undefined : null, diff --git a/src/wrap/transforms/RenameInterfaceFields.ts b/src/wrap/transforms/RenameInterfaceFields.ts new file mode 100644 index 00000000000..b1e174c838c --- /dev/null +++ b/src/wrap/transforms/RenameInterfaceFields.ts @@ -0,0 +1,31 @@ +import { GraphQLField, GraphQLSchema } from 'graphql'; + +import { Transform, Request } from '../../Interfaces'; + +import TransformInterfaceFields from './TransformInterfaceFields'; + +export default class RenameInterfaceFields implements Transform { + private readonly transformer: TransformInterfaceFields; + + constructor( + renamer: ( + typeName: string, + fieldName: string, + field: GraphQLField, + ) => string, + ) { + this.transformer = new TransformInterfaceFields( + (typeName: string, fieldName: string, field: GraphQLField) => ({ + name: renamer(typeName, fieldName, field), + }), + ); + } + + public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { + return this.transformer.transformSchema(originalSchema); + } + + public transformRequest(originalRequest: Request): Request { + return this.transformer.transformRequest(originalRequest); + } +} diff --git a/src/wrap/transforms/TransformCompositeFields.ts b/src/wrap/transforms/TransformCompositeFields.ts new file mode 100644 index 00000000000..2825000675c --- /dev/null +++ b/src/wrap/transforms/TransformCompositeFields.ts @@ -0,0 +1,222 @@ +import { + GraphQLSchema, + GraphQLType, + DocumentNode, + TypeInfo, + visit, + visitWithTypeInfo, + Kind, + SelectionSetNode, + SelectionNode, + FragmentDefinitionNode, + GraphQLInterfaceType, + isObjectType, + isInterfaceType, + GraphQLObjectType, +} from 'graphql'; + +import isEmptyObject from '../../utils/isEmptyObject'; +import { + Transform, + Request, + MapperKind, + FieldTransformer, + FieldNodeTransformer, + RenamedField, +} from '../../Interfaces'; +import { mapSchema } from '../../utils/index'; +import { toConfig } from '../../polyfills/index'; + +type FieldMapping = { + [typeName: string]: { + [newFieldName: string]: string; + }; +}; + +export default class TransformCompositeFields implements Transform { + private readonly fieldTransformer: FieldTransformer; + private readonly fieldNodeTransformer: FieldNodeTransformer; + private transformedSchema: GraphQLSchema; + private mapping: FieldMapping; + + constructor( + fieldTransformer: FieldTransformer, + fieldNodeTransformer?: FieldNodeTransformer, + ) { + this.fieldTransformer = fieldTransformer; + this.fieldNodeTransformer = fieldNodeTransformer; + this.mapping = {}; + } + + public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { + this.transformedSchema = mapSchema(originalSchema, { + [MapperKind.OBJECT_TYPE]: (type: GraphQLObjectType) => + this.transformFields(type, this.fieldTransformer), + [MapperKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) => + this.transformFields(type, this.fieldTransformer), + }); + + return this.transformedSchema; + } + + public transformRequest(originalRequest: Request): Request { + const fragments = {}; + originalRequest.document.definitions + .filter((def) => def.kind === Kind.FRAGMENT_DEFINITION) + .forEach((def) => { + fragments[(def as FragmentDefinitionNode).name.value] = def; + }); + const document = this.transformDocument( + originalRequest.document, + this.mapping, + this.fieldNodeTransformer, + fragments, + ); + return { + ...originalRequest, + document, + }; + } + + private transformFields( + type: GraphQLObjectType, + fieldTransformer: FieldTransformer, + ): GraphQLObjectType; + + private transformFields( + type: GraphQLInterfaceType, + fieldTransformer: FieldTransformer, + ): GraphQLInterfaceType; + + private transformFields(type: any, fieldTransformer: FieldTransformer): any { + const typeConfig = toConfig(type); + const fields = type.getFields(); + const newFields = {}; + + Object.keys(fields).forEach((fieldName) => { + const field = fields[fieldName]; + const transformedField = fieldTransformer(type.name, fieldName, field); + + if (typeof transformedField === 'undefined') { + newFields[fieldName] = typeConfig.fields[fieldName]; + } else if (transformedField !== null) { + const newName = (transformedField as RenamedField).name; + + if (newName) { + newFields[newName] = + (transformedField as RenamedField).field != null + ? (transformedField as RenamedField).field + : typeConfig.fields[fieldName]; + + if (newName !== fieldName) { + const typeName = type.name; + if (!this.mapping[typeName]) { + this.mapping[typeName] = {}; + } + this.mapping[typeName][newName] = fieldName; + } + } else { + newFields[fieldName] = transformedField; + } + } + }); + + if (isEmptyObject(newFields)) { + return null; + } + + if (isObjectType(type)) { + return new GraphQLObjectType({ + ...toConfig(type), + fields: newFields, + }); + } else if (isInterfaceType(type)) { + return new GraphQLInterfaceType({ + ...toConfig(type), + fields: newFields, + }); + } + } + + private transformDocument( + document: DocumentNode, + mapping: FieldMapping, + fieldNodeTransformer?: FieldNodeTransformer, + fragments: Record = {}, + ): DocumentNode { + const typeInfo = new TypeInfo(this.transformedSchema); + const newDocument: DocumentNode = visit( + document, + visitWithTypeInfo(typeInfo, { + leave: { + [Kind.SELECTION_SET]: (node: SelectionSetNode): SelectionSetNode => { + const parentType: GraphQLType = typeInfo.getParentType(); + if (parentType != null) { + const parentTypeName = parentType.name; + let newSelections: Array = []; + + node.selections.forEach((selection) => { + if (selection.kind !== Kind.FIELD) { + newSelections.push(selection); + return; + } + + const newName = selection.name.value; + + const transformedSelection = + fieldNodeTransformer != null + ? fieldNodeTransformer( + parentTypeName, + newName, + selection, + fragments, + ) + : selection; + + if (Array.isArray(transformedSelection)) { + newSelections = newSelections.concat(transformedSelection); + return; + } + + if (transformedSelection.kind !== Kind.FIELD) { + newSelections.push(transformedSelection); + return; + } + + const typeMapping = mapping[parentTypeName]; + if (typeMapping == null) { + newSelections.push(transformedSelection); + return; + } + + const oldName = mapping[parentTypeName][newName]; + if (oldName == null) { + newSelections.push(transformedSelection); + return; + } + + newSelections.push({ + ...transformedSelection, + name: { + kind: Kind.NAME, + value: oldName, + }, + alias: { + kind: Kind.NAME, + value: newName, + }, + }); + }); + + return { + ...node, + selections: newSelections, + }; + } + }, + }, + }), + ); + return newDocument; + } +} diff --git a/src/wrap/transforms/TransformInterfaceFields.ts b/src/wrap/transforms/TransformInterfaceFields.ts new file mode 100644 index 00000000000..47544b64baa --- /dev/null +++ b/src/wrap/transforms/TransformInterfaceFields.ts @@ -0,0 +1,49 @@ +import { GraphQLSchema, GraphQLField, isInterfaceType } from 'graphql'; + +import { + Transform, + Request, + FieldTransformer, + FieldNodeTransformer, +} from '../../Interfaces'; + +import TransformCompositeFields from './TransformCompositeFields'; + +export default class TransformInterfaceFields implements Transform { + private readonly interfaceFieldTransformer: FieldTransformer; + private readonly fieldNodeTransformer: FieldNodeTransformer; + private transformer: TransformCompositeFields; + + constructor( + interfaceFieldTransformer: FieldTransformer, + fieldNodeTransformer?: FieldNodeTransformer, + ) { + this.interfaceFieldTransformer = interfaceFieldTransformer; + this.fieldNodeTransformer = fieldNodeTransformer; + } + + public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { + const compositeToObjectFieldTransformer = ( + typeName: string, + fieldName: string, + field: GraphQLField, + ) => { + if (isInterfaceType(originalSchema.getType(typeName))) { + return this.interfaceFieldTransformer(typeName, fieldName, field); + } + + return undefined; + }; + + this.transformer = new TransformCompositeFields( + compositeToObjectFieldTransformer, + this.fieldNodeTransformer, + ); + + return this.transformer.transformSchema(originalSchema); + } + + public transformRequest(originalRequest: Request): Request { + return this.transformer.transformRequest(originalRequest); + } +} diff --git a/src/wrap/transforms/TransformObjectFields.ts b/src/wrap/transforms/TransformObjectFields.ts index b2b2510943c..c566bb5d34f 100644 --- a/src/wrap/transforms/TransformObjectFields.ts +++ b/src/wrap/transforms/TransformObjectFields.ts @@ -1,218 +1,49 @@ -import { - GraphQLObjectType, - GraphQLSchema, - GraphQLField, - GraphQLFieldConfig, - GraphQLType, - DocumentNode, - FieldNode, - TypeInfo, - visit, - visitWithTypeInfo, - Kind, - SelectionSetNode, - SelectionNode, - FragmentDefinitionNode, -} from 'graphql'; - -import isEmptyObject from '../../utils/isEmptyObject'; -import { Transform, Request, MapperKind } from '../../Interfaces'; -import { mapSchema } from '../../utils/index'; -import { toConfig } from '../../polyfills/index'; - -export type ObjectFieldTransformer = ( - typeName: string, - fieldName: string, - field: GraphQLField, -) => GraphQLFieldConfig | RenamedField | null | undefined; - -export type FieldNodeTransformer = ( - typeName: string, - fieldName: string, - fieldNode: FieldNode, - fragments: Record, -) => SelectionNode | Array; +import { GraphQLSchema, GraphQLField, isObjectType } from 'graphql'; -type FieldMapping = { - [typeName: string]: { - [newFieldName: string]: string; - }; -}; +import { + Transform, + Request, + FieldTransformer, + FieldNodeTransformer, +} from '../../Interfaces'; -type RenamedField = { name: string; field?: GraphQLFieldConfig }; +import TransformCompositeFields from './TransformCompositeFields'; export default class TransformObjectFields implements Transform { - private readonly objectFieldTransformer: ObjectFieldTransformer; + private readonly objectFieldTransformer: FieldTransformer; private readonly fieldNodeTransformer: FieldNodeTransformer; - private transformedSchema: GraphQLSchema; - private mapping: FieldMapping; + private transformer: TransformCompositeFields; constructor( - objectFieldTransformer: ObjectFieldTransformer, + objectFieldTransformer: FieldTransformer, fieldNodeTransformer?: FieldNodeTransformer, ) { this.objectFieldTransformer = objectFieldTransformer; this.fieldNodeTransformer = fieldNodeTransformer; - this.mapping = {}; } public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema { - this.transformedSchema = mapSchema(originalSchema, { - [MapperKind.OBJECT_TYPE]: (type: GraphQLObjectType) => - this.transformFields(type, this.objectFieldTransformer), - }); + const compositeToObjectFieldTransformer = ( + typeName: string, + fieldName: string, + field: GraphQLField, + ) => { + if (isObjectType(originalSchema.getType(typeName))) { + return this.objectFieldTransformer(typeName, fieldName, field); + } - return this.transformedSchema; - } + return undefined; + }; - public transformRequest(originalRequest: Request): Request { - const fragments = {}; - originalRequest.document.definitions - .filter((def) => def.kind === Kind.FRAGMENT_DEFINITION) - .forEach((def) => { - fragments[(def as FragmentDefinitionNode).name.value] = def; - }); - const document = this.transformDocument( - originalRequest.document, - this.mapping, + this.transformer = new TransformCompositeFields( + compositeToObjectFieldTransformer, this.fieldNodeTransformer, - fragments, ); - return { - ...originalRequest, - document, - }; - } - - private transformFields( - type: GraphQLObjectType, - objectFieldTransformer: ObjectFieldTransformer, - ): GraphQLObjectType { - const typeConfig = toConfig(type); - const fields = type.getFields(); - const newFields = {}; - - Object.keys(fields).forEach((fieldName) => { - const field = fields[fieldName]; - const transformedField = objectFieldTransformer( - type.name, - fieldName, - field, - ); - - if (typeof transformedField === 'undefined') { - newFields[fieldName] = typeConfig.fields[fieldName]; - } else if (transformedField !== null) { - const newName = (transformedField as RenamedField).name; - - if (newName) { - newFields[newName] = - (transformedField as RenamedField).field != null - ? (transformedField as RenamedField).field - : typeConfig.fields[fieldName]; - if (newName !== fieldName) { - const typeName = type.name; - if (!this.mapping[typeName]) { - this.mapping[typeName] = {}; - } - this.mapping[typeName][newName] = fieldName; - } - } else { - newFields[fieldName] = transformedField; - } - } - }); - - if (isEmptyObject(newFields)) { - return null; - } - - return new GraphQLObjectType({ - ...toConfig(type), - fields: newFields, - }); + return this.transformer.transformSchema(originalSchema); } - private transformDocument( - document: DocumentNode, - mapping: FieldMapping, - fieldNodeTransformer?: FieldNodeTransformer, - fragments: Record = {}, - ): DocumentNode { - const typeInfo = new TypeInfo(this.transformedSchema); - const newDocument: DocumentNode = visit( - document, - visitWithTypeInfo(typeInfo, { - leave: { - [Kind.SELECTION_SET]: (node: SelectionSetNode): SelectionSetNode => { - const parentType: GraphQLType = typeInfo.getParentType(); - if (parentType != null) { - const parentTypeName = parentType.name; - let newSelections: Array = []; - - node.selections.forEach((selection) => { - if (selection.kind !== Kind.FIELD) { - newSelections.push(selection); - return; - } - - const newName = selection.name.value; - - const transformedSelection = - fieldNodeTransformer != null - ? fieldNodeTransformer( - parentTypeName, - newName, - selection, - fragments, - ) - : selection; - - if (Array.isArray(transformedSelection)) { - newSelections = newSelections.concat(transformedSelection); - return; - } - - if (transformedSelection.kind !== Kind.FIELD) { - newSelections.push(transformedSelection); - return; - } - - const typeMapping = mapping[parentTypeName]; - if (typeMapping == null) { - newSelections.push(transformedSelection); - return; - } - - const oldName = mapping[parentTypeName][newName]; - if (oldName == null) { - newSelections.push(transformedSelection); - return; - } - - newSelections.push({ - ...transformedSelection, - name: { - kind: Kind.NAME, - value: oldName, - }, - alias: { - kind: Kind.NAME, - value: newName, - }, - }); - }); - - return { - ...node, - selections: newSelections, - }; - } - }, - }, - }), - ); - return newDocument; + public transformRequest(originalRequest: Request): Request { + return this.transformer.transformRequest(originalRequest); } } diff --git a/src/wrap/transforms/TransformRootFields.ts b/src/wrap/transforms/TransformRootFields.ts index 94426b4f40c..d2a14908a30 100644 --- a/src/wrap/transforms/TransformRootFields.ts +++ b/src/wrap/transforms/TransformRootFields.ts @@ -1,10 +1,8 @@ import { GraphQLSchema, GraphQLField, GraphQLFieldConfig } from 'graphql'; -import { Transform, Request } from '../../Interfaces'; +import { Transform, Request, FieldNodeTransformer } from '../../Interfaces'; -import TransformObjectFields, { - FieldNodeTransformer, -} from './TransformObjectFields'; +import TransformObjectFields from './TransformObjectFields'; export type RootTransformer = ( operation: 'Query' | 'Mutation' | 'Subscription', diff --git a/src/wrap/transforms/index.ts b/src/wrap/transforms/index.ts index 1e4c85368e8..40a405e6fd8 100644 --- a/src/wrap/transforms/index.ts +++ b/src/wrap/transforms/index.ts @@ -6,14 +6,18 @@ export { default as FilterToSchema } from './FilterToSchema'; export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; export { default as RenameTypes } from './RenameTypes'; -export { default as RenameRootTypes } from './RenameRootTypes'; export { default as FilterTypes } from './FilterTypes'; +export { default as RenameRootTypes } from './RenameRootTypes'; +export { default as TransformCompositeFields } from './TransformCompositeFields'; export { default as TransformRootFields } from './TransformRootFields'; export { default as RenameRootFields } from './RenameRootFields'; export { default as FilterRootFields } from './FilterRootFields'; export { default as TransformObjectFields } from './TransformObjectFields'; export { default as RenameObjectFields } from './RenameObjectFields'; export { default as FilterObjectFields } from './FilterObjectFields'; +export { default as TransformInterfaceFields } from './TransformInterfaceFields'; +export { default as RenameInterfaceFields } from './RenameInterfaceFields'; +export { default as FilterInterfaceFields } from './FilterInterfaceFields'; export { default as TransformQuery } from './TransformQuery'; export { default as ExtendSchema } from './ExtendSchema'; From 9da8f60d3a6205fa8897d0f6d92268a1161ed768 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 24 Mar 2020 00:53:08 -0400 Subject: [PATCH 246/250] refactor(transforms): remove unnecessary exports already contained within transforms folder --- src/wrap/index.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/wrap/index.ts b/src/wrap/index.ts index 5914d23ed35..6e276af170c 100644 --- a/src/wrap/index.ts +++ b/src/wrap/index.ts @@ -12,13 +12,3 @@ export { default as makeRemoteExecutableSchema, createResolver as defaultCreateRemoteResolver, } from './makeRemoteExecutableSchema'; - -// implemented directly within initial query proxy creation -export { default as AddArgumentsAsVariables } from './transforms/AddArgumentsAsVariables'; -// superseded by AddReplacementFragments -export { default as ReplaceFieldWithFragment } from './transforms/ReplaceFieldWithFragment'; -// superseded by AddReplacementSelectionSets -export { default as AddReplacementFragments } from './transforms/AddReplacementFragments'; -// superseded by TransformQuery -export { default as WrapQuery } from './transforms/WrapQuery'; -export { default as ExtractField } from './transforms/ExtractField'; From 6f1dea48a93f501106a26083d2b477b40cd08327 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Tue, 24 Mar 2020 12:55:20 -0400 Subject: [PATCH 247/250] refactor: use field toConfig when possible --- src/test/testAlternateMergeSchemas.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 8c2118d85b2..32b809b0edc 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -7,9 +7,8 @@ import { GraphQLScalarType, FieldNode, printSchema, - GraphQLFieldConfig, - GraphQLObjectType, graphqlSync, + GraphQLField, } from 'graphql'; import { forAwaitEach } from 'iterall'; import { expect } from 'chai'; @@ -349,17 +348,14 @@ describe('transform object fields', () => { before(() => { transformedPropertySchema = transformSchema(propertySchema, [ new TransformObjectFields( - (typeName: string, fieldName: string) => { + (typeName: string, fieldName: string, field: GraphQLField) => { if (typeName !== 'Property' || fieldName !== 'name') { return undefined; } - const type = propertySchema.getType(typeName) as GraphQLObjectType; - const typeConfig = toConfig(type); - const fieldConfig = typeConfig.fields[ - fieldName - ] as GraphQLFieldConfig; - fieldConfig.resolve = () => 'test'; - return fieldConfig; + return { + ...toConfig(field), + resolve: () => 'test', + }; }, (typeName: string, fieldName: string, fieldNode: FieldNode) => { if (typeName !== 'Property' || fieldName !== 'name') { From d91447b2336a3858fa3e3100a4182215210d2a79 Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Wed, 25 Mar 2020 01:29:00 -0400 Subject: [PATCH 248/250] fix(delegation): fix argument/variable bugs closes #46 and re-closes #44 --- src/Interfaces.ts | 19 ++ src/delegate/createRequest.ts | 265 ++++++++++++++++---------- src/delegate/delegateToSchema.ts | 2 + src/test/testAlternateMergeSchemas.ts | 52 ++++- src/test/testDelegateToSchema.ts | 96 ++++++++++ src/wrap/index.ts | 3 +- src/wrap/resolvers.ts | 16 ++ src/wrap/transformSchema.ts | 44 +---- src/wrap/wrapSchema.ts | 40 ++++ 9 files changed, 391 insertions(+), 146 deletions(-) create mode 100644 src/wrap/wrapSchema.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index b055ef811d6..750ebe3c4ed 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -27,6 +27,7 @@ import { GraphQLFieldConfig, FragmentDefinitionNode, SelectionNode, + VariableDefinitionNode, } from 'graphql'; import { TypeMap } from 'graphql/type/schema'; @@ -179,11 +180,13 @@ export interface IDelegateToSchemaOptions { transforms?: Array; skipValidation?: boolean; skipTypeMerging?: boolean; + transformedSchema?: GraphQLSchema; } export interface ICreateRequestFromInfo { info: IGraphQLToolsResolveInfo; schema: GraphQLSchema | SubschemaConfig; + transformedSchema: GraphQLSchema; operation: Operation; fieldName: string; args?: Record; @@ -191,6 +194,22 @@ export interface ICreateRequestFromInfo { fieldNodes?: ReadonlyArray; } +export interface ICreateRequest { + sourceSchema: GraphQLSchema; + sourceParentType: GraphQLObjectType; + sourceFieldName: string; + fragments: Record; + variableDefinitions: ReadonlyArray; + variableValues: Record; + targetSchema: GraphQLSchema; + targetOperation: Operation; + targetField: string; + args: Record; + selectionSet: SelectionSetNode; + fieldNodes: ReadonlyArray; + defaultArgs: Record; +} + export interface IDelegateRequestOptions extends IDelegateToSchemaOptions { request: Request; } diff --git a/src/delegate/createRequest.ts b/src/delegate/createRequest.ts index db0eb17a452..d7783bcd576 100644 --- a/src/delegate/createRequest.ts +++ b/src/delegate/createRequest.ts @@ -23,10 +23,9 @@ import { import { ICreateRequestFromInfo, - Operation, Request, - SubschemaConfig, isSubschemaConfig, + ICreateRequest, } from '../Interfaces'; import { serializeInputValue } from '../utils/index'; @@ -45,43 +44,71 @@ export function getDelegatingOperation( export function createRequestFromInfo({ info, - schema, + schema: subschemaOrSubschemaConfig, + transformedSchema, operation = getDelegatingOperation(info.parentType, info.schema), fieldName = info.fieldName, args, selectionSet, fieldNodes, }: ICreateRequestFromInfo): Request { - return createRequest( - info.schema, - info.fragments, - info.operation.variableDefinitions, - info.variableValues, - schema, - operation, - fieldName, + const sourceParentType = info.parentType; + const sourceFieldName = info.fieldName; + + const fieldArguments = sourceParentType.getFields()[sourceFieldName].args; + const defaultArgs = {}; + fieldArguments.forEach((argument) => { + if (argument.defaultValue != null) { + defaultArgs[argument.name] = argument.defaultValue; + } + }); + + let targetSchema; + if (transformedSchema != null) { + targetSchema = transformedSchema; + } else { + targetSchema = isSubschemaConfig(subschemaOrSubschemaConfig) + ? subschemaOrSubschemaConfig.schema + : subschemaOrSubschemaConfig; + } + + return createRequest({ + sourceSchema: info.schema, + sourceParentType, + sourceFieldName, + fragments: info.fragments, + variableDefinitions: info.operation.variableDefinitions, + variableValues: info.variableValues, + targetSchema, + targetOperation: operation, + targetField: fieldName, args, selectionSet, - selectionSet != null - ? undefined - : fieldNodes != null - ? fieldNodes - : info.fieldNodes, - ); + fieldNodes: + selectionSet != null + ? undefined + : fieldNodes != null + ? fieldNodes + : info.fieldNodes, + defaultArgs, + }); } -export function createRequest( - sourceSchema: GraphQLSchema, - fragments: Record, - variableDefinitions: ReadonlyArray, - variableValues: Record, - targetSchemaOrSchemaConfig: GraphQLSchema | SubschemaConfig, - targetOperation: Operation, - targetField: string, - args: Record, - selectionSet: SelectionSetNode, - fieldNodes: ReadonlyArray, -): Request { +export function createRequest({ + sourceSchema, + sourceParentType, + sourceFieldName, + fragments, + variableDefinitions, + variableValues, + targetSchema, + targetOperation, + targetField, + args, + selectionSet, + fieldNodes, + defaultArgs, +}: ICreateRequest): Request { let argumentNodes: ReadonlyArray; let newSelectionSet: SelectionSetNode = selectionSet; @@ -118,24 +145,25 @@ export function createRequest( variables[varName] = serializeInputValue(varType, variableValues[varName]); } - if (args != null) { - const { - arguments: updatedArguments, - variableDefinitions: updatedVariableDefinitions, - variableValues: updatedVariableValues, - } = updateArguments( - targetSchemaOrSchemaConfig, - targetOperation, - targetField, - argumentNodes, - variableDefinitions, - variables, - args, - ); - argumentNodes = updatedArguments; - newVariableDefinitions = updatedVariableDefinitions; - variables = updatedVariableValues; - } + const { + arguments: updatedArguments, + variableDefinitions: updatedVariableDefinitions, + variableValues: updatedVariableValues, + } = updateArguments( + sourceParentType, + sourceFieldName, + targetSchema, + targetOperation, + targetField, + argumentNodes, + variableDefinitions, + variables, + args, + defaultArgs, + ); + argumentNodes = updatedArguments; + newVariableDefinitions = updatedVariableDefinitions; + variables = updatedVariableValues; const rootfieldNode: FieldNode = { kind: Kind.FIELD, @@ -174,92 +202,129 @@ export function createRequest( } function updateArguments( - subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, + sourceParentType: GraphQLObjectType, + sourceFieldName: string, + targetSchema: GraphQLSchema, operation: OperationTypeNode, fieldName: string, argumentNodes: ReadonlyArray = [], variableDefinitions: ReadonlyArray = [], variableValues: Record = {}, - newArgsMap: Record = {}, + newArgs: Record = {}, + defaultArgs: Record = {}, ): { arguments: Array; variableDefinitions: Array; variableValues: Record; } { - const schema = isSubschemaConfig(subschemaOrSubschemaConfig) - ? subschemaOrSubschemaConfig.schema - : subschemaOrSubschemaConfig; - let type: GraphQLObjectType; if (operation === 'subscription') { - type = schema.getSubscriptionType(); + type = targetSchema.getSubscriptionType(); } else if (operation === 'mutation') { - type = schema.getMutationType(); + type = targetSchema.getMutationType(); } else { - type = schema.getQueryType(); + type = targetSchema.getQueryType(); } - const varNames = variableDefinitions.reduce((acc, def) => { - acc[def.variable.name.value] = true; - return acc; - }, {}); + const updatedVariableDefinitions = {}; + const varNames = {}; + variableDefinitions.forEach((def) => { + const varName = def.variable.name.value; + updatedVariableDefinitions[varName] = def; + varNames[varName] = true; + }); let numGeneratedVariables = 0; const updatedArgs: Record = {}; argumentNodes.forEach((argument: ArgumentNode) => { updatedArgs[argument.name.value] = argument; }); - const newVariableDefinitions: Array = []; const field: GraphQLField = type.getFields()[fieldName]; - field.args.forEach((argument: GraphQLArgument) => { - if (newArgsMap[argument.name]) { + if (field != null) { + field.args.forEach((argument: GraphQLArgument) => { const argName = argument.name; - let varName; - do { - varName = `_v${(numGeneratedVariables++).toString()}_${argName}`; - } while (varNames[varName]); - updatedArgs[argument.name] = { - kind: Kind.ARGUMENT, - name: { - kind: Kind.NAME, - value: argName, - }, - value: { - kind: Kind.VARIABLE, - name: { - kind: Kind.NAME, - value: varName, - }, - }, - }; - varNames[varName] = true; - newVariableDefinitions.push({ - kind: Kind.VARIABLE_DEFINITION, - variable: { - kind: Kind.VARIABLE, - name: { - kind: Kind.NAME, - value: varName, - }, - }, - type: astFromType(argument.type), - }); - variableValues[varName] = serializeInputValue( - argument.type, - newArgsMap[argName], - ); - } - }); + let newArg; + let argType; + if (newArgs[argName] != null) { + newArg = newArgs[argName]; + argType = argument.type; + } else if (updatedArgs[argName] == null && defaultArgs[argName] != null) { + newArg = defaultArgs[argName]; + const sourcefield = sourceParentType.getFields()[sourceFieldName]; + argType = sourcefield.args.find((arg) => arg.name === argName).type; + } + + if (newArg != null) { + numGeneratedVariables++; + updateArgument( + argName, + argType, + numGeneratedVariables, + varNames, + updatedArgs, + updatedVariableDefinitions, + variableValues, + newArg, + ); + } + }); + } return { arguments: Object.keys(updatedArgs).map((argName) => updatedArgs[argName]), - variableDefinitions: newVariableDefinitions, + variableDefinitions: Object.keys(updatedVariableDefinitions).map( + (varName) => updatedVariableDefinitions[varName], + ), variableValues, }; } +function updateArgument( + argName: string, + argType: GraphQLInputType, + numGeneratedVariables: number, + varNames: Record, + updatedArgs: Record, + updatedVariableDefinitions: Record, + variableValues: Record, + newArg: any, +) { + let varName; + do { + varName = `_v${numGeneratedVariables.toString()}_${argName}`; + } while (varNames[varName]); + + updatedArgs[argName] = { + kind: Kind.ARGUMENT, + name: { + kind: Kind.NAME, + value: argName, + }, + value: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: varName, + }, + }, + }; + varNames[varName] = true; + updatedVariableDefinitions[varName] = { + kind: Kind.VARIABLE_DEFINITION, + variable: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: varName, + }, + }, + type: astFromType(argType), + }; + variableValues[varName] = serializeInputValue(argType, newArg); +} + function astFromType(type: GraphQLType): TypeNode { if (isNonNullType(type)) { const innerType = astFromType(type.ofType); diff --git a/src/delegate/delegateToSchema.ts b/src/delegate/delegateToSchema.ts index 6dadeacd382..10e6dade824 100644 --- a/src/delegate/delegateToSchema.ts +++ b/src/delegate/delegateToSchema.ts @@ -51,6 +51,7 @@ export default function delegateToSchema( const { schema: subschemaOrSubschemaConfig, + transformedSchema, info, operation = getDelegatingOperation(info.parentType, info.schema), fieldName = info.fieldName, @@ -63,6 +64,7 @@ export default function delegateToSchema( const request = createRequestFromInfo({ info, schema: subschemaOrSubschemaConfig, + transformedSchema, operation, fieldName, args, diff --git a/src/test/testAlternateMergeSchemas.ts b/src/test/testAlternateMergeSchemas.ts index 32b809b0edc..af4fac1a9f1 100644 --- a/src/test/testAlternateMergeSchemas.ts +++ b/src/test/testAlternateMergeSchemas.ts @@ -27,6 +27,7 @@ import { FilterRootFields, FilterObjectFields, RenameInterfaceFields, + TransformRootFields, } from '../wrap/index'; import { isSpecifiedScalarType, toConfig } from '../polyfills/index'; @@ -343,12 +344,14 @@ describe('merge schemas through transforms', () => { }); describe('transform object fields', () => { - let transformedPropertySchema: GraphQLSchema; - - before(() => { - transformedPropertySchema = transformSchema(propertySchema, [ + it('should work to add a resolver', async () => { + const transformedPropertySchema = transformSchema(propertySchema, [ new TransformObjectFields( - (typeName: string, fieldName: string, field: GraphQLField) => { + ( + typeName: string, + fieldName: string, + field: GraphQLField, + ) => { if (typeName !== 'Property' || fieldName !== 'name') { return undefined; } @@ -372,9 +375,7 @@ describe('transform object fields', () => { }, ), ]); - }); - it('should work', async () => { const result = await graphql( transformedPropertySchema, ` @@ -409,6 +410,43 @@ describe('transform object fields', () => { }); }); +describe('default values', () => { + it('should work to add a default value even when renaming root fields', async () => { + const transformedPropertySchema = transformSchema(propertySchema, [ + new TransformRootFields( + ( + typeName: string, + fieldName: string, + field: GraphQLField, + ) => { + if (typeName === 'Query' && fieldName === 'jsonTest') { + const fieldConfig = toConfig(field); + fieldConfig.args.input.defaultValue = { test: 'test' }; + return { name: 'renamedJsonTest', field: fieldConfig }; + } + }, + ), + ]); + + const result = await graphql( + transformedPropertySchema, + ` + query { + renamedJsonTest + } + `, + ); + + expect(result).to.deep.equal({ + data: { + renamedJsonTest: { + test: 'test', + }, + }, + }); + }); +}); + describe('rename fields that implement interface fields', () => { it('should work', () => { const originalItem = { diff --git a/src/test/testDelegateToSchema.ts b/src/test/testDelegateToSchema.ts index 3119ed5b5e3..c94718fdf72 100644 --- a/src/test/testDelegateToSchema.ts +++ b/src/test/testDelegateToSchema.ts @@ -4,6 +4,8 @@ import { expect } from 'chai'; import delegateToSchema from '../delegate/delegateToSchema'; import mergeSchemas from '../stitch/mergeSchemas'; import { IResolvers } from '../Interfaces'; +import { makeExecutableSchema } from '../generate'; +import { wrapSchema } from '../wrap'; import { propertySchema, @@ -115,3 +117,97 @@ describe('stitching', () => { }); }); }); + +describe('schema delegation', () => { + it('should work even when there are default fields', async () => { + const schema = makeExecutableSchema({ + typeDefs: ` + scalar JSON + type Data { + json(input: JSON = "test"): JSON + } + type Query { + data: Data + } + `, + resolvers: { + Query: { + data: () => ({}), + }, + Data: { + json: (_root, args, context, info) => + delegateToSchema({ + schema: propertySchema, + fieldName: 'jsonTest', + args, + context, + info, + }), + }, + }, + }); + + const result = await graphql( + schema, + ` + query { + data { + json + } + } + `, + ); + + expect(result).to.deep.equal({ + data: { + data: { + json: 'test', + }, + }, + }); + }); + + it('should work even with variables', async () => { + const innerSchema = makeExecutableSchema({ + typeDefs: ` + type User { + id(show: Boolean): ID + } + type Query { + user: User + } + `, + resolvers: { + Query: { + user: () => ({}), + }, + User: { + id: () => '123', + }, + }, + }); + const schema = wrapSchema(innerSchema); + + const result = await graphql( + schema, + ` + query($show: Boolean) { + user { + id(show: $show) + } + } + `, + null, + null, + { show: true }, + ); + + expect(result).to.deep.equal({ + data: { + user: { + id: '123', + }, + }, + }); + }); +}); diff --git a/src/wrap/index.ts b/src/wrap/index.ts index 6e276af170c..4b6b0da6f2f 100644 --- a/src/wrap/index.ts +++ b/src/wrap/index.ts @@ -4,7 +4,8 @@ export { applyResultTransforms, } from './transforms'; -export { default as transformSchema, wrapSchema } from './transformSchema'; +export { transformSchema } from './transformSchema'; +export { wrapSchema } from './wrapSchema'; export * from './transforms/index'; diff --git a/src/wrap/resolvers.ts b/src/wrap/resolvers.ts index 9967bafc3a8..dc9662ba1f7 100644 --- a/src/wrap/resolvers.ts +++ b/src/wrap/resolvers.ts @@ -17,6 +17,8 @@ import { makeMergedType } from '../stitch/makeMergedType'; import { getResponseKeyFromInfo } from '../stitch/getResponseKeyFromInfo'; import { getErrors, getSubschema } from '../stitch/proxiedResult'; +import { applySchemaTransforms } from './transforms'; + export type Mapping = { [typeName: string]: { [fieldName: string]: { @@ -119,6 +121,19 @@ function defaultCreateProxyingResolver({ schema: SubschemaConfig; transforms: Array; }): GraphQLFieldResolver { + let schemaTransforms: Array = []; + if (schema.transforms != null) { + schemaTransforms = schemaTransforms.concat(schema.transforms); + } + if (transforms != null) { + schemaTransforms = schemaTransforms.concat(transforms); + } + + const transformedSchema = applySchemaTransforms( + schema.schema, + schemaTransforms, + ); + return (parent, _args, context, info) => { if (parent != null) { const responseKey = getResponseKeyFromInfo(info); @@ -140,6 +155,7 @@ function defaultCreateProxyingResolver({ context, info, transforms, + transformedSchema, }); }; } diff --git a/src/wrap/transformSchema.ts b/src/wrap/transformSchema.ts index 51c9c962d4e..61481aa0801 100644 --- a/src/wrap/transformSchema.ts +++ b/src/wrap/transformSchema.ts @@ -1,50 +1,18 @@ import { GraphQLSchema } from 'graphql'; -import { addResolversToSchema } from '../generate/index'; import { Transform, SubschemaConfig, - isSubschemaConfig, GraphQLSchemaWithTransforms, } from '../Interfaces'; -import { cloneSchema } from '../utils/index'; -import { generateProxyingResolvers, stripResolvers } from './resolvers'; -import { applySchemaTransforms } from './transforms'; +import { wrapSchema } from './wrapSchema'; -export function wrapSchema( - subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, - transforms?: Array, -): GraphQLSchema { - const subschemaConfig: SubschemaConfig = isSubschemaConfig( - subschemaOrSubschemaConfig, - ) - ? subschemaOrSubschemaConfig - : { schema: subschemaOrSubschemaConfig }; - - const schema = cloneSchema(subschemaConfig.schema); - stripResolvers(schema); - - addResolversToSchema({ - schema, - resolvers: generateProxyingResolvers({ subschemaConfig, transforms }), - resolverValidationOptions: { - allowResolversNotInSchema: true, - }, - }); - - let schemaTransforms: Array = []; - if (subschemaConfig.transforms != null) { - schemaTransforms = schemaTransforms.concat(subschemaConfig.transforms); - } - if (transforms != null) { - schemaTransforms = schemaTransforms.concat(transforms); - } - - return applySchemaTransforms(schema, schemaTransforms); -} - -export default function transformSchema( +// This function is deprecated in favor of wrapSchema as the name is misleading. +// transformSchema does not just "transform" a schema, it wraps a schema with transforms +// using a round of delegation. +// The applySchemaTransforms function actually "transforms" the schema and is used during wrapping. +export function transformSchema( subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, transforms: Array, ): GraphQLSchemaWithTransforms { diff --git a/src/wrap/wrapSchema.ts b/src/wrap/wrapSchema.ts new file mode 100644 index 00000000000..a60bb19c0b6 --- /dev/null +++ b/src/wrap/wrapSchema.ts @@ -0,0 +1,40 @@ +import { GraphQLSchema } from 'graphql'; + +import { addResolversToSchema } from '../generate/index'; +import { Transform, SubschemaConfig, isSubschemaConfig } from '../Interfaces'; +import { cloneSchema } from '../utils/index'; + +import { generateProxyingResolvers, stripResolvers } from './resolvers'; +import { applySchemaTransforms } from './transforms'; + +export function wrapSchema( + subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig, + transforms?: Array, +): GraphQLSchema { + const subschemaConfig: SubschemaConfig = isSubschemaConfig( + subschemaOrSubschemaConfig, + ) + ? subschemaOrSubschemaConfig + : { schema: subschemaOrSubschemaConfig }; + + const schema = cloneSchema(subschemaConfig.schema); + stripResolvers(schema); + + addResolversToSchema({ + schema, + resolvers: generateProxyingResolvers({ subschemaConfig, transforms }), + resolverValidationOptions: { + allowResolversNotInSchema: true, + }, + }); + + let schemaTransforms: Array = []; + if (subschemaConfig.transforms != null) { + schemaTransforms = schemaTransforms.concat(subschemaConfig.transforms); + } + if (transforms != null) { + schemaTransforms = schemaTransforms.concat(transforms); + } + + return applySchemaTransforms(schema, schemaTransforms); +} From 7dcd00616719fb30eafa6c01be6bf76222c9600d Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 26 Mar 2020 11:03:07 -0400 Subject: [PATCH 249/250] fix(stitching): delegateToSchema args specification BREAKING CHANGE: Allow specification of args even with root field transformations. Includes changes to createRequestFromInfo and createRequest signatures and related interfaces, streamlining them to only use the targetOperation and targetFieldName, returning to the original upstream graphql-tools behavior of adding args later as a transform. args passed to delegateToSchema, however, are still optional. All args passed to delegateToSchema are serialized using the targetSchema serialization, if available. --- src/Interfaces.ts | 9 +- src/delegate/createRequest.ts | 266 ++++-------------- src/delegate/delegateToSchema.ts | 14 +- src/utils/astFromType.ts | 37 +++ src/utils/updateArgument.ts | 51 ++++ src/wrap/resolvers.ts | 16 -- .../transforms/AddArgumentsAsVariables.ts | 172 ++++------- src/wrap/transforms/index.ts | 3 +- 8 files changed, 211 insertions(+), 357 deletions(-) create mode 100644 src/utils/astFromType.ts create mode 100644 src/utils/updateArgument.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 750ebe3c4ed..9a176068eeb 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -180,16 +180,12 @@ export interface IDelegateToSchemaOptions { transforms?: Array; skipValidation?: boolean; skipTypeMerging?: boolean; - transformedSchema?: GraphQLSchema; } export interface ICreateRequestFromInfo { info: IGraphQLToolsResolveInfo; - schema: GraphQLSchema | SubschemaConfig; - transformedSchema: GraphQLSchema; operation: Operation; fieldName: string; - args?: Record; selectionSet?: SelectionSetNode; fieldNodes?: ReadonlyArray; } @@ -201,13 +197,10 @@ export interface ICreateRequest { fragments: Record; variableDefinitions: ReadonlyArray; variableValues: Record; - targetSchema: GraphQLSchema; targetOperation: Operation; - targetField: string; - args: Record; + targetFieldName: string; selectionSet: SelectionSetNode; fieldNodes: ReadonlyArray; - defaultArgs: Record; } export interface IDelegateRequestOptions extends IDelegateToSchemaOptions { diff --git a/src/delegate/createRequest.ts b/src/delegate/createRequest.ts index d7783bcd576..7d7813099f9 100644 --- a/src/delegate/createRequest.ts +++ b/src/delegate/createRequest.ts @@ -11,23 +11,14 @@ import { typeFromAST, NamedTypeNode, GraphQLInputType, - GraphQLField, GraphQLArgument, VariableDefinitionNode, - TypeNode, - GraphQLType, SelectionSetNode, - isNonNullType, - isListType, } from 'graphql'; -import { - ICreateRequestFromInfo, - Request, - isSubschemaConfig, - ICreateRequest, -} from '../Interfaces'; +import { ICreateRequestFromInfo, Request, ICreateRequest } from '../Interfaces'; import { serializeInputValue } from '../utils/index'; +import { updateArgument } from '../utils/updateArgument'; export function getDelegatingOperation( parentType: GraphQLObjectType, @@ -44,45 +35,20 @@ export function getDelegatingOperation( export function createRequestFromInfo({ info, - schema: subschemaOrSubschemaConfig, - transformedSchema, operation = getDelegatingOperation(info.parentType, info.schema), fieldName = info.fieldName, - args, selectionSet, fieldNodes, }: ICreateRequestFromInfo): Request { - const sourceParentType = info.parentType; - const sourceFieldName = info.fieldName; - - const fieldArguments = sourceParentType.getFields()[sourceFieldName].args; - const defaultArgs = {}; - fieldArguments.forEach((argument) => { - if (argument.defaultValue != null) { - defaultArgs[argument.name] = argument.defaultValue; - } - }); - - let targetSchema; - if (transformedSchema != null) { - targetSchema = transformedSchema; - } else { - targetSchema = isSubschemaConfig(subschemaOrSubschemaConfig) - ? subschemaOrSubschemaConfig.schema - : subschemaOrSubschemaConfig; - } - return createRequest({ sourceSchema: info.schema, - sourceParentType, - sourceFieldName, + sourceParentType: info.parentType, + sourceFieldName: info.fieldName, fragments: info.fragments, variableDefinitions: info.operation.variableDefinitions, variableValues: info.variableValues, - targetSchema, targetOperation: operation, - targetField: fieldName, - args, + targetFieldName: fieldName, selectionSet, fieldNodes: selectionSet != null @@ -90,7 +56,6 @@ export function createRequestFromInfo({ : fieldNodes != null ? fieldNodes : info.fieldNodes, - defaultArgs, }); } @@ -101,19 +66,13 @@ export function createRequest({ fragments, variableDefinitions, variableValues, - targetSchema, targetOperation, - targetField, - args, + targetFieldName, selectionSet, fieldNodes, - defaultArgs, }: ICreateRequest): Request { let argumentNodes: ReadonlyArray; - let newSelectionSet: SelectionSetNode = selectionSet; - let newVariableDefinitions: ReadonlyArray = variableDefinitions; - if (!selectionSet && fieldNodes != null) { const selections: Array = fieldNodes.reduce( (acc, fieldNode) => @@ -135,51 +94,53 @@ export function createRequest({ argumentNodes = []; } - let variables = {}; - for (const variableDefinition of variableDefinitions) { - const varName = variableDefinition.variable.name.value; + const newVariables = {}; + const variableDefinitionMap = {}; + variableDefinitions.forEach((def) => { + const varName = def.variable.name.value; + variableDefinitionMap[varName] = def; const varType = typeFromAST( sourceSchema, - variableDefinition.type as NamedTypeNode, + def.type as NamedTypeNode, ) as GraphQLInputType; - variables[varName] = serializeInputValue(varType, variableValues[varName]); - } + newVariables[varName] = serializeInputValue( + varType, + variableValues[varName], + ); + }); + + const argumentNodeMap: Record = {}; + argumentNodes.forEach((argument: ArgumentNode) => { + argumentNodeMap[argument.name.value] = argument; + }); - const { - arguments: updatedArguments, - variableDefinitions: updatedVariableDefinitions, - variableValues: updatedVariableValues, - } = updateArguments( + updateArgumentsWithDefaults( sourceParentType, sourceFieldName, - targetSchema, - targetOperation, - targetField, - argumentNodes, - variableDefinitions, - variables, - args, - defaultArgs, + argumentNodeMap, + variableDefinitionMap, + newVariables, ); - argumentNodes = updatedArguments; - newVariableDefinitions = updatedVariableDefinitions; - variables = updatedVariableValues; const rootfieldNode: FieldNode = { kind: Kind.FIELD, alias: null, - arguments: argumentNodes, + arguments: Object.keys(argumentNodeMap).map( + (argName) => argumentNodeMap[argName], + ), selectionSet: newSelectionSet, name: { kind: Kind.NAME, - value: targetField || fieldNodes[0].name.value, + value: targetFieldName || fieldNodes[0].name.value, }, }; const operationDefinition: OperationDefinitionNode = { kind: Kind.OPERATION_DEFINITION, operation: targetOperation, - variableDefinitions: newVariableDefinitions, + variableDefinitions: Object.keys(variableDefinitionMap).map( + (varName) => variableDefinitionMap[varName], + ), selectionSet: { kind: Kind.SELECTION_SET, selections: [rootfieldNode], @@ -197,160 +158,35 @@ export function createRequest({ return { document, - variables, + variables: newVariables, }; } -function updateArguments( +function updateArgumentsWithDefaults( sourceParentType: GraphQLObjectType, sourceFieldName: string, - targetSchema: GraphQLSchema, - operation: OperationTypeNode, - fieldName: string, - argumentNodes: ReadonlyArray = [], - variableDefinitions: ReadonlyArray = [], - variableValues: Record = {}, - newArgs: Record = {}, - defaultArgs: Record = {}, -): { - arguments: Array; - variableDefinitions: Array; - variableValues: Record; -} { - let type: GraphQLObjectType; - if (operation === 'subscription') { - type = targetSchema.getSubscriptionType(); - } else if (operation === 'mutation') { - type = targetSchema.getMutationType(); - } else { - type = targetSchema.getQueryType(); - } - - const updatedVariableDefinitions = {}; - const varNames = {}; - variableDefinitions.forEach((def) => { - const varName = def.variable.name.value; - updatedVariableDefinitions[varName] = def; - varNames[varName] = true; - }); - let numGeneratedVariables = 0; - - const updatedArgs: Record = {}; - argumentNodes.forEach((argument: ArgumentNode) => { - updatedArgs[argument.name.value] = argument; - }); - - const field: GraphQLField = type.getFields()[fieldName]; - if (field != null) { - field.args.forEach((argument: GraphQLArgument) => { - const argName = argument.name; + 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; - let newArg; - let argType; - if (newArgs[argName] != null) { - newArg = newArgs[argName]; - argType = argument.type; - } else if (updatedArgs[argName] == null && defaultArgs[argName] != null) { - newArg = defaultArgs[argName]; - const sourcefield = sourceParentType.getFields()[sourceFieldName]; - argType = sourcefield.args.find((arg) => arg.name === argName).type; - } + if (argumentNodeMap[argName] == null) { + const defaultValue = argument.defaultValue; - if (newArg != null) { - numGeneratedVariables++; + if (defaultValue != null) { updateArgument( argName, - argType, - numGeneratedVariables, - varNames, - updatedArgs, - updatedVariableDefinitions, + sourceArgType, + argumentNodeMap, + variableDefinitionMap, variableValues, - newArg, + serializeInputValue(sourceArgType, defaultValue), ); } - }); - } - - return { - arguments: Object.keys(updatedArgs).map((argName) => updatedArgs[argName]), - variableDefinitions: Object.keys(updatedVariableDefinitions).map( - (varName) => updatedVariableDefinitions[varName], - ), - variableValues, - }; -} - -function updateArgument( - argName: string, - argType: GraphQLInputType, - numGeneratedVariables: number, - varNames: Record, - updatedArgs: Record, - updatedVariableDefinitions: Record, - variableValues: Record, - newArg: any, -) { - let varName; - do { - varName = `_v${numGeneratedVariables.toString()}_${argName}`; - } while (varNames[varName]); - - updatedArgs[argName] = { - kind: Kind.ARGUMENT, - name: { - kind: Kind.NAME, - value: argName, - }, - value: { - kind: Kind.VARIABLE, - name: { - kind: Kind.NAME, - value: varName, - }, - }, - }; - varNames[varName] = true; - updatedVariableDefinitions[varName] = { - kind: Kind.VARIABLE_DEFINITION, - variable: { - kind: Kind.VARIABLE, - name: { - kind: Kind.NAME, - value: varName, - }, - }, - type: astFromType(argType), - }; - variableValues[varName] = serializeInputValue(argType, newArg); -} - -function astFromType(type: GraphQLType): TypeNode { - if (isNonNullType(type)) { - const innerType = astFromType(type.ofType); - if (innerType.kind === Kind.NON_NULL_TYPE) { - throw new Error( - `Invalid type node ${JSON.stringify( - type, - )}. Inner type of non-null type cannot be a non-null type.`, - ); } - return { - kind: Kind.NON_NULL_TYPE, - type: innerType, - }; - } else if (isListType(type)) { - return { - kind: Kind.LIST_TYPE, - type: astFromType(type.ofType), - }; - } - - return { - kind: Kind.NAMED_TYPE, - name: { - kind: Kind.NAME, - value: type.name, - }, - }; + }); } diff --git a/src/delegate/delegateToSchema.ts b/src/delegate/delegateToSchema.ts index 10e6dade824..0fd2387483d 100644 --- a/src/delegate/delegateToSchema.ts +++ b/src/delegate/delegateToSchema.ts @@ -30,6 +30,7 @@ import { CheckResultAndHandleErrors, applyRequestTransforms, applyResultTransforms, + AddArgumentsAsVariables, } from '../wrap/index'; import linkToFetcher from '../stitch/linkToFetcher'; @@ -50,24 +51,18 @@ export default function delegateToSchema( } const { - schema: subschemaOrSubschemaConfig, - transformedSchema, info, operation = getDelegatingOperation(info.parentType, info.schema), fieldName = info.fieldName, returnType = info.returnType, - args, selectionSet, fieldNodes, } = options; const request = createRequestFromInfo({ info, - schema: subschemaOrSubschemaConfig, - transformedSchema, operation, fieldName, - args, selectionSet, fieldNodes, }); @@ -87,6 +82,7 @@ function buildDelegationTransforms( context: Record, targetSchema: GraphQLSchema, fieldName: string, + args: Record, returnType: GraphQLOutputType, transforms: Array, skipTypeMerging: boolean, @@ -125,6 +121,10 @@ function buildDelegationTransforms( ); } + if (args != null) { + delegationTransforms.push(new AddArgumentsAsVariables(targetSchema, args)); + } + delegationTransforms.push( new FilterToSchema(targetSchema), new AddTypenameToAbstract(targetSchema), @@ -140,6 +140,7 @@ export function delegateRequest({ info, operation = getDelegatingOperation(info.parentType, info.schema), fieldName = info.fieldName, + args, returnType = info.returnType, context, transforms = [], @@ -174,6 +175,7 @@ export function delegateRequest({ context, targetSchema, fieldName, + args, returnType, requestTransforms.reverse(), skipTypeMerging, diff --git a/src/utils/astFromType.ts b/src/utils/astFromType.ts new file mode 100644 index 00000000000..9b4cebaf5a7 --- /dev/null +++ b/src/utils/astFromType.ts @@ -0,0 +1,37 @@ +import { + isNonNullType, + Kind, + GraphQLType, + TypeNode, + isListType, +} from 'graphql'; + +export function astFromType(type: GraphQLType): TypeNode { + if (isNonNullType(type)) { + const innerType = astFromType(type.ofType); + if (innerType.kind === Kind.NON_NULL_TYPE) { + throw new Error( + `Invalid type node ${JSON.stringify( + type, + )}. Inner type of non-null type cannot be a non-null type.`, + ); + } + return { + kind: Kind.NON_NULL_TYPE, + type: innerType, + }; + } else if (isListType(type)) { + return { + kind: Kind.LIST_TYPE, + type: astFromType(type.ofType), + }; + } + + return { + kind: Kind.NAMED_TYPE, + name: { + kind: Kind.NAME, + value: type.name, + }, + }; +} diff --git a/src/utils/updateArgument.ts b/src/utils/updateArgument.ts new file mode 100644 index 00000000000..a28acf940da --- /dev/null +++ b/src/utils/updateArgument.ts @@ -0,0 +1,51 @@ +import { + GraphQLInputType, + ArgumentNode, + VariableDefinitionNode, + Kind, +} from 'graphql'; + +import { astFromType } from './astFromType'; + +export function updateArgument( + argName: string, + argType: GraphQLInputType, + argumentNodes: Record, + variableDefinitionsMap: Record, + variableValues: Record, + newArg: any, +): void { + let varName; + let numGeneratedVariables = 0; + do { + varName = `_v${(numGeneratedVariables++).toString()}_${argName}`; + } while (variableDefinitionsMap[varName] != null); + + argumentNodes[argName] = { + kind: Kind.ARGUMENT, + name: { + kind: Kind.NAME, + value: argName, + }, + value: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: varName, + }, + }, + }; + variableDefinitionsMap[varName] = { + kind: Kind.VARIABLE_DEFINITION, + variable: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: varName, + }, + }, + type: astFromType(argType), + }; + + variableValues[varName] = newArg; +} diff --git a/src/wrap/resolvers.ts b/src/wrap/resolvers.ts index dc9662ba1f7..9967bafc3a8 100644 --- a/src/wrap/resolvers.ts +++ b/src/wrap/resolvers.ts @@ -17,8 +17,6 @@ import { makeMergedType } from '../stitch/makeMergedType'; import { getResponseKeyFromInfo } from '../stitch/getResponseKeyFromInfo'; import { getErrors, getSubschema } from '../stitch/proxiedResult'; -import { applySchemaTransforms } from './transforms'; - export type Mapping = { [typeName: string]: { [fieldName: string]: { @@ -121,19 +119,6 @@ function defaultCreateProxyingResolver({ schema: SubschemaConfig; transforms: Array; }): GraphQLFieldResolver { - let schemaTransforms: Array = []; - if (schema.transforms != null) { - schemaTransforms = schemaTransforms.concat(schema.transforms); - } - if (transforms != null) { - schemaTransforms = schemaTransforms.concat(transforms); - } - - const transformedSchema = applySchemaTransforms( - schema.schema, - schemaTransforms, - ); - return (parent, _args, context, info) => { if (parent != null) { const responseKey = getResponseKeyFromInfo(info); @@ -155,7 +140,6 @@ function defaultCreateProxyingResolver({ context, info, transforms, - transformedSchema, }); }; } diff --git a/src/wrap/transforms/AddArgumentsAsVariables.ts b/src/wrap/transforms/AddArgumentsAsVariables.ts index 47cf6ab2049..8f4d5dd4fac 100644 --- a/src/wrap/transforms/AddArgumentsAsVariables.ts +++ b/src/wrap/transforms/AddArgumentsAsVariables.ts @@ -3,23 +3,20 @@ import { DocumentNode, FragmentDefinitionNode, GraphQLArgument, - GraphQLInputType, GraphQLField, GraphQLObjectType, GraphQLSchema, Kind, OperationDefinitionNode, SelectionNode, - TypeNode, VariableDefinitionNode, - isNonNullType, - isListType, } from 'graphql'; import { Transform, Request } from '../../Interfaces'; import { serializeInputValue } from '../../utils/index'; +import { updateArgument } from '../../utils/updateArgument'; -export default class AddArgumentsAsVariablesTransform implements Transform { +export default class AddArgumentsAsVariables implements Transform { private readonly targetSchema: GraphQLSchema; private readonly args: { [key: string]: any }; @@ -31,28 +28,28 @@ export default class AddArgumentsAsVariablesTransform implements Transform { public transformRequest(originalRequest: Request): Request { const { document, newVariables } = addVariablesToRootField( this.targetSchema, - originalRequest.document, + originalRequest, this.args, ); - const variables = { - ...originalRequest.variables, - ...newVariables, - }; + return { document, - variables, + variables: newVariables, }; } } function addVariablesToRootField( targetSchema: GraphQLSchema, - document: DocumentNode, + originalRequest: Request, args: { [key: string]: any }, ): { document: DocumentNode; newVariables: { [key: string]: any }; } { + const document = originalRequest.document; + const variableValues = originalRequest.variables; + const operations: Array = document.definitions.filter( (def) => def.kind === Kind.OPERATION_DEFINITION, ) as Array; @@ -60,27 +57,12 @@ function addVariablesToRootField( (def) => def.kind === Kind.FRAGMENT_DEFINITION, ) as Array; - const variableNames = {}; - const newVariables = {}; - const newOperations = operations.map((operation: OperationDefinitionNode) => { - const originalVariableDefinitions = operation.variableDefinitions; - const existingVariables = originalVariableDefinitions.map( - (variableDefinition: VariableDefinitionNode) => - variableDefinition.variable.name.value, - ); - - let variableCounter = 0; - const variables = {}; - - const generateVariableName = (argName: string) => { - let varName; - do { - varName = `_v${variableCounter.toString()}_${argName}`; - variableCounter++; - } while (existingVariables.indexOf(varName) !== -1); - return varName; - }; + const variableDefinitionMap = {}; + operation.variableDefinitions.forEach((def) => { + const varName = def.variable.name.value; + variableDefinitionMap[varName] = def; + }); let type: GraphQLObjectType | null | undefined; if (operation.operation === 'subscription') { @@ -90,62 +72,31 @@ function addVariablesToRootField( } else { type = targetSchema.getQueryType(); } - const newSelectionSet: Array = []; operation.selectionSet.selections.forEach((selection: SelectionNode) => { if (selection.kind === Kind.FIELD) { - const newArgs: { [name: string]: ArgumentNode } = {}; - if (selection.arguments != null) { - selection.arguments.forEach((argument: ArgumentNode) => { - newArgs[argument.name.value] = argument; - }); - } - const name: string = selection.name.value; - - if (type != null) { - const field: GraphQLField = type.getFields()[name]; - field.args.forEach((argument: GraphQLArgument) => { - if (argument.name in args) { - const variableName = generateVariableName(argument.name); - variableNames[argument.name] = variableName; - newArgs[argument.name] = { - kind: Kind.ARGUMENT, - name: { - kind: Kind.NAME, - value: argument.name, - }, - value: { - kind: Kind.VARIABLE, - name: { - kind: Kind.NAME, - value: variableName, - }, - }, - }; - existingVariables.push(variableName); - variables[variableName] = { - kind: Kind.VARIABLE_DEFINITION, - variable: { - kind: Kind.VARIABLE, - name: { - kind: Kind.NAME, - value: variableName, - }, - }, - type: typeToAst(argument.type), - }; - newVariables[variableName] = serializeInputValue( - argument.type, - args[argument.name], - ); - } - }); - } + const argumentNodes = selection.arguments; + const argumentNodeMap: Record = {}; + argumentNodes.forEach((argument: ArgumentNode) => { + argumentNodeMap[argument.name.value] = argument; + }); + + const targetField = type.getFields()[selection.name.value]; + + updateArguments( + targetField, + argumentNodeMap, + variableDefinitionMap, + variableValues, + args, + ); newSelectionSet.push({ ...selection, - arguments: Object.keys(newArgs).map((argName) => newArgs[argName]), + arguments: Object.keys(argumentNodeMap).map( + (argName) => argumentNodeMap[argName], + ), }); } else { newSelectionSet.push(selection); @@ -154,8 +105,8 @@ function addVariablesToRootField( return { ...operation, - variableDefinitions: originalVariableDefinitions.concat( - Object.keys(variables).map((varName) => variables[varName]), + variableDefinitions: Object.keys(variableDefinitionMap).map( + (varName) => variableDefinitionMap[varName], ), selectionSet: { kind: Kind.SELECTION_SET, @@ -169,35 +120,36 @@ function addVariablesToRootField( ...document, definitions: [...newOperations, ...fragments], }, - newVariables, + newVariables: variableValues, }; } -function typeToAst(type: GraphQLInputType): TypeNode { - if (isNonNullType(type)) { - const innerType = typeToAst(type.ofType); - if ( - innerType.kind === Kind.LIST_TYPE || - innerType.kind === Kind.NAMED_TYPE - ) { - return { - kind: Kind.NON_NULL_TYPE, - type: innerType, - }; - } - throw new Error('Incorrect inner non-null type'); - } else if (isListType(type)) { - return { - kind: Kind.LIST_TYPE, - type: typeToAst(type.ofType), - }; - } else { - return { - kind: Kind.NAMED_TYPE, - name: { - kind: Kind.NAME, - value: type.toString(), - }, - }; +function updateArguments( + targetField: GraphQLField, + argumentNodeMap: Record, + variableDefinitionMap: Record, + variableValues: Record, + newArgs: Record, +): void { + if (targetField != null) { + targetField.args.forEach((argument: GraphQLArgument) => { + const argName = argument.name; + const argType = argument.type; + + if (newArgs[argName] != null) { + const newArg = newArgs[argName]; + + if (newArg != null) { + updateArgument( + argName, + argType, + argumentNodeMap, + variableDefinitionMap, + variableValues, + serializeInputValue(argType, newArg), + ); + } + } + }); } } diff --git a/src/wrap/transforms/index.ts b/src/wrap/transforms/index.ts index 40a405e6fd8..68934b3d614 100644 --- a/src/wrap/transforms/index.ts +++ b/src/wrap/transforms/index.ts @@ -2,6 +2,7 @@ export { default as CheckResultAndHandleErrors } from './CheckResultAndHandleErr export { default as ExpandAbstractTypes } from './ExpandAbstractTypes'; export { default as AddReplacementSelectionSets } from './AddReplacementSelectionSets'; export { default as AddMergedTypeSelectionSets } from './AddMergedTypeSelectionSets'; +export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; export { default as FilterToSchema } from './FilterToSchema'; export { default as AddTypenameToAbstract } from './AddTypenameToAbstract'; @@ -26,8 +27,6 @@ export { default as WrapFields } from './WrapFields'; export { default as HoistField } from './HoistField'; export { default as MapFields } from './MapFields'; -// implemented directly within initial query proxy creation -export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables'; // superseded by AddReplacementFragments export { default as ReplaceFieldWithFragment } from './ReplaceFieldWithFragment'; // superseded by AddReplacementSelectionSets From 0b1477c87edaa33755a9d929c02ad816dc28327c Mon Sep 17 00:00:00 2001 From: yaacovCR Date: Thu, 26 Mar 2020 14:56:18 -0400 Subject: [PATCH 250/250] docs(fix): remove fork reference --- docs/source/index.mdx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/source/index.mdx b/docs/source/index.mdx index fbb2438d881..20f88979499 100644 --- a/docs/source/index.mdx +++ b/docs/source/index.mdx @@ -1,17 +1,15 @@ --- -title: graphql-tools-fork +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, authored originally by the Apollo team. -`graphql-tools-fork` is a fork of the original package with the goal of more active development and engagement with the community. - ```txt -npm install graphql-tools-fork graphql +npm install graphql-tools graphql ``` -Functions in the `graphql-tools` and `graphql-tools-fork` 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. +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. @@ -19,12 +17,12 @@ Even though we recommend a specific way of building GraphQL servers, you can use If you want to bind your JavaScript GraphQL schema to an HTTP server, we recommend using [Apollo Server](https://www.apollographql.com/docs/apollo-server/), which supports every popular Node HTTP server library including Express, Koa, Hapi, and more. -JavaScript GraphQL servers are often developed with `graphql-tools`/`graphql-tools-fork` and `apollo-server-express` together: One to write the schema and resolver code, and the other to connect it to a web server. +JavaScript GraphQL servers are often developed with `graphql-tools` and `apollo-server-express` together: One to write the schema and resolver code, and the other to connect it to a web server. ## The GraphQL-first philosophy 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`/`graphql-tools-fork` 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). +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. 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.