@fluojs/socket.io 1.0.3 → 1.0.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.
package/README.ko.md CHANGED
@@ -103,11 +103,11 @@ SocketIoModule.forRoot({
103
103
  });
104
104
  ```
105
105
 
106
- `cors`를 생략하면 `@fluojs/socket.io`는 `{ credentials: false, origin: false }`를 기본값으로 사용하므로 cross-origin 노출은 명시적 opt-in이 필요합니다. `engine.maxHttpBufferSize`를 생략하면 어댑터가 1 MiB Engine.IO payload 상한을 적용합니다. 기본값에는 `buffer.maxPendingMessagesPerSocket: 128`, `buffer.overflowPolicy: 'drop-oldest'`, `shutdown.timeoutMs: 5000`도 포함됩니다.
106
+ `cors`를 생략하면 `@fluojs/socket.io`는 `{ credentials: false, origin: false }`를 기본값으로 사용하므로 cross-origin 노출은 명시적 opt-in이 필요합니다. `engine.maxHttpBufferSize`를 생략하면 어댑터가 1 MiB Engine.IO payload 상한을 적용합니다. 기본값에는 `buffer.maxPendingMessagesPerSocket: 128`, `buffer.overflowPolicy: 'drop-oldest'`, `shutdown.timeoutMs: 5000`도 포함됩니다. 명시적인 `engine.maxHttpBufferSize`, `buffer.maxPendingMessagesPerSocket`, `shutdown.timeoutMs` 값은 양의 정수여야 하며, 잘못된 명시 값은 기본값으로 fallback하지 않고 모듈 등록 중 실패합니다.
107
107
 
108
108
  정적 `@WebSocketGateway({ path })` namespace는 fluo gateway discovery가 소유하며 Socket.IO dynamic child namespace로 취급하지 않습니다. 어댑터는 이러한 정적 namespace에 대해 Socket.IO의 `cleanupEmptyChildNamespaces` 동작을 비활성화합니다. 애플리케이션 코드가 raw `SOCKETIO_SERVER` 접근으로 dynamic child namespace를 만들면 해당 소유권과 cleanup 정책은 애플리케이션 수준 Socket.IO 통합이 담당합니다.
109
109
 
110
- 애플리케이션 종료 중 Socket.IO client 정리는 Socket.IO가 소유하지만 underlying HTTP server는 이를 제공한 platform adapter 또는 shared HTTP server 통합이 계속 소유합니다. 어댑터는 `io.close(...)` 전에 해당 HTTP server 참조를 분리하므로 client cleanup은 실행되지만 Socket.IO가 adapter-owned/shared HTTP listener를 닫지는 않습니다. 동일한 managed Socket.IO instance 주변에 별도의 manual socket-disconnect 경로를 추가하지 마세요.
110
+ 애플리케이션 종료 중 Socket.IO client 정리는 Socket.IO가 소유하지만 underlying HTTP server는 이를 제공한 platform adapter 또는 shared HTTP server 통합이 계속 소유합니다. 어댑터는 `io.close(...)` 전에 해당 HTTP server 참조를 분리하므로 client cleanup은 실행되지만 Socket.IO가 adapter-owned/shared HTTP listener를 닫지는 않습니다. Graceful Socket.IO close가 `shutdown.timeoutMs`를 넘으면 어댑터는 lifecycle state를 정리하기 전에 managed Socket.IO client를 force-disconnect합니다. 해당 force cleanup도 실패하면 shutdown retry를 위해 managed server reference와 registry를 보존합니다. 동일한 managed Socket.IO instance 주변에 별도의 manual socket-disconnect 경로를 추가하지 마세요.
111
111
 
112
112
  ### Guard 계약
113
113
 
@@ -130,7 +130,7 @@ Socket.IO 등록은 소유 모듈의 import 경로에서 구성하여 namespace/
130
130
  - `SOCKETIO_ROOM_SERVICE`
131
131
  - `SocketIoRoomService`: 공유 room 계약에 Socket.IO namespace-aware `joinRoom`, `leaveRoom`, `broadcastToRoom`, `getRooms` helper를 더한 타입입니다.
132
132
  - `SocketIoLifecycleService`: server와 room-service token 뒤에서 동작하는 lifecycle 기반 구현입니다. 애플리케이션 코드는 일반적으로 `SOCKETIO_SERVER` 또는 `SOCKETIO_ROOM_SERVICE`를 주입하세요.
133
- - 타입: `SocketIoModuleOptions`, `SocketIoConnectionGuardContext`, `SocketIoConnectionGuard`, `SocketIoMessageGuardContext`, `SocketIoMessageGuard`, `SocketIoGuardRejection`.
133
+ - 타입: `SocketIoModuleOptions`, `SocketIoHandshakeRequest`, `SocketIoConnectionGuardContext`, `SocketIoConnectionGuard`, `SocketIoMessageGuardContext`, `SocketIoMessageGuard`, `SocketIoGuardRejection`.
134
134
 
135
135
  `SocketIoModuleOptions`는 `global`, `auth`, `buffer`, `cors`, `engine`, `shutdown`, `transports`를 포함합니다. `global`의 기본값은 `true`이므로 `SOCKETIO_SERVER`와 `SOCKETIO_ROOM_SERVICE`가 앱 전체에서 보입니다. module-local provider visibility가 필요하면 `false`로 설정하세요. 지원되는 server-backed runtime adapter가 필요하며, unsupported/noop adapter는 bootstrap 중 빠르게 실패합니다.
136
136
 
package/README.md CHANGED
@@ -105,11 +105,11 @@ SocketIoModule.forRoot({
105
105
  });
106
106
  ```
