first commit
This commit is contained in:
@@ -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).
|
||||
Reference in New Issue
Block a user