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