@al8b/runtime-realtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # @al8b/runtime-realtime
2
+
3
+ Realtime transport adapter for [@al8b/runtime](../runtime/README.md). Enables multiplayer and synchronization features through a pluggable realtime bridge.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @al8b/runtime-realtime @al8b/runtime
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Implement a `RealtimeBridge` for your chosen transport (WebSocket, WebRTC, etc.), then adapt it to the runtime:
14
+
15
+ ```ts
16
+ import { createRealtimeBridge, type RealtimeBridge } from "@al8b/runtime-realtime";
17
+ import { createRuntime } from "@al8b/runtime";
18
+
19
+ // Your realtime transport implementation
20
+ const myWebSocketBridge: RealtimeBridge = {
21
+ async connect() {
22
+ // Connect to websocket server
23
+ },
24
+ async disconnect() {
25
+ // Clean up websocket
26
+ },
27
+ send(channel, payload) {
28
+ // Send message on channel
29
+ },
30
+ subscribe(channel, handler) {
31
+ // Listen for messages on channel
32
+ return () => {
33
+ // Unsubscribe
34
+ };
35
+ },
36
+ };
37
+
38
+ // Adapt it to the runtime
39
+ const bridge = {
40
+ ...createRealtimeBridge(myWebSocketBridge),
41
+ // Optionally add other bridge capabilities
42
+ getSession: () => getSessionFromServer(),
43
+ saveSnapshot: (snap) => cloudSave(snap),
44
+ };
45
+
46
+ // Create runtime with realtime bridge
47
+ const runtime = createRuntime({
48
+ sources: { /* ... */ },
49
+ resources: { /* ... */ },
50
+ bridge,
51
+ });
52
+
53
+ await runtime.start();
54
+ ```
55
+
56
+ ## API
57
+
58
+ ### `createRealtimeBridge(realtime: RealtimeBridge): RuntimeBridge`
59
+
60
+ Adapts a `RealtimeBridge` to a `RuntimeBridge`.
61
+
62
+ **Event Channels:**
63
+
64
+ The adapter subscribes to these channels:
65
+ - `"host.event"` - Incoming host events (mapped to HostEvent)
66
+ - `"player.message"` - Direct player-to-host messages
67
+
68
+ Game emissions are sent on:
69
+ - `"runtime.emit"` - Game-side events (name + payload)
70
+
71
+ **Composition:**
72
+
73
+ The returned `RuntimeBridge` only implements `emit` and `subscribe`. To add other capabilities:
74
+
75
+ ```ts
76
+ const bridge = {
77
+ ...createRealtimeBridge(realtime),
78
+ request: async (name, payload) => { /* ... */ },
79
+ getSession: () => { /* ... */ },
80
+ saveSnapshot: async (snap) => { /* ... */ },
81
+ loadSnapshot: async (meta) => { /* ... */ },
82
+ };
83
+ ```
84
+
85
+ ### `RealtimeBridge`
86
+
87
+ Interface for a realtime transport:
88
+
89
+ ```ts
90
+ export interface RealtimeBridge {
91
+ connect(): Promise<void>;
92
+ disconnect(): Promise<void>;
93
+ send(channel: string, payload: unknown): void;
94
+ subscribe(channel: string, handler: (payload: unknown) => void): () => void;
95
+ }
96
+ ```
97
+
98
+ ## Example: WebSocket Transport
99
+
100
+ ```ts
101
+ import { createRealtimeBridge } from "@al8b/runtime-realtime";
102
+ import type { RealtimeBridge } from "@al8b/runtime-realtime";
103
+
104
+ class WebSocketRealtimeBridge implements RealtimeBridge {
105
+ private ws: WebSocket | null = null;
106
+ private subscriptions = new Map<string, Set<(payload: unknown) => void>>();
107
+
108
+ async connect() {
109
+ return new Promise<void>((resolve, reject) => {
110
+ this.ws = new WebSocket("wss://realtime.example.com");
111
+ this.ws.onopen = () => resolve();
112
+ this.ws.onerror = () => reject(new Error("WebSocket connection failed"));
113
+ this.ws.onmessage = (event) => {
114
+ const message = JSON.parse(event.data);
115
+ const handlers = this.subscriptions.get(message.channel);
116
+ handlers?.forEach((h) => h(message.payload));
117
+ };
118
+ });
119
+ }
120
+
121
+ async disconnect() {
122
+ this.ws?.close();
123
+ }
124
+
125
+ send(channel: string, payload: unknown) {
126
+ this.ws?.send(JSON.stringify({ channel, payload }));
127
+ }
128
+
129
+ subscribe(channel: string, handler: (payload: unknown) => void) {
130
+ if (!this.subscriptions.has(channel)) {
131
+ this.subscriptions.set(channel, new Set());
132
+ }
133
+ this.subscriptions.get(channel)!.add(handler);
134
+ return () => {
135
+ this.subscriptions.get(channel)?.delete(handler);
136
+ };
137
+ }
138
+ }
139
+
140
+ const bridge = createRealtimeBridge(new WebSocketRealtimeBridge());
141
+ ```
142
+
143
+ ## Multiplayer Patterns
144
+
145
+ Once integrated, games can emit realtime events:
146
+
147
+ ```loot
148
+ // In LootiScript
149
+ host.emit("player.move", { x: 100, y: 50 })
150
+ host.emit("player.action", { action: "jump" })
151
+ ```
152
+
153
+ The realtime bridge forwards these to the backend, which can synchronize across players.
154
+
155
+ ## Notes
156
+
157
+ - The adapter is transport-agnostic; implement `RealtimeBridge` for your chosen protocol
158
+ - Channel names are conventions between your game and backend; adjust as needed
159
+ - Reliability, ordering, and backpressure handling are transport responsibilities
160
+ - Games should not assume synchronous delivery; use async request/response patterns for critical data
161
+
162
+ ## Scripts
163
+
164
+ ```bash
165
+ bun run build # Build the package
166
+ bun run test # Run tests
167
+ bun run clean # Clean build artifacts
168
+ ```
@@ -0,0 +1,27 @@
1
+ import { RealtimeBridge } from './types.mjs';
2
+ import { RuntimeBridge } from '@al8b/runtime';
3
+
4
+ /**
5
+ * Create a RuntimeBridge adapter from a RealtimeBridge.
6
+ *
7
+ * This adapter maps realtime transport channels to RuntimeBridge events.
8
+ * Incoming messages on the `"host.event"` channel are delivered as HostEvent.
9
+ * Outgoing `bridge.emit` calls are sent on the `"runtime.emit"` channel.
10
+ *
11
+ * Note: This adapter does not implement `request`, `getSession`, `saveSnapshot`,
12
+ * or `loadSnapshot`. Compose those capabilities separately:
13
+ *
14
+ * ```ts
15
+ * const bridge = {
16
+ * ...createRealtimeBridge(realtime),
17
+ * getSession: () => fetchSession(userId),
18
+ * saveSnapshot: (snap) => cloudSave(userId, snap),
19
+ * };
20
+ * ```
21
+ *
22
+ * @param realtime - The realtime transport bridge
23
+ * @returns A RuntimeBridge that integrates with the realtime transport
24
+ */
25
+ declare function createRealtimeBridge(realtime: RealtimeBridge): RuntimeBridge;
26
+
27
+ export { createRealtimeBridge };
@@ -0,0 +1,27 @@
1
+ import { RealtimeBridge } from './types.js';
2
+ import { RuntimeBridge } from '@al8b/runtime';
3
+
4
+ /**
5
+ * Create a RuntimeBridge adapter from a RealtimeBridge.
6
+ *
7
+ * This adapter maps realtime transport channels to RuntimeBridge events.
8
+ * Incoming messages on the `"host.event"` channel are delivered as HostEvent.
9
+ * Outgoing `bridge.emit` calls are sent on the `"runtime.emit"` channel.
10
+ *
11
+ * Note: This adapter does not implement `request`, `getSession`, `saveSnapshot`,
12
+ * or `loadSnapshot`. Compose those capabilities separately:
13
+ *
14
+ * ```ts
15
+ * const bridge = {
16
+ * ...createRealtimeBridge(realtime),
17
+ * getSession: () => fetchSession(userId),
18
+ * saveSnapshot: (snap) => cloudSave(userId, snap),
19
+ * };
20
+ * ```
21
+ *
22
+ * @param realtime - The realtime transport bridge
23
+ * @returns A RuntimeBridge that integrates with the realtime transport
24
+ */
25
+ declare function createRealtimeBridge(realtime: RealtimeBridge): RuntimeBridge;
26
+
27
+ export { createRealtimeBridge };
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/createRealtimeBridge.ts
22
+ var createRealtimeBridge_exports = {};
23
+ __export(createRealtimeBridge_exports, {
24
+ createRealtimeBridge: () => createRealtimeBridge
25
+ });
26
+ module.exports = __toCommonJS(createRealtimeBridge_exports);
27
+ function createRealtimeBridge(realtime) {
28
+ const isHostEvent = /* @__PURE__ */ __name((payload) => {
29
+ return payload !== null && typeof payload === "object" && "type" in payload && typeof payload.type === "string";
30
+ }, "isHostEvent");
31
+ const subscribe = /* @__PURE__ */ __name((handler) => {
32
+ const unsub1 = realtime.subscribe("host.event", (payload) => {
33
+ if (isHostEvent(payload)) {
34
+ handler({
35
+ ...payload,
36
+ source: payload.source ?? "realtime"
37
+ });
38
+ }
39
+ });
40
+ const unsub2 = realtime.subscribe("player.message", (payload) => {
41
+ handler({
42
+ type: "player.message",
43
+ payload,
44
+ source: "realtime"
45
+ });
46
+ });
47
+ return () => {
48
+ unsub1();
49
+ unsub2();
50
+ };
51
+ }, "subscribe");
52
+ const emit = /* @__PURE__ */ __name((name, payload) => {
53
+ realtime.send("runtime.emit", {
54
+ name,
55
+ payload
56
+ });
57
+ }, "emit");
58
+ return {
59
+ emit,
60
+ subscribe
61
+ };
62
+ }
63
+ __name(createRealtimeBridge, "createRealtimeBridge");
64
+ // Annotate the CommonJS export names for ESM import in node:
65
+ 0 && (module.exports = {
66
+ createRealtimeBridge
67
+ });
68
+ //# sourceMappingURL=createRealtimeBridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/createRealtimeBridge.ts"],"sourcesContent":["import type { RuntimeBridge, HostEvent } from \"@al8b/runtime\";\nimport type { RealtimeBridge } from \"./types\";\n\n/**\n * Create a RuntimeBridge adapter from a RealtimeBridge.\n *\n * This adapter maps realtime transport channels to RuntimeBridge events.\n * Incoming messages on the `\"host.event\"` channel are delivered as HostEvent.\n * Outgoing `bridge.emit` calls are sent on the `\"runtime.emit\"` channel.\n *\n * Note: This adapter does not implement `request`, `getSession`, `saveSnapshot`,\n * or `loadSnapshot`. Compose those capabilities separately:\n *\n * ```ts\n * const bridge = {\n * ...createRealtimeBridge(realtime),\n * getSession: () => fetchSession(userId),\n * saveSnapshot: (snap) => cloudSave(userId, snap),\n * };\n * ```\n *\n * @param realtime - The realtime transport bridge\n * @returns A RuntimeBridge that integrates with the realtime transport\n */\nexport function createRealtimeBridge(realtime: RealtimeBridge): RuntimeBridge {\n\tconst isHostEvent = (payload: unknown): payload is HostEvent => {\n\t\treturn (\n\t\t\tpayload !== null &&\n\t\t\ttypeof payload === \"object\" &&\n\t\t\t\"type\" in payload &&\n\t\t\ttypeof (payload as any).type === \"string\"\n\t\t);\n\t};\n\n\tconst subscribe: RuntimeBridge[\"subscribe\"] = (handler) => {\n\t\t// Subscribe to incoming host events from realtime\n\t\tconst unsub1 = realtime.subscribe(\"host.event\", (payload) => {\n\t\t\tif (isHostEvent(payload)) {\n\t\t\t\thandler({\n\t\t\t\t\t...payload,\n\t\t\t\t\tsource: (payload.source ?? \"realtime\") as \"host\" | \"backend\" | \"realtime\",\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t// Also subscribe to player messages for direct game-to-host communication\n\t\tconst unsub2 = realtime.subscribe(\"player.message\", (payload) => {\n\t\t\thandler({\n\t\t\t\ttype: \"player.message\",\n\t\t\t\tpayload,\n\t\t\t\tsource: \"realtime\",\n\t\t\t});\n\t\t});\n\n\t\treturn () => {\n\t\t\tunsub1();\n\t\t\tunsub2();\n\t\t};\n\t};\n\n\tconst emit: RuntimeBridge[\"emit\"] = (name, payload) => {\n\t\t// Forward game emissions back to the realtime transport\n\t\trealtime.send(\"runtime.emit\", { name, payload });\n\t};\n\n\treturn {\n\t\temit,\n\t\tsubscribe,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAGA;;;;;AAqBO,SAASA,qBAAqBC,UAAwB;AAC5D,QAAMC,cAAc,wBAACC,YAAAA;AACpB,WACCA,YAAY,QACZ,OAAOA,YAAY,YACnB,UAAUA,WACV,OAAQA,QAAgBC,SAAS;EAEnC,GAPoB;AASpB,QAAMC,YAAwC,wBAACC,YAAAA;AAE9C,UAAMC,SAASN,SAASI,UAAU,cAAc,CAACF,YAAAA;AAChD,UAAID,YAAYC,OAAAA,GAAU;AACzBG,gBAAQ;UACP,GAAGH;UACHK,QAASL,QAAQK,UAAU;QAC5B,CAAA;MACD;IACD,CAAA;AAGA,UAAMC,SAASR,SAASI,UAAU,kBAAkB,CAACF,YAAAA;AACpDG,cAAQ;QACPF,MAAM;QACND;QACAK,QAAQ;MACT,CAAA;IACD,CAAA;AAEA,WAAO,MAAA;AACND,aAAAA;AACAE,aAAAA;IACD;EACD,GAxB8C;AA0B9C,QAAMC,OAA8B,wBAACC,MAAMR,YAAAA;AAE1CF,aAASW,KAAK,gBAAgB;MAAED;MAAMR;IAAQ,CAAA;EAC/C,GAHoC;AAKpC,SAAO;IACNO;IACAL;EACD;AACD;AA7CgBL;","names":["createRealtimeBridge","realtime","isHostEvent","payload","type","subscribe","handler","unsub1","source","unsub2","emit","name","send"]}
@@ -0,0 +1,45 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/createRealtimeBridge.ts
5
+ function createRealtimeBridge(realtime) {
6
+ const isHostEvent = /* @__PURE__ */ __name((payload) => {
7
+ return payload !== null && typeof payload === "object" && "type" in payload && typeof payload.type === "string";
8
+ }, "isHostEvent");
9
+ const subscribe = /* @__PURE__ */ __name((handler) => {
10
+ const unsub1 = realtime.subscribe("host.event", (payload) => {
11
+ if (isHostEvent(payload)) {
12
+ handler({
13
+ ...payload,
14
+ source: payload.source ?? "realtime"
15
+ });
16
+ }
17
+ });
18
+ const unsub2 = realtime.subscribe("player.message", (payload) => {
19
+ handler({
20
+ type: "player.message",
21
+ payload,
22
+ source: "realtime"
23
+ });
24
+ });
25
+ return () => {
26
+ unsub1();
27
+ unsub2();
28
+ };
29
+ }, "subscribe");
30
+ const emit = /* @__PURE__ */ __name((name, payload) => {
31
+ realtime.send("runtime.emit", {
32
+ name,
33
+ payload
34
+ });
35
+ }, "emit");
36
+ return {
37
+ emit,
38
+ subscribe
39
+ };
40
+ }
41
+ __name(createRealtimeBridge, "createRealtimeBridge");
42
+ export {
43
+ createRealtimeBridge
44
+ };
45
+ //# sourceMappingURL=createRealtimeBridge.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/createRealtimeBridge.ts"],"sourcesContent":["import type { RuntimeBridge, HostEvent } from \"@al8b/runtime\";\nimport type { RealtimeBridge } from \"./types\";\n\n/**\n * Create a RuntimeBridge adapter from a RealtimeBridge.\n *\n * This adapter maps realtime transport channels to RuntimeBridge events.\n * Incoming messages on the `\"host.event\"` channel are delivered as HostEvent.\n * Outgoing `bridge.emit` calls are sent on the `\"runtime.emit\"` channel.\n *\n * Note: This adapter does not implement `request`, `getSession`, `saveSnapshot`,\n * or `loadSnapshot`. Compose those capabilities separately:\n *\n * ```ts\n * const bridge = {\n * ...createRealtimeBridge(realtime),\n * getSession: () => fetchSession(userId),\n * saveSnapshot: (snap) => cloudSave(userId, snap),\n * };\n * ```\n *\n * @param realtime - The realtime transport bridge\n * @returns A RuntimeBridge that integrates with the realtime transport\n */\nexport function createRealtimeBridge(realtime: RealtimeBridge): RuntimeBridge {\n\tconst isHostEvent = (payload: unknown): payload is HostEvent => {\n\t\treturn (\n\t\t\tpayload !== null &&\n\t\t\ttypeof payload === \"object\" &&\n\t\t\t\"type\" in payload &&\n\t\t\ttypeof (payload as any).type === \"string\"\n\t\t);\n\t};\n\n\tconst subscribe: RuntimeBridge[\"subscribe\"] = (handler) => {\n\t\t// Subscribe to incoming host events from realtime\n\t\tconst unsub1 = realtime.subscribe(\"host.event\", (payload) => {\n\t\t\tif (isHostEvent(payload)) {\n\t\t\t\thandler({\n\t\t\t\t\t...payload,\n\t\t\t\t\tsource: (payload.source ?? \"realtime\") as \"host\" | \"backend\" | \"realtime\",\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t// Also subscribe to player messages for direct game-to-host communication\n\t\tconst unsub2 = realtime.subscribe(\"player.message\", (payload) => {\n\t\t\thandler({\n\t\t\t\ttype: \"player.message\",\n\t\t\t\tpayload,\n\t\t\t\tsource: \"realtime\",\n\t\t\t});\n\t\t});\n\n\t\treturn () => {\n\t\t\tunsub1();\n\t\t\tunsub2();\n\t\t};\n\t};\n\n\tconst emit: RuntimeBridge[\"emit\"] = (name, payload) => {\n\t\t// Forward game emissions back to the realtime transport\n\t\trealtime.send(\"runtime.emit\", { name, payload });\n\t};\n\n\treturn {\n\t\temit,\n\t\tsubscribe,\n\t};\n}\n"],"mappings":";;;;AAwBO,SAASA,qBAAqBC,UAAwB;AAC5D,QAAMC,cAAc,wBAACC,YAAAA;AACpB,WACCA,YAAY,QACZ,OAAOA,YAAY,YACnB,UAAUA,WACV,OAAQA,QAAgBC,SAAS;EAEnC,GAPoB;AASpB,QAAMC,YAAwC,wBAACC,YAAAA;AAE9C,UAAMC,SAASN,SAASI,UAAU,cAAc,CAACF,YAAAA;AAChD,UAAID,YAAYC,OAAAA,GAAU;AACzBG,gBAAQ;UACP,GAAGH;UACHK,QAASL,QAAQK,UAAU;QAC5B,CAAA;MACD;IACD,CAAA;AAGA,UAAMC,SAASR,SAASI,UAAU,kBAAkB,CAACF,YAAAA;AACpDG,cAAQ;QACPF,MAAM;QACND;QACAK,QAAQ;MACT,CAAA;IACD,CAAA;AAEA,WAAO,MAAA;AACND,aAAAA;AACAE,aAAAA;IACD;EACD,GAxB8C;AA0B9C,QAAMC,OAA8B,wBAACC,MAAMR,YAAAA;AAE1CF,aAASW,KAAK,gBAAgB;MAAED;MAAMR;IAAQ,CAAA;EAC/C,GAHoC;AAKpC,SAAO;IACNO;IACAL;EACD;AACD;AA7CgBL;","names":["createRealtimeBridge","realtime","isHostEvent","payload","type","subscribe","handler","unsub1","source","unsub2","emit","name","send"]}
@@ -0,0 +1,3 @@
1
+ export { RealtimeBridge } from './types.mjs';
2
+ export { createRealtimeBridge } from './createRealtimeBridge.mjs';
3
+ import '@al8b/runtime';
@@ -0,0 +1,3 @@
1
+ export { RealtimeBridge } from './types.js';
2
+ export { createRealtimeBridge } from './createRealtimeBridge.js';
3
+ import '@al8b/runtime';
package/dist/index.js ADDED
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ createRealtimeBridge: () => createRealtimeBridge
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/createRealtimeBridge.ts
29
+ function createRealtimeBridge(realtime) {
30
+ const isHostEvent = /* @__PURE__ */ __name((payload) => {
31
+ return payload !== null && typeof payload === "object" && "type" in payload && typeof payload.type === "string";
32
+ }, "isHostEvent");
33
+ const subscribe = /* @__PURE__ */ __name((handler) => {
34
+ const unsub1 = realtime.subscribe("host.event", (payload) => {
35
+ if (isHostEvent(payload)) {
36
+ handler({
37
+ ...payload,
38
+ source: payload.source ?? "realtime"
39
+ });
40
+ }
41
+ });
42
+ const unsub2 = realtime.subscribe("player.message", (payload) => {
43
+ handler({
44
+ type: "player.message",
45
+ payload,
46
+ source: "realtime"
47
+ });
48
+ });
49
+ return () => {
50
+ unsub1();
51
+ unsub2();
52
+ };
53
+ }, "subscribe");
54
+ const emit = /* @__PURE__ */ __name((name, payload) => {
55
+ realtime.send("runtime.emit", {
56
+ name,
57
+ payload
58
+ });
59
+ }, "emit");
60
+ return {
61
+ emit,
62
+ subscribe
63
+ };
64
+ }
65
+ __name(createRealtimeBridge, "createRealtimeBridge");
66
+ // Annotate the CommonJS export names for ESM import in node:
67
+ 0 && (module.exports = {
68
+ createRealtimeBridge
69
+ });
70
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/createRealtimeBridge.ts"],"sourcesContent":["export type { RealtimeBridge } from \"./types\";\nexport { createRealtimeBridge } from \"./createRealtimeBridge\";\n","import type { RuntimeBridge, HostEvent } from \"@al8b/runtime\";\nimport type { RealtimeBridge } from \"./types\";\n\n/**\n * Create a RuntimeBridge adapter from a RealtimeBridge.\n *\n * This adapter maps realtime transport channels to RuntimeBridge events.\n * Incoming messages on the `\"host.event\"` channel are delivered as HostEvent.\n * Outgoing `bridge.emit` calls are sent on the `\"runtime.emit\"` channel.\n *\n * Note: This adapter does not implement `request`, `getSession`, `saveSnapshot`,\n * or `loadSnapshot`. Compose those capabilities separately:\n *\n * ```ts\n * const bridge = {\n * ...createRealtimeBridge(realtime),\n * getSession: () => fetchSession(userId),\n * saveSnapshot: (snap) => cloudSave(userId, snap),\n * };\n * ```\n *\n * @param realtime - The realtime transport bridge\n * @returns A RuntimeBridge that integrates with the realtime transport\n */\nexport function createRealtimeBridge(realtime: RealtimeBridge): RuntimeBridge {\n\tconst isHostEvent = (payload: unknown): payload is HostEvent => {\n\t\treturn (\n\t\t\tpayload !== null &&\n\t\t\ttypeof payload === \"object\" &&\n\t\t\t\"type\" in payload &&\n\t\t\ttypeof (payload as any).type === \"string\"\n\t\t);\n\t};\n\n\tconst subscribe: RuntimeBridge[\"subscribe\"] = (handler) => {\n\t\t// Subscribe to incoming host events from realtime\n\t\tconst unsub1 = realtime.subscribe(\"host.event\", (payload) => {\n\t\t\tif (isHostEvent(payload)) {\n\t\t\t\thandler({\n\t\t\t\t\t...payload,\n\t\t\t\t\tsource: (payload.source ?? \"realtime\") as \"host\" | \"backend\" | \"realtime\",\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t// Also subscribe to player messages for direct game-to-host communication\n\t\tconst unsub2 = realtime.subscribe(\"player.message\", (payload) => {\n\t\t\thandler({\n\t\t\t\ttype: \"player.message\",\n\t\t\t\tpayload,\n\t\t\t\tsource: \"realtime\",\n\t\t\t});\n\t\t});\n\n\t\treturn () => {\n\t\t\tunsub1();\n\t\t\tunsub2();\n\t\t};\n\t};\n\n\tconst emit: RuntimeBridge[\"emit\"] = (name, payload) => {\n\t\t// Forward game emissions back to the realtime transport\n\t\trealtime.send(\"runtime.emit\", { name, payload });\n\t};\n\n\treturn {\n\t\temit,\n\t\tsubscribe,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA;;;;;;;ACuBO,SAASA,qBAAqBC,UAAwB;AAC5D,QAAMC,cAAc,wBAACC,YAAAA;AACpB,WACCA,YAAY,QACZ,OAAOA,YAAY,YACnB,UAAUA,WACV,OAAQA,QAAgBC,SAAS;EAEnC,GAPoB;AASpB,QAAMC,YAAwC,wBAACC,YAAAA;AAE9C,UAAMC,SAASN,SAASI,UAAU,cAAc,CAACF,YAAAA;AAChD,UAAID,YAAYC,OAAAA,GAAU;AACzBG,gBAAQ;UACP,GAAGH;UACHK,QAASL,QAAQK,UAAU;QAC5B,CAAA;MACD;IACD,CAAA;AAGA,UAAMC,SAASR,SAASI,UAAU,kBAAkB,CAACF,YAAAA;AACpDG,cAAQ;QACPF,MAAM;QACND;QACAK,QAAQ;MACT,CAAA;IACD,CAAA;AAEA,WAAO,MAAA;AACND,aAAAA;AACAE,aAAAA;IACD;EACD,GAxB8C;AA0B9C,QAAMC,OAA8B,wBAACC,MAAMR,YAAAA;AAE1CF,aAASW,KAAK,gBAAgB;MAAED;MAAMR;IAAQ,CAAA;EAC/C,GAHoC;AAKpC,SAAO;IACNO;IACAL;EACD;AACD;AA7CgBL;","names":["createRealtimeBridge","realtime","isHostEvent","payload","type","subscribe","handler","unsub1","source","unsub2","emit","name","send"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,45 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/createRealtimeBridge.ts
5
+ function createRealtimeBridge(realtime) {
6
+ const isHostEvent = /* @__PURE__ */ __name((payload) => {
7
+ return payload !== null && typeof payload === "object" && "type" in payload && typeof payload.type === "string";
8
+ }, "isHostEvent");
9
+ const subscribe = /* @__PURE__ */ __name((handler) => {
10
+ const unsub1 = realtime.subscribe("host.event", (payload) => {
11
+ if (isHostEvent(payload)) {
12
+ handler({
13
+ ...payload,
14
+ source: payload.source ?? "realtime"
15
+ });
16
+ }
17
+ });
18
+ const unsub2 = realtime.subscribe("player.message", (payload) => {
19
+ handler({
20
+ type: "player.message",
21
+ payload,
22
+ source: "realtime"
23
+ });
24
+ });
25
+ return () => {
26
+ unsub1();
27
+ unsub2();
28
+ };
29
+ }, "subscribe");
30
+ const emit = /* @__PURE__ */ __name((name, payload) => {
31
+ realtime.send("runtime.emit", {
32
+ name,
33
+ payload
34
+ });
35
+ }, "emit");
36
+ return {
37
+ emit,
38
+ subscribe
39
+ };
40
+ }
41
+ __name(createRealtimeBridge, "createRealtimeBridge");
42
+ export {
43
+ createRealtimeBridge
44
+ };
45
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/createRealtimeBridge.ts"],"sourcesContent":["import type { RuntimeBridge, HostEvent } from \"@al8b/runtime\";\nimport type { RealtimeBridge } from \"./types\";\n\n/**\n * Create a RuntimeBridge adapter from a RealtimeBridge.\n *\n * This adapter maps realtime transport channels to RuntimeBridge events.\n * Incoming messages on the `\"host.event\"` channel are delivered as HostEvent.\n * Outgoing `bridge.emit` calls are sent on the `\"runtime.emit\"` channel.\n *\n * Note: This adapter does not implement `request`, `getSession`, `saveSnapshot`,\n * or `loadSnapshot`. Compose those capabilities separately:\n *\n * ```ts\n * const bridge = {\n * ...createRealtimeBridge(realtime),\n * getSession: () => fetchSession(userId),\n * saveSnapshot: (snap) => cloudSave(userId, snap),\n * };\n * ```\n *\n * @param realtime - The realtime transport bridge\n * @returns A RuntimeBridge that integrates with the realtime transport\n */\nexport function createRealtimeBridge(realtime: RealtimeBridge): RuntimeBridge {\n\tconst isHostEvent = (payload: unknown): payload is HostEvent => {\n\t\treturn (\n\t\t\tpayload !== null &&\n\t\t\ttypeof payload === \"object\" &&\n\t\t\t\"type\" in payload &&\n\t\t\ttypeof (payload as any).type === \"string\"\n\t\t);\n\t};\n\n\tconst subscribe: RuntimeBridge[\"subscribe\"] = (handler) => {\n\t\t// Subscribe to incoming host events from realtime\n\t\tconst unsub1 = realtime.subscribe(\"host.event\", (payload) => {\n\t\t\tif (isHostEvent(payload)) {\n\t\t\t\thandler({\n\t\t\t\t\t...payload,\n\t\t\t\t\tsource: (payload.source ?? \"realtime\") as \"host\" | \"backend\" | \"realtime\",\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t// Also subscribe to player messages for direct game-to-host communication\n\t\tconst unsub2 = realtime.subscribe(\"player.message\", (payload) => {\n\t\t\thandler({\n\t\t\t\ttype: \"player.message\",\n\t\t\t\tpayload,\n\t\t\t\tsource: \"realtime\",\n\t\t\t});\n\t\t});\n\n\t\treturn () => {\n\t\t\tunsub1();\n\t\t\tunsub2();\n\t\t};\n\t};\n\n\tconst emit: RuntimeBridge[\"emit\"] = (name, payload) => {\n\t\t// Forward game emissions back to the realtime transport\n\t\trealtime.send(\"runtime.emit\", { name, payload });\n\t};\n\n\treturn {\n\t\temit,\n\t\tsubscribe,\n\t};\n}\n"],"mappings":";;;;AAwBO,SAASA,qBAAqBC,UAAwB;AAC5D,QAAMC,cAAc,wBAACC,YAAAA;AACpB,WACCA,YAAY,QACZ,OAAOA,YAAY,YACnB,UAAUA,WACV,OAAQA,QAAgBC,SAAS;EAEnC,GAPoB;AASpB,QAAMC,YAAwC,wBAACC,YAAAA;AAE9C,UAAMC,SAASN,SAASI,UAAU,cAAc,CAACF,YAAAA;AAChD,UAAID,YAAYC,OAAAA,GAAU;AACzBG,gBAAQ;UACP,GAAGH;UACHK,QAASL,QAAQK,UAAU;QAC5B,CAAA;MACD;IACD,CAAA;AAGA,UAAMC,SAASR,SAASI,UAAU,kBAAkB,CAACF,YAAAA;AACpDG,cAAQ;QACPF,MAAM;QACND;QACAK,QAAQ;MACT,CAAA;IACD,CAAA;AAEA,WAAO,MAAA;AACND,aAAAA;AACAE,aAAAA;IACD;EACD,GAxB8C;AA0B9C,QAAMC,OAA8B,wBAACC,MAAMR,YAAAA;AAE1CF,aAASW,KAAK,gBAAgB;MAAED;MAAMR;IAAQ,CAAA;EAC/C,GAHoC;AAKpC,SAAO;IACNO;IACAL;EACD;AACD;AA7CgBL;","names":["createRealtimeBridge","realtime","isHostEvent","payload","type","subscribe","handler","unsub1","source","unsub2","emit","name","send"]}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Realtime transport bridge for multiplayer and synchronization features.
3
+ *
4
+ * This interface abstracts over specific realtime transports (WebSocket, WebRTC, etc.)
5
+ * and can be adapted into a RuntimeBridge for host-side communication.
6
+ */
7
+ interface RealtimeBridge {
8
+ /**
9
+ * Connect to the realtime service.
10
+ */
11
+ connect(): Promise<void>;
12
+ /**
13
+ * Disconnect from the realtime service.
14
+ */
15
+ disconnect(): Promise<void>;
16
+ /**
17
+ * Send a message on a channel.
18
+ * @param channel - Channel name
19
+ * @param payload - Message payload
20
+ */
21
+ send(channel: string, payload: unknown): void;
22
+ /**
23
+ * Subscribe to a channel.
24
+ * @param channel - Channel name
25
+ * @param handler - Called when a message arrives on the channel
26
+ * @returns Unsubscribe function
27
+ */
28
+ subscribe(channel: string, handler: (payload: unknown) => void): () => void;
29
+ }
30
+
31
+ export type { RealtimeBridge };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Realtime transport bridge for multiplayer and synchronization features.
3
+ *
4
+ * This interface abstracts over specific realtime transports (WebSocket, WebRTC, etc.)
5
+ * and can be adapted into a RuntimeBridge for host-side communication.
6
+ */
7
+ interface RealtimeBridge {
8
+ /**
9
+ * Connect to the realtime service.
10
+ */
11
+ connect(): Promise<void>;
12
+ /**
13
+ * Disconnect from the realtime service.
14
+ */
15
+ disconnect(): Promise<void>;
16
+ /**
17
+ * Send a message on a channel.
18
+ * @param channel - Channel name
19
+ * @param payload - Message payload
20
+ */
21
+ send(channel: string, payload: unknown): void;
22
+ /**
23
+ * Subscribe to a channel.
24
+ * @param channel - Channel name
25
+ * @param handler - Called when a message arrives on the channel
26
+ * @returns Unsubscribe function
27
+ */
28
+ subscribe(channel: string, handler: (payload: unknown) => void): () => void;
29
+ }
30
+
31
+ export type { RealtimeBridge };
package/dist/types.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/types.ts
17
+ var types_exports = {};
18
+ module.exports = __toCommonJS(types_exports);
19
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["/**\n * Realtime transport bridge for multiplayer and synchronization features.\n *\n * This interface abstracts over specific realtime transports (WebSocket, WebRTC, etc.)\n * and can be adapted into a RuntimeBridge for host-side communication.\n */\nexport interface RealtimeBridge {\n\t/**\n\t * Connect to the realtime service.\n\t */\n\tconnect(): Promise<void>;\n\n\t/**\n\t * Disconnect from the realtime service.\n\t */\n\tdisconnect(): Promise<void>;\n\n\t/**\n\t * Send a message on a channel.\n\t * @param channel - Channel name\n\t * @param payload - Message payload\n\t */\n\tsend(channel: string, payload: unknown): void;\n\n\t/**\n\t * Subscribe to a channel.\n\t * @param channel - Channel name\n\t * @param handler - Called when a message arrives on the channel\n\t * @returns Unsubscribe function\n\t */\n\tsubscribe(channel: string, handler: (payload: unknown) => void): () => void;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;;","names":[]}
package/dist/types.mjs ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@al8b/runtime-realtime",
3
+ "version": "0.1.0",
4
+ "sideEffects": false,
5
+ "files": [
6
+ "dist/**/*",
7
+ "README.md",
8
+ "package.json"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "clean": "bun --bun ../../../scripts/clean-package.mjs dist",
13
+ "test": "vitest run --passWithNoTests"
14
+ },
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.mjs",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "dependencies": {
27
+ "@al8b/runtime": "workspace:*"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "~5.9.3"
31
+ },
32
+ "keywords": [
33
+ "runtime",
34
+ "realtime",
35
+ "multiplayer",
36
+ "websocket"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }