# `MemoryBackend` _(no submodules)_ ## Purpose `MemoryBackend` is the universal in-memory translation backend for the `@suaveplan/i18n` family. It implements the full `Backend` contract from `@suaveplan/types` (read, readMulti, save, lastModified) backed by `Map>` plus a modification ledger keyed by `${locale} ${namespace}`. It is the canonical seed source for tests, the default cache layer for `ChainedBackend`, and the reference implementation every other backend's conformance suite is matched against. The backend has zero runtime dependencies on `@suaveplan/i18n` core, so it can be used standalone — for example, a test suite needing catalog-shaped data can import `MemoryBackend` without paying the cost of pulling in the full catalog stack. ## Features - Full 5-flag capability matrix: `canRead: true`, `canReadMulti: true`, `canSave: true`, `canTrackModification: true`, `canWatch: false`. - Optional constructor seed (`{ : { : TranslationRecord } }`) for pre-populating before first read. - Injectable `now: () => number` clock for deterministic `lastModified` testing. - Optional `signal?: AbortSignal` parameter on `read`, `readMulti`, and `save`; pre-aborted signals throw `I18nError(I18N.BACKEND_TIMEOUT)`. - OTel histograms + counters emitted on every read/save: `i18n.backend.memory.read_duration_ms`, `read_total{outcome}`, `read_errors_total{code}`, `write_duration_ms`, `write_total{outcome}`. - Zod boundary validation: invalid options throw `I18nError(I18N.INVALID_OPTIONS)` at construction time. ## Usage: Basic Example ```ts import { MemoryBackend } from "@suaveplan/i18n-backend-memory"; const backend = new MemoryBackend({ seed: { en: { common: { hello: "Hello" } }, fr: { common: { hello: "Bonjour" } }, }, }); const enCommon = await backend.read("en", "common"); // { hello: "Hello" } ``` ## Usage: Advanced Example The injectable clock makes `lastModified` deterministic so chained- backend tests can assert exact timestamps without fighting wall-clock non-determinism. ```ts import { MemoryBackend } from "@suaveplan/i18n-backend-memory"; let tick = 1000; const backend = new MemoryBackend({ name: "cache", now: () => tick++, }); await backend.save("en", "common", { hello: "Hello" }); await backend.save("en", "auth", { login: "Sign in" }); const tCommon = await backend.lastModified("en", "common"); // 1000 const tAuth = await backend.lastModified("en", "auth"); // 1001 // Cancellable reads play nicely with multi-backend orchestration. const controller = new AbortController(); controller.abort(); await backend.read("en", "common", controller.signal); // throws BACKEND_TIMEOUT ``` ## API Reference ### `class MemoryBackend implements Backend` - `constructor(options?: MemoryBackendOptions)` — validates `options` via `MemoryBackendOptionsSchema`; throws `I18nError(I18N.INVALID_OPTIONS)` on schema mismatch. - `name: string` — backend label (default `"memory"`). - `capabilities: BackendCapabilities` — frozen 5-flag matrix. - `read(locale, namespace, signal?): Promise` — resolves with the seeded record; throws `I18nError(I18N.BACKEND_LOCALE_NOT_AVAILABLE)` on miss. - `readMulti(locale, namespaces, signal?): Promise>` — returns only the namespaces that exist; missing namespaces are omitted (not undefined). - `save(locale, namespace, record, signal?): Promise` — persists the record and stamps the modification ledger via the injected `now()` function. - `lastModified(locale, namespace): Promise` — returns the timestamp of the last `save` for the slot, or `undefined` if never written. - `clear(): void` — test-only helper, drops all in-memory state. - `snapshot(): MemorySeed` — returns a fresh deep-copy snapshot of the current store keyed by locale → namespace. ## Implementation Notes - The `Map>` two-level data structure gives O(1) lookups and O(1) inserts. The modification ledger is a flat `Map` keyed by `"${locale} ${namespace}"` with a single space separator (BCP-47 tags do not contain spaces, so the key is unambiguous). - `signal?.aborted` is checked at the entry of every async method and the throw surfaces as `I18nError(I18N.BACKEND_TIMEOUT)`. The `aborted` flag is sampled exactly once at the top — the backend performs no further async work, so post-yield abort is not meaningful. - OTel `recordRead` / `recordWrite` calls emit zero overhead unless a `MeterProvider` is registered. Test suites use the in-memory metrics state ring exposed by `./observability` to assert counter/histogram payloads without standing up the full SDK. - Seeded options call the same private `#put` path as `save`, so the modification ledger is populated for every seeded slot using the injected `now()` function. The seed is consumed synchronously at construction; consumers needing async seeding call `save()` post-construction.