import { useDebugValue, useMemo, useSyncExternalStore } from "react"; import { createSelectorCache, createSnapshotCache, type SnapshotCache } from "./snapshot-cache.ts"; export interface IslandStore { snapshot(): Readonly; subscribe(listener: () => void): () => void; actions: Record unknown>; } export type StoreResolver = (name: string) => IslandStore | undefined; let resolver: StoreResolver | null = null; let knownNames: string[] = []; /** Registers how island stores are looked up. Set by the island runtime at mount. */ export function setStoreResolver(next: StoreResolver | null, names: string[] = []): void { resolver = next; knownNames = names; } export interface BoundStore { store: IslandStore; getSnapshot: () => Readonly; cache: SnapshotCache; } function resolveStore(name: string): BoundStore { if (!resolver) { throw new Error( `useWrnStore("${name}") was called before the island runtime registered any stores.`, ); } const store = resolver(name) as IslandStore | undefined; if (!store) { const available = knownNames.length > 0 ? knownNames.join(", ") : "(none registered)"; throw new Error(`Unknown WRNexus store "${name}". Available stores: ${available}`); } const cache = createSnapshotCache(store); return { store, cache, getSnapshot: cache.getSnapshot }; } /** Test seam — exercises resolution and caching without rendering React. */ export function getStoreForTest(name: string): BoundStore { return resolveStore(name); } /** * Reads a WRNexus store from inside a React island. * * Writes must go through `useWrnActions` from an event handler or effect — * never during render, which would loop. */ export function useWrnStore>( name: string, selector?: (state: Readonly) => R, isEqual?: (a: R, b: R) => boolean, ): R { const bound = useMemo(() => resolveStore(name), [name]); const read = useMemo( () => selector ? createSelectorCache(bound.getSnapshot, selector, isEqual) : (bound.getSnapshot as unknown as () => R), [bound, selector, isEqual], ); const value = useSyncExternalStore(bound.store.subscribe, read, read); useDebugValue(value); return value; } /** Returns the action map for a store, for writes from handlers and effects. */ export function useWrnActions(name: string): Record unknown> { return useMemo(() => resolveStore(name).store.actions, [name]); }