Shared
Handlers aren't expected to touch disk, but a single instance serves many connections and several
handlers often need to see the same data. The broker offers an in-memory shared store on every
ctx — ctx.shared — with a namespaced key/value API and a publish/subscribe
channel. Proposed the surface below.
Key/value
| Call | Returns | Meaning |
|---|---|---|
shared.get(key) | Promise<value | undefined> |
Read a key. undefined if unset. |
shared.set(key, value) | Promise<void> |
Write a key. value must be structured-cloneable (below). |
shared.delete(key) | Promise<void> |
Remove a key. |
The API is async throughout so the same code works whether the store is a local heap or
reached over a socket (see the note below). Keys are strings; namespace them with a prefix
("echo:count") to avoid collisions between handlers.
Publish / subscribe
Beyond point reads and writes, the store carries a channel so one handler (or one connection) can notify others of an event without polling.
| Call | Meaning |
|---|---|
shared.subscribe(channel, fn) | Register fn(message) for a
channel. Returns an unsubscribe function. |
shared.publish(channel, message) | Deliver message to every
current subscriber of the channel. message must be structured-cloneable. |
Example
A chat relay: each connection joins a channel, publishes lines it receives, and writes out lines others publish. Presence lives in the key/value store; the fan-out is pub/sub.
// relay.tcp.4000.port.js
export async function handle(conn, ctx) {
const n = ((await ctx.shared.get("relay:peers")) ?? 0) + 1;
await ctx.shared.set("relay:peers", n);
const off = ctx.shared.subscribe("relay:line", (line) => conn.write(line));
conn.on("data", (buf) => ctx.shared.publish("relay:line", buf));
conn.on("close", async () => {
off();
await ctx.shared.set("relay:peers", (await ctx.shared.get("relay:peers")) - 1);
});
}
Value constraints
Stored values and published messages must be structured-cloneable: primitives,
plain objects and arrays, Map, Set, Date, ArrayBuffer
and typed arrays. Not cloneable — and so not storable — are functions, class instances with
behaviour, sockets, and streams. This is what lets the store move a value between processes
unchanged if handlers are ever isolated.
Cross-process semantics
Today handlers run in the broker's process, so ctx.shared is a shared heap and reads
are effectively instant. The API is deliberately async and clone-constrained anyway, so
that the same handler code keeps working if handlers later move into isolated processes and
the store is served by the broker over a local socket. Write to the API, not to the current
implementation.
The store is in memory and does not survive a broker restart. When state must be durable, back it with datahoster rather than having the handler write to disk itself.