@tanstack/router-core 1.133.20 → 1.133.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/cjs/defer.cjs.map +1 -1
  2. package/dist/cjs/defer.d.cts +5 -0
  3. package/dist/cjs/path.cjs +15 -21
  4. package/dist/cjs/path.cjs.map +1 -1
  5. package/dist/cjs/path.d.cts +31 -3
  6. package/dist/cjs/process-route-tree.cjs +1 -1
  7. package/dist/cjs/process-route-tree.cjs.map +1 -1
  8. package/dist/cjs/process-route-tree.d.cts +8 -0
  9. package/dist/cjs/qss.cjs.map +1 -1
  10. package/dist/cjs/qss.d.cts +2 -0
  11. package/dist/cjs/redirect.cjs.map +1 -1
  12. package/dist/cjs/redirect.d.cts +9 -0
  13. package/dist/cjs/rewrite.cjs.map +1 -1
  14. package/dist/cjs/rewrite.d.cts +4 -0
  15. package/dist/cjs/root.cjs.map +1 -1
  16. package/dist/cjs/root.d.cts +1 -0
  17. package/dist/cjs/route.cjs.map +1 -1
  18. package/dist/cjs/route.d.cts +2 -2
  19. package/dist/cjs/router.cjs.map +1 -1
  20. package/dist/cjs/router.d.cts +59 -0
  21. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  22. package/dist/cjs/scroll-restoration.d.cts +23 -0
  23. package/dist/cjs/searchMiddleware.cjs.map +1 -1
  24. package/dist/cjs/searchMiddleware.d.cts +6 -0
  25. package/dist/cjs/searchParams.cjs.map +1 -1
  26. package/dist/cjs/searchParams.d.cts +4 -0
  27. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  28. package/dist/cjs/ssr/serializer/transformer.d.cts +6 -0
  29. package/dist/esm/defer.d.ts +5 -0
  30. package/dist/esm/defer.js.map +1 -1
  31. package/dist/esm/path.d.ts +31 -3
  32. package/dist/esm/path.js +15 -21
  33. package/dist/esm/path.js.map +1 -1
  34. package/dist/esm/process-route-tree.d.ts +8 -0
  35. package/dist/esm/process-route-tree.js +2 -2
  36. package/dist/esm/process-route-tree.js.map +1 -1
  37. package/dist/esm/qss.d.ts +2 -0
  38. package/dist/esm/qss.js.map +1 -1
  39. package/dist/esm/redirect.d.ts +9 -0
  40. package/dist/esm/redirect.js.map +1 -1
  41. package/dist/esm/rewrite.d.ts +4 -0
  42. package/dist/esm/rewrite.js.map +1 -1
  43. package/dist/esm/root.d.ts +1 -0
  44. package/dist/esm/root.js.map +1 -1
  45. package/dist/esm/route.d.ts +2 -2
  46. package/dist/esm/route.js.map +1 -1
  47. package/dist/esm/router.d.ts +59 -0
  48. package/dist/esm/router.js.map +1 -1
  49. package/dist/esm/scroll-restoration.d.ts +23 -0
  50. package/dist/esm/scroll-restoration.js.map +1 -1
  51. package/dist/esm/searchMiddleware.d.ts +6 -0
  52. package/dist/esm/searchMiddleware.js.map +1 -1
  53. package/dist/esm/searchParams.d.ts +4 -0
  54. package/dist/esm/searchParams.js.map +1 -1
  55. package/dist/esm/ssr/serializer/transformer.d.ts +6 -0
  56. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/defer.ts +5 -0
  59. package/src/path.ts +46 -37
  60. package/src/process-route-tree.ts +10 -2
  61. package/src/qss.ts +2 -0
  62. package/src/redirect.ts +9 -0
  63. package/src/rewrite.ts +4 -0
  64. package/src/root.ts +1 -0
  65. package/src/route.ts +2 -2
  66. package/src/router.ts +59 -3
  67. package/src/scroll-restoration.ts +23 -0
  68. package/src/searchMiddleware.ts +6 -0
  69. package/src/searchParams.ts +4 -0
  70. package/src/ssr/serializer/transformer.ts +6 -0
package/dist/esm/path.js CHANGED
@@ -1,4 +1,3 @@
1
- import { rootRouteId } from "./root.js";
2
1
  import { last } from "./utils.js";
3
2
  const SEGMENT_TYPE_PATHNAME = 0;
4
3
  const SEGMENT_TYPE_PARAM = 1;
