@signe/room 1.4.0 → 1.4.1
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 +80 -34
- package/dist/index.js +81 -16
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +43 -0
- package/src/decorators.ts +7 -4
- package/src/index.ts +1 -1
- package/src/mock.ts +40 -8
- package/src/server.ts +5 -1
- package/src/testing.ts +47 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Request as Request$1, WebSocket, DurableObjectState, AnalyticsEngineDataset,
|
|
1
|
+
import { Request as Request$1, DurableObjectStorage, WebSocket, DurableObjectState, AnalyticsEngineDataset, VectorizeIndex, R2Bucket, KVNamespace } from '@cloudflare/workers-types';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
type AssetFetcher = {
|
|
@@ -195,38 +195,6 @@ declare function RoomGuard(guards: RoomGuardFn[]): (target: any) => void;
|
|
|
195
195
|
*/
|
|
196
196
|
declare function Guard(guards: GuardFn[]): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
197
197
|
|
|
198
|
-
declare class Storage {
|
|
199
|
-
private memory;
|
|
200
|
-
put(key: any, value: any): Promise<void>;
|
|
201
|
-
get(key: any): Promise<any>;
|
|
202
|
-
delete(key: any): Promise<void>;
|
|
203
|
-
list(): Promise<Map<any, any>>;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
declare class MockPartySocket {
|
|
207
|
-
private events;
|
|
208
|
-
id: string;
|
|
209
|
-
addEventListener(event: any, cb: any): void;
|
|
210
|
-
removeEventListener(event: any, cb: any): void;
|
|
211
|
-
_trigger(event: any, data: any): void;
|
|
212
|
-
}
|
|
213
|
-
declare class MockPartyRoom {
|
|
214
|
-
id?: string;
|
|
215
|
-
private clients;
|
|
216
|
-
storage: Storage;
|
|
217
|
-
constructor(id?: string);
|
|
218
|
-
connection(client: any): void;
|
|
219
|
-
broadcast(data: any): void;
|
|
220
|
-
getConnections(): Map<string, MockPartySocket>;
|
|
221
|
-
clear(): void;
|
|
222
|
-
}
|
|
223
|
-
declare class MockConnection {
|
|
224
|
-
state: any;
|
|
225
|
-
setState(value: any): void;
|
|
226
|
-
}
|
|
227
|
-
declare const ServerIo: typeof MockPartyRoom;
|
|
228
|
-
declare const ClientIo: typeof MockPartySocket;
|
|
229
|
-
|
|
230
198
|
/**
|
|
231
199
|
* @class Server
|
|
232
200
|
* @implements {Party.Server}
|
|
@@ -275,6 +243,7 @@ declare class Server implements Server$1 {
|
|
|
275
243
|
* ```
|
|
276
244
|
*/
|
|
277
245
|
get isHibernate(): boolean;
|
|
246
|
+
get roomStorage(): Storage$1;
|
|
278
247
|
/**
|
|
279
248
|
* @method onStart
|
|
280
249
|
* @async
|
|
@@ -401,4 +370,81 @@ declare class Server implements Server$1 {
|
|
|
401
370
|
onRequest(req: Request): Promise<Response>;
|
|
402
371
|
}
|
|
403
372
|
|
|
404
|
-
|
|
373
|
+
declare class Storage {
|
|
374
|
+
private memory;
|
|
375
|
+
put(key: any, value: any): Promise<void>;
|
|
376
|
+
get(key: any): Promise<any>;
|
|
377
|
+
delete(key: any): Promise<void>;
|
|
378
|
+
list(): Promise<Map<any, any>>;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
declare class MockPartyClient {
|
|
382
|
+
server: Server;
|
|
383
|
+
private events;
|
|
384
|
+
id: string;
|
|
385
|
+
conn: MockConnection;
|
|
386
|
+
constructor(server: Server);
|
|
387
|
+
addEventListener(event: any, cb: any): void;
|
|
388
|
+
removeEventListener(event: any, cb: any): void;
|
|
389
|
+
_trigger(event: any, data: any): void;
|
|
390
|
+
send(data: any): Promise<void>;
|
|
391
|
+
}
|
|
392
|
+
declare class MockPartyRoom {
|
|
393
|
+
id?: string;
|
|
394
|
+
clients: Map<string, MockPartyClient>;
|
|
395
|
+
storage: Storage;
|
|
396
|
+
env: {};
|
|
397
|
+
constructor(id?: string);
|
|
398
|
+
connection(server: Server): Promise<MockPartyClient>;
|
|
399
|
+
broadcast(data: any): void;
|
|
400
|
+
getConnection(id: string): MockPartyClient;
|
|
401
|
+
getConnections(): Map<string, MockPartyClient>;
|
|
402
|
+
clear(): void;
|
|
403
|
+
}
|
|
404
|
+
declare class MockConnection {
|
|
405
|
+
client: MockPartyClient;
|
|
406
|
+
server: Server;
|
|
407
|
+
id: string;
|
|
408
|
+
constructor(client: MockPartyClient);
|
|
409
|
+
state: any;
|
|
410
|
+
setState(value: any): void;
|
|
411
|
+
send(data: any): void;
|
|
412
|
+
close(): void;
|
|
413
|
+
}
|
|
414
|
+
declare const ServerIo: typeof MockPartyRoom;
|
|
415
|
+
declare const ClientIo: typeof MockPartyClient;
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* @description Test the room with a mock server and client
|
|
419
|
+
* @param Room - The room class to test
|
|
420
|
+
* @param options - The options for the room
|
|
421
|
+
* @param options.hibernate - Whether to hibernate the server. If hybernate, room is null
|
|
422
|
+
* @example
|
|
423
|
+
* ```ts
|
|
424
|
+
* const { createClient, room, server } = await testRoom(GameRoom)
|
|
425
|
+
* const client1 = await createClient()
|
|
426
|
+
* const client2 = await createClient()
|
|
427
|
+
*
|
|
428
|
+
* client1.addEventListener('message', (data) => {
|
|
429
|
+
* console.log(data)
|
|
430
|
+
* })
|
|
431
|
+
* client2.addEventListener('message', (data) => {
|
|
432
|
+
* console.log(data)
|
|
433
|
+
* })
|
|
434
|
+
*
|
|
435
|
+
* await client1.send({
|
|
436
|
+
* action: 'increment'
|
|
437
|
+
* })
|
|
438
|
+
*
|
|
439
|
+
* ```
|
|
440
|
+
* @returns The server, room, and createClient function
|
|
441
|
+
*/
|
|
442
|
+
declare function testRoom(Room: any, options?: {
|
|
443
|
+
hibernate?: boolean;
|
|
444
|
+
}): Promise<{
|
|
445
|
+
server: Server;
|
|
446
|
+
room: any;
|
|
447
|
+
createClient: () => Promise<MockPartyClient>;
|
|
448
|
+
}>;
|
|
449
|
+
|
|
450
|
+
export { Action, ClientIo, Guard, MockConnection, Room, RoomGuard, type RoomOptions, Server, ServerIo, testRoom };
|
package/dist/index.js
CHANGED
|
@@ -17,10 +17,10 @@ __name(Action, "Action");
|
|
|
17
17
|
function Room(options) {
|
|
18
18
|
return function(target) {
|
|
19
19
|
target.path = options.path;
|
|
20
|
-
target.maxUsers = options.maxUsers;
|
|
21
|
-
target.throttleStorage = options.throttleStorage;
|
|
22
|
-
target.throttleSync = options.throttleSync;
|
|
23
|
-
target.sessionExpiryTime = options.sessionExpiryTime ?? 5 * 60 * 1e3;
|
|
20
|
+
target.prototype.maxUsers = options.maxUsers;
|
|
21
|
+
target.prototype.throttleStorage = options.throttleStorage;
|
|
22
|
+
target.prototype.throttleSync = options.throttleSync;
|
|
23
|
+
target.prototype.sessionExpiryTime = options.sessionExpiryTime ?? 5 * 60 * 1e3;
|
|
24
24
|
if (options.guards) {
|
|
25
25
|
target["_roomGuards"] = options.guards;
|
|
26
26
|
}
|
|
@@ -38,6 +38,11 @@ function Guard(guards) {
|
|
|
38
38
|
if (!target.constructor["_actionGuards"]) {
|
|
39
39
|
target.constructor["_actionGuards"] = /* @__PURE__ */ new Map();
|
|
40
40
|
}
|
|
41
|
+
if (!Array.isArray(guards)) {
|
|
42
|
+
guards = [
|
|
43
|
+
guards
|
|
44
|
+
];
|
|
45
|
+
}
|
|
41
46
|
target.constructor["_actionGuards"].set(propertyKey, guards);
|
|
42
47
|
};
|
|
43
48
|
}
|
|
@@ -76,12 +81,20 @@ var Storage = class {
|
|
|
76
81
|
};
|
|
77
82
|
|
|
78
83
|
// src/mock.ts
|
|
79
|
-
var
|
|
84
|
+
var MockPartyClient = class {
|
|
80
85
|
static {
|
|
81
|
-
__name(this, "
|
|
86
|
+
__name(this, "MockPartyClient");
|
|
87
|
+
}
|
|
88
|
+
server;
|
|
89
|
+
events;
|
|
90
|
+
id;
|
|
91
|
+
conn;
|
|
92
|
+
constructor(server) {
|
|
93
|
+
this.server = server;
|
|
94
|
+
this.events = /* @__PURE__ */ new Map();
|
|
95
|
+
this.id = generateShortUUID();
|
|
96
|
+
this.conn = new MockConnection(this);
|
|
82
97
|
}
|
|
83
|
-
events = /* @__PURE__ */ new Map();
|
|
84
|
-
id = generateShortUUID();
|
|
85
98
|
addEventListener(event, cb) {
|
|
86
99
|
this.events.set(event, cb);
|
|
87
100
|
}
|
|
@@ -91,6 +104,9 @@ var MockPartySocket = class {
|
|
|
91
104
|
_trigger(event, data) {
|
|
92
105
|
this.events.get(event)?.(data);
|
|
93
106
|
}
|
|
107
|
+
send(data) {
|
|
108
|
+
return this.server.onMessage(JSON.stringify(data), this.conn);
|
|
109
|
+
}
|
|
94
110
|
};
|
|
95
111
|
var MockPartyRoom = class MockPartyRoom2 {
|
|
96
112
|
static {
|
|
@@ -99,22 +115,30 @@ var MockPartyRoom = class MockPartyRoom2 {
|
|
|
99
115
|
id;
|
|
100
116
|
clients;
|
|
101
117
|
storage;
|
|
118
|
+
env;
|
|
102
119
|
constructor(id) {
|
|
103
120
|
this.id = id;
|
|
104
121
|
this.clients = /* @__PURE__ */ new Map();
|
|
105
122
|
this.storage = new Storage();
|
|
123
|
+
this.env = {};
|
|
106
124
|
this.id = id || generateShortUUID();
|
|
107
125
|
}
|
|
108
|
-
connection(
|
|
109
|
-
const socket = new
|
|
110
|
-
|
|
111
|
-
|
|
126
|
+
async connection(server) {
|
|
127
|
+
const socket = new MockPartyClient(server);
|
|
128
|
+
await server.onConnect(socket.conn, {
|
|
129
|
+
request: {}
|
|
130
|
+
});
|
|
131
|
+
this.clients.set(socket.id, socket);
|
|
132
|
+
return socket;
|
|
112
133
|
}
|
|
113
134
|
broadcast(data) {
|
|
114
135
|
this.clients.forEach((client) => {
|
|
115
136
|
client._trigger("message", data);
|
|
116
137
|
});
|
|
117
138
|
}
|
|
139
|
+
getConnection(id) {
|
|
140
|
+
return this.clients.get(id);
|
|
141
|
+
}
|
|
118
142
|
getConnections() {
|
|
119
143
|
return this.clients;
|
|
120
144
|
}
|
|
@@ -126,13 +150,28 @@ var MockConnection = class {
|
|
|
126
150
|
static {
|
|
127
151
|
__name(this, "MockConnection");
|
|
128
152
|
}
|
|
129
|
-
|
|
153
|
+
client;
|
|
154
|
+
server;
|
|
155
|
+
id;
|
|
156
|
+
constructor(client) {
|
|
157
|
+
this.client = client;
|
|
158
|
+
this.state = {};
|
|
159
|
+
this.server = client.server;
|
|
160
|
+
this.id = client.id;
|
|
161
|
+
}
|
|
162
|
+
state;
|
|
130
163
|
setState(value) {
|
|
131
164
|
this.state = value;
|
|
132
165
|
}
|
|
166
|
+
send(data) {
|
|
167
|
+
this.client._trigger("message", data);
|
|
168
|
+
}
|
|
169
|
+
close() {
|
|
170
|
+
this.server.onClose(this);
|
|
171
|
+
}
|
|
133
172
|
};
|
|
134
173
|
var ServerIo = MockPartyRoom;
|
|
135
|
-
var ClientIo =
|
|
174
|
+
var ClientIo = MockPartyClient;
|
|
136
175
|
|
|
137
176
|
// src/server.ts
|
|
138
177
|
import { dset as dset2 } from "dset";
|
|
@@ -260,6 +299,9 @@ var Server = class {
|
|
|
260
299
|
get isHibernate() {
|
|
261
300
|
return !!this["options"]?.hibernate;
|
|
262
301
|
}
|
|
302
|
+
get roomStorage() {
|
|
303
|
+
return this.room.storage;
|
|
304
|
+
}
|
|
263
305
|
/**
|
|
264
306
|
* @method onStart
|
|
265
307
|
* @async
|
|
@@ -595,7 +637,7 @@ var Server = class {
|
|
|
595
637
|
const user = signal?.()[publicId];
|
|
596
638
|
const actionName = actions.get(result.data.action);
|
|
597
639
|
if (actionName) {
|
|
598
|
-
const guards = subRoom
|
|
640
|
+
const guards = subRoom.constructor["_actionGuards"]?.get(actionName.key) || [];
|
|
599
641
|
for (const guard of guards) {
|
|
600
642
|
const isAuthorized = await guard(sender, result.data.value);
|
|
601
643
|
if (!isAuthorized) {
|
|
@@ -681,6 +723,28 @@ var Server = class {
|
|
|
681
723
|
return res(response, 200);
|
|
682
724
|
}
|
|
683
725
|
};
|
|
726
|
+
|
|
727
|
+
// src/testing.ts
|
|
728
|
+
async function testRoom(Room2, options = {}) {
|
|
729
|
+
const io = new ServerIo(Room2.path);
|
|
730
|
+
Room2.prototype.throttleSync = 0;
|
|
731
|
+
Room2.prototype.throttleStorage = 0;
|
|
732
|
+
Room2.prototype.options = options;
|
|
733
|
+
const server = new Server(io);
|
|
734
|
+
server.rooms = [
|
|
735
|
+
Room2
|
|
736
|
+
];
|
|
737
|
+
await server.onStart();
|
|
738
|
+
return {
|
|
739
|
+
server,
|
|
740
|
+
room: server.subRoom,
|
|
741
|
+
createClient: /* @__PURE__ */ __name(async () => {
|
|
742
|
+
const client = await io.connection(server);
|
|
743
|
+
return client;
|
|
744
|
+
}, "createClient")
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
__name(testRoom, "testRoom");
|
|
684
748
|
export {
|
|
685
749
|
Action,
|
|
686
750
|
ClientIo,
|
|
@@ -689,6 +753,7 @@ export {
|
|
|
689
753
|
Room,
|
|
690
754
|
RoomGuard,
|
|
691
755
|
Server,
|
|
692
|
-
ServerIo
|
|
756
|
+
ServerIo,
|
|
757
|
+
testRoom
|
|
693
758
|
};
|
|
694
759
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/decorators.ts","../../sync/src/utils.ts","../src/storage.ts","../src/mock.ts","../src/server.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 sessionExpiryTime?: 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.sessionExpiryTime = options.sessionExpiryTime ?? 5 * 60 * 1000;\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\nexport class 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 getConnections() {\n return this.clients;\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 DELETE_TOKEN,\n generateShortUUID\n} from \"@signe/sync\";\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 private async garbageCollector(options: { sessionExpiryTime: number }) {\n const subRoom = await this.getSubRoom();\n if (!subRoom) return;\n\n // Get active connections\n const activeConnections = [...this.room.getConnections()];\n const activePrivateIds = new Set(activeConnections.map(conn => conn.id));\n\n try {\n // Get all sessions from storage\n const sessions = await this.room.storage.list();\n const users = this.getUsersProperty(subRoom);\n const usersPropName = this.getUsersPropName(subRoom);\n\n // Store valid publicIds from sessions\n const validPublicIds = new Set<string>();\n const expiredPublicIds = new Set<string>();\n const SESSION_EXPIRY_TIME = options.sessionExpiryTime \n const now = Date.now();\n\n for (const [key, session] of sessions) {\n // Only process session entries\n if (!key.startsWith('session:')) continue;\n\n const privateId = key.replace('session:', '');\n const typedSession = session as {publicId: string, created: number, connected: boolean};\n \n // Check if session should be deleted based on:\n // 1. Connection is not active\n // 2. Session is marked as disconnected\n // 3. Session is older than expiry time\n if (!activePrivateIds.has(privateId) && \n !typedSession.connected && \n (now - typedSession.created) > SESSION_EXPIRY_TIME) {\n // Delete expired session\n await this.deleteSession(privateId);\n expiredPublicIds.add(typedSession.publicId);\n } else if (typedSession && typedSession.publicId) {\n // Keep track of valid publicIds from active or recent sessions\n validPublicIds.add(typedSession.publicId);\n }\n }\n\n // Clean up users only if ALL their sessions are expired\n if (users && usersPropName) {\n const currentUsers = users();\n for (const publicId in currentUsers) {\n // Only delete user if they have an expired session and no valid sessions\n if (expiredPublicIds.has(publicId) && !validPublicIds.has(publicId)) {\n delete currentUsers[publicId];\n await this.room.storage.delete(`${usersPropName}.${publicId}`);\n }\n }\n }\n \n } catch (error) {\n console.error('Error in garbage collector:', error);\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 * \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 let initPersist = 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 return null;\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.startsWith('session:')) {\n continue;\n }\n if (key == \".\") {\n continue;\n }\n dset(tmpObject, key, value);\n }\n load(instance, tmpObject, true);\n };\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: Map<string, any>) => {\n if (initPersist) {\n values.clear();\n return;\n }\n for (let [path, value] of values) {\n const _instance =\n path == \".\" ? instance : getByPath(instance, path);\n const itemValue = createStatesSnapshot(_instance); \n if (value == DELETE_TOKEN) {\n await this.room.storage.delete(path);\n } else {\n await this.room.storage.put(path, itemValue);\n }\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 await loadMemory();\n\n initPersist = false\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 = {}): Promise<any | null> {\n let subRoom // instance of the room or null\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 getUsersPropName(subRoom) {\n const meta = subRoom.constructor[\"_propertyMetadata\"];\n return meta?.get(\"users\")\n }\n\n private async getSession(privateId: string): Promise<{publicId: string, state?: any, created?: number, connected?: boolean} | 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, created: number, connected: boolean} | null;\n } catch (e) {\n return null;\n }\n }\n\n private async saveSession(privateId: string, data: {publicId: string, state?: any, created?: number, connected?: boolean}) {\n const sessionData = {\n ...data,\n created: data.created || Date.now(),\n connected: data.connected !== undefined ? data.connected : true\n };\n await this.room.storage.put(`session:${privateId}`, sessionData);\n }\n\n private async updateSessionConnection(privateId: string, connected: boolean) {\n const session = await this.getSession(privateId);\n if (session) {\n await this.saveSession(privateId, { ...session, connected });\n }\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 if (!subRoom) {\n conn.close();\n return;\n }\n\n const sessionExpiryTime = subRoom.constructor.sessionExpiryTime;\n await this.garbageCollector({ sessionExpiryTime });\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 existingSession = await this.getSession(conn.id) \n\n // Generate IDs\n const publicId = existingSession?.publicId || generateShortUUID();\n\n let user = null;\n const signal = this.getUsersProperty(subRoom);\n const usersPropName = this.getUsersPropName(subRoom);\n\n if (signal) {\n const { classType } = signal.options;\n \n // Restore state if exists\n if (!existingSession?.publicId) {\n user = isClass(classType) ? new classType() : classType(conn, ctx);\n signal()[publicId] = user;\n const snapshot = createStatesSnapshot(user);\n this.room.storage.put(`${usersPropName}.${publicId}`, snapshot);\n }\n \n // Only store new session if it doesn't exist\n if (!existingSession) {\n await this.saveSession(conn.id, {\n publicId\n });\n }\n else {\n await this.updateSessionConnection(conn.id, true);\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 });\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 ...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 // 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\n if (!subRoom) {\n return;\n }\n\n const signal = this.getUsersProperty(subRoom);\n\n if (!conn.state) {\n return;\n }\n\n const privateId = conn.id;\n const { publicId } = conn.state as any;\n const user = signal?.()[publicId];\n\n if (!user) return;\n\n await awaitReturn(subRoom[\"onLeave\"]?.(user, conn));\n\n // Mark session as disconnected instead of deleting it\n await this.updateSessionConnection(privateId, false);\n\n // Broadcast user disconnection\n this.room.broadcast(\n JSON.stringify({\n type: \"user_disconnected\",\n value: { publicId }\n })\n );\n }\n\n async onAlarm() {\n const subRoom = await this.getSubRoom()\n await awaitReturn(subRoom[\"onAlarm\"]?.(subRoom));\n }\n\n async onError(connection: Party.Connection, error: Error) {\n const subRoom = await this.getSubRoom()\n await awaitReturn(subRoom[\"onError\"]?.(connection, error));\n }\n\n async onRequest(req: Party.Request) {\n const subRoom = await this.getSubRoom()\n const res = (body: any, status: number) => {\n return new Response(JSON.stringify(body), { status });\n }\n if (!subRoom) {\n return res({\n error: \"Not found\"\n }, 404);\n }\n\n const response = await awaitReturn(subRoom[\"onRequest\"]?.(req, this.room));\n if (!response) {\n return res({\n error: \"Not found\"\n }, 404);\n }\n if (response instanceof Response) {\n return response;\n }\n return res(response, 200);\n }\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","sessionExpiryTime","guards","RoomGuard","Guard","descriptor","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","getConnections","clear","MockConnection","state","setState","value","ServerIo","ClientIo","dset","z","createStatesSnapshot","getByPath","load","syncClass","DELETE_TOKEN","generateShortUUID","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","garbageCollector","options","getSubRoom","activeConnections","getConnections","activePrivateIds","Set","map","conn","id","sessions","storage","list","users","getUsersProperty","usersPropName","getUsersPropName","validPublicIds","expiredPublicIds","SESSION_EXPIRY_TIME","sessionExpiryTime","now","Date","key","session","startsWith","privateId","replace","typedSession","has","connected","created","deleteSession","add","publicId","currentUsers","delete","error","console","instance","init","initPersist","params","extractParams","path","loadMemory","root","get","memory","tmpObject","dset","load","$memoryAll","syncCb","values","getMemoryAll","buildObject","packet","broadcast","JSON","stringify","type","clear","persistCb","_instance","getByPath","itemValue","createStatesSnapshot","DELETE_TOKEN","put","syncClass","onSync","throttle","onPersist","meta","propId","getSession","e","saveSession","data","sessionData","undefined","updateSessionConnection","onConnect","ctx","close","roomGuards","guard","isAuthorized","existingSession","generateShortUUID","user","signal","classType","isClass","snapshot","awaitReturn","setState","send","pId","onMessage","message","sender","json","parse","result","safeParse","success","actions","state","actionName","guards","$actionGuards","bodyValidation","bodyResult","onClose","onAlarm","onError","connection","onRequest","req","res","body","status","Response","response"]}
|
|
1
|
+
{"version":3,"sources":["../src/decorators.ts","../../sync/src/utils.ts","../src/storage.ts","../src/mock.ts","../src/server.ts","../src/utils.ts","../src/testing.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 sessionExpiryTime?: number;\n}\n\nexport function Room(options: RoomOptions) {\n return function (target: any) {\n target.path = options.path;\n target.prototype.maxUsers = options.maxUsers;\n target.prototype.throttleStorage = options.throttleStorage;\n target.prototype.throttleSync = options.throttleSync;\n target.prototype.sessionExpiryTime = options.sessionExpiryTime ?? 5 * 60 * 1000;\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 if (!Array.isArray(guards)) {\n guards = [guards]\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 { Server } from \"./server\";\nimport { Storage } from \"./storage\";\n\nexport class MockPartyClient {\n private events: Map<string, Function> = new Map();\n id = generateShortUUID()\n conn: MockConnection;\n\n constructor(public server: Server) {\n this.conn = new MockConnection(this)\n }\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 send(data) {\n return this.server.onMessage(JSON.stringify(data), this.conn as any)\n }\n}\n\nclass MockPartyRoom {\n clients: Map<string, MockPartyClient> = new Map();\n storage = new Storage();\n env = {}\n\n constructor(public id?: string) {\n this.id = id || generateShortUUID()\n }\n\n async connection(server: Server) {\n const socket = new MockPartyClient(server);\n await server.onConnect(socket.conn as any, { request: {} } as any);\n this.clients.set(socket.id, socket);\n return socket\n }\n\n broadcast(data: any) {\n this.clients.forEach((client) => {\n client._trigger('message', data);\n });\n }\n\n getConnection(id: string) {\n return this.clients.get(id)\n }\n\n getConnections() {\n return this.clients; \n }\n\n clear() {\n this.clients.clear();\n }\n}\n\nexport class MockConnection {\n server: Server;\n id: string;\n\n constructor(public client: MockPartyClient) {\n this.server = client.server\n this.id = client.id\n }\n\n state: any = {};\n\n setState(value: any) {\n this.state = value;\n }\n\n send(data: any) {\n this.client._trigger('message', data)\n }\n\n close() {\n this.server.onClose(this as any)\n }\n}\n\nexport const ServerIo = MockPartyRoom;\nexport const ClientIo = MockPartyClient;\n","import { dset } from \"dset\";\nimport z from \"zod\";\nimport {\n createStatesSnapshot,\n getByPath,\n load,\n syncClass,\n DELETE_TOKEN,\n generateShortUUID\n} from \"@signe/sync\";\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 get roomStorage(): Party.Storage {\n return this.room.storage\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 private async garbageCollector(options: { sessionExpiryTime: number }) {\n const subRoom = await this.getSubRoom();\n if (!subRoom) return;\n\n // Get active connections\n const activeConnections = [...this.room.getConnections()];\n const activePrivateIds = new Set(activeConnections.map(conn => conn.id));\n\n try {\n // Get all sessions from storage\n const sessions = await this.room.storage.list();\n const users = this.getUsersProperty(subRoom);\n const usersPropName = this.getUsersPropName(subRoom);\n\n // Store valid publicIds from sessions\n const validPublicIds = new Set<string>();\n const expiredPublicIds = new Set<string>();\n const SESSION_EXPIRY_TIME = options.sessionExpiryTime \n const now = Date.now();\n\n for (const [key, session] of sessions) {\n // Only process session entries\n if (!key.startsWith('session:')) continue;\n\n const privateId = key.replace('session:', '');\n const typedSession = session as {publicId: string, created: number, connected: boolean};\n \n // Check if session should be deleted based on:\n // 1. Connection is not active\n // 2. Session is marked as disconnected\n // 3. Session is older than expiry time\n if (!activePrivateIds.has(privateId) && \n !typedSession.connected && \n (now - typedSession.created) > SESSION_EXPIRY_TIME) {\n // Delete expired session\n await this.deleteSession(privateId);\n expiredPublicIds.add(typedSession.publicId);\n } else if (typedSession && typedSession.publicId) {\n // Keep track of valid publicIds from active or recent sessions\n validPublicIds.add(typedSession.publicId);\n }\n }\n\n // Clean up users only if ALL their sessions are expired\n if (users && usersPropName) {\n const currentUsers = users();\n for (const publicId in currentUsers) {\n // Only delete user if they have an expired session and no valid sessions\n if (expiredPublicIds.has(publicId) && !validPublicIds.has(publicId)) {\n delete currentUsers[publicId];\n await this.room.storage.delete(`${usersPropName}.${publicId}`);\n }\n }\n }\n \n } catch (error) {\n console.error('Error in garbage collector:', error);\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 * \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 let initPersist = 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 return null;\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.startsWith('session:')) {\n continue;\n }\n if (key == \".\") {\n continue;\n }\n dset(tmpObject, key, value);\n }\n load(instance, tmpObject, true);\n };\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: Map<string, any>) => {\n if (initPersist) {\n values.clear();\n return;\n }\n for (let [path, value] of values) {\n const _instance =\n path == \".\" ? instance : getByPath(instance, path);\n const itemValue = createStatesSnapshot(_instance); \n if (value == DELETE_TOKEN) {\n await this.room.storage.delete(path);\n } else {\n await this.room.storage.put(path, itemValue);\n }\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 await loadMemory();\n\n initPersist = false\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 = {}): Promise<any | null> {\n let subRoom // instance of the room or null\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 getUsersPropName(subRoom) {\n const meta = subRoom.constructor[\"_propertyMetadata\"];\n return meta?.get(\"users\")\n }\n\n private async getSession(privateId: string): Promise<{publicId: string, state?: any, created?: number, connected?: boolean} | 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, created: number, connected: boolean} | null;\n } catch (e) {\n return null;\n }\n }\n\n private async saveSession(privateId: string, data: {publicId: string, state?: any, created?: number, connected?: boolean}) {\n const sessionData = {\n ...data,\n created: data.created || Date.now(),\n connected: data.connected !== undefined ? data.connected : true\n };\n await this.room.storage.put(`session:${privateId}`, sessionData);\n }\n\n private async updateSessionConnection(privateId: string, connected: boolean) {\n const session = await this.getSession(privateId);\n if (session) {\n await this.saveSession(privateId, { ...session, connected });\n }\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 if (!subRoom) {\n conn.close();\n return;\n }\n\n const sessionExpiryTime = subRoom.constructor.sessionExpiryTime;\n await this.garbageCollector({ sessionExpiryTime });\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 existingSession = await this.getSession(conn.id) \n\n // Generate IDs\n const publicId = existingSession?.publicId || generateShortUUID();\n\n let user = null;\n const signal = this.getUsersProperty(subRoom);\n const usersPropName = this.getUsersPropName(subRoom);\n\n if (signal) {\n const { classType } = signal.options;\n \n // Restore state if exists\n if (!existingSession?.publicId) {\n user = isClass(classType) ? new classType() : classType(conn, ctx);\n signal()[publicId] = user;\n const snapshot = createStatesSnapshot(user);\n this.room.storage.put(`${usersPropName}.${publicId}`, snapshot);\n }\n \n // Only store new session if it doesn't exist\n if (!existingSession) {\n await this.saveSession(conn.id, {\n publicId\n });\n }\n else {\n await this.updateSessionConnection(conn.id, true);\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 });\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 ...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 // 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.constructor['_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\n if (!subRoom) {\n return;\n }\n\n const signal = this.getUsersProperty(subRoom);\n\n if (!conn.state) {\n return;\n }\n\n const privateId = conn.id;\n const { publicId } = conn.state as any;\n const user = signal?.()[publicId];\n\n if (!user) return;\n\n await awaitReturn(subRoom[\"onLeave\"]?.(user, conn));\n\n // Mark session as disconnected instead of deleting it\n await this.updateSessionConnection(privateId, false);\n\n // Broadcast user disconnection\n this.room.broadcast(\n JSON.stringify({\n type: \"user_disconnected\",\n value: { publicId }\n })\n );\n }\n\n async onAlarm() {\n const subRoom = await this.getSubRoom()\n await awaitReturn(subRoom[\"onAlarm\"]?.(subRoom));\n }\n\n async onError(connection: Party.Connection, error: Error) {\n const subRoom = await this.getSubRoom()\n await awaitReturn(subRoom[\"onError\"]?.(connection, error));\n }\n\n async onRequest(req: Party.Request) {\n const subRoom = await this.getSubRoom()\n const res = (body: any, status: number) => {\n return new Response(JSON.stringify(body), { status });\n }\n if (!subRoom) {\n return res({\n error: \"Not found\"\n }, 404);\n }\n\n const response = await awaitReturn(subRoom[\"onRequest\"]?.(req, this.room));\n if (!response) {\n return res({\n error: \"Not found\"\n }, 404);\n }\n if (response instanceof Response) {\n return response;\n }\n return res(response, 200);\n }\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}","import { ServerIo } from \"./mock\"\nimport { Server } from \"./server\"\n\n/**\n * @description Test the room with a mock server and client\n * @param Room - The room class to test\n * @param options - The options for the room\n * @param options.hibernate - Whether to hibernate the server. If hybernate, room is null\n * @example\n * ```ts\n * const { createClient, room, server } = await testRoom(GameRoom)\n * const client1 = await createClient()\n * const client2 = await createClient()\n * \n * client1.addEventListener('message', (data) => {\n * console.log(data)\n * })\n * client2.addEventListener('message', (data) => {\n * console.log(data)\n * })\n * \n * await client1.send({\n * action: 'increment'\n * })\n * \n * ```\n * @returns The server, room, and createClient function\n */\nexport async function testRoom(Room, options: {\n hibernate?: boolean\n} = {}) {\n const io = new ServerIo(Room.path)\n Room.prototype.throttleSync = 0\n Room.prototype.throttleStorage = 0\n Room.prototype.options = options\n const server = new Server(io as any)\n server.rooms = [Room]\n await server.onStart()\n return {\n server,\n room: server.subRoom,\n createClient: async () => {\n const client = await io.connection(server)\n return client\n }\n }\n}\n"],"mappings":"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","names":["Action","name","bodyValidation","target","propertyKey","constructor","_actionMetadata","Map","set","key","Room","options","path","prototype","maxUsers","throttleStorage","throttleSync","sessionExpiryTime","guards","RoomGuard","Guard","descriptor","Array","isArray","generateShortUUID","chars","uuid","i","randomIndex","Math","floor","random","length","Storage","memory","Map","put","key","value","set","get","delete","list","MockPartyClient","events","id","conn","constructor","server","Map","generateShortUUID","MockConnection","addEventListener","event","cb","set","removeEventListener","delete","_trigger","data","get","send","onMessage","JSON","stringify","MockPartyRoom","clients","storage","env","Storage","connection","socket","onConnect","request","broadcast","forEach","client","getConnection","getConnections","clear","state","setState","value","close","onClose","ServerIo","ClientIo","dset","z","createStatesSnapshot","getByPath","load","syncClass","DELETE_TOKEN","generateShortUUID","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","roomStorage","storage","onStart","createRoom","garbageCollector","options","getSubRoom","activeConnections","getConnections","activePrivateIds","Set","map","conn","id","sessions","list","users","getUsersProperty","usersPropName","getUsersPropName","validPublicIds","expiredPublicIds","SESSION_EXPIRY_TIME","sessionExpiryTime","now","Date","key","session","startsWith","privateId","replace","typedSession","has","connected","created","deleteSession","add","publicId","currentUsers","delete","error","console","instance","init","initPersist","params","extractParams","path","loadMemory","root","get","memory","tmpObject","dset","load","$memoryAll","syncCb","values","getMemoryAll","buildObject","packet","broadcast","JSON","stringify","type","clear","persistCb","_instance","getByPath","itemValue","createStatesSnapshot","DELETE_TOKEN","put","syncClass","onSync","throttle","onPersist","meta","propId","getSession","e","saveSession","data","sessionData","undefined","updateSessionConnection","onConnect","ctx","close","roomGuards","guard","isAuthorized","existingSession","generateShortUUID","user","signal","classType","isClass","snapshot","awaitReturn","setState","send","pId","onMessage","message","sender","json","parse","result","safeParse","success","actions","state","actionName","guards","bodyValidation","bodyResult","onClose","onAlarm","onError","connection","onRequest","req","res","body","status","Response","response","testRoom","Room","options","io","ServerIo","path","prototype","throttleSync","throttleStorage","server","Server","rooms","onStart","room","subRoom","createClient","client","connection"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signe/room",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
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.4.
|
|
20
|
+
"@signe/sync": "1.4.1"
|
|
21
21
|
},
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
package/readme.md
CHANGED
|
@@ -199,6 +199,49 @@ connection.close();
|
|
|
199
199
|
|
|
200
200
|
> https://docs.partykit.io/reference/partyserver-api/#partyconnection
|
|
201
201
|
|
|
202
|
+
## Testing
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { test, vi } from "vitest"
|
|
206
|
+
import { testRoom, Room, Action, sync } from "@signe/room"
|
|
207
|
+
import { signal } from "@signe/reactive"
|
|
208
|
+
|
|
209
|
+
test('test', async () => {
|
|
210
|
+
|
|
211
|
+
@Room({
|
|
212
|
+
path: "game"
|
|
213
|
+
})
|
|
214
|
+
class GameRoom {
|
|
215
|
+
@sync() count = signal(0);
|
|
216
|
+
|
|
217
|
+
@Action('increment')
|
|
218
|
+
increment() {
|
|
219
|
+
this.count.update(c => c + 1)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const { createClient, room, server } = await testRoom(GameRoom)
|
|
224
|
+
const client1 = await createClient()
|
|
225
|
+
const client2 = await createClient()
|
|
226
|
+
|
|
227
|
+
const countFn = vi.fn()
|
|
228
|
+
|
|
229
|
+
client1.addEventListener('message', countFn)
|
|
230
|
+
client2.addEventListener('message',countFn)
|
|
231
|
+
|
|
232
|
+
await client1.send({
|
|
233
|
+
action: 'increment'
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
expect(countFn).toHaveBeenCalledTimes(2)
|
|
237
|
+
expect(countFn).toHaveBeenCalledWith('{"type":"sync","value":{"count":1}}')
|
|
238
|
+
expect(room.count()).toBe(1)
|
|
239
|
+
expect(server.roomStorage.get('.')).toEqual({
|
|
240
|
+
count: 1
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
202
245
|
## License
|
|
203
246
|
|
|
204
247
|
MIT
|
package/src/decorators.ts
CHANGED
|
@@ -28,10 +28,10 @@ export interface RoomOptions {
|
|
|
28
28
|
export function Room(options: RoomOptions) {
|
|
29
29
|
return function (target: any) {
|
|
30
30
|
target.path = options.path;
|
|
31
|
-
target.maxUsers = options.maxUsers;
|
|
32
|
-
target.throttleStorage = options.throttleStorage;
|
|
33
|
-
target.throttleSync = options.throttleSync;
|
|
34
|
-
target.sessionExpiryTime = options.sessionExpiryTime ?? 5 * 60 * 1000;
|
|
31
|
+
target.prototype.maxUsers = options.maxUsers;
|
|
32
|
+
target.prototype.throttleStorage = options.throttleStorage;
|
|
33
|
+
target.prototype.throttleSync = options.throttleSync;
|
|
34
|
+
target.prototype.sessionExpiryTime = options.sessionExpiryTime ?? 5 * 60 * 1000;
|
|
35
35
|
if (options.guards) {
|
|
36
36
|
target['_roomGuards'] = options.guards;
|
|
37
37
|
}
|
|
@@ -61,6 +61,9 @@ export function Guard(guards: GuardFn[]) {
|
|
|
61
61
|
if (!target.constructor['_actionGuards']) {
|
|
62
62
|
target.constructor['_actionGuards'] = new Map();
|
|
63
63
|
}
|
|
64
|
+
if (!Array.isArray(guards)) {
|
|
65
|
+
guards = [guards]
|
|
66
|
+
}
|
|
64
67
|
target.constructor['_actionGuards'].set(propertyKey, guards);
|
|
65
68
|
};
|
|
66
69
|
}
|
package/src/index.ts
CHANGED
package/src/mock.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { generateShortUUID } from "../../sync/src/utils";
|
|
2
|
+
import { Server } from "./server";
|
|
2
3
|
import { Storage } from "./storage";
|
|
3
4
|
|
|
4
|
-
export class
|
|
5
|
+
export class MockPartyClient {
|
|
5
6
|
private events: Map<string, Function> = new Map();
|
|
6
7
|
id = generateShortUUID()
|
|
8
|
+
conn: MockConnection;
|
|
9
|
+
|
|
10
|
+
constructor(public server: Server) {
|
|
11
|
+
this.conn = new MockConnection(this)
|
|
12
|
+
}
|
|
7
13
|
|
|
8
14
|
addEventListener(event, cb) {
|
|
9
15
|
this.events.set(event, cb);
|
|
@@ -16,20 +22,26 @@ export class MockPartySocket {
|
|
|
16
22
|
_trigger(event, data) {
|
|
17
23
|
this.events.get(event)?.(data);
|
|
18
24
|
}
|
|
25
|
+
|
|
26
|
+
send(data) {
|
|
27
|
+
return this.server.onMessage(JSON.stringify(data), this.conn as any)
|
|
28
|
+
}
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
class MockPartyRoom {
|
|
22
|
-
|
|
32
|
+
clients: Map<string, MockPartyClient> = new Map();
|
|
23
33
|
storage = new Storage();
|
|
34
|
+
env = {}
|
|
24
35
|
|
|
25
36
|
constructor(public id?: string) {
|
|
26
37
|
this.id = id || generateShortUUID()
|
|
27
38
|
}
|
|
28
39
|
|
|
29
|
-
connection(
|
|
30
|
-
const socket = new
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
async connection(server: Server) {
|
|
41
|
+
const socket = new MockPartyClient(server);
|
|
42
|
+
await server.onConnect(socket.conn as any, { request: {} } as any);
|
|
43
|
+
this.clients.set(socket.id, socket);
|
|
44
|
+
return socket
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
broadcast(data: any) {
|
|
@@ -38,8 +50,12 @@ class MockPartyRoom {
|
|
|
38
50
|
});
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
getConnection(id: string) {
|
|
54
|
+
return this.clients.get(id)
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
getConnections() {
|
|
42
|
-
return this.clients;
|
|
58
|
+
return this.clients;
|
|
43
59
|
}
|
|
44
60
|
|
|
45
61
|
clear() {
|
|
@@ -48,12 +64,28 @@ class MockPartyRoom {
|
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
export class MockConnection {
|
|
67
|
+
server: Server;
|
|
68
|
+
id: string;
|
|
69
|
+
|
|
70
|
+
constructor(public client: MockPartyClient) {
|
|
71
|
+
this.server = client.server
|
|
72
|
+
this.id = client.id
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
state: any = {};
|
|
52
76
|
|
|
53
77
|
setState(value: any) {
|
|
54
78
|
this.state = value;
|
|
55
79
|
}
|
|
80
|
+
|
|
81
|
+
send(data: any) {
|
|
82
|
+
this.client._trigger('message', data)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
close() {
|
|
86
|
+
this.server.onClose(this as any)
|
|
87
|
+
}
|
|
56
88
|
}
|
|
57
89
|
|
|
58
90
|
export const ServerIo = MockPartyRoom;
|
|
59
|
-
export const ClientIo =
|
|
91
|
+
export const ClientIo = MockPartyClient;
|
package/src/server.ts
CHANGED
|
@@ -78,6 +78,10 @@ export class Server implements Party.Server {
|
|
|
78
78
|
return !!this["options"]?.hibernate;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
get roomStorage(): Party.Storage {
|
|
82
|
+
return this.room.storage
|
|
83
|
+
}
|
|
84
|
+
|
|
81
85
|
/**
|
|
82
86
|
* @method onStart
|
|
83
87
|
* @async
|
|
@@ -491,7 +495,7 @@ export class Server implements Party.Server {
|
|
|
491
495
|
if (actionName) {
|
|
492
496
|
|
|
493
497
|
// Check all guards if they exist
|
|
494
|
-
const guards = subRoom
|
|
498
|
+
const guards = subRoom.constructor['_actionGuards']?.get(actionName.key) || [];
|
|
495
499
|
for (const guard of guards) {
|
|
496
500
|
const isAuthorized = await guard(sender, result.data.value);
|
|
497
501
|
if (!isAuthorized) {
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ServerIo } from "./mock"
|
|
2
|
+
import { Server } from "./server"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @description Test the room with a mock server and client
|
|
6
|
+
* @param Room - The room class to test
|
|
7
|
+
* @param options - The options for the room
|
|
8
|
+
* @param options.hibernate - Whether to hibernate the server. If hybernate, room is null
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const { createClient, room, server } = await testRoom(GameRoom)
|
|
12
|
+
* const client1 = await createClient()
|
|
13
|
+
* const client2 = await createClient()
|
|
14
|
+
*
|
|
15
|
+
* client1.addEventListener('message', (data) => {
|
|
16
|
+
* console.log(data)
|
|
17
|
+
* })
|
|
18
|
+
* client2.addEventListener('message', (data) => {
|
|
19
|
+
* console.log(data)
|
|
20
|
+
* })
|
|
21
|
+
*
|
|
22
|
+
* await client1.send({
|
|
23
|
+
* action: 'increment'
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* ```
|
|
27
|
+
* @returns The server, room, and createClient function
|
|
28
|
+
*/
|
|
29
|
+
export async function testRoom(Room, options: {
|
|
30
|
+
hibernate?: boolean
|
|
31
|
+
} = {}) {
|
|
32
|
+
const io = new ServerIo(Room.path)
|
|
33
|
+
Room.prototype.throttleSync = 0
|
|
34
|
+
Room.prototype.throttleStorage = 0
|
|
35
|
+
Room.prototype.options = options
|
|
36
|
+
const server = new Server(io as any)
|
|
37
|
+
server.rooms = [Room]
|
|
38
|
+
await server.onStart()
|
|
39
|
+
return {
|
|
40
|
+
server,
|
|
41
|
+
room: server.subRoom,
|
|
42
|
+
createClient: async () => {
|
|
43
|
+
const client = await io.connection(server)
|
|
44
|
+
return client
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|