@tanstack/router-core 1.171.19 → 1.171.21
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/path.cjs +3 -2
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/searchParams.cjs +10 -6
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/cjs/utils.cjs +3 -4
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/path.js +3 -2
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/searchParams.js +10 -6
- package/dist/esm/searchParams.js.map +1 -1
- package/dist/esm/utils.js +3 -4
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/path.ts +6 -2
- package/src/searchParams.ts +11 -3
- package/src/utils.ts +18 -11
package/dist/cjs/path.cjs
CHANGED
|
@@ -42,8 +42,8 @@ function exactPathTest(pathName1, pathName2, basepath) {
|
|
|
42
42
|
* and supporting relative segments (`.`/`..`) and absolute `to` values.
|
|
43
43
|
*/
|
|
44
44
|
function resolvePath({ base, to, trailingSlash = "never", cache }) {
|
|
45
|
+
const isBase = to === ".";
|
|
45
46
|
const isAbsolute = to.startsWith("/");
|
|
46
|
-
const isBase = !isAbsolute && to === ".";
|
|
47
47
|
let key;
|
|
48
48
|
if (cache) {
|
|
49
49
|
key = isAbsolute ? to : isBase ? base : base + "\0" + to;
|
|
@@ -62,7 +62,8 @@ function resolvePath({ base, to, trailingSlash = "never", cache }) {
|
|
|
62
62
|
if (value === "") {
|
|
63
63
|
if (!index) baseSegments = [value];
|
|
64
64
|
else if (index === length - 1) baseSegments.push(value);
|
|
65
|
-
} else if (value === "..") baseSegments.pop();
|
|
65
|
+
} else if (value === "..") if (baseSegments.length > 1) baseSegments.pop();
|
|
66
|
+
else baseSegments = [""];
|
|
66
67
|
else if (value === ".") {} else baseSegments.push(value);
|
|
67
68
|
}
|
|
68
69
|
}
|
package/dist/cjs/path.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.cjs","names":[],"sources":["../../src/path.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport { last } from './utils'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n SEGMENT_TYPE_WILDCARD,\n parseSegment,\n} from './new-process-route-tree'\nimport type { LRUCache } from './lru-cache'\n\n/** Join path segments, cleaning duplicate slashes between parts. */\nexport function joinPaths(paths: Array<string | undefined>) {\n return cleanPath(\n paths\n .filter((val) => {\n return val !== undefined\n })\n .join('/'),\n )\n}\n\n/** Remove repeated slashes from a path string. */\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\n/** Trim leading slashes (except preserving root '/'). */\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\n/** Trim trailing slashes (except preserving root '/'). */\nexport function trimPathRight(path: string) {\n const len = path.length\n return len > 1 && path[len - 1] === '/' ? path.replace(/\\/{1,}$/, '') : path\n}\n\n/** Trim both leading and trailing slashes. */\nexport function trimPath(path: string) {\n return trimPathRight(trimPathLeft(path))\n}\n\n/** Remove a trailing slash from value when appropriate for comparisons. */\nexport function removeTrailingSlash(value: string, basepath: string): string {\n if (value?.endsWith('/') && value !== '/' && value !== `${basepath}/`) {\n return value.slice(0, -1)\n }\n return value\n}\n\n// intended to only compare path name\n// see the usage in the isActive under useLinkProps\n// /sample/path1 = /sample/path1/\n// /sample/path1/some <> /sample/path1\n/**\n * Compare two pathnames for exact equality after normalizing trailing slashes\n * relative to the provided `basepath`.\n */\nexport function exactPathTest(\n pathName1: string,\n pathName2: string,\n basepath: string,\n): boolean {\n return (\n removeTrailingSlash(pathName1, basepath) ===\n removeTrailingSlash(pathName2, basepath)\n )\n}\n\n// When resolving relative paths, we treat all paths as if they are trailing slash\n// documents. All trailing slashes are removed after the path is resolved.\n// Here are a few examples:\n//\n// /a/b/c + ./d = /a/b/c/d\n// /a/b/c + ../d = /a/b/d\n// /a/b/c + ./d/ = /a/b/c/d\n// /a/b/c + ../d/ = /a/b/d\n// /a/b/c + ./ = /a/b/c\n//\n// Absolute paths that start with `/` short circuit the resolution process to the root\n// path.\n//\n// Here are some examples:\n//\n// /a/b/c + /d = /d\n// /a/b/c + /d/ = /d\n// /a/b/c + / = /\n//\n// Non-.-prefixed paths are still treated as relative paths, resolved like `./`\n//\n// Here are some examples:\n//\n// /a/b/c + d = /a/b/c/d\n// /a/b/c + d/ = /a/b/c/d\n// /a/b/c + d/e = /a/b/c/d/e\ninterface ResolvePathOptions {\n base: string\n to: string\n trailingSlash?: 'always' | 'never' | 'preserve'\n cache?: LRUCache<string, string>\n}\n\n/**\n * Resolve a destination path against a base, honoring trailing-slash policy\n * and supporting relative segments (`.`/`..`) and absolute `to` values.\n */\nexport function resolvePath({\n base,\n to,\n trailingSlash = 'never',\n cache,\n}: ResolvePathOptions) {\n const isAbsolute = to.startsWith('/')\n const isBase = !isAbsolute && to === '.'\n\n let key\n if (cache) {\n // `trailingSlash` is static per router, so it doesn't need to be part of the cache key\n key = isAbsolute ? to : isBase ? base : base + '\\0' + to\n const cached = cache.get(key)\n if (cached) return cached\n }\n\n let baseSegments: Array<string>\n if (isBase) {\n baseSegments = base.split('/')\n } else if (isAbsolute) {\n baseSegments = to.split('/')\n } else {\n baseSegments = base.split('/')\n while (baseSegments.length > 1 && last(baseSegments) === '') {\n baseSegments.pop()\n }\n\n const toSegments = to.split('/')\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const value = toSegments[index]!\n if (value === '') {\n if (!index) {\n // Leading slash\n baseSegments = [value]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(value)\n } else {\n // ignore inter-slashes\n }\n } else if (value === '..') {\n baseSegments.pop()\n } else if (value === '.') {\n // ignore\n } else {\n baseSegments.push(value)\n }\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments) === '') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push('')\n }\n }\n\n const result = cleanPath(baseSegments.join('/')) || '/'\n if (key && cache) cache.set(key, result)\n return result\n}\n\n/**\n * Create a pre-compiled decode config from allowed characters.\n * This should be called once at router initialization.\n */\nexport function compileDecodeCharMap(\n pathParamsAllowedCharacters: ReadonlyArray<string>,\n) {\n const charMap = new Map(\n pathParamsAllowedCharacters.map((char) => [encodeURIComponent(char), char]),\n )\n // Escape special regex characters and join with |\n const pattern = Array.from(charMap.keys())\n .map((key) => key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('|')\n const regex = new RegExp(pattern, 'g')\n return (encoded: string) =>\n encoded.replace(regex, (match) => charMap.get(match) ?? match)\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n /**\n * A function that decodes a path parameter value.\n * Obtained from `compileDecodeCharMap(pathParamsAllowedCharacters)`.\n */\n decoder?: (encoded: string) => string\n /**\n * @internal\n * For testing only, in development mode we use the router.isServer value\n */\n server?: boolean\n}\n\ntype InterPolatePathResult = {\n interpolatedPath: string\n usedParams: Record<string, unknown>\n isMissingParams: boolean // true if any params were not available when being looked up in the params object\n}\n\nfunction encodeParam(\n key: string,\n params: InterpolatePathOptions['params'],\n decoder: InterpolatePathOptions['decoder'],\n): any {\n const value = params[key]\n if (typeof value !== 'string') return value\n\n if (key === '_splat') {\n // Early return if value only contains URL-safe characters (performance optimization)\n if (/^[a-zA-Z0-9\\-._~!/]*$/.test(value)) return value\n // the splat/catch-all routes shouldn't have the '/' encoded out\n // Use encodeURIComponent for each segment to properly encode spaces,\n // plus signs, and other special characters that encodeURI leaves unencoded\n return value\n .split('/')\n .map((segment) => encodePathParam(segment, decoder))\n .join('/')\n } else {\n return encodePathParam(value, decoder)\n }\n}\n\n/**\n * Interpolate params and wildcards into a route path template.\n *\n * - Encodes params safely (configurable allowed characters)\n * - Supports `{-$optional}` segments, `{prefix{$id}suffix}` and `{$}` wildcards\n */\nexport function interpolatePath({\n path,\n params,\n decoder,\n // `server` is marked @internal and stripped from .d.ts by `stripInternal`.\n // We avoid destructuring it in the function signature so the emitted\n // declaration doesn't reference a property that no longer exists.\n ...rest\n}: InterpolatePathOptions): InterPolatePathResult {\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n const usedParams: Record<string, unknown> = Object.create(null)\n\n if (!path || path === '/')\n return { interpolatedPath: '/', usedParams, isMissingParams }\n if (!path.includes('$'))\n return { interpolatedPath: path, usedParams, isMissingParams }\n\n if (isServer ?? rest.server) {\n // Fast path for common templates like `/posts/$id` or `/files/$`.\n // Braced segments (`{...}`) are more complex (prefix/suffix/optional) and are\n // handled by the general parser below.\n if (path.indexOf('{') === -1) {\n const length = path.length\n let cursor = 0\n let joined = ''\n\n while (cursor < length) {\n // Skip slashes between segments. '/' code is 47\n while (cursor < length && path.charCodeAt(cursor) === 47) cursor++\n if (cursor >= length) break\n\n const start = cursor\n let end = path.indexOf('/', cursor)\n if (end === -1) end = length\n cursor = end\n\n const part = path.substring(start, end)\n if (!part) continue\n\n // `$id` or `$` (splat). '$' code is 36\n if (part.charCodeAt(0) === 36) {\n if (part.length === 1) {\n const splat = params._splat\n usedParams._splat = splat\n // TODO: Deprecate *\n usedParams['*'] = splat\n\n if (!splat) {\n isMissingParams = true\n continue\n }\n\n const value = encodeParam('_splat', params, decoder)\n joined += '/' + value\n } else {\n const key = part.substring(1)\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const value = encodeParam(key, params, decoder) ?? 'undefined'\n joined += '/' + value\n }\n } else {\n joined += '/' + part\n }\n }\n\n if (path.endsWith('/')) joined += '/'\n\n const interpolatedPath = joined || '/'\n return { usedParams, interpolatedPath, isMissingParams }\n }\n }\n\n const length = path.length\n let cursor = 0\n let segment\n let joined = ''\n while (cursor < length) {\n const start = cursor\n segment = parseSegment(path, start, segment)\n const end = segment[5]\n cursor = end + 1\n\n if (start === end) continue\n\n const kind = segment[0]\n\n if (kind === SEGMENT_TYPE_PATHNAME) {\n joined += '/' + path.substring(start, end)\n continue\n }\n\n if (kind === SEGMENT_TYPE_WILDCARD) {\n const splat = params._splat\n usedParams._splat = splat\n // TODO: Deprecate *\n usedParams['*'] = splat\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n\n // Check if _splat parameter is missing. _splat could be missing if undefined or an empty string or some other falsy value.\n if (!splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (prefix || suffix) {\n joined += '/' + prefix + suffix\n }\n continue\n }\n\n const value = encodeParam('_splat', params, decoder)\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_PARAM) {\n const key = path.substring(segment[2], segment[3])\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decoder) ?? 'undefined'\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = path.substring(segment[2], segment[3])\n const valueRaw = params[key]\n\n // Check if optional parameter is missing or undefined\n if (valueRaw == null) continue\n\n usedParams[key] = valueRaw\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decoder) ?? ''\n joined += '/' + prefix + value + suffix\n continue\n }\n }\n\n if (path.endsWith('/')) joined += '/'\n\n const interpolatedPath = joined || '/'\n\n return { usedParams, interpolatedPath, isMissingParams }\n}\n\nfunction encodePathParam(\n value: string,\n decoder?: InterpolatePathOptions['decoder'],\n) {\n const encoded = encodeURIComponent(value)\n return decoder?.(encoded) ?? encoded\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"path.cjs","names":[],"sources":["../../src/path.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport { last } from './utils'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n SEGMENT_TYPE_WILDCARD,\n parseSegment,\n} from './new-process-route-tree'\nimport type { LRUCache } from './lru-cache'\n\n/** Join path segments, cleaning duplicate slashes between parts. */\nexport function joinPaths(paths: Array<string | undefined>) {\n return cleanPath(\n paths\n .filter((val) => {\n return val !== undefined\n })\n .join('/'),\n )\n}\n\n/** Remove repeated slashes from a path string. */\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\n/** Trim leading slashes (except preserving root '/'). */\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\n/** Trim trailing slashes (except preserving root '/'). */\nexport function trimPathRight(path: string) {\n const len = path.length\n return len > 1 && path[len - 1] === '/' ? path.replace(/\\/{1,}$/, '') : path\n}\n\n/** Trim both leading and trailing slashes. */\nexport function trimPath(path: string) {\n return trimPathRight(trimPathLeft(path))\n}\n\n/** Remove a trailing slash from value when appropriate for comparisons. */\nexport function removeTrailingSlash(value: string, basepath: string): string {\n if (value?.endsWith('/') && value !== '/' && value !== `${basepath}/`) {\n return value.slice(0, -1)\n }\n return value\n}\n\n// intended to only compare path name\n// see the usage in the isActive under useLinkProps\n// /sample/path1 = /sample/path1/\n// /sample/path1/some <> /sample/path1\n/**\n * Compare two pathnames for exact equality after normalizing trailing slashes\n * relative to the provided `basepath`.\n */\nexport function exactPathTest(\n pathName1: string,\n pathName2: string,\n basepath: string,\n): boolean {\n return (\n removeTrailingSlash(pathName1, basepath) ===\n removeTrailingSlash(pathName2, basepath)\n )\n}\n\n// When resolving relative paths, we treat all paths as if they are trailing slash\n// documents. All trailing slashes are removed after the path is resolved.\n// Here are a few examples:\n//\n// /a/b/c + ./d = /a/b/c/d\n// /a/b/c + ../d = /a/b/d\n// /a/b/c + ./d/ = /a/b/c/d\n// /a/b/c + ../d/ = /a/b/d\n// /a/b/c + ./ = /a/b/c\n//\n// Absolute paths that start with `/` short circuit the resolution process to the root\n// path.\n//\n// Here are some examples:\n//\n// /a/b/c + /d = /d\n// /a/b/c + /d/ = /d\n// /a/b/c + / = /\n//\n// Non-.-prefixed paths are still treated as relative paths, resolved like `./`\n//\n// Here are some examples:\n//\n// /a/b/c + d = /a/b/c/d\n// /a/b/c + d/ = /a/b/c/d\n// /a/b/c + d/e = /a/b/c/d/e\ninterface ResolvePathOptions {\n base: string\n to: string\n trailingSlash?: 'always' | 'never' | 'preserve'\n cache?: LRUCache<string, string>\n}\n\n/**\n * Resolve a destination path against a base, honoring trailing-slash policy\n * and supporting relative segments (`.`/`..`) and absolute `to` values.\n */\nexport function resolvePath({\n base,\n to,\n trailingSlash = 'never',\n cache,\n}: ResolvePathOptions) {\n const isBase = to === '.'\n const isAbsolute = to.startsWith('/')\n\n let key\n if (cache) {\n // `trailingSlash` is static per router, so it doesn't need to be part of the cache key\n key = isAbsolute ? to : isBase ? base : base + '\\0' + to\n const cached = cache.get(key)\n if (cached) return cached\n }\n\n let baseSegments: Array<string>\n if (isBase) {\n baseSegments = base.split('/')\n } else if (isAbsolute) {\n baseSegments = to.split('/')\n } else {\n baseSegments = base.split('/')\n while (baseSegments.length > 1 && last(baseSegments) === '') {\n baseSegments.pop()\n }\n\n const toSegments = to.split('/')\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const value = toSegments[index]!\n if (value === '') {\n if (!index) {\n // Leading slash\n baseSegments = [value]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(value)\n } else {\n // ignore inter-slashes\n }\n } else if (value === '..') {\n if (baseSegments.length > 1) {\n baseSegments.pop()\n } else {\n baseSegments = ['']\n }\n } else if (value === '.') {\n // ignore\n } else {\n baseSegments.push(value)\n }\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments) === '') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push('')\n }\n }\n\n const result = cleanPath(baseSegments.join('/')) || '/'\n if (key && cache) cache.set(key, result)\n return result\n}\n\n/**\n * Create a pre-compiled decode config from allowed characters.\n * This should be called once at router initialization.\n */\nexport function compileDecodeCharMap(\n pathParamsAllowedCharacters: ReadonlyArray<string>,\n) {\n const charMap = new Map(\n pathParamsAllowedCharacters.map((char) => [encodeURIComponent(char), char]),\n )\n // Escape special regex characters and join with |\n const pattern = Array.from(charMap.keys())\n .map((key) => key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('|')\n const regex = new RegExp(pattern, 'g')\n return (encoded: string) =>\n encoded.replace(regex, (match) => charMap.get(match) ?? match)\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n /**\n * A function that decodes a path parameter value.\n * Obtained from `compileDecodeCharMap(pathParamsAllowedCharacters)`.\n */\n decoder?: (encoded: string) => string\n /**\n * @internal\n * For testing only, in development mode we use the router.isServer value\n */\n server?: boolean\n}\n\ntype InterPolatePathResult = {\n interpolatedPath: string\n usedParams: Record<string, unknown>\n isMissingParams: boolean // true if any params were not available when being looked up in the params object\n}\n\nfunction encodeParam(\n key: string,\n params: InterpolatePathOptions['params'],\n decoder: InterpolatePathOptions['decoder'],\n): any {\n const value = params[key]\n if (typeof value !== 'string') return value\n\n if (key === '_splat') {\n // Early return if value only contains URL-safe characters (performance optimization)\n if (/^[a-zA-Z0-9\\-._~!/]*$/.test(value)) return value\n // the splat/catch-all routes shouldn't have the '/' encoded out\n // Use encodeURIComponent for each segment to properly encode spaces,\n // plus signs, and other special characters that encodeURI leaves unencoded\n return value\n .split('/')\n .map((segment) => encodePathParam(segment, decoder))\n .join('/')\n } else {\n return encodePathParam(value, decoder)\n }\n}\n\n/**\n * Interpolate params and wildcards into a route path template.\n *\n * - Encodes params safely (configurable allowed characters)\n * - Supports `{-$optional}` segments, `{prefix{$id}suffix}` and `{$}` wildcards\n */\nexport function interpolatePath({\n path,\n params,\n decoder,\n // `server` is marked @internal and stripped from .d.ts by `stripInternal`.\n // We avoid destructuring it in the function signature so the emitted\n // declaration doesn't reference a property that no longer exists.\n ...rest\n}: InterpolatePathOptions): InterPolatePathResult {\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n const usedParams: Record<string, unknown> = Object.create(null)\n\n if (!path || path === '/')\n return { interpolatedPath: '/', usedParams, isMissingParams }\n if (!path.includes('$'))\n return { interpolatedPath: path, usedParams, isMissingParams }\n\n if (isServer ?? rest.server) {\n // Fast path for common templates like `/posts/$id` or `/files/$`.\n // Braced segments (`{...}`) are more complex (prefix/suffix/optional) and are\n // handled by the general parser below.\n if (path.indexOf('{') === -1) {\n const length = path.length\n let cursor = 0\n let joined = ''\n\n while (cursor < length) {\n // Skip slashes between segments. '/' code is 47\n while (cursor < length && path.charCodeAt(cursor) === 47) cursor++\n if (cursor >= length) break\n\n const start = cursor\n let end = path.indexOf('/', cursor)\n if (end === -1) end = length\n cursor = end\n\n const part = path.substring(start, end)\n if (!part) continue\n\n // `$id` or `$` (splat). '$' code is 36\n if (part.charCodeAt(0) === 36) {\n if (part.length === 1) {\n const splat = params._splat\n usedParams._splat = splat\n // TODO: Deprecate *\n usedParams['*'] = splat\n\n if (!splat) {\n isMissingParams = true\n continue\n }\n\n const value = encodeParam('_splat', params, decoder)\n joined += '/' + value\n } else {\n const key = part.substring(1)\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const value = encodeParam(key, params, decoder) ?? 'undefined'\n joined += '/' + value\n }\n } else {\n joined += '/' + part\n }\n }\n\n if (path.endsWith('/')) joined += '/'\n\n const interpolatedPath = joined || '/'\n return { usedParams, interpolatedPath, isMissingParams }\n }\n }\n\n const length = path.length\n let cursor = 0\n let segment\n let joined = ''\n while (cursor < length) {\n const start = cursor\n segment = parseSegment(path, start, segment)\n const end = segment[5]\n cursor = end + 1\n\n if (start === end) continue\n\n const kind = segment[0]\n\n if (kind === SEGMENT_TYPE_PATHNAME) {\n joined += '/' + path.substring(start, end)\n continue\n }\n\n if (kind === SEGMENT_TYPE_WILDCARD) {\n const splat = params._splat\n usedParams._splat = splat\n // TODO: Deprecate *\n usedParams['*'] = splat\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n\n // Check if _splat parameter is missing. _splat could be missing if undefined or an empty string or some other falsy value.\n if (!splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (prefix || suffix) {\n joined += '/' + prefix + suffix\n }\n continue\n }\n\n const value = encodeParam('_splat', params, decoder)\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_PARAM) {\n const key = path.substring(segment[2], segment[3])\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decoder) ?? 'undefined'\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = path.substring(segment[2], segment[3])\n const valueRaw = params[key]\n\n // Check if optional parameter is missing or undefined\n if (valueRaw == null) continue\n\n usedParams[key] = valueRaw\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decoder) ?? ''\n joined += '/' + prefix + value + suffix\n continue\n }\n }\n\n if (path.endsWith('/')) joined += '/'\n\n const interpolatedPath = joined || '/'\n\n return { usedParams, interpolatedPath, isMissingParams }\n}\n\nfunction encodePathParam(\n value: string,\n decoder?: InterpolatePathOptions['decoder'],\n) {\n const encoded = encodeURIComponent(value)\n return decoder?.(encoded) ?? encoded\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_qss = require("./qss.cjs");
|
|
2
2
|
//#region src/searchParams.ts
|
|
3
|
+
const jsonStart = /^(?:\s|["[{\d-]|fa|nu|tr)/;
|
|
3
4
|
/** Default `parseSearch` that strips leading '?' and JSON-parses values. */
|
|
4
5
|
const defaultParseSearch = parseSearchWith(JSON.parse);
|
|
5
6
|
/** Default `stringifySearch` using JSON.stringify for complex values. */
|
|
@@ -40,15 +41,18 @@ function parseSearchWith(parser) {
|
|
|
40
41
|
* @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization
|
|
41
42
|
*/
|
|
42
43
|
function stringifySearchWith(stringify, parser) {
|
|
43
|
-
const
|
|
44
|
+
const isJsonParser = parser === JSON.parse;
|
|
44
45
|
function stringifyValue(val) {
|
|
45
|
-
if (typeof val === "object"
|
|
46
|
-
return stringify(val);
|
|
47
|
-
} catch (_err) {}
|
|
48
|
-
else if (hasParser && typeof val === "string") try {
|
|
49
|
-
parser(val);
|
|
46
|
+
if (val && typeof val === "object") try {
|
|
50
47
|
return stringify(val);
|
|
51
48
|
} catch (_err) {}
|
|
49
|
+
else if (parser && typeof val === "string") {
|
|
50
|
+
if (isJsonParser && !jsonStart.test(val)) return val;
|
|
51
|
+
try {
|
|
52
|
+
parser(val);
|
|
53
|
+
return stringify(val);
|
|
54
|
+
} catch (_err) {}
|
|
55
|
+
}
|
|
52
56
|
return val;
|
|
53
57
|
}
|
|
54
58
|
return (search) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchParams.cjs","names":[],"sources":["../../src/searchParams.ts"],"sourcesContent":["import { decode, encode } from './qss'\nimport type { AnySchema } from './validators'\n\n/** Default `parseSearch` that strips leading '?' and JSON-parses values. */\nexport const defaultParseSearch = parseSearchWith(JSON.parse)\n/** Default `stringifySearch` using JSON.stringify for complex values. */\nexport const defaultStringifySearch = stringifySearchWith(\n JSON.stringify,\n JSON.parse,\n)\n\n/**\n * Build a `parseSearch` function using a provided JSON-like parser.\n *\n * The returned function strips a leading `?`, decodes values, and attempts to\n * JSON-parse string values using the given `parser`.\n *\n * @param parser Function to parse a string value (e.g. `JSON.parse`).\n * @returns A `parseSearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function parseSearchWith(parser: (str: string) => any) {\n return (searchStr: string): AnySchema => {\n if (searchStr[0] === '?') {\n searchStr = searchStr.substring(1)\n }\n\n const query: Record<string, unknown> = decode(searchStr)\n\n // Try to parse any query params that might be json\n for (const key in query) {\n const value = query[key]\n if (typeof value === 'string') {\n try {\n query[key] = parser(value)\n } catch (_err) {\n // silent\n }\n }\n }\n\n return query\n }\n}\n\n/**\n * Build a `stringifySearch` function using a provided serializer.\n *\n * Non-primitive values are serialized with `stringify`. If a `parser` is\n * supplied, string values that are parseable are re-serialized to ensure\n * symmetry with `parseSearch`.\n *\n * @param stringify Function to serialize a value (e.g. `JSON.stringify`).\n * @param parser Optional parser to detect parseable strings.\n * @returns A `stringifySearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function stringifySearchWith(\n stringify: (search: any) => string,\n parser?: (str: string) => any,\n) {\n const
|
|
1
|
+
{"version":3,"file":"searchParams.cjs","names":[],"sources":["../../src/searchParams.ts"],"sourcesContent":["import { decode, encode } from './qss'\nimport type { AnySchema } from './validators'\n\n// JSON can start with whitespace, \", [, {, -, a digit, or fa/nu/tr.\n// False positives safely fall through to JSON.parse.\nconst jsonStart = /^(?:\\s|[\"[{\\d-]|fa|nu|tr)/\n\n/** Default `parseSearch` that strips leading '?' and JSON-parses values. */\nexport const defaultParseSearch = parseSearchWith(JSON.parse)\n/** Default `stringifySearch` using JSON.stringify for complex values. */\nexport const defaultStringifySearch = stringifySearchWith(\n JSON.stringify,\n JSON.parse,\n)\n\n/**\n * Build a `parseSearch` function using a provided JSON-like parser.\n *\n * The returned function strips a leading `?`, decodes values, and attempts to\n * JSON-parse string values using the given `parser`.\n *\n * @param parser Function to parse a string value (e.g. `JSON.parse`).\n * @returns A `parseSearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function parseSearchWith(parser: (str: string) => any) {\n return (searchStr: string): AnySchema => {\n if (searchStr[0] === '?') {\n searchStr = searchStr.substring(1)\n }\n\n const query: Record<string, unknown> = decode(searchStr)\n\n // Try to parse any query params that might be json\n for (const key in query) {\n const value = query[key]\n if (typeof value === 'string') {\n try {\n query[key] = parser(value)\n } catch (_err) {\n // silent\n }\n }\n }\n\n return query\n }\n}\n\n/**\n * Build a `stringifySearch` function using a provided serializer.\n *\n * Non-primitive values are serialized with `stringify`. If a `parser` is\n * supplied, string values that are parseable are re-serialized to ensure\n * symmetry with `parseSearch`.\n *\n * @param stringify Function to serialize a value (e.g. `JSON.stringify`).\n * @param parser Optional parser to detect parseable strings.\n * @returns A `stringifySearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function stringifySearchWith(\n stringify: (search: any) => string,\n parser?: (str: string) => any,\n) {\n const isJsonParser = parser === JSON.parse\n function stringifyValue(val: any) {\n if (val && typeof val === 'object') {\n try {\n return stringify(val)\n } catch (_err) {\n // silent\n }\n } else if (parser && typeof val === 'string') {\n // Skip JSON.parse when the value cannot begin valid JSON.\n if (isJsonParser && !jsonStart.test(val)) {\n return val\n }\n try {\n // Check if it's a valid parseable string.\n // If it is, then stringify it again.\n parser(val)\n return stringify(val)\n } catch (_err) {\n // silent\n }\n }\n return val\n }\n\n return (search: Record<string, any>) => {\n const searchStr = encode(search, stringifyValue)\n return searchStr ? `?${searchStr}` : ''\n }\n}\n\nexport type SearchSerializer = (searchObj: Record<string, any>) => string\nexport type SearchParser = (searchStr: string) => Record<string, any>\n"],"mappings":";;AAKA,MAAM,YAAY;;AAGlB,MAAa,qBAAqB,gBAAgB,KAAK,KAAK;;AAE5D,MAAa,yBAAyB,oBACpC,KAAK,WACL,KAAK,KACP;;;;;;;;;;;AAYA,SAAgB,gBAAgB,QAA8B;CAC5D,QAAQ,cAAiC;EACvC,IAAI,UAAU,OAAO,KACnB,YAAY,UAAU,UAAU,CAAC;EAGnC,MAAM,QAAiC,YAAA,OAAO,SAAS;EAGvD,KAAK,MAAM,OAAO,OAAO;GACvB,MAAM,QAAQ,MAAM;GACpB,IAAI,OAAO,UAAU,UACnB,IAAI;IACF,MAAM,OAAO,OAAO,KAAK;GAC3B,SAAS,MAAM,CAEf;EAEJ;EAEA,OAAO;CACT;AACF;;;;;;;;;;;;;AAcA,SAAgB,oBACd,WACA,QACA;CACA,MAAM,eAAe,WAAW,KAAK;CACrC,SAAS,eAAe,KAAU;EAChC,IAAI,OAAO,OAAO,QAAQ,UACxB,IAAI;GACF,OAAO,UAAU,GAAG;EACtB,SAAS,MAAM,CAEf;OACK,IAAI,UAAU,OAAO,QAAQ,UAAU;GAE5C,IAAI,gBAAgB,CAAC,UAAU,KAAK,GAAG,GACrC,OAAO;GAET,IAAI;IAGF,OAAO,GAAG;IACV,OAAO,UAAU,GAAG;GACtB,SAAS,MAAM,CAEf;EACF;EACA,OAAO;CACT;CAEA,QAAQ,WAAgC;EACtC,MAAM,YAAY,YAAA,OAAO,QAAQ,cAAc;EAC/C,OAAO,YAAY,IAAI,cAAc;CACvC;AACF"}
|
package/dist/cjs/utils.cjs
CHANGED
|
@@ -73,11 +73,10 @@ function replaceEqualDeep(prev, _next, _makeObj = () => ({}), _depth = 0) {
|
|
|
73
73
|
* Optimized for the common case where objects have no symbol properties.
|
|
74
74
|
*/
|
|
75
75
|
function getEnumerableOwnKeys(o) {
|
|
76
|
-
const
|
|
77
|
-
|
|
76
|
+
const keys = Object.keys(o);
|
|
77
|
+
if (keys.length !== Object.getOwnPropertyNames(o).length) return false;
|
|
78
78
|
const symbols = Object.getOwnPropertySymbols(o);
|
|
79
|
-
if (symbols.length === 0) return
|
|
80
|
-
const keys = names;
|
|
79
|
+
if (symbols.length === 0) return keys;
|
|
81
80
|
for (const symbol of symbols) {
|
|
82
81
|
if (!isEnumerable.call(o, symbol)) return false;
|
|
83
82
|
keys.push(symbol);
|
package/dist/cjs/utils.cjs.map
CHANGED
|
@@ -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\nexport const hasOwn = Object.prototype.hasOwnProperty\nconst isEnumerable = Object.prototype.propertyIsEnumerable\n\nexport function hasKeys(obj: Record<string, unknown>) {\n for (const key in obj) {\n if (hasOwn.call(obj, key)) return true\n }\n return false\n}\n\nexport const 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 * Re-encode characters that are unsafe in URL paths.\n * Includes ASCII control characters (0x00-0x1F, 0x7F) and a subset of the\n * WHATWG URL \"path percent-encode set\" (\", <, >, `, {, }).\n *\n * Space (0x20) is intentionally excluded — decodeURI decodes %20 to space\n * and the router stores decoded spaces in location.pathname. The existing\n * encodePathLikeUrl already handles re-encoding spaces for outgoing URLs.\n *\n * These characters are decoded by decodeURI but must remain percent-encoded\n * in paths to match how upstream layers (CDNs, edge middleware, browsers)\n * interpret the URL, preventing infinite redirect loops and path mismatches.\n */\n// eslint-disable-next-line no-control-regex\nconst PATH_UNSAFE_RE = /[\\x00-\\x1f\\x7f\"<>`{}]/g\n\nfunction sanitizePathSegment(segment: string): string {\n return segment.replace(\n PATH_UNSAFE_RE,\n (ch) => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'),\n )\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 // This is defense-in-depth: since control characters are no longer decoded,\n // paths like \"/%0d/evil.com\" can no longer become \"//evil.com\". But we keep\n // this check to guard against other edge cases.\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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"}
|
|
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\nexport const hasOwn = Object.prototype.hasOwnProperty\nconst isEnumerable = Object.prototype.propertyIsEnumerable\n\nexport function hasKeys(obj: Record<string, unknown>) {\n for (const key in obj) {\n if (hasOwn.call(obj, key)) return true\n }\n return false\n}\n\nexport const 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 // `Object.keys` returns only enumerable own string keys natively (no per-key\n // JS callback). If it has fewer entries than `getOwnPropertyNames` (all own\n // string keys), the object has a non-enumerable own string prop and is not\n // \"clone-friendly\" -> bail. This replaces an O(n) loop of\n // `propertyIsEnumerable` calls with two native calls.\n const keys = Object.keys(o)\n if (keys.length !== Object.getOwnPropertyNames(o).length) {\n 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 enumerable string keys directly\n if (symbols.length === 0) {\n return keys\n }\n\n // Slow path: has symbols, include only enumerable ones, bail on any\n // non-enumerable symbol so it round-trips like the string-key check above.\n for (const symbol of symbols) {\n if (!isEnumerable.call(o, symbol)) {\n return false\n }\n ;(keys as Array<string | symbol>).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 * Re-encode characters that are unsafe in URL paths.\n * Includes ASCII control characters (0x00-0x1F, 0x7F) and a subset of the\n * WHATWG URL \"path percent-encode set\" (\", <, >, `, {, }).\n *\n * Space (0x20) is intentionally excluded — decodeURI decodes %20 to space\n * and the router stores decoded spaces in location.pathname. The existing\n * encodePathLikeUrl already handles re-encoding spaces for outgoing URLs.\n *\n * These characters are decoded by decodeURI but must remain percent-encoded\n * in paths to match how upstream layers (CDNs, edge middleware, browsers)\n * interpret the URL, preventing infinite redirect loops and path mismatches.\n */\n// eslint-disable-next-line no-control-regex\nconst PATH_UNSAFE_RE = /[\\x00-\\x1f\\x7f\"<>`{}]/g\n\nfunction sanitizePathSegment(segment: string): string {\n return segment.replace(\n PATH_UNSAFE_RE,\n (ch) => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'),\n )\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 // This is defense-in-depth: since control characters are no longer decoded,\n // paths like \"/%0d/evil.com\" can no longer become \"//evil.com\". But we keep\n // this check to guard against other edge cases.\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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"}
|
package/dist/esm/path.js
CHANGED
|
@@ -42,8 +42,8 @@ function exactPathTest(pathName1, pathName2, basepath) {
|
|
|
42
42
|
* and supporting relative segments (`.`/`..`) and absolute `to` values.
|
|
43
43
|
*/
|
|
44
44
|
function resolvePath({ base, to, trailingSlash = "never", cache }) {
|
|
45
|
+
const isBase = to === ".";
|
|
45
46
|
const isAbsolute = to.startsWith("/");
|
|
46
|
-
const isBase = !isAbsolute && to === ".";
|
|
47
47
|
let key;
|
|
48
48
|
if (cache) {
|
|
49
49
|
key = isAbsolute ? to : isBase ? base : base + "\0" + to;
|
|
@@ -62,7 +62,8 @@ function resolvePath({ base, to, trailingSlash = "never", cache }) {
|
|
|
62
62
|
if (value === "") {
|
|
63
63
|
if (!index) baseSegments = [value];
|
|
64
64
|
else if (index === length - 1) baseSegments.push(value);
|
|
65
|
-
} else if (value === "..") baseSegments.pop();
|
|
65
|
+
} else if (value === "..") if (baseSegments.length > 1) baseSegments.pop();
|
|
66
|
+
else baseSegments = [""];
|
|
66
67
|
else if (value === ".") {} else baseSegments.push(value);
|
|
67
68
|
}
|
|
68
69
|
}
|
package/dist/esm/path.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.js","names":[],"sources":["../../src/path.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport { last } from './utils'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n SEGMENT_TYPE_WILDCARD,\n parseSegment,\n} from './new-process-route-tree'\nimport type { LRUCache } from './lru-cache'\n\n/** Join path segments, cleaning duplicate slashes between parts. */\nexport function joinPaths(paths: Array<string | undefined>) {\n return cleanPath(\n paths\n .filter((val) => {\n return val !== undefined\n })\n .join('/'),\n )\n}\n\n/** Remove repeated slashes from a path string. */\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\n/** Trim leading slashes (except preserving root '/'). */\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\n/** Trim trailing slashes (except preserving root '/'). */\nexport function trimPathRight(path: string) {\n const len = path.length\n return len > 1 && path[len - 1] === '/' ? path.replace(/\\/{1,}$/, '') : path\n}\n\n/** Trim both leading and trailing slashes. */\nexport function trimPath(path: string) {\n return trimPathRight(trimPathLeft(path))\n}\n\n/** Remove a trailing slash from value when appropriate for comparisons. */\nexport function removeTrailingSlash(value: string, basepath: string): string {\n if (value?.endsWith('/') && value !== '/' && value !== `${basepath}/`) {\n return value.slice(0, -1)\n }\n return value\n}\n\n// intended to only compare path name\n// see the usage in the isActive under useLinkProps\n// /sample/path1 = /sample/path1/\n// /sample/path1/some <> /sample/path1\n/**\n * Compare two pathnames for exact equality after normalizing trailing slashes\n * relative to the provided `basepath`.\n */\nexport function exactPathTest(\n pathName1: string,\n pathName2: string,\n basepath: string,\n): boolean {\n return (\n removeTrailingSlash(pathName1, basepath) ===\n removeTrailingSlash(pathName2, basepath)\n )\n}\n\n// When resolving relative paths, we treat all paths as if they are trailing slash\n// documents. All trailing slashes are removed after the path is resolved.\n// Here are a few examples:\n//\n// /a/b/c + ./d = /a/b/c/d\n// /a/b/c + ../d = /a/b/d\n// /a/b/c + ./d/ = /a/b/c/d\n// /a/b/c + ../d/ = /a/b/d\n// /a/b/c + ./ = /a/b/c\n//\n// Absolute paths that start with `/` short circuit the resolution process to the root\n// path.\n//\n// Here are some examples:\n//\n// /a/b/c + /d = /d\n// /a/b/c + /d/ = /d\n// /a/b/c + / = /\n//\n// Non-.-prefixed paths are still treated as relative paths, resolved like `./`\n//\n// Here are some examples:\n//\n// /a/b/c + d = /a/b/c/d\n// /a/b/c + d/ = /a/b/c/d\n// /a/b/c + d/e = /a/b/c/d/e\ninterface ResolvePathOptions {\n base: string\n to: string\n trailingSlash?: 'always' | 'never' | 'preserve'\n cache?: LRUCache<string, string>\n}\n\n/**\n * Resolve a destination path against a base, honoring trailing-slash policy\n * and supporting relative segments (`.`/`..`) and absolute `to` values.\n */\nexport function resolvePath({\n base,\n to,\n trailingSlash = 'never',\n cache,\n}: ResolvePathOptions) {\n const isAbsolute = to.startsWith('/')\n const isBase = !isAbsolute && to === '.'\n\n let key\n if (cache) {\n // `trailingSlash` is static per router, so it doesn't need to be part of the cache key\n key = isAbsolute ? to : isBase ? base : base + '\\0' + to\n const cached = cache.get(key)\n if (cached) return cached\n }\n\n let baseSegments: Array<string>\n if (isBase) {\n baseSegments = base.split('/')\n } else if (isAbsolute) {\n baseSegments = to.split('/')\n } else {\n baseSegments = base.split('/')\n while (baseSegments.length > 1 && last(baseSegments) === '') {\n baseSegments.pop()\n }\n\n const toSegments = to.split('/')\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const value = toSegments[index]!\n if (value === '') {\n if (!index) {\n // Leading slash\n baseSegments = [value]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(value)\n } else {\n // ignore inter-slashes\n }\n } else if (value === '..') {\n baseSegments.pop()\n } else if (value === '.') {\n // ignore\n } else {\n baseSegments.push(value)\n }\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments) === '') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push('')\n }\n }\n\n const result = cleanPath(baseSegments.join('/')) || '/'\n if (key && cache) cache.set(key, result)\n return result\n}\n\n/**\n * Create a pre-compiled decode config from allowed characters.\n * This should be called once at router initialization.\n */\nexport function compileDecodeCharMap(\n pathParamsAllowedCharacters: ReadonlyArray<string>,\n) {\n const charMap = new Map(\n pathParamsAllowedCharacters.map((char) => [encodeURIComponent(char), char]),\n )\n // Escape special regex characters and join with |\n const pattern = Array.from(charMap.keys())\n .map((key) => key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('|')\n const regex = new RegExp(pattern, 'g')\n return (encoded: string) =>\n encoded.replace(regex, (match) => charMap.get(match) ?? match)\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n /**\n * A function that decodes a path parameter value.\n * Obtained from `compileDecodeCharMap(pathParamsAllowedCharacters)`.\n */\n decoder?: (encoded: string) => string\n /**\n * @internal\n * For testing only, in development mode we use the router.isServer value\n */\n server?: boolean\n}\n\ntype InterPolatePathResult = {\n interpolatedPath: string\n usedParams: Record<string, unknown>\n isMissingParams: boolean // true if any params were not available when being looked up in the params object\n}\n\nfunction encodeParam(\n key: string,\n params: InterpolatePathOptions['params'],\n decoder: InterpolatePathOptions['decoder'],\n): any {\n const value = params[key]\n if (typeof value !== 'string') return value\n\n if (key === '_splat') {\n // Early return if value only contains URL-safe characters (performance optimization)\n if (/^[a-zA-Z0-9\\-._~!/]*$/.test(value)) return value\n // the splat/catch-all routes shouldn't have the '/' encoded out\n // Use encodeURIComponent for each segment to properly encode spaces,\n // plus signs, and other special characters that encodeURI leaves unencoded\n return value\n .split('/')\n .map((segment) => encodePathParam(segment, decoder))\n .join('/')\n } else {\n return encodePathParam(value, decoder)\n }\n}\n\n/**\n * Interpolate params and wildcards into a route path template.\n *\n * - Encodes params safely (configurable allowed characters)\n * - Supports `{-$optional}` segments, `{prefix{$id}suffix}` and `{$}` wildcards\n */\nexport function interpolatePath({\n path,\n params,\n decoder,\n // `server` is marked @internal and stripped from .d.ts by `stripInternal`.\n // We avoid destructuring it in the function signature so the emitted\n // declaration doesn't reference a property that no longer exists.\n ...rest\n}: InterpolatePathOptions): InterPolatePathResult {\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n const usedParams: Record<string, unknown> = Object.create(null)\n\n if (!path || path === '/')\n return { interpolatedPath: '/', usedParams, isMissingParams }\n if (!path.includes('$'))\n return { interpolatedPath: path, usedParams, isMissingParams }\n\n if (isServer ?? rest.server) {\n // Fast path for common templates like `/posts/$id` or `/files/$`.\n // Braced segments (`{...}`) are more complex (prefix/suffix/optional) and are\n // handled by the general parser below.\n if (path.indexOf('{') === -1) {\n const length = path.length\n let cursor = 0\n let joined = ''\n\n while (cursor < length) {\n // Skip slashes between segments. '/' code is 47\n while (cursor < length && path.charCodeAt(cursor) === 47) cursor++\n if (cursor >= length) break\n\n const start = cursor\n let end = path.indexOf('/', cursor)\n if (end === -1) end = length\n cursor = end\n\n const part = path.substring(start, end)\n if (!part) continue\n\n // `$id` or `$` (splat). '$' code is 36\n if (part.charCodeAt(0) === 36) {\n if (part.length === 1) {\n const splat = params._splat\n usedParams._splat = splat\n // TODO: Deprecate *\n usedParams['*'] = splat\n\n if (!splat) {\n isMissingParams = true\n continue\n }\n\n const value = encodeParam('_splat', params, decoder)\n joined += '/' + value\n } else {\n const key = part.substring(1)\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const value = encodeParam(key, params, decoder) ?? 'undefined'\n joined += '/' + value\n }\n } else {\n joined += '/' + part\n }\n }\n\n if (path.endsWith('/')) joined += '/'\n\n const interpolatedPath = joined || '/'\n return { usedParams, interpolatedPath, isMissingParams }\n }\n }\n\n const length = path.length\n let cursor = 0\n let segment\n let joined = ''\n while (cursor < length) {\n const start = cursor\n segment = parseSegment(path, start, segment)\n const end = segment[5]\n cursor = end + 1\n\n if (start === end) continue\n\n const kind = segment[0]\n\n if (kind === SEGMENT_TYPE_PATHNAME) {\n joined += '/' + path.substring(start, end)\n continue\n }\n\n if (kind === SEGMENT_TYPE_WILDCARD) {\n const splat = params._splat\n usedParams._splat = splat\n // TODO: Deprecate *\n usedParams['*'] = splat\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n\n // Check if _splat parameter is missing. _splat could be missing if undefined or an empty string or some other falsy value.\n if (!splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (prefix || suffix) {\n joined += '/' + prefix + suffix\n }\n continue\n }\n\n const value = encodeParam('_splat', params, decoder)\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_PARAM) {\n const key = path.substring(segment[2], segment[3])\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decoder) ?? 'undefined'\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = path.substring(segment[2], segment[3])\n const valueRaw = params[key]\n\n // Check if optional parameter is missing or undefined\n if (valueRaw == null) continue\n\n usedParams[key] = valueRaw\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decoder) ?? ''\n joined += '/' + prefix + value + suffix\n continue\n }\n }\n\n if (path.endsWith('/')) joined += '/'\n\n const interpolatedPath = joined || '/'\n\n return { usedParams, interpolatedPath, isMissingParams }\n}\n\nfunction encodePathParam(\n value: string,\n decoder?: InterpolatePathOptions['decoder'],\n) {\n const encoded = encodeURIComponent(value)\n return decoder?.(encoded) ?? encoded\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"path.js","names":[],"sources":["../../src/path.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport { last } from './utils'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n SEGMENT_TYPE_WILDCARD,\n parseSegment,\n} from './new-process-route-tree'\nimport type { LRUCache } from './lru-cache'\n\n/** Join path segments, cleaning duplicate slashes between parts. */\nexport function joinPaths(paths: Array<string | undefined>) {\n return cleanPath(\n paths\n .filter((val) => {\n return val !== undefined\n })\n .join('/'),\n )\n}\n\n/** Remove repeated slashes from a path string. */\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\n/** Trim leading slashes (except preserving root '/'). */\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\n/** Trim trailing slashes (except preserving root '/'). */\nexport function trimPathRight(path: string) {\n const len = path.length\n return len > 1 && path[len - 1] === '/' ? path.replace(/\\/{1,}$/, '') : path\n}\n\n/** Trim both leading and trailing slashes. */\nexport function trimPath(path: string) {\n return trimPathRight(trimPathLeft(path))\n}\n\n/** Remove a trailing slash from value when appropriate for comparisons. */\nexport function removeTrailingSlash(value: string, basepath: string): string {\n if (value?.endsWith('/') && value !== '/' && value !== `${basepath}/`) {\n return value.slice(0, -1)\n }\n return value\n}\n\n// intended to only compare path name\n// see the usage in the isActive under useLinkProps\n// /sample/path1 = /sample/path1/\n// /sample/path1/some <> /sample/path1\n/**\n * Compare two pathnames for exact equality after normalizing trailing slashes\n * relative to the provided `basepath`.\n */\nexport function exactPathTest(\n pathName1: string,\n pathName2: string,\n basepath: string,\n): boolean {\n return (\n removeTrailingSlash(pathName1, basepath) ===\n removeTrailingSlash(pathName2, basepath)\n )\n}\n\n// When resolving relative paths, we treat all paths as if they are trailing slash\n// documents. All trailing slashes are removed after the path is resolved.\n// Here are a few examples:\n//\n// /a/b/c + ./d = /a/b/c/d\n// /a/b/c + ../d = /a/b/d\n// /a/b/c + ./d/ = /a/b/c/d\n// /a/b/c + ../d/ = /a/b/d\n// /a/b/c + ./ = /a/b/c\n//\n// Absolute paths that start with `/` short circuit the resolution process to the root\n// path.\n//\n// Here are some examples:\n//\n// /a/b/c + /d = /d\n// /a/b/c + /d/ = /d\n// /a/b/c + / = /\n//\n// Non-.-prefixed paths are still treated as relative paths, resolved like `./`\n//\n// Here are some examples:\n//\n// /a/b/c + d = /a/b/c/d\n// /a/b/c + d/ = /a/b/c/d\n// /a/b/c + d/e = /a/b/c/d/e\ninterface ResolvePathOptions {\n base: string\n to: string\n trailingSlash?: 'always' | 'never' | 'preserve'\n cache?: LRUCache<string, string>\n}\n\n/**\n * Resolve a destination path against a base, honoring trailing-slash policy\n * and supporting relative segments (`.`/`..`) and absolute `to` values.\n */\nexport function resolvePath({\n base,\n to,\n trailingSlash = 'never',\n cache,\n}: ResolvePathOptions) {\n const isBase = to === '.'\n const isAbsolute = to.startsWith('/')\n\n let key\n if (cache) {\n // `trailingSlash` is static per router, so it doesn't need to be part of the cache key\n key = isAbsolute ? to : isBase ? base : base + '\\0' + to\n const cached = cache.get(key)\n if (cached) return cached\n }\n\n let baseSegments: Array<string>\n if (isBase) {\n baseSegments = base.split('/')\n } else if (isAbsolute) {\n baseSegments = to.split('/')\n } else {\n baseSegments = base.split('/')\n while (baseSegments.length > 1 && last(baseSegments) === '') {\n baseSegments.pop()\n }\n\n const toSegments = to.split('/')\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const value = toSegments[index]!\n if (value === '') {\n if (!index) {\n // Leading slash\n baseSegments = [value]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(value)\n } else {\n // ignore inter-slashes\n }\n } else if (value === '..') {\n if (baseSegments.length > 1) {\n baseSegments.pop()\n } else {\n baseSegments = ['']\n }\n } else if (value === '.') {\n // ignore\n } else {\n baseSegments.push(value)\n }\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments) === '') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push('')\n }\n }\n\n const result = cleanPath(baseSegments.join('/')) || '/'\n if (key && cache) cache.set(key, result)\n return result\n}\n\n/**\n * Create a pre-compiled decode config from allowed characters.\n * This should be called once at router initialization.\n */\nexport function compileDecodeCharMap(\n pathParamsAllowedCharacters: ReadonlyArray<string>,\n) {\n const charMap = new Map(\n pathParamsAllowedCharacters.map((char) => [encodeURIComponent(char), char]),\n )\n // Escape special regex characters and join with |\n const pattern = Array.from(charMap.keys())\n .map((key) => key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('|')\n const regex = new RegExp(pattern, 'g')\n return (encoded: string) =>\n encoded.replace(regex, (match) => charMap.get(match) ?? match)\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n /**\n * A function that decodes a path parameter value.\n * Obtained from `compileDecodeCharMap(pathParamsAllowedCharacters)`.\n */\n decoder?: (encoded: string) => string\n /**\n * @internal\n * For testing only, in development mode we use the router.isServer value\n */\n server?: boolean\n}\n\ntype InterPolatePathResult = {\n interpolatedPath: string\n usedParams: Record<string, unknown>\n isMissingParams: boolean // true if any params were not available when being looked up in the params object\n}\n\nfunction encodeParam(\n key: string,\n params: InterpolatePathOptions['params'],\n decoder: InterpolatePathOptions['decoder'],\n): any {\n const value = params[key]\n if (typeof value !== 'string') return value\n\n if (key === '_splat') {\n // Early return if value only contains URL-safe characters (performance optimization)\n if (/^[a-zA-Z0-9\\-._~!/]*$/.test(value)) return value\n // the splat/catch-all routes shouldn't have the '/' encoded out\n // Use encodeURIComponent for each segment to properly encode spaces,\n // plus signs, and other special characters that encodeURI leaves unencoded\n return value\n .split('/')\n .map((segment) => encodePathParam(segment, decoder))\n .join('/')\n } else {\n return encodePathParam(value, decoder)\n }\n}\n\n/**\n * Interpolate params and wildcards into a route path template.\n *\n * - Encodes params safely (configurable allowed characters)\n * - Supports `{-$optional}` segments, `{prefix{$id}suffix}` and `{$}` wildcards\n */\nexport function interpolatePath({\n path,\n params,\n decoder,\n // `server` is marked @internal and stripped from .d.ts by `stripInternal`.\n // We avoid destructuring it in the function signature so the emitted\n // declaration doesn't reference a property that no longer exists.\n ...rest\n}: InterpolatePathOptions): InterPolatePathResult {\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n const usedParams: Record<string, unknown> = Object.create(null)\n\n if (!path || path === '/')\n return { interpolatedPath: '/', usedParams, isMissingParams }\n if (!path.includes('$'))\n return { interpolatedPath: path, usedParams, isMissingParams }\n\n if (isServer ?? rest.server) {\n // Fast path for common templates like `/posts/$id` or `/files/$`.\n // Braced segments (`{...}`) are more complex (prefix/suffix/optional) and are\n // handled by the general parser below.\n if (path.indexOf('{') === -1) {\n const length = path.length\n let cursor = 0\n let joined = ''\n\n while (cursor < length) {\n // Skip slashes between segments. '/' code is 47\n while (cursor < length && path.charCodeAt(cursor) === 47) cursor++\n if (cursor >= length) break\n\n const start = cursor\n let end = path.indexOf('/', cursor)\n if (end === -1) end = length\n cursor = end\n\n const part = path.substring(start, end)\n if (!part) continue\n\n // `$id` or `$` (splat). '$' code is 36\n if (part.charCodeAt(0) === 36) {\n if (part.length === 1) {\n const splat = params._splat\n usedParams._splat = splat\n // TODO: Deprecate *\n usedParams['*'] = splat\n\n if (!splat) {\n isMissingParams = true\n continue\n }\n\n const value = encodeParam('_splat', params, decoder)\n joined += '/' + value\n } else {\n const key = part.substring(1)\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const value = encodeParam(key, params, decoder) ?? 'undefined'\n joined += '/' + value\n }\n } else {\n joined += '/' + part\n }\n }\n\n if (path.endsWith('/')) joined += '/'\n\n const interpolatedPath = joined || '/'\n return { usedParams, interpolatedPath, isMissingParams }\n }\n }\n\n const length = path.length\n let cursor = 0\n let segment\n let joined = ''\n while (cursor < length) {\n const start = cursor\n segment = parseSegment(path, start, segment)\n const end = segment[5]\n cursor = end + 1\n\n if (start === end) continue\n\n const kind = segment[0]\n\n if (kind === SEGMENT_TYPE_PATHNAME) {\n joined += '/' + path.substring(start, end)\n continue\n }\n\n if (kind === SEGMENT_TYPE_WILDCARD) {\n const splat = params._splat\n usedParams._splat = splat\n // TODO: Deprecate *\n usedParams['*'] = splat\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n\n // Check if _splat parameter is missing. _splat could be missing if undefined or an empty string or some other falsy value.\n if (!splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (prefix || suffix) {\n joined += '/' + prefix + suffix\n }\n continue\n }\n\n const value = encodeParam('_splat', params, decoder)\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_PARAM) {\n const key = path.substring(segment[2], segment[3])\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decoder) ?? 'undefined'\n joined += '/' + prefix + value + suffix\n continue\n }\n\n if (kind === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = path.substring(segment[2], segment[3])\n const valueRaw = params[key]\n\n // Check if optional parameter is missing or undefined\n if (valueRaw == null) continue\n\n usedParams[key] = valueRaw\n\n const prefix = path.substring(start, segment[1])\n const suffix = path.substring(segment[4], end)\n const value = encodeParam(key, params, decoder) ?? ''\n joined += '/' + prefix + value + suffix\n continue\n }\n }\n\n if (path.endsWith('/')) joined += '/'\n\n const interpolatedPath = joined || '/'\n\n return { usedParams, interpolatedPath, isMissingParams }\n}\n\nfunction encodePathParam(\n value: string,\n decoder?: InterpolatePathOptions['decoder'],\n) {\n const encoded = encodeURIComponent(value)\n return decoder?.(encoded) ?? encoded\n}\n"],"mappings":"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"}
|
package/dist/esm/searchParams.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { decode, encode } from "./qss.js";
|
|
2
2
|
//#region src/searchParams.ts
|
|
3
|
+
const jsonStart = /^(?:\s|["[{\d-]|fa|nu|tr)/;
|
|
3
4
|
/** Default `parseSearch` that strips leading '?' and JSON-parses values. */
|
|
4
5
|
const defaultParseSearch = parseSearchWith(JSON.parse);
|
|
5
6
|
/** Default `stringifySearch` using JSON.stringify for complex values. */
|
|
@@ -40,15 +41,18 @@ function parseSearchWith(parser) {
|
|
|
40
41
|
* @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization
|
|
41
42
|
*/
|
|
42
43
|
function stringifySearchWith(stringify, parser) {
|
|
43
|
-
const
|
|
44
|
+
const isJsonParser = parser === JSON.parse;
|
|
44
45
|
function stringifyValue(val) {
|
|
45
|
-
if (typeof val === "object"
|
|
46
|
-
return stringify(val);
|
|
47
|
-
} catch (_err) {}
|
|
48
|
-
else if (hasParser && typeof val === "string") try {
|
|
49
|
-
parser(val);
|
|
46
|
+
if (val && typeof val === "object") try {
|
|
50
47
|
return stringify(val);
|
|
51
48
|
} catch (_err) {}
|
|
49
|
+
else if (parser && typeof val === "string") {
|
|
50
|
+
if (isJsonParser && !jsonStart.test(val)) return val;
|
|
51
|
+
try {
|
|
52
|
+
parser(val);
|
|
53
|
+
return stringify(val);
|
|
54
|
+
} catch (_err) {}
|
|
55
|
+
}
|
|
52
56
|
return val;
|
|
53
57
|
}
|
|
54
58
|
return (search) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchParams.js","names":[],"sources":["../../src/searchParams.ts"],"sourcesContent":["import { decode, encode } from './qss'\nimport type { AnySchema } from './validators'\n\n/** Default `parseSearch` that strips leading '?' and JSON-parses values. */\nexport const defaultParseSearch = parseSearchWith(JSON.parse)\n/** Default `stringifySearch` using JSON.stringify for complex values. */\nexport const defaultStringifySearch = stringifySearchWith(\n JSON.stringify,\n JSON.parse,\n)\n\n/**\n * Build a `parseSearch` function using a provided JSON-like parser.\n *\n * The returned function strips a leading `?`, decodes values, and attempts to\n * JSON-parse string values using the given `parser`.\n *\n * @param parser Function to parse a string value (e.g. `JSON.parse`).\n * @returns A `parseSearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function parseSearchWith(parser: (str: string) => any) {\n return (searchStr: string): AnySchema => {\n if (searchStr[0] === '?') {\n searchStr = searchStr.substring(1)\n }\n\n const query: Record<string, unknown> = decode(searchStr)\n\n // Try to parse any query params that might be json\n for (const key in query) {\n const value = query[key]\n if (typeof value === 'string') {\n try {\n query[key] = parser(value)\n } catch (_err) {\n // silent\n }\n }\n }\n\n return query\n }\n}\n\n/**\n * Build a `stringifySearch` function using a provided serializer.\n *\n * Non-primitive values are serialized with `stringify`. If a `parser` is\n * supplied, string values that are parseable are re-serialized to ensure\n * symmetry with `parseSearch`.\n *\n * @param stringify Function to serialize a value (e.g. `JSON.stringify`).\n * @param parser Optional parser to detect parseable strings.\n * @returns A `stringifySearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function stringifySearchWith(\n stringify: (search: any) => string,\n parser?: (str: string) => any,\n) {\n const
|
|
1
|
+
{"version":3,"file":"searchParams.js","names":[],"sources":["../../src/searchParams.ts"],"sourcesContent":["import { decode, encode } from './qss'\nimport type { AnySchema } from './validators'\n\n// JSON can start with whitespace, \", [, {, -, a digit, or fa/nu/tr.\n// False positives safely fall through to JSON.parse.\nconst jsonStart = /^(?:\\s|[\"[{\\d-]|fa|nu|tr)/\n\n/** Default `parseSearch` that strips leading '?' and JSON-parses values. */\nexport const defaultParseSearch = parseSearchWith(JSON.parse)\n/** Default `stringifySearch` using JSON.stringify for complex values. */\nexport const defaultStringifySearch = stringifySearchWith(\n JSON.stringify,\n JSON.parse,\n)\n\n/**\n * Build a `parseSearch` function using a provided JSON-like parser.\n *\n * The returned function strips a leading `?`, decodes values, and attempts to\n * JSON-parse string values using the given `parser`.\n *\n * @param parser Function to parse a string value (e.g. `JSON.parse`).\n * @returns A `parseSearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function parseSearchWith(parser: (str: string) => any) {\n return (searchStr: string): AnySchema => {\n if (searchStr[0] === '?') {\n searchStr = searchStr.substring(1)\n }\n\n const query: Record<string, unknown> = decode(searchStr)\n\n // Try to parse any query params that might be json\n for (const key in query) {\n const value = query[key]\n if (typeof value === 'string') {\n try {\n query[key] = parser(value)\n } catch (_err) {\n // silent\n }\n }\n }\n\n return query\n }\n}\n\n/**\n * Build a `stringifySearch` function using a provided serializer.\n *\n * Non-primitive values are serialized with `stringify`. If a `parser` is\n * supplied, string values that are parseable are re-serialized to ensure\n * symmetry with `parseSearch`.\n *\n * @param stringify Function to serialize a value (e.g. `JSON.stringify`).\n * @param parser Optional parser to detect parseable strings.\n * @returns A `stringifySearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function stringifySearchWith(\n stringify: (search: any) => string,\n parser?: (str: string) => any,\n) {\n const isJsonParser = parser === JSON.parse\n function stringifyValue(val: any) {\n if (val && typeof val === 'object') {\n try {\n return stringify(val)\n } catch (_err) {\n // silent\n }\n } else if (parser && typeof val === 'string') {\n // Skip JSON.parse when the value cannot begin valid JSON.\n if (isJsonParser && !jsonStart.test(val)) {\n return val\n }\n try {\n // Check if it's a valid parseable string.\n // If it is, then stringify it again.\n parser(val)\n return stringify(val)\n } catch (_err) {\n // silent\n }\n }\n return val\n }\n\n return (search: Record<string, any>) => {\n const searchStr = encode(search, stringifyValue)\n return searchStr ? `?${searchStr}` : ''\n }\n}\n\nexport type SearchSerializer = (searchObj: Record<string, any>) => string\nexport type SearchParser = (searchStr: string) => Record<string, any>\n"],"mappings":";;AAKA,MAAM,YAAY;;AAGlB,MAAa,qBAAqB,gBAAgB,KAAK,KAAK;;AAE5D,MAAa,yBAAyB,oBACpC,KAAK,WACL,KAAK,KACP;;;;;;;;;;;AAYA,SAAgB,gBAAgB,QAA8B;CAC5D,QAAQ,cAAiC;EACvC,IAAI,UAAU,OAAO,KACnB,YAAY,UAAU,UAAU,CAAC;EAGnC,MAAM,QAAiC,OAAO,SAAS;EAGvD,KAAK,MAAM,OAAO,OAAO;GACvB,MAAM,QAAQ,MAAM;GACpB,IAAI,OAAO,UAAU,UACnB,IAAI;IACF,MAAM,OAAO,OAAO,KAAK;GAC3B,SAAS,MAAM,CAEf;EAEJ;EAEA,OAAO;CACT;AACF;;;;;;;;;;;;;AAcA,SAAgB,oBACd,WACA,QACA;CACA,MAAM,eAAe,WAAW,KAAK;CACrC,SAAS,eAAe,KAAU;EAChC,IAAI,OAAO,OAAO,QAAQ,UACxB,IAAI;GACF,OAAO,UAAU,GAAG;EACtB,SAAS,MAAM,CAEf;OACK,IAAI,UAAU,OAAO,QAAQ,UAAU;GAE5C,IAAI,gBAAgB,CAAC,UAAU,KAAK,GAAG,GACrC,OAAO;GAET,IAAI;IAGF,OAAO,GAAG;IACV,OAAO,UAAU,GAAG;GACtB,SAAS,MAAM,CAEf;EACF;EACA,OAAO;CACT;CAEA,QAAQ,WAAgC;EACtC,MAAM,YAAY,OAAO,QAAQ,cAAc;EAC/C,OAAO,YAAY,IAAI,cAAc;CACvC;AACF"}
|
package/dist/esm/utils.js
CHANGED
|
@@ -73,11 +73,10 @@ function replaceEqualDeep(prev, _next, _makeObj = () => ({}), _depth = 0) {
|
|
|
73
73
|
* Optimized for the common case where objects have no symbol properties.
|
|
74
74
|
*/
|
|
75
75
|
function getEnumerableOwnKeys(o) {
|
|
76
|
-
const
|
|
77
|
-
|
|
76
|
+
const keys = Object.keys(o);
|
|
77
|
+
if (keys.length !== Object.getOwnPropertyNames(o).length) return false;
|
|
78
78
|
const symbols = Object.getOwnPropertySymbols(o);
|
|
79
|
-
if (symbols.length === 0) return
|
|
80
|
-
const keys = names;
|
|
79
|
+
if (symbols.length === 0) return keys;
|
|
81
80
|
for (const symbol of symbols) {
|
|
82
81
|
if (!isEnumerable.call(o, symbol)) return false;
|
|
83
82
|
keys.push(symbol);
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","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\nexport const hasOwn = Object.prototype.hasOwnProperty\nconst isEnumerable = Object.prototype.propertyIsEnumerable\n\nexport function hasKeys(obj: Record<string, unknown>) {\n for (const key in obj) {\n if (hasOwn.call(obj, key)) return true\n }\n return false\n}\n\nexport const 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 * Re-encode characters that are unsafe in URL paths.\n * Includes ASCII control characters (0x00-0x1F, 0x7F) and a subset of the\n * WHATWG URL \"path percent-encode set\" (\", <, >, `, {, }).\n *\n * Space (0x20) is intentionally excluded — decodeURI decodes %20 to space\n * and the router stores decoded spaces in location.pathname. The existing\n * encodePathLikeUrl already handles re-encoding spaces for outgoing URLs.\n *\n * These characters are decoded by decodeURI but must remain percent-encoded\n * in paths to match how upstream layers (CDNs, edge middleware, browsers)\n * interpret the URL, preventing infinite redirect loops and path mismatches.\n */\n// eslint-disable-next-line no-control-regex\nconst PATH_UNSAFE_RE = /[\\x00-\\x1f\\x7f\"<>`{}]/g\n\nfunction sanitizePathSegment(segment: string): string {\n return segment.replace(\n PATH_UNSAFE_RE,\n (ch) => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'),\n )\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 // This is defense-in-depth: since control characters are no longer decoded,\n // paths like \"/%0d/evil.com\" can no longer become \"//evil.com\". But we keep\n // this check to guard against other edge cases.\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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"}
|
|
1
|
+
{"version":3,"file":"utils.js","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\nexport const hasOwn = Object.prototype.hasOwnProperty\nconst isEnumerable = Object.prototype.propertyIsEnumerable\n\nexport function hasKeys(obj: Record<string, unknown>) {\n for (const key in obj) {\n if (hasOwn.call(obj, key)) return true\n }\n return false\n}\n\nexport const 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 // `Object.keys` returns only enumerable own string keys natively (no per-key\n // JS callback). If it has fewer entries than `getOwnPropertyNames` (all own\n // string keys), the object has a non-enumerable own string prop and is not\n // \"clone-friendly\" -> bail. This replaces an O(n) loop of\n // `propertyIsEnumerable` calls with two native calls.\n const keys = Object.keys(o)\n if (keys.length !== Object.getOwnPropertyNames(o).length) {\n 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 enumerable string keys directly\n if (symbols.length === 0) {\n return keys\n }\n\n // Slow path: has symbols, include only enumerable ones, bail on any\n // non-enumerable symbol so it round-trips like the string-key check above.\n for (const symbol of symbols) {\n if (!isEnumerable.call(o, symbol)) {\n return false\n }\n ;(keys as Array<string | symbol>).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 * Re-encode characters that are unsafe in URL paths.\n * Includes ASCII control characters (0x00-0x1F, 0x7F) and a subset of the\n * WHATWG URL \"path percent-encode set\" (\", <, >, `, {, }).\n *\n * Space (0x20) is intentionally excluded — decodeURI decodes %20 to space\n * and the router stores decoded spaces in location.pathname. The existing\n * encodePathLikeUrl already handles re-encoding spaces for outgoing URLs.\n *\n * These characters are decoded by decodeURI but must remain percent-encoded\n * in paths to match how upstream layers (CDNs, edge middleware, browsers)\n * interpret the URL, preventing infinite redirect loops and path mismatches.\n */\n// eslint-disable-next-line no-control-regex\nconst PATH_UNSAFE_RE = /[\\x00-\\x1f\\x7f\"<>`{}]/g\n\nfunction sanitizePathSegment(segment: string): string {\n return segment.replace(\n PATH_UNSAFE_RE,\n (ch) => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'),\n )\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 // This is defense-in-depth: since control characters are no longer decoded,\n // paths like \"/%0d/evil.com\" can no longer become \"//evil.com\". But we keep\n // this check to guard against other edge cases.\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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"}
|
package/package.json
CHANGED
package/src/path.ts
CHANGED
|
@@ -112,8 +112,8 @@ export function resolvePath({
|
|
|
112
112
|
trailingSlash = 'never',
|
|
113
113
|
cache,
|
|
114
114
|
}: ResolvePathOptions) {
|
|
115
|
+
const isBase = to === '.'
|
|
115
116
|
const isAbsolute = to.startsWith('/')
|
|
116
|
-
const isBase = !isAbsolute && to === '.'
|
|
117
117
|
|
|
118
118
|
let key
|
|
119
119
|
if (cache) {
|
|
@@ -148,7 +148,11 @@ export function resolvePath({
|
|
|
148
148
|
// ignore inter-slashes
|
|
149
149
|
}
|
|
150
150
|
} else if (value === '..') {
|
|
151
|
-
baseSegments.
|
|
151
|
+
if (baseSegments.length > 1) {
|
|
152
|
+
baseSegments.pop()
|
|
153
|
+
} else {
|
|
154
|
+
baseSegments = ['']
|
|
155
|
+
}
|
|
152
156
|
} else if (value === '.') {
|
|
153
157
|
// ignore
|
|
154
158
|
} else {
|
package/src/searchParams.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { decode, encode } from './qss'
|
|
2
2
|
import type { AnySchema } from './validators'
|
|
3
3
|
|
|
4
|
+
// JSON can start with whitespace, ", [, {, -, a digit, or fa/nu/tr.
|
|
5
|
+
// False positives safely fall through to JSON.parse.
|
|
6
|
+
const jsonStart = /^(?:\s|["[{\d-]|fa|nu|tr)/
|
|
7
|
+
|
|
4
8
|
/** Default `parseSearch` that strips leading '?' and JSON-parses values. */
|
|
5
9
|
export const defaultParseSearch = parseSearchWith(JSON.parse)
|
|
6
10
|
/** Default `stringifySearch` using JSON.stringify for complex values. */
|
|
@@ -59,15 +63,19 @@ export function stringifySearchWith(
|
|
|
59
63
|
stringify: (search: any) => string,
|
|
60
64
|
parser?: (str: string) => any,
|
|
61
65
|
) {
|
|
62
|
-
const
|
|
66
|
+
const isJsonParser = parser === JSON.parse
|
|
63
67
|
function stringifyValue(val: any) {
|
|
64
|
-
if (typeof val === 'object'
|
|
68
|
+
if (val && typeof val === 'object') {
|
|
65
69
|
try {
|
|
66
70
|
return stringify(val)
|
|
67
71
|
} catch (_err) {
|
|
68
72
|
// silent
|
|
69
73
|
}
|
|
70
|
-
} else if (
|
|
74
|
+
} else if (parser && typeof val === 'string') {
|
|
75
|
+
// Skip JSON.parse when the value cannot begin valid JSON.
|
|
76
|
+
if (isJsonParser && !jsonStart.test(val)) {
|
|
77
|
+
return val
|
|
78
|
+
}
|
|
71
79
|
try {
|
|
72
80
|
// Check if it's a valid parseable string.
|
|
73
81
|
// If it is, then stringify it again.
|
package/src/utils.ts
CHANGED
|
@@ -299,24 +299,31 @@ export function replaceEqualDeep<T>(
|
|
|
299
299
|
* Optimized for the common case where objects have no symbol properties.
|
|
300
300
|
*/
|
|
301
301
|
function getEnumerableOwnKeys(o: object) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
302
|
+
// `Object.keys` returns only enumerable own string keys natively (no per-key
|
|
303
|
+
// JS callback). If it has fewer entries than `getOwnPropertyNames` (all own
|
|
304
|
+
// string keys), the object has a non-enumerable own string prop and is not
|
|
305
|
+
// "clone-friendly" -> bail. This replaces an O(n) loop of
|
|
306
|
+
// `propertyIsEnumerable` calls with two native calls.
|
|
307
|
+
const keys = Object.keys(o)
|
|
308
|
+
if (keys.length !== Object.getOwnPropertyNames(o).length) {
|
|
309
|
+
return false
|
|
307
310
|
}
|
|
308
311
|
|
|
309
312
|
// Only check symbols if the object has any (most plain objects don't)
|
|
310
313
|
const symbols = Object.getOwnPropertySymbols(o)
|
|
311
314
|
|
|
312
|
-
// Fast path: no symbols, return
|
|
313
|
-
if (symbols.length === 0)
|
|
315
|
+
// Fast path: no symbols, return enumerable string keys directly
|
|
316
|
+
if (symbols.length === 0) {
|
|
317
|
+
return keys
|
|
318
|
+
}
|
|
314
319
|
|
|
315
|
-
// Slow path: has symbols,
|
|
316
|
-
|
|
320
|
+
// Slow path: has symbols, include only enumerable ones, bail on any
|
|
321
|
+
// non-enumerable symbol so it round-trips like the string-key check above.
|
|
317
322
|
for (const symbol of symbols) {
|
|
318
|
-
if (!isEnumerable.call(o, symbol))
|
|
319
|
-
|
|
323
|
+
if (!isEnumerable.call(o, symbol)) {
|
|
324
|
+
return false
|
|
325
|
+
}
|
|
326
|
+
;(keys as Array<string | symbol>).push(symbol)
|
|
320
327
|
}
|
|
321
328
|
return keys
|
|
322
329
|
}
|