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

quickjs-vm

v0.3.0

Published

QuickJS-powered worker runtime for Node.js

Readme

quickjs-vm

QuickJS-powered worker runtime for Node.js.

If you want to run JavaScript in an isolated QuickJS runtime from Node, this library gives you a QuickJSWorker with support for eval, modules, injected globals, durable handles, message passing, limits, and runtime stats.

Install

npm install quickjs-vm

The Short Version

import { QuickJSWorker } from "quickjs-vm";

const worker = new QuickJSWorker({
  maxEvalMs: 500, // max time allowed for each execution
  maxCpuMs: 250, // max CPU time allowed for each execution
  maxMemoryBytes: 32 * 1024 * 1024, // max memory allowed for the runtime.
});

const answer = await worker.eval("(a, b) => a + b", { args: [20, 22] });
console.log(answer); // 42

await worker.close();

What You Get

  • eval(...) and evalSync(...)
  • module.eval(...), module.import(...), module.register(...), module.clear(...)
  • global.* for reading, writing, and calling things on globalThis
  • handle.* for durable references to runtime values
  • postMessage(...) and on("message", ...)
  • limits for wall time, CPU time, memory, stack size, and interrupts
  • stats for memory, CPU sampling, rates, latency, event loop lag, and last execution
  • runtime and error events with host callsite metadata

Basic Evaluation

import { QuickJSWorker } from "quickjs-vm";

const worker = new QuickJSWorker();

console.log(await worker.eval("1 + 1")); // 2
console.log(await worker.eval('"hello".toUpperCase()')); // HELLO
console.log(worker.evalSync("6 * 7")); // 42

await worker.close();

If the evaluated source returns a function and you pass args, the function is called for you:

const worker = new QuickJSWorker();

const result = await worker.eval("(name) => `Hello, ${name}`", {
  args: ["Ada"],
});

console.log(result); // Hello, Ada
await worker.close();

Modules

module.eval(...) returns a namespace-like object. Exported functions stay callable from Node.

import { QuickJSWorker } from "quickjs-vm";

const worker = new QuickJSWorker();

const math = await worker.module.eval(`
  export const version = "1.0.0";
  export function add(a, b) { return a + b; }
  export async function double(n) { return n * 2; }
`);

console.log(math.version); // 1.0.0
console.log(await math.add(20, 22)); // 42
console.log(await math.double(21)); // 42

await worker.close();

You can also register named modules:

const worker = new QuickJSWorker();

await worker.module.register("app:math", `
  export const tau = 6.28318;
  export function mul(a, b) { return a * b; }
`);

const math = await worker.module.import("app:math");

console.log(math.tau);
console.log(await math.mul(6, 7)); // 42

await worker.close();

And if you want imports to resolve through host code, provide an imports callback:

const worker = new QuickJSWorker({
  imports: (specifier) => {
    if (specifier === "./math.js") {
      return `
        export function add(a, b) { return a + b; }
      `;
    }
    return false; // throw on all other modules
  },
});

const mod = await worker.module.eval(`
  import { add } from "./math.js";
  export const value = add(20, 22);
`);

console.log(mod.value); // 42
await worker.close();

Globals

You can push values and functions into the worker:

const worker = new QuickJSWorker();

await worker.global.set("config", {
  env: "test",
  nested: { retries: 2 },
});

await worker.global.set("double", (n: number) => n * 2);

console.log(await worker.global.get("config.nested.retries")); // 2
console.log(await worker.global.call("double", [21])); // 42

await worker.close();

The global.* API mirrors a useful chunk of the handle API, so you can also:

  • has(path)
  • delete(path)
  • keys(path?)
  • entries(path?)
  • define(path, descriptor)
  • getOwnPropertyDescriptor(path)
  • isCallable(path?)
  • isPromise(path?)
  • construct(path, args?)
  • await(path, options?)
  • clone(path)
  • toJSON(path?)
  • apply(path, ops)
  • getType(path?)
  • instanceOf(path, constructorPath)

