OapiCodemode.Executor.SafeJS (oapi_codemode v0.1.0)

Copy Markdown View Source

In-process executor on ex_safejs, the QuickJS-NG engine embedded as a Rustler NIF (our hard fork of lpgauth/quicksand). Like Executor.ZapCode it's a pure dependency with precompiled binaries — no runtime binary in the image, no subprocess — but unlike zapcode it enforces a genuine hard memory cap (QuickJS's own allocator is the sole memory authority, so even typed-array/ArrayBuffer allocations are bounded, the vector that escapes V8's heap limit) and runs a mature, correct engine with O(1) container access (no O(n²) spec scans).

Dialect: same async arrow as Executor.Deno

Since ex_safejs 0.3.0 eval is async-aware, so guest code may be a sync or an async arrow — await, .then chains, and Promise.all all work. apis.<name>.request(...) blocks the JS thread while the Elixir callback runs and returns the response as a plain value, which await passes through unchanged; Promise.all over several requests therefore executes them serially, not concurrently. Host-callback time does not count against the sandbox timeout (it is a JS compute budget), and a promise that nothing can ever settle is reported as a deadlock error immediately instead of burning the timeout.

Injection differs from Deno: ex_safejs has no globals API, so env.globals is handed in through a host callback that returns the map (direct term→JS conversion — faster than embedding JSON) and Object.assigned onto globalThis in a preamble. The apis object and a console.log capture shim are built in the same preamble.

Isolation

ex_safejs guarantees a straggler-free mailbox: eval messages are tagged per-eval and a timed-out eval's late result is absorbed inside eval/3, so run/3 executes directly in the calling process — safe for a trap_exit GenServer caller (gentility's LoopServer). The quicksand-era throwaway worker per eval is gone.

Divergences from Executor.Deno, inherent to the engine

  • Serial Promise.all (above): requests resolve one at a time.
  • Raising callbacks are opaque to the guest. A callback that raises surfaces to JS as a generic "host function failed" exception; if uncaught, this run's {:error, %{message: ..., logs: ...}} carries the real exception message (host-side only).
  • No regex. (Shared with zapcode; steer codegen to string methods.)