@papack/cron
v1.0.0
Published
Minimal cron core that triggers time-based tasks without blocking the event loop.
Readme
@papack/cron
A minimal cron core that triggers time-based tasks without blocking the event loop, leaving execution semantics to user code.
- Classic 5-field cron syntax (minute-based)
- Explicit lifecycle (
start / stop) - Clear separation of concerns
- Deterministic behavior
- Fully testable core
- No hidden defaults
- No silent error swallowing
Installation
bun add @papack/cron
# or
npm install @papack/cronBasic Usage
import { Cron } from "@papack/cron";
const cron = new Cron({
timezone: "Europe/Berlin",
onError(error, context) {
console.error("Cron task failed:", context.expression, error);
},
});
cron.schedule("*/5 * * * *", () => {
console.log("runs every 5 minutes");
});
cron.start();Public API
Cron
new Cron(options);Options
interface CronOptions {
timezone?: string; // valid IANA timezone, e.g. "Europe/Berlin"
onError: (
error: unknown,
context: {
expression: string;
task: () => void | Promise<void>;
date: Date;
}
) => void;
}onErroris required- errors are never swallowed
- the core stays alive, the reaction is your decision
Methods
cron.schedule(expression, task);
cron.start();
cron.stop();schedule(expression, task)
Registers a cron task.
expression: classic 5-field cron expressiontask: sync or async function- async tasks are not awaited
cron.schedule("0 9 * * *", async () => {
await doWork();
});Cron Syntax
Five fields:
* * * * *
│ │ │ │ │
│ │ │ │ └─ weekday (0–6, Sunday = 0)
│ │ │ └─── month (1–12)
│ │ └───── day of month (1–31)
│ └─────── hour (0–23)
└───────── minute (0–59)Supported:
*- single numbers (
5) - steps (
*/5)
Not supported:
- seconds
- ranges (
1-5) - lists (
1,2,3) - aliases (
@daily) - Quartz extensions
Error Handling (Important)
The cron core catches task errors to protect itself,
but forces you to handle them via onError.
Example policies:
Log and continue
onError(err, ctx) {
console.error(err);
}Fail fast (crash process)
onError(err) {
throw err;
}Monitoring
onError(err, ctx) {
sendToSentry(err, ctx);
}What the core will not do:
- retry
- log automatically
- crash silently
- hide failures
Execution Semantics
What this cron guarantees:
- at most one trigger per minute
- no duplicate execution in the same minute
- correct cron matching
- deterministic behavior
What it does not guarantee:
- exactly-once execution
- catch-up after downtime
- ordering
- completion before next tick
- cluster safety
Skipped minutes are allowed. That is by design.
Timezones
Timezone is configured once, at instantiation:
new Cron({ timezone: "UTC", onError });- applies to all jobs
- validated at construction time
- implemented via
Intl.DateTimeFormat
No per-job timezones. No ambiguity.
Architecture Overview
Cron (API)
├─ Parser → cron expression → sets
├─ Matcher → cron + Date → boolean
├─ Scheduler → minute boundaries, never twice
│ ├─ Clock → current time (timezone-aware)
│ └─ Timer → minute ticks (aligned, no drift)Each part has exactly one responsibility.
Async Tasks
Async tasks are allowed:
cron.schedule("* * * * *", async () => {
await doSomething();
});Notes:
- the scheduler does not await
- parallel execution is possible
- error handling is your responsibility via
onError
