@hile/core 1.0.16 → 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 CHANGED
@@ -1,6 +1,6 @@
1
- # Hile
1
+ # @hile/core
2
2
 
3
- 轻量级异步服务容器,提供单例管理、并发请求合并和资源生命周期销毁。纯 TypeScript 实现,零运行时依赖。
3
+ 轻量级异步服务容器,提供单例管理、并发请求合并与生命周期销毁能力。纯 TypeScript 实现,零运行时依赖。
4
4
 
5
5
  ## 安装
6
6
 
@@ -13,7 +13,6 @@ pnpm add @hile/core
13
13
  ```typescript
14
14
  import { defineService, loadService } from '@hile/core'
15
15
 
16
- // 定义服务
17
16
  const greeterService = defineService(async (shutdown) => {
18
17
  return {
19
18
  hello(name: string) {
@@ -22,16 +21,15 @@ const greeterService = defineService(async (shutdown) => {
22
21
  }
23
22
  })
24
23
 
25
- // 加载并使用
26
24
  const greeter = await loadService(greeterService)
27
- greeter.hello('World') // "Hello, World!"
25
+ greeter.hello('World') // Hello, World!
28
26
  ```
29
27
 
30
28
  ## 核心概念
31
29
 
32
- ### 定义服务
30
+ ### 1) 定义服务
33
31
 
34
- 使用 `defineService` 注册一个服务。服务函数接收一个 `shutdown` 参数,用于注册资源清理回调。
32
+ 通过 `defineService` 注册服务。服务函数接收 `shutdown` 注册器,用于登记资源清理回调。
35
33
 
36
34
  ```typescript
37
35
  import { defineService } from '@hile/core'
@@ -43,9 +41,9 @@ export const databaseService = defineService(async (shutdown) => {
43
41
  })
44
42
  ```
45
43
 
46
- ### 加载服务
44
+ ### 2) 加载服务
47
45
 
48
- 使用 `loadService` 获取服务实例。容器保证服务函数只执行一次,后续调用直接返回缓存结果。
46
+ 通过 `loadService` 获取服务实例。容器保证同一服务函数只执行一次。
49
47
 
50
48
  ```typescript
51
49
  import { loadService } from '@hile/core'
@@ -55,23 +53,23 @@ const db = await loadService(databaseService)
55
53
  const users = await db.query('SELECT * FROM users')
56
54
  ```
57
55
 
58
- ### 并发请求合并
56
+ ### 3) 并发请求合并
59
57
 
60
- 多个地方同时请求同一服务时,服务函数不会重复执行,所有调用者共享同一结果。
58
+ 并发加载同一服务时,初始化只执行一次,调用方共享结果。
61
59
 
62
60
  ```typescript
63
- // 三个并发调用,服务函数只执行一次
64
61
  const [r1, r2, r3] = await Promise.all([
65
62
  loadService(heavyService),
66
63
  loadService(heavyService),
67
64
  loadService(heavyService),
68
65
  ])
66
+
69
67
  // r1 === r2 === r3
70
68
  ```
71
69
 
72
- ### 服务间依赖
70
+ ### 4) 服务间依赖
73
71
 
74
- 在服务函数内部通过 `loadService` 加载所依赖的其他服务:
72
+ 服务内部可通过 `loadService` 继续加载依赖服务。
75
73
 
76
74
  ```typescript
77
75
  import { defineService, loadService } from '@hile/core'
@@ -94,37 +92,77 @@ export const userService = defineService(async (shutdown) => {
94
92
  })
95
93
  ```
96
94
 
97
- ### 资源销毁(Shutdown)
95
+ ### 5) 资源销毁(Shutdown)
98
96
 
99
- 服务函数的第一个参数 `shutdown` 是一个注册器,用于在初始化过程中注册清理回调。当服务启动失败时,容器会自动执行已注册的清理回调。
97
+ 当服务初始化失败或手动执行全局关闭时,容器会按规则执行已注册的清理回调。
100
98
 
101
99
  ```typescript
102
100
  export const connectionService = defineService(async (shutdown) => {
103
101
  const primary = await connectPrimary()
104
- shutdown(() => primary.disconnect()) // 最后执行
102
+ shutdown(() => primary.disconnect())
105
103
 
106
104
  const replica = await connectReplica()
107
- shutdown(() => replica.disconnect()) // 第 2 个执行
105
+ shutdown(() => replica.disconnect())
108
106
 
109
107
  const cache = await initCache()
110
- shutdown(() => cache.flush()) // 最先执行
108
+ shutdown(() => cache.flush())
111
109
 
112
110
  return { primary, replica, cache }
113
111
  })
114
112
  ```
