Technology · @orbitalfoundation/bus

The bus

A late-binding, declarative publish/subscribe event bus that runs unchanged in Node and the browser, with no build step. It is the bottom layer of the orbital stack. Everything else, Volume included, is a listener on it.

npm install @orbitalfoundation/bus
import { createBus } from '@orbitalfoundation/bus'

const bus = createBus()

// register a listener
bus.register({
  id: 'counter',
  resolve(event) { if (event.tick) this.n = (this.n ?? 0) + 1 },
})

// publish (fan-out) and query (first responder) through the same call
await bus.resolve({ run: true, ticks: 5, dt: 1 })
const answer = await bus.resolve({ ping: true })   // first non-undefined return wins

Why late binding

The bus came out of building simulations in which many agents, written by different parties, run together in a system that is continuously extended and never stopped. New agents arrive while the system is running. Two things change when such a program is wired through a bus rather than through imports.

Who names whom. In ordinary imperative code, module A imports module B and calls it by name. For that line to compile, A must know that B exists, what it is called, and the shape of its interface. On the bus, A publishes an object with a thing key and B has registered interest in events that carry thing. Neither names the other. What they share is a vocabulary, and the vocabulary becomes the most important artifact in the system. The bus carries a schema registry for that reason: it is the one place otherwise anonymous parties agree on what words mean.

When the graph is fixed. In an import-wired program the call graph is fixed at author time and frozen at compile time. On the bus it is assembled at runtime from whatever listeners have registered by the time an event is dispatched. A module loaded an hour into the run can talk to services that registered an hour earlier, with no edit to any existing file. Alan Kay called the extreme form of this "extreme late binding of all things."

Early binding buys a proof, before the program runs, that every call has a target of the right shape. Late binding gives that proof up in exchange for runtime flexibility, hot loading, and a kernel small enough to run anywhere.

Forward, imperative The bus
Who names whom A imports and calls B neither; both name a vocabulary
When the graph is fixed author or compile time runtime, per dispatch
What is checked ahead of time call targets and types nothing; the schema is advisory
What is traded away flexibility, hot loading a compile-time correctness proof
The shared artifact interfaces the message vocabulary

One shape

One object shape serves three roles by its contents alone. An object with a resolve function is a listener, and passing it to the bus registers it. An object without one is an event, and passing it dispatches it. A listener that returns a value answers a query and stops the chain. Registering and publishing are the same operation on the same data.

Events are not cloned. Listeners may read and mutate the event in place, and downstream listeners see the mutation. State flows as objects decorated with properties, which is what gives the system its entity-component feel. The cost is mutation discipline. The specification lists the footguns.

Manifests

An application is assembled by loading manifests: ordinary ES modules whose exports are objects to register or dispatch. A manifest is declarative in shape but real JavaScript, so "twelve agents at incrementing positions" is a loop rather than a thousand lines of JSON. An entry can inherit from a template module and shallow-override its properties. A missing manifest is not fatal; it logs and returns empty, which allows a program to probe for optional manifests over HTTP.

The manifest loader, the tick driver and the schema registry are themselves ordinary listeners registered onto a bare bus. There are no privileged code paths.

Entities and components

The entity-component pattern rides on live decoration. An entity is an object. Components are properties that listeners add to it. Filtering on which keys an object carries is the bus's ordinary subscription mechanism, used as an organizing principle. The bus sits below this layer and does not know the word "component."

Documents

The repository keeps three documents at three altitudes. The primer is the why: where these ideas come from, the history of JavaScript's event model, the formal patterns the design instantiates (mediator, blackboard, chain of responsibility, tuple spaces), and a critical reading of its tradeoffs. The specification is the law, every load-bearing semantic stated precisely. The conformance tests are the executable form of the specification.

The bus is descended from orbital-sys, the 2024 JavaScript exploration of the same idea, which remains as the record of where it came from. Before that the same event-driven agent patterns were built in Rust; those sandboxes are on the work page. The design is implementation-agnostic. Version 2.0.1 is on npm, MIT licensed. Source: orbitalfoundation/orbital-bus.

Reviewed 2026-09-21