120 lines
4.5 KiB
Markdown
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.
|