@signe/room 1.2.1 → 1.4.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Request as Request$1, AnalyticsEngineDataset, WebSocket, R2Bucket, KVNamespace, DurableObjectState, DurableObjectStorage, VectorizeIndex } from '@cloudflare/workers-types';
1
+ import { Request as Request$1, WebSocket, DurableObjectState, AnalyticsEngineDataset, DurableObjectStorage, VectorizeIndex, R2Bucket, KVNamespace } from '@cloudflare/workers-types';
2
2
  import { z } from 'zod';
3
3
 
4
4
  type AssetFetcher = {
@@ -47,16 +47,6 @@ type Context = {
47
47
  bindings: CustomBindings;
48
48
  };
49
49
  type AI = Record<string, never>;
50
- type Lobby = {
51
- id: string;
52
- env: Record<string, unknown>;
53
- ai: AI;
54
- parties: Context["parties"];
55
- vectorize: Context["vectorize"];
56
- analytics: AnalyticsEngineDataset;
57
- assets: AssetFetcher;
58
- bindings: CustomBindings;
59
- };
60
50
  type ImmutablePrimitive = undefined | null | boolean | string | number;
61
51
  type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
62
52
  type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
@@ -191,7 +181,7 @@ interface RoomOptions {
191
181
  throttleSync?: number;
192
182
  hibernate?: boolean;
193
183
  guards?: RoomGuardFn[];
194
- disconnectTimeout?: number;
184
+ sessionExpiryTime?: number;
195
185
  }
196
186
  declare function Room(options: RoomOptions): (target: any) => void;
197
187
  /**
@@ -227,6 +217,7 @@ declare class MockPartyRoom {
227
217
  constructor(id?: string);
228
218
  connection(client: any): void;
229
219
  broadcast(data: any): void;
220
+ getConnections(): Map<string, MockPartySocket>;
230
221
  clear(): void;
231
222
  }
232
223
  declare class MockConnection {
@@ -262,8 +253,6 @@ declare class Server implements Server$1 {
262
253
  readonly room: Room$1;
263
254
  subRoom: any;
264
255
  rooms: any[];
265
- private timeoutHandles;
266
- static onBeforeConnect(request: Request, lobby: Lobby): Promise<Request>;
267
256
  /**
268
257
  * @constructor
269
258
  * @param {Party.Room} room - The room object representing the current game or application instance.
@@ -301,13 +290,13 @@ declare class Server implements Server$1 {
301
290
  * ```
302
291
  */
303
292
  onStart(): Promise<void>;
293
+ private garbageCollector;
304
294
  /**
305
295
  * @method createRoom
306
296
  * @private
307
297
  * @async
308
298
  * @param {CreateRoomOptions} [options={}] - Options for creating the room.
309
299
  * @returns {Promise<Object>} The created room instance.
310
- * @throws {Error} If no matching room is found.
311
300
  *
312
301
  * @example
313
302
  * ```typescript
@@ -352,8 +341,10 @@ declare class Server implements Server$1 {
352
341
  * ```
353
342
  */
354
343
  private getUsersProperty;
344
+ private getUsersPropName;
355
345
  private getSession;
356
346
  private saveSession;
347
+ private updateSessionConnection;
357
348
  private deleteSession;
358
349
  /**
359
350
  * @method onConnect
@@ -406,6 +397,8 @@ declare class Server implements Server$1 {
406
397
  */
407
398
  onClose(conn: Connection): Promise<void>;
408
399
  onAlarm(): Promise<void>;
400
+ onError(connection: Connection, error: Error): Promise<void>;
401
+ onRequest(req: Request): Promise<Response>;
409
402
  }
410
403
 
