@real-router/core 0.81.0 → 0.82.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.
- package/README.md +26 -13
- package/dist/cjs/Router-CCmcEi-S.js +2 -0
- package/dist/cjs/Router-CCmcEi-S.js.map +1 -0
- package/dist/cjs/Router.d.ts +7 -6
- package/dist/cjs/Router.d.ts.map +1 -1
- package/dist/cjs/Router.js +1 -1
- package/dist/cjs/Router.js.map +1 -1
- package/dist/cjs/api/getPluginApi.d.ts.map +1 -1
- package/dist/cjs/api/getPluginApi.js +1 -1
- package/dist/cjs/api/getPluginApi.js.map +1 -1
- package/dist/cjs/api/getRoutesApi.d.ts.map +1 -1
- package/dist/cjs/api/getRoutesApi.js +1 -1
- package/dist/cjs/api/getRoutesApi.js.map +1 -1
- package/dist/cjs/api.js +2 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/buildParamMeta-0m11OIJS.js +2 -0
- package/dist/cjs/buildParamMeta-0m11OIJS.js.map +1 -0
- package/dist/cjs/channels/defaults.js +2 -0
- package/dist/cjs/channels/defaults.js.map +1 -0
- package/dist/cjs/channels/guard.js +2 -0
- package/dist/cjs/channels/guard.js.map +1 -0
- package/dist/cjs/channels/modeGate.js +2 -0
- package/dist/cjs/channels/modeGate.js.map +1 -0
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +1 -1
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/createRouter.d.ts +1 -1
- package/dist/cjs/createRouter.d.ts.map +1 -1
- package/dist/cjs/createRouter.js.map +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts +14 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts.map +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/buildParts.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/context.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/context.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/errors.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/errors.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/index.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/index.js.map +1 -1
- package/dist/cjs/engine/path-matcher/types.d.ts +3 -0
- package/dist/cjs/engine/path-matcher/types.d.ts.map +1 -1
- package/dist/cjs/engine/search-params/strategies/number.js +1 -1
- package/dist/cjs/engine/search-params/strategies/number.js.map +1 -1
- package/dist/cjs/engine/validation/routes.js +1 -1
- package/dist/cjs/engine/validation/routes.js.map +1 -1
- package/dist/cjs/helpers.js +1 -1
- package/dist/cjs/helpers.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internals.d.ts +30 -8
- package/dist/cjs/internals.d.ts.map +1 -1
- package/dist/cjs/internals.js +1 -1
- package/dist/cjs/internals.js.map +1 -1
- package/dist/cjs/limits.js +2 -0
- package/dist/cjs/limits.js.map +1 -0
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +1 -1
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/constants.js +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +8 -0
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/types.d.ts +24 -7
- package/dist/cjs/namespaces/RoutesNamespace/types.d.ts.map +1 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +1 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +1 -1
- package/dist/cjs/pipeline/buildURL.js +2 -0
- package/dist/cjs/pipeline/buildURL.js.map +1 -0
- package/dist/cjs/pipeline/canonicalize.js +2 -0
- package/dist/cjs/pipeline/canonicalize.js.map +1 -0
- package/dist/cjs/pipeline/materialize.js +2 -0
- package/dist/cjs/pipeline/materialize.js.map +1 -0
- package/dist/cjs/pipeline/port.d.ts +147 -0
- package/dist/cjs/pipeline/port.d.ts.map +1 -0
- package/dist/cjs/transitionPath.js +1 -1
- package/dist/cjs/transitionPath.js.map +1 -1
- package/dist/cjs/types/RouterValidator.d.ts +14 -0
- package/dist/cjs/types/RouterValidator.d.ts.map +1 -1
- package/dist/cjs/types/api.d.ts +20 -11
- package/dist/cjs/types/api.d.ts.map +1 -1
- package/dist/cjs/types/base.d.ts +63 -4
- package/dist/cjs/types/base.d.ts.map +1 -1
- package/dist/cjs/types/constants.d.ts +1 -0
- package/dist/cjs/types/constants.d.ts.map +1 -1
- package/dist/cjs/types/router.d.ts +111 -22
- package/dist/cjs/types/router.d.ts.map +1 -1
- package/dist/cjs/types/tree-changed.d.ts +1 -1
- package/dist/cjs/types/tree-changed.d.ts.map +1 -1
- package/dist/cjs/types.d.ts +20 -11
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/validation.js +2 -1
- package/dist/cjs/validation.js.map +1 -1
- package/dist/cjs/wiring/wireNamespaces.js +1 -1
- package/dist/cjs/wiring/wireNamespaces.js.map +1 -1
- package/dist/esm/Router-DEpNuwBW.mjs +2 -0
- package/dist/esm/Router-DEpNuwBW.mjs.map +1 -0
- package/dist/esm/Router.d.mts +7 -6
- package/dist/esm/Router.d.mts.map +1 -1
- package/dist/esm/api/getPluginApi.d.mts.map +1 -1
- package/dist/esm/api/getRoutesApi.d.mts.map +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/buildParamMeta-D8td47fL.mjs +2 -0
- package/dist/esm/buildParamMeta-D8td47fL.mjs.map +1 -0
- package/dist/esm/constants.d.mts.map +1 -1
- package/dist/esm/createRouter.d.mts +1 -1
- package/dist/esm/createRouter.d.mts.map +1 -1
- package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts +14 -1
- package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts.map +1 -1
- package/dist/esm/engine/path-matcher/types.d.mts +3 -0
- package/dist/esm/engine/path-matcher/types.d.mts.map +1 -1
- package/dist/esm/index.d.mts +3 -3
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/internals.d.mts +30 -8
- package/dist/esm/internals.d.mts.map +1 -1
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +8 -0
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts.map +1 -1
- package/dist/esm/namespaces/RoutesNamespace/types.d.mts +24 -7
- package/dist/esm/namespaces/RoutesNamespace/types.d.mts.map +1 -1
- package/dist/esm/pipeline/port.d.mts +147 -0
- package/dist/esm/pipeline/port.d.mts.map +1 -0
- package/dist/esm/types/RouterValidator.d.mts +14 -0
- package/dist/esm/types/RouterValidator.d.mts.map +1 -1
- package/dist/esm/types/api.d.mts +20 -11
- package/dist/esm/types/api.d.mts.map +1 -1
- package/dist/esm/types/base.d.mts +63 -4
- package/dist/esm/types/base.d.mts.map +1 -1
- package/dist/esm/types/constants.d.mts +1 -0
- package/dist/esm/types/constants.d.mts.map +1 -1
- package/dist/esm/types/router.d.mts +111 -22
- package/dist/esm/types/router.d.mts.map +1 -1
- package/dist/esm/types/tree-changed.d.mts +1 -1
- package/dist/esm/types/tree-changed.d.mts.map +1 -1
- package/dist/esm/types.d.mts +20 -11
- package/dist/esm/types.d.mts.map +1 -1
- package/dist/esm/validation.mjs +1 -1
- package/dist/esm/validation.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/cjs/Router-BIYDZRgF.js +0 -2
- package/dist/cjs/Router-BIYDZRgF.js.map +0 -1
- package/dist/cjs/buildParamMeta-DniluAIV.js +0 -2
- package/dist/cjs/buildParamMeta-DniluAIV.js.map +0 -1
- package/dist/cjs/namespaces/StateNamespace/helpers.js +0 -2
- package/dist/cjs/namespaces/StateNamespace/helpers.js.map +0 -1
- package/dist/cjs/stateMetaStore.js +0 -2
- package/dist/cjs/stateMetaStore.js.map +0 -1
- package/dist/esm/Router-BIui2eew.mjs +0 -2
- package/dist/esm/Router-BIui2eew.mjs.map +0 -1
- package/dist/esm/buildParamMeta-bOLhLF9h.mjs +0 -2
- package/dist/esm/buildParamMeta-bOLhLF9h.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":[],"sources":["../../src/constants.ts"],"sourcesContent":["// packages/core/src/constants.ts\n\nimport type {\n EventToNameMap,\n EventToPluginMap,\n ErrorCodeToValueMap,\n ErrorCodeKeys,\n ErrorCodeValues,\n TransitionMeta,\n} from \"./types\";\n\nexport type ConstantsKeys = \"UNKNOWN_ROUTE\";\n\nexport type Constants = Record<ConstantsKeys, string>;\n\n// =============================================================================\n// Error Codes (migrated from router-error)\n// =============================================================================\n\nexport type ErrorCodes = Record<ErrorCodeKeys, ErrorCodeValues>;\n\n/**\n * Error codes for router operations.\n * Used to identify specific failure scenarios in navigation and lifecycle.\n * Frozen to prevent accidental modifications.\n */\nexport const errorCodes: ErrorCodeToValueMap = Object.freeze({\n ROUTER_NOT_STARTED: \"NOT_STARTED\", // navigate() called before start()\n NO_START_PATH_OR_STATE: \"NO_START_PATH_OR_STATE\", // start() without initial route\n ROUTER_ALREADY_STARTED: \"ALREADY_STARTED\", // start() called twice\n ROUTE_NOT_FOUND: \"ROUTE_NOT_FOUND\", // Navigation to non-existent route\n SAME_STATES: \"SAME_STATES\", // Navigate to current route without reload\n CANNOT_DEACTIVATE: \"CANNOT_DEACTIVATE\", // canDeactivate guard blocked navigation\n CANNOT_ACTIVATE: \"CANNOT_ACTIVATE\", // canActivate guard blocked navigation\n TRANSITION_ERR: \"TRANSITION_ERR\", // Generic transition failure\n TRANSITION_CANCELLED: \"CANCELLED\", // Navigation cancelled by user or new navigation\n ROUTER_DISPOSED: \"DISPOSED\", // Router has been disposed\n PLUGIN_CONFLICT: \"PLUGIN_CONFLICT\", // Plugin tried to extend router with already-existing property\n CONTEXT_NAMESPACE_ALREADY_CLAIMED: \"CONTEXT_NAMESPACE_ALREADY_CLAIMED\", // Plugin tried to claim a context namespace already owned by another plugin\n REENTRANT_NAVIGATION: \"REENTRANT_NAVIGATION\", // navigate() called synchronously from inside a transition-event listener (banned — use await/async listener)\n REENTRANT_TREE_MUTATION: \"REENTRANT_TREE_MUTATION\", // route-CRUD called synchronously from inside a subscribeChanges handler (banned — use await/async/queueMicrotask)\n});\n\n/**\n * General router constants.\n * Special route names and identifiers.\n */\nexport const UNKNOWN_ROUTE = \"@@router/UNKNOWN_ROUTE\";\n\nexport const constants: Constants = {\n UNKNOWN_ROUTE,\n};\n\n/**\n * Plugin method names.\n * Maps to methods that plugins can implement to hook into router lifecycle.\n */\nexport const plugins: EventToPluginMap = {\n ROUTER_START: \"onStart\", // Plugin method called when router starts\n ROUTER_STOP: \"onStop\", // Plugin method called when router stops\n TRANSITION_START: \"onTransitionStart\", // Plugin method called when navigation begins\n TRANSITION_LEAVE_APPROVE: \"onTransitionLeaveApprove\", // Plugin method called when deactivation guards pass\n TRANSITION_CANCEL: \"onTransitionCancel\", // Plugin method called when navigation cancelled\n TRANSITION_SUCCESS: \"onTransitionSuccess\", // Plugin method called when navigation succeeds\n TRANSITION_ERROR: \"onTransitionError\", // Plugin method called when navigation fails\n};\n\n/**\n * Event names for router event system.\n * Used with addEventListener/removeEventListener for reactive subscriptions.\n */\nexport const events: EventToNameMap = {\n ROUTER_START: \"$start\", // Emitted when router.start() succeeds\n ROUTER_STOP: \"$stop\", // Emitted when router.stop() is called\n TRANSITION_START: \"$$start\", // Emitted when navigation begins\n TRANSITION_LEAVE_APPROVE: \"$$leaveApprove\", // Emitted when deactivation guards pass\n TRANSITION_CANCEL: \"$$cancel\", // Emitted when navigation is cancelled\n TRANSITION_SUCCESS: \"$$success\", // Emitted when navigation completes successfully\n TRANSITION_ERROR: \"$$error\", // Emitted when navigation fails\n};\n\nexport const DEFAULT_LIMITS = {\n maxDependencies: 100,\n maxPlugins: 50,\n maxListeners: 10_000,\n warnListeners: 1000,\n maxLifecycleHandlers: 200,\n} as const;\n\nexport const EMPTY_PARAMS: Readonly<Record<string, never>> = Object.freeze({});\n\nconst FROZEN_EMPTY_SEGMENTS = Object.freeze({\n deactivated: Object.freeze([]) as unknown as string[],\n activated: Object.freeze([]) as unknown as string[],\n intersection: \"\",\n});\n\nexport const DEFAULT_TRANSITION = Object.freeze({\n phase: \"activating\",\n reason: \"success\",\n segments: FROZEN_EMPTY_SEGMENTS,\n}) as TransitionMeta;\n"],"mappings":"AA0BA,MAAa,EAAkC,OAAO,OAAO,CAC3D,mBAAoB,cACpB,uBAAwB,yBACxB,uBAAwB,kBACxB,gBAAiB,kBACjB,YAAa,cACb,kBAAmB,oBACnB,gBAAiB,kBACjB,eAAgB,iBAChB,qBAAsB,YACtB,gBAAiB,WACjB,gBAAiB,kBACjB,kCAAmC,oCACnC,qBAAsB,uBACtB,wBAAyB,
|
|
1
|
+
{"version":3,"file":"constants.js","names":[],"sources":["../../src/constants.ts"],"sourcesContent":["// packages/core/src/constants.ts\n\nimport type {\n EventToNameMap,\n EventToPluginMap,\n ErrorCodeToValueMap,\n ErrorCodeKeys,\n ErrorCodeValues,\n TransitionMeta,\n} from \"./types\";\n\nexport type ConstantsKeys = \"UNKNOWN_ROUTE\";\n\nexport type Constants = Record<ConstantsKeys, string>;\n\n// =============================================================================\n// Error Codes (migrated from router-error)\n// =============================================================================\n\nexport type ErrorCodes = Record<ErrorCodeKeys, ErrorCodeValues>;\n\n/**\n * Error codes for router operations.\n * Used to identify specific failure scenarios in navigation and lifecycle.\n * Frozen to prevent accidental modifications.\n */\nexport const errorCodes: ErrorCodeToValueMap = Object.freeze({\n ROUTER_NOT_STARTED: \"NOT_STARTED\", // navigate() called before start()\n NO_START_PATH_OR_STATE: \"NO_START_PATH_OR_STATE\", // start() without initial route\n ROUTER_ALREADY_STARTED: \"ALREADY_STARTED\", // start() called twice\n ROUTE_NOT_FOUND: \"ROUTE_NOT_FOUND\", // Navigation to non-existent route\n SAME_STATES: \"SAME_STATES\", // Navigate to current route without reload\n CANNOT_DEACTIVATE: \"CANNOT_DEACTIVATE\", // canDeactivate guard blocked navigation\n CANNOT_ACTIVATE: \"CANNOT_ACTIVATE\", // canActivate guard blocked navigation\n TRANSITION_ERR: \"TRANSITION_ERR\", // Generic transition failure\n TRANSITION_CANCELLED: \"CANCELLED\", // Navigation cancelled by user or new navigation\n ROUTER_DISPOSED: \"DISPOSED\", // Router has been disposed\n PLUGIN_CONFLICT: \"PLUGIN_CONFLICT\", // Plugin tried to extend router with already-existing property\n CONTEXT_NAMESPACE_ALREADY_CLAIMED: \"CONTEXT_NAMESPACE_ALREADY_CLAIMED\", // Plugin tried to claim a context namespace already owned by another plugin\n REENTRANT_NAVIGATION: \"REENTRANT_NAVIGATION\", // navigate() called synchronously from inside a transition-event listener (banned — use await/async listener)\n REENTRANT_TREE_MUTATION: \"REENTRANT_TREE_MUTATION\", // route-CRUD called synchronously from inside a subscribeChanges handler (banned — use await/async/queueMicrotask)\n WRONG_CHANNEL: \"WRONG_CHANNEL\", // a declared query key was supplied in the path channel (`params`) instead of `search` (#1572)\n});\n\n/**\n * General router constants.\n * Special route names and identifiers.\n */\nexport const UNKNOWN_ROUTE = \"@@router/UNKNOWN_ROUTE\";\n\nexport const constants: Constants = {\n UNKNOWN_ROUTE,\n};\n\n/**\n * Plugin method names.\n * Maps to methods that plugins can implement to hook into router lifecycle.\n */\nexport const plugins: EventToPluginMap = {\n ROUTER_START: \"onStart\", // Plugin method called when router starts\n ROUTER_STOP: \"onStop\", // Plugin method called when router stops\n TRANSITION_START: \"onTransitionStart\", // Plugin method called when navigation begins\n TRANSITION_LEAVE_APPROVE: \"onTransitionLeaveApprove\", // Plugin method called when deactivation guards pass\n TRANSITION_CANCEL: \"onTransitionCancel\", // Plugin method called when navigation cancelled\n TRANSITION_SUCCESS: \"onTransitionSuccess\", // Plugin method called when navigation succeeds\n TRANSITION_ERROR: \"onTransitionError\", // Plugin method called when navigation fails\n};\n\n/**\n * Event names for router event system.\n * Used with addEventListener/removeEventListener for reactive subscriptions.\n */\nexport const events: EventToNameMap = {\n ROUTER_START: \"$start\", // Emitted when router.start() succeeds\n ROUTER_STOP: \"$stop\", // Emitted when router.stop() is called\n TRANSITION_START: \"$$start\", // Emitted when navigation begins\n TRANSITION_LEAVE_APPROVE: \"$$leaveApprove\", // Emitted when deactivation guards pass\n TRANSITION_CANCEL: \"$$cancel\", // Emitted when navigation is cancelled\n TRANSITION_SUCCESS: \"$$success\", // Emitted when navigation completes successfully\n TRANSITION_ERROR: \"$$error\", // Emitted when navigation fails\n};\n\nexport const DEFAULT_LIMITS = {\n maxDependencies: 100,\n maxPlugins: 50,\n maxListeners: 10_000,\n warnListeners: 1000,\n maxLifecycleHandlers: 200,\n} as const;\n\nexport const EMPTY_PARAMS: Readonly<Record<string, never>> = Object.freeze({});\n\n/**\n * Shared frozen empty query bag reused for `State.search` when a navigation\n * carries no query params — the search-channel twin of {@link EMPTY_PARAMS}\n * (RFC-4 M2 / #1548). Lets `makeState` reuse one frozen `{}` (zero transient\n * allocation, #1027) instead of minting an object per query-less state.\n */\nexport const EMPTY_SEARCH: Readonly<Record<string, never>> = Object.freeze({});\n\nconst FROZEN_EMPTY_SEGMENTS = Object.freeze({\n deactivated: Object.freeze([]) as unknown as string[],\n activated: Object.freeze([]) as unknown as string[],\n intersection: \"\",\n});\n\nexport const DEFAULT_TRANSITION = Object.freeze({\n phase: \"activating\",\n reason: \"success\",\n segments: FROZEN_EMPTY_SEGMENTS,\n}) as TransitionMeta;\n"],"mappings":"AA0BA,MAAa,EAAkC,OAAO,OAAO,CAC3D,mBAAoB,cACpB,uBAAwB,yBACxB,uBAAwB,kBACxB,gBAAiB,kBACjB,YAAa,cACb,kBAAmB,oBACnB,gBAAiB,kBACjB,eAAgB,iBAChB,qBAAsB,YACtB,gBAAiB,WACjB,gBAAiB,kBACjB,kCAAmC,oCACnC,qBAAsB,uBACtB,wBAAyB,0BACzB,cAAe,eACjB,CAAC,EAMY,EAAgB,yBAEhB,EAAuB,CAClC,eACF,EAMa,EAA4B,CACvC,aAAc,UACd,YAAa,SACb,iBAAkB,oBAClB,yBAA0B,2BAC1B,kBAAmB,qBACnB,mBAAoB,sBACpB,iBAAkB,mBACpB,EAMa,EAAyB,CACpC,aAAc,SACd,YAAa,QACb,iBAAkB,UAClB,yBAA0B,iBAC1B,kBAAmB,WACnB,mBAAoB,YACpB,iBAAkB,SACpB,EAEa,EAAiB,CAC5B,gBAAiB,IACjB,WAAY,GACZ,aAAc,IACd,cAAe,IACf,qBAAsB,GACxB,EAEa,EAAgD,OAAO,OAAO,CAAC,CAAC,EAQhE,EAAgD,OAAO,OAAO,CAAC,CAAC,EAEvE,EAAwB,OAAO,OAAO,CAC1C,YAAa,OAAO,OAAO,CAAC,CAAC,EAC7B,UAAW,OAAO,OAAO,CAAC,CAAC,EAC3B,aAAc,EAChB,CAAC,EAEY,EAAqB,OAAO,OAAO,CAC9C,MAAO,aACP,OAAQ,UACR,SAAU,CACZ,CAAC"}
|
|
@@ -17,7 +17,7 @@ import { Router } from "./Router.js";
|
|
|
17
17
|
*
|
|
18
18
|
* router.start('/');
|
|
19
19
|
*/
|
|
20
|
-
declare const createRouter: <Dependencies extends DefaultDependencies = DefaultDependencies>(routes?: Route<Dependencies>[], options?: Partial<Options
|
|
20
|
+
declare const createRouter: <Dependencies extends DefaultDependencies = DefaultDependencies>(routes?: Route<Dependencies>[], options?: Partial<Options<Dependencies>>, dependencies?: Dependencies) => Router<Dependencies>;
|
|
21
21
|
//#endregion
|
|
22
22
|
export { createRouter };
|
|
23
23
|
//# sourceMappingURL=createRouter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createRouter.d.ts","names":[],"sources":["../../src/createRouter.ts"],"mappings":";;;;;;;;;;;;;;;;;;;cAsBa,eACX,qBAAqB,sBAAsB,qBAE3C,SAAQ,MAAM,iBACd,UAAS,QAAQ,
|
|
1
|
+
{"version":3,"file":"createRouter.d.ts","names":[],"sources":["../../src/createRouter.ts"],"mappings":";;;;;;;;;;;;;;;;;;;cAsBa,eACX,qBAAqB,sBAAsB,qBAE3C,SAAQ,MAAM,iBACd,UAAS,QAAQ,QAAQ,gBACzB,eAAc,iBACb,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createRouter.js","names":["Router"],"sources":["../../src/createRouter.ts"],"sourcesContent":["// packages/core/src/createRouter.ts\n\nimport { Router } from \"./Router\";\n\nimport type { DefaultDependencies, Options, Route } from \"./types\";\n\n/**\n * Creates a new router instance.\n *\n * @param routes - Array of route definitions\n * @param options - Router configuration options\n * @param dependencies - Dependencies to inject into the router\n * @returns A new Router instance\n *\n * @example\n * const router = createRouter([\n * { name: 'home', path: '/' },\n * { name: 'users', path: '/users' },\n * ]);\n *\n * router.start('/');\n */\nexport const createRouter = <\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routes: Route<Dependencies>[] = [],\n options: Partial<Options
|
|
1
|
+
{"version":3,"file":"createRouter.js","names":["Router"],"sources":["../../src/createRouter.ts"],"sourcesContent":["// packages/core/src/createRouter.ts\n\nimport { Router } from \"./Router\";\n\nimport type { DefaultDependencies, Options, Route } from \"./types\";\n\n/**\n * Creates a new router instance.\n *\n * @param routes - Array of route definitions\n * @param options - Router configuration options\n * @param dependencies - Dependencies to inject into the router\n * @returns A new Router instance\n *\n * @example\n * const router = createRouter([\n * { name: 'home', path: '/' },\n * { name: 'users', path: '/users' },\n * ]);\n *\n * router.start('/');\n */\nexport const createRouter = <\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routes: Route<Dependencies>[] = [],\n options: Partial<Options<Dependencies>> = {},\n dependencies: Dependencies = {} as Dependencies,\n): Router<Dependencies> => {\n return new Router<Dependencies>(routes, options, dependencies);\n};\n"],"mappings":"+BAsBa,GAGX,EAAgC,CAAC,EACjC,EAA0C,CAAC,EAC3C,EAA6B,CAAC,IAEvB,IAAIA,EAAAA,OAAqB,EAAQ,EAAS,CAAY"}
|
|
@@ -6,9 +6,22 @@ declare class SegmentMatcher {
|
|
|
6
6
|
constructor(options: SegmentMatcherOptions);
|
|
7
7
|
registerTree(node: MatcherInputNode): void;
|
|
8
8
|
match(path: string): MatchResult | undefined;
|
|
9
|
-
buildPath(name: string, params?: Record<string, unknown>, options?: BuildPathOptions): string;
|
|
9
|
+
buildPath(name: string, params?: Record<string, unknown>, search?: Record<string, unknown>, options?: BuildPathOptions): string;
|
|
10
10
|
getSegmentsByName(name: string): readonly MatcherInputNode[] | undefined;
|
|
11
11
|
getMetaByName(name: string): Readonly<Record<string, Record<string, "url" | "query">>> | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* The route's declared query-param names — the SAME registry the query-string
|
|
14
|
+
* build reads (`#buildQueryStringForBuild`), so a consumer classifying keys
|
|
15
|
+
* into channels cannot drift from what this matcher actually prints (#1556).
|
|
16
|
+
*
|
|
17
|
+
* Unlike a walk over {@link getSegmentsByName}, this includes the ROOT node's
|
|
18
|
+
* `?`-declarations (`setRootPath("?a&b")` — how persistent-params declares its
|
|
19
|
+
* keys): the root is captured in `#rootQueryParams` at `registerTree` and is
|
|
20
|
+
* deliberately NOT part of `matchSegments`, so a segment walk silently misses
|
|
21
|
+
* it. Path-slot collisions (`/items/:id?id`) are NOT filtered here — the
|
|
22
|
+
* caller owns that policy (core subtracts its `urlParams`, #843 / #1549).
|
|
23
|
+
*/
|
|
24
|
+
getDeclaredQueryParams(name: string): readonly string[] | undefined;
|
|
12
25
|
hasRoute(name: string): boolean;
|
|
13
26
|
}
|
|
14
27
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentMatcher.d.ts","names":[],"sources":["../../../../src/engine/path-matcher/SegmentMatcher.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"SegmentMatcher.d.ts","names":[],"sources":["../../../../src/engine/path-matcher/SegmentMatcher.ts"],"mappings":";;cA0Ca;;MACP,WAAW;EA2BH,YAAA,SAAS;EAiBrB,aAAa,MAAM;EAkBnB,MAAM,eAAe;EAqDrB,UACE,cACA,SAAS,yBACT,SAAS,yBACT,UAAU;EAyBZ,kBAAkB,wBAAwB;EAO1C,cACE,eACC,SAAS,eAAe;;;;;;;;;;;;;EAgB3B,uBAAuB;EAIvB,SAAS"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./encoding.js"),t=require("./pathUtils.js"),n=require("./percentEncoding.js"),r=require("./registration/index.js");function i(e){return typeof e==`string`?e:typeof e==`object`?JSON.stringify(e):String(e)}
|
|
1
|
+
const e=require("./encoding.js"),t=require("./pathUtils.js"),n=require("./percentEncoding.js"),r=require("./registration/index.js");function i(e){return typeof e==`string`?e:typeof e==`object`?JSON.stringify(e):String(e)}const a=Object.freeze({});var o=class{get options(){return this.#e}#e;#t=t.createSegmentNode();#n=new Map;#r=new Map;#i={cleanPath:``,normalized:``,queryString:void 0};#a=[];#o=``;#s;#c;constructor(t){this.#e={caseSensitive:t.caseSensitive??!0,strictTrailingSlash:t.strictTrailingSlash??!1,strictQueryParams:t.strictQueryParams??!1,urlParamsEncoding:t.urlParamsEncoding??`default`,parseQueryString:t.parseQueryString,buildQueryString:t.buildQueryString},this.#s=this.#e.caseSensitive,this.#c=this.#e.urlParamsEncoding===`none`?null:e.DECODING_METHODS[this.#e.urlParamsEncoding]}registerTree(e){this.#a=e.paramMeta.queryParams,r.registerNode({root:this.#t,options:this.#e,routesByName:this.#n,staticCache:this.#r,rootQueryParams:this.#a,rootUrlParams:e.paramMeta.urlParams},e,``,[],null)}match(e){if(!this.#f(e))return;let{cleanPath:t,normalized:n,queryString:r}=this.#i,i=this.#s?n:n.toLowerCase(),a=this.#r.get(i);if(a)return this.#e.strictTrailingSlash&&!this.#g(t,a)?void 0:r===void 0&&a.cachedResult?a.cachedResult:this.#m(a,{},r);let o={},s=this.#_(n,o);if(s&&!(this.#e.strictTrailingSlash&&!this.#g(t,s))&&this.#b(o))return this.#m(s,o,r)}buildPath(e,t,n,r){let i=this.#n.get(e);if(!i)throw Error(`[SegmentMatcher.buildPath] '${e}' is not defined`);let a=this.#l(i,t),o=this.#u(a,r?.trailingSlash),s=this.#d(i,n??t,r?.queryParamsMode);return o+(s?`?${s}`:``)}getSegmentsByName(e){return this.#n.get(e)?.matchSegments}getMetaByName(e){return this.#n.get(e)?.meta}getDeclaredQueryParams(e){return this.#n.get(e)?.declaredQueryParams}hasRoute(e){return this.#n.has(e)}#l(e,t){let n=e.buildStaticParts,r=e.buildParamSlots;if(r.length===0)return n[0];let a=n[0];for(let[e,o]of r.entries()){let r=t?.[o.paramName];if(r==null)throw Error(`[SegmentMatcher.buildPath] Missing required param '${o.paramName}'`);if(r===``)throw Error(`[SegmentMatcher.buildPath] Missing required param '${o.paramName}' (empty string)`);let s=o.encoder(i(r));a+=s+n[e+1]}return a}#u(e,t){return t===`always`&&!e.endsWith(`/`)?`${e}/`:t===`never`&&e!==`/`&&e.endsWith(`/`)?e.slice(0,-1):e}#d(e,t,n){if(!t||e.declaredQueryParams.length===0&&n!==`loose`)return``;let r={},i=!1;for(let n of e.declaredQueryParams)n in t&&(r[n]=t[n],i=!0);if(n===`loose`)for(let n in t)!Object.hasOwn(t,n)||e.declaredQueryParamsSet.has(n)||e.buildParamNamesSet.has(n)||(r[n]=t[n],i=!0);return i?this.#e.buildQueryString(r):``}#f(e){if(e===``&&(e=`/`),e.codePointAt(0)!==47)return!1;let n=this.#p(e);if(n===-2)return!1;n===-3&&(e=this.#o);let r=n>=0?e.slice(0,n):e,i=n>=0?e.slice(n+1):void 0;if(i!==void 0){let e=i.indexOf(`#`);e!==-1&&(i=i.slice(0,e))}let a=t.normalizeTrailingSlash(r);return this.#i.cleanPath=r,this.#i.normalized=a,this.#i.queryString=i,!0}#p(e){let t=!1;for(let n=0;n<e.length;n++){let r=e.codePointAt(n);if(r===35)return this.#o=e.slice(0,n),-3;if(r===63)return n;if(r>=128)return-2;if(r===47){if(t)return-2;t=!0}else t=!1}return-1}#m(e,t,n){let r=a;if(n!==void 0){let t=this.#h(e,n);if(t===void 0)return;r=t}return{segments:e.matchSegments,params:t,search:r,meta:e.meta}}#h(e,t){let n;try{n=this.#e.parseQueryString(t)}catch{return}if(this.#e.strictQueryParams){let t=e.declaredQueryParamsSet;for(let e in n)if(!t.has(e))return}return n}#g(e,t){return(e.length>1&&e.endsWith(`/`))===t.hasTrailingSlash}#_(e,t){return e.length===1?this.#t.slashChildRoute??this.#t.route:this.#v(this.#t,e,1,t)}#v(e,t,n,r){let i=e,a=t.length,o=this.#s;for(;n<=a;){let e=t.indexOf(`/`,n),s=e===-1?a:e,c=t.slice(n,s),l=o?c:c.toLowerCase(),u;if(l in i.staticChildren)u=i.staticChildren[l];else if(i.paramChild){let e=i.paramChild;if(i.splatChild!==void 0){let a={[e.name]:c},o=this.#v(e.node,t,s+1,a);return o===void 0?this.#y(i.splatChild,t,n,r):(Object.assign(r,a),o)}u=e.node,r[e.name]=c}else if(i.splatChild)return this.#y(i.splatChild,t,n,r);else return;i=u,n=s+1}return i.slashChildRoute??i.route}#y(e,t,n,r){let i=e.node;if(!i.hasChildren)return r[e.name]=t.slice(n),i.route;let a={},o=this.#v(i,t,n,a);return o?(Object.assign(r,a),o):(r[e.name]=t.slice(n),i.route)}#b(e){let t=this.#c;if(!t)return!0;for(let r in e){let i=e[r];if(i.includes(`%`)){if(!n.validatePercentEncoding(i))return!1;try{e[r]=t(i)}catch{return!1}}}return!0}};exports.SegmentMatcher=o;
|
|
2
2
|
//# sourceMappingURL=SegmentMatcher.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentMatcher.js","names":["#options","#root","createSegmentNode","#routesByName","#staticCache","#prepared","#caseSensitive","#decode","DECODING_METHODS","#rootQueryParams","#preparePath","#checkTrailingSlash","#buildResult","#traverse","#decodeParams","#buildUrlPath","#applyTrailingSlash","#buildQueryStringForBuild","#scanPath","#scanTruncated","normalizeTrailingSlash","#mergeQueryParams","#traverseFrom","#matchSplat","validatePercentEncoding"],"sources":["../../../../src/engine/path-matcher/SegmentMatcher.ts"],"sourcesContent":["import { DECODING_METHODS } from \"./encoding\";\nimport { createSegmentNode, normalizeTrailingSlash } from \"./pathUtils\";\nimport { validatePercentEncoding } from \"./percentEncoding\";\nimport { registerNode } from \"./registration\";\n\nimport type {\n BuildPathOptions,\n CompiledRoute,\n MatcherInputNode,\n MatchResult,\n ResolvedMatcherOptions,\n SegmentMatcherOptions,\n SegmentNode,\n} from \"./types\";\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Coerces a route param value (typed `unknown`, contractually primitive) to\n * the string the encoder receives. Objects are JSON-stringified. */\nfunction stringifyParamValue(value: unknown): string {\n // Stryker disable next-line BlockStatement: equivalent — String(value) === value for a string, so removing the early return is identical\n if (typeof value === \"string\") {\n return value;\n }\n\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- route params are typed `unknown` but contractually primitive\n return String(value);\n}\n\n/** Folds a parsed query param into the accumulator as an own DATA property. A\n * plain `params[name] = value` invokes the inherited `__proto__` accessor for the\n * literal key `\"__proto__\"` — so the param silently vanishes (string value) or\n * swaps the local prototype (object value), annulling search-params' #855 own-key\n * hardening one layer up. `defineProperty` writes a genuine own entry. (#1293) */\nfunction assignQueryParam(\n params: Record<string, unknown>,\n name: string,\n value: unknown,\n): void {\n if (name === \"__proto__\") {\n Object.defineProperty(params, name, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n } else {\n params[name] = value;\n }\n}\n\n// =============================================================================\n// SegmentMatcher Class\n// =============================================================================\n\nexport class SegmentMatcher {\n get options(): ResolvedMatcherOptions {\n return this.#options;\n }\n\n readonly #options: ResolvedMatcherOptions;\n\n readonly #root: SegmentNode = createSegmentNode();\n readonly #routesByName = new Map<string, CompiledRoute>();\n readonly #staticCache = new Map<string, CompiledRoute>();\n\n // H1: Reusable object eliminates tuple allocation per match() call\n readonly #prepared = {\n // Stryker disable next-line StringLiteral: equivalent — #prepared.cleanPath is overwritten by #preparePath before any read (dead initializer)\n cleanPath: \"\",\n // Stryker disable next-line StringLiteral: equivalent — #prepared.normalized is overwritten by #preparePath before any read (dead initializer)\n normalized: \"\",\n queryString: undefined as string | undefined,\n };\n\n // Stryker disable next-line ArrayDeclaration: equivalent — #rootQueryParams is overwritten by registerTree before any read (dead initializer)\n #rootQueryParams: readonly string[] = [];\n // Stryker disable next-line StringLiteral: equivalent — #scanTruncated is set by #scanPath before it is read (dead initializer)\n #scanTruncated = \"\";\n\n readonly #caseSensitive: boolean;\n readonly #decode: ((param: string) => string) | null;\n\n constructor(options: SegmentMatcherOptions) {\n this.#options = {\n caseSensitive: options.caseSensitive ?? true,\n strictTrailingSlash: options.strictTrailingSlash ?? false,\n strictQueryParams: options.strictQueryParams ?? false,\n urlParamsEncoding: options.urlParamsEncoding ?? \"default\",\n parseQueryString: options.parseQueryString,\n buildQueryString: options.buildQueryString,\n };\n\n this.#caseSensitive = this.#options.caseSensitive;\n this.#decode =\n this.#options.urlParamsEncoding === \"none\"\n ? null\n : DECODING_METHODS[this.#options.urlParamsEncoding];\n }\n\n registerTree(node: MatcherInputNode): void {\n this.#rootQueryParams = node.paramMeta.queryParams;\n registerNode(\n {\n root: this.#root,\n options: this.#options,\n routesByName: this.#routesByName,\n staticCache: this.#staticCache,\n rootQueryParams: this.#rootQueryParams,\n },\n node,\n \"\",\n [],\n null,\n );\n }\n\n match(path: string): MatchResult | undefined {\n if (!this.#preparePath(path)) {\n return undefined;\n }\n\n const { cleanPath, normalized, queryString } = this.#prepared;\n\n const cacheKey = this.#caseSensitive\n ? normalized\n : // Stryker disable next-line MethodExpression: equivalent — the case-insensitive cache key only governs a hit; a miss falls through to #traverse (also case-insensitive), same result (proven by injection)\n normalized.toLowerCase();\n const cached = this.#staticCache.get(cacheKey);\n\n // Stryker disable next-line BlockStatement: equivalent — emptying the cache-hit block routes through #traverse, which resolves the same route (cache is a pure optimization)\n if (cached) {\n if (\n this.#options.strictTrailingSlash &&\n !this.#checkTrailingSlash(cleanPath, cached)\n ) {\n return undefined;\n }\n\n // Stryker disable next-line BlockStatement: equivalent — #buildResult recomputes cached.cachedResult identically (cache short-circuit)\n if (queryString === undefined && cached.cachedResult) {\n return cached.cachedResult;\n }\n\n return this.#buildResult(cached, {}, queryString);\n }\n\n const params: Record<string, string> = {};\n const route = this.#traverse(normalized, params);\n\n if (!route) {\n return undefined;\n }\n\n if (\n this.#options.strictTrailingSlash &&\n !this.#checkTrailingSlash(cleanPath, route)\n ) {\n return undefined;\n }\n\n // Decode the captured params. `match()` must never throw — a malformed\n // percent sequence (#737) makes this return false → the URL is unmatched.\n if (!this.#decodeParams(params)) {\n return undefined;\n }\n\n return this.#buildResult(route, params, queryString);\n }\n\n buildPath(\n name: string,\n params?: Record<string, unknown>,\n options?: BuildPathOptions,\n ): string {\n const route = this.#routesByName.get(name);\n\n if (!route) {\n throw new Error(`[SegmentMatcher.buildPath] '${name}' is not defined`);\n }\n\n const path = this.#buildUrlPath(route, params);\n const finalPath = this.#applyTrailingSlash(path, options?.trailingSlash);\n const queryString = this.#buildQueryStringForBuild(\n route,\n params,\n options?.queryParamsMode,\n );\n\n return finalPath + (queryString ? `?${queryString}` : \"\");\n }\n\n getSegmentsByName(name: string): readonly MatcherInputNode[] | undefined {\n // Derived from #routesByName — `compiled.matchSegments` is the same frozen\n // array a dedicated #segmentsByName index would store, so the index was\n // pure duplication (#1010).\n return this.#routesByName.get(name)?.matchSegments;\n }\n\n getMetaByName(\n name: string,\n ): Readonly<Record<string, Record<string, \"url\" | \"query\">>> | undefined {\n return this.#routesByName.get(name)?.meta;\n }\n\n hasRoute(name: string): boolean {\n return this.#routesByName.has(name);\n }\n\n #buildUrlPath(\n route: CompiledRoute,\n params: Record<string, unknown> | undefined,\n ): string {\n const parts = route.buildStaticParts;\n const slots = route.buildParamSlots;\n\n // Stryker disable next-line BlockStatement: equivalent — the general loop returns parts[0] when slots is empty, identical to this fast path\n if (slots.length === 0) {\n return parts[0];\n }\n\n let result = parts[0];\n\n for (const [i, slot] of slots.entries()) {\n const value = params?.[slot.paramName];\n\n // 3-token grammar (M1): every param slot is required — no optional-omit\n // branch. A missing param is an error.\n if (value === undefined || value === null) {\n throw new Error(\n `[SegmentMatcher.buildPath] Missing required param '${slot.paramName}'`,\n );\n }\n\n // #740 item 3: an empty value collapses the segment, silently producing a\n // path that matches the parent route (`buildPath(\"u.p\", {id:\"\"})` →\n // `/users/` → matches `u`). Reject it like a missing param.\n if (value === \"\") {\n throw new Error(\n `[SegmentMatcher.buildPath] Missing required param '${slot.paramName}' (empty string)`,\n );\n }\n\n const encoded = slot.encoder(stringifyParamValue(value));\n\n result += encoded + parts[i + 1];\n }\n\n return result;\n }\n\n #applyTrailingSlash(\n path: string,\n mode: BuildPathOptions[\"trailingSlash\"],\n ): string {\n if (mode === \"always\" && !path.endsWith(\"/\")) {\n return `${path}/`;\n }\n\n /* v8 ignore next 3 -- @preserve: trailing slash may not appear in buildStaticParts; integration-tested via core */\n if (mode === \"never\" && path !== \"/\" && path.endsWith(\"/\")) {\n return path.slice(0, -1);\n }\n\n return path;\n }\n\n #buildQueryStringForBuild(\n route: CompiledRoute,\n params: Record<string, unknown> | undefined,\n queryParamsMode: BuildPathOptions[\"queryParamsMode\"],\n ): string {\n if (!params) {\n return \"\";\n }\n\n // Stryker disable next-line BlockStatement: equivalent — the downstream !hasKeys guard also returns '' for a route with no declared query params\n if (route.declaredQueryParams.length === 0 && queryParamsMode !== \"loose\") {\n return \"\";\n }\n\n const queryObj: Record<string, unknown> = {};\n // Stryker disable next-line BooleanLiteral: equivalent — buildQueryString({}) === '' so the hasKeys initial value is unobservable when no keys are added\n let hasKeys = false;\n\n for (const name of route.declaredQueryParams) {\n // Stryker disable next-line BlockStatement: equivalent — buildQueryString strips undefined, so adding absent declared keys instead of continue changes nothing\n if (!(name in params)) {\n continue;\n }\n\n queryObj[name] = params[name];\n hasKeys = true;\n }\n\n if (queryParamsMode === \"loose\") {\n for (const paramKey in params) {\n if (!(\n Object.hasOwn(params, paramKey) &&\n !route.declaredQueryParamsSet.has(paramKey) &&\n !route.buildParamNamesSet.has(paramKey)\n )) {\n continue;\n }\n\n queryObj[paramKey] = params[paramKey];\n hasKeys = true;\n }\n }\n\n // Stryker disable next-line BlockStatement: equivalent — buildQueryString({}) === '' so removing the !hasKeys early return yields the same ''\n if (!hasKeys) {\n return \"\";\n }\n\n return this.#options.buildQueryString(queryObj);\n }\n\n // H2: Single-pass scanner — replaces 4 separate scans (indexOf(\"#\"), regex unicode, indexOf(\"?\"), includes(\"//\"))\n #preparePath(path: string): boolean {\n if (path === \"\") {\n path = \"/\";\n }\n\n if (path.codePointAt(0) !== 0x2f /* / */) {\n return false;\n }\n\n const qIdx = this.#scanPath(path);\n\n if (qIdx === -2) {\n return false;\n }\n\n if (qIdx === -3) {\n path = this.#scanTruncated;\n }\n\n const pathPart = qIdx >= 0 ? path.slice(0, qIdx) : path;\n let queryString = qIdx >= 0 ? path.slice(qIdx + 1) : undefined;\n\n // #842: a fragment (`#…`) AFTER the query separator is not seen by\n // #scanPath (it returns at the first `?`), so it would otherwise be folded\n // into the query string and parsed into a param value (e.g. `?a=1#frag` →\n // `a=\"1#frag\"`). A fragment is everything after the first `#` in the whole\n // URL and must be stripped before query parsing — a native indexOf on the\n // (short) query substring, only when a query exists, is ~free (a `#` BEFORE\n // the `?` is already handled by #scanPath via the -3 truncation branch).\n if (queryString !== undefined) {\n const hashIdx = queryString.indexOf(\"#\");\n\n if (hashIdx !== -1) {\n queryString = queryString.slice(0, hashIdx);\n }\n }\n\n const normalized = normalizeTrailingSlash(pathPart);\n\n this.#prepared.cleanPath = pathPart;\n this.#prepared.normalized = normalized;\n this.#prepared.queryString = queryString;\n\n return true;\n }\n\n // Returns: qIdx >= 0 (found ?), -1 (no ? or #), -2 (invalid), -3 (truncated at #, result in #scanTruncated)\n #scanPath(path: string): number {\n let prevSlash = false;\n\n for (let i = 0; i < path.length; i++) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- bounds-checked by loop condition\n const ch = path.codePointAt(i)!;\n\n if (ch === 0x23 /* # */) {\n this.#scanTruncated = path.slice(0, i);\n\n return -3;\n }\n\n if (ch === 0x3f /* ? */) {\n return i;\n }\n\n if (ch >= 0x80) {\n return -2;\n }\n\n if (ch === 0x2f /* / */) {\n if (prevSlash) {\n return -2;\n }\n\n prevSlash = true;\n } else {\n prevSlash = false;\n }\n }\n\n return -1;\n }\n\n #buildResult(\n route: CompiledRoute,\n params: Record<string, unknown>,\n queryString: string | undefined,\n ): MatchResult | undefined {\n if (\n queryString !== undefined &&\n !this.#mergeQueryParams(route, params, queryString)\n ) {\n return undefined;\n }\n\n return {\n segments: route.matchSegments,\n params,\n meta: route.meta,\n };\n }\n\n // Parses the query string and folds it into `params`. Returns false (→ match\n // yields undefined) when the URL is unmatchable: the injected parser threw, or\n // strict mode saw an undeclared key.\n //\n // Precedence (#843, INVARIANTS Matching #25): query params are merged into the\n // SAME object that already holds the path params, so a query key equal to a\n // path-param name OVERWRITES the path value (`match(\"/u/5?id=9\")` → `{id:\"9\"}`).\n // Intentional and documented: `buildPath` never emits a path param as a query\n // key, so the build→match roundtrip is unaffected; the collision only arises\n // for hand-crafted/adversarial URLs where a query shadows a path segment.\n #mergeQueryParams(\n route: CompiledRoute,\n params: Record<string, unknown>,\n queryString: string,\n ): boolean {\n let queryParams: Record<string, unknown>;\n\n try {\n queryParams = this.#options.parseQueryString(queryString);\n } catch {\n // The injected query parser decodes percent-encoding too, so the same\n // valid-hex/invalid-UTF-8 sequence that breaks path params (e.g.\n // `?x=%E0%41`) makes it throw a URIError. `match()` must never throw —\n // treat the whole URL as unmatched so the router resolves to\n // UNKNOWN_ROUTE instead of crashing on start() (#737).\n return false;\n }\n\n if (this.#options.strictQueryParams) {\n const declared = route.declaredQueryParamsSet;\n\n for (const key in queryParams) {\n if (!declared.has(key)) {\n return false;\n }\n\n assignQueryParam(params, key, queryParams[key]);\n }\n } else {\n for (const key in queryParams) {\n assignQueryParam(params, key, queryParams[key]);\n }\n }\n\n return true;\n }\n\n #checkTrailingSlash(cleanPath: string, route: CompiledRoute): boolean {\n const inputHasSlash = cleanPath.length > 1 && cleanPath.endsWith(\"/\");\n\n return inputHasSlash === route.hasTrailingSlash;\n }\n\n #traverse(\n path: string,\n params: Record<string, string>,\n ): CompiledRoute | undefined {\n /* v8 ignore start -- @preserve: root \"/\" is always in #staticCache */\n if (path.length === 1) {\n return this.#root.slashChildRoute ?? this.#root.route;\n }\n /* v8 ignore stop */\n\n return this.#traverseFrom(this.#root, path, 1, params);\n }\n\n // The core match hot loop. The #1288 param+splat junction is inlined here\n // rather than extracted into a helper, because a per-param helper call\n // regresses the common single-param path ~5% (spike-measured).\n // eslint-disable-next-line sonarjs/cognitive-complexity -- inlined #1288 junction (see above): the static/param/splat dispatch is deliberately one function for the hot path\n #traverseFrom(\n startNode: SegmentNode,\n path: string,\n start: number,\n params: Record<string, string>,\n ): CompiledRoute | undefined {\n let node = startNode;\n const length = path.length;\n const caseSensitive = this.#caseSensitive;\n\n while (start <= length) {\n const end = path.indexOf(\"/\", start);\n const segmentEnd = end === -1 ? length : end;\n const segment = path.slice(start, segmentEnd);\n\n const lookupKey = caseSensitive ? segment : segment.toLowerCase();\n let next: SegmentNode;\n\n if (lookupKey in node.staticChildren) {\n next = node.staticChildren[lookupKey];\n } else if (node.paramChild) {\n const pc = node.paramChild;\n\n // #1288: validated sub-traverse on a param+splat junction. The param\n // branch is tried on a scratch object and commits ONLY if it structurally\n // completes (\"param wins if its branch can complete\", INVARIANTS Matching #8);\n // otherwise the splat sibling captures. Junction-free param hops (no splat\n // sibling) never enter this block — the common single-param path below is\n // untouched.\n if (node.splatChild !== undefined) {\n const childParams: Record<string, string> = { [pc.name]: segment };\n\n const taken = this.#traverseFrom(\n pc.node,\n path,\n segmentEnd + 1,\n childParams,\n );\n\n if (taken !== undefined) {\n Object.assign(params, childParams);\n\n return taken;\n }\n\n return this.#matchSplat(node.splatChild, path, start, params);\n }\n\n next = pc.node;\n params[pc.name] = segment;\n } else if (node.splatChild) {\n return this.#matchSplat(node.splatChild, path, start, params);\n } else {\n return undefined;\n }\n\n node = next;\n start = segmentEnd + 1;\n }\n\n return node.slashChildRoute ?? node.route;\n }\n\n #matchSplat(\n splatChild: { node: SegmentNode; name: string },\n path: string,\n start: number,\n params: Record<string, string>,\n ): CompiledRoute | undefined {\n const sn = splatChild.node;\n\n // Stryker disable next-line BlockStatement: equivalent — leaf-splat fast path; the #traverseFrom fallback returns the same route+params (proven via hasChildren injection)\n if (!sn.hasChildren) {\n params[splatChild.name] = path.slice(start);\n\n return sn.route;\n }\n\n const childParams: Record<string, string> = {};\n const specific = this.#traverseFrom(sn, path, start, childParams);\n\n // #1288: a structurally-complete specific child wins over the wildcard\n // capture; otherwise the splat captures the rest of the path.\n if (specific) {\n Object.assign(params, childParams);\n\n return specific;\n }\n\n params[splatChild.name] = path.slice(start);\n\n return sn.route;\n }\n\n #decodeParams(params: Record<string, string>): boolean {\n const decode = this.#decode;\n\n if (!decode) {\n return true;\n }\n\n for (const key in params) {\n const value = params[key];\n\n // Stryker disable next-line StringLiteral,BlockStatement: equivalent — includes('%') is a skip-optimization; decoding a %-free value is a no-op, so always-proceeding is identical\n if (!value.includes(\"%\")) {\n continue;\n }\n\n // Stryker disable next-line BlockStatement: equivalent — redundant with the try/catch below — decodeURIComponent throws on the same invalid-% input (proven by injection)\n if (!validatePercentEncoding(value)) {\n return false;\n }\n\n try {\n params[key] = decode(value);\n } catch {\n // `validatePercentEncoding` only checks `%XX` *syntax*. A sequence that\n // is syntactically valid but semantically invalid UTF-8 (e.g. `%E0%41`,\n // `%C0%80`, `%FF`) still makes `decodeURIComponent`/`decodeURI` throw a\n // URIError. `match()` must never throw — reject the path so the router\n // resolves to UNKNOWN_ROUTE instead of crashing on start() (#737).\n return false;\n }\n }\n\n return true;\n }\n}\n\nexport { createSegmentNode } from \"./pathUtils\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SegmentMatcher.js","names":["#options","#root","createSegmentNode","#routesByName","#staticCache","#prepared","#caseSensitive","#decode","DECODING_METHODS","#rootQueryParams","#preparePath","#checkTrailingSlash","#buildResult","#traverse","#decodeParams","#buildUrlPath","#applyTrailingSlash","#buildQueryStringForBuild","#scanPath","#scanTruncated","normalizeTrailingSlash","#parseSearch","#traverseFrom","#matchSplat","validatePercentEncoding"],"sources":["../../../../src/engine/path-matcher/SegmentMatcher.ts"],"sourcesContent":["import { DECODING_METHODS } from \"./encoding\";\nimport { createSegmentNode, normalizeTrailingSlash } from \"./pathUtils\";\nimport { validatePercentEncoding } from \"./percentEncoding\";\nimport { registerNode } from \"./registration\";\n\nimport type {\n BuildPathOptions,\n CompiledRoute,\n MatcherInputNode,\n MatchResult,\n ResolvedMatcherOptions,\n SegmentMatcherOptions,\n SegmentNode,\n} from \"./types\";\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Coerces a route param value (typed `unknown`, contractually primitive) to\n * the string the encoder receives. Objects are JSON-stringified. */\nfunction stringifyParamValue(value: unknown): string {\n // Stryker disable next-line BlockStatement: equivalent — String(value) === value for a string, so removing the early return is identical\n if (typeof value === \"string\") {\n return value;\n }\n\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- route params are typed `unknown` but contractually primitive\n return String(value);\n}\n\n/** Shared frozen empty query object reused for every query-less match. */\nconst EMPTY_SEARCH: Readonly<Record<string, unknown>> = Object.freeze({});\n\n// =============================================================================\n// SegmentMatcher Class\n// =============================================================================\n\nexport class SegmentMatcher {\n get options(): ResolvedMatcherOptions {\n return this.#options;\n }\n\n readonly #options: ResolvedMatcherOptions;\n\n readonly #root: SegmentNode = createSegmentNode();\n readonly #routesByName = new Map<string, CompiledRoute>();\n readonly #staticCache = new Map<string, CompiledRoute>();\n\n // H1: Reusable object eliminates tuple allocation per match() call\n readonly #prepared = {\n // Stryker disable next-line StringLiteral: equivalent — #prepared.cleanPath is overwritten by #preparePath before any read (dead initializer)\n cleanPath: \"\",\n // Stryker disable next-line StringLiteral: equivalent — #prepared.normalized is overwritten by #preparePath before any read (dead initializer)\n normalized: \"\",\n queryString: undefined as string | undefined,\n };\n\n // Stryker disable next-line ArrayDeclaration: equivalent — #rootQueryParams is overwritten by registerTree before any read (dead initializer)\n #rootQueryParams: readonly string[] = [];\n // Stryker disable next-line StringLiteral: equivalent — #scanTruncated is set by #scanPath before it is read (dead initializer)\n #scanTruncated = \"\";\n\n readonly #caseSensitive: boolean;\n readonly #decode: ((param: string) => string) | null;\n\n constructor(options: SegmentMatcherOptions) {\n this.#options = {\n caseSensitive: options.caseSensitive ?? true,\n strictTrailingSlash: options.strictTrailingSlash ?? false,\n strictQueryParams: options.strictQueryParams ?? false,\n urlParamsEncoding: options.urlParamsEncoding ?? \"default\",\n parseQueryString: options.parseQueryString,\n buildQueryString: options.buildQueryString,\n };\n\n this.#caseSensitive = this.#options.caseSensitive;\n this.#decode =\n this.#options.urlParamsEncoding === \"none\"\n ? null\n : DECODING_METHODS[this.#options.urlParamsEncoding];\n }\n\n registerTree(node: MatcherInputNode): void {\n this.#rootQueryParams = node.paramMeta.queryParams;\n registerNode(\n {\n root: this.#root,\n options: this.#options,\n routesByName: this.#routesByName,\n staticCache: this.#staticCache,\n rootQueryParams: this.#rootQueryParams,\n rootUrlParams: node.paramMeta.urlParams,\n },\n node,\n \"\",\n [],\n null,\n );\n }\n\n match(path: string): MatchResult | undefined {\n if (!this.#preparePath(path)) {\n return undefined;\n }\n\n const { cleanPath, normalized, queryString } = this.#prepared;\n\n const cacheKey = this.#caseSensitive\n ? normalized\n : // Stryker disable next-line MethodExpression: equivalent — the case-insensitive cache key only governs a hit; a miss falls through to #traverse (also case-insensitive), same result (proven by injection)\n normalized.toLowerCase();\n const cached = this.#staticCache.get(cacheKey);\n\n // Stryker disable next-line BlockStatement: equivalent — emptying the cache-hit block routes through #traverse, which resolves the same route (cache is a pure optimization)\n if (cached) {\n if (\n this.#options.strictTrailingSlash &&\n !this.#checkTrailingSlash(cleanPath, cached)\n ) {\n return undefined;\n }\n\n // Stryker disable next-line BlockStatement: equivalent — #buildResult recomputes cached.cachedResult identically (cache short-circuit)\n if (queryString === undefined && cached.cachedResult) {\n return cached.cachedResult;\n }\n\n return this.#buildResult(cached, {}, queryString);\n }\n\n const params: Record<string, string> = {};\n const route = this.#traverse(normalized, params);\n\n if (!route) {\n return undefined;\n }\n\n if (\n this.#options.strictTrailingSlash &&\n !this.#checkTrailingSlash(cleanPath, route)\n ) {\n return undefined;\n }\n\n // Decode the captured params. `match()` must never throw — a malformed\n // percent sequence (#737) makes this return false → the URL is unmatched.\n if (!this.#decodeParams(params)) {\n return undefined;\n }\n\n return this.#buildResult(route, params, queryString);\n }\n\n buildPath(\n name: string,\n params?: Record<string, unknown>,\n search?: Record<string, unknown>,\n options?: BuildPathOptions,\n ): string {\n const route = this.#routesByName.get(name);\n\n if (!route) {\n throw new Error(`[SegmentMatcher.buildPath] '${name}' is not defined`);\n }\n\n const path = this.#buildUrlPath(route, params);\n const finalPath = this.#applyTrailingSlash(path, options?.trailingSlash);\n // Search-aware (RFC-4 M2 / #1548): when an explicit `search` bag is passed,\n // the query string is built from it; the path comes from `params`. So a\n // colliding name (`/items/:id?id` with `buildPath(\"items\", {id:5}, {id:7})`)\n // emits `/items/5?id=7` — path wins its slot, query wins its own (the killed\n // #843 precedence). A v1 caller passes no `search`, so `search ?? params`\n // falls back to extracting the query half from the single bag, unchanged.\n const queryString = this.#buildQueryStringForBuild(\n route,\n search ?? params,\n options?.queryParamsMode,\n );\n\n return finalPath + (queryString ? `?${queryString}` : \"\");\n }\n\n getSegmentsByName(name: string): readonly MatcherInputNode[] | undefined {\n // Derived from #routesByName — `compiled.matchSegments` is the same frozen\n // array a dedicated #segmentsByName index would store, so the index was\n // pure duplication (#1010).\n return this.#routesByName.get(name)?.matchSegments;\n }\n\n getMetaByName(\n name: string,\n ): Readonly<Record<string, Record<string, \"url\" | \"query\">>> | undefined {\n return this.#routesByName.get(name)?.meta;\n }\n\n /**\n * The route's declared query-param names — the SAME registry the query-string\n * build reads (`#buildQueryStringForBuild`), so a consumer classifying keys\n * into channels cannot drift from what this matcher actually prints (#1556).\n *\n * Unlike a walk over {@link getSegmentsByName}, this includes the ROOT node's\n * `?`-declarations (`setRootPath(\"?a&b\")` — how persistent-params declares its\n * keys): the root is captured in `#rootQueryParams` at `registerTree` and is\n * deliberately NOT part of `matchSegments`, so a segment walk silently misses\n * it. Path-slot collisions (`/items/:id?id`) are NOT filtered here — the\n * caller owns that policy (core subtracts its `urlParams`, #843 / #1549).\n */\n getDeclaredQueryParams(name: string): readonly string[] | undefined {\n return this.#routesByName.get(name)?.declaredQueryParams;\n }\n\n hasRoute(name: string): boolean {\n return this.#routesByName.has(name);\n }\n\n #buildUrlPath(\n route: CompiledRoute,\n params: Record<string, unknown> | undefined,\n ): string {\n const parts = route.buildStaticParts;\n const slots = route.buildParamSlots;\n\n // Stryker disable next-line BlockStatement: equivalent — the general loop returns parts[0] when slots is empty, identical to this fast path\n if (slots.length === 0) {\n return parts[0];\n }\n\n let result = parts[0];\n\n for (const [i, slot] of slots.entries()) {\n const value = params?.[slot.paramName];\n\n // 3-token grammar (M1): every param slot is required — no optional-omit\n // branch. A missing param is an error.\n if (value === undefined || value === null) {\n throw new Error(\n `[SegmentMatcher.buildPath] Missing required param '${slot.paramName}'`,\n );\n }\n\n // #740 item 3: an empty value collapses the segment, silently producing a\n // path that matches the parent route (`buildPath(\"u.p\", {id:\"\"})` →\n // `/users/` → matches `u`). Reject it like a missing param.\n if (value === \"\") {\n throw new Error(\n `[SegmentMatcher.buildPath] Missing required param '${slot.paramName}' (empty string)`,\n );\n }\n\n const encoded = slot.encoder(stringifyParamValue(value));\n\n result += encoded + parts[i + 1];\n }\n\n return result;\n }\n\n #applyTrailingSlash(\n path: string,\n mode: BuildPathOptions[\"trailingSlash\"],\n ): string {\n if (mode === \"always\" && !path.endsWith(\"/\")) {\n return `${path}/`;\n }\n\n /* v8 ignore next 3 -- @preserve: trailing slash may not appear in buildStaticParts; integration-tested via core */\n if (mode === \"never\" && path !== \"/\" && path.endsWith(\"/\")) {\n return path.slice(0, -1);\n }\n\n return path;\n }\n\n #buildQueryStringForBuild(\n route: CompiledRoute,\n params: Record<string, unknown> | undefined,\n queryParamsMode: BuildPathOptions[\"queryParamsMode\"],\n ): string {\n if (!params) {\n return \"\";\n }\n\n // Stryker disable next-line BlockStatement: equivalent — the downstream !hasKeys guard also returns '' for a route with no declared query params\n if (route.declaredQueryParams.length === 0 && queryParamsMode !== \"loose\") {\n return \"\";\n }\n\n const queryObj: Record<string, unknown> = {};\n // Stryker disable next-line BooleanLiteral: equivalent — buildQueryString({}) === '' so the hasKeys initial value is unobservable when no keys are added\n let hasKeys = false;\n\n for (const name of route.declaredQueryParams) {\n // Stryker disable next-line BlockStatement: equivalent — buildQueryString strips undefined, so adding absent declared keys instead of continue changes nothing\n if (!(name in params)) {\n continue;\n }\n\n queryObj[name] = params[name];\n hasKeys = true;\n }\n\n if (queryParamsMode === \"loose\") {\n for (const paramKey in params) {\n if (\n !Object.hasOwn(params, paramKey) ||\n route.declaredQueryParamsSet.has(paramKey) ||\n route.buildParamNamesSet.has(paramKey)\n ) {\n continue;\n }\n\n queryObj[paramKey] = params[paramKey];\n hasKeys = true;\n }\n }\n\n // Stryker disable next-line BlockStatement: equivalent — buildQueryString({}) === '' so removing the !hasKeys early return yields the same ''\n if (!hasKeys) {\n return \"\";\n }\n\n return this.#options.buildQueryString(queryObj);\n }\n\n // H2: Single-pass scanner — replaces 4 separate scans (indexOf(\"#\"), regex unicode, indexOf(\"?\"), includes(\"//\"))\n #preparePath(path: string): boolean {\n if (path === \"\") {\n path = \"/\";\n }\n\n if (path.codePointAt(0) !== 0x2f /* / */) {\n return false;\n }\n\n const qIdx = this.#scanPath(path);\n\n if (qIdx === -2) {\n return false;\n }\n\n if (qIdx === -3) {\n path = this.#scanTruncated;\n }\n\n const pathPart = qIdx >= 0 ? path.slice(0, qIdx) : path;\n let queryString = qIdx >= 0 ? path.slice(qIdx + 1) : undefined;\n\n // #842: a fragment (`#…`) AFTER the query separator is not seen by\n // #scanPath (it returns at the first `?`), so it would otherwise be folded\n // into the query string and parsed into a param value (e.g. `?a=1#frag` →\n // `a=\"1#frag\"`). A fragment is everything after the first `#` in the whole\n // URL and must be stripped before query parsing — a native indexOf on the\n // (short) query substring, only when a query exists, is ~free (a `#` BEFORE\n // the `?` is already handled by #scanPath via the -3 truncation branch).\n if (queryString !== undefined) {\n const hashIdx = queryString.indexOf(\"#\");\n\n if (hashIdx !== -1) {\n queryString = queryString.slice(0, hashIdx);\n }\n }\n\n const normalized = normalizeTrailingSlash(pathPart);\n\n this.#prepared.cleanPath = pathPart;\n this.#prepared.normalized = normalized;\n this.#prepared.queryString = queryString;\n\n return true;\n }\n\n // Returns: qIdx >= 0 (found ?), -1 (no ? or #), -2 (invalid), -3 (truncated at #, result in #scanTruncated)\n #scanPath(path: string): number {\n let prevSlash = false;\n\n for (let i = 0; i < path.length; i++) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- bounds-checked by loop condition\n const ch = path.codePointAt(i)!;\n\n if (ch === 0x23 /* # */) {\n this.#scanTruncated = path.slice(0, i);\n\n return -3;\n }\n\n if (ch === 0x3f /* ? */) {\n return i;\n }\n\n if (ch >= 0x80) {\n return -2;\n }\n\n if (ch === 0x2f /* / */) {\n if (prevSlash) {\n return -2;\n }\n\n prevSlash = true;\n } else {\n prevSlash = false;\n }\n }\n\n return -1;\n }\n\n #buildResult(\n route: CompiledRoute,\n params: Record<string, unknown>,\n queryString: string | undefined,\n ): MatchResult | undefined {\n let search: Readonly<Record<string, unknown>> = EMPTY_SEARCH;\n\n if (queryString !== undefined) {\n const parsed = this.#parseSearch(route, queryString);\n\n if (parsed === undefined) {\n return undefined;\n }\n\n // Query goes ONLY into its own channel now (RFC-4 M2 / #1548). `params`\n // stays path-only — the A2 back-compat fold into `params`, and with it\n // the query-overwrites-path precedence (#843), are gone.\n search = parsed;\n }\n\n return {\n segments: route.matchSegments,\n params,\n search,\n meta: route.meta,\n };\n }\n\n // Parses the query string into its OWN object — the query channel (RFC-4 M2 /\n // #1548). Returns undefined (→ match yields undefined) when the URL is\n // unmatchable: the injected parser threw, or strict mode saw an undeclared\n // key. The injected parser (searchParams.ts) already hardens `__proto__` as an\n // own key (#855/#1293), so the parsed object is returned directly — no per-key\n // re-hardening (that folding, and `assignQueryParam`, live on only for the A3\n // back-compat merge into `params`).\n #parseSearch(\n route: CompiledRoute,\n queryString: string,\n ): Record<string, unknown> | undefined {\n let search: Record<string, unknown>;\n\n try {\n search = this.#options.parseQueryString(queryString);\n } catch {\n // The injected query parser decodes percent-encoding too, so the same\n // valid-hex/invalid-UTF-8 sequence that breaks path params (e.g.\n // `?x=%E0%41`) makes it throw a URIError. `match()` must never throw —\n // treat the whole URL as unmatched so the router resolves to\n // UNKNOWN_ROUTE instead of crashing on start() (#737).\n return undefined;\n }\n\n if (this.#options.strictQueryParams) {\n const declared = route.declaredQueryParamsSet;\n\n for (const key in search) {\n if (!declared.has(key)) {\n return undefined;\n }\n }\n }\n\n return search;\n }\n\n #checkTrailingSlash(cleanPath: string, route: CompiledRoute): boolean {\n const inputHasSlash = cleanPath.length > 1 && cleanPath.endsWith(\"/\");\n\n return inputHasSlash === route.hasTrailingSlash;\n }\n\n #traverse(\n path: string,\n params: Record<string, string>,\n ): CompiledRoute | undefined {\n /* v8 ignore start -- @preserve: root \"/\" is always in #staticCache */\n if (path.length === 1) {\n return this.#root.slashChildRoute ?? this.#root.route;\n }\n /* v8 ignore stop */\n\n return this.#traverseFrom(this.#root, path, 1, params);\n }\n\n // The core match hot loop. The #1288 param+splat junction is inlined here\n // rather than extracted into a helper, because a per-param helper call\n // regresses the common single-param path ~5% (spike-measured).\n // eslint-disable-next-line sonarjs/cognitive-complexity -- inlined #1288 junction (see above): the static/param/splat dispatch is deliberately one function for the hot path\n #traverseFrom(\n startNode: SegmentNode,\n path: string,\n start: number,\n params: Record<string, string>,\n ): CompiledRoute | undefined {\n let node = startNode;\n const length = path.length;\n const caseSensitive = this.#caseSensitive;\n\n while (start <= length) {\n const end = path.indexOf(\"/\", start);\n const segmentEnd = end === -1 ? length : end;\n const segment = path.slice(start, segmentEnd);\n\n const lookupKey = caseSensitive ? segment : segment.toLowerCase();\n let next: SegmentNode;\n\n if (lookupKey in node.staticChildren) {\n next = node.staticChildren[lookupKey];\n } else if (node.paramChild) {\n const pc = node.paramChild;\n\n // #1288: validated sub-traverse on a param+splat junction. The param\n // branch is tried on a scratch object and commits ONLY if it structurally\n // completes (\"param wins if its branch can complete\", INVARIANTS Matching #8);\n // otherwise the splat sibling captures. Junction-free param hops (no splat\n // sibling) never enter this block — the common single-param path below is\n // untouched.\n if (node.splatChild !== undefined) {\n const childParams: Record<string, string> = { [pc.name]: segment };\n\n const taken = this.#traverseFrom(\n pc.node,\n path,\n segmentEnd + 1,\n childParams,\n );\n\n if (taken !== undefined) {\n Object.assign(params, childParams);\n\n return taken;\n }\n\n return this.#matchSplat(node.splatChild, path, start, params);\n }\n\n next = pc.node;\n params[pc.name] = segment;\n } else if (node.splatChild) {\n return this.#matchSplat(node.splatChild, path, start, params);\n } else {\n return undefined;\n }\n\n node = next;\n start = segmentEnd + 1;\n }\n\n return node.slashChildRoute ?? node.route;\n }\n\n #matchSplat(\n splatChild: { node: SegmentNode; name: string },\n path: string,\n start: number,\n params: Record<string, string>,\n ): CompiledRoute | undefined {\n const sn = splatChild.node;\n\n // Stryker disable next-line BlockStatement: equivalent — leaf-splat fast path; the #traverseFrom fallback returns the same route+params (proven via hasChildren injection)\n if (!sn.hasChildren) {\n params[splatChild.name] = path.slice(start);\n\n return sn.route;\n }\n\n const childParams: Record<string, string> = {};\n const specific = this.#traverseFrom(sn, path, start, childParams);\n\n // #1288: a structurally-complete specific child wins over the wildcard\n // capture; otherwise the splat captures the rest of the path.\n if (specific) {\n Object.assign(params, childParams);\n\n return specific;\n }\n\n params[splatChild.name] = path.slice(start);\n\n return sn.route;\n }\n\n #decodeParams(params: Record<string, string>): boolean {\n const decode = this.#decode;\n\n if (!decode) {\n return true;\n }\n\n for (const key in params) {\n const value = params[key];\n\n // Stryker disable next-line StringLiteral,BlockStatement: equivalent — includes('%') is a skip-optimization; decoding a %-free value is a no-op, so always-proceeding is identical\n if (!value.includes(\"%\")) {\n continue;\n }\n\n // Stryker disable next-line BlockStatement: equivalent — redundant with the try/catch below — decodeURIComponent throws on the same invalid-% input (proven by injection)\n if (!validatePercentEncoding(value)) {\n return false;\n }\n\n try {\n params[key] = decode(value);\n } catch {\n // `validatePercentEncoding` only checks `%XX` *syntax*. A sequence that\n // is syntactically valid but semantically invalid UTF-8 (e.g. `%E0%41`,\n // `%C0%80`, `%FF`) still makes `decodeURIComponent`/`decodeURI` throw a\n // URIError. `match()` must never throw — reject the path so the router\n // resolves to UNKNOWN_ROUTE instead of crashing on start() (#737).\n return false;\n }\n }\n\n return true;\n }\n}\n\nexport { createSegmentNode } from \"./pathUtils\";\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../parseSegment.js"),t=require("../encoding.js"),n=require("./context.js");function r(e,n,r){let i=n.has(e.name);return{paramName:e.name,encoder:i?e=>t.encodeParam(e,r):t.ENCODING_METHODS[r]}}function i(t,i,a){let
|
|
1
|
+
const e=require("../parseSegment.js"),t=require("../encoding.js"),n=require("./context.js");function r(e,n,r){let i=n.has(e.name);return{paramName:e.name,encoder:i?e=>t.encodeParam(e,r):t.ENCODING_METHODS[r]}}function i(t,i,a,o){let s=new Set(o),c=new Set;for(let e of i){for(let t of e.paramMeta.urlParams)s.add(t);for(let t of e.paramMeta.spatParams)c.add(t)}if(s.size===0)return{buildStaticParts:[t],buildParamSlots:n.EMPTY_PARAM_SLOTS};let l=[],u=[],d=``,f=e.splitPathSegments(t),p=f.length-1;for(let[t,n]of f.entries()){let i=e.parseSegment(n);if(!(!(`error`in i)&&i.kind===`splat`&&t!==p)){if(t>0&&(d+=`/`),`error`in i||i.kind===`static`){d+=n;continue}l.push(d),d=``,u.push(r(i,c,a))}}return l.push(d),{buildStaticParts:l,buildParamSlots:u}}exports.compileBuildParts=i;
|
|
2
2
|
//# sourceMappingURL=buildParts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildParts.js","names":["encodeParam","ENCODING_METHODS","EMPTY_PARAM_SLOTS","splitPathSegments","parseSegment"],"sources":["../../../../../src/engine/path-matcher/registration/buildParts.ts"],"sourcesContent":["// Build-path template compilation: turns a (constraint-stripped) route path into\n// `buildStaticParts` + `buildParamSlots` through the shared `parseSegment` tokenizer (Реш.1).\n\nimport { encodeParam, ENCODING_METHODS } from \"../encoding\";\nimport {\n parseSegment,\n splitPathSegments,\n type SegmentTokens,\n} from \"../parseSegment\";\nimport { EMPTY_PARAM_SLOTS } from \"./context\";\n\nimport type {\n BuildParamSlot,\n MatcherInputNode,\n URLParamsEncodingType,\n} from \"../types\";\n\n/**\n * Builds one `BuildParamSlot` from a param/splat token. The encoder is the single\n * `encodeParam` implementation the encoding unit/property suites assert (the splat\n * variant encodes each segment individually, preserving `/`), so prod and the\n * oracle can't drift (#860).\n */\nfunction makeBuildParamSlot(\n token: Extract<SegmentTokens, { kind: \"param\" | \"splat\" }>,\n allSplatParams: ReadonlySet<string>,\n encoding: URLParamsEncodingType,\n): BuildParamSlot {\n const isSplat = allSplatParams.has(token.name);\n\n return {\n paramName: token.name,\n encoder: isSplat\n ? (value: string): string => encodeParam(value, encoding)\n : ENCODING_METHODS[encoding],\n };\n}\n\nexport function compileBuildParts(\n normalizedPath: string,\n segments: readonly MatcherInputNode[],\n encoding: URLParamsEncodingType,\n): {\n buildStaticParts: readonly string[];\n buildParamSlots: readonly BuildParamSlot[];\n} {\n const allUrlParams = new Set<string>();\n const allSplatParams = new Set<string>();\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n allUrlParams.add(param);\n }\n\n for (const param of segment.paramMeta.spatParams) {\n allSplatParams.add(param);\n }\n }\n\n // Stryker disable next-line BlockStatement: equivalent — fast path; the param-compile loop below yields [normalizedPath]/[] when allUrlParams is empty — identical output. Proven by injection.\n if (allUrlParams.size === 0) {\n return {\n buildStaticParts: [normalizedPath],\n buildParamSlots: EMPTY_PARAM_SLOTS,\n };\n }\n\n const parts: string[] = [];\n const slots: BuildParamSlot[] = [];\n let current = \"\";\n\n // Build the static-parts / param-slots template through the SAME tokenizer the\n // trie (L3) and `buildParamMeta` (L1) consume — `parseSegment`, not a parallel\n // `paramRgx` — so build's param NAME can no longer drift from the trie's\n // (#1050/#1150 build≠match closed structurally, not just by the round-trip\n // property). The path is already constraint-stripped (`matchPath`), so\n // `parseSegment` sees only name/optional; each `/` separator `splitPathSegments`\n // split away is re-added to the running static part.\n const pathSegments = splitPathSegments(normalizedPath);\n\n for (const [i, pathSegment] of pathSegments.entries()) {\n if (i > 0) {\n current += \"/\";\n }\n\n
|
|
1
|
+
{"version":3,"file":"buildParts.js","names":["encodeParam","ENCODING_METHODS","EMPTY_PARAM_SLOTS","splitPathSegments","parseSegment"],"sources":["../../../../../src/engine/path-matcher/registration/buildParts.ts"],"sourcesContent":["// Build-path template compilation: turns a (constraint-stripped) route path into\n// `buildStaticParts` + `buildParamSlots` through the shared `parseSegment` tokenizer (Реш.1).\n\nimport { encodeParam, ENCODING_METHODS } from \"../encoding\";\nimport {\n parseSegment,\n splitPathSegments,\n type SegmentTokens,\n} from \"../parseSegment\";\nimport { EMPTY_PARAM_SLOTS } from \"./context\";\n\nimport type {\n BuildParamSlot,\n MatcherInputNode,\n URLParamsEncodingType,\n} from \"../types\";\n\n/**\n * Builds one `BuildParamSlot` from a param/splat token. The encoder is the single\n * `encodeParam` implementation the encoding unit/property suites assert (the splat\n * variant encodes each segment individually, preserving `/`), so prod and the\n * oracle can't drift (#860).\n */\nfunction makeBuildParamSlot(\n token: Extract<SegmentTokens, { kind: \"param\" | \"splat\" }>,\n allSplatParams: ReadonlySet<string>,\n encoding: URLParamsEncodingType,\n): BuildParamSlot {\n const isSplat = allSplatParams.has(token.name);\n\n return {\n paramName: token.name,\n encoder: isSplat\n ? (value: string): string => encodeParam(value, encoding)\n : ENCODING_METHODS[encoding],\n };\n}\n\nexport function compileBuildParts(\n normalizedPath: string,\n segments: readonly MatcherInputNode[],\n encoding: URLParamsEncodingType,\n rootUrlParams: readonly string[],\n): {\n buildStaticParts: readonly string[];\n buildParamSlots: readonly BuildParamSlot[];\n} {\n // `normalizedPath` carries the ROOT path as its prefix, but the root node is\n // absent from `segments` — so seeding these from the segment walk alone made a\n // root-declared slot invisible here, and the fast path below emitted the whole\n // path (`:tenant` included) as literal static text (#1567).\n const allUrlParams = new Set<string>(rootUrlParams);\n // Splat names come from the segment walk only: a ROOT splat is never the final\n // segment (a route path always follows it), so it never becomes a slot and its\n // encoder is never chosen. (#1567 seeded it from the root too; #1568 made that\n // unreachable — see the finality rule below.)\n const allSplatParams = new Set<string>();\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n allUrlParams.add(param);\n }\n\n for (const param of segment.paramMeta.spatParams) {\n allSplatParams.add(param);\n }\n }\n\n // Stryker disable next-line BlockStatement: equivalent — fast path; the param-compile loop below yields [normalizedPath]/[] when allUrlParams is empty — identical output. Proven by injection.\n if (allUrlParams.size === 0) {\n return {\n buildStaticParts: [normalizedPath],\n buildParamSlots: EMPTY_PARAM_SLOTS,\n };\n }\n\n const parts: string[] = [];\n const slots: BuildParamSlot[] = [];\n let current = \"\";\n\n // Build the static-parts / param-slots template through the SAME tokenizer the\n // trie (L3) and `buildParamMeta` (L1) consume — `parseSegment`, not a parallel\n // `paramRgx` — so build's param NAME can no longer drift from the trie's\n // (#1050/#1150 build≠match closed structurally, not just by the round-trip\n // property). The path is already constraint-stripped (`matchPath`), so\n // `parseSegment` sees only name/optional; each `/` separator `splitPathSegments`\n // split away is re-added to the running static part.\n const pathSegments = splitPathSegments(normalizedPath);\n const lastIndex = pathSegments.length - 1;\n\n for (const [i, pathSegment] of pathSegments.entries()) {\n const token = parseSegment(pathSegment);\n\n // A splat binds ONLY as the final segment. The trie matches a splat node's\n // children at the splat's OWN position (INVARIANTS Matching #24: `/n/*rest`\n // + `/n/*rest/edit` → `/n/edit` resolves to the child), so a splat with\n // anything after it always captures the empty string. Emitting a slot for it\n // printed a URL `match` could not resolve — or, under a splat parent, one\n // that fell back to the parent's wildcard. It contributes neither text nor\n // separator. (#1568)\n if (!(\"error\" in token) && token.kind === \"splat\" && i !== lastIndex) {\n continue;\n }\n\n if (i > 0) {\n current += \"/\";\n }\n\n if (\"error\" in token || token.kind === \"static\") {\n // Static text — or a malformed segment, whose route is rejected at\n // `registerTree` before these buildParts are ever read (output moot).\n current += pathSegment;\n continue;\n }\n\n // param | splat: close the accumulated static part, emit a slot.\n parts.push(current);\n current = \"\";\n slots.push(makeBuildParamSlot(token, allSplatParams, encoding));\n }\n\n parts.push(current);\n\n return { buildStaticParts: parts, buildParamSlots: slots };\n}\n"],"mappings":"4FAuBA,SAAS,EACP,EACA,EACA,EACgB,CAChB,IAAM,EAAU,EAAe,IAAI,EAAM,IAAI,EAE7C,MAAO,CACL,UAAW,EAAM,KACjB,QAAS,EACJ,GAA0BA,EAAAA,YAAY,EAAO,CAAQ,EACtDC,EAAAA,iBAAiB,EACvB,CACF,CAEA,SAAgB,EACd,EACA,EACA,EACA,EAIA,CAKA,IAAM,EAAe,IAAI,IAAY,CAAa,EAK5C,EAAiB,IAAI,IAE3B,IAAK,IAAM,KAAW,EAAU,CAC9B,IAAK,IAAM,KAAS,EAAQ,UAAU,UACpC,EAAa,IAAI,CAAK,EAGxB,IAAK,IAAM,KAAS,EAAQ,UAAU,WACpC,EAAe,IAAI,CAAK,CAE5B,CAGA,GAAI,EAAa,OAAS,EACxB,MAAO,CACL,iBAAkB,CAAC,CAAc,EACjC,gBAAiBC,EAAAA,iBACnB,EAGF,IAAM,EAAkB,CAAC,EACnB,EAA0B,CAAC,EAC7B,EAAU,GASR,EAAeC,EAAAA,kBAAkB,CAAc,EAC/C,EAAY,EAAa,OAAS,EAExC,IAAK,GAAM,CAAC,EAAG,KAAgB,EAAa,QAAQ,EAAG,CACrD,IAAM,EAAQC,EAAAA,aAAa,CAAW,EASlC,OAAE,UAAW,IAAU,EAAM,OAAS,SAAW,IAAM,GAQ3D,IAJI,EAAI,IACN,GAAW,KAGT,UAAW,GAAS,EAAM,OAAS,SAAU,CAG/C,GAAW,EACX,QACF,CAGA,EAAM,KAAK,CAAO,EAClB,EAAU,GACV,EAAM,KAAK,EAAmB,EAAO,EAAgB,CAAQ,CAAC,CAL9D,CAMF,CAIA,OAFA,EAAM,KAAK,CAAO,EAEX,CAAE,iBAAkB,EAAO,gBAAiB,CAAM,CAC3D"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=Object.freeze([]),t=Object.freeze(new Set),n=Object.freeze([]),r=Object.freeze({}),i=Object.freeze({});exports.EMPTY_PARAMS=r,exports.EMPTY_PARAM_SLOTS=n,exports.EMPTY_ROUTE_META=i,exports.EMPTY_STRINGS=e,exports.EMPTY_STRING_SET=t;
|
|
1
|
+
const e=Object.freeze([]),t=Object.freeze(new Set),n=Object.freeze([]),r=Object.freeze({}),i=Object.freeze({}),a=Object.freeze({});exports.EMPTY_PARAMS=r,exports.EMPTY_PARAM_SLOTS=n,exports.EMPTY_ROUTE_META=a,exports.EMPTY_SEARCH=i,exports.EMPTY_STRINGS=e,exports.EMPTY_STRING_SET=t;
|
|
2
2
|
//# sourceMappingURL=context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../../../../src/engine/path-matcher/registration/context.ts"],"sourcesContent":["// Shared registration context: the `RegistrationState` interface and the frozen\n// empty-value sentinels (#1009). Leaf module (no sibling imports) so\n// `trie`/`trieNodes`/`buildParts` can depend on `RegistrationState` without\n// cycling back to the orchestrator.\n\nimport type {\n BuildParamSlot,\n CompiledRoute,\n ResolvedMatcherOptions,\n SegmentNode,\n} from \"../types\";\n\n// Shared frozen sentinels for the no-params common case — avoid a fresh empty\n// Set/array per route (#1009). All are ReadonlySet/[] and read-only on the\n// match/build hot paths.\nexport const EMPTY_STRINGS: readonly string[] = Object.freeze([]);\n\n// #1240 §5: freeze the Set shell too, so the \"Shared frozen sentinels\" claim\n// above holds for ALL of them and the #1009 sentinels are consistent with route-tree's\n// frozen `EMPTY_CHILDREN_MAP`. `Object.freeze` locks only the shell (not `.add`\n// — see route-tree INVARIANTS CC1), but these are `Readonly`-typed and never mutated.\nexport const EMPTY_STRING_SET: ReadonlySet<string> = Object.freeze(\n new Set<string>(),\n);\n\nexport const EMPTY_PARAM_SLOTS: readonly BuildParamSlot[] = Object.freeze([]);\n\nexport const EMPTY_PARAMS: Readonly<Record<string, unknown>> = Object.freeze(\n {},\n);\n\n// Shared frozen sentinel for a route whose every segment has an empty\n// paramTypeMap (all-static chain): `buildMeta` returns this instead of a fresh\n// per-route `{ [fullName]: {} }` record — with N distinct route names those\n// records degrade into N dictionary-mode objects (route-unique keys) while\n// carrying zero information. Consumers do keyed lookups (`meta[name]`) and\n// treat a missing entry as \"no params\", so empty-entry ≡ missing-entry; the\n// sentinel stays truthy for the route-found check in core's buildNavigateState.\nexport const EMPTY_ROUTE_META: Readonly<\n Record<string, Record<string, \"url\" | \"query\">>\n> = Object.freeze({});\n\nexport interface RegistrationState {\n readonly root: SegmentNode;\n readonly options: ResolvedMatcherOptions;\n readonly routesByName: Map<string, CompiledRoute>;\n readonly staticCache: Map<string, CompiledRoute>;\n readonly rootQueryParams: readonly string[];\n}\n"],"mappings":"AAeA,MAAa,EAAmC,OAAO,OAAO,CAAC,CAAC,EAMnD,EAAwC,OAAO,OAC1D,IAAI,GACN,EAEa,EAA+C,OAAO,OAAO,CAAC,CAAC,EAE/D,EAAkD,OAAO,OACpE,CAAC,CACH,EASa,EAET,OAAO,OAAO,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../../../../../src/engine/path-matcher/registration/context.ts"],"sourcesContent":["// Shared registration context: the `RegistrationState` interface and the frozen\n// empty-value sentinels (#1009). Leaf module (no sibling imports) so\n// `trie`/`trieNodes`/`buildParts` can depend on `RegistrationState` without\n// cycling back to the orchestrator.\n\nimport type {\n BuildParamSlot,\n CompiledRoute,\n ResolvedMatcherOptions,\n SegmentNode,\n} from \"../types\";\n\n// Shared frozen sentinels for the no-params common case — avoid a fresh empty\n// Set/array per route (#1009). All are ReadonlySet/[] and read-only on the\n// match/build hot paths.\nexport const EMPTY_STRINGS: readonly string[] = Object.freeze([]);\n\n// #1240 §5: freeze the Set shell too, so the \"Shared frozen sentinels\" claim\n// above holds for ALL of them and the #1009 sentinels are consistent with route-tree's\n// frozen `EMPTY_CHILDREN_MAP`. `Object.freeze` locks only the shell (not `.add`\n// — see route-tree INVARIANTS CC1), but these are `Readonly`-typed and never mutated.\nexport const EMPTY_STRING_SET: ReadonlySet<string> = Object.freeze(\n new Set<string>(),\n);\n\nexport const EMPTY_PARAM_SLOTS: readonly BuildParamSlot[] = Object.freeze([]);\n\nexport const EMPTY_PARAMS: Readonly<Record<string, unknown>> = Object.freeze(\n {},\n);\n\n/** Frozen empty query bag for precomputed query-less cached results (RFC-4 M2). */\nexport const EMPTY_SEARCH: Readonly<Record<string, unknown>> = Object.freeze(\n {},\n);\n\n// Shared frozen sentinel for a route whose every segment has an empty\n// paramTypeMap (all-static chain): `buildMeta` returns this instead of a fresh\n// per-route `{ [fullName]: {} }` record — with N distinct route names those\n// records degrade into N dictionary-mode objects (route-unique keys) while\n// carrying zero information. Consumers do keyed lookups (`meta[name]`) and\n// treat a missing entry as \"no params\", so empty-entry ≡ missing-entry; the\n// sentinel stays truthy for the route-found check in core's buildNavigateState.\nexport const EMPTY_ROUTE_META: Readonly<\n Record<string, Record<string, \"url\" | \"query\">>\n> = Object.freeze({});\n\nexport interface RegistrationState {\n readonly root: SegmentNode;\n readonly options: ResolvedMatcherOptions;\n readonly routesByName: Map<string, CompiledRoute>;\n readonly staticCache: Map<string, CompiledRoute>;\n readonly rootQueryParams: readonly string[];\n // The root node is deliberately absent from `matchSegments` (see SegmentMatcher\n // `registerTree`), so anything derived from a segment walk is root-blind. Its\n // QUERY declarations already had to be threaded separately (#1556); its PATH\n // slots need the same treatment for the build side (#1567).\n readonly rootUrlParams: readonly string[];\n}\n"],"mappings":"AAeA,MAAa,EAAmC,OAAO,OAAO,CAAC,CAAC,EAMnD,EAAwC,OAAO,OAC1D,IAAI,GACN,EAEa,EAA+C,OAAO,OAAO,CAAC,CAAC,EAE/D,EAAkD,OAAO,OACpE,CAAC,CACH,EAGa,EAAkD,OAAO,OACpE,CAAC,CACH,EASa,EAET,OAAO,OAAO,CAAC,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(e,t,n){throw Error(`[SegmentMatcher.registerTree] Parameter name conflict at the same path position: '${n}${e}' and '${n}${t}'. A parametric URL segment binds to a single name across every route that shares that position — the value cannot be captured under two names. Rename one so both routes agree (e.g. use '${n}${e}' in both).`)}function t(){throw Error(`[SegmentMatcher.registerTree] Empty parameter name: a parameter marker (':' or '*') must be followed by a name (e.g. ':id', '*rest'). A name-less marker, or a trailing '?' with no parameter name, would capture under an empty key at match but emit a literal at build — the two disagree, so it is rejected.`)}function n(e){throw Error(`[SegmentMatcher.registerTree] Fused parameter marker in segment "${e}": a ':'/'*' marker must begin a segment (e.g. 'a/:b', not 'a:b'). build extracts it as a param while the trie treats the segment as a literal — the two disagree.`)}function r(e){throw Error(`[SegmentMatcher.registerTree] Trailing parameter marker in segment "${e}": a param name cannot end in a bare ':' or '*' (e.g. ':y*' — the name is 'y' plus a stray marker). build/meta would capture the marker into the name while the gate rejects it as name-less — the two disagree, so it is rejected.`)}function i(e){throw Error(`[SegmentMatcher.registerTree] Optional params are not supported: "${e}" — declare two sibling routes instead (one with the segment, one without). The route hierarchy already expresses optionality.`)}function a(e){throw Error(`[SegmentMatcher.registerTree] Regex constraints are not supported: "<"/">" are reserved in path segments ("${e}"). Match the segment as a plain string and validate the value in a guard (canActivate) or app code.`)}function o(e){throw Error(`[SegmentMatcher.registerTree] Non-ASCII static segment "${e}": match rejects non-ASCII input and compares static keys raw, so this route would never match. Percent-encode it (e.g. "/caf%C3%A9") or use a param.`)}function s(e,o){switch(e){case`name-less`:return t();case`trailing-marker`:return r(o);case`fused-marker`:return n(o);case`optional-removed`:return i(o);case`constraint-removed`:return a(o)}}function c(e,t){let n=new Set,r=``;for(let e of t){if(n.has(e)){r=e;break}n.add(e)}throw Error(`[SegmentMatcher.registerTree] Duplicate parameter name ':${r}' in route "${e}": a param name must be unique within a route — two positions cannot both bind ':${r}' (the second silently overwrites the first). Rename one.`)}function l(e,t){throw Error(`[SegmentMatcher.registerTree] Invalid query-param declaration "${t}" in route "${e}": a query-param name cannot contain '<' or '>' — it would never round-trip. Rename the query param.`)}function u(e,t){throw Error(`[SegmentMatcher.registerTree]
|
|
1
|
+
function e(e,t,n){throw Error(`[SegmentMatcher.registerTree] Parameter name conflict at the same path position: '${n}${e}' and '${n}${t}'. A parametric URL segment binds to a single name across every route that shares that position — the value cannot be captured under two names. Rename one so both routes agree (e.g. use '${n}${e}' in both).`)}function t(){throw Error(`[SegmentMatcher.registerTree] Empty parameter name: a parameter marker (':' or '*') must be followed by a name (e.g. ':id', '*rest'). A name-less marker, or a trailing '?' with no parameter name, would capture under an empty key at match but emit a literal at build — the two disagree, so it is rejected.`)}function n(e){throw Error(`[SegmentMatcher.registerTree] Fused parameter marker in segment "${e}": a ':'/'*' marker must begin a segment (e.g. 'a/:b', not 'a:b'). build extracts it as a param while the trie treats the segment as a literal — the two disagree.`)}function r(e){throw Error(`[SegmentMatcher.registerTree] Trailing parameter marker in segment "${e}": a param name cannot end in a bare ':' or '*' (e.g. ':y*' — the name is 'y' plus a stray marker). build/meta would capture the marker into the name while the gate rejects it as name-less — the two disagree, so it is rejected.`)}function i(e){throw Error(`[SegmentMatcher.registerTree] Optional params are not supported: "${e}" — declare two sibling routes instead (one with the segment, one without). The route hierarchy already expresses optionality.`)}function a(e){throw Error(`[SegmentMatcher.registerTree] Regex constraints are not supported: "<"/">" are reserved in path segments ("${e}"). Match the segment as a plain string and validate the value in a guard (canActivate) or app code.`)}function o(e){throw Error(`[SegmentMatcher.registerTree] Non-ASCII static segment "${e}": match rejects non-ASCII input and compares static keys raw, so this route would never match. Percent-encode it (e.g. "/caf%C3%A9") or use a param.`)}function s(e,o){switch(e){case`name-less`:return t();case`trailing-marker`:return r(o);case`fused-marker`:return n(o);case`optional-removed`:return i(o);case`constraint-removed`:return a(o)}}function c(e,t){let n=new Set,r=``;for(let e of t){if(n.has(e)){r=e;break}n.add(e)}throw Error(`[SegmentMatcher.registerTree] Duplicate parameter name ':${r}' in route "${e}": a param name must be unique within a route — two positions cannot both bind ':${r}' (the second silently overwrites the first). Rename one.`)}function l(e,t){throw Error(`[SegmentMatcher.registerTree] Invalid query-param declaration "${t}" in route "${e}": a query-param name cannot contain '<' or '>' — it would never round-trip. Rename the query param.`)}function u(e,t){throw Error(`[SegmentMatcher.registerTree] Duplicate route path: routes "${e}" and "${t}" resolve to the same URL. The later registration would silently shadow the earlier (its deep link would resolve to the other route). Give them distinct paths.`)}function d(e,t){throw Error(`[SegmentMatcher.registerTree] Index route "${e}" (path "/") under the splat parent "${t}" is not supported: the index sits on the splat node, which the wildcard match never reaches, so it is unreachable. Give the index a distinct path, or make the parent static.`)}exports.throwDuplicateParamName=c,exports.throwDuplicateRoutePath=u,exports.throwEmptyParamName=t,exports.throwInvalidQueryParamName=l,exports.throwNonAsciiStatic=o,exports.throwParamNameConflict=e,exports.throwSegmentGrammarError=s,exports.throwSlashChildUnderDynamicParent=d;
|
|
2
2
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../../../../../src/engine/path-matcher/registration/errors.ts"],"sourcesContent":["// Registration error factories: every `throw*` helper the pipeline raises, plus the\n// `throwSegmentGrammarError` code→message dispatcher (Реш.2-A). Pure leaf — each\n// builds a message and throws; none reads trie state.\n\nimport type { SegmentErrorCode } from \"../parseSegment\";\n\n/**\n * Guards against param-name aliasing in the segment trie (issue #736).\n *\n * A parametric (`:name`) or splat (`*name`) position in the trie is keyed by\n * **position**, not by name — but the captured value is written under the name\n * recorded on that position. When two *different* routes share a position under\n * *different* names (e.g. `/user/:id` and `/user/:slug/profile`),\n * first-registration wins the name, so the second route silently captures its\n * value under the wrong key. Through `rewritePathOnMatch` that silent key-swap\n * becomes a hard `start()` crash on a legitimate config — so we reject the\n * ambiguity loudly at registration instead of corrupting matches.\n *\n * The conflict is strictly **cross-route**. Under the 3-token grammar (M1, #1516)\n * insertion is a strict linear walk — a route never revisits a slot it created\n * (the former optional-omit fork, which could land two differently-named params\n * on one position within a single route, is gone). So any name mismatch at a\n * position is unconditionally a prior route's slot: `ensureParamChild` throws on\n * `name !== paramName` with no `ownNodes` exception.\n */\nexport function throwParamNameConflict(\n existingName: string,\n newName: string,\n marker: \":\" | \"*\",\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Parameter name conflict at the same path ` +\n `position: '${marker}${existingName}' and '${marker}${newName}'. A ` +\n `parametric URL segment binds to a single name across every route that ` +\n `shares that position — the value cannot be captured under two names. ` +\n `Rename one so both routes agree (e.g. use '${marker}${existingName}' in both).`,\n );\n}\n\n/**\n * A bare marker (`:` or `*` with no name) compiles to a phantom empty-named\n * slot: match captures the value under `\"\"`, buildPath emits the literal marker,\n * and buildParamMeta reports no param at all — a three-way match/build/meta\n * desync of the same class as #736/#738 (#858). Reject it at registration,\n * symmetrically for both markers, instead of corrupting the trie.\n */\nexport function throwEmptyParamName(): never {\n // Marker-agnostic: this fires for a bare ':'/'*' (`/x/:`, `/x/*`), a marker\n // carrying only a modifier char with no name (`/x/:?`, `/x/:<...>`), AND a\n // static segment with a trailing '?' (`/faq?`) — all routed here via\n // `extractParamName`. So the message must NOT claim a specific ':' marker\n // (there isn't one for `/faq?`, #1241).\n throw new Error(\n `[SegmentMatcher.registerTree] Empty parameter name: a parameter marker ` +\n `(':' or '*') must be followed by a name (e.g. ':id', '*rest'). A name-less ` +\n `marker, or a trailing '?' with no parameter name, would capture under an ` +\n `empty key at match but emit a literal at build — the two disagree, so it ` +\n `is rejected.`,\n );\n}\n\n/**\n * Rejects a `:`/`*` marker fused to a static prefix within a segment (`a:b`,\n * `x:id`, `a*b`): the build/meta param regexes are unanchored and extract it as\n * a param, but this trie honors a marker only at segment start and compiles the\n * segment as a static literal — so `buildPath` emits an unmatchable URL while\n * `match` rejects it (#1050). The sibling of {@link throwEmptyParamName} (#858):\n * an ambiguous marker placement the three parsers cannot agree on. route-tree's\n * validation gate catches this first with a route-contextual error; this is the\n * standalone registration backstop.\n */\nfunction throwFusedMarker(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Fused parameter marker in segment \"${segment}\": ` +\n `a ':'/'*' marker must begin a segment (e.g. 'a/:b', not 'a:b'). build extracts ` +\n `it as a param while the trie treats the segment as a literal — the two disagree.`,\n );\n}\n\n/**\n * Rejects a param name ending in a bare marker (`:y*`, `:y:`, #1324): the\n * build/meta name class (`[^/?<]+`) greedily swallows the trailing `:`/`*` into\n * the name (`y*`) while the route-tree gate reads it as a name-less marker and\n * rejects — a real gate↔backstop divergence (formerly excluded from the parity\n * property, gate-masked in production). `parseSegment` ends the name before a\n * trailing marker, so this backstop now agrees with the gate. The sibling of\n * {@link throwEmptyParamName} (#858) / {@link throwFusedMarker} (#1050) on the\n * trailing-marker axis.\n */\nfunction throwTrailingMarker(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Trailing parameter marker in segment \"${segment}\": ` +\n `a param name cannot end in a bare ':' or '*' (e.g. ':y*' — the name is 'y' plus a ` +\n `stray marker). build/meta would capture the marker into the name while the gate ` +\n `rejects it as name-less — the two disagree, so it is rejected.`,\n );\n}\n\n/**\n * `optional-removed` (M1): a `:x?`/`*x?` optional modifier. The backstop tier —\n * a short, path-free recipe (the route-tree gate's rich tier computes the two\n * concrete sibling paths). Optional params were dropped for zero corpus use +\n * the axis's largest bug cluster; the hierarchy already expresses optionality.\n */\nfunction throwOptionalRemoved(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Optional params are not supported: \"${segment}\" — ` +\n `declare two sibling routes instead (one with the segment, one without). ` +\n `The route hierarchy already expresses optionality.`,\n );\n}\n\n/**\n * `constraint-removed` (M1): a `<re>` constraint or a stray `<`/`>`. The backstop\n * tier — a short recipe (the gate's rich tier names the offending segment). Regex\n * constraints were dropped; validate the value in a guard instead.\n */\nfunction throwConstraintRemoved(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Regex constraints are not supported: \"<\"/\">\" are ` +\n `reserved in path segments (\"${segment}\"). Match the segment as a plain ` +\n `string and validate the value in a guard (canActivate) or app code.`,\n );\n}\n\nexport function throwNonAsciiStatic(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Non-ASCII static segment \"${segment}\": match ` +\n `rejects non-ASCII input and compares static keys raw, so this route would ` +\n `never match. Percent-encode it (e.g. \"/caf%C3%A9\") or use a param.`,\n );\n}\n\n/**\n * Dispatches a `parseSegment` grammar-error code (the per-segment backstop) to the\n * matching matcher-level throw — the single place mapping the tokenizer's verdict\n * onto the message, so the reject reason stays byte-identical per code. The two\n * removed-form codes (M1) route to their short recipe throws; the route-tree gate\n * catches the same forms first with its richer route-contextual recipe.\n */\nexport function throwSegmentGrammarError(\n code: SegmentErrorCode,\n segment: string,\n): never {\n switch (code) {\n case \"name-less\": {\n return throwEmptyParamName();\n }\n case \"trailing-marker\": {\n return throwTrailingMarker(segment);\n }\n case \"fused-marker\": {\n return throwFusedMarker(segment);\n }\n case \"optional-removed\": {\n return throwOptionalRemoved(segment);\n }\n case \"constraint-removed\": {\n return throwConstraintRemoved(segment);\n }\n }\n}\n\nexport function throwDuplicateParamName(\n routeName: string,\n names: readonly string[],\n): never {\n const seen = new Set<string>();\n let duplicate = \"\";\n\n for (const name of names) {\n if (seen.has(name)) {\n duplicate = name;\n\n break;\n }\n\n seen.add(name);\n }\n\n throw new Error(\n `[SegmentMatcher.registerTree] Duplicate parameter name ':${duplicate}' in ` +\n `route \"${routeName}\": a param name must be unique within a route — two ` +\n `positions cannot both bind ':${duplicate}' (the second silently overwrites ` +\n `the first). Rename one.`,\n );\n}\n\nexport function throwInvalidQueryParamName(\n routeName: string,\n name: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Invalid query-param declaration \"${name}\" in ` +\n `route \"${routeName}\": a query-param name cannot contain '<' or '>' — it would ` +\n `never round-trip. Rename the query param.`,\n );\n}\n\nexport function throwPathQueryNameCollision(\n routeName: string,\n name: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Name collision in route \"${routeName}\": \"${name}\" ` +\n `is declared as BOTH a path param (':${name}') and a query param ('?${name}'). ` +\n `buildPath would emit its value twice (once in the path, once in the query). ` +\n `Rename one.`,\n );\n}\n\nexport function throwDuplicateRoutePath(\n existingName: string,\n newName: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Duplicate route path: routes \"${existingName}\" ` +\n `and \"${newName}\" resolve to the same URL. The later registration would ` +\n `silently shadow the earlier (its deep link would resolve to the other ` +\n `route). Give them distinct paths.`,\n );\n}\n\nexport function throwSlashChildUnderDynamicParent(\n routeName: string,\n parentPath: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Index route \"${routeName}\" (path \"/\") under the ` +\n `splat parent \"${parentPath}\" is not supported: the index sits on the splat ` +\n `node, which the wildcard match never reaches, so it is unreachable. Give the ` +\n `index a distinct path, or make the parent static.`,\n );\n}\n"],"mappings":"AAyBA,SAAgB,EACd,EACA,EACA,EACO,CACP,MAAU,MACR,qFACgB,IAAS,EAAa,SAAS,IAAS,EAAQ,6LAGhB,IAAS,EAAa,YACxE,CACF,CASA,SAAgB,GAA6B,CAM3C,MAAU,MACR,kTAKF,CACF,CAYA,SAAS,EAAiB,EAAwB,CAChD,MAAU,MACR,oEAAoE,EAAQ,mKAG9E,CACF,CAYA,SAAS,EAAoB,EAAwB,CACnD,MAAU,MACR,uEAAuE,EAAQ,oOAIjF,CACF,CAQA,SAAS,EAAqB,EAAwB,CACpD,MAAU,MACR,qEAAqE,EAAQ,+HAG/E,CACF,CAOA,SAAS,EAAuB,EAAwB,CACtD,MAAU,MACR,8GACiC,EAAQ,qGAE3C,CACF,CAEA,SAAgB,EAAoB,EAAwB,CAC1D,MAAU,MACR,2DAA2D,EAAQ,sJAGrE,CACF,CASA,SAAgB,EACd,EACA,EACO,CACP,OAAQ,EAAR,CACE,IAAK,YACH,OAAO,EAAoB,EAE7B,IAAK,kBACH,OAAO,EAAoB,CAAO,EAEpC,IAAK,eACH,OAAO,EAAiB,CAAO,EAEjC,IAAK,mBACH,OAAO,EAAqB,CAAO,EAErC,IAAK,qBACH,OAAO,EAAuB,CAAO,CAEzC,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,IAAM,EAAO,IAAI,IACb,EAAY,GAEhB,IAAK,IAAM,KAAQ,EAAO,CACxB,GAAI,EAAK,IAAI,CAAI,EAAG,CAClB,EAAY,EAEZ,KACF,CAEA,EAAK,IAAI,CAAI,CACf,CAEA,MAAU,MACR,4DAA4D,EAAU,cAC1D,EAAU,mFACY,EAAU,0DAE9C,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,kEAAkE,EAAK,cAC3D,EAAU,qGAExB,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,0DAA0D,EAAU,MAAM,EAAK,wCACtC,EAAK,0BAA0B,EAAK,4FAG/E,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,+DAA+D,EAAa,SAClE,EAAQ,gKAGpB,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,8CAA8C,EAAU,uCACrC,EAAW,+KAGhC,CACF"}
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../../../../src/engine/path-matcher/registration/errors.ts"],"sourcesContent":["// Registration error factories: every `throw*` helper the pipeline raises, plus the\n// `throwSegmentGrammarError` code→message dispatcher (Реш.2-A). Pure leaf — each\n// builds a message and throws; none reads trie state.\n\nimport type { SegmentErrorCode } from \"../parseSegment\";\n\n/**\n * Guards against param-name aliasing in the segment trie (issue #736).\n *\n * A parametric (`:name`) or splat (`*name`) position in the trie is keyed by\n * **position**, not by name — but the captured value is written under the name\n * recorded on that position. When two *different* routes share a position under\n * *different* names (e.g. `/user/:id` and `/user/:slug/profile`),\n * first-registration wins the name, so the second route silently captures its\n * value under the wrong key. Through `rewritePathOnMatch` that silent key-swap\n * becomes a hard `start()` crash on a legitimate config — so we reject the\n * ambiguity loudly at registration instead of corrupting matches.\n *\n * The conflict is strictly **cross-route**. Under the 3-token grammar (M1, #1516)\n * insertion is a strict linear walk — a route never revisits a slot it created\n * (the former optional-omit fork, which could land two differently-named params\n * on one position within a single route, is gone). So any name mismatch at a\n * position is unconditionally a prior route's slot: `ensureParamChild` throws on\n * `name !== paramName` with no `ownNodes` exception.\n */\nexport function throwParamNameConflict(\n existingName: string,\n newName: string,\n marker: \":\" | \"*\",\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Parameter name conflict at the same path ` +\n `position: '${marker}${existingName}' and '${marker}${newName}'. A ` +\n `parametric URL segment binds to a single name across every route that ` +\n `shares that position — the value cannot be captured under two names. ` +\n `Rename one so both routes agree (e.g. use '${marker}${existingName}' in both).`,\n );\n}\n\n/**\n * A bare marker (`:` or `*` with no name) compiles to a phantom empty-named\n * slot: match captures the value under `\"\"`, buildPath emits the literal marker,\n * and buildParamMeta reports no param at all — a three-way match/build/meta\n * desync of the same class as #736/#738 (#858). Reject it at registration,\n * symmetrically for both markers, instead of corrupting the trie.\n */\nexport function throwEmptyParamName(): never {\n // Marker-agnostic: this fires for a bare ':'/'*' (`/x/:`, `/x/*`), a marker\n // carrying only a modifier char with no name (`/x/:?`, `/x/:<...>`), AND a\n // static segment with a trailing '?' (`/faq?`) — all routed here via\n // `extractParamName`. So the message must NOT claim a specific ':' marker\n // (there isn't one for `/faq?`, #1241).\n throw new Error(\n `[SegmentMatcher.registerTree] Empty parameter name: a parameter marker ` +\n `(':' or '*') must be followed by a name (e.g. ':id', '*rest'). A name-less ` +\n `marker, or a trailing '?' with no parameter name, would capture under an ` +\n `empty key at match but emit a literal at build — the two disagree, so it ` +\n `is rejected.`,\n );\n}\n\n/**\n * Rejects a `:`/`*` marker fused to a static prefix within a segment (`a:b`,\n * `x:id`, `a*b`): the build/meta param regexes are unanchored and extract it as\n * a param, but this trie honors a marker only at segment start and compiles the\n * segment as a static literal — so `buildPath` emits an unmatchable URL while\n * `match` rejects it (#1050). The sibling of {@link throwEmptyParamName} (#858):\n * an ambiguous marker placement the three parsers cannot agree on. route-tree's\n * validation gate catches this first with a route-contextual error; this is the\n * standalone registration backstop.\n */\nfunction throwFusedMarker(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Fused parameter marker in segment \"${segment}\": ` +\n `a ':'/'*' marker must begin a segment (e.g. 'a/:b', not 'a:b'). build extracts ` +\n `it as a param while the trie treats the segment as a literal — the two disagree.`,\n );\n}\n\n/**\n * Rejects a param name ending in a bare marker (`:y*`, `:y:`, #1324): the\n * build/meta name class (`[^/?<]+`) greedily swallows the trailing `:`/`*` into\n * the name (`y*`) while the route-tree gate reads it as a name-less marker and\n * rejects — a real gate↔backstop divergence (formerly excluded from the parity\n * property, gate-masked in production). `parseSegment` ends the name before a\n * trailing marker, so this backstop now agrees with the gate. The sibling of\n * {@link throwEmptyParamName} (#858) / {@link throwFusedMarker} (#1050) on the\n * trailing-marker axis.\n */\nfunction throwTrailingMarker(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Trailing parameter marker in segment \"${segment}\": ` +\n `a param name cannot end in a bare ':' or '*' (e.g. ':y*' — the name is 'y' plus a ` +\n `stray marker). build/meta would capture the marker into the name while the gate ` +\n `rejects it as name-less — the two disagree, so it is rejected.`,\n );\n}\n\n/**\n * `optional-removed` (M1): a `:x?`/`*x?` optional modifier. The backstop tier —\n * a short, path-free recipe (the route-tree gate's rich tier computes the two\n * concrete sibling paths). Optional params were dropped for zero corpus use +\n * the axis's largest bug cluster; the hierarchy already expresses optionality.\n */\nfunction throwOptionalRemoved(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Optional params are not supported: \"${segment}\" — ` +\n `declare two sibling routes instead (one with the segment, one without). ` +\n `The route hierarchy already expresses optionality.`,\n );\n}\n\n/**\n * `constraint-removed` (M1): a `<re>` constraint or a stray `<`/`>`. The backstop\n * tier — a short recipe (the gate's rich tier names the offending segment). Regex\n * constraints were dropped; validate the value in a guard instead.\n */\nfunction throwConstraintRemoved(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Regex constraints are not supported: \"<\"/\">\" are ` +\n `reserved in path segments (\"${segment}\"). Match the segment as a plain ` +\n `string and validate the value in a guard (canActivate) or app code.`,\n );\n}\n\nexport function throwNonAsciiStatic(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Non-ASCII static segment \"${segment}\": match ` +\n `rejects non-ASCII input and compares static keys raw, so this route would ` +\n `never match. Percent-encode it (e.g. \"/caf%C3%A9\") or use a param.`,\n );\n}\n\n/**\n * Dispatches a `parseSegment` grammar-error code (the per-segment backstop) to the\n * matching matcher-level throw — the single place mapping the tokenizer's verdict\n * onto the message, so the reject reason stays byte-identical per code. The two\n * removed-form codes (M1) route to their short recipe throws; the route-tree gate\n * catches the same forms first with its richer route-contextual recipe.\n */\nexport function throwSegmentGrammarError(\n code: SegmentErrorCode,\n segment: string,\n): never {\n switch (code) {\n case \"name-less\": {\n return throwEmptyParamName();\n }\n case \"trailing-marker\": {\n return throwTrailingMarker(segment);\n }\n case \"fused-marker\": {\n return throwFusedMarker(segment);\n }\n case \"optional-removed\": {\n return throwOptionalRemoved(segment);\n }\n case \"constraint-removed\": {\n return throwConstraintRemoved(segment);\n }\n }\n}\n\nexport function throwDuplicateParamName(\n routeName: string,\n names: readonly string[],\n): never {\n const seen = new Set<string>();\n let duplicate = \"\";\n\n for (const name of names) {\n if (seen.has(name)) {\n duplicate = name;\n\n break;\n }\n\n seen.add(name);\n }\n\n throw new Error(\n `[SegmentMatcher.registerTree] Duplicate parameter name ':${duplicate}' in ` +\n `route \"${routeName}\": a param name must be unique within a route — two ` +\n `positions cannot both bind ':${duplicate}' (the second silently overwrites ` +\n `the first). Rename one.`,\n );\n}\n\nexport function throwInvalidQueryParamName(\n routeName: string,\n name: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Invalid query-param declaration \"${name}\" in ` +\n `route \"${routeName}\": a query-param name cannot contain '<' or '>' — it would ` +\n `never round-trip. Rename the query param.`,\n );\n}\n\nexport function throwDuplicateRoutePath(\n existingName: string,\n newName: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Duplicate route path: routes \"${existingName}\" ` +\n `and \"${newName}\" resolve to the same URL. The later registration would ` +\n `silently shadow the earlier (its deep link would resolve to the other ` +\n `route). Give them distinct paths.`,\n );\n}\n\nexport function throwSlashChildUnderDynamicParent(\n routeName: string,\n parentPath: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Index route \"${routeName}\" (path \"/\") under the ` +\n `splat parent \"${parentPath}\" is not supported: the index sits on the splat ` +\n `node, which the wildcard match never reaches, so it is unreachable. Give the ` +\n `index a distinct path, or make the parent static.`,\n );\n}\n"],"mappings":"AAyBA,SAAgB,EACd,EACA,EACA,EACO,CACP,MAAU,MACR,qFACgB,IAAS,EAAa,SAAS,IAAS,EAAQ,6LAGhB,IAAS,EAAa,YACxE,CACF,CASA,SAAgB,GAA6B,CAM3C,MAAU,MACR,kTAKF,CACF,CAYA,SAAS,EAAiB,EAAwB,CAChD,MAAU,MACR,oEAAoE,EAAQ,mKAG9E,CACF,CAYA,SAAS,EAAoB,EAAwB,CACnD,MAAU,MACR,uEAAuE,EAAQ,oOAIjF,CACF,CAQA,SAAS,EAAqB,EAAwB,CACpD,MAAU,MACR,qEAAqE,EAAQ,+HAG/E,CACF,CAOA,SAAS,EAAuB,EAAwB,CACtD,MAAU,MACR,8GACiC,EAAQ,qGAE3C,CACF,CAEA,SAAgB,EAAoB,EAAwB,CAC1D,MAAU,MACR,2DAA2D,EAAQ,sJAGrE,CACF,CASA,SAAgB,EACd,EACA,EACO,CACP,OAAQ,EAAR,CACE,IAAK,YACH,OAAO,EAAoB,EAE7B,IAAK,kBACH,OAAO,EAAoB,CAAO,EAEpC,IAAK,eACH,OAAO,EAAiB,CAAO,EAEjC,IAAK,mBACH,OAAO,EAAqB,CAAO,EAErC,IAAK,qBACH,OAAO,EAAuB,CAAO,CAEzC,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,IAAM,EAAO,IAAI,IACb,EAAY,GAEhB,IAAK,IAAM,KAAQ,EAAO,CACxB,GAAI,EAAK,IAAI,CAAI,EAAG,CAClB,EAAY,EAEZ,KACF,CAEA,EAAK,IAAI,CAAI,CACf,CAEA,MAAU,MACR,4DAA4D,EAAU,cAC1D,EAAU,mFACY,EAAU,0DAE9C,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,kEAAkE,EAAK,cAC3D,EAAU,qGAExB,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,+DAA+D,EAAa,SAClE,EAAQ,gKAGpB,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,8CAA8C,EAAU,uCACrC,EAAW,+KAGhC,CACF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../parseSegment.js"),t=require("../buildParamMeta.js"),n=require("../pathUtils.js"),r=require("./context.js"),i=require("./buildParts.js"),a=require("./errors.js"),o=require("./trie.js");function s(r,i,o,l,u){let d=i.fullName===``;d||l.push(i);let f=i.absolute,p=i.paramMeta===t.EMPTY_PARAM_META?i.path:i.paramMeta.pathPattern,m=f&&p.startsWith(`~`)?p.slice(1):p,h=f?m:p;for(let t of e.splitPathSegments(h)){let n=e.parseSegment(t);`error`in n&&a.throwSegmentGrammarError(n.error,t)}let g=h,_=f?g:n.buildFullPath(o,g),v=d?u:c(r,i,_,f?``:o,l,u);for(let e of i.children.values())s(r,e,_,l,v);d||l.pop()}function c(e,t,o,s,c,u){let g=p(o,s),_=Object.freeze([...c]),v=l(_),y=n.normalizeTrailingSlash(o),b=m(e.rootQueryParams,c),{buildStaticParts:x,buildParamSlots:S}=i.compileBuildParts(g?n.normalizeTrailingSlash(s):y,g?c.slice(0,-1):c,e.options.urlParamsEncoding),C=S.map(e=>e.paramName),w=C.length===0?r.EMPTY_STRING_SET:new Set(C);w.size!==C.length&&a.throwDuplicateParamName(t.fullName,C),h(t.fullName,b
|
|
1
|
+
const e=require("../parseSegment.js"),t=require("../buildParamMeta.js"),n=require("../pathUtils.js"),r=require("./context.js"),i=require("./buildParts.js"),a=require("./errors.js"),o=require("./trie.js");function s(r,i,o,l,u){let d=i.fullName===``;d||l.push(i);let f=i.absolute,p=i.paramMeta===t.EMPTY_PARAM_META?i.path:i.paramMeta.pathPattern,m=f&&p.startsWith(`~`)?p.slice(1):p,h=f?m:p;for(let t of e.splitPathSegments(h)){let n=e.parseSegment(t);`error`in n&&a.throwSegmentGrammarError(n.error,t)}let g=h,_=f?g:n.buildFullPath(o,g),v=d?u:c(r,i,_,f?``:o,l,u);for(let e of i.children.values())s(r,e,_,l,v);d||l.pop()}function c(e,t,o,s,c,u){let g=p(o,s),_=Object.freeze([...c]),v=l(_),y=n.normalizeTrailingSlash(o),b=m(e.rootQueryParams,c),{buildStaticParts:x,buildParamSlots:S}=i.compileBuildParts(g?n.normalizeTrailingSlash(s):y,g?c.slice(0,-1):c,e.options.urlParamsEncoding,e.rootUrlParams),C=S.map(e=>e.paramName),w=C.length===0?r.EMPTY_STRING_SET:new Set(C);w.size!==C.length&&a.throwDuplicateParamName(t.fullName,C),h(t.fullName,b);let T={name:t.fullName,parent:u,matchSegments:_,meta:v,declaredQueryParams:b,declaredQueryParamsSet:b.length===0?r.EMPTY_STRING_SET:new Set(b),hasTrailingSlash:o.length>1&&o.endsWith(`/`),buildStaticParts:x,buildParamSlots:S,buildParamNamesSet:w,cachedResult:void 0};return t.paramMeta.urlParams.length===0&&(T.cachedResult=Object.freeze({segments:T.matchSegments,params:r.EMPTY_PARAMS,search:r.EMPTY_SEARCH,meta:T.meta})),e.routesByName.set(t.fullName,T),g?d(e,T,s):f(e,T,o,y,t),T}function l(e){let t;for(let n of e)u(n.paramTypeMap)&&(t??={},t[n.fullName]=n.paramTypeMap);return t===void 0?r.EMPTY_ROUTE_META:Object.freeze(t)}function u(e){for(let t in e)if(Object.hasOwn(e,t))return!0;return!1}function d(e,t,r){o.insertSlashChildIntoTrie(e,t,r);let i=n.normalizeTrailingSlash(r),a=e.options.caseSensitive?i:i.toLowerCase();e.staticCache.has(a)&&e.staticCache.set(a,t)}function f(e,t,n,r,i){if(o.insertIntoTrie(e,t,n),i.paramMeta.urlParams.length===0){let n=e.options.caseSensitive?r:r.toLowerCase();e.staticCache.set(n,t)}}function p(e,t){return n.normalizeTrailingSlash(e)===n.normalizeTrailingSlash(t)}function m(e,t){let n=[];e.length>0&&n.push(...e);for(let e of t)e.paramMeta.queryParams.length>0&&n.push(...e.paramMeta.queryParams);return n.length===0?r.EMPTY_STRINGS:n}function h(e,n){for(let r of n)t.INVALID_QUERY_NAME_RGX.test(r)&&a.throwInvalidQueryParamName(e,r)}exports.registerNode=s;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["EMPTY_PARAM_META","splitPathSegments","parseSegment","buildFullPath","normalizeTrailingSlash","compileBuildParts","EMPTY_STRING_SET","EMPTY_PARAMS","EMPTY_ROUTE_META","EMPTY_STRINGS","INVALID_QUERY_NAME_RGX"],"sources":["../../../../../src/engine/path-matcher/registration/index.ts"],"sourcesContent":["// Registration entry + orchestration: `registerNode` (the one public entry, re-exported\n// for `SegmentMatcher`) runs the per-node grammar pre-pass (Реш.2-A), compile, and trie\n// insertion, plus the route-meta / query-and-constraint collection helpers.\n// Concerns split into ./context ./errors ./trieNodes ./trie ./buildParts.\n\nimport { EMPTY_PARAM_META, INVALID_QUERY_NAME_RGX } from \"../buildParamMeta\";\nimport { parseSegment, splitPathSegments } from \"../parseSegment\";\nimport { buildFullPath, normalizeTrailingSlash } from \"../pathUtils\";\nimport { compileBuildParts } from \"./buildParts\";\nimport {\n EMPTY_PARAMS,\n EMPTY_ROUTE_META,\n EMPTY_STRINGS,\n EMPTY_STRING_SET,\n type RegistrationState,\n} from \"./context\";\nimport {\n throwDuplicateParamName,\n throwInvalidQueryParamName,\n throwPathQueryNameCollision,\n throwSegmentGrammarError,\n} from \"./errors\";\nimport { insertIntoTrie, insertSlashChildIntoTrie } from \"./trie\";\n\nimport type { CompiledRoute, MatcherInputNode } from \"../types\";\n\nexport type { RegistrationState } from \"./context\";\n\nexport function registerNode(\n state: RegistrationState,\n node: MatcherInputNode,\n parentPath: string,\n segments: MatcherInputNode[],\n parentRoute: CompiledRoute | null,\n): void {\n const isRoot = node.fullName === \"\";\n\n if (!isRoot) {\n segments.push(node);\n }\n\n const isAbsolute = node.absolute;\n // The EMPTY_PARAM_META sentinel (fully-static node) carries pathPattern \"\";\n // its real pattern is the node's own path (sentinel is only installed when\n // the two were reference-equal).\n const pathPattern =\n node.paramMeta === EMPTY_PARAM_META\n ? node.path\n : node.paramMeta.pathPattern;\n const strippedPattern =\n isAbsolute && pathPattern.startsWith(\"~\")\n ? pathPattern.slice(1)\n : pathPattern;\n const rawNodePath = isAbsolute ? strippedPattern : pathPattern;\n\n // Per-segment grammar backstop: the trie's own grammar verdict reads the SAME\n // `parseSegment` tokenizer the route-tree gate reads (`findSegmentGrammarError`),\n // so backstop and gate cannot drift on a per-segment form. One pass over the RAW\n // path rejects every rejection form: name-less (#858), fused marker (#1050),\n // trailing marker (#1324), and the removed forms `optional-removed` /\n // `constraint-removed` (M1). So `processSegment` / `extractParamName` downstream\n // see only grammatically valid `static | :param | *splat` segments.\n for (const segment of splitPathSegments(rawNodePath)) {\n const token = parseSegment(segment);\n\n if (\"error\" in token) {\n throwSegmentGrammarError(token.error, segment);\n }\n }\n\n // 3-token grammar (M1): no `<...>` constraint to strip before trie insertion.\n const nodePath = rawNodePath;\n\n const matchPath = isAbsolute ? nodePath : buildFullPath(parentPath, nodePath);\n\n const compileParentPath = isAbsolute ? \"\" : parentPath;\n\n const currentRoute: CompiledRoute | null = isRoot\n ? parentRoute\n : compileAndRegisterRoute(\n state,\n node,\n matchPath,\n compileParentPath,\n segments,\n parentRoute,\n );\n\n for (const child of node.children.values()) {\n registerNode(state, child, matchPath, segments, currentRoute);\n }\n\n if (!isRoot) {\n segments.pop();\n }\n}\n\nfunction compileAndRegisterRoute(\n state: RegistrationState,\n node: MatcherInputNode,\n matchPath: string,\n parentPath: string,\n segments: MatcherInputNode[],\n parentRoute: CompiledRoute | null,\n): CompiledRoute {\n const slashChild = isSlashChild(matchPath, parentPath);\n\n const frozenSegments = Object.freeze([...segments]);\n const frozenMeta = buildMeta(frozenSegments);\n\n const normalizedPath = normalizeTrailingSlash(matchPath);\n\n const declaredQueryParams = collectDeclaredQueryParams(\n state.rootQueryParams,\n segments,\n );\n\n // Slash-child: use parent path for buildParts (not slash-child's path)\n const buildPath = slashChild\n ? normalizeTrailingSlash(parentPath)\n : normalizedPath;\n\n const { buildStaticParts, buildParamSlots } = compileBuildParts(\n buildPath,\n // Stryker disable next-line MethodExpression: equivalent — slash-child buildParts: dropping the last segment vs keeping it yields identical buildStaticParts here (no own params on the slash-child). Proven by injection (full suite green).\n slashChild ? segments.slice(0, -1) : segments,\n state.options.urlParamsEncoding,\n );\n\n // #1151: reject a duplicate param name within one route's full path (`/:id/:id`,\n // a param+splat clash `/:x/*x`, or a parent `/a/:x` + child `/:x`). buildParamSlots\n // keeps duplicates; the trie stores them at DIFFERENT positions under the SAME\n // name, so match's later capture silently overwrites the earlier and\n // rewritePathOnMatch then rewrites the user's URL from the single survivor. The\n // #736 conflict guard only fires on DIFFERENTLY-named params at ONE position, so\n // this same-name case slips through.\n const buildParamNames = buildParamSlots.map((slot) => slot.paramName);\n const buildParamNamesSet =\n buildParamNames.length === 0 ? EMPTY_STRING_SET : new Set(buildParamNames);\n\n if (buildParamNamesSet.size !== buildParamNames.length) {\n throwDuplicateParamName(node.fullName, buildParamNames);\n }\n\n // #1242 §5.1/§5.2/§5.3: validate query-param DECLARATIONS. A declared query name\n // must be a clean token — reject one carrying constraint/query metacharacters\n // (`:b?<\\d+>` declares query `<\\d+>`; `?tab=1` declares `tab=1`), and reject a name\n // shared with a path param (`/a/:tab?tab`), where buildPath would emit the value\n // twice (`/a/x?tab=x`). Both degraded silently before.\n validateQueryParamDeclarations(\n node.fullName,\n declaredQueryParams,\n buildParamNamesSet,\n );\n\n const compiled: CompiledRoute = {\n name: node.fullName,\n parent: parentRoute,\n matchSegments: frozenSegments,\n meta: frozenMeta,\n declaredQueryParams,\n declaredQueryParamsSet:\n declaredQueryParams.length === 0\n ? EMPTY_STRING_SET\n : new Set(declaredQueryParams),\n hasTrailingSlash: matchPath.length > 1 && matchPath.endsWith(\"/\"),\n buildStaticParts,\n buildParamSlots,\n buildParamNamesSet,\n // Initialized here (not added conditionally below) so static and param\n // routes share one hidden class — avoids a megamorphic CompiledRoute (#1009).\n cachedResult: undefined,\n };\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — cachedResult is a pure match() optimization; #buildResult recomputes the same value on a miss (proven: disabling the whole static cache keeps the unit+property suite green)\n if (node.paramMeta.urlParams.length === 0) {\n compiled.cachedResult = Object.freeze({\n segments: compiled.matchSegments,\n params: EMPTY_PARAMS,\n meta: compiled.meta,\n });\n }\n\n state.routesByName.set(node.fullName, compiled);\n\n if (slashChild) {\n registerSlashChild(state, compiled, parentPath);\n } else {\n registerStandardRoute(state, compiled, matchPath, normalizedPath, node);\n }\n\n return compiled;\n}\n\nfunction buildMeta(\n segments: readonly MatcherInputNode[],\n): Readonly<Record<string, Record<string, \"url\" | \"query\">>> {\n let meta: Record<string, Record<string, \"url\" | \"query\">> | undefined;\n\n for (const segment of segments) {\n if (!hasAnyParam(segment.paramTypeMap)) {\n continue;\n }\n\n meta ??= {};\n meta[segment.fullName] = segment.paramTypeMap;\n }\n\n return meta === undefined ? EMPTY_ROUTE_META : Object.freeze(meta);\n}\n\n// Allocation-free emptiness probe for a segment's paramTypeMap (Object.keys\n// would allocate a fresh array per segment during registration).\nfunction hasAnyParam(\n paramTypeMap: Readonly<Record<string, \"url\" | \"query\">>,\n): boolean {\n for (const key in paramTypeMap) {\n if (Object.hasOwn(paramTypeMap, key)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction registerSlashChild(\n state: RegistrationState,\n compiled: CompiledRoute,\n parentPath: string,\n): void {\n insertSlashChildIntoTrie(state, compiled, parentPath);\n\n const parentNormalized = normalizeTrailingSlash(parentPath);\n const cacheKey = state.options.caseSensitive\n ? parentNormalized\n : parentNormalized.toLowerCase();\n\n if (state.staticCache.has(cacheKey)) {\n state.staticCache.set(cacheKey, compiled);\n }\n}\n\nfunction registerStandardRoute(\n state: RegistrationState,\n compiled: CompiledRoute,\n matchPath: string,\n normalizedPath: string,\n node: MatcherInputNode,\n): void {\n insertIntoTrie(state, compiled, matchPath);\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — staticCache is a pure match() optimization; #traverse resolves the same route on a miss (proven: disabling the whole static cache keeps the unit+property suite green)\n if (node.paramMeta.urlParams.length === 0) {\n const cacheKey = state.options.caseSensitive\n ? normalizedPath\n : // Stryker disable next-line MethodExpression: equivalent — the case-insensitive cache key only governs a hit; a miss falls through to #traverse, which is also case-insensitive\n normalizedPath.toLowerCase();\n\n state.staticCache.set(cacheKey, compiled);\n }\n}\n\nfunction isSlashChild(matchPath: string, parentPath: string): boolean {\n const normalizedMatch = normalizeTrailingSlash(matchPath);\n const normalizedParent = normalizeTrailingSlash(parentPath);\n\n return normalizedMatch === normalizedParent;\n}\n\nfunction collectDeclaredQueryParams(\n rootQueryParams: readonly string[],\n segments: readonly MatcherInputNode[],\n): readonly string[] {\n // Stryker disable next-line ArrayDeclaration: equivalent — the array is populated then returned; a phantom seed element is never read back (declared-param loop skips absent keys, buildQueryString ignores it). Proven by injection.\n const queryParams: string[] = [];\n\n // Include query params declared on the root node (e.g., a root path like \"?mode\")\n if (rootQueryParams.length > 0) {\n queryParams.push(...rootQueryParams);\n }\n\n for (const segment of segments) {\n if (segment.paramMeta.queryParams.length > 0) {\n queryParams.push(...segment.paramMeta.queryParams);\n }\n }\n\n return queryParams.length === 0 ? EMPTY_STRINGS : queryParams;\n}\n\nfunction validateQueryParamDeclarations(\n routeName: string,\n queryParams: readonly string[],\n urlParamNames: ReadonlySet<string>,\n): void {\n for (const name of queryParams) {\n if (INVALID_QUERY_NAME_RGX.test(name)) {\n throwInvalidQueryParamName(routeName, name);\n }\n\n if (urlParamNames.has(name)) {\n throwPathQueryNameCollision(routeName, name);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["EMPTY_PARAM_META","splitPathSegments","parseSegment","buildFullPath","normalizeTrailingSlash","compileBuildParts","EMPTY_STRING_SET","EMPTY_PARAMS","EMPTY_SEARCH","EMPTY_ROUTE_META","EMPTY_STRINGS","INVALID_QUERY_NAME_RGX"],"sources":["../../../../../src/engine/path-matcher/registration/index.ts"],"sourcesContent":["// Registration entry + orchestration: `registerNode` (the one public entry, re-exported\n// for `SegmentMatcher`) runs the per-node grammar pre-pass (Реш.2-A), compile, and trie\n// insertion, plus the route-meta / query-and-constraint collection helpers.\n// Concerns split into ./context ./errors ./trieNodes ./trie ./buildParts.\n\nimport { EMPTY_PARAM_META, INVALID_QUERY_NAME_RGX } from \"../buildParamMeta\";\nimport { parseSegment, splitPathSegments } from \"../parseSegment\";\nimport { buildFullPath, normalizeTrailingSlash } from \"../pathUtils\";\nimport { compileBuildParts } from \"./buildParts\";\nimport {\n EMPTY_PARAMS,\n EMPTY_SEARCH,\n EMPTY_ROUTE_META,\n EMPTY_STRINGS,\n EMPTY_STRING_SET,\n type RegistrationState,\n} from \"./context\";\nimport {\n throwDuplicateParamName,\n throwInvalidQueryParamName,\n throwSegmentGrammarError,\n} from \"./errors\";\nimport { insertIntoTrie, insertSlashChildIntoTrie } from \"./trie\";\n\nimport type { CompiledRoute, MatcherInputNode } from \"../types\";\n\nexport type { RegistrationState } from \"./context\";\n\nexport function registerNode(\n state: RegistrationState,\n node: MatcherInputNode,\n parentPath: string,\n segments: MatcherInputNode[],\n parentRoute: CompiledRoute | null,\n): void {\n const isRoot = node.fullName === \"\";\n\n if (!isRoot) {\n segments.push(node);\n }\n\n const isAbsolute = node.absolute;\n // The EMPTY_PARAM_META sentinel (fully-static node) carries pathPattern \"\";\n // its real pattern is the node's own path (sentinel is only installed when\n // the two were reference-equal).\n const pathPattern =\n node.paramMeta === EMPTY_PARAM_META\n ? node.path\n : node.paramMeta.pathPattern;\n const strippedPattern =\n isAbsolute && pathPattern.startsWith(\"~\")\n ? pathPattern.slice(1)\n : pathPattern;\n const rawNodePath = isAbsolute ? strippedPattern : pathPattern;\n\n // Per-segment grammar backstop: the trie's own grammar verdict reads the SAME\n // `parseSegment` tokenizer the route-tree gate reads (`findSegmentGrammarError`),\n // so backstop and gate cannot drift on a per-segment form. One pass over the RAW\n // path rejects every rejection form: name-less (#858), fused marker (#1050),\n // trailing marker (#1324), and the removed forms `optional-removed` /\n // `constraint-removed` (M1). So `processSegment` / `extractParamName` downstream\n // see only grammatically valid `static | :param | *splat` segments.\n for (const segment of splitPathSegments(rawNodePath)) {\n const token = parseSegment(segment);\n\n if (\"error\" in token) {\n throwSegmentGrammarError(token.error, segment);\n }\n }\n\n // 3-token grammar (M1): no `<...>` constraint to strip before trie insertion.\n const nodePath = rawNodePath;\n\n const matchPath = isAbsolute ? nodePath : buildFullPath(parentPath, nodePath);\n\n const compileParentPath = isAbsolute ? \"\" : parentPath;\n\n const currentRoute: CompiledRoute | null = isRoot\n ? parentRoute\n : compileAndRegisterRoute(\n state,\n node,\n matchPath,\n compileParentPath,\n segments,\n parentRoute,\n );\n\n for (const child of node.children.values()) {\n registerNode(state, child, matchPath, segments, currentRoute);\n }\n\n if (!isRoot) {\n segments.pop();\n }\n}\n\nfunction compileAndRegisterRoute(\n state: RegistrationState,\n node: MatcherInputNode,\n matchPath: string,\n parentPath: string,\n segments: MatcherInputNode[],\n parentRoute: CompiledRoute | null,\n): CompiledRoute {\n const slashChild = isSlashChild(matchPath, parentPath);\n\n const frozenSegments = Object.freeze([...segments]);\n const frozenMeta = buildMeta(frozenSegments);\n\n const normalizedPath = normalizeTrailingSlash(matchPath);\n\n const declaredQueryParams = collectDeclaredQueryParams(\n state.rootQueryParams,\n segments,\n );\n\n // Slash-child: use parent path for buildParts (not slash-child's path)\n const buildPath = slashChild\n ? normalizeTrailingSlash(parentPath)\n : normalizedPath;\n\n const { buildStaticParts, buildParamSlots } = compileBuildParts(\n buildPath,\n // Stryker disable next-line MethodExpression: equivalent — slash-child buildParts: dropping the last segment vs keeping it yields identical buildStaticParts here (no own params on the slash-child). Proven by injection (full suite green).\n slashChild ? segments.slice(0, -1) : segments,\n state.options.urlParamsEncoding,\n state.rootUrlParams,\n );\n\n // #1151: reject a duplicate param name within one route's full path (`/:id/:id`,\n // a param+splat clash `/:x/*x`, or a parent `/a/:x` + child `/:x`). buildParamSlots\n // keeps duplicates; the trie stores them at DIFFERENT positions under the SAME\n // name, so match's later capture silently overwrites the earlier and\n // rewritePathOnMatch then rewrites the user's URL from the single survivor. The\n // #736 conflict guard only fires on DIFFERENTLY-named params at ONE position, so\n // this same-name case slips through.\n const buildParamNames = buildParamSlots.map((slot) => slot.paramName);\n const buildParamNamesSet =\n buildParamNames.length === 0 ? EMPTY_STRING_SET : new Set(buildParamNames);\n\n if (buildParamNamesSet.size !== buildParamNames.length) {\n throwDuplicateParamName(node.fullName, buildParamNames);\n }\n\n // #1242 §5.1/§5.2/§5.3: validate query-param DECLARATIONS. A declared query name\n // must be a clean token — reject one carrying constraint/query metacharacters\n // (`:b?<\\d+>` declares query `<\\d+>`; `?tab=1` declares `tab=1`), and reject a name\n // never round-trips (`<`/`>` in the name). A name shared with a path param\n // (`/a/:tab?tab`) is legal under M2 — separate params/search channels (#1548).\n validateQueryParamDeclarations(node.fullName, declaredQueryParams);\n\n const compiled: CompiledRoute = {\n name: node.fullName,\n parent: parentRoute,\n matchSegments: frozenSegments,\n meta: frozenMeta,\n declaredQueryParams,\n declaredQueryParamsSet:\n declaredQueryParams.length === 0\n ? EMPTY_STRING_SET\n : new Set(declaredQueryParams),\n hasTrailingSlash: matchPath.length > 1 && matchPath.endsWith(\"/\"),\n buildStaticParts,\n buildParamSlots,\n buildParamNamesSet,\n // Initialized here (not added conditionally below) so static and param\n // routes share one hidden class — avoids a megamorphic CompiledRoute (#1009).\n cachedResult: undefined,\n };\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — cachedResult is a pure match() optimization; #buildResult recomputes the same value on a miss (proven: disabling the whole static cache keeps the unit+property suite green)\n if (node.paramMeta.urlParams.length === 0) {\n compiled.cachedResult = Object.freeze({\n segments: compiled.matchSegments,\n params: EMPTY_PARAMS,\n search: EMPTY_SEARCH,\n meta: compiled.meta,\n });\n }\n\n state.routesByName.set(node.fullName, compiled);\n\n if (slashChild) {\n registerSlashChild(state, compiled, parentPath);\n } else {\n registerStandardRoute(state, compiled, matchPath, normalizedPath, node);\n }\n\n return compiled;\n}\n\nfunction buildMeta(\n segments: readonly MatcherInputNode[],\n): Readonly<Record<string, Record<string, \"url\" | \"query\">>> {\n let meta: Record<string, Record<string, \"url\" | \"query\">> | undefined;\n\n for (const segment of segments) {\n if (!hasAnyParam(segment.paramTypeMap)) {\n continue;\n }\n\n meta ??= {};\n meta[segment.fullName] = segment.paramTypeMap;\n }\n\n return meta === undefined ? EMPTY_ROUTE_META : Object.freeze(meta);\n}\n\n// Allocation-free emptiness probe for a segment's paramTypeMap (Object.keys\n// would allocate a fresh array per segment during registration).\nfunction hasAnyParam(\n paramTypeMap: Readonly<Record<string, \"url\" | \"query\">>,\n): boolean {\n for (const key in paramTypeMap) {\n if (Object.hasOwn(paramTypeMap, key)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction registerSlashChild(\n state: RegistrationState,\n compiled: CompiledRoute,\n parentPath: string,\n): void {\n insertSlashChildIntoTrie(state, compiled, parentPath);\n\n const parentNormalized = normalizeTrailingSlash(parentPath);\n const cacheKey = state.options.caseSensitive\n ? parentNormalized\n : parentNormalized.toLowerCase();\n\n if (state.staticCache.has(cacheKey)) {\n state.staticCache.set(cacheKey, compiled);\n }\n}\n\nfunction registerStandardRoute(\n state: RegistrationState,\n compiled: CompiledRoute,\n matchPath: string,\n normalizedPath: string,\n node: MatcherInputNode,\n): void {\n insertIntoTrie(state, compiled, matchPath);\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — staticCache is a pure match() optimization; #traverse resolves the same route on a miss (proven: disabling the whole static cache keeps the unit+property suite green)\n if (node.paramMeta.urlParams.length === 0) {\n const cacheKey = state.options.caseSensitive\n ? normalizedPath\n : // Stryker disable next-line MethodExpression: equivalent — the case-insensitive cache key only governs a hit; a miss falls through to #traverse, which is also case-insensitive\n normalizedPath.toLowerCase();\n\n state.staticCache.set(cacheKey, compiled);\n }\n}\n\nfunction isSlashChild(matchPath: string, parentPath: string): boolean {\n const normalizedMatch = normalizeTrailingSlash(matchPath);\n const normalizedParent = normalizeTrailingSlash(parentPath);\n\n return normalizedMatch === normalizedParent;\n}\n\nfunction collectDeclaredQueryParams(\n rootQueryParams: readonly string[],\n segments: readonly MatcherInputNode[],\n): readonly string[] {\n // Stryker disable next-line ArrayDeclaration: equivalent — the array is populated then returned; a phantom seed element is never read back (declared-param loop skips absent keys, buildQueryString ignores it). Proven by injection.\n const queryParams: string[] = [];\n\n // Include query params declared on the root node (e.g., a root path like \"?mode\")\n if (rootQueryParams.length > 0) {\n queryParams.push(...rootQueryParams);\n }\n\n for (const segment of segments) {\n if (segment.paramMeta.queryParams.length > 0) {\n queryParams.push(...segment.paramMeta.queryParams);\n }\n }\n\n return queryParams.length === 0 ? EMPTY_STRINGS : queryParams;\n}\n\nfunction validateQueryParamDeclarations(\n routeName: string,\n queryParams: readonly string[],\n): void {\n // A path/query name collision (`/a/:tab?tab`) is legal under M2 — `tab` lives\n // in `state.params` AND `state.search` as separate channels (RFC-4 M2 /\n // #1548); the former \"declared as both\" rejection is gone. Only a query name\n // that can never round-trip (contains `<`/`>`) is still rejected.\n for (const name of queryParams) {\n if (INVALID_QUERY_NAME_RGX.test(name)) {\n throwInvalidQueryParamName(routeName, name);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -69,7 +69,10 @@ interface BuildPathOptions {
|
|
|
69
69
|
}
|
|
70
70
|
interface MatchResult {
|
|
71
71
|
readonly segments: readonly MatcherInputNode[];
|
|
72
|
+
/** PATH params (query still folded in for A2 back-compat, RFC-4 M2 / #1548). */
|
|
72
73
|
readonly params: Readonly<Record<string, unknown>>;
|
|
74
|
+
/** QUERY params — the query channel; frozen `{}` when no query string. */
|
|
75
|
+
readonly search: Readonly<Record<string, unknown>>;
|
|
73
76
|
readonly meta: Readonly<Record<string, Record<string, "url" | "query">>>;
|
|
74
77
|
}
|
|
75
78
|
interface SegmentMatcherOptions {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/engine/path-matcher/types.ts"],"mappings":";;;;;;;;;;;UAWiB;;;;;;WAMN;;;;;;WAOA;;;;;;WAOA;;;;;;WAOA,cAAc,SAAS;;;;;;WAOvB;;;;;;;;;;KAeC;UAMK;WACN;WACA;WACA;WACA;WACA,UAAU,oBAAoB;WAC9B,8BAA8B;WAC9B,WAAW;WACX,cAAc,SAAS;;;;;UAqCjB;WACN;WACA;;UA0BM;WACN,mBAAmB
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/engine/path-matcher/types.ts"],"mappings":";;;;;;;;;;;UAWiB;;;;;;WAMN;;;;;;WAOA;;;;;;WAOA;;;;;;WAOA,cAAc,SAAS;;;;;;WAOvB;;;;;;;;;;KAeC;UAMK;WACN;WACA;WACA;WACA;WACA,UAAU,oBAAoB;WAC9B,8BAA8B;WAC9B,WAAW;WACX,cAAc,SAAS;;;;;UAqCjB;WACN;WACA;;UA0BM;WACN,mBAAmB;;WAEnB,QAAQ,SAAS;;WAEjB,QAAQ,SAAS;WACjB,MAAM,SAAS,eAAe;;UAOxB;EACf;EACA;EACA;EACA,oBAAoB;EACpB,mBAAmB,wBAAwB;EAC3C,mBAAmB,QAAQ;;UAGZ;WACN;WACA;WACA;WACA,mBAAmB;WACnB,mBAAmB,wBAAwB;WAC3C,mBAAmB,QAAQ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e={decode:()=>null},t={decode:e=>{let t=e.length;if(t===0)return null;let n=+(e.codePointAt(0)===45);if(n===t||t-n>1&&e.codePointAt(n)===48&&e.codePointAt(n+1)!==46)return null;let r=!1;for(let i=n;i<t;i++){let a=e.codePointAt(i);if(!(a!==void 0&&a>=48&&a<=57)){if(a===46&&!r&&i!==n&&i!==t-1){r=!0;continue}return null}}let i=Number(e);return
|
|
1
|
+
const e={decode:()=>null},t={decode:e=>{let t=e.length;if(t===0)return null;let n=+(e.codePointAt(0)===45);if(n===t||t-n>1&&e.codePointAt(n)===48&&e.codePointAt(n+1)!==46)return null;let r=!1;for(let i=n;i<t;i++){let a=e.codePointAt(i);if(!(a!==void 0&&a>=48&&a<=57)){if(a===46&&!r&&i!==n&&i!==t-1){r=!0;continue}return null}}let i=Number(e);return String(i)!==e||!Number.isSafeInteger(i)&&!r?null:i}},n={auto:t,none:e};exports.autoNumberStrategy=t,exports.noneNumberStrategy=e,exports.numberStrategies=n;
|
|
2
2
|
//# sourceMappingURL=number.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number.js","names":[],"sources":["../../../../../src/engine/search-params/strategies/number.ts"],"sourcesContent":["/**\n * Number decoding strategies.\n *\n * @module search-params/strategies/number\n */\n\nimport type { NumberFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for decoding number values.\n */\nexport interface NumberStrategy {\n decode: (decodedValue: string) => number | null;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * No special number handling - values remain strings.\n */\nexport const noneNumberStrategy: NumberStrategy = {\n decode: () => null, // passthrough\n};\n\n/**\n * Auto-detect numeric values and parse as numbers.\n * Matches integers and decimals via charCode scan (faster than regex for short strings).\n *\n * Recognizes an optional leading `-` so negatives round-trip symmetrically with the\n * values `navigate()`/`build()` produce (`build({ n: -5 })` → `\"n=-5\"`). Leading-zero\n * and unsafe-integer rejection apply to the magnitude regardless of sign. Exponent\n * notation stays a string — `build` never emits a canonical safe exponent, and unsafe\n * exponents would lose precision. (#742)\n */\nexport const autoNumberStrategy: NumberStrategy = {\n decode: (value) => {\n const length = value.length;\n\n if (length === 0) {\n return null;\n }\n\n // Optional leading minus; the magnitude (digits) begins at `start`.\n const start = value.codePointAt(0) === 45 ? 1 : 0; // '-'\n\n // A bare \"-\" has no magnitude.\n if (start === length) {\n return null;\n }\n\n // Leading zeros are not canonical numbers (\"00\", \"007\", \"-007\") — preserve as strings.\n // Allow \"0\" and \"0.x\" (single zero or decimal starting with 0).\n if (\n length - start > 1 &&\n value.codePointAt(start) === 48 &&\n value.codePointAt(start + 1) !== 46\n ) {\n return null;\n }\n\n let hasDot = false;\n\n for (let i = start; i < length; i++) {\n const ch = value.codePointAt(i);\n\n if (ch !== undefined && ch >= 48 && ch <= 57) {\n continue; // '0'-'9'\n }\n\n if (ch === 46 && !hasDot && i !== start && i !== length - 1) {\n hasDot = true;\n\n continue;\n }\n\n return null; // non-digit, non-dot, or invalid dot position\n }\n\n const num = Number(value);\n\n //
|
|
1
|
+
{"version":3,"file":"number.js","names":[],"sources":["../../../../../src/engine/search-params/strategies/number.ts"],"sourcesContent":["/**\n * Number decoding strategies.\n *\n * @module search-params/strategies/number\n */\n\nimport type { NumberFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for decoding number values.\n */\nexport interface NumberStrategy {\n decode: (decodedValue: string) => number | null;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * No special number handling - values remain strings.\n */\nexport const noneNumberStrategy: NumberStrategy = {\n decode: () => null, // passthrough\n};\n\n/**\n * Auto-detect numeric values and parse as numbers.\n * Matches integers and decimals via charCode scan (faster than regex for short strings).\n *\n * Recognizes an optional leading `-` so negatives round-trip symmetrically with the\n * values `navigate()`/`build()` produce (`build({ n: -5 })` → `\"n=-5\"`). Leading-zero\n * and unsafe-integer rejection apply to the magnitude regardless of sign. Exponent\n * notation stays a string — `build` never emits a canonical safe exponent, and unsafe\n * exponents would lose precision. (#742)\n */\nexport const autoNumberStrategy: NumberStrategy = {\n decode: (value) => {\n const length = value.length;\n\n if (length === 0) {\n return null;\n }\n\n // Optional leading minus; the magnitude (digits) begins at `start`.\n const start = value.codePointAt(0) === 45 ? 1 : 0; // '-'\n\n // A bare \"-\" has no magnitude.\n if (start === length) {\n return null;\n }\n\n // Leading zeros are not canonical numbers (\"00\", \"007\", \"-007\") — preserve as strings.\n // Allow \"0\" and \"0.x\" (single zero or decimal starting with 0).\n if (\n length - start > 1 &&\n value.codePointAt(start) === 48 &&\n value.codePointAt(start + 1) !== 46\n ) {\n return null;\n }\n\n let hasDot = false;\n\n for (let i = start; i < length; i++) {\n const ch = value.codePointAt(i);\n\n if (ch !== undefined && ch >= 48 && ch <= 57) {\n continue; // '0'-'9'\n }\n\n if (ch === 46 && !hasDot && i !== start && i !== length - 1) {\n hasDot = true;\n\n continue;\n }\n\n return null; // non-digit, non-dot, or invalid dot position\n }\n\n const num = Number(value);\n\n // The stated criterion is round-trip stability, enforced directly rather than\n // through per-case guards: `build` prints a number with String(), so a value\n // may be coerced only when String() reproduces the exact text we parsed. This\n // subsumes the former negative-zero guard (String(-0) === \"0\" ≠ \"-0\", #898)\n // and closes the decimal family the safe-integer check below deliberately\n // exempts — trailing zeros (\"2.0\" → 2 → \"2\") and precision loss\n // (\"9007199254740993.5\" → 9007199254740994), both of which rebuilt a URL\n // different from the one that was matched. (#1565)\n if (String(num) !== value) {\n return null;\n }\n\n // Unsafe integers pass the text check (2**53 prints back exactly) but lose\n // precision in arithmetic, so they stay strings. Decimals are exempt: a\n // fractional value is never a \"safe integer\", and the check above already\n // proves its text round-trips.\n if (!Number.isSafeInteger(num) && !hasDot) {\n return null;\n }\n\n return num;\n },\n};\n\n// =============================================================================\n// Strategy Map\n// =============================================================================\n\n/**\n * Map of number format to strategy implementation.\n */\nexport const numberStrategies: Record<NumberFormat, NumberStrategy> = {\n auto: autoNumberStrategy,\n none: noneNumberStrategy,\n};\n"],"mappings":"AA0BA,MAAa,EAAqC,CAChD,WAAc,IAChB,EAYa,EAAqC,CAChD,OAAS,GAAU,CACjB,IAAM,EAAS,EAAM,OAErB,GAAI,IAAW,EACb,OAAO,KAIT,IAAM,EAAQ,IAAM,YAAY,CAAC,IAAM,IASvC,GANI,IAAU,GAOZ,EAAS,EAAQ,GACjB,EAAM,YAAY,CAAK,IAAM,IAC7B,EAAM,YAAY,EAAQ,CAAC,IAAM,GAEjC,OAAO,KAGT,IAAI,EAAS,GAEb,IAAK,IAAI,EAAI,EAAO,EAAI,EAAQ,IAAK,CACnC,IAAM,EAAK,EAAM,YAAY,CAAC,EAE1B,SAAO,IAAA,IAAa,GAAM,IAAM,GAAM,IAI1C,IAAI,IAAO,IAAM,CAAC,GAAU,IAAM,GAAS,IAAM,EAAS,EAAG,CAC3D,EAAS,GAET,QACF,CAEA,OAAO,IAFP,CAGF,CAEA,IAAM,EAAM,OAAO,CAAK,EAsBxB,OAZI,OAAO,CAAG,IAAM,GAQhB,CAAC,OAAO,cAAc,CAAG,GAAK,CAAC,EAC1B,KAGF,CACT,CACF,EASa,EAAyD,CACpE,KAAM,EACN,KAAM,CACR"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../path-matcher/parseSegment.js"),t=require("../path-matcher/buildParamMeta.js");function n(e,t){return TypeError(`[router.${e}] ${t}`)}function r(e,t,r,i){let a=new Set;for(let o of e){if(a.has(o))throw n(r,`Invalid path for route "${t}": duplicate parameter name ':${o}' in "${i}" (a param name must be unique within a route — the second binding would overwrite the first)`);a.add(o)}}function i(e,r,i,a
|
|
1
|
+
const e=require("../path-matcher/parseSegment.js"),t=require("../path-matcher/buildParamMeta.js");function n(e,t){return TypeError(`[router.${e}] ${t}`)}function r(e,t,r,i){let a=new Set;for(let o of e){if(a.has(o))throw n(r,`Invalid path for route "${t}": duplicate parameter name ':${o}' in "${i}" (a param name must be unique within a route — the second binding would overwrite the first)`);a.add(o)}}function i(e,r,i,a){for(let o of e)if(t.INVALID_QUERY_NAME_RGX.test(o))throw n(i,`Invalid path for route "${r}": invalid query-param name "${o}" in "${a}" (a query-param name cannot contain '<' or '>' — it would never round-trip; rename the query param)`)}function a(e){let t=!0,n=!1;for(let r of e){if(r===`/`){t=!0;continue}if(t&&=(n=r===`:`||r===`*`,!1),!n&&r.codePointAt(0)>=128)return!0}return!1}function o(e,t,n){switch(e){case`name-less`:case`trailing-marker`:return`Invalid path for route "${t}": parameter marker (':' or '*') without a name in "${n}"`;case`fused-marker`:return`Invalid path for route "${t}": parameter marker (':' or '*') must begin a segment, but "${n}" fuses one to a static prefix (use a boundary marker like "/a/:b")`}}function s(e,t){return e.code===`optional-removed`?`Invalid path for route "${t}": optional params are not supported — "${e.segment}". Declare two sibling routes instead: "${e.withoutSegment}" and "${e.requiredForm}" (the route hierarchy already expresses optionality)`:`Invalid path for route "${t}": regex constraints are not supported — '<' and '>' are reserved in path segments ("${e.segment}"). Match the segment as a plain string and validate the value in a guard (canActivate) or app code`}function c(c,l,u,d){if(typeof c!=`string`){let e;throw e=c===null?`null`:Array.isArray(c)?`array`:typeof c,n(u,`Route path must be a string, got ${e}`)}if(c===``)return;if(/\s/.test(c))throw n(u,`Invalid path for route "${l}": whitespace not allowed in "${c}"`);if(!/^([/?~]|[^/]+$)/.test(c))throw n(u,`Route "${l}" has invalid path format: "${c}". Path should start with '/', '~', '?' or be a relative segment.`);if(c.includes(`//`))throw n(u,`Invalid path for route "${l}": double slashes not allowed in "${c}"`);let{pathPattern:f,urlParams:p,queryParams:m}=t.buildParamMeta(c);r(p,l,u,c),i(m,l,u,c);let h=e.describeRemovedForm(f);if(h!==void 0)throw n(u,s(h,l));let g=e.findSegmentGrammarError(f);if(g!==void 0)throw n(u,o(g,l,c));if(a(f))throw n(u,`Invalid path for route "${l}": non-ASCII static segment in "${c}" — match compares static segments raw and rejects non-ASCII input, so this route would never match. Percent-encode it (e.g. '/caf%C3%A9') or use a param`);let _=d&&d.paramMeta.urlParams.length>0;if(c.startsWith(`~`)&&_)throw n(u,`Absolute path "${c}" cannot be used under parent route with URL parameters`)}exports.validateRoutePath=c;
|
|
2
2
|
//# sourceMappingURL=routes.js.map
|