@al8b/runtime-realtime
v0.1.15
Published
Realtime transport adapter for [@al8b/runtime](../runtime/README.md). Enables multiplayer and synchronization features through a pluggable realtime bridge.
Maintainers
Readme
@al8b/runtime-realtime
Realtime transport adapter for @al8b/runtime. Enables multiplayer and synchronization features through a pluggable realtime bridge.
Installation
npm install @al8b/runtime-realtime @al8b/runtimeUsage
Implement a RealtimeBridge for your chosen transport (WebSocket, WebRTC, etc.), then adapt it to the runtime:
import { createRealtimeBridge, type RealtimeBridge } from "@al8b/runtime-realtime";
import { createRuntime } from "@al8b/runtime";
// Your realtime transport implementation
const myWebSocketBridge: RealtimeBridge = {
async connect() {
// Connect to websocket server
},
async disconnect() {
// Clean up websocket
},
send(channel, payload) {
// Send message on channel
},
subscribe(channel, handler) {
// Listen for messages on channel
return () => {
// Unsubscribe
};
},
};
// Adapt it to the runtime
const bridge = {
...createRealtimeBridge(myWebSocketBridge),
// Optionally add other bridge capabilities
getSession: () => getSessionFromServer(),
saveSnapshot: (snap) => cloudSave(snap),
};
// Create runtime with realtime bridge
const runtime = createRuntime({
sources: { /* ... */ },
resources: { /* ... */ },
bridge,
});
await runtime.start();API
createRealtimeBridge(realtime: RealtimeBridge): RuntimeBridge
Adapts a RealtimeBridge to a RuntimeBridge.
Event Channels:
The adapter subscribes to these channels:
"host.event"- Incoming host events (mapped to HostEvent)"player.message"- Direct player-to-host messages
Game emissions are sent on:
"runtime.emit"- Game-side events (name + payload)
Composition:
The returned RuntimeBridge only implements emit and subscribe. To add other capabilities:
const bridge = {
...createRealtimeBridge(realtime),
request: async (name, payload) => { /* ... */ },
getSession: () => { /* ... */ },
saveSnapshot: async (snap) => { /* ... */ },
loadSnapshot: async (meta) => { /* ... */ },
};RealtimeBridge
Interface for a realtime transport:
export interface RealtimeBridge {
connect(): Promise<void>;
disconnect(): Promise<void>;
send(channel: string, payload: unknown): void;
subscribe(channel: string, handler: (payload: unknown) => void): () => void;
}Combining with @al8b/http-bridge
For multiplayer games that also need REST API access (user profiles, leaderboards, inventory), compose @al8b/http-bridge with @al8b/runtime-realtime:
import { createRealtimeBridge } from "@al8b/runtime-realtime";
import { createHttpBridge } from "@al8b/http-bridge";
// Realtime bridge handles real-time player sync
// HttpBridge handles REST API calls
const bridge = {
...createRealtimeBridge(myWebSocket),
...createHttpBridge({ baseUrl: "https://api.mygame.com" }),
// Both getSession and saveSnapshot/loadSnapshot should come from
// your auth layer (server or secure storage), not just HTTP:
getSession: () => getSessionFromServer(),
saveSnapshot: async (snap) => cloudSave(snap),
loadSnapshot: async (meta) => cloudLoad(meta),
};
createRuntime({ bridge, sources: { main: "..." } });In your LootiScript game:
-- Realtime: fire-and-forget player events
host.emit("player.move", { x: 100, y: 50 });
-- REST: fetch data with callback
host.request("user.getProfile", { id: session.user().id }, function(response)
print("Hello " + response.name);
end);Example: WebSocket Transport
import { createRealtimeBridge } from "@al8b/runtime-realtime";
import type { RealtimeBridge } from "@al8b/runtime-realtime";
class WebSocketRealtimeBridge implements RealtimeBridge {
private ws: WebSocket | null = null;
private subscriptions = new Map<string, Set<(payload: unknown) => void>>();
async connect() {
return new Promise<void>((resolve, reject) => {
this.ws = new WebSocket("wss://realtime.example.com");
this.ws.onopen = () => resolve();
this.ws.onerror = () => reject(new Error("WebSocket connection failed"));
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
const handlers = this.subscriptions.get(message.channel);
handlers?.forEach((h) => h(message.payload));
};
});
}
async disconnect() {
this.ws?.close();
}
send(channel: string, payload: unknown) {
this.ws?.send(JSON.stringify({ channel, payload }));
}
subscribe(channel: string, handler: (payload: unknown) => void) {
if (!this.subscriptions.has(channel)) {
this.subscriptions.set(channel, new Set());
}
this.subscriptions.get(channel)!.add(handler);
return () => {
this.subscriptions.get(channel)?.delete(handler);
};
}
}
const bridge = createRealtimeBridge(new WebSocketRealtimeBridge());Multiplayer Patterns
Once integrated, games can emit realtime events:
// In LootiScript
host.emit("player.move", { x: 100, y: 50 })
host.emit("player.action", { action: "jump" })The realtime bridge forwards these to the backend, which can synchronize across players.
Notes
- The adapter is transport-agnostic; implement
RealtimeBridgefor your chosen protocol - Channel names are conventions between your game and backend; adjust as needed
- Reliability, ordering, and backpressure handling are transport responsibilities
- Games should not assume synchronous delivery; use async request/response patterns for critical data
Scripts
bun run build # Build the package
bun run test # Run tests
bun run clean # Clean build artifacts