115
113
 
116
- **关键特性:**
114
+ 特性:
117
115
 
118
- - 销毁回调按 **逆序(LIFO)** 执行——后注册的先执行
116
+ - 清理回调按逆序(LIFO)执行
119
117
  - 支持异步清理函数
120
- - 同一函数引用多次注册只会执行一次
121
- - 清理函数自身的错误不会影响原始错误的传播
118
+ - 同一函数引用重复注册只执行一次
119
+ - 清理函数错误不会覆盖原始业务错误
120
+
121
+ > 建议始终使用 `async` 服务函数,确保异常路径可正确触发销毁机制。
122
122
 
123
- > **注意:** 请使用 `async` 函数定义服务。同步函数中直接 `throw` 的错误无法触发销毁机制。
123
+ ### 6) 生命周期、超时与可观测事件
124
124
 
125
- ### 手动销毁(Graceful Shutdown)
125
+ 容器支持显式生命周期阶段:`init -> ready -> stopping -> stopped`。
126
126
 
127
- 调用 `container.shutdown()` 可手动触发所有已注册服务的销毁回调,适用于进程退出时优雅关闭资源:
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)
128
166
 
129
167
  ```typescript
130
168
  import container from '@hile/core'
@@ -135,24 +173,20 @@ process.on('SIGTERM', async () => {
135
173
  })
136
174
  ```
137
175
 
138
- 销毁按 **服务注册逆序** 执行:后注册的服务先销毁。调用后销毁队列清空,重复调用不会再次执行。
139
-
140
- ### 服务校验(isService)
141
-
142
- 使用 `isService` 判断一个对象是否为合法的服务注册信息。内部通过不可伪造的 Symbol 标识校验,确保只有通过 `defineService` / `container.register` 创建的对象才会返回 `true`:
176
+ ### 9) 服务校验(isService)
143
177
 
144
178
  ```typescript
145
179
  import { defineService, isService } from '@hile/core'
146
180
 
147
181
  const myService = defineService(async (shutdown) => 'hello')
148
182
 
149
- isService(myService) // true
150
- isService({ id: 1, fn: () => {} } as any) // false
183
+ isService(myService) // true
184
+ isService({ id: 1, fn: () => {} } as any) // false
151
185
  ```
152
186
 
153
187
  ## 隔离容器
154
188
 
155
- 除默认容器外,可以创建独立的 `Container` 实例,实现服务作用域隔离:
189
+ 除了默认容器,也可以手动创建独立容器以实现作用域隔离。
156
190
 
