svelte-optimistic-store Svelte Themes

Svelte Optimistic Store

Generic optimistic update store for Svelte 5 with retry logic, error handling, and rollback — built on runes

Svelte 5 Optimistic Store

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.

Overview

This project shows two counter implementations side-by-side:

  • Optimistic Store Counter — Updates the UI immediately, syncs with a (fake) server in the background, and rolls back on failure. Includes retry with exponential backoff, loading/error states, and derived computations.
  • Simple Runes Counter — A pure local-state counter using only $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.

How It Works

Optimistic Update Flow

User clicks +1
  → UI updates immediately (optimistic)
  → Request sent to server
     ├─ Success: keep the new value
     └─ Failure: rollback to previous value, show error

Retry with Exponential Backoff

When the initial data fetch fails, the store retries automatically:

  • Configurable max attempts (default: 3)
  • Delay doubles each attempt: min(1000ms * 2^attempt, 30000ms)
  • UI shows retry progress with attempt count

The Fake API

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.

The Store Class

import { 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),
});

Type Parameters

Store<T, M, D>
Parameter Description
T Data type (e.g. number)
M Mutation methods object
D Derived values object

State Machine

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

Project Structure

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

Getting Started

# Install dependencies
npm install

# Start dev server
npm run dev

# Type check
npm run check

# Build for production
npm run build

Tech Stack

  • Svelte 5 — Runes ($state, $derived) for reactive state
  • TypeScript — Full type safety with discriminated unions
  • Vite — Dev server and bundler

License

MIT

Top categories

Loading Svelte Themes