# `error-codes` — Notification-channel error registry _(no submodules)_ ## Purpose `error-codes.ts` is the single source of truth for every error the `@suaveplan/notification-channel` package can throw. It registers a stable namespace `NOTIFICATION_CHANNEL` with eleven reason codes via `defineCodes("NOTIFICATION_CHANNEL", […])` from `@suaveplan/error`, and exposes a typed `SuaveplanError` subclass for each reason. The registry preserves the literal-typed union through the public surface so downstream consumers — loggers, problem+json responders, retry wrappers — can `instanceof`-narrow on a specific subclass or match on the `.code` discriminant without unsafe casts. The module is side-effect-imported from `src/index.ts` so the codes are part of the package's public ABI even for consumers that pull a subpath export. ## Features - Eleven stable reason codes: `NOT_FOUND`, `CHANNEL_DISABLED`, `TEMPLATE_NOT_FOUND`, `RATE_LIMIT_EXCEEDED`, `ROUTING_FAILED`, `DELIVERY_FAILED`, `PROVIDER_ERROR`, `RATE_LIMITED`, `INVALID_RECIPIENT`, `CHANNEL_UNAVAILABLE`, `TIMEOUT`. - One typed subclass per code extending an abstract `NotificationChannelError` base. - Compile-time-frozen `NOTIFICATION_CHANNEL_CODES` record whose values are literal `"NOTIFICATION_CHANNEL.REASON"` strings. - Every constructor accepts the standard `SuaveplanErrorOptions` bag (`cause`, `context`, `traceId`, `clock`). - `RateLimitedError` carries an additional `retryAfterMs` field merged into `context`. ## Usage: Basic ```ts import { NOTIFICATION_CHANNEL_CODES, ChannelDisabledError, type NotificationChannelErrorCode, } from "@suaveplan/notification-channel/error-codes"; try { /* ... call the router ... */ } catch (err) { if (err instanceof ChannelDisabledError) { const code: NotificationChannelErrorCode = err.code; void code; // "NOTIFICATION_CHANNEL.CHANNEL_DISABLED" } } void NOTIFICATION_CHANNEL_CODES.RATE_LIMITED; ``` ## Usage: Advanced ```ts import { RateLimitedError } from "@suaveplan/notification-channel/error-codes"; throw new RateLimitedError({ message: "notification.errors.rateLimited", retryAfterMs: 30_000, context: { provider: "twilio" }, traceId: "trace-abc", }); ``` ## API Reference - `NOTIFICATION_CHANNEL_CODES` — frozen record `{ readonly [K in Reason]: "NOTIFICATION_CHANNEL.${K}" }`. - `NotificationChannelErrorCode` — discriminated union of all eleven literal codes. - `NotificationChannelError` — abstract base extending `SuaveplanError`. - Concrete subclasses: `NotificationNotFoundError`, `ChannelDisabledError`, `TemplateNotFoundError`, `RateLimitExceededError`, `RoutingFailedError`, `DeliveryFailedError`, `ProviderErrorError`, `RateLimitedError`, `InvalidRecipientError`, `ChannelUnavailableError`, `DeliveryTimeoutError`. ## Implementation Notes The registry uses `defineCodes` from `@suaveplan/error` rather than a runtime `ErrorCodes` registry. `defineCodes` is purely type-level plus a frozen object, so it has no module-load side effect beyond the freeze, and the literal-typed union survives tree-shaking. `pickOpts` discards undefined fields explicitly to remain compatible with `exactOptionalPropertyTypes: true` in `tsconfig.base.json`. The split between "local lifecycle errors" and "upstream-provider errors" upstream had two parallel registries (`error-codes.ts` and `errors/notification-errors.ts`) — those are unified here so consumers see a single canonical code list. `RateLimitedError` is the one place the constructor accepts a typed sidecar (`retryAfterMs`) — it merges into `context` so downstream HTTP `Retry-After` mapping is straightforward.