157
191
  ```typescript
158
192
  import { Container } from '@hile/core'
@@ -168,31 +202,37 @@ const result = await container.resolve(service)
168
202
 
169
203
  ## API
170
204
 
171
- ### 便捷函数
205
+ ### 顶层函数
172
206
 
173
207
  | 函数 | 说明 |
174
208
  |------|------|
175
- | `defineService(fn)` | 注册服务到默认容器,返回 `ServiceRegisterProps` |
176
- | `loadService(props)` | 从默认容器加载服务,返回 `Promise<R>` |
177
- | `isService(props)` | 判断对象是否为合法的服务注册信息(通过内部 Symbol 校验) |
209
+ | `defineService(fn)` | 注册服务到默认容器 |
210
+ | `loadService(props)` | 从默认容器加载服务 |
211
+ | `isService(props)` | 判断对象是否为合法服务注册信息 |
178
212
 
179
- ### Container
213
+ ### `Container`
180
214
 
181
215
  | 方法 | 说明 |
182
216
  |------|------|
183
- | `register(fn)` | 注册服务。同一函数引用只注册一次,返回 `ServiceRegisterProps` |
184
- | `resolve(props)` | 加载服务。根据当前状态决定执行、等待或返回缓存 |
185
- | `hasService(fn)` | 检查服务函数是否已注册 |
186
- | `hasMeta(id)` | 检查服务是否已运行(存在运行时元数据) |
187
- | `getIdByService(fn)` | 根据函数引用获取服务 ID |
188
- | `getMetaById(id)` | 根据服务 ID 获取运行时元数据 |
189
- | `shutdown()` | 手动销毁所有服务,返回 `Promise<void>`。按服务注册逆序执行所有销毁回调 |
217
+ | `new Container(options?)` | 创建容器,可配置启动/销毁超时 |
218
+ | `register(fn)` | 注册服务(同函数引用去重) |
219
+ | `resolve(props)` | 加载服务(执行、等待或返回缓存) |
220
+ | `shutdown()` | 销毁所有服务并执行清理回调 |
221
+ | `onEvent(listener)` | 订阅容器事件,返回取消订阅函数 |
222
+ | `offEvent(listener)` | 取消订阅 |
223
+ | `getLifecycle(id)` | 获取服务生命周期阶段 |
224
+ | `getDependencyGraph()` | 获取依赖图 `{ nodes, edges }` |
225
+ | `getStartupOrder()` | 获取服务启动顺序(首次启动顺序) |
226
+ | `hasService(fn)` | 检查函数是否已注册 |
227
+ | `hasMeta(id)` | 检查服务是否已有运行时元数据 |
228
+ | `getIdByService(fn)` | 通过函数获取服务 ID |
229
+ | `getMetaById(id)` | 通过 ID 获取运行时元数据 |
190
230
 
191
231
  ### 服务状态
192
232
 
193
- | 状态 | 值 | `resolve` 的行为 |
194
- |------|---|-----------------|
195
- | 从未运行 | — | 执行服务函数 |
233
+ | 状态 | 值 | `resolve` 行为 |
234
+ |------|---|---------------|
235
+ | 未运行 | — | 执行服务函数 |
196
236
  | 运行中 | `0` | 加入等待队列 |
197
237
  | 已成功 | `1` | 返回缓存值 |
198
238
  | 已失败 | `-1` | 返回缓存错误 |
@@ -201,9 +241,9 @@ const result = await container.resolve(service)
201
241
 
202
242
  ```bash
203
243
  pnpm install
204
- pnpm build # 编译
205
- pnpm dev # 监听模式
206
- pnpm test # 运行测试
244
+ pnpm build
245
+ pnpm dev
246
+ pnpm test
207
247
  ```
208
248
 
209
249
  ## License
package/SKILL.md CHANGED
@@ -1,44 +1,31 @@
1
1
  ---
2
2
  name: hile-core
3
- description: Code generation and usage rules for @hile/core async service container. Use when defining or loading Hile services, wiring lifecycle shutdown, or when the user asks about @hile/core, defineService, loadService, or service container patterns.
3
+ description: @hile/core 的代码生成与使用规范。适用于定义/加载 Hile 服务、生命周期 shutdown 编排、或涉及 defineServiceloadService、Container 等话题。
4
4
  ---
5
5
 
6
- # Hile
6
+ # @hile/core SKILL
7
7
 
8
- Hile 是一个轻量级异步服务容器。本文档是面向 AI 编码模型和人类开发者的 **代码生成规范**,阅读后应能正确地使用本库编写符合架构规则的代码。
8
+ 本文档用于约束 AI 与开发者在使用 `@hile/core` 时的代码生成方式,目标是保证服务定义、依赖加载与资源销毁行为一致且可维护。
9
9
 
10
- ---
11
-
12
- ## 1. 架构总览
13
-
14
- Hile 的核心是 `Container`(服务容器)。所有服务都必须经过 **定义 → 加载** 两个步骤才能使用。容器保证:
15
-
16
- - 每个服务函数只执行一次(**单例**)
17
- - 并发请求同一服务时自动合并(**不重复执行**)
18
- - 服务启动失败时自动执行已注册的清理回调(**销毁机制**)
10
+ ## 1. 架构概览
19
11
 
20
- 模块默认导出了一个全局容器实例,并提供 `defineService` / `loadService` 两个便捷函数。
12
+ Hile `Container` 为核心,服务遵循“定义 加载”两阶段:
21
13
 
22
- ---
14
+ - 单例:同一服务函数仅初始化一次
15
+ - 并发合并:并发加载同一服务时共享同一初始化过程
16
+ - 失败回收:初始化失败时自动执行已注册的清理回调
23
17
 
24
- ## 2. 类型签名
18
+ 模块默认导出全局容器,并提供 `defineService` / `loadService` 便捷函数。
25
19
 
26
- 生成代码时,必须严格遵循以下类型:
20
+ ## 2. 关键类型
27
21
 
28
22
  ```typescript