Handles

Handles are for when you want a durable reference to a runtime value instead of repeatedly serializing it in and out.

const worker = new QuickJSWorker();

const counter = await worker.handle.eval(`
  ({
    count: 0,
    inc() {
      this.count += 1;
      return this.count;
    }
  })
`);

console.log(await counter.call("inc"));   // 1
console.log(await counter.call("inc"));   // 2
console.log(await counter.get("count"));  // 2

await counter.dispose();
await worker.close();

You can also bind a handle to an existing runtime object:

const worker = new QuickJSWorker();

await worker.eval(`
  globalThis.toolbox = {
    value: 41,
    bump() { this.value += 1; return this.value; }
  };
`);

const toolbox = await worker.handle.get("toolbox");
console.log(await toolbox.call("bump")); // 42

await toolbox.dispose();
await worker.close();

Messaging

Worker to host:

const worker = new QuickJSWorker();

worker.on("message", (msg) => {
  console.log("from worker:", msg);
});

await worker.eval(`postMessage({ hello: "from QuickJS" })`);
await worker.close();

Host to worker:

const worker = new QuickJSWorker();

await worker.eval(`
  globalThis.received = null;
  on("message", (msg) => {
    globalThis.received = msg;
  });
`);

worker.postMessage({ hello: "from Node" });

setTimeout(async () => {
  console.log(await worker.eval("received.hello")); // from Node
  await worker.close();
}, 25);

Limits

You can set defaults on the worker and override them per call.

const worker = new QuickJSWorker({
  maxEvalMs: 100,
  maxCpuMs: 50,
  maxMemoryBytes: 16 * 1024 * 1024,
});

await worker.eval("1 + 1");

await worker.eval(
  `
    (() => {
      const end = Date.now() + 40;
      while (Date.now() < end) {}
      return 42;
    })()
  `,
  { maxCpuMs: 250 },
);

await worker.close();

Available limits/options include:

  • maxEvalMs
  • maxCpuMs
  • maxMemoryBytes
  • maxStackSizeBytes
  • maxInterrupt
  • gcThresholdAlloc
  • gcIntervalMs

Stats

The stats API is intentionally practical:

const worker = new QuickJSWorker();

await worker.eval("21 + 21");

console.log(worker.stats.lastExecution);
console.log(await worker.stats.cpu({ measureMs: 100 }));
console.log(await worker.stats.rates({ windowMs: 1000 }));
console.log(await worker.stats.latency({ windowMs: 1000 }));
console.log(await worker.stats.eventLoopLag({ measureMs: 20 }));
console.log(worker.stats.totals);
console.log(await worker.stats.memory());

worker.stats.reset();
await worker.close();

Available stats:

  • stats.lastExecution
  • stats.activeOps
  • stats.cpu({ measureMs })
  • stats.rates({ windowMs })
  • stats.latency({ windowMs })
  • stats.eventLoopLag({ measureMs })
  • stats.totals
  • stats.reset()
  • stats.memory()

Runtime and Error Events

QuickJSWorker exposes:

  • message
  • close
  • runtime
  • error

Runtime and error events include best-effort host callsite metadata, which is handy when some deeply wrapped helper eventually does something regrettable. Runtime and error events include best-effort host callsite metadata, which is useful when errors pass through a few layers before they are handled.

const worker = new QuickJSWorker();

worker.on("runtime", (event) => {
  console.log(event.kind, event.hostCallSite);
});

worker.on("error", (event) => {
  console.log(event.surface, event.error?.message);
  console.log(event.hostCallSite);
});

await worker.eval("1 + 1");
await worker.close();

Listeners can be removed with off(...), and listener failures are ignored so one broken callback does not poison the rest.

const onMessage = (msg: any) => console.log(msg);

worker.on("message", onMessage);
worker.off("message", onMessage);
worker.off("runtime"); // clear all runtime listeners

