A demonstration of optimistic updates with retry logic and error handling in Svelte 5, featuring a generic reactive store built entirely on Svelte 5 runes.
This project shows two counter implementations side-by-side:
$state and $derived for comparison.The core of the project is a generic Store class that can be adapted for any data type, not just counters.
User clicks +1
→ UI updates immediately (optimistic)
→ Request sent to server
├─ Success: keep the new value
└─ Failure: rollback to previous value, show error
When the initial data fetch fails, the store retries automatically:
min(1000ms * 2^attempt, 30000ms)A simulated server with a 50% failure rate and 200ms latency makes it easy to observe how the store handles errors, rollbacks, and retries without any backend setup.
Store Classimport { Store } from './lib/optimisticStore.svelte';
const counterStore = new Store({
// Initial data fetch
fetch: () => api.getCount(),
// Define mutations — each gets the current data and a mutate helper
mutations: (data, mutate) => ({
increment: () => mutate(data + 1, (n) => api.update(n)),
decrement: () => mutate(data - 1, (n) => api.update(n)),
reset: () => mutate(0, (n) => api.update(n)),
}),
// Derived values — recomputed reactively when data changes
derived: (data) => ({
double: data * 2,
quad: data * 4,
isEven: data % 2 === 0,
}),
// Optional callbacks
onError: (error) => console.error(error),
onUpdate: (data) => console.log('updated', data),
});
Store<T, M, D>
| Parameter | Description |
|---|---|
T |
Data type (e.g. number) |
M |
Mutation methods object |
D |
Derived values object |
The store exposes a discriminated union state for type-safe consumption:
loading → ready
loading → loading (retrying) → ready
loading → loading (retrying) → error
ready → ready (updating: true) → ready
ready → ready (updating: true) → ready (error, rolled back)
| Status | Fields |
|---|---|
loading |
retrying: false |
loading (retry) |
retrying: true, attempt, maxAttempts, error |
error |
error |
ready |
data, updating, syncing, error, + mutations + derived |
src/
├── App.svelte Main layout with side-by-side counters
├── main.ts Entry point
├── app.css Global styles (dark theme)
└── lib/
├── optimisticStore.svelte.ts Generic optimistic store
├── counterState.svelte.ts Counter store instance + fake API
├── Counter.svelte Optimistic counter component
└── SimpleCounter.svelte Pure runes counter component
# Install dependencies
npm install
# Start dev server
npm run dev
# Type check
npm run check
# Build for production
npm run build
$state, $derived) for reactive stateMIT