@zudojs/scheduler 0.1.0 → 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 (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -16
  3. package/dist/index.d.ts +7 -3
  4. package/dist/index.js +4 -3
  5. package/dist/scheduler/constants/index.d.ts +1 -1
  6. package/dist/scheduler/constants/index.js +1 -1
  7. package/dist/scheduler/constants/schedulerConstants.core.d.ts +0 -4
  8. package/dist/scheduler/constants/schedulerConstants.core.js +0 -4
  9. package/dist/scheduler/duration/duration.parser.d.ts +13 -5
  10. package/dist/scheduler/duration/duration.parser.js +54 -19
  11. package/dist/scheduler/errors/index.d.ts +1 -1
  12. package/dist/scheduler/errors/index.js +1 -1
  13. package/dist/scheduler/errors/scheduler.errors.d.ts +7 -1
  14. package/dist/scheduler/errors/scheduler.errors.js +7 -1
  15. package/dist/scheduler/executor/index.d.ts +1 -1
  16. package/dist/scheduler/executor/index.js +1 -1
  17. package/dist/scheduler/executor/jobExecutor.core.d.ts +24 -4
  18. package/dist/scheduler/executor/jobExecutor.core.js +151 -28
  19. package/dist/scheduler/job/jobOptions.type.d.ts +11 -0
  20. package/dist/scheduler/priorityQueue/schedulerPriorityQueue.core.d.ts +12 -0
  21. package/dist/scheduler/priorityQueue/schedulerPriorityQueue.core.js +32 -5
  22. package/dist/scheduler/registry/jobRegistry.core.d.ts +8 -0
  23. package/dist/scheduler/registry/jobRegistry.core.js +14 -0
  24. package/dist/scheduler/schedule/schedule.type.d.ts +11 -6
  25. package/dist/scheduler/schedule/schedule.type.js +10 -1
  26. package/dist/scheduler/schedule/scheduleOptions.type.d.ts +24 -1
  27. package/dist/scheduler/scheduleHandle/index.d.ts +1 -0
  28. package/dist/scheduler/scheduleHandle/scheduleHandle.type.d.ts +26 -3
  29. package/dist/scheduler/scheduleHandle/scheduleHandle.type.js +23 -7
  30. package/dist/scheduler/scheduler.core.d.ts +120 -11
  31. package/dist/scheduler/scheduler.core.js +424 -53
  32. package/dist/scheduler/trigger/cron.parser.d.ts +44 -0
  33. package/dist/scheduler/trigger/cron.parser.js +256 -0
  34. package/dist/scheduler/trigger/index.d.ts +2 -0
  35. package/dist/scheduler/trigger/index.js +1 -0
  36. package/dist/scheduler/trigger/schedulerTrigger.core.d.ts +22 -1
  37. package/dist/scheduler/trigger/schedulerTrigger.core.js +69 -6
  38. package/dist/scheduler/types/index.d.ts +12 -1
  39. package/dist/scheduler/types/index.js +7 -0
  40. package/dist/scheduler/types/schedulerTypes.core.d.ts +7 -87
  41. package/dist/scheduler/types/schedulerTypes.core.js +7 -1
  42. package/package.json +22 -15
  43. package/dist/.tsbuildinfo +0 -1
  44. package/dist/index.d.ts.map +0 -1
  45. package/dist/index.js.map +0 -1
  46. package/dist/scheduler/clock/index.d.ts.map +0 -1
  47. package/dist/scheduler/clock/index.js.map +0 -1
  48. package/dist/scheduler/clock/schedulerClock.type.d.ts.map +0 -1
  49. package/dist/scheduler/clock/schedulerClock.type.js.map +0 -1
  50. package/dist/scheduler/constants/index.d.ts.map +0 -1
  51. package/dist/scheduler/constants/index.js.map +0 -1
  52. package/dist/scheduler/constants/schedulerConstants.core.d.ts.map +0 -1
  53. package/dist/scheduler/constants/schedulerConstants.core.js.map +0 -1
  54. package/dist/scheduler/duration/duration.parser.d.ts.map +0 -1
  55. package/dist/scheduler/duration/duration.parser.js.map +0 -1
  56. package/dist/scheduler/duration/index.d.ts.map +0 -1
  57. package/dist/scheduler/duration/index.js.map +0 -1
  58. package/dist/scheduler/errors/index.d.ts.map +0 -1
  59. package/dist/scheduler/errors/index.js.map +0 -1
  60. package/dist/scheduler/errors/scheduler.errors.d.ts.map +0 -1
  61. package/dist/scheduler/errors/scheduler.errors.js.map +0 -1
  62. package/dist/scheduler/executor/index.d.ts.map +0 -1
  63. package/dist/scheduler/executor/index.js.map +0 -1
  64. package/dist/scheduler/executor/jobExecutor.core.d.ts.map +0 -1
  65. package/dist/scheduler/executor/jobExecutor.core.js.map +0 -1
  66. package/dist/scheduler/job/index.d.ts.map +0 -1
  67. package/dist/scheduler/job/index.js.map +0 -1
  68. package/dist/scheduler/job/jobContext.type.d.ts.map +0 -1
  69. package/dist/scheduler/job/jobContext.type.js.map +0 -1
  70. package/dist/scheduler/job/jobDefinition.type.d.ts.map +0 -1
  71. package/dist/scheduler/job/jobDefinition.type.js.map +0 -1
  72. package/dist/scheduler/job/jobHandler.type.d.ts.map +0 -1
  73. package/dist/scheduler/job/jobHandler.type.js.map +0 -1
  74. package/dist/scheduler/job/jobOptions.type.d.ts.map +0 -1
  75. package/dist/scheduler/job/jobOptions.type.js.map +0 -1
  76. package/dist/scheduler/priorityQueue/index.d.ts.map +0 -1
  77. package/dist/scheduler/priorityQueue/index.js.map +0 -1
  78. package/dist/scheduler/priorityQueue/schedulerPriorityQueue.core.d.ts.map +0 -1
  79. package/dist/scheduler/priorityQueue/schedulerPriorityQueue.core.js.map +0 -1
  80. package/dist/scheduler/registry/index.d.ts.map +0 -1
  81. package/dist/scheduler/registry/index.js.map +0 -1
  82. package/dist/scheduler/registry/jobRegistry.core.d.ts.map +0 -1
  83. package/dist/scheduler/registry/jobRegistry.core.js.map +0 -1
  84. package/dist/scheduler/schedule/index.d.ts.map +0 -1
  85. package/dist/scheduler/schedule/index.js.map +0 -1
  86. package/dist/scheduler/schedule/schedule.type.d.ts.map +0 -1
  87. package/dist/scheduler/schedule/schedule.type.js.map +0 -1
  88. package/dist/scheduler/schedule/scheduleOptions.type.d.ts.map +0 -1
  89. package/dist/scheduler/schedule/scheduleOptions.type.js.map +0 -1
  90. package/dist/scheduler/scheduleHandle/index.d.ts.map +0 -1
  91. package/dist/scheduler/scheduleHandle/index.js.map +0 -1
  92. package/dist/scheduler/scheduleHandle/scheduleHandle.type.d.ts.map +0 -1
  93. package/dist/scheduler/scheduleHandle/scheduleHandle.type.js.map +0 -1
  94. package/dist/scheduler/scheduler.core.d.ts.map +0 -1
  95. package/dist/scheduler/scheduler.core.js.map +0 -1
  96. package/dist/scheduler/trigger/index.d.ts.map +0 -1
  97. package/dist/scheduler/trigger/index.js.map +0 -1
  98. package/dist/scheduler/trigger/schedulerTrigger.core.d.ts.map +0 -1
  99. package/dist/scheduler/trigger/schedulerTrigger.core.js.map +0 -1
  100. package/dist/scheduler/trigger/trigger.type.d.ts.map +0 -1
  101. package/dist/scheduler/trigger/trigger.type.js.map +0 -1
  102. package/dist/scheduler/types/index.d.ts.map +0 -1
  103. package/dist/scheduler/types/index.js.map +0 -1
  104. package/dist/scheduler/types/schedulerTypes.core.d.ts.map +0 -1
  105. package/dist/scheduler/types/schedulerTypes.core.js.map +0 -1
@@ -1,8 +1,10 @@
1
1
  import { createJobContext } from "../job/jobContext.type.js";
2
- import { SchedulerJobExecutionError, SchedulerJobCancelledError, } from "../errors/scheduler.errors.js";
3
- import { DEFAULT_JOB_TIMEOUT } from "../constants/schedulerConstants.core.js";
2
+ import { SchedulerJobExecutionError, SchedulerJobCancelledError, SchedulerJobTimeoutError, } from "../errors/scheduler.errors.js";
3
+ import { DEFAULT_JOB_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_RETRY_DELAY, } from "../constants/schedulerConstants.core.js";
4
+ /** Marker attached to the rejection a timeout produces, carrying its budget. */
5
+ const TIMEOUT = Symbol("scheduler.timeout");
4
6
  /**
5
- * Executes a job with timeout support.
7
+ * Executes a job with timeout, cancellation and retry support.
6
8
  */
7
9
  export class JobExecutor {
8
10
  clock;
@@ -10,41 +12,162 @@ export class JobExecutor {
10
12
  this.clock = clock;
11
13
  }
12
14
  /**
13
- * Executes a job.
15
+ * Executes a job, retrying according to its retry policy.
16
+ *
17
+ * @param job - The job definition.
18
+ * @param executionId - Identifier for this execution.
19
+ * @param scheduledAt - The time the job was scheduled to run.
20
+ * @param attempt - The attempt number to start from (1-based).
21
+ * @param signal - Signal that aborts the job and stops further retries.
22
+ * @param data - Optional payload handed to the handler.
23
+ * @returns The execution result.
14
24
  */
15
- async execute(job, executionId, scheduledAt, attempt, signal) {
16
- const timeout = job.options?.timeout ?? DEFAULT_JOB_TIMEOUT;
17
- const context = createJobContext(job.id, executionId, scheduledAt, this.clock.now(), attempt, undefined, signal);
18
- try {
19
- await this.withTimeout(() => Promise.resolve(job.handler(context)), timeout, signal);
20
- return { success: true };
21
- }
22
- catch (error) {
25
+ async execute(job, executionId, scheduledAt, attempt, signal, data) {
26
+ const retry = job.options?.retry;
27
+ const maxAttempts = Math.max(1, retry?.attempts ?? 1);
28
+ let currentAttempt = attempt;
29
+ let lastError;
30
+ for (let i = 0; i < maxAttempts; i++, currentAttempt++) {
23
31
  if (signal.aborted) {
24
32
  throw new SchedulerJobCancelledError("Job was cancelled via signal.", job.id);
25
33
  }
26
- throw new SchedulerJobExecutionError(error instanceof Error ? error.message : "Unknown job execution error.", job.id);
34
+ try {
35
+ await this.runOnce(job, executionId, scheduledAt, currentAttempt, signal, data);
36
+ return { success: true };
37
+ }
38
+ catch (error) {
39
+ if (signal.aborted) {
40
+ throw new SchedulerJobCancelledError("Job was cancelled via signal.", job.id);
41
+ }
42
+ lastError = this.classify(error, job.id);
43
+ // Last attempt, or nothing to retry with.
44
+ if (i === maxAttempts - 1)
45
+ break;
46
+ const delay = retryDelay(retry, currentAttempt);
47
+ if (delay > 0) {
48
+ await sleep(delay, signal);
49
+ }
50
+ }
51
+ }
52
+ throw (lastError ??
53
+ new SchedulerJobExecutionError("Unknown job execution error.", job.id));
54
+ }
55
+ /** Runs the handler once, under a timeout that also aborts it. */
56
+ async runOnce(job, executionId, scheduledAt, attempt, signal, data) {
57
+ const timeout = job.options?.timeout ?? DEFAULT_JOB_TIMEOUT;
58
+ // A dedicated controller per attempt, chained to the caller's signal, so a
59
+ // timeout actually aborts the handler instead of only rejecting the
60
+ // wrapper while the work carries on.
61
+ const controller = new AbortController();
62
+ const onParentAbort = () => controller.abort(signal.reason);
63
+ if (signal.aborted) {
64
+ controller.abort(signal.reason);
27
65
  }
66
+ else {
67
+ signal.addEventListener("abort", onParentAbort, { once: true });
68
+ }
69
+ const context = createJobContext(job.id, executionId, scheduledAt, this.clock.now(), attempt, data, controller.signal);
70
+ try {
71
+ await this.withTimeout(() => Promise.resolve(job.handler(context)), timeout, controller);
72
+ }
73
+ finally {
74
+ signal.removeEventListener("abort", onParentAbort);
75
+ }
76
+ }
77
+ /** Maps a thrown value onto the scheduler's error taxonomy. */
78
+ classify(error, jobId) {
79
+ const budget = typeof error === "object" && error !== null
80
+ ? error[TIMEOUT]
81
+ : undefined;
82
+ if (typeof budget === "number") {
83
+ // SchedulerJobTimeoutError builds its own message from the budget.
84
+ return new SchedulerJobTimeoutError(budget, jobId);
85
+ }
86
+ if (error instanceof SchedulerJobCancelledError) {
87
+ return error;
88
+ }
89
+ // Carry the original error rather than flattening it to a message string,
90
+ // which used to discard the stack and any nested cause. The published
91
+ // error class takes no options bag, so the cause is attached directly.
92
+ const wrapped = new SchedulerJobExecutionError(error instanceof Error ? error.message : "Unknown job execution error.", jobId);
93
+ Object.defineProperty(wrapped, "cause", {
94
+ value: error,
95
+ writable: true,
96
+ enumerable: false,
97
+ configurable: true,
98
+ });
99
+ return wrapped;
28
100
  }
29
101
  /**
30
- * Wraps a promise with a timeout using AbortSignal.
102
+ * Wraps an operation with a timeout that aborts it.
31
103
  */
32
- async withTimeout(operation, timeout, signal) {
33
- return new Promise((resolve, reject) => {
34
- const timer = setTimeout(() => {
35
- reject(new Error(`Job timed out after ${timeout}ms.`));
36
- }, timeout);
37
- Promise.resolve()
38
- .then(() => Promise.resolve(operation()))
39
- .then((result) => {
40
- clearTimeout(timer);
41
- resolve(result);
42
- })
43
- .catch((error) => {
44
- clearTimeout(timer);
104
+ async withTimeout(operation, timeout, controller) {
105
+ let timer;
106
+ const timeoutPromise = new Promise((_resolve, reject) => {
107
+ timer = setTimeout(() => {
108
+ const error = new Error(`Job timed out after ${timeout}ms.`);
109
+ error[TIMEOUT] = timeout;
110
+ // Abort first so a handler that observes its signal can wind down.
111
+ controller.abort(error);
45
112
  reject(error);
46
- });
113
+ }, timeout);
114
+ if (timer.unref)
115
+ timer.unref();
47
116
  });
117
+ try {
118
+ return await Promise.race([operation(), timeoutPromise]);
119
+ }
120
+ finally {
121
+ if (timer)
122
+ clearTimeout(timer);
123
+ }
48
124
  }
49
125
  }
126
+ /**
127
+ * Computes the delay before the next retry attempt.
128
+ *
129
+ * `RetryPolicy` has carried strategy, delay, maxDelay and jitter since the
130
+ * package was written; none of it was ever applied.
131
+ */
132
+ export function retryDelay(policy, attempt) {
133
+ if (!policy)
134
+ return 0;
135
+ const base = policy.delay ?? DEFAULT_RETRY_DELAY;
136
+ const attempts = Math.min(attempt, DEFAULT_MAX_RETRIES * 4);
137
+ let delay;
138
+ switch (policy.strategy) {
139
+ case "linear":
140
+ delay = base * attempts;
141
+ break;
142
+ case "exponential":
143
+ delay = base * Math.pow(2, Math.max(0, attempts - 1));
144
+ break;
145
+ case "fixed":
146
+ default:
147
+ delay = base;
148
+ break;
149
+ }
150
+ if (policy.maxDelay !== undefined) {
151
+ delay = Math.min(delay, policy.maxDelay);
152
+ }
153
+ if (policy.jitter) {
154
+ // Full jitter: spreads a thundering herd of retries across the window.
155
+ delay = Math.random() * delay;
156
+ }
157
+ return Math.max(0, Math.round(delay));
158
+ }
159
+ /** Sleeps, resolving early if the signal aborts. */
160
+ function sleep(ms, signal) {
161
+ return new Promise((resolve) => {
162
+ const timer = setTimeout(finish, ms);
163
+ if (timer.unref)
164
+ timer.unref();
165
+ function finish() {
166
+ clearTimeout(timer);
167
+ signal.removeEventListener("abort", finish);
168
+ resolve();
169
+ }
170
+ signal.addEventListener("abort", finish, { once: true });
171
+ });
172
+ }
50
173
  //# sourceMappingURL=jobExecutor.core.js.map
@@ -3,9 +3,20 @@ import type { OverlapPolicy } from "../types/schedulerTypes.core.js";
3
3
  * Options for a scheduled job.
4
4
  */
5
5
  export interface JobOptions {
6
+ /** Per-execution timeout in ms. Default: 30 000. */
6
7
  readonly timeout?: number;
8
+ /** Retry policy applied to a failing execution. */
7
9
  readonly retry?: RetryPolicy;
10
+ /**
11
+ * Maximum executions of this job in flight at once, across every schedule
12
+ * that fires it. A fire time arriving at the ceiling is held and dispatched
13
+ * as soon as an execution finishes.
14
+ */
8
15
  readonly concurrency?: number;
16
+ /**
17
+ * Default overlap policy for every schedule of this job.
18
+ * `ScheduleOptions.overlap` overrides it.
19
+ */
9
20
  readonly overlap?: OverlapPolicy;
10
21
  }
11
22
  /**
@@ -2,6 +2,10 @@ import type { Schedule } from "../schedule/schedule.type.js";
2
2
  /**
3
3
  * Priority queue using a min heap for scheduling.
4
4
  *
5
+ * Ordered by fire time, with `ScheduleOptions.priority` breaking ties: a
6
+ * higher priority runs first among schedules due at the same instant. That
7
+ * option used to be documented as "reserved" and read by nothing at all.
8
+ *
5
9
  * Provides O(log n) insertion and removal, and O(1) peek.
6
10
  */
7
11
  export declare class PriorityQueue {
@@ -22,6 +26,8 @@ export declare class PriorityQueue {
22
26
  * Removes a schedule by ID.
23
27
  */
24
28
  remove(id: string): boolean;
29
+ /** Returns true when a schedule with the given id is queued. */
30
+ has(id: string): boolean;
25
31
  /**
26
32
  * Returns the number of schedules in the queue.
27
33
  */
@@ -34,6 +40,12 @@ export declare class PriorityQueue {
34
40
  * Clears all schedules from the queue.
35
41
  */
36
42
  clear(): void;
43
+ /**
44
+ * Orders two schedules: earlier fire time first, then higher priority.
45
+ *
46
+ * @returns A negative number when `a` should run before `b`.
47
+ */
48
+ private compare;
37
49
  /**
38
50
  * Bubble up an element to maintain heap property.
39
51
  */
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Priority queue using a min heap for scheduling.
3
3
  *
4
+ * Ordered by fire time, with `ScheduleOptions.priority` breaking ties: a
5
+ * higher priority runs first among schedules due at the same instant. That
6
+ * option used to be documented as "reserved" and read by nothing at all.
7
+ *
4
8
  * Provides O(log n) insertion and removal, and O(1) peek.
5
9
  */
6
10
  export class PriorityQueue {
@@ -9,6 +13,13 @@ export class PriorityQueue {
9
13
  * Inserts a schedule into the priority queue.
10
14
  */
11
15
  enqueue(schedule) {
16
+ const time = schedule.nextRunAt?.getTime();
17
+ if (typeof time !== "number" || Number.isNaN(time)) {
18
+ // Every heap comparison against NaN is false, so an invalid date never
19
+ // sinks: it parks itself at the head of the queue and blocks everything
20
+ // genuinely due behind it.
21
+ throw new RangeError(`Cannot enqueue schedule "${schedule.id}": nextRunAt is not a valid date`);
22
+ }
12
23
  this.heap.push(schedule);
13
24
  this.bubbleUp(this.heap.length - 1);
14
25
  }
@@ -47,11 +58,18 @@ export class PriorityQueue {
47
58
  const last = this.heap.pop();
48
59
  if (index < this.heap.length && last !== undefined) {
49
60
  this.heap[index] = last;
61
+ // Bubble first; if it moved, the element now at `index` is an ancestor
62
+ // of that subtree and cannot sink, so sinking is a no-op. If it did not
63
+ // move, sinking is the correction that is needed.
50
64
  this.bubbleUp(index);
51
65
  this.sinkDown(index);
52
66
  }
53
67
  return true;
54
68
  }
69
+ /** Returns true when a schedule with the given id is queued. */
70
+ has(id) {
71
+ return this.heap.some((schedule) => schedule.id === id);
72
+ }
55
73
  /**
56
74
  * Returns the number of schedules in the queue.
57
75
  */
@@ -70,6 +88,17 @@ export class PriorityQueue {
70
88
  clear() {
71
89
  this.heap.length = 0;
72
90
  }
91
+ /**
92
+ * Orders two schedules: earlier fire time first, then higher priority.
93
+ *
94
+ * @returns A negative number when `a` should run before `b`.
95
+ */
96
+ compare(a, b) {
97
+ const byTime = a.nextRunAt.getTime() - b.nextRunAt.getTime();
98
+ if (byTime !== 0)
99
+ return byTime;
100
+ return (b.options?.priority ?? 0) - (a.options?.priority ?? 0);
101
+ }
73
102
  /**
74
103
  * Bubble up an element to maintain heap property.
75
104
  */
@@ -78,7 +107,7 @@ export class PriorityQueue {
78
107
  const parentIndex = Math.floor((index - 1) / 2);
79
108
  const parent = this.heap[parentIndex];
80
109
  const current = this.heap[index];
81
- if (current.nextRunAt.getTime() < parent.nextRunAt.getTime()) {
110
+ if (this.compare(current, parent) < 0) {
82
111
  [this.heap[parentIndex], this.heap[index]] = [current, parent];
83
112
  index = parentIndex;
84
113
  }
@@ -96,13 +125,11 @@ export class PriorityQueue {
96
125
  const left = 2 * index + 1;
97
126
  const right = 2 * index + 2;
98
127
  if (left < this.heap.length &&
99
- this.heap[left].nextRunAt.getTime() <
100
- this.heap[smallest].nextRunAt.getTime()) {
128
+ this.compare(this.heap[left], this.heap[smallest]) < 0) {
101
129
  smallest = left;
102
130
  }
103
131
  if (right < this.heap.length &&
104
- this.heap[right].nextRunAt.getTime() <
105
- this.heap[smallest].nextRunAt.getTime()) {
132
+ this.compare(this.heap[right], this.heap[smallest]) < 0) {
106
133
  smallest = right;
107
134
  }
108
135
  if (smallest !== index) {
@@ -12,6 +12,14 @@ export declare class JobRegistry {
12
12
  * Retrieves a job definition by ID.
13
13
  */
14
14
  get(id: string): JobDefinition | undefined;
15
+ /**
16
+ * Retrieves a job definition by ID, throwing when it is not registered.
17
+ *
18
+ * @param id - The job identifier.
19
+ * @returns The job definition.
20
+ * @throws {SchedulerJobNotFoundError} when no job is registered under `id`.
21
+ */
22
+ getOrThrow(id: string): JobDefinition;
15
23
  /**
16
24
  * Determines whether a job is registered.
17
25
  */
@@ -24,6 +24,20 @@ export class JobRegistry {
24
24
  get(id) {
25
25
  return this.jobs.get(id);
26
26
  }
27
+ /**
28
+ * Retrieves a job definition by ID, throwing when it is not registered.
29
+ *
30
+ * @param id - The job identifier.
31
+ * @returns The job definition.
32
+ * @throws {SchedulerJobNotFoundError} when no job is registered under `id`.
33
+ */
34
+ getOrThrow(id) {
35
+ const job = this.jobs.get(id);
36
+ if (!job) {
37
+ throw new SchedulerJobNotFoundError(id);
38
+ }
39
+ return job;
40
+ }
27
41
  /**
28
42
  * Determines whether a job is registered.
29
43
  */
@@ -1,5 +1,6 @@
1
1
  import type { ScheduleOptions } from "./scheduleOptions.type.js";
2
- import type { ScheduleState } from "../types/schedulerTypes.core.js";
2
+ import type { ScheduleState, ScheduleType } from "../types/schedulerTypes.core.js";
3
+ export type { ScheduleType };
3
4
  /**
4
5
  * Schedule definition.
5
6
  */
@@ -13,12 +14,16 @@ export interface Schedule {
13
14
  readonly state: ScheduleState;
14
15
  readonly options?: ScheduleOptions;
15
16
  }
16
- /**
17
- * Type of schedule.
18
- */
19
- export type ScheduleType = "once" | "delay" | "interval" | "cron";
20
17
  /**
21
18
  * Creates a schedule.
19
+ *
20
+ * @param id - Schedule identifier.
21
+ * @param jobId - The job this schedule fires.
22
+ * @param type - The kind of schedule.
23
+ * @param nextRunAt - The first fire time.
24
+ * @param options - Schedule options.
25
+ * @param expression - The cron expression, for cron schedules.
26
+ * @returns A frozen, active schedule.
22
27
  */
23
- export declare function createSchedule(id: string, jobId: string, type: ScheduleType, nextRunAt: Date, options?: ScheduleOptions): Schedule;
28
+ export declare function createSchedule(id: string, jobId: string, type: ScheduleType, nextRunAt: Date, options?: ScheduleOptions, expression?: string): Schedule;
24
29
  //# sourceMappingURL=schedule.type.d.ts.map
@@ -1,7 +1,15 @@
1
1
  /**
2
2
  * Creates a schedule.
3
+ *
4
+ * @param id - Schedule identifier.
5
+ * @param jobId - The job this schedule fires.
6
+ * @param type - The kind of schedule.
7
+ * @param nextRunAt - The first fire time.
8
+ * @param options - Schedule options.
9
+ * @param expression - The cron expression, for cron schedules.
10
+ * @returns A frozen, active schedule.
3
11
  */
4
- export function createSchedule(id, jobId, type, nextRunAt, options = {}) {
12
+ export function createSchedule(id, jobId, type, nextRunAt, options = {}, expression) {
5
13
  return Object.freeze({
6
14
  id,
7
15
  jobId,
@@ -9,6 +17,7 @@ export function createSchedule(id, jobId, type, nextRunAt, options = {}) {
9
17
  nextRunAt,
10
18
  state: "active",
11
19
  options: Object.freeze(options),
20
+ ...(expression === undefined ? {} : { expression }),
12
21
  });
13
22
  }
14
23
  //# sourceMappingURL=schedule.type.js.map
@@ -1,10 +1,33 @@
1
- import type { MisfirePolicy } from "../types/schedulerTypes.core.js";
1
+ import type { MisfirePolicy, OverlapPolicy } from "../types/schedulerTypes.core.js";
2
2
  /**
3
3
  * Options for a schedule.
4
4
  */
5
5
  export interface ScheduleOptions {
6
+ /** IANA timezone name. Only "UTC" is currently honoured by CronTrigger. */
6
7
  readonly timezone?: string;
8
+ /**
9
+ * How to handle a fire time that has already passed (default: "run-once").
10
+ *
11
+ * `"skip"` refuses the schedule, `"run-once"` fires once immediately and
12
+ * then resumes from now, and `"catch-up"` replays every occurrence that was
13
+ * missed, one per tick, until the schedule has caught up with the clock.
14
+ */
7
15
  readonly misfire?: MisfirePolicy;
16
+ /**
17
+ * How to handle a fire time arriving while a previous run is still going.
18
+ *
19
+ * `"allow"` (default) starts it anyway, `"skip"` drops it, `"queue"` holds
20
+ * it until the running execution finishes, and `"replace"` aborts the
21
+ * running execution. Overrides `JobOptions.overlap` for this schedule.
22
+ */
23
+ readonly overlap?: OverlapPolicy;
24
+ /**
25
+ * Relative priority, breaking ties between schedules due at the same
26
+ * instant. Higher runs first; the default is 0. It does not let a schedule
27
+ * jump ahead of one that is due earlier.
28
+ */
8
29
  readonly priority?: number;
30
+ /** Payload handed to the job handler as `context.data`. */
31
+ readonly data?: unknown;
9
32
  }
10
33
  //# sourceMappingURL=scheduleOptions.type.d.ts.map
@@ -1,3 +1,4 @@
1
1
  export type { ScheduleHandle } from "./scheduleHandle.type.js";
2
2
  export { ScheduleHandleImpl } from "./scheduleHandle.type.js";
3
+ export type { ScheduleHandleBinding } from "./scheduleHandle.type.js";
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -10,14 +10,37 @@ export interface ScheduleHandle {
10
10
  cancel(): Promise<void>;
11
11
  nextRun(): Date | undefined;
12
12
  }
13
+ /**
14
+ * The scheduler operations a handle needs to act on its schedule.
15
+ *
16
+ * Without these the handle is a detached object: `cancel()` sets a local field
17
+ * and the job still runs.
18
+ */
19
+ export interface ScheduleHandleBinding {
20
+ /** Applies a state transition to the live schedule. */
21
+ setState(state: ScheduleState): void;
22
+ /** Reads the live schedule's state, or undefined once it is gone. */
23
+ getState(): ScheduleState | undefined;
24
+ /** Reads the live schedule's next fire time. */
25
+ getNextRun(): Date | undefined;
26
+ /** Aborts any execution of this schedule that is currently in flight. */
27
+ abortRunning(): void;
28
+ }
13
29
  /**
14
30
  * Implementation of ScheduleHandle.
31
+ *
32
+ * Bound to its scheduler, so pause, resume and cancel reach the queue. An
33
+ * unbound handle (the two-argument form) still tracks state locally, which
34
+ * keeps it usable in tests that do not involve a scheduler.
15
35
  */
16
36
  export declare class ScheduleHandleImpl implements ScheduleHandle {
17
37
  readonly id: string;
18
- state: ScheduleState;
19
- private cancelled;
20
- constructor(id: string, state: ScheduleState);
38
+ private _state;
39
+ private readonly binding;
40
+ constructor(id: string, state: ScheduleState, binding?: ScheduleHandleBinding);
41
+ /** The schedule's current state, read from the scheduler when bound. */
42
+ get state(): ScheduleState;
43
+ set state(next: ScheduleState);
21
44
  pause(): Promise<void>;
22
45
  resume(): Promise<void>;
23
46
  cancel(): Promise<void>;
@@ -1,13 +1,26 @@
1
1
  /**
2
2
  * Implementation of ScheduleHandle.
3
+ *
4
+ * Bound to its scheduler, so pause, resume and cancel reach the queue. An
5
+ * unbound handle (the two-argument form) still tracks state locally, which
6
+ * keeps it usable in tests that do not involve a scheduler.
3
7
  */
4
8
  export class ScheduleHandleImpl {
5
9
  id;
6
- state;
7
- cancelled = false;
8
- constructor(id, state) {
10
+ _state;
11
+ binding;
12
+ constructor(id, state, binding) {
9
13
  this.id = id;
10
- this.state = state;
14
+ this._state = state;
15
+ this.binding = binding;
16
+ }
17
+ /** The schedule's current state, read from the scheduler when bound. */
18
+ get state() {
19
+ return this.binding?.getState() ?? this._state;
20
+ }
21
+ set state(next) {
22
+ this._state = next;
23
+ this.binding?.setState(next);
11
24
  }
12
25
  pause() {
13
26
  this.state = "paused";
@@ -18,12 +31,15 @@ export class ScheduleHandleImpl {
18
31
  return Promise.resolve();
19
32
  }
20
33
  cancel() {
21
- this.cancelled = true;
22
- this.state = "cancelled";
34
+ // Abort first: a job already running should stop, not just be removed from
35
+ // future scheduling.
36
+ this.binding?.abortRunning();
37
+ this._state = "cancelled";
38
+ this.binding?.setState("cancelled");
23
39
  return Promise.resolve();
24
40
  }
25
41
  nextRun() {
26
- return undefined;
42
+ return this.binding?.getNextRun();
27
43
  }
28
44
  }
29
45
  //# sourceMappingURL=scheduleHandle.type.js.map