eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
107 lines • 5.47 kB
TypeScript
/**
* Host-side serialization for the QuickJS engine.
*
* Implements the workflow wire codec (format-prefixed devalue, identical to
* `codec-devalue-vm.ts` / the node:vm engine's workflow-mode codec) as
* host code operating on `JSValueHandle`s, using devalue 5.9's pluggable
* operations (quickjs-wasi's host-side introspection primitives underneath).
* The serde bundle previously evaluated inside the VM is gone: guest values
* are read and built through handles, so no serializer code lives in (or
* can be tampered with from) the guest realm.
*
* Side-effect discipline mirrors the node:vm engine's hardened codec
* (serialization/hardened.ts):
*
* - classification is by engine brand (`classId` against boot-captured
* samples, `isError`, `isProxy`), never `instanceof` or
* `Symbol.toStringTag`;
* - extraction goes through intrinsics captured at boot (before any user
* code runs) invoked with explicit receivers, or through own-property
* descriptor reads, so patched prototypes and inherited accessors never
* run;
* - the only guest code serialization can execute is the same code the
* previous in-VM codec executed by contract: a class's static
* `WORKFLOW_SERIALIZE` method, a step proxy's `__closureVarsFn`, and
* `WORKFLOW_USE_STEP` / `WORKFLOW_DESERIALIZE` on revival.
*
* Wire-format parity with the previous in-VM codec is REQUIRED and covered
* by tests: event logs written by either codec must be readable by the
* other (steps serialized by the node runtime feed VM revival and vice
* versa).
*
* Hybrid value space: reducers return host shapes (plain objects, strings,
* numbers) whose leaves may be guest handles, exactly how the node:vm
* codec mixes host shapes with sandbox-realm leaves. Every stringify
* operation therefore dispatches on `JSValueHandle` and falls back to
* devalue's default host operations for host values. Parse operations
* always build guest values, so the parse side is handle-only.
*/
import { JSValueHandle, type QuickJS } from '../_quickjs-wasi.js';
/**
* Evaluate the serde capture root in a VM. Must run after the runtime
* bootstrap and BEFORE any user code. The returned handle owns the
* container; on the baseline-snapshot path its raw box pointer
* (`handle.ptr`) is recorded next to the snapshot and re-adopted per
* restored VM via {@link adoptSerdeRoot}. Do NOT dispose the handle
* before the snapshot is taken (the box must stay live in the memory
* image).
*/
export declare function captureSerdeRoot(vm: QuickJS): JSValueHandle;
/**
* Re-create the capture-root handle in a VM restored from a snapshot
* taken while the exported root was alive, via quickjs-wasi's
* snapshot-portable handle tokens (`importHandle` duplicates the
* underlying value: the returned handle is independently owned and the
* serde disposes it per restored VM). No guest code executes.
*/
export declare function adoptSerdeRoot(vm: QuickJS, token: number): JSValueHandle;
/**
* Export the capture root as a snapshot-portable token to record next to
* the baseline snapshot. The root handle must stay undisposed until the
* snapshot is taken (its box, and the reference it holds, must be part
* of the memory image).
*/
export declare function exportSerdeRoot(vm: QuickJS, root: JSValueHandle): number;
export interface QuickJSSerde {
/** Serialize a guest value handle to format-prefixed wire bytes. */
serialize(value: JSValueHandle): Uint8Array;
/** Build a guest value in the VM from format-prefixed wire bytes. */
deserialize(data: Uint8Array): JSValueHandle;
/**
* The reducer names this codec applies, in registration order. Exposed
* so tests can assert exhaustiveness against the shared value-space
* codec (codec-devalue-vm): a reducer added there but not here would
* otherwise silently round-trip values as plain objects.
*/
reducerKeys: readonly string[];
/** Reviver names, for the same exhaustiveness check. */
reviverKeys: readonly string[];
/**
* Install `globalThis.process = { env: Object.freeze({...}) }` in the
* VM through handles and boot-captured intrinsics only: no guest
* source is evaluated, so a module-scope wrapper around JSON.parse /
* Object.freeze cannot observe the injection. This matters on the
* baseline-snapshot path, where env injection happens after user code
* ran; evaluating guest source there would let patched globals see
* (and perturb) it, diverging from the fresh path.
*/
installProcessEnv(env: Record<string, string | undefined>): void;
dispose(): void;
}
/**
* Create the host-side serde for a VM. Must be called after the runtime
* bootstrap has been evaluated (so bootstrap-installed globals are
* capturable) and before the workflow bundle runs.
*/
export declare function createQuickJSSerde(vm: QuickJS,
/**
* Pre-captured serde root (baseline-snapshot path): the container
* `captureSerdeRoot` created BEFORE user code, re-adopted from the
* restored memory image via `adoptSerdeRoot`. When omitted, the root
* is captured now; callers must guarantee no user code has run yet.
* Either way, initialization below performs only plain-data property
* reads and C-level classId reads on the container: NO guest code
* executes here, so nothing user-patchable can observe or perturb it.
*/
capturedRoot?: JSValueHandle): QuickJSSerde;
//# sourceMappingURL=quickjs-serde.d.ts.map