react-routes-forge 1.4.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathOptions } from '../index-CRAdjXCI.cjs';
1
+ import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathOptions } from '../index-_N1whVbt.cjs';
2
2
 
3
3
  /**
4
4
  * A typed wrapper around React Router's `useParams`.
@@ -15,6 +15,9 @@ import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathO
15
15
  * const PATHS = defineRoutes({ USERS: { EDIT: '/users/edit/:id' } } as const);
16
16
  * const { id } = useRouteParams(PATHS.USERS.EDIT);
17
17
  * ```
18
+ *
19
+ * @warning React Router can return `undefined` for missing parameters (e.g., if a param is optional or absent).
20
+ * Always check for `undefined` values at runtime, even though the type signature assumes they are present.
18
21
  */
19
22
  declare function useRouteParams<T extends string = string>(): Record<ExtractParams<T>, string>;
20
23
  declare function useRouteParams<P extends string>(route: {
@@ -88,10 +91,10 @@ declare function useActivePath(template: string, options?: {
88
91
  * setQuery({ tab: 'details', page: 2 });
89
92
  * ```
90
93
  */
91
- declare function useTypedSearchParams(options?: {
94
+ declare function useTypedSearchParams<T extends QueryParams = QueryParams>(options?: {
92
95
  coerceBooleans?: boolean;
93
96
  coerceNumbers?: boolean;
94
- }): readonly [QueryParams, (newQuery: QueryParams, navigateOptions?: {
97
+ }): readonly [T, (newQuery: Partial<T> | QueryParams, navigateOptions?: {
95
98
  replace?: boolean;
96
99
  state?: unknown;
97
100
  }) => void];
@@ -1,4 +1,4 @@
1
- import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathOptions } from '../index-CRAdjXCI.js';
1
+ import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathOptions } from '../index-_N1whVbt.js';
2
2
 
3
3
  /**
4
4
  * A typed wrapper around React Router's `useParams`.
@@ -15,6 +15,9 @@ import { E as ExtractParams, R as RouteParams, Q as QueryParams, B as BuildPathO
15
15
  * const PATHS = defineRoutes({ USERS: { EDIT: '/users/edit/:id' } } as const);
16
16
  * const { id } = useRouteParams(PATHS.USERS.EDIT);
17
17
  * ```
18
+ *
19
+ * @warning React Router can return `undefined` for missing parameters (e.g., if a param is optional or absent).
20
+ * Always check for `undefined` values at runtime, even though the type signature assumes they are present.
18
21
  */
19
22
  declare function useRouteParams<T extends string = string>(): Record<ExtractParams<T>, string>;
20
23
  declare function useRouteParams<P extends string>(route: {
@@ -88,10 +91,10 @@ declare function useActivePath(template: string, options?: {
88
91
  * setQuery({ tab: 'details', page: 2 });
89
92
  * ```
90
93
  */
91
- declare function useTypedSearchParams(options?: {
94
+ declare function useTypedSearchParams<T extends QueryParams = QueryParams>(options?: {
92
95
  coerceBooleans?: boolean;
93
96
  coerceNumbers?: boolean;
94
- }): readonly [QueryParams, (newQuery: QueryParams, navigateOptions?: {
97
+ }): readonly [T, (newQuery: Partial<T> | QueryParams, navigateOptions?: {
95
98
  replace?: boolean;
96
99
  state?: unknown;
97
100
  }) => void];
@@ -1,2 +1,2 @@
1
- import{j as s,k as n,l as i,o as u}from"../chunk-IP4ZU25B.js";import{useParams as y}from"react-router";function g(e){return y()}import{useCallback as l}from"react";import{useNavigate as f}from"react-router";function d(){let e=f();return l((r,t)=>{e(String(r),t)},[e])}function x(e,r,t,a){return i(e,r,t,a)}import{useLocation as h}from"react-router";function v(e,r={}){let t=h();return u(t.pathname,e,r)}import{useCallback as R}from"react";import{useLocation as b,useNavigate as N}from"react-router";function T(e){let r=b(),t=N(),a=n(r.search,e),m=R((p,c)=>{let o=s("",p),P=new URLSearchParams(o.startsWith("?")?o.slice(1):o);t(`?${P.toString()}`,c)},[t]);return[a,m]}export{v as useActivePath,d as useNavigateTo,x as useResolvedPath,g as useRouteParams,T as useTypedSearchParams};
1
+ import{j as o,k as s,l as n,o as i}from"../chunk-3E7SIS7B.js";import{useParams as P}from"react-router";function l(e){return P()}import{useCallback as y}from"react";import{useNavigate as g}from"react-router";function f(){let e=g();return y((r,t)=>{e(String(r),t)},[e])}function x(e,r,t,a){return n(e,r,t,a)}import{useLocation as d}from"react-router";function h(e,r={}){let t=d();return i(t.pathname,e,r)}import{useCallback as v,useMemo as R}from"react";import{useLocation as b,useNavigate as N}from"react-router";function T(e){let r=b(),t=N(),a=R(()=>s(r.search,e),[r.search,e]),u=v((m,c)=>{let p=o("",m);t(`${p}${r.hash}`,c)},[t,r.hash]);return[a,u]}export{h as useActivePath,f as useNavigateTo,x as useResolvedPath,l as useRouteParams,T as useTypedSearchParams};
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/useRouteParams.ts","../../src/hooks/useNavigateTo.ts","../../src/hooks/useResolvedPath.ts","../../src/hooks/useActivePath.ts","../../src/hooks/useTypedSearchParams.ts"],"sourcesContent":["import { useParams } from \"react-router\";\nimport type { ExtractParams } from \"../types\";\n\n/**\n * A typed wrapper around React Router's `useParams`.\n *\n * Pass a route template as a type parameter, or a dynamic route value from a\n * `defineRoutes()` tree for automatic type inference:\n *\n * @example\n * ```tsx\n * // Route: '/a/:x/b/:y/c/:z'\n * const { x, y, z } = useRouteParams<'/a/:x/b/:y/c/:z'>();\n *\n * // Or pass a route from your PATHS tree — params are inferred from it:\n * const PATHS = defineRoutes({ USERS: { EDIT: '/users/edit/:id' } } as const);\n * const { id } = useRouteParams(PATHS.USERS.EDIT);\n * ```\n */\n// Overload 1: no-arg generic — caller provides the template literal as T\nexport function useRouteParams<T extends string = string>(): Record<\n ExtractParams<T>,\n string\n>;\n// Overload 2: pass a route from defineRoutes() — P is inferred from paramNames array element type\nexport function useRouteParams<P extends string>(route: {\n readonly paramNames: ReadonlyArray<P> | Array<P>;\n}): Record<P, string>;\n// Implementation\nexport function useRouteParams<P extends string>(_route?: {\n readonly paramNames: ReadonlyArray<P> | Array<P>;\n}): Record<string, string> {\n return useParams() as Record<string, string>;\n}\n","import { useCallback } from \"react\";\nimport { useNavigate } from \"react-router\";\n\nexport type NavigateOptions = {\n replace?: boolean;\n state?: unknown;\n};\n\n/**\n * A typed `navigate` helper that accepts a resolved path (output of `.build()`)\n * or a route value straight from `defineRoutes()` (e.g. a `String` object or a\n * primitive string), with optional navigation options.\n *\n * `String` objects (used by route values so they can carry `.build()`) are\n * coerced to primitives, since React Router's `navigate()` ignores them.\n *\n * @example\n * ```tsx\n * const navigateTo = useNavigateTo();\n * navigateTo(PATHS.USERS.EDIT.build({ id: 42 }));\n * navigateTo(PATHS.HOME, { replace: true });\n * ```\n */\nexport function useNavigateTo() {\n const navigate = useNavigate();\n\n return useCallback(\n (path: string, options?: NavigateOptions) => {\n navigate(String(path), options);\n },\n [navigate],\n );\n}\n","import { buildPath } from \"../core/build\";\nimport type { BuildPathOptions, QueryParams, RouteParams } from \"../types\";\n\n/**\n * Resolves a dynamic path template against params, mirroring `buildPath()`.\n *\n * Accepts the same `options` bag as `build()` / `buildPath()`:\n * - (default) soft-fail: `console.warn` and return the partial path with unresolved `:param` placeholders.\n * - `{ strict: true }`: throw a `RangeError` on missing params — matching `.build()`'s strict behaviour.\n *\n * @example\n * ```tsx\n * const path = useResolvedPath('/users/:id', { id: 42 }); // → '/users/42'\n * const path = useResolvedPath('/users/:id', {}, undefined, { strict: true }); // throws RangeError\n * const path = useResolvedPath('/files/*', { \"*\": \"a/b\" }); // → '/files/a/b'\n * ```\n */\nexport function useResolvedPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n return buildPath(template, params, query, options);\n}\n","import { useLocation } from \"react-router\";\nimport { isActivePath } from \"../core/match\";\n\n/**\n * A hook that checks whether the current location matches a route template or path.\n * Thin wrapper around `isActivePath(useLocation().pathname, template, options)`.\n *\n * @example\n * ```tsx\n * const isActive = useActivePath(PATHS.USERS.ROOT, { exact: false });\n * ```\n */\nexport function useActivePath(\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {},\n): boolean {\n // Forwarded as-is — isActivePath defaults `exact`/`caseSensitive`\n // individually, so a partial options object here still behaves correctly.\n const location = useLocation();\n return isActivePath(location.pathname, template, options);\n}\n","import { useCallback } from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { appendQuery, extractQueryFromPath } from \"../core/query\";\nimport type { QueryParams } from \"../types\";\n\n/**\n * A typed wrapper around React Router's `useSearchParams`.\n * Returns parsed query params object and a setter that updates query params.\n *\n * Implemented on top of `useLocation` + `useNavigate` (both exported by the\n * `react-router` core in v6 and v7) rather than `useSearchParams`, because in\n * React Router v6 `useSearchParams` only exists in the DOM wrapper package\n * (`react-router-dom`), while v7 moved it into `react-router`. Reimplementing\n * it lets the whole hooks entry work identically against both packages.\n *\n * @example\n * ```tsx\n * const [query, setQuery] = useTypedSearchParams({ coerceBooleans: true, coerceNumbers: true });\n * setQuery({ tab: 'details', page: 2 });\n * ```\n */\nexport function useTypedSearchParams(options?: {\n coerceBooleans?: boolean;\n coerceNumbers?: boolean;\n}) {\n const location = useLocation();\n const navigate = useNavigate();\n\n const queryParams = extractQueryFromPath(location.search, options);\n\n const setTypedQuery = useCallback(\n (\n newQuery: QueryParams,\n navigateOptions?: { replace?: boolean; state?: unknown },\n ) => {\n const updatedPath = appendQuery(\"\", newQuery);\n const updatedSearchParams = new URLSearchParams(\n updatedPath.startsWith(\"?\") ? updatedPath.slice(1) : updatedPath,\n );\n navigate(`?${updatedSearchParams.toString()}`, navigateOptions);\n },\n [navigate],\n );\n\n return [queryParams, setTypedQuery] as const;\n}\n"],"mappings":"8DAAA,OAAS,aAAAA,MAAiB,eA6BnB,SAASC,EAAiCC,EAEtB,CACzB,OAAOF,EAAU,CACnB,CCjCA,OAAS,eAAAG,MAAmB,QAC5B,OAAS,eAAAC,MAAmB,eAsBrB,SAASC,GAAgB,CAC9B,IAAMC,EAAWF,EAAY,EAE7B,OAAOD,EACL,CAACI,EAAcC,IAA8B,CAC3CF,EAAS,OAAOC,CAAI,EAAGC,CAAO,CAChC,EACA,CAACF,CAAQ,CACX,CACF,CCfO,SAASG,EACdC,EACAC,EACAC,EACAC,EACQ,CACR,OAAOC,EAAUJ,EAAUC,EAAQC,EAAOC,CAAO,CACnD,CCxBA,OAAS,eAAAE,MAAmB,eAYrB,SAASC,EACdC,EACAC,EAAwD,CAAC,EAChD,CAGT,IAAMC,EAAWC,EAAY,EAC7B,OAAOC,EAAaF,EAAS,SAAUF,EAAUC,CAAO,CAC1D,CCpBA,OAAS,eAAAI,MAAmB,QAC5B,OAAS,eAAAC,EAAa,eAAAC,MAAmB,eAoBlC,SAASC,EAAqBC,EAGlC,CACD,IAAMC,EAAWC,EAAY,EACvBC,EAAWC,EAAY,EAEvBC,EAAcC,EAAqBL,EAAS,OAAQD,CAAO,EAE3DO,EAAgBC,EACpB,CACEC,EACAC,IACG,CACH,IAAMC,EAAcC,EAAY,GAAIH,CAAQ,EACtCI,EAAsB,IAAI,gBAC9BF,EAAY,WAAW,GAAG,EAAIA,EAAY,MAAM,CAAC,EAAIA,CACvD,EACAR,EAAS,IAAIU,EAAoB,SAAS,CAAC,GAAIH,CAAe,CAChE,EACA,CAACP,CAAQ,CACX,EAEA,MAAO,CAACE,EAAaE,CAAa,CACpC","names":["useParams","useRouteParams","_route","useCallback","useNavigate","useNavigateTo","navigate","path","options","useResolvedPath","template","params","query","options","buildPath","useLocation","useActivePath","template","options","location","useLocation","isActivePath","useCallback","useLocation","useNavigate","useTypedSearchParams","options","location","useLocation","navigate","useNavigate","queryParams","extractQueryFromPath","setTypedQuery","useCallback","newQuery","navigateOptions","updatedPath","appendQuery","updatedSearchParams"]}
1
+ {"version":3,"sources":["../../src/hooks/useRouteParams.ts","../../src/hooks/useNavigateTo.ts","../../src/hooks/useResolvedPath.ts","../../src/hooks/useActivePath.ts","../../src/hooks/useTypedSearchParams.ts"],"sourcesContent":["\"use client\";\n\nimport { useParams } from \"react-router\";\nimport type { ExtractParams } from \"../types\";\n\n/**\n * A typed wrapper around React Router's `useParams`.\n *\n * Pass a route template as a type parameter, or a dynamic route value from a\n * `defineRoutes()` tree for automatic type inference:\n *\n * @example\n * ```tsx\n * // Route: '/a/:x/b/:y/c/:z'\n * const { x, y, z } = useRouteParams<'/a/:x/b/:y/c/:z'>();\n *\n * // Or pass a route from your PATHS tree — params are inferred from it:\n * const PATHS = defineRoutes({ USERS: { EDIT: '/users/edit/:id' } } as const);\n * const { id } = useRouteParams(PATHS.USERS.EDIT);\n * ```\n *\n * @warning React Router can return `undefined` for missing parameters (e.g., if a param is optional or absent).\n * Always check for `undefined` values at runtime, even though the type signature assumes they are present.\n */\n// Overload 1: no-arg generic — caller provides the template literal as T\nexport function useRouteParams<T extends string = string>(): Record<\n ExtractParams<T>,\n string\n>;\n// Overload 2: pass a route from defineRoutes() — P is inferred from paramNames array element type\nexport function useRouteParams<P extends string>(route: {\n readonly paramNames: ReadonlyArray<P> | Array<P>;\n}): Record<P, string>;\n// Implementation\nexport function useRouteParams<P extends string>(_route?: {\n readonly paramNames: ReadonlyArray<P> | Array<P>;\n}): Record<string, string> {\n\n return useParams() as Record<string, string>;\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useNavigate } from \"react-router\";\n\nexport type NavigateOptions = {\n replace?: boolean;\n state?: unknown;\n};\n\n/**\n * A typed `navigate` helper that accepts a resolved path (output of `.build()`)\n * or a route value straight from `defineRoutes()` (e.g. a `String` object or a\n * primitive string), with optional navigation options.\n *\n * `String` objects (used by route values so they can carry `.build()`) are\n * coerced to primitives, since React Router's `navigate()` ignores them.\n *\n * @example\n * ```tsx\n * const navigateTo = useNavigateTo();\n * navigateTo(PATHS.USERS.EDIT.build({ id: 42 }));\n * navigateTo(PATHS.HOME, { replace: true });\n * ```\n */\nexport function useNavigateTo() {\n\n const navigate = useNavigate();\n\n return useCallback(\n (path: string, options?: NavigateOptions) => {\n navigate(String(path), options);\n },\n [navigate],\n );\n}\n","\"use client\";\n\nimport { buildPath } from \"../core/build\";\nimport type { BuildPathOptions, QueryParams, RouteParams } from \"../types\";\n\n/**\n * Resolves a dynamic path template against params, mirroring `buildPath()`.\n *\n * Accepts the same `options` bag as `build()` / `buildPath()`:\n * - (default) soft-fail: `console.warn` and return the partial path with unresolved `:param` placeholders.\n * - `{ strict: true }`: throw a `RangeError` on missing params — matching `.build()`'s strict behaviour.\n *\n * @example\n * ```tsx\n * const path = useResolvedPath('/users/:id', { id: 42 }); // → '/users/42'\n * const path = useResolvedPath('/users/:id', {}, undefined, { strict: true }); // throws RangeError\n * const path = useResolvedPath('/files/*', { \"*\": \"a/b\" }); // → '/files/a/b'\n * ```\n */\nexport function useResolvedPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n\n return buildPath(template, params, query, options);\n}\n","\"use client\";\n\nimport { useLocation } from \"react-router\";\nimport { isActivePath } from \"../core/match\";\n\n/**\n * A hook that checks whether the current location matches a route template or path.\n * Thin wrapper around `isActivePath(useLocation().pathname, template, options)`.\n *\n * @example\n * ```tsx\n * const isActive = useActivePath(PATHS.USERS.ROOT, { exact: false });\n * ```\n */\nexport function useActivePath(\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {},\n): boolean {\n\n // Forwarded as-is — isActivePath defaults `exact`/`caseSensitive`\n // individually, so a partial options object here still behaves correctly.\n const location = useLocation();\n return isActivePath(location.pathname, template, options);\n}\n","\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { extractQueryFromPath, appendQuery } from \"../core/query\";\nimport type { QueryParams } from \"../types\";\n\n/**\n * A typed wrapper around React Router's `useSearchParams`.\n * Returns parsed query params object and a setter that updates query params.\n *\n * Implemented on top of `useLocation` + `useNavigate` (both exported by the\n * `react-router` core in v6 and v7) rather than `useSearchParams`, because in\n * React Router v6 `useSearchParams` only exists in the DOM wrapper package\n * (`react-router-dom`), while v7 moved it into `react-router`. Reimplementing\n * it lets the whole hooks entry work identically against both packages.\n *\n * @example\n * ```tsx\n * const [query, setQuery] = useTypedSearchParams({ coerceBooleans: true, coerceNumbers: true });\n * setQuery({ tab: 'details', page: 2 });\n * ```\n */\nexport function useTypedSearchParams<T extends QueryParams = QueryParams>(options?: {\n coerceBooleans?: boolean;\n coerceNumbers?: boolean;\n}) {\n\n const location = useLocation();\n const navigate = useNavigate();\n\n const queryParams = useMemo(\n () => extractQueryFromPath(location.search, options) as T,\n [location.search, options],\n );\n\n const setTypedQuery = useCallback(\n (\n newQuery: Partial<T> | QueryParams,\n navigateOptions?: { replace?: boolean; state?: unknown },\n ) => {\n const queryString = appendQuery(\"\", newQuery as QueryParams);\n navigate(`${queryString}${location.hash}`, navigateOptions);\n },\n [navigate, location.hash],\n );\n\n return [queryParams, setTypedQuery] as const;\n}\n"],"mappings":"8DAEA,OAAS,aAAAA,MAAiB,eAgCnB,SAASC,EAAiCC,EAEtB,CAEzB,OAAOF,EAAU,CACnB,CCrCA,OAAS,eAAAG,MAAmB,QAC5B,OAAS,eAAAC,MAAmB,eAsBrB,SAASC,GAAgB,CAE9B,IAAMC,EAAWF,EAAY,EAE7B,OAAOD,EACL,CAACI,EAAcC,IAA8B,CAC3CF,EAAS,OAAOC,CAAI,EAAGC,CAAO,CAChC,EACA,CAACF,CAAQ,CACX,CACF,CChBO,SAASG,EACdC,EACAC,EACAC,EACAC,EACQ,CAER,OAAOC,EAAUJ,EAAUC,EAAQC,EAAOC,CAAO,CACnD,CCzBA,OAAS,eAAAE,MAAmB,eAYrB,SAASC,EACdC,EACAC,EAAwD,CAAC,EAChD,CAIT,IAAMC,EAAWC,EAAY,EAC7B,OAAOC,EAAaF,EAAS,SAAUF,EAAUC,CAAO,CAC1D,CCrBA,OAAS,eAAAI,EAAa,WAAAC,MAAe,QACrC,OAAS,eAAAC,EAAa,eAAAC,MAAmB,eAoBlC,SAASC,EAA0DC,EAGvE,CAED,IAAMC,EAAWC,EAAY,EACvBC,EAAWC,EAAY,EAEvBC,EAAcC,EAClB,IAAMC,EAAqBN,EAAS,OAAQD,CAAO,EACnD,CAACC,EAAS,OAAQD,CAAO,CAC3B,EAEMQ,EAAgBC,EACpB,CACEC,EACAC,IACG,CACH,IAAMC,EAAcC,EAAY,GAAIH,CAAuB,EAC3DP,EAAS,GAAGS,CAAW,GAAGX,EAAS,IAAI,GAAIU,CAAe,CAC5D,EACA,CAACR,EAAUF,EAAS,IAAI,CAC1B,EAEA,MAAO,CAACI,EAAaG,CAAa,CACpC","names":["useParams","useRouteParams","_route","useCallback","useNavigate","useNavigateTo","navigate","path","options","useResolvedPath","template","params","query","options","buildPath","useLocation","useActivePath","template","options","location","useLocation","isActivePath","useCallback","useMemo","useLocation","useNavigate","useTypedSearchParams","options","location","useLocation","navigate","useNavigate","queryParams","useMemo","extractQueryFromPath","setTypedQuery","useCallback","newQuery","navigateOptions","queryString","appendQuery"]}
@@ -11,6 +11,7 @@ type RouteBuilder<TParams extends RouteParams = RouteParams> = (params: TParams)
11
11
  */
12
12
  type RouteParam = string | number | boolean;
13
13
  type RouteParams = Record<string, RouteParam>;
14
+
14
15
  /**
15
16
  * A leaf node in a route definition: either a static path or a builder function.
16
17
  */
@@ -60,7 +61,10 @@ type PathParams<T extends string> = ExtractParams<T> extends never ? never : {
60
61
  /**
61
62
  * Acceptable query param value types for route builders.
62
63
  */
63
- type QueryParams = Record<string, RouteParam | RouteParam[] | null | undefined>;
64
+ type QueryParamValue = RouteParam | (RouteParam | null | undefined)[] | null | undefined | QueryParams;
65
+ type QueryParams = {
66
+ [key: string]: QueryParamValue;
67
+ };
64
68
  /**
65
69
  * Options accepted by `buildPath` (4th positional argument).
66
70
  *
@@ -88,6 +92,11 @@ type BuildPathOptions = {
88
92
  * The leading `#` is added automatically.
89
93
  */
90
94
  hash?: string;
95
+ /**
96
+ * Optional locale prefix to prepend to the built path (e.g., "fr" or "/en-US").
97
+ * Automatically handles slash normalization.
98
+ */
99
+ locale?: string;
91
100
  };
92
101
  /**
93
102
  * A single entry produced by `flattenRoutes()`.
@@ -11,6 +11,7 @@ type RouteBuilder<TParams extends RouteParams = RouteParams> = (params: TParams)
11
11
  */
12
12
  type RouteParam = string | number | boolean;
13
13
  type RouteParams = Record<string, RouteParam>;
14
+
14
15
  /**
15
16
  * A leaf node in a route definition: either a static path or a builder function.
16
17
  */
@@ -60,7 +61,10 @@ type PathParams<T extends string> = ExtractParams<T> extends never ? never : {
60
61
  /**
61
62
  * Acceptable query param value types for route builders.
62
63
  */
63
- type QueryParams = Record<string, RouteParam | RouteParam[] | null | undefined>;
64
+ type QueryParamValue = RouteParam | (RouteParam | null | undefined)[] | null | undefined | QueryParams;
65
+ type QueryParams = {
66
+ [key: string]: QueryParamValue;
67
+ };
64
68
  /**
65
69
  * Options accepted by `buildPath` (4th positional argument).
66
70
  *
@@ -88,6 +92,11 @@ type BuildPathOptions = {
88
92
  * The leading `#` is added automatically.
89
93
  */
90
94
  hash?: string;
95
+ /**
96
+ * Optional locale prefix to prepend to the built path (e.g., "fr" or "/en-US").
97
+ * Automatically handles slash normalization.
98
+ */
99
+ locale?: string;
91
100
  };
92
101
  /**
93
102
  * A single entry produced by `flattenRoutes()`.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var S=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var q=(t,e)=>{for(var r in e)S(t,r,{get:e[r],enumerable:!0})},G=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of H(e))!z.call(t,o)&&o!==r&&S(t,o,{get:()=>e[o],enumerable:!(n=U(e,o))||n.enumerable});return t};var Z=t=>G(S({},"__esModule",{value:!0}),t);var nt={};q(nt,{appendQuery:()=>v,build:()=>F,buildPath:()=>m,clearPathCache:()=>j,defineRoutes:()=>K,devWarn:()=>f,extractParamNames:()=>g,extractParamsFromPath:()=>T,extractQueryFromPath:()=>_,flattenRoutes:()=>R,getBreadcrumbs:()=>I,getParamNames:()=>D,isActivePath:()=>k,isDynamic:()=>l,joinPaths:()=>M,matchPath:()=>h});module.exports=Z(nt);function E(){return globalThis.process?.env?.NODE_ENV==="production"}function f(t){E()||console.warn(t)}var X=()=>/[.*+?^${}()|[\]\\]/g,$=()=>/(^|\/):([A-Za-z0-9_]+)(\?)?/g;function y(t){return t.replace(X(),"\\$&")}function C(t){let e=t.endsWith("/*"),r=e?t.slice(0,-2):t,n="",o=0;for(let a of r.matchAll($())){let c=a.index??0,i=a[0];n+=y(r.slice(o,c));let[,u="",,s]=a;n+=s?`(?:${y(u)}([^/]+))?`:`${y(u)}([^/]+)`,o=c+(i?.length??0)}return n+y(r.slice(o))+(e?"(?:/(.*))?":"")}function N(t,e){return new RegExp(`:${y(e)}\\?(?![A-Za-z0-9_])`).test(t)}var A=new Map,V=/^/;function w(t){if(t==="/")return V;let e=A.get(t);if(e!==void 0)return e;let r=new RegExp(`^${C(t)}(?=/|$)`);return A.set(t,r),r}function Q(t){try{return decodeURIComponent(t)}catch{return t}}var B=new Map;function h(t,e){let r=e?.end??!0,n=e?.caseSensitive?"":"i",o=`${t}:${r}:${n}`,a=B.get(o);if(a!==void 0)return a;let c=C(t),i=r?`^${c}$`:`^${c}(?=/|$)`,u=new RegExp(i,n);return B.set(o,u),u}function j(){A.clear(),B.clear()}function g(t){let e=[...t.matchAll($())].map(r=>r[2]);return t.endsWith("/*")&&e.push("*"),e}function l(t){return $().test(t)||t.endsWith("/*")}function D(t){return g(t)}function T(t,e){let r=e.split("?")[0]??"",n=g(t),o=r.match(h(t));if(!o)return{};let a={};return n.forEach((c,i)=>{let u=o[i+1];u!==void 0&&(a[c]=Q(u))}),a}function v(t,e,r){let n=t.indexOf("#"),o=n===-1?t:t.slice(0,n),a=n===-1?"":t.slice(n+1),c=new URLSearchParams;if(e)for(let[s,p]of Object.entries(e))p!=null&&(Array.isArray(p)?p.forEach(d=>{d!=null&&c.append(s,String(d))}):c.append(s,String(p)));let i=o,u=c.toString();return u&&(i+=(i.includes("?")?"&":"?")+u),r?i+="#"+r:a&&(i+="#"+a),i}function _(t,e){let r=t.indexOf("#"),n=r===-1?t:t.slice(0,r),o=n.indexOf("?");if(o===-1)return{};let a=new URLSearchParams(n.slice(o+1)),c={};for(let i of new Set(a.keys())){let s=a.getAll(i).map(p=>e?.coerceBooleans&&(p==="true"||p==="false")?p==="true":e?.coerceNumbers&&p.trim()!==""&&!isNaN(Number(p))?Number(p):p);c[i]=s.length>1?s:s[0]??""}return c}function m(t,e,r,n){let o=g(t),a=o.filter(i=>(e[i]===void 0||e[i]===null)&&!N(t,i)&&i!=="*"),c=o.reduce((i,u)=>{let s=e[u],p=s==null;if(u==="*"){if(p)return i.replace(/\/\*$/,"")||"/";let x=n?.encode===!1?String(s):String(s).split("/").map(P=>encodeURIComponent(P)).join("/");return i.replace(/\/\*$/,`/${x}`)}let d=new RegExp(`(^|/):${y(u)}\\??(?![A-Za-z0-9_])`,"g");return i.replace(d,(x,P)=>{if(p)return x.endsWith("?")?"":`${P}:${u}`;let b=n?.encode===!1?String(s):encodeURIComponent(String(s));return`${P}${b}`})},t);if(a.length>0){if(n?.strict)throw new RangeError(`[route-forge] Missing required param(s) ${a.map(i=>`":${i}"`).join(", ")} in template "${t}".`);f(`[route-forge] Unresolved params in path "${c}". Check that all :param segments have matching keys.`)}return v(c,r,n?.hash)}function F(t,e,r,n){return m(t,e,r,n)}function M(...t){return"/"+t.map(n=>n.replace(/^\/+/,"").replace(/\/+$/,"")).filter(Boolean).join("/")}function k(t,e,r={}){let n=r.exact??!0,o=r.caseSensitive??!1,a=(t.split("?")[0]??"").replace(/\/+$/,"")||"/",c=e.replace(/\/+$/,"")||"/",i=o?c:c.toLowerCase(),u=o?a:a.toLowerCase();return(n?h(i,{caseSensitive:o}):w(i)).test(u)}function R(t,e=""){let r=[];for(let n of Object.keys(t)){let o=e?`${e}.${n}`:n,a=t[n];typeof a=="string"?r.push({key:o,path:a}):a instanceof String?r.push({key:o,path:a.valueOf()}):typeof a=="object"&&a!==null&&r.push(...R(a,o))}return r}function J(t){let e=t.split("."),r=e[e.length-1]??t;return(r==="ROOT"&&e.length>1?e[e.length-2]:r).replace(/_/g," ").toLowerCase().replace(/^\w/,o=>o.toUpperCase())}function I(t,e,r){let n=Array.isArray(t)?t:R(t),o=e.split("?")[0]??"",a=r?.labelResolver??J,c=r?.labels??{},i=[];for(let s of n){let p=h(s.path),d=o.match(p);if(d){let b=T(s.path,d[0]),O=l(s.path)?m(s.path,b):s.path;i.push({key:s.key,resolvedPath:O,template:s.path,isCurrent:!0});continue}let x=w(s.path),P=o.match(x);if(P){let b=P[0],O=T(s.path,b),L=l(s.path)?m(s.path,O):s.path;i.push({key:s.key,resolvedPath:L,template:s.path,isCurrent:!1})}}let u=s=>s.split("/").filter(Boolean).length;return i.sort((s,p)=>u(s.template)-u(p.template)),i.map(s=>({key:s.key,label:c[s.key]??a(s.key),path:s.resolvedPath,isCurrent:s.isCurrent}))}var Y=t=>typeof t=="object"&&t!==null&&!Array.isArray(t)&&Object.getPrototypeOf(t)===Object.prototype;function tt(t,e){t.startsWith("/")||f(`[route-forge] Route "${e}" does not start with "/": "${t}".`),t.includes("*")&&!t.endsWith("/*")&&f(`[route-forge] Route "${e}" uses "*" outside a trailing "/*" splat; only a trailing splat is supported: "${t}".`)}function et(t){let e=new Map,r=new Set,n=R(t);for(let o of n){let a=e.get(o.path);a===void 0?e.set(o.path,o.key):r.has(o.path)||(r.add(o.path),f(`[route-forge] Duplicate route path "${o.path}" for "${a}" and "${o.key}". Only one of them will be reachable.`))}for(let o=0;o<n.length;o++){let a=n[o];if(l(a.path))for(let c=o+1;c<n.length;c++){let i=n[c];if(!l(i.path)&&k(i.path,a.path,{exact:!0})){let u=`${a.key}->${i.key}`;r.has(u)||(r.add(u),f(`[route-forge] Route "${i.key}" ("${i.path}") is shadowed by dynamic route "${a.key}" ("${a.path}"). Place static routes before dynamic parameters in route trees.`))}}}}function rt(){Object.prototype.hasOwnProperty.call(String.prototype,"build")||(Object.defineProperty(String.prototype,"build",{value:function(e,r,n){let o=this.valueOf();return l(o)?m(o,e??{},r,n):m(o,{},e,r)},writable:!1,enumerable:!1,configurable:!0}),Object.defineProperty(String.prototype,"paramNames",{get(){return g(this.valueOf())},enumerable:!1,configurable:!0}))}rt();function W(t){let e={};for(let r in t){if(!Object.prototype.hasOwnProperty.call(t,r))continue;let n=t[r];typeof n=="string"?(tt(n,r),e[r]=l(n)?n:n):Y(n)&&(e[r]=W(n))}return e}function K(t){let e=W(t);return E()||et(e),e}0&&(module.exports={appendQuery,build,buildPath,clearPathCache,defineRoutes,devWarn,extractParamNames,extractParamsFromPath,extractQueryFromPath,flattenRoutes,getBreadcrumbs,getParamNames,isActivePath,isDynamic,joinPaths,matchPath});
1
+ "use strict";var S=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var V=(t,e)=>{for(var o in e)S(t,o,{get:e[o],enumerable:!0})},G=(t,e,o,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of q(e))!z.call(t,r)&&r!==o&&S(t,r,{get:()=>e[r],enumerable:!(n=H(e,r))||n.enumerable});return t};var Z=t=>G(S({},"__esModule",{value:!0}),t);var at={};V(at,{appendQuery:()=>k,build:()=>I,buildPath:()=>g,clearPathCache:()=>D,defineRoutes:()=>U,devWarn:()=>h,extractParamNames:()=>R,extractParamsFromPath:()=>$,extractQueryFromPath:()=>F,flattenRoutes:()=>b,getBreadcrumbs:()=>W,getParamNames:()=>M,isActivePath:()=>v,isDynamic:()=>d,joinPaths:()=>Q,matchPath:()=>y});module.exports=Z(at);function E(){return globalThis.process?.env?.NODE_ENV==="production"}function h(t){E()||console.warn(t)}var X=/[.*+?^${}()|[\]\\]/g,w=/(^|\/):([A-Za-z0-9_]+)(\?)?/g;function x(t){return t.replace(X,"\\$&")}function C(t){let e=t.endsWith("/*"),o=e?t.slice(0,-2):t,n="",r=0;for(let a of o.matchAll(w)){let s=a.index??0,u=a[0];n+=x(o.slice(r,s));let[,c="",,i]=a;n+=i?`(?:${x(c)}([^/]+))?`:`${x(c)}([^/]+)`,r=s+(u?.length??0)}return n+x(o.slice(r))+(e?"(?:/(.*))?":"")}function j(t,e){return new RegExp(`:${x(e)}\\?(?![A-Za-z0-9_])`).test(t)}var A=new Map,J=/^/;function O(t){if(t==="/")return J;let e=A.get(t);if(e!==void 0)return e;let o=new RegExp(`^${C(t)}(?=/|$)`);return A.set(t,o),o}function N(t){try{return decodeURIComponent(t)}catch{return t}}var B=new Map;function y(t,e){let o=e?.end??!0,n=e?.caseSensitive?"":"i",r=`${t}:${o}:${n}`,a=B.get(r);if(a!==void 0)return a;let s=C(t),u=o?`^${s}$`:`^${s}(?=/|$)`,c=new RegExp(u,n);return B.set(r,c),c}function D(){A.clear(),B.clear()}function R(t){let e=[...t.matchAll(w)].map(o=>o[2]);return t.endsWith("/*")&&e.push("*"),e}function d(t){return t.search(w)!==-1||t.endsWith("/*")}function M(t){return R(t)}function $(t,e){let o=e.split("?")[0]??"",n=R(t),r=o.match(y(t));if(!r)return{};let a={};return n.forEach((s,u)=>{let c=r[u+1];c!==void 0&&(a[s]=N(c))}),a}function _(t,e,o){if(e!=null)if(Array.isArray(e))e.forEach(n=>{n!=null&&o.append(t,String(n))});else if(typeof e=="object")for(let[n,r]of Object.entries(e))_(`${t}[${n}]`,r,o);else o.append(t,String(e))}function Y(t,e){let o=`${e}[`,n=[];for(let r of t.keys())(r===e||r.startsWith(o))&&n.push(r);for(let r of n)t.delete(r)}function k(t,e,o){let n=t.indexOf("#"),r=n===-1?t:t.slice(0,n),a=n===-1?"":t.slice(n+1),s=r.indexOf("?"),u=s===-1?r:r.slice(0,s),c=s===-1?"":r.slice(s+1),i=new URLSearchParams(c);if(e)for(let[f,m]of Object.entries(e))Object.prototype.hasOwnProperty.call(e,f)&&(Y(i,f),m!=null&&_(f,m,i));let l=u,p=i.toString();return p&&(l+="?"+p),o?l+="#"+encodeURIComponent(o):a&&(l+="#"+a),l}function tt(t,e,o){let n=e.replace(/\]/g,"").split(/\[/),r=t;for(let a=0;a<n.length-1;a++){let s=n[a];(!r[s]||typeof r[s]!="object"||Array.isArray(r[s]))&&(r[s]={}),r=r[s]}r[n[n.length-1]]=o}function F(t,e){let o=t.indexOf("#"),n=o===-1?t:t.slice(0,o),r=n.indexOf("?");if(r===-1)return{};let a=new URLSearchParams(n.slice(r+1)),s={};for(let u of new Set(a.keys())){let i=a.getAll(u).map(p=>{if(e?.coerceBooleans){let f=String(p).toLowerCase();if(f==="true"||f==="false")return f==="true"}return e?.coerceNumbers&&p.trim()!==""&&!isNaN(Number(p))?Number(p):p}),l=i.length>1?i:i[0]??"";tt(s,u,l)}return s}function g(t,e,o,n){let r=R(t),a=r.filter(c=>(e[c]===void 0||e[c]===null)&&!j(t,c)&&c!=="*"),s=r.reduce((c,i)=>{let l=e[i],p=l==null;if(i==="*"){if(p)return c.replace(/\/\*$/,"")||"/";if(typeof l!="string"&&typeof l!="number")throw new TypeError(`Splat parameter must be string or number, got ${typeof l}`);let m=n?.encode===!1?String(l):String(l).split("/").map(P=>encodeURIComponent(P)).join("/");return c.replace(/\/\*$/,`/${m}`)}let f=new RegExp(`(^|/):${x(i)}\\??(?![A-Za-z0-9_])`,"g");return c.replace(f,(m,P)=>{if(p)return m.endsWith("?")?"":`${P}:${i}`;let T=n?.encode===!1?String(l):encodeURIComponent(String(l));return`${P}${T}`})},t);if(a.length>0){if(n?.strict)throw new RangeError(`[route-forge] Missing required param(s) ${a.map(c=>`":${c}"`).join(", ")} in template "${t}".`);h(`[route-forge] Unresolved params in path "${s}". Check that all :param segments have matching keys.`)}let u=n?.locale?Q(n.locale,s):s;return k(u,o,n?.hash)}function I(t,e,o,n){return g(t,e,o,n)}function Q(...t){return"/"+t.map(n=>n.replace(/^\/+/,"").replace(/\/+$/,"")).filter(Boolean).join("/")}function v(t,e,o={}){let n=o.exact??!0,r=o.caseSensitive??!1,a=(t.split("?")[0]??"").replace(/\/+$/,"")||"/",s=e.replace(/\/+$/,"")||"/",u=r?s:s.toLowerCase(),c=r?a:a.toLowerCase();return(n?y(u,{caseSensitive:r}):O(u)).test(c)}function b(t,e="",o=new Set){if(o.has(t))return[];o.add(t);let n=[];for(let r of Object.keys(t)){let a=e?`${e}.${r}`:r,s=t[r];typeof s=="string"?n.push({key:a,path:s}):s instanceof String?n.push({key:a,path:s.valueOf()}):typeof s=="object"&&s!==null&&n.push(...b(s,a,o))}return n}function et(t){let e=t.split("."),o=e[e.length-1]??t;return(o==="ROOT"&&e.length>1?e[e.length-2]:o).replace(/_/g," ").toLowerCase().replace(/^\w/,r=>r.toUpperCase())}function W(t,e,o){let n=Array.isArray(t)?t:b(t),r=e.split("?")[0]??"",a=o?.labelResolver??et,s=o?.labels??{},u=[];for(let i of n){let l=y(i.path),p=r.match(l);if(p){let P=$(i.path,p[0]),T=d(i.path)?g(i.path,P):i.path;u.push({key:i.key,resolvedPath:T,template:i.path,isCurrent:!0});continue}let f=O(i.path),m=r.match(f);if(m){let P=m[0],T=$(i.path,P),K=d(i.path)?g(i.path,T):i.path;u.push({key:i.key,resolvedPath:K,template:i.path,isCurrent:!1})}}let c=i=>i.split("/").filter(Boolean).length;return u.sort((i,l)=>c(i.template)-c(l.template)),u.map(i=>({key:i.key,label:s[i.key]??a(i.key),path:i.resolvedPath,isCurrent:i.isCurrent}))}var rt=t=>typeof t=="object"&&t!==null&&!Array.isArray(t)&&Object.getPrototypeOf(t)===Object.prototype;function nt(t,e){t.startsWith("/")||h(`[route-forge] Route "${e}" does not start with "/": "${t}".`),t.includes("*")&&!t.endsWith("/*")&&h(`[route-forge] Route "${e}" uses "*" outside a trailing "/*" splat; only a trailing splat is supported: "${t}".`)}function ot(t){let e=new Map,o=new Set,n=b(t);for(let r of n){let a=e.get(r.path);a===void 0?e.set(r.path,r.key):o.has(r.path)||(o.add(r.path),h(`[route-forge] Duplicate route path "${r.path}" for "${a}" and "${r.key}". Only one of them will be reachable.`))}for(let r=0;r<n.length;r++){let a=n[r];if(d(a.path))for(let s=r+1;s<n.length;s++){let u=n[s];if(!d(u.path)&&v(u.path,a.path,{exact:!0})){let c=`${a.key}->${u.key}`;o.has(c)||(o.add(c),h(`[route-forge] Route "${u.key}" ("${u.path}") is shadowed by dynamic route "${a.key}" ("${a.path}"). Place static routes before dynamic parameters in route trees.`))}}}}function st(){Object.prototype.hasOwnProperty.call(String.prototype,"build")||(Object.defineProperty(String.prototype,"build",{value:function(e,o,n){let r=this.valueOf();return d(r)?g(r,e??{},o,n):g(r,{},e,o)},writable:!1,enumerable:!1,configurable:!0}),Object.defineProperty(String.prototype,"buildRelative",{value:function(e,o,n){return this.build(e,o,n).replace(/^\/+/,"")||"."},writable:!1,enumerable:!1,configurable:!0}),Object.defineProperty(String.prototype,"paramNames",{get(){return R(this.valueOf())},enumerable:!1,configurable:!0}))}st();function L(t){let e={};for(let o in t){if(!Object.prototype.hasOwnProperty.call(t,o))continue;let n=t[o];typeof n=="string"?(nt(n,o),e[o]=d(n)?n:n):rt(n)&&(e[o]=L(n))}return e}function U(t){let e=L(t);return E()||ot(e),e}0&&(module.exports={appendQuery,build,buildPath,clearPathCache,defineRoutes,devWarn,extractParamNames,extractParamsFromPath,extractQueryFromPath,flattenRoutes,getBreadcrumbs,getParamNames,isActivePath,isDynamic,joinPaths,matchPath});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core/environment.ts","../src/core/pattern.ts","../src/core/params.ts","../src/core/query.ts","../src/core/build.ts","../src/core/match.ts","../src/core/routes.ts","../src/core/defineRoutes.ts"],"sourcesContent":["// Core\nexport { defineRoutes } from \"./core/defineRoutes\";\nexport type { StaticRoute, DynamicRoute } from \"./core/defineRoutes\";\n\n// Path building\nexport { buildPath, build, joinPaths } from \"./core/build\";\nexport {\n extractParamNames,\n extractParamsFromPath,\n getParamNames,\n isDynamic,\n} from \"./core/params\";\n\n// Matching\nexport { isActivePath } from \"./core/match\";\nexport { matchPath } from \"./core/pattern\";\n\n// Query strings & hashes\nexport { appendQuery, extractQueryFromPath } from \"./core/query\";\n\n// Route trees\nexport { flattenRoutes, getBreadcrumbs } from \"./core/routes\";\n\n// Environment\nexport { devWarn } from \"./core/environment\";\nexport { clearPathCache } from \"./core/pattern\";\n\n// Types\nexport type {\n RoutePath,\n RouteBuilder,\n RouteParam,\n RouteParams,\n QueryParams,\n RouteLeaf,\n RouteMap,\n RouteTree,\n ExtractParams,\n PathParams,\n BuildPathOptions,\n FlatRoute,\n BreadcrumbItem,\n BreadcrumbOptions,\n MatchPathOptions,\n} from \"./types\";\n","/**\n * Returns `true` when running in a production bundle (suppresses dev warnings).\n */\nexport function isProduction(): boolean {\n const runtimeProcess = (\n globalThis as typeof globalThis & {\n process?: { env?: Record<string, string | undefined> };\n }\n ).process;\n return runtimeProcess?.env?.NODE_ENV === \"production\";\n}\n\n/**\n * Emits a `console.warn` in non-production environments.\n * Shared by the core utilities and `defineRoutes()` so the production check\n * lives in one place.\n */\nexport function devWarn(message: string): void {\n if (!isProduction()) {\n console.warn(message);\n }\n}\n","/** Returns a fresh RegExp each call — avoids shared `lastIndex` state on /g patterns. */\nconst ESCAPE_RE = () => /[.*+?^${}()|[\\]\\\\]/g;\n\n/**\n * Matches a `:param` token that starts a URL segment.\n *\n * A param is only recognized when its `:` sits at the very start of the\n * template or is immediately preceded by `/`, so a literal colon inside a\n * segment (e.g. `/users/foo:bar`) is not treated as a param. Param names are\n * restricted to `[A-Za-z0-9_]` (matching React Router), so any static suffix\n * (e.g. `.json` in `/files/:name.json`) stays a literal. The optional `?`\n * marker (`:param?`) is captured separately so it can be handled as a\n * whole-segment modifier.\n */\nexport const PARAM_SEGMENT_RE = () => /(^|\\/):([A-Za-z0-9_]+)(\\?)?/g;\n\nexport function escapeRegex(value: string): string {\n return value.replace(ESCAPE_RE(), \"\\\\$&\");\n}\n\n/**\n * Convert a route template into an unanchored `RegExp` source string.\n *\n * - Required `:name` segments become a capturing group `([^/]+)`.\n * - Optional `:name?` segments become `(?:([^/]+))?` — the whole segment is\n * optional, matching React Router semantics.\n * - A trailing splat (`/*`) becomes `(?:/(.*))?`, capturing the remainder of\n * the path (including slashes) or nothing at all.\n * - Everything else is regex-escaped literally.\n */\nfunction createTemplatePattern(template: string): string {\n const splat = template.endsWith(\"/*\");\n const base = splat ? template.slice(0, -2) : template;\n let pattern = \"\";\n let cursor = 0;\n\n for (const match of base.matchAll(PARAM_SEGMENT_RE())) {\n const start = match.index ?? 0;\n const token = match[0];\n\n pattern += escapeRegex(base.slice(cursor, start));\n\n const [, boundary = \"\", , optional] = match;\n pattern += optional\n ? `(?:${escapeRegex(boundary)}([^/]+))?`\n : `${escapeRegex(boundary)}([^/]+)`;\n\n cursor = start + (token?.length ?? 0);\n }\n\n return (\n pattern + escapeRegex(base.slice(cursor)) + (splat ? \"(?:/(.*))?\" : \"\")\n );\n}\n\n/** Returns `true` when `name` is an optional (`:name?`) param in `template`. */\nexport function isOptionalParam(template: string, name: string): boolean {\n return new RegExp(`:${escapeRegex(name)}\\\\?(?![A-Za-z0-9_])`).test(template);\n}\n\n/**\n * Returns a `RegExp` that matches `template` as a path prefix, honoring\n * segment boundaries (`/users` matches `/users/42` but not `/usersettings`).\n *\n * The root template `/` is a prefix of every path.\n *\n * Compiled patterns are cached per template: the resulting `RegExp` has no\n * `/g` flag, so repeated `.test()`/`.exec()` calls are side-effect free and\n * sharing instances across callers is safe.\n */\nconst PREFIX_CACHE = new Map<string, RegExp>();\nconst ROOT_PREFIX_RE = /^/;\n\nexport function matchPrefix(template: string): RegExp {\n if (template === \"/\") return ROOT_PREFIX_RE;\n const cached = PREFIX_CACHE.get(template);\n if (cached !== undefined) return cached;\n const re = new RegExp(`^${createTemplatePattern(template)}(?=/|$)`);\n PREFIX_CACHE.set(template, re);\n return re;\n}\n\n/** Decode a URL-encoded param value, falling back to the raw value on error. */\nexport function safeDecode(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n/**\n * Convert a route template string into an anchored `RegExp` for matching paths.\n *\n * Each `:param` segment becomes a capturing group so the returned regex\n * can be used with `.test()` or `.exec()`.\n *\n * Query strings are **not** stripped — callers should split on `\"?\"` first\n * (see {@link isActivePath} or {@link extractParamsFromPath} for higher-level\n * helpers that handle this automatically).\n *\n * @param template - A route template, e.g. `\"/users/:id\"` or `\"/users/:id/posts/:postId\"`.\n * @returns A `RegExp` anchored with `^` and `$` that captures param values.\n *\n * @example\n * ```ts\n * const re = matchPath(\"/users/:id\");\n * re.test(\"/users/42\"); // true\n * re.exec(\"/users/42\"); // [\"/users/42\", \"42\"]\n * re.test(\"/users/42/posts\"); // false (exact match)\n * re.test(\"/users/42?page=1\"); // true (query is part of captured value)\n * ```\n *\n * Compiled patterns are cached per template (the regex has no `/g` flag, so\n * sharing instances across callers is safe).\n */\nconst PATH_CACHE = new Map<string, RegExp>();\n\nexport function matchPath(\n template: string,\n options?: { end?: boolean; caseSensitive?: boolean },\n): RegExp {\n const end = options?.end ?? true;\n const flags = options?.caseSensitive ? \"\" : \"i\";\n const cacheKey = `${template}:${end}:${flags}`;\n\n const cached = PATH_CACHE.get(cacheKey);\n if (cached !== undefined) return cached;\n\n const basePattern = createTemplatePattern(template);\n const pattern = end ? `^${basePattern}$` : `^${basePattern}(?=/|$)`;\n const re = new RegExp(pattern, flags);\n PATH_CACHE.set(cacheKey, re);\n return re;\n}\n\n/**\n * Clears internal regex cache maps (PREFIX_CACHE and PATH_CACHE).\n * Useful in test suites to prevent cached patterns from leaking across test cases.\n */\nexport function clearPathCache(): void {\n PREFIX_CACHE.clear();\n PATH_CACHE.clear();\n}\n","import { PARAM_SEGMENT_RE, matchPath, safeDecode } from \"./pattern\";\n\n/**\n * Extract the `:param` names from a route template.\n *\n * A param is only recognized at the start of a segment (see\n * {@link PARAM_SEGMENT_RE}), so literal colons (`/users/foo:bar`) and static\n * suffixes after a param (`/files/:name.json`) are not treated as params.\n * A trailing splat (`/*`) is reported as the `\"*\"` name.\n */\nexport function extractParamNames(template: string): string[] {\n const names = [...template.matchAll(PARAM_SEGMENT_RE())].map(\n (match) => match[2] as string,\n );\n if (template.endsWith(\"/*\")) names.push(\"*\");\n return names;\n}\n\n/** Returns `true` when the template contains a `:param` or trailing `/*` splat. */\nexport function isDynamic(path: string): boolean {\n return PARAM_SEGMENT_RE().test(path) || path.endsWith(\"/*\");\n}\n\n/**\n * Alias of {@link extractParamNames} — kept for backwards compatibility.\n */\nexport function getParamNames(template: string): string[] {\n return extractParamNames(template);\n}\n\n/**\n * Extract the param values matched by `template` from a resolved path.\n *\n * The query string is stripped before matching, so `/users/42?tab=profile`\n * still yields `{ id: \"42\" }`. Values are URL-decoded back to their original\n * form; a non-matching path yields an empty object.\n */\nexport function extractParamsFromPath(\n template: string,\n resolvedPath: string,\n): Record<string, string> {\n const pathWithoutSearch = resolvedPath.split(\"?\")[0] ?? \"\";\n const paramNames = extractParamNames(template);\n const match = pathWithoutSearch.match(matchPath(template));\n\n if (!match) return {};\n\n const result: Record<string, string> = {};\n paramNames.forEach((name, index) => {\n const raw = match[index + 1];\n if (raw !== undefined) result[name] = safeDecode(raw);\n });\n return result;\n}\n","import type { QueryParams } from \"../types\";\n\n/**\n * Append a query string and/or hash fragment to a path that may already\n * contain a query or hash.\n *\n * - Existing query pairs are preserved; new ones are joined with `&`.\n * - The query string is always inserted before any hash fragment, so an\n * existing `#section` on `path` is kept unless a new `hash` is given.\n *\n * @example\n * ```ts\n * appendQuery(\"/users?tab=list\", { page: 2 }); // → \"/users?tab=list&page=2\"\n * appendQuery(\"/users#top\", { tab: \"list\" }); // → \"/users?tab=list#top\"\n * ```\n */\nexport function appendQuery(\n path: string,\n query?: QueryParams,\n hash?: string,\n): string {\n const hashIdx = path.indexOf(\"#\");\n const base = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const existingHash = hashIdx === -1 ? \"\" : path.slice(hashIdx + 1);\n\n const searchParams = new URLSearchParams();\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n if (Array.isArray(value)) {\n value.forEach((v) => {\n if (v !== undefined && v !== null)\n searchParams.append(key, String(v));\n });\n } else {\n searchParams.append(key, String(value));\n }\n }\n }\n\n let result = base;\n const queryString = searchParams.toString();\n if (queryString) {\n result += (result.includes(\"?\") ? \"&\" : \"?\") + queryString;\n }\n\n if (hash) {\n result += \"#\" + hash;\n } else if (existingHash) {\n result += \"#\" + existingHash;\n }\n\n return result;\n}\n\n/**\n * Parse the query string out of a path (or bare query string) into a plain\n * object. Repeated keys become arrays; a single key is a scalar string.\n *\n * With `{ coerceBooleans: true }`, the strings `\"true\"`/`\"false\"` are\n * converted to actual booleans.\n *\n * @example\n * ```ts\n * extractQueryFromPath(\"/users/42?tab=profile&tag=a&tag=b\");\n * // → { tab: \"profile\", tag: [\"a\", \"b\"] }\n * extractQueryFromPath(\"/search?active=true\", { coerceBooleans: true });\n * // → { active: true }\n * ```\n */\nexport function extractQueryFromPath(\n path: string,\n options?: { coerceBooleans?: boolean; coerceNumbers?: boolean },\n): QueryParams {\n const hashIdx = path.indexOf(\"#\");\n const noHash = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const queryIdx = noHash.indexOf(\"?\");\n if (queryIdx === -1) return {};\n\n const params = new URLSearchParams(noHash.slice(queryIdx + 1));\n const result: QueryParams = {};\n\n for (const key of new Set(params.keys())) {\n const values = params.getAll(key);\n const parsed = values.map((v) => {\n if (options?.coerceBooleans && (v === \"true\" || v === \"false\")) {\n return v === \"true\";\n }\n if (options?.coerceNumbers && v.trim() !== \"\" && !isNaN(Number(v))) {\n return Number(v);\n }\n return v;\n });\n result[key] = parsed.length > 1 ? parsed : (parsed[0] ?? \"\");\n }\n\n return result;\n}\n","import { escapeRegex, isOptionalParam } from \"./pattern\";\nimport { extractParamNames } from \"./params\";\nimport { appendQuery } from \"./query\";\nimport { devWarn } from \"./environment\";\nimport type { BuildPathOptions, QueryParams, RouteParams } from \"../types\";\n\n/**\n * Resolve a route template against a params object, appending an optional\n * query string and hash fragment.\n *\n * Missing required params are left as their `:name` placeholder and a\n * `console.warn` is emitted — pass `{ strict: true }` to throw a `RangeError`\n * instead. Optional (`:param?`) segments are dropped entirely when missing;\n * a missing splat just drops the trailing `/*`. Values are URL-encoded by\n * default (`{ encode: false }` opts out).\n */\nexport function buildPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n const paramNames = extractParamNames(template);\n const unresolved = paramNames.filter(\n (name) =>\n (params[name] === undefined || params[name] === null) &&\n !isOptionalParam(template, name) &&\n // A missing splat simply drops the `/*` suffix (matching React Router,\n // where `/files/*` also matches `/files`).\n name !== \"*\",\n );\n\n const resolved = paramNames.reduce((path, name) => {\n const value = params[name];\n const missing = value === undefined || value === null;\n\n if (name === \"*\") {\n if (missing) return path.replace(/\\/\\*$/, \"\") || \"/\";\n // Splat values are path-like: preserve `/` separators but still\n // encode characters that could break the URL (`?`, `#`, spaces, …).\n const encoded =\n options?.encode === false\n ? String(value)\n : String(value)\n .split(\"/\")\n .map((segment) => encodeURIComponent(segment))\n .join(\"/\");\n return path.replace(/\\/\\*$/, `/${encoded}`);\n }\n\n const re = new RegExp(\n `(^|/):${escapeRegex(name)}\\\\??(?![A-Za-z0-9_])`,\n \"g\",\n );\n\n return path.replace(re, (match, boundary) => {\n if (missing) {\n // Optional segment: drop the whole `/segment`. Required: keep the\n // `:name` placeholder so strict mode can report it.\n return match.endsWith(\"?\") ? \"\" : `${boundary}:${name}`;\n }\n\n const encoded =\n options?.encode === false\n ? String(value)\n : encodeURIComponent(String(value));\n return `${boundary}${encoded}`;\n });\n }, template);\n\n if (unresolved.length > 0) {\n if (options?.strict) {\n throw new RangeError(\n `[route-forge] Missing required param(s) ${unresolved.map((p) => `\":${p}\"`).join(\", \")} in template \"${template}\".`,\n );\n }\n\n devWarn(\n `[route-forge] Unresolved params in path \"${resolved}\". ` +\n `Check that all :param segments have matching keys.`,\n );\n }\n\n return appendQuery(resolved, query, options?.hash);\n}\n\n/**\n * Standalone alias of {@link buildPath} — resolves a template against params.\n */\nexport function build(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n return buildPath(template, params, query, options);\n}\n\n/**\n * Join URL path segments into a single slash-prefixed path, normalising\n * duplicate slashes.\n *\n * @example\n * joinPaths(\"/api/\", \"/v1/\", \"users\"); // → \"/api/v1/users\"\n */\nexport function joinPaths(...segments: string[]): string {\n const processed = segments.map((segment) =>\n segment.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\"),\n );\n const filtered = processed.filter(Boolean);\n return \"/\" + filtered.join(\"/\");\n}\n","import { matchPath, matchPrefix } from \"./pattern\";\n\n/**\n * Test whether `currentPath` matches `template`, mirroring React Router's\n * `NavLink` matching semantics:\n *\n * - Case-insensitive by default (pass `caseSensitive: true` to opt out).\n * - Trailing slashes are tolerated (`/users/` matches `/users`).\n * - `exact: true` (the default) requires a full match; `exact: false`\n * matches any path that starts with the template.\n */\nexport function isActivePath(\n currentPath: string,\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {},\n): boolean {\n // NOTE: default each property individually rather than relying on a\n // default *object* for `options` — JS default parameters only apply when\n // the whole argument is omitted, so `isActivePath(a, b, { caseSensitive: true })`\n // would otherwise silently lose the documented `exact: true` default.\n const exact = options.exact ?? true;\n const caseSensitive = options.caseSensitive ?? false;\n\n const pathname = (currentPath.split(\"?\")[0] ?? \"\").replace(/\\/+$/, \"\") || \"/\";\n const normalizedTemplate = template.replace(/\\/+$/, \"\") || \"/\";\n const target = caseSensitive\n ? normalizedTemplate\n : normalizedTemplate.toLowerCase();\n const candidate = caseSensitive ? pathname : pathname.toLowerCase();\n const regex = exact\n ? matchPath(target, { caseSensitive })\n : matchPrefix(target);\n\n return regex.test(candidate);\n}\n","import { matchPath, matchPrefix } from \"./pattern\";\nimport { extractParamsFromPath, isDynamic } from \"./params\";\nimport { buildPath } from \"./build\";\nimport type {\n BreadcrumbItem,\n BreadcrumbOptions,\n FlatRoute,\n} from \"../types\";\n\n/**\n * Walk a `defineRoutes` output tree and return a flat array of\n * `{ key, path }` entries where `key` is the dot-joined key path from\n * the root (e.g. `\"SERVICES.BCC.EDIT\"`) and `path` is the raw template\n * string (e.g. `\"/services/bcc/edit/:id\"`).\n *\n * Useful for:\n * - Generating sitemaps from a single source of truth.\n * - Detecting duplicate path strings across branches at startup:\n *\n * @example\n * const flat = flattenRoutes(PATHS);\n * const paths = flat.map((r) => r.path);\n * const dupes = paths.filter((p, i) => paths.indexOf(p) !== i);\n * if (dupes.length) console.warn('Duplicate paths:', dupes);\n */\nexport function flattenRoutes(\n routes: Record<string, unknown>,\n prefix = \"\",\n): FlatRoute[] {\n const entries: FlatRoute[] = [];\n\n for (const key of Object.keys(routes)) {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n const value = routes[key];\n\n if (typeof value === \"string\") {\n // Plain static string leaf.\n entries.push({ key: fullKey, path: value });\n } else if (value instanceof String) {\n // String-object leaf (wrapped dynamic path from defineRoutes).\n entries.push({ key: fullKey, path: value.valueOf() });\n } else if (typeof value === \"object\" && value !== null) {\n // Nested route group — recurse.\n entries.push(...flattenRoutes(value as Record<string, unknown>, fullKey));\n }\n // Anything else (functions, numbers, …) is silently skipped.\n }\n\n return entries;\n}\n\nfunction deriveBreadcrumbLabel(key: string): string {\n const parts = key.split(\".\");\n const last = parts[parts.length - 1] ?? key;\n // Use the parent segment when the leaf is the conventional \"ROOT\" key,\n // so USERS.ROOT → \"Users\" rather than \"Root\".\n const raw =\n last === \"ROOT\" && parts.length > 1 ? parts[parts.length - 2]! : last;\n return raw\n .replace(/_/g, \" \")\n .toLowerCase()\n .replace(/^\\w/, (c) => c.toUpperCase());\n}\n\n/**\n * Build a breadcrumb trail from a route tree or a flat route list.\n *\n * For a given `currentPath`, it walks the route tree and returns every route\n * that is an ancestor of (or an exact match to) the current page. Ancestors\n * are matched by prefix (e.g. `/users` matches `/users/edit/42/posts`).\n *\n * Dynamic params in ancestor paths are automatically resolved from the\n * matched portion of the URL.\n *\n * @param routes - A route tree (output of `defineRoutes`) or a pre-flattened\n * array from `flattenRoutes()`.\n * @param currentPath - The current URL (with or without query string).\n * @param options - Optional label resolver.\n * @returns An array of {@link BreadcrumbItem} ordered by depth\n * (most general first), where the last item is the current page.\n *\n * @example\n * ```ts\n * const PATHS = defineRoutes({\n * HOME: \"/\",\n * USERS: { ROOT: \"/users\", EDIT: \"/users/edit/:id\" },\n * } as const);\n *\n * getBreadcrumbs(PATHS, \"/users/edit/42\");\n * // → [\n * // { key: \"HOME\", label: \"Home\", path: \"/\", isCurrent: false },\n * // { key: \"USERS.ROOT\", label: \"Users\", path: \"/users\", isCurrent: false },\n * // { key: \"USERS.EDIT\", label: \"Edit\", path: \"/users/edit/42\", isCurrent: true },\n * // ]\n * ```\n */\nexport function getBreadcrumbs(\n routes: Record<string, unknown> | FlatRoute[],\n currentPath: string,\n options?: BreadcrumbOptions,\n): BreadcrumbItem[] {\n const flat = Array.isArray(routes) ? routes : flattenRoutes(routes);\n const pathname = currentPath.split(\"?\")[0] ?? \"\";\n const labelFn = options?.labelResolver ?? deriveBreadcrumbLabel;\n const labels = options?.labels ?? {};\n\n const items: Array<{\n key: string;\n resolvedPath: string;\n template: string;\n isCurrent: boolean;\n }> = [];\n\n for (const route of flat) {\n const exactRe = matchPath(route.path);\n const exactMatch = pathname.match(exactRe);\n\n if (exactMatch) {\n const params = extractParamsFromPath(route.path, exactMatch[0]);\n const resolved = isDynamic(route.path)\n ? buildPath(route.path, params)\n : route.path;\n items.push({\n key: route.key,\n resolvedPath: resolved,\n template: route.path,\n isCurrent: true,\n });\n continue;\n }\n\n const prefixRe = matchPrefix(route.path);\n const prefixMatch = pathname.match(prefixRe);\n\n if (prefixMatch) {\n const matchedPortion = prefixMatch[0];\n const params = extractParamsFromPath(route.path, matchedPortion);\n const resolved = isDynamic(route.path)\n ? buildPath(route.path, params)\n : route.path;\n items.push({\n key: route.key,\n resolvedPath: resolved,\n template: route.path,\n isCurrent: false,\n });\n }\n }\n\n // Sort by path depth (segment count), not raw string length — a shallow\n // route with a long param name (e.g. \"/shop/:reallyLongSlugName\") must\n // still sort before a deeper route with short segments (e.g.\n // \"/shop/widgets/details\"), even though the latter has fewer characters.\n const segmentCount = (t: string) => t.split(\"/\").filter(Boolean).length;\n items.sort((a, b) => segmentCount(a.template) - segmentCount(b.template));\n\n return items.map((item) => ({\n key: item.key,\n label: labels[item.key] ?? labelFn(item.key),\n path: item.resolvedPath,\n isCurrent: item.isCurrent,\n }));\n}\n","import { devWarn, isProduction } from \"./environment\";\nimport { buildPath } from \"./build\";\nimport { extractParamNames, isDynamic } from \"./params\";\nimport { isActivePath } from \"./match\";\nimport { flattenRoutes } from \"./routes\";\nimport type {\n BuildPathOptions,\n ExtractParams,\n PathParams,\n QueryParams,\n RoutePath,\n RouteTree,\n} from \"../types\";\n\n// Re-export so consumers that import from 'core/defineRoutes' get the full surface\nexport { buildPath } from \"./build\";\nexport { extractParamNames, isDynamic } from \"./params\";\n\n/**\n * A dynamic route is a template string with a `:param` or trailing `/*` splat,\n * augmented with a `.build()` helper and a `.paramNames` array.\n *\n * Exported so consumers (e.g. the hooks entry) can type against it.\n */\nexport type StaticRoute<T extends string> = T & {\n build(query?: QueryParams, options?: BuildPathOptions): RoutePath;\n};\n\nexport type DynamicRoute<T extends string> = T extends\n | `${string}:${string}`\n | `${string}/*`\n ? T & {\n build(\n params: PathParams<T>,\n query?: QueryParams,\n options?: BuildPathOptions,\n ): RoutePath;\n paramNames: Array<ExtractParams<T>>;\n }\n : StaticRoute<T>;\n\ntype ResolvedRoutes<T extends RouteTree> = {\n [K in keyof T]: T[K] extends RouteTree\n ? ResolvedRoutes<T[K]>\n : T[K] extends string\n ? DynamicRoute<T[K]>\n : never;\n};\n\nconst isRouteGroup = (value: unknown): value is RouteTree =>\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype;\n\n/**\n * Validate a single route template and warn (dev-only) about common mistakes:\n * missing leading `/` and non-trailing `*` splats.\n */\nfunction validateTemplate(template: string, key: string): void {\n if (!template.startsWith(\"/\")) {\n devWarn(\n `[route-forge] Route \"${key}\" does not start with \"/\": \"${template}\".`,\n );\n }\n\n if (template.includes(\"*\") && !template.endsWith(\"/*\")) {\n devWarn(\n `[route-forge] Route \"${key}\" uses \"*\" outside a trailing \"/*\" splat; only a trailing splat is supported: \"${template}\".`,\n );\n }\n}\n\n/**\n * Warn (dev-only) when two routes resolve to the same path template or shadow each other.\n */\nfunction detectDuplicatePaths(routes: Record<string, unknown>): void {\n const seen = new Map<string, string>();\n const warned = new Set<string>();\n const flat = flattenRoutes(routes);\n\n for (const route of flat) {\n const existing = seen.get(route.path);\n if (existing === undefined) {\n seen.set(route.path, route.key);\n } else if (!warned.has(route.path)) {\n warned.add(route.path);\n devWarn(\n `[route-forge] Duplicate route path \"${route.path}\" for \"${existing}\" and \"${route.key}\". ` +\n `Only one of them will be reachable.`,\n );\n }\n }\n\n // Shadowing check: warn when a static path matches a dynamic path defined before it\n for (let i = 0; i < flat.length; i++) {\n const r1 = flat[i]!;\n if (!isDynamic(r1.path)) continue;\n\n for (let j = i + 1; j < flat.length; j++) {\n const r2 = flat[j]!;\n if (isDynamic(r2.path)) continue;\n\n if (isActivePath(r2.path, r1.path, { exact: true })) {\n const pairKey = `${r1.key}->${r2.key}`;\n if (!warned.has(pairKey)) {\n warned.add(pairKey);\n devWarn(\n `[route-forge] Route \"${r2.key}\" (\"${r2.path}\") is shadowed by dynamic route \"${r1.key}\" (\"${r1.path}\"). Place static routes before dynamic parameters in route trees.`,\n );\n }\n }\n }\n }\n}\n\n/**\n * Attach `.build()` (and, for dynamic templates, `.paramNames`) to\n * `String.prototype` exactly once, so route values can stay **genuine\n * primitive strings** instead of `new String(template)` objects.\n *\n * Route values used to be wrapped in `new String(template)` so `.build()`\n * could be attached as an own property. That silently broke direct use with\n * React Router's `<Link to={...}>` (and anything else that branches on\n * `typeof to === \"string\"`, e.g. React Router's internal `resolveTo()`):\n * `typeof` on a `String` object is `\"object\"`, not `\"string\"`, so React\n * Router treated the route value as a `Partial<Path>` and spread it\n * (`{ ...routeValue }`) instead of parsing it as a path — producing a\n * `pathname`-less destination and a broken/no-op navigation. `.build()`\n * happened to \"fix\" it only because it returns a plain string.\n *\n * Putting `.build()`/`.paramNames` on the prototype instead means every\n * primitive string route value can still call `.build()` / read\n * `.paramNames` (resolved lazily from `this`, the template text itself),\n * while `typeof route === \"string\"` stays true — so route values now work\n * directly anywhere a plain path string is expected, `<Link to={route}>`\n * included, with no `.build()` call required.\n */\nfunction installBuildProtocol(): void {\n if (Object.prototype.hasOwnProperty.call(String.prototype, \"build\")) {\n return;\n }\n\n Object.defineProperty(String.prototype, \"build\", {\n value: function build(\n this: string,\n paramsOrQuery?: PathParams<string> | QueryParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n ): RoutePath {\n const template = this.valueOf();\n\n // Dynamic templates take (params, query?, options?); static templates\n // take (query?, options?) — disambiguated from the template itself,\n // since that's all the prototype method has to go on.\n return (\n isDynamic(template)\n ? buildPath(\n template,\n (paramsOrQuery ?? {}) as PathParams<string>,\n query,\n options,\n )\n : buildPath(\n template,\n {},\n paramsOrQuery as QueryParams,\n query as unknown as BuildPathOptions,\n )\n ) as RoutePath;\n },\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n Object.defineProperty(String.prototype, \"paramNames\", {\n get(this: string): string[] {\n return extractParamNames(this.valueOf());\n },\n enumerable: false,\n configurable: true,\n });\n}\n\ninstallBuildProtocol();\n\nfunction wrapStaticPath<T extends string>(template: T): StaticRoute<T> {\n // A genuine primitive string — `.build()` comes from String.prototype\n // (see installBuildProtocol above), so `typeof` stays \"string\".\n return template as StaticRoute<T>;\n}\n\nfunction wrapDynamicPath<T extends string>(template: T): DynamicRoute<T> {\n // Same here: primitive string, `.build()` and `.paramNames` are resolved\n // lazily from String.prototype based on the template text itself.\n return template as unknown as DynamicRoute<T>;\n}\n\nfunction processRouteMap<T extends RouteTree>(routes: T): ResolvedRoutes<T> {\n const result = {} as ResolvedRoutes<T>;\n\n for (const key in routes) {\n if (!Object.prototype.hasOwnProperty.call(routes, key)) continue;\n\n const value = routes[key];\n\n if (typeof value === \"string\") {\n validateTemplate(value, key);\n result[key] = (\n isDynamic(value) ? wrapDynamicPath(value) : wrapStaticPath(value)\n ) as ResolvedRoutes<T>[typeof key];\n } else if (isRouteGroup(value)) {\n result[key] = processRouteMap(\n value as RouteTree,\n ) as unknown as ResolvedRoutes<T>[typeof key];\n }\n }\n\n return result;\n}\n\nexport function defineRoutes<T extends RouteTree>(\n routes: T,\n): ResolvedRoutes<T> {\n const result = processRouteMap(routes);\n // detectDuplicatePaths only ever produces console.warn output (suppressed\n // in production by devWarn), so skip the O(n^2) walk entirely in\n // production rather than computing it and discarding the result.\n if (!isProduction()) {\n detectDuplicatePaths(result as unknown as Record<string, unknown>);\n }\n return result;\n}\n"],"mappings":"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","names":["index_exports","__export","appendQuery","build","buildPath","clearPathCache","defineRoutes","devWarn","extractParamNames","extractParamsFromPath","extractQueryFromPath","flattenRoutes","getBreadcrumbs","getParamNames","isActivePath","isDynamic","joinPaths","matchPath","__toCommonJS","isProduction","devWarn","message","ESCAPE_RE","PARAM_SEGMENT_RE","escapeRegex","value","createTemplatePattern","template","splat","base","pattern","cursor","match","start","token","boundary","optional","isOptionalParam","name","PREFIX_CACHE","ROOT_PREFIX_RE","matchPrefix","cached","re","safeDecode","PATH_CACHE","matchPath","options","end","flags","cacheKey","basePattern","clearPathCache","extractParamNames","template","names","PARAM_SEGMENT_RE","match","isDynamic","path","getParamNames","extractParamsFromPath","resolvedPath","pathWithoutSearch","paramNames","matchPath","result","name","index","raw","safeDecode","appendQuery","path","query","hash","hashIdx","base","existingHash","searchParams","key","value","v","result","queryString","extractQueryFromPath","options","noHash","queryIdx","params","parsed","buildPath","template","params","query","options","paramNames","extractParamNames","unresolved","name","isOptionalParam","resolved","path","value","missing","encoded","segment","re","escapeRegex","match","boundary","p","devWarn","appendQuery","build","joinPaths","segments","isActivePath","currentPath","template","options","exact","caseSensitive","pathname","normalizedTemplate","target","candidate","matchPath","matchPrefix","flattenRoutes","routes","prefix","entries","key","fullKey","value","deriveBreadcrumbLabel","parts","last","c","getBreadcrumbs","currentPath","options","flat","pathname","labelFn","labels","items","route","exactRe","matchPath","exactMatch","params","extractParamsFromPath","resolved","isDynamic","buildPath","prefixRe","matchPrefix","prefixMatch","matchedPortion","segmentCount","t","a","b","item","isRouteGroup","value","validateTemplate","template","key","devWarn","detectDuplicatePaths","routes","seen","warned","flat","flattenRoutes","route","existing","i","r1","isDynamic","j","r2","isActivePath","pairKey","installBuildProtocol","paramsOrQuery","query","options","buildPath","extractParamNames","processRouteMap","routes","result","key","value","validateTemplate","isDynamic","isRouteGroup","defineRoutes","isProduction","detectDuplicatePaths"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/core/environment.ts","../src/core/pattern.ts","../src/core/params.ts","../src/core/query.ts","../src/core/build.ts","../src/core/match.ts","../src/core/routes.ts","../src/core/defineRoutes.ts"],"sourcesContent":["// Core\nexport { defineRoutes } from \"./core/defineRoutes\";\nexport type { StaticRoute, DynamicRoute } from \"./core/defineRoutes\";\n\n// Path building\nexport { buildPath, build, joinPaths } from \"./core/build\";\nexport {\n extractParamNames,\n extractParamsFromPath,\n getParamNames,\n isDynamic,\n} from \"./core/params\";\n\n// Matching\nexport { isActivePath } from \"./core/match\";\nexport { matchPath } from \"./core/pattern\";\n\n// Query strings & hashes\nexport { appendQuery, extractQueryFromPath } from \"./core/query\";\n\n// Route trees\nexport { flattenRoutes, getBreadcrumbs } from \"./core/routes\";\n\n// Environment\nexport { devWarn } from \"./core/environment\";\nexport { clearPathCache } from \"./core/pattern\";\n\n// Types\nexport type {\n RoutePath,\n RouteBuilder,\n RouteParam,\n RouteParams,\n QueryParams,\n RouteLeaf,\n RouteMap,\n RouteTree,\n ExtractParams,\n PathParams,\n BuildPathOptions,\n FlatRoute,\n BreadcrumbItem,\n BreadcrumbOptions,\n MatchPathOptions,\n} from \"./types\";\n","/**\n * Returns `true` when running in a production bundle (suppresses dev warnings).\n */\nexport function isProduction(): boolean {\n const runtimeProcess = (\n globalThis as typeof globalThis & {\n process?: { env?: Record<string, string | undefined> };\n }\n ).process;\n return runtimeProcess?.env?.NODE_ENV === \"production\";\n}\n\n/**\n * Emits a `console.warn` in non-production environments.\n * Shared by the core utilities and `defineRoutes()` so the production check\n * lives in one place.\n */\nexport function devWarn(message: string): void {\n if (!isProduction()) {\n console.warn(message);\n }\n}\n","/** Global RegExp instances — safe since String.prototype.replace/matchAll don't bleed lastIndex. */\nconst ESCAPE_RE = /[.*+?^${}()|[\\]\\\\]/g;\n\n/**\n * Matches a `:param` token that starts a URL segment.\n *\n * A param is only recognized when its `:` sits at the very start of the\n * template or is immediately preceded by `/`, so a literal colon inside a\n * segment (e.g. `/users/foo:bar`) is not treated as a param. Param names are\n * restricted to `[A-Za-z0-9_]` (matching React Router), so any static suffix\n * (e.g. `.json` in `/files/:name.json`) stays a literal. The optional `?`\n * marker (`:param?`) is captured separately so it can be handled as a\n * whole-segment modifier.\n */\nexport const PARAM_SEGMENT_RE = /(^|\\/):([A-Za-z0-9_]+)(\\?)?/g;\n\nexport function escapeRegex(value: string): string {\n return value.replace(ESCAPE_RE, \"\\\\$&\");\n}\n\n/**\n * Convert a route template into an unanchored `RegExp` source string.\n *\n * - Required `:name` segments become a capturing group `([^/]+)`.\n * - Optional `:name?` segments become `(?:([^/]+))?` — the whole segment is\n * optional, matching React Router semantics.\n * - A trailing splat (`/*`) becomes `(?:/(.*))?`, capturing the remainder of\n * the path (including slashes) or nothing at all.\n * - Everything else is regex-escaped literally.\n */\nfunction createTemplatePattern(template: string): string {\n const splat = template.endsWith(\"/*\");\n const base = splat ? template.slice(0, -2) : template;\n let pattern = \"\";\n let cursor = 0;\n\n for (const match of base.matchAll(PARAM_SEGMENT_RE)) {\n const start = match.index ?? 0;\n const token = match[0];\n\n pattern += escapeRegex(base.slice(cursor, start));\n\n const [, boundary = \"\", , optional] = match;\n pattern += optional\n ? `(?:${escapeRegex(boundary)}([^/]+))?`\n : `${escapeRegex(boundary)}([^/]+)`;\n\n cursor = start + (token?.length ?? 0);\n }\n\n return (\n pattern + escapeRegex(base.slice(cursor)) + (splat ? \"(?:/(.*))?\" : \"\")\n );\n}\n\n/** Returns `true` when `name` is an optional (`:name?`) param in `template`. */\nexport function isOptionalParam(template: string, name: string): boolean {\n return new RegExp(`:${escapeRegex(name)}\\\\?(?![A-Za-z0-9_])`).test(template);\n}\n\n/**\n * Returns a `RegExp` that matches `template` as a path prefix, honoring\n * segment boundaries (`/users` matches `/users/42` but not `/usersettings`).\n *\n * The root template `/` is a prefix of every path.\n *\n * Compiled patterns are cached per template: the resulting `RegExp` has no\n * `/g` flag, so repeated `.test()`/`.exec()` calls are side-effect free and\n * sharing instances across callers is safe.\n */\nconst PREFIX_CACHE = new Map<string, RegExp>();\nconst ROOT_PREFIX_RE = /^/;\n\nexport function matchPrefix(template: string): RegExp {\n if (template === \"/\") return ROOT_PREFIX_RE;\n const cached = PREFIX_CACHE.get(template);\n if (cached !== undefined) return cached;\n const re = new RegExp(`^${createTemplatePattern(template)}(?=/|$)`);\n PREFIX_CACHE.set(template, re);\n return re;\n}\n\n/** Decode a URL-encoded param value, falling back to the raw value on error. */\nexport function safeDecode(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n/**\n * Convert a route template string into an anchored `RegExp` for matching paths.\n *\n * Each `:param` segment becomes a capturing group so the returned regex\n * can be used with `.test()` or `.exec()`.\n *\n * Query strings are **not** stripped — callers should split on `\"?\"` first\n * (see {@link isActivePath} or {@link extractParamsFromPath} for higher-level\n * helpers that handle this automatically).\n *\n * @param template - A route template, e.g. `\"/users/:id\"` or `\"/users/:id/posts/:postId\"`.\n * @returns A `RegExp` anchored with `^` and `$` that captures param values.\n *\n * @example\n * ```ts\n * const re = matchPath(\"/users/:id\");\n * re.test(\"/users/42\"); // true\n * re.exec(\"/users/42\"); // [\"/users/42\", \"42\"]\n * re.test(\"/users/42/posts\"); // false (exact match)\n * re.test(\"/users/42?page=1\"); // true (query is part of captured value)\n * ```\n *\n * Compiled patterns are cached per template (the regex has no `/g` flag, so\n * sharing instances across callers is safe).\n */\nconst PATH_CACHE = new Map<string, RegExp>();\n\nexport function matchPath(\n template: string,\n options?: { end?: boolean; caseSensitive?: boolean },\n): RegExp {\n const end = options?.end ?? true;\n const flags = options?.caseSensitive ? \"\" : \"i\";\n const cacheKey = `${template}:${end}:${flags}`;\n\n const cached = PATH_CACHE.get(cacheKey);\n if (cached !== undefined) return cached;\n\n const basePattern = createTemplatePattern(template);\n const pattern = end ? `^${basePattern}$` : `^${basePattern}(?=/|$)`;\n const re = new RegExp(pattern, flags);\n PATH_CACHE.set(cacheKey, re);\n return re;\n}\n\n/**\n * Clears internal regex cache maps (PREFIX_CACHE and PATH_CACHE).\n * Useful in test suites to prevent cached patterns from leaking across test cases.\n */\nexport function clearPathCache(): void {\n PREFIX_CACHE.clear();\n PATH_CACHE.clear();\n}\n","import { PARAM_SEGMENT_RE, matchPath, safeDecode } from \"./pattern\";\n\n/**\n * Extract the `:param` names from a route template.\n *\n * A param is only recognized at the start of a segment (see\n * {@link PARAM_SEGMENT_RE}), so literal colons (`/users/foo:bar`) and static\n * suffixes after a param (`/files/:name.json`) are not treated as params.\n * A trailing splat (`/*`) is reported as the `\"*\"` name.\n */\nexport function extractParamNames(template: string): string[] {\n const names = [...template.matchAll(PARAM_SEGMENT_RE)].map(\n (match) => match[2] as string,\n );\n if (template.endsWith(\"/*\")) names.push(\"*\");\n return names;\n}\n\n/** Returns `true` when the template contains a `:param` or trailing `/*` splat. */\nexport function isDynamic(path: string): boolean {\n return path.search(PARAM_SEGMENT_RE) !== -1 || path.endsWith(\"/*\");\n}\n\n/**\n * Alias of {@link extractParamNames} — kept for backwards compatibility.\n */\nexport function getParamNames(template: string): string[] {\n return extractParamNames(template);\n}\n\n/**\n * Extract the param values matched by `template` from a resolved path.\n *\n * The query string is stripped before matching, so `/users/42?tab=profile`\n * still yields `{ id: \"42\" }`. Values are URL-decoded back to their original\n * form; a non-matching path yields an empty object.\n */\nexport function extractParamsFromPath(\n template: string,\n resolvedPath: string,\n): Record<string, string> {\n const pathWithoutSearch = resolvedPath.split(\"?\")[0] ?? \"\";\n const paramNames = extractParamNames(template);\n const match = pathWithoutSearch.match(matchPath(template));\n\n if (!match) return {};\n\n const result: Record<string, string> = {};\n paramNames.forEach((name, index) => {\n const raw = match[index + 1];\n if (raw !== undefined) result[name] = safeDecode(raw);\n });\n return result;\n}\n","import type { QueryParams, QueryParamValue } from \"../types\";\n\nfunction serializeToParams(keyPrefix: string, value: QueryParamValue, searchParams: URLSearchParams) {\n if (value === undefined || value === null) return;\n \n if (Array.isArray(value)) {\n value.forEach((v) => {\n if (v !== undefined && v !== null) {\n searchParams.append(keyPrefix, String(v));\n }\n });\n } else if (typeof value === 'object') {\n for (const [k, v] of Object.entries(value)) {\n serializeToParams(`${keyPrefix}[${k}]`, v, searchParams);\n }\n } else {\n searchParams.append(keyPrefix, String(value));\n }\n}\n\nfunction deleteMatchingKeys(searchParams: URLSearchParams, rootKey: string) {\n const prefix = `${rootKey}[`;\n const keysToDelete: string[] = [];\n for (const key of searchParams.keys()) {\n if (key === rootKey || key.startsWith(prefix)) {\n keysToDelete.push(key);\n }\n }\n for (const key of keysToDelete) {\n searchParams.delete(key);\n }\n}\n\n/**\n * Append a query string and/or hash fragment to a path that may already\n * contain a query or hash.\n *\n * - Existing query pairs are preserved; new ones are joined with `&`.\n * - The query string is always inserted before any hash fragment, so an\n * existing `#section` on `path` is kept unless a new `hash` is given.\n *\n * @example\n * ```ts\n * appendQuery(\"/users?tab=list\", { page: 2 }); // → \"/users?tab=list&page=2\"\n * appendQuery(\"/users#top\", { tab: \"list\" }); // → \"/users?tab=list#top\"\n * ```\n */\nexport function appendQuery(\n path: string,\n query?: QueryParams,\n hash?: string,\n): string {\n const hashIdx = path.indexOf(\"#\");\n const pathWithoutHash = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const existingHash = hashIdx === -1 ? \"\" : path.slice(hashIdx + 1);\n\n const queryIdx = pathWithoutHash.indexOf(\"?\");\n const base = queryIdx === -1 ? pathWithoutHash : pathWithoutHash.slice(0, queryIdx);\n const existingQueryString = queryIdx === -1 ? \"\" : pathWithoutHash.slice(queryIdx + 1);\n\n const searchParams = new URLSearchParams(existingQueryString);\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (!Object.prototype.hasOwnProperty.call(query, key)) continue;\n \n deleteMatchingKeys(searchParams, key);\n \n if (value !== undefined && value !== null) {\n serializeToParams(key, value, searchParams);\n }\n }\n }\n\n let result = base;\n const queryString = searchParams.toString();\n if (queryString) {\n result += \"?\" + queryString;\n }\n\n if (hash) {\n result += \"#\" + encodeURIComponent(hash);\n } else if (existingHash) {\n result += \"#\" + existingHash;\n }\n\n return result;\n}\n\nfunction setDeepProperty(obj: Record<string, unknown>, path: string, value: unknown) {\n const parts = path.replace(/\\]/g, \"\").split(/\\[/);\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i] as string;\n if (!current[part] || typeof current[part] !== 'object' || Array.isArray(current[part])) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n current[parts[parts.length - 1] as string] = value;\n}\n\n/**\n * Parse the query string out of a path (or bare query string) into a plain\n * object. Repeated keys become arrays; a single key is a scalar string.\n * Supports deep object nesting via bracket notation (e.g. `user[name]=John`).\n *\n * With `{ coerceBooleans: true }`, the strings `\"true\"`/`\"false\"` are\n * converted to actual booleans.\n *\n * @example\n * ```ts\n * extractQueryFromPath(\"/users/42?tab=profile&tag=a&tag=b\");\n * // → { tab: \"profile\", tag: [\"a\", \"b\"] }\n * extractQueryFromPath(\"/search?active=true\", { coerceBooleans: true });\n * // → { active: true }\n * ```\n */\nexport function extractQueryFromPath(\n path: string,\n options?: { coerceBooleans?: boolean; coerceNumbers?: boolean },\n): QueryParams {\n const hashIdx = path.indexOf(\"#\");\n const noHash = hashIdx === -1 ? path : path.slice(0, hashIdx);\n const queryIdx = noHash.indexOf(\"?\");\n if (queryIdx === -1) return {};\n\n const params = new URLSearchParams(noHash.slice(queryIdx + 1));\n const result: QueryParams = {};\n\n for (const key of new Set(params.keys())) {\n const values = params.getAll(key);\n const parsed = values.map((v) => {\n if (options?.coerceBooleans) {\n const lower = String(v).toLowerCase();\n if (lower === \"true\" || lower === \"false\") {\n return lower === \"true\";\n }\n }\n if (options?.coerceNumbers && v.trim() !== \"\" && !isNaN(Number(v))) {\n return Number(v);\n }\n return v;\n });\n const finalValue = parsed.length > 1 ? parsed : (parsed[0] ?? \"\");\n setDeepProperty(result, key, finalValue);\n }\n\n return result;\n}\n","import { escapeRegex, isOptionalParam } from \"./pattern\";\nimport { extractParamNames } from \"./params\";\nimport { appendQuery } from \"./query\";\nimport { devWarn } from \"./environment\";\nimport type { BuildPathOptions, QueryParams, RouteParams } from \"../types\";\n\n/**\n * Resolve a route template against a params object, appending an optional\n * query string and hash fragment.\n *\n * Missing required params are left as their `:name` placeholder and a\n * `console.warn` is emitted — pass `{ strict: true }` to throw a `RangeError`\n * instead. Optional (`:param?`) segments are dropped entirely when missing;\n * a missing splat just drops the trailing `/*`. Values are URL-encoded by\n * default (`{ encode: false }` opts out).\n */\nexport function buildPath(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n const paramNames = extractParamNames(template);\n const unresolved = paramNames.filter(\n (name) =>\n (params[name] === undefined || params[name] === null) &&\n !isOptionalParam(template, name) &&\n // A missing splat simply drops the `/*` suffix (matching React Router,\n // where `/files/*` also matches `/files`).\n name !== \"*\",\n );\n\n const resolved = paramNames.reduce((path, name) => {\n const value = params[name];\n const missing = value === undefined || value === null;\n\n if (name === \"*\") {\n if (missing) return path.replace(/\\/\\*$/, \"\") || \"/\";\n\n if (typeof value !== 'string' && typeof value !== 'number') {\n throw new TypeError(\n `Splat parameter must be string or number, got ${typeof value}`\n );\n }\n\n // Splat values are path-like: preserve `/` separators but still\n // encode characters that could break the URL (`?`, `#`, spaces, …).\n const encoded =\n options?.encode === false\n ? String(value)\n : String(value)\n .split(\"/\")\n .map((segment) => encodeURIComponent(segment))\n .join(\"/\");\n return path.replace(/\\/\\*$/, `/${encoded}`);\n }\n\n const re = new RegExp(\n `(^|/):${escapeRegex(name)}\\\\??(?![A-Za-z0-9_])`,\n \"g\",\n );\n\n return path.replace(re, (match, boundary) => {\n if (missing) {\n // Optional segment: drop the whole `/segment`. Required: keep the\n // `:name` placeholder so strict mode can report it.\n return match.endsWith(\"?\") ? \"\" : `${boundary}:${name}`;\n }\n\n const encoded =\n options?.encode === false\n ? String(value)\n : encodeURIComponent(String(value));\n return `${boundary}${encoded}`;\n });\n }, template);\n\n if (unresolved.length > 0) {\n if (options?.strict) {\n throw new RangeError(\n `[route-forge] Missing required param(s) ${unresolved.map((p) => `\":${p}\"`).join(\", \")} in template \"${template}\".`,\n );\n }\n\n devWarn(\n `[route-forge] Unresolved params in path \"${resolved}\". ` +\n `Check that all :param segments have matching keys.`,\n );\n }\n\n const finalPath = options?.locale\n ? joinPaths(options.locale, resolved)\n : resolved;\n\n return appendQuery(finalPath, query, options?.hash);\n}\n\n/**\n * Standalone alias of {@link buildPath} — resolves a template against params.\n */\nexport function build(\n template: string,\n params: RouteParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n): string {\n return buildPath(template, params, query, options);\n}\n\n/**\n * Join URL path segments into a single slash-prefixed path, normalising\n * duplicate slashes.\n *\n * @example\n * joinPaths(\"/api/\", \"/v1/\", \"users\"); // → \"/api/v1/users\"\n */\nexport function joinPaths(...segments: string[]): string {\n const processed = segments.map((segment) =>\n segment.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\"),\n );\n const filtered = processed.filter(Boolean);\n return \"/\" + filtered.join(\"/\");\n}\n","import { matchPath, matchPrefix } from \"./pattern\";\n\n/**\n * Test whether `currentPath` matches `template`, mirroring React Router's\n * `NavLink` matching semantics:\n *\n * - Case-insensitive by default (pass `caseSensitive: true` to opt out).\n * - Trailing slashes are tolerated (`/users/` matches `/users`).\n * - `exact: true` (the default) requires a full match; `exact: false`\n * matches any path that starts with the template.\n */\nexport function isActivePath(\n currentPath: string,\n template: string,\n options: { exact?: boolean; caseSensitive?: boolean } = {},\n): boolean {\n // NOTE: default each property individually rather than relying on a\n // default *object* for `options` — JS default parameters only apply when\n // the whole argument is omitted, so `isActivePath(a, b, { caseSensitive: true })`\n // would otherwise silently lose the documented `exact: true` default.\n const exact = options.exact ?? true;\n const caseSensitive = options.caseSensitive ?? false;\n\n const pathname = (currentPath.split(\"?\")[0] ?? \"\").replace(/\\/+$/, \"\") || \"/\";\n const normalizedTemplate = template.replace(/\\/+$/, \"\") || \"/\";\n const target = caseSensitive\n ? normalizedTemplate\n : normalizedTemplate.toLowerCase();\n const candidate = caseSensitive ? pathname : pathname.toLowerCase();\n const regex = exact\n ? matchPath(target, { caseSensitive })\n : matchPrefix(target);\n\n return regex.test(candidate);\n}\n","import { matchPath, matchPrefix } from \"./pattern\";\nimport { extractParamsFromPath, isDynamic } from \"./params\";\nimport { buildPath } from \"./build\";\nimport type {\n BreadcrumbItem,\n BreadcrumbOptions,\n FlatRoute,\n} from \"../types\";\n\n/**\n * Walk a `defineRoutes` output tree and return a flat array of\n * `{ key, path }` entries where `key` is the dot-joined key path from\n * the root (e.g. `\"SERVICES.BCC.EDIT\"`) and `path` is the raw template\n * string (e.g. `\"/services/bcc/edit/:id\"`).\n *\n * Useful for:\n * - Generating sitemaps from a single source of truth.\n * - Detecting duplicate path strings across branches at startup:\n *\n * @example\n * const flat = flattenRoutes(PATHS);\n * const paths = flat.map((r) => r.path);\n * const dupes = paths.filter((p, i) => paths.indexOf(p) !== i);\n * if (dupes.length) console.warn('Duplicate paths:', dupes);\n */\nexport function flattenRoutes(\n routes: Record<string, unknown>,\n prefix = \"\",\n visited = new Set<Record<string, unknown>>(),\n): FlatRoute[] {\n if (visited.has(routes)) return [];\n visited.add(routes);\n\n const entries: FlatRoute[] = [];\n\n for (const key of Object.keys(routes)) {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n const value = routes[key];\n\n if (typeof value === \"string\") {\n // Plain static string leaf.\n entries.push({ key: fullKey, path: value });\n } else if (value instanceof String) {\n // String-object leaf (wrapped dynamic path from defineRoutes).\n entries.push({ key: fullKey, path: value.valueOf() });\n } else if (typeof value === \"object\" && value !== null) {\n // Nested route group — recurse.\n entries.push(...flattenRoutes(value as Record<string, unknown>, fullKey, visited));\n }\n // Anything else (functions, numbers, …) is silently skipped.\n }\n\n return entries;\n}\n\nfunction deriveBreadcrumbLabel(key: string): string {\n const parts = key.split(\".\");\n const last = parts[parts.length - 1] ?? key;\n // Use the parent segment when the leaf is the conventional \"ROOT\" key,\n // so USERS.ROOT → \"Users\" rather than \"Root\".\n const raw =\n last === \"ROOT\" && parts.length > 1 ? parts[parts.length - 2]! : last;\n return raw\n .replace(/_/g, \" \")\n .toLowerCase()\n .replace(/^\\w/, (c) => c.toUpperCase());\n}\n\n/**\n * Build a breadcrumb trail from a route tree or a flat route list.\n *\n * For a given `currentPath`, it walks the route tree and returns every route\n * that is an ancestor of (or an exact match to) the current page. Ancestors\n * are matched by prefix (e.g. `/users` matches `/users/edit/42/posts`).\n *\n * Dynamic params in ancestor paths are automatically resolved from the\n * matched portion of the URL.\n *\n * @param routes - A route tree (output of `defineRoutes`) or a pre-flattened\n * array from `flattenRoutes()`.\n * @param currentPath - The current URL (with or without query string).\n * @param options - Optional label resolver.\n * @returns An array of {@link BreadcrumbItem} ordered by depth\n * (most general first), where the last item is the current page.\n *\n * @example\n * ```ts\n * const PATHS = defineRoutes({\n * HOME: \"/\",\n * USERS: { ROOT: \"/users\", EDIT: \"/users/edit/:id\" },\n * } as const);\n *\n * getBreadcrumbs(PATHS, \"/users/edit/42\");\n * // → [\n * // { key: \"HOME\", label: \"Home\", path: \"/\", isCurrent: false },\n * // { key: \"USERS.ROOT\", label: \"Users\", path: \"/users\", isCurrent: false },\n * // { key: \"USERS.EDIT\", label: \"Edit\", path: \"/users/edit/42\", isCurrent: true },\n * // ]\n * ```\n */\nexport function getBreadcrumbs(\n routes: Record<string, unknown> | FlatRoute[],\n currentPath: string,\n options?: BreadcrumbOptions,\n): BreadcrumbItem[] {\n const flat = Array.isArray(routes) ? routes : flattenRoutes(routes);\n const pathname = currentPath.split(\"?\")[0] ?? \"\";\n const labelFn = options?.labelResolver ?? deriveBreadcrumbLabel;\n const labels = options?.labels ?? {};\n\n const items: Array<{\n key: string;\n resolvedPath: string;\n template: string;\n isCurrent: boolean;\n }> = [];\n\n for (const route of flat) {\n const exactRe = matchPath(route.path);\n const exactMatch = pathname.match(exactRe);\n\n if (exactMatch) {\n const params = extractParamsFromPath(route.path, exactMatch[0]);\n const resolved = isDynamic(route.path)\n ? buildPath(route.path, params)\n : route.path;\n items.push({\n key: route.key,\n resolvedPath: resolved,\n template: route.path,\n isCurrent: true,\n });\n continue;\n }\n\n const prefixRe = matchPrefix(route.path);\n const prefixMatch = pathname.match(prefixRe);\n\n if (prefixMatch) {\n const matchedPortion = prefixMatch[0];\n const params = extractParamsFromPath(route.path, matchedPortion);\n const resolved = isDynamic(route.path)\n ? buildPath(route.path, params)\n : route.path;\n items.push({\n key: route.key,\n resolvedPath: resolved,\n template: route.path,\n isCurrent: false,\n });\n }\n }\n\n // Sort by path depth (segment count), not raw string length — a shallow\n // route with a long param name (e.g. \"/shop/:reallyLongSlugName\") must\n // still sort before a deeper route with short segments (e.g.\n // \"/shop/widgets/details\"), even though the latter has fewer characters.\n const segmentCount = (t: string) => t.split(\"/\").filter(Boolean).length;\n items.sort((a, b) => segmentCount(a.template) - segmentCount(b.template));\n\n return items.map((item) => ({\n key: item.key,\n label: labels[item.key] ?? labelFn(item.key),\n path: item.resolvedPath,\n isCurrent: item.isCurrent,\n }));\n}\n","import { devWarn, isProduction } from \"./environment\";\nimport { buildPath } from \"./build\";\nimport { extractParamNames, isDynamic } from \"./params\";\nimport { isActivePath } from \"./match\";\nimport { flattenRoutes } from \"./routes\";\nimport type {\n BuildPathOptions,\n ExtractParams,\n PathParams,\n QueryParams,\n RoutePath,\n RouteTree,\n} from \"../types\";\n\n// Re-export so consumers that import from 'core/defineRoutes' get the full surface\nexport { buildPath } from \"./build\";\nexport { extractParamNames, isDynamic } from \"./params\";\n\n/**\n * A dynamic route is a template string with a `:param` or trailing `/*` splat,\n * augmented with a `.build()` helper and a `.paramNames` array.\n *\n * Exported so consumers (e.g. the hooks entry) can type against it.\n */\nexport type StaticRoute<T extends string> = T & {\n build(query?: QueryParams, options?: BuildPathOptions): RoutePath;\n buildRelative(query?: QueryParams, options?: BuildPathOptions): string;\n};\n\nexport type DynamicRoute<T extends string> = T extends\n | `${string}:${string}`\n | `${string}/*`\n ? T & {\n build(\n params: PathParams<T>,\n query?: QueryParams,\n options?: BuildPathOptions,\n ): RoutePath;\n buildRelative(\n params: PathParams<T>,\n query?: QueryParams,\n options?: BuildPathOptions,\n ): string;\n paramNames: Array<ExtractParams<T>>;\n }\n : StaticRoute<T>;\n\nexport type ResolvedRoutes<T extends RouteTree> = {\n [K in keyof T]: T[K] extends RouteTree\n ? ResolvedRoutes<T[K]>\n : T[K] extends string\n ? DynamicRoute<T[K]>\n : never;\n};\n\nconst isRouteGroup = (value: unknown): value is RouteTree =>\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n Object.getPrototypeOf(value) === Object.prototype;\n\n/**\n * Validate a single route template and warn (dev-only) about common mistakes:\n * missing leading `/` and non-trailing `*` splats.\n */\nfunction validateTemplate(template: string, key: string): void {\n if (!template.startsWith(\"/\")) {\n devWarn(\n `[route-forge] Route \"${key}\" does not start with \"/\": \"${template}\".`,\n );\n }\n\n if (template.includes(\"*\") && !template.endsWith(\"/*\")) {\n devWarn(\n `[route-forge] Route \"${key}\" uses \"*\" outside a trailing \"/*\" splat; only a trailing splat is supported: \"${template}\".`,\n );\n }\n}\n\n/**\n * Warn (dev-only) when two routes resolve to the same path template or shadow each other.\n */\nfunction detectDuplicatePaths(routes: Record<string, unknown>): void {\n const seen = new Map<string, string>();\n const warned = new Set<string>();\n const flat = flattenRoutes(routes);\n\n for (const route of flat) {\n const existing = seen.get(route.path);\n if (existing === undefined) {\n seen.set(route.path, route.key);\n } else if (!warned.has(route.path)) {\n warned.add(route.path);\n devWarn(\n `[route-forge] Duplicate route path \"${route.path}\" for \"${existing}\" and \"${route.key}\". ` +\n `Only one of them will be reachable.`,\n );\n }\n }\n\n // Shadowing check: warn when a static path matches a dynamic path defined before it\n for (let i = 0; i < flat.length; i++) {\n const r1 = flat[i]!;\n if (!isDynamic(r1.path)) continue;\n\n for (let j = i + 1; j < flat.length; j++) {\n const r2 = flat[j]!;\n if (isDynamic(r2.path)) continue;\n\n if (isActivePath(r2.path, r1.path, { exact: true })) {\n const pairKey = `${r1.key}->${r2.key}`;\n if (!warned.has(pairKey)) {\n warned.add(pairKey);\n devWarn(\n `[route-forge] Route \"${r2.key}\" (\"${r2.path}\") is shadowed by dynamic route \"${r1.key}\" (\"${r1.path}\"). Place static routes before dynamic parameters in route trees.`,\n );\n }\n }\n }\n }\n}\n\n/**\n * Attach `.build()` (and, for dynamic templates, `.paramNames`) to\n * `String.prototype` exactly once, so route values can stay **genuine\n * primitive strings** instead of `new String(template)` objects.\n *\n * Route values used to be wrapped in `new String(template)` so `.build()`\n * could be attached as an own property. That silently broke direct use with\n * React Router's `<Link to={...}>` (and anything else that branches on\n * `typeof to === \"string\"`, e.g. React Router's internal `resolveTo()`):\n * `typeof` on a `String` object is `\"object\"`, not `\"string\"`, so React\n * Router treated the route value as a `Partial<Path>` and spread it\n * (`{ ...routeValue }`) instead of parsing it as a path — producing a\n * `pathname`-less destination and a broken/no-op navigation. `.build()`\n * happened to \"fix\" it only because it returns a plain string.\n *\n * Putting `.build()`/`.paramNames` on the prototype instead means every\n * primitive string route value can still call `.build()` / read\n * `.paramNames` (resolved lazily from `this`, the template text itself),\n * while `typeof route === \"string\"` stays true — so route values now work\n * directly anywhere a plain path string is expected, `<Link to={route}>`\n * included, with no `.build()` call required.\n */\nfunction installBuildProtocol(): void {\n if (Object.prototype.hasOwnProperty.call(String.prototype, \"build\")) {\n return;\n }\n\n Object.defineProperty(String.prototype, \"build\", {\n value: function build(\n this: string,\n paramsOrQuery?: PathParams<string> | QueryParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n ): RoutePath {\n const template = this.valueOf();\n\n // Dynamic templates take (params, query?, options?); static templates\n // take (query?, options?) — disambiguated from the template itself,\n // since that's all the prototype method has to go on.\n return (\n isDynamic(template)\n ? buildPath(\n template,\n (paramsOrQuery ?? {}) as PathParams<string>,\n query,\n options,\n )\n : buildPath(\n template,\n {},\n paramsOrQuery as QueryParams,\n query as unknown as BuildPathOptions,\n )\n ) as RoutePath;\n },\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n Object.defineProperty(String.prototype, \"buildRelative\", {\n value: function buildRelative(\n this: string,\n paramsOrQuery?: PathParams<string> | QueryParams,\n query?: QueryParams,\n options?: BuildPathOptions,\n ): string {\n const absolutePath = (this as any).build(paramsOrQuery, query, options);\n return absolutePath.replace(/^\\/+/, \"\") || \".\";\n },\n writable: false,\n enumerable: false,\n configurable: true,\n });\n\n Object.defineProperty(String.prototype, \"paramNames\", {\n get(this: string): string[] {\n return extractParamNames(this.valueOf());\n },\n enumerable: false,\n configurable: true,\n });\n}\n\ninstallBuildProtocol();\n\nfunction wrapStaticPath<T extends string>(template: T): StaticRoute<T> {\n // A genuine primitive string — `.build()` comes from String.prototype\n // (see installBuildProtocol above), so `typeof` stays \"string\".\n return template as StaticRoute<T>;\n}\n\nfunction wrapDynamicPath<T extends string>(template: T): DynamicRoute<T> {\n // Same here: primitive string, `.build()` and `.paramNames` are resolved\n // lazily from String.prototype based on the template text itself.\n return template as DynamicRoute<T>;\n}\n\nfunction processRouteMap<T extends RouteTree>(routes: T): ResolvedRoutes<T> {\n const result = {} as ResolvedRoutes<T>;\n\n for (const key in routes) {\n if (!Object.prototype.hasOwnProperty.call(routes, key)) continue;\n\n const value = routes[key];\n\n if (typeof value === \"string\") {\n validateTemplate(value, key);\n result[key] = (\n isDynamic(value) ? wrapDynamicPath(value) : wrapStaticPath(value)\n ) as ResolvedRoutes<T>[typeof key];\n } else if (isRouteGroup(value)) {\n result[key] = processRouteMap(\n value,\n ) as ResolvedRoutes<T>[typeof key];\n }\n }\n\n return result;\n}\n\nexport function defineRoutes<T extends RouteTree>(\n routes: T,\n): ResolvedRoutes<T> {\n const result = processRouteMap(routes);\n // detectDuplicatePaths only ever produces console.warn output (suppressed\n // in production by devWarn), so skip the O(n^2) walk entirely in\n // production rather than computing it and discarding the result.\n if (!isProduction()) {\n detectDuplicatePaths(result as Record<string, unknown>);\n }\n return result;\n}\n"],"mappings":"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","names":["index_exports","__export","appendQuery","build","buildPath","clearPathCache","defineRoutes","devWarn","extractParamNames","extractParamsFromPath","extractQueryFromPath","flattenRoutes","getBreadcrumbs","getParamNames","isActivePath","isDynamic","joinPaths","matchPath","__toCommonJS","isProduction","devWarn","message","ESCAPE_RE","PARAM_SEGMENT_RE","escapeRegex","value","createTemplatePattern","template","splat","base","pattern","cursor","match","start","token","boundary","optional","isOptionalParam","name","PREFIX_CACHE","ROOT_PREFIX_RE","matchPrefix","cached","re","safeDecode","PATH_CACHE","matchPath","options","end","flags","cacheKey","basePattern","clearPathCache","extractParamNames","template","names","PARAM_SEGMENT_RE","match","isDynamic","path","getParamNames","extractParamsFromPath","resolvedPath","pathWithoutSearch","paramNames","matchPath","result","name","index","raw","safeDecode","serializeToParams","keyPrefix","value","searchParams","v","k","deleteMatchingKeys","rootKey","prefix","keysToDelete","key","appendQuery","path","query","hash","hashIdx","pathWithoutHash","existingHash","queryIdx","base","existingQueryString","result","queryString","setDeepProperty","obj","parts","current","i","part","extractQueryFromPath","options","noHash","params","parsed","lower","finalValue","buildPath","template","params","query","options","paramNames","extractParamNames","unresolved","name","isOptionalParam","resolved","path","value","missing","encoded","segment","re","escapeRegex","match","boundary","p","devWarn","finalPath","joinPaths","appendQuery","build","segments","isActivePath","currentPath","template","options","exact","caseSensitive","pathname","normalizedTemplate","target","candidate","matchPath","matchPrefix","flattenRoutes","routes","prefix","visited","entries","key","fullKey","value","deriveBreadcrumbLabel","parts","last","c","getBreadcrumbs","currentPath","options","flat","pathname","labelFn","labels","items","route","exactRe","matchPath","exactMatch","params","extractParamsFromPath","resolved","isDynamic","buildPath","prefixRe","matchPrefix","prefixMatch","matchedPortion","segmentCount","t","a","b","item","isRouteGroup","value","validateTemplate","template","key","devWarn","detectDuplicatePaths","routes","seen","warned","flat","flattenRoutes","route","existing","i","r1","isDynamic","j","r2","isActivePath","pairKey","installBuildProtocol","paramsOrQuery","query","options","buildPath","extractParamNames","processRouteMap","routes","result","key","value","validateTemplate","isDynamic","isRouteGroup","defineRoutes","isProduction","detectDuplicatePaths"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RouteParams, Q as QueryParams, B as BuildPathOptions, P as PathParams, a as RoutePath, E as ExtractParams, b as RouteTree, F as FlatRoute, c as BreadcrumbOptions, d as BreadcrumbItem } from './index-CRAdjXCI.cjs';
2
- export { M as MatchPathOptions, e as RouteBuilder, f as RouteLeaf, g as RouteMap, h as RouteParam } from './index-CRAdjXCI.cjs';
1
+ import { R as RouteParams, Q as QueryParams, B as BuildPathOptions, P as PathParams, a as RoutePath, E as ExtractParams, b as RouteTree, F as FlatRoute, c as BreadcrumbOptions, d as BreadcrumbItem } from './index-_N1whVbt.cjs';
2
+ export { M as MatchPathOptions, e as RouteBuilder, f as RouteLeaf, g as RouteMap, h as RouteParam } from './index-_N1whVbt.cjs';
3
3
 
4
4
  /**
5
5
  * Resolve a route template against a params object, appending an optional
@@ -57,9 +57,11 @@ declare function extractParamsFromPath(template: string, resolvedPath: string):
57
57
  */
58
58
  type StaticRoute<T extends string> = T & {
59
59
  build(query?: QueryParams, options?: BuildPathOptions): RoutePath;
60
+ buildRelative(query?: QueryParams, options?: BuildPathOptions): string;
60
61
  };
61
62
  type DynamicRoute<T extends string> = T extends `${string}:${string}` | `${string}/*` ? T & {
62
63
  build(params: PathParams<T>, query?: QueryParams, options?: BuildPathOptions): RoutePath;
64
+ buildRelative(params: PathParams<T>, query?: QueryParams, options?: BuildPathOptions): string;
63
65
  paramNames: Array<ExtractParams<T>>;
64
66
  } : StaticRoute<T>;
65
67
  type ResolvedRoutes<T extends RouteTree> = {
@@ -109,6 +111,7 @@ declare function appendQuery(path: string, query?: QueryParams, hash?: string):
109
111
  /**
110
112
  * Parse the query string out of a path (or bare query string) into a plain
111
113
  * object. Repeated keys become arrays; a single key is a scalar string.
114
+ * Supports deep object nesting via bracket notation (e.g. `user[name]=John`).
112
115
  *
113
116
  * With `{ coerceBooleans: true }`, the strings `"true"`/`"false"` are
114
117
  * converted to actual booleans.
@@ -142,7 +145,7 @@ declare function extractQueryFromPath(path: string, options?: {
142
145
  * const dupes = paths.filter((p, i) => paths.indexOf(p) !== i);
143
146
  * if (dupes.length) console.warn('Duplicate paths:', dupes);
144
147
  */
145
- declare function flattenRoutes(routes: Record<string, unknown>, prefix?: string): FlatRoute[];
148
+ declare function flattenRoutes(routes: Record<string, unknown>, prefix?: string, visited?: Set<Record<string, unknown>>): FlatRoute[];
146
149
  /**
147
150
  * Build a breadcrumb trail from a route tree or a flat route list.
148
151
  *
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RouteParams, Q as QueryParams, B as BuildPathOptions, P as PathParams, a as RoutePath, E as ExtractParams, b as RouteTree, F as FlatRoute, c as BreadcrumbOptions, d as BreadcrumbItem } from './index-CRAdjXCI.js';
2
- export { M as MatchPathOptions, e as RouteBuilder, f as RouteLeaf, g as RouteMap, h as RouteParam } from './index-CRAdjXCI.js';
1
+ import { R as RouteParams, Q as QueryParams, B as BuildPathOptions, P as PathParams, a as RoutePath, E as ExtractParams, b as RouteTree, F as FlatRoute, c as BreadcrumbOptions, d as BreadcrumbItem } from './index-_N1whVbt.js';
2
+ export { M as MatchPathOptions, e as RouteBuilder, f as RouteLeaf, g as RouteMap, h as RouteParam } from './index-_N1whVbt.js';
3
3
 
4
4
  /**
5
5
  * Resolve a route template against a params object, appending an optional
@@ -57,9 +57,11 @@ declare function extractParamsFromPath(template: string, resolvedPath: string):
57
57
  */
58
58
  type StaticRoute<T extends string> = T & {
59
59
  build(query?: QueryParams, options?: BuildPathOptions): RoutePath;
60
+ buildRelative(query?: QueryParams, options?: BuildPathOptions): string;
60
61
  };
61
62
  type DynamicRoute<T extends string> = T extends `${string}:${string}` | `${string}/*` ? T & {
62
63
  build(params: PathParams<T>, query?: QueryParams, options?: BuildPathOptions): RoutePath;
64
+ buildRelative(params: PathParams<T>, query?: QueryParams, options?: BuildPathOptions): string;
63
65
  paramNames: Array<ExtractParams<T>>;
64
66
  } : StaticRoute<T>;
65
67
  type ResolvedRoutes<T extends RouteTree> = {
@@ -109,6 +111,7 @@ declare function appendQuery(path: string, query?: QueryParams, hash?: string):
109
111
  /**
110
112
  * Parse the query string out of a path (or bare query string) into a plain
111
113
  * object. Repeated keys become arrays; a single key is a scalar string.
114
+ * Supports deep object nesting via bracket notation (e.g. `user[name]=John`).
112
115
  *
113
116
  * With `{ coerceBooleans: true }`, the strings `"true"`/`"false"` are
114
117
  * converted to actual booleans.
@@ -142,7 +145,7 @@ declare function extractQueryFromPath(path: string, options?: {
142
145
  * const dupes = paths.filter((p, i) => paths.indexOf(p) !== i);
143
146
  * if (dupes.length) console.warn('Duplicate paths:', dupes);
144
147
  */
145
- declare function flattenRoutes(routes: Record<string, unknown>, prefix?: string): FlatRoute[];
148
+ declare function flattenRoutes(routes: Record<string, unknown>, prefix?: string, visited?: Set<Record<string, unknown>>): FlatRoute[];
146
149
  /**
147
150
  * Build a breadcrumb trail from a route tree or a flat route list.
148
151
  *
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{a as k,b as c,c as v,d as R,e as $,f as g,g as u,h as D,i as h,j,k as S,l as p,m as C,n as F,o as b}from"./chunk-IP4ZU25B.js";function f(t,e=""){let a=[];for(let s of Object.keys(t)){let r=e?`${e}.${s}`:s,n=t[s];typeof n=="string"?a.push({key:r,path:n}):n instanceof String?a.push({key:r,path:n.valueOf()}):typeof n=="object"&&n!==null&&a.push(...f(n,r))}return a}function A(t){let e=t.split("."),a=e[e.length-1]??t;return(a==="ROOT"&&e.length>1?e[e.length-2]:a).replace(/_/g," ").toLowerCase().replace(/^\w/,r=>r.toUpperCase())}function Q(t,e,a){let s=Array.isArray(t)?t:f(t),r=e.split("?")[0]??"",n=a?.labelResolver??A,l=a?.labels??{},i=[];for(let o of s){let d=R(o.path),x=r.match(d);if(x){let y=h(o.path,x[0]),P=u(o.path)?p(o.path,y):o.path;i.push({key:o.key,resolvedPath:P,template:o.path,isCurrent:!0});continue}let O=v(o.path),T=r.match(O);if(T){let y=T[0],P=h(o.path,y),B=u(o.path)?p(o.path,P):o.path;i.push({key:o.key,resolvedPath:B,template:o.path,isCurrent:!1})}}let m=o=>o.split("/").filter(Boolean).length;return i.sort((o,d)=>m(o.template)-m(d.template)),i.map(o=>({key:o.key,label:l[o.key]??n(o.key),path:o.resolvedPath,isCurrent:o.isCurrent}))}var K=t=>typeof t=="object"&&t!==null&&!Array.isArray(t)&&Object.getPrototypeOf(t)===Object.prototype;function M(t,e){t.startsWith("/")||c(`[route-forge] Route "${e}" does not start with "/": "${t}".`),t.includes("*")&&!t.endsWith("/*")&&c(`[route-forge] Route "${e}" uses "*" outside a trailing "/*" splat; only a trailing splat is supported: "${t}".`)}function N(t){let e=new Map,a=new Set,s=f(t);for(let r of s){let n=e.get(r.path);n===void 0?e.set(r.path,r.key):a.has(r.path)||(a.add(r.path),c(`[route-forge] Duplicate route path "${r.path}" for "${n}" and "${r.key}". Only one of them will be reachable.`))}for(let r=0;r<s.length;r++){let n=s[r];if(u(n.path))for(let l=r+1;l<s.length;l++){let i=s[l];if(!u(i.path)&&b(i.path,n.path,{exact:!0})){let m=`${n.key}->${i.key}`;a.has(m)||(a.add(m),c(`[route-forge] Route "${i.key}" ("${i.path}") is shadowed by dynamic route "${n.key}" ("${n.path}"). Place static routes before dynamic parameters in route trees.`))}}}}function W(){Object.prototype.hasOwnProperty.call(String.prototype,"build")||(Object.defineProperty(String.prototype,"build",{value:function(e,a,s){let r=this.valueOf();return u(r)?p(r,e??{},a,s):p(r,{},e,a)},writable:!1,enumerable:!1,configurable:!0}),Object.defineProperty(String.prototype,"paramNames",{get(){return g(this.valueOf())},enumerable:!1,configurable:!0}))}W();function w(t){let e={};for(let a in t){if(!Object.prototype.hasOwnProperty.call(t,a))continue;let s=t[a];typeof s=="string"?(M(s,a),e[a]=u(s)?s:s):K(s)&&(e[a]=w(s))}return e}function E(t){let e=w(t);return k()||N(e),e}export{j as appendQuery,C as build,p as buildPath,$ as clearPathCache,E as defineRoutes,c as devWarn,g as extractParamNames,h as extractParamsFromPath,S as extractQueryFromPath,f as flattenRoutes,Q as getBreadcrumbs,D as getParamNames,b as isActivePath,u as isDynamic,F as joinPaths,R as matchPath};
1
+ import{a as n,b as o,c as p}from"./chunk-LKDKVG2S.js";import{b as a,d as b,e as c,f as d,g as e,h as f,i as g,j as h,k as i,l as j,m as k,n as l,o as m}from"./chunk-3E7SIS7B.js";export{h as appendQuery,k as build,j as buildPath,c as clearPathCache,p as defineRoutes,a as devWarn,d as extractParamNames,g as extractParamsFromPath,i as extractQueryFromPath,n as flattenRoutes,o as getBreadcrumbs,f as getParamNames,m as isActivePath,e as isDynamic,l as joinPaths,b as matchPath};
2
2
  //# sourceMappingURL=index.js.map