@dangao/bun-server 1.9.0 → 1.12.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.
Files changed (209) hide show
  1. package/README.md +79 -6
  2. package/dist/cache/cache-module.d.ts +6 -0
  3. package/dist/cache/cache-module.d.ts.map +1 -1
  4. package/dist/client/generator.d.ts +16 -0
  5. package/dist/client/generator.d.ts.map +1 -0
  6. package/dist/client/index.d.ts +4 -0
  7. package/dist/client/index.d.ts.map +1 -0
  8. package/dist/client/runtime.d.ts +15 -0
  9. package/dist/client/runtime.d.ts.map +1 -0
  10. package/dist/client/types.d.ts +36 -0
  11. package/dist/client/types.d.ts.map +1 -0
  12. package/dist/config/config-module.d.ts +7 -0
  13. package/dist/config/config-module.d.ts.map +1 -1
  14. package/dist/config/index.d.ts +1 -1
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/service.d.ts +13 -0
  17. package/dist/config/service.d.ts.map +1 -1
  18. package/dist/config/types.d.ts +10 -0
  19. package/dist/config/types.d.ts.map +1 -1
  20. package/dist/core/application.d.ts +7 -0
  21. package/dist/core/application.d.ts.map +1 -1
  22. package/dist/core/apply-decorators.d.ts +6 -0
  23. package/dist/core/apply-decorators.d.ts.map +1 -0
  24. package/dist/core/cluster.d.ts +47 -0
  25. package/dist/core/cluster.d.ts.map +1 -0
  26. package/dist/core/index.d.ts +1 -0
  27. package/dist/core/index.d.ts.map +1 -1
  28. package/dist/core/server.d.ts +8 -0
  29. package/dist/core/server.d.ts.map +1 -1
  30. package/dist/dashboard/controller.d.ts +55 -0
  31. package/dist/dashboard/controller.d.ts.map +1 -0
  32. package/dist/dashboard/dashboard-extension.d.ts +20 -0
  33. package/dist/dashboard/dashboard-extension.d.ts.map +1 -0
  34. package/dist/dashboard/dashboard-module.d.ts +13 -0
  35. package/dist/dashboard/dashboard-module.d.ts.map +1 -0
  36. package/dist/dashboard/index.d.ts +4 -0
  37. package/dist/dashboard/index.d.ts.map +1 -0
  38. package/dist/dashboard/types.d.ts +16 -0
  39. package/dist/dashboard/types.d.ts.map +1 -0
  40. package/dist/dashboard/ui.d.ts +7 -0
  41. package/dist/dashboard/ui.d.ts.map +1 -0
  42. package/dist/database/database-module.d.ts +7 -0
  43. package/dist/database/database-module.d.ts.map +1 -1
  44. package/dist/debug/debug-module.d.ts +13 -0
  45. package/dist/debug/debug-module.d.ts.map +1 -0
  46. package/dist/debug/debug-ui-middleware.d.ts +8 -0
  47. package/dist/debug/debug-ui-middleware.d.ts.map +1 -0
  48. package/dist/debug/index.d.ts +5 -0
  49. package/dist/debug/index.d.ts.map +1 -0
  50. package/dist/debug/middleware.d.ts +12 -0
  51. package/dist/debug/middleware.d.ts.map +1 -0
  52. package/dist/debug/recorder.d.ts +61 -0
  53. package/dist/debug/recorder.d.ts.map +1 -0
  54. package/dist/debug/types.d.ts +48 -0
  55. package/dist/debug/types.d.ts.map +1 -0
  56. package/dist/debug/ui.d.ts +6 -0
  57. package/dist/debug/ui.d.ts.map +1 -0
  58. package/dist/di/async-module.d.ts +49 -0
  59. package/dist/di/async-module.d.ts.map +1 -0
  60. package/dist/di/lifecycle.d.ts +49 -0
  61. package/dist/di/lifecycle.d.ts.map +1 -0
  62. package/dist/di/module-registry.d.ts +24 -0
  63. package/dist/di/module-registry.d.ts.map +1 -1
  64. package/dist/index.d.ts +9 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +1887 -35
  67. package/dist/router/route.d.ts +5 -7
  68. package/dist/router/route.d.ts.map +1 -1
  69. package/dist/swagger/generator.d.ts +10 -0
  70. package/dist/swagger/generator.d.ts.map +1 -1
  71. package/dist/testing/test-client.d.ts +49 -0
  72. package/dist/testing/test-client.d.ts.map +1 -0
  73. package/dist/testing/testing-module.d.ts +90 -0
  74. package/dist/testing/testing-module.d.ts.map +1 -0
  75. package/dist/websocket/registry.d.ts +1 -6
  76. package/dist/websocket/registry.d.ts.map +1 -1
  77. package/docs/async-module.md +59 -0
  78. package/docs/client-generation.md +100 -0
  79. package/docs/cluster.md +81 -0
  80. package/docs/custom-decorators.md +1 -7
  81. package/docs/dashboard.md +54 -0
  82. package/docs/debug.md +58 -0
  83. package/docs/extensions.md +0 -2
  84. package/docs/guide.md +0 -1
  85. package/docs/lifecycle.md +72 -0
  86. package/docs/testing.md +110 -0
  87. package/docs/zh/async-module.md +98 -0
  88. package/docs/zh/client-generation.md +92 -0
  89. package/docs/zh/cluster.md +74 -0
  90. package/docs/zh/custom-decorators.md +1 -7
  91. package/docs/zh/dashboard.md +69 -0
  92. package/docs/zh/debug.md +81 -0
  93. package/docs/zh/extensions.md +0 -2
  94. package/docs/zh/guide.md +0 -1
  95. package/docs/zh/lifecycle.md +87 -0
  96. package/docs/zh/migration.md +0 -5
  97. package/docs/zh/testing.md +119 -0
  98. package/package.json +4 -4
  99. package/src/cache/cache-module.ts +25 -0
  100. package/src/client/generator.ts +36 -0
  101. package/src/client/index.ts +8 -0
  102. package/src/client/runtime.ts +101 -0
  103. package/src/client/types.ts +38 -0
  104. package/src/config/config-module.ts +44 -4
  105. package/src/config/index.ts +1 -0
  106. package/src/config/service.ts +50 -0
  107. package/src/config/types.ts +12 -0
  108. package/src/core/application.ts +37 -0
  109. package/src/core/apply-decorators.ts +31 -0
  110. package/src/core/cluster.ts +143 -0
  111. package/src/core/index.ts +1 -0
  112. package/src/core/server.ts +14 -1
  113. package/src/dashboard/controller.ts +227 -0
  114. package/src/dashboard/dashboard-extension.ts +26 -0
  115. package/src/dashboard/dashboard-module.ts +38 -0
  116. package/src/dashboard/index.ts +3 -0
  117. package/src/dashboard/types.ts +16 -0
  118. package/src/dashboard/ui.ts +219 -0
  119. package/src/database/database-module.ts +20 -0
  120. package/src/debug/debug-module.ts +70 -0
  121. package/src/debug/debug-ui-middleware.ts +110 -0
  122. package/src/debug/index.ts +9 -0
  123. package/src/debug/middleware.ts +126 -0
  124. package/src/debug/recorder.ts +141 -0
  125. package/src/debug/types.ts +49 -0
  126. package/src/debug/ui.ts +393 -0
  127. package/src/di/async-module.ts +141 -0
  128. package/src/di/lifecycle.ts +117 -0
  129. package/src/di/module-registry.ts +75 -0
  130. package/src/index.ts +35 -0
  131. package/src/router/route.ts +20 -20
  132. package/src/swagger/generator.ts +100 -0
  133. package/src/testing/test-client.ts +112 -0
  134. package/src/testing/testing-module.ts +238 -0
  135. package/src/websocket/registry.ts +3 -16
  136. package/tests/auth/auth-decorators.test.ts +0 -1
  137. package/tests/auth/oauth2-service.test.ts +0 -1
  138. package/tests/cache/cache-decorators-extended.test.ts +0 -1
  139. package/tests/cache/cache-decorators.test.ts +0 -1
  140. package/tests/cache/cache-interceptors.test.ts +0 -1
  141. package/tests/cache/cache-module.test.ts +0 -1
  142. package/tests/cache/cache-service-proxy.test.ts +0 -1
  143. package/tests/client/client-generator.test.ts +142 -0
  144. package/tests/config/config-center-integration.test.ts +0 -1
  145. package/tests/config/config-module-extended.test.ts +0 -1
  146. package/tests/config/config-module.test.ts +0 -1
  147. package/tests/controller/controller.test.ts +0 -1
  148. package/tests/controller/param-binder.test.ts +0 -1
  149. package/tests/controller/path-combination.test.ts +0 -1
  150. package/tests/core/application.test.ts +34 -0
  151. package/tests/core/apply-decorators.test.ts +109 -0
  152. package/tests/core/cluster.test.ts +32 -0
  153. package/tests/dashboard/dashboard-module.test.ts +85 -0
  154. package/tests/database/database-module.test.ts +0 -1
  155. package/tests/database/orm.test.ts +0 -1
  156. package/tests/database/postgres-mysql-integration.test.ts +0 -1
  157. package/tests/database/transaction.test.ts +0 -1
  158. package/tests/debug/debug-module.test.ts +141 -0
  159. package/tests/di/async-module.test.ts +125 -0
  160. package/tests/di/container.test.ts +0 -1
  161. package/tests/di/lifecycle.test.ts +140 -0
  162. package/tests/error/error-handler.test.ts +0 -1
  163. package/tests/events/event-decorators.test.ts +0 -1
  164. package/tests/events/event-listener-scanner.test.ts +0 -1
  165. package/tests/events/event-module.test.ts +0 -1
  166. package/tests/extensions/logger-module.test.ts +0 -1
  167. package/tests/health/health-module.test.ts +0 -1
  168. package/tests/integration/oauth2-e2e.test.ts +0 -1
  169. package/tests/integration/session-e2e.test.ts +0 -1
  170. package/tests/interceptor/base-interceptor.test.ts +0 -1
  171. package/tests/interceptor/builtin/cache-interceptor.test.ts +0 -1
  172. package/tests/interceptor/builtin/log-interceptor.test.ts +0 -1
  173. package/tests/interceptor/builtin/permission-interceptor.test.ts +0 -1
  174. package/tests/interceptor/interceptor-advanced-integration.test.ts +0 -1
  175. package/tests/interceptor/interceptor-chain.test.ts +0 -1
  176. package/tests/interceptor/interceptor-integration.test.ts +0 -1
  177. package/tests/interceptor/interceptor-metadata.test.ts +0 -1
  178. package/tests/interceptor/interceptor-registry.test.ts +0 -1
  179. package/tests/interceptor/perf/interceptor-performance.test.ts +0 -1
  180. package/tests/metrics/metrics-module.test.ts +0 -1
  181. package/tests/microservice/config-center.test.ts +0 -1
  182. package/tests/microservice/service-client-decorators.test.ts +0 -1
  183. package/tests/microservice/service-registry-decorators.test.ts +0 -1
  184. package/tests/microservice/service-registry.test.ts +0 -1
  185. package/tests/middleware/builtin/middleware-builtin-extended.test.ts +0 -1
  186. package/tests/middleware/builtin/rate-limit.test.ts +0 -1
  187. package/tests/middleware/middleware-decorators.test.ts +0 -1
  188. package/tests/middleware/middleware-pipeline.test.ts +0 -1
  189. package/tests/middleware/middleware.test.ts +0 -1
  190. package/tests/perf/optimization.test.ts +0 -1
  191. package/tests/queue/queue-decorators.test.ts +0 -1
  192. package/tests/queue/queue-module.test.ts +0 -1
  193. package/tests/queue/queue-service.test.ts +0 -1
  194. package/tests/router/router-decorators.test.ts +0 -1
  195. package/tests/router/router-extended.test.ts +0 -1
  196. package/tests/security/guards/guards-integration.test.ts +0 -1
  197. package/tests/security/guards/guards.test.ts +0 -1
  198. package/tests/security/guards/reflector.test.ts +0 -1
  199. package/tests/security/security-filter.test.ts +0 -1
  200. package/tests/security/security-module-extended.test.ts +0 -1
  201. package/tests/security/security-module.test.ts +0 -1
  202. package/tests/session/session-decorators.test.ts +0 -1
  203. package/tests/session/session-module.test.ts +0 -1
  204. package/tests/swagger/decorators.test.ts +0 -1
  205. package/tests/swagger/swagger-module.test.ts +0 -1
  206. package/tests/swagger/ui.test.ts +0 -1
  207. package/tests/testing/testing-module.test.ts +129 -0
  208. package/tests/validation/class-validator.test.ts +0 -1
  209. package/tests/validation/controller-validation.test.ts +0 -1
package/dist/index.js CHANGED
@@ -1,42 +1,39 @@
1
1
  // @bun
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
2
  var __defProp = Object.defineProperty;
