@colyseus/core 0.18.3 → 0.18.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/build/Debug.cjs +1 -0
  2. package/build/Debug.cjs.map +1 -1
  3. package/build/IPC.cjs +1 -0
  4. package/build/IPC.cjs.map +1 -1
  5. package/build/Logger.cjs +1 -0
  6. package/build/Logger.cjs.map +1 -1
  7. package/build/MatchMaker.cjs +13 -5
  8. package/build/MatchMaker.cjs.map +2 -2
  9. package/build/MatchMaker.d.ts +10 -4
  10. package/build/MatchMaker.mjs +12 -5
  11. package/build/MatchMaker.mjs.map +2 -2
  12. package/build/Protocol.cjs +1 -0
  13. package/build/Protocol.cjs.map +1 -1
  14. package/build/Rewind.cjs +1 -0
  15. package/build/Rewind.cjs.map +1 -1
  16. package/build/Room.cjs +113 -3
  17. package/build/Room.cjs.map +2 -2
  18. package/build/Room.d.ts +58 -1
  19. package/build/Room.mjs +112 -3
  20. package/build/Room.mjs.map +2 -2
  21. package/build/RoomMessages.cjs +3 -1
  22. package/build/RoomMessages.cjs.map +2 -2
  23. package/build/RoomMessages.d.ts +3 -1
  24. package/build/RoomMessages.mjs +2 -1
  25. package/build/RoomMessages.mjs.map +2 -2
  26. package/build/RoomPlugin.cjs +1 -0
  27. package/build/RoomPlugin.cjs.map +1 -1
  28. package/build/Server.cjs +1 -0
  29. package/build/Server.cjs.map +1 -1
  30. package/build/Stats.cjs +1 -0
  31. package/build/Stats.cjs.map +1 -1
  32. package/build/Transport.cjs +39 -0
  33. package/build/Transport.cjs.map +2 -2
  34. package/build/Transport.d.ts +66 -1
  35. package/build/Transport.mjs +37 -1
  36. package/build/Transport.mjs.map +2 -2
  37. package/build/errors/RoomExceptions.cjs +1 -0
  38. package/build/errors/RoomExceptions.cjs.map +1 -1
  39. package/build/errors/SeatReservationError.cjs +1 -0
  40. package/build/errors/SeatReservationError.cjs.map +1 -1
  41. package/build/errors/ServerError.cjs +1 -0
  42. package/build/errors/ServerError.cjs.map +1 -1
  43. package/build/index.cjs +5 -0
  44. package/build/index.cjs.map +2 -2
  45. package/build/index.d.ts +1 -1
  46. package/build/index.mjs +3 -1
  47. package/build/index.mjs.map +2 -2
  48. package/build/input/InputBuffer.cjs +1 -0
  49. package/build/input/InputBuffer.cjs.map +1 -1
  50. package/build/input/RoomInput.cjs +64 -3
  51. package/build/input/RoomInput.cjs.map +2 -2
  52. package/build/input/RoomInput.d.ts +16 -2
  53. package/build/input/RoomInput.mjs +63 -3
  54. package/build/input/RoomInput.mjs.map +2 -2
  55. package/build/input/types.cjs +1 -0
  56. package/build/input/types.cjs.map +1 -1
  57. package/build/internal.cjs +1 -0
  58. package/build/internal.cjs.map +1 -1
  59. package/build/matchmaker/Lobby.cjs +1 -0
  60. package/build/matchmaker/Lobby.cjs.map +1 -1
  61. package/build/matchmaker/LocalDriver/LocalDriver.cjs +1 -0
  62. package/build/matchmaker/LocalDriver/LocalDriver.cjs.map +1 -1
  63. package/build/matchmaker/LocalDriver/Query.cjs +1 -0
  64. package/build/matchmaker/LocalDriver/Query.cjs.map +1 -1
  65. package/build/matchmaker/RegisteredHandler.cjs +1 -0
  66. package/build/matchmaker/RegisteredHandler.cjs.map +1 -1
  67. package/build/matchmaker/controller.cjs +1 -0
  68. package/build/matchmaker/controller.cjs.map +1 -1
  69. package/build/matchmaker/driver.cjs +1 -0
  70. package/build/matchmaker/driver.cjs.map +2 -2
  71. package/build/matchmaker/driver.d.ts +4 -2
  72. package/build/matchmaker/driver.mjs.map +1 -1
  73. package/build/presence/LocalPresence.cjs +1 -0
  74. package/build/presence/LocalPresence.cjs.map +1 -1
  75. package/build/presence/Presence.cjs +1 -0
  76. package/build/presence/Presence.cjs.map +1 -1
  77. package/build/rooms/LobbyRoom.cjs +1 -0
  78. package/build/rooms/LobbyRoom.cjs.map +1 -1
  79. package/build/rooms/QueueRoom.cjs +1 -0
  80. package/build/rooms/QueueRoom.cjs.map +1 -1
  81. package/build/rooms/RelayRoom.cjs +1 -0
  82. package/build/rooms/RelayRoom.cjs.map +1 -1
  83. package/build/router/default_routes.cjs +4 -8
  84. package/build/router/default_routes.cjs.map +2 -2
  85. package/build/router/default_routes.mjs +4 -9
  86. package/build/router/default_routes.mjs.map +2 -2
  87. package/build/router/index.cjs +7 -1
  88. package/build/router/index.cjs.map +2 -2
  89. package/build/router/index.mjs +6 -1
  90. package/build/router/index.mjs.map +2 -2
  91. package/build/router/node.cjs +23 -2
  92. package/build/router/node.cjs.map +2 -2
  93. package/build/router/node.mjs +22 -2
  94. package/build/router/node.mjs.map +2 -2
  95. package/build/serializer/NoneSerializer.cjs +1 -0
  96. package/build/serializer/NoneSerializer.cjs.map +1 -1
  97. package/build/serializer/SchemaSerializer.cjs +98 -0
  98. package/build/serializer/SchemaSerializer.cjs.map +2 -2
  99. package/build/serializer/SchemaSerializer.d.ts +25 -0
  100. package/build/serializer/SchemaSerializer.mjs +98 -1
  101. package/build/serializer/SchemaSerializer.mjs.map +2 -2
  102. package/build/serializer/Serializer.cjs +1 -0
  103. package/build/serializer/Serializer.cjs.map +2 -2
  104. package/build/serializer/Serializer.d.ts +15 -0
  105. package/build/utils/DevMode.cjs +1 -0
  106. package/build/utils/DevMode.cjs.map +1 -1
  107. package/build/utils/Env.cjs +1 -0
  108. package/build/utils/Env.cjs.map +1 -1
  109. package/build/utils/StandardSchema.cjs +1 -0
  110. package/build/utils/StandardSchema.cjs.map +1 -1
  111. package/build/utils/UserSessionIndex.cjs +1 -0
  112. package/build/utils/UserSessionIndex.cjs.map +1 -1
  113. package/build/utils/Utils.cjs +1 -0
  114. package/build/utils/Utils.cjs.map +2 -2
  115. package/build/utils/Utils.d.ts +12 -0
  116. package/build/utils/Utils.mjs.map +2 -2
  117. package/build/utils/nanoevents.cjs +5 -1
  118. package/build/utils/nanoevents.cjs.map +2 -2
  119. package/build/utils/nanoevents.d.ts +3 -1
  120. package/build/utils/nanoevents.mjs +4 -1
  121. package/build/utils/nanoevents.mjs.map +2 -2
  122. package/package.json +8 -7
  123. package/src/MatchMaker.ts +53 -15
  124. package/src/Room.ts +145 -4
  125. package/src/RoomMessages.ts +2 -1
  126. package/src/Transport.ts +132 -4
  127. package/src/index.ts +1 -1
  128. package/src/input/RoomInput.ts +76 -3
  129. package/src/matchmaker/driver.ts +4 -2
  130. package/src/router/default_routes.ts +4 -9
  131. package/src/router/index.ts +7 -0
  132. package/src/router/node.ts +26 -2
  133. package/src/serializer/SchemaSerializer.ts +146 -1
  134. package/src/serializer/Serializer.ts +17 -0
  135. package/src/utils/Utils.ts +18 -0
  136. package/src/utils/nanoevents.ts +4 -1
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/UserSessionIndex.ts"],
4
4
  "sourcesContent": ["/**\n * User \u2192 active sessions index: \"which active rooms is user X in?\"\n *\n * Without this, answering that question would require fanning out\n * `getInspectorView()` across every running room \u2014 O(rooms) per lookup.\n * Instead, each Room writes a small Presence hash entry on `_onJoin` and\n * removes it on `_onAfterLeave` / dispose. The admin endpoint reads the\n * hash with a single `hgetall`, then reconciles against the matchmaker's\n * live room listing to drop stale entries left behind by hard crashes\n * (no `onLeave` ran).\n *\n * Hash schema:\n * key: colyseus:user-rooms:{userId}\n * field: sessionId\n * value: JSON `{ roomId, roomName, joinedAt }` (joinedAt is unix ms)\n *\n * Anonymous clients (no userId resolvable) are skipped \u2014 the index is\n * for forensic / support workflows, not anonymous traffic. A Room can\n * also opt out wholesale by setting `trackUserSessions = false` (e.g.\n * a high-volume relay room that doesn't want to pay the Presence\n * write per join).\n *\n * Per-Room state (which sessionIds have an entry, and under which userId)\n * lives in a module-level WeakMap rather than as a field on Room itself.\n * Two reasons:\n *\n * 1. Room.ts stays thin \u2014 it only knows about three call points\n * (`trackRoomJoin`, `releaseRoomLeave`, `sweepRoomDispose`) and\n * doesn't have to carry an extra Map field or two private helper\n * methods solely for this concern.\n * 2. The WeakMap is GC-tied to the Room \u2014 when a Room instance is\n * collected, its entries vanish automatically. No explicit teardown\n * hook needed beyond the dispose sweep that drains Presence.\n *\n * @internal\n */\nimport type { Presence } from '../presence/Presence.ts';\n\nexport const USER_ROOMS_KEY_PREFIX = 'colyseus:user-rooms:';\n\nexport function userRoomsKey(userId: string): string {\n return USER_ROOMS_KEY_PREFIX + userId;\n}\n\n/**\n * What's serialized into the Presence hash value (sessionId is the\n * hash field key, not part of the body). Internal write-side shape.\n */\nexport interface UserRoomEntry {\n roomId: string;\n roomName: string;\n joinedAt: number;\n}\n\n/**\n * Public read-side shape returned by `listUserSessions`: a parsed\n * `UserRoomEntry` plus its sessionId, optionally enriched with\n * `processId` when reconcile against the matchmaker was on.\n */\nexport interface UserSessionInfo extends UserRoomEntry {\n sessionId: string;\n /**\n * Process hosting the room, per the matchmaker. Populated only\n * when `listUserSessions` was called with `reconcile: true` AND\n * the room is still in the matchmaker roster.\n */\n processId?: string;\n}\n\n/**\n * Structural subset of `Room` needed by the index. Lets this module\n * avoid importing `Room` (which would create a cycle) while still\n * staying typed at the call site.\n */\ninterface InspectorRoomShape {\n roomId: string;\n roomName: string;\n presence: Presence;\n}\n\n/**\n * Structural subset of a Client we read at join/leave. `userId` and\n * `auth` are both optional \u2014 the index simply skips clients without\n * either, which is the correct \"anonymous traffic doesn't show up\n * in support tooling\" behavior.\n */\ninterface InspectorClientShape {\n sessionId: string;\n userId?: string;\n auth?: { id?: string } | null;\n}\n\n/**\n * sessionId \u2192 userId for clients currently registered in the index,\n * scoped per Room. WeakMap-keyed so a forgotten Room takes its tracking\n * map with it.\n */\nconst tracked = new WeakMap<InspectorRoomShape, Map<string, string>>();\n\nfunction getTrackingMap(room: InspectorRoomShape): Map<string, string> {\n let map = tracked.get(room);\n if (!map) {\n map = new Map();\n tracked.set(room, map);\n }\n return map;\n}\n\nfunction resolveUserId(client: InspectorClientShape): string | undefined {\n return client.userId ?? client.auth?.id;\n}\n\n/**\n * Best-effort: write the join entry. Errors are swallowed because the\n * index is observability metadata \u2014 a Presence outage shouldn't reject\n * a player's join. Exposed as a pure helper for tests + the admin\n * endpoint; `trackRoomJoin` is the Room-flavored entrypoint.\n */\nexport async function trackUserSession(\n presence: Presence,\n userId: string,\n sessionId: string,\n entry: UserRoomEntry,\n): Promise<void> {\n try {\n await presence.hset(userRoomsKey(userId), sessionId, JSON.stringify(entry));\n } catch {\n // intentional: see fn-doc\n }\n}\n\n/**\n * Best-effort: remove the join entry. Errors are swallowed so a Presence\n * blip doesn't bubble into `_onAfterLeave` / `_dispose`.\n */\nexport async function releaseUserSession(\n presence: Presence,\n userId: string,\n sessionId: string,\n): Promise<void> {\n try {\n await presence.hdel(userRoomsKey(userId), sessionId);\n } catch {\n // intentional: see fn-doc\n }\n}\n\n/**\n * Record `client`'s join under `room` in the reverse index. No-op for\n * clients with no resolvable userId (anonymous), and when the room\n * carries no presence (shouldn't happen \u2014 defensive for unit tests).\n *\n * Fire-and-forget against Presence; the in-memory tracking map updates\n * synchronously so a follow-up `releaseRoomLeave` always finds the\n * right userId even if the Presence write is still in flight.\n */\nexport function trackRoomJoin(room: InspectorRoomShape, client: InspectorClientShape): void {\n if (!room.presence) { return; }\n const userId = resolveUserId(client);\n if (!userId) { return; }\n getTrackingMap(room).set(client.sessionId, userId);\n const entry: UserRoomEntry = {\n roomId: room.roomId,\n roomName: room.roomName,\n joinedAt: Date.now(),\n };\n void trackUserSession(room.presence, userId, client.sessionId, entry);\n}\n\n/**\n * Drop `client`'s entry from the reverse index. Idempotent \u2014 no-op\n * when the client wasn't tracked (anonymous, or tracking failed at\n * join time).\n */\nexport function releaseRoomLeave(room: InspectorRoomShape, client: InspectorClientShape): void {\n if (!room.presence) { return; }\n const map = tracked.get(room);\n const userId = map?.get(client.sessionId);\n if (!userId || !map) { return; }\n map.delete(client.sessionId);\n void releaseUserSession(room.presence, userId, client.sessionId);\n}\n\n/**\n * Sweep any still-tracked sessions for `room`. Called during dispose to\n * cover the case where `disconnect()` races the per-client `_onAfterLeave`\n * path \u2014 the read-side reconcile handles cross-process crash recovery,\n * but this is the cheap deterministic cleanup for a clean local dispose.\n *\n * Awaits the pending `hdel`s so the caller can sequence against \"the\n * index is now coherent\" \u2014 the dispose path uses that ordering.\n */\nexport async function sweepRoomDispose(room: InspectorRoomShape): Promise<void> {\n if (!room.presence) { return; }\n const map = tracked.get(room);\n if (!map || map.size === 0) { return; }\n const pending: Promise<void>[] = [];\n for (const [sessionId, userId] of map) {\n pending.push(releaseUserSession(room.presence, userId, sessionId));\n }\n map.clear();\n await Promise.all(pending);\n}\n\n/**\n * Minimal shape of a matchmaker room record needed for reconcile \u2014\n * keeps this module decoupled from the matchmaker / driver types.\n * `matchMaker.query()`'s actual return (`IRoomCache[]`) is structurally\n * a supertype of this, so callers can pass `matchMaker.query` directly.\n */\ninterface MatchmakerRoomLike {\n roomId: string;\n processId?: string;\n}\n\nexport interface ListUserSessionsOptions {\n /**\n * Drop entries whose `roomId` is no longer in the matchmaker roster\n * (the index can lag a crashed process). When `true`, the returned\n * entries also carry `processId` from the live room record.\n *\n * Off by default \u2014 most callers (kick everyone, count) don't need it\n * and the extra `matchMaker.query` round-trip isn't free.\n */\n reconcile?: boolean;\n\n /**\n * Fire-and-forget `hdel` for stale entries \u2014 those dropped by\n * reconcile (matchmaker doesn't know the room anymore) plus any\n * with corrupt JSON. Lets read endpoints self-heal the index on\n * each call. No-op when `reconcile` is `false`.\n */\n removeStale?: boolean;\n}\n\n/**\n * Read the user \u2192 active sessions index. Pure helper \u2014 the\n * `Presence` + matchmaker batch lookup are injected so this module\n * stays free of matchmaker imports (and so unit tests can drive it\n * with fake deps).\n *\n * Wire-op count per call:\n * - 1 HGETALL on the user's hash (always).\n * - 1 batch room lookup when `reconcile: true` AND there are\n * entries to verify; skipped otherwise.\n *\n * Bounded at 2 wire ops regardless of the user's session count.\n */\nexport async function listUserSessions(\n presence: Presence,\n findRooms: (roomIds: string[]) => Promise<Map<string, MatchmakerRoomLike>>,\n userId: string,\n options: ListUserSessionsOptions = {},\n): Promise<UserSessionInfo[]> {\n const reconcile = options.reconcile === true;\n const removeStale = reconcile && options.removeStale === true;\n\n let raw: Record<string, string>;\n try {\n raw = await presence.hgetall(userRoomsKey(userId));\n } catch {\n // Presence outage \u2014 observability shouldn't bring down the caller.\n return [];\n }\n const fields = Object.keys(raw);\n if (fields.length === 0) { return []; }\n\n const staleSessions: string[] = [];\n const parsed: Array<{ sessionId: string; entry: UserRoomEntry }> = [];\n for (const sessionId of fields) {\n try {\n parsed.push({ sessionId, entry: JSON.parse(raw[sessionId]) as UserRoomEntry });\n } catch {\n // Corrupt JSON \u2014 index drift. Removable even without reconcile.\n staleSessions.push(sessionId);\n }\n }\n\n if (!reconcile) {\n return parsed.map(({ sessionId, entry }) => ({ sessionId, ...entry }));\n }\n\n // One batch lookup for the K roomIds we care about. K = entries\n // surviving the JSON.parse stage, not cluster size.\n const live = parsed.length > 0\n ? await findRooms(parsed.map((p) => p.entry.roomId))\n : new Map<string, MatchmakerRoomLike>();\n\n const result: UserSessionInfo[] = [];\n for (const { sessionId, entry } of parsed) {\n const room = live.get(entry.roomId);\n if (!room) {\n // Matchmaker doesn't know this roomId anymore \u2014 stale entry\n // from a crashed process. Drop it (and remove if requested).\n staleSessions.push(sessionId);\n continue;\n }\n const info: UserSessionInfo = { sessionId, ...entry };\n if (room.processId !== undefined) { info.processId = room.processId; }\n result.push(info);\n }\n\n if (removeStale && staleSessions.length > 0) {\n const key = userRoomsKey(userId);\n void Promise.all(\n staleSessions.map((s) => presence.hdel(key, s)),\n ).catch(() => { /* presence outage, swallow */ });\n }\n\n return result;\n}\n"],
