3.17.2 — Storage facade expansion: DB-only publishers
Patch release. Closes the second half of issue #11, addressing April's comment on the original ticket: "There's other places in the codebase where we need a facade. This includes creatures, fixtures, etc."
3.17.1 shipped the file-storage half (JobWorker handlers + DialogCache + read-side path resolution). This release ships the DB-only half — every paired DB-write/cache-invalidation in controllers and services now goes through the creatures::storage facade. The original footgun ("forgot to fire scheduleCacheInvalidationEvent after a DB write") is structurally impossible now.
New facade surface
11 new functions in creatures::storage, all pairing a DB call with the matching invalidation atomically:
| Function | DB call + invalidation(s) |
|---|---|
publishCreature |
upsertCreature + Creature |
publishFixture |
upsertFixture + Fixture |
deleteFixture |
deleteFixture + Fixture |
setFixtureUniverse |
setFixtureUniverse + Fixture |
publishPlaylist |
upsertPlaylist + Playlist |
publishDialogScript |
upsertDialogScript + DialogScriptList |
deleteDialogScript |
deleteDialogScript + DialogScriptList |
publishStoryboard |
upsertStoryboard + StoryboardList |
deleteStoryboard |
deleteStoryboard + StoryboardList |
deleteAnimation |
deleteAnimation + Animation |
broadcastCacheInvalidation(type) |
named manual broadcast for the few remaining spots where the mutation happens outside our process (Debug refresh buttons + the legacy Voice sound-write that still goes through CreatureVoicesLib plumbing) |
Shared runPublisher<DbCall, Caches...> template owns the "call DB, fire invalidations only on success" contract — used by every publisher including the three from 3.17.1 (refactored).
Migrations
- DialogScript + Storyboard controllers (×3 each): direct
publishX/deleteX - DmxFixture / Creature / Animation / Playlist services:
db->upsertX/deleteX→storage::publishX/deleteX; their controllers then drop the now-redundantscheduleCacheInvalidationEventlines - Voice + Debug controllers:
broadcastCacheInvalidation - AnimationController delete keeps its belt-and-suspenders synchronous
broadcastCacheInvalidationToAllClientsfor the irreversible op
Audit (the regression proof)
grep -rn 'scheduleCacheInvalidationEvent' src/server/ws/{controller,service}/ → empty
grep -rn 'db->upsert|db->delete|db->insert' src/server/ws/{controller,service}/ → empty
Tests
19 new PublishersTest.* pin the pairing contract per function — success-path verifies the exact CacheTypes fire, failure-path verifies nothing fires (invalidations are atomic with the DB call). FakeDatabase gains a testing::setFakeDatabaseSucceeds(bool) toggle for happy-path tests; FakeWebsocketUtils keeps a thread-local invalidation log so tests can assert without standing up a real EventLoop. Full suite 158 / 158 (was 139 + 19 new).
Verification
Live smoke against deployed 3.17.2:
- Storyboard create + delete → both clients refresh
- DialogScript create + delete → both clients refresh
- Debug refresh button → clients refresh
- Wire envelope unchanged (lowercase
"ok"/"error"/"not_found"from issue #16 intact)
Notes
- Pure internal refactor; no wire-format change.
- Plan + design rationale:
docs/storage-facade-expansion-plan.md. - Companion to
docs/storage-facade-plan.md(3.17.1).