@real-router/core 0.84.0 → 0.85.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-CUtHMhRV.js → Router-D6629wsY.js} +2 -2
- package/dist/cjs/{Router-CUtHMhRV.js.map → Router-D6629wsY.js.map} +1 -1
- package/dist/cjs/Router.d.ts.map +1 -1
- package/dist/cjs/api/getRoutesApi.d.ts.map +1 -1
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/buildParamMeta-0m11OIJS.js.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/internals.d.ts +7 -0
- package/dist/cjs/internals.d.ts.map +1 -1
- package/dist/esm/Router-hyjjNX3c.mjs +2 -0
- package/dist/esm/{Router-sWkNlsWW.mjs.map → Router-hyjjNX3c.mjs.map} +1 -1
- package/dist/esm/Router.d.mts.map +1 -1
- package/dist/esm/api/getRoutesApi.d.mts.map +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/buildParamMeta-D8td47fL.mjs.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/internals.d.mts +7 -0
- package/dist/esm/internals.d.mts.map +1 -1
- package/package.json +2 -2
- package/dist/cjs/Router.js +0 -2
- package/dist/cjs/Router.js.map +0 -1
- package/dist/cjs/RouterError.js +0 -2
- package/dist/cjs/RouterError.js.map +0 -1
- package/dist/cjs/api/cloneRouter.js +0 -2
- package/dist/cjs/api/cloneRouter.js.map +0 -1
- package/dist/cjs/api/getDependenciesApi.js +0 -2
- package/dist/cjs/api/getDependenciesApi.js.map +0 -1
- package/dist/cjs/api/getLifecycleApi.js +0 -2
- package/dist/cjs/api/getLifecycleApi.js.map +0 -1
- package/dist/cjs/api/getPluginApi.js +0 -2
- package/dist/cjs/api/getPluginApi.js.map +0 -1
- package/dist/cjs/api/getRoutesApi.js +0 -2
- package/dist/cjs/api/getRoutesApi.js.map +0 -1
- package/dist/cjs/api/helpers.js +0 -2
- package/dist/cjs/api/helpers.js.map +0 -1
- package/dist/cjs/channels/defaults.js +0 -2
- package/dist/cjs/channels/defaults.js.map +0 -1
- package/dist/cjs/channels/guard.js +0 -2
- package/dist/cjs/channels/guard.js.map +0 -1
- package/dist/cjs/channels/modeGate.js +0 -2
- package/dist/cjs/channels/modeGate.js.map +0 -1
- package/dist/cjs/constants.js +0 -2
- package/dist/cjs/constants.js.map +0 -1
- package/dist/cjs/createRouter.js +0 -2
- package/dist/cjs/createRouter.js.map +0 -1
- package/dist/cjs/engine/builder/buildTree.js +0 -2
- package/dist/cjs/engine/builder/buildTree.js.map +0 -1
- package/dist/cjs/engine/builder/computeCaches.js +0 -2
- package/dist/cjs/engine/builder/computeCaches.js.map +0 -1
- package/dist/cjs/engine/builder/createRouteTree.js +0 -2
- package/dist/cjs/engine/builder/createRouteTree.js.map +0 -1
- package/dist/cjs/engine/createMatcher.js +0 -2
- package/dist/cjs/engine/createMatcher.js.map +0 -1
- package/dist/cjs/engine/operations/routeTreeToDefinitions.js +0 -2
- package/dist/cjs/engine/operations/routeTreeToDefinitions.js.map +0 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js +0 -2
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +0 -1
- package/dist/cjs/engine/path-matcher/buildParamMeta.js +0 -2
- package/dist/cjs/engine/path-matcher/buildParamMeta.js.map +0 -1
- package/dist/cjs/engine/path-matcher/encoding.js +0 -2
- package/dist/cjs/engine/path-matcher/encoding.js.map +0 -1
- package/dist/cjs/engine/path-matcher/parseSegment.js +0 -2
- package/dist/cjs/engine/path-matcher/parseSegment.js.map +0 -1
- package/dist/cjs/engine/path-matcher/pathUtils.js +0 -2
- package/dist/cjs/engine/path-matcher/pathUtils.js.map +0 -1
- package/dist/cjs/engine/path-matcher/percentEncoding.js +0 -2
- package/dist/cjs/engine/path-matcher/percentEncoding.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/buildParts.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/context.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/context.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/errors.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/errors.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/index.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/index.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/trie.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/trie.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/trieNodes.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +0 -1
- package/dist/cjs/engine/search-params/decode.js +0 -2
- package/dist/cjs/engine/search-params/decode.js.map +0 -1
- package/dist/cjs/engine/search-params/encode.js +0 -2
- package/dist/cjs/engine/search-params/encode.js.map +0 -1
- package/dist/cjs/engine/search-params/searchParams.js +0 -2
- package/dist/cjs/engine/search-params/searchParams.js.map +0 -1
- package/dist/cjs/engine/search-params/strategies/array.js +0 -2
- package/dist/cjs/engine/search-params/strategies/array.js.map +0 -1
- package/dist/cjs/engine/search-params/strategies/boolean.js +0 -2
- package/dist/cjs/engine/search-params/strategies/boolean.js.map +0 -1
- package/dist/cjs/engine/search-params/strategies/index.js +0 -2
- package/dist/cjs/engine/search-params/strategies/index.js.map +0 -1
- package/dist/cjs/engine/search-params/strategies/null.js +0 -2
- package/dist/cjs/engine/search-params/strategies/null.js.map +0 -1
- package/dist/cjs/engine/search-params/strategies/number.js +0 -2
- package/dist/cjs/engine/search-params/strategies/number.js.map +0 -1
- package/dist/cjs/engine/search-params/utils.js +0 -2
- package/dist/cjs/engine/search-params/utils.js.map +0 -1
- package/dist/cjs/engine/validation/route-batch.js +0 -2
- package/dist/cjs/engine/validation/route-batch.js.map +0 -1
- package/dist/cjs/engine/validation/routes.js +0 -2
- package/dist/cjs/engine/validation/routes.js.map +0 -1
- package/dist/cjs/getNavigator.js +0 -2
- package/dist/cjs/getNavigator.js.map +0 -1
- package/dist/cjs/guards.js +0 -2
- package/dist/cjs/guards.js.map +0 -1
- package/dist/cjs/helpers.js +0 -2
- package/dist/cjs/helpers.js.map +0 -1
- package/dist/cjs/internals.js +0 -2
- package/dist/cjs/internals.js.map +0 -1
- package/dist/cjs/limits.js +0 -2
- package/dist/cjs/limits.js.map +0 -1
- package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js +0 -2
- package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js.map +0 -1
- package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js +0 -2
- package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/InFlightNavigation.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/InFlightNavigation.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/constants.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/constants.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/transition/executeNavigation.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/transition/executeNavigation.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/transition/navigateToNotFound.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/transition/navigateToNotFound.js.map +0 -1
- package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js +0 -2
- package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +0 -1
- package/dist/cjs/namespaces/OptionsNamespace/constants.js +0 -2
- package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +0 -1
- package/dist/cjs/namespaces/OptionsNamespace/helpers.js +0 -2
- package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +0 -1
- package/dist/cjs/namespaces/OptionsNamespace/validators.js +0 -2
- package/dist/cjs/namespaces/OptionsNamespace/validators.js.map +0 -1
- package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js +0 -2
- package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js.map +0 -1
- package/dist/cjs/namespaces/PluginsNamespace/constants.js +0 -2
- package/dist/cjs/namespaces/PluginsNamespace/constants.js.map +0 -1
- package/dist/cjs/namespaces/PluginsNamespace/validators.js +0 -2
- package/dist/cjs/namespaces/PluginsNamespace/validators.js.map +0 -1
- package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js +0 -2
- package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js.map +0 -1
- package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js +0 -2
- package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js.map +0 -1
- package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js +0 -2
- package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/constants.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/constants.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +0 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +0 -2
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +0 -1
- package/dist/cjs/pipeline/buildURL.js +0 -2
- package/dist/cjs/pipeline/buildURL.js.map +0 -1
- package/dist/cjs/pipeline/canonicalize.js +0 -2
- package/dist/cjs/pipeline/canonicalize.js.map +0 -1
- package/dist/cjs/pipeline/materialize.js +0 -2
- package/dist/cjs/pipeline/materialize.js.map +0 -1
- package/dist/cjs/routerFSM.js +0 -2
- package/dist/cjs/routerFSM.js.map +0 -1
- package/dist/cjs/transitionPath.js +0 -2
- package/dist/cjs/transitionPath.js.map +0 -1
- package/dist/cjs/utils/event-emitter/EventEmitter.js +0 -2
- package/dist/cjs/utils/event-emitter/EventEmitter.js.map +0 -1
- package/dist/cjs/utils/fsm/fsm.js +0 -2
- package/dist/cjs/utils/fsm/fsm.js.map +0 -1
- package/dist/cjs/utils/logger/RouterLogger.js +0 -2
- package/dist/cjs/utils/logger/RouterLogger.js.map +0 -1
- package/dist/cjs/utils/logger/constants.js +0 -2
- package/dist/cjs/utils/logger/constants.js.map +0 -1
- package/dist/cjs/wiring/wireNamespaces.js +0 -2
- package/dist/cjs/wiring/wireNamespaces.js.map +0 -1
- package/dist/esm/Router-sWkNlsWW.mjs +0 -2
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../parseSegment.js"),t=require("../encoding.js"),n=require("./context.js");function r(e,n,r){let i=n.has(e.name);return{paramName:e.name,encoder:i?e=>t.encodeParam(e,r):t.ENCODING_METHODS[r]}}function i(t,i,a,o){let s=new Set(o),c=new Set;for(let e of i){for(let t of e.paramMeta.urlParams)s.add(t);for(let t of e.paramMeta.spatParams)c.add(t)}if(s.size===0)return{buildStaticParts:[t],buildParamSlots:n.EMPTY_PARAM_SLOTS};let l=[],u=[],d=``,f=e.splitPathSegments(t),p=f.length-1;for(let[t,n]of f.entries()){let i=e.parseSegment(n);if(!(!(`error`in i)&&i.kind===`splat`&&t!==p)){if(t>0&&(d+=`/`),`error`in i||i.kind===`static`){d+=n;continue}l.push(d),d=``,u.push(r(i,c,a))}}return l.push(d),{buildStaticParts:l,buildParamSlots:u}}exports.compileBuildParts=i;
|
|
2
|
-
//# sourceMappingURL=buildParts.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"buildParts.js","names":["encodeParam","ENCODING_METHODS","EMPTY_PARAM_SLOTS","splitPathSegments","parseSegment"],"sources":["../../../../../src/engine/path-matcher/registration/buildParts.ts"],"sourcesContent":["// Build-path template compilation: turns a (constraint-stripped) route path into\n// `buildStaticParts` + `buildParamSlots` through the shared `parseSegment` tokenizer (Реш.1).\n\nimport { encodeParam, ENCODING_METHODS } from \"../encoding\";\nimport {\n parseSegment,\n splitPathSegments,\n type SegmentTokens,\n} from \"../parseSegment\";\nimport { EMPTY_PARAM_SLOTS } from \"./context\";\n\nimport type {\n BuildParamSlot,\n MatcherInputNode,\n URLParamsEncodingType,\n} from \"../types\";\n\n/**\n * Builds one `BuildParamSlot` from a param/splat token. The encoder is the single\n * `encodeParam` implementation the encoding unit/property suites assert (the splat\n * variant encodes each segment individually, preserving `/`), so prod and the\n * oracle can't drift (#860).\n */\nfunction makeBuildParamSlot(\n token: Extract<SegmentTokens, { kind: \"param\" | \"splat\" }>,\n allSplatParams: ReadonlySet<string>,\n encoding: URLParamsEncodingType,\n): BuildParamSlot {\n const isSplat = allSplatParams.has(token.name);\n\n return {\n paramName: token.name,\n encoder: isSplat\n ? (value: string): string => encodeParam(value, encoding)\n : ENCODING_METHODS[encoding],\n };\n}\n\nexport function compileBuildParts(\n normalizedPath: string,\n segments: readonly MatcherInputNode[],\n encoding: URLParamsEncodingType,\n rootUrlParams: readonly string[],\n): {\n buildStaticParts: readonly string[];\n buildParamSlots: readonly BuildParamSlot[];\n} {\n // `normalizedPath` carries the ROOT path as its prefix, but the root node is\n // absent from `segments` — so seeding these from the segment walk alone made a\n // root-declared slot invisible here, and the fast path below emitted the whole\n // path (`:tenant` included) as literal static text (#1567).\n const allUrlParams = new Set<string>(rootUrlParams);\n // Splat names come from the segment walk only: a ROOT splat is never the final\n // segment (a route path always follows it), so it never becomes a slot and its\n // encoder is never chosen. (#1567 seeded it from the root too; #1568 made that\n // unreachable — see the finality rule below.)\n const allSplatParams = new Set<string>();\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n allUrlParams.add(param);\n }\n\n for (const param of segment.paramMeta.spatParams) {\n allSplatParams.add(param);\n }\n }\n\n // Stryker disable next-line BlockStatement: equivalent — fast path; the param-compile loop below yields [normalizedPath]/[] when allUrlParams is empty — identical output. Proven by injection.\n if (allUrlParams.size === 0) {\n return {\n buildStaticParts: [normalizedPath],\n buildParamSlots: EMPTY_PARAM_SLOTS,\n };\n }\n\n const parts: string[] = [];\n const slots: BuildParamSlot[] = [];\n let current = \"\";\n\n // Build the static-parts / param-slots template through the SAME tokenizer the\n // trie (L3) and `buildParamMeta` (L1) consume — `parseSegment`, not a parallel\n // `paramRgx` — so build's param NAME can no longer drift from the trie's\n // (#1050/#1150 build≠match closed structurally, not just by the round-trip\n // property). The path is already constraint-stripped (`matchPath`), so\n // `parseSegment` sees only name/optional; each `/` separator `splitPathSegments`\n // split away is re-added to the running static part.\n const pathSegments = splitPathSegments(normalizedPath);\n const lastIndex = pathSegments.length - 1;\n\n for (const [i, pathSegment] of pathSegments.entries()) {\n const token = parseSegment(pathSegment);\n\n // A splat binds ONLY as the final segment. The trie matches a splat node's\n // children at the splat's OWN position (INVARIANTS Matching #24: `/n/*rest`\n // + `/n/*rest/edit` → `/n/edit` resolves to the child), so a splat with\n // anything after it always captures the empty string. Emitting a slot for it\n // printed a URL `match` could not resolve — or, under a splat parent, one\n // that fell back to the parent's wildcard. It contributes neither text nor\n // separator. (#1568)\n if (!(\"error\" in token) && token.kind === \"splat\" && i !== lastIndex) {\n continue;\n }\n\n if (i > 0) {\n current += \"/\";\n }\n\n if (\"error\" in token || token.kind === \"static\") {\n // Static text — or a malformed segment, whose route is rejected at\n // `registerTree` before these buildParts are ever read (output moot).\n current += pathSegment;\n continue;\n }\n\n // param | splat: close the accumulated static part, emit a slot.\n parts.push(current);\n current = \"\";\n slots.push(makeBuildParamSlot(token, allSplatParams, encoding));\n }\n\n parts.push(current);\n\n return { buildStaticParts: parts, buildParamSlots: slots };\n}\n"],"mappings":"4FAuBA,SAAS,EACP,EACA,EACA,EACgB,CAChB,IAAM,EAAU,EAAe,IAAI,EAAM,IAAI,EAE7C,MAAO,CACL,UAAW,EAAM,KACjB,QAAS,EACJ,GAA0BA,EAAAA,YAAY,EAAO,CAAQ,EACtDC,EAAAA,iBAAiB,EACvB,CACF,CAEA,SAAgB,EACd,EACA,EACA,EACA,EAIA,CAKA,IAAM,EAAe,IAAI,IAAY,CAAa,EAK5C,EAAiB,IAAI,IAE3B,IAAK,IAAM,KAAW,EAAU,CAC9B,IAAK,IAAM,KAAS,EAAQ,UAAU,UACpC,EAAa,IAAI,CAAK,EAGxB,IAAK,IAAM,KAAS,EAAQ,UAAU,WACpC,EAAe,IAAI,CAAK,CAE5B,CAGA,GAAI,EAAa,OAAS,EACxB,MAAO,CACL,iBAAkB,CAAC,CAAc,EACjC,gBAAiBC,EAAAA,iBACnB,EAGF,IAAM,EAAkB,CAAC,EACnB,EAA0B,CAAC,EAC7B,EAAU,GASR,EAAeC,EAAAA,kBAAkB,CAAc,EAC/C,EAAY,EAAa,OAAS,EAExC,IAAK,GAAM,CAAC,EAAG,KAAgB,EAAa,QAAQ,EAAG,CACrD,IAAM,EAAQC,EAAAA,aAAa,CAAW,EASlC,OAAE,UAAW,IAAU,EAAM,OAAS,SAAW,IAAM,GAQ3D,IAJI,EAAI,IACN,GAAW,KAGT,UAAW,GAAS,EAAM,OAAS,SAAU,CAG/C,GAAW,EACX,QACF,CAGA,EAAM,KAAK,CAAO,EAClB,EAAU,GACV,EAAM,KAAK,EAAmB,EAAO,EAAgB,CAAQ,CAAC,CAL9D,CAMF,CAIA,OAFA,EAAM,KAAK,CAAO,EAEX,CAAE,iBAAkB,EAAO,gBAAiB,CAAM,CAC3D"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=Object.freeze([]),t=Object.freeze(new Set),n=Object.freeze([]),r=Object.freeze({}),i=Object.freeze({}),a=Object.freeze({});exports.EMPTY_PARAMS=r,exports.EMPTY_PARAM_SLOTS=n,exports.EMPTY_ROUTE_META=a,exports.EMPTY_SEARCH=i,exports.EMPTY_STRINGS=e,exports.EMPTY_STRING_SET=t;
|
|
2
|
-
//# sourceMappingURL=context.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../../../../src/engine/path-matcher/registration/context.ts"],"sourcesContent":["// Shared registration context: the `RegistrationState` interface and the frozen\n// empty-value sentinels (#1009). Leaf module (no sibling imports) so\n// `trie`/`trieNodes`/`buildParts` can depend on `RegistrationState` without\n// cycling back to the orchestrator.\n\nimport type {\n BuildParamSlot,\n CompiledRoute,\n ResolvedMatcherOptions,\n SegmentNode,\n} from \"../types\";\n\n// Shared frozen sentinels for the no-params common case — avoid a fresh empty\n// Set/array per route (#1009). All are ReadonlySet/[] and read-only on the\n// match/build hot paths.\nexport const EMPTY_STRINGS: readonly string[] = Object.freeze([]);\n\n// #1240 §5: freeze the Set shell too, so the \"Shared frozen sentinels\" claim\n// above holds for ALL of them and the #1009 sentinels are consistent with route-tree's\n// frozen `EMPTY_CHILDREN_MAP`. `Object.freeze` locks only the shell (not `.add`\n// — see route-tree INVARIANTS CC1), but these are `Readonly`-typed and never mutated.\nexport const EMPTY_STRING_SET: ReadonlySet<string> = Object.freeze(\n new Set<string>(),\n);\n\nexport const EMPTY_PARAM_SLOTS: readonly BuildParamSlot[] = Object.freeze([]);\n\nexport const EMPTY_PARAMS: Readonly<Record<string, unknown>> = Object.freeze(\n {},\n);\n\n/** Frozen empty query bag for precomputed query-less cached results (RFC-4 M2). */\nexport const EMPTY_SEARCH: Readonly<Record<string, unknown>> = Object.freeze(\n {},\n);\n\n// Shared frozen sentinel for a route whose every segment has an empty\n// paramTypeMap (all-static chain): `buildMeta` returns this instead of a fresh\n// per-route `{ [fullName]: {} }` record — with N distinct route names those\n// records degrade into N dictionary-mode objects (route-unique keys) while\n// carrying zero information. Consumers do keyed lookups (`meta[name]`) and\n// treat a missing entry as \"no params\", so empty-entry ≡ missing-entry; the\n// sentinel stays truthy for the route-found check in core's buildNavigateState.\nexport const EMPTY_ROUTE_META: Readonly<\n Record<string, Record<string, \"url\" | \"query\">>\n> = Object.freeze({});\n\nexport interface RegistrationState {\n readonly root: SegmentNode;\n readonly options: ResolvedMatcherOptions;\n readonly routesByName: Map<string, CompiledRoute>;\n readonly staticCache: Map<string, CompiledRoute>;\n readonly rootQueryParams: readonly string[];\n // The root node is deliberately absent from `matchSegments` (see SegmentMatcher\n // `registerTree`), so anything derived from a segment walk is root-blind. Its\n // QUERY declarations already had to be threaded separately (#1556); its PATH\n // slots need the same treatment for the build side (#1567).\n readonly rootUrlParams: readonly string[];\n}\n"],"mappings":"AAeA,MAAa,EAAmC,OAAO,OAAO,CAAC,CAAC,EAMnD,EAAwC,OAAO,OAC1D,IAAI,GACN,EAEa,EAA+C,OAAO,OAAO,CAAC,CAAC,EAE/D,EAAkD,OAAO,OACpE,CAAC,CACH,EAGa,EAAkD,OAAO,OACpE,CAAC,CACH,EASa,EAET,OAAO,OAAO,CAAC,CAAC"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
function e(e,t,n){throw Error(`[SegmentMatcher.registerTree] Parameter name conflict at the same path position: '${n}${e}' and '${n}${t}'. A parametric URL segment binds to a single name across every route that shares that position — the value cannot be captured under two names. Rename one so both routes agree (e.g. use '${n}${e}' in both).`)}function t(){throw Error(`[SegmentMatcher.registerTree] Empty parameter name: a parameter marker (':' or '*') must be followed by a name (e.g. ':id', '*rest'). A name-less marker, or a trailing '?' with no parameter name, would capture under an empty key at match but emit a literal at build — the two disagree, so it is rejected.`)}function n(e){throw Error(`[SegmentMatcher.registerTree] Fused parameter marker in segment "${e}": a ':'/'*' marker must begin a segment (e.g. 'a/:b', not 'a:b'). build extracts it as a param while the trie treats the segment as a literal — the two disagree.`)}function r(e){throw Error(`[SegmentMatcher.registerTree] Trailing parameter marker in segment "${e}": a param name cannot end in a bare ':' or '*' (e.g. ':y*' — the name is 'y' plus a stray marker). build/meta would capture the marker into the name while the gate rejects it as name-less — the two disagree, so it is rejected.`)}function i(e){throw Error(`[SegmentMatcher.registerTree] Optional params are not supported: "${e}" — declare two sibling routes instead (one with the segment, one without). The route hierarchy already expresses optionality.`)}function a(e){throw Error(`[SegmentMatcher.registerTree] Regex constraints are not supported: "<"/">" are reserved in path segments ("${e}"). Match the segment as a plain string and validate the value in a guard (canActivate) or app code.`)}function o(e){throw Error(`[SegmentMatcher.registerTree] Non-ASCII static segment "${e}": match rejects non-ASCII input and compares static keys raw, so this route would never match. Percent-encode it (e.g. "/caf%C3%A9") or use a param.`)}function s(e,o){switch(e){case`name-less`:return t();case`trailing-marker`:return r(o);case`fused-marker`:return n(o);case`optional-removed`:return i(o);case`constraint-removed`:return a(o)}}function c(e,t){let n=new Set,r=``;for(let e of t){if(n.has(e)){r=e;break}n.add(e)}throw Error(`[SegmentMatcher.registerTree] Duplicate parameter name ':${r}' in route "${e}": a param name must be unique within a route — two positions cannot both bind ':${r}' (the second silently overwrites the first). Rename one.`)}function l(e,t){throw Error(`[SegmentMatcher.registerTree] Invalid query-param declaration "${t}" in route "${e}": a query-param name cannot contain '<' or '>' — it would never round-trip. Rename the query param.`)}function u(e,t){throw Error(`[SegmentMatcher.registerTree] Duplicate route path: routes "${e}" and "${t}" resolve to the same URL. The later registration would silently shadow the earlier (its deep link would resolve to the other route). Give them distinct paths.`)}function d(e,t){throw Error(`[SegmentMatcher.registerTree] Index route "${e}" (path "/") under the splat parent "${t}" is not supported: the index sits on the splat node, which the wildcard match never reaches, so it is unreachable. Give the index a distinct path, or make the parent static.`)}exports.throwDuplicateParamName=c,exports.throwDuplicateRoutePath=u,exports.throwEmptyParamName=t,exports.throwInvalidQueryParamName=l,exports.throwNonAsciiStatic=o,exports.throwParamNameConflict=e,exports.throwSegmentGrammarError=s,exports.throwSlashChildUnderDynamicParent=d;
|
|
2
|
-
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../../../../../src/engine/path-matcher/registration/errors.ts"],"sourcesContent":["// Registration error factories: every `throw*` helper the pipeline raises, plus the\n// `throwSegmentGrammarError` code→message dispatcher (Реш.2-A). Pure leaf — each\n// builds a message and throws; none reads trie state.\n\nimport type { SegmentErrorCode } from \"../parseSegment\";\n\n/**\n * Guards against param-name aliasing in the segment trie (issue #736).\n *\n * A parametric (`:name`) or splat (`*name`) position in the trie is keyed by\n * **position**, not by name — but the captured value is written under the name\n * recorded on that position. When two *different* routes share a position under\n * *different* names (e.g. `/user/:id` and `/user/:slug/profile`),\n * first-registration wins the name, so the second route silently captures its\n * value under the wrong key. Through `rewritePathOnMatch` that silent key-swap\n * becomes a hard `start()` crash on a legitimate config — so we reject the\n * ambiguity loudly at registration instead of corrupting matches.\n *\n * The conflict is strictly **cross-route**. Under the 3-token grammar (M1, #1516)\n * insertion is a strict linear walk — a route never revisits a slot it created\n * (the former optional-omit fork, which could land two differently-named params\n * on one position within a single route, is gone). So any name mismatch at a\n * position is unconditionally a prior route's slot: `ensureParamChild` throws on\n * `name !== paramName` with no `ownNodes` exception.\n */\nexport function throwParamNameConflict(\n existingName: string,\n newName: string,\n marker: \":\" | \"*\",\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Parameter name conflict at the same path ` +\n `position: '${marker}${existingName}' and '${marker}${newName}'. A ` +\n `parametric URL segment binds to a single name across every route that ` +\n `shares that position — the value cannot be captured under two names. ` +\n `Rename one so both routes agree (e.g. use '${marker}${existingName}' in both).`,\n );\n}\n\n/**\n * A bare marker (`:` or `*` with no name) compiles to a phantom empty-named\n * slot: match captures the value under `\"\"`, buildPath emits the literal marker,\n * and buildParamMeta reports no param at all — a three-way match/build/meta\n * desync of the same class as #736/#738 (#858). Reject it at registration,\n * symmetrically for both markers, instead of corrupting the trie.\n */\nexport function throwEmptyParamName(): never {\n // Marker-agnostic: this fires for a bare ':'/'*' (`/x/:`, `/x/*`), a marker\n // carrying only a modifier char with no name (`/x/:?`, `/x/:<...>`), AND a\n // static segment with a trailing '?' (`/faq?`) — all routed here via\n // `extractParamName`. So the message must NOT claim a specific ':' marker\n // (there isn't one for `/faq?`, #1241).\n throw new Error(\n `[SegmentMatcher.registerTree] Empty parameter name: a parameter marker ` +\n `(':' or '*') must be followed by a name (e.g. ':id', '*rest'). A name-less ` +\n `marker, or a trailing '?' with no parameter name, would capture under an ` +\n `empty key at match but emit a literal at build — the two disagree, so it ` +\n `is rejected.`,\n );\n}\n\n/**\n * Rejects a `:`/`*` marker fused to a static prefix within a segment (`a:b`,\n * `x:id`, `a*b`): the build/meta param regexes are unanchored and extract it as\n * a param, but this trie honors a marker only at segment start and compiles the\n * segment as a static literal — so `buildPath` emits an unmatchable URL while\n * `match` rejects it (#1050). The sibling of {@link throwEmptyParamName} (#858):\n * an ambiguous marker placement the three parsers cannot agree on. route-tree's\n * validation gate catches this first with a route-contextual error; this is the\n * standalone registration backstop.\n */\nfunction throwFusedMarker(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Fused parameter marker in segment \"${segment}\": ` +\n `a ':'/'*' marker must begin a segment (e.g. 'a/:b', not 'a:b'). build extracts ` +\n `it as a param while the trie treats the segment as a literal — the two disagree.`,\n );\n}\n\n/**\n * Rejects a param name ending in a bare marker (`:y*`, `:y:`, #1324): the\n * build/meta name class (`[^/?<]+`) greedily swallows the trailing `:`/`*` into\n * the name (`y*`) while the route-tree gate reads it as a name-less marker and\n * rejects — a real gate↔backstop divergence (formerly excluded from the parity\n * property, gate-masked in production). `parseSegment` ends the name before a\n * trailing marker, so this backstop now agrees with the gate. The sibling of\n * {@link throwEmptyParamName} (#858) / {@link throwFusedMarker} (#1050) on the\n * trailing-marker axis.\n */\nfunction throwTrailingMarker(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Trailing parameter marker in segment \"${segment}\": ` +\n `a param name cannot end in a bare ':' or '*' (e.g. ':y*' — the name is 'y' plus a ` +\n `stray marker). build/meta would capture the marker into the name while the gate ` +\n `rejects it as name-less — the two disagree, so it is rejected.`,\n );\n}\n\n/**\n * `optional-removed` (M1): a `:x?`/`*x?` optional modifier. The backstop tier —\n * a short, path-free recipe (the route-tree gate's rich tier computes the two\n * concrete sibling paths). Optional params were dropped for zero corpus use +\n * the axis's largest bug cluster; the hierarchy already expresses optionality.\n */\nfunction throwOptionalRemoved(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Optional params are not supported: \"${segment}\" — ` +\n `declare two sibling routes instead (one with the segment, one without). ` +\n `The route hierarchy already expresses optionality.`,\n );\n}\n\n/**\n * `constraint-removed` (M1): a `<re>` constraint or a stray `<`/`>`. The backstop\n * tier — a short recipe (the gate's rich tier names the offending segment). Regex\n * constraints were dropped; validate the value in a guard instead.\n */\nfunction throwConstraintRemoved(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Regex constraints are not supported: \"<\"/\">\" are ` +\n `reserved in path segments (\"${segment}\"). Match the segment as a plain ` +\n `string and validate the value in a guard (canActivate) or app code.`,\n );\n}\n\nexport function throwNonAsciiStatic(segment: string): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Non-ASCII static segment \"${segment}\": match ` +\n `rejects non-ASCII input and compares static keys raw, so this route would ` +\n `never match. Percent-encode it (e.g. \"/caf%C3%A9\") or use a param.`,\n );\n}\n\n/**\n * Dispatches a `parseSegment` grammar-error code (the per-segment backstop) to the\n * matching matcher-level throw — the single place mapping the tokenizer's verdict\n * onto the message, so the reject reason stays byte-identical per code. The two\n * removed-form codes (M1) route to their short recipe throws; the route-tree gate\n * catches the same forms first with its richer route-contextual recipe.\n */\nexport function throwSegmentGrammarError(\n code: SegmentErrorCode,\n segment: string,\n): never {\n switch (code) {\n case \"name-less\": {\n return throwEmptyParamName();\n }\n case \"trailing-marker\": {\n return throwTrailingMarker(segment);\n }\n case \"fused-marker\": {\n return throwFusedMarker(segment);\n }\n case \"optional-removed\": {\n return throwOptionalRemoved(segment);\n }\n case \"constraint-removed\": {\n return throwConstraintRemoved(segment);\n }\n }\n}\n\nexport function throwDuplicateParamName(\n routeName: string,\n names: readonly string[],\n): never {\n const seen = new Set<string>();\n let duplicate = \"\";\n\n for (const name of names) {\n if (seen.has(name)) {\n duplicate = name;\n\n break;\n }\n\n seen.add(name);\n }\n\n throw new Error(\n `[SegmentMatcher.registerTree] Duplicate parameter name ':${duplicate}' in ` +\n `route \"${routeName}\": a param name must be unique within a route — two ` +\n `positions cannot both bind ':${duplicate}' (the second silently overwrites ` +\n `the first). Rename one.`,\n );\n}\n\nexport function throwInvalidQueryParamName(\n routeName: string,\n name: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Invalid query-param declaration \"${name}\" in ` +\n `route \"${routeName}\": a query-param name cannot contain '<' or '>' — it would ` +\n `never round-trip. Rename the query param.`,\n );\n}\n\nexport function throwDuplicateRoutePath(\n existingName: string,\n newName: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Duplicate route path: routes \"${existingName}\" ` +\n `and \"${newName}\" resolve to the same URL. The later registration would ` +\n `silently shadow the earlier (its deep link would resolve to the other ` +\n `route). Give them distinct paths.`,\n );\n}\n\nexport function throwSlashChildUnderDynamicParent(\n routeName: string,\n parentPath: string,\n): never {\n throw new Error(\n `[SegmentMatcher.registerTree] Index route \"${routeName}\" (path \"/\") under the ` +\n `splat parent \"${parentPath}\" is not supported: the index sits on the splat ` +\n `node, which the wildcard match never reaches, so it is unreachable. Give the ` +\n `index a distinct path, or make the parent static.`,\n );\n}\n"],"mappings":"AAyBA,SAAgB,EACd,EACA,EACA,EACO,CACP,MAAU,MACR,qFACgB,IAAS,EAAa,SAAS,IAAS,EAAQ,6LAGhB,IAAS,EAAa,YACxE,CACF,CASA,SAAgB,GAA6B,CAM3C,MAAU,MACR,kTAKF,CACF,CAYA,SAAS,EAAiB,EAAwB,CAChD,MAAU,MACR,oEAAoE,EAAQ,mKAG9E,CACF,CAYA,SAAS,EAAoB,EAAwB,CACnD,MAAU,MACR,uEAAuE,EAAQ,oOAIjF,CACF,CAQA,SAAS,EAAqB,EAAwB,CACpD,MAAU,MACR,qEAAqE,EAAQ,+HAG/E,CACF,CAOA,SAAS,EAAuB,EAAwB,CACtD,MAAU,MACR,8GACiC,EAAQ,qGAE3C,CACF,CAEA,SAAgB,EAAoB,EAAwB,CAC1D,MAAU,MACR,2DAA2D,EAAQ,sJAGrE,CACF,CASA,SAAgB,EACd,EACA,EACO,CACP,OAAQ,EAAR,CACE,IAAK,YACH,OAAO,EAAoB,EAE7B,IAAK,kBACH,OAAO,EAAoB,CAAO,EAEpC,IAAK,eACH,OAAO,EAAiB,CAAO,EAEjC,IAAK,mBACH,OAAO,EAAqB,CAAO,EAErC,IAAK,qBACH,OAAO,EAAuB,CAAO,CAEzC,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,IAAM,EAAO,IAAI,IACb,EAAY,GAEhB,IAAK,IAAM,KAAQ,EAAO,CACxB,GAAI,EAAK,IAAI,CAAI,EAAG,CAClB,EAAY,EAEZ,KACF,CAEA,EAAK,IAAI,CAAI,CACf,CAEA,MAAU,MACR,4DAA4D,EAAU,cAC1D,EAAU,mFACY,EAAU,0DAE9C,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,kEAAkE,EAAK,cAC3D,EAAU,qGAExB,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,+DAA+D,EAAa,SAClE,EAAQ,gKAGpB,CACF,CAEA,SAAgB,EACd,EACA,EACO,CACP,MAAU,MACR,8CAA8C,EAAU,uCACrC,EAAW,+KAGhC,CACF"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../parseSegment.js"),t=require("../buildParamMeta.js"),n=require("../pathUtils.js"),r=require("./context.js"),i=require("./buildParts.js"),a=require("./errors.js"),o=require("./trie.js");function s(r,i,o,l,u){let d=i.fullName===``;d||l.push(i);let f=i.absolute,p=i.paramMeta===t.EMPTY_PARAM_META?i.path:i.paramMeta.pathPattern,m=f&&p.startsWith(`~`)?p.slice(1):p,h=f?m:p;for(let t of e.splitPathSegments(h)){let n=e.parseSegment(t);`error`in n&&a.throwSegmentGrammarError(n.error,t)}let g=h,_=f?g:n.buildFullPath(o,g),v=d?u:c(r,i,_,f?``:o,l,u);for(let e of i.children.values())s(r,e,_,l,v);d||l.pop()}function c(e,t,o,s,c,u){let g=p(o,s),_=Object.freeze([...c]),v=l(_),y=n.normalizeTrailingSlash(o),b=m(e.rootQueryParams,c),{buildStaticParts:x,buildParamSlots:S}=i.compileBuildParts(g?n.normalizeTrailingSlash(s):y,g?c.slice(0,-1):c,e.options.urlParamsEncoding,e.rootUrlParams),C=S.map(e=>e.paramName),w=C.length===0?r.EMPTY_STRING_SET:new Set(C);w.size!==C.length&&a.throwDuplicateParamName(t.fullName,C),h(t.fullName,b);let T={name:t.fullName,parent:u,matchSegments:_,meta:v,declaredQueryParams:b,declaredQueryParamsSet:b.length===0?r.EMPTY_STRING_SET:new Set(b),hasTrailingSlash:o.length>1&&o.endsWith(`/`),buildStaticParts:x,buildParamSlots:S,buildParamNamesSet:w,cachedResult:void 0};return t.paramMeta.urlParams.length===0&&(T.cachedResult=Object.freeze({segments:T.matchSegments,params:r.EMPTY_PARAMS,search:r.EMPTY_SEARCH,meta:T.meta})),e.routesByName.set(t.fullName,T),g?d(e,T,s):f(e,T,o,y,t),T}function l(e){let t;for(let n of e)u(n.paramTypeMap)&&(t??={},t[n.fullName]=n.paramTypeMap);return t===void 0?r.EMPTY_ROUTE_META:Object.freeze(t)}function u(e){for(let t in e)if(Object.hasOwn(e,t))return!0;return!1}function d(e,t,r){o.insertSlashChildIntoTrie(e,t,r);let i=n.normalizeTrailingSlash(r),a=e.options.caseSensitive?i:i.toLowerCase();e.staticCache.has(a)&&e.staticCache.set(a,t)}function f(e,t,n,r,i){if(o.insertIntoTrie(e,t,n),i.paramMeta.urlParams.length===0){let n=e.options.caseSensitive?r:r.toLowerCase();e.staticCache.set(n,t)}}function p(e,t){return n.normalizeTrailingSlash(e)===n.normalizeTrailingSlash(t)}function m(e,t){let n=[];e.length>0&&n.push(...e);for(let e of t)e.paramMeta.queryParams.length>0&&n.push(...e.paramMeta.queryParams);return n.length===0?r.EMPTY_STRINGS:n}function h(e,n){for(let r of n)t.INVALID_QUERY_NAME_RGX.test(r)&&a.throwInvalidQueryParamName(e,r)}exports.registerNode=s;
|
|
2
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["EMPTY_PARAM_META","splitPathSegments","parseSegment","buildFullPath","normalizeTrailingSlash","compileBuildParts","EMPTY_STRING_SET","EMPTY_PARAMS","EMPTY_SEARCH","EMPTY_ROUTE_META","EMPTY_STRINGS","INVALID_QUERY_NAME_RGX"],"sources":["../../../../../src/engine/path-matcher/registration/index.ts"],"sourcesContent":["// Registration entry + orchestration: `registerNode` (the one public entry, re-exported\n// for `SegmentMatcher`) runs the per-node grammar pre-pass (Реш.2-A), compile, and trie\n// insertion, plus the route-meta / query-and-constraint collection helpers.\n// Concerns split into ./context ./errors ./trieNodes ./trie ./buildParts.\n\nimport { EMPTY_PARAM_META, INVALID_QUERY_NAME_RGX } from \"../buildParamMeta\";\nimport { parseSegment, splitPathSegments } from \"../parseSegment\";\nimport { buildFullPath, normalizeTrailingSlash } from \"../pathUtils\";\nimport { compileBuildParts } from \"./buildParts\";\nimport {\n EMPTY_PARAMS,\n EMPTY_SEARCH,\n EMPTY_ROUTE_META,\n EMPTY_STRINGS,\n EMPTY_STRING_SET,\n type RegistrationState,\n} from \"./context\";\nimport {\n throwDuplicateParamName,\n throwInvalidQueryParamName,\n throwSegmentGrammarError,\n} from \"./errors\";\nimport { insertIntoTrie, insertSlashChildIntoTrie } from \"./trie\";\n\nimport type { CompiledRoute, MatcherInputNode } from \"../types\";\n\nexport type { RegistrationState } from \"./context\";\n\nexport function registerNode(\n state: RegistrationState,\n node: MatcherInputNode,\n parentPath: string,\n segments: MatcherInputNode[],\n parentRoute: CompiledRoute | null,\n): void {\n const isRoot = node.fullName === \"\";\n\n if (!isRoot) {\n segments.push(node);\n }\n\n const isAbsolute = node.absolute;\n // The EMPTY_PARAM_META sentinel (fully-static node) carries pathPattern \"\";\n // its real pattern is the node's own path (sentinel is only installed when\n // the two were reference-equal).\n const pathPattern =\n node.paramMeta === EMPTY_PARAM_META\n ? node.path\n : node.paramMeta.pathPattern;\n const strippedPattern =\n isAbsolute && pathPattern.startsWith(\"~\")\n ? pathPattern.slice(1)\n : pathPattern;\n const rawNodePath = isAbsolute ? strippedPattern : pathPattern;\n\n // Per-segment grammar backstop: the trie's own grammar verdict reads the SAME\n // `parseSegment` tokenizer the route-tree gate reads (`findSegmentGrammarError`),\n // so backstop and gate cannot drift on a per-segment form. One pass over the RAW\n // path rejects every rejection form: name-less (#858), fused marker (#1050),\n // trailing marker (#1324), and the removed forms `optional-removed` /\n // `constraint-removed` (M1). So `processSegment` / `extractParamName` downstream\n // see only grammatically valid `static | :param | *splat` segments.\n for (const segment of splitPathSegments(rawNodePath)) {\n const token = parseSegment(segment);\n\n if (\"error\" in token) {\n throwSegmentGrammarError(token.error, segment);\n }\n }\n\n // 3-token grammar (M1): no `<...>` constraint to strip before trie insertion.\n const nodePath = rawNodePath;\n\n const matchPath = isAbsolute ? nodePath : buildFullPath(parentPath, nodePath);\n\n const compileParentPath = isAbsolute ? \"\" : parentPath;\n\n const currentRoute: CompiledRoute | null = isRoot\n ? parentRoute\n : compileAndRegisterRoute(\n state,\n node,\n matchPath,\n compileParentPath,\n segments,\n parentRoute,\n );\n\n for (const child of node.children.values()) {\n registerNode(state, child, matchPath, segments, currentRoute);\n }\n\n if (!isRoot) {\n segments.pop();\n }\n}\n\nfunction compileAndRegisterRoute(\n state: RegistrationState,\n node: MatcherInputNode,\n matchPath: string,\n parentPath: string,\n segments: MatcherInputNode[],\n parentRoute: CompiledRoute | null,\n): CompiledRoute {\n const slashChild = isSlashChild(matchPath, parentPath);\n\n const frozenSegments = Object.freeze([...segments]);\n const frozenMeta = buildMeta(frozenSegments);\n\n const normalizedPath = normalizeTrailingSlash(matchPath);\n\n const declaredQueryParams = collectDeclaredQueryParams(\n state.rootQueryParams,\n segments,\n );\n\n // Slash-child: use parent path for buildParts (not slash-child's path)\n const buildPath = slashChild\n ? normalizeTrailingSlash(parentPath)\n : normalizedPath;\n\n const { buildStaticParts, buildParamSlots } = compileBuildParts(\n buildPath,\n // Stryker disable next-line MethodExpression: equivalent — slash-child buildParts: dropping the last segment vs keeping it yields identical buildStaticParts here (no own params on the slash-child). Proven by injection (full suite green).\n slashChild ? segments.slice(0, -1) : segments,\n state.options.urlParamsEncoding,\n state.rootUrlParams,\n );\n\n // #1151: reject a duplicate param name within one route's full path (`/:id/:id`,\n // a param+splat clash `/:x/*x`, or a parent `/a/:x` + child `/:x`). buildParamSlots\n // keeps duplicates; the trie stores them at DIFFERENT positions under the SAME\n // name, so match's later capture silently overwrites the earlier and\n // rewritePathOnMatch then rewrites the user's URL from the single survivor. The\n // #736 conflict guard only fires on DIFFERENTLY-named params at ONE position, so\n // this same-name case slips through.\n const buildParamNames = buildParamSlots.map((slot) => slot.paramName);\n const buildParamNamesSet =\n buildParamNames.length === 0 ? EMPTY_STRING_SET : new Set(buildParamNames);\n\n if (buildParamNamesSet.size !== buildParamNames.length) {\n throwDuplicateParamName(node.fullName, buildParamNames);\n }\n\n // #1242 §5.1/§5.2/§5.3: validate query-param DECLARATIONS. A declared query name\n // must be a clean token — reject one carrying constraint/query metacharacters\n // (`:b?<\\d+>` declares query `<\\d+>`; `?tab=1` declares `tab=1`), and reject a name\n // never round-trips (`<`/`>` in the name). A name shared with a path param\n // (`/a/:tab?tab`) is legal under M2 — separate params/search channels (#1548).\n validateQueryParamDeclarations(node.fullName, declaredQueryParams);\n\n const compiled: CompiledRoute = {\n name: node.fullName,\n parent: parentRoute,\n matchSegments: frozenSegments,\n meta: frozenMeta,\n declaredQueryParams,\n declaredQueryParamsSet:\n declaredQueryParams.length === 0\n ? EMPTY_STRING_SET\n : new Set(declaredQueryParams),\n hasTrailingSlash: matchPath.length > 1 && matchPath.endsWith(\"/\"),\n buildStaticParts,\n buildParamSlots,\n buildParamNamesSet,\n // Initialized here (not added conditionally below) so static and param\n // routes share one hidden class — avoids a megamorphic CompiledRoute (#1009).\n cachedResult: undefined,\n };\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — cachedResult is a pure match() optimization; #buildResult recomputes the same value on a miss (proven: disabling the whole static cache keeps the unit+property suite green)\n if (node.paramMeta.urlParams.length === 0) {\n compiled.cachedResult = Object.freeze({\n segments: compiled.matchSegments,\n params: EMPTY_PARAMS,\n search: EMPTY_SEARCH,\n meta: compiled.meta,\n });\n }\n\n state.routesByName.set(node.fullName, compiled);\n\n if (slashChild) {\n registerSlashChild(state, compiled, parentPath);\n } else {\n registerStandardRoute(state, compiled, matchPath, normalizedPath, node);\n }\n\n return compiled;\n}\n\nfunction buildMeta(\n segments: readonly MatcherInputNode[],\n): Readonly<Record<string, Record<string, \"url\" | \"query\">>> {\n let meta: Record<string, Record<string, \"url\" | \"query\">> | undefined;\n\n for (const segment of segments) {\n if (!hasAnyParam(segment.paramTypeMap)) {\n continue;\n }\n\n meta ??= {};\n meta[segment.fullName] = segment.paramTypeMap;\n }\n\n return meta === undefined ? EMPTY_ROUTE_META : Object.freeze(meta);\n}\n\n// Allocation-free emptiness probe for a segment's paramTypeMap (Object.keys\n// would allocate a fresh array per segment during registration).\nfunction hasAnyParam(\n paramTypeMap: Readonly<Record<string, \"url\" | \"query\">>,\n): boolean {\n for (const key in paramTypeMap) {\n if (Object.hasOwn(paramTypeMap, key)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction registerSlashChild(\n state: RegistrationState,\n compiled: CompiledRoute,\n parentPath: string,\n): void {\n insertSlashChildIntoTrie(state, compiled, parentPath);\n\n const parentNormalized = normalizeTrailingSlash(parentPath);\n const cacheKey = state.options.caseSensitive\n ? parentNormalized\n : parentNormalized.toLowerCase();\n\n if (state.staticCache.has(cacheKey)) {\n state.staticCache.set(cacheKey, compiled);\n }\n}\n\nfunction registerStandardRoute(\n state: RegistrationState,\n compiled: CompiledRoute,\n matchPath: string,\n normalizedPath: string,\n node: MatcherInputNode,\n): void {\n insertIntoTrie(state, compiled, matchPath);\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — staticCache is a pure match() optimization; #traverse resolves the same route on a miss (proven: disabling the whole static cache keeps the unit+property suite green)\n if (node.paramMeta.urlParams.length === 0) {\n const cacheKey = state.options.caseSensitive\n ? normalizedPath\n : // Stryker disable next-line MethodExpression: equivalent — the case-insensitive cache key only governs a hit; a miss falls through to #traverse, which is also case-insensitive\n normalizedPath.toLowerCase();\n\n state.staticCache.set(cacheKey, compiled);\n }\n}\n\nfunction isSlashChild(matchPath: string, parentPath: string): boolean {\n const normalizedMatch = normalizeTrailingSlash(matchPath);\n const normalizedParent = normalizeTrailingSlash(parentPath);\n\n return normalizedMatch === normalizedParent;\n}\n\nfunction collectDeclaredQueryParams(\n rootQueryParams: readonly string[],\n segments: readonly MatcherInputNode[],\n): readonly string[] {\n // Stryker disable next-line ArrayDeclaration: equivalent — the array is populated then returned; a phantom seed element is never read back (declared-param loop skips absent keys, buildQueryString ignores it). Proven by injection.\n const queryParams: string[] = [];\n\n // Include query params declared on the root node (e.g., a root path like \"?mode\")\n if (rootQueryParams.length > 0) {\n queryParams.push(...rootQueryParams);\n }\n\n for (const segment of segments) {\n if (segment.paramMeta.queryParams.length > 0) {\n queryParams.push(...segment.paramMeta.queryParams);\n }\n }\n\n return queryParams.length === 0 ? EMPTY_STRINGS : queryParams;\n}\n\nfunction validateQueryParamDeclarations(\n routeName: string,\n queryParams: readonly string[],\n): void {\n // A path/query name collision (`/a/:tab?tab`) is legal under M2 — `tab` lives\n // in `state.params` AND `state.search` as separate channels (RFC-4 M2 /\n // #1548); the former \"declared as both\" rejection is gone. Only a query name\n // that can never round-trip (contains `<`/`>`) is still rejected.\n for (const name of queryParams) {\n if (INVALID_QUERY_NAME_RGX.test(name)) {\n throwInvalidQueryParamName(routeName, name);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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 // This ALSO relies on createNode's leading-`/` normalization (#1407): a\n // slash-less path (`a:`) let the trie's index-1 scan drop the leading char and\n // reach `:` here (a name-less marker) — normalization keeps that branch dead.\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,EAelC,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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e={decode:()=>null},t={decode:e=>{let t=e.length;if(t===0)return null;let n=+(e.codePointAt(0)===45);if(n===t||t-n>1&&e.codePointAt(n)===48&&e.codePointAt(n+1)!==46)return null;let r=!1;for(let i=n;i<t;i++){let a=e.codePointAt(i);if(!(a!==void 0&&a>=48&&a<=57)){if(a===46&&!r&&i!==n&&i!==t-1){r=!0;continue}return null}}let i=Number(e);return String(i)!==e||!Number.isSafeInteger(i)&&!r?null:i}},n={auto:t,none:e};exports.autoNumberStrategy=t,exports.noneNumberStrategy=e,exports.numberStrategies=n;
|
|
2
|
-
//# sourceMappingURL=number.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"number.js","names":[],"sources":["../../../../../src/engine/search-params/strategies/number.ts"],"sourcesContent":["/**\n * Number decoding strategies.\n *\n * @module search-params/strategies/number\n */\n\nimport type { NumberFormat } from \"../types\";\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Strategy for decoding number values.\n */\nexport interface NumberStrategy {\n decode: (decodedValue: string) => number | null;\n}\n\n// =============================================================================\n// Strategy Implementations\n// =============================================================================\n\n/**\n * No special number handling - values remain strings.\n */\nexport const noneNumberStrategy: NumberStrategy = {\n decode: () => null, // passthrough\n};\n\n/**\n * Auto-detect numeric values and parse as numbers.\n * Matches integers and decimals via charCode scan (faster than regex for short strings).\n *\n * Recognizes an optional leading `-` so negatives round-trip symmetrically with the\n * values `navigate()`/`build()` produce (`build({ n: -5 })` → `\"n=-5\"`). Leading-zero\n * and unsafe-integer rejection apply to the magnitude regardless of sign. Exponent\n * notation stays a string — `build` never emits a canonical safe exponent, and unsafe\n * exponents would lose precision. (#742)\n */\nexport const autoNumberStrategy: NumberStrategy = {\n decode: (value) => {\n const length = value.length;\n\n if (length === 0) {\n return null;\n }\n\n // Optional leading minus; the magnitude (digits) begins at `start`.\n const start = value.codePointAt(0) === 45 ? 1 : 0; // '-'\n\n // A bare \"-\" has no magnitude.\n if (start === length) {\n return null;\n }\n\n // Leading zeros are not canonical numbers (\"00\", \"007\", \"-007\") — preserve as strings.\n // Allow \"0\" and \"0.x\" (single zero or decimal starting with 0).\n if (\n length - start > 1 &&\n value.codePointAt(start) === 48 &&\n value.codePointAt(start + 1) !== 46\n ) {\n return null;\n }\n\n let hasDot = false;\n\n for (let i = start; i < length; i++) {\n const ch = value.codePointAt(i);\n\n if (ch !== undefined && ch >= 48 && ch <= 57) {\n continue; // '0'-'9'\n }\n\n if (ch === 46 && !hasDot && i !== start && i !== length - 1) {\n hasDot = true;\n\n continue;\n }\n\n return null; // non-digit, non-dot, or invalid dot position\n }\n\n const num = Number(value);\n\n // The stated criterion is round-trip stability, enforced directly rather than\n // through per-case guards: `build` prints a number with String(), so a value\n // may be coerced only when String() reproduces the exact text we parsed. This\n // subsumes the former negative-zero guard (String(-0) === \"0\" ≠ \"-0\", #898)\n // and closes the decimal family the safe-integer check below deliberately\n // exempts — trailing zeros (\"2.0\" → 2 → \"2\") and precision loss\n // (\"9007199254740993.5\" → 9007199254740994), both of which rebuilt a URL\n // different from the one that was matched. (#1565)\n if (String(num) !== value) {\n return null;\n }\n\n // Unsafe integers pass the text check (2**53 prints back exactly) but lose\n // precision in arithmetic, so they stay strings. Decimals are exempt: a\n // fractional value is never a \"safe integer\", and the check above already\n // proves its text round-trips.\n if (!Number.isSafeInteger(num) && !hasDot) {\n return null;\n }\n\n return num;\n },\n};\n\n// =============================================================================\n// Strategy Map\n// =============================================================================\n\n/**\n * Map of number format to strategy implementation.\n */\nexport const numberStrategies: Record<NumberFormat, NumberStrategy> = {\n auto: autoNumberStrategy,\n none: noneNumberStrategy,\n};\n"],"mappings":"AA0BA,MAAa,EAAqC,CAChD,WAAc,IAChB,EAYa,EAAqC,CAChD,OAAS,GAAU,CACjB,IAAM,EAAS,EAAM,OAErB,GAAI,IAAW,EACb,OAAO,KAIT,IAAM,EAAQ,IAAM,YAAY,CAAC,IAAM,IASvC,GANI,IAAU,GAOZ,EAAS,EAAQ,GACjB,EAAM,YAAY,CAAK,IAAM,IAC7B,EAAM,YAAY,EAAQ,CAAC,IAAM,GAEjC,OAAO,KAGT,IAAI,EAAS,GAEb,IAAK,IAAI,EAAI,EAAO,EAAI,EAAQ,IAAK,CACnC,IAAM,EAAK,EAAM,YAAY,CAAC,EAE1B,SAAO,IAAA,IAAa,GAAM,IAAM,GAAM,IAI1C,IAAI,IAAO,IAAM,CAAC,GAAU,IAAM,GAAS,IAAM,EAAS,EAAG,CAC3D,EAAS,GAET,QACF,CAEA,OAAO,IAFP,CAGF,CAEA,IAAM,EAAM,OAAO,CAAK,EAsBxB,OAZI,OAAO,CAAG,IAAM,GAQhB,CAAC,OAAO,cAAc,CAAG,GAAK,CAAC,EAC1B,KAGF,CACT,CACF,EASa,EAAyD,CACpE,KAAM,EACN,KAAM,CACR"}
|
|
@@ -1,2 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./routes.js"),t=/^[A-Z_a-z][\w-]*$/,n=/\S/,r=1e4;function i(e){if(e===null)return`null`;if(typeof e==`object`){try{let t=e.constructor;if(typeof t==`function`&&t.name!==`Object`)return t.name||`object`}catch{}return`object`}return typeof e}function a(e){for(let t of Object.keys(e)){let n=Object.getOwnPropertyDescriptor(e,t);if(n&&(n.get||n.set))return!0}return!1}function o(e,t){if(!e||typeof e!=`object`)throw TypeError(`[router.${t}] Route must be an object, got ${i(e)}`);let n=Object.getPrototypeOf(e);if(n!==Object.prototype&&n!==null)throw TypeError(`[router.${t}] Route must be a plain object, got ${i(e)}`);if(a(e))throw TypeError(`[router.${t}] Route must not have getters or setters`)}function s(e,t){if(e.encodeParams!==void 0&&typeof e.encodeParams!=`function`)throw TypeError(`[router.${t}] Route "${String(e.name)}" encodeParams must be a function`)}function c(e,t){if(e.decodeParams!==void 0&&typeof e.decodeParams!=`function`)throw TypeError(`[router.${t}] Route "${String(e.name)}" decodeParams must be a function`)}function l(e,a){if(typeof e.name!=`string`)throw TypeError(`[router.${a}] Route name must be a string, got ${i(e.name)}`);let o=e.name;if(o===``)throw TypeError(`[router.${a}] Route name cannot be empty`);if(!n.test(o))throw TypeError(`[router.${a}] Route name cannot contain only whitespace`);if(o.length>r)throw TypeError(`[router.${a}] Route name exceeds maximum length of ${r} characters`);if(!o.startsWith(`@@`)){if(o.includes(`.`))throw TypeError(`[router.${a}] Route name "${o}" cannot contain dots. Use children array or { parent } option in addRoute() instead.`);if(!t.test(o))throw TypeError(`[router.${a}] Invalid route name "${o}". Name must start with a letter or underscore, followed by letters, numbers, underscores, or hyphens.`)}}function u(e,t){if(!t.includes(`.`))return e.children.get(t);let n=e;for(let e of t.split(`.`))if(n=n.children.get(e),!n)return;return n}function d(e,t,n){if(u(e,t))throw Error(`[router.${n}] Route "${t}" already exists`)}function f(e,t,n){if(e.has(t))throw Error(`[router.${n}] Duplicate route "${t}" in batch`);e.add(t)}function p(e,t,n,r){let i=t===``?e:u(e,t);if(i){for(let e of i.children.values())if(e.path===n)throw Error(`[router.${r}] Path "${n}" is already defined`)}}function m(e,t,n,r){let i=e.get(t);if(i?.has(n))throw Error(`[router.${r}] Path "${n}" is already defined`);i?i.add(n):e.set(t,new Set([n]))}function h(t,n,r,a=``,u,g){o(t,n);let _=t;l(_,n),e.validateRoutePath(_.path,_.name,n,r),s(_,n),c(_,n);let v=_.name,y=a?`${a}.${v}`:v;r&&y&&d(r,y,n),u&&f(u,y,n);let b=_.path,x=a;if(r&&p(r,x,b,n),g&&m(g,x,b,n),_.children!==void 0){if(!Array.isArray(_.children))throw TypeError(`[router.${n}] Route "${v}" children must be an array, got ${i(_.children)}`);for(let e of _.children)h(e,n,r,y,u,g)}}exports.validateRoute=h;
|
|
2
|
-
//# sourceMappingURL=route-batch.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"route-batch.js","names":[],"sources":["../../../../src/engine/validation/route-batch.ts"],"sourcesContent":["/**\n * Batch route validation utilities.\n *\n * Provides validation for adding routes with cross-batch duplicate detection.\n * Used by router.addRoute() to ensure atomicity - all routes validated before any modification.\n */\n\nimport { validateRoutePath } from \"./routes\";\n\nimport type { RouteDefinition, RouteTree } from \"../types\";\n\n/**\n * Pattern for route name validation.\n * Each route name must start with letter/underscore, followed by alphanumeric/hyphen/underscore.\n * Dots are NOT allowed - use children array or { parent } option in addRoute() instead.\n * Rejects: names starting with numbers/hyphens, names containing dots.\n */\nconst ROUTE_NAME_PATTERN = /^[A-Z_a-z][\\w-]*$/;\n\n/**\n * Checks if string contains at least one non-whitespace character.\n */\nconst HAS_NON_WHITESPACE = /\\S/;\n\n/**\n * Maximum route name length to prevent DoS and performance issues.\n */\nconst MAX_ROUTE_NAME_LENGTH = 10_000;\n\n/**\n * Gets a human-readable description of a value's type.\n * Used for error messages to provide helpful debugging information.\n */\nfunction getTypeDescription(value: unknown): string {\n // Handle null explicitly (typeof null === \"object\")\n if (value === null) {\n return \"null\";\n }\n\n if (typeof value === \"object\") {\n // Read `constructor`/`.name` defensively: an adversarial own `constructor`\n // (null, a string, a number, …) is not a real constructor and must not crash\n // here nor yield a non-string (#903); a THROWING accessor — a `constructor`\n // getter, a function constructor with a throwing `.name` getter, or a Proxy\n // that throws on [[Get]] — must not crash either (#1052). Both fall back to\n // \"object\". (Byte-identical twin of type-guards' getTypeDescription —\n // route-tree has no type-guards dependency, so the hardening is duplicated.)\n try {\n const ctor: unknown = (value as { constructor?: unknown }).constructor;\n\n // Return constructor name for class instances\n if (typeof ctor === \"function\" && ctor.name !== \"Object\") {\n return ctor.name || \"object\"; // empty name (anonymous class) → \"object\"\n }\n } catch {\n // Throwing constructor/.name getter or Proxy [[Get]] → fall through (#1052).\n }\n\n // Plain object\n return \"object\";\n }\n\n // Primitive types (string, number, boolean, undefined, etc.)\n return typeof value;\n}\n\n/**\n * Checks if an object has getters or setters.\n * Used to detect potentially malicious route objects that could mutate during processing.\n *\n * @param obj - Object to check\n * @returns true if object has getters or setters\n */\nfunction hasGettersOrSetters(obj: Record<string, unknown>): boolean {\n for (const key of Object.keys(obj)) {\n const descriptor = Object.getOwnPropertyDescriptor(obj, key);\n\n if (descriptor && (descriptor.get || descriptor.set)) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Validates that route is a plain object without getters/setters.\n * Prevents malicious objects that could mutate during processing.\n *\n * @param route - Route to validate\n * @param methodName - Calling method for error context\n * @throws {TypeError} If route is not a plain object\n */\nfunction validateRouteType(\n route: unknown,\n methodName: string,\n): asserts route is Record<string, unknown> {\n if (!route || typeof route !== \"object\") {\n throw new TypeError(\n `[router.${methodName}] Route must be an object, got ${getTypeDescription(route)}`,\n );\n }\n\n // Check for plain object (prototype must be Object.prototype or null)\n const proto: object | null = Object.getPrototypeOf(route) as object | null;\n\n if (proto !== Object.prototype && proto !== null) {\n throw new TypeError(\n `[router.${methodName}] Route must be a plain object, got ${getTypeDescription(route)}`,\n );\n }\n\n // Check for getters/setters (could cause mutations during processing)\n if (hasGettersOrSetters(route as Record<string, unknown>)) {\n throw new TypeError(\n `[router.${methodName}] Route must not have getters or setters`,\n );\n }\n}\n\n/**\n * Validates that encodeParams is a function if provided.\n *\n * @param route - Route configuration\n * @param methodName - Calling method for error context\n * @throws {TypeError} If encodeParams is not a function\n */\nfunction validateEncodeParams(\n route: Record<string, unknown>,\n methodName: string,\n): void {\n if (\n route.encodeParams !== undefined &&\n typeof route.encodeParams !== \"function\"\n ) {\n throw new TypeError(\n `[router.${methodName}] Route \"${String(route.name)}\" encodeParams must be a function`,\n );\n }\n}\n\n/**\n * Validates that decodeParams is a function if provided.\n *\n * @param route - Route configuration\n * @param methodName - Calling method for error context\n * @throws {TypeError} If decodeParams is not a function\n */\nfunction validateDecodeParams(\n route: Record<string, unknown>,\n methodName: string,\n): void {\n if (\n route.decodeParams !== undefined &&\n typeof route.decodeParams !== \"function\"\n ) {\n throw new TypeError(\n `[router.${methodName}] Route \"${String(route.name)}\" decodeParams must be a function`,\n );\n }\n}\n\n/**\n * Validates that route name is a valid string matching the expected format.\n *\n * Route name rules:\n * - Must be a string\n * - Cannot be empty or whitespace-only\n * - Cannot contain dots (use children array or { parent } option instead)\n * - Must match [a-zA-Z_][a-zA-Z0-9_-]*\n *\n * @param route - Route configuration\n * @param methodName - Calling method for error context\n * @throws {TypeError} If name is missing, not a string, or invalid format\n */\nfunction validateRouteName(\n route: Record<string, unknown>,\n methodName: string,\n): asserts route is Record<string, unknown> & { name: string } {\n if (typeof route.name !== \"string\") {\n throw new TypeError(\n `[router.${methodName}] Route name must be a string, got ${getTypeDescription(route.name)}`,\n );\n }\n\n const name = route.name;\n\n // Empty string is not allowed for addRoute (unlike root node in type-guards)\n if (name === \"\") {\n throw new TypeError(`[router.${methodName}] Route name cannot be empty`);\n }\n\n // Whitespace-only strings are invalid\n if (!HAS_NON_WHITESPACE.test(name)) {\n throw new TypeError(\n `[router.${methodName}] Route name cannot contain only whitespace`,\n );\n }\n\n // Length check for technical safety\n if (name.length > MAX_ROUTE_NAME_LENGTH) {\n throw new TypeError(\n `[router.${methodName}] Route name exceeds maximum length of ${MAX_ROUTE_NAME_LENGTH} characters`,\n );\n }\n\n // System routes bypass pattern validation (e.g., @@router/UNKNOWN_ROUTE)\n if (name.startsWith(\"@@\")) {\n return;\n }\n\n // Dots are not allowed in route names\n if (name.includes(\".\")) {\n throw new TypeError(\n `[router.${methodName}] Route name \"${name}\" cannot contain dots. ` +\n `Use children array or { parent } option in addRoute() instead.`,\n );\n }\n\n // Validate route pattern (ASCII only: letters, numbers, underscores, hyphens)\n if (!ROUTE_NAME_PATTERN.test(name)) {\n throw new TypeError(\n `[router.${methodName}] Invalid route name \"${name}\". ` +\n `Name must start with a letter or underscore, ` +\n `followed by letters, numbers, underscores, or hyphens.`,\n );\n }\n}\n\n/**\n * Finds a node by its fullName in the tree.\n * Even though user-provided route names cannot contain dots,\n * fullName is computed during validation (e.g., \"parent.child\").\n *\n * @param rootNode - Root node to start from\n * @param fullName - Full route name (e.g., \"users.profile\")\n * @returns The resolved node, or undefined if not found\n */\nfunction findNodeByFullName(\n rootNode: RouteTree,\n fullName: string,\n): RouteTree | undefined {\n // Fast path: single-segment names don't need splitting\n // Stryker disable next-line ConditionalExpression,StringLiteral,BlockStatement: equivalent — the fast path is a pure optimization; for a dotless name the general path below yields the identical result (`name.split(\".\")` → `[name]`, a one-iteration `children.get(name)`). (BooleanLiteral stays live: dropping the `!` runs the fast path for a dotted name → `children.get(\"a.b\")` is undefined = killed.)\n if (!fullName.includes(\".\")) {\n return rootNode.children.get(fullName);\n }\n\n let current: RouteTree | undefined = rootNode;\n\n for (const segment of fullName.split(\".\")) {\n current = current.children.get(segment);\n\n if (!current) {\n return undefined;\n }\n }\n\n return current;\n}\n\n/**\n * Checks for duplicate route name in existing tree.\n *\n * @param rootNode - Root node to search in\n * @param fullName - Full route name (dot-notation)\n * @param methodName - Calling method for error context\n * @throws {Error} If route name already exists\n */\nfunction checkTreeNameDuplicate(\n rootNode: RouteTree,\n fullName: string,\n methodName: string,\n): void {\n if (findNodeByFullName(rootNode, fullName)) {\n throw new Error(\n `[router.${methodName}] Route \"${fullName}\" already exists`,\n );\n }\n}\n\n/**\n * Checks for duplicate route name in current batch.\n *\n * @param seenNames - Set of names already seen in batch\n * @param fullName - Full route name to check\n * @param methodName - Calling method for error context\n * @throws {Error} If duplicate name in batch\n */\nfunction checkBatchNameDuplicate(\n seenNames: Set<string>,\n fullName: string,\n methodName: string,\n): void {\n if (seenNames.has(fullName)) {\n throw new Error(\n `[router.${methodName}] Duplicate route \"${fullName}\" in batch`,\n );\n }\n\n seenNames.add(fullName);\n}\n\n/**\n * Checks for duplicate path in existing tree at same parent level.\n *\n * @param rootNode - Root node to search in\n * @param parentName - Parent route name (empty string for root level)\n * @param routePath - Path to check for duplicates\n * @throws {Error} If path already exists at this level\n */\nfunction checkTreePathDuplicate(\n rootNode: RouteTree,\n parentName: string,\n routePath: string,\n methodName: string,\n): void {\n const parentNode =\n parentName === \"\" ? rootNode : findNodeByFullName(rootNode, parentName);\n\n if (!parentNode) {\n return; // Parent doesn't exist, so no duplicate\n }\n\n for (const child of parentNode.children.values()) {\n if (child.path === routePath) {\n throw new Error(\n `[router.${methodName}] Path \"${routePath}\" is already defined`,\n );\n }\n }\n}\n\n/**\n * Checks for duplicate path in current batch at same parent level.\n *\n * @param seenPathsByParent - Map of paths by parent name\n * @param parentName - Parent route name (empty string for root level)\n * @param routePath - Path to check for duplicates\n * @throws {Error} If path already exists at this level in batch\n */\nfunction checkBatchPathDuplicate(\n seenPathsByParent: Map<string, Set<string>>,\n parentName: string,\n routePath: string,\n methodName: string,\n): void {\n const pathsAtLevel = seenPathsByParent.get(parentName);\n\n if (pathsAtLevel?.has(routePath)) {\n throw new Error(\n `[router.${methodName}] Path \"${routePath}\" is already defined`,\n );\n }\n\n if (pathsAtLevel) {\n pathsAtLevel.add(routePath);\n } else {\n seenPathsByParent.set(parentName, new Set([routePath]));\n }\n}\n\n/**\n * Validates route structure for add operations.\n *\n * Performs comprehensive validation including:\n * - Type check (must be object)\n * - Name validation\n * - Path validation\n * - Duplicate name detection (in tree and batch)\n * - Duplicate path detection (in tree and batch)\n * - Recursive children validation\n *\n * @param route - Route to validate\n * @param methodName - Calling method for error context\n * @param rootNode - Optional root node for duplicate checking\n * @param parentName - Parent route name for building full path (used in recursion)\n * @param seenNames - Set of names already seen in this batch\n * @param seenPathsByParent - Map of paths by parent for path duplicate detection\n * @throws {TypeError} If route structure is invalid\n * @throws {Error} If route already exists (duplicate)\n * @throws {Error} If path already defined (duplicate)\n *\n * @example\n * ```typescript\n * const seenNames = new Set<string>();\n * const seenPaths = new Map<string, Set<string>>();\n *\n * // Validate routes before adding\n * for (const route of routes) {\n * validateRoute(route, \"add\", rootNode, \"\", seenNames, seenPaths);\n * }\n * ```\n */\nexport function validateRoute(\n route: unknown,\n methodName: string,\n rootNode?: RouteTree,\n parentName = \"\",\n seenNames?: Set<string>,\n seenPathsByParent?: Map<string, Set<string>>,\n): asserts route is RouteDefinition {\n validateRouteType(route, methodName);\n\n const routeDef = route;\n\n // Validate that name is a non-empty string\n validateRouteName(routeDef, methodName);\n\n // Validate path structure\n validateRoutePath(routeDef.path, routeDef.name, methodName, rootNode);\n\n // Validate optional function properties\n validateEncodeParams(routeDef, methodName);\n validateDecodeParams(routeDef, methodName);\n\n const routeName = routeDef.name;\n const fullName = parentName ? `${parentName}.${routeName}` : routeName;\n\n // Check for duplicate name in existing tree\n if (rootNode && fullName) {\n checkTreeNameDuplicate(rootNode, fullName, methodName);\n }\n\n // Check for duplicate name in current batch\n if (seenNames) {\n checkBatchNameDuplicate(seenNames, fullName, methodName);\n }\n\n const routePath = routeDef.path;\n const pathCheckParent = parentName;\n\n // Check for duplicate path in existing tree\n if (rootNode) {\n checkTreePathDuplicate(rootNode, pathCheckParent, routePath, methodName);\n }\n\n // Check for duplicate path in current batch\n if (seenPathsByParent) {\n checkBatchPathDuplicate(\n seenPathsByParent,\n pathCheckParent,\n routePath,\n methodName,\n );\n }\n\n // Validate children recursively\n if (routeDef.children !== undefined) {\n if (!Array.isArray(routeDef.children)) {\n throw new TypeError(\n `[router.${methodName}] Route \"${routeName}\" children must be an array, got ${getTypeDescription(routeDef.children)}`,\n );\n }\n\n for (const child of routeDef.children) {\n validateRoute(\n child,\n methodName,\n rootNode,\n fullName,\n seenNames,\n seenPathsByParent,\n );\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../path-matcher/parseSegment.js"),t=require("../path-matcher/buildParamMeta.js");function n(e,t){return TypeError(`[router.${e}] ${t}`)}function r(e,t,r,i){let a=new Set;for(let o of e){if(a.has(o))throw n(r,`Invalid path for route "${t}": duplicate parameter name ':${o}' in "${i}" (a param name must be unique within a route — the second binding would overwrite the first)`);a.add(o)}}function i(e,r,i,a){for(let o of e)if(t.INVALID_QUERY_NAME_RGX.test(o))throw n(i,`Invalid path for route "${r}": invalid query-param name "${o}" in "${a}" (a query-param name cannot contain '<' or '>' — it would never round-trip; rename the query param)`)}function a(e){let t=!0,n=!1;for(let r of e){if(r===`/`){t=!0;continue}if(t&&=(n=r===`:`||r===`*`,!1),!n&&r.codePointAt(0)>=128)return!0}return!1}function o(e,t,n){switch(e){case`name-less`:case`trailing-marker`:return`Invalid path for route "${t}": parameter marker (':' or '*') without a name in "${n}"`;case`fused-marker`:return`Invalid path for route "${t}": parameter marker (':' or '*') must begin a segment, but "${n}" fuses one to a static prefix (use a boundary marker like "/a/:b")`}}function s(e,t){return e.code===`optional-removed`?`Invalid path for route "${t}": optional params are not supported — "${e.segment}". Declare two sibling routes instead: "${e.withoutSegment}" and "${e.requiredForm}" (the route hierarchy already expresses optionality)`:`Invalid path for route "${t}": regex constraints are not supported — '<' and '>' are reserved in path segments ("${e.segment}"). Match the segment as a plain string and validate the value in a guard (canActivate) or app code`}function c(c,l,u,d){if(typeof c!=`string`){let e;throw e=c===null?`null`:Array.isArray(c)?`array`:typeof c,n(u,`Route path must be a string, got ${e}`)}if(c===``)return;if(/\s/.test(c))throw n(u,`Invalid path for route "${l}": whitespace not allowed in "${c}"`);if(!/^([/?~]|[^/]+$)/.test(c))throw n(u,`Route "${l}" has invalid path format: "${c}". Path should start with '/', '~', '?' or be a relative segment.`);if(c.includes(`//`))throw n(u,`Invalid path for route "${l}": double slashes not allowed in "${c}"`);let{pathPattern:f,urlParams:p,queryParams:m}=t.buildParamMeta(c);r(p,l,u,c),i(m,l,u,c);let h=e.describeRemovedForm(f);if(h!==void 0)throw n(u,s(h,l));let g=e.findSegmentGrammarError(f);if(g!==void 0)throw n(u,o(g,l,c));if(a(f))throw n(u,`Invalid path for route "${l}": non-ASCII static segment in "${c}" — match compares static segments raw and rejects non-ASCII input, so this route would never match. Percent-encode it (e.g. '/caf%C3%A9') or use a param`);let _=d&&d.paramMeta.urlParams.length>0;if(c.startsWith(`~`)&&_)throw n(u,`Absolute path "${c}" cannot be used under parent route with URL parameters`)}exports.validateRoutePath=c;
|
|
2
|
-
//# sourceMappingURL=routes.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","names":["INVALID_QUERY_NAME_RGX","buildParamMeta","describeRemovedForm","findSegmentGrammarError"],"sources":["../../../../src/engine/validation/routes.ts"],"sourcesContent":["import {\n buildParamMeta,\n describeRemovedForm,\n findSegmentGrammarError,\n INVALID_QUERY_NAME_RGX,\n} from \"../path-matcher\";\n\nimport type { RemovedForm, SegmentErrorCode } from \"../path-matcher\";\nimport type { RouteTree } from \"../types\";\n\n/**\n * Creates a TypeError with consistent router error message format.\n *\n * @param methodName - Name of the method that triggered the error\n * @param message - Error message\n * @returns TypeError with formatted message\n * @internal\n */\nfunction createRouterError(methodName: string, message: string): TypeError {\n return new TypeError(`[router.${methodName}] ${message}`);\n}\n\n/**\n * Rejects a param name repeated within one route's own path (`/:id/:id`, a\n * param+splat clash `/:x/*x`, #1151). `buildParamMeta.urlParams` lists every\n * path-binding name — params AND splats — in order, keeping duplicates (`/:x/*x`\n * → `[\"x\", \"x\"]`), so a single pass over it catches both. The trie binds the\n * duplicates at different positions under one name, so match's later capture\n * silently overwrites the earlier and `rewritePathOnMatch` then rewrites the\n * user's URL from the single survivor. The #736 conflict guard only fires on\n * DIFFERENTLY-named params at one position, so this same-name case slips through.\n * path-matcher's `registerTree` backstop additionally catches CROSS-level dups (a\n * parent's param reused by a child), which this per-path gate cannot see.\n * Extracted so `validateRoutePath` stays within the cognitive-complexity budget.\n */\nfunction validateUniqueParamNames(\n urlParams: readonly string[],\n routeName: string,\n methodName: string,\n path: string,\n): void {\n const seen = new Set<string>();\n\n for (const name of urlParams) {\n if (seen.has(name)) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": duplicate parameter name ':${name}' in \"${path}\" (a param name must be unique within a route — the second binding would overwrite the first)`,\n );\n }\n\n seen.add(name);\n }\n}\n\n/**\n * #1242 §5.1/§5.3: rejects a malformed query-param declaration — a query name\n * carrying `<`/`>` (`/a?fil<ter` — a `<` in a plain query tail; never round-trips),\n * or one that collides with a path-param name (`/a/:tab?tab`, where buildPath emits\n * the value twice). Narrow to `<>`: a `=` in the declaration (`?tab=1`, §5.2) is\n * tolerated today and left as a separate call. (Under M1 a reverse-order typo\n * `/a/:b?<c>` is caught earlier as optional-removed — the `?<` keeps it in the\n * path, §3.3.) path-matcher's `registerTree` backstops both; this gate adds the\n * route-contextual message.\n */\nfunction validateQueryParamDeclarations(\n queryParams: readonly string[],\n routeName: string,\n methodName: string,\n path: string,\n): void {\n // A path/query name collision (`/a/:tab?tab`) is legal under M2: `tab` lives\n // in both `state.params` and `state.search` as separate channels (RFC-4 M2 /\n // #1548), so the former \"declared as both\" rejection is gone. Only a query\n // name that can never round-trip (contains `<`/`>`) is still rejected.\n for (const name of queryParams) {\n if (INVALID_QUERY_NAME_RGX.test(name)) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": invalid query-param name \"${name}\" in \"${path}\" (a query-param name cannot contain '<' or '>' — it would never round-trip; rename the query param)`,\n );\n }\n }\n}\n\n/**\n * Reports whether a path has a raw non-ASCII code point (≥ U+0080) in a STATIC\n * segment (`/café`, `/меню`, #1154). match rejects any non-ASCII input byte\n * (`#scanPath`) and compares static trie keys raw, so such a route registers but\n * is unmatchable — `buildPath` emits `/café`, which its own `match` rejects. Only\n * static text is flagged: a marker-led segment (`:café`, a non-ASCII param NAME)\n * is skipped. A `for…of` code-point scan tracking segment start. Runs AFTER the\n * removed-form / grammar rejections, so no `<`/`>` (a former constraint) can\n * reach it — the 3-token grammar has no constraint body to skip (M1, #1516).\n */\nfunction hasNonAsciiStatic(path: string): boolean {\n let atSegmentStart = true;\n let segmentIsMarker = false;\n\n for (const char of path) {\n if (char === \"/\") {\n atSegmentStart = true;\n\n continue;\n }\n\n if (atSegmentStart) {\n segmentIsMarker = char === \":\" || char === \"*\";\n atSegmentStart = false;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- non-empty code point from for-of\n if (!segmentIsMarker && char.codePointAt(0)! >= 0x80) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Maps a SURVIVING per-segment grammar error code (from `findSegmentGrammarError`)\n * to the gate's route-contextual message. The removed-form codes (`optional-removed`\n * / `constraint-removed`) are NOT handled here — they carry a richer replacement\n * recipe built by `removedFormMessage` from `describeRemovedForm`.\n */\nfunction gateGrammarMessage(\n code: Exclude<SegmentErrorCode, \"optional-removed\" | \"constraint-removed\">,\n routeName: string,\n path: string,\n): string {\n switch (code) {\n case \"name-less\":\n case \"trailing-marker\": {\n return `Invalid path for route \"${routeName}\": parameter marker (':' or '*') without a name in \"${path}\"`;\n }\n case \"fused-marker\": {\n return `Invalid path for route \"${routeName}\": parameter marker (':' or '*') must begin a segment, but \"${path}\" fuses one to a static prefix (use a boundary marker like \"/a/:b\")`;\n }\n }\n}\n\n/**\n * Builds the route-contextual replacement recipe for a removed form (M1) — the\n * RICH tier (the matcher backstop uses a shorter, path-free recipe). For an\n * optional it names the offending segment and the two concrete sibling paths that\n * replace it (computed from the actual path by `describeRemovedForm`); for a\n * constraint it names the offending segment and points to a guard.\n */\nfunction removedFormMessage(removed: RemovedForm, routeName: string): string {\n if (removed.code === \"optional-removed\") {\n return `Invalid path for route \"${routeName}\": optional params are not supported — \"${removed.segment}\". Declare two sibling routes instead: \"${removed.withoutSegment}\" and \"${removed.requiredForm}\" (the route hierarchy already expresses optionality)`;\n }\n\n return `Invalid path for route \"${routeName}\": regex constraints are not supported — '<' and '>' are reserved in path segments (\"${removed.segment}\"). Match the segment as a plain string and validate the value in a guard (canActivate) or app code`;\n}\n\n/**\n * Validates route path format.\n * Throws a descriptive error if validation fails.\n *\n * Allows:\n * - Empty string (for grouping/root routes)\n * - Absolute paths: /path, ~path\n * - Query strings: ?query\n * - Relative segments: segment\n *\n * Prevents:\n * - Non-string values\n * - Double slashes (//)\n * - Absolute paths (~) under parameterized parent nodes\n *\n * @param path - Route path to validate\n * @param routeName - Name of route (for error messages)\n * @param methodName - Name of calling method for error messages\n * @param parentNode - Optional parent node for context validation\n * @throws {TypeError} If path is invalid\n *\n * @example\n * // Valid paths\n * validateRoutePath(\"\", \"home\", \"add\"); // ok (empty for grouping)\n * validateRoutePath(\"/users\", \"users\", \"add\"); // ok (absolute)\n * validateRoutePath(\"~dashboard\", \"dash\", \"add\"); // ok (absolute, if no parameterized parent)\n * validateRoutePath(\"?tab=1\", \"home\", \"add\"); // ok (query)\n * validateRoutePath(\"profile\", \"users.profile\", \"add\"); // ok (relative)\n *\n * @example\n * // Invalid paths (throws)\n * validateRoutePath(\"/users//list\", \"users.list\", \"add\"); // throws (double slash)\n * validateRoutePath(\"~dash\", \"dash\", \"add\", paramParent); // throws (~ under parameterized parent)\n */\n// A format-validation gate: a flat sequence of INDEPENDENT guard clauses (type,\n// whitespace, format, double-slash, dup-param, query-decl, the M1 removed-form\n// recipe, surviving grammar markers (name-less / fused / trailing), non-ASCII\n// static, absolute-under-param). Each is a simple early throw; extracting them\n// would only scatter one checklist.\n\nexport function validateRoutePath(\n path: unknown,\n routeName: string,\n methodName: string,\n parentNode?: RouteTree,\n): asserts path is string {\n // Type check\n if (typeof path !== \"string\") {\n // Get type description for error message\n let typeDesc: string;\n\n if (path === null) {\n typeDesc = \"null\";\n } else if (Array.isArray(path)) {\n typeDesc = \"array\";\n } else {\n typeDesc = typeof path;\n }\n\n throw createRouterError(\n methodName,\n `Route path must be a string, got ${typeDesc}`,\n );\n }\n\n // Empty path is valid (for grouping/root)\n if (path === \"\") {\n return;\n }\n\n // No whitespace characters (spaces, tabs, newlines)\n if (/\\s/.test(path)) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": whitespace not allowed in \"${path}\"`,\n );\n }\n\n // Valid path pattern\n if (!/^([/?~]|[^/]+$)/.test(path)) {\n throw createRouterError(\n methodName,\n `Route \"${routeName}\" has invalid path format: \"${path}\". Path should start with '/', '~', '?' or be a relative segment.`,\n );\n }\n\n // No double slashes\n if (path.includes(\"//\")) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": double slashes not allowed in \"${path}\"`,\n );\n }\n\n // The grammar checks below scan only the URL-path portion: `buildParamMeta`\n // strips the query the same way the trie does, so a `:`/`*` inside a query\n // declaration is not falsely flagged. (`buildParamMeta` is total — the 3-token\n // grammar has no constraint body to compile, so it never throws — M1, #1516.)\n const { pathPattern, urlParams, queryParams } = buildParamMeta(path);\n\n // Duplicate param name within this route's own path (`/:id/:id`, `/:x/*x`, #1151).\n validateUniqueParamNames(urlParams, routeName, methodName, path);\n\n // Malformed query-param declarations (#1242 §5.1): a query name with `<>`\n // (never round-trips). Name collisions with a path param are legal under M2\n // (separate params/search channels, #1548).\n validateQueryParamDeclarations(queryParams, routeName, methodName, path);\n\n // Removed-form (M1) rejection first — a `:x?` optional or a `<re>` constraint —\n // with the RICH route-contextual replacement recipe (the offending segment plus,\n // for an optional, the two computed sibling paths). Returns undefined when the\n // path's first grammar error is a SURVIVING code, so the fall-through below runs.\n const removed = describeRemovedForm(pathPattern);\n\n if (removed !== undefined) {\n throw createRouterError(methodName, removedFormMessage(removed, routeName));\n }\n\n // Surviving per-segment grammar rejections via the canonical `parseSegment`\n // tokenizer: name-less (#858/#863), fused-marker (#1050), trailing-marker\n // (#1324). `findSegmentGrammarError` runs the same split+parse the matcher uses,\n // so the gate cannot drift. (Only removed-form codes reach `removed` above; the\n // first error here is therefore always a surviving code.)\n const grammarError = findSegmentGrammarError(pathPattern);\n\n if (grammarError !== undefined) {\n throw createRouterError(\n methodName,\n gateGrammarMessage(\n grammarError as Exclude<\n SegmentErrorCode,\n \"optional-removed\" | \"constraint-removed\"\n >,\n routeName,\n path,\n ),\n );\n }\n\n // Raw non-ASCII in a STATIC segment (`/café`, `/меню`, #1154). match rejects\n // non-ASCII input and compares static keys raw, so the route registers but never\n // matches. Reject with the percent-encode workaround (path-matcher backstops at\n // `registerTree`); a non-ASCII param NAME or constraint body is unaffected.\n if (hasNonAsciiStatic(pathPattern)) {\n throw createRouterError(\n methodName,\n `Invalid path for route \"${routeName}\": non-ASCII static segment in \"${path}\" — match compares static segments raw and rejects non-ASCII input, so this route would never match. Percent-encode it (e.g. '/caf%C3%A9') or use a param`,\n );\n }\n\n // Absolute paths under parameterized parents\n // Check if parent has URL parameters via paramTypeMap\n const hasUrlParams = parentNode && parentNode.paramMeta.urlParams.length > 0;\n\n if (path.startsWith(\"~\") && hasUrlParams) {\n throw createRouterError(\n methodName,\n `Absolute path \"${path}\" cannot be used under parent route with URL parameters`,\n );\n }\n}\n"],"mappings":"kGAkBA,SAAS,EAAkB,EAAoB,EAA4B,CACzE,OAAW,UAAU,WAAW,EAAW,IAAI,GAAS,CAC1D,CAeA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,IAAM,EAAO,IAAI,IAEjB,IAAK,IAAM,KAAQ,EAAW,CAC5B,GAAI,EAAK,IAAI,CAAI,EACf,MAAM,EACJ,EACA,2BAA2B,EAAU,gCAAgC,EAAK,QAAQ,EAAK,8FACzF,EAGF,EAAK,IAAI,CAAI,CACf,CACF,CAYA,SAAS,EACP,EACA,EACA,EACA,EACM,CAKN,IAAK,IAAM,KAAQ,EACjB,GAAIA,EAAAA,uBAAuB,KAAK,CAAI,EAClC,MAAM,EACJ,EACA,2BAA2B,EAAU,+BAA+B,EAAK,QAAQ,EAAK,qGACxF,CAGN,CAYA,SAAS,EAAkB,EAAuB,CAChD,IAAI,EAAiB,GACjB,EAAkB,GAEtB,IAAK,IAAM,KAAQ,EAAM,CACvB,GAAI,IAAS,IAAK,CAChB,EAAiB,GAEjB,QACF,CAQA,GANA,AAEE,KADA,EAAkB,IAAS,KAAO,IAAS,IAC1B,IAIf,CAAC,GAAmB,EAAK,YAAY,CAAC,GAAM,IAC9C,MAAO,EAEX,CAEA,MAAO,EACT,CAQA,SAAS,EACP,EACA,EACA,EACQ,CACR,OAAQ,EAAR,CACE,IAAK,YACL,IAAK,kBACH,MAAO,2BAA2B,EAAU,sDAAsD,EAAK,GAEzG,IAAK,eACH,MAAO,2BAA2B,EAAU,8DAA8D,EAAK,oEAEnH,CACF,CASA,SAAS,EAAmB,EAAsB,EAA2B,CAK3E,OAJI,EAAQ,OAAS,mBACZ,2BAA2B,EAAU,0CAA0C,EAAQ,QAAQ,0CAA0C,EAAQ,eAAe,SAAS,EAAQ,aAAa,uDAGhM,2BAA2B,EAAU,uFAAuF,EAAQ,QAAQ,oGACrJ,CA0CA,SAAgB,EACd,EACA,EACA,EACA,EACwB,CAExB,GAAI,OAAO,GAAS,SAAU,CAE5B,IAAI,EAUJ,KARA,CAKE,EALE,IAAS,KACA,OACF,MAAM,QAAQ,CAAI,EAChB,QAEA,OAAO,EAGd,EACJ,EACA,oCAAoC,GACtC,CACF,CAGA,GAAI,IAAS,GACX,OAIF,GAAI,KAAK,KAAK,CAAI,EAChB,MAAM,EACJ,EACA,2BAA2B,EAAU,gCAAgC,EAAK,EAC5E,EAIF,GAAI,CAAC,kBAAkB,KAAK,CAAI,EAC9B,MAAM,EACJ,EACA,UAAU,EAAU,8BAA8B,EAAK,kEACzD,EAIF,GAAI,EAAK,SAAS,IAAI,EACpB,MAAM,EACJ,EACA,2BAA2B,EAAU,oCAAoC,EAAK,EAChF,EAOF,GAAM,CAAE,cAAa,YAAW,eAAgBC,EAAAA,eAAe,CAAI,EAGnE,EAAyB,EAAW,EAAW,EAAY,CAAI,EAK/D,EAA+B,EAAa,EAAW,EAAY,CAAI,EAMvE,IAAM,EAAUC,EAAAA,oBAAoB,CAAW,EAE/C,GAAI,IAAY,IAAA,GACd,MAAM,EAAkB,EAAY,EAAmB,EAAS,CAAS,CAAC,EAQ5E,IAAM,EAAeC,EAAAA,wBAAwB,CAAW,EAExD,GAAI,IAAiB,IAAA,GACnB,MAAM,EACJ,EACA,EACE,EAIA,EACA,CACF,CACF,EAOF,GAAI,EAAkB,CAAW,EAC/B,MAAM,EACJ,EACA,2BAA2B,EAAU,kCAAkC,EAAK,0JAC9E,EAKF,IAAM,EAAe,GAAc,EAAW,UAAU,UAAU,OAAS,EAE3E,GAAI,EAAK,WAAW,GAAG,GAAK,EAC1B,MAAM,EACJ,EACA,kBAAkB,EAAK,wDACzB,CAEJ"}
|
package/dist/cjs/getNavigator.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=new WeakMap,t=t=>{let n=e.get(t);return n||(n=Object.freeze({navigate:t.navigate,getState:t.getState,isActiveRoute:t.isActiveRoute,canNavigateTo:t.canNavigateTo,subscribe:t.subscribe,subscribeLeave:t.subscribeLeave,isLeaveApproved:t.isLeaveApproved}),e.set(t,n)),n};exports.getNavigator=t;
|
|
2
|
-
//# sourceMappingURL=getNavigator.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getNavigator.js","names":[],"sources":["../../src/getNavigator.ts"],"sourcesContent":["import type { Navigator, DefaultDependencies, Router } from \"./types\";\n\nconst cache = new WeakMap<Router, Navigator>();\n\nexport const getNavigator = <\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n router: Router<Dependencies>,\n): Navigator => {\n let nav = cache.get(router);\n\n if (!nav) {\n nav = Object.freeze({\n navigate: router.navigate,\n getState: router.getState,\n isActiveRoute: router.isActiveRoute,\n canNavigateTo: router.canNavigateTo,\n subscribe: router.subscribe,\n subscribeLeave: router.subscribeLeave,\n isLeaveApproved: router.isLeaveApproved,\n } as Navigator);\n cache.set(router, nav);\n }\n\n return nav;\n};\n"],"mappings":"AAEA,MAAM,EAAQ,IAAI,QAEL,EAGX,GACc,CACd,IAAI,EAAM,EAAM,IAAI,CAAM,EAe1B,OAbK,IACH,EAAM,OAAO,OAAO,CAClB,SAAU,EAAO,SACjB,SAAU,EAAO,SACjB,cAAe,EAAO,cACtB,cAAe,EAAO,cACtB,UAAW,EAAO,UAClB,eAAgB,EAAO,eACvB,gBAAiB,EAAO,eAC1B,CAAc,EACd,EAAM,IAAI,EAAQ,CAAG,GAGhB,CACT"}
|
package/dist/cjs/guards.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
function e(e){if(!e||typeof e!=`object`||e.constructor!==Object)throw TypeError(`dependencies must be a plain object`);for(let t in e)if(Object.getOwnPropertyDescriptor(e,t)?.get)throw TypeError(`dependencies cannot contain getters: "${t}"`)}function t(e,n){for(let r of e){let e=r;if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`route must be a non-array object`);n?.routes.guardRouteCallbacks(r),n?.routes.guardNoAsyncCallbacks(r);let i=r.children;i&&t(i,n)}}const n=new Set([`all`,`warn-error`,`error-only`,`none`]);function r(e){return typeof e==`string`&&n.has(e)}function i(e){return typeof e==`string`?`"${e}"`:typeof e==`object`?JSON.stringify(e):String(e)}function a(e){if(typeof e!=`object`)throw TypeError(`Logger config must be an object`);let t=e;for(let e of Object.keys(t))if(e!==`level`&&e!==`callback`&&e!==`callbackIgnoresLevel`)throw TypeError(`Unknown logger config property: "${e}"`);if(`level`in t&&t.level!==void 0&&!r(t.level))throw TypeError(`Invalid logger level: ${i(t.level)}. Expected: "all" | "warn-error" | "error-only" | "none"`);if(`callback`in t&&t.callback!==void 0&&typeof t.callback!=`function`)throw TypeError(`Logger callback must be a function, got ${typeof t.callback}`);if(`callbackIgnoresLevel`in t&&t.callbackIgnoresLevel!==void 0&&typeof t.callbackIgnoresLevel!=`boolean`)throw TypeError(`Logger callbackIgnoresLevel must be a boolean, got ${typeof t.callbackIgnoresLevel}`)}exports.assertLoggerConfig=a,exports.guardDependencies=e,exports.guardRouteStructure=t;
|
|
2
|
-
//# sourceMappingURL=guards.js.map
|
package/dist/cjs/guards.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"guards.js","names":[],"sources":["../../src/guards.ts"],"sourcesContent":["// packages/core/src/guards.ts\n\nimport type { LoggerConfig, LogLevelConfig, Route } from \"./types\";\nimport type { RouterValidator } from \"./types/RouterValidator\";\n\n// ============================================================================\n// Structural invariant guards (dependencies + route-tree shape)\n// ============================================================================\n\nexport function guardDependencies(deps: unknown): void {\n if (\n !deps ||\n typeof deps !== \"object\" ||\n (deps as { constructor: unknown }).constructor !== Object\n ) {\n throw new TypeError(\"dependencies must be a plain object\");\n }\n for (const key in deps as Record<string, unknown>) {\n if (Object.getOwnPropertyDescriptor(deps, key)?.get) {\n throw new TypeError(`dependencies cannot contain getters: \"${key}\"`);\n }\n }\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- accepts any Route type */\nexport function guardRouteStructure(\n routes: Route<any>[],\n validator?: RouterValidator | null,\n): void {\n /* eslint-enable @typescript-eslint/no-explicit-any */\n for (const route of routes) {\n const routeValue: unknown = route;\n\n if (\n routeValue === null ||\n typeof routeValue !== \"object\" ||\n Array.isArray(routeValue)\n ) {\n throw new TypeError(\"route must be a non-array object\");\n }\n\n validator?.routes.guardRouteCallbacks(route as Route);\n validator?.routes.guardNoAsyncCallbacks(route as Route);\n const children = (route as Route).children;\n\n if (children) {\n guardRouteStructure(children, validator);\n }\n }\n}\n\n// ============================================================================\n// Logger config assertion (RealRouter-specific)\n// ============================================================================\n\nconst VALID_LEVELS_SET = new Set<string>([\n \"all\",\n \"warn-error\",\n \"error-only\",\n \"none\",\n]);\n\nfunction isValidLevel(value: unknown): value is LogLevelConfig {\n return typeof value === \"string\" && VALID_LEVELS_SET.has(value);\n}\n\nfunction formatValue(value: unknown): string {\n if (typeof value === \"string\") {\n return `\"${value}\"`;\n }\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string\n return String(value);\n}\n\nexport function assertLoggerConfig(\n config: unknown,\n): asserts config is LoggerConfig {\n if (typeof config !== \"object\") {\n throw new TypeError(\"Logger config must be an object\");\n }\n\n // `typeof null === \"object\"`, so TS still sees `object | null` here — but the\n // sole caller (Router's ctor) gates on `if (loggerConfig)`, so null/falsy never\n // arrives; treat it as the non-null object the gate guarantees.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- gated caller never passes null\n const obj = config!;\n\n // Check for unknown properties\n for (const key of Object.keys(obj)) {\n if (\n key !== \"level\" &&\n key !== \"callback\" &&\n key !== \"callbackIgnoresLevel\"\n ) {\n throw new TypeError(`Unknown logger config property: \"${key}\"`);\n }\n }\n\n // Validate level if present\n if (\"level\" in obj && obj.level !== undefined && !isValidLevel(obj.level)) {\n throw new TypeError(\n `Invalid logger level: ${formatValue(obj.level)}. Expected: \"all\" | \"warn-error\" | \"error-only\" | \"none\"`,\n );\n }\n\n // Validate callback if present\n if (\n \"callback\" in obj &&\n obj.callback !== undefined &&\n typeof obj.callback !== \"function\"\n ) {\n throw new TypeError(\n `Logger callback must be a function, got ${typeof obj.callback}`,\n );\n }\n\n // Validate callbackIgnoresLevel if present (logger.configure does not type-check it)\n if (\n \"callbackIgnoresLevel\" in obj &&\n obj.callbackIgnoresLevel !== undefined &&\n typeof obj.callbackIgnoresLevel !== \"boolean\"\n ) {\n throw new TypeError(\n `Logger callbackIgnoresLevel must be a boolean, got ${typeof obj.callbackIgnoresLevel}`,\n );\n }\n}\n"],"mappings":"AASA,SAAgB,EAAkB,EAAqB,CACrD,GACE,CAAC,GACD,OAAO,GAAS,UACf,EAAkC,cAAgB,OAEnD,MAAU,UAAU,qCAAqC,EAE3D,IAAK,IAAM,KAAO,EAChB,GAAI,OAAO,yBAAyB,EAAM,CAAG,CAAC,EAAE,IAC9C,MAAU,UAAU,yCAAyC,EAAI,EAAE,CAGzE,CAGA,SAAgB,EACd,EACA,EACM,CAEN,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAsB,EAE5B,GAEE,OAAO,GAAe,WADtB,GAEA,MAAM,QAAQ,CAAU,EAExB,MAAU,UAAU,kCAAkC,EAGxD,GAAW,OAAO,oBAAoB,CAAc,EACpD,GAAW,OAAO,sBAAsB,CAAc,EACtD,IAAM,EAAY,EAAgB,SAE9B,GACF,EAAoB,EAAU,CAAS,CAE3C,CACF,CAMA,MAAM,EAAmB,IAAI,IAAY,CACvC,MACA,aACA,aACA,MACF,CAAC,EAED,SAAS,EAAa,EAAyC,CAC7D,OAAO,OAAO,GAAU,UAAY,EAAiB,IAAI,CAAK,CAChE,CAEA,SAAS,EAAY,EAAwB,CAS3C,OARI,OAAO,GAAU,SACZ,IAAI,EAAM,GAEf,OAAO,GAAU,SACZ,KAAK,UAAU,CAAK,EAItB,OAAO,CAAK,CACrB,CAEA,SAAgB,EACd,EACgC,CAChC,GAAI,OAAO,GAAW,SACpB,MAAU,UAAU,iCAAiC,EAOvD,IAAM,EAAM,EAGZ,IAAK,IAAM,KAAO,OAAO,KAAK,CAAG,EAC/B,GACE,IAAQ,SACR,IAAQ,YACR,IAAQ,uBAER,MAAU,UAAU,oCAAoC,EAAI,EAAE,EAKlE,GAAI,UAAW,GAAO,EAAI,QAAU,IAAA,IAAa,CAAC,EAAa,EAAI,KAAK,EACtE,MAAU,UACR,yBAAyB,EAAY,EAAI,KAAK,EAAE,yDAClD,EAIF,GACE,aAAc,GACd,EAAI,WAAa,IAAA,IACjB,OAAO,EAAI,UAAa,WAExB,MAAU,UACR,2CAA2C,OAAO,EAAI,UACxD,EAIF,GACE,yBAA0B,GAC1B,EAAI,uBAAyB,IAAA,IAC7B,OAAO,EAAI,sBAAyB,UAEpC,MAAU,UACR,sDAAsD,OAAO,EAAI,sBACnE,CAEJ"}
|
package/dist/cjs/helpers.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./constants.js");function t(e,t){if(e===void 0)return n(t);let r={};for(let t in e)Object.hasOwn(e,t)&&e[t]!==void 0&&(r[t]=e[t]);if(t!==void 0)for(let e in t)Object.hasOwn(t,e)&&t[e]!==void 0&&(r[e]=t[e]);return r}function n(e){if(e===void 0)return;let t;for(let n in e)Object.hasOwn(e,n)&&e[n]===void 0&&(t??={...e},delete t[n]);return t??e}const r=new Set([`string`,`number`,`boolean`]);function i(e){return r.has(typeof e)}function a(e,t){if(e===t)return!0;if(Array.isArray(e)){if(!Array.isArray(t))return e.length===1&&a(e[0],t);if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(!a(e[n],t[n]))return!1;return!0}return Array.isArray(t)?t.length===1&&a(e,t[0]):i(e)&&i(t)&&String(e)===String(t)}function o(e){return Object.freeze(e)}function s(e,n,r,i=!1){if(e!==void 0)return Object.freeze(t(e,n));if(n===void 0||n===r)return r;if(i)return Object.freeze(n);let a=t(void 0,n);return Object.freeze(a===n?{...n}:a)}function c(t){if(t===void 0)return t;let n;for(let e in t){if(!Object.hasOwn(t,e))continue;let r=t[e];r!==void 0&&(n??={},n[e]=r)}return n??e.EMPTY_PARAMS}exports.areParamValuesEqual=a,exports.freezeStateShell=o,exports.mergeDefined=t,exports.mergeWithDefault=s,exports.normalizeParams=c;
|
|
2
|
-
//# sourceMappingURL=helpers.js.map
|