Elixir wrapper for zapcode, a minimal secure TypeScript-subset interpreter written in Rust.
ExZapcode is the TypeScript sibling of ExMonty (Python). Both embed a small, sandboxed interpreter as a NIF and share the same interactive start/resume model:
- Microsecond startup — no Node/V8, just a bytecode VM
- Interactive execution — guest code pauses at external function calls, hands control to Elixir, and resumes with the result
- Resource limits — cap wall-clock time, memory, stack depth, allocations
- Language-level sandbox — no filesystem, network, env,
eval, orimport; the only way guest code reaches the host is through the external functions you register
Quick start
# Evaluate an expression (the last expression is the result)
{:ok, 7, ""} = ExZapcode.eval("1 + 2 * 3")
# With inputs bound as globals
{:ok, "hi Sam", ""} =
ExZapcode.eval("`hi ${name}`", inputs: %{"name" => "Sam"})The tool bridge
Register host functions the guest may await. Execution suspends at each call,
you run the tool in ordinary Elixir, and it resumes with the return value:
{:ok, 2, ""} =
ExZapcode.Sandbox.run(
"const r = await db({ sql: \"SELECT 1\" }); r.rows[0].n + 1",
functions: %{"db" => fn [%{"sql" => _}] -> {:ok, %{"rows" => [%{"n" => 1}]}} end}
)See ExZapcode.Sandbox for the high-level driver that automates the loop.
Interactive API (low level)
{:function_call, "getWeather", [city], snapshot, _out} =
ExZapcode.start("await getWeather(city)", inputs: %{"city" => "London"},
functions: ["getWeather"])
{:complete, value, _out} = ExZapcode.resume(snapshot, %{"temp" => 18})
Summary
Functions
The resource limits applied when :limits is omitted.
Serializes a suspended snapshot to a binary for storage or transport.
Compiles and runs code to completion, with no external functions.
Deserializes a snapshot binary produced by dump_snapshot/1 into a snapshot
usable with resume/2.
Resumes a suspended run with the external function's return value.
Begins interactive execution of TypeScript code.
Types
Functions
@spec default_limits() :: map()
The resource limits applied when :limits is omitted.
@spec dump_snapshot(snapshot()) :: {:ok, binary()} | {:error, ExZapcode.Exception.t()}
Serializes a suspended snapshot to a binary for storage or transport.
The pausing use case: a run suspends at an external function (:function_call),
you dump_snapshot/1 the snapshot to a binary, persist it (DB, queue, another
node), run the tool whenever, then load_snapshot/1 and resume/2 with the
result — possibly in a different process or after a restart.
Non-destructive: unlike ExMonty.dump_snapshot/1, this does not consume the
snapshot, so the same in-memory snapshot can still be resume/2d afterward.
Trusted input only
Only load_snapshot/1 binaries your application produced and stored in a
trusted location. The bytes deserialize directly into native VM state.
@spec eval( String.t(), keyword() ) :: {:ok, term(), String.t()} | {:error, ExZapcode.Exception.t()}
Compiles and runs code to completion, with no external functions.
Convenience over ExZapcode.Sandbox.run/2 for pure expressions.
{:ok, 4, ""} = ExZapcode.eval("2 + 2")
@spec load_snapshot(binary()) :: {:ok, snapshot()} | {:error, ExZapcode.Exception.t()}
Deserializes a snapshot binary produced by dump_snapshot/1 into a snapshot
usable with resume/2.
Resumes a suspended run with the external function's return value.
Returns the next progress/0 — another :function_call if the guest awaits
again, or :complete when it finishes.
Examples
{:function_call, "getWeather", ["London"], snap, _} =
ExZapcode.start("await getWeather(city)",
functions: ["getWeather"], inputs: %{"city" => "London"})
{:complete, %{"temp" => 18}, ""} = ExZapcode.resume(snap, %{"temp" => 18})
Begins interactive execution of TypeScript code.
Options
:inputs— map ofname => valuebound as globals (default:%{}):functions— the names of host functions the guest mayawait. Either a list of names, or a map whose keys are the names (values ignored here). They must be declared up front: zapcode compiles calls to them as suspension points.:limits— resource limits map, merged overdefault_limits/0:script_name— accepted forExMontyparity; currently unused
Returns a progress/0 tuple.