• #111 — public per-graph teardown. Releases the wasm engine behind a Graph constructed by this module: removes its per-engineId handler registrations from the sidecar (the module-level maps that otherwise pin the whole engine — its unbounded value cache, fn-table, and observers), frees its Rust slot (when the artefact carries the per-engine free extern), and drops it from the live registry. Idempotent; a no-op for a graph this module did not construct. Equivalent to graph.dispose() / using graph = createCauslWasmSync(...).

    Parameters

    Returns void

    causl/causl-core-rs#370. Calling this releases the engine IMMEDIATELY, in the turn that calls it. A loop that constructs and disposes 20 000 graphs without yielding retains 2.1 MB, about 0.11 KB per graph (Node v26.5.1, arm64, heapUsed after two forced full collections). Before #370 the same loop retained 299.4 MB, about 15.33 KB per graph, because registerWasmGraphTeardown aimed a WeakRef at the backend and the WeakRef constructor's AddToKeptObjects pin (ECMA-262 §26.1.1) is released by the host at the next event-loop turn and by nothing else.

    DROPPING a graph without disposing it is different, and it is still bounded by a turn. An undisposed graph's engine is held by the sidecar's module-level per-engineId handler maps, and the only thing that removes those entries for a graph nobody disposed is the FinalizationRegistry backstop, whose callback is a turn boundary by definition. Measured at about 16.2 KB per graph inside the turn, returning to baseline across one. So a synchronous loop over tens of thousands of graphs must either call this, or yield (await new Promise((r) => setImmediate(r))) periodically. Calling this is the cheaper of the two and is the only one available inside a single turn.