diff --git a/.changeset/eight-poets-run.md b/.changeset/eight-poets-run.md new file mode 100644 index 0000000000..403f48d274 --- /dev/null +++ b/.changeset/eight-poets-run.md @@ -0,0 +1,23 @@ +--- +'graphql-yoga': patch +--- + +Do not allow reserved context keys in UserContext and ServerContext if they don't match + +For example, you cannot have `request` as a key in UserContext or ServerContext unless it is `Request` like below; +```ts +// @ts-expect-error Not allowed +createYoga<{ + request: FastifyRequest +}>(/* ... */); + +// But allowed +createYoga<{ + req: FastifyRequest +}>(/* ... */); + +// Also allowed +createYoga<{ + request: Request // From Fetch API +}>(/* ... */); +``` diff --git a/packages/graphql-yoga/src/server.ts b/packages/graphql-yoga/src/server.ts index 44302af713..2de4837fcd 100644 --- a/packages/graphql-yoga/src/server.ts +++ b/packages/graphql-yoga/src/server.ts @@ -734,18 +734,20 @@ export class YogaServer< }; } +export type ContextBase = { + [key: Exclude]: unknown; +} & Partial; + /* eslint-disable */ export type YogaServerInstance< - TServerContext extends Record, - TUserContext extends Record, + TServerContext extends ContextBase, + TUserContext extends ContextBase, > = ServerAdapter>; export function createYoga< - TServerContext extends Record = {}, - TUserContext extends Record = {}, ->( - options: YogaServerOptions, -): YogaServerInstance { + TServerContext extends ContextBase = {}, + TUserContext extends ContextBase = {}, +>(options: YogaServerOptions) { const server = new YogaServer(options); return createServerAdapter>(server, { fetchAPI: server.fetchAPI, diff --git a/packages/graphql-yoga/type-api-check.ts b/packages/graphql-yoga/type-api-check.ts index 4aca1f8c8a..55f4a76a6d 100644 --- a/packages/graphql-yoga/type-api-check.ts +++ b/packages/graphql-yoga/type-api-check.ts @@ -1,7 +1,7 @@ import { ClientRequest } from 'node:http'; import type { GraphQLSchema } from 'graphql'; import { IResolvers } from '@graphql-tools/utils'; -import { createSchema, createYoga, YogaInitialContext } from './src/index.js'; +import { createSchema, createYoga, GraphQLParams, YogaInitialContext } from './src/index.js'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const schema: GraphQLSchema = null as any; @@ -41,6 +41,54 @@ const request: Request = null as any; server.handleRequest(request, { req: clientRequest }); } +// Do not allow reserved context keys +{ + // @ts-expect-error ServerContext type cannot contain reserved key 'request'. + createYoga<{}, { request: { myParam: string } }>({ + schema, + }); +} +{ + // @ts-expect-error ServerContext type cannot contain reserved key 'params'. + createYoga<{}, { params: { myParam: string } }>({ + schema, + }); +} +{ + // @ts-expect-error ServerContext type cannot contain reserved key 'request'. + createYoga<{}, { request: { myParam: string } }>({ + schema, + }); +} +{ + // @ts-expect-error ServerContext type cannot contain reserved key 'params'. + createYoga<{ params: { myParam: string } }>({ + schema, + }); +} +// Allow reserved context keys if they match in ServerContext +{ + createYoga<{ request: Request }>({ + schema, + }); +} +{ + createYoga<{ params: GraphQLParams }>({ + schema, + }); +} +// Allow reserved context keys if they match in UserContext +{ + createYoga<{}, { request: Request }>({ + schema, + }); +} +{ + createYoga<{}, { params: GraphQLParams }>({ + schema, + }); +} + /** * Context + Resolvers */