@warlock.js/scheduler 5.34.0 → 5.34.2
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/CHANGELOG.md +6 -0
- package/llms-full.txt +54 -106
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/skills/index.md +29 -2
- package/skills/overview/SKILL.md +0 -102
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `@warlock.js/scheduler` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.34.2 - 2026-10-19
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Merge package orientation into the grouped skill index and remove the duplicate overview topic; preserve that orientation in generated LLM projections.
|
|
12
|
+
|
|
7
13
|
## 5.32.0 - 2026-10-18
|
|
8
14
|
|
|
9
15
|
### Changed
|
package/llms-full.txt
CHANGED
|
@@ -4,6 +4,60 @@
|
|
|
4
4
|
|
|
5
5
|
> Generated artifact. Concatenates every SKILL.md and reference file under `@warlock.js/scheduler/skills/`. Re-run `node scripts/generate-llms.mjs` after any change.
|
|
6
6
|
|
|
7
|
+
## Package orientation
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
description: 'In-process job scheduler for Warlock.js with fluent and cron scheduling, retries, overlap prevention and timezones. Exports `Scheduler`, `scheduler`, `Job`, `job`, `CronParser`, `parseCron`, plus types `RetryConfig`, `JobResult`, `SchedulerEvents`. Use for: "run this every day at 3am", "run a cron expression on weekdays", "retry a failing job 3 times", "stop a job overlapping itself", "run in New York time", "log job failures". Not this package: durable queues and background workers are not scheduling; per-run locking across instances uses @warlock.js/cache.'
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# @warlock.js/scheduler
|
|
14
|
+
|
|
15
|
+
Build a `job(name, fn)`, describe when it runs with the fluent API (`.daily().at("03:00")`) or `.cron("...")`, add it to a `Scheduler`, then `start()`. Events (`job:error` and others) provide observability; `shutdown()` stops gracefully.
|
|
16
|
+
|
|
17
|
+
## The 80% path
|
|
18
|
+
|
|
19
|
+
1. Start with `scheduler-basics.md`.
|
|
20
|
+
2. Define the schedule fluently: `schedule-fluently.md`, or by cron: `schedule-with-cron.md`.
|
|
21
|
+
3. Make it resilient: `configure-retry-and-overlap.md`.
|
|
22
|
+
4. Fix wall-clock semantics: `pin-schedule-timezone.md`.
|
|
23
|
+
5. Watch it run: `observe-scheduler.md`.
|
|
24
|
+
|
|
25
|
+
## Conventions and pitfalls
|
|
26
|
+
|
|
27
|
+
- It is in-memory and per-process: each app instance runs its own copy. With multiple instances, guard jobs with a lock (for example `@warlock.js/cache`) or run the scheduler in one instance only.
|
|
28
|
+
- Missed runs while the process was down are not replayed.
|
|
29
|
+
- Set a timezone explicitly for jobs tied to business hours; otherwise UTC applies.
|
|
30
|
+
- Call `scheduler.shutdown()` on SIGTERM so running jobs finish.
|
|
31
|
+
- Use `preventOverlap()` for jobs that can outlast their interval.
|
|
32
|
+
|
|
33
|
+
## Server-only package
|
|
34
|
+
|
|
35
|
+
`@warlock.js/scheduler`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. This is build-boundary metadata read by `@warlock.js/web`'s Gate A (import resolution) and Gate C (emitted-bundle verification), not application behavior. App code that reaches the client bundle (including page and layout modules) must not value-import `@warlock.js/scheduler` — Gate A refuses the build. A type-only import (`import type { ... }`) is allowed. Server loaders, controllers, and modules may import it freely.
|
|
36
|
+
|
|
37
|
+
## When to reach for it
|
|
38
|
+
|
|
39
|
+
- You have **recurring work** in a Node service (cleanups, reports, syncs, polling) and want it scheduled inside the app process — no external cron, no docker-cron sidecar.
|
|
40
|
+
- You'd reach for **node-cron** or **agenda** but want a typed fluent API, retry-with-backoff, overlap prevention, and a working observability story built in — without bringing Redis (agenda) or hand-rolling retry yourself (node-cron).
|
|
41
|
+
- You're inside a `@warlock.js/*` project — the framework already uses this for built-in maintenance jobs (token cleanup, etc.).
|
|
42
|
+
|
|
43
|
+
Skip if you need a **distributed** job queue with persistence, retries across processes, and worker pools — reach for **BullMQ** or **Temporal**. This package runs jobs **in-process**; if the process dies before the next tick, the schedule resumes from now, not from the missed fire time.
|
|
44
|
+
|
|
45
|
+
## The mental model in one paragraph
|
|
46
|
+
|
|
47
|
+
Define a job with `job("name", async () => { ... })`. Attach a schedule via the fluent API (`.daily().at("03:00")`) or a cron string (`.cron("0 3 * * *")`). Optionally pin a timezone (`.inTimezone("America/New_York")`), prevent concurrent runs (`.preventOverlap()`), and configure retries (`.retry(3, 1000)`). Register the job with `scheduler.addJob(job)`, call `scheduler.start()`, and listen on the seven lifecycle events (`job:start` / `complete` / `error` / `skip`, `scheduler:started` / `stopped` / `tick`) for logging, metrics, and alerting. `scheduler.shutdown()` drains in-flight jobs before exit.
|
|
48
|
+
|
|
49
|
+
## What this package deliberately doesn't do
|
|
50
|
+
|
|
51
|
+
- **Distributed scheduling across processes.** Jobs run in the calling process. For multi-instance deployments, either run the scheduler on one instance (with a leader election) or reach for BullMQ / Temporal.
|
|
52
|
+
- **Persistent job state across restarts.** A missed fire while the process was down is missed — it doesn't catch up. For exactly-once-eventually semantics, use a queue.
|
|
53
|
+
- **Job priority queues or fan-out.** One job = one function. For fan-out, your job dispatches to a queue.
|
|
54
|
+
- **Cron-syntax extensions** beyond the standard 5-field form (no `@daily` macros, no seconds field). Use the fluent API for human-readable schedules instead.
|
|
55
|
+
|
|
56
|
+
## See also
|
|
57
|
+
|
|
58
|
+
- The `warlock-js-core` skill index — the parent framework that scheduled jobs typically run alongside.
|
|
59
|
+
|
|
60
|
+
|
|
7
61
|
## configure-retry-and-overlap `@warlock.js/scheduler/configure-retry-and-overlap/SKILL.md`
|
|
8
62
|
|
|
9
63
|
---
|
|
@@ -308,112 +362,6 @@ job.cronExpression; // string | null
|
|
|
308
362
|
- The `schedule-fluently` topic — the methods that mutate the state these getters expose
|
|
309
363
|
|
|
310
364
|
|
|
311
|
-
## overview `@warlock.js/scheduler/overview/SKILL.md`
|
|
312
|
-
|
|
313
|
-
---
|
|
314
|
-
name: overview
|
|
315
|
-
description: "@warlock.js/scheduler overview for orientation and choosing the focused skill for the task."
|
|
316
|
-
---
|
|
317
|
-
|
|
318
|
-
# `@warlock.js/scheduler` — overview
|
|
319
|
-
|
|
320
|
-
Production-ready job scheduler with a cron engine, fluent schedule builder, retry-with-backoff, overlap prevention, IANA timezone pinning, and seven typed lifecycle events. Two primitives — `job(name, fn)` and the `scheduler` singleton — that compose into everything else.
|
|
321
|
-
|
|
322
|
-
## Server-only package
|
|
323
|
-
|
|
324
|
-
`@warlock.js/scheduler`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. This is build-boundary metadata read by `@warlock.js/web`'s Gate A (import resolution) and Gate C (emitted-bundle verification), not application behavior. App code that reaches the client bundle (including page and layout modules) must not value-import `@warlock.js/scheduler` — Gate A refuses the build. A type-only import (`import type { ... }`) is allowed. Server loaders, controllers, and modules may import it freely.
|
|
325
|
-
|
|
326
|
-
## When to reach for it
|
|
327
|
-
|
|
328
|
-
- You have **recurring work** in a Node service (cleanups, reports, syncs, polling) and want it scheduled inside the app process — no external cron, no docker-cron sidecar.
|
|
329
|
-
- You'd reach for **node-cron** or **agenda** but want a typed fluent API, retry-with-backoff, overlap prevention, and a working observability story built in — without bringing Redis (agenda) or hand-rolling retry yourself (node-cron).
|
|
330
|
-
- You're inside a `@warlock.js/*` project — the framework already uses this for built-in maintenance jobs (token cleanup, etc.).
|
|
331
|
-
|
|
332
|
-
Skip if you need a **distributed** job queue with persistence, retries across processes, and worker pools — reach for **BullMQ** or **Temporal**. This package runs jobs **in-process**; if the process dies before the next tick, the schedule resumes from now, not from the missed fire time.
|
|
333
|
-
|
|
334
|
-
## The mental model in one paragraph
|
|
335
|
-
|
|
336
|
-
Define a job with `job("name", async () => { ... })`. Attach a schedule via the fluent API (`.daily().at("03:00")`) or a cron string (`.cron("0 3 * * *")`). Optionally pin a timezone (`.inTimezone("America/New_York")`), prevent concurrent runs (`.preventOverlap()`), and configure retries (`.retry(3, 1000)`). Register the job with `scheduler.addJob(job)`, call `scheduler.start()`, and listen on the seven lifecycle events (`job:start` / `complete` / `error` / `skip`, `scheduler:started` / `stopped` / `tick`) for logging, metrics, and alerting. `scheduler.shutdown()` drains in-flight jobs before exit.
|
|
337
|
-
|
|
338
|
-
## Skills index
|
|
339
|
-
|
|
340
|
-
Six task skills cover the full surface. Most callers only need `scheduler-basics` + `schedule-fluently` + `observe-scheduler`.
|
|
341
|
-
|
|
342
|
-
### Foundations
|
|
343
|
-
|
|
344
|
-
#### `scheduler-basics`
|
|
345
|
-
|
|
346
|
-
Start here. The `job(name, fn)` factory, the `scheduler` singleton, the 2-primitive surface, UTC default, why the API is factory-first.
|
|
347
|
-
|
|
348
|
-
### Scheduling
|
|
349
|
-
|
|
350
|
-
#### `schedule-fluently`
|
|
351
|
-
|
|
352
|
-
Build schedules without writing cron: `.everyMinutes(N)`, `.daily().at("03:00")`, `.weekly().on("monday")`, `.monthly()`, `.beginOf("month")`, `.endOf("year")`, and friends. Reach for this 80% of the time.
|
|
353
|
-
|
|
354
|
-
#### `schedule-with-cron`
|
|
355
|
-
|
|
356
|
-
Drop down to `.cron("0 9 * * 1-5")` when the fluent API can't express it (Vixie OR semantics for DOM/DOW, complex multi-value lists). Includes `parseCron()` for previewing next-run times before deploy.
|
|
357
|
-
|
|
358
|
-
### Production concerns
|
|
359
|
-
|
|
360
|
-
#### `configure-retry-and-overlap`
|
|
361
|
-
|
|
362
|
-
`.retry(maxRetries, delay?, backoffMultiplier?)` for fixed or exponential backoff; `.preventOverlap()` so a slow run never collides with the next tick. Reach for both anytime a job calls external services.
|
|
363
|
-
|
|
364
|
-
#### `pin-schedule-timezone`
|
|
365
|
-
|
|
366
|
-
`.inTimezone("America/New_York")` pins wall-clock fire times across DST and across servers. The default is UTC — if your job description includes "9 AM," you almost certainly want this skill.
|
|
367
|
-
|
|
368
|
-
### Observability + shutdown
|
|
369
|
-
|
|
370
|
-
#### `observe-scheduler`
|
|
371
|
-
|
|
372
|
-
The seven typed `SchedulerEvents` (`job:start` / `complete` / `error` / `skip`, `scheduler:started` / `stopped` / `tick`), the `JobResult` payload, `start()` / `stop()` / `shutdown()`. Wire this for logging, metrics, alerting, and graceful SIGTERM handling.
|
|
373
|
-
|
|
374
|
-
## Quick taste
|
|
375
|
-
|
|
376
|
-
```ts
|
|
377
|
-
import { Scheduler, job } from "@warlock.js/scheduler";
|
|
378
|
-
|
|
379
|
-
const scheduler = new Scheduler();
|
|
380
|
-
|
|
381
|
-
scheduler.on("job:error", (name, error) => {
|
|
382
|
-
console.error(`${name} failed:`, error);
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
scheduler.addJob(
|
|
386
|
-
job("cleanup", async () => {
|
|
387
|
-
await cleanupExpiredTokens();
|
|
388
|
-
})
|
|
389
|
-
.daily()
|
|
390
|
-
.at("03:00")
|
|
391
|
-
.inTimezone("America/New_York")
|
|
392
|
-
.preventOverlap()
|
|
393
|
-
.retry(3, 1000),
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
scheduler.addJob(
|
|
397
|
-
job("reports", sendReports).cron("0 9 * * 1-5"), // 9 AM weekdays
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
scheduler.start();
|
|
401
|
-
process.on("SIGTERM", () => scheduler.shutdown());
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
## What this package deliberately doesn't do
|
|
405
|
-
|
|
406
|
-
- **Distributed scheduling across processes.** Jobs run in the calling process. For multi-instance deployments, either run the scheduler on one instance (with a leader election) or reach for BullMQ / Temporal.
|
|
407
|
-
- **Persistent job state across restarts.** A missed fire while the process was down is missed — it doesn't catch up. For exactly-once-eventually semantics, use a queue.
|
|
408
|
-
- **Job priority queues or fan-out.** One job = one function. For fan-out, your job dispatches to a queue.
|
|
409
|
-
- **Cron-syntax extensions** beyond the standard 5-field form (no `@daily` macros, no seconds field). Use the fluent API for human-readable schedules instead.
|
|
410
|
-
|
|
411
|
-
## See also
|
|
412
|
-
|
|
413
|
-
- The `overview` topic of the `warlock-js-core` skill — the parent framework that scheduled jobs typically run alongside.
|
|
414
|
-
- `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this `overview` topic becomes the front-door skill in `.claude/skills/warlock-js-scheduler-overview/`. Every cross-link above names the topic (and the owning skill, across packages) instead of a file path so it survives that flattening.
|
|
415
|
-
|
|
416
|
-
|
|
417
365
|
## pin-schedule-timezone `@warlock.js/scheduler/pin-schedule-timezone/SKILL.md`
|
|
418
366
|
|
|
419
367
|
---
|
package/llms.txt
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
+
- [Package orientation](@warlock.js/scheduler/skills/index.md): In-process job scheduler for Warlock.js with fluent and cron scheduling, retries, overlap prevention and timezones. Exports `Scheduler`, `scheduler`, `Job`, `job`, `CronParser`, `parseCron`, plus types `RetryConfig`, `JobResult`, `SchedulerEvents`. Use for: "run this every day at 3am", "run a cron expression on weekdays", "retry a failing job 3 times", "stop a job overlapping itself", "run in New York time", "log job failures". Not this package: durable queues and background workers are not scheduling; per-run locking across instances uses @warlock.js/cache.
|
|
9
10
|
- [configure-retry-and-overlap](@warlock.js/scheduler/configure-retry-and-overlap/SKILL.md): Configure `.retry(maxRetries, delay?, backoffMultiplier?)` for fixed / exponential backoff and `.preventOverlap()` for external-invocation safety. Triggers: `.retry`, `.preventOverlap`, `maxRetries`, `backoffMultiplier`, `JobResult.retries`, `job:error`, `job:skip`; "how do I retry a failed job", "exponential backoff for scheduled job", "prevent concurrent job runs", "stop firing after N consecutive failures"; typical import `import { job, scheduler } from "@warlock.js/scheduler"`. Skip: events / shutdown — the `observe-scheduler` topic; building the schedule itself — the `schedule-fluently` topic; competing libs `p-retry`, `async-retry`, `bullmq`.
|
|
10
11
|
- [observe-scheduler](@warlock.js/scheduler/observe-scheduler/SKILL.md): Subscribe to scheduler lifecycle and job events for logging / metrics / alerting / graceful shutdown — seven typed `SchedulerEvents`, `JobResult` payload, `start()` / `stop()` / `shutdown()`. Triggers: `scheduler.on`, `scheduler.shutdown`, `JobResult`, `job:complete`, `job:error`, `scheduler:started`, `scheduler.getJob`; "graceful SIGTERM shutdown", "did this job run", "scheduler metrics and alerts", "subscribe to job events"; typical import `import { scheduler, type JobResult } from "@warlock.js/scheduler"`. Skip: retry / overlap — the `configure-retry-and-overlap` topic; building schedules — the `schedule-fluently` topic; native `EventEmitter`, `process.on`.
|
|
11
|
-
- [overview](@warlock.js/scheduler/overview/SKILL.md): @warlock.js/scheduler overview for orientation and choosing the focused skill for the task.
|
|
12
12
|
- [pin-schedule-timezone](@warlock.js/scheduler/pin-schedule-timezone/SKILL.md): Pin a job to a specific IANA timezone via `.inTimezone(zone)` so its wall-clock fire time stays correct regardless of server location / DST. Triggers: `.inTimezone`, `America/New_York`, `Europe/Berlin`, `Asia/Tokyo`, `NODE_ICU_DATA`; "schedule job in a timezone", "DST drift on non-UTC server", "multi-region fan-out scheduling", "fire at 9am ET regardless of server"; typical import `import { job, scheduler } from "@warlock.js/scheduler"`. Skip: fluent interval methods — the `schedule-fluently` topic; cron syntax — the `schedule-with-cron` topic; competing libs `dayjs-timezone`, `luxon`, `date-fns-tz`.
|
|
13
13
|
- [schedule-fluently](@warlock.js/scheduler/schedule-fluently/SKILL.md): 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 — the `schedule-with-cron` topic; timezone — the `pin-schedule-timezone` topic; retry — the `configure-retry-and-overlap` topic; competing `node-cron`, `node-schedule`, `agenda`; native `setInterval`.
|
|
14
14
|
- [schedule-with-cron](@warlock.js/scheduler/schedule-with-cron/SKILL.md): 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 — the `schedule-fluently` topic; timezone — the `pin-schedule-timezone` topic; competing libs `node-cron`, `cron`, `croner`, `cronstrue`.
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"dayjs": "^1.11.19"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
|
-
"@warlock.js/cache": "5.34.
|
|
11
|
+
"@warlock.js/cache": "5.34.2"
|
|
12
12
|
},
|
|
13
13
|
"peerDependenciesMeta": {
|
|
14
14
|
"@warlock.js/cache": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
],
|
|
35
35
|
"author": "hassanzohdy",
|
|
36
36
|
"license": "MIT",
|
|
37
|
-
"version": "5.34.
|
|
37
|
+
"version": "5.34.2",
|
|
38
38
|
"main": "./cjs/index.cjs",
|
|
39
39
|
"module": "./esm/index.mjs",
|
|
40
40
|
"types": "./esm/index.d.mts",
|
package/skills/index.md
CHANGED
|
@@ -8,7 +8,7 @@ Build a `job(name, fn)`, describe when it runs with the fluent API (`.daily().at
|
|
|
8
8
|
|
|
9
9
|
## The 80% path
|
|
10
10
|
|
|
11
|
-
1.
|
|
11
|
+
1. Start with `scheduler-basics.md`.
|
|
12
12
|
2. Define the schedule fluently: `schedule-fluently.md`, or by cron: `schedule-with-cron.md`.
|
|
13
13
|
3. Make it resilient: `configure-retry-and-overlap.md`.
|
|
14
14
|
4. Fix wall-clock semantics: `pin-schedule-timezone.md`.
|
|
@@ -18,6 +18,33 @@ Build a `job(name, fn)`, describe when it runs with the fluent API (`.daily().at
|
|
|
18
18
|
|
|
19
19
|
- It is in-memory and per-process: each app instance runs its own copy. With multiple instances, guard jobs with a lock (for example `@warlock.js/cache`) or run the scheduler in one instance only.
|
|
20
20
|
- Missed runs while the process was down are not replayed.
|
|
21
|
-
- Set a timezone explicitly for jobs tied to business hours; otherwise
|
|
21
|
+
- Set a timezone explicitly for jobs tied to business hours; otherwise UTC applies.
|
|
22
22
|
- Call `scheduler.shutdown()` on SIGTERM so running jobs finish.
|
|
23
23
|
- Use `preventOverlap()` for jobs that can outlast their interval.
|
|
24
|
+
|
|
25
|
+
## Server-only package
|
|
26
|
+
|
|
27
|
+
`@warlock.js/scheduler`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. This is build-boundary metadata read by `@warlock.js/web`'s Gate A (import resolution) and Gate C (emitted-bundle verification), not application behavior. App code that reaches the client bundle (including page and layout modules) must not value-import `@warlock.js/scheduler` — Gate A refuses the build. A type-only import (`import type { ... }`) is allowed. Server loaders, controllers, and modules may import it freely.
|
|
28
|
+
|
|
29
|
+
## When to reach for it
|
|
30
|
+
|
|
31
|
+
- You have **recurring work** in a Node service (cleanups, reports, syncs, polling) and want it scheduled inside the app process — no external cron, no docker-cron sidecar.
|
|
32
|
+
- You'd reach for **node-cron** or **agenda** but want a typed fluent API, retry-with-backoff, overlap prevention, and a working observability story built in — without bringing Redis (agenda) or hand-rolling retry yourself (node-cron).
|
|
33
|
+
- You're inside a `@warlock.js/*` project — the framework already uses this for built-in maintenance jobs (token cleanup, etc.).
|
|
34
|
+
|
|
35
|
+
Skip if you need a **distributed** job queue with persistence, retries across processes, and worker pools — reach for **BullMQ** or **Temporal**. This package runs jobs **in-process**; if the process dies before the next tick, the schedule resumes from now, not from the missed fire time.
|
|
36
|
+
|
|
37
|
+
## The mental model in one paragraph
|
|
38
|
+
|
|
39
|
+
Define a job with `job("name", async () => { ... })`. Attach a schedule via the fluent API (`.daily().at("03:00")`) or a cron string (`.cron("0 3 * * *")`). Optionally pin a timezone (`.inTimezone("America/New_York")`), prevent concurrent runs (`.preventOverlap()`), and configure retries (`.retry(3, 1000)`). Register the job with `scheduler.addJob(job)`, call `scheduler.start()`, and listen on the seven lifecycle events (`job:start` / `complete` / `error` / `skip`, `scheduler:started` / `stopped` / `tick`) for logging, metrics, and alerting. `scheduler.shutdown()` drains in-flight jobs before exit.
|
|
40
|
+
|
|
41
|
+
## What this package deliberately doesn't do
|
|
42
|
+
|
|
43
|
+
- **Distributed scheduling across processes.** Jobs run in the calling process. For multi-instance deployments, either run the scheduler on one instance (with a leader election) or reach for BullMQ / Temporal.
|
|
44
|
+
- **Persistent job state across restarts.** A missed fire while the process was down is missed — it doesn't catch up. For exactly-once-eventually semantics, use a queue.
|
|
45
|
+
- **Job priority queues or fan-out.** One job = one function. For fan-out, your job dispatches to a queue.
|
|
46
|
+
- **Cron-syntax extensions** beyond the standard 5-field form (no `@daily` macros, no seconds field). Use the fluent API for human-readable schedules instead.
|
|
47
|
+
|
|
48
|
+
## See also
|
|
49
|
+
|
|
50
|
+
- The `warlock-js-core` skill index — the parent framework that scheduled jobs typically run alongside.
|
package/skills/overview/SKILL.md
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: overview
|
|
3
|
-
description: "@warlock.js/scheduler overview for orientation and choosing the focused skill for the task."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# `@warlock.js/scheduler` — overview
|
|
7
|
-
|
|
8
|
-
Production-ready job scheduler with a cron engine, fluent schedule builder, retry-with-backoff, overlap prevention, IANA timezone pinning, and seven typed lifecycle events. Two primitives — `job(name, fn)` and the `scheduler` singleton — that compose into everything else.
|
|
9
|
-
|
|
10
|
-
## Server-only package
|
|
11
|
-
|
|
12
|
-
`@warlock.js/scheduler`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. This is build-boundary metadata read by `@warlock.js/web`'s Gate A (import resolution) and Gate C (emitted-bundle verification), not application behavior. App code that reaches the client bundle (including page and layout modules) must not value-import `@warlock.js/scheduler` — Gate A refuses the build. A type-only import (`import type { ... }`) is allowed. Server loaders, controllers, and modules may import it freely.
|
|
13
|
-
|
|
14
|
-
## When to reach for it
|
|
15
|
-
|
|
16
|
-
- You have **recurring work** in a Node service (cleanups, reports, syncs, polling) and want it scheduled inside the app process — no external cron, no docker-cron sidecar.
|
|
17
|
-
- You'd reach for **node-cron** or **agenda** but want a typed fluent API, retry-with-backoff, overlap prevention, and a working observability story built in — without bringing Redis (agenda) or hand-rolling retry yourself (node-cron).
|
|
18
|
-
- You're inside a `@warlock.js/*` project — the framework already uses this for built-in maintenance jobs (token cleanup, etc.).
|
|
19
|
-
|
|
20
|
-
Skip if you need a **distributed** job queue with persistence, retries across processes, and worker pools — reach for **BullMQ** or **Temporal**. This package runs jobs **in-process**; if the process dies before the next tick, the schedule resumes from now, not from the missed fire time.
|
|
21
|
-
|
|
22
|
-
## The mental model in one paragraph
|
|
23
|
-
|
|
24
|
-
Define a job with `job("name", async () => { ... })`. Attach a schedule via the fluent API (`.daily().at("03:00")`) or a cron string (`.cron("0 3 * * *")`). Optionally pin a timezone (`.inTimezone("America/New_York")`), prevent concurrent runs (`.preventOverlap()`), and configure retries (`.retry(3, 1000)`). Register the job with `scheduler.addJob(job)`, call `scheduler.start()`, and listen on the seven lifecycle events (`job:start` / `complete` / `error` / `skip`, `scheduler:started` / `stopped` / `tick`) for logging, metrics, and alerting. `scheduler.shutdown()` drains in-flight jobs before exit.
|
|
25
|
-
|
|
26
|
-
## Skills index
|
|
27
|
-
|
|
28
|
-
Six task skills cover the full surface. Most callers only need `scheduler-basics` + `schedule-fluently` + `observe-scheduler`.
|
|
29
|
-
|
|
30
|
-
### Foundations
|
|
31
|
-
|
|
32
|
-
#### `scheduler-basics`
|
|
33
|
-
|
|
34
|
-
Start here. The `job(name, fn)` factory, the `scheduler` singleton, the 2-primitive surface, UTC default, why the API is factory-first.
|
|
35
|
-
|
|
36
|
-
### Scheduling
|
|
37
|
-
|
|
38
|
-
#### `schedule-fluently`
|
|
39
|
-
|
|
40
|
-
Build schedules without writing cron: `.everyMinutes(N)`, `.daily().at("03:00")`, `.weekly().on("monday")`, `.monthly()`, `.beginOf("month")`, `.endOf("year")`, and friends. Reach for this 80% of the time.
|
|
41
|
-
|
|
42
|
-
#### `schedule-with-cron`
|
|
43
|
-
|
|
44
|
-
Drop down to `.cron("0 9 * * 1-5")` when the fluent API can't express it (Vixie OR semantics for DOM/DOW, complex multi-value lists). Includes `parseCron()` for previewing next-run times before deploy.
|
|
45
|
-
|
|
46
|
-
### Production concerns
|
|
47
|
-
|
|
48
|
-
#### `configure-retry-and-overlap`
|
|
49
|
-
|
|
50
|
-
`.retry(maxRetries, delay?, backoffMultiplier?)` for fixed or exponential backoff; `.preventOverlap()` so a slow run never collides with the next tick. Reach for both anytime a job calls external services.
|
|
51
|
-
|
|
52
|
-
#### `pin-schedule-timezone`
|
|
53
|
-
|
|
54
|
-
`.inTimezone("America/New_York")` pins wall-clock fire times across DST and across servers. The default is UTC — if your job description includes "9 AM," you almost certainly want this skill.
|
|
55
|
-
|
|
56
|
-
### Observability + shutdown
|
|
57
|
-
|
|
58
|
-
#### `observe-scheduler`
|
|
59
|
-
|
|
60
|
-
The seven typed `SchedulerEvents` (`job:start` / `complete` / `error` / `skip`, `scheduler:started` / `stopped` / `tick`), the `JobResult` payload, `start()` / `stop()` / `shutdown()`. Wire this for logging, metrics, alerting, and graceful SIGTERM handling.
|
|
61
|
-
|
|
62
|
-
## Quick taste
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
import { Scheduler, job } from "@warlock.js/scheduler";
|
|
66
|
-
|
|
67
|
-
const scheduler = new Scheduler();
|
|
68
|
-
|
|
69
|
-
scheduler.on("job:error", (name, error) => {
|
|
70
|
-
console.error(`${name} failed:`, error);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
scheduler.addJob(
|
|
74
|
-
job("cleanup", async () => {
|
|
75
|
-
await cleanupExpiredTokens();
|
|
76
|
-
})
|
|
77
|
-
.daily()
|
|
78
|
-
.at("03:00")
|
|
79
|
-
.inTimezone("America/New_York")
|
|
80
|
-
.preventOverlap()
|
|
81
|
-
.retry(3, 1000),
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
scheduler.addJob(
|
|
85
|
-
job("reports", sendReports).cron("0 9 * * 1-5"), // 9 AM weekdays
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
scheduler.start();
|
|
89
|
-
process.on("SIGTERM", () => scheduler.shutdown());
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## What this package deliberately doesn't do
|
|
93
|
-
|
|
94
|
-
- **Distributed scheduling across processes.** Jobs run in the calling process. For multi-instance deployments, either run the scheduler on one instance (with a leader election) or reach for BullMQ / Temporal.
|
|
95
|
-
- **Persistent job state across restarts.** A missed fire while the process was down is missed — it doesn't catch up. For exactly-once-eventually semantics, use a queue.
|
|
96
|
-
- **Job priority queues or fan-out.** One job = one function. For fan-out, your job dispatches to a queue.
|
|
97
|
-
- **Cron-syntax extensions** beyond the standard 5-field form (no `@daily` macros, no seconds field). Use the fluent API for human-readable schedules instead.
|
|
98
|
-
|
|
99
|
-
## See also
|
|
100
|
-
|
|
101
|
-
- The `overview` topic of the `warlock-js-core` skill — the parent framework that scheduled jobs typically run alongside.
|
|
102
|
-
- `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this `overview` topic becomes the front-door skill in `.claude/skills/warlock-js-scheduler-overview/`. Every cross-link above names the topic (and the owning skill, across packages) instead of a file path so it survives that flattening.
|