first commit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# `error-codes` — Typed error registry for currency providers
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the stable `CURRENCY_PROVIDER_CODES` registry and the
|
||||
`CurrencyProviderError` class every provider in
|
||||
`@suaveplan/currency-providers` raises. The registry guarantees that
|
||||
downstream code — most importantly `CurrencyConverter`'s fallback chain —
|
||||
can programmatically dispatch on the namespaced code string instead of
|
||||
parsing free-form error messages. This is the suaveplan-quality replacement
|
||||
for the upstream package's plain `throw new Error(...)` pattern, which
|
||||
gave fallback logic no way to tell "Fixer returned 401 — try the next
|
||||
provider" apart from "Fixer URL was malformed — give up".
|
||||
|
||||
`CurrencyProviderError` extends `SuaveplanError<CurrencyProviderErrorCode>`,
|
||||
preserving the full `SuaveplanError` contract — `cause` chaining,
|
||||
`toWire()` redaction, OTel span enrichment, and i18n-key dispatch — while
|
||||
adding three optional discriminants relevant to provider failures:
|
||||
`providerName` (which provider hit the error), `status` (the HTTP status
|
||||
when the failure came from an HTTP response), and `upstreamMessage` (the
|
||||
free-form error string the upstream API supplied).
|
||||
|
||||
## Features
|
||||
|
||||
- Eight stable error codes covering every provider failure mode plus the
|
||||
slim-core registry dispatch: HTTP_ERROR, PARSE_ERROR, UPSTREAM_ERROR,
|
||||
UNSUPPORTED_BASE, MISSING_CONFIG, UNKNOWN_CURRENCY,
|
||||
PROVIDER_NOT_REGISTERED, PROVIDER_ALREADY_REGISTERED
|
||||
- `CurrencyProviderError extends SuaveplanError<CurrencyProviderErrorCode>` —
|
||||
full `SuaveplanError` contract preserved
|
||||
- Optional `providerName` / `status` / `upstreamMessage` discriminants
|
||||
- Registered via `defineCodes` so the code values are typed as the
|
||||
namespaced strings (`"CURRENCY_PROVIDER.HTTP_ERROR"` etc.)
|
||||
- The first six codes are reachable from sibling
|
||||
`@suaveplan/currency-provider-<name>` packages (where the concrete HTTP
|
||||
call sites live); the two `PROVIDER_*` codes are reachable from this
|
||||
package's `CurrencyProviderRegistry`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
CURRENCY_PROVIDER_CODES,
|
||||
CurrencyProviderError,
|
||||
} from "@suaveplan/currency-providers";
|
||||
|
||||
throw new CurrencyProviderError(
|
||||
CURRENCY_PROVIDER_CODES.HTTP_ERROR,
|
||||
"ECB API error: 503 Service Unavailable",
|
||||
{ providerName: "ecb", status: 503 }
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
Use the codes to drive fallback-chain decisions in a custom converter:
|
||||
|
||||
```ts
|
||||
import {
|
||||
CURRENCY_PROVIDER_CODES,
|
||||
CurrencyProviderError,
|
||||
} from "@suaveplan/currency-providers";
|
||||
|
||||
async function fetchWithFallback(providers: readonly CurrencyProvider[]) {
|
||||
let lastError: unknown;
|
||||
for (const provider of providers) {
|
||||
try {
|
||||
return await provider.fetchRates();
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (!(error instanceof CurrencyProviderError)) throw error;
|
||||
// 4xx auth / config errors are not worth retrying with the next
|
||||
// provider — re-throw to bubble up
|
||||
if (error.code === CURRENCY_PROVIDER_CODES.MISSING_CONFIG) throw error;
|
||||
if (error.status === 401 || error.status === 403) throw error;
|
||||
// anything else: try next provider
|
||||
}
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `const CURRENCY_PROVIDER_CODES` — frozen registry mapping each code name
|
||||
to its namespaced string (`"CURRENCY_PROVIDER.HTTP_ERROR"`, etc.).
|
||||
- `type CurrencyProviderErrorCode` — union of the eight namespaced strings.
|
||||
- `interface CurrencyProviderErrorOptions extends SuaveplanErrorOptions` —
|
||||
adds optional `providerName: string`, `status: number`,
|
||||
`upstreamMessage: string`.
|
||||
- `class CurrencyProviderError extends SuaveplanError<CurrencyProviderErrorCode>`
|
||||
— constructor `(code, message, options?)`. Read-only `providerName`,
|
||||
`status`, `upstreamMessage` properties.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The discriminants are surfaced as top-level read-only properties (rather
|
||||
than fields on `context`) to make destructured `try/catch` handling
|
||||
ergonomic — `if (error instanceof CurrencyProviderError && error.status === 429)`
|
||||
reads naturally.
|
||||
- Codes that don't have a natural HTTP shape (e.g. `MISSING_CONFIG`,
|
||||
`UNKNOWN_CURRENCY`) simply omit `status` and `upstreamMessage`. The
|
||||
optional fields are populated only when meaningful.
|
||||
- `defineCodes` enforces namespace prefix at runtime — `CURRENCY_PROVIDER.X`
|
||||
cannot collide with `CURRENCY.X` from `@suaveplan/currency` or any other
|
||||
package's registry.
|
||||
Reference in New Issue
Block a user