first commit
This commit is contained in:
97
docs/communication/messaging/sms/src/scheduler/scheduler.md
Normal file
97
docs/communication/messaging/sms/src/scheduler/scheduler.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# `scheduler`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`scheduler` provides `MessageScheduler` — the minimal timer
|
||||
seam that `SmsService.scheduleAt` uses to fire a delayed
|
||||
delivery. The class wraps an injectable `TimersLike` so tests
|
||||
drive it deterministically, computes the delay from an
|
||||
injectable `Clock`, and returns a `ScheduleHandle` with
|
||||
`cancel()` so a caller can abort a scheduled message before the
|
||||
timer fires.
|
||||
|
||||
The scheduler is intentionally small: it does not own retry,
|
||||
rate-limit, opt-out, or template logic — those layers live in
|
||||
`SmsService` so a scheduled message goes through the full
|
||||
pipeline when the timer fires.
|
||||
|
||||
## Features
|
||||
|
||||
- `scheduleAt(date, fn)` — schedules `fn` to run at the target
|
||||
instant.
|
||||
- Returns a `ScheduleHandle` with `cancel()` (idempotent) and
|
||||
the original `sendAt` field for observation.
|
||||
- Injectable `TimersLike` seam — defaults to a
|
||||
`globalThis.setTimeout` / `clearTimeout` adapter.
|
||||
- Injectable `Clock` — defaults to `Date.now`.
|
||||
- Past dates collapse to a zero-delay timer, matching the
|
||||
expectation that "send it now (or as soon as possible)".
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { MessageScheduler } from "@suaveplan/sms/scheduler";
|
||||
|
||||
const scheduler = new MessageScheduler();
|
||||
const handle = scheduler.scheduleAt(
|
||||
new Date(Date.now() + 60_000),
|
||||
() => console.log("fire!")
|
||||
);
|
||||
handle.cancel(); // call before firing to abort
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { MessageScheduler } from "@suaveplan/sms/scheduler";
|
||||
|
||||
// Controlled timers + clock for deterministic tests.
|
||||
const pending: Array<{ cb: () => void; delayMs: number }> = [];
|
||||
const scheduler = new MessageScheduler({
|
||||
timers: {
|
||||
setTimer(cb, delayMs) {
|
||||
pending.push({ cb, delayMs });
|
||||
return pending.length;
|
||||
},
|
||||
clearTimer() {
|
||||
/* no-op for tests */
|
||||
},
|
||||
},
|
||||
clock: () => 1_000,
|
||||
});
|
||||
|
||||
const handle = scheduler.scheduleAt(new Date(2_000), () => {
|
||||
/* runs at virtual t=2000 */
|
||||
});
|
||||
|
||||
// Manually fire the queued callback to simulate the timer
|
||||
pending[0]?.cb();
|
||||
handle.cancel(); // safe to call after fire
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class MessageScheduler`
|
||||
- `constructor(options?: MessageSchedulerOptions)`
|
||||
- `scheduleAt(sendAt: Date, fn: () => void): ScheduleHandle`
|
||||
- `interface ScheduleHandle`
|
||||
- `cancel(): void` — idempotent
|
||||
- `readonly sendAt: Date`
|
||||
- `interface MessageSchedulerOptions`
|
||||
- `timers?: TimersLike`
|
||||
- `clock?: Clock`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The scheduler tracks a `cancelled` flag inside its timer
|
||||
callback so a cancellation that races the timer (i.e. the
|
||||
caller calls `cancel` after the timer has already been pulled
|
||||
but before the callback runs) is still honoured. Double-cancel
|
||||
is a no-op. The platform adapter casts the `setTimeout` return
|
||||
to `number` for cross-runtime compatibility — Node returns a
|
||||
`Timeout` object, browsers/Bun return a number; both shapes are
|
||||
safe to forward back to `clearTimeout`.
|
||||
Reference in New Issue
Block a user