@tanstack/router-core 1.154.8 → 1.154.13
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.
- package/dist/cjs/router.cjs +91 -79
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -1
- package/dist/esm/router.js +91 -79
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/utils.d.ts +1 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/router.ts +130 -97
- package/src/utils.ts +1 -1
package/dist/esm/utils.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export type LooseAsyncReturnType<T> = T extends (...args: Array<any>) => infer T
|
|
|
57
57
|
* Return the last element of an array.
|
|
58
58
|
* Intended for non-empty arrays used within router internals.
|
|
59
59
|
*/
|
|
60
|
-
export declare function last<T>(arr:
|
|
60
|
+
export declare function last<T>(arr: ReadonlyArray<T>): T | undefined;
|
|
61
61
|
/**
|
|
62
62
|
* Apply a value-or-updater to a previous value.
|
|
63
63
|
* Accepts either a literal value or a function of the previous value.
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import 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: Array<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\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>(prev: any, _next: T, _depth = 0): T {\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) : {}\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, _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 * List of URL protocols that are safe for navigation.\n * Only these protocols are allowed in redirects and navigation.\n */\nexport const SAFE_URL_PROTOCOLS = ['http:', 'https:', 'mailto:', 'tel:']\n\n/**\n * Check if a URL string uses a protocol that is not in the safe list.\n * Returns true for dangerous protocols like javascript:, data:, vbscript:, 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 * @returns true if the URL uses a dangerous (non-whitelisted) protocol\n */\nexport function isDangerousProtocol(url: string): 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 !SAFE_URL_PROTOCOLS.includes(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, decodeIgnore?: Array<string>): string {\n if (!path) return path\n const re = decodeIgnore\n ? new RegExp(`${decodeIgnore.join('|')}`, 'gi')\n : /%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 if (result.startsWith('//')) {\n result = '/' + result.replace(/^\\/+/, '')\n }\n\n return result\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"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import 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\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>(prev: any, _next: T, _depth = 0): T {\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) : {}\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, _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 * List of URL protocols that are safe for navigation.\n * Only these protocols are allowed in redirects and navigation.\n */\nexport const SAFE_URL_PROTOCOLS = ['http:', 'https:', 'mailto:', 'tel:']\n\n/**\n * Check if a URL string uses a protocol that is not in the safe list.\n * Returns true for dangerous protocols like javascript:, data:, vbscript:, 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 * @returns true if the URL uses a dangerous (non-whitelisted) protocol\n */\nexport function isDangerousProtocol(url: string): 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 !SAFE_URL_PROTOCOLS.includes(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, decodeIgnore?: Array<string>): string {\n if (!path) return path\n const re = decodeIgnore\n ? new RegExp(`${decodeIgnore.join('|')}`, 'gi')\n : /%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 if (result.startsWith('//')) {\n result = '/' + result.replace(/^\\/+/, '')\n }\n\n return result\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"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/router.ts
CHANGED
|
@@ -1330,22 +1330,9 @@ export class RouterCore<
|
|
|
1330
1330
|
}
|
|
1331
1331
|
}
|
|
1332
1332
|
|
|
1333
|
-
globalNotFoundRouteId =
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
if (this.options.notFoundMode !== 'root') {
|
|
1339
|
-
for (let i = matchedRoutes.length - 1; i >= 0; i--) {
|
|
1340
|
-
const route = matchedRoutes[i]!
|
|
1341
|
-
if (route.children) {
|
|
1342
|
-
return route.id
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
return rootRouteId
|
|
1348
|
-
})()
|
|
1333
|
+
globalNotFoundRouteId = isGlobalNotFound
|
|
1334
|
+
? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes)
|
|
1335
|
+
: undefined
|
|
1349
1336
|
}
|
|
1350
1337
|
|
|
1351
1338
|
const matches: Array<AnyRouteMatch> = []
|
|
@@ -1775,16 +1762,30 @@ export class RouterCore<
|
|
|
1775
1762
|
params: nextParams,
|
|
1776
1763
|
}).interpolatedPath
|
|
1777
1764
|
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
)
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1765
|
+
// Use lightweight getMatchedRoutes instead of matchRoutesInternal
|
|
1766
|
+
// This avoids creating full match objects (AbortController, ControlledPromise, etc.)
|
|
1767
|
+
// which are expensive and not needed for buildLocation
|
|
1768
|
+
const destMatchResult = this.getMatchedRoutes(interpolatedNextTo)
|
|
1769
|
+
let destRoutes = destMatchResult.matchedRoutes
|
|
1770
|
+
const rawParams = destMatchResult.routeParams
|
|
1771
|
+
|
|
1772
|
+
// Compute globalNotFoundRouteId using the same logic as matchRoutesInternal
|
|
1773
|
+
const isGlobalNotFound = destMatchResult.foundRoute
|
|
1774
|
+
? destMatchResult.foundRoute.path !== '/' &&
|
|
1775
|
+
destMatchResult.routeParams['**']
|
|
1776
|
+
: trimPathRight(interpolatedNextTo)
|
|
1777
|
+
|
|
1778
|
+
let globalNotFoundRouteId: string | undefined
|
|
1779
|
+
if (isGlobalNotFound) {
|
|
1780
|
+
if (this.options.notFoundRoute) {
|
|
1781
|
+
destRoutes = [...destRoutes, this.options.notFoundRoute]
|
|
1782
|
+
} else {
|
|
1783
|
+
globalNotFoundRouteId = findGlobalNotFoundRouteId(
|
|
1784
|
+
this.options.notFoundMode,
|
|
1785
|
+
destRoutes,
|
|
1786
|
+
)
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1788
1789
|
|
|
1789
1790
|
// If there are any params, we need to stringify them
|
|
1790
1791
|
if (Object.keys(nextParams).length > 0) {
|
|
@@ -1877,7 +1878,7 @@ export class RouterCore<
|
|
|
1877
1878
|
routes: destRoutes,
|
|
1878
1879
|
params: snapshotParams,
|
|
1879
1880
|
searchStr,
|
|
1880
|
-
globalNotFoundRouteId
|
|
1881
|
+
globalNotFoundRouteId,
|
|
1881
1882
|
})
|
|
1882
1883
|
|
|
1883
1884
|
// Create the full path of the location
|
|
@@ -3037,6 +3038,10 @@ export function buildMatchSnapshotFromRoutes({
|
|
|
3037
3038
|
return snapshot
|
|
3038
3039
|
}
|
|
3039
3040
|
|
|
3041
|
+
/**
|
|
3042
|
+
* TODO: once caches are persisted across requests on the server,
|
|
3043
|
+
* we can cache the built middleware chain using `last(destRoutes)` as the key
|
|
3044
|
+
*/
|
|
3040
3045
|
function applySearchMiddleware({
|
|
3041
3046
|
search,
|
|
3042
3047
|
dest,
|
|
@@ -3045,83 +3050,85 @@ function applySearchMiddleware({
|
|
|
3045
3050
|
}: {
|
|
3046
3051
|
search: any
|
|
3047
3052
|
dest: BuildNextOptions
|
|
3048
|
-
destRoutes:
|
|
3053
|
+
destRoutes: ReadonlyArray<AnyRoute>
|
|
3049
3054
|
_includeValidateSearch: boolean | undefined
|
|
3050
3055
|
}) {
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3056
|
+
const middleware = buildMiddlewareChain(destRoutes)
|
|
3057
|
+
return middleware(search, dest, _includeValidateSearch ?? false)
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
function buildMiddlewareChain(destRoutes: ReadonlyArray<AnyRoute>) {
|
|
3061
|
+
const context = {
|
|
3062
|
+
dest: null as unknown as BuildNextOptions,
|
|
3063
|
+
_includeValidateSearch: false,
|
|
3064
|
+
middlewares: [] as Array<SearchMiddleware<any>>,
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
for (const route of destRoutes) {
|
|
3068
|
+
if ('search' in route.options) {
|
|
3069
|
+
if (route.options.search?.middlewares) {
|
|
3070
|
+
context.middlewares.push(...route.options.search.middlewares)
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
// TODO remove preSearchFilters and postSearchFilters in v2
|
|
3074
|
+
else if (
|
|
3075
|
+
route.options.preSearchFilters ||
|
|
3076
|
+
route.options.postSearchFilters
|
|
3077
|
+
) {
|
|
3078
|
+
const legacyMiddleware: SearchMiddleware<any> = ({ search, next }) => {
|
|
3079
|
+
let nextSearch = search
|
|
3080
|
+
|
|
3081
|
+
if (
|
|
3082
|
+
'preSearchFilters' in route.options &&
|
|
3083
|
+
route.options.preSearchFilters
|
|
3065
3084
|
) {
|
|
3066
|
-
|
|
3085
|
+
nextSearch = route.options.preSearchFilters.reduce(
|
|
3086
|
+
(prev, next) => next(prev),
|
|
3067
3087
|
search,
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
let nextSearch = search
|
|
3071
|
-
|
|
3072
|
-
if (
|
|
3073
|
-
'preSearchFilters' in route.options &&
|
|
3074
|
-
route.options.preSearchFilters
|
|
3075
|
-
) {
|
|
3076
|
-
nextSearch = route.options.preSearchFilters.reduce(
|
|
3077
|
-
(prev, next) => next(prev),
|
|
3078
|
-
search,
|
|
3079
|
-
)
|
|
3080
|
-
}
|
|
3081
|
-
|
|
3082
|
-
const result = next(nextSearch)
|
|
3088
|
+
)
|
|
3089
|
+
}
|
|
3083
3090
|
|
|
3084
|
-
|
|
3085
|
-
'postSearchFilters' in route.options &&
|
|
3086
|
-
route.options.postSearchFilters
|
|
3087
|
-
) {
|
|
3088
|
-
return route.options.postSearchFilters.reduce(
|
|
3089
|
-
(prev, next) => next(prev),
|
|
3090
|
-
result,
|
|
3091
|
-
)
|
|
3092
|
-
}
|
|
3091
|
+
const result = next(nextSearch)
|
|
3093
3092
|
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3093
|
+
if (
|
|
3094
|
+
'postSearchFilters' in route.options &&
|
|
3095
|
+
route.options.postSearchFilters
|
|
3096
|
+
) {
|
|
3097
|
+
return route.options.postSearchFilters.reduce(
|
|
3098
|
+
(prev, next) => next(prev),
|
|
3099
|
+
result,
|
|
3100
|
+
)
|
|
3097
3101
|
}
|
|
3098
3102
|
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
const validatedSearch = {
|
|
3104
|
-
...result,
|
|
3105
|
-
...(validateSearch(route.options.validateSearch, result) ??
|
|
3106
|
-
undefined),
|
|
3107
|
-
}
|
|
3108
|
-
return validatedSearch
|
|
3109
|
-
} catch {
|
|
3110
|
-
// ignore errors here because they are already handled in matchRoutes
|
|
3111
|
-
return result
|
|
3112
|
-
}
|
|
3113
|
-
}
|
|
3103
|
+
return result
|
|
3104
|
+
}
|
|
3105
|
+
context.middlewares.push(legacyMiddleware)
|
|
3106
|
+
}
|
|
3114
3107
|
|
|
3115
|
-
|
|
3108
|
+
if (route.options.validateSearch) {
|
|
3109
|
+
const validate: SearchMiddleware<any> = ({ search, next }) => {
|
|
3110
|
+
const result = next(search)
|
|
3111
|
+
if (!context._includeValidateSearch) return result
|
|
3112
|
+
try {
|
|
3113
|
+
const validatedSearch = {
|
|
3114
|
+
...result,
|
|
3115
|
+
...(validateSearch(route.options.validateSearch, result) ??
|
|
3116
|
+
undefined),
|
|
3117
|
+
}
|
|
3118
|
+
return validatedSearch
|
|
3119
|
+
} catch {
|
|
3120
|
+
// ignore errors here because they are already handled in matchRoutes
|
|
3121
|
+
return result
|
|
3116
3122
|
}
|
|
3123
|
+
}
|
|
3117
3124
|
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
) ?? []
|
|
3125
|
+
context.middlewares.push(validate)
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3122
3128
|
|
|
3123
3129
|
// the chain ends here since `next` is not called
|
|
3124
3130
|
const final: SearchMiddleware<any> = ({ search }) => {
|
|
3131
|
+
const dest = context.dest
|
|
3125
3132
|
if (!dest.search) {
|
|
3126
3133
|
return {}
|
|
3127
3134
|
}
|
|
@@ -3131,23 +3138,49 @@ function applySearchMiddleware({
|
|
|
3131
3138
|
return functionalUpdate(dest.search, search)
|
|
3132
3139
|
}
|
|
3133
3140
|
|
|
3134
|
-
|
|
3141
|
+
context.middlewares.push(final)
|
|
3135
3142
|
|
|
3136
|
-
const applyNext = (
|
|
3143
|
+
const applyNext = (
|
|
3144
|
+
index: number,
|
|
3145
|
+
currentSearch: any,
|
|
3146
|
+
middlewares: Array<SearchMiddleware<any>>,
|
|
3147
|
+
): any => {
|
|
3137
3148
|
// no more middlewares left, return the current search
|
|
3138
|
-
if (index >=
|
|
3149
|
+
if (index >= middlewares.length) {
|
|
3139
3150
|
return currentSearch
|
|
3140
3151
|
}
|
|
3141
3152
|
|
|
3142
|
-
const middleware =
|
|
3153
|
+
const middleware = middlewares[index]!
|
|
3143
3154
|
|
|
3144
3155
|
const next = (newSearch: any): any => {
|
|
3145
|
-
return applyNext(index + 1, newSearch)
|
|
3156
|
+
return applyNext(index + 1, newSearch, middlewares)
|
|
3146
3157
|
}
|
|
3147
3158
|
|
|
3148
3159
|
return middleware({ search: currentSearch, next })
|
|
3149
3160
|
}
|
|
3150
3161
|
|
|
3151
|
-
|
|
3152
|
-
|
|
3162
|
+
return function middleware(
|
|
3163
|
+
search: any,
|
|
3164
|
+
dest: BuildNextOptions,
|
|
3165
|
+
_includeValidateSearch: boolean,
|
|
3166
|
+
) {
|
|
3167
|
+
context.dest = dest
|
|
3168
|
+
context._includeValidateSearch = _includeValidateSearch
|
|
3169
|
+
return applyNext(0, search, context.middlewares)
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
function findGlobalNotFoundRouteId(
|
|
3174
|
+
notFoundMode: 'root' | 'fuzzy' | undefined,
|
|
3175
|
+
routes: ReadonlyArray<AnyRoute>,
|
|
3176
|
+
) {
|
|
3177
|
+
if (notFoundMode !== 'root') {
|
|
3178
|
+
for (let i = routes.length - 1; i >= 0; i--) {
|
|
3179
|
+
const route = routes[i]!
|
|
3180
|
+
if (route.children) {
|
|
3181
|
+
return route.id
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
return rootRouteId
|
|
3153
3186
|
}
|
package/src/utils.ts
CHANGED
|
@@ -188,7 +188,7 @@ export type LooseAsyncReturnType<T> = T extends (
|
|
|
188
188
|
* Return the last element of an array.
|
|
189
189
|
* Intended for non-empty arrays used within router internals.
|
|
190
190
|
*/
|
|
191
|
-
export function last<T>(arr:
|
|
191
|
+
export function last<T>(arr: ReadonlyArray<T>) {
|
|
192
192
|
return arr[arr.length - 1]
|
|
193
193
|
}
|
|
194
194
|
|