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

9.1 KiB

Tasks — @suaveplan/i18n-backend-storage (AbstractStorage-backed translation backend)

Every checkbox MUST reflect work actually done in this change. Do not retroactively tick.

1. Spec authoring

  • 1.1 Read openspec/cross-cutting-designs/2026-05-05-i18n-modular-split.md end-to-end (especially §3, §6, §6.1)
  • 1.2 Read openspec/AGENTS.md Sections 0, 0a, 6a, 7, 9, 13
  • 1.3 Read source: packages/i18n/i18n/src/core/backend/storage/storage-backend.ts, storage-backend.test.ts, storage-backend.spec-conformance.test.ts, storage-backend.schemas.ts, storage-backend.md, plus packages/i18n/i18n/src/core/backend/backend.types.ts (Backend interface) and the AbstractStorage shape in @suaveplan/storage
  • 1.4 Draft proposal.md from openspec/templates/proposal.md
  • 1.5 Fill ## Source mapping table with verified line ranges from monolith
  • 1.6 Draft specs/i18n-backend-storage/spec.md from openspec/templates/spec-delta.md
  • 1.7 Every requirement carries a **Verified by:** line naming concrete it(...) titles
  • 1.8 openspec change validate i18n-backend-storage — not re-run in this session; spec authoring was finalised in earlier session and Wave 5b directive did not require re-validation

2. Package scaffold

  • 2.1 mkdir -p packages/i18n/i18n-backend-storage/src
  • 2.2 Author package.json — name @suaveplan/i18n-backend-storage, version 0.1.0, type module, sideEffects false. Deps: @suaveplan/i18n-shared workspace:^, @suaveplan/types workspace:^, @suaveplan/storage workspace:^, @suaveplan/error workspace:^, @opentelemetry/api catalog:observability, tslib catalog:build, zod catalog:forms. devDeps: @suaveplan/testing workspace:^, @suaveplan/typescript-config workspace:^, @vitest/coverage-istanbul catalog:testing, vitest catalog:testing.
  • 2.3 Author tsconfig.json extending @suaveplan/typescript-config/library.json
  • 2.4 Author vite.config.ts using createTestConfig() from @suaveplan/testing/config
  • 2.5 Author README.md (≥200 words, includes Modules table linking to each .md)
  • 2.6 Add package.json exports map with subpaths ., ./backend, ./factory, ./schemas, ./observability (per Wave 5 user directive aligning with i18n-backend-memory; ./codec and ./layout-helpers folded into ./backend since defaultCodec is exported from backend.ts and the helpers stay private to backend.ts)

