first commit
This commit is contained in:
109
docs/foundation/timing/cron/src/scheduler/cron-scheduler.md
Normal file
109
docs/foundation/timing/cron/src/scheduler/cron-scheduler.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# CronScheduler
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`CronScheduler` is a deliberately minimal in-process cron driver. It
|
||||
parses each registered expression once via `parseCron`, schedules a
|
||||
single `setTimeout` per job pointing at the next computed fire time, and
|
||||
re-schedules immediately after each handler completes. Queueing,
|
||||
retries, concurrency limits, persistence, and lifecycle hooks are
|
||||
intentionally out of scope — that's `@suaveplan/job-scheduler`'s
|
||||
responsibility. This class is small enough to embed inside a CLI, a
|
||||
browser admin panel, or a serverless worker without dragging in the
|
||||
full job-runner footprint.
|
||||
|
||||
## Features
|
||||
|
||||
- `add(expr, handler, options?)` returns a string id (auto-generated via
|
||||
`crypto.randomUUID()` when omitted).
|
||||
- `remove(id)` clears the timer and removes the job.
|
||||
- `list()` snapshots every registered job with its next-run time.
|
||||
- `start()` / `stop()` are idempotent; `stop()` awaits every in-flight
|
||||
handler before resolving.
|
||||
- Handler errors route to `options.onError` when provided, otherwise are
|
||||
silently swallowed.
|
||||
- Injectable `Clock` and injectable `{ setTimer, clearTimer }` pair for
|
||||
fast, deterministic tests.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import { CronScheduler } from "@suaveplan/cron";
|
||||
|
||||
const scheduler = new CronScheduler();
|
||||
scheduler.add("0 * * * *", async () => {
|
||||
console.log("hourly tick");
|
||||
});
|
||||
scheduler.start();
|
||||
|
||||
// later…
|
||||
await scheduler.stop();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```typescript
|
||||
import { createCronScheduler } from "@suaveplan/cron";
|
||||
|
||||
let fakeNow = Date.UTC(2026, 4, 11, 9, 59, 0);
|
||||
const pending = new Map<number, { cb: () => void; fireAt: number }>();
|
||||
let nextTimerId = 1;
|
||||
|
||||
const scheduler = createCronScheduler({
|
||||
clock: () => fakeNow,
|
||||
timers: {
|
||||
setTimer: (cb, ms) => {
|
||||
const id = nextTimerId++;
|
||||
pending.set(id, { cb, fireAt: fakeNow + ms });
|
||||
return id;
|
||||
},
|
||||
clearTimer: (id) => pending.delete(id),
|
||||
},
|
||||
onError: (err, id) => console.error(`job ${id}`, err),
|
||||
});
|
||||
|
||||
scheduler.add("0 * * * *", () => console.log("fired"));
|
||||
scheduler.start();
|
||||
|
||||
// fast-forward
|
||||
fakeNow = Date.UTC(2026, 4, 11, 10, 0, 1);
|
||||
for (const [id, { cb, fireAt }] of pending) {
|
||||
if (fireAt <= fakeNow) {
|
||||
pending.delete(id);
|
||||
cb();
|
||||
}
|
||||
}
|
||||
await scheduler.stop();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class CronScheduler` with `constructor(options?: CronSchedulerOptions)`.
|
||||
- `add(expr: string, handler: () => void | Promise<void>,
|
||||
options?: CronSchedulerAddOptions): string`.
|
||||
- `remove(id: string): boolean`.
|
||||
- `list(): readonly CronSchedulerJob[]`.
|
||||
- `start(): void`.
|
||||
- `stop(): Promise<void>`.
|
||||
- `createCronScheduler(options?): CronScheduler` — factory mirror.
|
||||
- `CronSchedulerOptions`, `CronSchedulerAddOptions`, `CronSchedulerJob`,
|
||||
`SchedulerTimers` types.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Each job tracks at most one outstanding timer id. `stop()` walks every
|
||||
job, clears its timer, and then `await`s the union of every promise in
|
||||
the `inFlight` set, so handlers mid-execution always run to completion
|
||||
before the returned promise resolves. Handler errors are caught inside
|
||||
`invokeHandler` and forwarded to `onError`; the post-handler
|
||||
re-scheduling continues unconditionally so a transient handler failure
|
||||
does not stop the job from firing again. The injectable `timers` pair
|
||||
is the testing seam: production callers get the real `setTimeout`, while
|
||||
tests bind it to a `FakeClock` so the integration suite can fire several
|
||||
hours of cron triggers in milliseconds. Job ids are auto-generated via
|
||||
`crypto.randomUUID` when available, falling back to a timestamp-plus-
|
||||
random string in environments that lack it, so the scheduler stays
|
||||
universal.
|
||||
Reference in New Issue
Block a user