From 6f785559d05d6bda4e52c887d721151d982670bf Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Sat, 9 Mar 2024 12:19:13 +0100 Subject: [PATCH 01/10] feat: forward signal to executor --- .../__tests__/request-cancellation.spec.ts | 82 +++++++++++++++++++ packages/graphql-yoga/src/process-request.ts | 5 +- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 packages/graphql-yoga/__tests__/request-cancellation.spec.ts diff --git a/packages/graphql-yoga/__tests__/request-cancellation.spec.ts b/packages/graphql-yoga/__tests__/request-cancellation.spec.ts new file mode 100644 index 0000000000..685e355b8c --- /dev/null +++ b/packages/graphql-yoga/__tests__/request-cancellation.spec.ts @@ -0,0 +1,82 @@ +import { createSchema, createYoga } from '../src/index'; + +describe('request cancellation', () => { + it('request cancellation stops invocation of subsequent resolvers', async () => { + const rootResolverGotInvokedD = createDeferred(); + const requestGotCancelledD = createDeferred(); + + let aResolverGotInvoked = false; + let rootResolverGotInvoked = false; + + const schema = createSchema({ + typeDefs: /* GraphQL */ ` + type Query { + root: A! + } + + type A { + a: String! + } + `, + resolvers: { + Query: { + async root() { + rootResolverGotInvoked = true; + rootResolverGotInvokedD.resolve(); + await requestGotCancelledD.promise; + + return { a: 'a' }; + }, + }, + A: { + a() { + aResolverGotInvoked = true; + return 'a'; + }, + }, + }, + }); + + const yoga = createYoga({ schema }); + + const abortController = new AbortController(); + const promise = Promise.resolve( + yoga.fetch('http://yoga/graphql', { + method: 'POST', + body: JSON.stringify({ query: '{ root { a } }' }), + headers: { + 'Content-Type': 'application/json', + }, + signal: abortController.signal, + }), + ); + + await rootResolverGotInvokedD.promise; + abortController.abort(); + + requestGotCancelledD.resolve(); + + // The request was aborted, so the promise should raise instead + const result = await promise.then(res => res.text()); + // why does this return a (invalid) GraphQL response? + expect(result).toMatchInlineSnapshot(`"{"data":{"root":{"a":null}}}"`); + + expect(rootResolverGotInvoked).toBe(true); + expect(aResolverGotInvoked).toBe(false); + }); +}); + +type Deferred = { + resolve: (value: T) => void; + reject: (value: unknown) => void; + promise: Promise; +}; + +function createDeferred(): Deferred { + const d = {} as Deferred; + d.promise = new Promise((resolve, reject) => { + d.resolve = resolve; + d.reject = reject; + }); + return d; +} diff --git a/packages/graphql-yoga/src/process-request.ts b/packages/graphql-yoga/src/process-request.ts index f846228292..597fbe8fb8 100644 --- a/packages/graphql-yoga/src/process-request.ts +++ b/packages/graphql-yoga/src/process-request.ts @@ -1,5 +1,6 @@ -import { ExecutionArgs, getOperationAST } from 'graphql'; +import { getOperationAST } from 'graphql'; import { GetEnvelopedFn } from '@envelop/core'; +import { ExecutionArgs } from '@graphql-tools/executor'; import { OnResultProcess, ResultProcessor, ResultProcessorInput } from './plugins/types.js'; import { FetchAPI, GraphQLParams } from './types.js'; @@ -71,13 +72,13 @@ export async function processRequest({ // Build the context for the execution const contextValue = await enveloped.contextFactory(); - const executionArgs: ExecutionArgs = { schema: enveloped.schema, document, contextValue, variableValues: params.variables, operationName: params.operationName, + signal: contextValue?.request?.signal ?? undefined, }; // Get the actual operation From f99abc636aecbcef28313fae8e71797da0fb0a25 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 9 Mar 2024 11:24:20 +0000 Subject: [PATCH 02/10] chore(dependencies): updated changesets for modified dependencies --- .changeset/graphql-yoga-3197-dependencies.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/graphql-yoga-3197-dependencies.md diff --git a/.changeset/graphql-yoga-3197-dependencies.md b/.changeset/graphql-yoga-3197-dependencies.md new file mode 100644 index 0000000000..6939e8c86d --- /dev/null +++ b/.changeset/graphql-yoga-3197-dependencies.md @@ -0,0 +1,7 @@ +--- +'graphql-yoga': patch +--- +dependencies updates: + - Updated dependency [`@graphql-tools/executor@^1.2.1` + ↗︎](https://www.npmjs.com/package/@graphql-tools/executor/v/1.2.1) (from `^1.0.0`, in + `dependencies`) From 493d5328e04206a7b8d1e4d301f18bed499482c0 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Mon, 11 Mar 2024 15:32:50 +0300 Subject: [PATCH 03/10] Update tests --- .../__tests__/request-cancellation.spec.ts | 5 +- packages/graphql-yoga/package.json | 2 +- pnpm-lock.yaml | 55 +++++++++++++------ 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/packages/graphql-yoga/__tests__/request-cancellation.spec.ts b/packages/graphql-yoga/__tests__/request-cancellation.spec.ts index 685e355b8c..e485c83b0a 100644 --- a/packages/graphql-yoga/__tests__/request-cancellation.spec.ts +++ b/packages/graphql-yoga/__tests__/request-cancellation.spec.ts @@ -56,10 +56,7 @@ describe('request cancellation', () => { requestGotCancelledD.resolve(); - // The request was aborted, so the promise should raise instead - const result = await promise.then(res => res.text()); - // why does this return a (invalid) GraphQL response? - expect(result).toMatchInlineSnapshot(`"{"data":{"root":{"a":null}}}"`); + await expect(promise).rejects.toThrow('Aborted'); expect(rootResolverGotInvoked).toBe(true); expect(aResolverGotInvoked).toBe(false); diff --git a/packages/graphql-yoga/package.json b/packages/graphql-yoga/package.json index ff145bc0b7..37c8d05798 100644 --- a/packages/graphql-yoga/package.json +++ b/packages/graphql-yoga/package.json @@ -56,7 +56,7 @@ "@graphql-yoga/logger": "^2.0.0", "@graphql-yoga/subscription": "^5.0.0", "@whatwg-node/fetch": "^0.9.7", - "@whatwg-node/server": "^0.9.1", + "@whatwg-node/server": "^0.9.27", "dset": "^3.1.1", "lru-cache": "^10.0.0", "tslib": "^2.5.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0927207d2..ed3658bb67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -389,7 +389,7 @@ importers: dependencies: '@whatwg-node/server-plugin-cookies': specifier: ^1.0.0 - version: 1.0.0(@whatwg-node/server@0.9.1) + version: 1.0.0(@whatwg-node/server@0.9.27) graphql: specifier: 16.6.0 version: 16.6.0 @@ -1602,8 +1602,8 @@ importers: specifier: ^0.9.7 version: 0.9.12 '@whatwg-node/server': - specifier: ^0.9.1 - version: 0.9.1 + specifier: ^0.9.27 + version: 0.9.27 dset: specifier: ^3.1.1 version: 3.1.2 @@ -7861,7 +7861,7 @@ packages: dependencies: '@ardatan/sync-fetch': 0.0.1 '@graphql-tools/utils': 10.1.1(graphql@16.6.0) - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 graphql: 16.6.0 tslib: 2.6.2 transitivePeerDependencies: @@ -7995,7 +7995,7 @@ packages: dependencies: '@graphql-tools/utils': 10.1.1(graphql@16.6.0) '@repeaterjs/repeater': 3.0.4 - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 extract-files: 11.0.0 graphql: 16.6.0 meros: 1.2.1(@types/node@18.16.16) @@ -8089,7 +8089,7 @@ packages: '@graphql-tools/executor-http': 1.0.4(@types/node@18.16.16)(graphql@16.6.0) '@graphql-tools/graphql-tag-pluck': 8.0.0(@babel/core@7.22.1)(graphql@16.6.0) '@graphql-tools/utils': 10.1.1(graphql@16.6.0) - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 graphql: 16.6.0 tslib: 2.6.2 value-or-promise: 1.0.12 @@ -8232,7 +8232,7 @@ packages: '@graphql-tools/utils': 10.1.1(graphql@16.6.0) '@types/js-yaml': 4.0.5 '@types/json-stable-stringify': 1.0.34 - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 chalk: 4.1.2 debug: 4.3.4(supports-color@9.2.3) dotenv: 16.3.1 @@ -8320,7 +8320,7 @@ packages: '@graphql-tools/utils': 10.1.1(graphql@16.6.0) '@graphql-tools/wrap': 10.0.0(graphql@16.6.0) '@types/ws': 8.5.4 - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 graphql: 16.6.0 isomorphic-ws: 5.0.0(ws@8.13.0) tslib: 2.6.2 @@ -9476,6 +9476,9 @@ packages: dependencies: lodash: 4.17.21 + /@kamilkisiela/fast-url-parser@1.1.4: + resolution: {integrity: sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==} + /@lezer/common@1.1.1: resolution: {integrity: sha512-aAPB9YbvZHqAW+bIwiuuTDGB4DG0sYNRObGLxud8cW7osw1ZQxfDuTZ8KQiqfZ0QJGcR34CvpTMDXEyo/+Htgg==} dev: false @@ -14670,6 +14673,13 @@ packages: '@whatwg-node/node-fetch': 0.4.18 urlpattern-polyfill: 9.0.0 + /@whatwg-node/fetch@0.9.17: + resolution: {integrity: sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==} + engines: {node: '>=16.0.0'} + dependencies: + '@whatwg-node/node-fetch': 0.5.8 + urlpattern-polyfill: 10.0.0 + /@whatwg-node/node-fetch@0.3.6: resolution: {integrity: sha512-w9wKgDO4C95qnXZRwZTfCmLWqyRnooGjcIwG0wADWjw9/HN0p7dtvtgSvItZtUyNteEvgTrd8QojNEqV6DAGTA==} dependencies: @@ -14701,14 +14711,24 @@ packages: fast-url-parser: 1.1.3 tslib: 2.6.2 - /@whatwg-node/server-plugin-cookies@1.0.0(@whatwg-node/server@0.9.1): + /@whatwg-node/node-fetch@0.5.8: + resolution: {integrity: sha512-rB+2P3oi9fD4TcsijkflJAQqOh4yZrPgOV4fGaDgCdOqqwTicJvL2nnVbr3comW8bxEuypOcyE1AtBtkpip0Gw==} + engines: {node: '>=16.0.0'} + dependencies: + '@kamilkisiela/fast-url-parser': 1.1.4 + '@whatwg-node/events': 0.1.0 + busboy: 1.6.0 + fast-querystring: 1.1.1 + tslib: 2.6.2 + + /@whatwg-node/server-plugin-cookies@1.0.0(@whatwg-node/server@0.9.27): resolution: {integrity: sha512-o0MqK6H4Yhxht+J+cgZIpEhdb4SID1grFWfOiMdni3csNHBKZ+MKAFhxS+6wAJsarHN7BEZ0QMu4PG09Uwhr2g==} engines: {node: '>=16.0.0'} peerDependencies: '@whatwg-node/server': ^0.9.0 dependencies: '@whatwg-node/cookie-store': 0.2.0 - '@whatwg-node/server': 0.9.1 + '@whatwg-node/server': 0.9.27 tslib: 2.5.3 dev: false @@ -14716,16 +14736,16 @@ packages: resolution: {integrity: sha512-5nzOE61W5VA43rE4MeUVHLDPpZGDfGiQ5LnEwJNbVehUI6Ua8QD566C8dhBmXxFuT8pXTlxWZHe3sjjGZCc4ng==} engines: {node: '>=16.0.0'} dependencies: - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 tslib: 2.6.2 dev: false - /@whatwg-node/server@0.9.1: - resolution: {integrity: sha512-Zq0FWafIlJzYyyAL7UwzsEhqSMzvKci/XqIFKpC4V031PxKKPBmZSP1mXCew5eZFW8Z5Sj6bZNi2CIsHd062ug==} + /@whatwg-node/server@0.9.27: + resolution: {integrity: sha512-6H+rbzWtr+PD4MLiEroDzunp+L0UcsPleXg0BjttuEgA9N5a8like3kvQaJsTKQd37IaUsdX5b/eUVbx4ZT/7A==} engines: {node: '>=16.0.0'} dependencies: - '@whatwg-node/fetch': 0.9.12 - tslib: 2.5.3 + '@whatwg-node/fetch': 0.9.17 + tslib: 2.6.2 dev: false /@wry/context@0.7.0: @@ -20682,7 +20702,7 @@ packages: dependencies: '@ardatan/fast-json-stringify': 0.0.6(ajv-formats@2.1.1)(ajv@8.12.0) '@whatwg-node/cookie-store': 0.1.0 - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 '@whatwg-node/server': 0.8.1 ajv: 8.12.0 ajv-formats: 2.1.1(ajv@8.12.0) @@ -33965,6 +33985,9 @@ packages: querystring: 0.2.0 dev: false + /urlpattern-polyfill@10.0.0: + resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==} + /urlpattern-polyfill@8.0.2: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} dev: true From 2798c40f5726adcd4065021dd99c72063aa8cbeb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 11 Mar 2024 12:38:44 +0000 Subject: [PATCH 04/10] chore(dependencies): updated changesets for modified dependencies --- .changeset/graphql-yoga-3197-dependencies.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.changeset/graphql-yoga-3197-dependencies.md b/.changeset/graphql-yoga-3197-dependencies.md index 6939e8c86d..0aae4d0270 100644 --- a/.changeset/graphql-yoga-3197-dependencies.md +++ b/.changeset/graphql-yoga-3197-dependencies.md @@ -5,3 +5,6 @@ dependencies updates: - Updated dependency [`@graphql-tools/executor@^1.2.1` ↗︎](https://www.npmjs.com/package/@graphql-tools/executor/v/1.2.1) (from `^1.0.0`, in `dependencies`) + - Updated dependency [`@whatwg-node/server@^0.9.27` + ↗︎](https://www.npmjs.com/package/@whatwg-node/server/v/0.9.27) (from `^0.9.1`, in + `dependencies`) From cf6fd5099d63293bd7a5aebe2dcfae88fdc3ec76 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Thu, 21 Mar 2024 11:25:05 +0100 Subject: [PATCH 05/10] use fixed executor version --- examples/apollo-federation/package.json | 2 +- examples/bun/package.json | 2 +- examples/cloudflare-modules/package.json | 2 +- examples/error-handling/package.json | 2 +- .../file-upload-nextjs-pothos/package.json | 2 +- examples/file-upload-nexus/package.json | 2 +- examples/file-upload/package.json | 2 +- examples/generic-auth/package.json | 2 +- examples/hapi/package.json | 2 +- examples/response-cache/package.json | 2 +- examples/service-worker/package.json | 2 +- examples/uwebsockets/package.json | 2 +- packages/graphql-yoga/package.json | 6 +- packages/graphql-yoga/src/error.ts | 5 + packages/nestjs/package.json | 2 +- .../plugins/apollo-inline-trace/package.json | 4 +- .../defer-stream.spec.ts | 90 ++++++----- packages/plugins/defer-stream/package.json | 2 +- pnpm-lock.yaml | 147 ++++++++++-------- 19 files changed, 155 insertions(+), 125 deletions(-) diff --git a/examples/apollo-federation/package.json b/examples/apollo-federation/package.json index 3de03eefd4..f35597af09 100644 --- a/examples/apollo-federation/package.json +++ b/examples/apollo-federation/package.json @@ -17,6 +17,6 @@ }, "devDependencies": { "@apollo/gateway": "2.4.7", - "@whatwg-node/fetch": "^0.9.0" + "@whatwg-node/fetch": "^0.9.17" } } diff --git a/examples/bun/package.json b/examples/bun/package.json index d67c6676e1..bf41a4b363 100644 --- a/examples/bun/package.json +++ b/examples/bun/package.json @@ -12,6 +12,6 @@ "graphql-yoga": "5.2.0" }, "devDependencies": { - "@whatwg-node/fetch": "^0.9.0" + "@whatwg-node/fetch": "^0.9.17" } } diff --git a/examples/cloudflare-modules/package.json b/examples/cloudflare-modules/package.json index 5662edf526..bd27e6a06b 100644 --- a/examples/cloudflare-modules/package.json +++ b/examples/cloudflare-modules/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@cloudflare/workers-types": "4.20230518.0", - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "typescript": "5.1.6", "wrangler": "3.1.0" } diff --git a/examples/error-handling/package.json b/examples/error-handling/package.json index b0b3e94740..a8aa40bc73 100644 --- a/examples/error-handling/package.json +++ b/examples/error-handling/package.json @@ -7,7 +7,7 @@ "start": "ts-node src/index.ts" }, "dependencies": { - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "graphql": "^16.1.0", "graphql-yoga": "5.2.0" }, diff --git a/examples/file-upload-nextjs-pothos/package.json b/examples/file-upload-nextjs-pothos/package.json index a2f377a3fd..912215eb9b 100644 --- a/examples/file-upload-nextjs-pothos/package.json +++ b/examples/file-upload-nextjs-pothos/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@types/react": "^18.0.17", - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "eslint": "8.42.0", "eslint-config-next": "13.4.12", "typescript": "5.1.6" diff --git a/examples/file-upload-nexus/package.json b/examples/file-upload-nexus/package.json index 8ae7ab7e66..e0f5792577 100644 --- a/examples/file-upload-nexus/package.json +++ b/examples/file-upload-nexus/package.json @@ -12,7 +12,7 @@ "nexus": "^1.3.0" }, "devDependencies": { - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "ts-node": "10.9.1", "typescript": "5.1.6" } diff --git a/examples/file-upload/package.json b/examples/file-upload/package.json index 185e2dea94..dea5b60918 100644 --- a/examples/file-upload/package.json +++ b/examples/file-upload/package.json @@ -12,6 +12,6 @@ "ts-node": "10.9.1" }, "devDependencies": { - "@whatwg-node/fetch": "^0.9.0" + "@whatwg-node/fetch": "^0.9.17" } } diff --git a/examples/generic-auth/package.json b/examples/generic-auth/package.json index f460f2b439..4ae47b8478 100644 --- a/examples/generic-auth/package.json +++ b/examples/generic-auth/package.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@types/node": "18.16.16", - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "cross-env": "7.0.3", "ts-node": "10.9.1", "ts-node-dev": "2.0.0", diff --git a/examples/hapi/package.json b/examples/hapi/package.json index 1c80ea41db..6a2529585a 100644 --- a/examples/hapi/package.json +++ b/examples/hapi/package.json @@ -12,7 +12,7 @@ "graphql-yoga": "5.2.0" }, "devDependencies": { - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "ts-node": "10.9.1", "typescript": "5.1.6" } diff --git a/examples/response-cache/package.json b/examples/response-cache/package.json index 404f91efd4..1df6135e9b 100644 --- a/examples/response-cache/package.json +++ b/examples/response-cache/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@types/node": "18.16.16", - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "cross-env": "7.0.3", "ts-node": "10.9.1", "ts-node-dev": "2.0.0", diff --git a/examples/service-worker/package.json b/examples/service-worker/package.json index 6f69f0201a..50b946b1ec 100644 --- a/examples/service-worker/package.json +++ b/examples/service-worker/package.json @@ -13,7 +13,7 @@ "graphql-yoga": "5.2.0" }, "devDependencies": { - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "typescript": "5.1.6", "wrangler": "3.1.0" } diff --git a/examples/uwebsockets/package.json b/examples/uwebsockets/package.json index 08b452b2c4..b7a5b41470 100644 --- a/examples/uwebsockets/package.json +++ b/examples/uwebsockets/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@types/ws": "8.5.4", - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "ws": "8.13.0" } } diff --git a/packages/graphql-yoga/package.json b/packages/graphql-yoga/package.json index 37c8d05798..b125634191 100644 --- a/packages/graphql-yoga/package.json +++ b/packages/graphql-yoga/package.json @@ -50,13 +50,13 @@ }, "dependencies": { "@envelop/core": "^5.0.0", - "@graphql-tools/executor": "^1.2.2", + "@graphql-tools/executor": "^1.2.3", "@graphql-tools/schema": "^10.0.0", "@graphql-tools/utils": "^10.1.0", "@graphql-yoga/logger": "^2.0.0", "@graphql-yoga/subscription": "^5.0.0", - "@whatwg-node/fetch": "^0.9.7", - "@whatwg-node/server": "^0.9.27", + "@whatwg-node/fetch": "^0.9.17", + "@whatwg-node/server": "^0.9.30", "dset": "^3.1.1", "lru-cache": "^10.0.0", "tslib": "^2.5.2" diff --git a/packages/graphql-yoga/src/error.ts b/packages/graphql-yoga/src/error.ts index d821e4c1a0..e090473b08 100644 --- a/packages/graphql-yoga/src/error.ts +++ b/packages/graphql-yoga/src/error.ts @@ -50,6 +50,11 @@ export function handleError( errors.add(handledError); } } + } else if ( + error?.constructor?.name === 'DOMException' && + (error as Record).name === 'AbortError' + ) { + logger.debug('Request aborted'); } else if (maskedErrorsOpts) { const maskedError = maskedErrorsOpts.maskError( error, diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 1ca372c9f7..fb2fd248e7 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -62,7 +62,7 @@ "@types/express": "^4.17.17", "@types/glob": "^8.0.1", "@types/ws": "^8.5.4", - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "express": "^4.18.2", "fastify": "^4.13.0", "glob": "^10.0.0", diff --git a/packages/plugins/apollo-inline-trace/package.json b/packages/plugins/apollo-inline-trace/package.json index 3eca455f58..622c1b70ff 100644 --- a/packages/plugins/apollo-inline-trace/package.json +++ b/packages/plugins/apollo-inline-trace/package.json @@ -38,7 +38,7 @@ }, "peerDependencies": { "@graphql-tools/utils": "^10.0.0", - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "graphql": "^15.2.0 || ^16.0.0", "graphql-yoga": "^5.2.0" }, @@ -49,7 +49,7 @@ }, "devDependencies": { "@envelop/on-resolve": "^4.0.0", - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "graphql": "^16.6.0", "graphql-yoga": "5.2.0" }, diff --git a/packages/plugins/defer-stream/__integration-tests__/defer-stream.spec.ts b/packages/plugins/defer-stream/__integration-tests__/defer-stream.spec.ts index 7577d026ae..b3736a9e6b 100644 --- a/packages/plugins/defer-stream/__integration-tests__/defer-stream.spec.ts +++ b/packages/plugins/defer-stream/__integration-tests__/defer-stream.spec.ts @@ -1,8 +1,7 @@ import { createServer, get, IncomingMessage } from 'node:http'; import { AddressInfo } from 'node:net'; -import { createSchema, createYoga } from 'graphql-yoga'; +import { createLogger, createSchema, createYoga } from 'graphql-yoga'; import { useDeferStream } from '@graphql-yoga/plugin-defer-stream'; -import { fetch } from '@whatwg-node/fetch'; import { createPushPullAsyncIterable } from '../__tests__/push-pull-async-iterable.js'; it('correctly deals with the source upon aborted requests', async () => { @@ -87,33 +86,48 @@ it('correctly deals with the source upon aborted requests', async () => { } }); +type Deferred = { + resolve: (value: T) => void; + reject: (value: unknown) => void; + promise: Promise; +}; + +function createDeferred(): Deferred { + const d = {} as Deferred; + d.promise = new Promise((resolve, reject) => { + d.resolve = resolve; + d.reject = reject; + }); + return d; +} + it('memory/cleanup leak by source that never publishes a value', async () => { let sourceGotCleanedUp = false; - let i = 1; - let interval: NodeJS.Timer; const controller = new AbortController(); + const d = createDeferred(); + + const noop = d.promise.then(() => ({ done: true, value: undefined })); - const noop = new Promise<{ done: true; value: undefined }>(() => undefined); const source = { [Symbol.asyncIterator]() { return this; }, next() { - interval = setInterval(() => { - i++; - if (i === 3) { - controller.abort(); - } + setTimeout(() => { + controller.abort(); }, 10); return noop; }, return() { - clearInterval(interval); sourceGotCleanedUp = true; return Promise.resolve({ done: true, value: undefined }); }, }; + const logger = createLogger('silent'); + const debugLogger = jest.fn(); + + logger.debug = debugLogger; const yoga = createYoga({ schema: createSchema({ typeDefs: /* GraphQL */ ` @@ -128,9 +142,11 @@ it('memory/cleanup leak by source that never publishes a value', async () => { }, }), plugins: [useDeferStream()], + logging: logger, }); const server = createServer(yoga); + try { await new Promise(resolve => { server.listen(() => { @@ -144,46 +160,46 @@ it('memory/cleanup leak by source that never publishes a value', async () => { } const response = await new Promise(resolve => { - const request = get( + get( `http://localhost:${port}/graphql?query={hi @stream}`, { headers: { accept: 'multipart/mixed', }, + signal: controller.signal, }, - res => resolve(res), - ); - controller.signal.addEventListener( - 'abort', - () => { - request.destroy(); - }, - { once: true }, + response => resolve(response), ); }); - try { - for await (const chunk of response) { - const chunkStr = Buffer.from(chunk).toString('utf-8'); - expect(chunkStr).toMatchInlineSnapshot(` - "--- - Content-Type: application/json; charset=utf-8 - Content-Length: 33 - - {"data":{"hi":[]},"hasNext":true} - ---" - `); - } - } catch (err: unknown) { - expect((err as Error).message).toContain('aborted'); - } + const iterator = response![Symbol.asyncIterator](); + + const next = await iterator.next(); + + const chunkStr = Buffer.from(next.value).toString('utf-8'); + expect(chunkStr).toMatchInlineSnapshot(` + "--- + Content-Type: application/json; charset=utf-8 + Content-Length: 33 + + {"data":{"hi":[]},"hasNext":true} + ---" + `); + + await expect(iterator.next()).rejects.toMatchInlineSnapshot(`[Error: aborted]`); // Wait a bit - just to make sure the time is cleaned up for sure... await new Promise(res => setTimeout(res, 50)); - - expect(i).toEqual(3); expect(sourceGotCleanedUp).toBe(true); + + expect(debugLogger.mock.calls).toEqual([ + ['Parsing request to extract GraphQL parameters'], + ['Processing GraphQL Parameters'], + ['Processing GraphQL Parameters done.'], + ['Request aborted'], + ]); } finally { + d.resolve(); await new Promise(res => { server.close(() => { res(); diff --git a/packages/plugins/defer-stream/package.json b/packages/plugins/defer-stream/package.json index 4b29ec61eb..af4bba852f 100644 --- a/packages/plugins/defer-stream/package.json +++ b/packages/plugins/defer-stream/package.json @@ -45,7 +45,7 @@ }, "devDependencies": { "@graphql-tools/executor-http": "^1.0.4", - "@whatwg-node/fetch": "^0.9.7", + "@whatwg-node/fetch": "^0.9.17", "graphql": "^16.6.0", "tslib": "^2.5.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed3658bb67..b1631b789e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -190,8 +190,8 @@ importers: specifier: 2.4.7 version: 2.4.7(graphql@16.6.0) '@whatwg-node/fetch': - specifier: ^0.9.0 - version: 0.9.0 + specifier: ^0.9.17 + version: 0.9.17 examples/apollo-federation-compatibility: dependencies: @@ -335,8 +335,8 @@ importers: version: link:../../packages/graphql-yoga/dist devDependencies: '@whatwg-node/fetch': - specifier: ^0.9.0 - version: 0.9.0 + specifier: ^0.9.17 + version: 0.9.17 examples/cloudflare-advanced: dependencies: @@ -376,8 +376,8 @@ importers: specifier: 4.20230518.0 version: 4.20230518.0 '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 typescript: specifier: 5.1.6 version: 5.1.6 @@ -389,7 +389,7 @@ importers: dependencies: '@whatwg-node/server-plugin-cookies': specifier: ^1.0.0 - version: 1.0.0(@whatwg-node/server@0.9.27) + version: 1.0.0(@whatwg-node/server@0.9.30) graphql: specifier: 16.6.0 version: 16.6.0 @@ -437,8 +437,8 @@ importers: examples/error-handling: dependencies: '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 graphql: specifier: 16.6.0 version: 16.6.0 @@ -554,8 +554,8 @@ importers: version: 10.9.1(@types/node@18.16.16)(typescript@5.1.6) devDependencies: '@whatwg-node/fetch': - specifier: ^0.9.0 - version: 0.9.0 + specifier: ^0.9.17 + version: 0.9.17 examples/file-upload-nextjs-pothos: dependencies: @@ -582,8 +582,8 @@ importers: specifier: 18.2.8 version: 18.2.8 '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 eslint: specifier: 8.42.0 version: 8.42.0 @@ -607,8 +607,8 @@ importers: version: 1.3.0(graphql@16.6.0) devDependencies: '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 ts-node: specifier: 10.9.1 version: 10.9.1(@types/node@18.16.16)(typescript@5.1.6) @@ -688,8 +688,8 @@ importers: specifier: 18.16.16 version: 18.16.16 '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 cross-env: specifier: 7.0.3 version: 7.0.3 @@ -825,8 +825,8 @@ importers: version: link:../../packages/graphql-yoga/dist devDependencies: '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 ts-node: specifier: 10.9.1 version: 10.9.1(@types/node@18.16.16)(typescript@5.1.6) @@ -1275,8 +1275,8 @@ importers: specifier: 18.16.16 version: 18.16.16 '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 cross-env: specifier: 7.0.3 version: 7.0.3 @@ -1300,8 +1300,8 @@ importers: version: link:../../packages/graphql-yoga/dist devDependencies: '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 typescript: specifier: 5.1.6 version: 5.1.6 @@ -1450,8 +1450,8 @@ importers: specifier: 8.5.4 version: 8.5.4 '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 ws: specifier: 8.13.0 version: 8.13.0 @@ -1545,7 +1545,7 @@ importers: version: 0.8.4(graphql@16.6.0) '@graphql-tools/url-loader': specifier: 8.0.1 - version: 8.0.1(@types/node@18.16.16)(graphql@16.6.0) + version: 8.0.1(graphql@16.6.0) graphiql: specifier: 2.0.7 version: 2.0.7(@codemirror/language@6.0.0)(@types/react@18.2.8)(graphql@16.6.0)(react-dom@18.2.0)(react-is@17.0.2)(react@18.2.0) @@ -1584,8 +1584,8 @@ importers: specifier: 4.0.0 version: 4.0.0 '@graphql-tools/executor': - specifier: ^1.2.2 - version: 1.2.2(graphql@16.6.0) + specifier: ^1.2.3 + version: 1.2.3(graphql@16.6.0) '@graphql-tools/schema': specifier: ^10.0.0 version: 10.0.0(graphql@16.6.0) @@ -1599,11 +1599,11 @@ importers: specifier: ^5.0.0 version: link:../subscription/dist '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 '@whatwg-node/server': - specifier: ^0.9.27 - version: 0.9.27 + specifier: ^0.9.30 + version: 0.9.30 dset: specifier: ^3.1.1 version: 3.1.2 @@ -1701,8 +1701,8 @@ importers: specifier: ^8.5.4 version: 8.5.4 '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 express: specifier: ^4.18.2 version: 4.18.2 @@ -1796,8 +1796,8 @@ importers: version: 2.5.2 devDependencies: '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 graphql: specifier: 16.6.0 version: 16.6.0 @@ -1846,8 +1846,8 @@ importers: specifier: ^1.0.4 version: 1.0.4(@types/node@18.16.16)(graphql@16.6.0) '@whatwg-node/fetch': - specifier: ^0.9.7 - version: 0.9.12 + specifier: ^0.9.17 + version: 0.9.17 graphql: specifier: 16.6.0 version: 16.6.0 @@ -6735,7 +6735,7 @@ packages: dependencies: '@envelop/core': 5.0.0 '@graphql-tools/utils': 10.1.0(graphql@16.6.0) - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 fast-json-stable-stringify: 2.1.0 graphql: 16.6.0 lru-cache: 10.0.0 @@ -7916,7 +7916,7 @@ packages: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 dependencies: '@graphql-tools/batch-execute': 9.0.0(graphql@16.6.0) - '@graphql-tools/executor': 1.2.2(graphql@16.6.0) + '@graphql-tools/executor': 1.2.3(graphql@16.6.0) '@graphql-tools/schema': 10.0.0(graphql@16.6.0) '@graphql-tools/utils': 10.1.1(graphql@16.6.0) dataloader: 2.2.2 @@ -7978,7 +7978,7 @@ packages: dependencies: '@graphql-tools/utils': 10.0.6(graphql@16.6.0) '@repeaterjs/repeater': 3.0.4 - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 extract-files: 11.0.0 graphql: 16.6.0 meros: 1.2.1(@types/node@18.16.16) @@ -8048,8 +8048,8 @@ packages: value-or-promise: 1.0.12 dev: false - /@graphql-tools/executor@1.2.2(graphql@16.6.0): - resolution: {integrity: sha512-wZkyjndwlzi01HTU3PDveoucKA8qVO0hdKmJhjIGK/vRN/A4w5rDdeqRGcyXVss0clCAy3R6jpixCVu5pWs2Qg==} + /@graphql-tools/executor@1.2.3(graphql@16.6.0): + resolution: {integrity: sha512-aAS+TGjSq8BJuDq1RV/A/8E53Iu3KvaWpD8DPio0Qe/0YF26tdpK6EcmNSGrrjiZOwVIZ80wclZrFstHzCPm8A==} engines: {node: '>=16.0.0'} peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 @@ -8334,6 +8334,33 @@ packages: dev: true /@graphql-tools/url-loader@8.0.1(@types/node@18.16.16)(graphql@16.6.0): + resolution: {integrity: sha512-B2k8KQEkEQmfV1zhurT5GLoXo8jbXP+YQHUayhCSxKYlRV7j/1Fhp1b21PDM8LXIDGlDRXaZ0FbWKOs7eYXDuQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + dependencies: + '@ardatan/sync-fetch': 0.0.1 + '@graphql-tools/delegate': 10.0.0(graphql@16.6.0) + '@graphql-tools/executor-graphql-ws': 1.0.0(graphql@16.6.0) + '@graphql-tools/executor-http': 1.0.5(@types/node@18.16.16)(graphql@16.6.0) + '@graphql-tools/executor-legacy-ws': 1.0.0(graphql@16.6.0) + '@graphql-tools/utils': 10.1.1(graphql@16.6.0) + '@graphql-tools/wrap': 10.0.0(graphql@16.6.0) + '@types/ws': 8.5.4 + '@whatwg-node/fetch': 0.9.17 + graphql: 16.6.0 + isomorphic-ws: 5.0.0(ws@8.13.0) + tslib: 2.6.2 + value-or-promise: 1.0.12 + ws: 8.13.0 + transitivePeerDependencies: + - '@types/node' + - bufferutil + - encoding + - utf-8-validate + dev: true + + /@graphql-tools/url-loader@8.0.1(graphql@16.6.0): resolution: {integrity: sha512-B2k8KQEkEQmfV1zhurT5GLoXo8jbXP+YQHUayhCSxKYlRV7j/1Fhp1b21PDM8LXIDGlDRXaZ0FbWKOs7eYXDuQ==} engines: {node: '>=16.0.0'} peerDependencies: @@ -8347,7 +8374,7 @@ packages: '@graphql-tools/utils': 10.0.1(graphql@16.6.0) '@graphql-tools/wrap': 10.0.0(graphql@16.6.0) '@types/ws': 8.5.4 - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 graphql: 16.6.0 isomorphic-ws: 5.0.0(ws@8.13.0) tslib: 2.6.2 @@ -8358,6 +8385,7 @@ packages: - bufferutil - encoding - utf-8-validate + dev: false /@graphql-tools/utils@10.0.0(graphql@16.6.0): resolution: {integrity: sha512-ndBPc6zgR+eGU/jHLpuojrs61kYN3Z89JyMLwK3GCRkPv4EQn9EOr1UWqF1JO0iM+/jAVHY0mvfUxyrFFN9DUQ==} @@ -14666,13 +14694,6 @@ packages: urlpattern-polyfill: 8.0.2 dev: true - /@whatwg-node/fetch@0.9.12: - resolution: {integrity: sha512-zNUkPJNfM1v9Jhy3Vmi2a7lQxaNIDTSiAb1NKO5eMsSdo05XoddBEj/CHj1xu4IOMU68VerDvuBVwzPjxBl12g==} - engines: {node: '>=16.0.0'} - dependencies: - '@whatwg-node/node-fetch': 0.4.18 - urlpattern-polyfill: 9.0.0 - /@whatwg-node/fetch@0.9.17: resolution: {integrity: sha512-TDYP3CpCrxwxpiNY0UMNf096H5Ihf67BK1iKGegQl5u9SlpEDYrvnV71gWBGJm+Xm31qOy8ATgma9rm8Pe7/5Q==} engines: {node: '>=16.0.0'} @@ -14701,16 +14722,6 @@ packages: tslib: 2.6.2 dev: true - /@whatwg-node/node-fetch@0.4.18: - resolution: {integrity: sha512-zdey6buMKCqDVDq+tMqcjopO75Fb6iLqWo+g6cWwN5kiwctEHtVcbws2lJUFhCbo+TLZeH6bMDRUXEo5bkPtcQ==} - engines: {node: '>=16.0.0'} - dependencies: - '@whatwg-node/events': 0.1.0 - busboy: 1.6.0 - fast-querystring: 1.1.1 - fast-url-parser: 1.1.3 - tslib: 2.6.2 - /@whatwg-node/node-fetch@0.5.8: resolution: {integrity: sha512-rB+2P3oi9fD4TcsijkflJAQqOh4yZrPgOV4fGaDgCdOqqwTicJvL2nnVbr3comW8bxEuypOcyE1AtBtkpip0Gw==} engines: {node: '>=16.0.0'} @@ -14721,14 +14732,14 @@ packages: fast-querystring: 1.1.1 tslib: 2.6.2 - /@whatwg-node/server-plugin-cookies@1.0.0(@whatwg-node/server@0.9.27): + /@whatwg-node/server-plugin-cookies@1.0.0(@whatwg-node/server@0.9.30): resolution: {integrity: sha512-o0MqK6H4Yhxht+J+cgZIpEhdb4SID1grFWfOiMdni3csNHBKZ+MKAFhxS+6wAJsarHN7BEZ0QMu4PG09Uwhr2g==} engines: {node: '>=16.0.0'} peerDependencies: '@whatwg-node/server': ^0.9.0 dependencies: '@whatwg-node/cookie-store': 0.2.0 - '@whatwg-node/server': 0.9.27 + '@whatwg-node/server': 0.9.30 tslib: 2.5.3 dev: false @@ -14740,8 +14751,8 @@ packages: tslib: 2.6.2 dev: false - /@whatwg-node/server@0.9.27: - resolution: {integrity: sha512-6H+rbzWtr+PD4MLiEroDzunp+L0UcsPleXg0BjttuEgA9N5a8like3kvQaJsTKQd37IaUsdX5b/eUVbx4ZT/7A==} + /@whatwg-node/server@0.9.30: + resolution: {integrity: sha512-SeL1vL0/uF2xBkpy/dW4sA3aS4IBQMPk097QQ15vW21EhO0ow3RqQu2FCRLCnU1Xgiu8SCpvt3iQUBx74o4ZjQ==} engines: {node: '>=16.0.0'} dependencies: '@whatwg-node/fetch': 0.9.17 @@ -20505,6 +20516,7 @@ packages: resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} dependencies: punycode: 1.4.1 + dev: true /fast-write-atomic@0.2.1: resolution: {integrity: sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==} @@ -31522,7 +31534,7 @@ packages: graphql: ^15.0.0 || ^16.0.0 dependencies: '@graphql-tools/utils': 10.1.0(graphql@16.6.0) - '@whatwg-node/fetch': 0.9.12 + '@whatwg-node/fetch': 0.9.17 ansi-colors: 4.1.3 fets: 0.2.0 graphql: 16.6.0 @@ -33992,9 +34004,6 @@ packages: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} dev: true - /urlpattern-polyfill@9.0.0: - resolution: {integrity: sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==} - /use-callback-ref@1.3.0(@types/react@18.2.8)(react@18.2.0): resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} engines: {node: '>=10'} From 685e85fcabc4883650368ab933df5c64394f769f Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Thu, 21 Mar 2024 12:06:04 +0100 Subject: [PATCH 06/10] correct message --- .../__integration-tests__/nextjs-legacy.spec.ts | 13 +++---------- .../__tests__/request-cancellation.spec.ts | 13 ++----------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/examples/nextjs-legacy-pages/__integration-tests__/nextjs-legacy.spec.ts b/examples/nextjs-legacy-pages/__integration-tests__/nextjs-legacy.spec.ts index 5f111202dd..5196d4336e 100644 --- a/examples/nextjs-legacy-pages/__integration-tests__/nextjs-legacy.spec.ts +++ b/examples/nextjs-legacy-pages/__integration-tests__/nextjs-legacy.spec.ts @@ -35,16 +35,9 @@ describe('NextJS Legacy Pages', () => { ...Object.fromEntries(response.headers.entries()), date: null, 'keep-alive': null, - }).toMatchInlineSnapshot(` - { - "connection": "close", - "content-length": "79", - "content-type": "application/json; charset=utf-8", - "date": null, - "keep-alive": null, - "vary": "Accept-Encoding", - } - `); + }).toMatchObject({ + 'content-type': 'application/json; charset=utf-8', + }); const json = await response.json(); diff --git a/packages/graphql-yoga/__tests__/request-cancellation.spec.ts b/packages/graphql-yoga/__tests__/request-cancellation.spec.ts index e485c83b0a..56cf812cd1 100644 --- a/packages/graphql-yoga/__tests__/request-cancellation.spec.ts +++ b/packages/graphql-yoga/__tests__/request-cancellation.spec.ts @@ -4,16 +4,13 @@ describe('request cancellation', () => { it('request cancellation stops invocation of subsequent resolvers', async () => { const rootResolverGotInvokedD = createDeferred(); const requestGotCancelledD = createDeferred(); - let aResolverGotInvoked = false; let rootResolverGotInvoked = false; - const schema = createSchema({ typeDefs: /* GraphQL */ ` type Query { root: A! } - type A { a: String! } @@ -24,7 +21,6 @@ describe('request cancellation', () => { rootResolverGotInvoked = true; rootResolverGotInvokedD.resolve(); await requestGotCancelledD.promise; - return { a: 'a' }; }, }, @@ -36,9 +32,7 @@ describe('request cancellation', () => { }, }, }); - const yoga = createYoga({ schema }); - const abortController = new AbortController(); const promise = Promise.resolve( yoga.fetch('http://yoga/graphql', { @@ -50,16 +44,13 @@ describe('request cancellation', () => { signal: abortController.signal, }), ); - await rootResolverGotInvokedD.promise; abortController.abort(); - requestGotCancelledD.resolve(); - - await expect(promise).rejects.toThrow('Aborted'); - + await expect(promise).rejects.toThrow('This operation was aborted'); expect(rootResolverGotInvoked).toBe(true); expect(aResolverGotInvoked).toBe(false); + await requestGotCancelledD.promise; }); }); From 63ca8ef34ce6d114f1d1c3ff70e3d58b07936350 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Thu, 21 Mar 2024 14:22:19 +0100 Subject: [PATCH 07/10] test: DOMException thrown from resolver --- .../__tests__/error-masking.spec.ts | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/packages/graphql-yoga/__tests__/error-masking.spec.ts b/packages/graphql-yoga/__tests__/error-masking.spec.ts index da0ee877f6..1c6f785a2f 100644 --- a/packages/graphql-yoga/__tests__/error-masking.spec.ts +++ b/packages/graphql-yoga/__tests__/error-masking.spec.ts @@ -1,5 +1,5 @@ import { inspect } from '@graphql-tools/utils'; -import { createGraphQLError, createSchema, createYoga } from '../src/index.js'; +import { createGraphQLError, createLogger, createSchema, createYoga } from '../src/index.js'; import { eventStream } from './utilities.js'; describe('error masking', () => { @@ -693,4 +693,67 @@ describe('error masking', () => { expect(counter).toBe(3); }); + + it('AbortSignal cancelation within resolver is not treated as a execution request cancelation by the yoga error handler', async () => { + const schema = createSchema({ + typeDefs: /* GraphQL */ ` + type Query { + root: A! + } + type A { + a: String! + } + `, + resolvers: { + Query: { + async root() { + /** we just gonna throw a DOMException here to see what happens */ + const abortController = new AbortController(); + abortController.abort(); + expect(abortController.signal.reason?.constructor.name).toBe('DOMException'); + throw abortController.signal.reason; + }, + }, + }, + }); + + const logger = createLogger('silent'); + const error = jest.fn(); + const debug = jest.fn(); + logger.debug = debug; + logger.error = error; + const yoga = createYoga({ schema, logging: logger }); + + const result = await yoga.fetch('http://yoga/graphql', { + method: 'POST', + body: JSON.stringify({ query: '{ root { a } }' }), + headers: { + 'Content-Type': 'application/json', + }, + }); + + expect(result.status).toEqual(200); + expect(await result.json()).toEqual({ + data: null, + errors: [ + { + locations: [ + { + column: 3, + line: 1, + }, + ], + message: 'Unexpected error.', + path: ['root'], + }, + ], + }); + // in the future this might change as we decide to within our graphql-tools/executor error handler treat DOMException similar to a normal Error + expect(error.mock.calls).toMatchObject([[{ message: 'Unexpected error value: {}' }]]); + expect(debug.mock.calls).toEqual([ + ['Parsing request to extract GraphQL parameters'], + ['Processing GraphQL Parameters'], + ['Processing GraphQL Parameters done.'], + ]); + }); }); From f8cec3219cdac55a376ca0357042e72a36d194b3 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Thu, 21 Mar 2024 14:29:24 +0100 Subject: [PATCH 08/10] add changeset --- .changeset/green-badgers-work.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/green-badgers-work.md diff --git a/.changeset/green-badgers-work.md b/.changeset/green-badgers-work.md new file mode 100644 index 0000000000..aeafd08490 --- /dev/null +++ b/.changeset/green-badgers-work.md @@ -0,0 +1,8 @@ +--- +'graphql-yoga': minor +--- + +Abort GraphQL execution when HTTP request is canceled. + +The execution of subsequent GraphQL resolvers is now aborted if the incoming HTTP request is canceled from the client side. +This reduces the load of your API in case incoming requests with deep GraphQL operation selection sets are canceled. From 0b192a76e5b32ec24224cc9c1fce27c8a46add6f Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Fri, 22 Mar 2024 14:13:36 +0100 Subject: [PATCH 09/10] wip: sse single connection mode --- .../redis-event-target/src/index.ts | 7 +- .../typed-event-target/src/index.ts | 5 +- .../__tests__/sse-single-connection.spec.ts | 318 ++++++++++++++++++ packages/graphql-yoga/src/plugins/types.ts | 12 +- .../src/plugins/use-sse-single-connection.ts | 183 ++++++++++ packages/graphql-yoga/src/process-request.ts | 16 +- packages/graphql-yoga/src/server.ts | 6 +- 7 files changed, 535 insertions(+), 12 deletions(-) create mode 100644 packages/graphql-yoga/__tests__/sse-single-connection.spec.ts create mode 100644 packages/graphql-yoga/src/plugins/use-sse-single-connection.ts diff --git a/packages/event-target/redis-event-target/src/index.ts b/packages/event-target/redis-event-target/src/index.ts index 7680bb5f5d..1661a1d994 100644 --- a/packages/event-target/redis-event-target/src/index.ts +++ b/packages/event-target/redis-event-target/src/index.ts @@ -41,10 +41,11 @@ export function createRedisEventTarget( if (callbacks === undefined) { callbacks = new Set(); callbacksForTopic.set(topic, callbacks); - - subscribeClient.subscribe(topic); + callbacks.add(callback); + return subscribeClient.subscribe(topic).then(() => undefined); } callbacks.add(callback); + return; } function removeCallback(topic: string, callback: (event: TEvent) => void) { @@ -65,7 +66,7 @@ export function createRedisEventTarget( if (callbackOrOptions != null) { const callback = 'handleEvent' in callbackOrOptions ? callbackOrOptions.handleEvent : callbackOrOptions; - addCallback(topic, callback); + return addCallback(topic, callback); } }, dispatchEvent(event: TEvent) { diff --git a/packages/event-target/typed-event-target/src/index.ts b/packages/event-target/typed-event-target/src/index.ts index 865b34fbe9..58125c095a 100644 --- a/packages/event-target/typed-event-target/src/index.ts +++ b/packages/event-target/typed-event-target/src/index.ts @@ -11,11 +11,14 @@ export type TypedEventListenerOrEventListenerObject | TypedEventListenerObject; export interface TypedEventTarget extends EventTarget { + /** + * If the return value is a promise, the promise will resolve once the event listener has been set up. + */ addEventListener( type: string, callback: TypedEventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean, - ): void; + ): void | Promise; dispatchEvent(event: TEvent): boolean; removeEventListener( type: string, diff --git a/packages/graphql-yoga/__tests__/sse-single-connection.spec.ts b/packages/graphql-yoga/__tests__/sse-single-connection.spec.ts new file mode 100644 index 0000000000..73471e5393 --- /dev/null +++ b/packages/graphql-yoga/__tests__/sse-single-connection.spec.ts @@ -0,0 +1,318 @@ +import { createSchema, createYoga } from 'graphql-yoga'; +import { useSSESingleConnection } from '../src/plugins/use-sse-single-connection'; + +describe('SSE Single Connection plugin', () => { + it('reservation conflict', async () => { + const abortController = new AbortController(); + const yoga = createYoga({ + plugins: [useSSESingleConnection()], + schema: createSchema({ + typeDefs: /* GraphQL */ ` + type Query { + hello: String + } + + type Subscription { + hello: String + } + `, + resolvers: { + Subscription: { + hello: { + async *subscribe() { + yield { hello: 'Hello' }; + }, + }, + }, + }, + }), + }); + + const url = new URL('http://yoga/graphql'); + const eventStreamRequest = await yoga.fetch(url, { + method: 'GET', + headers: { + 'x-graphql-event-stream-token': 'my-token', + accept: 'text/event-stream', + }, + signal: abortController.signal, + }); + + const iterator = eventStreamRequest.body![Symbol.asyncIterator](); + const next = await iterator.next(); + expect(Buffer.from(next.value).toString('utf-8')).toMatchInlineSnapshot(` +"event: ready + +" +`); + + const conflictRequest = await yoga.fetch(url, { + method: 'GET', + headers: { + 'x-graphql-event-stream-token': 'my-token', + accept: 'text/event-stream', + }, + }); + + expect(conflictRequest.status).toEqual(409); + expect(await conflictRequest.text()).toMatchInlineSnapshot(`""`); + abortController.abort(); + await expect(iterator.next()).rejects.toThrow('aborted'); + }); + + it('execute query', async () => { + const abortController = new AbortController(); + const yoga = createYoga({ + plugins: [useSSESingleConnection()], + schema: createSchema({ + typeDefs: /* GraphQL */ ` + type Query { + hello: String + } + `, + }), + }); + + const url = new URL('http://yoga/graphql'); + const eventStreamRequest = await yoga.fetch(url, { + method: 'GET', + headers: { + 'x-graphql-event-stream-token': 'my-token', + accept: 'text/event-stream', + }, + signal: abortController.signal, + }); + + const iterator = eventStreamRequest.body![Symbol.asyncIterator](); + let next = await iterator.next(); + expect(Buffer.from(next.value).toString('utf-8')).toMatchInlineSnapshot(` +"event: ready + +" +`); + + const executeRequest = await yoga.fetch(url, { + method: 'POST', + body: JSON.stringify({ + query: `query { hello }`, + extensions: { + operationId: 'abc', + }, + }), + headers: { + 'x-graphql-event-stream-token': 'my-token', + 'content-type': 'application/json', + }, + }); + + expect(executeRequest.status).toEqual(202); + expect(await executeRequest.text()).toMatchInlineSnapshot(`""`); + next = await iterator.next(); + expect(Buffer.from(next.value).toString('utf-8')).toMatchInlineSnapshot(` +"event: next +id: abc +data: {"id":"abc","payload":{"data":{"hello":null}}} + +" +`); + abortController.abort(); + }); + + it('execute mutation', async () => { + const abortController = new AbortController(); + const yoga = createYoga({ + plugins: [useSSESingleConnection()], + schema: createSchema({ + typeDefs: /* GraphQL */ ` + type Query { + hello: String + } + type Mutation { + hello: String + } + `, + }), + }); + + const url = new URL('http://yoga/graphql'); + const eventStreamRequest = await yoga.fetch(url, { + method: 'GET', + headers: { + 'x-graphql-event-stream-token': 'my-token', + accept: 'text/event-stream', + }, + signal: abortController.signal, + }); + + const iterator = eventStreamRequest.body![Symbol.asyncIterator](); + let next = await iterator.next(); + expect(Buffer.from(next.value).toString('utf-8')).toMatchInlineSnapshot(` +"event: ready + +" +`); + + const executeRequest = await yoga.fetch(url, { + method: 'POST', + body: JSON.stringify({ + query: `mutation { hello }`, + extensions: { + operationId: 'abc', + }, + }), + headers: { + 'x-graphql-event-stream-token': 'my-token', + 'content-type': 'application/json', + }, + }); + + expect(executeRequest.status).toEqual(202); + expect(await executeRequest.text()).toMatchInlineSnapshot(`""`); + next = await iterator.next(); + expect(Buffer.from(next.value).toString('utf-8')).toMatchInlineSnapshot(` +"event: next +id: abc +data: {"id":"abc","payload":{"data":{"hello":null}}} + +" +`); + abortController.abort(); + }); + + it('execute subscription', async () => { + const abortController = new AbortController(); + const yoga = createYoga({ + plugins: [useSSESingleConnection()], + schema: createSchema({ + typeDefs: /* GraphQL */ ` + type Query { + hello: String + } + type Subscription { + hello: String + } + `, + resolvers: { + Subscription: { + hello: { + async *subscribe() { + yield { hello: 'Hello' }; + }, + }, + }, + }, + }), + }); + + const url = new URL('http://yoga/graphql'); + const eventStreamRequest = await yoga.fetch(url, { + method: 'GET', + headers: { + 'x-graphql-event-stream-token': 'my-token', + accept: 'text/event-stream', + }, + signal: abortController.signal, + }); + + const iterator = eventStreamRequest.body![Symbol.asyncIterator](); + let next = await iterator.next(); + expect(Buffer.from(next.value).toString('utf-8')).toMatchInlineSnapshot(` +"event: ready + +" +`); + + const executionURL = new URL('http://yoga/graphql'); + executionURL.searchParams.set('query', 'subscription { hello }'); + executionURL.searchParams.set('extensions', JSON.stringify({ operationId: 'abc' })); + + const executeRequest = await yoga.fetch(executionURL, { + method: 'GET', + headers: { + 'x-graphql-event-stream-token': 'my-token', + }, + }); + + expect(executeRequest.status).toEqual(202); + expect(await executeRequest.text()).toMatchInlineSnapshot(`""`); + next = await iterator.next(); + expect(Buffer.from(next.value).toString('utf-8')).toMatchInlineSnapshot(` +"event: next +id: abc +data: {"id":"abc","payload":{"data":{"hello":"Hello"}}} + +" +`); + abortController.abort(); + }); + + it('concurrent operations', async () => { + const d = createDeferred(); + const yoga = createYoga({ + plugins: [useSSESingleConnection()], + schema: createSchema({ + typeDefs: /* GraphQL */ ` + type Query { + hello: String + } + + type Subscription { + hello: String + } + `, + resolvers: { + Subscription: { + hello: { + async *subscribe() { + await d.promise; + yield { hello: 'Hello' }; + }, + }, + }, + }, + }), + }); + + const url = new URL('http://yoga/graphql'); + url.searchParams.set('query', 'subscription { hello }'); + const response1 = await yoga.fetch(url, { + headers: { + 'X-GraphQL-Event-Stream-Token': 'my-token', + }, + }); + const response1Iterator = response1.body![Symbol.asyncIterator](); + const response1First = await response1Iterator.next(); + expect(Buffer.from(response1First.value).toString('utf-8')).toMatchInlineSnapshot(` +": + +" +`); + const response2 = await yoga.fetch(url, { + headers: { + 'X-GraphQL-Event-Stream-Token': 'my-token', + }, + }); + const response2Iterator = response2.body![Symbol.asyncIterator](); + const response2First = await response2Iterator.next(); + expect(Buffer.from(response2First.value).toString('utf-8')).toMatchInlineSnapshot(` +": + +" +`); + }); +}); + +type Deferred = { + resolve: (value: T) => void; + reject: (value: unknown) => void; + promise: Promise; +}; + +function createDeferred(): Deferred { + const d = {} as Deferred; + d.promise = new Promise((resolve, reject) => { + d.resolve = resolve; + d.reject = reject; + }); + return d; +} diff --git a/packages/graphql-yoga/src/plugins/types.ts b/packages/graphql-yoga/src/plugins/types.ts index 79aac461c7..6a420dc113 100644 --- a/packages/graphql-yoga/src/plugins/types.ts +++ b/packages/graphql-yoga/src/plugins/types.ts @@ -54,7 +54,7 @@ export type Plugin< * Use this hook with your own risk. It is still experimental and may change in the future. * @internal */ - onResultProcess?: OnResultProcess; + onResultProcess?: OnResultProcess; }; export type OnYogaInitHook> = ( @@ -73,7 +73,6 @@ export interface OnRequestEventPayload { request: Request; serverContext: TServerContext | undefined; fetchAPI: FetchAPI; - endResponse(response: Response): void; url: URL; } @@ -116,7 +115,9 @@ export interface OnParamsEventPayload { fetchAPI: FetchAPI; } -export type OnResultProcess = (payload: OnResultProcessEventPayload) => PromiseOrValue; +export type OnResultProcess = ( + payload: OnResultProcessEventPayload, +) => PromiseOrValue; export type ExecutionResultWithSerializer = ExecutionResult< TData, @@ -135,13 +136,16 @@ export type ResultProcessor = ( acceptedMediaType: string, ) => PromiseOrValue; -export interface OnResultProcessEventPayload { +export interface OnResultProcessEventPayload { request: Request; result: ResultProcessorInput; setResult(result: ResultProcessorInput): void; resultProcessor?: ResultProcessor; acceptableMediaTypes: string[]; setResultProcessor(resultProcessor: ResultProcessor, acceptedMediaType: string): void; + fetchAPI: FetchAPI; + serverContext: TServerContext; + endResponse(response: Response): void; } export type OnResponseHook = ( diff --git a/packages/graphql-yoga/src/plugins/use-sse-single-connection.ts b/packages/graphql-yoga/src/plugins/use-sse-single-connection.ts new file mode 100644 index 0000000000..b40a1d15c6 --- /dev/null +++ b/packages/graphql-yoga/src/plugins/use-sse-single-connection.ts @@ -0,0 +1,183 @@ +import { ExecutionResult } from 'graphql'; +import { isAsyncIterable } from '@graphql-tools/utils'; +import { createPubSub, map, pipe, Repeater, type PubSub } from '@graphql-yoga/subscription'; +import { TypedEventTarget } from '@graphql-yoga/typed-event-target'; +import { YogaInitialContext } from '../types.js'; +import type { Plugin, ResultProcessorInput } from './types.js'; + +type SSEPubSub = PubSub<{ + 'graphql-sse-subscribe': [string, string]; + 'graphql-sse-unsubscribe': [string, boolean]; +}>; + +export interface SSESingleConnectionPluginOptions { + eventTarget?: TypedEventTarget; +} + +export function useSSESingleConnection( + options?: SSESingleConnectionPluginOptions, +): Plugin): void }> { + const eventTarget: TypedEventTarget = options?.eventTarget ?? new EventTarget(); + const pubSub = createPubSub({ + eventTarget, + }); + const tokenByRequest = new WeakMap(); + const operationIdByRequest = new WeakMap(); + const tokenReservations = new Map(); + + return { + onRequest({ request, url, fetchAPI, endResponse }) { + const streamToken = + request.headers.get('X-GraphQL-Event-Stream-Token') || url.searchParams.get('token'); + + if (!streamToken) { + return; + } + + const acceptHeader = request.headers.get('Accept'); + if (acceptHeader?.includes('text/event-stream') && request.method === 'GET') { + const reservation = tokenReservations.get(streamToken); + if (reservation) { + endResponse( + new fetchAPI.Response(null, { + status: 409, + statusText: 'Conflict', + }), + ); + } else { + tokenReservations.set(streamToken, request); + const encoder = new fetchAPI.TextEncoder(); + // TODO: + // Maybe auto-close the stream after some time without operations being registered? + + const eventStream = new Repeater(async (push, stop) => { + function eventListener(event: CustomEvent) { + push(event.detail as string); + } + + // TODO: in case of a conflict, the client that causes the conflict could use that information for executing GraphQL operations on behalf of that stream token + // instead we should generate a new stream token for the client that it should use for sending graphql requests and send it to the client as part of the ready event. + + stop.then(() => { + eventTarget.removeEventListener( + `graphql-sse-subscribe:${streamToken}`, + eventListener, + ); + }); + + await eventTarget.addEventListener( + `graphql-sse-subscribe:${streamToken}`, + eventListener, + ); + + // Notify client that the connection is ready + await push('event: ready\n\n'); + }); + + const stream = Repeater.merge([ + pipe( + eventStream, + map((str: string) => { + return encoder.encode(str); + }), + ), + new Repeater(async (_, stop) => { + request.signal.addEventListener('abort', () => { + stop(request.signal.reason); + }); + }), + ]); + + const response = new fetchAPI.Response(stream as unknown as BodyInit, { + status: 200, + headers: { + 'Content-Type': 'text/event-stream', + }, + }); + + endResponse(response); + } + + return; + } + tokenByRequest.set(request, streamToken); + }, + onParams({ request, params }) { + if (tokenByRequest.has(request) && params?.extensions?.operationId) { + operationIdByRequest.set(request, params.extensions.operationId); + } + }, + onResultProcess({ request, result, fetchAPI, endResponse }) { + const token = tokenByRequest.get(request); + const operationId = operationIdByRequest.get(request); + + if (token && operationId) { + // serverContext.waitUntil( + Promise.resolve().then( + () => + runOperation({ + pubSub, + operationId, + token, + result, + }), + // ), + ); + endResponse( + new fetchAPI.Response(null, { + status: 202, + }), + ); + } + }, + }; +} + +async function runOperation(args: { + pubSub: SSEPubSub; + operationId: string; + token: string; + result: ResultProcessorInput; +}) { + if (isAsyncIterable(args.result)) { + const asyncIterator = args.result[Symbol.asyncIterator](); + let breakLoop = false; + args.pubSub + .subscribe('graphql-sse-unsubscribe', args.operationId) + .next() + .finally(() => { + breakLoop = true; + return asyncIterator.return?.(); + }); + const asyncIterable: AsyncIterable = { + [Symbol.asyncIterator]: () => asyncIterator, + }; + for await (const chunk of asyncIterable) { + if (breakLoop) { + break; + } + const messageJson = { + id: args.operationId, + payload: chunk, + }; + const messageStr = `event: next\nid: ${args.operationId}\ndata: ${JSON.stringify( + messageJson, + )}\n\n`; + args.pubSub.publish('graphql-sse-subscribe', args.token, messageStr); + } + } else { + const messageJson = { + id: args.operationId, + payload: args.result, + }; + const messageStr = `event: next\nid: ${args.operationId}\ndata: ${JSON.stringify( + messageJson, + )}\n\n`; + args.pubSub.publish('graphql-sse-subscribe', args.token, messageStr); + } + const completeMessageJson = { + id: args.operationId, + }; + const completeMessageStr = `event: complete\ndata: ${JSON.stringify(completeMessageJson)}\n\n`; + args.pubSub.publish('graphql-sse-subscribe', args.token, completeMessageStr); +} diff --git a/packages/graphql-yoga/src/process-request.ts b/packages/graphql-yoga/src/process-request.ts index 597fbe8fb8..d914bc0514 100644 --- a/packages/graphql-yoga/src/process-request.ts +++ b/packages/graphql-yoga/src/process-request.ts @@ -4,24 +4,27 @@ import { ExecutionArgs } from '@graphql-tools/executor'; import { OnResultProcess, ResultProcessor, ResultProcessorInput } from './plugins/types.js'; import { FetchAPI, GraphQLParams } from './types.js'; -export async function processResult({ +export async function processResult({ request, result, fetchAPI, + serverContext, onResultProcessHooks, }: { request: Request; result: ResultProcessorInput; fetchAPI: FetchAPI; + serverContext: TServerContext; /** * Response Hooks */ - onResultProcessHooks: OnResultProcess[]; + onResultProcessHooks: OnResultProcess[]; }) { let resultProcessor: ResultProcessor | undefined; const acceptableMediaTypes: string[] = []; let acceptedMediaType = '*/*'; + let earlyResponse: Response | undefined; for (const onResultProcessHook of onResultProcessHooks) { await onResultProcessHook({ @@ -36,7 +39,16 @@ export async function processResult({ resultProcessor = newResultProcessor; acceptedMediaType = newAcceptedMimeType; }, + serverContext, + fetchAPI, + endResponse(response) { + earlyResponse = response; + }, }); + + if (earlyResponse) { + return earlyResponse; + } } // If no result processor found for this result, return an error diff --git a/packages/graphql-yoga/src/server.ts b/packages/graphql-yoga/src/server.ts index 9a98974f03..e8f3e01239 100644 --- a/packages/graphql-yoga/src/server.ts +++ b/packages/graphql-yoga/src/server.ts @@ -198,7 +198,7 @@ export class YogaServer< >; private onRequestParseHooks: OnRequestParseHook[]; private onParamsHooks: OnParamsHook[]; - private onResultProcessHooks: OnResultProcess[]; + private onResultProcessHooks: OnResultProcess[]; private maskedErrorsOpts: YogaMaskedErrorOpts | null; private id: string; @@ -327,7 +327,7 @@ export class YogaServer< }), // Middlewares after the GraphQL execution useResultProcessors(), - useErrorHandling((error, request) => { + useErrorHandling((error, request, serverContext) => { const errors = handleError(error, this.maskedErrorsOpts, this.logger); const result = { @@ -339,6 +339,7 @@ export class YogaServer< result, fetchAPI: this.fetchAPI, onResultProcessHooks: this.onResultProcessHooks, + serverContext, }); }), @@ -580,6 +581,7 @@ export class YogaServer< result, fetchAPI: this.fetchAPI, onResultProcessHooks: this.onResultProcessHooks, + serverContext, }); }; } From f40d8bef5521515cac336bb41b9bd03ccb200011 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 22 Mar 2024 13:20:34 +0000 Subject: [PATCH 10/10] chore(dependencies): updated changesets for modified dependencies --- pnpm-lock.yaml | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1631b789e..b06d533c27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1545,7 +1545,7 @@ importers: version: 0.8.4(graphql@16.6.0) '@graphql-tools/url-loader': specifier: 8.0.1 - version: 8.0.1(graphql@16.6.0) + version: 8.0.1(@types/node@18.16.16)(graphql@16.6.0) graphiql: specifier: 2.0.7 version: 2.0.7(@codemirror/language@6.0.0)(@types/react@18.2.8)(graphql@16.6.0)(react-dom@18.2.0)(react-is@17.0.2)(react@18.2.0) @@ -8334,33 +8334,6 @@ packages: dev: true /@graphql-tools/url-loader@8.0.1(@types/node@18.16.16)(graphql@16.6.0): - resolution: {integrity: sha512-B2k8KQEkEQmfV1zhurT5GLoXo8jbXP+YQHUayhCSxKYlRV7j/1Fhp1b21PDM8LXIDGlDRXaZ0FbWKOs7eYXDuQ==} - engines: {node: '>=16.0.0'} - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - dependencies: - '@ardatan/sync-fetch': 0.0.1 - '@graphql-tools/delegate': 10.0.0(graphql@16.6.0) - '@graphql-tools/executor-graphql-ws': 1.0.0(graphql@16.6.0) - '@graphql-tools/executor-http': 1.0.5(@types/node@18.16.16)(graphql@16.6.0) - '@graphql-tools/executor-legacy-ws': 1.0.0(graphql@16.6.0) - '@graphql-tools/utils': 10.1.1(graphql@16.6.0) - '@graphql-tools/wrap': 10.0.0(graphql@16.6.0) - '@types/ws': 8.5.4 - '@whatwg-node/fetch': 0.9.17 - graphql: 16.6.0 - isomorphic-ws: 5.0.0(ws@8.13.0) - tslib: 2.6.2 - value-or-promise: 1.0.12 - ws: 8.13.0 - transitivePeerDependencies: - - '@types/node' - - bufferutil - - encoding - - utf-8-validate - dev: true - - /@graphql-tools/url-loader@8.0.1(graphql@16.6.0): resolution: {integrity: sha512-B2k8KQEkEQmfV1zhurT5GLoXo8jbXP+YQHUayhCSxKYlRV7j/1Fhp1b21PDM8LXIDGlDRXaZ0FbWKOs7eYXDuQ==} engines: {node: '>=16.0.0'} peerDependencies: @@ -8385,7 +8358,6 @@ packages: - bufferutil - encoding - utf-8-validate - dev: false /@graphql-tools/utils@10.0.0(graphql@16.6.0): resolution: {integrity: sha512-ndBPc6zgR+eGU/jHLpuojrs61kYN3Z89JyMLwK3GCRkPv4EQn9EOr1UWqF1JO0iM+/jAVHY0mvfUxyrFFN9DUQ==}