@taicode/common-base 3.1.0 → 3.2.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.
@@ -13,6 +13,7 @@ export interface CatchSuccess<T> {
13
13
  */
14
14
  export interface CatchFailure<E> {
15
15
  error: E;
16
+ context: unknown;
16
17
  }
17
18
  /**
18
19
  * 捕获结果联合类型,使用判别联合类型以支持类型守卫
@@ -1 +1 @@
1
- {"version":3,"file":"catch.d.ts","sourceRoot":"","sources":["../../source/catch/catch.ts"],"names":[],"mappings":"AAMA,KAAK,SAAS,CAAC,CAAC,SAAS,MAAM,OAAO,IAAI,CAAC,SAAS,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAExH;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAA;CACT;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAA;CACT;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG;IAC9E;;;;OAIG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,IAAI,YAAY,CAAC,CAAC,CAAC,CAAA;CACpD,CAAA;AA0BD;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AAChG,wBAAsB,OAAO,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"catch.d.ts","sourceRoot":"","sources":["../../source/catch/catch.ts"],"names":[],"mappings":"AAMA,KAAK,SAAS,CAAC,CAAC,SAAS,MAAM,OAAO,IAAI,CAAC,SAAS,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,MAAM,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAExH;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAA;CACT;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAA;IACR,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG;IAC9E;;;;OAIG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,IAAI,YAAY,CAAC,CAAC,CAAC,CAAA;CACpD,CAAA;AA0BD;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AAChG,wBAAsB,OAAO,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA"}
@@ -1,6 +1,8 @@
1
+ type CleanupFunction = () => unknown | Promise<unknown>;
1
2
  export declare class Disposer {
2
3
  private cleanupFunctions;
3
- addDisposer(cleanupFn: () => unknown): void;
4
- dispose(): void;
4
+ addDisposer(cleanupFn: CleanupFunction): void;
5
+ dispose(): Promise<void>;
5
6
  }
7
+ export {};
6
8
  //# sourceMappingURL=disposer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"disposer.d.ts","sourceRoot":"","sources":["../../source/disposer/disposer.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAQ;IACnB,OAAO,CAAC,gBAAgB,CAAyB;IAGjD,WAAW,CAAC,SAAS,EAAE,MAAM,OAAO,GAAG,IAAI;IAK3C,OAAO,IAAI,IAAI;CAUhB"}
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"}
@@ -6,15 +6,16 @@ export class Disposer {
6
6
  this.cleanupFunctions.push(cleanupFn);
7
7
  }
8
8
  // 执行所有清理函数
9
- dispose() {
10
- for (const cleanupFn of this.cleanupFunctions) {
9
+ async dispose() {
10
+ const cleanupFunctions = this.cleanupFunctions;
11
+ this.cleanupFunctions = [];
12
+ for (const cleanupFn of cleanupFunctions) {
11
13
  try {
12
- cleanupFn();
14
+ await cleanupFn();
13
15
  }
14
16
  catch (error) {
15
17
  logger.error('Error during cleanup:', error);
16
18
  }
17
19
  }
18
- this.cleanupFunctions = []; // 清空清理函数列表
19
20
  }
20
21
  }
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { Disposer } from './disposer.js';
3
+ import { logger } from '../logger/index.js';
3
4
  describe('Disposer', () => {
4
5
  let disposer;
5
6
  beforeEach(() => {
@@ -12,19 +13,19 @@ describe('Disposer', () => {
12
13
  disposer.addDisposer(cleanupFn);
13
14
  }).not.toThrow();
14
15
  });
15
- it('应该能够添加多个清理函数', () => {
16
+ it('应该能够添加多个清理函数', async () => {
16
17
  const cleanupFn1 = vi.fn();
17
18
  const cleanupFn2 = vi.fn();
18
19
  const cleanupFn3 = vi.fn();
19
20
  disposer.addDisposer(cleanupFn1);
20
21
  disposer.addDisposer(cleanupFn2);
21
22
  disposer.addDisposer(cleanupFn3);
22
- disposer.dispose();
23
+ await disposer.dispose();
23
24
  expect(cleanupFn1).toHaveBeenCalledOnce();
24
25
  expect(cleanupFn2).toHaveBeenCalledOnce();
25
26
  expect(cleanupFn3).toHaveBeenCalledOnce();
26
27
  });
27
- it('应该能够添加返回不同类型的清理函数', () => {
28
+ it('应该能够添加返回不同类型的清理函数', async () => {
28
29
  const voidFn = vi.fn(() => { });
29
30
  const stringFn = vi.fn(() => 'cleanup result');
30
31
  const numberFn = vi.fn(() => 42);
@@ -33,7 +34,7 @@ describe('Disposer', () => {
33
34
  disposer.addDisposer(stringFn);
34
35
  disposer.addDisposer(numberFn);
35
36
  disposer.addDisposer(promiseFn);
36
- disposer.dispose();
37
+ await disposer.dispose();
37
38
  expect(voidFn).toHaveBeenCalled();
38
39
  expect(stringFn).toHaveBeenCalled();
39
40
  expect(numberFn).toHaveBeenCalled();
@@ -41,111 +42,124 @@ describe('Disposer', () => {
41
42
  });
42
43
  });
43
44
  describe('dispose', () => {
44
- it('应该执行所有清理函数', () => {
45
+ it('应该执行所有清理函数', async () => {
45
46
  const cleanupFn1 = vi.fn();
46
47
  const cleanupFn2 = vi.fn();
47
48
  const cleanupFn3 = vi.fn();
48
49
  disposer.addDisposer(cleanupFn1);
49
50
  disposer.addDisposer(cleanupFn2);
50
51
  disposer.addDisposer(cleanupFn3);
51
- disposer.dispose();
52
+ await disposer.dispose();
52
53
  expect(cleanupFn1).toHaveBeenCalledOnce();
53
54
  expect(cleanupFn2).toHaveBeenCalledOnce();
54
55
  expect(cleanupFn3).toHaveBeenCalledOnce();
55
56
  });
56
- it('应该按添加顺序执行清理函数', () => {
57
+ it('应该按添加顺序执行清理函数', async () => {
57
58
  const executionOrder = [];
58
59
  disposer.addDisposer(() => executionOrder.push(1));
59
60
  disposer.addDisposer(() => executionOrder.push(2));
60
61
  disposer.addDisposer(() => executionOrder.push(3));
61
- disposer.dispose();
62
+ await disposer.dispose();
62
63
  expect(executionOrder).toEqual([1, 2, 3]);
63
64
  });
64
- it('应该处理清理函数中的错误', () => {
65
+ it('应该按添加顺序执行异步清理函数', async () => {
66
+ const executionOrder = [];
67
+ disposer.addDisposer(async () => {
68
+ await Promise.resolve();
69
+ executionOrder.push(1);
70
+ });
71
+ disposer.addDisposer(async () => {
72
+ await Promise.resolve();
73
+ executionOrder.push(2);
74
+ });
75
+ disposer.addDisposer(async () => {
76
+ await Promise.resolve();
77
+ executionOrder.push(3);
78
+ });
79
+ await disposer.dispose();
80
+ expect(executionOrder).toEqual([1, 2, 3]);
81
+ });
82
+ it('应该处理清理函数中的错误', async () => {
65
83
  const workingFn1 = vi.fn();
66
84
  const errorFn = vi.fn(() => {
67
85
  throw new Error('Cleanup error');
68
86
  });
69
87
  const workingFn2 = vi.fn();
70
- // 模拟 console.error
71
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
88
+ // 模拟 logger.error
89
+ const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
72
90
  disposer.addDisposer(workingFn1);
73
91
  disposer.addDisposer(errorFn);
74
92
  disposer.addDisposer(workingFn2);
75
93
  // dispose 不应该抛出错误
76
- expect(() => {
77
- disposer.dispose();
78
- }).not.toThrow();
94
+ await expect(disposer.dispose()).resolves.toBeUndefined();
79
95
  // 所有函数都应该被调用
80
96
  expect(workingFn1).toHaveBeenCalled();
81
97
  expect(errorFn).toHaveBeenCalled();
82
98
  expect(workingFn2).toHaveBeenCalled();
83
99
  // 错误应该被记录
84
- expect(consoleSpy).toHaveBeenCalledWith('Error during cleanup:', expect.any(Error));
85
- consoleSpy.mockRestore();
100
+ expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', expect.any(Error));
101
+ loggerErrorSpy.mockRestore();
86
102
  });
87
- it('应该在执行后清空清理函数列表', () => {
103
+ it('应该在执行后清空清理函数列表', async () => {
88
104
  const cleanupFn = vi.fn();
89
105
  disposer.addDisposer(cleanupFn);
90
- disposer.dispose();
106
+ await disposer.dispose();
91
107
  expect(cleanupFn).toHaveBeenCalledOnce();
92
108
  // 再次调用 dispose 不应该执行任何函数
93
- disposer.dispose();
109
+ await disposer.dispose();
94
110
  expect(cleanupFn).toHaveBeenCalledOnce();
95
111
  });
96
- it('应该能够在清空后重新添加清理函数', () => {
112
+ it('应该能够在清空后重新添加清理函数', async () => {
97
113
  const firstCleanup = vi.fn();
98
114
  const secondCleanup = vi.fn();
99
115
  disposer.addDisposer(firstCleanup);
100
- disposer.dispose();
116
+ await disposer.dispose();
101
117
  disposer.addDisposer(secondCleanup);
102
- disposer.dispose();
118
+ await disposer.dispose();
103
119
  expect(firstCleanup).toHaveBeenCalledOnce();
104
120
  expect(secondCleanup).toHaveBeenCalledOnce();
105
121
  });
106
- it('应该在没有清理函数时安全执行', () => {
107
- expect(() => {
108
- disposer.dispose();
109
- }).not.toThrow();
122
+ it('应该在没有清理函数时安全执行', async () => {
123
+ await expect(disposer.dispose()).resolves.toBeUndefined();
110
124
  });
111
- it('应该能够多次调用 dispose', () => {
125
+ it('应该能够多次调用 dispose', async () => {
112
126
  const cleanupFn = vi.fn();
113
127
  disposer.addDisposer(cleanupFn);
114
- disposer.dispose();
115
- disposer.dispose();
116
- disposer.dispose();
128
+ await disposer.dispose();
129
+ await disposer.dispose();
130
+ await disposer.dispose();
117
131
  expect(cleanupFn).toHaveBeenCalledOnce();
118
132
  });
119
133
  });
120
134
  describe('实际使用场景', () => {
121
- it('应该能够清理事件监听器', () => {
135
+ it('应该能够清理事件监听器', async () => {
122
136
  const removeEventListener = vi.fn();
123
137
  const unsubscribe = vi.fn();
124
138
  disposer.addDisposer(removeEventListener);
125
139
  disposer.addDisposer(unsubscribe);
126
- disposer.dispose();
140
+ await disposer.dispose();
127
141
  expect(removeEventListener).toHaveBeenCalled();
128
142
  expect(unsubscribe).toHaveBeenCalled();
129
143
  });
130
- it('应该能够清理定时器', () => {
144
+ it('应该能够清理定时器', async () => {
131
145
  const clearTimeout = vi.fn();
132
146
  const clearInterval = vi.fn();
133
147
  disposer.addDisposer(() => clearTimeout(123));
134
148
  disposer.addDisposer(() => clearInterval(456));
135
- disposer.dispose();
149
+ await disposer.dispose();
136
150
  expect(clearTimeout).toHaveBeenCalledWith(123);
137
151
  expect(clearInterval).toHaveBeenCalledWith(456);
138
152
  });
139
- it('应该能够清理资源连接', () => {
153
+ it('应该能够清理资源连接', async () => {
140
154
  const closeConnection = vi.fn();
141
155
  const releaseResource = vi.fn();
142
156
  disposer.addDisposer(() => closeConnection());
143
157
  disposer.addDisposer(() => releaseResource());
144
- disposer.dispose();
158
+ await disposer.dispose();
145
159
  expect(closeConnection).toHaveBeenCalled();
146
160
  expect(releaseResource).toHaveBeenCalled();
147
161
  });
148
- it('应该与 EventEmitter 配合使用', () => {
162
+ it('应该与 EventEmitter 配合使用', async () => {
149
163
  // 模拟 EventEmitter 的使用
150
164
  const mockEventEmitter = {
151
165
  on: vi.fn().mockReturnValue(vi.fn()), // 返回 off 函数
@@ -156,25 +170,25 @@ describe('Disposer', () => {
156
170
  disposer.addDisposer(offFunction1);
157
171
  disposer.addDisposer(offFunction2);
158
172
  disposer.addDisposer(() => mockEventEmitter.cleanup());
159
- disposer.dispose();
173
+ await disposer.dispose();
160
174
  expect(offFunction1).toHaveBeenCalled();
161
175
  expect(offFunction2).toHaveBeenCalled();
162
176
  expect(mockEventEmitter.cleanup).toHaveBeenCalled();
163
177
  });
164
178
  });
165
179
  describe('错误处理', () => {
166
- it('应该捕获并记录同步错误', () => {
167
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
180
+ it('应该捕获并记录同步错误', async () => {
181
+ const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
168
182
  const error = new Error('Sync error');
169
183
  disposer.addDisposer(() => {
170
184
  throw error;
171
185
  });
172
- disposer.dispose();
173
- expect(consoleSpy).toHaveBeenCalledWith('Error during cleanup:', error);
174
- consoleSpy.mockRestore();
186
+ await disposer.dispose();
187
+ expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', error);
188
+ loggerErrorSpy.mockRestore();
175
189
  });
176
- it('应该处理不同类型的错误', () => {
177
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
190
+ it('应该处理不同类型的错误', async () => {
191
+ const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
178
192
  disposer.addDisposer(() => {
179
193
  throw new TypeError('Type error');
180
194
  });
@@ -184,9 +198,19 @@ describe('Disposer', () => {
184
198
  disposer.addDisposer(() => {
185
199
  throw { message: 'Object error' };
186
200
  });
187
- disposer.dispose();
188
- expect(consoleSpy).toHaveBeenCalledTimes(3);
189
- consoleSpy.mockRestore();
201
+ await disposer.dispose();
202
+ expect(loggerErrorSpy).toHaveBeenCalledTimes(3);
203
+ loggerErrorSpy.mockRestore();
204
+ });
205
+ it('应该捕获并记录异步错误', async () => {
206
+ const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
207
+ const asyncError = new Error('Async error');
208
+ disposer.addDisposer(async () => {
209
+ throw asyncError;
210
+ });
211
+ await disposer.dispose();
212
+ expect(loggerErrorSpy).toHaveBeenCalledWith('Error during cleanup:', asyncError);
213
+ loggerErrorSpy.mockRestore();
190
214
  });
191
215
  });
192
216
  });
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
2
  import { FlowQueue } from './flow-queue.js';
3
+ import { logger } from '../logger/index.js';
3
4
  describe('FlowQueue', () => {
4
5
  let flowQueue;
5
6
  let mockProcessor;
@@ -158,8 +159,8 @@ describe('FlowQueue', () => {
158
159
  throw new Error('Callback error');
159
160
  });
160
161
  const normalCallback = vi.fn();
161
- // Mock console.error 来验证错误被正确记录
162
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
162
+ // Mock logger.error 来验证错误被正确记录
163
+ const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => { });
163
164
  mockProcessor = vi.fn().mockResolvedValue(expectedOutput);
164
165
  flowQueue = new FlowQueue({ retry: { maxRetries: 3 } }, mockProcessor);
165
166
  flowQueue.onGenerate(faultyCallback);
@@ -168,8 +169,8 @@ describe('FlowQueue', () => {
168
169
  await new Promise(resolve => setTimeout(resolve, 50));
169
170
  expect(faultyCallback).toHaveBeenCalled();
170
171
  expect(normalCallback).toHaveBeenCalledWith(expectedOutput);
171
- expect(consoleErrorSpy).toHaveBeenCalledWith('Error in onGenerate callback:', expect.any(Error));
172
- consoleErrorSpy.mockRestore();
172
+ expect(loggerErrorSpy).toHaveBeenCalledWith('Error in onGenerate callback:', expect.any(Error));
173
+ loggerErrorSpy.mockRestore();
173
174
  });
174
175
  it('失败的处理不应该触发回调', async () => {
175
176
  const testInput = { id: 1, data: 'test' };
@@ -77,9 +77,10 @@ describe('RingCache', () => {
77
77
  expect(results.map(e => e.data)).toEqual(['apple', 'apricot']);
78
78
  });
79
79
  it('应该支持时间范围搜索', () => {
80
- const now = new Date();
81
- const future = new Date(now.getTime() + 1000);
82
- const results = cache.getByTimeRange(now, future);
80
+ const entries = cache.getAll();
81
+ const start = new Date(entries[0].timestamp.getTime() - 1);
82
+ const end = new Date(entries[entries.length - 1].timestamp.getTime() + 1);
83
+ const results = cache.getByTimeRange(start, end);
83
84
  expect(results.length).toBe(3);
84
85
  });
85
86
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taicode/common-base",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "author": "Alain",
5
5
  "license": "ISC",
6
6
  "description": "",
@@ -22,15 +22,15 @@
22
22
  },
23
23
  "scripts": {
24
24
  "test": "vitest run",
25
- "build": "tsc -p tsconfig.json",
25
+ "build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json",
26
26
  "prepublishOnly": "npm run build",
27
- "dev": "tsc -p tsconfig.json --watch"
27
+ "dev": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json --watch"
28
28
  },
29
29
  "files": [
30
30
  "output"
31
31
  ],
32
32
  "devDependencies": {
33
33
  "vitest": "^3.2.4",
34
- "mobx": "^6.0.0"
34
+ "mobx": "^6.15.0"
35
35
  }
36
36
  }
@@ -1,2 +0,0 @@
1
- export * from './scheduler';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/scheduler/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA"}
@@ -1 +0,0 @@
1
- export * from './scheduler';
@@ -1,222 +0,0 @@
1
- import { EventEmitter } from '../event-emitter';
2
- import { type CatchResult } from '../catch';
3
- /**
4
- * 定时任务配置
5
- */
6
- export interface ScheduledTaskConfig {
7
- /**
8
- * 任务唯一标识
9
- */
10
- id: string;
11
- /**
12
- * 任务执行间隔(秒),必须是 10 的倍数
13
- */
14
- interval: number;
15
- /**
16
- * 任务处理函数
17
- */
18
- handler: () => Promise<void> | void;
19
- /**
20
- * 最大重试次数,默认 3
21
- */
22
- maxRetries?: number;
23
- /**
24
- * 重试间隔(毫秒),默认 1000
25
- */
26
- retryDelay?: number;
27
- /**
28
- * 是否在启动时立即执行一次,默认 false
29
- */
30
- immediate?: boolean;
31
- /**
32
- * 是否启用,默认 true
33
- */
34
- enabled?: boolean;
35
- }
36
- /**
37
- * 任务执行记录
38
- */
39
- export interface TaskExecution {
40
- taskId: string;
41
- startTime: number;
42
- endTime?: number;
43
- success: boolean;
44
- error?: Error;
45
- retryCount: number;
46
- }
47
- /**
48
- * 调度器事件
49
- */
50
- export interface SchedulerEvents {
51
- 'task:start': {
52
- taskId: string;
53
- };
54
- 'task:success': {
55
- taskId: string;
56
- duration: number;
57
- };
58
- 'task:error': {
59
- taskId: string;
60
- error: Error;
61
- retryCount: number;
62
- };
63
- 'task:retry': {
64
- taskId: string;
65
- retryCount: number;
66
- maxRetries: number;
67
- };
68
- 'task:failed': {
69
- taskId: string;
70
- error: Error;
71
- };
72
- 'scheduler:start': void;
73
- 'scheduler:stop': void;
74
- 'scheduler:tick': {
75
- tick: number;
76
- };
77
- }
78
- /**
79
- * 内部任务状态
80
- */
81
- interface TaskState {
82
- config: Required<ScheduledTaskConfig>;
83
- nextTick: number;
84
- running: boolean;
85
- retryCount: number;
86
- lastError?: Error;
87
- executions: TaskExecution[];
88
- }
89
- /**
90
- * 定时任务调度器
91
- *
92
- * 以 10 秒为基本时间单位,支持多个不同间隔的定时任务
93
- *
94
- * @example
95
- * ```ts
96
- * const scheduler = new Scheduler()
97
- *
98
- * // 添加每 30 秒执行一次的任务
99
- * scheduler.addTask({
100
- * id: 'sync-data',
101
- * interval: 30,
102
- * handler: async () => {
103
- * await syncDataToServer()
104
- * },
105
- * maxRetries: 3
106
- * })
107
- *
108
- * // 添加每 60 秒执行一次的任务
109
- * scheduler.addTask({
110
- * id: 'cleanup',
111
- * interval: 60,
112
- * handler: async () => {
113
- * await cleanupTempFiles()
114
- * }
115
- * })
116
- *
117
- * // 启动调度器
118
- * scheduler.start()
119
- *
120
- * // 监听事件
121
- * scheduler.on('task:error', (taskId, error) => {
122
- * console.error(`Task ${taskId} failed:`, error)
123
- * })
124
- *
125
- * // 停止调度器
126
- * scheduler.stop()
127
- * ```
128
- */
129
- export declare class Scheduler extends EventEmitter<SchedulerEvents> {
130
- /**
131
- * 基本时间单位(秒)
132
- */
133
- private static readonly TICK_INTERVAL;
134
- /**
135
- * 当前 tick 计数
136
- */
137
- private currentTick;
138
- /**
139
- * 定时器 ID
140
- */
141
- private timerId;
142
- /**
143
- * 是否正在运行
144
- */
145
- private running;
146
- /**
147
- * 任务状态映射
148
- */
149
- private tasks;
150
- /**
151
- * 添加定时任务
152
- */
153
- addTask(config: ScheduledTaskConfig): void;
154
- /**
155
- * 移除定时任务
156
- */
157
- removeTask(taskId: string): boolean;
158
- /**
159
- * 启用任务
160
- */
161
- enableTask(taskId: string): boolean;
162
- /**
163
- * 禁用任务
164
- */
165
- disableTask(taskId: string): boolean;
166
- /**
167
- * 获取任务状态
168
- */
169
- getTaskState(taskId: string): Readonly<TaskState> | undefined;
170
- /**
171
- * 获取所有任务 ID
172
- */
173
- getTaskIds(): string[];
174
- /**
175
- * 启动调度器
176
- */
177
- start(): void;
178
- /**
179
- * 停止调度器
180
- */
181
- stop(): void;
182
- /**
183
- * 是否正在运行
184
- */
185
- isRunning(): boolean;
186
- /**
187
- * 获取当前 tick
188
- */
189
- getCurrentTick(): number;
190
- /**
191
- * 手动触发任务执行(忽略时间间隔)
192
- */
193
- triggerTask(taskId: string): Promise<CatchResult<void, string>>;
194
- /**
195
- * 执行 tick
196
- */
197
- private tick;
198
- /**
199
- * 执行任务
200
- */
201
- private executeTask;
202
- /**
203
- * 获取任务执行历史
204
- */
205
- getTaskExecutions(taskId: string, limit?: number): TaskExecution[];
206
- /**
207
- * 获取调度器统计信息
208
- */
209
- getStats(): {
210
- running: boolean;
211
- currentTick: number;
212
- totalTasks: number;
213
- enabledTasks: number;
214
- runningTasks: number;
215
- };
216
- /**
217
- * 清理所有任务
218
- */
219
- clear(): void;
220
- }
221
- export {};
222
- //# sourceMappingURL=scheduler.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../source/scheduler/scheduler.ts"],"names":[],"mappings":"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"}
@@ -1,348 +0,0 @@
1
- import { EventEmitter } from '../event-emitter';
2
- import { catchIt } from '../catch';
3
- /**
4
- * 定时任务调度器
5
- *
6
- * 以 10 秒为基本时间单位,支持多个不同间隔的定时任务
7
- *
8
- * @example
9
- * ```ts
10
- * const scheduler = new Scheduler()
11
- *
12
- * // 添加每 30 秒执行一次的任务
13
- * scheduler.addTask({
14
- * id: 'sync-data',
15
- * interval: 30,
16
- * handler: async () => {
17
- * await syncDataToServer()
18
- * },
19
- * maxRetries: 3
20
- * })
21
- *
22
- * // 添加每 60 秒执行一次的任务
23
- * scheduler.addTask({
24
- * id: 'cleanup',
25
- * interval: 60,
26
- * handler: async () => {
27
- * await cleanupTempFiles()
28
- * }
29
- * })
30
- *
31
- * // 启动调度器
32
- * scheduler.start()
33
- *
34
- * // 监听事件
35
- * scheduler.on('task:error', (taskId, error) => {
36
- * console.error(`Task ${taskId} failed:`, error)
37
- * })
38
- *
39
- * // 停止调度器
40
- * scheduler.stop()
41
- * ```
42
- */
43
- export class Scheduler extends EventEmitter {
44
- /**
45
- * 基本时间单位(秒)
46
- */
47
- static TICK_INTERVAL = 10;
48
- /**
49
- * 当前 tick 计数
50
- */
51
- currentTick = 0;
52
- /**
53
- * 定时器 ID
54
- */
55
- timerId = null;
56
- /**
57
- * 是否正在运行
58
- */
59
- running = false;
60
- /**
61
- * 任务状态映射
62
- */
63
- tasks = new Map();
64
- /**
65
- * 添加定时任务
66
- */
67
- addTask(config) {
68
- // 验证参数
69
- if (!config.id) {
70
- throw new Error('[Scheduler] Task id is required');
71
- }
72
- if (!config.interval || config.interval <= 0) {
73
- throw new Error('[Scheduler] Task interval must be positive');
74
- }
75
- if (config.interval % Scheduler.TICK_INTERVAL !== 0) {
76
- throw new Error(`[Scheduler] Task interval must be a multiple of ${Scheduler.TICK_INTERVAL} seconds`);
77
- }
78
- if (!config.handler) {
79
- throw new Error('[Scheduler] Task handler is required');
80
- }
81
- if (this.tasks.has(config.id)) {
82
- throw new Error(`[Scheduler] Task with id "${config.id}" already exists`);
83
- }
84
- // 创建任务状态
85
- const taskState = {
86
- config: {
87
- id: config.id,
88
- interval: config.interval,
89
- handler: config.handler,
90
- maxRetries: config.maxRetries ?? 3,
91
- retryDelay: config.retryDelay ?? 1000,
92
- immediate: config.immediate ?? false,
93
- enabled: config.enabled ?? true,
94
- },
95
- nextTick: config.immediate ? 0 : Math.ceil(config.interval / Scheduler.TICK_INTERVAL),
96
- running: false,
97
- retryCount: 0,
98
- executions: [],
99
- };
100
- this.tasks.set(config.id, taskState);
101
- console.log(`[Scheduler] Task "${config.id}" added (interval: ${config.interval}s)`);
102
- }
103
- /**
104
- * 移除定时任务
105
- */
106
- removeTask(taskId) {
107
- const task = this.tasks.get(taskId);
108
- if (!task) {
109
- return false;
110
- }
111
- if (task.running) {
112
- console.warn(`[Scheduler] Cannot remove running task "${taskId}"`);
113
- return false;
114
- }
115
- this.tasks.delete(taskId);
116
- console.log(`[Scheduler] Task "${taskId}" removed`);
117
- return true;
118
- }
119
- /**
120
- * 启用任务
121
- */
122
- enableTask(taskId) {
123
- const task = this.tasks.get(taskId);
124
- if (!task) {
125
- return false;
126
- }
127
- task.config.enabled = true;
128
- console.log(`[Scheduler] Task "${taskId}" enabled`);
129
- return true;
130
- }
131
- /**
132
- * 禁用任务
133
- */
134
- disableTask(taskId) {
135
- const task = this.tasks.get(taskId);
136
- if (!task) {
137
- return false;
138
- }
139
- task.config.enabled = false;
140
- console.log(`[Scheduler] Task "${taskId}" disabled`);
141
- return true;
142
- }
143
- /**
144
- * 获取任务状态
145
- */
146
- getTaskState(taskId) {
147
- const task = this.tasks.get(taskId);
148
- return task ? { ...task } : undefined;
149
- }
150
- /**
151
- * 获取所有任务 ID
152
- */
153
- getTaskIds() {
154
- return Array.from(this.tasks.keys());
155
- }
156
- /**
157
- * 启动调度器
158
- */
159
- start() {
160
- if (this.running) {
161
- console.warn('[Scheduler] Scheduler is already running');
162
- return;
163
- }
164
- this.running = true;
165
- this.currentTick = 0;
166
- this.emit('scheduler:start', undefined);
167
- console.log('[Scheduler] Scheduler started');
168
- // 立即执行一次 tick
169
- this.tick();
170
- // 设置定时器
171
- this.timerId = setInterval(() => {
172
- this.tick();
173
- }, Scheduler.TICK_INTERVAL * 1000);
174
- }
175
- /**
176
- * 停止调度器
177
- */
178
- stop() {
179
- if (!this.running) {
180
- return;
181
- }
182
- if (this.timerId) {
183
- clearInterval(this.timerId);
184
- this.timerId = null;
185
- }
186
- this.running = false;
187
- this.emit('scheduler:stop', undefined);
188
- console.log('[Scheduler] Scheduler stopped');
189
- }
190
- /**
191
- * 是否正在运行
192
- */
193
- isRunning() {
194
- return this.running;
195
- }
196
- /**
197
- * 获取当前 tick
198
- */
199
- getCurrentTick() {
200
- return this.currentTick;
201
- }
202
- /**
203
- * 手动触发任务执行(忽略时间间隔)
204
- */
205
- async triggerTask(taskId) {
206
- const task = this.tasks.get(taskId);
207
- if (!task) {
208
- return catchIt(async () => {
209
- throw new Error(`Task "${taskId}" not found`);
210
- });
211
- }
212
- return this.executeTask(task);
213
- }
214
- /**
215
- * 执行 tick
216
- */
217
- tick() {
218
- this.currentTick++;
219
- this.emit('scheduler:tick', { tick: this.currentTick });
220
- // 检查需要执行的任务
221
- for (const task of this.tasks.values()) {
222
- if (!task.config.enabled) {
223
- continue;
224
- }
225
- if (task.running) {
226
- continue;
227
- }
228
- // 检查是否到达执行时间
229
- if (this.currentTick >= task.nextTick) {
230
- // 异步执行任务,不阻塞 tick
231
- this.executeTask(task).catch((error) => {
232
- console.error(`[Scheduler] Unexpected error in task "${task.config.id}":`, error);
233
- });
234
- }
235
- }
236
- }
237
- /**
238
- * 执行任务
239
- */
240
- async executeTask(task) {
241
- const { config } = task;
242
- const startTime = Date.now();
243
- task.running = true;
244
- this.emit('task:start', { taskId: config.id });
245
- const execution = {
246
- taskId: config.id,
247
- startTime,
248
- success: false,
249
- retryCount: task.retryCount,
250
- };
251
- const result = await catchIt(async () => config.handler());
252
- const endTime = Date.now();
253
- const duration = endTime - startTime;
254
- execution.endTime = endTime;
255
- if (result.isError()) {
256
- // 执行失败
257
- const error = new Error(String(result.error));
258
- execution.success = false;
259
- execution.error = error;
260
- task.lastError = error;
261
- task.retryCount++;
262
- this.emit('task:error', { taskId: config.id, error, retryCount: task.retryCount });
263
- // 检查是否需要重试
264
- if (task.retryCount <= config.maxRetries) {
265
- this.emit('task:retry', {
266
- taskId: config.id,
267
- retryCount: task.retryCount,
268
- maxRetries: config.maxRetries,
269
- });
270
- console.warn(`[Scheduler] Task "${config.id}" failed, will retry (${task.retryCount}/${config.maxRetries})`);
271
- // 延迟后重试
272
- await new Promise((resolve) => setTimeout(resolve, config.retryDelay));
273
- task.running = false;
274
- return this.executeTask(task);
275
- }
276
- else {
277
- // 超过最大重试次数
278
- this.emit('task:failed', { taskId: config.id, error });
279
- console.error(`[Scheduler] Task "${config.id}" failed after ${config.maxRetries} retries:`, error.message);
280
- // 重置重试计数,计划下次执行
281
- task.retryCount = 0;
282
- task.nextTick = this.currentTick + Math.ceil(config.interval / Scheduler.TICK_INTERVAL);
283
- task.running = false;
284
- task.executions.push(execution);
285
- return result;
286
- }
287
- }
288
- else {
289
- // 执行成功
290
- execution.success = true;
291
- task.retryCount = 0;
292
- task.lastError = undefined;
293
- this.emit('task:success', { taskId: config.id, duration });
294
- // 计划下次执行
295
- task.nextTick = this.currentTick + Math.ceil(config.interval / Scheduler.TICK_INTERVAL);
296
- task.running = false;
297
- task.executions.push(execution);
298
- // 保留最近 100 条执行记录
299
- if (task.executions.length > 100) {
300
- task.executions = task.executions.slice(-100);
301
- }
302
- return result;
303
- }
304
- }
305
- /**
306
- * 获取任务执行历史
307
- */
308
- getTaskExecutions(taskId, limit = 10) {
309
- const task = this.tasks.get(taskId);
310
- if (!task) {
311
- return [];
312
- }
313
- return task.executions.slice(-limit);
314
- }
315
- /**
316
- * 获取调度器统计信息
317
- */
318
- getStats() {
319
- let enabledTasks = 0;
320
- let runningTasks = 0;
321
- for (const task of this.tasks.values()) {
322
- if (task.config.enabled) {
323
- enabledTasks++;
324
- }
325
- if (task.running) {
326
- runningTasks++;
327
- }
328
- }
329
- return {
330
- running: this.running,
331
- currentTick: this.currentTick,
332
- totalTasks: this.tasks.size,
333
- enabledTasks,
334
- runningTasks,
335
- };
336
- }
337
- /**
338
- * 清理所有任务
339
- */
340
- clear() {
341
- if (this.running) {
342
- console.warn('[Scheduler] Cannot clear tasks while scheduler is running');
343
- return;
344
- }
345
- this.tasks.clear();
346
- console.log('[Scheduler] All tasks cleared');
347
- }
348
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=scheduler.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scheduler.test.d.ts","sourceRoot":"","sources":["../../source/scheduler/scheduler.test.ts"],"names":[],"mappings":""}
@@ -1,341 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { Scheduler } from './scheduler';
3
- describe('Scheduler', () => {
4
- let scheduler;
5
- beforeEach(() => {
6
- scheduler = new Scheduler();
7
- vi.useFakeTimers();
8
- });
9
- afterEach(() => {
10
- scheduler.stop();
11
- scheduler.clear();
12
- vi.restoreAllMocks();
13
- vi.useRealTimers();
14
- });
15
- describe('任务管理', () => {
16
- it('应该成功添加任务', () => {
17
- const handler = vi.fn();
18
- scheduler.addTask({
19
- id: 'test-task',
20
- interval: 10,
21
- handler,
22
- });
23
- expect(scheduler.getTaskIds()).toContain('test-task');
24
- });
25
- it('添加任务时 interval 必须是 10 的倍数', () => {
26
- expect(() => {
27
- scheduler.addTask({
28
- id: 'invalid-task',
29
- interval: 15,
30
- handler: vi.fn(),
31
- });
32
- }).toThrow('must be a multiple of 10');
33
- });
34
- it('不应该添加重复 id 的任务', () => {
35
- scheduler.addTask({
36
- id: 'test-task',
37
- interval: 10,
38
- handler: vi.fn(),
39
- });
40
- expect(() => {
41
- scheduler.addTask({
42
- id: 'test-task',
43
- interval: 20,
44
- handler: vi.fn(),
45
- });
46
- }).toThrow('already exists');
47
- });
48
- it('应该成功移除任务', () => {
49
- scheduler.addTask({
50
- id: 'test-task',
51
- interval: 10,
52
- handler: vi.fn(),
53
- });
54
- const removed = scheduler.removeTask('test-task');
55
- expect(removed).toBe(true);
56
- expect(scheduler.getTaskIds()).not.toContain('test-task');
57
- });
58
- it('应该能够启用和禁用任务', () => {
59
- scheduler.addTask({
60
- id: 'test-task',
61
- interval: 10,
62
- handler: vi.fn(),
63
- });
64
- expect(scheduler.disableTask('test-task')).toBe(true);
65
- const state = scheduler.getTaskState('test-task');
66
- expect(state?.config.enabled).toBe(false);
67
- expect(scheduler.enableTask('test-task')).toBe(true);
68
- const state2 = scheduler.getTaskState('test-task');
69
- expect(state2?.config.enabled).toBe(true);
70
- });
71
- });
72
- describe('任务调度', () => {
73
- it('应该按照指定间隔执行任务', async () => {
74
- const handler = vi.fn();
75
- scheduler.addTask({
76
- id: 'test-task',
77
- interval: 10,
78
- handler,
79
- });
80
- scheduler.start();
81
- // 第一次 tick (0s)
82
- await vi.advanceTimersByTimeAsync(10000);
83
- expect(handler).toHaveBeenCalledTimes(1);
84
- // 第二次 tick (10s)
85
- await vi.advanceTimersByTimeAsync(10000);
86
- expect(handler).toHaveBeenCalledTimes(2);
87
- // 第三次 tick (20s)
88
- await vi.advanceTimersByTimeAsync(10000);
89
- expect(handler).toHaveBeenCalledTimes(3);
90
- });
91
- it('应该支持不同间隔的多个任务', async () => {
92
- const handler1 = vi.fn();
93
- const handler2 = vi.fn();
94
- scheduler.addTask({
95
- id: 'task-10s',
96
- interval: 10,
97
- handler: handler1,
98
- });
99
- scheduler.addTask({
100
- id: 'task-30s',
101
- interval: 30,
102
- handler: handler2,
103
- });
104
- scheduler.start();
105
- // 10s
106
- await vi.advanceTimersByTimeAsync(10000);
107
- expect(handler1).toHaveBeenCalledTimes(1);
108
- expect(handler2).toHaveBeenCalledTimes(0);
109
- // 20s
110
- await vi.advanceTimersByTimeAsync(10000);
111
- expect(handler1).toHaveBeenCalledTimes(2);
112
- expect(handler2).toHaveBeenCalledTimes(0);
113
- // 30s
114
- await vi.advanceTimersByTimeAsync(10000);
115
- expect(handler1).toHaveBeenCalledTimes(3);
116
- expect(handler2).toHaveBeenCalledTimes(1);
117
- });
118
- it('immediate 参数应该使任务立即执行', async () => {
119
- const handler = vi.fn();
120
- scheduler.addTask({
121
- id: 'immediate-task',
122
- interval: 10,
123
- handler,
124
- immediate: true,
125
- });
126
- scheduler.start();
127
- // 立即执行
128
- await vi.runAllTimersAsync();
129
- expect(handler).toHaveBeenCalledTimes(1);
130
- });
131
- it('禁用的任务不应该执行', async () => {
132
- const handler = vi.fn();
133
- scheduler.addTask({
134
- id: 'disabled-task',
135
- interval: 10,
136
- handler,
137
- enabled: false,
138
- });
139
- scheduler.start();
140
- await vi.advanceTimersByTimeAsync(20000);
141
- expect(handler).not.toHaveBeenCalled();
142
- });
143
- });
144
- describe('重试机制', () => {
145
- it('失败的任务应该自动重试', async () => {
146
- let attemptCount = 0;
147
- const handler = vi.fn(async () => {
148
- attemptCount++;
149
- if (attemptCount < 3) {
150
- throw new Error('Task failed');
151
- }
152
- });
153
- const errorListener = vi.fn();
154
- const retryListener = vi.fn();
155
- const successListener = vi.fn();
156
- scheduler.on('task:error', errorListener);
157
- scheduler.on('task:retry', retryListener);
158
- scheduler.on('task:success', successListener);
159
- scheduler.addTask({
160
- id: 'retry-task',
161
- interval: 10,
162
- handler,
163
- maxRetries: 3,
164
- retryDelay: 100,
165
- });
166
- scheduler.start();
167
- await vi.advanceTimersByTimeAsync(10000);
168
- await vi.advanceTimersByTimeAsync(500); // 等待重试
169
- expect(handler).toHaveBeenCalledTimes(3);
170
- expect(errorListener).toHaveBeenCalledTimes(2);
171
- expect(retryListener).toHaveBeenCalledTimes(2);
172
- expect(successListener).toHaveBeenCalledTimes(1);
173
- });
174
- it('超过最大重试次数后应该触发 task:failed 事件', async () => {
175
- const handler = vi.fn(async () => {
176
- throw new Error('Always fails');
177
- });
178
- const failedListener = vi.fn();
179
- scheduler.on('task:failed', failedListener);
180
- scheduler.addTask({
181
- id: 'failing-task',
182
- interval: 10,
183
- handler,
184
- maxRetries: 2,
185
- retryDelay: 100,
186
- });
187
- scheduler.start();
188
- await vi.advanceTimersByTimeAsync(10000);
189
- await vi.advanceTimersByTimeAsync(500); // 等待重试
190
- expect(handler).toHaveBeenCalledTimes(3); // 1 次初始 + 2 次重试
191
- expect(failedListener).toHaveBeenCalledTimes(1);
192
- });
193
- it('失败后应该在下个周期重新执行', async () => {
194
- let shouldFail = true;
195
- const handler = vi.fn(async () => {
196
- if (shouldFail) {
197
- throw new Error('First execution fails');
198
- }
199
- });
200
- scheduler.addTask({
201
- id: 'recover-task',
202
- interval: 10,
203
- handler,
204
- maxRetries: 0, // 不重试
205
- });
206
- scheduler.start();
207
- // 第一次执行 - 失败
208
- await vi.advanceTimersByTimeAsync(10000);
209
- expect(handler).toHaveBeenCalledTimes(1);
210
- shouldFail = false;
211
- // 第二次执行 - 成功
212
- await vi.advanceTimersByTimeAsync(10000);
213
- expect(handler).toHaveBeenCalledTimes(2);
214
- });
215
- });
216
- describe('事件系统', () => {
217
- it('应该触发 scheduler:start 事件', () => {
218
- const listener = vi.fn();
219
- scheduler.on('scheduler:start', listener);
220
- scheduler.start();
221
- expect(listener).toHaveBeenCalledTimes(1);
222
- });
223
- it('应该触发 scheduler:stop 事件', () => {
224
- const listener = vi.fn();
225
- scheduler.on('scheduler:stop', listener);
226
- scheduler.start();
227
- scheduler.stop();
228
- expect(listener).toHaveBeenCalledTimes(1);
229
- });
230
- it('应该触发 scheduler:tick 事件', async () => {
231
- const listener = vi.fn();
232
- scheduler.on('scheduler:tick', listener);
233
- scheduler.start();
234
- await vi.advanceTimersByTimeAsync(10000);
235
- expect(listener).toHaveBeenCalledWith({ tick: 1 });
236
- await vi.advanceTimersByTimeAsync(10000);
237
- expect(listener).toHaveBeenCalledWith({ tick: 2 });
238
- });
239
- it('应该触发 task:start 和 task:success 事件', async () => {
240
- const startListener = vi.fn();
241
- const successListener = vi.fn();
242
- scheduler.on('task:start', startListener);
243
- scheduler.on('task:success', successListener);
244
- scheduler.addTask({
245
- id: 'success-task',
246
- interval: 10,
247
- handler: vi.fn(),
248
- });
249
- scheduler.start();
250
- await vi.advanceTimersByTimeAsync(10000);
251
- expect(startListener).toHaveBeenCalledWith({ taskId: 'success-task' });
252
- expect(successListener).toHaveBeenCalledWith({
253
- taskId: 'success-task',
254
- duration: expect.any(Number)
255
- });
256
- });
257
- });
258
- describe('手动触发', () => {
259
- it('应该能够手动触发任务执行', async () => {
260
- const handler = vi.fn();
261
- scheduler.addTask({
262
- id: 'manual-task',
263
- interval: 60,
264
- handler,
265
- });
266
- const result = await scheduler.triggerTask('manual-task');
267
- expect(result.isError()).toBe(false);
268
- expect(handler).toHaveBeenCalledTimes(1);
269
- });
270
- it('手动触发不存在的任务应该返回错误', async () => {
271
- const result = await scheduler.triggerTask('non-existent');
272
- expect(result.isError()).toBe(true);
273
- });
274
- });
275
- describe('统计信息', () => {
276
- it('应该正确返回统计信息', () => {
277
- scheduler.addTask({
278
- id: 'task-1',
279
- interval: 10,
280
- handler: vi.fn(),
281
- });
282
- scheduler.addTask({
283
- id: 'task-2',
284
- interval: 20,
285
- handler: vi.fn(),
286
- enabled: false,
287
- });
288
- const stats = scheduler.getStats();
289
- expect(stats.totalTasks).toBe(2);
290
- expect(stats.enabledTasks).toBe(1);
291
- expect(stats.running).toBe(false);
292
- });
293
- it('应该记录任务执行历史', async () => {
294
- const handler = vi.fn();
295
- scheduler.addTask({
296
- id: 'history-task',
297
- interval: 10,
298
- handler,
299
- });
300
- scheduler.start();
301
- await vi.advanceTimersByTimeAsync(30000); // 执行 3 次
302
- const executions = scheduler.getTaskExecutions('history-task', 10);
303
- expect(executions.length).toBe(3);
304
- expect(executions.every((e) => e.success)).toBe(true);
305
- });
306
- });
307
- describe('边界情况', () => {
308
- it('重复启动应该被忽略', () => {
309
- scheduler.start();
310
- scheduler.start();
311
- expect(scheduler.isRunning()).toBe(true);
312
- });
313
- it('停止未启动的调度器应该正常处理', () => {
314
- expect(() => scheduler.stop()).not.toThrow();
315
- });
316
- it('不应该移除正在运行的任务', async () => {
317
- const handler = vi.fn(async () => {
318
- await new Promise((resolve) => setTimeout(resolve, 100));
319
- });
320
- scheduler.addTask({
321
- id: 'running-task',
322
- interval: 10,
323
- handler,
324
- });
325
- scheduler.start();
326
- await vi.advanceTimersByTimeAsync(10000);
327
- const removed = scheduler.removeTask('running-task');
328
- expect(removed).toBe(false);
329
- });
330
- it('运行中不应该清空任务', () => {
331
- scheduler.addTask({
332
- id: 'task-1',
333
- interval: 10,
334
- handler: vi.fn(),
335
- });
336
- scheduler.start();
337
- scheduler.clear();
338
- expect(scheduler.getTaskIds().length).toBe(1);
339
- });
340
- });
341
- });