@hile/core 1.0.15 → 1.0.17
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 +45 -53
- package/SKILL.md +76 -352
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @hile/core
|
|
2
2
|
|
|
3
|
-
|
|
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') //
|
|
25
|
+
greeter.hello('World') // Hello, World!
|
|
28
26
|
```
|
|
29
27
|
|
|
30
28
|
## 核心概念
|
|
31
29
|
|
|
32
|
-
### 定义服务
|
|
30
|
+
### 1) 定义服务
|
|
33
31
|
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
+
服务内部可通过 `loadService` 继续加载依赖服务。
|
|
75
73
|
|
|
76
74
|
```typescript
|
|
77
75
|
import { defineService, loadService } from '@hile/core'
|
|
@@ -94,37 +92,35 @@ export const userService = defineService(async (shutdown) => {
|
|
|
94
92
|
})
|
|
95
93
|
```
|
|
96
94
|
|
|
97
|
-
### 资源销毁(Shutdown)
|
|
95
|
+
### 5) 资源销毁(Shutdown)
|
|
98
96
|
|
|
99
|
-
|
|
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())
|
|
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
|
-
-
|
|
116
|
+
- 清理回调按逆序(LIFO)执行
|
|
119
117
|
- 支持异步清理函数
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
> **注意:** 请使用 `async` 函数定义服务。同步函数中直接 `throw` 的错误无法触发销毁机制。
|
|
118
|
+
- 同一函数引用重复注册只执行一次
|
|
119
|
+
- 清理函数错误不会覆盖原始业务错误
|
|
124
120
|
|
|
125
|
-
|
|
121
|
+
> 建议始终使用 `async` 服务函数,确保异常路径可正确触发销毁机制。
|
|
126
122
|
|
|
127
|
-
|
|
123
|
+
### 6) 手动销毁(Graceful Shutdown)
|
|
128
124
|
|
|
129
125
|
```typescript
|
|
130
126
|
import container from '@hile/core'
|
|
@@ -135,24 +131,20 @@ process.on('SIGTERM', async () => {
|
|
|
135
131
|
})
|
|
136
132
|
```
|
|
137
133
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
### 服务校验(isService)
|
|
141
|
-
|
|
142
|
-
使用 `isService` 判断一个对象是否为合法的服务注册信息。内部通过不可伪造的 Symbol 标识校验,确保只有通过 `defineService` / `container.register` 创建的对象才会返回 `true`:
|
|
134
|
+
### 7) 服务校验(isService)
|
|
143
135
|
|
|
144
136
|
```typescript
|
|
145
137
|
import { defineService, isService } from '@hile/core'
|
|
146
138
|
|
|
147
139
|
const myService = defineService(async (shutdown) => 'hello')
|
|
148
140
|
|
|
149
|
-
isService(myService)
|
|
150
|
-
isService({ id: 1, fn: () => {} } as any)
|
|
141
|
+
isService(myService) // true
|
|
142
|
+
isService({ id: 1, fn: () => {} } as any) // false
|
|
151
143
|
```
|
|
152
144
|
|
|
153
145
|
## 隔离容器
|
|
154
146
|
|
|
155
|
-
|
|
147
|
+
除了默认容器,也可以手动创建独立容器以实现作用域隔离。
|
|
156
148
|
|
|
157
149
|
```typescript
|
|
158
150
|
import { Container } from '@hile/core'
|
|
@@ -168,31 +160,31 @@ const result = await container.resolve(service)
|
|
|
168
160
|
|
|
169
161
|
## API
|
|
170
162
|
|
|
171
|
-
###
|
|
163
|
+
### 顶层函数
|
|
172
164
|
|
|
173
165
|
| 函数 | 说明 |
|
|
174
166
|
|------|------|
|
|
175
|
-
| `defineService(fn)` |
|
|
176
|
-
| `loadService(props)` |
|
|
177
|
-
| `isService(props)` |
|
|
167
|
+
| `defineService(fn)` | 注册服务到默认容器 |
|
|
168
|
+
| `loadService(props)` | 从默认容器加载服务 |
|
|
169
|
+
| `isService(props)` | 判断对象是否为合法服务注册信息 |
|
|
178
170
|
|
|
179
|
-
### Container
|
|
171
|
+
### `Container`
|
|
180
172
|
|
|
181
173
|
| 方法 | 说明 |
|
|
182
174
|
|------|------|
|
|
183
|
-
| `register(fn)` |
|
|
184
|
-
| `resolve(props)` |
|
|
185
|
-
| `hasService(fn)` |
|
|
186
|
-
| `hasMeta(id)` |
|
|
187
|
-
| `getIdByService(fn)` |
|
|
188
|
-
| `getMetaById(id)` |
|
|
189
|
-
| `shutdown()` |
|
|
175
|
+
| `register(fn)` | 注册服务(同函数引用去重) |
|
|
176
|
+
| `resolve(props)` | 加载服务(执行、等待或返回缓存) |
|
|
177
|
+
| `hasService(fn)` | 检查函数是否已注册 |
|
|
178
|
+
| `hasMeta(id)` | 检查服务是否已有运行时元数据 |
|
|
179
|
+
| `getIdByService(fn)` | 通过函数获取服务 ID |
|
|
180
|
+
| `getMetaById(id)` | 通过 ID 获取运行时元数据 |
|
|
181
|
+
| `shutdown()` | 销毁所有服务并执行清理回调 |
|
|
190
182
|
|
|
191
183
|
### 服务状态
|
|
192
184
|
|
|
193
|
-
| 状态 | 值 | `resolve`
|
|
194
|
-
|
|
195
|
-
|
|
|
185
|
+
| 状态 | 值 | `resolve` 行为 |
|
|
186
|
+
|------|---|---------------|
|
|
187
|
+
| 未运行 | — | 执行服务函数 |
|
|
196
188
|
| 运行中 | `0` | 加入等待队列 |
|
|
197
189
|
| 已成功 | `1` | 返回缓存值 |
|
|
198
190
|
| 已失败 | `-1` | 返回缓存错误 |
|
|
@@ -201,9 +193,9 @@ const result = await container.resolve(service)
|
|
|
201
193
|
|
|
202
194
|
```bash
|
|
203
195
|
pnpm install
|
|
204
|
-
pnpm build
|
|
205
|
-
pnpm dev
|
|
206
|
-
pnpm test
|
|
196
|
+
pnpm build
|
|
197
|
+
pnpm dev
|
|
198
|
+
pnpm test
|
|
207
199
|
```
|
|
208
200
|
|
|
209
201
|
## License
|
package/SKILL.md
CHANGED
|
@@ -1,44 +1,31 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hile-core
|
|
3
|
-
description:
|
|
3
|
+
description: @hile/core 的代码生成与使用规范。适用于定义/加载 Hile 服务、生命周期 shutdown 编排、或涉及 defineService、loadService、Container 等话题。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# @hile/core SKILL
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
本文档用于约束 AI 与开发者在使用 `@hile/core` 时的代码生成方式,目标是保证服务定义、依赖加载与资源销毁行为一致且可维护。
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## 1. 架构总览
|
|
13
|
-
|
|
14
|
-
Hile 的核心是 `Container`(服务容器)。所有服务都必须经过 **定义 → 加载** 两个步骤才能使用。容器保证:
|
|
15
|
-
|
|
16
|
-
- 每个服务函数只执行一次(**单例**)
|
|
17
|
-
- 并发请求同一服务时自动合并(**不重复执行**)
|
|
18
|
-
- 服务启动失败时自动执行已注册的清理回调(**销毁机制**)
|
|
10
|
+
## 1. 架构概览
|
|
19
11
|
|
|
20
|
-
|
|
12
|
+
Hile 以 `Container` 为核心,服务遵循“定义 → 加载”两阶段:
|
|
21
13
|
|
|
22
|
-
|
|
14
|
+
- 单例:同一服务函数仅初始化一次
|
|
15
|
+
- 并发合并:并发加载同一服务时共享同一初始化过程
|
|
16
|
+
- 失败回收:初始化失败时自动执行已注册的清理回调
|
|
23
17
|
|
|
24
|
-
|
|
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
|
-
|
|
39
|
-
const sericeFlag: unique symbol;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
+
- 始终通过 `loadService` 获取实例
|
|
69
|
+
- `loadService` 返回 Promise,必须 `await`
|
|
96
70
|
|
|
97
|
-
|
|
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
|
-
|
|
85
|
+
- 在服务函数内部加载依赖
|
|
86
|
+
- 不在模块顶层缓存 `loadService` 结果
|
|
122
87
|
|
|
123
|
-
|
|
88
|
+
### 3.4 注册清理回调
|
|
124
89
|
|
|
125
90
|
```typescript
|
|
126
91
|
export const connectionService = defineService(async (shutdown) => {
|
|
127
|
-
const
|
|
128
|
-
shutdown(() =>
|
|
129
|
-
|
|
130
|
-
const replica = await connectReplica()
|
|
131
|
-
shutdown(() => replica.disconnect()) // 注册第 2 个
|
|
92
|
+
const a = await connectA()
|
|
93
|
+
shutdown(() => a.close())
|
|
132
94
|
|
|
133
|
-
const
|
|
134
|
-
shutdown(() =>
|
|
95
|
+
const b = await connectB()
|
|
96
|
+
shutdown(() => b.close())
|
|
135
97
|
|
|
136
|
-
return {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
shutdown(() =>
|
|
226
|
-
|
|
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
|
-
###
|
|
147
|
+
### 顶层缓存服务实例
|
|
231
148
|
|
|
232
149
|
```typescript
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
+
| 函数 | 说明 |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `defineService(fn)` | 注册服务到默认容器 |
|
|
167
|
+
| `loadService(props)` | 加载服务实例 |
|
|
168
|
+
| `isService(props)` | 判断是否为合法服务注册对象 |
|
|
453
169
|
|
|
454
|
-
|
|
170
|
+
### Container
|
|
455
171
|
|
|
456
|
-
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
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": "6672fc4cdc551c4265912cc85ee2e96fc44bc4c9"
|
|
27
27
|
}
|