6.4 KiB
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— sixDeliveryChannelimplementations. - 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:
FakeFetchtest double forFetchLike. - 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-memoryMeterProvider.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.sendFncallbacks. 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 throughEventBus.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.