first commit
This commit is contained in:
110
docs/communication/messaging/sms/src/error-codes/error-codes.md
Normal file
110
docs/communication/messaging/sms/src/error-codes/error-codes.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# `error-codes`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The error-codes module owns the canonical `SMS.*` registry for
|
||||
`@suaveplan/sms`. Every error surfaced by the package — invalid
|
||||
phone number, missing provider, exhausted retries, opted-out
|
||||
recipient, rate limit, scheduled-message cancellation, abort —
|
||||
carries one of the thirteen stable codes registered here. This
|
||||
keeps downstream consumers (loggers, problem+json responders,
|
||||
retry wrappers, dashboards) free from re-implementing the
|
||||
classifier.
|
||||
|
||||
The thirteen codes are declared with the
|
||||
`defineCodes("SMS", [...])` helper, which produces a frozen
|
||||
`Namespace.Reason` string union. Each code has a dedicated
|
||||
leaf-error class extending the package-local abstract
|
||||
`BaseSmsError` (itself a thin `SuaveplanError<SmsCode>`
|
||||
subclass), so consumers can `instanceof` the class as well as
|
||||
match on `.code`.
|
||||
|
||||
## Features
|
||||
|
||||
- Thirteen `SMS.*` codes: `INVALID_NUMBER`, `INVALID_CONFIG`,
|
||||
`PROVIDER_NOT_CONFIGURED`, `PROVIDER_ERROR`, `SEND_FAILED`,
|
||||
`TEMPLATE_NOT_FOUND`, `RECIPIENT_OPTED_OUT`, `RATE_LIMITED`,
|
||||
`FORBIDDEN`, `CONFIG_MISSING`, `SCHEDULE_CANCELLED`, `ABORTED`,
|
||||
`RECEIPT_NOT_FOUND`.
|
||||
- One leaf class per code, all extending `BaseSmsError`.
|
||||
- `BaseOpts`-typed constructors that accept `message`,
|
||||
`context`, `cause`, `traceId`, with `pickOpts` stripping
|
||||
undefined keys so the `exactOptionalPropertyTypes`
|
||||
contract holds.
|
||||
- `SmsInvalidConfigError` merges Zod issues into
|
||||
`context.issues`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
SMS_CODES,
|
||||
SmsRateLimitedError,
|
||||
} from "@suaveplan/sms/error-codes";
|
||||
|
||||
try {
|
||||
throw new SmsRateLimitedError({
|
||||
context: { phoneNumber: "+15551234567" },
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof SmsRateLimitedError) {
|
||||
console.log(err.code === SMS_CODES.RATE_LIMITED);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
BaseSmsError,
|
||||
SmsInvalidConfigError,
|
||||
} from "@suaveplan/sms/error-codes";
|
||||
|
||||
function classifyAndLog(err: unknown): void {
|
||||
if (err instanceof SmsInvalidConfigError) {
|
||||
for (const issue of (err.context?.issues ?? []) as ReadonlyArray<
|
||||
Record<string, unknown>
|
||||
>) {
|
||||
console.error("zod issue", issue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (err instanceof BaseSmsError) {
|
||||
console.error(`${err.code}: ${err.message}`, err.context ?? {});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `SMS_CODES: Readonly<Record<Reason, "SMS.Reason">>` — frozen
|
||||
registry returned from `defineCodes("SMS", [...])`.
|
||||
- `SmsCode = (typeof SMS_CODES)[keyof typeof SMS_CODES]` — union
|
||||
of every fully-qualified code.
|
||||
- `abstract class BaseSmsError extends SuaveplanError<SmsCode>` —
|
||||
base class for every typed leaf error.
|
||||
- Leaf classes (each takes a `BaseOpts` argument and forwards a
|
||||
default messageKey for the i18n resolver):
|
||||
`SmsInvalidNumberError`, `SmsInvalidConfigError`,
|
||||
`SmsProviderNotConfiguredError`, `SmsProviderError`,
|
||||
`SmsSendFailedError`, `SmsTemplateNotFoundError`,
|
||||
`SmsRecipientOptedOutError`, `SmsRateLimitedError`,
|
||||
`SmsForbiddenError`, `SmsConfigMissingError`,
|
||||
`SmsScheduleCancelledError`, `SmsAbortedError`,
|
||||
`SmsReceiptNotFoundError`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The leaf-class pattern (one class per code) was chosen over a
|
||||
single generic class so callers can `instanceof Xxx` to discriminate
|
||||
without inspecting `.code`. The `pickOpts` helper guards against
|
||||
the `exactOptionalPropertyTypes` contract — copying a
|
||||
`cause: undefined` into the super options table would fail to
|
||||
typecheck against `SuaveplanErrorOptions`. Codes are reserved at
|
||||
module load via `defineCodes` (which returns a frozen object) so
|
||||
the registry cannot drift across module reloads.
|
||||
Reference in New Issue
Block a user