@signe/room 1.1.0 → 1.2.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,43 +1,5 @@
1
- import { Request as Request$1, WebSocket, DurableObjectState, AnalyticsEngineDataset, DurableObjectStorage, VectorizeIndex, R2Bucket, KVNamespace } from '@cloudflare/workers-types';
2
-
3
- declare function Action(name: string, bodyValidation?: any): (target: any, propertyKey: string) => void;
4
- interface RoomOptions {
5
- path: string;
6
- maxUsers?: number;
7
- throttleStorage?: number;
8
- throttleSync?: number;
9
- hibernate?: boolean;
10
- }
11
- declare function Room$1(options: RoomOptions): (target: any) => void;
12
-
13
- declare class MockPartySocket {
14
- private events;
15
- id: string;
16
- addEventListener(event: any, cb: any): void;
17
- removeEventListener(event: any, cb: any): void;
18
- _trigger(event: any, data: any): void;
19
- }
20
- declare class MockStorage {
21
- private storage;
22
- get(key: string): Promise<any>;
23
- put(key: string, value: any): Promise<void>;
24
- list(): Promise<Map<string, any>>;
25
- }
26
- declare class MockPartyRoom {
27
- id?: string;
28
- private clients;
29
- storage: MockStorage;
30
- constructor(id?: string);
31
- connection(client: any): void;
32
- broadcast(data: any): void;
33
- clear(): void;
34
- }
35
- declare class MockConnection {
36
- state: any;
37
- setState(value: any): void;
38
- }
39
- declare const ServerIo: typeof MockPartyRoom;
40
- declare const ClientIo: typeof MockPartySocket;
1
+ import { Request as Request$1, AnalyticsEngineDataset, WebSocket, R2Bucket, KVNamespace, DurableObjectState, DurableObjectStorage, VectorizeIndex } from '@cloudflare/workers-types';
2
+ import { z } from 'zod';
41
3
 