107
107
 
108
- When `cors` is omitted, `@fluojs/socket.io` defaults to `{ credentials: false, origin: false }` so cross-origin exposure stays opt-in. When `engine.maxHttpBufferSize` is omitted, the adapter applies a bounded 1 MiB Engine.IO payload limit. Defaults also include `buffer.maxPendingMessagesPerSocket: 128`, `buffer.overflowPolicy: 'drop-oldest'`, and `shutdown.timeoutMs: 5000`.
108
+ When `cors` is omitted, `@fluojs/socket.io` defaults to `{ credentials: false, origin: false }` so cross-origin exposure stays opt-in. When `engine.maxHttpBufferSize` is omitted, the adapter applies a bounded 1 MiB Engine.IO payload limit. Defaults also include `buffer.maxPendingMessagesPerSocket: 128`, `buffer.overflowPolicy: 'drop-oldest'`, and `shutdown.timeoutMs: 5000`. Explicit `engine.maxHttpBufferSize`, `buffer.maxPendingMessagesPerSocket`, and `shutdown.timeoutMs` values must be positive integers; invalid explicit values fail during module registration instead of falling back to defaults.
109
109
 
110
110
  Static `@WebSocketGateway({ path })` namespaces are owned by fluo's gateway discovery and are not treated as Socket.IO dynamic child namespaces. The adapter keeps Socket.IO's `cleanupEmptyChildNamespaces` behavior disabled for those static namespaces. If application code creates dynamic child namespaces through raw `SOCKETIO_SERVER` access, that ownership and cleanup policy stays with the application-level Socket.IO integration.
111
111
 
112
- During application shutdown, Socket.IO owns cleanup for connected Socket.IO clients, but the underlying HTTP server remains owned by the platform adapter or shared HTTP server integration that supplied it. The adapter detaches that HTTP server reference before `io.close(...)`, so client cleanup still runs while Socket.IO does not close adapter-owned/shared HTTP listeners. Do not add a second manual socket-disconnect path around the same managed Socket.IO instance.
112
+ During application shutdown, Socket.IO owns cleanup for connected Socket.IO clients, but the underlying HTTP server remains owned by the platform adapter or shared HTTP server integration that supplied it. The adapter detaches that HTTP server reference before `io.close(...)`, so client cleanup still runs while Socket.IO does not close adapter-owned/shared HTTP listeners. If graceful Socket.IO close exceeds `shutdown.timeoutMs`, the adapter force-disconnects managed Socket.IO clients before clearing lifecycle state; if that force cleanup fails, the managed server reference and registries are retained for shutdown retry. Do not add a second manual socket-disconnect path around the same managed Socket.IO instance.
113
113
 
114
114
  ### Guard contracts
115
115
 
@@ -132,7 +132,7 @@ Register Socket.IO through module imports in the owning module so namespace/mess
132
132
  - `SOCKETIO_ROOM_SERVICE`: Token to inject the `SocketIoRoomService`.
133
133
  - `SocketIoRoomService`: Shared room contract plus Socket.IO namespace-aware `joinRoom`, `leaveRoom`, `broadcastToRoom`, and `getRooms` helpers.
134
134
  - `SocketIoLifecycleService`: Lifecycle-backed implementation behind the server and room-service tokens; application code should usually inject `SOCKETIO_SERVER` or `SOCKETIO_ROOM_SERVICE` instead.
135
- - Types: `SocketIoModuleOptions`, `SocketIoConnectionGuardContext`, `SocketIoConnectionGuard`, `SocketIoMessageGuardContext`, `SocketIoMessageGuard`, `SocketIoGuardRejection`.
135
+ - Types: `SocketIoModuleOptions`, `SocketIoHandshakeRequest`, `SocketIoConnectionGuardContext`, `SocketIoConnectionGuard`, `SocketIoMessageGuardContext`, `SocketIoMessageGuard`, `SocketIoGuardRejection`.
136
136
 
137
137
  `SocketIoModuleOptions` covers `global`, `auth`, `buffer`, `cors`, `engine`, `shutdown`, and `transports`. `global` defaults to `true`, which keeps `SOCKETIO_SERVER` and `SOCKETIO_ROOM_SERVICE` visible across the app; set it to `false` when you want module-local provider visibility. A supported server-backed runtime adapter is required; unsupported/noop adapters fail fast during bootstrap.
138
138
 
package/dist/adapter.d.ts CHANGED
@@ -22,6 +22,7 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
22
22
  private readonly namespaceContext;
23
23
  private readonly socketRegistry;
24
24
  private shutdownPromise;
25
+ private shutdownStarted;
25
26
  private wired;
26
27
  constructor(runtimeContainer: Container, compiledModules: readonly CompiledModule[], logger: ApplicationLogger, adapter: HttpApplicationAdapter, moduleOptions: SocketIoModuleOptions);
27
28
  /**
@@ -31,6 +32,7 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
31
32
  * @throws {Error} When the selected realtime capability cannot expose the server contract Socket.IO requires.
32
33
  */
33
34
  getServer(): Server;
35
+ getServerAsync(): Promise<Server>;
34
36
  /**
35
37
  * Discovers gateway classes and binds their handlers to the resolved namespaces once per application lifecycle.
36
38
  */
@@ -76,6 +78,7 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
76
78
  */
77
79
  getRooms(socketId: string): ReadonlySet<string>;
78
80
  private createServerOptions;
81
+ private createNodeServer;
79
82
  private createBunEngineOptions;
80
83
  private resolveCorsOptions;
