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

@wasmer/sdk

v0.19.0

Published

A portable, package-first sandbox SDK powered by Wasmer

Readme

@wasmer/sdk

Run Wasmer packages in Node.js or the browser with one package-first sandbox API. The runtime is Wasmer + WASIX compiled to WebAssembly with wasm-bindgen; Node.js does not load a native addon.

Package download progress

const abort = new AbortController();
const packages = await wasmer.packages.loadMany(
  ["wasmer/bash", "python/python"],
  {
    signal: abort.signal,
    onProgress({ phase, download, packages }) {
      console.log(phase, download.percent ?? "unknown", download.downloadedBytes);
      for (const pkg of packages) console.log(pkg.id, pkg.cached, pkg.download.percent);
    },
  },
);

packages.load(source, options) accepts the same onProgress and signal. Use onPackageProgress in sandboxes.create or onProgress in sandbox.installPackage. Byte and existing-package sources work in loadMany. Abort uses the signal's reason. An observer exception is logged and detaches that observer without failing acquisition.

Progress is a snapshot, not a delta. download contains downloaded bytes, an optional total, and an optional percentage from 0 to 100. Totals include the unique required package artifacts and their dependencies, weighted by bytes. Unknown sizes stay indeterminate. Counts describe decoded package bodies; SDK cache hits and local sources add zero download bytes. An entirely cached or local load reports 0 bytes of 0 and 100%.

The phases are resolving, downloading, loading, and ready. Downloading can overlap resolution. 100% means the transfer is complete; await the load before using the package. The callback does not cover SDK initialization, guest execution, or guest npm/pip downloads. The final ready snapshot is delivered before a successful load returns, with no callbacks after settlement. Errors use the existing load error channel and do not emit ready.

Batch results preserve input order. Concurrent loads on the same client share in-flight downloads. Cancelling one caller does not interrupt other callers; cancelling the last subscriber stops its acquisition. Callbacks are serialized per operation, coalesced to about ten byte updates per second, with phase changes and completion delivered promptly. Keep callbacks short.

Install

npm install @wasmer/sdk

Load or create a package from Wasm

For a single raw WASI/WASIX module, load its bytes directly:

const pkg = await wasmer.packages.load(wasmBytes);

load() detects Wasm or WEBC from the bytes. A raw module must export _start; the resulting package has one command named main, selected automatically as its entrypoint. Run it with sandbox.command(pkg) or sandbox.command("main"). WEBC packages retain their declared commands and entrypoint.

Use packages.create() to run a raw WASI/WASIX module without building a WEBC archive. It works with the Node and browser entrypoints:

const pkg = await wasmer.packages.create({
  modules: { app: wasmBytes }, // Uint8Array; for example, the result of readFile()
  commands: { hello: { module: "app" } },
  files: { "/data/config.json": '{"debug":true}' },
});
const sandbox = await wasmer.sandboxes.create({ packages: [pkg] });
try {
  console.log((await sandbox.command(pkg, ["--help"]).run()).text());
} finally {
  await sandbox.close();
}

Each command references a named module exporting _start. A sole command is inferred as the entrypoint; for multiple commands, set entrypoint: "hello" or select pkg.command("hello"). Modules and files are captured during creation. File paths must be absolute guest paths without . or .. segments; bundled files use private writable overlays during execution. Use /workspace for persistent sandbox data. Close wasmer when finished.

To migrate from Wasmer.fromWasm(), use packages.load(wasmBytes) and run its package in a sandbox. Use create() for custom command names or bundled files. This API executes WASI/WASIX commands; it does not invoke arbitrary exports or WebAssembly components.

Run Python from Node.js

import { Wasmer } from "@wasmer/sdk/node";

const wasmer = new Wasmer();
const sandbox = await wasmer.sandboxes.create({
  packages: ["python/python@=3.13.20"],
  files: {
    "main.py": "print(sum(number * number for number in range(10)))",
  },
});

const output = await sandbox
  .command("python", ["/workspace/main.py"])
  .run();

console.log(output.text());

new Wasmer() is synchronous. Package downloads, sandbox creation, commands, and shutdown are asynchronous.

run() throws ProcessExitError for a non-zero exit, termination, or timeout by default. Pass { check: false } when the outcome is expected and should be inspected as an Output.

For live processes, use spawn() and iterate the SDK streams directly:

const process = await sandbox
  .command("python", ["-u", "-c", "print('ready')"])
  .spawn({ stdin: "pipe", stdout: "pipe", stderr: "capture" });

for await (const line of process.stdout.lines()) {
  console.log(line);
}

const result = await process.wait({ check: true });

Unlike run(), process.wait() is unchecked by default.

For an interactive shell or REPL, attach one terminal to the entire process tree. Child packages inherit its TTY state and live streams:

const bash = await sandbox
  .command("bash", ["-i"])
  .spawn({ terminal: { columns: 100, rows: 30 } });

if (!bash.stdin || !bash.stdout || !bash.stderr) throw new Error("no terminal");
terminal.onData((data) => void bash.stdin.write(data));
terminal.onResize(({ cols, rows }) => bash.resizeTerminal(cols, rows));
const pump = async (stream) => {
  for await (const chunk of stream) terminal.write(chunk);
};
void pump(bash.stdout);
void pump(bash.stderr);

Call sandbox.close() and wasmer.close() when a long-lived application no longer needs them.

An unexpected worker failure stops the client's worker pool and completes its active processes with a nonzero exit status. wasmer.close() rejects with WORKER_FAILED, including when shutdown was already waiting for that worker.

Node.js networking and caching

Use network: { mode: "host" } when a package needs TCP or DNS:

const sandbox = await wasmer.sandboxes.create({
  packages: ["wasmer/[email protected]"],
  network: { mode: "host" },
});

Node.js maps WASIX networking to node:net and node:dns. Its default .wasmer cache uses the same registry and package layout as the Rust and Python SDKs:

const wasmer = new Wasmer({
  cache: { directory: ".cache/wasmer" },
});

Compiled WebAssembly engine artifacts are intentionally not shared with native targets.

Browser

Import the browser entrypoint:

import { Wasmer } from "@wasmer/sdk/browser";

const httpHost = "https://http.example.com";
const wasmer = new Wasmer();
const sandbox = await wasmer.sandboxes.create({
  packages: ["python/python@=3.13.20"],
});
const output = await sandbox
  .command("python", ["-c", "print('Hello from the browser')"])
  .run();

Worker-backed WASIX execution requires a cross-origin-isolated page so the browser can use SharedArrayBuffer. Serve the page with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp.

Browser package data is persisted with browser storage rather than the Node filesystem cache.

Local TCP between browser sandboxes

Browser sandboxes with network: { mode: "http" } or "wisp" share virtual localhost automatically within one Wasmer client. localhost resolves locally; connections to 127.0.0.1 and ::1 reach guest listeners through bounded in-memory TCP sockets. Local traffic never opens a WebSocket or uses WISP. Missing listeners fail locally rather than reaching the real host. Different Wasmer clients are isolated.

For explicit links, set peers: [databaseSandbox]. Set peers: [] for an isolated sandbox. Restricted sandboxes are excluded from automatic discovery, but can still be selected explicitly as peers. Links are directional, and do not grant access to the peer's peers or its external network. A sandbox's own listener takes priority; if several automatic peers own the same address and port, the connection fails rather than choosing one arbitrarily. Use an explicit peer to select the intended server.

PostgreSQL and psql in the browser

const wasmer = new Wasmer();
const database = await wasmer.sandboxes.create({
  packages: ["wasmer/pglite@=0.1.3"],
  network: { mode: "http" },
});
const postgres = await database.command("pglite").spawn({
  stdout: "capture", stderr: "capture",
});
// Browser readiness observes the listener without consuming a connection.
await database.ports.wait(5432);

const client = await wasmer.sandboxes.create({
  packages: ["wasmer/psql@=18.4.0"],
  network: { mode: "http" }, // Or { mode: "http", peers: [database] }
});
const result = await client.command("psql", [
  "-h", "localhost", "-U", "postgres", "-d", "postgres", "-Atc", "SELECT 6 * 7",
]).run();
console.log(result.text());
await wasmer.close();

wasmer/pglite accepts one connection per process. Keep an interactive psql session open for multiple queries, or restart the server to reconnect. Data lives in the sandbox's in-memory filesystem. The real WASIX psql client is built from PostgreSQL 18.4; its package and build script are in packages/psql.

Run the interactive browser example against a local SDK build:

npm run build
node examples/serve-postgres.mjs
# Open the printed URL. Start the database, then enter SQL or psql commands.

External browser connections

Browsers cannot open external TCP sockets directly. Give a browser sandbox a WISP endpoint to multiplex its WASIX TCP and DNS traffic over one WebSocket:

import { Wasmer } from "@wasmer/sdk/browser";

const wasmer = new Wasmer();
const sandbox = await wasmer.sandboxes.create({
  packages: ["wasmer/[email protected]"],
  network: { mode: "wisp", url: "wss://proxy.example/wisp/" },
});

const output = await sandbox
  .command("pnpm", ["add", "[email protected]", "--ignore-scripts"])
  .run();
console.log(output.text());

The SDK opens the WISP connection lazily when the guest first requests DNS or outbound TCP access. Browser applications can also supply or replace the endpoint at that point:

const sandbox = await wasmer.sandboxes.create({
  packages: ["curl/curl"],
  network: {
    mode: "wisp",
    requestUrl: async ({ url, error }) => {
      // Show application UI here. `url` and `error` are set after a failed
      // configured endpoint; return the WebSocket URL selected by the user.
      return await requestWispUrlFromUser({ url, error });
    },
  },
});

Concurrent guest connections share the same pending endpoint request. The SDK owns one WISP connection per sandbox and routes networking from every WASIX worker through it. Use a trusted, access-controlled proxy: it can observe connection metadata and decide which destinations and ports are allowed.

Change an active sandbox's endpoint without recreating its filesystem or process environment:

sandbox.network.setWispUrl("wss://another-proxy.example/");

Existing WISP streams are closed. The replacement connection is opened lazily on the next outbound network operation.

Run a web server in an iframe

Browser sandboxes expose one HTTP listener at the root of a dedicated static origin. By default, ports.expose() uses Wasmer's managed HTTP host at https://default.local.wasmer.site/, so applications do not need to configure or deploy one:

const server = await sandbox.ports.expose(8080);

To operate a custom HTTP host, serve the worker as /wasmer-service-worker.js:

import "@wasmer/sdk/service-worker";

Serve a control document at /.wasmer/host.html which imports:

import "@wasmer/sdk/service-worker-host";

The application itself stays on a different origin. Start the guest server; omit the second argument to use the managed host, or pass serviceWorker to override it:

import { Wasmer } from "@wasmer/sdk/browser";

const wasmer = new Wasmer();
const sandbox = await wasmer.sandboxes.create({
  packages: ["php/[email protected]"],
  network: { mode: "http" },
  files: { "index.php": "<h1><?php echo 'Hello from PHP'; ?></h1>" },
});

const php = await sandbox
  .command("php", ["-S", "0.0.0.0:8080", "-t", "/workspace"])
  .spawn({ stdout: "capture", stderr: "capture" });

const server = await sandbox.ports.expose(8080);
document.body.append(server.createIframe({ title: "PHP preview" }));

Applications whose listeners speak HTTP can discover ports instead of knowing them in advance. Other protocols, such as PostgreSQL, use the local TCP network directly and must not be exposed as HTTP previews:

const stopWatching = sandbox.ports.onListen((port) => {
  void sandbox.ports
    .expose(port)
    .then((server) => document.body.append(server.createIframe()));
}, {
  onClose: (port) => console.log(`server on ${port} closed`),
});

server.url is the HTTP-host origin root. Requests are forwarded with their original path and body; the SDK does not rewrite HTML or add a path prefix. One service worker exposes exactly one guest server. A second call using the same origin fails until the first BrowserServer is closed. To serve guests concurrently, assign each one its own origin (for example with wildcard subdomains).

Pass a custom host explicitly when needed:

const server = await sandbox.ports.expose(8080, {
  serviceWorker: "https://http.example.com",
});

See wasmer-sh/service-worker in this repository for a complete static Vite build. The host document retains the sandbox connection and gives the service worker a replaceable bridge. If the browser stops an idle service worker, the next request reconnects through the live host document automatically. The Wasmer runtime remains in the application page; closing that page ends the server. Update both the service-worker and host-document bundles together.

network: { mode: "wisp", ... } includes this HTTP-listener support, so one sandbox can serve a browser preview while making outbound connections.

The worker needs scope / to route absolute guest URLs. The SDK-generated iframe uses the capabilities required for scripts and service worker control, but an iframe sandbox is not an origin boundary when both allow-scripts and allow-same-origin are enabled.

Examples

Run these from the repository root after npm run build in js/:

node js/examples/python.mjs
node js/examples/multiple_runtimes.mjs
node js/examples/edgejs_http.mjs
node js/examples/postgres_psql.mjs

The complete browser PHP preview is in examples/browser_php.

The PostgreSQL example requires psql on PATH; set PSQL or pass its path as the first argument otherwise. All examples reuse the programs in ../fixtures/.

Build and test locally

Install Node.js 20 or newer, Rust nightly with rust-src, and the matching wasm-bindgen CLI. npm ci installs the pinned Binaryen toolchain used to run wasm-opt -Oz as part of every JavaScript build:

rustup toolchain install nightly \
  --profile minimal \
  --component rust-src \
  --target wasm32-unknown-unknown
cargo +nightly install wasm-bindgen-cli --version 0.2.126 --locked

cd js
npm ci
npm run build
npm test

Run the network and browser regressions explicitly:

npm run test:edgejs
npm run test:postgres
npx playwright install chromium firefox webkit
npm run test:browser
npm run test:browser-http
npm run test:browser-node
npm run test:browser-postgres

npm run check type-checks the handwritten TypeScript API without rebuilding the wasm module.

The Node browser regression suite covers HTTP, readline, Express installation, structured stack traces, and server cancellation/restart in Chromium, Firefox, and WebKit. To run the same suite in Safari 27+, run node tests/support/serve-node-compat.mjs and open its printed URL in Safari. It displays each check and a final pass or failure. WASMER_TEST_BROWSERS selects Playwright engines; WASMER_EDGEJS_WEBC optionally supplies a local Edge package.