# `errors` Stable error class hierarchy and code registry for `@suaveplan/third-party-elevenlabs`. Every non-2xx HTTP response and every transport-level failure maps to one of 17 typed `ElevenLabsApiError` subclasses under namespace `THIRD_PARTY_ELEVENLABS.*`. _(no submodules)_ ## Purpose The ElevenLabs API surface is large and surfaces failures across HTTP status codes, malformed JSON bodies, transport timeouts, and WebSocket protocol errors. Without a stable error hierarchy, every consumer of this client would have to learn the upstream's quirks. This module defines the canonical 17-code surface so callers can branch on `error.code` or `error instanceof RateLimitExceededError` without touching the wire. The mapping is total and disjoint over the `[400, 600)` HTTP range, every documented network failure mode, every JSON / Zod parse failure, and the two WebSocket failure modes (handshake / protocol). Every error preserves the original cause via the cause-chain on `SuaveplanError`. ## Features - 17 error codes registered via `defineCodes('THIRD_PARTY_ELEVENLABS', ...)`. - 17 typed subclasses, one per code, with sensible default messages. - Convenience options surface common transport context: - `RateLimitExceededError({ retryAfterMs })` — retries planning. - `RequestValidationFailedError({ body })` — preserved 422 body. - `WebSocketConnectionFailedError({ code })` — close code. - `NetworkTimeoutError({ timeoutMs })` — observed limit. - Every HTTP error accepts `{ endpoint, statusCode, requestId }`. - Caller-supplied `context` is shallow-merged on top of derived context so adapter packages can layer their own attribution (e.g. `tenantId`). ## Usage: Basic Example ```ts import { ElevenLabsApiError, RateLimitExceededError, } from "@suaveplan/third-party-elevenlabs/errors/errors"; try { await client.fetchJson(req, schema); } catch (err) { if (err instanceof RateLimitExceededError) { console.log(`retry after ${err.context?.retryAfterMs ?? "unknown"} ms`); } } ``` ## Usage: Advanced Example Branch on every documented failure mode and re-throw with adapter context: ```ts import { ElevenLabsApiError, ELEVENLABS_CODES, } from "@suaveplan/third-party-elevenlabs/errors/errors"; function handleFailure(err: unknown): never { if (!(err instanceof ElevenLabsApiError)) throw err; switch (err.code) { case ELEVENLABS_CODES["AUTH.UNAUTHORIZED"]: case ELEVENLABS_CODES["AUTH.FORBIDDEN"]: throw new ProviderNotConfiguredError({ cause: err }); case ELEVENLABS_CODES["RATE_LIMIT.EXCEEDED"]: case ELEVENLABS_CODES["RATE_LIMIT.QUOTA_EXHAUSTED"]: throw new ProviderRateLimitedError({ retryAfterMs: err.context?.retryAfterMs as number | undefined, cause: err, }); default: throw new TtsSynthesisFailedError({ cause: err }); } } ``` ## API Reference - `ELEVENLABS_CODES` — frozen record `{ "": "THIRD_PARTY_ELEVENLABS." }`. - `ElevenLabsErrorCode` — TypeScript union of every code value. - `ElevenLabsApiError` — abstract base extending `SuaveplanError`. Auth + request errors: - `UnauthorizedError({ endpoint?, statusCode?, requestId?, ...base })` — `AUTH.UNAUTHORIZED` (HTTP 401). - `ForbiddenError(...)` — `AUTH.FORBIDDEN` (HTTP 403). - `RequestInvalidError(...)` — `REQUEST.INVALID` (other 4xx). - `RequestValidationFailedError({ body?, ...request })` — `REQUEST.VALIDATION_FAILED` (HTTP 422); body preserved verbatim. - `RequestNotFoundError(...)` — `REQUEST.NOT_FOUND` (HTTP 404). Rate limit: - `RateLimitExceededError({ retryAfterMs?, ...request })` — `RATE_LIMIT.EXCEEDED` (HTTP 429). - `RateLimitQuotaExhaustedError(...)` — `RATE_LIMIT.QUOTA_EXHAUSTED`. Server: - `ServerTransientError(...)` — `SERVER.TRANSIENT` (retryable 5xx). - `ServerUnavailableError(...)` — `SERVER.UNAVAILABLE` (non-retryable / circuit-open). Network: - `NetworkTimeoutError({ endpoint?, timeoutMs?, ...base })` — `NETWORK.TIMEOUT`. - `NetworkAbortedError({ endpoint?, ...base })` — `NETWORK.ABORTED`. - `NetworkUnreachableError({ endpoint?, ...base })` — `NETWORK.UNREACHABLE`. Response / stream: - `ResponseMalformedError({ endpoint?, bodyPreview?, ...base })` — `RESPONSE.MALFORMED`. - `ResponseUnsupportedFormatError({ contentType?, ...base })` — `RESPONSE.UNSUPPORTED_FORMAT`. - `StreamInterruptedError(...)` — `STREAM.INTERRUPTED`. WebSocket: - `WebSocketConnectionFailedError({ code?, ...base })` — `WEBSOCKET.CONNECTION_FAILED`. - `WebSocketProtocolError({ code?, ...base })` — `WEBSOCKET.PROTOCOL`. Every constructor accepts `{ message?, cause?, context?, traceId? }`. ## Implementation Notes - The 17 codes partition `[400, 600)` HTTP statuses plus three networking modes plus three response / stream modes plus two WebSocket modes. The client never throws a generic `ElevenLabsApiError` — every thrown error is one of the typed subclasses. - `RequestValidationFailedError.body` should be the **parsed** JSON body (truncated to a reasonable bound by the client — currently 4 KB; see `client.ts`). Never put raw network buffers here. - `bodyPreview` on `ResponseMalformedError` is a short string (≤ 256 bytes) suitable for logs. Adapters should not re-serialise the full body into context. - All subclasses default to a stable English message. Localisation flows through `MessageResolver` on `@suaveplan/error`.