@cleverbrush/scheduler 1.1.10 → 2.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.
- package/README.md +160 -52
- package/dist/ScheduleCalculator.d.ts +43 -1
- package/dist/index.d.ts +6 -9
- package/dist/index.js +2 -408
- package/dist/index.js.map +1 -0
- package/dist/jobRepository.d.ts +13 -1
- package/dist/types.d.ts +281 -274
- package/package.json +15 -4
- package/dist/ScheduleCalculator.js +0 -208
- package/dist/jobRepository.js +0 -79
- package/dist/types.js +0 -115
package/README.md
CHANGED
|
@@ -1,66 +1,174 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @cleverbrush/scheduler
|
|
2
|
+
<!-- coverage-badge-start -->
|
|
3
|
+

|
|
4
|
+
<!-- coverage-badge-end -->
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
A job scheduler for Node.js that runs tasks in worker threads on configurable schedules.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
4
9
|
|
|
5
10
|
```bash
|
|
6
|
-
|
|
11
|
+
npm install @cleverbrush/scheduler
|
|
7
12
|
```
|
|
8
13
|
|
|
9
|
-
This library
|
|
10
|
-
on Node.js v16, but should work on older versions as well.
|
|
14
|
+
This library uses the Node.js `worker_threads` module. It is tested on Node.js v16+.
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
## Quick Start
|
|
13
17
|
|
|
14
18
|
```typescript
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
timeout: 1000 * 60 * 4,
|
|
46
|
-
maxRetries: 3
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
scheduler.start();
|
|
19
|
+
import { JobScheduler } from '@cleverbrush/scheduler';
|
|
20
|
+
|
|
21
|
+
const scheduler = new JobScheduler({
|
|
22
|
+
rootFolder: '/path/to/your/jobs'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
scheduler.addJob({
|
|
26
|
+
id: 'my-job-1',
|
|
27
|
+
path: 'job1.js',
|
|
28
|
+
schedule: {
|
|
29
|
+
every: 'minute',
|
|
30
|
+
interval: 5
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
scheduler.addJob({
|
|
35
|
+
id: 'my-job-2',
|
|
36
|
+
path: 'job2.js',
|
|
37
|
+
schedule: {
|
|
38
|
+
every: 'week',
|
|
39
|
+
interval: 2,
|
|
40
|
+
dayOfWeek: [1, 3, 5],
|
|
41
|
+
hour: 9,
|
|
42
|
+
minute: 30
|
|
43
|
+
},
|
|
44
|
+
timeout: 1000 * 60 * 4,
|
|
45
|
+
maxRetries: 3
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
scheduler.start();
|
|
51
49
|
```
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
## API
|
|
52
|
+
|
|
53
|
+
### `JobScheduler`
|
|
54
|
+
|
|
55
|
+
The main class for scheduling and running jobs. Extends `EventEmitter`.
|
|
56
|
+
|
|
57
|
+
#### Constructor
|
|
55
58
|
|
|
56
59
|
```typescript
|
|
57
|
-
scheduler
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const scheduler = new JobScheduler(props: JobSchedulerProps);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Property | Type | Default | Description |
|
|
64
|
+
| --- | --- | --- | --- |
|
|
65
|
+
| `rootFolder` | `string` | — | Path to the folder containing job files |
|
|
66
|
+
| `defaultTimeZone` | `string` | `'UTC'` | Timezone used for scheduling |
|
|
67
|
+
|
|
68
|
+
#### Methods
|
|
69
|
+
|
|
70
|
+
| Method | Description |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `start()` | Starts the scheduler |
|
|
73
|
+
| `stop()` | Stops the scheduler |
|
|
74
|
+
| `addJob(job)` | Adds a job to the scheduler |
|
|
75
|
+
| `removeJob(jobId)` | Removes a job by ID |
|
|
76
|
+
| `jobExists(jobId)` | Returns `true` if a job with the given ID exists |
|
|
77
|
+
| `status` | Current scheduler status: `'started'` or `'stopped'` |
|
|
78
|
+
|
|
79
|
+
#### Events
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
scheduler.on('job:start', ({ jobId, instanceId, startDate }) => {
|
|
83
|
+
console.log(`Job ${jobId} started`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
scheduler.on('job:end', ({ jobId, instanceId, startDate, endDate, stdout, stderr }) => {
|
|
87
|
+
console.log(`Job ${jobId} finished`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
scheduler.on('job:error', ({ jobId, instanceId, startDate, endDate, stdout, stderr }) => {
|
|
91
|
+
console.error(`Job ${jobId} failed`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
scheduler.on('job:timeout', ({ jobId, instanceId, startDate, endDate }) => {
|
|
95
|
+
console.warn(`Job ${jobId} timed out`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
scheduler.on('job:message', ({ jobId, instanceId, message }) => {
|
|
99
|
+
console.log(`Message from ${jobId}:`, message);
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Job Configuration
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
scheduler.addJob({
|
|
107
|
+
id: 'unique-job-id',
|
|
108
|
+
path: 'relative/path/to/job.js',
|
|
109
|
+
schedule: { /* see Schedule Types */ },
|
|
110
|
+
timeout: 60000, // timeout in milliseconds
|
|
111
|
+
maxRetries: 3, // retry on failure
|
|
112
|
+
maxConsequentFails: 10, // disable after N consecutive failures
|
|
113
|
+
noConcurrentRuns: true, // prevent overlapping executions
|
|
114
|
+
props: { key: 'value' } // data passed to the worker
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Schedule Types
|
|
63
119
|
|
|
64
|
-
|
|
65
|
-
|
|
120
|
+
All schedules share these optional properties:
|
|
121
|
+
|
|
122
|
+
| Property | Type | Description |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| `interval` | `number` | Number of periods between repeats (1–356) |
|
|
125
|
+
| `startsOn` | `Date` | Do not start before this date |
|
|
126
|
+
| `endsOn` | `Date` | Do not repeat after this date |
|
|
127
|
+
| `maxOccurences` | `number` | Maximum number of executions |
|
|
128
|
+
| `skipFirst` | `number` | Skip this many initial executions |
|
|
129
|
+
|
|
130
|
+
#### Minute
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
{ every: 'minute', interval: 5 }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Runs every N minutes.
|
|
137
|
+
|
|
138
|
+
#### Day
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
{ every: 'day', interval: 1, hour: 9, minute: 30 }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Runs every N days at the specified time.
|
|
145
|
+
|
|
146
|
+
#### Week
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
{ every: 'week', interval: 2, dayOfWeek: [1, 3, 5], hour: 9, minute: 30 }
|
|
66
150
|
```
|
|
151
|
+
|
|
152
|
+
Runs every N weeks on the specified days (1 = Monday, 7 = Sunday).
|
|
153
|
+
|
|
154
|
+
#### Month
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
{ every: 'month', interval: 1, day: 15, hour: 0, minute: 0 }
|
|
158
|
+
// or on the last day of the month:
|
|
159
|
+
{ every: 'month', interval: 1, day: 'last', hour: 0, minute: 0 }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Runs every N months on the specified day (1–28 or `'last'`).
|
|
163
|
+
|
|
164
|
+
#### Year
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
{ every: 'year', interval: 1, month: 6, day: 1, hour: 12, minute: 0 }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Runs every N years on the specified month (1–12) and day (1–28 or `'last'`).
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
BSD-3-Clause
|
|
@@ -1,8 +1,50 @@
|
|
|
1
|
-
import { Schedule } from './types.js';
|
|
1
|
+
import type { Schedule } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Iterates over the dates defined by a {@link Schedule}.
|
|
4
|
+
*
|
|
5
|
+
* Given a schedule configuration (e.g. every 2 days, every week on Monday/Friday,
|
|
6
|
+
* every month on the 15th) the calculator produces the sequence of `Date` objects
|
|
7
|
+
* that match that schedule. Use {@link hasNext} / {@link next} to walk through
|
|
8
|
+
* the sequence.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const calc = new ScheduleCalculator({
|
|
13
|
+
* every: 'day',
|
|
14
|
+
* interval: 1,
|
|
15
|
+
* hour: 9,
|
|
16
|
+
* minute: 0,
|
|
17
|
+
* startsOn: new Date('2025-01-01T00:00:00Z'),
|
|
18
|
+
* maxOccurences: 5
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* while (calc.hasNext()) {
|
|
22
|
+
* const { date, index } = calc.next();
|
|
23
|
+
* console.log(`Run #${index} at ${date.toISOString()}`);
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
2
27
|
export declare class ScheduleCalculator {
|
|
3
28
|
#private;
|
|
29
|
+
/**
|
|
30
|
+
* @param schedule - The schedule definition to iterate over.
|
|
31
|
+
* @throws If `schedule` is falsy.
|
|
32
|
+
*/
|
|
4
33
|
constructor(schedule: Schedule);
|
|
34
|
+
/**
|
|
35
|
+
* Returns `true` when the schedule has at least one more date.
|
|
36
|
+
*
|
|
37
|
+
* @param span - Optional millisecond window. When provided the method
|
|
38
|
+
* returns `true` only if the next date falls within `span` ms from now.
|
|
39
|
+
*/
|
|
5
40
|
hasNext(span?: number): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Advances to the next scheduled date and returns it together with
|
|
43
|
+
* its 1-based index in the sequence.
|
|
44
|
+
*
|
|
45
|
+
* @returns An object with the scheduled `date` and its `index`.
|
|
46
|
+
* @throws If the schedule has no more dates ({@link hasNext} is `false`).
|
|
47
|
+
*/
|
|
6
48
|
next(): {
|
|
7
49
|
date: Date;
|
|
8
50
|
index: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
|
-
/// <reference types="node" />
|
|
4
1
|
import { EventEmitter } from 'events';
|
|
5
|
-
import { Readable } from 'stream';
|
|
2
|
+
import { type Readable } from 'stream';
|
|
6
3
|
import { Worker } from 'worker_threads';
|
|
7
|
-
import {
|
|
4
|
+
import { type IJobRepository } from './jobRepository.js';
|
|
8
5
|
import { ScheduleCalculator } from './ScheduleCalculator.js';
|
|
9
|
-
import {
|
|
10
|
-
export {
|
|
6
|
+
import { type CreateJobRequest, type Job, type JobInstance, type JobInstanceStatus, type JobSchedulerProps, Schedule, type SchedulerStatus, Schemas } from './types.js';
|
|
7
|
+
export { Schedule as TaskSchedule, ScheduleCalculator, Schemas };
|
|
11
8
|
type WorkerResult = {
|
|
12
9
|
status: JobInstanceStatus;
|
|
13
10
|
exitCode: number;
|
|
@@ -49,7 +46,7 @@ export declare class JobScheduler extends EventEmitter implements IJobScheduler
|
|
|
49
46
|
protected _rootFolder: string;
|
|
50
47
|
protected _status: SchedulerStatus;
|
|
51
48
|
protected _defaultTimezone: string;
|
|
52
|
-
protected _checkTimer:
|
|
49
|
+
protected _checkTimer: ReturnType<typeof setTimeout> | undefined;
|
|
53
50
|
protected _jobsRepository: IJobRepository;
|
|
54
51
|
protected _jobProps: Map<string, any>;
|
|
55
52
|
/**
|
|
@@ -63,7 +60,7 @@ export declare class JobScheduler extends EventEmitter implements IJobScheduler
|
|
|
63
60
|
* @param {Job} job
|
|
64
61
|
* @returns {Promise<ScheduleCalculator>}
|
|
65
62
|
*/
|
|
66
|
-
protected getJobSchedule(job: Job): Promise<ScheduleCalculator>;
|
|
63
|
+
protected getJobSchedule(job: Job): Promise<ScheduleCalculator | undefined>;
|
|
67
64
|
protected runWorkerWithTimeout(file: string, props: any, timeout: number): {
|
|
68
65
|
promise: Promise<WorkerResult>;
|
|
69
66
|
worker: Worker;
|