@tanstack/router-core 1.167.4 → 1.168.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/cjs/index.cjs +3 -0
  2. package/dist/cjs/index.d.cts +2 -0
  3. package/dist/cjs/load-matches.cjs +14 -9
  4. package/dist/cjs/load-matches.cjs.map +1 -1
  5. package/dist/cjs/router.cjs +135 -151
  6. package/dist/cjs/router.cjs.map +1 -1
  7. package/dist/cjs/router.d.cts +16 -10
  8. package/dist/cjs/scroll-restoration.cjs +5 -4
  9. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  10. package/dist/cjs/ssr/createRequestHandler.cjs +2 -2
  11. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  12. package/dist/cjs/ssr/ssr-client.cjs +14 -17
  13. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  14. package/dist/cjs/ssr/ssr-server.cjs +1 -1
  15. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  16. package/dist/cjs/stores.cjs +148 -0
  17. package/dist/cjs/stores.cjs.map +1 -0
  18. package/dist/cjs/stores.d.cts +70 -0
  19. package/dist/cjs/utils.cjs +7 -0
  20. package/dist/cjs/utils.cjs.map +1 -1
  21. package/dist/cjs/utils.d.cts +1 -0
  22. package/dist/esm/index.d.ts +2 -0
  23. package/dist/esm/index.js +2 -1
  24. package/dist/esm/load-matches.js +14 -9
  25. package/dist/esm/load-matches.js.map +1 -1
  26. package/dist/esm/router.d.ts +16 -10
  27. package/dist/esm/router.js +135 -151
  28. package/dist/esm/router.js.map +1 -1
  29. package/dist/esm/scroll-restoration.js +5 -4
  30. package/dist/esm/scroll-restoration.js.map +1 -1
  31. package/dist/esm/ssr/createRequestHandler.js +2 -2
  32. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  33. package/dist/esm/ssr/ssr-client.js +14 -17
  34. package/dist/esm/ssr/ssr-client.js.map +1 -1
  35. package/dist/esm/ssr/ssr-server.js +1 -1
  36. package/dist/esm/ssr/ssr-server.js.map +1 -1
  37. package/dist/esm/stores.d.ts +70 -0
  38. package/dist/esm/stores.js +146 -0
  39. package/dist/esm/stores.js.map +1 -0
  40. package/dist/esm/utils.d.ts +1 -0
  41. package/dist/esm/utils.js +7 -1
  42. package/dist/esm/utils.js.map +1 -1
  43. package/package.json +3 -3
  44. package/src/index.ts +11 -0
  45. package/src/load-matches.ts +23 -11
  46. package/src/router.ts +238 -252
  47. package/src/scroll-restoration.ts +6 -5
  48. package/src/ssr/createRequestHandler.ts +5 -4
  49. package/src/ssr/ssr-client.ts +17 -18
  50. package/src/ssr/ssr-server.ts +1 -1
  51. package/src/stores.ts +342 -0
  52. package/src/utils.ts +9 -0
  53. package/dist/cjs/utils/batch.cjs +0 -16
  54. package/dist/cjs/utils/batch.cjs.map +0 -1
  55. package/dist/cjs/utils/batch.d.cts +0 -1
  56. package/dist/esm/utils/batch.d.ts +0 -1
  57. package/dist/esm/utils/batch.js +0 -15
  58. package/dist/esm/utils/batch.js.map +0 -1
  59. package/src/utils/batch.ts +0 -18
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stores.cjs","names":[],"sources":["../../src/stores.ts"],"sourcesContent":["import { createLRUCache } from './lru-cache'\nimport { arraysEqual } 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 readonly state: TValue\n}\n\nexport interface RouterWritableStore<\n TValue,\n> extends RouterReadableStore<TValue> {\n setState: (updater: (prev: TValue) => 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 state() {\n return value\n },\n setState(updater: (prev: TValue) => TValue) {\n value = updater(value)\n },\n }\n}\n\n/** SSR non-reactive createReadonlyStore */\nexport function createNonReactiveReadonlyStore<TValue>(\n read: () => TValue,\n): RouterReadableStore<TValue> {\n return {\n get state() {\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 pendingMatchesId: RouterWritableStore<Array<string>>\n /** @internal */\n cachedMatchesId: RouterWritableStore<Array<string>>\n activeMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>\n pendingMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>\n cachedMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>\n firstMatchId: ReadableStore<string | undefined>\n hasPendingMatches: ReadableStore<boolean>\n matchRouteReactivity: ReadableStore<{\n locationHref: string\n resolvedLocationHref: string | undefined\n status: RouterState<TRouteTree>['status']\n }>\n __store: RouterReadableStore<RouterState<TRouteTree>>\n\n activeMatchStoresById: Map<string, MatchStore>\n pendingMatchStoresById: Map<string, MatchStore>\n cachedMatchStoresById: 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 getMatchStoreByRouteId: (\n routeId: string,\n ) => RouterReadableStore<AnyRouteMatch | undefined>\n\n setActiveMatches: (nextMatches: Array<AnyRouteMatch>) => void\n setPendingMatches: (nextMatches: Array<AnyRouteMatch>) => void\n setCachedMatches: (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 activeMatchStoresById = new Map<string, MatchStore>()\n const pendingMatchStoresById = new Map<string, MatchStore>()\n const cachedMatchStoresById = 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 pendingMatchesId = createMutableStore<Array<string>>([])\n const cachedMatchesId = createMutableStore<Array<string>>([])\n\n // 1st order derived stores\n const activeMatchesSnapshot = createReadonlyStore(() =>\n readPoolMatches(activeMatchStoresById, matchesId.state),\n )\n const pendingMatchesSnapshot = createReadonlyStore(() =>\n readPoolMatches(pendingMatchStoresById, pendingMatchesId.state),\n )\n const cachedMatchesSnapshot = createReadonlyStore(() =>\n readPoolMatches(cachedMatchStoresById, cachedMatchesId.state),\n )\n const firstMatchId = createReadonlyStore(() => matchesId.state[0])\n const hasPendingMatches = createReadonlyStore(() =>\n matchesId.state.some((matchId) => {\n const store = activeMatchStoresById.get(matchId)\n return store?.state.status === 'pending'\n }),\n )\n const matchRouteReactivity = createReadonlyStore(() => ({\n locationHref: location.state.href,\n resolvedLocationHref: resolvedLocation.state?.href,\n status: status.state,\n }))\n\n // compatibility \"big\" state store\n const __store = createReadonlyStore(() => ({\n status: status.state,\n loadedAt: loadedAt.state,\n isLoading: isLoading.state,\n isTransitioning: isTransitioning.state,\n matches: activeMatchesSnapshot.state,\n location: location.state,\n resolvedLocation: resolvedLocation.state,\n statusCode: statusCode.state,\n redirect: redirect.state,\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 getMatchStoreByRouteId(\n routeId: string,\n ): RouterReadableStore<AnyRouteMatch | undefined> {\n let cached = matchStoreByRouteIdCache.get(routeId)\n if (!cached) {\n cached = createReadonlyStore(() => {\n // Reading matchesId.state tracks it as a dependency.\n // When matchesId changes (navigation), this computed re-evaluates.\n const ids = matchesId.state\n for (const id of ids) {\n const matchStore = activeMatchStoresById.get(id)\n if (matchStore && matchStore.routeId === routeId) {\n // Reading matchStore.state tracks it as a dependency.\n // When the match store's state changes, this re-evaluates.\n return matchStore.state\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 pendingMatchesId,\n cachedMatchesId,\n\n // derived\n activeMatchesSnapshot,\n pendingMatchesSnapshot,\n cachedMatchesSnapshot,\n firstMatchId,\n hasPendingMatches,\n matchRouteReactivity,\n\n // non-reactive state\n activeMatchStoresById,\n pendingMatchStoresById,\n cachedMatchStoresById,\n\n // compatibility \"big\" state\n __store,\n\n // per-key computed stores\n getMatchStoreByRouteId,\n\n // methods\n setActiveMatches,\n setPendingMatches,\n setCachedMatches,\n }\n\n // initialize the active matches\n setActiveMatches(initialState.matches as Array<AnyRouteMatch>)\n init?.(store)\n\n // setters to update non-reactive utilities in sync with the reactive stores\n function setActiveMatches(nextMatches: Array<AnyRouteMatch>) {\n reconcileMatchPool(\n nextMatches,\n activeMatchStoresById,\n matchesId,\n createMutableStore,\n batch,\n )\n }\n\n function setPendingMatches(nextMatches: Array<AnyRouteMatch>) {\n reconcileMatchPool(\n nextMatches,\n pendingMatchStoresById,\n pendingMatchesId,\n createMutableStore,\n batch,\n )\n }\n\n function setCachedMatches(nextMatches: Array<AnyRouteMatch>) {\n reconcileMatchPool(\n nextMatches,\n cachedMatchStoresById,\n cachedMatchesId,\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.state)\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.state !== nextMatch) {\n existing.setState(() => nextMatch)\n }\n }\n\n if (!arraysEqual(idStore.state, nextIds)) {\n idStore.setState(() => nextIds)\n }\n })\n}\n"],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import { AnyRoute } from './route.cjs';
2
+ import { RouterState } from './router.cjs';
3
+ import { FullSearchSchema } from './routeInfo.cjs';
4
+ import { ParsedLocation } from './location.cjs';
5
+ import { AnyRedirect } from './redirect.cjs';
6
+ import { AnyRouteMatch } from './Matches.cjs';
7
+ export interface RouterReadableStore<TValue> {
8
+ readonly state: TValue;
9
+ }
10
+ export interface RouterWritableStore<TValue> extends RouterReadableStore<TValue> {
11
+ setState: (updater: (prev: TValue) => TValue) => void;
12
+ }
13
+ export type RouterBatchFn = (fn: () => void) => void;
14
+ export type MutableStoreFactory = <TValue>(initialValue: TValue) => RouterWritableStore<TValue>;
15
+ export type ReadonlyStoreFactory = <TValue>(read: () => TValue) => RouterReadableStore<TValue>;
16
+ export type GetStoreConfig = (opts: {
17
+ isServer?: boolean;
18
+ }) => StoreConfig;
19
+ export type StoreConfig = {
20
+ createMutableStore: MutableStoreFactory;
21
+ createReadonlyStore: ReadonlyStoreFactory;
22
+ batch: RouterBatchFn;
23
+ init?: (stores: RouterStores<AnyRoute>) => void;
24
+ };
25
+ type MatchStore = RouterWritableStore<AnyRouteMatch> & {
26
+ routeId?: string;
27
+ };
28
+ type ReadableStore<TValue> = RouterReadableStore<TValue>;
29
+ /** SSR non-reactive createMutableStore */
30
+ export declare function createNonReactiveMutableStore<TValue>(initialValue: TValue): RouterWritableStore<TValue>;
31
+ /** SSR non-reactive createReadonlyStore */
32
+ export declare function createNonReactiveReadonlyStore<TValue>(read: () => TValue): RouterReadableStore<TValue>;
33
+ export interface RouterStores<in out TRouteTree extends AnyRoute> {
34
+ status: RouterWritableStore<RouterState<TRouteTree>['status']>;
35
+ loadedAt: RouterWritableStore<number>;
36
+ isLoading: RouterWritableStore<boolean>;
37
+ isTransitioning: RouterWritableStore<boolean>;
38
+ location: RouterWritableStore<ParsedLocation<FullSearchSchema<TRouteTree>>>;
39
+ resolvedLocation: RouterWritableStore<ParsedLocation<FullSearchSchema<TRouteTree>> | undefined>;
40
+ statusCode: RouterWritableStore<number>;
41
+ redirect: RouterWritableStore<AnyRedirect | undefined>;
42
+ matchesId: RouterWritableStore<Array<string>>;
43
+ pendingMatchesId: RouterWritableStore<Array<string>>;
44
+ activeMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>;
45
+ pendingMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>;
46
+ cachedMatchesSnapshot: ReadableStore<Array<AnyRouteMatch>>;
47
+ firstMatchId: ReadableStore<string | undefined>;
48
+ hasPendingMatches: ReadableStore<boolean>;
49
+ matchRouteReactivity: ReadableStore<{
50
+ locationHref: string;
51
+ resolvedLocationHref: string | undefined;
52
+ status: RouterState<TRouteTree>['status'];
53
+ }>;
54
+ __store: RouterReadableStore<RouterState<TRouteTree>>;
55
+ activeMatchStoresById: Map<string, MatchStore>;
56
+ pendingMatchStoresById: Map<string, MatchStore>;
57
+ cachedMatchStoresById: Map<string, MatchStore>;
58
+ /**
59
+ * Get a computed store that resolves a routeId to its current match state.
60
+ * Returns the same cached store instance for repeated calls with the same key.
61
+ * The computed depends on matchesId + the individual match store, so
62
+ * subscribers are only notified when the resolved match state changes.
63
+ */
64
+ getMatchStoreByRouteId: (routeId: string) => RouterReadableStore<AnyRouteMatch | undefined>;
65
+ setActiveMatches: (nextMatches: Array<AnyRouteMatch>) => void;
66
+ setPendingMatches: (nextMatches: Array<AnyRouteMatch>) => void;
67
+ setCachedMatches: (nextMatches: Array<AnyRouteMatch>) => void;
68
+ }
69
+ export declare function createRouterStores<TRouteTree extends AnyRoute>(initialState: RouterState<TRouteTree>, config: StoreConfig): RouterStores<TRouteTree>;
70
+ export {};
@@ -311,8 +311,15 @@ function buildDevStylesUrl(basepath, routeIds) {
311
311
  const trimmedBasepath = basepath.replace(/^\/+|\/+$/g, "");
312
312
  return `${trimmedBasepath === "" ? "" : `/${trimmedBasepath}`}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(","))}`;
313
313
  }
314
+ function arraysEqual(a, b) {
315
+ if (a === b) return true;
316
+ if (a.length !== b.length) return false;
317
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
318
+ return true;
319
+ }
314
320
  //#endregion
315
321
  exports.DEFAULT_PROTOCOL_ALLOWLIST = DEFAULT_PROTOCOL_ALLOWLIST;
322
+ exports.arraysEqual = arraysEqual;
316
323
  exports.buildDevStylesUrl = buildDevStylesUrl;
317
324
  exports.createControlledPromise = createControlledPromise;
318
325
  exports.decodePath = decodePath;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs","names":[],"sources":["../../src/utils.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport type { RouteIds } from './routeInfo'\nimport type { AnyRouter } from './router'\n\nexport type Awaitable<T> = T | Promise<T>\nexport type NoInfer<T> = [T][T extends any ? 0 : never]\nexport type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue\n ? TYesResult\n : TNoResult\n\nexport type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<\n TValue,\n TKey\n> &\n Required<Pick<TValue, TKey>>\n\nexport type PickRequired<T> = {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\nexport type PickOptional<T> = {\n [K in keyof T as undefined extends T[K] ? K : never]: T[K]\n}\n\n// from https://stackoverflow.com/a/76458160\nexport type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never\n\nexport type Expand<T> = T extends object\n ? T extends infer O\n ? O extends Function\n ? O\n : { [K in keyof O]: O[K] }\n : never\n : T\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n\nexport type MakeDifferenceOptional<TLeft, TRight> = keyof TLeft &\n keyof TRight extends never\n ? TRight\n : Omit<TRight, keyof TLeft & keyof TRight> & {\n [K in keyof TLeft & keyof TRight]?: TRight[K]\n }\n\n// from https://stackoverflow.com/a/53955431\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport type IsUnion<T, U extends T = T> = (\n T extends any ? (U extends T ? false : true) : never\n) extends false\n ? false\n : true\n\nexport type IsNonEmptyObject<T> = T extends object\n ? keyof T extends never\n ? false\n : true\n : false\n\nexport type Assign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : keyof TLeft & keyof TRight extends never\n ? TLeft & TRight\n : Omit<TLeft, keyof TRight> & TRight\n : never\n : never\n\nexport type IntersectAssign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : TRight & TLeft\n : never\n : never\n\nexport type Timeout = ReturnType<typeof setTimeout>\n\nexport type Updater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev?: TPrevious) => TResult)\n\nexport type NonNullableUpdater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev: TPrevious) => TResult)\n\nexport type ExtractObjects<TUnion> = TUnion extends MergeAllPrimitive\n ? never\n : TUnion\n\nexport type PartialMergeAllObject<TUnion> =\n ExtractObjects<TUnion> extends infer TObj\n ? [TObj] extends [never]\n ? never\n : {\n [TKey in TObj extends any ? keyof TObj : never]?: TObj extends any\n ? TKey extends keyof TObj\n ? TObj[TKey]\n : never\n : never\n }\n : never\n\nexport type MergeAllPrimitive =\n | ReadonlyArray<any>\n | number\n | string\n | bigint\n | boolean\n | symbol\n | undefined\n | null\n\nexport type ExtractPrimitives<TUnion> = TUnion extends MergeAllPrimitive\n ? TUnion\n : TUnion extends object\n ? never\n : TUnion\n\nexport type PartialMergeAll<TUnion> =\n | ExtractPrimitives<TUnion>\n | PartialMergeAllObject<TUnion>\n\nexport type Constrain<T, TConstraint, TDefault = TConstraint> =\n | (T extends TConstraint ? T : never)\n | TDefault\n\nexport type ConstrainLiteral<T, TConstraint, TDefault = TConstraint> =\n | (T & TConstraint)\n | TDefault\n\n/**\n * To be added to router types\n */\nexport type UnionToIntersection<T> = (\n T extends any ? (arg: T) => any : never\n) extends (arg: infer T) => any\n ? T\n : never\n\n/**\n * Merges everything in a union into one object.\n * This mapped type is homomorphic which means it preserves stuff! :)\n */\nexport type MergeAllObjects<\n TUnion,\n TIntersected = UnionToIntersection<ExtractObjects<TUnion>>,\n> = [keyof TIntersected] extends [never]\n ? never\n : {\n [TKey in keyof TIntersected]: TUnion extends any\n ? TUnion[TKey & keyof TUnion]\n : never\n }\n\nexport type MergeAll<TUnion> =\n | MergeAllObjects<TUnion>\n | ExtractPrimitives<TUnion>\n\nexport type ValidateJSON<T> = ((...args: Array<any>) => any) extends T\n ? unknown extends T\n ? never\n : 'Function is not serializable'\n : { [K in keyof T]: ValidateJSON<T[K]> }\n\nexport type LooseReturnType<T> = T extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn\n : never\n\nexport type LooseAsyncReturnType<T> = T extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn extends Promise<infer TReturn>\n ? TReturn\n : TReturn\n : never\n\n/**\n * Return the last element of an array.\n * Intended for non-empty arrays used within router internals.\n */\nexport function last<T>(arr: ReadonlyArray<T>) {\n return arr[arr.length - 1]\n}\n\nfunction isFunction(d: any): d is Function {\n return typeof d === 'function'\n}\n\n/**\n * Apply a value-or-updater to a previous value.\n * Accepts either a literal value or a function of the previous value.\n */\nexport function functionalUpdate<TPrevious, TResult = TPrevious>(\n updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>,\n previous: TPrevious,\n): TResult {\n if (isFunction(updater)) {\n return updater(previous)\n }\n\n return updater\n}\n\nconst hasOwn = Object.prototype.hasOwnProperty\nconst isEnumerable = Object.prototype.propertyIsEnumerable\n\nconst createNull = () => Object.create(null)\nexport const nullReplaceEqualDeep: typeof replaceEqualDeep = (prev, next) =>\n replaceEqualDeep(prev, next, createNull)\n\n/**\n * This function returns `prev` if `_next` is deeply equal.\n * If not, it will replace any deeply equal children of `b` with those of `a`.\n * This can be used for structural sharing between immutable JSON values for example.\n * Do not use this with signals\n */\nexport function replaceEqualDeep<T>(\n prev: any,\n _next: T,\n _makeObj = () => ({}),\n _depth = 0,\n): T {\n if (isServer) {\n return _next\n }\n if (prev === _next) {\n return prev\n }\n\n if (_depth > 500) return _next\n\n const next = _next as any\n\n const array = isPlainArray(prev) && isPlainArray(next)\n\n if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next\n\n const prevItems = array ? prev : getEnumerableOwnKeys(prev)\n if (!prevItems) return next\n const nextItems = array ? next : getEnumerableOwnKeys(next)\n if (!nextItems) return next\n const prevSize = prevItems.length\n const nextSize = nextItems.length\n const copy: any = array ? new Array(nextSize) : _makeObj()\n\n let equalItems = 0\n\n for (let i = 0; i < nextSize; i++) {\n const key = array ? i : (nextItems[i] as any)\n const p = prev[key]\n const n = next[key]\n\n if (p === n) {\n copy[key] = p\n if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++\n continue\n }\n\n if (\n p === null ||\n n === null ||\n typeof p !== 'object' ||\n typeof n !== 'object'\n ) {\n copy[key] = n\n continue\n }\n\n const v = replaceEqualDeep(p, n, _makeObj, _depth + 1)\n copy[key] = v\n if (v === p) equalItems++\n }\n\n return prevSize === nextSize && equalItems === prevSize ? prev : copy\n}\n\n/**\n * Equivalent to `Reflect.ownKeys`, but ensures that objects are \"clone-friendly\":\n * will return false if object has any non-enumerable properties.\n *\n * Optimized for the common case where objects have no symbol properties.\n */\nfunction getEnumerableOwnKeys(o: object) {\n const names = Object.getOwnPropertyNames(o)\n\n // Fast path: check all string property names are enumerable\n for (const name of names) {\n if (!isEnumerable.call(o, name)) return false\n }\n\n // Only check symbols if the object has any (most plain objects don't)\n const symbols = Object.getOwnPropertySymbols(o)\n\n // Fast path: no symbols, return names directly (avoids array allocation/concat)\n if (symbols.length === 0) return names\n\n // Slow path: has symbols, need to check and merge\n const keys: Array<string | symbol> = names\n for (const symbol of symbols) {\n if (!isEnumerable.call(o, symbol)) return false\n keys.push(symbol)\n }\n return keys\n}\n\n// Copied from: https://github.com/jonschlinkert/is-plain-object\nexport function isPlainObject(o: any) {\n if (!hasObjectPrototype(o)) {\n return false\n }\n\n // If has modified constructor\n const ctor = o.constructor\n if (typeof ctor === 'undefined') {\n return true\n }\n\n // If has modified prototype\n const prot = ctor.prototype\n if (!hasObjectPrototype(prot)) {\n return false\n }\n\n // If constructor does not have an Object-specific method\n if (!prot.hasOwnProperty('isPrototypeOf')) {\n return false\n }\n\n // Most likely a plain Object\n return true\n}\n\nfunction hasObjectPrototype(o: any) {\n return Object.prototype.toString.call(o) === '[object Object]'\n}\n\n/**\n * Check if a value is a \"plain\" array (no extra enumerable keys).\n */\nexport function isPlainArray(value: unknown): value is Array<unknown> {\n return Array.isArray(value) && value.length === Object.keys(value).length\n}\n\n/**\n * Perform a deep equality check with options for partial comparison and\n * ignoring `undefined` values. Optimized for router state comparisons.\n */\nexport function deepEqual(\n a: any,\n b: any,\n opts?: { partial?: boolean; ignoreUndefined?: boolean },\n): boolean {\n if (a === b) {\n return true\n }\n\n if (typeof a !== typeof b) {\n return false\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let i = 0, l = a.length; i < l; i++) {\n if (!deepEqual(a[i], b[i], opts)) return false\n }\n return true\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const ignoreUndefined = opts?.ignoreUndefined ?? true\n\n if (opts?.partial) {\n for (const k in b) {\n if (!ignoreUndefined || b[k] !== undefined) {\n if (!deepEqual(a[k], b[k], opts)) return false\n }\n }\n return true\n }\n\n let aCount = 0\n if (!ignoreUndefined) {\n aCount = Object.keys(a).length\n } else {\n for (const k in a) {\n if (a[k] !== undefined) aCount++\n }\n }\n\n let bCount = 0\n for (const k in b) {\n if (!ignoreUndefined || b[k] !== undefined) {\n bCount++\n if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false\n }\n }\n\n return aCount === bCount\n }\n\n return false\n}\n\nexport type StringLiteral<T> = T extends string\n ? string extends T\n ? string\n : T\n : never\n\nexport type ThrowOrOptional<T, TThrow extends boolean> = TThrow extends true\n ? T\n : T | undefined\n\nexport type StrictOrFrom<\n TRouter extends AnyRouter,\n TFrom,\n TStrict extends boolean = true,\n> = TStrict extends false\n ? {\n from?: never\n strict: TStrict\n }\n : {\n from: ConstrainLiteral<TFrom, RouteIds<TRouter['routeTree']>>\n strict?: TStrict\n }\n\nexport type ThrowConstraint<\n TStrict extends boolean,\n TThrow extends boolean,\n> = TStrict extends false ? (TThrow extends true ? never : TThrow) : TThrow\n\nexport type ControlledPromise<T> = Promise<T> & {\n resolve: (value: T) => void\n reject: (value: any) => void\n status: 'pending' | 'resolved' | 'rejected'\n value?: T\n}\n\n/**\n * Create a promise with exposed resolve/reject and status fields.\n * Useful for coordinating async router lifecycle operations.\n */\nexport function createControlledPromise<T>(onResolve?: (value: T) => void) {\n let resolveLoadPromise!: (value: T) => void\n let rejectLoadPromise!: (value: any) => void\n\n const controlledPromise = new Promise<T>((resolve, reject) => {\n resolveLoadPromise = resolve\n rejectLoadPromise = reject\n }) as ControlledPromise<T>\n\n controlledPromise.status = 'pending'\n\n controlledPromise.resolve = (value: T) => {\n controlledPromise.status = 'resolved'\n controlledPromise.value = value\n resolveLoadPromise(value)\n onResolve?.(value)\n }\n\n controlledPromise.reject = (e) => {\n controlledPromise.status = 'rejected'\n rejectLoadPromise(e)\n }\n\n return controlledPromise\n}\n\n/**\n * Heuristically detect dynamic import \"module not found\" errors\n * across major browsers for lazy route component handling.\n */\nexport function isModuleNotFoundError(error: any): boolean {\n // chrome: \"Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split\"\n // firefox: \"error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split\"\n // safari: \"Importing a module script failed.\"\n if (typeof error?.message !== 'string') return false\n return (\n error.message.startsWith('Failed to fetch dynamically imported module') ||\n error.message.startsWith('error loading dynamically imported module') ||\n error.message.startsWith('Importing a module script failed')\n )\n}\n\nexport function isPromise<T>(\n value: Promise<Awaited<T>> | T,\n): value is Promise<Awaited<T>> {\n return Boolean(\n value &&\n typeof value === 'object' &&\n typeof (value as Promise<T>).then === 'function',\n )\n}\n\nexport function findLast<T>(\n array: ReadonlyArray<T>,\n predicate: (item: T) => boolean,\n): T | undefined {\n for (let i = array.length - 1; i >= 0; i--) {\n const item = array[i]!\n if (predicate(item)) return item\n }\n return undefined\n}\n\n/**\n * Remove control characters that can cause open redirect vulnerabilities.\n * Characters like \\r (CR) and \\n (LF) can trick URL parsers into interpreting\n * paths like \"/\\r/evil.com\" as \"http://evil.com\".\n */\nfunction sanitizePathSegment(segment: string): string {\n // Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)\n // These include CR (\\r = 0x0D), LF (\\n = 0x0A), and other potentially dangerous characters\n // eslint-disable-next-line no-control-regex\n return segment.replace(/[\\x00-\\x1f\\x7f]/g, '')\n}\n\nfunction decodeSegment(segment: string): string {\n let decoded: string\n try {\n decoded = decodeURI(segment)\n } catch {\n // if the decoding fails, try to decode the various parts leaving the malformed tags in place\n decoded = segment.replaceAll(/%[0-9A-F]{2}/gi, (match) => {\n try {\n return decodeURI(match)\n } catch {\n return match\n }\n })\n }\n return sanitizePathSegment(decoded)\n}\n\n/**\n * Default list of URL protocols to allow in links, redirects, and navigation.\n * Any absolute URL protocol not in this list is treated as dangerous by default.\n */\nexport const DEFAULT_PROTOCOL_ALLOWLIST = [\n // Standard web navigation\n 'http:',\n 'https:',\n\n // Common browser-safe actions\n 'mailto:',\n 'tel:',\n]\n\n/**\n * Check if a URL string uses a protocol that is not in the allowlist.\n * Returns true for blocked protocols like javascript:, blob:, data:, etc.\n *\n * The URL constructor correctly normalizes:\n * - Mixed case (JavaScript: → javascript:)\n * - Whitespace/control characters (java\\nscript: → javascript:)\n * - Leading whitespace\n *\n * For relative URLs (no protocol), returns false (safe).\n *\n * @param url - The URL string to check\n * @param allowlist - Set of protocols to allow\n * @returns true if the URL uses a protocol that is not allowed\n */\nexport function isDangerousProtocol(\n url: string,\n allowlist: Set<string>,\n): boolean {\n if (!url) return false\n\n try {\n // Use the URL constructor - it correctly normalizes protocols\n // per WHATWG URL spec, handling all bypass attempts automatically\n const parsed = new URL(url)\n return !allowlist.has(parsed.protocol)\n } catch {\n // URL constructor throws for relative URLs (no protocol)\n // These are safe - they can't execute scripts\n return false\n }\n}\n\n// This utility is based on https://github.com/zertosh/htmlescape\n// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE\nconst HTML_ESCAPE_LOOKUP: { [match: string]: string } = {\n '&': '\\\\u0026',\n '>': '\\\\u003e',\n '<': '\\\\u003c',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n}\n\nconst HTML_ESCAPE_REGEX = /[&><\\u2028\\u2029]/g\n\n/**\n * Escape HTML special characters in a string to prevent XSS attacks\n * when embedding strings in script tags during SSR.\n *\n * This is essential for preventing XSS vulnerabilities when user-controlled\n * content is embedded in inline scripts.\n */\nexport function escapeHtml(str: string): string {\n return str.replace(HTML_ESCAPE_REGEX, (match) => HTML_ESCAPE_LOOKUP[match]!)\n}\n\nexport function decodePath(path: string) {\n if (!path) return { path, handledProtocolRelativeURL: false }\n\n // Fast path: most paths are already decoded and safe.\n // Only fall back to the slower scan/regex path when we see a '%' (encoded),\n // a backslash (explicitly handled), a control character, or a protocol-relative\n // prefix which needs collapsing.\n // eslint-disable-next-line no-control-regex\n if (!/[%\\\\\\x00-\\x1f\\x7f]/.test(path) && !path.startsWith('//')) {\n return { path, handledProtocolRelativeURL: false }\n }\n\n const re = /%25|%5C/gi\n let cursor = 0\n let result = ''\n let match\n while (null !== (match = re.exec(path))) {\n result += decodeSegment(path.slice(cursor, match.index)) + match[0]\n cursor = re.lastIndex\n }\n result = result + decodeSegment(cursor ? path.slice(cursor) : path)\n\n // Prevent open redirect via protocol-relative URLs (e.g. \"//evil.com\")\n // After sanitizing control characters, paths like \"/\\r/evil.com\" become \"//evil.com\"\n // Collapse leading double slashes to a single slash\n let handledProtocolRelativeURL = false\n if (result.startsWith('//')) {\n handledProtocolRelativeURL = true\n result = '/' + result.replace(/^\\/+/, '')\n }\n\n return { path: result, handledProtocolRelativeURL }\n}\n\n/**\n * Encodes a path the same way `new URL()` would, but without the overhead of full URL parsing.\n *\n * This function encodes:\n * - Whitespace characters (spaces → %20, tabs → %09, etc.)\n * - Non-ASCII/Unicode characters (emojis, accented characters, etc.)\n *\n * It preserves:\n * - Already percent-encoded sequences (won't double-encode %2F, %25, etc.)\n * - ASCII special characters valid in URL paths (@, $, &, +, etc.)\n * - Forward slashes as path separators\n *\n * Used to generate proper href values for SSR without constructing URL objects.\n *\n * @example\n * encodePathLikeUrl('/path/file name.pdf') // '/path/file%20name.pdf'\n * encodePathLikeUrl('/path/日本語') // '/path/%E6%97%A5%E6%9C%AC%E8%AA%9E'\n * encodePathLikeUrl('/path/already%20encoded') // '/path/already%20encoded' (preserved)\n */\nexport function encodePathLikeUrl(path: string): string {\n // Encode whitespace and non-ASCII characters that browsers encode in URLs\n\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check\n // eslint-disable-next-line no-control-regex\n if (!/\\s|[^\\u0000-\\u007F]/.test(path)) return path\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check\n // eslint-disable-next-line no-control-regex\n return path.replace(/\\s|[^\\u0000-\\u007F]/gu, encodeURIComponent)\n}\n\n/**\n * Builds the dev-mode CSS styles URL for route-scoped CSS collection.\n * Used by HeadContent components in all framework implementations to construct\n * the URL for the `/@tanstack-start/styles.css` endpoint.\n *\n * @param basepath - The router's basepath (may or may not have leading slash)\n * @param routeIds - Array of matched route IDs to include in the CSS collection\n * @returns The full URL path for the dev styles CSS endpoint\n */\nexport function buildDevStylesUrl(\n basepath: string,\n routeIds: Array<string>,\n): string {\n // Trim all leading and trailing slashes from basepath\n const trimmedBasepath = basepath.replace(/^\\/+|\\/+$/g, '')\n // Build normalized basepath: empty string for root, or '/path' for non-root\n const normalizedBasepath = trimmedBasepath === '' ? '' : `/${trimmedBasepath}`\n return `${normalizedBasepath}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.cjs","names":[],"sources":["../../src/utils.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport type { RouteIds } from './routeInfo'\nimport type { AnyRouter } from './router'\n\nexport type Awaitable<T> = T | Promise<T>\nexport type NoInfer<T> = [T][T extends any ? 0 : never]\nexport type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue\n ? TYesResult\n : TNoResult\n\nexport type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<\n TValue,\n TKey\n> &\n Required<Pick<TValue, TKey>>\n\nexport type PickRequired<T> = {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\nexport type PickOptional<T> = {\n [K in keyof T as undefined extends T[K] ? K : never]: T[K]\n}\n\n// from https://stackoverflow.com/a/76458160\nexport type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never\n\nexport type Expand<T> = T extends object\n ? T extends infer O\n ? O extends Function\n ? O\n : { [K in keyof O]: O[K] }\n : never\n : T\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n\nexport type MakeDifferenceOptional<TLeft, TRight> = keyof TLeft &\n keyof TRight extends never\n ? TRight\n : Omit<TRight, keyof TLeft & keyof TRight> & {\n [K in keyof TLeft & keyof TRight]?: TRight[K]\n }\n\n// from https://stackoverflow.com/a/53955431\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport type IsUnion<T, U extends T = T> = (\n T extends any ? (U extends T ? false : true) : never\n) extends false\n ? false\n : true\n\nexport type IsNonEmptyObject<T> = T extends object\n ? keyof T extends never\n ? false\n : true\n : false\n\nexport type Assign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : keyof TLeft & keyof TRight extends never\n ? TLeft & TRight\n : Omit<TLeft, keyof TRight> & TRight\n : never\n : never\n\nexport type IntersectAssign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : TRight & TLeft\n : never\n : never\n\nexport type Timeout = ReturnType<typeof setTimeout>\n\nexport type Updater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev?: TPrevious) => TResult)\n\nexport type NonNullableUpdater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev: TPrevious) => TResult)\n\nexport type ExtractObjects<TUnion> = TUnion extends MergeAllPrimitive\n ? never\n : TUnion\n\nexport type PartialMergeAllObject<TUnion> =\n ExtractObjects<TUnion> extends infer TObj\n ? [TObj] extends [never]\n ? never\n : {\n [TKey in TObj extends any ? keyof TObj : never]?: TObj extends any\n ? TKey extends keyof TObj\n ? TObj[TKey]\n : never\n : never\n }\n : never\n\nexport type MergeAllPrimitive =\n | ReadonlyArray<any>\n | number\n | string\n | bigint\n | boolean\n | symbol\n | undefined\n | null\n\nexport type ExtractPrimitives<TUnion> = TUnion extends MergeAllPrimitive\n ? TUnion\n : TUnion extends object\n ? never\n : TUnion\n\nexport type PartialMergeAll<TUnion> =\n | ExtractPrimitives<TUnion>\n | PartialMergeAllObject<TUnion>\n\nexport type Constrain<T, TConstraint, TDefault = TConstraint> =\n | (T extends TConstraint ? T : never)\n | TDefault\n\nexport type ConstrainLiteral<T, TConstraint, TDefault = TConstraint> =\n | (T & TConstraint)\n | TDefault\n\n/**\n * To be added to router types\n */\nexport type UnionToIntersection<T> = (\n T extends any ? (arg: T) => any : never\n) extends (arg: infer T) => any\n ? T\n : never\n\n/**\n * Merges everything in a union into one object.\n * This mapped type is homomorphic which means it preserves stuff! :)\n */\nexport type MergeAllObjects<\n TUnion,\n TIntersected = UnionToIntersection<ExtractObjects<TUnion>>,\n> = [keyof TIntersected] extends [never]\n ? never\n : {\n [TKey in keyof TIntersected]: TUnion extends any\n ? TUnion[TKey & keyof TUnion]\n : never\n }\n\nexport type MergeAll<TUnion> =\n | MergeAllObjects<TUnion>\n | ExtractPrimitives<TUnion>\n\nexport type ValidateJSON<T> = ((...args: Array<any>) => any) extends T\n ? unknown extends T\n ? never\n : 'Function is not serializable'\n : { [K in keyof T]: ValidateJSON<T[K]> }\n\nexport type LooseReturnType<T> = T extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn\n : never\n\nexport type LooseAsyncReturnType<T> = T extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn extends Promise<infer TReturn>\n ? TReturn\n : TReturn\n : never\n\n/**\n * Return the last element of an array.\n * Intended for non-empty arrays used within router internals.\n */\nexport function last<T>(arr: ReadonlyArray<T>) {\n return arr[arr.length - 1]\n}\n\nfunction isFunction(d: any): d is Function {\n return typeof d === 'function'\n}\n\n/**\n * Apply a value-or-updater to a previous value.\n * Accepts either a literal value or a function of the previous value.\n */\nexport function functionalUpdate<TPrevious, TResult = TPrevious>(\n updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>,\n previous: TPrevious,\n): TResult {\n if (isFunction(updater)) {\n return updater(previous)\n }\n\n return updater\n}\n\nconst hasOwn = Object.prototype.hasOwnProperty\nconst isEnumerable = Object.prototype.propertyIsEnumerable\n\nconst createNull = () => Object.create(null)\nexport const nullReplaceEqualDeep: typeof replaceEqualDeep = (prev, next) =>\n replaceEqualDeep(prev, next, createNull)\n\n/**\n * This function returns `prev` if `_next` is deeply equal.\n * If not, it will replace any deeply equal children of `b` with those of `a`.\n * This can be used for structural sharing between immutable JSON values for example.\n * Do not use this with signals\n */\nexport function replaceEqualDeep<T>(\n prev: any,\n _next: T,\n _makeObj = () => ({}),\n _depth = 0,\n): T {\n if (isServer) {\n return _next\n }\n if (prev === _next) {\n return prev\n }\n\n if (_depth > 500) return _next\n\n const next = _next as any\n\n const array = isPlainArray(prev) && isPlainArray(next)\n\n if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next\n\n const prevItems = array ? prev : getEnumerableOwnKeys(prev)\n if (!prevItems) return next\n const nextItems = array ? next : getEnumerableOwnKeys(next)\n if (!nextItems) return next\n const prevSize = prevItems.length\n const nextSize = nextItems.length\n const copy: any = array ? new Array(nextSize) : _makeObj()\n\n let equalItems = 0\n\n for (let i = 0; i < nextSize; i++) {\n const key = array ? i : (nextItems[i] as any)\n const p = prev[key]\n const n = next[key]\n\n if (p === n) {\n copy[key] = p\n if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++\n continue\n }\n\n if (\n p === null ||\n n === null ||\n typeof p !== 'object' ||\n typeof n !== 'object'\n ) {\n copy[key] = n\n continue\n }\n\n const v = replaceEqualDeep(p, n, _makeObj, _depth + 1)\n copy[key] = v\n if (v === p) equalItems++\n }\n\n return prevSize === nextSize && equalItems === prevSize ? prev : copy\n}\n\n/**\n * Equivalent to `Reflect.ownKeys`, but ensures that objects are \"clone-friendly\":\n * will return false if object has any non-enumerable properties.\n *\n * Optimized for the common case where objects have no symbol properties.\n */\nfunction getEnumerableOwnKeys(o: object) {\n const names = Object.getOwnPropertyNames(o)\n\n // Fast path: check all string property names are enumerable\n for (const name of names) {\n if (!isEnumerable.call(o, name)) return false\n }\n\n // Only check symbols if the object has any (most plain objects don't)\n const symbols = Object.getOwnPropertySymbols(o)\n\n // Fast path: no symbols, return names directly (avoids array allocation/concat)\n if (symbols.length === 0) return names\n\n // Slow path: has symbols, need to check and merge\n const keys: Array<string | symbol> = names\n for (const symbol of symbols) {\n if (!isEnumerable.call(o, symbol)) return false\n keys.push(symbol)\n }\n return keys\n}\n\n// Copied from: https://github.com/jonschlinkert/is-plain-object\nexport function isPlainObject(o: any) {\n if (!hasObjectPrototype(o)) {\n return false\n }\n\n // If has modified constructor\n const ctor = o.constructor\n if (typeof ctor === 'undefined') {\n return true\n }\n\n // If has modified prototype\n const prot = ctor.prototype\n if (!hasObjectPrototype(prot)) {\n return false\n }\n\n // If constructor does not have an Object-specific method\n if (!prot.hasOwnProperty('isPrototypeOf')) {\n return false\n }\n\n // Most likely a plain Object\n return true\n}\n\nfunction hasObjectPrototype(o: any) {\n return Object.prototype.toString.call(o) === '[object Object]'\n}\n\n/**\n * Check if a value is a \"plain\" array (no extra enumerable keys).\n */\nexport function isPlainArray(value: unknown): value is Array<unknown> {\n return Array.isArray(value) && value.length === Object.keys(value).length\n}\n\n/**\n * Perform a deep equality check with options for partial comparison and\n * ignoring `undefined` values. Optimized for router state comparisons.\n */\nexport function deepEqual(\n a: any,\n b: any,\n opts?: { partial?: boolean; ignoreUndefined?: boolean },\n): boolean {\n if (a === b) {\n return true\n }\n\n if (typeof a !== typeof b) {\n return false\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let i = 0, l = a.length; i < l; i++) {\n if (!deepEqual(a[i], b[i], opts)) return false\n }\n return true\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const ignoreUndefined = opts?.ignoreUndefined ?? true\n\n if (opts?.partial) {\n for (const k in b) {\n if (!ignoreUndefined || b[k] !== undefined) {\n if (!deepEqual(a[k], b[k], opts)) return false\n }\n }\n return true\n }\n\n let aCount = 0\n if (!ignoreUndefined) {\n aCount = Object.keys(a).length\n } else {\n for (const k in a) {\n if (a[k] !== undefined) aCount++\n }\n }\n\n let bCount = 0\n for (const k in b) {\n if (!ignoreUndefined || b[k] !== undefined) {\n bCount++\n if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false\n }\n }\n\n return aCount === bCount\n }\n\n return false\n}\n\nexport type StringLiteral<T> = T extends string\n ? string extends T\n ? string\n : T\n : never\n\nexport type ThrowOrOptional<T, TThrow extends boolean> = TThrow extends true\n ? T\n : T | undefined\n\nexport type StrictOrFrom<\n TRouter extends AnyRouter,\n TFrom,\n TStrict extends boolean = true,\n> = TStrict extends false\n ? {\n from?: never\n strict: TStrict\n }\n : {\n from: ConstrainLiteral<TFrom, RouteIds<TRouter['routeTree']>>\n strict?: TStrict\n }\n\nexport type ThrowConstraint<\n TStrict extends boolean,\n TThrow extends boolean,\n> = TStrict extends false ? (TThrow extends true ? never : TThrow) : TThrow\n\nexport type ControlledPromise<T> = Promise<T> & {\n resolve: (value: T) => void\n reject: (value: any) => void\n status: 'pending' | 'resolved' | 'rejected'\n value?: T\n}\n\n/**\n * Create a promise with exposed resolve/reject and status fields.\n * Useful for coordinating async router lifecycle operations.\n */\nexport function createControlledPromise<T>(onResolve?: (value: T) => void) {\n let resolveLoadPromise!: (value: T) => void\n let rejectLoadPromise!: (value: any) => void\n\n const controlledPromise = new Promise<T>((resolve, reject) => {\n resolveLoadPromise = resolve\n rejectLoadPromise = reject\n }) as ControlledPromise<T>\n\n controlledPromise.status = 'pending'\n\n controlledPromise.resolve = (value: T) => {\n controlledPromise.status = 'resolved'\n controlledPromise.value = value\n resolveLoadPromise(value)\n onResolve?.(value)\n }\n\n controlledPromise.reject = (e) => {\n controlledPromise.status = 'rejected'\n rejectLoadPromise(e)\n }\n\n return controlledPromise\n}\n\n/**\n * Heuristically detect dynamic import \"module not found\" errors\n * across major browsers for lazy route component handling.\n */\nexport function isModuleNotFoundError(error: any): boolean {\n // chrome: \"Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split\"\n // firefox: \"error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split\"\n // safari: \"Importing a module script failed.\"\n if (typeof error?.message !== 'string') return false\n return (\n error.message.startsWith('Failed to fetch dynamically imported module') ||\n error.message.startsWith('error loading dynamically imported module') ||\n error.message.startsWith('Importing a module script failed')\n )\n}\n\nexport function isPromise<T>(\n value: Promise<Awaited<T>> | T,\n): value is Promise<Awaited<T>> {\n return Boolean(\n value &&\n typeof value === 'object' &&\n typeof (value as Promise<T>).then === 'function',\n )\n}\n\nexport function findLast<T>(\n array: ReadonlyArray<T>,\n predicate: (item: T) => boolean,\n): T | undefined {\n for (let i = array.length - 1; i >= 0; i--) {\n const item = array[i]!\n if (predicate(item)) return item\n }\n return undefined\n}\n\n/**\n * Remove control characters that can cause open redirect vulnerabilities.\n * Characters like \\r (CR) and \\n (LF) can trick URL parsers into interpreting\n * paths like \"/\\r/evil.com\" as \"http://evil.com\".\n */\nfunction sanitizePathSegment(segment: string): string {\n // Remove ASCII control characters (0x00-0x1F) and DEL (0x7F)\n // These include CR (\\r = 0x0D), LF (\\n = 0x0A), and other potentially dangerous characters\n // eslint-disable-next-line no-control-regex\n return segment.replace(/[\\x00-\\x1f\\x7f]/g, '')\n}\n\nfunction decodeSegment(segment: string): string {\n let decoded: string\n try {\n decoded = decodeURI(segment)\n } catch {\n // if the decoding fails, try to decode the various parts leaving the malformed tags in place\n decoded = segment.replaceAll(/%[0-9A-F]{2}/gi, (match) => {\n try {\n return decodeURI(match)\n } catch {\n return match\n }\n })\n }\n return sanitizePathSegment(decoded)\n}\n\n/**\n * Default list of URL protocols to allow in links, redirects, and navigation.\n * Any absolute URL protocol not in this list is treated as dangerous by default.\n */\nexport const DEFAULT_PROTOCOL_ALLOWLIST = [\n // Standard web navigation\n 'http:',\n 'https:',\n\n // Common browser-safe actions\n 'mailto:',\n 'tel:',\n]\n\n/**\n * Check if a URL string uses a protocol that is not in the allowlist.\n * Returns true for blocked protocols like javascript:, blob:, data:, etc.\n *\n * The URL constructor correctly normalizes:\n * - Mixed case (JavaScript: → javascript:)\n * - Whitespace/control characters (java\\nscript: → javascript:)\n * - Leading whitespace\n *\n * For relative URLs (no protocol), returns false (safe).\n *\n * @param url - The URL string to check\n * @param allowlist - Set of protocols to allow\n * @returns true if the URL uses a protocol that is not allowed\n */\nexport function isDangerousProtocol(\n url: string,\n allowlist: Set<string>,\n): boolean {\n if (!url) return false\n\n try {\n // Use the URL constructor - it correctly normalizes protocols\n // per WHATWG URL spec, handling all bypass attempts automatically\n const parsed = new URL(url)\n return !allowlist.has(parsed.protocol)\n } catch {\n // URL constructor throws for relative URLs (no protocol)\n // These are safe - they can't execute scripts\n return false\n }\n}\n\n// This utility is based on https://github.com/zertosh/htmlescape\n// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE\nconst HTML_ESCAPE_LOOKUP: { [match: string]: string } = {\n '&': '\\\\u0026',\n '>': '\\\\u003e',\n '<': '\\\\u003c',\n '\\u2028': '\\\\u2028',\n '\\u2029': '\\\\u2029',\n}\n\nconst HTML_ESCAPE_REGEX = /[&><\\u2028\\u2029]/g\n\n/**\n * Escape HTML special characters in a string to prevent XSS attacks\n * when embedding strings in script tags during SSR.\n *\n * This is essential for preventing XSS vulnerabilities when user-controlled\n * content is embedded in inline scripts.\n */\nexport function escapeHtml(str: string): string {\n return str.replace(HTML_ESCAPE_REGEX, (match) => HTML_ESCAPE_LOOKUP[match]!)\n}\n\nexport function decodePath(path: string) {\n if (!path) return { path, handledProtocolRelativeURL: false }\n\n // Fast path: most paths are already decoded and safe.\n // Only fall back to the slower scan/regex path when we see a '%' (encoded),\n // a backslash (explicitly handled), a control character, or a protocol-relative\n // prefix which needs collapsing.\n // eslint-disable-next-line no-control-regex\n if (!/[%\\\\\\x00-\\x1f\\x7f]/.test(path) && !path.startsWith('//')) {\n return { path, handledProtocolRelativeURL: false }\n }\n\n const re = /%25|%5C/gi\n let cursor = 0\n let result = ''\n let match\n while (null !== (match = re.exec(path))) {\n result += decodeSegment(path.slice(cursor, match.index)) + match[0]\n cursor = re.lastIndex\n }\n result = result + decodeSegment(cursor ? path.slice(cursor) : path)\n\n // Prevent open redirect via protocol-relative URLs (e.g. \"//evil.com\")\n // After sanitizing control characters, paths like \"/\\r/evil.com\" become \"//evil.com\"\n // Collapse leading double slashes to a single slash\n let handledProtocolRelativeURL = false\n if (result.startsWith('//')) {\n handledProtocolRelativeURL = true\n result = '/' + result.replace(/^\\/+/, '')\n }\n\n return { path: result, handledProtocolRelativeURL }\n}\n\n/**\n * Encodes a path the same way `new URL()` would, but without the overhead of full URL parsing.\n *\n * This function encodes:\n * - Whitespace characters (spaces → %20, tabs → %09, etc.)\n * - Non-ASCII/Unicode characters (emojis, accented characters, etc.)\n *\n * It preserves:\n * - Already percent-encoded sequences (won't double-encode %2F, %25, etc.)\n * - ASCII special characters valid in URL paths (@, $, &, +, etc.)\n * - Forward slashes as path separators\n *\n * Used to generate proper href values for SSR without constructing URL objects.\n *\n * @example\n * encodePathLikeUrl('/path/file name.pdf') // '/path/file%20name.pdf'\n * encodePathLikeUrl('/path/日本語') // '/path/%E6%97%A5%E6%9C%AC%E8%AA%9E'\n * encodePathLikeUrl('/path/already%20encoded') // '/path/already%20encoded' (preserved)\n */\nexport function encodePathLikeUrl(path: string): string {\n // Encode whitespace and non-ASCII characters that browsers encode in URLs\n\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check\n // eslint-disable-next-line no-control-regex\n if (!/\\s|[^\\u0000-\\u007F]/.test(path)) return path\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ASCII range check\n // eslint-disable-next-line no-control-regex\n return path.replace(/\\s|[^\\u0000-\\u007F]/gu, encodeURIComponent)\n}\n\n/**\n * Builds the dev-mode CSS styles URL for route-scoped CSS collection.\n * Used by HeadContent components in all framework implementations to construct\n * the URL for the `/@tanstack-start/styles.css` endpoint.\n *\n * @param basepath - The router's basepath (may or may not have leading slash)\n * @param routeIds - Array of matched route IDs to include in the CSS collection\n * @returns The full URL path for the dev styles CSS endpoint\n */\nexport function buildDevStylesUrl(\n basepath: string,\n routeIds: Array<string>,\n): string {\n // Trim all leading and trailing slashes from basepath\n const trimmedBasepath = basepath.replace(/^\\/+|\\/+$/g, '')\n // Build normalized basepath: empty string for root, or '/path' for non-root\n const normalizedBasepath = trimmedBasepath === '' ? '' : `/${trimmedBasepath}`\n return `${normalizedBasepath}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`\n}\n\nexport function arraysEqual<T>(a: Array<T>, b: Array<T>) {\n if (a === b) return true\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false\n }\n return true\n}\n"],"mappings":"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"}
@@ -175,3 +175,4 @@ export declare function encodePathLikeUrl(path: string): string;
175
175
  * @returns The full URL path for the dev styles CSS endpoint
176
176
  */
177
177
  export declare function buildDevStylesUrl(basepath: string, routeIds: Array<string>): string;
178
+ export declare function arraysEqual<T>(a: Array<T>, b: Array<T>): boolean;
@@ -15,6 +15,8 @@ export { rootRouteId } from './root.js';
15
15
  export type { RootRouteId } from './root.js';
16
16
  export { BaseRoute, BaseRouteApi, BaseRootRoute } from './route.js';
17
17
  export type { AnyPathParams, SearchSchemaInput, AnyContext, RouteContext, PreloadableObj, RoutePathOptions, StaticDataRouteOption, RoutePathOptionsIntersection, SearchFilter, SearchMiddlewareContext, SearchMiddleware, ResolveId, InferFullSearchSchema, InferFullSearchSchemaInput, InferAllParams, InferAllContext, MetaDescriptor, RouteLinkEntry, SearchValidator, AnySearchValidator, DefaultSearchValidator, ErrorRouteProps, ErrorComponentProps, NotFoundRouteProps, ResolveParams, ParseParamsFn, StringifyParamsFn, ParamsOptions, UpdatableStaticRouteOption, ContextReturnType, ContextAsyncReturnType, ResolveRouteContext, ResolveLoaderData, RoutePrefix, TrimPath, TrimPathLeft, TrimPathRight, ResolveSearchSchemaFnInput, ResolveSearchSchemaInput, ResolveSearchSchemaFn, ResolveSearchSchema, ResolveFullSearchSchema, ResolveFullSearchSchemaInput, ResolveAllContext, BeforeLoadContextParameter, RouteContextParameter, ResolveAllParamsFromParent, AnyRoute, Route, RouteTypes, FullSearchSchemaOption, RemountDepsOptions, MakeRemountDepsOptionsUnion, ResolveFullPath, AnyRouteWithContext, RouteOptions, FileBaseRouteOptions, BaseRouteOptions, UpdatableRouteOptions, LoaderStaleReloadMode, RouteLoaderFn, RouteLoaderEntry, LoaderFnContext, RouteContextFn, ContextOptions, RouteContextOptions, BeforeLoadContextOptions, RootRouteOptions, RootRouteOptionsExtensions, UpdatableRouteOptionsExtensions, RouteConstraints, RouteTypesById, RouteMask, RouteExtensions, RouteLazyFn, RouteAddChildrenFn, RouteAddFileChildrenFn, RouteAddFileTypesFn, ResolveOptionalParams, ResolveRequiredParams, RootRoute, FilebaseRouteOptionsInterface, } from './route.js';
18
+ export { createNonReactiveMutableStore, createNonReactiveReadonlyStore, } from './stores.js';
19
+ export type { RouterBatchFn, RouterReadableStore, GetStoreConfig, RouterStores, RouterWritableStore, } from './stores.js';
18
20
  export { defaultSerializeError, getLocationChangeInfo, RouterCore, lazyFn, SearchParamError, PathParamError, getInitialRouterState, getMatchedRoutes, trailingSlashOptions, } from './router.js';
19
21
  export type { ViewTransitionOptions, TrailingSlashOption, Register, AnyRouter, AnyRouterWithContext, RegisteredRouter, RouterState, BuildNextOptions, RouterListener, RouterEvent, ListenerFn, RouterEvents, MatchRoutesOpts, RouterOptionsExtensions, DefaultRemountDepsFn, PreloadRouteFn, MatchRouteFn, RouterContextOptions, RouterOptions, RouterConstructorOptions, UpdateFn, ParseLocationFn, InvalidateFn, ControllablePromise, InjectedHtmlEntry, EmitFn, LoadFn, GetMatchFn, SubscribeFn, UpdateMatchFn, CommitLocationFn, GetMatchRoutesFn, MatchRoutesFn, StartTransitionFn, LoadRouteChunkFn, ClearCacheFn, CreateRouterFn, SSROption, } from './router.js';
20
22
  export * from './config.js';
package/dist/esm/index.js CHANGED
@@ -7,6 +7,7 @@ import { defaultParseSearch, defaultStringifySearch, parseSearchWith, stringifyS
7
7
  import { rootRouteId } from "./root.js";
8
8
  import { isRedirect, isResolvedRedirect, parseRedirect, redirect } from "./redirect.js";
9
9
  import { composeRewrites, executeRewriteInput } from "./rewrite.js";
10
+ import { createNonReactiveMutableStore, createNonReactiveReadonlyStore } from "./stores.js";
10
11
  import { PathParamError, RouterCore, SearchParamError, defaultSerializeError, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, lazyFn, trailingSlashOptions } from "./router.js";
11
12
  import { TSR_DEFERRED_PROMISE, defer } from "./defer.js";
12
13
  import { preloadWarning } from "./link.js";
@@ -17,4 +18,4 @@ import { retainSearchParams, stripSearchParams } from "./searchMiddleware.js";
17
18
  import { createSerializationAdapter, makeSerovalPlugin, makeSsrSerovalPlugin } from "./ssr/serializer/transformer.js";
18
19
  import { RawStream, createRawStreamDeserializePlugin, createRawStreamRPCPlugin } from "./ssr/serializer/RawStream.js";
19
20
  import { defaultSerovalPlugins } from "./ssr/serializer/seroval-plugins.js";
20
- export { BaseRootRoute, BaseRoute, BaseRouteApi, DEFAULT_PROTOCOL_ALLOWLIST, PathParamError, RawStream, RouterCore, SearchParamError, TSR_DEFERRED_PROMISE, buildDevStylesUrl, cleanPath, composeRewrites, createControlledPromise, createRawStreamDeserializePlugin, createRawStreamRPCPlugin, createRouterConfig, createSerializationAdapter, decode, deepEqual, defaultGetScrollRestorationKey, defaultParseSearch, defaultSerializeError, defaultSerovalPlugins, defaultStringifySearch, defer, encode, escapeHtml, exactPathTest, executeRewriteInput, functionalUpdate, getCssSelector, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, handleHashScroll, interpolatePath, isDangerousProtocol, isMatch, isModuleNotFoundError, isNotFound, isPlainArray, isPlainObject, isRedirect, isResolvedRedirect, joinPaths, lazyFn, makeSerovalPlugin, makeSsrSerovalPlugin, notFound, parseRedirect, parseSearchWith, preloadWarning, redirect, removeTrailingSlash, replaceEqualDeep, resolvePath, restoreScroll, retainSearchParams, rootRouteId, scrollRestorationCache, setupScrollRestoration, storageKey, stringifySearchWith, stripSearchParams, trailingSlashOptions, trimPath, trimPathLeft, trimPathRight };
21
+ export { BaseRootRoute, BaseRoute, BaseRouteApi, DEFAULT_PROTOCOL_ALLOWLIST, PathParamError, RawStream, RouterCore, SearchParamError, TSR_DEFERRED_PROMISE, buildDevStylesUrl, cleanPath, composeRewrites, createControlledPromise, createNonReactiveMutableStore, createNonReactiveReadonlyStore, createRawStreamDeserializePlugin, createRawStreamRPCPlugin, createRouterConfig, createSerializationAdapter, decode, deepEqual, defaultGetScrollRestorationKey, defaultParseSearch, defaultSerializeError, defaultSerovalPlugins, defaultStringifySearch, defer, encode, escapeHtml, exactPathTest, executeRewriteInput, functionalUpdate, getCssSelector, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, handleHashScroll, interpolatePath, isDangerousProtocol, isMatch, isModuleNotFoundError, isNotFound, isPlainArray, isPlainObject, isRedirect, isResolvedRedirect, joinPaths, lazyFn, makeSerovalPlugin, makeSsrSerovalPlugin, notFound, parseRedirect, parseSearchWith, preloadWarning, redirect, removeTrailingSlash, replaceEqualDeep, resolvePath, restoreScroll, retainSearchParams, rootRouteId, scrollRestorationCache, setupScrollRestoration, storageKey, stringifySearchWith, stripSearchParams, trailingSlashOptions, trimPath, trimPathLeft, trimPathRight };
@@ -1,4 +1,3 @@
1
- import { batch } from "./utils/batch.js";
2
1
  import { createControlledPromise, isPromise } from "./utils.js";
3
2
  import { isNotFound } from "./not-found.js";
4
3
  import { rootRouteId } from "./root.js";
@@ -12,8 +11,13 @@ var triggerOnReady = (inner) => {
12
11
  return inner.onReady?.();
13
12
  }
14
13
  };
14
+ var hasForcePendingActiveMatch = (router) => {
15
+ return router.stores.matchesId.state.some((matchId) => {
16
+ return router.stores.activeMatchStoresById.get(matchId)?.state._forcePending;
17
+ });
18
+ };
15
19
  var resolvePreload = (inner, matchId) => {
16
- return !!(inner.preload && !inner.router.state.matches.some((d) => d.id === matchId));
20
+ return !!(inner.preload && !inner.router.stores.activeMatchStoresById.has(matchId));
17
21
  };
18
22
  /**
19
23
  * Builds the accumulated context from router options and all matches up to (and optionally including) the given index.
@@ -215,7 +219,7 @@ var executeBeforeLoad = (inner, matchId, index, route) => {
215
219
  }));
216
220
  };
217
221
  if (!route.options.beforeLoad) {
218
- batch(() => {
222
+ inner.router.batch(() => {
219
223
  pending();
220
224
  resolve();
221
225
  });
@@ -247,7 +251,7 @@ var executeBeforeLoad = (inner, matchId, index, route) => {
247
251
  };
248
252
  const updateContext = (beforeLoadContext) => {
249
253
  if (beforeLoadContext === void 0) {
250
- batch(() => {
254
+ inner.router.batch(() => {
251
255
  pending();
252
256
  resolve();
253
257
  });
@@ -257,7 +261,7 @@ var executeBeforeLoad = (inner, matchId, index, route) => {
257
261
  pending();
258
262
  handleSerialError(inner, index, beforeLoadContext, "BEFORE_LOAD");
259
263
  }
260
- batch(() => {
264
+ inner.router.batch(() => {
261
265
  pending();
262
266
  inner.updateMatch(matchId, (prev) => ({
263
267
  ...prev,
@@ -428,7 +432,7 @@ var loadRouteMatch = async (inner, matchPromises, index) => {
428
432
  const shouldReloadOption = route.options.shouldReload;
429
433
  const shouldReload = typeof shouldReloadOption === "function" ? shouldReloadOption(getLoaderContext(inner, matchPromises, matchId, index, route)) : shouldReloadOption;
430
434
  const { status, invalid } = match;
431
- const staleMatchShouldReload = age > staleAge && (!!inner.forceStaleReload || match.cause === "enter" || previousRouteMatchId !== void 0 && previousRouteMatchId !== match.id);
435
+ const staleMatchShouldReload = age >= staleAge && (!!inner.forceStaleReload || match.cause === "enter" || previousRouteMatchId !== void 0 && previousRouteMatchId !== match.id);
432
436
  loaderShouldRunAsync = status === "success" && (invalid || (shouldReload ?? staleMatchShouldReload));
433
437
  if (preload && route.options.preload === false) {} else if (loaderShouldRunAsync && !inner.sync && shouldReloadInBackground) {
434
438
  loaderIsRunningAsync = true;
@@ -459,7 +463,8 @@ var loadRouteMatch = async (inner, matchPromises, index) => {
459
463
  if (isServer ?? inner.router.isServer) return inner.router.getMatch(matchId);
460
464
  } else {
461
465
  const prevMatch = inner.router.getMatch(matchId);
462
- const previousRouteMatchId = inner.router.state.matches[index]?.routeId === routeId ? inner.router.state.matches[index].id : inner.router.state.matches.find((d) => d.routeId === routeId)?.id;
466
+ const activeIdAtIndex = inner.router.stores.matchesId.state[index];
467
+ const previousRouteMatchId = (activeIdAtIndex && inner.router.stores.activeMatchStoresById.get(activeIdAtIndex) || null)?.routeId === routeId ? activeIdAtIndex : inner.router.stores.activeMatchesSnapshot.state.find((d) => d.routeId === routeId)?.id;
463
468
  const preload = resolvePreload(inner, matchId);
464
469
  if (prevMatch._nonReactive.loaderPromise) {
465
470
  if (prevMatch.status === "success" && !inner.sync && !prevMatch.preload && shouldReloadInBackground) return prevMatch;
@@ -469,7 +474,7 @@ var loadRouteMatch = async (inner, matchPromises, index) => {
469
474
  if (error) handleRedirectAndNotFound(inner, match, error);
470
475
  if (match.status === "pending") await handleLoader(preload, prevMatch, previousRouteMatchId, match, route);
471
476
  } else {
472
- const nextPreload = preload && !inner.router.state.matches.some((d) => d.id === matchId);
477
+ const nextPreload = preload && !inner.router.stores.activeMatchStoresById.has(matchId);
473
478
  const match = inner.router.getMatch(matchId);
474
479
  match._nonReactive.loaderPromise = createControlledPromise();
475
480
  if (nextPreload !== match.preload) inner.updateMatch(matchId, (prev) => ({
@@ -502,7 +507,7 @@ var loadRouteMatch = async (inner, matchPromises, index) => {
502
507
  async function loadMatches(arg) {
503
508
  const inner = arg;
504
509
  const matchPromises = [];
505
- if (!(isServer ?? inner.router.isServer) && inner.router.state.matches.some((d) => d._forcePending)) triggerOnReady(inner);
510
+ if (!(isServer ?? inner.router.isServer) && hasForcePendingActiveMatch(inner.router)) triggerOnReady(inner);
506
511
  let beforeLoadNotFound;
507
512
  for (let i = 0; i < inner.matches.length; i++) {
508
513
  try {