@hile/core 1.0.17 → 1.0.18
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 +51 -3
- package/dist/index.d.ts +61 -63
- package/dist/index.js +149 -73
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -120,7 +120,49 @@ export const connectionService = defineService(async (shutdown) => {
|
|
|
120
120
|
|
|
121
121
|
> 建议始终使用 `async` 服务函数,确保异常路径可正确触发销毁机制。
|
|
122
122
|
|
|
123
|
-
### 6)
|
|
123
|
+
### 6) 生命周期、超时与可观测事件
|
|
124
|
+
|
|
125
|
+
容器支持显式生命周期阶段:`init -> ready -> stopping -> stopped`。
|
|
126
|
+
|
|
127
|
+
可通过构造参数设置超时:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { Container } from '@hile/core'
|
|
131
|
+
|
|
132
|
+
const container = new Container({
|
|
133
|
+
startTimeoutMs: 5_000,
|
|
134
|
+
shutdownTimeoutMs: 3_000,
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
订阅事件:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const off = container.onEvent((event) => {
|
|
142
|
+
if (event.type === 'service:ready') {
|
|
143
|
+
console.log(`service#${event.id} ready in ${event.durationMs}ms`)
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// later
|
|
148
|
+
off()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 7) 依赖图与启动顺序
|
|
152
|
+
|
|
153
|
+
容器会在 `resolve` 过程中自动记录依赖关系,并检测循环依赖。
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const graph = container.getDependencyGraph()
|
|
157
|
+
// graph.nodes: number[]
|
|
158
|
+
// graph.edges: Array<{ from: number; to: number }>
|
|
159
|
+
|
|
160
|
+
const startupOrder = container.getStartupOrder()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
若出现循环依赖,会抛出 `circular dependency detected` 错误。
|
|
164
|
+
|
|
165
|
+
### 8) 手动销毁(Graceful Shutdown)
|
|
124
166
|
|
|
125
167
|
```typescript
|
|
126
168
|
import container from '@hile/core'
|
|
@@ -131,7 +173,7 @@ process.on('SIGTERM', async () => {
|
|
|
131
173
|
})
|
|
132
174
|
```
|
|
133
175
|
|
|
134
|
-
###
|
|
176
|
+
### 9) 服务校验(isService)
|
|
135
177
|
|
|
136
178
|
```typescript
|
|
137
179
|
import { defineService, isService } from '@hile/core'
|
|
@@ -172,13 +214,19 @@ const result = await container.resolve(service)
|
|
|
172
214
|
|
|
173
215
|
| 方法 | 说明 |
|
|
174
216
|
|------|------|
|
|
217
|
+
| `new Container(options?)` | 创建容器,可配置启动/销毁超时 |
|
|
175
218
|
| `register(fn)` | 注册服务(同函数引用去重) |
|
|
176
219
|
| `resolve(props)` | 加载服务(执行、等待或返回缓存) |
|
|
220
|
+
| `shutdown()` | 销毁所有服务并执行清理回调 |
|
|
221
|
+
| `onEvent(listener)` | 订阅容器事件,返回取消订阅函数 |
|
|
222
|
+
| `offEvent(listener)` | 取消订阅 |
|
|
223
|
+
| `getLifecycle(id)` | 获取服务生命周期阶段 |
|
|
224
|
+
| `getDependencyGraph()` | 获取依赖图 `{ nodes, edges }` |
|
|
225
|
+
| `getStartupOrder()` | 获取服务启动顺序(首次启动顺序) |
|
|
177
226
|
| `hasService(fn)` | 检查函数是否已注册 |
|
|
178
227
|
| `hasMeta(id)` | 检查服务是否已有运行时元数据 |
|
|
179
228
|
| `getIdByService(fn)` | 通过函数获取服务 ID |
|
|
180
229
|
| `getMetaById(id)` | 通过 ID 获取运行时元数据 |
|
|
181
|
-
| `shutdown()` | 销毁所有服务并执行清理回调 |
|
|
182
230
|
|
|
183
231
|
### 服务状态
|
|
184
232
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,102 +2,100 @@ export type ServiceCutDownFunction = () => unknown | Promise<unknown>;
|
|
|
2
2
|
export type ServiceCutDownHandler = (fn: ServiceCutDownFunction) => void;
|
|
3
3
|
export type ServiceFunction<R> = (fn: ServiceCutDownHandler) => R | Promise<R>;
|
|
4
4
|
declare const sericeFlag: unique symbol;
|
|
5
|
+
export type ServiceLifecycleStage = 'init' | 'ready' | 'stopping' | 'stopped';
|
|
5
6
|
export interface ServiceRegisterProps<R> {
|
|
6
7
|
id: number;
|
|
7
8
|
fn: ServiceFunction<R>;
|
|
8
9
|
flag: typeof sericeFlag;
|
|
9
10
|
}
|
|
11
|
+
export interface ContainerOptions {
|
|
12
|
+
startTimeoutMs?: number;
|
|
13
|
+
shutdownTimeoutMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export type ContainerEvent = {
|
|
16
|
+
type: 'service:init';
|
|
17
|
+
id: number;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'service:ready';
|
|
20
|
+
id: number;
|
|
21
|
+
durationMs: number;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'service:error';
|
|
24
|
+
id: number;
|
|
25
|
+
error: any;
|
|
26
|
+
durationMs: number;
|
|
27
|
+
} | {
|
|
28
|
+
type: 'service:shutdown:start';
|
|
29
|
+
id: number;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'service:shutdown:done';
|
|
32
|
+
id: number;
|
|
33
|
+
durationMs: number;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'service:shutdown:error';
|
|
36
|
+
id: number;
|
|
37
|
+
error: any;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'container:shutdown:start';
|
|
40
|
+
} | {
|
|
41
|
+
type: 'container:shutdown:done';
|
|
42
|
+
durationMs: number;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'container:error';
|
|
45
|
+
error: any;
|
|
46
|
+
};
|
|
10
47
|
interface Paddings<R = any> {
|
|
11
48
|
status: -1 | 0 | 1;
|
|
49
|
+
lifecycle: ServiceLifecycleStage;
|
|
12
50
|
value: R;
|
|
13
51
|
error?: any;
|
|
14
52
|
queue: Set<{
|
|
15
53
|
resolve: (value: R) => void;
|
|
16
54
|
reject: (error: any) => void;
|
|
17
55
|
}>;
|
|
56
|
+
startedAt: number;
|
|
57
|
+
endedAt?: number;
|
|
18
58
|
}
|
|
19
59
|
export declare class Container {
|
|
60
|
+
private readonly options;
|
|
20
61
|
private id;
|
|
21
62
|
private readonly packages;
|
|
22
63
|
private readonly paddings;
|
|
64
|
+
private readonly dependencies;
|
|
65
|
+
private readonly dependents;
|
|
23
66
|
private readonly shutdownFunctions;
|
|
24
67
|
private readonly shutdownQueues;
|
|
68
|
+
private readonly startupOrder;
|
|
69
|
+
private readonly listeners;
|
|
70
|
+
private readonly context;
|
|
71
|
+
constructor(options?: ContainerOptions);
|
|
72
|
+
private emit;
|
|
25
73
|
private getId;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
74
|
+
private hasPath;
|
|
75
|
+
private trackDependency;
|
|
76
|
+
onEvent(listener: (event: ContainerEvent) => void): () => boolean;
|
|
77
|
+
offEvent(listener: (event: ContainerEvent) => void): void;
|
|
78
|
+
getLifecycle(id: number): ServiceLifecycleStage | undefined;
|
|
79
|
+
getDependencyGraph(): {
|
|
80
|
+
nodes: number[];
|
|
81
|
+
edges: {
|
|
82
|
+
from: number;
|
|
83
|
+
to: number;
|
|
84
|
+
}[];
|
|
85
|
+
};
|
|
86
|
+
getStartupOrder(): number[];
|
|
31
87
|
register<R>(fn: ServiceFunction<R>): ServiceRegisterProps<R>;
|
|
32
|
-
/**
|
|
33
|
-
* 从容器中解决服务
|
|
34
|
-
* 当服务未注册时,会自动注册并运行服务
|
|
35
|
-
* 当服务已注册时,会返回服务实例
|
|
36
|
-
* 当服务运行中时,会等待服务运行完成并返回服务实例
|
|
37
|
-
* 当服务运行完成时,会返回服务实例
|
|
38
|
-
* 当服务运行失败时,会返回错误
|
|
39
|
-
* 多次调用正在运行中的服务时,不会重复运行同一服务,而是将等待状态(Promise)加入到等待队列,
|
|
40
|
-
* 直到服务运行完毕被 resolve 或者 reject
|
|
41
|
-
* @param props - 服务注册信息
|
|
42
|
-
* @returns - 服务实例
|
|
43
|
-
*/
|
|
44
88
|
resolve<R>(props: ServiceRegisterProps<R>): Promise<R>;
|
|
45
|
-
/**
|
|
46
|
-
* 运行服务
|
|
47
|
-
* 注意:运行服务过程中将自动按顺序注册销毁函数,
|
|
48
|
-
* 如果服务启动失败,则立即执行销毁函数,并返回错误
|
|
49
|
-
* 销毁函数执行都是逆向执行的
|
|
50
|
-
* 先加入的后执行,后加入的先执行
|
|
51
|
-
* @param id - 服务ID
|
|
52
|
-
* @param fn - 服务函数
|
|
53
|
-
* @param callback - 回调函数
|
|
54
|
-
*/
|
|
55
89
|
private run;
|
|
56
|
-
/**
|
|
57
|
-
* 销毁服务
|
|
58
|
-
* @param id - 服务ID
|
|
59
|
-
* @returns - 销毁结果
|
|
60
|
-
*/
|
|
61
90
|
private shutdownService;
|
|
62
|
-
/**
|
|
63
|
-
* 销毁所有服务
|
|
64
|
-
* 销毁过程都是逆向销毁的,
|
|
65
|
-
* 先注册的后销毁,后注册的先销毁
|
|
66
|
-
* @returns - 销毁结果
|
|
67
|
-
*/
|
|
68
91
|
shutdown(): Promise<void>;
|
|
69
|
-
/**
|
|
70
|
-
* 检查服务是否已注册
|
|
71
|
-
* @param fn - 服务函数
|
|
72
|
-
* @returns - 是否已注册
|
|
73
|
-
*/
|
|
74
92
|
hasService<R>(fn: ServiceFunction<R>): boolean;
|
|
75
|
-
/**
|
|
76
|
-
* 检查服务是否已运行
|
|
77
|
-
* @param id - 服务ID
|
|
78
|
-
* @returns - 是否已运行
|
|
79
|
-
*/
|
|
80
93
|
hasMeta(id: number): boolean;
|
|
81
|
-
/**
|
|
82
|
-
* 获取服务ID
|
|
83
|
-
* @param fn - 服务函数
|
|
84
|
-
* @returns - 服务ID
|
|
85
|
-
*/
|
|
86
94
|
getIdByService<R>(fn: ServiceFunction<R>): number | undefined;
|
|
87
|
-
/**
|
|
88
|
-
* 获取服务元数据
|
|
89
|
-
* @param id - 服务ID
|
|
90
|
-
* @returns - 服务元数据
|
|
91
|
-
*/
|
|
92
95
|
getMetaById(id: number): Paddings<any> | undefined;
|
|
93
96
|
}
|
|
94
97
|
export declare const container: Container;
|
|
95
98
|
export declare function defineService<R>(fn: ServiceFunction<R>): ServiceRegisterProps<R>;
|
|
96
99
|
export declare function loadService<R>(props: ServiceRegisterProps<R>): Promise<R>;
|
|
97
|
-
/**
|
|
98
|
-
* 判断是否为服务
|
|
99
|
-
* @param props - 服务注册信息
|
|
100
|
-
* @returns - 是否为服务
|
|
101
|
-
*/
|
|
102
100
|
export declare function isService<R>(props: ServiceRegisterProps<R>): boolean;
|
|
103
101
|
export default container;
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
1
2
|
const sericeFlag = Symbol('service');
|
|
3
|
+
async function withTimeout(promise, timeoutMs, message) {
|
|
4
|
+
if (!timeoutMs || timeoutMs <= 0)
|
|
5
|
+
return promise;
|
|
6
|
+
let timer;
|
|
7
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
8
|
+
timer = setTimeout(() => reject(new Error(message || `Operation timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
9
|
+
});
|
|
10
|
+
try {
|
|
11
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
if (timer)
|
|
15
|
+
clearTimeout(timer);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
2
18
|
export class Container {
|
|
19
|
+
options;
|
|
3
20
|
id = 1;
|
|
4
21
|
packages = new Map();
|
|
5
22
|
paddings = new Map();
|
|
23
|
+
dependencies = new Map();
|
|
24
|
+
dependents = new Map();
|
|
6
25
|
shutdownFunctions = new Map();
|
|
7
26
|
shutdownQueues = [];
|
|
27
|
+
startupOrder = [];
|
|
28
|
+
listeners = new Set();
|
|
29
|
+
context = new AsyncLocalStorage();
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this.options = options;
|
|
32
|
+
}
|
|
33
|
+
emit(event) {
|
|
34
|
+
for (const listener of this.listeners) {
|
|
35
|
+
try {
|
|
36
|
+
listener(event);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// ignore listener errors
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
8
43
|
getId() {
|
|
9
44
|
let i = this.id++;
|
|
10
45
|
if (i >= Number.MAX_SAFE_INTEGER) {
|
|
@@ -12,11 +47,65 @@ export class Container {
|
|
|
12
47
|
}
|
|
13
48
|
return i;
|
|
14
49
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
50
|
+
hasPath(from, to, visited = new Set()) {
|
|
51
|
+
if (from === to)
|
|
52
|
+
return true;
|
|
53
|
+
if (visited.has(from))
|
|
54
|
+
return false;
|
|
55
|
+
visited.add(from);
|
|
56
|
+
const deps = this.dependencies.get(from);
|
|
57
|
+
if (!deps)
|
|
58
|
+
return false;
|
|
59
|
+
for (const next of deps) {
|
|
60
|
+
if (this.hasPath(next, to, visited))
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
trackDependency(parentId, childId) {
|
|
66
|
+
if (parentId === childId) {
|
|
67
|
+
throw new Error(`circular dependency detected: ${parentId} -> ${childId}`);
|
|
68
|
+
}
|
|
69
|
+
if (!this.dependencies.has(parentId)) {
|
|
70
|
+
this.dependencies.set(parentId, new Set());
|
|
71
|
+
}
|
|
72
|
+
if (!this.dependents.has(childId)) {
|
|
73
|
+
this.dependents.set(childId, new Set());
|
|
74
|
+
}
|
|
75
|
+
const parentDeps = this.dependencies.get(parentId);
|
|
76
|
+
if (!parentDeps.has(childId)) {
|
|
77
|
+
if (this.hasPath(childId, parentId)) {
|
|
78
|
+
const error = new Error(`circular dependency detected: ${parentId} -> ${childId}`);
|
|
79
|
+
this.emit({ type: 'container:error', error });
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
parentDeps.add(childId);
|
|
83
|
+
this.dependents.get(childId).add(parentId);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
onEvent(listener) {
|
|
87
|
+
this.listeners.add(listener);
|
|
88
|
+
return () => this.listeners.delete(listener);
|
|
89
|
+
}
|
|
90
|
+
offEvent(listener) {
|
|
91
|
+
this.listeners.delete(listener);
|
|
92
|
+
}
|
|
93
|
+
getLifecycle(id) {
|
|
94
|
+
return this.paddings.get(id)?.lifecycle;
|
|
95
|
+
}
|
|
96
|
+
getDependencyGraph() {
|
|
97
|
+
const nodes = Array.from(this.packages.values()).sort((a, b) => a - b);
|
|
98
|
+
const edges = [];
|
|
99
|
+
for (const [id, deps] of this.dependencies.entries()) {
|
|
100
|
+
for (const dep of deps) {
|
|
101
|
+
edges.push({ from: id, to: dep });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { nodes, edges };
|
|
105
|
+
}
|
|
106
|
+
getStartupOrder() {
|
|
107
|
+
return [...this.startupOrder];
|
|
108
|
+
}
|
|
20
109
|
register(fn) {
|
|
21
110
|
if (this.packages.has(fn)) {
|
|
22
111
|
return { id: this.packages.get(fn), fn, flag: sericeFlag };
|
|
@@ -25,20 +114,13 @@ export class Container {
|
|
|
25
114
|
this.packages.set(fn, id);
|
|
26
115
|
return { id, fn, flag: sericeFlag };
|
|
27
116
|
}
|
|
28
|
-
/**
|
|
29
|
-
* 从容器中解决服务
|
|
30
|
-
* 当服务未注册时,会自动注册并运行服务
|
|
31
|
-
* 当服务已注册时,会返回服务实例
|
|
32
|
-
* 当服务运行中时,会等待服务运行完成并返回服务实例
|
|
33
|
-
* 当服务运行完成时,会返回服务实例
|
|
34
|
-
* 当服务运行失败时,会返回错误
|
|
35
|
-
* 多次调用正在运行中的服务时,不会重复运行同一服务,而是将等待状态(Promise)加入到等待队列,
|
|
36
|
-
* 直到服务运行完毕被 resolve 或者 reject
|
|
37
|
-
* @param props - 服务注册信息
|
|
38
|
-
* @returns - 服务实例
|
|
39
|
-
*/
|
|
40
117
|
resolve(props) {
|
|
41
118
|
const { id, fn } = props;
|
|
119
|
+
const stack = this.context.getStore() || [];
|
|
120
|
+
const parentId = stack.length ? stack[stack.length - 1] : undefined;
|
|
121
|
+
if (parentId !== undefined) {
|
|
122
|
+
this.trackDependency(parentId, id);
|
|
123
|
+
}
|
|
42
124
|
return new Promise((resolve, reject) => {
|
|
43
125
|
if (!this.paddings.has(id)) {
|
|
44
126
|
return this.run(id, fn, (e, v) => {
|
|
@@ -64,20 +146,20 @@ export class Container {
|
|
|
64
146
|
}
|
|
65
147
|
});
|
|
66
148
|
}
|
|
67
|
-
/**
|
|
68
|
-
* 运行服务
|
|
69
|
-
* 注意:运行服务过程中将自动按顺序注册销毁函数,
|
|
70
|
-
* 如果服务启动失败,则立即执行销毁函数,并返回错误
|
|
71
|
-
* 销毁函数执行都是逆向执行的
|
|
72
|
-
* 先加入的后执行,后加入的先执行
|
|
73
|
-
* @param id - 服务ID
|
|
74
|
-
* @param fn - 服务函数
|
|
75
|
-
* @param callback - 回调函数
|
|
76
|
-
*/
|
|
77
149
|
run(id, fn, callback) {
|
|
78
|
-
const state = {
|
|
150
|
+
const state = {
|
|
151
|
+
status: 0,
|
|
152
|
+
lifecycle: 'init',
|
|
153
|
+
value: undefined,
|
|
154
|
+
queue: new Set(),
|
|
155
|
+
startedAt: Date.now(),
|
|
156
|
+
};
|
|
79
157
|
this.paddings.set(id, state);
|
|
80
|
-
|
|
158
|
+
if (!this.startupOrder.includes(id)) {
|
|
159
|
+
this.startupOrder.push(id);
|
|
160
|
+
}
|
|
161
|
+
this.emit({ type: 'service:init', id });
|
|
162
|
+
const curDown = (cutDownFn) => {
|
|
81
163
|
if (!this.shutdownQueues.includes(id)) {
|
|
82
164
|
this.shutdownQueues.push(id);
|
|
83
165
|
}
|
|
@@ -85,13 +167,19 @@ export class Container {
|
|
|
85
167
|
this.shutdownFunctions.set(id, []);
|
|
86
168
|
}
|
|
87
169
|
const pools = this.shutdownFunctions.get(id);
|
|
88
|
-
if (!pools.includes(
|
|
89
|
-
pools.push(
|
|
170
|
+
if (!pools.includes(cutDownFn)) {
|
|
171
|
+
pools.push(cutDownFn);
|
|
90
172
|
}
|
|
91
173
|
};
|
|
92
|
-
|
|
174
|
+
const parentStack = this.context.getStore() || [];
|
|
175
|
+
const startupPromise = this.context.run([...parentStack, id], () => Promise.resolve(fn(curDown)));
|
|
176
|
+
withTimeout(startupPromise, this.options.startTimeoutMs, `service startup timeout: ${id} exceeded ${this.options.startTimeoutMs}ms`).then((value) => {
|
|
93
177
|
state.status = 1;
|
|
178
|
+
state.lifecycle = 'ready';
|
|
94
179
|
state.value = value;
|
|
180
|
+
state.endedAt = Date.now();
|
|
181
|
+
const durationMs = state.endedAt - state.startedAt;
|
|
182
|
+
this.emit({ type: 'service:ready', id, durationMs });
|
|
95
183
|
for (const queue of state.queue) {
|
|
96
184
|
queue.resolve(value);
|
|
97
185
|
}
|
|
@@ -99,81 +187,74 @@ export class Container {
|
|
|
99
187
|
callback(null, value);
|
|
100
188
|
}).catch(e => {
|
|
101
189
|
state.status = -1;
|
|
190
|
+
state.lifecycle = 'stopping';
|
|
102
191
|
state.error = e;
|
|
103
|
-
|
|
192
|
+
state.endedAt = Date.now();
|
|
193
|
+
const durationMs = state.endedAt - state.startedAt;
|
|
194
|
+
this.emit({ type: 'service:error', id, error: e, durationMs });
|
|
104
195
|
const clear = () => {
|
|
196
|
+
state.lifecycle = 'stopped';
|
|
105
197
|
for (const queue of state.queue) {
|
|
106
198
|
queue.reject(e);
|
|
107
199
|
}
|
|
108
200
|
state.queue.clear();
|
|
109
201
|
callback(e);
|
|
110
202
|
};
|
|
111
|
-
// 已运行的销毁函数立即执行,
|
|
112
|
-
// 无论成功失败都通知所有等待的任务结果是失败的,并清空等待队列
|
|
113
203
|
this.shutdownService(id)
|
|
114
204
|
.then(clear)
|
|
115
|
-
.catch(
|
|
205
|
+
.catch((shutdownError) => {
|
|
206
|
+
this.emit({ type: 'service:shutdown:error', id, error: shutdownError });
|
|
207
|
+
clear();
|
|
208
|
+
});
|
|
116
209
|
});
|
|
117
210
|
}
|
|
118
|
-
/**
|
|
119
|
-
* 销毁服务
|
|
120
|
-
* @param id - 服务ID
|
|
121
|
-
* @returns - 销毁结果
|
|
122
|
-
*/
|
|
123
211
|
async shutdownService(id) {
|
|
124
212
|
if (this.shutdownQueues.includes(id)) {
|
|
213
|
+
const meta = this.paddings.get(id);
|
|
214
|
+
if (meta) {
|
|
215
|
+
meta.lifecycle = 'stopping';
|
|
216
|
+
}
|
|
217
|
+
this.emit({ type: 'service:shutdown:start', id });
|
|
218
|
+
const startedAt = Date.now();
|
|
125
219
|
const pools = this.shutdownFunctions.get(id);
|
|
126
220
|
let i = pools.length;
|
|
127
221
|
while (i--) {
|
|
128
|
-
|
|
222
|
+
const teardown = pools[i];
|
|
223
|
+
try {
|
|
224
|
+
await withTimeout(Promise.resolve(teardown()), this.options.shutdownTimeoutMs, `service shutdown timeout: ${id} exceeded ${this.options.shutdownTimeoutMs}ms`);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
this.emit({ type: 'service:shutdown:error', id, error });
|
|
228
|
+
}
|
|
129
229
|
}
|
|
130
230
|
this.shutdownFunctions.delete(id);
|
|
131
231
|
this.shutdownQueues.splice(this.shutdownQueues.indexOf(id), 1);
|
|
232
|
+
if (meta) {
|
|
233
|
+
meta.lifecycle = 'stopped';
|
|
234
|
+
}
|
|
235
|
+
this.emit({ type: 'service:shutdown:done', id, durationMs: Date.now() - startedAt });
|
|
132
236
|
}
|
|
133
237
|
}
|
|
134
|
-
/**
|
|
135
|
-
* 销毁所有服务
|
|
136
|
-
* 销毁过程都是逆向销毁的,
|
|
137
|
-
* 先注册的后销毁,后注册的先销毁
|
|
138
|
-
* @returns - 销毁结果
|
|
139
|
-
*/
|
|
140
238
|
async shutdown() {
|
|
239
|
+
const startedAt = Date.now();
|
|
240
|
+
this.emit({ type: 'container:shutdown:start' });
|
|
141
241
|
let i = this.shutdownQueues.length;
|
|
142
242
|
while (i--) {
|
|
143
243
|
await this.shutdownService(this.shutdownQueues[i]);
|
|
144
244
|
}
|
|
145
245
|
this.shutdownFunctions.clear();
|
|
146
246
|
this.shutdownQueues.length = 0;
|
|
247
|
+
this.emit({ type: 'container:shutdown:done', durationMs: Date.now() - startedAt });
|
|
147
248
|
}
|
|
148
|
-
/**
|
|
149
|
-
* 检查服务是否已注册
|
|
150
|
-
* @param fn - 服务函数
|
|
151
|
-
* @returns - 是否已注册
|
|
152
|
-
*/
|
|
153
249
|
hasService(fn) {
|
|
154
250
|
return this.packages.has(fn);
|
|
155
251
|
}
|
|
156
|
-
/**
|
|
157
|
-
* 检查服务是否已运行
|
|
158
|
-
* @param id - 服务ID
|
|
159
|
-
* @returns - 是否已运行
|
|
160
|
-
*/
|
|
161
252
|
hasMeta(id) {
|
|
162
253
|
return this.paddings.has(id);
|
|
163
254
|
}
|
|
164
|
-
/**
|
|
165
|
-
* 获取服务ID
|
|
166
|
-
* @param fn - 服务函数
|
|
167
|
-
* @returns - 服务ID
|
|
168
|
-
*/
|
|
169
255
|
getIdByService(fn) {
|
|
170
256
|
return this.packages.get(fn);
|
|
171
257
|
}
|
|
172
|
-
/**
|
|
173
|
-
* 获取服务元数据
|
|
174
|
-
* @param id - 服务ID
|
|
175
|
-
* @returns - 服务元数据
|
|
176
|
-
*/
|
|
177
258
|
getMetaById(id) {
|
|
178
259
|
return this.paddings.get(id);
|
|
179
260
|
}
|
|
@@ -185,11 +266,6 @@ export function defineService(fn) {
|
|
|
185
266
|
export function loadService(props) {
|
|
186
267
|
return container.resolve(props);
|
|
187
268
|
}
|
|
188
|
-
/**
|
|
189
|
-
* 判断是否为服务
|
|
190
|
-
* @param props - 服务注册信息
|
|
191
|
-
* @returns - 是否为服务
|
|
192
|
-
*/
|
|
193
269
|
export function isService(props) {
|
|
194
270
|
return props.flag === sericeFlag && typeof props.id === 'number' && typeof props.fn === 'function';
|
|
195
271
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "Hile core - lightweight async service container",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,5 +23,5 @@
|
|
|
23
23
|
"@types/node": "^25.3.1",
|
|
24
24
|
"vitest": "^4.0.18"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "81347b9de460b693ed82af46c0f4a287d4527323"
|
|
27
27
|
}
|