import { setImmediate } from "node:timers/promises"; import { AnalysisResourceConstraintError } from "../../domain/analysisErrorCore.js"; import { fixedNativeAllowance } from "../../domain/native/nativeDeadlines.js"; const NATIVE_MATCHING_REPRESENTATION_BYTES = 96 * 1024 * 1024; /** Budget the actual selected observations, before cloning or index allocation. */ export class NativeMatchingAdmission { #remaining = NATIVE_MATCHING_REPRESENTATION_BYTES; #work = 0; readonly #seen = new WeakSet(); constructor( readonly operation: string, readonly signal?: AbortSignal, ) {} readonly checkpoint = async (bytes = 0): Promise => { this.#remaining -= bytes; if (this.#remaining < 0) throw new AnalysisResourceConstraintError( this.operation, "capacity", `Native function matching exceeds its ${String(NATIVE_MATCHING_REPRESENTATION_BYTES)}-byte (96 MiB) representation allowance. This release has no runtime override.`, { maximum_bytes: NATIVE_MATCHING_REPRESENTATION_BYTES }, fixedNativeAllowance( "The native function-matching representation allowance", "bytes", `${String(NATIVE_MATCHING_REPRESENTATION_BYTES)} bytes`, ), ); this.signal?.throwIfAborted(); if (++this.#work % 256 === 0) await setImmediate( undefined, this.signal === undefined ? {} : { signal: this.signal }, ); }; async admit(root: unknown): Promise { const pending: Iterator[] = [[root][Symbol.iterator]()]; while (pending.length) { const next = pending[pending.length - 1]!.next(); if (next.done) { pending.pop(); continue; } const value = next.value; if (typeof value === "object" && value !== null) { if (this.#seen.has(value)) continue; this.#seen.add(value); await this.checkpoint(128); // Iterator traversal avoids a second width-sized array of child values. pending.push( (function* () { for (const key in value) if (Object.hasOwn(value, key)) { yield key; yield (value as Record)[key]; } })(), ); } else await this.checkpoint( typeof value === "string" ? 64 + value.length * 4 : 128, ); } } }