@taicode/common-base 4.0.0-beta.3 → 4.0.0-beta.5

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,6 +1,24 @@
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;
21
+ constructor(opts?: DisposerOptions);
4
22
  add(cleanupFn: CleanupFunction): void;
5
23
  dispose(): Promise<void>;
6
24
  }
@@ -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,GAAG,CAAC,SAAS,EAAE,eAAe,GAAG,IAAI;IAK/B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAa/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,6 +1,10 @@
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
9
  add(cleanupFn) {
6
10
  this.cleanupFunctions.push(cleanupFn);
@@ -15,7 +19,10 @@ export class Disposer {
15
19
  await cleanupFn();
16
20
  }
17
21
  catch (error) {
18
- 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`);
19
26
  }
20
27
  }
21
28
  }
@@ -98,7 +98,7 @@ describe('Disposer', () => {
98
98
  expect(errorFn).toHaveBeenCalled();
99
99
  expect(workingFn2).toHaveBeenCalled();
100
100
  // 错误应该被记录
101
- expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', expect.any(Error));
101
+ expect(loggerErrorSpy).toHaveBeenCalledWith({ error: expect.any(Error) }, '[disposer] cleanup failed');
102
102
  loggerErrorSpy.mockRestore();
103
103
  });
104
104
  it('应该在执行后清空清理函数列表', async () => {
@@ -132,6 +132,54 @@ describe('Disposer', () => {
132
132
  expect(cleanupFn).toHaveBeenCalledOnce();
133
133
  });
134
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
+ });
135
183
  describe('实际使用场景', () => {
136
184
  it('应该能够清理事件监听器', async () => {
137
185
  const removeEventListener = vi.fn();
@@ -185,7 +233,7 @@ describe('Disposer', () => {
185
233
  throw error;
186
234
  });
187
235
  await disposer.dispose();
188
- expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', error);
236
+ expect(loggerErrorSpy).toHaveBeenCalledWith({ error }, '[disposer] cleanup failed');
189
237
  loggerErrorSpy.mockRestore();
190
238
  });
191
239
  it('应该处理不同类型的错误', async () => {
@@ -210,7 +258,7 @@ describe('Disposer', () => {
210
258
  throw asyncError;
211
259
  });
212
260
  await disposer.dispose();
213
- expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', asyncError);
261
+ expect(loggerErrorSpy).toHaveBeenCalledWith({ error: asyncError }, '[disposer] cleanup failed');
214
262
  loggerErrorSpy.mockRestore();
215
263
  });
216
264
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taicode/common-base",
3
- "version": "4.0.0-beta.3",
3
+ "version": "4.0.0-beta.5",
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
  }