@tanstack/router-core 1.119.0 → 1.120.4-alpha.1

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 (51) hide show
  1. package/dist/cjs/fileRoute.d.cts +6 -2
  2. package/dist/cjs/index.cjs +4 -0
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/cjs/index.d.cts +5 -5
  5. package/dist/cjs/link.cjs.map +1 -1
  6. package/dist/cjs/link.d.cts +7 -2
  7. package/dist/cjs/path.cjs +130 -16
  8. package/dist/cjs/path.cjs.map +1 -1
  9. package/dist/cjs/path.d.cts +17 -0
  10. package/dist/cjs/redirect.cjs +19 -14
  11. package/dist/cjs/redirect.cjs.map +1 -1
  12. package/dist/cjs/redirect.d.cts +10 -8
  13. package/dist/cjs/route.cjs +12 -1
  14. package/dist/cjs/route.cjs.map +1 -1
  15. package/dist/cjs/route.d.cts +10 -11
  16. package/dist/cjs/router.cjs +227 -155
  17. package/dist/cjs/router.cjs.map +1 -1
  18. package/dist/cjs/router.d.cts +50 -7
  19. package/dist/cjs/typePrimitives.d.cts +2 -2
  20. package/dist/cjs/utils.cjs.map +1 -1
  21. package/dist/cjs/utils.d.cts +3 -1
  22. package/dist/esm/fileRoute.d.ts +6 -2
  23. package/dist/esm/index.d.ts +5 -5
  24. package/dist/esm/index.js +7 -3
  25. package/dist/esm/link.d.ts +7 -2
  26. package/dist/esm/link.js.map +1 -1
  27. package/dist/esm/path.d.ts +17 -0
  28. package/dist/esm/path.js +130 -16
  29. package/dist/esm/path.js.map +1 -1
  30. package/dist/esm/redirect.d.ts +10 -8
  31. package/dist/esm/redirect.js +20 -15
  32. package/dist/esm/redirect.js.map +1 -1
  33. package/dist/esm/route.d.ts +10 -11
  34. package/dist/esm/route.js +12 -1
  35. package/dist/esm/route.js.map +1 -1
  36. package/dist/esm/router.d.ts +50 -7
  37. package/dist/esm/router.js +230 -158
  38. package/dist/esm/router.js.map +1 -1
  39. package/dist/esm/typePrimitives.d.ts +2 -2
  40. package/dist/esm/utils.d.ts +3 -1
  41. package/dist/esm/utils.js.map +1 -1
  42. package/package.json +2 -2
  43. package/src/fileRoute.ts +90 -1
  44. package/src/index.ts +13 -3
  45. package/src/link.ts +10 -7
  46. package/src/path.ts +181 -16
  47. package/src/redirect.ts +42 -28
  48. package/src/route.ts +96 -23
  49. package/src/router.ts +332 -215
  50. package/src/typePrimitives.ts +2 -2
  51. package/src/utils.ts +20 -6
@@ -1,5 +1,5 @@
1
1
  import { FromPathOption, NavigateOptions, PathParamOptions, SearchParamOptions, ToPathOption } from './link.js';
2
- import { Redirect } from './redirect.js';
2
+ import { RedirectOptions } from './redirect.js';
3
3
  import { RouteIds } from './routeInfo.js';
4
4
  import { AnyRouter, RegisteredRouter } from './router.js';
5
5
  import { UseParamsResult } from './useParams.js';
@@ -38,7 +38,7 @@ export type ValidateNavigateOptions<TRouter extends AnyRouter = RegisteredRouter
38
38
  export type ValidateNavigateOptionsArray<TRouter extends AnyRouter = RegisteredRouter, TOptions extends ReadonlyArray<any> = ReadonlyArray<unknown>, TDefaultFrom extends string = string> = {
39
39
  [K in keyof TOptions]: ValidateNavigateOptions<TRouter, TOptions[K], TDefaultFrom>;
40
40
  };
41
- export type ValidateRedirectOptions<TRouter extends AnyRouter = RegisteredRouter, TOptions = unknown, TDefaultFrom extends string = string> = Constrain<TOptions, Redirect<TRouter, InferFrom<TOptions, TDefaultFrom>, InferTo<TOptions>, InferMaskFrom<TOptions>, InferMaskTo<TOptions>>>;
41
+ export type ValidateRedirectOptions<TRouter extends AnyRouter = RegisteredRouter, TOptions = unknown, TDefaultFrom extends string = string> = Constrain<TOptions, RedirectOptions<TRouter, InferFrom<TOptions, TDefaultFrom>, InferTo<TOptions>, InferMaskFrom<TOptions>, InferMaskTo<TOptions>>>;
42
42
  export type ValidateRedirectOptionsArray<TRouter extends AnyRouter = RegisteredRouter, TOptions extends ReadonlyArray<any> = ReadonlyArray<unknown>, TDefaultFrom extends string = string> = {
43
43
  [K in keyof TOptions]: ValidateRedirectOptions<TRouter, TOptions[K], TDefaultFrom>;
44
44
  };
@@ -16,7 +16,7 @@ export type Expand<T> = T extends object ? T extends infer O ? O extends Functio
16
16
  export type DeepPartial<T> = T extends object ? {
17
17
  [P in keyof T]?: DeepPartial<T[P]>;
18
18
  } : T;
