UNPKG

@graphql-tools/executor

Version:

Fork of GraphQL.js' execute function

330 lines (329 loc) • 14.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildIncrementalResponse = void 0; const utils_1 = require("@graphql-tools/utils"); const DeferredFragments_js_1 = require("./DeferredFragments.js"); const IncrementalGraph_js_1 = require("./IncrementalGraph.js"); const invariant_js_1 = require("./invariant.js"); const types_js_1 = require("./types.js"); function buildIncrementalResponse(context, result, errors, incrementalDataRecords) { const incrementalPublisher = new IncrementalPublisher(context); return incrementalPublisher.buildResponse(result, errors, incrementalDataRecords); } exports.buildIncrementalResponse = buildIncrementalResponse; /** * The IncrementalPublisherState Enum tracks the state of the IncrementalPublisher, which is initialized to * "Started". When there are no more incremental results to publish, the state is set to "Completed". On the * next call to next, clean-up is potentially performed and the state is set to "Finished". * * If the IncrementalPublisher is ended early, it may be advanced directly from "Started" to "Finished". */ var IncrementalPublisherState; (function (IncrementalPublisherState) { IncrementalPublisherState[IncrementalPublisherState["Started"] = 1] = "Started"; IncrementalPublisherState[IncrementalPublisherState["Completed"] = 2] = "Completed"; IncrementalPublisherState[IncrementalPublisherState["Finished"] = 3] = "Finished"; })(IncrementalPublisherState || (IncrementalPublisherState = {})); /** * This class is used to publish incremental results to the client, enabling semi-concurrent * execution while preserving result order. * * @internal */ class IncrementalPublisher { _context; _nextId; _incrementalGraph; constructor(context) { this._context = context; this._nextId = 0; let deferredFragmentFactory = context.deferredFragmentFactory; if (deferredFragmentFactory === undefined) { context.deferredFragmentFactory = deferredFragmentFactory = new DeferredFragments_js_1.DeferredFragmentFactory(); } this._incrementalGraph = new IncrementalGraph_js_1.IncrementalGraph(deferredFragmentFactory); } buildResponse(data, errors, incrementalDataRecords) { const newRootNodes = this._incrementalGraph.getNewRootNodes(incrementalDataRecords); const initialResult = this._context .useIncrementalNotifications ? errors === undefined ? { data, pending: this._toPendingResults(newRootNodes), hasNext: true } : { errors, data, pending: this._toPendingResults(newRootNodes), hasNext: true } : errors === undefined ? { data, hasNext: true } : { errors, data, hasNext: true }; return { initialResult, subsequentResults: this._subscribe(), }; } _toPendingResults(newRootNodes) { const pendingResults = []; for (const node of newRootNodes) { const id = String(this._getNextId()); node.id = id; const pendingResult = { id, path: (0, utils_1.pathToArray)(node.path), }; if (node.label !== undefined) { pendingResult.label = node.label; } pendingResults.push(pendingResult); } return pendingResults; } _getNextId() { return String(this._nextId++); } _subscribe() { let incrementalPublisherState = IncrementalPublisherState.Started; const _finish = async () => { incrementalPublisherState = IncrementalPublisherState.Finished; this._incrementalGraph.abort(); await this._returnAsyncIterators(); }; this._context.signal?.addEventListener('abort', () => { this._incrementalGraph.abort(); }); const _next = async () => { switch (incrementalPublisherState) { case IncrementalPublisherState.Finished: { return { value: undefined, done: true }; } case IncrementalPublisherState.Completed: { await _finish(); return { value: undefined, done: true }; } case IncrementalPublisherState.Started: { // continue } } const context = { pending: [], incremental: [], completed: [], }; let batch = this._incrementalGraph.currentCompletedBatch(); do { for (const completedResult of batch) { this._handleCompletedIncrementalData(completedResult, context); } const { incremental, completed } = context; if (incremental.length > 0 || completed.length > 0) { const hasNext = this._incrementalGraph.hasNext(); if (!hasNext) { incrementalPublisherState = IncrementalPublisherState.Completed; } const subsequentIncrementalExecutionResult = { hasNext, }; const pending = context.pending; if (pending.length > 0) { subsequentIncrementalExecutionResult.pending = pending; } if (incremental.length > 0) { subsequentIncrementalExecutionResult.incremental = incremental; } if (completed.length > 0) { subsequentIncrementalExecutionResult.completed = completed; } return { value: subsequentIncrementalExecutionResult, done: false }; } batch = await this._incrementalGraph.nextCompletedBatch(); } while (batch !== undefined); if (this._context.signal?.aborted) { throw this._context.signal.reason; } return { value: undefined, done: true }; }; const _return = async () => { await _finish(); return { value: undefined, done: true }; }; const _throw = async (error) => { await _finish(); return Promise.reject(error); }; return { [Symbol.asyncIterator]() { return this; }, next: _next, return: _return, throw: _throw, }; } _handleCompletedIncrementalData(completedIncrementalData, context) { if ((0, types_js_1.isCompletedExecutionGroup)(completedIncrementalData)) { this._handleCompletedExecutionGroup(completedIncrementalData, context); } else { this._handleCompletedStreamItems(completedIncrementalData, context); } } _handleCompletedExecutionGroup(completedExecutionGroup, context) { const { deferUsages, path } = completedExecutionGroup.pendingExecutionGroup; if ((0, types_js_1.isFailedExecutionGroup)(completedExecutionGroup)) { for (const deferUsage of deferUsages) { const deferredFragmentRecord = this._incrementalGraph.removeDeferredFragment(deferUsage, path); if (deferredFragmentRecord === undefined) { // This can occur if multiple deferred grouped field sets error for a fragment. continue; } if (this._context.useIncrementalNotifications) { const id = deferredFragmentRecord.id; (0, invariant_js_1.invariant)(id !== undefined); context.completed.push({ id, errors: completedExecutionGroup.errors, }); } else { const incrementalEntry = { errors: completedExecutionGroup.errors, data: null, }; const { path, label } = deferredFragmentRecord; incrementalEntry.path = (0, utils_1.pathToArray)(path); if (label !== undefined) { incrementalEntry.label = label; } context.incremental.push(incrementalEntry); } } return; } this._incrementalGraph.addCompletedSuccessfulExecutionGroup(completedExecutionGroup); for (const deferUsage of deferUsages) { const completion = this._incrementalGraph.completeDeferredFragment(deferUsage, path); if (completion === undefined) { continue; } const incremental = context.incremental; const { deferredFragmentRecord, newRootNodes, successfulExecutionGroups } = completion; if (this._context.useIncrementalNotifications) { context.pending.push(...this._toPendingResults(newRootNodes)); for (const successfulExecutionGroup of successfulExecutionGroups) { const { deferUsages: resultDeferUsages, path: resultPath } = successfulExecutionGroup.pendingExecutionGroup; const bestDeferredFragmentRecord = this._incrementalGraph.getDeepestDeferredFragmentAtRoot(deferUsage, resultDeferUsages, resultPath); const bestId = bestDeferredFragmentRecord.id; (0, invariant_js_1.invariant)(bestId !== undefined); const incrementalEntry = { ...successfulExecutionGroup.result, id: bestId, }; const subPath = (0, utils_1.pathToArray)(resultPath).slice((0, utils_1.pathToArray)(bestDeferredFragmentRecord.path).length); if (subPath.length > 0) { incrementalEntry.subPath = subPath; } incremental.push(incrementalEntry); } const id = deferredFragmentRecord.id; (0, invariant_js_1.invariant)(id !== undefined); context.completed.push({ id }); } else { for (const successfulExecutionGroup of successfulExecutionGroups) { const incrementalEntry = { ...successfulExecutionGroup.result, }; const { path, label } = deferredFragmentRecord; incrementalEntry.path = (0, utils_1.pathToArray)(path); if (label !== undefined) { incrementalEntry.label = label; } incremental.push(incrementalEntry); } } } } _handleCompletedStreamItems(streamItemsResult, context) { const streamRecord = streamItemsResult.streamRecord; if (streamItemsResult.errors !== undefined) { if (this._context.useIncrementalNotifications) { const id = streamRecord.id; (0, invariant_js_1.invariant)(id !== undefined); context.completed.push({ id, errors: streamItemsResult.errors, }); } else { const incrementalEntry = { errors: streamItemsResult.errors, items: null, }; const { path, label, index } = streamRecord; incrementalEntry.path = (0, utils_1.pathToArray)((0, utils_1.addPath)(path, index, undefined)); if (label !== undefined) { incrementalEntry.label = label; } context.incremental.push(incrementalEntry); } this._incrementalGraph.removeStream(streamRecord); if ((0, types_js_1.isCancellableStreamRecord)(streamRecord)) { (0, invariant_js_1.invariant)(this._context.cancellableStreams !== undefined); this._context.cancellableStreams.delete(streamRecord); streamRecord.earlyReturn().catch(() => { /* c8 ignore next 1 */ // ignore error }); } } else if (streamItemsResult.result === undefined) { if (this._context.useIncrementalNotifications) { const id = streamRecord.id; (0, invariant_js_1.invariant)(id !== undefined); context.completed.push({ id }); } this._incrementalGraph.removeStream(streamRecord); if ((0, types_js_1.isCancellableStreamRecord)(streamRecord)) { (0, invariant_js_1.invariant)(this._context.cancellableStreams !== undefined); this._context.cancellableStreams.delete(streamRecord); } } else { const bareResult = streamItemsResult.result; const incrementalEntry = { ...bareResult, }; if (this._context.useIncrementalNotifications) { const id = streamRecord.id; (0, invariant_js_1.invariant)(id !== undefined); incrementalEntry.id = id; } else { const { path, label, index } = streamRecord; incrementalEntry.path = (0, utils_1.pathToArray)((0, utils_1.addPath)(path, index, undefined)); streamRecord.index += bareResult.items.length; if (label !== undefined) { incrementalEntry.label = label; } } context.incremental.push(incrementalEntry); const incrementalDataRecords = streamItemsResult.incrementalDataRecords; if (incrementalDataRecords !== undefined) { const newPending = this._incrementalGraph.getNewRootNodes(incrementalDataRecords); if (this._context.useIncrementalNotifications) { context.pending.push(...this._toPendingResults(newPending)); } } } } async _returnAsyncIterators() { await this._incrementalGraph.abort(); const cancellableStreams = this._context.cancellableStreams; if (cancellableStreams === undefined) { return; } const promises = []; for (const streamRecord of cancellableStreams) { if (streamRecord.earlyReturn !== undefined) { promises.push(streamRecord.earlyReturn()); } } await Promise.all(promises); } }