81
84
  private resolveMaxHttpBufferSize;
@@ -116,5 +119,6 @@ export declare class SocketIoLifecycleService implements OnApplicationBootstrap,
116
119
  private shutdown;
117
120
  private runShutdownLifecycle;
118
121
  private closeServerWithTimeout;
122
+ private clearManagedState;
119
123
  }
120
124
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAiC,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EAChB,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAAkB,MAAM,EAAmC,MAAM,WAAW,CAAC;AAGpF,OAAO,KAAK,EAGV,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AA+SpB;;;;;;GAMG;AACH,qBACa,wBACX,YAAW,sBAAsB,EAAE,qBAAqB,EAAE,eAAe,EAAE,mBAAmB;IAY5F,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAdhC,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,SAAS,CAA8B;IAC/C,OAAO,CAAC,EAAE,CAAqB;IAC/B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmC;IACpE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,KAAK,CAAS;gBAGH,gBAAgB,EAAE,SAAS,EAC3B,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,sBAAsB,EAC/B,aAAa,EAAE,qBAAqB;IAGvD;;;;;OAKG;IACH,SAAS,IAAI,MAAM;IAmBb,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAqBvC;;OAEG;IACG,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB7C;;OAEG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI;IAWtE;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI;IAWvE;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI;IAIzF;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;IAU/C,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,wBAAwB;YAIlB,yBAAyB;IAYvC,OAAO,CAAC,wBAAwB;IAyBhC,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,yBAAyB;YAInB,0CAA0C;IAQxD,OAAO,CAAC,gCAAgC;IAkBxC,OAAO,CAAC,2BAA2B;IAqBnC,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,wBAAwB;IAUhC,OAAO,CAAC,0BAA0B;IAwBlC,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,qBAAqB;YAgBf,kBAAkB;YAsBlB,sBAAsB;IAYpC,OAAO,CAAC,4BAA4B;IAQpC,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,yBAAyB;YAqEnB,8BAA8B;YAyB9B,yBAAyB;YAgBzB,kBAAkB;YAUlB,aAAa;YAyCb,4BAA4B;IA2B1C,OAAO,CAAC,qBAAqB;IAuB7B,OAAO,CAAC,qBAAqB;YAUf,gBAAgB;YAWhB,WAAW;YAaX,mBAAmB;YA8BnB,sBAAsB;IAapC,OAAO,CAAC,0BAA0B;IAsBlC,OAAO,CAAC,0BAA0B;IAYlC,OAAO,CAAC,uBAAuB;IAyB/B,OAAO,CAAC,mBAAmB;IAsC3B,OAAO,CAAC,wBAAwB;YAIlB,QAAQ;YAeR,oBAAoB;IAmClC,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,iBAAiB;CAU1B"}
package/dist/adapter.js CHANGED
@@ -7,17 +7,14 @@ function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side
7
7
  import { AsyncLocalStorage } from 'node:async_hooks';
8
8
  import { Inject } from '@fluojs/core';
9
9
  import { getClassDiMetadata } from '@fluojs/core/internal';
