hyper-scheduler 1.0.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.
Files changed (76) hide show
  1. package/.editorconfig +21 -0
  2. package/.eslintrc.cjs +26 -0
  3. package/GEMINI.md +1 -0
  4. package/README.md +38 -0
  5. package/docs/.vitepress/config.ts +52 -0
  6. package/docs/README.md +120 -0
  7. package/docs/api/devtools.md +232 -0
  8. package/docs/api/index.md +178 -0
  9. package/docs/api/scheduler.md +322 -0
  10. package/docs/api/task.md +439 -0
  11. package/docs/api/types.md +365 -0
  12. package/docs/examples/index.md +295 -0
  13. package/docs/guide/best-practices.md +436 -0
  14. package/docs/guide/core-concepts.md +363 -0
  15. package/docs/guide/getting-started.md +138 -0
  16. package/docs/index.md +33 -0
  17. package/docs/public/logo.svg +54 -0
  18. package/examples/browser/index.html +354 -0
  19. package/examples/node/simple.js +36 -0
  20. package/examples/react-demo/index.html +12 -0
  21. package/examples/react-demo/package.json +23 -0
  22. package/examples/react-demo/src/App.css +212 -0
  23. package/examples/react-demo/src/App.jsx +160 -0
  24. package/examples/react-demo/src/main.jsx +9 -0
  25. package/examples/react-demo/vite.config.ts +12 -0
  26. package/examples/react-demo/yarn.lock +752 -0
  27. package/examples/vue-demo/index.html +12 -0
  28. package/examples/vue-demo/package.json +21 -0
  29. package/examples/vue-demo/src/App.vue +373 -0
  30. package/examples/vue-demo/src/main.ts +4 -0
  31. package/examples/vue-demo/vite.config.ts +13 -0
  32. package/examples/vue-demo/yarn.lock +375 -0
  33. package/package.json +51 -0
  34. package/src/constants.ts +18 -0
  35. package/src/core/retry-strategy.ts +28 -0
  36. package/src/core/scheduler.ts +601 -0
  37. package/src/core/task-registry.ts +58 -0
  38. package/src/index.ts +74 -0
  39. package/src/platform/browser/browser-timer.ts +66 -0
  40. package/src/platform/browser/main-thread-timer.ts +16 -0
  41. package/src/platform/browser/worker.ts +31 -0
  42. package/src/platform/node/debug-cli.ts +19 -0
  43. package/src/platform/node/node-timer.ts +15 -0
  44. package/src/platform/timer-strategy.ts +19 -0
  45. package/src/plugins/dev-tools.ts +101 -0
  46. package/src/types.ts +115 -0
  47. package/src/ui/components/devtools.ts +525 -0
  48. package/src/ui/components/floating-trigger.ts +102 -0
  49. package/src/ui/components/icons.ts +16 -0
  50. package/src/ui/components/resizer.ts +129 -0
  51. package/src/ui/components/task-detail.ts +228 -0
  52. package/src/ui/components/task-header.ts +319 -0
  53. package/src/ui/components/task-list.ts +416 -0
  54. package/src/ui/components/timeline.ts +364 -0
  55. package/src/ui/debug-panel.ts +56 -0
  56. package/src/ui/i18n/en.ts +76 -0
  57. package/src/ui/i18n/index.ts +42 -0
  58. package/src/ui/i18n/zh.ts +76 -0
  59. package/src/ui/store/dev-tools-store.ts +191 -0
  60. package/src/ui/styles/theme.css.ts +56 -0
  61. package/src/ui/styles.ts +43 -0
  62. package/src/utils/cron-lite.ts +221 -0
  63. package/src/utils/cron.ts +20 -0
  64. package/src/utils/id.ts +10 -0
  65. package/src/utils/schedule.ts +93 -0
  66. package/src/vite-env.d.ts +1 -0
  67. package/stats.html +4949 -0
  68. package/tests/integration/Debug.test.ts +58 -0
  69. package/tests/unit/Plugin.test.ts +16 -0
  70. package/tests/unit/RetryStrategy.test.ts +21 -0
  71. package/tests/unit/Scheduler.test.ts +38 -0
  72. package/tests/unit/schedule.test.ts +70 -0
  73. package/tests/unit/ui/DevToolsStore.test.ts +67 -0
  74. package/tsconfig.json +28 -0
  75. package/vite.config.ts +51 -0
  76. package/vitest.config.ts +24 -0
