UNPKG

graphql

Version:

A Query Language and Runtime which can target any service.

381 lines 13.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createWorkQueue = createWorkQueue; const isPromise_ts_1 = require("../../jsutils/isPromise.js"); const Queue_ts_1 = require("./Queue.js"); function createWorkQueue(initialWork) { const rootGroups = new Set(); const rootStreams = new Set(); const groupNodes = new Map(); const taskNodes = new Map(); let pushGraphEvent; let stopGraphEvents; const { newGroups: initialRootGroups, newStreams: initialRootStreams } = maybeIntegrateWork(initialWork); const nonEmptyInitialRootGroups = pruneEmptyGroups(initialRootGroups); for (const group of nonEmptyInitialRootGroups) { rootGroups.add(group); } for (const stream of initialRootStreams) { rootStreams.add(stream); } const events = new Queue_ts_1.Queue(({ push: _push, stop: _stop, onStop, started }) => { pushGraphEvent = _push; stopGraphEvents = _stop; started.then(() => { for (const group of rootGroups) { startGroup(group); } for (const stream of rootStreams) { startStream(stream); } }); onStop((reason) => cancel(reason)); }, 1).subscribe((graphEvents) => handleGraphEvents(graphEvents)); return { initialGroups: nonEmptyInitialRootGroups, initialStreams: initialRootStreams, events, }; function cancel(reason) { const cancelPromises = []; for (const group of rootGroups) { cancelGroup(group, reason, cancelPromises); } for (const stream of rootStreams) { cancelStream(stream, reason, cancelPromises); } if (cancelPromises.length > 0) { return Promise.allSettled(cancelPromises).then(() => undefined); } } function cancelGroup(group, reason, cancelPromises) { const groupNode = groupNodes.get(group); if (groupNode) { for (const task of groupNode.tasks) { cancelTask(task, reason, cancelPromises); } for (const childGroup of groupNode.childGroups) { cancelGroup(childGroup, reason, cancelPromises); } } } function cancelTask(task, reason, cancelPromises) { const abortResult = task.computation.abort(reason); if ((0, isPromise_ts_1.isPromise)(abortResult)) { cancelPromises.push(abortResult); } const taskNode = taskNodes.get(task); if (taskNode) { for (const childStream of taskNode.childStreams) { cancelStream(childStream, reason, cancelPromises); } } } function cancelStream(stream, reason, cancelPromises) { const abortResult = stream.queue.abort(reason); if ((0, isPromise_ts_1.isPromise)(abortResult)) { cancelPromises.push(abortResult); } } function maybeIntegrateWork(work, parentTask) { if (!work) { return { newGroups: [], newStreams: [] }; } const { groups, tasks, streams } = work; const newGroups = groups ? addGroups(groups, parentTask) : []; if (tasks) { for (const task of tasks) { addTask(task); } } const newStreams = streams ? addStreams(streams, parentTask) : []; return { newGroups, newStreams }; } function addGroups(originalGroups, parentTask) { const groupSet = new Set(originalGroups); const visited = new Set(); const newRootGroups = []; for (const group of originalGroups) { addGroup(group, groupSet, newRootGroups, visited, parentTask); } return newRootGroups; } function addGroup(group, groupSet, newRootGroups, visited, parentTask) { if (visited.has(group)) { return; } visited.add(group); const parent = group.parent; if (parent !== undefined && groupSet.has(parent)) { addGroup(parent, groupSet, newRootGroups, visited, parentTask); } const groupNode = { childGroups: [], tasks: new Set(), pending: 0, }; groupNodes.set(group, groupNode); if (parentTask === undefined && !parent) { newRootGroups.push(group); } else if (parent) { groupNodes.get(parent)?.childGroups.push(group); } } function addTask(task) { for (const group of task.groups) { const groupNode = groupNodes.get(group); if (groupNode) { groupNode.tasks.add(task); groupNode.pending++; if (rootGroups.has(group)) { startTask(task); } } } } function addStreams(streams, parentTask) { if (!parentTask) { return streams; } const taskNode = taskNodes.get(parentTask); if (taskNode) { taskNode.childStreams.push(...streams); } return []; } function pruneEmptyGroups(newGroups, nonEmptyNewGroups = []) { for (const newGroup of newGroups) { const newGroupState = groupNodes.get(newGroup); if (newGroupState) { if (newGroupState.pending === 0) { groupNodes.delete(newGroup); pruneEmptyGroups(newGroupState.childGroups, nonEmptyNewGroups); } else { nonEmptyNewGroups.push(newGroup); } } } return nonEmptyNewGroups; } function startNewWork(newGroups, newStreams) { for (const group of newGroups) { rootGroups.add(group); startGroup(group); } for (const stream of newStreams) { rootStreams.add(stream); startStream(stream); } } function startGroup(group) { const groupNode = groupNodes.get(group); if (groupNode) { for (const task of groupNode.tasks) { startTask(task); } } } function startTask(task) { if (taskNodes.has(task)) { return; } taskNodes.set(task, { value: undefined, childStreams: [], }); try { const result = task.computation.result(); if ((0, isPromise_ts_1.isPromise)(result)) { result.then((resolved) => { pushGraphEvent({ kind: 'TASK_SUCCESS', task, result: resolved }); }, (error) => { pushGraphEvent({ kind: 'TASK_FAILURE', task, error }); }); } else { pushGraphEvent({ kind: 'TASK_SUCCESS', task, result }); } } catch (error) { pushGraphEvent({ kind: 'TASK_FAILURE', task, error }); } } async function startStream(stream) { try { await stream.queue.forEachBatch(async (items) => { const pushed = pushGraphEvent({ kind: 'STREAM_ITEMS', stream, items, }); if ((0, isPromise_ts_1.isPromise)(pushed)) { await pushed; } }); pushGraphEvent({ kind: 'STREAM_SUCCESS', stream }); } catch (error) { pushGraphEvent({ kind: 'STREAM_FAILURE', stream, error }); } } function handleGraphEvents(graphEvents) { const workQueueEvents = []; for (const graphEvent of graphEvents) { switch (graphEvent.kind) { case 'TASK_SUCCESS': workQueueEvents.push(...taskSuccess(graphEvent)); break; case 'TASK_FAILURE': workQueueEvents.push(...taskFailure(graphEvent)); break; case 'STREAM_ITEMS': workQueueEvents.push(...streamItems(graphEvent)); break; case 'STREAM_SUCCESS': if (rootStreams.has(graphEvent.stream)) { rootStreams.delete(graphEvent.stream); workQueueEvents.push(graphEvent); } break; case 'STREAM_FAILURE': rootStreams.delete(graphEvent.stream); workQueueEvents.push(graphEvent); break; } } if (rootGroups.size === 0 && rootStreams.size === 0) { stopGraphEvents(); workQueueEvents.push({ kind: 'WORK_QUEUE_TERMINATION' }); } return workQueueEvents.length > 0 ? workQueueEvents : undefined; } function taskSuccess(graphEvent) { const { task, result } = graphEvent; const { value, work } = result; const taskNode = taskNodes.get(task); if (taskNode) { taskNode.value = value; } maybeIntegrateWork(work, task); const groupEvents = []; const newGroups = []; const newStreams = []; for (const group of task.groups) { const groupNode = groupNodes.get(group); if (groupNode) { groupNode.pending--; if (rootGroups.has(group) && groupNode.pending === 0) { const { groupValuesEvent, groupSuccessEvent, newGroups: childNewGroups, newStreams: childNewStreams, } = finishGroupSuccess(group, groupNode); if (groupValuesEvent) { groupEvents.push(groupValuesEvent); } groupEvents.push(groupSuccessEvent); newGroups.push(...childNewGroups); newStreams.push(...childNewStreams); } } } startNewWork(newGroups, newStreams); return groupEvents; } function taskFailure(graphEvent) { const { task, error } = graphEvent; taskNodes.delete(task); const groupFailureEvents = []; for (const group of task.groups) { const groupNode = groupNodes.get(group); if (groupNode) { groupFailureEvents.push(finishGroupFailure(group, groupNode, error)); } } return groupFailureEvents; } function streamItems(graphEvent) { const { stream, items } = graphEvent; const values = []; const newGroups = []; const newStreams = []; for (const { value, work } of items) { const { newGroups: itemNewGroups, newStreams: itemNewStreams } = maybeIntegrateWork(work); const nonEmptyNewGroups = pruneEmptyGroups(itemNewGroups); startNewWork(nonEmptyNewGroups, itemNewStreams); values.push(value); newGroups.push(...nonEmptyNewGroups); newStreams.push(...itemNewStreams); } const streamValuesEvent = { kind: 'STREAM_VALUES', stream, values, newGroups, newStreams, }; if (stream.queue.isStopped()) { rootStreams.delete(stream); return [streamValuesEvent, { kind: 'STREAM_SUCCESS', stream }]; } return [streamValuesEvent]; } function finishGroupSuccess(group, groupNode) { groupNodes.delete(group); const values = []; const newStreams = []; for (const task of groupNode.tasks) { const taskNode = taskNodes.get(task); if (taskNode) { const { value, childStreams } = taskNode; if (value !== undefined) { values.push(value); } for (const childStream of childStreams) { newStreams.push(childStream); } removeTask(task); } } const newGroups = pruneEmptyGroups(groupNode.childGroups); rootGroups.delete(group); return { groupValuesEvent: values.length ? { kind: 'GROUP_VALUES', group, values } : undefined, groupSuccessEvent: { kind: 'GROUP_SUCCESS', group, newGroups, newStreams, }, newGroups, newStreams, }; } function finishGroupFailure(group, groupNode, error) { removeGroup(group, groupNode); rootGroups.delete(group); return { kind: 'GROUP_FAILURE', group, error }; } function removeGroup(group, groupNode) { groupNodes.delete(group); for (const task of groupNode.tasks) { if (task.groups.every((taskGroup) => !groupNodes.has(taskGroup))) { removeTask(task); } } for (const childGroup of groupNode.childGroups) { const childGroupState = groupNodes.get(childGroup); if (childGroupState) { removeGroup(childGroup, childGroupState); } } } function removeTask(task) { for (const group of task.groups) { const groupNode = groupNodes.get(group); groupNode?.tasks.delete(task); } taskNodes.delete(task); } } //# sourceMappingURL=WorkQueue.js.map