@ricsam/quickjs-runtime
v0.2.25
Published
Complete QuickJS runtime with fetch, fs, and core bindings
Downloads
2,882
Maintainers
Readme
@ricsam/quickjs-runtime
The recommended way to create QuickJS sandboxed runtimes with web-standard APIs.
Installation
bun add @ricsam/quickjs-runtime quickjs-emscriptenQuick Start
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();API
createRuntime(options?)
Creates a fully configured QuickJS runtime with all WHATWG APIs.
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;`;
}
throw new Error(`Unknown module: ${moduleName}`);
},
// 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",
},
});RuntimeHandle
The returned handle provides:
interface RuntimeHandle {
// Unique runtime identifier
readonly id: string;
// Execute code as ES module (supports top-level await)
eval(code: string, filenameOrOptions?: string | EvalOptions): Promise<void>;
// Dispose all resources
dispose(): Promise<void>;
// 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;
}Execution Timeout
Use maxExecutionMs to prevent infinite loops and long-running code:
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();Examples
HTTP Server
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();Running Tests
const runtime = await createRuntime({
testEnvironment: {
onEvent: (event) => {
if (event.type === "testEnd") {
const icon = event.test.status === "pass" ? "✓" : "✗";
console.log(`${icon} ${event.test.fullName}`);
}
},
},
});
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();Browser Automation with Playwright
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
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)
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:
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',
},
}// 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();ES Modules
const runtime = await createRuntime({
moduleLoader: async (moduleName) => {
const modules = {
"@/utils": `export const double = (n) => n * 2;`,
"@/config": `export default { apiUrl: "https://api.example.com" };`,
};
if (moduleName in modules) {
return modules[moduleName];
}
throw new Error(`Module not found: ${moduleName}`);
},
});
await runtime.eval(`
import { double } from "@/utils";
import config from "@/config";
console.log(double(21)); // 42
console.log(config.apiUrl);
`);Included APIs
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,AbortController - Server:
serve()with WebSocket support - Crypto:
crypto.getRandomValues(),crypto.randomUUID(),crypto.subtle - Encoding:
atob(),btoa() - Timers:
setTimeout,setInterval,clearTimeout,clearInterval - Streams:
ReadableStream,WritableStream,TransformStream - Blob/File:
Blob,File - Path:
path.join(),path.resolve(), etc.
Optional (when configured):
- File System:
getDirectory()(requiresfsoption) - Test Environment:
describe,it,expect(requirestestEnvironmentoption) - Playwright:
pageobject (requiresplaywrightoption)
Security
- No automatic network access -
fetchoption must be explicitly provided - File system isolation -
getDirectorycontrols all path access - Memory limits - Use
memoryLimitMBoption to prevent memory exhaustion - Execution timeouts - Use
maxExecutionMsineval()to prevent infinite loops - No access to host - Code runs in isolated QuickJS VM
Advanced: Low-level API
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:
