npm package: @papack/cron
Description: Minimal cron core that triggers time-based tasks without blocking the event loop.
Author: Matthias Steiner
Latest version: 1.0.0

# @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

```bash
bun add @papack/cron
# or
npm install @papack/cron
```

## Basic Usage

```ts
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`

```ts
new Cron(options);
```

#### Options

```ts
interface CronOptions {
  timezone?: string; // valid IANA timezone, e.g. "Europe/Berlin"
  onError: (
    error: unknown,
    context: {
      expression: string;
      task: () => void | Promise<void>;
      date: Date;
    }
  ) => void;
}
```

- `onError` is **required**
- errors are **never swallowed**
- the core stays alive, the reaction is your decision

### Methods

```ts
cron.schedule(expression, task);
cron.start();
cron.stop();
```

#### `schedule(expression, task)`

Registers a cron task.

- `expression`: classic 5-field cron expression
- `task`: sync or async function
- async tasks are **not awaited**

```ts
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

```ts
onError(err, ctx) {
  console.error(err);
}
```

### Fail fast (crash process)

```ts
onError(err) {
  throw err;
}
```

### Monitoring

```ts
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:

```ts
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

```txt
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:

```ts
cron.schedule("* * * * *", async () => {
  await doSomething();
});
```

Notes:

- the scheduler does **not await**
- parallel execution is possible
- error handling is your responsibility via `onError`