diff --git a/packages/graphql-yoga/src/plugins/types.ts b/packages/graphql-yoga/src/plugins/types.ts index b8c99db267..2009483816 100644 --- a/packages/graphql-yoga/src/plugins/types.ts +++ b/packages/graphql-yoga/src/plugins/types.ts @@ -57,7 +57,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> = ( @@ -122,8 +122,8 @@ export interface OnParamsEventPayload { fetchAPI: FetchAPI } -export type OnResultProcess = ( - payload: OnResultProcessEventPayload, +export type OnResultProcess = ( + payload: OnResultProcessEventPayload, ) => PromiseOrValue export type ResultProcessorInput = @@ -136,7 +136,7 @@ export type ResultProcessor = ( acceptedMediaType: string, ) => PromiseOrValue -export interface OnResultProcessEventPayload { +export interface OnResultProcessEventPayload { request: Request result: ResultProcessorInput setResult(result: ResultProcessorInput): void @@ -147,6 +147,7 @@ export interface OnResultProcessEventPayload { acceptedMediaType: string, ): void fetchAPI: FetchAPI + serverContext: TServerContext endResponse(response: Response): void } diff --git a/packages/graphql-yoga/src/process-request.ts b/packages/graphql-yoga/src/process-request.ts index dac332e955..094fed6139 100644 --- a/packages/graphql-yoga/src/process-request.ts +++ b/packages/graphql-yoga/src/process-request.ts @@ -8,19 +8,21 @@ import { } 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 @@ -42,6 +44,7 @@ export async function processResult({ acceptedMediaType = newAcceptedMimeType }, fetchAPI, + serverContext, endResponse(response) { earlyResponse = response }, diff --git a/packages/graphql-yoga/src/server.ts b/packages/graphql-yoga/src/server.ts index e7a32f66d2..6ce3e1298a 100644 --- a/packages/graphql-yoga/src/server.ts +++ b/packages/graphql-yoga/src/server.ts @@ -216,7 +216,7 @@ export class YogaServer< > private onRequestParseHooks: OnRequestParseHook[] private onParamsHooks: OnParamsHook[] - private onResultProcessHooks: OnResultProcess[] + private onResultProcessHooks: OnResultProcess[] private maskedErrorsOpts: YogaMaskedErrorOpts | null private id: string @@ -356,7 +356,7 @@ export class YogaServer< useResultProcessors({ legacySSE: options?.legacySse !== false, }), - useErrorHandling((error, request) => { + useErrorHandling((error, request, serverContext) => { const errors = handleError(error, this.maskedErrorsOpts, this.logger) const result = { @@ -368,6 +368,7 @@ export class YogaServer< result, fetchAPI: this.fetchAPI, onResultProcessHooks: this.onResultProcessHooks, + serverContext, }) }), ...(options?.plugins ?? []), @@ -603,6 +604,7 @@ export class YogaServer< result, fetchAPI: this.fetchAPI, onResultProcessHooks: this.onResultProcessHooks, + serverContext, }) } } diff --git a/packages/plugins/graphql-sse/src/index.ts b/packages/plugins/graphql-sse/src/index.ts index 5d6e88fc16..06cad02caf 100644 --- a/packages/plugins/graphql-sse/src/index.ts +++ b/packages/plugins/graphql-sse/src/index.ts @@ -24,7 +24,7 @@ export function useGraphQLSSE( options: GraphQLSSEPluginOptions = { pubsub: createPubSub(), }, -): Plugin { +): Plugin): void }> { const { pubsub } = options const tokenByRequest = new WeakMap() const operationIdByRequest = new WeakMap() @@ -80,51 +80,54 @@ export function useGraphQLSSE( operationIdByRequest.set(request, params.extensions.operationId) } }, - onResultProcess({ request, result, fetchAPI, endResponse }) { + onResultProcess({ request, result, fetchAPI, serverContext, endResponse }) { const token = tokenByRequest.get(request) if (token) { const operationId = operationIdByRequest.get(request) // Batching is not supported by GraphQL SSE yet if (operationId && !Array.isArray(result)) { - Promise.resolve().then(async () => { - if (isAsyncIterable(result)) { - const asyncIterator = result[Symbol.asyncIterator]() - pubsub - .subscribe('graphql-sse-unsubscribe', operationId) - .next() - .finally(() => { - asyncIterator.return?.() - }) - let iteratorValue: IteratorResult - while (!(iteratorValue = await asyncIterator.next()).done) { - const chunk = iteratorValue.value + serverContext.waitUntil( + Promise.resolve().then(async () => { + if (isAsyncIterable(result)) { + const asyncIterator = result[Symbol.asyncIterator]() + pubsub + .subscribe('graphql-sse-unsubscribe', operationId) + .next() + .finally(() => { + asyncIterator.return?.() + }) + let iteratorValue: IteratorResult + while (!(iteratorValue = await asyncIterator.next()).done) { + const chunk = iteratorValue.value + const messageJson = { + id: operationId, + payload: chunk, + } + const messageStr = `event: next\nid: ${operationId}\ndata: ${JSON.stringify( + messageJson, + )}\n\n` + pubsub.publish('graphql-sse-subscribe', token, messageStr) + } + } else { const messageJson = { id: operationId, - payload: chunk, + payload: result, } const messageStr = `event: next\nid: ${operationId}\ndata: ${JSON.stringify( messageJson, )}\n\n` pubsub.publish('graphql-sse-subscribe', token, messageStr) } - } else { - const messageJson = { + const completeMessageJson = { id: operationId, - payload: result, } - const messageStr = `event: next\nid: ${operationId}\ndata: ${JSON.stringify( - messageJson, + const completeMessageStr = `event: complete\ndata: ${JSON.stringify( + completeMessageJson, )}\n\n` - pubsub.publish('graphql-sse-subscribe', token, messageStr) - } - const completeMessageJson = { - id: operationId, - } - const completeMessageStr = `event: complete\ndata: ${JSON.stringify( - completeMessageJson, - )}\n\n` - pubsub.publish('graphql-sse-subscribe', token, completeMessageStr) - }) + pubsub.publish('graphql-sse-subscribe', token, completeMessageStr) + }), + ) + endResponse( new fetchAPI.Response(null, { status: 202,