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

@jtrb/runtime

v0.2.2

Published

A browser-based C runtime powered by WASM, with stdin/stdout/stderr I/O and a debugger

Downloads

86

Readme

@jtrb/runtime

A browser-based C++ runtime powered by WebAssembly. Compile and run C++ programs entirely in the browser, with stdin/stdout/stderr I/O and a built-in debugger.

Installation

npm install @jtrb/runtime

Requirements: Your bundler or server must set these HTTP headers, as the runtime uses SharedArrayBuffer for stdin:

Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin

In Next.js, add these in next.config.js. In Vite, use the vitePlugin or configure your dev server headers.


Quick start

import { Runtime } from '@jtrb/runtime';

// 1. Create the runtime (loads and compiles the WASM module)
const rt = await Runtime.create('c');

// 2. Set the virtual filesystem — the program sees these as real files
rt.fs = {
  'main.c': `
    #include <iostream>
    int main() {
      std::cout << "Hello, world!" << std::endl;
      return 0;
    }
  `
};

// 3. Subscribe to stdout / stderr (chunks are UTF-8 bytes as Uint8Array)
const decoder = new TextDecoder();
const printChunk = (chunk: Uint8Array) => {
  process.stdout.write(decoder.decode(chunk));
};
rt.stdout.on('data', printChunk);
rt.stderr.on('data', printChunk);

// 4. Perform the required DAP handshake (see below), then run
await rt.run();

The DAP handshake

The runtime compiles programs in debug mode, which means a DAP (Debug Adapter Protocol) debugger is always active. The worker blocks at startup and will not execute your program until the handshake is complete.

This is a required step — skipping it will cause rt.run() to hang forever.

// Keep a monotonically increasing sequence number for DAP messages
let dapSeq = 1;

// Helper: send a DAP request and log the synchronous response
const dapSend = (command: string, args: Record<string, unknown>) => {
  return rt.debugger.send({
    type: 'request',
    seq: dapSeq++,
    command,
    arguments: args
  });
};

// Register the event listener BEFORE sending initialize.
// All async events from the runtime arrive here — initialized, stopped, etc.
rt.debugger.on('event', (msg) => {
  const m = msg as { type?: string; event?: string };

  if (m?.type === 'event' && m?.event === 'initialized') {
    // The runtime is ready to receive configuration.
    // Send an empty breakpoint list for now.
    dapSend('setBreakpoints', { source: { path: '/main.c' }, breakpoints: [] });
    dapSend('setExceptionBreakpoints', { filters: [] });
    // This unblocks the worker — the program starts executing after this
    dapSend('configurationDone', {});
  }
});

// Kick off the handshake
dapSend('initialize', {});

// Now run — resolves when the program exits
await rt.run();

Why DAP?

The runtime exposes a full DAP interface so that IDEs can add debugging features (breakpoints, stepping, variable inspection) without any special runtime changes. Everything goes through standard DAP requests and events.

What works today:

  • initialize / initialized / configurationDone — required startup handshake
  • setBreakpoints — accepted (does nothing, support coming soon)
  • setExceptionBreakpoints — accepted (does nothing, might support later)

Coming soon:

  • Breakpoint hits (stopped event)
  • Variable inspection (scopes, variables requests)
  • Step over / step in / step out

Wiring stdin

rt.stdin.write() accepts a UTF-8 string or a Uint8Array. The program reads via cin, scanf, read(), etc.

// Send a line of input (programs typically expect a trailing newline)
await rt.stdin.write('42\n');

// Or raw bytes:
await rt.stdin.write(new TextEncoder().encode('42\n'));

For interactive terminals (e.g. xterm.js), buffer keystrokes locally and flush on Enter:

let inputBuf = '';

terminal.onData((data) => {
  if (data === '\r') {
    terminal.write('\r\n');
    void rt.stdin.write(inputBuf + '\n');
    inputBuf = '';
  } else if (data === '\x7f') {
    if (inputBuf.length > 0) {
      inputBuf = inputBuf.slice(0, -1);
      terminal.write('\b \b');
    }
  } else {
    inputBuf += data;
    terminal.write(data);
  }
});

Stopping a program

rt.stop(); // terminates the worker immediately; rt.run() resolves

Full API

// Create a runtime for the given language ('c' is currently supported)
const rt = await Runtime.create('c');

rt.fs; // DirNode  — virtual filesystem, set before calling run()
rt.stdout; // RuntimeOutput — program stdout; use .on('data', fn) / .off('data', fn)
rt.stderr; // RuntimeOutput — program stderr
rt.stdin; // RuntimeStdin — program stdin; use .write(string | Uint8Array)
rt.debugger; // Debugger — DAP interface
rt.lang; // Lang     — language this runtime was created for

rt.run(); // Promise<void> — start execution, resolves on exit
rt.stop(); // void         — kill the worker

rt.debugger.send(message); // send a DAP request, returns response synchronously
rt.debugger.on('event', handler); // receive async DAP events

Example project

See the ide/ folder for a complete Next.js example that wires up CodeMirror 6, xterm.js, and @jtrb/runtime into a working in-browser IDE. The CodeEditor.tsx component is heavily commented and walks through every step of the integration.


Contributing / building from source

Requires Cargo 1.91+, wasm-pack, and Node v22+.

cargo install wasm-pack
npm install
npm run build   # wasm-pack build --target web && vite build

For local development, use:

npm run dev

To run the built-in demo:

npm link
cd demo
npm link @jtrb/runtime
npm run dev