@colyseus/core 0.17.49 → 0.17.50

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/nanoevents.ts"],
4
- "sourcesContent": ["export const createNanoEvents = () => ({\n emit(event: string, ...args: any[]) {\n for (\n let callbacks = this.events[event] || [],\n i = 0,\n length = callbacks.length;\n i < length;\n i++\n ) {\n callbacks[i](...args)\n }\n },\n events: {},\n on(event: string, cb: (...args: any[]) => void) {\n ;(this.events[event] ||= []).push(cb)\n return () => {\n this.events[event] = this.events[event]?.filter(i => cb !== i)\n }\n }\n })"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,IAAM,mBAAmB,OAAO;AAAA,EACnC,KAAK,UAAkB,MAAa;AAClC,aACM,YAAY,KAAK,OAAO,KAAK,KAAK,CAAC,GACrC,IAAI,GACJ,SAAS,UAAU,QACrB,IAAI,QACJ,KACA;AACA,gBAAU,CAAC,EAAE,GAAG,IAAI;AAAA,IACtB;AAAA,EACF;AAAA,EACA,QAAQ,CAAC;AAAA,EACT,GAAG,OAAe,IAA8B;AAC9C;AAAC,KAAC,KAAK,OAAO,KAAK,MAAM,CAAC,GAAG,KAAK,EAAE;AACpC,WAAO,MAAM;AACX,WAAK,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,GAAG,OAAO,OAAK,OAAO,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["export const createNanoEvents = () => ({\n emit(event: string, ...args: any[]) {\n for (\n let callbacks = this.events[event] || [],\n i = 0,\n length = callbacks.length;\n i < length;\n i++\n ) {\n callbacks[i](...args)\n }\n },\n // null-prototype: event names are client-supplied (message types), and on a\n // plain object \"__proto__\" / \"constructor\" / \"toString\" resolve to inherited\n // members instead of missing (colyseus/colyseus#951)\n events: Object.create(null) as { [event: string]: Array<(...args: any[]) => void> },\n on(event: string, cb: (...args: any[]) => void) {\n ;(this.events[event] ||= []).push(cb)\n return () => {\n this.events[event] = this.events[event]?.filter(i => cb !== i)\n }\n }\n })"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,IAAM,mBAAmB,OAAO;AAAA,EACnC,KAAK,UAAkB,MAAa;AAClC,aACM,YAAY,KAAK,OAAO,KAAK,KAAK,CAAC,GACrC,IAAI,GACJ,SAAS,UAAU,QACrB,IAAI,QACJ,KACA;AACA,gBAAU,CAAC,EAAE,GAAG,IAAI;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAIA,QAAQ,uBAAO,OAAO,IAAI;AAAA,EAC1B,GAAG,OAAe,IAA8B;AAC9C;AAAC,KAAC,KAAK,OAAO,KAAK,MAAM,CAAC,GAAG,KAAK,EAAE;AACpC,WAAO,MAAM;AACX,WAAK,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,GAAG,OAAO,OAAK,OAAO,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,7 @@
1
1
  export declare const createNanoEvents: () => {
2
2
  emit(event: string, ...args: any[]): void;
3
- events: {};
3
+ events: {
4
+ [event: string]: Array<(...args: any[]) => void>;
5
+ };
4
6
  on(event: string, cb: (...args: any[]) => void): () => void;
5
7
  };
@@ -5,7 +5,10 @@ var createNanoEvents = () => ({
5
5
  callbacks[i](...args);
6
6
  }
7
7
  },
8
- events: {},
8
+ // null-prototype: event names are client-supplied (message types), and on a
9
+ // plain object "__proto__" / "constructor" / "toString" resolve to inherited
10
+ // members instead of missing (colyseus/colyseus#951)
11
+ events: /* @__PURE__ */ Object.create(null),
9
12
  on(event, cb) {
10
13
  ;
11
14
  (this.events[event] ||= []).push(cb);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/nanoevents.ts"],
4
- "sourcesContent": ["export const createNanoEvents = () => ({\n emit(event: string, ...args: any[]) {\n for (\n let callbacks = this.events[event] || [],\n i = 0,\n length = callbacks.length;\n i < length;\n i++\n ) {\n callbacks[i](...args)\n }\n },\n events: {},\n on(event: string, cb: (...args: any[]) => void) {\n ;(this.events[event] ||= []).push(cb)\n return () => {\n this.events[event] = this.events[event]?.filter(i => cb !== i)\n }\n }\n })"],
5
- "mappings": ";AAAO,IAAM,mBAAmB,OAAO;AAAA,EACnC,KAAK,UAAkB,MAAa;AAClC,aACM,YAAY,KAAK,OAAO,KAAK,KAAK,CAAC,GACrC,IAAI,GACJ,SAAS,UAAU,QACrB,IAAI,QACJ,KACA;AACA,gBAAU,CAAC,EAAE,GAAG,IAAI;AAAA,IACtB;AAAA,EACF;AAAA,EACA,QAAQ,CAAC;AAAA,EACT,GAAG,OAAe,IAA8B;AAC9C;AAAC,KAAC,KAAK,OAAO,KAAK,MAAM,CAAC,GAAG,KAAK,EAAE;AACpC,WAAO,MAAM;AACX,WAAK,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,GAAG,OAAO,OAAK,OAAO,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["export const createNanoEvents = () => ({\n emit(event: string, ...args: any[]) {\n for (\n let callbacks = this.events[event] || [],\n i = 0,\n length = callbacks.length;\n i < length;\n i++\n ) {\n callbacks[i](...args)\n }\n },\n // null-prototype: event names are client-supplied (message types), and on a\n // plain object \"__proto__\" / \"constructor\" / \"toString\" resolve to inherited\n // members instead of missing (colyseus/colyseus#951)\n events: Object.create(null) as { [event: string]: Array<(...args: any[]) => void> },\n on(event: string, cb: (...args: any[]) => void) {\n ;(this.events[event] ||= []).push(cb)\n return () => {\n this.events[event] = this.events[event]?.filter(i => cb !== i)\n }\n }\n })"],
5
+ "mappings": ";AAAO,IAAM,mBAAmB,OAAO;AAAA,EACnC,KAAK,UAAkB,MAAa;AAClC,aACM,YAAY,KAAK,OAAO,KAAK,KAAK,CAAC,GACrC,IAAI,GACJ,SAAS,UAAU,QACrB,IAAI,QACJ,KACA;AACA,gBAAU,CAAC,EAAE,GAAG,IAAI;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAIA,QAAQ,uBAAO,OAAO,IAAI;AAAA,EAC1B,GAAG,OAAe,IAA8B;AAC9C;AAAC,KAAC,KAAK,OAAO,KAAK,MAAM,CAAC,GAAG,KAAK,EAAE;AACpC,WAAO,MAAM;AACX,WAAK,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,GAAG,OAAO,OAAK,OAAO,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/core",
3
- "version": "0.17.49",
3
+ "version": "0.17.50",
4
4
  "description": "Multiplayer Framework for Node.js.",
5
5
  "type": "module",
6
6
  "input": "./src/index.ts",
@@ -52,15 +52,16 @@
52
52
  "debug": "^4.3.4",
53
53
  "nanoid": "^3.3.11",
54
54
  "@colyseus/shared-types": "^0.17.6",
55
- "@colyseus/greeting-banner": "^3.0.8",
56
- "@colyseus/better-call": "^1.3.1"
55
+ "@colyseus/better-call": "^1.3.1",
56
+ "@colyseus/greeting-banner": "^3.0.8"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@colyseus/schema": "^4.0.7",
60
60
  "express": "^5.0.0",
61
+ "vitest": "^3.1.1",
62
+ "@colyseus/redis-presence": "^0.17.8",
61
63
  "@colyseus/redis-driver": "^0.17.7",
62
- "@colyseus/tools": "^0.17.20",
63
- "@colyseus/redis-presence": "^0.17.8"
64
+ "@colyseus/tools": "^0.17.20"
64
65
  },
65
66
  "peerDependencies": {
66
67
  "@colyseus/schema": "^4.0.7",
@@ -94,6 +95,6 @@
94
95
  },
95
96
  "gitHead": "c45b410e99eadffff4b74e701339992e2faa15f8",
96
97
  "scripts": {
97
- "test": "tsc -p test"
98
+ "test": "vitest run"
98
99
  }
99
100
  }
package/src/MatchMaker.ts CHANGED
@@ -2,7 +2,7 @@ import { EventEmitter } from 'events';
2
2
 
3
3
  import { requestFromIPC, subscribeIPC, subscribeWithTimeout } from './IPC.ts';
4
4
 
5
- import { type Type, Deferred, generateId, merge, retry, MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME, REMOTE_ROOM_SHORT_TIMEOUT, type MethodName, type ExtractMethodOrPropertyType } from './utils/Utils.ts';
5
+ import { type Type, Deferred, generateId, merge, retry, MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME, REMOTE_ROOM_SHORT_TIMEOUT, type MethodName, type RemoteRoomCallReturn } from './utils/Utils.ts';
6
6
  import { isDevMode, cacheRoomHistory, getPreviousProcessId, getRoomRestoreListKey, reloadFromCache } from './utils/DevMode.ts';
7
7
 
8
8
  import { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';
@@ -32,7 +32,8 @@ export { controller, stats, type MatchMakerDriver };
32
32
  export type ClientOptions = any;
33
33
  export type SelectProcessIdCallback = (roomName: string, clientOptions: ClientOptions) => Promise<string>;
34
34
 
35
- const handlers: {[id: string]: RegisteredHandler} = {};
35
+ // null-prototype: keyed by client-supplied room name (colyseus/colyseus#951)
36
+ const handlers: {[id: string]: RegisteredHandler} = Object.create(null);
36
37
  const rooms: {[roomId: string]: Room} = {};
37
38
  const events = new EventEmitter();
38
39
 
@@ -326,7 +327,7 @@ export async function query<T extends Room = any>(
326
327
  *
327
328
  * @param roomName - The Id of the specific room.
328
329
  * @param filterOptions - Filter options.
329
- * @param sortOptions - Sorting options.
330
+ * @param additionalSortOptions - Sorting options, merged over the ones declared on the room handler.
330
331
  *
331
332
  * @returns Promise<IRoomCache> - A promise contaning an object which includes room metadata and configurations.
332
333
  */
@@ -353,23 +354,43 @@ export async function findOneRoomAvailable(
353
354
  /**
354
355
  * Call a method or return a property on a remote room.
355
356
  *
357
+ * Provide both type arguments for a precisely typed result, e.g.
358
+ * `remoteRoomCall<MyRoom, 'myMethod'>(roomId, 'myMethod')`. With only the
359
+ * room type given, TypeScript cannot infer the method literal
360
+ * (microsoft/TypeScript#26242) and the result is typed `any`.
361
+ *
356
362
  * @param roomId - The Id of the specific room instance.
357
363
  * @param method - Method or attribute to call or retrive.
358
364
  * @param args - Array of arguments for the method
359
365
  *
360
- * @returns Promise<any> - Returned value from the called or retrieved method/attribute.
366
+ * @returns Promise - Returned value from the called or retrieved method/attribute.
361
367
  */
362
- export async function remoteRoomCall<TRoom = Room>(
368
+ export function remoteRoomCall<TRoom = Room, TMethod extends keyof TRoom = keyof TRoom>(
369
+ roomId: string,
370
+ method: TMethod,
371
+ args?: any[],
372
+ rejectionTimeout?: number,
373
+ ): Promise<RemoteRoomCallReturn<TRoom, TMethod>>;
374
+
375
+ // fallback for dynamic method names not present on the room type
376
+ export function remoteRoomCall<TRoom = Room>(
363
377
  roomId: string,
364
378
  method: keyof TRoom,
365
379
  args?: any[],
380
+ rejectionTimeout?: number,
381
+ ): Promise<any>;
382
+
383
+ export async function remoteRoomCall(
384
+ roomId: string,
385
+ method: any,
386
+ args?: any[],
366
387
  rejectionTimeout = REMOTE_ROOM_SHORT_TIMEOUT,
367
- ): Promise<ExtractMethodOrPropertyType<TRoom, typeof method>> {
368
- const room = rooms[roomId] as TRoom;
388
+ ): Promise<any> {
389
+ const room = rooms[roomId];
369
390
 
370
391
  if (!room) {
371
392
  try {
372
- return await requestFromIPC(presence, getRoomChannel(roomId), method as string, args, rejectionTimeout);
393
+ return await requestFromIPC(presence, getRoomChannel(roomId), method, args, rejectionTimeout);
373
394
 
374
395
  } catch (e: any) {
375
396
 
@@ -384,7 +405,7 @@ export async function remoteRoomCall<TRoom = Room>(
384
405
 
385
406
  // TODO: for 1.0, consider always throwing previous error directly.
386
407
 
387
- const request = `${String(method)}${args && ' with args ' + JSON.stringify(args) || ''}`;
408
+ const request = `${method}${args && ' with args ' + JSON.stringify(args) || ''}`;
388
409
  throw new ServerError(
389
410
  ErrorCode.MATCHMAKE_UNHANDLED,
390
411
  `remote room (${roomId}) timed out, requesting "${request}". (${rejectionTimeout}ms exceeded)`,
@@ -392,9 +413,9 @@ export async function remoteRoomCall<TRoom = Room>(
392
413
  }
393
414
 
394
415
  } else {
395
- return (!args && typeof (room[method]) !== 'function')
396
- ? room[method as string]
397
- : (await room[method as string].apply(room, args && JSON.parse(JSON.stringify(args))));
416
+ return (!args && typeof ((room as any)[method]) !== 'function')
417
+ ? (room as any)[method]
418
+ : (await (room as any)[method].apply(room, args && JSON.parse(JSON.stringify(args))));
398
419
  }
399
420
  }
400
421
 
package/src/Room.ts CHANGED
@@ -255,7 +255,8 @@ export class Room<T extends RoomOptions = RoomOptions> {
255
255
  public messages?: Messages<any>;
256
256
 
257
257
  private onMessageEvents = createNanoEvents();
258
- private onMessageValidators: {[message: string]: StandardSchemaV1} = {};
258
+ // null-prototype: keyed by client-supplied message type (colyseus/colyseus#951)
259
+ private onMessageValidators: {[message: string]: StandardSchemaV1} = Object.create(null);
259
260
 
260
261
  private onMessageFallbacks = {
261
262
  '__no_message_handler': (client: ExtractRoomClient<T>, messageType: string | number, _: unknown) => {
@@ -1308,7 +1309,7 @@ export class Room<T extends RoomOptions = RoomOptions> {
1308
1309
  * Allow the specified client to reconnect into the room. Must be used inside `onLeave()` method.
1309
1310
  * If seconds is provided, the reconnection is going to be cancelled after the provided amount of seconds.
1310
1311
  *
1311
- * @param client - The client that is allowed to reconnect into the room.
1312
+ * @param previousClient - The client that is allowed to reconnect into the room.
1312
1313
  * @param seconds - The time in seconds that the client is allowed to reconnect into the room.
1313
1314
  *
1314
1315
  * @returns Deferred<Client> - The differed is a promise like type.
@@ -22,6 +22,24 @@ export type ExtractMethodOrPropertyType<
22
22
  ? Awaited<R>
23
23
  : TClass[TKey];
24
24
 
25
+ /**
26
+ * Return type of `remoteRoomCall()`.
27
+ *
28
+ * Resolves to the method's awaited return type (or the property's type) when
29
+ * the method name was captured as a literal type. Falls back to `any` when it
30
+ * wasn't — e.g. `remoteRoomCall<MyRoom>(...)` with only the room type given:
31
+ * TypeScript applies the `TMethod` default instead of inferring the literal
32
+ * once an explicit type argument list is present (microsoft/TypeScript#26242).
33
+ * Pass both type arguments (`remoteRoomCall<MyRoom, 'myMethod'>`) for a
34
+ * precise return type.
35
+ */
36
+ export type RemoteRoomCallReturn<
37
+ TRoom,
38
+ TMethod extends keyof TRoom
39
+ > = keyof TRoom extends TMethod
40
+ ? any
41
+ : ExtractMethodOrPropertyType<TRoom, TMethod>;
42
+
25
43
  // remote room call timeouts
26
44
  export const REMOTE_ROOM_SHORT_TIMEOUT = Number(process.env.COLYSEUS_PRESENCE_SHORT_TIMEOUT || 2000);
27
45
  export const MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME = Number(process.env.COLYSEUS_MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME || 0.5);
@@ -10,7 +10,10 @@ export const createNanoEvents = () => ({
10
10
  callbacks[i](...args)
11
11
  }
12
12
  },
13
- events: {},
13
+ // null-prototype: event names are client-supplied (message types), and on a
14
+ // plain object "__proto__" / "constructor" / "toString" resolve to inherited
15
+ // members instead of missing (colyseus/colyseus#951)
16
+ events: Object.create(null) as { [event: string]: Array<(...args: any[]) => void> },
14
17
  on(event: string, cb: (...args: any[]) => void) {
15
18
  ;(this.events[event] ||= []).push(cb)
16
19
  return () => {