import { browser } from '$app/environment'; import { createSubscriber } from 'svelte/reactivity'; type PersistedBaseOptions = { read: (key: string) => T | undefined; write: (key: string, value: T) => void; }; class PersistedBase { #value: T; #subscribe: () => void; #update = () => {}; #write: (value: T) => void; get current() { this.#subscribe(); return this.#value as T; } set current(value: T) { this.#write(value); this.#update(); this.#value = value; } constructor(key: string, defaultValue: T, options: PersistedBaseOptions) { const value = options.read(key); this.#value = value === undefined ? defaultValue : value; this.#write = (value: T) => options.write(key, value); this.#subscribe = createSubscriber((update) => { this.#update = update; return () => { this.#update = () => {}; }; }); } } type PersistedLocalStorageOptions = { serializer?: { stringify(value: T): string; parse(text: string): T; }; valid?: (value: T | unknown) => value is T; }; export class PersistedLocalStorage extends PersistedBase { constructor(key: string, defaultValue: T, options: PersistedLocalStorageOptions = {}) { const valid = options.valid || (() => true); const serializer = options.serializer || JSON; super(key, defaultValue, { read: (key: string) => { if (!browser) { return; } const item = localStorage.getItem(key) ?? undefined; if (item === undefined) { return; } const parsed = serializer.parse(item); if (!valid(parsed)) { return; } return parsed; }, write: (key: string, value: T) => { if (browser) { localStorage.setItem(key, serializer.stringify(value)); } }, }); } }