Files
2026-05-27 23:29:53 +01:00

89 lines
6.4 KiB
Markdown

# Ship @suaveplan/notification-channel
**Package**: `@suaveplan/notification-channel`
**Status**: PROPOSED
**Type**: new-package (migration)
- **Design**: ./design.md (inlined below — single-file change)
- **Spec delta**: ./specs/notification-channel/spec.md
## Why
Genesis needs a unified multi-channel notification router so downstream services (api-plugin-notifications, marketing-ops, alerting) have a single, deterministic surface for delivering messages over email, SMS, push, in-app, and webhooks. The upstream `@suaveplan/notification-channel@1.2.6` ships in `../suaveplan/packages/universal/notification-channel/` with 34 source files and ~95% coverage; this change lands a feature-complete port at 100/100/100/100 coverage, rebuilt against the genesis foundation (`defineCodes` registry, `EventBus`, Zod boundaries, OTel meters, injectable `FetchLike`/`Clock`).
## What changes
This is a brand-new package — every export is ADDED.
- ADDED: `NotificationRouter` — primary entrypoint; routes notifications across channels with template rendering, sliding-window rate limiting, status lifecycle, EventBus emission, optional delivery-channel dispatch.
- ADDED: `EmailChannel`, `SmsChannel`, `PushChannel`, `InAppChannel`, `WebhookChannel`, `DigestChannel` — six `DeliveryChannel` implementations.
- ADDED: `TemplateEngine` — channel-specific template variants with `{{var}}` substitution.
- ADDED: `ChannelRateLimiter` — sliding-window per-channel + per-user rate limits.
- ADDED: `DeliveryRetry` — exponential-backoff retry wrapper with dead-letter queue.
- ADDED: `NotificationScheduler` — schedule-for-future + interval processing.
- ADDED: `PreferenceManager` — per-user opt-in/out + quiet-hours, storage-adapter pluggable.
- ADDED: `NOTIFICATION_CHANNEL_CODES` — `defineCodes("NOTIFICATION_CHANNEL", […])` registry with 11 stable reason codes.
- ADDED: `notificationHooks` / `onNotificationRouting` — pre-routing hook pipeline.
- ADDED: `FakeFetch` test double for `FetchLike`.
- ADDED: OTel meters: `notification_channel.routed`, `notification_channel.delivery_attempts`, `notification_channel.dead_letters`, `notification_channel.latency_ms`.
- ADDED: Zod schemas at every public option boundary.
## Source mapping
This change derives from `../suaveplan/enterprise/notification-channel.md` and the upstream package source.
| Requirement | Source lines | Notes |
|---|---|---|
| Multi-channel router with EventBus + lifecycle | `../suaveplan/packages/universal/notification-channel/src/router/notification-router.ts` L1-L313 | Re-shaped onto genesis `EventBus`; injectable `Clock`. |
| Email / SMS / Push / InApp / Webhook channels | `src/channels/*.ts` (upstream) | Webhook uses injectable `FetchLike`. Push uses injectable `FetchLike` for VAPID POST. |
| DigestChannel | `src/digest/digest-channel.ts` | Injectable `Clock` for interval gating. |
| TemplateEngine with channel variants | `src/templates/template-engine.ts` | Verbatim. |
| ChannelRateLimiter sliding-window | `src/rate-limiter/channel-rate-limiter.ts` | Injectable `Clock`. |
| DeliveryRetry + dead-letters | `src/retry/delivery-retry.ts` | Injectable `Clock`/`TimersLike` sleeper. |
| NotificationScheduler | `src/scheduler/notification-scheduler.ts` | Injectable `Clock`. |
| PreferenceManager + quiet hours | `src/preferences/preference-manager.ts` | Injectable `Clock`. |
| Error codes registry | `enterprise/notification-channel.md` L51-L75 + upstream `error-codes.ts` + `errors/notification-errors.ts` | Reunified onto `defineCodes("NOTIFICATION_CHANNEL", [...])`; the upstream split between `error-codes.ts` (process-wide `ErrorCodes`) and `errors/notification-errors.ts` (`NotificationError` factory) is collapsed into one stable code registry per genesis convention. |
| Hooks pipeline | `src/hooks.ts` | Reimplemented as local `Hooks<Map>` — `@suaveplan/events` does not yet export `Hooks` on genesis. |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| OTel meters + `refreshNotificationMeters` | Enterprise audit §3 requires OTel; upstream has none — added per genesis convention. |
| Zod boundary schemas | Enterprise audit §3 requires Zod at every public type — upstream has none — added per genesis convention. |
| Injectable `FetchLike` in webhook/push | Genesis universal-tier deterministic-fetch convention — upstream uses raw `globalThis.fetch`. |
| `FakeFetch` test double | Replaces `vi.mock`/runtime-monkey-patching tests (no mocks rule). |
## Verification approach
Every requirement in the spec delta carries a `**Verified by:**` line naming concrete test titles.
- `src/error-codes.test.ts` — registry stability, code shape, side-effect import.
- `src/channels/<channel>.test.ts` — one per channel.
- `src/digest/digest-channel.test.ts` — digest queueing/flushing.
- `src/router/notification-router.test.ts` + `src/router/notification-router-routing.test.ts` — send/lifecycle/routing/rate-limit.
- `src/rate-limiter/channel-rate-limiter.test.ts`, `src/retry/delivery-retry.test.ts`, `src/scheduler/notification-scheduler.test.ts`, `src/templates/template-engine.test.ts`, `src/preferences/preference-manager.test.ts`, `src/hooks/hooks.test.ts`, `src/types/notification.types.test.ts`.
- `src/otel/metrics.test.ts` — instruments fire against real in-memory `MeterProvider`.
- `src/fakes/fake-fetch.test.ts` — fake-fetch double.
## Out of scope
- Real provider integrations (Twilio, SendGrid, Amazon SES, Firebase Cloud Messaging) — gated by `EmailChannel.sendFn`/`SmsChannel.sendFn`/`PushChannel.sendFn` callbacks. Provider adapters land in sibling packages (`@suaveplan/emailer`, `@suaveplan/sms-providers`) per the genesis enterprise plan.
- Persistence of notification history — the in-memory `Map<string, Notification>` is intentional; downstream consumers wire their own storage adapter through `EventBus.on("notification.sent", …)`.
### Excluded from this migration
None — the migration is at full functional parity with upstream `1.2.6` plus genesis enterprise enhancements (OTel, Zod boundaries, injectable fetch).
## Impact
### Affected dependents
Run `bunx turbo ls --filter=...@suaveplan/notification-channel` to enumerate. This is a brand-new package on genesis — no genesis consumers yet.
- `@suaveplan/api-plugin-notifications` — [x] no action needed (consumer will land in a follow-up change; no genesis code depends on this package today).
## Migration impact
None — additive only.