grafast
Version:
Cutting edge GraphQL planning and execution engine
1,020 lines • 46 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutputPlan = void 0;
exports.coerceError = coerceError;
exports.nonNullError = nonNullError;
exports.getChildBucketAndIndex = getChildBucketAndIndex;
exports.getDirectLayerPlanChild = getDirectLayerPlanChild;
const tslib_1 = require("tslib");
const debug_1 = tslib_1.__importDefault(require("debug"));
const graphql = tslib_1.__importStar(require("graphql"));
const assert = tslib_1.__importStar(require("../assert.js"));
const constants_ts_1 = require("../constants.js");
const dev_ts_1 = require("../dev.js");
const index_ts_1 = require("../index.js");
const inspect_ts_1 = require("../inspect.js");
const tamedevilUtils_ts_1 = require("../tamedevilUtils.js");
const utils_ts_1 = require("../utils.js");
const LayerPlan_ts_1 = require("./LayerPlan.js");
const debug = (0, debug_1.default)("grafast:OutputPlan");
const debugVerbose = debug.extend("verbose");
const { executeSync, GraphQLBoolean, GraphQLError, GraphQLFloat, GraphQLID, GraphQLInt, GraphQLString, isObjectType, Kind, OperationTypeNode, } = graphql;
/**
* Defines a way of taking a layerPlan and converting it into an output value.
*
* The result of executing an output plan will be the following:
*
* - data?: the data for this layer, could be object, array or leaf (see OutputPlanMode)
* - errors: a list of errors that occurred (if any), including path details _within the output plan_
* - streams: a list of streams that were created
*/
class OutputPlan {
constructor(layerPlan,
/**
* For root, this should always be an object.
*
* For object this could represent an object or null.
*
* For polymorphic this could represent a polymorphic object or null.
*
* For array it's the list itself (or null) and dictates the length of the result.
*
* For leaf, it's the leaf plan itself.
*
* For `introspection`, `null` it's irrelevant. Use `constant(null)` or whatever.
*/
rootStep, type, locationDetails) {
/**
* Appended to the root step when accessed to avoid the need for AccessSteps
*
* @internal
*/
this.processRoot = null;
/**
* For root/object output plans, the keys to set on the resulting object
* grouped by the concrete object type name.
*
* IMPORTANT: the order of these keys is significant, they MUST match the
* order in the request otherwise we break GraphQL spec compliance!
*/
this.keys = Object.create(null);
/**
* For list output plans, the output plan that describes the list children.
*/
this.child = null;
this.childIsNonNull = false;
/**
* For object output plan types only.
*/
this.deferredOutputPlans = [];
this.layerPlan = layerPlan;
this.type = type;
this.locationDetails = locationDetails;
this.rootStep = rootStep;
layerPlan.operationPlan.stepTracker.addOutputPlan(this);
this.childByTypeName =
this.type.mode === "polymorphic" ? Object.create(null) : undefined;
// NOTE: this may be cleared during `this.finalize()`
this.sideEffectStep = layerPlan.latestSideEffectStep;
}
print() {
const type = this.type;
switch (type.mode) {
case "root":
case "object": {
return `${this.toString()}\n${Object.entries(this.keys)
.map(([fieldName, val]) => {
return `.${fieldName}: ${val.type === "__typename" ? "" : val.isNonNull ? "!" : "?"}${val.type === "__typename"
? `__typename(${type.typeName})`
: val.outputPlan.print().replace(/\n/g, "\n ")}`;
})
.join("\n")}`;
}
case "polymorphic": {
return `${this.toString()}\n${Object.entries(this.childByTypeName)
.map(([typeName, outputPlan]) => {
return `? ${typeName}: ${outputPlan
.print()
.replace(/\n/g, "\n ")}`;
})
.join("\n")}`;
}
case "array": {
return `${this.toString()}\n ${this.childIsNonNull ? "!" : "?"}${this.child.print().replace(/\n/g, "\n ")}`;
}
default: {
return this.toString();
}
}
}
toString() {
let processRootString = "";
if (this.processRoot != null) {
const fnBody = this.processRoot.toString();
const VALUE_ARROW = "value => ";
if (fnBody.startsWith(VALUE_ARROW)) {
const sliced = fnBody.slice(VALUE_ARROW.length);
if (sliced.startsWith("(value") &&
sliced.endsWith(")") &&
sliced.replace(/[()]/g, "").length === sliced.length - 2) {
processRootString = sliced.slice(6, -1);
}
else {
processRootString = `:value=>${sliced}`;
}
}
else {
processRootString = `:${fnBody}`;
}
}
return `OutputPlan<${this.type.mode}${this.sideEffectStep ? `^${this.sideEffectStep.id}` : ""}∈${this.layerPlan.id}!${this.rootStep.id}${processRootString}>`;
}
// HACK: ensure output objects are ordered correctly
expectChild(type, key) {
if (this.type.mode === "root" || this.type.mode === "object") {
this.keys[key] = undefined; // placeholder
}
}
addChild(type, key, child) {
if (this.type.mode === "root" || this.type.mode === "object") {
if (dev_ts_1.isDev) {
if (typeof key !== "string") {
throw new Error(`GrafastInternalError<7334ec50-23dc-442a-8ffa-19664c9eb79f>: Key must be provided in ${this.type.mode} OutputPlan mode`);
}
if (type == null) {
throw new Error(`GrafastInternalError<638cebef-4ec6-49f4-b681-2f390fb1c0fc>: Type must be provided in ${this.type.mode} OutputPlan mode.`);
}
if (!isObjectType(type)) {
throw new Error(`GrafastInternalError<eaa87576-1d50-49be-990a-345a9b57b998>: Type must provided in ${this.type.mode} OutputPlan mode must be an object type, instead saw '${type}'.`);
}
assert.ok(["root", "object"].includes(this.type.mode), "Can only addChild on root/object output plans");
if (this.keys[key] !== undefined) {
throw new Error(`GrafastInternalError<5ceecb19-8c2c-4797-9be5-9be1b207fa45>: child already set for key '${key}'`);
}
}
this.keys[key] =
child.type === "outputPlan"
? {
...child,
layerPlanId: child.outputPlan.layerPlan.id,
}
: child;
}
else if (this.type.mode === "array") {
if (dev_ts_1.isDev) {
if (key != null) {
throw new Error(`GrafastInternalError<7de67325-a02f-4619-b118-61bb2d84f33b>: Key must not be provided in ${this.type.mode} OutputPlan mode`);
}
assert.ok(type == null, "Array should not specify type");
if (this.child !== null) {
throw new Error(`GrafastInternalError<07059d9d-a47d-441f-b834-683cca1d856a>: child already set`);
}
}
if (child.type === "outputPlan") {
this.child = child.outputPlan;
this.childIsNonNull = child.isNonNull;
}
else {
throw new Error(`GrafastInternalError<7525c854-9145-4c6d-8d60-79c14f040519>: Array child must be an outputPlan`);
}
}
else if (this.type.mode === "polymorphic") {
if (dev_ts_1.isDev) {
assert.ok(type && this.type.typeNames.includes(type.name), "GrafastInternalError<566a34ac-1138-4dbf-943e-f704819431dd>: polymorphic output plan can only addChild for a matching type");
assert.strictEqual(key, null, "GrafastInternalError<4346ebda-a02d-4489-b767-7a6d621a73c7>: addChild for polymorphic OutputPlan should not specify a key");
assert.ok(child.type === "outputPlan", "GrafastInternalError<b29285da-fb07-4943-9038-708edc785041>: polymorphic OutputPlan child must be an outputPlan");
assert.ok(child.outputPlan.type.mode === "object" ||
child.outputPlan.type.mode === "null", "GrafastInternalError<203469c6-4bfa-4cd1-ae82-cc5d0132ca16>: polymorphic OutputPlan child must be an object outputPlan");
}
this.childByTypeName[type.name] = child.outputPlan;
}
else {
throw new Error(`GrafastInternalError<5667df5f-30b7-48d3-be3f-a0065ed9c05c>: Doesn't make sense to set a child in mode '${this.type.mode}'`);
}
}
getLayerPlans(layerPlans = new Set()) {
// Find all the layerPlans referenced
layerPlans.add(this.layerPlan);
if (this.child !== null) {
if (this.child.layerPlan != this.layerPlan) {
this.child.getLayerPlans(layerPlans);
}
else {
throw new Error("GrafastInternalError<4013e05f-b8ed-41ea-a869-204232d02763>: how could the child be in the same layer?");
}
}
for (const key in this.keys) {
const spec = this.keys[key];
if (spec.type === "outputPlan") {
if (spec.outputPlan.layerPlan !== this.layerPlan) {
spec.outputPlan.getLayerPlans(layerPlans);
layerPlans.add(spec.outputPlan.layerPlan);
}
}
}
return layerPlans;
}
makeNextStepByLayerPlan() {
const layerPlans = this.getLayerPlans();
const map = Object.create(null);
for (const layerPlan of layerPlans) {
map[layerPlan.id] = [];
}
return map;
}
// This gets replaced with a mode-specific executor
execute(_root,
// By just reusing the same path over and over we don't need to allocate
// more memory for more arrays; but we must be _incredibly_ careful to
// ensure any changes to it are reversed.
_mutablePath, _bucket, _bucketIndex, _rawBucketRootValue, _bucketRootFlags) {
throw new Error(`OutputPlan.execute has yet to be built!`);
}
// This gets replaced with a mode-specific executor
executeString(_root,
// By just reusing the same path over and over we don't need to allocate
// more memory for more arrays; but we must be _incredibly_ careful to
// ensure any changes to it are reversed.
_mutablePath, _bucket, _bucketIndex, _rawBucketRootValue, _bucketRootFlags) {
throw new Error(`OutputPlan.executeString has yet to be built!`);
}
optimize() { }
finalize() {
// Clear the sideEffectStep if the rootStep explicitly depends on it.
// NOTE: this occurs here since more dependencies could have been added to
// $root after `this` was created.
const $sideEffect = this.sideEffectStep;
if ($sideEffect) {
const $root = this.rootStep;
if ($root === $sideEffect ||
$root.implicitSideEffectStep === $sideEffect ||
(0, index_ts_1.stepADependsOnStepB)($root, $sideEffect)) {
// It's marked readonly, but we override it anyway
this.sideEffectStep = null;
}
}
// Build the executor
switch (this.type.mode) {
case "null": {
this.execute = nullExecutor;
this.executeString = nullExecutorString;
break;
}
case "leaf": {
this.execute = leafExecutor;
if (this.type.graphqlType.serialize === GraphQLID.serialize ||
this.type.graphqlType.serialize === GraphQLString.serialize) {
// String types
this.executeString = stringLeafExecutorString;
}
else if (this.type.graphqlType.serialize === GraphQLInt.serialize) {
this.executeString = intLeafExecutorString;
}
else if (this.type.graphqlType.serialize === GraphQLFloat.serialize) {
this.executeString = floatLeafExecutorString;
}
else if (this.type.graphqlType.serialize === GraphQLBoolean.serialize) {
// Boolean type
this.executeString = booleanLeafExecutorString;
}
else {
// PERF: we could probably optimize enums too
this.executeString = leafExecutorString;
}
break;
}
case "introspection": {
this.execute = introspectionExecutor;
this.executeString = introspectionExecutorString;
break;
}
case "polymorphic": {
this.execute = polymorphicExecutor;
this.executeString = polymorphicExecutorString;
break;
}
case "array": {
if (!this.child) {
throw new Error("GrafastInternalError<48fabdc8-ce84-45ec-ac20-35a2af9098e0>: No child output plan for list bucket?");
}
const childIsNonNull = this.childIsNonNull;
const directLayerPlanChild = getDirectLayerPlanChild(this.layerPlan, this.child.layerPlan);
const canStream = directLayerPlanChild.reason.type === "listItem" &&
!!directLayerPlanChild.reason.stream;
if (childIsNonNull) {
if (canStream) {
this.execute = arrayExecutor_nonNullable_streaming;
this.executeString = arrayExecutorString_nonNullable_streaming;
}
else {
this.execute = arrayExecutor_nonNullable;
this.executeString = arrayExecutorString_nonNullable;
}
}
else {
if (canStream) {
this.execute = arrayExecutor_nullable_streaming;
this.executeString = arrayExecutorString_nullable_streaming;
}
else {
this.execute = arrayExecutor_nullable;
this.executeString = arrayExecutorString_nullable;
}
}
break;
}
case "root":
case "object": {
const type = this.type;
const digestFieldTypes = Object.create(null);
for (const [responseKey, spec] of Object.entries(this.keys)) {
digestFieldTypes[responseKey] = {
fieldType: spec.type === "__typename"
? "__typename"
: spec.isNonNull
? "outputPlan!"
: "outputPlan?",
sameBucket: spec.type === "__typename" ||
spec.outputPlan.layerPlan.id === this.layerPlan.id,
};
}
this.execute = makeObjectExecutor(type.typeName, digestFieldTypes, this.deferredOutputPlans.length > 0, type.mode === "root" || type.hasRootBehavior, false);
this.executeString = makeObjectExecutor(type.typeName, digestFieldTypes, this.deferredOutputPlans.length > 0, type.mode === "root" || type.hasRootBehavior, true);
break;
}
default: {
const never = this.type;
throw new Error(`GrafastInternalError<e88531b2-d9af-4d3a-8cd5-e9f034324341>: Could not build executor for OutputPlan with type ${(0, inspect_ts_1.inspect)(never)}}`);
}
}
}
}
exports.OutputPlan = OutputPlan;
function coerceError(error, locationDetails, path) {
// Ensure it's a GraphQL error
if (error instanceof GraphQLError) {
if (error.path !== undefined) {
return error;
}
else {
return new GraphQLError(error.message, {
nodes: locationDetails.node,
path,
originalError: error.originalError ?? error,
extensions: error.extensions,
});
}
}
else {
return new GraphQLError(error?.message ?? String(error), {
nodes: locationDetails.node,
path,
originalError: error,
});
}
}
function nonNullError(locationDetails, path) {
const { parentTypeName, fieldName, node } = locationDetails;
if (!parentTypeName || !fieldName) {
return new GraphQLError(`GrafastInternalError<a3706bba-4f88-4643-8a47-2fe2eaaadbea>: null bubbled to root`, { nodes: node, path });
}
return new GraphQLError(`Cannot return null for non-nullable field ${parentTypeName}.${fieldName}.`, { nodes: node, path });
}
/**
* We're currently running OutputPlan `outputPlan` in bucket `bucket` at index
* `bucketIndex`. If this is an array OutputPlan then we're looping over the
* entries in our list and we're currently at index `inArrayIndex` (otherwise
* this is null).
*
* Now we want to run `childOutputPlan`, but to do so we need to find the
* related `childBucket` and `childBucketIndex`.
*
* @internal
*/
function getChildBucketAndIndex(layerPlan, outputPlan, bucket, bucketIndex, arrayIndex = null) {
if (bucket.layerPlan === layerPlan) {
return [bucket, bucketIndex];
}
if ((arrayIndex == null) ===
(outputPlan != null && outputPlan.type.mode === "array")) {
throw new Error("GrafastInternalError<83d0e3cc-7eec-4185-85b4-846540288162>: arrayIndex must be supplied iff outputPlan is an array");
}
const paths = (0, utils_ts_1.pathsFromAncestorToTargetLayerPlan)(bucket.layerPlan, layerPlan);
if (paths.length === 0) {
return null;
}
toNextPath: for (const path of paths) {
let currentBucket = bucket;
let currentIndex = bucketIndex;
for (let i = 0, l = path.length; i < l; i++) {
const layerPlan = path[i];
const child = currentBucket.children[layerPlan.id];
if (!child) {
continue toNextPath;
}
const out = child.map.get(currentIndex);
if (out == null) {
continue toNextPath;
}
/*
* TEST: I think this '|| i !== l - 1' check is saying that an array can
* only occur at the furthest ancestor and everything since then must be
* non-array. Presumably in the case of nested arrays there would be an
* intermediary bucket, hence why this check is allowed, but that should be
* tested. Also, are there any confounding factors when it comes to steps
* themselves using arrays for object values - for example pgSelectSingle is
* represented by an array (tuple), but that doesn't make it a list so it should
* be fine. Use tests to validate this is all fine.
*/
if (arrayIndex == null || i !== 0) {
if (Array.isArray(out)) {
throw new Error("GrafastInternalError<db189d32-bf8f-4e58-b55f-5c5ac3bb2381>: Was expecting an arrayIndex, but none was provided");
}
currentBucket = child.bucket;
currentIndex = out;
}
else {
if (!Array.isArray(out)) {
throw new Error(`GrafastInternalError<8190d09f-dc75-46ec-8162-b20ad516de41>: Cannot access array index in non-array ${(0, inspect_ts_1.inspect)(out)}`);
}
if (!(out.length > arrayIndex)) {
throw new Error(`GrafastInternalError<1f596c22-368b-4d0d-94df-fb3df632b064>: Attempted to retrieve array index '${arrayIndex}' which is out of bounds of array with length '${out.length}'`);
}
currentBucket = child.bucket;
currentIndex = out[arrayIndex];
}
}
if (currentIndex == null) {
continue toNextPath;
}
return [currentBucket, currentIndex];
}
return null;
}
function makeExecutor(config) {
const { preamble = null, inner, asString, nameExtra, skipNullHandling = false, } = config;
const fn = function (root, mutablePath, bucket, bucketIndex, rawBucketRootValue = bucket.store.get(this.rootStep.id).at(bucketIndex), bucketRootFlags = bucket.store.get(this.rootStep.id)._flagsAt(bucketIndex)) {
if (this.sideEffectStep !== null) {
const sideEffectBucketDetails = getChildBucketAndIndex(this.sideEffectStep.layerPlan, null, bucket, bucketIndex);
if (sideEffectBucketDetails) {
const [sideEffectBucket, sideEffectBucketIndex] = sideEffectBucketDetails;
const ev = sideEffectBucket.store.get(this.sideEffectStep.id);
if (!ev) {
throw new Error(`GrafastInternalError<da9cc5a0-23bf-4af8-a103-92f995795398>: ${(0, index_ts_1.stripAnsi)(String(this.sideEffectStep))} has no entry in ${bucket}`);
}
const seFlags = ev._flagsAt(sideEffectBucketIndex);
if (seFlags & constants_ts_1.FLAG_ERROR) {
const seVal = ev.at(sideEffectBucketIndex);
throw coerceError(seVal, this.locationDetails, mutablePath.slice(1));
}
}
}
if (bucketRootFlags & constants_ts_1.FLAG_ERROR) {
throw coerceError(rawBucketRootValue, this.locationDetails, mutablePath.slice(1));
}
const bucketRootValue = this.processRoot !== null
? this.processRoot(rawBucketRootValue, bucketRootFlags)
: rawBucketRootValue;
const earlyReturn = preamble?.call(this, bucketRootValue);
if (earlyReturn !== undefined) {
return earlyReturn;
}
if (!skipNullHandling) {
if (bucketRootValue == null)
return (asString ? "null" : null);
}
return inner.call(this, bucketRootValue, root, mutablePath, bucket, bucketIndex, rawBucketRootValue, bucketRootFlags);
};
fn.displayName = `outputPlan${asString ? "String" : ""}_${nameExtra}`;
return fn;
}
function executeChildPlan(that, locationDetails, childOutputPlan, isNonNull, asString, childBucket, childBucketIndex, bucket, bucketIndex, mutablePath, mutablePathIndex, root, rawBucketRootValue, bucketRootFlags) {
const streamCount = root.streams.length;
const queueCount = root.queue.length;
try {
const $sideEffect = childOutputPlan.layerPlan.parentSideEffectStep;
if ($sideEffect !== null) {
// Check if there's an error
const sideEffectBucketDetails = getChildBucketAndIndex($sideEffect.layerPlan, null, bucket, bucketIndex);
if (sideEffectBucketDetails) {
const [sideEffectBucket, sideEffectBucketIndex] = sideEffectBucketDetails;
const ev = sideEffectBucket.store.get($sideEffect.id);
if (!ev) {
throw new Error(`GrafastInternalError<d18d52b5-f5bf-42df-a2dd-80e522310b8e>: ${(0, index_ts_1.stripAnsi)(String($sideEffect))} has no entry in ${bucket}`);
}
const flags = ev._flagsAt(sideEffectBucketIndex);
if (flags & constants_ts_1.FLAG_ERROR) {
throw coerceError(ev.at(sideEffectBucketIndex), locationDetails, mutablePath.slice(1));
}
}
}
if (childBucket !== bucket && childBucket == null) {
if (isNonNull) {
throw nonNullError(locationDetails, mutablePath.slice(1));
}
return asString ? "null" : null;
}
else {
const fieldResult = childOutputPlan[asString ? "executeString" : "execute"](root, mutablePath, childBucket, childBucketIndex,
// NOTE: the previous code may have had a bug here, it referenced childBucket.rootStep
childOutputPlan.rootStep === that.rootStep
? rawBucketRootValue
: undefined, childOutputPlan.rootStep === that.rootStep
? bucketRootFlags
: undefined);
if (isNonNull && fieldResult == (asString ? "null" : null)) {
throw nonNullError(locationDetails, mutablePath.slice(1));
}
return fieldResult;
}
}
catch (e) {
const error = coerceError(e, locationDetails, mutablePath.slice(1));
if (root.errorBehavior === "HALT")
throw error;
if (isNonNull && root.errorBehavior !== "NULL")
throw error;
if (root.streams.length > streamCount) {
root.streams.splice(streamCount);
}
if (root.queue.length > queueCount) {
root.queue.splice(queueCount);
}
const pathLengthTarget = mutablePathIndex + 1;
const overSize = mutablePath.length - pathLengthTarget;
if (overSize > 0) {
mutablePath.splice(pathLengthTarget, overSize);
}
root.errors.push(error);
return asString ? "null" : null;
}
}
const nullExecutor = makeExecutor({
inner: () => null,
nameExtra: "null",
asString: false,
});
const nullExecutorString = makeExecutor({
inner: () => "null",
nameExtra: `null`,
asString: true,
});
const leafExecutor = makeExecutor({
inner(bucketRootValue) {
return this.type.graphqlType.serialize(bucketRootValue);
},
nameExtra: `leaf`,
asString: false,
});
const leafExecutorString = makeExecutor({
inner(bucketRootValue) {
return (0, tamedevilUtils_ts_1.stringifyJSON)(this.type.graphqlType.serialize(bucketRootValue));
},
nameExtra: `leaf`,
asString: true,
});
const booleanLeafExecutorString = makeExecutor({
inner(bucketRootValue) {
const val = this.type.graphqlType.serialize(bucketRootValue);
return val === true ? "true" : "false";
},
nameExtra: `booleanLeaf`,
asString: true,
skipNullHandling: false,
preamble(bucketRootValue) {
if (bucketRootValue === true)
return "true";
if (bucketRootValue === false)
return "false";
},
});
const intLeafExecutorString = makeExecutor({
inner(bucketRootValue) {
return "" + this.type.graphqlType.serialize(bucketRootValue);
},
nameExtra: `intLeaf`,
asString: true,
skipNullHandling: false,
// Fast check to see if number is 32 bit integer
preamble(bucketRootValue) {
if ((bucketRootValue | 0) === bucketRootValue) {
return "" + bucketRootValue;
}
},
});
const floatLeafExecutorString = makeExecutor({
inner(bucketRootValue) {
return String(this.type.graphqlType.serialize(bucketRootValue));
},
nameExtra: `floatLeaf`,
asString: true,
skipNullHandling: false,
preamble(bucketRootValue) {
if (Number.isFinite(bucketRootValue)) {
return "" + bucketRootValue;
}
},
});
const stringLeafExecutorString = makeExecutor({
inner(bucketRootValue) {
return (0, tamedevilUtils_ts_1.stringifyString)(this.type.graphqlType.serialize(bucketRootValue));
},
nameExtra: `stringLeaf`,
asString: true,
skipNullHandling: false,
preamble(bucketRootValue) {
if (typeof bucketRootValue === "string") {
return (0, tamedevilUtils_ts_1.stringifyString)(bucketRootValue);
}
},
});
// TODO: do we need this or can we skip it?
function makePolymorphicExecutor(asString) {
return makeExecutor({
inner(bucketRootValue, root, mutablePath, bucket, bucketIndex) {
const typeName = bucketRootValue;
if (dev_ts_1.isDev) {
assert.ok(typeName, "GrafastInternalError<fd3f3cf0-0789-4c74-a6cd-839c808896ed>: Could not determine concreteType for object");
}
const childOutputPlan = this.childByTypeName[typeName];
if (!childOutputPlan) {
// Search: InvalidConcreteTypeName
console.warn(`Invalid object type name for this abstract position${this.locationDetails.parentTypeName && this.locationDetails.fieldName ? ` at ${this.locationDetails.parentTypeName}.${this.locationDetails.fieldName}` : ""}; saw %o, but expected one of ${this.type.typeNames.join(", ")}`, typeName);
return (asString ? "null" : null);
}
const directChild = bucket.children[childOutputPlan.layerPlan.id];
if (directChild !== undefined) {
return childOutputPlan[asString ? "executeString" : "execute"](root, mutablePath, directChild.bucket, directChild.map.get(bucketIndex));
}
else {
const c = getChildBucketAndIndex(childOutputPlan.layerPlan, this, bucket, bucketIndex);
if (!c) {
// TODO: Implies that the bucket didn't run; is this a normal
// situation? We should be able to detect it and handle it more
// cleanly.
if (dev_ts_1.isDev) {
debugVerbose(`GrafastInternalWarning<691509d8-31fa-4cfe-a6df-dcba21bd521f>: polymorphic executor couldn't determine child bucket. childOutputPlan=%c, childOutputPlan.layerPlan=%c, bucket=%c`, childOutputPlan, childOutputPlan.layerPlan, bucket);
}
return (asString ? "null" : null);
}
const [childBucket, childBucketIndex] = c;
return childOutputPlan[asString ? "executeString" : "execute"](root, mutablePath, childBucket, childBucketIndex);
}
},
nameExtra: "polymorphic",
asString,
});
}
const polymorphicExecutor = makePolymorphicExecutor(false);
const polymorphicExecutorString = makePolymorphicExecutor(true);
function makeArrayExecutor(childIsNonNull, canStream, asString) {
return makeExecutor({
inner(bucketRootValue, root, mutablePath, bucket, bucketIndex, rawBucketRootValue, bucketRootFlags) {
if (!Array.isArray(bucketRootValue)) {
console.warn(`${this} Hit fallback for value ${(0, inspect_ts_1.inspect)(bucketRootValue)} coercion to mode 'array'`);
return (asString ? "null" : null);
}
if (this.child === null) {
throw new Error(`GrafastInternalError<365fd45e-2939-411b-92a6-4f6875d8fbd3>: ${this} has no child output plan?!`);
}
const childOutputPlan = this.child;
const l = bucketRootValue.length;
let string;
let data;
if (l === 0) {
if (asString) {
string = "[]";
}
else {
data = [];
}
}
else {
if (asString) {
string = "[";
}
else {
data = [];
}
const mutablePathIndex = mutablePath.push(-1) - 1;
// Now to populate the children...
const directChild = bucket.children[childOutputPlan.layerPlan.id];
let childBucket, childBucketIndex, lookup;
if (directChild !== undefined) {
childBucket = directChild.bucket;
lookup = directChild.map.get(bucketIndex);
}
for (let i = 0; i < l; i++) {
if (directChild !== undefined) {
childBucketIndex = lookup[i];
}
else {
const c = getChildBucketAndIndex(childOutputPlan.layerPlan, this, bucket, bucketIndex, i);
if (c !== null) {
[childBucket, childBucketIndex] = c;
}
else {
childBucket = childBucketIndex = null;
}
}
mutablePath[mutablePathIndex] = i;
if (asString) {
if (i > 0) {
string += ",";
}
}
const val = executeChildPlan(this, this.locationDetails, childOutputPlan, childIsNonNull, asString, childBucket, childBucketIndex, bucket, bucketIndex, mutablePath, mutablePathIndex, root, rawBucketRootValue, bucketRootFlags);
if (asString) {
string += val;
}
else {
data[i] = val;
}
}
mutablePath.length = mutablePathIndex;
if (asString) {
string += "]";
}
}
if (canStream) {
const stream = bucketRootValue[constants_ts_1.$$streamMore];
if (stream !== undefined) {
const labelStepId = childOutputPlan.layerPlan.reason.stream?.labelStepId;
root.streams.push({
root,
path: mutablePath.slice(1),
bucket,
bucketIndex,
outputPlan: childOutputPlan,
label: labelStepId != null
? bucket.store.get(labelStepId)?.unaryValue()
: undefined,
stream,
startIndex: bucketRootValue.length,
});
}
}
return (asString ? string : data);
},
nameExtra: `array${childIsNonNull ? "_nonNull" : ""}${canStream ? "_stream" : ""}`,
asString,
});
}
const arrayExecutor_nullable = makeArrayExecutor(false, false, false);
const arrayExecutor_nullable_streaming = makeArrayExecutor(false, true, false);
const arrayExecutor_nonNullable = makeArrayExecutor(true, false, false);
const arrayExecutor_nonNullable_streaming = makeArrayExecutor(true, true, false);
const arrayExecutorString_nullable = makeArrayExecutor(false, false, true);
const arrayExecutorString_nullable_streaming = makeArrayExecutor(false, true, true);
const arrayExecutorString_nonNullable = makeArrayExecutor(true, false, true);
const arrayExecutorString_nonNullable_streaming = makeArrayExecutor(true, true, true);
/**
* This piggy-backs off of GraphQL.js by rewriting the request, executing it in
* GraphQL.js, and then patching it into our response. It's a temporary
* workaround until we can afford the time to write our own introspection
* execution.
*
* ENHANCE: write our own introspection execution!
*/
function introspect(root, outputPlan, mutablePath, asString) {
const { locationDetails } = outputPlan;
const { field: rawField, introspectionCacheByVariableValues, variableNames, } = outputPlan.type;
const field = {
...rawField,
alias: { kind: Kind.NAME, value: "a" },
};
const document = {
definitions: [
{
kind: Kind.OPERATION_DEFINITION,
operation: OperationTypeNode.QUERY,
selectionSet: {
kind: Kind.SELECTION_SET,
selections: [field],
},
variableDefinitions: outputPlan.layerPlan.operationPlan.operation.variableDefinitions?.filter((d) => variableNames.includes(d.variable.name.value)),
},
...Object.values(outputPlan.layerPlan.operationPlan.fragments),
],
kind: Kind.DOCUMENT,
};
const variableValues = Object.create(null);
const sortedVariableNames = [...variableNames].sort();
for (const variableName of sortedVariableNames) {
variableValues[variableName] = root.variables[variableName];
}
// "canonical" only to one level, but given introspection doesn't really
// accept objects this should be mostly sufficient for decent optimization
// level.
const canonical = JSON.stringify(variableValues);
const cached = introspectionCacheByVariableValues.get(canonical);
if (cached !== undefined) {
return (asString ? JSON.stringify(cached) : cached);
}
const graphqlResult = executeSync({
schema: outputPlan.layerPlan.operationPlan.schema,
document,
variableValues,
});
if (graphqlResult.errors !== undefined) {
// NOTE: we should map the introspection path, however that might require
// us to be able to raise multiple errors. Theoretically if the query
// validates we shouldn't be able to get errors out of the introspection
// system, so we'll skip over this for now. When we get around to
// implementing introspection natively in Grafast rather than piggy-backing
// off of GraphQL.js then this problem will go away on its own.
console.error("INTROSPECTION FAILED!");
console.error(graphqlResult);
const { node } = locationDetails;
throw new GraphQLError("INTROSPECTION FAILED!", {
nodes: node,
path: mutablePath.slice(1),
});
}
const result = graphqlResult.data.a;
introspectionCacheByVariableValues.set(canonical, result);
return (asString ? JSON.stringify(result) : result);
}
const introspectionExecutor = makeExecutor({
inner(_bucketRootValue, root, mutablePath) {
return introspect(root, this, mutablePath, false);
},
nameExtra: `introspection`,
asString: false,
skipNullHandling: true,
});
const introspectionExecutorString = makeExecutor({
inner(_bucketRootValue, root, mutablePath) {
return introspect(root, this, mutablePath, true);
},
nameExtra: `introspection`,
asString: true,
skipNullHandling: true,
});
const SAFE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
function makeObjectExecutor(typeName, fieldTypes, hasDeferredOutputPlans,
// this.type.mode === "root",
isRoot, asString) {
if (!SAFE_NAME.test(typeName)) {
throw new Error(`Unsafe type name: ${typeName}; doesn't conform to 'Name' in the GraphQL spec`);
}
if (dev_ts_1.isDev) {
if (Object.getPrototypeOf(fieldTypes) !== null) {
throw new Error(`GrafastInternalError<3d8e3547-d818-44e1-a076-16b828e3a34d>: fieldTypes must have a null prototype`);
}
}
const fieldDigests = Object.entries(fieldTypes).map(([responseKey, fieldSpec], i) => {
// NOTE: this code relies on the fact that fieldName and typeName do
// not require any quoting in JSON/JS - they must conform to GraphQL
// `Name`.
if (!SAFE_NAME.test(responseKey)) {
// This should not be able to happen if the GraphQL operation is valid
throw new Error(`Unsafe key: ${responseKey}; doesn't conform to 'Name' in the GraphQL spec`);
}
const { fieldType, sameBucket } = fieldSpec;
const addComma = asString && i > 0;
switch (fieldType) {
case "__typename": {
return {
isTypeName: true,
addComma,
responseKey,
stringValue: asString ? `"${responseKey}":"${typeName}"` : null,
};
}
case "outputPlan!":
case "outputPlan?": {
return {
isTypeName: false,
addComma,
responseKey,
stringPrefix: asString ? `"${responseKey}":` : null,
sameBucket,
isNonNull: fieldType === "outputPlan!",
};
}
default: {
const never = fieldType;
throw new Error(`GrafastInternalError<879082f4-fe6f-4112-814f-852b9932ca83>: unsupported key type ${never}`);
}
}
});
return makeExecutor({
inner(bucketRootValue, root, mutablePath, bucket, bucketIndex, rawBucketRootValue, bucketRootFlags) {
let string = asString ? "{" : undefined;
const obj = asString
? undefined
: Object.create(null);
const keys = this.keys;
const mutablePathIndex = mutablePath.push("!") - 1;
for (const digest of fieldDigests) {
if (digest.addComma) {
string += ",";
}
const responseKey = digest.responseKey;
if (digest.isTypeName) {
if (asString) {
string += digest.stringValue;
}
else {
obj[responseKey] = typeName;
}
}
else {
mutablePath[mutablePathIndex] = responseKey;
const spec = keys[responseKey];
if (digest.sameBucket) {
const val = executeChildPlan(this, spec.locationDetails, spec.outputPlan, digest.isNonNull, asString, bucket, bucketIndex, bucket, bucketIndex, mutablePath, mutablePathIndex, root, rawBucketRootValue, bucketRootFlags);
if (asString) {
string += digest.stringPrefix;
string += val;
}
else {
obj[responseKey] = val;
}
}
else {
const directChild = bucket.children[spec.outputPlan.layerPlan.id];
let childBucket, childBucketIndex;
if (directChild !== undefined) {
childBucket = directChild.bucket;
childBucketIndex = directChild.map.get(bucketIndex);
}
else {
const c = getChildBucketAndIndex(spec.outputPlan.layerPlan, this, bucket, bucketIndex);
if (c !== null) {
[childBucket, childBucketIndex] = c;
}
else {
childBucket = childBucketIndex = null;
}
}
const val = executeChildPlan(this, spec.locationDetails, spec.outputPlan, digest.isNonNull, asString, childBucket, childBucketIndex, bucket, bucketIndex, mutablePath, mutablePathIndex, root, rawBucketRootValue, bucketRootFlags);
if (asString) {
string += digest.stringPrefix;
string += val;
}
else {
obj[responseKey] = val;
}
}
}
}
mutablePath.length = mutablePathIndex;
if (asString) {
string += "}";
}
if (hasDeferredOutputPlans) {
// Everything seems okay; queue any deferred payloads
for (const defer of this.deferredOutputPlans) {
root.queue.push({
root,
path: mutablePath.slice(1),
bucket,
bucketIndex,
outputPlan: defer,
label: defer.type.deferLabel,
});
}
}
return (asString ? string : obj);
},
nameExtra: "object",
asString,
skipNullHandling: isRoot,
});
}
/**
* Finds the child of `targetParent` that has a path towards `descendent` (by
* walking backwards from descendent to targetParent).
*/
function getDirectLayerPlanChild(targetParent, descendent) {
const child = _getDirectLayerPlanChild(targetParent, descendent);
if (child == null) {
throw new Error(`GrafastInternalError<d43e06d8-c533-4e7b-b3e7-af399f19c83f>: Invalid hierarchy - could not find direct layerPlan child of ${targetParent} that leads to ${descendent}`);
}
return child;
}
function _getDirectLayerPlanChild(targetParent, descendent) {
let directLayerPlanChild = descendent;
let parent;
while ((0, LayerPlan_ts_1.hasParentLayerPlan)(directLayerPlanChild.reason) &&
(parent = directLayerPlanChild.reason.parentLayerPlan) !== targetParent) {
directLayerPlanChild = parent;
}
if (directLayerPlanChild.reason.type === "combined") {
for (const plp of directLayerPlanChild.reason.parentLayerPlans) {
const dlpc = _getDirectLayerPlanChild(targetParent, plp);
if (dlpc)
return dlpc;
}
return null;
}
else if (directLayerPlanChild.reason.type === "root") {
return null;
}
else {
return directLayerPlanChild;
}
}
//# sourceMappingURL=OutputPlan.js.map