@real-router/core 0.79.0 → 0.79.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Router-DGUed2F4.js +2 -0
- package/dist/cjs/{Router-BRuC-daj.js.map → Router-DGUed2F4.js.map} +1 -1
- package/dist/cjs/{Router-BG5yNYUG.d.ts → Router.d.ts} +5 -4
- package/dist/cjs/Router.d.ts.map +1 -0
- package/dist/cjs/Router.js +2 -0
- package/dist/cjs/Router.js.map +1 -0
- package/dist/cjs/RouterError.d.ts +182 -0
- package/dist/cjs/RouterError.d.ts.map +1 -0
- package/dist/cjs/RouterError.js +2 -0
- package/dist/cjs/RouterError.js.map +1 -0
- package/dist/cjs/api/cloneRouter.d.ts +86 -0
- package/dist/cjs/api/cloneRouter.d.ts.map +1 -0
- package/dist/cjs/api/cloneRouter.js +2 -0
- package/dist/cjs/api/cloneRouter.js.map +1 -0
- package/dist/cjs/api/getDependenciesApi.d.ts +7 -0
- package/dist/cjs/api/getDependenciesApi.d.ts.map +1 -0
- package/dist/cjs/api/getDependenciesApi.js +2 -0
- package/dist/cjs/api/getDependenciesApi.js.map +1 -0
- package/dist/cjs/api/getLifecycleApi.d.ts +7 -0
- package/dist/cjs/api/getLifecycleApi.d.ts.map +1 -0
- package/dist/cjs/api/getLifecycleApi.js +2 -0
- package/dist/cjs/api/getLifecycleApi.js.map +1 -0
- package/dist/cjs/api/getPluginApi.d.ts +7 -0
- package/dist/cjs/api/getPluginApi.d.ts.map +1 -0
- package/dist/cjs/api/getPluginApi.js +2 -0
- package/dist/cjs/api/getPluginApi.js.map +1 -0
- package/dist/cjs/api/getRoutesApi.d.ts +7 -0
- package/dist/cjs/api/getRoutesApi.d.ts.map +1 -0
- package/dist/cjs/api/getRoutesApi.js +2 -0
- package/dist/cjs/api/getRoutesApi.js.map +1 -0
- package/dist/cjs/api/helpers.js +2 -0
- package/dist/cjs/api/helpers.js.map +1 -0
- package/dist/cjs/api/types.d.ts +9 -0
- package/dist/cjs/api/types.d.ts.map +1 -0
- package/dist/cjs/api.d.ts +8 -105
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/{cloneRouter-3AOUeQ8M.js → cloneRouter-Ch25z3Z2.js} +2 -2
- package/dist/cjs/{cloneRouter-3AOUeQ8M.js.map → cloneRouter-Ch25z3Z2.js.map} +1 -1
- package/dist/cjs/constants.d.ts +25 -0
- package/dist/cjs/constants.d.ts.map +1 -0
- package/dist/cjs/constants.js +2 -0
- package/dist/cjs/constants.js.map +1 -0
- package/dist/cjs/createRouter.d.ts +23 -0
- package/dist/cjs/createRouter.d.ts.map +1 -0
- package/dist/cjs/createRouter.js +2 -0
- package/dist/cjs/createRouter.js.map +1 -0
- package/dist/cjs/engine/builder/buildTree.js +2 -0
- package/dist/cjs/engine/builder/buildTree.js.map +1 -0
- package/dist/cjs/engine/builder/computeCaches.js +2 -0
- package/dist/cjs/engine/builder/computeCaches.js.map +1 -0
- package/dist/cjs/engine/builder/createRouteTree.js +2 -0
- package/dist/cjs/engine/builder/createRouteTree.js.map +1 -0
- package/dist/cjs/engine/builder/types.d.ts +48 -0
- package/dist/cjs/engine/builder/types.d.ts.map +1 -0
- package/dist/cjs/engine/createMatcher.d.ts +29 -0
- package/dist/cjs/engine/createMatcher.d.ts.map +1 -0
- package/dist/cjs/engine/createMatcher.js +2 -0
- package/dist/cjs/engine/createMatcher.js.map +1 -0
- package/dist/cjs/engine/operations/routeTreeToDefinitions.js +2 -0
- package/dist/cjs/engine/operations/routeTreeToDefinitions.js.map +1 -0
- package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts +16 -0
- package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts.map +1 -0
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js +2 -0
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +1 -0
- package/dist/cjs/engine/path-matcher/buildParamMeta.js +2 -0
- package/dist/cjs/engine/path-matcher/buildParamMeta.js.map +1 -0
- package/dist/cjs/engine/path-matcher/encoding.js +2 -0
- package/dist/cjs/engine/path-matcher/encoding.js.map +1 -0
- package/dist/cjs/engine/path-matcher/parseSegment.js +2 -0
- package/dist/cjs/engine/path-matcher/parseSegment.js.map +1 -0
- package/dist/cjs/engine/path-matcher/pathUtils.js +2 -0
- package/dist/cjs/engine/path-matcher/pathUtils.js.map +1 -0
- package/dist/cjs/engine/path-matcher/percentEncoding.js +2 -0
- package/dist/cjs/engine/path-matcher/percentEncoding.js.map +1 -0
- package/dist/cjs/engine/path-matcher/registration/buildParts.js +2 -0
- package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +1 -0
- package/dist/cjs/engine/path-matcher/registration/context.js +2 -0
- package/dist/cjs/engine/path-matcher/registration/context.js.map +1 -0
- package/dist/cjs/engine/path-matcher/registration/errors.js +2 -0
- package/dist/cjs/engine/path-matcher/registration/errors.js.map +1 -0
- package/dist/cjs/engine/path-matcher/registration/index.js +2 -0
- package/dist/cjs/engine/path-matcher/registration/index.js.map +1 -0
- package/dist/cjs/engine/path-matcher/registration/trie.js +2 -0
- package/dist/cjs/engine/path-matcher/registration/trie.js.map +1 -0
- package/dist/cjs/engine/path-matcher/registration/trieNodes.js +2 -0
- package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +1 -0
- package/dist/cjs/{types-DaXCTxi1.d.ts → engine/path-matcher/types.d.ts} +2 -47
- package/dist/cjs/engine/path-matcher/types.d.ts.map +1 -0
- package/dist/cjs/engine/search-params/decode.js +2 -0
- package/dist/cjs/engine/search-params/decode.js.map +1 -0
- package/dist/cjs/engine/search-params/encode.js +2 -0
- package/dist/cjs/engine/search-params/encode.js.map +1 -0
- package/dist/cjs/engine/search-params/searchParams.js +2 -0
- package/dist/cjs/engine/search-params/searchParams.js.map +1 -0
- package/dist/cjs/engine/search-params/strategies/array.js +2 -0
- package/dist/cjs/engine/search-params/strategies/array.js.map +1 -0
- package/dist/cjs/engine/search-params/strategies/boolean.js +2 -0
- package/dist/cjs/engine/search-params/strategies/boolean.js.map +1 -0
- package/dist/cjs/engine/search-params/strategies/index.js +2 -0
- package/dist/cjs/engine/search-params/strategies/index.js.map +1 -0
- package/dist/cjs/engine/search-params/strategies/null.js +2 -0
- package/dist/cjs/engine/search-params/strategies/null.js.map +1 -0
- package/dist/cjs/engine/search-params/strategies/number.js +2 -0
- package/dist/cjs/engine/search-params/strategies/number.js.map +1 -0
- package/dist/cjs/engine/search-params/types.d.ts +72 -0
- package/dist/cjs/engine/search-params/types.d.ts.map +1 -0
- package/dist/cjs/engine/search-params/utils.js +2 -0
- package/dist/cjs/engine/search-params/utils.js.map +1 -0
- package/dist/cjs/engine/validation/route-batch.d.ts +38 -0
- package/dist/cjs/engine/validation/route-batch.d.ts.map +1 -0
- package/dist/cjs/engine/validation/route-batch.js +2 -0
- package/dist/cjs/engine/validation/route-batch.js.map +1 -0
- package/dist/cjs/engine/validation/routes.js +2 -0
- package/dist/cjs/engine/validation/routes.js.map +1 -0
- package/dist/cjs/foundation/event-emitter/EventEmitter.js +2 -0
- package/dist/cjs/foundation/event-emitter/EventEmitter.js.map +1 -0
- package/dist/cjs/foundation/fsm/fsm.js +2 -0
- package/dist/cjs/foundation/fsm/fsm.js.map +1 -0
- package/dist/cjs/foundation/logger/RouterLogger.js +2 -0
- package/dist/cjs/foundation/logger/RouterLogger.js.map +1 -0
- package/dist/cjs/foundation/logger/constants.js +2 -0
- package/dist/cjs/foundation/logger/constants.js.map +1 -0
- package/dist/cjs/getNavigator.d.ts +6 -0
- package/dist/cjs/getNavigator.d.ts.map +1 -0
- package/dist/cjs/getNavigator.js +2 -0
- package/dist/cjs/getNavigator.js.map +1 -0
- package/dist/cjs/guards.js +2 -0
- package/dist/cjs/guards.js.map +1 -0
- package/dist/cjs/helpers.js +2 -0
- package/dist/cjs/helpers.js.map +1 -0
- package/dist/cjs/index.d.ts +17 -239
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/internals.d.ts +99 -0
- package/dist/cjs/internals.d.ts.map +1 -0
- package/dist/cjs/internals.js +2 -0
- package/dist/cjs/internals.js.map +1 -0
- package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.d.ts +10 -0
- package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.d.ts.map +1 -0
- package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js +2 -0
- package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js.map +1 -0
- package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js +2 -0
- package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +1 -0
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +2 -0
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +1 -0
- package/dist/cjs/namespaces/NavigationNamespace/constants.js +2 -0
- package/dist/cjs/namespaces/NavigationNamespace/constants.js.map +1 -0
- package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js +2 -0
- package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js.map +1 -0
- package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js +2 -0
- package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js.map +1 -0
- package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js +2 -0
- package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js.map +1 -0
- package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js +2 -0
- package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +1 -0
- package/dist/cjs/namespaces/OptionsNamespace/constants.js +2 -0
- package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +1 -0
- package/dist/cjs/namespaces/OptionsNamespace/helpers.js +2 -0
- package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +1 -0
- package/dist/cjs/namespaces/OptionsNamespace/validators.js +2 -0
- package/dist/cjs/namespaces/OptionsNamespace/validators.js.map +1 -0
- package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js +2 -0
- package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js.map +1 -0
- package/dist/cjs/namespaces/PluginsNamespace/constants.js +2 -0
- package/dist/cjs/namespaces/PluginsNamespace/constants.js.map +1 -0
- package/dist/cjs/namespaces/PluginsNamespace/validators.js +2 -0
- package/dist/cjs/namespaces/PluginsNamespace/validators.js.map +1 -0
- package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.ts +174 -0
- package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.ts.map +1 -0
- package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js +2 -0
- package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js.map +1 -0
- package/dist/cjs/namespaces/RouteLifecycleNamespace/types.d.ts +17 -0
- package/dist/cjs/namespaces/RouteLifecycleNamespace/types.d.ts.map +1 -0
- package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js +2 -0
- package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js.map +1 -0
- package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js +2 -0
- package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js.map +1 -0
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +2 -0
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +1 -0
- package/dist/cjs/namespaces/RoutesNamespace/constants.js +2 -0
- package/dist/cjs/namespaces/RoutesNamespace/constants.js.map +1 -0
- package/dist/cjs/namespaces/RoutesNamespace/forwardChain.d.ts +5 -0
- package/dist/cjs/namespaces/RoutesNamespace/forwardChain.d.ts.map +1 -0
- package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js +2 -0
- package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js.map +1 -0
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js +2 -0
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +1 -0
- package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js +2 -0
- package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js.map +1 -0
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +42 -0
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts.map +1 -0
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +2 -0
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +1 -0
- package/dist/cjs/namespaces/RoutesNamespace/types.d.ts +57 -0
- package/dist/cjs/namespaces/RoutesNamespace/types.d.ts.map +1 -0
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +2 -0
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +1 -0
- package/dist/cjs/namespaces/StateNamespace/helpers.js +2 -0
- package/dist/cjs/namespaces/StateNamespace/helpers.js.map +1 -0
- package/dist/cjs/routerFSM.js +2 -0
- package/dist/cjs/routerFSM.js.map +1 -0
- package/dist/cjs/stateMetaStore.js +2 -0
- package/dist/cjs/stateMetaStore.js.map +1 -0
- package/dist/cjs/transitionPath.js +2 -0
- package/dist/cjs/transitionPath.js.map +1 -0
- package/dist/cjs/{RouterValidator-CZ8kPkrI.d.ts → types/RouterValidator.d.ts} +2 -2
- package/dist/cjs/types/RouterValidator.d.ts.map +1 -0
- package/dist/cjs/types/api.d.ts +184 -0
- package/dist/cjs/types/api.d.ts.map +1 -0
- package/dist/cjs/types/base.d.ts +86 -0
- package/dist/cjs/types/base.d.ts.map +1 -0
- package/dist/cjs/types/constants.d.ts +72 -0
- package/dist/cjs/types/constants.d.ts.map +1 -0
- package/dist/cjs/types/internal.d.ts +9 -0
- package/dist/cjs/types/internal.d.ts.map +1 -0
- package/dist/cjs/types/limits.d.ts +45 -0
- package/dist/cjs/types/limits.d.ts.map +1 -0
- package/dist/cjs/types/route-node-types.d.ts +46 -0
- package/dist/cjs/types/route-node-types.d.ts.map +1 -0
- package/dist/cjs/types/router.d.ts +313 -0
- package/dist/cjs/types/router.d.ts.map +1 -0
- package/dist/cjs/types/tree-changed.d.ts +55 -0
- package/dist/cjs/types/tree-changed.d.ts.map +1 -0
- package/dist/cjs/types.d.ts +208 -2
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/utils/createRequestScope.d.ts +111 -0
- package/dist/cjs/utils/createRequestScope.d.ts.map +1 -0
- package/dist/cjs/utils/createRequestScope.js +2 -0
- package/dist/cjs/utils/createRequestScope.js.map +1 -0
- package/dist/cjs/utils/getStaticPaths.d.ts +7 -0
- package/dist/cjs/utils/getStaticPaths.d.ts.map +1 -0
- package/dist/cjs/utils/getStaticPaths.js +2 -0
- package/dist/cjs/utils/getStaticPaths.js.map +1 -0
- package/dist/cjs/utils/hydrateRouter.d.ts +69 -0
- package/dist/cjs/utils/hydrateRouter.d.ts.map +1 -0
- package/dist/cjs/utils/hydrateRouter.js +2 -0
- package/dist/cjs/utils/hydrateRouter.js.map +1 -0
- package/dist/cjs/utils/serializeRouterState.d.ts +84 -0
- package/dist/cjs/utils/serializeRouterState.d.ts.map +1 -0
- package/dist/cjs/utils/serializeRouterState.js +2 -0
- package/dist/cjs/utils/serializeRouterState.js.map +1 -0
- package/dist/cjs/utils/serializeState.d.ts +50 -0
- package/dist/cjs/utils/serializeState.d.ts.map +1 -0
- package/dist/cjs/utils/serializeState.js +2 -0
- package/dist/cjs/utils/serializeState.js.map +1 -0
- package/dist/cjs/utils.d.ts +5 -1
- package/dist/cjs/utils.js +1 -1
- package/dist/cjs/validation.d.ts +6 -518
- package/dist/cjs/wiring/wireNamespaces.js +2 -0
- package/dist/cjs/wiring/wireNamespaces.js.map +1 -0
- package/dist/esm/Router-Bk6PfSE8.mjs +2 -0
- package/dist/esm/{Router-DikzugaD.mjs.map → Router-Bk6PfSE8.mjs.map} +1 -1
- package/dist/esm/{Router-Cy75qbgU.d.mts → Router.d.mts} +5 -4
- package/dist/esm/Router.d.mts.map +1 -0
- package/dist/esm/RouterError.d.mts +182 -0
- package/dist/esm/RouterError.d.mts.map +1 -0
- package/dist/esm/api/cloneRouter.d.mts +86 -0
- package/dist/esm/api/cloneRouter.d.mts.map +1 -0
- package/dist/esm/api/getDependenciesApi.d.mts +7 -0
- package/dist/esm/api/getDependenciesApi.d.mts.map +1 -0
- package/dist/esm/api/getLifecycleApi.d.mts +7 -0
- package/dist/esm/api/getLifecycleApi.d.mts.map +1 -0
- package/dist/esm/api/getPluginApi.d.mts +7 -0
- package/dist/esm/api/getPluginApi.d.mts.map +1 -0
- package/dist/esm/api/getRoutesApi.d.mts +7 -0
- package/dist/esm/api/getRoutesApi.d.mts.map +1 -0
- package/dist/esm/api/types.d.mts +9 -0
- package/dist/esm/api/types.d.mts.map +1 -0
- package/dist/esm/api.d.mts +8 -105
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/{cloneRouter-CAh5vA3l.mjs → cloneRouter-D4rnqmi9.mjs} +2 -2
- package/dist/esm/{cloneRouter-CAh5vA3l.mjs.map → cloneRouter-D4rnqmi9.mjs.map} +1 -1
- package/dist/esm/constants.d.mts +25 -0
- package/dist/esm/constants.d.mts.map +1 -0
- package/dist/esm/createRouter.d.mts +23 -0
- package/dist/esm/createRouter.d.mts.map +1 -0
- package/dist/esm/engine/builder/types.d.mts +48 -0
- package/dist/esm/engine/builder/types.d.mts.map +1 -0
- package/dist/esm/engine/createMatcher.d.mts +29 -0
- package/dist/esm/engine/createMatcher.d.mts.map +1 -0
- package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts +16 -0
- package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts.map +1 -0
- package/dist/esm/{types-DaXCTxi1.d.mts → engine/path-matcher/types.d.mts} +2 -47
- package/dist/esm/engine/path-matcher/types.d.mts.map +1 -0
- package/dist/esm/engine/search-params/types.d.mts +72 -0
- package/dist/esm/engine/search-params/types.d.mts.map +1 -0
- package/dist/esm/engine/validation/route-batch.d.mts +38 -0
- package/dist/esm/engine/validation/route-batch.d.mts.map +1 -0
- package/dist/esm/getNavigator.d.mts +6 -0
- package/dist/esm/getNavigator.d.mts.map +1 -0
- package/dist/esm/index.d.mts +17 -239
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/internals.d.mts +99 -0
- package/dist/esm/internals.d.mts.map +1 -0
- package/dist/esm/namespaces/DependenciesNamespace/dependenciesStore.d.mts +10 -0
- package/dist/esm/namespaces/DependenciesNamespace/dependenciesStore.d.mts.map +1 -0
- package/dist/esm/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.mts +174 -0
- package/dist/esm/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.d.mts.map +1 -0
- package/dist/esm/namespaces/RouteLifecycleNamespace/types.d.mts +17 -0
- package/dist/esm/namespaces/RouteLifecycleNamespace/types.d.mts.map +1 -0
- package/dist/esm/namespaces/RoutesNamespace/forwardChain.d.mts +5 -0
- package/dist/esm/namespaces/RoutesNamespace/forwardChain.d.mts.map +1 -0
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +42 -0
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts.map +1 -0
- package/dist/esm/namespaces/RoutesNamespace/types.d.mts +57 -0
- package/dist/esm/namespaces/RoutesNamespace/types.d.mts.map +1 -0
- package/dist/esm/{RouterValidator-CZ8kPkrI.d.mts → types/RouterValidator.d.mts} +2 -2
- package/dist/esm/types/RouterValidator.d.mts.map +1 -0
- package/dist/esm/types/api.d.mts +184 -0
- package/dist/esm/types/api.d.mts.map +1 -0
- package/dist/esm/types/base.d.mts +86 -0
- package/dist/esm/types/base.d.mts.map +1 -0
- package/dist/esm/types/constants.d.mts +72 -0
- package/dist/esm/types/constants.d.mts.map +1 -0
- package/dist/esm/types/internal.d.mts +9 -0
- package/dist/esm/types/internal.d.mts.map +1 -0
- package/dist/esm/types/limits.d.mts +45 -0
- package/dist/esm/types/limits.d.mts.map +1 -0
- package/dist/esm/types/route-node-types.d.mts +46 -0
- package/dist/esm/types/route-node-types.d.mts.map +1 -0
- package/dist/esm/types/router.d.mts +313 -0
- package/dist/esm/types/router.d.mts.map +1 -0
- package/dist/esm/types/tree-changed.d.mts +55 -0
- package/dist/esm/types/tree-changed.d.mts.map +1 -0
- package/dist/esm/types.d.mts +208 -2
- package/dist/esm/types.d.mts.map +1 -0
- package/dist/esm/utils/createRequestScope.d.mts +111 -0
- package/dist/esm/utils/createRequestScope.d.mts.map +1 -0
- package/dist/esm/utils/getStaticPaths.d.mts +7 -0
- package/dist/esm/utils/getStaticPaths.d.mts.map +1 -0
- package/dist/esm/utils/hydrateRouter.d.mts +69 -0
- package/dist/esm/utils/hydrateRouter.d.mts.map +1 -0
- package/dist/esm/utils/serializeRouterState.d.mts +84 -0
- package/dist/esm/utils/serializeRouterState.d.mts.map +1 -0
- package/dist/esm/utils/serializeState.d.mts +50 -0
- package/dist/esm/utils/serializeState.d.mts.map +1 -0
- package/dist/esm/utils.d.mts +5 -1
- package/dist/esm/utils.mjs +1 -1
- package/dist/esm/validation.d.mts +6 -518
- package/package.json +2 -2
- package/dist/cjs/Router-BG5yNYUG.d.ts.map +0 -1
- package/dist/cjs/Router-BRuC-daj.js +0 -2
- package/dist/cjs/RouterValidator-CZ8kPkrI.d.ts.map +0 -1
- package/dist/cjs/api.d.ts.map +0 -1
- package/dist/cjs/index-CAnGQpnz.d.ts +0 -974
- package/dist/cjs/index-CAnGQpnz.d.ts.map +0 -1
- package/dist/cjs/index-DDrryY2U.d.ts +0 -309
- package/dist/cjs/index-DDrryY2U.d.ts.map +0 -1
- package/dist/cjs/index.d.ts.map +0 -1
- package/dist/cjs/types-DaXCTxi1.d.ts.map +0 -1
- package/dist/cjs/validation.d.ts.map +0 -1
- package/dist/esm/Router-Cy75qbgU.d.mts.map +0 -1
- package/dist/esm/Router-DikzugaD.mjs +0 -2
- package/dist/esm/RouterValidator-CZ8kPkrI.d.mts.map +0 -1
- package/dist/esm/api.d.mts.map +0 -1
- package/dist/esm/index-CAnGQpnz.d.mts +0 -974
- package/dist/esm/index-CAnGQpnz.d.mts.map +0 -1
- package/dist/esm/index-DnSU6huX.d.mts +0 -309
- package/dist/esm/index-DnSU6huX.d.mts.map +0 -1
- package/dist/esm/index.d.mts.map +0 -1
- package/dist/esm/types-DaXCTxi1.d.mts.map +0 -1
- package/dist/esm/validation.d.mts.map +0 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=e=>e===58||e===42;function t(e){let t=[],n=0;for(let r=0;r<e.length;r+=1)e.charCodeAt(r)===47&&(t.push(e.slice(n,r)),n=r+1);return t.push(e.slice(n)),t}function n(t){let n=t.length;if(n===0)return{kind:`static`,text:``};if(!e(t.charCodeAt(0))){for(let r=0;r<n;r+=1){let i=t.charCodeAt(r);if(i===60||i===62)return{error:`constraint-removed`};if(e(i)&&r+1<n)return{error:`fused-marker`}}return t.charCodeAt(n-1)===63?{error:`name-less`}:{kind:`static`,text:t}}let r=t.charCodeAt(0)===42,i=1;for(;i<n;){let e=t.charCodeAt(i);if(e===60||e===62||e===63)break;i+=1}let a=t.slice(1,i);return a.length===0?{error:`name-less`}:e(a.charCodeAt(a.length-1))?{error:`trailing-marker`}:i<n?t.charCodeAt(i)===63?{error:`optional-removed`}:{error:`constraint-removed`}:r?{kind:`splat`,name:a}:{kind:`param`,name:a}}function r(e){for(let r of t(e)){let e=n(r);if(`error`in e)return e.error}}function i(e){let r=t(e);for(let e=0;e<r.length;e+=1){let t=n(r[e]);if(`error`in t){if(t.error===`optional-removed`){let t=r[e],n=[...r];return n[e]=t.slice(0,t.indexOf(`?`)),{code:`optional-removed`,segment:t,withoutSegment:r.filter((t,n)=>n!==e).join(`/`),requiredForm:n.join(`/`)}}return t.error===`constraint-removed`?{code:`constraint-removed`,segment:r[e]}:void 0}}}exports.describeRemovedForm=i,exports.findSegmentGrammarError=r,exports.parseSegment=n,exports.splitPathSegments=t;
|
|
2
|
+
//# sourceMappingURL=parseSegment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseSegment.js","names":[],"sources":["../../../../src/engine/path-matcher/parseSegment.ts"],"sourcesContent":["/**\n * Canonical route-segment tokenizer.\n *\n * The single owner of \"where does a name / marker / constraint end\" for ONE\n * path segment (post-`/`-split). It replaces the five name-boundary compositions\n * of `PARAM_NAME_PATTERN` currently spread across `buildParamMeta` (L1),\n * `registration` (L2 build + L3 trie), and `route-tree`'s validation gate (L4),\n * so those layers can never disagree on a boundary (the gate↔backstop drift\n * class — #858 / #1050 / #1150 / #1311 / #1149 / #1324).\n *\n * A single left-to-right `charCodeAt` scan produces either a token tuple or a\n * typed error. Grammar — **3 tokens only** (`static | :param | *splat`); the\n * grammar has no optional `:x?` or `<re>` constraint forms. Any `<`/`>` or a\n * post-name `?` in the path is a *registration error* carrying a replacement\n * recipe (`optional-removed` / `constraint-removed`), not a token:\n * 1. Leading `:`/`*` → param/splat; otherwise `static` (a marker glued *after* a\n * static prefix ⇒ `fused-marker`; any `<`/`>` (a former constraint) ⇒\n * `constraint-removed`; a trailing `?` on a marker-less segment ⇒ `name-less`\n * — the modifier has no param name, #1241 / `/faq?`).\n * 2. name = any char except `<`/`?` (no `/` remains inside a segment); a name\n * ending in a bare `:`/`*` ⇒ `trailing-marker` (#1324). A *mid* marker stays\n * a name char — `:a:b` → name `a:b`, preserved.\n * 3. empty name ⇒ `name-less` (#858).\n * 4. a `<` after the name (a former `<re>` constraint) ⇒ `constraint-removed`.\n * 5. a post-name `?` (a former optional modifier, on `:param` or `*splat`) ⇒\n * `optional-removed`.\n *\n * @module parseSegment\n */\n\n/* eslint-disable unicorn/prefer-code-point, unicorn/prefer-includes-over-repeated-comparisons, sonarjs/cognitive-complexity -- charCodeAt code-unit scan + a single inlined branchy pass are this RFC's char-scan perf basis (§9); the same deliberate choices as registration/trie.ts hasNonAsciiSegment (#1285) and SegmentMatcher's inlined #traverseFrom. A `[LT,GT,QUESTION].includes(code)` boundary check would allocate an array literal per scanned char. Markers compared are ASCII (`:` `*` `<` `>` `?`, < 0x80). */\n\n/** A successfully tokenized segment (3-token grammar: `static | :param | *splat`). */\nexport type SegmentTokens =\n | { readonly kind: \"static\"; readonly text: string }\n | { readonly kind: \"param\"; readonly name: string }\n | { readonly kind: \"splat\"; readonly name: string };\n\n/** Grammar-shape rejections, each mirroring a registration guard. */\nexport type SegmentErrorCode =\n | \"name-less\" // #858 — a marker with no name\n | \"trailing-marker\" // #1324 — a param name ending in a bare `:`/`*`\n | \"fused-marker\" // #1050 — a marker glued after a static prefix\n | \"optional-removed\" // M1 — a `:x?`/`*x?` optional modifier (removed; two sibling routes)\n | \"constraint-removed\"; // M1 — a `<re>` constraint or stray `<`/`>` (removed; validate in a guard)\n\nexport interface SegmentError {\n readonly error: SegmentErrorCode;\n}\n\nconst COLON = 58; // :\nconst STAR = 42; // *\nconst LT = 60; // <\nconst GT = 62; // >\nconst QUESTION = 63; // ?\nconst SLASH = 47; // /\n\nconst isMarker = (code: number): boolean => code === COLON || code === STAR;\n\n/**\n * Splits a path into its `/`-delimited segments. A plain `/`-split (M1): the\n * 3-token grammar has no `<...>` constraint whose body could legally contain a\n * `/`, so no constraint-awareness is needed — a stray `<`/`>` is a\n * `constraint-removed` error, caught per segment by `parseSegment`. This is the\n * **segmentation** half of the path-grammar unification: `parseSegment` owns the\n * per-segment grammar, `splitPathSegments` owns where a segment begins and ends.\n *\n * @param path - a route path (query already stripped by the caller)\n * @returns the segments in order, including empty leading/trailing/`//` segments\n * (the caller skips empties, matching the current behaviour)\n */\nexport function splitPathSegments(path: string): string[] {\n const segments: string[] = [];\n let start = 0;\n\n for (let i = 0; i < path.length; i += 1) {\n if (path.charCodeAt(i) !== SLASH) {\n continue;\n }\n\n segments.push(path.slice(start, i));\n start = i + 1;\n }\n\n segments.push(path.slice(start));\n\n return segments;\n}\n\n/**\n * Tokenizes a single path segment (already split on `/`).\n *\n * @param segment - one `/`-delimited segment of a route path\n * @returns the segment's tokens, or a typed grammar error\n */\nexport function parseSegment(segment: string): SegmentTokens | SegmentError {\n const length = segment.length;\n\n if (length === 0) {\n return { kind: \"static\", text: \"\" };\n }\n\n // ---- static segment (no leading marker) -------------------------------\n if (!isMarker(segment.charCodeAt(0))) {\n for (let i = 0; i < length; i += 1) {\n const code = segment.charCodeAt(i);\n\n // A `<`/`>` (a former `<re>` constraint or a stray delimiter) is no longer\n // grammar — M1 removed constraints. Reject with the constraint recipe.\n if (code === LT || code === GT) {\n return { error: \"constraint-removed\" };\n }\n\n // A marker glued after a static prefix is extracted as a param by build/meta\n // but compiled as a static literal by the trie (#1050) — reject it as fused.\n // A marker ENDING the segment (a static ending in `:`/`*` — `/a:`, `/a*`, F2)\n // is NOT fused: caught by `i + 1 < length` being false. Every other following\n // char is fused — including a `?` (`a:?`): that shape never reaches the\n // tokenizer through a real path (a `?` after a bare marker is not a valid\n // `:name?` form, so the query mask strips it before `/`-segmentation), so a\n // direct call correctly reports fused-marker. (`a<`/`a>` already returned\n // `constraint-removed` above, so no `<`-follows exception is needed here.)\n if (isMarker(code) && i + 1 < length) {\n return { error: \"fused-marker\" };\n }\n }\n\n // A trailing `?` is a former optional modifier; on a marker-less segment (no\n // param name) it is a modifier-with-no-name — name-less (#858/#1241, `/faq?`),\n // NOT `optional-removed` (there is no param to route to two siblings). The\n // backstop rejects it by the SAME rule: its `endsWith(\"?\")` fork routes the\n // segment to `extractParamName` → this tokenizer. Owning the `?` here (not\n // only in the marker branch) is what lets the gate and backstop agree on it —\n // otherwise the gate reads `faq?` as a valid static (#1324 §4).\n if (segment.charCodeAt(length - 1) === QUESTION) {\n return { error: \"name-less\" };\n }\n\n return { kind: \"static\", text: segment };\n }\n\n const splat = segment.charCodeAt(0) === STAR;\n\n // ---- name: up to the first `<`/`>` (former constraint delimiter, reserved —\n // В1.3) or `?` (former optional). A segment holds no `/`. -----------------\n let cursor = 1;\n\n while (cursor < length) {\n const code = segment.charCodeAt(cursor);\n\n if (code === LT || code === GT || code === QUESTION) {\n break;\n }\n\n cursor += 1;\n }\n\n const name = segment.slice(1, cursor);\n\n if (name.length === 0) {\n return { error: \"name-less\" }; // #858\n }\n\n if (isMarker(name.charCodeAt(name.length - 1))) {\n return { error: \"trailing-marker\" }; // #1324\n }\n\n // ---- former constraint / optional modifiers (removed in M1) ------------\n // The name scan stops at the first `<`/`>` or `?`. Either is a form removed\n // in M1: a `<re>` constraint (also a stray `<`/`>` — В1.3), or a `:x?`/`*x?`\n // optional. Only `?` is the optional; `<`/`>` are the constraint recipe.\n // Reject with the matching replacement recipe rather than tokenize it.\n if (cursor < length) {\n return segment.charCodeAt(cursor) === QUESTION\n ? { error: \"optional-removed\" }\n : { error: \"constraint-removed\" }; // LT or GT\n }\n\n return splat ? { kind: \"splat\", name } : { kind: \"param\", name };\n}\n\n/**\n * Returns the first per-segment grammar error in a path, or `undefined` if every\n * segment tokenizes cleanly.\n *\n * The **validation-facing** entry over the tokenizer: `route-tree`'s\n * `validateRoutePath` calls this instead of re-running its own split+parse loop,\n * so the gate and the matcher's own grammar cannot drift (#1324) and the loop\n * stays single-sourced here — the tokenizer primitives (`parseSegment`,\n * `splitPathSegments`) need not leak into the package's public surface. An empty\n * segment tokenizes as `static` (never an error), so leading/trailing/`//`\n * empties are skipped naturally.\n *\n * @param path - a route path (query already stripped by the caller)\n * @returns the first `SegmentErrorCode` (scanned left to right), or `undefined`\n */\nexport function findSegmentGrammarError(\n path: string,\n): SegmentErrorCode | undefined {\n for (const segment of splitPathSegments(path)) {\n const token = parseSegment(segment);\n\n if (\"error\" in token) {\n return token.error;\n }\n }\n\n return undefined;\n}\n\n/** A removed-form (M1) match, describing the offending segment and — for an\n * optional — the two sibling paths that replace it (path without the optional\n * segment + path with the param made required). The route-tree gate uses this to\n * build a route-contextual replacement recipe; the matcher backstop uses only the\n * error code (a shorter, path-free recipe). */\nexport type RemovedForm =\n | {\n readonly code: \"optional-removed\";\n readonly segment: string;\n readonly withoutSegment: string;\n readonly requiredForm: string;\n }\n | { readonly code: \"constraint-removed\"; readonly segment: string };\n\n/**\n * The rich (route-tree gate) view over the tokenizer for a removed form: finds\n * the first `optional-removed` / `constraint-removed` segment and, for an\n * optional, computes its two replacement sibling paths from the ACTUAL path\n * (dropping the segment → without-form; dropping the trailing `?` → required\n * form). Returns `undefined` if no removed form is present (the gate then uses\n * `findSegmentGrammarError` for a surviving grammar rejection).\n *\n * @param path - a route path (query already stripped by the caller)\n */\nexport function describeRemovedForm(path: string): RemovedForm | undefined {\n const segments = splitPathSegments(path);\n\n for (let i = 0; i < segments.length; i += 1) {\n const token = parseSegment(segments[i]);\n\n if (!(\"error\" in token)) {\n continue;\n }\n\n // First error wins (mirrors `findSegmentGrammarError`): describe it ONLY if\n // it is a removed form, else return undefined so the caller falls to the\n // surviving-code message — this keeps the gate's reason in lockstep with the\n // matcher backstop's first-error verdict.\n if (token.error === \"optional-removed\") {\n const segment = segments[i];\n const required = [...segments];\n\n // Drop the `?` optional modifier AND everything after it (the tokenizer\n // stopped the name at the first `?`, so it is the modifier). Using the `?`\n // index — not a blind `slice(0, -1)` — keeps the required sibling VALID for\n // a reverse/compound form whose `?` is not the last char: `:b?<x>` → `:b`\n // (not `:b?<x`), `:id??` → `:id` (not `:id?`). #1516\n required[i] = segment.slice(0, segment.indexOf(\"?\"));\n\n return {\n code: \"optional-removed\",\n segment,\n withoutSegment: segments.filter((_, j) => j !== i).join(\"/\"),\n requiredForm: required.join(\"/\"),\n };\n }\n\n return token.error === \"constraint-removed\"\n ? { code: \"constraint-removed\", segment: segments[i] }\n : undefined;\n }\n\n return undefined;\n}\n"],"mappings":"AAkDA,MAOM,EAAY,GAA0B,IAAS,IAAS,IAAS,GAcvE,SAAgB,EAAkB,EAAwB,CACxD,IAAM,EAAqB,CAAC,EACxB,EAAQ,EAEZ,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,GAAK,EAChC,EAAK,WAAW,CAAC,IAAM,KAI3B,EAAS,KAAK,EAAK,MAAM,EAAO,CAAC,CAAC,EAClC,EAAQ,EAAI,GAKd,OAFA,EAAS,KAAK,EAAK,MAAM,CAAK,CAAC,EAExB,CACT,CAQA,SAAgB,EAAa,EAA+C,CAC1E,IAAM,EAAS,EAAQ,OAEvB,GAAI,IAAW,EACb,MAAO,CAAE,KAAM,SAAU,KAAM,EAAG,EAIpC,GAAI,CAAC,EAAS,EAAQ,WAAW,CAAC,CAAC,EAAG,CACpC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,GAAK,EAAG,CAClC,IAAM,EAAO,EAAQ,WAAW,CAAC,EAIjC,GAAI,IAAS,IAAM,IAAS,GAC1B,MAAO,CAAE,MAAO,oBAAqB,EAYvC,GAAI,EAAS,CAAI,GAAK,EAAI,EAAI,EAC5B,MAAO,CAAE,MAAO,cAAe,CAEnC,CAaA,OAJI,EAAQ,WAAW,EAAS,CAAC,IAAM,GAC9B,CAAE,MAAO,WAAY,EAGvB,CAAE,KAAM,SAAU,KAAM,CAAQ,CACzC,CAEA,IAAM,EAAQ,EAAQ,WAAW,CAAC,IAAM,GAIpC,EAAS,EAEb,KAAO,EAAS,GAAQ,CACtB,IAAM,EAAO,EAAQ,WAAW,CAAM,EAEtC,GAAI,IAAS,IAAM,IAAS,IAAM,IAAS,GACzC,MAGF,GAAU,CACZ,CAEA,IAAM,EAAO,EAAQ,MAAM,EAAG,CAAM,EAqBpC,OAnBI,EAAK,SAAW,EACX,CAAE,MAAO,WAAY,EAG1B,EAAS,EAAK,WAAW,EAAK,OAAS,CAAC,CAAC,EACpC,CAAE,MAAO,iBAAkB,EAQhC,EAAS,EACJ,EAAQ,WAAW,CAAM,IAAM,GAClC,CAAE,MAAO,kBAAmB,EAC5B,CAAE,MAAO,oBAAqB,EAG7B,EAAQ,CAAE,KAAM,QAAS,MAAK,EAAI,CAAE,KAAM,QAAS,MAAK,CACjE,CAiBA,SAAgB,EACd,EAC8B,CAC9B,IAAK,IAAM,KAAW,EAAkB,CAAI,EAAG,CAC7C,IAAM,EAAQ,EAAa,CAAO,EAElC,GAAI,UAAW,EACb,OAAO,EAAM,KAEjB,CAGF,CA0BA,SAAgB,EAAoB,EAAuC,CACzE,IAAM,EAAW,EAAkB,CAAI,EAEvC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,GAAK,EAAG,CAC3C,IAAM,EAAQ,EAAa,EAAS,EAAE,EAEhC,aAAW,EAQjB,IAAI,EAAM,QAAU,mBAAoB,CACtC,IAAM,EAAU,EAAS,GACnB,EAAW,CAAC,GAAG,CAAQ,EAS7B,MAFA,GAAS,GAAK,EAAQ,MAAM,EAAG,EAAQ,QAAQ,GAAG,CAAC,EAE5C,CACL,KAAM,mBACN,UACA,eAAgB,EAAS,QAAQ,EAAG,IAAM,IAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAC3D,aAAc,EAAS,KAAK,GAAG,CACjC,CACF,CAEA,OAAO,EAAM,QAAU,qBACnB,CAAE,KAAM,qBAAsB,QAAS,EAAS,EAAG,EACnD,IAAA,EAJJ,CAKF,CAGF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=Object.freeze(Object.create(null));function t(){return{staticChildren:e,hasChildren:!1,paramChild:void 0,splatChild:void 0,route:void 0,slashChildRoute:void 0}}function n(e){return e.length>1&&e.endsWith(`/`)?e.slice(0,-1):e}function r(e,t){return e===``?t:t===``?e:e+t}exports.EMPTY_STATIC_CHILDREN=e,exports.buildFullPath=r,exports.createSegmentNode=t,exports.normalizeTrailingSlash=n;
|
|
2
|
+
//# sourceMappingURL=pathUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathUtils.js","names":[],"sources":["../../../../src/engine/path-matcher/pathUtils.ts"],"sourcesContent":["import type { SegmentNode } from \"./types\";\n\n// Every trie node used to allocate its own `Object.create(null)` for\n// `staticChildren`. A null-proto empty object is V8 dictionary-mode from birth\n// (~192 B — own map + backing store, ~3× a plain `{}`), and the leaf-majority\n// (one node per registered route) never gains a static child, so it held that\n// empty object purely to answer the match-path `key in node.staticChildren`\n// read. Share ONE frozen empty null-proto sentinel across every fresh node;\n// `processSegment` (registration/trie.ts) copies-on-write — swaps in a fresh\n// mutable null-proto object — before the first real write. The frozen shell\n// fails loud if a write ever skips that guard. Mirrors the #1009 `EMPTY_*`\n// sentinels in `registration/context.ts`.\nexport const EMPTY_STATIC_CHILDREN: Record<string, SegmentNode> = Object.freeze(\n Object.create(null) as Record<string, SegmentNode>,\n);\n\nexport function createSegmentNode(): SegmentNode {\n return {\n staticChildren: EMPTY_STATIC_CHILDREN,\n // Stryker disable next-line BooleanLiteral: equivalent — for a leaf splat node the `!hasChildren` fast path and the `#traverseFrom` fallback both set `params[name] = slice` and return `sn.route`; nodes that gain children overwrite this flag during registration. Proven: forcing `true` keeps the full suite green (it only un-covers the now-unreachable fast path).\n hasChildren: false,\n paramChild: undefined,\n splatChild: undefined,\n route: undefined,\n slashChildRoute: undefined,\n };\n}\n\nexport function normalizeTrailingSlash(path: string): string {\n if (path.length > 1 && path.endsWith(\"/\")) {\n return path.slice(0, -1);\n }\n\n return path;\n}\n\nexport function buildFullPath(parentPath: string, nodePath: string): string {\n // Stryker disable next-line StringLiteral,BlockStatement: equivalent — empty-operand fast path; `parentPath + nodePath` already yields the identical string when `parentPath === \"\"`, so emptying the block or never triggering it (sentinel comparand) changes no result. ConditionalExpression stays live (killable `->true` sibling).\n if (parentPath === \"\") {\n return nodePath;\n }\n\n // Stryker disable next-line StringLiteral,BlockStatement: equivalent — symmetric empty-operand fast path; `parentPath + nodePath` already equals `parentPath` when `nodePath === \"\"`. ConditionalExpression stays live (killable `->true` sibling).\n if (nodePath === \"\") {\n return parentPath;\n }\n\n return parentPath + nodePath;\n}\n"],"mappings":"AAYA,MAAa,EAAqD,OAAO,OACvE,OAAO,OAAO,IAAI,CACpB,EAEA,SAAgB,GAAiC,CAC/C,MAAO,CACL,eAAgB,EAEhB,YAAa,GACb,WAAY,IAAA,GACZ,WAAY,IAAA,GACZ,MAAO,IAAA,GACP,gBAAiB,IAAA,EACnB,CACF,CAEA,SAAgB,EAAuB,EAAsB,CAK3D,OAJI,EAAK,OAAS,GAAK,EAAK,SAAS,GAAG,EAC/B,EAAK,MAAM,EAAG,EAAE,EAGlB,CACT,CAEA,SAAgB,EAAc,EAAoB,EAA0B,CAW1E,OATI,IAAe,GACV,EAIL,IAAa,GACR,EAGF,EAAa,CACtB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function t(t){let n=0;for(;n<t.length;)if(t.codePointAt(n)===37){if(n+2>=t.length)return!1;let r=t.codePointAt(n+1)??0,i=t.codePointAt(n+2)??0;if(!e(r)||!e(i))return!1;n+=3}else n++;return!0}exports.validatePercentEncoding=t;
|
|
2
|
+
//# sourceMappingURL=percentEncoding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"percentEncoding.js","names":[],"sources":["../../../../src/engine/path-matcher/percentEncoding.ts"],"sourcesContent":["function isHexCodePoint(code: number): boolean {\n return (\n (code >= 0x30 && code <= 0x39) ||\n (code >= 0x41 && code <= 0x46) ||\n (code >= 0x61 && code <= 0x66)\n );\n}\n\nexport function validatePercentEncoding(value: string): boolean {\n let i = 0;\n\n while (i < value.length) {\n if (value.codePointAt(i) === 0x25 /* % */) {\n // Stryker disable next-line ArithmeticOperator,BlockStatement: equivalent — a truncated \"%\" is independently rejected downstream by the `?? 0` fallback (codePointAt past end -> 0, never a hex code point), so weakening (`i - 2`) or emptying this early-exit changes no observable result. Proven: removing the whole block keeps the full unit+property+stress suite green. The ConditionalExpression/EqualityOperator variants here stay live (they have killable siblings).\n if (i + 2 >= value.length) {\n return false;\n }\n\n /* v8 ignore start -- @preserve: codePointAt cannot return undefined due to bounds check above */\n const hex1 = value.codePointAt(i + 1) ?? 0;\n const hex2 = value.codePointAt(i + 2) ?? 0;\n /* v8 ignore stop */\n\n if (!isHexCodePoint(hex1) || !isHexCodePoint(hex2)) {\n return false;\n }\n\n i += 3;\n } else {\n i++;\n }\n }\n\n return true;\n}\n"],"mappings":"AAAA,SAAS,EAAe,EAAuB,CAC7C,OACG,GAAQ,IAAQ,GAAQ,IACxB,GAAQ,IAAQ,GAAQ,IACxB,GAAQ,IAAQ,GAAQ,GAE7B,CAEA,SAAgB,EAAwB,EAAwB,CAC9D,IAAI,EAAI,EAER,KAAO,EAAI,EAAM,QACf,GAAI,EAAM,YAAY,CAAC,IAAM,GAAc,CAEzC,GAAI,EAAI,GAAK,EAAM,OACjB,MAAO,GAIT,IAAM,EAAO,EAAM,YAAY,EAAI,CAAC,GAAK,EACnC,EAAO,EAAM,YAAY,EAAI,CAAC,GAAK,EAGzC,GAAI,CAAC,EAAe,CAAI,GAAK,CAAC,EAAe,CAAI,EAC/C,MAAO,GAGT,GAAK,CACP,KACE,KAIJ,MAAO,EACT"}
|
|
@@ -0,0 +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 o=new Set,s=new Set;for(let e of i){for(let t of e.paramMeta.urlParams)o.add(t);for(let t of e.paramMeta.spatParams)s.add(t)}if(o.size===0)return{buildStaticParts:[t],buildParamSlots:n.EMPTY_PARAM_SLOTS};let c=[],l=[],u=``,d=e.splitPathSegments(t);for(let[t,n]of d.entries()){t>0&&(u+=`/`);let i=e.parseSegment(n);if(`error`in i||i.kind===`static`){u+=n;continue}c.push(u),u=``,l.push(r(i,s,a))}return c.push(u),{buildStaticParts:c,buildParamSlots:l}}exports.compileBuildParts=i;
|
|
2
|
+
//# sourceMappingURL=buildParts.js.map
|
|
@@ -0,0 +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 const token = parseSegment(pathSegment);\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,EAIA,CACA,IAAM,EAAe,IAAI,IACnB,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,EAErD,IAAK,GAAM,CAAC,EAAG,KAAgB,EAAa,QAAQ,EAAG,CACjD,EAAI,IACN,GAAW,KAGb,IAAM,EAAQC,EAAAA,aAAa,CAAW,EAEtC,GAAI,UAAW,GAAS,EAAM,OAAS,SAAU,CAG/C,GAAW,EACX,QACF,CAGA,EAAM,KAAK,CAAO,EAClB,EAAU,GACV,EAAM,KAAK,EAAmB,EAAO,EAAgB,CAAQ,CAAC,CAChE,CAIA,OAFA,EAAM,KAAK,CAAO,EAEX,CAAE,iBAAkB,EAAO,gBAAiB,CAAM,CAC3D"}
|
|
@@ -0,0 +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;
|
|
2
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +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] Name collision in route "${e}": "${t}" is declared as BOTH a path param (':${t}') and a query param ('?${t}'). buildPath would emit its value twice (once in the path, once in the query). Rename one.`)}function d(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 f(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=d,exports.throwEmptyParamName=t,exports.throwInvalidQueryParamName=l,exports.throwNonAsciiStatic=o,exports.throwParamNameConflict=e,exports.throwPathQueryNameCollision=u,exports.throwSegmentGrammarError=s,exports.throwSlashChildUnderDynamicParent=f;
|
|
2
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +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,w);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,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,r){for(let i of n)t.INVALID_QUERY_NAME_RGX.test(i)&&a.throwInvalidQueryParamName(e,i),r.has(i)&&a.throwPathQueryNameCollision(e,i)}exports.registerNode=s;
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":"4MA4BA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACM,CACN,IAAM,EAAS,EAAK,WAAa,GAE5B,GACH,EAAS,KAAK,CAAI,EAGpB,IAAM,EAAa,EAAK,SAIlB,EACJ,EAAK,YAAcA,EAAAA,iBACf,EAAK,KACL,EAAK,UAAU,YACf,EACJ,GAAc,EAAY,WAAW,GAAG,EACpC,EAAY,MAAM,CAAC,EACnB,EACA,EAAc,EAAa,EAAkB,EASnD,IAAK,IAAM,KAAWC,EAAAA,kBAAkB,CAAW,EAAG,CACpD,IAAM,EAAQC,EAAAA,aAAa,CAAO,EAE9B,UAAW,GACb,EAAA,yBAAyB,EAAM,MAAO,CAAO,CAEjD,CAGA,IAAM,EAAW,EAEX,EAAY,EAAa,EAAWC,EAAAA,cAAc,EAAY,CAAQ,EAItE,EAAqC,EACvC,EACA,EACE,EACA,EACA,EAPoB,EAAa,GAAK,EAStC,EACA,CACF,EAEJ,IAAK,IAAM,KAAS,EAAK,SAAS,OAAO,EACvC,EAAa,EAAO,EAAO,EAAW,EAAU,CAAY,EAGzD,GACH,EAAS,IAAI,CAEjB,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACe,CACf,IAAM,EAAa,EAAa,EAAW,CAAU,EAE/C,EAAiB,OAAO,OAAO,CAAC,GAAG,CAAQ,CAAC,EAC5C,EAAa,EAAU,CAAc,EAErC,EAAiBC,EAAAA,uBAAuB,CAAS,EAEjD,EAAsB,EAC1B,EAAM,gBACN,CACF,EAOM,CAAE,mBAAkB,mBAAoBC,EAAAA,kBAJ5B,EACdD,EAAAA,uBAAuB,CAAU,EACjC,EAKF,EAAa,EAAS,MAAM,EAAG,EAAE,EAAI,EACrC,EAAM,QAAQ,iBAChB,EASM,EAAkB,EAAgB,IAAK,GAAS,EAAK,SAAS,EAC9D,EACJ,EAAgB,SAAW,EAAIE,EAAAA,iBAAmB,IAAI,IAAI,CAAe,EAEvE,EAAmB,OAAS,EAAgB,QAC9C,EAAA,wBAAwB,EAAK,SAAU,CAAe,EAQxD,EACE,EAAK,SACL,EACA,CACF,EAEA,IAAM,EAA0B,CAC9B,KAAM,EAAK,SACX,OAAQ,EACR,cAAe,EACf,KAAM,EACN,sBACA,uBACE,EAAoB,SAAW,EAC3BA,EAAAA,iBACA,IAAI,IAAI,CAAmB,EACjC,iBAAkB,EAAU,OAAS,GAAK,EAAU,SAAS,GAAG,EAChE,mBACA,kBACA,qBAGA,aAAc,IAAA,EAChB,EAmBA,OAhBI,EAAK,UAAU,UAAU,SAAW,IACtC,EAAS,aAAe,OAAO,OAAO,CACpC,SAAU,EAAS,cACnB,OAAQC,EAAAA,aACR,KAAM,EAAS,IACjB,CAAC,GAGH,EAAM,aAAa,IAAI,EAAK,SAAU,CAAQ,EAE1C,EACF,EAAmB,EAAO,EAAU,CAAU,EAE9C,EAAsB,EAAO,EAAU,EAAW,EAAgB,CAAI,EAGjE,CACT,CAEA,SAAS,EACP,EAC2D,CAC3D,IAAI,EAEJ,IAAK,IAAM,KAAW,EACf,EAAY,EAAQ,YAAY,IAIrC,IAAS,CAAC,EACV,EAAK,EAAQ,UAAY,EAAQ,cAGnC,OAAO,IAAS,IAAA,GAAYC,EAAAA,iBAAmB,OAAO,OAAO,CAAI,CACnE,CAIA,SAAS,EACP,EACS,CACT,IAAK,IAAM,KAAO,EAChB,GAAI,OAAO,OAAO,EAAc,CAAG,EACjC,MAAO,GAIX,MAAO,EACT,CAEA,SAAS,EACP,EACA,EACA,EACM,CACN,EAAA,yBAAyB,EAAO,EAAU,CAAU,EAEpD,IAAM,EAAmBJ,EAAAA,uBAAuB,CAAU,EACpD,EAAW,EAAM,QAAQ,cAC3B,EACA,EAAiB,YAAY,EAE7B,EAAM,YAAY,IAAI,CAAQ,GAChC,EAAM,YAAY,IAAI,EAAU,CAAQ,CAE5C,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACM,CAIN,GAHA,EAAA,eAAe,EAAO,EAAU,CAAS,EAGrC,EAAK,UAAU,UAAU,SAAW,EAAG,CACzC,IAAM,EAAW,EAAM,QAAQ,cAC3B,EAEA,EAAe,YAAY,EAE/B,EAAM,YAAY,IAAI,EAAU,CAAQ,CAC1C,CACF,CAEA,SAAS,EAAa,EAAmB,EAA6B,CAIpE,OAHwBA,EAAAA,uBAAuB,CAG1B,IAFIA,EAAAA,uBAAuB,CAEN,CAC5C,CAEA,SAAS,EACP,EACA,EACmB,CAEnB,IAAM,EAAwB,CAAC,EAG3B,EAAgB,OAAS,GAC3B,EAAY,KAAK,GAAG,CAAe,EAGrC,IAAK,IAAM,KAAW,EAChB,EAAQ,UAAU,YAAY,OAAS,GACzC,EAAY,KAAK,GAAG,EAAQ,UAAU,WAAW,EAIrD,OAAO,EAAY,SAAW,EAAIK,EAAAA,cAAgB,CACpD,CAEA,SAAS,EACP,EACA,EACA,EACM,CACN,IAAK,IAAM,KAAQ,EACbC,EAAAA,uBAAuB,KAAK,CAAI,GAClC,EAAA,2BAA2B,EAAW,CAAI,EAGxC,EAAc,IAAI,CAAI,GACxB,EAAA,4BAA4B,EAAW,CAAI,CAGjD"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../pathUtils.js"),t=require("./errors.js"),n=require("./trieNodes.js");function r(e,n){e.route!==void 0&&e.route!==n&&t.throwDuplicateRoutePath(e.route.name,n.name),e.route=n}function i(e){for(let t=0;t<e.length;t++)if(e.charCodeAt(t)>=128)return!0;return!1}function a(t,n,i){let a=e.normalizeTrailingSlash(i);if(a===`/`){r(t.root,n);return}o(t,t.root,a,1,n)}function o(e,t,n,i,a){let o=n.length;for(;i<=o;){let r=n.indexOf(`/`,i),a=r===-1?o:r,s=n.slice(i,a);t=u(e,t,s),i=a+1}r(t,a)}function s(e,n,r){r.slice(r.lastIndexOf(`/`)+1).startsWith(`*`)&&t.throwSlashChildUnderDynamicParent(n.name,r);let i=c(e,r);i.slashChildRoute=n}function c(e,t){return l(e,e.root,t)}function l(t,n,r){let i=e.normalizeTrailingSlash(r);if(i===`/`||i===``)return n;let a=n,o=1,s=i.length;for(;o<=s;){let e=i.indexOf(`/`,o),n=e===-1?s:e;if(n<=o)break;let r=i.slice(o,n);a=u(t,a,r),o=n+1}return a}function u(r,a,o){if(o.startsWith(`*`)){let e=n.ensureSplatChild(a,n.extractParamName(o));return a.hasChildren=!0,e}if(o.startsWith(`:`)){let e=n.ensureParamChild(a,n.extractParamName(o));return a.hasChildren=!0,e}i(o)&&t.throwNonAsciiStatic(o);let s=r.options.caseSensitive?o:o.toLowerCase();return s in a.staticChildren||(a.staticChildren===e.EMPTY_STATIC_CHILDREN&&(a.staticChildren=Object.create(null)),a.staticChildren[s]=e.createSegmentNode(),a.hasChildren=!0),a.staticChildren[s]}exports.insertIntoTrie=a,exports.insertSlashChildIntoTrie=s;
|
|
2
|
+
//# sourceMappingURL=trie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trie.js","names":["normalizeTrailingSlash","ensureSplatChild","extractParamName","ensureParamChild","EMPTY_STATIC_CHILDREN","createSegmentNode"],"sources":["../../../../../src/engine/path-matcher/registration/trie.ts"],"sourcesContent":["// Trie insertion + walking: the recursive `insertIntoTrieFrom` linear walk, slash-child\n// insertion, per-segment `processSegment`, and the `walkTrie` lookups. Builds the\n// segment trie from the node builders in `./trieNodes`.\n\nimport {\n createSegmentNode,\n EMPTY_STATIC_CHILDREN,\n normalizeTrailingSlash,\n} from \"../pathUtils\";\nimport {\n throwDuplicateRoutePath,\n throwNonAsciiStatic,\n throwSlashChildUnderDynamicParent,\n} from \"./errors\";\nimport {\n ensureParamChild,\n ensureSplatChild,\n extractParamName,\n} from \"./trieNodes\";\n\nimport type { CompiledRoute, SegmentNode } from \"../types\";\nimport type { RegistrationState } from \"./context\";\n\n/**\n * #1153: writes a terminal route, rejecting a second write by a DIFFERENT route —\n * two routes compiling to the same effective path (flat vs nested `/a/b`, or `/x`\n * vs `/x/`), where the later would silently shadow the earlier (its deep link\n * would resolve to the other route). A revisit by the SAME route is idempotent.\n * (With the 3-token grammar every terminal write is a full insertion — the former\n * WEAK optional-omit `??=` writes are gone with optional params.)\n */\nfunction writeTerminalRoute(node: SegmentNode, compiled: CompiledRoute): void {\n if (node.route !== undefined && node.route !== compiled) {\n throwDuplicateRoutePath(node.route.name, compiled.name);\n }\n\n node.route = compiled;\n}\n\n/**\n * #1154: whether a STATIC segment carries a code point outside ASCII (≥ U+0080).\n * A raw non-ASCII static (`café`) registers but never matches — match rejects\n * non-ASCII input and compares static keys raw. A per-code-point scan (`for…of`\n * iterates by code point, so surrogate pairs are handled).\n */\nfunction hasNonAsciiSegment(segment: string): boolean {\n // #1285: charCodeAt (code UNIT) index loop, not for-of code points. For a\n // \"has non-ASCII\" predicate the result is identical — any surrogate (≥ 0xD800) is\n // itself ≥ 0x80, so an astral char is still flagged — without the iterator +\n // code-point decoding cost per static segment of every registered route.\n for (let i = 0; i < segment.length; i++) {\n // eslint-disable-next-line unicorn/prefer-code-point -- charCodeAt (code unit) is intentional: a \"has non-ASCII\" test needs only units (a surrogate is itself >= 0x80), and it skips the code-point decoding that codePointAt does per index (#1285)\n if (segment.charCodeAt(i) >= 0x80) {\n return true;\n }\n }\n\n return false;\n}\n\nexport function insertIntoTrie(\n state: RegistrationState,\n compiled: CompiledRoute,\n fullPath: string,\n): void {\n const normalized = normalizeTrailingSlash(fullPath);\n\n if (normalized === \"/\") {\n writeTerminalRoute(state.root, compiled);\n\n return;\n }\n\n insertIntoTrieFrom(state, state.root, normalized, 1, compiled);\n}\n\nfunction insertIntoTrieFrom(\n state: RegistrationState,\n node: SegmentNode,\n path: string,\n start: number,\n compiled: CompiledRoute,\n): void {\n const length = path.length;\n\n // 3-token grammar (M1): every segment is `static | :param | *splat` — a single\n // linear walk down the trie (no optional take/skip fork, so no `visited` memo,\n // and — since a route never revisits a slot it created — no #736 `ownNodes` set).\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 node = processSegment(state, node, segment);\n start = segmentEnd + 1;\n }\n\n writeTerminalRoute(node, compiled);\n}\n\nexport function insertSlashChildIntoTrie(\n state: RegistrationState,\n compiled: CompiledRoute,\n parentPath: string,\n): void {\n // #1242 §5.4: an index route (path \"/\") under a parent whose path ends in a\n // SPLAT is unreachable — `slashChildRoute` sits on the splat node, which\n // `#matchSplat`'s fast path never reads. A REQUIRED-param parent (`/users/:id`,\n // `/a/:b/c`) has a single form and its slash-child is coherent (existing\n // behaviour) — allowed. (The former OPTIONAL-param arm, #1294, is gone with\n // optional params — M1.)\n const lastSegment = parentPath.slice(parentPath.lastIndexOf(\"/\") + 1);\n\n if (lastSegment.startsWith(\"*\")) {\n throwSlashChildUnderDynamicParent(compiled.name, parentPath);\n }\n\n const node = walkTrie(state, parentPath);\n\n node.slashChildRoute = compiled;\n}\n\nfunction walkTrie(state: RegistrationState, fullPath: string): SegmentNode {\n return walkTrieFrom(state, state.root, fullPath);\n}\n\nfunction walkTrieFrom(\n state: RegistrationState,\n startNode: SegmentNode,\n path: string,\n): SegmentNode {\n const normalized = normalizeTrailingSlash(path);\n\n /* v8 ignore start -- defensive: slash-child always passes valid path */\n if (normalized === \"/\" || normalized === \"\") {\n return startNode;\n }\n /* v8 ignore stop */\n\n let node = startNode;\n let start = 1;\n const length = normalized.length;\n\n // Slash-child re-walks an already-inserted path of the same route family —\n // names always match, so the conflict guard never fires.\n\n while (start <= length) {\n const end = normalized.indexOf(\"/\", start);\n const segmentEnd = end === -1 ? length : end;\n\n /* v8 ignore start -- defensive: indexOf always returns valid index for non-empty segments */\n if (segmentEnd <= start) {\n break;\n }\n /* v8 ignore stop */\n\n const segment = normalized.slice(start, segmentEnd);\n\n node = processSegment(state, node, segment);\n start = segmentEnd + 1;\n }\n\n return node;\n}\n\nfunction processSegment(\n state: RegistrationState,\n node: SegmentNode,\n segment: string,\n): SegmentNode {\n if (segment.startsWith(\"*\")) {\n // extractParamName (via parseSegment) rejects a name-less `*` (#858) AND a\n // trailing marker (`*y:`, #1324) — the splat name shares one boundary with\n // the param branch and the route-tree gate.\n const splatName = extractParamName(segment);\n const child = ensureSplatChild(node, splatName);\n\n // Stryker disable next-line BooleanLiteral: equivalent — sets hasChildren on the node ACQUIRING a splat child; only a splat NODE's own hasChildren is read (in #matchSplat), and splat-of-splat is unreachable (splat is terminal-greedy). Proven by injection.\n node.hasChildren = true;\n\n return child;\n }\n\n if (segment.startsWith(\":\")) {\n const paramName = extractParamName(segment);\n const child = ensureParamChild(node, paramName);\n\n node.hasChildren = true;\n\n return child;\n }\n\n // The segment does not start with a marker, so it compiles as a static literal.\n // A `:`/`*` fused to a static prefix within it (`a:b`, `x:id`, `a*b`, #1050) was\n // already rejected by the per-segment grammar pass in `registerNode` — a\n // fused-marker segment never reaches this literal compilation.\n\n // #1154: a raw non-ASCII code point in a STATIC segment (`/café`, `/меню`).\n // match rejects any input byte ≥ 0x80 (`#scanPath`) AND compares static trie\n // keys raw (never percent-decoded), so such a route registers but is\n // unmatchable — `buildPath` emits `/café`, which its own `match` rejects (a dead\n // route). Reject at registration with the percent-encode workaround. A non-ASCII\n // PARAM name or constraint is unaffected (only static text is compared raw).\n if (hasNonAsciiSegment(segment)) {\n throwNonAsciiStatic(segment);\n }\n\n const key = state.options.caseSensitive ? segment : segment.toLowerCase();\n\n if (!(key in node.staticChildren)) {\n // Copy-on-write off the shared frozen EMPTY_STATIC_CHILDREN sentinel: the\n // first static child this node gains earns it a fresh mutable null-proto map.\n if (node.staticChildren === EMPTY_STATIC_CHILDREN) {\n node.staticChildren = Object.create(null) as Record<string, SegmentNode>;\n }\n\n node.staticChildren[key] = createSegmentNode();\n node.hasChildren = true;\n }\n\n return node.staticChildren[key];\n}\n"],"mappings":"wFA+BA,SAAS,EAAmB,EAAmB,EAA+B,CACxE,EAAK,QAAU,IAAA,IAAa,EAAK,QAAU,GAC7C,EAAA,wBAAwB,EAAK,MAAM,KAAM,EAAS,IAAI,EAGxD,EAAK,MAAQ,CACf,CAQA,SAAS,EAAmB,EAA0B,CAKpD,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,OAAQ,IAElC,GAAI,EAAQ,WAAW,CAAC,GAAK,IAC3B,MAAO,GAIX,MAAO,EACT,CAEA,SAAgB,EACd,EACA,EACA,EACM,CACN,IAAM,EAAaA,EAAAA,uBAAuB,CAAQ,EAElD,GAAI,IAAe,IAAK,CACtB,EAAmB,EAAM,KAAM,CAAQ,EAEvC,MACF,CAEA,EAAmB,EAAO,EAAM,KAAM,EAAY,EAAG,CAAQ,CAC/D,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACM,CACN,IAAM,EAAS,EAAK,OAKpB,KAAO,GAAS,GAAQ,CACtB,IAAM,EAAM,EAAK,QAAQ,IAAK,CAAK,EAC7B,EAAa,IAAQ,GAAK,EAAS,EACnC,EAAU,EAAK,MAAM,EAAO,CAAU,EAE5C,EAAO,EAAe,EAAO,EAAM,CAAO,EAC1C,EAAQ,EAAa,CACvB,CAEA,EAAmB,EAAM,CAAQ,CACnC,CAEA,SAAgB,EACd,EACA,EACA,EACM,CAOc,EAAW,MAAM,EAAW,YAAY,GAAG,EAAI,CAErD,CAAC,CAAC,WAAW,GAAG,GAC5B,EAAA,kCAAkC,EAAS,KAAM,CAAU,EAG7D,IAAM,EAAO,EAAS,EAAO,CAAU,EAEvC,EAAK,gBAAkB,CACzB,CAEA,SAAS,EAAS,EAA0B,EAA+B,CACzE,OAAO,EAAa,EAAO,EAAM,KAAM,CAAQ,CACjD,CAEA,SAAS,EACP,EACA,EACA,EACa,CACb,IAAM,EAAaA,EAAAA,uBAAuB,CAAI,EAG9C,GAAI,IAAe,KAAO,IAAe,GACvC,OAAO,EAIT,IAAI,EAAO,EACP,EAAQ,EACN,EAAS,EAAW,OAK1B,KAAO,GAAS,GAAQ,CACtB,IAAM,EAAM,EAAW,QAAQ,IAAK,CAAK,EACnC,EAAa,IAAQ,GAAK,EAAS,EAGzC,GAAI,GAAc,EAChB,MAIF,IAAM,EAAU,EAAW,MAAM,EAAO,CAAU,EAElD,EAAO,EAAe,EAAO,EAAM,CAAO,EAC1C,EAAQ,EAAa,CACvB,CAEA,OAAO,CACT,CAEA,SAAS,EACP,EACA,EACA,EACa,CACb,GAAI,EAAQ,WAAW,GAAG,EAAG,CAK3B,IAAM,EAAQC,EAAAA,iBAAiB,EADbC,EAAAA,iBAAiB,CACU,CAAC,EAK9C,MAFA,GAAK,YAAc,GAEZ,CACT,CAEA,GAAI,EAAQ,WAAW,GAAG,EAAG,CAE3B,IAAM,EAAQC,EAAAA,iBAAiB,EADbD,EAAAA,iBAAiB,CACU,CAAC,EAI9C,MAFA,GAAK,YAAc,GAEZ,CACT,CAaI,EAAmB,CAAO,GAC5B,EAAA,oBAAoB,CAAO,EAG7B,IAAM,EAAM,EAAM,QAAQ,cAAgB,EAAU,EAAQ,YAAY,EAaxE,OAXM,KAAO,EAAK,iBAGZ,EAAK,iBAAmBE,EAAAA,wBAC1B,EAAK,eAAiB,OAAO,OAAO,IAAI,GAG1C,EAAK,eAAe,GAAOC,EAAAA,kBAAkB,EAC7C,EAAK,YAAc,IAGd,EAAK,eAAe,EAC7B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../parseSegment.js"),t=require("../pathUtils.js"),n=require("./errors.js");function r(t){let r=e.parseSegment(t);return(`error`in r||r.kind===`static`)&&n.throwEmptyParamName(),r.name}function i(e,r){return e.paramChild?e.paramChild.name!==r&&n.throwParamNameConflict(e.paramChild.name,r,`:`):e.paramChild={node:t.createSegmentNode(),name:r},e.paramChild.node}function a(e,r){return e.splatChild?e.splatChild.name!==r&&n.throwParamNameConflict(e.splatChild.name,r,`*`):e.splatChild={node:t.createSegmentNode(),name:r},e.splatChild.node}exports.ensureParamChild=i,exports.ensureSplatChild=a,exports.extractParamName=r;
|
|
2
|
+
//# sourceMappingURL=trieNodes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trieNodes.js","names":["parseSegment","createSegmentNode"],"sources":["../../../../../src/engine/path-matcher/registration/trieNodes.ts"],"sourcesContent":["// Trie node construction: param-name extraction + the param/splat child creators\n// (#736). Consumed by `trie` during insertion.\n\nimport { parseSegment } from \"../parseSegment\";\nimport { createSegmentNode } from \"../pathUtils\";\nimport { throwEmptyParamName, throwParamNameConflict } from \"./errors\";\n\nimport type { SegmentNode } from \"../types\";\n\n/**\n * Extracts the param name from a marker-led segment (`:name` / `*name`),\n * delegating the boundary to the canonical `parseSegment` tokenizer (#1324) so the\n * trie backstop, the route-tree gate, and `buildParamMeta` share ONE grammar and\n * cannot drift. `registerNode`'s per-segment grammar pre-pass has already rejected\n * every malformed form (name-less #858, trailing marker `:y*`/`:y:` #1324, fused\n * marker #1050, and the M1 removed optional/constraint forms), so a `:param` /\n * `*splat` name is guaranteed here — the error/`static` branches below are an\n * unreachable typed backstop. Single source for the param branch in\n * `processSegment` (the 3-token walk no longer forks, #1516).\n */\nexport function extractParamName(segment: string): string {\n const token = parseSegment(segment);\n\n // registerNode's per-segment grammar pass (Реш.2) rejects every malformed segment\n // — name-less (#858), trailing-marker (#1324), fused-marker (#1050), constraint\n // forms — before trie insertion, so a param|splat name is guaranteed here. The\n // error/`static` branches are unreachable, kept as a typed defensive backstop.\n /* v8 ignore start -- unreachable: registerNode's grammar pass rejects non-name segments first */\n if (\"error\" in token || token.kind === \"static\") {\n throwEmptyParamName();\n }\n /* v8 ignore stop */\n\n return token.name;\n}\n\n/**\n * Returns the param child of `node`, creating it on first use. A pre-existing\n * child with a *different* name is a #736 conflict (two routes binding the same\n * trie position under different names). With the 3-token grammar (M1) insertion is\n * a strict linear walk, so a single route never revisits a slot it created — the\n * former optional-omit `ownNodes` exception is gone with optional params.\n */\nexport function ensureParamChild(\n node: SegmentNode,\n paramName: string,\n): SegmentNode {\n if (!node.paramChild) {\n node.paramChild = { node: createSegmentNode(), name: paramName };\n } else if (node.paramChild.name !== paramName) {\n throwParamNameConflict(node.paramChild.name, paramName, \":\");\n }\n\n return node.paramChild.node;\n}\n\n/** Splat counterpart of {@link ensureParamChild}. */\nexport function ensureSplatChild(\n node: SegmentNode,\n splatName: string,\n): SegmentNode {\n if (!node.splatChild) {\n node.splatChild = { node: createSegmentNode(), name: splatName };\n } else if (node.splatChild.name !== splatName) {\n throwParamNameConflict(node.splatChild.name, splatName, \"*\");\n }\n\n return node.splatChild.node;\n}\n"],"mappings":"4FAoBA,SAAgB,EAAiB,EAAyB,CACxD,IAAM,EAAQA,EAAAA,aAAa,CAAO,EAYlC,OALI,UAAW,GAAS,EAAM,OAAS,WACrC,EAAA,oBAAoB,EAIf,EAAM,IACf,CASA,SAAgB,EACd,EACA,EACa,CAOb,OANK,EAAK,WAEC,EAAK,WAAW,OAAS,GAClC,EAAA,uBAAuB,EAAK,WAAW,KAAM,EAAW,GAAG,EAF3D,EAAK,WAAa,CAAE,KAAMC,EAAAA,kBAAkB,EAAG,KAAM,CAAU,EAK1D,EAAK,WAAW,IACzB,CAGA,SAAgB,EACd,EACA,EACa,CAOb,OANK,EAAK,WAEC,EAAK,WAAW,OAAS,GAClC,EAAA,uBAAuB,EAAK,WAAW,KAAM,EAAW,GAAG,EAF3D,EAAK,WAAa,CAAE,KAAMA,EAAAA,kBAAkB,EAAG,KAAM,CAAU,EAK1D,EAAK,WAAW,IACzB"}
|
|
@@ -89,50 +89,5 @@ interface ResolvedMatcherOptions {
|
|
|
89
89
|
readonly buildQueryString: (params: Record<string, unknown>) => string;
|
|
90
90
|
}
|
|
91
91
|
//#endregion
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* Definition of a route for configuration.
|
|
95
|
-
*/
|
|
96
|
-
interface RouteDefinition {
|
|
97
|
-
[key: string]: unknown;
|
|
98
|
-
name: string;
|
|
99
|
-
path: string;
|
|
100
|
-
children?: RouteDefinition[] | undefined;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Immutable route tree node.
|
|
104
|
-
*
|
|
105
|
-
* This is the core data structure of the new route-tree architecture.
|
|
106
|
-
* It contains only data (no methods) and is created by the builder.
|
|
107
|
-
*
|
|
108
|
-
* All caches are pre-computed at build time:
|
|
109
|
-
* - nonAbsoluteChildren: filtered children without absolute paths
|
|
110
|
-
* - fullName: pre-computed "users.profile" instead of runtime join
|
|
111
|
-
*/
|
|
112
|
-
interface RouteTree {
|
|
113
|
-
/** Route segment name (e.g., "users" in "users.profile") */
|
|
114
|
-
readonly name: string;
|
|
115
|
-
/** Route path pattern (e.g., "/users/:id") */
|
|
116
|
-
readonly path: string;
|
|
117
|
-
/** Whether this route uses absolute path matching (path starts with "~") */
|
|
118
|
-
readonly absolute: boolean;
|
|
119
|
-
/** Child route nodes (Map for O(1) lookup by name) */
|
|
120
|
-
readonly children: ReadonlyMap<string, RouteTree>;
|
|
121
|
-
/** Parameter metadata extracted from path pattern (replaces parser dependency) */
|
|
122
|
-
readonly paramMeta: ParamMeta;
|
|
123
|
-
/** Parent node (null for root) */
|
|
124
|
-
readonly parent: RouteTree | null;
|
|
125
|
-
/** Children without absolute paths (for regular matching) */
|
|
126
|
-
readonly nonAbsoluteChildren: readonly RouteTree[];
|
|
127
|
-
/** Pre-computed full name (e.g., "users.profile") */
|
|
128
|
-
readonly fullName: string;
|
|
129
|
-
/**
|
|
130
|
-
* Pre-computed parameter type map for this segment.
|
|
131
|
-
* Cached to avoid recomputing on every navigation.
|
|
132
|
-
* Maps param name → "url" | "query".
|
|
133
|
-
*/
|
|
134
|
-
readonly paramTypeMap: Readonly<Record<string, "url" | "query">>;
|
|
135
|
-
}
|
|
136
|
-
//#endregion
|
|
137
|
-
export { MatcherInputNode as a, MatchResult as i, RouteTree as n, ResolvedMatcherOptions as o, BuildPathOptions as r, SegmentMatcherOptions as s, RouteDefinition as t };
|
|
138
|
-
//# sourceMappingURL=types-DaXCTxi1.d.ts.map
|
|
92
|
+
export { BuildPathOptions, MatchResult, MatcherInputNode, ParamMeta, ResolvedMatcherOptions, SegmentMatcherOptions, URLParamsEncodingType };
|
|
93
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +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;WACnB,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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=e=>{let t=e.indexOf(`%`),n=e.indexOf(`+`);if(t===-1&&n===-1)return e;let r=n===-1?e:e.replaceAll(`+`,` `);return t===-1?r:decodeURIComponent(r)},t=(t,n)=>{if(t===void 0)return n.boolean.decodeUndefined();let r=n.boolean.decodeRaw(t);if(r!==null)return r;let i=e(t),a=n.number.decode(i);return a===null?n.boolean.decodeValue(i):a};exports.decode=t,exports.decodeValue=e;
|
|
2
|
+
//# sourceMappingURL=decode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode.js","names":[],"sources":["../../../../src/engine/search-params/decode.ts"],"sourcesContent":["/**\n * Decoding functions for search-params.\n *\n * Extracted from encode.ts for better separation of concerns.\n *\n * @module search-params/decode\n */\n\nimport type { ResolvedStrategies } from \"./strategies\";\nimport type { DecodeResult } from \"./types\";\n\n// =============================================================================\n// Value Decoding\n// =============================================================================\n\n/**\n * Decodes a URL-encoded value, handling + as space.\n * Optimized: only replaces + if present, skips decodeURIComponent if not needed.\n *\n * Uses indexOf for checking presence - more mutation-resistant than includes\n * because indexOf(\"x\") returns -1 when not found, while includes(\"\") always returns true.\n */\nexport const decodeValue = (value: string): string => {\n // Stryker disable next-line StringLiteral: equivalent — indexOf(\"%\") -> indexOf(\"\") returns 0, forcing the full decode path; decodeURIComponent is a no-op on a %-free string, so the result is identical (proven by injection). See the indexOf rationale above.\n const percentIdx = value.indexOf(\"%\");\n // Stryker disable next-line StringLiteral: equivalent — indexOf(\"+\") -> indexOf(\"\") returns 0, forcing replaceAll(\"+\",\" \") which is a no-op on a +-free string (proven by injection).\n const plusIdx = value.indexOf(\"+\");\n\n // Fast path: no encoding needed (common case - most values are simple)\n if (percentIdx === -1 && plusIdx === -1) {\n return value;\n }\n\n // Only replace + if present (avoid regex overhead)\n const withSpaces = plusIdx === -1 ? value : value.replaceAll(\"+\", \" \");\n\n // Only decode if % is present\n return percentIdx === -1 ? withSpaces : decodeURIComponent(withSpaces);\n};\n\n// =============================================================================\n// Main Decode\n// =============================================================================\n\n/**\n * Decodes a query parameter value using resolved strategies.\n *\n * @param value - Raw value from query string (undefined for key-only params)\n * @param strategies - Pre-resolved format strategies\n * @returns Decoded value (string, boolean, or null)\n */\nexport const decode = (\n value: string | undefined,\n strategies: ResolvedStrategies,\n): DecodeResult => {\n // Handle undefined (key-only params like ?flag)\n if (value === undefined) {\n return strategies.boolean.decodeUndefined();\n }\n\n // Check raw value for boolean formats (e.g., \"true\"/\"false\")\n const rawResult = strategies.boolean.decodeRaw(value);\n\n if (rawResult !== null) {\n return rawResult;\n }\n\n // Decode URI and check decoded value for boolean formats (e.g., ✓/✗)\n const decoded = decodeValue(value);\n\n const numberResult = strategies.number.decode(decoded);\n\n if (numberResult !== null) {\n return numberResult;\n }\n\n return strategies.boolean.decodeValue(decoded);\n};\n"],"mappings":"AAsBA,MAAa,EAAe,GAA0B,CAEpD,IAAM,EAAa,EAAM,QAAQ,GAAG,EAE9B,EAAU,EAAM,QAAQ,GAAG,EAGjC,GAAI,IAAe,IAAM,IAAY,GACnC,OAAO,EAIT,IAAM,EAAa,IAAY,GAAK,EAAQ,EAAM,WAAW,IAAK,GAAG,EAGrE,OAAO,IAAe,GAAK,EAAa,mBAAmB,CAAU,CACvE,EAaa,GACX,EACA,IACiB,CAEjB,GAAI,IAAU,IAAA,GACZ,OAAO,EAAW,QAAQ,gBAAgB,EAI5C,IAAM,EAAY,EAAW,QAAQ,UAAU,CAAK,EAEpD,GAAI,IAAc,KAChB,OAAO,EAIT,IAAM,EAAU,EAAY,CAAK,EAE3B,EAAe,EAAW,OAAO,OAAO,CAAO,EAMrD,OAJI,IAAiB,KAId,EAAW,QAAQ,YAAY,CAAO,EAHpC,CAIX"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./utils.js"),t=require("./strategies/index.js"),n={arrayFormat:`none`,booleanFormat:`auto`,nullFormat:`default`,numberFormat:`auto`},r={...n,strategies:t.DEFAULT_STRATEGIES},i=e=>{if(!e||e.arrayFormat===void 0&&e.booleanFormat===void 0&&e.nullFormat===void 0&&e.numberFormat===void 0)return r;let i=e.arrayFormat??n.arrayFormat,a=e.booleanFormat??n.booleanFormat,o=e.nullFormat??n.nullFormat,s=e.numberFormat??n.numberFormat;return{arrayFormat:i,booleanFormat:a,nullFormat:o,numberFormat:s,strategies:t.resolveStrategies(i,a,o,s)}},a=t=>e.safeEncode(t),o=(e,t,n)=>{let r=a(e);switch(typeof t){case`string`:case`number`:return`${r}=${a(t)}`;case`boolean`:return n.strategies.boolean.encode(r,t);case`object`:return t===null?n.strategies.null.encode(r):Array.isArray(t)?n.strategies.array.encodeArray(r,t,n.strategies.null):`${r}=${a(t)}`;default:return`${r}=${a(t)}`}};exports.DEFAULT_QUERY_PARAMS=n,exports.encode=o,exports.encodeValue=a,exports.makeOptions=i;
|
|
2
|
+
//# sourceMappingURL=encode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encode.js","names":["DEFAULT_STRATEGIES","resolveStrategies","safeEncode"],"sources":["../../../../src/engine/search-params/encode.ts"],"sourcesContent":["/**\n * Encoding functions for search-params.\n *\n * Uses Strategy pattern for format-specific encoding.\n * Strategies are resolved once when options are created.\n *\n * @module search-params/encode\n */\n\nimport {\n DEFAULT_STRATEGIES,\n resolveStrategies,\n type ResolvedStrategies,\n} from \"./strategies\";\nimport { safeEncode } from \"./utils\";\n\nimport type { FinalOptions, Options } from \"./types\";\n\n// =============================================================================\n// Default Query Params\n// =============================================================================\n\n/**\n * Default query parameter options. Single source of truth for all packages.\n */\nexport const DEFAULT_QUERY_PARAMS: FinalOptions = {\n arrayFormat: \"none\",\n booleanFormat: \"auto\",\n nullFormat: \"default\",\n numberFormat: \"auto\",\n};\n\n// =============================================================================\n// Options with Strategies\n// =============================================================================\n\n/**\n * Extended options with pre-resolved strategies.\n */\nexport interface OptionsWithStrategies extends FinalOptions {\n readonly strategies: ResolvedStrategies;\n}\n\n/**\n * Cached default options with strategies - avoids allocation when no options passed.\n */\nconst DEFAULT_OPTIONS: OptionsWithStrategies = {\n ...DEFAULT_QUERY_PARAMS,\n strategies: DEFAULT_STRATEGIES,\n};\n\n/**\n * Creates options with defaults and pre-resolved strategies.\n * Returns cached DEFAULT_OPTIONS when no custom options are specified.\n */\nexport const makeOptions = (opts?: Options): OptionsWithStrategies => {\n if (\n !opts ||\n (opts.arrayFormat === undefined &&\n opts.booleanFormat === undefined &&\n opts.nullFormat === undefined &&\n opts.numberFormat === undefined)\n ) {\n return DEFAULT_OPTIONS;\n }\n\n // Avoid object spread - direct property assignment is faster\n const arrayFormat = opts.arrayFormat ?? DEFAULT_QUERY_PARAMS.arrayFormat;\n const booleanFormat =\n opts.booleanFormat ?? DEFAULT_QUERY_PARAMS.booleanFormat;\n const nullFormat = opts.nullFormat ?? DEFAULT_QUERY_PARAMS.nullFormat;\n const numberFormat = opts.numberFormat ?? DEFAULT_QUERY_PARAMS.numberFormat;\n\n return {\n arrayFormat,\n booleanFormat,\n nullFormat,\n numberFormat,\n strategies: resolveStrategies(\n arrayFormat,\n booleanFormat,\n nullFormat,\n numberFormat,\n ),\n };\n};\n\n// =============================================================================\n// Value Encoding\n// =============================================================================\n\n/**\n * Encodes a value for use in a URL query string.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\nexport const encodeValue = (value: any): string => safeEncode(value);\n\n// =============================================================================\n// Main Encode\n// =============================================================================\n\n/**\n * Encodes a parameter name and value as a query string segment.\n *\n * Uses pre-resolved strategies for format-specific encoding.\n * Strategies handle all formats uniformly - no inline optimizations\n * to avoid equivalent mutants in mutation testing.\n *\n * @param name - Parameter name\n * @param value - Parameter value\n * @param opts - Options with pre-resolved strategies\n * @returns Query string segment (e.g., \"key=value\")\n */\nexport const encode = (\n name: string,\n value: unknown,\n opts: OptionsWithStrategies,\n): string => {\n const encodedName = encodeValue(name);\n\n // Handle each type using strategies\n switch (typeof value) {\n case \"string\":\n case \"number\": {\n return `${encodedName}=${encodeValue(value)}`;\n }\n case \"boolean\": {\n return opts.strategies.boolean.encode(encodedName, value);\n }\n case \"object\": {\n // Null check\n if (value === null) {\n return opts.strategies.null.encode(encodedName);\n }\n // Array check\n if (Array.isArray(value)) {\n return opts.strategies.array.encodeArray(\n encodedName,\n value,\n opts.strategies.null,\n );\n }\n\n // Fallback for other objects - treat as string\n return `${encodedName}=${encodeValue(value)}`;\n }\n default: {\n // Fallback for other types (undefined handled by caller)\n return `${encodedName}=${encodeValue(value)}`;\n }\n }\n};\n"],"mappings":"iEAyBa,EAAqC,CAChD,YAAa,OACb,cAAe,OACf,WAAY,UACZ,aAAc,MAChB,EAgBM,EAAyC,CAC7C,GAAG,EACH,WAAYA,EAAAA,kBACd,EAMa,EAAe,GAA0C,CACpE,GACE,CAAC,GACA,EAAK,cAAgB,IAAA,IACpB,EAAK,gBAAkB,IAAA,IACvB,EAAK,aAAe,IAAA,IACpB,EAAK,eAAiB,IAAA,GAExB,OAAO,EAIT,IAAM,EAAc,EAAK,aAAe,EAAqB,YACvD,EACJ,EAAK,eAAiB,EAAqB,cACvC,EAAa,EAAK,YAAc,EAAqB,WACrD,EAAe,EAAK,cAAgB,EAAqB,aAE/D,MAAO,CACL,cACA,gBACA,aACA,eACA,WAAYC,EAAAA,kBACV,EACA,EACA,EACA,CACF,CACF,CACF,EAUa,EAAe,GAAuBC,EAAAA,WAAW,CAAK,EAkBtD,GACX,EACA,EACA,IACW,CACX,IAAM,EAAc,EAAY,CAAI,EAGpC,OAAQ,OAAO,EAAf,CACE,IAAK,SACL,IAAK,SACH,MAAO,GAAG,EAAY,GAAG,EAAY,CAAK,IAE5C,IAAK,UACH,OAAO,EAAK,WAAW,QAAQ,OAAO,EAAa,CAAK,EAE1D,IAAK,SAeH,OAbI,IAAU,KACL,EAAK,WAAW,KAAK,OAAO,CAAW,EAG5C,MAAM,QAAQ,CAAK,EACd,EAAK,WAAW,MAAM,YAC3B,EACA,EACA,EAAK,WAAW,IAClB,EAIK,GAAG,EAAY,GAAG,EAAY,CAAK,IAE5C,QAEE,MAAO,GAAG,EAAY,GAAG,EAAY,CAAK,GAE9C,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./decode.js"),t=require("./encode.js");function n(e,t,n){t===`__proto__`?Object.defineProperty(e,t,{value:n,writable:!0,enumerable:!0,configurable:!0}):e[t]=n}function r(e,t,r,i){if(!Object.hasOwn(e,t)){n(e,t,i?[r]:r);return}let a=e[t];Array.isArray(a)?a.push(r):n(e,t,[a,r])}function i(t,n,r,i,a){return e.decode(i?t.slice(n+1,r):void 0,a)}function a(e,t,n){let r=t+1,i=0,a=!1;for(;r<n;){let t=e.codePointAt(r);if(t===93)return a?i:null;if(t!==void 0&&t>=48&&t<=57){i=i*10+(t-48),a=!0,r++;continue}return null}return null}function o(e,t,n){let{searchPart:r,nameEnd:o,nameSourceEnd:s,eqPos:c,end:l,hasValue:u,decodedName:d}=e,f=a(r,o,s);if(f===null)return!1;let p=i(r,c,l,u,t),m=n.get(d);return m===void 0?n.set(d,[[f,p]]):m.push([f,p]),!0}function s(t,n,a,s,c,l,u){let d=l!==-1&&l<a,f=d?l:a,p=f,m=!1;for(let e=n;e<f;e++)if(t.codePointAt(e)===91){p=e,m=!0;break}let h=e.decodeValue(t.slice(n,p));if(!(u!==void 0&&m&&o({searchPart:t,nameEnd:p,nameSourceEnd:f,eqPos:l,end:a,hasValue:d,decodedName:h},c,u))){if(!m&&d&&c.array.decodeValue){let n=t.slice(l+1,a),i=c.array.decodeValue(n);if(i){for(let t of i)r(s,h,e.decode(t,c),!0);return}}r(s,h,i(t,l,a,d,c),m)}}const c=(e,n)=>{if(e===``||e===`?`)return{};let r={};return l(e,r,t.makeOptions(n).strategies),r};function l(e,t,r){let i=r.array.indexed?new Map:void 0,a=0,o=e.length,c=-2;for(;a<o;){let n=e.indexOf(`&`,a);n===-1&&(n=o),n>a&&(c!==-1&&c<a&&(c=e.indexOf(`=`,a)),s(e,a,n,t,r,c,i)),a=n+1}if(i!==void 0)for(let[e,r]of i)r.sort((e,t)=>e[0]-t[0]),n(t,e,r.map(e=>e[1]))}const u=(e,n)=>{let r=Object.keys(e);if(r.length===0)return``;let i=t.makeOptions(n),a=[];for(let n of r){let r=e[n];if(r===void 0)continue;let o=t.encode(n,r,i);o&&a.push(o)}return a.join(`&`)};exports.build=u,exports.parseQuery=c;
|
|
2
|
+
//# sourceMappingURL=searchParams.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"searchParams.js","names":["decode","decodeValue","makeOptions","encode"],"sources":["../../../../src/engine/search-params/searchParams.ts"],"sourcesContent":["/**\n * Search Params - Query String Parsing and Building.\n *\n * Internalized from https://github.com/troch/search-params (MIT License)\n * for better code control and optimization.\n *\n * @module search-params/searchParams\n */\n\nimport { decode, decodeValue } from \"./decode\";\nimport { encode, makeOptions } from \"./encode\";\n\nimport type { ResolvedStrategies } from \"./strategies\";\nimport type { Options } from \"./types\";\n\n// =============================================================================\n// Internal Helpers\n// =============================================================================\n\n/**\n * Assigns a parameter as an own data property.\n *\n * Plain `params[name] = value` invokes the inherited `__proto__` accessor for the\n * literal key `\"__proto__\"`, so that key would mutate the prototype instead of\n * becoming a real entry; `defineProperty` writes a genuine own property.\n *\n * @internal\n */\nfunction assignParam(\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 * Adds a decoded value to params object, handling array accumulation.\n *\n * Collisions are detected via `Object.hasOwn`, not `params[name] !== undefined`:\n * a query key that shadows an `Object.prototype` member (`valueOf`, `constructor`,\n * `toString`, …) would otherwise read the inherited function and be mistaken for\n * a pre-existing value, corrupting the result into `[<fn>, value]`. (#855)\n *\n * @internal\n */\nfunction addToParams(\n params: Record<string, unknown>,\n decodedName: string,\n decodedValue: unknown,\n hasBrackets: boolean,\n): void {\n if (!Object.hasOwn(params, decodedName)) {\n assignParam(\n params,\n decodedName,\n hasBrackets ? [decodedValue] : decodedValue,\n );\n\n return;\n }\n\n const currentValue = params[decodedName];\n\n if (Array.isArray(currentValue)) {\n currentValue.push(decodedValue);\n } else {\n assignParam(params, decodedName, [currentValue, decodedValue]);\n }\n}\n\n/**\n * Decodes a parameter value through the resolved strategies.\n *\n * @internal\n */\nfunction decodeParamValue(\n searchPart: string,\n eqPos: number,\n end: number,\n hasValue: boolean,\n strategies: ResolvedStrategies,\n): unknown {\n const rawValue = hasValue ? searchPart.slice(eqPos + 1, end) : undefined;\n\n return decode(rawValue, strategies);\n}\n\n/**\n * Reads the non-negative integer index from a bracketed name (`a[12]`).\n *\n * `open` points at the `[`; digits up to the matching `]` form the index. Returns\n * `null` for `[]`, non-digit content (`a[x]`), or a missing `]` — those fall back\n * to insertion-order accumulation. (#856)\n *\n * @internal\n */\nfunction bracketIndex(\n searchPart: string,\n open: number,\n limit: number,\n): number | null {\n let i = open + 1;\n let value = 0;\n let hasDigit = false;\n\n while (i < limit) {\n const ch = searchPart.codePointAt(i);\n\n if (ch === 93) {\n // ']' — a numeric index only if at least one digit preceded it (\"[]\" → null)\n return hasDigit ? value : null;\n }\n\n if (ch !== undefined && ch >= 48 && ch <= 57) {\n value = value * 10 + (ch - 48);\n hasDigit = true;\n i++;\n\n continue;\n }\n\n return null; // non-digit inside brackets — not a numeric index\n }\n\n return null; // no closing ']' (incl. \"[\" at end) — malformed, fall back\n}\n\n/**\n * A single parsed query chunk: the source string plus the boundary offsets and\n * decoded name that `processParamChunk` computes once. Bundled into one\n * descriptor so the indexed-format collector reuses them without a long\n * parameter list (#856).\n *\n * @internal\n */\ninterface ParsedChunk {\n searchPart: string;\n /** Offset of `[` (bracket notation), or the name terminator. */\n nameEnd: number;\n /** Offset where the raw name ends (`=` for valued chunks, else `end`). */\n nameSourceEnd: number;\n /** Offset of `=`, or -1 when the chunk has no value. */\n eqPos: number;\n /** Offset one past the chunk. */\n end: number;\n hasValue: boolean;\n decodedName: string;\n}\n\n/**\n * Collects a bracketed chunk into the index-format group, to be sorted by index\n * after the full pass. Returns `false` when the bracket is not a numeric index\n * (`a[]`, `a[x]`, `a[`), so the caller falls back to insertion-order push. (#856)\n *\n * @internal\n */\nfunction collectIndexedChunk(\n chunk: ParsedChunk,\n strategies: ResolvedStrategies,\n indexedGroups: Map<string, [number, unknown][]>,\n): boolean {\n const {\n searchPart,\n nameEnd,\n nameSourceEnd,\n eqPos,\n end,\n hasValue,\n decodedName,\n } = chunk;\n const index = bracketIndex(searchPart, nameEnd, nameSourceEnd);\n\n if (index === null) {\n return false;\n }\n\n const value = decodeParamValue(searchPart, eqPos, end, hasValue, strategies);\n const group = indexedGroups.get(decodedName);\n\n if (group === undefined) {\n indexedGroups.set(decodedName, [[index, value]]);\n } else {\n group.push([index, value]);\n }\n\n // Stryker disable next-line BooleanLiteral: equivalent — returning false makes the caller ALSO push via insertion order, but indexedGroups was already populated above and parseIntoInternal overwrites the key with the index-sorted result, so the final params are identical (proven by injection).\n return true;\n}\n\n/**\n * Processes a single query parameter chunk and adds to params.\n *\n * `indexedGroups` is supplied only for `arrayFormat: \"index\"`: bracketed chunks\n * with a numeric index are collected there (to be sorted by index after the full\n * pass) instead of pushed in insertion order. (#856)\n *\n * @internal\n */\nfunction processParamChunk(\n searchPart: string,\n start: number,\n end: number,\n params: Record<string, unknown>,\n strategies: ResolvedStrategies,\n eqPos: number,\n indexedGroups?: Map<string, [number, unknown][]>,\n): void {\n // `eqPos` is the position of the next `=` at or after `start`, resolved once by\n // the caller's monotonic cursor (#1316) — never re-scanned here. `eqPos < end`\n // means it falls inside THIS chunk (so the chunk has a value); otherwise the\n // chunk is key-only.\n const hasValue = eqPos !== -1 && eqPos < end;\n\n const nameSourceEnd = hasValue ? eqPos : end;\n let nameEnd = nameSourceEnd;\n let hasBrackets = false;\n\n for (let i = start; i < nameSourceEnd; i++) {\n if (searchPart.codePointAt(i) !== 91) {\n continue;\n }\n\n // '['\n nameEnd = i;\n hasBrackets = true;\n\n break;\n }\n\n const decodedName = decodeValue(searchPart.slice(start, nameEnd));\n\n // Index array format: order by the bracket index, not insertion. A non-numeric\n // bracket (`a[]`, `a[x]`) returns false → falls through to insertion-order push.\n if (\n indexedGroups !== undefined &&\n hasBrackets &&\n collectIndexedChunk(\n { searchPart, nameEnd, nameSourceEnd, eqPos, end, hasValue, decodedName },\n strategies,\n indexedGroups,\n )\n ) {\n return;\n }\n\n // Comma array decode: split raw value before individual element decoding\n if (!hasBrackets && hasValue && strategies.array.decodeValue) {\n const rawValue = searchPart.slice(eqPos + 1, end);\n const parts = strategies.array.decodeValue(rawValue);\n\n if (parts) {\n for (const part of parts) {\n addToParams(params, decodedName, decode(part, strategies), true);\n }\n\n return;\n }\n }\n\n const decodedValue = decodeParamValue(\n searchPart,\n eqPos,\n end,\n hasValue,\n strategies,\n );\n\n addToParams(params, decodedName, decodedValue, hasBrackets);\n}\n\n// =============================================================================\n// Parse\n// =============================================================================\n\n/**\n * Parse an ALREADY-EXTRACTED query string (no path prefix, no leading \"?\") into\n * an object of parameters.\n *\n * The input must already be split at the first \"?\" — `SegmentMatcher.#preparePath`\n * does this before the DI call, so route-tree wires `parseQuery` (not a\n * path-accepting wrapper) as its query parser: re-splitting the input here would\n * break at a \"?\" *inside* a query value (legal per RFC 3986), silently dropping\n * the param (and unmatching the whole URL under `strictQueryParams`). (#1292)\n *\n * @example\n * ```typescript\n * parseQuery(\"page=1&sort=name\");\n * // => { page: 1, sort: \"name\" }\n *\n * parseQuery(\"items[]=a&items[]=b\", { arrayFormat: \"brackets\" });\n * // => { items: [\"a\", \"b\"] }\n * ```\n */\nexport const parseQuery = (\n search: string,\n opts?: Options,\n): Record<string, unknown> => {\n // Fast path: empty query string\n if (search === \"\" || search === \"?\") {\n return {};\n }\n\n // makeOptions(undefined) returns the cached DEFAULT_OPTIONS (auto) — the same\n // defaults `build` uses — so parseQuery(build(x)) === x even without options. (#744)\n const params: Record<string, unknown> = {};\n\n parseIntoInternal(search, params, makeOptions(opts).strategies);\n\n return params;\n};\n\n/**\n * Internal function to parse a query string into a target object.\n * The shared parse engine behind `parseQuery`.\n *\n * @internal\n */\nfunction parseIntoInternal(\n searchPart: string,\n params: Record<string, unknown>,\n strategies: ResolvedStrategies,\n): void {\n // `index` format orders by the bracket index; collect (index, value) pairs and\n // sort after the pass. `undefined` for every other format (no overhead). (#856)\n const indexedGroups = strategies.array.indexed\n ? new Map<string, [number, unknown][]>()\n : undefined;\n\n let start = 0;\n const length = searchPart.length;\n\n // Monotonic cursor for the next `=`. Its position only ever moves forward with\n // `start`, so the whole parse does a single amortised O(n) scan for `=` —\n // replacing `processParamChunk`'s former per-chunk `indexOf(\"=\", start)`, which\n // scanned to the end of the string on every key-only chunk and made `parse`\n // O(n²) on `\"a&a&…\"` (#1316). `-2` = not yet searched; `-1` = no `=` remains.\n let eqCache = -2;\n\n while (start < length) {\n let end = searchPart.indexOf(\"&\", start);\n\n if (end === -1) {\n end = length;\n }\n\n // Skip empty chunks — a `&&`, a leading `&`, or a trailing `&` produces a\n // zero-length span that carries no name and no value. Processing it would\n // decode the empty name to `\"\"` and the missing value to `null`, injecting a\n // junk `{ \"\": null }` param (and `[null, …]` on repeats) (#1156). An\n // intentional empty-key chunk always carries an `=` (`\"=1\"` → `end > start`),\n // so it is unaffected.\n if (end > start) {\n // Advance the cursor only when the cached `=` is behind the current chunk;\n // once it reports `-1` (no `=` left in the string) it is final.\n if (eqCache !== -1 && eqCache < start) {\n eqCache = searchPart.indexOf(\"=\", start);\n }\n\n processParamChunk(\n searchPart,\n start,\n end,\n params,\n strategies,\n eqCache,\n indexedGroups,\n );\n }\n\n start = end + 1;\n }\n\n if (indexedGroups !== undefined) {\n for (const [name, pairs] of indexedGroups) {\n // Stable sort by index (V8 sort is stable) → equal indices keep arrival order.\n pairs.sort((left, right) => left[0] - right[0]);\n assignParam(\n params,\n name,\n pairs.map((pair) => pair[1]),\n );\n }\n }\n}\n\n// =============================================================================\n// Build\n// =============================================================================\n\n/**\n * Build a querystring from an object of parameters.\n *\n * Note: Empty arrays produce an empty string, so `parseQuery(build({ items: [] }))`\n * will not contain the `items` key. This is expected behavior for all array\n * formats including `comma` — the key is erased uniformly (INVARIANTS Parse/Build #9).\n *\n * @example\n * ```typescript\n * build({ page: 1, sort: \"name\" });\n * // => \"page=1&sort=name\"\n *\n * build({ items: [\"a\", \"b\"] }, { arrayFormat: \"brackets\" });\n * // => \"items[]=a&items[]=b\"\n * ```\n */\nexport const build = (\n params: Record<string, unknown>,\n opts?: Options,\n): string => {\n // Fast path for empty params (common case)\n const keys = Object.keys(params);\n\n if (keys.length === 0) {\n return \"\";\n }\n\n const options = makeOptions(opts);\n\n // Optimized: single loop instead of filter().map().filter().join()\n // Avoids creating 3 intermediate arrays\n const parts: string[] = [];\n\n for (const key of keys) {\n const value = params[key];\n\n // Skip undefined values (not serialisable)\n if (value === undefined) {\n continue;\n }\n\n const encoded = encode(key, value, options);\n\n // Skip empty strings (e.g., from nullFormat: \"hidden\")\n if (encoded) {\n parts.push(encoded);\n }\n }\n\n return parts.join(\"&\");\n};\n"],"mappings":"wDA4BA,SAAS,EACP,EACA,EACA,EACM,CACF,IAAS,YACX,OAAO,eAAe,EAAQ,EAAM,CAClC,QACA,SAAU,GACV,WAAY,GACZ,aAAc,EAChB,CAAC,EAED,EAAO,GAAQ,CAEnB,CAYA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,GAAI,CAAC,OAAO,OAAO,EAAQ,CAAW,EAAG,CACvC,EACE,EACA,EACA,EAAc,CAAC,CAAY,EAAI,CACjC,EAEA,MACF,CAEA,IAAM,EAAe,EAAO,GAExB,MAAM,QAAQ,CAAY,EAC5B,EAAa,KAAK,CAAY,EAE9B,EAAY,EAAQ,EAAa,CAAC,EAAc,CAAY,CAAC,CAEjE,CAOA,SAAS,EACP,EACA,EACA,EACA,EACA,EACS,CAGT,OAAOA,EAAAA,OAFU,EAAW,EAAW,MAAM,EAAQ,EAAG,CAAG,EAAI,IAAA,GAEvC,CAAU,CACpC,CAWA,SAAS,EACP,EACA,EACA,EACe,CACf,IAAI,EAAI,EAAO,EACX,EAAQ,EACR,EAAW,GAEf,KAAO,EAAI,GAAO,CAChB,IAAM,EAAK,EAAW,YAAY,CAAC,EAEnC,GAAI,IAAO,GAET,OAAO,EAAW,EAAQ,KAG5B,GAAI,IAAO,IAAA,IAAa,GAAM,IAAM,GAAM,GAAI,CAC5C,EAAQ,EAAQ,IAAM,EAAK,IAC3B,EAAW,GACX,IAEA,QACF,CAEA,OAAO,IACT,CAEA,OAAO,IACT,CA+BA,SAAS,EACP,EACA,EACA,EACS,CACT,GAAM,CACJ,aACA,UACA,gBACA,QACA,MACA,WACA,eACE,EACE,EAAQ,EAAa,EAAY,EAAS,CAAa,EAE7D,GAAI,IAAU,KACZ,MAAO,GAGT,IAAM,EAAQ,EAAiB,EAAY,EAAO,EAAK,EAAU,CAAU,EACrE,EAAQ,EAAc,IAAI,CAAW,EAS3C,OAPI,IAAU,IAAA,GACZ,EAAc,IAAI,EAAa,CAAC,CAAC,EAAO,CAAK,CAAC,CAAC,EAE/C,EAAM,KAAK,CAAC,EAAO,CAAK,CAAC,EAIpB,EACT,CAWA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACA,EACM,CAKN,IAAM,EAAW,IAAU,IAAM,EAAQ,EAEnC,EAAgB,EAAW,EAAQ,EACrC,EAAU,EACV,EAAc,GAElB,IAAK,IAAI,EAAI,EAAO,EAAI,EAAe,IACjC,KAAW,YAAY,CAAC,IAAM,GAMlC,CADA,EAAU,EACV,EAAc,GAEd,KAFc,CAKhB,IAAM,EAAcC,EAAAA,YAAY,EAAW,MAAM,EAAO,CAAO,CAAC,EAK9D,SAAkB,IAAA,IAClB,GACA,EACE,CAAE,aAAY,UAAS,gBAAe,QAAO,MAAK,WAAU,aAAY,EACxE,EACA,CACF,GAMF,IAAI,CAAC,GAAe,GAAY,EAAW,MAAM,YAAa,CAC5D,IAAM,EAAW,EAAW,MAAM,EAAQ,EAAG,CAAG,EAC1C,EAAQ,EAAW,MAAM,YAAY,CAAQ,EAEnD,GAAI,EAAO,CACT,IAAK,IAAM,KAAQ,EACjB,EAAY,EAAQ,EAAaD,EAAAA,OAAO,EAAM,CAAU,EAAG,EAAI,EAGjE,MACF,CACF,CAUA,EAAY,EAAQ,EARC,EACnB,EACA,EACA,EACA,EACA,CAG0C,EAAG,CAAW,CAV1D,CAWF,CAyBA,MAAa,GACX,EACA,IAC4B,CAE5B,GAAI,IAAW,IAAM,IAAW,IAC9B,MAAO,CAAC,EAKV,IAAM,EAAkC,CAAC,EAIzC,OAFA,EAAkB,EAAQ,EAAQE,EAAAA,YAAY,CAAI,CAAC,CAAC,UAAU,EAEvD,CACT,EAQA,SAAS,EACP,EACA,EACA,EACM,CAGN,IAAM,EAAgB,EAAW,MAAM,QACnC,IAAI,IACJ,IAAA,GAEA,EAAQ,EACN,EAAS,EAAW,OAOtB,EAAU,GAEd,KAAO,EAAQ,GAAQ,CACrB,IAAI,EAAM,EAAW,QAAQ,IAAK,CAAK,EAEnC,IAAQ,KACV,EAAM,GASJ,EAAM,IAGJ,IAAY,IAAM,EAAU,IAC9B,EAAU,EAAW,QAAQ,IAAK,CAAK,GAGzC,EACE,EACA,EACA,EACA,EACA,EACA,EACA,CACF,GAGF,EAAQ,EAAM,CAChB,CAEA,GAAI,IAAkB,IAAA,GACpB,IAAK,GAAM,CAAC,EAAM,KAAU,EAE1B,EAAM,MAAM,EAAM,IAAU,EAAK,GAAK,EAAM,EAAE,EAC9C,EACE,EACA,EACA,EAAM,IAAK,GAAS,EAAK,EAAE,CAC7B,CAGN,CAsBA,MAAa,GACX,EACA,IACW,CAEX,IAAM,EAAO,OAAO,KAAK,CAAM,EAE/B,GAAI,EAAK,SAAW,EAClB,MAAO,GAGT,IAAM,EAAUA,EAAAA,YAAY,CAAI,EAI1B,EAAkB,CAAC,EAEzB,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAQ,EAAO,GAGrB,GAAI,IAAU,IAAA,GACZ,SAGF,IAAM,EAAUC,EAAAA,OAAO,EAAK,EAAO,CAAO,EAGtC,GACF,EAAM,KAAK,CAAO,CAEtB,CAEA,OAAO,EAAM,KAAK,GAAG,CACvB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../utils.js"),t=t=>{let n=typeof t;if(n!==`string`&&n!==`number`&&n!==`boolean`)throw TypeError(`[search-params] Array element must be a string, number, or boolean — received ${n}`);return e.safeEncode(t)},n=(e,n,r,i)=>{let a=`${e}${r}`,o=[];for(let e of n)if(e===null){let e=i.encode(a);e&&o.push(e)}else o.push(`${a}=${t(e)}`);return o.join(`&`)},r={encodeArray:(e,t,r)=>n(e,t,``,r)},i={encodeArray:(e,t,r)=>n(e,t,`[]`,r)},a={encodeArray:(e,n,r)=>{let i=[];for(let[a,o]of n.entries()){let n=`${e}[${a}]`;if(o===null){let e=r.encode(n);e&&i.push(e)}else i.push(`${n}=${t(o)}`)}return i.join(`&`)},indexed:!0},o={encodeArray:(e,n)=>{let r=[];for(let e of n)e!==null&&r.push(t(e));return r.length===0?``:`${e}=${r.join(`,`)}`},decodeValue:e=>e.includes(`,`)?e.split(`,`):null},s={none:r,brackets:i,index:a,comma:o};exports.arrayStrategies=s,exports.bracketsArrayStrategy=i,exports.commaArrayStrategy=o,exports.indexArrayStrategy=a,exports.noneArrayStrategy=r;
|
|
2
|
+
//# sourceMappingURL=array.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"array.js","names":["safeEncode"],"sources":["../../../../../src/engine/search-params/strategies/array.ts"],"sourcesContent":["/**\n * Array encoding strategies.\n *\n * @module search-params/strategies/array\n */\n\nimport { safeEncode } from \"../utils\";\n\nimport type { NullStrategy } from \"./null\";\nimport type { ArrayFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for encoding array values.\n */\nexport interface ArrayStrategy {\n /**\n * Encodes an array as a query string segment.\n *\n * @param name - URL-encoded parameter name\n * @param values - Array values to encode\n * @param nullStrategy - Null strategy, so a `null` element encodes to the same\n * wire token a scalar null does (bare key under `default`, dropped under\n * `hidden`) — closing `range(parse) ⊆ dom(build)` (#1155)\n * @returns Query string segment (e.g., \"items=a&items=b\" or \"items=a,b\")\n */\n encodeArray: (\n name: string,\n values: unknown[],\n nullStrategy: NullStrategy,\n ) => string;\n\n /**\n * Splits a raw (URI-encoded) value into array parts during parsing.\n * Returns null if the value is not an array in this format.\n *\n * @param rawValue - Raw value before URI decoding\n * @returns Array of raw parts, or null if not an array\n */\n decodeValue?: (rawValue: string) => string[] | null;\n\n /**\n * When true, the parser orders bracketed elements (`a[n]`) by the numeric\n * index `n` rather than insertion order. Only `index` format sets this. (#856)\n */\n indexed?: boolean;\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n// Encodes a non-null array element. `null` is handled per-format by the caller\n// (bare-key / skip); this throws only on genuinely unserialisable elements\n// (`undefined`, objects) — which `parse` never produces in an array.\nconst encodeValue = (value: unknown): string => {\n const type = typeof value;\n\n if (type !== \"string\" && type !== \"number\" && type !== \"boolean\") {\n // `null` is handled by the caller (bare-key / skip) and never reaches here,\n // so `type` names the offender directly (`undefined`, `object`, `symbol`, …).\n throw new TypeError(\n `[search-params] Array element must be a string, number, or boolean — received ${type}`,\n );\n }\n\n return safeEncode(value as string | number | boolean);\n};\n\n// Repeats `${name}${suffix}` keys joined by `&`. Shared between `none` (suffix\n// `\"\"`) and `brackets` (suffix `\"[]\"`). A `null` element encodes to the SAME\n// wire token a scalar null does via `nullStrategy` — the bare key\n// `${name}${suffix}` under `nullFormat: \"default\"`, or `\"\"` (dropped, filtered\n// below so no `&&` appears) under `\"hidden\"`. So `parseQuery(\"a&a=1\")` →\n// `{a:[null,\"1\"]}` round-trips to `\"a&a=1\"` instead of throwing (#1155).\nconst repeatKey = (\n name: string,\n values: unknown[],\n suffix: string,\n nullStrategy: NullStrategy,\n): string => {\n const key = `${name}${suffix}`;\n const parts: string[] = [];\n\n for (const value of values) {\n if (value === null) {\n const encoded = nullStrategy.encode(key);\n\n if (encoded) {\n parts.push(encoded);\n }\n } else {\n parts.push(`${key}=${encodeValue(value)}`);\n }\n }\n\n return parts.join(\"&\");\n};\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * Repeated keys without brackets.\n * Example: items=a&items=b\n */\nexport const noneArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values, nullStrategy) =>\n repeatKey(name, values, \"\", nullStrategy),\n};\n\n/**\n * Bracket notation without index.\n * Example: items[]=a&items[]=b\n */\nexport const bracketsArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values, nullStrategy) =>\n repeatKey(name, values, \"[]\", nullStrategy),\n};\n\n/**\n * Indexed bracket notation.\n * Example: items[0]=a&items[1]=b\n */\nexport const indexArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values, nullStrategy) => {\n const parts: string[] = [];\n\n for (const [i, value] of values.entries()) {\n const key = `${name}[${i}]`;\n\n if (value === null) {\n const encoded = nullStrategy.encode(key);\n\n if (encoded) {\n parts.push(encoded);\n }\n } else {\n parts.push(`${key}=${encodeValue(value)}`);\n }\n }\n\n return parts.join(\"&\");\n },\n\n indexed: true,\n};\n\n/**\n * Comma-separated values.\n * Example: items=a,b,c\n *\n * Comma has no per-element bare-key form (an empty part like `a=,` decodes to\n * the empty string, not `null`), so a `null` element is unrepresentable and\n * dropped. `parse` only yields null-in-array under `comma` via a bracketed\n * chunk (`a[]`) — a wire/format mismatch, never the comma-native path — so this\n * is a total-but-lossy edge. The `nullStrategy` arg is intentionally omitted\n * (a 2-arg impl satisfies the 3-arg interface).\n */\nexport const commaArrayStrategy: ArrayStrategy = {\n encodeArray: (name, values) => {\n const parts: string[] = [];\n\n for (const value of values) {\n if (value !== null) {\n parts.push(encodeValue(value));\n }\n }\n\n if (parts.length === 0) {\n return \"\";\n }\n\n return `${name}=${parts.join(\",\")}`;\n },\n\n decodeValue: (rawValue) => {\n // No unencoded comma → not an array (single value).\n // Encoded commas (%2C) are part of the value, not separators.\n if (!rawValue.includes(\",\")) {\n return null;\n }\n\n return rawValue.split(\",\");\n },\n};\n\n/**\n * Map of array format to strategy implementation.\n */\nexport const arrayStrategies: Record<ArrayFormat, ArrayStrategy> = {\n none: noneArrayStrategy,\n brackets: bracketsArrayStrategy,\n index: indexArrayStrategy,\n comma: commaArrayStrategy,\n};\n"],"mappings":"+BA0DM,EAAe,GAA2B,CAC9C,IAAM,EAAO,OAAO,EAEpB,GAAI,IAAS,UAAY,IAAS,UAAY,IAAS,UAGrD,MAAU,UACR,iFAAiF,GACnF,EAGF,OAAOA,EAAAA,WAAW,CAAkC,CACtD,EAQM,GACJ,EACA,EACA,EACA,IACW,CACX,IAAM,EAAM,GAAG,IAAO,IAChB,EAAkB,CAAC,EAEzB,IAAK,IAAM,KAAS,EAClB,GAAI,IAAU,KAAM,CAClB,IAAM,EAAU,EAAa,OAAO,CAAG,EAEnC,GACF,EAAM,KAAK,CAAO,CAEtB,MACE,EAAM,KAAK,GAAG,EAAI,GAAG,EAAY,CAAK,GAAG,EAI7C,OAAO,EAAM,KAAK,GAAG,CACvB,EAUa,EAAmC,CAC9C,aAAc,EAAM,EAAQ,IAC1B,EAAU,EAAM,EAAQ,GAAI,CAAY,CAC5C,EAMa,EAAuC,CAClD,aAAc,EAAM,EAAQ,IAC1B,EAAU,EAAM,EAAQ,KAAM,CAAY,CAC9C,EAMa,EAAoC,CAC/C,aAAc,EAAM,EAAQ,IAAiB,CAC3C,IAAM,EAAkB,CAAC,EAEzB,IAAK,GAAM,CAAC,EAAG,KAAU,EAAO,QAAQ,EAAG,CACzC,IAAM,EAAM,GAAG,EAAK,GAAG,EAAE,GAEzB,GAAI,IAAU,KAAM,CAClB,IAAM,EAAU,EAAa,OAAO,CAAG,EAEnC,GACF,EAAM,KAAK,CAAO,CAEtB,MACE,EAAM,KAAK,GAAG,EAAI,GAAG,EAAY,CAAK,GAAG,CAE7C,CAEA,OAAO,EAAM,KAAK,GAAG,CACvB,EAEA,QAAS,EACX,EAaa,EAAoC,CAC/C,aAAc,EAAM,IAAW,CAC7B,IAAM,EAAkB,CAAC,EAEzB,IAAK,IAAM,KAAS,EACd,IAAU,MACZ,EAAM,KAAK,EAAY,CAAK,CAAC,EAQjC,OAJI,EAAM,SAAW,EACZ,GAGF,GAAG,EAAK,GAAG,EAAM,KAAK,GAAG,GAClC,EAEA,YAAc,GAGP,EAAS,SAAS,GAAG,EAInB,EAAS,MAAM,GAAG,EAHhB,IAKb,EAKa,EAAsD,CACjE,KAAM,EACN,SAAU,EACV,MAAO,EACP,MAAO,CACT"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e={encode:(e,t)=>`${e}=${t}`,decodeUndefined:()=>null,decodeRaw:()=>null,decodeValue:e=>e},t={encode:(e,t)=>`${e}=${t}`,decodeUndefined:()=>null,decodeRaw:e=>e===`true`||e!==`false`&&null,decodeValue:e=>e},n={encode:(e,t)=>t?e:`${e}=false`,decodeUndefined:()=>!0,decodeRaw:e=>e===`true`||e!==`false`&&null,decodeValue:e=>e},r={none:e,auto:t,"empty-true":n};exports.autoBooleanStrategy=t,exports.booleanStrategies=r,exports.emptyTrueBooleanStrategy=n,exports.noneBooleanStrategy=e;
|
|
2
|
+
//# sourceMappingURL=boolean.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boolean.js","names":[],"sources":["../../../../../src/engine/search-params/strategies/boolean.ts"],"sourcesContent":["/**\n * Boolean encoding/decoding strategies.\n *\n * @module search-params/strategies/boolean\n */\n\nimport type { DecodeResult, BooleanFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Map\n// =============================================================================\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for encoding/decoding boolean values.\n */\nexport interface BooleanStrategy {\n /**\n * Encodes a boolean value as a query string segment.\n *\n * @param name - URL-encoded parameter name\n * @param value - Boolean value to encode\n * @returns Query string segment (e.g., \"flag=true\" or just \"flag\")\n */\n encode: (name: string, value: boolean) => string;\n\n /**\n * Handles undefined value (key-only params like ?flag).\n *\n * @returns Decoded value for undefined\n */\n decodeUndefined: () => DecodeResult;\n\n /**\n * Decodes a raw (not URI-decoded) value before decodeValue() is called.\n * Used for formats that check raw values (e.g., \"auto\" checks \"true\"/\"false\").\n *\n * @param rawValue - Raw value before URI decoding\n * @returns boolean if matched, null to continue with URI decoding\n */\n decodeRaw: (rawValue: string) => boolean | null;\n\n /**\n * Decodes a URI-decoded value.\n *\n * @param decodedValue - Value after URI decoding\n * @returns Decoded value (boolean or the same string)\n */\n decodeValue: (decodedValue: string) => DecodeResult;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * No special boolean handling - values are treated as strings.\n */\nexport const noneBooleanStrategy: BooleanStrategy = {\n encode: (name, value) => `${name}=${value}`,\n decodeUndefined: () => null,\n decodeRaw: () => null, // No raw value matching\n decodeValue: (decoded) => decoded, // Return as-is\n};\n\n/**\n * Auto-detect \"true\"/\"false\" strings as boolean values.\n * Checks raw value before URI decoding.\n */\nexport const autoBooleanStrategy: BooleanStrategy = {\n encode: (name, value) => `${name}=${value}`,\n decodeUndefined: () => null,\n decodeRaw: (raw) => {\n if (raw === \"true\") {\n return true;\n }\n if (raw === \"false\") {\n return false;\n }\n\n return null; // Continue with URI decoding\n },\n decodeValue: (decoded) => decoded, // Return decoded string\n};\n\n/**\n * Scalar true values are key-only (no =value); scalar false keeps an explicit\n * `=false`. Example: `?flag` for true, `?flag=false` for false. Array elements\n * always carry an explicit value (`?a=true&a=false`), so decoding must map both\n * `\"true\"`→true and `\"false\"`→false to round-trip booleans losslessly in either\n * position — a key-only param is `true` (decodeUndefined), and any explicit\n * `=true`/`=false` decodes back to the boolean (not the string).\n */\nexport const emptyTrueBooleanStrategy: BooleanStrategy = {\n encode: (name, value) => (value ? name : `${name}=false`),\n decodeUndefined: () => true, // Key-only means true\n decodeRaw: (raw) => {\n if (raw === \"true\") {\n return true;\n }\n if (raw === \"false\") {\n return false;\n }\n\n return null; // Continue with URI decoding\n },\n decodeValue: (decoded) => decoded, // Return as-is\n};\n\n/**\n * Map of boolean format to strategy implementation.\n */\nexport const booleanStrategies: Record<BooleanFormat, BooleanStrategy> = {\n none: noneBooleanStrategy,\n auto: autoBooleanStrategy,\n \"empty-true\": emptyTrueBooleanStrategy,\n};\n"],"mappings":"AA6DA,MAAa,EAAuC,CAClD,QAAS,EAAM,IAAU,GAAG,EAAK,GAAG,IACpC,oBAAuB,KACvB,cAAiB,KACjB,YAAc,GAAY,CAC5B,EAMa,EAAuC,CAClD,QAAS,EAAM,IAAU,GAAG,EAAK,GAAG,IACpC,oBAAuB,KACvB,UAAY,GACN,IAAQ,QAGR,IAAQ,SAIL,KAET,YAAc,GAAY,CAC5B,EAUa,EAA4C,CACvD,QAAS,EAAM,IAAW,EAAQ,EAAO,GAAG,EAAK,QACjD,oBAAuB,GACvB,UAAY,GACN,IAAQ,QAGR,IAAQ,SAIL,KAET,YAAc,GAAY,CAC5B,EAKa,EAA4D,CACvE,KAAM,EACN,KAAM,EACN,aAAc,CAChB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./array.js"),t=require("./boolean.js"),n=require("./null.js"),r=require("./number.js"),i=(e,t,n,r)=>{if(e===void 0)throw TypeError(`[search-params] Unknown ${t} "${n}" — expected ${r}`);return e},a=(a,o,s,c)=>({boolean:i(t.booleanStrategies[o],`booleanFormat`,o,`"none" | "auto" | "empty-true"`),null:i(n.nullStrategies[s],`nullFormat`,s,`"default" | "hidden"`),number:i(r.numberStrategies[c],`numberFormat`,c,`"none" | "auto"`),array:i(e.arrayStrategies[a],`arrayFormat`,a,`"none" | "brackets" | "index" | "comma"`)}),o={boolean:t.booleanStrategies.auto,null:n.nullStrategies.default,number:r.numberStrategies.auto,array:e.arrayStrategies.none};exports.DEFAULT_STRATEGIES=o,exports.resolveStrategies=a;
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["booleanStrategies","nullStrategies","numberStrategies","arrayStrategies"],"sources":["../../../../../src/engine/search-params/strategies/index.ts"],"sourcesContent":["/**\n * Search Params Strategies - Factory and Types.\n *\n * Provides a unified interface for format-specific encoding/decoding strategies.\n * Strategies are resolved once when options are created, avoiding repeated\n * format checks during encode/decode operations.\n *\n * @module search-params/strategies\n */\n\nimport { arrayStrategies, type ArrayStrategy } from \"./array\";\nimport { booleanStrategies, type BooleanStrategy } from \"./boolean\";\nimport { nullStrategies, type NullStrategy } from \"./null\";\nimport { numberStrategies, type NumberStrategy } from \"./number\";\n\nimport type { FinalOptions } from \"../types\";\n\n// =============================================================================\n// Exports\n// =============================================================================\n\nexport type { ArrayStrategy } from \"./array\";\n\nexport type { BooleanStrategy } from \"./boolean\";\n\nexport type { NullStrategy } from \"./null\";\n\nexport type { NumberStrategy } from \"./number\";\n\n// =============================================================================\n// Resolved Strategies\n// =============================================================================\n\n/**\n * Pre-resolved strategies based on options.\n * Created once when makeOptions() is called, avoiding repeated lookups.\n */\nexport interface ResolvedStrategies {\n readonly boolean: BooleanStrategy;\n readonly null: NullStrategy;\n readonly number: NumberStrategy;\n readonly array: ArrayStrategy;\n}\n\n/**\n * Resolves strategies based on format options.\n *\n * @param arrayFormat - Array format\n * @param booleanFormat - Boolean format\n * @param nullFormat - Null format\n * @param numberFormat - Number format\n * @returns Resolved strategy implementations\n */\n/**\n * Fail fast on an unknown format. A `queryParams` typo in a JS consumer (no TS to\n * forbid it) otherwise indexes the strategy map to `undefined`, deferring a cryptic\n * `TypeError` to first use — which the router's `SegmentMatcher.#mergeQueryParams`\n * catch-all then masks as `UNKNOWN_ROUTE` for EVERY query URL, with zero diagnostics\n * (#1318). TS consumers are unaffected (the union types already forbid the typo).\n */\nconst requireStrategy = <T>(\n strategy: T | undefined,\n field: string,\n value: string,\n allowed: string,\n): T => {\n if (strategy === undefined) {\n throw new TypeError(\n `[search-params] Unknown ${field} \"${value}\" — expected ${allowed}`,\n );\n }\n\n return strategy;\n};\n\nexport const resolveStrategies = (\n arrayFormat: FinalOptions[\"arrayFormat\"],\n booleanFormat: FinalOptions[\"booleanFormat\"],\n nullFormat: FinalOptions[\"nullFormat\"],\n numberFormat: FinalOptions[\"numberFormat\"],\n): ResolvedStrategies => ({\n boolean: requireStrategy(\n booleanStrategies[booleanFormat],\n \"booleanFormat\",\n booleanFormat,\n '\"none\" | \"auto\" | \"empty-true\"',\n ),\n null: requireStrategy(\n nullStrategies[nullFormat],\n \"nullFormat\",\n nullFormat,\n '\"default\" | \"hidden\"',\n ),\n number: requireStrategy(\n numberStrategies[numberFormat],\n \"numberFormat\",\n numberFormat,\n '\"none\" | \"auto\"',\n ),\n array: requireStrategy(\n arrayStrategies[arrayFormat],\n \"arrayFormat\",\n arrayFormat,\n '\"none\" | \"brackets\" | \"index\" | \"comma\"',\n ),\n});\n\n// =============================================================================\n// Default Strategies\n// =============================================================================\n\n/**\n * Default strategies matching DEFAULT_OPTIONS.\n * Used when no custom options are provided.\n */\nexport const DEFAULT_STRATEGIES: ResolvedStrategies = {\n boolean: booleanStrategies.auto,\n null: nullStrategies.default,\n number: numberStrategies.auto,\n array: arrayStrategies.none,\n};\n"],"mappings":"wGA4DM,GACJ,EACA,EACA,EACA,IACM,CACN,GAAI,IAAa,IAAA,GACf,MAAU,UACR,2BAA2B,EAAM,IAAI,EAAM,eAAe,GAC5D,EAGF,OAAO,CACT,EAEa,GACX,EACA,EACA,EACA,KACwB,CACxB,QAAS,EACPA,EAAAA,kBAAkB,GAClB,gBACA,EACA,gCACF,EACA,KAAM,EACJC,EAAAA,eAAe,GACf,aACA,EACA,sBACF,EACA,OAAQ,EACNC,EAAAA,iBAAiB,GACjB,eACA,EACA,iBACF,EACA,MAAO,EACLC,EAAAA,gBAAgB,GAChB,cACA,EACA,yCACF,CACF,GAUa,EAAyC,CACpD,QAASH,EAAAA,kBAAkB,KAC3B,KAAMC,EAAAA,eAAe,QACrB,OAAQC,EAAAA,iBAAiB,KACzB,MAAOC,EAAAA,gBAAgB,IACzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"null.js","names":[],"sources":["../../../../../src/engine/search-params/strategies/null.ts"],"sourcesContent":["/**\n * Null encoding/decoding strategies.\n *\n * @module search-params/strategies/null\n */\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for encoding/decoding null values.\n */\n// =============================================================================\n// Strategy Map\n// =============================================================================\n\nimport type { NullFormat } from \"../types\";\n\nexport interface NullStrategy {\n /**\n * Encodes a null value as a query string segment.\n *\n * @param name - URL-encoded parameter name\n * @returns Query string segment (e.g., \"key\" or \"\")\n */\n encode: (name: string) => string;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * Default null format - key only without value.\n * Example: ?key (no equals sign)\n */\nexport const defaultNullStrategy: NullStrategy = {\n encode: (name) => name,\n};\n\n/**\n * Null values are hidden (omitted from query string).\n * Example: (nothing)\n */\nexport const hiddenNullStrategy: NullStrategy = {\n encode: () => \"\",\n};\n\n/**\n * Map of null format to strategy implementation.\n */\nexport const nullStrategies: Record<NullFormat, NullStrategy> = {\n default: defaultNullStrategy,\n hidden: hiddenNullStrategy,\n};\n"],"mappings":"AAqCA,MAAa,EAAoC,CAC/C,OAAS,GAAS,CACpB,EAMa,EAAmC,CAC9C,WAAc,EAChB,EAKa,EAAmD,CAC9D,QAAS,EACT,OAAQ,CACV"}
|
|
@@ -0,0 +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 Object.is(i,-0)||!Number.isSafeInteger(i)&&!r?null:i}},n={auto:t,none:e};exports.autoNumberStrategy=t,exports.noneNumberStrategy=e,exports.numberStrategies=n;
|
|
2
|
+
//# sourceMappingURL=number.js.map
|
|
@@ -0,0 +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 // Negative zero is not round-trippable: build(-0) emits \"0\" and\n // String(-0) === \"0\", so \"-0\"/\"-0.0\" must stay strings. (#898)\n if (Object.is(num, -0)) {\n return null;\n }\n\n // Reject unsafe integers — precision loss would corrupt the value on roundtrip.\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,EAaxB,OATI,OAAO,GAAG,EAAK,EAAE,GAKjB,CAAC,OAAO,cAAc,CAAG,GAAK,CAAC,EAC1B,KAGF,CACT,CACF,EASa,EAAyD,CACpE,KAAM,EACN,KAAM,CACR"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//#region src/engine/search-params/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Search Params Type Definitions.
|
|
4
|
+
*
|
|
5
|
+
* Consolidated interfaces and type aliases for the search-params module.
|
|
6
|
+
*
|
|
7
|
+
* @module search-params/types
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Array parameter encoding format.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* - `none` - repeated keys: `a=1&a=2`
|
|
14
|
+
* - `brackets` - bracket notation: `a[]=1&a[]=2`
|
|
15
|
+
* - `index` - indexed notation: `a[0]=1&a[1]=2`
|
|
16
|
+
* - `comma` - comma-separated: `a=1,2`
|
|
17
|
+
*/
|
|
18
|
+
type ArrayFormat = "none" | "brackets" | "index" | "comma";
|
|
19
|
+
/**
|
|
20
|
+
* Boolean parameter encoding format.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* - `none` - no special handling
|
|
24
|
+
* - `auto` - auto-detect "true"/"false" strings as boolean values
|
|
25
|
+
* - `empty-true` - true values have no value: `?flag` instead of `?flag=true`
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* Under `empty-true` the bare-key wire form (`?flag`) is reserved for `true`, so
|
|
29
|
+
* a `null` value with `nullFormat: "default"` (which also encodes to a bare key)
|
|
30
|
+
* is **not representable**: it round-trips back as `true`, not `null`. Both null
|
|
31
|
+
* and true collapse to the same token and only `true` decodes back. Pair
|
|
32
|
+
* `empty-true` with `nullFormat: "hidden"`, or avoid null query values.
|
|
33
|
+
*/
|
|
34
|
+
type BooleanFormat = "none" | "auto" | "empty-true";
|
|
35
|
+
/**
|
|
36
|
+
* Null parameter encoding format.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* - `default` - key only: `?key` (collides with `booleanFormat: "empty-true"`,
|
|
40
|
+
* which reserves the bare-key form for `true` — null then decodes as `true`)
|
|
41
|
+
* - `hidden` - omit from query string
|
|
42
|
+
*/
|
|
43
|
+
type NullFormat = "default" | "hidden";
|
|
44
|
+
/**
|
|
45
|
+
* Number parameter encoding format.
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* - `none` - no special handling
|
|
49
|
+
* - `auto` - decode canonical decimal numbers (`/^-?(0|[1-9]\d*)(\.\d+)?$/`) to
|
|
50
|
+
* `Number()`. Recognizes negatives so the parsed type matches what
|
|
51
|
+
* `build`/`navigate` produce; rejects leading zeros (`"007"`), exponent
|
|
52
|
+
* notation, unsafe integers, and negative zero (`"-0"` — the grammar matches
|
|
53
|
+
* it, but an `Object.is` guard rejects it since `build(-0)` emits `"0"`, #898)
|
|
54
|
+
* — those keep their exact text as strings.
|
|
55
|
+
*/
|
|
56
|
+
type NumberFormat = "none" | "auto";
|
|
57
|
+
/**
|
|
58
|
+
* Options for search-params parsing and building.
|
|
59
|
+
*/
|
|
60
|
+
interface Options {
|
|
61
|
+
/** Array parameter encoding format. @default "none" */
|
|
62
|
+
arrayFormat?: ArrayFormat;
|
|
63
|
+
/** Boolean parameter encoding format. @default "auto" */
|
|
64
|
+
booleanFormat?: BooleanFormat;
|
|
65
|
+
/** Null parameter encoding format. @default "default" */
|
|
66
|
+
nullFormat?: NullFormat;
|
|
67
|
+
/** Number parameter encoding format. @default "auto" */
|
|
68
|
+
numberFormat?: NumberFormat;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { ArrayFormat, BooleanFormat, NullFormat, NumberFormat, Options };
|
|
72
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/engine/search-params/types.ts"],"mappings":";;;;;;;;;;;;;;;;;KAqBY;;;;;;;;;;;;;;;;KAiBA;;;;;;;;;KAUA;;;;;;;;;;;;;KAcA;;;;UASK;;EAEf,cAAc;;EAEd,gBAAgB;;EAEhB,aAAa;;EAEb,eAAe"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,t=t=>{try{return encodeURIComponent(t)}catch(n){if(!(n instanceof URIError))throw n;return encodeURIComponent(String(t).replaceAll(e,`�`))}};exports.safeEncode=t;
|
|
2
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../../../../src/engine/search-params/utils.ts"],"sourcesContent":["/**\n * Utility functions for search-params.\n *\n * Internalized from https://github.com/troch/search-params\n *\n * @module search-params/utils\n */\n\n// =============================================================================\n// Total Percent-Encoding\n// =============================================================================\n\n/**\n * A lone (unpaired) surrogate — a high surrogate not followed by a low, or a low\n * not preceded by a high. This is a manual, lib-target-agnostic\n * `String.prototype.toWellFormed` (ES2024): consumers compile this `src` under their\n * OWN `tsconfig` (whose `lib` may predate es2024 — e.g. `hash-plugin`), so a regex\n * `replace` avoids a `toWellFormed` type error in every consumer while producing the\n * identical result (verified by parity across the surrogate space).\n */\nconst LONE_SURROGATE_RGX =\n /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g;\n\n/**\n * Percent-encodes a value, staying TOTAL on a lone (unpaired) surrogate.\n *\n * `encodeURIComponent` throws `URIError` on an unpaired UTF-16 surrogate — the\n * only input it rejects. `parseQuery` accepts such a value (its non-percent decode\n * is an identity fast path), so `build(parseQuery(qs))` would throw and violate the\n * inverse-pair totality invariant `range(parseQuery) ⊆ dom(build)` (INVARIANTS\n * Parse/Build #12). We sanitize each lone surrogate to U+FFFD instead: the first\n * round-trip mutates the (already non-round-trippable) garbage, then stabilises;\n * well-formed inputs are untouched. Single source for BOTH encode sites — scalar/key\n * (`encode.ts`) and array element (`strategies/array.ts`) — so they cannot drift\n * (#1314).\n */\nexport const safeEncode = (value: string | number | boolean): string => {\n try {\n return encodeURIComponent(value);\n } catch (error) {\n // Only a lone surrogate (URIError) is sanitized. Anything else is a real error\n // the caller must see — e.g. a Symbol value throws `TypeError` here, and\n // `String(symbol)` would silently coerce it to \"Symbol(…)\" instead of rethrowing.\n if (!(error instanceof URIError)) {\n throw error;\n }\n\n // Lone surrogate → U+FFFD, keeping build total.\n return encodeURIComponent(\n String(value).replaceAll(LONE_SURROGATE_RGX, \"�\"),\n );\n }\n};\n"],"mappings":"AAoBA,MAAM,EACJ,0EAeW,EAAc,GAA6C,CACtE,GAAI,CACF,OAAO,mBAAmB,CAAK,CACjC,OAAS,EAAO,CAId,GAAI,EAAE,aAAiB,UACrB,MAAM,EAIR,OAAO,mBACL,OAAO,CAAK,CAAC,CAAC,WAAW,EAAoB,GAAG,CAClD,CACF,CACF"}
|