Files
skills/docs/communication/messaging/sms/src/error-codes/error-codes.md
2026-05-29 08:20:56 +01:00

3.6 KiB

error-codes

(no submodules)

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

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

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.