42
4
  type AssetFetcher = {
43
5
  fetch(path: string): Promise<Response | null>;
@@ -47,7 +9,7 @@ interface Request extends Request$1 {
47
9
  }
48
10
  type ReturnRequest = StandardRequest | Request$1;
49
11
  /** Per-party key-value storage */
50
- interface Storage extends DurableObjectStorage {
12
+ interface Storage$1 extends DurableObjectStorage {
51
13
  }
52
14
  /** Connection metadata only available when the connection is made */
53
15
  type ConnectionContext = {
@@ -85,6 +47,16 @@ type Context = {
85
47
  bindings: CustomBindings;
86
48
  };
87
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
+ };
88
60
  type ImmutablePrimitive = undefined | null | boolean | string | number;
89
61
  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>;
90
62
  type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
@@ -118,7 +90,7 @@ type CustomBindings = {
118
90
  kv: Record<string, KVNamespace>;
119
91
  };
120
92
  /** Room represents a single, self-contained, long-lived session. */
121
- type Room = {
93
+ type Room$1 = {
122
94
  /** Room ID defined in the Party URL, e.g. /parties/:name/:id */
123
95
  id: string;
124
96
  /** Internal ID assigned by the platform. Use Party.id instead. */
@@ -128,7 +100,7 @@ type Room = {
128
100
  /** Environment variables (--var, partykit.json#vars, or .env) */
129
101
  env: Record<string, unknown>;
130
102
  /** A per-room key-value storage */
131
- storage: Storage;
103
+ storage: Storage$1;
132
104
  /** `blockConcurrencyWhile()` ensures no requests are delivered until */
133
105
  blockConcurrencyWhile: DurableObjectState["blockConcurrencyWhile"];
134
106
  /** Additional information about other resources in the current project */
@@ -209,6 +181,61 @@ type ServerOptions = {
209
181
  hibernate?: boolean;
210
182
  };
211
183
 
184
+ type GuardFn = (sender: Connection, value: any) => boolean | Promise<boolean>;
185
+ type RoomGuardFn = (conn: Connection, ctx: ConnectionContext) => boolean | Promise<boolean>;
186
+ declare function Action(name: string, bodyValidation?: z.ZodSchema): (target: any, propertyKey: string) => void;
187
+ interface RoomOptions {
188
+ path: string;
189
+ maxUsers?: number;
190
+ throttleStorage?: number;
191
+ throttleSync?: number;
192
+ hibernate?: boolean;
193
+ guards?: RoomGuardFn[];
194
+ disconnectTimeout?: number;
195
+ }
196
+ declare function Room(options: RoomOptions): (target: any) => void;
197
+ /**
198
+ * Room guard decorator
199
+ * @param guards Array of guard functions to check on connection
200
+ */
201
+ declare function RoomGuard(guards: RoomGuardFn[]): (target: any) => void;
202
+ /**
203
+ * Action guard decorator
204
+ * @param guards Array of guard functions to check before action execution
205
+ */
206
+ declare function Guard(guards: GuardFn[]): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
207
+
208
+ declare class Storage {
209
+ private memory;
210
+ put(key: any, value: any): Promise<void>;
211
+ get(key: any): Promise<any>;
212
+ delete(key: any): Promise<void>;
213
+ list(): Promise<Map<any, any>>;
214
+ }
215
+
216
+ declare class MockPartySocket {
217
+ private events;
218
+ id: string;
219
+ addEventListener(event: any, cb: any): void;
220
+ removeEventListener(event: any, cb: any): void;
221
+ _trigger(event: any, data: any): void;
222
+ }
223
+ declare class MockPartyRoom {
224
+ id?: string;
225
+ private clients;
226
+ storage: Storage;
227
+ constructor(id?: string);
228
+ connection(client: any): void;
229
+ broadcast(data: any): void;
230
+ clear(): void;
231
+ }
232
+ declare class MockConnection {
233
+ state: any;
234
+ setState(value: any): void;
235
+ }
236
+ declare const ServerIo: typeof MockPartyRoom;
237
+ declare const ClientIo: typeof MockPartySocket;
238
+
212
239
  /**
213
240
  * @class Server
214
241
  * @implements {Party.Server}
@@ -232,9 +259,11 @@ type ServerOptions = {
232
259
  * ```
233
260
  */
234
261
  declare class Server implements Server$1 {
235
- readonly room: Room;
262
+ readonly room: Room$1;
236
263
  subRoom: any;
237
264
  rooms: any[];
265
+ private timeoutHandles;
266
+ static onBeforeConnect(request: Request, lobby: Lobby): Promise<Request>;
238
267
  /**
239
268
  * @constructor
240
269
  * @param {Party.Room} room - The room object representing the current game or application instance.
@@ -244,7 +273,7 @@ declare class Server implements Server$1 {
244
273
  * const server = new MyServer(new ServerIo("game"));
245
274
  * ```
246
275
  */
247
- constructor(room: Room);
276
+ constructor(room: Room$1);
248
277
  /**
249
278
  * @readonly
250
279
  * @property {boolean} isHibernate - Indicates whether the server is in hibernate mode.
@@ -323,6 +352,9 @@ declare class Server implements Server$1 {
323
352
  * ```
324
353
  */
325
354
  private getUsersProperty;
355
+ private getSession;
356
+ private saveSession;
357
+ private deleteSession;
326
358
  /**
327
359
  * @method onConnect
328
360
  * @async
@@ -375,4 +407,4 @@ declare class Server implements Server$1 {
375
407
  onClose(conn: Connection): Promise<void>;
376
408
  }
377
409
 
378
- export { Action, ClientIo, MockConnection, Room$1 as Room, type RoomOptions, Server, ServerIo };
410
+ export { Action, ClientIo, Guard, MockConnection, Room, RoomGuard, type RoomOptions, Server, ServerIo };
package/dist/index.js CHANGED
@@ -20,9 +20,28 @@ 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;
24
+ if (options.guards) {
25
+ target["_roomGuards"] = options.guards;
26
+ }
23
27
  };
24
28
  }
25
29
  __name(Room, "Room");
30
+ function RoomGuard(guards) {
31
+ return function(target) {
32
+ target["_roomGuards"] = guards;
33
+ };
34
+ }
35
+ __name(RoomGuard, "RoomGuard");
36
+ function Guard(guards) {
37
+ return function(target, propertyKey, descriptor) {
38
+ if (!target.constructor["_actionGuards"]) {
39
+ target.constructor["_actionGuards"] = /* @__PURE__ */ new Map();
40
+ }
41
+ target.constructor["_actionGuards"].set(propertyKey, guards);
42
+ };
43
+ }
44
+ __name(Guard, "Guard");
26
45
 
27
46
  // ../sync/src/utils.ts
28
47
  function isClass(obj) {
@@ -41,6 +60,26 @@ function generateShortUUID() {
41
60
  }
42
61
  __name(generateShortUUID, "generateShortUUID");
43
62
 
63
+ // src/storage.ts
64
+ var Storage = class {
65
+ static {
66
+ __name(this, "Storage");
67
+ }
68
+ memory = /* @__PURE__ */ new Map();
69
+ async put(key, value) {
70
+ this.memory.set(key, value);
71
+ }
72
+ async get(key) {
73
+ return this.memory.get(key);
74
+ }
75
+ async delete(key) {
76
+ this.memory.delete(key);
77
+ }
78
+ async list() {
79
+ return this.memory;
80
+ }
81
+ };
82
+
44
83
  // src/mock.ts
45
84
  var MockPartySocket = class MockPartySocket2 {
46
85
  static {
@@ -58,21 +97,6 @@ var MockPartySocket = class MockPartySocket2 {
58
97
  this.events.get(event)?.(data);
59
98
  }
60
99
  };
61
- var MockStorage = class MockStorage2 {
62
- static {
63
- __name(this, "MockStorage");
64
- }
65
- storage = /* @__PURE__ */ new Map();
66
- async get(key) {
67
- return this.storage.get(key);
68
- }
69
- async put(key, value) {
70
- this.storage.set(key, value);
71
- }
72
- async list() {
73
- return this.storage;
74
- }
75
- };
76
100
  var MockPartyRoom = class MockPartyRoom2 {
77
101
  static {
78
102
  __name(this, "MockPartyRoom");
@@ -83,7 +107,7 @@ var MockPartyRoom = class MockPartyRoom2 {
83
107
  constructor(id) {
84
108
  this.id = id;
85
109
  this.clients = /* @__PURE__ */ new Map();
86
- this.storage = new MockStorage();
110
+ this.storage = new Storage();
87
111
  this.id = id || generateShortUUID();
88
112
  }
89
113
  connection(client) {
@@ -375,6 +399,12 @@ var Server = class {
375
399
  room;
376
400
  subRoom;
377
401
  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
+ }
378
408
  /**
379
409
  * @constructor
380
410
  * @param {Party.Room} room - The room object representing the current game or application instance.
@@ -388,6 +418,7 @@ var Server = class {
388
418
  this.room = room;
389
419
  this.subRoom = null;
390
420
  this.rooms = [];
421
+ this.timeoutHandles = /* @__PURE__ */ new Map();
391
422
  }
392
423
  /**
393
424
  * @readonly
@@ -543,6 +574,21 @@ var Server = class {
543
574
  }
544
575
  return null;
545
576
  }
577
+ async getSession(privateId) {
578
+ if (!privateId) return null;
579
+ try {
580
+ const session = await this.room.storage.get(`session:${privateId}`);
581
+ return session;
582
+ } catch (e) {
583
+ return null;
584
+ }
585
+ }
586
+ async saveSession(privateId, data) {
587
+ await this.room.storage.put(`session:${privateId}`, data);
588
+ }
589
+ async deleteSession(privateId) {
590
+ await this.room.storage.delete(`session:${privateId}`);
591
+ }
546
592
  /**
547
593
  * @method onConnect
548
594
  * @async
@@ -563,22 +609,43 @@ var Server = class {
563
609
  const subRoom = await this.getSubRoom({
564
610
  getMemoryAll: true
565
611
  });
566
- const publicId = generateShortUUID();
612
+ const roomGuards = subRoom.constructor["_roomGuards"] || [];
613
+ for (const guard of roomGuards) {
614
+ const isAuthorized = await guard(conn, ctx);
615
+ if (!isAuthorized) {
616
+ conn.close();
617
+ return;
618
+ }
619
+ }
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();
567
624
  let user = null;
568
625
  const signal = this.getUsersProperty(subRoom);
569
626
  if (signal) {
570
627
  const { classType } = signal.options;
571
628
  user = isClass2(classType) ? new classType() : classType(conn, ctx);
629
+ if (existingSession?.state) {
630
+ Object.assign(user, existingSession.state);
631
+ }
572
632
  signal()[publicId] = user;
633
+ if (!existingSession) {
634
+ await this.saveSession(privateId, {
635
+ publicId
636
+ });
637
+ }
573
638
  }
574
639
  await awaitReturn(subRoom["onJoin"]?.(user, conn, ctx));
575
640
  conn.setState({
576
- publicId
641
+ publicId,
642
+ privateId
577
643
  });
578
644
  conn.send(JSON.stringify({
579
645
  type: "sync",
580
646
  value: {
581
647
  pId: publicId,
648
+ privateId,
582
649
  ...subRoom.$memoryAll
583
650
  }
584
651
  }));
@@ -611,6 +678,13 @@ var Server = class {
611
678
  return;
612
679
  }
613
680
  const subRoom = await this.getSubRoom();
681
+ const roomGuards = subRoom.constructor["_roomGuards"] || [];
682
+ for (const guard of roomGuards) {
683
+ const isAuthorized = await guard(sender, result.data.value);
684
+ if (!isAuthorized) {
685
+ return;
686
+ }
687
+ }
614
688
  const actions = subRoom.constructor["_actionMetadata"];
615
689
  if (actions) {
616
690
  const signal = this.getUsersProperty(subRoom);
@@ -618,6 +692,13 @@ var Server = class {
618
692
  const user = signal?.()[publicId];
619
693
  const actionName = actions.get(result.data.action);
620
694
  if (actionName) {
695
+ const guards = subRoom.$actionGuards?.get(actionName.key) || [];
696
+ for (const guard of guards) {
697
+ const isAuthorized = await guard(sender, result.data.value);
698
+ if (!isAuthorized) {
699
+ return;
700
+ }
701
+ }
621
702
  if (actionName.bodyValidation) {
622
703
  const bodyResult = actionName.bodyValidation.safeParse(result.data.value);
623
704
  if (!bodyResult.success) {
@@ -646,19 +727,66 @@ var Server = class {
646
727
  async onClose(conn) {
647
728
  const subRoom = await this.getSubRoom();
648
729
  const signal = this.getUsersProperty(subRoom);
649
- const { publicId } = conn.state;
730
+ if (!conn.state) {
731
+ return;
732
+ }
733
+ const { publicId, privateId } = conn.state;
650
734
  const user = signal?.()[publicId];
651
- await awaitReturn(subRoom["onLeave"]?.(user, conn));
652
- if (signal) {
653
- delete signal()[publicId];
735
+ if (!user) return;
736
+ if (privateId) {
737
+ await this.saveSession(privateId, {
738
+ publicId,
739
+ state: {
740
+ ...user
741
+ }
742
+ });
743
+ }
744
+ const existingTimeout = this.timeoutHandles.get(privateId);
745
+ if (existingTimeout) {
746
+ clearTimeout(existingTimeout);
747
+ this.timeoutHandles.delete(privateId);
748
+ }
749
+ const cleanup = /* @__PURE__ */ __name(async () => {
750
+ await awaitReturn(subRoom["onLeave"]?.(user, conn));
751
+ if (signal) {
752
+ delete signal()[publicId];
753
+ }
754
+ if (privateId) {
755
+ await this.deleteSession(privateId);
756
+ }
757
+ this.room.broadcast(JSON.stringify({
758
+ type: "user_disconnected",
759
+ value: {
760
+ publicId
761
+ }
762
+ }));
763
+ this.timeoutHandles.delete(privateId);
764
+ }, "cleanup");
765
+ const disconnectTimeout = subRoom.constructor.disconnectTimeout ?? 0;
766
+ if (disconnectTimeout > 0) {
767
+ if (user.status) {
768
+ user.status.set("offline");
769
+ }
770
+ this.room.broadcast(JSON.stringify({
771
+ type: "user_offline",
772
+ value: {
773
+ publicId
774
+ }
775
+ }));
776
+ const timeout = setTimeout(cleanup, disconnectTimeout);
777
+ this.timeoutHandles.set(privateId, timeout);
778
+ } else {
779
+ await cleanup();
654
780
  }
655
781
  }
656
782
  };
657
783
  export {
658
784
  Action,
659
785
  ClientIo,
786
+ Guard,
660
787
  MockConnection,
661
788
  Room,
789
+ RoomGuard,
662
790
  Server,
663
791
  ServerIo
664
792
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/decorators.ts","../../sync/src/utils.ts","../src/mock.ts","../src/server.ts","../../sync/src/core.ts","../../sync/src/load.ts","../src/utils.ts"],"sourcesContent":["export function Action(name: string, bodyValidation?) {\n return function (target: any, propertyKey: string) {\n if (!target.constructor._actionMetadata) {\n target.constructor._actionMetadata = new Map();\n }\n target.constructor._actionMetadata.set(name, {\n key: propertyKey,\n bodyValidation,\n });\n };\n}\n\nexport interface RoomOptions {\n path: string;\n maxUsers?: number;\n throttleStorage?: number;\n throttleSync?: number;\n hibernate?: boolean;\n}\n\nexport function Room(options: RoomOptions) {\n return function (target: any) {\n target.path = options.path;\n target.maxUsers = options.maxUsers;\n target.throttleStorage = options.throttleStorage;\n target.throttleSync = options.throttleSync;\n };\n}\n","/**\n * Checks if the given value is a function.\n *\n * @param {unknown} val - The value to check.\n * @returns {boolean} - True if the value is a function, false otherwise.\n * @example\n * isFunction(function() {}); // true\n * isFunction(() => {}); // true\n * isFunction(123); // false\n */\nexport function isFunction(val: unknown): boolean {\n return {}.toString.call(val) === \"[object Function]\";\n}\n\n/**\n * Checks if the given object is a class.\n *\n * @param {any} obj - The object to check.\n * @returns {boolean} - True if the object is a class, false otherwise.\n * @example\n * class MyClass {}\n * isClass(MyClass); // true\n * isClass(() => {}); // false\n */\nexport function isClass(obj: any): boolean {\n return (\n typeof obj === \"function\" &&\n obj.prototype &&\n obj.prototype.constructor === obj\n );\n}\n\n/**\n * Checks if the given item is an object.\n *\n * @param {any} item - The item to check.\n * @returns {boolean} - True if the item is an object, false otherwise.\n * @example\n * isObject({}); // true\n * isObject(null); // false\n * isObject([]); // false\n */\nexport const isObject = (item: any): boolean =>\n item && typeof item === \"object\" && !Array.isArray(item) && item !== null;\n\n/**\n * Checks if the given value is an instance of a class.\n *\n * @param {unknown} value - The value to check.\n * @returns {boolean} - True if the value is an instance of a class, false otherwise.\n * @example\n * class MyClass {}\n * const instance = new MyClass();\n * isInstanceOfClass(instance); // true\n * isInstanceOfClass({}); // false\n */\nexport function isInstanceOfClass(value: unknown): boolean {\n if (\n value === null ||\n typeof value !== \"object\" ||\n value === undefined ||\n Array.isArray(value)\n ) {\n return false;\n }\n return Object.getPrototypeOf(value) !== Object.prototype;\n}\n\nexport function generateShortUUID(): string {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let uuid = '';\n for (let i = 0; i < 8; i++) {\n const randomIndex = Math.floor(Math.random() * chars.length);\n uuid += chars[randomIndex];\n }\n return uuid;\n}","import { generateShortUUID } from \"../../sync/src/utils\";\n\nclass MockPartySocket {\n private events: Map<string, Function> = new Map();\n id = generateShortUUID()\n \n addEventListener(event, cb) {\n this.events.set(event, cb);\n }\n\n removeEventListener(event, cb) {\n this.events.delete(event);\n }\n\n _trigger(event, data) {\n this.events.get(event)?.(data);\n }\n}\n\nclass MockStorage {\n private storage: Map<string, any> = new Map();\n \n async get(key: string) {\n return this.storage.get(key);\n }\n \n async put(key: string, value: any) {\n this.storage.set(key, value);\n }\n \n async list() {\n return this.storage\n }\n}\n\nclass MockPartyRoom {\n private clients: Map<string, MockPartySocket> = new Map();\n storage = new MockStorage();\n\n constructor(public id?: string) {\n this.id = id || generateShortUUID()\n }\n\n connection(client) {\n const socket = new MockPartySocket();\n this.clients.set(socket.id, client);\n client.id = socket.id;\n }\n\n broadcast(data: any) {\n this.clients.forEach((client) => {\n client._trigger('message', data);\n });\n }\n\n clear() {\n this.clients.clear();\n }\n}\n\nexport class MockConnection {\n state: any = {};\n\n setState(value: any) {\n this.state = value;\n }\n}\n\nexport const ServerIo = MockPartyRoom;\nexport const ClientIo = MockPartySocket;\n","import { dset } from \"dset\";\nimport z from \"zod\";\nimport {\n createStatesSnapshot,\n getByPath,\n load,\n syncClass,\n} from \"../../sync/src\";\nimport { generateShortUUID } from \"../../sync/src/utils\";\nimport type * as Party from \"./types/party\";\nimport {\n awaitReturn,\n buildObject,\n extractParams,\n isClass,\n throttle,\n} from \"./utils\";\n\nconst Message = z.object({\n action: z.string(),\n value: z.any(),\n});\n\ntype CreateRoomOptions = {\n getMemoryAll?: boolean;\n};\n\n/**\n * @class Server\n * @implements {Party.Server}\n * @description Represents a server that manages rooms and connections for a multiplayer game or application.\n * \n * @example\n * ```typescript\n * import { Room, Server, ServerIo } from \"@yourpackage/room\";\n * \n * @Room({ path: \"game\" })\n * class GameRoom {\n * // Room implementation\n * }\n * \n * class MyServer extends Server {\n * rooms = [GameRoom];\n * }\n * \n * const server = new MyServer(new ServerIo(\"game\"));\n * server.onStart();\n * ```\n */\nexport class Server implements Party.Server {\n subRoom = null;\n rooms: any[] = [];\n\n /**\n * @constructor\n * @param {Party.Room} room - The room object representing the current game or application instance.\n * \n * @example\n * ```typescript\n * const server = new MyServer(new ServerIo(\"game\"));\n * ```\n */\n constructor(readonly room: Party.Room) {}\n\n /**\n * @readonly\n * @property {boolean} isHibernate - Indicates whether the server is in hibernate mode.\n * \n * @example\n * ```typescript\n * if (!server.isHibernate) {\n * console.log(\"Server is active\");\n * }\n * ```\n */\n get isHibernate(): boolean {\n return !!this[\"options\"]?.hibernate;\n }\n\n /**\n * @method onStart\n * @async\n * @description Initializes the server and creates the initial room if not in hibernate mode.\n * @returns {Promise<void>}\n * \n * @example\n * ```typescript\n * async function initServer() {\n * await server.onStart();\n * console.log(\"Server started\");\n * }\n * ```\n */\n\n async onStart() {\n // Only create a room if not in hibernate mode\n // This prevents unnecessary resource allocation for inactive rooms\n if (!this.isHibernate) {\n this.subRoom = await this.createRoom();\n }\n }\n\n /**\n * @method createRoom\n * @private\n * @async\n * @param {CreateRoomOptions} [options={}] - Options for creating the room.\n * @returns {Promise<Object>} The created room instance.\n * @throws {Error} If no matching room is found.\n * \n * @example\n * ```typescript\n * // This method is private and called internally\n * async function internalCreateRoom() {\n * const room = await this.createRoom({ getMemoryAll: true });\n * console.log(\"Room created:\", room);\n * }\n * ```\n */\n private async createRoom(options: CreateRoomOptions = {}) {\n let instance\n let init = true\n\n // Find the appropriate room based on the current room ID\n for (let room of this.rooms) {\n const params = extractParams(room.path, this.room.id);\n if (params) {\n instance = new room(this.room, params);\n break;\n }\n }\n\n if (!instance) {\n throw new Error(\"Room not found\");\n }\n\n // Load the room's memory from storage\n // This ensures persistence across server restarts\n const loadMemory = async () => {\n const root = await this.room.storage.get(\".\");\n const memory = await this.room.storage.list();\n const tmpObject: any = root || {};\n for (let [key, value] of memory) {\n if (key == \".\") {\n continue;\n }\n dset(tmpObject, key, value);\n }\n load(instance, tmpObject);\n };\n\n await loadMemory();\n\n instance.$memoryAll = {}\n\n // Sync callback: Broadcast changes to all clients\n const syncCb = (values) => {\n if (options.getMemoryAll) {\n buildObject(values, instance.$memoryAll);\n }\n if (init && this.isHibernate) {\n init = false;\n return;\n }\n const packet = buildObject(values, instance.$memoryAll);\n this.room.broadcast(\n JSON.stringify({\n type: \"sync\",\n value: packet,\n })\n );\n values.clear();\n }\n\n // Persist callback: Save changes to storage\n const persistCb = async (values) => {\n for (let path of values) {\n const _instance =\n path == \".\" ? instance : getByPath(instance, path);\n const itemValue = createStatesSnapshot(_instance);\n await this.room.storage.put(path, itemValue);\n }\n values.clear();\n }\n\n // Set up syncing and persistence with throttling to optimize performance\n syncClass(instance, {\n onSync: throttle(syncCb, instance[\"throttleSync\"] ?? 500),\n onPersist: throttle(persistCb, instance[\"throttleStorage\"] ?? 2000),\n });\n\n return instance\n }\n\n /**\n * @method getSubRoom\n * @private\n * @async\n * @param {Object} [options={}] - Options for getting the sub-room.\n * @returns {Promise<Object>} The sub-room instance.\n * \n * @example\n * ```typescript\n * // This method is private and called internally\n * async function internalGetSubRoom() {\n * const subRoom = await this.getSubRoom();\n * console.log(\"Sub-room retrieved:\", subRoom);\n * }\n * ```\n */\n private async getSubRoom(options = {}) {\n let subRoom\n if (this.isHibernate) {\n subRoom = await this.createRoom(options)\n }\n else {\n subRoom = this.subRoom\n }\n return subRoom\n }\n\n /**\n * @method getUsersProperty\n * @private\n * @param {Object} subRoom - The sub-room instance.\n * @returns {Object|null} The users property of the sub-room, or null if not found.\n * \n * @example\n * ```typescript\n * // This method is private and called internally\n * function internalGetUsers(subRoom) {\n * const users = this.getUsersProperty(subRoom);\n * console.log(\"Users:\", users);\n * }\n * ```\n */\n\n private getUsersProperty(subRoom) {\n const meta = subRoom.constructor[\"_propertyMetadata\"];\n const propId = meta?.get(\"users\");\n if (propId) {\n return subRoom[propId];\n }\n return null;\n }\n\n /**\n * @method onConnect\n * @async\n * @param {Party.Connection} conn - The connection object for the new user.\n * @param {Party.ConnectionContext} ctx - The context of the connection.\n * @description Handles a new user connection, creates a user object, and sends initial sync data.\n * @returns {Promise<void>}\n * \n * @example\n * ```typescript\n * server.onConnect = async (conn, ctx) => {\n * await server.onConnect(conn, ctx);\n * console.log(\"New user connected:\", conn.id);\n * };\n * ```\n */\n async onConnect(conn: Party.Connection, ctx: Party.ConnectionContext) {\n const subRoom = await this.getSubRoom({\n getMemoryAll: true,\n })\n // Generate a unique public ID for the user\n const publicId = generateShortUUID()\n let user = null;\n const signal = this.getUsersProperty(subRoom);\n if (signal) {\n const { classType } = signal.options;\n // Create a new user instance based on the defined class type\n user = isClass(classType) ? new classType() : classType(conn, ctx);\n signal()[publicId] = user;\n }\n // Call the room's onJoin method if it exists\n await awaitReturn(subRoom[\"onJoin\"]?.(user, conn, ctx));\n conn.setState({ publicId });\n // Send initial sync data to the new connection\n conn.send(\n JSON.stringify({\n type: \"sync\",\n value: {\n pId: publicId,\n ...subRoom.$memoryAll,\n },\n })\n );\n }\n\n /**\n * @method onMessage\n * @async\n * @param {string} message - The message received from a user.\n * @param {Party.Connection} sender - The connection object of the sender.\n * @description Processes incoming messages and triggers corresponding actions in the sub-room.\n * @returns {Promise<void>}\n * \n * @example\n * ```typescript\n * server.onMessage = async (message, sender) => {\n * await server.onMessage(message, sender);\n * console.log(\"Message processed from:\", sender.id);\n * };\n * ```\n */\n\n async onMessage(message: string, sender: Party.Connection) {\n let json\n try {\n json = JSON.parse(message)\n }\n catch (e) {\n return;\n }\n // Validate incoming messages\n const result = Message.safeParse(json);\n if (!result.success) {\n return;\n }\n const subRoom = await this.getSubRoom()\n const actions = subRoom.constructor[\"_actionMetadata\"];\n if (actions) {\n const signal = this.getUsersProperty(subRoom);\n const { publicId } = sender.state as any;\n const user = signal?.()[publicId];\n const actionName = actions.get(result.data.action);\n if (actionName) {\n // Validate action body if a validation schema is defined\n if (actionName.bodyValidation) {\n const bodyResult = actionName.bodyValidation.safeParse(\n result.data.value\n );\n if (!bodyResult.success) {\n return;\n }\n }\n // Execute the action\n await awaitReturn(\n subRoom[actionName.key](user, result.data.value, sender)\n );\n }\n }\n }\n\n /**\n * @method onClose\n * @async\n * @param {Party.Connection} conn - The connection object of the disconnecting user.\n * @description Handles user disconnection, removing them from the room and triggering the onLeave event.\n * @returns {Promise<void>}\n * \n * @example\n * ```typescript\n * server.onClose = async (conn) => {\n * await server.onClose(conn);\n * console.log(\"User disconnected:\", conn.id);\n * };\n * ```\n */\n async onClose(conn: Party.Connection) {\n const subRoom = await this.getSubRoom()\n const signal = this.getUsersProperty(subRoom);\n const { publicId } = conn.state as any;\n const user = signal?.()[publicId];\n // Call the room's onLeave method if it exists\n await awaitReturn(subRoom[\"onLeave\"]?.(user, conn));\n if (signal) {\n // Remove the user from the room\n delete signal()[publicId];\n }\n }\n}\n","import {\n ArraySubject,\n ObjectSubject,\n isSignal,\n type WritableSignal,\n} from \"@signe/reactive\";\nimport { isInstanceOfClass, isObject } from \"./utils\";\n\ninterface SyncOptions {\n onSync?: (value: Map<string, any>) => void;\n onPersist?: (value: Set<string>) => void;\n}\n\ninterface TypeOptions {\n syncToClient?: boolean;\n persist?: boolean;\n classType?: any;\n}\n\n/**\n * Synchronizes an instance by adding `$valuesChanges` methods for state management.\n *\n * This function initializes a cache for syncing and persisting values. It adds methods to the instance\n * to set values, mark values for persistence, and check and retrieve values from the cache.\n * Optionally, callbacks can be provided to handle synchronization and persistence events.\n *\n * @param {Record<string, any>} instance - The instance to be synchronized.\n * @param {SyncOptions} [options={}] - Optional synchronization options.\n * @param {Function} [options.onSync] - Callback function to be called on value sync with the current cache.\n * @param {Function} [options.onPersist] - Callback function to be called on value persistence with the current cache.\n *\n * @example\n * class TestClass {\n * @sync() count = signal(0);\n * @sync() text = signal('hello');\n * }\n * const instance = new TestClass();\n * syncClass(instance, {\n * onSync: (cache) => console.log('Sync cache:', cache),\n * onPersist: (cache) => console.log('Persist cache:', cache),\n * });\n */\nexport const syncClass = (instance: any, options: SyncOptions = {}) => {\n const cacheSync = new Map();\n const cachePersist = new Set<string>();\n instance.$valuesChanges = {\n set: (path: string, value: any) => {\n cacheSync.set(path, value);\n options.onSync?.(cacheSync);\n },\n setPersist: (path: string) => {\n if (path == \"\") path = \".\";\n cachePersist.add(path);\n options.onPersist?.(cachePersist);\n },\n has: (path: string) => {\n return cacheSync.has(path);\n },\n get: (path: string) => {\n return cacheSync.get(path);\n },\n };\n createSyncClass(instance);\n};\n\n/**\n * Creates a snapshot of the current state of an instance's signals.\n *\n * This function iterates over the signals stored in the instance's $snapshot property.\n * If a signal's value is not an object or array and the signal's persist option is true or undefined,\n * it adds the signal's value to the returned snapshot object.\n *\n * @param {Record<string, any>} instance - The instance containing the $snapshot map of signals.\n * @returns {Record<string, any>} - An object representing the persisted snapshot of the instance's state.\n *\n * @example\n * ```typescript\n * class TestClass {\n * @sync() count = signal(0);\n * @sync() text = signal('hello');\n * }\n * const instance = new TestClass();\n * syncClass(instance);\n * const snapshot = createStatesSnapshot(instance);\n * console.log(snapshot); // { count: 0, text: 'hello' }\n * ```\n */\nexport function createStatesSnapshot(instance: Record<string, any>): Record<string, any> {\n let persistObject: any = {};\n if (instance.$snapshot) {\n for (const key of instance.$snapshot.keys()) {\n const signal = instance.$snapshot.get(key);\n const persist = signal.options.persist ?? true;\n let value = signal();\n if (isObject(value) || Array.isArray(value)) {\n break;\n }\n if (persist) {\n persistObject[key] = value;\n }\n }\n }\n return persistObject;\n}\n\nexport function setMetadata(target: any, key: string, value: any) {\n const meta = target.constructor._propertyMetadata;\n const propId = meta?.get(key);\n if (propId) {\n if (isSignal(target[propId])) {\n target[propId].set(value);\n } else {\n target[propId] = value;\n }\n }\n}\n\nexport const createSyncClass = (\n currentClass: any,\n parentKey: any = null,\n parentClass = null,\n path = \"\"\n) => {\n currentClass.$path = path;\n if (parentClass) {\n currentClass.$valuesChanges = parentClass.$valuesChanges;\n }\n if (parentKey) {\n setMetadata(currentClass, \"id\", parentKey);\n }\n if (currentClass.$snapshot) {\n for (const key of currentClass.$snapshot.keys()) {\n const signal = currentClass.$snapshot.get(key);\n const syncToClient = signal.options.syncToClient ?? true;\n const persist = signal.options.persist ?? true;\n let value = signal();\n if (isObject(value) || Array.isArray(value)) {\n value = { ...value };\n }\n const newPath = (path ? path + \".\" : \"\") + key;\n if (syncToClient) {\n currentClass.$valuesChanges.set(newPath, value);\n }\n if (persist) {\n if (parentClass) currentClass.$valuesChanges.setPersist(path);\n }\n }\n }\n};\n\nexport const type = (\n _signal: any,\n path: string,\n options: TypeOptions = {},\n currentInstance: any\n): WritableSignal<any> => {\n const syncToClient = options.syncToClient ?? true;\n const persist = options.persist ?? true;\n let init = true;\n _signal.options = options;\n _signal.observable.subscribe((value) => {\n const check = currentInstance.$valuesChanges;\n\n function savePath(propPath, value) {\n if (syncToClient) check.set(propPath, value);\n if (persist) {\n check.setPersist(currentInstance.$path);\n }\n }\n\n if (init) {\n init = false;\n return;\n }\n if (currentInstance.$path !== undefined) {\n const propPath =\n (currentInstance.$path ? currentInstance.$path + \".\" : \"\") + path;\n if (_signal._subject instanceof ObjectSubject) {\n const newPath =\n (currentInstance.$path ? currentInstance.$path + \".\" : \"\") +\n path +\n \".\" +\n value.key;\n\n if (value.type == \"add\") {\n if (isInstanceOfClass(value.value)) {\n createSyncClass(value.value, value.key, currentInstance, newPath);\n } else {\n savePath(newPath, value.value);\n }\n } else if (value.type == \"update\") {\n if (isObject(value.value) || Array.isArray(value.value)) {\n createSyncClass(value.value, value.key, currentInstance, newPath);\n } else {\n savePath(newPath, value.value);\n }\n } else if (value.type == \"remove\") {\n savePath(newPath, \"$delete\");\n }\n } else if (_signal._subject instanceof ArraySubject) {\n const newPath = propPath + \".\" + value.index;\n const firstItem = value.items[0];\n if (value.type == \"add\") {\n if (isInstanceOfClass(firstItem)) {\n createSyncClass(firstItem, value.key, currentInstance, newPath);\n } else {\n savePath(newPath, firstItem);\n }\n } else if (value.type == \"update\") {\n if (isObject(firstItem) || Array.isArray(firstItem)) {\n createSyncClass(firstItem, value.key, currentInstance, newPath);\n } else {\n savePath(newPath, firstItem);\n }\n } else if (value.type == \"remove\") {\n savePath(newPath, \"$delete\");\n }\n } else {\n savePath(propPath, value);\n }\n }\n });\n\n if (!currentInstance.$snapshot) {\n currentInstance.$snapshot = new Map();\n }\n\n currentInstance.$snapshot.set(path, _signal);\n\n return _signal;\n};\n","import { isSignal } from \"@signe/reactive\";\nimport { setMetadata } from \"./core\";\nimport { isClass } from \"./utils\";\n\n/**\n * Loads values into the root instance by paths or from an object.\n * \n * @param {object} rootInstance - The instance into which values will be loaded.\n * @param {object} values - The values to load, either as paths or an object.\n * @param {boolean} [valueIsObject=false] - If true, `values` is treated as an object.\n * @example\n * // Using paths:\n * load(instance, { 'position.x': 10, 'position.y': 20 });\n * \n * // Using an object:\n * load(instance, { position: { x: 10, y: 20 } }, true);\n */\nexport function load(rootInstance: any, values: { [path: string]: any }): void;\nexport function load(\n rootInstance: any,\n values: object,\n valueIsObject: true\n): void;\nexport function load(\n rootInstance: any,\n values: { [path: string]: any } | object,\n valueIsObject?: boolean\n) {\n if (valueIsObject) {\n loadFromObject(rootInstance, values);\n } else {\n loadFromPaths(rootInstance, values);\n }\n}\n\n/**\n * Loads values into the root instance using paths.\n * \n * @param {object} rootInstance - The instance into which values will be loaded.\n * @param {object} values - The values to load, with keys as paths.\n * @example\n * loadFromPaths(instance, { 'position.x': 10, 'position.y': 20 });\n */\nfunction loadFromPaths(rootInstance: any, values: { [path: string]: any }) {\n for (const [path, value] of Object.entries(values)) {\n const parts = path.split(\".\");\n loadValue(rootInstance, parts, value);\n }\n}\n\n/**\n * Recursively loads values from an object into the root instance.\n * \n * @param {object} rootInstance - The instance into which values will be loaded.\n * @param {object} values - The values to load.\n * @param {string} [currentPath=\"\"] - The current path in the recursion.\n * @example\n * loadFromObject(instance, { position: { x: 10, y: 20 } });\n */\nfunction loadFromObject(\n rootInstance: any,\n values: object,\n currentPath: string = \"\"\n) {\n for (let key in values) {\n const value = values[key];\n const newPath = currentPath ? `${currentPath}.${key}` : key;\n if (typeof value === \"object\" && !Array.isArray(value) && value !== null) {\n loadFromObject(rootInstance, value, newPath);\n } else {\n const parts = newPath.split(\".\");\n loadValue(rootInstance, parts, value);\n }\n }\n}\n\n/**\n * Sets a value in the root instance by navigating through the path parts.\n * \n * @param {object} rootInstance - The instance into which the value will be set.\n * @param {string[]} parts - The parts of the path.\n * @param {any} value - The value to set.\n * @example\n * loadValue(instance, ['position', 'x'], 10);\n */\nfunction loadValue(rootInstance: any, parts: string[], value: any) {\n let current: any = rootInstance;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n\n if (i === parts.length - 1) {\n if (value == '$delete') {\n if (isSignal(current)) {\n current = current();\n }\n Reflect.deleteProperty(current, part);\n }\n else if (current[part]?._subject) {\n current[part].set(value);\n }\n else {\n current[part] = value;\n }\n } else {\n if (isSignal(current)) {\n current = current();\n }\n const currentValue = current[part];\n if (currentValue === undefined) {\n const parentInstance = getByPath(\n rootInstance,\n parts.slice(0, i).join(\".\")\n );\n const classType = parentInstance?.options?.classType;\n if (classType) {\n current[part] = !isClass(classType) ? classType(part) : new classType();\n setMetadata(current[part], 'id', part)\n } else {\n current[part] = {};\n }\n }\n current = current[part];\n }\n }\n}\n\n/**\n * Retrieves a value from the root instance by a path.\n * \n * @param {object} root - The root instance.\n * @param {string} path - The path to the value.\n * @returns {any} - The value at the specified path.\n * @example\n * const value = getByPath(instance, 'position.x');\n */\nexport function getByPath(root: any, path: string) {\n const parts = path.split(\".\");\n let current = root;\n for (const part of parts) {\n if (isSignal(current)) {\n current = current();\n }\n if (current[part]) {\n current = current[part];\n } else {\n return undefined;\n }\n }\n return current;\n}\n","import { dset } from \"dset\";\n\n/**\n * Checks if a value is a Promise.\n *\n * @param {unknown} value - The value to check.\n * @returns {boolean} - Returns true if the value is a Promise, otherwise false.\n *\n * @example\n * isPromise(Promise.resolve()); // true\n * isPromise(42); // false\n */\nexport function isPromise(value: unknown): value is Promise<any> {\n return value instanceof Promise;\n}\n\n/**\n * Awaits the given value if it is a Promise, otherwise returns the value directly.\n *\n * @param {unknown} val - The value to await or return.\n * @returns {Promise<any>} - Returns a Promise that resolves to the value.\n *\n * @example\n * awaitReturn(Promise.resolve(42)); // 42\n * awaitReturn(42); // 42\n */\nexport async function awaitReturn(val: unknown): Promise<any> {\n return isPromise(val) ? await val : val;\n}\n\n/**\n * Checks if a value is a class.\n *\n * @param {unknown} obj - The value to check.\n * @returns {boolean} - Returns true if the value is a class, otherwise false.\n *\n * @example\n * class MyClass {}\n * isClass(MyClass); // true\n * isClass(() => {}); // false\n */\nexport function isClass(obj: unknown): boolean {\n return (\n typeof obj === \"function\" &&\n obj.prototype &&\n obj.prototype.constructor === obj\n );\n}\n\n\n/**\n * Creates a throttled function that only invokes the provided function at most once per every wait milliseconds.\n *\n * The throttled function comes with a cancel method to cancel delayed invocations.\n * If the throttled function is invoked more than once during the wait timeout,\n * it will call the provided function with the latest arguments.\n *\n * @template F - The type of the function to throttle.\n * @param {F} func - The function to throttle.\n * @param {number} wait - The number of milliseconds to throttle invocations to.\n * @returns {(...args: Parameters<F>) => void} - Returns the new throttled function.\n *\n * @example\n * const log = throttle((message) => console.log(message), 1000);\n * log(\"Hello\"); // Will log \"Hello\" immediately\n * log(\"World\"); // Will log \"World\" after 1 second, if no other calls to log() are made within the 1 second.\n */\nexport function throttle<F extends (...args: any[]) => any>(\n func: F,\n wait: number\n): (...args: Parameters<F>) => void {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let lastArgs: Parameters<F> | null = null;\n\n return function (...args: Parameters<F>) {\n if (!timeout) {\n func(...args);\n timeout = setTimeout(() => {\n if (lastArgs) {\n func(...lastArgs);\n lastArgs = null;\n }\n timeout = null;\n }, wait);\n } else {\n lastArgs = args;\n }\n };\n}\n\n/**\n * Extracts parameters from a given string based on a specified pattern.\n *\n * The pattern can include placeholders in the form of {paramName}, which will be\n * extracted from the input string if they match.\n *\n * @param {string} pattern - The pattern containing placeholders.\n * @param {string} str - The string to extract parameters from.\n * @returns {{ [key: string]: string } | null} - An object containing the extracted parameters,\n * or null if the string does not match the pattern.\n *\n * @example\n * // returns { id: '123' }\n * extractParams('game-{id}', 'game-123');\n *\n * @example\n * // returns { foo: 'abc', bar: 'xyz' }\n * extractParams('test-{foo}-{bar}', 'test-abc-xyz');\n *\n */\nexport function extractParams(\n pattern: string,\n str: string\n): { [key: string]: string } | null {\n // Replace placeholders in the pattern with named capture groups\n const regexPattern = pattern.replace(/{(\\w+)}/g, \"(?<$1>[\\\\w-]+)\");\n\n // Create a strict regular expression from the pattern\n const regex = new RegExp(`^${regexPattern}$`);\n const match = regex.exec(str);\n\n // If a match is found and groups are present, return the captured groups\n if (match && match.groups) {\n return match.groups;\n } else if (pattern === str) {\n // If the pattern exactly matches the string, return an empty object\n return {};\n } else {\n // Otherwise, return null\n return null;\n }\n}\n\n/**\n * Removes a property from an object based on a dot-separated key string or an array of keys.\n *\n * The function modifies the original object by deleting the specified property.\n * It safely handles dangerous keys like __proto__, constructor, and prototype.\n *\n * @param {Record<string, any>} obj - The object from which to remove the property.\n * @param {string | string[]} keys - The key(s) specifying the property to remove. Can be a dot-separated string or an array of strings.\n *\n * @example\n * const obj = { a: { b: { c: 3 } } };\n * dremove(obj, 'a.b.c');\n * // obj is now { a: { b: {} } }\n *\n * @example\n * const obj = { a: 1, b: 2 };\n * dremove(obj, 'a');\n * // obj is now { b: 2 }\n *\n * @example\n * const obj = { a: { b: { c: 3 } } };\n * dremove(obj, ['a', 'b', 'c']);\n * // obj is now { a: { b: {} } }\n */\nexport function dremove(\n obj: Record<string, any>,\n keys: string | string[]\n): void {\n // If keys is a string, convert it to an array using the \".\" separator\n if (typeof keys === \"string\") {\n keys = keys.split(\".\");\n }\n\n let i = 0;\n const l = keys.length;\n let t = obj;\n let k;\n\n while (i < l - 1) {\n k = keys[i++];\n if (k === \"__proto__\" || k === \"constructor\" || k === \"prototype\") return; // Avoid dangerous keys\n if (typeof t[k] !== \"object\" || t[k] === null) return; // If the object doesn't exist, stop\n t = t[k];\n }\n\n k = keys[i];\n if (\n t &&\n typeof t === \"object\" &&\n !(k === \"__proto__\" || k === \"constructor\" || k === \"prototype\")\n ) {\n delete t[k];\n }\n}\n\n/**\n * Builds an object from a map of values and updates the provided memory object.\n *\n * For each key-value pair in the map, this function sets the value at the given path in the `memoryObj`.\n * If the value is \"$delete\", it removes the corresponding path from `allMemory`.\n *\n * @param {Map<string, any>} valuesMap - A map where the keys are paths and the values are the values to set at those paths.\n * @param {Record<string, any>} allMemory - The object to update based on the values in the map.\n * @returns {Record<string, any>} - The built memory object with the applied values from the map.\n *\n * @example\n * const valuesMap = new Map();\n * valuesMap.set('a.b.c', 1);\n * valuesMap.set('x.y.z', '$delete');\n * const allMemory = { x: { y: { z: 2 } } };\n * const result = buildObject(valuesMap, allMemory);\n * // result is { a: { b: { c: 1 } }, x: { y: { z: '$delete' } } }\n * // allMemory is { a: { b: { c: 1 } }, x: { y: {} } }\n */\nexport function buildObject(valuesMap: Map<string, any>, allMemory: Record<string, any>): Record<string, any> {\n let memoryObj = {};\n for (let path of valuesMap.keys()) {\n const value = valuesMap.get(path);\n dset(memoryObj, path, value);\n if (value === \"$delete\") {\n dremove(allMemory, path);\n } else {\n dset(allMemory, path, value);\n }\n }\n return memoryObj;\n}"],"mappings":"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","names":["Action","name","bodyValidation","target","propertyKey","constructor","_actionMetadata","Map","set","key","Room","options","path","maxUsers","throttleStorage","throttleSync","isClass","obj","prototype","constructor","isObject","item","Array","isArray","generateShortUUID","chars","uuid","i","randomIndex","Math","floor","random","length","MockPartySocket","events","Map","id","generateShortUUID","addEventListener","event","cb","set","removeEventListener","delete","_trigger","data","get","MockStorage","storage","key","put","value","list","MockPartyRoom","clients","constructor","connection","client","socket","broadcast","forEach","clear","MockConnection","state","setState","ServerIo","ClientIo","dset","z","ArraySubject","ObjectSubject","isSignal","syncClass","instance","options","cacheSync","Map","cachePersist","Set","$valuesChanges","set","path","value","onSync","setPersist","add","onPersist","has","get","createSyncClass","createStatesSnapshot","persistObject","$snapshot","key","keys","signal","persist","isObject","Array","isArray","setMetadata","target","meta","constructor","_propertyMetadata","propId","isSignal","currentClass","parentKey","parentClass","$path","syncToClient","newPath","isSignal","load","rootInstance","values","valueIsObject","loadFromObject","loadFromPaths","path","value","Object","entries","parts","split","loadValue","currentPath","key","newPath","Array","isArray","current","i","length","part","isSignal","Reflect","deleteProperty","_subject","set","currentValue","undefined","parentInstance","getByPath","slice","join","classType","options","isClass","setMetadata","root","dset","isPromise","value","Promise","awaitReturn","val","isClass","obj","prototype","constructor","throttle","func","wait","timeout","lastArgs","args","setTimeout","extractParams","pattern","str","regexPattern","replace","regex","RegExp","match","exec","groups","dremove","keys","split","i","l","length","t","k","buildObject","valuesMap","allMemory","memoryObj","path","get","dset","Message","z","object","action","string","value","any","Server","subRoom","rooms","constructor","room","isHibernate","hibernate","onStart","createRoom","options","instance","init","params","extractParams","path","id","Error","loadMemory","root","storage","get","memory","list","tmpObject","key","dset","load","$memoryAll","syncCb","values","getMemoryAll","buildObject","packet","broadcast","JSON","stringify","type","clear","persistCb","_instance","getByPath","itemValue","createStatesSnapshot","put","syncClass","onSync","throttle","onPersist","getSubRoom","getUsersProperty","meta","propId","onConnect","conn","ctx","publicId","generateShortUUID","user","signal","classType","isClass","awaitReturn","setState","send","pId","onMessage","message","sender","json","parse","e","result","safeParse","success","actions","state","actionName","data","bodyValidation","bodyResult","onClose"]}
1
+ {"version":3,"sources":["../src/decorators.ts","../../sync/src/utils.ts","../src/storage.ts","../src/mock.ts","../src/server.ts","../../sync/src/core.ts","../../sync/src/load.ts","../src/utils.ts"],"sourcesContent":["import type * as Party from \"./types/party\"\nimport type { z } from \"zod\"\ntype GuardFn = (sender: Party.Connection, value: any) => boolean | Promise<boolean>;\ntype RoomGuardFn = (conn: Party.Connection, ctx: Party.ConnectionContext) => boolean | Promise<boolean>;\n\nexport function Action(name: string, bodyValidation?: z.ZodSchema) {\n return function (target: any, propertyKey: string) {\n if (!target.constructor._actionMetadata) {\n target.constructor._actionMetadata = new Map();\n }\n target.constructor._actionMetadata.set(name, {\n key: propertyKey,\n bodyValidation,\n });\n };\n}\n\nexport interface RoomOptions {\n path: string;\n maxUsers?: number;\n throttleStorage?: number;\n throttleSync?: number;\n hibernate?: boolean;\n guards?: RoomGuardFn[];\n disconnectTimeout?: number;\n}\n\nexport function Room(options: RoomOptions) {\n return function (target: any) {\n target.path = options.path;\n target.maxUsers = options.maxUsers;\n target.throttleStorage = options.throttleStorage;\n target.throttleSync = options.throttleSync;\n target.disconnectTimeout = options.disconnectTimeout ?? 0;\n if (options.guards) {\n target['_roomGuards'] = options.guards;\n }\n };\n}\n\n/**\n * Room guard decorator\n * @param guards Array of guard functions to check on connection\n */\nexport function RoomGuard(guards: RoomGuardFn[]) {\n return function (target: any) {\n target['_roomGuards'] = guards;\n };\n}\n\n/**\n * Action guard decorator\n * @param guards Array of guard functions to check before action execution\n */\nexport function Guard(guards: GuardFn[]) {\n return function (\n target: any,\n propertyKey: string,\n descriptor: PropertyDescriptor\n ) {\n if (!target.constructor['_actionGuards']) {\n target.constructor['_actionGuards'] = new Map();\n }\n target.constructor['_actionGuards'].set(propertyKey, guards);\n };\n}","/**\n * Checks if the given value is a function.\n *\n * @param {unknown} val - The value to check.\n * @returns {boolean} - True if the value is a function, false otherwise.\n * @example\n * isFunction(function() {}); // true\n * isFunction(() => {}); // true\n * isFunction(123); // false\n */\nexport function isFunction(val: unknown): boolean {\n return {}.toString.call(val) === \"[object Function]\";\n}\n\n/**\n * Checks if the given object is a class.\n *\n * @param {any} obj - The object to check.\n * @returns {boolean} - True if the object is a class, false otherwise.\n * @example\n * class MyClass {}\n * isClass(MyClass); // true\n * isClass(() => {}); // false\n */\nexport function isClass(obj: any): boolean {\n return (\n typeof obj === \"function\" &&\n obj.prototype &&\n obj.prototype.constructor === obj\n );\n}\n\n/**\n * Checks if the given item is an object.\n *\n * @param {any} item - The item to check.\n * @returns {boolean} - True if the item is an object, false otherwise.\n * @example\n * isObject({}); // true\n * isObject(null); // false\n * isObject([]); // false\n */\nexport const isObject = (item: any): boolean =>\n item && typeof item === \"object\" && !Array.isArray(item) && item !== null;\n\n/**\n * Checks if the given value is an instance of a class.\n *\n * @param {unknown} value - The value to check.\n * @returns {boolean} - True if the value is an instance of a class, false otherwise.\n * @example\n * class MyClass {}\n * const instance = new MyClass();\n * isInstanceOfClass(instance); // true\n * isInstanceOfClass({}); // false\n */\nexport function isInstanceOfClass(value: unknown): boolean {\n if (\n value === null ||\n typeof value !== \"object\" ||\n value === undefined ||\n Array.isArray(value)\n ) {\n return false;\n }\n return Object.getPrototypeOf(value) !== Object.prototype;\n}\n\nexport function generateShortUUID(): string {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let uuid = '';\n for (let i = 0; i < 8; i++) {\n const randomIndex = Math.floor(Math.random() * chars.length);\n uuid += chars[randomIndex];\n }\n return uuid;\n}","export class Storage {\n private memory = new Map();\n async put(key, value) {\n this.memory.set(key, value);\n }\n async get(key) {\n return this.memory.get(key);\n }\n async delete(key) {\n this.memory.delete(key);\n }\n async list() {\n return this.memory;\n }\n}\n","import { generateShortUUID } from \"../../sync/src/utils\";\nimport { Storage } from \"./storage\";\n\nclass MockPartySocket {\n private events: Map<string, Function> = new Map();\n id = generateShortUUID()\n \n addEventListener(event, cb) {\n this.events.set(event, cb);\n }\n\n removeEventListener(event, cb) {\n this.events.delete(event);\n }\n\n _trigger(event, data) {\n this.events.get(event)?.(data);\n }\n}\n\nclass MockPartyRoom {\n private clients: Map<string, MockPartySocket> = new Map();\n storage = new Storage();\n\n constructor(public id?: string) {\n this.id = id || generateShortUUID()\n }\n\n connection(client) {\n const socket = new MockPartySocket();\n this.clients.set(socket.id, client);\n client.id = socket.id;\n }\n\n broadcast(data: any) {\n this.clients.forEach((client) => {\n client._trigger('message', data);\n });\n }\n\n clear() {\n this.clients.clear();\n }\n}\n\nexport class MockConnection {\n state: any = {};\n\n setState(value: any) {\n this.state = value;\n }\n}\n\nexport const ServerIo = MockPartyRoom;\nexport const ClientIo = MockPartySocket;\n","import { dset } from \"dset\";\nimport z from \"zod\";\nimport {\n createStatesSnapshot,\n getByPath,\n load,\n syncClass,\n} from \"../../sync/src\";\nimport { generateShortUUID } from \"../../sync/src/utils\";\nimport type * as Party from \"./types/party\";\nimport {\n awaitReturn,\n buildObject,\n extractParams,\n isClass,\n throttle,\n} from \"./utils\";\n\nconst Message = z.object({\n action: z.string(),\n value: z.any(),\n});\n\ntype CreateRoomOptions = {\n getMemoryAll?: boolean;\n};\n\n/**\n * @class Server\n * @implements {Party.Server}\n * @description Represents a server that manages rooms and connections for a multiplayer game or application.\n * \n * @example\n * ```typescript\n * import { Room, Server, ServerIo } from \"@yourpackage/room\";\n * \n * @Room({ path: \"game\" })\n * class GameRoom {\n * // Room implementation\n * }\n * \n * class MyServer extends Server {\n * rooms = [GameRoom];\n * }\n * \n * const server = new MyServer(new ServerIo(\"game\"));\n * server.onStart();\n * ```\n */\nexport class Server implements Party.Server {\n subRoom = null;\n rooms: any[] = [];\n private timeoutHandles: Map<string, any> = new Map();\n\n static async onBeforeConnect(request: Party.Request, lobby: Party.Lobby) {\n const token = new URL(request.url).searchParams.get(\"token\") ?? \"\";\n request.headers.set(\"X-User-ID\", token);\n return request;\n }\n\n /**\n * @constructor\n * @param {Party.Room} room - The room object representing the current game or application instance.\n * \n * @example\n * ```typescript\n * const server = new MyServer(new ServerIo(\"game\"));\n * ```\n */\n constructor(readonly room: Party.Room) {}\n\n /**\n * @readonly\n * @property {boolean} isHibernate - Indicates whether the server is in hibernate mode.\n * \n * @example\n * ```typescript\n * if (!server.isHibernate) {\n * console.log(\"Server is active\");\n * }\n * ```\n */\n get isHibernate(): boolean {\n return !!this[\"options\"]?.hibernate;\n }\n\n /**\n * @method onStart\n * @async\n * @description Initializes the server and creates the initial room if not in hibernate mode.\n * @returns {Promise<void>}\n * \n * @example\n * ```typescript\n * async function initServer() {\n * await server.onStart();\n * console.log(\"Server started\");\n * }\n * ```\n */\n\n async onStart() {\n // Only create a room if not in hibernate mode\n // This prevents unnecessary resource allocation for inactive rooms\n if (!this.isHibernate) {\n this.subRoom = await this.createRoom();\n }\n }\n\n /**\n * @method createRoom\n * @private\n * @async\n * @param {CreateRoomOptions} [options={}] - Options for creating the room.\n * @returns {Promise<Object>} The created room instance.\n * @throws {Error} If no matching room is found.\n * \n * @example\n * ```typescript\n * // This method is private and called internally\n * async function internalCreateRoom() {\n * const room = await this.createRoom({ getMemoryAll: true });\n * console.log(\"Room created:\", room);\n * }\n * ```\n */\n private async createRoom(options: CreateRoomOptions = {}) {\n let instance\n let init = true\n\n // Find the appropriate room based on the current room ID\n for (let room of this.rooms) {\n const params = extractParams(room.path, this.room.id);\n if (params) {\n instance = new room(this.room, params);\n break;\n }\n }\n\n if (!instance) {\n throw new Error(\"Room not found\");\n }\n\n // Load the room's memory from storage\n // This ensures persistence across server restarts\n const loadMemory = async () => {\n const root = await this.room.storage.get(\".\");\n const memory = await this.room.storage.list();\n const tmpObject: any = root || {};\n for (let [key, value] of memory) {\n if (key == \".\") {\n continue;\n }\n dset(tmpObject, key, value);\n }\n load(instance, tmpObject);\n };\n\n await loadMemory();\n\n instance.$memoryAll = {}\n\n // Sync callback: Broadcast changes to all clients\n const syncCb = (values) => {\n if (options.getMemoryAll) {\n buildObject(values, instance.$memoryAll);\n }\n if (init && this.isHibernate) {\n init = false;\n return;\n }\n const packet = buildObject(values, instance.$memoryAll);\n this.room.broadcast(\n JSON.stringify({\n type: \"sync\",\n value: packet,\n })\n );\n values.clear();\n }\n\n // Persist callback: Save changes to storage\n const persistCb = async (values) => {\n for (let path of values) {\n const _instance =\n path == \".\" ? instance : getByPath(instance, path);\n const itemValue = createStatesSnapshot(_instance);\n await this.room.storage.put(path, itemValue);\n }\n values.clear();\n }\n\n // Set up syncing and persistence with throttling to optimize performance\n syncClass(instance, {\n onSync: throttle(syncCb, instance[\"throttleSync\"] ?? 500),\n onPersist: throttle(persistCb, instance[\"throttleStorage\"] ?? 2000),\n });\n\n return instance\n }\n\n /**\n * @method getSubRoom\n * @private\n * @async\n * @param {Object} [options={}] - Options for getting the sub-room.\n * @returns {Promise<Object>} The sub-room instance.\n * \n * @example\n * ```typescript\n * // This method is private and called internally\n * async function internalGetSubRoom() {\n * const subRoom = await this.getSubRoom();\n * console.log(\"Sub-room retrieved:\", subRoom);\n * }\n * ```\n */\n private async getSubRoom(options = {}) {\n let subRoom\n if (this.isHibernate) {\n subRoom = await this.createRoom(options)\n }\n else {\n subRoom = this.subRoom\n }\n return subRoom\n }\n\n /**\n * @method getUsersProperty\n * @private\n * @param {Object} subRoom - The sub-room instance.\n * @returns {Object|null} The users property of the sub-room, or null if not found.\n * \n * @example\n * ```typescript\n * // This method is private and called internally\n * function internalGetUsers(subRoom) {\n * const users = this.getUsersProperty(subRoom);\n * console.log(\"Users:\", users);\n * }\n * ```\n */\n\n private getUsersProperty(subRoom) {\n const meta = subRoom.constructor[\"_propertyMetadata\"];\n const propId = meta?.get(\"users\");\n if (propId) {\n return subRoom[propId];\n }\n return null;\n }\n\n private async getSession(privateId: string): Promise<{publicId: string, state?: any} | null> {\n if (!privateId) return null;\n try {\n const session = await this.room.storage.get(`session:${privateId}`);\n return session as {publicId: string, state?: any} | null;\n } catch (e) {\n return null;\n }\n }\n\n private async saveSession(privateId: string, data: {publicId: string, state?: any}) {\n await this.room.storage.put(`session:${privateId}`, data);\n }\n\n private async deleteSession(privateId: string) {\n await this.room.storage.delete(`session:${privateId}`);\n }\n\n /**\n * @method onConnect\n * @async\n * @param {Party.Connection} conn - The connection object for the new user.\n * @param {Party.ConnectionContext} ctx - The context of the connection.\n * @description Handles a new user connection, creates a user object, and sends initial sync data.\n * @returns {Promise<void>}\n * \n * @example\n * ```typescript\n * server.onConnect = async (conn, ctx) => {\n * await server.onConnect(conn, ctx);\n * console.log(\"New user connected:\", conn.id);\n * };\n * ```\n */\n async onConnect(conn: Party.Connection, ctx: Party.ConnectionContext) {\n const subRoom = await this.getSubRoom({\n getMemoryAll: true,\n })\n\n // Check room guards\n const roomGuards = subRoom.constructor['_roomGuards'] || [];\n for (const guard of roomGuards) {\n const isAuthorized = await guard(conn, ctx);\n if (!isAuthorized) {\n conn.close();\n return;\n }\n }\n\n // Check for existing session\n const providedPrivateId = ctx.request?.headers.get(\"X-User-ID\");\n const existingSession = providedPrivateId ? await this.getSession(providedPrivateId) : null;\n\n // Generate IDs\n const publicId = existingSession?.publicId || generateShortUUID();\n const privateId = existingSession ? providedPrivateId : generateShortUUID();\n\n let user = null;\n const signal = this.getUsersProperty(subRoom);\n \n if (signal) {\n const { classType } = signal.options;\n user = isClass(classType) ? new classType() : classType(conn, ctx);\n \n // Restore state if exists\n if (existingSession?.state) {\n Object.assign(user, existingSession.state);\n }\n \n signal()[publicId] = user;\n \n // Only store new session if it doesn't exist\n if (!existingSession) {\n await this.saveSession(privateId, {\n publicId\n });\n }\n }\n\n // Call the room's onJoin method if it exists\n await awaitReturn(subRoom[\"onJoin\"]?.(user, conn, ctx));\n \n // Store both IDs in connection state\n conn.setState({ publicId, privateId });\n\n // Send initial sync data with both IDs to the new connection\n conn.send(\n JSON.stringify({\n type: \"sync\",\n value: {\n pId: publicId,\n privateId,\n ...subRoom.$memoryAll,\n },\n })\n );\n }\n\n /**\n * @method onMessage\n * @async\n * @param {string} message - The message received from a user.\n * @param {Party.Connection} sender - The connection object of the sender.\n * @description Processes incoming messages and triggers corresponding actions in the sub-room.\n * @returns {Promise<void>}\n * \n * @example\n * ```typescript\n * server.onMessage = async (message, sender) => {\n * await server.onMessage(message, sender);\n * console.log(\"Message processed from:\", sender.id);\n * };\n * ```\n */\n\n async onMessage(message: string, sender: Party.Connection) {\n let json\n try {\n json = JSON.parse(message)\n }\n catch (e) {\n return;\n }\n // Validate incoming messages\n const result = Message.safeParse(json);\n if (!result.success) {\n return;\n }\n const subRoom = await this.getSubRoom()\n\n // Check room guards\n const roomGuards = subRoom.constructor['_roomGuards'] || [];\n for (const guard of roomGuards) {\n const isAuthorized = await guard(sender, result.data.value);\n if (!isAuthorized) {\n return;\n }\n }\n\n const actions = subRoom.constructor[\"_actionMetadata\"];\n if (actions) {\n const signal = this.getUsersProperty(subRoom);\n const { publicId } = sender.state as any;\n const user = signal?.()[publicId];\n const actionName = actions.get(result.data.action);\n if (actionName) {\n\n // Check all guards if they exist\n const guards = subRoom.$actionGuards?.get(actionName.key) || [];\n for (const guard of guards) {\n const isAuthorized = await guard(sender, result.data.value);\n if (!isAuthorized) {\n return;\n }\n }\n\n // Validate action body if a validation schema is defined\n if (actionName.bodyValidation) {\n const bodyResult = actionName.bodyValidation.safeParse(\n result.data.value\n );\n if (!bodyResult.success) {\n return;\n }\n }\n // Execute the action\n await awaitReturn(\n subRoom[actionName.key](user, result.data.value, sender)\n );\n }\n }\n }\n\n /**\n * @method onClose\n * @async\n * @param {Party.Connection} conn - The connection object of the disconnecting user.\n * @description Handles user disconnection, removing them from the room and triggering the onLeave event.\n * @returns {Promise<void>}\n * \n * @example\n * ```typescript\n * server.onClose = async (conn) => {\n * await server.onClose(conn);\n * console.log(\"User disconnected:\", conn.id);\n * };\n * ```\n */\n async onClose(conn: Party.Connection) {\n const subRoom = await this.getSubRoom()\n const signal = this.getUsersProperty(subRoom);\n if (!conn.state) {\n return;\n }\n const { publicId, privateId } = conn.state as any;\n const user = signal?.()[publicId];\n \n if (!user) return;\n\n // Save current state\n if (privateId) {\n await this.saveSession(privateId, {\n publicId,\n state: { ...user }\n });\n }\n\n // Clear any existing timeout for this user\n const existingTimeout = this.timeoutHandles.get(privateId);\n if (existingTimeout) {\n clearTimeout(existingTimeout);\n this.timeoutHandles.delete(privateId);\n }\n\n const cleanup = async () => {\n // Call onLeave hook\n await awaitReturn(subRoom[\"onLeave\"]?.(user, conn));\n \n // Remove user from signal\n if (signal) {\n delete signal()[publicId];\n }\n \n // Delete session\n if (privateId) {\n await this.deleteSession(privateId);\n }\n\n // Broadcast user disconnection\n this.room.broadcast(\n JSON.stringify({\n type: \"user_disconnected\",\n value: { publicId }\n })\n );\n\n // Clear timeout handle\n this.timeoutHandles.delete(privateId);\n };\n\n const disconnectTimeout = subRoom.constructor.disconnectTimeout ?? 0;\n \n if (disconnectTimeout > 0) {\n // Set temporary offline status\n if (user.status) {\n user.status.set('offline');\n }\n\n // Broadcast temporary disconnection\n this.room.broadcast(\n JSON.stringify({\n type: \"user_offline\",\n value: { publicId }\n })\n );\n\n // Set cleanup timeout\n const timeout = setTimeout(cleanup, disconnectTimeout);\n this.timeoutHandles.set(privateId, timeout);\n } else {\n // Immediate cleanup if no timeout\n await cleanup();\n }\n }\n}\n","import {\n ArraySubject,\n ObjectSubject,\n isSignal,\n type WritableSignal,\n} from \"@signe/reactive\";\nimport { isInstanceOfClass, isObject } from \"./utils\";\n\ninterface SyncOptions {\n onSync?: (value: Map<string, any>) => void;\n onPersist?: (value: Set<string>) => void;\n}\n\ninterface TypeOptions {\n syncToClient?: boolean;\n persist?: boolean;\n classType?: any;\n}\n\n/**\n * Synchronizes an instance by adding `$valuesChanges` methods for state management.\n *\n * This function initializes a cache for syncing and persisting values. It adds methods to the instance\n * to set values, mark values for persistence, and check and retrieve values from the cache.\n * Optionally, callbacks can be provided to handle synchronization and persistence events.\n *\n * @param {Record<string, any>} instance - The instance to be synchronized.\n * @param {SyncOptions} [options={}] - Optional synchronization options.\n * @param {Function} [options.onSync] - Callback function to be called on value sync with the current cache.\n * @param {Function} [options.onPersist] - Callback function to be called on value persistence with the current cache.\n *\n * @example\n * class TestClass {\n * @sync() count = signal(0);\n * @sync() text = signal('hello');\n * }\n * const instance = new TestClass();\n * syncClass(instance, {\n * onSync: (cache) => console.log('Sync cache:', cache),\n * onPersist: (cache) => console.log('Persist cache:', cache),\n * });\n */\nexport const syncClass = (instance: any, options: SyncOptions = {}) => {\n const cacheSync = new Map();\n const cachePersist = new Set<string>();\n instance.$valuesChanges = {\n set: (path: string, value: any) => {\n cacheSync.set(path, value);\n options.onSync?.(cacheSync);\n },\n setPersist: (path: string) => {\n if (path == \"\") path = \".\";\n cachePersist.add(path);\n options.onPersist?.(cachePersist);\n },\n has: (path: string) => {\n return cacheSync.has(path);\n },\n get: (path: string) => {\n return cacheSync.get(path);\n },\n };\n createSyncClass(instance);\n};\n\n/**\n * Creates a snapshot of the current state of an instance's signals.\n *\n * This function iterates over the signals stored in the instance's $snapshot property.\n * If a signal's value is not an object or array and the signal's persist option is true or undefined,\n * it adds the signal's value to the returned snapshot object.\n *\n * @param {Record<string, any>} instance - The instance containing the $snapshot map of signals.\n * @returns {Record<string, any>} - An object representing the persisted snapshot of the instance's state.\n *\n * @example\n * ```typescript\n * class TestClass {\n * @sync() count = signal(0);\n * @sync() text = signal('hello');\n * }\n * const instance = new TestClass();\n * syncClass(instance);\n * const snapshot = createStatesSnapshot(instance);\n * console.log(snapshot); // { count: 0, text: 'hello' }\n * ```\n */\nexport function createStatesSnapshot(instance: Record<string, any>): Record<string, any> {\n let persistObject: any = {};\n if (instance.$snapshot) {\n for (const key of instance.$snapshot.keys()) {\n const signal = instance.$snapshot.get(key);\n const persist = signal.options.persist ?? true;\n let value = signal();\n if (isObject(value) || Array.isArray(value)) {\n break;\n }\n if (persist) {\n persistObject[key] = value;\n }\n }\n }\n return persistObject;\n}\n\nexport function setMetadata(target: any, key: string, value: any) {\n const meta = target.constructor._propertyMetadata;\n const propId = meta?.get(key);\n if (propId) {\n if (isSignal(target[propId])) {\n target[propId].set(value);\n } else {\n target[propId] = value;\n }\n }\n}\n\nexport const createSyncClass = (\n currentClass: any,\n parentKey: any = null,\n parentClass = null,\n path = \"\"\n) => {\n currentClass.$path = path;\n if (parentClass) {\n currentClass.$valuesChanges = parentClass.$valuesChanges;\n }\n if (parentKey) {\n setMetadata(currentClass, \"id\", parentKey);\n }\n if (currentClass.$snapshot) {\n for (const key of currentClass.$snapshot.keys()) {\n const signal = currentClass.$snapshot.get(key);\n const syncToClient = signal.options.syncToClient ?? true;\n const persist = signal.options.persist ?? true;\n let value = signal();\n if (isObject(value) || Array.isArray(value)) {\n value = { ...value };\n }\n const newPath = (path ? path + \".\" : \"\") + key;\n if (syncToClient) {\n currentClass.$valuesChanges.set(newPath, value);\n }\n if (persist) {\n if (parentClass) currentClass.$valuesChanges.setPersist(path);\n }\n }\n }\n};\n\nexport const type = (\n _signal: any,\n path: string,\n options: TypeOptions = {},\n currentInstance: any\n): WritableSignal<any> => {\n const syncToClient = options.syncToClient ?? true;\n const persist = options.persist ?? true;\n let init = true;\n _signal.options = options;\n _signal.observable.subscribe((value) => {\n const check = currentInstance.$valuesChanges;\n\n function savePath(propPath, value) {\n if (syncToClient) check.set(propPath, value);\n if (persist) {\n check.setPersist(currentInstance.$path);\n }\n }\n\n if (init) {\n init = false;\n return;\n }\n if (currentInstance.$path !== undefined) {\n const propPath =\n (currentInstance.$path ? currentInstance.$path + \".\" : \"\") + path;\n if (_signal._subject instanceof ObjectSubject) {\n const newPath =\n (currentInstance.$path ? currentInstance.$path + \".\" : \"\") +\n path +\n \".\" +\n value.key;\n\n if (value.type == \"add\") {\n if (isInstanceOfClass(value.value)) {\n createSyncClass(value.value, value.key, currentInstance, newPath);\n } else {\n savePath(newPath, value.value);\n }\n } else if (value.type == \"update\") {\n if (isObject(value.value) || Array.isArray(value.value)) {\n createSyncClass(value.value, value.key, currentInstance, newPath);\n } else {\n savePath(newPath, value.value);\n }\n } else if (value.type == \"remove\") {\n savePath(newPath, \"$delete\");\n }\n } else if (_signal._subject instanceof ArraySubject) {\n const newPath = propPath + \".\" + value.index;\n const firstItem = value.items[0];\n if (value.type == \"add\") {\n if (isInstanceOfClass(firstItem)) {\n createSyncClass(firstItem, value.key, currentInstance, newPath);\n } else {\n savePath(newPath, firstItem);\n }\n } else if (value.type == \"update\") {\n if (isObject(firstItem) || Array.isArray(firstItem)) {\n createSyncClass(firstItem, value.key, currentInstance, newPath);\n } else {\n savePath(newPath, firstItem);\n }\n } else if (value.type == \"remove\") {\n savePath(newPath, \"$delete\");\n }\n } else {\n savePath(propPath, value);\n }\n }\n });\n\n if (!currentInstance.$snapshot) {\n currentInstance.$snapshot = new Map();\n }\n\n currentInstance.$snapshot.set(path, _signal);\n\n return _signal;\n};\n","import { isSignal } from \"@signe/reactive\";\nimport { setMetadata } from \"./core\";\nimport { isClass } from \"./utils\";\n\n/**\n * Loads values into the root instance by paths or from an object.\n * \n * @param {object} rootInstance - The instance into which values will be loaded.\n * @param {object} values - The values to load, either as paths or an object.\n * @param {boolean} [valueIsObject=false] - If true, `values` is treated as an object.\n * @example\n * // Using paths:\n * load(instance, { 'position.x': 10, 'position.y': 20 });\n * \n * // Using an object:\n * load(instance, { position: { x: 10, y: 20 } }, true);\n */\nexport function load(rootInstance: any, values: { [path: string]: any }): void;\nexport function load(\n rootInstance: any,\n values: object,\n valueIsObject: true\n): void;\nexport function load(\n rootInstance: any,\n values: { [path: string]: any } | object,\n valueIsObject?: boolean\n) {\n if (valueIsObject) {\n loadFromObject(rootInstance, values);\n } else {\n loadFromPaths(rootInstance, values);\n }\n}\n\n/**\n * Loads values into the root instance using paths.\n * \n * @param {object} rootInstance - The instance into which values will be loaded.\n * @param {object} values - The values to load, with keys as paths.\n * @example\n * loadFromPaths(instance, { 'position.x': 10, 'position.y': 20 });\n */\nfunction loadFromPaths(rootInstance: any, values: { [path: string]: any }) {\n for (const [path, value] of Object.entries(values)) {\n const parts = path.split(\".\");\n loadValue(rootInstance, parts, value);\n }\n}\n\n/**\n * Recursively loads values from an object into the root instance.\n * \n * @param {object} rootInstance - The instance into which values will be loaded.\n * @param {object} values - The values to load.\n * @param {string} [currentPath=\"\"] - The current path in the recursion.\n * @example\n * loadFromObject(instance, { position: { x: 10, y: 20 } });\n */\nfunction loadFromObject(\n rootInstance: any,\n values: object,\n currentPath: string = \"\"\n) {\n for (let key in values) {\n const value = values[key];\n const newPath = currentPath ? `${currentPath}.${key}` : key;\n if (typeof value === \"object\" && !Array.isArray(value) && value !== null) {\n loadFromObject(rootInstance, value, newPath);\n } else {\n const parts = newPath.split(\".\");\n loadValue(rootInstance, parts, value);\n }\n }\n}\n\n/**\n * Sets a value in the root instance by navigating through the path parts.\n * \n * @param {object} rootInstance - The instance into which the value will be set.\n * @param {string[]} parts - The parts of the path.\n * @param {any} value - The value to set.\n * @example\n * loadValue(instance, ['position', 'x'], 10);\n */\nfunction loadValue(rootInstance: any, parts: string[], value: any) {\n let current: any = rootInstance;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n\n if (i === parts.length - 1) {\n if (value == '$delete') {\n if (isSignal(current)) {\n current = current();\n }\n Reflect.deleteProperty(current, part);\n }\n else if (current[part]?._subject) {\n current[part].set(value);\n }\n else {\n current[part] = value;\n }\n } else {\n if (isSignal(current)) {\n current = current();\n }\n const currentValue = current[part];\n if (currentValue === undefined) {\n const parentInstance = getByPath(\n rootInstance,\n parts.slice(0, i).join(\".\")\n );\n const classType = parentInstance?.options?.classType;\n if (classType) {\n current[part] = !isClass(classType) ? classType(part) : new classType();\n setMetadata(current[part], 'id', part)\n } else {\n current[part] = {};\n }\n }\n current = current[part];\n }\n }\n}\n\n/**\n * Retrieves a value from the root instance by a path.\n * \n * @param {object} root - The root instance.\n * @param {string} path - The path to the value.\n * @returns {any} - The value at the specified path.\n * @example\n * const value = getByPath(instance, 'position.x');\n */\nexport function getByPath(root: any, path: string) {\n const parts = path.split(\".\");\n let current = root;\n for (const part of parts) {\n if (isSignal(current)) {\n current = current();\n }\n if (current[part]) {\n current = current[part];\n } else {\n return undefined;\n }\n }\n return current;\n}\n","import { dset } from \"dset\";\n\n/**\n * Checks if a value is a Promise.\n *\n * @param {unknown} value - The value to check.\n * @returns {boolean} - Returns true if the value is a Promise, otherwise false.\n *\n * @example\n * isPromise(Promise.resolve()); // true\n * isPromise(42); // false\n */\nexport function isPromise(value: unknown): value is Promise<any> {\n return value instanceof Promise;\n}\n\n/**\n * Awaits the given value if it is a Promise, otherwise returns the value directly.\n *\n * @param {unknown} val - The value to await or return.\n * @returns {Promise<any>} - Returns a Promise that resolves to the value.\n *\n * @example\n * awaitReturn(Promise.resolve(42)); // 42\n * awaitReturn(42); // 42\n */\nexport async function awaitReturn(val: unknown): Promise<any> {\n return isPromise(val) ? await val : val;\n}\n\n/**\n * Checks if a value is a class.\n *\n * @param {unknown} obj - The value to check.\n * @returns {boolean} - Returns true if the value is a class, otherwise false.\n *\n * @example\n * class MyClass {}\n * isClass(MyClass); // true\n * isClass(() => {}); // false\n */\nexport function isClass(obj: unknown): boolean {\n return (\n typeof obj === \"function\" &&\n obj.prototype &&\n obj.prototype.constructor === obj\n );\n}\n\n\n/**\n * Creates a throttled function that only invokes the provided function at most once per every wait milliseconds.\n *\n * The throttled function comes with a cancel method to cancel delayed invocations.\n * If the throttled function is invoked more than once during the wait timeout,\n * it will call the provided function with the latest arguments.\n *\n * @template F - The type of the function to throttle.\n * @param {F} func - The function to throttle.\n * @param {number} wait - The number of milliseconds to throttle invocations to.\n * @returns {(...args: Parameters<F>) => void} - Returns the new throttled function.\n *\n * @example\n * const log = throttle((message) => console.log(message), 1000);\n * log(\"Hello\"); // Will log \"Hello\" immediately\n * log(\"World\"); // Will log \"World\" after 1 second, if no other calls to log() are made within the 1 second.\n */\nexport function throttle<F extends (...args: any[]) => any>(\n func: F,\n wait: number\n): (...args: Parameters<F>) => void {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n let lastArgs: Parameters<F> | null = null;\n\n return function (...args: Parameters<F>) {\n if (!timeout) {\n func(...args);\n timeout = setTimeout(() => {\n if (lastArgs) {\n func(...lastArgs);\n lastArgs = null;\n }\n timeout = null;\n }, wait);\n } else {\n lastArgs = args;\n }\n };\n}\n\n/**\n * Extracts parameters from a given string based on a specified pattern.\n *\n * The pattern can include placeholders in the form of {paramName}, which will be\n * extracted from the input string if they match.\n *\n * @param {string} pattern - The pattern containing placeholders.\n * @param {string} str - The string to extract parameters from.\n * @returns {{ [key: string]: string } | null} - An object containing the extracted parameters,\n * or null if the string does not match the pattern.\n *\n * @example\n * // returns { id: '123' }\n * extractParams('game-{id}', 'game-123');\n *\n * @example\n * // returns { foo: 'abc', bar: 'xyz' }\n * extractParams('test-{foo}-{bar}', 'test-abc-xyz');\n *\n */\nexport function extractParams(\n pattern: string,\n str: string\n): { [key: string]: string } | null {\n // Replace placeholders in the pattern with named capture groups\n const regexPattern = pattern.replace(/{(\\w+)}/g, \"(?<$1>[\\\\w-]+)\");\n\n // Create a strict regular expression from the pattern\n const regex = new RegExp(`^${regexPattern}$`);\n const match = regex.exec(str);\n\n // If a match is found and groups are present, return the captured groups\n if (match && match.groups) {\n return match.groups;\n } else if (pattern === str) {\n // If the pattern exactly matches the string, return an empty object\n return {};\n } else {\n // Otherwise, return null\n return null;\n }\n}\n\n/**\n * Removes a property from an object based on a dot-separated key string or an array of keys.\n *\n * The function modifies the original object by deleting the specified property.\n * It safely handles dangerous keys like __proto__, constructor, and prototype.\n *\n * @param {Record<string, any>} obj - The object from which to remove the property.\n * @param {string | string[]} keys - The key(s) specifying the property to remove. Can be a dot-separated string or an array of strings.\n *\n * @example\n * const obj = { a: { b: { c: 3 } } };\n * dremove(obj, 'a.b.c');\n * // obj is now { a: { b: {} } }\n *\n * @example\n * const obj = { a: 1, b: 2 };\n * dremove(obj, 'a');\n * // obj is now { b: 2 }\n *\n * @example\n * const obj = { a: { b: { c: 3 } } };\n * dremove(obj, ['a', 'b', 'c']);\n * // obj is now { a: { b: {} } }\n */\nexport function dremove(\n obj: Record<string, any>,\n keys: string | string[]\n): void {\n // If keys is a string, convert it to an array using the \".\" separator\n if (typeof keys === \"string\") {\n keys = keys.split(\".\");\n }\n\n let i = 0;\n const l = keys.length;\n let t = obj;\n let k;\n\n while (i < l - 1) {\n k = keys[i++];\n if (k === \"__proto__\" || k === \"constructor\" || k === \"prototype\") return; // Avoid dangerous keys\n if (typeof t[k] !== \"object\" || t[k] === null) return; // If the object doesn't exist, stop\n t = t[k];\n }\n\n k = keys[i];\n if (\n t &&\n typeof t === \"object\" &&\n !(k === \"__proto__\" || k === \"constructor\" || k === \"prototype\")\n ) {\n delete t[k];\n }\n}\n\n/**\n * Builds an object from a map of values and updates the provided memory object.\n *\n * For each key-value pair in the map, this function sets the value at the given path in the `memoryObj`.\n * If the value is \"$delete\", it removes the corresponding path from `allMemory`.\n *\n * @param {Map<string, any>} valuesMap - A map where the keys are paths and the values are the values to set at those paths.\n * @param {Record<string, any>} allMemory - The object to update based on the values in the map.\n * @returns {Record<string, any>} - The built memory object with the applied values from the map.\n *\n * @example\n * const valuesMap = new Map();\n * valuesMap.set('a.b.c', 1);\n * valuesMap.set('x.y.z', '$delete');\n * const allMemory = { x: { y: { z: 2 } } };\n * const result = buildObject(valuesMap, allMemory);\n * // result is { a: { b: { c: 1 } }, x: { y: { z: '$delete' } } }\n * // allMemory is { a: { b: { c: 1 } }, x: { y: {} } }\n */\nexport function buildObject(valuesMap: Map<string, any>, allMemory: Record<string, any>): Record<string, any> {\n let memoryObj = {};\n for (let path of valuesMap.keys()) {\n const value = valuesMap.get(path);\n dset(memoryObj, path, value);\n if (value === \"$delete\") {\n dremove(allMemory, path);\n } else {\n dset(allMemory, path, value);\n }\n }\n return memoryObj;\n}"],"mappings":";;;;AAKO,SAASA,OAAOC,MAAcC,gBAA4B;AAC/D,SAAO,SAAUC,QAAaC,aAAmB;AAC/C,QAAI,CAACD,OAAOE,YAAYC,iBAAiB;AACvCH,aAAOE,YAAYC,kBAAkB,oBAAIC,IAAAA;IAC3C;AACAJ,WAAOE,YAAYC,gBAAgBE,IAAIP,MAAM;MAC3CQ,KAAKL;MACLF;IACF,CAAA;EACF;AACF;AAVgBF;AAsBT,SAASU,KAAKC,SAAoB;AACvC,SAAO,SAAUR,QAAW;AAC1BA,WAAOS,OAAOD,QAAQC;AACtBT,WAAOU,WAAWF,QAAQE;AAC1BV,WAAOW,kBAAkBH,QAAQG;AACjCX,WAAOY,eAAeJ,QAAQI;AAC9BZ,WAAOa,oBAAoBL,QAAQK,qBAAqB;AACxD,QAAIL,QAAQM,QAAQ;AAClBd,aAAO,aAAA,IAAiBQ,QAAQM;IAClC;EACF;AACF;AAXgBP;AAiBT,SAASQ,UAAUD,QAAqB;AAC7C,SAAO,SAAUd,QAAW;AAC1BA,WAAO,aAAA,IAAiBc;EAC1B;AACF;AAJgBC;AAUT,SAASC,MAAMF,QAAiB;AACrC,SAAO,SACLd,QACAC,aACAgB,YAA8B;AAE9B,QAAI,CAACjB,OAAOE,YAAY,eAAA,GAAkB;AACxCF,aAAOE,YAAY,eAAA,IAAmB,oBAAIE,IAAAA;IAC5C;AACAJ,WAAOE,YAAY,eAAA,EAAiBG,IAAIJ,aAAaa,MAAAA;EACvD;AACF;AAXgBE;;;AC9BT,SAASE,QAAQC,KAAQ;AAC9B,SACE,OAAOA,QAAQ,cACfA,IAAIC,aACJD,IAAIC,UAAUC,gBAAgBF;AAElC;AANgBD;AAkBT,IAAMI,WAAW,wBAACC,SACvBA,QAAQ,OAAOA,SAAS,YAAY,CAACC,MAAMC,QAAQF,IAAAA,KAASA,SAAS,MAD/C;AA0BjB,SAASG,oBAAAA;AACd,QAAMC,QAAQ;AACd,MAAIC,OAAO;AACX,WAASC,IAAI,GAAGA,IAAI,GAAGA,KAAK;AACxB,UAAMC,cAAcC,KAAKC,MAAMD,KAAKE,OAAM,IAAKN,MAAMO,MAAM;AAC3DN,YAAQD,MAAMG,WAAAA;EAClB;AACA,SAAOF;AACT;AARgBF;;;ACpET,IAAMS,UAAN,MAAMA;EAAb,OAAaA;;;EACHC,SAAS,oBAAIC,IAAAA;EACrB,MAAMC,IAAIC,KAAKC,OAAO;AACpB,SAAKJ,OAAOK,IAAIF,KAAKC,KAAAA;EACvB;EACA,MAAME,IAAIH,KAAK;AACb,WAAO,KAAKH,OAAOM,IAAIH,GAAAA;EACzB;EACA,MAAMI,OAAOJ,KAAK;AAChB,SAAKH,OAAOO,OAAOJ,GAAAA;EACrB;EACA,MAAMK,OAAO;AACX,WAAO,KAAKR;EACd;AACF;;;ACXA,IAAMS,kBAAN,MAAMA,iBAAAA;EAHN,OAGMA;;;EACMC,SAAgC,oBAAIC,IAAAA;EAC5CC,KAAKC,kBAAAA;EAELC,iBAAiBC,OAAOC,IAAI;AACxB,SAAKN,OAAOO,IAAIF,OAAOC,EAAAA;EAC3B;EAEAE,oBAAoBH,OAAOC,IAAI;AAC3B,SAAKN,OAAOS,OAAOJ,KAAAA;EACvB;EAEAK,SAASL,OAAOM,MAAM;AAClB,SAAKX,OAAOY,IAAIP,KAAAA,IAASM,IAAAA;EAC7B;AACJ;AAEA,IAAME,gBAAN,MAAMA,eAAAA;EApBN,OAoBMA;;;;EACIC;EACRC;EAEAC,YAAmBd,IAAa;SAAbA,KAAAA;SAHXY,UAAwC,oBAAIb,IAAAA;SACpDc,UAAU,IAAIE,QAAAA;AAGZ,SAAKf,KAAKA,MAAMC,kBAAAA;EAClB;EAEAe,WAAWC,QAAQ;AACjB,UAAMC,SAAS,IAAIrB,gBAAAA;AACnB,SAAKe,QAAQP,IAAIa,OAAOlB,IAAIiB,MAAAA;AAC5BA,WAAOjB,KAAKkB,OAAOlB;EACrB;EAEAmB,UAAUV,MAAW;AACnB,SAAKG,QAAQQ,QAAQ,CAACH,WAAAA;AACpBA,aAAOT,SAAS,WAAWC,IAAAA;IAC7B,CAAA;EACF;EAEAY,QAAQ;AACN,SAAKT,QAAQS,MAAK;EACpB;AACF;AAEO,IAAMC,iBAAN,MAAMA;EA7Cb,OA6CaA;;;EACXC,QAAa,CAAC;EAEdC,SAASC,OAAY;AACnB,SAAKF,QAAQE;EACf;AACF;AAEO,IAAMC,WAAWf;AACjB,IAAMgB,WAAW9B;;;ACtDxB,SAAS+B,QAAAA,aAAY;AACrB,OAAOC,OAAO;;;ACDd,SACEC,cACAC,eACAC,gBAEK;AAqCA,IAAMC,YAAY,wBAACC,UAAeC,UAAuB,CAAC,MAAC;AAChE,QAAMC,YAAY,oBAAIC,IAAAA;AACtB,QAAMC,eAAe,oBAAIC,IAAAA;AACzBL,WAASM,iBAAiB;IACxBC,KAAK,wBAACC,MAAcC,UAAAA;AAClBP,gBAAUK,IAAIC,MAAMC,KAAAA;AACpBR,cAAQS,SAASR,SAAAA;IACnB,GAHK;IAILS,YAAY,wBAACH,SAAAA;AACX,UAAIA,QAAQ,GAAIA,QAAO;AACvBJ,mBAAaQ,IAAIJ,IAAAA;AACjBP,cAAQY,YAAYT,YAAAA;IACtB,GAJY;IAKZU,KAAK,wBAACN,SAAAA;AACJ,aAAON,UAAUY,IAAIN,IAAAA;IACvB,GAFK;IAGLO,KAAK,wBAACP,SAAAA;AACJ,aAAON,UAAUa,IAAIP,IAAAA;IACvB,GAFK;EAGP;AACAQ,kBAAgBhB,QAAAA;AAClB,GArByB;AA6ClB,SAASiB,qBAAqBjB,UAA6B;AAChE,MAAIkB,gBAAqB,CAAC;AAC1B,MAAIlB,SAASmB,WAAW;AACtB,eAAWC,OAAOpB,SAASmB,UAAUE,KAAI,GAAI;AAC3C,YAAMC,SAAStB,SAASmB,UAAUJ,IAAIK,GAAAA;AACtC,YAAMG,UAAUD,OAAOrB,QAAQsB,WAAW;AAC1C,UAAId,QAAQa,OAAAA;AACZ,UAAIE,SAASf,KAAAA,KAAUgB,MAAMC,QAAQjB,KAAAA,GAAQ;AAC3C;MACF;AACA,UAAIc,SAAS;AACXL,sBAAcE,GAAAA,IAAOX;MACvB;IACF;EACF;AACA,SAAOS;AACT;AAhBgBD;AAkBT,SAASU,YAAYC,QAAaR,KAAaX,OAAU;AAC9D,QAAMoB,OAAOD,OAAOE,YAAYC;AAChC,QAAMC,SAASH,MAAMd,IAAIK,GAAAA;AACzB,MAAIY,QAAQ;AACV,QAAIC,SAASL,OAAOI,MAAAA,CAAO,GAAG;AAC5BJ,aAAOI,MAAAA,EAAQzB,IAAIE,KAAAA;IACrB,OAAO;AACLmB,aAAOI,MAAAA,IAAUvB;IACnB;EACF;AACF;AAVgBkB;AAYT,IAAMX,kBAAkB,wBAC7BkB,cACAC,YAAiB,MACjBC,cAAc,MACd5B,OAAO,OAAE;AAET0B,eAAaG,QAAQ7B;AACrB,MAAI4B,aAAa;AACfF,iBAAa5B,iBAAiB8B,YAAY9B;EAC5C;AACA,MAAI6B,WAAW;AACbR,gBAAYO,cAAc,MAAMC,SAAAA;EAClC;AACA,MAAID,aAAaf,WAAW;AAC1B,eAAWC,OAAOc,aAAaf,UAAUE,KAAI,GAAI;AAC/C,YAAMC,SAASY,aAAaf,UAAUJ,IAAIK,GAAAA;AAC1C,YAAMkB,eAAehB,OAAOrB,QAAQqC,gBAAgB;AACpD,YAAMf,UAAUD,OAAOrB,QAAQsB,WAAW;AAC1C,UAAId,QAAQa,OAAAA;AACZ,UAAIE,SAASf,KAAAA,KAAUgB,MAAMC,QAAQjB,KAAAA,GAAQ;AAC3CA,gBAAQ;UAAE,GAAGA;QAAM;MACrB;AACA,YAAM8B,WAAW/B,OAAOA,OAAO,MAAM,MAAMY;AAC3C,UAAIkB,cAAc;AAChBJ,qBAAa5B,eAAeC,IAAIgC,SAAS9B,KAAAA;MAC3C;AACA,UAAIc,SAAS;AACX,YAAIa,YAAaF,cAAa5B,eAAeK,WAAWH,IAAAA;MAC1D;IACF;EACF;AACF,GA/B+B;;;ACrH/B,SAASgC,YAAAA,iBAAgB;AAuBlB,SAASC,KACdC,cACAC,QACAC,eAAuB;AAEvB,MAAIA,eAAe;AACjBC,mBAAeH,cAAcC,MAAAA;EAC/B,OAAO;AACLG,kBAAcJ,cAAcC,MAAAA;EAC9B;AACF;AAVgBF;AAoBhB,SAASK,cAAcJ,cAAmBC,QAA+B;AACvE,aAAW,CAACI,MAAMC,KAAAA,KAAUC,OAAOC,QAAQP,MAAAA,GAAS;AAClD,UAAMQ,QAAQJ,KAAKK,MAAM,GAAA;AACzBC,cAAUX,cAAcS,OAAOH,KAAAA;EACjC;AACF;AALSF;AAgBT,SAASD,eACPH,cACAC,QACAW,cAAsB,IAAE;AAExB,WAASC,OAAOZ,QAAQ;AACtB,UAAMK,QAAQL,OAAOY,GAAAA;AACrB,UAAMC,UAAUF,cAAc,GAAGA,WAAAA,IAAeC,GAAAA,KAAQA;AACxD,QAAI,OAAOP,UAAU,YAAY,CAACS,MAAMC,QAAQV,KAAAA,KAAUA,UAAU,MAAM;AACxEH,qBAAeH,cAAcM,OAAOQ,OAAAA;IACtC,OAAO;AACL,YAAML,QAAQK,QAAQJ,MAAM,GAAA;AAC5BC,gBAAUX,cAAcS,OAAOH,KAAAA;IACjC;EACF;AACF;AAfSH;AA0BT,SAASQ,UAAUX,cAAmBS,OAAiBH,OAAU;AAC/D,MAAIW,UAAejB;AAEnB,WAASkB,IAAI,GAAGA,IAAIT,MAAMU,QAAQD,KAAK;AACrC,UAAME,OAAOX,MAAMS,CAAAA;AAEnB,QAAIA,MAAMT,MAAMU,SAAS,GAAG;AAC1B,UAAIb,SAAS,WAAW;AACtB,YAAIe,UAASJ,OAAAA,GAAU;AACrBA,oBAAUA,QAAAA;QACZ;AACAK,gBAAQC,eAAeN,SAASG,IAAAA;MAClC,WACSH,QAAQG,IAAAA,GAAOI,UAAU;AAChCP,gBAAQG,IAAAA,EAAMK,IAAInB,KAAAA;MACpB,OACK;AACHW,gBAAQG,IAAAA,IAAQd;MAClB;IACF,OAAO;AACL,UAAIe,UAASJ,OAAAA,GAAU;AACrBA,kBAAUA,QAAAA;MACZ;AACA,YAAMS,eAAeT,QAAQG,IAAAA;AAC7B,UAAIM,iBAAiBC,QAAW;AAC9B,cAAMC,iBAAiBC,UACrB7B,cACAS,MAAMqB,MAAM,GAAGZ,CAAAA,EAAGa,KAAK,GAAA,CAAA;AAEzB,cAAMC,YAAYJ,gBAAgBK,SAASD;AAC3C,YAAIA,WAAW;AACbf,kBAAQG,IAAAA,IAAQ,CAACc,QAAQF,SAAAA,IAAaA,UAAUZ,IAAAA,IAAQ,IAAIY,UAAAA;AAC5DG,sBAAYlB,QAAQG,IAAAA,GAAO,MAAMA,IAAAA;QACnC,OAAO;AACLH,kBAAQG,IAAAA,IAAQ,CAAC;QACnB;MACF;AACAH,gBAAUA,QAAQG,IAAAA;IACpB;EACF;AACF;AAxCST;AAmDF,SAASkB,UAAUO,MAAW/B,MAAY;AAC/C,QAAMI,QAAQJ,KAAKK,MAAM,GAAA;AACzB,MAAIO,UAAUmB;AACd,aAAWhB,QAAQX,OAAO;AACxB,QAAIY,UAASJ,OAAAA,GAAU;AACrBA,gBAAUA,QAAAA;IACZ;AACA,QAAIA,QAAQG,IAAAA,GAAO;AACjBH,gBAAUA,QAAQG,IAAAA;IACpB,OAAO;AACL,aAAOO;IACT;EACF;AACA,SAAOV;AACT;AAdgBY;;;ACxIhB,SAASQ,YAAY;AAYd,SAASC,UAAUC,OAAc;AACtC,SAAOA,iBAAiBC;AAC1B;AAFgBF;AAchB,eAAsBG,YAAYC,KAAY;AAC5C,SAAOJ,UAAUI,GAAAA,IAAO,MAAMA,MAAMA;AACtC;AAFsBD;AAef,SAASE,SAAQC,KAAY;AAClC,SACE,OAAOA,QAAQ,cACfA,IAAIC,aACJD,IAAIC,UAAUC,gBAAgBF;AAElC;AANgBD,OAAAA,UAAAA;AA0BT,SAASI,SACdC,MACAC,MAAY;AAEZ,MAAIC,UAAgD;AACpD,MAAIC,WAAiC;AAErC,SAAO,YAAaC,MAAmB;AACrC,QAAI,CAACF,SAAS;AACZF,WAAAA,GAAQI,IAAAA;AACRF,gBAAUG,WAAW,MAAA;AACnB,YAAIF,UAAU;AACZH,eAAAA,GAAQG,QAAAA;AACRA,qBAAW;QACb;AACAD,kBAAU;MACZ,GAAGD,IAAAA;IACL,OAAO;AACLE,iBAAWC;IACb;EACF;AACF;AArBgBL;AA2CT,SAASO,cACdC,SACAC,KAAW;AAGX,QAAMC,eAAeF,QAAQG,QAAQ,YAAY,gBAAA;AAGjD,QAAMC,QAAQ,IAAIC,OAAO,IAAIH,YAAAA,GAAe;AAC5C,QAAMI,QAAQF,MAAMG,KAAKN,GAAAA;AAGzB,MAAIK,SAASA,MAAME,QAAQ;AACzB,WAAOF,MAAME;EACf,WAAWR,YAAYC,KAAK;AAE1B,WAAO,CAAC;EACV,OAAO;AAEL,WAAO;EACT;AACF;AArBgBF;AA+CT,SAASU,QACdpB,KACAqB,MAAuB;AAGvB,MAAI,OAAOA,SAAS,UAAU;AAC5BA,WAAOA,KAAKC,MAAM,GAAA;EACpB;AAEA,MAAIC,IAAI;AACR,QAAMC,IAAIH,KAAKI;AACf,MAAIC,IAAI1B;AACR,MAAI2B;AAEJ,SAAOJ,IAAIC,IAAI,GAAG;AAChBG,QAAIN,KAAKE,GAAAA;AACT,QAAII,MAAM,eAAeA,MAAM,iBAAiBA,MAAM,YAAa;AACnE,QAAI,OAAOD,EAAEC,CAAAA,MAAO,YAAYD,EAAEC,CAAAA,MAAO,KAAM;AAC/CD,QAAIA,EAAEC,CAAAA;EACR;AAEAA,MAAIN,KAAKE,CAAAA;AACT,MACEG,KACA,OAAOA,MAAM,YACb,EAAEC,MAAM,eAAeA,MAAM,iBAAiBA,MAAM,cACpD;AACA,WAAOD,EAAEC,CAAAA;EACX;AACF;AA7BgBP;AAkDT,SAASQ,YAAYC,WAA6BC,WAA8B;AACrF,MAAIC,YAAY,CAAC;AACjB,WAASC,QAAQH,UAAUR,KAAI,GAAI;AACjC,UAAM1B,QAAQkC,UAAUI,IAAID,IAAAA;AAC5BE,SAAKH,WAAWC,MAAMrC,KAAAA;AACtB,QAAIA,UAAU,WAAW;AACvByB,cAAQU,WAAWE,IAAAA;IACrB,OAAO;AACLE,WAAKJ,WAAWE,MAAMrC,KAAAA;IACxB;EACF;AACA,SAAOoC;AACT;AAZgBH;;;AH7LhB,IAAMO,UAAUC,EAAEC,OAAO;EACvBC,QAAQF,EAAEG,OAAM;EAChBC,OAAOJ,EAAEK,IAAG;AACd,CAAA;AA4BO,IAAMC,SAAN,MAAMA;EAjDb,OAiDaA;;;;EACXC;EACAC;EACQC;EAER,aAAaC,gBAAgBC,SAAwBC,OAAoB;AACvE,UAAMC,QAAQ,IAAIC,IAAIH,QAAQI,GAAG,EAAEC,aAAaC,IAAI,OAAA,KAAY;AAChEN,YAAQO,QAAQC,IAAI,aAAaN,KAAAA;AACjC,WAAOF;EACT;;;;;;;;;;EAWAS,YAAqBC,MAAkB;SAAlBA,OAAAA;SAnBrBd,UAAU;SACVC,QAAe,CAAA;SACPC,iBAAmC,oBAAIa,IAAAA;EAiBP;;;;;;;;;;;;EAaxC,IAAIC,cAAuB;AACzB,WAAO,CAAC,CAAC,KAAK,SAAA,GAAYC;EAC5B;;;;;;;;;;;;;;;EAiBA,MAAMC,UAAU;AAGd,QAAI,CAAC,KAAKF,aAAa;AACrB,WAAKhB,UAAU,MAAM,KAAKmB,WAAU;IACtC;EACF;;;;;;;;;;;;;;;;;;EAmBA,MAAcA,WAAWC,UAA6B,CAAC,GAAG;AACxD,QAAIC;AACJ,QAAIC,OAAO;AAGX,aAASR,QAAQ,KAAKb,OAAO;AAC3B,YAAMsB,SAASC,cAAcV,KAAKW,MAAM,KAAKX,KAAKY,EAAE;AACpD,UAAIH,QAAQ;AACVF,mBAAW,IAAIP,KAAK,KAAKA,MAAMS,MAAAA;AAC/B;MACF;IACF;AAEA,QAAI,CAACF,UAAU;AACb,YAAM,IAAIM,MAAM,gBAAA;IAClB;AAIA,UAAMC,aAAa,mCAAA;AACjB,YAAMC,OAAO,MAAM,KAAKf,KAAKgB,QAAQpB,IAAI,GAAA;AACzC,YAAMqB,SAAS,MAAM,KAAKjB,KAAKgB,QAAQE,KAAI;AAC3C,YAAMC,YAAiBJ,QAAQ,CAAC;AAChC,eAAS,CAACK,KAAKrC,KAAAA,KAAUkC,QAAQ;AAC/B,YAAIG,OAAO,KAAK;AACd;QACF;AACAC,QAAAA,MAAKF,WAAWC,KAAKrC,KAAAA;MACvB;AACAuC,WAAKf,UAAUY,SAAAA;IACjB,GAXmB;AAanB,UAAML,WAAAA;AAENP,aAASgB,aAAa,CAAC;AAGvB,UAAMC,SAAS,wBAACC,WAAAA;AACd,UAAInB,QAAQoB,cAAc;AACxBC,oBAAYF,QAAQlB,SAASgB,UAAU;MACzC;AACA,UAAIf,QAAQ,KAAKN,aAAa;AAC5BM,eAAO;AACP;MACF;AACA,YAAMoB,SAASD,YAAYF,QAAQlB,SAASgB,UAAU;AACtD,WAAKvB,KAAK6B,UACRC,KAAKC,UAAU;QACbC,MAAM;QACNjD,OAAO6C;MACT,CAAA,CAAA;AAEFH,aAAOQ,MAAK;IACd,GAhBe;AAmBf,UAAMC,YAAY,8BAAOT,WAAAA;AACvB,eAASd,QAAQc,QAAQ;AACvB,cAAMU,YACJxB,QAAQ,MAAMJ,WAAW6B,UAAU7B,UAAUI,IAAAA;AAC/C,cAAM0B,YAAYC,qBAAqBH,SAAAA;AACvC,cAAM,KAAKnC,KAAKgB,QAAQuB,IAAI5B,MAAM0B,SAAAA;MACpC;AACAZ,aAAOQ,MAAK;IACd,GARkB;AAWlBO,cAAUjC,UAAU;MAClBkC,QAAQC,SAASlB,QAAQjB,SAAS,cAAA,KAAmB,GAAA;MACrDoC,WAAWD,SAASR,WAAW3B,SAAS,iBAAA,KAAsB,GAAA;IAChE,CAAA;AAEA,WAAOA;EACT;;;;;;;;;;;;;;;;;EAkBA,MAAcqC,WAAWtC,UAAU,CAAC,GAAG;AACrC,QAAIpB;AACJ,QAAI,KAAKgB,aAAa;AACpBhB,gBAAU,MAAM,KAAKmB,WAAWC,OAAAA;IAClC,OACK;AACHpB,gBAAU,KAAKA;IACjB;AACA,WAAOA;EACT;;;;;;;;;;;;;;;;EAkBQ2D,iBAAiB3D,SAAS;AAChC,UAAM4D,OAAO5D,QAAQa,YAAY,mBAAA;AACjC,UAAMgD,SAASD,MAAMlD,IAAI,OAAA;AACzB,QAAImD,QAAQ;AACV,aAAO7D,QAAQ6D,MAAAA;IACjB;AACA,WAAO;EACT;EAEA,MAAcC,WAAWC,WAAoE;AAC3F,QAAI,CAACA,UAAW,QAAO;AACvB,QAAI;AACF,YAAMC,UAAU,MAAM,KAAKlD,KAAKgB,QAAQpB,IAAI,WAAWqD,SAAAA,EAAW;AAClE,aAAOC;IACT,SAASC,GAAG;AACV,aAAO;IACT;EACF;EAEA,MAAcC,YAAYH,WAAmBI,MAAuC;AAClF,UAAM,KAAKrD,KAAKgB,QAAQuB,IAAI,WAAWU,SAAAA,IAAaI,IAAAA;EACtD;EAEA,MAAcC,cAAcL,WAAmB;AAC7C,UAAM,KAAKjD,KAAKgB,QAAQuC,OAAO,WAAWN,SAAAA,EAAW;EACvD;;;;;;;;;;;;;;;;;EAkBA,MAAMO,UAAUC,MAAwBC,KAA8B;AACpE,UAAMxE,UAAU,MAAM,KAAK0D,WAAW;MACpClB,cAAc;IAChB,CAAA;AAGA,UAAMiC,aAAazE,QAAQa,YAAY,aAAA,KAAkB,CAAA;AACzD,eAAW6D,SAASD,YAAY;AAC9B,YAAME,eAAe,MAAMD,MAAMH,MAAMC,GAAAA;AACvC,UAAI,CAACG,cAAc;AACjBJ,aAAKK,MAAK;AACV;MACF;IACF;AAGA,UAAMC,oBAAoBL,IAAIpE,SAASO,QAAQD,IAAI,WAAA;AACnD,UAAMoE,kBAAkBD,oBAAoB,MAAM,KAAKf,WAAWe,iBAAAA,IAAqB;AAGvF,UAAME,WAAWD,iBAAiBC,YAAYC,kBAAAA;AAC9C,UAAMjB,YAAYe,kBAAkBD,oBAAoBG,kBAAAA;AAExD,QAAIC,OAAO;AACX,UAAMC,SAAS,KAAKvB,iBAAiB3D,OAAAA;AAErC,QAAIkF,QAAQ;AACV,YAAM,EAAEC,UAAS,IAAKD,OAAO9D;AAC7B6D,aAAOG,SAAQD,SAAAA,IAAa,IAAIA,UAAAA,IAAcA,UAAUZ,MAAMC,GAAAA;AAG9D,UAAIM,iBAAiBO,OAAO;AAC1BC,eAAOC,OAAON,MAAMH,gBAAgBO,KAAK;MAC3C;AAEAH,aAAAA,EAASH,QAAAA,IAAYE;AAGrB,UAAI,CAACH,iBAAiB;AACpB,cAAM,KAAKZ,YAAYH,WAAW;UAChCgB;QACF,CAAA;MACF;IACF;AAGA,UAAMS,YAAYxF,QAAQ,QAAA,IAAYiF,MAAMV,MAAMC,GAAAA,CAAAA;AAGlDD,SAAKkB,SAAS;MAAEV;MAAUhB;IAAU,CAAA;AAGpCQ,SAAKmB,KACH9C,KAAKC,UAAU;MACbC,MAAM;MACNjD,OAAO;QACL8F,KAAKZ;QACLhB;QACA,GAAG/D,QAAQqC;MACb;IACF,CAAA,CAAA;EAEJ;;;;;;;;;;;;;;;;;EAmBA,MAAMuD,UAAUC,SAAiBC,QAA0B;AACzD,QAAIC;AACJ,QAAI;AACFA,aAAOnD,KAAKoD,MAAMH,OAAAA;IACpB,SACO5B,GAAG;AACR;IACF;AAEA,UAAMgC,SAASzG,QAAQ0G,UAAUH,IAAAA;AACjC,QAAI,CAACE,OAAOE,SAAS;AACnB;IACF;AACA,UAAMnG,UAAU,MAAM,KAAK0D,WAAU;AAGrC,UAAMe,aAAazE,QAAQa,YAAY,aAAA,KAAkB,CAAA;AACzD,eAAW6D,SAASD,YAAY;AAC9B,YAAME,eAAe,MAAMD,MAAMoB,QAAQG,OAAO9B,KAAKtE,KAAK;AAC1D,UAAI,CAAC8E,cAAc;AACjB;MACF;IACF;AAEA,UAAMyB,UAAUpG,QAAQa,YAAY,iBAAA;AACpC,QAAIuF,SAAS;AACX,YAAMlB,SAAS,KAAKvB,iBAAiB3D,OAAAA;AACrC,YAAM,EAAE+E,SAAQ,IAAKe,OAAOT;AAC5B,YAAMJ,OAAOC,SAAAA,EAAWH,QAAAA;AACxB,YAAMsB,aAAaD,QAAQ1F,IAAIuF,OAAO9B,KAAKxE,MAAM;AACjD,UAAI0G,YAAY;AAGd,cAAMC,SAAStG,QAAQuG,eAAe7F,IAAI2F,WAAWnE,GAAG,KAAK,CAAA;AAC7D,mBAAWwC,SAAS4B,QAAQ;AAC1B,gBAAM3B,eAAe,MAAMD,MAAMoB,QAAQG,OAAO9B,KAAKtE,KAAK;AAC1D,cAAI,CAAC8E,cAAc;AACjB;UACF;QACF;AAGA,YAAI0B,WAAWG,gBAAgB;AAC7B,gBAAMC,aAAaJ,WAAWG,eAAeN,UAC3CD,OAAO9B,KAAKtE,KAAK;AAEnB,cAAI,CAAC4G,WAAWN,SAAS;AACvB;UACF;QACF;AAEA,cAAMX,YACJxF,QAAQqG,WAAWnE,GAAG,EAAE+C,MAAMgB,OAAO9B,KAAKtE,OAAOiG,MAAAA,CAAAA;MAErD;IACF;EACF;;;;;;;;;;;;;;;;EAiBA,MAAMY,QAAQnC,MAAwB;AACpC,UAAMvE,UAAU,MAAM,KAAK0D,WAAU;AACrC,UAAMwB,SAAS,KAAKvB,iBAAiB3D,OAAAA;AACrC,QAAI,CAACuE,KAAKc,OAAO;AACf;IACF;AACA,UAAM,EAAEN,UAAUhB,UAAS,IAAKQ,KAAKc;AACrC,UAAMJ,OAAOC,SAAAA,EAAWH,QAAAA;AAExB,QAAI,CAACE,KAAM;AAGX,QAAIlB,WAAW;AACb,YAAM,KAAKG,YAAYH,WAAW;QAChCgB;QACAM,OAAO;UAAE,GAAGJ;QAAK;MACnB,CAAA;IACF;AAGA,UAAM0B,kBAAkB,KAAKzG,eAAeQ,IAAIqD,SAAAA;AAChD,QAAI4C,iBAAiB;AACnBC,mBAAaD,eAAAA;AACb,WAAKzG,eAAemE,OAAON,SAAAA;IAC7B;AAEA,UAAM8C,UAAU,mCAAA;AAEd,YAAMrB,YAAYxF,QAAQ,SAAA,IAAaiF,MAAMV,IAAAA,CAAAA;AAG7C,UAAIW,QAAQ;AACV,eAAOA,OAAAA,EAASH,QAAAA;MAClB;AAGA,UAAIhB,WAAW;AACb,cAAM,KAAKK,cAAcL,SAAAA;MAC3B;AAGA,WAAKjD,KAAK6B,UACRC,KAAKC,UAAU;QACbC,MAAM;QACNjD,OAAO;UAAEkF;QAAS;MACpB,CAAA,CAAA;AAIF,WAAK7E,eAAemE,OAAON,SAAAA;IAC7B,GAxBgB;AA0BhB,UAAM+C,oBAAoB9G,QAAQa,YAAYiG,qBAAqB;AAEnE,QAAIA,oBAAoB,GAAG;AAEzB,UAAI7B,KAAK8B,QAAQ;AACf9B,aAAK8B,OAAOnG,IAAI,SAAA;MAClB;AAGA,WAAKE,KAAK6B,UACRC,KAAKC,UAAU;QACbC,MAAM;QACNjD,OAAO;UAAEkF;QAAS;MACpB,CAAA,CAAA;AAIF,YAAMiC,UAAUC,WAAWJ,SAASC,iBAAAA;AACpC,WAAK5G,eAAeU,IAAImD,WAAWiD,OAAAA;IACrC,OAAO;AAEL,YAAMH,QAAAA;IACR;EACF;AACF;","names":["Action","name","bodyValidation","target","propertyKey","constructor","_actionMetadata","Map","set","key","Room","options","path","maxUsers","throttleStorage","throttleSync","disconnectTimeout","guards","RoomGuard","Guard","descriptor","isClass","obj","prototype","constructor","isObject","item","Array","isArray","generateShortUUID","chars","uuid","i","randomIndex","Math","floor","random","length","Storage","memory","Map","put","key","value","set","get","delete","list","MockPartySocket","events","Map","id","generateShortUUID","addEventListener","event","cb","set","removeEventListener","delete","_trigger","data","get","MockPartyRoom","clients","storage","constructor","Storage","connection","client","socket","broadcast","forEach","clear","MockConnection","state","setState","value","ServerIo","ClientIo","dset","z","ArraySubject","ObjectSubject","isSignal","syncClass","instance","options","cacheSync","Map","cachePersist","Set","$valuesChanges","set","path","value","onSync","setPersist","add","onPersist","has","get","createSyncClass","createStatesSnapshot","persistObject","$snapshot","key","keys","signal","persist","isObject","Array","isArray","setMetadata","target","meta","constructor","_propertyMetadata","propId","isSignal","currentClass","parentKey","parentClass","$path","syncToClient","newPath","isSignal","load","rootInstance","values","valueIsObject","loadFromObject","loadFromPaths","path","value","Object","entries","parts","split","loadValue","currentPath","key","newPath","Array","isArray","current","i","length","part","isSignal","Reflect","deleteProperty","_subject","set","currentValue","undefined","parentInstance","getByPath","slice","join","classType","options","isClass","setMetadata","root","dset","isPromise","value","Promise","awaitReturn","val","isClass","obj","prototype","constructor","throttle","func","wait","timeout","lastArgs","args","setTimeout","extractParams","pattern","str","regexPattern","replace","regex","RegExp","match","exec","groups","dremove","keys","split","i","l","length","t","k","buildObject","valuesMap","allMemory","memoryObj","path","get","dset","Message","z","object","action","string","value","any","Server","subRoom","rooms","timeoutHandles","onBeforeConnect","request","lobby","token","URL","url","searchParams","get","headers","set","constructor","room","Map","isHibernate","hibernate","onStart","createRoom","options","instance","init","params","extractParams","path","id","Error","loadMemory","root","storage","memory","list","tmpObject","key","dset","load","$memoryAll","syncCb","values","getMemoryAll","buildObject","packet","broadcast","JSON","stringify","type","clear","persistCb","_instance","getByPath","itemValue","createStatesSnapshot","put","syncClass","onSync","throttle","onPersist","getSubRoom","getUsersProperty","meta","propId","getSession","privateId","session","e","saveSession","data","deleteSession","delete","onConnect","conn","ctx","roomGuards","guard","isAuthorized","close","providedPrivateId","existingSession","publicId","generateShortUUID","user","signal","classType","isClass","state","Object","assign","awaitReturn","setState","send","pId","onMessage","message","sender","json","parse","result","safeParse","success","actions","actionName","guards","$actionGuards","bodyValidation","bodyResult","onClose","existingTimeout","clearTimeout","cleanup","disconnectTimeout","status","timeout","setTimeout"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signe/room",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "keywords": [],
@@ -17,7 +17,7 @@
17
17
  "dset": "^3.1.3",
18
18
  "partysocket": "^1.0.1",
19
19
  "zod": "^3.23.8",
20
- "@signe/sync": "1.1.0"
20
+ "@signe/sync": "1.2.0"
21
21
  },
22
22
  "publishConfig": {
23
23
  "access": "public"
package/readme.md CHANGED
@@ -10,104 +10,194 @@ npm install @signe/room @signe/reactive @signe/sync
10
10
 
11
11
  ## Features
12
12
 
13
- - 🔄 Automatic state synchronization
14
- - 👥 Built-in user management
15
- - 🎮 Action-based message handling
16
- - 🔐 Authentication support
17
- - 🎯 TypeScript support
13
+ - 🔄 Automatic state synchronization across clients
14
+ - 👥 Built-in user management with customizable player classes
15
+ - 🎮 Action-based message handling with type safety
16
+ - 🔐 Flexible authentication and authorization system
17
+ - 🛡️ Guard system for room and action-level security
18
+ - 🎯 Full TypeScript support
19
+ - 🔌 WebSocket-based real-time communication
20
+ - 💾 Automatic state persistence
21
+ - 🚀 Optimized for performance with throttling support
18
22
 
19
- ## Usage
23
+ ## Basic Usage
20
24
 
21
- Here's a complete example of how to create a multiplayer room:
25
+ Here's a simple example of a multiplayer game room:
22
26
 
23
27
  ```ts
24
28
  import { signal } from "@signe/reactive";
25
- import { Room, Server, action } from "@signe/room";
29
+ import { Room, Server, Action } from "@signe/room";
26
30
  import { id, sync, users } from "@signe/sync";
27
31
 
28
- // Define a Player class to represent connected users
29
- export class Player {
30
- @id() id = signal("");
32
+ // Define a Player class
33
+ class Player {
34
+ @id() id: string;
31
35
  @sync() x = signal(0);
32
36
  @sync() y = signal(0);
33
- @sync() color = signal("#000000");
37
+ @sync() score = signal(0);
38
+ }
39
+
40
+ // Create your room
41
+ @Room({
42
+ path: "game",
43
+ })
44
+ class GameRoom {
45
+ @users(Player) players = signal({});
46
+ @sync() gameState = signal("waiting");
34
47
 
35
- constructor() {
36
- const randomColor = Math.floor(Math.random() * 16777215).toString(16);
37
- this.color.set("#" + randomColor);
48
+ @Action("move")
49
+ move(player: Player, position: { x: number, y: number }) {
50
+ player.x.set(position.x);
51
+ player.y.set(position.y);
38
52
  }
39
53
  }
40
54
 
41
- // Define your room's state schema
42
- export class RoomSchema {
43
- @sync() count = signal(0);
44
- @users(Player) players = signal({});
55
+ // Create your server
56
+ export default class GameServer extends Server {
57
+ rooms = [GameRoom];
45
58
  }
59
+ ```
60
+
61
+ ## Action
62
+
63
+ An action is a function that is called when a client sends a message to the server.
64
+
65
+ Function have to be decorated with the `@Action` decorator and have 3 parameters:
66
+
67
+ - The first parameter is the player instance
68
+ - The second parameter is the value of the action
69
+ - The third parameter is the Party.Connection instance
46
70
 
47
- // Create your room with custom logic
71
+ ## Advanced Features
72
+
73
+ ### Room Configuration
74
+
75
+ The `@Room` decorator accepts various configuration options:
76
+
77
+ ```ts
48
78
  @Room({
49
- path: "chess-{id}", // Dynamic room path with parameters
50
- maxUsers: 2, // Limit number of users per room
79
+ path: "game-{id}", // Dynamic path with parameters
80
+ maxUsers: 4, // Limit number of users
81
+ throttleStorage: 1000, // Throttle storage updates (ms)
82
+ throttleSync: 100, // Throttle sync updates (ms)
83
+ hibernate: false, // Enable/disable hibernation
84
+ guards: [isAuthenticated], // Room-level guards
51
85
  })
52
- export class MyRoom extends RoomSchema {
53
- // Authentication hook
54
- static onAuth() {
55
- // Add your authentication logic here
56
- }
86
+ ```
57
87
 
58
- constructor(readonly room, readonly params: { id: string }) {
59
- super();
60
- }
88
+ ### Authentication & Authorization
61
89
 
62
- // Room lifecycle hooks
63
- onCreate() {
64
- // Called when the room is created
65
- }
90
+ You can implement authentication and authorization using guards:
91
+
92
+ ```ts
93
+ // Authentication guard
94
+ function isAuthenticated(conn: Connection, ctx: ConnectionContext) {
95
+ const token = ctx.request.headers.get("authorization");
96
+ return validateToken(token); // Returns boolean or Promise<boolean>
97
+ }
66
98
 
67
- onJoin(player: Player) {
68
- console.log(player.id(), "joined");
69
- // Handle player joining
99
+ // Role-based guard
100
+ function isAdmin(conn: Connection, value: any) {
101
+ return conn.state.role === "admin";
102
+ }
103
+
104
+ @Room({
105
+ path: "admin-panel",
106
+ guards: [isAuthenticated], // Applied to all connections and messages
107
+ })
108
+ class AdminRoom {
109
+ @Action("deleteUser")
110
+ @Guard([isAdmin]) // Applied only to this action
111
+ async deleteUser(admin: Player, userId: string) {
112
+ // Only authenticated admins can execute this
70
113
  }
114
+ }
115
+ ```
71
116
 
72
- onLeave() {
73
- // Handle player leaving
117
+ ### Action Validation with Zod
118
+
119
+ You can validate action input data using Zod schemas:
120
+
121
+ ```ts
122
+ import { z } from "zod";
123
+
124
+ class GameRoom {
125
+ @Action("move", z.object({
126
+ x: z.number().min(0).max(1000),
127
+ y: z.number().min(0).max(1000)
128
+ }))
129
+ move(player: Player, position: { x: number, y: number }) {
130
+ player.x.set(position.x);
131
+ player.y.set(position.y);
74
132
  }
75
133
 
76
- // Custom actions
77
- @action("move")
78
- move(player: Player, data: any) {
79
- player.x.set(data.x);
80
- player.y.set(data.y);
134
+ @Action("setName", z.object({
135
+ name: z.string().min(3).max(20)
136
+ }))
137
+ setName(player: Player, data: { name: string }) {
138
+ player.name.set(data.name);
81
139
  }
82
140
  }
141
+ ```
142
+
143
+ Actions with invalid data will be automatically rejected if they don't match the validation schema.
83
144
 
84
- // Create your server with room definitions
85
- export default class MyServer extends Server {
86
- rooms = [MyRoom];
145
+ ### State Management
146
+
147
+ The room system provides several ways to manage state:
148
+
149
+ ```ts
150
+ class GameRoom {
151
+ // Synchronized signals
152
+ @sync() score = signal(0);
153
+ @sync() gameState = signal<"waiting" | "playing" | "ended">("waiting");
154
+
155
+ // User management
156
+ @users(Player) players = signal({});
157
+
158
+ // Complex state
159
+ @sync()
160
+ gameConfig = signal({
161
+ maxPlayers: 4,
162
+ timeLimit: 300,
163
+ mapSize: { width: 1000, height: 1000 }
164
+ });
165
+
166
+ // Methods to update state
167
+ @Action("updateConfig")
168
+ updateConfig(player: Player, config: Partial<GameConfig>) {
169
+ if (player.isHost) {
170
+ this.gameConfig.update(current => ({
171
+ ...current,
172
+ ...config
173
+ }));
174
+ }
175
+ }
87
176
  }
88
177
  ```
89
178
 
90
- ## Decorators
179
+ ### Lifecycle Hooks
91
180
 
92
- - `@Room(options)`: Defines a room with configuration options
93
- - `@sync()`: Marks a property for automatic synchronization
94
- - `@id()`: Marks a property as the unique identifier
95
- - `@users(PlayerClass)`: Creates a synchronized collection of users
96
- - `@action(name)`: Defines a method as a callable action from clients
181
+ Rooms provide several lifecycle hooks:
97
182
 
98
- ## Lifecycle Hooks
183
+ ```ts
184
+ class GameRoom {
185
+ async onCreate()
186
+ async onJoin(player: Player, conn: Connection, ctx: ConnectionContext) {}
187
+ async onLeave(player: Player, conn: Connection) {}
188
+ }
189
+ ```
190
+
191
+ ## Party.Connection
99
192
 
100
- - `onAuth()`: Called during authentication
101
- - `onCreate()`: Called when the room is created
102
- - `onJoin(player)`: Called when a player joins
103
- - `onLeave()`: Called when a player leaves
193
+ Wraps a standard WebSocket, with a few additional PartyKit-specific properties.
104
194
 
105
- ## Best Practices
195
+ ```ts
196
+ connection.send("Good-bye!");
197
+ connection.close();
198
+ ```
106
199
 
107
- 1. Always define proper types for your actions' data parameters
108
- 2. Implement proper authentication in the `onAuth` hook
109
- 3. Clean up resources in the `onLeave` hook
110
- 4. Use TypeScript for better type safety and development experience
200
+ > https://docs.partykit.io/reference/partyserver-api/#partyconnection
111
201
 
112
202
  ## License
113
203
 
package/src/decorators.ts CHANGED
@@ -1,4 +1,9 @@
1
- export function Action(name: string, bodyValidation?) {
1
+ import type * as Party from "./types/party"
2
+ import type { z } from "zod"
3
+ type GuardFn = (sender: Party.Connection, value: any) => boolean | Promise<boolean>;
4
+ type RoomGuardFn = (conn: Party.Connection, ctx: Party.ConnectionContext) => boolean | Promise<boolean>;
5
+
6
+ export function Action(name: string, bodyValidation?: z.ZodSchema) {
2
7
  return function (target: any, propertyKey: string) {
3
8
  if (!target.constructor._actionMetadata) {
4
9
  target.constructor._actionMetadata = new Map();
@@ -16,6 +21,8 @@ export interface RoomOptions {
16
21
  throttleStorage?: number;
17
22
  throttleSync?: number;
18
23
  hibernate?: boolean;
24
+ guards?: RoomGuardFn[];
25
+ disconnectTimeout?: number;
19
26
  }
20
27
 
21
28
  export function Room(options: RoomOptions) {
@@ -24,5 +31,36 @@ export function Room(options: RoomOptions) {
24
31
  target.maxUsers = options.maxUsers;
25
32
  target.throttleStorage = options.throttleStorage;
26
33
  target.throttleSync = options.throttleSync;
34
+ target.disconnectTimeout = options.disconnectTimeout ?? 0;
35
+ if (options.guards) {
36
+ target['_roomGuards'] = options.guards;
37
+ }
27
38
  };
28
39
  }
40
+
41
+ /**
42
+ * Room guard decorator
43
+ * @param guards Array of guard functions to check on connection
44
+ */
45
+ export function RoomGuard(guards: RoomGuardFn[]) {
46
+ return function (target: any) {
47
+ target['_roomGuards'] = guards;
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Action guard decorator
53
+ * @param guards Array of guard functions to check before action execution
54
+ */
55
+ export function Guard(guards: GuardFn[]) {
56
+ return function (
57
+ target: any,
58
+ propertyKey: string,
59
+ descriptor: PropertyDescriptor
60
+ ) {
61
+ if (!target.constructor['_actionGuards']) {
62
+ target.constructor['_actionGuards'] = new Map();
63
+ }
64
+ target.constructor['_actionGuards'].set(propertyKey, guards);
65
+ };
66
+ }
package/src/mock.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { generateShortUUID } from "../../sync/src/utils";
2
+ import { Storage } from "./storage";
2
3
 
3
4
  class MockPartySocket {
4
5
  private events: Map<string, Function> = new Map();
@@ -17,25 +18,9 @@ class MockPartySocket {
17
18
  }
18
19
  }
19
20
 
20
- class MockStorage {
21
- private storage: Map<string, any> = new Map();
22
-
23
- async get(key: string) {
24
- return this.storage.get(key);
25
- }
26
-
27
- async put(key: string, value: any) {
28
- this.storage.set(key, value);
29
- }
30
-
31
- async list() {
32
- return this.storage
33
- }
34
- }
35
-
36
21
  class MockPartyRoom {
37
22
  private clients: Map<string, MockPartySocket> = new Map();
38
- storage = new MockStorage();
23
+ storage = new Storage();
39
24
 
40
25
  constructor(public id?: string) {
41
26
  this.id = id || generateShortUUID()
package/src/server.ts CHANGED
@@ -50,6 +50,13 @@ type CreateRoomOptions = {
50
50
  export class Server implements Party.Server {
51
51
  subRoom = null;
52
52
  rooms: any[] = [];
53
+ private timeoutHandles: Map<string, any> = new Map();
54
+
55
+ static async onBeforeConnect(request: Party.Request, lobby: Party.Lobby) {
56
+ const token = new URL(request.url).searchParams.get("token") ?? "";
57
+ request.headers.set("X-User-ID", token);
58
+ return request;
59
+ }
53
60
 
54
61
  /**
55
62
  * @constructor
@@ -244,6 +251,24 @@ export class Server implements Party.Server {
244
251
  return null;
245
252
  }
246
253
 
254
+ private async getSession(privateId: string): Promise<{publicId: string, state?: any} | null> {
255
+ if (!privateId) return null;
256
+ try {
257
+ const session = await this.room.storage.get(`session:${privateId}`);
258
+ return session as {publicId: string, state?: any} | null;
259
+ } catch (e) {
260
+ return null;
261
+ }
262
+ }
263
+
264
+ private async saveSession(privateId: string, data: {publicId: string, state?: any}) {
265
+ await this.room.storage.put(`session:${privateId}`, data);
266
+ }
267
+
268
+ private async deleteSession(privateId: string) {
269
+ await this.room.storage.delete(`session:${privateId}`);
270
+ }
271
+
247
272
  /**
248
273
  * @method onConnect
249
274
  * @async
@@ -264,25 +289,60 @@ export class Server implements Party.Server {
264
289
  const subRoom = await this.getSubRoom({
265
290
  getMemoryAll: true,
266
291
  })
267
- // Generate a unique public ID for the user
268
- const publicId = generateShortUUID()
292
+
293
+ // Check room guards
294
+ const roomGuards = subRoom.constructor['_roomGuards'] || [];
295
+ for (const guard of roomGuards) {
296
+ const isAuthorized = await guard(conn, ctx);
297
+ if (!isAuthorized) {
298
+ conn.close();
299
+ return;
300
+ }
301
+ }
302
+
303
+ // Check for existing session
304
+ const providedPrivateId = ctx.request?.headers.get("X-User-ID");
305
+ const existingSession = providedPrivateId ? await this.getSession(providedPrivateId) : null;
306
+
307
+ // Generate IDs
308
+ const publicId = existingSession?.publicId || generateShortUUID();
309
+ const privateId = existingSession ? providedPrivateId : generateShortUUID();
310
+
269
311
  let user = null;
270
312
  const signal = this.getUsersProperty(subRoom);
313
+
271
314
  if (signal) {
272
315
  const { classType } = signal.options;
273
- // Create a new user instance based on the defined class type
274
316
  user = isClass(classType) ? new classType() : classType(conn, ctx);
317
+
318
+ // Restore state if exists
319
+ if (existingSession?.state) {
320
+ Object.assign(user, existingSession.state);
321
+ }
322
+
275
323
  signal()[publicId] = user;
324
+
325
+ // Only store new session if it doesn't exist
326
+ if (!existingSession) {
327
+ await this.saveSession(privateId, {
328
+ publicId
329
+ });
330
+ }
276
331
  }
332
+
277
333
  // Call the room's onJoin method if it exists
278
334
  await awaitReturn(subRoom["onJoin"]?.(user, conn, ctx));
279
- conn.setState({ publicId });
280
- // Send initial sync data to the new connection
335
+
336
+ // Store both IDs in connection state
337
+ conn.setState({ publicId, privateId });
338
+
339
+ // Send initial sync data with both IDs to the new connection
281
340
  conn.send(
282
341
  JSON.stringify({
283
342
  type: "sync",
284
343
  value: {
285
344
  pId: publicId,
345
+ privateId,
286
346
  ...subRoom.$memoryAll,
287
347
  },
288
348
  })
@@ -320,6 +380,16 @@ export class Server implements Party.Server {
320
380
  return;
321
381
  }
322
382
  const subRoom = await this.getSubRoom()
383
+
384
+ // Check room guards
385
+ const roomGuards = subRoom.constructor['_roomGuards'] || [];
386
+ for (const guard of roomGuards) {
387
+ const isAuthorized = await guard(sender, result.data.value);
388
+ if (!isAuthorized) {
389
+ return;
390
+ }
391
+ }
392
+
323
393
  const actions = subRoom.constructor["_actionMetadata"];
324
394
  if (actions) {
325
395
  const signal = this.getUsersProperty(subRoom);
@@ -327,6 +397,16 @@ export class Server implements Party.Server {
327
397
  const user = signal?.()[publicId];
328
398
  const actionName = actions.get(result.data.action);
329
399
  if (actionName) {
400
+
401
+ // Check all guards if they exist
402
+ const guards = subRoom.$actionGuards?.get(actionName.key) || [];
403
+ for (const guard of guards) {
404
+ const isAuthorized = await guard(sender, result.data.value);
405
+ if (!isAuthorized) {
406
+ return;
407
+ }
408
+ }
409
+
330
410
  // Validate action body if a validation schema is defined
331
411
  if (actionName.bodyValidation) {
332
412
  const bodyResult = actionName.bodyValidation.safeParse(
@@ -362,13 +442,77 @@ export class Server implements Party.Server {
362
442
  async onClose(conn: Party.Connection) {
363
443
  const subRoom = await this.getSubRoom()
364
444
  const signal = this.getUsersProperty(subRoom);
365
- const { publicId } = conn.state as any;
445
+ if (!conn.state) {
446
+ return;
447
+ }
448
+ const { publicId, privateId } = conn.state as any;
366
449
  const user = signal?.()[publicId];
367
- // Call the room's onLeave method if it exists
368
- await awaitReturn(subRoom["onLeave"]?.(user, conn));
369
- if (signal) {
370
- // Remove the user from the room
371
- delete signal()[publicId];
450
+
451
+ if (!user) return;
452
+
453
+ // Save current state
454
+ if (privateId) {
455
+ await this.saveSession(privateId, {
456
+ publicId,
457
+ state: { ...user }
458
+ });
459
+ }
460
+
461
+ // Clear any existing timeout for this user
462
+ const existingTimeout = this.timeoutHandles.get(privateId);
463
+ if (existingTimeout) {
464
+ clearTimeout(existingTimeout);
465
+ this.timeoutHandles.delete(privateId);
466
+ }
467
+
468
+ const cleanup = async () => {
469
+ // Call onLeave hook
470
+ await awaitReturn(subRoom["onLeave"]?.(user, conn));
471
+
472
+ // Remove user from signal
473
+ if (signal) {
474
+ delete signal()[publicId];
475
+ }
476
+
477
+ // Delete session
478
+ if (privateId) {
479
+ await this.deleteSession(privateId);
480
+ }
481
+
482
+ // Broadcast user disconnection
483
+ this.room.broadcast(
484
+ JSON.stringify({
485
+ type: "user_disconnected",
486
+ value: { publicId }
487
+ })
488
+ );
489
+
490
+ // Clear timeout handle
491
+ this.timeoutHandles.delete(privateId);
492
+ };
493
+
494
+ const disconnectTimeout = subRoom.constructor.disconnectTimeout ?? 0;
495
+
496
+ if (disconnectTimeout > 0) {
497
+ // Set temporary offline status
498
+ if (user.status) {
499
+ user.status.set('offline');
500
+ }
501
+
502
+ // Broadcast temporary disconnection
503
+ this.room.broadcast(
504
+ JSON.stringify({
505
+ type: "user_offline",
506
+ value: { publicId }
507
+ })
508
+ );
509
+
510
+ // Set cleanup timeout
511
+ const timeout = setTimeout(cleanup, disconnectTimeout);
512
+ this.timeoutHandles.set(privateId, timeout);
513
+ } else {
514
+ // Immediate cleanup if no timeout
515
+ await cleanup();
372
516
  }
373
517
  }
374
518
  }
@@ -232,6 +232,26 @@ import type {
232
232
  analytics: AnalyticsEngineDataset;
233
233
  };
234
234
 
235
+ /**
236
+ * Interface defining the lifecycle hooks available in a Room
237
+ */
238
+ export interface RoomHooks<TUser = any> {
239
+ /**
240
+ * Called when a user joins the room
241
+ * @param user The user object that joined
242
+ * @param conn The connection object
243
+ * @param ctx The connection context
244
+ */
245
+ onJoin?(user: TUser, conn: Connection, ctx: ConnectionContext): void | Promise<void>;
246
+
247
+ /**
248
+ * Called when a user leaves the room
249
+ * @param user The user object that left
250
+ * @param conn The connection object
251
+ */
252
+ onLeave?(user: TUser, conn: Connection): void | Promise<void>;
253
+ }
254
+
235
255
  /** @deprecated Use `Party.Room` instead */
236
256
  export type Party = Room;
237
257