alepha@docs:~/framework/docs/reference/primitives$
cat $room.md | pretty1 min read
#$room
#Import
typescript
1import { $room } from "alepha/websocket";
#Overview
Defines a stateful WebSocket room - the home for an authoritative, optionally tick-driven simulation.
Where $websocket is a stateless, per-message pub/sub handler, $room
holds in-memory state across messages and drives a server-side tick loop. It
is the primitive a real-time game server is built on:
tickHz > 0→ an authoritative simulation room. The loop runs only while a socket is connected (an empty room costs nothing), callingonTick(room, dt)every1000/tickHzms. Inside a tick you readroom.state, iterateroom.connections, and push per-recipient frames withroom.send(id, msg)orroom.broadcast(msg).tickHzomitted → a headless coordinator addressed by id and reached through server-sidemethods. This is how you model cross-room party state or a single-owner presence lease.
Same code runs on Node (one process, real timers) and on Cloudflare (one
Durable Object per channelPath:roomId, the loop alive while the socket is).
#Examples
An authoritative 20Hz world
typescript
1class GameServer { 2 world = $channel({ 3 path: "/ws/world", 4 schema: { in: serverMsg, out: clientIntent }, 5 }); 6 7 room = $room({ 8 channel: this.world, 9 tickHz: 20,10 state: () => new World(),11 onJoin: (room, conn) => room.state.addPlayer(conn.id),12 onMessage: (room, conn, intent) => room.state.enqueue(conn.id, intent),13 onTick: (room, dt) => {14 room.state.step(dt);15 for (const conn of room.connections)16 room.send(conn.id, room.state.viewFor(conn.id));17 },18 onLeave: (room, conn) => room.state.removePlayer(conn.id),19 onEmpty: (room) => room.state.persist(),20 });21}
A headless coordinator (party-wide state)
typescript
1class Coordinator { 2 party = $channel({ path: "/ws/party", schema: { in: any, out: any } }); 3 4 session = $room({ 5 channel: this.party, 6 state: () => ({ switches: {} as Record<string, boolean> }), 7 methods: { 8 setSwitch: (room, id: string, value: boolean) => { 9 room.state.switches[id] = value;10 },11 snapshot: (room) => room.state.switches,12 },13 });14 15 async flip(partyId: string, id: string) {16 await this.session.call(partyId, "setSwitch", id, true);17 }18}