diff --git a/.changeset/graphql-yoga-3491-dependencies.md b/.changeset/graphql-yoga-3491-dependencies.md new file mode 100644 index 0000000000..7ec023d0d0 --- /dev/null +++ b/.changeset/graphql-yoga-3491-dependencies.md @@ -0,0 +1,7 @@ +--- +'graphql-yoga': patch +--- +dependencies updates: + - Updated dependency [`@whatwg-node/server@^0.9.55` + ↗︎](https://www.npmjs.com/package/@whatwg-node/server/v/0.9.55) (from `^0.9.54`, in + `dependencies`) diff --git a/.changeset/pink-snails-thank.md b/.changeset/pink-snails-thank.md new file mode 100644 index 0000000000..0b67db0c01 --- /dev/null +++ b/.changeset/pink-snails-thank.md @@ -0,0 +1,9 @@ +--- +'graphql-yoga': patch +--- + +Fix issue where context values being shared between batched requests. + +A bug within `@whatwg-node/server` caused properties assigned to a batched requests context to be +propagated to all other batched requests contexts. It is resolved by updating the dependency of +`@whatwg-node/server` to `0.9.55`. diff --git a/packages/graphql-yoga/__tests__/batching.spec.ts b/packages/graphql-yoga/__tests__/batching.spec.ts index a141c2393f..de2bfaaf27 100644 --- a/packages/graphql-yoga/__tests__/batching.spec.ts +++ b/packages/graphql-yoga/__tests__/batching.spec.ts @@ -287,4 +287,51 @@ describe('Batching', () => { ], }); }); + + it('batching context have different identity and properties are assignments are not shared', async () => { + let i = 0; + type Context = { + i?: number; + }; + const contexts = [] as Array; + const yoga = createYoga({ + schema: createSchema({ typeDefs: `type Query { _: ID }` }), + batching: true, + plugins: [ + { + onParams(ctx) { + contexts.push(ctx.context); + }, + }, + ], + context() { + i = i + 1; + return { i }; + }, + }); + + const response = await yoga.fetch('http://yoga/graphql', { + method: 'POST', + headers: { + accept: 'application/graphql-response+json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify([{ query: `query A {__typename}` }, { query: `query B {__typename}` }]), + }); + expect(response.status).toBe(200); + const result = await response.json(); + expect(result).toEqual([ + { + data: { __typename: 'Query' }, + }, + { + data: { __typename: 'Query' }, + }, + ]); + + expect(contexts.length).toEqual(2); + expect(contexts[0]).not.toBe(contexts[1]); + expect(contexts[0].i).toEqual(1); + expect(contexts[1].i).toEqual(2); + }); }); diff --git a/packages/graphql-yoga/package.json b/packages/graphql-yoga/package.json index 300ecd2e1d..efd13d02d8 100644 --- a/packages/graphql-yoga/package.json +++ b/packages/graphql-yoga/package.json @@ -57,7 +57,7 @@ "@graphql-yoga/logger": "workspace:^", "@graphql-yoga/subscription": "workspace:^", "@whatwg-node/fetch": "^0.10.1", - "@whatwg-node/server": "^0.9.54", + "@whatwg-node/server": "^0.9.55", "dset": "^3.1.1", "lru-cache": "^10.0.0", "tslib": "^2.5.2" diff --git a/packages/graphql-yoga/src/server.ts b/packages/graphql-yoga/src/server.ts index 46c59593c7..7ecd702926 100644 --- a/packages/graphql-yoga/src/server.ts +++ b/packages/graphql-yoga/src/server.ts @@ -462,17 +462,13 @@ export class YogaServer< { params, request, - batched, }: { params: GraphQLParams; request: Request; - batched: boolean; }, - serverContext: TServerContext, + context: TServerContext, ) { let result: ExecutionResult | AsyncIterable | undefined; - const context: TServerContext & YogaInitialContext = - batched === true ? Object.create(serverContext) : serverContext; try { for (const onParamsHook of this.onParamsHooks) { @@ -492,7 +488,7 @@ export class YogaServer< if (result == null) { const additionalContext = - serverContext.request === request + context.request === request ? { params, } @@ -549,7 +545,7 @@ export class YogaServer< result = newResult; }, request, - context, + context: context as TServerContext & YogaInitialContext, }); } return result; @@ -610,9 +606,8 @@ export class YogaServer< { params, request, - batched: true, }, - serverContext, + Object.create(serverContext), ), ), ) @@ -620,7 +615,6 @@ export class YogaServer< { params: requestParserResult, request, - batched: false, }, serverContext, ))) as ResultProcessorInput; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7db3123230..72f19c51ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -395,7 +395,7 @@ importers: dependencies: '@whatwg-node/server-plugin-cookies': specifier: ^1.0.3 - version: 1.0.3(@whatwg-node/server@0.9.54) + version: 1.0.3(@whatwg-node/server@0.9.55) graphql: specifier: 16.8.1 version: 16.8.1 @@ -1590,8 +1590,8 @@ importers: specifier: ^0.10.1 version: 0.10.1 '@whatwg-node/server': - specifier: ^0.9.54 - version: 0.9.54 + specifier: ^0.9.55 + version: 0.9.55 dset: specifier: ^3.1.1 version: 3.1.4 @@ -1951,7 +1951,7 @@ importers: dependencies: '@whatwg-node/server-plugin-cookies': specifier: ^1.0.3 - version: 1.0.3(@whatwg-node/server@0.9.54) + version: 1.0.3(@whatwg-node/server@0.9.55) jsonwebtoken: specifier: ^9.0.0 version: 9.0.2 @@ -8062,8 +8062,8 @@ packages: peerDependencies: '@whatwg-node/server': ^0.9.44 - '@whatwg-node/server@0.9.54': - resolution: {integrity: sha512-AFwts1RNnnPwpGnbCqyb7fwaE3ROBgkIstpmtXqb2YklFIlQ97Y1rabzeoyKoQqQxX89nMRmfsLAwPzfCpV9pw==} + '@whatwg-node/server@0.9.55': + resolution: {integrity: sha512-FW04dJZfgBGaGoHQosCTeSOXKksCVzMLMV5YZPMpUfEmkH8VeDjCIMguvw2cKgrjnLjwQ1J3irLg2eNQbLxLNg==} engines: {node: '>=18.0.0'} '@wry/context@0.7.4': @@ -21897,7 +21897,7 @@ snapshots: find-up: 6.3.0 minimatch: 9.0.5 read-pkg: 7.1.0 - semver: 7.5.4 + semver: 7.6.3 toml: 3.0.0 yaml: 2.6.0 yargs: 17.7.2 @@ -21950,7 +21950,7 @@ snapshots: resolve: 2.0.0-next.5 rfdc: 1.4.1 safe-json-stringify: 1.2.0 - semver: 7.5.4 + semver: 7.6.3 string-width: 5.1.2 strip-ansi: 7.1.0 supports-color: 9.4.0 @@ -22027,7 +22027,7 @@ snapshots: p-wait-for: 4.1.0 path-key: 4.0.0 regexp-tree: 0.1.27 - semver: 7.5.4 + semver: 7.6.3 tmp-promise: 3.0.3 urlpattern-polyfill: 8.0.2 uuid: 9.0.0 @@ -22057,7 +22057,7 @@ snapshots: p-wait-for: 4.1.0 path-key: 4.0.0 regexp-tree: 0.1.27 - semver: 7.5.4 + semver: 7.6.3 tmp-promise: 3.0.3 urlpattern-polyfill: 8.0.2 uuid: 9.0.0 @@ -22076,7 +22076,7 @@ snapshots: p-locate: 6.0.0 process: 0.11.10 read-pkg-up: 9.1.0 - semver: 7.5.4 + semver: 7.6.3 '@netlify/functions-utils@5.2.93(encoding@0.1.13)(supports-color@9.4.0)': dependencies: @@ -22189,7 +22189,7 @@ snapshots: precinct: 11.0.5(supports-color@9.4.0) require-package-name: 2.0.1 resolve: 2.0.0-next.5 - semver: 7.5.4 + semver: 7.6.3 tmp-promise: 3.0.3 toml: 3.0.0 unixify: 1.0.0 @@ -24945,13 +24945,13 @@ snapshots: fast-querystring: 1.1.2 tslib: 2.6.3 - '@whatwg-node/server-plugin-cookies@1.0.3(@whatwg-node/server@0.9.54)': + '@whatwg-node/server-plugin-cookies@1.0.3(@whatwg-node/server@0.9.55)': dependencies: '@whatwg-node/cookie-store': 0.2.2 - '@whatwg-node/server': 0.9.54 + '@whatwg-node/server': 0.9.55 tslib: 2.6.3 - '@whatwg-node/server@0.9.54': + '@whatwg-node/server@0.9.55': dependencies: '@whatwg-node/fetch': 0.10.1 tslib: 2.6.3 @@ -25137,7 +25137,7 @@ snapshots: global-cache-dir: 4.4.0 is-plain-obj: 4.1.0 path-exists: 5.0.0 - semver: 7.5.4 + semver: 7.6.3 write-file-atomic: 4.0.2 amqp-connection-manager@4.1.14(amqplib@0.10.4): @@ -26418,7 +26418,7 @@ snapshots: js-string-escape: 1.0.1 lodash: 4.17.21 md5-hex: 3.0.1 - semver: 7.5.4 + semver: 7.6.3 well-known-symbols: 2.0.0 concurrently@9.1.0: @@ -28530,7 +28530,7 @@ snapshots: proxy-addr: 2.0.7 rfdc: 1.4.1 secure-json-parse: 2.7.0 - semver: 7.5.4 + semver: 7.6.3 tiny-lru: 11.2.11 fastify@4.28.1: @@ -28640,7 +28640,7 @@ snapshots: '@sinclair/typebox': 0.33.22 '@whatwg-node/cookie-store': 0.2.2 '@whatwg-node/fetch': 0.9.23 - '@whatwg-node/server': 0.9.54 + '@whatwg-node/server': 0.9.55 hotscript: 1.0.13 json-schema-to-ts: 3.1.1 qs: 6.13.0 @@ -29037,7 +29037,7 @@ snapshots: dependencies: '@xhmikosr/downloader': 13.0.1 node-fetch: 3.3.2 - semver: 7.5.4 + semver: 7.6.3 giscus@1.5.0: dependencies: @@ -30704,7 +30704,7 @@ snapshots: jws: 3.2.2 lodash: 4.17.21 ms: 2.1.3 - semver: 7.5.4 + semver: 7.6.3 jsonwebtoken@9.0.2: dependencies: @@ -31068,7 +31068,7 @@ snapshots: jest-validate: 27.5.1 map-obj: 5.0.2 moize: 6.1.6 - semver: 7.5.4 + semver: 7.6.3 log-symbols@1.0.2: dependencies: @@ -32798,7 +32798,7 @@ snapshots: is-plain-obj: 4.1.0 normalize-node-version: 12.4.0 path-exists: 5.0.0 - semver: 7.5.4 + semver: 7.6.3 non-layered-tidy-tree-layout@2.0.2: {} @@ -32816,7 +32816,7 @@ snapshots: dependencies: all-node-versions: 11.3.0 filter-obj: 5.1.0 - semver: 7.5.4 + semver: 7.6.3 normalize-package-data@2.5.0: dependencies: @@ -34715,7 +34715,7 @@ snapshots: semver-diff@4.0.0: dependencies: - semver: 7.5.4 + semver: 7.6.3 semver@5.7.2: {} @@ -36357,7 +36357,7 @@ snapshots: is-yarn-global: 0.4.1 latest-version: 7.0.0 pupa: 3.1.0 - semver: 7.5.4 + semver: 7.6.3 semver-diff: 4.0.0 xdg-basedir: 5.1.0 diff --git a/website/src/pages/docs/features/envelop-plugins.mdx b/website/src/pages/docs/features/envelop-plugins.mdx index caf29404f0..59e9164954 100644 --- a/website/src/pages/docs/features/envelop-plugins.mdx +++ b/website/src/pages/docs/features/envelop-plugins.mdx @@ -172,23 +172,20 @@ tokens etc. import { Plugin } from 'graphql-yoga' function useAuth(): Plugin { - return { - onRequest({ request, fetchAPI, endResponse }) { - if (!request.headers.get('authorization')) { - endResponse( - new fetchAPI.Response( - null, - { + return { + onRequest({ request, fetchAPI, endResponse }) { + if (!request.headers.get('authorization')) { + endResponse( + new fetchAPI.Response(null, { status: 401, headers: { 'Content-Type': 'application/json' } - } + }) ) - ) + } } } - } } ``` diff --git a/website/src/pages/docs/features/jwt.mdx b/website/src/pages/docs/features/jwt.mdx index f823d3ea2e..7d6e3b10df 100644 --- a/website/src/pages/docs/features/jwt.mdx +++ b/website/src/pages/docs/features/jwt.mdx @@ -50,7 +50,12 @@ For the setup mentioned above, you can use the following configuration for your ```ts filename="server.ts" import { createYoga } from 'graphql-yoga' import jwt from 'jsonwebtoken' -import { useJWT, createInlineSigningKeyProvider, createRemoteJwksSigningKeyProvider, extractFromHeader } from '@graphql-yoga/plugin-jwt' +import { + createInlineSigningKeyProvider, + createRemoteJwksSigningKeyProvider, + extractFromHeader, + useJWT +} from '@graphql-yoga/plugin-jwt' import { getUserById, getUserByLogin } from './db' const signingKey = process.env.JWT_SECRET @@ -66,16 +71,14 @@ const yoga = createYoga({ ], // Configure where to look for the JWT token: in the headers, or cookies. // By default, the plugin will look for the token in the 'authorization' header only. - tokenLookupLocations: [ - extractFromHeader({ name: 'authorization', prefix: 'Bearer' }), - ], + tokenLookupLocations: [extractFromHeader({ name: 'authorization', prefix: 'Bearer' })], // Configure your token issuers/audience/algorithms verification options. // By default, the plugin will only verify the HS256/RS256 algorithms. // Please note that this should match the JWT signer issuer/audience/algorithms. tokenVerification: { issuer: 'http://my-issuer.com', audience: 'my-audience', - algorithms: ['HS256', 'RS256'], + algorithms: ['HS256', 'RS256'] }, // Configure context injection after the token is verified. // By default, the plugin will inject the token's payload into the context into the `jwt` field. @@ -85,7 +88,7 @@ const yoga = createYoga({ // By default, the plugin will reject the request if the token is missing or invalid. reject: { missingToken: true, - invalidToken: true, + invalidToken: true } }) ]