Skip to main content
Executing Code

Contexts

Keep JavaScript variables and imports between calls with a context, and run several contexts in parallel in one VM.

Every call on a VM starts with fresh JavaScript memory. A context keeps it: variables and imports from one call are still there in the next. Use one for a REPL, a notebook, or an agent that builds on its earlier steps.

Files, packages, and processes belong to the VM, not to a context, so you do not need a context for those.

Keep state between calls

import { createVm } from "secure-exec";

const vm = await createVm();

// A context keeps JavaScript variables and imports between calls, like a REPL.
const context = await vm.createContext();

await context.execute("globalThis.cart = []");
await context.execute(`cart.push({ item: "coffee", price: 4 })`);
await context.execute(`cart.push({ item: "bagel", price: 3 })`);

const total = await context.evaluate<number>(
	"cart.reduce((sum, line) => sum + line.price, 0)",
);
console.log(total.outcome === "succeeded" ? total.value : total.error); // 7

Each call is its own ES module, so top-level const and let stay scoped to that call. Put values on globalThis to share them. A context holds memory and does nothing between calls.

TypeScript

// TypeScript shares the same state. Pass the context's id to `vm.typescript`.
const typed = await vm.typescript.evaluate<number>(
	"(globalThis.cart as { price: number }[]).length",
	{ contextId: context.contextId },
);
console.log(typed.outcome === "succeeded" ? typed.value : typed.error); // 2

Isolation

// Calls outside the context never see its state, even in the same VM.
const outside = await vm.javascript.evaluate<string>("typeof globalThis.cart");
console.log(outside.outcome === "succeeded" ? outside.value : outside.error); // undefined

Run in parallel

A context runs one call at a time. A second call while one is running fails with execution_busy instead of queueing. For parallel work, create several contexts in the same VM. Each has its own memory, and they share the VM’s files and packages.

import { createVm } from "secure-exec";

const vm = await createVm();

// A context runs one call at a time. For parallel work, create several: each
// has its own JavaScript memory, and they share the VM's files and packages.
const shards = await Promise.all([
	vm.createContext(),
	vm.createContext(),
	vm.createContext(),
]);

const started = performance.now();
const results = await Promise.all(
	shards.map((context, shard) =>
		context.evaluate<number>(
			"new Promise((resolve) => setTimeout(() => resolve(inputs.shard * 10), 500))",
			{ inputs: { shard } },
		),
	),
);

console.log(results.map((result) => ("value" in result ? result.value : null))); // [0, 10, 20]
console.log(`${Math.round(performance.now() - started)}ms`); // well under the 1500ms that running them one after another takes

await vm.dispose();

When parallel contexts need shared mutable state, keep it in a file, or in a background process that they all call.

Reset and dispose

// `reset` clears the state. `dispose` deletes the context and leaves the VM.
await context.reset();
const afterReset = await context.evaluate<string>("typeof globalThis.cart");
console.log(
	afterReset.outcome === "succeeded" ? afterReset.value : afterReset.error,
); // undefined

await context.dispose();
await vm.dispose();

context.dispose() deletes the context and leaves the VM running. Disposing the VM removes every context in it.

Edit this page Last updated September 21, 2026