# `notification-scheduler` — Future-delivery scheduler _(no submodules)_ ## 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`. ## 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).