3.1 KiB
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
enabledChannelsanddisabledChannelssets. - Default channels (configurable; built-in default:
["email", "in_app"]). - Quiet-hours config with
startHour,endHour(wrap-around supported), and optionaldaysfilter. urgentThresholdcontrols which priorities punch through quiet hours (defaulturgent).canDeliver(userId, channel, priority)is the single decision point.- Pluggable
PreferenceStoreAdapterso persistence is delegated to SurrealDB, Postgres, Redis, etc. - Injectable
Clockfor deterministic tests.
Usage: Basic
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
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.