Bytecode

If you want to precompile some code and run it later, there is support for that too.

const worker = new QuickJSWorker();

const bytes = await worker.getByteCode("globalThis.answer = 42;");
await worker.loadByteCode(bytes);

console.log(await worker.eval("answer")); // 42

await worker.close();

Resource Management

Workers and handles should be closed or disposed when you are done with them.

const worker = new QuickJSWorker();

try {
  await worker.eval("1 + 1");
} finally {
  await worker.close();
}

QuickJSWorker and handles also support Symbol.asyncDispose.

Tests

npm test

Development

Build the native addon and TypeScript wrapper:

npm run build

API Reference

new QuickJSWorker(options?)

Creates a new QuickJS runtime.

Common options:

  • maxEvalMs
  • maxCpuMs
  • maxMemoryBytes
  • maxStackSizeBytes
  • maxInterrupt
  • gcThresholdAlloc
  • gcIntervalMs
  • globals
  • modules
  • imports

Evaluation

  • worker.eval(source, options?) Evaluates code asynchronously and resolves with the resulting value. If the evaluated value is a function and options.args is provided, that function is called with the supplied arguments.
  • worker.evalSync(source, options?) Evaluates code synchronously and returns the resulting value directly. This is useful for small, fully synchronous snippets.

Evaluation options:

  • filename Sets the filename shown in runtime error messages and stacks.
  • args Supplies arguments when the evaluated source returns a callable function.
  • maxEvalMs Overrides the wall-clock timeout for this call.
  • maxCpuMs Overrides the CPU-time budget for this call.

Modules

  • worker.module.eval(source, options?) Evaluates module source and returns a namespace-like object containing the module exports.
  • worker.module.import(specifier) Imports a named module or a module resolved through the configured imports callback.
  • worker.module.register(moduleName, source, options?) Registers module source under a name so it can be imported later.
  • worker.module.clear(moduleName) Removes a registered module name from the local module registry.

Module options:

  • all evaluation options Module evaluation accepts the same per-call filename, args, maxEvalMs, and maxCpuMs options as eval(...).
  • moduleName Pins a module to a stable name so it can be imported again later.
  • cjs Wraps the provided source as CommonJS and exposes the result as module exports.

Globals

  • worker.global.set(path, value, options?) Writes a value onto globalThis inside the worker.
  • worker.global.get(path, options?) Reads a value from globalThis using a dotted path.
  • worker.global.has(path, options?) Returns whether a dotted path exists on globalThis.
  • worker.global.delete(path, options?) Deletes a property from globalThis.
  • worker.global.keys(path?, options?) Returns enumerable keys from the target object.
  • worker.global.entries(path?, options?) Returns enumerable [key, value] pairs from the target object.
  • worker.global.getOwnPropertyDescriptor(path, options?) Returns the property descriptor for a path if one exists.
  • worker.global.define(path, descriptor, options?) Defines a property using a standard JavaScript property descriptor.
  • worker.global.isCallable(path?, options?) Returns whether the target value is callable.
  • worker.global.isPromise(path?, options?) Returns whether the target value is promise-like.
  • worker.global.call(path, args?, options?) Calls a global function by path and resolves with its return value.
  • worker.global.construct(path, args?, options?) Calls a global constructor with new and resolves with the created value.
  • worker.global.await(path, options?) Awaits a promise stored on globalThis and resolves with the fulfilled value.
  • worker.global.clone(path, options?) Creates a handle bound to a value on globalThis.
  • worker.global.toJSON(path?, options?) Produces a JSON-safe snapshot of the target value.
  • worker.global.apply(path, ops, options?) Clones a global value as a temporary handle and runs a batch of handle operations against it.
  • worker.global.getType(path?, options?) Returns type information for the target value, including whether it is callable.
  • worker.global.instanceOf(path, constructorPath, options?) Returns whether a global value is an instance of a global constructor.

