@dangao/bun-server 2.1.0 → 2.2.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/dist/core/application.d.ts.map +1 -1
- package/dist/di/container.d.ts +16 -0
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/lifecycle.d.ts +48 -0
- package/dist/di/lifecycle.d.ts.map +1 -1
- package/dist/di/module-registry.d.ts +10 -6
- package/dist/di/module-registry.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +185 -67
- package/docs/lifecycle.md +74 -4
- package/docs/zh/lifecycle.md +47 -8
- package/package.json +1 -1
- package/src/core/application.ts +36 -23
- package/src/di/container.ts +55 -1
- package/src/di/lifecycle.ts +114 -0
- package/src/di/module-registry.ts +58 -10
- package/src/index.ts +4 -0
- package/tests/di/lifecycle.test.ts +102 -1
- package/tests/di/scoped-lifecycle.test.ts +61 -0
package/docs/lifecycle.md
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
# Lifecycle Hooks
|
|
2
2
|
|
|
3
|
-
Bun Server supports lifecycle hooks that let
|
|
3
|
+
Bun Server supports lifecycle hooks that let components (`@Injectable` / `@Controller`) participate from creation to destruction.
|
|
4
4
|
|
|
5
5
|
## Interfaces
|
|
6
6
|
|
|
7
7
|
| Interface | Method | When Called |
|
|
8
8
|
|-----------|--------|-------------|
|
|
9
|
+
| `ComponentClassBeforeCreate` | `static onBeforeCreate()` | Right before the component instance is created |
|
|
10
|
+
| `OnAfterCreate` | `onAfterCreate()` | Right after instance creation and post processors |
|
|
9
11
|
| `OnModuleInit` | `onModuleInit()` | After all module providers are registered |
|
|
10
12
|
| `OnModuleDestroy` | `onModuleDestroy()` | During shutdown (reverse order) |
|
|
13
|
+
| `OnBeforeDestroy` | `onBeforeDestroy()` | Before `onModuleDestroy` during shutdown (reverse order) |
|
|
14
|
+
| `OnAfterDestroy` | `onAfterDestroy()` | After `onModuleDestroy` during shutdown (reverse order) |
|
|
11
15
|
| `OnApplicationBootstrap` | `onApplicationBootstrap()` | After all modules init, before server listens |
|
|
12
16
|
| `OnApplicationShutdown` | `onApplicationShutdown(signal?)` | When graceful shutdown begins |
|
|
13
17
|
|
|
14
18
|
## Execution Order
|
|
15
19
|
|
|
20
|
+
**Creation (per component instance)**: `onBeforeCreate` (static) -> instantiate -> `onAfterCreate`
|
|
21
|
+
|
|
16
22
|
**Startup**: `onModuleInit` (all modules) -> `onApplicationBootstrap` (all modules) -> server starts
|
|
17
23
|
|
|
18
|
-
**Shutdown**: `onApplicationShutdown` (reverse order) -> `onModuleDestroy` (reverse order)
|
|
24
|
+
**Shutdown**: `onApplicationShutdown` (reverse order) -> `onBeforeDestroy` (reverse order) -> `onModuleDestroy` (reverse order) -> `onAfterDestroy` (reverse order)
|
|
25
|
+
|
|
26
|
+
For `Lifecycle.Scoped` components, destroy hooks are executed automatically at the end of each request context.
|
|
19
27
|
|
|
20
28
|
## Provider Deduplication
|
|
21
29
|
|
|
@@ -23,19 +31,41 @@ When the same provider instance is exported or registered by multiple tokens,
|
|
|
23
31
|
`onModuleInit` now runs only once for that instance. This avoids duplicate
|
|
24
32
|
initialization side effects in shared singleton objects.
|
|
25
33
|
|
|
26
|
-
## Example:
|
|
34
|
+
## Example: Component hooks from create to destroy
|
|
27
35
|
|
|
28
36
|
```ts
|
|
29
37
|
import {
|
|
30
38
|
Injectable,
|
|
39
|
+
Controller,
|
|
40
|
+
GET,
|
|
41
|
+
Module,
|
|
31
42
|
OnModuleInit,
|
|
32
43
|
OnModuleDestroy,
|
|
44
|
+
type ComponentClassBeforeCreate,
|
|
45
|
+
OnAfterCreate,
|
|
46
|
+
OnBeforeDestroy,
|
|
47
|
+
OnAfterDestroy,
|
|
33
48
|
} from '@dangao/bun-server';
|
|
34
49
|
|
|
35
50
|
@Injectable()
|
|
36
|
-
class DatabaseService
|
|
51
|
+
class DatabaseService
|
|
52
|
+
implements
|
|
53
|
+
OnAfterCreate,
|
|
54
|
+
OnModuleInit,
|
|
55
|
+
OnBeforeDestroy,
|
|
56
|
+
OnModuleDestroy,
|
|
57
|
+
OnAfterDestroy
|
|
58
|
+
{
|
|
37
59
|
private connected = false;
|
|
38
60
|
|
|
61
|
+
public static onBeforeCreate(): void {
|
|
62
|
+
console.log('[DatabaseService] Before create');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public onAfterCreate(): void {
|
|
66
|
+
console.log('[DatabaseService] After create');
|
|
67
|
+
}
|
|
68
|
+
|
|
39
69
|
public async onModuleInit(): Promise<void> {
|
|
40
70
|
console.log('[DatabaseService] Connecting...');
|
|
41
71
|
await this.connect();
|
|
@@ -48,6 +78,14 @@ class DatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
48
78
|
this.connected = false;
|
|
49
79
|
}
|
|
50
80
|
|
|
81
|
+
public onBeforeDestroy(): void {
|
|
82
|
+
console.log('[DatabaseService] Before destroy');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public onAfterDestroy(): void {
|
|
86
|
+
console.log('[DatabaseService] After destroy');
|
|
87
|
+
}
|
|
88
|
+
|
|
51
89
|
public isConnected(): boolean {
|
|
52
90
|
return this.connected;
|
|
53
91
|
}
|
|
@@ -60,6 +98,36 @@ class DatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
60
98
|
// Close DB connection
|
|
61
99
|
}
|
|
62
100
|
}
|
|
101
|
+
|
|
102
|
+
@Controller('/health')
|
|
103
|
+
class HealthController implements OnAfterCreate, OnBeforeDestroy, OnAfterDestroy {
|
|
104
|
+
public static onBeforeCreate(): void {
|
|
105
|
+
console.log('[HealthController] Before create');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public onAfterCreate(): void {
|
|
109
|
+
console.log('[HealthController] After create');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public onBeforeDestroy(): void {
|
|
113
|
+
console.log('[HealthController] Before destroy');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public onAfterDestroy(): void {
|
|
117
|
+
console.log('[HealthController] After destroy');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@GET('/')
|
|
121
|
+
public get(): object {
|
|
122
|
+
return { ok: true };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@Module({
|
|
127
|
+
controllers: [HealthController],
|
|
128
|
+
providers: [DatabaseService],
|
|
129
|
+
})
|
|
130
|
+
class AppModule {}
|
|
63
131
|
```
|
|
64
132
|
|
|
65
133
|
## Example: Application-level hooks
|
|
@@ -75,4 +143,6 @@ class AppService implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
|
75
143
|
console.log(`Shutting down (signal: ${signal ?? 'none'})`);
|
|
76
144
|
}
|
|
77
145
|
}
|
|
146
|
+
|
|
147
|
+
const _beforeCreateHook: ComponentClassBeforeCreate = DatabaseService;
|
|
78
148
|
```
|
package/docs/zh/lifecycle.md
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
# 生命周期钩子
|
|
2
2
|
|
|
3
|
-
Bun Server
|
|
3
|
+
Bun Server 支持组件级生命周期钩子,可让 `@Injectable` / `@Controller` 从创建前到销毁后执行自定义逻辑。
|
|
4
4
|
|
|
5
5
|
## 接口定义
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
7
|
+
- **ComponentClassBeforeCreate**:`static onBeforeCreate()`,组件实例创建前调用
|
|
8
|
+
- **OnAfterCreate**:`onAfterCreate()`,组件实例创建并完成后处理后调用
|
|
9
|
+
- **OnModuleInit**:`onModuleInit()`,在模块所有组件初始化阶段调用
|
|
9
10
|
- **OnApplicationBootstrap**:`onApplicationBootstrap()`,在所有模块初始化完成后、服务器开始监听前调用
|
|
10
11
|
- **OnApplicationShutdown**:`onApplicationShutdown(signal?)`,在优雅停机开始时调用
|
|
12
|
+
- **OnBeforeDestroy**:`onBeforeDestroy()`,在 `onModuleDestroy()` 前调用(反向顺序)
|
|
13
|
+
- **OnModuleDestroy**:`onModuleDestroy()`,在应用关闭时调用(反向顺序)
|
|
14
|
+
- **OnAfterDestroy**:`onAfterDestroy()`,在 `onModuleDestroy()` 后调用(反向顺序)
|
|
11
15
|
|
|
12
16
|
## 执行顺序
|
|
13
17
|
|
|
18
|
+
**创建阶段(每个组件实例)**:`onBeforeCreate`(静态)→ 实例化 → `onAfterCreate`
|
|
19
|
+
|
|
14
20
|
**启动阶段**:`onModuleInit` → `onApplicationBootstrap`
|
|
15
21
|
|
|
16
|
-
**关闭阶段**:`onApplicationShutdown` → `onModuleDestroy`(均为反向顺序,即后注册的先执行)
|
|
22
|
+
**关闭阶段**:`onApplicationShutdown` → `onBeforeDestroy` → `onModuleDestroy` → `onAfterDestroy`(均为反向顺序,即后注册的先执行)
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
对于 `Lifecycle.Scoped` 组件,请求上下文结束时会自动触发其销毁钩子。
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
## 组件去重行为
|
|
27
|
+
|
|
28
|
+
当同一个组件实例通过多个 token 重复注册/导出时,生命周期钩子只会执行一次,避免共享单例出现重复副作用。
|
|
22
29
|
|
|
23
30
|
## 示例:DatabaseService 的初始化和销毁
|
|
24
31
|
|
|
@@ -41,6 +48,14 @@ import type {
|
|
|
41
48
|
class DatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
42
49
|
private connected = false;
|
|
43
50
|
|
|
51
|
+
public static onBeforeCreate(): void {
|
|
52
|
+
console.log('[DatabaseService] 创建前');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public onAfterCreate(): void {
|
|
56
|
+
console.log('[DatabaseService] 创建后');
|
|
57
|
+
}
|
|
58
|
+
|
|
44
59
|
public async onModuleInit(): Promise<void> {
|
|
45
60
|
console.log('[DatabaseService] 正在连接数据库...');
|
|
46
61
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
@@ -54,6 +69,14 @@ class DatabaseService implements OnModuleInit, OnModuleDestroy {
|
|
|
54
69
|
console.log('[DatabaseService] 已断开');
|
|
55
70
|
}
|
|
56
71
|
|
|
72
|
+
public onBeforeDestroy(): void {
|
|
73
|
+
console.log('[DatabaseService] 销毁前');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public onAfterDestroy(): void {
|
|
77
|
+
console.log('[DatabaseService] 销毁后');
|
|
78
|
+
}
|
|
79
|
+
|
|
57
80
|
public isConnected(): boolean {
|
|
58
81
|
return this.connected;
|
|
59
82
|
}
|
|
@@ -72,6 +95,22 @@ class AppService implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
|
72
95
|
|
|
73
96
|
@Controller('/api')
|
|
74
97
|
class AppController {
|
|
98
|
+
public static onBeforeCreate(): void {
|
|
99
|
+
console.log('[AppController] 创建前');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public onAfterCreate(): void {
|
|
103
|
+
console.log('[AppController] 创建后');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public onBeforeDestroy(): void {
|
|
107
|
+
console.log('[AppController] 销毁前');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public onAfterDestroy(): void {
|
|
111
|
+
console.log('[AppController] 销毁后');
|
|
112
|
+
}
|
|
113
|
+
|
|
75
114
|
@GET('/status')
|
|
76
115
|
public status(): object {
|
|
77
116
|
return { status: 'running', timestamp: Date.now() };
|
|
@@ -89,4 +128,4 @@ app.registerModule(AppModule);
|
|
|
89
128
|
await app.listen();
|
|
90
129
|
```
|
|
91
130
|
|
|
92
|
-
按 Ctrl+C 触发关闭时,将依次执行 `onApplicationShutdown
|
|
131
|
+
按 Ctrl+C 触发关闭时,将依次执行 `onApplicationShutdown`、`onBeforeDestroy`、`onModuleDestroy`、`onAfterDestroy`。
|
package/package.json
CHANGED
package/src/core/application.ts
CHANGED
|
@@ -324,6 +324,7 @@ export class Application {
|
|
|
324
324
|
*/
|
|
325
325
|
private async handleRequest(context: Context): Promise<Response> {
|
|
326
326
|
const logger = LoggerManager.getLogger();
|
|
327
|
+
const moduleRegistry = ModuleRegistry.getInstance();
|
|
327
328
|
logger.debug('[Request] Incoming', {
|
|
328
329
|
method: context.method,
|
|
329
330
|
path: context.path,
|
|
@@ -332,34 +333,46 @@ export class Application {
|
|
|
332
333
|
|
|
333
334
|
// 使用 AsyncLocalStorage 包裹请求处理,确保所有中间件和控制器都在请求上下文中执行
|
|
334
335
|
return await contextStore.run(context, async () => {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
336
|
+
try {
|
|
337
|
+
// 对于 POST、PUT、PATCH 请求,提前解析 body 并缓存
|
|
338
|
+
// 这样可以确保 Request.body 流只读取一次
|
|
339
|
+
if (['POST', 'PUT', 'PATCH'].includes(context.method)) {
|
|
340
|
+
await context.getBody();
|
|
341
|
+
}
|
|
340
342
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
343
|
+
// 先通过路由解析出处理器信息,便于安全中间件等基于路由元数据做决策
|
|
344
|
+
const registry = RouteRegistry.getInstance();
|
|
345
|
+
const router = registry.getRouter();
|
|
344
346
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
+
// 预解析路由,仅设置上下文信息,不执行处理器
|
|
348
|
+
await router.preHandle(context);
|
|
347
349
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
350
|
+
// 再进入中间件管道,由中间件(如安全过滤器)根据 routeHandler 和 Auth 元数据做校验,
|
|
351
|
+
// 最后再由路由真正执行控制器方法
|
|
352
|
+
return await this.middlewarePipeline.run(context, async () => {
|
|
353
|
+
const response = await router.handle(context);
|
|
354
|
+
if (response) {
|
|
355
|
+
return response;
|
|
356
|
+
}
|
|
355
357
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
358
|
+
logger.debug('[Router] No route matched', {
|
|
359
|
+
method: context.method,
|
|
360
|
+
path: context.path,
|
|
361
|
+
});
|
|
362
|
+
context.setStatus(404);
|
|
363
|
+
return context.createErrorResponse({ error: 'Not Found' });
|
|
359
364
|
});
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
365
|
+
} finally {
|
|
366
|
+
try {
|
|
367
|
+
await moduleRegistry.disposeScopedInstances(context);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
logger.warn('[Application] Failed to dispose scoped instances', {
|
|
370
|
+
path: context.path,
|
|
371
|
+
method: context.method,
|
|
372
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
363
376
|
});
|
|
364
377
|
}
|
|
365
378
|
|
package/src/di/container.ts
CHANGED
|
@@ -14,6 +14,13 @@ import {
|
|
|
14
14
|
import { LoggerManager } from "@dangao/logsmith";
|
|
15
15
|
import type { Constructor } from "@/core/types";
|
|
16
16
|
import { contextStore } from "../core/context-service";
|
|
17
|
+
import {
|
|
18
|
+
callComponentBeforeCreate,
|
|
19
|
+
callOnAfterCreate,
|
|
20
|
+
callOnBeforeDestroy,
|
|
21
|
+
callOnModuleDestroy,
|
|
22
|
+
callOnAfterDestroy,
|
|
23
|
+
} from "./lifecycle";
|
|
17
24
|
|
|
18
25
|
/**
|
|
19
26
|
* 依赖注入容器
|
|
@@ -365,6 +372,7 @@ export class Container {
|
|
|
365
372
|
* @returns 实例
|
|
366
373
|
*/
|
|
367
374
|
private instantiate<T>(constructor: Constructor<T>): T {
|
|
375
|
+
callComponentBeforeCreate(constructor);
|
|
368
376
|
const plan = this.getDependencyPlan(constructor);
|
|
369
377
|
|
|
370
378
|
let instance: T;
|
|
@@ -379,7 +387,9 @@ export class Container {
|
|
|
379
387
|
}
|
|
380
388
|
|
|
381
389
|
// 应用后处理器
|
|
382
|
-
|
|
390
|
+
const processed = this.applyPostProcessors(instance, constructor);
|
|
391
|
+
callOnAfterCreate(processed);
|
|
392
|
+
return processed;
|
|
383
393
|
}
|
|
384
394
|
|
|
385
395
|
/**
|
|
@@ -409,6 +419,50 @@ export class Container {
|
|
|
409
419
|
// scopedInstances 使用 WeakMap,当 Context 对象被 GC 时会自动清理
|
|
410
420
|
}
|
|
411
421
|
|
|
422
|
+
/**
|
|
423
|
+
* 获取指定请求上下文下的 scoped 实例
|
|
424
|
+
* @param context - 请求上下文对象
|
|
425
|
+
* @returns 去重后的 scoped 实例列表
|
|
426
|
+
*/
|
|
427
|
+
public getScopedInstances(context: object): unknown[] {
|
|
428
|
+
const scopedMap = this.scopedInstances.get(context);
|
|
429
|
+
if (!scopedMap || scopedMap.size === 0) {
|
|
430
|
+
return [];
|
|
431
|
+
}
|
|
432
|
+
const seen = new Set<unknown>();
|
|
433
|
+
const instances: unknown[] = [];
|
|
434
|
+
for (const instance of scopedMap.values()) {
|
|
435
|
+
if (!seen.has(instance)) {
|
|
436
|
+
seen.add(instance);
|
|
437
|
+
instances.push(instance);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return instances;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* 清理指定请求上下文的 scoped 实例缓存
|
|
445
|
+
* @param context - 请求上下文对象
|
|
446
|
+
*/
|
|
447
|
+
public clearScopedInstances(context: object): void {
|
|
448
|
+
this.scopedInstances.delete(context);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* 触发指定请求上下文下 scoped 实例的销毁钩子并清理缓存
|
|
453
|
+
* @param context - 请求上下文对象
|
|
454
|
+
*/
|
|
455
|
+
public async disposeScopedInstances(context: object): Promise<void> {
|
|
456
|
+
const instances = this.getScopedInstances(context);
|
|
457
|
+
if (instances.length === 0) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
await callOnBeforeDestroy(instances);
|
|
461
|
+
await callOnModuleDestroy(instances);
|
|
462
|
+
await callOnAfterDestroy(instances);
|
|
463
|
+
this.clearScopedInstances(context);
|
|
464
|
+
}
|
|
465
|
+
|
|
412
466
|
/**
|
|
413
467
|
* 检查是否已注册
|
|
414
468
|
* @param token - 提供者标识符
|
package/src/di/lifecycle.ts
CHANGED
|
@@ -30,6 +30,38 @@ export interface OnApplicationShutdown {
|
|
|
30
30
|
onApplicationShutdown(signal?: string): Promise<void> | void;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 组件创建前钩子(静态类方法)
|
|
35
|
+
* 在实例化前调用,适用于 Controller / Injectable 类
|
|
36
|
+
*/
|
|
37
|
+
export type ComponentClassBeforeCreate = {
|
|
38
|
+
onBeforeCreate(): void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 组件创建后钩子(实例)
|
|
43
|
+
* 在实例化并完成后处理后调用
|
|
44
|
+
*/
|
|
45
|
+
export interface OnAfterCreate {
|
|
46
|
+
onAfterCreate(): void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 组件销毁前钩子(实例)
|
|
51
|
+
* 在 onModuleDestroy 之前调用(反向顺序)
|
|
52
|
+
*/
|
|
53
|
+
export interface OnBeforeDestroy {
|
|
54
|
+
onBeforeDestroy(): Promise<void> | void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 组件销毁后钩子(实例)
|
|
59
|
+
* 在 onModuleDestroy 之后调用(反向顺序)
|
|
60
|
+
*/
|
|
61
|
+
export interface OnAfterDestroy {
|
|
62
|
+
onAfterDestroy(): Promise<void> | void;
|
|
63
|
+
}
|
|
64
|
+
|
|
33
65
|
export function hasOnModuleInit(instance: unknown): instance is OnModuleInit {
|
|
34
66
|
return (
|
|
35
67
|
instance !== null &&
|
|
@@ -70,6 +102,64 @@ export function hasOnApplicationShutdown(instance: unknown): instance is OnAppli
|
|
|
70
102
|
);
|
|
71
103
|
}
|
|
72
104
|
|
|
105
|
+
export function hasComponentBeforeCreate(target: unknown): target is ComponentClassBeforeCreate {
|
|
106
|
+
return (
|
|
107
|
+
target !== null &&
|
|
108
|
+
target !== undefined &&
|
|
109
|
+
typeof target === 'function' &&
|
|
110
|
+
'onBeforeCreate' in target &&
|
|
111
|
+
typeof (target as ComponentClassBeforeCreate).onBeforeCreate === 'function'
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function hasOnAfterCreate(instance: unknown): instance is OnAfterCreate {
|
|
116
|
+
return (
|
|
117
|
+
instance !== null &&
|
|
118
|
+
instance !== undefined &&
|
|
119
|
+
typeof instance === 'object' &&
|
|
120
|
+
'onAfterCreate' in instance &&
|
|
121
|
+
typeof (instance as OnAfterCreate).onAfterCreate === 'function'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function hasOnBeforeDestroy(instance: unknown): instance is OnBeforeDestroy {
|
|
126
|
+
return (
|
|
127
|
+
instance !== null &&
|
|
128
|
+
instance !== undefined &&
|
|
129
|
+
typeof instance === 'object' &&
|
|
130
|
+
'onBeforeDestroy' in instance &&
|
|
131
|
+
typeof (instance as OnBeforeDestroy).onBeforeDestroy === 'function'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function hasOnAfterDestroy(instance: unknown): instance is OnAfterDestroy {
|
|
136
|
+
return (
|
|
137
|
+
instance !== null &&
|
|
138
|
+
instance !== undefined &&
|
|
139
|
+
typeof instance === 'object' &&
|
|
140
|
+
'onAfterDestroy' in instance &&
|
|
141
|
+
typeof (instance as OnAfterDestroy).onAfterDestroy === 'function'
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 调用组件类静态 onBeforeCreate
|
|
147
|
+
*/
|
|
148
|
+
export function callComponentBeforeCreate(target: unknown): void {
|
|
149
|
+
if (hasComponentBeforeCreate(target)) {
|
|
150
|
+
target.onBeforeCreate();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 调用组件实例 onAfterCreate
|
|
156
|
+
*/
|
|
157
|
+
export function callOnAfterCreate(instance: unknown): void {
|
|
158
|
+
if (hasOnAfterCreate(instance)) {
|
|
159
|
+
instance.onAfterCreate();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
73
163
|
/**
|
|
74
164
|
* 按顺序调用 onModuleInit
|
|
75
165
|
*/
|
|
@@ -115,3 +205,27 @@ export async function callOnApplicationShutdown(instances: unknown[], signal?: s
|
|
|
115
205
|
}
|
|
116
206
|
}
|
|
117
207
|
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 按反向顺序调用 onBeforeDestroy
|
|
211
|
+
*/
|
|
212
|
+
export async function callOnBeforeDestroy(instances: unknown[]): Promise<void> {
|
|
213
|
+
for (let i = instances.length - 1; i >= 0; i--) {
|
|
214
|
+
const instance = instances[i];
|
|
215
|
+
if (hasOnBeforeDestroy(instance)) {
|
|
216
|
+
await instance.onBeforeDestroy();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 按反向顺序调用 onAfterDestroy
|
|
223
|
+
*/
|
|
224
|
+
export async function callOnAfterDestroy(instances: unknown[]): Promise<void> {
|
|
225
|
+
for (let i = instances.length - 1; i >= 0; i--) {
|
|
226
|
+
const instance = instances[i];
|
|
227
|
+
if (hasOnAfterDestroy(instance)) {
|
|
228
|
+
await instance.onAfterDestroy();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
callOnModuleDestroy,
|
|
12
12
|
callOnApplicationBootstrap,
|
|
13
13
|
callOnApplicationShutdown,
|
|
14
|
+
callOnBeforeDestroy,
|
|
15
|
+
callOnAfterDestroy,
|
|
14
16
|
} from './lifecycle';
|
|
15
17
|
|
|
16
18
|
interface ModuleRef {
|
|
@@ -225,9 +227,9 @@ export class ModuleRegistry {
|
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
/**
|
|
228
|
-
*
|
|
230
|
+
* 解析所有模块中的组件实例(providers + controllers),用于生命周期钩子调用
|
|
229
231
|
*/
|
|
230
|
-
public
|
|
232
|
+
public resolveAllComponentInstances(): unknown[] {
|
|
231
233
|
const instances: unknown[] = [];
|
|
232
234
|
const seen = new Set<unknown>();
|
|
233
235
|
for (const [, ref] of this.moduleRefs) {
|
|
@@ -262,42 +264,88 @@ export class ModuleRegistry {
|
|
|
262
264
|
// skip providers that can't be resolved (e.g. pending async providers)
|
|
263
265
|
}
|
|
264
266
|
}
|
|
267
|
+
for (const controller of ref.metadata.controllers) {
|
|
268
|
+
try {
|
|
269
|
+
const instance = ref.container.resolve(controller);
|
|
270
|
+
if (!seen.has(instance)) {
|
|
271
|
+
seen.add(instance);
|
|
272
|
+
instances.push(instance);
|
|
273
|
+
}
|
|
274
|
+
} catch (_error) {
|
|
275
|
+
// skip controllers that can't be resolved
|
|
276
|
+
}
|
|
277
|
+
}
|
|
265
278
|
}
|
|
266
279
|
return instances;
|
|
267
280
|
}
|
|
268
281
|
|
|
269
282
|
/**
|
|
270
|
-
*
|
|
283
|
+
* 调用所有组件(providers + controllers)的 onModuleInit 钩子
|
|
271
284
|
*/
|
|
272
285
|
public async callModuleInitHooks(): Promise<void> {
|
|
273
|
-
const instances = this.
|
|
286
|
+
const instances = this.resolveAllComponentInstances();
|
|
274
287
|
await callOnModuleInit(instances);
|
|
275
288
|
}
|
|
276
289
|
|
|
277
290
|
/**
|
|
278
|
-
*
|
|
291
|
+
* 调用所有组件(providers + controllers)的 onApplicationBootstrap 钩子
|
|
279
292
|
*/
|
|
280
293
|
public async callBootstrapHooks(): Promise<void> {
|
|
281
|
-
const instances = this.
|
|
294
|
+
const instances = this.resolveAllComponentInstances();
|
|
282
295
|
await callOnApplicationBootstrap(instances);
|
|
283
296
|
}
|
|
284
297
|
|
|
285
298
|
/**
|
|
286
|
-
*
|
|
299
|
+
* 调用所有组件(providers + controllers)的 onModuleDestroy 钩子
|
|
287
300
|
*/
|
|
288
301
|
public async callModuleDestroyHooks(): Promise<void> {
|
|
289
|
-
const instances = this.
|
|
302
|
+
const instances = this.resolveAllComponentInstances();
|
|
303
|
+
await callOnBeforeDestroy(instances);
|
|
290
304
|
await callOnModuleDestroy(instances);
|
|
305
|
+
await callOnAfterDestroy(instances);
|
|
291
306
|
}
|
|
292
307
|
|
|
293
308
|
/**
|
|
294
|
-
*
|
|
309
|
+
* 调用所有组件(providers + controllers)的 onApplicationShutdown 钩子
|
|
295
310
|
*/
|
|
296
311
|
public async callShutdownHooks(signal?: string): Promise<void> {
|
|
297
|
-
const instances = this.
|
|
312
|
+
const instances = this.resolveAllComponentInstances();
|
|
298
313
|
await callOnApplicationShutdown(instances, signal);
|
|
299
314
|
}
|
|
300
315
|
|
|
316
|
+
/**
|
|
317
|
+
* 调用当前请求上下文下 scoped 组件的销毁钩子并清理缓存
|
|
318
|
+
*/
|
|
319
|
+
public async disposeScopedInstances(context: object): Promise<void> {
|
|
320
|
+
const containers: Container[] = [];
|
|
321
|
+
if (this.rootContainer) {
|
|
322
|
+
containers.push(this.rootContainer);
|
|
323
|
+
}
|
|
324
|
+
containers.push(...this.getAllModuleContainers());
|
|
325
|
+
|
|
326
|
+
const instances: unknown[] = [];
|
|
327
|
+
const seen = new Set<unknown>();
|
|
328
|
+
for (const container of containers) {
|
|
329
|
+
const scopedInstances = container.getScopedInstances(context);
|
|
330
|
+
for (const instance of scopedInstances) {
|
|
331
|
+
if (!seen.has(instance)) {
|
|
332
|
+
seen.add(instance);
|
|
333
|
+
instances.push(instance);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (instances.length > 0) {
|
|
339
|
+
await callOnBeforeDestroy(instances);
|
|
340
|
+
await callOnModuleDestroy(instances);
|
|
341
|
+
await callOnAfterDestroy(instances);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
for (const container of containers) {
|
|
345
|
+
container.clearScopedInstances(context);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
301
349
|
private registerExport(parentContainer: Container, moduleRef: ModuleRef, token: ProviderToken): void {
|
|
302
350
|
if (!moduleRef.container.isRegistered(token)) {
|
|
303
351
|
throw new Error(
|
package/src/index.ts
CHANGED