svelte-stash
v1.0.0
Published
A lightweight state manager for Svelte 5
Maintainers
Readme
svelte-stash
A lightweight, generic state management class for Svelte 5 projects that bridges in-memory reactive state with persistent storage. Uses Svelte 5 $state runes, letting you sync with your chosen storage backend (localStorage, APIs, databases, etc.).
Requires Svelte 5.0.0 or higher - This package uses Svelte 5's
$staterunes and will not work with Svelte 4 or earlier versions.
Features
- Extends Svelte 5's
$staterune for automatic reactivity - Configurable Debouncing
- Works with any storage backend (localStorage, database, API endpoints, etc.)
- Full TypeScript support with generic type constraints
- Prevents reference mutations between state and storage
Installation
npm install svelte-stashThe package has one peer dependency: Svelte 5
Usage
Basic setup
import { Stash } from 'svelte-stash';
interface UserSettings {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
}
// Create a stash that syncs in-memory state with localStorage
const settings = new Stash<UserSettings>(
// Load callback - retrieves state from storage
async () => {
const saved = localStorage.getItem('userSettings');
return saved
? JSON.parse(saved)
: {
theme: 'dark',
language: 'en',
notifications: true
};
},
// Save function - persist state changes
async data => {
localStorage.setItem('userSettings', JSON.stringify(data));
},
// Debounce options - optimize storage writes
{ delay: 500, maxWait: 2000 }
);
// Initialise: Load from persistent storage into reactive memory
await settings.load();
// Export state
export { settings };
```
### In Svelte components
```svelte
<script lang="ts">
import { settings } from '$lib/stores/settings';
// The state is reactive in-memory - changes instantly update the UI
let { state } = settings;
function toggleTheme() {
state.theme = state.theme === 'light' ? 'dark' : 'light';
}
</script>
<button on:click={toggleTheme}>
Current theme: {state.theme}
<!-- Updates instantly -->
</button>
<label>
<input type="checkbox" bind:checked={state.notifications} />
Enable notifications
</label>How it works:
- Load: Storage → Memory (on initialization)
- Mutate: Direct in-memory changes (instant UI updates)
- Sync: Memory → Storage (debounced)
Custom debouncing
const stash = new Stash(loadFn, saveFn, {
delay: 300, // Wait 300ms after last change
maxWait: 2000, // Force save after 2 seconds maximum
immediate: true // Save immediately on first change
});Multiple stashs
// Separate stashs for different concerns
const userSettings = new Stash(loadUserSettings, saveUserSettings);
const appCache = new Stash(loadCache, saveCache, { delay: 100 });
const gameState = new Stash(loadGame, saveGame, { immediate: true });Force save before page close
const stash = new Stash(loadFn, saveFn, { delay: 500 });
// Ensure pending saves complete before page closes
window.addEventListener('beforeunload', () => {
stash.flush();
});Discard pending changes
async function discardChanges() {
stash.cancel(); // Cancel pending save
await stash.load(); // Reload original state from storage
}Error handling
const stash = new Stash(loadFn, saveFn, {
delay: 500,
onError: error => {
console.error('Failed to save state:', error);
showNotification('Auto-save failed. Your changes may not be saved.');
}
});By default, save errors surface as unhandled rejections. Use onError to handle them explicitly. Load errors are thrown and should be caught with try-catch.
API
new Stash<T>(loadCallback, saveCallback?, debounceOptions?)
Parameters:
loadCallback— Function to load initial state data (sync or async). Should return the complete state object.saveCallback(optional) — Function to persist state changes. Receives a deep clone of current state (sync or async).debounceOptions(optional) — Configuration for save debouncing:
| Option | Type | Default | Description |
| ----------- | -------------------------- | ------- | ------------------------------------------ |
| delay | number | 0 | Milliseconds to wait after last change |
| maxWait | number | 0 | Maximum milliseconds before forcing a save |
| immediate | boolean | false | Execute save immediately on first change |
| onError | (error: unknown) => void | — | Callback for handling save errors |
Properties
state— The reactive state object (Svelte 5$state)
Methods
load()— Loads state from persistent storage into reactive memorysave()— Manually triggers a sync from memory to persistent storageflush()— Immediately executes any pending debounced save and clears timerscancel()— Cancels any pending debounced save without persisting
Important Notes
Type constraints
- State type
Tmust be structured-cloneable/serializable - Functions, DOM nodes, and certain class instances will not work properly
- Use plain objects, arrays, primitives, and serializable data only
Reactivity best practices
- Mutate state fields directly:
stash.state.theme = 'dark' - Avoid replacing the entire state object
- Use
$state.snapshot(stash.state)to get a non-reactive copy for external use
License
MIT
