@signe/room 2.10.0 → 3.0.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/chunk-EUXUH3YW.js +15 -0
  3. package/dist/chunk-EUXUH3YW.js.map +1 -0
  4. package/dist/cloudflare/index.d.ts +71 -0
  5. package/dist/cloudflare/index.js +320 -0
  6. package/dist/cloudflare/index.js.map +1 -0
  7. package/dist/index.d.ts +66 -187
  8. package/dist/index.js +727 -106
  9. package/dist/index.js.map +1 -1
  10. package/dist/node/index.d.ts +164 -0
  11. package/dist/node/index.js +786 -0
  12. package/dist/node/index.js.map +1 -0
  13. package/dist/party-dNs-hqkq.d.ts +175 -0
  14. package/examples/cloudflare/README.md +62 -0
  15. package/examples/cloudflare/node_modules/.bin/tsc +17 -0
  16. package/examples/cloudflare/node_modules/.bin/tsserver +17 -0
  17. package/examples/cloudflare/node_modules/.bin/wrangler +17 -0
  18. package/examples/cloudflare/node_modules/.bin/wrangler2 +17 -0
  19. package/examples/cloudflare/package.json +24 -0
  20. package/examples/cloudflare/public/index.html +443 -0
  21. package/examples/cloudflare/src/index.ts +28 -0
  22. package/examples/cloudflare/src/room.ts +44 -0
  23. package/examples/cloudflare/tsconfig.json +10 -0
  24. package/examples/cloudflare/wrangler.jsonc +25 -0
  25. package/examples/node/README.md +57 -0
  26. package/examples/node/node_modules/.bin/tsc +17 -0
  27. package/examples/node/node_modules/.bin/tsserver +17 -0
  28. package/examples/node/node_modules/.bin/tsx +17 -0
  29. package/examples/node/package.json +23 -0
  30. package/examples/node/public/index.html +443 -0
  31. package/examples/node/room.ts +44 -0
  32. package/examples/node/server.sqlite.ts +52 -0
  33. package/examples/node/server.ts +51 -0
  34. package/examples/node/tsconfig.json +10 -0
  35. package/examples/node-game/README.md +66 -0
  36. package/examples/node-game/package.json +23 -0
  37. package/examples/node-game/public/index.html +705 -0
  38. package/examples/node-game/room.ts +145 -0
  39. package/examples/node-game/server.sqlite.ts +54 -0
  40. package/examples/node-game/server.ts +53 -0
  41. package/examples/node-game/tsconfig.json +10 -0
  42. package/examples/node-shard/README.md +32 -0
  43. package/examples/node-shard/dev.ts +39 -0
  44. package/examples/node-shard/package.json +24 -0
  45. package/examples/node-shard/public/index.html +777 -0
  46. package/examples/node-shard/room-server.ts +68 -0
  47. package/examples/node-shard/room.ts +105 -0
  48. package/examples/node-shard/shared.ts +6 -0
  49. package/examples/node-shard/tsconfig.json +14 -0
  50. package/examples/node-shard/world-server.ts +169 -0
  51. package/package.json +14 -5
  52. package/readme.md +371 -4
  53. package/src/cloudflare/index.ts +474 -0
  54. package/src/jwt.ts +1 -5
  55. package/src/mock.ts +29 -7
  56. package/src/node/index.ts +1112 -0
  57. package/src/server.ts +600 -51
  58. package/src/session.guard.ts +6 -2
  59. package/src/shard.ts +91 -23
  60. package/src/storage.ts +29 -5
  61. package/src/testing.ts +4 -3
  62. package/src/types/party.ts +4 -1
  63. package/src/world.guard.ts +23 -4
  64. package/src/world.ts +121 -21
  65. package/examples/game/.vscode/launch.json +0 -11
  66. package/examples/game/.vscode/settings.json +0 -11
  67. package/examples/game/README.md +0 -40
  68. package/examples/game/app/client.tsx +0 -15
  69. package/examples/game/app/components/Admin.tsx +0 -1089
  70. package/examples/game/app/components/Room.tsx +0 -162
  71. package/examples/game/app/styles.css +0 -31
  72. package/examples/game/package-lock.json +0 -225
  73. package/examples/game/package.json +0 -20
  74. package/examples/game/party/game.room.ts +0 -32
  75. package/examples/game/party/server.ts +0 -10
  76. package/examples/game/party/shard.ts +0 -5
  77. package/examples/game/partykit.json +0 -14
  78. package/examples/game/public/favicon.ico +0 -0
  79. package/examples/game/public/index.html +0 -27
  80. package/examples/game/public/normalize.css +0 -351
  81. package/examples/game/shared/room.schema.ts +0 -14
  82. package/examples/game/tsconfig.json +0 -109
