UNPKG

grafast

Version:

Cutting edge GraphQL planning and execution engine

939 lines 65.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.executeBucket = executeBucket; exports.newBucket = newBucket; exports.bucketToString = bucketToString; exports.batchExecutionValue = batchExecutionValue; exports.unaryExecutionValue = unaryExecutionValue; const tslib_1 = require("tslib"); const iterall_1 = require("iterall"); const assert = tslib_1.__importStar(require("../assert.js")); const constants_ts_1 = require("../constants.js"); const dev_ts_1 = require("../dev.js"); const error_ts_1 = require("../error.js"); const inspect_ts_1 = require("../inspect.js"); const timeSource_ts_1 = require("../timeSource.js"); const utils_ts_1 = require("../utils.js"); const distributor_ts_1 = require("./distributor.js"); /** Default recoverable/trappable flags (excluding NULL) */ const INHIBIT_OR_ERROR_FLAGS = constants_ts_1.FLAG_INHIBITED | constants_ts_1.FLAG_ERROR; const timeoutError = Object.freeze(new error_ts_1.SafeError("Execution timeout exceeded, please simplify or add limits to your request.", Object.freeze({ [constants_ts_1.$$timeout]: true }))); function noop() { /*noop*/ } /** * Takes a list of `results` (shorter than `resultCount`) and an object with * errors and indexes; returns a list of length `resultCount` with the results * from `results` but with errors injected at the indexes specified in * `errors`. * * ASSERT: `results.length + Object.values(errors).length === resultCount` * * @internal */ function mergeErrorsBackIn(results, forcedValues, resultCount) { const finalFlags = []; const finalResults = []; /** The index within `results`, which is shorter than `resultCount` */ let resultIndex = 0; for (let finalIndex = 0; finalIndex < resultCount; finalIndex++) { const flags = forcedValues.flags[finalIndex]; if (flags !== undefined) { const value = forcedValues.results[finalIndex]; finalResults[finalIndex] = value; finalFlags[finalIndex] = flags; } else { finalResults[finalIndex] = results[resultIndex++]; finalFlags[finalIndex] = 0; } } return { flags: finalFlags, results: finalResults }; } /** @internal */ function executeBucket(bucket, requestContext) { const distributorOptions = (0, distributor_ts_1.resolveDistributorOptions)(requestContext.args.resolvedPreset?.grafast); /** * Execute the step directly; since there's no errors we can pass the * dependencies through verbatim. */ function reallyExecuteStepWithoutFiltering(size, step, dependencies, extra) { const results = executeOrStream(size, step, dependencies, extra); const flags = (0, utils_ts_1.arrayOfLength)(size, constants_ts_1.NO_FLAGS); if ((0, utils_ts_1.isPromiseLike)(results)) { return results.then((results) => ({ flags, results })); } else { return { flags, results }; } } const { stopTime, eventEmitter, args } = requestContext; const { middleware } = args; const { metaByMetaKey, size, store, layerPlan: { phases }, } = bucket; const phaseCount = phases.length; // Like a `for(i = 0; i < phaseCount; i++)` loop with some `await`s in it, except it does promise // handling manually so that it can complete synchronously (no promises) if // possible. const nextPhase = (phaseIndex) => { if (phaseIndex >= phaseCount) { return; } const phase = phases[phaseIndex]; const { _allSteps } = phase; /** * To ensure we don't enter a situation where an "unhandled" promise * rejection causes Node to exit, we must process each completed step during the * same tick in which it completes. */ let indexesPendingLoopOver = []; let executePromises = null; let executePromiseResultIndex = null; const resultList = []; if (phase.checkTimeout && stopTime !== null && timeSource_ts_1.timeSource.now() >= stopTime) { // ABORT! if (phase.normalSteps !== undefined) { const normalSteps = phase.normalSteps; for (let normalStepIndex = 0, l = normalSteps.length; normalStepIndex < l; normalStepIndex++) { const step = normalSteps[normalStepIndex].step; const stepSize = step._isUnary ? 1 : bucket.size; const r = timeoutError; const results = (0, utils_ts_1.arrayOfLength)(stepSize, r); const flags = (0, utils_ts_1.arrayOfLength)(stepSize, constants_ts_1.FLAG_ERROR); resultList[normalStepIndex] = { flags, results }; indexesPendingLoopOver.push(normalStepIndex); // TODO: I believe we can remove this line? bucket.flagUnion |= constants_ts_1.FLAG_ERROR; } } } else if (phase.normalSteps !== undefined) { const normalSteps = phase.normalSteps; for (let normalStepIndex = 0, l = normalSteps.length; normalStepIndex < l; normalStepIndex++) { const step = normalSteps[normalStepIndex].step; const stepSize = step._isUnary ? 1 : bucket.size; try { const r = executeStep(step); if ((0, utils_ts_1.isPromiseLike)(r)) { resultList[normalStepIndex] = undefined /* will populate shortly */; if (!executePromises) { executePromises = [r]; executePromiseResultIndex = [normalStepIndex]; } else { const newIndex = executePromises.push(r) - 1; executePromiseResultIndex[newIndex] = normalStepIndex; } } else { resultList[normalStepIndex] = r; indexesPendingLoopOver.push(normalStepIndex); } } catch (e) { const r = e; const results = (0, utils_ts_1.arrayOfLength)(stepSize, r); const flags = (0, utils_ts_1.arrayOfLength)(stepSize, constants_ts_1.FLAG_ERROR); resultList[normalStepIndex] = { flags, results }; indexesPendingLoopOver.push(normalStepIndex); // TODO: I believe we can remove this line? bucket.flagUnion |= constants_ts_1.FLAG_ERROR; } } } const next = () => { return nextPhase(phaseIndex + 1); }; /** * Loops over (and resets) the indexesPendingLoopOver array, ensuring that * all errors are handled. */ const loopOverResults = () => { if (indexesPendingLoopOver.length === 0) return; const indexesToProcess = indexesPendingLoopOver; indexesPendingLoopOver = []; // Validate executed steps for (const allStepsIndex of indexesToProcess) { const finishedStep = _allSteps[allStepsIndex]; const internalResult = resultList[allStepsIndex]; if (!internalResult) { throw new Error(`GrafastInternalError<166ef53d-b80d-4bea-8b54-803c2694112a>: Result from ${finishedStep} should exist`); } const { /* flags, */ results } = internalResult; const resultLength = results?.length; const expectedSize = finishedStep._isUnary ? 1 : size; if (resultLength !== expectedSize) { if (!Array.isArray(results)) { throw new Error(`Result from ${finishedStep} should be an array, instead received ${(0, inspect_ts_1.inspect)(results, { colors: true })}`); } throw new Error(`Result array from ${finishedStep} should have length ${expectedSize}${finishedStep._isUnary ? " (because it's unary)" : ""}, instead it had length ${results.length}`); } if (finishedStep._isUnary) { // Handled later } else { bucket.store.set(finishedStep.id, batchExecutionValue((0, utils_ts_1.arrayOfLength)(size))); } } // Need to complete promises, check for errors, etc. // **DO NOT THROW, DO NOT ALLOW AN ERROR TO BE RAISED!** // **USE DEFENSIVE PROGRAMMING HERE!** /** PROMISES ADDED HERE MUST NOT REJECT */ let promises = undefined; // TODO: it seems that if this throws an error it results in a permanent // hang of defers? In the mean time... Don't throw any errors here! const success = (finishedStep, bucket, resultIndex, rawValue, flags) => { // Fast lanes if (rawValue == null) { // null / undefined const finalFlags = flags | constants_ts_1.FLAG_NULL; bucket.setResult(finishedStep, resultIndex, rawValue, finalFlags); return; } else if (typeof rawValue !== "object") { // non-objects bucket.setResult(finishedStep, resultIndex, rawValue, flags); return; } else if ((0, error_ts_1.isFlaggedValue)(rawValue)) { // flagged values const finalFlags = flags | rawValue.flags; bucket.setResult(finishedStep, resultIndex, rawValue.value, finalFlags); return; } // PERF: do we want to handle arrays differently? let valueIsIterable = false; let valueIsAsyncIterable = false; if (finishedStep.cloneStreams || finishedStep._stepOptions.walkIterable) { valueIsIterable = (0, iterall_1.isIterable)(rawValue); valueIsAsyncIterable = !valueIsIterable && (0, iterall_1.isAsyncIterable)(rawValue); } let stream = null; if (finishedStep._stepOptions.walkIterable && (valueIsAsyncIterable || valueIsIterable)) { // PERF: we've already calculated this once; can we reference that again here? stream = evaluateStream(bucket, finishedStep, distributorOptions); } if (!stream && !valueIsAsyncIterable && Array.isArray(rawValue)) { // Fast mode const value = rawValue; const valueCount = value.length; /** We only create a duplicate array if the source array contains promises */ let replacement = null; for (let i = 0; i < valueCount; i++) { const item = value[i]; if ((0, utils_ts_1.isPromiseLike)(item)) { if (replacement === null) { // Copy up to here! replacement = value.slice(0, i); } replacement[i] = null; const index = i; // Must not reject const promise = item.then((v) => void (replacement[index] = v), (e) => void (replacement[index] = (0, error_ts_1.flagError)(e))); if (promises === undefined) { promises = [promise]; } else { promises.push(promise); } } else if (replacement !== null) { replacement[i] = item; } } const list = replacement === null ? value : replacement; bucket.setResult(finishedStep, resultIndex, list, flags); return; } const willConsumeAsIterator = finishedStep._stepOptions.walkIterable && (valueIsAsyncIterable || valueIsIterable); /** * If 'cloneStreams' is set, we clone iff it's an iterable and we're * not walking it ourselves. */ const shouldUseDistributor = finishedStep.cloneStreams && (valueIsIterable || valueIsAsyncIterable) && !Array.isArray(rawValue) && // A single `__ItemStep` is fine, but more than that and we need a distributor finishedStep.dependents.length > 1; if (shouldUseDistributor) { if (finishedStep._stepOptions.walkIterable) { const error = new Error(`GrafastInternalError<ea0665f6-1b5c-4f7f-bcf0-92ddc112dcf3>: ${finishedStep} needs to be walked (it is used for a list field or subscription), but should use a distributor (it's dependend on by other steps too). We should be using a wrapping 'cloneStream' step for this, but we don't seem to be doing so.`); bucket.setResult(finishedStep, resultIndex, error, flags | constants_ts_1.FLAG_ERROR); } else { const value = (0, distributor_ts_1.distributor)(rawValue, finishedStep.dependents.map((d) => d.step), requestContext.abortSignal, distributorOptions); // TODO: add distributor to cleanup bucket.setResult(finishedStep, resultIndex, value, flags); } } else if (willConsumeAsIterator) { const value = rawValue; const initialCount = stream?.initialCount ?? Infinity; let iterator; try { iterator = valueIsAsyncIterable ? value[Symbol.asyncIterator]() : value[Symbol.iterator](); } catch (e) { bucket.setResult(finishedStep, resultIndex, e, flags | constants_ts_1.FLAG_ERROR); return; } // Here we track the iterator via the bucket, this allows us to // ensure that the iterator is terminated even if the stream is never // consumed (e.g. if an error is thrown/caught during execution of // the output plan). if (!bucket.iterators[resultIndex]) { bucket.iterators[resultIndex] = new Set(); } bucket.iterators[resultIndex].add(iterator); if (initialCount === 0) { // Optimization - defer everything const arr = []; arr[constants_ts_1.$$streamMore] = iterator; bucket.setResult(finishedStep, resultIndex, arr, flags); } else { // Evaluate the first initialCount entries, rest is streamed. const promise = (async () => { try { let valuesSeen = 0; const arr = []; /* * We need to "shift" a few entries off the top of the * iterator, but still keep it iterable for the later * stream. To accomplish this we have to do manual * looping */ let resultPromise; while ((resultPromise = iterator.next())) { const resolvedResult = await resultPromise; if (resolvedResult.done) { break; } try { const v = resolvedResult.value; if ((0, utils_ts_1.isPromiseLike)(v)) { arr.push(await v); } else { arr.push(v); } } catch (e) { arr.push((0, error_ts_1.flagError)(e)); } if (++valuesSeen >= initialCount) { // This is safe to do in the `while` since we checked // the `0` entries condition in the optimization // above. arr[constants_ts_1.$$streamMore] = iterator; break; } } bucket.setResult(finishedStep, resultIndex, arr, flags); } catch (e) { bucket.setResult(finishedStep, resultIndex, e, flags | constants_ts_1.FLAG_ERROR); } })(); if (promises === undefined) { promises = [promise]; } else { promises.push(promise); } } } else { bucket.setResult(finishedStep, resultIndex, rawValue, flags); } }; for (const allStepsIndex of indexesToProcess) { const step = _allSteps[allStepsIndex]; const internalResult = resultList[allStepsIndex]; if (!internalResult) { throw new Error(`GrafastInternalError<b82038d6-ca4e-4b55-8ed4-4d7c879f5dc2>: Result from ${step} should exist`); } const { flags, results } = internalResult; const count = step._isUnary ? 1 : size; for (let dataIndex = 0; dataIndex < count; dataIndex++) { const val = results[dataIndex]; const valFlags = flags[dataIndex]; if (valFlags == null) { throw new Error(`GraphileInternalError<75df71bb-0f76-4a98-9664-9167d502296a>: result for ${step} has no flag at index ${dataIndex} (value = ${(0, inspect_ts_1.inspect)(val)})`); } if (step.isSyncAndSafe || !(0, utils_ts_1.isPromiseLike)(val)) { success(step, bucket, dataIndex, val, valFlags); } else { // Must not reject! const valSuccess = val.then((val) => success(step, bucket, dataIndex, val, valFlags), (error) => bucket.setResult(step, dataIndex, error, constants_ts_1.FLAG_ERROR)); if (promises === undefined) { promises = [valSuccess]; } else { promises.push(valSuccess); } } } } return promises === undefined ? undefined : Promise.all(promises); }; const runSyncSteps = () => { const executedLength = resultList.length; if (dev_ts_1.isDev) { assert.strictEqual(executedLength, phase.normalSteps?.length ?? 0, "Expected only and all normalSteps to have executed"); } if (!phase.unbatchedSyncAndSafeSteps) { return next(); } const allStepsLength = _allSteps.length; const extras = []; for (let allStepsIndex = executedLength; allStepsIndex < allStepsLength; allStepsIndex++) { const step = _allSteps[allStepsIndex]; const meta = step.metaKey !== undefined ? metaByMetaKey[step.metaKey] : undefined; extras[allStepsIndex] = { stopTime, meta, eventEmitter, stream: evaluateStream(bucket, step, distributorOptions), _bucket: bucket, _requestContext: requestContext, }; if (step._isUnary) { // Handled later } else { bucket.store.set(step.id, batchExecutionValue((0, utils_ts_1.arrayOfLength)(size))); } } for (let dataIndex = 0; dataIndex < size; dataIndex++) { stepLoop: for (let allStepsIndex = executedLength; allStepsIndex < allStepsLength; allStepsIndex++) { const step = (0, utils_ts_1.sudo)(_allSteps[allStepsIndex]); // Unary steps only need to be processed once if (step._isUnary && dataIndex !== 0) { continue; } // Check if the side effect errored const $sideEffect = step.implicitSideEffectStep; if ($sideEffect) { let currentPolymorphicPath; if ($sideEffect.polymorphicPaths === null || (currentPolymorphicPath = bucket.polymorphicPathList[dataIndex]) === null || $sideEffect.polymorphicPaths.has(currentPolymorphicPath)) { const depExecutionValue = bucket.store.get($sideEffect.id); if (depExecutionValue === undefined) { throw new Error(`GrafastInternalError<fcc8d302-ac66-40e8-aaea-ee2c7e2b30b2>: failed to get result for side effect ${$sideEffect} which impacts ${step}`); } const depFlags = depExecutionValue._flagsAt(dataIndex); if (depFlags & constants_ts_1.FLAG_POLY_SKIPPED) { /* noop */ } else if (depFlags & constants_ts_1.FLAG_ERROR) { if (step._isUnary) { // COPY the unary value bucket.store.set(step.id, depExecutionValue); bucket.flagUnion |= depFlags; } else { const depVal = depExecutionValue.at(dataIndex); bucket.setResult(step, dataIndex, depVal, depFlags); } continue stepLoop; } } } try { const deps = []; const extra = extras[allStepsIndex]; let forceIndexValue = undefined; let rejectValue = undefined; let indexFlags = constants_ts_1.NO_FLAGS; // Check polymorphism matches const stepPolymorphicPaths = step.polymorphicPaths; if (stepPolymorphicPaths !== null && (step._isUnary ? /* * already asserted that it matches; * !! search "f4ce2c83" */ false : /* batch check */ !stepPolymorphicPaths.has(bucket.polymorphicPathList[dataIndex]))) { indexFlags |= constants_ts_1.FLAG_POLY_SKIPPED; forceIndexValue = null; } else { for (let i = 0, l = step.dependencies.length; i < l; i++) { const $dep = step.dependencies[i]; const forbiddenFlags = step.dependencyForbiddenFlags[i]; const onReject = step.dependencyOnReject[i]; const depExecutionVal = bucket.store.get($dep.id); if (depExecutionVal === undefined) { throw new Error(`GrafastInternalError<480e7c98-a777-4efb-b826-c339129ccff8>: could not find value in ${bucket} for ${$dep}, dependency ${i} of ${step}`); } // Search for "f2b3b1b3" for similar block const flags = depExecutionVal._flagsAt(dataIndex); const disallowedFlags = flags & forbiddenFlags; if (disallowedFlags) { indexFlags |= disallowedFlags; // If there's a reject behavior and we're FRESHLY rejected (weren't // already inhibited), use that as a fallback. // TODO: validate this. // If dep is inhibited and we don't allow inhibited, copy through (null or error). // If dep is inhibited and we do allow inhibited, but we're disallowed, use our onReject. // If dep is not inhibited, but we're disallowed, use our onReject. if (onReject !== undefined && (disallowedFlags & INHIBIT_OR_ERROR_FLAGS) === constants_ts_1.NO_FLAGS) { rejectValue ||= onReject; } if (forceIndexValue == null) { if ((flags & constants_ts_1.FLAG_ERROR) !== constants_ts_1.NO_FLAGS) { const v = depExecutionVal.at(dataIndex); forceIndexValue = v; } else { forceIndexValue = null; } } else { // First error wins, ignore this second error. } // End "f2b3b1b3" block } else if (forceIndexValue === undefined) { const depVal = depExecutionVal.at(dataIndex); let depFlags; if ((bucket.flagUnion & constants_ts_1.FLAG_ERROR) === constants_ts_1.FLAG_ERROR && ((depFlags = depExecutionVal._flagsAt(dataIndex)) & constants_ts_1.FLAG_ERROR) === constants_ts_1.FLAG_ERROR) { if (step._isUnary) { // COPY the unary value bucket.store.set(step.id, depExecutionVal); bucket.flagUnion |= depFlags; } else { bucket.setResult(step, dataIndex, depVal, constants_ts_1.FLAG_ERROR); } continue stepLoop; } if ($dep.cloneStreams) { // if (isDistributor(depVal)) { // deps.push(depVal.iterableFor(step.id)); // } const err = new Error(`It's not safe for an unbatched isSyncAndSafe step (${step}) to consume a step that has cloneStreams=true (${$dep})`); if (step._isUnary) { const uev = unaryExecutionValue(err, constants_ts_1.FLAG_ERROR); bucket.store.set(step.id, uev); bucket.flagUnion |= constants_ts_1.FLAG_ERROR; } else { bucket.setResult(step, dataIndex, err, constants_ts_1.FLAG_ERROR); } continue stepLoop; } else { deps.push(depVal); } } } } let stepResult, stepFlags; if (forceIndexValue !== undefined) { // Search for "17217999b7a7" for similar block if ((indexFlags & constants_ts_1.FLAG_POLY_SKIPPED) === constants_ts_1.FLAG_POLY_SKIPPED) { // Already skipped } else if (forceIndexValue == null && rejectValue != null) { indexFlags |= constants_ts_1.FLAG_ERROR; forceIndexValue = rejectValue; } else { indexFlags |= constants_ts_1.FLAG_INHIBITED; } // End "17217999b7a7" block stepResult = forceIndexValue; stepFlags = indexFlags; } else { const rawStepResult = step.unbatchedExecute(extra, ...deps); if (typeof rawStepResult === "object" && rawStepResult !== null && (0, error_ts_1.isFlaggedValue)(rawStepResult)) { stepResult = rawStepResult.value; stepFlags = rawStepResult.flags; } else { stepResult = rawStepResult; stepFlags = rawStepResult == null ? constants_ts_1.FLAG_NULL : constants_ts_1.NO_FLAGS; } } // TODO: what if stepResult is _returned_ error (as opposed to // thrown)? // NOTE: we are in `runSyncSteps` so this step is guaranteed to // be "isSyncAndSafe". As such, we don't need to worry about it // returning an error (unsafe) or a promise (async), we only // need to check if it's null. bucket.setResult(step, dataIndex, stepResult, stepFlags); } catch (e) { bucket.setResult(step, dataIndex, e, constants_ts_1.FLAG_ERROR); } } } // Note: we don't need to release the distributors for sync steps because // we specifically forbid isSyncAndSafe steps from having distributors as // deps. return next(); }; if (executePromises !== null) { const processedPromises = []; if (indexesPendingLoopOver.length > 0) { // This **must be done in the same tick** const promiseOrNot = loopOverResults(); if ((0, utils_ts_1.isPromiseLike)(promiseOrNot)) { processedPromises.push(promiseOrNot); } } for (let i = 0, l = executePromises.length; i < l; i++) { const executePromise = executePromises[i]; const index = executePromiseResultIndex[i]; processedPromises.push(executePromise.then((promiseResult) => { resultList[index] = promiseResult; indexesPendingLoopOver.push(index); // We must loop over the results in the same tick in which the // promise resolved. return loopOverResults(); })); } return Promise.all(processedPromises).then(runSyncSteps); } else { const promiseOrNot = loopOverResults(); if ((0, utils_ts_1.isPromiseLike)(promiseOrNot)) { return promiseOrNot.then(runSyncSteps); } else { return runSyncSteps(); } } }; const promise = nextPhase(0); if ((0, utils_ts_1.isPromiseLike)(promise)) { return promise.then(executeSamePhaseChildren); } else { return executeSamePhaseChildren(); } // Function definitions below here function executeOrStream(count, step, values, extra) { if (dev_ts_1.isDev) { if (step._isUnary && count !== 1) { throw new Error(`GrafastInternalError<84a6cdfa-e8fe-4dea-85fe-9426a6a78027>: ${step} is a unary step, but we're attempting to pass it ${count} (!= 1) values`); } if (step.execute.length > 1) { throw new Error(`${step} is using a legacy form of 'execute' which accepts multiple arguments, please see https://err.red/gev2`); } } const executeDetails = { indexMap: makeIndexMap(count), indexForEach: makeIndexForEach(count), count, values, extra, stream: evaluateStream(bucket, step, distributorOptions), }; if (!step.isSyncAndSafe && middleware != null) { return middleware.run("executeStep", { args, step, executeDetails }, executeStepFromEvent); } else { return step.execute(executeDetails); } } // Slow mode... /** * Execute the step, filtering out errors and entries with non-matching * polymorphicPaths from the input dependencies and then padding the lists * back out at the end. */ function reallyExecuteStepWithFiltering(step, dependenciesIncludingSideEffects, dependencyForbiddenFlags, dependencyOnReject, polymorphicPathList, extra) { const expectedSize = step._isUnary ? 1 : size; const forcedValues = { flags: (0, utils_ts_1.arrayOfLength)(expectedSize, undefined), results: (0, utils_ts_1.arrayOfLength)(expectedSize, undefined), }; /** * If there's errors/forbidden values, we must manipulate the arrays being * passed into the step execution */ let needsTransform = false; /** If all we see is errors, there's no need to execute! */ let newSize = 0; const newPolymorphicPathList = []; let stepPolymorphicPaths = step.polymorphicPaths; const legitDepsCount = (0, utils_ts_1.sudo)(step).dependencies.length; const dependenciesIncludingSideEffectsCount = dependenciesIncludingSideEffects.length; let dependencies = dependenciesIncludingSideEffectsCount > legitDepsCount ? dependenciesIncludingSideEffects.slice(0, legitDepsCount) : dependenciesIncludingSideEffects; // OPTIM: if unariesIncludingSideEffects.some(isGrafastError) then shortcut execution because everything fails let hasPolyMatch = true; if (step._isUnary && stepPolymorphicPaths !== null) { // Check that at least one datapoint matches one of our paths hasPolyMatch = false; for (let dataIndex = 0; dataIndex < size; dataIndex++) { if (stepPolymorphicPaths.has(polymorphicPathList[dataIndex])) { hasPolyMatch = true; break; } } stepPolymorphicPaths = null; } for (let dataIndex = 0; dataIndex < expectedSize; dataIndex++) { let forceIndexValue = hasPolyMatch ? undefined : null; let rejectValue = undefined; let indexFlags = hasPolyMatch ? constants_ts_1.NO_FLAGS : constants_ts_1.FLAG_POLY_SKIPPED; if (stepPolymorphicPaths !== null && !stepPolymorphicPaths.has(polymorphicPathList[dataIndex])) { indexFlags |= constants_ts_1.FLAG_POLY_SKIPPED; forceIndexValue = null; } else if (extra._bucket.flagUnion !== constants_ts_1.NO_FLAGS) { for (let i = 0; i < dependenciesIncludingSideEffectsCount; i++) { const depExecutionVal = dependenciesIncludingSideEffects[i]; const forbiddenFlags = dependencyForbiddenFlags[i]; const onReject = dependencyOnReject[i]; // Search for "f2b3b1b3" for similar block const flags = depExecutionVal._flagsAt(dataIndex); const disallowedFlags = flags & forbiddenFlags; if (disallowedFlags) { indexFlags |= disallowedFlags; // If there's a reject behavior and we're FRESHLY rejected (weren't // already inhibited), use that as a fallback. // TODO: validate this. // If dep is inhibited and we don't allow inhibited, copy through (null or error). // If dep is inhibited and we do allow inhibited, but we're disallowed, use our onReject. // If dep is not inhibited, but we're disallowed, use our onReject. if (onReject !== undefined && (disallowedFlags & (constants_ts_1.FLAG_INHIBITED | constants_ts_1.FLAG_ERROR)) === constants_ts_1.NO_FLAGS) { rejectValue ||= onReject; } if (forceIndexValue == null) { if ((flags & constants_ts_1.FLAG_ERROR) !== 0) { const v = depExecutionVal.at(dataIndex); forceIndexValue = v; } else { forceIndexValue = null; } } else { // First error wins, ignore this second error. } // End "f2b3b1b3" block break; } } } else { // All good } if (forceIndexValue !== undefined) { if (!needsTransform) { needsTransform = true; // Clone up until this point, make mutable, don't add self dependencies = dependencies.map((ev) => ev.isBatch ? // TODO: move this creation to happen once the full list is // already built, ideally we shouldn't be mutating an execution // value later. batchExecutionValue(ev.entries.slice(0, dataIndex)) : ev); } // Search for "17217999b7a7" for similar block if (forceIndexValue == null && rejectValue != null) { indexFlags |= constants_ts_1.FLAG_ERROR; forceIndexValue = rejectValue; } else { indexFlags |= constants_ts_1.FLAG_INHIBITED; } // End "17217999b7a7" block forcedValues.flags[dataIndex] = indexFlags; forcedValues.results[dataIndex] = forceIndexValue; } else { const newIndex = newSize++; newPolymorphicPathList[newIndex] = polymorphicPathList[dataIndex]; if (needsTransform) { // dependenciesWithoutErrors has limited content; add this non-error value for (let depListIndex = 0; depListIndex < legitDepsCount; depListIndex++) { const depList = dependencies[depListIndex]; if (depList.isBatch) { const depVal = dependenciesIncludingSideEffects[depListIndex]; depList.entries.push(depVal.at(dataIndex)); depList._flags.push(depVal._flagsAt(dataIndex)); } } } } } if (newSize === 0) { // Everything is errors; we can skip execution return forcedValues; } else if (needsTransform) { const resultWithoutErrors = executeOrStream(newSize, step, dependencies, extra); if ((0, utils_ts_1.isPromiseLike)(resultWithoutErrors)) { return resultWithoutErrors.then((r) => mergeErrorsBackIn(r, forcedValues, expectedSize)); } else { return mergeErrorsBackIn(resultWithoutErrors, forcedValues, expectedSize); } } else { if (dev_ts_1.isDev) { assert.ok(newSize === expectedSize, "GrafastInternalError<47fca4ab-069c-428f-8374-267479c7fd27>: Expected newSize to equal expectedSize"); } return reallyExecuteStepWithoutFiltering(newSize, step, dependencies, extra); } } /** * This function MIGHT throw or reject, so be sure to handle that. */ function executeStep(step) { // DELIBERATE SHADOWING! const size = step._isUnary ? 1 : bucket.size; try { const meta = step.metaKey !== undefined ? metaByMetaKey[step.metaKey] : undefined; const extra = { stopTime, meta, eventEmitter, stream: evaluateStream(bucket, step, distributorOptions), _bucket: bucket, _requestContext: requestContext, }; /** Only mutate this inside `addDependency` */ const _rawDependencies = []; const _rawForbiddenFlags = []; const _rawOnReject = []; const dependencies = _rawDependencies; let needsFiltering = false; const defaultForbiddenFlags = (0, utils_ts_1.sudo)(step).defaultForbiddenFlags; const addDependency = ($dep, forbiddenFlags, onReject) => { if (step._isUnary && !$dep._isUnary) { throw new Error(`GrafastInternalError<58bc38e2-8722-4c19-ba38-fd01a020654b>: unary step ${step} cannot be made dependent on non-unary step ${$dep}!`); } const rawExecutionValue = store.get($dep.id); if (rawExecutionValue === undefined) { throw new Error(`GrafastInternalError<d9e9eb37-4251-4659-a545-4730826ecf0e>: ${$dep} data couldn't be found, but required by ${step} (with side effect ${step.implicitSideEffectStep})!`); } let executionValue; if ($dep.cloneStreams) { // Need to check if the EV contains distributors if (rawExecutionValue.isBatch) { const firstDistributorIndex = rawExecutionValue.entries.findIndex(distributor_ts_1.isDistributor); if (firstDistributorIndex >= 0) { const entries = []; for (let i = 0; i < size; i++) { const val = rawExecutionValue.entries[i]; if (i < firstDistributorIndex || !(0, distributor_ts_1.isDistributor)(val)) { entries.push(val); } else { entries.push(val.iterableFor(step.id)); } } executionValue = batchExecutionValue(entries, rawExecutionValue._flags); } else { executionValue = rawExecutionValue; } } else { if ((0, distributor_ts_1.isDistributor)(rawExecutionValue.value)) { executionValue = unaryExecutionValue(rawExecutionValue.value.iterableFor(step.id), rawExecutionValue._entryFlags); } else { executionValue = rawExecutionValue; } } } else { executionValue = rawExecutionValue; } _rawDependencies.push(executionValue); _rawForbiddenFlags.push(forbiddenFlags); _rawOnReject.push(onReject); if (!needsFiltering && (bucket.flagUnion & forbiddenFlags) !== 0) { const flags = executionValue._getStateUnion(); needsFiltering = (flags & forbiddenFlags) !== 0; } }; const sstep = (0, utils_ts_1.sudo)(step); const depCount = sstep.dependencies.length; if (depCount > 0) { for (let i = 0, l = depCount; i < l; i++) { const $dep = sstep.dependencies[i]; addDependency($dep, sstep.dependencyForbiddenFlags[i], sstep.dependencyOnReject[i]); } } const $sideEffect = step.implicitSideEffectStep; if ($sideEffect) { if ($sideEffect._isUnary || !step._isUnary) { addDependency($sideEffect, defaultForbiddenFlags, undefined); } } if (dev_ts_1.isDev && (step.layerPlan.reason.type === "polymorphic" || step.layerPlan.reason.type === "polymorphicPartition") && step.polymorphicPaths === null) { throw new Error(`GrafastInternalError<c33328fe-6758-4699-8ac6-7be41ce58bfb>: a step without polymorphic paths cannot belong to a polymorphic bucket`); } needsFiltering ||= step._isSelectiveStep; const result = needsFiltering ? reallyExecuteStepWithFiltering(step, dependencies, _rawForbiddenFlags, _rawOnReject, bucket.polymorphicPathList, extra) : reallyExecuteStepWithoutFiltering(size, step, $sideEffect ? dependencies.slice(0, depCount) : dependencies, extra); if ((0, utils_ts_1.isPromiseLike)(result)) { return result.then(null, (error) => { // Don't need to do this here, it will be done where the // ExecutionValue is created: // bucket.hasNonZeroStatus = true; return { flags: (0, utils_ts_1.arrayOfLength)(size, constants_ts_1.FLAG_ERROR | constants_ts_1.FLAG_STOPPED), results: (0, utils_ts_1.arrayOfLength)(size, error), }; }); } else { return result; } } catch (error) { // Don't need to do this here, it will be done where the // ExecutionValue is created: // bucket.hasNonZeroStatus = true; return { flags: (0, utils_ts_1.arrayOfLength)(size, constants_ts_1.FLAG_ERROR | constants_ts_1.FLAG_STOPPED), results: (0, utils_ts_1.arrayOfLength)(size, error), }; } } function executeSamePhaseChildren() { const result = completeBucketAndExecuteSamePhaseChildren(bucket, requestContext); if (result != null) { return result.then(() => { // PERF: this seems like a bad idea! We're forcing the bucket to be // retain