// The keystone adapter. Everything reactive in the router funnels through // `@tanstack/react-store`'s `useStore(atom, selector)`, which is // `useSyncExternalStoreWithSelector(atom.subscribe→unsubscribe, atom.get, …, // selector, compare)`. Octane has no `…WithSelector`, so we fold the selector + // compare into a memoized snapshot getter on top of octane's native // `useSyncExternalStore`. The atoms come from router-core's client store factory // (`createAtom` from `@tanstack/store`): `.subscribe(cb) → { unsubscribe }` + `.get()`. import { useSyncExternalStore, useCallback, useRef } from 'octane'; import { isServer } from '@tanstack/router-core/isServer'; import { subSlot, splitSlot } from './internal'; interface Atom { subscribe: (cb: () => void) => { unsubscribe: () => void }; get: () => T; } // Public signature mirrors @tanstack/react-store's `useStore(store, selector, // compare)`; the store parameter is the structural `{ get }` shape so router-core's // `RouterReadableStore`/`RouterWritableStore` atoms (whose types omit `subscribe`) // infer `T` directly. The trailing `slot` is the binding's forwarded call-site slot // (see internal.ts) — the implementation splits it off the raw argument list. export function useStore( atom: { get: () => T }, selector?: (state: T) => S, compare?: (a: S, b: S) => boolean, slot?: symbol, ): S; export function useStore(...args: any[]): any { const rawAtom = args[0] as Atom; if (isServer && typeof rawAtom.subscribe !== 'function') { const value = rawAtom.get(); const selector = args[1]; return typeof selector === 'function' ? selector(value) : value; } const [user, slot] = splitSlot(args); const atom = user[0] as Atom; const selector = (user[1] ?? ((s: unknown) => s)) as (s: unknown) => unknown; const compare = (user[2] ?? Object.is) as (a: unknown, b: unknown) => boolean; // Re-subscribe only when the atom identity changes (it's stable across renders). const subscribe = useCallback( (onChange: () => void) => atom.subscribe(onChange).unsubscribe, [atom], subSlot(slot, 'us:cb'), ); // A cached snapshot belongs to its atom, selector, and comparator as well as // its input. Route parameters captured by a new selector must be re-evaluated // even when the atom has already published the current match-id list. // Structurally-equal output keeps its previous reference so // useSyncExternalStore does not loop on allocating selectors. const cache = useRef<{ atom: Atom; in: unknown; selector: typeof selector; compare: typeof compare; out: unknown; } | null>(null, subSlot(slot, 'us:cache')); function getSnapshot(): unknown { const input = atom.get(); const prev = cache.current; if ( prev && prev.atom === atom && prev.selector === selector && prev.compare === compare && Object.is(prev.in, input) ) { return prev.out; } const next = selector(input); const out = prev && compare(prev.out, next) ? prev.out : next; cache.current = { atom, in: input, selector, compare, out }; return out; } return useSyncExternalStore(subscribe, getSnapshot, getSnapshot, subSlot(slot, 'us:uses')); }