Complete QuickJS runtime with fetch, fs, and core bindings
npm install @ricsam/quickjs-runtimeThe recommended way to create QuickJS sandboxed runtimes with web-standard APIs.
``bash`
bun add @ricsam/quickjs-runtime quickjs-emscripten
`typescript
import { createRuntime } from "@ricsam/quickjs-runtime";
const runtime = await createRuntime({
console: {
onEntry: (entry) => {
if (entry.type === "output") {
console.log([sandbox:${entry.level}], ...entry.args);
}
},
},
fetch: async (request) => fetch(request),
});
await runtime.eval(
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log("Fetched:", data););
await runtime.dispose();
`
Creates a fully configured QuickJS runtime with all WHATWG APIs.
`typescript
const runtime = await createRuntime({
// Memory limit in megabytes
memoryLimitMB: 10,
// Console output handler
console: {
onEntry: (entry) => { / handle console output / },
},
// Fetch handler for outbound requests
fetch: async (request) => fetch(request),
// File system access
fs: {
getDirectory: async (path) => createNodeDirectoryHandle(./sandbox${path}),
},
// ES module loader
moduleLoader: async (moduleName) => {
if (moduleName === "@/utils") {
return export const add = (a, b) => a + b;;Unknown module: ${moduleName}
}
throw new Error();
},
// Custom host functions
customFunctions: {
hashPassword: {
fn: async (password) => Bun.password.hash(password),
type: 'async',
},
getConfig: {
fn: () => ({ environment: "production" }),
type: 'sync',
},
},
// Enable test environment (describe, it, expect)
testEnvironment: {
onEvent: (event) => { / handle test events / },
},
// Playwright browser automation
playwright: {
page: playwrightPage,
baseUrl: "https://example.com",
},
});
`
The returned handle provides:
`typescript
interface RuntimeHandle {
// Unique runtime identifier
readonly id: string;
// Execute code as ES module (supports top-level await)
eval(code: string, filenameOrOptions?: string | EvalOptions): Promise
// Dispose all resources
dispose(): Promise
// Sub-handles for specific features
readonly fetch: RuntimeFetchHandle;
readonly timers: RuntimeTimersHandle;
readonly console: RuntimeConsoleHandle;
readonly testEnvironment: RuntimeTestEnvironmentHandle;
readonly playwright: RuntimePlaywrightHandle;
}
interface EvalOptions {
// Filename for the evaluated code (for stack traces)
filename?: string;
// Maximum execution time in milliseconds
maxExecutionMs?: number;
}
`
Use maxExecutionMs to prevent infinite loops and long-running code:
`typescript
const runtime = await createRuntime();
// Set a 5 second timeout
await runtime.eval(
// Code that completes quickly
const result = compute();, { maxExecutionMs: 5000 });
// Infinite loops will be interrupted
try {
await runtime.eval(
while (true) { / infinite loop / }
, { maxExecutionMs: 100 });
} catch (error) {
console.log("Execution timed out");
}
await runtime.dispose();
`
`typescript
const runtime = await createRuntime({
console: { onEntry: (e) => e.type === "output" && console.log(...e.args) },
});
await runtime.eval(
serve({
fetch(request) {
const url = new URL(request.url);
return Response.json({ path: url.pathname });
},
}););
// Dispatch requests to the sandboxed server
const response = await runtime.fetch.dispatchRequest(
new Request("http://localhost/api/users")
);
console.log(await response.json()); // { path: "/api/users" }
await runtime.dispose();
`
`typescript${icon} ${event.test.fullName}
const runtime = await createRuntime({
testEnvironment: {
onEvent: (event) => {
if (event.type === "testEnd") {
const icon = event.test.status === "pass" ? "✓" : "✗";
console.log();
}
},
},
});
await runtime.eval(
describe("Math", () => {
it("adds numbers", () => {
expect(1 + 1).toBe(2);
});
}););
const results = await runtime.testEnvironment.runTests();
console.log(${results.passed}/${results.total} passed);
await runtime.dispose();
`
`typescript
import { chromium } from "playwright";
const browser = await chromium.launch();
const page = await browser.newPage();
const runtime = await createRuntime({
testEnvironment: true,
playwright: {
page,
baseUrl: "https://example.com",
},
});
await runtime.eval(
describe("Homepage", () => {
it("displays welcome message", async () => {
await page.goto("/");
await expect(page.getByRole("heading")).toContainText("Welcome");
});
}););
await runtime.testEnvironment.runTests();
await runtime.dispose();
await browser.close();
`
Custom functions expose host capabilities to the sandbox. Arguments and return values are automatically marshalled between host and QuickJS.
Function types:
- type: 'sync' - Synchronous functiontype: 'async'
- - Returns a Promisetype: 'asyncIterator'
- - Returns an async iterable (for streaming)
`typescript
const runtime = await createRuntime({
customFunctions: {
// Sync function
generateId: {
fn: () => crypto.randomUUID(),
type: 'sync',
},
// Async function
hashPassword: {
fn: async (password) => Bun.password.hash(password),
type: 'async',
},
// Async iterator for streaming
streamData: {
fn: async function* (count) {
for (let i = 0; i < count; i++) {
await new Promise(r => setTimeout(r, 100));
yield { index: i, timestamp: Date.now() };
}
},
type: 'asyncIterator',
},
},
});
await runtime.eval(
const id = generateId();
const hash = await hashPassword("secret123");
for await (const item of streamData(3)) {
console.log(item); // { index: 0, timestamp: ... }, etc.
});`
Supported return types (auto-marshalled):
| Type | Notes |
|------|-------|
| string, number, boolean, null, undefined, bigint | Primitives |{ key: value }
| | Plain objects (nested supported, max depth 10) |[1, 2, 3]
| | Arrays |Date
| | Becomes QuickJS Date |Uint8Array
| , ArrayBuffer | Binary data |Promise
| | Becomes QuickJS Promise |
| Functions | Become callable from QuickJS (see below) |
Returning callable functions:
`typescript`
customFunctions: {
// Return a factory function
createMultiplier: {
fn: (factor) => (x) => x * factor,
type: 'sync',
},
// Return an object with methods
getDatabase: {
fn: async () => {
const db = await connectToDb();
return {
query: async (sql) => db.query(sql),
close: () => db.close(),
};
},
type: 'async',
},
}
`javascript
// In sandbox:
const double = createMultiplier(2);
console.log(double(5)); // 10
const db = await getDatabase();
const users = await db.query("SELECT * FROM users");
db.close();
`
`typescriptexport const double = (n) => n * 2;
const runtime = await createRuntime({
moduleLoader: async (moduleName) => {
const modules = {
"@/utils": ,export default { apiUrl: "https://api.example.com" };
"@/config": ,Module not found: ${moduleName}
};
if (moduleName in modules) {
return modules[moduleName];
}
throw new Error();
},
});
await runtime.eval(
import { double } from "@/utils";
import config from "@/config";
console.log(double(21)); // 42
console.log(config.apiUrl););`
When you use createRuntime(), the following globals are automatically available in the sandbox:
- Console: console.log, console.warn, console.error, etc.fetch
- Fetch: , Request, Response, Headers, FormData, AbortControllerserve()
- Server: with WebSocket supportcrypto.getRandomValues()
- Crypto: , crypto.randomUUID(), crypto.subtleatob()
- Encoding: , btoa()setTimeout
- Timers: , setInterval, clearTimeout, clearIntervalReadableStream
- Streams: , WritableStream, TransformStreamBlob
- Blob/File: , Filepath.join()
- Path: , path.resolve(), etc.
Optional (when configured):
- File System: getDirectory() (requires fs option)describe
- Test Environment: , it, expect (requires testEnvironment option)page
- Playwright: object (requires playwright option)
- No automatic network access - fetch option must be explicitly providedgetDirectory
- File system isolation - controls all path accessmemoryLimitMB
- Memory limits - Use option to prevent memory exhaustionmaxExecutionMs
- Execution timeouts - Use in eval() to prevent infinite loops
- No access to host - Code runs in isolated QuickJS VM
For advanced use cases requiring direct context manipulation, you can use the low-level setupRuntime()` function or individual package setup functions. See the individual package READMEs for details:
- @ricsam/quickjs-core
- @ricsam/quickjs-console
- @ricsam/quickjs-fetch
- @ricsam/quickjs-fs
- @ricsam/quickjs-crypto
- @ricsam/quickjs-encoding
- @ricsam/quickjs-timers
- @ricsam/quickjs-path
- @ricsam/quickjs-test-environment
- @ricsam/quickjs-playwright