19
- export type MakeDifferenceOptional<TLeft, TRight> = Omit<TRight, keyof TLeft> & {
19
+ export type MakeDifferenceOptional<TLeft, TRight> = keyof TLeft & keyof TRight extends never ? TRight : Omit<TRight, keyof TLeft & keyof TRight> & {
20
20
  [K in keyof TLeft & keyof TRight]?: TRight[K];
21
21
  };
22
22
  export type IsUnion<T, U extends T = T> = (T extends any ? (U extends T ? false : true) : never) extends false ? false : true;
@@ -50,6 +50,8 @@ export type MergeAll<TUnion> = MergeAllObjects<TUnion> | ExtractPrimitives<TUnio
50
50
  export type ValidateJSON<T> = ((...args: Array<any>) => any) extends T ? unknown extends T ? never : 'Function is not serializable' : {
51
51
  [K in keyof T]: ValidateJSON<T[K]>;
52
52
  };
53
+ export type LooseReturnType<T> = T extends (...args: Array<any>) => infer TReturn ? TReturn : never;
54
+ export type LooseAsyncReturnType<T> = T extends (...args: Array<any>) => infer TReturn ? TReturn extends Promise<infer TReturn> ? TReturn : TReturn : never;
53
55
  export declare function last<T>(arr: Array<T>): T | undefined;
54
56
  export declare function functionalUpdate<TPrevious, TResult = TPrevious>(updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>, previous: TPrevious): TResult;
55
57
  export declare function pick<TValue, TKey extends keyof TValue>(parent: TValue, keys: Array<TKey>): Pick<TValue, TKey>;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import type { RouteIds } from './routeInfo'\nimport type { AnyRouter } from './router'\n\nexport type NoInfer<T> = [T][T extends any ? 0 : never]\nexport type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue\n ? TYesResult\n : TNoResult\n\nexport type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<\n TValue,\n TKey\n> &\n Required<Pick<TValue, TKey>>\n\nexport type PickRequired<T> = {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\nexport type PickOptional<T> = {\n [K in keyof T as undefined extends T[K] ? K : never]: T[K]\n}\n\n// from https://stackoverflow.com/a/76458160\nexport type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never\n\nexport type Expand<T> = T extends object\n ? T extends infer O\n ? O extends Function\n ? O\n : { [K in keyof O]: O[K] }\n : never\n : T\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n\nexport type MakeDifferenceOptional<TLeft, TRight> = Omit<\n TRight,\n keyof TLeft\n> & {\n [K in keyof TLeft & keyof TRight]?: TRight[K]\n}\n\n// from https://stackoverflow.com/a/53955431\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport type IsUnion<T, U extends T = T> = (\n T extends any ? (U extends T ? false : true) : never\n) extends false\n ? false\n : true\n\nexport type IsNonEmptyObject<T> = T extends object\n ? keyof T extends never\n ? false\n : true\n : false\n\nexport type Assign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : keyof TLeft & keyof TRight extends never\n ? TLeft & TRight\n : Omit<TLeft, keyof TRight> & TRight\n : never\n : never\n\nexport type IntersectAssign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : TRight & TLeft\n : never\n : never\n\nexport type Timeout = ReturnType<typeof setTimeout>\n\nexport type Updater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev?: TPrevious) => TResult)\n\nexport type NonNullableUpdater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev: TPrevious) => TResult)\n\nexport type ExtractObjects<TUnion> = TUnion extends MergeAllPrimitive\n ? never\n : TUnion\n\nexport type PartialMergeAllObject<TUnion> =\n ExtractObjects<TUnion> extends infer TObj\n ? [TObj] extends [never]\n ? never\n : {\n [TKey in TObj extends any ? keyof TObj : never]?: TObj extends any\n ? TKey extends keyof TObj\n ? TObj[TKey]\n : never\n : never\n }\n : never\n\nexport type MergeAllPrimitive =\n | ReadonlyArray<any>\n | number\n | string\n | bigint\n | boolean\n | symbol\n | undefined\n | null\n\nexport type ExtractPrimitives<TUnion> = TUnion extends MergeAllPrimitive\n ? TUnion\n : TUnion extends object\n ? never\n : TUnion\n\nexport type PartialMergeAll<TUnion> =\n | ExtractPrimitives<TUnion>\n | PartialMergeAllObject<TUnion>\n\nexport type Constrain<T, TConstraint, TDefault = TConstraint> =\n | (T extends TConstraint ? T : never)\n | TDefault\n\nexport type ConstrainLiteral<T, TConstraint, TDefault = TConstraint> =\n | (T & TConstraint)\n | TDefault\n\n/**\n * To be added to router types\n */\nexport type UnionToIntersection<T> = (\n T extends any ? (arg: T) => any : never\n) extends (arg: infer T) => any\n ? T\n : never\n\n/**\n * Merges everything in a union into one object.\n * This mapped type is homomorphic which means it preserves stuff! :)\n */\nexport type MergeAllObjects<\n TUnion,\n TIntersected = UnionToIntersection<ExtractObjects<TUnion>>,\n> = [keyof TIntersected] extends [never]\n ? never\n : {\n [TKey in keyof TIntersected]: TUnion extends any\n ? TUnion[TKey & keyof TUnion]\n : never\n }\n\nexport type MergeAll<TUnion> =\n | MergeAllObjects<TUnion>\n | ExtractPrimitives<TUnion>\n\nexport type ValidateJSON<T> = ((...args: Array<any>) => any) extends T\n ? unknown extends T\n ? never\n : 'Function is not serializable'\n : { [K in keyof T]: ValidateJSON<T[K]> }\n\nexport function last<T>(arr: Array<T>) {\n return arr[arr.length - 1]\n}\n\nfunction isFunction(d: any): d is Function {\n return typeof d === 'function'\n}\n\nexport function functionalUpdate<TPrevious, TResult = TPrevious>(\n updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>,\n previous: TPrevious,\n): TResult {\n if (isFunction(updater)) {\n return updater(previous)\n }\n\n return updater\n}\n\nexport function pick<TValue, TKey extends keyof TValue>(\n parent: TValue,\n keys: Array<TKey>,\n): Pick<TValue, TKey> {\n return keys.reduce((obj: any, key: TKey) => {\n obj[key] = parent[key]\n return obj\n }, {} as any)\n}\n\n/**\n * This function returns `prev` if `_next` is deeply equal.\n * If not, it will replace any deeply equal children of `b` with those of `a`.\n * This can be used for structural sharing between immutable JSON values for example.\n * Do not use this with signals\n */\nexport function replaceEqualDeep<T>(prev: any, _next: T): T {\n if (prev === _next) {\n return prev\n }\n\n const next = _next as any\n\n const array = isPlainArray(prev) && isPlainArray(next)\n\n if (array || (isPlainObject(prev) && isPlainObject(next))) {\n const prevItems = array ? prev : Object.keys(prev)\n const prevSize = prevItems.length\n const nextItems = array ? next : Object.keys(next)\n const nextSize = nextItems.length\n const copy: any = array ? [] : {}\n\n let equalItems = 0\n\n for (let i = 0; i < nextSize; i++) {\n const key = array ? i : (nextItems[i] as any)\n if (\n ((!array && prevItems.includes(key)) || array) &&\n prev[key] === undefined &&\n next[key] === undefined\n ) {\n copy[key] = undefined\n equalItems++\n } else {\n copy[key] = replaceEqualDeep(prev[key], next[key])\n if (copy[key] === prev[key] && prev[key] !== undefined) {\n equalItems++\n }\n }\n }\n\n return prevSize === nextSize && equalItems === prevSize ? prev : copy\n }\n\n return next\n}\n\n// Copied from: https://github.com/jonschlinkert/is-plain-object\nexport function isPlainObject(o: any) {\n if (!hasObjectPrototype(o)) {\n return false\n }\n\n // If has modified constructor\n const ctor = o.constructor\n if (typeof ctor === 'undefined') {\n return true\n }\n\n // If has modified prototype\n const prot = ctor.prototype\n if (!hasObjectPrototype(prot)) {\n return false\n }\n\n // If constructor does not have an Object-specific method\n if (!prot.hasOwnProperty('isPrototypeOf')) {\n return false\n }\n\n // Most likely a plain Object\n return true\n}\n\nfunction hasObjectPrototype(o: any) {\n return Object.prototype.toString.call(o) === '[object Object]'\n}\n\nexport function isPlainArray(value: unknown): value is Array<unknown> {\n return Array.isArray(value) && value.length === Object.keys(value).length\n}\n\nfunction getObjectKeys(obj: any, ignoreUndefined: boolean) {\n let keys = Object.keys(obj)\n if (ignoreUndefined) {\n keys = keys.filter((key) => obj[key] !== undefined)\n }\n return keys\n}\n\nexport function deepEqual(\n a: any,\n b: any,\n opts?: { partial?: boolean; ignoreUndefined?: boolean },\n): boolean {\n if (a === b) {\n return true\n }\n\n if (typeof a !== typeof b) {\n return false\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const ignoreUndefined = opts?.ignoreUndefined ?? true\n const aKeys = getObjectKeys(a, ignoreUndefined)\n const bKeys = getObjectKeys(b, ignoreUndefined)\n\n if (!opts?.partial && aKeys.length !== bKeys.length) {\n return false\n }\n\n return bKeys.every((key) => deepEqual(a[key], b[key], opts))\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) {\n return false\n }\n return !a.some((item, index) => !deepEqual(item, b[index], opts))\n }\n\n return false\n}\n\nexport type StringLiteral<T> = T extends string\n ? string extends T\n ? string\n : T\n : never\n\nexport type ThrowOrOptional<T, TThrow extends boolean> = TThrow extends true\n ? T\n : T | undefined\n\nexport type StrictOrFrom<\n TRouter extends AnyRouter,\n TFrom,\n TStrict extends boolean = true,\n> = TStrict extends false\n ? {\n from?: never\n strict: TStrict\n }\n : {\n from: ConstrainLiteral<TFrom, RouteIds<TRouter['routeTree']>>\n strict?: TStrict\n }\n\nexport type ThrowConstraint<\n TStrict extends boolean,\n TThrow extends boolean,\n> = TStrict extends false ? (TThrow extends true ? never : TThrow) : TThrow\n\nexport type ControlledPromise<T> = Promise<T> & {\n resolve: (value: T) => void\n reject: (value: any) => void\n status: 'pending' | 'resolved' | 'rejected'\n value?: T\n}\n\nexport function createControlledPromise<T>(onResolve?: (value: T) => void) {\n let resolveLoadPromise!: (value: T) => void\n let rejectLoadPromise!: (value: any) => void\n\n const controlledPromise = new Promise<T>((resolve, reject) => {\n resolveLoadPromise = resolve\n rejectLoadPromise = reject\n }) as ControlledPromise<T>\n\n controlledPromise.status = 'pending'\n\n controlledPromise.resolve = (value: T) => {\n controlledPromise.status = 'resolved'\n controlledPromise.value = value\n resolveLoadPromise(value)\n onResolve?.(value)\n }\n\n controlledPromise.reject = (e) => {\n controlledPromise.status = 'rejected'\n rejectLoadPromise(e)\n }\n\n return controlledPromise\n}\n\n/**\n *\n * @deprecated use `jsesc` instead\n */\nexport function escapeJSON(jsonString: string) {\n return jsonString\n .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n .replace(/'/g, \"\\\\'\") // Escape single quotes\n .replace(/\"/g, '\\\\\"') // Escape double quotes\n}\n\nexport function shallow<T>(objA: T, objB: T) {\n if (Object.is(objA, objB)) {\n return true\n }\n\n if (\n typeof objA !== 'object' ||\n objA === null ||\n typeof objB !== 'object' ||\n objB === null\n ) {\n return false\n }\n\n const keysA = Object.keys(objA)\n if (keysA.length !== Object.keys(objB).length) {\n return false\n }\n\n for (const item of keysA) {\n if (\n !Object.prototype.hasOwnProperty.call(objB, item) ||\n !Object.is(objA[item as keyof T], objB[item as keyof T])\n ) {\n return false\n }\n }\n return true\n}\n\n/**\n * Checks if a string contains URI-encoded special characters (e.g., %3F, %20).\n *\n * @param {string} inputString The string to check.\n * @returns {boolean} True if the string contains URI-encoded characters, false otherwise.\n * @example\n * ```typescript\n * const str1 = \"foo%3Fbar\";\n * const hasEncodedChars = hasUriEncodedChars(str1); // returns true\n * ```\n */\nexport function hasUriEncodedChars(inputString: string): boolean {\n // This regex looks for a percent sign followed by two hexadecimal digits\n const pattern = /%[0-9A-Fa-f]{2}/\n return pattern.test(inputString)\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import type { RouteIds } from './routeInfo'\nimport type { AnyRouter } from './router'\n\nexport type NoInfer<T> = [T][T extends any ? 0 : never]\nexport type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue\n ? TYesResult\n : TNoResult\n\nexport type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<\n TValue,\n TKey\n> &\n Required<Pick<TValue, TKey>>\n\nexport type PickRequired<T> = {\n [K in keyof T as undefined extends T[K] ? never : K]: T[K]\n}\n\nexport type PickOptional<T> = {\n [K in keyof T as undefined extends T[K] ? K : never]: T[K]\n}\n\n// from https://stackoverflow.com/a/76458160\nexport type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never\n\nexport type Expand<T> = T extends object\n ? T extends infer O\n ? O extends Function\n ? O\n : { [K in keyof O]: O[K] }\n : never\n : T\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n\nexport type MakeDifferenceOptional<TLeft, TRight> = keyof TLeft &\n keyof TRight extends never\n ? TRight\n : Omit<TRight, keyof TLeft & keyof TRight> & {\n [K in keyof TLeft & keyof TRight]?: TRight[K]\n }\n\n// from https://stackoverflow.com/a/53955431\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport type IsUnion<T, U extends T = T> = (\n T extends any ? (U extends T ? false : true) : never\n) extends false\n ? false\n : true\n\nexport type IsNonEmptyObject<T> = T extends object\n ? keyof T extends never\n ? false\n : true\n : false\n\nexport type Assign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : keyof TLeft & keyof TRight extends never\n ? TLeft & TRight\n : Omit<TLeft, keyof TRight> & TRight\n : never\n : never\n\nexport type IntersectAssign<TLeft, TRight> = TLeft extends any\n ? TRight extends any\n ? IsNonEmptyObject<TLeft> extends false\n ? TRight\n : IsNonEmptyObject<TRight> extends false\n ? TLeft\n : TRight & TLeft\n : never\n : never\n\nexport type Timeout = ReturnType<typeof setTimeout>\n\nexport type Updater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev?: TPrevious) => TResult)\n\nexport type NonNullableUpdater<TPrevious, TResult = TPrevious> =\n | TResult\n | ((prev: TPrevious) => TResult)\n\nexport type ExtractObjects<TUnion> = TUnion extends MergeAllPrimitive\n ? never\n : TUnion\n\nexport type PartialMergeAllObject<TUnion> =\n ExtractObjects<TUnion> extends infer TObj\n ? [TObj] extends [never]\n ? never\n : {\n [TKey in TObj extends any ? keyof TObj : never]?: TObj extends any\n ? TKey extends keyof TObj\n ? TObj[TKey]\n : never\n : never\n }\n : never\n\nexport type MergeAllPrimitive =\n | ReadonlyArray<any>\n | number\n | string\n | bigint\n | boolean\n | symbol\n | undefined\n | null\n\nexport type ExtractPrimitives<TUnion> = TUnion extends MergeAllPrimitive\n ? TUnion\n : TUnion extends object\n ? never\n : TUnion\n\nexport type PartialMergeAll<TUnion> =\n | ExtractPrimitives<TUnion>\n | PartialMergeAllObject<TUnion>\n\nexport type Constrain<T, TConstraint, TDefault = TConstraint> =\n | (T extends TConstraint ? T : never)\n | TDefault\n\nexport type ConstrainLiteral<T, TConstraint, TDefault = TConstraint> =\n | (T & TConstraint)\n | TDefault\n\n/**\n * To be added to router types\n */\nexport type UnionToIntersection<T> = (\n T extends any ? (arg: T) => any : never\n) extends (arg: infer T) => any\n ? T\n : never\n\n/**\n * Merges everything in a union into one object.\n * This mapped type is homomorphic which means it preserves stuff! :)\n */\nexport type MergeAllObjects<\n TUnion,\n TIntersected = UnionToIntersection<ExtractObjects<TUnion>>,\n> = [keyof TIntersected] extends [never]\n ? never\n : {\n [TKey in keyof TIntersected]: TUnion extends any\n ? TUnion[TKey & keyof TUnion]\n : never\n }\n\nexport type MergeAll<TUnion> =\n | MergeAllObjects<TUnion>\n | ExtractPrimitives<TUnion>\n\nexport type ValidateJSON<T> = ((...args: Array<any>) => any) extends T\n ? unknown extends T\n ? never\n : 'Function is not serializable'\n : { [K in keyof T]: ValidateJSON<T[K]> }\n\nexport type LooseReturnType<T> = T extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn\n : never\n\nexport type LooseAsyncReturnType<T> = T extends (\n ...args: Array<any>\n) => infer TReturn\n ? TReturn extends Promise<infer TReturn>\n ? TReturn\n : TReturn\n : never\n\nexport function last<T>(arr: Array<T>) {\n return arr[arr.length - 1]\n}\n\nfunction isFunction(d: any): d is Function {\n return typeof d === 'function'\n}\n\nexport function functionalUpdate<TPrevious, TResult = TPrevious>(\n updater: Updater<TPrevious, TResult> | NonNullableUpdater<TPrevious, TResult>,\n previous: TPrevious,\n): TResult {\n if (isFunction(updater)) {\n return updater(previous)\n }\n\n return updater\n}\n\nexport function pick<TValue, TKey extends keyof TValue>(\n parent: TValue,\n keys: Array<TKey>,\n): Pick<TValue, TKey> {\n return keys.reduce((obj: any, key: TKey) => {\n obj[key] = parent[key]\n return obj\n }, {} as any)\n}\n\n/**\n * This function returns `prev` if `_next` is deeply equal.\n * If not, it will replace any deeply equal children of `b` with those of `a`.\n * This can be used for structural sharing between immutable JSON values for example.\n * Do not use this with signals\n */\nexport function replaceEqualDeep<T>(prev: any, _next: T): T {\n if (prev === _next) {\n return prev\n }\n\n const next = _next as any\n\n const array = isPlainArray(prev) && isPlainArray(next)\n\n if (array || (isPlainObject(prev) && isPlainObject(next))) {\n const prevItems = array ? prev : Object.keys(prev)\n const prevSize = prevItems.length\n const nextItems = array ? next : Object.keys(next)\n const nextSize = nextItems.length\n const copy: any = array ? [] : {}\n\n let equalItems = 0\n\n for (let i = 0; i < nextSize; i++) {\n const key = array ? i : (nextItems[i] as any)\n if (\n ((!array && prevItems.includes(key)) || array) &&\n prev[key] === undefined &&\n next[key] === undefined\n ) {\n copy[key] = undefined\n equalItems++\n } else {\n copy[key] = replaceEqualDeep(prev[key], next[key])\n if (copy[key] === prev[key] && prev[key] !== undefined) {\n equalItems++\n }\n }\n }\n\n return prevSize === nextSize && equalItems === prevSize ? prev : copy\n }\n\n return next\n}\n\n// Copied from: https://github.com/jonschlinkert/is-plain-object\nexport function isPlainObject(o: any) {\n if (!hasObjectPrototype(o)) {\n return false\n }\n\n // If has modified constructor\n const ctor = o.constructor\n if (typeof ctor === 'undefined') {\n return true\n }\n\n // If has modified prototype\n const prot = ctor.prototype\n if (!hasObjectPrototype(prot)) {\n return false\n }\n\n // If constructor does not have an Object-specific method\n if (!prot.hasOwnProperty('isPrototypeOf')) {\n return false\n }\n\n // Most likely a plain Object\n return true\n}\n\nfunction hasObjectPrototype(o: any) {\n return Object.prototype.toString.call(o) === '[object Object]'\n}\n\nexport function isPlainArray(value: unknown): value is Array<unknown> {\n return Array.isArray(value) && value.length === Object.keys(value).length\n}\n\nfunction getObjectKeys(obj: any, ignoreUndefined: boolean) {\n let keys = Object.keys(obj)\n if (ignoreUndefined) {\n keys = keys.filter((key) => obj[key] !== undefined)\n }\n return keys\n}\n\nexport function deepEqual(\n a: any,\n b: any,\n opts?: { partial?: boolean; ignoreUndefined?: boolean },\n): boolean {\n if (a === b) {\n return true\n }\n\n if (typeof a !== typeof b) {\n return false\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const ignoreUndefined = opts?.ignoreUndefined ?? true\n const aKeys = getObjectKeys(a, ignoreUndefined)\n const bKeys = getObjectKeys(b, ignoreUndefined)\n\n if (!opts?.partial && aKeys.length !== bKeys.length) {\n return false\n }\n\n return bKeys.every((key) => deepEqual(a[key], b[key], opts))\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) {\n return false\n }\n return !a.some((item, index) => !deepEqual(item, b[index], opts))\n }\n\n return false\n}\n\nexport type StringLiteral<T> = T extends string\n ? string extends T\n ? string\n : T\n : never\n\nexport type ThrowOrOptional<T, TThrow extends boolean> = TThrow extends true\n ? T\n : T | undefined\n\nexport type StrictOrFrom<\n TRouter extends AnyRouter,\n TFrom,\n TStrict extends boolean = true,\n> = TStrict extends false\n ? {\n from?: never\n strict: TStrict\n }\n : {\n from: ConstrainLiteral<TFrom, RouteIds<TRouter['routeTree']>>\n strict?: TStrict\n }\n\nexport type ThrowConstraint<\n TStrict extends boolean,\n TThrow extends boolean,\n> = TStrict extends false ? (TThrow extends true ? never : TThrow) : TThrow\n\nexport type ControlledPromise<T> = Promise<T> & {\n resolve: (value: T) => void\n reject: (value: any) => void\n status: 'pending' | 'resolved' | 'rejected'\n value?: T\n}\n\nexport function createControlledPromise<T>(onResolve?: (value: T) => void) {\n let resolveLoadPromise!: (value: T) => void\n let rejectLoadPromise!: (value: any) => void\n\n const controlledPromise = new Promise<T>((resolve, reject) => {\n resolveLoadPromise = resolve\n rejectLoadPromise = reject\n }) as ControlledPromise<T>\n\n controlledPromise.status = 'pending'\n\n controlledPromise.resolve = (value: T) => {\n controlledPromise.status = 'resolved'\n controlledPromise.value = value\n resolveLoadPromise(value)\n onResolve?.(value)\n }\n\n controlledPromise.reject = (e) => {\n controlledPromise.status = 'rejected'\n rejectLoadPromise(e)\n }\n\n return controlledPromise\n}\n\n/**\n *\n * @deprecated use `jsesc` instead\n */\nexport function escapeJSON(jsonString: string) {\n return jsonString\n .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n .replace(/'/g, \"\\\\'\") // Escape single quotes\n .replace(/\"/g, '\\\\\"') // Escape double quotes\n}\n\nexport function shallow<T>(objA: T, objB: T) {\n if (Object.is(objA, objB)) {\n return true\n }\n\n if (\n typeof objA !== 'object' ||\n objA === null ||\n typeof objB !== 'object' ||\n objB === null\n ) {\n return false\n }\n\n const keysA = Object.keys(objA)\n if (keysA.length !== Object.keys(objB).length) {\n return false\n }\n\n for (const item of keysA) {\n if (\n !Object.prototype.hasOwnProperty.call(objB, item) ||\n !Object.is(objA[item as keyof T], objB[item as keyof T])\n ) {\n return false\n }\n }\n return true\n}\n\n/**\n * Checks if a string contains URI-encoded special characters (e.g., %3F, %20).\n *\n * @param {string} inputString The string to check.\n * @returns {boolean} True if the string contains URI-encoded characters, false otherwise.\n * @example\n * ```typescript\n * const str1 = \"foo%3Fbar\";\n * const hasEncodedChars = hasUriEncodedChars(str1); // returns true\n * ```\n */\nexport function hasUriEncodedChars(inputString: string): boolean {\n // This regex looks for a percent sign followed by two hexadecimal digits\n const pattern = /%[0-9A-Fa-f]{2}/\n return pattern.test(inputString)\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
- "version": "1.119.0",
3
+ "version": "1.120.4-alpha.1",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -46,7 +46,7 @@
46
46
  "dependencies": {
47
47
  "@tanstack/store": "^0.7.0",
48
48
  "tiny-invariant": "^1.3.3",
49
- "@tanstack/history": "1.115.0"
49
+ "@tanstack/history": "1.120.4-alpha.1"
50
50
  },
51
51
  "scripts": {}
52
52
  }
package/src/fileRoute.ts CHANGED
@@ -2,6 +2,10 @@ import type {
2
2
  AnyContext,
3
3
  AnyPathParams,
4
4
  AnyRoute,
5
+ FileBaseRouteOptions,
6
+ ResolveParams,
7
+ Route,
8
+ RouteConstraints,
5
9
  UpdatableRouteOptions,
6
10
  } from './route'
7
11
  import type { AnyValidator } from './validators'
@@ -28,6 +32,87 @@ export interface FileRoutesByPath {
28
32
  // }
29
33
  }
30
34
 
35
+ export interface FileRouteOptions<
36
+ TFilePath extends string,
37
+ TParentRoute extends AnyRoute,
38
+ TId extends RouteConstraints['TId'],
39
+ TPath extends RouteConstraints['TPath'],
40
+ TFullPath extends RouteConstraints['TFullPath'],
41
+ TSearchValidator = undefined,
42
+ TParams = ResolveParams<TPath>,
43
+ TRouteContextFn = AnyContext,
44
+ TBeforeLoadFn = AnyContext,
45
+ TLoaderDeps extends Record<string, any> = {},
46
+ TLoaderFn = undefined,
47
+ > extends FileBaseRouteOptions<
48
+ TParentRoute,
49
+ TId,
50
+ TPath,
51
+ TSearchValidator,
52
+ TParams,
53
+ TLoaderDeps,
54
+ TLoaderFn,
55
+ AnyContext,
56
+ TRouteContextFn,
57
+ TBeforeLoadFn
58
+ >,
59
+ UpdatableRouteOptions<
60
+ TParentRoute,
61
+ TId,
62
+ TFullPath,
63
+ TParams,
64
+ TSearchValidator,
65
+ TLoaderFn,
66
+ TLoaderDeps,
67
+ AnyContext,
68
+ TRouteContextFn,
69
+ TBeforeLoadFn
70
+ > {}
71
+
72
+ export type CreateFileRoute<
73
+ TFilePath extends string,
74
+ TParentRoute extends AnyRoute,
75
+ TId extends RouteConstraints['TId'],
76
+ TPath extends RouteConstraints['TPath'],
77
+ TFullPath extends RouteConstraints['TFullPath'],
78
+ > = <
79
+ TSearchValidator = undefined,
80
+ TParams = ResolveParams<TPath>,
81
+ TRouteContextFn = AnyContext,
82
+ TBeforeLoadFn = AnyContext,
83
+ TLoaderDeps extends Record<string, any> = {},
84
+ TLoaderFn = undefined,
85
+ >(
86
+ options?: FileRouteOptions<
87
+ TFilePath,
88
+ TParentRoute,
89
+ TId,
90
+ TPath,
91
+ TFullPath,
92
+ TSearchValidator,
93
+ TParams,
94
+ TRouteContextFn,
95
+ TBeforeLoadFn,
96
+ TLoaderDeps,
97
+ TLoaderFn
98
+ >,
99
+ ) => Route<
100
+ TParentRoute,
101
+ TPath,
102
+ TFullPath,
103
+ TFilePath,
104
+ TId,
105
+ TSearchValidator,
106
+ TParams,
107
+ AnyContext,
108
+ TRouteContextFn,
109
+ TBeforeLoadFn,
110
+ TLoaderDeps,
111
+ TLoaderFn,
112
+ unknown,
113
+ unknown
114
+ >
115
+
31
116
  export type LazyRouteOptions = Pick<
32
117
  UpdatableRouteOptions<
33
118
  AnyRoute,
@@ -44,8 +129,12 @@ export type LazyRouteOptions = Pick<
44
129
  'component' | 'errorComponent' | 'pendingComponent' | 'notFoundComponent'
45
130
  >
46
131
 
47
- export interface LazyRoute {
132
+ export interface LazyRoute<in out TRoute extends AnyRoute> {
48
133
  options: {
49
134
  id: string
50
135
  } & LazyRouteOptions
51
136
  }
137
+
138
+ export type CreateLazyFileRoute<TRoute extends AnyRoute> = (
139
+ opts: LazyRouteOptions,
140
+ ) => LazyRoute<TRoute>
package/src/index.ts CHANGED
@@ -60,8 +60,10 @@ export type {
60
60
  InferFileRouteTypes,
61
61
  FileRouteTypes,
62
62
  FileRoutesByPath,
63
+ CreateFileRoute,
63
64
  LazyRoute,
64
65
  LazyRouteOptions,
66
+ CreateLazyFileRoute,
65
67
  } from './fileRoute'
66
68
 
67
69
  export type {
@@ -149,8 +151,6 @@ export type {
149
151
  StringifyParamsFn,
150
152
  ParamsOptions,
151
153
  UpdatableStaticRouteOption,
152
- LooseReturnType,
153
- LooseAsyncReturnType,
154
154
  ContextReturnType,
155
155
  ContextAsyncReturnType,
156
156
  ResolveRouteContext,
@@ -209,6 +209,8 @@ export {
209
209
  SearchParamError,
210
210
  PathParamError,
211
211
  getInitialRouterState,
212
+ processRouteTree,
213
+ getMatchedRoutes,
212
214
  } from './router'
213
215
  export type {
214
216
  ViewTransitionOptions,
@@ -322,6 +324,8 @@ export type {
322
324
  MergeAll,
323
325
  ValidateJSON,
324
326
  StrictOrFrom,
327
+ LooseReturnType,
328
+ LooseAsyncReturnType,
325
329
  } from './utils'
326
330
 
327
331
  export type {
@@ -370,7 +374,13 @@ export type { UseLoaderDataResult, ResolveUseLoaderData } from './useLoaderData'
370
374
 
371
375
  export type { Redirect, ResolvedRedirect, AnyRedirect } from './redirect'
372
376
 
373
- export { redirect, isRedirect, isResolvedRedirect } from './redirect'
377
+ export {
378
+ redirect,
379
+ isRedirect,
380
+ isResolvedRedirect,
381
+ tsrRedirectHeaderKey,
382
+ parseRedirect,
383
+ } from './redirect'
374
384
 
375
385
  export type { NotFoundError } from './not-found'
376
386
  export { isNotFound, notFound } from './not-found'
package/src/link.ts CHANGED
@@ -23,9 +23,7 @@ import type {
23
23
  MakeDifferenceOptional,
24
24
  NoInfer,
25
25
  NonNullableUpdater,
26
- PickRequired,
27
26
  Updater,
28
- WithoutEmpty,
29
27
  } from './utils'
30
28
  import type { ParsedLocation } from './location'
31
29
 
@@ -469,11 +467,11 @@ type MakeRequiredParamsReducer<
469
467
  > =
470
468
  | (string extends TFrom
471
469
  ? never
472
- : ResolveFromParams<TRouter, TParamVariant, TFrom> extends WithoutEmpty<
473
- PickRequired<
474
- ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>
475
- >
476
- >
470
+ : ResolveFromParams<
471
+ TRouter,
472
+ TParamVariant,
473
+ TFrom
474
+ > extends ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>
477
475
  ? true
478
476
  : never)
479
477
  | (ParamsReducer<TRouter, TParamVariant, TFrom, TTo> & {})
@@ -593,6 +591,11 @@ export interface LinkOptionsProps {
593
591
  * @default false
594
592
  */
595
593
  disabled?: boolean
594
+ /**
595
+ * When the preload strategy is set to `intent`, this controls the proximity of the link to the cursor before it is preloaded.
596
+ * If the user exits this proximity before this delay, the preload will be cancelled.
597
+ */
598
+ preloadIntentProximity?: number
596
599
  }
597
600
 
598
601
  export type LinkOptions<
package/src/path.ts CHANGED
@@ -5,6 +5,9 @@ import type { AnyPathParams } from './route'
5
5
  export interface Segment {
6
6
  type: 'pathname' | 'param' | 'wildcard'
7
7
  value: string
8
+ // Add a new property to store the static segment if present
9
+ prefixSegment?: string
10
+ suffixSegment?: string
8
11
  }
9
12
 
10
13
  export function joinPaths(paths: Array<string | undefined>) {
@@ -137,10 +140,52 @@ export function resolvePath({
137
140
  }
138
141
  }
139
142
 
140
- const joined = joinPaths([basepath, ...baseSegments.map((d) => d.value)])
143
+ const segmentValues = baseSegments.map((segment) => {
144
+ if (segment.type === 'param') {
145
+ const param = segment.value.substring(1)
146
+ if (segment.prefixSegment && segment.suffixSegment) {
147
+ return `${segment.prefixSegment}{$${param}}${segment.suffixSegment}`
148
+ } else if (segment.prefixSegment) {
149
+ return `${segment.prefixSegment}{$${param}}`
150
+ } else if (segment.suffixSegment) {
151
+ return `{$${param}}${segment.suffixSegment}`
152
+ }
153
+ }
154
+ if (segment.type === 'wildcard') {
155
+ if (segment.prefixSegment && segment.suffixSegment) {
156
+ return `${segment.prefixSegment}{$}${segment.suffixSegment}`
157
+ } else if (segment.prefixSegment) {
158
+ return `${segment.prefixSegment}{$}`
159
+ } else if (segment.suffixSegment) {
160
+ return `{$}${segment.suffixSegment}`
161
+ }
162
+ }
163
+ return segment.value
164
+ })
165
+ const joined = joinPaths([basepath, ...segmentValues])
141
166
  return cleanPath(joined)
142
167
  }
143
168
 
169
+ const PARAM_RE = /^\$.{1,}$/ // $paramName
170
+ const PARAM_W_CURLY_BRACES_RE = /^(.*?)\{(\$[a-zA-Z_$][a-zA-Z0-9_$]*)\}(.*)$/ // prefix{$paramName}suffix
171
+ const WILDCARD_RE = /^\$$/ // $
172
+ const WILDCARD_W_CURLY_BRACES_RE = /^(.*?)\{\$\}(.*)$/ // prefix{$}suffix
173
+
174
+ /**
175
+ * Required: `/foo/$bar` ✅
176
+ * Prefix and Suffix: `/foo/prefix${bar}suffix` ✅
177
+ * Wildcard: `/foo/$` ✅
178
+ * Wildcard with Prefix and Suffix: `/foo/prefix{$}suffix` ✅
179
+ *
180
+ * Future:
181
+ * Optional: `/foo/{-bar}`
182
+ * Optional named segment: `/foo/{bar}`
183
+ * Optional named segment with Prefix and Suffix: `/foo/prefix{-bar}suffix`
184
+ * Escape special characters:
185
+ * - `/foo/[$]` - Static route
186
+ * - `/foo/[$]{$foo} - Dynamic route with a static prefix of `$`
187
+ * - `/foo/{$foo}[$]` - Dynamic route with a static suffix of `$`
188
+ */
144
189
  export function parsePathname(pathname?: string): Array<Segment> {
145
190
  if (!pathname) {
146
191
  return []
@@ -167,20 +212,55 @@ export function parsePathname(pathname?: string): Array<Segment> {
167
212
 
168
213
  segments.push(
169
214
  ...split.map((part): Segment => {
170
- if (part === '$' || part === '*') {
215
+ // Check for wildcard with curly braces: prefix{$}suffix
216
+ const wildcardBracesMatch = part.match(WILDCARD_W_CURLY_BRACES_RE)
217
+ if (wildcardBracesMatch) {
218
+ const prefix = wildcardBracesMatch[1]
219
+ const suffix = wildcardBracesMatch[2]
171
220
  return {
172
221
  type: 'wildcard',
173
- value: part,
222
+ value: '$',
223
+ prefixSegment: prefix || undefined,
224
+ suffixSegment: suffix || undefined,
174
225
  }
175
226
  }
176
227
 
177
- if (part.charAt(0) === '$') {
228
+ // Check for the new parameter format: prefix{$paramName}suffix
229
+ const paramBracesMatch = part.match(PARAM_W_CURLY_BRACES_RE)
230
+ if (paramBracesMatch) {
231
+ const prefix = paramBracesMatch[1]
232
+ const paramName = paramBracesMatch[2]
233
+ const suffix = paramBracesMatch[3]
178
234
  return {
179
235
  type: 'param',
180
- value: part,
236
+ value: '' + paramName,
237
+ prefixSegment: prefix || undefined,
238
+ suffixSegment: suffix || undefined,
181
239
  }
182
240
  }
183
241
 
242
+ // Check for bare parameter format: $paramName (without curly braces)
243
+ if (PARAM_RE.test(part)) {
244
+ const paramName = part.substring(1)
245
+ return {
246
+ type: 'param',
247
+ value: '$' + paramName,
248
+ prefixSegment: undefined,
249
+ suffixSegment: undefined,
250
+ }
251
+ }
252
+
253
+ // Check for bare wildcard: $ (without curly braces)
254
+ if (WILDCARD_RE.test(part)) {
255
+ return {
256
+ type: 'wildcard',
257
+ value: '$',
258
+ prefixSegment: undefined,
259
+ suffixSegment: undefined,
260
+ }
261
+ }
262
+
263
+ // Handle regular pathname segment
184
264
  return {
185
265
  type: 'pathname',
186
266
  value: part.includes('%25')
@@ -248,9 +328,13 @@ export function interpolatePath({
248
328
  interpolatedPathSegments.map((segment) => {
249
329
  if (segment.type === 'wildcard') {
250
330
  usedParams._splat = params._splat
331
+ const segmentPrefix = segment.prefixSegment || ''
332
+ const segmentSuffix = segment.suffixSegment || ''
251
333
  const value = encodeParam('_splat')
252
- if (leaveWildcards) return `${segment.value}${value ?? ''}`
253
- return value
334
+ if (leaveWildcards) {
335
+ return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`
336
+ }
337
+ return `${segmentPrefix}${value}${segmentSuffix}`
254
338
  }
255
339
 
256
340
  if (segment.type === 'param') {
@@ -259,11 +343,14 @@ export function interpolatePath({
259
343
  isMissingParams = true
260
344
  }
261
345
  usedParams[key] = params[key]
346
+
347
+ const segmentPrefix = segment.prefixSegment || ''
348
+ const segmentSuffix = segment.suffixSegment || ''
262
349
  if (leaveParams) {
263
350
  const value = encodeParam(segment.value)
264
- return `${segment.value}${value ?? ''}`
351
+ return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`
265
352
  }
266
- return encodeParam(key) ?? 'undefined'
353
+ return `${segmentPrefix}${encodeParam(key) ?? 'undefined'}${segmentSuffix}`
267
354
  }
268
355
 
269
356
  return segment.value
@@ -390,9 +477,57 @@ export function matchByPath(
390
477
 
391
478
  if (routeSegment) {
392
479
  if (routeSegment.type === 'wildcard') {
393
- const _splat = decodeURI(
394
- joinPaths(baseSegments.slice(i).map((d) => d.value)),
395
- )
480
+ // Capture all remaining segments for a wildcard
481
+ const remainingBaseSegments = baseSegments.slice(i)
482
+
483
+ let _splat: string
484
+
485
+ // If this is a wildcard with prefix/suffix, we need to handle the first segment specially
486
+ if (routeSegment.prefixSegment || routeSegment.suffixSegment) {
487
+ if (!baseSegment) return false
488
+
489
+ const prefix = routeSegment.prefixSegment || ''
490
+ const suffix = routeSegment.suffixSegment || ''
491
+
492
+ // Check if the base segment starts with prefix and ends with suffix
493
+ const baseValue = baseSegment.value
494
+ if ('prefixSegment' in routeSegment) {
495
+ if (!baseValue.startsWith(prefix)) {
496
+ return false
497
+ }
498
+ }
499
+ if ('suffixSegment' in routeSegment) {
500
+ if (
501
+ !baseSegments[baseSegments.length - 1]?.value.endsWith(suffix)
502
+ ) {
503
+ return false
504
+ }
505
+ }
506
+
507
+ let rejoinedSplat = decodeURI(
508
+ joinPaths(remainingBaseSegments.map((d) => d.value)),
509
+ )
510
+
511
+ // Remove the prefix and suffix from the rejoined splat
512
+ if (prefix && rejoinedSplat.startsWith(prefix)) {
513
+ rejoinedSplat = rejoinedSplat.slice(prefix.length)
514
+ }
515
+
516
+ if (suffix && rejoinedSplat.endsWith(suffix)) {
517
+ rejoinedSplat = rejoinedSplat.slice(
518
+ 0,
519
+ rejoinedSplat.length - suffix.length,
520
+ )
521
+ }
522
+
523
+ _splat = rejoinedSplat
524
+ } else {
525
+ // If no prefix/suffix, just rejoin the remaining segments
526
+ _splat = decodeURI(
527
+ joinPaths(remainingBaseSegments.map((d) => d.value)),
528
+ )
529
+ }
530
+
396
531
  // TODO: Deprecate *
397
532
  params['*'] = _splat
398
533
  params['_splat'] = _splat
@@ -426,11 +561,41 @@ export function matchByPath(
426
561
  if (baseSegment.value === '/') {
427
562
  return false
428
563
  }
429
- if (baseSegment.value.charAt(0) !== '$') {
430
- params[routeSegment.value.substring(1)] = decodeURIComponent(
431
- baseSegment.value,
432
- )
564
+
565
+ let _paramValue: string
566
+
567
+ // If this param has prefix/suffix, we need to extract the actual parameter value
568
+ if (routeSegment.prefixSegment || routeSegment.suffixSegment) {
569
+ const prefix = routeSegment.prefixSegment || ''
570
+ const suffix = routeSegment.suffixSegment || ''
571
+
572
+ // Check if the base segment starts with prefix and ends with suffix
573
+ const baseValue = baseSegment.value
574
+ if (prefix && !baseValue.startsWith(prefix)) {
575
+ return false
576
+ }
577
+ if (suffix && !baseValue.endsWith(suffix)) {
578
+ return false
579
+ }
580
+
581
+ let paramValue = baseValue
582
+ if (prefix && paramValue.startsWith(prefix)) {
583
+ paramValue = paramValue.slice(prefix.length)
584
+ }
585
+ if (suffix && paramValue.endsWith(suffix)) {
586
+ paramValue = paramValue.slice(
587
+ 0,
588
+ paramValue.length - suffix.length,
589
+ )
590
+ }
591
+
592
+ _paramValue = decodeURIComponent(paramValue)
593
+ } else {
594
+ // If no prefix/suffix, just decode the base segment value
595
+ _paramValue = decodeURIComponent(baseSegment.value)
433
596
  }
597
+
598
+ params[routeSegment.value.substring(1)] = _paramValue
434
599
  }
435
600
  }
436
601