feat(01-03): Base64 codec + DoS-capped import + index re-exports + SaveDB interface refactor [GREEN]

- codec.ts: exportToBase64 / importFromBase64 via lz-string with
  MAX_IMPORT_BYTES=50MB DoS cap (T-01-02 in plan threat model); import
  validates against SaveEnvelopeSchema before returning. lz-string sync
  caveat documented per RESEARCH Pitfall 5 (Web Worker mitigation deferred
  to Phase 8 per CONTEXT D-09)
- index.ts: 14 public re-exports — the only entry point Phase 2 should
  import from. Includes the LocalStorageDBAdapter class so consumers can
  type-check the fallback path explicitly if needed

[Rule 1 - Bug] Build was failing because the original SaveDB type was a
union (IDBPDatabase<SaveDBSchema> | LocalStorageDBAdapter) — TypeScript
cannot resolve method calls through a union when each branch has
differently-shaped overloads ('no compatible signature' on every db.put).
Fixed by:
  - Defining SaveDB as a single common-contract interface that both
    backends MUST satisfy (get/put/delete/getAll/transaction with
    conditional-type RecordOf<S> return values)
  - Hoisting the canonical SavedRecord/SnapshotRecord/StoreName types
    into db-localstorage-adapter.ts (lower-level module) and re-exporting
    them from db.ts to avoid a circular import
  - Casting the idb-returned IDBPDatabase to SaveDB at the open-call
    boundary (the casts are isolated to openSaveDB; Phase 2 only sees
    the SaveDB interface)
  - Promoting SnapshotEntry to a type-alias of SnapshotRecord so
    snapshots.ts no longer redeclares the shape and can rely on
    canonical types

Tests: 36/36 pass under 'npx vitest run src/save/' (full suite incl
sentinel: 37/37). 'npm run build' exits 0 under TypeScript strict.
'npm run lint' is not invoked here because Plan 02 (eslint-firewall) has
not landed yet — the lint script will fail until it does, by design per
the Plan 01-01 SUMMARY ('Plan 02 owns it').
This commit is contained in:
2026-05-08 23:42:00 -04:00
parent bec0df1dc2
commit 2761bcc1e0
5 changed files with 195 additions and 30 deletions
+6 -6
View File
@@ -1,12 +1,12 @@
import { openSaveDB } from './db';
import type { SnapshotRecord } from './db';
import type { SaveEnvelope } from './envelope';
export interface SnapshotEntry {
id: string;
schemaVersion: number;
savedAt: string;
envelope: SaveEnvelope;
}
/**
* Public type for what listSnapshots returns. Structurally identical to
* SnapshotRecord but exposed under a friendlier name for Phase 2's UI.
*/
export type SnapshotEntry = SnapshotRecord;
/**
* Last-N pre-migration snapshot retention. CORE-08 mandates exactly 3.