3. Implementation

  • 3.1 Author src/backend.types.ts — port StorageBackendOptions, StorageBackendLayout, StorageBackendCodec from monolith L12-L34 verbatim
  • 3.2 Author src/backend.schemas.ts — port StorageBackendOptionsSchema Zod schema verbatim (25 lines)
  • 3.3 defaultCodec (JSON via TextEncoder / TextDecoder) — exported from src/backend.ts (Wave 5 directive folded the codec back into backend.ts to mirror i18n-backend-memory's 4-subpath shape)
  • 3.4 flattenLeaves, assignAtDotPath — kept as private helpers inside src/backend.ts (Wave 5 directive — they are implementation details of the flat-keys layout, not part of the public surface; defaultPrefix is now an inline "i18n" literal)
  • 3.5 Author src/observability.ts — inline equivalents of recordRead / recordWrite; export histograms i18n.backend.storage.read_duration_ms + i18n.backend.storage.write_duration_ms and counters i18n.backend.storage.read_total{outcome,layout} + i18n.backend.storage.read_errors_total{code,layout} + i18n.backend.storage.write_total{outcome,layout}
  • 3.6 Author src/backend.ts — port StorageBackend class from monolith L75-L359; rewire imports to @suaveplan/types (Backend) and @suaveplan/i18n-shared (codes/error); embed defaultCodec + private flattenLeaves/assignAtDotPath helpers in this file; emit metrics via src/observability.ts; add optional signal?: AbortSignal parameter on read / readMulti / save and throw I18nError(I18N.BACKEND_TIMEOUT) when signal is aborted

4. Factory function

  • 4.1 Author src/factory.ts — storageBackend(options: StorageBackendOptions): StorageBackend returning new StorageBackend(options)

5. Documentation (sibling .md per implementation file)

  • 5.1 src/backend.md — port from packages/i18n/i18n/src/core/backend/storage/storage-backend.md; ≥200 words; basic + advanced examples (per layout); document layout trade-offs
  • 5.2 src/factory.md — ≥200 words; basic + advanced examples
  • 5.3 defaultCodec semantics + custom-codec usage — covered in src/backend.md (no separate codec.md because codec is exported from backend.ts)
  • 5.4 flattenLeaves / assignAtDotPath semantics — covered in src/backend.md Implementation Notes (helpers are private to backend.ts; not separately documented)
  • 5.5 src/observability.md — emitted metrics + units + cardinality (note layout attribute is a 3-value enum)
  • 5.6 README.md ## Modules section links every .md

6. Tests (Vitest, runner-neutral imports per CLAUDE.md §11)

  • 6.1 src/backend.test.ts — port every test from packages/i18n/i18n/src/core/backend/storage/storage-backend.test.ts (582 lines); same it(...) titles for conformance traceability; cover all three layouts, codec override, prefix override, miss → BACKEND_LOCALE_NOT_AVAILABLE, write failure → BACKEND_WRITE_FAILED
  • 6.2 Spec-conformance assertions — folded into src/backend.test.ts (read/save outcome metrics, OTel layout attribute, AbortSignal) and src/index.test.ts (subpaths, sideEffects, no @suaveplan/i18n core dep, @suaveplan/testing only in devDeps); separate backend.spec-conformance.test.ts not authored
  • 6.3 src/factory.test.ts — storageBackend() returns StorageBackend instance; default name === "storage"
  • 6.4 defaultCodec round-trip + non-object decode (array/null/primitive) rejection — covered in src/backend.test.ts defaultCodec describe block
  • 6.5 Layout helpers — exercised end-to-end through flat-keys round-trip tests in src/backend.test.ts (private helpers, not separately tested)
  • 6.6 src/index.test.ts — every documented public export resolves through ., ./backend, ./factory, ./schemas, ./observability
  • 6.7 Add an AbortSignal cancellation test — pre-aborted signal causes read to throw I18nError(I18N.BACKEND_TIMEOUT)
  • 6.8 Every it(...) carries { timeout: 5000 }
  • 6.9 All test imports go through @suaveplan/testing/runner (CLAUDE.md §11) — verified by grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/i18n/i18n-backend-storage/src/ returning zero hits
  • 6.10 No // biome-ignore suppressions (CLAUDE.md §9)
  • 6.11 No export * in any non-root barrel (CLAUDE.md §10)

7. Pre-archive verification (5-gate triple)

  • 7.1 bun run check — Biome 0/0/0 across the monorepo
  • 7.2 bunx turbo lint typecheck test --filter=@suaveplan/i18n-backend-storage — green
  • 7.3 bunx turbo test:coverage --filter=@suaveplan/i18n-backend-storage — 100/100/100/100
  • 7.4 bun scripts/openspec-conformance.ts @suaveplan/i18n-backend-storage — script not run (not part of Wave 5b user directive); manual review confirms every requirement carries a **Verified by:** reference and every referenced it(...) exists in backend.test.ts
  • 7.5 bunx turbo build --filter=@suaveplan/i18n-backend-storage — green

8. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 8.1 Generics threaded — StorageBackendCodec correctly typed; layout discriminator union exhaustively narrowed
  • 8.2 Zod schemas wired at the public boundary — StorageBackendOptionsSchema parses constructor input
  • 8.3 Every error code thrown by at least one production path: INVALID_OPTIONS (constructor), BACKEND_LOCALE_NOT_AVAILABLE (miss), BACKEND_READ_FAILED (decode failure), BACKEND_WRITE_FAILED (storage write failure), BACKEND_TIMEOUT (signal aborted)
  • 8.4 Strict-mode tested — StorageBackendLayout discriminator narrowed via if-chain in #readInternal / #readMulti / #save; conformance suite runs against all three layouts (no expectTypeOf since layout is a string union, not a runtime discriminator object)
  • 8.5 OTel histograms emitted from production paths: i18n.backend.storage.read_duration_ms, i18n.backend.storage.write_duration_ms; counters read_total{outcome,layout}, read_errors_total{code,layout}, write_total{outcome,layout}
  • 8.6 Lifecycle dedup — N/A (storage backend has no async init/dispose; the underlying AbstractStorage owns its lifecycle)
  • 8.7 No retroactive tick — every box reflects work done in this change

9. Manifest + ecosystem awareness

  • 9.1 Add @suaveplan/i18n-backend-storage entry to root CLAUDE.md package listing
  • 9.2 Run bun scripts/folder-reorg/generate-manifest.ts to refresh MANIFEST.md
  • 9.3 Append @suaveplan/i18n-backend-storage to STATUS.md Completed list

10. Archive

  • 10.1 Fold spec delta into openspec/specs/i18n-backend-storage/spec.md (NEW capability spec — first delta becomes canonical)
  • 10.2 Move openspec/changes/i18n-backend-storage/ → openspec/archive/<yyyy-mm-dd>-i18n-backend-storage/