# `preference-manager` — Per-user opt-in / opt-out + quiet hours _(no submodules)_ ## 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`. - `getPreferences(userId): Promise`. - `setEnabled(userId, channel, enabled): Promise`. - `setQuietHours(userId, quietHours | null): Promise`. - `resetPreferences(userId): Promise`. - `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.