5
- "mappings": "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",
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/Utils.ts"],
4
- "sourcesContent": ["import { nanoid } from 'nanoid';\nimport { type RoomException, type RoomMethodName } from '../errors/RoomExceptions.ts';\n\nimport { debugAndPrintError, debugMatchMaking } from '../Debug.ts';\n\nexport type Type<T> = new (...args: any[]) => T;\nexport type MethodName<T> = string & {\n [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never\n}[keyof T];\n\n/**\n * Utility type that extracts the return type of a method or the type of a property\n * from a given class/object type.\n *\n * - If the key is a method, returns the awaited return type of that method\n * - If the key is a property, returns the type of that property\n */\nexport type ExtractMethodOrPropertyType<\n TClass,\n TKey extends keyof TClass\n> = TClass[TKey] extends (...args: any[]) => infer R\n ? Awaited<R>\n : TClass[TKey];\n\n// remote room call timeouts\nexport const REMOTE_ROOM_SHORT_TIMEOUT = Number(process.env.COLYSEUS_PRESENCE_SHORT_TIMEOUT || 2000);\nexport const MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME = Number(process.env.COLYSEUS_MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME || 0.5);\n\n/**\n * The well-known `Symbol.metadata` (TC39 decorator metadata), falling back to the\n * registered symbol on runtimes that don't expose it globally yet. `@colyseus/schema`\n * stores each class's field metadata under this key \u2014 read it via\n * `instance.constructor[$METADATA]`. Shared so every reader resolves the IDENTICAL\n * symbol (InputBuffer's field-name walk, Rewind's field-index lookup).\n */\nexport const $METADATA: symbol = (Symbol as { metadata?: symbol }).metadata ?? Symbol.for(\"Symbol.metadata\");\n\nexport function generateId(length: number = 9) {\n return nanoid(length);\n}\n\nexport function getBearerToken(authHeader: string) {\n return (authHeader && authHeader.startsWith(\"Bearer \") && authHeader.substring(7, authHeader.length)) || undefined;\n}\n\n// nodemon sends SIGUSR2 before reloading\n// (https://github.com/remy/nodemon#controlling-shutdown-of-your-script)\n//\nconst signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGUSR2'];\n\nexport function registerGracefulShutdown(callback: (err?: Error) => void) {\n /**\n * Gracefully shutdown on uncaught errors\n */\n process.on('uncaughtException', (err) => {\n debugAndPrintError(err);\n callback(err);\n });\n\n signals.forEach((signal) =>\n process.once(signal, () => callback()));\n}\n\nexport function retry<T = any>(\n cb: Function,\n maxRetries: number = 3,\n errorWhiteList: any[] = [],\n retries: number = 0,\n) {\n return new Promise<T>((resolve, reject) => {\n cb()\n .then(resolve)\n .catch((e: any) => {\n if (\n errorWhiteList.indexOf(e.constructor) !== -1 &&\n retries++ < maxRetries\n ) {\n setTimeout(() => {\n debugMatchMaking(\"retrying due to error (error: %s, retries: %s, maxRetries: %s)\", e.message, retries, maxRetries);\n retry<T>(cb, maxRetries, errorWhiteList, retries).\n then(resolve).\n catch((e2) => reject(e2));\n }, Math.floor(Math.random() * Math.pow(2, retries) * 400));\n\n } else {\n reject(e);\n }\n });\n });\n}\n\nexport function spliceOne(arr: any[], index: number): boolean {\n // manually splice availableRooms array\n // http://jsperf.com/manual-splice\n if (index === -1 || index >= arr.length) {\n return false;\n }\n\n const len = arr.length - 1;\n for (let i = index; i < len; i++) {\n arr[i] = arr[i + 1];\n }\n\n arr.length = len;\n return true;\n}\n\nexport class Deferred<T = any> {\n public promise: Promise<T>;\n\n public resolve: Function;\n public reject: Function;\n\n constructor(promise?: Promise<T>) {\n this.promise = promise ?? new Promise<T>((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n }\n\n public then(onFulfilled?: (value: T) => any, onRejected?: (reason: any) => any) {\n return this.promise.then(onFulfilled, onRejected);\n }\n\n public catch(func: (value: any) => any) {\n return this.promise.catch(func);\n }\n\n static reject (reason?: any) {\n return new Deferred(Promise.reject(reason));\n }\n\n static resolve<T = any>(value?: T) {\n return new Deferred<T>(Promise.resolve(value));\n }\n\n}\n\nexport function merge(a: any, ...objs: any[]): any {\n for (let i = 0, len = objs.length; i < len; i++) {\n const b = objs[i];\n for (const key in b) {\n if (b.hasOwnProperty(key)) {\n a[key] = b[key];\n }\n }\n }\n return a;\n}\n\nexport function wrapTryCatch(\n method: Function,\n onError: (error: RoomException, methodName: RoomMethodName) => void,\n exceptionClass: Type<RoomException>,\n methodName: RoomMethodName,\n rethrow: boolean = false,\n ...additionalErrorArgs: any[]\n) {\n return (...args: any[]) => {\n try {\n const result = method(...args);\n if (typeof (result?.catch) === \"function\") {\n return result.catch((e: Error) => {\n onError(new exceptionClass(e, e.message, ...args, ...additionalErrorArgs), methodName);\n if (rethrow) { throw e; }\n });\n }\n return result;\n } catch (e: any) {\n onError(new exceptionClass(e, e.message, ...args, ...additionalErrorArgs), methodName);\n if (rethrow) { throw e; }\n }\n };\n}\n\n/**\n * Dynamically import a module using either require() or import()\n * based on the current module system (CJS vs ESM).\n *\n * This avoids double-loading packages when running in mixed ESM/CJS environments.\n * Errors are silently caught - await the promise and handle errors at usage site.\n */\nexport function dynamicImport<T = any>(moduleName: string): Promise<T> {\n // __dirname exists in CJS but not in ESM\n if (\n typeof __dirname !== 'undefined' &&\n // @ts-ignore\n typeof (Bun) === 'undefined' // prevent bun from loading CJS modules\n ) {\n // CJS context - use require()\n try {\n return Promise.resolve(require(moduleName));\n } catch (e: any) {\n // If the error is not a MODULE_NOT_FOUND error, reject with the error.\n if (e.code !== 'MODULE_NOT_FOUND') {\n return Promise.reject(e);\n }\n return Promise.resolve(undefined);\n }\n } else {\n // ESM context - use import()\n const promise = import(/* @vite-ignore */ moduleName);\n promise.catch(() => {}); // prevent unhandled rejection warnings\n return promise;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { nanoid } from 'nanoid';\nimport { type RoomException, type RoomMethodName } from '../errors/RoomExceptions.ts';\n\nimport { debugAndPrintError, debugMatchMaking } from '../Debug.ts';\n\nexport type Type<T> = new (...args: any[]) => T;\nexport type MethodName<T> = string & {\n [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never\n}[keyof T];\n\n/**\n * Utility type that extracts the return type of a method or the type of a property\n * from a given class/object type.\n *\n * - If the key is a method, returns the awaited return type of that method\n * - If the key is a property, returns the type of that property\n */\nexport type ExtractMethodOrPropertyType<\n TClass,\n TKey extends keyof TClass\n> = TClass[TKey] extends (...args: any[]) => infer R\n ? Awaited<R>\n : TClass[TKey];\n\n/**\n * Return type of `remoteRoomCall()`.\n *\n * Resolves to the method's awaited return type (or the property's type) when\n * the method name was captured as a literal type. Falls back to `any` when it\n * wasn't \u2014 e.g. `remoteRoomCall<MyRoom>(...)` with only the room type given:\n * TypeScript applies the `TMethod` default instead of inferring the literal\n * once an explicit type argument list is present (microsoft/TypeScript#26242).\n * Pass both type arguments (`remoteRoomCall<MyRoom, 'myMethod'>`) for a\n * precise return type.\n */\nexport type RemoteRoomCallReturn<\n TRoom,\n TMethod extends keyof TRoom\n> = keyof TRoom extends TMethod\n ? any\n : ExtractMethodOrPropertyType<TRoom, TMethod>;\n\n// remote room call timeouts\nexport const REMOTE_ROOM_SHORT_TIMEOUT = Number(process.env.COLYSEUS_PRESENCE_SHORT_TIMEOUT || 2000);\nexport const MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME = Number(process.env.COLYSEUS_MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME || 0.5);\n\n/**\n * The well-known `Symbol.metadata` (TC39 decorator metadata), falling back to the\n * registered symbol on runtimes that don't expose it globally yet. `@colyseus/schema`\n * stores each class's field metadata under this key \u2014 read it via\n * `instance.constructor[$METADATA]`. Shared so every reader resolves the IDENTICAL\n * symbol (InputBuffer's field-name walk, Rewind's field-index lookup).\n */\nexport const $METADATA: symbol = (Symbol as { metadata?: symbol }).metadata ?? Symbol.for(\"Symbol.metadata\");\n\nexport function generateId(length: number = 9) {\n return nanoid(length);\n}\n\nexport function getBearerToken(authHeader: string) {\n return (authHeader && authHeader.startsWith(\"Bearer \") && authHeader.substring(7, authHeader.length)) || undefined;\n}\n\n// nodemon sends SIGUSR2 before reloading\n// (https://github.com/remy/nodemon#controlling-shutdown-of-your-script)\n//\nconst signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGUSR2'];\n\nexport function registerGracefulShutdown(callback: (err?: Error) => void) {\n /**\n * Gracefully shutdown on uncaught errors\n */\n process.on('uncaughtException', (err) => {\n debugAndPrintError(err);\n callback(err);\n });\n\n signals.forEach((signal) =>\n process.once(signal, () => callback()));\n}\n\nexport function retry<T = any>(\n cb: Function,\n maxRetries: number = 3,\n errorWhiteList: any[] = [],\n retries: number = 0,\n) {\n return new Promise<T>((resolve, reject) => {\n cb()\n .then(resolve)\n .catch((e: any) => {\n if (\n errorWhiteList.indexOf(e.constructor) !== -1 &&\n retries++ < maxRetries\n ) {\n setTimeout(() => {\n debugMatchMaking(\"retrying due to error (error: %s, retries: %s, maxRetries: %s)\", e.message, retries, maxRetries);\n retry<T>(cb, maxRetries, errorWhiteList, retries).\n then(resolve).\n catch((e2) => reject(e2));\n }, Math.floor(Math.random() * Math.pow(2, retries) * 400));\n\n } else {\n reject(e);\n }\n });\n });\n}\n\nexport function spliceOne(arr: any[], index: number): boolean {\n // manually splice availableRooms array\n // http://jsperf.com/manual-splice\n if (index === -1 || index >= arr.length) {\n return false;\n }\n\n const len = arr.length - 1;\n for (let i = index; i < len; i++) {\n arr[i] = arr[i + 1];\n }\n\n arr.length = len;\n return true;\n}\n\nexport class Deferred<T = any> {\n public promise: Promise<T>;\n\n public resolve: Function;\n public reject: Function;\n\n constructor(promise?: Promise<T>) {\n this.promise = promise ?? new Promise<T>((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n }\n\n public then(onFulfilled?: (value: T) => any, onRejected?: (reason: any) => any) {\n return this.promise.then(onFulfilled, onRejected);\n }\n\n public catch(func: (value: any) => any) {\n return this.promise.catch(func);\n }\n\n static reject (reason?: any) {\n return new Deferred(Promise.reject(reason));\n }\n\n static resolve<T = any>(value?: T) {\n return new Deferred<T>(Promise.resolve(value));\n }\n\n}\n\nexport function merge(a: any, ...objs: any[]): any {\n for (let i = 0, len = objs.length; i < len; i++) {\n const b = objs[i];\n for (const key in b) {\n if (b.hasOwnProperty(key)) {\n a[key] = b[key];\n }\n }\n }\n return a;\n}\n\nexport function wrapTryCatch(\n method: Function,\n onError: (error: RoomException, methodName: RoomMethodName) => void,\n exceptionClass: Type<RoomException>,\n methodName: RoomMethodName,\n rethrow: boolean = false,\n ...additionalErrorArgs: any[]\n) {\n return (...args: any[]) => {\n try {\n const result = method(...args);\n if (typeof (result?.catch) === \"function\") {\n return result.catch((e: Error) => {\n onError(new exceptionClass(e, e.message, ...args, ...additionalErrorArgs), methodName);\n if (rethrow) { throw e; }\n });\n }\n return result;\n } catch (e: any) {\n onError(new exceptionClass(e, e.message, ...args, ...additionalErrorArgs), methodName);\n if (rethrow) { throw e; }\n }\n };\n}\n\n/**\n * Dynamically import a module using either require() or import()\n * based on the current module system (CJS vs ESM).\n *\n * This avoids double-loading packages when running in mixed ESM/CJS environments.\n * Errors are silently caught - await the promise and handle errors at usage site.\n */\nexport function dynamicImport<T = any>(moduleName: string): Promise<T> {\n // __dirname exists in CJS but not in ESM\n if (\n typeof __dirname !== 'undefined' &&\n // @ts-ignore\n typeof (Bun) === 'undefined' // prevent bun from loading CJS modules\n ) {\n // CJS context - use require()\n try {\n return Promise.resolve(require(moduleName));\n } catch (e: any) {\n // If the error is not a MODULE_NOT_FOUND error, reject with the error.\n if (e.code !== 'MODULE_NOT_FOUND') {\n return Promise.reject(e);\n }\n return Promise.resolve(undefined);\n }\n } else {\n // ESM context - use import()\n const promise = import(/* @vite-ignore */ moduleName);\n promise.catch(() => {}); // prevent unhandled rejection warnings\n return promise;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -11,6 +11,18 @@ export type MethodName<T> = string & {
11
11
  * - If the key is a property, returns the type of that property
12
12
  */
13
13
  export type ExtractMethodOrPropertyType<TClass, TKey extends keyof TClass> = TClass[TKey] extends (...args: any[]) => infer R ? Awaited<R> : TClass[TKey];
14
+ /**
15
+ * Return type of `remoteRoomCall()`.
16
+ *
17
+ * Resolves to the method's awaited return type (or the property's type) when
18
+ * the method name was captured as a literal type. Falls back to `any` when it
19
+ * wasn't — e.g. `remoteRoomCall<MyRoom>(...)` with only the room type given:
20
+ * TypeScript applies the `TMethod` default instead of inferring the literal
21
+ * once an explicit type argument list is present (microsoft/TypeScript#26242).
22
+ * Pass both type arguments (`remoteRoomCall<MyRoom, 'myMethod'>`) for a
23
+ * precise return type.
24
+ */
25
+ export type RemoteRoomCallReturn<TRoom, TMethod extends keyof TRoom> = keyof TRoom extends TMethod ? any : ExtractMethodOrPropertyType<TRoom, TMethod>;
14
26
  export declare const REMOTE_ROOM_SHORT_TIMEOUT: number;
15
27
  export declare const MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME: number;
16
28
  /**
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/Utils.ts"],
4
- "sourcesContent": ["import { nanoid } from 'nanoid';\nimport { type RoomException, type RoomMethodName } from '../errors/RoomExceptions.ts';\n\nimport { debugAndPrintError, debugMatchMaking } from '../Debug.ts';\n\nexport type Type<T> = new (...args: any[]) => T;\nexport type MethodName<T> = string & {\n [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never\n}[keyof T];\n\n/**\n * Utility type that extracts the return type of a method or the type of a property\n * from a given class/object type.\n *\n * - If the key is a method, returns the awaited return type of that method\n * - If the key is a property, returns the type of that property\n */\nexport type ExtractMethodOrPropertyType<\n TClass,\n TKey extends keyof TClass\n> = TClass[TKey] extends (...args: any[]) => infer R\n ? Awaited<R>\n : TClass[TKey];\n\n// remote room call timeouts\nexport const REMOTE_ROOM_SHORT_TIMEOUT = Number(process.env.COLYSEUS_PRESENCE_SHORT_TIMEOUT || 2000);\nexport const MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME = Number(process.env.COLYSEUS_MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME || 0.5);\n\n/**\n * The well-known `Symbol.metadata` (TC39 decorator metadata), falling back to the\n * registered symbol on runtimes that don't expose it globally yet. `@colyseus/schema`\n * stores each class's field metadata under this key \u2014 read it via\n * `instance.constructor[$METADATA]`. Shared so every reader resolves the IDENTICAL\n * symbol (InputBuffer's field-name walk, Rewind's field-index lookup).\n */\nexport const $METADATA: symbol = (Symbol as { metadata?: symbol }).metadata ?? Symbol.for(\"Symbol.metadata\");\n\nexport function generateId(length: number = 9) {\n return nanoid(length);\n}\n\nexport function getBearerToken(authHeader: string) {\n return (authHeader && authHeader.startsWith(\"Bearer \") && authHeader.substring(7, authHeader.length)) || undefined;\n}\n\n// nodemon sends SIGUSR2 before reloading\n// (https://github.com/remy/nodemon#controlling-shutdown-of-your-script)\n//\nconst signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGUSR2'];\n\nexport function registerGracefulShutdown(callback: (err?: Error) => void) {\n /**\n * Gracefully shutdown on uncaught errors\n */\n process.on('uncaughtException', (err) => {\n debugAndPrintError(err);\n callback(err);\n });\n\n signals.forEach((signal) =>\n process.once(signal, () => callback()));\n}\n\nexport function retry<T = any>(\n cb: Function,\n maxRetries: number = 3,\n errorWhiteList: any[] = [],\n retries: number = 0,\n) {\n return new Promise<T>((resolve, reject) => {\n cb()\n .then(resolve)\n .catch((e: any) => {\n if (\n errorWhiteList.indexOf(e.constructor) !== -1 &&\n retries++ < maxRetries\n ) {\n setTimeout(() => {\n debugMatchMaking(\"retrying due to error (error: %s, retries: %s, maxRetries: %s)\", e.message, retries, maxRetries);\n retry<T>(cb, maxRetries, errorWhiteList, retries).\n then(resolve).\n catch((e2) => reject(e2));\n }, Math.floor(Math.random() * Math.pow(2, retries) * 400));\n\n } else {\n reject(e);\n }\n });\n });\n}\n\nexport function spliceOne(arr: any[], index: number): boolean {\n // manually splice availableRooms array\n // http://jsperf.com/manual-splice\n if (index === -1 || index >= arr.length) {\n return false;\n }\n\n const len = arr.length - 1;\n for (let i = index; i < len; i++) {\n arr[i] = arr[i + 1];\n }\n\n arr.length = len;\n return true;\n}\n\nexport class Deferred<T = any> {\n public promise: Promise<T>;\n\n public resolve: Function;\n public reject: Function;\n\n constructor(promise?: Promise<T>) {\n this.promise = promise ?? new Promise<T>((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n }\n\n public then(onFulfilled?: (value: T) => any, onRejected?: (reason: any) => any) {\n return this.promise.then(onFulfilled, onRejected);\n }\n\n public catch(func: (value: any) => any) {\n return this.promise.catch(func);\n }\n\n static reject (reason?: any) {\n return new Deferred(Promise.reject(reason));\n }\n\n static resolve<T = any>(value?: T) {\n return new Deferred<T>(Promise.resolve(value));\n }\n\n}\n\nexport function merge(a: any, ...objs: any[]): any {\n for (let i = 0, len = objs.length; i < len; i++) {\n const b = objs[i];\n for (const key in b) {\n if (b.hasOwnProperty(key)) {\n a[key] = b[key];\n }\n }\n }\n return a;\n}\n\nexport function wrapTryCatch(\n method: Function,\n onError: (error: RoomException, methodName: RoomMethodName) => void,\n exceptionClass: Type<RoomException>,\n methodName: RoomMethodName,\n rethrow: boolean = false,\n ...additionalErrorArgs: any[]\n) {\n return (...args: any[]) => {\n try {\n const result = method(...args);\n if (typeof (result?.catch) === \"function\") {\n return result.catch((e: Error) => {\n onError(new exceptionClass(e, e.message, ...args, ...additionalErrorArgs), methodName);\n if (rethrow) { throw e; }\n });\n }\n return result;\n } catch (e: any) {\n onError(new exceptionClass(e, e.message, ...args, ...additionalErrorArgs), methodName);\n if (rethrow) { throw e; }\n }\n };\n}\n\n/**\n * Dynamically import a module using either require() or import()\n * based on the current module system (CJS vs ESM).\n *\n * This avoids double-loading packages when running in mixed ESM/CJS environments.\n * Errors are silently caught - await the promise and handle errors at usage site.\n */\nexport function dynamicImport<T = any>(moduleName: string): Promise<T> {\n // __dirname exists in CJS but not in ESM\n if (\n typeof __dirname !== 'undefined' &&\n // @ts-ignore\n typeof (Bun) === 'undefined' // prevent bun from loading CJS modules\n ) {\n // CJS context - use require()\n try {\n return Promise.resolve(require(moduleName));\n } catch (e: any) {\n // If the error is not a MODULE_NOT_FOUND error, reject with the error.\n if (e.code !== 'MODULE_NOT_FOUND') {\n return Promise.reject(e);\n }\n return Promise.resolve(undefined);\n }\n } else {\n // ESM context - use import()\n const promise = import(/* @vite-ignore */ moduleName);\n promise.catch(() => {}); // prevent unhandled rejection warnings\n return promise;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { nanoid } from 'nanoid';\nimport { type RoomException, type RoomMethodName } from '../errors/RoomExceptions.ts';\n\nimport { debugAndPrintError, debugMatchMaking } from '../Debug.ts';\n\nexport type Type<T> = new (...args: any[]) => T;\nexport type MethodName<T> = string & {\n [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never\n}[keyof T];\n\n/**\n * Utility type that extracts the return type of a method or the type of a property\n * from a given class/object type.\n *\n * - If the key is a method, returns the awaited return type of that method\n * - If the key is a property, returns the type of that property\n */\nexport type ExtractMethodOrPropertyType<\n TClass,\n TKey extends keyof TClass\n> = TClass[TKey] extends (...args: any[]) => infer R\n ? Awaited<R>\n : TClass[TKey];\n\n/**\n * Return type of `remoteRoomCall()`.\n *\n * Resolves to the method's awaited return type (or the property's type) when\n * the method name was captured as a literal type. Falls back to `any` when it\n * wasn't \u2014 e.g. `remoteRoomCall<MyRoom>(...)` with only the room type given:\n * TypeScript applies the `TMethod` default instead of inferring the literal\n * once an explicit type argument list is present (microsoft/TypeScript#26242).\n * Pass both type arguments (`remoteRoomCall<MyRoom, 'myMethod'>`) for a\n * precise return type.\n */\nexport type RemoteRoomCallReturn<\n TRoom,\n TMethod extends keyof TRoom\n> = keyof TRoom extends TMethod\n ? any\n : ExtractMethodOrPropertyType<TRoom, TMethod>;\n\n// remote room call timeouts\nexport const REMOTE_ROOM_SHORT_TIMEOUT = Number(process.env.COLYSEUS_PRESENCE_SHORT_TIMEOUT || 2000);\nexport const MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME = Number(process.env.COLYSEUS_MAX_CONCURRENT_CREATE_ROOM_WAIT_TIME || 0.5);\n\n/**\n * The well-known `Symbol.metadata` (TC39 decorator metadata), falling back to the\n * registered symbol on runtimes that don't expose it globally yet. `@colyseus/schema`\n * stores each class's field metadata under this key \u2014 read it via\n * `instance.constructor[$METADATA]`. Shared so every reader resolves the IDENTICAL\n * symbol (InputBuffer's field-name walk, Rewind's field-index lookup).\n */\nexport const $METADATA: symbol = (Symbol as { metadata?: symbol }).metadata ?? Symbol.for(\"Symbol.metadata\");\n\nexport function generateId(length: number = 9) {\n return nanoid(length);\n}\n\nexport function getBearerToken(authHeader: string) {\n return (authHeader && authHeader.startsWith(\"Bearer \") && authHeader.substring(7, authHeader.length)) || undefined;\n}\n\n// nodemon sends SIGUSR2 before reloading\n// (https://github.com/remy/nodemon#controlling-shutdown-of-your-script)\n//\nconst signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGUSR2'];\n\nexport function registerGracefulShutdown(callback: (err?: Error) => void) {\n /**\n * Gracefully shutdown on uncaught errors\n */\n process.on('uncaughtException', (err) => {\n debugAndPrintError(err);\n callback(err);\n });\n\n signals.forEach((signal) =>\n process.once(signal, () => callback()));\n}\n\nexport function retry<T = any>(\n cb: Function,\n maxRetries: number = 3,\n errorWhiteList: any[] = [],\n retries: number = 0,\n) {\n return new Promise<T>((resolve, reject) => {\n cb()\n .then(resolve)\n .catch((e: any) => {\n if (\n errorWhiteList.indexOf(e.constructor) !== -1 &&\n retries++ < maxRetries\n ) {\n setTimeout(() => {\n debugMatchMaking(\"retrying due to error (error: %s, retries: %s, maxRetries: %s)\", e.message, retries, maxRetries);\n retry<T>(cb, maxRetries, errorWhiteList, retries).\n then(resolve).\n catch((e2) => reject(e2));\n }, Math.floor(Math.random() * Math.pow(2, retries) * 400));\n\n } else {\n reject(e);\n }\n });\n });\n}\n\nexport function spliceOne(arr: any[], index: number): boolean {\n // manually splice availableRooms array\n // http://jsperf.com/manual-splice\n if (index === -1 || index >= arr.length) {\n return false;\n }\n\n const len = arr.length - 1;\n for (let i = index; i < len; i++) {\n arr[i] = arr[i + 1];\n }\n\n arr.length = len;\n return true;\n}\n\nexport class Deferred<T = any> {\n public promise: Promise<T>;\n\n public resolve: Function;\n public reject: Function;\n\n constructor(promise?: Promise<T>) {\n this.promise = promise ?? new Promise<T>((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n }\n\n public then(onFulfilled?: (value: T) => any, onRejected?: (reason: any) => any) {\n return this.promise.then(onFulfilled, onRejected);\n }\n\n public catch(func: (value: any) => any) {\n return this.promise.catch(func);\n }\n\n static reject (reason?: any) {\n return new Deferred(Promise.reject(reason));\n }\n\n static resolve<T = any>(value?: T) {\n return new Deferred<T>(Promise.resolve(value));\n }\n\n}\n\nexport function merge(a: any, ...objs: any[]): any {\n for (let i = 0, len = objs.length; i < len; i++) {\n const b = objs[i];\n for (const key in b) {\n if (b.hasOwnProperty(key)) {\n a[key] = b[key];\n }\n }\n }\n return a;\n}\n\nexport function wrapTryCatch(\n method: Function,\n onError: (error: RoomException, methodName: RoomMethodName) => void,\n exceptionClass: Type<RoomException>,\n methodName: RoomMethodName,\n rethrow: boolean = false,\n ...additionalErrorArgs: any[]\n) {\n return (...args: any[]) => {\n try {\n const result = method(...args);\n if (typeof (result?.catch) === \"function\") {\n return result.catch((e: Error) => {\n onError(new exceptionClass(e, e.message, ...args, ...additionalErrorArgs), methodName);\n if (rethrow) { throw e; }\n });\n }\n return result;\n } catch (e: any) {\n onError(new exceptionClass(e, e.message, ...args, ...additionalErrorArgs), methodName);\n if (rethrow) { throw e; }\n }\n };\n}\n\n/**\n * Dynamically import a module using either require() or import()\n * based on the current module system (CJS vs ESM).\n *\n * This avoids double-loading packages when running in mixed ESM/CJS environments.\n * Errors are silently caught - await the promise and handle errors at usage site.\n */\nexport function dynamicImport<T = any>(moduleName: string): Promise<T> {\n // __dirname exists in CJS but not in ESM\n if (\n typeof __dirname !== 'undefined' &&\n // @ts-ignore\n typeof (Bun) === 'undefined' // prevent bun from loading CJS modules\n ) {\n // CJS context - use require()\n try {\n return Promise.resolve(require(moduleName));\n } catch (e: any) {\n // If the error is not a MODULE_NOT_FOUND error, reject with the error.\n if (e.code !== 'MODULE_NOT_FOUND') {\n return Promise.reject(e);\n }\n return Promise.resolve(undefined);\n }\n } else {\n // ESM context - use import()\n const promise = import(/* @vite-ignore */ moduleName);\n promise.catch(() => {}); // prevent unhandled rejection warnings\n return promise;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ const __cjsImportMetaUrl = require('node:url').pathToFileURL(__filename).href;
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -29,7 +30,10 @@ var createNanoEvents = () => ({
29
30
  callbacks[i](...args);
30
31
  }
31
32
  },
32
- events: {},
33
+ // null-prototype: event names are client-supplied (message types), and on a
34
+ // plain object "__proto__" / "constructor" / "toString" resolve to inherited
35
+ // members instead of missing (colyseus/colyseus#951)
36
+ events: /* @__PURE__ */ Object.create(null),
33
37
  on(event, cb) {
34
38
  ;
35
39
  (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": ";;;;;;;;;;;;;;;;;;;;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.18.3",
3
+ "version": "0.18.5",
4
4
  "description": "Multiplayer Framework for Node.js.",
5
5
  "type": "module",
6
6
  "input": "./src/index.ts",
@@ -52,13 +52,14 @@
52
52
  "msgpackr": "^2.0.1",
53
53
  "nanoid": "^3.3.11",
54
54
  "@colyseus/better-call": "^1.3.1",
55
- "@colyseus/greeting-banner": "^4.0.1",
56
- "@colyseus/shared-types": "^0.18.1"
55
+ "@colyseus/shared-types": "^0.18.1",
56
+ "@colyseus/greeting-banner": "^4.0.1"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@colyseus/schema": "^5.0.8",
60
60
  "express": "^5.0.0",
61
- "@colyseus/redis-driver": "^0.18.1",
61
+ "vitest": "^3.1.1",
62
+ "@colyseus/redis-driver": "^0.18.2",
62
63
  "@colyseus/redis-presence": "^0.18.2",
63
64
  "@colyseus/tools": "^0.18.2"
64
65
  },
@@ -67,9 +68,9 @@
67
68
  "@pm2/io": "^6.1.0",
68
69
  "express": "^4.16.0 || ^5.0.0",
69
70
  "zod": "^4.1.12",
70
- "@colyseus/better-call": "^1.3.1",
71
71
  "@colyseus/ws-transport": "^0.18.1",
72
- "@colyseus/auth": "^0.18.1"
72
+ "@colyseus/auth": "^0.18.1",
73
+ "@colyseus/better-call": "^1.3.1"
73
74
  },
74
75
  "peerDependenciesMeta": {
75
76
  "@colyseus/auth": {
@@ -99,6 +100,6 @@
99
100
  },
100
101
  "gitHead": "c45b410e99eadffff4b74e701339992e2faa15f8",
101
102
  "scripts": {
102
- "test": "tsc -p test"
103
+ "test": "vitest run"
103
104
  }
104
105
  }
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, getRoomRestoreListKey, reloadFromCache } from './utils/DevMode.ts';
7
7
 
