UNPKG

grafast

Version:

Cutting edge GraphQL planning and execution engine

221 lines (219 loc) 8.48 kB
"use strict"; // import debugFactory from "debug"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ObjectStep = void 0; exports.object = object; const tslib_1 = require("tslib"); const tamedevil_1 = tslib_1.__importStar(require("tamedevil")); const dev_js_1 = require("../dev.js"); const step_js_1 = require("../step.js"); const utils_js_1 = require("../utils.js"); const constant_js_1 = require("./constant.js"); const DEFAULT_CACHE_SIZE = 100; const EMPTY_OBJECT = Object.freeze(Object.create(null)); /** * A plan that represents an object using the keys given and the values being * the results of the associated plans. */ class ObjectStep extends step_js_1.UnbatchedStep { static { this.$$export = { moduleName: "grafast", exportName: "ObjectStep", }; } constructor(obj, cacheConfig) { super(); this.cacheConfig = cacheConfig; this.isSyncAndSafe = true; this.allowMultipleOptimizations = true; this.keys = []; // Optimize needs the same 'meta' for all ObjectSteps this.optimizeMetaKey = "ObjectStep"; this.cacheSize = cacheConfig?.cacheSize ?? (cacheConfig?.identifier ? DEFAULT_CACHE_SIZE : 0); const keys = Object.keys(obj); this._setKeys(keys); for (let i = 0, l = this.keys.length; i < l; i++) { this.addDependency({ step: obj[keys[i]], skipDeduplication: true }); } } _setKeys(keys) { this.keys = keys; this.peerKey = (0, utils_js_1.digestKeys)(keys); this.metaKey = this.cacheSize <= 0 ? undefined : this.cacheConfig?.identifier ? `object|${this.peerKey}|${this.cacheConfig.identifier}` : this.id; } /** * This key doesn't get typed, but it can be added later which can be quite * handy. */ set(key, plan) { this._setKeys([...this.keys, key]); this.addDependency({ step: plan, skipDeduplication: true }); } getStepForKey(key, allowMissing = false) { const idx = this.keys.indexOf(key); if (idx < 0) { if (!allowMissing) { throw new Error(`${this}: failed to retrieve plan for key '${String(key)}' - we have no such key`); } return null; } return this.getDepOptions(idx).step; } toStringMeta() { return "{" + this.keys.join(",") + "}"; } /* tupleToObject( meta: ObjectPlanMeta<TPlans>, ...tuple: Array<DataFromObjectSteps<TPlans>[keyof TPlans]> ): DataFromObjectSteps<TPlans> { // Note: `outerloop` is a JavaScript "label". They are not very common. // First look for an existing match: outerloop: for (let i = 0, l = meta.results.length; i < l; i++) { const [values, obj] = meta.results[i]; // Slow loop over each value in the tuples; this is not expected to be a // particularly big loop, typically only 2-5 keys. for (let j = 0, m = this.keys.length; j < m; j++) { if (values[j] !== tuple[j]) { // This isn't a match; try the next record in the outer loop continue outerloop; } } return obj; } // That failed; create a new object. debugObjectPlanVerbose( "%s: Could not find cache for keys %c values %c, constructing new object", this, this.keys, tuple, ); const newObj = this.keys.reduce((memo, key, i) => { memo[key] = tuple[i]; return memo; }, {} as Partial<DataFromObjectSteps<TPlans>>) as DataFromObjectSteps<TPlans>; // Cache newObj so the same tuple values result in the exact same object. meta.results.push([tuple, newObj]); return newObj; } */ tupleToObjectJIT(callback) { if (this.keys.length === 0) { // Shortcut simple case return callback(() => EMPTY_OBJECT); } const keysAreSafe = this.keys.every(tamedevil_1.isSafeObjectPropertyName); const inner = keysAreSafe ? (0, tamedevil_1.default) `\ const newObj = { ${tamedevil_1.default.join(this.keys.map((key, i) => (0, tamedevil_1.default) ` ${tamedevil_1.default.safeKeyOrThrow(key)}: ${tamedevil_1.default.identifier(`val${i}`)}`), ",\n")} }; ` : (0, tamedevil_1.default) `\ const newObj = Object.create(null); ${tamedevil_1.default.join(this.keys.map((key, i) => (0, tamedevil_1.default) ` newObj${tamedevil_1.default.set(key, true)} = ${tamedevil_1.default.identifier(`val${i}`)};\n`), "")}\ `; const vals = tamedevil_1.default.join(this.keys.map((_k, i) => tamedevil_1.default.identifier(`val${i}`)), ", "); if (this.cacheSize > 0) { return tamedevil_1.default.runInBatch((0, tamedevil_1.default) `\ (function ({ meta }, ${vals}) { if (meta.nextIndex != null) { for (let i = 0, l = meta.results.length; i < l; i++) { const [values, obj] = meta.results[i]; if (${tamedevil_1.default.join(this.keys.map((_key, i) => (0, tamedevil_1.default) `values[${tamedevil_1.default.lit(i)}] === ${tamedevil_1.default.identifier(`val${i}`)}`), " && ")}) { return obj; } } } else { meta.nextIndex = 0; meta.results = []; } ${inner} meta.results[meta.nextIndex] = [[${tamedevil_1.default.join(this.keys.map((_key, i) => tamedevil_1.default.identifier(`val${i}`)), ",")}], newObj]; // Only cache ${tamedevil_1.default.lit(this.cacheSize)} results, use a round-robin meta.nextIndex = meta.nextIndex === ${tamedevil_1.default.lit(this.cacheSize - 1)} ? 0 : meta.nextIndex + 1; return newObj; })`, callback); } else { return tamedevil_1.default.runInBatch((0, tamedevil_1.default) `\ (function (_, ${vals}) { ${inner} return newObj; })`, callback); } } finalize() { this.tupleToObjectJIT((fn) => { this.unbatchedExecute = fn; }); return super.finalize(); } execute({ indexMap, values, extra, }) { return indexMap((i) => this.unbatchedExecute(extra, ...values.map((v) => v.at(i)))); } unbatchedExecute(_extra, ..._values) { throw new Error(`${this} didn't finalize? No unbatchedExecute method.`); } deduplicate(peers) { // Managed through peerKey return peers; } optimize(opts) { if (this.dependencies.every((dep) => dep instanceof constant_js_1.ConstantStep)) { // Replace self with constant // We'll cache so that the constants can be more easily deduplicated const meta = opts.meta; const keysJoined = this.keys.join(","); if (!meta[keysJoined]) { meta[keysJoined] = []; } const existing = meta[keysJoined].find((existingObj) => this.keys.every((key, i) => existingObj[key] === this.dependencies[i].data)); const isSensitive = this.dependencies.some((d) => d.isSensitive); if (existing !== undefined) { return (0, constant_js_1.constant)(existing, isSensitive); } else { const obj = Object.create(null); for (let i = 0, l = this.keys.length; i < l; i++) { const key = this.keys[i]; const value = this.dependencies[i].data; obj[key] = value; } meta[keysJoined].push(obj); return (0, constant_js_1.constant)(obj, isSensitive); } } return this; } /** * Get the original plan with the given key back again. */ get(key) { const index = this.keys.indexOf(key); if (index < 0) { if (dev_js_1.isDev) { // TODO: move this to diagnostics console.warn(`${this} doesn't have key '${String(key)}'; supported keys: '${this.keys.join("', '")}'`); } return (0, constant_js_1.constant)(undefined); } return this.getDepOptions(index).step; } } exports.ObjectStep = ObjectStep; /** * A plan that represents an object using the keys given and the values being * the results of the associated plans. */ function object(obj, cacheConfig) { return new ObjectStep(obj, cacheConfig); } //# sourceMappingURL=object.js.map