Files
2026-05-27 23:29:53 +01:00

8.5 KiB

@suaveplan/i18n-backend-http — fetch-based translation backend

Package: @suaveplan/i18n-backend-http (NEW) Status: PROPOSED Type: feature (new universal-tier package; verbatim port from monolith) Date: 2026-05-05 Wave: 5 Design doc: openspec/cross-cutting-designs/2026-05-05-i18n-modular-split.md Path: packages/i18n/i18n-backend-http/

Why

The Wave 5 backend split lifts each Backend implementation from the @suaveplan/i18n monolith into its own sibling package. The HTTP backend is the canonical production source for browser-tier consumers — it fetches <loadPath> per (locale, namespace), maps HTTP 404 to BACKEND_LOCALE_NOT_AVAILABLE (chained-backend fallthrough signal) and HTTP 5xx / network errors to BACKEND_READ_FAILED (chain-stopping signal), supports retry-policy injection (structurally compatible with @suaveplan/retry's RetryPolicy), and accepts custom payload parsers (JSON default; YAML / MessagePack / schema-validating parsers are caller-pluggable).

The Backend interface itself moves to @suaveplan/types/i18n in Wave 1 (zero-runtime types extension). @suaveplan/i18n-backend-http implements Backend from that location and does NOT depend on @suaveplan/i18n core. Runtime atoms (I18nError, I18N_CODES) come from @suaveplan/i18n-shared.

What changes

New package: @suaveplan/i18n-backend-http

  • ADDED: packages/i18n/i18n-backend-http/ package scaffold (package.json, tsconfig.json, vite.config.ts, README.md).
  • ADDED: src/backend.ts — HttpBackend class implementing Backend from @suaveplan/types/i18n. Source: packages/i18n/i18n/src/core/backend/http/http-backend.ts L80-L240. Capabilities: canRead: true, canReadMulti: false, canSave: false, canTrackModification: false, canWatch: false. Read-only by design — write operations (saveMissing on the catalog side) are not surfaced.
  • ADDED: src/backend.types.ts — HttpBackendOptions, ParsePayloadFn, HttpHeadersInit, CustomHeadersFn, HttpBackendRetry. Source: monolith L8-L70.
  • ADDED: src/backend.schemas.ts — HttpBackendOptionsSchema Zod schema. Source: monolith http-backend.schemas.ts (40 lines).
  • ADDED: src/factory.ts — httpBackend(options: HttpBackendOptions): HttpBackend factory. Source: monolith L266-L268.
  • ADDED: src/observability.ts — emits i18n.backend.http.read_duration_ms (histogram), i18n.backend.http.read_total{outcome} (counter), i18n.backend.http.read_errors_total{code,status?} (counter). Histogram + counter pattern follows existing recordRead helper in monolith.
  • ADDED: src/url-builder.ts — buildUrl(loadPath, locale, namespace) extracted as a standalone helper for tree-shaking + isolated testing. Source: monolith L220-L224 (#buildUrl).
  • ADDED: src/payload-parser.ts — defaultParsePayload standalone helper (JSON parser with non-object rejection). Source: monolith L254-L263.
  • ADDED: src/backend.test.ts, src/backend.spec-conformance.test.ts, src/factory.test.ts, src/url-builder.test.ts, src/payload-parser.test.ts, src/index.test.ts — Vitest suites verbatim-ported from monolith plus new spec-conformance suites.
  • ADDED: src/backend.md, src/factory.md, src/url-builder.md, src/payload-parser.md — co-located docs.
  • ADDED: src/index.ts — explicit named re-exports.

Subpath exports

  • . — root barrel
  • ./backend — HttpBackend class only
  • ./factory — httpBackend factory function only
  • ./parser — defaultParsePayload helper (consumers building custom parsers)
  • ./url-builder — buildUrl helper (consumers customising URL templating)

Dependencies

  • @suaveplan/i18n-shared workspace:^ — for I18N_CODES, I18nError
  • @suaveplan/types workspace:^ — for Backend, BackendCapabilities, TranslationRecord from @suaveplan/types/i18n
  • @suaveplan/error workspace:^
  • @opentelemetry/api catalog:observability
  • tslib catalog:build
  • zod catalog:forms — for HttpBackendOptionsSchema
  • No dep on @suaveplan/i18n core. No dep on any other backend package. No dep on @suaveplan/retry (the HttpBackendRetry interface is structural so consumers can pass @suaveplan/retry's RetryPolicy directly without us taking a hard dep).

Tier

Universal. Uses globalThis.fetch which is available in browsers, Bun, and Node.js >= 18. The defaultFetch() helper throws BACKEND_READ_FAILED with a clear message when globalThis.fetch is unavailable, prompting consumers on older runtimes to inject fetchFn explicitly.

Out of scope

  • Removing the HTTP backend from packages/i18n/i18n/src/core/backend/http/ — happens in Wave 6.
  • Server-Sent Events / WebSocket-driven HTTP backend — out of scope; consumers wire EventBus from @suaveplan/events against a separate transport.
  • HTTP-level caching (Cache-Control / ETag / Last-Modified) — out of scope; consumers wire ChainedBackend with a MemoryBackend cache layer.
  • save / lastModified implementations — explicit non-goals; the HTTP backend is read-only by design. Consumers who need write-back wire MultiFileFsBackend or StorageBackend behind the catalog.

Source mapping

Requirement Source file Source lines Notes
HttpBackend class + capabilities matrix packages/i18n/i18n/src/core/backend/http/http-backend.ts L80-L116 Verbatim port; rewire imports to @suaveplan/i18n-shared and @suaveplan/types/i18n
HttpBackend.read (404 → BACKEND_LOCALE_NOT_AVAILABLE, 5xx / network → BACKEND_READ_FAILED) same file L118-L205 Verbatim port
#fetchOnce network-error classifier same file L207-L218 Verbatim port
#buildUrl (lifted to src/url-builder.ts) same file L220-L224 Extracted to standalone helper
#buildInit (request-init builder) same file L226-L239 Inline private method on class
defaultFetch() — globalThis.fetch fallback same file L242-L252 Verbatim port
defaultParsePayload (lifted to src/payload-parser.ts) same file L254-L263 Extracted to standalone helper
HttpBackendOptions, ParsePayloadFn, HttpHeadersInit, CustomHeadersFn, HttpBackendRetry types same file L8-L70 Verbatim port to backend.types.ts
HttpBackendOptionsSchema Zod schema packages/i18n/i18n/src/core/backend/http/http-backend.schemas.ts full file (40 lines) Verbatim port
httpBackend factory packages/i18n/i18n/src/core/backend/http/http-backend.ts L266-L268 Lifted to factory.ts
recordRead instrumentation pattern packages/i18n/i18n/src/observability/backend-metrics.ts full file Reimplemented inline; metric names re-prefixed i18n.backend.http.

Requirements with no source line

Requirement Justification
Subpath exports ./backend, ./factory, ./parser, ./url-builder Tree-shaking — consumers customising URL or payload parsing should pull only the helper they extend
AbortSignal parameter on read Cancellation parity across backends; existing monolith does NOT thread signal into fetch, so this is a small surface improvement landing in the split

Verification approach

Each requirement carries a **Verified by:** line in the spec delta. Concrete test files:

  • packages/i18n/i18n-backend-http/src/backend.test.ts — full Backend contract; 200 OK round-trip with stubbed fetchFn; 404 → BACKEND_LOCALE_NOT_AVAILABLE; 500 → BACKEND_READ_FAILED; network failure (TypeError from fetchFn) → BACKEND_READ_FAILED; custom parsePayload invoked; customHeaders callback invoked; crossDomain: true sets credentials + mode; injected retry.run wraps every fetch
  • packages/i18n/i18n-backend-http/src/backend.spec-conformance.test.ts — every spec requirement mapped 1:1 to an it(...) title
  • packages/i18n/i18n-backend-http/src/factory.test.ts — httpBackend() returns HttpBackend instance; defaults name to "http"
  • packages/i18n/i18n-backend-http/src/url-builder.test.ts — buildUrl substitutes {locale} and {namespace}; URL-encodes both
  • packages/i18n/i18n-backend-http/src/payload-parser.test.ts — defaultParsePayload accepts JSON object; rejects array, null, primitive
  • packages/i18n/i18n-backend-http/src/index.test.ts — every documented public export resolves

Impact

  • New universal-tier package — joins the i18n family under packages/i18n/.
  • No downstream consumer changes needed in Wave 5 — the slim core (Wave 6) re-exports HttpBackend and httpBackend.
  • MANIFEST.md regenerated; CLAUDE.md package listing updated with a one-line entry.