@signe/room 2.9.4 → 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 (81) hide show
  1. package/CHANGELOG.md +13 -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 +65 -188
  8. package/dist/index.js +742 -146
  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 +377 -11
  53. package/src/cloudflare/index.ts +474 -0
  54. package/src/mock.ts +29 -7
  55. package/src/node/index.ts +1112 -0
  56. package/src/server.ts +626 -90
  57. package/src/session.guard.ts +6 -2
  58. package/src/shard.ts +91 -23
  59. package/src/storage.ts +29 -5
  60. package/src/testing.ts +4 -3
  61. package/src/types/party.ts +4 -1
  62. package/src/world.guard.ts +23 -4
  63. package/src/world.ts +170 -79
  64. package/examples/game/.vscode/launch.json +0 -11
  65. package/examples/game/.vscode/settings.json +0 -11
  66. package/examples/game/README.md +0 -40
  67. package/examples/game/app/client.tsx +0 -15
  68. package/examples/game/app/components/Admin.tsx +0 -1089
  69. package/examples/game/app/components/Room.tsx +0 -162
  70. package/examples/game/app/styles.css +0 -31
  71. package/examples/game/package-lock.json +0 -225
  72. package/examples/game/package.json +0 -20
  73. package/examples/game/party/game.room.ts +0 -32
  74. package/examples/game/party/server.ts +0 -10
  75. package/examples/game/party/shard.ts +0 -5
  76. package/examples/game/partykit.json +0 -14
  77. package/examples/game/public/favicon.ico +0 -0
  78. package/examples/game/public/index.html +0 -27
  79. package/examples/game/public/normalize.css +0 -351
  80. package/examples/game/shared/room.schema.ts +0 -14
  81. package/examples/game/tsconfig.json +0 -109