411
404
  export { Action, ClientIo, Guard, MockConnection, Room, RoomGuard, type RoomOptions, Server, ServerIo };
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ function Room(options) {
20
20
  target.maxUsers = options.maxUsers;
21
21
  target.throttleStorage = options.throttleStorage;
22
22
  target.throttleSync = options.throttleSync;
23
- target.disconnectTimeout = options.disconnectTimeout ?? 0;
23
+ target.sessionExpiryTime = options.sessionExpiryTime ?? 5 * 60 * 1e3;
24
24
  if (options.guards) {
25
25
  target["_roomGuards"] = options.guards;
26
26
  }
@@ -44,11 +44,6 @@ function Guard(guards) {
44
44
  __name(Guard, "Guard");
45
45
 
46
46
  // ../sync/src/utils.ts
47
- function isClass(obj) {
48
- return typeof obj === "function" && obj.prototype && obj.prototype.constructor === obj;
49
- }
50
- __name(isClass, "isClass");
51
- var isObject = /* @__PURE__ */ __name((item) => item && typeof item === "object" && !Array.isArray(item) && item !== null, "isObject");
52
47
  function generateShortUUID() {
53
48
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
54
49
  let uuid = "";
@@ -81,7 +76,7 @@ var Storage = class {
81
76
  };
82
77
 
83
78
  // src/mock.ts
84
- var MockPartySocket = class MockPartySocket2 {
79
+ var MockPartySocket = class {
85
80
  static {
86
81
  __name(this, "MockPartySocket");
87
82
  }
@@ -120,6 +115,9 @@ var MockPartyRoom = class MockPartyRoom2 {
120
115
  client._trigger("message", data);
121
116
  });
122
117
  }
118
+ getConnections() {
119
+ return this.clients;
120
+ }
123
121
  clear() {
124
122
  this.clients.clear();
125
123
  }
@@ -139,172 +137,7 @@ var ClientIo = MockPartySocket;
139
137
  // src/server.ts
140
138
  import { dset as dset2 } from "dset";
141
139
  import z from "zod";
142
-
143
- // ../sync/src/core.ts
144
- import { ArraySubject, ObjectSubject, isSignal } from "@signe/reactive";
145
- var syncClass = /* @__PURE__ */ __name((instance, options = {}) => {
146
- const cacheSync = /* @__PURE__ */ new Map();
147
- const cachePersist = /* @__PURE__ */ new Set();
148
- instance.$valuesChanges = {
149
- set: /* @__PURE__ */ __name((path, value) => {
150
- cacheSync.set(path, value);
151
- options.onSync?.(cacheSync);
152
- }, "set"),
153
- setPersist: /* @__PURE__ */ __name((path) => {
154
- if (path == "") path = ".";
155
- cachePersist.add(path);
156
- options.onPersist?.(cachePersist);
157
- }, "setPersist"),
158
- has: /* @__PURE__ */ __name((path) => {
159
- return cacheSync.has(path);
160
- }, "has"),
161
- get: /* @__PURE__ */ __name((path) => {
162
- return cacheSync.get(path);
163
- }, "get")
164
- };
165
- createSyncClass(instance);
166
- }, "syncClass");
167
- function createStatesSnapshot(instance) {
168
- let persistObject = {};
169
- if (instance.$snapshot) {
170
- for (const key of instance.$snapshot.keys()) {
171
- const signal = instance.$snapshot.get(key);
172
- const persist = signal.options.persist ?? true;
173
- let value = signal();
174
- if (isObject(value) || Array.isArray(value)) {
175
- break;
176
- }
177
- if (persist) {
178
- persistObject[key] = value;
179
- }
180
- }
181
- }
182
- return persistObject;
183
- }
184
- __name(createStatesSnapshot, "createStatesSnapshot");
185
- function setMetadata(target, key, value) {
186
- const meta = target.constructor._propertyMetadata;
187
- const propId = meta?.get(key);
188
- if (propId) {
189
- if (isSignal(target[propId])) {
190
- target[propId].set(value);
191
- } else {
192
- target[propId] = value;
193
- }
194
- }
195
- }
196
- __name(setMetadata, "setMetadata");
197
- var createSyncClass = /* @__PURE__ */ __name((currentClass, parentKey = null, parentClass = null, path = "") => {
198
- currentClass.$path = path;
199
- if (parentClass) {
200
- currentClass.$valuesChanges = parentClass.$valuesChanges;
201
- }
202
- if (parentKey) {
203
- setMetadata(currentClass, "id", parentKey);
204
- }
205
- if (currentClass.$snapshot) {
206
- for (const key of currentClass.$snapshot.keys()) {
207
- const signal = currentClass.$snapshot.get(key);
208
- const syncToClient = signal.options.syncToClient ?? true;
209
- const persist = signal.options.persist ?? true;
210
- let value = signal();
211
- if (isObject(value) || Array.isArray(value)) {
212
- value = {
213
- ...value
214
- };
215
- }
216
- const newPath = (path ? path + "." : "") + key;
217
- if (syncToClient) {
218
- currentClass.$valuesChanges.set(newPath, value);
219
- }
220
- if (persist) {
221
- if (parentClass) currentClass.$valuesChanges.setPersist(path);
222
- }
223
- }
224
- }
225
- }, "createSyncClass");
226
-
227
- // ../sync/src/load.ts
228
- import { isSignal as isSignal2 } from "@signe/reactive";
229
- function load(rootInstance, values, valueIsObject) {
230
- if (valueIsObject) {
231
- loadFromObject(rootInstance, values);
232
- } else {
233
- loadFromPaths(rootInstance, values);
234
- }
235
- }
236
- __name(load, "load");
237
- function loadFromPaths(rootInstance, values) {
238
- for (const [path, value] of Object.entries(values)) {
239
- const parts = path.split(".");
240
- loadValue(rootInstance, parts, value);
241
- }
242
- }
243
- __name(loadFromPaths, "loadFromPaths");
244
- function loadFromObject(rootInstance, values, currentPath = "") {
245
- for (let key in values) {
246
- const value = values[key];
247
- const newPath = currentPath ? `${currentPath}.${key}` : key;
248
- if (typeof value === "object" && !Array.isArray(value) && value !== null) {
249
- loadFromObject(rootInstance, value, newPath);
250
- } else {
251
- const parts = newPath.split(".");
252
- loadValue(rootInstance, parts, value);
253
- }
254
- }
255
- }
256
- __name(loadFromObject, "loadFromObject");
257
- function loadValue(rootInstance, parts, value) {
258
- let current = rootInstance;
259
- for (let i = 0; i < parts.length; i++) {
260
- const part = parts[i];
261
- if (i === parts.length - 1) {
262
- if (value == "$delete") {
263
- if (isSignal2(current)) {
264
- current = current();
265
- }
266
- Reflect.deleteProperty(current, part);
267
- } else if (current[part]?._subject) {
268
- current[part].set(value);
269
- } else {
270
- current[part] = value;
271
- }
272
- } else {
273
- if (isSignal2(current)) {
274
- current = current();
275
- }
276
- const currentValue = current[part];
277
- if (currentValue === void 0) {
278
- const parentInstance = getByPath(rootInstance, parts.slice(0, i).join("."));
279
- const classType = parentInstance?.options?.classType;
280
- if (classType) {
281
- current[part] = !isClass(classType) ? classType(part) : new classType();
282
- setMetadata(current[part], "id", part);
283
- } else {
284
- current[part] = {};
285
- }
286
- }
287
- current = current[part];
288
- }
289
- }
290
- }
291
- __name(loadValue, "loadValue");
292
- function getByPath(root, path) {
293
- const parts = path.split(".");
294
- let current = root;
295
- for (const part of parts) {
296
- if (isSignal2(current)) {
297
- current = current();
298
- }
299
- if (current[part]) {
300
- current = current[part];
301
- } else {
302
- return void 0;
303
- }
304
- }
305
- return current;
306
- }
307
- __name(getByPath, "getByPath");
140
+ import { createStatesSnapshot, getByPath, load, syncClass, DELETE_TOKEN, generateShortUUID as generateShortUUID2 } from "@signe/sync";
308
141
 
309
142
  // src/utils.ts
310
143
  import { dset } from "dset";
@@ -316,10 +149,10 @@ async function awaitReturn(val) {
316
149
  return isPromise(val) ? await val : val;
317
150
  }
318
151
  __name(awaitReturn, "awaitReturn");
319
- function isClass2(obj) {
152
+ function isClass(obj) {
320
153
  return typeof obj === "function" && obj.prototype && obj.prototype.constructor === obj;
321
154
  }
322
- __name(isClass2, "isClass");
155
+ __name(isClass, "isClass");
323
156
  function throttle(func, wait) {
324
157
  let timeout = null;
325
158
  let lastArgs = null;
@@ -399,12 +232,6 @@ var Server = class {
399
232
  room;
400
233
  subRoom;
401
234
  rooms;
402
- timeoutHandles;
403
- static async onBeforeConnect(request, lobby) {
404
- const token = new URL(request.url).searchParams.get("token") ?? "";
405
- request.headers.set("X-User-ID", token);
406
- return request;
407
- }
408
235
  /**
409
236
  * @constructor
410
237
  * @param {Party.Room} room - The room object representing the current game or application instance.
@@ -418,7 +245,6 @@ var Server = class {
418
245
  this.room = room;
419
246
  this.subRoom = null;
420
247
  this.rooms = [];
421
- this.timeoutHandles = /* @__PURE__ */ new Map();
422
248
  }
423
249
  /**
424
250
  * @readonly
@@ -453,13 +279,51 @@ var Server = class {
453
279
  this.subRoom = await this.createRoom();
454
280
  }
455
281
  }
282
+ async garbageCollector(options) {
283
+ const subRoom = await this.getSubRoom();
284
+ if (!subRoom) return;
285
+ const activeConnections = [
286
+ ...this.room.getConnections()
287
+ ];
288
+ const activePrivateIds = new Set(activeConnections.map((conn) => conn.id));
289
+ try {
290
+ const sessions = await this.room.storage.list();
291
+ const users = this.getUsersProperty(subRoom);
292
+ const usersPropName = this.getUsersPropName(subRoom);
293
+ const validPublicIds = /* @__PURE__ */ new Set();
294
+ const expiredPublicIds = /* @__PURE__ */ new Set();
295
+ const SESSION_EXPIRY_TIME = options.sessionExpiryTime;
296
+ const now = Date.now();
297
+ for (const [key, session] of sessions) {
298
+ if (!key.startsWith("session:")) continue;
299
+ const privateId = key.replace("session:", "");
300
+ const typedSession = session;
301
+ if (!activePrivateIds.has(privateId) && !typedSession.connected && now - typedSession.created > SESSION_EXPIRY_TIME) {
302
+ await this.deleteSession(privateId);
303
+ expiredPublicIds.add(typedSession.publicId);
304
+ } else if (typedSession && typedSession.publicId) {
305
+ validPublicIds.add(typedSession.publicId);
306
+ }
307
+ }
308
+ if (users && usersPropName) {
309
+ const currentUsers = users();
310
+ for (const publicId in currentUsers) {
311
+ if (expiredPublicIds.has(publicId) && !validPublicIds.has(publicId)) {
312
+ delete currentUsers[publicId];
313
+ await this.room.storage.delete(`${usersPropName}.${publicId}`);
314
+ }
315
+ }
316
+ }
317
+ } catch (error) {
318
+ console.error("Error in garbage collector:", error);
319
+ }
320
+ }
456
321
  /**
457
322
  * @method createRoom
458
323
  * @private
459
324
  * @async
460
325
  * @param {CreateRoomOptions} [options={}] - Options for creating the room.
461
326
  * @returns {Promise<Object>} The created room instance.
462
- * @throws {Error} If no matching room is found.
463
327
  *
464
328
  * @example
465
329
  * ```typescript
@@ -473,6 +337,7 @@ var Server = class {
473
337
  async createRoom(options = {}) {
474
338
  let instance;
475
339
  let init = true;
340
+ let initPersist = true;
476
341
  for (let room of this.rooms) {
477
342
  const params = extractParams(room.path, this.room.id);
478
343
  if (params) {
@@ -481,21 +346,23 @@ var Server = class {
481
346
  }
482
347
  }
483
348
  if (!instance) {
484
- throw new Error("Room not found");
349
+ return null;
485
350
  }
486
351
  const loadMemory = /* @__PURE__ */ __name(async () => {
487
352
  const root = await this.room.storage.get(".");
488
353
  const memory = await this.room.storage.list();
489
354
  const tmpObject = root || {};
490
355
  for (let [key, value] of memory) {
356
+ if (key.startsWith("session:")) {
357
+ continue;
358
+ }
491
359
  if (key == ".") {
492
360
  continue;
493
361
  }
494
362
  dset2(tmpObject, key, value);
495
363
  }
496
- load(instance, tmpObject);
364
+ load(instance, tmpObject, true);
497
365
  }, "loadMemory");
498
- await loadMemory();
499
366
  instance.$memoryAll = {};
500
367
  const syncCb = /* @__PURE__ */ __name((values) => {
501
368
  if (options.getMemoryAll) {
@@ -513,10 +380,18 @@ var Server = class {
513
380
  values.clear();
514
381
  }, "syncCb");
515
382
  const persistCb = /* @__PURE__ */ __name(async (values) => {
516
- for (let path of values) {
383
+ if (initPersist) {
384
+ values.clear();
385
+ return;
386
+ }
387
+ for (let [path, value] of values) {
517
388
  const _instance = path == "." ? instance : getByPath(instance, path);
518
389
  const itemValue = createStatesSnapshot(_instance);
519
- await this.room.storage.put(path, itemValue);
390
+ if (value == DELETE_TOKEN) {
391
+ await this.room.storage.delete(path);
392
+ } else {
393
+ await this.room.storage.put(path, itemValue);
394
+ }
520
395
  }
521
396
  values.clear();
522
397
  }, "persistCb");
@@ -524,6 +399,8 @@ var Server = class {
524
399
  onSync: throttle(syncCb, instance["throttleSync"] ?? 500),
525
400
  onPersist: throttle(persistCb, instance["throttleStorage"] ?? 2e3)
526
401
  });
402
+ await loadMemory();
403
+ initPersist = false;
527
404
  return instance;
528
405
  }
529
406
  /**
@@ -574,6 +451,10 @@ var Server = class {
574
451
  }
575
452
  return null;
576
453
  }
454
+ getUsersPropName(subRoom) {
455
+ const meta = subRoom.constructor["_propertyMetadata"];
456
+ return meta?.get("users");
457
+ }
577
458
  async getSession(privateId) {
578
459
  if (!privateId) return null;
579
460
  try {
@@ -584,7 +465,21 @@ var Server = class {
584
465
  }
585
466
  }
586
467
  async saveSession(privateId, data) {
587
- await this.room.storage.put(`session:${privateId}`, data);
468
+ const sessionData = {
469
+ ...data,
470
+ created: data.created || Date.now(),
471
+ connected: data.connected !== void 0 ? data.connected : true
472
+ };
473
+ await this.room.storage.put(`session:${privateId}`, sessionData);
474
+ }
475
+ async updateSessionConnection(privateId, connected) {
476
+ const session = await this.getSession(privateId);
477
+ if (session) {
478
+ await this.saveSession(privateId, {
479
+ ...session,
480
+ connected
481
+ });
482
+ }
588
483
  }
589
484
  async deleteSession(privateId) {
590
485
  await this.room.storage.delete(`session:${privateId}`);
@@ -609,6 +504,14 @@ var Server = class {
609
504
  const subRoom = await this.getSubRoom({
610
505
  getMemoryAll: true
611
506
  });
507
+ if (!subRoom) {
508
+ conn.close();
509
+ return;
510
+ }
511
+ const sessionExpiryTime = subRoom.constructor.sessionExpiryTime;
512
+ await this.garbageCollector({
513
+ sessionExpiryTime
514
+ });
612
515
  const roomGuards = subRoom.constructor["_roomGuards"] || [];
613
516
  for (const guard of roomGuards) {
614
517
  const isAuthorized = await guard(conn, ctx);
@@ -617,34 +520,35 @@ var Server = class {
617
520
  return;
618
521
  }
619
522
  }
620
- const providedPrivateId = ctx.request?.headers.get("X-User-ID");
621
- const existingSession = providedPrivateId ? await this.getSession(providedPrivateId) : null;
622
- const publicId = existingSession?.publicId || generateShortUUID();
623
- const privateId = existingSession ? providedPrivateId : generateShortUUID();
523
+ const existingSession = await this.getSession(conn.id);
524
+ const publicId = existingSession?.publicId || generateShortUUID2();
624
525
  let user = null;
625
526
  const signal = this.getUsersProperty(subRoom);
527
+ const usersPropName = this.getUsersPropName(subRoom);
626
528
  if (signal) {
627
529
  const { classType } = signal.options;
628
530
  if (!existingSession?.publicId) {
629
- user = isClass2(classType) ? new classType() : classType(conn, ctx);
531
+ user = isClass(classType) ? new classType() : classType(conn, ctx);
630
532
  signal()[publicId] = user;
533
+ const snapshot = createStatesSnapshot(user);
534
+ this.room.storage.put(`${usersPropName}.${publicId}`, snapshot);
631
535
  }
632
536
  if (!existingSession) {
633
- await this.saveSession(privateId, {
537
+ await this.saveSession(conn.id, {
634
538
  publicId
635
539
  });
540
+ } else {
541
+ await this.updateSessionConnection(conn.id, true);
636
542
  }
637
543
  }
638
544
  await awaitReturn(subRoom["onJoin"]?.(user, conn, ctx));
639
545
  conn.setState({
640
- publicId,
641
- privateId
546
+ publicId
642
547
  });
643
548
  conn.send(JSON.stringify({
644
549
  type: "sync",
645
550
  value: {
646
551
  pId: publicId,
647
- privateId,
648
552
  ...subRoom.$memoryAll
649
553
  }
650
554
  }));
@@ -725,60 +629,57 @@ var Server = class {
725
629
  */
726
630
  async onClose(conn) {
727
631
  const subRoom = await this.getSubRoom();
632
+ if (!subRoom) {
633
+ return;
634
+ }
728
635
  const signal = this.getUsersProperty(subRoom);
729
636
  if (!conn.state) {
730
637
  return;
731
638
  }
732
- const { publicId, privateId } = conn.state;
639
+ const privateId = conn.id;
640
+ const { publicId } = conn.state;
733
641
  const user = signal?.()[publicId];
734
642
  if (!user) return;
735
- if (privateId) {
736
- await this.saveSession(privateId, {
643
+ await awaitReturn(subRoom["onLeave"]?.(user, conn));
644
+ await this.updateSessionConnection(privateId, false);
645
+ this.room.broadcast(JSON.stringify({
646
+ type: "user_disconnected",
647
+ value: {
737
648
  publicId
738
- });
739
- }
740
- const existingTimeout = this.timeoutHandles.get(privateId);
741
- if (existingTimeout) {
742
- clearTimeout(existingTimeout);
743
- this.timeoutHandles.delete(privateId);
744
- }
745
- const cleanup = /* @__PURE__ */ __name(async () => {
746
- await awaitReturn(subRoom["onLeave"]?.(user, conn));
747
- if (signal) {
748
- delete signal()[publicId];
749
- }
750
- if (privateId) {
751
- await this.deleteSession(privateId);
752
649
  }
753
- this.room.broadcast(JSON.stringify({
754
- type: "user_disconnected",
755
- value: {
756
- publicId
757
- }
758
- }));
759
- this.timeoutHandles.delete(privateId);
760
- }, "cleanup");
761
- const disconnectTimeout = subRoom.constructor.disconnectTimeout ?? 0;
762
- if (disconnectTimeout > 0) {
763
- if (user.status) {
764
- user.status.set("offline");
765
- }
766
- this.room.broadcast(JSON.stringify({
767
- type: "user_offline",
768
- value: {
769
- publicId
770
- }
771
- }));
772
- const timeout = setTimeout(cleanup, disconnectTimeout);
773
- this.timeoutHandles.set(privateId, timeout);
774
- } else {
775
- await cleanup();
776
- }
650
+ }));
777
651
  }
778
652
  async onAlarm() {
779
653
  const subRoom = await this.getSubRoom();
780
654
  await awaitReturn(subRoom["onAlarm"]?.(subRoom));
781
655
  }
656
+ async onError(connection, error) {
657
+ const subRoom = await this.getSubRoom();
658
+ await awaitReturn(subRoom["onError"]?.(connection, error));
659
+ }
660
+ async onRequest(req) {
661
+ const subRoom = await this.getSubRoom();
662
+ const res = /* @__PURE__ */ __name((body, status) => {
663
+ return new Response(JSON.stringify(body), {
664
+ status
665
+ });
666
+ }, "res");
667
+ if (!subRoom) {
668
+ return res({
669
+ error: "Not found"
670
+ }, 404);
671
+ }
672
+ const response = await awaitReturn(subRoom["onRequest"]?.(req, this.room));
673
+ if (!response) {
674
+ return res({
675
+ error: "Not found"
676
+ }, 404);
677
+ }
678
+ if (response instanceof Response) {
679
+ return response;
680
+ }
681
+ return res(response, 200);
682
+ }
782
683
  };
783
684
  export {
784
685
  Action,