first commit
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# `preference-manager` — Per-user opt-in / opt-out + quiet hours
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`preference-manager.ts` ships the `PreferenceManager` class that answers, at every send-time, "should this notification reach this user on this channel right now?". The manager combines per-channel opt-in / opt-out preferences with optional quiet-hours configuration (with priority thresholds) and a pluggable `PreferenceStoreAdapter` for persistence.
|
||||
|
||||
## Features
|
||||
|
||||
- Per-user channel enable / disable, with explicit `enabledChannels` and `disabledChannels` sets.
|
||||
- Default channels (configurable; built-in default: `["email", "in_app"]`).
|
||||
- Quiet-hours config with `startHour`, `endHour` (wrap-around supported), and optional `days` filter.
|
||||
- `urgentThreshold` controls which priorities punch through quiet hours (default `urgent`).
|
||||
- `canDeliver(userId, channel, priority)` is the single decision point.
|
||||
- Pluggable `PreferenceStoreAdapter` so persistence is delegated to SurrealDB, Postgres, Redis, etc.
|
||||
- Injectable `Clock` for deterministic tests.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
PreferenceManager,
|
||||
type PreferenceStoreAdapter,
|
||||
} from "@suaveplan/notification-channel";
|
||||
|
||||
const store: PreferenceStoreAdapter = makeStore();
|
||||
const manager = new PreferenceManager({ store });
|
||||
|
||||
if (await manager.canDeliver("user-1", "email", "normal")) {
|
||||
await router.send(...);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
const manager = new PreferenceManager({
|
||||
store,
|
||||
defaultChannels: ["email", "push", "in_app"],
|
||||
clock: () => Date.now(),
|
||||
});
|
||||
|
||||
await manager.setEnabled("user-1", "sms", true);
|
||||
await manager.setQuietHours("user-1", {
|
||||
startHour: 22,
|
||||
endHour: 7,
|
||||
days: [1, 2, 3, 4, 5], // weekdays
|
||||
});
|
||||
|
||||
// At dispatch time:
|
||||
for (const channel of selected) {
|
||||
if (await manager.canDeliver("user-1", channel, "high")) {
|
||||
await router.send({ recipient: "user-1", body: text, channel });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `PreferenceManager(options: PreferenceManagerOptions)` — constructor.
|
||||
- `PreferenceManagerOptions` — `{ store, defaultChannels?, clock? }`.
|
||||
- `canDeliver(userId, channel, priority): Promise<boolean>`.
|
||||
- `getPreferences(userId): Promise<UserPreferences>`.
|
||||
- `setEnabled(userId, channel, enabled): Promise<void>`.
|
||||
- `setQuietHours(userId, quietHours | null): Promise<void>`.
|
||||
- `resetPreferences(userId): Promise<boolean>`.
|
||||
- `PreferenceStoreAdapter`, `UserPreferences`, `StoredPreferences`, `QuietHoursConfig`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Quiet-hours wrap around midnight when `startHour > endHour` (e.g. `22 → 7`). Priorities are ordered `low < normal < high < urgent`; if `urgentThreshold` is `"high"`, then both `high` and `urgent` punch through quiet hours. The day filter is opt-in — when omitted, quiet hours apply every day; when supplied, only the listed days (`0` = Sunday) trigger the window. `setQuietHours(userId, null)` clears the field; existing `enabledChannels`/`disabledChannels`/`urgentThreshold` are preserved. The store adapter is the single integration seam — the rest of the class is purely functional logic, so swapping persistence layers does not change behaviour.
|
||||
Reference in New Issue
Block a user