@taicode/common-base 4.0.0-beta.1 → 4.0.0-beta.10

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.
@@ -1,7 +1,25 @@
1
1
  type CleanupFunction = () => unknown | Promise<unknown>;
2
+ /**
3
+ * 清理失败时的日志出口(结构性类型)。
4
+ *
5
+ * 既兼容 base 全局 logger 的可变参形式 `(...args: unknown[])`,
6
+ * 也兼容 pino 的 `log.error(obj, msg)`;调用方传入应用自己的 logger 后,
7
+ * 失败日志就不会落进 console,而是进应用日志流。
8
+ */
9
+ export interface DisposerLogSink {
10
+ error(payload: unknown, message?: string): void;
11
+ }
12
+ export interface DisposerOptions {
13
+ /** 失败日志出口;不传则回退到 base 全局 logger */
14
+ log?: DisposerLogSink;
15
+ /** 日志标签,用于区分是哪个阶段的 disposer(缺省 `disposer`) */
16
+ label?: string;
17
+ }
2
18
  export declare class Disposer {
19
+ private readonly opts;
3
20
  private cleanupFunctions;
4
- addDisposer(cleanupFn: CleanupFunction): void;
21
+ constructor(opts?: DisposerOptions);
22
+ add(cleanupFn: CleanupFunction): void;
5
23
  dispose(): Promise<void>;
6
24
  }
7
25
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"disposer.d.ts","sourceRoot":"","sources":["../../source/disposer/disposer.ts"],"names":[],"mappings":"AAEA,KAAK,eAAe,GAAG,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;AAEvD,qBAAa,QAAQ;IACnB,OAAO,CAAC,gBAAgB,CAAwB;IAGhD,WAAW,CAAC,SAAS,EAAE,eAAe,GAAG,IAAI;IAKvC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAY/B"}
1
+ {"version":3,"file":"disposer.d.ts","sourceRoot":"","sources":["../../source/disposer/disposer.ts"],"names":[],"mappings":"AAEA,KAAK,eAAe,GAAG,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;AAEvD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAChD;AAED,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,GAAG,CAAC,EAAE,eAAe,CAAA;IACrB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,QAAQ;IAGP,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,gBAAgB,CAAwB;gBAEnB,IAAI,GAAE,eAAoB;IAGvD,GAAG,CAAC,SAAS,EAAE,eAAe,GAAG,IAAI;IAK/B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB/B"}
@@ -1,20 +1,28 @@
1
1
  import { logger } from '../logger/index.js';