10
- import { Server as BunEngineServer } from '@socket.io/bun-engine';
11
10
  import { APPLICATION_LOGGER, COMPILED_MODULES, HTTP_APPLICATION_ADAPTER, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
12
11
  import { getWebSocketGatewayMetadata, getWebSocketHandlerMetadataEntries } from '@fluojs/websockets';
13
12
  import { Server } from 'socket.io';
14
13
  import { SOCKETIO_OPTIONS_INTERNAL } from './options-token.internal.js';
15
- const DEFAULT_SOCKETIO_SHUTDOWN_TIMEOUT_MS = 5_000;
16
- const DEFAULT_MAX_PENDING_MESSAGES_PER_SOCKET = 128;
17
- const DEFAULT_SOCKETIO_ENGINE_PATH = '/socket.io/';
18
- const DEFAULT_SOCKETIO_MAX_HTTP_BUFFER_SIZE = 1_048_576;
19
- function isFinitePositiveInteger(value) {
20
- return typeof value === 'number' && Number.isInteger(value) && Number.isFinite(value) && value > 0;
14
+ import { DEFAULT_SOCKETIO_ENGINE_PATH, resolveSocketIoMaxHttpBufferSize, resolveSocketIoMaxPendingMessagesPerSocket, resolveSocketIoShutdownTimeoutMs } from './config.internal.js';
15
+ import { closeSocketIoServerWithTimeout } from './shutdown.internal.js';
16
+ async function loadBunEngineServer() {
17
+ return (await import('@socket.io/bun-engine')).Server;
21
18
  }
22
19
  function normalizeGuardRejection(result, defaultMessage) {
23
20
  if (result === undefined || result === true) {
@@ -170,6 +167,7 @@ class SocketIoLifecycleService {
170
167
  namespaceContext = new AsyncLocalStorage();
171
168
  socketRegistry = new Map();
172
169
  shutdownPromise;
170
+ shutdownStarted = false;
173
171
  wired = false;
174
172
  constructor(runtimeContainer, compiledModules, logger, adapter, moduleOptions) {
175
173
  this.runtimeContainer = runtimeContainer;
@@ -186,20 +184,33 @@ class SocketIoLifecycleService {
186
184
  * @throws {Error} When the selected realtime capability cannot expose the server contract Socket.IO requires.
187
185
  */
188
186
  getServer() {
187
+ if (this.shutdownStarted) {
188
+ throw new Error('Socket.IO server access is unavailable after application shutdown has started.');
189
+ }
189
190
  if (this.io) {
190
191
  return this.io;
191
192
  }
192
193
  const runtime = resolveSocketIoBootstrapRuntime(this.adapter);
193
194
  if (runtime.kind === 'server-backed') {
194
- const httpServer = runtime.capability.server;
195
- if (!isNodeHttpServerLike(httpServer)) {
196
- throw new Error('Socket.IO bootstrap requires the selected realtime capability to expose a Node HTTP/S server instance.');
197
- }
198
- this.io = new Server(httpServer, this.createServerOptions());
195
+ this.io = this.createNodeServer(runtime);
196
+ return this.io;
197
+ }
198
+ throw new Error('Socket.IO Bun engine bootstrap is asynchronous. Use getServerAsync() for Bun-style adapters.');
199
+ }
200
+ async getServerAsync() {
201
+ if (this.shutdownStarted) {
202
+ throw new Error('Socket.IO server access is unavailable after application shutdown has started.');
203
+ }
204
+ if (this.io) {
205
+ return this.io;
206
+ }
207
+ const runtime = resolveSocketIoBootstrapRuntime(this.adapter);
208
+ if (runtime.kind === 'server-backed') {
209
+ this.io = this.createNodeServer(runtime);
199
210
  return this.io;
200
211
  }
201
212
  this.io = new Server(this.createServerOptions());
202
- this.installBunSocketIoBinding(runtime, this.io);
213
+ await this.installBunSocketIoBinding(runtime, this.io);
203
214
  return this.io;
204
215
  }
205
216
 
@@ -212,11 +223,11 @@ class SocketIoLifecycleService {
212
223
  }
213
224
  const descriptors = this.discoverGatewayDescriptors();
214
225
  if (descriptors.length === 0) {
215
- this.ensureBunRealtimeBindingForRawServerAccess();
226
+ await this.ensureBunRealtimeBindingForRawServerAccess();
216
227
  return;
217
228
  }
218
229
  this.assertNoServerBackedGatewayOptIn(descriptors);
219
- const io = this.getServer();
230
+ const io = await this.getServerAsync();
220
231
  const attachments = this.prepareNamespaceAttachments(io, descriptors);
221
232
  for (const attachment of attachments) {
222
233
  this.bindNamespaceHandlers(attachment);
@@ -313,6 +324,13 @@ class SocketIoLifecycleService {
313
324
  }
314
325
  return options;
315
326
  }
327
+ createNodeServer(runtime) {
328
+ const httpServer = runtime.capability.server;
329
+ if (!isNodeHttpServerLike(httpServer)) {
330
+ throw new Error('Socket.IO bootstrap requires the selected realtime capability to expose a Node HTTP/S server instance.');
331
+ }
332
+ return new Server(httpServer, this.createServerOptions());
333
+ }
316
334
  createBunEngineOptions() {
317
335
  const options = {
318
336
  path: DEFAULT_SOCKETIO_ENGINE_PATH
@@ -328,16 +346,13 @@ class SocketIoLifecycleService {
328
346
  };
329
347
  }
330
348
  resolveMaxHttpBufferSize() {
331
- const configured = this.moduleOptions.engine?.maxHttpBufferSize;
332
- if (!isFinitePositiveInteger(configured)) {
333
- return DEFAULT_SOCKETIO_MAX_HTTP_BUFFER_SIZE;
334
- }
335
- return configured;
349
+ return resolveSocketIoMaxHttpBufferSize(this.moduleOptions);
336
350
  }
337
- installBunSocketIoBinding(runtime, io) {
351
+ async installBunSocketIoBinding(runtime, io) {
338
352
  if (this.bunEngine) {
339
353
  return;
340
354
  }
355
+ const BunEngineServer = await loadBunEngineServer();
341
356
  const engine = new BunEngineServer(this.createBunEngineOptions());
342
357
  io.bind(engine);
343
358
  runtime.bindingHost.configureRealtimeBinding(this.createBunSocketIoBinding(engine));
@@ -374,11 +389,11 @@ class SocketIoLifecycleService {
374
389
  matchesSocketIoEnginePath(pathname) {
375
390
  return pathname === '/socket.io' || pathname === DEFAULT_SOCKETIO_ENGINE_PATH;
376
391
  }
377
- ensureBunRealtimeBindingForRawServerAccess() {
392
+ async ensureBunRealtimeBindingForRawServerAccess() {
378
393
  if (!hasBunRealtimeBindingHost(this.adapter)) {
379
394
  return;
380
395
  }
381
- this.getServer();
396
+ await this.getServerAsync();
382
397
  }
383
398
  assertNoServerBackedGatewayOptIn(descriptors) {
384
399
  const runtime = resolveSocketIoBootstrapRuntime(this.adapter);
@@ -502,7 +517,7 @@ class SocketIoLifecycleService {
502
517
  };
503
518
  }
504
519
  maxPendingMessagesPerSocket() {
505
- return isFinitePositiveInteger(this.moduleOptions.buffer?.maxPendingMessagesPerSocket) ? this.moduleOptions.buffer.maxPendingMessagesPerSocket : DEFAULT_MAX_PENDING_MESSAGES_PER_SOCKET;
520
+ return resolveSocketIoMaxPendingMessagesPerSocket(this.moduleOptions);
506
521
  }
507
522
  attachConnectionListeners(state, resolved, socket, request) {
508
523
  socket.onAny((event, ...args) => {
@@ -706,13 +721,19 @@ class SocketIoLifecycleService {
706
721
  createGatewayDescriptor(candidate, path) {
707
722
  const metadata = getWebSocketGatewayMetadata(candidate.targetType);
708
723
  const entries = getWebSocketHandlerMetadataEntries(candidate.targetType.prototype);
724
+ const handlers = entries.map(entry => ({
725
+ event: entry.metadata.event,
726
+ methodKey: entry.propertyKey,
727
+ methodName: methodKeyToName(entry.propertyKey),
728
+ type: entry.metadata.type
729
+ }));
730
+ const handlerIndex = createGatewayHandlerIndex(handlers);
709
731
  return {
710
- handlers: entries.map(entry => ({
711
- event: entry.metadata.event,
712
- methodKey: entry.propertyKey,
713
- methodName: methodKeyToName(entry.propertyKey),
714
- type: entry.metadata.type
715
- })),
732
+ connectHandlers: handlerIndex.connectHandlers,
733
+ disconnectHandlers: handlerIndex.disconnectHandlers,
734
+ handlers,
735
+ messageHandlersByEvent: handlerIndex.messageHandlersByEvent,
736
+ wildcardMessageHandlers: handlerIndex.wildcardMessageHandlers,
716
737
  moduleName: candidate.moduleName,
717
738
  path: normalizeGatewayPath(path),
718
739
  serverBacked: metadata?.serverBacked,
@@ -754,67 +775,87 @@ class SocketIoLifecycleService {
754
775
  return candidates;
755
776
  }
756
777
  resolveShutdownTimeoutMs() {
757
- const configured = this.moduleOptions.shutdown?.timeoutMs;
758
- if (typeof configured !== 'number' || !Number.isFinite(configured) || configured <= 0) {
759
- return DEFAULT_SOCKETIO_SHUTDOWN_TIMEOUT_MS;
760
- }
761
- return Math.floor(configured);
778
+ return resolveSocketIoShutdownTimeoutMs(this.moduleOptions);
762
779
  }
763
780
  async shutdown() {
764
781
  if (this.shutdownPromise) {
765
782
  await this.shutdownPromise;
766
783
  return;
767
784
  }
785
+ this.shutdownStarted = true;
768
786
  this.shutdownPromise = this.runShutdownLifecycle();
769
- await this.shutdownPromise;
787
+ try {
788
+ await this.shutdownPromise;
789
+ } finally {
790
+ this.shutdownPromise = undefined;
791
+ }
770
792
  }
771
793
  async runShutdownLifecycle() {
772
794
  const io = this.io;
773
- this.wired = false;
774
795
  if (!io) {
775
- this.attachments = [];
776
- this.socketRegistry.clear();
796
+ this.clearManagedState();
777
797
  return;
778
798
  }
799
+ let shouldClearManagedState = false;
800
+ const timeoutMs = this.resolveShutdownTimeoutMs();
779
801
  try {
780
- await this.closeServerWithTimeout(io, this.resolveShutdownTimeoutMs());
802
+ const result = await this.closeServerWithTimeout(io, timeoutMs);
803
+ shouldClearManagedState = true;
804
+ if (result.kind === 'forced') {
805
+ this.logger.error(`Failed to close Socket.IO server within ${String(timeoutMs)}ms; force-disconnected managed Socket.IO clients before clearing lifecycle state.`, result.timeoutError, 'SocketIoLifecycleService');
806
+ }
781
807
  } catch (error) {
782
- this.logger.error(`Failed to close Socket.IO server within ${String(this.resolveShutdownTimeoutMs())}ms.`, error, 'SocketIoLifecycleService');
808
+ this.logger.error(`Failed to close Socket.IO server within ${String(timeoutMs)}ms; retaining managed Socket.IO state for shutdown retry.`, error, 'SocketIoLifecycleService');
783
809
  } finally {
784
- this.io = undefined;
785
- this.bunEngine = undefined;
786
- this.attachments = [];
787
- if (hasBunRealtimeBindingHost(this.adapter)) {
788
- this.adapter.configureRealtimeBinding(undefined);
810
+ if (shouldClearManagedState) {
811
+ this.clearManagedState();
789
812
  }
790
- this.socketRegistry.clear();
791
813
  }
792
814
  }
793
815
  closeServerWithTimeout(io, timeoutMs) {
794
- return new Promise((resolve, reject) => {
795
- let settled = false;
796
- const timeout = setTimeout(() => {
797
- if (settled) {
798
- return;
799
- }
800
- settled = true;
801
- reject(new Error(`Timed out while closing Socket.IO server after ${String(timeoutMs)}ms.`));
802
- }, timeoutMs);
803
-
804
- // Socket.IO owns client cleanup; the shared HTTP server remains owned by the platform adapter.
805
- io.httpServer = undefined;
806
- io.close(() => {
807
- if (settled) {
808
- return;
809
- }
810
- settled = true;
811
- clearTimeout(timeout);
812
- resolve();
813
- });
814
- });
816
+ return closeSocketIoServerWithTimeout(io, timeoutMs);
817
+ }
818
+ clearManagedState() {
819
+ this.wired = false;
820
+ this.io = undefined;
821
+ this.bunEngine = undefined;
822
+ this.attachments = [];
823
+ if (hasBunRealtimeBindingHost(this.adapter)) {
824
+ this.adapter.configureRealtimeBinding(undefined);
825
+ }
826
+ this.socketRegistry.clear();
815
827
  }
816
828
  static {
817
829
  _initClass();
818
830
  }
819
831
  }
820
- export { _SocketIoLifecycleSer as SocketIoLifecycleService };
832
+ export { _SocketIoLifecycleSer as SocketIoLifecycleService };
833
+ function createGatewayHandlerIndex(handlers) {
834
+ const connectHandlers = [];
835
+ const disconnectHandlers = [];
836
+ const messageHandlersByEvent = new Map();
837
+ const wildcardMessageHandlers = [];
838
+ for (const handler of handlers) {
839
+ if (handler.type === 'connect') {
840
+ connectHandlers.push(handler);
841
+ continue;
842
+ }
843
+ if (handler.type === 'disconnect') {
844
+ disconnectHandlers.push(handler);
845
+ continue;
846
+ }
847
+ if (handler.event === undefined) {
848
+ wildcardMessageHandlers.push(handler);
849
+ continue;
850
+ }
851
+ const eventHandlers = messageHandlersByEvent.get(handler.event) ?? [];
852
+ eventHandlers.push(handler);
853
+ messageHandlersByEvent.set(handler.event, eventHandlers);
854
+ }
855
+ return {
856
+ connectHandlers,
857
+ disconnectHandlers,
858
+ messageHandlersByEvent,
859
+ wildcardMessageHandlers
860
+ };
861
+ }
@@ -0,0 +1,37 @@
1
+ import type { SocketIoModuleOptions } from './types.js';
2
+ /** Default number of pre-connect Socket.IO messages buffered per socket. */
3
+ export declare const DEFAULT_MAX_PENDING_MESSAGES_PER_SOCKET = 128;
4
+ /** Default Engine.IO request path used by Socket.IO transports. */
5
+ export declare const DEFAULT_SOCKETIO_ENGINE_PATH = "/socket.io/";
6
+ /** Default maximum inbound Engine.IO payload size in bytes. */
7
+ export declare const DEFAULT_SOCKETIO_MAX_HTTP_BUFFER_SIZE = 1048576;
8
+ /** Default graceful Socket.IO shutdown timeout in milliseconds. */
9
+ export declare const DEFAULT_SOCKETIO_SHUTDOWN_TIMEOUT_MS = 5000;
10
+ /**
11
+ * Assert that explicit Socket.IO numeric options are positive integers.
12
+ *
13
+ * @param options Module options supplied to `SocketIoModule.forRoot(...)`.
14
+ */
15
+ export declare function assertValidSocketIoModuleOptions(options: SocketIoModuleOptions): void;
16
+ /**
17
+ * Resolve the effective Socket.IO Engine.IO payload bound.
18
+ *
19
+ * @param options Module options supplied to `SocketIoModule.forRoot(...)`.
20
+ * @returns The explicit payload bound or the package default.
21
+ */
22
+ export declare function resolveSocketIoMaxHttpBufferSize(options: SocketIoModuleOptions): number;
23
+ /**
24
+ * Resolve the effective pending-message buffer limit.
25
+ *
26
+ * @param options Module options supplied to `SocketIoModule.forRoot(...)`.
27
+ * @returns The explicit per-socket buffer limit or the package default.
28
+ */
29
+ export declare function resolveSocketIoMaxPendingMessagesPerSocket(options: SocketIoModuleOptions): number;
30
+ /**
31
+ * Resolve the effective Socket.IO shutdown timeout.
32
+ *
33
+ * @param options Module options supplied to `SocketIoModule.forRoot(...)`.
34
+ * @returns The explicit shutdown timeout or the package default.
35
+ */
36
+ export declare function resolveSocketIoShutdownTimeoutMs(options: SocketIoModuleOptions): number;
37
+ //# sourceMappingURL=config.internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.internal.d.ts","sourceRoot":"","sources":["../src/config.internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,4EAA4E;AAC5E,eAAO,MAAM,uCAAuC,MAAM,CAAC;AAE3D,mEAAmE;AACnE,eAAO,MAAM,4BAA4B,gBAAgB,CAAC;AAE1D,+DAA+D;AAC/D,eAAO,MAAM,qCAAqC,UAAY,CAAC;AAE/D,mEAAmE;AACnE,eAAO,MAAM,oCAAoC,OAAQ,CAAC;AAmB1D;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAIrF;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAEvF;AAED;;;;;GAKG;AACH,wBAAgB,0CAA0C,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAEjG;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAEvF"}
@@ -0,0 +1,61 @@
1
+ /** Default number of pre-connect Socket.IO messages buffered per socket. */
2
+ export const DEFAULT_MAX_PENDING_MESSAGES_PER_SOCKET = 128;
3
+
4
+ /** Default Engine.IO request path used by Socket.IO transports. */
5
+ export const DEFAULT_SOCKETIO_ENGINE_PATH = '/socket.io/';
6
+
7
+ /** Default maximum inbound Engine.IO payload size in bytes. */
8
+ export const DEFAULT_SOCKETIO_MAX_HTTP_BUFFER_SIZE = 1_048_576;
9
+
10
+ /** Default graceful Socket.IO shutdown timeout in milliseconds. */
11
+ export const DEFAULT_SOCKETIO_SHUTDOWN_TIMEOUT_MS = 5_000;
12
+ function isPositiveInteger(value) {
13
+ return typeof value === 'number' && Number.isInteger(value) && Number.isFinite(value) && value > 0;
14
+ }
15
+ function assertOptionalPositiveInteger(value, path) {
16
+ if (value === undefined || isPositiveInteger(value)) {
17
+ return;
18
+ }
19
+ throw new Error(`Socket.IO configuration ${path} must be a positive integer when provided.`);
20
+ }
21
+
22
+ /**
23
+ * Assert that explicit Socket.IO numeric options are positive integers.
24
+ *
25
+ * @param options Module options supplied to `SocketIoModule.forRoot(...)`.
26
+ */
27
+ export function assertValidSocketIoModuleOptions(options) {
28
+ assertOptionalPositiveInteger(options.engine?.maxHttpBufferSize, 'engine.maxHttpBufferSize');
29
+ assertOptionalPositiveInteger(options.buffer?.maxPendingMessagesPerSocket, 'buffer.maxPendingMessagesPerSocket');
30
+ assertOptionalPositiveInteger(options.shutdown?.timeoutMs, 'shutdown.timeoutMs');
31
+ }
32
+
33
+ /**
34
+ * Resolve the effective Socket.IO Engine.IO payload bound.
35
+ *
36
+ * @param options Module options supplied to `SocketIoModule.forRoot(...)`.
37
+ * @returns The explicit payload bound or the package default.
38
+ */
39
+ export function resolveSocketIoMaxHttpBufferSize(options) {
40
+ return options.engine?.maxHttpBufferSize ?? DEFAULT_SOCKETIO_MAX_HTTP_BUFFER_SIZE;
41
+ }
42
+
43
+ /**
44
+ * Resolve the effective pending-message buffer limit.
45
+ *
46
+ * @param options Module options supplied to `SocketIoModule.forRoot(...)`.
47
+ * @returns The explicit per-socket buffer limit or the package default.
48
+ */
49
+ export function resolveSocketIoMaxPendingMessagesPerSocket(options) {
50
+ return options.buffer?.maxPendingMessagesPerSocket ?? DEFAULT_MAX_PENDING_MESSAGES_PER_SOCKET;
51
+ }
52
+
53
+ /**
54
+ * Resolve the effective Socket.IO shutdown timeout.
55
+ *
56
+ * @param options Module options supplied to `SocketIoModule.forRoot(...)`.
57
+ * @returns The explicit shutdown timeout or the package default.
58
+ */
59
+ export function resolveSocketIoShutdownTimeoutMs(options) {
60
+ return options.shutdown?.timeoutMs ?? DEFAULT_SOCKETIO_SHUTDOWN_TIMEOUT_MS;
61
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAQhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAwBxD;;GAEG;AACH,qBAAa,cAAc;IACzB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,qBAA0B,GAAG,UAAU;CAShE"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAShE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAwBxD;;GAEG;AACH,qBAAa,cAAc;IACzB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,qBAA0B,GAAG,UAAU;CAWhE"}
package/dist/module.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { defineModule } from '@fluojs/runtime';
2
+ import { assertValidSocketIoModuleOptions } from './config.internal.js';
2
3
  import { SOCKETIO_OPTIONS_INTERNAL } from './options-token.internal.js';
3
4
  import { SocketIoLifecycleService } from './adapter.js';
4
5
  import { SOCKETIO_ROOM_SERVICE, SOCKETIO_SERVER } from './tokens.js';
@@ -11,7 +12,7 @@ function createSocketIoProviderSet(options = {}) {
11
12
  useClass: SocketIoLifecycleService
12
13
  }, {
13
14
  provide: SOCKETIO_SERVER,
14
- useFactory: service => service.getServer(),
15
+ useFactory: async service => await service.getServerAsync(),
15
16
  inject: [SocketIoLifecycleService]
16
17
  }, {
17
18
  provide: SOCKETIO_ROOM_SERVICE,
@@ -41,6 +42,7 @@ export class SocketIoModule {
41
42
  * ```
42
43
  */
43
44
  static forRoot(options = {}) {
45
+ assertValidSocketIoModuleOptions(options);
44
46
  class SocketIoRuntimeModule extends SocketIoModule {}
45
47
  return defineModule(SocketIoRuntimeModule, {
46
48
  exports: [SOCKETIO_ROOM_SERVICE, SOCKETIO_SERVER],
@@ -0,0 +1,25 @@
1
+ /** Result of a bounded Socket.IO close attempt. */
2
+ export interface SocketIoCloseResult {
3
+ readonly kind: 'closed' | 'forced';
4
+ readonly timeoutError?: SocketIoShutdownTimeoutError;
5
+ }
6
+ /** Minimal Socket.IO server surface required for lifecycle shutdown. */
7
+ export interface SocketIoCloseTarget {
8
+ close(callback?: () => void): unknown;
9
+ disconnectSockets?: (close?: boolean) => unknown;
10
+ httpServer?: unknown;
11
+ }
12
+ /** Error retained when Socket.IO shutdown exceeds the configured timeout. */
13
+ export declare class SocketIoShutdownTimeoutError extends Error {
14
+ readonly timeoutMs: number;
15
+ constructor(timeoutMs: number);
16
+ }
17
+ /**
18
+ * Close a Socket.IO server while bounding graceful cleanup time.
19
+ *
20
+ * @param io Socket.IO close target managed by the lifecycle service.
21
+ * @param timeoutMs Maximum graceful close duration before managed clients are force-disconnected.
22
+ * @returns A close result that identifies graceful completion or forced client cleanup.
23
+ */
24
+ export declare function closeSocketIoServerWithTimeout(io: SocketIoCloseTarget, timeoutMs: number): Promise<SocketIoCloseResult>;
25
+ //# sourceMappingURL=shutdown.internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shutdown.internal.d.ts","sourceRoot":"","sources":["../src/shutdown.internal.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACnC,QAAQ,CAAC,YAAY,CAAC,EAAE,4BAA4B,CAAC;CACtD;AAED,wEAAwE;AACxE,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;IACtC,iBAAiB,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,6EAA6E;AAC7E,qBAAa,4BAA6B,SAAQ,KAAK;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM;gBAAjB,SAAS,EAAE,MAAM;CAIvC;AAUD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAwCvH"}
@@ -0,0 +1,66 @@
1
+ /** Result of a bounded Socket.IO close attempt. */
2
+
3
+ /** Minimal Socket.IO server surface required for lifecycle shutdown. */
4
+
5
+ /** Error retained when Socket.IO shutdown exceeds the configured timeout. */
6
+ export class SocketIoShutdownTimeoutError extends Error {
7
+ constructor(timeoutMs) {
8
+ super(`Timed out while closing Socket.IO server after ${String(timeoutMs)}ms.`);
9
+ this.timeoutMs = timeoutMs;
10
+ this.name = 'SocketIoShutdownTimeoutError';
11
+ }
12
+ }
13
+ function detachAdapterOwnedHttpServer(io) {
14
+ io.httpServer = undefined;
15
+ }
16
+ function forceDisconnectManagedClients(io) {
17
+ io.disconnectSockets?.(true);
18
+ }
19
+
20
+ /**
21
+ * Close a Socket.IO server while bounding graceful cleanup time.
22
+ *
23
+ * @param io Socket.IO close target managed by the lifecycle service.
24
+ * @param timeoutMs Maximum graceful close duration before managed clients are force-disconnected.
25
+ * @returns A close result that identifies graceful completion or forced client cleanup.
26
+ */
27
+ export function closeSocketIoServerWithTimeout(io, timeoutMs) {
28
+ return new Promise((resolve, reject) => {
29
+ let settled = false;
30
+ const timeout = setTimeout(() => {
31
+ if (settled) {
32
+ return;
33
+ }
34
+ const timeoutError = new SocketIoShutdownTimeoutError(timeoutMs);
35
+ try {
36
+ forceDisconnectManagedClients(io);
37
+ } catch (error) {
38
+ settled = true;
39
+ reject(error);
40
+ return;
41
+ }
42
+ settled = true;
43
+ resolve({
44
+ kind: 'forced',
45
+ timeoutError
46
+ });
47
+ }, timeoutMs);
48
+ detachAdapterOwnedHttpServer(io);
49
+ try {
50
+ io.close(() => {
51
+ if (settled) {
52
+ return;
53
+ }
54
+ settled = true;
55
+ clearTimeout(timeout);
56
+ resolve({
57
+ kind: 'closed'
58
+ });
59
+ });
60
+ } catch (error) {
61
+ settled = true;
62
+ clearTimeout(timeout);
63
+ reject(error);
64
+ }
65
+ });
66
+ }
package/dist/types.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { IncomingMessage } from 'node:http';
2
2
  import type { ServerOptions, Socket } from 'socket.io';
3
3
  import type { WebSocketRoomService } from '@fluojs/websockets';
4
+ /** Runtime-specific Socket.IO handshake request surfaced to guard callbacks. */
5
+ export type SocketIoHandshakeRequest = IncomingMessage | Request;
4
6
  /**
5
7
  * Room management contract exposed by `@fluojs/socket.io` gateways.
6
8
  *
@@ -52,8 +54,8 @@ export interface SocketIoConnectionGuardContext {
52
54
  activeConnectionCount: number;
53
55
  /** Namespace path currently being connected, normalized to the Socket.IO gateway path contract. */
54
56
  namespacePath: string;
55
- /** Raw HTTP handshake request exposed by the selected Socket.IO runtime. */
56
- request: IncomingMessage;
57
+ /** Runtime-specific handshake request exposed by the selected Socket.IO runtime. */
58
+ request: SocketIoHandshakeRequest;
57
59
  /** Socket.IO socket instance under evaluation. */
58
60
  socket: Socket;
59
61
  }
@@ -73,8 +75,8 @@ export interface SocketIoMessageGuardContext {
73
75
  namespacePath: string;
74
76
  /** Event payload extracted from the Socket.IO argument list. */
75
77
  payload: unknown;
76
- /** Raw HTTP handshake request associated with the current socket. */
77
- request: IncomingMessage;
78
+ /** Runtime-specific handshake request associated with the current socket. */
79
+ request: SocketIoHandshakeRequest;
78
80
  /** Socket.IO socket instance emitting the event. */
79
81
  socket: Socket;
80
82
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "realtime",
10
10
  "platform"
11
11
  ],
12
- "version": "1.0.3",
12
+ "version": "1.0.5",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -38,10 +38,10 @@
38
38
  "dependencies": {
39
39
  "@socket.io/bun-engine": "^0.1.0",
40
40
  "@fluojs/core": "^1.0.3",
41
- "@fluojs/di": "^1.0.3",
41
+ "@fluojs/di": "^1.1.0",
42
42
  "@fluojs/http": "^1.1.0",
43
- "@fluojs/runtime": "^1.1.1",
44
- "@fluojs/websockets": "^1.0.3"
43
+ "@fluojs/runtime": "^1.1.6",
44
+ "@fluojs/websockets": "^1.0.5"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "socket.io": "^4.0.0"
@@ -50,9 +50,9 @@
50
50
  "socket.io": "^4.8.1",
51
51
  "socket.io-client": "^4.8.1",
52
52
  "vitest": "^3.2.4",
53
- "@fluojs/platform-express": "^1.0.2",
54
- "@fluojs/platform-fastify": "^1.0.3",
55
- "@fluojs/platform-nodejs": "^1.0.2"
53
+ "@fluojs/platform-express": "^1.0.5",
54
+ "@fluojs/platform-fastify": "^1.0.6",
55
+ "@fluojs/platform-nodejs": "^1.0.5"
56
56
  },
57
57
  "scripts": {
58
58
  "prebuild": "node ../../tooling/scripts/clean-dist.mjs",