@dangao/bun-server 1.4.0 → 1.6.0

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.
@@ -15,6 +15,16 @@ export interface ApplicationOptions {
15
15
  * 主机名
16
16
  */
17
17
  hostname?: string;
18
+ /**
19
+ * 优雅停机超时时间(毫秒)
20
+ * 默认 30 秒
21
+ */
22
+ gracefulShutdownTimeout?: number;
23
+ /**
24
+ * 是否启用信号监听(SIGTERM、SIGINT)
25
+ * 默认 true
26
+ */
27
+ enableSignalHandlers?: boolean;
18
28
  }
19
29
  /**
20
30
  * 应用主类
@@ -26,6 +36,7 @@ export declare class Application {
26
36
  private readonly middlewarePipeline;
27
37
  private readonly websocketRegistry;
28
38
  private readonly extensions;
39
+ private signalHandlersInstalled;
29
40
  constructor(options?: ApplicationOptions);
30
41
  /**
31
42
  * 注册全局中间件
@@ -46,9 +57,16 @@ export declare class Application {
46
57
  */
47
58
  private initializeConfigCenter;
48
59
  /**
49
- * 停止应用
60
+ * 停止应用(立即停止,不等待请求完成)
50
61
  */
51
62
  stop(): Promise<void>;
63
+ /**
64
+ * 优雅停机
65
+ * 停止接受新请求,等待正在处理的请求完成,然后关闭应用
66
+ * @param timeout - 超时时间(毫秒),默认使用配置的 gracefulShutdownTimeout 或 30000
67
+ * @returns Promise,在停机完成时 resolve
68
+ */
69
+ gracefulShutdown(timeout?: number): Promise<void>;
52
70
  /**
53
71
  * 关闭所有扩展
54
72
  */
@@ -99,5 +117,13 @@ export declare class Application {
99
117
  * @returns DI 容器
100
118
  */
101
119
  getContainer(): import("..").Container;
120
+ /**
121
+ * 安装信号处理器(SIGTERM、SIGINT)
122
+ */
123
+ private installSignalHandlers;
124
+ /**
125
+ * 移除信号处理器
126
+ */
127
+ private removeSignalHandlers;
102
128
  }
103
129
  //# sourceMappingURL=application.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"application.d.ts","sourceRoot":"","sources":["../../src/core/application.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAsB,MAAM,UAAU,CAAC;AAMzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAM3C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IACxD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA2B;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA8B;gBAEtC,OAAO,GAAE,kBAAuB;IAsBnD;;;OAGG;IACI,GAAG,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAIxC;;OAEG;IACU,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BpE;;OAEG;YACW,oBAAoB;IAqBlC;;;OAGG;YACW,sBAAsB;IAyBpC;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IASlC;;OAEG;YACW,eAAe;IAkB7B;;;;OAIG;YACW,aAAa;IA8B3B;;;OAGG;IACI,kBAAkB,CAAC,eAAe,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI;IAKtE;;;OAGG;IACI,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAgBrD;;;OAGG;IACI,wBAAwB,CAAC,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI;IAIzE;;;OAGG;IACI,iBAAiB,CAAC,SAAS,EAAE,oBAAoB,GAAG,IAAI;IAK/D;;;OAGG;IACI,SAAS,IAAI,SAAS,GAAG,SAAS;IAIzC;;;OAGG;YACW,gBAAgB;IAsB9B;;;OAGG;YACW,kBAAkB;IAwBhC;;;OAGG;IACI,YAAY;CAGpB"}
