Files
skills/docs/localization/i18n/i18n-backend-http/src/backend/backend.md
2026-05-29 08:20:56 +01:00

120 lines
4.5 KiB
Markdown

# `HttpBackend`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`HttpBackend` is the canonical browser-tier translation source for the
`@suaveplan/i18n` family. It fetches `<loadPath>` per `(locale,
namespace)` via an injectable `fetchFn` (defaulting to
`globalThis.fetch`) and parses the response body via an injectable
`parsePayload` (defaulting to a JSON-object parser that rejects arrays,
null, and primitive payloads). HTTP 404 maps to
`BACKEND_LOCALE_NOT_AVAILABLE` — the chained-backend fallthrough signal
— while HTTP 5xx, network failures, and parse failures map to
`BACKEND_READ_FAILED`, the chain-stopping signal that prevents silent
degradation when the transport itself is broken.
The backend is read-only by design (`canSave: false`,
`canTrackModification: false`, `canWatch: false`). Consumers needing
write-back wire `MultiFileFsBackend` or `StorageBackend` behind the
catalog instead. Universal — runs identically in browsers, Bun,
and Node.js >= 18; older Node runtimes get a clear
`BACKEND_READ_FAILED` from `defaultFetch()` instructing them to inject
`fetchFn`.
## Features
- 5-flag capability matrix `{ canRead: true, canReadMulti: false,
canSave: false, canTrackModification: false, canWatch: false }`.
- URL templating via `{locale}` / `{namespace}` placeholders with
`encodeURIComponent` per segment.
- Pluggable `parsePayload` accepts non-JSON bodies (YAML, MessagePack,
schema-validated JSON).
- Pluggable `customHeaders` builder per request.
- `crossDomain: true` shorthand for `credentials: "include"` +
`mode: "cors"` (preserves explicit overrides in `requestOptions`).
- Optional `retry?: HttpBackendRetry` (structurally compatible with
`@suaveplan/retry`'s `RetryPolicy`) wraps every fetch.
- Optional `signal?: AbortSignal` — pre-aborted signals throw
`BACKEND_TIMEOUT` without issuing a network request; mid-flight
AbortError surfaces as `BACKEND_TIMEOUT`; the signal is threaded into
the underlying `RequestInit` so `fetch` can cancel mid-flight.
- Zod boundary validation throws `INVALID_OPTIONS` at construction.
- Per-call OTel instrumentation under `i18n.backend.http.*`.
## Usage: Basic Example
```ts
import { HttpBackend } from "@suaveplan/i18n-backend-http";
const backend = new HttpBackend({
loadPath: "/i18n/{locale}/{namespace}.json",
});
const record = await backend.read("en", "auth");
```
## Usage: Advanced Example
```ts
import { HttpBackend } from "@suaveplan/i18n-backend-http";
const backend = new HttpBackend({
loadPath: "/i18n/{locale}/{namespace}.yaml",
crossDomain: true,
customHeaders: (locale) => ({ "x-locale": locale }),
parsePayload: async (res) => parseYaml(await res.text()),
retry: {
run: async (op) => {
for (let i = 0; i < 3; i++) {
try {
return await op();
} catch (err) {
if (i === 2) throw err;
}
}
throw new Error("unreachable");
},
},
});
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 2_000);
const record = await backend.read("en", "auth", ctrl.signal);
```
## API Reference
### `class HttpBackend implements Backend`
- `new HttpBackend(options: HttpBackendOptions)` — Zod-validated;
throws `I18nError(I18N.INVALID_OPTIONS)` on malformed input.
- `read(locale, namespace, signal?): Promise<TranslationRecord>` —
fetches and parses the URL. Throws `BACKEND_LOCALE_NOT_AVAILABLE` on
404; `BACKEND_READ_FAILED` on 5xx / network / parse failures;
`BACKEND_TIMEOUT` on signal abort.
### Helpers
- `buildUrl(loadPath, locale, namespace): string` — substitutes
`{locale}` and `{namespace}` with `encodeURIComponent`-applied
values.
- `defaultParsePayload: ParsePayloadFn` — JSON object parser. Rejects
arrays, null, and primitives as `BACKEND_READ_FAILED`.
- `HttpBackendOptionsSchema: z.ZodObject` — Zod boundary schema.
## Implementation Notes
The class delegates to a private `#fetchOnce` helper that classifies
network failures: `AbortError` / `ABORT_ERR` codes surface as
`BACKEND_TIMEOUT` (so consumers can distinguish cancellation from
transport faults), and `I18nError(I18N.BACKEND_TIMEOUT)` thrown from
inside `fetchFn` (e.g. an injected fake) propagates unchanged.
Everything else from the network surface — `TypeError`, refused
connections, DNS failures, CORS preflights — maps to
`BACKEND_READ_FAILED`. Capability flags stay `false` for write paths
because the HTTP transport has no canonical `PUT` semantics across
CDNs; consumers needing write-back wire a different backend.