UNPKG

@tanstack/router-core

Version:

Modern and scalable routing for React applications

1 lines 13.3 kB
{"version":3,"file":"stores.cjs","names":[],"sources":["../../src/stores.ts"],"sourcesContent":["import { createLRUCache } from './lru-cache'\nimport { arraysEqual, functionalUpdate } from './utils'\n\nimport type { AnyRoute } from './route'\nimport type { RouterState } from './router'\nimport type { FullSearchSchema } from './routeInfo'\nimport type { ParsedLocation } from './location'\nimport type { AnyRedirect } from './redirect'\nimport type { AnyRouteMatch } from './Matches'\n\nexport interface RouterReadableStore<TValue> {\n get: () => TValue\n}\n\nexport interface RouterWritableStore<\n TValue,\n> extends RouterReadableStore<TValue> {\n set: ((updater: (prev: TValue) => TValue) => void) & ((value: TValue) => void)\n}\n\nexport type RouterBatchFn = (fn: () => void) => void\n\nexport type MutableStoreFactory = <TValue>(\n initialValue: TValue,\n) => RouterWritableStore<TValue>\n\nexport type ReadonlyStoreFactory = <TValue>(\n read: () => TValue,\n) => RouterReadableStore<TValue>\n\nexport type GetStoreConfig = (opts: { isServer?: boolean }) => StoreConfig\n\nexport type StoreConfig = {\n createMutableStore: MutableStoreFactory\n createReadonlyStore: ReadonlyStoreFactory\n batch: RouterBatchFn\n init?: (stores: RouterStores<AnyRoute>) => void\n}\n\ntype MatchStore = RouterWritableStore<AnyRouteMatch> & {\n routeId?: string\n}\ntype ReadableStore<TValue> = RouterReadableStore<TValue>\n\n/** SSR non-reactive createMutableStore */\nexport function createNonReactiveMutableStore<TValue>(\n initialValue: TValue,\n): RouterWritableStore<TValue> {\n let value = initialValue\n\n return {\n get() {\n return value\n },\n set(nextOrUpdater: TValue | ((prev: TValue) => TValue)) {\n value = functionalUpdate(nextOrUpdater, value)\n },\n }\n}\n\n/** SSR non-reactive createReadonlyStore */\nexport function createNonReactiveReadonlyStore<TValue>(\n read: () => TValue,\n): RouterReadableStore<TValue> {\n return {\n get() {\n return read()\n },\n }\n}\n\nexport interface RouterStores<in out TRouteTree extends AnyRoute> {\n status: RouterWritableStore<RouterState<TRouteTree>['status']>\n loadedAt: RouterWritableStore<number>\n isLoading: RouterWritableStore<boolean>\n isTransitioning: RouterWritableStore<boolean>\n location: RouterWritableStore<ParsedLocation<FullSearchSchema<TRouteTree>>>\n resolvedLocation: RouterWritableStore<\n ParsedLocation<FullSearchSchema<TRouteTree>> | undefined\n >\n statusCode: RouterWritableStore<number>\n redirect: RouterWritableStore<AnyRedirect | undefined>\n matchesId: RouterWritableStore<Array<string>>\n pendingIds: RouterWritableStore<Array<string>>\n /** @internal */\n cachedIds: RouterWritableStore<Array<string>>\n matches: ReadableStore<Array<AnyRouteMatch>>\n pendingMatches: ReadableStore<Array<AnyRouteMatch>>\n cachedMatches: ReadableStore<Array<AnyRouteMatch>>\n firstId: ReadableStore<string | undefined>\n hasPending: ReadableStore<boolean>\n matchRouteDeps: ReadableStore<{\n locationHref: string\n resolvedLocationHref: string | undefined\n status: RouterState<TRouteTree>['status']\n }>\n __store: RouterReadableStore<RouterState<TRouteTree>>\n\n matchStores: Map<string, MatchStore>\n pendingMatchStores: Map<string, MatchStore>\n cachedMatchStores: Map<string, MatchStore>\n\n /**\n * Get a computed store that resolves a routeId to its current match state.\n * Returns the same cached store instance for repeated calls with the same key.\n * The computed depends on matchesId + the individual match store, so\n * subscribers are only notified when the resolved match state changes.\n */\n getRouteMatchStore: (\n routeId: string,\n ) => RouterReadableStore<AnyRouteMatch | undefined>\n\n setMatches: (nextMatches: Array<AnyRouteMatch>) => void\n setPending: (nextMatches: Array<AnyRouteMatch>) => void\n setCached: (nextMatches: Array<AnyRouteMatch>) => void\n}\n\nexport function createRouterStores<TRouteTree extends AnyRoute>(\n initialState: RouterState<TRouteTree>,\n config: StoreConfig,\n): RouterStores<TRouteTree> {\n const { createMutableStore, createReadonlyStore, batch, init } = config\n\n // non reactive utilities\n const matchStores = new Map<string, MatchStore>()\n const pendingMatchStores = new Map<string, MatchStore>()\n const cachedMatchStores = new Map<string, MatchStore>()\n\n // atoms\n const status = createMutableStore(initialState.status)\n const loadedAt = createMutableStore(initialState.loadedAt)\n const isLoading = createMutableStore(initialState.isLoading)\n const isTransitioning = createMutableStore(initialState.isTransitioning)\n const location = createMutableStore(initialState.location)\n const resolvedLocation = createMutableStore(initialState.resolvedLocation)\n const statusCode = createMutableStore(initialState.statusCode)\n const redirect = createMutableStore(initialState.redirect)\n const matchesId = createMutableStore<Array<string>>([])\n const pendingIds = createMutableStore<Array<string>>([])\n const cachedIds = createMutableStore<Array<string>>([])\n\n // 1st order derived stores\n const matches = createReadonlyStore(() =>\n readPoolMatches(matchStores, matchesId.get()),\n )\n const pendingMatches = createReadonlyStore(() =>\n readPoolMatches(pendingMatchStores, pendingIds.get()),\n )\n const cachedMatches = createReadonlyStore(() =>\n readPoolMatches(cachedMatchStores, cachedIds.get()),\n )\n const firstId = createReadonlyStore(() => matchesId.get()[0])\n const hasPending = createReadonlyStore(() =>\n matchesId.get().some((matchId) => {\n const store = matchStores.get(matchId)\n return store?.get().status === 'pending'\n }),\n )\n const matchRouteDeps = createReadonlyStore(() => ({\n locationHref: location.get().href,\n resolvedLocationHref: resolvedLocation.get()?.href,\n status: status.get(),\n }))\n\n // compatibility \"big\" state store\n const __store = createReadonlyStore(() => ({\n status: status.get(),\n loadedAt: loadedAt.get(),\n isLoading: isLoading.get(),\n isTransitioning: isTransitioning.get(),\n matches: matches.get(),\n location: location.get(),\n resolvedLocation: resolvedLocation.get(),\n statusCode: statusCode.get(),\n redirect: redirect.get(),\n }))\n\n // Per-routeId computed store cache.\n // Each entry resolves routeId → match state through the signal graph,\n // giving consumers a single store to subscribe to instead of the\n // two-level byRouteId → matchStore pattern.\n //\n // 64 max size is arbitrary, this is only for active matches anyway so\n // it should be plenty. And we already have a 32 limit due to route\n // matching bitmask anyway.\n const matchStoreByRouteIdCache = createLRUCache<\n string,\n RouterReadableStore<AnyRouteMatch | undefined>\n >(64)\n\n function getRouteMatchStore(\n routeId: string,\n ): RouterReadableStore<AnyRouteMatch | undefined> {\n let cached = matchStoreByRouteIdCache.get(routeId)\n if (!cached) {\n cached = createReadonlyStore(() => {\n // Reading matchesId.get() tracks it as a dependency.\n // When matchesId changes (navigation), this computed re-evaluates.\n const ids = matchesId.get()\n for (const id of ids) {\n const matchStore = matchStores.get(id)\n if (matchStore && matchStore.routeId === routeId) {\n // Reading matchStore.get() tracks it as a dependency.\n // When the match store's state changes, this re-evaluates.\n return matchStore.get()\n }\n }\n return undefined\n })\n matchStoreByRouteIdCache.set(routeId, cached)\n }\n return cached\n }\n\n const store = {\n // atoms\n status,\n loadedAt,\n isLoading,\n isTransitioning,\n location,\n resolvedLocation,\n statusCode,\n redirect,\n matchesId,\n pendingIds,\n cachedIds,\n\n // derived\n matches,\n pendingMatches,\n cachedMatches,\n firstId,\n hasPending,\n matchRouteDeps,\n\n // non-reactive state\n matchStores,\n pendingMatchStores,\n cachedMatchStores,\n\n // compatibility \"big\" state\n __store,\n\n // per-key computed stores\n getRouteMatchStore,\n\n // methods\n setMatches,\n setPending,\n setCached,\n }\n\n // initialize the active matches\n setMatches(initialState.matches as Array<AnyRouteMatch>)\n init?.(store)\n\n // setters to update non-reactive utilities in sync with the reactive stores\n function setMatches(nextMatches: Array<AnyRouteMatch>) {\n reconcileMatchPool(\n nextMatches,\n matchStores,\n matchesId,\n createMutableStore,\n batch,\n )\n }\n\n function setPending(nextMatches: Array<AnyRouteMatch>) {\n reconcileMatchPool(\n nextMatches,\n pendingMatchStores,\n pendingIds,\n createMutableStore,\n batch,\n )\n }\n\n function setCached(nextMatches: Array<AnyRouteMatch>) {\n reconcileMatchPool(\n nextMatches,\n cachedMatchStores,\n cachedIds,\n createMutableStore,\n batch,\n )\n }\n\n return store\n}\n\nfunction readPoolMatches(\n pool: Map<string, MatchStore>,\n ids: Array<string>,\n): Array<AnyRouteMatch> {\n const matches: Array<AnyRouteMatch> = []\n for (const id of ids) {\n const matchStore = pool.get(id)\n if (matchStore) {\n matches.push(matchStore.get())\n }\n }\n return matches\n}\n\nfunction reconcileMatchPool(\n nextMatches: Array<AnyRouteMatch>,\n pool: Map<string, MatchStore>,\n idStore: RouterWritableStore<Array<string>>,\n createMutableStore: MutableStoreFactory,\n batch: RouterBatchFn,\n): void {\n const nextIds = nextMatches.map((d) => d.id)\n const nextIdSet = new Set(nextIds)\n\n batch(() => {\n for (const id of pool.keys()) {\n if (!nextIdSet.has(id)) {\n pool.delete(id)\n }\n }\n\n for (const nextMatch of nextMatches) {\n const existing = pool.get(nextMatch.id)\n if (!existing) {\n const matchStore = createMutableStore(nextMatch) as MatchStore\n matchStore.routeId = nextMatch.routeId\n pool.set(nextMatch.id, matchStore)\n continue\n }\n\n existing.routeId = nextMatch.routeId\n if (existing.get() !== nextMatch) {\n existing.set(nextMatch)\n }\n }\n\n if (!arraysEqual(idStore.get(), nextIds)) {\n idStore.set(nextIds)\n }\n })\n}\n"],"mappings":"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"}