@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.
- package/README.md +79 -6
- package/dist/cache/cache-module.d.ts +6 -0
- package/dist/cache/cache-module.d.ts.map +1 -1
- package/dist/client/generator.d.ts +16 -0
- package/dist/client/generator.d.ts.map +1 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/runtime.d.ts +15 -0
- package/dist/client/runtime.d.ts.map +1 -0
- package/dist/client/types.d.ts +36 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/config/config-module.d.ts +7 -0
- package/dist/config/config-module.d.ts.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/service.d.ts +13 -0
- package/dist/config/service.d.ts.map +1 -1
- package/dist/config/types.d.ts +10 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/core/application.d.ts +7 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/apply-decorators.d.ts +6 -0
- package/dist/core/apply-decorators.d.ts.map +1 -0
- package/dist/core/cluster.d.ts +47 -0
- package/dist/core/cluster.d.ts.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/server.d.ts +8 -0
- package/dist/core/server.d.ts.map +1 -1
- package/dist/dashboard/controller.d.ts +55 -0
- package/dist/dashboard/controller.d.ts.map +1 -0
- package/dist/dashboard/dashboard-extension.d.ts +20 -0
- package/dist/dashboard/dashboard-extension.d.ts.map +1 -0
- package/dist/dashboard/dashboard-module.d.ts +13 -0
- package/dist/dashboard/dashboard-module.d.ts.map +1 -0
- package/dist/dashboard/index.d.ts +4 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/types.d.ts +16 -0
- package/dist/dashboard/types.d.ts.map +1 -0
- package/dist/dashboard/ui.d.ts +7 -0
- package/dist/dashboard/ui.d.ts.map +1 -0
- package/dist/database/database-module.d.ts +7 -0
- package/dist/database/database-module.d.ts.map +1 -1
- package/dist/debug/debug-module.d.ts +13 -0
- package/dist/debug/debug-module.d.ts.map +1 -0
- package/dist/debug/debug-ui-middleware.d.ts +8 -0
- package/dist/debug/debug-ui-middleware.d.ts.map +1 -0
- package/dist/debug/index.d.ts +5 -0
- package/dist/debug/index.d.ts.map +1 -0
- package/dist/debug/middleware.d.ts +12 -0
- package/dist/debug/middleware.d.ts.map +1 -0
- package/dist/debug/recorder.d.ts +61 -0
- package/dist/debug/recorder.d.ts.map +1 -0
- package/dist/debug/types.d.ts +48 -0
- package/dist/debug/types.d.ts.map +1 -0
- package/dist/debug/ui.d.ts +6 -0
- package/dist/debug/ui.d.ts.map +1 -0
- package/dist/di/async-module.d.ts +49 -0
- package/dist/di/async-module.d.ts.map +1 -0
- package/dist/di/lifecycle.d.ts +49 -0
- package/dist/di/lifecycle.d.ts.map +1 -0
- package/dist/di/module-registry.d.ts +24 -0
- package/dist/di/module-registry.d.ts.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1887 -35
- package/dist/router/route.d.ts +5 -7
- package/dist/router/route.d.ts.map +1 -1
- package/dist/swagger/generator.d.ts +10 -0
- package/dist/swagger/generator.d.ts.map +1 -1
- package/dist/testing/test-client.d.ts +49 -0
- package/dist/testing/test-client.d.ts.map +1 -0
- package/dist/testing/testing-module.d.ts +90 -0
- package/dist/testing/testing-module.d.ts.map +1 -0
- package/dist/websocket/registry.d.ts +1 -6
- package/dist/websocket/registry.d.ts.map +1 -1
- package/docs/async-module.md +59 -0
- package/docs/client-generation.md +100 -0
- package/docs/cluster.md +81 -0
- package/docs/custom-decorators.md +1 -7
- package/docs/dashboard.md +54 -0
- package/docs/debug.md +58 -0
- package/docs/extensions.md +0 -2
- package/docs/guide.md +0 -1
- package/docs/lifecycle.md +72 -0
- package/docs/testing.md +110 -0
- package/docs/zh/async-module.md +98 -0
- package/docs/zh/client-generation.md +92 -0
- package/docs/zh/cluster.md +74 -0
- package/docs/zh/custom-decorators.md +1 -7
- package/docs/zh/dashboard.md +69 -0
- package/docs/zh/debug.md +81 -0
- package/docs/zh/extensions.md +0 -2
- package/docs/zh/guide.md +0 -1
- package/docs/zh/lifecycle.md +87 -0
- package/docs/zh/migration.md +0 -5
- package/docs/zh/testing.md +119 -0
- package/package.json +4 -4
- package/src/cache/cache-module.ts +25 -0
- package/src/client/generator.ts +36 -0
- package/src/client/index.ts +8 -0
- package/src/client/runtime.ts +101 -0
- package/src/client/types.ts +38 -0
- package/src/config/config-module.ts +44 -4
- package/src/config/index.ts +1 -0
- package/src/config/service.ts +50 -0
- package/src/config/types.ts +12 -0
- package/src/core/application.ts +37 -0
- package/src/core/apply-decorators.ts +31 -0
- package/src/core/cluster.ts +143 -0
- package/src/core/index.ts +1 -0
- package/src/core/server.ts +14 -1
- package/src/dashboard/controller.ts +227 -0
- package/src/dashboard/dashboard-extension.ts +26 -0
- package/src/dashboard/dashboard-module.ts +38 -0
- package/src/dashboard/index.ts +3 -0
- package/src/dashboard/types.ts +16 -0
- package/src/dashboard/ui.ts +219 -0
- package/src/database/database-module.ts +20 -0
- package/src/debug/debug-module.ts +70 -0
- package/src/debug/debug-ui-middleware.ts +110 -0
- package/src/debug/index.ts +9 -0
- package/src/debug/middleware.ts +126 -0
- package/src/debug/recorder.ts +141 -0
- package/src/debug/types.ts +49 -0
- package/src/debug/ui.ts +393 -0
- package/src/di/async-module.ts +141 -0
- package/src/di/lifecycle.ts +117 -0
- package/src/di/module-registry.ts +75 -0
- package/src/index.ts +35 -0
- package/src/router/route.ts +20 -20
- package/src/swagger/generator.ts +100 -0
- package/src/testing/test-client.ts +112 -0
- package/src/testing/testing-module.ts +238 -0
- package/src/websocket/registry.ts +3 -16
- package/tests/auth/auth-decorators.test.ts +0 -1
- package/tests/auth/oauth2-service.test.ts +0 -1
- package/tests/cache/cache-decorators-extended.test.ts +0 -1
- package/tests/cache/cache-decorators.test.ts +0 -1
- package/tests/cache/cache-interceptors.test.ts +0 -1
- package/tests/cache/cache-module.test.ts +0 -1
- package/tests/cache/cache-service-proxy.test.ts +0 -1
- package/tests/client/client-generator.test.ts +142 -0
- package/tests/config/config-center-integration.test.ts +0 -1
- package/tests/config/config-module-extended.test.ts +0 -1
- package/tests/config/config-module.test.ts +0 -1
- package/tests/controller/controller.test.ts +0 -1
- package/tests/controller/param-binder.test.ts +0 -1
- package/tests/controller/path-combination.test.ts +0 -1
- package/tests/core/application.test.ts +34 -0
- package/tests/core/apply-decorators.test.ts +109 -0
- package/tests/core/cluster.test.ts +32 -0
- package/tests/dashboard/dashboard-module.test.ts +85 -0
- package/tests/database/database-module.test.ts +0 -1
- package/tests/database/orm.test.ts +0 -1
- package/tests/database/postgres-mysql-integration.test.ts +0 -1
- package/tests/database/transaction.test.ts +0 -1
- package/tests/debug/debug-module.test.ts +141 -0
- package/tests/di/async-module.test.ts +125 -0
- package/tests/di/container.test.ts +0 -1
- package/tests/di/lifecycle.test.ts +140 -0
- package/tests/error/error-handler.test.ts +0 -1
- package/tests/events/event-decorators.test.ts +0 -1
- package/tests/events/event-listener-scanner.test.ts +0 -1
- package/tests/events/event-module.test.ts +0 -1
- package/tests/extensions/logger-module.test.ts +0 -1
- package/tests/health/health-module.test.ts +0 -1
- package/tests/integration/oauth2-e2e.test.ts +0 -1
- package/tests/integration/session-e2e.test.ts +0 -1
- package/tests/interceptor/base-interceptor.test.ts +0 -1
- package/tests/interceptor/builtin/cache-interceptor.test.ts +0 -1
- package/tests/interceptor/builtin/log-interceptor.test.ts +0 -1
- package/tests/interceptor/builtin/permission-interceptor.test.ts +0 -1
- package/tests/interceptor/interceptor-advanced-integration.test.ts +0 -1
- package/tests/interceptor/interceptor-chain.test.ts +0 -1
- package/tests/interceptor/interceptor-integration.test.ts +0 -1
- package/tests/interceptor/interceptor-metadata.test.ts +0 -1
- package/tests/interceptor/interceptor-registry.test.ts +0 -1
- package/tests/interceptor/perf/interceptor-performance.test.ts +0 -1
- package/tests/metrics/metrics-module.test.ts +0 -1
- package/tests/microservice/config-center.test.ts +0 -1
- package/tests/microservice/service-client-decorators.test.ts +0 -1
- package/tests/microservice/service-registry-decorators.test.ts +0 -1
- package/tests/microservice/service-registry.test.ts +0 -1
- package/tests/middleware/builtin/middleware-builtin-extended.test.ts +0 -1
- package/tests/middleware/builtin/rate-limit.test.ts +0 -1
- package/tests/middleware/middleware-decorators.test.ts +0 -1
- package/tests/middleware/middleware-pipeline.test.ts +0 -1
- package/tests/middleware/middleware.test.ts +0 -1
- package/tests/perf/optimization.test.ts +0 -1
- package/tests/queue/queue-decorators.test.ts +0 -1
- package/tests/queue/queue-module.test.ts +0 -1
- package/tests/queue/queue-service.test.ts +0 -1
- package/tests/router/router-decorators.test.ts +0 -1
- package/tests/router/router-extended.test.ts +0 -1
- package/tests/security/guards/guards-integration.test.ts +0 -1
- package/tests/security/guards/guards.test.ts +0 -1
- package/tests/security/guards/reflector.test.ts +0 -1
- package/tests/security/security-filter.test.ts +0 -1
- package/tests/security/security-module-extended.test.ts +0 -1
- package/tests/security/security-module.test.ts +0 -1
- package/tests/session/session-decorators.test.ts +0 -1
- package/tests/session/session-module.test.ts +0 -1
- package/tests/swagger/decorators.test.ts +0 -1
- package/tests/swagger/swagger-module.test.ts +0 -1
- package/tests/swagger/ui.test.ts +0 -1
- package/tests/testing/testing-module.test.ts +129 -0
- package/tests/validation/class-validator.test.ts +0 -1
- 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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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: (
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 } =
|
|
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 =
|
|
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 =
|
|
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,
|