@hile/core 1.0.17 → 1.0.19

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 CHANGED
@@ -120,7 +120,49 @@ export const connectionService = defineService(async (shutdown) => {
120
120
 
121
121
  > 建议始终使用 `async` 服务函数,确保异常路径可正确触发销毁机制。
122
122
 
123
- ### 6) 手动销毁(Graceful Shutdown)
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.on((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
- ### 7) 服务校验(isService)
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
+ | `on(listener)` | 订阅容器事件,返回取消订阅函数 |
222
+ | `off(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/SKILL.md CHANGED
@@ -1,180 +1,112 @@
1
1
  ---
2
2
  name: hile-core
3
- description: @hile/core 的代码生成与使用规范。适用于定义/加载 Hile 服务、生命周期 shutdown 编排、或涉及 defineService、loadService、Container 等话题。
3
+ description: "@hile/core 的代码生成与使用规范。适用于定义/加载 Hile 服务、生命周期编排、依赖图与容器事件相关场景。"
4
4
  ---
5
5
 
6
6
  # @hile/core SKILL
7
7
 
8
- 本文档用于约束 AI 与开发者在使用 `@hile/core` 时的代码生成方式,目标是保证服务定义、依赖加载与资源销毁行为一致且可维护。
8
+ 本文档面向代码生成器与维护者,目标是确保生成代码严格符合容器语义。
9
9
 
10
- ## 1. 架构概览
10
+ ## 1. 强约束(必须遵守)
11
11
 
12
- Hile `Container` 为核心,服务遵循“定义 → 加载”两阶段:
12
+ 1. 服务必须使用 `async (shutdown)` 形态定义。
13
+ 2. 只能通过 `defineService` / `container.register` 产出服务对象。
14
+ 3. 只能通过 `loadService` / `container.resolve` 获取服务实例。
15
+ 4. 外部资源创建后必须立即注册 `shutdown`。
16
+ 5. 禁止在模块顶层缓存 `await loadService(...)` 结果。
17
+ 6. 依赖服务必须在服务函数内部加载。
18
+ 7. 多个 teardown 默认按 LIFO 顺序执行。
13
19
 
14
- - 单例:同一服务函数仅初始化一次
15
- - 并发合并:并发加载同一服务时共享同一初始化过程
16
- - 失败回收:初始化失败时自动执行已注册的清理回调
20
+ ## 2. 生命周期与超时约束
17
21
 
18
- 模块默认导出全局容器,并提供 `defineService` / `loadService` 便捷函数。
22
+ 容器生命周期:`init -> ready -> stopping -> stopped`。
19
23
 
20
- ## 2. 关键类型
24
+ - 启动超时:`new Container({ startTimeoutMs })`
25
+ - 销毁超时:`new Container({ shutdownTimeoutMs })`
21
26
 
22
- ```typescript
23
- type ServiceCutDownFunction = () => unknown | Promise<unknown>;
24
- type ServiceCutDownHandler = (fn: ServiceCutDownFunction) => void;
25
- type ServiceFunction<R> = (shutdown: ServiceCutDownHandler) => R | Promise<R>;
26
-
27
- const sericeFlag = Symbol('service');
27
+ 生成代码时:
28
28
 
29
- interface ServiceRegisterProps<R> {
30
- id: number;
31
- fn: ServiceFunction<R>;
32
- flag: typeof sericeFlag;
33
- }
34
- ```
29
+ - 不要吞掉启动超时错误。
30
+ - 不要假设 teardown 一定成功;应允许 `service:shutdown:error` 事件出现。
35
31
 
36
- ## 3. 标准模板
32
+ ## 3. 可观测事件约束
37
33
 
38
- ### 3.1 定义服务
34
+ 允许订阅:`container.on(listener)`。
39
35
 
40
- ```typescript
41
- import { defineService } from '@hile/core'
36
+ 关键事件:
42
37
 
43
- export const xxxService = defineService(async (shutdown) => {
44
- const resource = await createResource()
45
- shutdown(() => resource.close())
46
- return resource
47
- })
48
- ```
38
+ - `service:init`
39
+ - `service:ready`
40
+ - `service:error`
41
+ - `service:shutdown:start`
42
+ - `service:shutdown:done`
43
+ - `service:shutdown:error`
44
+ - `container:shutdown:start`
45
+ - `container:shutdown:done`
46
+ - `container:error`
49
47
 
50
48
  规则:
51
49
 
52
- - 服务函数第一个参数固定为 `shutdown`
53
- - 推荐必须使用 `async`
54
- - `defineService` 结果需使用模块级 `export const` 暴露
55
- - 命名建议以 `Service` 结尾
50
+ - 订阅后必须在生命周期结束时取消订阅。
51
+ - 记录错误时保留原始 error 对象。
56
52
 
57
- ### 3.2 加载服务
53
+ ## 4. 依赖图与循环依赖
58
54
 
59
- ```typescript
60
- import { loadService } from '@hile/core'
61
- import { databaseService } from './services/database'
55
+ 容器会自动记录服务依赖并检测循环依赖:
62
56
 
63
- const db = await loadService(databaseService)
64
- ```
57
+ - `getDependencyGraph()`
58
+ - `getStartupOrder()`
65
59
 
66
60
  规则:
67
61
 
68
- - 始终通过 `loadService` 获取实例
69
- - `loadService` 返回 Promise,必须 `await`
62
+ - 不要绕开容器手动构建“隐式全局单例依赖”。
63
+ - 出现 `circular dependency detected` 时应通过拆分服务职责或引入中间层服务解决。
70
64
 
71
- ### 3.3 服务依赖服务
65
+ ## 5. 反模式(禁止)
72
66
 
73
- ```typescript
74
- import { defineService, loadService } from '@hile/core'
75
- import { databaseService } from './database'
76
-
77
- export const userService = defineService(async (shutdown) => {
78
- const db = await loadService(databaseService)
79
- return new UserRepository(db)
80
- })
81
- ```
82
-
83
- 规则:
84
-
85
- - 在服务函数内部加载依赖
86
- - 不在模块顶层缓存 `loadService` 结果
87
-
88
- ### 3.4 注册清理回调
67
+ ### 5.1 顶层缓存实例
89
68
 
90
69
  ```typescript
91
- export const connectionService = defineService(async (shutdown) => {
92
- const a = await connectA()
93
- shutdown(() => a.close())
94
-
95
- const b = await connectB()
96
- shutdown(() => b.close())
70
+ //
71
+ const db = await loadService(dbService)
97
72
 
98
- return { a, b }
99
- })
73
+ //
74
+ export async function query(sql: string) {
75
+ const db = await loadService(dbService)
76
+ return db.query(sql)
77
+ }
100
78
  ```
101
79
 
102
- 规则:
103
-
104
- - 资源创建后立即注册清理
105
- - 回调按 LIFO 执行
106
- - 支持异步回调
107
-
108
- ### 3.5 全局优雅关闭
80
+ ### 5.2 手动伪造服务对象
109
81
 
110
82
  ```typescript
111
- import container from '@hile/core'
83
+ //
84
+ const fake = { id: 1, fn: async () => 1 }
112
85
 
113
- process.on('SIGTERM', async () => {
114
- await container.shutdown()
115
- process.exit(0)
116
- })
86
+ //
87
+ const real = defineService(async () => 1)
117
88
  ```
118
89
 
119
- ## 4. 强制规则
120
-
121
- 1. 服务函数必须使用 `async`,避免同步 `throw` 破坏销毁机制。
122
- 2. 不要手动构造 `ServiceRegisterProps`。
123
- 3. 不要在工厂函数里动态调用 `defineService` 生成新引用。
124
- 4. 不要在模块顶层 `await loadService(...)`。
125
- 5. 每个外部资源都应对应一次 `shutdown` 注册。
126
-
127
- ## 5. 常见反模式
128
-
129
- ### 同步 throw
90
+ ### 5.3 不注册资源清理
130
91
 
131
92
  ```typescript
132
93
  // ✗
133
- export const bad = defineService((shutdown) => {
134
- const r = createSync()
135
- shutdown(() => r.close())
136
- throw new Error('boom')
94
+ export const bad = defineService(async () => {
95
+ return await createPool()
137
96
  })
138
97
 
139
98
  // ✓
140
99
  export const good = defineService(async (shutdown) => {
141
- const r = await createAsync()
142
- shutdown(() => r.close())
143
- throw new Error('boom')
100
+ const pool = await createPool()
101
+ shutdown(() => pool.end())
102
+ return pool
144
103
  })
145
104
  ```
146
105
 
147
- ### 顶层缓存服务实例
148
-
149
- ```typescript
150
- // ✗
151
- const db = await loadService(databaseService)
152
-
153
- // ✓
154
- export async function query(sql: string) {
155
- const db = await loadService(databaseService)
156
- return db.query(sql)
157
- }
158
- ```
159
-
160
- ## 6. API 速查
161
-
162
- ### 便捷函数
163
-
164
- | 函数 | 说明 |
165
- |---|---|
166
- | `defineService(fn)` | 注册服务到默认容器 |
167
- | `loadService(props)` | 加载服务实例 |
168
- | `isService(props)` | 判断是否为合法服务注册对象 |
169
-
170
- ### Container
106
+ ## 6. 边界条件清单
171
107
 
172
- | 方法 | 说明 |
173
- |---|---|
174
- | `register(fn)` | 注册服务 |
175
- | `resolve(props)` | 解析服务 |
176
- | `hasService(fn)` | 检查函数是否已注册 |
177
- | `hasMeta(id)` | 检查运行时元数据 |
178
- | `getIdByService(fn)` | 通过函数获取 ID |
179
- | `getMetaById(id)` | 通过 ID 获取元数据 |
180
- | `shutdown()` | 销毁所有服务 |
108
+ - [ ] 服务同步抛错路径是否可观测
109
+ - [ ] 异步 reject 路径是否会触发 teardown
110
+ - [ ] teardown 抛错是否不覆盖原始业务错误
111
+ - [ ] 并发 resolve 同一服务是否只初始化一次
112
+ - [ ] shutdown 重复调用是否幂等
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
- * @param fn - 服务函数
29
- * @returns - 服务注册信息
30
- */
74
+ private hasPath;
75
+ private trackDependency;
76
+ on(listener: (event: ContainerEvent) => void): () => boolean;
77
+ off(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
- * @param fn - 服务函数
18
- * @returns - 服务注册信息
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
+ on(listener) {
87
+ this.listeners.add(listener);
88
+ return () => this.listeners.delete(listener);
89
+ }
90
+ off(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 = { status: 0, value: undefined, queue: new Set() };
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
- const curDown = (fn) => {
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(fn)) {
89
- pools.push(fn);
170
+ if (!pools.includes(cutDownFn)) {
171
+ pools.push(cutDownFn);
90
172
  }
91
173
  };
92
- Promise.resolve(fn(curDown)).then((value) => {
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(clear);
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
- await Promise.resolve(pools[i]());
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.17",
3
+ "version": "1.0.19",
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": "6672fc4cdc551c4265912cc85ee2e96fc44bc4c9"
26
+ "gitHead": "4bb9d38b309e72c720f2cba579ce5c498d27e044"
27
27
  }