@hile/core 1.0.22 → 1.1.0

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
1
  # @hile/core
2
2
 
3
- 轻量级异步服务容器,提供单例管理、并发请求合并与生命周期销毁能力。纯 TypeScript 实现,零运行时依赖。
3
+ 轻量级异步服务容器,提供单例管理、并发请求合并与生命周期销毁能力。纯 TypeScript 实现,零运行时依赖。默认容器使用进程级单例 `globalThis.HILE_GLOBAL_CONTAINER`。
4
4
 
5
5
  ## 安装
6
6
 
@@ -13,7 +13,7 @@ pnpm add @hile/core
13
13
  ```typescript
14
14
  import { defineService, loadService } from '@hile/core'
15
15
 
16
- const greeterService = defineService(async (shutdown) => {
16
+ const greeterService = defineService('greeter', async (shutdown) => {
17
17
  return {
18
18
  hello(name: string) {
19
19
  return `Hello, ${name}!`
@@ -27,23 +27,27 @@ greeter.hello('World') // Hello, World!
27
27
 
28
28
  ## 核心概念
29
29
 
30
- ### 1) 定义服务
30
+ ### 1) 服务 key
31
31
 
32
- 通过 `defineService` 注册服务。服务函数接收 `shutdown` 注册器,用于登记资源清理回调。
32
+ 每个服务必须指定 **`ServiceKey`(`string` 或 `symbol`)**,用于在容器内唯一标识该服务槽位。同一 key 多次 `defineService`/`register` 指向同一单例;不同 key 彼此独立。集成包常用 `Symbol.for('@scope/package')`,应用内可用 `'http'`、`'database'` 等字符串。
33
+
34
+ ### 2) 定义服务
35
+
36
+ 通过 `defineService(key, fn)` 注册服务。服务函数接收 `shutdown` 注册器,用于登记资源清理回调。
33
37
 
34
38
  ```typescript
35
39
  import { defineService } from '@hile/core'
36
40
 
37
- export const databaseService = defineService(async (shutdown) => {
41
+ export const databaseService = defineService('database', async (shutdown) => {
38
42
  const pool = await createPool('postgres://localhost:5432/app')
39
43
  shutdown(() => pool.end())
40
44
  return pool
41
45
  })
42
46
  ```
43
47
 
44
- ### 2) 加载服务
48
+ ### 3) 加载服务
45
49
 
46
- 通过 `loadService` 获取服务实例。容器保证同一服务函数只执行一次。
50
+ 通过 `loadService` 获取服务实例。容器保证同一服务 key 只执行一次工厂函数。
47
51
 
48
52
  ```typescript
49
53
  import { loadService } from '@hile/core'
@@ -53,7 +57,7 @@ const db = await loadService(databaseService)
53
57
  const users = await db.query('SELECT * FROM users')
54
58
  ```
55
59
 
56
- ### 3) 并发请求合并
60
+ ### 4) 并发请求合并
57
61
 
58
62
  并发加载同一服务时,初始化只执行一次,调用方共享结果。
59
63
 
@@ -67,7 +71,7 @@ const [r1, r2, r3] = await Promise.all([
67
71
  // r1 === r2 === r3
68
72
  ```
69
73
 
70
- ### 4) 服务间依赖
74
+ ### 5) 服务间依赖
71
75
 
72
76
  服务内部可通过 `loadService` 继续加载依赖服务。
73
77
 
@@ -76,7 +80,7 @@ import { defineService, loadService } from '@hile/core'
76
80
  import { databaseService } from './database'
77
81
  import { cacheService } from './cache'
78
82
 
79
- export const userService = defineService(async (shutdown) => {
83
+ export const userService = defineService('user', async (shutdown) => {
80
84
  const db = await loadService(databaseService)
81
85
  const cache = await loadService(cacheService)
82
86
 
@@ -92,12 +96,12 @@ export const userService = defineService(async (shutdown) => {
92
96
  })
93
97
  ```
94
98
 
95
- ### 5) 资源销毁(Shutdown)
99
+ ### 6) 资源销毁(Shutdown)
96
100
 
97
101
  当服务初始化失败或手动执行全局关闭时,容器会按规则执行已注册的清理回调。`container.shutdown()` 会循环处理队列直到清空,并让出一次事件循环(`setImmediate`),确保在 shutdown 进行中才完成启动并注册的 teardown 也会被执行。
98
102
 
99
103
  ```typescript
100
- export const connectionService = defineService(async (shutdown) => {
104
+ export const connectionService = defineService('connection', async (shutdown) => {
101
105
  const primary = await connectPrimary()
102
106
  shutdown(() => primary.disconnect())
103
107
 
@@ -120,7 +124,7 @@ export const connectionService = defineService(async (shutdown) => {
120
124
 
121
125
  > 建议始终使用 `async` 服务函数,确保异常路径可正确触发销毁机制。
122
126
 
123
- ### 6) 生命周期、超时与可观测事件
127
+ ### 7) 生命周期、超时与可观测事件
124
128
 
125
129
  容器支持显式生命周期阶段:`init -> ready -> stopping -> stopped`。
126
130
 
@@ -135,12 +139,14 @@ const container = new Container({
135
139
  })
136
140
  ```
137
141
 
138
- 订阅事件:
142
+ 订阅事件(服务相关事件携带 `key`):
139
143
 
140
144
  ```typescript
145
+ import { formatServiceKey } from '@hile/core'
146
+
141
147
  const off = container.on((event) => {
142
148
  if (event.type === 'service:ready') {
143
- console.log(`service#${event.id} ready in ${event.durationMs}ms`)
149
+ console.log(`service(${formatServiceKey(event.key)}) ready in ${event.durationMs}ms`)
144
150
  }
145
151
  })
146
152
 
@@ -148,21 +154,21 @@ const off = container.on((event) => {
148
154
  off()
149
155
  ```
150
156
 
151
- ### 7) 依赖图与启动顺序
157
+ ### 8) 依赖图与启动顺序
152
158
 
153
159
  容器会在 `resolve` 过程中自动记录依赖关系,并检测循环依赖。
154
160
 
155
161
  ```typescript
156
162
  const graph = container.getDependencyGraph()
157
- // graph.nodes: number[]
158
- // graph.edges: Array<{ from: number; to: number }>
163
+ // graph.nodes: ServiceKey[]
164
+ // graph.edges: Array<{ from: ServiceKey; to: ServiceKey }>
159
165
 
160
166
  const startupOrder = container.getStartupOrder()
161
167
  ```
162
168
 
163
169
  若出现循环依赖,会抛出 `circular dependency detected` 错误。
164
170
 
165
- ### 8) 手动销毁(Graceful Shutdown)
171
+ ### 9) 手动销毁(Graceful Shutdown)
166
172
 
167
173
  ```typescript
168
174
  import container from '@hile/core'
@@ -175,15 +181,15 @@ process.on('SIGTERM', async () => {
175
181
 
176
182
  保证:每个在 defineService 中通过 `shutdown(fn)` 注册的回调都会在 `shutdown()` 时被执行;若某服务在 shutdown 期间才完成启动并调用 `shutdown(fn)`,也会在下一轮事件循环中被关掉。
177
183
 
178
- ### 9) 服务校验(isService)
184
+ ### 10) 服务校验(isService)
179
185
 
180
186
  ```typescript
181
187
  import { defineService, isService } from '@hile/core'
182
188
 
183
- const myService = defineService(async (shutdown) => 'hello')
189
+ const myService = defineService('my', async (shutdown) => 'hello')
184
190
 
185
191
  isService(myService) // true
186
- isService({ id: 1, fn: () => {} } as any) // false
192
+ isService({ key: 'x', fn: () => {} } as any) // false
187
193
  ```
188
194
 
189
195
  ## 隔离容器
@@ -195,7 +201,7 @@ import { Container } from '@hile/core'
195
201
 
196
202
  const container = new Container()
197
203
 
198
- const service = container.register(async (shutdown) => {
204
+ const service = container.register('test', async (shutdown) => {
199
205
  return { value: 42 }
200
206
  })
201
207
 
@@ -208,27 +214,27 @@ const result = await container.resolve(service)
208
214
 
209
215
  | 函数 | 说明 |
210
216
  |------|------|
211
- | `defineService(fn)` | 注册服务到默认容器 |
217
+ | `defineService(key, fn)` | 注册服务到默认容器 |
212
218
  | `loadService(props)` | 从默认容器加载服务 |
213
219
  | `isService(props)` | 判断对象是否为合法服务注册信息 |
220
+ | `formatServiceKey(key)` | 将 key 格式化为可读字符串(日志) |
214
221
 
215
222
  ### `Container`
216
223
 
217
224
  | 方法 | 说明 |
218
225
  |------|------|
219
226
  | `new Container(options?)` | 创建容器,可配置启动/销毁超时 |
220
- | `register(fn)` | 注册服务(同函数引用去重) |
227
+ | `register(key, fn)` | 注册服务(同一 key 共享槽位) |
221
228
  | `resolve(props)` | 加载服务(执行、等待或返回缓存) |
222
229
  | `shutdown()` | 销毁所有服务并执行清理回调 |
223
230
  | `on(listener)` | 订阅容器事件,返回取消订阅函数 |
224
231
  | `off(listener)` | 取消订阅 |
225
- | `getLifecycle(id)` | 获取服务生命周期阶段 |
226
- | `getDependencyGraph()` | 获取依赖图 `{ nodes, edges }` |
232
+ | `getLifecycle(key)` | 获取服务生命周期阶段 |
233
+ | `getDependencyGraph()` | 获取依赖图 `{ nodes, edges }`(key 为 `ServiceKey`) |
227
234
  | `getStartupOrder()` | 获取服务启动顺序(首次启动顺序) |
228
- | `hasService(fn)` | 检查函数是否已注册 |
229
- | `hasMeta(id)` | 检查服务是否已有运行时元数据 |
230
- | `getIdByService(fn)` | 通过函数获取服务 ID |
231
- | `getMetaById(id)` | 通过 ID 获取运行时元数据 |
235
+ | `hasService(key)` | 是否已对该 key 注册 |
236
+ | `hasMeta(key)` | key 是否已有运行时元数据 |
237
+ | `getMetaByKey(key)` | key 获取运行时元数据 |
232
238
 
233
239
  ### 服务状态
234
240
 
package/SKILL.md CHANGED
@@ -9,7 +9,7 @@ description: "@hile/core 的代码生成与使用规范。适用于定义/加载
9
9
 
10
10
  ## 1. 强约束(必须遵守)
11
11
 
12
- 1. 服务必须使用 `async (shutdown)` 形态定义。
12
+ 1. 服务必须使用 `defineService(key, async (shutdown) => ...)` 形态:`key` 为 `ServiceKey`(`string | symbol`),在容器内唯一标识该服务槽位;同一 key 共享单例。
13
13
  2. 只能通过 `defineService` / `container.register` 产出服务对象。
14
14
  3. 只能通过 `loadService` / `container.resolve` 获取服务实例。
15
15
  4. 外部资源创建后必须立即注册 `shutdown`。
@@ -34,7 +34,7 @@ description: "@hile/core 的代码生成与使用规范。适用于定义/加载
34
34
 
35
35
  允许订阅:`container.on(listener)`。
36
36
 
37
- 关键事件:
37
+ 关键事件(服务相关事件携带 `key: ServiceKey`):
38
38
 
39
39
  - `service:init`
40
40
  - `service:ready`
@@ -50,13 +50,14 @@ description: "@hile/core 的代码生成与使用规范。适用于定义/加载
50
50
 
51
51
  - 订阅后必须在生命周期结束时取消订阅。
52
52
  - 记录错误时保留原始 error 对象。
53
+ - 日志展示 key 时使用 `formatServiceKey(event.key)`。
53
54
 
54
55
  ## 4. 依赖图与循环依赖
55
56
 
56
57
  容器会自动记录服务依赖并检测循环依赖:
57
58
 
58
- - `getDependencyGraph()`
59
- - `getStartupOrder()`
59
+ - `getDependencyGraph()`(`nodes` / `edges` 使用 `ServiceKey`)
60
+ - `getStartupOrder()` 返回 `ServiceKey[]`
60
61
 
61
62
  规则:
62
63
 
@@ -82,22 +83,22 @@ export async function query(sql: string) {
82
83
 
83
84
  ```typescript
84
85
  // ✗
85
- const fake = { id: 1, fn: async () => 1 }
86
+ const fake = { key: 'x', fn: async () => 1 }
86
87
 
87
88
  // ✓
88
- const real = defineService(async () => 1)
89
+ const real = defineService('real', async () => 1)
89
90
  ```
90
91
 
91
92
  ### 5.3 不注册资源清理
92
93
 
93
94
  ```typescript
94
95
  // ✗
95
- export const bad = defineService(async () => {
96
+ export const bad = defineService('bad', async () => {
96
97
  return await createPool()
97
98
  })
98
99
 
99
100
  // ✓
100
- export const good = defineService(async (shutdown) => {
101
+ export const good = defineService('good', async (shutdown) => {
101
102
  const pool = await createPool()
102
103
  shutdown(() => pool.end())
103
104
  return pool
package/dist/index.d.ts CHANGED
@@ -1,12 +1,16 @@
1
1
  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
+ export type ServiceKey = string | symbol;
4
5
  declare const sericeFlag: unique symbol;
6
+ declare global {
7
+ var HILE_GLOBAL_CONTAINER: Container;
8
+ }
5
9
  export type ServiceLifecycleStage = 'init' | 'ready' | 'stopping' | 'stopped';
6
10
  export interface ServiceRegisterProps<R> {
7
- id: number;
8
11
  fn: ServiceFunction<R>;
9
12
  flag: typeof sericeFlag;
13
+ key: ServiceKey;
10
14
  }
11
15
  export interface ContainerOptions {
12
16
  startTimeoutMs?: number;
@@ -14,26 +18,26 @@ export interface ContainerOptions {
14
18
  }
15
19
  export type ContainerEvent = {
16
20
  type: 'service:init';
17
- id: number;
21
+ key: ServiceKey;
18
22
  } | {
19
23
  type: 'service:ready';
20
- id: number;
24
+ key: ServiceKey;
21
25
  durationMs: number;
22
26
  } | {
23
27
  type: 'service:error';
24
- id: number;
28
+ key: ServiceKey;
25
29
  error: any;
26
30
  durationMs: number;
27
31
  } | {
28
32
  type: 'service:shutdown:start';
29
- id: number;
33
+ key: ServiceKey;
30
34
  } | {
31
35
  type: 'service:shutdown:done';
32
- id: number;
36
+ key: ServiceKey;
33
37
  durationMs: number;
34
38
  } | {
35
39
  type: 'service:shutdown:error';
36
- id: number;
40
+ key: ServiceKey;
37
41
  error: any;
38
42
  } | {
39
43
  type: 'container:shutdown:start';
@@ -56,10 +60,13 @@ interface Paddings<R = any> {
56
60
  startedAt: number;
57
61
  endedAt?: number;
58
62
  }
63
+ /** 日志或调试时展示服务 key */
64
+ export declare function formatServiceKey(k: ServiceKey): string;
59
65
  export declare class Container {
60
66
  private readonly options;
61
- private id;
62
- private readonly packages;
67
+ private keyOrder;
68
+ private readonly keyOrdinal;
69
+ private readonly registeredKeys;
63
70
  private readonly paddings;
64
71
  private readonly dependencies;
65
72
  private readonly dependents;
@@ -70,32 +77,31 @@ export declare class Container {
70
77
  private readonly context;
71
78
  constructor(options?: ContainerOptions);
72
79
  private emit;
73
- private getId;
80
+ private nextOrdinal;
74
81
  private hasPath;
75
82
  private trackDependency;
76
83
  on(listener: (event: ContainerEvent) => void): () => boolean;
77
84
  off(listener: (event: ContainerEvent) => void): void;
78
- getLifecycle(id: number): ServiceLifecycleStage | undefined;
85
+ getLifecycle(key: ServiceKey): ServiceLifecycleStage | undefined;
79
86
  getDependencyGraph(): {
80
- nodes: number[];
87
+ nodes: ServiceKey[];
81
88
  edges: {
82
- from: number;
83
- to: number;
89
+ from: ServiceKey;
90
+ to: ServiceKey;
84
91
  }[];
85
92
  };
86
- getStartupOrder(): number[];
87
- register<R>(fn: ServiceFunction<R>): ServiceRegisterProps<R>;
93
+ getStartupOrder(): ServiceKey[];
94
+ register<R>(key: ServiceKey, fn: ServiceFunction<R>): ServiceRegisterProps<R>;
88
95
  resolve<R>(props: ServiceRegisterProps<R>): Promise<R>;
89
96
  private run;
90
97
  private shutdownService;
91
98
  shutdown(): Promise<void>;
92
- hasService<R>(fn: ServiceFunction<R>): boolean;
93
- hasMeta(id: number): boolean;
94
- getIdByService<R>(fn: ServiceFunction<R>): number | undefined;
95
- getMetaById(id: number): Paddings<any> | undefined;
99
+ hasService(key: ServiceKey): boolean;
100
+ hasMeta(key: ServiceKey): boolean;
101
+ getMetaByKey(key: ServiceKey): Paddings<any> | undefined;
96
102
  }
97
103
  export declare const container: Container;
98
- export declare function defineService<R>(fn: ServiceFunction<R>): ServiceRegisterProps<R>;
104
+ export declare function defineService<R>(key: ServiceKey, fn: ServiceFunction<R>): ServiceRegisterProps<R>;
99
105
  export declare function loadService<R>(props: ServiceRegisterProps<R>): Promise<R>;
100
106
  export declare function isService<R>(props: ServiceRegisterProps<R>): boolean;
101
107
  export default container;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
- const sericeFlag = Symbol('service');
2
+ const sericeFlag = Symbol.for('service');
3
3
  async function withTimeout(promise, timeoutMs, message) {
4
4
  if (!timeoutMs || timeoutMs <= 0)
5
5
  return promise;
@@ -15,10 +15,15 @@ async function withTimeout(promise, timeoutMs, message) {
15
15
  clearTimeout(timer);
16
16
  }
17
17
  }
18
+ /** 日志或调试时展示服务 key */
19
+ export function formatServiceKey(k) {
20
+ return typeof k === 'string' ? k : String(k);
21
+ }
18
22
  export class Container {
19
23
  options;
20
- id = 1;
21
- packages = new Map();
24
+ keyOrder = 0;
25
+ keyOrdinal = new Map();
26
+ registeredKeys = new Set();
22
27
  paddings = new Map();
23
28
  dependencies = new Map();
24
29
  dependents = new Map();
@@ -40,12 +45,12 @@ export class Container {
40
45
  }
41
46
  }
42
47
  }
43
- getId() {
44
- let i = this.id++;
45
- if (i >= Number.MAX_SAFE_INTEGER) {
46
- i = this.id = 1;
48
+ nextOrdinal() {
49
+ let o = ++this.keyOrder;
50
+ if (o >= Number.MAX_SAFE_INTEGER) {
51
+ o = this.keyOrder = 1;
47
52
  }
48
- return i;
53
+ return o;
49
54
  }
50
55
  hasPath(from, to, visited = new Set()) {
51
56
  if (from === to)
@@ -62,25 +67,25 @@ export class Container {
62
67
  }
63
68
  return false;
64
69
  }
65
- trackDependency(parentId, childId) {
66
- if (parentId === childId) {
67
- throw new Error(`circular dependency detected: ${parentId} -> ${childId}`);
70
+ trackDependency(parentKey, childKey) {
71
+ if (parentKey === childKey) {
72
+ throw new Error(`circular dependency detected: ${formatServiceKey(parentKey)} -> ${formatServiceKey(childKey)}`);
68
73
  }
69
- if (!this.dependencies.has(parentId)) {
70
- this.dependencies.set(parentId, new Set());
74
+ if (!this.dependencies.has(parentKey)) {
75
+ this.dependencies.set(parentKey, new Set());
71
76
  }
72
- if (!this.dependents.has(childId)) {
73
- this.dependents.set(childId, new Set());
77
+ if (!this.dependents.has(childKey)) {
78
+ this.dependents.set(childKey, new Set());
74
79
  }
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}`);
80
+ const parentDeps = this.dependencies.get(parentKey);
81
+ if (!parentDeps.has(childKey)) {
82
+ if (this.hasPath(childKey, parentKey)) {
83
+ const error = new Error(`circular dependency detected: ${formatServiceKey(parentKey)} -> ${formatServiceKey(childKey)}`);
79
84
  this.emit({ type: 'container:error', error });
80
85
  throw error;
81
86
  }
82
- parentDeps.add(childId);
83
- this.dependents.get(childId).add(parentId);
87
+ parentDeps.add(childKey);
88
+ this.dependents.get(childKey).add(parentKey);
84
89
  }
85
90
  }
86
91
  on(listener) {
@@ -90,15 +95,15 @@ export class Container {
90
95
  off(listener) {
91
96
  this.listeners.delete(listener);
92
97
  }
93
- getLifecycle(id) {
94
- return this.paddings.get(id)?.lifecycle;
98
+ getLifecycle(key) {
99
+ return this.paddings.get(key)?.lifecycle;
95
100
  }
96
101
  getDependencyGraph() {
97
- const nodes = Array.from(this.packages.values()).sort((a, b) => a - b);
102
+ const nodes = Array.from(this.registeredKeys).sort((a, b) => (this.keyOrdinal.get(a) - this.keyOrdinal.get(b)));
98
103
  const edges = [];
99
- for (const [id, deps] of this.dependencies.entries()) {
100
- for (const dep of deps) {
101
- edges.push({ from: id, to: dep });
104
+ for (const [fromKey, deps] of this.dependencies.entries()) {
105
+ for (const toKey of deps) {
106
+ edges.push({ from: fromKey, to: toKey });
102
107
  }
103
108
  }
104
109
  return { nodes, edges };
@@ -106,24 +111,23 @@ export class Container {
106
111
  getStartupOrder() {
107
112
  return [...this.startupOrder];
108
113
  }
109
- register(fn) {
110
- if (this.packages.has(fn)) {
111
- return { id: this.packages.get(fn), fn, flag: sericeFlag };
114
+ register(key, fn) {
115
+ if (!this.keyOrdinal.has(key)) {
116
+ this.keyOrdinal.set(key, this.nextOrdinal());
112
117
  }
113
- const id = this.getId();
114
- this.packages.set(fn, id);
115
- return { id, fn, flag: sericeFlag };
118
+ this.registeredKeys.add(key);
119
+ return { key, fn, flag: sericeFlag };
116
120
  }
117
121
  resolve(props) {
118
- const { id, fn } = props;
122
+ const { key, fn } = props;
119
123
  const stack = this.context.getStore() || [];
120
- const parentId = stack.length ? stack[stack.length - 1] : undefined;
121
- if (parentId !== undefined) {
122
- this.trackDependency(parentId, id);
124
+ const parentKey = stack.length ? stack[stack.length - 1] : undefined;
125
+ if (parentKey !== undefined) {
126
+ this.trackDependency(parentKey, key);
123
127
  }
124
128
  return new Promise((resolve, reject) => {
125
- if (!this.paddings.has(id)) {
126
- return this.run(id, fn, (e, v) => {
129
+ if (!this.paddings.has(key)) {
130
+ return this.run(key, fn, (e, v) => {
127
131
  if (e) {
128
132
  reject(e);
129
133
  }
@@ -132,7 +136,7 @@ export class Container {
132
136
  }
133
137
  });
134
138
  }
135
- const state = this.paddings.get(id);
139
+ const state = this.paddings.get(key);
136
140
  switch (state.status) {
137
141
  case 0:
138
142
  state.queue.add({ resolve, reject });
@@ -146,7 +150,7 @@ export class Container {
146
150
  }
147
151
  });
148
152
  }
149
- run(id, fn, callback) {
153
+ run(key, fn, callback) {
150
154
  const state = {
151
155
  status: 0,
152
156
  lifecycle: 'init',
@@ -154,32 +158,32 @@ export class Container {
154
158
  queue: new Set(),
155
159
  startedAt: Date.now(),
156
160
  };
157
- this.paddings.set(id, state);
158
- if (!this.startupOrder.includes(id)) {
159
- this.startupOrder.push(id);
161
+ this.paddings.set(key, state);
162
+ if (!this.startupOrder.includes(key)) {
163
+ this.startupOrder.push(key);
160
164
  }
161
- this.emit({ type: 'service:init', id });
165
+ this.emit({ type: 'service:init', key });
162
166
  const curDown = (cutDownFn) => {
163
- if (!this.shutdownQueues.includes(id)) {
164
- this.shutdownQueues.push(id);
167
+ if (!this.shutdownQueues.includes(key)) {
168
+ this.shutdownQueues.push(key);
165
169
  }
166
- if (!this.shutdownFunctions.has(id)) {
167
- this.shutdownFunctions.set(id, []);
170
+ if (!this.shutdownFunctions.has(key)) {
171
+ this.shutdownFunctions.set(key, []);
168
172
  }
169
- const pools = this.shutdownFunctions.get(id);
173
+ const pools = this.shutdownFunctions.get(key);
170
174
  if (!pools.includes(cutDownFn)) {
171
175
  pools.push(cutDownFn);
172
176
  }
173
177
  };
174
178
  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) => {
179
+ const startupPromise = this.context.run([...parentStack, key], () => Promise.resolve(fn(curDown)));
180
+ withTimeout(startupPromise, this.options.startTimeoutMs, `service startup timeout: ${formatServiceKey(key)} exceeded ${this.options.startTimeoutMs}ms`).then((value) => {
177
181
  state.status = 1;
178
182
  state.lifecycle = 'ready';
179
183
  state.value = value;
180
184
  state.endedAt = Date.now();
181
185
  const durationMs = state.endedAt - state.startedAt;
182
- this.emit({ type: 'service:ready', id, durationMs });
186
+ this.emit({ type: 'service:ready', key, durationMs });
183
187
  for (const queue of state.queue) {
184
188
  queue.resolve(value);
185
189
  }
@@ -191,7 +195,7 @@ export class Container {
191
195
  state.error = e;
192
196
  state.endedAt = Date.now();
193
197
  const durationMs = state.endedAt - state.startedAt;
194
- this.emit({ type: 'service:error', id, error: e, durationMs });
198
+ this.emit({ type: 'service:error', key, error: e, durationMs });
195
199
  const clear = () => {
196
200
  state.lifecycle = 'stopped';
197
201
  for (const queue of state.queue) {
@@ -200,39 +204,39 @@ export class Container {
200
204
  state.queue.clear();
201
205
  callback(e);
202
206
  };
203
- this.shutdownService(id)
207
+ this.shutdownService(key)
204
208
  .then(clear)
205
209
  .catch((shutdownError) => {
206
- this.emit({ type: 'service:shutdown:error', id, error: shutdownError });
210
+ this.emit({ type: 'service:shutdown:error', key, error: shutdownError });
207
211
  clear();
208
212
  });
209
213
  });
210
214
  }
211
- async shutdownService(id) {
212
- if (this.shutdownQueues.includes(id)) {
213
- const meta = this.paddings.get(id);
215
+ async shutdownService(key) {
216
+ if (this.shutdownQueues.includes(key)) {
217
+ const meta = this.paddings.get(key);
214
218
  if (meta) {
215
219
  meta.lifecycle = 'stopping';
216
220
  }
217
- this.emit({ type: 'service:shutdown:start', id });
221
+ this.emit({ type: 'service:shutdown:start', key });
218
222
  const startedAt = Date.now();
219
- const pools = this.shutdownFunctions.get(id);
223
+ const pools = this.shutdownFunctions.get(key);
220
224
  let i = pools.length;
221
225
  while (i--) {
222
226
  const teardown = pools[i];
223
227
  try {
224
- await withTimeout(Promise.resolve(teardown()), this.options.shutdownTimeoutMs, `service shutdown timeout: ${id} exceeded ${this.options.shutdownTimeoutMs}ms`);
228
+ await withTimeout(Promise.resolve(teardown()), this.options.shutdownTimeoutMs, `service shutdown timeout: ${formatServiceKey(key)} exceeded ${this.options.shutdownTimeoutMs}ms`);
225
229
  }
226
230
  catch (error) {
227
- this.emit({ type: 'service:shutdown:error', id, error });
231
+ this.emit({ type: 'service:shutdown:error', key, error });
228
232
  }
229
233
  }
230
- this.shutdownFunctions.delete(id);
231
- this.shutdownQueues.splice(this.shutdownQueues.indexOf(id), 1);
234
+ this.shutdownFunctions.delete(key);
235
+ this.shutdownQueues.splice(this.shutdownQueues.indexOf(key), 1);
232
236
  if (meta) {
233
237
  meta.lifecycle = 'stopped';
234
238
  }
235
- this.emit({ type: 'service:shutdown:done', id, durationMs: Date.now() - startedAt });
239
+ this.emit({ type: 'service:shutdown:done', key, durationMs: Date.now() - startedAt });
236
240
  }
237
241
  }
238
242
  async shutdown() {
@@ -241,8 +245,8 @@ export class Container {
241
245
  // 循环直到队列清空;再让出一次事件循环,处理「shutdown 期间才调用 curDown」的晚注册 teardown
242
246
  while (true) {
243
247
  while (this.shutdownQueues.length > 0) {
244
- const id = this.shutdownQueues[this.shutdownQueues.length - 1];
245
- await this.shutdownService(id);
248
+ const key = this.shutdownQueues[this.shutdownQueues.length - 1];
249
+ await this.shutdownService(key);
246
250
  }
247
251
  await new Promise(r => setImmediate(r));
248
252
  if (this.shutdownQueues.length === 0)
@@ -252,27 +256,32 @@ export class Container {
252
256
  this.shutdownQueues.length = 0;
253
257
  this.emit({ type: 'container:shutdown:done', durationMs: Date.now() - startedAt });
254
258
  }
255
- hasService(fn) {
256
- return this.packages.has(fn);
259
+ hasService(key) {
260
+ return this.registeredKeys.has(key);
257
261
  }
258
- hasMeta(id) {
259
- return this.paddings.has(id);
262
+ hasMeta(key) {
263
+ return this.paddings.has(key);
260
264
  }
261
- getIdByService(fn) {
262
- return this.packages.get(fn);
265
+ getMetaByKey(key) {
266
+ return this.paddings.get(key);
263
267
  }
264
- getMetaById(id) {
265
- return this.paddings.get(id);
268
+ }
269
+ function getGlobalContainer() {
270
+ if (!globalThis.HILE_GLOBAL_CONTAINER) {
271
+ globalThis.HILE_GLOBAL_CONTAINER = new Container();
266
272
  }
273
+ return globalThis.HILE_GLOBAL_CONTAINER;
267
274
  }
268
- export const container = new Container();
269
- export function defineService(fn) {
270
- return container.register(fn);
275
+ export const container = getGlobalContainer();
276
+ export function defineService(key, fn) {
277
+ return container.register(key, fn);
271
278
  }
272
279
  export function loadService(props) {
273
280
  return container.resolve(props);
274
281
  }
275
282
  export function isService(props) {
276
- return props.flag === sericeFlag && typeof props.id === 'number' && typeof props.fn === 'function';
283
+ return (props.flag === sericeFlag
284
+ && typeof props.fn === 'function'
285
+ && (typeof props.key === 'string' || typeof props.key === 'symbol'));
277
286
  }
278
287
  export default container;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hile/core",
3
- "version": "1.0.22",
3
+ "version": "1.1.0",
4
4
  "description": "Hile core - lightweight async service container",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,5 +24,5 @@
24
24
  "fix-esm-import-path": "^1.10.3",
25
25
  "vitest": "^4.0.18"
26
26
  },
27
- "gitHead": "ffc0bf9ab4d0e251244c174536bac79c1f2f77fe"
27
+ "gitHead": "ec49eb8979b6c9c3ce99cb5311f1684bcde1bf2a"
28
28
  }