29
- // 销毁回调:无参数,可返回 Promise
30
23
  type ServiceCutDownFunction = () => unknown | Promise<unknown>;
31
-
32
- // 销毁注册器:在服务函数内部调用,将清理回调注册到容器
33
24
  type ServiceCutDownHandler = (fn: ServiceCutDownFunction) => void;
34
-
35
- // 服务函数:第一个参数固定为销毁注册器,返回值为同步值或 Promise
36
25
  type ServiceFunction<R> = (shutdown: ServiceCutDownHandler) => R | Promise<R>;
37
26
 
38
- // 内部服务标识(Symbol,不可外部构造)
39
27
  const sericeFlag = Symbol('service');
40
28
 
41
- // 服务注册信息:由 defineService/register 返回,作为 loadService/resolve 的入参
42
29
  interface ServiceRegisterProps<R> {
43
30
  id: number;
44
31
  fn: ServiceFunction<R>;
@@ -46,39 +33,28 @@ interface ServiceRegisterProps<R> {
46
33
  }
47
34
  ```
48
35
 
49
- ---
36
+ ## 3. 标准模板
50
37
 
51
- ## 3. 代码生成模板
52
-
53
- ### 3.1 定义服务(必须遵循的模式)
54
-
55
- **模板:**
38
+ ### 3.1 定义服务
56
39
 
57
40
  ```typescript
58
41
  import { defineService } from '@hile/core'
59
42
 
60
43
  export const xxxService = defineService(async (shutdown) => {
61
- // 1. 初始化资源
62
44
  const resource = await createResource()
63
-
64
- // 2. 注册销毁回调(每创建一个资源就注册一个对应的清理)
65
45
  shutdown(() => resource.close())
66
-
67
- // 3. 返回服务实例
68
46
  return resource
69
47
  })
70
48
  ```
71
49
 
72
- **规则:**
73
- - 服务函数的第一个参数 **必须** 命名为 `shutdown`,类型为 `ServiceCutDownHandler`
74
- - 服务函数 **应当** 使用 `async` 声明(确保销毁机制在失败时正确触发)
75
- - `defineService` 的返回值 **必须** 赋值给一个模块级常量并 `export`
76
- - 常量命名 **必须** 以 `Service` 结尾(如 `databaseService`、`cacheService`)
77
- - 每个服务定义 **应当** 放在独立的文件中
50
+ 规则:
78
51
 
79
- ### 3.2 加载服务
52
+ - 服务函数第一个参数固定为 `shutdown`
53
+ - 推荐必须使用 `async`
54
+ - `defineService` 结果需使用模块级 `export const` 暴露
55
+ - 命名建议以 `Service` 结尾
80
56
 
81
- **模板:**
57
+ ### 3.2 加载服务
82
58
 
83
59
  ```typescript
84
60
  import { loadService } from '@hile/core'
@@ -87,68 +63,49 @@ import { databaseService } from './services/database'
87
63
  const db = await loadService(databaseService)
88
64
  ```
89
65
 
90
- **规则:**
91
- - **永远** 使用 `loadService()` 获取服务实例,**不要** 直接调用服务函数
92
- - `loadService` 返回 `Promise`,**必须** `await`
93
- - 可以在任何地方多次调用 `loadService(同一个服务)`,容器保证只执行一次
66
+ 规则:
94
67
 
95
- ### 3.3 服务间依赖
68
+ - 始终通过 `loadService` 获取实例
69
+ - `loadService` 返回 Promise,必须 `await`
96
70
 
97
- 当一个服务依赖另一个服务时,在服务函数内部通过 `loadService` 加载依赖:
71
+ ### 3.3 服务依赖服务
98
72
 
99
73
  ```typescript
100
74
  import { defineService, loadService } from '@hile/core'
101
75
  import { databaseService } from './database'
102
- import { configService } from './config'
103
76
 
104
77
  export const userService = defineService(async (shutdown) => {
105
- // 加载依赖的服务(若已完成则直接返回缓存,否则等待)
106
- const config = await loadService(configService)
107
78
  const db = await loadService(databaseService)
108
-
109
- const repo = new UserRepository(db, config)
110
- shutdown(() => repo.dispose())
111
-
112
- return repo
79
+ return new UserRepository(db)
113
80
  })
114
81
  ```
115
82
 
116
- **规则:**
117
- - 依赖的服务通过 `import` 引入其 `ServiceRegisterProps`,然后在函数体内 `loadService` 加载
118
- - **不要** 将 `loadService` 的结果缓存到模块作用域变量中
119
- - **不要** 在服务函数外部调用 `loadService` 来获取另一个服务并传入——应在服务函数内部加载
83
+ 规则:
120
84
 
121
- ### 3.4 注册销毁回调
85
+ - 在服务函数内部加载依赖
86
+ - 不在模块顶层缓存 `loadService` 结果
122
87
 
123
- **模板:**
88
+ ### 3.4 注册清理回调
124
89
 
125
90
  ```typescript
126
91
  export const connectionService = defineService(async (shutdown) => {
127
- const primary = await connectPrimary()
128
- shutdown(() => primary.disconnect()) // 注册第 1 个 → 最后执行
129
-
130
- const replica = await connectReplica()
131
- shutdown(() => replica.disconnect()) // 注册第 2 个
92
+ const a = await connectA()
93
+ shutdown(() => a.close())
132
94
 
133
- const cache = await initCache()
134
- shutdown(() => cache.flush()) // 注册第 3 个 → 最先执行
95
+ const b = await connectB()
96
+ shutdown(() => b.close())
135
97
 
136
- return { primary, replica, cache }
98
+ return { a, b }
137
99
  })
138
100
  ```
139
101
 
140
- **规则:**
141
- - 每初始化一个需要清理的资源后,**立即** 调用 `shutdown()` 注册对应的清理函数
142
- - 不要把所有清理逻辑放在一个 shutdown 里,**每个资源对应一个 shutdown 调用**
143
- - 销毁函数按 **逆序(LIFO)** 执行:后注册的先执行,先注册的后执行
144
- - 销毁函数可以是 `async`,容器会依次 `await`
145
- - 同一个函数引用多次传给 `shutdown()` 只会注册一次
146
-
147
- ### 3.5 手动销毁所有服务
102
+ 规则:
148
103
 
149
- 在应用退出或需要优雅关闭时,调用 `container.shutdown()` 手动触发所有已注册的销毁回调:
104
+ - 资源创建后立即注册清理
105
+ - 回调按 LIFO 执行
106
+ - 支持异步回调
150
107
 
151
- **模板:**
108
+ ### 3.5 全局优雅关闭
152
109
 
153
110
  ```typescript
154
111
  import container from '@hile/core'
@@ -159,298 +116,65 @@ process.on('SIGTERM', async () => {
159
116
  })
160
117
  ```
161
118
 
162
- **规则:**
163
- - `shutdown()` 返回 `Promise`,**必须** `await`
164
- - 销毁按 **服务注册逆序** 执行:后注册的服务先销毁
165
- - 每个服务内部的销毁回调同样按 **逆序(LIFO)** 执行
166
- - `shutdown()` 执行完毕后,再次调用不会重复执行(销毁队列已被清空)
119
+ ## 4. 强制规则
167
120
 
168
- ---
169
-
170
- ## 4. 强制规则(生成代码时必须遵守)
121
+ 1. 服务函数必须使用 `async`,避免同步 `throw` 破坏销毁机制。
122
+ 2. 不要手动构造 `ServiceRegisterProps`。
123
+ 3. 不要在工厂函数里动态调用 `defineService` 生成新引用。
124
+ 4. 不要在模块顶层 `await loadService(...)`。
125
+ 5. 每个外部资源都应对应一次 `shutdown` 注册。
171
126
 
172
- | # | 规则 | 原因 |
173
- |---|------|------|
174
- | 1 | 服务函数**必须**使用 `async` 声明 | 同步 `throw` 不会触发销毁机制,只有异步 reject 才会触发 |
175
- | 2 | 服务函数第一个参数**必须**是 `shutdown` | 这是容器注入的销毁注册器,即使不使用也要声明 |
176
- | 3 | `defineService` 的结果**必须**赋给模块级 `export const` | 服务基于函数引用去重,引用必须稳定 |
177
- | 4 | **不要**在 `defineService` 内直接写匿名函数再传给另一个函数 | 每次调用会创建新引用,导致重复注册 |
178
- | 5 | **不要**手动构造 `ServiceRegisterProps` 对象 | 必须通过 `defineService` 或 `container.register` 获取,内部 `flag` 为不可伪造的 Symbol |
179
- | 6 | **不要**缓存 `loadService` 的结果到模块顶层变量 | 服务可能尚未初始化,应在需要时 `await loadService()` |
180
- | 7 | 每个外部资源初始化后**立即**注册 `shutdown` | 确保初始化中途失败时已创建的资源能被正确清理 |
181
- | 8 | 一个文件只定义一个服务 | 保持服务职责单一、依赖清晰 |
182
-
183
- ---
127
+ ## 5. 常见反模式
184
128
 
185
- ## 5. 完整示例:项目结构
186
-
187
- ```
188
- src/
189
- ├── services/
190
- │ ├── config.ts # 配置服务
191
- │ ├── database.ts # 数据库服务(依赖 config)
192
- │ ├── cache.ts # 缓存服务(依赖 config)
193
- │ └── user.ts # 用户服务(依赖 database, cache)
194
- └── main.ts # 入口
195
- ```
196
-
197
- ### services/config.ts
129
+ ### 同步 throw
198
130
 
199
131
  ```typescript
200
- import { defineService } from '@hile/core'
201
-
202
- interface AppConfig {
203
- dbUrl: string
204
- cacheHost: string
205
- }
206
-
207
- export const configService = defineService(async (shutdown) => {
208
- const config: AppConfig = {
209
- dbUrl: process.env.DB_URL ?? 'postgres://localhost:5432/app',
210
- cacheHost: process.env.CACHE_HOST ?? 'localhost',
211
- }
212
- return config
132
+ //
133
+ export const bad = defineService((shutdown) => {
134
+ const r = createSync()
135
+ shutdown(() => r.close())
136
+ throw new Error('boom')
213
137
  })
214
- ```
215
-
216
- ### services/database.ts
217
-
218
- ```typescript
219
- import { defineService, loadService } from '@hile/core'
220
- import { configService } from './config'
221
138
 
222
- export const databaseService = defineService(async (shutdown) => {
223
- const config = await loadService(configService)
224
- const pool = await createPool(config.dbUrl)
225
- shutdown(() => pool.end())
226
- return pool
139
+ //
140
+ export const good = defineService(async (shutdown) => {
141
+ const r = await createAsync()
142
+ shutdown(() => r.close())
143
+ throw new Error('boom')
227
144
  })
228
145
  ```
229
146
 
230
- ### services/cache.ts
147
+ ### 顶层缓存服务实例
231
148
 
232
149
  ```typescript
233
- import { defineService, loadService } from '@hile/core'
234
- import { configService } from './config'
235
-
236
- export const cacheService = defineService(async (shutdown) => {
237
- const config = await loadService(configService)
238
- const client = await createRedisClient(config.cacheHost)
239
- shutdown(() => client.quit())
240
- return client
241
- })
242
- ```
243
-
244
- ### services/user.ts
245
-
246
- ```typescript
247
- import { defineService, loadService } from '@hile/core'
248
- import { databaseService } from './database'
249
- import { cacheService } from './cache'
250
-
251
- interface User {
252
- id: number
253
- name: string
254
- }
255
-
256
- export const userService = defineService(async (shutdown) => {
257
- const db = await loadService(databaseService)
258
- const cache = await loadService(cacheService)
259
-
260
- return {
261
- async getById(id: number): Promise<User> {
262
- const cached = await cache.get(`user:${id}`)
263
- if (cached) return JSON.parse(cached)
264
- const user = await db.query('SELECT * FROM users WHERE id = $1', [id])
265
- await cache.set(`user:${id}`, JSON.stringify(user))
266
- return user
267
- }
268
- }
269
- })
270
- ```
271
-
272
- ### main.ts
273
-
274
- ```typescript
275
- import { loadService } from '@hile/core'
276
- import { userService } from './services/user'
277
-
278
- async function main() {
279
- const users = await loadService(userService)
280
- const user = await users.getById(1)
281
- console.log(user)
282
- }
283
-
284
- main()
285
- ```
286
-
287
- ---
288
-
289
- ## 6. 反模式(生成代码时必须避免)
290
-
291
- ### 6.1 不要使用同步 throw
292
-
293
- ```typescript
294
- // ✗ 错误:同步 throw 不会触发 shutdown 销毁机制
295
- export const badService = defineService((shutdown) => {
296
- const res = createResourceSync()
297
- shutdown(() => res.close())
298
- if (!res.isValid()) throw new Error('invalid')
299
- return res
300
- })
301
-
302
- // ✓ 正确:使用 async 函数
303
- export const goodService = defineService(async (shutdown) => {
304
- const res = await createResource()
305
- shutdown(() => res.close())
306
- if (!res.isValid()) throw new Error('invalid')
307
- return res
308
- })
309
- ```
310
-
311
- ### 6.2 不要在模块顶层缓存服务结果
312
-
313
- ```typescript
314
- // ✗ 错误:模块加载时服务可能尚未就绪
150
+ //
315
151
  const db = await loadService(databaseService)
316
- export function query(sql: string) {
317
- return db.query(sql)
318
- }
319
152
 
320
- // ✓ 正确:每次在函数内部加载
153
+ // ✓
321
154
  export async function query(sql: string) {
322
155
  const db = await loadService(databaseService)
323
156
  return db.query(sql)
324
157
  }
325
158
  ```
326
159
 
327
- ### 6.3 不要内联定义服务函数
328
-
329
- ```typescript
330
- // ✗ 错误:每次调用 getService() 都创建新函数引用,无法去重
331
- function getService() {
332
- return defineService(async (shutdown) => { ... })
333
- }
334
-
335
- // ✓ 正确:模块级常量
336
- export const myService = defineService(async (shutdown) => { ... })
337
- ```
338
-
339
- ### 6.4 不要延迟注册 shutdown
340
-
341
- ```typescript
342
- // ✗ 错误:如果 doSomething 抛错,resourceA 不会被清理
343
- export const badService = defineService(async (shutdown) => {
344
- const a = await createResourceA()
345
- const b = await doSomething(a)
346
- shutdown(() => a.close()) // 太晚了!
347
- shutdown(() => b.close())
348
- return b
349
- })
350
-
351
- // ✓ 正确:创建后立即注册
352
- export const goodService = defineService(async (shutdown) => {
353
- const a = await createResourceA()
354
- shutdown(() => a.close()) // 立即注册
355
- const b = await doSomething(a)
356
- shutdown(() => b.close())
357
- return b
358
- })
359
- ```
360
-
361
- ---
362
-
363
- ## 7. API 速查
364
-
365
- ### 便捷函数(操作默认容器)
366
-
367
- | 函数 | 签名 | 说明 |
368
- |------|------|------|
369
- | `defineService` | `<R>(fn: ServiceFunction<R>) => ServiceRegisterProps<R>` | 注册服务,返回注册信息 |
370
- | `loadService` | `<R>(props: ServiceRegisterProps<R>) => Promise<R>` | 加载服务,返回服务实例 |
371
- | `isService` | `<R>(props: ServiceRegisterProps<R>) => boolean` | 判断对象是否为合法的服务注册信息(通过内部 Symbol 校验) |
372
-
373
- ### Container 类(用于创建隔离容器)
374
-
375
- | 方法 | 签名 | 说明 |
376
- |------|------|------|
377
- | `register` | `<R>(fn: ServiceFunction<R>) => ServiceRegisterProps<R>` | 注册服务。同一函数引用只注册一次 |
378
- | `resolve` | `<R>(props: ServiceRegisterProps<R>) => Promise<R>` | 解析服务(状态机见下方) |
379
- | `hasService` | `<R>(fn: ServiceFunction<R>) => boolean` | 检查服务是否已注册 |
380
- | `hasMeta` | `(id: number) => boolean` | 检查服务是否已运行过 |
381
- | `getIdByService` | `<R>(fn: ServiceFunction<R>) => number \| undefined` | 根据函数引用查 ID |
382
- | `getMetaById` | `(id: number) => Paddings \| undefined` | 根据 ID 查运行时元数据 |
383
- | `shutdown` | `() => Promise<void>` | 手动销毁所有服务,按服务注册逆序执行所有销毁回调 |
384
-
385
- ### resolve 状态机
386
-
387
- ```
388
- resolve(props)
389
-
390
- ├─ paddings 中无记录 → 首次运行
391
- │ → run(id, fn, callback)
392
- │ → 创建 Paddings { status: 0 }
393
- │ ├─ fn 成功 → status = 1, value = 返回值, 通知 queue 所有等待者
394
- │ └─ fn 失败 → status = -1, error = 错误
395
- │ → 先逆序执行 shutdown 回调
396
- │ → 再通知 queue 所有等待者
397
-
398
- ├─ status = 0 (运行中) → 加入 queue 等待
399
- ├─ status = 1 (已成功) → 直接 resolve(缓存值)
400
- └─ status = -1 (已失败) → 直接 reject(缓存错误)
401
- ```
402
-
403
- ---
404
-
405
- ## 8. 内部机制(供理解,不供直接调用)
160
+ ## 6. API 速查
406
161
 
407
- ### 数据结构
408
-
409
- | 字段 | 类型 | 说明 |
410
- |------|------|------|
411
- | `packages` | `Map<Function, number>` | 函数引用 → 服务 ID |
412
- | `paddings` | `Map<number, Paddings>` | 服务 ID → 运行时状态 |
413
- | `shutdownFunctions` | `Map<number, ServiceCutDownFunction[]>` | 服务 ID → 销毁回调数组 |
414
- | `shutdownQueues` | `number[]` | 注册了销毁回调的服务 ID 队列(有序) |
415
-
416
- ### Paddings 结构
417
-
418
- | 字段 | 类型 | 说明 |
419
- |------|------|------|
420
- | `status` | `-1 \| 0 \| 1` | -1 失败 / 0 运行中 / 1 成功 |
421
- | `value` | `R` | 成功时的返回值 |
422
- | `error` | `any` | 失败时的错误 |
423
- | `queue` | `Set<{ resolve, reject }>` | 等待中的 Promise 回调 |
424
-
425
- ### 销毁执行顺序
426
-
427
- - **单个服务内**:销毁回调按注册的逆序(LIFO)依次 `await` 执行
428
- - **全局销毁**:按服务注册顺序的逆序依次销毁
429
- - **触发时机**:
430
- - 服务函数异步失败(reject)时自动触发该服务的销毁回调
431
- - 手动调用 `container.shutdown()` 时触发所有已注册服务的销毁回调(按服务注册逆序)
432
-
433
- ### 服务标识机制(sericeFlag)
434
-
435
- 容器内部使用 `const sericeFlag = Symbol('service')` 作为服务注册信息的标识。`register` 返回的对象会携带 `flag: sericeFlag` 字段。`isService()` 通过检查 `flag === sericeFlag` 以及 `id` 和 `fn` 的类型来判断一个对象是否为合法的服务注册信息。由于 Symbol 不可伪造,外部无法手动构造通过 `isService` 校验的对象。
436
-
437
- ### 函数去重机制
438
-
439
- 容器通过 `===` 比较函数引用。两个函数即使代码完全相同,只要引用不同就会被视为不同服务。因此服务必须定义为模块级常量。
440
-
441
- ---
442
-
443
- ## 9. 开发
444
-
445
- ```bash
446
- pnpm install
447
- pnpm build # 编译
448
- pnpm dev # 监听模式
449
- pnpm test # 运行测试
450
- ```
162
+ ### 便捷函数
451
163
 
452
- **技术栈:** TypeScript, Vitest
164
+ | 函数 | 说明 |
165
+ |---|---|
166
+ | `defineService(fn)` | 注册服务到默认容器 |
167
+ | `loadService(props)` | 加载服务实例 |
168
+ | `isService(props)` | 判断是否为合法服务注册对象 |
453
169
 
454
- ## License
170
+ ### Container
455
171
 
456
- MIT
172
+ | 方法 | 说明 |
173
+ |---|---|
174
+ | `register(fn)` | 注册服务 |
175
+ | `resolve(props)` | 解析服务 |
176
+ | `hasService(fn)` | 检查函数是否已注册 |
177
+ | `hasMeta(id)` | 检查运行时元数据 |
178
+ | `getIdByService(fn)` | 通过函数获取 ID |
179
+ | `getMetaById(id)` | 通过 ID 获取元数据 |
180
+ | `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
+ 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
- * @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
+ 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 = { 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.16",
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": "a6eab4e7803f0df7d4bca9a0bdeca76692dbc883"
26
+ "gitHead": "81347b9de460b693ed82af46c0f4a287d4527323"
27
27
  }