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

3.6 KiB

Error codes

Purpose

This module defines the stable, namespaced error-code registry for @suaveplan/api-plugin-banking. Every failure path in the plugin throws a BankingError carrying one of fourteen BANKING.* codes, so callers can branch on error.code without parsing free-form messages and the HTTP layer can map a code → status with a one-line lookup.

Features

  • 14 typed string-literal codes registered via defineCodes("BANKING", ...)
  • BankingError class extending SuaveplanError, generic over the code literal type so instanceof narrows the code field
  • Stable JSDoc with HTTP-status mapping for each code
  • Re-exported under the package's ./error-codes/error-codes subpath

Usage: Basic

import { BankingError, BANKING_CODES } from "@suaveplan/api-plugin-banking";

throw new BankingError(
    BANKING_CODES.ACCOUNT_NOT_FOUND,
    `Account ${id} not found`
);

Usage: Advanced

Codes carry no behaviour — they are documentation. The expected pattern is to catch at the route boundary, narrow on instanceof BankingError, and map error.code to an HTTP status:

import {
    BankingError,
    BANKING_CODES,
    type BankingErrorCode,
} from "@suaveplan/api-plugin-banking";

const STATUS: Record<BankingErrorCode, number> = {
    INVALID_OPTIONS: 500,
    SERVICE_NOT_AVAILABLE: 500,
    BANK_NOT_FOUND: 404,
    BANK_NAME_TAKEN: 400,
    BANK_HAS_ACTIVE_ACCOUNTS: 400,
    ACCOUNT_NOT_FOUND: 404,
    ACCOUNT_LIMIT_REACHED: 400,
    INSUFFICIENT_FUNDS: 400,
    INVALID_AMOUNT: 400,
    INVALID_FIELD_NAME: 500,
    INVALID_ACCOUNT_NUMBER: 400,
    INVALID_SORT_CODE: 400,
    TENANT_CLEANUP_FAILED: 500,
    DATABASE_ERROR: 500,
};

try {
    await service.transferFunds(from, to, amount);
} catch (err) {
    if (err instanceof BankingError) {
        const status = STATUS[err.code];
        return { status, body: { error: err.message, code: err.code } };
    }
    throw err;
}

API Reference

BANKING_CODES

const BANKING_CODES: { readonly INVALID_OPTIONS: "BANKING.INVALID_OPTIONS" } & {...}

Frozen, namespaced map of every code in the registry. Indexing it yields the canonical "BANKING.XYZ" string passed to BankingError.

BankingErrorCode

type BankingErrorCode = (typeof BANKING_CODES)[keyof typeof BANKING_CODES]

Discriminated union of all literal code values.

class BankingError extends SuaveplanError<BankingErrorCode>

constructor(code: BankingErrorCode, message: string, options?: SuaveplanErrorOptions)

error.code: BankingErrorCode — the literal code string. error.bankingCode: BankingErrorCode — alias for ergonomic exhaustive switches without re-typing the union annotation. error.cause, error.context, error.toJSON() — inherited from SuaveplanError.

Implementation Notes

The registry is intentionally exhaustive: every error code that appears here is thrown by at least one production code path inside the package. The pre-archive checklist (AGENTS.md §13) gates this — codes registered but never thrown are removed before the change ships, so the list reflects the real fault surface a caller can branch on.

BankingError keeps the bankingCode alias so consumers writing exhaustive switches do not need to re-type the union annotation: switch (err.bankingCode) { case "BANKING.ACCOUNT_NOT_FOUND": ... } type-checks against BankingErrorCode directly.

When throwing from a SurrealDB call site, always set { cause: originalError, context: { ... } } — the wire-format JSON adapter (SuaveplanError.toJSON) preserves both. DATABASE_ERROR is the catch-all wrapper used by repositories when SurrealDB rejects a query for a reason outside the typed code list.