grafast
Version:
Cutting edge GraphQL planning and execution engine
220 lines • 8.45 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccessStep = exports.expressionSymbol = void 0;
exports.access = access;
const tslib_1 = require("tslib");
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const tamedevil_1 = tslib_1.__importDefault(require("tamedevil"));
const inspect_js_1 = require("../inspect.js");
const step_js_1 = require("../step.js");
const utils_js_1 = require("../utils.js");
/** @internal */
exports.expressionSymbol = Symbol("expression");
const makeDestructureCache = Object.create(null);
const makingDestructureCache = Object.create(null);
/**
* Returns a function that will extract the value at the given path from an
* incoming object. If possible it will return a dynamically constructed
* function which will enable V8 to optimise the function over time via the
* JIT.
*/
function constructDestructureFunction(path, fallback, callback) {
const n = path.length;
/** 0 - slow mode; 1 - middle mode; 2 - turbo mode */
let mode = n > 50 || n < 1 ? 0 : n > 5 ? 1 : 2;
for (let i = 0; i < n; i++) {
const pathItem = path[i];
const t = typeof pathItem;
if (t === "symbol") {
// Cannot use in superfast mode (because cannot create signature)
if (mode === 2)
mode = 1;
}
else if (t === "string") {
// Cannot use in superfast mode (because signature becomes ambiguous)
if (mode === 2 && pathItem.includes("|"))
mode = 1;
}
else if (t === "number") {
if (!Number.isFinite(pathItem)) {
mode = 0;
}
}
else if (pathItem == null) {
// Slow mode required
mode = 0;
}
else {
throw new Error(`Invalid path item: ${(0, inspect_js_1.inspect)(pathItem)} in path '${JSON.stringify(path)}'`);
}
}
if (mode === 0) {
// Slow mode
callback(function slowlyExtractValueAtPath(_meta, value) {
let current = value;
for (let i = 0, l = path.length; i < l && current != null; i++) {
const pathItem = path[i];
current = current[pathItem];
}
return current ?? fallback;
});
}
else {
const signature = (fallback !== undefined ? "f" : "n") + n;
const done = mode === 2
? (factory) => {
const fn = factory(fallback, ...path);
// ?.blah?.bog?.["!!!"]?.[0]
const expressionDetail = [path, fallback];
fn[exports.expressionSymbol] = expressionDetail;
callback(fn);
}
: (factory) => callback(factory(fallback, ...path));
const fn = makeDestructureCache[signature];
if (fn !== undefined) {
done(fn);
return;
}
const making = makingDestructureCache[signature];
if (making !== undefined) {
making.push(done);
return;
}
const doneHandlers = [done];
makingDestructureCache[signature] = doneHandlers;
// DO NOT REFERENCE 'path' BELOW HERE!
const names = [];
const access = [];
for (let i = 0; i < n; i++) {
const te_name = tamedevil_1.default.identifier(`p${i}`);
names.push(te_name);
access.push((0, tamedevil_1.default) `[${te_name}]`);
}
tamedevil_1.default.runInBatch((0, tamedevil_1.default) `function (fallback, ${tamedevil_1.default.join(names, ", ")}) {
return (_meta, value) => value?.${tamedevil_1.default.join(access, "?.")}${fallback === undefined ? tamedevil_1.default.blank : tamedevil_1.default.cache ` ?? fallback`};
}`, (factory) => {
makeDestructureCache[signature] = factory;
delete makingDestructureCache[signature];
for (const doneHandler of doneHandlers) {
doneHandler(factory);
}
});
}
}
/**
* Accesses a (potentially nested) property from the result of a plan.
*
* NOTE: this could lead to unexpected results (which could introduce security
* issues) if it is not used carefully; only use it on JSON-like data,
* preferably where the objects have null prototypes, and be sure to adhere to
* the naming conventions detailed in assertSafeToAccessViaBraces.
*/
class AccessStep extends step_js_1.UnbatchedStep {
static { this.$$export = {
moduleName: "grafast",
exportName: "AccessStep",
}; }
constructor(parentPlan, path, fallback) {
super();
this.fallback = fallback;
this.isSyncAndSafe = true;
this.allowMultipleOptimizations = true;
this._isImmutable = parentPlan._isImmutable;
this.path = path;
this.hasSymbols = this.path.some((k) => typeof k === "symbol");
this.peerKey =
(this.fallback === "undefined" ? "U" : "D") +
(this.hasSymbols ? "§" : ".") +
(0, utils_js_1.digestKeys)(this.path);
this.addStrongDependency(parentPlan);
}
toStringMeta() {
return `${chalk_1.default.bold.yellow(String(this.getDepOptions(0).step.id))}.${this.path.map((p) => String(p)).join(".")}`;
}
getParentStep() {
return this.getDep(0);
}
/**
* Get the named property of an object.
*/
get(attrName) {
if (typeof attrName !== "string") {
throw new Error(`AccessStep::get can only be called with string values`);
}
return access(this.getDep(0), [...this.path, attrName]);
}
/**
* Get the entry at the given index in an array.
*/
at(index) {
if (typeof index !== "number") {
throw new Error(`AccessStep::get can only be called with string values`);
}
return access(this.getDep(0), [...this.path, index]);
}
// An access of an access can become a single access
optimize() {
const $dep = this.getDep(0);
if (this.fallback === undefined && this.path.length === 0) {
// I don't do anything
return $dep;
}
if ($dep instanceof AccessStep && $dep.fallback === undefined) {
return access($dep.getDep(0), [...$dep.path, ...this.path], this.fallback);
}
return this;
}
finalize() {
// Note that `OutputPlan.optimize` depends on this.
constructDestructureFunction(this.path, this.fallback, (fn) => {
this.unbatchedExecute = fn;
});
super.finalize();
}
unbatchedExecute(_extra, ..._values) {
throw new Error(`${this}: should have had unbatchedExecute method replaced`);
}
deduplicate(peers) {
if (peers.length === 0) {
return peers;
}
else if (!this.hasSymbols && this.fallback === undefined) {
// Rely entirely on peerKey
return peers;
}
else if (!this.hasSymbols) {
// Rely on peerKey for path, but check fallback
const { fallback } = this;
return peers.filter((p) => p.fallback === fallback);
}
else {
// Check both fallback and path
const { fallback, path } = this;
return peers.filter((p) => p.fallback === fallback && (0, utils_js_1.arraysMatch)(p.path, path));
}
}
}
exports.AccessStep = AccessStep;
/**
* Access the property at path `path` in the value returned from `parentPlan`,
* falling back to `fallback` if it were null-ish.
*/
function access(parentPlan, rawPath, fallback) {
const path = Array.isArray(rawPath)
? rawPath
: rawPath != null
? [rawPath]
: [];
if (typeof fallback === "undefined" &&
!path.some((k) => typeof k === "symbol")) {
const pathKey = (0, utils_js_1.digestKeys)(path);
if (parentPlan._isImmutable) {
return parentPlan.operationPlan.withRootLayerPlan(() => parentPlan.operationPlan.cacheStep(parentPlan, "GrafastInternal:access()", pathKey, () => new AccessStep(parentPlan, path)));
}
else {
return parentPlan.operationPlan.cacheStep(parentPlan, "GrafastInternal:access()", pathKey, () => new AccessStep(parentPlan, path));
}
}
return new AccessStep(parentPlan, path, fallback);
}
//# sourceMappingURL=access.js.map