- lib/store.ts: createStore<T> — one observable-value primitive (get/set/subscribe/use) replacing the hand-rolled listeners/subscribe/emit triad several modules each had. - lib/storage.ts: LS key registry (every siftlode.* key in one place), readMerged/ readJSON/writeJSON (the try/JSON.parse/merge/catch done once), and usePersistedState (the reactive persisted-string hook, generalizing usePersistedTab).
34 lines
1018 B
TypeScript
34 lines
1018 B
TypeScript
import { useSyncExternalStore } from "react";
|
|
|
|
// A minimal observable value shared across React components AND non-React code (the api
|
|
// layer, websocket handlers, etc.). Replaces the hand-rolled `listeners`/`subscribe`/`emit`
|
|
// triad that several modules each re-implemented slightly differently.
|
|
export interface Store<T> {
|
|
get(): T;
|
|
set(next: T | ((prev: T) => T)): void;
|
|
subscribe(listener: () => void): () => void;
|
|
/** Subscribe from a component; re-renders on every change. */
|
|
use(): T;
|
|
}
|
|
|
|
export function createStore<T>(initial: T): Store<T> {
|
|
let value = initial;
|
|
const listeners = new Set<() => void>();
|
|
const get = () => value;
|
|
const subscribe = (l: () => void) => {
|
|
listeners.add(l);
|
|
return () => {
|
|
listeners.delete(l);
|
|
};
|
|
};
|
|
return {
|
|
get,
|
|
set(next) {
|
|
value = typeof next === "function" ? (next as (p: T) => T)(value) : next;
|
|
listeners.forEach((l) => l());
|
|
},
|
|
subscribe,
|
|
use: () => useSyncExternalStore(subscribe, get, get),
|
|
};
|
|
}
|