@@ -0,0 +1,443 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Signe Durable Object Room</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: light;
10
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
11
+ }
12
+
13
+ * {
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ body {
18
+ margin: 0;
19
+ min-height: 100vh;
20
+ background: #f5f7fa;
21
+ color: #17191c;
22
+ }
23
+
24
+ main {
25
+ width: min(960px, calc(100vw - 32px));
26
+ margin: 0 auto;
27
+ padding: 32px 0;
28
+ display: grid;
29
+ gap: 18px;
30
+ }
31
+
32
+ header {
33
+ display: flex;
34
+ align-items: end;
35
+ justify-content: space-between;
36
+ gap: 16px;
37
+ }
38
+
39
+ h1,
40
+ h2 {
41
+ margin: 0;
42
+ line-height: 1.1;
43
+ }
44
+
45
+ h1 {
46
+ font-size: 30px;
47
+ }
48
+
49
+ h2 {
50
+ font-size: 18px;
51
+ }
52
+
53
+ .muted,
54
+ .status {
55
+ color: #5b6472;
56
+ }
57
+
58
+ .panel,
59
+ .card {
60
+ border: 1px solid #d9dde3;
61
+ border-radius: 8px;
62
+ background: #ffffff;
63
+ }
64
+
65
+ .panel {
66
+ padding: 20px;
67
+ display: grid;
68
+ gap: 16px;
69
+ }
70
+
71
+ .join {
72
+ max-width: 520px;
73
+ }
74
+
75
+ form {
76
+ display: grid;
77
+ gap: 12px;
78
+ }
79
+
80
+ label {
81
+ display: grid;
82
+ gap: 6px;
83
+ font-size: 14px;
84
+ font-weight: 600;
85
+ }
86
+
87
+ input {
88
+ min-height: 42px;
89
+ border: 1px solid #c9d0d9;
90
+ border-radius: 6px;
91
+ padding: 0 12px;
92
+ font: inherit;
93
+ }
94
+
95
+ button {
96
+ min-height: 40px;
97
+ border: 1px solid #17191c;
98
+ border-radius: 6px;
99
+ background: #17191c;
100
+ color: #ffffff;
101
+ padding: 0 14px;
102
+ font: inherit;
103
+ cursor: pointer;
104
+ }
105
+
106
+ button.secondary {
107
+ background: #ffffff;
108
+ color: #17191c;
109
+ }
110
+
111
+ .room {
112
+ display: grid;
113
+ grid-template-columns: minmax(0, 1fr) 280px;
114
+ gap: 18px;
115
+ }
116
+
117
+ .count {
118
+ font-size: 72px;
119
+ line-height: 1;
120
+ font-weight: 700;
121
+ }
122
+
123
+ .actions {
124
+ display: flex;
125
+ gap: 10px;
126
+ flex-wrap: wrap;
127
+ }
128
+
129
+ .session {
130
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
131
+ font-size: 12px;
132
+ }
133
+
134
+ .users {
135
+ padding: 16px;
136
+ display: grid;
137
+ gap: 12px;
138
+ align-content: start;
139
+ }
140
+
141
+ .user-list {
142
+ display: grid;
143
+ gap: 8px;
144
+ }
145
+
146
+ .user {
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: space-between;
150
+ gap: 10px;
151
+ min-height: 40px;
152
+ padding: 8px 10px;
153
+ border: 1px solid #e1e5ea;
154
+ border-radius: 6px;
155
+ }
156
+
157
+ .badge {
158
+ border-radius: 999px;
159
+ padding: 3px 8px;
160
+ font-size: 12px;
161
+ background: #edf1f5;
162
+ color: #4b5563;
163
+ white-space: nowrap;
164
+ }
165
+
166
+ .badge.online {
167
+ background: #e7f7ed;
168
+ color: #146c36;
169
+ }
170
+
171
+ .hidden {
172
+ display: none;
173
+ }
174
+
175
+ @media (max-width: 720px) {
176
+ header,
177
+ .room {
178
+ grid-template-columns: 1fr;
179
+ display: grid;
180
+ }
181
+ }
182
+ </style>
183
+ </head>
184
+ <body>
185
+ <main>
186
+ <header>
187
+ <div>
188
+ <h1>Signe Durable Object Room</h1>
189
+ <div class="muted" id="roomLabel">Choose a room</div>
190
+ </div>
191
+ <button id="leaveRoom" class="secondary hidden" type="button">Change room</button>
192
+ </header>
193
+
194
+ <section class="panel join" id="joinPanel">
195
+ <h2>Join a room</h2>
196
+ <form id="joinForm">
197
+ <label>
198
+ Room
199
+ <input id="roomInput" name="room" autocomplete="off" required value="demo" />
200
+ </label>
201
+ <label>
202
+ Name
203
+ <input id="nameInput" name="name" autocomplete="name" required />
204
+ </label>
205
+ <button type="submit">Enter room</button>
206
+ </form>
207
+ </section>
208
+
209
+ <section class="room hidden" id="roomPanel">
210
+ <div class="panel">
211
+ <h2>Counter</h2>
212
+ <div class="count" id="count">0</div>
213
+ <div class="actions">
214
+ <button id="increment" type="button">Increment</button>
215
+ <button id="reset" class="secondary" type="button">Reset</button>
216
+ <button id="resetSession" class="secondary" type="button">New session</button>
217
+ </div>
218
+ <div class="status" id="status">Disconnected</div>
219
+ <div class="muted session" id="sessionLabel"></div>
220
+ </div>
221
+
222
+ <aside class="card users">
223
+ <h2>Users</h2>
224
+ <div class="user-list" id="users"></div>
225
+ </aside>
226
+ </section>
227
+ </main>
228
+
229
+ <script>
230
+ const storagePrefix = "signe-cloudflare";
231
+ const state = {
232
+ socket: null,
233
+ roomId: getRoomIdFromPath(),
234
+ name: localStorage.getItem(`${storagePrefix}-name`) || "",
235
+ sessionId: localStorage.getItem(`${storagePrefix}-session-id`) || "",
236
+ users: {},
237
+ };
238
+
239
+ const joinPanel = document.querySelector("#joinPanel");
240
+ const roomPanel = document.querySelector("#roomPanel");
241
+ const roomLabel = document.querySelector("#roomLabel");
242
+ const leaveRoom = document.querySelector("#leaveRoom");
243
+ const joinForm = document.querySelector("#joinForm");
244
+ const roomInput = document.querySelector("#roomInput");
245
+ const nameInput = document.querySelector("#nameInput");
246
+ const count = document.querySelector("#count");
247
+ const status = document.querySelector("#status");
248
+ const sessionLabel = document.querySelector("#sessionLabel");
249
+ const users = document.querySelector("#users");
250
+
251
+ nameInput.value = state.name;
252
+
253
+ if (state.roomId) {
254
+ roomInput.value = state.roomId;
255
+ }
256
+
257
+ joinForm.addEventListener("submit", (event) => {
258
+ event.preventDefault();
259
+ const roomId = normalizeRoomId(roomInput.value);
260
+ const name = nameInput.value.trim() || "Anonymous";
261
+
262
+ localStorage.setItem(`${storagePrefix}-name`, name);
263
+ history.pushState({}, "", `/rooms/${encodeURIComponent(roomId)}`);
264
+ enterRoom(roomId, name);
265
+ });
266
+
267
+ leaveRoom.addEventListener("click", () => {
268
+ disconnect();
269
+ history.pushState({}, "", "/");
270
+ showJoin();
271
+ });
272
+
273
+ window.addEventListener("popstate", () => {
274
+ const roomId = getRoomIdFromPath();
275
+ if (roomId) {
276
+ enterRoom(roomId, state.name || "Anonymous");
277
+ } else {
278
+ disconnect();
279
+ showJoin();
280
+ }
281
+ });
282
+
283
+ document.querySelector("#increment").addEventListener("click", () => {
284
+ state.socket?.send(JSON.stringify({ action: "increment", value: { amount: 1 } }));
285
+ });
286
+
287
+ document.querySelector("#reset").addEventListener("click", async () => {
288
+ if (!state.roomId) return;
289
+ await fetch(`/parties/main/${encodeURIComponent(state.roomId)}/reset`, { method: "POST" });
290
+ });
291
+
292
+ document.querySelector("#resetSession").addEventListener("click", () => {
293
+ if (!state.roomId) return;
294
+ disconnect();
295
+ localStorage.removeItem(`${storagePrefix}-session-id`);
296
+ state.sessionId = "";
297
+ enterRoom(state.roomId, state.name || "Anonymous");
298
+ });
299
+
300
+ if (state.roomId) {
301
+ enterRoom(state.roomId, state.name || "Anonymous");
302
+ } else {
303
+ showJoin();
304
+ }
305
+
306
+ function enterRoom(roomId, name) {
307
+ disconnect();
308
+ state.roomId = roomId;
309
+ state.name = name;
310
+ state.users = {};
311
+ count.textContent = "0";
312
+ renderUsers();
313
+ showRoom();
314
+
315
+ const protocol = location.protocol === "https:" ? "wss" : "ws";
316
+ const params = new URLSearchParams({ name, id: getSessionId() });
317
+ state.socket = new WebSocket(`${protocol}://${location.host}/parties/main/${encodeURIComponent(roomId)}?${params}`);
318
+
319
+ state.socket.addEventListener("open", () => {
320
+ status.textContent = "Connected";
321
+ });
322
+
323
+ state.socket.addEventListener("close", () => {
324
+ status.textContent = "Disconnected";
325
+ });
326
+
327
+ state.socket.addEventListener("message", (event) => {
328
+ const packet = JSON.parse(event.data);
329
+ if (packet.type !== "sync" || !packet.value) return;
330
+
331
+ if (typeof packet.value.count === "number") {
332
+ count.textContent = String(packet.value.count);
333
+ }
334
+
335
+ if (packet.value.users) {
336
+ mergeUsers(state.users, packet.value.users);
337
+ renderUsers();
338
+ }
339
+ });
340
+ }
341
+
342
+ function disconnect() {
343
+ if (state.socket) {
344
+ state.socket.close();
345
+ state.socket = null;
346
+ }
347
+ }
348
+
349
+ function showJoin() {
350
+ state.roomId = null;
351
+ joinPanel.classList.remove("hidden");
352
+ roomPanel.classList.add("hidden");
353
+ leaveRoom.classList.add("hidden");
354
+ roomLabel.textContent = "Choose a room";
355
+ status.textContent = "Disconnected";
356
+ sessionLabel.textContent = "";
357
+ }
358
+
359
+ function showRoom() {
360
+ joinPanel.classList.add("hidden");
361
+ roomPanel.classList.remove("hidden");
362
+ leaveRoom.classList.remove("hidden");
363
+ roomLabel.textContent = `/rooms/${state.roomId}`;
364
+ sessionLabel.textContent = `session ${getSessionId().slice(0, 8)}`;
365
+ }
366
+
367
+ function getSessionId() {
368
+ if (!state.sessionId) {
369
+ state.sessionId = crypto.randomUUID();
370
+ localStorage.setItem(`${storagePrefix}-session-id`, state.sessionId);
371
+ }
372
+
373
+ return state.sessionId;
374
+ }
375
+
376
+ function renderUsers() {
377
+ const entries = Object.entries(state.users);
378
+ users.innerHTML = entries.length
379
+ ? entries.map(([, user]) => {
380
+ const name = escapeHtml(user.name || "Anonymous");
381
+ const connected = Boolean(user.connected);
382
+ return `
383
+ <div class="user">
384
+ <span>${name}</span>
385
+ <span class="badge ${connected ? "online" : ""}">${connected ? "connected" : "offline"}</span>
386
+ </div>
387
+ `;
388
+ }).join("")
389
+ : `<div class="muted">No users yet</div>`;
390
+ }
391
+
392
+ function mergeUsers(target, patch) {
393
+ for (const [id, value] of Object.entries(patch)) {
394
+ if (value === "$delete") {
395
+ delete target[id];
396
+ continue;
397
+ }
398
+
399
+ target[id] = mergeValue(target[id], value);
400
+ }
401
+ }
402
+
403
+ function mergeValue(current, patch) {
404
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
405
+ return patch;
406
+ }
407
+
408
+ const next = current && typeof current === "object" && !Array.isArray(current)
409
+ ? { ...current }
410
+ : {};
411
+
412
+ for (const [key, value] of Object.entries(patch)) {
413
+ if (value === "$delete") {
414
+ delete next[key];
415
+ } else {
416
+ next[key] = mergeValue(next[key], value);
417
+ }
418
+ }
419
+
420
+ return next;
421
+ }
422
+
423
+ function getRoomIdFromPath() {
424
+ const match = location.pathname.match(/^\/rooms\/([^/]+)$/);
425
+ return match ? decodeURIComponent(match[1]) : null;
426
+ }
427
+
428
+ function normalizeRoomId(value) {
429
+ return value.trim().replace(/[^a-zA-Z0-9_-]/g, "-") || "demo";
430
+ }
431
+
432
+ function escapeHtml(value) {
433
+ return value.replace(/[&<>"']/g, (char) => ({
434
+ "&": "&amp;",
435
+ "<": "&lt;",
436
+ ">": "&gt;",
437
+ "\"": "&quot;",
438
+ "'": "&#039;",
439
+ }[char]));
440
+ }
441
+ </script>
442
+ </body>
443
+ </html>
@@ -0,0 +1,28 @@
1
+ import { createCloudflareRoomWorker, SigneRoomDurableObject } from "@signe/room/cloudflare";
2
+ import { CounterServer } from "./room";
3
+
4
+ export { SigneRoomDurableObject };
5
+
6
+ interface Env extends Record<string, unknown> {
7
+ ROOMS: DurableObjectNamespace;
8
+ ASSETS: {
9
+ fetch(request: Request): Promise<Response>;
10
+ };
11
+ }
12
+
13
+ const roomWorker = createCloudflareRoomWorker(CounterServer, {
14
+ binding: "ROOMS",
15
+ partiesPath: "/parties/main",
16
+ });
17
+
18
+ export default {
19
+ async fetch(request: Request, env: Env, ctx: unknown): Promise<Response> {
20
+ const url = new URL(request.url);
21
+
22
+ if (url.pathname.startsWith("/parties/main/")) {
23
+ return roomWorker.fetch(request, env, ctx);
24
+ }
25
+
26
+ return env.ASSETS.fetch(request);
27
+ },
28
+ };
@@ -0,0 +1,44 @@
1
+ import { Action, Request, Room, Server } from "@signe/room";
2
+ import { signal } from "@signe/reactive";
3
+ import { connected, sync, users } from "@signe/sync";
4
+ import { z } from "zod";
5
+
6
+ class DemoUser {
7
+ @sync() name = signal("Anonymous");
8
+ @connected() connected = signal(false);
9
+ }
10
+
11
+ @Room({ path: "{roomId}", sessionExpiryTime: 2000 })
12
+ class CounterRoom {
13
+ @sync() count = signal(0);
14
+ @users(DemoUser) users = signal<Record<string, DemoUser>>({});
15
+
16
+ onJoin(user: DemoUser, _conn: unknown, ctx: { request?: Request }) {
17
+ const url = new URL(ctx.request?.url ?? "http://localhost");
18
+ const name = url.searchParams.get("name")?.trim();
19
+
20
+ if (name) {
21
+ user.name.set(name.slice(0, 40));
22
+ }
23
+ }
24
+
25
+ @Action("increment", z.object({ amount: z.number().optional() }))
26
+ increment(_user: DemoUser, value: { amount?: number }) {
27
+ this.count.update((count) => count + (value.amount ?? 1));
28
+ }
29
+
30
+ @Request({ path: "/count" })
31
+ getCount() {
32
+ return { count: this.count() };
33
+ }
34
+
35
+ @Request({ path: "/reset", method: "POST" })
36
+ reset() {
37
+ this.count.set(0);
38
+ return { count: this.count() };
39
+ }
40
+ }
41
+
42
+ export class CounterServer extends Server {
43
+ rooms = [CounterRoom];
44
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "types": ["@cloudflare/workers-types", "node"]
7
+ },
8
+ "include": ["src/**/*.ts"],
9
+ "exclude": []
10
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "./node_modules/wrangler/config-schema.json",
3
+ "name": "signe-room-cloudflare-example",
4
+ "main": "src/index.ts",
5
+ "compatibility_date": "2024-12-18",
6
+ "assets": {
7
+ "directory": "./public",
8
+ "binding": "ASSETS",
9
+ "not_found_handling": "single-page-application"
10
+ },
11
+ "durable_objects": {
12
+ "bindings": [
13
+ {
14
+ "name": "ROOMS",
15
+ "class_name": "SigneRoomDurableObject"
16
+ }
17
+ ]
18
+ },
19
+ "migrations": [
20
+ {
21
+ "tag": "v1",
22
+ "new_sqlite_classes": ["SigneRoomDurableObject"]
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,57 @@
1
+ # `@signe/room` Node Adapter Example
2
+
3
+ This example runs a `@signe/room` server in a plain Node.js HTTP server with
4
+ WebSocket upgrades handled by `ws`.
5
+
6
+ ```bash
7
+ pnpm install
8
+ pnpm --filter @signe/room-node-example dev
9
+ ```
10
+
11
+ Open http://localhost:3000, choose a room id and a display name, then enter the
12
+ room. The browser URL changes to `/rooms/:roomId`; refreshing that URL serves
13
+ the same app and reconnects to the matching room.
14
+
15
+ - App URL: `http://localhost:3000/rooms/demo`
16
+ - HTTP: `GET /parties/main/demo/count`
17
+ - HTTP: `POST /parties/main/demo/reset`
18
+ - WebSocket: `ws://localhost:3000/parties/main/demo?name=Sam&id=browser-session-id`
19
+
20
+ The room uses `@users()` and `@connected()` from `@signe/sync`, so the right
21
+ panel shows every known user and whether they are currently connected.
22
+
23
+ The browser example stores a session id in `localStorage` and sends it as the
24
+ WebSocket `id` query parameter. That id is the private session id used by the
25
+ room server, so refreshing or reconnecting brings the same user back online. If
26
+ you click "New session" or clear local storage, the next connection gets a new
27
+ session and the previous user remains visible as offline until normal session
28
+ cleanup removes it. Multiple tabs with the same stored session id stay attached
29
+ to the same user and receive room broadcasts independently. Server handlers
30
+ receive a unique `conn.id` for each WebSocket and the shared private session id
31
+ as `conn.sessionId`.
32
+
33
+ ## SQLite storage
34
+
35
+ The default example uses `createMemoryNodeRoomStorage()`. To run the same room
36
+ with the package's SQLite-backed `room.storage`, use:
37
+
38
+ ```bash
39
+ pnpm --filter @signe/room-node-example dev:sqlite
40
+ ```
41
+
42
+ The SQLite example uses `createSqliteNodeRoomStorage()` from `@signe/room/node`
43
+ and Node's built-in `node:sqlite` module. It stores room state in
44
+ `packages/room/examples/node/rooms.sqlite`.
45
+ Because the users collection is persisted, reopening the SQLite example can show
46
+ previous users as offline until they reconnect with the same session id.
47
+
48
+ The storage provider is passed to `createNodeRoomTransport`:
49
+
50
+ ```ts
51
+ const transport = createNodeRoomTransport(CounterServer, {
52
+ partiesPath: "/parties/main",
53
+ storage: createSqliteNodeRoomStorage({
54
+ databasePath: "./rooms.sqlite",
55
+ }),
56
+ });
57
+ ```
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
15
+ else
16
+ exec node "$basedir/../typescript/bin/tsc" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
15
+ else
16
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/dist/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/tsx@4.21.0/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/dist/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/tsx@4.21.0/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../tsx/dist/cli.mjs" "$@"
15
+ else
16
+ exec node "$basedir/../tsx/dist/cli.mjs" "$@"
17
+ fi
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@signe/room-node-example",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx server.ts",
8
+ "dev:sqlite": "node --experimental-sqlite --import tsx server.sqlite.ts",
9
+ "build": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@signe/reactive": "workspace:*",
13
+ "@signe/room": "workspace:*",
14
+ "@signe/sync": "workspace:*",
15
+ "ws": "^8.17.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.13.9",
19
+ "@types/ws": "^8.5.12",
20
+ "tsx": "^4.19.2",
21
+ "typescript": "^5.4.5"
22
+ }
23
+ }