2
2
  export class Disposer {
3
+ opts;
3
4
  cleanupFunctions = [];
5
+ constructor(opts = {}) {
6
+ this.opts = opts;
7
+ }
4
8
  // 添加清理函数
5
- addDisposer(cleanupFn) {
9
+ add(cleanupFn) {
6
10
  this.cleanupFunctions.push(cleanupFn);
7
11
  }
8
- // 执行所有清理函数
12
+ // 执行所有清理函数(LIFO:后注册的先执行)
9
13
  async dispose() {
10
14
  const cleanupFunctions = this.cleanupFunctions;
11
15
  this.cleanupFunctions = [];
12
- for (const cleanupFn of cleanupFunctions) {
16
+ // 后创建的东西依赖先创建的,必须**后**销毁:逆注册序执行
17
+ for (const cleanupFn of [...cleanupFunctions].reverse()) {
13
18
  try {
14
19
  await cleanupFn();
15
20
  }
16
21
  catch (error) {
17
- logger.error('Error during cleanup:', error);
22
+ // 单个清理失败不阻断后续,也不向调用方抛出
23
+ const label = this.opts.label ?? 'disposer';
24
+ const sink = this.opts.log ?? logger;
25
+ sink.error({ error }, `[${label}] cleanup failed`);
18
26
  }
19
27
  }
20
28
  }
@@ -6,20 +6,20 @@ describe('Disposer', () => {
6
6
  beforeEach(() => {
7
7
  disposer = new Disposer();
8
8
  });
9
- describe('addDisposer', () => {
9
+ describe('add', () => {
10
10
  it('应该能够添加清理函数', () => {
11
11
  const cleanupFn = vi.fn();
12
12
  expect(() => {
13
- disposer.addDisposer(cleanupFn);
13
+ disposer.add(cleanupFn);
14
14
  }).not.toThrow();
15
15
  });
16
16
  it('应该能够添加多个清理函数', async () => {
17
17
  const cleanupFn1 = vi.fn();
18
18
  const cleanupFn2 = vi.fn();
19
19
  const cleanupFn3 = vi.fn();
20
- disposer.addDisposer(cleanupFn1);
21
- disposer.addDisposer(cleanupFn2);
22
- disposer.addDisposer(cleanupFn3);
20
+ disposer.add(cleanupFn1);
21
+ disposer.add(cleanupFn2);
22
+ disposer.add(cleanupFn3);
23
23
  await disposer.dispose();
24
24
  expect(cleanupFn1).toHaveBeenCalledOnce();
25
25
  expect(cleanupFn2).toHaveBeenCalledOnce();
@@ -30,10 +30,10 @@ describe('Disposer', () => {
30
30
  const stringFn = vi.fn(() => 'cleanup result');
31
31
  const numberFn = vi.fn(() => 42);
32
32
  const promiseFn = vi.fn(() => Promise.resolve('async cleanup'));
33
- disposer.addDisposer(voidFn);
34
- disposer.addDisposer(stringFn);
35
- disposer.addDisposer(numberFn);
36
- disposer.addDisposer(promiseFn);
33
+ disposer.add(voidFn);
34
+ disposer.add(stringFn);
35
+ disposer.add(numberFn);
36
+ disposer.add(promiseFn);
37
37
  await disposer.dispose();
38
38
  expect(voidFn).toHaveBeenCalled();
39
39
  expect(stringFn).toHaveBeenCalled();
@@ -46,38 +46,39 @@ describe('Disposer', () => {
46
46
  const cleanupFn1 = vi.fn();
47
47
  const cleanupFn2 = vi.fn();
48
48
  const cleanupFn3 = vi.fn();
49
- disposer.addDisposer(cleanupFn1);
50
- disposer.addDisposer(cleanupFn2);
51
- disposer.addDisposer(cleanupFn3);
49
+ disposer.add(cleanupFn1);
50
+ disposer.add(cleanupFn2);
51
+ disposer.add(cleanupFn3);
52
52
  await disposer.dispose();
53
53
  expect(cleanupFn1).toHaveBeenCalledOnce();
54
54
  expect(cleanupFn2).toHaveBeenCalledOnce();
55
55
  expect(cleanupFn3).toHaveBeenCalledOnce();
56
56
  });
57
- it('应该按添加顺序执行清理函数', async () => {
57
+ it('应该按添加顺序的**逆序**(LIFO)执行清理函数', async () => {
58
58
  const executionOrder = [];
59
- disposer.addDisposer(() => executionOrder.push(1));
60
- disposer.addDisposer(() => executionOrder.push(2));
61
- disposer.addDisposer(() => executionOrder.push(3));
59
+ disposer.add(() => executionOrder.push(1));
60
+ disposer.add(() => executionOrder.push(2));
61
+ disposer.add(() => executionOrder.push(3));
62
62
  await disposer.dispose();
63
- expect(executionOrder).toEqual([1, 2, 3]);
63
+ // 后创建的东西依赖先创建的,必须后销毁
64
+ expect(executionOrder).toEqual([3, 2, 1]);
64
65
  });
65
- it('应该按添加顺序执行异步清理函数', async () => {
66
+ it('应该按添加顺序的**逆序**(LIFO)执行异步清理函数', async () => {
66
67
  const executionOrder = [];
67
- disposer.addDisposer(async () => {
68
+ disposer.add(async () => {
68
69
  await Promise.resolve();
69
70
  executionOrder.push(1);
70
71
  });
71
- disposer.addDisposer(async () => {
72
+ disposer.add(async () => {
72
73
  await Promise.resolve();
73
74
  executionOrder.push(2);
74
75
  });
75
- disposer.addDisposer(async () => {
76
+ disposer.add(async () => {
76
77
  await Promise.resolve();
77
78
  executionOrder.push(3);
78
79
  });
79
80
  await disposer.dispose();
80
- expect(executionOrder).toEqual([1, 2, 3]);
81
+ expect(executionOrder).toEqual([3, 2, 1]);
81
82
  });
82
83
  it('应该处理清理函数中的错误', async () => {
83
84
  const workingFn1 = vi.fn();
@@ -87,9 +88,9 @@ describe('Disposer', () => {
87
88
  const workingFn2 = vi.fn();
88
89
  // 模拟 logger.error
89
90
  const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
90
- disposer.addDisposer(workingFn1);
91
- disposer.addDisposer(errorFn);
92
- disposer.addDisposer(workingFn2);
91
+ disposer.add(workingFn1);
92
+ disposer.add(errorFn);
93
+ disposer.add(workingFn2);
93
94
  // dispose 不应该抛出错误
94
95
  await expect(disposer.dispose()).resolves.toBeUndefined();
95
96
  // 所有函数都应该被调用
@@ -97,12 +98,12 @@ describe('Disposer', () => {
97
98
  expect(errorFn).toHaveBeenCalled();
98
99
  expect(workingFn2).toHaveBeenCalled();
99
100
  // 错误应该被记录
100
- expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', expect.any(Error));
101
+ expect(loggerErrorSpy).toHaveBeenCalledWith({ error: expect.any(Error) }, '[disposer] cleanup failed');
101
102
  loggerErrorSpy.mockRestore();
102
103
  });
103
104
  it('应该在执行后清空清理函数列表', async () => {
104
105
  const cleanupFn = vi.fn();
105
- disposer.addDisposer(cleanupFn);
106
+ disposer.add(cleanupFn);
106
107
  await disposer.dispose();
107
108
  expect(cleanupFn).toHaveBeenCalledOnce();
108
109
  // 再次调用 dispose 不应该执行任何函数
@@ -112,9 +113,9 @@ describe('Disposer', () => {
112
113
  it('应该能够在清空后重新添加清理函数', async () => {
113
114
  const firstCleanup = vi.fn();
114
115
  const secondCleanup = vi.fn();
115
- disposer.addDisposer(firstCleanup);
116
+ disposer.add(firstCleanup);
116
117
  await disposer.dispose();
117
- disposer.addDisposer(secondCleanup);
118
+ disposer.add(secondCleanup);
118
119
  await disposer.dispose();
119
120
  expect(firstCleanup).toHaveBeenCalledOnce();
120
121
  expect(secondCleanup).toHaveBeenCalledOnce();
@@ -124,19 +125,67 @@ describe('Disposer', () => {
124
125
  });
125
126
  it('应该能够多次调用 dispose', async () => {
126
127
  const cleanupFn = vi.fn();
127
- disposer.addDisposer(cleanupFn);
128
+ disposer.add(cleanupFn);
128
129
  await disposer.dispose();
129
130
  await disposer.dispose();
130
131
  await disposer.dispose();
131
132
  expect(cleanupFn).toHaveBeenCalledOnce();
132
133
  });
133
134
  });
135
+ describe('日志注入', () => {
136
+ it('应该在传入 log 时使用注入的日志出口,不再走 base logger', async () => {
137
+ const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
138
+ const logError = vi.fn();
139
+ const error = new Error('Injected log error');
140
+ const injected = new Disposer({ log: { error: logError }, label: 'resources' });
141
+ injected.add(() => {
142
+ throw error;
143
+ });
144
+ await expect(injected.dispose()).resolves.toBeUndefined();
145
+ expect(logError).toHaveBeenCalledOnce();
146
+ expect(logError).toHaveBeenCalledWith({ error }, '[resources] cleanup failed');
147
+ expect(loggerErrorSpy).not.toHaveBeenCalled();
148
+ loggerErrorSpy.mockRestore();
149
+ });
150
+ it('标签应该出现在消息里(注入 log 且自定义 label)', async () => {
151
+ const logError = vi.fn();
152
+ const injected = new Disposer({ log: { error: logError }, label: 'middlewares' });
153
+ injected.add(() => {
154
+ throw new Error('boom');
155
+ });
156
+ await injected.dispose();
157
+ expect(logError).toHaveBeenCalledWith({ error: expect.any(Error) }, '[middlewares] cleanup failed');
158
+ });
159
+ it('未传 log 时应该回退到 base logger', async () => {
160
+ const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
161
+ const error = new Error('Fallback error');
162
+ const noLog = new Disposer({ label: 'routers' });
163
+ noLog.add(() => {
164
+ throw error;
165
+ });
166
+ await noLog.dispose();
167
+ expect(loggerErrorSpy).toHaveBeenCalledWith({ error }, '[routers] cleanup failed');
168
+ loggerErrorSpy.mockRestore();
169
+ });
170
+ it('传入 log 时单个失败仍不阻断后续,且全部走注入出口', async () => {
171
+ const logError = vi.fn();
172
+ const first = vi.fn();
173
+ const injected = new Disposer({ log: { error: logError }, label: 'services' });
174
+ injected.add(first);
175
+ injected.add(() => {
176
+ throw new Error('boom');
177
+ });
178
+ await injected.dispose();
179
+ expect(first).toHaveBeenCalledOnce();
180
+ expect(logError).toHaveBeenCalledOnce();
181
+ });
182
+ });
134
183
  describe('实际使用场景', () => {
135
184
  it('应该能够清理事件监听器', async () => {
136
185
  const removeEventListener = vi.fn();
137
186
  const unsubscribe = vi.fn();
138
- disposer.addDisposer(removeEventListener);
139
- disposer.addDisposer(unsubscribe);
187
+ disposer.add(removeEventListener);
188
+ disposer.add(unsubscribe);
140
189
  await disposer.dispose();
141
190
  expect(removeEventListener).toHaveBeenCalled();
142
191
  expect(unsubscribe).toHaveBeenCalled();
@@ -144,8 +193,8 @@ describe('Disposer', () => {
144
193
  it('应该能够清理定时器', async () => {
145
194
  const clearTimeout = vi.fn();
146
195
  const clearInterval = vi.fn();
147
- disposer.addDisposer(() => clearTimeout(123));
148
- disposer.addDisposer(() => clearInterval(456));
196
+ disposer.add(() => clearTimeout(123));
197
+ disposer.add(() => clearInterval(456));
149
198
  await disposer.dispose();
150
199
  expect(clearTimeout).toHaveBeenCalledWith(123);
151
200
  expect(clearInterval).toHaveBeenCalledWith(456);
@@ -153,8 +202,8 @@ describe('Disposer', () => {
153
202
  it('应该能够清理资源连接', async () => {
154
203
  const closeConnection = vi.fn();
155
204
  const releaseResource = vi.fn();
156
- disposer.addDisposer(() => closeConnection());
157
- disposer.addDisposer(() => releaseResource());
205
+ disposer.add(() => closeConnection());
206
+ disposer.add(() => releaseResource());
158
207
  await disposer.dispose();
159
208
  expect(closeConnection).toHaveBeenCalled();
160
209
  expect(releaseResource).toHaveBeenCalled();
@@ -167,9 +216,9 @@ describe('Disposer', () => {
167
216
  };
168
217
  const offFunction1 = mockEventEmitter.on('event1', vi.fn());
169
218
  const offFunction2 = mockEventEmitter.on('event2', vi.fn());
170
- disposer.addDisposer(offFunction1);
171
- disposer.addDisposer(offFunction2);
172
- disposer.addDisposer(() => mockEventEmitter.cleanup());
219
+ disposer.add(offFunction1);
220
+ disposer.add(offFunction2);
221
+ disposer.add(() => mockEventEmitter.cleanup());
173
222
  await disposer.dispose();
174
223
  expect(offFunction1).toHaveBeenCalled();
175
224
  expect(offFunction2).toHaveBeenCalled();
@@ -180,22 +229,22 @@ describe('Disposer', () => {
180
229
  it('应该捕获并记录同步错误', async () => {
181
230
  const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
182
231
  const error = new Error('Sync error');
183
- disposer.addDisposer(() => {
232
+ disposer.add(() => {
184
233
  throw error;
185
234
  });
186
235
  await disposer.dispose();
187
- expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', error);
236
+ expect(loggerErrorSpy).toHaveBeenCalledWith({ error }, '[disposer] cleanup failed');
188
237
  loggerErrorSpy.mockRestore();
189
238
  });
190
239
  it('应该处理不同类型的错误', async () => {
191
240
  const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
192
- disposer.addDisposer(() => {
241
+ disposer.add(() => {
193
242
  throw new TypeError('Type error');
194
243
  });
195
- disposer.addDisposer(() => {
244
+ disposer.add(() => {
196
245
  throw 'String error';
197
246
  });
198
- disposer.addDisposer(() => {
247
+ disposer.add(() => {
199
248
  throw { message: 'Object error' };
200
249
  });
201
250
  await disposer.dispose();
@@ -205,11 +254,11 @@ describe('Disposer', () => {
205
254
  it('应该捕获并记录异步错误', async () => {
206
255
  const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
207
256
  const asyncError = new Error('Async error');
208
- disposer.addDisposer(async () => {
257
+ disposer.add(async () => {
209
258
  throw asyncError;
210
259
  });
211
260
  await disposer.dispose();
212
- expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', asyncError);
261
+ expect(loggerErrorSpy).toHaveBeenCalledWith({ error: asyncError }, '[disposer] cleanup failed');
213
262
  loggerErrorSpy.mockRestore();
214
263
  });
215
264
  });
@@ -2,6 +2,4 @@
2
2
  * 递归合并对象,只针对对象类型的属性进行合并,其他类型后者覆盖前者
3
3
  */
4
4
  export declare function objectMerge<T extends object>(target: T, ...rest: Partial<T>[]): T;
5
- export declare function ObjectKeys<T extends object>(obj: T): (keyof T)[];
6
- export declare function ObjectValues<T extends object>(obj: T): T[keyof T][];
7
5
  //# sourceMappingURL=object.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../../source/object/object.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAqBjF;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,CAEhE;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAEnE"}
1
+ {"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../../source/object/object.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAqBjF"}
@@ -22,9 +22,3 @@ export function objectMerge(target, ...rest) {
22
22
  }
23
23
  return target;
24
24
  }
25
- export function ObjectKeys(obj) {
26
- return Object.keys(obj);
27
- }
28
- export function ObjectValues(obj) {
29
- return Object.values(obj);
30
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taicode/common-base",
3
- "version": "4.0.0-beta.1",
3
+ "version": "4.0.0-beta.10",
4
4
  "author": "Alain",
5
5
  "license": "ISC",
6
6
  "description": "",
@@ -39,7 +39,7 @@
39
39
  "test:ci": "vitest run",
40
40
  "lint": "eslint && tsc --noEmit",
41
41
  "typecheck": "tsc --noEmit",
42
- "build": "tsc -p tsconfig.json",
42
+ "build": "rm -rf output && tsc -p tsconfig.json",
43
43
  "dev": "tsc -p tsconfig.json --watch"
44
44
  }
45
45
  }