Handles

  • worker.handle.get(path, options?) Creates a handle for an existing runtime value reachable by path.
  • worker.handle.tryGet(path, options?) Creates a handle if the path exists, otherwise resolves with undefined.
  • worker.handle.eval(source, options?) Evaluates source and returns a durable handle to the resulting value.

Handle instance methods:

  • handle.get(path?, options?) Reads a property from the handled value.
  • handle.has(path, options?) Returns whether a property exists on the handled value.
  • handle.set(path, value, options?) Writes a property on the handled value.
  • handle.delete(path, options?) Deletes a property from the handled value.
  • handle.keys(path?, options?) Returns enumerable keys from the handled value.
  • handle.entries(path?, options?) Returns enumerable entries from the handled value.
  • handle.getOwnPropertyDescriptor(path, options?) Returns the property descriptor for a property on the handled value.
  • handle.define(path, descriptor, options?) Defines a property on the handled value using a descriptor.
  • handle.instanceOf(constructorPath, options?) Returns whether the handled value is an instance of a global constructor.
  • handle.isCallable(path?, options?) Returns whether the handled value, or a property on it, is callable.
  • handle.isPromise(path?, options?) Returns whether the handled value, or a property on it, is promise-like.
  • handle.call(args?, options?) Calls the handled value itself if it is a function.
  • handle.call(path, args?, options?) Calls a function property on the handled value.
  • handle.construct(args?, options?) Uses the handled value as a constructor and returns the created value.
  • handle.await(options?) Awaits a promise handle and optionally updates the handle to the resolved value.
  • handle.clone(options?) Creates another handle pointing at the same runtime value.
  • handle.toJSON(path?, options?) Produces a JSON-safe snapshot of the handled value or a nested property.
  • handle.apply(ops, options?) Runs a list of handle operations in sequence and returns all results.
  • handle.getType(path?, options?) Returns type information for the handled value or nested property.
  • handle.dispose(options?) Releases the handle so it can no longer be used.

Messaging and Lifecycle

  • worker.postMessage(message) Sends a message from Node into the worker message channel.
  • worker.on("message", handler) Registers a listener for messages posted from QuickJS.
  • worker.on("close", handler) Registers a listener that fires once when the worker closes.
  • worker.on("runtime", handler) Registers a listener for runtime telemetry such as eval.begin, eval.end, and error.thrown.
  • worker.on("error", handler) Registers a listener for runtime error events only.
  • worker.off(event, handler?) Removes one listener for an event, or all listeners for that event if no handler is provided.
  • worker.close() Closes the worker and releases the native runtime.
  • worker.isClosed() Returns whether the worker has been closed.

Stats

  • worker.stats.lastExecution Returns the last execution stats snapshot, or null if nothing has run yet.
  • worker.stats.activeOps Returns the number of currently active async wrapper operations.
  • worker.stats.cpu(options?) Samples CPU usage for the QuickJS thread over a short time window.
  • worker.stats.rates(options?) Returns operation throughput by category over a rolling time window.
  • worker.stats.latency(options?) Returns simple latency aggregates over a rolling time window.
  • worker.stats.eventLoopLag(options?) Measures host event-loop lag over a short timer interval.
  • worker.stats.totals Returns cumulative counters collected by the wrapper.
  • worker.stats.reset(options?) Clears rolling samples and, by default, resets the totals counters as well.
  • worker.stats.memory() Returns QuickJS memory usage information from the native runtime.

Other Runtime Methods

  • worker.getByteCode(source) Compiles source to QuickJS bytecode and returns the resulting bytes.
  • worker.loadByteCode(bytes) Loads previously compiled QuickJS bytecode into the runtime.
  • worker.gc() Triggers garbage collection in the QuickJS runtime.
  • worker.memory() Returns the raw QuickJS memory usage payload directly from the runtime.
  • worker[Symbol.asyncDispose]() Async disposal hook that closes the worker when used with await using.

License

MIT