feat(query): default invalidations to the keys optimistic() declares#158
Merged
Conversation
Every optimistic CRUD mutation ended with the same boilerplate invalidations method restating keys the engine already knew from optimistic(_:) — and FORGETTING the method was the exact footgun a DEBUG diagnostic papered over (cache keeps the optimistic guess forever). The default invalidations(input:output:) now derives from optimistic(input)'s DECLARED edits: .exact per declared key, deduped in declaration order. Derived from declarations, not the rollback stack — a .noValue-skipped edit (nothing cached yet) still gets its refetch, so the server value lands either way (deriving from rollback would silently lose exactly those; issue #96's territory). Dedup matters: duplicate invalidations of one key cancel-respawn its repair fetch. Existing overrides win — not source-breaking; an explicit [] override remains the deliberate opt-out. The optimistic-without-invalidations diagnostic and its exit-test suite are retired: the misuse it warned about is now inexpressible (the derived default cannot be empty while optimistic edits exist) — making misuse inexpressible beats warning about it. New contract documented on the protocol: optimistic(_:) should be a PURE declaration (the default re-reads it on success to learn the keys); effects that must run once — like allocating an optimistic temp id — belong inside the edit's transform closure, which runs only when the edit applies. Dogfood: all 4 invalidations overrides in the repo deleted (TodoCRUD x3, QueryDemo x1); AddTodo's temp-id allocation moved into its transform closure per the purity rule; EmbeddedTemplates regenerated. TDD: RED reproduced the footgun end-to-end (no repair refetch, cache stuck on the guess) before the diagnostic deletion + implementation turned it GREEN. Verified: host 1383/277, TodoCRUD + QueryDemo wasm builds clean, release CLI rebuilt, TodoCRUD Playwright e2e 5/5 against the real Bun backend scaffolded from the new templates (optimistic-add-reconcile now exercises the DERIVED invalidation; rollback unaffected). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📦 Bundle size
✅ Within budget (≤5% growth allowed). Baseline: Swift 6.3, WASM SDK 6.3-RELEASE, measured 2026-06-18. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part II Wave 2 · #1 — delete the boilerplate, make the footgun inexpressible
Every optimistic CRUD mutation ended with the same required boilerplate restating keys the engine already knew:
…and forgetting the method was the exact trap a DEBUG diagnostic papered over: the cache keeps the optimistic guess and never reconciles with the server.
Change
Mutation.invalidationsnow defaults to the keysoptimistic(input)declares —.exactper declared key, deduped in declaration order:.noValue-skipped edit — nothing cached yet — still gets its refetch, so the server value lands either way. Deriving from rollback would silently lose exactly those (issue bug: optimistic mutation before its query loads traps in DEBUG (.noValue should be a benign skip) #96's territory). Test-pinned.[]override is the deliberate opt-out.optimistic(_:)should be a pure declaration (the default re-reads it on success to learn the keys); run-once effects — like allocating an optimistic temp id — belong inside the edit's transform closure, which runs only when the edit applies.Dogfood
All 4
invalidationsoverrides in the repo deleted (TodoCRUD ×3, QueryDemo ×1).AddTodo's temp-id allocation moved into its transform closure per the purity rule.EmbeddedTemplates.swiftregenerated.TDD
RED reproduced the footgun end-to-end before implementing —
fetches → 1(no repair refetch) and the cache stuck on the optimistic guess[100, 7]. 5 new tests: derived-default shape (dedup + order), skipped-edits-still-contribute (the caveat), override-wins, empty-optimistic → empty, and end-to-end reconciliation via the derived refetch.Verification
🤖 Generated with Claude Code