@@ -76,8 +75,8 @@ function resolvePath({
76
75
  trailingSlash = "never",
77
76
  parseCache
78
77
  }) {
79
- let baseSegments = parseBasePathSegments(base, parseCache).slice();
80
- const toSegments = parseRoutePathSegments(to, parseCache);
78
+ let baseSegments = parsePathname(base, parseCache).slice();
79
+ const toSegments = parsePathname(to, parseCache);
81
80
  if (baseSegments.length > 1 && last(baseSegments)?.value === "/") {
82
81
  baseSegments.pop();
83
82
  }
@@ -110,13 +109,11 @@ function resolvePath({
110
109
  const joined = joinPaths(segmentValues);
111
110
  return joined;
112
111
  }
113
- const parseBasePathSegments = (pathname, cache) => parsePathname(pathname, cache, true);
114
- const parseRoutePathSegments = (pathname, cache) => parsePathname(pathname, cache, false);
115
- const parsePathname = (pathname, cache, basePathValues) => {
112
+ const parsePathname = (pathname, cache) => {
116
113
  if (!pathname) return [];
117
114
  const cached = cache?.get(pathname);
118
115
  if (cached) return cached;
119
- const parsed = baseParsePathname(pathname, basePathValues);
116
+ const parsed = baseParsePathname(pathname);
120
117
  cache?.set(pathname, parsed);
121
118
  return parsed;
122
119
  };
@@ -125,7 +122,7 @@ const PARAM_W_CURLY_BRACES_RE = /^(.*?)\{(\$[a-zA-Z_$][a-zA-Z0-9_$]*)\}(.*)$/;
125
122
  const OPTIONAL_PARAM_W_CURLY_BRACES_RE = /^(.*?)\{-(\$[a-zA-Z_$][a-zA-Z0-9_$]*)\}(.*)$/;
126
123
  const WILDCARD_RE = /^\$$/;
127
124
  const WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\{\$\}(.*)$/;
128
- function baseParsePathname(pathname, basePathValues) {
125
+ function baseParsePathname(pathname) {
129
126
  pathname = cleanPath(pathname);
130
127
  const segments = [];
131
128
  if (pathname.slice(0, 1) === "/") {
@@ -141,8 +138,7 @@ function baseParsePathname(pathname, basePathValues) {
141
138
  const split = pathname.split("/").filter(Boolean);
142
139
  segments.push(
143
140
  ...split.map((part) => {
144
- const partToMatch = !basePathValues && part !== rootRouteId && part.slice(-1) === "_" ? part.slice(0, -1) : part;
145
- const wildcardBracesMatch = partToMatch.match(WILDCARD_W_CURLY_BRACES_RE);
141
+ const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE);
146
142
  if (wildcardBracesMatch) {
147
143
  const prefix = wildcardBracesMatch[1];
148
144
  const suffix = wildcardBracesMatch[2];
@@ -153,7 +149,7 @@ function baseParsePathname(pathname, basePathValues) {
153
149
  suffixSegment: suffix || void 0
154
150
  };
155
151
  }
156
- const optionalParamBracesMatch = partToMatch.match(
152
+ const optionalParamBracesMatch = part.match(
157
153
  OPTIONAL_PARAM_W_CURLY_BRACES_RE
158
154
  );
159
155
  if (optionalParamBracesMatch) {
@@ -168,7 +164,7 @@ function baseParsePathname(pathname, basePathValues) {
168
164
  suffixSegment: suffix || void 0
169
165
  };
170
166
  }
171
- const paramBracesMatch = partToMatch.match(PARAM_W_CURLY_BRACES_RE);
167
+ const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE);
172
168
  if (paramBracesMatch) {
173
169
  const prefix = paramBracesMatch[1];
174
170
  const paramName = paramBracesMatch[2];
@@ -180,8 +176,8 @@ function baseParsePathname(pathname, basePathValues) {
180
176
  suffixSegment: suffix || void 0
181
177
  };
182
178
  }
183
- if (PARAM_RE.test(partToMatch)) {
184
- const paramName = partToMatch.substring(1);
179
+ if (PARAM_RE.test(part)) {
180
+ const paramName = part.substring(1);
185
181
  return {
186
182
  type: SEGMENT_TYPE_PARAM,
187
183
  value: "$" + paramName,
@@ -189,7 +185,7 @@ function baseParsePathname(pathname, basePathValues) {
189
185
  suffixSegment: void 0
190
186
  };
191
187
  }
192
- if (WILDCARD_RE.test(partToMatch)) {
188
+ if (WILDCARD_RE.test(part)) {
193
189
  return {
194
190
  type: SEGMENT_TYPE_WILDCARD,
195
191
  value: "$",
@@ -199,7 +195,7 @@ function baseParsePathname(pathname, basePathValues) {
199
195
  }
200
196
  return {
201
197
  type: SEGMENT_TYPE_PATHNAME,
202
- value: partToMatch.includes("%25") ? partToMatch.split("%25").map((segment) => decodeURI(segment)).join("%25") : decodeURI(partToMatch)
198
+ value: part.includes("%25") ? part.split("%25").map((segment) => decodeURI(segment)).join("%25") : decodeURI(part)
203
199
  };
204
200
  })
205
201
  );
@@ -220,7 +216,7 @@ function interpolatePath({
220
216
  decodeCharMap,
221
217
  parseCache
222
218
  }) {
223
- const interpolatedPathSegments = parseRoutePathSegments(path, parseCache);
219
+ const interpolatedPathSegments = parsePathname(path, parseCache);
224
220
  function encodeParam(key) {
225
221
  const value = params[key];
226
222
  const isValueString = typeof value === "string";
@@ -322,11 +318,11 @@ function matchByPath(from, {
322
318
  caseSensitive
323
319
  }, parseCache) {
324
320
  const stringTo = to;
325
- const baseSegments = parseBasePathSegments(
321
+ const baseSegments = parsePathname(
326
322
  from.startsWith("/") ? from : `/${from}`,
327
323
  parseCache
328
324
  );
329
- const routeSegments = parseRoutePathSegments(
325
+ const routeSegments = parsePathname(
330
326
  stringTo.startsWith("/") ? stringTo : `/${stringTo}`,
331
327
  parseCache
332
328
  );
@@ -532,9 +528,7 @@ export {
532
528
  joinPaths,
533
529
  matchByPath,
534
530
  matchPathname,
535
- parseBasePathSegments,
536
531
  parsePathname,
537
- parseRoutePathSegments,
538
532
  removeTrailingSlash,
539
533
  resolvePath,
540
534
  trimPath,
@@ -1 +1 @@
1
- {"version":3,"file":"path.js","sources":["../../src/path.ts"],"sourcesContent":["import { rootRouteId } from './root'\nimport { last } from './utils'\nimport type { LRUCache } from './lru-cache'\nimport type { MatchLocation } from './RouterProvider'\nimport type { AnyPathParams } from './route'\n\nexport const SEGMENT_TYPE_PATHNAME = 0\nexport const SEGMENT_TYPE_PARAM = 1\nexport const SEGMENT_TYPE_WILDCARD = 2\nexport const SEGMENT_TYPE_OPTIONAL_PARAM = 3\n\nexport interface Segment {\n readonly type:\n | typeof SEGMENT_TYPE_PATHNAME\n | typeof SEGMENT_TYPE_PARAM\n | typeof SEGMENT_TYPE_WILDCARD\n | typeof SEGMENT_TYPE_OPTIONAL_PARAM\n readonly value: string\n readonly prefixSegment?: string\n readonly suffixSegment?: string\n // Indicates if there is a static segment after this required/optional param\n readonly hasStaticAfter?: boolean\n}\n\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\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\nexport function trimPathRight(path: string) {\n return path === '/' ? path : path.replace(/\\/{1,}$/, '')\n}\n\nexport function trimPath(path: string) {\n return trimPathRight(trimPathLeft(path))\n}\n\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\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 parseCache?: ParsePathnameCache\n}\n\nfunction segmentToString(segment: Segment): string {\n const { type, value } = segment\n if (type === SEGMENT_TYPE_PATHNAME) {\n return value\n }\n\n const { prefixSegment, suffixSegment } = segment\n\n if (type === SEGMENT_TYPE_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$${param}}`\n } else if (suffixSegment) {\n return `{$${param}}${suffixSegment}`\n }\n }\n\n if (type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{-$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{-$${param}}`\n } else if (suffixSegment) {\n return `{-$${param}}${suffixSegment}`\n }\n return `{-$${param}}`\n }\n\n if (type === SEGMENT_TYPE_WILDCARD) {\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$}`\n } else if (suffixSegment) {\n return `{$}${suffixSegment}`\n }\n }\n\n // This case should never happen, should we throw instead?\n return value\n}\n\nexport function resolvePath({\n base,\n to,\n trailingSlash = 'never',\n parseCache,\n}: ResolvePathOptions) {\n let baseSegments = parseBasePathSegments(base, parseCache).slice()\n const toSegments = parseRoutePathSegments(to, parseCache)\n\n if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {\n baseSegments.pop()\n }\n\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const toSegment = toSegments[index]!\n const value = toSegment.value\n if (value === '/') {\n if (!index) {\n // Leading slash\n baseSegments = [toSegment]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(toSegment)\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(toSegment)\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments)!.value === '/') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push({ type: SEGMENT_TYPE_PATHNAME, value: '/' })\n }\n }\n\n const segmentValues = baseSegments.map(segmentToString)\n // const joined = joinPaths([basepath, ...segmentValues])\n const joined = joinPaths(segmentValues)\n return joined\n}\n\nexport type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>\n\nexport const parseBasePathSegments = (\n pathname?: string,\n cache?: ParsePathnameCache,\n): ReadonlyArray<Segment> => parsePathname(pathname, cache, true)\n\nexport const parseRoutePathSegments = (\n pathname?: string,\n cache?: ParsePathnameCache,\n): ReadonlyArray<Segment> => parsePathname(pathname, cache, false)\n\nexport const parsePathname = (\n pathname?: string,\n cache?: ParsePathnameCache,\n basePathValues?: boolean,\n): ReadonlyArray<Segment> => {\n if (!pathname) return []\n const cached = cache?.get(pathname)\n if (cached) return cached\n const parsed = baseParsePathname(pathname, basePathValues)\n cache?.set(pathname, parsed)\n return parsed\n}\n\nconst PARAM_RE = /^\\$.{1,}$/ // $paramName\nconst PARAM_W_CURLY_BRACES_RE = /^(.*?)\\{(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{$paramName}suffix\nconst OPTIONAL_PARAM_W_CURLY_BRACES_RE =\n /^(.*?)\\{-(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{-$paramName}suffix\n\nconst WILDCARD_RE = /^\\$$/ // $\nconst WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\\{\\$\\}(.*)$/ // prefix{$}suffix\n\n/**\n * Required: `/foo/$bar` ✅\n * Prefix and Suffix: `/foo/prefix${bar}suffix` ✅\n * Wildcard: `/foo/$` ✅\n * Wildcard with Prefix and Suffix: `/foo/prefix{$}suffix` ✅\n *\n * Optional param: `/foo/{-$bar}`\n * Optional param with Prefix and Suffix: `/foo/prefix{-$bar}suffix`\n\n * Future:\n * Optional named segment: `/foo/{bar}`\n * Optional named segment with Prefix and Suffix: `/foo/prefix{-bar}suffix`\n * Escape special characters:\n * - `/foo/[$]` - Static route\n * - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`\n * - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`\n */\nfunction baseParsePathname(\n pathname: string,\n basePathValues?: boolean,\n): ReadonlyArray<Segment> {\n pathname = cleanPath(pathname)\n\n const segments: Array<Segment> = []\n\n if (pathname.slice(0, 1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n if (!pathname) {\n return segments\n }\n\n // Remove empty segments and '.' segments\n const split = pathname.split('/').filter(Boolean)\n\n segments.push(\n ...split.map((part): Segment => {\n // strip tailing underscore for non-nested paths\n const partToMatch =\n !basePathValues && part !== rootRouteId && part.slice(-1) === '_'\n ? part.slice(0, -1)\n : part\n\n // Check for wildcard with curly braces: prefix{$}suffix\n const wildcardBracesMatch = partToMatch.match(WILDCARD_W_CURLY_BRACES_RE)\n if (wildcardBracesMatch) {\n const prefix = wildcardBracesMatch[1]\n const suffix = wildcardBracesMatch[2]\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for optional parameter format: prefix{-$paramName}suffix\n const optionalParamBracesMatch = partToMatch.match(\n OPTIONAL_PARAM_W_CURLY_BRACES_RE,\n )\n if (optionalParamBracesMatch) {\n const prefix = optionalParamBracesMatch[1]\n const paramName = optionalParamBracesMatch[2]!\n const suffix = optionalParamBracesMatch[3]\n return {\n type: SEGMENT_TYPE_OPTIONAL_PARAM,\n value: paramName, // Now just $paramName (no prefix)\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for the new parameter format: prefix{$paramName}suffix\n const paramBracesMatch = partToMatch.match(PARAM_W_CURLY_BRACES_RE)\n if (paramBracesMatch) {\n const prefix = paramBracesMatch[1]\n const paramName = paramBracesMatch[2]\n const suffix = paramBracesMatch[3]\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '' + paramName,\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for bare parameter format: $paramName (without curly braces)\n if (PARAM_RE.test(partToMatch)) {\n const paramName = partToMatch.substring(1)\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '$' + paramName,\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Check for bare wildcard: $ (without curly braces)\n if (WILDCARD_RE.test(partToMatch)) {\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Handle regular pathname segment\n return {\n type: SEGMENT_TYPE_PATHNAME,\n value: partToMatch.includes('%25')\n ? partToMatch\n .split('%25')\n .map((segment) => decodeURI(segment))\n .join('%25')\n : decodeURI(partToMatch),\n }\n }),\n )\n\n if (pathname.slice(-1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n return segments\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n leaveWildcards?: boolean\n leaveParams?: boolean\n // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params\n decodeCharMap?: Map<string, string>\n parseCache?: ParsePathnameCache\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}\nexport function interpolatePath({\n path,\n params,\n leaveWildcards,\n leaveParams,\n decodeCharMap,\n parseCache,\n}: InterpolatePathOptions): InterPolatePathResult {\n const interpolatedPathSegments = parseRoutePathSegments(path, parseCache)\n\n function encodeParam(key: string): any {\n const value = params[key]\n const isValueString = typeof value === 'string'\n\n if (key === '*' || key === '_splat') {\n // the splat/catch-all routes shouldn't have the '/' encoded out\n return isValueString ? encodeURI(value) : value\n } else {\n return isValueString ? encodePathParam(value, decodeCharMap) : value\n }\n }\n\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n\n const usedParams: Record<string, unknown> = {}\n const interpolatedPath = joinPaths(\n interpolatedPathSegments.map((segment) => {\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return segment.value\n }\n\n if (segment.type === SEGMENT_TYPE_WILDCARD) {\n usedParams._splat = params._splat\n\n // TODO: Deprecate *\n usedParams['*'] = params._splat\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\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 (!params._splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${segmentSuffix}`\n }\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n return undefined\n }\n\n const value = encodeParam('_splat')\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${value}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_PARAM) {\n const key = segment.value.substring(1)\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? 'undefined'}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = segment.value.substring(1)\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n\n // Check if optional parameter is missing or undefined\n if (!(key in params) || params[key] == null) {\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${segmentSuffix}`\n }\n // For optional params with prefix/suffix, keep the prefix/suffix but omit the param\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n // If no prefix/suffix, omit the entire segment\n return undefined\n }\n\n usedParams[key] = params[key]\n\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n\n return segment.value\n }),\n )\n return { usedParams, interpolatedPath, isMissingParams }\n}\n\nfunction encodePathParam(value: string, decodeCharMap?: Map<string, string>) {\n let encoded = encodeURIComponent(value)\n if (decodeCharMap) {\n for (const [encodedChar, char] of decodeCharMap) {\n encoded = encoded.replaceAll(encodedChar, char)\n }\n }\n return encoded\n}\n\nexport function matchPathname(\n currentPathname: string,\n matchLocation: Pick<MatchLocation, 'to' | 'fuzzy' | 'caseSensitive'>,\n parseCache?: ParsePathnameCache,\n): AnyPathParams | undefined {\n const pathParams = matchByPath(currentPathname, matchLocation, parseCache)\n // const searchMatched = matchBySearch(location.search, matchLocation)\n\n if (matchLocation.to && !pathParams) {\n return\n }\n\n return pathParams ?? {}\n}\n\nexport function matchByPath(\n from: string,\n {\n to,\n fuzzy,\n caseSensitive,\n }: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>,\n parseCache?: ParsePathnameCache,\n): Record<string, string> | undefined {\n const stringTo = to as string\n\n // Parse the from and to\n const baseSegments = parseBasePathSegments(\n from.startsWith('/') ? from : `/${from}`,\n parseCache,\n )\n const routeSegments = parseRoutePathSegments(\n stringTo.startsWith('/') ? stringTo : `/${stringTo}`,\n parseCache,\n )\n\n const params: Record<string, string> = {}\n\n const result = isMatch(\n baseSegments,\n routeSegments,\n params,\n fuzzy,\n caseSensitive,\n )\n\n return result ? params : undefined\n}\n\nfunction isMatch(\n baseSegments: ReadonlyArray<Segment>,\n routeSegments: ReadonlyArray<Segment>,\n params: Record<string, string>,\n fuzzy?: boolean,\n caseSensitive?: boolean,\n): boolean {\n let baseIndex = 0\n let routeIndex = 0\n\n while (baseIndex < baseSegments.length || routeIndex < routeSegments.length) {\n const baseSegment = baseSegments[baseIndex]\n const routeSegment = routeSegments[routeIndex]\n\n if (routeSegment) {\n if (routeSegment.type === SEGMENT_TYPE_WILDCARD) {\n // Capture all remaining segments for a wildcard\n const remainingBaseSegments = baseSegments.slice(baseIndex)\n\n let _splat: string\n\n // If this is a wildcard with prefix/suffix, we need to handle the first segment specially\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n if (!baseSegment) return false\n\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if ('prefixSegment' in routeSegment) {\n if (!baseValue.startsWith(prefix)) {\n return false\n }\n }\n if ('suffixSegment' in routeSegment) {\n if (\n !baseSegments[baseSegments.length - 1]?.value.endsWith(suffix)\n ) {\n return false\n }\n }\n\n let rejoinedSplat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n\n // Remove the prefix and suffix from the rejoined splat\n if (prefix && rejoinedSplat.startsWith(prefix)) {\n rejoinedSplat = rejoinedSplat.slice(prefix.length)\n }\n\n if (suffix && rejoinedSplat.endsWith(suffix)) {\n rejoinedSplat = rejoinedSplat.slice(\n 0,\n rejoinedSplat.length - suffix.length,\n )\n }\n\n _splat = rejoinedSplat\n } else {\n // If no prefix/suffix, just rejoin the remaining segments\n _splat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n }\n\n // TODO: Deprecate *\n params['*'] = _splat\n params['_splat'] = _splat\n return true\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PATHNAME) {\n if (routeSegment.value === '/' && !baseSegment?.value) {\n routeIndex++\n continue\n }\n\n if (baseSegment) {\n if (caseSensitive) {\n if (routeSegment.value !== baseSegment.value) {\n return false\n }\n } else if (\n routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()\n ) {\n return false\n }\n baseIndex++\n routeIndex++\n continue\n } else {\n return false\n }\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PARAM) {\n if (!baseSegment) {\n return false\n }\n\n if (baseSegment.value === '/') {\n return false\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (prefix && !baseValue.startsWith(prefix)) {\n return false\n }\n if (suffix && !baseValue.endsWith(suffix)) {\n return false\n }\n\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(0, paramValue.length - suffix.length)\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n } else {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n\n if (routeSegment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n // Optional parameters can be missing - don't fail the match\n if (!baseSegment) {\n // No base segment for optional param - skip this route segment\n routeIndex++\n continue\n }\n\n if (baseSegment.value === '/') {\n // Skip slash segments for optional params\n routeIndex++\n continue\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this optional param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (\n (!prefix || baseValue.startsWith(prefix)) &&\n (!suffix || baseValue.endsWith(suffix))\n ) {\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(\n 0,\n paramValue.length - suffix.length,\n )\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n }\n } else {\n // For optional params without prefix/suffix, we need to check if the current\n // base segment should match this optional param or a later route segment\n\n // Look ahead to see if there's a later route segment that matches the current base segment\n let shouldMatchOptional = true\n for (\n let lookAhead = routeIndex + 1;\n lookAhead < routeSegments.length;\n lookAhead++\n ) {\n const futureRouteSegment = routeSegments[lookAhead]\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PATHNAME &&\n futureRouteSegment.value === baseSegment.value\n ) {\n // The current base segment matches a future pathname segment,\n // so we should skip this optional parameter\n shouldMatchOptional = false\n break\n }\n\n // If we encounter a required param or wildcard, stop looking ahead\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PARAM ||\n futureRouteSegment?.type === SEGMENT_TYPE_WILDCARD\n ) {\n if (baseSegments.length < routeSegments.length) {\n shouldMatchOptional = false\n }\n break\n }\n }\n\n if (shouldMatchOptional) {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n }\n\n // If we have base segments left but no route segments, it's a fuzzy match\n if (baseIndex < baseSegments.length && routeIndex >= routeSegments.length) {\n params['**'] = joinPaths(\n baseSegments.slice(baseIndex).map((d) => d.value),\n )\n return !!fuzzy && routeSegments[routeSegments.length - 1]?.value !== '/'\n }\n\n // If we have route segments left but no base segments, check if remaining are optional\n if (routeIndex < routeSegments.length && baseIndex >= baseSegments.length) {\n // Check if all remaining route segments are optional\n for (let i = routeIndex; i < routeSegments.length; i++) {\n if (routeSegments[i]?.type !== SEGMENT_TYPE_OPTIONAL_PARAM) {\n return false\n }\n }\n // All remaining are optional, so we can finish\n break\n }\n\n break\n }\n\n return true\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"path.js","sources":["../../src/path.ts"],"sourcesContent":["import { last } from './utils'\nimport type { LRUCache } from './lru-cache'\nimport type { MatchLocation } from './RouterProvider'\nimport type { AnyPathParams } from './route'\n\nexport const SEGMENT_TYPE_PATHNAME = 0\nexport const SEGMENT_TYPE_PARAM = 1\nexport const SEGMENT_TYPE_WILDCARD = 2\nexport const SEGMENT_TYPE_OPTIONAL_PARAM = 3\n\nexport interface Segment {\n readonly type:\n | typeof SEGMENT_TYPE_PATHNAME\n | typeof SEGMENT_TYPE_PARAM\n | typeof SEGMENT_TYPE_WILDCARD\n | typeof SEGMENT_TYPE_OPTIONAL_PARAM\n readonly value: string\n readonly prefixSegment?: string\n readonly suffixSegment?: string\n // Indicates if there is a static segment after this required/optional param\n readonly hasStaticAfter?: boolean\n}\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 return path === '/' ? path : path.replace(/\\/{1,}$/, '')\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 parseCache?: ParsePathnameCache\n}\n\nfunction segmentToString(segment: Segment): string {\n const { type, value } = segment\n if (type === SEGMENT_TYPE_PATHNAME) {\n return value\n }\n\n const { prefixSegment, suffixSegment } = segment\n\n if (type === SEGMENT_TYPE_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$${param}}`\n } else if (suffixSegment) {\n return `{$${param}}${suffixSegment}`\n }\n }\n\n if (type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const param = value.substring(1)\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{-$${param}}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{-$${param}}`\n } else if (suffixSegment) {\n return `{-$${param}}${suffixSegment}`\n }\n return `{-$${param}}`\n }\n\n if (type === SEGMENT_TYPE_WILDCARD) {\n if (prefixSegment && suffixSegment) {\n return `${prefixSegment}{$}${suffixSegment}`\n } else if (prefixSegment) {\n return `${prefixSegment}{$}`\n } else if (suffixSegment) {\n return `{$}${suffixSegment}`\n }\n }\n\n // This case should never happen, should we throw instead?\n return value\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 parseCache,\n}: ResolvePathOptions) {\n let baseSegments = parsePathname(base, parseCache).slice()\n const toSegments = parsePathname(to, parseCache)\n\n if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {\n baseSegments.pop()\n }\n\n for (let index = 0, length = toSegments.length; index < length; index++) {\n const toSegment = toSegments[index]!\n const value = toSegment.value\n if (value === '/') {\n if (!index) {\n // Leading slash\n baseSegments = [toSegment]\n } else if (index === length - 1) {\n // Trailing Slash\n baseSegments.push(toSegment)\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(toSegment)\n }\n }\n\n if (baseSegments.length > 1) {\n if (last(baseSegments)!.value === '/') {\n if (trailingSlash === 'never') {\n baseSegments.pop()\n }\n } else if (trailingSlash === 'always') {\n baseSegments.push({ type: SEGMENT_TYPE_PATHNAME, value: '/' })\n }\n }\n\n const segmentValues = baseSegments.map(segmentToString)\n // const joined = joinPaths([basepath, ...segmentValues])\n const joined = joinPaths(segmentValues)\n return joined\n}\n\nexport type ParsePathnameCache = LRUCache<string, ReadonlyArray<Segment>>\n\n/**\n * Parse a pathname into an array of typed segments used by the router's\n * matcher. Results are optionally cached via an LRU cache.\n */\nexport const parsePathname = (\n pathname?: string,\n cache?: ParsePathnameCache,\n): ReadonlyArray<Segment> => {\n if (!pathname) return []\n const cached = cache?.get(pathname)\n if (cached) return cached\n const parsed = baseParsePathname(pathname)\n cache?.set(pathname, parsed)\n return parsed\n}\n\nconst PARAM_RE = /^\\$.{1,}$/ // $paramName\nconst PARAM_W_CURLY_BRACES_RE = /^(.*?)\\{(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{$paramName}suffix\nconst OPTIONAL_PARAM_W_CURLY_BRACES_RE =\n /^(.*?)\\{-(\\$[a-zA-Z_$][a-zA-Z0-9_$]*)\\}(.*)$/ // prefix{-$paramName}suffix\n\nconst WILDCARD_RE = /^\\$$/ // $\nconst WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\\{\\$\\}(.*)$/ // prefix{$}suffix\n\n/**\n * Required: `/foo/$bar` ✅\n * Prefix and Suffix: `/foo/prefix${bar}suffix` ✅\n * Wildcard: `/foo/$` ✅\n * Wildcard with Prefix and Suffix: `/foo/prefix{$}suffix` ✅\n *\n * Optional param: `/foo/{-$bar}`\n * Optional param with Prefix and Suffix: `/foo/prefix{-$bar}suffix`\n\n * Future:\n * Optional named segment: `/foo/{bar}`\n * Optional named segment with Prefix and Suffix: `/foo/prefix{-bar}suffix`\n * Escape special characters:\n * - `/foo/[$]` - Static route\n * - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`\n * - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`\n */\nfunction baseParsePathname(pathname: string): ReadonlyArray<Segment> {\n pathname = cleanPath(pathname)\n\n const segments: Array<Segment> = []\n\n if (pathname.slice(0, 1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n if (!pathname) {\n return segments\n }\n\n // Remove empty segments and '.' segments\n const split = pathname.split('/').filter(Boolean)\n\n segments.push(\n ...split.map((part): Segment => {\n // Check for wildcard with curly braces: prefix{$}suffix\n const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE)\n if (wildcardBracesMatch) {\n const prefix = wildcardBracesMatch[1]\n const suffix = wildcardBracesMatch[2]\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for optional parameter format: prefix{-$paramName}suffix\n const optionalParamBracesMatch = part.match(\n OPTIONAL_PARAM_W_CURLY_BRACES_RE,\n )\n if (optionalParamBracesMatch) {\n const prefix = optionalParamBracesMatch[1]\n const paramName = optionalParamBracesMatch[2]!\n const suffix = optionalParamBracesMatch[3]\n return {\n type: SEGMENT_TYPE_OPTIONAL_PARAM,\n value: paramName, // Now just $paramName (no prefix)\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for the new parameter format: prefix{$paramName}suffix\n const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE)\n if (paramBracesMatch) {\n const prefix = paramBracesMatch[1]\n const paramName = paramBracesMatch[2]\n const suffix = paramBracesMatch[3]\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '' + paramName,\n prefixSegment: prefix || undefined,\n suffixSegment: suffix || undefined,\n }\n }\n\n // Check for bare parameter format: $paramName (without curly braces)\n if (PARAM_RE.test(part)) {\n const paramName = part.substring(1)\n return {\n type: SEGMENT_TYPE_PARAM,\n value: '$' + paramName,\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Check for bare wildcard: $ (without curly braces)\n if (WILDCARD_RE.test(part)) {\n return {\n type: SEGMENT_TYPE_WILDCARD,\n value: '$',\n prefixSegment: undefined,\n suffixSegment: undefined,\n }\n }\n\n // Handle regular pathname segment\n return {\n type: SEGMENT_TYPE_PATHNAME,\n value: part.includes('%25')\n ? part\n .split('%25')\n .map((segment) => decodeURI(segment))\n .join('%25')\n : decodeURI(part),\n }\n }),\n )\n\n if (pathname.slice(-1) === '/') {\n pathname = pathname.substring(1)\n segments.push({\n type: SEGMENT_TYPE_PATHNAME,\n value: '/',\n })\n }\n\n return segments\n}\n\ninterface InterpolatePathOptions {\n path?: string\n params: Record<string, unknown>\n leaveWildcards?: boolean\n leaveParams?: boolean\n // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params\n decodeCharMap?: Map<string, string>\n parseCache?: ParsePathnameCache\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/**\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 * - Optionally leaves placeholders or wildcards in place\n */\nexport function interpolatePath({\n path,\n params,\n leaveWildcards,\n leaveParams,\n decodeCharMap,\n parseCache,\n}: InterpolatePathOptions): InterPolatePathResult {\n const interpolatedPathSegments = parsePathname(path, parseCache)\n\n function encodeParam(key: string): any {\n const value = params[key]\n const isValueString = typeof value === 'string'\n\n if (key === '*' || key === '_splat') {\n // the splat/catch-all routes shouldn't have the '/' encoded out\n return isValueString ? encodeURI(value) : value\n } else {\n return isValueString ? encodePathParam(value, decodeCharMap) : value\n }\n }\n\n // Tracking if any params are missing in the `params` object\n // when interpolating the path\n let isMissingParams = false\n\n const usedParams: Record<string, unknown> = {}\n const interpolatedPath = joinPaths(\n interpolatedPathSegments.map((segment) => {\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return segment.value\n }\n\n if (segment.type === SEGMENT_TYPE_WILDCARD) {\n usedParams._splat = params._splat\n\n // TODO: Deprecate *\n usedParams['*'] = params._splat\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\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 (!params._splat) {\n isMissingParams = true\n // For missing splat parameters, just return the prefix and suffix without the wildcard\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${segmentSuffix}`\n }\n // If there is a prefix or suffix, return them joined, otherwise omit the segment\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n return undefined\n }\n\n const value = encodeParam('_splat')\n if (leaveWildcards) {\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${value}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_PARAM) {\n const key = segment.value.substring(1)\n if (!isMissingParams && !(key in params)) {\n isMissingParams = true\n }\n usedParams[key] = params[key]\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? 'undefined'}${segmentSuffix}`\n }\n\n if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n const key = segment.value.substring(1)\n\n const segmentPrefix = segment.prefixSegment || ''\n const segmentSuffix = segment.suffixSegment || ''\n\n // Check if optional parameter is missing or undefined\n if (!(key in params) || params[key] == null) {\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${segmentSuffix}`\n }\n // For optional params with prefix/suffix, keep the prefix/suffix but omit the param\n if (segmentPrefix || segmentSuffix) {\n return `${segmentPrefix}${segmentSuffix}`\n }\n // If no prefix/suffix, omit the entire segment\n return undefined\n }\n\n usedParams[key] = params[key]\n\n if (leaveParams) {\n const value = encodeParam(segment.value)\n return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`\n }\n if (leaveWildcards) {\n return `${segmentPrefix}${key}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n return `${segmentPrefix}${encodeParam(key) ?? ''}${segmentSuffix}`\n }\n\n return segment.value\n }),\n )\n return { usedParams, interpolatedPath, isMissingParams }\n}\n\nfunction encodePathParam(value: string, decodeCharMap?: Map<string, string>) {\n let encoded = encodeURIComponent(value)\n if (decodeCharMap) {\n for (const [encodedChar, char] of decodeCharMap) {\n encoded = encoded.replaceAll(encodedChar, char)\n }\n }\n return encoded\n}\n\n/**\n * Match a pathname against a route destination and return extracted params\n * or `undefined`. Uses the same parsing as the router for consistency.\n */\nexport function matchPathname(\n currentPathname: string,\n matchLocation: Pick<MatchLocation, 'to' | 'fuzzy' | 'caseSensitive'>,\n parseCache?: ParsePathnameCache,\n): AnyPathParams | undefined {\n const pathParams = matchByPath(currentPathname, matchLocation, parseCache)\n // const searchMatched = matchBySearch(location.search, matchLocation)\n\n if (matchLocation.to && !pathParams) {\n return\n }\n\n return pathParams ?? {}\n}\n\n/** Low-level matcher that compares two path strings and extracts params. */\nexport function matchByPath(\n from: string,\n {\n to,\n fuzzy,\n caseSensitive,\n }: Pick<MatchLocation, 'to' | 'caseSensitive' | 'fuzzy'>,\n parseCache?: ParsePathnameCache,\n): Record<string, string> | undefined {\n const stringTo = to as string\n\n // Parse the from and to\n const baseSegments = parsePathname(\n from.startsWith('/') ? from : `/${from}`,\n parseCache,\n )\n const routeSegments = parsePathname(\n stringTo.startsWith('/') ? stringTo : `/${stringTo}`,\n parseCache,\n )\n\n const params: Record<string, string> = {}\n\n const result = isMatch(\n baseSegments,\n routeSegments,\n params,\n fuzzy,\n caseSensitive,\n )\n\n return result ? params : undefined\n}\n\nfunction isMatch(\n baseSegments: ReadonlyArray<Segment>,\n routeSegments: ReadonlyArray<Segment>,\n params: Record<string, string>,\n fuzzy?: boolean,\n caseSensitive?: boolean,\n): boolean {\n let baseIndex = 0\n let routeIndex = 0\n\n while (baseIndex < baseSegments.length || routeIndex < routeSegments.length) {\n const baseSegment = baseSegments[baseIndex]\n const routeSegment = routeSegments[routeIndex]\n\n if (routeSegment) {\n if (routeSegment.type === SEGMENT_TYPE_WILDCARD) {\n // Capture all remaining segments for a wildcard\n const remainingBaseSegments = baseSegments.slice(baseIndex)\n\n let _splat: string\n\n // If this is a wildcard with prefix/suffix, we need to handle the first segment specially\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n if (!baseSegment) return false\n\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if ('prefixSegment' in routeSegment) {\n if (!baseValue.startsWith(prefix)) {\n return false\n }\n }\n if ('suffixSegment' in routeSegment) {\n if (\n !baseSegments[baseSegments.length - 1]?.value.endsWith(suffix)\n ) {\n return false\n }\n }\n\n let rejoinedSplat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n\n // Remove the prefix and suffix from the rejoined splat\n if (prefix && rejoinedSplat.startsWith(prefix)) {\n rejoinedSplat = rejoinedSplat.slice(prefix.length)\n }\n\n if (suffix && rejoinedSplat.endsWith(suffix)) {\n rejoinedSplat = rejoinedSplat.slice(\n 0,\n rejoinedSplat.length - suffix.length,\n )\n }\n\n _splat = rejoinedSplat\n } else {\n // If no prefix/suffix, just rejoin the remaining segments\n _splat = decodeURI(\n joinPaths(remainingBaseSegments.map((d) => d.value)),\n )\n }\n\n // TODO: Deprecate *\n params['*'] = _splat\n params['_splat'] = _splat\n return true\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PATHNAME) {\n if (routeSegment.value === '/' && !baseSegment?.value) {\n routeIndex++\n continue\n }\n\n if (baseSegment) {\n if (caseSensitive) {\n if (routeSegment.value !== baseSegment.value) {\n return false\n }\n } else if (\n routeSegment.value.toLowerCase() !== baseSegment.value.toLowerCase()\n ) {\n return false\n }\n baseIndex++\n routeIndex++\n continue\n } else {\n return false\n }\n }\n\n if (routeSegment.type === SEGMENT_TYPE_PARAM) {\n if (!baseSegment) {\n return false\n }\n\n if (baseSegment.value === '/') {\n return false\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (prefix && !baseValue.startsWith(prefix)) {\n return false\n }\n if (suffix && !baseValue.endsWith(suffix)) {\n return false\n }\n\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(0, paramValue.length - suffix.length)\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n } else {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n\n if (routeSegment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n // Optional parameters can be missing - don't fail the match\n if (!baseSegment) {\n // No base segment for optional param - skip this route segment\n routeIndex++\n continue\n }\n\n if (baseSegment.value === '/') {\n // Skip slash segments for optional params\n routeIndex++\n continue\n }\n\n let _paramValue = ''\n let matched = false\n\n // If this optional param has prefix/suffix, we need to extract the actual parameter value\n if (routeSegment.prefixSegment || routeSegment.suffixSegment) {\n const prefix = routeSegment.prefixSegment || ''\n const suffix = routeSegment.suffixSegment || ''\n\n // Check if the base segment starts with prefix and ends with suffix\n const baseValue = baseSegment.value\n if (\n (!prefix || baseValue.startsWith(prefix)) &&\n (!suffix || baseValue.endsWith(suffix))\n ) {\n let paramValue = baseValue\n if (prefix && paramValue.startsWith(prefix)) {\n paramValue = paramValue.slice(prefix.length)\n }\n if (suffix && paramValue.endsWith(suffix)) {\n paramValue = paramValue.slice(\n 0,\n paramValue.length - suffix.length,\n )\n }\n\n _paramValue = decodeURIComponent(paramValue)\n matched = true\n }\n } else {\n // For optional params without prefix/suffix, we need to check if the current\n // base segment should match this optional param or a later route segment\n\n // Look ahead to see if there's a later route segment that matches the current base segment\n let shouldMatchOptional = true\n for (\n let lookAhead = routeIndex + 1;\n lookAhead < routeSegments.length;\n lookAhead++\n ) {\n const futureRouteSegment = routeSegments[lookAhead]\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PATHNAME &&\n futureRouteSegment.value === baseSegment.value\n ) {\n // The current base segment matches a future pathname segment,\n // so we should skip this optional parameter\n shouldMatchOptional = false\n break\n }\n\n // If we encounter a required param or wildcard, stop looking ahead\n if (\n futureRouteSegment?.type === SEGMENT_TYPE_PARAM ||\n futureRouteSegment?.type === SEGMENT_TYPE_WILDCARD\n ) {\n if (baseSegments.length < routeSegments.length) {\n shouldMatchOptional = false\n }\n break\n }\n }\n\n if (shouldMatchOptional) {\n // If no prefix/suffix, just decode the base segment value\n _paramValue = decodeURIComponent(baseSegment.value)\n matched = true\n }\n }\n\n if (matched) {\n params[routeSegment.value.substring(1)] = _paramValue\n baseIndex++\n }\n\n routeIndex++\n continue\n }\n }\n\n // If we have base segments left but no route segments, it's a fuzzy match\n if (baseIndex < baseSegments.length && routeIndex >= routeSegments.length) {\n params['**'] = joinPaths(\n baseSegments.slice(baseIndex).map((d) => d.value),\n )\n return !!fuzzy && routeSegments[routeSegments.length - 1]?.value !== '/'\n }\n\n // If we have route segments left but no base segments, check if remaining are optional\n if (routeIndex < routeSegments.length && baseIndex >= baseSegments.length) {\n // Check if all remaining route segments are optional\n for (let i = routeIndex; i < routeSegments.length; i++) {\n if (routeSegments[i]?.type !== SEGMENT_TYPE_OPTIONAL_PARAM) {\n return false\n }\n }\n // All remaining are optional, so we can finish\n break\n }\n\n break\n }\n\n return true\n}\n"],"names":[],"mappings":"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"}
@@ -4,6 +4,14 @@ export type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {
4
4
  routesByPath: Record<string, TRouteLike>;
5
5
  flatRoutes: Array<TRouteLike>;
6
6
  };
7
+ /**
8
+ * Build lookup maps and a specificity-sorted flat list from a route tree.
9
+ * Returns `routesById`, `routesByPath`, and `flatRoutes`.
10
+ */
11
+ /**
12
+ * Build lookup maps and a specificity-sorted flat list from a route tree.
13
+ * Returns `routesById`, `routesByPath`, and `flatRoutes`.
14
+ */
7
15
  export declare function processRouteTree<TRouteLike extends RouteLike>({ routeTree, initRoute, }: {
8
16
  routeTree: TRouteLike;
9
17
  initRoute?: (route: TRouteLike, index: number) => void;
@@ -1,5 +1,5 @@
1
1
  import invariant from "tiny-invariant";
2
- import { trimPathRight, trimPathLeft, parseRoutePathSegments, SEGMENT_TYPE_PATHNAME, SEGMENT_TYPE_PARAM, SEGMENT_TYPE_OPTIONAL_PARAM } from "./path.js";
2
+ import { trimPathRight, trimPathLeft, parsePathname, SEGMENT_TYPE_PATHNAME, SEGMENT_TYPE_PARAM, SEGMENT_TYPE_OPTIONAL_PARAM } from "./path.js";
3
3
  const SLASH_SCORE = 0.75;
4
4
  const STATIC_SEGMENT_SCORE = 1;
5
5
  const REQUIRED_PARAM_BASE_SCORE = 0.5;
@@ -30,7 +30,7 @@ function sortRoutes(routes) {
30
30
  return;
31
31
  }
32
32
  const trimmed = trimPathLeft(d.fullPath);
33
- let parsed = parseRoutePathSegments(trimmed);
33
+ let parsed = parsePathname(trimmed);
34
34
  let skip = 0;
35
35
  while (parsed.length > skip + 1 && parsed[skip]?.value === "/") {
36
36
  skip++;
@@ -1 +1 @@
1
- {"version":3,"file":"process-route-tree.js","sources":["../../src/process-route-tree.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n parseRoutePathSegments,\n trimPathLeft,\n trimPathRight,\n} from './path'\nimport type { Segment } from './path'\nimport type { RouteLike } from './route'\n\nconst SLASH_SCORE = 0.75\nconst STATIC_SEGMENT_SCORE = 1\nconst REQUIRED_PARAM_BASE_SCORE = 0.5\nconst OPTIONAL_PARAM_BASE_SCORE = 0.4\nconst WILDCARD_PARAM_BASE_SCORE = 0.25\nconst STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2\nconst BOTH_PRESENCE_BASE_SCORE = 0.05\nconst PREFIX_PRESENCE_BASE_SCORE = 0.02\nconst SUFFIX_PRESENCE_BASE_SCORE = 0.01\nconst PREFIX_LENGTH_SCORE_MULTIPLIER = 0.0002\nconst SUFFIX_LENGTH_SCORE_MULTIPLIER = 0.0001\n\nfunction handleParam(segment: Segment, baseScore: number) {\n if (segment.prefixSegment && segment.suffixSegment) {\n return (\n baseScore +\n BOTH_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n if (segment.prefixSegment) {\n return (\n baseScore +\n PREFIX_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length\n )\n }\n\n if (segment.suffixSegment) {\n return (\n baseScore +\n SUFFIX_PRESENCE_BASE_SCORE +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n return baseScore\n}\n\nfunction sortRoutes<TRouteLike extends RouteLike>(\n routes: ReadonlyArray<TRouteLike>,\n): Array<TRouteLike> {\n const scoredRoutes: Array<{\n child: TRouteLike\n trimmed: string\n parsed: ReadonlyArray<Segment>\n index: number\n scores: Array<number>\n hasStaticAfter: boolean\n optionalParamCount: number\n }> = []\n\n routes.forEach((d, i) => {\n if (d.isRoot || !d.path) {\n return\n }\n\n const trimmed = trimPathLeft(d.fullPath)\n let parsed = parseRoutePathSegments(trimmed)\n\n // Removes the leading slash if it is not the only remaining segment\n let skip = 0\n while (parsed.length > skip + 1 && parsed[skip]?.value === '/') {\n skip++\n }\n if (skip > 0) parsed = parsed.slice(skip)\n\n let optionalParamCount = 0\n let hasStaticAfter = false\n const scores = parsed.map((segment, index) => {\n if (segment.value === '/') {\n return SLASH_SCORE\n }\n\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return STATIC_SEGMENT_SCORE\n }\n\n let baseScore: number | undefined = undefined\n if (segment.type === SEGMENT_TYPE_PARAM) {\n baseScore = REQUIRED_PARAM_BASE_SCORE\n } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n baseScore = OPTIONAL_PARAM_BASE_SCORE\n optionalParamCount++\n } else {\n baseScore = WILDCARD_PARAM_BASE_SCORE\n }\n\n // if there is any static segment (that is not an index) after a required / optional param,\n // we will boost this param so it ranks higher than a required/optional param without a static segment after it\n // JUST FOR SORTING, NOT FOR MATCHING\n for (let i = index + 1; i < parsed.length; i++) {\n const nextSegment = parsed[i]!\n if (\n nextSegment.type === SEGMENT_TYPE_PATHNAME &&\n nextSegment.value !== '/'\n ) {\n hasStaticAfter = true\n return handleParam(\n segment,\n baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE,\n )\n }\n }\n\n return handleParam(segment, baseScore)\n })\n\n scoredRoutes.push({\n child: d,\n trimmed,\n parsed,\n index: i,\n scores,\n optionalParamCount,\n hasStaticAfter,\n })\n })\n\n const flatRoutes = scoredRoutes\n .sort((a, b) => {\n const minLength = Math.min(a.scores.length, b.scores.length)\n\n // Sort by segment-by-segment score comparison ONLY for the common prefix\n for (let i = 0; i < minLength; i++) {\n if (a.scores[i] !== b.scores[i]) {\n return b.scores[i]! - a.scores[i]!\n }\n }\n\n // If all common segments have equal scores, then consider length and specificity\n if (a.scores.length !== b.scores.length) {\n // If different number of optional parameters, fewer optional parameters wins (more specific)\n // only if both or none of the routes has static segments after the params\n if (a.optionalParamCount !== b.optionalParamCount) {\n if (a.hasStaticAfter === b.hasStaticAfter) {\n return a.optionalParamCount - b.optionalParamCount\n } else if (a.hasStaticAfter && !b.hasStaticAfter) {\n return -1\n } else if (!a.hasStaticAfter && b.hasStaticAfter) {\n return 1\n }\n }\n\n // If same number of optional parameters, longer path wins (for static segments)\n return b.scores.length - a.scores.length\n }\n\n // Sort by min available parsed value for alphabetical ordering\n for (let i = 0; i < minLength; i++) {\n if (a.parsed[i]!.value !== b.parsed[i]!.value) {\n return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1\n }\n }\n\n // Sort by original index\n return a.index - b.index\n })\n .map((d, i) => {\n d.child.rank = i\n return d.child\n })\n\n return flatRoutes\n}\n\nexport type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {\n routesById: Record<string, TRouteLike>\n routesByPath: Record<string, TRouteLike>\n flatRoutes: Array<TRouteLike>\n}\n\nexport function processRouteTree<TRouteLike extends RouteLike>({\n routeTree,\n initRoute,\n}: {\n routeTree: TRouteLike\n initRoute?: (route: TRouteLike, index: number) => void\n}): ProcessRouteTreeResult<TRouteLike> {\n const routesById = {} as Record<string, TRouteLike>\n const routesByPath = {} as Record<string, TRouteLike>\n\n const recurseRoutes = (childRoutes: Array<TRouteLike>) => {\n childRoutes.forEach((childRoute, i) => {\n initRoute?.(childRoute, i)\n\n const existingRoute = routesById[childRoute.id]\n\n invariant(\n !existingRoute,\n `Duplicate routes found with id: ${String(childRoute.id)}`,\n )\n\n routesById[childRoute.id] = childRoute\n\n if (!childRoute.isRoot && childRoute.path) {\n const trimmedFullPath = trimPathRight(childRoute.fullPath)\n if (\n !routesByPath[trimmedFullPath] ||\n childRoute.fullPath.endsWith('/')\n ) {\n routesByPath[trimmedFullPath] = childRoute\n }\n }\n\n const children = childRoute.children as Array<TRouteLike>\n\n if (children?.length) {\n recurseRoutes(children)\n }\n })\n }\n\n recurseRoutes([routeTree])\n\n const flatRoutes = sortRoutes(Object.values(routesById))\n\n return { routesById, routesByPath, flatRoutes }\n}\n"],"names":["i"],"mappings":"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"}
1
+ {"version":3,"file":"process-route-tree.js","sources":["../../src/process-route-tree.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport {\n SEGMENT_TYPE_OPTIONAL_PARAM,\n SEGMENT_TYPE_PARAM,\n SEGMENT_TYPE_PATHNAME,\n parsePathname,\n trimPathLeft,\n trimPathRight,\n} from './path'\nimport type { Segment } from './path'\nimport type { RouteLike } from './route'\n\nconst SLASH_SCORE = 0.75\nconst STATIC_SEGMENT_SCORE = 1\nconst REQUIRED_PARAM_BASE_SCORE = 0.5\nconst OPTIONAL_PARAM_BASE_SCORE = 0.4\nconst WILDCARD_PARAM_BASE_SCORE = 0.25\nconst STATIC_AFTER_DYNAMIC_BONUS_SCORE = 0.2\nconst BOTH_PRESENCE_BASE_SCORE = 0.05\nconst PREFIX_PRESENCE_BASE_SCORE = 0.02\nconst SUFFIX_PRESENCE_BASE_SCORE = 0.01\nconst PREFIX_LENGTH_SCORE_MULTIPLIER = 0.0002\nconst SUFFIX_LENGTH_SCORE_MULTIPLIER = 0.0001\n\nfunction handleParam(segment: Segment, baseScore: number) {\n if (segment.prefixSegment && segment.suffixSegment) {\n return (\n baseScore +\n BOTH_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n if (segment.prefixSegment) {\n return (\n baseScore +\n PREFIX_PRESENCE_BASE_SCORE +\n PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length\n )\n }\n\n if (segment.suffixSegment) {\n return (\n baseScore +\n SUFFIX_PRESENCE_BASE_SCORE +\n SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length\n )\n }\n\n return baseScore\n}\n\nfunction sortRoutes<TRouteLike extends RouteLike>(\n routes: ReadonlyArray<TRouteLike>,\n): Array<TRouteLike> {\n const scoredRoutes: Array<{\n child: TRouteLike\n trimmed: string\n parsed: ReadonlyArray<Segment>\n index: number\n scores: Array<number>\n hasStaticAfter: boolean\n optionalParamCount: number\n }> = []\n\n routes.forEach((d, i) => {\n if (d.isRoot || !d.path) {\n return\n }\n\n const trimmed = trimPathLeft(d.fullPath)\n let parsed = parsePathname(trimmed)\n\n // Removes the leading slash if it is not the only remaining segment\n let skip = 0\n while (parsed.length > skip + 1 && parsed[skip]?.value === '/') {\n skip++\n }\n if (skip > 0) parsed = parsed.slice(skip)\n\n let optionalParamCount = 0\n let hasStaticAfter = false\n const scores = parsed.map((segment, index) => {\n if (segment.value === '/') {\n return SLASH_SCORE\n }\n\n if (segment.type === SEGMENT_TYPE_PATHNAME) {\n return STATIC_SEGMENT_SCORE\n }\n\n let baseScore: number | undefined = undefined\n if (segment.type === SEGMENT_TYPE_PARAM) {\n baseScore = REQUIRED_PARAM_BASE_SCORE\n } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {\n baseScore = OPTIONAL_PARAM_BASE_SCORE\n optionalParamCount++\n } else {\n baseScore = WILDCARD_PARAM_BASE_SCORE\n }\n\n // if there is any static segment (that is not an index) after a required / optional param,\n // we will boost this param so it ranks higher than a required/optional param without a static segment after it\n // JUST FOR SORTING, NOT FOR MATCHING\n for (let i = index + 1; i < parsed.length; i++) {\n const nextSegment = parsed[i]!\n if (\n nextSegment.type === SEGMENT_TYPE_PATHNAME &&\n nextSegment.value !== '/'\n ) {\n hasStaticAfter = true\n return handleParam(\n segment,\n baseScore + STATIC_AFTER_DYNAMIC_BONUS_SCORE,\n )\n }\n }\n\n return handleParam(segment, baseScore)\n })\n\n scoredRoutes.push({\n child: d,\n trimmed,\n parsed,\n index: i,\n scores,\n optionalParamCount,\n hasStaticAfter,\n })\n })\n\n const flatRoutes = scoredRoutes\n .sort((a, b) => {\n const minLength = Math.min(a.scores.length, b.scores.length)\n\n // Sort by segment-by-segment score comparison ONLY for the common prefix\n for (let i = 0; i < minLength; i++) {\n if (a.scores[i] !== b.scores[i]) {\n return b.scores[i]! - a.scores[i]!\n }\n }\n\n // If all common segments have equal scores, then consider length and specificity\n if (a.scores.length !== b.scores.length) {\n // If different number of optional parameters, fewer optional parameters wins (more specific)\n // only if both or none of the routes has static segments after the params\n if (a.optionalParamCount !== b.optionalParamCount) {\n if (a.hasStaticAfter === b.hasStaticAfter) {\n return a.optionalParamCount - b.optionalParamCount\n } else if (a.hasStaticAfter && !b.hasStaticAfter) {\n return -1\n } else if (!a.hasStaticAfter && b.hasStaticAfter) {\n return 1\n }\n }\n\n // If same number of optional parameters, longer path wins (for static segments)\n return b.scores.length - a.scores.length\n }\n\n // Sort by min available parsed value for alphabetical ordering\n for (let i = 0; i < minLength; i++) {\n if (a.parsed[i]!.value !== b.parsed[i]!.value) {\n return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1\n }\n }\n\n // Sort by original index\n return a.index - b.index\n })\n .map((d, i) => {\n d.child.rank = i\n return d.child\n })\n\n return flatRoutes\n}\n\nexport type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {\n routesById: Record<string, TRouteLike>\n routesByPath: Record<string, TRouteLike>\n flatRoutes: Array<TRouteLike>\n}\n\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\n/**\n * Build lookup maps and a specificity-sorted flat list from a route tree.\n * Returns `routesById`, `routesByPath`, and `flatRoutes`.\n */\nexport function processRouteTree<TRouteLike extends RouteLike>({\n routeTree,\n initRoute,\n}: {\n routeTree: TRouteLike\n initRoute?: (route: TRouteLike, index: number) => void\n}): ProcessRouteTreeResult<TRouteLike> {\n const routesById = {} as Record<string, TRouteLike>\n const routesByPath = {} as Record<string, TRouteLike>\n\n const recurseRoutes = (childRoutes: Array<TRouteLike>) => {\n childRoutes.forEach((childRoute, i) => {\n initRoute?.(childRoute, i)\n\n const existingRoute = routesById[childRoute.id]\n\n invariant(\n !existingRoute,\n `Duplicate routes found with id: ${String(childRoute.id)}`,\n )\n\n routesById[childRoute.id] = childRoute\n\n if (!childRoute.isRoot && childRoute.path) {\n const trimmedFullPath = trimPathRight(childRoute.fullPath)\n if (\n !routesByPath[trimmedFullPath] ||\n childRoute.fullPath.endsWith('/')\n ) {\n routesByPath[trimmedFullPath] = childRoute\n }\n }\n\n const children = childRoute.children as Array<TRouteLike>\n\n if (children?.length) {\n recurseRoutes(children)\n }\n })\n }\n\n recurseRoutes([routeTree])\n\n const flatRoutes = sortRoutes(Object.values(routesById))\n\n return { routesById, routesByPath, flatRoutes }\n}\n"],"names":["i"],"mappings":"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"}
package/dist/esm/qss.d.ts CHANGED
@@ -21,6 +21,7 @@
21
21
  * // Expected output: "token=foo&key=value"
22
22
  * ```
23
23
  */
24
+ /** Encode a plain object into a URL query string using URLSearchParams. */
24
25
  export declare function encode(obj: Record<string, any>, stringify?: (value: any) => string): string;
25
26
  /**
26
27
  * Decodes a query string into an object.
@@ -30,4 +31,5 @@ export declare function encode(obj: Record<string, any>, stringify?: (value: any
30
31
  * // Example input: decode("token=foo&key=value")
31
32
  * // Expected output: { "token": "foo", "key": "value" }
32
33
  */
34
+ /** Decode a URL query string into an object with basic type coercion. */
33
35
  export declare function decode(str: any): any;
@@ -1 +1 @@
1
- {"version":3,"file":"qss.js","sources":["../../src/qss.ts"],"sourcesContent":["/**\n * Program is a reimplementation of the `qss` package:\n * Copyright (c) Luke Edwards luke.edwards05@gmail.com, MIT License\n * https://github.com/lukeed/qss/blob/master/license.md\n *\n * This reimplementation uses modern browser APIs\n * (namely URLSearchParams) and TypeScript while still\n * maintaining the original functionality and interface.\n *\n * Update: this implementation has also been mangled to\n * fit exactly our use-case (single value per key in encoding).\n */\n\n/**\n * Encodes an object into a query string.\n * @param obj - The object to encode into a query string.\n * @param stringify - An optional custom stringify function.\n * @returns The encoded query string.\n * @example\n * ```\n * // Example input: encode({ token: 'foo', key: 'value' })\n * // Expected output: \"token=foo&key=value\"\n * ```\n */\nexport function encode(\n obj: Record<string, any>,\n stringify: (value: any) => string = String,\n): string {\n const result = new URLSearchParams()\n\n for (const key in obj) {\n const val = obj[key]\n if (val !== undefined) {\n result.set(key, stringify(val))\n }\n }\n\n return result.toString()\n}\n\n/**\n * Converts a string value to its appropriate type (string, number, boolean).\n * @param mix - The string value to convert.\n * @returns The converted value.\n * @example\n * // Example input: toValue(\"123\")\n * // Expected output: 123\n */\nfunction toValue(str: unknown) {\n if (!str) return ''\n\n if (str === 'false') return false\n if (str === 'true') return true\n return +str * 0 === 0 && +str + '' === str ? +str : str\n}\n\n/**\n * Decodes a query string into an object.\n * @param str - The query string to decode.\n * @returns The decoded key-value pairs in an object format.\n * @example\n * // Example input: decode(\"token=foo&key=value\")\n * // Expected output: { \"token\": \"foo\", \"key\": \"value\" }\n */\nexport function decode(str: any): any {\n const searchParams = new URLSearchParams(str)\n\n const result: Record<string, unknown> = {}\n\n for (const [key, value] of searchParams.entries()) {\n const previousValue = result[key]\n if (previousValue == null) {\n result[key] = toValue(value)\n } else if (Array.isArray(previousValue)) {\n previousValue.push(toValue(value))\n } else {\n result[key] = [previousValue, toValue(value)]\n }\n }\n\n return result\n}\n"],"names":[],"mappings":"AAwBO,SAAS,OACd,KACA,YAAoC,QAC5B;AACR,QAAM,SAAS,IAAI,gBAAA;AAEnB,aAAW,OAAO,KAAK;AACrB,UAAM,MAAM,IAAI,GAAG;AACnB,QAAI,QAAQ,QAAW;AACrB,aAAO,IAAI,KAAK,UAAU,GAAG,CAAC;AAAA,IAChC;AAAA,EACF;AAEA,SAAO,OAAO,SAAA;AAChB;AAUA,SAAS,QAAQ,KAAc;AAC7B,MAAI,CAAC,IAAK,QAAO;AAEjB,MAAI,QAAQ,QAAS,QAAO;AAC5B,MAAI,QAAQ,OAAQ,QAAO;AAC3B,SAAO,CAAC,MAAM,MAAM,KAAK,CAAC,MAAM,OAAO,MAAM,CAAC,MAAM;AACtD;AAUO,SAAS,OAAO,KAAe;AACpC,QAAM,eAAe,IAAI,gBAAgB,GAAG;AAE5C,QAAM,SAAkC,CAAA;AAExC,aAAW,CAAC,KAAK,KAAK,KAAK,aAAa,WAAW;AACjD,UAAM,gBAAgB,OAAO,GAAG;AAChC,QAAI,iBAAiB,MAAM;AACzB,aAAO,GAAG,IAAI,QAAQ,KAAK;AAAA,IAC7B,WAAW,MAAM,QAAQ,aAAa,GAAG;AACvC,oBAAc,KAAK,QAAQ,KAAK,CAAC;AAAA,IACnC,OAAO;AACL,aAAO,GAAG,IAAI,CAAC,eAAe,QAAQ,KAAK,CAAC;AAAA,IAC9C;AAAA,EACF;AAEA,SAAO;AACT;"}
1
+ {"version":3,"file":"qss.js","sources":["../../src/qss.ts"],"sourcesContent":["/**\n * Program is a reimplementation of the `qss` package:\n * Copyright (c) Luke Edwards luke.edwards05@gmail.com, MIT License\n * https://github.com/lukeed/qss/blob/master/license.md\n *\n * This reimplementation uses modern browser APIs\n * (namely URLSearchParams) and TypeScript while still\n * maintaining the original functionality and interface.\n *\n * Update: this implementation has also been mangled to\n * fit exactly our use-case (single value per key in encoding).\n */\n\n/**\n * Encodes an object into a query string.\n * @param obj - The object to encode into a query string.\n * @param stringify - An optional custom stringify function.\n * @returns The encoded query string.\n * @example\n * ```\n * // Example input: encode({ token: 'foo', key: 'value' })\n * // Expected output: \"token=foo&key=value\"\n * ```\n */\n/** Encode a plain object into a URL query string using URLSearchParams. */\nexport function encode(\n obj: Record<string, any>,\n stringify: (value: any) => string = String,\n): string {\n const result = new URLSearchParams()\n\n for (const key in obj) {\n const val = obj[key]\n if (val !== undefined) {\n result.set(key, stringify(val))\n }\n }\n\n return result.toString()\n}\n\n/**\n * Converts a string value to its appropriate type (string, number, boolean).\n * @param mix - The string value to convert.\n * @returns The converted value.\n * @example\n * // Example input: toValue(\"123\")\n * // Expected output: 123\n */\nfunction toValue(str: unknown) {\n if (!str) return ''\n\n if (str === 'false') return false\n if (str === 'true') return true\n return +str * 0 === 0 && +str + '' === str ? +str : str\n}\n\n/**\n * Decodes a query string into an object.\n * @param str - The query string to decode.\n * @returns The decoded key-value pairs in an object format.\n * @example\n * // Example input: decode(\"token=foo&key=value\")\n * // Expected output: { \"token\": \"foo\", \"key\": \"value\" }\n */\n/** Decode a URL query string into an object with basic type coercion. */\nexport function decode(str: any): any {\n const searchParams = new URLSearchParams(str)\n\n const result: Record<string, unknown> = {}\n\n for (const [key, value] of searchParams.entries()) {\n const previousValue = result[key]\n if (previousValue == null) {\n result[key] = toValue(value)\n } else if (Array.isArray(previousValue)) {\n previousValue.push(toValue(value))\n } else {\n result[key] = [previousValue, toValue(value)]\n }\n }\n\n return result\n}\n"],"names":[],"mappings":"AAyBO,SAAS,OACd,KACA,YAAoC,QAC5B;AACR,QAAM,SAAS,IAAI,gBAAA;AAEnB,aAAW,OAAO,KAAK;AACrB,UAAM,MAAM,IAAI,GAAG;AACnB,QAAI,QAAQ,QAAW;AACrB,aAAO,IAAI,KAAK,UAAU,GAAG,CAAC;AAAA,IAChC;AAAA,EACF;AAEA,SAAO,OAAO,SAAA;AAChB;AAUA,SAAS,QAAQ,KAAc;AAC7B,MAAI,CAAC,IAAK,QAAO;AAEjB,MAAI,QAAQ,QAAS,QAAO;AAC5B,MAAI,QAAQ,OAAQ,QAAO;AAC3B,SAAO,CAAC,MAAM,MAAM,KAAK,CAAC,MAAM,OAAO,MAAM,CAAC,MAAM;AACtD;AAWO,SAAS,OAAO,KAAe;AACpC,QAAM,eAAe,IAAI,gBAAgB,GAAG;AAE5C,QAAM,SAAkC,CAAA;AAExC,aAAW,CAAC,KAAK,KAAK,KAAK,aAAa,WAAW;AACjD,UAAM,gBAAgB,OAAO,GAAG;AAChC,QAAI,iBAAiB,MAAM;AACzB,aAAO,GAAG,IAAI,QAAQ,KAAK;AAAA,IAC7B,WAAW,MAAM,QAAQ,aAAa,GAAG;AACvC,oBAAc,KAAK,QAAQ,KAAK,CAAC;AAAA,IACnC,OAAO;AACL,aAAO,GAAG,IAAI,CAAC,eAAe,QAAQ,KAAK,CAAC;AAAA,IAC9C;AAAA,EACF;AAEA,SAAO;AACT;"}
@@ -48,12 +48,21 @@ export type ResolvedRedirect<TRouter extends AnyRouter = RegisteredRouter, TFrom
48
48
  * @returns A Response augmented with router navigation options.
49
49
  * @link https://tanstack.com/router/latest/docs/framework/react/api/router/redirectFunction
50
50
  */
51
+ /**
52
+ * Create a redirect Response understood by TanStack Router.
53
+ * Use inside loaders/beforeLoad or server handlers to trigger navigation.
54
+ */
51
55
  export declare function redirect<TRouter extends AnyRouter = RegisteredRouter, const TTo extends string | undefined = '.', const TFrom extends string = string, const TMaskFrom extends string = TFrom, const TMaskTo extends string = ''>(opts: RedirectOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>): Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>;
52
56
  /** Check whether a value is a TanStack Router redirect Response. */
57
+ /** Check whether a value is a TanStack Router redirect Response. */
53
58
  export declare function isRedirect(obj: any): obj is AnyRedirect;
59
+ /** True if value is a redirect with a resolved `href` location. */
60
+ /** True if value is a redirect with a resolved `href` location. */
54
61
  export declare function isResolvedRedirect(obj: any): obj is AnyRedirect & {
55
62
  options: {
56
63
  href: string;
57
64
  };
58
65
  };
66
+ /** Parse a serialized redirect object back into a redirect Response. */
67
+ /** Parse a serialized redirect object back into a redirect Response. */
59
68
  export declare function parseRedirect(obj: any): Redirect<AnyRouter, string, ".", string, ""> | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"redirect.js","sources":["../../src/redirect.ts"],"sourcesContent":["import type { NavigateOptions } from './link'\nimport type { AnyRouter, RegisteredRouter } from './router'\n\nexport type AnyRedirect = Redirect<any, any, any, any, any>\n\n/**\n * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RedirectType)\n */\nexport type Redirect<\n TRouter extends AnyRouter = RegisteredRouter,\n TFrom extends string = string,\n TTo extends string | undefined = undefined,\n TMaskFrom extends string = TFrom,\n TMaskTo extends string = '.',\n> = Response & {\n options: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>\n redirectHandled?: boolean\n}\n\nexport type RedirectOptions<\n TRouter extends AnyRouter = RegisteredRouter,\n TFrom extends string = string,\n TTo extends string | undefined = undefined,\n TMaskFrom extends string = TFrom,\n TMaskTo extends string = '.',\n> = {\n href?: string\n /**\n * @deprecated Use `statusCode` instead\n **/\n code?: number\n /**\n * The HTTP status code to use when redirecting.\n * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RedirectType#statuscode-property)\n */\n statusCode?: number\n /**\n * If provided, will throw the redirect object instead of returning it. This can be useful in places where `throwing` in a function might cause it to have a return type of `never`. In that case, you can use `redirect({ throw: true })` to throw the redirect object instead of returning it.\n * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RedirectType#throw-property)\n */\n throw?: any\n /**\n * The HTTP headers to use when redirecting.\n * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RedirectType#headers-property)\n */\n headers?: HeadersInit\n} & NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>\n\nexport type ResolvedRedirect<\n TRouter extends AnyRouter = RegisteredRouter,\n TFrom extends string = string,\n TTo extends string = '',\n TMaskFrom extends string = TFrom,\n TMaskTo extends string = '',\n> = Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>\n\n/**\n * Create a redirect Response understood by TanStack Router.\n *\n * Use from route `loader`/`beforeLoad` or server functions to trigger a\n * navigation. If `throw: true` is set, the redirect is thrown instead of\n * returned. When an absolute `href` is supplied and `reloadDocument` is not\n * set, a full-document navigation is inferred.\n *\n * @param opts Options for the redirect. Common fields:\n * - `href`: absolute URL for external redirects; infers `reloadDocument`.\n * - `statusCode`: HTTP status code to use (defaults to 307).\n * - `headers`: additional headers to include on the Response.\n * - Standard navigation options like `to`, `params`, `search`, `replace`,\n * and `reloadDocument` for internal redirects.\n * @returns A Response augmented with router navigation options.\n * @link https://tanstack.com/router/latest/docs/framework/react/api/router/redirectFunction\n */\nexport function redirect<\n TRouter extends AnyRouter = RegisteredRouter,\n const TTo extends string | undefined = '.',\n const TFrom extends string = string,\n const TMaskFrom extends string = TFrom,\n const TMaskTo extends string = '',\n>(\n opts: RedirectOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,\n): Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> {\n opts.statusCode = opts.statusCode || opts.code || 307\n\n if (!opts.reloadDocument && typeof opts.href === 'string') {\n try {\n new URL(opts.href)\n opts.reloadDocument = true\n } catch {}\n }\n\n const headers = new Headers(opts.headers)\n if (opts.href && headers.get('Location') === null) {\n headers.set('Location', opts.href)\n }\n\n const response = new Response(null, {\n status: opts.statusCode,\n headers,\n })\n\n ;(response as Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>).options =\n opts\n\n if (opts.throw) {\n throw response\n }\n\n return response as Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>\n}\n\n/** Check whether a value is a TanStack Router redirect Response. */\nexport function isRedirect(obj: any): obj is AnyRedirect {\n return obj instanceof Response && !!(obj as any).options\n}\n\nexport function isResolvedRedirect(\n obj: any,\n): obj is AnyRedirect & { options: { href: string } } {\n return isRedirect(obj) && !!obj.options.href\n}\n\nexport function parseRedirect(obj: any) {\n if (obj !== null && typeof obj === 'object' && obj.isSerializedRedirect) {\n return redirect(obj)\n }\n\n return undefined\n}\n"],"names":[],"mappings":"AAyEO,SAAS,SAOd,MACmD;AACnD,OAAK,aAAa,KAAK,cAAc,KAAK,QAAQ;AAElD,MAAI,CAAC,KAAK,kBAAkB,OAAO,KAAK,SAAS,UAAU;AACzD,QAAI;AACF,UAAI,IAAI,KAAK,IAAI;AACjB,WAAK,iBAAiB;AAAA,IACxB,QAAQ;AAAA,IAAC;AAAA,EACX;AAEA,QAAM,UAAU,IAAI,QAAQ,KAAK,OAAO;AACxC,MAAI,KAAK,QAAQ,QAAQ,IAAI,UAAU,MAAM,MAAM;AACjD,YAAQ,IAAI,YAAY,KAAK,IAAI;AAAA,EACnC;AAEA,QAAM,WAAW,IAAI,SAAS,MAAM;AAAA,IAClC,QAAQ,KAAK;AAAA,IACb;AAAA,EAAA,CACD;AAEC,WAA+D,UAC/D;AAEF,MAAI,KAAK,OAAO;AACd,UAAM;AAAA,EACR;AAEA,SAAO;AACT;AAGO,SAAS,WAAW,KAA8B;AACvD,SAAO,eAAe,YAAY,CAAC,CAAE,IAAY;AACnD;AAEO,SAAS,mBACd,KACoD;AACpD,SAAO,WAAW,GAAG,KAAK,CAAC,CAAC,IAAI,QAAQ;AAC1C;AAEO,SAAS,cAAc,KAAU;AACtC,MAAI,QAAQ,QAAQ,OAAO,QAAQ,YAAY,IAAI,sBAAsB;AACvE,WAAO,SAAS,GAAG;AAAA,EACrB;AAEA,SAAO;AACT;"}
1
+ {"version":3,"file":"redirect.js","sources":["../../src/redirect.ts"],"sourcesContent":["import type { NavigateOptions } from './link'\nimport type { AnyRouter, RegisteredRouter } from './router'\n\nexport type AnyRedirect = Redirect<any, any, any, any, any>\n\n/**\n * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RedirectType)\n */\nexport type Redirect<\n TRouter extends AnyRouter = RegisteredRouter,\n TFrom extends string = string,\n TTo extends string | undefined = undefined,\n TMaskFrom extends string = TFrom,\n TMaskTo extends string = '.',\n> = Response & {\n options: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>\n redirectHandled?: boolean\n}\n\nexport type RedirectOptions<\n TRouter extends AnyRouter = RegisteredRouter,\n TFrom extends string = string,\n TTo extends string | undefined = undefined,\n TMaskFrom extends string = TFrom,\n TMaskTo extends string = '.',\n> = {\n href?: string\n /**\n * @deprecated Use `statusCode` instead\n **/\n code?: number\n /**\n * The HTTP status code to use when redirecting.\n * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RedirectType#statuscode-property)\n */\n statusCode?: number\n /**\n * If provided, will throw the redirect object instead of returning it. This can be useful in places where `throwing` in a function might cause it to have a return type of `never`. In that case, you can use `redirect({ throw: true })` to throw the redirect object instead of returning it.\n * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RedirectType#throw-property)\n */\n throw?: any\n /**\n * The HTTP headers to use when redirecting.\n * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RedirectType#headers-property)\n */\n headers?: HeadersInit\n} & NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>\n\nexport type ResolvedRedirect<\n TRouter extends AnyRouter = RegisteredRouter,\n TFrom extends string = string,\n TTo extends string = '',\n TMaskFrom extends string = TFrom,\n TMaskTo extends string = '',\n> = Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>\n\n/**\n * Create a redirect Response understood by TanStack Router.\n *\n * Use from route `loader`/`beforeLoad` or server functions to trigger a\n * navigation. If `throw: true` is set, the redirect is thrown instead of\n * returned. When an absolute `href` is supplied and `reloadDocument` is not\n * set, a full-document navigation is inferred.\n *\n * @param opts Options for the redirect. Common fields:\n * - `href`: absolute URL for external redirects; infers `reloadDocument`.\n * - `statusCode`: HTTP status code to use (defaults to 307).\n * - `headers`: additional headers to include on the Response.\n * - Standard navigation options like `to`, `params`, `search`, `replace`,\n * and `reloadDocument` for internal redirects.\n * @returns A Response augmented with router navigation options.\n * @link https://tanstack.com/router/latest/docs/framework/react/api/router/redirectFunction\n */\n/**\n * Create a redirect Response understood by TanStack Router.\n * Use inside loaders/beforeLoad or server handlers to trigger navigation.\n */\nexport function redirect<\n TRouter extends AnyRouter = RegisteredRouter,\n const TTo extends string | undefined = '.',\n const TFrom extends string = string,\n const TMaskFrom extends string = TFrom,\n const TMaskTo extends string = '',\n>(\n opts: RedirectOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,\n): Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> {\n opts.statusCode = opts.statusCode || opts.code || 307\n\n if (!opts.reloadDocument && typeof opts.href === 'string') {\n try {\n new URL(opts.href)\n opts.reloadDocument = true\n } catch {}\n }\n\n const headers = new Headers(opts.headers)\n if (opts.href && headers.get('Location') === null) {\n headers.set('Location', opts.href)\n }\n\n const response = new Response(null, {\n status: opts.statusCode,\n headers,\n })\n\n ;(response as Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>).options =\n opts\n\n if (opts.throw) {\n throw response\n }\n\n return response as Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>\n}\n\n/** Check whether a value is a TanStack Router redirect Response. */\n/** Check whether a value is a TanStack Router redirect Response. */\nexport function isRedirect(obj: any): obj is AnyRedirect {\n return obj instanceof Response && !!(obj as any).options\n}\n\n/** True if value is a redirect with a resolved `href` location. */\n/** True if value is a redirect with a resolved `href` location. */\nexport function isResolvedRedirect(\n obj: any,\n): obj is AnyRedirect & { options: { href: string } } {\n return isRedirect(obj) && !!obj.options.href\n}\n\n/** Parse a serialized redirect object back into a redirect Response. */\n/** Parse a serialized redirect object back into a redirect Response. */\nexport function parseRedirect(obj: any) {\n if (obj !== null && typeof obj === 'object' && obj.isSerializedRedirect) {\n return redirect(obj)\n }\n\n return undefined\n}\n"],"names":[],"mappings":"AA6EO,SAAS,SAOd,MACmD;AACnD,OAAK,aAAa,KAAK,cAAc,KAAK,QAAQ;AAElD,MAAI,CAAC,KAAK,kBAAkB,OAAO,KAAK,SAAS,UAAU;AACzD,QAAI;AACF,UAAI,IAAI,KAAK,IAAI;AACjB,WAAK,iBAAiB;AAAA,IACxB,QAAQ;AAAA,IAAC;AAAA,EACX;AAEA,QAAM,UAAU,IAAI,QAAQ,KAAK,OAAO;AACxC,MAAI,KAAK,QAAQ,QAAQ,IAAI,UAAU,MAAM,MAAM;AACjD,YAAQ,IAAI,YAAY,KAAK,IAAI;AAAA,EACnC;AAEA,QAAM,WAAW,IAAI,SAAS,MAAM;AAAA,IAClC,QAAQ,KAAK;AAAA,IACb;AAAA,EAAA,CACD;AAEC,WAA+D,UAC/D;AAEF,MAAI,KAAK,OAAO;AACd,UAAM;AAAA,EACR;AAEA,SAAO;AACT;AAIO,SAAS,WAAW,KAA8B;AACvD,SAAO,eAAe,YAAY,CAAC,CAAE,IAAY;AACnD;AAIO,SAAS,mBACd,KACoD;AACpD,SAAO,WAAW,GAAG,KAAK,CAAC,CAAC,IAAI,QAAQ;AAC1C;AAIO,SAAS,cAAc,KAAU;AACtC,MAAI,QAAQ,QAAQ,OAAO,QAAQ,YAAY,IAAI,sBAAsB;AACvE,WAAO,SAAS,GAAG;AAAA,EACrB;AAEA,SAAO;AACT;"}
@@ -1,4 +1,5 @@
1
1
  import { LocationRewrite } from './router.js';
2
+ /** Compose multiple rewrite pairs into a single in/out rewrite. */
2
3
  export declare function composeRewrites(rewrites: Array<LocationRewrite>): {
3
4
  input: ({ url }: {
4
5
  url: URL;
@@ -7,6 +8,7 @@ export declare function composeRewrites(rewrites: Array<LocationRewrite>): {
7
8
  url: URL;
8
9
  }) => URL;
9
10
  };
11
+ /** Create a rewrite pair that strips/adds a basepath on input/output. */
10
12
  export declare function rewriteBasepath(opts: {
11
13
  basepath: string;
12
14
  caseSensitive?: boolean;
@@ -18,5 +20,7 @@ export declare function rewriteBasepath(opts: {
18
20
  url: URL;
19
21
  }) => URL;
20
22
  };
23
+ /** Execute a location input rewrite if provided. */
21
24
  export declare function executeRewriteInput(rewrite: LocationRewrite | undefined, url: URL): URL;
25
+ /** Execute a location output rewrite if provided. */
22
26
  export declare function executeRewriteOutput(rewrite: LocationRewrite | undefined, url: URL): URL;
@@ -1 +1 @@
1
- {"version":3,"file":"rewrite.js","sources":["../../src/rewrite.ts"],"sourcesContent":["import { joinPaths, trimPath } from './path'\nimport type { LocationRewrite } from './router'\n\nexport function composeRewrites(rewrites: Array<LocationRewrite>) {\n return {\n input: ({ url }) => {\n for (const rewrite of rewrites) {\n url = executeRewriteInput(rewrite, url)\n }\n return url\n },\n output: ({ url }) => {\n for (let i = rewrites.length - 1; i >= 0; i--) {\n url = executeRewriteOutput(rewrites[i], url)\n }\n return url\n },\n } satisfies LocationRewrite\n}\n\nexport function rewriteBasepath(opts: {\n basepath: string\n caseSensitive?: boolean\n}) {\n const trimmedBasepath = trimPath(opts.basepath)\n const normalizedBasepath = `/${trimmedBasepath}`\n const normalizedBasepathWithSlash = `${normalizedBasepath}/`\n const checkBasepath = opts.caseSensitive\n ? normalizedBasepath\n : normalizedBasepath.toLowerCase()\n const checkBasepathWithSlash = opts.caseSensitive\n ? normalizedBasepathWithSlash\n : normalizedBasepathWithSlash.toLowerCase()\n\n return {\n input: ({ url }) => {\n const pathname = opts.caseSensitive\n ? url.pathname\n : url.pathname.toLowerCase()\n\n // Handle exact basepath match (e.g., /my-app -> /)\n if (pathname === checkBasepath) {\n url.pathname = '/'\n } else if (pathname.startsWith(checkBasepathWithSlash)) {\n // Handle basepath with trailing content (e.g., /my-app/users -> /users)\n url.pathname = url.pathname.slice(normalizedBasepath.length)\n }\n return url\n },\n output: ({ url }) => {\n url.pathname = joinPaths(['/', trimmedBasepath, url.pathname])\n return url\n },\n } satisfies LocationRewrite\n}\n\nexport function executeRewriteInput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.input?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n\nexport function executeRewriteOutput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.output?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n"],"names":[],"mappings":";AAGO,SAAS,gBAAgB,UAAkC;AAChE,SAAO;AAAA,IACL,OAAO,CAAC,EAAE,UAAU;AAClB,iBAAW,WAAW,UAAU;AAC9B,cAAM,oBAAoB,SAAS,GAAG;AAAA,MACxC;AACA,aAAO;AAAA,IACT;AAAA,IACA,QAAQ,CAAC,EAAE,UAAU;AACnB,eAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,cAAM,qBAAqB,SAAS,CAAC,GAAG,GAAG;AAAA,MAC7C;AACA,aAAO;AAAA,IACT;AAAA,EAAA;AAEJ;AAEO,SAAS,gBAAgB,MAG7B;AACD,QAAM,kBAAkB,SAAS,KAAK,QAAQ;AAC9C,QAAM,qBAAqB,IAAI,eAAe;AAC9C,QAAM,8BAA8B,GAAG,kBAAkB;AACzD,QAAM,gBAAgB,KAAK,gBACvB,qBACA,mBAAmB,YAAA;AACvB,QAAM,yBAAyB,KAAK,gBAChC,8BACA,4BAA4B,YAAA;AAEhC,SAAO;AAAA,IACL,OAAO,CAAC,EAAE,UAAU;AAClB,YAAM,WAAW,KAAK,gBAClB,IAAI,WACJ,IAAI,SAAS,YAAA;AAGjB,UAAI,aAAa,eAAe;AAC9B,YAAI,WAAW;AAAA,MACjB,WAAW,SAAS,WAAW,sBAAsB,GAAG;AAEtD,YAAI,WAAW,IAAI,SAAS,MAAM,mBAAmB,MAAM;AAAA,MAC7D;AACA,aAAO;AAAA,IACT;AAAA,IACA,QAAQ,CAAC,EAAE,UAAU;AACnB,UAAI,WAAW,UAAU,CAAC,KAAK,iBAAiB,IAAI,QAAQ,CAAC;AAC7D,aAAO;AAAA,IACT;AAAA,EAAA;AAEJ;AAEO,SAAS,oBACd,SACA,KACK;AACL,QAAM,MAAM,SAAS,QAAQ,EAAE,KAAK;AACpC,MAAI,KAAK;AACP,QAAI,OAAO,QAAQ,UAAU;AAC3B,aAAO,IAAI,IAAI,GAAG;AAAA,IACpB,WAAW,eAAe,KAAK;AAC7B,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,qBACd,SACA,KACK;AACL,QAAM,MAAM,SAAS,SAAS,EAAE,KAAK;AACrC,MAAI,KAAK;AACP,QAAI,OAAO,QAAQ,UAAU;AAC3B,aAAO,IAAI,IAAI,GAAG;AAAA,IACpB,WAAW,eAAe,KAAK;AAC7B,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;"}
1
+ {"version":3,"file":"rewrite.js","sources":["../../src/rewrite.ts"],"sourcesContent":["import { joinPaths, trimPath } from './path'\nimport type { LocationRewrite } from './router'\n\n/** Compose multiple rewrite pairs into a single in/out rewrite. */\nexport function composeRewrites(rewrites: Array<LocationRewrite>) {\n return {\n input: ({ url }) => {\n for (const rewrite of rewrites) {\n url = executeRewriteInput(rewrite, url)\n }\n return url\n },\n output: ({ url }) => {\n for (let i = rewrites.length - 1; i >= 0; i--) {\n url = executeRewriteOutput(rewrites[i], url)\n }\n return url\n },\n } satisfies LocationRewrite\n}\n\n/** Create a rewrite pair that strips/adds a basepath on input/output. */\nexport function rewriteBasepath(opts: {\n basepath: string\n caseSensitive?: boolean\n}) {\n const trimmedBasepath = trimPath(opts.basepath)\n const normalizedBasepath = `/${trimmedBasepath}`\n const normalizedBasepathWithSlash = `${normalizedBasepath}/`\n const checkBasepath = opts.caseSensitive\n ? normalizedBasepath\n : normalizedBasepath.toLowerCase()\n const checkBasepathWithSlash = opts.caseSensitive\n ? normalizedBasepathWithSlash\n : normalizedBasepathWithSlash.toLowerCase()\n\n return {\n input: ({ url }) => {\n const pathname = opts.caseSensitive\n ? url.pathname\n : url.pathname.toLowerCase()\n\n // Handle exact basepath match (e.g., /my-app -> /)\n if (pathname === checkBasepath) {\n url.pathname = '/'\n } else if (pathname.startsWith(checkBasepathWithSlash)) {\n // Handle basepath with trailing content (e.g., /my-app/users -> /users)\n url.pathname = url.pathname.slice(normalizedBasepath.length)\n }\n return url\n },\n output: ({ url }) => {\n url.pathname = joinPaths(['/', trimmedBasepath, url.pathname])\n return url\n },\n } satisfies LocationRewrite\n}\n\n/** Execute a location input rewrite if provided. */\nexport function executeRewriteInput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.input?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n\n/** Execute a location output rewrite if provided. */\nexport function executeRewriteOutput(\n rewrite: LocationRewrite | undefined,\n url: URL,\n): URL {\n const res = rewrite?.output?.({ url })\n if (res) {\n if (typeof res === 'string') {\n return new URL(res)\n } else if (res instanceof URL) {\n return res\n }\n }\n return url\n}\n"],"names":[],"mappings":";AAIO,SAAS,gBAAgB,UAAkC;AAChE,SAAO;AAAA,IACL,OAAO,CAAC,EAAE,UAAU;AAClB,iBAAW,WAAW,UAAU;AAC9B,cAAM,oBAAoB,SAAS,GAAG;AAAA,MACxC;AACA,aAAO;AAAA,IACT;AAAA,IACA,QAAQ,CAAC,EAAE,UAAU;AACnB,eAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,cAAM,qBAAqB,SAAS,CAAC,GAAG,GAAG;AAAA,MAC7C;AACA,aAAO;AAAA,IACT;AAAA,EAAA;AAEJ;AAGO,SAAS,gBAAgB,MAG7B;AACD,QAAM,kBAAkB,SAAS,KAAK,QAAQ;AAC9C,QAAM,qBAAqB,IAAI,eAAe;AAC9C,QAAM,8BAA8B,GAAG,kBAAkB;AACzD,QAAM,gBAAgB,KAAK,gBACvB,qBACA,mBAAmB,YAAA;AACvB,QAAM,yBAAyB,KAAK,gBAChC,8BACA,4BAA4B,YAAA;AAEhC,SAAO;AAAA,IACL,OAAO,CAAC,EAAE,UAAU;AAClB,YAAM,WAAW,KAAK,gBAClB,IAAI,WACJ,IAAI,SAAS,YAAA;AAGjB,UAAI,aAAa,eAAe;AAC9B,YAAI,WAAW;AAAA,MACjB,WAAW,SAAS,WAAW,sBAAsB,GAAG;AAEtD,YAAI,WAAW,IAAI,SAAS,MAAM,mBAAmB,MAAM;AAAA,MAC7D;AACA,aAAO;AAAA,IACT;AAAA,IACA,QAAQ,CAAC,EAAE,UAAU;AACnB,UAAI,WAAW,UAAU,CAAC,KAAK,iBAAiB,IAAI,QAAQ,CAAC;AAC7D,aAAO;AAAA,IACT;AAAA,EAAA;AAEJ;AAGO,SAAS,oBACd,SACA,KACK;AACL,QAAM,MAAM,SAAS,QAAQ,EAAE,KAAK;AACpC,MAAI,KAAK;AACP,QAAI,OAAO,QAAQ,UAAU;AAC3B,aAAO,IAAI,IAAI,GAAG;AAAA,IACpB,WAAW,eAAe,KAAK;AAC7B,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,qBACd,SACA,KACK;AACL,QAAM,MAAM,SAAS,SAAS,EAAE,KAAK;AACrC,MAAI,KAAK;AACP,QAAI,OAAO,QAAQ,UAAU;AAC3B,aAAO,IAAI,IAAI,GAAG;AAAA,IACpB,WAAW,eAAe,KAAK;AAC7B,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;"}
@@ -1,2 +1,3 @@
1
+ /** Stable identifier used for the root route in a route tree. */
1
2
  export declare const rootRouteId = "__root__";
2
3
  export type RootRouteId = typeof rootRouteId;
@@ -1 +1 @@
1
- {"version":3,"file":"root.js","sources":["../../src/root.ts"],"sourcesContent":["export const rootRouteId = '__root__'\nexport type RootRouteId = typeof rootRouteId\n"],"names":[],"mappings":"AAAO,MAAM,cAAc;"}
1
+ {"version":3,"file":"root.js","sources":["../../src/root.ts"],"sourcesContent":["/** Stable identifier used for the root route in a route tree. */\nexport const rootRouteId = '__root__'\nexport type RootRouteId = typeof rootRouteId\n"],"names":[],"mappings":"AACO,MAAM,cAAc;"}
@@ -68,7 +68,7 @@ export type ResolveRequiredParams<TPath extends string, T> = {
68
68
  [K in ParsePathParams<TPath>['required']]: T;
69
69
  };
70
70
  export type ResolveOptionalParams<TPath extends string, T> = {
71
- [K in ParsePathParams<TPath>['optional']]?: T;
71
+ [K in ParsePathParams<TPath>['optional']]?: T | undefined;
72
72
  };
73
73
  export type ResolveParams<TPath extends string, T = string> = ResolveRequiredParams<TPath, T> & ResolveOptionalParams<TPath, T>;
74
74
  export type ParseParamsFn<in out TPath extends string, in out TParams> = (rawParams: Expand<ResolveParams<TPath>>) => TParams extends ResolveParams<TPath, any> ? TParams : ResolveParams<TPath, any>;
@@ -176,7 +176,7 @@ export interface RouteTypes<in out TRegister, in out TParentRoute extends AnyRou
176
176
  allSsr: ResolveAllSSR<TParentRoute, TSSR>;
177
177
  }
178
178
  export type ResolveSSR<TSSR> = TSSR extends (...args: ReadonlyArray<any>) => any ? LooseReturnType<TSSR> : TSSR;
179
- export type ResolveAllSSR<TParentRoute extends AnyRoute, TSSR> = unknown extends TParentRoute ? ResolveSSR<TSSR> : unknown extends TSSR ? TParentRoute['types']['ssr'] : ResolveSSR<TSSR>;
179
+ export type ResolveAllSSR<TParentRoute extends AnyRoute, TSSR> = unknown extends TParentRoute ? ResolveSSR<TSSR> : unknown extends TSSR ? TParentRoute['types']['allSsr'] : ResolveSSR<TSSR>;
180
180
  export type ResolveFullPath<TParentRoute extends AnyRoute, TPath extends string, TPrefixed = RoutePrefix<TParentRoute['fullPath'], TPath>> = TPrefixed extends RootRouteId ? '/' : TPrefixed;
181
181
  export interface RouteExtensions<in out TId, in out TFullPath> {
182
182
  id: TId;