@real-router/core 0.134.0 → 0.135.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Router-B4mEOfJE.js +2 -0
- package/dist/cjs/Router-B4mEOfJE.js.map +1 -0
- package/dist/cjs/Router.d.ts +4 -3
- package/dist/cjs/Router.d.ts.map +1 -1
- package/dist/cjs/RouterError-C-p1yaby.js +2 -0
- package/dist/cjs/RouterError-C-p1yaby.js.map +1 -0
- package/dist/cjs/api/getLifecycleApi.d.ts.map +1 -1
- package/dist/cjs/api/getPluginApi.d.ts.map +1 -1
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/dependenciesStore.d.ts +18 -0
- package/dist/cjs/dependenciesStore.d.ts.map +1 -0
- package/dist/cjs/helpers-RzUXJQAh.js +2 -0
- package/dist/cjs/helpers-RzUXJQAh.js.map +1 -0
- package/dist/cjs/helpers.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internals-NAYRbkal.js +2 -0
- package/dist/cjs/internals-NAYRbkal.js.map +1 -0
- package/dist/cjs/internals.d.ts +1 -1
- package/dist/cjs/internals.d.ts.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +1 -1
- package/dist/cjs/utils.js +1 -1
- package/dist/cjs/validation.js +1 -1
- package/dist/esm/Router-DWr9wSTV.mjs +2 -0
- package/dist/{cjs/Router-DSHn-OMr.js.map → esm/Router-DWr9wSTV.mjs.map} +1 -1
- package/dist/esm/Router.d.mts +4 -3
- package/dist/esm/Router.d.mts.map +1 -1
- package/dist/{cjs/ingest-C6HQ7xXf.js → esm/RouterError-gEM9-BIR.mjs} +2 -2
- package/dist/esm/RouterError-gEM9-BIR.mjs.map +1 -0
- package/dist/esm/api/getLifecycleApi.d.mts.map +1 -1
- package/dist/esm/api/getPluginApi.d.mts.map +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/dependenciesStore.d.mts +18 -0
- package/dist/esm/dependenciesStore.d.mts.map +1 -0
- package/dist/esm/helpers-D1uQgDim.mjs +2 -0
- package/dist/esm/helpers-D1uQgDim.mjs.map +1 -0
- package/dist/esm/helpers.d.mts.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/internals-BXb1N4ET.mjs +2 -0
- package/dist/esm/{internals-CcqRqpm9.mjs.map → internals-BXb1N4ET.mjs.map} +1 -1
- package/dist/esm/internals.d.mts +1 -1
- package/dist/esm/internals.d.mts.map +1 -1
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +1 -1
- package/dist/esm/utils.mjs +1 -1
- package/dist/esm/validation.mjs +1 -1
- package/package.json +1 -1
- package/dist/cjs/Router-DSHn-OMr.js +0 -2
- package/dist/cjs/helpers-RoLubPTf.js +0 -2
- package/dist/cjs/helpers-RoLubPTf.js.map +0 -1
- package/dist/cjs/ingest-C6HQ7xXf.js.map +0 -1
- package/dist/cjs/internals-BGTllfGV.js +0 -2
- package/dist/cjs/internals-BGTllfGV.js.map +0 -1
- package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.d.ts +0 -9
- package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.d.ts.map +0 -1
- package/dist/esm/Router-Vyf1k63r.mjs +0 -2
- package/dist/esm/Router-Vyf1k63r.mjs.map +0 -1
- package/dist/esm/helpers-CP7uvUBo.mjs +0 -2
- package/dist/esm/helpers-CP7uvUBo.mjs.map +0 -1
- package/dist/esm/ingest-XMtKw1RB.mjs +0 -2
- package/dist/esm/ingest-XMtKw1RB.mjs.map +0 -1
- package/dist/esm/internals-CcqRqpm9.mjs +0 -2
- package/dist/esm/namespaces/DependenciesNamespace/dependenciesStore.d.mts +0 -9
- package/dist/esm/namespaces/DependenciesNamespace/dependenciesStore.d.mts.map +0 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DefaultDependencies } from "./types/router.js";
|
|
2
|
+
import { Limits } from "./types/internal.js";
|
|
3
|
+
//#region src/dependenciesStore.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The router's dependency record — plain data the router owns itself, with no
|
|
6
|
+
* namespace in front of it. Every operation on it that core runs for its own
|
|
7
|
+
* sake lives in this module; the edits a caller asks for live in
|
|
8
|
+
* `getDependenciesApi`.
|
|
9
|
+
*
|
|
10
|
+
* ⚠ `limits` is read by `@real-router/validation-plugin` off the store it is
|
|
11
|
+
* handed, and by nothing in core.
|
|
12
|
+
*/
|
|
13
|
+
export interface DependenciesStore<Dependencies extends DefaultDependencies = DefaultDependencies> {
|
|
14
|
+
dependencies: Partial<Dependencies>;
|
|
15
|
+
limits: Limits;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//# sourceMappingURL=dependenciesStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependenciesStore.d.ts","names":[],"sources":["../../src/dependenciesStore.ts"],"mappings":";;;;;;;;;;;;iBAkBiB,kBACf,qBAAqB,sBAAsB;EAE3C,cAAc,QAAQ;EACtB,QAAQ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./RouterError-C-p1yaby.js"),t=Object.freeze,n=Object.hasOwn,r=Object.keys,i=Object.getPrototypeOf,a=Array.isArray,o=Array.prototype.slice,s=`signal`;function c(t,n){if(t===void 0)return n;let i={};for(let n of r(t)){if(n===`__proto__`)continue;let r=t[n];r!==void 0&&e.s(i,n,r)}if(n!==void 0)for(let t of r(n)){if(t===`__proto__`)continue;let r=n[t];r!==void 0&&e.s(i,t,r)}return i}const l=new Set([`string`,`number`,`boolean`]);function u(e){return l.has(typeof e)}function d(e,t){if(e===t)return!0;if(Array.isArray(e)){let n=e.length;if(!Array.isArray(t))return n===1&&d(e[0],t);if(n!==t.length)return!1;for(let r=0;r<n;r++)if(!d(e[r],t[r]))return!1;return!0}return Array.isArray(t)?t.length===1&&d(e,t[0]):u(e)&&u(t)&&String(e)===String(t)}function f(e,t){let n=r(e),i=r(t);if(n.length!==i.length)return!1;for(let r of n)if(!i.includes(r)||!d(e[r],t[r]))return!1;return!0}function p(e,t,n){if(n.length===0)return!0;let i=r(e),a=r(t);for(let r of n)if(!d(i.includes(r)?e[r]:void 0,a.includes(r)?t[r]:void 0))return!1;return!0}function m(e){return t(e)}function h(e,t,n){return e===void 0?t===void 0||t===n?n:t:c(e,t)}function g(t,n){return h(t,n,e.d)}function _(n,r){let i=h(n,r,e.f);return i===e.f?i:t(i)}function v(n,i){if(n==null||n===i)return i;let a={};for(let t of r(n)){if(t===`__proto__`)continue;let r=n[t];r!==void 0&&e.s(a,t,r)}return t(a)}function y(n){let i={};for(let s of r(n)){if(s===`__proto__`)continue;let r=n[s];r!==void 0&&e.s(i,s,a(r)?t(o.call(r)):r)}return t(i)}function b(n){let i={};for(let t of r(n)){if(t===`__proto__`)continue;let r=n[t];if(r!==void 0){if(t===`segments`){r!==null&&e.s(i,t,y(r));continue}e.s(i,t,r)}}return t(i)}function x(e){return n(e,s)?e.signal:void 0}function S(e,t){return n(e,t)?e[t]:void 0}function C(n){if(n===e.u)return e.u;let i={},a=n;for(let t of r(a))t!==`__proto__`&&t!==s&&e.s(i,t,a[t]);return t(i)}function w(t,n){if(t===void 0)return t;if(t===null)return n;let i;for(let n of r(t)){if(n===`__proto__`)continue;let r=t[n];r!==void 0&&(i??={},e.s(i,n,r))}return i??n}function T(e){if(e==null)return e;let t=i(e);return!Array.isArray(e)&&(t===null||t===Object.prototype)?{...e}:e}function E(t){return delete t[e.m],t}function D(e,n){if(typeof n!=`object`||!n)return n;try{let e=Array.isArray(n)?[...n]:{...n};return t(E(e))}catch(t){throw TypeError(`[router.constructor] Invalid "${e}": reading it threw.`,{cause:t})}}function O(t){if(!n(t,`__proto__`))return t;let i={};for(let n of r(t))n!==`__proto__`&&e.s(i,n,t[n]);return i}Object.defineProperty(exports,"_",{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,"d",{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,"f",{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,"g",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"h",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,"m",{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,"p",{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,"u",{enumerable:!0,get:function(){return g}});
|
|
2
|
+
//# sourceMappingURL=helpers-RzUXJQAh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers-RzUXJQAh.js","names":["EMPTY_PARAMS","EMPTY_SEARCH","EMPTY_OPTS","UNSAFE_KEY"],"sources":["../../src/helpers.ts"],"sourcesContent":["// packages/core/src/helpers.ts\n\nimport {\n EMPTY_OPTS,\n EMPTY_PARAMS,\n EMPTY_SEARCH,\n UNSAFE_KEY,\n} from \"./constants\";\nimport { putField } from \"./utils/ingest\";\n\nimport type { NavigationOptions, State, TransitionMeta } from \"./types\";\n\n/**\n * Intrinsics captured at module load: `freeze`, `hasOwn`, `objectKeys`.\n *\n * ⚑ A guard is only as strong as the intrinsic it reads WHEN IT RUNS, and an\n * application can re-point any of these AFTER boot — which is what this closes.\n * Measured on the uncaptured form: one naive `Object.hasOwn` polyfill walked\n * straight through five sibling readers while the single captured guard held.\n *\n * ⚠ The limit of what capture buys — and the shim order that defeats it — is\n * stated once, in `guards.ts`. Not restated here (#2091).\n */\nconst freeze = Object.freeze;\nconst hasOwn = Object.hasOwn;\n// ⚑ Captured for the same reason as its two siblings above, and it matters more\n// here than for either: since #1854 this is the OWN-NESS gate for both channels,\n// so an application that re-points `Object.keys` after boot would be re-pointing\n// the guard itself. The dependency door reads through a capture too, held by\n// `ingestDependencies` in `guards.ts` — `dependenciesStore` delegates to it and\n// captures nothing of its own.\nconst objectKeys = Object.keys;\nconst getPrototypeOf = Object.getPrototypeOf;\n// ⚑ Captured for the same reason as the four above, and it is the only one of\n// them that is not an `Object` member — so `captured-intrinsics-authority-1971`,\n// whose scan is scoped to `Object.<name>`, would not have asked for it. It\n// decides whether a slot of the committed `transition` is copied or carried, so\n// a re-pointed `Array.isArray` decides that instead (see\n// {@link adoptForeignSegments}).\nconst isArray = Array.isArray;\n// ⚑ And the COPY is taken with a captured method rather than by asking the\n// object, for the reason the copy exists at all: `entry.slice()` reads a method\n// off the caller's array and `[...entry]` reads its `Symbol.iterator`, so an own\n// property on either shadows the intrinsic and the \"copy\" is then whatever the\n// caller's function returned. Measured on a plain array with an own `slice`:\n// `entry.slice()` answers `[\"HIJACKED\"]`, this answers the real elements.\nconst arraySlice = Array.prototype.slice;\n/**\n * The one `NavigationOptions` key core's entry door withholds from its copy\n * (#1962). Typed as `keyof NavigationOptions` rather than written as a bare\n * literal, so renaming the field is a compile error here instead of a silently\n * preserved `signal` — the spelling is not the thing being named, the FIELD is.\n */\nconst SIGNAL_KEY: keyof NavigationOptions = \"signal\";\n/**\n * The one `TransitionMeta` key whose value is itself a container core owns\n * (#2140) — typed for the same reason as {@link SIGNAL_KEY} above: the FIELD is\n * what is being named, so renaming it in `TransitionMeta` is a compile error\n * here rather than an adoption that silently stops reaching one level down.\n */\nconst SEGMENTS_KEY: keyof TransitionMeta = \"segments\";\n// =============================================================================\n// Default merge — `undefined` ≡ absence (#1550 / #1551)\n// =============================================================================\n\n/**\n * Merges a route default UNDER a value (the value wins), treating `undefined` as\n * **absence on both sides** (#1550 / #1551).\n *\n * When there IS a default, a key survives only when its winning value is\n * defined:\n * - `mergeDefined({ page: \"1\" }, { page: undefined })` → `{ page: \"1\" }` — an\n * explicit `undefined` from the caller does not outrank the default (this is\n * what the path channel always did via `normalizeChannel`, and what the query\n * channel did not, #1550);\n * - `mergeDefined({ q: undefined }, undefined)` → `{}` — a default that itself\n * carries `undefined` behaves exactly like no default entry, instead of\n * leaking an `undefined`-valued own key into the frozen state (#1551).\n *\n * Because the rule lives in the merge rather than in a separately-ordered\n * \"normalize\" stage, it holds for every producer and cannot be reintroduced by\n * whichever side is merged last.\n *\n * Allocation contract: **returns the `value` argument itself** when there is no\n * default, so a caller that freezes or stores the result must copy it first.\n * `undefined` in ⇒ `undefined` out then too, which keeps the matcher's\n * single-bag fallback (`search ?? params`) reachable.\n */\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T,\n value: T | undefined,\n): T;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T,\n): T;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T | undefined,\n): T | undefined;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T | undefined,\n): T | undefined {\n if (defaultValue === undefined) {\n return value;\n }\n\n const merged: Record<string, unknown> = {};\n\n // ⚑ `Object.keys`, for the reason `normalizeChannel` above carries (#1854) —\n // and this door is the SIBLING, found by probing it rather than by reasoning\n // from the other one. A route's `defaultParams` / `defaultSearch` is a bag the\n // application still holds and may be Proxy-backed. Measured on the\n // descriptor-reading form: a lying `getOwnPropertyDescriptor` puts an\n // inherited key into `state.params` while `state.path` prints without it —\n // the same state-contradicts-its-own-URL outcome, one function away.\n for (const key of objectKeys(defaultValue)) {\n // Dropped from the published channel, same rule as the entry guard (#1852).\n //\n // ⚠ UNREACHABLE from every current caller, and kept anyway. `defaultValue`\n // reaches this loop from exactly three places — `mergeOwnChannel` and the\n // two `forwardState` hops in `RoutesNamespace` — and all three read it out\n // of `store.config.defaultParams` / `.defaultSearch`, which since #2172 hold\n // core-minted copies that `copyOwnData` already stripped. Measured by\n // coverage rather than argued: the suite does not reach this line.\n //\n // ⚑ It is defence in depth at a boundary, not dead code, and the\n // distinction is the dependency: what makes it unreachable is a guarantee\n // held by ANOTHER function. A fourth caller that hands this loop a bag from\n // somewhere else, or an adoption door that stops dropping the key, makes it\n // live again with no signal here.\n /* v8 ignore next 3 -- @preserve: see above; unreachable via the adoption door, not via this function's own contract */\n if (key === UNSAFE_KEY) {\n continue;\n }\n\n // One read here too: a route default is a bag the app still holds.\n const entry = defaultValue[key];\n\n if (entry !== undefined) {\n putField(merged, key, entry);\n }\n }\n\n if (value !== undefined) {\n // ⚑ `objectKeys`, one spelling across every copy loop in this file.\n //\n // What the spelling buys: `Object.keys` asks `ownKeys` FIRST and consults\n // descriptors only for what that returned. A `for…in` + `hasOwn` pair asks\n // the bag about a key the walk already chose, and on a Proxy that is a trap\n // free to vouch for a key `ownKeys` never listed — #1854.\n //\n // ⚠ Do NOT read that as \"nothing foreign reaches these loops\". This one has a\n // SECOND caller (`#layerChainDefaults`) handing it a raw bag from the caller\n // and from the matcher; the `UNSAFE_KEY` skip below is live because of it,\n // measured. `objectKeys` closes the own-ness question, not the provenance\n // one.\n for (const key of objectKeys(value)) {\n // ⚠ LIVE, and \"dead by construction because `value` always arrives\n // through `normalizeChannel`\" is a REACHABILITY argument — the kind this\n // repository records as having been wrong repeatedly.\n //\n // The second caller is `RoutesNamespace.#layerChainDefaults`, which merges\n // a `forwardTo` hop's own defaults with the caller's bag AND with the\n // MATCHER's bag. Measured on a chain whose hop carries `defaultParams` /\n // `defaultSearch`: the merged record comes back with `__proto__` among its\n // own keys on BOTH directions — including the URL one, where the value is\n // whatever the address bar said — and every `forwardState` interceptor\n // receives it.\n if (key === UNSAFE_KEY) {\n continue;\n }\n\n // ONE read, both decisions from it. Asking and then taking would be two\n // calls into the caller's accessor, and a bag that answers differently\n // between them lands `undefined` in a frozen channel — the same shape\n // {@link adoptForeignBag}'s copy loop names, on the defaulted path.\n const entry = value[key];\n\n // `undefined` means \"I said nothing\", so the default keeps the slot.\n if (entry === undefined) {\n continue;\n }\n\n putField(merged, key, entry);\n }\n }\n\n return merged as T;\n}\n\n// =============================================================================\n// Param value comparison (#1554)\n// =============================================================================\n\n/** The value types a channel prints into (and parses back from) a URL. */\nconst PRINTABLE_TYPES = new Set([\"string\", \"number\", \"boolean\"]);\n\n/** A value the two channels can carry across a URL round-trip. */\nfunction isPrintableScalar(value: unknown): value is string | number | boolean {\n return PRINTABLE_TYPES.has(typeof value);\n}\n\n/**\n * Compares two param / query values for equality **independently of where they\n * came from** (#1554).\n *\n * The two directions produce different value DOMAINS for the same location: the\n * URL direction parses (`?page=2` → `2`, `?a=1&a=2` → `[1, 2]`, a path slot is\n * always a string), the intent direction keeps whatever the caller supplied\n * (`{ page: \"2\" }` stays a string). Both build the SAME `state.path`, so a\n * `===`-based comparison reported a URL-derived state and an intent-derived\n * state on one location as UNEQUAL — an active link rendered inactive.\n *\n * The rule is therefore \"equal when both values print the same query string\":\n * - **scalars** (string / number / boolean) compare by their printed form, so\n * `2 ≡ \"2\"` and `true ≡ \"true\"`;\n * - **arrays** compare element-wise under the same rule, and a **singleton\n * array** compares against a bare scalar (`[\"1\"]` and `1` both print `?a=1`);\n * - everything else (`null`, `undefined`, objects) keeps strict semantics —\n * those print differently (`?a` vs `?a=` vs nothing at all), so tolerating\n * them would equate genuinely different URLs.\n *\n * Value normalization is deliberately NOT done: `state.search` keeps the mixed\n * domain (RFC-4 M2 / §10.14 decision (b)) and comparison is the single place\n * that knows the two domains describe the same location. Unifying the domain\n * itself belongs to the typed search-schema stage.\n */\nexport function areParamValuesEqual(val1: unknown, val2: unknown): boolean {\n if (val1 === val2) {\n return true;\n }\n\n if (Array.isArray(val1)) {\n // ⚑ ONE read of the length (#2085). `Array.isArray` answers TRUE through a\n // Proxy — a reactive array is exactly that shape — so the guard above does\n // not make this slot plain data, and the equality test and the loop bound\n // are two consumers of one answer. It also gives the walk a local bound.\n const length1 = val1.length;\n\n // A singleton array prints exactly like its element (`[\"1\"]` and `1` both\n // print `?a=1`), so compare across the shape instead of rejecting on it.\n if (!Array.isArray(val2)) {\n return length1 === 1 && areParamValuesEqual(val1[0], val2);\n }\n\n if (length1 !== val2.length) {\n return false;\n }\n\n for (let i = 0; i < length1; i++) {\n if (!areParamValuesEqual(val1[i], val2[i])) {\n return false;\n }\n }\n\n return true;\n }\n\n if (Array.isArray(val2)) {\n return val2.length === 1 && areParamValuesEqual(val1, val2[0]);\n }\n\n return (\n isPrintableScalar(val1) &&\n isPrintableScalar(val2) &&\n String(val1) === String(val2)\n );\n}\n\n/**\n * Shallow key/value equality of two param-like records (path params or query),\n * using {@link areParamValuesEqual} per key so array values compare by content.\n *\n * Both readers need the SAME comparison and it is built on the function above,\n * so it lives beside it: `StateNamespace.areStatesEqual` uses it for state\n * IDENTITY (both channels, whole bags — #515 / #478), and `isActiveRoute`'s\n * exact arm for the query half of a LOCATION (#1978).\n *\n * ⚑ Membership is decided by the LIST the count already produced (#1815), never\n * by a second interrogation of the bag. `key in right`, `hasOwn(right, key)` and\n * `propertyIsEnumerable.call(right, key)` are one family — the caller picks the\n * key and the bag is asked about THAT key: `in` through `[[HasProperty]]`, the\n * other two through `[[GetOwnProperty]]`. On a Proxy each is a trap (`has`,\n * `getOwnPropertyDescriptor`) free to vouch for a key `ownKeys` never listed,\n * while `objectKeys` asks `ownKeys` first and consults descriptors only for what\n * it returned. That is the #1854 argument, and it applies here for the same\n * reason: the count is `objectKeys`, so anything that answers a different\n * question can disagree with it, and a bag whose own-enumerable surface is\n * DISJOINT from the other side's then compares equal.\n *\n * ⚠ A linear scan per key, so this is O(n²) in bag width where the `in` form was\n * O(n) — a deliberate trade, and the crossover was measured rather than assumed.\n * Against `in`: 0.9–1.1× up to n = 20, 2.8× at n = 200, 9.6× at n = 1000. A `Set`\n * is the O(n) alternative and was rejected because it costs a FLAT 1.4–1.5×\n * everywhere and 2.5× at n = 1, which is the width these bags actually have.\n * `state.params` is bounded by the route's slots; `state.search` is bounded by\n * the URL, so a query string a browser will carry lands around n = 200, i.e.\n * inside the regime where the two forms are within noise of each other.\n */\nexport function recordsShallowEqual(\n left: Readonly<Record<string, unknown>>,\n right: Readonly<Record<string, unknown>>,\n): boolean {\n const leftKeys = objectKeys(left);\n const rightKeys = objectKeys(right);\n\n if (leftKeys.length !== rightKeys.length) {\n return false;\n }\n\n for (const key of leftKeys) {\n if (\n !rightKeys.includes(key) ||\n !areParamValuesEqual(left[key], right[key])\n ) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * {@link recordsShallowEqual} restricted to the slots a route declares: the two\n * bags may carry anything else, and only these keys are compared.\n *\n * `StateNamespace.areStatesEqual` asks this on its default arm, where the route\n * names the path slots and the query channel is out of scope.\n *\n * ⚑ A slot the key list does not vouch for reads as `undefined` — the same\n * value an absent slot has — so a bag carrying the slot as `undefined` compares\n * equal to one omitting it. The whole-bag reader disagrees there by design: it\n * gates on key COUNT, and the two bags have different surfaces.\n */\nexport function slotsShallowEqual(\n left: Readonly<Record<string, unknown>>,\n right: Readonly<Record<string, unknown>>,\n slots: readonly string[],\n): boolean {\n // Above the key lists, not below: a route declaring no slot has nothing to\n // compare, and this must not touch either bag to say so — building the lists\n // first turns an answer into a THROW for a state that omits `params`.\n if (slots.length === 0) {\n return true;\n }\n\n const leftKeys = objectKeys(left);\n const rightKeys = objectKeys(right);\n\n for (const slot of slots) {\n if (\n !areParamValuesEqual(\n leftKeys.includes(slot) ? left[slot] : undefined,\n rightKeys.includes(slot) ? right[slot] : undefined,\n )\n ) {\n return false;\n }\n }\n\n return true;\n}\n\n// =============================================================================\n// State Helpers\n// =============================================================================\n\n/**\n * Freezes the State object's own level — the SHELL, not the state.\n *\n * The name says SHELL because that is the whole depth (#1599): it blocks\n * reassignment of every field the `State` interface declares, and nothing more.\n *\n * **The depth is a POLICY, not this function's job: every object is frozen once,\n * where it is created.** That is deliberate and measured — re-freezing an\n * already-frozen object costs ~8 ns, so a recursive walk would pay per node for\n * work its producers already did. The four producers and what each owns:\n *\n * - `params` — {@link materialize} at the publication boundary, and nowhere else\n * no merge to freeze it, so `pipeline/materialize` does at the publication\n * boundary (#1598), for the frozen and the pending producer alike\n * - `search` — the `EMPTY_SEARCH` singleton, or `admittedSearch`\n * (`channels/modeGate.ts`) on its DROP branch, the one branch that builds a bag\n * the caller did not already freeze\n * - `transition` + nested — `buildTransitionMeta()` (or inline in\n * `navigateToNotFound()`), and {@link adoptForeignTransition} for the one\n * producer whose meta core did not build: the state `systemCommit` is handed\n * from outside, where there IS no origin to have frozen it\n * - the shell — here\n *\n * `state.context` is **intentionally not frozen** — plugins write to it via\n * `claim.write(state, value)` after state creation.\n *\n * The whole matrix is pinned black-box in\n * `tests/functional/error/helpers.test.ts` (\"state immutability across every\n * producer\"), mutationally validated against all four sites. Before #1599 two of\n * them were unguarded: deleting the `canonicalize` freeze left the entire suite\n * green, and the mode gate's freeze was reachable only under a non-`loose` mode\n * with one key dropped AND one admitted.\n *\n * @internal\n */\nexport function freezeStateShell<T extends State>(state: T): T {\n // No `if (!state) return state` guard: `Object.freeze` returns non-objects\n // (incl. null/undefined) unchanged, every caller reaches here with a state in\n // hand (the commit `update` on the table, `materialize`'s publication\n // boundary), and `T extends State` is typed non-null.\n return freeze(state);\n}\n\n/**\n * The shared half of the two channel merges: a route default UNDER a value the\n * PIPELINE minted, with the channel's own `empty` singleton (#1027) reused when\n * neither side has anything — so the hot path allocates zero objects.\n *\n * `undefined` is absence on BOTH sides (`mergeDefined`, #1550 / #1551): an\n * explicitly-`undefined` caller value leaves the default in place, and a default\n * carrying `undefined` behaves like no entry.\n *\n * ⚑ It does NOT freeze and it does NOT copy, and both follow from the ONE thing\n * its callers have in common: `value` is an object `normalizeChannel` returned,\n * so nothing outside holds a reference to protect and each channel's own\n * publication rule decides the freeze. PROVENANCE is the guarantee, not\n * proximity — the query bag is minted on the line above its use in\n * `canonicalize`, the path bag two hundred lines above its own, and both are\n * equally core's. A bag that came from\n * somewhere else must go through {@link adoptForeignBag} instead. The two are\n * separate functions because they share no caller, and because the split is\n * already visible in the parameters rather than needing a flag to name it.\n */\nfunction mergeOwnChannel(\n defaultValue: Record<string, unknown> | undefined,\n value: Record<string, unknown> | undefined,\n empty: Readonly<Record<string, never>>,\n): Readonly<Record<string, unknown>> {\n if (defaultValue !== undefined) {\n return mergeDefined(defaultValue, value);\n }\n\n if (value === undefined || value === empty) {\n return empty;\n }\n\n return value;\n}\n\n/**\n * Stage ③ for the PATH channel — and it hands the bag back UNFROZEN.\n *\n * `materialize` owns this freeze, at the publication boundary and nowhere else\n * (#1598 / #1928). A second freeze here would certify nothing a consumer can\n * observe — every `Canonical` that becomes a State is frozen by `materialize`,\n * and the ones that do not become a State are discarded — while splitting the\n * channel into a frozen form on a route with defaults and a live one without.\n * `canonicalize.properties.ts` holds the line: the only frozen thing this stage\n * may hand back on this channel is the shared `EMPTY_PARAMS` singleton.\n *\n * @internal\n */\nexport function mergePathChannel(\n defaultParams: Record<string, unknown> | undefined,\n bag: Record<string, unknown> | undefined,\n): Readonly<Record<string, unknown>> {\n return mergeOwnChannel(defaultParams, bag, EMPTY_PARAMS);\n}\n\n/**\n * Stage ③ for the QUERY channel — and this one DOES freeze, which is the\n * asymmetry with {@link mergePathChannel}.\n *\n * ⚠ **The asymmetry is PERF-GATED, and nothing but that gate holds it.**\n * Correctness does not: with this freeze removed and `freeze(state.search)`\n * added to `materialize` beside `params`, the whole suite is GREEN.\n * `materialize` is below every hop that could publish, so the publication\n * guarantee survives the move.\n *\n * What the move costs is now MEASURED on this shape, and the cited figure was\n * pointing at the wrong arm. Alternating A/B processes, 9 pairs, medians, with\n * an A/A control interleaved (`isActiveRoute-sibling` early-outs above\n * `canonicalize` and must not move — it did not, −0.5 % against a ±0.7 % floor):\n *\n * arm merge (now) materialize delta A/A floor\n * isActiveRoute-exact 215.8 219.3 +1.6 % ±1.6 % noise\n * isActiveRoute-parent 131.5 139.9 +6.3 % ±0.9 % REGRESSION\n * buildPath-params 460.8 444.0 −3.6 % ±0.8 % gain\n * canNavigateTo 781.2 783.4 +0.3 % ±0.4 % noise\n *\n * ⚠ #1598 named `isActiveRoute-exact` at 9.8 %. On this shape that arm is inside\n * the noise floor and the cost has MOVED to `parent` at 6.3 % — which is why the\n * figure had to be re-taken rather than cited: it was measured when the merge\n * froze BOTH channels, and #1928 ended that.\n *\n * The mechanism is the short-circuit below. On `parent` the query bag IS the\n * `EMPTY_SEARCH` singleton, so this function returns it untouched while\n * `materialize` would re-freeze it — the ~8 ns #1598 identified, landing on a\n * different arm than it recorded. `buildPath` never materialises, so under the\n * move it stops paying any freeze at all: that is the −3.6 %, and it is real but\n * does not buy back a render-path predicate.\n *\n * So the split stands, on a measurement rather than on a citation. Numbers are\n * tsx-against-`src`, i.e. comparable to each other, not to a `dist` absolute.\n *\n * @internal\n */\nexport function mergeQueryChannel(\n defaultSearch: Record<string, unknown> | undefined,\n bag: Record<string, unknown> | undefined,\n): Readonly<Record<string, unknown>> {\n const merged = mergeOwnChannel(defaultSearch, bag, EMPTY_SEARCH);\n\n // The singleton is frozen by construction, and re-freezing costs the same\n // ~8 ns the paragraph above is about.\n //\n // ⚑ EQUIVALENT under mutation, and declared rather than left as a silent\n // survivor: replacing this with a plain `freeze(merged)` reds NOTHING in the\n // package suite. It cannot — re-freezing an already-frozen object is a\n // no-op observationally, so only a benchmark can tell the two apart. And one\n // did: this branch is exactly what makes `isActiveRoute-parent` 6.3 % cheaper\n // here than at the publication boundary, because on that arm the bag IS the\n // singleton. Behaviourally equivalent, NOT perf-equivalent — do not\n // \"simplify\" it back on the strength of a green suite.\n return merged === EMPTY_SEARCH ? merged : freeze(merged);\n}\n\n/**\n * Adopts a bag the router does NOT own: strips `undefined`-valued keys, drops\n * `UNSAFE_KEY`, copies, and freezes the copy.\n *\n * The five call sites are `navigateToState` and `systemCommit`, which copy a\n * `State` handed in through a published API (#1792). None of them merges a\n * default — that is the other half of the split described on\n * {@link mergeOwnChannel} — so there is no `defaultValue` parameter to pass\n * `undefined` to five times over.\n *\n * ⚠ Every step here exists because the object is foreign: freezing in place\n * would freeze the caller's own bag, committing it by reference would let the\n * caller mutate a published state, and skipping the key drop would carry an own\n * `__proto__` into it.\n *\n * @internal\n */\nexport function adoptForeignBag(\n value: Record<string, unknown> | undefined,\n empty: Readonly<Record<string, never>>,\n): Readonly<Record<string, unknown>> {\n // ⚠ `null` is here for the reason the FOREIGN in this function's name is:\n // the declared type says the slot is filled, and a bag the router does not\n // own is exactly what is free to disagree — an interceptor spreading a\n // partial result nulls a slot, the same producer `Router`'s codec seam names.\n // Without it `Object.keys` performs `ToObject` and `navigateToState` rejects\n // with a bare `TypeError`, carrying no code to classify (#1822).\n //\n // `== null` is the intent, the spelling `Router` already uses for it: both\n // nullish values mean \"there is no bag\".\n if (value == null || value === empty) {\n return empty;\n }\n\n // ⚑ ONE walk, and the count is the contract (#1952). `objectKeys` materialises\n // the key list before any accessor on the bag runs, so a getter that DEFINES a\n // sibling key mid-walk — `__proto__`, or an `undefined`-valued one — adds a key\n // this loop never visits. A second walk re-asks `ownKeys` and does see it, so\n // with one walk the two guards below answer for the ordinary case only.\n //\n // ⚠ They still answer for it. A bag that CARRIES `__proto__`, or a key whose\n // value is `undefined`, is in the first snapshot like any other, and the\n // guards are what keep it out of a published channel — mutate either and\n // `proto-key-guarantee` / `undefined-as-absent` red (#1792, #1550 / #1551).\n const copy: Record<string, unknown> = {};\n\n for (const key of objectKeys(value)) {\n // Dropped from the published channel, same rule as the entry guard (#1852).\n if (key === UNSAFE_KEY) {\n continue;\n }\n\n // ONE read, then both decisions from it — a second `value[key]` here would\n // be a second call into the caller's accessor, which `read-count-authority`\n // pins and which is the whole hazard this file is about.\n const entry = value[key];\n\n if (entry !== undefined) {\n putField(copy, key, entry);\n }\n }\n\n return freeze(copy);\n}\n\n/**\n * The nested half of {@link adoptForeignTransition}: `segments` and the arrays\n * it holds, which are STATE SHAPE rather than caller values.\n *\n * The line between the two is measured, not preferred. On the pipeline's own\n * arc `buildTransitionMeta` freezes this container AND both of its arrays,\n * while an array sitting in a param SLOT (`params.tags`) comes back from\n * `navigate` as the caller's own object, unfrozen. So every producer already\n * agrees on where a foreign object stops being core's business, and this copies\n * exactly as deep as the declared shape goes.\n *\n * An array is copied rather than frozen in place, for the reason\n * {@link adoptForeignBag} gives for copying at all: freezing it would seal an\n * object the caller still holds. The copy is taken with the captured\n * `arraySlice`, so the caller does not get to decide what its own copy contains.\n */\nfunction adoptForeignSegments(\n value: Record<string, unknown>,\n): Readonly<Record<string, unknown>> {\n const copy: Record<string, unknown> = {};\n\n for (const key of objectKeys(value)) {\n if (key === UNSAFE_KEY) {\n continue;\n }\n\n const entry = value[key];\n\n if (entry !== undefined) {\n putField(\n copy,\n key,\n isArray(entry) ? freeze(arraySlice.call(entry)) : entry,\n );\n }\n }\n\n return freeze(copy);\n}\n\n/**\n * Adopts a `TransitionMeta` the router does NOT own — {@link adoptForeignBag}'s\n * rule applied to BOTH levels the shape declares, because `Object.freeze` is\n * shallow and the level below the meta is a container too (#2140).\n *\n * Its one call site is `EventBusNamespace.systemCommit`, the single door a\n * `State` enters core through from outside.\n *\n * ⚠ It walks the caller's bag ONCE, like its sibling, and the nested slot is\n * adopted from the value that walk already read. Reading `value.segments` again\n * to adopt it would be a second call into a caller accessor — the divergence\n * where the container is judged on one answer and committed from another.\n * `read-count-authority`'s `systemCommit · transition` and\n * `systemCommit · transition.segments` rows are what hold both levels at one.\n *\n * ⚠ Nullish is absence on the nested slot, both spellings, for the reason\n * {@link adoptForeignBag} spells out for its own `== null` guard: written\n * unconditionally the slot would hold that guard's empty answer, which is a\n * SHARED singleton — so `getState().transition.segments` would be the same\n * object as some other state's `params`, under a type that declares\n * `deactivated`, `activated` and `intersection` as required.\n */\nexport function adoptForeignTransition(\n value: Record<string, unknown>,\n): Readonly<Record<string, unknown>> {\n const copy: Record<string, unknown> = {};\n\n for (const key of objectKeys(value)) {\n if (key === UNSAFE_KEY) {\n continue;\n }\n\n const entry = value[key];\n\n if (entry === undefined) {\n continue;\n }\n\n if (key === SEGMENTS_KEY) {\n if (entry !== null) {\n putField(\n copy,\n key,\n adoptForeignSegments(entry as Record<string, unknown>),\n );\n }\n\n continue;\n }\n\n putField(copy, key, entry);\n }\n\n return freeze(copy);\n}\n\n/**\n * Core's own copy of the caller's `NavigationOptions`, made once at the entry\n * door (#1962) — every own enumerable key, minus `signal`, into a frozen record.\n *\n * ⚑ **Every own enumerable key, not the declared ones** — owner decision,\n * 2026-08-30, recorded in `CLAUDE.md` \"Supported Input Shapes\". Curating to \"the\n * declared fields\" is not expensive, it is INEXPRESSIBLE: `NavigationOptions` is\n * extended by module augmentation (`hash` / `hashChange` / `source`, three URL\n * plugins), and augmentation leaves nothing in the emitted JS to normalise\n * against — a curating copy could only hold a list hard-coded when it was\n * written, going stale against the contract in silence.\n *\n * ⚑ `signal` is the one key dropped, and dropping it HERE is what removes the\n * defect rather than moving it: dropping it any later makes whether a plugin\n * receives the app's own object or a copy turn on whether the app happened to\n * pass a signal — a discriminator the plugin never sees. Core reads it once at\n * the entry\n * and carries it as `NavigationContext.externalSignal`, which is the only form\n * the machine may ask about (#1690 / #1717); nothing downstream reads\n * `opts.signal`, so the key has no reader left to lose.\n *\n * ⚠ `UNSAFE_KEY` goes too, and that is the HAND-OUT rule acting on this object\n * rather than a second decision: this copy IS what every plugin hook receives,\n * so an own `\"__proto__\"` on it is a prototype-swap primitive for anything that\n * merges it with `Object.assign` (#1957). Entry and hand-out coincide here, so\n * the drop happens once.\n *\n * ⚠ It reads each key EXACTLY once — the rule `opts-read-once-1817` pins for the\n * named flags, extended by this copy to keys core never names. Every read below\n * the door is then a read of core's own data, so the six hoisted flags cost the\n * caller's accessors nothing at all.\n *\n * ⚠ **A key loop, and the cheaper spread is REFUTED rather than merely\n * disliked.** `{ signal: _, ...rest }` measured 27 ns against this loop's 120\n * on the four-key bag a URL plugin actually sends — and it READS `opts.signal`\n * to exclude it, which is a second call into the caller's accessor. That is\n * exactly what `commit-gate-reads-the-snapshot-1717` counts: it caught the\n * substitution at `readsAtAnnounce` 2 instead of 1. Any spread reads every key,\n * so skipping one WITHOUT reading it is what forces the loop.\n *\n * ⚑ `putField` inside it, then, for the ordinary reason (#1852): the key is the\n * caller's, the target has `Object.prototype` on its chain, and a plain\n * `[[Set]]` under a name like `toString` is divertible by an ambient accessor.\n *\n * ⚠ **Not folded into the channel copy loops, which it nearly repeats.** They\n * differ on `undefined`: a channel drops an `undefined`-valued key because a\n * frozen `state.search` may never expose one (#1550 / #1551), and this one keeps\n * it because `{ replace: undefined }` is what the caller wrote. Folding them\n * needs a skip-set or a flag, and this file carries neither: `mergePathChannel`,\n * `mergeQueryChannel` and `adoptForeignBag` are named copies with different\n * rules, not one with switches (#1928).\n *\n * @internal\n */\n/**\n * The caller's `signal` slot, read OWN and once (#2132).\n *\n * ⚑ The gate the five sibling flags carry inline at the entry — this one needs\n * its own home because it is read somewhere else. The copy deliberately skips\n * `signal` without reading it (#1717), and the entry reads the slot ABOVE the\n * copy by #1817's order, so this read asks the CALLER's object rather than\n * core's, where an inherited `signal` would cancel a navigation nobody asked to\n * cancel.\n *\n * ⚠ Its own function because the branch pushes `executeNavigation` past the\n * cognitive-complexity gate inline — inlining it back reds lint.\n *\n * @internal\n */\nexport function ownSignal(opts: NavigationOptions): AbortSignal | undefined {\n return hasOwn(opts, SIGNAL_KEY) ? opts.signal : undefined;\n}\n\n/**\n * One own-gated flag of `NavigationOptions` (#2132).\n *\n * ⚑ A function rather than five inline ternaries because inline they push\n * `executeNavigation` from 15 to 20 on the cognitive-complexity gate. It returns\n * a VALUE and allocates nothing — the shape the entry's own docblock warns about\n * is a helper returning the whole block, which has to allocate a record.\n */\nexport function ownFlag<K extends keyof NavigationOptions>(\n opts: NavigationOptions,\n key: K,\n): NavigationOptions[K] | undefined {\n return hasOwn(opts, key) ? opts[key] : undefined;\n}\n\nexport function adoptNavigationOptions(\n opts: NavigationOptions,\n): Readonly<NavigationOptions> {\n // `navigate(\"b\")` is the commonest call there is, and the facade substitutes\n // this exact singleton for it — already core's own, already frozen, already\n // empty. Recognising it by identity is the difference between one comparison\n // and a copy plus a freeze (~57 ns measured) on the majority of navigations.\n if (opts === EMPTY_OPTS) {\n return EMPTY_OPTS;\n }\n\n const copy: Record<string, unknown> = {};\n const bag = opts as Record<string, unknown>;\n\n for (const key of objectKeys(bag)) {\n // `signal` is skipped WITHOUT being read — core already holds the entry\n // snapshot, and reading it again here is the very thing #1717 pins.\n if (key === UNSAFE_KEY || key === SIGNAL_KEY) {\n continue;\n }\n\n putField(copy, key, bag[key]);\n }\n\n return freeze(copy);\n}\n\n// =============================================================================\n// Params Helpers\n// =============================================================================\n\n/**\n * Strips `undefined` values from a params object before handoff to the query\n * string engine and state storage.\n *\n * **Why this exists:** `router.navigate(name, { x: undefined })` must not put\n * `x` into the resulting URL (publicly documented contract). The underlying\n * query engine (`search-params`) already does this, but the contract belongs\n * to `@real-router/core` — this function guarantees it at the core boundary\n * so that:\n * - Plugin interceptors on `forwardState` that inject `undefined` values are\n * caught before they reach the engine\n * - `state.params` never contains `undefined` values (roundtrip consistent\n * with URL)\n * - The contract is verifiable at core's own test surface (doesn't depend on\n * engine behavior for regression detection)\n *\n * Single pass. When nothing survives (empty input, or every value `undefined`)\n * it returns the shared frozen `EMPTY_PARAMS` singleton, so the merge's\n * `value === empty` reuse branch (the channel merges) fires and an empty-params\n * navigation allocates zero transient `{}` (#1027); a non-empty input returns a fresh\n * object. Either way reference identity is not preserved across calls, and the\n * result MUST be treated as read-only — callers must not mutate it (the empty\n * case is a shared frozen singleton).\n */\nexport function normalizeChannel<T extends Record<string, unknown>>(\n bag: T,\n empty: Readonly<Record<string, never>>,\n): T;\n\nexport function normalizeChannel(\n bag: undefined,\n empty: Readonly<Record<string, never>>,\n): undefined;\n\nexport function normalizeChannel<T extends Record<string, unknown>>(\n bag: T | undefined,\n empty: Readonly<Record<string, never>>,\n): T | undefined;\n\nexport function normalizeChannel<T extends Record<string, unknown>>(\n bag: T | undefined,\n empty: Readonly<Record<string, never>>,\n): T | undefined {\n if (bag === undefined) {\n return bag;\n }\n\n // ⚠ The OTHER spelling of an absent bag, and it does not get the arm above:\n // `undefined` passes through so a caller can still tell \"no bag\" from \"empty\n // bag\", while `null` arrives only as runtime misuse of a slot the signature\n // types optional. It normalises to the same answer `{}` gets. Without the arm\n // `Object.keys` performs `ToObject`, and the two render-path doors express\n // that one throw differently: `buildPath` lets a bare `TypeError` out, and\n // `isActiveRoute`'s safety net catches it and calls a link to the CURRENT\n // page inactive (#1822).\n //\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime null guard for buildPath(name, null)\n if (bag === null) {\n return empty as unknown as T;\n }\n\n let normalized: Record<string, unknown> | undefined;\n\n // ⚑ `Object.keys`, not `for…in` + `Object.hasOwn` (#1854). `hasOwn` is\n // `[[GetOwnProperty]]`, which on a Proxy is the `getOwnPropertyDescriptor`\n // TRAP — and the caller of `hasOwn` chooses the key, so the trap is asked\n // about one it is free to lie about. The Proxy invariants permit exactly that\n // while the target is extensible and the descriptor is `configurable`.\n // `Object.keys` asks `ownKeys` FIRST and consults descriptors only for keys\n // `ownKeys` already vouched for, so a key the target does not own is never put\n // to the trap.\n //\n // ⚠ Not a hypothetical bag: Svelte 5's `$props()` reports own-ness for a key\n // only its prototype has, on every `RouteView` render (#1853) — nobody wrote a\n // Proxy, the framework did. Measured through this door before the change: an\n // inherited `leaked` reached `state.params` while `state.path` printed without\n // it, i.e. a committed state contradicting its own URL.\n //\n // ⚠ The ambient half is NOT part of this, measured rather than assumed: an\n // enumerable `Object.prototype.x` was already filtered correctly here, because\n // `hasOwn` is honest about an object that does not lie. Only the trap shape\n // was ever admitted.\n //\n // Cost, same-session A/B, medians, noisy at the ±3 ns level: a wash on an\n // empty bag, ~8 ns cheaper at one key, ~equal at three. One read per key\n // either way.\n for (const key of objectKeys(bag)) {\n // ⚑ The key is DROPPED from the published channel, and that is a decision\n // SEPARATE from how the write is performed (#1852). `putField` below closes\n // the ambient-accessor class for every name — `id`, `tab`, `lang` — and this\n // skip closes a different one, on the far side of the contract: a bag core\n // hands BACK carrying an own `\"__proto__\"` is a prototype-swap primitive for\n // any consumer that merges it with `Object.assign` or a `for…in` copy.\n // Measured from a bare URL: `?__proto__` yields `null` and\n // `?__proto__=1&__proto__=2` an array, and the inherited setter accepts both.\n //\n // ⚑ Core's OWN precedent, not a fresh judgement. `getDependenciesApi.getAll()`\n // deletes this key for that reason in those words, and records the asymmetry:\n // a single read hands back a VALUE, a door like this hands back a CONTAINER\n // that someone will merge. `state.params` / `state.search` are the most-merged\n // containers the router publishes.\n //\n // ⚠ The data-preservation argument for carrying it does not survive contact\n // with a consumer either: `Object.assign` DROPS the key even in the safe\n // string case, so \"the user's `?__proto__=1` is kept\" held for exactly one\n // hop and then failed unpredictably rather than here.\n if (key === UNSAFE_KEY) {\n continue;\n }\n\n // ⚑ ONE read per key, and the result is built from it. A test-then-re-read\n // pair here would be a TOCTOU on an object the caller owns: the key is\n // ADMITTED on the first value and USED with the second (#1812).\n const value = bag[key];\n\n if (value !== undefined) {\n // Lazy allocation: an all-empty / all-undefined input costs zero objects.\n normalized ??= {};\n putField(normalized, key, value);\n }\n }\n\n // Reuse the caller's shared singleton when nothing survived so the merge's\n // `value === empty` reuse branch fires (#1027). The two channels have DISTINCT\n // singletons — `EMPTY_PARAMS` and `EMPTY_SEARCH` are separate frozen objects\n // compared by identity — which is why `empty` is a parameter, not a constant\n // read here.\n return (normalized as T | undefined) ?? (empty as unknown as T);\n}\n\n/**\n * CORE's own copy of a caller-owned channel bag — one read per key, and nothing\n * else changed (#2134).\n *\n * ⚑ **Content-preserving, and that is the whole difference from\n * {@link normalizeChannel}.** The strip stays where it is, BELOW the\n * `forwardState` seam. This copy exists for another reason entirely: to make the\n * read a validator judges and the read the pipeline ships the same read. A key\n * that answers `v1` then `v2` is otherwise admitted on one value and printed on\n * the other.\n *\n * ⚠ **Stripping here instead is a measured regression, not a tidier place for\n * the same work.** An `undefined` value is\n * `@real-router/persistent-params-plugin`'s removal marker and it is read at the\n * seam: dropped above it, `navigate(\"b\", { id: \"2\", page: undefined })` leaves\n * `?page=7` on the URL instead of clearing it. #1849 records the same\n * measurement from the other side — eight of that package's cells red on the\n * `normalizeChannel` form.\n *\n * ⚠ **Absence passes through on BOTH spellings.** `{ ...null }` is `{}`, which\n * turns \"no bag\" into \"empty bag\" above the code that tells them apart; each\n * caller's own `?? EMPTY_*` is what resolves it.\n *\n * ⚠ **And so does anything that is not SHAPED like a bag**, which is the guard\n * against laundering: a copy of a string is `{0:\"a\",1:\"b\"}` and a copy of a\n * class instance has lost its prototype, so copying first would hand the\n * validating layer an acceptable object built out of one it refuses. Returned\n * unchanged, such a value reaches that layer as the caller wrote it. Below the\n * seam nothing changes — `normalizeChannel` treats it exactly as it does today.\n * \"Shaped like a bag\" is `Object.prototype` BY IDENTITY, so another realm's\n * plain object is not one; `adopt-channel-authority-2187` owns the row per\n * shape.\n *\n * ⚠ **A caller with no validating door below it must REFUSE what came back by\n * reference rather than write to it (#2187).** Core's own have one and it does\n * the refusing in its own words; a plugin reaching this through\n * `@real-router/core/utils` usually does not, and `adopted.x = 1` after a\n * by-reference input lands on the APPLICATION's object. The return answers \"did\n * I copy this\", and the caller owns what follows from \"no\".\n *\n * The `ownKeys`-first Proxy safety of the spread is the same argument\n * {@link normalizeChannel} makes for its own walk, and is stated there (#2091).\n */\n// ONE signature, not an overload set: `T` is generic over the whole admitted\n// union, so every spelling types itself. An overload set cannot — the arm that\n// admits absence widens the return for callers that never pass it, and adding\n// a narrower arm beside it puts the answer in resolution ORDER. The per-spelling\n// row is in `adopt-channel-authority-2187`.\nexport function adoptChannel<\n T extends Record<string, unknown> | undefined | null,\n>(bag: T): T {\n // Both spellings of \"no bag\" in one term — `== null` matches `null` and\n // `undefined` and nothing else. The two strict comparisons need a disable\n // comment apiece here: the redundant-comparison rule does not resolve `null`\n // against an unresolved type PARAMETER and calls each of them dead.\n if (bag == null) {\n return bag;\n }\n\n // ⚑ `Array.isArray` is NOT an unreachable arm, and the copy is why it matters\n // (#2282). The prototype read below goes through a `Proxy`'s trap, so an array\n // can present itself as plain — and the `true` branch SPREADS it, turning a\n // non-bag into a genuine `{0:…,1:…}`. Every shape check downstream then asks\n // what the value IS and finds nothing wrong, because the laundering already\n // happened. `IsArray` unwraps proxies to the target, so it survives the lie.\n //\n // `null` is excluded above because it is the one value that cannot be asked\n // for a prototype at all.\n const proto = getPrototypeOf(bag) as object | null;\n\n return !Array.isArray(bag) && (proto === null || proto === Object.prototype)\n ? { ...bag }\n : bag;\n}\n\n/**\n * Withholds `UNSAFE_KEY` from a bag core BUILT and is about to hand to\n * application code (#1904).\n *\n * `matchPath` parses the query out of the URL itself, and the parser creates\n * that own key deliberately (#855 / #1293) — writing it any other way would\n * swap the parsed object's own prototype. The published channels drop it much\n * further down, at `normalizeChannel`, which leaves two seams ABOVE the drop\n * holding a container core will not publish: a route's `decodeParams`, and\n * every `forwardState` interceptor on the URL direction.\n *\n * ⚑ The reason is the CONSUMER's merge, not core's own write. `putField` stores\n * the name perfectly well; what it cannot make safe is the next hop, where\n * `Object.assign` / a `for…in` copy / `dst[key] = value` reaches the inherited\n * setter and replaces that target's prototype instead of adding an entry —\n * silently, and from a bare URL (`?__proto__` parses to `null`,\n * `?__proto__=1&__proto__=2` to an array; a plain string value is inert). Same\n * rule, same words, as `getDependenciesApi.getAll`: a single read hands back a\n * VALUE, a door like this hands back a CONTAINER someone will merge.\n *\n * ⚠ NOT applied in the parser. Its `defineProperty` write must stay, or the\n * parse itself swaps a prototype — the drop belongs at the door, not at the\n * construction.\n *\n * Returns the input untouched, with no allocation, when the key is absent —\n * which is every ordinary URL, so the common path pays one `hasOwn`.\n */\n/**\n * Remove `UNSAFE_KEY` from an object core JUST allocated for a hand-out (#1957).\n *\n * The sibling of {@link withoutUnsafeKey}, and the split between them is which\n * of the two questions the call site has already answered:\n *\n * - {@link withoutUnsafeKey} is handed a bag it does NOT own, so it gates on\n * `hasOwn` and copies only when it must. That gate is the price of not\n * allocating on a path where the key is almost always absent.\n * - this one is handed the RESULT of a spread core just performed, so there\n * is nothing to copy and nothing to preserve — and the delete is therefore\n * UNCONDITIONAL. `getAll` recorded why (#1823) and the reason generalises:\n * guarding it with `hasOwn` decides nothing (deleting an absent key is a\n * no-op in every observable respect, measured) while putting a re-pointable\n * intrinsic read in front of the one line that neutralises the hazard.\n * Capturing `hasOwn` at module load does not close that either — a shim\n * evaluated BEFORE this module, the ordinary polyfill order, still wins.\n *\n * ⚠ Only ever call this on an object core allocated in the same expression. It\n * MUTATES, and a caller's own bag is not core's to edit.\n *\n * ⚠ That precondition is the CONTRACT and the type cannot express it, so here\n * is what breaking it costs, measured: `delete` on a FROZEN container carrying\n * the key, or on a non-configurable own key, throws\n * `TypeError: Cannot delete property '__proto__'` — every module is strict. An\n * absent key is a no-op even under a freeze, which is why no shipped site can\n * reach the throw by accident (all four hand over a spread made one expression\n * earlier). The throw is the right failure mode rather than a flaw: a silent\n * no-op would leave the key on a published container with nobody the wiser.\n * Not pinned by a test — no public door can hand this a frozen bag, and a\n * functional test may not reach `src` to try.\n *\n * ⚠ The three sites it serves all SPREAD rather than copy key by key, and that\n * is load-bearing (#1852): a spread `[[Define]]`s, while `dst[key] = value`\n * dispatches into whatever `Object.prototype` carries under an ordinary\n * dependency or option name. Using the loop here would turn an already-immune\n * door into a member of that class.\n */\nexport function dropUnsafeKey<T extends object>(fresh: T): T {\n delete (fresh as Record<string, unknown>)[UNSAFE_KEY];\n\n return fresh;\n}\n\n/**\n * One own-enumerable FROZEN copy of a bag the caller owns, named if it throws.\n *\n * ⚑ **The shared half of #2171 and #2172.** Both retire the same decision from\n * #2145 — core stops holding an application container live — at two different\n * doors: `Router`'s option sub-bags and `routesStore`'s route-config tables.\n * The copy rule is one rule, so it lives here rather than twice.\n *\n * ⚑ **The try/catch is the reason this is a function and not a spread.** These\n * reads happen at construction and at registration, and an accessor-backed\n * config is the ordinary lazy spelling — so a throw here is a config fault about\n * a FIELD and is reported as one. `asKey` reports the sibling slot the same way\n * (`Invalid \"queryParams.arrayFormat\": reading it threw`, original as `cause`);\n * naming one field and not the ones beside it would be an asymmetry.\n * `config-aliasing-authority-1958` pins both doors against it.\n *\n * ⚠ Own-enumerable, deliberately — a spread's semantics, which is what\n * `createLimits` and `packages/core/CLAUDE.md` › Supported Input Shapes already\n * specify for these bags. A prototype-chain walk belongs to `queryParams`\n * alone, where layering is the documented feature.\n *\n * ⚠ The parameter is NOT `T extends object`, and that is deliberate rather than\n * loose. Constrained, TypeScript reads the non-object guard below as dead — the\n * declared type carries no primitive and no `null` — and `no-unnecessary-condition`\n * says so. The guard exists because the runtime disagrees with the declared type,\n * which is the entire premise of adopting a caller's value; a constraint that\n * argues the disagreement away would delete the check that handles it.\n *\n * ⚠ `dropUnsafeKey` on the copy, for the reason the freeze one level up gives\n * (#1957): the object is one CORE mints, so the exemption that covered the\n * nested bag — \"it is the caller's, not ours\" — is exactly what these two\n * issues retired. Same treatment `adoptForeignBag` gives a published channel,\n * so every door into a params bag now agrees.\n */\nexport function copyOwnData<T>(field: string, bag: T): T {\n // ⚠ A non-object passes through UNTOUCHED, for the reason the array branch\n // below exists. `{ ...\"abc\" }` is `{ 0: \"a\", 1: \"b\", 2: \"c\" }`, so spreading a\n // string handed to `defaultParams` manufactures a plausible-looking object out\n // of invalid config — and the layer that would have named it sees an object and\n // says nothing. The static type forbids this; the runtime is what actually\n // arrives, which is the whole premise of adopting in the first place.\n if (typeof bag !== \"object\" || bag === null) {\n return bag;\n }\n\n try {\n // ⚠ KIND-preserving, and that is a correctness requirement rather than\n // tidiness. `{ ...[] }` is `{}`, so a plain spread silently turns an array\n // handed to `defaultParams` into an object — and `@real-router/validation-plugin`\n // refuses that field by asking whether it IS an array. Measured: the spread\n // alone moved four cells of that plugin's own coverage table out of \"plugin\n // refuses\" and into \"unreachable\", i.e. core's copy laundered a structurally\n // invalid config past the layer that exists to name it.\n //\n // ⚑ The freeze is HERE rather than at each call site: both callers wanted it,\n // and a third that forgot would hand out a core-minted bag that is writable\n // — the half of the defect that survives copying.\n const copy = Array.isArray(bag) ? [...bag] : { ...bag };\n\n return freeze(dropUnsafeKey(copy)) as T;\n } catch (error) {\n throw new TypeError(\n `[router.constructor] Invalid \"${field}\": reading it threw.`,\n { cause: error },\n );\n }\n}\n\n/**\n * The bag without `UNSAFE_KEY`, or the bag itself when it does not carry one.\n *\n * ⚑ The `hasOwn` gate is what makes this affordable at its two seams — the\n * `forwardState` chain and `matchPath`'s decode — a clean bag is returned BY\n * IDENTITY, so nothing is allocated.\n *\n * ⚠ **Do not rewrite this as a DESCRIPTOR copy.** It is the obvious way to strip\n * a key without invoking the caller's accessors, it was tried, and it is worse\n * on the axis this module exists for: `Object.defineProperties` runs\n * `ToPropertyDescriptor`, which asks `HasProperty` for `get` / `set` / `value` /\n * `writable` — names a data descriptor does not own — so an ambient\n * `Object.prototype.get` makes every copy throw. Measured: `navigate()` then\n * fails silently, the state does not move, and the `TypeError` surfaces only in\n * the fire-and-forget log. Two further costs came with it — the source's\n * `writable` / `configurable` propagate into a copy whose OBJECT is still\n * extensible (so `Object.isFrozen` does not warn a consumer before its merge\n * throws), and a preserved getter means the caller keeps writing into a\n * container the router has already published.\n *\n * ⚠ The read it would have avoided is not core's only one: `normalizeChannel`\n * above reads every key of a caller's bag, so the public doors invoke an\n * accessor either way. What the descriptor form bought was one read out of two.\n */\nexport function withoutUnsafeKey<T extends Record<string, unknown>>(bag: T): T {\n if (!hasOwn(bag, UNSAFE_KEY)) {\n return bag;\n }\n\n const copy: Record<string, unknown> = {};\n\n for (const key of objectKeys(bag)) {\n if (key !== UNSAFE_KEY) {\n // `putField`, not a plain store: the remaining names also come straight\n // out of the URL, so the whole prototype chain is in play for them too.\n putField(copy, key, bag[key]);\n }\n }\n\n return copy as T;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","names":[],"sources":["../../src/helpers.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","names":[],"sources":["../../src/helpers.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA29BgB,aACd,UAAU,4CACV,KAAK,IAAI"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./RouterError-C-p1yaby.js"),t=require("./Router-B4mEOfJE.js"),n=(e=[],n={},r={})=>new t.t(e,n,r),r=Object.freeze,i=new WeakMap,a=e=>{let t=i.get(e);return t||(t=r({navigate:e.navigate,getState:e.getState,isActiveRoute:e.isActiveRoute,canNavigateTo:e.canNavigateTo,subscribe:e.subscribe,subscribeLeave:e.subscribeLeave,isLeaveApproved:e.isLeaveApproved}),i.set(e,t)),t};exports.Router=t.t,exports.RouterError=e.t,exports.UNKNOWN_ROUTE=e.p,exports.constants=e.h,exports.createRouter=n,exports.errorCodes=e.g,exports.events=e._,exports.getNavigator=a,exports.resolveForwardChain=t.v;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Router"],"sources":["../../src/createRouter.ts","../../src/getNavigator.ts"],"sourcesContent":["// packages/core/src/createRouter.ts\n\nimport { Router } from \"./Router\";\n\nimport type { DefaultDependencies, Options, Route } from \"./types\";\n\n/**\n * Creates a new router instance.\n *\n * @param routes - Array of route definitions\n * @param options - Router configuration options\n * @param dependencies - Dependencies to inject into the router\n * @returns A new Router instance\n *\n * @example\n * const router = createRouter([\n * { name: 'home', path: '/' },\n * { name: 'users', path: '/users' },\n * ]);\n *\n * router.start('/');\n */\nexport const createRouter = <\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routes: Route<Dependencies>[] = [],\n options: Partial<Options<Dependencies>> = {},\n dependencies: Dependencies = {} as Dependencies,\n): Router<Dependencies> => {\n return new Router<Dependencies>(routes, options, dependencies);\n};\n","import type { Navigator, DefaultDependencies, Router } from \"./types\";\n\n/** Captured like the deciding seven, but this one BUILDS the guarantee (#2073). */\nconst freeze = Object.freeze;\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 = 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":"
|
|
1
|
+
{"version":3,"file":"index.js","names":["Router"],"sources":["../../src/createRouter.ts","../../src/getNavigator.ts"],"sourcesContent":["// packages/core/src/createRouter.ts\n\nimport { Router } from \"./Router\";\n\nimport type { DefaultDependencies, Options, Route } from \"./types\";\n\n/**\n * Creates a new router instance.\n *\n * @param routes - Array of route definitions\n * @param options - Router configuration options\n * @param dependencies - Dependencies to inject into the router\n * @returns A new Router instance\n *\n * @example\n * const router = createRouter([\n * { name: 'home', path: '/' },\n * { name: 'users', path: '/users' },\n * ]);\n *\n * router.start('/');\n */\nexport const createRouter = <\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routes: Route<Dependencies>[] = [],\n options: Partial<Options<Dependencies>> = {},\n dependencies: Dependencies = {} as Dependencies,\n): Router<Dependencies> => {\n return new Router<Dependencies>(routes, options, dependencies);\n};\n","import type { Navigator, DefaultDependencies, Router } from \"./types\";\n\n/** Captured like the deciding seven, but this one BUILDS the guarantee (#2073). */\nconst freeze = Object.freeze;\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 = 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":"kJAsBa,GAGX,EAAgC,CAAC,EACjC,EAA0C,CAAC,EAC3C,EAA6B,CAAC,IAEvB,IAAIA,EAAAA,EAAqB,EAAQ,EAAS,CAAY,EC1BzD,EAAS,OAAO,OAEhB,EAAQ,IAAI,QAEL,EAGX,GACc,CACd,IAAI,EAAM,EAAM,IAAI,CAAM,EAe1B,OAbK,IACH,EAAM,EAAO,CACX,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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./RouterError-C-p1yaby.js"),t=Object.hasOwn;function n(e,n){if(n.length!==0&&e!=null)for(let r of n){if(!t(e,r))continue;let n;try{n=e[r]}catch{return}if(n!==void 0)return r}}function r(e,t,r,i,o,s){let c=n(r,i);if(c!==void 0)throw TypeError(`[router.${e}] ${a(t,c,typeof o==`function`?o():o,s)}`)}function i(e,t,n,i){r(e,t,n,i,"the `params` bag this call is about to ship — the channel check above it read a different value, so the caller's object answered differently between the two reads")}function a(e,t,n="the `params` argument",r="Pass it in `search` instead"){return`Route "${e}" declares \`${t}\` as a query param, but it was given in ${n} — the path channel. ${r}; the two channels are separate since RFC-4 M2 and the router never moves a key between them.`}const o=e=>e===58||e===42;function s(e){let t=[],n=0;for(let r=0;r<e.length;r+=1)e.charCodeAt(r)===47&&(t.push(e.slice(n,r)),n=r+1);return t.push(e.slice(n)),t}function c(e){let t=e.length;if(t===0)return{kind:`static`,text:``};if(!o(e.charCodeAt(0))){for(let n=0;n<t;n+=1){let r=e.charCodeAt(n);if(r===60||r===62)return{error:`constraint-removed`};if(o(r)&&n+1<t)return{error:`fused-marker`}}return e.charCodeAt(t-1)===63?{error:`name-less`}:{kind:`static`,text:e}}let n=e.charCodeAt(0)===42,r=1;for(;r<t;){let t=e.charCodeAt(r);if(t===60||t===62||t===63)break;r+=1}let i=e.slice(1,r);return i.length===0?{error:`name-less`}:o(i.charCodeAt(i.length-1))?{error:`trailing-marker`}:r<t?e.charCodeAt(r)===63?{error:`optional-removed`}:{error:`constraint-removed`}:n?{kind:`splat`,name:i}:{kind:`param`,name:i}}function l(e){for(let t of s(e)){let e=c(t);if(`error`in e)return e.error}}function u(e){let t=s(e);for(let e=0;e<t.length;e+=1){let n=c(t[e]);if(`error`in n){if(n.error===`optional-removed`){let n=t[e],r=[...t];return r[e]=n.slice(0,n.indexOf(`?`)),{code:`optional-removed`,segment:n,withoutSegment:t.filter((t,n)=>n!==e).join(`/`),requiredForm:r.join(`/`)}}return n.error===`constraint-removed`?{code:`constraint-removed`,segment:t[e]}:void 0}}}const d=/[<>]/u;function f(e){for(let t=0;t<e.length;t+=1){if(e.codePointAt(t)!==63)continue;let n=t+1<e.length?e.codePointAt(t+1):-1;if(n!==-1&&n!==47&&n!==63&&n!==60)return t}return-1}const p=Object.freeze([]),m=Object.freeze({}),h=Object.freeze({urlParams:p,queryParams:p,paramTypeMap:m,pathPattern:``});function g(e,t,n){for(let r of s(e)){if(r.length===0)continue;let e=c(r);`error`in e||e.kind===`static`||(t.push(e.name),n[e.name]=`url`)}}function _(t){let n=[],r=[],i=e.a(),a=f(t);if(a!==-1){let e=t.slice(a+1).split(`&`);for(let t of e){let e=t.trim();e.length>0&&(r.push(e),i[e]=`query`)}t=t.slice(0,a)}return g(t,n,i),v(n,r,e.o(i),t)}function v(e,t,n,r){return{urlParams:e.length===0?p:e,queryParams:t.length===0?p:t,paramTypeMap:e.length===0&&t.length===0?m:n,pathPattern:r}}const y=/^[A-Z_a-z][\w-]*$/,b=/\S/,x=1e4;function S(e,t){if(e===``)throw TypeError(`[router.${t}] Route name cannot be empty`)}function ee(e,t){if(!b.test(e))throw TypeError(`[router.${t}] Route name cannot contain only whitespace`)}function C(e,t){if(e.length>x)throw TypeError(`[router.${t}] Route name exceeds maximum length of ${x} characters`)}function w(e,t){if(e.includes(`.`))throw TypeError(`[router.${t}] Route name "${e}" cannot contain dots. Use children array or { parent } option in addRoute() instead.`)}function te(e,t){if(!y.test(e))throw TypeError(`[router.${t}] Invalid route name "${e}". Name must start with a letter or underscore, followed by letters, numbers, underscores, or hyphens.`)}function T(e,t){return TypeError(`[router.${e}] ${t}`)}function E(e,t,n,r){let i=new Set;for(let a of e){if(i.has(a))throw T(n,`Invalid path for route "${t}": duplicate parameter name ':${a}' in "${r}" (a param name must be unique within a route — the second binding would overwrite the first)`);i.add(a)}}function D(e,t,n,r){for(let i of e)if(d.test(i))throw T(n,`Invalid path for route "${t}": invalid query-param name "${i}" in "${r}" (a query-param name cannot contain '<' or '>' — it would never round-trip; rename the query param)`)}function O(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 k(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 A(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 j(e,t,n,r){if(typeof e!=`string`){let t;throw t=e===null?`null`:Array.isArray(e)?`array`:typeof e,T(n,`Route path must be a string, got ${t}`)}if(e===``)return;if(/\s/.test(e))throw T(n,`Invalid path for route "${t}": whitespace not allowed in "${e}"`);if(!/^([/?~]|[^/]+$)/.test(e))throw T(n,`Route "${t}" has invalid path format: "${e}". Path should start with '/', '~', '?' or be a relative segment.`);if(e.includes(`//`))throw T(n,`Invalid path for route "${t}": double slashes not allowed in "${e}"`);let{pathPattern:i,urlParams:a,queryParams:o}=_(e);E(a,t,n,e),D(o,t,n,e);let s=u(i);if(s!==void 0)throw T(n,A(s,t));let c=l(i);if(c!==void 0)throw T(n,k(c,t,e));if(O(i))throw T(n,`Invalid path for route "${t}": non-ASCII static segment in "${e}" — 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=r&&r.paramMeta.urlParams.length>0;if(e.startsWith(`~`)&&d)throw T(n,`Absolute path "${e}" cannot be used under parent route with URL parameters`)}const M=Object.getOwnPropertyDescriptor,N=Object.keys,P=Object.getPrototypeOf;function F(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 I(e){for(let t of N(e)){let n=M(e,t);if(n&&(n.get||n.set))return!0}return!1}function L(e,t){if(!e||typeof e!=`object`)throw TypeError(`[router.${t}] Route must be an object, got ${F(e)}`);let n=P(e);if(n!==Object.prototype&&n!==null)throw TypeError(`[router.${t}] Route must be a plain object, got ${F(e)}`);if(I(e))throw TypeError(`[router.${t}] Route must not have getters or setters`)}function R(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 z(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 B(e,t){if(typeof e.name!=`string`)throw TypeError(`[router.${t}] Route name must be a string, got ${F(e.name)}`);let n=e.name;S(n,t),ee(n,t),C(n,t),!n.startsWith(`@@`)&&(w(n,t),te(n,t))}function V(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 H(e,t,n){if(V(e,t))throw Error(`[router.${n}] Route "${t}" already exists`)}function U(e,t,n){if(e.has(t))throw Error(`[router.${n}] Duplicate route "${t}" in batch`);e.add(t)}function W(e,t,n,r){let i=t===``?e:V(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 G(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 K(e,t,n,r=``,i,a){L(e,t);let o=e;B(o,t),j(o.path,o.name,t,n),R(o,t),z(o,t);let s=o.name,c=r?`${r}.${s}`:s;n&&c&&H(n,c,t),i&&U(i,c,t);let l=o.path,u=r;if(n&&W(n,u,l,t),a&&G(a,u,l,t),o.children!==void 0){if(!Array.isArray(o.children))throw TypeError(`[router.${t}] Route "${s}" children must be an array, got ${F(o.children)}`);for(let e of o.children)K(e,t,n,c,i,a)}}const q=new WeakMap,J=Symbol.for(`real-router.router`);function Y(e){try{return e?.[J]===!0}catch{return!1}}function X(e){let t=q.get(e);if(!t)throw TypeError(Y(e)?"[real-router] This IS a router, but not one this copy of @real-router/core built. Core identifies a router by object identity, so it is either wrapped in a Proxy (Vue `reactive()` / Pinia — store it with `markRaw`), or your dependency tree holds two copies of @real-router/core — dedupe it to one.":`[real-router] Invalid router instance — not found in internals registry`);return t}function Z(e,t,n,i){r(t,n,i,e.getQueryParams(n))}function Q(t){if(t())throw e.n(new e.t(e.g.ROUTER_DISPOSED))}function ne(e,t){q.set(e,t),Object.defineProperty(e,J,{value:!0})}function $(e,t,n,r){let i=t;for(let t of e){let e=i,n=r===void 0?e:(...t)=>r(e(...t));i=(...e)=>t(n,...e)}return i(...n)}const re={start:`start`,forwardState:`forwardState`};function ie(e,t,n){return((...r)=>{let i=n.get(e);return!i||i.length===0?t(...r):$(i,t,r)})}function ae(e,t,n,r,i){return(a,o,s)=>{let c=n.get(e);return!c||c.length===0?t(a,o,s):$(c,t,i(a,o,s),r)}}Object.defineProperty(exports,"_",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return ne}}),Object.defineProperty(exports,"b",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,"d",{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,"f",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"g",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"h",{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return X}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return L}}),Object.defineProperty(exports,"m",{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return ie}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return Q}}),Object.defineProperty(exports,"p",{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return ae}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return Z}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return re}}),Object.defineProperty(exports,"u",{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,"v",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"y",{enumerable:!0,get:function(){return n}});
|
|
2
|
+
//# sourceMappingURL=internals-NAYRbkal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internals-NAYRbkal.js","names":["LT","QUESTION","SLASH","emptyRecord","publishRecord","freezeThrownError","RouterError","errorCodes"],"sources":["../../src/channels/guard.ts","../../src/engine/path-matcher/parseSegment.ts","../../src/engine/path-matcher/buildParamMeta.ts","../../src/engine/validation/route-name.ts","../../src/engine/validation/routes.ts","../../src/engine/validation/route-batch.ts","../../src/internals.ts"],"sourcesContent":["// packages/core/src/channels/guard.ts\n\nimport type { Params } from \"../types\";\n\n/**\n * Intrinsics captured at module load: `hasOwn`.\n *\n * ⚑ A guard is only as strong as the intrinsic it reads WHEN IT RUNS, and an\n * application can re-point any of these AFTER boot — which is what this closes.\n * Measured on the uncaptured form: one naive `Object.hasOwn` polyfill walked\n * straight through five sibling readers while the single captured guard held.\n *\n * ⚠ The limit of what capture buys — and the shim order that defeats it — is\n * stated once, in `guards.ts`. Not restated here (#2091).\n */\nconst hasOwn = Object.hasOwn;\n\n/**\n * THE predicate of the always-on channel guard: the first key the caller put in\n * the PATH bag while the route declares it as a QUERY param, or `undefined`\n * when the bag is channel-correct.\n *\n * A DETECTOR, not a normaliser — the key is never moved. There is no stage ②\n * and no `separateChannels`: channel-correctness is the producer's contract,\n * not a repair the pipeline performs behind everyone's back.\n *\n * Scans `queryNames` (a route's declared query names — small, cached) rather\n * than the bag, so there is no `Object.keys` allocation, and short-circuits on\n * a route with no query declarations, which is the common case.\n *\n * `undefined` is absence on both sides (#1550 / #1551), so an\n * `undefined`-valued key is NOT a mis-channel: it is the documented removal\n * marker `persistent-params` relies on, and it never reaches a built state\n * anyway. A name that also occupies a path slot (`/items/:id?id`) is absent\n * from `queryNames` by construction (#843 / #1549 carve-out), so the collision\n * form is legitimately path-owned and passes.\n *\n * @internal\n */\nexport function findMisChanneledKey(\n params: Params | undefined,\n queryNames: readonly string[],\n): string | undefined {\n // ⚠ An absent BAG has two spellings, and `== null` is the intent: both\n // nullish values mean \"there is no bag\". `navigate(name, null)` is supported\n // runtime input while the signature admits only `Params | undefined`, so\n // testing `=== undefined` alone lets `Object.hasOwn` perform `ToObject` — and\n // this DIAGNOSTIC becomes the thing that throws (#1822).\n //\n // ⚑ In the predicate rather than in `assertChannelCorrect`: `canNavigateTo`\n // and `navigateToState` reach it directly, and neither may throw — the first\n // answers on the render path, the second rejects because URL plugins call it\n // from popstate handlers.\n if (queryNames.length === 0 || params == null) {\n return undefined;\n }\n\n for (const key of queryNames) {\n if (!hasOwn(params, key)) {\n continue;\n }\n\n let value: unknown;\n\n try {\n value = params[key];\n } catch {\n // A DIAGNOSTIC must never become the thing that throws. The bag may be\n // backed by accessors (a Proxy, a getter, a framework's reactive object),\n // and reading one here happens EARLIER than any consumer would have read\n // it — so an accessor that throws would surface from the guard instead of\n // from the code that actually needed the value, moving the origin of an\n // existing failure. Treat it as \"nothing to report\" and let the real\n // consumer hit the same accessor exactly as it did before.\n return undefined;\n }\n\n if (value !== undefined) {\n return key;\n }\n }\n\n return undefined;\n}\n\n/**\n * THE centralized channel check — the single place a mis-channelled bag is\n * refused, wherever it came from.\n *\n * A key the route declares with `?`, sitting in the PATH bag, is a producer's\n * mistake — the producer named the route, so it knows the declaration — and the\n * router SAYS so instead of quietly moving the field into the other object.\n * Moving it would be invisible: the caller keeps believing their bag is the one\n * that shipped, and two producers of the SAME intent can disagree about which\n * channel a key ended up in.\n *\n * `source` names WHOSE bag is wrong, which is the whole diagnostic value at a\n * seam: the caller's argument, a `forwardState` interceptor's return, or the\n * output of a route's own `decodeParams`. It takes a THUNK as well as a string\n * because the seam sits on the navigation hot path — a source that has to be\n * composed (naming the route a chain forwarded from) must not build its string\n * on every call just to discard it on the 99.99% of calls that pass.\n *\n * @internal\n */\nexport function assertChannelCorrect(\n method: string,\n routeName: string,\n params: Params | undefined,\n queryNames: readonly string[],\n source?: string | (() => string),\n remedy?: string,\n): void {\n const key = findMisChanneledKey(params, queryNames);\n\n if (key !== undefined) {\n throw new TypeError(\n `[router.${method}] ${misChanneledKeyMessage(\n routeName,\n key,\n typeof source === \"function\" ? source() : source,\n remedy,\n )}`,\n );\n }\n}\n\n/**\n * The channel verdict, re-asked on the bag that actually SHIPS (#1927).\n *\n * Every position above a producer reads the CALLER's object — P1 at the door,\n * the `forwardState` seam, the `decodeParams` boundary. The canonical bag is\n * then built by a SECOND read of that same object, and between the two it still\n * belongs to the application: a Proxy, a framework's reactive object, a plain\n * getter. A bag answering `undefined` while the guards look — the documented\n * removal marker, correctly waved through — commits a declared query name into\n * `state.params` while `state.path` prints without it. `read-count-authority`\n * owns how many times each door reads a caller-owned key; this file does not\n * restate the counts, because a number written twice goes stale twice.\n *\n * The SAME predicate, one position later, on core's own object. A canonical bag\n * has no accessors, so this verdict cannot be overtaken: the invariant is\n * structural rather than maintained by care.\n *\n * ⚑ Called by the four doors that PUBLISH a State, and by no one else. The two\n * render-path predicates — `buildPath` (a string) and `isActiveRoute` (a boolean)\n * — ship no value for a verdict to vouch for, and #1572 / #1581 record that they\n * are deliberately not instrumented: detecting there is fine, throwing is not.\n * They express that the way they always have, by not calling.\n *\n * ⚠ `canNavigateTo` produces a State too and is deliberately NOT here — measured,\n * not assumed. It discards the state, so nothing ships for a verdict to vouch\n * for, and every bag this check would refuse it already answers `false` to: the\n * seam sees the same key one read earlier. Adding the call changed no answer for\n * any blindness from 0 to 3 reads, while costing one predicate call on the render\n * path, which runs per `<Link>` per render.\n *\n * ⚑ On a canonical bag the `value !== undefined` arm is vacuous — those keys are\n * already dropped — so `undefined` stays the removal marker (#1550 / #1551).\n *\n * ⚑ The declarations are the RESOLVED route's, which is why callers pass\n * `canonical.name`: the bag came out of the chain, and the resolved route owns\n * the URL that gets printed — the same authority the seam names.\n */\nexport function assertShippedChannelCorrect(\n method: string,\n routeName: string,\n shipped: Params,\n queryNames: readonly string[],\n): void {\n assertChannelCorrect(\n method,\n routeName,\n shipped,\n queryNames,\n \"the `params` bag this call is about to ship — the channel check above it read a different value, so the caller's object answered differently between the two reads\",\n );\n}\n\n/**\n * The guard's actionable message. One builder for every position, so the\n * wording a user sees does not depend on which door they came through — the\n * facade's `TypeError`, the seam's, the decoder's, and `navigateToState`'s\n * `RouterError(WRONG_CHANNEL)`, which needs the wording WITHOUT the throw and is\n * why this is a separate function from {@link assertChannelCorrect}.\n *\n * @internal\n */\nexport function misChanneledKeyMessage(\n routeName: string,\n key: string,\n source = \"the `params` argument\",\n remedy = \"Pass it in `search` instead\",\n): string {\n return `Route \"${routeName}\" declares \\`${key}\\` as a query param, but it was given in ${source} — the path channel. ${remedy}; the two channels are separate since RFC-4 M2 and the router never moves a key between them.`;\n}\n","/**\n * Canonical route-segment tokenizer.\n *\n * The single owner of \"where does a name / marker / constraint end\" for ONE\n * path segment (post-`/`-split). It replaces the five name-boundary compositions\n * of `PARAM_NAME_PATTERN` currently spread across `buildParamMeta` (L1),\n * `registration` (L2 build + L3 trie), and `route-tree`'s validation gate (L4),\n * so those layers can never disagree on a boundary (the gate↔backstop drift\n * class — #858 / #1050 / #1150 / #1311 / #1149 / #1324).\n *\n * A single left-to-right `charCodeAt` scan produces either a token tuple or a\n * typed error. Grammar — **3 tokens only** (`static | :param | *splat`); the\n * grammar has no optional `:x?` or `<re>` constraint forms. Any `<`/`>` or a\n * post-name `?` in the path is a *registration error* carrying a replacement\n * recipe (`optional-removed` / `constraint-removed`), not a token:\n * 1. Leading `:`/`*` → param/splat; otherwise `static` (a marker glued *after* a\n * static prefix ⇒ `fused-marker`; any `<`/`>` (a former constraint) ⇒\n * `constraint-removed`; a trailing `?` on a marker-less segment ⇒ `name-less`\n * — the modifier has no param name, #1241 / `/faq?`).\n * 2. name = any char except `<`/`?` (no `/` remains inside a segment); a name\n * ending in a bare `:`/`*` ⇒ `trailing-marker` (#1324). A *mid* marker stays\n * a name char — `:a:b` → name `a:b`, preserved.\n * 3. empty name ⇒ `name-less` (#858).\n * 4. a `<` after the name (a former `<re>` constraint) ⇒ `constraint-removed`.\n * 5. a post-name `?` (a former optional modifier, on `:param` or `*splat`) ⇒\n * `optional-removed`.\n *\n * @module parseSegment\n */\n\n/* eslint-disable unicorn/prefer-code-point, unicorn/prefer-includes-over-repeated-comparisons, sonarjs/cognitive-complexity -- charCodeAt code-unit scan + a single inlined branchy pass are this RFC's char-scan perf basis (§9); the same deliberate choices as registration/trie.ts hasNonAsciiSegment (#1285) and SegmentMatcher's inlined #traverseFrom. A `[LT,GT,QUESTION].includes(code)` boundary check would allocate an array literal per scanned char. Markers compared are ASCII (`:` `*` `<` `>` `?`, < 0x80). */\n\n/** A successfully tokenized segment (3-token grammar: `static | :param | *splat`). */\nexport type SegmentTokens =\n | { readonly kind: \"static\"; readonly text: string }\n | { readonly kind: \"param\"; readonly name: string }\n | { readonly kind: \"splat\"; readonly name: string };\n\n/** Grammar-shape rejections, each mirroring a registration guard. */\nexport type SegmentErrorCode =\n | \"name-less\" // #858 — a marker with no name\n | \"trailing-marker\" // #1324 — a param name ending in a bare `:`/`*`\n | \"fused-marker\" // #1050 — a marker glued after a static prefix\n | \"optional-removed\" // M1 — a `:x?`/`*x?` optional modifier (removed; two sibling routes)\n | \"constraint-removed\"; // M1 — a `<re>` constraint or stray `<`/`>` (removed; validate in a guard)\n\nexport interface SegmentError {\n readonly error: SegmentErrorCode;\n}\n\nconst COLON = 58; // :\nconst STAR = 42; // *\nconst LT = 60; // <\nconst GT = 62; // >\nconst QUESTION = 63; // ?\nconst SLASH = 47; // /\n\nconst isMarker = (code: number): boolean => code === COLON || code === STAR;\n\n/**\n * Splits a path into its `/`-delimited segments. A plain `/`-split (M1): the\n * 3-token grammar has no `<...>` constraint whose body could legally contain a\n * `/`, so no constraint-awareness is needed — a stray `<`/`>` is a\n * `constraint-removed` error, caught per segment by `parseSegment`. This is the\n * **segmentation** half of the path-grammar unification: `parseSegment` owns the\n * per-segment grammar, `splitPathSegments` owns where a segment begins and ends.\n *\n * @param path - a route path (query already stripped by the caller)\n * @returns the segments in order, including empty leading/trailing/`//` segments\n * (the caller skips empties, matching the current behaviour)\n */\nexport function splitPathSegments(path: string): string[] {\n const segments: string[] = [];\n let start = 0;\n\n for (let i = 0; i < path.length; i += 1) {\n if (path.charCodeAt(i) !== SLASH) {\n continue;\n }\n\n segments.push(path.slice(start, i));\n start = i + 1;\n }\n\n segments.push(path.slice(start));\n\n return segments;\n}\n\n/**\n * Tokenizes a single path segment (already split on `/`).\n *\n * @param segment - one `/`-delimited segment of a route path\n * @returns the segment's tokens, or a typed grammar error\n */\nexport function parseSegment(segment: string): SegmentTokens | SegmentError {\n const length = segment.length;\n\n if (length === 0) {\n return { kind: \"static\", text: \"\" };\n }\n\n // ---- static segment (no leading marker) -------------------------------\n if (!isMarker(segment.charCodeAt(0))) {\n for (let i = 0; i < length; i += 1) {\n const code = segment.charCodeAt(i);\n\n // A `<`/`>` (a former `<re>` constraint or a stray delimiter) is no longer\n // grammar — M1 removed constraints. Reject with the constraint recipe.\n if (code === LT || code === GT) {\n return { error: \"constraint-removed\" };\n }\n\n // A marker glued after a static prefix is extracted as a param by build/meta\n // but compiled as a static literal by the trie (#1050) — reject it as fused.\n // A marker ENDING the segment (a static ending in `:`/`*` — `/a:`, `/a*`, F2)\n // is NOT fused: caught by `i + 1 < length` being false. Every other following\n // char is fused — including a `?` (`a:?`): that shape never reaches the\n // tokenizer through a real path (a `?` after a bare marker is not a valid\n // `:name?` form, so the query mask strips it before `/`-segmentation), so a\n // direct call correctly reports fused-marker. (`a<`/`a>` already returned\n // `constraint-removed` above, so no `<`-follows exception is needed here.)\n if (isMarker(code) && i + 1 < length) {\n return { error: \"fused-marker\" };\n }\n }\n\n // A trailing `?` is a former optional modifier; on a marker-less segment (no\n // param name) it is a modifier-with-no-name — name-less (#858/#1241, `/faq?`),\n // NOT `optional-removed` (there is no param to route to two siblings). The\n // backstop rejects it by the SAME rule: `processSegment` asks this tokenizer\n // for the segment's kind (#1998). Owning the `?` here (not\n // only in the marker branch) is what lets the gate and backstop agree on it —\n // otherwise the gate reads `faq?` as a valid static (#1324 §4).\n if (segment.charCodeAt(length - 1) === QUESTION) {\n return { error: \"name-less\" };\n }\n\n return { kind: \"static\", text: segment };\n }\n\n const splat = segment.charCodeAt(0) === STAR;\n\n // ---- name: up to the first `<`/`>` (former constraint delimiter, reserved —\n // B1.3) or `?` (former optional). A segment holds no `/`. -----------------\n let cursor = 1;\n\n while (cursor < length) {\n const code = segment.charCodeAt(cursor);\n\n if (code === LT || code === GT || code === QUESTION) {\n break;\n }\n\n cursor += 1;\n }\n\n const name = segment.slice(1, cursor);\n\n if (name.length === 0) {\n return { error: \"name-less\" }; // #858\n }\n\n if (isMarker(name.charCodeAt(name.length - 1))) {\n return { error: \"trailing-marker\" }; // #1324\n }\n\n // ---- former constraint / optional modifiers (removed in M1) ------------\n // The name scan stops at the first `<`/`>` or `?`. Either is a form removed\n // in M1: a `<re>` constraint (also a stray `<`/`>` — B1.3), or a `:x?`/`*x?`\n // optional. Only `?` is the optional; `<`/`>` are the constraint recipe.\n // Reject with the matching replacement recipe rather than tokenize it.\n if (cursor < length) {\n return segment.charCodeAt(cursor) === QUESTION\n ? { error: \"optional-removed\" }\n : { error: \"constraint-removed\" }; // LT or GT\n }\n\n return splat ? { kind: \"splat\", name } : { kind: \"param\", name };\n}\n\n/**\n * Returns the first per-segment grammar error in a path, or `undefined` if every\n * segment tokenizes cleanly.\n *\n * The **validation-facing** entry over the tokenizer: `route-tree`'s\n * `validateRoutePath` calls this instead of re-running its own split+parse loop,\n * so the gate and the matcher's own grammar cannot drift (#1324) and the loop\n * stays single-sourced here — the tokenizer primitives (`parseSegment`,\n * `splitPathSegments`) need not leak into the package's public surface. An empty\n * segment tokenizes as `static` (never an error), so leading/trailing/`//`\n * empties are skipped naturally.\n *\n * @param path - a route path (query already stripped by the caller)\n * @returns the first `SegmentErrorCode` (scanned left to right), or `undefined`\n */\nexport function findSegmentGrammarError(\n path: string,\n): SegmentErrorCode | undefined {\n for (const segment of splitPathSegments(path)) {\n const token = parseSegment(segment);\n\n if (\"error\" in token) {\n return token.error;\n }\n }\n\n return undefined;\n}\n\n/** A removed-form (M1) match, describing the offending segment and — for an\n * optional — the two sibling paths that replace it (path without the optional\n * segment + path with the param made required). The route-tree gate uses this to\n * build a route-contextual replacement recipe; the matcher backstop uses only the\n * error code (a shorter, path-free recipe). */\nexport type RemovedForm =\n | {\n readonly code: \"optional-removed\";\n readonly segment: string;\n readonly withoutSegment: string;\n readonly requiredForm: string;\n }\n | { readonly code: \"constraint-removed\"; readonly segment: string };\n\n/**\n * The rich (route-tree gate) view over the tokenizer for a removed form: finds\n * the first `optional-removed` / `constraint-removed` segment and, for an\n * optional, computes its two replacement sibling paths from the ACTUAL path\n * (dropping the segment → without-form; dropping the trailing `?` → required\n * form). Returns `undefined` if no removed form is present (the gate then uses\n * `findSegmentGrammarError` for a surviving grammar rejection).\n *\n * @param path - a route path (query already stripped by the caller)\n */\nexport function describeRemovedForm(path: string): RemovedForm | undefined {\n const segments = splitPathSegments(path);\n\n for (let i = 0; i < segments.length; i += 1) {\n const token = parseSegment(segments[i]);\n\n if (!(\"error\" in token)) {\n continue;\n }\n\n // First error wins (mirrors `findSegmentGrammarError`): describe it ONLY if\n // it is a removed form, else return undefined so the caller falls to the\n // surviving-code message — this keeps the gate's reason in lockstep with the\n // matcher backstop's first-error verdict.\n if (token.error === \"optional-removed\") {\n const segment = segments[i];\n const required = [...segments];\n\n // Drop the `?` optional modifier AND everything after it (the tokenizer\n // stopped the name at the first `?`, so it is the modifier). Using the `?`\n // index — not a blind `slice(0, -1)` — keeps the required sibling VALID for\n // a reverse/compound form whose `?` is not the last char: `:b?<x>` → `:b`\n // (not `:b?<x`), `:id??` → `:id` (not `:id?`). #1516\n required[i] = segment.slice(0, segment.indexOf(\"?\"));\n\n return {\n code: \"optional-removed\",\n segment,\n withoutSegment: segments.filter((_, j) => j !== i).join(\"/\"),\n requiredForm: required.join(\"/\"),\n };\n }\n\n return token.error === \"constraint-removed\"\n ? { code: \"constraint-removed\", segment: segments[i] }\n : undefined;\n }\n\n return undefined;\n}\n","/**\n * Route Parameter Metadata Extraction.\n *\n * Extracts parameter metadata from route path patterns without requiring\n * a full path-parser instance. Replaces parser.urlParams/queryParams.\n *\n * @module buildParamMeta\n */\n\nimport { parseSegment, splitPathSegments } from \"./parseSegment\";\nimport { emptyRecord, publishRecord } from \"../../utils/ingest\";\n\nimport type { ParamMeta } from \"./types\";\n\n/**\n * A query-param NAME may not contain `<`/`>` (#1242 §5.1) — a constraint\n * delimiter leaked into the query via a reverse-order modifier typo (`/a/:b?<c>`\n * parses the `?` as the query start, making `<c>` the query name). Consumed by\n * the route-tree gate and the `registerTree` backstop; relocated here from the\n * deleted `constraint-grammar.ts` when M1 removed constraints (query-param name\n * validation is a query concern, and this module owns query extraction).\n */\nexport const INVALID_QUERY_NAME_RGX = /[<>]/u;\n\nconst QUESTION = 0x3f; // ?\nconst SLASH = 0x2f; // /\nconst LT = 0x3c; // <\n\n/**\n * Locates the query separator `?` in a route path — the FIRST `?` whose tail is\n * non-empty and does not begin with `/`, `?`, or `<` (M1 §3.3). The 3-token\n * grammar leaves `?` a single role (there is no optional modifier and no\n * constraint body to hide one), so no length-preserving mask is needed. The three\n * excluded tails keep a REMOVED form in the path part, where `parseSegment`\n * rejects it with a recipe instead of mis-reading it as a query declaration:\n * - end-of-string (`/:id?`) and `/` (`/:id?/edit`) → a bare `:x?` optional;\n * - `?` (`/:id??tab`) → the leading `?` is the optional, the later `?` the query;\n * - `<` (`/a/:b?<x>`) → a reverse-order `:b?<x>` (optional then a former constraint).\n *\n * @param path - a route path\n * @returns the index of the query separator, or -1 if there is none\n */\nfunction findQuerySeparator(path: string): number {\n for (let i = 0; i < path.length; i += 1) {\n if (path.codePointAt(i) !== QUESTION) {\n continue;\n }\n\n // `next` is the code point after the `?`, or the `-1` sentinel at end-of-string.\n // The `-1` sentinel is the SOLE end-of-string guard, and a separate\n // `next !== undefined` conjunct would be dead: the ternary bounds the index,\n // so `codePointAt` never returns `undefined`; the `!` is a type assertion,\n // not a runtime branch, so it keeps the scan at 100% coverage. Mirrors\n // `#scanPath`.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- ternary-bounded in-range index; codePointAt is defined\n const next = i + 1 < path.length ? path.codePointAt(i + 1)! : -1;\n\n if (next !== -1 && next !== SLASH && next !== QUESTION && next !== LT) {\n return i;\n }\n }\n\n return -1;\n}\n\n/**\n * Builds parameter metadata from a route path pattern.\n *\n * Extracts URL parameters, query parameters, and splat parameters\n * from the path pattern string.\n *\n * @param path - Route path pattern (e.g., \"/users/:id/posts/:postId?q\")\n * @returns Parameter metadata object\n *\n * @example\n * ```typescript\n * buildParamMeta(\"/users/:id\")\n * // → { urlParams: [\"id\"], queryParams: [], paramTypeMap: { id: \"url\" } }\n *\n * buildParamMeta(\"/search?q&page\")\n * // → { urlParams: [], queryParams: [\"q\", \"page\"],\n * // paramTypeMap: { q: \"query\", page: \"query\" } }\n *\n * buildParamMeta(\"/files/*path\")\n * // → { urlParams: [\"path\"], queryParams: [], paramTypeMap: { path: \"url\" } }\n * ```\n */\n// Shared frozen sentinels for the common no-params case — avoid a fresh empty\n// array/object per route (#1009). ParamMeta fields are Readonly*; match/build\n// only read them, and computeCaches' Object.freeze on the arrays/object is a\n// no-op on an already-frozen shared instance.\nconst EMPTY_PARAM_NAMES: readonly string[] = Object.freeze([]);\nconst EMPTY_PARAM_TYPE_MAP: Readonly<Record<string, \"url\" | \"query\">> =\n Object.freeze({});\n\n// Whole-meta shared sentinel for the fully-static case: every collection is a\n// #1009 sentinel AND pathPattern degenerates to the input path itself (no query\n// to strip), so the wrapper carries zero per-route information. The RETAINING\n// caller (route-tree's computeCaches) swaps a matching fresh result for this\n// instance — buildParamMeta itself keeps returning fresh objects so the\n// validation gate can read the real pathPattern of arbitrary input paths.\n// `pathPattern` is \"\" here; the one stored-meta reader (`registerNode`) falls\n// back to `node.path` on identity match.\nexport const EMPTY_PARAM_META: ParamMeta = Object.freeze({\n urlParams: EMPTY_PARAM_NAMES,\n queryParams: EMPTY_PARAM_NAMES,\n paramTypeMap: EMPTY_PARAM_TYPE_MAP,\n pathPattern: \"\",\n});\n\n/**\n * Extracts URL params (including splats) from a path's segments into the given accumulators\n * via the canonical `parseSegment` tokenizer. Split out of `buildParamMeta` so\n * the builder stays under the cognitive-complexity budget. A malformed segment\n * (token errors) or a `static` segment contributes nothing — a malformed route is\n * rejected downstream before it compiles, so its meta is moot.\n */\nfunction collectUrlParams(\n path: string,\n urlParams: string[],\n paramTypeMap: Record<string, \"url\" | \"query\">,\n): void {\n for (const segment of splitPathSegments(path)) {\n if (segment.length === 0) {\n continue;\n }\n\n const token = parseSegment(segment);\n\n if (\"error\" in token || token.kind === \"static\") {\n continue;\n }\n\n urlParams.push(token.name);\n paramTypeMap[token.name] = \"url\";\n }\n}\n\nexport function buildParamMeta(path: string): ParamMeta {\n const urlParams: string[] = [];\n const queryParams: string[] = [];\n const paramTypeMap = emptyRecord<\"url\" | \"query\">();\n\n // Locate the real query separator (M1 §3.3: first `?` whose tail is not a\n // former optional/reverse form).\n const separator = findQuerySeparator(path);\n\n if (separator !== -1) {\n const queryString = path.slice(separator + 1);\n const params = queryString.split(\"&\");\n\n for (const param of params) {\n const paramName = param.trim();\n\n if (paramName.length > 0) {\n queryParams.push(paramName);\n paramTypeMap[paramName] = \"query\";\n }\n }\n\n path = path.slice(0, separator);\n }\n\n collectUrlParams(path, urlParams, paramTypeMap);\n\n return shareEmptyCollections(\n urlParams,\n queryParams,\n publishRecord(paramTypeMap),\n path,\n );\n}\n\n// #1009: swap each freshly-built empty collection for a shared frozen sentinel\n// — factored out of buildParamMeta so the hot builder stays under the cognitive-\n// complexity budget. match/build only read these (Readonly*), and computeCaches'\n// Object.freeze is a no-op on an already-frozen shared instance.\nfunction shareEmptyCollections(\n urlParams: string[],\n queryParams: string[],\n paramTypeMap: Record<string, \"url\" | \"query\">,\n pathPattern: string,\n): ParamMeta {\n return {\n urlParams: urlParams.length === 0 ? EMPTY_PARAM_NAMES : urlParams,\n queryParams: queryParams.length === 0 ? EMPTY_PARAM_NAMES : queryParams,\n paramTypeMap:\n urlParams.length === 0 && queryParams.length === 0\n ? EMPTY_PARAM_TYPE_MAP\n : paramTypeMap,\n pathPattern,\n };\n}\n","/**\n * The route-name rules, one named predicate each — the name-side counterpart to\n * {@link validateRoutePath} in `./routes`.\n *\n * Two layers apply them, and they apply different subsets. Bare-core\n * registration (`namespaces/RoutesNamespace/routesStore.ts`) applies the dotted\n * rule on every door; {@link validateRoute} — which core exports for\n * `@real-router/validation-plugin` and never calls itself — applies all of\n * them.\n *\n * ⚑ One owner per rule is the point of this file: putting a rule on the live\n * path is a CALL, never a second copy of its message (#2035).\n */\n\n/**\n * Route names are ASCII — a letter or underscore, then letters, digits,\n * underscores or hyphens.\n */\nconst ROUTE_NAME_PATTERN = /^[A-Z_a-z][\\w-]*$/;\n\n/**\n * Matches when the name carries at least one non-whitespace character.\n */\nconst HAS_NON_WHITESPACE = /\\S/;\n\n/**\n * Maximum route name length, bounding DoS and performance risk.\n */\nconst MAX_ROUTE_NAME_LENGTH = 10_000;\n\n/**\n * Refuses `{ name: \"\" }`.\n */\nexport function assertRouteNameNotEmpty(\n name: string,\n methodName: string,\n): void {\n if (name === \"\") {\n throw new TypeError(`[router.${methodName}] Route name cannot be empty`);\n }\n}\n\n/**\n * Refuses a name built only of whitespace.\n */\nexport function assertRouteNameNotWhitespaceOnly(\n name: string,\n methodName: string,\n): void {\n if (!HAS_NON_WHITESPACE.test(name)) {\n throw new TypeError(\n `[router.${methodName}] Route name cannot contain only whitespace`,\n );\n }\n}\n\n/**\n * Refuses a name longer than {@link MAX_ROUTE_NAME_LENGTH}.\n */\nexport function assertRouteNameWithinLength(\n name: string,\n methodName: string,\n): void {\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\n/**\n * Refuses a BARE route name carrying a dot — `{ name: \"users.view\" }` where the\n * nesting must be spelled with `children` or `{ parent }` (#1763).\n *\n * ⚠ Carries no \"@@\" exemption. {@link validateRoute} returns early on a\n * reserved name before reaching this predicate, and bare-core registration\n * refuses one outright, so neither caller needs one.\n */\nexport function assertNoDottedRouteName(\n name: string,\n methodName: string,\n): void {\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\n/**\n * Refuses a name outside {@link ROUTE_NAME_PATTERN}.\n */\nexport function assertRouteNameMatchesPattern(\n name: string,\n methodName: string,\n): void {\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","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. ⚠ With\n * a hole, stated as its CAUSE rather than as a list of shapes, because the list\n * is longer than it looks: that backstop reads the names off `buildParamSlots`,\n * and #1568's finality rule drops a NON-final splat before it becomes a slot.\n * So the rule is about FINALITY, not about levels: **a collision is admitted iff\n * one of the colliding bindings is a NON-final splat.** Measured, 8/8 — admitted:\n * `/p/*x/:x`, `/p/*x/*x`, and the cross-level `*`+`:` and `*`+`*` (an ancestor's\n * marker is never final, since a descendant path follows it); refused:\n * `/p/:x/*x`, `/z/:x/:x`, and the cross-level `:`+`*` and `:`+`:`. The same cause\n * opens a door at the ROOT, which is an ancestor like any other:\n * `setRootPath(\"/app/:x\")` + a route `/:x/e` throws, `setRootPath(\"/app/*x\")` +\n * the same route registers. Nothing is bound twice in any of them (the dropped\n * splat binds nothing), and since #1975 the surviving `:x` takes the param\n * encoder, so they round-trip instead of needing a refusal.\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","/**\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 {\n assertNoDottedRouteName,\n assertRouteNameMatchesPattern,\n assertRouteNameNotEmpty,\n assertRouteNameNotWhitespaceOnly,\n assertRouteNameWithinLength,\n} from \"./route-name\";\nimport { validateRoutePath } from \"./routes\";\n\nimport type { RouteDefinition, RouteTree } from \"../types\";\n\n/**\n * Intrinsics captured at module load: `getOwnPropertyDescriptor`, `objectKeys`, `getPrototypeOf`.\n *\n * ⚑ A guard is only as strong as the intrinsic it reads WHEN IT RUNS, and an\n * application can re-point any of these AFTER boot — which is what this closes.\n * Measured on the uncaptured form: one naive `Object.hasOwn` polyfill walked\n * straight through five sibling readers while the single captured guard held.\n *\n * ⚠ The limit of what capture buys — and the shim order that defeats it — is\n * stated once, in `guards.ts`. Not restated here (#2091).\n */\nconst getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nconst objectKeys = Object.keys;\nconst getPrototypeOf = Object.getPrototypeOf;\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 objectKeys(obj)) {\n const descriptor = 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 * ⚑ Every question here is about the OBJECT — its type, its prototype, its\n * descriptors — never about a value it carries, and that is what decides WHERE\n * it may run (#1911). A snapshot answers all three the same way whatever the\n * caller handed over, because a spread produces a plain, accessor-free object.\n * So this has to see the CALLER's value, which is why `guardRouteStructure`\n * calls it above `snapshotRouteBatch` on every registration door.\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 */\nexport function 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 = 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 * ⚠ The string check stays inline rather than joining `./route-name`: core\n * carries two wordings for it — this one reports `getTypeDescription`, and\n * `assertNoInternalRouteName` reports bare `typeof` to mirror the plugin — so\n * sharing one predicate would change a message rather than move it.\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 assertRouteNameNotEmpty(name, methodName);\n assertRouteNameNotWhitespaceOnly(name, methodName);\n assertRouteNameWithinLength(name, methodName);\n\n // System routes bypass the spelling rules (e.g., @@router/UNKNOWN_ROUTE).\n if (name.startsWith(\"@@\")) {\n return;\n }\n\n assertNoDottedRouteName(name, methodName);\n assertRouteNameMatchesPattern(name, methodName);\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","import { assertChannelCorrect } from \"./channels\";\nimport { errorCodes } from \"./constants\";\nimport { RouterError, freezeThrownError } from \"./RouterError\";\n\nimport type { DependenciesStore } from \"./dependenciesStore\";\nimport type { RouteTree } from \"./engine\";\nimport type { RoutesStore } from \"./namespaces/RoutesNamespace\";\nimport type { RouteResolver } from \"./pipeline\";\nimport type { AdoptedOrigins, Router as RouterClass } from \"./Router\";\nimport type {\n AnyOptions,\n ContextNamespaceClaim,\n DefaultDependencies,\n EventName,\n LoggerConfig,\n NavigationOptions,\n Options,\n Params,\n Plugin,\n Router as RouterInterface,\n RouterLogger,\n RouteTreeState,\n SearchParams,\n SerializedRouterState,\n SimpleState,\n State,\n TreeChangedEvent,\n Unsubscribe,\n EventMethodMap,\n InterceptableMethodMap,\n PluginFactory,\n} from \"./types\";\nimport type { Limits } from \"./types/internal\";\nimport type { RouterValidator } from \"./types/RouterValidator\";\n\nexport interface RouterInternals<\n D extends DefaultDependencies = DefaultDependencies,\n> {\n readonly makeState: <\n P extends Params = Params,\n S extends SearchParams = SearchParams,\n >(\n name: string,\n params?: P,\n search?: S,\n path?: string,\n ) => State<P, S>;\n\n /**\n * Per-segment param-source map for a route name (`{ segment: { param: \"url\" |\n * \"query\" } }`), read from the live matcher — the ownership channel for\n * `getTransitionPath` (RFC-4 M2 / #1548, replaced the removed per-State\n * `stateMetaStore` WeakMap). `undefined` when the name is not in the tree.\n */\n readonly getMetaForState: (\n name: string,\n ) => Record<string, Record<string, \"url\" | \"query\">> | undefined;\n\n /**\n * The route's DECLARED query-param names — the same registry the URL build\n * prints from (#1556), minus path slots. Feeds the always-on channel guard\n * (#1572); read here rather than re-derived, so classification cannot drift.\n */\n readonly getQueryParams: (name: string) => readonly string[];\n\n readonly forwardState: <\n P extends Params = Params,\n S extends SearchParams = SearchParams,\n >(\n routeName: string,\n routeParams: P,\n routeSearch?: S,\n ) => SimpleState<P, S>;\n\n readonly buildStateResolved: (\n resolvedName: string,\n resolvedParams: Params,\n ) => RouteTreeState | undefined;\n\n /**\n * Print a path for an intent the caller has ALREADY resolved — the facade's\n * printer without the `forwardState` chain the facade runs above it (#2260).\n *\n * ⚑ **For a caller that ran the chain itself**, which is the href door:\n * `buildHref` resolves through `forwardState` (so the href is where the click\n * lands, #2250) and then prints, and until this member existed the printer it\n * reached ran the whole chain a second time (#2087). One href is one\n * operation, so a plugin's interceptor sees one pass.\n *\n * ⚠ **Not `port.buildPath`, which is also seam-free and is NOT the same\n * printer.** That one prints below the default merge; this terminal is\n * `canonicalize(…, { resolveForward: false })` + `buildURL`, the same form\n * `makeState` takes. Substituting it drops a route's `defaultParams` /\n * `defaultSearch` from every href, silently.\n */\n readonly buildPathResolved: (\n resolvedName: string,\n resolvedParams?: Params,\n resolvedSearch?: SearchParams,\n ) => string;\n\n readonly matchPath: <P extends Params = Params>(\n path: string,\n options?: AnyOptions,\n ) => State<P> | undefined;\n\n readonly getOptions: () => Options<D>;\n\n /**\n * Where the adopted option bags came from, weakly (#2148).\n *\n * ⚑ For the VALIDATION layer, and it is the only reason this door exists.\n * `@real-router/validation-plugin` derefs these at install, takes its own\n * snapshot, and reports once if the application mutates a bag afterwards —\n * because since #2171 such a mutation reaches nothing and says nothing.\n *\n * ⚠ An entry may deref to `undefined`, and that is the correct answer rather\n * than a failure: the application dropped its bag, so there is no mutation left\n * to make and nothing to report.\n */\n readonly getAdoptedOrigins: () => AdoptedOrigins;\n\n readonly addEventListener: <E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n ) => Unsubscribe;\n\n /**\n * Route-tree mutation channel — internal access for the `getRoutesApi`\n * wrapper. A dedicated bridge is required because the public\n * `addEventListener<E extends EventName>` structurally rejects\n * `\"TREE_CHANGED\"` (it is not in the public `EventName` union), is strict on\n * duplicates, and exposes neither `emit` nor `listenerCount`.\n */\n readonly treeChanged: {\n readonly emit: (event: TreeChangedEvent) => void;\n readonly subscribe: (\n handler: (event: TreeChangedEvent) => void,\n ) => Unsubscribe;\n readonly listenerCount: () => number;\n /**\n * True while a `TREE_CHANGED` emit is on the stack — `getRoutesApi` reads it\n * to reject reentrant route-CRUD from a `subscribeChanges` handler (#1032).\n */\n readonly isEmitting: () => boolean;\n };\n\n /**\n * The navigation pipeline's read-model, for entry points that live on this\n * plugin-facing surface rather than in a namespace. Resolved LAZILY: the port\n * is created during wiring, and `registerInternals` runs before that, so the\n * accessor is a closure rather than a value — the same shape the interceptable\n * methods above already use.\n */\n readonly port: () => RouteResolver;\n\n readonly emitTransitionError: (error: Error) => void;\n\n /**\n * Commits the not-found (`UNKNOWN_ROUTE`) state for `path` and emits\n * `TRANSITION_SUCCESS` — the `NavigationNamespace.navigateToNotFound`\n * primitive. `replace()` uses it when a structural replace drops the active\n * route, so subscribers are notified instead of the state silently clearing\n * (#950).\n */\n readonly navigateToNotFound: (path: string) => State;\n\n /**\n * The `replace()` revalidation's twin of the above: commits `UNKNOWN_ROUTE`\n * WITHOUT consulting the departing route's `canDeactivate` (#1652, #1981).\n * A tree swap is not a departure the user chose.\n */\n readonly revalidateToNotFound: (path: string) => State;\n\n readonly start: (path: string) => Promise<State>;\n\n /**\n * Plugin-only navigation entry point — delegates to\n * `NavigationNamespace.navigateToState` (`getPluginApi(router).navigateToState`).\n * Hidden from `Router`/`Navigator` to keep the userland surface minimal;\n * see `types/api.ts` for usage docs.\n */\n readonly navigateToState: (\n state: State,\n options?: NavigationOptions,\n ) => Promise<State>;\n\n /* eslint-disable @typescript-eslint/no-explicit-any -- heterogeneous map: stores different InterceptorFn<M> types under different keys */\n readonly interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >;\n /* eslint-enable @typescript-eslint/no-explicit-any */\n\n readonly setRootPath: (rootPath: string) => void;\n readonly getRootPath: () => string;\n\n readonly getTree: () => RouteTree;\n\n readonly isDisposed: () => boolean;\n\n validator: RouterValidator | null;\n\n // Per-router logger instance (built from `options.logger` in the Router\n // constructor). The facade reads it as `getInternals(this).logger`; namespaces\n // receive it via their deps at wiring; plugins reach it through\n // `getPluginApi(router).logger`. Replaces the former process-global singleton\n // from the standalone `@real-router/logger` package (now folded into\n // `utils/logger`), whose `configure()` leaked across routers (#724).\n readonly logger: RouterLogger;\n\n // Dependencies (issue #172)\n readonly dependenciesGetStore: () => DependenciesStore<D>;\n\n // Clone support (issue #173, consolidated #964). One accessor for the\n // source-side snapshot a clone carries over besides the route store, so a new\n // clone-relevant subsystem is wired in a single place instead of being spread\n // across separate methods.\n readonly getCloneState: () => {\n options: Options<D>;\n dependencies: Record<string, unknown>;\n pluginFactories: PluginFactory<D>[];\n // Resolved logger config of the base router, so a clone can build its OWN\n // logger inheriting the base's level/callback. Frozen `options` do NOT carry\n // `logger` (stripped in the constructor), so `options` above can't convey it;\n // cloneRouter merges a per-request override (traceId) over this snapshot.\n loggerConfig: LoggerConfig;\n // Resolved limits of the base router (#1880). Same reason as `loggerConfig`\n // one line up: `options.limits` is the caller's own bag, so a clone built\n // from it re-invokes an accessor there and can end up with a different cap\n // than its base. The base already resolved them to numbers; the clone\n // inherits that rather than re-reading.\n limits: Limits;\n // The KEY SET the base was CONSTRUCTED with (#1961). `limits` above carries\n // the resolved VALUES, which is what #1880 needed; the clone also needs to\n // know which of them the caller actually passed, because substituting the\n // whole resolved bag materialises the unset defaults into the clone's\n // reported options and `validation-plugin` refuses one such pair at install.\n //\n // ⚠ A snapshot, NOT `Object.keys(options.limits)` read at clone time:\n // `options.limits` is the caller's own object and mutable — core freezes\n // only the level it owns (#1832). Reading it late lets a key deleted after\n // construction leave the base capped and every later clone uncapped.\n //\n // `undefined` — not `[]` — when the caller passed no bag at all, so the\n // clone can tell \"nothing to substitute\" from \"an empty bag\", which\n // `options.limits` itself still distinguishes (`undefined` vs `null` vs\n // `{}`) and which the clone must not flatten.\n //\n // ⚠ Handed out BY REFERENCE and therefore FROZEN at the source, exactly as\n // `limits` above is: `readonly string[]` is a compile-time claim and this\n // surface is reached by plugins through `@real-router/core/validation`.\n limitKeys: readonly string[] | undefined;\n };\n\n // Consolidated route data store (issue #174 Phase 2)\n readonly routeGetStore: () => RoutesStore<D>;\n\n // Cross-namespace state (issue #174)\n readonly getStateName: () => string | undefined;\n readonly isTransitioning: () => boolean;\n /**\n * Commit a state that is NOT the product of a navigation — the 404 bypass and\n * `replace()`'s revalidation. Writes AND announces through the FSM\n * `SYSTEM_COMMIT` action, so neither half happens outside the table.\n *\n * THROWS when the machine has no edge to take. The throw is NOT redundant\n * with the table: a refusal there is silent (a `send` from a state without an\n * edge is a no-op), and the contract these callers already had promises an\n * error, not a quietly skipped commit (#1186).\n *\n * Two codes, and the split is #1644's: `ROUTER_DISPOSED` only for a router\n * that IS disposed, `ROUTER_NOT_STARTED` for every other refusal — stopped,\n * never started, still STARTING, or mid-transition — because `SYSTEM_COMMIT`\n * is declared on `READY` alone and therefore also refuses routers that are\n * very much alive. The phase rides the message rather than the code.\n */\n readonly systemCommit: (\n toState: State,\n fromState: State | undefined,\n opts: NavigationOptions,\n ) => State;\n readonly routerExtensions: { keys: string[] }[];\n readonly contextClaimRecords: Map<string, ContextNamespaceClaim>;\n\n /**\n * One-shot hydration scratchpad populated by `hydrateRouter` immediately\n * before delegating to `router.start(parsed.path)` and cleared in the\n * matching `finally`. SSR loader plugins read this slot directly via\n * `getInternals(router).hydrationState` to short-circuit their own loader\n * call when the server-resolved namespace value is already present in the\n * parsed state (#596). `null` outside of an active `hydrateRouter`\n * invocation.\n */\n hydrationState: SerializedRouterState | null;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- existential type: stores RouterInternals for all Dependencies types\nconst internals = new WeakMap<object, RouterInternals<any>>();\n\n/**\n * \"I am a router\" — readable from OUTSIDE this module instance (#2294).\n *\n * ⚑ The registry above keys on object IDENTITY, so it misses for three\n * different things and the refusal has to say WHICH: an object that is not a\n * router, a PROXY over one (`reactive()` / Pinia — `packages/vue/CLAUDE.md`\n * documents the trap and its `markRaw` remedy), and a router built by ANOTHER\n * COPY of this package. The last is reachable by ordinary resolution: core is a\n * plain `dependency` of every adapter and plugin, a caret range on a `0.x`\n * version pins to the MINOR, and an application that updates core without its\n * adapter gets two copies with two registries.\n *\n * ⚑ `Symbol.for`, because nothing else answers across a module boundary.\n * `instanceof` and `#private in` are per-class and two copies have two classes;\n * a local symbol is per-module by construction. Measured: a real router carries\n * this, a transparent proxy forwards the read, a plain object does not.\n *\n * ⚠ It picks a MESSAGE and gates NOTHING, which is why a global symbol is\n * acceptable here where `packages/solid/src/components/RouteView/components.tsx`\n * chose a local one against spoofing — forging this buys a better error, not\n * access. The WeakMap still decides who is served.\n */\nconst ROUTER_BRAND = Symbol.for(\"real-router.router\");\n\n/**\n * Is this a real router, just not one THIS copy of core registered?\n *\n * ⚠ Read defensively: the argument is the caller's object and may be a `Proxy`\n * whose `get` trap throws — and a diagnostic that throws would change where the\n * error comes FROM, which is the #1572 class. A throwing read means \"cannot\n * tell\", and the generic message is the right answer then.\n */\nfunction isForeignRouter(candidate: unknown): boolean {\n try {\n return (\n (candidate as Record<symbol, unknown> | null | undefined)?.[\n ROUTER_BRAND\n ] === true\n );\n } catch {\n return false;\n }\n}\n\nexport function getInternals<D extends DefaultDependencies>(\n router: RouterInterface<D>,\n): RouterInternals<D> {\n const ctx = internals.get(router);\n\n if (!ctx) {\n throw new TypeError(\n isForeignRouter(router)\n ? \"[real-router] This IS a router, but not one this copy of @real-router/core built. \" +\n \"Core identifies a router by object identity, so it is either wrapped in a Proxy \" +\n \"(Vue `reactive()` / Pinia — store it with `markRaw`), or your dependency tree holds \" +\n \"two copies of @real-router/core — dedupe it to one.\"\n : \"[real-router] Invalid router instance — not found in internals registry\",\n );\n }\n\n return ctx as RouterInternals<D>;\n}\n\n/**\n * Channel guard, position P1 (#1572) — the caller's RAW `params` argument, at\n * the API boundary and BEFORE any interceptor runs, so what it reports is what\n * the CALLER wrote (a plugin's later injection is P2's population, not this one).\n *\n * THROWS. The warn-first step (#1572) announced the contract so every call site\n * could identify itself in the logs; this is the promotion it announced.\n *\n * A `TypeError`, synchronous, rather than a `RouterError` on a rejected promise:\n * this is an ARGUMENT-shape defect at the API boundary, caught before any\n * interceptor or transition exists — the same class as the `subscribe` /\n * `navigateToNotFound` / `start` guards beside it. Rejecting instead would let a\n * `.catch()` written for navigation failures swallow a programming error.\n *\n * P3 (`navigateToState`) keeps REJECTING — deliberately asymmetric, because it\n * takes a ready-made `State` from a popstate handler, where a new synchronous\n * throw would change an existing method's failure shape.\n *\n * The predicates (`buildPath` / `isActiveRoute` / `canNavigateTo`) are still NOT\n * instrumented: they run on every `<Link>` render, an answer there is read\n * immediately and corrupts nothing, and throwing inside a render in six adapters\n * is not a trade this guard is worth.\n *\n * ⚠ Not instrumented ≠ blind. `canNavigateTo` answers whether `navigate` WOULD\n * work, so it consults {@link findMisChanneledKey} directly and returns `false`\n * for a shape this function would have thrown on (#1576) — an answer, not a\n * throw, so the render-path trade above is untouched. `buildPath` /\n * `isActiveRoute` ask a different question and are unchanged.\n *\n * @internal\n */\nexport function throwOnMisChanneledKey<D extends DefaultDependencies>(\n ctx: RouterInternals<D>,\n method: string,\n routeName: string,\n params: Params | undefined,\n): void {\n assertChannelCorrect(\n method,\n routeName,\n params,\n ctx.getQueryParams(routeName),\n );\n}\n\n/**\n * Refuses a call on a disposed router with a frozen `ROUTER_DISPOSED`.\n *\n * ⚑ Both door families make this refusal — the standalone doors in `api/` and\n * the `RouterInternals` adapters `Router.ts` registers — so it lives here,\n * below both. Nothing else in `src/` imports from `api/`: the\n * `import-x/no-restricted-paths` zone in `packages/core/eslint.config.mjs`\n * holds that.\n */\nexport function throwIfDisposed(isDisposed: () => boolean): void {\n if (isDisposed()) {\n throw freezeThrownError(new RouterError(errorCodes.ROUTER_DISPOSED));\n }\n}\n\nexport function registerInternals<D extends DefaultDependencies>(\n router: RouterClass<D>,\n ctx: RouterInternals<D>,\n): void {\n internals.set(router, ctx);\n\n // On the INSTANCE rather than the prototype: this module holds `Router` as a\n // type only, and importing the class to brand its prototype would close a\n // cycle. Non-enumerable by `defineProperty`'s defaults, so no surface census\n // sees it.\n Object.defineProperty(router, ROUTER_BRAND, { value: true });\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument -- internal chain execution: type safety enforced at public API boundary (PluginApi.addInterceptor) */\nfunction executeInterceptorChain<T>(\n interceptors: ((next: (...args: any[]) => any, ...args: any[]) => any)[],\n original: (...args: any[]) => T,\n args: any[],\n sanitiseNext?: (result: T) => T,\n): T {\n let chain = original as (...args: any[]) => any;\n\n for (const interceptor of interceptors) {\n const prev = chain;\n // ⚑ The `next` an interceptor RECEIVES is wrapped, not the value it returns\n // (#1986). This covers exactly the boundaries nothing else does — `original`\n // into the first interceptor, and each interceptor into the one outside it —\n // and leaves the outermost hop's result to the seam's own exit copy.\n //\n // ⚠ The alternative, wrapping the RETURN, was built and measured rather\n // than argued about. It puts two mechanisms on that last boundary, and one\n // cell stops discriminating: \"an interceptor's OWN poison does not leave the\n // door either\". It does NOT make the exit copy redundant — the\n // no-interceptor fast path skips this chain entirely, so two other cells\n // still red that copy's removal either way.\n const next =\n sanitiseNext === undefined\n ? prev\n : (...nextArgs: any[]) => sanitiseNext(prev(...nextArgs) as T);\n\n chain = (...chainArgs: any[]) => interceptor(next, ...chainArgs);\n }\n\n return chain(...args) as T;\n}\n\n/**\n * THE interceptable seams, and the object the wrappers are NAMED from.\n *\n * `addInterceptor` refuses a name that is not a key here (#2088), so this is the\n * runtime half of a set whose compile-time half is {@link InterceptableMethodMap}\n * — and `satisfies` ties the two in BOTH directions rather than leaving them a\n * pair someone maintains: a seam added to the map fails this object to compile,\n * a key here that the map does not declare fails too, and the mapped type makes\n * a value that drifts from its key an error rather than a silent alias.\n *\n * ⚑ The three `create*Interceptable` call sites in `Router.ts` take their name\n * from THIS object rather than spelling a literal, which is what makes \"the set\n * that decides is the set that acts\" a check instead of a convention. A literal\n * at a call site could drift from the set; a property read cannot.\n */\nexport const SEAM = {\n start: \"start\",\n forwardState: \"forwardState\",\n} as const satisfies { [K in keyof InterceptableMethodMap]: K };\n\n/**\n * Variadic interceptor wrapper — wraps a function of any arity, returning the\n * same callable type `T`. Use {@link createTernaryInterceptable} instead when\n * the wrapped method takes exactly three args and the caller needs the precise\n * `(a, b, c) => r` signature preserved (the variadic form widens args to\n * `any[]`).\n */\nexport function createInterceptable<T extends (...args: any[]) => any>(\n name: string,\n original: T,\n interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >,\n): T {\n return ((...args: any[]) => {\n const chain = interceptors.get(name);\n\n if (!chain || chain.length === 0) {\n return original(...args);\n }\n\n return executeInterceptorChain(chain, original, args);\n }) as T;\n}\n\n/**\n * Three-argument interceptor wrapper — preserves the exact\n * `(a: A, b: B, c: C) => R` signature that the variadic\n * {@link createInterceptable} widens to `any[]`. Backs the search-aware\n * interceptable `forwardState(name, params, search)` (RFC-4 M2 / #1548). Every\n * first-party plugin registers the full three-argument form; a shorter-arity\n * interceptor from a third party remains type-valid (TS allows fewer params,\n * and `next(a, b)` leaves the third arg `undefined`).\n *\n * ⚑ `sanitiseNext` is applied to whatever `next` hands an interceptor, at every\n * hop (#1986). It exists because `forwardState` returns CONTAINERS a plugin is\n * documented to merge, so what one interceptor hands the next is a hand-out in\n * the #1957 sense. The seam that needs it owns the function — this module only\n * applies it.\n *\n * ⚠ It does NOT reach the chain's own return value. That one goes to the caller,\n * which is the seam's own business and already has an exit copy.\n *\n * ⚑ `prepareArgs` shapes what the FIRST interceptor is handed, and it runs only\n * on the non-empty branch below (#1849). That placement is the whole of its\n * affordability: with no interceptor registered the wrapper takes `original`\n * directly and pays nothing, so the cost falls on the configuration that\n * created the need. What the seam does with it is the seam's business, as with\n * `sanitiseNext`.\n *\n * ⚠ Both are REQUIRED. One seam is left and both of its wrappers supply both,\n * so an optional slot would be a branch nothing takes — the shape a coverage\n * threshold catches and a reader does not.\n */\nexport function createTernaryInterceptable<A, B, C, R>(\n name: string,\n original: (a: A, b: B, c: C) => R,\n interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >,\n sanitiseNext: (result: R) => R,\n prepareArgs: (a: A, b: B, c: C) => [A, B, C],\n): (a: A, b: B, c: C) => R {\n return (arg1: A, arg2: B, arg3: C) => {\n const chain = interceptors.get(name);\n\n if (!chain || chain.length === 0) {\n return original(arg1, arg2, arg3);\n }\n\n return executeInterceptorChain(\n chain,\n original,\n prepareArgs(arg1, arg2, arg3),\n sanitiseNext,\n );\n };\n}\n/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */\n"],"mappings":"6CAeM,EAAS,OAAO,OAwBtB,SAAgB,EACd,EACA,EACoB,CAWhB,KAAW,SAAW,GAAK,GAAU,KAIzC,IAAK,IAAM,KAAO,EAAY,CAC5B,GAAI,CAAC,EAAO,EAAQ,CAAG,EACrB,SAGF,IAAI,EAEJ,GAAI,CACF,EAAQ,EAAO,EACjB,MAAQ,CAQN,MACF,CAEA,GAAI,IAAU,IAAA,GACZ,OAAO,CAEX,CAGF,CAsBA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACA,EACM,CACN,IAAM,EAAM,EAAoB,EAAQ,CAAU,EAElD,GAAI,IAAQ,IAAA,GACV,MAAU,UACR,WAAW,EAAO,IAAI,EACpB,EACA,EACA,OAAO,GAAW,WAAa,EAAO,EAAI,EAC1C,CACF,GACF,CAEJ,CAuCA,SAAgB,EACd,EACA,EACA,EACA,EACM,CACN,EACE,EACA,EACA,EACA,EACA,oKACF,CACF,CAWA,SAAgB,EACd,EACA,EACA,EAAS,wBACT,EAAS,8BACD,CACR,MAAO,UAAU,EAAU,eAAe,EAAI,2CAA2C,EAAO,uBAAuB,EAAO,8FAChI,CCjJA,MAOM,EAAY,GAA0B,IAAS,IAAS,IAAS,GAcvE,SAAgB,EAAkB,EAAwB,CACxD,IAAM,EAAqB,CAAC,EACxB,EAAQ,EAEZ,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,GAAK,EAChC,EAAK,WAAW,CAAC,IAAME,KAI3B,EAAS,KAAK,EAAK,MAAM,EAAO,CAAC,CAAC,EAClC,EAAQ,EAAI,GAKd,OAFA,EAAS,KAAK,EAAK,MAAM,CAAK,CAAC,EAExB,CACT,CAQA,SAAgB,EAAa,EAA+C,CAC1E,IAAM,EAAS,EAAQ,OAEvB,GAAI,IAAW,EACb,MAAO,CAAE,KAAM,SAAU,KAAM,EAAG,EAIpC,GAAI,CAAC,EAAS,EAAQ,WAAW,CAAC,CAAC,EAAG,CACpC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,GAAK,EAAG,CAClC,IAAM,EAAO,EAAQ,WAAW,CAAC,EAIjC,GAAI,IAASF,IAAM,IAAS,GAC1B,MAAO,CAAE,MAAO,oBAAqB,EAYvC,GAAI,EAAS,CAAI,GAAK,EAAI,EAAI,EAC5B,MAAO,CAAE,MAAO,cAAe,CAEnC,CAaA,OAJI,EAAQ,WAAW,EAAS,CAAC,IAAMC,GAC9B,CAAE,MAAO,WAAY,EAGvB,CAAE,KAAM,SAAU,KAAM,CAAQ,CACzC,CAEA,IAAM,EAAQ,EAAQ,WAAW,CAAC,IAAM,GAIpC,EAAS,EAEb,KAAO,EAAS,GAAQ,CACtB,IAAM,EAAO,EAAQ,WAAW,CAAM,EAEtC,GAAI,IAASD,IAAM,IAAS,IAAM,IAASC,GACzC,MAGF,GAAU,CACZ,CAEA,IAAM,EAAO,EAAQ,MAAM,EAAG,CAAM,EAqBpC,OAnBI,EAAK,SAAW,EACX,CAAE,MAAO,WAAY,EAG1B,EAAS,EAAK,WAAW,EAAK,OAAS,CAAC,CAAC,EACpC,CAAE,MAAO,iBAAkB,EAQhC,EAAS,EACJ,EAAQ,WAAW,CAAM,IAAMA,GAClC,CAAE,MAAO,kBAAmB,EAC5B,CAAE,MAAO,oBAAqB,EAG7B,EAAQ,CAAE,KAAM,QAAS,MAAK,EAAI,CAAE,KAAM,QAAS,MAAK,CACjE,CAiBA,SAAgB,EACd,EAC8B,CAC9B,IAAK,IAAM,KAAW,EAAkB,CAAI,EAAG,CAC7C,IAAM,EAAQ,EAAa,CAAO,EAElC,GAAI,UAAW,EACb,OAAO,EAAM,KAEjB,CAGF,CA0BA,SAAgB,EAAoB,EAAuC,CACzE,IAAM,EAAW,EAAkB,CAAI,EAEvC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,GAAK,EAAG,CAC3C,IAAM,EAAQ,EAAa,EAAS,EAAE,EAEhC,aAAW,EAQjB,IAAI,EAAM,QAAU,mBAAoB,CACtC,IAAM,EAAU,EAAS,GACnB,EAAW,CAAC,GAAG,CAAQ,EAS7B,MAFA,GAAS,GAAK,EAAQ,MAAM,EAAG,EAAQ,QAAQ,GAAG,CAAC,EAE5C,CACL,KAAM,mBACN,UACA,eAAgB,EAAS,QAAQ,EAAG,IAAM,IAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAC3D,aAAc,EAAS,KAAK,GAAG,CACjC,CACF,CAEA,OAAO,EAAM,QAAU,qBACnB,CAAE,KAAM,qBAAsB,QAAS,EAAS,EAAG,EACnD,IAAA,EAJJ,CAKF,CAGF,CC3PA,MAAa,EAAyB,QAoBtC,SAAS,EAAmB,EAAsB,CAChD,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,GAAK,EAAG,CACvC,GAAI,EAAK,YAAY,CAAC,IAAM,GAC1B,SAUF,IAAM,EAAO,EAAI,EAAI,EAAK,OAAS,EAAK,YAAY,EAAI,CAAC,EAAK,GAE9D,GAAI,IAAS,IAAM,IAAS,IAAS,IAAS,IAAY,IAAS,GACjE,OAAO,CAEX,CAEA,MAAO,EACT,CA4BA,MAAM,EAAuC,OAAO,OAAO,CAAC,CAAC,EACvD,EACJ,OAAO,OAAO,CAAC,CAAC,EAUL,EAA8B,OAAO,OAAO,CACvD,UAAW,EACX,YAAa,EACb,aAAc,EACd,YAAa,EACf,CAAC,EASD,SAAS,EACP,EACA,EACA,EACM,CACN,IAAK,IAAM,KAAW,EAAkB,CAAI,EAAG,CAC7C,GAAI,EAAQ,SAAW,EACrB,SAGF,IAAM,EAAQ,EAAa,CAAO,EAE9B,UAAW,GAAS,EAAM,OAAS,WAIvC,EAAU,KAAK,EAAM,IAAI,EACzB,EAAa,EAAM,MAAQ,MAC7B,CACF,CAEA,SAAgB,EAAe,EAAyB,CACtD,IAAM,EAAsB,CAAC,EACvB,EAAwB,CAAC,EACzB,EAAeE,EAAAA,EAA6B,EAI5C,EAAY,EAAmB,CAAI,EAEzC,GAAI,IAAc,GAAI,CAEpB,IAAM,EADc,EAAK,MAAM,EAAY,CAClB,CAAC,CAAC,MAAM,GAAG,EAEpC,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAY,EAAM,KAAK,EAEzB,EAAU,OAAS,IACrB,EAAY,KAAK,CAAS,EAC1B,EAAa,GAAa,QAE9B,CAEA,EAAO,EAAK,MAAM,EAAG,CAAS,CAChC,CAIA,OAFA,EAAiB,EAAM,EAAW,CAAY,EAEvC,EACL,EACA,EACAC,EAAAA,EAAc,CAAY,EAC1B,CACF,CACF,CAMA,SAAS,EACP,EACA,EACA,EACA,EACW,CACX,MAAO,CACL,UAAW,EAAU,SAAW,EAAI,EAAoB,EACxD,YAAa,EAAY,SAAW,EAAI,EAAoB,EAC5D,aACE,EAAU,SAAW,GAAK,EAAY,SAAW,EAC7C,EACA,EACN,aACF,CACF,CC9KA,MAAM,EAAqB,oBAKrB,EAAqB,KAKrB,EAAwB,IAK9B,SAAgB,EACd,EACA,EACM,CACN,GAAI,IAAS,GACX,MAAU,UAAU,WAAW,EAAW,6BAA6B,CAE3E,CAKA,SAAgB,GACd,EACA,EACM,CACN,GAAI,CAAC,EAAmB,KAAK,CAAI,EAC/B,MAAU,UACR,WAAW,EAAW,4CACxB,CAEJ,CAKA,SAAgB,EACd,EACA,EACM,CACN,GAAI,EAAK,OAAS,EAChB,MAAU,UACR,WAAW,EAAW,yCAAyC,EAAsB,YACvF,CAEJ,CAUA,SAAgB,EACd,EACA,EACM,CACN,GAAI,EAAK,SAAS,GAAG,EACnB,MAAU,UACR,WAAW,EAAW,gBAAgB,EAAK,sFAE7C,CAEJ,CAKA,SAAgB,GACd,EACA,EACM,CACN,GAAI,CAAC,EAAmB,KAAK,CAAI,EAC/B,MAAU,UACR,WAAW,EAAW,wBAAwB,EAAK,uGAGrD,CAEJ,CCtFA,SAAS,EAAkB,EAAoB,EAA4B,CACzE,OAAW,UAAU,WAAW,EAAW,IAAI,GAAS,CAC1D,CA4BA,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,GAAI,EAAuB,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,eAAgB,EAAe,CAAI,EAGnE,EAAyB,EAAW,EAAW,EAAY,CAAI,EAK/D,EAA+B,EAAa,EAAW,EAAY,CAAI,EAMvE,IAAM,EAAU,EAAoB,CAAW,EAE/C,GAAI,IAAY,IAAA,GACd,MAAM,EAAkB,EAAY,EAAmB,EAAS,CAAS,CAAC,EAQ5E,IAAM,EAAe,EAAwB,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,CC7SA,MAAM,EAA2B,OAAO,yBAClC,EAAa,OAAO,KACpB,EAAiB,OAAO,eAM9B,SAAS,EAAmB,EAAwB,CAElD,GAAI,IAAU,KACZ,MAAO,OAGT,GAAI,OAAO,GAAU,SAAU,CAQ7B,GAAI,CACF,IAAM,EAAiB,EAAoC,YAG3D,GAAI,OAAO,GAAS,YAAc,EAAK,OAAS,SAC9C,OAAO,EAAK,MAAQ,QAExB,MAAQ,CAER,CAGA,MAAO,QACT,CAGA,OAAO,OAAO,CAChB,CASA,SAAS,EAAoB,EAAuC,CAClE,IAAK,IAAM,KAAO,EAAW,CAAG,EAAG,CACjC,IAAM,EAAa,EAAyB,EAAK,CAAG,EAEpD,GAAI,IAAe,EAAW,KAAO,EAAW,KAC9C,MAAO,EAEX,CAEA,MAAO,EACT,CAiBA,SAAgB,EACd,EACA,EAC0C,CAC1C,GAAI,CAAC,GAAS,OAAO,GAAU,SAC7B,MAAU,UACR,WAAW,EAAW,iCAAiC,EAAmB,CAAK,GACjF,EAIF,IAAM,EAAuB,EAAe,CAAK,EAEjD,GAAI,IAAU,OAAO,WAAa,IAAU,KAC1C,MAAU,UACR,WAAW,EAAW,sCAAsC,EAAmB,CAAK,GACtF,EAIF,GAAI,EAAoB,CAAgC,EACtD,MAAU,UACR,WAAW,EAAW,yCACxB,CAEJ,CASA,SAAS,EACP,EACA,EACM,CACN,GACE,EAAM,eAAiB,IAAA,IACvB,OAAO,EAAM,cAAiB,WAE9B,MAAU,UACR,WAAW,EAAW,WAAW,OAAO,EAAM,IAAI,EAAE,kCACtD,CAEJ,CASA,SAAS,EACP,EACA,EACM,CACN,GACE,EAAM,eAAiB,IAAA,IACvB,OAAO,EAAM,cAAiB,WAE9B,MAAU,UACR,WAAW,EAAW,WAAW,OAAO,EAAM,IAAI,EAAE,kCACtD,CAEJ,CAoBA,SAAS,EACP,EACA,EAC6D,CAC7D,GAAI,OAAO,EAAM,MAAS,SACxB,MAAU,UACR,WAAW,EAAW,qCAAqC,EAAmB,EAAM,IAAI,GAC1F,EAGF,IAAM,EAAO,EAAM,KAEnB,EAAwB,EAAM,CAAU,EACxC,GAAiC,EAAM,CAAU,EACjD,EAA4B,EAAM,CAAU,EAGxC,GAAK,WAAW,IAAI,IAIxB,EAAwB,EAAM,CAAU,EACxC,GAA8B,EAAM,CAAU,EAChD,CAWA,SAAS,EACP,EACA,EACuB,CAGvB,GAAI,CAAC,EAAS,SAAS,GAAG,EACxB,OAAO,EAAS,SAAS,IAAI,CAAQ,EAGvC,IAAI,EAAiC,EAErC,IAAK,IAAM,KAAW,EAAS,MAAM,GAAG,EAGtC,GAFA,EAAU,EAAQ,SAAS,IAAI,CAAO,EAElC,CAAC,EACH,OAIJ,OAAO,CACT,CAUA,SAAS,EACP,EACA,EACA,EACM,CACN,GAAI,EAAmB,EAAU,CAAQ,EACvC,MAAU,MACR,WAAW,EAAW,WAAW,EAAS,iBAC5C,CAEJ,CAUA,SAAS,EACP,EACA,EACA,EACM,CACN,GAAI,EAAU,IAAI,CAAQ,EACxB,MAAU,MACR,WAAW,EAAW,qBAAqB,EAAS,WACtD,EAGF,EAAU,IAAI,CAAQ,CACxB,CAUA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,IAAM,EACJ,IAAe,GAAK,EAAW,EAAmB,EAAU,CAAU,EAEnE,KAIL,KAAK,IAAM,KAAS,EAAW,SAAS,OAAO,EAC7C,GAAI,EAAM,OAAS,EACjB,MAAU,MACR,WAAW,EAAW,UAAU,EAAU,qBAC5C,CAAA,CAGN,CAUA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,IAAM,EAAe,EAAkB,IAAI,CAAU,EAErD,GAAI,GAAc,IAAI,CAAS,EAC7B,MAAU,MACR,WAAW,EAAW,UAAU,EAAU,qBAC5C,EAGE,EACF,EAAa,IAAI,CAAS,EAE1B,EAAkB,IAAI,EAAY,IAAI,IAAI,CAAC,CAAS,CAAC,CAAC,CAE1D,CAkCA,SAAgB,EACd,EACA,EACA,EACA,EAAa,GACb,EACA,EACkC,CAClC,EAAkB,EAAO,CAAU,EAEnC,IAAM,EAAW,EAGjB,EAAkB,EAAU,CAAU,EAGtC,EAAkB,EAAS,KAAM,EAAS,KAAM,EAAY,CAAQ,EAGpE,EAAqB,EAAU,CAAU,EACzC,EAAqB,EAAU,CAAU,EAEzC,IAAM,EAAY,EAAS,KACrB,EAAW,EAAa,GAAG,EAAW,GAAG,IAAc,EAGzD,GAAY,GACd,EAAuB,EAAU,EAAU,CAAU,EAInD,GACF,EAAwB,EAAW,EAAU,CAAU,EAGzD,IAAM,EAAY,EAAS,KACrB,EAAkB,EAkBxB,GAfI,GACF,EAAuB,EAAU,EAAiB,EAAW,CAAU,EAIrE,GACF,EACE,EACA,EACA,EACA,CACF,EAIE,EAAS,WAAa,IAAA,GAAW,CACnC,GAAI,CAAC,MAAM,QAAQ,EAAS,QAAQ,EAClC,MAAU,UACR,WAAW,EAAW,WAAW,EAAU,mCAAmC,EAAmB,EAAS,QAAQ,GACpH,EAGF,IAAK,IAAM,KAAS,EAAS,SAC3B,EACE,EACA,EACA,EACA,EACA,EACA,CACF,CAEJ,CACF,CC3JA,MAAM,EAAY,IAAI,QAwBhB,EAAe,OAAO,IAAI,oBAAoB,EAUpD,SAAS,EAAgB,EAA6B,CACpD,GAAI,CACF,OACG,IACC,KACI,EAEV,MAAQ,CACN,MAAO,EACT,CACF,CAEA,SAAgB,EACd,EACoB,CACpB,IAAM,EAAM,EAAU,IAAI,CAAM,EAEhC,GAAI,CAAC,EACH,MAAU,UACR,EAAgB,CAAM,EAClB,4SAIA,yEACN,EAGF,OAAO,CACT,CAiCA,SAAgB,EACd,EACA,EACA,EACA,EACM,CACN,EACE,EACA,EACA,EACA,EAAI,eAAe,CAAS,CAC9B,CACF,CAWA,SAAgB,EAAgB,EAAiC,CAC/D,GAAI,EAAW,EACb,MAAMC,EAAAA,EAAkB,IAAIC,EAAAA,EAAYC,EAAAA,EAAW,eAAe,CAAC,CAEvE,CAEA,SAAgB,GACd,EACA,EACM,CACN,EAAU,IAAI,EAAQ,CAAG,EAMzB,OAAO,eAAe,EAAQ,EAAc,CAAE,MAAO,EAAK,CAAC,CAC7D,CAGA,SAAS,EACP,EACA,EACA,EACA,EACG,CACH,IAAI,EAAQ,EAEZ,IAAK,IAAM,KAAe,EAAc,CACtC,IAAM,EAAO,EAYP,EACJ,IAAiB,IAAA,GACb,GACC,GAAG,IAAoB,EAAa,EAAK,GAAG,CAAQ,CAAM,EAEjE,GAAS,GAAG,IAAqB,EAAY,EAAM,GAAG,CAAS,CACjE,CAEA,OAAO,EAAM,GAAG,CAAI,CACtB,CAiBA,MAAa,GAAO,CAClB,MAAO,QACP,aAAc,cAChB,EASA,SAAgB,GACd,EACA,EACA,EAIG,CACH,QAAS,GAAG,IAAgB,CAC1B,IAAM,EAAQ,EAAa,IAAI,CAAI,EAMnC,MAJI,CAAC,GAAS,EAAM,SAAW,EACtB,EAAS,GAAG,CAAI,EAGlB,EAAwB,EAAO,EAAU,CAAI,CACtD,EACF,CA+BA,SAAgB,GACd,EACA,EACA,EAIA,EACA,EACyB,CACzB,OAAQ,EAAS,EAAS,IAAY,CACpC,IAAM,EAAQ,EAAa,IAAI,CAAI,EAMnC,MAJI,CAAC,GAAS,EAAM,SAAW,EACtB,EAAS,EAAM,EAAM,CAAI,EAG3B,EACL,EACA,EACA,EAAY,EAAM,EAAM,CAAI,EAC5B,CACF,CACF,CACF"}
|
package/dist/cjs/internals.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { NavigationOptions } from "./types.js";
|
|
|
9
9
|
import { AdoptedOrigins } from "./Router.js";
|
|
10
10
|
import { RouterValidator } from "./types/RouterValidator.js";
|
|
11
11
|
import { Limits } from "./types/internal.js";
|
|
12
|
-
import { DependenciesStore } from "./
|
|
12
|
+
import { DependenciesStore } from "./dependenciesStore.js";
|
|
13
13
|
import { RouteResolver } from "./pipeline/port.js";
|
|
14
14
|
import { RoutesStore } from "./namespaces/RoutesNamespace/routesStore.js";
|
|
15
15
|
//#region src/internals.d.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internals.d.ts","names":[],"sources":["../../src/internals.ts"],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"internals.d.ts","names":[],"sources":["../../src/internals.ts"],"mappings":";;;;;;;;;;;;;;;iBAmCiB,gBACf,UAAU,sBAAsB;WAEvB,YACP,UAAU,SAAS,QACnB,UAAU,eAAe,cAEzB,cACA,SAAS,GACT,SAAS,GACT,kBACG,MAAM,GAAG;;;;;;;WAQL,kBACP,iBACG,eAAe;;;;;;WAOX,iBAAiB;WAEjB,eACP,UAAU,SAAS,QACnB,UAAU,eAAe,cAEzB,mBACA,aAAa,GACb,cAAc,MACX,YAAY,GAAG;WAEX,qBACP,sBACA,gBAAgB,WACb;;;;;;;;;;;;;;;;;WAkBI,oBACP,sBACA,iBAAiB,QACjB,iBAAiB;WAGV,YAAY,UAAU,SAAS,QACtC,cACA,UAAU,eACP,MAAM;WAEF,kBAAkB,QAAQ;;;;;;;;;;;;;WAc1B,yBAAyB;WAEzB,mBAAmB,UAAU,WACpC,WAAW,GACX,IAAI,OAAO,eAAe,QACvB;;;;;;;;WASI;aACE,OAAO,OAAO;aACd,YACP,UAAU,OAAO,8BACd;aACI;;;;;aAKA;;;;;;;;;WAUF,YAAY;WAEZ,sBAAsB,OAAO;;;;;;;;WAS7B,qBAAqB,iBAAiB;;;;;;WAOtC,uBAAuB,iBAAiB;WAExC,QAAQ,iBAAiB,QAAQ;;;;;;;WAQjC,kBACP,OAAO,OACP,UAAU,sBACP,QAAQ;WAGJ,cAAc,cAEnB,UAAU,wBAAwB;WAI7B,cAAc;WACd;WAEA,eAAe;WAEf;EAET,WAAW;WAQF,QAAQ;WAGR,4BAA4B,kBAAkB;WAM9C;IACP,SAAS,QAAQ;IACjB,cAAc;IACd,iBAAiB,cAAc;IAK/B,cAAc;IAMd,QAAQ;IAoBR;;WAIO,qBAAqB,YAAY;WAGjC;WACA;;;;;;;;;;;;;;;;;WAiBA,eACP,SAAS,OACT,WAAW,mBACX,MAAM,sBACH;WACI;IAAoB;;WACpB,qBAAqB,YAAY;;;;;;;;;;EAW1C,gBAAgB;;wBAkDF,aAAa,UAAU,qBACrC,QAAQ,OAAgB,KACvB,gBAAgB"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { RouteDefinition, RouteTree } from "../../engine/builder/types.js";
|
|
2
2
|
import { CreateMatcherOptions, Matcher } from "../../engine/createMatcher.js";
|
|
3
3
|
import { DefaultDependencies, GuardFnFactory } from "../../types/router.js";
|
|
4
|
-
import { RouteLifecycleNamespace } from "../RouteLifecycleNamespace/RouteLifecycleNamespace.js";
|
|
5
4
|
import { RouteConfig, RoutesDependencies } from "./types.js";
|
|
5
|
+
import { RouteLifecycleNamespace } from "../RouteLifecycleNamespace/RouteLifecycleNamespace.js";
|
|
6
6
|
//#region src/namespaces/RoutesNamespace/routesStore.d.ts
|
|
7
7
|
export interface RoutesStore<Dependencies extends DefaultDependencies = DefaultDependencies> {
|
|
8
8
|
/**
|
package/dist/cjs/utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./RouterError-C-p1yaby.js"),t=require("./helpers-RzUXJQAh.js");exports.adoptChannel=t.t,exports.copyFields=e.i,exports.freezeThrownError=e.n,exports.putField=e.s;
|
package/dist/cjs/validation.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./internals-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./internals-NAYRbkal.js");exports.findMisChanneledKey=e.y,exports.getInternals=e.i,exports.validateRoute=e.c;
|