hyper-scheduler 1.0.0 → 1.0.1

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 (80) hide show
  1. package/dist/devtools-4xVHTaUz.js +2161 -0
  2. package/dist/devtools-DdQ1I25H.cjs +1 -0
  3. package/dist/index.cjs +1 -0
  4. package/dist/index.js +995 -0
  5. package/dist/index.umd.cjs +1 -0
  6. package/package.json +7 -1
  7. package/.editorconfig +0 -21
  8. package/.eslintrc.cjs +0 -26
  9. package/GEMINI.md +0 -1
  10. package/docs/.vitepress/config.ts +0 -52
  11. package/docs/README.md +0 -120
  12. package/docs/api/devtools.md +0 -232
  13. package/docs/api/index.md +0 -178
  14. package/docs/api/scheduler.md +0 -322
  15. package/docs/api/task.md +0 -439
  16. package/docs/api/types.md +0 -365
  17. package/docs/examples/index.md +0 -295
  18. package/docs/guide/best-practices.md +0 -436
  19. package/docs/guide/core-concepts.md +0 -363
  20. package/docs/guide/getting-started.md +0 -138
  21. package/docs/index.md +0 -33
  22. package/docs/public/logo.svg +0 -54
  23. package/examples/browser/index.html +0 -354
  24. package/examples/node/simple.js +0 -36
  25. package/examples/react-demo/index.html +0 -12
  26. package/examples/react-demo/package.json +0 -23
  27. package/examples/react-demo/src/App.css +0 -212
  28. package/examples/react-demo/src/App.jsx +0 -160
  29. package/examples/react-demo/src/main.jsx +0 -9
  30. package/examples/react-demo/vite.config.ts +0 -12
  31. package/examples/react-demo/yarn.lock +0 -752
  32. package/examples/vue-demo/index.html +0 -12
  33. package/examples/vue-demo/package.json +0 -21
  34. package/examples/vue-demo/src/App.vue +0 -373
  35. package/examples/vue-demo/src/main.ts +0 -4
  36. package/examples/vue-demo/vite.config.ts +0 -13
  37. package/examples/vue-demo/yarn.lock +0 -375
  38. package/src/constants.ts +0 -18
  39. package/src/core/retry-strategy.ts +0 -28
  40. package/src/core/scheduler.ts +0 -601
  41. package/src/core/task-registry.ts +0 -58
  42. package/src/index.ts +0 -74
  43. package/src/platform/browser/browser-timer.ts +0 -66
  44. package/src/platform/browser/main-thread-timer.ts +0 -16
  45. package/src/platform/browser/worker.ts +0 -31
  46. package/src/platform/node/debug-cli.ts +0 -19
  47. package/src/platform/node/node-timer.ts +0 -15
  48. package/src/platform/timer-strategy.ts +0 -19
  49. package/src/plugins/dev-tools.ts +0 -101
  50. package/src/types.ts +0 -115
  51. package/src/ui/components/devtools.ts +0 -525
  52. package/src/ui/components/floating-trigger.ts +0 -102
  53. package/src/ui/components/icons.ts +0 -16
  54. package/src/ui/components/resizer.ts +0 -129
  55. package/src/ui/components/task-detail.ts +0 -228
  56. package/src/ui/components/task-header.ts +0 -319
  57. package/src/ui/components/task-list.ts +0 -416
  58. package/src/ui/components/timeline.ts +0 -364
  59. package/src/ui/debug-panel.ts +0 -56
  60. package/src/ui/i18n/en.ts +0 -76
  61. package/src/ui/i18n/index.ts +0 -42
  62. package/src/ui/i18n/zh.ts +0 -76
  63. package/src/ui/store/dev-tools-store.ts +0 -191
  64. package/src/ui/styles/theme.css.ts +0 -56
  65. package/src/ui/styles.ts +0 -43
  66. package/src/utils/cron-lite.ts +0 -221
  67. package/src/utils/cron.ts +0 -20
  68. package/src/utils/id.ts +0 -10
  69. package/src/utils/schedule.ts +0 -93
  70. package/src/vite-env.d.ts +0 -1
  71. package/stats.html +0 -4949
  72. package/tests/integration/Debug.test.ts +0 -58
  73. package/tests/unit/Plugin.test.ts +0 -16
  74. package/tests/unit/RetryStrategy.test.ts +0 -21
  75. package/tests/unit/Scheduler.test.ts +0 -38
  76. package/tests/unit/schedule.test.ts +0 -70
  77. package/tests/unit/ui/DevToolsStore.test.ts +0 -67
  78. package/tsconfig.json +0 -28
  79. package/vite.config.ts +0 -51
  80. package/vitest.config.ts +0 -24
