@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;
|
|
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
|
-
|
|
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(
|
|
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('
|
|
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('
|
|
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
|
+
"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
|
}
|