Svelte demos

./persistable/demo.svelte

{
  "hello": "world",
  "favoriteNumber": 0
}
Source Code
import { writable, type Writable } from 'svelte/store';

/**
 * A writable store that will persist its value in localStorage.
 */
export interface Persistable<T> extends Writable<T> {
	sync: () => StoredItem<T>;
}

/**
 * Creates a persistable store that will persist its value in localStorage.
 * @param key Key to use for localStorage.
 * @param optimisticValue Value to use before the store is initialized.
 * @param initialValue Initial value to use if there is no value in localStorage.
 * @returns A writable store that will persist its value in localStorage.
 */
export function persistable<T>(
	key: string,
	optimisticValue: T,
	initialValue: T = optimisticValue
): Persistable<T> {
	const { value, synced } = safelyGetItem<T>(key);
	const { subscribe, set, update } = writable(
		!synced ? optimisticValue : value !== undefined ? value : initialValue
	);

	return {
		subscribe,
		update,
		set(value: T) {
			const storedItem = safelySetItem(key, value);
			if (!storedItem.synced) {
				return;
			}

			set(value);
		},
		sync(): StoredItem<T> {
			const storedItem = safelyGetItem<T>(key);
			if (!storedItem.synced || storedItem.value === undefined) {
				return storedItem;
			}

			set(storedItem.value);
			return storedItem;
		}
	};
}

interface StoredItem<T> {
	value: T | undefined;
	synced: boolean;
}

function safelyGetItem<T>(key: string): StoredItem<T> {
	if (typeof localStorage === 'undefined') {
		return makeStoredItem(false);
	}

	const serialized = localStorage.getItem(key);
	if (serialized === null) {
		return makeStoredItem(true);
	}

	return makeStoredItem(true, JSON.parse(serialized));
}

function safelySetItem<T>(key: string, value: T): StoredItem<T> {
	if (typeof localStorage === 'undefined') {
		return makeStoredItem(false);
	}

	const serialized = JSON.stringify(value);
	localStorage.setItem(key, serialized);
	return makeStoredItem(true, value);
}

function makeStoredItem<T>(synced: boolean, value?: T): StoredItem<T> {
	return { value, synced };
}