1
+ {"version":3,"file":"application.d.ts","sourceRoot":"","sources":["../../src/core/application.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAsB,MAAM,UAAU,CAAC;AAMzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAO3C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IACxD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA2B;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA8B;IACzD,OAAO,CAAC,uBAAuB,CAAkB;gBAE9B,OAAO,GAAE,kBAAuB;IAsBnD;;;OAGG;IACI,GAAG,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAIxC;;OAEG;IACU,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkCpE;;OAEG;YACW,oBAAoB;IAqBlC;;;OAGG;YACW,sBAAsB;IAyBpC;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAYlC;;;;;OAKG;IACU,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB9D;;OAEG;YACW,eAAe;IAkB7B;;;;OAIG;YACW,aAAa;IA8B3B;;;OAGG;IACI,kBAAkB,CAAC,eAAe,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI;IAKtE;;;OAGG;IACI,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAgBrD;;;OAGG;IACI,wBAAwB,CAAC,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI;IAIzE;;;OAGG;IACI,iBAAiB,CAAC,SAAS,EAAE,oBAAoB,GAAG,IAAI;IAK/D;;;OAGG;IACI,SAAS,IAAI,SAAS,GAAG,SAAS;IAIzC;;;OAGG;YACW,gBAAgB;IAsB9B;;;OAGG;YACW,kBAAkB;IAwBhC;;;OAGG;IACI,YAAY;IAInB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA2B7B;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAS7B"}
@@ -22,6 +22,11 @@ export interface ServerOptions {
22
22
  * WebSocket 网关注册表
23
23
  */
24
24
  websocketRegistry?: WebSocketGatewayRegistry;
25
+ /**
26
+ * 优雅停机超时时间(毫秒)
27
+ * 默认 30 秒
28
+ */
29
+ gracefulShutdownTimeout?: number;
25
30
  }
26
31
  /**
27
32
  * 服务器封装类
@@ -30,15 +35,34 @@ export interface ServerOptions {
30
35
  export declare class BunServer {
31
36
  private server?;
32
37
  private readonly options;
38
+ private activeRequests;
39
+ private isShuttingDown;
40
+ private shutdownPromise?;
41
+ private shutdownResolve?;
33
42
  constructor(options: ServerOptions);
34
43
  /**
35
44
  * 启动服务器
36
45
  */
37
46
  start(): void;
38
47
  /**
39
- * 停止服务器
48
+ * 停止服务器(立即停止,不等待请求完成)
40
49
  */
41
50
  stop(): void;
51
+ /**
52
+ * 优雅停机
53
+ * 停止接受新请求,等待正在处理的请求完成
54
+ * @param timeout - 超时时间(毫秒),默认使用配置的 gracefulShutdownTimeout 或 30000
55
+ * @returns Promise,在停机完成时 resolve
56
+ */
57
+ gracefulShutdown(timeout?: number): Promise<void>;
58
+ /**
59
+ * 获取当前活跃请求数
60
+ */
61
+ getActiveRequests(): number;
62
+ /**
63
+ * 检查是否正在关闭
64
+ */
65
+ isShuttingDownState(): boolean;
42
66
  /**
43
67
  * 获取服务器实例
44
68
  * @returns Bun Server 实例
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE1D;;OAEG;IACH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C;AAED;;;GAGG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAC,CAAkC;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;gBAErB,OAAO,EAAE,aAAa;IAIzC;;OAEG;IACI,KAAK,IAAI,IAAI;IAsDpB;;OAEG;IACI,IAAI,IAAI,IAAI;IASnB;;;OAGG;IACI,SAAS,IAAI,MAAM,CAAC,uBAAuB,CAAC,GAAG,SAAS;IAI/D;;;OAGG;IACI,SAAS,IAAI,OAAO;IAI3B;;OAEG;IACI,OAAO,IAAI,MAAM;IAIxB;;OAEG;IACI,WAAW,IAAI,MAAM,GAAG,SAAS;CAGzC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE1D;;OAEG;IACH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAE7C;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;GAGG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAC,CAAkC;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,eAAe,CAAC,CAAgB;IACxC,OAAO,CAAC,eAAe,CAAC,CAAa;gBAElB,OAAO,EAAE,aAAa;IAIzC;;OAEG;IACI,KAAK,IAAI,IAAI;IAmGpB;;OAEG;IACI,IAAI,IAAI,IAAI;IAWnB;;;;;OAKG;IACU,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2C9D;;OAEG;IACI,iBAAiB,IAAI,MAAM;IAIlC;;OAEG;IACI,mBAAmB,IAAI,OAAO;IAIrC;;;OAGG;IACI,SAAS,IAAI,MAAM,CAAC,uBAAuB,CAAC,GAAG,SAAS;IAI/D;;;OAGG;IACI,SAAS,IAAI,OAAO;IAI3B;;OAEG;IACI,OAAO,IAAI,MAAM;IAIxB;;OAEG;IACI,WAAW,IAAI,MAAM,GAAG,SAAS;CAGzC"}
package/dist/index.js CHANGED
@@ -3235,6 +3235,10 @@ import { LoggerManager } from "@dangao/logsmith";
3235
3235
  class BunServer {
3236
3236
  server;
3237
3237
  options;
3238
+ activeRequests = 0;
3239
+ isShuttingDown = false;
3240
+ shutdownPromise;
3241
+ shutdownResolve;
3238
3242
  constructor(options) {
3239
3243
  this.options = options;
3240
3244
  }
@@ -3243,36 +3247,64 @@ class BunServer {
3243
3247
  throw new Error("Server is already running");
3244
3248
  }
3245
3249
  const logger = LoggerManager.getLogger();
3250
+ this.activeRequests = 0;
3251
+ this.isShuttingDown = false;
3252
+ this.shutdownPromise = undefined;
3253
+ this.shutdownResolve = undefined;
3246
3254
  this.server = Bun.serve({
3247
3255
  port: this.options.port ?? 3000,
3248
3256
  hostname: this.options.hostname,
3249
3257
  fetch: (request, server) => {
3258
+ if (this.isShuttingDown) {
3259
+ return new Response("Server is shutting down", { status: 503 });
3260
+ }
3250
3261
  const upgradeHeader = request.headers.get("upgrade");
3251
3262
  if (this.options.websocketRegistry && upgradeHeader && upgradeHeader.toLowerCase() === "websocket") {
3252
3263
  const url = new URL(request.url);
3253
3264
  if (!this.options.websocketRegistry.hasGateway(url.pathname)) {
3254
3265
  return new Response("WebSocket gateway not found", { status: 404 });
3255
3266
  }
3267
+ const context2 = new Context(request);
3268
+ const queryParams = new URLSearchParams(url.searchParams);
3256
3269
  const upgraded = server.upgrade(request, {
3257
- data: { path: url.pathname }
3270
+ data: {
3271
+ path: url.pathname,
3272
+ query: queryParams,
3273
+ context: context2
3274
+ }
3258
3275
  });
3259
3276
  if (upgraded) {
3260
3277
  return;
3261
3278
  }
3262
3279
  return new Response("WebSocket upgrade failed", { status: 400 });
3263
3280
  }
3281
+ this.activeRequests++;
3264
3282
  const context = new Context(request);
3265
- return this.options.fetch(context);
3283
+ const responsePromise = this.options.fetch(context);
3284
+ if (responsePromise instanceof Promise) {
3285
+ responsePromise.finally(() => {
3286
+ this.activeRequests--;
3287
+ if (this.isShuttingDown && this.activeRequests === 0 && this.shutdownResolve) {
3288
+ this.shutdownResolve();
3289
+ }
3290
+ }).catch(() => {});
3291
+ } else {
3292
+ this.activeRequests--;
3293
+ if (this.isShuttingDown && this.activeRequests === 0 && this.shutdownResolve) {
3294
+ this.shutdownResolve();
3295
+ }
3296
+ }
3297
+ return responsePromise;
3266
3298
  },
3267
3299
  websocket: {
3268
- open: (ws) => {
3269
- this.options.websocketRegistry?.handleOpen(ws);
3300
+ open: async (ws) => {
3301
+ await this.options.websocketRegistry?.handleOpen(ws);
3270
3302
  },
3271
- message: (ws, message) => {
3272
- this.options.websocketRegistry?.handleMessage(ws, message);
3303
+ message: async (ws, message) => {
3304
+ await this.options.websocketRegistry?.handleMessage(ws, message);
3273
3305
  },
3274
- close: (ws, code, reason) => {
3275
- this.options.websocketRegistry?.handleClose(ws, code, reason);
3306
+ close: async (ws, code, reason) => {
3307
+ await this.options.websocketRegistry?.handleClose(ws, code, reason);
3276
3308
  }
3277
3309
  }
3278
3310
  });
@@ -3285,9 +3317,45 @@ class BunServer {
3285
3317
  const logger = LoggerManager.getLogger();
3286
3318
  this.server.stop();
3287
3319
  this.server = undefined;
3320
+ this.isShuttingDown = false;
3321
+ this.activeRequests = 0;
3288
3322
  logger.info("Server stopped");
3289
3323
  }
3290
3324
  }
3325
+ async gracefulShutdown(timeout) {
3326
+ if (!this.server || this.isShuttingDown) {
3327
+ return;
3328
+ }
3329
+ const logger = LoggerManager.getLogger();
3330
+ const shutdownTimeout = timeout ?? this.options.gracefulShutdownTimeout ?? 30000;
3331
+ logger.info(`Starting graceful shutdown (timeout: ${shutdownTimeout}ms, active requests: ${this.activeRequests})`);
3332
+ this.isShuttingDown = true;
3333
+ if (this.activeRequests === 0) {
3334
+ this.stop();
3335
+ logger.info("Graceful shutdown completed (no active requests)");
3336
+ return;
3337
+ }
3338
+ if (!this.shutdownPromise) {
3339
+ this.shutdownPromise = new Promise((resolve) => {
3340
+ this.shutdownResolve = resolve;
3341
+ });
3342
+ }
3343
+ const timeoutPromise = new Promise((resolve) => {
3344
+ setTimeout(() => {
3345
+ logger.warn(`Graceful shutdown timeout (${shutdownTimeout}ms), forcing shutdown`);
3346
+ resolve();
3347
+ }, shutdownTimeout);
3348
+ });
3349
+ await Promise.race([this.shutdownPromise, timeoutPromise]);
3350
+ this.stop();
3351
+ logger.info(`Graceful shutdown completed (remaining active requests: ${this.activeRequests})`);
3352
+ }
3353
+ getActiveRequests() {
3354
+ return this.activeRequests;
3355
+ }
3356
+ isShuttingDownState() {
3357
+ return this.isShuttingDown;
3358
+ }
3291
3359
  getServer() {
3292
3360
  return this.server;
3293
3361
  }
@@ -3341,6 +3409,8 @@ function getHandlerMetadata(target) {
3341
3409
  }
3342
3410
 
3343
3411
  // src/websocket/registry.ts
3412
+ init_param_binder();
3413
+ init_decorators2();
3344
3414
  class WebSocketGatewayRegistry {
3345
3415
  static instance;
3346
3416
  container;
@@ -3381,47 +3451,90 @@ class WebSocketGatewayRegistry {
3381
3451
  getGateway(path) {
3382
3452
  return this.gateways.get(path);
3383
3453
  }
3384
- getGatewayInstance(definition) {
3385
- if (!definition.instance) {
3386
- definition.instance = this.container.resolve(definition.gatewayClass);
3387
- }
3388
- return definition.instance;
3454
+ createGatewayInstance(definition) {
3455
+ return this.container.resolve(definition.gatewayClass);
3389
3456
  }
3390
- invokeHandler(ws, definition, handlerName, ...args) {
3457
+ async invokeHandler(ws, definition, handlerName, ...args) {
3391
3458
  if (!handlerName) {
3392
3459
  return;
3393
3460
  }
3394
- const instance = this.getGatewayInstance(definition);
3461
+ const instance = this.createGatewayInstance(definition);
3395
3462
  const handler = instance[handlerName];
3396
- if (typeof handler === "function") {
3463
+ if (typeof handler !== "function") {
3464
+ return;
3465
+ }
3466
+ const prototype = definition.gatewayClass.prototype;
3467
+ const paramMetadata = getParamMetadata(prototype, handlerName);
3468
+ if (paramMetadata.length > 0) {
3469
+ let context = ws.data?.context;
3470
+ if (!context) {
3471
+ const url = new URL(ws.data.path || "/", "http://localhost");
3472
+ if (ws.data.query) {
3473
+ ws.data.query.forEach((value, key) => {
3474
+ url.searchParams.set(key, value);
3475
+ });
3476
+ }
3477
+ const request = new Request(url.toString(), {
3478
+ method: "GET",
3479
+ headers: new Headers
3480
+ });
3481
+ context = new Context(request);
3482
+ ws.data.context = context;
3483
+ }
3484
+ const boundParams = await ParamBinder.bind(prototype, handlerName, context, this.container);
3485
+ const finalArgs = [];
3486
+ const maxParamIndex = paramMetadata.length > 0 ? Math.max(...paramMetadata.map((m) => m.index)) : -1;
3487
+ const totalParamCount = Math.max(maxParamIndex + 1, args.length + 1);
3488
+ for (let i = 0;i < totalParamCount; i++) {
3489
+ const meta = paramMetadata.find((m) => m.index === i);
3490
+ if (meta) {
3491
+ finalArgs[i] = boundParams[i];
3492
+ } else if (i === 0) {
3493
+ finalArgs[i] = ws;
3494
+ } else {
3495
+ const argIndex = i - 1;
3496
+ if (argIndex < args.length) {
3497
+ finalArgs[i] = args[argIndex];
3498
+ } else {
3499
+ finalArgs[i] = undefined;
3500
+ }
3501
+ }
3502
+ }
3503
+ if (args.length > maxParamIndex) {
3504
+ for (let i = maxParamIndex + 1;i < args.length; i++) {
3505
+ finalArgs.push(args[i]);
3506
+ }
3507
+ }
3508
+ handler.apply(instance, finalArgs);
3509
+ } else {
3397
3510
  handler.apply(instance, [ws, ...args]);
3398
3511
  }
3399
3512
  }
3400
- handleOpen(ws) {
3513
+ async handleOpen(ws) {
3401
3514
  const path = ws.data?.path;
3402
3515
  const definition = path ? this.getGateway(path) : undefined;
3403
3516
  if (!definition) {
3404
3517
  ws.close(1008, "Gateway not found");
3405
3518
  return;
3406
3519
  }
3407
- this.invokeHandler(ws, definition, definition.handlers.open);
3520
+ await this.invokeHandler(ws, definition, definition.handlers.open);
3408
3521
  }
3409
- handleMessage(ws, message) {
3522
+ async handleMessage(ws, message) {
3410
3523
  const path = ws.data?.path;
3411
3524
  const definition = path ? this.getGateway(path) : undefined;
3412
3525
  if (!definition) {
3413
3526
  ws.close(1008, "Gateway not found");
3414
3527
  return;
3415
3528
  }
3416
- this.invokeHandler(ws, definition, definition.handlers.message, message);
3529
+ await this.invokeHandler(ws, definition, definition.handlers.message, message);
3417
3530
  }
3418
- handleClose(ws, code, reason) {
3531
+ async handleClose(ws, code, reason) {
3419
3532
  const path = ws.data?.path;
3420
3533
  const definition = path ? this.getGateway(path) : undefined;
3421
3534
  if (!definition) {
3422
3535
  return;
3423
3536
  }
3424
- this.invokeHandler(ws, definition, definition.handlers.close, code, reason);
3537
+ await this.invokeHandler(ws, definition, definition.handlers.close, code, reason);
3425
3538
  }
3426
3539
  }
3427
3540
 
@@ -3840,12 +3953,15 @@ ConfigModule = __legacyDecorateClassTS([
3840
3953
  ], ConfigModule);
3841
3954
 
3842
3955
  // src/core/application.ts
3956
+ import { LoggerManager as LoggerManager8 } from "@dangao/logsmith";
3957
+
3843
3958
  class Application {
3844
3959
  server;
3845
3960
  options;
3846
3961
  middlewarePipeline;
3847
3962
  websocketRegistry;
3848
3963
  extensions = [];
3964
+ signalHandlersInstalled = false;
3849
3965
  constructor(options = {}) {
3850
3966
  this.options = options;
3851
3967
  this.middlewarePipeline = new MiddlewarePipeline([createErrorHandlingMiddleware()]);
@@ -3874,10 +3990,14 @@ class Application {
3874
3990
  port: finalPort,
3875
3991
  hostname: finalHostname,
3876
3992
  fetch: this.handleRequest.bind(this),
3877
- websocketRegistry: this.websocketRegistry
3993
+ websocketRegistry: this.websocketRegistry,
3994
+ gracefulShutdownTimeout: this.options.gracefulShutdownTimeout
3878
3995
  };
3879
3996
  this.server = new BunServer(serverOptions);
3880
3997
  this.server.start();
3998
+ if (this.options.enableSignalHandlers !== false) {
3999
+ this.installSignalHandlers();
4000
+ }
3881
4001
  await this.registerServices(finalPort, finalHostname);
3882
4002
  }
3883
4003
  async initializeExtensions() {
@@ -3905,10 +4025,19 @@ class Application {
3905
4025
  }
3906
4026
  }
3907
4027
  async stop() {
4028
+ this.removeSignalHandlers();
3908
4029
  await this.deregisterServices();
3909
4030
  await this.closeExtensions();
3910
4031
  this.server?.stop();
3911
4032
  }
4033
+ async gracefulShutdown(timeout) {
4034
+ this.removeSignalHandlers();
4035
+ await this.deregisterServices();
4036
+ await this.closeExtensions();
4037
+ if (this.server) {
4038
+ await this.server.gracefulShutdown(timeout);
4039
+ }
4040
+ }
3912
4041
  async closeExtensions() {
3913
4042
  const container = this.getContainer();
3914
4043
  for (let i = this.extensions.length - 1;i >= 0; i--) {
@@ -3995,6 +4124,33 @@ class Application {
3995
4124
  getContainer() {
3996
4125
  return ControllerRegistry.getInstance().getContainer();
3997
4126
  }
4127
+ installSignalHandlers() {
4128
+ if (this.signalHandlersInstalled) {
4129
+ return;
4130
+ }
4131
+ const logger = LoggerManager8.getLogger();
4132
+ const shutdownHandler = async (signal) => {
4133
+ logger.info(`Received ${signal}, starting graceful shutdown...`);
4134
+ try {
4135
+ await this.gracefulShutdown();
4136
+ process.exit(0);
4137
+ } catch (error) {
4138
+ logger.error(`Error during graceful shutdown:`, error);
4139
+ process.exit(1);
4140
+ }
4141
+ };
4142
+ process.on("SIGTERM", () => shutdownHandler("SIGTERM"));
4143
+ process.on("SIGINT", () => shutdownHandler("SIGINT"));
4144
+ this.signalHandlersInstalled = true;
4145
+ }
4146
+ removeSignalHandlers() {
4147
+ if (!this.signalHandlersInstalled) {
4148
+ return;
4149
+ }
4150
+ process.removeAllListeners("SIGTERM");
4151
+ process.removeAllListeners("SIGINT");
4152
+ this.signalHandlersInstalled = false;
4153
+ }
3998
4154
  }
3999
4155
 
4000
4156
  // src/index.ts
@@ -1,7 +1,10 @@
1
1
  import type { ServerWebSocket } from 'bun';
2
+ import { Context } from '../core/context';
2
3
  import type { Constructor } from '@/core/types';
3
4
  export interface WebSocketConnectionData {
4
5
  path: string;
6
+ query?: URLSearchParams;
7
+ context?: Context;
5
8
  }
6
9
  export declare class WebSocketGatewayRegistry {
7
10
  private static instance;
@@ -13,10 +16,22 @@ export declare class WebSocketGatewayRegistry {
13
16
  hasGateway(path: string): boolean;
14
17
  clear(): void;
15
18
  private getGateway;
16
- private getGatewayInstance;
19
+ /**
20
+ * 动态创建网关实例(每次连接创建新实例)
21
+ * @param definition - 网关定义
22
+ * @returns 网关实例
23
+ */
24
+ private createGatewayInstance;
25
+ /**
26
+ * 调用处理器,支持参数绑定
27
+ * @param ws - WebSocket 连接
28
+ * @param definition - 网关定义
29
+ * @param handlerName - 处理器方法名
30
+ * @param args - 原始参数(message, code, reason 等,不包括 ws)
31
+ */
17
32
  private invokeHandler;
18
- handleOpen(ws: ServerWebSocket<WebSocketConnectionData>): void;
19
- handleMessage(ws: ServerWebSocket<WebSocketConnectionData>, message: string | ArrayBuffer | ArrayBufferView): void;
20
- handleClose(ws: ServerWebSocket<WebSocketConnectionData>, code: number, reason: string): void;
33
+ handleOpen(ws: ServerWebSocket<WebSocketConnectionData>): Promise<void>;
34
+ handleMessage(ws: ServerWebSocket<WebSocketConnectionData>, message: string | ArrayBuffer | ArrayBufferView): Promise<void>;
35
+ handleClose(ws: ServerWebSocket<WebSocketConnectionData>, code: number, reason: string): Promise<void>;
21
36
  }