package/dist/index.js ADDED
@@ -0,0 +1,995 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ const TaskStatus = {
5
+ IDLE: "idle",
6
+ RUNNING: "running",
7
+ STOPPED: "stopped",
8
+ ERROR: "error"
9
+ };
10
+ const SchedulerEvents = {
11
+ TASK_REGISTERED: "task_registered",
12
+ TASK_STARTED: "task_started",
13
+ TASK_COMPLETED: "task_completed",
14
+ TASK_FAILED: "task_failed",
15
+ TASK_STOPPED: "task_stopped",
16
+ TASK_REMOVED: "task_removed",
17
+ TASK_UPDATED: "task_updated",
18
+ SCHEDULER_STARTED: "scheduler_started",
19
+ SCHEDULER_STOPPED: "scheduler_stopped"
20
+ };
21
+ class TaskRegistry {
22
+ constructor() {
23
+ __publicField(this, "tasks");
24
+ this.tasks = /* @__PURE__ */ new Map();
25
+ }
26
+ /**
27
+ * 注册一个新任务。
28
+ * @param task 任务对象
29
+ * @throws Error 如果 ID 已存在
30
+ */
31
+ addTask(task) {
32
+ if (this.tasks.has(task.id)) {
33
+ throw new Error(`Task with ID "${task.id}" already exists.`);
34
+ }
35
+ this.tasks.set(task.id, task);
36
+ }
37
+ /**
38
+ * 根据 ID 获取任务。
39
+ * @param id 任务 ID
40
+ * @returns 任务对象或 undefined
41
+ */
42
+ getTask(id) {
43
+ return this.tasks.get(id);
44
+ }
45
+ /**
46
+ * 删除任务。
47
+ * @param id 任务 ID
48
+ * @returns 是否删除成功
49
+ */
50
+ deleteTask(id) {
51
+ return this.tasks.delete(id);
52
+ }
53
+ /**
54
+ * 获取所有已注册的任务。
55
+ * @returns 任务数组
56
+ */
57
+ getAllTasks() {
58
+ return Array.from(this.tasks.values());
59
+ }
60
+ /**
61
+ * 清空所有任务。
62
+ */
63
+ clear() {
64
+ this.tasks.clear();
65
+ }
66
+ }
67
+ function validateId(id) {
68
+ if (!id || typeof id !== "string" || id.trim() === "") {
69
+ throw new Error("Task ID must be a non-empty string.");
70
+ }
71
+ }
72
+ function parseField(field, min, max) {
73
+ const values = /* @__PURE__ */ new Set();
74
+ if (field === "*") {
75
+ for (let i = min; i <= max; i++) {
76
+ values.add(i);
77
+ }
78
+ return Array.from(values).sort((a, b) => a - b);
79
+ }
80
+ const parts = field.split(",");
81
+ for (const part of parts) {
82
+ if (part.includes("/")) {
83
+ const [range, step] = part.split("/");
84
+ const stepNum = parseInt(step, 10);
85
+ if (range === "*") {
86
+ for (let i = min; i <= max; i += stepNum) {
87
+ values.add(i);
88
+ }
89
+ } else if (range.includes("-")) {
90
+ const [start, end] = range.split("-").map(Number);
91
+ if (start < min || end > max) {
92
+ throw new Error(`Value out of range: ${start}-${end} (expected ${min}-${max})`);
93
+ }
94
+ for (let i = start; i <= end; i += stepNum) {
95
+ values.add(i);
96
+ }
97
+ } else {
98
+ const start = parseInt(range, 10);
99
+ if (start < min || start > max) {
100
+ throw new Error(`Value out of range: ${start} (expected ${min}-${max})`);
101
+ }
102
+ for (let i = start; i <= max; i += stepNum) {
103
+ values.add(i);
104
+ }
105
+ }
106
+ } else if (part.includes("-")) {
107
+ const [start, end] = part.split("-").map(Number);
108
+ if (start < min || end > max) {
109
+ throw new Error(`Value out of range: ${start}-${end} (expected ${min}-${max})`);
110
+ }
111
+ for (let i = start; i <= end; i++) {
112
+ values.add(i);
113
+ }
114
+ } else {
115
+ const val = parseInt(part, 10);
116
+ if (val < min || val > max) {
117
+ throw new Error(`Value out of range: ${val} (expected ${min}-${max})`);
118
+ }
119
+ values.add(val);
120
+ }
121
+ }
122
+ return Array.from(values).sort((a, b) => a - b);
123
+ }
124
+ function parseCronExpression(expression) {
125
+ const parts = expression.trim().split(/\s+/);
126
+ let second, minute, hour, dayOfMonth, month, dayOfWeek;
127
+ if (parts.length === 5) {
128
+ second = [0];
129
+ [minute, hour, dayOfMonth, month, dayOfWeek] = [
130
+ parseField(parts[0], 0, 59),
131
+ parseField(parts[1], 0, 23),
132
+ parseField(parts[2], 1, 31),
133
+ parseField(parts[3], 1, 12),
134
+ parseField(parts[4], 0, 6)
135
+ ];
136
+ } else if (parts.length === 6) {
137
+ [second, minute, hour, dayOfMonth, month, dayOfWeek] = [
138
+ parseField(parts[0], 0, 59),
139
+ parseField(parts[1], 0, 59),
140
+ parseField(parts[2], 0, 23),
141
+ parseField(parts[3], 1, 31),
142
+ parseField(parts[4], 1, 12),
143
+ parseField(parts[5], 0, 6)
144
+ ];
145
+ } else {
146
+ throw new Error(`Invalid cron expression: expected 5 or 6 fields, got ${parts.length}`);
147
+ }
148
+ return { second, minute, hour, dayOfMonth, month, dayOfWeek };
149
+ }
150
+ function validateCron(expression) {
151
+ try {
152
+ parseCronExpression(expression);
153
+ } catch (err) {
154
+ throw new Error(`Invalid cron expression: ${expression}`);
155
+ }
156
+ }
157
+ function matchesCron(date, fields) {
158
+ const second = date.getSeconds();
159
+ const minute = date.getMinutes();
160
+ const hour = date.getHours();
161
+ const dayOfMonth = date.getDate();
162
+ const month = date.getMonth() + 1;
163
+ const dayOfWeek = date.getDay();
164
+ return fields.second.includes(second) && fields.minute.includes(minute) && fields.hour.includes(hour) && fields.month.includes(month) && (fields.dayOfMonth.includes(dayOfMonth) || fields.dayOfWeek.includes(dayOfWeek));
165
+ }
166
+ function getNextRun$1(expression, _timezone) {
167
+ const fields = parseCronExpression(expression);
168
+ const now = /* @__PURE__ */ new Date();
169
+ let candidate = new Date(now.getTime() + 1e3);
170
+ candidate.setMilliseconds(0);
171
+ const maxIterations = 4 * 365 * 24 * 60 * 60;
172
+ let iterations = 0;
173
+ while (iterations < maxIterations) {
174
+ if (matchesCron(candidate, fields)) {
175
+ return candidate;
176
+ }
177
+ const second = candidate.getSeconds();
178
+ const minute = candidate.getMinutes();
179
+ const hour = candidate.getHours();
180
+ if (!fields.second.includes(second)) {
181
+ const nextSecond = fields.second.find((s) => s > second) ?? fields.second[0];
182
+ if (nextSecond > second) {
183
+ candidate.setSeconds(nextSecond);
184
+ } else {
185
+ candidate.setMinutes(minute + 1, nextSecond);
186
+ }
187
+ candidate.setMilliseconds(0);
188
+ iterations++;
189
+ continue;
190
+ }
191
+ if (!fields.minute.includes(minute)) {
192
+ const nextMinute = fields.minute.find((m) => m > minute) ?? fields.minute[0];
193
+ if (nextMinute > minute) {
194
+ candidate.setMinutes(nextMinute, fields.second[0]);
195
+ } else {
196
+ candidate.setHours(hour + 1, nextMinute, fields.second[0]);
197
+ }
198
+ candidate.setMilliseconds(0);
199
+ iterations++;
200
+ continue;
201
+ }
202
+ if (!fields.hour.includes(hour)) {
203
+ const nextHour = fields.hour.find((h) => h > hour) ?? fields.hour[0];
204
+ if (nextHour > hour) {
205
+ candidate.setHours(nextHour, fields.minute[0], fields.second[0]);
206
+ } else {
207
+ candidate.setDate(candidate.getDate() + 1);
208
+ candidate.setHours(nextHour, fields.minute[0], fields.second[0]);
209
+ }
210
+ candidate.setMilliseconds(0);
211
+ iterations++;
212
+ continue;
213
+ }
214
+ candidate = new Date(candidate.getTime() + 1e3);
215
+ iterations++;
216
+ }
217
+ throw new Error(`Could not find next run time for cron expression: ${expression}`);
218
+ }
219
+ function parseSchedule(schedule) {
220
+ const intervalRegex = /^(\d+)(s|m|h|d)$/;
221
+ const match = schedule.match(intervalRegex);
222
+ if (match) {
223
+ const value = parseInt(match[1], 10);
224
+ const unit = match[2];
225
+ let multiplier = 1e3;
226
+ switch (unit) {
227
+ case "s":
228
+ multiplier = 1e3;
229
+ break;
230
+ case "m":
231
+ multiplier = 60 * 1e3;
232
+ break;
233
+ case "h":
234
+ multiplier = 60 * 60 * 1e3;
235
+ break;
236
+ case "d":
237
+ multiplier = 24 * 60 * 60 * 1e3;
238
+ break;
239
+ }
240
+ return {
241
+ type: "interval",
242
+ value: value * multiplier
243
+ };
244
+ }
245
+ try {
246
+ validateCron(schedule);
247
+ return {
248
+ type: "cron",
249
+ value: schedule
250
+ };
251
+ } catch (err) {
252
+ throw new Error(`无效的调度格式: "${schedule}". 必须是有效的 Cron 表达式或间隔字符串 (例如 "10s")。`);
253
+ }
254
+ }
255
+ function getNextRun(schedule, options) {
256
+ let parsed;
257
+ if (typeof schedule === "string") {
258
+ parsed = parseSchedule(schedule);
259
+ } else {
260
+ parsed = schedule;
261
+ }
262
+ if (parsed.type === "interval") {
263
+ const intervalMs = parsed.value;
264
+ const lastRun = (options == null ? void 0 : options.lastRun) || Date.now();
265
+ return new Date(lastRun + intervalMs);
266
+ } else {
267
+ const cronExpression = parsed.value;
268
+ try {
269
+ return getNextRun$1(cronExpression, options == null ? void 0 : options.timezone);
270
+ } catch (err) {
271
+ throw new Error(`无法计算下一次 Cron 运行时间: ${cronExpression}`);
272
+ }
273
+ }
274
+ }
275
+ class RetryStrategy {
276
+ /**
277
+ * 计算下一次重试的延迟时间。
278
+ * 采用指数退避算法 (Exponential Backoff)。
279
+ * @param attempt 当前重试次数 (从 0 开始,0 表示第一次重试)
280
+ * @param options 任务的重试配置
281
+ * @returns 延迟毫秒数,如果超过最大尝试次数则返回 -1
282
+ */
283
+ static getDelay(attempt, options) {
284
+ if (!options) {
285
+ return -1;
286
+ }
287
+ const { maxAttempts, initialDelay, factor = 2 } = options;
288
+ if (attempt >= maxAttempts) {
289
+ return -1;
290
+ }
291
+ return initialDelay * Math.pow(factor, attempt);
292
+ }
293
+ }
294
+ let Scheduler$1 = class Scheduler {
295
+ /**
296
+ * 创建一个新的调度器实例。
297
+ * @param timerStrategy 默认计时策略(NodeTimer 或 BrowserTimer)
298
+ * @param config 调度器配置
299
+ * @param timerStrategyFactory 可选的定时器策略工厂,用于创建任务级别的定时器
300
+ */
301
+ constructor(timerStrategy, config = {}, timerStrategyFactory) {
302
+ __publicField(this, "registry");
303
+ __publicField(this, "config");
304
+ __publicField(this, "defaultTimerStrategy");
305
+ __publicField(this, "timerStrategyFactory");
306
+ __publicField(this, "taskTimerStrategies");
307
+ // Task ID -> TimerStrategy
308
+ __publicField(this, "running");
309
+ __publicField(this, "timers");
310
+ // Task ID -> Timer Handle
311
+ __publicField(this, "listeners");
312
+ __publicField(this, "eventListeners");
313
+ this.registry = new TaskRegistry();
314
+ this.defaultTimerStrategy = timerStrategy;
315
+ this.timerStrategyFactory = timerStrategyFactory;
316
+ this.taskTimerStrategies = /* @__PURE__ */ new Map();
317
+ this.config = {
318
+ debug: false,
319
+ maxHistory: 50,
320
+ ...config
321
+ };
322
+ this.running = false;
323
+ this.timers = /* @__PURE__ */ new Map();
324
+ this.listeners = [];
325
+ this.eventListeners = /* @__PURE__ */ new Map();
326
+ if (this.config.plugins && Array.isArray(this.config.plugins)) {
327
+ this.config.plugins.forEach((plugin) => {
328
+ try {
329
+ this.log(`Initializing plugin: ${plugin.name}`);
330
+ plugin.init(this);
331
+ } catch (err) {
332
+ console.warn(`[HyperScheduler] Failed to initialize plugin ${plugin.name}:`, err);
333
+ }
334
+ });
335
+ }
336
+ }
337
+ /**
338
+ * 订阅任务列表变更事件。
339
+ * 当任务创建、状态改变或删除时触发。
340
+ * @param listener 回调函数
341
+ * @returns 取消订阅的函数
342
+ */
343
+ subscribe(listener) {
344
+ this.listeners.push(listener);
345
+ return () => {
346
+ this.listeners = this.listeners.filter((l) => l !== listener);
347
+ };
348
+ }
349
+ /**
350
+ * 订阅特定事件
351
+ * @param event 事件名称
352
+ * @param handler 事件处理函数
353
+ * @returns 取消订阅的函数
354
+ */
355
+ on(event, handler) {
356
+ if (!this.eventListeners.has(event)) {
357
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
358
+ }
359
+ this.eventListeners.get(event).add(handler);
360
+ return () => {
361
+ var _a;
362
+ (_a = this.eventListeners.get(event)) == null ? void 0 : _a.delete(handler);
363
+ };
364
+ }
365
+ emit(event, payload) {
366
+ const handlers = this.eventListeners.get(event);
367
+ if (handlers) {
368
+ handlers.forEach((handler) => handler(payload));
369
+ }
370
+ }
371
+ /**
372
+ * 获取任务的定时器策略
373
+ * 优先使用任务级别配置,其次使用全局配置,最后使用默认策略
374
+ */
375
+ getTimerStrategy(task) {
376
+ var _a;
377
+ const taskDriver = (_a = task.options) == null ? void 0 : _a.driver;
378
+ const globalDriver = this.config.driver;
379
+ const driver = taskDriver || globalDriver;
380
+ if (!driver || !this.timerStrategyFactory) {
381
+ return this.defaultTimerStrategy;
382
+ }
383
+ const cacheKey = `${task.id}_${driver}`;
384
+ if (this.taskTimerStrategies.has(cacheKey)) {
385
+ return this.taskTimerStrategies.get(cacheKey);
386
+ }
387
+ const strategy = this.timerStrategyFactory(driver);
388
+ this.taskTimerStrategies.set(cacheKey, strategy);
389
+ return strategy;
390
+ }
391
+ notify() {
392
+ if (this.listeners.length > 0) {
393
+ const tasks = this.registry.getAllTasks();
394
+ this.listeners.forEach((l) => l(tasks));
395
+ }
396
+ }
397
+ /**
398
+ * 创建并注册一个新任务。
399
+ * @param definition 任务定义
400
+ */
401
+ createTask(definition) {
402
+ validateId(definition.id);
403
+ parseSchedule(definition.schedule);
404
+ const task = {
405
+ ...definition,
406
+ tags: definition.tags || [],
407
+ // 新创建的任务默认是 stopped 状态,需要手动启动或等调度器启动
408
+ status: TaskStatus.STOPPED,
409
+ history: [],
410
+ executionCount: 0
411
+ };
412
+ this.registry.addTask(task);
413
+ this.log(`Task created: ${task.id}`);
414
+ this.emit(SchedulerEvents.TASK_REGISTERED, { taskId: task.id, task });
415
+ this.notify();
416
+ if (this.running) {
417
+ task.status = TaskStatus.IDLE;
418
+ this.scheduleTask(task);
419
+ }
420
+ }
421
+ /**
422
+ * 删除指定 ID 的任务。
423
+ * @param id 任务 ID
424
+ * @returns 如果删除成功返回 true,否则返回 false
425
+ */
426
+ deleteTask(id) {
427
+ this.stopTask(id);
428
+ const deleted = this.registry.deleteTask(id);
429
+ if (deleted) {
430
+ this.log(`Task deleted: ${id}`);
431
+ this.emit(SchedulerEvents.TASK_REMOVED, { taskId: id });
432
+ this.notify();
433
+ }
434
+ return deleted;
435
+ }
436
+ /**
437
+ * 手动启动一个任务(将其置为 idle 状态并重新加入调度)。
438
+ * 即使调度器未启动,也可以单独启动某个任务。
439
+ * @param id 任务 ID
440
+ */
441
+ startTask(id) {
442
+ const task = this.registry.getTask(id);
443
+ if (!task) {
444
+ throw new Error(`Task not found: ${id}`);
445
+ }
446
+ if (task.status === TaskStatus.RUNNING) {
447
+ return;
448
+ }
449
+ const existingHandle = this.timers.get(id);
450
+ if (existingHandle) {
451
+ this.getTimerStrategy(task).cancel(existingHandle);
452
+ this.timers.delete(id);
453
+ }
454
+ task.status = TaskStatus.IDLE;
455
+ this.log(`Starting task: ${id}`);
456
+ this.emit(SchedulerEvents.TASK_STARTED, { taskId: id, task });
457
+ this.notify();
458
+ this.scheduleTaskForce(task);
459
+ }
460
+ /**
461
+ * 强制调度任务,即使调度器未启动
462
+ */
463
+ scheduleTaskForce(task) {
464
+ var _a;
465
+ try {
466
+ const nextRun = getNextRun(task.schedule, {
467
+ timezone: ((_a = task.options) == null ? void 0 : _a.timezone) || this.config.timezone,
468
+ lastRun: task.lastRun
469
+ });
470
+ task.nextRun = nextRun.getTime();
471
+ const now = Date.now();
472
+ const delay = Math.max(0, nextRun.getTime() - now);
473
+ this.log(`Scheduling task ${task.id} for ${nextRun.toISOString()} (in ${delay}ms)`);
474
+ const timerStrategy = this.getTimerStrategy(task);
475
+ const handle = timerStrategy.schedule(() => {
476
+ this.executeTaskIndividual(task);
477
+ }, delay);
478
+ this.timers.set(task.id, handle);
479
+ } catch (err) {
480
+ this.log(`Error scheduling task ${task.id}: ${err}`);
481
+ task.status = TaskStatus.ERROR;
482
+ this.notify();
483
+ }
484
+ }
485
+ /**
486
+ * 执行单个任务(用于独立启动的任务)
487
+ */
488
+ async executeTaskIndividual(task, attempt = 0) {
489
+ var _a, _b;
490
+ this.timers.delete(task.id);
491
+ if (task.status === TaskStatus.STOPPED) return;
492
+ task.status = TaskStatus.RUNNING;
493
+ task.lastRun = Date.now();
494
+ task.executionCount = (task.executionCount || 0) + 1;
495
+ const startTime = Date.now();
496
+ this.log(`Executing task: ${task.id} (Attempt ${attempt})`);
497
+ this.emit(SchedulerEvents.TASK_STARTED, { taskId: task.id, task });
498
+ this.notify();
499
+ try {
500
+ await task.handler();
501
+ const duration = Date.now() - startTime;
502
+ this.recordHistory(task, {
503
+ timestamp: startTime,
504
+ duration,
505
+ success: true
506
+ });
507
+ task.status = TaskStatus.IDLE;
508
+ this.log(`Task execution success: ${task.id}`);
509
+ this.emit(SchedulerEvents.TASK_COMPLETED, { taskId: task.id, task, duration });
510
+ this.notify();
511
+ this.scheduleTaskForce(task);
512
+ } catch (err) {
513
+ const duration = Date.now() - startTime;
514
+ this.recordHistory(task, {
515
+ timestamp: startTime,
516
+ duration,
517
+ success: false,
518
+ error: err.message
519
+ });
520
+ this.log(`Task execution failed: ${task.id} - ${err.message}`);
521
+ this.emit(SchedulerEvents.TASK_FAILED, { taskId: task.id, task, error: err.message, duration });
522
+ if ((_a = task.options) == null ? void 0 : _a.onError) {
523
+ try {
524
+ task.options.onError(err, task.id);
525
+ } catch (e) {
526
+ this.log(`onError callback failed: ${e}`);
527
+ }
528
+ }
529
+ const retryDelay = RetryStrategy.getDelay(attempt, (_b = task.options) == null ? void 0 : _b.retry);
530
+ if (retryDelay >= 0) {
531
+ this.log(`Retrying task ${task.id} in ${retryDelay}ms (Attempt ${attempt + 1})`);
532
+ const timerStrategy = this.getTimerStrategy(task);
533
+ const handle = timerStrategy.schedule(() => {
534
+ this.executeTaskIndividual(task, attempt + 1);
535
+ }, retryDelay);
536
+ this.timers.set(task.id, handle);
537
+ task.status = TaskStatus.ERROR;
538
+ this.notify();
539
+ } else {
540
+ task.status = TaskStatus.ERROR;
541
+ this.notify();
542
+ this.scheduleTaskForce(task);
543
+ }
544
+ }
545
+ }
546
+ /**
547
+ * 停止一个任务(取消当前的定时器并标记为 stopped)。
548
+ * @param id 任务 ID
549
+ */
550
+ stopTask(id) {
551
+ const task = this.registry.getTask(id);
552
+ const handle = this.timers.get(id);
553
+ if (handle && task) {
554
+ this.getTimerStrategy(task).cancel(handle);
555
+ this.timers.delete(id);
556
+ } else if (handle) {
557
+ this.defaultTimerStrategy.cancel(handle);
558
+ this.timers.delete(id);
559
+ }
560
+ if (task) {
561
+ task.status = TaskStatus.STOPPED;
562
+ this.log(`Task stopped: ${id}`);
563
+ this.emit(SchedulerEvents.TASK_STOPPED, { taskId: id, task });
564
+ this.notify();
565
+ }
566
+ }
567
+ /**
568
+ * 获取任务信息。
569
+ * @param id 任务 ID
570
+ */
571
+ getTask(id) {
572
+ return this.registry.getTask(id);
573
+ }
574
+ /**
575
+ * 获取所有任务。
576
+ */
577
+ getAllTasks() {
578
+ return this.registry.getAllTasks();
579
+ }
580
+ /**
581
+ * 获取调度器运行状态。
582
+ */
583
+ isRunning() {
584
+ return this.running;
585
+ }
586
+ /**
587
+ * 获取任务的实际驱动方式。
588
+ * 优先使用任务级配置,其次使用全局配置,默认为 'worker'。
589
+ */
590
+ getTaskDriver(id) {
591
+ var _a;
592
+ const task = this.registry.getTask(id);
593
+ if (!task) return this.config.driver || "worker";
594
+ return ((_a = task.options) == null ? void 0 : _a.driver) || this.config.driver || "worker";
595
+ }
596
+ /**
597
+ * 获取全局驱动配置。
598
+ */
599
+ getGlobalDriver() {
600
+ return this.config.driver || "worker";
601
+ }
602
+ /**
603
+ * 手动触发任务执行(忽略调度器状态,立即执行一次)。
604
+ * 执行完成后恢复到之前的状态。
605
+ * @param id 任务 ID
606
+ */
607
+ async triggerTask(id) {
608
+ const task = this.getTask(id);
609
+ if (!task) return;
610
+ if (task.status === TaskStatus.RUNNING) return;
611
+ const previousStatus = task.status;
612
+ task.status = TaskStatus.RUNNING;
613
+ task.lastRun = Date.now();
614
+ task.executionCount = (task.executionCount || 0) + 1;
615
+ const startTime = Date.now();
616
+ this.log(`Triggering task: ${task.id}`);
617
+ this.emit(SchedulerEvents.TASK_STARTED, { taskId: task.id, task });
618
+ this.notify();
619
+ try {
620
+ await task.handler();
621
+ const duration = Date.now() - startTime;
622
+ this.recordHistory(task, {
623
+ timestamp: startTime,
624
+ duration,
625
+ success: true
626
+ });
627
+ task.status = previousStatus;
628
+ this.log(`Task trigger success: ${task.id}`);
629
+ this.emit(SchedulerEvents.TASK_COMPLETED, { taskId: task.id, task, duration });
630
+ this.notify();
631
+ } catch (err) {
632
+ const duration = Date.now() - startTime;
633
+ this.recordHistory(task, {
634
+ timestamp: startTime,
635
+ duration,
636
+ success: false,
637
+ error: err.message
638
+ });
639
+ this.log(`Task trigger failed: ${task.id} - ${err.message}`);
640
+ this.emit(SchedulerEvents.TASK_FAILED, { taskId: task.id, task, error: err.message, duration });
641
+ task.status = previousStatus;
642
+ this.notify();
643
+ }
644
+ }
645
+ /**
646
+ * 启动调度器。
647
+ * 开始处理所有任务(除了手动停止的任务)。
648
+ */
649
+ start() {
650
+ if (this.running) return;
651
+ this.running = true;
652
+ this.log("Scheduler started");
653
+ this.emit(SchedulerEvents.SCHEDULER_STARTED, { running: true });
654
+ this.registry.getAllTasks().forEach((task) => {
655
+ if (task.status === TaskStatus.STOPPED) {
656
+ task.status = TaskStatus.IDLE;
657
+ this.emit(SchedulerEvents.TASK_UPDATED, { taskId: task.id, task });
658
+ }
659
+ if (task.status !== TaskStatus.RUNNING) {
660
+ this.scheduleTask(task);
661
+ }
662
+ });
663
+ this.notify();
664
+ }
665
+ /**
666
+ * 停止调度器。
667
+ * 取消所有正在等待的定时器。
668
+ */
669
+ stop() {
670
+ this.running = false;
671
+ this.log("Scheduler stopped");
672
+ this.emit(SchedulerEvents.SCHEDULER_STOPPED, { running: false });
673
+ this.timers.forEach((handle, taskId) => {
674
+ const task = this.registry.getTask(taskId);
675
+ if (task) {
676
+ this.getTimerStrategy(task).cancel(handle);
677
+ } else {
678
+ this.defaultTimerStrategy.cancel(handle);
679
+ }
680
+ });
681
+ this.timers.clear();
682
+ this.registry.getAllTasks().forEach((task) => {
683
+ if (task.status !== TaskStatus.STOPPED) {
684
+ task.status = TaskStatus.STOPPED;
685
+ this.emit(SchedulerEvents.TASK_UPDATED, { taskId: task.id, task });
686
+ }
687
+ });
688
+ this.notify();
689
+ }
690
+ scheduleTask(task) {
691
+ var _a;
692
+ if (!this.running && task.status !== TaskStatus.RUNNING) return;
693
+ try {
694
+ const nextRun = getNextRun(task.schedule, {
695
+ timezone: ((_a = task.options) == null ? void 0 : _a.timezone) || this.config.timezone,
696
+ lastRun: task.lastRun
697
+ // 对于 interval 任务,需要上次运行时间
698
+ });
699
+ task.nextRun = nextRun.getTime();
700
+ const now = Date.now();
701
+ const delay = Math.max(0, nextRun.getTime() - now);
702
+ this.log(`Scheduling task ${task.id} for ${nextRun.toISOString()} (in ${delay}ms)`);
703
+ const timerStrategy = this.getTimerStrategy(task);
704
+ const handle = timerStrategy.schedule(() => {
705
+ this.executeTask(task);
706
+ }, delay);
707
+ this.timers.set(task.id, handle);
708
+ } catch (err) {
709
+ this.log(`Error scheduling task ${task.id}: ${err}`);
710
+ task.status = TaskStatus.ERROR;
711
+ }
712
+ }
713
+ async executeTask(task, attempt = 0, force = false) {
714
+ var _a, _b;
715
+ this.timers.delete(task.id);
716
+ if (!force && (!this.running || task.status === TaskStatus.STOPPED)) return;
717
+ task.status = TaskStatus.RUNNING;
718
+ task.lastRun = Date.now();
719
+ task.executionCount = (task.executionCount || 0) + 1;
720
+ const startTime = Date.now();
721
+ this.log(`Executing task: ${task.id} (Attempt ${attempt})`);
722
+ this.emit(SchedulerEvents.TASK_STARTED, { taskId: task.id, task });
723
+ this.notify();
724
+ try {
725
+ await task.handler();
726
+ const duration = Date.now() - startTime;
727
+ this.recordHistory(task, {
728
+ timestamp: startTime,
729
+ duration,
730
+ success: true
731
+ });
732
+ task.status = TaskStatus.IDLE;
733
+ this.log(`Task execution success: ${task.id}`);
734
+ this.emit(SchedulerEvents.TASK_COMPLETED, { taskId: task.id, task, duration });
735
+ this.notify();
736
+ this.scheduleTask(task);
737
+ } catch (err) {
738
+ const duration = Date.now() - startTime;
739
+ this.recordHistory(task, {
740
+ timestamp: startTime,
741
+ duration,
742
+ success: false,
743
+ error: err.message
744
+ });
745
+ this.log(`Task execution failed: ${task.id} - ${err.message}`);
746
+ this.emit(SchedulerEvents.TASK_FAILED, { taskId: task.id, task, error: err.message, duration });
747
+ if ((_a = task.options) == null ? void 0 : _a.onError) {
748
+ try {
749
+ task.options.onError(err, task.id);
750
+ } catch (e) {
751
+ this.log(`onError callback failed: ${e}`);
752
+ }
753
+ }
754
+ const retryDelay = RetryStrategy.getDelay(attempt, (_b = task.options) == null ? void 0 : _b.retry);
755
+ if (retryDelay >= 0) {
756
+ this.log(`Retrying task ${task.id} in ${retryDelay}ms (Attempt ${attempt + 1})`);
757
+ const timerStrategy = this.getTimerStrategy(task);
758
+ const handle = timerStrategy.schedule(() => {
759
+ this.executeTask(task, attempt + 1);
760
+ }, retryDelay);
761
+ this.timers.set(task.id, handle);
762
+ task.status = TaskStatus.ERROR;
763
+ this.notify();
764
+ } else {
765
+ task.status = TaskStatus.ERROR;
766
+ this.notify();
767
+ this.scheduleTask(task);
768
+ }
769
+ }
770
+ }
771
+ recordHistory(task, record) {
772
+ task.history.unshift(record);
773
+ if (this.config.maxHistory && task.history.length > this.config.maxHistory) {
774
+ task.history.pop();
775
+ }
776
+ }
777
+ log(message) {
778
+ if (this.config.debug) {
779
+ console.log(`[HyperScheduler] ${message}`);
780
+ }
781
+ }
782
+ };
783
+ class NodeTimer {
784
+ schedule(callback, delay) {
785
+ return setTimeout(callback, delay);
786
+ }
787
+ cancel(handle) {
788
+ clearTimeout(handle);
789
+ }
790
+ }
791
+ const encodedJs = "IWZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2NvbnN0IGU9bmV3IE1hcDtzZWxmLm9ubWVzc2FnZT1zPT57Y29uc3R7aWQ6dCxkZWxheTpjLHR5cGU6bH09cy5kYXRhO2lmKCJzY2hlZHVsZSI9PT1sKXtjb25zdCBzPXNlbGYuc2V0VGltZW91dCgoKT0+e3NlbGYucG9zdE1lc3NhZ2Uoe2lkOnQsdHlwZToidGljayJ9KSxlLmRlbGV0ZSh0KX0sYyk7ZS5zZXQodCxzKX1lbHNlIGlmKCJjYW5jZWwiPT09bCl7Y29uc3Qgcz1lLmdldCh0KTt2b2lkIDAhPT1zJiYoc2VsZi5jbGVhclRpbWVvdXQocyksZS5kZWxldGUodCkpfX19KCk7Cg==";
792
+ const decodeBase64 = (base64) => Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
793
+ const blob = typeof self !== "undefined" && self.Blob && new Blob([decodeBase64(encodedJs)], { type: "text/javascript;charset=utf-8" });
794
+ function WorkerWrapper(options) {
795
+ let objURL;
796
+ try {
797
+ objURL = blob && (self.URL || self.webkitURL).createObjectURL(blob);
798
+ if (!objURL) throw "";
799
+ const worker = new Worker(objURL, {
800
+ name: options == null ? void 0 : options.name
801
+ });
802
+ worker.addEventListener("error", () => {
803
+ (self.URL || self.webkitURL).revokeObjectURL(objURL);
804
+ });
805
+ return worker;
806
+ } catch (e) {
807
+ return new Worker(
808
+ "data:text/javascript;base64," + encodedJs,
809
+ {
810
+ name: options == null ? void 0 : options.name
811
+ }
812
+ );
813
+ } finally {
814
+ objURL && (self.URL || self.webkitURL).revokeObjectURL(objURL);
815
+ }
816
+ }
817
+ class BrowserTimer {
818
+ constructor() {
819
+ __publicField(this, "worker");
820
+ __publicField(this, "callbacks");
821
+ __publicField(this, "nextId");
822
+ if (typeof WorkerWrapper !== "undefined") {
823
+ this.worker = new WorkerWrapper();
824
+ } else {
825
+ this.worker = {
826
+ postMessage: () => {
827
+ },
828
+ onmessage: null,
829
+ terminate: () => {
830
+ },
831
+ addEventListener: () => {
832
+ },
833
+ removeEventListener: () => {
834
+ },
835
+ dispatchEvent: () => false
836
+ };
837
+ }
838
+ this.callbacks = /* @__PURE__ */ new Map();
839
+ this.nextId = 1;
840
+ this.worker.onmessage = (e) => {
841
+ const { id, type } = e.data;
842
+ if (type === "tick") {
843
+ const callback = this.callbacks.get(id);
844
+ if (callback) {
845
+ callback();
846
+ this.callbacks.delete(id);
847
+ }
848
+ }
849
+ };
850
+ }
851
+ /**
852
+ * 安排一个定时任务。
853
+ * 向 Web Worker 发送消息以注册计时器。
854
+ */
855
+ schedule(callback, delay) {
856
+ const id = this.nextId++;
857
+ this.callbacks.set(id, callback);
858
+ this.worker.postMessage({ id, delay, type: "schedule" });
859
+ return id;
860
+ }
861
+ /**
862
+ * 取消一个定时任务。
863
+ */
864
+ cancel(handle) {
865
+ this.worker.postMessage({ id: handle, type: "cancel" });
866
+ this.callbacks.delete(handle);
867
+ }
868
+ }
869
+ class MainThreadTimer {
870
+ schedule(callback, delay) {
871
+ return window.setTimeout(callback, delay);
872
+ }
873
+ cancel(handle) {
874
+ window.clearTimeout(handle);
875
+ }
876
+ }
877
+ class DevTools {
878
+ constructor(options = {}) {
879
+ __publicField(this, "name", "DevTools");
880
+ __publicField(this, "options");
881
+ this.options = options;
882
+ }
883
+ init(scheduler) {
884
+ if (typeof window === "undefined" || typeof window.document === "undefined") {
885
+ return;
886
+ }
887
+ this.mount(scheduler);
888
+ }
889
+ async mount(scheduler) {
890
+ try {
891
+ await import("./devtools-4xVHTaUz.js");
892
+ await customElements.whenDefined("hs-devtools");
893
+ this.setupElement(scheduler);
894
+ } catch (e) {
895
+ console.error("[DevTools] Failed to mount:", e);
896
+ }
897
+ }
898
+ setupElement(scheduler) {
899
+ try {
900
+ let el = document.querySelector("hs-devtools");
901
+ if (el) {
902
+ el.remove();
903
+ }
904
+ el = document.createElement("hs-devtools");
905
+ const options = this.options;
906
+ console.log("[DevTools Plugin] options:", JSON.stringify(options));
907
+ if (options.theme) el.setAttribute("theme", options.theme);
908
+ if (options.dockPosition) el.setAttribute("dock", options.dockPosition);
909
+ if (options.language) el.setAttribute("language", options.language);
910
+ if (options.defaultZoom) el.setAttribute("default-zoom", options.defaultZoom.toString());
911
+ if (options.trigger) {
912
+ if (options.trigger.backgroundColor) el.setAttribute("trigger-bg", options.trigger.backgroundColor);
913
+ if (options.trigger.textColor) el.setAttribute("trigger-color", options.trigger.textColor);
914
+ if (options.trigger.position) el.setAttribute("trigger-position", options.trigger.position);
915
+ }
916
+ console.log("[DevTools Plugin] attributes set:", el.getAttribute("dock"), el.getAttribute("trigger-position"));
917
+ document.body.appendChild(el);
918
+ if (typeof el.setScheduler === "function") {
919
+ el.setScheduler({
920
+ getTasks: () => {
921
+ return scheduler.getAllTasks().map((task) => ({
922
+ id: task.id,
923
+ status: task.status,
924
+ lastRun: task.lastRun || null,
925
+ nextRun: task.nextRun || null,
926
+ executionCount: task.executionCount || 0,
927
+ schedule: task.schedule,
928
+ tags: task.tags || [],
929
+ error: task.status === TaskStatus.ERROR ? "Execution failed" : null,
930
+ driver: scheduler.getTaskDriver(task.id)
931
+ }));
932
+ },
933
+ on: (evt, handler) => {
934
+ return scheduler.on(evt, handler);
935
+ },
936
+ isRunning: () => scheduler.isRunning(),
937
+ trigger: (id) => scheduler.triggerTask(id),
938
+ pause: (id) => scheduler.stopTask(id),
939
+ resume: (id) => scheduler.startTask(id),
940
+ remove: (id) => scheduler.deleteTask(id)
941
+ });
942
+ } else {
943
+ console.warn("[DevTools] hs-devtools element does not have setScheduler method.");
944
+ }
945
+ } catch (e) {
946
+ console.error("[DevTools] Failed to setup element:", e);
947
+ }
948
+ }
949
+ }
950
+ const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
951
+ let workerTimerInstance = null;
952
+ let mainTimerInstance = null;
953
+ function getDefaultTimerStrategy(config) {
954
+ if (isBrowser) {
955
+ const driver = (config == null ? void 0 : config.driver) || "worker";
956
+ if (driver === "main") {
957
+ if (!mainTimerInstance) mainTimerInstance = new MainThreadTimer();
958
+ return mainTimerInstance;
959
+ }
960
+ if (!workerTimerInstance) workerTimerInstance = new BrowserTimer();
961
+ return workerTimerInstance;
962
+ }
963
+ return new NodeTimer();
964
+ }
965
+ function createTimerStrategyFactory() {
966
+ if (!isBrowser) return void 0;
967
+ return (driver) => {
968
+ if (driver === "main") {
969
+ if (!mainTimerInstance) mainTimerInstance = new MainThreadTimer();
970
+ return mainTimerInstance;
971
+ }
972
+ if (!workerTimerInstance) workerTimerInstance = new BrowserTimer();
973
+ return workerTimerInstance;
974
+ };
975
+ }
976
+ class Scheduler2 extends Scheduler$1 {
977
+ /**
978
+ * 创建调度器实例。
979
+ * @param config 配置项
980
+ */
981
+ constructor(config) {
982
+ super(
983
+ getDefaultTimerStrategy(config),
984
+ config,
985
+ createTimerStrategyFactory()
986
+ );
987
+ }
988
+ }
989
+ export {
990
+ Scheduler$1 as CoreScheduler,
991
+ DevTools,
992
+ Scheduler2 as Scheduler,
993
+ SchedulerEvents,
994
+ TaskStatus
995
+ };