Skip to main content
The TypeScript SDK runs the same sandbox in Node and in a browser tab. The API below is identical in both; what differs is where snapshots are cached and what the guest’s network can reach. See Browser for that.

Install

In a browser, import from @capsule-run/vpod/browser instead. The package root resolves by export condition, which some bundlers get wrong for client code.

Overview

Everything is async, because loading the engine and fetching a snapshot are.

Persistent sessions

All calls share one running sandbox, so state carries across them. Declaring it with await using closes it when the scope ends:
If your toolchain does not support await using, call await sandbox.close() yourself. Shell commands and Python code share one filesystem:
Environment variables do not cross between the two. commands.run("export FOO=bar") is invisible to code.run(...). Use the filesystem to pass data between them.

Return values

sandbox.commands.run(cmd)

sandbox.code.run(source)

Variables and imports live for the lifetime of the session, so a REPL built on this behaves the way people expect.

Timeouts

Both methods take a timeout in seconds. When it is reached the guest work is interrupted and the call returns rather than waiting.

Interrupting a command

A command no longer has to run to its timeout. interrupt() stops whatever is in the foreground, and commands.run() takes an AbortSignal for callers who would rather set the deadline up front.
The two report differently, on purpose. interrupt() resolves with an ordinary result carrying exit code 130, because a terminal wants the exit code. A signal rejects with signal.reason, so your own controller throws AbortError and AbortSignal.timeout() throws TimeoutError. The command really stops. This is not the caller walking away while the guest keeps working, so the sandbox is yours again immediately.
code.run() cannot be interrupted yet. It accepts signal because it shares an options type with commands.run(), but ignores it, so use timeout there.A command that ignores the interrupt runs to its own deadline and is recovered the same way a timeout is.

Sandbox.create() options

snapshot

A registry name, or a snapshot you built yourself:
Keep the RAM size in the file name, because the emulator reads it from there.
The first Sandbox.create() downloads the snapshot and caches it, on disk in Node and in origin-private storage in a browser. Later runs use the cache. See Snapshots.

network

The guest’s network is on by default wherever it can be. Pass false to run fully offline, or true to fail loudly instead of silently starting without it:
sandbox.network reports what the guest can actually reach:
The three asset URL options only matter in a browser, where the worker and the wasm are fetched at runtime rather than imported.

Differences from the Python SDK

Three things differ, and all three surprise people who move between the two:
  • The default snapshot is vsnap-base:latest here and alpine in Python.
  • sandbox.suspend() returns the delta bytes, not an instance id. Browsers have nowhere to put a file, so where the state goes is your decision. See Suspend & resume.
  • There is no mounts option. Mounting a host directory has no meaning in a browser tab, so the TypeScript SDK does not offer it anywhere.