@warlock.js/scheduler 4.0.171 → 4.1.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.
- package/README.md +60 -1
- package/cjs/index.cjs +1132 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/cron-parser.d.mts +129 -0
- package/esm/cron-parser.d.mts.map +1 -0
- package/esm/cron-parser.mjs +210 -0
- package/esm/cron-parser.mjs.map +1 -0
- package/esm/index.d.mts +5 -0
- package/esm/index.mjs +5 -0
- package/esm/job.d.mts +386 -0
- package/esm/job.d.mts.map +1 -0
- package/esm/job.mjs +633 -0
- package/esm/job.mjs.map +1 -0
- package/esm/scheduler.d.mts +193 -0
- package/esm/scheduler.d.mts.map +1 -0
- package/esm/scheduler.mjs +261 -0
- package/esm/scheduler.mjs.map +1 -0
- package/esm/types.d.mts +70 -0
- package/esm/types.d.mts.map +1 -0
- package/llms-full.txt +888 -0
- package/llms.txt +15 -0
- package/package.json +40 -28
- package/skills/configure-retry-and-overlap/SKILL.md +137 -0
- package/skills/observe-scheduler/SKILL.md +153 -0
- package/skills/overview/SKILL.md +92 -0
- package/skills/pin-schedule-timezone/SKILL.md +114 -0
- package/skills/schedule-fluently/SKILL.md +141 -0
- package/skills/schedule-with-cron/SKILL.md +123 -0
- package/skills/scheduler-basics/SKILL.md +94 -0
- package/cjs/cron-parser.d.ts +0 -98
- package/cjs/cron-parser.d.ts.map +0 -1
- package/cjs/cron-parser.js +0 -193
- package/cjs/cron-parser.js.map +0 -1
- package/cjs/index.d.ts +0 -44
- package/cjs/index.d.ts.map +0 -1
- package/cjs/index.js +0 -1
- package/cjs/index.js.map +0 -1
- package/cjs/job.d.ts +0 -332
- package/cjs/job.d.ts.map +0 -1
- package/cjs/job.js +0 -616
- package/cjs/job.js.map +0 -1
- package/cjs/scheduler.d.ts +0 -182
- package/cjs/scheduler.d.ts.map +0 -1
- package/cjs/scheduler.js +0 -316
- package/cjs/scheduler.js.map +0 -1
- package/cjs/types.d.ts +0 -63
- package/cjs/types.d.ts.map +0 -1
- package/cjs/utils.d.ts +0 -3
- package/cjs/utils.d.ts.map +0 -1
- package/esm/cron-parser.d.ts +0 -98
- package/esm/cron-parser.d.ts.map +0 -1
- package/esm/cron-parser.js +0 -193
- package/esm/cron-parser.js.map +0 -1
- package/esm/index.d.ts +0 -44
- package/esm/index.d.ts.map +0 -1
- package/esm/index.js +0 -1
- package/esm/index.js.map +0 -1
- package/esm/job.d.ts +0 -332
- package/esm/job.d.ts.map +0 -1
- package/esm/job.js +0 -616
- package/esm/job.js.map +0 -1
- package/esm/scheduler.d.ts +0 -182
- package/esm/scheduler.d.ts.map +0 -1
- package/esm/scheduler.js +0 -316
- package/esm/scheduler.js.map +0 -1
- package/esm/types.d.ts +0 -63
- package/esm/types.d.ts.map +0 -1
- package/esm/utils.d.ts +0 -3
- package/esm/utils.d.ts.map +0 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: schedule-fluently
|
|
3
|
+
description: 'Build a job schedule via `every*` / `daily` / `weekly` / `monthly` / `at` / `on` / `beginOf` / `endOf`. Triggers: `.everyMinutes`, `.daily`, `.weekly`, `.monthly`, `.at`, `.on`, `.every`, `.beginOf`, `.endOf`, `.twiceDaily`, `scheduler.newJob`; "run every 5 minutes", "daily at 3am", "monday standup", "first of every month", "last day of month"; typical import `import { job, scheduler } from "@warlock.js/scheduler"`. Skip: cron — `@warlock.js/scheduler/schedule-with-cron/SKILL.md`; timezone — `@warlock.js/scheduler/pin-schedule-timezone/SKILL.md`; retry — `@warlock.js/scheduler/configure-retry-and-overlap/SKILL.md`; competing `node-cron`, `node-schedule`, `agenda`; native `setInterval`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fluent scheduling API
|
|
7
|
+
|
|
8
|
+
Chainable methods on a `Job` instance. Each method returns `this` for chaining; each call recomputes `job.nextRun` on the spot.
|
|
9
|
+
|
|
10
|
+
## Preset intervals
|
|
11
|
+
|
|
12
|
+
| Method | Equivalent of | Notes |
|
|
13
|
+
| -------------------- | ------------------------ | ------------------------------ |
|
|
14
|
+
| `.everySecond()` | `.every(1, "second")` | High frequency — use sparingly |
|
|
15
|
+
| `.everySeconds(N)` | `.every(N, "second")` | |
|
|
16
|
+
| `.everyMinute()` | `.every(1, "minute")` | |
|
|
17
|
+
| `.everyMinutes(N)` | `.every(N, "minute")` | |
|
|
18
|
+
| `.everyHour()` | `.every(1, "hour")` | |
|
|
19
|
+
| `.everyHours(N)` | `.every(N, "hour")` | |
|
|
20
|
+
| `.everyDay()` | `.every(1, "day")` | Same as `.daily()` |
|
|
21
|
+
| `.daily()` | `.every(1, "day")` | |
|
|
22
|
+
| `.twiceDaily()` | `.every(12, "hour")` | Every 12 hours |
|
|
23
|
+
| `.everyWeek()` | `.every(1, "week")` | Same as `.weekly()` |
|
|
24
|
+
| `.weekly()` | `.every(1, "week")` | |
|
|
25
|
+
| `.everyMonth()` | `.every(1, "month")` | Same as `.monthly()` |
|
|
26
|
+
| `.monthly()` | `.every(1, "month")` | |
|
|
27
|
+
| `.everyYear()` | `.every(1, "year")` | Same as `.yearly()` |
|
|
28
|
+
| `.yearly()` | `.every(1, "year")` | |
|
|
29
|
+
| `.always()` | `.every(1, "minute")` | Continuous "tick" jobs |
|
|
30
|
+
|
|
31
|
+
## Custom intervals — `.every(value, unit)`
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
job("t", task).every(5, "minute");
|
|
35
|
+
job("t", task).every(2, "hour");
|
|
36
|
+
job("t", task).every(3, "day");
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Units: `"second" | "minute" | "hour" | "day" | "week" | "month" | "year"`.
|
|
40
|
+
|
|
41
|
+
**Validation.** `every(value, unit)` throws at definition time if `value` is `0`, negative, `NaN`, or `Infinity`. This guards against the misconfigured-interval class of bugs that would otherwise spin the scheduler.
|
|
42
|
+
|
|
43
|
+
## Target a specific time — `.at("HH:mm" | "HH:mm:ss")`
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
job("nightly", fn).daily().at("03:00");
|
|
47
|
+
job("midday", fn).daily().at("12:30:15");
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Validation.** `.at()` throws if the format is malformed (`"foo"`, `"9-30"`), or any component is out of range (hour > 23, minute > 59, second > 59).
|
|
51
|
+
|
|
52
|
+
## Target a specific day — `.on(day)`
|
|
53
|
+
|
|
54
|
+
Day-of-week (string) or day-of-month (number 1–31):
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
job("monday-standup", task).weekly().on("monday");
|
|
58
|
+
job("mid-month-sync", task).monthly().on(15);
|
|
59
|
+
job("end-of-month", task).monthly().on(31); // see beginOf/endOf for the right way
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Valid day-of-week strings: `"sunday"`, `"monday"`, `"tuesday"`, `"wednesday"`, `"thursday"`, `"friday"`, `"saturday"`.
|
|
63
|
+
|
|
64
|
+
**Validation.** Numeric `on(N)` throws if `N < 1 || N > 31`.
|
|
65
|
+
|
|
66
|
+
**Gotcha.** `monthly().on(31)` clamps to the actual month length in dayjs — February runs are unpredictable. Use `endOf("month")` for "last day" semantics instead.
|
|
67
|
+
|
|
68
|
+
## Boundary shortcuts — `.beginOf(type)` / `.endOf(type)`
|
|
69
|
+
|
|
70
|
+
Both accept `"day" | "month" | "year"`.
|
|
71
|
+
|
|
72
|
+
| Method | Fires at |
|
|
73
|
+
| -------------------- | ------------------------------------------------- |
|
|
74
|
+
| `beginOf("day")` | 00:00 every day |
|
|
75
|
+
| `endOf("day")` | 23:59 every day |
|
|
76
|
+
| `beginOf("month")` | 1st of every month at 00:00 |
|
|
77
|
+
| `endOf("month")` | Last day of every month at 23:59 (**dynamic**) |
|
|
78
|
+
| `beginOf("year")` | January 1 at 00:00 every year |
|
|
79
|
+
| `endOf("year")` | December 31 at 23:59 every year |
|
|
80
|
+
|
|
81
|
+
**`endOf("month")` is dynamic** — recomputes per cycle, so a job defined in February (28 days) still fires on March 31, April 30, etc. Leap years pick Feb 29 correctly.
|
|
82
|
+
|
|
83
|
+
**`beginOf("year")` / `endOf("year")` lock the month** — always Jan 1 / Dec 31, never "1st/31st of whatever month the job was defined in."
|
|
84
|
+
|
|
85
|
+
## Cron is an alternative, not an addition
|
|
86
|
+
|
|
87
|
+
`.cron("…")` clears any prior interval/`at`/`on`/`beginOf`/`endOf` config — and vice versa. They're mutually exclusive. See [`@warlock.js/scheduler/schedule-with-cron/SKILL.md`](@warlock.js/scheduler/schedule-with-cron/SKILL.md).
|
|
88
|
+
|
|
89
|
+
## Chaining order doesn't matter (for the most part)
|
|
90
|
+
|
|
91
|
+
Every fluent method recomputes `nextRun` at the end. Putting `.at()` before `.daily()` produces the same schedule as putting `.daily()` before `.at()` — but for readability, the conventional order is **interval → day → time → timezone → execution options**:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
job("good", fn)
|
|
95
|
+
.weekly() // interval
|
|
96
|
+
.on("monday") // day
|
|
97
|
+
.at("09:00") // time
|
|
98
|
+
.inTimezone("UTC") // timezone
|
|
99
|
+
.preventOverlap() // execution
|
|
100
|
+
.retry(3, 1000); // execution
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Inline registration — `scheduler.newJob()`
|
|
104
|
+
|
|
105
|
+
For one-liners, the scheduler exposes a `newJob()` shortcut that creates, registers, and returns the job in one call:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
scheduler
|
|
109
|
+
.newJob("cleanup", cleanupFn)
|
|
110
|
+
.daily()
|
|
111
|
+
.at("03:00");
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
To register several pre-built jobs at once, `scheduler.addJobs([...])` is the batch counterpart to `addJob` (both are chainable and preserve insertion order):
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
scheduler.addJobs([
|
|
118
|
+
job("cleanup", cleanupFn).daily().at("03:00"),
|
|
119
|
+
job("reports", sendReports).weekly().on("monday").at("09:00"),
|
|
120
|
+
]);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
If the scheduler is already running, every job added via `addJob` / `addJobs` is prepared on the spot (its `nextRun` is computed) so it fires on the next tick.
|
|
124
|
+
|
|
125
|
+
## Reading state at runtime
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const j = scheduler.getJob("nightly-cleanup");
|
|
129
|
+
|
|
130
|
+
j?.nextRun?.toISOString(); // next scheduled run (Dayjs)
|
|
131
|
+
j?.lastRun?.toISOString(); // last attempt — success OR failure
|
|
132
|
+
j?.isRunning; // currently executing?
|
|
133
|
+
j?.intervals; // { every?, day?, dayOfMonthMode?, month?, time? } (readonly)
|
|
134
|
+
j?.cronExpression; // null if using fluent API
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## See also
|
|
138
|
+
|
|
139
|
+
- [`@warlock.js/scheduler/schedule-with-cron/SKILL.md`](@warlock.js/scheduler/schedule-with-cron/SKILL.md) — when the fluent API isn't enough
|
|
140
|
+
- [`@warlock.js/scheduler/configure-retry-and-overlap/SKILL.md`](@warlock.js/scheduler/configure-retry-and-overlap/SKILL.md) — `.retry()` and `.preventOverlap()` on the same job
|
|
141
|
+
- [`@warlock.js/scheduler/pin-schedule-timezone/SKILL.md`](@warlock.js/scheduler/pin-schedule-timezone/SKILL.md) — `.inTimezone()`
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: schedule-with-cron
|
|
3
|
+
description: 'Write `.cron(''…'')` expressions — 5-field syntax, operators (`*` `,` `-` `/`), Vixie OR semantics for DOM / DOW, `parseCron()` preview utility. Triggers: `.cron`, `parseCron`, `CronParser`, `*/5 * * * *`, `0 9 * * 1-5`; "write a cron expression", "every weekday at 9am cron", "preview next cron run", "migrate crontab(5) entry"; typical import `import { parseCron } from "@warlock.js/scheduler"`. Skip: human-readable schedules — `@warlock.js/scheduler/schedule-fluently/SKILL.md`; timezone — `@warlock.js/scheduler/pin-schedule-timezone/SKILL.md`; competing libs `node-cron`, `cron`, `croner`, `cronstrue`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cron expressions
|
|
7
|
+
|
|
8
|
+
The escape hatch when the fluent API can't express a schedule. Activating `.cron()` clears any prior fluent config — the two are mutually exclusive.
|
|
9
|
+
|
|
10
|
+
## 5-field syntax
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
┌───────────── minute (0-59)
|
|
14
|
+
│ ┌───────────── hour (0-23)
|
|
15
|
+
│ │ ┌───────────── day-of-month (1-31)
|
|
16
|
+
│ │ │ ┌───────────── month (1-12)
|
|
17
|
+
│ │ │ │ ┌───────────── day-of-week (0-6, Sunday = 0)
|
|
18
|
+
│ │ │ │ │
|
|
19
|
+
* * * * *
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Note:** Sunday is `0` only. Some cron dialects also accept `7` for Sunday — this parser does NOT.
|
|
23
|
+
|
|
24
|
+
## Operators
|
|
25
|
+
|
|
26
|
+
| Syntax | Meaning | Example |
|
|
27
|
+
| --------- | ----------------------------- | ---------------- |
|
|
28
|
+
| `*` | Any value (full range) | `* * * * *` |
|
|
29
|
+
| `5` | Single value | `30 14 * * *` |
|
|
30
|
+
| `1,3,5` | List | `0,30 * * * *` |
|
|
31
|
+
| `1-5` | Inclusive range | `0 9-17 * * *` |
|
|
32
|
+
| `*/N` | Step over the wildcard | `*/5 * * * *` |
|
|
33
|
+
| `A-B/N` | Step over a range | `0 1-10/2 * * *` |
|
|
34
|
+
|
|
35
|
+
**Not supported (yet):** named tokens (`MON-FRI`, `JAN-DEC`), special strings (`@daily`, `@hourly`, `@reboot`), seconds field (6-field), Quartz modifiers (`L`, `W`, `#`).
|
|
36
|
+
|
|
37
|
+
## Day-of-month + day-of-week — Vixie OR semantics
|
|
38
|
+
|
|
39
|
+
When **both** `dayOfMonth` AND `dayOfWeek` are restricted (neither is `*`), the date matches if **either** constraint matches. When one is `*` and the other is restricted, only the restricted one matters.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// "1st of month OR any Monday" — fires on the 1st even if not a Monday,
|
|
43
|
+
// AND fires on any Monday even if not the 1st.
|
|
44
|
+
job("digest", sendDigest).cron("0 0 1 * 1");
|
|
45
|
+
|
|
46
|
+
// "15th of month" — DOW is *, so only DOM applies. Fires only on the 15th.
|
|
47
|
+
job("midmonth", task).cron("0 0 15 * *");
|
|
48
|
+
|
|
49
|
+
// "Every Monday" — DOM is *, so only DOW applies. Fires only on Mondays.
|
|
50
|
+
job("monday-only", task).cron("0 0 * * 1");
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This matches Vixie cron (the de facto standard on Unix). Migrating an existing cron table from `crontab(5)` should "just work" semantically.
|
|
54
|
+
|
|
55
|
+
## Common recipes
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// Every 5 minutes
|
|
59
|
+
.cron("*/5 * * * *")
|
|
60
|
+
|
|
61
|
+
// Top of every hour
|
|
62
|
+
.cron("0 * * * *")
|
|
63
|
+
|
|
64
|
+
// Every weekday at 9 AM
|
|
65
|
+
.cron("0 9 * * 1-5")
|
|
66
|
+
|
|
67
|
+
// 2:30 PM on the 15th of every month
|
|
68
|
+
.cron("30 14 15 * *")
|
|
69
|
+
|
|
70
|
+
// Every 2 hours, on the hour
|
|
71
|
+
.cron("0 */2 * * *")
|
|
72
|
+
|
|
73
|
+
// First day of each month at midnight
|
|
74
|
+
.cron("0 0 1 * *")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Validation
|
|
78
|
+
|
|
79
|
+
`new CronParser(expr)` and `.cron(expr)` both throw at definition time on:
|
|
80
|
+
|
|
81
|
+
- Wrong field count (must be exactly 5)
|
|
82
|
+
- Non-numeric values, out-of-range values, inverted ranges (`5-1`)
|
|
83
|
+
- Step `<= 0`, non-numeric step
|
|
84
|
+
- Impossible day-of-month / month combinations — a date that can never occur, e.g. `0 0 30 2 *` (Feb 30) or `0 0 31 4 *` (April has 30 days). Rejected with an `Impossible cron expression` error.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
new CronParser("0 0 30 2 *"); // throws: Impossible cron expression — Feb never has a 30th
|
|
88
|
+
new CronParser("0 0 31 4 *"); // throws: April has only 30 days
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Leap years and the OR escape hatch.** `0 0 29 2 *` (Feb 29) is **accepted** — it occurs in leap years. And when day-of-week is also restricted, Vixie OR semantics keep the schedule alive via the weekday path, so the combo is never rejected: `0 0 30 2 1` (Feb 30 OR any Monday) parses fine and fires on Mondays.
|
|
92
|
+
|
|
93
|
+
Throws are eager — bad expressions fail fast at construction, not at first tick.
|
|
94
|
+
|
|
95
|
+
## Standalone preview — `parseCron()`
|
|
96
|
+
|
|
97
|
+
The `CronParser` class is exported as a utility for ad-hoc next-run calculation, separate from any job:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { parseCron } from "@warlock.js/scheduler";
|
|
101
|
+
import dayjs from "dayjs";
|
|
102
|
+
|
|
103
|
+
const parser = parseCron("0 9 * * 1-5");
|
|
104
|
+
|
|
105
|
+
parser.nextRun().toISOString(); // next weekday at 9 AM
|
|
106
|
+
parser.nextRun(dayjs("2026-12-31")).format(); // from a specific anchor
|
|
107
|
+
|
|
108
|
+
parser.matches(dayjs()); // is "now" a fire moment?
|
|
109
|
+
|
|
110
|
+
parser.fields; // parsed numeric arrays
|
|
111
|
+
parser.expression; // original string
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Impossible expressions are rejected eagerly at construction (see Validation), so `nextRun(from?)` always resolves a satisfiable expression within a year — its one-year scan bound is just a defensive backstop, never the path that catches a bad expression.
|
|
115
|
+
|
|
116
|
+
## Timezone interaction
|
|
117
|
+
|
|
118
|
+
If the job has `.inTimezone(tz)`, the cron parser receives a timezone-aware `dayjs` object — so `0 9 * * *` with `.inTimezone("Asia/Tokyo")` fires at 09:00 JST. See [`@warlock.js/scheduler/pin-schedule-timezone/SKILL.md`](@warlock.js/scheduler/pin-schedule-timezone/SKILL.md).
|
|
119
|
+
|
|
120
|
+
## See also
|
|
121
|
+
|
|
122
|
+
- [`@warlock.js/scheduler/schedule-fluently/SKILL.md`](@warlock.js/scheduler/schedule-fluently/SKILL.md) — the higher-level alternative for human-readable schedules
|
|
123
|
+
- [`@warlock.js/scheduler/pin-schedule-timezone/SKILL.md`](@warlock.js/scheduler/pin-schedule-timezone/SKILL.md) — pinning a cron schedule to a wall-clock timezone
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scheduler-basics
|
|
3
|
+
description: 'Start with `@warlock.js/scheduler` — `job()` factory + `scheduler` singleton, 2-primitive surface, UTC default, factory-first API. Triggers: `job`, `scheduler`, `scheduler.addJob`, `scheduler.start`, `scheduler.newJob`; "schedule a recurring job", "how to use warlock scheduler", "where do I start"; typical import `import { job, scheduler } from "@warlock.js/scheduler"`. Skip: fluent — `@warlock.js/scheduler/schedule-fluently/SKILL.md`; cron — `@warlock.js/scheduler/schedule-with-cron/SKILL.md`; retry — `@warlock.js/scheduler/configure-retry-and-overlap/SKILL.md`; events — `@warlock.js/scheduler/observe-scheduler/SKILL.md`; timezone — `@warlock.js/scheduler/pin-schedule-timezone/SKILL.md`; competing `bullmq`, `agenda`, `node-cron`; native `setInterval`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Schedule recurring jobs
|
|
7
|
+
|
|
8
|
+
In-process recurring job scheduler. Built on `dayjs`. Two primitives, factory-first API, type-safe events.
|
|
9
|
+
|
|
10
|
+
> This skill is the scheduler **map** — read it first, then load the specific skill for the task.
|
|
11
|
+
|
|
12
|
+
## The 2-primitive surface
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Job → the scheduled unit (factory: `job(name, callback)`)
|
|
16
|
+
Scheduler → the runtime / tick loop (singleton: `scheduler` | class: `new Scheduler()`)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
A `Job` carries the schedule (interval or cron), retry config, overlap rule, timezone, and the callback. A `Scheduler` owns the tick loop, the registered jobs, the parallel/sequential execution mode, and emits lifecycle events. The exported `CronParser` is a utility for ad-hoc cron preview — not part of the scheduling flow.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
yarn add @warlock.js/scheduler
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Foundations
|
|
28
|
+
|
|
29
|
+
The 9 things that are true in every scheduler use:
|
|
30
|
+
|
|
31
|
+
1. **Factory-first.** `import { job, scheduler } from "@warlock.js/scheduler"`. Users do not call `new Job()` directly (it works, but `job()` is the documented surface).
|
|
32
|
+
2. **Default timezone is UTC.** `daily().at("09:00")` fires at 09:00 UTC, regardless of the server's clock. Pin a job to wall-clock with `.inTimezone("America/New_York")`. See [`@warlock.js/scheduler/pin-schedule-timezone/SKILL.md`](@warlock.js/scheduler/pin-schedule-timezone/SKILL.md).
|
|
33
|
+
3. **The scheduler awaits each tick.** Concurrent same-job runs from the scheduler's own loop are structurally impossible. `preventOverlap()` is for jobs that ALSO get invoked outside the loop. See [`@warlock.js/scheduler/configure-retry-and-overlap/SKILL.md`](@warlock.js/scheduler/configure-retry-and-overlap/SKILL.md).
|
|
34
|
+
4. **Cron uses 5-field Vixie semantics.** When both `dayOfMonth` and `dayOfWeek` are restricted (neither is `*`), a date matches if EITHER constraint matches. Standard-cron compatible. See [`@warlock.js/scheduler/schedule-with-cron/SKILL.md`](@warlock.js/scheduler/schedule-with-cron/SKILL.md).
|
|
35
|
+
5. **`nextRun` always advances after a run — success OR failure.** A permanently failing job re-fires on its next scheduled slot, not on every tick.
|
|
36
|
+
6. **Validation throws at definition time.** `every(0)`, `at("24:00")`, `on(32)`, `retry(-1)`, malformed cron — all throw immediately when you wire the job, not at runtime.
|
|
37
|
+
7. **`Job.run()` returns a `JobResult`, never throws.** Errors funnel into `result.error`. The scheduler emits `job:error` after all retries are exhausted.
|
|
38
|
+
8. **Parallel mode is within a tick, not across.** Even with `runInParallel(true)`, the NEXT tick waits for the current tick's jobs to all settle.
|
|
39
|
+
9. **Tick interval is drift-compensated.** A tick that takes 200 ms is followed by an 800 ms delay so the cadence between tick *starts* averages `tickInterval`, not `tickInterval + work-time`.
|
|
40
|
+
|
|
41
|
+
## 30-second example
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { scheduler, job } from "@warlock.js/scheduler";
|
|
45
|
+
|
|
46
|
+
scheduler.on("job:error", (name, error) => logger.error({ name, error }));
|
|
47
|
+
|
|
48
|
+
scheduler.addJob(
|
|
49
|
+
job("nightly-cleanup", async () => {
|
|
50
|
+
await db.deleteExpiredTokens();
|
|
51
|
+
})
|
|
52
|
+
.daily()
|
|
53
|
+
.at("03:00")
|
|
54
|
+
.inTimezone("America/New_York")
|
|
55
|
+
.preventOverlap()
|
|
56
|
+
.retry(3, 1000)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
scheduler.start();
|
|
60
|
+
|
|
61
|
+
process.on("SIGTERM", async () => {
|
|
62
|
+
await scheduler.shutdown(30_000);
|
|
63
|
+
process.exit(0);
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Pick a skill
|
|
68
|
+
|
|
69
|
+
| If the task is about… | Load |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| Building schedules via `every*`/`daily`/`weekly`/`monthly`/`at`/`on`/`beginOf`/`endOf` | [`@warlock.js/scheduler/schedule-fluently/SKILL.md`](@warlock.js/scheduler/schedule-fluently/SKILL.md) |
|
|
72
|
+
| Writing `.cron("…")` expressions, debugging DOM/DOW behavior, using `parseCron()` for preview | [`@warlock.js/scheduler/schedule-with-cron/SKILL.md`](@warlock.js/scheduler/schedule-with-cron/SKILL.md) |
|
|
73
|
+
| Configuring `.retry()` / exponential backoff, `.preventOverlap()`, understanding failure rescheduling | [`@warlock.js/scheduler/configure-retry-and-overlap/SKILL.md`](@warlock.js/scheduler/configure-retry-and-overlap/SKILL.md) |
|
|
74
|
+
| Subscribing to scheduler events, reading `JobResult`, lifecycle, graceful shutdown | [`@warlock.js/scheduler/observe-scheduler/SKILL.md`](@warlock.js/scheduler/observe-scheduler/SKILL.md) |
|
|
75
|
+
| Per-job `.inTimezone()`, multi-region patterns, DST handling | [`@warlock.js/scheduler/pin-schedule-timezone/SKILL.md`](@warlock.js/scheduler/pin-schedule-timezone/SKILL.md) |
|
|
76
|
+
|
|
77
|
+
## When NOT to use this skill
|
|
78
|
+
|
|
79
|
+
- Jobs imported from `bullmq`, `agenda`, `node-cron`, etc. — those are different libraries.
|
|
80
|
+
- Long-running queue workers (consumer processes) — this is a scheduler, not a queue.
|
|
81
|
+
- One-off "run this at a specific date once" — currently not supported (on the backlog as `runAt()`).
|
|
82
|
+
- Multi-replica deployments needing leader election — also on the backlog (distributed locking).
|
|
83
|
+
|
|
84
|
+
## Package structure
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
@warlock.js/scheduler
|
|
88
|
+
src/
|
|
89
|
+
index.ts — barrel: Scheduler, scheduler, Job, job, CronParser, parseCron, types
|
|
90
|
+
scheduler.ts — Scheduler class + default singleton
|
|
91
|
+
job.ts — Job class + job() factory
|
|
92
|
+
cron-parser.ts — CronParser class + parseCron() factory
|
|
93
|
+
types.ts — TimeType, Day, JobIntervals, JobResult, RetryConfig, SchedulerEvents
|
|
94
|
+
```
|
package/cjs/cron-parser.d.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { type Dayjs } from "dayjs";
|
|
2
|
-
/**
|
|
3
|
-
* Parsed cron expression fields
|
|
4
|
-
*/
|
|
5
|
-
export type CronFields = {
|
|
6
|
-
/** Minutes (0-59) */
|
|
7
|
-
minutes: number[];
|
|
8
|
-
/** Hours (0-23) */
|
|
9
|
-
hours: number[];
|
|
10
|
-
/** Days of month (1-31) */
|
|
11
|
-
daysOfMonth: number[];
|
|
12
|
-
/** Months (1-12) */
|
|
13
|
-
months: number[];
|
|
14
|
-
/** Days of week (0-6, Sunday = 0) */
|
|
15
|
-
daysOfWeek: number[];
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Cron expression parser
|
|
19
|
-
*
|
|
20
|
-
* Supports standard 5-field cron expressions:
|
|
21
|
-
* ```
|
|
22
|
-
* ┌───────────── minute (0-59)
|
|
23
|
-
* │ ┌───────────── hour (0-23)
|
|
24
|
-
* │ │ ┌───────────── day of month (1-31)
|
|
25
|
-
* │ │ │ ┌───────────── month (1-12)
|
|
26
|
-
* │ │ │ │ ┌───────────── day of week (0-6, Sunday = 0)
|
|
27
|
-
* │ │ │ │ │
|
|
28
|
-
* * * * * *
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* Supports:
|
|
32
|
-
* - `*` - any value
|
|
33
|
-
* - `5` - specific value
|
|
34
|
-
* - `1,3,5` - list of values
|
|
35
|
-
* - `1-5` - range of values
|
|
36
|
-
* - `* /5` - step values (every 5)
|
|
37
|
-
* - `1-10/2` - range with step
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```typescript
|
|
41
|
-
* const parser = new CronParser("0 9 * * 1-5"); // 9 AM weekdays
|
|
42
|
-
* const nextRun = parser.nextRun();
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
export declare class CronParser {
|
|
46
|
-
private readonly _expression;
|
|
47
|
-
private readonly _fields;
|
|
48
|
-
/**
|
|
49
|
-
* Creates a new CronParser instance
|
|
50
|
-
*
|
|
51
|
-
* @param expression - Standard 5-field cron expression
|
|
52
|
-
* @throws Error if expression is invalid
|
|
53
|
-
*/
|
|
54
|
-
constructor(_expression: string);
|
|
55
|
-
/**
|
|
56
|
-
* Get the parsed cron fields
|
|
57
|
-
*/
|
|
58
|
-
get fields(): Readonly<CronFields>;
|
|
59
|
-
/**
|
|
60
|
-
* Get the original expression
|
|
61
|
-
*/
|
|
62
|
-
get expression(): string;
|
|
63
|
-
/**
|
|
64
|
-
* Calculate the next run time from a given date
|
|
65
|
-
*
|
|
66
|
-
* @param from - Starting date (defaults to now)
|
|
67
|
-
* @returns Next run time as Dayjs
|
|
68
|
-
*/
|
|
69
|
-
nextRun(from?: Dayjs): Dayjs;
|
|
70
|
-
/**
|
|
71
|
-
* Check if a given date matches the cron expression
|
|
72
|
-
*
|
|
73
|
-
* @param date - Date to check
|
|
74
|
-
* @returns true if the date matches
|
|
75
|
-
*/
|
|
76
|
-
matches(date: Dayjs): boolean;
|
|
77
|
-
/**
|
|
78
|
-
* Parse a cron expression into fields
|
|
79
|
-
*/
|
|
80
|
-
private _parse;
|
|
81
|
-
/**
|
|
82
|
-
* Parse a single cron field
|
|
83
|
-
*
|
|
84
|
-
* @param field - Field value (e.g., "*", "5", "1-5", "* /2", "1,3,5")
|
|
85
|
-
* @param min - Minimum allowed value
|
|
86
|
-
* @param max - Maximum allowed value
|
|
87
|
-
* @returns Array of matching values
|
|
88
|
-
*/
|
|
89
|
-
private _parseField;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Parse a cron expression string
|
|
93
|
-
*
|
|
94
|
-
* @param expression - Cron expression (5 fields)
|
|
95
|
-
* @returns CronParser instance
|
|
96
|
-
*/
|
|
97
|
-
export declare function parseCron(expression: string): CronParser;
|
|
98
|
-
//# sourceMappingURL=cron-parser.d.ts.map
|
package/cjs/cron-parser.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cron-parser.d.ts","sourceRoot":"","sources":["../src/cron-parser.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,qBAAqB;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mBAAmB;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,2BAA2B;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,oBAAoB;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,qCAAqC;IACrC,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,UAAU;IASF,OAAO,CAAC,QAAQ,CAAC,WAAW;IAR/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;IAErC;;;;;OAKG;gBACiC,WAAW,EAAE,MAAM;IAIvD;;OAEG;IACH,IAAW,MAAM,IAAI,QAAQ,CAAC,UAAU,CAAC,CAExC;IAED;;OAEG;IACH,IAAW,UAAU,IAAI,MAAM,CAE9B;IAED;;;;;OAKG;IACI,OAAO,CAAC,IAAI,GAAE,KAAe,GAAG,KAAK;IA+C5C;;;;;OAKG;IACI,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO;IAUpC;;OAEG;IACH,OAAO,CAAC,MAAM;IAoBd;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;CA2DpB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAExD"}
|
package/cjs/cron-parser.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
'use strict';var dayjs=require('dayjs');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var dayjs__default=/*#__PURE__*/_interopDefault(dayjs);/**
|
|
2
|
-
* Cron expression parser
|
|
3
|
-
*
|
|
4
|
-
* Supports standard 5-field cron expressions:
|
|
5
|
-
* ```
|
|
6
|
-
* ┌───────────── minute (0-59)
|
|
7
|
-
* │ ┌───────────── hour (0-23)
|
|
8
|
-
* │ │ ┌───────────── day of month (1-31)
|
|
9
|
-
* │ │ │ ┌───────────── month (1-12)
|
|
10
|
-
* │ │ │ │ ┌───────────── day of week (0-6, Sunday = 0)
|
|
11
|
-
* │ │ │ │ │
|
|
12
|
-
* * * * * *
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* Supports:
|
|
16
|
-
* - `*` - any value
|
|
17
|
-
* - `5` - specific value
|
|
18
|
-
* - `1,3,5` - list of values
|
|
19
|
-
* - `1-5` - range of values
|
|
20
|
-
* - `* /5` - step values (every 5)
|
|
21
|
-
* - `1-10/2` - range with step
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```typescript
|
|
25
|
-
* const parser = new CronParser("0 9 * * 1-5"); // 9 AM weekdays
|
|
26
|
-
* const nextRun = parser.nextRun();
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
class CronParser {
|
|
30
|
-
_expression;
|
|
31
|
-
_fields;
|
|
32
|
-
/**
|
|
33
|
-
* Creates a new CronParser instance
|
|
34
|
-
*
|
|
35
|
-
* @param expression - Standard 5-field cron expression
|
|
36
|
-
* @throws Error if expression is invalid
|
|
37
|
-
*/
|
|
38
|
-
constructor(_expression) {
|
|
39
|
-
this._expression = _expression;
|
|
40
|
-
this._fields = this._parse(_expression);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Get the parsed cron fields
|
|
44
|
-
*/
|
|
45
|
-
get fields() {
|
|
46
|
-
return this._fields;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Get the original expression
|
|
50
|
-
*/
|
|
51
|
-
get expression() {
|
|
52
|
-
return this._expression;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Calculate the next run time from a given date
|
|
56
|
-
*
|
|
57
|
-
* @param from - Starting date (defaults to now)
|
|
58
|
-
* @returns Next run time as Dayjs
|
|
59
|
-
*/
|
|
60
|
-
nextRun(from = dayjs__default.default()) {
|
|
61
|
-
let date = from.add(1, "minute").second(0).millisecond(0);
|
|
62
|
-
// Maximum iterations to prevent infinite loops
|
|
63
|
-
const maxIterations = 366 * 24 * 60; // 1 year of minutes
|
|
64
|
-
let iterations = 0;
|
|
65
|
-
while (iterations < maxIterations) {
|
|
66
|
-
iterations++;
|
|
67
|
-
// Check month
|
|
68
|
-
if (!this._fields.months.includes(date.month() + 1)) {
|
|
69
|
-
date = date.add(1, "month").date(1).hour(0).minute(0);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
// Check day of month
|
|
73
|
-
if (!this._fields.daysOfMonth.includes(date.date())) {
|
|
74
|
-
date = date.add(1, "day").hour(0).minute(0);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
// Check day of week
|
|
78
|
-
if (!this._fields.daysOfWeek.includes(date.day())) {
|
|
79
|
-
date = date.add(1, "day").hour(0).minute(0);
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
// Check hour
|
|
83
|
-
if (!this._fields.hours.includes(date.hour())) {
|
|
84
|
-
date = date.add(1, "hour").minute(0);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
// Check minute
|
|
88
|
-
if (!this._fields.minutes.includes(date.minute())) {
|
|
89
|
-
date = date.add(1, "minute");
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
// All fields match!
|
|
93
|
-
return date;
|
|
94
|
-
}
|
|
95
|
-
throw new Error(`Could not find next run time for cron expression: ${this._expression}`);
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Check if a given date matches the cron expression
|
|
99
|
-
*
|
|
100
|
-
* @param date - Date to check
|
|
101
|
-
* @returns true if the date matches
|
|
102
|
-
*/
|
|
103
|
-
matches(date) {
|
|
104
|
-
return (this._fields.minutes.includes(date.minute()) &&
|
|
105
|
-
this._fields.hours.includes(date.hour()) &&
|
|
106
|
-
this._fields.daysOfMonth.includes(date.date()) &&
|
|
107
|
-
this._fields.months.includes(date.month() + 1) &&
|
|
108
|
-
this._fields.daysOfWeek.includes(date.day()));
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Parse a cron expression into fields
|
|
112
|
-
*/
|
|
113
|
-
_parse(expression) {
|
|
114
|
-
const parts = expression.trim().split(/\s+/);
|
|
115
|
-
if (parts.length !== 5) {
|
|
116
|
-
throw new Error(`Invalid cron expression: "${expression}". Expected 5 fields (minute hour day month weekday).`);
|
|
117
|
-
}
|
|
118
|
-
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
|
|
119
|
-
return {
|
|
120
|
-
minutes: this._parseField(minute, 0, 59),
|
|
121
|
-
hours: this._parseField(hour, 0, 23),
|
|
122
|
-
daysOfMonth: this._parseField(dayOfMonth, 1, 31),
|
|
123
|
-
months: this._parseField(month, 1, 12),
|
|
124
|
-
daysOfWeek: this._parseField(dayOfWeek, 0, 6),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Parse a single cron field
|
|
129
|
-
*
|
|
130
|
-
* @param field - Field value (e.g., "*", "5", "1-5", "* /2", "1,3,5")
|
|
131
|
-
* @param min - Minimum allowed value
|
|
132
|
-
* @param max - Maximum allowed value
|
|
133
|
-
* @returns Array of matching values
|
|
134
|
-
*/
|
|
135
|
-
_parseField(field, min, max) {
|
|
136
|
-
const values = new Set();
|
|
137
|
-
// Handle lists (e.g., "1,3,5")
|
|
138
|
-
const parts = field.split(",");
|
|
139
|
-
for (const part of parts) {
|
|
140
|
-
// Handle step values (e.g., "*/5" or "1-10/2")
|
|
141
|
-
const [range, stepStr] = part.split("/");
|
|
142
|
-
const step = stepStr ? parseInt(stepStr, 10) : 1;
|
|
143
|
-
if (isNaN(step) || step < 1) {
|
|
144
|
-
throw new Error(`Invalid step value in cron field: "${field}"`);
|
|
145
|
-
}
|
|
146
|
-
let rangeStart;
|
|
147
|
-
let rangeEnd;
|
|
148
|
-
if (range === "*") {
|
|
149
|
-
// Wildcard - all values
|
|
150
|
-
rangeStart = min;
|
|
151
|
-
rangeEnd = max;
|
|
152
|
-
}
|
|
153
|
-
else if (range.includes("-")) {
|
|
154
|
-
// Range (e.g., "1-5")
|
|
155
|
-
const [startStr, endStr] = range.split("-");
|
|
156
|
-
rangeStart = parseInt(startStr, 10);
|
|
157
|
-
rangeEnd = parseInt(endStr, 10);
|
|
158
|
-
if (isNaN(rangeStart) || isNaN(rangeEnd)) {
|
|
159
|
-
throw new Error(`Invalid range in cron field: "${field}"`);
|
|
160
|
-
}
|
|
161
|
-
if (rangeStart < min || rangeEnd > max || rangeStart > rangeEnd) {
|
|
162
|
-
throw new Error(`Range out of bounds in cron field: "${field}" (valid: ${min}-${max})`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
// Single value
|
|
167
|
-
const value = parseInt(range, 10);
|
|
168
|
-
if (isNaN(value)) {
|
|
169
|
-
throw new Error(`Invalid value in cron field: "${field}"`);
|
|
170
|
-
}
|
|
171
|
-
if (value < min || value > max) {
|
|
172
|
-
throw new Error(`Value out of bounds in cron field: "${field}" (valid: ${min}-${max})`);
|
|
173
|
-
}
|
|
174
|
-
rangeStart = value;
|
|
175
|
-
rangeEnd = value;
|
|
176
|
-
}
|
|
177
|
-
// Add values with step
|
|
178
|
-
for (let i = rangeStart; i <= rangeEnd; i += step) {
|
|
179
|
-
values.add(i);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return Array.from(values).sort((a, b) => a - b);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Parse a cron expression string
|
|
187
|
-
*
|
|
188
|
-
* @param expression - Cron expression (5 fields)
|
|
189
|
-
* @returns CronParser instance
|
|
190
|
-
*/
|
|
191
|
-
function parseCron(expression) {
|
|
192
|
-
return new CronParser(expression);
|
|
193
|
-
}exports.CronParser=CronParser;exports.parseCron=parseCron;//# sourceMappingURL=cron-parser.js.map
|
package/cjs/cron-parser.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cron-parser.js","sources":["../src/cron-parser.ts"],"sourcesContent":[null],"names":["dayjs"],"mappings":"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"}
|