Cached Server-State Client with Optimistic Mutations
Practical 09 - Cached Server-State Client with Optimistic Mutations
Related: Chapter 9 · Lecture slides
Objective
Build a resilient, framework-agnostic asynchronous server-state cache manager (a micro TanStack Query) from scratch in TypeScript.
You will implement:
- Deterministic Query Key Hashing: Storing and retrieving queries by composite serialized keys.
- Stale-While-Revalidate (SWR) Caching: Serving cached snapshots instantly while asynchronously fetching fresh server data in the background.
- In-Flight Request Deduplication: Merging concurrent duplicate calls into a single shared network promise.
- Transient Error Retry with Exponential Backoff: Automatically retrying 5xx and network failures while respecting cancellation signals.
- Optimistic Mutations with Snapshot Rollback: Updating cache entries immediately upon user action, reverting gracefully if the server rejects the request.
Prerequisites and Workspace Setup
You need Node.js (v18+) and the TypeScript compiler.
Initialize your practical workspace:
Stage 1 - Deterministic Query Key Serialization
Query keys represent the semantic identity of a server request. Keys often contain nested objects (such as { sort: 'date', page: 2 }), which cannot be compared with standard referential equality (===).
In src/query-key.ts, implement a deterministic serializer that sorts object keys alphabetically:
Verify that ['permits', { page: 1, sort: 'name' }] and ['permits', { sort: 'name', page: 1 }] produce identical hash strings: ["permits",{"page":1,"sort":"name"}].
Stage 2 - Stale-While-Revalidate and Request Deduplication
In src/query-cache.ts, implement the core SWR cache engine:
Stage 3 - Abortable Fetch with Exponential Backoff
In src/fetch-retry.ts, implement a transport wrapper that handles network dropouts and 5xx server errors with randomized exponential jitter:
Stage 4 - Optimistic Mutations and Verification Matrix
1. The Optimistic Mutation Contract
In src/mutation-manager.ts, implement mutation execution with snapshot rollback:
2. Verification Matrix
| # | Action | Expected Observable Result | Status |
|---|---|---|---|
| V1 | Mount three components calling client.fetch(['permits']) simultaneously | Exact single network call dispatched (inFlight deduplicated); all three resolve same data. | |
| V2 | Read cached data within staleTime | Resolves synchronously from memory in 0ms without network dispatch. | |
| V3 | Simulate 503 Service Unavailable on fetch | Client retries 3 times with exponentially increasing intervals before throwing error. | |
| V4 | User edits permit title with optimistic update | UI updates title instantly. Simulated network error 500 triggers rollback to original title. | |
| V5 | Mutation succeeds on server | Triggers client.invalidate(['permits']), marking queries stale and triggering background refresh. |
Evaluation Rubric
| Criterion | Exemplary (4) | Proficient (3) | Developing (2) | Inadequate (1) |
|---|---|---|---|---|
| Cache Key Architecture | Deterministic key sorting, nested object support, zero collision between similar query structures. | Keys serialized, but sensitive to object property insertion order. | Flat string keys only; lacks object parameter serialization. | Cache uses URL strings directly with no key structure. |
| Deduplication & SWR | Flawless promise reuse for concurrent requests; stale data served instantly while background revalidation executes. | In-flight deduplication works, but lacks SWR background update capability. | Caches data, but duplicate simultaneous calls create duplicate HTTP requests. | No caching; every component fetch initiates independent network calls. |
| Retry & Backoff Engine | Exponential backoff with jitter; fails fast on 4xx client errors; respects AbortSignal cancellation. | Backoff implemented, but retries client 4xx errors or lacks jitter. | Retries with fixed timeout delay; no backoff. | No retry logic; single network dropout causes permanent failure. |
| Optimistic Rollback | Clean snapshot preservation, instant UI preview, automatic rollback on failure, authoritative confirmation on success. | Optimistic update works, but leaves UI in corrupted state if server rejects request. | Pessimistic updates only; UI waits for server round-trip. | Directly mutates local state without server synchronization. |