22
37
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/websocket/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,KAAK,CAAC;AAK3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAa/C,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IAEjE,OAAO;WAIO,WAAW,IAAI,wBAAwB;IAO9C,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI;IAsBlD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC,KAAK,IAAI,IAAI;IAIpB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,aAAa;IAgBd,UAAU,CAAC,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,GAAG,IAAI;IAU9D,aAAa,CAClB,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,EAC5C,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,eAAe,GAC9C,IAAI;IAUA,WAAW,CAChB,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,EAC5C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,IAAI;CAQR"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/websocket/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,KAAK,CAAC;AAO3C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAYhD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IAEjE,OAAO;WAIO,WAAW,IAAI,wBAAwB;IAO9C,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI;IAsBlD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC,KAAK,IAAI,IAAI;IAIpB,OAAO,CAAC,UAAU;IAIlB;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAI7B;;;;;;OAMG;YACW,aAAa;IAiGd,UAAU,CAAC,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvE,aAAa,CACxB,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,EAC5C,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,eAAe,GAC9C,OAAO,CAAC,IAAI,CAAC;IAUH,WAAW,CACtB,EAAE,EAAE,eAAe,CAAC,uBAAuB,CAAC,EAC5C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;CAQjB"}
package/docs/zh/guide.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ```ts
8
8
  import "reflect-metadata";
9
- import { Application } from "../src";
9
+ import { Application } from "@dangao/bun-server";
10
10
 
11
11
  const app = new Application({ port: 3000 });
12
12
  app.listen();
@@ -26,7 +26,7 @@ import {
26
26
  Injectable,
27
27
  Param,
28
28
  POST,
29
- } from "../src";
29
+ } from "@dangao/bun-server";
30
30
 
31
31
  @Injectable()
32
32
  class UserService {
@@ -64,7 +64,7 @@ app.listen();
64
64
  ## 3. 使用中间件
65
65
 
66
66
  ```ts
