first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
# `notification-scheduler` — Future-delivery scheduler
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`notification-scheduler.ts` ships the `NotificationScheduler` class for scheduling notifications for future delivery. The scheduler does NOT call the router itself — callers register an `onDue(handler)` that receives the original `NotificationInput`, typically wiring it back into `NotificationRouter.send` or `enqueue`. The class exposes both manual `processDue()` (call from cron / queue worker) and `start()`/`stop()` for in-process polling.
## Features
- `schedule(input, deliverAt)` returns a stable id.
- `cancel(id)`, `get(id)`, `listPending()`, `getDue(now?)` for inspection.
- `processDue()` awaits the handler per entry and removes processed entries.
- `start()`/`stop()` polling driver with idempotent semantics.
- Injectable `Clock` for deterministic tests.
- Throws `NotificationNotFoundError` for unknown ids on `cancel` / `get`.
## Usage: Basic
```ts
import {
NotificationRouter,
NotificationScheduler,
} from "@suaveplan/notification-channel";
const router = new NotificationRouter({ channels, defaultChannel: "email" });
const scheduler = new NotificationScheduler();
scheduler.onDue(async (input) => {
await router.send(input);
});
scheduler.schedule({ recipient: "u@x", body: "Reminder" }, new Date(Date.now() + 60_000));
scheduler.start();
```
## Usage: Advanced
```ts
const scheduler = new NotificationScheduler(5_000, () => clock.now());
scheduler.onDue(async (input) => {
try {
await router.send(input);
} catch (err) {
await failureQueue.push({ input, err });
}
});
// Hook into a cron worker instead of in-process polling:
cron.schedule("* * * * *", () => {
void scheduler.processDue();
});
```
## API Reference
- `NotificationScheduler(checkIntervalMs?, clock?)` — constructor.
- `schedule(input, deliverAt)`, `cancel(id)`, `get(id)`, `listPending()`, `getDue(now?)`, `processDue()`, `start()`, `stop()`, `size`.
- `ScheduledNotification` — `{ id, input, scheduledFor, createdAt, cancelled }`.
- `ScheduleHandler` — `(input: NotificationInput) => Promise<void>`.
## Implementation Notes
`start()` is idempotent — calling twice has no effect; the second call is dropped before `setInterval` runs. `stop()` is idempotent — calling twice is safe and a leading call before `start()` is a no-op. `processDue()` is the single place that mutates the schedule map — both the polling driver and manual / cron callers use it, so behaviour is identical regardless of how processing is triggered. The scheduler does not persist across process restarts — production deployments should layer their own durable store atop `schedule`/`cancel` (e.g. snapshot `listPending()` to disk on shutdown, restore on boot via additional `schedule` calls).