# `@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>`, 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` — `MemoryBackend` class implementing `Backend` from `@suaveplan/types/i18n`. Source: `packages/i18n/i18n/src/core/backend/memory/memory-backend.ts` L36-L133. Backed by `Map>` plus a `Map` modification ledger keyed by `${locale} ${namespace}`. Capabilities: `canRead: true`, `canReadMulti: true`, `canSave: true`, `canTrackModification: true`, `canWatch: false`. - ADDED: `src/backend.types.ts` — `MemorySeed = Record>` and `MemoryBackendOptions` ({ `seed?`, `name?`, `now?` }). Source: monolith L11-L26. - ADDED: `src/backend.schemas.ts` — Zod schema `MemoryBackendOptionsSchema` validating the constructor options at the public boundary. - ADDED: `src/factory.ts` — `memoryBackend(options?: MemoryBackendOptions): MemoryBackend` factory function. Source: monolith L136-L138. - ADDED: `src/observability.ts` — emits `i18n.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 existing `recordRead` / `recordWrite` helpers 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 monolith `memory-backend.test.ts` plus new spec-conformance suites. - ADDED: `src/backend.md`, `src/factory.md` — co-located docs; root `README.md` links them. - ADDED: `src/index.ts` — explicit named re-exports (`MemoryBackend`, `memoryBackend`, type `MemoryBackendOptions`, type `MemorySeed`). ### Subpath exports - `.` — root barrel (`MemoryBackend` class + `memoryBackend` factory + types) - `./backend` — `MemoryBackend` class only - `./factory` — `memoryBackend` factory function only ### Dependencies - `@suaveplan/i18n-shared workspace:^` — for `I18N_CODES`, `I18nError` - `@suaveplan/types workspace:^` — for `Backend`, `BackendCapabilities`, `TranslationRecord` from `@suaveplan/types/i18n` - `@suaveplan/error workspace:^` — for `defineCodes`-derived consumer-side helpers (if needed by tests) - `@opentelemetry/api catalog:observability` — for histogram + counter emission - `tslib catalog:build` - `zod catalog:forms` — for `MemoryBackendOptionsSchema` - **No** dep on `@suaveplan/i18n` core. **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 wire `MemoryBackend` behind a `ChainedBackend` cache layer or use `StorageBackend` against `MemoryStorage`. - **Async seed loading** — the constructor `seed` is synchronous; consumers who load seeds asynchronously call `save()` post-construction. - **Cross-instance event broadcasting** — the in-memory backend is per-instance by design; consumers who need broadcast wire the `EventBus` from `@suaveplan/events` themselves. ## 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` — full `Backend` contract (read / readMulti / save / lastModified) round-trip, hit + miss paths, capabilities matrix, custom `name` / `now` injection - `packages/i18n/i18n-backend-memory/src/backend.spec-conformance.test.ts` — every spec requirement mapped 1:1 to an `it(...)` title for traceability - `packages/i18n/i18n-backend-memory/src/factory.test.ts` — `memoryBackend()` returns `MemoryBackend` instance; passes seed through; defaults `name` to `"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 `MemoryBackend` and `memoryBackend` from the new package, preserving every existing `import { MemoryBackend } from "@suaveplan/i18n"` call site. - `MANIFEST.md` regenerated via `bun scripts/folder-reorg/generate-manifest.ts` after the package lands. - `CLAUDE.md` package listing updated with a one-line entry under the i18n cluster.