/** * Worker-thread entry. Receives a bundled ESM string + timeout, hooks * `console.*` to a capture buffer, evaluates the bundle via a data: * URL import (Node's native ESM loader handles top-level await for us), * then sends the SandboxResult back. * * The host (`./host.ts`) enforces the wall-clock budget by calling * `worker.terminate()` on overrun — there's no cooperative cancellation * from inside the worker. This keeps the worker code simple and means * even hostile snippets that try to outlast the budget get hard-killed. * * Why a data: URL import rather than `vm.runInContext`? `vm` doesn't * link ESM imports, so a snippet that imports `@directive-run/core` * fails at parse time. Data URL imports go through Node's real ESM * loader, which DOES link external imports — `@directive-run/core` is * already installed in the parent's node_modules, so the worker finds * it via standard resolution. The cost: data URL imports happen * exactly once per worker (which is what we want — one-shot execution). */ /** * Look for a system on globalThis the snippet attached. The runner * scaffold emits `const system = createSystem({...}); system.start();` * at top level, but inside an ESM module those bindings are scoped to * the module's own scope — we can't read them from the worker. * * Workaround: the worker patches `createSystem` to also stash the * returned system on `globalThis.__directiveSandbox_system__`. After * the bundle finishes, we read facts from there. * * This monkey-patch happens via a tiny ESM module we prepend to the * bundle before evaluation (see `wrapForExecution`). */ interface SandboxSystem { facts: { $store: { toObject(): Record; }; }; derive: Record; destroy(): void; } declare global { var __directiveSandbox_system__: SandboxSystem | null; }