From c38c8538d5e7a12b743f6ea17e5f694a07fe4804 Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 16:05:25 +0200 Subject: [PATCH 01/33] chore(federation): add deps --- package-lock.json | 342 +++++++++++++++++++++++++++++++++------------- package.json | 4 + 2 files changed, 248 insertions(+), 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1fd0aa03a..1ea346d7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,167 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@apollo/federation": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/@apollo/federation/-/federation-0.6.10.tgz", + "integrity": "sha512-KdEg2N/T9OEV9wYtrhkNTxnoemap3gCKaJzxogf6a14cVHk9/HMrv8hDUcl3Uu3fzUq591JVBfBcLJntD+2FkQ==", + "optional": true, + "requires": { + "apollo-env": "^0.5.1", + "apollo-graphql": "^0.3.3", + "apollo-server-env": "2.4.0" + }, + "dependencies": { + "apollo-server-env": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.0.tgz", + "integrity": "sha512-7ispR68lv92viFeu5zsRUVGP+oxsVI3WeeBNniM22Cx619maBUwcYTIC3+Y3LpXILhLZCzA1FASZwusgSlyN9w==", + "optional": true, + "requires": { + "node-fetch": "^2.1.2", + "util.promisify": "^1.0.0" + } + } + } + }, + "@apollo/gateway": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/@apollo/gateway/-/gateway-0.6.14.tgz", + "integrity": "sha512-/DSk0PHhl+2vItPslKvtjnvK1uVYkaKxebkA+S4I9BTB4OJSuiXTvE0aH8ZaEthBEGczCsdmrLRQU4qGJfVcUw==", + "optional": true, + "requires": { + "@apollo/federation": "0.6.10", + "apollo-env": "^0.5.1", + "apollo-graphql": "^0.3.3", + "apollo-server-caching": "0.4.0", + "apollo-server-core": "2.6.9", + "apollo-server-env": "2.4.0", + "loglevel": "^1.6.1", + "loglevel-debug": "^0.0.1", + "pretty-format": "^24.7.0" + }, + "dependencies": { + "@apollographql/apollo-tools": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.3.7.tgz", + "integrity": "sha512-+ertvzAwzkYmuUtT8zH3Zi6jPdyxZwOgnYaZHY7iLnMVJDhQKWlkyjLMF8wyzlPiEdDImVUMm5lOIBZo7LkGlg==", + "requires": { + "apollo-env": "0.5.1" + } + }, + "apollo-cache-control": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.7.5.tgz", + "integrity": "sha512-zCPwHjbo/VlmXl0sclZfBq/MlVVeGUAg02Q259OIXSgHBvn9BbExyz+EkO/DJvZfGMquxqS1X1BFO3VKuLUTdw==", + "requires": { + "apollo-server-env": "2.4.0", + "graphql-extensions": "0.7.7" + } + }, + "apollo-datasource": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.5.0.tgz", + "integrity": "sha512-SVXxJyKlWguuDjxkY/WGlC/ykdsTmPxSF0z8FenagcQ91aPURXzXP1ZDz5PbamY+0iiCRubazkxtTQw4GWTFPg==", + "requires": { + "apollo-server-caching": "0.4.0", + "apollo-server-env": "2.4.0" + } + }, + "apollo-engine-reporting": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-1.3.6.tgz", + "integrity": "sha512-oCoFAUBGveg1i1Sao/2gNsf1kirJBT6vw6Zan9BCNUkyh68ewDts+xRg32VnD9lDhaHpXVJ3tVtuaV44HmdSEw==", + "requires": { + "apollo-engine-reporting-protobuf": "0.3.1", + "apollo-graphql": "^0.3.3", + "apollo-server-core": "2.6.9", + "apollo-server-env": "2.4.0", + "async-retry": "^1.2.1", + "graphql-extensions": "0.7.7" + } + }, + "apollo-engine-reporting-protobuf": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.3.1.tgz", + "integrity": "sha512-Ui3nPG6BSZF8BEqxFs6EkX6mj2OnFLMejxEHSOdM82bakyeouCGd7J0fiy8AD6liJoIyc4X7XfH4ZGGMvMh11A==", + "requires": { + "protobufjs": "^6.8.6" + } + }, + "apollo-server-caching": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.4.0.tgz", + "integrity": "sha512-GTOZdbLhrSOKYNWMYgaqX5cVNSMT0bGUTZKV8/tYlyYmsB6ey7l6iId3Q7UpHS6F6OR2lstz5XaKZ+T3fDfPzQ==", + "requires": { + "lru-cache": "^5.0.0" + } + }, + "apollo-server-core": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.6.9.tgz", + "integrity": "sha512-r2/Kjm1UmxoTViUt5EcExWXkWl0riXsuGyS1q5LpHKKnA+6b+t4LQKECkRU4EWNpuuzJQn7aF7MmMdvURxoEig==", + "requires": { + "@apollographql/apollo-tools": "^0.3.6", + "@apollographql/graphql-playground-html": "1.6.24", + "@types/ws": "^6.0.0", + "apollo-cache-control": "0.7.5", + "apollo-datasource": "0.5.0", + "apollo-engine-reporting": "1.3.6", + "apollo-server-caching": "0.4.0", + "apollo-server-env": "2.4.0", + "apollo-server-errors": "2.3.1", + "apollo-server-plugin-base": "0.5.8", + "apollo-tracing": "0.7.4", + "fast-json-stable-stringify": "^2.0.0", + "graphql-extensions": "0.7.7", + "graphql-subscriptions": "^1.0.0", + "graphql-tag": "^2.9.2", + "graphql-tools": "^4.0.0", + "graphql-upload": "^8.0.2", + "sha.js": "^2.4.11", + "subscriptions-transport-ws": "^0.9.11", + "ws": "^6.0.0" + } + }, + "apollo-server-env": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.0.tgz", + "integrity": "sha512-7ispR68lv92viFeu5zsRUVGP+oxsVI3WeeBNniM22Cx619maBUwcYTIC3+Y3LpXILhLZCzA1FASZwusgSlyN9w==", + "requires": { + "node-fetch": "^2.1.2", + "util.promisify": "^1.0.0" + } + }, + "apollo-server-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.3.1.tgz", + "integrity": "sha512-errZvnh0vUQChecT7M4A/h94dnBSRL213dNxpM5ueMypaLYgnp4hiCTWIEaooo9E4yMGd1qA6WaNbLDG2+bjcg==" + }, + "apollo-server-plugin-base": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.8.tgz", + "integrity": "sha512-ICbaXr0ycQZL5llbtZhg8zyHbxuZ4khdAJsJgiZaUXXP6+F47XfDQ5uwnl/4Sq9fvkpwS0ctvfZ1D+Ks4NvUzA==" + }, + "apollo-tracing": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.7.4.tgz", + "integrity": "sha512-vA0FJCBkFpwdWyVF5UtCqN+enShejyiqSGqq8NxXHU1+GEYTngWa56x9OGsyhX+z4aoDIa3HPKPnP3pjzA0qpg==", + "requires": { + "apollo-server-env": "2.4.0", + "graphql-extensions": "0.7.7" + } + }, + "graphql-extensions": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.7.7.tgz", + "integrity": "sha512-xiTbVGPUpLbF86Bc+zxI/v/axRkwZx3s+y2/kUb2c2MxNZeNhMZEw1dSutuhY2f2JkRkYFJii0ucjIVqPAQ/Lg==", + "requires": { + "@apollographql/apollo-tools": "^0.3.6", + "apollo-server-env": "2.4.0" + } + } + } + }, "@apollo/protobufjs": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.3.tgz", @@ -45,8 +206,7 @@ "@apollographql/graphql-playground-html": { "version": "1.6.24", "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.24.tgz", - "integrity": "sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ==", - "dev": true + "integrity": "sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ==" }, "@babel/code-frame": { "version": "7.0.0", @@ -550,7 +710,6 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^1.1.1", @@ -687,32 +846,27 @@ "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", - "dev": true + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" }, "@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "dev": true + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" }, "@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", - "dev": true + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" }, "@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "dev": true, "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -721,32 +875,27 @@ "@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", - "dev": true + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" }, "@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", - "dev": true + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" }, "@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", - "dev": true + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" }, "@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", - "dev": true + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" }, "@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", - "dev": true + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@samverschueren/stream-to-observable": { "version": "0.3.0", @@ -960,14 +1109,12 @@ "@types/istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", - "dev": true + "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" }, "@types/istanbul-lib-report": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", - "dev": true, "requires": { "@types/istanbul-lib-coverage": "*" } @@ -976,7 +1123,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", - "dev": true, "requires": { "@types/istanbul-lib-coverage": "*", "@types/istanbul-lib-report": "*" @@ -1023,8 +1169,7 @@ "@types/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", - "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==", - "dev": true + "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" }, "@types/mime": { "version": "2.0.1", @@ -1104,7 +1249,6 @@ "version": "13.0.2", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.2.tgz", "integrity": "sha512-lwwgizwk/bIIU+3ELORkyuOgDjCh7zuWDFqRtPPhhVgq9N1F7CvLNKg1TX4f2duwtKQ0p044Au9r1PLIXHrIzQ==", - "dev": true, "requires": { "@types/yargs-parser": "*" } @@ -1112,8 +1256,7 @@ "@types/yargs-parser": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw==", - "dev": true + "integrity": "sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw==" }, "@wry/equality": { "version": "0.1.9", @@ -1591,8 +1734,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" }, "async-retry": { "version": "1.3.1", @@ -1741,8 +1883,7 @@ "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, "balanced-match": { "version": "1.0.0", @@ -2504,7 +2645,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -2559,8 +2699,7 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "deprecated-decorator": { "version": "0.1.6", @@ -2707,7 +2846,6 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", @@ -2721,7 +2859,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -2836,8 +2973,7 @@ "eventemitter3": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", - "dev": true + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" }, "exec-sh": { "version": "0.3.2", @@ -3457,8 +3593,7 @@ "fs-capacitor": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-2.0.4.tgz", - "integrity": "sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA==", - "dev": true + "integrity": "sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA==" }, "fs-extra": { "version": "8.1.0", @@ -3491,8 +3626,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "get-caller-file": { "version": "2.0.5", @@ -3614,7 +3748,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz", "integrity": "sha512-6WzlBFC0lWmXJbIVE8OgFgXIP4RJi3OQgTPa0DVMsDXdpRDjTsM1K9wfl5HSYX7R87QAGlvcv2Y4BIZa/ItonA==", - "dev": true, "requires": { "iterall": "^1.2.1" } @@ -3622,8 +3755,7 @@ "graphql-tag": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.1.tgz", - "integrity": "sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg==", - "dev": true + "integrity": "sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg==" }, "graphql-tools": { "version": "4.0.6", @@ -3726,7 +3858,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -3749,8 +3880,7 @@ "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-value": { "version": "1.0.0", @@ -3823,7 +3953,6 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -4094,8 +4223,7 @@ "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" }, "is-ci": { "version": "2.0.0", @@ -4129,8 +4257,7 @@ "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-descriptor": { "version": "0.1.6", @@ -4229,7 +4356,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, "requires": { "has": "^1.0.1" } @@ -4258,7 +4384,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, "requires": { "has-symbols": "^1.0.0" } @@ -6029,8 +6154,7 @@ "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, "log-symbols": { "version": "3.0.0", @@ -6065,11 +6189,25 @@ "wrap-ansi": "^3.0.1" } }, + "loglevel": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.4.tgz", + "integrity": "sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g==", + "optional": true + }, + "loglevel-debug": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/loglevel-debug/-/loglevel-debug-0.0.1.tgz", + "integrity": "sha1-ifidPboTy6iiy0YV1dOtcYn0iik=", + "optional": true, + "requires": { + "loglevel": "^1.4.0" + } + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "dev": true + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "loose-envify": { "version": "1.4.0", @@ -6084,7 +6222,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "requires": { "yallist": "^3.0.2" } @@ -6611,14 +6748,12 @@ "object-keys": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", - "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", - "dev": true + "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==" }, "object-path": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz", - "integrity": "sha1-NwrnUvvzfePqcKhhwju6iRVpGUk=", - "dev": true + "integrity": "sha1-NwrnUvvzfePqcKhhwju6iRVpGUk=" }, "object-visit": { "version": "1.0.1", @@ -6633,7 +6768,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, "requires": { "define-properties": "^1.1.2", "es-abstract": "^1.5.1" @@ -6701,8 +6835,7 @@ "optional": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz", - "integrity": "sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==", - "dev": true + "integrity": "sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==" }, "optionator": { "version": "0.8.2", @@ -6944,7 +7077,6 @@ "version": "24.9.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", - "dev": true, "requires": { "@jest/types": "^24.9.0", "ansi-regex": "^4.0.0", @@ -6955,8 +7087,7 @@ "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" } } }, @@ -6976,6 +7107,34 @@ "sisteransi": "^1.0.3" } }, + "protobufjs": { + "version": "6.8.8", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", + "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "10.17.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.5.tgz", + "integrity": "sha512-RElZIr/7JreF1eY6oD5RF3kpmdcreuQPjg5ri4oQ5g9sq7YWU8HkfB3eH8GwAwxf5OaCh0VPi7r4N/yoTGelrA==", + "dev": true + } + } + }, "proxy-addr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", @@ -7041,8 +7200,7 @@ "react-is": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", - "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==", - "dev": true + "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==" }, "read-pkg-up": { "version": "4.0.0", @@ -7288,8 +7446,7 @@ "retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, "reusify": { "version": "1.0.4", @@ -7334,8 +7491,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "safe-regex": { "version": "1.1.0", @@ -7512,14 +7668,12 @@ "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -7831,8 +7985,7 @@ "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "stealthy-require": { "version": "1.1.1", @@ -7843,8 +7996,7 @@ "streamsearch": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", - "dev": true + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, "string-length": { "version": "2.0.0", @@ -7935,7 +8087,6 @@ "version": "0.9.16", "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.16.tgz", "integrity": "sha512-pQdoU7nC+EpStXnCfh/+ho0zE0Z+ma+i7xvj7bkXKb1dvYHSZxgRPaU6spRP+Bjzow67c/rRDoix5RT0uU9omw==", - "dev": true, "requires": { "backo2": "^1.0.2", "eventemitter3": "^3.1.0", @@ -7948,7 +8099,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, "requires": { "async-limiter": "~1.0.0" } @@ -7995,8 +8145,7 @@ "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, "symbol-tree": { "version": "3.2.4", @@ -8115,8 +8264,7 @@ "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tough-cookie": { "version": "2.5.0", @@ -8210,9 +8358,9 @@ } }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tunnel-agent": { "version": "0.6.0", @@ -8445,7 +8593,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, "requires": { "define-properties": "^1.1.2", "object.getownpropertydescriptors": "^2.0.3" @@ -8635,7 +8782,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "dev": true, "requires": { "async-limiter": "~1.0.0" } diff --git a/package.json b/package.json index 0667c3657..5506bb99b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,9 @@ "lodash": "4.17.15", "merge-graphql-schemas": "1.7.6", "normalize-path": "3.0.0", + "optional": "0.1.4", "ts-morph": "5.0.0", + "tslib": "^1.10.0", "uuid": "3.4.0" }, "peerDependencies": { @@ -56,6 +58,8 @@ "reflect-metadata": "^0.1.12" }, "optionalDependencies": { + "@apollo/federation": "^0.6.4", + "@apollo/gateway": "^0.6.7", "type-graphql": "^0.17.3" }, "lint-staged": { From 75df3499e7d2f6c01475127a0ea19d19cf26e807 Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 16:05:52 +0200 Subject: [PATCH 02/33] build: import tslib helpers --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index a0e4c5dc3..fa0991cd9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "module": "commonjs", "declaration": true, "noImplicitAny": false, + "importHelpers": true, "removeComments": true, "noLib": false, "emitDecoratorMetadata": true, From f2d5950f548966b00fc25fe869372f4e868b8c07 Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 16:34:23 +0200 Subject: [PATCH 03/33] feat(federation): ResolveReference decorator --- lib/decorators/index.ts | 1 + lib/decorators/resolve-reference.decorator.ts | 9 +++++++++ lib/graphql.constants.ts | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 lib/decorators/resolve-reference.decorator.ts diff --git a/lib/decorators/index.ts b/lib/decorators/index.ts index 794d08e8d..0c3c94dc4 100644 --- a/lib/decorators/index.ts +++ b/lib/decorators/index.ts @@ -10,3 +10,4 @@ export * from './resolver.decorator'; export * from './root.decorator'; export * from './scalar.decorator'; export * from './subscription.decorator'; +export * from './resolve-reference.decorator'; diff --git a/lib/decorators/resolve-reference.decorator.ts b/lib/decorators/resolve-reference.decorator.ts new file mode 100644 index 000000000..55d8921c7 --- /dev/null +++ b/lib/decorators/resolve-reference.decorator.ts @@ -0,0 +1,9 @@ +import { RESOLVER_REFERENCE_KEY, RESOLVER_NAME_METADATA, RESOLVER_REFERENCE_METADATA } from '../graphql.constants'; +import { SetMetadata } from '@nestjs/common'; + +export function ResolveReference(): MethodDecorator { + return (target: Function | Object, key?: string | symbol, descriptor?: any) => { + SetMetadata(RESOLVER_REFERENCE_METADATA, true)(target, key, descriptor); + SetMetadata(RESOLVER_NAME_METADATA, RESOLVER_REFERENCE_KEY)(target, key, descriptor); + }; +} diff --git a/lib/graphql.constants.ts b/lib/graphql.constants.ts index ac016aa1c..97a154d34 100644 --- a/lib/graphql.constants.ts +++ b/lib/graphql.constants.ts @@ -1,6 +1,7 @@ export const RESOLVER_TYPE_METADATA = 'graphql:resolver_type'; export const RESOLVER_NAME_METADATA = 'graphql:resolver_name'; export const RESOLVER_PROPERTY_METADATA = 'graphql:resolve_property'; +export const RESOLVER_REFERENCE_METADATA = 'graphql:resolve_reference'; export const RESOLVER_DELEGATE_METADATA = 'graphql:delegate_property'; export const SCALAR_NAME_METADATA = 'graphql:scalar_name'; export const SCALAR_TYPE_METADATA = 'graphql:scalar_type'; @@ -12,6 +13,8 @@ export const GRAPHQL_MODULE_OPTIONS = 'GqlModuleOptions'; export const GRAPHQL_MODULE_ID = 'GqlModuleId'; export const SUBSCRIPTION_TYPE = 'Subscription'; +export const RESOLVER_REFERENCE_KEY = '__resolveReference'; + export const DEFINITIONS_FILE_HEADER = ` /** ------------------------------------------------------ * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) From 2c83518a7b6275e52556d0f05123f2b6824128eb Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 16:51:39 +0200 Subject: [PATCH 04/33] feat(federation): make getTypesFromPaths method public --- lib/graphql-types.loader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphql-types.loader.ts b/lib/graphql-types.loader.ts index af36c6cae..bb6604627 100644 --- a/lib/graphql-types.loader.ts +++ b/lib/graphql-types.loader.ts @@ -20,7 +20,7 @@ export class GraphQLTypesLoader { return mergeTypes(flatTypes, { all: true }) as any; } - private async getTypesFromPaths(paths: string | string[]): Promise { + async getTypesFromPaths(paths: string | string[]): Promise { paths = util.isArray(paths) ? paths.map(path => normalize(path)) : normalize(paths); From 7e235f9353e1ab3841d2be79751b83558bfdb30d Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 16:52:30 +0200 Subject: [PATCH 05/33] feat(federation): interfaces for gateway module options --- lib/interfaces/gql-gateway-module-options.interface.ts | 7 +++++++ lib/interfaces/index.ts | 1 + 2 files changed, 8 insertions(+) create mode 100644 lib/interfaces/gql-gateway-module-options.interface.ts diff --git a/lib/interfaces/gql-gateway-module-options.interface.ts b/lib/interfaces/gql-gateway-module-options.interface.ts new file mode 100644 index 000000000..9e37d0b8d --- /dev/null +++ b/lib/interfaces/gql-gateway-module-options.interface.ts @@ -0,0 +1,7 @@ +import { Omit, GqlModuleOptions } from './gql-module-options.interface'; +import { GatewayConfig, ServiceEndpointDefinition } from '@apollo/gateway'; +import { GraphQLDataSource } from '@apollo/gateway/src/datasources/types'; + +export interface GatewayModuleOptions extends Pick, Omit {} + +export type GatewayBuildService = (definition: ServiceEndpointDefinition) => GraphQLDataSource; diff --git a/lib/interfaces/index.ts b/lib/interfaces/index.ts index 69ec58599..b32259b47 100644 --- a/lib/interfaces/index.ts +++ b/lib/interfaces/index.ts @@ -1,5 +1,6 @@ export * from './custom-scalar.interface'; export * from './gql-exception-filter.interface'; +export * from './gql-gateway-module-options.interface'; export { GqlModuleAsyncOptions, GqlModuleOptions, From 7a0da242021163f939f5c74c6e4980b6a4d483e8 Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 17:05:27 +0200 Subject: [PATCH 06/33] refactor(federation): barrels --- lib/graphql-definitions.factory.ts | 2 +- lib/graphql-schema-builder.ts | 2 +- lib/graphql.factory.ts | 9 +++------ lib/graphql.module.ts | 18 +++++++++++------- lib/services/index.ts | 6 ++++++ lib/utils/index.ts | 7 +++++++ 6 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 lib/services/index.ts create mode 100644 lib/utils/index.ts diff --git a/lib/graphql-definitions.factory.ts b/lib/graphql-definitions.factory.ts index a5a96c823..3b1c6a31b 100644 --- a/lib/graphql-definitions.factory.ts +++ b/lib/graphql-definitions.factory.ts @@ -5,7 +5,7 @@ import * as chokidar from 'chokidar'; import { printSchema } from 'graphql'; import { GraphQLAstExplorer } from './graphql-ast.explorer'; import { GraphQLTypesLoader } from './graphql-types.loader'; -import { removeTempField } from './utils/remove-temp.util'; +import { removeTempField } from './utils'; export class GraphQLDefinitionsFactory { private readonly gqlAstExplorer = new GraphQLAstExplorer(); diff --git a/lib/graphql-schema-builder.ts b/lib/graphql-schema-builder.ts index 77e6e64f0..26ff2fd12 100644 --- a/lib/graphql-schema-builder.ts +++ b/lib/graphql-schema-builder.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { loadPackage } from '@nestjs/common/utils/load-package.util'; import { GraphQLSchema } from 'graphql'; import { BuildSchemaOptions } from './external/type-graphql.types'; -import { ScalarsExplorerService } from './services/scalars-explorer.service'; +import { ScalarsExplorerService } from './services'; import { lazyMetadataStorage } from './storages/lazy-metadata.storage'; @Injectable() diff --git a/lib/graphql.factory.ts b/lib/graphql.factory.ts index c963ddeb0..0fa531303 100644 --- a/lib/graphql.factory.ts +++ b/lib/graphql.factory.ts @@ -11,12 +11,9 @@ import { import { forEach, isEmpty } from 'lodash'; import { GraphQLAstExplorer } from './graphql-ast.explorer'; import { GraphQLSchemaBuilder } from './graphql-schema-builder'; -import { GqlModuleOptions } from './interfaces/gql-module-options.interface'; -import { DelegatesExplorerService } from './services/delegates-explorer.service'; -import { ResolversExplorerService } from './services/resolvers-explorer.service'; -import { ScalarsExplorerService } from './services/scalars-explorer.service'; -import { extend } from './utils/extend.util'; -import { removeTempField } from './utils/remove-temp.util'; +import { GqlModuleOptions } from './interfaces'; +import { ScalarsExplorerService, DelegatesExplorerService, ResolversExplorerService } from './services'; +import { extend, removeTempField } from './utils'; @Injectable() export class GraphQLFactory { diff --git a/lib/graphql.module.ts b/lib/graphql.module.ts index 16924f0e0..1c660a371 100644 --- a/lib/graphql.module.ts +++ b/lib/graphql.module.ts @@ -19,13 +19,17 @@ import { GqlModuleOptions, GqlOptionsFactory, } from './interfaces/gql-module-options.interface'; -import { DelegatesExplorerService } from './services/delegates-explorer.service'; -import { ResolversExplorerService } from './services/resolvers-explorer.service'; -import { ScalarsExplorerService } from './services/scalars-explorer.service'; -import { extend } from './utils/extend.util'; -import { generateString } from './utils/generate-token.util'; -import { mergeDefaults } from './utils/merge-defaults.util'; -import { normalizeRoutePath } from './utils/normalize-route-path.util'; +import { + DelegatesExplorerService, + ResolversExplorerService, + ScalarsExplorerService, +} from './services'; +import { + extend, + generateString, + mergeDefaults, + normalizeRoutePath, +} from './utils'; @Module({ providers: [ diff --git a/lib/services/index.ts b/lib/services/index.ts new file mode 100644 index 000000000..d65cd6c2d --- /dev/null +++ b/lib/services/index.ts @@ -0,0 +1,6 @@ +export * from './base-explorer.service'; +export * from './delegates-explorer.service'; +export * from './gql-arguments-host'; +export * from './gql-execution-context'; +export * from './resolvers-explorer.service'; +export * from './scalars-explorer.service'; diff --git a/lib/utils/index.ts b/lib/utils/index.ts new file mode 100644 index 000000000..fb46ad94f --- /dev/null +++ b/lib/utils/index.ts @@ -0,0 +1,7 @@ +export * from './async-iterator.util'; +export * from './extend.util'; +export * from './extract-metadata.util'; +export * from './generate-token.util'; +export * from './merge-defaults.util'; +export * from './normalize-route-path.util'; +export * from './remove-temp.util'; From b09070228cd5fa6de80aabb7d5f6131014fd2aeb Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 17:50:56 +0200 Subject: [PATCH 07/33] feat(federation): extract metadata reference resolver --- lib/services/resolvers-explorer.service.ts | 2 ++ lib/utils/extract-metadata.util.ts | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/services/resolvers-explorer.service.ts b/lib/services/resolvers-explorer.service.ts index d0b1b3d43..a448e309e 100644 --- a/lib/services/resolvers-explorer.service.ts +++ b/lib/services/resolvers-explorer.service.ts @@ -70,10 +70,12 @@ export class ResolversExplorerService extends BaseExplorerService { const predicate = ( resolverType: string, isDelegated: boolean, + isReferenceResolver: boolean, isPropertyResolver: boolean, ) => isUndefined(resolverType) || isDelegated || + !isReferenceResolver || (!isPropertyResolver && ![Resolvers.MUTATION, Resolvers.QUERY, Resolvers.SUBSCRIPTION].some( type => type === resolverType, diff --git a/lib/utils/extract-metadata.util.ts b/lib/utils/extract-metadata.util.ts index 544fd76ae..ddaccf4f0 100644 --- a/lib/utils/extract-metadata.util.ts +++ b/lib/utils/extract-metadata.util.ts @@ -2,7 +2,7 @@ import 'reflect-metadata'; import { RESOLVER_DELEGATE_METADATA, RESOLVER_NAME_METADATA, - RESOLVER_PROPERTY_METADATA, + RESOLVER_PROPERTY_METADATA, RESOLVER_REFERENCE_METADATA, RESOLVER_TYPE_METADATA, } from '../graphql.constants'; import { ResolverMetadata } from '../interfaces/resolver-metadata.interface'; @@ -14,6 +14,7 @@ export function extractMetadata( filterPredicate: ( resolverType: string, isDelegated: boolean, + isReferenceResolver?: boolean, isPropertyResolver?: boolean, ) => boolean, ): ResolverMetadata { @@ -26,14 +27,22 @@ export function extractMetadata( RESOLVER_PROPERTY_METADATA, callback, ); + const resolverName = Reflect.getMetadata(RESOLVER_NAME_METADATA, callback); const isDelegated = !!Reflect.getMetadata( RESOLVER_DELEGATE_METADATA, callback, ); - if (filterPredicate(resolverType, isDelegated, isPropertyResolver)) { + + const isReferenceResolver = Reflect.getMetadata( + RESOLVER_REFERENCE_METADATA, + callback, + ); + + if (filterPredicate(resolverType, isDelegated, isReferenceResolver, isPropertyResolver)) { return null; } + return { name: resolverName || methodName, type: resolverType, From ded92ceb1c92bed0a01f37d2658f3d24c6d1e479 Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 17:51:53 +0200 Subject: [PATCH 08/33] feat(federation): graphql gateway module --- lib/graphql-gateway.module.ts | 88 +++++++++++++++++++++++++++++++++++ lib/graphql.constants.ts | 3 ++ lib/index.ts | 1 + 3 files changed, 92 insertions(+) create mode 100644 lib/graphql-gateway.module.ts diff --git a/lib/graphql-gateway.module.ts b/lib/graphql-gateway.module.ts new file mode 100644 index 000000000..e04026f01 --- /dev/null +++ b/lib/graphql-gateway.module.ts @@ -0,0 +1,88 @@ +import { DynamicModule, Inject, Module, OnModuleInit, Optional } from '@nestjs/common'; +import { ApolloServer } from 'apollo-server-express'; +import { HttpAdapterHost } from '@nestjs/core'; +import { GRAPHQL_GATEWAY_BUILD_SERVICE, GRAPHQL_GATEWAY_MODULE_OPTIONS } from './graphql.constants'; +import { GatewayBuildService, GatewayModuleOptions } from './interfaces'; +import { loadPackage } from '@nestjs/common/utils/load-package.util'; + +@Module({}) +export class GraphQLGatewayModule implements OnModuleInit { + private apolloServer: ApolloServer; + + constructor( + @Optional() + private readonly httpAdapterHost: HttpAdapterHost, + @Optional() @Inject(GRAPHQL_GATEWAY_BUILD_SERVICE) + private readonly buildService: GatewayBuildService, + @Inject(GRAPHQL_GATEWAY_MODULE_OPTIONS) + private readonly options: GatewayModuleOptions, + ) {} + + static forRoot(options: GatewayModuleOptions): DynamicModule { + return { + module: GraphQLGatewayModule, + providers: [ + { + provide: GRAPHQL_GATEWAY_MODULE_OPTIONS, + useValue: options, + }, + ], + }; + } + + async onModuleInit() { + if (!this.httpAdapterHost) return; + const { httpAdapter } = this.httpAdapterHost; + + if (!httpAdapter) return; + + const { ApolloGateway } = loadPackage('@apollo/gateway', 'ApolloGateway'); + const app = httpAdapter.getInstance(); + + const { + options: { + __exposeQueryPlanExperimental, + debug, + serviceList, + path, + disableHealthCheck, + onHealthCheck, + cors, + bodyParserConfig, + installSubscriptionHandlers, + }, + buildService, + } = this; + + const gateway = new ApolloGateway({ + __exposeQueryPlanExperimental, + debug, + serviceList, + buildService, + }); + + const { schema, executor } = await gateway.load(); + + this.apolloServer = new ApolloServer({ + executor, + schema, + }); + + this.apolloServer.applyMiddleware({ + app, + path, + disableHealthCheck, + onHealthCheck, + cors, + bodyParserConfig, + }); + + if (installSubscriptionHandlers) { + // TL;DR + throw new Error('No support for subscriptions yet when using Apollo Federation'); + /*this.apolloServer.installSubscriptionHandlers( + httpAdapter.getHttpServer(), + );*/ + } + } +} diff --git a/lib/graphql.constants.ts b/lib/graphql.constants.ts index 97a154d34..f2d04ec9a 100644 --- a/lib/graphql.constants.ts +++ b/lib/graphql.constants.ts @@ -8,6 +8,9 @@ export const SCALAR_TYPE_METADATA = 'graphql:scalar_type'; export const PARAM_ARGS_METADATA = '__routeArguments__'; export const SUBSCRIPTION_OPTIONS_METADATA = 'graphql:subscription_options;'; +export const GRAPHQL_GATEWAY_MODULE_OPTIONS = 'graphql:gateway_module_options'; +export const GRAPHQL_GATEWAY_BUILD_SERVICE = 'graphql:gateway_build_service'; + export const FIELD_TYPENAME = '__resolveType'; export const GRAPHQL_MODULE_OPTIONS = 'GqlModuleOptions'; export const GRAPHQL_MODULE_ID = 'GqlModuleId'; diff --git a/lib/index.ts b/lib/index.ts index 1e7af3922..3572ea0b2 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -8,3 +8,4 @@ export * from './interfaces'; export * from './services/gql-arguments-host'; export * from './services/gql-execution-context'; export * from './tokens'; +export * from './graphql-gateway.module'; From d98b4803298de23111acb69c9a5e58f658570752 Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 17:53:22 +0200 Subject: [PATCH 09/33] feat(federation): graphql federation factory --- lib/graphql-federation.factory.ts | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 lib/graphql-federation.factory.ts diff --git a/lib/graphql-federation.factory.ts b/lib/graphql-federation.factory.ts new file mode 100644 index 000000000..e4a7c560f --- /dev/null +++ b/lib/graphql-federation.factory.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@nestjs/common'; +import { gql } from 'apollo-server-core'; +import { loadPackage } from '@nestjs/common/utils/load-package.util'; + +import { extend } from './utils'; +import { ScalarsExplorerService, DelegatesExplorerService, ResolversExplorerService } from './services'; +import { GqlModuleOptions } from './interfaces'; + +@Injectable() +export class GraphQLFederationFactory { + constructor( + private readonly resolversExplorerService: ResolversExplorerService, + private readonly delegatesExplorerService: DelegatesExplorerService, + private readonly scalarsExplorerService: ScalarsExplorerService, + ) {} + + private extendResolvers(resolvers: any[]) { + return resolvers.reduce((prev, curr) => extend(prev, curr), {}); + } + + async mergeOptions(options: GqlModuleOptions = {}): Promise { + const { buildFederatedSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); + + const resolvers = this.extendResolvers([ + this.resolversExplorerService.explore(), + this.scalarsExplorerService.explore(), + this.delegatesExplorerService.explore(), + ]); + + const schema = buildFederatedSchema([ + { + typeDefs: gql`${options.typeDefs}`, + resolvers, + } + ]); + + return { + ...options, + schema, + typeDefs: undefined, + }; + } +} From 5948d673ed452b1521fff8509aa312d3114509bd Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 17:53:51 +0200 Subject: [PATCH 10/33] feat(federation): graphql federation module --- lib/graphql-federation.module.ts | 157 +++++++++++++++++++++++++++++++ lib/index.ts | 1 + 2 files changed, 158 insertions(+) create mode 100644 lib/graphql-federation.module.ts diff --git a/lib/graphql-federation.module.ts b/lib/graphql-federation.module.ts new file mode 100644 index 000000000..c0e264c96 --- /dev/null +++ b/lib/graphql-federation.module.ts @@ -0,0 +1,157 @@ +import { DynamicModule, Inject, Module, OnModuleInit, Optional, Provider } from '@nestjs/common'; +import { ApolloServer } from 'apollo-server-express'; +import { MetadataScanner } from '@nestjs/core/metadata-scanner'; +import { loadPackage } from '@nestjs/common/utils/load-package.util'; +import { HttpAdapterHost } from '@nestjs/core'; + +import { GraphQLFederationFactory } from './graphql-federation.factory'; +import { ScalarsExplorerService, DelegatesExplorerService, ResolversExplorerService } from './services'; +import { GraphQLAstExplorer } from './graphql-ast.explorer'; +import { GraphQLTypesLoader } from './graphql-types.loader'; +import { GraphQLSchemaBuilder } from './graphql-schema-builder'; +import { GRAPHQL_MODULE_ID, GRAPHQL_MODULE_OPTIONS } from './graphql.constants'; +import { GqlModuleAsyncOptions, GqlModuleOptions, GqlOptionsFactory } from './interfaces'; +import { generateString, extend, mergeDefaults } from './utils'; +import { GraphQLFactory } from './graphql.factory'; + +@Module({ + providers: [ + GraphQLFederationFactory, + GraphQLFactory, + MetadataScanner, + ResolversExplorerService, + DelegatesExplorerService, + ScalarsExplorerService, + GraphQLAstExplorer, + GraphQLTypesLoader, + GraphQLSchemaBuilder, + ], + exports: [], +}) +export class GraphQLFederationModule implements OnModuleInit { + private apolloServer: ApolloServer; + + constructor( + @Optional() + private readonly httpAdapterHost: HttpAdapterHost, + @Inject(GRAPHQL_MODULE_OPTIONS) + private readonly options: GqlModuleOptions, + private readonly graphqlFederationFactory: GraphQLFederationFactory, + private readonly graphqlTypesLoader: GraphQLTypesLoader, + private readonly graphqlFactory: GraphQLFactory, + ) {} + + static forRoot(options: GqlModuleOptions = {}): DynamicModule { + options = mergeDefaults(options); + + return { + module: GraphQLFederationModule, + providers: [ + { + provide: GRAPHQL_MODULE_OPTIONS, + useValue: options, + }, + ], + }; + } + + static forRootAsync(options: GqlModuleAsyncOptions): DynamicModule { + return { + module: GraphQLFederationModule, + imports: options.imports, + providers: [ + ...this.createAsyncProviders(options), + { + provide: GRAPHQL_MODULE_ID, + useValue: generateString(), + }, + ], + }; + } + + private static createAsyncProviders( + options: GqlModuleAsyncOptions, + ): Provider[] { + if (options.useExisting || options.useFactory) { + return [this.createAsyncOptionsProvider(options)]; + } + + return [ + this.createAsyncOptionsProvider(options), + { + provide: options.useClass, + useClass: options.useClass, + }, + ]; + } + + private static createAsyncOptionsProvider( + options: GqlModuleAsyncOptions, + ): Provider { + if (options.useFactory) { + return { + provide: GRAPHQL_MODULE_OPTIONS, + useFactory: options.useFactory, + inject: options.inject || [], + }; + } + + return { + provide: GRAPHQL_MODULE_OPTIONS, + useFactory: (optionsFactory: GqlOptionsFactory) => optionsFactory.createGqlOptions(), + inject: [options.useExisting || options.useClass], + }; + } + + async onModuleInit() { + if (!this.httpAdapterHost) return; + const { httpAdapter } = this.httpAdapterHost; + + if (!httpAdapter) return; + + const { printSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); + + const { + path, + disableHealthCheck, + onHealthCheck, + cors, + bodyParserConfig, + typePaths, + } = this.options; + const app = httpAdapter.getInstance(); + + const typeDefs = await this.graphqlTypesLoader.getTypesFromPaths(typePaths); + + const mergedTypeDefs = extend(typeDefs, this.options.typeDefs); + const apolloOptions = await this.graphqlFederationFactory.mergeOptions({ + ...this.options, + typeDefs: mergedTypeDefs, + }); + + if (this.options.definitions && this.options.definitions.path) { + await this.graphqlFactory.generateDefinitions( + printSchema(apolloOptions.schema), + this.options, + ); + } + + this.apolloServer = new ApolloServer(apolloOptions as any); + this.apolloServer.applyMiddleware({ + app, + path, + disableHealthCheck, + onHealthCheck, + cors, + bodyParserConfig, + }); + + if (this.options.installSubscriptionHandlers) { + // TL;DR + throw new Error('No support for subscriptions yet when using Apollo Federation'); + /*this.apolloServer.installSubscriptionHandlers( + httpAdapter.getHttpServer(), + );*/ + } + } +} diff --git a/lib/index.ts b/lib/index.ts index 3572ea0b2..5bee303f5 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -8,4 +8,5 @@ export * from './interfaces'; export * from './services/gql-arguments-host'; export * from './services/gql-execution-context'; export * from './tokens'; +export * from './graphql-federation.module'; export * from './graphql-gateway.module'; From 63fa587d58e624bd1bcd296e9b287f32e749b82d Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 20:38:49 +0200 Subject: [PATCH 11/33] fix(federation): filter resolvers predication --- lib/services/resolvers-explorer.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/services/resolvers-explorer.service.ts b/lib/services/resolvers-explorer.service.ts index a448e309e..316bd1d39 100644 --- a/lib/services/resolvers-explorer.service.ts +++ b/lib/services/resolvers-explorer.service.ts @@ -75,8 +75,7 @@ export class ResolversExplorerService extends BaseExplorerService { ) => isUndefined(resolverType) || isDelegated || - !isReferenceResolver || - (!isPropertyResolver && + (!isReferenceResolver && !isPropertyResolver && ![Resolvers.MUTATION, Resolvers.QUERY, Resolvers.SUBSCRIPTION].some( type => type === resolverType, )); From 5940608c2ae4958ccff05878b0dec3e0643afbe1 Mon Sep 17 00:00:00 2001 From: marcus-sa Date: Sat, 22 Jun 2019 20:40:02 +0200 Subject: [PATCH 12/33] fix(federation): use resolver reference instead of resolver name --- lib/decorators/resolve-reference.decorator.ts | 3 +-- lib/utils/extract-metadata.util.ts | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/decorators/resolve-reference.decorator.ts b/lib/decorators/resolve-reference.decorator.ts index 55d8921c7..1405339fa 100644 --- a/lib/decorators/resolve-reference.decorator.ts +++ b/lib/decorators/resolve-reference.decorator.ts @@ -1,9 +1,8 @@ -import { RESOLVER_REFERENCE_KEY, RESOLVER_NAME_METADATA, RESOLVER_REFERENCE_METADATA } from '../graphql.constants'; +import { RESOLVER_REFERENCE_METADATA } from '../graphql.constants'; import { SetMetadata } from '@nestjs/common'; export function ResolveReference(): MethodDecorator { return (target: Function | Object, key?: string | symbol, descriptor?: any) => { SetMetadata(RESOLVER_REFERENCE_METADATA, true)(target, key, descriptor); - SetMetadata(RESOLVER_NAME_METADATA, RESOLVER_REFERENCE_KEY)(target, key, descriptor); }; } diff --git a/lib/utils/extract-metadata.util.ts b/lib/utils/extract-metadata.util.ts index ddaccf4f0..3ecb72062 100644 --- a/lib/utils/extract-metadata.util.ts +++ b/lib/utils/extract-metadata.util.ts @@ -1,11 +1,13 @@ import 'reflect-metadata'; +import { ResolverMetadata } from '../interfaces/resolver-metadata.interface'; import { RESOLVER_DELEGATE_METADATA, RESOLVER_NAME_METADATA, - RESOLVER_PROPERTY_METADATA, RESOLVER_REFERENCE_METADATA, + RESOLVER_PROPERTY_METADATA, + RESOLVER_REFERENCE_KEY, + RESOLVER_REFERENCE_METADATA, RESOLVER_TYPE_METADATA, } from '../graphql.constants'; -import { ResolverMetadata } from '../interfaces/resolver-metadata.interface'; export function extractMetadata( instance: Object, @@ -23,7 +25,7 @@ export function extractMetadata( Reflect.getMetadata(RESOLVER_TYPE_METADATA, callback) || Reflect.getMetadata(RESOLVER_TYPE_METADATA, instance.constructor); - const isPropertyResolver = Reflect.getMetadata( + const isPropertyResolver = !!Reflect.getMetadata( RESOLVER_PROPERTY_METADATA, callback, ); @@ -34,18 +36,23 @@ export function extractMetadata( callback, ); - const isReferenceResolver = Reflect.getMetadata( + const isReferenceResolver = !!Reflect.getMetadata( RESOLVER_REFERENCE_METADATA, callback, ); + if (filterPredicate(resolverType, isDelegated, isReferenceResolver, isPropertyResolver)) { return null; } + const name = isReferenceResolver + ? RESOLVER_REFERENCE_KEY + : resolverName || methodName; + return { - name: resolverName || methodName, type: resolverType, methodName, + name, }; } From 3a548328afdf99f5a7424251c8ba799ddc58a350 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Tue, 29 Oct 2019 10:56:57 +0100 Subject: [PATCH 13/33] style: formatting with prettier --- .prettierrc | 1 + lib/decorators/index.ts | 2 +- lib/graphql-federation.factory.ts | 12 +++++++++--- lib/graphql-federation.module.ts | 21 +++++++++++---------- lib/graphql-gateway.module.ts | 10 ++++++---- lib/index.ts | 4 ++-- lib/utils/extract-metadata.util.ts | 20 ++++---------------- 7 files changed, 34 insertions(+), 36 deletions(-) diff --git a/.prettierrc b/.prettierrc index ffa374c07..3e0689773 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,5 @@ { + "printWidth": 100, "trailingComma": "all", "singleQuote": true } \ No newline at end of file diff --git a/lib/decorators/index.ts b/lib/decorators/index.ts index 0c3c94dc4..71d011f95 100644 --- a/lib/decorators/index.ts +++ b/lib/decorators/index.ts @@ -6,8 +6,8 @@ export * from './mutation.decorator'; export * from './parent.decorator'; export * from './query.decorator'; export * from './resolve-property.decorator'; +export * from './resolve-reference.decorator'; export * from './resolver.decorator'; export * from './root.decorator'; export * from './scalar.decorator'; export * from './subscription.decorator'; -export * from './resolve-reference.decorator'; diff --git a/lib/graphql-federation.factory.ts b/lib/graphql-federation.factory.ts index e4a7c560f..4762ba839 100644 --- a/lib/graphql-federation.factory.ts +++ b/lib/graphql-federation.factory.ts @@ -3,7 +3,11 @@ import { gql } from 'apollo-server-core'; import { loadPackage } from '@nestjs/common/utils/load-package.util'; import { extend } from './utils'; -import { ScalarsExplorerService, DelegatesExplorerService, ResolversExplorerService } from './services'; +import { + ScalarsExplorerService, + DelegatesExplorerService, + ResolversExplorerService, +} from './services'; import { GqlModuleOptions } from './interfaces'; @Injectable() @@ -29,9 +33,11 @@ export class GraphQLFederationFactory { const schema = buildFederatedSchema([ { - typeDefs: gql`${options.typeDefs}`, + typeDefs: gql` + ${options.typeDefs} + `, resolvers, - } + }, ]); return { diff --git a/lib/graphql-federation.module.ts b/lib/graphql-federation.module.ts index c0e264c96..2bb1a7a27 100644 --- a/lib/graphql-federation.module.ts +++ b/lib/graphql-federation.module.ts @@ -5,7 +5,11 @@ import { loadPackage } from '@nestjs/common/utils/load-package.util'; import { HttpAdapterHost } from '@nestjs/core'; import { GraphQLFederationFactory } from './graphql-federation.factory'; -import { ScalarsExplorerService, DelegatesExplorerService, ResolversExplorerService } from './services'; +import { + ScalarsExplorerService, + DelegatesExplorerService, + ResolversExplorerService, +} from './services'; import { GraphQLAstExplorer } from './graphql-ast.explorer'; import { GraphQLTypesLoader } from './graphql-types.loader'; import { GraphQLSchemaBuilder } from './graphql-schema-builder'; @@ -69,9 +73,7 @@ export class GraphQLFederationModule implements OnModuleInit { }; } - private static createAsyncProviders( - options: GqlModuleAsyncOptions, - ): Provider[] { + private static createAsyncProviders(options: GqlModuleAsyncOptions): Provider[] { if (options.useExisting || options.useFactory) { return [this.createAsyncOptionsProvider(options)]; } @@ -85,9 +87,7 @@ export class GraphQLFederationModule implements OnModuleInit { ]; } - private static createAsyncOptionsProvider( - options: GqlModuleAsyncOptions, - ): Provider { + private static createAsyncOptionsProvider(options: GqlModuleAsyncOptions): Provider { if (options.useFactory) { return { provide: GRAPHQL_MODULE_OPTIONS, @@ -104,10 +104,11 @@ export class GraphQLFederationModule implements OnModuleInit { } async onModuleInit() { - if (!this.httpAdapterHost) return; - const { httpAdapter } = this.httpAdapterHost; + const { httpAdapter } = this.httpAdapterHost || {}; - if (!httpAdapter) return; + if (!httpAdapter) { + return; + } const { printSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); diff --git a/lib/graphql-gateway.module.ts b/lib/graphql-gateway.module.ts index e04026f01..19b23e16a 100644 --- a/lib/graphql-gateway.module.ts +++ b/lib/graphql-gateway.module.ts @@ -12,7 +12,8 @@ export class GraphQLGatewayModule implements OnModuleInit { constructor( @Optional() private readonly httpAdapterHost: HttpAdapterHost, - @Optional() @Inject(GRAPHQL_GATEWAY_BUILD_SERVICE) + @Optional() + @Inject(GRAPHQL_GATEWAY_BUILD_SERVICE) private readonly buildService: GatewayBuildService, @Inject(GRAPHQL_GATEWAY_MODULE_OPTIONS) private readonly options: GatewayModuleOptions, @@ -31,10 +32,11 @@ export class GraphQLGatewayModule implements OnModuleInit { } async onModuleInit() { - if (!this.httpAdapterHost) return; - const { httpAdapter } = this.httpAdapterHost; + const { httpAdapter } = this.httpAdapterHost || {}; - if (!httpAdapter) return; + if (!httpAdapter) { + return; + } const { ApolloGateway } = loadPackage('@apollo/gateway', 'ApolloGateway'); const app = httpAdapter.getInstance(); diff --git a/lib/index.ts b/lib/index.ts index 5bee303f5..a07886f7a 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -3,10 +3,10 @@ export * from './graphql-ast.explorer'; export * from './graphql-definitions.factory'; export * from './graphql-types.loader'; export * from './graphql.factory'; +export * from './graphql-federation.module'; +export * from './graphql-gateway.module'; export * from './graphql.module'; export * from './interfaces'; export * from './services/gql-arguments-host'; export * from './services/gql-execution-context'; export * from './tokens'; -export * from './graphql-federation.module'; -export * from './graphql-gateway.module'; diff --git a/lib/utils/extract-metadata.util.ts b/lib/utils/extract-metadata.util.ts index 3ecb72062..3877bf433 100644 --- a/lib/utils/extract-metadata.util.ts +++ b/lib/utils/extract-metadata.util.ts @@ -25,30 +25,18 @@ export function extractMetadata( Reflect.getMetadata(RESOLVER_TYPE_METADATA, callback) || Reflect.getMetadata(RESOLVER_TYPE_METADATA, instance.constructor); - const isPropertyResolver = !!Reflect.getMetadata( - RESOLVER_PROPERTY_METADATA, - callback, - ); + const isPropertyResolver = !!Reflect.getMetadata(RESOLVER_PROPERTY_METADATA, callback); const resolverName = Reflect.getMetadata(RESOLVER_NAME_METADATA, callback); - const isDelegated = !!Reflect.getMetadata( - RESOLVER_DELEGATE_METADATA, - callback, - ); - - const isReferenceResolver = !!Reflect.getMetadata( - RESOLVER_REFERENCE_METADATA, - callback, - ); + const isDelegated = !!Reflect.getMetadata(RESOLVER_DELEGATE_METADATA, callback); + const isReferenceResolver = !!Reflect.getMetadata(RESOLVER_REFERENCE_METADATA, callback); if (filterPredicate(resolverType, isDelegated, isReferenceResolver, isPropertyResolver)) { return null; } - const name = isReferenceResolver - ? RESOLVER_REFERENCE_KEY - : resolverName || methodName; + const name = isReferenceResolver ? RESOLVER_REFERENCE_KEY : resolverName || methodName; return { type: resolverType, From 073a701baaf266836124140cc3618fd5c2734cce Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Tue, 29 Oct 2019 10:59:19 +0100 Subject: [PATCH 14/33] test: add integration test for federation module --- tests/e2e/graphql-federation.spec.ts | 121 ++++++++++++++++++ .../posts-service/federation-posts.module.ts | 14 ++ .../posts-service/posts/posts.interfaces.ts | 6 + .../posts-service/posts/posts.module.ts | 9 ++ .../posts-service/posts/posts.resolvers.ts | 26 ++++ .../posts-service/posts/posts.service.ts | 26 ++++ .../posts-service/posts/posts.types.graphql | 15 +++ .../posts-service/posts/users.resolvers.ts | 12 ++ .../users-service/federation-users.module.ts | 14 ++ .../users-service/users/users.interfaces.ts | 4 + .../users-service/users/users.module.ts | 8 ++ .../users-service/users/users.resolvers.ts | 26 ++++ .../users-service/users/users.service.ts | 16 +++ .../users-service/users/users.types.graphql | 8 ++ 14 files changed, 305 insertions(+) create mode 100644 tests/e2e/graphql-federation.spec.ts create mode 100644 tests/graphql-federation/posts-service/federation-posts.module.ts create mode 100644 tests/graphql-federation/posts-service/posts/posts.interfaces.ts create mode 100644 tests/graphql-federation/posts-service/posts/posts.module.ts create mode 100644 tests/graphql-federation/posts-service/posts/posts.resolvers.ts create mode 100644 tests/graphql-federation/posts-service/posts/posts.service.ts create mode 100644 tests/graphql-federation/posts-service/posts/posts.types.graphql create mode 100644 tests/graphql-federation/posts-service/posts/users.resolvers.ts create mode 100644 tests/graphql-federation/users-service/federation-users.module.ts create mode 100644 tests/graphql-federation/users-service/users/users.interfaces.ts create mode 100644 tests/graphql-federation/users-service/users/users.module.ts create mode 100644 tests/graphql-federation/users-service/users/users.resolvers.ts create mode 100644 tests/graphql-federation/users-service/users/users.service.ts create mode 100644 tests/graphql-federation/users-service/users/users.types.graphql diff --git a/tests/e2e/graphql-federation.spec.ts b/tests/e2e/graphql-federation.spec.ts new file mode 100644 index 000000000..bd04dcf8e --- /dev/null +++ b/tests/e2e/graphql-federation.spec.ts @@ -0,0 +1,121 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule as PostsModule } from '../graphql-federation/posts-service/federation-posts.module'; +import { AppModule as UsersModule } from '../graphql-federation/users-service/federation-users.module'; + +describe('GraphQL', () => { + let app: INestApplication; + + describe('UsersService', () => { + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [UsersModule], + }).compile(); + + app = module.createNestApplication(); + await app.init(); + }); + + it(`should return query result`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getUser(id: "5") { + id, + name, + } + }`, + }) + .expect(200, { + data: { + getUser: { + id: '5', + name: 'GraphQL', + }, + }, + }); + }); + }); + + describe('PostsService', () => { + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [PostsModule], + }).compile(); + + app = module.createNestApplication(); + await app.init(); + }); + + it(`should return query result`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getPosts { + id, + title, + body, + } + }`, + }) + .expect(200, { + data: { + getPosts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + ], + }, + }); + }); + + it('should return a stripped reference', () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getPosts { + id, + title, + body, + user { + id + } + } + }`, + }) + .expect(200, { + data: { + getPosts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + user: { + id: '5', + }, + }, + ], + }, + }); + }); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/tests/graphql-federation/posts-service/federation-posts.module.ts b/tests/graphql-federation/posts-service/federation-posts.module.ts new file mode 100644 index 000000000..8ecd698e1 --- /dev/null +++ b/tests/graphql-federation/posts-service/federation-posts.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { GraphQLFederationModule } from '../../../lib/graphql-federation.module'; +import { PostsModule } from './posts/posts.module'; +import { join } from 'path'; + +@Module({ + imports: [ + GraphQLFederationModule.forRoot({ + typePaths: [join(__dirname, '**/*.graphql')], + }), + PostsModule, + ], +}) +export class AppModule {} diff --git a/tests/graphql-federation/posts-service/posts/posts.interfaces.ts b/tests/graphql-federation/posts-service/posts/posts.interfaces.ts new file mode 100644 index 000000000..986518ad2 --- /dev/null +++ b/tests/graphql-federation/posts-service/posts/posts.interfaces.ts @@ -0,0 +1,6 @@ +export interface Post { + id: string; + title: string; + body: string; + userId: string; +} diff --git a/tests/graphql-federation/posts-service/posts/posts.module.ts b/tests/graphql-federation/posts-service/posts/posts.module.ts new file mode 100644 index 000000000..9fa531c4b --- /dev/null +++ b/tests/graphql-federation/posts-service/posts/posts.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { PostsResolvers } from './posts.resolvers'; +import { UsersResolvers } from './users.resolvers'; +import { PostsService } from './posts.service'; + +@Module({ + providers: [PostsResolvers, PostsService, UsersResolvers], +}) +export class PostsModule {} diff --git a/tests/graphql-federation/posts-service/posts/posts.resolvers.ts b/tests/graphql-federation/posts-service/posts/posts.resolvers.ts new file mode 100644 index 000000000..6b80e7436 --- /dev/null +++ b/tests/graphql-federation/posts-service/posts/posts.resolvers.ts @@ -0,0 +1,26 @@ +import { + Args, + Mutation, + Query, + Resolver, + ResolveReference, + Parent, + ResolveProperty, +} from '../../../../lib'; +import { PostsService } from './posts.service'; +import { Post } from './posts.interfaces'; + +@Resolver('Post') +export class PostsResolvers { + constructor(private readonly postsService: PostsService) {} + + @Query('getPosts') + getPosts() { + return this.postsService.findAll(); + } + + @ResolveProperty('user') + getUser(@Parent() post: Post) { + return { __typename: 'User', id: post.userId }; + } +} diff --git a/tests/graphql-federation/posts-service/posts/posts.service.ts b/tests/graphql-federation/posts-service/posts/posts.service.ts new file mode 100644 index 000000000..1a096af41 --- /dev/null +++ b/tests/graphql-federation/posts-service/posts/posts.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { Post } from './posts.interfaces'; + +@Injectable() +export class PostsService { + private readonly posts: Post[] = [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + userId: '5', + }, + ]; + + findAll() { + return Promise.resolve(this.posts); + } + + findById(id: string) { + return Promise.resolve(this.posts.find(p => p.id === id)); + } + + findByUserId(id: string) { + return Promise.resolve(this.posts.filter(p => p.userId === id)); + } +} diff --git a/tests/graphql-federation/posts-service/posts/posts.types.graphql b/tests/graphql-federation/posts-service/posts/posts.types.graphql new file mode 100644 index 000000000..89d8bbf5d --- /dev/null +++ b/tests/graphql-federation/posts-service/posts/posts.types.graphql @@ -0,0 +1,15 @@ +type Post @key(fields: "id") { + id: ID! + title: String! + body: String! + user: User +} + +extend type User @key(fields: "id") { + id: ID! @external + posts: [Post] +} + +extend type Query { + getPosts: [Post] +} \ No newline at end of file diff --git a/tests/graphql-federation/posts-service/posts/users.resolvers.ts b/tests/graphql-federation/posts-service/posts/users.resolvers.ts new file mode 100644 index 000000000..eede198b1 --- /dev/null +++ b/tests/graphql-federation/posts-service/posts/users.resolvers.ts @@ -0,0 +1,12 @@ +import { Resolver, ResolveProperty } from '../../../../lib'; +import { PostsService } from './posts.service'; + +@Resolver('User') +export class UsersResolvers { + constructor(private readonly postsService: PostsService) {} + + @ResolveProperty('posts') + getPosts(reference: any) { + return this.postsService.findByUserId(reference.id); + } +} diff --git a/tests/graphql-federation/users-service/federation-users.module.ts b/tests/graphql-federation/users-service/federation-users.module.ts new file mode 100644 index 000000000..d706e28d7 --- /dev/null +++ b/tests/graphql-federation/users-service/federation-users.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { GraphQLFederationModule } from '../../../lib/graphql-federation.module'; +import { UsersModule } from './users/users.module'; +import { join } from 'path'; + +@Module({ + imports: [ + GraphQLFederationModule.forRoot({ + typePaths: [join(__dirname, '**/*.graphql')], + }), + UsersModule, + ], +}) +export class AppModule {} diff --git a/tests/graphql-federation/users-service/users/users.interfaces.ts b/tests/graphql-federation/users-service/users/users.interfaces.ts new file mode 100644 index 000000000..c58931c6c --- /dev/null +++ b/tests/graphql-federation/users-service/users/users.interfaces.ts @@ -0,0 +1,4 @@ +export interface User { + id: string; + name: string; +} diff --git a/tests/graphql-federation/users-service/users/users.module.ts b/tests/graphql-federation/users-service/users/users.module.ts new file mode 100644 index 000000000..d71042e83 --- /dev/null +++ b/tests/graphql-federation/users-service/users/users.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { UsersResolvers } from './users.resolvers'; +import { UsersService } from './users.service'; + +@Module({ + providers: [UsersResolvers, UsersService], +}) +export class UsersModule {} diff --git a/tests/graphql-federation/users-service/users/users.resolvers.ts b/tests/graphql-federation/users-service/users/users.resolvers.ts new file mode 100644 index 000000000..7762d9722 --- /dev/null +++ b/tests/graphql-federation/users-service/users/users.resolvers.ts @@ -0,0 +1,26 @@ +import { + Args, + Mutation, + Query, + Resolver, + ResolveReference, + Parent, + ResolveProperty, +} from '../../../../lib'; +import { UsersService } from './users.service'; +import { User } from './users.interfaces'; + +@Resolver('User') +export class UsersResolvers { + constructor(private readonly usersService: UsersService) {} + + @Query() + getUser(@Args('id') id: string) { + return this.usersService.findById(id); + } + + @ResolveReference() + resolveReference(reference: any) { + return this.usersService.findById(reference.id); + } +} diff --git a/tests/graphql-federation/users-service/users/users.service.ts b/tests/graphql-federation/users-service/users/users.service.ts new file mode 100644 index 000000000..c4dffb1db --- /dev/null +++ b/tests/graphql-federation/users-service/users/users.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { User } from './users.interfaces'; + +@Injectable() +export class UsersService { + private readonly users: User[] = [ + { + id: '5', + name: 'GraphQL', + }, + ]; + + findById(id: string) { + return Promise.resolve(this.users.find(p => p.id === id)); + } +} diff --git a/tests/graphql-federation/users-service/users/users.types.graphql b/tests/graphql-federation/users-service/users/users.types.graphql new file mode 100644 index 000000000..d1ebd5b21 --- /dev/null +++ b/tests/graphql-federation/users-service/users/users.types.graphql @@ -0,0 +1,8 @@ +type User @key(fields: "id") { + id: ID! + name: String! +} + +extend type Query { + getUser(id: ID!): User +} \ No newline at end of file From bef82ab62e776bbf3b66e3072d48f570abee1b48 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Tue, 29 Oct 2019 10:59:41 +0100 Subject: [PATCH 15/33] test: add integration test for gateway module --- package.json | 2 +- tests/e2e/graphql-gateway.spec.ts | 113 ++++++++++++++++++ .../gateway/gateway.module.ts | 15 +++ 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/graphql-gateway.spec.ts create mode 100644 tests/graphql-federation/gateway/gateway.module.ts diff --git a/package.json b/package.json index 5506bb99b..4e01ad4f9 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "publish:npm": "npm publish --access public", "prepublish:next": "npm run build", "publish:next": "npm publish --access public --tag next", - "test:integration": "jest --config ./tests/jest-e2e.json" + "test:integration": "jest --config ./tests/jest-e2e.json --runInBand" }, "devDependencies": { "@nestjs/common": "6.10.14", diff --git a/tests/e2e/graphql-gateway.spec.ts b/tests/e2e/graphql-gateway.spec.ts new file mode 100644 index 000000000..e6261e57d --- /dev/null +++ b/tests/e2e/graphql-gateway.spec.ts @@ -0,0 +1,113 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule as PostsModule } from '../graphql-federation/posts-service/federation-posts.module'; +import { AppModule as UsersModule } from '../graphql-federation/users-service/federation-users.module'; +import { AppModule as GatewayModule } from '../graphql-federation/gateway/gateway.module'; + +describe('GraphQL', () => { + let postsApp: INestApplication; + let usersApp: INestApplication; + let gatewayApp: INestApplication; + + beforeEach(async () => { + const usersModule = await Test.createTestingModule({ + imports: [UsersModule], + }).compile(); + + usersApp = usersModule.createNestApplication(); + await usersApp.listenAsync(3001); + + const postsModule = await Test.createTestingModule({ + imports: [PostsModule], + }).compile(); + + postsApp = postsModule.createNestApplication(); + await postsApp.listenAsync(3002); + + const gatewayModule = await Test.createTestingModule({ + imports: [GatewayModule], + }).compile(); + + gatewayApp = gatewayModule.createNestApplication(); + await gatewayApp.init(); + }); + + it(`should run lookup across boundaries`, () => { + return request(gatewayApp.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getPosts { + id, + title, + body, + user { + id, + name, + } + } + }`, + }) + .expect(200, { + data: { + getPosts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + user: { + id: '5', + name: 'GraphQL', + }, + }, + ], + }, + }); + }); + + it(`should run reverse lookup across boundaries`, () => { + return request(gatewayApp.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getUser(id: "5") { + id, + name, + posts { + id, + title, + body, + } + } + }`, + }) + .expect(200, { + data: { + getUser: { + id: '5', + name: 'GraphQL', + posts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + ], + }, + }, + }); + }); + + afterEach(async () => { + await postsApp.close(); + await usersApp.close(); + await gatewayApp.close(); + }); +}); diff --git a/tests/graphql-federation/gateway/gateway.module.ts b/tests/graphql-federation/gateway/gateway.module.ts new file mode 100644 index 000000000..ec69fa4a3 --- /dev/null +++ b/tests/graphql-federation/gateway/gateway.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { GraphQLGatewayModule } from '../../../lib/graphql-gateway.module'; +import { join } from 'path'; + +@Module({ + imports: [ + GraphQLGatewayModule.forRoot({ + serviceList: [ + { name: 'users', url: 'http://localhost:3001/graphql' }, + { name: 'posts', url: 'http://localhost:3002/graphql' }, + ], + }), + ], +}) +export class AppModule {} From ee39632a413874b9b1c1f5c79878c98de20cc2dc Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Tue, 29 Oct 2019 12:38:24 +0100 Subject: [PATCH 16/33] test: asynchronous federation module initialisation --- .../graphql-federation-async-class.spec.ts | 45 +++++++++++++++++++ .../graphql-federation-async-existing.spec.ts | 45 +++++++++++++++++++ tests/e2e/graphql-federation-async.spec.ts | 45 +++++++++++++++++++ tests/e2e/graphql-federation.spec.ts | 2 +- .../users-service/config/config.module.ts | 8 ++++ .../users-service/config/config.service.ts | 12 +++++ .../federation-users.async-class.module.ts | 17 +++++++ .../federation-users.async-existing.module.ts | 17 +++++++ .../federation-users.async.module.ts | 19 ++++++++ 9 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/graphql-federation-async-class.spec.ts create mode 100644 tests/e2e/graphql-federation-async-existing.spec.ts create mode 100644 tests/e2e/graphql-federation-async.spec.ts create mode 100644 tests/graphql-federation/users-service/config/config.module.ts create mode 100644 tests/graphql-federation/users-service/config/config.service.ts create mode 100644 tests/graphql-federation/users-service/federation-users.async-class.module.ts create mode 100644 tests/graphql-federation/users-service/federation-users.async-existing.module.ts create mode 100644 tests/graphql-federation/users-service/federation-users.async.module.ts diff --git a/tests/e2e/graphql-federation-async-class.spec.ts b/tests/e2e/graphql-federation-async-class.spec.ts new file mode 100644 index 000000000..826184203 --- /dev/null +++ b/tests/e2e/graphql-federation-async-class.spec.ts @@ -0,0 +1,45 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule } from '../graphql-federation/users-service/federation-users.async-class.module'; + +describe('GraphQL Federation async-class', () => { + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = module.createNestApplication(); + await app.init(); + }); + + it(`should return query result`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getUser(id: "5") { + id, + name, + } + }`, + }) + .expect(200, { + data: { + getUser: { + id: '5', + name: 'GraphQL', + }, + }, + }); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/tests/e2e/graphql-federation-async-existing.spec.ts b/tests/e2e/graphql-federation-async-existing.spec.ts new file mode 100644 index 000000000..8a3a5c6e5 --- /dev/null +++ b/tests/e2e/graphql-federation-async-existing.spec.ts @@ -0,0 +1,45 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule } from '../graphql-federation/users-service/federation-users.async-existing.module'; + +describe('GraphQL Federation Async', () => { + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = module.createNestApplication(); + await app.init(); + }); + + it(`should return query result`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getUser(id: "5") { + id, + name, + } + }`, + }) + .expect(200, { + data: { + getUser: { + id: '5', + name: 'GraphQL', + }, + }, + }); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/tests/e2e/graphql-federation-async.spec.ts b/tests/e2e/graphql-federation-async.spec.ts new file mode 100644 index 000000000..69182b033 --- /dev/null +++ b/tests/e2e/graphql-federation-async.spec.ts @@ -0,0 +1,45 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule } from '../graphql-federation/users-service/federation-users.async.module'; + +describe('GraphQL Federation Async', () => { + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = module.createNestApplication(); + await app.init(); + }); + + it(`should return query result`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getUser(id: "5") { + id, + name, + } + }`, + }) + .expect(200, { + data: { + getUser: { + id: '5', + name: 'GraphQL', + }, + }, + }); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/tests/e2e/graphql-federation.spec.ts b/tests/e2e/graphql-federation.spec.ts index bd04dcf8e..71d3e2125 100644 --- a/tests/e2e/graphql-federation.spec.ts +++ b/tests/e2e/graphql-federation.spec.ts @@ -4,7 +4,7 @@ import * as request from 'supertest'; import { AppModule as PostsModule } from '../graphql-federation/posts-service/federation-posts.module'; import { AppModule as UsersModule } from '../graphql-federation/users-service/federation-users.module'; -describe('GraphQL', () => { +describe('GraphQL Federation', () => { let app: INestApplication; describe('UsersService', () => { diff --git a/tests/graphql-federation/users-service/config/config.module.ts b/tests/graphql-federation/users-service/config/config.module.ts new file mode 100644 index 000000000..eaa505c84 --- /dev/null +++ b/tests/graphql-federation/users-service/config/config.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { ConfigService } from './config.service'; + +@Module({ + providers: [ConfigService], + exports: [ConfigService], +}) +export class ConfigModule {} diff --git a/tests/graphql-federation/users-service/config/config.service.ts b/tests/graphql-federation/users-service/config/config.service.ts new file mode 100644 index 000000000..6bfb60172 --- /dev/null +++ b/tests/graphql-federation/users-service/config/config.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; +import { GqlModuleOptions, GqlOptionsFactory } from '../../../../lib'; +import { join } from 'path'; + +@Injectable() +export class ConfigService implements GqlOptionsFactory { + public createGqlOptions(): Partial { + return { + typePaths: [join(__dirname, '../**/*.graphql')], + }; + } +} diff --git a/tests/graphql-federation/users-service/federation-users.async-class.module.ts b/tests/graphql-federation/users-service/federation-users.async-class.module.ts new file mode 100644 index 000000000..267a6500f --- /dev/null +++ b/tests/graphql-federation/users-service/federation-users.async-class.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { GraphQLFederationModule } from '../../../lib/graphql-federation.module'; +import { UsersModule } from './users/users.module'; +import { ConfigService } from './config/config.service'; +import { ConfigModule } from './config/config.module'; + +@Module({ + imports: [ + GraphQLFederationModule.forRootAsync({ + useClass: ConfigService, + imports: [ConfigModule], + inject: [ConfigService], + }), + UsersModule, + ], +}) +export class AppModule {} diff --git a/tests/graphql-federation/users-service/federation-users.async-existing.module.ts b/tests/graphql-federation/users-service/federation-users.async-existing.module.ts new file mode 100644 index 000000000..1c662c2f1 --- /dev/null +++ b/tests/graphql-federation/users-service/federation-users.async-existing.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { GraphQLFederationModule } from '../../../lib/graphql-federation.module'; +import { UsersModule } from './users/users.module'; +import { ConfigService } from './config/config.service'; +import { ConfigModule } from './config/config.module'; + +@Module({ + imports: [ + GraphQLFederationModule.forRootAsync({ + useExisting: ConfigService, + imports: [ConfigModule], + inject: [ConfigService], + }), + UsersModule, + ], +}) +export class AppModule {} diff --git a/tests/graphql-federation/users-service/federation-users.async.module.ts b/tests/graphql-federation/users-service/federation-users.async.module.ts new file mode 100644 index 000000000..5dc859563 --- /dev/null +++ b/tests/graphql-federation/users-service/federation-users.async.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { GraphQLFederationModule } from '../../../lib/graphql-federation.module'; +import { UsersModule } from './users/users.module'; +import { ConfigService } from './config/config.service'; +import { ConfigModule } from './config/config.module'; + +@Module({ + imports: [ + GraphQLFederationModule.forRootAsync({ + useFactory: async (configService: ConfigService) => ({ + ...configService.createGqlOptions(), + }), + imports: [ConfigModule], + inject: [ConfigService], + }), + UsersModule, + ], +}) +export class AppModule {} From aba664f059dcc840bf3114c26610addc0666cd43 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Tue, 29 Oct 2019 12:51:36 +0100 Subject: [PATCH 17/33] feat: add async module support to graphql gateway --- lib/graphql-gateway.module.ts | 61 ++++++++++++++- .../gql-gateway-module-options.interface.ts | 24 +++++- tests/e2e/graphql-gateway-async-class.spec.ts | 77 +++++++++++++++++++ .../graphql-gateway-async-existing.spec.ts | 77 +++++++++++++++++++ tests/e2e/graphql-gateway-async.spec.ts | 77 +++++++++++++++++++ tests/e2e/graphql-gateway.spec.ts | 2 +- .../gateway/config/config.module.ts | 8 ++ .../gateway/config/config.service.ts | 14 ++++ .../gateway/gateway-async-class.module.ts | 15 ++++ .../gateway/gateway-async-existing.module.ts | 15 ++++ .../gateway/gateway-async.module.ts | 17 ++++ .../gateway/gateway.module.ts | 1 - 12 files changed, 382 insertions(+), 6 deletions(-) create mode 100644 tests/e2e/graphql-gateway-async-class.spec.ts create mode 100644 tests/e2e/graphql-gateway-async-existing.spec.ts create mode 100644 tests/e2e/graphql-gateway-async.spec.ts create mode 100644 tests/graphql-federation/gateway/config/config.module.ts create mode 100644 tests/graphql-federation/gateway/config/config.service.ts create mode 100644 tests/graphql-federation/gateway/gateway-async-class.module.ts create mode 100644 tests/graphql-federation/gateway/gateway-async-existing.module.ts create mode 100644 tests/graphql-federation/gateway/gateway-async.module.ts diff --git a/lib/graphql-gateway.module.ts b/lib/graphql-gateway.module.ts index 19b23e16a..bb1afd864 100644 --- a/lib/graphql-gateway.module.ts +++ b/lib/graphql-gateway.module.ts @@ -1,9 +1,20 @@ -import { DynamicModule, Inject, Module, OnModuleInit, Optional } from '@nestjs/common'; +import { DynamicModule, Inject, Module, OnModuleInit, Optional, Provider } from '@nestjs/common'; import { ApolloServer } from 'apollo-server-express'; import { HttpAdapterHost } from '@nestjs/core'; -import { GRAPHQL_GATEWAY_BUILD_SERVICE, GRAPHQL_GATEWAY_MODULE_OPTIONS } from './graphql.constants'; -import { GatewayBuildService, GatewayModuleOptions } from './interfaces'; +import { + GRAPHQL_GATEWAY_BUILD_SERVICE, + GRAPHQL_GATEWAY_MODULE_OPTIONS, + GRAPHQL_MODULE_ID, + GRAPHQL_MODULE_OPTIONS, +} from './graphql.constants'; +import { + GatewayBuildService, + GatewayModuleOptions, + GatewayOptionsFactory, + GatewayModuleAsyncOptions, +} from './interfaces'; import { loadPackage } from '@nestjs/common/utils/load-package.util'; +import { generateString } from './utils'; @Module({}) export class GraphQLGatewayModule implements OnModuleInit { @@ -31,6 +42,50 @@ export class GraphQLGatewayModule implements OnModuleInit { }; } + static forRootAsync(options: GatewayModuleAsyncOptions): DynamicModule { + return { + module: GraphQLGatewayModule, + imports: options.imports, + providers: [ + ...this.createAsyncProviders(options), + { + provide: GRAPHQL_MODULE_ID, + useValue: generateString(), + }, + ], + }; + } + + private static createAsyncProviders(options: GatewayModuleAsyncOptions): Provider[] { + if (options.useExisting || options.useFactory) { + return [this.createAsyncOptionsProvider(options)]; + } + + return [ + this.createAsyncOptionsProvider(options), + { + provide: options.useClass, + useClass: options.useClass, + }, + ]; + } + + private static createAsyncOptionsProvider(options: GatewayModuleAsyncOptions): Provider { + if (options.useFactory) { + return { + provide: GRAPHQL_GATEWAY_MODULE_OPTIONS, + useFactory: options.useFactory, + inject: options.inject || [], + }; + } + + return { + provide: GRAPHQL_GATEWAY_MODULE_OPTIONS, + useFactory: (optionsFactory: GatewayOptionsFactory) => optionsFactory.createGatewayOptions(), + inject: [options.useExisting || options.useClass], + }; + } + async onModuleInit() { const { httpAdapter } = this.httpAdapterHost || {}; diff --git a/lib/interfaces/gql-gateway-module-options.interface.ts b/lib/interfaces/gql-gateway-module-options.interface.ts index 9e37d0b8d..03217ab4b 100644 --- a/lib/interfaces/gql-gateway-module-options.interface.ts +++ b/lib/interfaces/gql-gateway-module-options.interface.ts @@ -1,7 +1,29 @@ +import { Type } from '@nestjs/common'; import { Omit, GqlModuleOptions } from './gql-module-options.interface'; import { GatewayConfig, ServiceEndpointDefinition } from '@apollo/gateway'; import { GraphQLDataSource } from '@apollo/gateway/src/datasources/types'; +import { ModuleMetadata } from '@nestjs/common/interfaces'; -export interface GatewayModuleOptions extends Pick, Omit {} +export interface GatewayModuleOptions + extends Pick< + GqlModuleOptions, + | 'path' + | 'disableHealthCheck' + | 'onHealthCheck' + | 'cors' + | 'bodyParserConfig' + | 'installSubscriptionHandlers' + >, + Omit {} + +export interface GatewayOptionsFactory { + createGatewayOptions(): Promise | GatewayModuleOptions; +} +export interface GatewayModuleAsyncOptions extends Pick { + useExisting?: Type; + useClass?: Type; + useFactory?: (...args: any[]) => Promise | GqlModuleOptions; + inject?: any[]; +} export type GatewayBuildService = (definition: ServiceEndpointDefinition) => GraphQLDataSource; diff --git a/tests/e2e/graphql-gateway-async-class.spec.ts b/tests/e2e/graphql-gateway-async-class.spec.ts new file mode 100644 index 000000000..95f272637 --- /dev/null +++ b/tests/e2e/graphql-gateway-async-class.spec.ts @@ -0,0 +1,77 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule as PostsModule } from '../graphql-federation/posts-service/federation-posts.module'; +import { AppModule as UsersModule } from '../graphql-federation/users-service/federation-users.module'; +import { AppModule as GatewayModule } from '../graphql-federation/gateway/gateway-async-class.module'; + +describe('GraphQL Gateway async-class', () => { + let postsApp: INestApplication; + let usersApp: INestApplication; + let gatewayApp: INestApplication; + + beforeEach(async () => { + const usersModule = await Test.createTestingModule({ + imports: [UsersModule], + }).compile(); + + usersApp = usersModule.createNestApplication(); + await usersApp.listenAsync(3001); + + const postsModule = await Test.createTestingModule({ + imports: [PostsModule], + }).compile(); + + postsApp = postsModule.createNestApplication(); + await postsApp.listenAsync(3002); + + const gatewayModule = await Test.createTestingModule({ + imports: [GatewayModule], + }).compile(); + + gatewayApp = gatewayModule.createNestApplication(); + await gatewayApp.init(); + }); + + it(`should run lookup across boundaries`, () => { + return request(gatewayApp.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getPosts { + id, + title, + body, + user { + id, + name, + } + } + }`, + }) + .expect(200, { + data: { + getPosts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + user: { + id: '5', + name: 'GraphQL', + }, + }, + ], + }, + }); + }); + + afterEach(async () => { + await postsApp.close(); + await usersApp.close(); + await gatewayApp.close(); + }); +}); diff --git a/tests/e2e/graphql-gateway-async-existing.spec.ts b/tests/e2e/graphql-gateway-async-existing.spec.ts new file mode 100644 index 000000000..67ff35aea --- /dev/null +++ b/tests/e2e/graphql-gateway-async-existing.spec.ts @@ -0,0 +1,77 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule as PostsModule } from '../graphql-federation/posts-service/federation-posts.module'; +import { AppModule as UsersModule } from '../graphql-federation/users-service/federation-users.module'; +import { AppModule as GatewayModule } from '../graphql-federation/gateway/gateway-async-existing.module'; + +describe('GraphQL gateway async-existing', () => { + let postsApp: INestApplication; + let usersApp: INestApplication; + let gatewayApp: INestApplication; + + beforeEach(async () => { + const usersModule = await Test.createTestingModule({ + imports: [UsersModule], + }).compile(); + + usersApp = usersModule.createNestApplication(); + await usersApp.listenAsync(3001); + + const postsModule = await Test.createTestingModule({ + imports: [PostsModule], + }).compile(); + + postsApp = postsModule.createNestApplication(); + await postsApp.listenAsync(3002); + + const gatewayModule = await Test.createTestingModule({ + imports: [GatewayModule], + }).compile(); + + gatewayApp = gatewayModule.createNestApplication(); + await gatewayApp.init(); + }); + + it(`should run lookup across boundaries`, () => { + return request(gatewayApp.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getPosts { + id, + title, + body, + user { + id, + name, + } + } + }`, + }) + .expect(200, { + data: { + getPosts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + user: { + id: '5', + name: 'GraphQL', + }, + }, + ], + }, + }); + }); + + afterEach(async () => { + await postsApp.close(); + await usersApp.close(); + await gatewayApp.close(); + }); +}); diff --git a/tests/e2e/graphql-gateway-async.spec.ts b/tests/e2e/graphql-gateway-async.spec.ts new file mode 100644 index 000000000..4d0513f44 --- /dev/null +++ b/tests/e2e/graphql-gateway-async.spec.ts @@ -0,0 +1,77 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule as PostsModule } from '../graphql-federation/posts-service/federation-posts.module'; +import { AppModule as UsersModule } from '../graphql-federation/users-service/federation-users.module'; +import { AppModule as GatewayModule } from '../graphql-federation/gateway/gateway-async.module'; + +describe('GraphQL Gateway async', () => { + let postsApp: INestApplication; + let usersApp: INestApplication; + let gatewayApp: INestApplication; + + beforeEach(async () => { + const usersModule = await Test.createTestingModule({ + imports: [UsersModule], + }).compile(); + + usersApp = usersModule.createNestApplication(); + await usersApp.listenAsync(3001); + + const postsModule = await Test.createTestingModule({ + imports: [PostsModule], + }).compile(); + + postsApp = postsModule.createNestApplication(); + await postsApp.listenAsync(3002); + + const gatewayModule = await Test.createTestingModule({ + imports: [GatewayModule], + }).compile(); + + gatewayApp = gatewayModule.createNestApplication(); + await gatewayApp.init(); + }); + + it(`should run lookup across boundaries`, () => { + return request(gatewayApp.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getPosts { + id, + title, + body, + user { + id, + name, + } + } + }`, + }) + .expect(200, { + data: { + getPosts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + user: { + id: '5', + name: 'GraphQL', + }, + }, + ], + }, + }); + }); + + afterEach(async () => { + await postsApp.close(); + await usersApp.close(); + await gatewayApp.close(); + }); +}); diff --git a/tests/e2e/graphql-gateway.spec.ts b/tests/e2e/graphql-gateway.spec.ts index e6261e57d..24bce4720 100644 --- a/tests/e2e/graphql-gateway.spec.ts +++ b/tests/e2e/graphql-gateway.spec.ts @@ -5,7 +5,7 @@ import { AppModule as PostsModule } from '../graphql-federation/posts-service/fe import { AppModule as UsersModule } from '../graphql-federation/users-service/federation-users.module'; import { AppModule as GatewayModule } from '../graphql-federation/gateway/gateway.module'; -describe('GraphQL', () => { +describe('GraphQL Gateway', () => { let postsApp: INestApplication; let usersApp: INestApplication; let gatewayApp: INestApplication; diff --git a/tests/graphql-federation/gateway/config/config.module.ts b/tests/graphql-federation/gateway/config/config.module.ts new file mode 100644 index 000000000..eaa505c84 --- /dev/null +++ b/tests/graphql-federation/gateway/config/config.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { ConfigService } from './config.service'; + +@Module({ + providers: [ConfigService], + exports: [ConfigService], +}) +export class ConfigModule {} diff --git a/tests/graphql-federation/gateway/config/config.service.ts b/tests/graphql-federation/gateway/config/config.service.ts new file mode 100644 index 000000000..221205230 --- /dev/null +++ b/tests/graphql-federation/gateway/config/config.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common'; +import { GatewayModuleOptions, GatewayOptionsFactory } from '../../../../lib'; + +@Injectable() +export class ConfigService implements GatewayOptionsFactory { + public createGatewayOptions(): Partial { + return { + serviceList: [ + { name: 'users', url: 'http://localhost:3001/graphql' }, + { name: 'posts', url: 'http://localhost:3002/graphql' }, + ], + }; + } +} diff --git a/tests/graphql-federation/gateway/gateway-async-class.module.ts b/tests/graphql-federation/gateway/gateway-async-class.module.ts new file mode 100644 index 000000000..1a62f29f2 --- /dev/null +++ b/tests/graphql-federation/gateway/gateway-async-class.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { GraphQLGatewayModule } from '../../../lib/graphql-gateway.module'; +import { ConfigModule } from './config/config.module'; +import { ConfigService } from './config/config.service'; + +@Module({ + imports: [ + GraphQLGatewayModule.forRootAsync({ + useClass: ConfigService, + imports: [ConfigModule], + inject: [ConfigService], + }), + ], +}) +export class AppModule {} diff --git a/tests/graphql-federation/gateway/gateway-async-existing.module.ts b/tests/graphql-federation/gateway/gateway-async-existing.module.ts new file mode 100644 index 000000000..3f801a2d0 --- /dev/null +++ b/tests/graphql-federation/gateway/gateway-async-existing.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { GraphQLGatewayModule } from '../../../lib/graphql-gateway.module'; +import { ConfigModule } from './config/config.module'; +import { ConfigService } from './config/config.service'; + +@Module({ + imports: [ + GraphQLGatewayModule.forRootAsync({ + useExisting: ConfigService, + imports: [ConfigModule], + inject: [ConfigService], + }), + ], +}) +export class AppModule {} diff --git a/tests/graphql-federation/gateway/gateway-async.module.ts b/tests/graphql-federation/gateway/gateway-async.module.ts new file mode 100644 index 000000000..a270c7296 --- /dev/null +++ b/tests/graphql-federation/gateway/gateway-async.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { GraphQLGatewayModule } from '../../../lib/graphql-gateway.module'; +import { ConfigModule } from './config/config.module'; +import { ConfigService } from './config/config.service'; + +@Module({ + imports: [ + GraphQLGatewayModule.forRootAsync({ + useFactory: async (configService: ConfigService) => ({ + ...configService.createGatewayOptions(), + }), + imports: [ConfigModule], + inject: [ConfigService], + }), + ], +}) +export class AppModule {} diff --git a/tests/graphql-federation/gateway/gateway.module.ts b/tests/graphql-federation/gateway/gateway.module.ts index ec69fa4a3..9600f3294 100644 --- a/tests/graphql-federation/gateway/gateway.module.ts +++ b/tests/graphql-federation/gateway/gateway.module.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; import { GraphQLGatewayModule } from '../../../lib/graphql-gateway.module'; -import { join } from 'path'; @Module({ imports: [ From 3b9175fe2a2e250ba46c045d2af727147b7956c9 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Tue, 29 Oct 2019 16:06:27 +0100 Subject: [PATCH 18/33] test: add tests for generating interfaces for federation --- lib/graphql-definitions.factory.ts | 60 ++++++++-- tests/e2e/generated-definitions.spec.ts | 109 ++++++++---------- .../federation.fixture.ts | 11 ++ .../generated-definitions/federation.graphql | 13 +++ 4 files changed, 124 insertions(+), 69 deletions(-) create mode 100644 tests/generated-definitions/federation.fixture.ts create mode 100644 tests/generated-definitions/federation.graphql diff --git a/lib/graphql-definitions.factory.ts b/lib/graphql-definitions.factory.ts index 3b1c6a31b..9d89469f9 100644 --- a/lib/graphql-definitions.factory.ts +++ b/lib/graphql-definitions.factory.ts @@ -1,11 +1,12 @@ import { isEmpty } from '@nestjs/common/utils/shared.utils'; +import { loadPackage } from '@nestjs/common/utils/load-package.util'; import { gql } from 'apollo-server-core'; import { makeExecutableSchema } from 'graphql-tools'; import * as chokidar from 'chokidar'; import { printSchema } from 'graphql'; import { GraphQLAstExplorer } from './graphql-ast.explorer'; import { GraphQLTypesLoader } from './graphql-types.loader'; -import { removeTempField } from './utils'; +import { removeTempField, extend } from './utils'; export class GraphQLDefinitionsFactory { private readonly gqlAstExplorer = new GraphQLAstExplorer(); @@ -17,17 +18,16 @@ export class GraphQLDefinitionsFactory { outputAs?: 'class' | 'interface'; watch?: boolean; debug?: boolean; + federation?: boolean; }) { const isDebugEnabled = !(options && options.debug === false); const typePathsExists = options.typePaths && !isEmpty(options.typePaths); + const isFederation = options && options.federation; if (!typePathsExists) { throw new Error(`"typePaths" property cannot be empty.`); } if (options.watch) { - this.printMessage( - 'GraphQL factory is watching your files...', - isDebugEnabled, - ); + this.printMessage('GraphQL factory is watching your files...', isDebugEnabled); const watcher = chokidar.watch(options.typePaths); watcher.on('change', async file => { this.printMessage( @@ -38,6 +38,7 @@ export class GraphQLDefinitionsFactory { options.typePaths, options.path, options.outputAs, + isFederation, isDebugEnabled, ); }); @@ -46,6 +47,7 @@ export class GraphQLDefinitionsFactory { options.typePaths, options.path, options.outputAs, + isFederation, isDebugEnabled, ); } @@ -54,11 +56,55 @@ export class GraphQLDefinitionsFactory { typePaths: string[], path: string, outputAs: 'class' | 'interface', + isFederation: boolean, isDebugEnabled: boolean, ) { - const typeDefs = await this.gqlTypesLoader.mergeTypesByPaths( - typePaths || [], + if (isFederation) { + return this.exploreAndEmitFederation(typePaths, path, outputAs, isDebugEnabled); + } + return this.exploreAndEmitRegular(typePaths, path, outputAs, isDebugEnabled); + } + + private async exploreAndEmitFederation( + typePaths: string[], + path: string, + outputAs: 'class' | 'interface', + isDebugEnabled: boolean, + ) { + const typeDefs = await this.gqlTypesLoader.getTypesFromPaths(typePaths); + + const { buildFederatedSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); + const { printSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); + + const schema = buildFederatedSchema([ + { + typeDefs: gql` + ${typeDefs} + `, + resolvers: {}, + }, + ]); + const tsFile = await this.gqlAstExplorer.explore( + gql` + ${printSchema(schema)} + `, + path, + outputAs, + ); + await tsFile.save(); + this.printMessage( + `[${new Date().toLocaleTimeString()}] The definitions have been updated.`, + isDebugEnabled, ); + } + + private async exploreAndEmitRegular( + typePaths: string[], + path: string, + outputAs: 'class' | 'interface', + isDebugEnabled: boolean, + ) { + const typeDefs = await this.gqlTypesLoader.mergeTypesByPaths(typePaths || []); if (!typeDefs) { throw new Error(`"typeDefs" property cannot be null.`); } diff --git a/tests/e2e/generated-definitions.spec.ts b/tests/e2e/generated-definitions.spec.ts index d8fe41408..39c2e53cf 100644 --- a/tests/e2e/generated-definitions.spec.ts +++ b/tests/e2e/generated-definitions.spec.ts @@ -3,7 +3,7 @@ import * as fs from 'fs'; import * as util from 'util'; import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { GraphQLFactory } from '../../lib'; +import { GraphQLFactory, GraphQLDefinitionsFactory } from '../../lib'; import { ApplicationModule } from '../type-graphql/app.module'; const readFile = util.promisify(fs.readFile); @@ -27,10 +27,7 @@ describe('Generated Definitions', () => { }); it('should generate interface definitions for types', async () => { - const typeDefs = await readFile( - generatedDefinitions('simple-type.graphql'), - 'utf8', - ); + const typeDefs = await readFile(generatedDefinitions('simple-type.graphql'), 'utf8'); const outputFile = generatedDefinitions('simple-type.test-definitions.ts'); await graphqlFactory.generateDefinitions(typeDefs, { @@ -39,59 +36,43 @@ describe('Generated Definitions', () => { }, }); - expect( - await readFile(generatedDefinitions('simple-type.fixture.ts'), 'utf8'), - ).toBe(await readFile(outputFile, 'utf8')); + expect(await readFile(generatedDefinitions('simple-type.fixture.ts'), 'utf8')).toBe( + await readFile(outputFile, 'utf8'), + ); }); it('should generate properties referencing other interfaces', async () => { - const typeDefs = await readFile( - generatedDefinitions('interface-property.graphql'), - 'utf8', - ); + const typeDefs = await readFile(generatedDefinitions('interface-property.graphql'), 'utf8'); - const outputFile = generatedDefinitions( - 'interface-property.test-definitions.ts', - ); + const outputFile = generatedDefinitions('interface-property.test-definitions.ts'); await graphqlFactory.generateDefinitions(typeDefs, { definitions: { path: outputFile, }, }); - expect( - await readFile( - generatedDefinitions('interface-property.fixture.ts'), - 'utf8', - ), - ).toBe(await readFile(outputFile, 'utf8')); + expect(await readFile(generatedDefinitions('interface-property.fixture.ts'), 'utf8')).toBe( + await readFile(outputFile, 'utf8'), + ); }); it('should generate array properties with correct optionality', async () => { - const typeDefs = await readFile( - generatedDefinitions('array-property.graphql'), - 'utf8', - ); + const typeDefs = await readFile(generatedDefinitions('array-property.graphql'), 'utf8'); - const outputFile = generatedDefinitions( - 'array-property.test-definitions.ts', - ); + const outputFile = generatedDefinitions('array-property.test-definitions.ts'); await graphqlFactory.generateDefinitions(typeDefs, { definitions: { path: outputFile, }, }); - expect( - await readFile(generatedDefinitions('array-property.fixture.ts'), 'utf8'), - ).toBe(await readFile(outputFile, 'utf8')); + expect(await readFile(generatedDefinitions('array-property.fixture.ts'), 'utf8')).toBe( + await readFile(outputFile, 'utf8'), + ); }); it('should generate queries', async () => { - const typeDefs = await readFile( - generatedDefinitions('query.graphql'), - 'utf8', - ); + const typeDefs = await readFile(generatedDefinitions('query.graphql'), 'utf8'); const outputFile = generatedDefinitions('query.test-definitions.ts'); await graphqlFactory.generateDefinitions(typeDefs, { @@ -100,16 +81,13 @@ describe('Generated Definitions', () => { }, }); - expect( - await readFile(generatedDefinitions('query.fixture.ts'), 'utf8'), - ).toBe(await readFile(outputFile, 'utf8')); + expect(await readFile(generatedDefinitions('query.fixture.ts'), 'utf8')).toBe( + await readFile(outputFile, 'utf8'), + ); }); it('should generate mutations', async () => { - const typeDefs = await readFile( - generatedDefinitions('mutation.graphql'), - 'utf8', - ); + const typeDefs = await readFile(generatedDefinitions('mutation.graphql'), 'utf8'); const outputFile = generatedDefinitions('mutation.test-definitions.ts'); await graphqlFactory.generateDefinitions(typeDefs, { @@ -118,16 +96,13 @@ describe('Generated Definitions', () => { }, }); - expect( - await readFile(generatedDefinitions('mutation.fixture.ts'), 'utf8'), - ).toBe(await readFile(outputFile, 'utf8')); + expect(await readFile(generatedDefinitions('mutation.fixture.ts'), 'utf8')).toBe( + await readFile(outputFile, 'utf8'), + ); }); it('should generate enums', async () => { - const typeDefs = await readFile( - generatedDefinitions('enum.graphql'), - 'utf8', - ); + const typeDefs = await readFile(generatedDefinitions('enum.graphql'), 'utf8'); const outputFile = generatedDefinitions('enum.test-definitions.ts'); await graphqlFactory.generateDefinitions(typeDefs, { @@ -136,29 +111,39 @@ describe('Generated Definitions', () => { }, }); - expect( - await readFile(generatedDefinitions('enum.fixture.ts'), 'utf8'), - ).toBe(await readFile(outputFile, 'utf8')); + expect(await readFile(generatedDefinitions('enum.fixture.ts'), 'utf8')).toBe( + await readFile(outputFile, 'utf8'), + ); }); it('should generate custom scalars', async () => { - const typeDefs = await readFile( - generatedDefinitions('custom-scalar.graphql'), - 'utf8', - ); + const typeDefs = await readFile(generatedDefinitions('custom-scalar.graphql'), 'utf8'); - const outputFile = generatedDefinitions( - 'custom-scalar.test-definitions.ts', - ); + const outputFile = generatedDefinitions('custom-scalar.test-definitions.ts'); await graphqlFactory.generateDefinitions(typeDefs, { definitions: { path: outputFile, }, }); - expect( - await readFile(generatedDefinitions('custom-scalar.fixture.ts'), 'utf8'), - ).toBe(await readFile(outputFile, 'utf8')); + expect(await readFile(generatedDefinitions('custom-scalar.fixture.ts'), 'utf8')).toBe( + await readFile(outputFile, 'utf8'), + ); + }); + + it('should generate for a federated graph', async () => { + const outputFile = generatedDefinitions('federation.test-definitions.ts'); + const factory = new GraphQLDefinitionsFactory(); + await factory.generate({ + typePaths: [generatedDefinitions('federation.graphql')], + path: outputFile, + outputAs: 'class', + federation: true, + }); + + expect(await readFile(generatedDefinitions('federation.fixture.ts'), 'utf8')).toBe( + await readFile(outputFile, 'utf8'), + ); }); afterEach(async () => { diff --git a/tests/generated-definitions/federation.fixture.ts b/tests/generated-definitions/federation.fixture.ts new file mode 100644 index 000000000..1f91b9687 --- /dev/null +++ b/tests/generated-definitions/federation.fixture.ts @@ -0,0 +1,11 @@ + +/** ------------------------------------------------------ + * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) + * ------------------------------------------------------- + */ + +/* tslint:disable */ +export class Post { + id: string; + title: string; +} diff --git a/tests/generated-definitions/federation.graphql b/tests/generated-definitions/federation.graphql new file mode 100644 index 000000000..7d030b6a4 --- /dev/null +++ b/tests/generated-definitions/federation.graphql @@ -0,0 +1,13 @@ +type Post { + id: ID! + title: String! +} + +extend type User @key(fields: "id") { + id: ID! @external + posts: [Post] +} + +extend type Query { + getPosts: [Post] +} \ No newline at end of file From ee32547190276b5a211d4cf3bdc7943b6752e772 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Tue, 29 Oct 2019 16:51:38 +0100 Subject: [PATCH 19/33] feat: add fastify support to federation and gateway modules --- lib/graphql-federation.module.ts | 97 +++++++++++---- lib/graphql-gateway.module.ts | 85 ++++++++----- tests/e2e/graphql-fastify.spec.ts | 2 +- tests/e2e/graphql-federation-fastify.spec.ts | 54 +++++++++ tests/e2e/graphql-gateway-fastify.spec.ts | 118 +++++++++++++++++++ 5 files changed, 302 insertions(+), 54 deletions(-) create mode 100644 tests/e2e/graphql-federation-fastify.spec.ts create mode 100644 tests/e2e/graphql-gateway-fastify.spec.ts diff --git a/lib/graphql-federation.module.ts b/lib/graphql-federation.module.ts index 2bb1a7a27..f4aac6636 100644 --- a/lib/graphql-federation.module.ts +++ b/lib/graphql-federation.module.ts @@ -1,8 +1,8 @@ import { DynamicModule, Inject, Module, OnModuleInit, Optional, Provider } from '@nestjs/common'; -import { ApolloServer } from 'apollo-server-express'; +import { ApolloServerBase } from 'apollo-server-core'; import { MetadataScanner } from '@nestjs/core/metadata-scanner'; import { loadPackage } from '@nestjs/common/utils/load-package.util'; -import { HttpAdapterHost } from '@nestjs/core'; +import { HttpAdapterHost, ApplicationConfig } from '@nestjs/core'; import { GraphQLFederationFactory } from './graphql-federation.factory'; import { @@ -15,7 +15,7 @@ import { GraphQLTypesLoader } from './graphql-types.loader'; import { GraphQLSchemaBuilder } from './graphql-schema-builder'; import { GRAPHQL_MODULE_ID, GRAPHQL_MODULE_OPTIONS } from './graphql.constants'; import { GqlModuleAsyncOptions, GqlModuleOptions, GqlOptionsFactory } from './interfaces'; -import { generateString, extend, mergeDefaults } from './utils'; +import { generateString, extend, mergeDefaults, normalizeRoutePath } from './utils'; import { GraphQLFactory } from './graphql.factory'; @Module({ @@ -33,7 +33,7 @@ import { GraphQLFactory } from './graphql.factory'; exports: [], }) export class GraphQLFederationModule implements OnModuleInit { - private apolloServer: ApolloServer; + private apolloServer: ApolloServerBase; constructor( @Optional() @@ -43,6 +43,7 @@ export class GraphQLFederationModule implements OnModuleInit { private readonly graphqlFederationFactory: GraphQLFederationFactory, private readonly graphqlTypesLoader: GraphQLTypesLoader, private readonly graphqlFactory: GraphQLFactory, + private readonly applicationConfig: ApplicationConfig, ) {} static forRoot(options: GqlModuleOptions = {}): DynamicModule { @@ -104,23 +105,13 @@ export class GraphQLFederationModule implements OnModuleInit { } async onModuleInit() { - const { httpAdapter } = this.httpAdapterHost || {}; - - if (!httpAdapter) { + if (!this.httpAdapterHost || !this.httpAdapterHost.httpAdapter) { return; } const { printSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); - const { - path, - disableHealthCheck, - onHealthCheck, - cors, - bodyParserConfig, - typePaths, - } = this.options; - const app = httpAdapter.getInstance(); + const { typePaths } = this.options; const typeDefs = await this.graphqlTypesLoader.getTypesFromPaths(typePaths); @@ -137,15 +128,7 @@ export class GraphQLFederationModule implements OnModuleInit { ); } - this.apolloServer = new ApolloServer(apolloOptions as any); - this.apolloServer.applyMiddleware({ - app, - path, - disableHealthCheck, - onHealthCheck, - cors, - bodyParserConfig, - }); + this.registerGqlServer(apolloOptions); if (this.options.installSubscriptionHandlers) { // TL;DR @@ -155,4 +138,68 @@ export class GraphQLFederationModule implements OnModuleInit { );*/ } } + + private registerGqlServer(apolloOptions: GqlModuleOptions) { + const httpAdapter = this.httpAdapterHost.httpAdapter; + const adapterName = httpAdapter.constructor && httpAdapter.constructor.name; + + if (adapterName === 'ExpressAdapter') { + this.registerExpress(apolloOptions); + } else if (adapterName === 'FastifyAdapter') { + this.registerFastify(apolloOptions); + } else { + throw new Error(`No support for current HttpAdapter: ${adapterName}`); + } + } + + private registerExpress(apolloOptions: GqlModuleOptions) { + const { ApolloServer } = loadPackage('apollo-server-express', 'GraphQLModule', () => + require('apollo-server-express'), + ); + const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig } = this.options; + const app = this.httpAdapterHost.httpAdapter.getInstance(); + const path = this.getNormalizedPath(apolloOptions); + + const apolloServer = new ApolloServer(apolloOptions as any); + apolloServer.applyMiddleware({ + app, + path, + disableHealthCheck, + onHealthCheck, + cors, + bodyParserConfig, + }); + this.apolloServer = apolloServer; + } + + private registerFastify(apolloOptions: GqlModuleOptions) { + const { ApolloServer } = loadPackage('apollo-server-fastify', 'GraphQLModule', () => + require('apollo-server-fastify'), + ); + + const httpAdapter = this.httpAdapterHost.httpAdapter; + const app = httpAdapter.getInstance(); + const path = this.getNormalizedPath(apolloOptions); + + const apolloServer = new ApolloServer(apolloOptions as any); + const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig } = this.options; + app.register( + apolloServer.createHandler({ + disableHealthCheck, + onHealthCheck, + cors, + bodyParserConfig, + path, + }), + ); + + this.apolloServer = apolloServer; + } + + private getNormalizedPath(apolloOptions: GqlModuleOptions): string { + const prefix = this.applicationConfig.getGlobalPrefix(); + const useGlobalPrefix = prefix && this.options.useGlobalPrefix; + const gqlOptionsPath = normalizeRoutePath(apolloOptions.path); + return useGlobalPrefix ? normalizeRoutePath(prefix) + gqlOptionsPath : gqlOptionsPath; + } } diff --git a/lib/graphql-gateway.module.ts b/lib/graphql-gateway.module.ts index bb1afd864..5bbd66124 100644 --- a/lib/graphql-gateway.module.ts +++ b/lib/graphql-gateway.module.ts @@ -1,11 +1,10 @@ import { DynamicModule, Inject, Module, OnModuleInit, Optional, Provider } from '@nestjs/common'; -import { ApolloServer } from 'apollo-server-express'; +import { ApolloServerBase, Config as ApolloServerConfig } from 'apollo-server-core'; import { HttpAdapterHost } from '@nestjs/core'; import { GRAPHQL_GATEWAY_BUILD_SERVICE, GRAPHQL_GATEWAY_MODULE_OPTIONS, GRAPHQL_MODULE_ID, - GRAPHQL_MODULE_OPTIONS, } from './graphql.constants'; import { GatewayBuildService, @@ -18,7 +17,7 @@ import { generateString } from './utils'; @Module({}) export class GraphQLGatewayModule implements OnModuleInit { - private apolloServer: ApolloServer; + private apolloServer: ApolloServerBase; constructor( @Optional() @@ -94,20 +93,8 @@ export class GraphQLGatewayModule implements OnModuleInit { } const { ApolloGateway } = loadPackage('@apollo/gateway', 'ApolloGateway'); - const app = httpAdapter.getInstance(); - const { - options: { - __exposeQueryPlanExperimental, - debug, - serviceList, - path, - disableHealthCheck, - onHealthCheck, - cors, - bodyParserConfig, - installSubscriptionHandlers, - }, + options: { __exposeQueryPlanExperimental, debug, serviceList, installSubscriptionHandlers }, buildService, } = this; @@ -119,13 +106,39 @@ export class GraphQLGatewayModule implements OnModuleInit { }); const { schema, executor } = await gateway.load(); + this.registerGqlServer({ schema, executor }); - this.apolloServer = new ApolloServer({ - executor, - schema, - }); + if (installSubscriptionHandlers) { + // TL;DR + throw new Error('No support for subscriptions yet when using Apollo Federation'); + /*this.apolloServer.installSubscriptionHandlers( + httpAdapter.getHttpServer(), + );*/ + } + } + + private registerGqlServer(apolloOptions: ApolloServerConfig) { + const httpAdapter = this.httpAdapterHost.httpAdapter; + const adapterName = httpAdapter.constructor && httpAdapter.constructor.name; - this.apolloServer.applyMiddleware({ + if (adapterName === 'ExpressAdapter') { + this.registerExpress(apolloOptions); + } else if (adapterName === 'FastifyAdapter') { + this.registerFastify(apolloOptions); + } else { + throw new Error(`No support for current HttpAdapter: ${adapterName}`); + } + } + + private registerExpress(apolloOptions: ApolloServerConfig) { + const { ApolloServer } = loadPackage('apollo-server-express', 'GraphQLModule', () => + require('apollo-server-express'), + ); + const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } = this.options; + const app = this.httpAdapterHost.httpAdapter.getInstance(); + + const apolloServer = new ApolloServer(apolloOptions); + apolloServer.applyMiddleware({ app, path, disableHealthCheck, @@ -133,13 +146,29 @@ export class GraphQLGatewayModule implements OnModuleInit { cors, bodyParserConfig, }); + this.apolloServer = apolloServer; + } - if (installSubscriptionHandlers) { - // TL;DR - throw new Error('No support for subscriptions yet when using Apollo Federation'); - /*this.apolloServer.installSubscriptionHandlers( - httpAdapter.getHttpServer(), - );*/ - } + private registerFastify(apolloOptions: ApolloServerConfig) { + const { ApolloServer } = loadPackage('apollo-server-fastify', 'GraphQLModule', () => + require('apollo-server-fastify'), + ); + + const httpAdapter = this.httpAdapterHost.httpAdapter; + const app = httpAdapter.getInstance(); + + const apolloServer = new ApolloServer(apolloOptions as any); + const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } = this.options; + app.register( + apolloServer.createHandler({ + disableHealthCheck, + onHealthCheck, + cors, + bodyParserConfig, + path, + }), + ); + + this.apolloServer = apolloServer; } } diff --git a/tests/e2e/graphql-fastify.spec.ts b/tests/e2e/graphql-fastify.spec.ts index be7f1b1c4..68a43e2f5 100644 --- a/tests/e2e/graphql-fastify.spec.ts +++ b/tests/e2e/graphql-fastify.spec.ts @@ -4,7 +4,7 @@ import * as request from 'supertest'; import { ApplicationModule } from '../graphql/app.module'; import { FastifyAdapter } from '@nestjs/platform-fastify'; -describe('GraphQL', () => { +describe('GraphQL with fastify', () => { let app: INestApplication; beforeEach(async () => { diff --git a/tests/e2e/graphql-federation-fastify.spec.ts b/tests/e2e/graphql-federation-fastify.spec.ts new file mode 100644 index 000000000..c6b1803a6 --- /dev/null +++ b/tests/e2e/graphql-federation-fastify.spec.ts @@ -0,0 +1,54 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule } from '../graphql-federation/posts-service/federation-posts.module'; +import { FastifyAdapter } from '@nestjs/platform-fastify'; + +describe('GraphQL federation with fastify', () => { + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = module.createNestApplication(new FastifyAdapter()); + await app.init(); + await app + .getHttpAdapter() + .getInstance() + .ready(); + }); + + it(`should return query result`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getPosts { + id, + title, + body, + } + }`, + }) + .expect(200, { + data: { + getPosts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + ], + }, + }); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/tests/e2e/graphql-gateway-fastify.spec.ts b/tests/e2e/graphql-gateway-fastify.spec.ts new file mode 100644 index 000000000..c26e6dbbf --- /dev/null +++ b/tests/e2e/graphql-gateway-fastify.spec.ts @@ -0,0 +1,118 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule as PostsModule } from '../graphql-federation/posts-service/federation-posts.module'; +import { AppModule as UsersModule } from '../graphql-federation/users-service/federation-users.module'; +import { AppModule as GatewayModule } from '../graphql-federation/gateway/gateway.module'; +import { FastifyAdapter } from '@nestjs/platform-fastify'; + +describe('GraphQL Gateway with fastify', () => { + let postsApp: INestApplication; + let usersApp: INestApplication; + let gatewayApp: INestApplication; + + beforeEach(async () => { + const usersModule = await Test.createTestingModule({ + imports: [UsersModule], + }).compile(); + + usersApp = usersModule.createNestApplication(); + await usersApp.listenAsync(3001); + + const postsModule = await Test.createTestingModule({ + imports: [PostsModule], + }).compile(); + + postsApp = postsModule.createNestApplication(); + await postsApp.listenAsync(3002); + + const gatewayModule = await Test.createTestingModule({ + imports: [GatewayModule], + }).compile(); + + gatewayApp = gatewayModule.createNestApplication(new FastifyAdapter()); + await gatewayApp.init(); + await gatewayApp + .getHttpAdapter() + .getInstance() + .ready(); + }); + + it(`should run lookup across boundaries`, () => { + return request(gatewayApp.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getPosts { + id, + title, + body, + user { + id, + name, + } + } + }`, + }) + .expect(200, { + data: { + getPosts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + user: { + id: '5', + name: 'GraphQL', + }, + }, + ], + }, + }); + }); + + it(`should run reverse lookup across boundaries`, () => { + return request(gatewayApp.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getUser(id: "5") { + id, + name, + posts { + id, + title, + body, + } + } + }`, + }) + .expect(200, { + data: { + getUser: { + id: '5', + name: 'GraphQL', + posts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + ], + }, + }, + }); + }); + + afterEach(async () => { + await postsApp.close(); + await usersApp.close(); + await gatewayApp.close(); + }); +}); From c85b13916f0f8eb8f5893fa84baec25c210f3bb0 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Wed, 30 Oct 2019 08:34:33 +0100 Subject: [PATCH 20/33] test: add test for injecting custom buildservice into gateway --- .../gql-gateway-module-options.interface.ts | 2 +- .../e2e/graphql-gateway-buildservice.spec.ts | 87 +++++++++++++++++++ .../gateway/gateway-buildservice.module.ts | 34 ++++++++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/graphql-gateway-buildservice.spec.ts create mode 100644 tests/graphql-federation/gateway/gateway-buildservice.module.ts diff --git a/lib/interfaces/gql-gateway-module-options.interface.ts b/lib/interfaces/gql-gateway-module-options.interface.ts index 03217ab4b..5871873a6 100644 --- a/lib/interfaces/gql-gateway-module-options.interface.ts +++ b/lib/interfaces/gql-gateway-module-options.interface.ts @@ -22,7 +22,7 @@ export interface GatewayOptionsFactory { export interface GatewayModuleAsyncOptions extends Pick { useExisting?: Type; useClass?: Type; - useFactory?: (...args: any[]) => Promise | GqlModuleOptions; + useFactory?: (...args: any[]) => Promise | GatewayModuleOptions; inject?: any[]; } diff --git a/tests/e2e/graphql-gateway-buildservice.spec.ts b/tests/e2e/graphql-gateway-buildservice.spec.ts new file mode 100644 index 000000000..e96021ced --- /dev/null +++ b/tests/e2e/graphql-gateway-buildservice.spec.ts @@ -0,0 +1,87 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AppModule as PostsModule } from '../graphql-federation/posts-service/federation-posts.module'; +import { AppModule as UsersModule } from '../graphql-federation/users-service/federation-users.module'; +import { AppModule as GatewayModule } from '../graphql-federation/gateway/gateway-buildservice.module'; +import { RemoteGraphQLDataSource } from '@apollo/gateway'; +import { GRAPHQL_GATEWAY_BUILD_SERVICE } from '../../lib/graphql.constants'; + +describe('GraphQL Gateway buildservice', () => { + let postsApp: INestApplication; + let usersApp: INestApplication; + let gatewayApp: INestApplication; + let buildServiceHook: Function; + + beforeEach(async () => { + buildServiceHook = jest.fn(); + const usersModule = await Test.createTestingModule({ + imports: [UsersModule], + }).compile(); + + usersApp = usersModule.createNestApplication(); + await usersApp.listenAsync(3001); + + const postsModule = await Test.createTestingModule({ + imports: [PostsModule], + }).compile(); + + postsApp = postsModule.createNestApplication(); + await postsApp.listenAsync(3002); + + const gatewayModule = await Test.createTestingModule({ + imports: [GatewayModule], + }) + .overrideProvider(GRAPHQL_GATEWAY_BUILD_SERVICE) + .useValue(({ url }) => { + return new RemoteGraphQLDataSource({ url, willSendRequest: buildServiceHook as any }); + }) + .compile(); + + gatewayApp = gatewayModule.createNestApplication(); + await gatewayApp.init(); + }); + + it(`should run query through build service`, async () => { + await request(gatewayApp.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + getPosts { + id, + title, + body, + user { + id, + name, + } + } + }`, + }) + .expect(200, { + data: { + getPosts: [ + { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + user: { + id: '5', + name: 'GraphQL', + }, + }, + ], + }, + }); + expect(buildServiceHook).toHaveBeenCalled(); + }); + + afterEach(async () => { + await postsApp.close(); + await usersApp.close(); + await gatewayApp.close(); + }); +}); diff --git a/tests/graphql-federation/gateway/gateway-buildservice.module.ts b/tests/graphql-federation/gateway/gateway-buildservice.module.ts new file mode 100644 index 000000000..8b4c26284 --- /dev/null +++ b/tests/graphql-federation/gateway/gateway-buildservice.module.ts @@ -0,0 +1,34 @@ +import { Module } from '@nestjs/common'; +import { GraphQLGatewayModule } from '../../../lib/graphql-gateway.module'; +import { GRAPHQL_GATEWAY_BUILD_SERVICE } from '../../../lib/graphql.constants'; +import { RemoteGraphQLDataSource } from '@apollo/gateway'; + +@Module({ + providers: [ + { + provide: GRAPHQL_GATEWAY_BUILD_SERVICE, + useValue: ({ name, url }) => { + console.log('BuildService: %s', name); + return new RemoteGraphQLDataSource({ url }); + }, + }, + ], + exports: [GRAPHQL_GATEWAY_BUILD_SERVICE], +}) +class BuildServiceModule {} + +@Module({ + imports: [ + GraphQLGatewayModule.forRootAsync({ + useFactory: async () => ({ + serviceList: [ + { name: 'users', url: 'http://localhost:3001/graphql' }, + { name: 'posts', url: 'http://localhost:3002/graphql' }, + ], + }), + imports: [BuildServiceModule], + inject: [GRAPHQL_GATEWAY_BUILD_SERVICE], + }), + ], +}) +export class AppModule {} From aecc008482e6e8dea0e37a3fa2af9649d412f048 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Wed, 30 Oct 2019 13:16:06 +0100 Subject: [PATCH 21/33] build: update apollo federation and gateway --- .../gql-gateway-module-options.interface.ts | 6 +- package-lock.json | 241 +++++++++--------- package.json | 5 +- 3 files changed, 131 insertions(+), 121 deletions(-) diff --git a/lib/interfaces/gql-gateway-module-options.interface.ts b/lib/interfaces/gql-gateway-module-options.interface.ts index 5871873a6..9e4a3f169 100644 --- a/lib/interfaces/gql-gateway-module-options.interface.ts +++ b/lib/interfaces/gql-gateway-module-options.interface.ts @@ -3,6 +3,7 @@ import { Omit, GqlModuleOptions } from './gql-module-options.interface'; import { GatewayConfig, ServiceEndpointDefinition } from '@apollo/gateway'; import { GraphQLDataSource } from '@apollo/gateway/src/datasources/types'; import { ModuleMetadata } from '@nestjs/common/interfaces'; +import { HeaderInit } from 'node-fetch'; export interface GatewayModuleOptions extends Pick< @@ -14,7 +15,10 @@ export interface GatewayModuleOptions | 'bodyParserConfig' | 'installSubscriptionHandlers' >, - Omit {} + Omit { + serviceList: ServiceEndpointDefinition[]; + introspectionHeaders?: HeaderInit; +} export interface GatewayOptionsFactory { createGatewayOptions(): Promise | GatewayModuleOptions; diff --git a/package-lock.json b/package-lock.json index 1ea346d7f..aed808b15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,119 +5,114 @@ "requires": true, "dependencies": { "@apollo/federation": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/@apollo/federation/-/federation-0.6.10.tgz", - "integrity": "sha512-KdEg2N/T9OEV9wYtrhkNTxnoemap3gCKaJzxogf6a14cVHk9/HMrv8hDUcl3Uu3fzUq591JVBfBcLJntD+2FkQ==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@apollo/federation/-/federation-0.10.2.tgz", + "integrity": "sha512-N+JoD7YraGAgDJq1GTkAzRRPUA+FgclFgAUcUDng9ICA4N147nuGUrbrpQdgLnE59nCLJAhcFvOgQRs177XMWw==", "optional": true, "requires": { "apollo-env": "^0.5.1", - "apollo-graphql": "^0.3.3", - "apollo-server-env": "2.4.0" + "apollo-graphql": "^0.3.4", + "apollo-server-env": "^2.4.3", + "lodash.xorby": "^4.7.0" }, "dependencies": { - "apollo-server-env": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.0.tgz", - "integrity": "sha512-7ispR68lv92viFeu5zsRUVGP+oxsVI3WeeBNniM22Cx619maBUwcYTIC3+Y3LpXILhLZCzA1FASZwusgSlyN9w==", + "apollo-graphql": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.3.4.tgz", + "integrity": "sha512-w+Az1qxePH4oQ8jvbhQBl5iEVvqcqynmU++x/M7MM5xqN1C7m1kyIzpN17gybXlTJXY4Oxej2WNURC2/hwpfYw==", "optional": true, "requires": { - "node-fetch": "^2.1.2", - "util.promisify": "^1.0.0" + "apollo-env": "^0.5.1", + "lodash.sortby": "^4.7.0" } } } }, "@apollo/gateway": { - "version": "0.6.14", - "resolved": "https://registry.npmjs.org/@apollo/gateway/-/gateway-0.6.14.tgz", - "integrity": "sha512-/DSk0PHhl+2vItPslKvtjnvK1uVYkaKxebkA+S4I9BTB4OJSuiXTvE0aH8ZaEthBEGczCsdmrLRQU4qGJfVcUw==", + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@apollo/gateway/-/gateway-0.10.8.tgz", + "integrity": "sha512-j+FR/ui9G+A2h6bqlhmf1AzJLft3DH96uj4CV3K/CvAXbhlQLXAK+snNhxwrS/YD7nBjOsMTzzzSa1bJOpayYg==", "optional": true, "requires": { - "@apollo/federation": "0.6.10", + "@apollo/federation": "^0.10.2", + "apollo-engine-reporting-protobuf": "^0.4.1", "apollo-env": "^0.5.1", - "apollo-graphql": "^0.3.3", - "apollo-server-caching": "0.4.0", - "apollo-server-core": "2.6.9", - "apollo-server-env": "2.4.0", + "apollo-graphql": "^0.3.4", + "apollo-server-caching": "^0.5.0", + "apollo-server-core": "^2.9.7", + "apollo-server-env": "^2.4.3", + "apollo-server-types": "^0.2.5", + "graphql-extensions": "^0.10.4", "loglevel": "^1.6.1", "loglevel-debug": "^0.0.1", "pretty-format": "^24.7.0" }, "dependencies": { - "@apollographql/apollo-tools": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.3.7.tgz", - "integrity": "sha512-+ertvzAwzkYmuUtT8zH3Zi6jPdyxZwOgnYaZHY7iLnMVJDhQKWlkyjLMF8wyzlPiEdDImVUMm5lOIBZo7LkGlg==", - "requires": { - "apollo-env": "0.5.1" - } - }, "apollo-cache-control": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.7.5.tgz", - "integrity": "sha512-zCPwHjbo/VlmXl0sclZfBq/MlVVeGUAg02Q259OIXSgHBvn9BbExyz+EkO/DJvZfGMquxqS1X1BFO3VKuLUTdw==", - "requires": { - "apollo-server-env": "2.4.0", - "graphql-extensions": "0.7.7" - } - }, - "apollo-datasource": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.5.0.tgz", - "integrity": "sha512-SVXxJyKlWguuDjxkY/WGlC/ykdsTmPxSF0z8FenagcQ91aPURXzXP1ZDz5PbamY+0iiCRubazkxtTQw4GWTFPg==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.8.5.tgz", + "integrity": "sha512-2yQ1vKgJQ54SGkoQS/ZLZrDX3La6cluAYYdruFYJMJtL4zQrSdeOCy11CQliCMYEd6eKNyE70Rpln51QswW2Og==", + "optional": true, "requires": { - "apollo-server-caching": "0.4.0", - "apollo-server-env": "2.4.0" + "apollo-server-env": "^2.4.3", + "graphql-extensions": "^0.10.4" } }, "apollo-engine-reporting": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-1.3.6.tgz", - "integrity": "sha512-oCoFAUBGveg1i1Sao/2gNsf1kirJBT6vw6Zan9BCNUkyh68ewDts+xRg32VnD9lDhaHpXVJ3tVtuaV44HmdSEw==", - "requires": { - "apollo-engine-reporting-protobuf": "0.3.1", - "apollo-graphql": "^0.3.3", - "apollo-server-core": "2.6.9", - "apollo-server-env": "2.4.0", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-1.4.7.tgz", + "integrity": "sha512-qsKDz9VkoctFhojM3Nj3nvRBO98t8TS2uTgtiIjUGs3Hln2poKMP6fIQ37Nm2Q2B3JJst76HQtpPwXmRJd1ZUg==", + "optional": true, + "requires": { + "apollo-engine-reporting-protobuf": "^0.4.1", + "apollo-graphql": "^0.3.4", + "apollo-server-caching": "^0.5.0", + "apollo-server-env": "^2.4.3", + "apollo-server-types": "^0.2.5", "async-retry": "^1.2.1", - "graphql-extensions": "0.7.7" + "graphql-extensions": "^0.10.4" } }, "apollo-engine-reporting-protobuf": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.3.1.tgz", - "integrity": "sha512-Ui3nPG6BSZF8BEqxFs6EkX6mj2OnFLMejxEHSOdM82bakyeouCGd7J0fiy8AD6liJoIyc4X7XfH4ZGGMvMh11A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.4.1.tgz", + "integrity": "sha512-d7vFFZ2oUrvGaN0Hpet8joe2ZG0X0lIGilN+SwgVP38dJnOuadjsaYMyrD9JudGQJg0bJA5wVQfYzcCVy0slrw==", + "optional": true, "requires": { "protobufjs": "^6.8.6" } }, - "apollo-server-caching": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.4.0.tgz", - "integrity": "sha512-GTOZdbLhrSOKYNWMYgaqX5cVNSMT0bGUTZKV8/tYlyYmsB6ey7l6iId3Q7UpHS6F6OR2lstz5XaKZ+T3fDfPzQ==", + "apollo-graphql": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.3.4.tgz", + "integrity": "sha512-w+Az1qxePH4oQ8jvbhQBl5iEVvqcqynmU++x/M7MM5xqN1C7m1kyIzpN17gybXlTJXY4Oxej2WNURC2/hwpfYw==", + "optional": true, "requires": { - "lru-cache": "^5.0.0" + "apollo-env": "^0.5.1", + "lodash.sortby": "^4.7.0" } }, "apollo-server-core": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.6.9.tgz", - "integrity": "sha512-r2/Kjm1UmxoTViUt5EcExWXkWl0riXsuGyS1q5LpHKKnA+6b+t4LQKECkRU4EWNpuuzJQn7aF7MmMdvURxoEig==", + "version": "2.9.7", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.9.7.tgz", + "integrity": "sha512-EqKyROy+21sM93YHjGpy6wlnzK/vH0fnZh7RCf3uB69aQ3OjgdP4AQ5oWRQ62NDN+aoic7OLhChSDJeDonq/NQ==", + "optional": true, "requires": { - "@apollographql/apollo-tools": "^0.3.6", + "@apollographql/apollo-tools": "^0.4.0", "@apollographql/graphql-playground-html": "1.6.24", + "@types/graphql-upload": "^8.0.0", "@types/ws": "^6.0.0", - "apollo-cache-control": "0.7.5", - "apollo-datasource": "0.5.0", - "apollo-engine-reporting": "1.3.6", - "apollo-server-caching": "0.4.0", - "apollo-server-env": "2.4.0", - "apollo-server-errors": "2.3.1", - "apollo-server-plugin-base": "0.5.8", - "apollo-tracing": "0.7.4", + "apollo-cache-control": "^0.8.5", + "apollo-datasource": "^0.6.3", + "apollo-engine-reporting": "^1.4.7", + "apollo-server-caching": "^0.5.0", + "apollo-server-env": "^2.4.3", + "apollo-server-errors": "^2.3.4", + "apollo-server-plugin-base": "^0.6.5", + "apollo-server-types": "^0.2.5", + "apollo-tracing": "^0.8.5", "fast-json-stable-stringify": "^2.0.0", - "graphql-extensions": "0.7.7", - "graphql-subscriptions": "^1.0.0", + "graphql-extensions": "^0.10.4", "graphql-tag": "^2.9.2", "graphql-tools": "^4.0.0", "graphql-upload": "^8.0.2", @@ -126,41 +121,51 @@ "ws": "^6.0.0" } }, - "apollo-server-env": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.0.tgz", - "integrity": "sha512-7ispR68lv92viFeu5zsRUVGP+oxsVI3WeeBNniM22Cx619maBUwcYTIC3+Y3LpXILhLZCzA1FASZwusgSlyN9w==", - "requires": { - "node-fetch": "^2.1.2", - "util.promisify": "^1.0.0" - } - }, "apollo-server-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.3.1.tgz", - "integrity": "sha512-errZvnh0vUQChecT7M4A/h94dnBSRL213dNxpM5ueMypaLYgnp4hiCTWIEaooo9E4yMGd1qA6WaNbLDG2+bjcg==" + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.3.4.tgz", + "integrity": "sha512-Y0PKQvkrb2Kd18d1NPlHdSqmlr8TgqJ7JQcNIfhNDgdb45CnqZlxL1abuIRhr8tiw8OhVOcFxz2KyglBi8TKdA==", + "optional": true }, "apollo-server-plugin-base": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.8.tgz", - "integrity": "sha512-ICbaXr0ycQZL5llbtZhg8zyHbxuZ4khdAJsJgiZaUXXP6+F47XfDQ5uwnl/4Sq9fvkpwS0ctvfZ1D+Ks4NvUzA==" + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.6.5.tgz", + "integrity": "sha512-z2ve7HEPWmZI3EzL0iiY9qyt1i0hitT+afN5PzssCw594LB6DfUQWsI14UW+W+gcw8hvl8VQUpXByfUntAx5vw==", + "optional": true, + "requires": { + "apollo-server-types": "^0.2.5" + } + }, + "apollo-server-types": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.2.5.tgz", + "integrity": "sha512-6iJQsPh59FWu4K7ABrVmpnQVgeK8Ockx8BcawBh+saFYWTlVczwcLyGSZPeV1tPSKwFwKZutyEslrYSafcarXQ==", + "optional": true, + "requires": { + "apollo-engine-reporting-protobuf": "^0.4.1", + "apollo-server-caching": "^0.5.0", + "apollo-server-env": "^2.4.3" + } }, "apollo-tracing": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.7.4.tgz", - "integrity": "sha512-vA0FJCBkFpwdWyVF5UtCqN+enShejyiqSGqq8NxXHU1+GEYTngWa56x9OGsyhX+z4aoDIa3HPKPnP3pjzA0qpg==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.8.5.tgz", + "integrity": "sha512-lZn10/GRBZUlMxVYLghLMFsGcLN0jTYDd98qZfBtxw+wEWUx+PKkZdljDT+XNoOm/kDvEutFGmi5tSLhArIzWQ==", + "optional": true, "requires": { - "apollo-server-env": "2.4.0", - "graphql-extensions": "0.7.7" + "apollo-server-env": "^2.4.3", + "graphql-extensions": "^0.10.4" } }, "graphql-extensions": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.7.7.tgz", - "integrity": "sha512-xiTbVGPUpLbF86Bc+zxI/v/axRkwZx3s+y2/kUb2c2MxNZeNhMZEw1dSutuhY2f2JkRkYFJii0ucjIVqPAQ/Lg==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.10.4.tgz", + "integrity": "sha512-lE6MroluEYocbR/ICwccv39w+Pz4cBPadJ11z1rJkbZv5wstISEganbDOwl9qN21rcZGiWzh7QUNxUiFUXXEDw==", + "optional": true, "requires": { - "@apollographql/apollo-tools": "^0.3.6", - "apollo-server-env": "2.4.0" + "@apollographql/apollo-tools": "^0.4.0", + "apollo-server-env": "^2.4.3", + "apollo-server-types": "^0.2.5" } } } @@ -946,7 +951,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", - "dev": true, "requires": { "@types/node": "*" } @@ -996,7 +1000,6 @@ "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", - "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -1021,7 +1024,6 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.4.tgz", "integrity": "sha512-oTGtMzZZAVuEjTwCjIh8T8FrC8n/uwy+PG0yTvQcdZ7etoel7C7/3MSd7qrukENTgQtotG7gvBlBojuVs7X5rw==", - "dev": true, "requires": { "@types/connect": "*", "@types/express": "*", @@ -1068,7 +1070,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz", "integrity": "sha512-FKVPOCFbhCvZxpVAMhdBdTfVfXUpsh15wFHgqOKxh9N9vzWZVuWCSijZ5T4U34XYNnuj2oduh6xcs1i+LPI+BQ==", - "dev": true, "requires": { "@types/node": "*" } @@ -1086,13 +1087,13 @@ "@types/graphql": { "version": "14.2.3", "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-14.2.3.tgz", - "integrity": "sha512-UoCovaxbJIxagCvVfalfK7YaNhmxj3BQFRQ2RHQKLiu+9wNXhJnlbspsLHt/YQM99IaLUUFJNzCwzc6W0ypMeQ==" + "integrity": "sha512-UoCovaxbJIxagCvVfalfK7YaNhmxj3BQFRQ2RHQKLiu+9wNXhJnlbspsLHt/YQM99IaLUUFJNzCwzc6W0ypMeQ==", + "dev": true }, "@types/graphql-upload": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/@types/graphql-upload/-/graphql-upload-8.0.3.tgz", "integrity": "sha512-hmLg9pCU/GmxBscg8GCr1vmSoEmbItNNxdD5YH2TJkXm//8atjwuprB+xJBK714JG1dkxbbhp5RHX+Pz1KsCMA==", - "dev": true, "requires": { "@types/express": "*", "@types/fs-capacitor": "*", @@ -1103,8 +1104,7 @@ "@types/http-assert": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz", - "integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==", - "dev": true + "integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==" }, "@types/istanbul-lib-coverage": { "version": "2.0.1", @@ -1174,8 +1174,7 @@ "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", - "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", - "dev": true + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" }, "@types/minimatch": { "version": "3.0.3", @@ -1196,6 +1195,12 @@ "@types/node": "*" } }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, "@types/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1211,8 +1216,7 @@ "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", - "dev": true + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" }, "@types/semver": { "version": "6.0.0", @@ -1224,7 +1228,6 @@ "version": "1.13.3", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", - "dev": true, "requires": { "@types/express-serve-static-core": "*", "@types/mime": "*" @@ -1524,7 +1527,6 @@ "version": "2.4.3", "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.3.tgz", "integrity": "sha512-23R5Xo9OMYX0iyTu2/qT0EUb+AULCBriA9w8HDfMoChB8M+lFClqUkYtaTTHDfp6eoARLW8kDBhPOBavsvKAjA==", - "dev": true, "requires": { "node-fetch": "^2.1.2", "util.promisify": "^1.0.0" @@ -3719,7 +3721,6 @@ "version": "14.5.8", "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.5.8.tgz", "integrity": "sha512-MMwmi0zlVLQKLdGiMfWkgQD7dY/TUKt4L+zgJ/aR0Howebod3aNgP5JkgvAULiR2HPVZaP2VEElqtdidHweLkg==", - "dev": true, "requires": { "iterall": "^1.2.2" } @@ -3748,6 +3749,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz", "integrity": "sha512-6WzlBFC0lWmXJbIVE8OgFgXIP4RJi3OQgTPa0DVMsDXdpRDjTsM1K9wfl5HSYX7R87QAGlvcv2Y4BIZa/ItonA==", + "dev": true, "requires": { "iterall": "^1.2.1" } @@ -3773,7 +3775,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/graphql-upload/-/graphql-upload-8.1.0.tgz", "integrity": "sha512-U2OiDI5VxYmzRKw0Z2dmfk0zkqMRaecH9Smh1U277gVgVe9Qn+18xqf4skwr4YJszGIh7iQDZ57+5ygOK9sM/Q==", - "dev": true, "requires": { "busboy": "^0.3.1", "fs-capacitor": "^2.0.4", @@ -3803,7 +3804,6 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.4", @@ -3815,8 +3815,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" } } }, @@ -3953,6 +3952,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -6156,6 +6156,12 @@ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, + "lodash.xorby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.xorby/-/lodash.xorby-4.7.0.tgz", + "integrity": "sha1-nBmm+fBjputT3QPBtocXmYAUY9c=", + "optional": true + }, "log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -8807,8 +8813,7 @@ "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yaml": { "version": "1.7.2", diff --git a/package.json b/package.json index 4e01ad4f9..0d36ff549 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@types/graphql": "14.2.3", "@types/jest": "24.9.0", "@types/node": "12.12.21", + "@types/node-fetch": "^2.5.2", "@types/normalize-path": "3.0.0", "apollo-server-express": "2.9.16", "apollo-server-fastify": "2.9.16", @@ -58,8 +59,8 @@ "reflect-metadata": "^0.1.12" }, "optionalDependencies": { - "@apollo/federation": "^0.6.4", - "@apollo/gateway": "^0.6.7", + "@apollo/federation": "^0.10.1", + "@apollo/gateway": "^0.10.8", "type-graphql": "^0.17.3" }, "lint-staged": { From 1d3ef174f819097fb4fbc64ba6b636f93c3f31a3 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Wed, 30 Oct 2019 15:09:41 +0100 Subject: [PATCH 22/33] fix: change GatewayModuleOptions interface Should (theoretically) allow managed federation --- lib/graphql-gateway.module.ts | 14 +++++----- .../gql-gateway-module-options.interface.ts | 27 +++++++++---------- .../gateway/config/config.service.ts | 10 ++++--- .../gateway/gateway-buildservice.module.ts | 10 ++++--- .../gateway/gateway.module.ts | 10 ++++--- 5 files changed, 37 insertions(+), 34 deletions(-) diff --git a/lib/graphql-gateway.module.ts b/lib/graphql-gateway.module.ts index 5bbd66124..395572d4f 100644 --- a/lib/graphql-gateway.module.ts +++ b/lib/graphql-gateway.module.ts @@ -94,21 +94,19 @@ export class GraphQLGatewayModule implements OnModuleInit { const { ApolloGateway } = loadPackage('@apollo/gateway', 'ApolloGateway'); const { - options: { __exposeQueryPlanExperimental, debug, serviceList, installSubscriptionHandlers }, + options: { server: serverOpts = {}, gateway: gatewayOpts = {} }, buildService, } = this; const gateway = new ApolloGateway({ - __exposeQueryPlanExperimental, - debug, - serviceList, + ...gatewayOpts, buildService, }); const { schema, executor } = await gateway.load(); this.registerGqlServer({ schema, executor }); - if (installSubscriptionHandlers) { + if (serverOpts.installSubscriptionHandlers) { // TL;DR throw new Error('No support for subscriptions yet when using Apollo Federation'); /*this.apolloServer.installSubscriptionHandlers( @@ -134,7 +132,8 @@ export class GraphQLGatewayModule implements OnModuleInit { const { ApolloServer } = loadPackage('apollo-server-express', 'GraphQLModule', () => require('apollo-server-express'), ); - const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } = this.options; + const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } = + this.options.server || {}; const app = this.httpAdapterHost.httpAdapter.getInstance(); const apolloServer = new ApolloServer(apolloOptions); @@ -158,7 +157,8 @@ export class GraphQLGatewayModule implements OnModuleInit { const app = httpAdapter.getInstance(); const apolloServer = new ApolloServer(apolloOptions as any); - const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } = this.options; + const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } = + this.options.server || {}; app.register( apolloServer.createHandler({ disableHealthCheck, diff --git a/lib/interfaces/gql-gateway-module-options.interface.ts b/lib/interfaces/gql-gateway-module-options.interface.ts index 9e4a3f169..ebb972081 100644 --- a/lib/interfaces/gql-gateway-module-options.interface.ts +++ b/lib/interfaces/gql-gateway-module-options.interface.ts @@ -1,23 +1,20 @@ import { Type } from '@nestjs/common'; -import { Omit, GqlModuleOptions } from './gql-module-options.interface'; +import { GqlModuleOptions } from './gql-module-options.interface'; import { GatewayConfig, ServiceEndpointDefinition } from '@apollo/gateway'; import { GraphQLDataSource } from '@apollo/gateway/src/datasources/types'; import { ModuleMetadata } from '@nestjs/common/interfaces'; -import { HeaderInit } from 'node-fetch'; -export interface GatewayModuleOptions - extends Pick< - GqlModuleOptions, - | 'path' - | 'disableHealthCheck' - | 'onHealthCheck' - | 'cors' - | 'bodyParserConfig' - | 'installSubscriptionHandlers' - >, - Omit { - serviceList: ServiceEndpointDefinition[]; - introspectionHeaders?: HeaderInit; +export interface GatewayModuleOptions { + gateway?: GatewayConfig; + server?: Pick< + GqlModuleOptions, + | 'path' + | 'disableHealthCheck' + | 'onHealthCheck' + | 'cors' + | 'bodyParserConfig' + | 'installSubscriptionHandlers' + >; } export interface GatewayOptionsFactory { diff --git a/tests/graphql-federation/gateway/config/config.service.ts b/tests/graphql-federation/gateway/config/config.service.ts index 221205230..55b3a7b70 100644 --- a/tests/graphql-federation/gateway/config/config.service.ts +++ b/tests/graphql-federation/gateway/config/config.service.ts @@ -5,10 +5,12 @@ import { GatewayModuleOptions, GatewayOptionsFactory } from '../../../../lib'; export class ConfigService implements GatewayOptionsFactory { public createGatewayOptions(): Partial { return { - serviceList: [ - { name: 'users', url: 'http://localhost:3001/graphql' }, - { name: 'posts', url: 'http://localhost:3002/graphql' }, - ], + gateway: { + serviceList: [ + { name: 'users', url: 'http://localhost:3001/graphql' }, + { name: 'posts', url: 'http://localhost:3002/graphql' }, + ], + }, }; } } diff --git a/tests/graphql-federation/gateway/gateway-buildservice.module.ts b/tests/graphql-federation/gateway/gateway-buildservice.module.ts index 8b4c26284..11ed6605a 100644 --- a/tests/graphql-federation/gateway/gateway-buildservice.module.ts +++ b/tests/graphql-federation/gateway/gateway-buildservice.module.ts @@ -21,10 +21,12 @@ class BuildServiceModule {} imports: [ GraphQLGatewayModule.forRootAsync({ useFactory: async () => ({ - serviceList: [ - { name: 'users', url: 'http://localhost:3001/graphql' }, - { name: 'posts', url: 'http://localhost:3002/graphql' }, - ], + gateway: { + serviceList: [ + { name: 'users', url: 'http://localhost:3001/graphql' }, + { name: 'posts', url: 'http://localhost:3002/graphql' }, + ], + }, }), imports: [BuildServiceModule], inject: [GRAPHQL_GATEWAY_BUILD_SERVICE], diff --git a/tests/graphql-federation/gateway/gateway.module.ts b/tests/graphql-federation/gateway/gateway.module.ts index 9600f3294..4a0e9f8d8 100644 --- a/tests/graphql-federation/gateway/gateway.module.ts +++ b/tests/graphql-federation/gateway/gateway.module.ts @@ -4,10 +4,12 @@ import { GraphQLGatewayModule } from '../../../lib/graphql-gateway.module'; @Module({ imports: [ GraphQLGatewayModule.forRoot({ - serviceList: [ - { name: 'users', url: 'http://localhost:3001/graphql' }, - { name: 'posts', url: 'http://localhost:3002/graphql' }, - ], + gateway: { + serviceList: [ + { name: 'users', url: 'http://localhost:3001/graphql' }, + { name: 'posts', url: 'http://localhost:3002/graphql' }, + ], + }, }), ], }) From 5689b212f38607dea5b4a1ebe5a58a64f2339842 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Wed, 30 Oct 2019 15:42:16 +0100 Subject: [PATCH 23/33] style: cleanup unused imports --- .../gateway/gateway-buildservice.module.ts | 1 - .../posts-service/posts/posts.resolvers.ts | 10 +--------- .../users-service/users/users.resolvers.ts | 13 ++----------- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/tests/graphql-federation/gateway/gateway-buildservice.module.ts b/tests/graphql-federation/gateway/gateway-buildservice.module.ts index 11ed6605a..2a9416802 100644 --- a/tests/graphql-federation/gateway/gateway-buildservice.module.ts +++ b/tests/graphql-federation/gateway/gateway-buildservice.module.ts @@ -8,7 +8,6 @@ import { RemoteGraphQLDataSource } from '@apollo/gateway'; { provide: GRAPHQL_GATEWAY_BUILD_SERVICE, useValue: ({ name, url }) => { - console.log('BuildService: %s', name); return new RemoteGraphQLDataSource({ url }); }, }, diff --git a/tests/graphql-federation/posts-service/posts/posts.resolvers.ts b/tests/graphql-federation/posts-service/posts/posts.resolvers.ts index 6b80e7436..9e0d8dac2 100644 --- a/tests/graphql-federation/posts-service/posts/posts.resolvers.ts +++ b/tests/graphql-federation/posts-service/posts/posts.resolvers.ts @@ -1,12 +1,4 @@ -import { - Args, - Mutation, - Query, - Resolver, - ResolveReference, - Parent, - ResolveProperty, -} from '../../../../lib'; +import { Query, Resolver, Parent, ResolveProperty } from '../../../../lib'; import { PostsService } from './posts.service'; import { Post } from './posts.interfaces'; diff --git a/tests/graphql-federation/users-service/users/users.resolvers.ts b/tests/graphql-federation/users-service/users/users.resolvers.ts index 7762d9722..76b21101b 100644 --- a/tests/graphql-federation/users-service/users/users.resolvers.ts +++ b/tests/graphql-federation/users-service/users/users.resolvers.ts @@ -1,14 +1,5 @@ -import { - Args, - Mutation, - Query, - Resolver, - ResolveReference, - Parent, - ResolveProperty, -} from '../../../../lib'; +import { Args, Query, Resolver, ResolveReference } from '../../../../lib'; import { UsersService } from './users.service'; -import { User } from './users.interfaces'; @Resolver('User') export class UsersResolvers { @@ -20,7 +11,7 @@ export class UsersResolvers { } @ResolveReference() - resolveReference(reference: any) { + resolveReference(reference: { __typename: string; id: string }) { return this.usersService.findById(reference.id); } } From cf87ea7af96dee73476eb1616638100db9eabf08 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Wed, 30 Oct 2019 16:03:02 +0100 Subject: [PATCH 24/33] fix: export BuildService token --- lib/index.ts | 1 + .../graphql-federation/gateway/gateway-buildservice.module.ts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index a07886f7a..913fcb633 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,5 +1,6 @@ export * from './decorators'; export * from './graphql-ast.explorer'; +export { GRAPHQL_GATEWAY_BUILD_SERVICE } from './graphql.constants'; export * from './graphql-definitions.factory'; export * from './graphql-types.loader'; export * from './graphql.factory'; diff --git a/tests/graphql-federation/gateway/gateway-buildservice.module.ts b/tests/graphql-federation/gateway/gateway-buildservice.module.ts index 2a9416802..570a7074c 100644 --- a/tests/graphql-federation/gateway/gateway-buildservice.module.ts +++ b/tests/graphql-federation/gateway/gateway-buildservice.module.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; -import { GraphQLGatewayModule } from '../../../lib/graphql-gateway.module'; -import { GRAPHQL_GATEWAY_BUILD_SERVICE } from '../../../lib/graphql.constants'; +import { GRAPHQL_GATEWAY_BUILD_SERVICE, GraphQLGatewayModule } from '../../../lib'; import { RemoteGraphQLDataSource } from '@apollo/gateway'; @Module({ From 5c6e1687dab98027eb3ca4a2dd3cea0a699e2247 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Thu, 31 Oct 2019 10:37:32 +0100 Subject: [PATCH 25/33] fix: add all options for ApolloServer to Gateway Was missing support for context and plugins options --- lib/graphql-gateway.module.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/graphql-gateway.module.ts b/lib/graphql-gateway.module.ts index 395572d4f..946741e7d 100644 --- a/lib/graphql-gateway.module.ts +++ b/lib/graphql-gateway.module.ts @@ -1,5 +1,5 @@ import { DynamicModule, Inject, Module, OnModuleInit, Optional, Provider } from '@nestjs/common'; -import { ApolloServerBase, Config as ApolloServerConfig } from 'apollo-server-core'; +import { ApolloServerBase } from 'apollo-server-core'; import { HttpAdapterHost } from '@nestjs/core'; import { GRAPHQL_GATEWAY_BUILD_SERVICE, @@ -11,6 +11,7 @@ import { GatewayModuleOptions, GatewayOptionsFactory, GatewayModuleAsyncOptions, + GqlModuleOptions, } from './interfaces'; import { loadPackage } from '@nestjs/common/utils/load-package.util'; import { generateString } from './utils'; @@ -104,7 +105,7 @@ export class GraphQLGatewayModule implements OnModuleInit { }); const { schema, executor } = await gateway.load(); - this.registerGqlServer({ schema, executor }); + this.registerGqlServer({ ...serverOpts, schema, executor }); if (serverOpts.installSubscriptionHandlers) { // TL;DR @@ -115,7 +116,7 @@ export class GraphQLGatewayModule implements OnModuleInit { } } - private registerGqlServer(apolloOptions: ApolloServerConfig) { + private registerGqlServer(apolloOptions: GqlModuleOptions) { const httpAdapter = this.httpAdapterHost.httpAdapter; const adapterName = httpAdapter.constructor && httpAdapter.constructor.name; @@ -128,12 +129,11 @@ export class GraphQLGatewayModule implements OnModuleInit { } } - private registerExpress(apolloOptions: ApolloServerConfig) { + private registerExpress(apolloOptions: GqlModuleOptions) { const { ApolloServer } = loadPackage('apollo-server-express', 'GraphQLModule', () => require('apollo-server-express'), ); - const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } = - this.options.server || {}; + const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } = apolloOptions; const app = this.httpAdapterHost.httpAdapter.getInstance(); const apolloServer = new ApolloServer(apolloOptions); @@ -148,7 +148,7 @@ export class GraphQLGatewayModule implements OnModuleInit { this.apolloServer = apolloServer; } - private registerFastify(apolloOptions: ApolloServerConfig) { + private registerFastify(apolloOptions: GqlModuleOptions) { const { ApolloServer } = loadPackage('apollo-server-fastify', 'GraphQLModule', () => require('apollo-server-fastify'), ); @@ -156,9 +156,8 @@ export class GraphQLGatewayModule implements OnModuleInit { const httpAdapter = this.httpAdapterHost.httpAdapter; const app = httpAdapter.getInstance(); - const apolloServer = new ApolloServer(apolloOptions as any); - const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } = - this.options.server || {}; + const apolloServer = new ApolloServer(apolloOptions); + const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } = apolloOptions; app.register( apolloServer.createHandler({ disableHealthCheck, From a5bf10bfb1dc581e42bdc9e936b162879bf13315 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Tue, 5 Nov 2019 20:28:01 +0100 Subject: [PATCH 26/33] fix: support Apollo Graph Manager --- lib/graphql-gateway.module.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/graphql-gateway.module.ts b/lib/graphql-gateway.module.ts index 946741e7d..d73afcb37 100644 --- a/lib/graphql-gateway.module.ts +++ b/lib/graphql-gateway.module.ts @@ -104,8 +104,7 @@ export class GraphQLGatewayModule implements OnModuleInit { buildService, }); - const { schema, executor } = await gateway.load(); - this.registerGqlServer({ ...serverOpts, schema, executor }); + this.registerGqlServer({ ...serverOpts, gateway, subscriptions: false }); if (serverOpts.installSubscriptionHandlers) { // TL;DR From dfa7332d036ace4374b40c552ac40ec7f1b22097 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Wed, 6 Nov 2019 08:33:50 +0100 Subject: [PATCH 27/33] fix: scalar resolvers Type mismatch on GraphQL Federation factory caused scalars to be added to resolvers incorrectly. Fixed and tests added. --- lib/graphql-federation.factory.ts | 2 +- lib/graphql-federation.module.ts | 3 +- tests/e2e/graphql-federation.spec.ts | 28 +++++++++++++++++++ .../posts-service/posts/date.scalar.ts | 26 +++++++++++++++++ .../posts-service/posts/posts.interfaces.ts | 1 + .../posts-service/posts/posts.module.ts | 3 +- .../posts-service/posts/posts.resolvers.ts | 7 ++++- .../posts-service/posts/posts.service.ts | 7 +++++ .../posts-service/posts/posts.types.graphql | 7 +++++ 9 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 tests/graphql-federation/posts-service/posts/date.scalar.ts diff --git a/lib/graphql-federation.factory.ts b/lib/graphql-federation.factory.ts index 4762ba839..d328ab1ea 100644 --- a/lib/graphql-federation.factory.ts +++ b/lib/graphql-federation.factory.ts @@ -27,8 +27,8 @@ export class GraphQLFederationFactory { const resolvers = this.extendResolvers([ this.resolversExplorerService.explore(), - this.scalarsExplorerService.explore(), this.delegatesExplorerService.explore(), + ...this.scalarsExplorerService.explore(), ]); const schema = buildFederatedSchema([ diff --git a/lib/graphql-federation.module.ts b/lib/graphql-federation.module.ts index f4aac6636..9f2eebb78 100644 --- a/lib/graphql-federation.module.ts +++ b/lib/graphql-federation.module.ts @@ -112,10 +112,9 @@ export class GraphQLFederationModule implements OnModuleInit { const { printSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); const { typePaths } = this.options; - const typeDefs = await this.graphqlTypesLoader.getTypesFromPaths(typePaths); - const mergedTypeDefs = extend(typeDefs, this.options.typeDefs); + const apolloOptions = await this.graphqlFederationFactory.mergeOptions({ ...this.options, typeDefs: mergedTypeDefs, diff --git a/tests/e2e/graphql-federation.spec.ts b/tests/e2e/graphql-federation.spec.ts index 71d3e2125..998e2b0f8 100644 --- a/tests/e2e/graphql-federation.spec.ts +++ b/tests/e2e/graphql-federation.spec.ts @@ -113,6 +113,34 @@ describe('GraphQL Federation', () => { }, }); }); + + it(`should handle scalars`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + mutation { + publishPost(id: "1", publishDate: 500) { + id, + title, + body, + publishDate + } + }`, + }) + .expect(200, { + data: { + publishPost: { + id: '1', + title: 'Hello world', + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + publishDate: 500, + }, + }, + }); + }); }); afterEach(async () => { diff --git a/tests/graphql-federation/posts-service/posts/date.scalar.ts b/tests/graphql-federation/posts-service/posts/date.scalar.ts new file mode 100644 index 000000000..fb49f412f --- /dev/null +++ b/tests/graphql-federation/posts-service/posts/date.scalar.ts @@ -0,0 +1,26 @@ +import { Scalar, CustomScalar } from '../../../../lib'; +import { Kind, ValueNode } from 'graphql'; + +@Scalar('Date') +export class DateScalar implements CustomScalar { + description = 'Date custom scalar type'; + + parseValue(value: any): Date { + const date = new Date(parseInt(value, 10)); // value from the client + if (isNaN(date.getTime())) { + throw new TypeError('Invalid date given'); + } + return date; + } + + serialize(value: Date): number { + return value.getTime(); // value sent to the client + } + + parseLiteral(ast: ValueNode): Date { + if (ast.kind === Kind.INT) { + return new Date(parseInt(ast.value, 10)); // value from the client + } + return null; + } +} diff --git a/tests/graphql-federation/posts-service/posts/posts.interfaces.ts b/tests/graphql-federation/posts-service/posts/posts.interfaces.ts index 986518ad2..1ff6a20f9 100644 --- a/tests/graphql-federation/posts-service/posts/posts.interfaces.ts +++ b/tests/graphql-federation/posts-service/posts/posts.interfaces.ts @@ -3,4 +3,5 @@ export interface Post { title: string; body: string; userId: string; + publishDate: Date; } diff --git a/tests/graphql-federation/posts-service/posts/posts.module.ts b/tests/graphql-federation/posts-service/posts/posts.module.ts index 9fa531c4b..3747ab33a 100644 --- a/tests/graphql-federation/posts-service/posts/posts.module.ts +++ b/tests/graphql-federation/posts-service/posts/posts.module.ts @@ -2,8 +2,9 @@ import { Module } from '@nestjs/common'; import { PostsResolvers } from './posts.resolvers'; import { UsersResolvers } from './users.resolvers'; import { PostsService } from './posts.service'; +import { DateScalar } from './date.scalar'; @Module({ - providers: [PostsResolvers, PostsService, UsersResolvers], + providers: [PostsResolvers, PostsService, UsersResolvers, DateScalar], }) export class PostsModule {} diff --git a/tests/graphql-federation/posts-service/posts/posts.resolvers.ts b/tests/graphql-federation/posts-service/posts/posts.resolvers.ts index 9e0d8dac2..b4b7bfbf4 100644 --- a/tests/graphql-federation/posts-service/posts/posts.resolvers.ts +++ b/tests/graphql-federation/posts-service/posts/posts.resolvers.ts @@ -1,4 +1,4 @@ -import { Query, Resolver, Parent, ResolveProperty } from '../../../../lib'; +import { Query, Resolver, Parent, ResolveProperty, Mutation, Args } from '../../../../lib'; import { PostsService } from './posts.service'; import { Post } from './posts.interfaces'; @@ -11,6 +11,11 @@ export class PostsResolvers { return this.postsService.findAll(); } + @Mutation() + publishPost(@Args('id') id, @Args('publishDate') publishDate: Date) { + return this.postsService.publish(id, publishDate); + } + @ResolveProperty('user') getUser(@Parent() post: Post) { return { __typename: 'User', id: post.userId }; diff --git a/tests/graphql-federation/posts-service/posts/posts.service.ts b/tests/graphql-federation/posts-service/posts/posts.service.ts index 1a096af41..ed28475b9 100644 --- a/tests/graphql-federation/posts-service/posts/posts.service.ts +++ b/tests/graphql-federation/posts-service/posts/posts.service.ts @@ -9,6 +9,7 @@ export class PostsService { title: 'Hello world', body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', userId: '5', + publishDate: new Date(0), }, ]; @@ -23,4 +24,10 @@ export class PostsService { findByUserId(id: string) { return Promise.resolve(this.posts.filter(p => p.userId === id)); } + + async publish(id: string, publishDate: Date) { + const post = await this.findById(id); + post.publishDate = publishDate; + return post; + } } diff --git a/tests/graphql-federation/posts-service/posts/posts.types.graphql b/tests/graphql-federation/posts-service/posts/posts.types.graphql index 89d8bbf5d..2a508fbd2 100644 --- a/tests/graphql-federation/posts-service/posts/posts.types.graphql +++ b/tests/graphql-federation/posts-service/posts/posts.types.graphql @@ -1,8 +1,11 @@ +scalar Date + type Post @key(fields: "id") { id: ID! title: String! body: String! user: User + publishDate: Date } extend type User @key(fields: "id") { @@ -12,4 +15,8 @@ extend type User @key(fields: "id") { extend type Query { getPosts: [Post] +} + +extend type Mutation { + publishPost(id: ID!, publishDate: Date!): Post } \ No newline at end of file From 32f395b35cdd699c5493a5d213b91ff471b5096a Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Wed, 6 Nov 2019 09:13:16 +0100 Subject: [PATCH 28/33] fix: support for external resolvers Allows you to pass `graphql-type-json` as resolver to GraphQLFederationModule --- lib/graphql-federation.factory.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/graphql-federation.factory.ts b/lib/graphql-federation.factory.ts index d328ab1ea..909982992 100644 --- a/lib/graphql-federation.factory.ts +++ b/lib/graphql-federation.factory.ts @@ -25,10 +25,14 @@ export class GraphQLFederationFactory { async mergeOptions(options: GqlModuleOptions = {}): Promise { const { buildFederatedSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); + const externalResolvers = Array.isArray(options.resolvers) + ? options.resolvers + : [options.resolvers]; const resolvers = this.extendResolvers([ this.resolversExplorerService.explore(), this.delegatesExplorerService.explore(), ...this.scalarsExplorerService.explore(), + ...externalResolvers, ]); const schema = buildFederatedSchema([ From 77ae270daaa68c078ce61dab8c7d445e784993f7 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Wed, 13 Nov 2019 20:45:20 +0100 Subject: [PATCH 29/33] fix(federation): use type merger from 'merge-graphql-schemas' --- lib/graphql-definitions.factory.ts | 2 +- lib/graphql-federation.module.ts | 7 +++---- lib/graphql-types.loader.ts | 6 ++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/graphql-definitions.factory.ts b/lib/graphql-definitions.factory.ts index 9d89469f9..b8a066eea 100644 --- a/lib/graphql-definitions.factory.ts +++ b/lib/graphql-definitions.factory.ts @@ -71,7 +71,7 @@ export class GraphQLDefinitionsFactory { outputAs: 'class' | 'interface', isDebugEnabled: boolean, ) { - const typeDefs = await this.gqlTypesLoader.getTypesFromPaths(typePaths); + const typeDefs = await this.gqlTypesLoader.mergeTypesByPaths(typePaths); const { buildFederatedSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); const { printSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); diff --git a/lib/graphql-federation.module.ts b/lib/graphql-federation.module.ts index 9f2eebb78..f0402c74d 100644 --- a/lib/graphql-federation.module.ts +++ b/lib/graphql-federation.module.ts @@ -15,7 +15,7 @@ import { GraphQLTypesLoader } from './graphql-types.loader'; import { GraphQLSchemaBuilder } from './graphql-schema-builder'; import { GRAPHQL_MODULE_ID, GRAPHQL_MODULE_OPTIONS } from './graphql.constants'; import { GqlModuleAsyncOptions, GqlModuleOptions, GqlOptionsFactory } from './interfaces'; -import { generateString, extend, mergeDefaults, normalizeRoutePath } from './utils'; +import { generateString, mergeDefaults, normalizeRoutePath } from './utils'; import { GraphQLFactory } from './graphql.factory'; @Module({ @@ -112,12 +112,11 @@ export class GraphQLFederationModule implements OnModuleInit { const { printSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); const { typePaths } = this.options; - const typeDefs = await this.graphqlTypesLoader.getTypesFromPaths(typePaths); - const mergedTypeDefs = extend(typeDefs, this.options.typeDefs); + const typeDefs = await this.graphqlTypesLoader.mergeTypesByPaths(typePaths); const apolloOptions = await this.graphqlFederationFactory.mergeOptions({ ...this.options, - typeDefs: mergedTypeDefs, + typeDefs, }); if (this.options.definitions && this.options.definitions.path) { diff --git a/lib/graphql-types.loader.ts b/lib/graphql-types.loader.ts index bb6604627..ee8623f5d 100644 --- a/lib/graphql-types.loader.ts +++ b/lib/graphql-types.loader.ts @@ -20,10 +20,8 @@ export class GraphQLTypesLoader { return mergeTypes(flatTypes, { all: true }) as any; } - async getTypesFromPaths(paths: string | string[]): Promise { - paths = util.isArray(paths) - ? paths.map(path => normalize(path)) - : normalize(paths); + private async getTypesFromPaths(paths: string | string[]): Promise { + paths = util.isArray(paths) ? paths.map(path => normalize(path)) : normalize(paths); const filePaths = await glob(paths, { ignore: ['node_modules'], From bc4be4897efc9ee8e1a5cbd0377dd1df335715a5 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Wed, 13 Nov 2019 21:07:20 +0100 Subject: [PATCH 30/33] fix(federation): allow apollo engine config in gateway Replaced the Pick with it's inversion which allows more Apollo Server options to pass through. --- .../gql-gateway-module-options.interface.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/interfaces/gql-gateway-module-options.interface.ts b/lib/interfaces/gql-gateway-module-options.interface.ts index ebb972081..969120cd1 100644 --- a/lib/interfaces/gql-gateway-module-options.interface.ts +++ b/lib/interfaces/gql-gateway-module-options.interface.ts @@ -6,14 +6,22 @@ import { ModuleMetadata } from '@nestjs/common/interfaces'; export interface GatewayModuleOptions { gateway?: GatewayConfig; - server?: Pick< + server?: Omit< GqlModuleOptions, - | 'path' - | 'disableHealthCheck' - | 'onHealthCheck' - | 'cors' - | 'bodyParserConfig' - | 'installSubscriptionHandlers' + | 'typeDefs' + | 'typePaths' + | 'include' + | 'resolvers' + | 'resolverValidationOptions' + | 'directiveResolvers' + | 'autoSchemaFile' + | 'transformSchema' + | 'definitions' + | 'schema' + | 'subscriptions' + | 'schemaDirectives' + | 'buildSchemaOptions' + | 'fieldResolverEnhancers' >; } From ef2e40961dc4b982caae3f07d56ff5d8fdb65c39 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Sun, 17 Nov 2019 17:20:32 +0100 Subject: [PATCH 31/33] feat: add code-first support for grahql federation Blocked until support for Directives lands in type-graphql[1] 1. https://github.com/MichalLytek/type-graphql/issues/351 --- lib/external/type-graphql.types.ts | 17 ++-- lib/graphql-federation.factory.ts | 89 ++++++++++++++----- lib/graphql-schema-builder.ts | 50 +++++++++-- tests/e2e/typegraphql-federation.spec.ts | 42 +++++++++ tests/type-graphql-federation/app.module.ts | 20 +++++ tests/type-graphql-federation/main.ts | 10 +++ .../post/post.entity.ts | 18 ++++ .../post/post.module.ts | 9 ++ .../post/post.resolver.ts | 23 +++++ .../post/post.service.ts | 34 +++++++ .../user/user.entity.ts | 18 ++++ .../user/user.module.ts | 9 ++ .../user/user.resolver.ts | 13 +++ 13 files changed, 314 insertions(+), 38 deletions(-) create mode 100644 tests/e2e/typegraphql-federation.spec.ts create mode 100644 tests/type-graphql-federation/app.module.ts create mode 100644 tests/type-graphql-federation/main.ts create mode 100644 tests/type-graphql-federation/post/post.entity.ts create mode 100644 tests/type-graphql-federation/post/post.module.ts create mode 100644 tests/type-graphql-federation/post/post.resolver.ts create mode 100644 tests/type-graphql-federation/post/post.service.ts create mode 100644 tests/type-graphql-federation/user/user.entity.ts create mode 100644 tests/type-graphql-federation/user/user.module.ts create mode 100644 tests/type-graphql-federation/user/user.resolver.ts diff --git a/lib/external/type-graphql.types.ts b/lib/external/type-graphql.types.ts index 0ff1978e9..3203b2113 100644 --- a/lib/external/type-graphql.types.ts +++ b/lib/external/type-graphql.types.ts @@ -1,5 +1,5 @@ import { Type } from '@nestjs/common'; -import { GraphQLScalarType } from 'graphql'; +import { GraphQLDirective, GraphQLScalarType } from 'graphql'; /** * Some external types have to be included in order to provide types safety @@ -8,12 +8,7 @@ import { GraphQLScalarType } from 'graphql'; * see: https://github.com/19majkel94/type-graphql * 0.16.0 */ -export type TypeValue = - | Type - | GraphQLScalarType - | Function - | object - | symbol; +export type TypeValue = Type | GraphQLScalarType | Function | object | symbol; export type ReturnTypeFuncValue = TypeValue | [TypeValue]; export type ReturnTypeFunc = (returns?: void) => ReturnTypeFuncValue; export type NullableListOptions = 'items' | 'itemsAndList'; @@ -38,12 +33,14 @@ export interface ResolverClassOptions { } export type ClassTypeResolver = (of?: void) => Type; export type BasicOptions = DecoratorTypeOptions & DescriptionOptions; -export type AdvancedOptions = BasicOptions & - DepreciationOptions & - SchemaNameOptions; +export type AdvancedOptions = BasicOptions & DepreciationOptions & SchemaNameOptions; + export interface BuildSchemaOptions { dateScalarMode?: DateScalarMode; scalarsMap?: ScalarsTypeMap[]; + /** Any types that are not directly referenced or returned by resolvers */ + orphanedTypes?: Function[]; + directives?: GraphQLDirective[]; } export type DateScalarMode = 'isoDate' | 'timestamp'; export interface ScalarsTypeMap { diff --git a/lib/graphql-federation.factory.ts b/lib/graphql-federation.factory.ts index 909982992..5f1ad4d67 100644 --- a/lib/graphql-federation.factory.ts +++ b/lib/graphql-federation.factory.ts @@ -1,14 +1,19 @@ import { Injectable } from '@nestjs/common'; import { gql } from 'apollo-server-core'; import { loadPackage } from '@nestjs/common/utils/load-package.util'; - import { extend } from './utils'; +import { isEmpty, forEach } from 'lodash'; import { ScalarsExplorerService, DelegatesExplorerService, ResolversExplorerService, } from './services'; +import { mergeSchemas } from 'graphql-tools'; import { GqlModuleOptions } from './interfaces'; +import { GraphQLSchemaBuilder } from './graphql-schema-builder'; +import { GraphQLFactory } from './graphql.factory'; +import { GraphQLSchema, GraphQLSchemaConfig } from 'graphql'; +import { GraphQLObjectType } from 'graphql'; @Injectable() export class GraphQLFederationFactory { @@ -16,38 +21,80 @@ export class GraphQLFederationFactory { private readonly resolversExplorerService: ResolversExplorerService, private readonly delegatesExplorerService: DelegatesExplorerService, private readonly scalarsExplorerService: ScalarsExplorerService, + private readonly gqlSchemaBuilder: GraphQLSchemaBuilder, + private readonly graphqlFactory: GraphQLFactory, ) {} - private extendResolvers(resolvers: any[]) { - return resolvers.reduce((prev, curr) => extend(prev, curr), {}); + async mergeOptions(options: GqlModuleOptions = {}): Promise { + const transformSchema = async s => (options.transformSchema ? options.transformSchema(s) : s); + + let schema: GraphQLSchema; + if (options.autoSchemaFile) { + // Enable support when Directive support in type-graphql goes stable + throw new Error('Code-first not supported yet'); + schema = await this.generateSchema(options); + } else if (!isEmpty(options.typeDefs)) { + schema = options.schema; + } else { + schema = this.buildSchemaFromTypeDefs(options); + } + + return { + ...options, + schema: await transformSchema(schema), + typeDefs: undefined, + }; } - async mergeOptions(options: GqlModuleOptions = {}): Promise { + private buildSchemaFromTypeDefs(options: GqlModuleOptions) { const { buildFederatedSchema } = loadPackage('@apollo/federation', 'ApolloFederation'); - const externalResolvers = Array.isArray(options.resolvers) - ? options.resolvers - : [options.resolvers]; - const resolvers = this.extendResolvers([ - this.resolversExplorerService.explore(), - this.delegatesExplorerService.explore(), - ...this.scalarsExplorerService.explore(), - ...externalResolvers, - ]); - - const schema = buildFederatedSchema([ + return buildFederatedSchema([ { typeDefs: gql` ${options.typeDefs} `, - resolvers, + resolvers: this.getResolvers(options.resolvers), }, ]); + } + + private async generateSchema(options: GqlModuleOptions): Promise { + const { buildFederatedSchema, printSchema } = loadPackage( + '@apollo/federation', + 'ApolloFederation', + ); + + const autoGeneratedSchema: GraphQLSchema = await this.gqlSchemaBuilder.buildFederatedSchema( + options.autoSchemaFile, + options.buildSchemaOptions, + this.resolversExplorerService.getAllCtors(), + ); + const executableSchema = buildFederatedSchema({ + typeDefs: gql(printSchema(autoGeneratedSchema)), + resolvers: this.getResolvers(options.resolvers), + }); + + const schema = options.schema + ? mergeSchemas({ + schemas: [options.schema, executableSchema], + }) + : executableSchema; + + return schema; + } + + private getResolvers(optionResolvers) { + optionResolvers = Array.isArray(optionResolvers) ? optionResolvers : [optionResolvers]; + return this.extendResolvers([ + this.resolversExplorerService.explore(), + this.delegatesExplorerService.explore(), + ...this.scalarsExplorerService.explore(), + ...optionResolvers, + ]); + } - return { - ...options, - schema, - typeDefs: undefined, - }; + private extendResolvers(resolvers: any[]) { + return resolvers.reduce((prev, curr) => extend(prev, curr), {}); } } diff --git a/lib/graphql-schema-builder.ts b/lib/graphql-schema-builder.ts index 26ff2fd12..ee72d3b4c 100644 --- a/lib/graphql-schema-builder.ts +++ b/lib/graphql-schema-builder.ts @@ -1,15 +1,13 @@ import { Injectable } from '@nestjs/common'; import { loadPackage } from '@nestjs/common/utils/load-package.util'; -import { GraphQLSchema } from 'graphql'; +import { GraphQLSchema, specifiedDirectives } from 'graphql'; import { BuildSchemaOptions } from './external/type-graphql.types'; import { ScalarsExplorerService } from './services'; import { lazyMetadataStorage } from './storages/lazy-metadata.storage'; @Injectable() export class GraphQLSchemaBuilder { - constructor( - private readonly scalarsExplorerService: ScalarsExplorerService, - ) {} + constructor(private readonly scalarsExplorerService: ScalarsExplorerService) {} async build( autoSchemaFile: string | boolean, @@ -36,10 +34,48 @@ export class GraphQLSchemaBuilder { } } + async buildFederatedSchema( + autoSchemaFile: string | boolean, + options: BuildSchemaOptions = {}, + resolvers: Function[], + ) { + lazyMetadataStorage.load(); + + const buildSchema = this.loadBuildSchemaFactory(); + const scalarsMap = this.scalarsExplorerService.getScalarsMap(); + + try { + return await buildSchema({ + ...options, + directives: [ + ...specifiedDirectives, + ...this.loadFederationDirectives(), + ...((options && options.directives) || []), + ], + emitSchemaFile: autoSchemaFile !== true ? autoSchemaFile : false, + validate: false, + scalarsMap, + resolvers, + skipCheck: true, + }); + } catch (err) { + if (err && err.details) { + console.error(err.details); + } + throw err; + } + } + private loadBuildSchemaFactory(): (...args: any[]) => GraphQLSchema { - const { buildSchema } = loadPackage('type-graphql', 'SchemaBuilder', () => - require('type-graphql'), - ); + const { buildSchema } = loadPackage('type-graphql', 'SchemaBuilder'); return buildSchema; } + + private loadFederationDirectives() { + const { federationDirectives } = loadPackage( + '@apollo/federation/dist/directives', + 'SchemaBuilder', + ); + return federationDirectives; + } } diff --git a/tests/e2e/typegraphql-federation.spec.ts b/tests/e2e/typegraphql-federation.spec.ts new file mode 100644 index 000000000..cf1de8ca6 --- /dev/null +++ b/tests/e2e/typegraphql-federation.spec.ts @@ -0,0 +1,42 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { ApplicationModule } from '../type-graphql-federation/app.module'; + +describe.skip('TypeGraphQL - Federation', () => { + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [ApplicationModule], + }).compile(); + + app = module.createNestApplication(); + await app.init(); + }); + + it(`should return query result`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: ` + { + _service { sdl } + }`, + }) + .expect(200, { + data: { + _service: { + sdl: + 'type Post @key(fields: "id") {\n id: ID!\n title: String!\n authorId: Int!\n}\n\ntype Query {\n findPost(id: Float!): Post!\n getPosts: [Post!]!\n}\n\ntype User @extends @key(fields: "id") {\n id: ID! @external\n posts: [Post!]!\n}\n', + }, + }, + }); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/tests/type-graphql-federation/app.module.ts b/tests/type-graphql-federation/app.module.ts new file mode 100644 index 000000000..5542db0c4 --- /dev/null +++ b/tests/type-graphql-federation/app.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { GraphQLFederationModule } from '../../lib'; +import { UserModule } from './user/user.module'; +import { PostModule } from './post/post.module'; +import { User } from './user/user.entity'; + +@Module({ + imports: [ + UserModule, + PostModule, + GraphQLFederationModule.forRoot({ + debug: false, + autoSchemaFile: true, + buildSchemaOptions: { + orphanedTypes: [User], + }, + }), + ], +}) +export class ApplicationModule {} diff --git a/tests/type-graphql-federation/main.ts b/tests/type-graphql-federation/main.ts new file mode 100644 index 000000000..c04a543a1 --- /dev/null +++ b/tests/type-graphql-federation/main.ts @@ -0,0 +1,10 @@ +import { ValidationPipe } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; +import { ApplicationModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(ApplicationModule); + app.useGlobalPipes(new ValidationPipe()); + await app.listen(3000); +} +bootstrap(); diff --git a/tests/type-graphql-federation/post/post.entity.ts b/tests/type-graphql-federation/post/post.entity.ts new file mode 100644 index 000000000..5cd68f897 --- /dev/null +++ b/tests/type-graphql-federation/post/post.entity.ts @@ -0,0 +1,18 @@ +import { Field, ID, ObjectType, Directive, Int } from 'type-graphql'; + +@ObjectType() +@Directive('@key(fields: "id")') +export class Post { + @Field(type => ID) + public id: number; + + @Field() + public title: string; + + @Field(type => Int) + public authorId: number; + + constructor(post: Partial) { + Object.assign(this, post); + } +} diff --git a/tests/type-graphql-federation/post/post.module.ts b/tests/type-graphql-federation/post/post.module.ts new file mode 100644 index 000000000..1cba92ef0 --- /dev/null +++ b/tests/type-graphql-federation/post/post.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { PostService } from './post.service'; +import { PostResolver } from './post.resolver'; + +@Module({ + providers: [PostService, PostResolver], + exports: [PostService], +}) +export class PostModule {} diff --git a/tests/type-graphql-federation/post/post.resolver.ts b/tests/type-graphql-federation/post/post.resolver.ts new file mode 100644 index 000000000..c6a42e587 --- /dev/null +++ b/tests/type-graphql-federation/post/post.resolver.ts @@ -0,0 +1,23 @@ +import { Query, Args, ResolveReference, Resolver } from '../../../lib'; +import { PostService } from './post.service'; +import { Post } from './post.entity'; + +@Resolver(of => Post) +export class PostResolver { + constructor(private readonly postService: PostService) {} + + @Query(returns => Post) + public findPost(@Args('id') id: number) { + return this.postService.findOne(id); + } + + @Query(returns => [Post]) + public getPosts() { + return this.postService.all(); + } + + @ResolveReference() + public resolveRef(reference: any) { + return this.postService.findOne(reference.id); + } +} diff --git a/tests/type-graphql-federation/post/post.service.ts b/tests/type-graphql-federation/post/post.service.ts new file mode 100644 index 000000000..09f9a6a31 --- /dev/null +++ b/tests/type-graphql-federation/post/post.service.ts @@ -0,0 +1,34 @@ +import { Post } from './post.entity'; +import { Injectable } from '@nestjs/common'; + +const data = [ + { + id: 1, + title: 'hello world', + authorId: 2, + }, + { + id: 2, + title: 'lorem ipsum', + authorId: 1, + }, +]; + +@Injectable() +export class PostService { + public findOne(id: number) { + const post = data.find(p => p.id === id); + if (post) { + return new Post(post); + } + return null; + } + + public all() { + return data.map(p => new Post(p)); + } + + public forAuthor(authorId: number) { + return data.filter(p => p.authorId === authorId).map(p => new Post(p)); + } +} diff --git a/tests/type-graphql-federation/user/user.entity.ts b/tests/type-graphql-federation/user/user.entity.ts new file mode 100644 index 000000000..6b718d353 --- /dev/null +++ b/tests/type-graphql-federation/user/user.entity.ts @@ -0,0 +1,18 @@ +import { Field, ID, ObjectType, Directive } from 'type-graphql'; +import { Post } from '../post/post.entity'; + +@ObjectType() +@Directive('@extends') +@Directive('@key(fields: "id")') +export class User { + @Field(type => ID) + @Directive('@external') + public id: number; + + @Field(type => [Post]) + public posts: Post[]; + + constructor(post: Partial) { + Object.assign(this, post); + } +} diff --git a/tests/type-graphql-federation/user/user.module.ts b/tests/type-graphql-federation/user/user.module.ts new file mode 100644 index 000000000..502eb79af --- /dev/null +++ b/tests/type-graphql-federation/user/user.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { UserResolver } from './user.resolver'; +import { PostModule } from '../post/post.module'; + +@Module({ + providers: [UserResolver], + imports: [PostModule], +}) +export class UserModule {} diff --git a/tests/type-graphql-federation/user/user.resolver.ts b/tests/type-graphql-federation/user/user.resolver.ts new file mode 100644 index 000000000..5ced2e1db --- /dev/null +++ b/tests/type-graphql-federation/user/user.resolver.ts @@ -0,0 +1,13 @@ +import { ResolveProperty, Parent, Resolver } from '../../../lib'; +import { PostService } from '../post/post.service'; +import { User } from './user.entity'; + +@Resolver(of => User) +export class UserResolver { + constructor(private readonly postService: PostService) {} + + @ResolveProperty() + public posts(@Parent() user: User) { + return this.postService.forAuthor(user.id); + } +} From c8ba59019ca27d6c6503fef254f5bfba43777e22 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Thu, 5 Dec 2019 09:25:16 +0100 Subject: [PATCH 32/33] fix: invert logic for providing your own schema --- lib/graphql-federation.factory.ts | 2 +- tests/e2e/typegraphql-federation.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/graphql-federation.factory.ts b/lib/graphql-federation.factory.ts index 5f1ad4d67..cf8307ffe 100644 --- a/lib/graphql-federation.factory.ts +++ b/lib/graphql-federation.factory.ts @@ -33,7 +33,7 @@ export class GraphQLFederationFactory { // Enable support when Directive support in type-graphql goes stable throw new Error('Code-first not supported yet'); schema = await this.generateSchema(options); - } else if (!isEmpty(options.typeDefs)) { + } else if (isEmpty(options.typeDefs)) { schema = options.schema; } else { schema = this.buildSchemaFromTypeDefs(options); diff --git a/tests/e2e/typegraphql-federation.spec.ts b/tests/e2e/typegraphql-federation.spec.ts index cf1de8ca6..6e7e33fe2 100644 --- a/tests/e2e/typegraphql-federation.spec.ts +++ b/tests/e2e/typegraphql-federation.spec.ts @@ -1,14 +1,14 @@ import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; -import { ApplicationModule } from '../type-graphql-federation/app.module'; +// import { ApplicationModule } from '../type-graphql-federation/app.module'; describe.skip('TypeGraphQL - Federation', () => { let app: INestApplication; beforeEach(async () => { const module = await Test.createTestingModule({ - imports: [ApplicationModule], + imports: [], // ApplicationModule], }).compile(); app = module.createNestApplication(); From 48ff69ca62e3766006b229e02cde672f1fa6ff31 Mon Sep 17 00:00:00 2001 From: Rick Dutour Geerling Date: Mon, 20 Jan 2020 13:24:48 +0100 Subject: [PATCH 33/33] chore(deps): remove deprecated @types/graphql dependency --- package-lock.json | 65 ++++++++++++++++++++--------------------------- package.json | 2 -- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index aed808b15..5789e76c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,17 @@ "lodash.xorby": "^4.7.0" }, "dependencies": { + "apollo-env": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/apollo-env/-/apollo-env-0.5.1.tgz", + "integrity": "sha512-fndST2xojgSdH02k5hxk1cbqA9Ti8RX4YzzBoAB4oIe1Puhq7+YlhXGXfXB5Y4XN0al8dLg+5nAkyjNAR2qZTw==", + "optional": true, + "requires": { + "core-js": "^3.0.1", + "node-fetch": "^2.2.0", + "sha.js": "^2.4.11" + } + }, "apollo-graphql": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.3.4.tgz", @@ -82,6 +93,17 @@ "protobufjs": "^6.8.6" } }, + "apollo-env": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/apollo-env/-/apollo-env-0.5.1.tgz", + "integrity": "sha512-fndST2xojgSdH02k5hxk1cbqA9Ti8RX4YzzBoAB4oIe1Puhq7+YlhXGXfXB5Y4XN0al8dLg+5nAkyjNAR2qZTw==", + "optional": true, + "requires": { + "core-js": "^3.0.1", + "node-fetch": "^2.2.0", + "sha.js": "^2.4.11" + } + }, "apollo-graphql": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.3.4.tgz", @@ -203,7 +225,6 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.4.3.tgz", "integrity": "sha512-CtC1bmohB1owdGMT2ZZKacI94LcPAZDN2WvCe+4ZXT5d7xO5PHOAb70EP/LcFbvnS8QI+pkYRSCGFQnUcv9efg==", - "dev": true, "requires": { "apollo-env": "^0.6.1" } @@ -1015,7 +1036,6 @@ "version": "3.4.33", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", - "dev": true, "requires": { "@types/node": "*" } @@ -1049,7 +1069,6 @@ "version": "4.17.2", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", - "dev": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", @@ -1060,7 +1079,6 @@ "version": "4.17.1", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.1.tgz", "integrity": "sha512-9e7jj549ZI+RxY21Cl0t8uBnWyb22HzILupyHZjYEVK//5TT/1bZodU+yUbLnPdoYViBBnNWbxp4zYjGV0zUGw==", - "dev": true, "requires": { "@types/node": "*", "@types/range-parser": "*" @@ -1084,12 +1102,6 @@ "@types/node": "*" } }, - "@types/graphql": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-14.2.3.tgz", - "integrity": "sha512-UoCovaxbJIxagCvVfalfK7YaNhmxj3BQFRQ2RHQKLiu+9wNXhJnlbspsLHt/YQM99IaLUUFJNzCwzc6W0ypMeQ==", - "dev": true - }, "@types/graphql-upload": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/@types/graphql-upload/-/graphql-upload-8.0.3.tgz", @@ -1140,14 +1152,12 @@ "@types/keygrip": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", - "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==", - "dev": true + "integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==" }, "@types/koa": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.0.tgz", "integrity": "sha512-Hgx/1/rVlJvqYBrdeCsS7PDiR2qbxlMt1RnmNWD4Uxi5FF9nwkYqIldo7urjc+dfNpk+2NRGcnAYd4L5xEhCcQ==", - "dev": true, "requires": { "@types/accepts": "*", "@types/cookies": "*", @@ -1161,7 +1171,6 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz", "integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==", - "dev": true, "requires": { "@types/koa": "*" } @@ -1190,17 +1199,10 @@ "version": "2.5.4", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.4.tgz", "integrity": "sha512-Oz6id++2qAOFuOlE1j0ouk1dzl3mmI1+qINPNBhi9nt/gVOz0G+13Ao6qjhdF0Ys+eOkhu6JnFmt38bR3H0POQ==", - "dev": true, "requires": { "@types/node": "*" } }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true - }, "@types/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1243,7 +1245,6 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz", "integrity": "sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg==", - "dev": true, "requires": { "@types/node": "*" } @@ -1375,7 +1376,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -1421,7 +1421,6 @@ "version": "0.6.4", "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.6.4.tgz", "integrity": "sha512-u4eu6Q94q6KuZacZfdo4vCevA81F4QWeTYEXUvoksQMJpiacPHHe0DJrofKVKvxngUp5kCi1RnPXSc6kBY+/oA==", - "dev": true, "requires": { "apollo-server-caching": "^0.5.1", "apollo-server-env": "^2.4.3" @@ -1456,7 +1455,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/apollo-env/-/apollo-env-0.6.1.tgz", "integrity": "sha512-B9BgpQGR1ndeDtb4Gtor0J4CITQ+OPACZrVW6lgStnljKEe9ZB76DZ1dAd3OCeizAswW6Lo9uvfK8jhVS5nBhQ==", - "dev": true, "requires": { "@types/node-fetch": "2.5.4", "core-js": "^3.0.1", @@ -1489,7 +1487,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.1.tgz", "integrity": "sha512-L7LHZ3k9Ao5OSf2WStvQhxdsNVplRQi7kCAPfqf9Z3GBEnQ2uaL0EgO0hSmtVHfXTbk5CTRziMT1Pe87bXrFIw==", - "dev": true, "requires": { "lru-cache": "^5.0.0" } @@ -1742,7 +1739,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz", "integrity": "sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==", - "dev": true, "requires": { "retry": "0.12.0" } @@ -2353,7 +2349,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, "requires": { "color-name": "^1.1.1" } @@ -2361,8 +2356,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "combined-stream": { "version": "1.0.7", @@ -2468,8 +2462,7 @@ "core-js": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.2.tgz", - "integrity": "sha512-hIE5dXkRzRvnZ5vhkRfQxUvDxQZmD9oueA08jDYRBKJHx+VIl/Pne/e0A4x9LObEEthC/TqiZybUoNM4tRgnKg==", - "dev": true + "integrity": "sha512-hIE5dXkRzRvnZ5vhkRfQxUvDxQZmD9oueA08jDYRBKJHx+VIl/Pne/e0A4x9LObEEthC/TqiZybUoNM4tRgnKg==" }, "core-util-is": { "version": "1.0.2", @@ -3786,7 +3779,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", - "dev": true, "requires": { "dicer": "0.3.0" } @@ -3795,7 +3787,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", - "dev": true, "requires": { "streamsearch": "0.1.2" } @@ -6636,8 +6627,7 @@ "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", - "dev": true + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, "node-int64": { "version": "0.4.0", @@ -7117,6 +7107,7 @@ "version": "6.8.8", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -7137,7 +7128,7 @@ "version": "10.17.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.5.tgz", "integrity": "sha512-RElZIr/7JreF1eY6oD5RF3kpmdcreuQPjg5ri4oQ5g9sq7YWU8HkfB3eH8GwAwxf5OaCh0VPi7r4N/yoTGelrA==", - "dev": true + "optional": true } } }, diff --git a/package.json b/package.json index 0d36ff549..a96c79106 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "@nestjs/platform-express": "6.10.14", "@nestjs/platform-fastify": "6.10.14", "@nestjs/testing": "6.10.14", - "@types/graphql": "14.2.3", "@types/jest": "24.9.0", "@types/node": "12.12.21", "@types/node-fetch": "^2.5.2", @@ -40,7 +39,6 @@ "typescript": "3.7.5" }, "dependencies": { - "@types/graphql": "14.2.3", "chokidar": "3.3.1", "fast-glob": "3.1.1", "graphql-tools": "4.0.6",