@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { ModuleClass, ModuleProvider, ProviderToken } from './module';
|
|
2
|
+
import { MODULE_METADATA_KEY } from './module';
|
|
3
|
+
import type { Container } from './container';
|
|
4
|
+
import { ModuleRegistry } from './module-registry';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 异步模块配置选项
|
|
8
|
+
*/
|
|
9
|
+
export interface AsyncModuleOptions<T> {
|
|
10
|
+
/**
|
|
11
|
+
* 需要导入的模块(提供 inject 依赖的来源)
|
|
12
|
+
*/
|
|
13
|
+
imports?: ModuleClass[];
|
|
14
|
+
/**
|
|
15
|
+
* 要注入到 useFactory 中的 provider tokens
|
|
16
|
+
*/
|
|
17
|
+
inject?: ProviderToken[];
|
|
18
|
+
/**
|
|
19
|
+
* 异步工厂函数,返回模块配置
|
|
20
|
+
*/
|
|
21
|
+
useFactory: (...deps: unknown[]) => T | Promise<T>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 异步 provider 注册表
|
|
26
|
+
* 在 Application.listen() 期间,所有待初始化的异步 provider 将被顺序执行
|
|
27
|
+
*/
|
|
28
|
+
export class AsyncProviderRegistry {
|
|
29
|
+
private static instance: AsyncProviderRegistry;
|
|
30
|
+
private readonly pendingFactories: Array<{
|
|
31
|
+
token: ProviderToken;
|
|
32
|
+
factory: (container: Container) => Promise<unknown>;
|
|
33
|
+
}> = [];
|
|
34
|
+
|
|
35
|
+
public static getInstance(): AsyncProviderRegistry {
|
|
36
|
+
if (!AsyncProviderRegistry.instance) {
|
|
37
|
+
AsyncProviderRegistry.instance = new AsyncProviderRegistry();
|
|
38
|
+
}
|
|
39
|
+
return AsyncProviderRegistry.instance;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 注册一个异步 provider
|
|
44
|
+
*/
|
|
45
|
+
public register(
|
|
46
|
+
token: ProviderToken,
|
|
47
|
+
factory: (container: Container) => Promise<unknown>,
|
|
48
|
+
): void {
|
|
49
|
+
this.pendingFactories.push({ token, factory });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 初始化所有异步 providers
|
|
54
|
+
* 同时注册到根容器和所有模块子容器(覆盖占位 factory)
|
|
55
|
+
* @param container - 根 DI 容器
|
|
56
|
+
*/
|
|
57
|
+
public async initializeAll(container: Container): Promise<void> {
|
|
58
|
+
const moduleContainers = ModuleRegistry.getInstance().getAllModuleContainers();
|
|
59
|
+
|
|
60
|
+
for (const { token, factory } of this.pendingFactories) {
|
|
61
|
+
const value = await factory(container);
|
|
62
|
+
container.registerInstance(token, value);
|
|
63
|
+
|
|
64
|
+
for (const moduleContainer of moduleContainers) {
|
|
65
|
+
if (moduleContainer.isRegistered(token)) {
|
|
66
|
+
moduleContainer.registerInstance(token, value);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.pendingFactories.length = 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 检查是否有待初始化的异步 providers
|
|
75
|
+
*/
|
|
76
|
+
public hasPending(): boolean {
|
|
77
|
+
return this.pendingFactories.length > 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public clear(): void {
|
|
81
|
+
this.pendingFactories.length = 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 辅助函数:为模块添加 forRootAsync 的标准实现
|
|
87
|
+
* 各模块可直接在 forRootAsync 中调用此函数
|
|
88
|
+
*/
|
|
89
|
+
export function registerAsyncProviders<T>(
|
|
90
|
+
moduleClass: ModuleClass,
|
|
91
|
+
asyncOptions: AsyncModuleOptions<T>,
|
|
92
|
+
tokenMap: Map<ProviderToken, (config: T) => unknown>,
|
|
93
|
+
extraProviders?: ModuleProvider[],
|
|
94
|
+
): typeof moduleClass {
|
|
95
|
+
const providers: ModuleProvider[] = [];
|
|
96
|
+
const exports: ProviderToken[] = [];
|
|
97
|
+
|
|
98
|
+
for (const [token] of tokenMap) {
|
|
99
|
+
// Placeholder factory that will be resolved async during init
|
|
100
|
+
providers.push({
|
|
101
|
+
provide: token,
|
|
102
|
+
useFactory: () => {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Async provider ${String(token)} not yet initialized. ` +
|
|
105
|
+
'Ensure Application.listen() is called before resolving async providers.',
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
exports.push(token);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (extraProviders) {
|
|
113
|
+
providers.push(...extraProviders);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Register the async factory
|
|
117
|
+
const registry = AsyncProviderRegistry.getInstance();
|
|
118
|
+
for (const [token, mapper] of tokenMap) {
|
|
119
|
+
registry.register(token, async (container) => {
|
|
120
|
+
const deps: unknown[] = [];
|
|
121
|
+
if (asyncOptions.inject) {
|
|
122
|
+
for (const injectToken of asyncOptions.inject) {
|
|
123
|
+
deps.push(container.resolve(injectToken as any));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const config = await asyncOptions.useFactory(...deps);
|
|
127
|
+
return mapper(config);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const existingMetadata = Reflect.getMetadata(MODULE_METADATA_KEY, moduleClass) || {};
|
|
132
|
+
const metadata = {
|
|
133
|
+
...existingMetadata,
|
|
134
|
+
imports: [...(existingMetadata.imports || []), ...(asyncOptions.imports || [])],
|
|
135
|
+
providers: [...(existingMetadata.providers || []), ...providers],
|
|
136
|
+
exports: [...(existingMetadata.exports || []), ...exports],
|
|
137
|
+
};
|
|
138
|
+
Reflect.defineMetadata(MODULE_METADATA_KEY, metadata, moduleClass);
|
|
139
|
+
|
|
140
|
+
return moduleClass;
|
|
141
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块初始化钩子
|
|
3
|
+
* 在模块所有 providers 注册完成后调用
|
|
4
|
+
*/
|
|
5
|
+
export interface OnModuleInit {
|
|
6
|
+
onModuleInit(): Promise<void> | void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 模块销毁钩子
|
|
11
|
+
* 在应用关闭时调用(反向顺序)
|
|
12
|
+
*/
|
|
13
|
+
export interface OnModuleDestroy {
|
|
14
|
+
onModuleDestroy(): Promise<void> | void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 应用启动钩子
|
|
19
|
+
* 在所有模块初始化完成后、服务器开始监听前调用
|
|
20
|
+
*/
|
|
21
|
+
export interface OnApplicationBootstrap {
|
|
22
|
+
onApplicationBootstrap(): Promise<void> | void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 应用关闭钩子
|
|
27
|
+
* 在优雅停机开始时调用
|
|
28
|
+
*/
|
|
29
|
+
export interface OnApplicationShutdown {
|
|
30
|
+
onApplicationShutdown(signal?: string): Promise<void> | void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function hasOnModuleInit(instance: unknown): instance is OnModuleInit {
|
|
34
|
+
return (
|
|
35
|
+
instance !== null &&
|
|
36
|
+
instance !== undefined &&
|
|
37
|
+
typeof instance === 'object' &&
|
|
38
|
+
'onModuleInit' in instance &&
|
|
39
|
+
typeof (instance as OnModuleInit).onModuleInit === 'function'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function hasOnModuleDestroy(instance: unknown): instance is OnModuleDestroy {
|
|
44
|
+
return (
|
|
45
|
+
instance !== null &&
|
|
46
|
+
instance !== undefined &&
|
|
47
|
+
typeof instance === 'object' &&
|
|
48
|
+
'onModuleDestroy' in instance &&
|
|
49
|
+
typeof (instance as OnModuleDestroy).onModuleDestroy === 'function'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function hasOnApplicationBootstrap(instance: unknown): instance is OnApplicationBootstrap {
|
|
54
|
+
return (
|
|
55
|
+
instance !== null &&
|
|
56
|
+
instance !== undefined &&
|
|
57
|
+
typeof instance === 'object' &&
|
|
58
|
+
'onApplicationBootstrap' in instance &&
|
|
59
|
+
typeof (instance as OnApplicationBootstrap).onApplicationBootstrap === 'function'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function hasOnApplicationShutdown(instance: unknown): instance is OnApplicationShutdown {
|
|
64
|
+
return (
|
|
65
|
+
instance !== null &&
|
|
66
|
+
instance !== undefined &&
|
|
67
|
+
typeof instance === 'object' &&
|
|
68
|
+
'onApplicationShutdown' in instance &&
|
|
69
|
+
typeof (instance as OnApplicationShutdown).onApplicationShutdown === 'function'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 按顺序调用 onModuleInit
|
|
75
|
+
*/
|
|
76
|
+
export async function callOnModuleInit(instances: unknown[]): Promise<void> {
|
|
77
|
+
for (const instance of instances) {
|
|
78
|
+
if (hasOnModuleInit(instance)) {
|
|
79
|
+
await instance.onModuleInit();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 按顺序调用 onApplicationBootstrap
|
|
86
|
+
*/
|
|
87
|
+
export async function callOnApplicationBootstrap(instances: unknown[]): Promise<void> {
|
|
88
|
+
for (const instance of instances) {
|
|
89
|
+
if (hasOnApplicationBootstrap(instance)) {
|
|
90
|
+
await instance.onApplicationBootstrap();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 按反向顺序调用 onModuleDestroy
|
|
97
|
+
*/
|
|
98
|
+
export async function callOnModuleDestroy(instances: unknown[]): Promise<void> {
|
|
99
|
+
for (let i = instances.length - 1; i >= 0; i--) {
|
|
100
|
+
const instance = instances[i];
|
|
101
|
+
if (hasOnModuleDestroy(instance)) {
|
|
102
|
+
await instance.onModuleDestroy();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 按反向顺序调用 onApplicationShutdown
|
|
109
|
+
*/
|
|
110
|
+
export async function callOnApplicationShutdown(instances: unknown[], signal?: string): Promise<void> {
|
|
111
|
+
for (let i = instances.length - 1; i >= 0; i--) {
|
|
112
|
+
const instance = instances[i];
|
|
113
|
+
if (hasOnApplicationShutdown(instance)) {
|
|
114
|
+
await instance.onApplicationShutdown(signal);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -6,6 +6,12 @@ import { isGlobalModule } from './decorators';
|
|
|
6
6
|
import type { Constructor } from '@/core/types';
|
|
7
7
|
import type { ApplicationExtension } from '../extensions/types';
|
|
8
8
|
import type { Middleware } from '../middleware';
|
|
9
|
+
import {
|
|
10
|
+
callOnModuleInit,
|
|
11
|
+
callOnModuleDestroy,
|
|
12
|
+
callOnApplicationBootstrap,
|
|
13
|
+
callOnApplicationShutdown,
|
|
14
|
+
} from './lifecycle';
|
|
9
15
|
|
|
10
16
|
interface ModuleRef {
|
|
11
17
|
moduleClass: ModuleClass;
|
|
@@ -49,6 +55,17 @@ export class ModuleRegistry {
|
|
|
49
55
|
return this.moduleRefs.get(moduleClass);
|
|
50
56
|
}
|
|
51
57
|
|
|
58
|
+
/**
|
|
59
|
+
* 获取所有模块的子容器(用于异步 provider 初始化)
|
|
60
|
+
*/
|
|
61
|
+
public getAllModuleContainers(): Container[] {
|
|
62
|
+
const containers: Container[] = [];
|
|
63
|
+
for (const [, ref] of this.moduleRefs) {
|
|
64
|
+
containers.push(ref.container);
|
|
65
|
+
}
|
|
66
|
+
return containers;
|
|
67
|
+
}
|
|
68
|
+
|
|
52
69
|
public clear(): void {
|
|
53
70
|
this.moduleRefs.clear();
|
|
54
71
|
this.processing.clear();
|
|
@@ -207,6 +224,64 @@ export class ModuleRegistry {
|
|
|
207
224
|
return moduleRef.middlewares;
|
|
208
225
|
}
|
|
209
226
|
|
|
227
|
+
/**
|
|
228
|
+
* 解析所有模块中的 provider 实例,用于生命周期钩子调用
|
|
229
|
+
*/
|
|
230
|
+
public resolveAllProviderInstances(): unknown[] {
|
|
231
|
+
const instances: unknown[] = [];
|
|
232
|
+
for (const [, ref] of this.moduleRefs) {
|
|
233
|
+
for (const provider of ref.metadata.providers) {
|
|
234
|
+
try {
|
|
235
|
+
if (typeof provider === 'function') {
|
|
236
|
+
instances.push(ref.container.resolve(provider));
|
|
237
|
+
} else if ('useClass' in provider) {
|
|
238
|
+
const token = provider.provide ?? provider.useClass;
|
|
239
|
+
instances.push(ref.container.resolve(token as Constructor<unknown>));
|
|
240
|
+
} else if ('useValue' in provider) {
|
|
241
|
+
instances.push(provider.useValue);
|
|
242
|
+
} else if ('useFactory' in provider) {
|
|
243
|
+
instances.push(ref.container.resolve(provider.provide as Constructor<unknown>));
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
// skip providers that can't be resolved (e.g. pending async providers)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return instances;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 调用所有 provider 的 onModuleInit 钩子
|
|
255
|
+
*/
|
|
256
|
+
public async callModuleInitHooks(): Promise<void> {
|
|
257
|
+
const instances = this.resolveAllProviderInstances();
|
|
258
|
+
await callOnModuleInit(instances);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 调用所有 provider 的 onApplicationBootstrap 钩子
|
|
263
|
+
*/
|
|
264
|
+
public async callBootstrapHooks(): Promise<void> {
|
|
265
|
+
const instances = this.resolveAllProviderInstances();
|
|
266
|
+
await callOnApplicationBootstrap(instances);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 调用所有 provider 的 onModuleDestroy 钩子
|
|
271
|
+
*/
|
|
272
|
+
public async callModuleDestroyHooks(): Promise<void> {
|
|
273
|
+
const instances = this.resolveAllProviderInstances();
|
|
274
|
+
await callOnModuleDestroy(instances);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 调用所有 provider 的 onApplicationShutdown 钩子
|
|
279
|
+
*/
|
|
280
|
+
public async callShutdownHooks(signal?: string): Promise<void> {
|
|
281
|
+
const instances = this.resolveAllProviderInstances();
|
|
282
|
+
await callOnApplicationShutdown(instances, signal);
|
|
283
|
+
}
|
|
284
|
+
|
|
210
285
|
private registerExport(parentContainer: Container, moduleRef: ModuleRef, token: ProviderToken): void {
|
|
211
286
|
if (!moduleRef.container.isRegistered(token)) {
|
|
212
287
|
throw new Error(
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { Application, type ApplicationOptions } from './core/application';
|
|
2
|
+
export { applyDecorators } from './core/apply-decorators';
|
|
2
3
|
export { BunServer, type ServerOptions } from './core/server';
|
|
4
|
+
export { ClusterManager, type ClusterOptions } from './core/cluster';
|
|
3
5
|
export { Context } from './core/context';
|
|
4
6
|
export { ContextService, CONTEXT_SERVICE_TOKEN, contextStore } from './core/context-service';
|
|
5
7
|
export { Route, Router, RouteRegistry } from './router';
|
|
@@ -34,6 +36,13 @@ export {
|
|
|
34
36
|
type ModuleClass,
|
|
35
37
|
} from './di/module';
|
|
36
38
|
export { ModuleRegistry } from './di/module-registry';
|
|
39
|
+
export { AsyncProviderRegistry, type AsyncModuleOptions } from './di/async-module';
|
|
40
|
+
export type {
|
|
41
|
+
OnModuleInit,
|
|
42
|
+
OnModuleDestroy,
|
|
43
|
+
OnApplicationBootstrap,
|
|
44
|
+
OnApplicationShutdown,
|
|
45
|
+
} from './di/lifecycle';
|
|
37
46
|
export {
|
|
38
47
|
InterceptorRegistry,
|
|
39
48
|
InterceptorChain,
|
|
@@ -271,6 +280,12 @@ export {
|
|
|
271
280
|
HEALTH_INDICATORS_TOKEN,
|
|
272
281
|
HEALTH_OPTIONS_TOKEN,
|
|
273
282
|
} from './health';
|
|
283
|
+
export {
|
|
284
|
+
DashboardModule,
|
|
285
|
+
DashboardService,
|
|
286
|
+
DASHBOARD_OPTIONS_TOKEN,
|
|
287
|
+
type DashboardModuleOptions,
|
|
288
|
+
} from './dashboard';
|
|
274
289
|
export {
|
|
275
290
|
MetricsModule,
|
|
276
291
|
MetricsCollector,
|
|
@@ -351,12 +366,32 @@ export {
|
|
|
351
366
|
type AuthContext,
|
|
352
367
|
type AuthConfig,
|
|
353
368
|
} from './auth';
|
|
369
|
+
export {
|
|
370
|
+
DebugModule,
|
|
371
|
+
RequestRecorder,
|
|
372
|
+
createDebugMiddleware,
|
|
373
|
+
DEBUG_OPTIONS_TOKEN,
|
|
374
|
+
DEBUG_RECORDER_TOKEN,
|
|
375
|
+
type DebugModuleOptions,
|
|
376
|
+
type RequestRecord,
|
|
377
|
+
} from './debug';
|
|
354
378
|
export {
|
|
355
379
|
PerformanceHarness,
|
|
356
380
|
StressTester,
|
|
357
381
|
type BenchmarkResult,
|
|
358
382
|
type StressResult,
|
|
359
383
|
} from './testing/harness';
|
|
384
|
+
export { Test, TestingModule, TestingModuleBuilder } from './testing/testing-module';
|
|
385
|
+
export { TestHttpClient } from './testing/test-client';
|
|
386
|
+
// Client Generation
|
|
387
|
+
export {
|
|
388
|
+
ClientGenerator,
|
|
389
|
+
createClient,
|
|
390
|
+
type RouteManifest,
|
|
391
|
+
type RouteManifestEntry,
|
|
392
|
+
type ClientConfig,
|
|
393
|
+
type ClientRequestOptions,
|
|
394
|
+
} from './client';
|
|
360
395
|
// Cache 模块
|
|
361
396
|
export {
|
|
362
397
|
CacheModule,
|
package/src/router/route.ts
CHANGED
|
@@ -35,14 +35,14 @@ export class Route {
|
|
|
35
35
|
public readonly methodName?: string;
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* 动态路由匹配用正则(比 URLPattern.exec() 快约 10 倍)
|
|
39
39
|
*/
|
|
40
|
-
private readonly pattern
|
|
40
|
+
private readonly pattern?: RegExp;
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* 动态路由参数名列表
|
|
44
44
|
*/
|
|
45
|
-
private readonly paramNames
|
|
45
|
+
private readonly paramNames?: string[];
|
|
46
46
|
|
|
47
47
|
private readonly middlewarePipeline: MiddlewarePipeline | null;
|
|
48
48
|
private readonly staticKey?: string;
|
|
@@ -62,23 +62,21 @@ export class Route {
|
|
|
62
62
|
this.controllerClass = controllerClass;
|
|
63
63
|
this.methodName = methodName;
|
|
64
64
|
|
|
65
|
-
// 解析路径参数
|
|
66
|
-
const { pattern, paramNames } = this.parsePath(path);
|
|
67
|
-
this.pattern = pattern;
|
|
68
|
-
this.paramNames = paramNames;
|
|
69
|
-
this.middlewarePipeline = middlewares.length > 0 ? new MiddlewarePipeline(middlewares) : null;
|
|
70
65
|
this.isStatic = !path.includes(':') && !path.includes('*');
|
|
71
66
|
if (this.isStatic) {
|
|
72
67
|
this.staticKey = `${method}:${path}`;
|
|
68
|
+
} else {
|
|
69
|
+
const { pattern, paramNames } = Route.parsePath(path);
|
|
70
|
+
this.pattern = pattern;
|
|
71
|
+
this.paramNames = paramNames;
|
|
73
72
|
}
|
|
73
|
+
this.middlewarePipeline = middlewares.length > 0 ? new MiddlewarePipeline(middlewares) : null;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
77
|
* 解析路径,生成匹配模式和参数名列表
|
|
78
|
-
* @param path - 路由路径
|
|
79
|
-
* @returns 匹配模式和参数名列表
|
|
80
78
|
*/
|
|
81
|
-
private parsePath(path: string): { pattern: RegExp; paramNames: string[] } {
|
|
79
|
+
private static parsePath(path: string): { pattern: RegExp; paramNames: string[] } {
|
|
82
80
|
const paramNames: string[] = [];
|
|
83
81
|
const patternString = path
|
|
84
82
|
.replace(/:([^/]+)/g, (_, paramName) => {
|
|
@@ -87,8 +85,7 @@ export class Route {
|
|
|
87
85
|
})
|
|
88
86
|
.replace(/\*/g, '.*');
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
return { pattern, paramNames };
|
|
88
|
+
return { pattern: new RegExp(`^${patternString}$`), paramNames };
|
|
92
89
|
}
|
|
93
90
|
|
|
94
91
|
/**
|
|
@@ -98,21 +95,24 @@ export class Route {
|
|
|
98
95
|
* @returns 匹配结果
|
|
99
96
|
*/
|
|
100
97
|
public match(method: HttpMethod, path: string): RouteMatch {
|
|
101
|
-
// 方法不匹配
|
|
102
98
|
if (this.method !== method) {
|
|
103
99
|
return { matched: false, params: {} };
|
|
104
100
|
}
|
|
105
101
|
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
if (this.isStatic) {
|
|
103
|
+
return path === this.path
|
|
104
|
+
? { matched: true, params: {} }
|
|
105
|
+
: { matched: false, params: {} };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const match = path.match(this.pattern!);
|
|
108
109
|
if (!match) {
|
|
109
110
|
return { matched: false, params: {} };
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
// 提取路径参数
|
|
113
113
|
const params: Record<string, string> = {};
|
|
114
|
-
for (let i = 0; i < this.paramNames
|
|
115
|
-
params[this.paramNames[i]] = match[i + 1] ?? '';
|
|
114
|
+
for (let i = 0; i < this.paramNames!.length; i++) {
|
|
115
|
+
params[this.paramNames![i]] = match[i + 1] ?? '';
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
return { matched: true, params };
|
package/src/swagger/generator.ts
CHANGED
|
@@ -214,6 +214,106 @@ export class SwaggerGenerator {
|
|
|
214
214
|
return document;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* 生成 Markdown 格式的 API 文档
|
|
219
|
+
* 利用 Bun 1.3.8+ 原生 Bun.markdown 内置解析器
|
|
220
|
+
*/
|
|
221
|
+
public generateMarkdown(): string {
|
|
222
|
+
const doc = this.generate();
|
|
223
|
+
const lines: string[] = [];
|
|
224
|
+
|
|
225
|
+
lines.push(`# ${doc.info.title}`);
|
|
226
|
+
lines.push('');
|
|
227
|
+
if (doc.info.description) {
|
|
228
|
+
lines.push(doc.info.description);
|
|
229
|
+
lines.push('');
|
|
230
|
+
}
|
|
231
|
+
lines.push(`**Version:** ${doc.info.version}`);
|
|
232
|
+
lines.push('');
|
|
233
|
+
|
|
234
|
+
if (doc.servers?.length) {
|
|
235
|
+
lines.push('## Servers');
|
|
236
|
+
lines.push('');
|
|
237
|
+
for (const server of doc.servers) {
|
|
238
|
+
lines.push(`- \`${server.url}\`${server.description ? ' — ' + server.description : ''}`);
|
|
239
|
+
}
|
|
240
|
+
lines.push('');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (doc.tags?.length) {
|
|
244
|
+
lines.push('## Tags');
|
|
245
|
+
lines.push('');
|
|
246
|
+
for (const tag of doc.tags) {
|
|
247
|
+
lines.push(`- **${tag.name}**${tag.description ? ': ' + tag.description : ''}`);
|
|
248
|
+
}
|
|
249
|
+
lines.push('');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
lines.push('## Endpoints');
|
|
253
|
+
lines.push('');
|
|
254
|
+
|
|
255
|
+
const methods = ['get', 'post', 'put', 'delete', 'patch'] as const;
|
|
256
|
+
for (const [path, pathObj] of Object.entries(doc.paths)) {
|
|
257
|
+
for (const method of methods) {
|
|
258
|
+
const operation = pathObj[method];
|
|
259
|
+
if (!operation) continue;
|
|
260
|
+
|
|
261
|
+
lines.push(`### \`${method.toUpperCase()}\` ${path}`);
|
|
262
|
+
lines.push('');
|
|
263
|
+
if (operation.summary) {
|
|
264
|
+
lines.push(`**${operation.summary}**`);
|
|
265
|
+
lines.push('');
|
|
266
|
+
}
|
|
267
|
+
if (operation.description) {
|
|
268
|
+
lines.push(operation.description);
|
|
269
|
+
lines.push('');
|
|
270
|
+
}
|
|
271
|
+
if (operation.tags?.length) {
|
|
272
|
+
lines.push(`Tags: ${operation.tags.map((t) => `\`${t}\``).join(', ')}`);
|
|
273
|
+
lines.push('');
|
|
274
|
+
}
|
|
275
|
+
if (operation.deprecated) {
|
|
276
|
+
lines.push('> **Deprecated**');
|
|
277
|
+
lines.push('');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (operation.parameters?.length) {
|
|
281
|
+
lines.push('| Parameter | In | Type | Required | Description |');
|
|
282
|
+
lines.push('|---|---|---|---|---|');
|
|
283
|
+
for (const param of operation.parameters) {
|
|
284
|
+
const type = param.schema?.type ?? 'string';
|
|
285
|
+
const required = param.required ? 'Yes' : 'No';
|
|
286
|
+
lines.push(`| \`${param.name}\` | ${param.in} | ${type} | ${required} | ${param.description ?? ''} |`);
|
|
287
|
+
}
|
|
288
|
+
lines.push('');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (operation.responses) {
|
|
292
|
+
lines.push('**Responses:**');
|
|
293
|
+
lines.push('');
|
|
294
|
+
for (const [status, resp] of Object.entries(operation.responses)) {
|
|
295
|
+
lines.push(`- **${status}**: ${resp.description ?? ''}`);
|
|
296
|
+
}
|
|
297
|
+
lines.push('');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
lines.push('---');
|
|
301
|
+
lines.push('');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return lines.join('\n');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 生成 Markdown 并渲染为 HTML
|
|
310
|
+
* 利用 Bun.markdown.html() 进行高性能渲染
|
|
311
|
+
*/
|
|
312
|
+
public generateMarkdownHtml(): string {
|
|
313
|
+
const md = this.generateMarkdown();
|
|
314
|
+
return Bun.markdown.html(md, { headings: true });
|
|
315
|
+
}
|
|
316
|
+
|
|
217
317
|
/**
|
|
218
318
|
* 规范化路径
|
|
219
319
|
*/
|