@colyseus/core 0.17.44 → 0.18.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.
Files changed (157) hide show
  1. package/build/MatchMaker.cjs +22 -13
  2. package/build/MatchMaker.cjs.map +2 -2
  3. package/build/MatchMaker.d.ts +10 -0
  4. package/build/MatchMaker.mjs +22 -14
  5. package/build/MatchMaker.mjs.map +2 -2
  6. package/build/Protocol.cjs +102 -37
  7. package/build/Protocol.cjs.map +2 -2
  8. package/build/Protocol.d.ts +33 -2
  9. package/build/Protocol.mjs +102 -37
  10. package/build/Protocol.mjs.map +2 -2
  11. package/build/Rewind.cjs +385 -0
  12. package/build/Rewind.cjs.map +7 -0
  13. package/build/Rewind.d.ts +291 -0
  14. package/build/Rewind.mjs +359 -0
  15. package/build/Rewind.mjs.map +7 -0
  16. package/build/Room.cjs +425 -149
  17. package/build/Room.cjs.map +3 -3
  18. package/build/Room.d.ts +314 -27
  19. package/build/Room.mjs +434 -152
  20. package/build/Room.mjs.map +3 -3
  21. package/build/RoomMessages.cjs +265 -0
  22. package/build/RoomMessages.cjs.map +7 -0
  23. package/build/RoomMessages.d.ts +49 -0
  24. package/build/RoomMessages.mjs +240 -0
  25. package/build/RoomMessages.mjs.map +7 -0
  26. package/build/RoomPlugin.cjs +252 -0
  27. package/build/RoomPlugin.cjs.map +7 -0
  28. package/build/RoomPlugin.d.ts +271 -0
  29. package/build/RoomPlugin.mjs +220 -0
  30. package/build/RoomPlugin.mjs.map +7 -0
  31. package/build/Server.cjs +49 -11
  32. package/build/Server.cjs.map +2 -2
  33. package/build/Server.d.ts +26 -0
  34. package/build/Server.mjs +50 -12
  35. package/build/Server.mjs.map +2 -2
  36. package/build/Transport.cjs +64 -2
  37. package/build/Transport.cjs.map +2 -2
  38. package/build/Transport.d.ts +108 -5
  39. package/build/Transport.mjs +63 -2
  40. package/build/Transport.mjs.map +2 -2
  41. package/build/errors/RoomExceptions.cjs +7 -4
  42. package/build/errors/RoomExceptions.cjs.map +2 -2
  43. package/build/errors/RoomExceptions.d.ts +15 -3
  44. package/build/errors/RoomExceptions.mjs +5 -3
  45. package/build/errors/RoomExceptions.mjs.map +2 -2
  46. package/build/index.cjs +30 -4
  47. package/build/index.cjs.map +2 -2
  48. package/build/index.d.ts +9 -5
  49. package/build/index.mjs +29 -6
  50. package/build/index.mjs.map +2 -2
  51. package/build/input/InputBuffer.cjs +536 -0
  52. package/build/input/InputBuffer.cjs.map +7 -0
  53. package/build/input/InputBuffer.d.ts +226 -0
  54. package/build/input/InputBuffer.mjs +507 -0
  55. package/build/input/InputBuffer.mjs.map +7 -0
  56. package/build/input/RoomInput.cjs +306 -0
  57. package/build/input/RoomInput.cjs.map +7 -0
  58. package/build/input/RoomInput.d.ts +91 -0
  59. package/build/input/RoomInput.mjs +287 -0
  60. package/build/input/RoomInput.mjs.map +7 -0
  61. package/build/input/types.cjs +18 -0
  62. package/build/input/types.cjs.map +7 -0
  63. package/build/input/types.d.ts +476 -0
  64. package/build/input/types.mjs +0 -0
  65. package/build/input/types.mjs.map +7 -0
  66. package/build/internal.cjs +61 -0
  67. package/build/internal.cjs.map +7 -0
  68. package/build/internal.d.ts +9 -0
  69. package/build/internal.mjs +29 -0
  70. package/build/internal.mjs.map +7 -0
  71. package/build/matchmaker/LocalDriver/LocalDriver.cjs +13 -0
  72. package/build/matchmaker/LocalDriver/LocalDriver.cjs.map +2 -2
  73. package/build/matchmaker/LocalDriver/LocalDriver.d.ts +1 -0
  74. package/build/matchmaker/LocalDriver/LocalDriver.mjs +13 -0
  75. package/build/matchmaker/LocalDriver/LocalDriver.mjs.map +2 -2
  76. package/build/matchmaker/driver.cjs.map +1 -1
  77. package/build/matchmaker/driver.d.ts +12 -0
  78. package/build/matchmaker/driver.mjs.map +1 -1
  79. package/build/presence/LocalPresence.d.ts +1 -1
  80. package/build/rooms/LobbyRoom.cjs +8 -10
  81. package/build/rooms/LobbyRoom.cjs.map +2 -2
  82. package/build/rooms/LobbyRoom.d.ts +4 -3
  83. package/build/rooms/LobbyRoom.mjs +8 -10
  84. package/build/rooms/LobbyRoom.mjs.map +2 -2
  85. package/build/rooms/QueueRoom.cjs +1 -1
  86. package/build/rooms/QueueRoom.cjs.map +2 -2
  87. package/build/rooms/QueueRoom.mjs +1 -1
  88. package/build/rooms/QueueRoom.mjs.map +2 -2
  89. package/build/rooms/RelayRoom.cjs +12 -16
  90. package/build/rooms/RelayRoom.cjs.map +2 -2
  91. package/build/rooms/RelayRoom.d.ts +32 -11
  92. package/build/rooms/RelayRoom.mjs +10 -16
  93. package/build/rooms/RelayRoom.mjs.map +2 -2
  94. package/build/router/default_routes.cjs +4 -1
  95. package/build/router/default_routes.cjs.map +2 -2
  96. package/build/router/default_routes.mjs +4 -1
  97. package/build/router/default_routes.mjs.map +2 -2
  98. package/build/router/index.cjs +71 -5
  99. package/build/router/index.cjs.map +2 -2
  100. package/build/router/index.d.ts +30 -6
  101. package/build/router/index.mjs +72 -7
  102. package/build/router/index.mjs.map +3 -3
  103. package/build/serializer/NoneSerializer.cjs +2 -2
  104. package/build/serializer/NoneSerializer.cjs.map +2 -2
  105. package/build/serializer/NoneSerializer.d.ts +3 -3
  106. package/build/serializer/NoneSerializer.mjs +2 -2
  107. package/build/serializer/NoneSerializer.mjs.map +2 -2
  108. package/build/serializer/SchemaSerializer.cjs +44 -14
  109. package/build/serializer/SchemaSerializer.cjs.map +2 -2
  110. package/build/serializer/SchemaSerializer.d.ts +3 -3
  111. package/build/serializer/SchemaSerializer.mjs +46 -16
  112. package/build/serializer/SchemaSerializer.mjs.map +2 -2
  113. package/build/serializer/Serializer.cjs.map +1 -1
  114. package/build/serializer/Serializer.d.ts +12 -2
  115. package/build/utils/DevMode.cjs +14 -14
  116. package/build/utils/DevMode.cjs.map +2 -2
  117. package/build/utils/DevMode.d.ts +0 -2
  118. package/build/utils/DevMode.mjs +14 -12
  119. package/build/utils/DevMode.mjs.map +2 -2
  120. package/build/utils/UserSessionIndex.cjs +162 -0
  121. package/build/utils/UserSessionIndex.cjs.map +7 -0
  122. package/build/utils/UserSessionIndex.d.ts +166 -0
  123. package/build/utils/UserSessionIndex.mjs +130 -0
  124. package/build/utils/UserSessionIndex.mjs.map +7 -0
  125. package/build/utils/Utils.cjs +3 -0
  126. package/build/utils/Utils.cjs.map +2 -2
  127. package/build/utils/Utils.d.ts +8 -0
  128. package/build/utils/Utils.mjs +2 -0
  129. package/build/utils/Utils.mjs.map +2 -2
  130. package/package.json +19 -14
  131. package/src/MatchMaker.ts +47 -15
  132. package/src/Protocol.ts +130 -59
  133. package/src/Rewind.ts +572 -0
  134. package/src/Room.ts +649 -235
  135. package/src/RoomMessages.ts +342 -0
  136. package/src/RoomPlugin.ts +563 -0
  137. package/src/Server.ts +82 -15
  138. package/src/Transport.ts +172 -9
  139. package/src/errors/RoomExceptions.ts +18 -4
  140. package/src/index.ts +23 -3
  141. package/src/input/InputBuffer.ts +549 -0
  142. package/src/input/RoomInput.ts +337 -0
  143. package/src/input/types.ts +503 -0
  144. package/src/internal.ts +46 -0
  145. package/src/matchmaker/LocalDriver/LocalDriver.ts +10 -0
  146. package/src/matchmaker/driver.ts +13 -0
  147. package/src/rooms/LobbyRoom.ts +12 -8
  148. package/src/rooms/QueueRoom.ts +1 -1
  149. package/src/rooms/RelayRoom.ts +9 -15
  150. package/src/router/default_routes.ts +6 -1
  151. package/src/router/index.ts +117 -11
  152. package/src/serializer/NoneSerializer.ts +3 -3
  153. package/src/serializer/SchemaSerializer.ts +110 -19
  154. package/src/serializer/Serializer.ts +13 -2
  155. package/src/utils/DevMode.ts +18 -13
  156. package/src/utils/UserSessionIndex.ts +311 -0
  157. package/src/utils/Utils.ts +9 -0
