first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View 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.