@@ -0,0 +1,786 @@
1
+ import "../chunk-EUXUH3YW.js";
2
+
3
+ // src/node/index.ts
4
+ import { EventEmitter } from "node:events";
5
+ import { createRequire } from "node:module";
6
+ var DEFAULT_PARTIES_PATH = "/parties/main";
7
+ var WEBSOCKET_OPEN = 1;
8
+ var DEFAULT_SQLITE_BUSY_TIMEOUT_MS = 5e3;
9
+ var DEFAULT_SQLITE_BUSY_RETRIES = 3;
10
+ var SQLITE_RETRY_BASE_DELAY_MS = 25;
11
+ function createMemoryNodeRoomStorage(options = {}) {
12
+ return new MemoryNodeRoomStorage(options);
13
+ }
14
+ function createSqliteNodeRoomStorage(options) {
15
+ return new SqliteNodeRoomStorage(options);
16
+ }
17
+ function createNodeRoomTransport(ServerClass, options = {}) {
18
+ return new NodeRoomTransport(ServerClass, options);
19
+ }
20
+ var MemoryNodeRoomStorage = class {
21
+ constructor(options = {}) {
22
+ this.rooms = /* @__PURE__ */ new Map();
23
+ if (options.snapshot) {
24
+ this.restore(options.snapshot);
25
+ }
26
+ }
27
+ getStorage(namespace, roomId) {
28
+ const key = getStorageKey(namespace, roomId);
29
+ let memory = this.rooms.get(key);
30
+ if (!memory) {
31
+ memory = /* @__PURE__ */ new Map();
32
+ this.rooms.set(key, memory);
33
+ }
34
+ return new MemoryNodeRoomStorageInstance(memory);
35
+ }
36
+ snapshot() {
37
+ const snapshot = {};
38
+ for (const [roomKey, memory] of this.rooms) {
39
+ if (memory.size === 0) {
40
+ continue;
41
+ }
42
+ snapshot[roomKey] = Array.from(memory.entries()).map(([key, value]) => [
43
+ key,
44
+ cloneStorageValue(value)
45
+ ]);
46
+ }
47
+ return snapshot;
48
+ }
49
+ restore(snapshot) {
50
+ this.clear();
51
+ for (const [roomKey, entries] of Object.entries(snapshot)) {
52
+ this.rooms.set(
53
+ roomKey,
54
+ new Map(entries.map(([key, value]) => [key, cloneStorageValue(value)]))
55
+ );
56
+ }
57
+ }
58
+ clear() {
59
+ this.rooms.clear();
60
+ }
61
+ };
62
+ var MemoryNodeRoomStorageInstance = class {
63
+ constructor(memory) {
64
+ this.memory = memory;
65
+ }
66
+ async put(keyOrEntries, value) {
67
+ if (typeof keyOrEntries === "string") {
68
+ this.memory.set(keyOrEntries, value);
69
+ return;
70
+ }
71
+ for (const [key, entryValue] of Object.entries(keyOrEntries)) {
72
+ this.memory.set(key, entryValue);
73
+ }
74
+ }
75
+ async get(key) {
76
+ return this.memory.get(key);
77
+ }
78
+ async delete(keyOrKeys) {
79
+ if (Array.isArray(keyOrKeys)) {
80
+ let deleted = 0;
81
+ for (const key of keyOrKeys) {
82
+ if (this.memory.delete(key)) {
83
+ deleted += 1;
84
+ }
85
+ }
86
+ return deleted;
87
+ }
88
+ return this.memory.delete(keyOrKeys);
89
+ }
90
+ async list(options = {}) {
91
+ let entries = Array.from(this.memory.entries());
92
+ if (options.prefix !== void 0) {
93
+ entries = entries.filter(([key]) => key.startsWith(options.prefix));
94
+ }
95
+ if (options.start !== void 0) {
96
+ entries = entries.filter(([key]) => key >= options.start);
97
+ }
98
+ if (options.startAfter !== void 0) {
99
+ entries = entries.filter(([key]) => key > options.startAfter);
100
+ }
101
+ if (options.end !== void 0) {
102
+ entries = entries.filter(([key]) => key < options.end);
103
+ }
104
+ entries.sort(([a], [b]) => a.localeCompare(b));
105
+ if (options.reverse) {
106
+ entries.reverse();
107
+ }
108
+ if (options.limit !== void 0) {
109
+ entries = entries.slice(0, options.limit);
110
+ }
111
+ return new Map(entries);
112
+ }
113
+ };
114
+ var SqliteNodeRoomStorage = class {
115
+ constructor(options) {
116
+ this.options = options;
117
+ this.configured = false;
118
+ if (!options.database && !options.databasePath) {
119
+ throw new Error("createSqliteNodeRoomStorage requires `database` or `databasePath`.");
120
+ }
121
+ this.tableName = options.tableName ?? "signe_room_storage";
122
+ assertSafeSqlIdentifier(this.tableName);
123
+ }
124
+ async getStorage(namespace, roomId) {
125
+ const database = await this.getDatabase();
126
+ await this.ensureConfigured(database);
127
+ return new SqliteNodeRoomStorageInstance(
128
+ database,
129
+ this.tableName,
130
+ namespace,
131
+ roomId,
132
+ this.options.busyRetries ?? DEFAULT_SQLITE_BUSY_RETRIES
133
+ );
134
+ }
135
+ async getDatabase() {
136
+ if (!this.databasePromise) {
137
+ this.databasePromise = this.createDatabase();
138
+ }
139
+ return this.databasePromise;
140
+ }
141
+ async createDatabase() {
142
+ if (this.options.database) {
143
+ return this.options.database;
144
+ }
145
+ const sqliteModule = loadNodeSqliteModule();
146
+ const { DatabaseSync } = sqliteModule;
147
+ return new DatabaseSync(this.options.databasePath);
148
+ }
149
+ async ensureConfigured(database) {
150
+ if (this.configured) {
151
+ return;
152
+ }
153
+ const busyTimeoutMs = normalizeSqliteTimeout(
154
+ this.options.busyTimeoutMs ?? DEFAULT_SQLITE_BUSY_TIMEOUT_MS
155
+ );
156
+ const journalMode = normalizeSqliteJournalMode(this.options.journalMode ?? "WAL");
157
+ runSqliteOperation(
158
+ () => {
159
+ database.exec(`PRAGMA busy_timeout = ${busyTimeoutMs}`);
160
+ database.exec(`PRAGMA journal_mode = ${journalMode}`);
161
+ database.exec(`
162
+ CREATE TABLE IF NOT EXISTS ${this.tableName} (
163
+ namespace TEXT NOT NULL,
164
+ room_id TEXT NOT NULL,
165
+ key TEXT NOT NULL,
166
+ value TEXT NOT NULL,
167
+ PRIMARY KEY (namespace, room_id, key)
168
+ )
169
+ `);
170
+ },
171
+ this.options.busyRetries ?? DEFAULT_SQLITE_BUSY_RETRIES
172
+ );
173
+ this.configured = true;
174
+ }
175
+ };
176
+ var SqliteNodeRoomStorageInstance = class {
177
+ constructor(database, tableName, namespace, roomId, busyRetries) {
178
+ this.database = database;
179
+ this.tableName = tableName;
180
+ this.namespace = namespace;
181
+ this.roomId = roomId;
182
+ this.busyRetries = busyRetries;
183
+ }
184
+ async get(key) {
185
+ const row = runSqliteOperation(
186
+ () => this.database.prepare(`
187
+ SELECT value
188
+ FROM ${this.tableName}
189
+ WHERE namespace = ? AND room_id = ? AND key = ?
190
+ `).get(this.namespace, this.roomId, key),
191
+ this.busyRetries
192
+ );
193
+ return row ? JSON.parse(row.value) : void 0;
194
+ }
195
+ async put(keyOrEntries, value) {
196
+ const entries = typeof keyOrEntries === "string" ? [[keyOrEntries, value]] : Object.entries(keyOrEntries);
197
+ runSqliteOperation(
198
+ () => {
199
+ const statement = this.database.prepare(`
200
+ INSERT INTO ${this.tableName} (namespace, room_id, key, value)
201
+ VALUES (?, ?, ?, ?)
202
+ ON CONFLICT(namespace, room_id, key) DO UPDATE SET value = excluded.value
203
+ `);
204
+ for (const [key, entryValue] of entries) {
205
+ statement.run(this.namespace, this.roomId, key, JSON.stringify(entryValue));
206
+ }
207
+ },
208
+ this.busyRetries
209
+ );
210
+ }
211
+ async delete(keyOrKeys) {
212
+ if (Array.isArray(keyOrKeys)) {
213
+ if (keyOrKeys.length === 0) {
214
+ return 0;
215
+ }
216
+ const result2 = runSqliteOperation(
217
+ () => this.database.prepare(`
218
+ DELETE FROM ${this.tableName}
219
+ WHERE namespace = ? AND room_id = ? AND key IN (${keyOrKeys.map(() => "?").join(", ")})
220
+ `).run(this.namespace, this.roomId, ...keyOrKeys),
221
+ this.busyRetries
222
+ );
223
+ return Number(result2.changes);
224
+ }
225
+ const result = runSqliteOperation(
226
+ () => this.database.prepare(`
227
+ DELETE FROM ${this.tableName}
228
+ WHERE namespace = ? AND room_id = ? AND key = ?
229
+ `).run(this.namespace, this.roomId, keyOrKeys),
230
+ this.busyRetries
231
+ );
232
+ return Number(result.changes) > 0;
233
+ }
234
+ async list(options = {}) {
235
+ const conditions = ["namespace = ?", "room_id = ?"];
236
+ const params = [this.namespace, this.roomId];
237
+ if (options.prefix !== void 0) {
238
+ conditions.push("key >= ?", "key < ?");
239
+ params.push(options.prefix, getPrefixEnd(options.prefix));
240
+ }
241
+ if (options.start !== void 0) {
242
+ conditions.push("key >= ?");
243
+ params.push(options.start);
244
+ }
245
+ if (options.startAfter !== void 0) {
246
+ conditions.push("key > ?");
247
+ params.push(options.startAfter);
248
+ }
249
+ if (options.end !== void 0) {
250
+ conditions.push("key < ?");
251
+ params.push(options.end);
252
+ }
253
+ const limit = options.limit !== void 0 ? normalizeSqliteLimit(options.limit) : void 0;
254
+ const rows = runSqliteOperation(
255
+ () => this.database.prepare(`
256
+ SELECT key, value
257
+ FROM ${this.tableName}
258
+ WHERE ${conditions.join(" AND ")}
259
+ ORDER BY key ${options.reverse ? "DESC" : "ASC"}
260
+ ${limit !== void 0 ? `LIMIT ${limit}` : ""}
261
+ `).all(...params),
262
+ this.busyRetries
263
+ );
264
+ return new Map(rows.map((row) => [row.key, JSON.parse(row.value)]));
265
+ }
266
+ };
267
+ var NodeRoomTransport = class {
268
+ constructor(ServerClass, options = {}) {
269
+ this.ServerClass = ServerClass;
270
+ this.records = /* @__PURE__ */ new Map();
271
+ this.partiesPath = normalizePath(options.partiesPath ?? DEFAULT_PARTIES_PATH);
272
+ this.env = options.env ?? {};
273
+ this.rooms = {
274
+ main: ServerClass,
275
+ ...options.rooms ?? {}
276
+ };
277
+ this.storage = options.storage ?? createMemoryNodeRoomStorage();
278
+ this.externalParties = options.externalParties ?? {};
279
+ }
280
+ async fetch(pathOrRequest, init) {
281
+ const request = typeof pathOrRequest === "string" ? new Request(toLocalUrl(pathOrRequest), init) : pathOrRequest;
282
+ const parsed = this.parsePartyRequest(request.url);
283
+ if (!parsed) {
284
+ return new Response("Not Found", { status: 404 });
285
+ }
286
+ const record = await this.getRecord(parsed.namespace, parsed.roomId);
287
+ return record.server.onRequest?.(request) ?? new Response("Not Found", { status: 404 });
288
+ }
289
+ async handleNodeRequest(req, res, next) {
290
+ const url = getRequestUrl(req);
291
+ if (!this.parsePartyRequest(url)) {
292
+ if (next) {
293
+ next();
294
+ return;
295
+ }
296
+ await writeNodeResponse(res, new Response("Not Found", { status: 404 }));
297
+ return;
298
+ }
299
+ try {
300
+ const request = await createWebRequest(req, url);
301
+ const response = await this.fetch(request);
302
+ await writeNodeResponse(res, response);
303
+ } catch (error) {
304
+ if (next) {
305
+ next(error);
306
+ return;
307
+ }
308
+ await writeNodeResponse(res, new Response("Internal Server Error", { status: 500 }));
309
+ }
310
+ }
311
+ handleUpgrade(wsServer, request, socket, head) {
312
+ const parsed = this.parsePartyRequest(getRequestUrl(request));
313
+ if (!parsed) {
314
+ socket.destroy();
315
+ return;
316
+ }
317
+ wsServer.handleUpgrade(request, socket, head, (webSocket) => {
318
+ void this.acceptWebSocket(webSocket, request, parsed).catch(() => {
319
+ webSocket.close(1011, "Unable to start room connection");
320
+ });
321
+ wsServer.emit?.("connection", webSocket, request);
322
+ });
323
+ }
324
+ async acceptWebSocket(webSocket, request, parsedPath) {
325
+ const url = request instanceof Request ? request.url : getRequestUrl(request);
326
+ const parsed = parsedPath ?? this.parsePartyRequest(url);
327
+ if (!parsed) {
328
+ webSocket.close(1008, "Invalid room path");
329
+ throw new Error(`Unable to route WebSocket URL: ${url}`);
330
+ }
331
+ const record = await this.getRecord(parsed.namespace, parsed.roomId);
332
+ const connection = new NodeConnection(webSocket, url, getConnectionIdFromUrl(url));
333
+ const connectRequest = request instanceof Request ? request : await createWebRequest(request, url, false);
334
+ await record.server.onConnect?.(connection, {
335
+ request: connectRequest
336
+ });
337
+ const onMessage = (data) => {
338
+ void record.server.onMessage?.(normalizeWebSocketMessage(data), connection);
339
+ };
340
+ const onClose = () => {
341
+ record.room.deleteConnection(connection.id, connection);
342
+ void record.server.onClose?.(connection);
343
+ };
344
+ const onError = (error) => {
345
+ void record.server.onError?.(connection, error);
346
+ };
347
+ webSocket.on("message", onMessage);
348
+ webSocket.on("close", onClose);
349
+ webSocket.on("error", onError);
350
+ record.room.addConnection(connection);
351
+ return connection;
352
+ }
353
+ getRoom(namespace, roomId) {
354
+ return this.getRecord(namespace, roomId).then((record) => record.room);
355
+ }
356
+ getNamespacePath(namespace, roomId) {
357
+ const baseSegments = trimSlashes(this.getPartiesBase()).split("/").slice(0, -1);
358
+ return `/${[...baseSegments, namespace, encodeURIComponent(roomId)].join("/")}`;
359
+ }
360
+ async getRecord(namespace, roomId) {
361
+ const key = `${namespace}:${roomId}`;
362
+ const existing = this.records.get(key);
363
+ if (existing) {
364
+ return existing;
365
+ }
366
+ const recordPromise = this.createRecord(namespace, roomId);
367
+ this.records.set(key, recordPromise);
368
+ try {
369
+ return await recordPromise;
370
+ } catch (error) {
371
+ this.records.delete(key);
372
+ throw error;
373
+ }
374
+ }
375
+ async createRecord(namespace, roomId) {
376
+ const ServerClass = this.rooms[namespace] ?? this.ServerClass;
377
+ const room = new NodeRoom({
378
+ id: roomId,
379
+ name: namespace,
380
+ env: this.env,
381
+ storage: await this.resolveStorage(namespace, roomId),
382
+ transport: this
383
+ });
384
+ const server = new ServerClass(room);
385
+ const record = {
386
+ room,
387
+ server,
388
+ started: Promise.resolve(server.onStart?.()).then(() => void 0)
389
+ };
390
+ await record.started;
391
+ return record;
392
+ }
393
+ async resolveStorage(namespace, roomId) {
394
+ if (typeof this.storage === "function") {
395
+ return this.storage(namespace, roomId);
396
+ }
397
+ return this.storage.getStorage(namespace, roomId);
398
+ }
399
+ parsePartyRequest(url) {
400
+ const requestUrl = new URL(url, "http://localhost");
401
+ const partiesBase = this.getPartiesBase();
402
+ const segments = trimSlashes(requestUrl.pathname).split("/");
403
+ const configuredSegments = trimSlashes(partiesBase).split("/");
404
+ const baseSegments = configuredSegments.slice(0, -1);
405
+ if (segments.length < baseSegments.length + 2) {
406
+ return null;
407
+ }
408
+ for (let index = 0; index < baseSegments.length; index++) {
409
+ if (segments[index] !== baseSegments[index]) {
410
+ return null;
411
+ }
412
+ }
413
+ const namespace = decodeURIComponent(segments[baseSegments.length]);
414
+ const roomId = decodeURIComponent(segments[baseSegments.length + 1]);
415
+ const rest = segments.slice(baseSegments.length + 2).join("/");
416
+ return {
417
+ namespace,
418
+ roomId,
419
+ restPath: rest ? `/${rest}` : "/"
420
+ };
421
+ }
422
+ getPartiesBase() {
423
+ return this.partiesPath;
424
+ }
425
+ };
426
+ var NodeRoom = class {
427
+ constructor(options) {
428
+ this.connections = /* @__PURE__ */ new Map();
429
+ this.analytics = {};
430
+ this.id = options.id;
431
+ this.internalID = `${options.name}:${options.id}`;
432
+ this.name = options.name;
433
+ this.env = options.env;
434
+ this.storage = options.storage;
435
+ this.parties = createPartiesContext(options.transport);
436
+ this.context = {
437
+ parties: this.parties,
438
+ ai: {},
439
+ vectorize: {},
440
+ analytics: this.analytics,
441
+ assets: {
442
+ fetch: async () => null
443
+ },
444
+ bindings: {
445
+ r2: {},
446
+ kv: {}
447
+ }
448
+ };
449
+ }
450
+ blockConcurrencyWhile(callback) {
451
+ return callback();
452
+ }
453
+ broadcast(msg, without = []) {
454
+ for (const connection of this.connections.values()) {
455
+ if (!without.includes(connection.id)) {
456
+ connection.send(msg);
457
+ }
458
+ }
459
+ }
460
+ getConnection(id) {
461
+ let connection;
462
+ for (const current of this.connections.values()) {
463
+ if (current.id === id || current.sessionId === id) {
464
+ connection = current;
465
+ }
466
+ }
467
+ return connection;
468
+ }
469
+ getConnections() {
470
+ return Array.from(this.connections.values());
471
+ }
472
+ addConnection(connection) {
473
+ this.connections.set(connection.id, connection);
474
+ }
475
+ deleteConnection(id, connection) {
476
+ if (connection) {
477
+ this.connections.delete(connection.id);
478
+ return;
479
+ }
480
+ for (const [connectionKey, current] of this.connections) {
481
+ if (current.id === id || current.sessionId === id) {
482
+ this.connections.delete(connectionKey);
483
+ }
484
+ }
485
+ }
486
+ };
487
+ var NodeConnection = class {
488
+ constructor(webSocket, uri, sessionId) {
489
+ this.webSocket = webSocket;
490
+ this.id = createConnectionId();
491
+ this.socket = this;
492
+ this.state = null;
493
+ this.attachment = null;
494
+ this.sessionId = sessionId || this.id;
495
+ this.uri = uri;
496
+ }
497
+ send(data) {
498
+ if (this.webSocket.readyState === void 0 || this.webSocket.readyState === WEBSOCKET_OPEN) {
499
+ this.webSocket.send(data);
500
+ }
501
+ }
502
+ close(code, reason) {
503
+ this.webSocket.close(code, reason);
504
+ }
505
+ setState(state) {
506
+ this.state = typeof state === "function" ? state(this.state) : state;
507
+ return this.state;
508
+ }
509
+ serializeAttachment(attachment) {
510
+ this.attachment = attachment;
511
+ }
512
+ deserializeAttachment() {
513
+ return this.attachment;
514
+ }
515
+ };
516
+ function createPartiesContext(transport) {
517
+ return new Proxy({}, {
518
+ get(_target, namespace) {
519
+ const externalNamespace = transport.externalParties[namespace];
520
+ if (externalNamespace) {
521
+ return externalNamespace;
522
+ }
523
+ return {
524
+ get(roomId) {
525
+ return {
526
+ connect: () => {
527
+ throw new Error("Party stub connect() is not implemented by @signe/room/node");
528
+ },
529
+ async socket(pathOrInit, init) {
530
+ const path = typeof pathOrInit === "string" ? pathOrInit : "/";
531
+ const requestInit = typeof pathOrInit === "string" ? init : pathOrInit;
532
+ const request = new Request(
533
+ toLocalUrl(`${transport.getNamespacePath(namespace, roomId)}${normalizeStubPath(path)}`),
534
+ requestInit
535
+ );
536
+ const pair = createInMemoryWebSocketPair();
537
+ void transport.acceptWebSocket(pair.server, request).catch(() => {
538
+ pair.client.close(1011, "Unable to start room connection");
539
+ });
540
+ return pair.client;
541
+ },
542
+ fetch(pathOrInit, init) {
543
+ const path = typeof pathOrInit === "string" ? pathOrInit : "/";
544
+ const requestInit = typeof pathOrInit === "string" ? init : pathOrInit;
545
+ return transport.fetch(`${transport.getNamespacePath(namespace, roomId)}${normalizeStubPath(path)}`, requestInit);
546
+ }
547
+ };
548
+ }
549
+ };
550
+ }
551
+ });
552
+ }
553
+ function createInMemoryWebSocketPair() {
554
+ const client = new InMemoryWebSocket();
555
+ const server = new InMemoryWebSocket();
556
+ client.setPeer(server);
557
+ server.setPeer(client);
558
+ return { client, server };
559
+ }
560
+ var InMemoryWebSocket = class {
561
+ constructor() {
562
+ this.readyState = WEBSOCKET_OPEN;
563
+ this.emitter = new EventEmitter();
564
+ }
565
+ setPeer(peer) {
566
+ this.peer = peer;
567
+ }
568
+ send(data, cb) {
569
+ if (this.readyState !== WEBSOCKET_OPEN || this.peer?.readyState !== WEBSOCKET_OPEN) {
570
+ cb?.(new Error("WebSocket is not open"));
571
+ return;
572
+ }
573
+ queueMicrotask(() => {
574
+ this.peer?.emitMessage(data);
575
+ cb?.();
576
+ });
577
+ }
578
+ close(code, reason) {
579
+ if (this.readyState !== WEBSOCKET_OPEN) {
580
+ return;
581
+ }
582
+ this.readyState = 3;
583
+ this.emitter.emit("close", code, reason);
584
+ if (this.peer?.readyState === WEBSOCKET_OPEN) {
585
+ this.peer.readyState = 3;
586
+ this.peer.emitter.emit("close", code, reason);
587
+ }
588
+ }
589
+ on(event, listener) {
590
+ this.emitter.on(event, listener);
591
+ return this;
592
+ }
593
+ off(event, listener) {
594
+ this.emitter.off(event, listener);
595
+ return this;
596
+ }
597
+ removeListener(event, listener) {
598
+ this.emitter.removeListener(event, listener);
599
+ return this;
600
+ }
601
+ addEventListener(type, listener) {
602
+ if (type === "message") {
603
+ this.on("message", (data) => listener({ data }));
604
+ return;
605
+ }
606
+ this.on(type, (event) => listener(event));
607
+ }
608
+ emitMessage(data) {
609
+ if (this.readyState === WEBSOCKET_OPEN) {
610
+ this.emitter.emit("message", data);
611
+ }
612
+ }
613
+ };
614
+ async function createWebRequest(req, url, includeBody = true) {
615
+ const headers = new Headers();
616
+ for (const [key, value] of Object.entries(req.headers)) {
617
+ if (Array.isArray(value)) {
618
+ for (const item of value) {
619
+ headers.append(key, item);
620
+ }
621
+ } else if (value !== void 0) {
622
+ headers.set(key, String(value));
623
+ }
624
+ }
625
+ const method = req.method ?? "GET";
626
+ const hasBody = includeBody && !["GET", "HEAD"].includes(method);
627
+ const body = hasBody ? await readIncomingBody(req) : void 0;
628
+ return new Request(url, {
629
+ method,
630
+ headers,
631
+ body
632
+ });
633
+ }
634
+ async function readIncomingBody(req) {
635
+ const chunks = [];
636
+ for await (const chunk of req) {
637
+ if (typeof chunk === "string") {
638
+ chunks.push(new TextEncoder().encode(chunk));
639
+ } else {
640
+ chunks.push(chunk);
641
+ }
642
+ }
643
+ const size = chunks.reduce((total, chunk) => total + chunk.byteLength, 0);
644
+ const body = new Uint8Array(size);
645
+ let offset = 0;
646
+ for (const chunk of chunks) {
647
+ body.set(chunk, offset);
648
+ offset += chunk.byteLength;
649
+ }
650
+ return body;
651
+ }
652
+ async function writeNodeResponse(res, response) {
653
+ res.statusCode = response.status;
654
+ response.headers.forEach((value, key) => {
655
+ res.setHeader(key, value);
656
+ });
657
+ const body = new Uint8Array(await response.arrayBuffer());
658
+ res.end(body);
659
+ }
660
+ function getRequestUrl(req) {
661
+ const protocol = req.headers["x-forwarded-proto"] ?? "http";
662
+ const host = req.headers.host ?? "localhost";
663
+ return `${protocol}://${host}${req.url ?? "/"}`;
664
+ }
665
+ function normalizeWebSocketMessage(data) {
666
+ if (typeof data === "string") {
667
+ return data;
668
+ }
669
+ if (data instanceof ArrayBuffer) {
670
+ return new TextDecoder().decode(data);
671
+ }
672
+ if (ArrayBuffer.isView(data)) {
673
+ return new TextDecoder().decode(data);
674
+ }
675
+ return String(data);
676
+ }
677
+ function normalizePath(path) {
678
+ return `/${trimSlashes(path)}`;
679
+ }
680
+ function normalizeStubPath(path) {
681
+ if (!path || path === "/") {
682
+ return "";
683
+ }
684
+ return path.startsWith("/") ? path : `/${path}`;
685
+ }
686
+ function toLocalUrl(path) {
687
+ return path.startsWith("http://") || path.startsWith("https://") ? path : `http://localhost${path.startsWith("/") ? path : `/${path}`}`;
688
+ }
689
+ function getConnectionIdFromUrl(url) {
690
+ const requestedId = new URL(url).searchParams.get("id")?.trim();
691
+ return requestedId || void 0;
692
+ }
693
+ function trimSlashes(value) {
694
+ return value.replace(/^\/+|\/+$/g, "");
695
+ }
696
+ function getStorageKey(namespace, roomId) {
697
+ return `${encodeURIComponent(namespace)}:${encodeURIComponent(roomId)}`;
698
+ }
699
+ function assertSafeSqlIdentifier(identifier) {
700
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
701
+ throw new Error(`Invalid SQLite table name: ${identifier}`);
702
+ }
703
+ }
704
+ function getPrefixEnd(prefix) {
705
+ return `${prefix}\uFFFF`;
706
+ }
707
+ function normalizeSqliteLimit(value) {
708
+ if (!Number.isFinite(value) || value < 0) {
709
+ return 0;
710
+ }
711
+ return Math.floor(value);
712
+ }
713
+ function normalizeSqliteTimeout(value) {
714
+ if (!Number.isFinite(value) || value < 0) {
715
+ return DEFAULT_SQLITE_BUSY_TIMEOUT_MS;
716
+ }
717
+ return Math.floor(value);
718
+ }
719
+ function normalizeSqliteJournalMode(value) {
720
+ const journalMode = value.toUpperCase();
721
+ const allowedModes = /* @__PURE__ */ new Set([
722
+ "DELETE",
723
+ "TRUNCATE",
724
+ "PERSIST",
725
+ "MEMORY",
726
+ "WAL",
727
+ "OFF"
728
+ ]);
729
+ if (!allowedModes.has(journalMode)) {
730
+ throw new Error(`Invalid SQLite journal mode: ${value}`);
731
+ }
732
+ return journalMode;
733
+ }
734
+ function runSqliteOperation(operation, retries) {
735
+ const maxRetries = Math.max(0, Math.floor(retries));
736
+ for (let attempt = 0; ; attempt += 1) {
737
+ try {
738
+ return operation();
739
+ } catch (error) {
740
+ if (!isSqliteBusyError(error) || attempt >= maxRetries) {
741
+ throw error;
742
+ }
743
+ sleepSync(Math.min(250, SQLITE_RETRY_BASE_DELAY_MS * 2 ** attempt));
744
+ }
745
+ }
746
+ }
747
+ function isSqliteBusyError(error) {
748
+ if (!error || typeof error !== "object") {
749
+ return false;
750
+ }
751
+ const err = error;
752
+ return err.errcode === 5 || err.errcode === 6 || err.errstr === "database is locked" || err.errstr === "database table is locked" || err.code === "SQLITE_BUSY" || err.code === "SQLITE_LOCKED" || err.message?.includes("database is locked") || err.message?.includes("database table is locked");
753
+ }
754
+ function sleepSync(ms) {
755
+ const buffer = new SharedArrayBuffer(4);
756
+ const view = new Int32Array(buffer);
757
+ Atomics.wait(view, 0, 0, ms);
758
+ }
759
+ function loadNodeSqliteModule() {
760
+ const require2 = createRequire(`${process.cwd()}/package.json`);
761
+ return require2("node:sqlite");
762
+ }
763
+ function cloneStorageValue(value) {
764
+ if (typeof structuredClone === "function") {
765
+ try {
766
+ return structuredClone(value);
767
+ } catch {
768
+ return value;
769
+ }
770
+ }
771
+ return value;
772
+ }
773
+ function createConnectionId() {
774
+ return Math.random().toString(36).slice(2, 12);
775
+ }
776
+ export {
777
+ MemoryNodeRoomStorage,
778
+ NodeConnection,
779
+ NodeRoom,
780
+ NodeRoomTransport,
781
+ SqliteNodeRoomStorage,
782
+ createMemoryNodeRoomStorage,
783
+ createNodeRoomTransport,
784
+ createSqliteNodeRoomStorage
785
+ };
786
+ //# sourceMappingURL=index.js.map