grafast
Version:
Cutting edge GraphQL planning and execution engine
939 lines • 65.3 kB
JavaScript
"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