Files
suaveplan/packages/api-stack/api/api-plugin-campaigns/src/error-codes/error-codes.md
2026-05-27 23:29:53 +01:00

3.8 KiB

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.<REASON>" 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

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

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.<KEY>".
  • class CampaignsApiError extends SuaveplanError<CampaignsApiErrorCode>
    • 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.