5
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __toESM = (mod, isNodeMode, target) => {
9
- target = mod != null ? __create(__getProtoOf(mod)) : {};
10
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
- for (let key of __getOwnPropNames(mod))
12
- if (!__hasOwnProp.call(to, key))
13
- __defProp(to, key, {
14
- get: () => mod[key],
15
- enumerable: true
16
- });
17
- return to;
18
- };
19
- var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ function __accessProp(key) {
7
+ return this[key];
8
+ }
20
9
  var __toCommonJS = (from) => {
21
- var entry = __moduleCache.get(from), desc;
10
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
22
11
  if (entry)
23
12
  return entry;
24
13
  entry = __defProp({}, "__esModule", { value: true });
25
- if (from && typeof from === "object" || typeof from === "function")
26
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
27
- get: () => from[key],
28
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
29
- }));
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (var key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(entry, key))
17
+ __defProp(entry, key, {
18
+ get: __accessProp.bind(from, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
30
22
  __moduleCache.set(from, entry);
31
23
  return entry;
32
24
  };
25
+ var __moduleCache;
26
+ var __returnValue = (v) => v;
27
+ function __exportSetter(name, newValue) {
28
+ this[name] = __returnValue.bind(null, newValue);
29
+ }
33
30
  var __export = (target, all) => {
34
31
  for (var name in all)
35
32
  __defProp(target, name, {
36
33
  get: all[name],
37
34
  enumerable: true,
38
35
  configurable: true,
39
- set: (newValue) => all[name] = () => newValue
36
+ set: __exportSetter.bind(all, name)
40
37
  });
41
38
  };
42
39
  var __legacyDecorateClassTS = function(decorators, target, key, desc) {
@@ -286,28 +283,31 @@ class Route {
286
283
  this.handler = handler;
287
284
  this.controllerClass = controllerClass;
288
285
  this.methodName = methodName;
289
- const { pattern, paramNames } = this.parsePath(path);
290
- this.pattern = pattern;
291
- this.paramNames = paramNames;
292
- this.middlewarePipeline = middlewares.length > 0 ? new MiddlewarePipeline(middlewares) : null;
293
286
  this.isStatic = !path.includes(":") && !path.includes("*");
294
287
  if (this.isStatic) {
295
288
  this.staticKey = `${method}:${path}`;
289
+ } else {
290
+ const { pattern, paramNames } = Route.parsePath(path);
291
+ this.pattern = pattern;
292
+ this.paramNames = paramNames;
296
293
  }
294
+ this.middlewarePipeline = middlewares.length > 0 ? new MiddlewarePipeline(middlewares) : null;
297
295
  }
298
- parsePath(path) {
296
+ static parsePath(path) {
299
297
  const paramNames = [];
300
298
  const patternString = path.replace(/:([^/]+)/g, (_, paramName) => {
301
299
  paramNames.push(paramName);
302
300
  return "([^/]+)";
303
301
  }).replace(/\*/g, ".*");
304
- const pattern = new RegExp(`^${patternString}$`);
305
- return { pattern, paramNames };
302
+ return { pattern: new RegExp(`^${patternString}$`), paramNames };
306
303
  }
307
304
  match(method, path) {
308
305
  if (this.method !== method) {
309
306
  return { matched: false, params: {} };
310
307
  }
308
+ if (this.isStatic) {
309
+ return path === this.path ? { matched: true, params: {} } : { matched: false, params: {} };
310
+ }
311
311
  const match = path.match(this.pattern);
312
312
  if (!match) {
313
313
  return { matched: false, params: {} };
@@ -4006,6 +4006,7 @@ class BunServer {
4006
4006
  this.server = Bun.serve({
4007
4007
  port: this.options.port ?? 3000,
4008
4008
  hostname: this.options.hostname,
4009
+ reusePort: this.options.reusePort,
4009
4010
  fetch: (request, server) => {
4010
4011
  if (this.isShuttingDown) {
4011
4012
  return new Response("Server is shutting down", { status: 503 });
@@ -4061,7 +4062,7 @@ class BunServer {
4061
4062
  }
4062
4063
  });
4063
4064
  const hostname = this.options.hostname ?? "localhost";
4064
- const port = this.options.port ?? 3000;
4065
+ const port = this.server.port;
4065
4066
  logger.info(`Server started at http://${hostname}:${port}`);
4066
4067
  }
4067
4068
  stop() {
@@ -4115,6 +4116,9 @@ class BunServer {
4115
4116
  return this.server !== undefined;
4116
4117
  }
4117
4118
  getPort() {
4119
+ if (this.server) {
4120
+ return this.server.port ?? this.options.port ?? 3000;
4121
+ }
4118
4122
  return this.options.port ?? 3000;
4119
4123
  }
4120
4124
  getHostname() {
@@ -4178,14 +4182,13 @@ class WebSocketGatewayRegistry {
4178
4182
  }
4179
4183
  return WebSocketGatewayRegistry.instance;
4180
4184
  }
4181
- parsePath(path) {
4185
+ static parsePath(path) {
4182
4186
  const paramNames = [];
4183
4187
  const patternString = path.replace(/:([^/]+)/g, (_, paramName) => {
4184
4188
  paramNames.push(paramName);
4185
4189
  return "([^/]+)";
4186
4190
  }).replace(/\*/g, ".*");
4187
- const pattern = new RegExp(`^${patternString}$`);
4188
- return { pattern, paramNames };
4191
+ return { pattern: new RegExp(`^${patternString}$`), paramNames };
4189
4192
  }
4190
4193
  register(gatewayClass) {
4191
4194
  const metadata = getGatewayMetadata(gatewayClass);
@@ -4199,7 +4202,7 @@ class WebSocketGatewayRegistry {
4199
4202
  if (!this.container.isRegistered(gatewayClass)) {
4200
4203
  this.container.register(gatewayClass);
4201
4204
  }
4202
- const { pattern, paramNames } = this.parsePath(metadata.path);
4205
+ const { pattern, paramNames } = WebSocketGatewayRegistry.parsePath(metadata.path);
4203
4206
  const isStatic = !metadata.path.includes(":") && !metadata.path.includes("*");
4204
4207
  const definition = {
4205
4208
  path: metadata.path,
@@ -4359,6 +4362,51 @@ init_types();
4359
4362
  init_module();
4360
4363
  init_decorators();
4361
4364
 
4365
+ // src/di/lifecycle.ts
4366
+ function hasOnModuleInit(instance) {
4367
+ return instance !== null && instance !== undefined && typeof instance === "object" && "onModuleInit" in instance && typeof instance.onModuleInit === "function";
4368
+ }
4369
+ function hasOnModuleDestroy(instance) {
4370
+ return instance !== null && instance !== undefined && typeof instance === "object" && "onModuleDestroy" in instance && typeof instance.onModuleDestroy === "function";
4371
+ }
4372
+ function hasOnApplicationBootstrap(instance) {
4373
+ return instance !== null && instance !== undefined && typeof instance === "object" && "onApplicationBootstrap" in instance && typeof instance.onApplicationBootstrap === "function";
4374
+ }
4375
+ function hasOnApplicationShutdown(instance) {
4376
+ return instance !== null && instance !== undefined && typeof instance === "object" && "onApplicationShutdown" in instance && typeof instance.onApplicationShutdown === "function";
4377
+ }
4378
+ async function callOnModuleInit(instances) {
4379
+ for (const instance of instances) {
4380
+ if (hasOnModuleInit(instance)) {
4381
+ await instance.onModuleInit();
4382
+ }
4383
+ }
4384
+ }
4385
+ async function callOnApplicationBootstrap(instances) {
4386
+ for (const instance of instances) {
4387
+ if (hasOnApplicationBootstrap(instance)) {
4388
+ await instance.onApplicationBootstrap();
4389
+ }
4390
+ }
4391
+ }
4392
+ async function callOnModuleDestroy(instances) {
4393
+ for (let i = instances.length - 1;i >= 0; i--) {
4394
+ const instance = instances[i];
4395
+ if (hasOnModuleDestroy(instance)) {
4396
+ await instance.onModuleDestroy();
4397
+ }
4398
+ }
4399
+ }
4400
+ async function callOnApplicationShutdown(instances, signal) {
4401
+ for (let i = instances.length - 1;i >= 0; i--) {
4402
+ const instance = instances[i];
4403
+ if (hasOnApplicationShutdown(instance)) {
4404
+ await instance.onApplicationShutdown(signal);
4405
+ }
4406
+ }
4407
+ }
4408
+
4409
+ // src/di/module-registry.ts
4362
4410
  class ModuleRegistry {
4363
4411
  static instance;
4364
4412
  moduleRefs = new Map;
@@ -4380,6 +4428,13 @@ class ModuleRegistry {
4380
4428
  getModuleRef(moduleClass) {
4381
4429
  return this.moduleRefs.get(moduleClass);
4382
4430
  }
4431
+ getAllModuleContainers() {
4432
+ const containers = [];
4433
+ for (const [, ref] of this.moduleRefs) {
4434
+ containers.push(ref.container);
4435
+ }
4436
+ return containers;
4437
+ }
4383
4438
  clear() {
4384
4439
  this.moduleRefs.clear();
4385
4440
  this.processing.clear();
@@ -4508,6 +4563,42 @@ class ModuleRegistry {
4508
4563
  }
4509
4564
  return moduleRef.middlewares;
4510
4565
  }
4566
+ resolveAllProviderInstances() {
4567
+ const instances = [];
4568
+ for (const [, ref] of this.moduleRefs) {
4569
+ for (const provider of ref.metadata.providers) {
4570
+ try {
4571
+ if (typeof provider === "function") {
4572
+ instances.push(ref.container.resolve(provider));
4573
+ } else if ("useClass" in provider) {
4574
+ const token = provider.provide ?? provider.useClass;
4575
+ instances.push(ref.container.resolve(token));
4576
+ } else if ("useValue" in provider) {
4577
+ instances.push(provider.useValue);
4578
+ } else if ("useFactory" in provider) {
4579
+ instances.push(ref.container.resolve(provider.provide));
4580
+ }
4581
+ } catch {}
4582
+ }
4583
+ }
4584
+ return instances;
4585
+ }
4586
+ async callModuleInitHooks() {
4587
+ const instances = this.resolveAllProviderInstances();
4588
+ await callOnModuleInit(instances);
4589
+ }
4590
+ async callBootstrapHooks() {
4591
+ const instances = this.resolveAllProviderInstances();
4592
+ await callOnApplicationBootstrap(instances);
4593
+ }
4594
+ async callModuleDestroyHooks() {
4595
+ const instances = this.resolveAllProviderInstances();
4596
+ await callOnModuleDestroy(instances);
4597
+ }
4598
+ async callShutdownHooks(signal) {
4599
+ const instances = this.resolveAllProviderInstances();
4600
+ await callOnApplicationShutdown(instances, signal);
4601
+ }
4511
4602
  registerExport(parentContainer, moduleRef, token) {
4512
4603
  if (!moduleRef.container.isRegistered(token)) {
4513
4604
  throw new Error(`Module ${moduleRef.moduleClass.name} cannot export ${typeof token === "function" ? token.name : String(token)} because it is not registered`);
@@ -4537,8 +4628,114 @@ var CONFIG_CENTER_TOKEN = Symbol("CONFIG_CENTER_TOKEN");
4537
4628
  // src/config/config-module.ts
4538
4629
  init_controller();
4539
4630
 
4631
+ // src/di/async-module.ts
4632
+ init_module();
4633
+ class AsyncProviderRegistry {
4634
+ static instance;
4635
+ pendingFactories = [];
4636
+ static getInstance() {
4637
+ if (!AsyncProviderRegistry.instance) {
4638
+ AsyncProviderRegistry.instance = new AsyncProviderRegistry;
4639
+ }
4640
+ return AsyncProviderRegistry.instance;
4641
+ }
4642
+ register(token, factory) {
4643
+ this.pendingFactories.push({ token, factory });
4644
+ }
4645
+ async initializeAll(container) {
4646
+ const moduleContainers = ModuleRegistry.getInstance().getAllModuleContainers();
4647
+ for (const { token, factory } of this.pendingFactories) {
4648
+ const value = await factory(container);
4649
+ container.registerInstance(token, value);
4650
+ for (const moduleContainer of moduleContainers) {
4651
+ if (moduleContainer.isRegistered(token)) {
4652
+ moduleContainer.registerInstance(token, value);
4653
+ }
4654
+ }
4655
+ }
4656
+ this.pendingFactories.length = 0;
4657
+ }
4658
+ hasPending() {
4659
+ return this.pendingFactories.length > 0;
4660
+ }
4661
+ clear() {
4662
+ this.pendingFactories.length = 0;
4663
+ }
4664
+ }
4665
+ function registerAsyncProviders(moduleClass, asyncOptions, tokenMap, extraProviders) {
4666
+ const providers = [];
4667
+ const exports = [];
4668
+ for (const [token] of tokenMap) {
4669
+ providers.push({
4670
+ provide: token,
4671
+ useFactory: () => {
4672
+ throw new Error(`Async provider ${String(token)} not yet initialized. ` + "Ensure Application.listen() is called before resolving async providers.");
4673
+ }
4674
+ });
4675
+ exports.push(token);
4676
+ }
4677
+ if (extraProviders) {
4678
+ providers.push(...extraProviders);
4679
+ }
4680
+ const registry = AsyncProviderRegistry.getInstance();
4681
+ for (const [token, mapper] of tokenMap) {
4682
+ registry.register(token, async (container) => {
4683
+ const deps = [];
4684
+ if (asyncOptions.inject) {
4685
+ for (const injectToken of asyncOptions.inject) {
4686
+ deps.push(container.resolve(injectToken));
4687
+ }
4688
+ }
4689
+ const config = await asyncOptions.useFactory(...deps);
4690
+ return mapper(config);
4691
+ });
4692
+ }
4693
+ const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, moduleClass) || {};
4694
+ const metadata = {
4695
+ ...existingMetadata,
4696
+ imports: [...existingMetadata.imports || [], ...asyncOptions.imports || []],
4697
+ providers: [...existingMetadata.providers || [], ...providers],
4698
+ exports: [...existingMetadata.exports || [], ...exports]
4699
+ };
4700
+ Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, moduleClass);
4701
+ return moduleClass;
4702
+ }
4703
+
4540
4704
  // src/config/service.ts
4541
4705
  class ConfigService {
4706
+ static parseConfigContent(content, format) {
4707
+ if (format === "jsonc") {
4708
+ return Bun.JSONC.parse(content);
4709
+ }
4710
+ if (format === "json5") {
4711
+ return Bun.JSON5.parse(content);
4712
+ }
4713
+ if (format === "json") {
4714
+ return JSON.parse(content);
4715
+ }
4716
+ try {
4717
+ return JSON.parse(content);
4718
+ } catch {
4719
+ try {
4720
+ return Bun.JSONC.parse(content);
4721
+ } catch {
4722
+ return Bun.JSON5.parse(content);
4723
+ }
4724
+ }
4725
+ }
4726
+ static async loadConfigFile(filePath) {
4727
+ const file = Bun.file(filePath);
4728
+ const content = await file.text();
4729
+ let format;
4730
+ if (filePath.endsWith(".json5")) {
4731
+ format = "json5";
4732
+ } else if (filePath.endsWith(".jsonc")) {
4733
+ format = "jsonc";
4734
+ } else if (filePath.endsWith(".json")) {
4735
+ format = "json";
4736
+ }
4737
+ return ConfigService.parseConfigContent(content, format);
4738
+ }
4542
4739
  config;
4543
4740
  namespace;
4544
4741
  configUpdateListeners = [];
@@ -4662,6 +4859,27 @@ class ConfigModule {
4662
4859
  Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, ConfigModule);
4663
4860
  return ConfigModule;
4664
4861
  }
4862
+ static forRootAsync(asyncOptions) {
4863
+ const tokenMap = new Map;
4864
+ tokenMap.set(CONFIG_SERVICE_TOKEN, async (options) => {
4865
+ const env = ConfigModule.snapshotEnv();
4866
+ const defaultConfig = options.defaultConfig ?? {};
4867
+ let fileConfig = {};
4868
+ if (options.configFiles?.length) {
4869
+ const fileConfigs = await Promise.all(options.configFiles.map((f) => ConfigService.loadConfigFile(f)));
4870
+ for (const fc of fileConfigs) {
4871
+ fileConfig = { ...fileConfig, ...fc };
4872
+ }
4873
+ }
4874
+ const loadedConfig = options.load ? options.load(env) : {};
4875
+ const mergedConfig = { ...defaultConfig, ...fileConfig, ...loadedConfig };
4876
+ if (options.validate) {
4877
+ options.validate(mergedConfig);
4878
+ }
4879
+ return new ConfigService(mergedConfig, options.namespace);
4880
+ });
4881
+ return registerAsyncProviders(ConfigModule, asyncOptions, tokenMap);
4882
+ }
4665
4883
  static snapshotEnv() {
4666
4884
  const env = {};
4667
4885
  for (const [key, value] of Object.entries(process.env)) {
@@ -4716,7 +4934,7 @@ class ConfigModule {
4716
4934
  for (const [configPath, configInfo] of configCenterOptions.configs.entries()) {
4717
4935
  loadPromises.push(configCenter.getConfig(configInfo.dataId, configInfo.groupName, configInfo.namespaceId).then((result) => {
4718
4936
  try {
4719
- const parsed = JSON.parse(result.content);
4937
+ const parsed = ConfigService.parseConfigContent(result.content);
4720
4938
  ConfigModule.setValueByPath(configMap, configPath, parsed);
4721
4939
  } catch {
4722
4940
  ConfigModule.setValueByPath(configMap, configPath, result.content);
@@ -4744,7 +4962,7 @@ class ConfigModule {
4744
4962
  try {
4745
4963
  let parsedValue;
4746
4964
  try {
4747
- parsedValue = JSON.parse(result.content);
4965
+ parsedValue = ConfigService.parseConfigContent(result.content);
4748
4966
  } catch {
4749
4967
  parsedValue = result.content;
4750
4968
  }
@@ -5391,6 +5609,18 @@ class CacheModule {
5391
5609
  Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, CacheModule);
5392
5610
  return CacheModule;
5393
5611
  }
5612
+ static forRootAsync(asyncOptions) {
5613
+ const tokenMap = new Map;
5614
+ tokenMap.set(CACHE_SERVICE_TOKEN, (config) => {
5615
+ const resolvedConfig = {
5616
+ ...config,
5617
+ store: config.store ?? new MemoryCacheStore({ cleanupInterval: 60000 })
5618
+ };
5619
+ return new CacheService(resolvedConfig);
5620
+ });
5621
+ tokenMap.set(CACHE_OPTIONS_TOKEN, (config) => config);
5622
+ return registerAsyncProviders(CacheModule, asyncOptions, tokenMap);
5623
+ }
5394
5624
  static getPostProcessor() {
5395
5625
  return CacheModule.postProcessor;
5396
5626
  }
@@ -5852,14 +6082,22 @@ class Application {
5852
6082
  if (this.server?.isRunning()) {
5853
6083
  throw new Error("Application is already running");
5854
6084
  }
6085
+ const asyncRegistry = AsyncProviderRegistry.getInstance();
6086
+ if (asyncRegistry.hasPending()) {
6087
+ await asyncRegistry.initializeAll(this.getContainer());
6088
+ }
5855
6089
  await this.initializeExtensions();
5856
6090
  await this.initializeConfigCenter();
5857
6091
  this.initializeEventListeners();
6092
+ const registry = ModuleRegistry.getInstance();
6093
+ await registry.callModuleInitHooks();
6094
+ await registry.callBootstrapHooks();
5858
6095
  const finalPort = port ?? this.options.port ?? 3000;
5859
6096
  const finalHostname = hostname ?? this.options.hostname;
5860
6097
  const serverOptions = {
5861
6098
  port: finalPort,
5862
6099
  hostname: finalHostname,
6100
+ reusePort: this.options.reusePort,
5863
6101
  fetch: this.handleRequest.bind(this),
5864
6102
  websocketRegistry: this.websocketRegistry,
5865
6103
  gracefulShutdownTimeout: this.options.gracefulShutdownTimeout
@@ -5910,12 +6148,18 @@ class Application {
5910
6148
  }
5911
6149
  async stop() {
5912
6150
  this.removeSignalHandlers();
6151
+ const moduleRegistry = ModuleRegistry.getInstance();
6152
+ await moduleRegistry.callShutdownHooks();
6153
+ await moduleRegistry.callModuleDestroyHooks();
5913
6154
  await this.deregisterServices();
5914
6155
  await this.closeExtensions();
5915
6156
  this.server?.stop();
5916
6157
  }
5917
6158
  async gracefulShutdown(timeout) {
5918
6159
  this.removeSignalHandlers();
6160
+ const moduleRegistry = ModuleRegistry.getInstance();
6161
+ await moduleRegistry.callShutdownHooks();
6162
+ await moduleRegistry.callModuleDestroyHooks();
5919
6163
  await this.deregisterServices();
5920
6164
  await this.closeExtensions();
5921
6165
  if (this.server) {
@@ -6057,6 +6301,110 @@ class Application {
6057
6301
  this.signalHandlersInstalled = false;
6058
6302
  }
6059
6303
  }
6304
+ // src/core/apply-decorators.ts
6305
+ function applyDecorators(...decorators) {
6306
+ return (target, propertyKey, descriptor) => {
6307
+ for (const decorator of decorators.reverse()) {
6308
+ if (descriptor) {
6309
+ const result = decorator(target, propertyKey, descriptor);
6310
+ if (result) {
6311
+ descriptor = result;
6312
+ }
6313
+ } else if (propertyKey) {
6314
+ decorator(target, propertyKey);
6315
+ } else {
6316
+ decorator(target);
6317
+ }
6318
+ }
6319
+ return descriptor;
6320
+ };
6321
+ }
6322
+ // src/core/cluster.ts
6323
+ var {spawn } = globalThis.Bun;
6324
+ import { LoggerManager as LoggerManager12 } from "@dangao/logsmith";
6325
+
6326
+ class ClusterManager {
6327
+ workerCount;
6328
+ workers = [];
6329
+ scriptPath;
6330
+ port;
6331
+ hostname;
6332
+ constructor(options) {
6333
+ this.workerCount = options.workers === "auto" ? navigator.hardwareConcurrency : options.workers;
6334
+ this.scriptPath = options.scriptPath;
6335
+ this.port = options.port;
6336
+ this.hostname = options.hostname;
6337
+ }
6338
+ start() {
6339
+ const logger = LoggerManager12.getLogger();
6340
+ logger.info(`[Cluster] Starting ${this.workerCount} workers on port ${this.port}`);
6341
+ for (let i = 0;i < this.workerCount; i++) {
6342
+ const worker = spawn({
6343
+ cmd: ["bun", "run", this.scriptPath],
6344
+ env: {
6345
+ ...process.env,
6346
+ PORT: String(this.port),
6347
+ REUSE_PORT: "1",
6348
+ CLUSTER_WORKER: "1",
6349
+ WORKER_ID: String(i),
6350
+ ...this.hostname ? { HOSTNAME: this.hostname } : {}
6351
+ },
6352
+ stdout: "inherit",
6353
+ stderr: "inherit"
6354
+ });
6355
+ this.workers.push(worker);
6356
+ }
6357
+ logger.info(`[Cluster] ${this.workerCount} workers started (reusePort mode)`);
6358
+ this.monitorWorkers();
6359
+ }
6360
+ async stop() {
6361
+ const logger = LoggerManager12.getLogger();
6362
+ logger.info("[Cluster] Stopping all workers...");
6363
+ for (const worker of this.workers) {
6364
+ worker.kill("SIGTERM");
6365
+ }
6366
+ const timeout = setTimeout(() => {
6367
+ for (const worker of this.workers) {
6368
+ worker.kill("SIGKILL");
6369
+ }
6370
+ }, 5000);
6371
+ await Promise.all(this.workers.map((w) => w.exited));
6372
+ clearTimeout(timeout);
6373
+ this.workers.length = 0;
6374
+ logger.info("[Cluster] All workers stopped");
6375
+ }
6376
+ monitorWorkers() {
6377
+ for (let i = 0;i < this.workers.length; i++) {
6378
+ const index = i;
6379
+ this.workers[index].exited.then((exitCode) => {
6380
+ if (exitCode !== 0 && exitCode !== null) {
6381
+ const logger = LoggerManager12.getLogger();
6382
+ logger.warn(`[Cluster] Worker ${index} exited with code ${exitCode}, restarting...`);
6383
+ const newWorker = spawn({
6384
+ cmd: ["bun", "run", this.scriptPath],
6385
+ env: {
6386
+ ...process.env,
6387
+ PORT: String(this.port),
6388
+ REUSE_PORT: "1",
6389
+ CLUSTER_WORKER: "1",
6390
+ WORKER_ID: String(index),
6391
+ ...this.hostname ? { HOSTNAME: this.hostname } : {}
6392
+ },
6393
+ stdout: "inherit",
6394
+ stderr: "inherit"
6395
+ });
6396
+ this.workers[index] = newWorker;
6397
+ }
6398
+ });
6399
+ }
6400
+ }
6401
+ static isWorker() {
6402
+ return process.env.CLUSTER_WORKER === "1";
6403
+ }
6404
+ static getWorkerId() {
6405
+ return Number(process.env.WORKER_ID ?? -1);
6406
+ }
6407
+ }
6060
6408
 
6061
6409
  // src/index.ts
6062
6410
  init_context_service();
@@ -6406,6 +6754,88 @@ class SwaggerGenerator {
6406
6754
  }
6407
6755
  return document;
6408
6756
  }
6757
+ generateMarkdown() {
6758
+ const doc = this.generate();
6759
+ const lines = [];
6760
+ lines.push(`# ${doc.info.title}`);
6761
+ lines.push("");
6762
+ if (doc.info.description) {
6763
+ lines.push(doc.info.description);
6764
+ lines.push("");
6765
+ }
6766
+ lines.push(`**Version:** ${doc.info.version}`);
6767
+ lines.push("");
6768
+ if (doc.servers?.length) {
6769
+ lines.push("## Servers");
6770
+ lines.push("");
6771
+ for (const server of doc.servers) {
6772
+ lines.push(`- \`${server.url}\`${server.description ? " \u2014 " + server.description : ""}`);
6773
+ }
6774
+ lines.push("");
6775
+ }
6776
+ if (doc.tags?.length) {
6777
+ lines.push("## Tags");
6778
+ lines.push("");
6779
+ for (const tag of doc.tags) {
6780
+ lines.push(`- **${tag.name}**${tag.description ? ": " + tag.description : ""}`);
6781
+ }
6782
+ lines.push("");
6783
+ }
6784
+ lines.push("## Endpoints");
6785
+ lines.push("");
6786
+ const methods = ["get", "post", "put", "delete", "patch"];
6787
+ for (const [path, pathObj] of Object.entries(doc.paths)) {
6788
+ for (const method of methods) {
6789
+ const operation = pathObj[method];
6790
+ if (!operation)
6791
+ continue;
6792
+ lines.push(`### \`${method.toUpperCase()}\` ${path}`);
6793
+ lines.push("");
6794
+ if (operation.summary) {
6795
+ lines.push(`**${operation.summary}**`);
6796
+ lines.push("");
6797
+ }
6798
+ if (operation.description) {
6799
+ lines.push(operation.description);
6800
+ lines.push("");
6801
+ }
6802
+ if (operation.tags?.length) {
6803
+ lines.push(`Tags: ${operation.tags.map((t) => `\`${t}\``).join(", ")}`);
6804
+ lines.push("");
6805
+ }
6806
+ if (operation.deprecated) {
6807
+ lines.push("> **Deprecated**");
6808
+ lines.push("");
6809
+ }
6810
+ if (operation.parameters?.length) {
6811
+ lines.push("| Parameter | In | Type | Required | Description |");
6812
+ lines.push("|---|---|---|---|---|");
6813
+ for (const param of operation.parameters) {
6814
+ const type = param.schema?.type ?? "string";
6815
+ const required = param.required ? "Yes" : "No";
6816
+ lines.push(`| \`${param.name}\` | ${param.in} | ${type} | ${required} | ${param.description ?? ""} |`);
6817
+ }
6818
+ lines.push("");
6819
+ }
6820
+ if (operation.responses) {
6821
+ lines.push("**Responses:**");
6822
+ lines.push("");
6823
+ for (const [status, resp] of Object.entries(operation.responses)) {
6824
+ lines.push(`- **${status}**: ${resp.description ?? ""}`);
6825
+ }
6826
+ lines.push("");
6827
+ }
6828
+ lines.push("---");
6829
+ lines.push("");
6830
+ }
6831
+ }
6832
+ return lines.join(`
6833
+ `);
6834
+ }
6835
+ generateMarkdownHtml() {
6836
+ const md = this.generateMarkdown();
6837
+ return Bun.markdown.html(md, { headings: true });
6838
+ }
6409
6839
  normalizePath(path) {
6410
6840
  path = path.replace(/\/+/g, "/");
6411
6841
  if (!path.startsWith("/")) {
@@ -7740,6 +8170,417 @@ HealthModule = __legacyDecorateClassTS([
7740
8170
 
7741
8171
  // src/health/index.ts
7742
8172
  init_types5();
8173
+ // src/dashboard/dashboard-module.ts
8174
+ init_module();
8175
+
8176
+ // src/dashboard/controller.ts
8177
+ init_registry();
8178
+
8179
+ // src/dashboard/ui.ts
8180
+ function createDashboardHTML(basePath) {
8181
+ const apiBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
8182
+ return `<!DOCTYPE html>
8183
+ <html lang="en">
8184
+ <head>
8185
+ <meta charset="UTF-8">
8186
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8187
+ <title>Bun Server Dashboard</title>
8188
+ <style>
8189
+ :root {
8190
+ --bg-primary: #0f0f12;
8191
+ --bg-secondary: #18181c;
8192
+ --bg-card: #1e1e24;
8193
+ --border: #2a2a32;
8194
+ --text-primary: #e4e4e7;
8195
+ --text-secondary: #a1a1aa;
8196
+ --accent: #6366f1;
8197
+ --accent-hover: #818cf8;
8198
+ --success: #22c55e;
8199
+ --warning: #eab308;
8200
+ --error: #ef4444;
8201
+ }
8202
+ * { box-sizing: border-box; margin: 0; padding: 0; }
8203
+ body {
8204
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
8205
+ background: var(--bg-primary);
8206
+ color: var(--text-primary);
8207
+ min-height: 100vh;
8208
+ padding: 1.5rem;
8209
+ line-height: 1.5;
8210
+ }
8211
+ .header {
8212
+ margin-bottom: 1.5rem;
8213
+ padding-bottom: 1rem;
8214
+ border-bottom: 1px solid var(--border);
8215
+ }
8216
+ .header h1 {
8217
+ font-size: 1.5rem;
8218
+ font-weight: 600;
8219
+ color: var(--text-primary);
8220
+ }
8221
+ .header p {
8222
+ font-size: 0.875rem;
8223
+ color: var(--text-secondary);
8224
+ margin-top: 0.25rem;
8225
+ }
8226
+ .grid {
8227
+ display: grid;
8228
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
8229
+ gap: 1rem;
8230
+ margin-bottom: 1.5rem;
8231
+ }
8232
+ .card {
8233
+ background: var(--bg-card);
8234
+ border: 1px solid var(--border);
8235
+ border-radius: 0.5rem;
8236
+ padding: 1rem;
8237
+ overflow: hidden;
8238
+ }
8239
+ .card-title {
8240
+ font-size: 0.875rem;
8241
+ font-weight: 600;
8242
+ color: var(--text-secondary);
8243
+ text-transform: uppercase;
8244
+ letter-spacing: 0.05em;
8245
+ margin-bottom: 0.75rem;
8246
+ }
8247
+ .stat-row {
8248
+ display: flex;
8249
+ justify-content: space-between;
8250
+ padding: 0.375rem 0;
8251
+ font-size: 0.875rem;
8252
+ }
8253
+ .stat-label { color: var(--text-secondary); }
8254
+ .stat-value { font-weight: 500; }
8255
+ .status-up { color: var(--success); }
8256
+ .status-down { color: var(--error); }
8257
+ .table-wrap {
8258
+ overflow-x: auto;
8259
+ margin: 0 -1rem -1rem;
8260
+ }
8261
+ table {
8262
+ width: 100%;
8263
+ border-collapse: collapse;
8264
+ font-size: 0.8125rem;
8265
+ }
8266
+ th, td {
8267
+ padding: 0.5rem 1rem;
8268
+ text-align: left;
8269
+ border-top: 1px solid var(--border);
8270
+ }
8271
+ th {
8272
+ color: var(--text-secondary);
8273
+ font-weight: 500;
8274
+ text-transform: uppercase;
8275
+ letter-spacing: 0.05em;
8276
+ }
8277
+ .method {
8278
+ display: inline-block;
8279
+ padding: 0.125rem 0.375rem;
8280
+ border-radius: 0.25rem;
8281
+ font-size: 0.75rem;
8282
+ font-weight: 600;
8283
+ }
8284
+ .method-GET { background: rgba(34, 197, 94, 0.2); color: var(--success); }
8285
+ .method-POST { background: rgba(99, 102, 241, 0.2); color: var(--accent); }
8286
+ .method-PUT { background: rgba(234, 179, 8, 0.2); color: var(--warning); }
8287
+ .method-DELETE { background: rgba(239, 68, 68, 0.2); color: var(--error); }
8288
+ .method-PATCH { background: rgba(168, 85, 247, 0.2); color: #a855f7; }
8289
+ .refresh-badge {
8290
+ display: inline-block;
8291
+ font-size: 0.75rem;
8292
+ color: var(--text-secondary);
8293
+ margin-top: 1rem;
8294
+ }
8295
+ .loading { color: var(--text-secondary); font-style: italic; }
8296
+ .error-msg { color: var(--error); font-size: 0.875rem; }
8297
+ </style>
8298
+ </head>
8299
+ <body>
8300
+ <div class="header">
8301
+ <h1>Bun Server Dashboard</h1>
8302
+ <p>Monitoring UI - Auto-refresh every 5 seconds</p>
8303
+ </div>
8304
+ <div class="grid">
8305
+ <div class="card">
8306
+ <div class="card-title">System Info</div>
8307
+ <div id="system-info" class="loading">Loading...</div>
8308
+ </div>
8309
+ <div class="card">
8310
+ <div class="card-title">Health Status</div>
8311
+ <div id="health-info" class="loading">Loading...</div>
8312
+ </div>
8313
+ </div>
8314
+ <div class="card">
8315
+ <div class="card-title">Registered Routes</div>
8316
+ <div class="table-wrap">
8317
+ <table>
8318
+ <thead>
8319
+ <tr>
8320
+ <th>Method</th>
8321
+ <th>Path</th>
8322
+ <th>Controller</th>
8323
+ <th>Method</th>
8324
+ </tr>
8325
+ </thead>
8326
+ <tbody id="routes-body">
8327
+ <tr><td colspan="4" class="loading">Loading...</td></tr>
8328
+ </tbody>
8329
+ </table>
8330
+ </div>
8331
+ </div>
8332
+ <div class="refresh-badge" id="last-update">Last update: -</div>
8333
+ <script>
8334
+ (function() {
8335
+ var base = '${apiBase}';
8336
+ function fetchJson(path) {
8337
+ return fetch(base + path).then(function(r) {
8338
+ if (!r.ok) throw new Error(r.status);
8339
+ return r.json();
8340
+ });
8341
+ }
8342
+ function formatBytes(n) {
8343
+ if (n < 1024) return n + ' B';
8344
+ if (n < 1048576) return (n / 1024).toFixed(1) + ' KB';
8345
+ return (n / 1048576).toFixed(2) + ' MB';
8346
+ }
8347
+ function renderSystem(data) {
8348
+ var el = document.getElementById('system-info');
8349
+ if (!data) { el.innerHTML = '<span class="error-msg">Failed to load</span>'; return; }
8350
+ el.innerHTML = '<div class="stat-row"><span class="stat-label">Uptime</span><span class="stat-value">' + Math.floor(data.uptime || 0) + 's</span></div>' +
8351
+ '<div class="stat-row"><span class="stat-label">RSS</span><span class="stat-value">' + formatBytes(data.memory?.rss || 0) + '</span></div>' +
8352
+ '<div class="stat-row"><span class="stat-label">Heap Used</span><span class="stat-value">' + formatBytes(data.memory?.heapUsed || 0) + '</span></div>' +
8353
+ '<div class="stat-row"><span class="stat-label">Heap Total</span><span class="stat-value">' + formatBytes(data.memory?.heapTotal || 0) + '</span></div>' +
8354
+ '<div class="stat-row"><span class="stat-label">Platform</span><span class="stat-value">' + (data.platform || '-') + '</span></div>' +
8355
+ '<div class="stat-row"><span class="stat-label">Bun</span><span class="stat-value">' + (data.bunVersion || '-') + '</span></div>';
8356
+ }
8357
+ function renderHealth(data) {
8358
+ var el = document.getElementById('health-info');
8359
+ if (!data) { el.innerHTML = '<span class="error-msg">Failed to load</span>'; return; }
8360
+ var statusClass = data.status === 'up' ? 'status-up' : 'status-down';
8361
+ el.innerHTML = '<div class="stat-row"><span class="stat-label">Status</span><span class="stat-value ' + statusClass + '">' + (data.status || 'unknown') + '</span></div>' +
8362
+ '<div class="stat-row"><span class="stat-label">Timestamp</span><span class="stat-value">' + (data.timestamp ? new Date(data.timestamp).toLocaleTimeString() : '-') + '</span></div>';
8363
+ }
8364
+ function renderRoutes(data) {
8365
+ var tbody = document.getElementById('routes-body');
8366
+ if (!data || !Array.isArray(data)) {
8367
+ tbody.innerHTML = '<tr><td colspan="4" class="error-msg">Failed to load</td></tr>';
8368
+ return;
8369
+ }
8370
+ if (data.length === 0) {
8371
+ tbody.innerHTML = '<tr><td colspan="4" class="loading">No routes registered</td></tr>';
8372
+ return;
8373
+ }
8374
+ tbody.innerHTML = data.map(function(r) {
8375
+ return '<tr><td><span class="method method-' + (r.method || 'GET') + '">' + (r.method || 'GET') + '</span></td>' +
8376
+ '<td><code>' + (r.path || '') + '</code></td>' +
8377
+ '<td>' + (r.controller || '-') + '</td>' +
8378
+ '<td>' + (r.methodName || '-') + '</td></tr>';
8379
+ }).join('');
8380
+ }
8381
+ function update() {
8382
+ fetchJson('/api/system').then(renderSystem).catch(function() { renderSystem(null); });
8383
+ fetchJson('/api/health').then(renderHealth).catch(function() { renderHealth(null); });
8384
+ fetchJson('/api/routes').then(renderRoutes).catch(function() { renderRoutes(null); });
8385
+ document.getElementById('last-update').textContent = 'Last update: ' + new Date().toLocaleTimeString();
8386
+ }
8387
+ update();
8388
+ setInterval(update, 5000);
8389
+ })();
8390
+ </script>
8391
+ </body>
8392
+ </html>`;
8393
+ }
8394
+
8395
+ // src/dashboard/controller.ts
8396
+ init_controller();
8397
+ init_types5();
8398
+
8399
+ class DashboardService {
8400
+ basePath;
8401
+ auth;
8402
+ startTime = Date.now();
8403
+ constructor(basePath, auth) {
8404
+ this.basePath = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
8405
+ this.auth = auth;
8406
+ }
8407
+ registerRoutes() {
8408
+ const registry = RouteRegistry.getInstance();
8409
+ registry.register("GET", this.basePath, (ctx) => this.handleDashboard(ctx));
8410
+ registry.register("GET", `${this.basePath}/api/system`, (ctx) => this.handleSystem(ctx));
8411
+ registry.register("GET", `${this.basePath}/api/routes`, (ctx) => this.handleRoutes(ctx));
8412
+ registry.register("GET", `${this.basePath}/api/health`, (ctx) => this.handleHealth(ctx));
8413
+ registry.register("POST", `${this.basePath}/api/markdown`, (ctx) => this.handleMarkdownRender(ctx));
8414
+ }
8415
+ checkAuth(ctx) {
8416
+ if (!this.auth) {
8417
+ return true;
8418
+ }
8419
+ const authHeader = ctx.getHeader("Authorization");
8420
+ if (!authHeader || !authHeader.startsWith("Basic ")) {
8421
+ return false;
8422
+ }
8423
+ try {
8424
+ const decoded = atob(authHeader.slice(6));
8425
+ const [username, password] = decoded.split(":");
8426
+ return username === this.auth.username && password === this.auth.password;
8427
+ } catch {
8428
+ return false;
8429
+ }
8430
+ }
8431
+ unauthorizedResponse() {
8432
+ return new Response("Unauthorized", {
8433
+ status: 401,
8434
+ headers: {
8435
+ "WWW-Authenticate": 'Basic realm="Dashboard"',
8436
+ "Content-Type": "text/plain"
8437
+ }
8438
+ });
8439
+ }
8440
+ async handleDashboard(ctx) {
8441
+ if (!this.checkAuth(ctx)) {
8442
+ return this.unauthorizedResponse();
8443
+ }
8444
+ const html = createDashboardHTML(this.basePath);
8445
+ return new Response(html, {
8446
+ headers: { "Content-Type": "text/html; charset=utf-8" }
8447
+ });
8448
+ }
8449
+ async handleSystem(ctx) {
8450
+ if (!this.checkAuth(ctx)) {
8451
+ return this.unauthorizedResponse();
8452
+ }
8453
+ const mem = process.memoryUsage();
8454
+ const data = {
8455
+ uptime: Math.floor(process.uptime()),
8456
+ memory: {
8457
+ rss: mem.rss,
8458
+ heapUsed: mem.heapUsed,
8459
+ heapTotal: mem.heapTotal
8460
+ },
8461
+ platform: process.platform,
8462
+ bunVersion: typeof Bun !== "undefined" ? Bun.version : undefined
8463
+ };
8464
+ return new Response(JSON.stringify(data), {
8465
+ headers: { "Content-Type": "application/json; charset=utf-8" }
8466
+ });
8467
+ }
8468
+ async handleRoutes(ctx) {
8469
+ if (!this.checkAuth(ctx)) {
8470
+ return this.unauthorizedResponse();
8471
+ }
8472
+ const registry = RouteRegistry.getInstance();
8473
+ const router = registry.getRouter();
8474
+ const routes = router.getRoutes();
8475
+ const data = routes.map((r) => ({
8476
+ method: r.method,
8477
+ path: r.path,
8478
+ controller: r.controllerClass?.name ?? undefined,
8479
+ methodName: r.methodName ?? undefined
8480
+ }));
8481
+ return new Response(JSON.stringify(data), {
8482
+ headers: { "Content-Type": "application/json; charset=utf-8" }
8483
+ });
8484
+ }
8485
+ async handleMarkdownRender(ctx) {
8486
+ if (!this.checkAuth(ctx)) {
8487
+ return this.unauthorizedResponse();
8488
+ }
8489
+ try {
8490
+ const body = await ctx.request.json();
8491
+ if (!body.content) {
8492
+ return new Response(JSON.stringify({ error: "content field is required" }), {
8493
+ status: 400,
8494
+ headers: { "Content-Type": "application/json; charset=utf-8" }
8495
+ });
8496
+ }
8497
+ const html = Bun.markdown.html(body.content, { headings: true });
8498
+ return new Response(JSON.stringify({ html }), {
8499
+ headers: { "Content-Type": "application/json; charset=utf-8" }
8500
+ });
8501
+ } catch (err) {
8502
+ return new Response(JSON.stringify({ error: err.message }), {
8503
+ status: 500,
8504
+ headers: { "Content-Type": "application/json; charset=utf-8" }
8505
+ });
8506
+ }
8507
+ }
8508
+ async handleHealth(ctx) {
8509
+ if (!this.checkAuth(ctx)) {
8510
+ return this.unauthorizedResponse();
8511
+ }
8512
+ try {
8513
+ const container = ControllerRegistry.getInstance().getContainer();
8514
+ const indicators = container.resolve(HEALTH_INDICATORS_TOKEN);
8515
+ const details = {};
8516
+ for (const indicator of indicators) {
8517
+ try {
8518
+ const result = await indicator.check();
8519
+ details[indicator.name] = result;
8520
+ } catch (err) {
8521
+ details[indicator.name] = {
8522
+ status: "down",
8523
+ details: { error: err.message }
8524
+ };
8525
+ }
8526
+ }
8527
+ const allUp = Object.keys(details).length === 0 || Object.values(details).every((r) => r.status === "up");
8528
+ const data = {
8529
+ status: allUp ? "up" : "down",
8530
+ timestamp: Date.now(),
8531
+ details
8532
+ };
8533
+ return new Response(JSON.stringify(data), {
8534
+ headers: { "Content-Type": "application/json; charset=utf-8" }
8535
+ });
8536
+ } catch {
8537
+ const data = {
8538
+ status: "up",
8539
+ timestamp: Date.now()
8540
+ };
8541
+ return new Response(JSON.stringify(data), {
8542
+ headers: { "Content-Type": "application/json; charset=utf-8" }
8543
+ });
8544
+ }
8545
+ }
8546
+ }
8547
+
8548
+ // src/dashboard/dashboard-extension.ts
8549
+ class DashboardExtension {
8550
+ service;
8551
+ constructor(service) {
8552
+ this.service = service;
8553
+ }
8554
+ register(_container) {
8555
+ this.service.registerRoutes();
8556
+ }
8557
+ }
8558
+
8559
+ // src/dashboard/dashboard-module.ts
8560
+ class DashboardModule {
8561
+ static forRoot(options = {}) {
8562
+ const path = options.path ?? "/_dashboard";
8563
+ const basePath = path.endsWith("/") ? path.slice(0, -1) : path;
8564
+ const service = new DashboardService(basePath, options.auth);
8565
+ const extension = new DashboardExtension(service);
8566
+ const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, DashboardModule) || {};
8567
+ const metadata = {
8568
+ ...existingMetadata,
8569
+ extensions: [extension]
8570
+ };
8571
+ Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, DashboardModule);
8572
+ return DashboardModule;
8573
+ }
8574
+ }
8575
+ DashboardModule = __legacyDecorateClassTS([
8576
+ Module({
8577
+ extensions: [],
8578
+ controllers: [],
8579
+ providers: []
8580
+ })
8581
+ ], DashboardModule);
8582
+ // src/dashboard/types.ts
8583
+ var DASHBOARD_OPTIONS_TOKEN = Symbol("@dangao/bun-server:dashboard:options");
7743
8584
  // src/metrics/metrics-module.ts
7744
8585
  init_module();
7745
8586
 
@@ -8946,6 +9787,12 @@ class DatabaseModule {
8946
9787
  Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, DatabaseModule);
8947
9788
  return DatabaseModule;
8948
9789
  }
9790
+ static forRootAsync(asyncOptions) {
9791
+ const tokenMap = new Map;
9792
+ tokenMap.set(DATABASE_SERVICE_TOKEN, (config) => new DatabaseService2(config));
9793
+ tokenMap.set(DATABASE_OPTIONS_TOKEN, (config) => config);
9794
+ return registerAsyncProviders(DatabaseModule, asyncOptions, tokenMap);
9795
+ }
8949
9796
  static createHealthIndicator(databaseService) {
8950
9797
  return new DatabaseHealthIndicator(databaseService);
8951
9798
  }
@@ -9095,6 +9942,707 @@ class DrizzleBaseRepository {
9095
9942
  this.databaseService = databaseService;
9096
9943
  }
9097
9944
  }
9945
+ // src/debug/debug-module.ts
9946
+ init_module();
9947
+
9948
+ // src/debug/recorder.ts
9949
+ class RequestRecorder {
9950
+ buffer;
9951
+ maxRecords;
9952
+ writeIndex = 0;
9953
+ count = 0;
9954
+ idCounter = 0;
9955
+ idMap = new Map;
9956
+ constructor(maxRecords = 500) {
9957
+ this.maxRecords = maxRecords;
9958
+ this.buffer = new Array(maxRecords).fill(null);
9959
+ }
9960
+ generateId() {
9961
+ this.idCounter += 1;
9962
+ const ts = Date.now().toString(16);
9963
+ const counter = this.idCounter.toString(16);
9964
+ return `${ts}-${counter}`;
9965
+ }
9966
+ record(record) {
9967
+ const id = this.generateId();
9968
+ const fullRecord = { ...record, id };
9969
+ const oldRecord = this.buffer[this.writeIndex];
9970
+ if (oldRecord) {
9971
+ this.idMap.delete(oldRecord.id);
9972
+ }
9973
+ this.buffer[this.writeIndex] = fullRecord;
9974
+ this.idMap.set(id, this.writeIndex);
9975
+ this.writeIndex = (this.writeIndex + 1) % this.maxRecords;
9976
+ if (this.count < this.maxRecords) {
9977
+ this.count += 1;
9978
+ }
9979
+ }
9980
+ getAll() {
9981
+ const records = [];
9982
+ for (let i = 0;i < this.count; i++) {
9983
+ const idx = (this.writeIndex - 1 - i + this.maxRecords) % this.maxRecords;
9984
+ const record = this.buffer[idx];
9985
+ if (record) {
9986
+ records.push(record);
9987
+ }
9988
+ }
9989
+ return records;
9990
+ }
9991
+ getById(id) {
9992
+ const index = this.idMap.get(id);
9993
+ if (index === undefined) {
9994
+ return;
9995
+ }
9996
+ return this.buffer[index] ?? undefined;
9997
+ }
9998
+ clear() {
9999
+ for (let i = 0;i < this.maxRecords; i++) {
10000
+ this.buffer[i] = null;
10001
+ }
10002
+ this.writeIndex = 0;
10003
+ this.count = 0;
10004
+ this.idMap.clear();
10005
+ }
10006
+ getCount() {
10007
+ return this.count;
10008
+ }
10009
+ exportToJsonl() {
10010
+ const records = this.getAll();
10011
+ return records.map((r) => JSON.stringify(r)).join(`
10012
+ `) + `
10013
+ `;
10014
+ }
10015
+ importFromJsonl(content) {
10016
+ const records = Bun.JSONL.parse(content);
10017
+ for (const record of records) {
10018
+ if (record.id) {
10019
+ const oldRecord = this.buffer[this.writeIndex];
10020
+ if (oldRecord) {
10021
+ this.idMap.delete(oldRecord.id);
10022
+ }
10023
+ this.buffer[this.writeIndex] = record;
10024
+ this.idMap.set(record.id, this.writeIndex);
10025
+ this.writeIndex = (this.writeIndex + 1) % this.maxRecords;
10026
+ if (this.count < this.maxRecords) {
10027
+ this.count += 1;
10028
+ }
10029
+ }
10030
+ }
10031
+ }
10032
+ static parseJsonl(content) {
10033
+ return Bun.JSONL.parse(content);
10034
+ }
10035
+ }
10036
+
10037
+ // src/debug/middleware.ts
10038
+ init_registry();
10039
+ function headersToRecord(headers) {
10040
+ const record = {};
10041
+ headers.forEach((value, key) => {
10042
+ record[key] = value;
10043
+ });
10044
+ return record;
10045
+ }
10046
+ async function getResponseBodySize(response) {
10047
+ const contentLength = response.headers.get("content-length");
10048
+ if (contentLength) {
10049
+ const n = parseInt(contentLength, 10);
10050
+ if (!Number.isNaN(n)) {
10051
+ return n;
10052
+ }
10053
+ }
10054
+ if (response.body) {
10055
+ const clone = response.clone();
10056
+ const buf = await clone.arrayBuffer();
10057
+ return buf.byteLength;
10058
+ }
10059
+ return 0;
10060
+ }
10061
+ function createDebugMiddleware(recorder, options = {}) {
10062
+ const recordBody = options.recordBody ?? true;
10063
+ const basePath = options.basePath ?? "/_debug";
10064
+ return async (context2, next) => {
10065
+ const startTime = Date.now();
10066
+ const requestHeaders = {};
10067
+ context2.headers.forEach((value, key) => {
10068
+ requestHeaders[key] = value;
10069
+ });
10070
+ let requestBody = undefined;
10071
+ if (recordBody && ["POST", "PUT", "PATCH"].includes(context2.method)) {
10072
+ try {
10073
+ requestBody = await context2.getBody();
10074
+ } catch {
10075
+ requestBody = undefined;
10076
+ }
10077
+ }
10078
+ const response = await next();
10079
+ const endTime = Date.now();
10080
+ const totalMs = endTime - startTime;
10081
+ if (context2.path.startsWith(basePath)) {
10082
+ return response;
10083
+ }
10084
+ const responseHeaders = headersToRecord(response.headers);
10085
+ const bodySize = await getResponseBodySize(response);
10086
+ const routeHandler = context2.routeHandler;
10087
+ let matchedRoute;
10088
+ try {
10089
+ const registry = RouteRegistry.getInstance();
10090
+ const router = registry.getRouter();
10091
+ const route = router.findRoute(context2.method, context2.path);
10092
+ if (route) {
10093
+ matchedRoute = route.path;
10094
+ }
10095
+ } catch {
10096
+ matchedRoute = undefined;
10097
+ }
10098
+ const record = {
10099
+ timestamp: startTime,
10100
+ request: {
10101
+ method: context2.method,
10102
+ path: context2.path,
10103
+ headers: requestHeaders,
10104
+ body: requestBody
10105
+ },
10106
+ response: {
10107
+ status: response.status,
10108
+ headers: responseHeaders,
10109
+ bodySize
10110
+ },
10111
+ timing: {
10112
+ total: totalMs
10113
+ },
10114
+ metadata: {
10115
+ matchedRoute,
10116
+ controller: routeHandler?.controller?.name,
10117
+ methodName: routeHandler?.method
10118
+ }
10119
+ };
10120
+ recorder.record(record);
10121
+ return response;
10122
+ };
10123
+ }
10124
+
10125
+ // src/debug/ui.ts
10126
+ function createDebugHTML(basePath) {
10127
+ const apiBase = `${basePath}/api`;
10128
+ const normalizePath = (p) => p.endsWith("/") && p.length > 1 ? p.slice(0, -1) : p;
10129
+ const base = normalizePath(basePath);
10130
+ return `<!DOCTYPE html>
10131
+ <html lang="en">
10132
+ <head>
10133
+ <meta charset="UTF-8">
10134
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
10135
+ <title>Debug - Request Replay</title>
10136
+ <style>
10137
+ :root {
10138
+ --bg-primary: #0d1117;
10139
+ --bg-secondary: #161b22;
10140
+ --bg-tertiary: #21262d;
10141
+ --text-primary: #e6edf3;
10142
+ --text-secondary: #8b949e;
10143
+ --border: #30363d;
10144
+ --accent: #58a6ff;
10145
+ --accent-hover: #79b8ff;
10146
+ --success: #3fb950;
10147
+ --warning: #d29922;
10148
+ --error: #f85149;
10149
+ --get: #3fb950;
10150
+ --post: #58a6ff;
10151
+ --put: #d29922;
10152
+ --delete: #f85149;
10153
+ --patch: #a371f7;
10154
+ }
10155
+ * { box-sizing: border-box; }
10156
+ body {
10157
+ margin: 0;
10158
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10159
+ background: var(--bg-primary);
10160
+ color: var(--text-primary);
10161
+ line-height: 1.5;
10162
+ min-height: 100vh;
10163
+ }
10164
+ .container {
10165
+ max-width: 1400px;
10166
+ margin: 0 auto;
10167
+ padding: 1.5rem;
10168
+ }
10169
+ header {
10170
+ display: flex;
10171
+ align-items: center;
10172
+ justify-content: space-between;
10173
+ margin-bottom: 1.5rem;
10174
+ padding-bottom: 1rem;
10175
+ border-bottom: 1px solid var(--border);
10176
+ }
10177
+ h1 {
10178
+ margin: 0;
10179
+ font-size: 1.5rem;
10180
+ font-weight: 600;
10181
+ }
10182
+ .toolbar {
10183
+ display: flex;
10184
+ gap: 0.75rem;
10185
+ align-items: center;
10186
+ }
10187
+ .btn {
10188
+ padding: 0.5rem 1rem;
10189
+ border: 1px solid var(--border);
10190
+ border-radius: 6px;
10191
+ background: var(--bg-secondary);
10192
+ color: var(--text-primary);
10193
+ cursor: pointer;
10194
+ font-size: 0.875rem;
10195
+ transition: background 0.15s;
10196
+ }
10197
+ .btn:hover {
10198
+ background: var(--bg-tertiary);
10199
+ }
10200
+ .btn-primary {
10201
+ background: var(--accent);
10202
+ border-color: var(--accent);
10203
+ color: #fff;
10204
+ }
10205
+ .btn-primary:hover {
10206
+ background: var(--accent-hover);
10207
+ }
10208
+ .btn-danger {
10209
+ background: var(--error);
10210
+ border-color: var(--error);
10211
+ color: #fff;
10212
+ }
10213
+ .toggle-label {
10214
+ display: flex;
10215
+ align-items: center;
10216
+ gap: 0.5rem;
10217
+ font-size: 0.875rem;
10218
+ color: var(--text-secondary);
10219
+ }
10220
+ .toggle {
10221
+ width: 36px;
10222
+ height: 20px;
10223
+ background: var(--bg-tertiary);
10224
+ border-radius: 10px;
10225
+ position: relative;
10226
+ cursor: pointer;
10227
+ transition: background 0.2s;
10228
+ }
10229
+ .toggle.active { background: var(--accent); }
10230
+ .toggle::after {
10231
+ content: '';
10232
+ position: absolute;
10233
+ width: 16px;
10234
+ height: 16px;
10235
+ background: #fff;
10236
+ border-radius: 50%;
10237
+ top: 2px;
10238
+ left: 2px;
10239
+ transition: transform 0.2s;
10240
+ }
10241
+ .toggle.active::after { transform: translateX(16px); }
10242
+ .main-grid {
10243
+ display: grid;
10244
+ grid-template-columns: 1fr 1fr;
10245
+ gap: 1.5rem;
10246
+ }
10247
+ @media (max-width: 900px) {
10248
+ .main-grid { grid-template-columns: 1fr; }
10249
+ }
10250
+ .panel {
10251
+ background: var(--bg-secondary);
10252
+ border: 1px solid var(--border);
10253
+ border-radius: 8px;
10254
+ overflow: hidden;
10255
+ }
10256
+ .panel-header {
10257
+ padding: 0.75rem 1rem;
10258
+ background: var(--bg-tertiary);
10259
+ font-weight: 600;
10260
+ font-size: 0.875rem;
10261
+ }
10262
+ .record-list {
10263
+ max-height: 70vh;
10264
+ overflow-y: auto;
10265
+ }
10266
+ .record-item {
10267
+ padding: 0.75rem 1rem;
10268
+ border-bottom: 1px solid var(--border);
10269
+ cursor: pointer;
10270
+ transition: background 0.15s;
10271
+ display: grid;
10272
+ grid-template-columns: 70px 1fr 60px 70px;
10273
+ gap: 0.75rem;
10274
+ align-items: center;
10275
+ font-size: 0.8125rem;
10276
+ }
10277
+ .record-item:hover { background: var(--bg-tertiary); }
10278
+ .record-item.selected { background: var(--bg-tertiary); border-left: 3px solid var(--accent); }
10279
+ .method {
10280
+ font-weight: 600;
10281
+ font-size: 0.75rem;
10282
+ padding: 0.2rem 0.5rem;
10283
+ border-radius: 4px;
10284
+ }
10285
+ .method-GET { background: rgba(63,185,80,0.2); color: var(--get); }
10286
+ .method-POST { background: rgba(88,166,255,0.2); color: var(--post); }
10287
+ .method-PUT { background: rgba(210,153,34,0.2); color: var(--warning); }
10288
+ .method-DELETE { background: rgba(248,81,73,0.2); color: var(--error); }
10289
+ .method-PATCH { background: rgba(163,113,247,0.2); color: var(--patch); }
10290
+ .path { font-family: monospace; word-break: break-all; }
10291
+ .status { font-weight: 500; }
10292
+ .status-2xx { color: var(--success); }
10293
+ .status-4xx { color: var(--warning); }
10294
+ .status-5xx { color: var(--error); }
10295
+ .detail-content {
10296
+ padding: 1rem;
10297
+ font-size: 0.8125rem;
10298
+ max-height: 70vh;
10299
+ overflow-y: auto;
10300
+ }
10301
+ .detail-section {
10302
+ margin-bottom: 1rem;
10303
+ }
10304
+ .detail-section h4 {
10305
+ margin: 0 0 0.5rem;
10306
+ font-size: 0.75rem;
10307
+ text-transform: uppercase;
10308
+ color: var(--text-secondary);
10309
+ }
10310
+ .detail-section pre {
10311
+ margin: 0;
10312
+ padding: 0.75rem;
10313
+ background: var(--bg-primary);
10314
+ border-radius: 6px;
10315
+ overflow-x: auto;
10316
+ font-size: 0.75rem;
10317
+ white-space: pre-wrap;
10318
+ word-break: break-all;
10319
+ }
10320
+ .empty {
10321
+ padding: 2rem;
10322
+ text-align: center;
10323
+ color: var(--text-secondary);
10324
+ }
10325
+ .loading { opacity: 0.6; pointer-events: none; }
10326
+ </style>
10327
+ </head>
10328
+ <body>
10329
+ <div class="container">
10330
+ <header>
10331
+ <h1>Debug - Request Replay</h1>
10332
+ <div class="toolbar">
10333
+ <label class="toggle-label">
10334
+ <span>Auto-refresh</span>
10335
+ <div class="toggle" id="autoRefresh" title="Toggle auto-refresh"></div>
10336
+ </label>
10337
+ <button class="btn btn-primary" id="replayBtn" disabled>Replay</button>
10338
+ <button class="btn btn-danger" id="clearBtn">Clear</button>
10339
+ </div>
10340
+ </header>
10341
+ <div class="main-grid">
10342
+ <div class="panel">
10343
+ <div class="panel-header">Recorded Requests (<span id="count">0</span>)</div>
10344
+ <div class="record-list" id="recordList"></div>
10345
+ </div>
10346
+ <div class="panel">
10347
+ <div class="panel-header">Request Details</div>
10348
+ <div class="detail-content" id="detailContent">
10349
+ <div class="empty" id="emptyDetail">Select a request to view details</div>
10350
+ <div id="detailBody" style="display:none"></div>
10351
+ </div>
10352
+ </div>
10353
+ </div>
10354
+ </div>
10355
+ <script>
10356
+ (function() {
10357
+ const apiBase = '${apiBase}';
10358
+ const recordList = document.getElementById('recordList');
10359
+ const countEl = document.getElementById('count');
10360
+ const emptyDetail = document.getElementById('emptyDetail');
10361
+ const detailBody = document.getElementById('detailBody');
10362
+ const replayBtn = document.getElementById('replayBtn');
10363
+ const clearBtn = document.getElementById('clearBtn');
10364
+ const autoRefreshToggle = document.getElementById('autoRefresh');
10365
+
10366
+ let records = [];
10367
+ let selectedId = null;
10368
+ let autoRefreshInterval = null;
10369
+
10370
+ function methodClass(m) {
10371
+ return 'method-' + (m || 'GET');
10372
+ }
10373
+ function statusClass(s) {
10374
+ if (s >= 200 && s < 300) return 'status-2xx';
10375
+ if (s >= 400 && s < 500) return 'status-4xx';
10376
+ if (s >= 500) return 'status-5xx';
10377
+ return '';
10378
+ }
10379
+
10380
+ async function fetchRecords() {
10381
+ const res = await fetch(apiBase + '/records');
10382
+ if (!res.ok) throw new Error('Failed to fetch');
10383
+ return res.json();
10384
+ }
10385
+
10386
+ async function fetchRecord(id) {
10387
+ const res = await fetch(apiBase + '/records/' + id);
10388
+ if (!res.ok) throw new Error('Failed to fetch');
10389
+ return res.json();
10390
+ }
10391
+
10392
+ async function loadRecords() {
10393
+ try {
10394
+ records = await fetchRecords();
10395
+ countEl.textContent = records.length;
10396
+ renderList();
10397
+ } catch (e) {
10398
+ recordList.innerHTML = '<div class="empty">Failed to load records</div>';
10399
+ }
10400
+ }
10401
+
10402
+ function renderList() {
10403
+ if (records.length === 0) {
10404
+ recordList.innerHTML = '<div class="empty">No requests recorded yet</div>';
10405
+ return;
10406
+ }
10407
+ recordList.innerHTML = records.map(function(r) {
10408
+ return '<div class="record-item' + (r.id === selectedId ? ' selected' : '') + '" data-id="' + r.id + '">' +
10409
+ '<span class="method ' + methodClass(r.request.method) + '">' + (r.request.method || 'GET') + '</span>' +
10410
+ '<span class="path">' + escapeHtml(r.request.path) + '</span>' +
10411
+ '<span class="status ' + statusClass(r.response.status) + '">' + r.response.status + '</span>' +
10412
+ '<span>' + r.timing.total + 'ms</span>' +
10413
+ '</div>';
10414
+ }).join('');
10415
+ recordList.querySelectorAll('.record-item').forEach(function(el) {
10416
+ el.addEventListener('click', function() {
10417
+ selectedId = el.dataset.id;
10418
+ loadDetail(selectedId);
10419
+ renderList();
10420
+ replayBtn.disabled = false;
10421
+ });
10422
+ });
10423
+ }
10424
+
10425
+ function escapeHtml(s) {
10426
+ if (s == null) return '';
10427
+ const div = document.createElement('div');
10428
+ div.textContent = s;
10429
+ return div.innerHTML;
10430
+ }
10431
+
10432
+ async function loadDetail(id) {
10433
+ try {
10434
+ const record = await fetchRecord(id);
10435
+ emptyDetail.style.display = 'none';
10436
+ detailBody.style.display = 'block';
10437
+ const body = record.request.body !== undefined
10438
+ ? JSON.stringify(record.request.body, null, 2)
10439
+ : '(no body)';
10440
+ const respBody = record.response.bodySize + ' bytes';
10441
+ const meta = [];
10442
+ if (record.metadata.matchedRoute) meta.push('Route: ' + record.metadata.matchedRoute);
10443
+ if (record.metadata.controller) meta.push('Controller: ' + record.metadata.controller);
10444
+ if (record.metadata.methodName) meta.push('Method: ' + record.metadata.methodName);
10445
+ detailBody.innerHTML =
10446
+ '<div class="detail-section"><h4>Request Headers</h4><pre>' + escapeHtml(JSON.stringify(record.request.headers, null, 2)) + '</pre></div>' +
10447
+ '<div class="detail-section"><h4>Request Body</h4><pre>' + escapeHtml(body) + '</pre></div>' +
10448
+ '<div class="detail-section"><h4>Response</h4><pre>Status: ' + record.response.status + '\\nHeaders: ' + JSON.stringify(record.response.headers, null, 2) + '\\nBody size: ' + respBody + '</pre></div>' +
10449
+ (meta.length ? '<div class="detail-section"><h4>Metadata</h4><pre>' + escapeHtml(meta.join('\\n')) + '</pre></div>' : '');
10450
+ } catch (e) {
10451
+ detailBody.innerHTML = '<div class="empty">Failed to load details</div>';
10452
+ }
10453
+ }
10454
+
10455
+ async function replay(id) {
10456
+ if (!id) return;
10457
+ replayBtn.disabled = true;
10458
+ replayBtn.textContent = 'Replaying...';
10459
+ try {
10460
+ const res = await fetch(apiBase + '/replay/' + id, { method: 'POST' });
10461
+ const data = await res.json();
10462
+ if (data.ok) {
10463
+ await loadRecords();
10464
+ if (data.newId) selectedId = data.newId;
10465
+ renderList();
10466
+ loadDetail(selectedId || id);
10467
+ } else {
10468
+ alert('Replay failed: ' + (data.error || res.statusText));
10469
+ }
10470
+ } catch (e) {
10471
+ alert('Replay failed: ' + e.message);
10472
+ }
10473
+ replayBtn.disabled = false;
10474
+ replayBtn.textContent = 'Replay';
10475
+ }
10476
+
10477
+ async function clear() {
10478
+ if (!confirm('Clear all recorded requests?')) return;
10479
+ try {
10480
+ await fetch(apiBase + '/records', { method: 'DELETE' });
10481
+ records = [];
10482
+ selectedId = null;
10483
+ emptyDetail.style.display = 'block';
10484
+ detailBody.style.display = 'none';
10485
+ detailBody.innerHTML = '';
10486
+ replayBtn.disabled = true;
10487
+ loadRecords();
10488
+ } catch (e) {
10489
+ alert('Clear failed: ' + e.message);
10490
+ }
10491
+ }
10492
+
10493
+ replayBtn.addEventListener('click', function() {
10494
+ if (selectedId) replay(selectedId);
10495
+ });
10496
+ clearBtn.addEventListener('click', clear);
10497
+
10498
+ autoRefreshToggle.addEventListener('click', function() {
10499
+ autoRefreshToggle.classList.toggle('active');
10500
+ if (autoRefreshToggle.classList.contains('active')) {
10501
+ autoRefreshInterval = setInterval(loadRecords, 2000);
10502
+ } else {
10503
+ clearInterval(autoRefreshInterval);
10504
+ autoRefreshInterval = null;
10505
+ }
10506
+ });
10507
+
10508
+ loadRecords();
10509
+ })();
10510
+ </script>
10511
+ </body>
10512
+ </html>`;
10513
+ }
10514
+
10515
+ // src/debug/debug-ui-middleware.ts
10516
+ function createDebugUIMiddleware(recorder, basePath) {
10517
+ const normalizedBase = basePath.endsWith("/") && basePath.length > 1 ? basePath.slice(0, -1) : basePath;
10518
+ const apiPrefix = `${normalizedBase}/api`;
10519
+ return async (context2, next) => {
10520
+ const path = context2.path;
10521
+ if (!path.startsWith(normalizedBase)) {
10522
+ return await next();
10523
+ }
10524
+ const jsonResponse = (data, status = 200) => {
10525
+ return new Response(JSON.stringify(data), {
10526
+ status,
10527
+ headers: { "Content-Type": "application/json; charset=utf-8" }
10528
+ });
10529
+ };
10530
+ if (path === normalizedBase || path === `${normalizedBase}/`) {
10531
+ const html = createDebugHTML(normalizedBase);
10532
+ return new Response(html, {
10533
+ headers: { "Content-Type": "text/html; charset=utf-8" }
10534
+ });
10535
+ }
10536
+ if (path === `${apiPrefix}/records`) {
10537
+ if (context2.method === "GET") {
10538
+ const records = recorder.getAll();
10539
+ return jsonResponse(records);
10540
+ }
10541
+ if (context2.method === "DELETE") {
10542
+ recorder.clear();
10543
+ return jsonResponse({ ok: true });
10544
+ }
10545
+ return jsonResponse({ error: "Method not allowed" }, 405);
10546
+ }
10547
+ const recordsIdPrefix = `${apiPrefix}/records/`;
10548
+ if (path.startsWith(recordsIdPrefix)) {
10549
+ const id = path.slice(recordsIdPrefix.length).split("/")[0];
10550
+ if (!id) {
10551
+ return jsonResponse({ error: "Invalid record id" }, 400);
10552
+ }
10553
+ const record = recorder.getById(id);
10554
+ if (!record) {
10555
+ return jsonResponse({ error: "Not found" }, 404);
10556
+ }
10557
+ return jsonResponse(record);
10558
+ }
10559
+ const replayPrefix = `${apiPrefix}/replay/`;
10560
+ if (path.startsWith(replayPrefix) && context2.method === "POST") {
10561
+ const id = path.slice(replayPrefix.length).split("/")[0];
10562
+ if (!id) {
10563
+ return jsonResponse({ ok: false, error: "Invalid record id" }, 400);
10564
+ }
10565
+ const record = recorder.getById(id);
10566
+ if (!record) {
10567
+ return jsonResponse({ ok: false, error: "Record not found" }, 404);
10568
+ }
10569
+ try {
10570
+ const origin = new URL(context2.request.url).origin;
10571
+ const targetUrl = new URL(record.request.path, origin).href;
10572
+ const headers = new Headers(record.request.headers);
10573
+ let body;
10574
+ if (record.request.body !== undefined && record.request.body !== null) {
10575
+ body = JSON.stringify(record.request.body);
10576
+ }
10577
+ const replayedRequest = new Request(targetUrl, {
10578
+ method: record.request.method,
10579
+ headers,
10580
+ body
10581
+ });
10582
+ const response = await fetch(replayedRequest);
10583
+ const responseText = await response.text();
10584
+ return jsonResponse({
10585
+ ok: true,
10586
+ status: response.status,
10587
+ body: responseText
10588
+ });
10589
+ } catch (error) {
10590
+ return jsonResponse({
10591
+ ok: false,
10592
+ error: error.message
10593
+ }, 500);
10594
+ }
10595
+ }
10596
+ return await next();
10597
+ };
10598
+ }
10599
+
10600
+ // src/debug/types.ts
10601
+ var DEBUG_OPTIONS_TOKEN = Symbol("@dangao/bun-server:debug:options");
10602
+ var DEBUG_RECORDER_TOKEN = Symbol("@dangao/bun-server:debug:recorder");
10603
+
10604
+ // src/debug/debug-module.ts
10605
+ class DebugModule {
10606
+ static forRoot(options = {}) {
10607
+ const enabled = options.enabled ?? true;
10608
+ const maxRecords = options.maxRecords ?? 500;
10609
+ const recordBody = options.recordBody ?? true;
10610
+ const path = options.path ?? "/_debug";
10611
+ const normalizedPath = path.endsWith("/") && path.length > 1 ? path.slice(0, -1) : path;
10612
+ const recorder = new RequestRecorder(maxRecords);
10613
+ const opts = {
10614
+ enabled,
10615
+ maxRecords,
10616
+ recordBody,
10617
+ path: normalizedPath
10618
+ };
10619
+ const debugUIMiddleware = createDebugUIMiddleware(recorder, normalizedPath);
10620
+ const recordingMiddleware = createDebugMiddleware(recorder, {
10621
+ recordBody,
10622
+ basePath: normalizedPath
10623
+ });
10624
+ const providers2 = [
10625
+ { provide: DEBUG_OPTIONS_TOKEN, useValue: opts },
10626
+ { provide: DEBUG_RECORDER_TOKEN, useValue: recorder }
10627
+ ];
10628
+ const middlewares = enabled ? [debugUIMiddleware, recordingMiddleware] : [];
10629
+ const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, DebugModule) ?? {};
10630
+ const metadata = {
10631
+ ...existingMetadata,
10632
+ providers: [...existingMetadata.providers ?? [], ...providers2],
10633
+ middlewares: [...existingMetadata.middlewares ?? [], ...middlewares]
10634
+ };
10635
+ Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, DebugModule);
10636
+ return DebugModule;
10637
+ }
10638
+ }
10639
+ DebugModule = __legacyDecorateClassTS([
10640
+ Module({
10641
+ providers: [],
10642
+ extensions: [],
10643
+ middlewares: []
10644
+ })
10645
+ ], DebugModule);
9098
10646
  // src/testing/harness.ts
9099
10647
  import { performance as performance2 } from "perf_hooks";
9100
10648
 
@@ -9155,6 +10703,293 @@ class StressTester {
9155
10703
  };
9156
10704
  }
9157
10705
  }
10706
+ // src/testing/testing-module.ts
10707
+ import"reflect-metadata";
10708
+
10709
+ // src/testing/test-client.ts
10710
+ class TestHttpClient {
10711
+ baseUrl;
10712
+ app;
10713
+ constructor(baseUrl, app) {
10714
+ this.baseUrl = baseUrl;
10715
+ this.app = app;
10716
+ }
10717
+ async get(path, options) {
10718
+ return this.request("GET", path, options);
10719
+ }
10720
+ async post(path, options) {
10721
+ return this.request("POST", path, options);
10722
+ }
10723
+ async put(path, options) {
10724
+ return this.request("PUT", path, options);
10725
+ }
10726
+ async delete(path, options) {
10727
+ return this.request("DELETE", path, options);
10728
+ }
10729
+ async patch(path, options) {
10730
+ return this.request("PATCH", path, options);
10731
+ }
10732
+ async close() {
10733
+ await this.app.stop();
10734
+ }
10735
+ async request(method, path, options) {
10736
+ let url = `${this.baseUrl}${path}`;
10737
+ if (options?.query) {
10738
+ const params = new URLSearchParams(options.query);
10739
+ url += `?${params.toString()}`;
10740
+ }
10741
+ const fetchOptions = {
10742
+ method,
10743
+ headers: {
10744
+ "Content-Type": "application/json",
10745
+ ...options?.headers || {}
10746
+ }
10747
+ };
10748
+ if (options?.body && method !== "GET") {
10749
+ fetchOptions.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
10750
+ }
10751
+ const response = await fetch(url, fetchOptions);
10752
+ const text = await response.text();
10753
+ let body;
10754
+ try {
10755
+ body = JSON.parse(text);
10756
+ } catch {
10757
+ body = text;
10758
+ }
10759
+ return {
10760
+ status: response.status,
10761
+ headers: response.headers,
10762
+ body,
10763
+ text,
10764
+ ok: response.ok
10765
+ };
10766
+ }
10767
+ }
10768
+
10769
+ // src/testing/testing-module.ts
10770
+ class TestingModuleBuilder {
10771
+ metadata;
10772
+ overrides = [];
10773
+ constructor(metadata) {
10774
+ this.metadata = { ...metadata };
10775
+ }
10776
+ overrideProvider(token) {
10777
+ return new ProviderOverrideBuilder(this, token);
10778
+ }
10779
+ async compile() {
10780
+ const providers2 = this.buildProviders();
10781
+ return new TestingModule(this.metadata, providers2, this.overrides);
10782
+ }
10783
+ addOverride(override) {
10784
+ this.overrides.push(override);
10785
+ }
10786
+ buildProviders() {
10787
+ const providers2 = [...this.metadata.providers || []];
10788
+ const overrideMap = new Map;
10789
+ for (const override of this.overrides) {
10790
+ const key = typeof override.token === "function" ? override.token.name : override.token;
10791
+ overrideMap.set(key, override);
10792
+ }
10793
+ const result = [];
10794
+ for (const provider of providers2) {
10795
+ const token = typeof provider === "function" ? provider.name : provider.provide;
10796
+ const key = typeof token === "function" ? token.name : token;
10797
+ const override = overrideMap.get(key);
10798
+ if (override) {
10799
+ if (override.useValue !== undefined) {
10800
+ result.push({ provide: override.token, useValue: override.useValue });
10801
+ } else if (override.useClass) {
10802
+ result.push({ provide: override.token, useClass: override.useClass });
10803
+ } else if (override.useFactory) {
10804
+ result.push({ provide: override.token, useFactory: override.useFactory });
10805
+ }
10806
+ overrideMap.delete(key);
10807
+ } else {
10808
+ result.push(provider);
10809
+ }
10810
+ }
10811
+ for (const override of overrideMap.values()) {
10812
+ if (override.useValue !== undefined) {
10813
+ result.push({ provide: override.token, useValue: override.useValue });
10814
+ }
10815
+ }
10816
+ return result;
10817
+ }
10818
+ }
10819
+
10820
+ class ProviderOverrideBuilder {
10821
+ builder;
10822
+ token;
10823
+ constructor(builder, token) {
10824
+ this.builder = builder;
10825
+ this.token = token;
10826
+ }
10827
+ useValue(value) {
10828
+ this.builder.addOverride({ token: this.token, useValue: value });
10829
+ return this.builder;
10830
+ }
10831
+ useClass(cls) {
10832
+ this.builder.addOverride({ token: this.token, useClass: cls });
10833
+ return this.builder;
10834
+ }
10835
+ useFactory(factory) {
10836
+ this.builder.addOverride({ token: this.token, useFactory: factory });
10837
+ return this.builder;
10838
+ }
10839
+ }
10840
+
10841
+ class TestingModule {
10842
+ metadata;
10843
+ providers;
10844
+ overrides;
10845
+ app;
10846
+ container;
10847
+ constructor(metadata, providers2, overrides) {
10848
+ this.metadata = metadata;
10849
+ this.providers = providers2;
10850
+ this.overrides = overrides;
10851
+ }
10852
+ get(token) {
10853
+ return this.getContainer().resolve(token);
10854
+ }
10855
+ createApplication(options = {}) {
10856
+ if (this.app)
10857
+ return this.app;
10858
+ this.app = new Application({
10859
+ enableSignalHandlers: false,
10860
+ ...options
10861
+ });
10862
+ const container = this.app.getContainer();
10863
+ this.container = container;
10864
+ for (const provider of this.providers) {
10865
+ if (typeof provider === "function") {
10866
+ container.register(provider);
10867
+ } else if ("useValue" in provider && provider.provide) {
10868
+ container.registerInstance(provider.provide, provider.useValue);
10869
+ } else if ("useFactory" in provider && provider.provide) {
10870
+ container.register(provider.provide, {
10871
+ factory: () => provider.useFactory(container)
10872
+ });
10873
+ } else if ("useClass" in provider) {
10874
+ const token = provider.provide ?? provider.useClass;
10875
+ container.register(token, {
10876
+ implementation: provider.useClass
10877
+ });
10878
+ }
10879
+ }
10880
+ if (this.metadata.imports) {
10881
+ for (const moduleClass of this.metadata.imports) {
10882
+ this.app.registerModule(moduleClass);
10883
+ }
10884
+ }
10885
+ if (this.metadata.controllers) {
10886
+ for (const ctrl of this.metadata.controllers) {
10887
+ this.app.registerController(ctrl);
10888
+ }
10889
+ }
10890
+ return this.app;
10891
+ }
10892
+ async createHttpClient(options = {}) {
10893
+ const app = this.createApplication(options);
10894
+ await app.listen(0);
10895
+ const server = app.getServer();
10896
+ const port = server?.getPort() ?? 3000;
10897
+ return new TestHttpClient(`http://localhost:${port}`, app);
10898
+ }
10899
+ getContainer() {
10900
+ if (this.container)
10901
+ return this.container;
10902
+ this.createApplication();
10903
+ return this.container;
10904
+ }
10905
+ }
10906
+
10907
+ class Test {
10908
+ static createTestingModule(metadata) {
10909
+ return new TestingModuleBuilder(metadata);
10910
+ }
10911
+ }
10912
+ // src/client/generator.ts
10913
+ init_registry();
10914
+
10915
+ class ClientGenerator {
10916
+ static generate() {
10917
+ const registry = RouteRegistry.getInstance();
10918
+ const router = registry.getRouter();
10919
+ const routes = router.getRoutes();
10920
+ const entries = [];
10921
+ for (const route of routes) {
10922
+ entries.push({
10923
+ method: route.method,
10924
+ path: route.path,
10925
+ controllerName: route.controllerClass?.name ?? "unknown",
10926
+ methodName: route.methodName ?? "unknown"
10927
+ });
10928
+ }
10929
+ return { routes: entries };
10930
+ }
10931
+ static generateJSON() {
10932
+ return JSON.stringify(ClientGenerator.generate(), null, 2);
10933
+ }
10934
+ }
10935
+ // src/client/runtime.ts
10936
+ function buildUrl(baseUrl, path, options) {
10937
+ let resolvedPath = path;
10938
+ if (options?.params) {
10939
+ for (const [key, value] of Object.entries(options.params)) {
10940
+ resolvedPath = resolvedPath.replace(`:${key}`, encodeURIComponent(value));
10941
+ }
10942
+ }
10943
+ let url = `${baseUrl}${resolvedPath}`;
10944
+ if (options?.query) {
10945
+ const params = new URLSearchParams(options.query);
10946
+ url += `?${params.toString()}`;
10947
+ }
10948
+ return url;
10949
+ }
10950
+ function createClient(manifest, config) {
10951
+ const fetchFn = config.fetch ?? globalThis.fetch;
10952
+ const groups = new Map;
10953
+ for (const route of manifest.routes) {
10954
+ const groupName = route.controllerName.replace(/Controller$/i, "").replace(/^./, (c) => c.toLowerCase());
10955
+ if (!groups.has(groupName)) {
10956
+ groups.set(groupName, new Map);
10957
+ }
10958
+ groups.get(groupName).set(route.methodName, {
10959
+ method: route.method,
10960
+ path: route.path
10961
+ });
10962
+ }
10963
+ const client = {};
10964
+ for (const [groupName, methods] of groups) {
10965
+ const group = {};
10966
+ for (const [methodName, routeInfo] of methods) {
10967
+ group[methodName] = async (options) => {
10968
+ const url = buildUrl(config.baseUrl, routeInfo.path, options);
10969
+ const fetchOptions = {
10970
+ method: routeInfo.method,
10971
+ headers: {
10972
+ "Content-Type": "application/json",
10973
+ ...config.headers || {},
10974
+ ...options?.headers || {}
10975
+ }
10976
+ };
10977
+ if (options?.body && routeInfo.method !== "GET") {
10978
+ fetchOptions.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
10979
+ }
10980
+ const response = await fetchFn(url, fetchOptions);
10981
+ const text = await response.text();
10982
+ try {
10983
+ return JSON.parse(text);
10984
+ } catch {
10985
+ return text;
10986
+ }
10987
+ };
10988
+ }
10989
+ client[groupName] = group;
10990
+ }
10991
+ return client;
10992
+ }
9158
10993
  // src/queue/queue-module.ts
9159
10994
  init_module();
9160
10995
 
@@ -11121,10 +12956,13 @@ export {
11121
12956
  createHttpMetricsMiddleware,
11122
12957
  createFileUploadMiddleware,
11123
12958
  createErrorHandlingMiddleware,
12959
+ createDebugMiddleware,
11124
12960
  createCustomValidator,
11125
12961
  createCorsMiddleware,
12962
+ createClient,
11126
12963
  contextStore,
11127
12964
  checkRoles,
12965
+ applyDecorators,
11128
12966
  WeightedRoundRobinLoadBalancer,
11129
12967
  WebSocketGatewayRegistry,
11130
12968
  WebSocketGateway,
@@ -11144,6 +12982,10 @@ export {
11144
12982
  TransactionInterceptor,
11145
12983
  Tracer,
11146
12984
  TraceIdRequestInterceptor,
12985
+ TestingModuleBuilder,
12986
+ TestingModule,
12987
+ TestHttpClient,
12988
+ Test,
11147
12989
  TRANSACTION_SERVICE_TOKEN,
11148
12990
  SwaggerModule,
11149
12991
  SwaggerGenerator,
@@ -11174,6 +13016,7 @@ export {
11174
13016
  ResponseLogInterceptor,
11175
13017
  ResponseBuilder,
11176
13018
  RequestWrapper,
13019
+ RequestRecorder,
11177
13020
  RequestLogInterceptor,
11178
13021
  Repository,
11179
13022
  Reflector,
@@ -11319,14 +13162,20 @@ export {
11319
13162
  EVENT_LISTENER_CLASS_METADATA_KEY,
11320
13163
  EVENT_EMITTER_TOKEN,
11321
13164
  DrizzleBaseRepository,
13165
+ DebugModule,
11322
13166
  DatabaseService2 as DatabaseService,
11323
13167
  DatabaseModule,
11324
13168
  DatabaseHealthIndicator,
11325
13169
  DatabaseExtension,
11326
13170
  DatabaseConnectionManager,
13171
+ DashboardService,
13172
+ DashboardModule,
11327
13173
  DELETE,
13174
+ DEBUG_RECORDER_TOKEN,
13175
+ DEBUG_OPTIONS_TOKEN,
11328
13176
  DATABASE_SERVICE_TOKEN,
11329
13177
  DATABASE_OPTIONS_TOKEN,
13178
+ DASHBOARD_OPTIONS_TOKEN,
11330
13179
  Cron,
11331
13180
  ControllerRegistry,
11332
13181
  Controller,
@@ -11342,6 +13191,8 @@ export {
11342
13191
  ConfigModule,
11343
13192
  ConfigCenterModule,
11344
13193
  Column,
13194
+ ClusterManager,
13195
+ ClientGenerator,
11345
13196
  CircuitBreakerState,
11346
13197
  CircuitBreaker,
11347
13198
  CacheableInterceptor,
@@ -11372,6 +13223,7 @@ export {
11372
13223
  AuthenticationManager,
11373
13224
  AuthGuard,
11374
13225
  Auth,
13226
+ AsyncProviderRegistry,
11375
13227
  ArrayUnique,
11376
13228
  ArrayNotEmpty,
11377
13229
  ArrayNotContains,