8
8
  import { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';
@@ -32,10 +32,21 @@ 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
 
40
+ // A room isn't gone when 'dispose' fires — that only *starts* `Room.#_dispose()`,
41
+ // which awaits the user's async `onDispose()`. `roomCount` is decremented there,
42
+ // so this covers the window until 'disconnect' (emitted once `#_dispose()`
43
+ // settles). Invariant: roomCount + disposingRoomCount = rooms not yet fully gone.
44
+ let disposingRoomCount = 0;
45
+
46
+ function hasActiveRooms() {
47
+ return stats.local.roomCount > 0 || disposingRoomCount > 0;
48
+ }
49
+
39
50
  export let publicAddress: string;
40
51
  export let processId: string;
41
52
  export let presence: Presence;
@@ -102,6 +113,7 @@ export async function setup(
102
113
  publicAddress = _publicAddress || getDefaultPublicAddress();
103
114
 
104
115
  stats.reset(false);
116
+ disposingRoomCount = 0;
105
117
 
106
118
  // ensure processId is set
107
119
  if (!processId) { processId = generateId(); }
@@ -337,7 +349,7 @@ export async function findRoomsByIds(roomIds: string[]): Promise<Map<string, IRo
337
349
  *
338
350
  * @param roomName - The Id of the specific room.
339
351
  * @param filterOptions - Filter options.
340
- * @param sortOptions - Sorting options.
352
+ * @param additionalSortOptions - Sorting options, merged over the ones declared on the room handler.
341
353
  *
342
354
  * @returns Promise<IRoomCache> - A promise contaning an object which includes room metadata and configurations.
343
355
  */
@@ -364,23 +376,43 @@ export async function findOneRoomAvailable(
364
376
  /**
365
377
  * Call a method or return a property on a remote room.
366
378
  *
379
+ * Provide both type arguments for a precisely typed result, e.g.
380
+ * `remoteRoomCall<MyRoom, 'myMethod'>(roomId, 'myMethod')`. With only the
381
+ * room type given, TypeScript cannot infer the method literal
382
+ * (microsoft/TypeScript#26242) and the result is typed `any`.
383
+ *
367
384
  * @param roomId - The Id of the specific room instance.
368
385
  * @param method - Method or attribute to call or retrive.
369
386
  * @param args - Array of arguments for the method
370
387
  *
371
- * @returns Promise<any> - Returned value from the called or retrieved method/attribute.
388
+ * @returns Promise - Returned value from the called or retrieved method/attribute.
372
389
  */
373
- export async function remoteRoomCall<TRoom = Room>(
390
+ export function remoteRoomCall<TRoom = Room, TMethod extends keyof TRoom = keyof TRoom>(
391
+ roomId: string,
392
+ method: TMethod,
393
+ args?: any[],
394
+ rejectionTimeout?: number,
395
+ ): Promise<RemoteRoomCallReturn<TRoom, TMethod>>;
396
+
397
+ // fallback for dynamic method names not present on the room type
398
+ export function remoteRoomCall<TRoom = Room>(
374
399
  roomId: string,
375
400
  method: keyof TRoom,
376
401
  args?: any[],
402
+ rejectionTimeout?: number,
403
+ ): Promise<any>;
404
+
405
+ export async function remoteRoomCall(
406
+ roomId: string,
407
+ method: any,
408
+ args?: any[],
377
409
  rejectionTimeout = REMOTE_ROOM_SHORT_TIMEOUT,
378
- ): Promise<ExtractMethodOrPropertyType<TRoom, typeof method>> {
379
- const room = rooms[roomId] as TRoom;
410
+ ): Promise<any> {
411
+ const room = rooms[roomId];
380
412
 
381
413
  if (!room) {
382
414
  try {
383
- return await requestFromIPC(presence, getRoomChannel(roomId), method as string, args, rejectionTimeout);
415
+ return await requestFromIPC(presence, getRoomChannel(roomId), method, args, rejectionTimeout);
384
416
 
385
417
  } catch (e: any) {
386
418
 
@@ -395,7 +427,7 @@ export async function remoteRoomCall<TRoom = Room>(
395
427
 
396
428
  // TODO: for 1.0, consider always throwing previous error directly.
397
429
 
398
- const request = `${String(method)}${args && ' with args ' + JSON.stringify(args) || ''}`;
430
+ const request = `${method}${args && ' with args ' + JSON.stringify(args) || ''}`;
399
431
  throw new ServerError(
400
432
  ErrorCode.MATCHMAKE_UNHANDLED,
401
433
  `remote room (${roomId}) timed out, requesting "${request}". (${rejectionTimeout}ms exceeded)`,
@@ -403,9 +435,9 @@ export async function remoteRoomCall<TRoom = Room>(
403
435
  }
404
436
 
405
437
  } else {
406
- return (!args && typeof (room[method]) !== 'function')
407
- ? room[method as string]
408
- : (await room[method as string].apply(room, args && JSON.parse(JSON.stringify(args))));
438
+ return (!args && typeof ((room as any)[method]) !== 'function')
439
+ ? (room as any)[method]
440
+ : (await (room as any)[method].apply(room, args && JSON.parse(JSON.stringify(args))));
409
441
  }
410
442
  }
411
443
 
@@ -627,11 +659,14 @@ export async function handleCreateRoom(roomName: string, clientOptions: ClientOp
627
659
  room['_events'].removeAllListeners('metadata-change');
628
660
  }
629
661
 
662
+ // this room's `onDispose()` has settled
663
+ disposingRoomCount--;
664
+
630
665
  //
631
666
  // emit "no active rooms" event when there are no more rooms in this process
632
667
  // (used during graceful shutdown)
633
668
  //
634
- if (stats.local.roomCount <= 0) {
669
+ if (!hasActiveRooms()) {
635
670
  events.emit('no-active-rooms');
636
671
  }
637
672
  });
@@ -692,7 +727,7 @@ async function lockAndDisposeAll(): Promise<any> {
692
727
  }
693
728
 
694
729
  const noActiveRooms = new Deferred();
695
- if (stats.local.roomCount <= 0) {
730
+ if (!hasActiveRooms()) {
696
731
  // no active rooms to dispose
697
732
  noActiveRooms.resolve();
698
733
 
@@ -799,7 +834,7 @@ export async function hotReload(): Promise<void> {
799
834
 
800
835
  // Lock all rooms and trigger default onBeforeShutdown (dev mode impl).
801
836
  const noActiveRooms = new Deferred();
802
- if (stats.local.roomCount <= 0) {
837
+ if (!hasActiveRooms()) {
803
838
  noActiveRooms.resolve();
804
839
  } else {
805
840
  events.once('no-active-rooms', () => noActiveRooms.resolve());
@@ -1184,6 +1219,9 @@ async function disposeRoom(roomName: string, room: Room) {
1184
1219
  driver.remove(room['_listing'].roomId);
1185
1220
  stats.local.roomCount--;
1186
1221
 
1222
+ // `onDispose()` is still pending — released on 'disconnect'
1223
+ disposingRoomCount++;
1224
+
1187
1225
  // decrease amount of rooms this process is handling
1188
1226
  if (state !== MatchMakerState.SHUTTING_DOWN) {
1189
1227
  stats.persist();