67
- import { createCorsMiddleware, createLoggerMiddleware } from "../src";
67
+ import { createCorsMiddleware, createLoggerMiddleware } from "@dangao/bun-server";
68
68
 
69
69
  const app = new Application();
70
70
  app.use(createLoggerMiddleware({ prefix: "[Example]" }));
@@ -74,7 +74,7 @@ app.use(createCorsMiddleware({ origin: "*" }));
74
74
  `@UseMiddleware()` 可作用于单个控制器或方法:
75
75
 
76
76
  ```ts
77
- import { UseMiddleware } from '../src';
77
+ import { UseMiddleware } from "@dangao/bun-server";
78
78
 
79
79
  const auth = async (ctx, next) => {
80
80
  if (ctx.getHeader('authorization') !== 'token') {
@@ -92,7 +92,7 @@ class SecureController { ... }
92
92
  ## 4. 参数验证
93
93
 
94
94
  ```ts
95
- import { Validate, IsEmail, MinLength } from '../src';
95
+ import { Validate, IsEmail, MinLength } from "@dangao/bun-server";
96
96
 
97
97
  @POST('/register')
98
98
  public register(
@@ -108,32 +108,37 @@ public register(
108
108
  ## 5. WebSocket 网关
109
109
 
110
110
  ```ts
111
- import { OnMessage, WebSocketGateway } from "../src";
111
+ import { OnMessage, WebSocketGateway } from "@dangao/bun-server";
112
+ import type { ServerWebSocket } from "bun";
112
113
 
113
- @WebSocketGateway("/ws/chat")
114
+ @WebSocketGateway("/ws")
114
115
  class ChatGateway {
115
116
  @OnMessage
116
- public onMessage(ws, message: string) {
117
- ws.send(`echo: ${message}`);
117
+ public handleMessage(ws: ServerWebSocket, message: string) {
118
+ ws.send(`Echo: ${message}`);
118
119
  }
119
120
  }
120
121
 
122
+ const app = new Application({ port: 3000 });
121
123
  app.registerWebSocketGateway(ChatGateway);
124
+ app.listen();
122
125
  ```
123
126
 
124
127
  ## 6. 文件上传与静态资源
125
128
 
126
129
  ```ts
127
- import { createFileUploadMiddleware, createStaticFileMiddleware } from "../src";
130
+ import {
131
+ createFileUploadMiddleware,
132
+ createStaticFileMiddleware,
133
+ } from "@dangao/bun-server";
128
134
 
135
+ const app = new Application({ port: 3000 });
136
+
137
+ // File upload
129
138
  app.use(createFileUploadMiddleware({ maxSize: 5 * 1024 * 1024 }));
130
- app.use(
131
- createStaticFileMiddleware({
132
- root: "./public",
133
- prefix: "/assets",
134
- enableCache: true,
135
- }),
136
- );
139
+
140
+ // Static files
141
+ app.use(createStaticFileMiddleware({ root: "./public", prefix: "/assets" }));
137
142
  ```
138
143
 
139
144
  上传后的文件可在 `context.body.files` 中读取;静态资源请求会自动设置
@@ -142,7 +147,7 @@ Content-Type 与缓存头。
142
147
  ## 7. 错误处理与自定义过滤器
143
148
 
144
149
  ```ts
145
- import { ExceptionFilterRegistry, HttpException } from "../src";
150
+ import { ExceptionFilterRegistry, HttpException } from "@dangao/bun-server";
146
151
 
147
152
  ExceptionFilterRegistry.getInstance().register({
148
153
  catch(error, context) {
@@ -63,9 +63,15 @@
63
63
 
64
64
  - 将原本在 `Bun.serve({ websocket })` 中手写的逻辑迁移到 `@WebSocketGateway`:
65
65
  ```ts
66
- @WebSocketGateway('/ws/chat')
66
+ import { OnMessage, WebSocketGateway } from "@dangao/bun-server";
67
+ import type { ServerWebSocket } from "bun";
68
+
69
+ @WebSocketGateway('/ws')
67
70
  class ChatGateway {
68
- @OnMessage onMessage(ws, msg) { ws.send(msg); }
71
+ @OnMessage
72
+ public handleMessage(ws: ServerWebSocket, message: string) {
73
+ ws.send(`Echo: ${message}`);
74
+ }
69
75
  }
70
76
  app.registerWebSocketGateway(ChatGateway);
71
77
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dangao/bun-server",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",