# `error-codes` _(no submodules)_ ## Purpose The error-codes module owns the stable `CAMPAIGNS_API.*` namespace used by every failure path inside `@suaveplan/api-plugin-campaigns`. Consumers branch on the string `code` value (rather than `instanceof` checks against private subclasses) so the namespace doubles as both an internal vocabulary and a public-API contract surface. This is the foundation module for the chunked migration. Chunk A registers only the codes that the foundation surface can actually throw; Chunks B (repositories) and C (routes) extend the same `defineCodes` call rather than introducing parallel namespaces. The intentional minimalism keeps coverage at 100% across the package boundary at every chunk milestone. ## Features - `CAMPAIGNS_API_CODES` — frozen `defineCodes` registry mapping reason names to `"CAMPAIGNS_API."` string values. - `CampaignsApiError` — concrete subclass of `SuaveplanError` with an explicit public constructor delegating to `super`, plus a typed `campaignsApiCode` field that narrows to `CampaignsApiErrorCode`. - `CampaignsApiErrorCode` — discriminated string-literal union of every registered code. ## Usage: Basic ```ts import { CAMPAIGNS_API_CODES, CampaignsApiError, } from "@suaveplan/api-plugin-campaigns"; try { createCampaignsPlugin({ routePrefix: 42 as never }); } catch (err) { if ( err instanceof CampaignsApiError && err.code === CAMPAIGNS_API_CODES.INVALID_OPTIONS ) { // The ZodError is attached as `cause`. console.error(err.cause); } } ``` ## Usage: Advanced ```ts import { CAMPAIGNS_API_CODES, CampaignsApiError, } from "@suaveplan/api-plugin-campaigns"; function rethrowAsCampaigns(cause: unknown): never { throw new CampaignsApiError( CAMPAIGNS_API_CODES.INTERNAL_ERROR, "unhandled internal failure inside campaigns plugin", { cause: cause instanceof Error ? cause : new Error(String(cause)), context: { statusCode: 500 }, } ); } ``` ## API Reference - `CAMPAIGNS_API_CODES` — frozen record with one key per registered reason. Chunk-A keys: `INVALID_OPTIONS`, `INTERNAL_ERROR`. Chunk-B additions: `CAMPAIGN_NOT_FOUND`, `SUPPRESSION_NOT_FOUND`, `INVALID_INPUT`, `DB_UNAVAILABLE`, `DUPLICATE_SUPPRESSION`, `TENANT_REQUIRED`. Chunk-C additions: `INVALID_STATE_TRANSITION`, `INVALID_UNSUBSCRIBE_TOKEN`, `ROUTE_PREFIX_COLLISION`, `WEBHOOK_REJECTED`, `SERVICE_NOT_AVAILABLE`. Every value is the literal string `"CAMPAIGNS_API."`. - `class CampaignsApiError extends SuaveplanError` - `new CampaignsApiError(code, message, options?)` - `error.code` — the namespaced code string. - `error.campaignsApiCode` — same as `code`, typed narrowly. - `error.cause` / `error.context` — inherited from `SuaveplanError`. - `type CampaignsApiErrorCode` — string-literal union over every value of `CAMPAIGNS_API_CODES` (currently eight codes spanning Chunks A and B). ## Implementation Notes The constructor is `public` rather than `protected` because consumers (test suites and integration scaffolds) construct the error directly without subclassing. `defineCodes` returns a frozen `Object.create(null)`, so `Object.isFrozen(CAMPAIGNS_API_CODES) === true` is an invariant the test suite enforces — accidentally swapping it for a plain object literal would silently weaken the API contract and tests would catch it. The Chunk-A registration deliberately excludes codes that Chunks B and C will need. Adding unthrown codes here now would either depress branch coverage or force placeholder tests that exercise dead code paths; incremental registration aligned with each chunk's actual throw sites keeps the verification gate honest.