8.2 KiB
@suaveplan/i18n-backend-memory — in-memory translation backend
Package: @suaveplan/i18n-backend-memory (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-memory/
Why
The Wave 5 backend split lifts each Backend implementation from the @suaveplan/i18n monolith into its own sibling package so consumers can install only the backends they need (matching the crypto-* / drive-* / network-plugin-* granularity established elsewhere in the monorepo). The in-memory backend is the simplest and most universal of the five — backed by Map<locale, Map<namespace, TranslationRecord>>, it ships the full Backend contract (read / readMulti / save / lastModified), is the canonical seed source for tests, and is the standard cache layer for ChainedBackend. Because it has zero external runtime deps, it is the cleanest candidate to land first in the Wave 5 batch.
The Backend interface itself moves to @suaveplan/types/i18n in Wave 1 (zero-runtime types extension). Every backend in this wave implements Backend from that location — @suaveplan/i18n-backend-memory does NOT depend on @suaveplan/i18n core. The runtime atoms (I18nError, I18N_CODES) come from @suaveplan/i18n-shared (Wave 1b).
What changes
New package: @suaveplan/i18n-backend-memory
- ADDED:
packages/i18n/i18n-backend-memory/package scaffold (package.json, tsconfig.json, vite.config.ts, README.md). - ADDED:
src/backend.ts—MemoryBackendclass implementingBackendfrom@suaveplan/types/i18n. Source:packages/i18n/i18n/src/core/backend/memory/memory-backend.tsL36-L133. Backed byMap<locale, Map<namespace, TranslationRecord>>plus aMap<string, number>modification ledger keyed by${locale} ${namespace}. Capabilities:canRead: true,canReadMulti: true,canSave: true,canTrackModification: true,canWatch: false. - ADDED:
src/backend.types.ts—MemorySeed = Record<string, Record<string, TranslationRecord>>andMemoryBackendOptions({seed?,name?,now?}). Source: monolith L11-L26. - ADDED:
src/backend.schemas.ts— Zod schemaMemoryBackendOptionsSchemavalidating the constructor options at the public boundary. - ADDED:
src/factory.ts—memoryBackend(options?: MemoryBackendOptions): MemoryBackendfactory function. Source: monolith L136-L138. - ADDED:
src/observability.ts— emitsi18n.backend.memory.read_duration_ms(histogram),i18n.backend.memory.read_total{outcome}(counter),i18n.backend.memory.read_errors_total{code}(counter),i18n.backend.memory.write_duration_ms(histogram),i18n.backend.memory.write_total{outcome}(counter). The histogram pattern follows the existingrecordRead/recordWritehelpers in the monolith (packages/i18n/i18n/src/observability/backend-metrics.ts) but is inlined here so the leaf does not depend on the monolith. - ADDED:
src/backend.test.ts,src/backend.spec-conformance.test.ts,src/factory.test.ts,src/index.test.ts— Vitest suites verbatim-ported from monolithmemory-backend.test.tsplus new spec-conformance suites. - ADDED:
src/backend.md,src/factory.md— co-located docs; rootREADME.mdlinks them. - ADDED:
src/index.ts— explicit named re-exports (MemoryBackend,memoryBackend, typeMemoryBackendOptions, typeMemorySeed).
Subpath exports
.— root barrel (MemoryBackendclass +memoryBackendfactory + types)./backend—MemoryBackendclass only./factory—memoryBackendfactory function only
Dependencies
@suaveplan/i18n-shared workspace:^— forI18N_CODES,I18nError@suaveplan/types workspace:^— forBackend,BackendCapabilities,TranslationRecordfrom@suaveplan/types/i18n@suaveplan/error workspace:^— fordefineCodes-derived consumer-side helpers (if needed by tests)@opentelemetry/api catalog:observability— for histogram + counter emissiontslib catalog:buildzod catalog:forms— forMemoryBackendOptionsSchema- No dep on
@suaveplan/i18ncore. No dep on any other backend package.
Tier
Universal. Runs in browser AND server with no platform-specific imports. The Map-backed store and Date.now fallback work identically across runtimes.
Out of scope
- Removing the in-memory backend from
packages/i18n/i18n/src/core/backend/memory/— happens in Wave 6 (i18n-core-slim). For Wave 5, both copies coexist; the monolith remains green. - Persistent variants of the in-memory store —
WeakMap/ TTL-eviction / LRU bounds are explicit non-goals; consumers who need eviction wireMemoryBackendbehind aChainedBackendcache layer or useStorageBackendagainstMemoryStorage. - Async seed loading — the constructor
seedis synchronous; consumers who load seeds asynchronously callsave()post-construction. - Cross-instance event broadcasting — the in-memory backend is per-instance by design; consumers who need broadcast wire the
EventBusfrom@suaveplan/eventsthemselves.
Source mapping
| Requirement | Source file | Source lines | Notes |
|---|---|---|---|
MemoryBackend class + capabilities matrix |
packages/i18n/i18n/src/core/backend/memory/memory-backend.ts |
L36-L60 | Verbatim port; rewire imports to @suaveplan/i18n-shared and @suaveplan/types/i18n |
MemoryBackend.read (hit + miss paths, BACKEND_LOCALE_NOT_AVAILABLE on miss) |
same file | L62-L84 | Verbatim port |
MemoryBackend.readMulti |
same file | L86-L100 | Verbatim port |
MemoryBackend.save |
same file | L102-L109 | Verbatim port |
MemoryBackend.lastModified |
same file | L111-L116 | Verbatim port |
MemoryBackend.clear test-only helper |
same file | L119-L122 | Verbatim port |
MemorySeed, MemoryBackendOptions types |
same file | L11-L26 | Verbatim port to backend.types.ts |
memoryBackend factory |
same file | L136-L138 | Lifted into factory.ts |
recordRead / recordWrite instrumentation pattern |
packages/i18n/i18n/src/observability/backend-metrics.ts |
full file | Reimplemented inline in src/observability.ts; same metric names with i18n.backend.memory. prefix |
Requirements with no source line
| Requirement | Justification |
|---|---|
Subpath exports ./backend and ./factory |
Tree-shaking — consumers who construct via the factory should not pull the full Backend class transitively; consumers who subclass should not pull factory wrappers |
MemoryBackendOptionsSchema Zod schema |
Boundary validation — every backend in Wave 5 validates options at the public boundary, matching the pattern in HttpBackend (already does this) |
AbortSignal parameter on read / readMulti / save |
Cancellation parity across backends — every Wave 5 backend accepts an optional AbortSignal so chained / multi-backend orchestration can propagate cancellation |
Verification approach
Each requirement carries a **Verified by:** line in the spec delta. Concrete test files:
packages/i18n/i18n-backend-memory/src/backend.test.ts— fullBackendcontract (read / readMulti / save / lastModified) round-trip, hit + miss paths, capabilities matrix, customname/nowinjectionpackages/i18n/i18n-backend-memory/src/backend.spec-conformance.test.ts— every spec requirement mapped 1:1 to anit(...)title for traceabilitypackages/i18n/i18n-backend-memory/src/factory.test.ts—memoryBackend()returnsMemoryBackendinstance; passes seed through; defaultsnameto"memory"packages/i18n/i18n-backend-memory/src/index.test.ts— every documented public export resolves through every documented subpath
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
MemoryBackendandmemoryBackendfrom the new package, preserving every existingimport { MemoryBackend } from "@suaveplan/i18n"call site. MANIFEST.mdregenerated viabun scripts/folder-reorg/generate-manifest.tsafter the package lands.CLAUDE.mdpackage listing updated with a one-line entry under the i18n cluster.