grafast
Version:
Cutting edge GraphQL planning and execution engine
266 lines • 10.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__FlagStep = exports.TRAP_ERROR_OR_INHIBITED = exports.TRAP_INHIBITED = exports.TRAP_ERROR = void 0;
exports.inhibitOnNull = inhibitOnNull;
exports.assertNotNull = assertNotNull;
exports.trap = trap;
const constants_js_1 = require("../constants.js");
const error_js_1 = require("../error.js");
const inspect_js_1 = require("../inspect.js");
const step_js_1 = require("../step.js");
const utils_js_1 = require("../utils.js");
// PUBLIC FLAGS
exports.TRAP_ERROR = constants_js_1.FLAG_ERROR;
exports.TRAP_INHIBITED = constants_js_1.FLAG_INHIBITED;
exports.TRAP_ERROR_OR_INHIBITED = (constants_js_1.FLAG_ERROR |
constants_js_1.FLAG_INHIBITED);
function digestAcceptFlags(acceptFlags) {
const parts = [];
if ((acceptFlags & constants_js_1.FLAG_NULL) === 0) {
parts.push("rejectNull");
}
if ((acceptFlags & constants_js_1.FLAG_ERROR) !== 0) {
parts.push("trapError");
}
if ((acceptFlags & constants_js_1.FLAG_INHIBITED) !== 0) {
parts.push("trapInhibited");
}
return parts.join("&");
}
const TRAP_VALUES = [
"NULL",
"EMPTY_LIST",
"PASS_THROUGH",
// "UNDEFINED", // waiting for a need
];
const EMPTY_LIST = Object.freeze([]);
function trim(string, length = 15) {
if (string.length > length) {
return string.substring(0, length - 2) + "…";
}
else {
return string;
}
}
function resolveTrapValue(tv) {
switch (tv) {
case "NULL":
return null;
case "EMPTY_LIST":
return EMPTY_LIST;
case "PASS_THROUGH":
return false;
default: {
const never = tv;
throw new Error(`TrapValue '${never}' not understood; please use one of: ${TRAP_VALUES.join(", ")}`);
}
}
}
class __FlagStep extends step_js_1.Step {
static { this.$$export = {
moduleName: "grafast",
exportName: "__FlagStep",
}; }
constructor(step, options) {
super();
this.isSyncAndSafe = false;
this.ifDep = null;
const { acceptFlags = constants_js_1.DEFAULT_ACCEPT_FLAGS, onReject, dataOnly, if: $cond, valueForInhibited = "PASS_THROUGH", valueForError = "PASS_THROUGH", } = options;
this.forbiddenFlags = constants_js_1.ALL_FLAGS & ~acceptFlags;
this.onRejectReturnValue =
onReject == null ? error_js_1.$$inhibit : (0, error_js_1.flagError)(onReject, step.id);
this.valueForInhibited = resolveTrapValue(valueForInhibited);
this.valueForError = resolveTrapValue(valueForError);
this.canBeInlined =
!$cond &&
valueForInhibited === "PASS_THROUGH" &&
valueForError === "PASS_THROUGH" &&
// Can't PASS_THROUGH errors since they need to be converted into TRAPPED
// error.
// TODO: should we be handling this in Grafast core?
(acceptFlags & constants_js_1.FLAG_ERROR) === 0;
if (!this.canBeInlined) {
this.addDependency({ step, acceptFlags: constants_js_1.TRAPPABLE_FLAGS });
if ($cond) {
this.ifDep = this.addDependency($cond);
}
}
else {
this.addDependency({ step, acceptFlags, onReject, dataOnly });
}
if ((0, step_js_1.isListCapableStep)(step)) {
this.listItem = this._listItem;
}
(0, utils_js_1.sudo)(this).implicitSideEffectStep = null;
this.layerPlan.latestSideEffectStep = null; // Can't be `this`, because __FlagStep can be optimized away.
}
toStringMeta() {
const acceptFlags = constants_js_1.ALL_FLAGS & ~this.forbiddenFlags;
const rej = this.onRejectReturnValue
? trim(String(this.onRejectReturnValue))
: (0, inspect_js_1.inspect)(this.onRejectReturnValue);
const $if = this.ifDep !== null ? this.getDepOptions(this.ifDep).step : null;
return `${this.dependencies[0].id}, ${$if ? `if(${$if.id}), ` : ``}${digestAcceptFlags(acceptFlags)}, onReject: ${rej}`;
}
[constants_js_1.$$deepDepSkip]() {
return this.getDepOptions(0).step;
}
// Copied over listItem if the dependent step is a list capable step
_listItem($item) {
const $dep = this.dependencies[0];
return (0, step_js_1.isListCapableStep)($dep) ? $dep.listItem($item) : $item;
}
/** Return inlining instructions if we can be inlined. @internal */
inline(options) {
if (!this.canBeInlined) {
return null;
}
const step = this.dependencies[0];
const forbiddenFlags = this.dependencyForbiddenFlags[0];
const onReject = this.dependencyOnReject[0];
const dataOnly = this.dependencyDataOnly[0];
const acceptFlags = constants_js_1.ALL_FLAGS & ~forbiddenFlags;
if (
// TODO: this logic could be improved so that more flag checks were
// inlined, e.g. `trap(inhibitOnNull($foo), TRAP_INHIBIT)` should just
// become `$foo`.
//
// However, we must be careful that we don't optimize away flags, e.g.
// `trap(inhibitOnNull($foo), TRAP_INHIBIT, { if: $cond })` needs to see
// the inhibit flag to know what to do, so in this case we shouldn't be
// inlined. This may only apply to __FlagStep and might be something we
// want to optimize later.
options.onReject === undefined ||
options.onReject === onReject) {
if (options.acceptFlags === undefined ||
options.acceptFlags === constants_js_1.DEFAULT_ACCEPT_FLAGS ||
options.acceptFlags === acceptFlags ||
false) {
return { step, acceptFlags, onReject, dataOnly };
}
}
return null;
}
deduplicate(_peers) {
return _peers.filter((p) => {
// ifDep has already been tested by Grafast (it's a dependency)
if (p.forbiddenFlags !== this.forbiddenFlags)
return false;
if (p.onRejectReturnValue !== this.onRejectReturnValue)
return false;
if (p.valueForInhibited !== this.valueForInhibited)
return false;
if (p.valueForError !== this.valueForError)
return false;
if (p.canBeInlined !== this.canBeInlined)
return false;
return true;
});
}
execute(_details) {
throw new Error(`${this} not finalized?`);
}
finalize() {
if (this.canBeInlined) {
this.execute = this.passThroughExecute;
}
else {
this.execute = this.fancyExecute;
}
super.finalize();
}
fancyExecute(details) {
const dataEv = details.values[0];
const condEv = this.ifDep === null ? null : details.values[this.ifDep];
const { forbiddenFlags: thisForbiddenFlags, onRejectReturnValue, valueForError, valueForInhibited, } = this;
return details.indexMap((i) => {
const cond = condEv ? condEv.at(i) : true;
const forbiddenFlags = cond
? thisForbiddenFlags
: constants_js_1.DEFAULT_FORBIDDEN_FLAGS;
// Search for "f2b3b1b3" for similar block
const flags = dataEv._flagsAt(i);
const disallowedFlags = flags & forbiddenFlags;
if (disallowedFlags) {
if (disallowedFlags & constants_js_1.FLAG_INHIBITED) {
// We were already rejected, maintain this
return error_js_1.$$inhibit;
}
else if (disallowedFlags & constants_js_1.FLAG_ERROR) {
// We were already rejected, maintain this
return (0, error_js_1.flagError)(dataEv.at(i));
}
else {
// We weren't already inhibited
return onRejectReturnValue;
}
}
else {
if (flags & constants_js_1.FLAG_ERROR && this.valueForError !== false) {
return valueForError;
}
if (flags & constants_js_1.FLAG_INHIBITED && this.valueForInhibited !== false) {
return valueForInhibited;
}
// Assume pass-through
return dataEv.at(i);
}
});
}
// Checks already performed via addDependency, just pass everything through. Should have been inlined!
passThroughExecute(details) {
const ev = details.values[0];
if (ev.isBatch) {
return ev.entries;
}
else {
const val = ev.value;
return details.indexMap(() => val);
}
}
}
exports.__FlagStep = __FlagStep;
/**
* Example use case: get user by id, but id is null: no need to fetch the user
* since we know they won't exist.
*/
function inhibitOnNull($step, options) {
return new __FlagStep($step, {
...options,
acceptFlags: constants_js_1.DEFAULT_ACCEPT_FLAGS & ~constants_js_1.FLAG_NULL,
});
}
/**
* Example use case: expecting a node ID that represents a User, but get one
* that represents a Post instead: throw error to tell user they've sent invalid
* data.
*/
function assertNotNull($step, message, options) {
return new __FlagStep($step, {
...options,
acceptFlags: constants_js_1.DEFAULT_ACCEPT_FLAGS & ~constants_js_1.FLAG_NULL,
onReject: new error_js_1.SafeError(message),
});
}
function trap($step, acceptFlags, options) {
return new __FlagStep($step, {
...options,
acceptFlags: (acceptFlags & constants_js_1.TRAPPABLE_FLAGS) | constants_js_1.FLAG_NULL,
});
}
// Have to overwrite the getDep method due to circular dependency
step_js_1.Step.prototype.getDep = function (depId, throwOnFlagged = false) {
this._assertAccessAllowed(depId);
const { step, acceptFlags, onReject, dataOnly } = this.getDepOptions(depId);
if (acceptFlags === constants_js_1.DEFAULT_ACCEPT_FLAGS && onReject == null) {
return step;
}
else {
if (throwOnFlagged) {
throw new Error(`When retrieving dependency ${step} of ${this}, the dependency is flagged as ${digestAcceptFlags(acceptFlags)}/onReject=${String(onReject)}. Please use \`this.getDepOptions(depId)\` instead, and handle the flags`);
}
// Return a __FlagStep around options.step so that all the options are preserved.
return new __FlagStep(step, { acceptFlags, onReject, dataOnly });
}
};
//# sourceMappingURL=__flag.js.map