@znight/graphql 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"testing.mjs","names":[],"sources":["../src/testing.ts"],"sourcesContent":["/**\n * `@znight/graphql/testing`: test helpers for GraphQL applications (RFC 0005 §5.12).\n *\n * - `executeOperation()` sends an operation through the whole HTTP pipeline of a `TestApp`\n * (`app.inject()`: guards, hooks, every adapter) or, without HTTP transport, through\n * `GraphQLPlugin.execute()`;\n * - `subscribeOperation()` streams an operation over GraphQL over SSE in memory, or through\n * `GraphQLPlugin.subscribe()` without HTTP transport;\n * - `fakeLoader()` builds a `Loader<L>` whose `load` / `loadMany` are spies of the running test\n * framework (`bun:test`, Vitest, Jest, or the built-in spy of `@znight/testing`).\n *\n * Needs `@znight/testing` (an optional peer dependency of `@znight/graphql`).\n */\nimport type { Plugin } from '@znight/core'\nimport type { SseItem } from '@znight/http'\nimport type { MockFunction, TestApp } from '@znight/testing'\nimport type { FormattedExecutionResult, GraphQLFormattedError } from 'graphql'\nimport type { GraphQLRequest, GraphQLResultStream } from './execution.ts'\nimport type { BatchLoad, BatchLoader, LoaderKey, LoaderValue } from './types.ts'\n\nimport { parseSseStream } from '@znight/http'\nimport { resolveMockFn } from '@znight/testing'\n\nimport { endpointOf, executorOf, type GraphQLPlugin } from './plugin.ts'\n\n/** An operation for {@link executeOperation} and {@link subscribeOperation}. */\nexport interface OperationRequest {\n readonly query: string\n readonly variables?: Readonly<Record<string, unknown>>\n readonly operationName?: string\n readonly extensions?: Readonly<Record<string, unknown>>\n /**\n * Request headers (`x-api-key`, `accept`...), merged over the defaults (`content-type:\n * application/json`, and `accept: application/graphql-response+json, application/json;q=0.9`\n * or `text/event-stream`). Ignored without HTTP transport.\n */\n readonly headers?: Readonly<Record<string, string>>\n}\n\n/** A GraphQL result: `data`, `errors` and `extensions` when present. */\nexport interface OperationResult<TData = Record<string, unknown>> {\n readonly data?: TData | null\n readonly errors?: readonly GraphQLFormattedError[]\n readonly extensions?: Readonly<Record<string, unknown>>\n}\n\n/** What {@link executeOperation} resolves with. */\nexport interface OperationResponse<TData = Record<string, unknown>> extends OperationResult<TData> {\n /**\n * The HTTP status: 200 for an executed operation (field errors included), 400 for a request\n * error (the default `accept` is `application/graphql-response+json`), 403 for an HTTP guard...\n * Without HTTP transport, the status the endpoint would answer.\n */\n readonly status: number\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>\n /** The raw body (an HTTP error body is not a GraphQL result: `data` and `errors` are then absent). */\n readonly body: string\n}\n\n/** Options of {@link OperationStream.next}. */\nexport interface OperationStreamWaitOptions {\n /** Maximum wait for the next result (ms). Default 2 000. */\n readonly timeoutMs?: number\n}\n\n/**\n * The results of {@link subscribeOperation}, one per `next` event. Ends after `complete` (or the\n * single result of a query, or a request error). `return()` disconnects: the server sees a client\n * abort (`ctx.signal`, then the source's `return()`).\n */\nexport interface OperationStream<TData = Record<string, unknown>>\n extends AsyncIterableIterator<OperationResult<TData>>,\n AsyncDisposable {\n /** HTTP status of the stream (200), or of a request error (400, 405, 503...). */\n readonly status: number\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>\n /** The next result. Rejects when none arrives within `timeoutMs` (2 000 ms by default). */\n next(options?: OperationStreamWaitOptions): Promise<IteratorResult<OperationResult<TData>, undefined>>\n /** Ends the stream (disconnects). */\n return(): Promise<IteratorResult<OperationResult<TData>, undefined>>\n}\n\n/** The application `executeOperation()` and `subscribeOperation()` run against: a `TestApp`. */\nexport type GraphQLTestApp = Pick<TestApp, 'app' | 'inject' | 'injectStream'>\n\nconst DEFAULT_TIMEOUT_MS = 2_000\nconst RESPONSE_ACCEPT = 'application/graphql-response+json, application/json;q=0.9'\nconst DONE: IteratorResult<never, undefined> = Object.freeze({ done: true, value: undefined })\n\n/**\n * Executes a query or a mutation through the whole HTTP pipeline (`app.inject()`: `POST` on the\n * endpoint), or through `GraphQLPlugin` without HTTP transport.\n *\n * ```ts\n * const res = await executeOperation(app, { query: '{ books { title } }', headers: { 'x-api-key': KEY } })\n * expect(res.status).toBe(200)\n * expect(res.errors).toBeUndefined()\n * ```\n */\nexport async function executeOperation<TData = Record<string, unknown>>(\n app: GraphQLTestApp,\n request: OperationRequest,\n): Promise<OperationResponse<TData>> {\n const plugin = graphqlPlugin(app, 'executeOperation()')\n const body = operationBody(request, 'executeOperation()')\n const endpoint = endpointOf(plugin)\n if (endpoint === undefined) {\n const response = await executorOf(plugin).execute(body)\n return {\n ...pick<TData>(response.result),\n status: response.status,\n headers: { ...response.headers },\n body: JSON.stringify(response.result),\n }\n }\n const response = await app.inject({\n method: 'POST',\n url: endpoint,\n headers: headers(request, RESPONSE_ACCEPT),\n body: JSON.stringify(body),\n })\n return {\n ...parseResult<TData>(response.body),\n status: response.status,\n headers: response.headers,\n body: response.body,\n }\n}\n\n/**\n * Starts an operation as a stream: GraphQL over SSE in memory (`app.injectStream()`: `POST` with\n * `accept: text/event-stream`), or `GraphQLPlugin.subscribe()` without HTTP transport (no HTTP\n * guards then). Resolves once the subscription is established: its source has started (a resolver\n * written as an async generator has subscribed to the PubSub, when it subscribes before its first\n * `await`), so events published from then on\n * are received, even before the first `next()`.\n *\n * ```ts\n * await using events = await subscribeOperation(app, { query: 'subscription { bookAdded { title } }' })\n * await executeOperation(app, { query: ADD_DUNE })\n * expect((await events.next()).value).toEqual({ data: { bookAdded: { title: 'Dune' } } })\n * ```\n */\nexport async function subscribeOperation<TData = Record<string, unknown>>(\n app: GraphQLTestApp,\n request: OperationRequest,\n): Promise<OperationStream<TData>> {\n const plugin = graphqlPlugin(app, 'subscribeOperation()')\n const body = operationBody(request, 'subscribeOperation()')\n const endpoint = endpointOf(plugin)\n const label = describeOperation(body.query)\n if (endpoint === undefined) {\n const outcome = await executorOf(plugin).subscribe(body)\n if ('status' in outcome) return new SingleResult(outcome.status, {}, pick<TData>(outcome.result))\n return new Results<TData>(200, {}, directSource(outcome), label)\n }\n const response = await app.injectStream({\n method: 'POST',\n url: endpoint,\n headers: headers(request, 'text/event-stream'),\n body: JSON.stringify(body),\n })\n const type = response.headers['content-type']\n if (typeof type !== 'string' || !type.startsWith('text/event-stream')) {\n const text = await new Response(response.body).text()\n return new SingleResult(response.status, response.headers, parseResult<TData>(text))\n }\n return new Results<TData>(response.status, response.headers, sseSource(parseSseStream(response.body)), label)\n}\n\n/** Options of {@link fakeLoader}: what a key resolves to. */\nexport type FakeLoaderValues<K, V> =\n | Readonly<Record<string, V | Error>>\n | ReadonlyMap<K, V | Error>\n | ((key: K) => V | Error | Promise<V | Error>)\n\n/** A `Loader<L>` built by {@link fakeLoader}: `load` and `loadMany` are spies. */\nexport interface FakeLoader<K, V> extends BatchLoader<K, V> {\n readonly load: MockFunction<(key: K) => Promise<V>> & ((key: K) => Promise<V>)\n readonly loadMany: MockFunction<(keys: readonly K[]) => Promise<(V | Error)[]>> &\n ((keys: readonly K[]) => Promise<(V | Error)[]>)\n}\n\n/**\n * A `Loader<L>` for unit tests of resolvers (with `TestBed`), answering from `values` (a record by\n * key, a map, or a function of the key; an `Error` value fails that key). `load` and `loadMany` are\n * spies; a missing key rejects.\n *\n * ```ts\n * const authors = fakeLoader<AuthorsById>({ [herbert.id]: herbert })\n * expect(await instance.author(dune, authors)).toBe(herbert)\n * expect(authors.load).toHaveBeenCalledWith(herbert.id)\n * ```\n */\nexport function fakeLoader<L extends BatchLoad<unknown, unknown>>(\n values: FakeLoaderValues<LoaderKey<L>, LoaderValue<L>>,\n): FakeLoader<LoaderKey<L>, LoaderValue<L>> {\n type K = LoaderKey<L>\n type V = LoaderValue<L>\n const resolve = lookup<K, V>(values)\n const cache = new Map<unknown, Promise<V>>()\n const loadOne = (key: K): Promise<V> => {\n const cached = cache.get(key)\n if (cached !== undefined) return cached\n const loaded = Promise.resolve()\n .then(() => resolve(key))\n .then(value => (value instanceof Error ? Promise.reject(value) : value))\n cache.set(key, loaded)\n return loaded\n }\n const create = resolveMockFn().create\n const load = create((key: K) => loadOne(key)) as FakeLoader<K, V>['load']\n const loadMany = create((keys: readonly K[]) =>\n Promise.all(keys.map(key => loadOne(key).catch((error: unknown) => asError(error)))),\n ) as FakeLoader<K, V>['loadMany']\n const loader: FakeLoader<K, V> = {\n load,\n loadMany,\n clear(key: K) {\n cache.delete(key)\n return this\n },\n clearAll() {\n cache.clear()\n return this\n },\n prime(key: K, value: V | PromiseLike<V> | Error) {\n if (!cache.has(key)) {\n const primed = value instanceof Error ? Promise.reject(value) : Promise.resolve(value)\n primed.catch(() => undefined)\n cache.set(key, primed)\n }\n return this\n },\n }\n return loader\n}\n\n// ------------------------------------------------------------------------------------------------\n\nfunction graphqlPlugin(app: GraphQLTestApp, what: string): GraphQLPlugin {\n const plugin = app?.app?.getPlugin<Plugin>('graphql')\n if (plugin === undefined) {\n throw new TypeError(`${what} expects a TestApp whose plugins include GraphQLPlugin.configure()`)\n }\n return plugin as GraphQLPlugin\n}\n\nfunction operationBody(request: OperationRequest, what: string): GraphQLRequest {\n if (request === null || typeof request !== 'object' || typeof request.query !== 'string') {\n throw new TypeError(`${what} expects { query, variables?, operationName?, extensions?, headers? }`)\n }\n return {\n query: request.query,\n ...(request.variables === undefined ? {} : { variables: request.variables }),\n ...(request.operationName === undefined ? {} : { operationName: request.operationName }),\n ...(request.extensions === undefined ? {} : { extensions: request.extensions }),\n }\n}\n\n/** The request headers: the defaults, then the operation's (names lower-cased, so they replace the defaults). */\nfunction headers(request: OperationRequest, accept: string): Record<string, string> {\n const result: Record<string, string> = { 'content-type': 'application/json', accept }\n for (const [name, value] of Object.entries(request.headers ?? {})) result[name.toLowerCase()] = value\n return result\n}\n\nfunction pick<TData>(result: FormattedExecutionResult): OperationResult<TData> {\n return {\n ...('data' in result ? { data: result.data as TData | null } : {}),\n ...(result.errors === undefined ? {} : { errors: result.errors }),\n ...(result.extensions === undefined ? {} : { extensions: result.extensions }),\n }\n}\n\n/** The GraphQL result of a body; `{}` when it is not one (an HTTP error body, a page). */\nfunction parseResult<TData>(body: string): OperationResult<TData> {\n let parsed: unknown\n try {\n parsed = JSON.parse(body)\n } catch {\n return {}\n }\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return {}\n const result = parsed as FormattedExecutionResult\n if (!('data' in result) && !Array.isArray(result.errors)) return {}\n return pick<TData>(result)\n}\n\nfunction describeOperation(query: string): string {\n const text = query.replace(/\\s+/g, ' ').trim()\n return text.length > 60 ? `${text.slice(0, 60)}…` : text\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error))\n}\n\nfunction lookup<K, V>(values: FakeLoaderValues<K, V>): (key: K) => V | Error | Promise<V | Error> {\n if (typeof values === 'function') return values\n if (values instanceof Map) {\n return key => (values.has(key) ? (values.get(key) as V | Error) : missing(key))\n }\n const record = values as Readonly<Record<string, V | Error>>\n return key => (Object.hasOwn(record, String(key)) ? (record[String(key)] as V | Error) : missing(key))\n}\n\nfunction missing(key: unknown): Error {\n return new Error(`fakeLoader(): no value for the key ${JSON.stringify(key) ?? String(key)}`)\n}\n\n/** A source of results: `undefined` when it ended. */\ninterface ResultSource {\n next(): Promise<FormattedExecutionResult | undefined>\n close(): Promise<void>\n}\n\nfunction directSource(stream: GraphQLResultStream): ResultSource {\n return {\n next: async () => {\n const step = await stream.next()\n return step.done === true ? undefined : step.value\n },\n close: async () => {\n await stream.return()\n },\n }\n}\n\n/** The `next` events of an event stream, until `complete` (comments, `retry` and other events skipped). */\nfunction sseSource(items: AsyncIterableIterator<SseItem>): ResultSource {\n return {\n next: async () => {\n for (;;) {\n const step = await items.next()\n if (step.done === true) return undefined\n const item = step.value\n if (item.type !== 'event') continue\n if (item.event === 'complete') return undefined\n if (item.event === 'next') return JSON.parse(item.data) as FormattedExecutionResult\n }\n },\n close: async () => {\n await items.return?.()\n },\n }\n}\n\n/** The stream of a subscription (or of a query sent as a stream). */\nclass Results<TData> implements OperationStream<TData> {\n readonly status: number\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>\n readonly #source: ResultSource\n readonly #label: string\n /** A read still pending after a timeout: the next call waits for it, so no result is lost. */\n #pending: Promise<FormattedExecutionResult | undefined> | undefined\n #ended = false\n\n constructor(\n status: number,\n headers: Readonly<Record<string, string | readonly string[] | undefined>>,\n source: ResultSource,\n label: string,\n ) {\n this.status = status\n this.headers = headers\n this.#source = source\n this.#label = label\n }\n\n async next(options: OperationStreamWaitOptions = {}): Promise<IteratorResult<OperationResult<TData>, undefined>> {\n if (this.#ended) return DONE\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n this.#pending ??= this.#source.next()\n const pending = this.#pending\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<typeof TIMEOUT>(resolve => {\n timer = setTimeout(() => resolve(TIMEOUT), timeoutMs)\n })\n let result: FormattedExecutionResult | undefined | typeof TIMEOUT\n try {\n result = await Promise.race([pending, timeout])\n } catch (error) {\n this.#pending = undefined\n await this.return()\n throw error\n } finally {\n clearTimeout(timer)\n }\n if (result === TIMEOUT) {\n throw new Error(`subscribeOperation(): no result within ${timeoutMs} ms for ${this.#label}`)\n }\n this.#pending = undefined\n if (result === undefined) {\n await this.return()\n return DONE\n }\n return { done: false, value: pick<TData>(result) }\n }\n\n async return(): Promise<IteratorResult<OperationResult<TData>, undefined>> {\n if (!this.#ended) {\n this.#ended = true\n await this.#source.close()\n }\n return DONE\n }\n\n [Symbol.asyncIterator](): OperationStream<TData> {\n return this\n }\n\n [Symbol.asyncDispose](): Promise<void> {\n return this.return().then(() => undefined)\n }\n}\n\nconst TIMEOUT: unique symbol = Symbol('timeout')\n\n/** A request error (or a query's result without HTTP transport): one result, then the end. */\nclass SingleResult<TData> implements OperationStream<TData> {\n readonly status: number\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>\n #result: OperationResult<TData> | undefined\n\n constructor(\n status: number,\n headers: Readonly<Record<string, string | readonly string[] | undefined>>,\n result: OperationResult<TData>,\n ) {\n this.status = status\n this.headers = headers\n this.#result = result\n }\n\n next(): Promise<IteratorResult<OperationResult<TData>, undefined>> {\n const result = this.#result\n this.#result = undefined\n return Promise.resolve(result === undefined ? DONE : { done: false, value: result })\n }\n\n return(): Promise<IteratorResult<OperationResult<TData>, undefined>> {\n this.#result = undefined\n return Promise.resolve(DONE)\n }\n\n [Symbol.asyncIterator](): OperationStream<TData> {\n return this\n }\n\n [Symbol.asyncDispose](): Promise<void> {\n return Promise.resolve()\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"testing.mjs","names":[],"sources":["../src/conformance.ts","../src/testing.ts"],"sourcesContent":["/**\n * The driver conformance suite (RFC 0009 §5.4, §12): the same scenarios against the built-in\n * transport and every route-mounted driver, on the HTTP adapters given, through the whole\n * `HttpPlugin` pipeline (`executeOperation()`, `subscribeOperation()`). Exported from\n * `@znight/graphql/testing` as `describeDriverConformance()` for third-party drivers, and\n * framework-neutral: the caller passes its `describe` / `it` / `beforeAll` / `afterAll` (`bun:test`,\n * Vitest, Jest, `node:test`), the assertions are `node:assert`'s.\n *\n * The application is hand-written metadata (what the compiler generates): a catalog with a\n * mutation publishing an event, a field resolved through a loader, a request-scoped session read by\n * a field guard and by a resolver, a result mapper, an error to mask, subscriptions (a PubSub topic,\n * a finite and an endless generator), an HTTP guard on the endpoint, introspection off and limits.\n */\n// biome-ignore-all lint/suspicious/useExpect: a framework-neutral suite, asserting with node:assert\nimport type { CanActivate, HttpAdapter, HttpContext } from '@znight/http'\nimport type { GraphQLDriver } from './driver.ts'\nimport type { GraphQLResolverBinding, GraphQLResolverOperation, GraphQLTypeDefinition } from './metadata.ts'\nimport type { PubSub } from './pubsub.ts'\nimport type { OperationResponse, OperationResult, OperationStream } from './testing.ts'\nimport type { BatchLoad, GraphQLContext, GraphQLResultMapper } from './types.ts'\n\nimport { deepStrictEqual, ok, strictEqual } from 'node:assert/strict'\nimport { HttpPlugin, NotFoundException } from '@znight/http'\nimport { defineTestMetadata, TestApp, testToken } from '@znight/testing'\n\nimport { GraphQLPlugin } from './plugin.ts'\nimport { executeOperation, subscribeOperation } from './testing.ts'\n\n/** The test functions of the framework running the suite. */\nexport interface ConformanceTestApi {\n describe(name: string, body: () => void): unknown\n it(name: string, body: () => Promise<void>): unknown\n beforeAll(body: () => Promise<void>): unknown\n afterAll(body: () => Promise<void>): unknown\n}\n\n/** Options of {@link describeDriverConformance}. */\nexport interface DriverConformanceOptions {\n /**\n * A new driver for each application the suite boots (a driver serves one application at a\n * time); `undefined` for the built-in transport.\n */\n readonly driver: () => GraphQLDriver | undefined\n /** `describe`, `it`, `beforeAll` and `afterAll` of the test framework. */\n readonly test: ConformanceTestApi\n /** The adapters to run on, by name (`{ node: () => nodeAdapter(), hono: () => honoAdapter() }`). Default: `HttpPlugin`'s. */\n readonly adapters?: Readonly<Record<string, () => HttpAdapter>>\n /** Whether the driver serves subscriptions over SSE (`subscribeOperation()`). Default `true`. */\n readonly subscriptions?: boolean\n}\n\n/**\n * Describes the conformance scenarios of a GraphQL driver (RFC 0009 §12): queries, mutations,\n * field guards and HTTP guards, loaders batching, request-scoped providers, result mappers,\n * masking, introspection, limits, the `graphql:operation` hook, `POST` bodies other than JSON and\n * mutations over `GET` refused (CSRF) and, over SSE, subscriptions (events, completion, client\n * abort, `maxActive`, shutdown whatever the plugin order).\n *\n * ```ts\n * import { describeDriverConformance } from '@znight/graphql/testing'\n * import { yogaDriver } from '@znight/graphql/yoga'\n * import { afterAll, beforeAll, describe, it } from 'bun:test'\n *\n * describeDriverConformance({ driver: () => yogaDriver(), test: { describe, it, beforeAll, afterAll } })\n * ```\n */\nexport function describeDriverConformance(options: DriverConformanceOptions): void {\n const { test } = options\n const adapters = Object.entries(options.adapters ?? { default: undefined })\n const name = options.driver()?.name ?? 'builtin'\n test.describe(`GraphQL driver conformance: ${name}`, () => {\n for (const [adapterName, adapter] of adapters) {\n test.describe(`${adapterName} adapter`, () => {\n describeOperations(options, name, adapter)\n if (options.subscriptions !== false) describeSubscriptions(options, adapter)\n })\n }\n })\n}\n\n// ------------------------------------------------------------------------------------------------\n// the application\n\ninterface Book {\n readonly id: string\n readonly title: string\n readonly authorId: string\n}\n\ninterface Author {\n readonly id: string\n readonly name: string\n}\n\ninterface Events {\n bookAdded: Book\n}\n\n/** What the resolvers observed, per application. */\ninterface Observed {\n readonly batches: (readonly string[])[]\n readonly ended: string[]\n sessions: number\n}\n\nconst OBSERVED = Symbol('conformance.observed')\n\nclass Catalog {\n readonly #pubsub: PubSub<Events>\n readonly #books: Book[] = [\n { id: '1', title: 'Dune', authorId: 'herbert' },\n { id: '2', title: 'Hyperion', authorId: 'simmons' },\n { id: '3', title: 'Children of Dune', authorId: 'herbert' },\n ]\n readonly [OBSERVED]: Observed = { batches: [], ended: [], sessions: 0 }\n\n constructor(pubsub: PubSub<Events>) {\n this.#pubsub = pubsub\n }\n\n books(): readonly Book[] {\n return this.#books\n }\n\n book(args: { id: string }): Book {\n const book = this.#books.find(candidate => candidate.id === args.id)\n if (book === undefined) throw new NotFoundException(`No book ${args.id}`)\n return book\n }\n\n secret(): string {\n throw new Error('postgres://admin:secret@db')\n }\n\n async addBook(args: { title: string }): Promise<Book> {\n const book = { id: String(this.#books.length + 1), title: args.title, authorId: 'herbert' }\n this.#books.push(book)\n await this.#pubsub.publish('bookAdded', book)\n return book\n }\n\n bookAdded(ctx: GraphQLContext): AsyncIterableIterator<Book> {\n return this.#pubsub.subscribe('bookAdded', { signal: ctx.signal })\n }\n\n async *countdown(args: { from: number }): AsyncGenerator<number> {\n for (let n = args.from; n > 0; n--) yield await Promise.resolve(n)\n }\n\n async *forever(ctx: GraphQLContext): AsyncGenerator<number> {\n const signal = ctx.signal\n try {\n for (let n = 1; !signal.aborted; n++) {\n yield n\n await new Promise<void>(resolve => {\n const timer = setTimeout(resolve, 10)\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer)\n resolve()\n },\n { once: true },\n )\n })\n }\n } finally {\n this[OBSERVED].ended.push('forever')\n }\n }\n}\n\nclass AuthorsById implements BatchLoad<string, Author> {\n readonly #catalog: Catalog\n\n constructor(catalog: Catalog) {\n this.#catalog = catalog\n }\n\n load(keys: readonly string[]): Promise<readonly Author[]> {\n this.#catalog[OBSERVED].batches.push([...keys])\n return Promise.resolve(keys.map(id => ({ id, name: id === 'herbert' ? 'Frank Herbert' : 'Dan Simmons' })))\n }\n}\n\n/** Request-scoped: one per HTTP request, shared by the guard and the resolver of an operation. */\nclass Session {\n readonly id: number\n readonly user: string | undefined\n\n constructor(http: HttpContext, catalog: Catalog) {\n this.id = ++catalog[OBSERVED].sessions\n this.user = http.header('x-user')\n }\n}\n\nclass Viewer {\n readonly #session: Session\n\n constructor(session: Session) {\n this.#session = session\n }\n\n me(): { id: number; user: string | null } {\n return { id: this.#session.id, user: this.#session.user ?? null }\n }\n}\n\n/** A field guard: editors only, read from the request-scoped session. */\nclass EditorGuard {\n readonly #session: Session\n\n constructor(session: Session) {\n this.#session = session\n }\n\n canResolve(): boolean {\n return this.#session.user === 'editor'\n }\n}\n\n/** An HTTP guard on the endpoint (`http.guards`). */\nclass Gate implements CanActivate {\n canActivate(ctx: HttpContext): boolean {\n return ctx.header('x-block') !== 'yes'\n }\n}\n\n/** Errors as data (RFC 0008): a missing book is a `NotFound` of `BookResult`. */\nclass Rejections implements GraphQLResultMapper {\n static readonly types: readonly string[] = ['NotFound']\n\n toResult(error: unknown): unknown {\n return error instanceof NotFoundException ? { __typename: 'NotFound', message: error.message } : undefined\n }\n}\n\ntype Invoke = (resolver: never, parent: never, args: never, ctx: never) => unknown\n\n/** The binding of a resolver method (what the compiler generates for `@Query()`, `@ResolveField()`...). */\nfunction resolver(\n cls: { readonly name: string },\n method: string,\n operation: GraphQLResolverOperation,\n invoke: Invoke,\n extra: Partial<GraphQLResolverBinding> = {},\n): GraphQLResolverBinding {\n return {\n token: testToken(cls as never),\n method,\n operation,\n guards: [],\n meta: {},\n invoke: invoke as unknown as GraphQLResolverBinding['invoke'],\n ...extra,\n }\n}\n\nconst ID = [{ name: 'id', type: 'ID!' }]\n\nconst TYPES: readonly GraphQLTypeDefinition[] = [\n { kind: 'object', name: 'Author', fields: [{ name: 'name', type: 'String!' }] },\n {\n kind: 'object',\n name: 'Book',\n fields: [\n { name: 'id', type: 'ID!' },\n { name: 'title', type: 'String!' },\n {\n name: 'author',\n type: 'Author!',\n resolver: resolver(\n Catalog,\n 'books',\n 'field',\n ((_r: Catalog, parent: Book, _a: unknown, ctx: GraphQLContext) =>\n ctx.loader(testToken(AuthorsById)).load(parent.authorId)) as Invoke,\n { loaders: [testToken(AuthorsById)] },\n ),\n },\n ],\n },\n { kind: 'object', name: 'NotFound', fields: [{ name: 'message', type: 'String!' }] },\n { kind: 'union', name: 'BookResult', members: ['Book', 'NotFound'], discriminator: { by: 'typename' } },\n {\n kind: 'object',\n name: 'Me',\n fields: [\n { name: 'id', type: 'Int!' },\n { name: 'user', type: 'String' },\n ],\n },\n {\n kind: 'object',\n name: 'Query',\n fields: [\n {\n name: 'books',\n type: '[Book!]!',\n resolver: resolver(Catalog, 'books', 'query', ((r: Catalog) => r.books()) as Invoke),\n },\n {\n name: 'book',\n type: 'BookResult!',\n args: ID,\n resolver: resolver(Catalog, 'book', 'query', ((r: Catalog, _p: unknown, a: { id: string }) => ({\n __typename: 'Book',\n ...r.book(a),\n })) as Invoke),\n },\n {\n name: 'secret',\n type: 'String',\n resolver: resolver(Catalog, 'secret', 'query', ((r: Catalog) => r.secret()) as Invoke),\n },\n {\n name: 'me',\n type: 'Me!',\n resolver: resolver(Viewer, 'me', 'query', ((r: Viewer) => r.me()) as Invoke),\n },\n ],\n },\n {\n kind: 'object',\n name: 'Mutation',\n fields: [\n {\n name: 'addBook',\n type: 'Book!',\n args: [{ name: 'title', type: 'String!' }],\n resolver: resolver(\n Catalog,\n 'addBook',\n 'mutation',\n ((r: Catalog, _p: unknown, a: { title: string }) => r.addBook(a)) as Invoke,\n { guards: [testToken(EditorGuard)] },\n ),\n },\n ],\n },\n {\n kind: 'object',\n name: 'Subscription',\n fields: [\n {\n name: 'bookAdded',\n type: 'Book!',\n resolver: resolver(Catalog, 'bookAdded', 'subscription', ((\n r: Catalog,\n _p: unknown,\n _a: unknown,\n c: GraphQLContext,\n ) => r.bookAdded(c)) as Invoke),\n },\n {\n name: 'countdown',\n type: 'Int!',\n args: [{ name: 'from', type: 'Int!' }],\n resolver: resolver(Catalog, 'countdown', 'subscription', ((r: Catalog, _p: unknown, a: { from: number }) =>\n r.countdown(a)) as Invoke),\n },\n {\n name: 'forever',\n type: 'Int!',\n resolver: resolver(Catalog, 'forever', 'subscription', ((\n r: Catalog,\n _p: unknown,\n _a: unknown,\n c: GraphQLContext,\n ) => r.forever(c)) as Invoke),\n },\n ],\n },\n]\n\nfunction conformanceMetadata() {\n return defineTestMetadata({\n providers: [\n { class: Catalog, deps: ['@znight/graphql#PubSub'] },\n { class: AuthorsById, deps: [Catalog] },\n { class: Session, scope: 'request', deps: ['@znight/http#HttpContext', Catalog] },\n { class: Viewer, scope: 'request', deps: [Session] },\n { class: EditorGuard, scope: 'request', deps: [Session] },\n Gate,\n Rejections,\n ],\n externals: [\n { token: '@znight/graphql#GraphQLContext', scope: 'request', providedBy: 'GraphQLPlugin' },\n { token: '@znight/graphql#PubSub', providedBy: 'GraphQLPlugin' },\n { token: '@znight/graphql#GraphQLPlugin', providedBy: 'GraphQLPlugin' },\n { token: '@znight/http#HttpContext', scope: 'request', providedBy: 'HttpPlugin' },\n { token: '@znight/http#RequestId', scope: 'request', providedBy: 'HttpPlugin' },\n ],\n extensions: {\n graphql: {\n version: 1,\n schemaHash: 'c0nf0rmance00000',\n roots: { query: 'Query', mutation: 'Mutation', subscription: 'Subscription' },\n types: [...TYPES].sort((a, b) => (a.name < b.name ? -1 : 1)),\n },\n },\n })\n}\n\n/** The drain timeout of `HttpPlugin` in the suite: a shutdown waiting for it is a failure. */\nconst DRAIN_TIMEOUT_MS = 5_000\n\n/**\n * Boots the conformance application on `adapter` with `driver`, `GraphQLPlugin` after `HttpPlugin`\n * (or before it, `graphqlFirst`).\n */\nfunction boot(\n options: DriverConformanceOptions,\n adapter: (() => HttpAdapter) | undefined,\n graphqlFirst = false,\n): Promise<TestApp> {\n const driver = options.driver()\n const http = HttpPlugin.configure({\n port: 0,\n host: '127.0.0.1',\n drainTimeoutMs: DRAIN_TIMEOUT_MS,\n ...(adapter === undefined ? {} : { adapter: adapter() }),\n })\n const graphql = GraphQLPlugin.configure({\n introspection: false,\n resultMappers: [Rejections],\n limits: { maxAliases: 3, maxDepth: 4 },\n http: { guards: [Gate] },\n subscriptions: { maxActive: 2 },\n ...(driver === undefined ? {} : { driver }),\n })\n return TestApp.create({\n metadata: conformanceMetadata(),\n plugins: graphqlFirst ? [graphql, http] : [http, graphql],\n logger: { debug() {}, info() {}, warn() {}, error() {} },\n })\n}\n\nfunction observed(app: TestApp): Observed {\n return (app.get(testToken(Catalog) as never) as Catalog)[OBSERVED]\n}\n\nfunction graphql(app: TestApp): GraphQLPlugin {\n return app.app.getPlugin('graphql') as GraphQLPlugin\n}\n\nasync function until(condition: () => boolean, what: string, timeoutMs = 2_000): Promise<void> {\n const started = Date.now()\n while (!condition()) {\n if (Date.now() - started > timeoutMs) throw new Error(`conformance: ${what} did not happen within ${timeoutMs} ms`)\n await new Promise(resolve => setTimeout(resolve, 5))\n }\n}\n\nfunction codes(result: OperationResult): readonly unknown[] {\n return (result.errors ?? []).map(error => error.extensions?.['code'])\n}\n\nfunction result(response: OperationResponse): OperationResult {\n return {\n ...('data' in response ? { data: response.data } : {}),\n ...(response.errors === undefined ? {} : { errors: response.errors }),\n }\n}\n\n// ------------------------------------------------------------------------------------------------\n// the scenarios\n\nfunction describeOperations(\n options: DriverConformanceOptions,\n driverName: string,\n adapter: (() => HttpAdapter) | undefined,\n): void {\n const { test } = options\n let app: TestApp\n const events: { driver: string; transport: string; operationName: string | undefined }[] = []\n\n test.beforeAll(async () => {\n app = await boot(options, adapter)\n app.app.hooks.on('graphql:operation', event => {\n events.push({ driver: event.driver, transport: event.transport, operationName: event.operationName })\n })\n })\n test.afterAll(async () => {\n await app?.close()\n })\n\n test.it('executes queries, with field resolvers batched through a loader', async () => {\n const response = await executeOperation(app, { query: '{ books { title author { name } } }' })\n strictEqual(response.status, 200)\n deepStrictEqual(result(response), {\n data: {\n books: [\n { title: 'Dune', author: { name: 'Frank Herbert' } },\n { title: 'Hyperion', author: { name: 'Dan Simmons' } },\n { title: 'Children of Dune', author: { name: 'Frank Herbert' } },\n ],\n },\n })\n deepStrictEqual(observed(app).batches.at(-1), ['herbert', 'simmons'])\n })\n\n test.it('executes mutations behind a field guard reading a request-scoped provider', async () => {\n const denied = await executeOperation(app, { query: 'mutation { addBook(title: \"Ilium\") { title } }' })\n strictEqual(denied.status, 200)\n deepStrictEqual(denied.data, null)\n deepStrictEqual(codes(denied), ['FORBIDDEN'])\n const added = await executeOperation(app, {\n query: 'mutation Add($title: String!) { addBook(title: $title) { title author { name } } }',\n variables: { title: 'Ilium' },\n headers: { 'x-user': 'editor' },\n })\n deepStrictEqual(result(added), { data: { addBook: { title: 'Ilium', author: { name: 'Frank Herbert' } } } })\n })\n\n test.it('creates request-scoped providers once per request', async () => {\n const first = await executeOperation<{ a: { id: number }; b: { id: number; user: string } }>(app, {\n query: '{ a: me { id } b: me { id user } }',\n headers: { 'x-user': 'ada' },\n })\n const second = await executeOperation<{ me: { id: number } }>(app, { query: '{ me { id } }' })\n ok(first.data !== undefined && first.data !== null && second.data !== undefined && second.data !== null)\n strictEqual(first.data.a.id, first.data.b.id)\n strictEqual(first.data.b.user, 'ada')\n ok(second.data.me.id > first.data.a.id)\n })\n\n test.it('turns errors into data with result mappers, and masks unexpected errors', async () => {\n const missing = await executeOperation(app, {\n query: '{ book(id: \"404\") { __typename ... on NotFound { message } ... on Book { title } } }',\n })\n deepStrictEqual(result(missing), { data: { book: { __typename: 'NotFound', message: 'No book 404' } } })\n const secret = await executeOperation(app, { query: '{ secret }' })\n strictEqual(secret.status, 200)\n deepStrictEqual(result(secret), {\n data: { secret: null },\n errors: [\n {\n message: 'Unexpected error.',\n locations: [{ line: 1, column: 3 }],\n path: ['secret'],\n extensions: { code: 'INTERNAL_SERVER_ERROR' },\n },\n ],\n })\n ok(!secret.body.includes('postgres'), 'the original error leaked')\n })\n\n test.it('refuses introspection, documents past the limits and invalid documents (400)', async () => {\n for (const [query, code] of [\n ['{ __schema { queryType { name } } }', 'GRAPHQL_VALIDATION_FAILED'],\n ['{ a: me { id } b: me { id } c: me { id } d: me { id } }', 'QUERY_TOO_COMPLEX'],\n ['{ books { author { name } author2: author { name } } }', undefined],\n ['{ nope }', 'GRAPHQL_VALIDATION_FAILED'],\n ['{ books', 'GRAPHQL_PARSE_FAILED'],\n ] as const) {\n const response = await executeOperation(app, { query })\n if (code === undefined) {\n strictEqual(response.status, 200, query)\n continue\n }\n strictEqual(response.status, 400, query)\n strictEqual(response.data, undefined, query)\n deepStrictEqual([...new Set(codes(response))], [code], query)\n }\n })\n\n test.it('runs behind the HTTP guards of the endpoint', async () => {\n const blocked = await executeOperation(app, { query: '{ books { title } }', headers: { 'x-block': 'yes' } })\n strictEqual(blocked.status, 403)\n strictEqual(blocked.data, undefined)\n })\n\n test.it('reports every executed operation to the graphql:operation hook, with the driver', async () => {\n events.length = 0\n await executeOperation(app, { query: 'query Named { books { id } }' })\n deepStrictEqual(events, [{ driver: driverName, transport: 'http', operationName: 'Named' }])\n })\n\n test.it('refuses POST bodies other than JSON (415): a cross-site form never runs an operation', async () => {\n const mutation = 'mutation { addBook(title: \"Forged\") { id } }'\n const before = await executeOperation<{ books: unknown[] }>(app, { query: '{ books { id } }' })\n for (const [type, body] of [\n ['application/x-www-form-urlencoded', `query=${encodeURIComponent(mutation)}`],\n [\n 'multipart/form-data; boundary=x',\n `--x\\r\\ncontent-disposition: form-data; name=\"query\"\\r\\n\\r\\n${mutation}\\r\\n--x--\\r\\n`,\n ],\n ['text/plain', JSON.stringify({ query: mutation })],\n ['application/graphql', mutation],\n ] as const) {\n const response = await app.inject({\n method: 'POST',\n url: '/graphql',\n headers: { 'content-type': type, 'x-user': 'editor' },\n body,\n })\n strictEqual(response.status, 415, type)\n deepStrictEqual(\n response.json(),\n {\n errors: [\n {\n message: 'Send GraphQL requests as application/json (content-type: application/json)',\n extensions: { code: 'BAD_REQUEST' },\n },\n ],\n },\n type,\n )\n }\n const after = await executeOperation<{ books: unknown[] }>(app, { query: '{ books { id } }' })\n strictEqual(after.data?.books.length, before.data?.books.length)\n })\n\n test.it('refuses mutations over GET (405, allow: POST)', async () => {\n const before = await executeOperation<{ books: unknown[] }>(app, { query: '{ books { id } }' })\n const response = await app.inject({\n method: 'GET',\n url: '/graphql',\n query: { query: 'mutation { addBook(title: \"Fetched\") { id } }' },\n headers: { 'x-user': 'editor' },\n })\n strictEqual(response.status, 405)\n strictEqual(response.headers['allow'], 'POST')\n const after = await executeOperation<{ books: unknown[] }>(app, { query: '{ books { id } }' })\n strictEqual(after.data?.books.length, before.data?.books.length)\n })\n}\n\nfunction describeSubscriptions(options: DriverConformanceOptions, adapter: (() => HttpAdapter) | undefined): void {\n const { test } = options\n let app: TestApp\n\n test.beforeAll(async () => {\n app = await boot(options, adapter)\n })\n test.afterAll(async () => {\n await app?.close()\n })\n\n test.it('streams the events of a subscription until the client leaves', async () => {\n const events = await subscribeOperation(app, { query: 'subscription { bookAdded { title } }' })\n strictEqual(events.status, 200)\n await executeOperation(app, {\n query: 'mutation { addBook(title: \"Endymion\") { id } }',\n headers: { 'x-user': 'editor' },\n })\n deepStrictEqual((await events.next()).value, { data: { bookAdded: { title: 'Endymion' } } })\n await events.return()\n await until(() => graphql(app).activeSubscriptions === 0, 'the end of the subscription')\n })\n\n test.it('completes a finite subscription', async () => {\n const events = await subscribeOperation(app, { query: 'subscription { countdown(from: 2) }' })\n deepStrictEqual(await drain(events), [{ data: { countdown: 2 } }, { data: { countdown: 1 } }])\n })\n\n test.it('ends the source when the client aborts, and caps the active subscriptions', async () => {\n const ended = observed(app).ended\n ended.length = 0\n const first = await subscribeOperation(app, { query: 'subscription { forever }' })\n const second = await subscribeOperation(app, { query: 'subscription { forever }' })\n deepStrictEqual((await first.next()).value, { data: { forever: 1 } })\n deepStrictEqual((await second.next()).value, { data: { forever: 1 } })\n const third = await subscribeOperation(app, { query: 'subscription { forever }' })\n const refused = await third.next()\n deepStrictEqual(codes(refused.value ?? {}), ['SERVICE_UNAVAILABLE'])\n await third.return()\n await first.return()\n await second.return()\n await until(() => ended.length === 2, 'the end of both sources')\n await until(() => graphql(app).activeSubscriptions === 0, 'the end of the subscriptions')\n })\n\n for (const graphqlFirst of [false, true]) {\n const order = graphqlFirst ? 'GraphQLPlugin before HttpPlugin' : 'HttpPlugin before GraphQLPlugin'\n test.it(`ends the subscriptions when the application stops, without waiting for the drain (${order})`, async () => {\n const stopping = await boot(options, adapter, graphqlFirst)\n const ended = observed(stopping).ended\n const events = await subscribeOperation(stopping, { query: 'subscription { forever }' })\n deepStrictEqual((await events.next()).value, { data: { forever: 1 } })\n // the client keeps reading, as a real one does (the stream ends once read)\n const rest = drain(events)\n const started = Date.now()\n await stopping.close()\n const elapsed = Date.now() - started\n ok(elapsed < DRAIN_TIMEOUT_MS / 2, `the shutdown waited ${elapsed} ms for the drain`)\n await until(() => ended.includes('forever'), 'the end of the source on shutdown')\n await rest\n strictEqual(graphql(stopping).activeSubscriptions, 0)\n })\n }\n}\n\nasync function drain(events: OperationStream): Promise<OperationResult[]> {\n const results: OperationResult[] = []\n for (let step = await events.next(); step.done !== true; step = await events.next()) results.push(step.value)\n return results\n}\n","/**\n * `@znight/graphql/testing`: test helpers for GraphQL applications (RFC 0005 §5.12).\n *\n * - `executeOperation()` sends an operation through the whole HTTP pipeline of a `TestApp`\n * (`app.inject()`: guards, hooks, every adapter) or, without HTTP transport, through\n * `GraphQLPlugin.execute()`;\n * - `subscribeOperation()` streams an operation over GraphQL over SSE in memory, or through\n * `GraphQLPlugin.subscribe()` without HTTP transport;\n * - `describeDriverConformance()` describes the conformance scenarios of a GraphQL driver (RFC 0009);\n * - `fakeLoader()` builds a `Loader<L>` whose `load` / `loadMany` are spies of the running test\n * framework (`bun:test`, Vitest, Jest, or the built-in spy of `@znight/testing`).\n *\n * Needs `@znight/testing` (an optional peer dependency of `@znight/graphql`).\n */\nimport type { Plugin } from '@znight/core'\nimport type { SseItem } from '@znight/http'\nimport type { MockFunction, TestApp } from '@znight/testing'\nimport type { FormattedExecutionResult, GraphQLFormattedError } from 'graphql'\nimport type { GraphQLRequest, GraphQLResultStream } from './execution.ts'\nimport type { BatchLoad, BatchLoader, LoaderKey, LoaderValue } from './types.ts'\n\nimport { parseSseStream } from '@znight/http'\nimport { resolveMockFn } from '@znight/testing'\n\nimport { endpointOf, executorOf, type GraphQLPlugin } from './plugin.ts'\n\nexport { type ConformanceTestApi, type DriverConformanceOptions, describeDriverConformance } from './conformance.ts'\n\n/** An operation for {@link executeOperation} and {@link subscribeOperation}. */\nexport interface OperationRequest {\n readonly query: string\n readonly variables?: Readonly<Record<string, unknown>>\n readonly operationName?: string\n readonly extensions?: Readonly<Record<string, unknown>>\n /**\n * Request headers (`x-api-key`, `accept`...), merged over the defaults (`content-type:\n * application/json`, and `accept: application/graphql-response+json, application/json;q=0.9`\n * or `text/event-stream`). Ignored without HTTP transport.\n */\n readonly headers?: Readonly<Record<string, string>>\n}\n\n/** A GraphQL result: `data`, `errors` and `extensions` when present. */\nexport interface OperationResult<TData = Record<string, unknown>> {\n readonly data?: TData | null\n readonly errors?: readonly GraphQLFormattedError[]\n readonly extensions?: Readonly<Record<string, unknown>>\n}\n\n/** What {@link executeOperation} resolves with. */\nexport interface OperationResponse<TData = Record<string, unknown>> extends OperationResult<TData> {\n /**\n * The HTTP status: 200 for an executed operation (field errors included), 400 for a request\n * error (the default `accept` is `application/graphql-response+json`), 403 for an HTTP guard...\n * Without HTTP transport, the status the endpoint would answer.\n */\n readonly status: number\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>\n /** The raw body (an HTTP error body is not a GraphQL result: `data` and `errors` are then absent). */\n readonly body: string\n}\n\n/** Options of {@link OperationStream.next}. */\nexport interface OperationStreamWaitOptions {\n /** Maximum wait for the next result (ms). Default 2 000. */\n readonly timeoutMs?: number\n}\n\n/**\n * The results of {@link subscribeOperation}, one per `next` event. Ends after `complete` (or the\n * single result of a query, or a request error). `return()` disconnects: the server sees a client\n * abort (`ctx.signal`, then the source's `return()`).\n */\nexport interface OperationStream<TData = Record<string, unknown>>\n extends AsyncIterableIterator<OperationResult<TData>>,\n AsyncDisposable {\n /** HTTP status of the stream (200), or of a request error (400, 405, 503...). */\n readonly status: number\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>\n /** The next result. Rejects when none arrives within `timeoutMs` (2 000 ms by default). */\n next(options?: OperationStreamWaitOptions): Promise<IteratorResult<OperationResult<TData>, undefined>>\n /** Ends the stream (disconnects). */\n return(): Promise<IteratorResult<OperationResult<TData>, undefined>>\n}\n\n/** The application `executeOperation()` and `subscribeOperation()` run against: a `TestApp`. */\nexport type GraphQLTestApp = Pick<TestApp, 'app' | 'inject' | 'injectStream'>\n\nconst DEFAULT_TIMEOUT_MS = 2_000\nconst RESPONSE_ACCEPT = 'application/graphql-response+json, application/json;q=0.9'\nconst DONE: IteratorResult<never, undefined> = Object.freeze({ done: true, value: undefined })\n\n/**\n * Executes a query or a mutation through the whole HTTP pipeline (`app.inject()`: `POST` on the\n * endpoint), or through `GraphQLPlugin` without HTTP transport.\n *\n * ```ts\n * const res = await executeOperation(app, { query: '{ books { title } }', headers: { 'x-api-key': KEY } })\n * expect(res.status).toBe(200)\n * expect(res.errors).toBeUndefined()\n * ```\n */\nexport async function executeOperation<TData = Record<string, unknown>>(\n app: GraphQLTestApp,\n request: OperationRequest,\n): Promise<OperationResponse<TData>> {\n const plugin = graphqlPlugin(app, 'executeOperation()')\n const body = operationBody(request, 'executeOperation()')\n const endpoint = endpointOf(plugin)\n if (endpoint === undefined) {\n const response = await executorOf(plugin).execute(body)\n return {\n ...pick<TData>(response.result),\n status: response.status,\n headers: { ...response.headers },\n body: JSON.stringify(response.result),\n }\n }\n const response = await app.inject({\n method: 'POST',\n url: endpoint,\n headers: headers(request, RESPONSE_ACCEPT),\n body: JSON.stringify(body),\n })\n return {\n ...parseResult<TData>(response.body),\n status: response.status,\n headers: response.headers,\n body: response.body,\n }\n}\n\n/**\n * Starts an operation as a stream: GraphQL over SSE in memory (`app.injectStream()`: `POST` with\n * `accept: text/event-stream`), or `GraphQLPlugin.subscribe()` without HTTP transport (no HTTP\n * guards then). Resolves once the subscription is established: its source has started (a resolver\n * written as an async generator has subscribed to the PubSub, when it subscribes before its first\n * `await`), so events published from then on\n * are received, even before the first `next()`.\n *\n * ```ts\n * await using events = await subscribeOperation(app, { query: 'subscription { bookAdded { title } }' })\n * await executeOperation(app, { query: ADD_DUNE })\n * expect((await events.next()).value).toEqual({ data: { bookAdded: { title: 'Dune' } } })\n * ```\n */\nexport async function subscribeOperation<TData = Record<string, unknown>>(\n app: GraphQLTestApp,\n request: OperationRequest,\n): Promise<OperationStream<TData>> {\n const plugin = graphqlPlugin(app, 'subscribeOperation()')\n const body = operationBody(request, 'subscribeOperation()')\n const endpoint = endpointOf(plugin)\n const label = describeOperation(body.query)\n if (endpoint === undefined) {\n const outcome = await executorOf(plugin).subscribe(body)\n if ('status' in outcome) return new SingleResult(outcome.status, {}, pick<TData>(outcome.result))\n return new Results<TData>(200, {}, directSource(outcome), label)\n }\n const response = await app.injectStream({\n method: 'POST',\n url: endpoint,\n headers: headers(request, 'text/event-stream'),\n body: JSON.stringify(body),\n })\n const type = response.headers['content-type']\n if (typeof type !== 'string' || !type.startsWith('text/event-stream')) {\n const text = await new Response(response.body).text()\n return new SingleResult(response.status, response.headers, parseResult<TData>(text))\n }\n return new Results<TData>(response.status, response.headers, sseSource(parseSseStream(response.body)), label)\n}\n\n/** Options of {@link fakeLoader}: what a key resolves to. */\nexport type FakeLoaderValues<K, V> =\n | Readonly<Record<string, V | Error>>\n | ReadonlyMap<K, V | Error>\n | ((key: K) => V | Error | Promise<V | Error>)\n\n/** A `Loader<L>` built by {@link fakeLoader}: `load` and `loadMany` are spies. */\nexport interface FakeLoader<K, V> extends BatchLoader<K, V> {\n readonly load: MockFunction<(key: K) => Promise<V>> & ((key: K) => Promise<V>)\n readonly loadMany: MockFunction<(keys: readonly K[]) => Promise<(V | Error)[]>> &\n ((keys: readonly K[]) => Promise<(V | Error)[]>)\n}\n\n/**\n * A `Loader<L>` for unit tests of resolvers (with `TestBed`), answering from `values` (a record by\n * key, a map, or a function of the key; an `Error` value fails that key). `load` and `loadMany` are\n * spies; a missing key rejects.\n *\n * ```ts\n * const authors = fakeLoader<AuthorsById>({ [herbert.id]: herbert })\n * expect(await instance.author(dune, authors)).toBe(herbert)\n * expect(authors.load).toHaveBeenCalledWith(herbert.id)\n * ```\n */\nexport function fakeLoader<L extends BatchLoad<unknown, unknown>>(\n values: FakeLoaderValues<LoaderKey<L>, LoaderValue<L>>,\n): FakeLoader<LoaderKey<L>, LoaderValue<L>> {\n type K = LoaderKey<L>\n type V = LoaderValue<L>\n const resolve = lookup<K, V>(values)\n const cache = new Map<unknown, Promise<V>>()\n const loadOne = (key: K): Promise<V> => {\n const cached = cache.get(key)\n if (cached !== undefined) return cached\n const loaded = Promise.resolve()\n .then(() => resolve(key))\n .then(value => (value instanceof Error ? Promise.reject(value) : value))\n cache.set(key, loaded)\n return loaded\n }\n const create = resolveMockFn().create\n const load = create((key: K) => loadOne(key)) as FakeLoader<K, V>['load']\n const loadMany = create((keys: readonly K[]) =>\n Promise.all(keys.map(key => loadOne(key).catch((error: unknown) => asError(error)))),\n ) as FakeLoader<K, V>['loadMany']\n const loader: FakeLoader<K, V> = {\n load,\n loadMany,\n clear(key: K) {\n cache.delete(key)\n return this\n },\n clearAll() {\n cache.clear()\n return this\n },\n prime(key: K, value: V | PromiseLike<V> | Error) {\n if (!cache.has(key)) {\n const primed = value instanceof Error ? Promise.reject(value) : Promise.resolve(value)\n primed.catch(() => undefined)\n cache.set(key, primed)\n }\n return this\n },\n }\n return loader\n}\n\n// ------------------------------------------------------------------------------------------------\n\nfunction graphqlPlugin(app: GraphQLTestApp, what: string): GraphQLPlugin {\n const plugin = app?.app?.getPlugin<Plugin>('graphql')\n if (plugin === undefined) {\n throw new TypeError(`${what} expects a TestApp whose plugins include GraphQLPlugin.configure()`)\n }\n return plugin as GraphQLPlugin\n}\n\nfunction operationBody(request: OperationRequest, what: string): GraphQLRequest {\n if (request === null || typeof request !== 'object' || typeof request.query !== 'string') {\n throw new TypeError(`${what} expects { query, variables?, operationName?, extensions?, headers? }`)\n }\n return {\n query: request.query,\n ...(request.variables === undefined ? {} : { variables: request.variables }),\n ...(request.operationName === undefined ? {} : { operationName: request.operationName }),\n ...(request.extensions === undefined ? {} : { extensions: request.extensions }),\n }\n}\n\n/** The request headers: the defaults, then the operation's (names lower-cased, so they replace the defaults). */\nfunction headers(request: OperationRequest, accept: string): Record<string, string> {\n const result: Record<string, string> = { 'content-type': 'application/json', accept }\n for (const [name, value] of Object.entries(request.headers ?? {})) result[name.toLowerCase()] = value\n return result\n}\n\nfunction pick<TData>(result: FormattedExecutionResult): OperationResult<TData> {\n return {\n ...('data' in result ? { data: result.data as TData | null } : {}),\n ...(result.errors === undefined ? {} : { errors: result.errors }),\n ...(result.extensions === undefined ? {} : { extensions: result.extensions }),\n }\n}\n\n/** The GraphQL result of a body; `{}` when it is not one (an HTTP error body, a page). */\nfunction parseResult<TData>(body: string): OperationResult<TData> {\n let parsed: unknown\n try {\n parsed = JSON.parse(body)\n } catch {\n return {}\n }\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return {}\n const result = parsed as FormattedExecutionResult\n if (!('data' in result) && !Array.isArray(result.errors)) return {}\n return pick<TData>(result)\n}\n\nfunction describeOperation(query: string): string {\n const text = query.replace(/\\s+/g, ' ').trim()\n return text.length > 60 ? `${text.slice(0, 60)}…` : text\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error))\n}\n\nfunction lookup<K, V>(values: FakeLoaderValues<K, V>): (key: K) => V | Error | Promise<V | Error> {\n if (typeof values === 'function') return values\n if (values instanceof Map) {\n return key => (values.has(key) ? (values.get(key) as V | Error) : missing(key))\n }\n const record = values as Readonly<Record<string, V | Error>>\n return key => (Object.hasOwn(record, String(key)) ? (record[String(key)] as V | Error) : missing(key))\n}\n\nfunction missing(key: unknown): Error {\n return new Error(`fakeLoader(): no value for the key ${JSON.stringify(key) ?? String(key)}`)\n}\n\n/** A source of results: `undefined` when it ended. */\ninterface ResultSource {\n next(): Promise<FormattedExecutionResult | undefined>\n close(): Promise<void>\n}\n\nfunction directSource(stream: GraphQLResultStream): ResultSource {\n return {\n next: async () => {\n const step = await stream.next()\n return step.done === true ? undefined : step.value\n },\n close: async () => {\n await stream.return()\n },\n }\n}\n\n/** The `next` events of an event stream, until `complete` (comments, `retry` and other events skipped). */\nfunction sseSource(items: AsyncIterableIterator<SseItem>): ResultSource {\n return {\n next: async () => {\n for (;;) {\n const step = await items.next()\n if (step.done === true) return undefined\n const item = step.value\n if (item.type !== 'event') continue\n if (item.event === 'complete') return undefined\n if (item.event === 'next') return JSON.parse(item.data) as FormattedExecutionResult\n }\n },\n close: async () => {\n await items.return?.()\n },\n }\n}\n\n/** The stream of a subscription (or of a query sent as a stream). */\nclass Results<TData> implements OperationStream<TData> {\n readonly status: number\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>\n readonly #source: ResultSource\n readonly #label: string\n /** A read still pending after a timeout: the next call waits for it, so no result is lost. */\n #pending: Promise<FormattedExecutionResult | undefined> | undefined\n #ended = false\n\n constructor(\n status: number,\n headers: Readonly<Record<string, string | readonly string[] | undefined>>,\n source: ResultSource,\n label: string,\n ) {\n this.status = status\n this.headers = headers\n this.#source = source\n this.#label = label\n }\n\n async next(options: OperationStreamWaitOptions = {}): Promise<IteratorResult<OperationResult<TData>, undefined>> {\n if (this.#ended) return DONE\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n this.#pending ??= this.#source.next()\n const pending = this.#pending\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<typeof TIMEOUT>(resolve => {\n timer = setTimeout(() => resolve(TIMEOUT), timeoutMs)\n })\n let result: FormattedExecutionResult | undefined | typeof TIMEOUT\n try {\n result = await Promise.race([pending, timeout])\n } catch (error) {\n this.#pending = undefined\n await this.return()\n throw error\n } finally {\n clearTimeout(timer)\n }\n if (result === TIMEOUT) {\n throw new Error(`subscribeOperation(): no result within ${timeoutMs} ms for ${this.#label}`)\n }\n this.#pending = undefined\n if (result === undefined) {\n await this.return()\n return DONE\n }\n return { done: false, value: pick<TData>(result) }\n }\n\n async return(): Promise<IteratorResult<OperationResult<TData>, undefined>> {\n if (!this.#ended) {\n this.#ended = true\n await this.#source.close()\n }\n return DONE\n }\n\n [Symbol.asyncIterator](): OperationStream<TData> {\n return this\n }\n\n [Symbol.asyncDispose](): Promise<void> {\n return this.return().then(() => undefined)\n }\n}\n\nconst TIMEOUT: unique symbol = Symbol('timeout')\n\n/** A request error (or a query's result without HTTP transport): one result, then the end. */\nclass SingleResult<TData> implements OperationStream<TData> {\n readonly status: number\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>\n #result: OperationResult<TData> | undefined\n\n constructor(\n status: number,\n headers: Readonly<Record<string, string | readonly string[] | undefined>>,\n result: OperationResult<TData>,\n ) {\n this.status = status\n this.headers = headers\n this.#result = result\n }\n\n next(): Promise<IteratorResult<OperationResult<TData>, undefined>> {\n const result = this.#result\n this.#result = undefined\n return Promise.resolve(result === undefined ? DONE : { done: false, value: result })\n }\n\n return(): Promise<IteratorResult<OperationResult<TData>, undefined>> {\n this.#result = undefined\n return Promise.resolve(DONE)\n }\n\n [Symbol.asyncIterator](): OperationStream<TData> {\n return this\n }\n\n [Symbol.asyncDispose](): Promise<void> {\n return Promise.resolve()\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { g as GraphQLContext, t as GraphQLDriver } from "./driver-Cnze8Oyr.mjs";
2
+ import { HttpContext } from "@znight/http";
3
+ import { GraphiQLOptions, YogaInitialContext, YogaServerInstance, YogaServerOptions } from "graphql-yoga";
4
+ /** The server context the driver gives Yoga: the `HttpContext` of the route, as `znight`. */
5
+ export interface YogaServerContext {
6
+ readonly znight: HttpContext;
7
+ }
8
+ /**
9
+ * Yoga's context of an operation: its initial context (`request`, `params`, `log`) and the server
10
+ * context of the driver. What {@link yogaContext} returns.
11
+ */
12
+ export type YogaContext = YogaInitialContext & YogaServerContext;
13
+ /** Options of Yoga the engine or `GraphQLPlugin` own (RFC 0009 §4.6): refused by {@link yogaDriver}. */
14
+ declare const OWNED: readonly ["schema", "context", "graphqlEndpoint", "maskedErrors", "cors", "fetchAPI", "maxRequestBodySize", "parserAndValidationCache", "requestId", "healthCheckEndpoint", "landingPage", "disposeOnProcessTerminate", "batching", "multipart"];
15
+ /**
16
+ * Options of {@link yogaDriver}: Yoga's own (`plugins`, `logging`, `allowedHeaders`...), except
17
+ * those the engine, `GraphQLPlugin` or `HttpPlugin` own: the schema, the context, the endpoint
18
+ * (`path`), masking (`exposeErrors`), CORS, the body limit (`http.bodyLimit`), the document cache,
19
+ * the request id and the IDE switch (`graphiql`); and those the driver refuses: `batching` (one
20
+ * operation per request scope) and `multipart` (JSON bodies only, see {@link yogaDriver}). Yoga's
21
+ * `logging` is off unless set: requests and operations are logged by Znight.
22
+ */
23
+ export interface YogaDriverOptions extends Omit<YogaServerOptions<YogaServerContext, Record<string, unknown>>, (typeof OWNED)[number] | "graphiql"> {
24
+ /**
25
+ * GraphiQL's options (title, default query...), when the `graphiql` option of `GraphQLPlugin` is
26
+ * on (it decides whether the IDE is served).
27
+ */
28
+ readonly graphiql?: GraphiQLOptions;
29
+ }
30
+ /** A GraphQL Yoga driver: `GraphQLPlugin.configure({ driver: yogaDriver() })`. */
31
+ export interface YogaDriver extends GraphQLDriver {
32
+ readonly name: "yoga";
33
+ /** The Yoga server, once started (`undefined` before `start()` and after `stop()`). */
34
+ readonly yoga: YogaServerInstance<YogaServerContext, Record<string, unknown>> | undefined;
35
+ }
36
+ /**
37
+ * The GraphQL Yoga driver (RFC 0009 §5.1): `GraphQLPlugin.configure({ driver: yogaDriver({ plugins }) })`.
38
+ * Yoga handles the protocol and its Envelop plugins; the engine parses, validates and executes
39
+ * (guards, loaders, request scope, masking, the `graphql:operation` hook). Plugins that replace
40
+ * the engine's execution (`useGraphQlJit`) are refused: the operation fails with ZNIGHT066.
41
+ *
42
+ * Like the built-in transport, a `POST` body is `application/json` only (415 otherwise): form,
43
+ * multipart and text bodies, which a cross-site page sends without a CORS preflight, never run
44
+ * an operation (CSRF). Errors thrown outside the engine (by Yoga or an Envelop plugin) are masked
45
+ * like the engine's (`Unexpected error.` unless `exposeErrors`) and logged.
46
+ *
47
+ * ```ts
48
+ * GraphQLPlugin.configure({
49
+ * guards: [AccessGuard],
50
+ * driver: yogaDriver({ plugins: [useResponseCache({ session: () => null })] }),
51
+ * })
52
+ * ```
53
+ */
54
+ export declare function yogaDriver(options?: YogaDriverOptions): YogaDriver;
55
+ /**
56
+ * Yoga's context of an operation received by the Yoga driver (RFC 0009 Q2): `GraphQLContext.serverContext`,
57
+ * typed. For the rare resolver that needs Yoga's `request`, `params` or the context an Envelop
58
+ * plugin extended; the HTTP request is `ctx.http`, the user a request-scoped provider.
59
+ *
60
+ * @throws ZnightError (ZNIGHT066) when the operation did not come through the Yoga driver.
61
+ */
62
+ export declare function yogaContext<TContext extends Record<string, unknown> = Record<string, unknown>>(ctx: GraphQLContext): YogaContext & TContext;
63
+ export {};
64
+ //# sourceMappingURL=yoga.d.mts.map