@@ -0,0 +1,601 @@
1
+ import { SchedulerConfig, Task, TaskDefinition, ExecutionRecord } from '../types';
2
+ import { TaskStatus, SchedulerEvents } from '../constants';
3
+ import { TaskRegistry } from './task-registry';
4
+ import { TimerStrategy } from '../platform/timer-strategy';
5
+ import { validateId } from '../utils/id';
6
+ import { parseSchedule, getNextRun as getNextScheduleRun } from '../utils/schedule'; // 替换为新的 schedule 工具
7
+ import { RetryStrategy } from './retry-strategy';
8
+
9
+ export type TimerStrategyFactory = (driver: 'worker' | 'main') => TimerStrategy;
10
+
11
+ /**
12
+ * 核心调度器类。
13
+ * 负责管理任务生命周期、时间循环和任务执行。
14
+ */
15
+ export class Scheduler {
16
+ private registry: TaskRegistry;
17
+ private config: SchedulerConfig;
18
+ private defaultTimerStrategy: TimerStrategy;
19
+ private timerStrategyFactory?: TimerStrategyFactory;
20
+ private taskTimerStrategies: Map<string, TimerStrategy>; // Task ID -> TimerStrategy
21
+ private running: boolean;
22
+ private timers: Map<string, any>; // Task ID -> Timer Handle
23
+ private listeners: ((tasks: Task[]) => void)[];
24
+ private eventListeners: Map<string, Set<(payload: any) => void>>;
25
+
26
+ /**
27
+ * 创建一个新的调度器实例。
28
+ * @param timerStrategy 默认计时策略(NodeTimer 或 BrowserTimer)
29
+ * @param config 调度器配置
30
+ * @param timerStrategyFactory 可选的定时器策略工厂,用于创建任务级别的定时器
31
+ */
32
+ constructor(
33
+ timerStrategy: TimerStrategy,
34
+ config: SchedulerConfig = {},
35
+ timerStrategyFactory?: TimerStrategyFactory
36
+ ) {
37
+ this.registry = new TaskRegistry();
38
+ this.defaultTimerStrategy = timerStrategy;
39
+ this.timerStrategyFactory = timerStrategyFactory;
40
+ this.taskTimerStrategies = new Map();
41
+ this.config = {
42
+ debug: false,
43
+ maxHistory: 50,
44
+ ...config,
45
+ };
46
+ this.running = false;
47
+ this.timers = new Map();
48
+ this.listeners = [];
49
+ this.eventListeners = new Map();
50
+
51
+ // Initialize plugins
52
+ if (this.config.plugins && Array.isArray(this.config.plugins)) {
53
+ this.config.plugins.forEach(plugin => {
54
+ try {
55
+ this.log(`Initializing plugin: ${plugin.name}`);
56
+ plugin.init(this);
57
+ } catch (err) {
58
+ console.warn(`[HyperScheduler] Failed to initialize plugin ${plugin.name}:`, err);
59
+ }
60
+ });
61
+ }
62
+ }
63
+
64
+ /**
65
+ * 订阅任务列表变更事件。
66
+ * 当任务创建、状态改变或删除时触发。
67
+ * @param listener 回调函数
68
+ * @returns 取消订阅的函数
69
+ */
70
+ subscribe(listener: (tasks: Task[]) => void): () => void {
71
+ this.listeners.push(listener);
72
+ return () => {
73
+ this.listeners = this.listeners.filter(l => l !== listener);
74
+ };
75
+ }
76
+
77
+ /**
78
+ * 订阅特定事件
79
+ * @param event 事件名称
80
+ * @param handler 事件处理函数
81
+ * @returns 取消订阅的函数
82
+ */
83
+ on(event: string, handler: (payload: any) => void): () => void {
84
+ if (!this.eventListeners.has(event)) {
85
+ this.eventListeners.set(event, new Set());
86
+ }
87
+ this.eventListeners.get(event)!.add(handler);
88
+
89
+ return () => {
90
+ this.eventListeners.get(event)?.delete(handler);
91
+ };
92
+ }
93
+
94
+ private emit(event: string, payload: any): void {
95
+ const handlers = this.eventListeners.get(event);
96
+ if (handlers) {
97
+ handlers.forEach(handler => handler(payload));
98
+ }
99
+ }
100
+
101
+ /**
102
+ * 获取任务的定时器策略
103
+ * 优先使用任务级别配置,其次使用全局配置,最后使用默认策略
104
+ */
105
+ private getTimerStrategy(task: Task): TimerStrategy {
106
+ const taskDriver = task.options?.driver;
107
+ const globalDriver = this.config.driver;
108
+ const driver = taskDriver || globalDriver;
109
+
110
+ // 如果没有指定 driver 或没有工厂函数,使用默认策略
111
+ if (!driver || !this.timerStrategyFactory) {
112
+ return this.defaultTimerStrategy;
113
+ }
114
+
115
+ // 检查是否已经为该任务创建了策略
116
+ const cacheKey = `${task.id}_${driver}`;
117
+ if (this.taskTimerStrategies.has(cacheKey)) {
118
+ return this.taskTimerStrategies.get(cacheKey)!;
119
+ }
120
+
121
+ // 创建新的策略并缓存
122
+ const strategy = this.timerStrategyFactory(driver);
123
+ this.taskTimerStrategies.set(cacheKey, strategy);
124
+ return strategy;
125
+ }
126
+
127
+ private notify(): void {
128
+ if (this.listeners.length > 0) {
129
+ const tasks = this.registry.getAllTasks();
130
+ this.listeners.forEach(l => l(tasks));
131
+ }
132
+ }
133
+
134
+ /**
135
+ * 创建并注册一个新任务。
136
+ * @param definition 任务定义
137
+ */
138
+ createTask(definition: TaskDefinition): void {
139
+ validateId(definition.id);
140
+ parseSchedule(definition.schedule); // 使用新的解析函数进行验证
141
+
142
+ const task: Task = {
143
+ ...definition,
144
+ tags: definition.tags || [],
145
+ // 新创建的任务默认是 stopped 状态,需要手动启动或等调度器启动
146
+ status: TaskStatus.STOPPED,
147
+ history: [],
148
+ executionCount: 0,
149
+ };
150
+
151
+ this.registry.addTask(task);
152
+ this.log(`Task created: ${task.id}`);
153
+ this.emit(SchedulerEvents.TASK_REGISTERED, { taskId: task.id, task });
154
+ this.notify();
155
+
156
+ // 如果调度器已经在运行,自动启动新任务
157
+ if (this.running) {
158
+ task.status = TaskStatus.IDLE;
159
+ this.scheduleTask(task);
160
+ }
161
+ }
162
+
163
+ /**
164
+ * 删除指定 ID 的任务。
165
+ * @param id 任务 ID
166
+ * @returns 如果删除成功返回 true,否则返回 false
167
+ */
168
+ deleteTask(id: string): boolean {
169
+ this.stopTask(id);
170
+ const deleted = this.registry.deleteTask(id);
171
+ if (deleted) {
172
+ this.log(`Task deleted: ${id}`);
173
+ this.emit(SchedulerEvents.TASK_REMOVED, { taskId: id });
174
+ this.notify();
175
+ }
176
+ return deleted;
177
+ }
178
+
179
+ /**
180
+ * 手动启动一个任务(将其置为 idle 状态并重新加入调度)。
181
+ * 即使调度器未启动,也可以单独启动某个任务。
182
+ * @param id 任务 ID
183
+ */
184
+ startTask(id: string): void {
185
+ const task = this.registry.getTask(id);
186
+ if (!task) {
187
+ throw new Error(`Task not found: ${id}`);
188
+ }
189
+
190
+ if (task.status === TaskStatus.RUNNING) {
191
+ return; // Already running
192
+ }
193
+
194
+ // Cancel existing timer if any
195
+ const existingHandle = this.timers.get(id);
196
+ if (existingHandle) {
197
+ this.getTimerStrategy(task).cancel(existingHandle);
198
+ this.timers.delete(id);
199
+ }
200
+
201
+ task.status = TaskStatus.IDLE; // Reset status if it was stopped or error
202
+ this.log(`Starting task: ${id}`);
203
+ this.emit(SchedulerEvents.TASK_STARTED, { taskId: id, task });
204
+ this.notify();
205
+
206
+ // Schedule task even if scheduler is not running (individual task control)
207
+ this.scheduleTaskForce(task);
208
+ }
209
+
210
+ /**
211
+ * 强制调度任务,即使调度器未启动
212
+ */
213
+ private scheduleTaskForce(task: Task): void {
214
+ try {
215
+ const nextRun = getNextScheduleRun(task.schedule, {
216
+ timezone: task.options?.timezone || this.config.timezone,
217
+ lastRun: task.lastRun,
218
+ });
219
+ task.nextRun = nextRun.getTime();
220
+
221
+ const now = Date.now();
222
+ const delay = Math.max(0, nextRun.getTime() - now);
223
+
224
+ this.log(`Scheduling task ${task.id} for ${nextRun.toISOString()} (in ${delay}ms)`);
225
+
226
+ const timerStrategy = this.getTimerStrategy(task);
227
+ const handle = timerStrategy.schedule(() => {
228
+ this.executeTaskIndividual(task);
229
+ }, delay);
230
+
231
+ this.timers.set(task.id, handle);
232
+ } catch (err) {
233
+ this.log(`Error scheduling task ${task.id}: ${err}`);
234
+ task.status = TaskStatus.ERROR;
235
+ this.notify();
236
+ }
237
+ }
238
+
239
+ /**
240
+ * 执行单个任务(用于独立启动的任务)
241
+ */
242
+ private async executeTaskIndividual(task: Task, attempt: number = 0): Promise<void> {
243
+ this.timers.delete(task.id);
244
+
245
+ // Check if task was stopped
246
+ if (task.status === TaskStatus.STOPPED) return;
247
+
248
+ task.status = TaskStatus.RUNNING;
249
+ task.lastRun = Date.now();
250
+ task.executionCount = (task.executionCount || 0) + 1;
251
+ const startTime = Date.now();
252
+
253
+ this.log(`Executing task: ${task.id} (Attempt ${attempt})`);
254
+ this.emit(SchedulerEvents.TASK_STARTED, { taskId: task.id, task });
255
+ this.notify();
256
+
257
+ try {
258
+ await task.handler();
259
+
260
+ const duration = Date.now() - startTime;
261
+ this.recordHistory(task, {
262
+ timestamp: startTime,
263
+ duration,
264
+ success: true,
265
+ });
266
+
267
+ task.status = TaskStatus.IDLE;
268
+ this.log(`Task execution success: ${task.id}`);
269
+ this.emit(SchedulerEvents.TASK_COMPLETED, { taskId: task.id, task, duration });
270
+ this.notify();
271
+
272
+ // Schedule next run
273
+ this.scheduleTaskForce(task);
274
+
275
+ } catch (err: any) {
276
+ const duration = Date.now() - startTime;
277
+ this.recordHistory(task, {
278
+ timestamp: startTime,
279
+ duration,
280
+ success: false,
281
+ error: err.message,
282
+ });
283
+
284
+ this.log(`Task execution failed: ${task.id} - ${err.message}`);
285
+ this.emit(SchedulerEvents.TASK_FAILED, { taskId: task.id, task, error: err.message, duration });
286
+
287
+ // 调用任务的 onError 回调
288
+ if (task.options?.onError) {
289
+ try {
290
+ task.options.onError(err, task.id);
291
+ } catch (e) {
292
+ this.log(`onError callback failed: ${e}`);
293
+ }
294
+ }
295
+
296
+ // Retry logic
297
+ const retryDelay = RetryStrategy.getDelay(attempt, task.options?.retry);
298
+ if (retryDelay >= 0) {
299
+ this.log(`Retrying task ${task.id} in ${retryDelay}ms (Attempt ${attempt + 1})`);
300
+ const timerStrategy = this.getTimerStrategy(task);
301
+ const handle = timerStrategy.schedule(() => {
302
+ this.executeTaskIndividual(task, attempt + 1);
303
+ }, retryDelay);
304
+ this.timers.set(task.id, handle);
305
+ task.status = TaskStatus.ERROR;
306
+ this.notify();
307
+ } else {
308
+ task.status = TaskStatus.ERROR;
309
+ this.notify();
310
+ // Schedule next regular run
311
+ this.scheduleTaskForce(task);
312
+ }
313
+ }
314
+ }
315
+
316
+ /**
317
+ * 停止一个任务(取消当前的定时器并标记为 stopped)。
318
+ * @param id 任务 ID
319
+ */
320
+ stopTask(id: string): void {
321
+ const task = this.registry.getTask(id);
322
+ const handle = this.timers.get(id);
323
+ if (handle && task) {
324
+ this.getTimerStrategy(task).cancel(handle);
325
+ this.timers.delete(id);
326
+ } else if (handle) {
327
+ // Fallback to default strategy if task not found
328
+ this.defaultTimerStrategy.cancel(handle);
329
+ this.timers.delete(id);
330
+ }
331
+
332
+ if (task) {
333
+ task.status = TaskStatus.STOPPED;
334
+ this.log(`Task stopped: ${id}`);
335
+ this.emit(SchedulerEvents.TASK_STOPPED, { taskId: id, task });
336
+ this.notify();
337
+ }
338
+ }
339
+
340
+ /**
341
+ * 获取任务信息。
342
+ * @param id 任务 ID
343
+ */
344
+ getTask(id: string): Task | undefined {
345
+ return this.registry.getTask(id);
346
+ }
347
+
348
+ /**
349
+ * 获取所有任务。
350
+ */
351
+ getAllTasks(): Task[] {
352
+ return this.registry.getAllTasks();
353
+ }
354
+
355
+ /**
356
+ * 获取调度器运行状态。
357
+ */
358
+ isRunning(): boolean {
359
+ return this.running;
360
+ }
361
+
362
+ /**
363
+ * 获取任务的实际驱动方式。
364
+ * 优先使用任务级配置,其次使用全局配置,默认为 'worker'。
365
+ */
366
+ getTaskDriver(id: string): 'worker' | 'main' {
367
+ const task = this.registry.getTask(id);
368
+ if (!task) return this.config.driver || 'worker';
369
+ return task.options?.driver || this.config.driver || 'worker';
370
+ }
371
+
372
+ /**
373
+ * 获取全局驱动配置。
374
+ */
375
+ getGlobalDriver(): 'worker' | 'main' {
376
+ return this.config.driver || 'worker';
377
+ }
378
+
379
+ /**
380
+ * 手动触发任务执行(忽略调度器状态,立即执行一次)。
381
+ * 执行完成后恢复到之前的状态。
382
+ * @param id 任务 ID
383
+ */
384
+ async triggerTask(id: string): Promise<void> {
385
+ const task = this.getTask(id);
386
+ if (!task) return;
387
+ if (task.status === TaskStatus.RUNNING) return;
388
+
389
+ const previousStatus = task.status;
390
+
391
+ task.status = TaskStatus.RUNNING;
392
+ task.lastRun = Date.now();
393
+ task.executionCount = (task.executionCount || 0) + 1;
394
+ const startTime = Date.now();
395
+
396
+ this.log(`Triggering task: ${task.id}`);
397
+ this.emit(SchedulerEvents.TASK_STARTED, { taskId: task.id, task });
398
+ this.notify();
399
+
400
+ try {
401
+ await task.handler();
402
+
403
+ const duration = Date.now() - startTime;
404
+ this.recordHistory(task, {
405
+ timestamp: startTime,
406
+ duration,
407
+ success: true,
408
+ });
409
+
410
+ // 恢复到之前的状态
411
+ task.status = previousStatus;
412
+ this.log(`Task trigger success: ${task.id}`);
413
+ this.emit(SchedulerEvents.TASK_COMPLETED, { taskId: task.id, task, duration });
414
+ this.notify();
415
+
416
+ } catch (err: any) {
417
+ const duration = Date.now() - startTime;
418
+ this.recordHistory(task, {
419
+ timestamp: startTime,
420
+ duration,
421
+ success: false,
422
+ error: err.message,
423
+ });
424
+
425
+ this.log(`Task trigger failed: ${task.id} - ${err.message}`);
426
+ this.emit(SchedulerEvents.TASK_FAILED, { taskId: task.id, task, error: err.message, duration });
427
+
428
+ // 恢复到之前的状态
429
+ task.status = previousStatus;
430
+ this.notify();
431
+ }
432
+ }
433
+
434
+ /**
435
+ * 启动调度器。
436
+ * 开始处理所有任务(除了手动停止的任务)。
437
+ */
438
+ start(): void {
439
+ if (this.running) return;
440
+ this.running = true;
441
+ this.log('Scheduler started');
442
+ this.emit(SchedulerEvents.SCHEDULER_STARTED, { running: true });
443
+ this.registry.getAllTasks().forEach((task) => {
444
+ // 启动所有 stopped 状态的任务(新创建的任务默认是 stopped)
445
+ if (task.status === TaskStatus.STOPPED) {
446
+ task.status = TaskStatus.IDLE;
447
+ this.emit(SchedulerEvents.TASK_UPDATED, { taskId: task.id, task });
448
+ }
449
+ // 调度所有非 running 状态的任务
450
+ if (task.status !== TaskStatus.RUNNING) {
451
+ this.scheduleTask(task);
452
+ }
453
+ });
454
+ this.notify();
455
+ }
456
+
457
+ /**
458
+ * 停止调度器。
459
+ * 取消所有正在等待的定时器。
460
+ */
461
+ stop(): void {
462
+ this.running = false;
463
+ this.log('Scheduler stopped');
464
+ this.emit(SchedulerEvents.SCHEDULER_STOPPED, { running: false });
465
+
466
+ // Cancel all timers using appropriate strategy for each task
467
+ this.timers.forEach((handle, taskId) => {
468
+ const task = this.registry.getTask(taskId);
469
+ if (task) {
470
+ this.getTimerStrategy(task).cancel(handle);
471
+ } else {
472
+ this.defaultTimerStrategy.cancel(handle);
473
+ }
474
+ });
475
+ this.timers.clear();
476
+
477
+ // 将所有非 stopped 状态的任务标记为 stopped
478
+ this.registry.getAllTasks().forEach((task) => {
479
+ if (task.status !== TaskStatus.STOPPED) {
480
+ task.status = TaskStatus.STOPPED;
481
+ this.emit(SchedulerEvents.TASK_UPDATED, { taskId: task.id, task });
482
+ }
483
+ });
484
+
485
+ this.notify(); // 通知 DevTools 更新状态
486
+ }
487
+
488
+ private scheduleTask(task: Task): void {
489
+ if (!this.running && task.status !== TaskStatus.RUNNING) return;
490
+
491
+ try {
492
+ // 使用新的 getNextScheduleRun
493
+ const nextRun = getNextScheduleRun(task.schedule, {
494
+ timezone: task.options?.timezone || this.config.timezone,
495
+ lastRun: task.lastRun, // 对于 interval 任务,需要上次运行时间
496
+ });
497
+ task.nextRun = nextRun.getTime();
498
+
499
+ const now = Date.now();
500
+ const delay = Math.max(0, nextRun.getTime() - now);
501
+
502
+ this.log(`Scheduling task ${task.id} for ${nextRun.toISOString()} (in ${delay}ms)`);
503
+
504
+ const timerStrategy = this.getTimerStrategy(task);
505
+ const handle = timerStrategy.schedule(() => {
506
+ this.executeTask(task);
507
+ }, delay);
508
+
509
+ this.timers.set(task.id, handle);
510
+ } catch (err) {
511
+ this.log(`Error scheduling task ${task.id}: ${err}`);
512
+ task.status = TaskStatus.ERROR;
513
+ }
514
+ }
515
+
516
+ private async executeTask(task: Task, attempt: number = 0, force: boolean = false): Promise<void> {
517
+ this.timers.delete(task.id);
518
+
519
+ if (!force && (!this.running || task.status === TaskStatus.STOPPED)) return;
520
+
521
+ task.status = TaskStatus.RUNNING;
522
+ task.lastRun = Date.now();
523
+ task.executionCount = (task.executionCount || 0) + 1;
524
+ const startTime = Date.now();
525
+
526
+ this.log(`Executing task: ${task.id} (Attempt ${attempt})`);
527
+ this.emit(SchedulerEvents.TASK_STARTED, { taskId: task.id, task });
528
+ this.notify();
529
+
530
+ try {
531
+ await task.handler();
532
+
533
+ const duration = Date.now() - startTime;
534
+ this.recordHistory(task, {
535
+ timestamp: startTime,
536
+ duration,
537
+ success: true,
538
+ });
539
+
540
+ task.status = TaskStatus.IDLE;
541
+ this.log(`Task execution success: ${task.id}`);
542
+ this.emit(SchedulerEvents.TASK_COMPLETED, { taskId: task.id, task, duration });
543
+ this.notify();
544
+
545
+ // Schedule next run
546
+ this.scheduleTask(task);
547
+
548
+ } catch (err: any) {
549
+ const duration = Date.now() - startTime;
550
+ this.recordHistory(task, {
551
+ timestamp: startTime,
552
+ duration,
553
+ success: false,
554
+ error: err.message,
555
+ });
556
+
557
+ this.log(`Task execution failed: ${task.id} - ${err.message}`);
558
+ this.emit(SchedulerEvents.TASK_FAILED, { taskId: task.id, task, error: err.message, duration });
559
+
560
+ // 调用任务的 onError 回调
561
+ if (task.options?.onError) {
562
+ try {
563
+ task.options.onError(err, task.id);
564
+ } catch (e) {
565
+ this.log(`onError callback failed: ${e}`);
566
+ }
567
+ }
568
+
569
+ // Retry logic
570
+ const retryDelay = RetryStrategy.getDelay(attempt, task.options?.retry);
571
+ if (retryDelay >= 0) {
572
+ this.log(`Retrying task ${task.id} in ${retryDelay}ms (Attempt ${attempt + 1})`);
573
+ const timerStrategy = this.getTimerStrategy(task);
574
+ const handle = timerStrategy.schedule(() => {
575
+ this.executeTask(task, attempt + 1);
576
+ }, retryDelay);
577
+ this.timers.set(task.id, handle);
578
+ task.status = TaskStatus.ERROR;
579
+ this.notify();
580
+ } else {
581
+ task.status = TaskStatus.ERROR;
582
+ this.notify();
583
+ // Schedule next regular run even if failed
584
+ this.scheduleTask(task);
585
+ }
586
+ }
587
+ }
588
+
589
+ private recordHistory(task: Task, record: ExecutionRecord): void {
590
+ task.history.unshift(record);
591
+ if (this.config.maxHistory && task.history.length > this.config.maxHistory) {
592
+ task.history.pop();
593
+ }
594
+ }
595
+
596
+ private log(message: string): void {
597
+ if (this.config.debug) {
598
+ console.log(`[HyperScheduler] ${message}`);
599
+ }
600
+ }
601
+ }
@@ -0,0 +1,58 @@
1
+ import { Task } from '../types';
2
+
3
+ /**
4
+ * 任务注册表。
5
+ * 负责存储和管理所有的任务实例。
6
+ */
7
+ export class TaskRegistry {
8
+ private tasks: Map<string, Task>;
9
+
10
+ constructor() {
11
+ this.tasks = new Map();
12
+ }
13
+
14
+ /**
15
+ * 注册一个新任务。
16
+ * @param task 任务对象
17
+ * @throws Error 如果 ID 已存在
18
+ */
19
+ addTask(task: Task): void {
20
+ if (this.tasks.has(task.id)) {
21
+ throw new Error(`Task with ID "${task.id}" already exists.`);
22
+ }
23
+ this.tasks.set(task.id, task);
24
+ }
25
+
26
+ /**
27
+ * 根据 ID 获取任务。
28
+ * @param id 任务 ID
29
+ * @returns 任务对象或 undefined
30
+ */
31
+ getTask(id: string): Task | undefined {
32
+ return this.tasks.get(id);
33
+ }
34
+
35
+ /**
36
+ * 删除任务。
37
+ * @param id 任务 ID
38
+ * @returns 是否删除成功
39
+ */
40
+ deleteTask(id: string): boolean {
41
+ return this.tasks.delete(id);
42
+ }
43
+
44
+ /**
45
+ * 获取所有已注册的任务。
46
+ * @returns 任务数组
47
+ */
48
+ getAllTasks(): Task[] {
49
+ return Array.from(this.tasks.values());
50
+ }
51
+
52
+ /**
53
+ * 清空所有任务。
54
+ */
55
+ clear(): void {
56
+ this.tasks.clear();
57
+ }
58
+ }