npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ricsam/quickjs-runtime

v0.2.25

Published

Complete QuickJS runtime with fetch, fs, and core bindings

Downloads

2,882

Readme

@ricsam/quickjs-runtime

The recommended way to create QuickJS sandboxed runtimes with web-standard APIs.

Installation

bun add @ricsam/quickjs-runtime quickjs-emscripten

Quick 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 function
  • type: 'async' - Returns a Promise
  • type: '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() (requires fs option)
  • Test Environment: describe, it, expect (requires testEnvironment option)
  • Playwright: page object (requires playwright option)

Security

  • No automatic network access - fetch option must be explicitly provided
  • File system isolation - getDirectory controls all path access
  • Memory limits - Use memoryLimitMB option to prevent memory exhaustion
  • Execution timeouts - Use maxExecutionMs in eval() 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: