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

@pocketactors/ax-browser

v0.0.0-dev.3

Published

ax for the browser — the actor state-machine engine, in WebAssembly.

Readme

@pocketactors/ax — the browser binding

Run ax actors in the browser, from a tiny ax.wasm that is just the ax core. A machine written for the Node binding runs here unchanged: same System, same ActorRef, same actor handle, for every method the core can back. The runtime underneath is the freestanding ax core in WebAssembly, with no libuv, no sockets, and no journal, but the surface a machine touches is identical.

The binding imposes none of the limits the core itself does without. A machine has as many states and transitions as you write; a system holds as many actors as memory allows; a pid carries its full generation, so a reference to a dead actor dead-letters instead of reaching whoever took its slot; names and state ids grow to any length; and memory grows to the wasm32 ceiling unless the host caps it. Snapshot, machine inspection, and the invariant check sit alongside the actor surface. What is absent is only what needs a runtime backend the browser core has not been given — durability and the peer.

This binding folds the prototype that lived in examples/multiplayer-editor into a first-class binding, a sibling of bindings/node and bindings/python. It is literate: the markdown is the source of truth and the .c/.js are generated.

| Document | Generates | What it is | |----------|-----------|------------| | ax-wasm.md | ax-wasm.c | the C facade: trampolines into JS, the grant-and-grow seam, the growable staging arena | | ax-browser.md | ax-browser.js | the JS binding — System, ActorRef, Actor | | build.md | dist/ax.wasm | the recipe that links the core-only module and reports its size |

The API — the same as the Node binding

The browser binding wraps the core, so it implements every method the core can back, named and shaped as in bindings/node/axr.js.

import { System } from './ax-browser.js';

const sys = await System.create('/ax.wasm');     // fetch + instantiate + open

sys.plug('dom', (target, event, payload) => paint(target, payload));

sys.define({
  name: 'counter',
  data: () => ({ n: 0 }),
  states: {
    idle: {
      entry: (a) => a.emit('dom', '#count', 'set', { n: a.data.n }),
      on: {
        inc: (a, ev) => { a.data.n += ev.by ?? 1; a.emit('dom', '#count', 'set', { n: a.data.n }); },
        total: (a) => a.reply('total', { n: a.data.n }),   // answers a call
      },
    },
  },
});

const c = sys.spawn('counter');                  // -> ActorRef
c.send('inc', { by: 2 });                        // post + pump
const { n } = await c.call('total');             // request/reply

System

  • define(spec)spec is { name, data, initial, states }; states[id] is { on, entry, exit, initial }; a transition is an action fn, a string target, or { target, when, then }.
  • spawn(name, opts?)ActorRef. A durable or store option throws (see the gaps below).
  • post(pid, event, data, { via, reply }?), send(name, event, data) (by registered name), call(target, event, data, { timeout }?)Promise.
  • register(name, pid), whereis(name), list(), has(name) — a JS-side name registry, since the core keeps none, matching the Node semantics.
  • plug(name, emit) or plug({ name, emit }).
  • observe(observer), tick(now), due(), run() / drive(), kill(pid), close().
  • snapshot(refOrPid) → the actor's active leaf-state ids; verify() → the core's invariant check (true when sound); inspect(name) → a machine's compiled shape, { name, states }.

ActorRef (from spawn)

send(event, data, opts?), call(event, data, opts?), kill(), and .pid, .spec, .idx, .gen.

the actor handle (a handler receives (actor, event))

actor.data (a JS object held JS-side, keyed by pid), actor.send(toPidOrName, event, data, { delay }?), actor.emit(port, target, event, data), actor.reply(event, data), actor.forward(toPidOrName) (hand the current event on, keeping its return address — choreography), actor.after(name, ms, data?), actor.cancel(name), actor.raise(event, data), actor.child(id), actor.in(stateId), actor.self, actor.parent.

call/reply is request/reply over a hidden one-shot reply port: call posts the event stamped with a reply address, the actor's actor.reply emits to it, and the host resolves the Promise with the reply data.

The memory model — a growable WebAssembly.Memory

ax.wasm is tiny and just the ax core. The host creates a small WebAssembly.Memory (8 pages to start, no fixed maximum) and imports it; the module owns the grant and grows the memory itself, to the wasm32 ceiling or to a maxPages cap the host opts into at create.

  • The facade's zero-argument ax_open(void) carves the core's grant from &__heap_base to the current end of linear memory, and wires the grow callback to __builtin_wasm_memory_grow, which extends linear memory a page at a time and returns the new span — NULL at the host's ceiling, so the core reports AX_NOMEM. There is no JS-side grant and no JS import for growth.
  • Strings cross through a facade-owned staging arena: axw_inbuf(len) returns a pointer the JS writes a UTF-8 C string into, and the facade interns it into a growable arena sized to the string, so no fixed buffer caps a name's length. JS never carves scratch out of the core's region.
  • Payloads and handlers stay JS-side, keyed by id; the wasm holds only the configuration. The payload store is cleared after each pumped post.

This follows minislack's model from examples/minislack/wasm/glue.c and the wasm platform seam. A grown page is the engine's for the instance's life, so a cached buffer view is re-read after every call that may allocate — memory.grow detaches it.

Building

From the repository root:

mdc compile bindings/browser/build.md

That tangles the core (ax.c), the bulk-memory floor (axr-wasm-mem.c), and the facade (ax-wasm.c), links them with clang into dist/ax.wasm, validates it, tangles ax-browser.js, and prints the byte size. The link is core-only: no axr, no axlog. It is built -Oz with --gc-sections and --strip-all, so the module stays small, about 26 KB.

What is here, and what is not

The binding backs every method the core can back, with no cap the core itself does not impose. What it leaves out is the axr runtime, whose features each need a host backend the browser core does not carry:

  • Durabilitystore, durable, evict, peek. A durable/store spawn throws a clear error rather than silently doing nothing.
  • Tracing and metrics — the observe-driven span engine and the Prometheus formatter.
  • The peer / TcpTransport — cross-runtime addressing and the wire.

These are axr (runtime) features, absent by construction. The binding exports { System, Actor, ActorRef }, the Node names minus TcpTransport.