/** * relay-web local error taxonomy. Typed, explicit errors (coding-style.md "Error Handling") * so callers never swallow failures or render partial objects. */ /** Base class for every relay-web boundary error. */ export class RelayWebError extends Error { constructor(message: string) { super(message) this.name = 'RelayWebError' } } /** Thrown by readConfig when the origin is not a valid tenant subdomain (fail-fast, T1). */ export class ConfigError extends RelayWebError { constructor(message: string) { super(message) this.name = 'ConfigError' } } /** Discriminated kinds for control-plane API failures (T2). */ export type ApiErrorKind = | 'unauthenticated' // 401 — session missing/expired; surface re-login | 'forbidden' // 403 — INV1/INV6 upstream denial (foreign host_id etc.) | 'http' // other non-2xx | 'parse' // response failed Zod validation (never return a torn object) | 'network' // fetch itself rejected /** Control-plane HTTP client error. `kind` drives the UI response (re-login, error banner, …). */ export class ApiError extends RelayWebError { readonly kind: ApiErrorKind readonly status: number | null constructor(kind: ApiErrorKind, message: string, status: number | null = null) { super(message) this.name = 'ApiError' this.kind = kind this.status = status } }