IOweb/node_modules/satteri/dist/child-stub.js
2026-07-03 15:07:38 -05:00

57 lines
2.1 KiB
JavaScript

/**
* Shared machinery for walk-path child stubs (see `hast/child-stub.ts` and
* `mdast/child-stub.ts`): lightweight stand-ins that carry arena id + type
* eagerly and defer the full-arena snapshot until a plugin reads a real field.
* Passthrough children (the common replaceNode shape) thus compile to one-word
* refs without ever serializing the arena.
*/
/** Stub → materialized arena node, filled on the first real-field read. */
const REAL_NODES = new WeakMap();
/** One shared memoizing getter per field name, so stubs install shared
* descriptor templates instead of allocating per-field closures per node. */
const FIELD_GETTERS = new Map();
function fieldGetter(key) {
let getter = FIELD_GETTERS.get(key);
if (getter === undefined) {
getter = function () {
let real = REAL_NODES.get(this);
if (real === undefined) {
real = this._resolver.materializeOne(this._id);
REAL_NODES.set(this, real);
}
const value = real[key];
Object.defineProperty(this, key, {
value,
writable: true,
enumerable: true,
configurable: true,
});
return value;
};
FIELD_GETTERS.set(key, getter);
}
return getter;
}
/**
* Descriptor template for one node type's stub fields. Own enumerable getters
* so a spread copy carries correct values. `position`/`data` ride on every
* stub; they may yield `undefined` where a materialized node omits the key —
* accepted drift, invisible to `toEqual`.
*/
export function stubDescriptors(fields) {
const map = {};
for (const key of [...fields, "position", "data"]) {
map[key] = { get: fieldGetter(key), enumerable: true, configurable: true };
}
return map;
}
/** Node tags are dense small ints, so the per-child stub loop indexes flat
* arrays instead of paying Map/dictionary lookups. */
export function flatByTag(table) {
const flat = [];
for (const tag of Object.keys(table)) {
const nodeType = Number(tag);
flat[nodeType] = table[nodeType];
}
return flat;
}