Move entity rehydration to domain layer #20

Open
opened 2026-03-27 22:06:12 +01:00 by dostulata · 0 comments
Owner

Summary

Entity rehydration (reconstructing typed domain objects from untyped JSON) currently lives in persistence adapters, duplicating validation logic that the domain already owns. This caused a bug where adding level to PlayerCharacter required updating both the domain type and a separate rehydration function in the persistence layer — the persistence function was missed, silently dropping levels on reload. Moving rehydration to the domain ensures adding a field to a type and its rehydration function is the only change needed.

Acceptance Criteria

  • Domain exports rehydratePlayerCharacter(raw: unknown): PlayerCharacter | null that validates all fields (id, name, ac, maxHp, color, icon, level) and returns a typed instance or null
  • Domain exports rehydrateCombatant(raw: unknown): Combatant | null with equivalent validation for combatant fields
  • player-character-storage.ts delegates to rehydratePlayerCharacter instead of reimplementing field checks
  • encounter-storage.ts delegates to rehydrateCombatant instead of reimplementing field checks
  • validateImportBundle and rehydrateEncounter are unchanged — they validate bundle envelope / encounter shape in the adapter and delegate entity validation to domain rehydration functions
  • Adding a new optional field to PlayerCharacter or Combatant requires updating only the domain type and its rehydration function — persistence adapters stay in sync automatically
  • Exhaustive corrupt/missing-field test cases live in domain tests; persistence tests only verify localStorage round-trip and delegation
## Summary Entity rehydration (reconstructing typed domain objects from untyped JSON) currently lives in persistence adapters, duplicating validation logic that the domain already owns. This caused a bug where adding `level` to `PlayerCharacter` required updating both the domain type and a separate rehydration function in the persistence layer — the persistence function was missed, silently dropping levels on reload. Moving rehydration to the domain ensures adding a field to a type and its rehydration function is the only change needed. ## Acceptance Criteria - [ ] Domain exports `rehydratePlayerCharacter(raw: unknown): PlayerCharacter | null` that validates all fields (id, name, ac, maxHp, color, icon, level) and returns a typed instance or null - [ ] Domain exports `rehydrateCombatant(raw: unknown): Combatant | null` with equivalent validation for combatant fields - [ ] `player-character-storage.ts` delegates to `rehydratePlayerCharacter` instead of reimplementing field checks - [ ] `encounter-storage.ts` delegates to `rehydrateCombatant` instead of reimplementing field checks - [ ] `validateImportBundle` and `rehydrateEncounter` are unchanged — they validate bundle envelope / encounter shape in the adapter and delegate entity validation to domain rehydration functions - [ ] Adding a new optional field to `PlayerCharacter` or `Combatant` requires updating only the domain type and its rehydration function — persistence adapters stay in sync automatically - [ ] Exhaustive corrupt/missing-field test cases live in domain tests; persistence tests only verify localStorage round-trip and delegation
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dostulata/initiative#20