package/src/Server.ts CHANGED
@@ -14,7 +14,7 @@ import type { Presence } from "./presence/Presence.ts";
14
14
  import { setTransport, Transport } from './Transport.ts';
15
15
  import { logger, setLogger } from './Logger.ts';
16
16
  import { setDevMode, isDevMode } from './utils/DevMode.ts';
17
- import { type Router, bindRouterToTransport } from './router/index.ts';
17
+ import { type Router, bindRouterToTransport, createRouter } from './router/index.ts';
18
18
  import { prereadRequestBodies } from './router/node.ts';
19
19
  import { type SDKTypes as SharedSDKTypes } from '@colyseus/shared-types';
20
20
  import { getDefaultRouter } from './router/default_routes.ts';
@@ -33,6 +33,29 @@ export type ServerOptions = {
33
33
  */
34
34
  beforeListen?: () => Promise<void> | void,
35
35
 
36
+ /**
37
+ * Booted before `matchMaker.accept()`. Structural so `@colyseus/core` keeps
38
+ * no runtime dep on `@colyseus/database` — any `{ boot(): Promise<void> }`
39
+ * works. The optional `applyRouterDefaults` is invoked after boot with the
40
+ * user's router so the database can contribute endpoints (e.g. auth routes).
41
+ */
42
+ database?: {
43
+ boot(): Promise<void>;
44
+ applyRouterDefaults?(router: Router): Router | Promise<Router>;
45
+ },
46
+
47
+ /**
48
+ * Mount `@colyseus/auth` routes into the router. Pass an options object
49
+ * (forwarded to `auth.endpoints(...)`) to wire it explicitly — useful when
50
+ * `database` is not in use. `false` disables auto-mounting even if a
51
+ * `database` is present and would have provided defaults.
52
+ */
53
+ auth?: false | {
54
+ settings?: Record<string, any>;
55
+ oauth?: boolean | { cookieSecret?: string };
56
+ prefix?: string;
57
+ },
58
+
36
59
  /**
37
60
  * Optional callback to configure Express routes.
38
61
  * When provided, the transport layer will initialize an Express-compatible app
@@ -104,6 +127,12 @@ export class Server<
104
127
 
105
128
  private _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;
106
129
 
130
+ // Implicit default for callers that omit explicit Server reference — e.g.
131
+ // `playground()` reads `Server.current.router.endpoints` at request time.
132
+ // Last-construction wins; multi-server setups should reference instances
133
+ // explicitly.
134
+ static current: Server<any, any> | undefined;
135
+
107
136
  constructor(options: ServerOptions = {}) {
108
137
  const {
109
138
  gracefullyShutdown = true,
@@ -115,6 +144,8 @@ export class Server<
115
144
  this.options = options;
116
145
  this.greet = greet;
117
146
 
147
+ (Server as { current: Server<any, any> | undefined }).current = this as Server<any, any>;
148
+
118
149
  this.attach(options);
119
150
 
120
151
  // Pass options.presence/driver through as-is (possibly undefined).
@@ -141,14 +172,8 @@ export class Server<
141
172
 
142
173
  public async attach(options: ServerOptions) {
143
174
  this.transport = options.transport || await this.getDefaultTransport(options);
144
-
145
- // Initialize Express if callback is provided
146
- if (options.express && this.transport.getExpressApp) {
147
- const expressApp = await this.transport.getExpressApp();
148
- await options.express(expressApp);
149
- }
150
-
151
- // Resolve the promise when the transport is ready
175
+ // `options.express` runs in `listen()` after `database.boot()` so user
176
+ // code reading `database.auth.settings` etc. finds services instantiated.
152
177
  this._onTransportReady.resolve(this.transport);
153
178
  }
154
179
 
@@ -161,9 +186,7 @@ export class Server<
161
186
  * @param listeningListener
162
187
  */
163
188
  public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {
164
- if (this.options.beforeListen) {
165
- await this.options.beforeListen();
166
- }
189
+ await this._bootServices();
167
190
 
168
191
  //
169
192
  // if Colyseus Cloud is detected, use @colyseus/tools to listen
@@ -254,9 +277,7 @@ export class Server<
254
277
  * ```
255
278
  */
256
279
  public async serverless(): Promise<HttpServer> {
257
- if (this.options.beforeListen) {
258
- await this.options.beforeListen();
259
- }
280
+ await this._bootServices();
260
281
 
261
282
  // transport (and its HTTP server) must be ready before binding routes
262
283
  await this._onTransportReady;
@@ -419,6 +440,52 @@ export class Server<
419
440
  this.onBeforeShutdownCallback = callback;
420
441
  }
421
442
 
443
+ // Boot sequence shared by listen() and serverless(): user `beforeListen`
444
+ // hook, database boot, framework router defaults (auth/db endpoints), then
445
+ // the express app. serverless() needs this too — its exported server still
446
+ // serves the express callback and auth/database routes.
447
+ private async _bootServices(): Promise<void> {
448
+ const { beforeListen, database, express } = this.options;
449
+
450
+ if (beforeListen) { await beforeListen(); }
451
+ if (database) { await database.boot(); }
452
+
453
+ await this._applyRouterDefaults();
454
+
455
+ if (express) {
456
+ await this._onTransportReady;
457
+ if (this.transport.getExpressApp) {
458
+ const expressApp = await this.transport.getExpressApp();
459
+ await express(expressApp);
460
+ }
461
+ }
462
+ }
463
+
464
+ // Extend the user's router with framework-contributed endpoints. An explicit
465
+ // `auth` option wins over the database's `applyRouterDefaults` so a non-DB
466
+ // setup can still auto-mount @colyseus/auth, and a DB user can opt out.
467
+ private async _applyRouterDefaults(): Promise<void> {
468
+ const { auth, database } = this.options;
469
+ const wantsAuth = auth !== undefined && auth !== false;
470
+ const wantsDatabase = auth === undefined && !!database?.applyRouterDefaults;
471
+ if (!wantsAuth && !wantsDatabase) { return; }
472
+
473
+ // Boot an empty router if the user didn't pass `routes` — the framework's
474
+ // default routes get layered on later in the `transport.listen()` callback.
475
+ this.router ??= createRouter({}) as unknown as Routes;
476
+
477
+ if (wantsAuth) {
478
+ const authMod: any = await dynamicImport('@colyseus/auth').catch(() => undefined);
479
+ const endpointsFn = authMod?.auth?.endpoints ?? authMod?.default?.auth?.endpoints;
480
+ if (typeof endpointsFn === 'function') {
481
+ this.router = (this.router as Router).extend(endpointsFn(auth)) as Routes;
482
+ }
483
+ } else {
484
+ const updated = await database!.applyRouterDefaults!(this.router as Router);
485
+ if (updated) { this.router = updated as Routes; }
486
+ }
487
+ }
488
+
422
489
  protected async getDefaultTransport(options: any): Promise<Transport> {
423
490
  try {
424
491
  const module = await dynamicImport('@colyseus/ws-transport');
package/src/Transport.ts CHANGED
@@ -5,6 +5,7 @@ import type { Router } from '@colyseus/better-call';
5
5
 
6
6
  import { ErrorCode } from '@colyseus/shared-types';
7
7
  import { StateView } from '@colyseus/schema';
8
+ import type { InputDecoder } from '@colyseus/schema/input';
8
9
 
9
10
  import { EventEmitter } from 'events';
10
11
  import { spliceOne } from './utils/Utils.ts';
@@ -18,6 +19,11 @@ export function getTransport() { return _transport; }
18
19
 
19
20
  export abstract class Transport {
20
21
  public protocol?: string;
22
+ /** Self-signed cert SHA-256 hash (byte array), surfaced to clients in the
23
+ * matchmake response so a WebTransport client can pin it via
24
+ * `serverCertificateHashes`. Set by transports that generate their own cert
25
+ * (h3). Undefined for transports using a CA-trusted cert. */
26
+ public fingerprint?: number[];
21
27
  public server?: http.Server | https.Server;
22
28
 
23
29
  public abstract listen(port?: number | string, hostname?: string, backlog?: number, listeningListener?: Function): this;
@@ -90,15 +96,10 @@ export interface Client<T extends { userData?: any, auth?: any, messages?: Recor
90
96
 
91
97
  ref: EventEmitter;
92
98
 
93
- /**
94
- * @deprecated use `sessionId` instead.
95
- */
96
- id: string;
97
-
98
99
  /**
99
100
  * Unique id per session.
100
101
  */
101
- sessionId: string; // TODO: remove sessionId on version 1.0.0
102
+ sessionId: string;
102
103
 
103
104
  /**
104
105
  * Connection state
@@ -190,7 +191,6 @@ export interface Client<T extends { userData?: any, auth?: any, messages?: Recor
190
191
  export interface ClientPrivate {
191
192
  readyState: number; // TODO: remove readyState on version 1.0.0. Use only "state" instead.
192
193
  _enqueuedMessages?: any[];
193
- _afterNextPatchQueue: Array<[string | number | Client, ArrayLike<any>]>;
194
194
  _joinedAt: number; // "elapsedTime" when the client joined the room.
195
195
 
196
196
  /**
@@ -198,15 +198,178 @@ export interface ClientPrivate {
198
198
  */
199
199
  _numMessagesLastSecond?: number;
200
200
  _lastMessageTime?: number;
201
+
202
+ /**
203
+ * Per-client input Schema instance, allocated on join when the Room
204
+ * declares `input`. Mutated in-place by {@link _inputDecoder} on each
205
+ * incoming ROOM_INPUT_* packet.
206
+ *
207
+ * Typed loosely (`any`) so duplicate `@colyseus/schema` installs don't
208
+ * trigger type-identity errors against user-defined input classes.
209
+ */
210
+ _input?: any;
211
+ _inputDecoder?: InputDecoder;
212
+
213
+ /**
214
+ * Per-client buffer of cloned input snapshots, allocated on join when
215
+ * `Room.inputOptions.bufferMaxSize > 0`. Populated on each decoded frame.
216
+ */
217
+ _inputBuffer?: import('./input/InputBuffer.ts').InputBufferImpl;
218
+
219
+ /**
220
+ * Cached per-client accessor returned by `room.input(sessionId)`. Built
221
+ * once at join (when the Room called `defineInput()`), so the public API
222
+ * call is a Map lookup + property read with no per-call allocation.
223
+ */
224
+ _inputAccessor?: import('./input/types.ts').InputAccessor;
225
+
226
+ /**
227
+ * Used for rate limiting ROOM_INPUT_* packets via maxInputsPerSecond,
228
+ * independent of maxMessagesPerSecond.
229
+ */
230
+ _numInputsLastSecond?: number;
231
+ _lastInputTime?: number;
232
+
233
+ /**
234
+ * `performance.now()` recorded when the most recent ROOM_INPUT_* packet
235
+ * from this client was received. Drives the per-recipient `lastTReceived`
236
+ * field of the {@link ProtocolModifier.TIMED} state prefix.
237
+ *
238
+ * `0` until the client has sent its first input.
239
+ */
240
+ _lastInputReceivedAt?: number;
241
+
242
+ /**
243
+ * Monotonic count of *reliable* inputs successfully received from this
244
+ * client. Echoed back in the TIMED prefix as `lastInputSeq` so the
245
+ * client can correlate to its own send-time table and compute RTT.
246
+ * Stays at the default `0` until the client sends its first reliable
247
+ * input.
248
+ *
249
+ * Only ROOM_INPUT_RELIABLE bumps this — unreliable's redundant-ring
250
+ * pattern would double-count.
251
+ */
252
+ _receivedInputCount?: number;
253
+
254
+ /**
255
+ * Running baseline for the DELTA-CODED lag-comp stamp on ROOM_INPUT_RELIABLE
256
+ * frames (the {@link ProtocolModifier.TIMED} prefix). Each frame carries only
257
+ * the signed change from the previous stamp; this accumulates them back into
258
+ * the absolute timeline value. Zeroed on (re)connect alongside the SDK's own
259
+ * baseline so the first delta after a reset is absolute. `0` until allocated.
260
+ */
261
+ _reckonBaseline?: number;
262
+
263
+ /**
264
+ * @internal Per-client raw frames staged to ride out right AFTER this client's
265
+ * next state patch — per-client `afterNextPatch` messages. Lazily allocated.
266
+ * Pushed by {@link enqueueClientRaw} (the `afterNextPatch` path), flushed as
267
+ * standalone frames after the patch by {@link Room._flushPendingClientFrames}.
268
+ * Room-level `broadcast` `afterNextPatch` uses the Room's own queue instead, not
269
+ * this buffer.
270
+ */
271
+ _pendingFrames?: Uint8Array[];
272
+
273
+ /**
274
+ * @internal Back-reference to the Room's "clients with staged frames" list,
275
+ * shared by reference at join. {@link enqueueClientRaw} pushes the client here
276
+ * on its first staged frame of a cycle, so the after-patch flush iterates just
277
+ * those clients rather than scanning every client.
278
+ */
279
+ _pendingFrameClients?: Array<Client & ClientPrivate>;
280
+ }
281
+
282
+ /**
283
+ * The framework-level send path shared by every transport's `enqueueRaw` — the
284
+ * single source of truth so each transport implements only the wire-level `raw`.
285
+ * Routes a raw frame by where it should go:
286
+ *
287
+ * - `afterNextPatch` → stage onto the per-client {@link ClientPrivate._pendingFrames}
288
+ * buffer, sent as a standalone frame right AFTER the next state patch; a
289
+ * no-allocation push into a reused array. The client announces itself to the
290
+ * Room's {@link ClientPrivate._pendingFrameClients} list on its first staged
291
+ * frame of a cycle.
292
+ * - before JOIN → buffer in `_enqueuedMessages` until the JOIN_ROOM handshake flushes.
293
+ * - otherwise → send now via the transport's `raw`.
294
+ *
295
+ * @internal
296
+ */
297
+ export function enqueueClientRaw(
298
+ client: Client & ClientPrivate,
299
+ data: Uint8Array | Buffer,
300
+ options?: ISendOptions,
301
+ ): void {
302
+ if (options?.afterNextPatch) {
303
+ let frames = client._pendingFrames;
304
+ if (frames === undefined) { frames = client._pendingFrames = []; }
305
+ if (frames.length === 0) { client._pendingFrameClients?.push(client); } // first frame this cycle
306
+ frames.push(data);
307
+ return;
308
+ }
309
+ if (client.state !== ClientState.JOINED) {
310
+ // During `onJoin` / `onReconnect` the client can't register onMessage
311
+ // handlers yet — buffer until JOIN_ROOM has been sent.
312
+ client._enqueuedMessages?.push(data);
313
+ return;
314
+ }
315
+ client.raw(data, options);
201
316
  }
202
317
 
203
318
  export class ClientArray<C extends Client = Client> extends Array<C> {
319
+ /**
320
+ * Secondary index for O(1) lookup by sessionId. Kept in sync by the
321
+ * mutating methods overridden below. Direct index assignment
322
+ * (`arr[i] = client`) and `arr.length = 0` bypass this index — use
323
+ * `push` / `splice` / `delete` / `pop` / `shift` / `unshift` instead.
324
+ */
325
+ private _byId: Map<string, C> = new Map();
326
+
327
+ /** The client for `sessionId`, or `undefined` — O(1). The canonical per-session
328
+ * lookup (mirrors `room.inputs.get(sessionId)`). */
329
+ public get(sessionId: string): C | undefined {
330
+ return this._byId.get(sessionId);
331
+ }
332
+
333
+ /** @deprecated Use {@link get}. */
204
334
  public getById(sessionId: string): C | undefined {
205
- return this.find((client) => client.sessionId === sessionId);
335
+ return this._byId.get(sessionId);
206
336
  }
207
337
 
208
338
  public delete(client: C): boolean {
209
- return spliceOne(this, this.indexOf(client));
339
+ const removed = spliceOne(this, this.indexOf(client));
340
+ if (removed) this._byId.delete(client.sessionId);
341
+ return removed;
342
+ }
343
+
344
+ public push(...items: C[]): number {
345
+ for (let i = 0; i < items.length; i++) this._byId.set(items[i].sessionId, items[i]);
346
+ return super.push(...items);
347
+ }
348
+
349
+ public pop(): C | undefined {
350
+ const removed = super.pop();
351
+ if (removed !== undefined) this._byId.delete(removed.sessionId);
352
+ return removed;
353
+ }
354
+
355
+ public shift(): C | undefined {
356
+ const removed = super.shift();
357
+ if (removed !== undefined) this._byId.delete(removed.sessionId);
358
+ return removed;
359
+ }
360
+
361
+ public unshift(...items: C[]): number {
362
+ for (let i = 0; i < items.length; i++) this._byId.set(items[i].sessionId, items[i]);
363
+ return super.unshift(...items);
364
+ }
365
+
366
+ public splice(start: number, deleteCount?: number, ...items: C[]): C[] {
367
+ const removed = (deleteCount === undefined)
368
+ ? super.splice(start)
369
+ : super.splice(start, deleteCount, ...items);
370
+ for (let i = 0; i < removed.length; i++) this._byId.delete(removed[i].sessionId);
371
+ for (let i = 0; i < items.length; i++) this._byId.set(items[i].sessionId, items[i]);
372
+ return removed;
210
373
  }
211
374
  }
212
375
 
@@ -9,7 +9,8 @@ export type RoomMethodName = 'onCreate'
9
9
  | 'onReconnect'
10
10
  | 'onDispose'
11
11
  | 'onMessage'
12
- | 'setSimulationInterval'
12
+ | 'setTimestep'
13
+ | 'setFixedTimestep'
13
14
  | 'setInterval'
14
15
  | 'setTimeout';
15
16
 
@@ -22,7 +23,7 @@ export type RoomException<R extends Room = Room> =
22
23
  OnReconnectException<R> |
23
24
  OnDisposeException |
24
25
  OnMessageException<R> |
25
- SimulationIntervalException |
26
+ TimestepException |
26
27
  TimedEventException;
27
28
 
28
29
  export class OnCreateException<R extends Room = Room> extends Error {
@@ -151,16 +152,29 @@ export class OnMessageException<R extends Room, MessageType extends keyof R['mes
151
152
  }
152
153
  }
153
154
 
154
- export class SimulationIntervalException extends Error {
155
+ /** Thrown when a {@link Room.setTimestep} or {@link Room.setFixedTimestep} tick
156
+ * callback throws (or rejects). Reported to `onUncaughtException` with the
157
+ * originating method as `methodName` (`'setTimestep'` / `'setFixedTimestep'`). */
158
+ export class TimestepException extends Error {
155
159
  constructor(
156
160
  cause: Error,
157
161
  message: string,
158
162
  ) {
159
163
  super(message, { cause });
160
- this.name = 'SimulationIntervalException';
164
+ this.name = 'TimestepException';
161
165
  }
162
166
  }
163
167
 
168
+ /**
169
+ * @deprecated Renamed to {@link TimestepException} (it now covers both
170
+ * `setTimestep` and `setFixedTimestep`). Kept as an alias for backwards
171
+ * compatibility — `instanceof` checks against either name still match.
172
+ *
173
+ * TODO: remove this on 1.0
174
+ */
175
+ export const SimulationIntervalException = TimestepException;
176
+ export type SimulationIntervalException = TimestepException;
177
+
164
178
  export class TimedEventException extends Error {
165
179
  public args: any[];
166
180
  constructor(
package/src/index.ts CHANGED
@@ -3,16 +3,30 @@ import { ClockTimer as Clock, Delayed } from '@colyseus/timer';
3
3
  // Shared types - re-export from @colyseus/shared-types for convenience
4
4
  export {
5
5
  Protocol,
6
+ ProtocolModifier,
6
7
  ErrorCode,
7
8
  CloseCode,
9
+ ResponseStatus,
8
10
  type InferState,
9
11
  type ExtractRoomMessages,
10
12
  type ExtractRoomClientMessages,
13
+ type MessageContext,
14
+ type Rejection,
15
+ type Resolution,
16
+ type ExtractRejectReason,
17
+ type ExtractResponseType,
11
18
  } from '@colyseus/shared-types';
12
19
 
13
20
  // Core classes
14
21
  export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
15
- export { Room, room, RoomInternalState, validate, type RoomOptions, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';
22
+ export { Room, RoomInternalState, validate, type RoomOptions, type DefineInputOptions, type SimulationCallback, type FixedTimestepCallback, type StepContext, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';
23
+ export { InputBufferImpl, compileSanitizer } from './input/InputBuffer.ts';
24
+ export { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf } from './input/types.ts';
25
+ export { Rewind, RewindView, type RewindOptions, type RewindMode } from './Rewind.ts';
26
+ export {
27
+ RoomPlugin, definePlugins, attachToTestRoom,
28
+ type RoomPluginOrder, type PluginDependencies, type RoomPluginClass,
29
+ } from './RoomPlugin.ts';
16
30
  export { getMessageBytes } from './Protocol.ts';
17
31
  export { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';
18
32
  export { ServerError } from './errors/ServerError.ts';
@@ -26,6 +40,7 @@ export {
26
40
  OnLeaveException,
27
41
  OnDisposeException,
28
42
  OnMessageException,
43
+ TimestepException,
29
44
  SimulationIntervalException,
30
45
  TimedEventException,
31
46
  } from './errors/RoomExceptions.ts';
@@ -41,7 +56,7 @@ export * from './matchmaker/LocalDriver/LocalDriver.ts';
41
56
  export { initializeRoomCache } from './matchmaker/driver.ts';
42
57
 
43
58
  // Transport
44
- export { type Client, type ClientPrivate, type AuthContext, ClientState, ClientArray, Transport, type ISendOptions, connectClientToRoom } from './Transport.ts';
59
+ export { type Client, type ClientPrivate, type AuthContext, ClientState, ClientArray, Transport, type ISendOptions, connectClientToRoom, enqueueClientRaw } from './Transport.ts';
45
60
 
46
61
  // Presence
47
62
  export { type Presence } from './presence/Presence.ts';
@@ -82,8 +97,10 @@ export {
82
97
  createInternalContext,
83
98
  createMiddleware,
84
99
  createRouter,
100
+ basicAuth,
101
+ type BasicAuthOptions,
85
102
  toNodeHandler,
86
- __globalEndpoints,
103
+ dualModeEndpoints,
87
104
  type Router,
88
105
  type RouterConfig,
89
106
  type Endpoint,
@@ -91,6 +108,9 @@ export {
91
108
  type EndpointOptions,
92
109
  type EndpointContext,
93
110
  type StrictEndpoint,
111
+ type ExpressMiddleware,
112
+ type NodeHandler,
113
+ type DualModeHelpers,
94
114
  } from './router/index.ts';
95
115
 
96
116
  // Abstract logging support