@real-router/core 0.129.1 → 0.131.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","names":["freezeThrownError","RouterError","errorCodes","freeze","objectKeys","cache","getInternals","adoptChannel","canonicalize","materialize","buildURL","freezeThrownError","RouterError","errorCodes","buildAddArtifacts","buildReplaceArtifacts","compileArtifactGuards","getTransitionPath","spliceSubtree","nodeToDefinition","getInternals","guardRouteStructure","commitRouteUpdate","freezeThrownError","RouterError","errorCodes","hasOwn","getInternals","dropUnsafeKey","getInternals","getInternals","freezeThrownError","RouterError","errorCodes","routeTreeToDefinitions","RouterClass"],"sources":["../../src/api/helpers.ts","../../src/namespaces/RoutesNamespace/routeGuards.ts","../../src/api/getPluginApi.ts","../../src/api/getRoutesApi.ts","../../src/api/getDependenciesApi.ts","../../src/api/getLifecycleApi.ts","../../src/api/cloneRouter.ts"],"sourcesContent":["// packages/core/src/api/helpers.ts\n\nimport { errorCodes } from \"../constants\";\nimport { RouterError, freezeThrownError } from \"../RouterError\";\n\nexport function throwIfDisposed(isDisposed: () => boolean): void {\n if (isDisposed()) {\n throw freezeThrownError(new RouterError(errorCodes.ROUTER_DISPOSED));\n }\n}\n\n/**\n * Bans synchronous reentrant tree mutation: a mutator called while a\n * `TREE_CHANGED` emit is on the stack (i.e. from inside a `subscribeChanges`\n * handler) throws `REENTRANT_TREE_MUTATION` BEFORE mutating — the tree stays\n * atomic (#1032). Six callers, not five: the `getRoutesApi` mutators and\n * `getPluginApi.setRootPath` (#1751), which rebuilds tree and matcher alike.\n * Deferred CRUD (`queueMicrotask` / `await`) runs after the dispatch settles and\n * is unaffected; CRUD from a transition listener is not a TREE_CHANGED dispatch.\n *\n * ⚑ The remedy rides the ERROR, not this docblock (#1665). Kept here alone it is\n * visible to whoever maintains core and not to the application developer who\n * caught the throw — the same omission on the navigation ban produced two docs\n * issues before anyone reached the code.\n */\nexport function throwIfReentrantTreeMutation(isEmitting: () => boolean): void {\n if (isEmitting()) {\n throw freezeThrownError(\n new RouterError(errorCodes.REENTRANT_TREE_MUTATION, {\n message:\n \"[router] cannot mutate the route tree from inside a subscribeChanges handler — the mutation would run while a TREE_CHANGED emit is on the stack and the tree must stay atomic. Defer it: queueMicrotask(() => routes.add(...)) or await.\",\n }),\n );\n }\n}\n","import type { RouterLogger } from \"../../types\";\n\n/**\n * Validates removeRoute constraints.\n * Returns false if removal should be blocked (route is active).\n *\n * ⚠ This is the GATE only. The in-flight report is\n * {@link warnRemovalDuringNavigation}, and it deliberately does NOT live here:\n * it describes what a removal did to a navigation, which is not knowable until\n * the removal is known to have happened at all (#1756).\n *\n * @param name - Route name to remove\n * @param currentStateName - Current active route name (or undefined)\n * @param logger - Per-router logger instance (from `getInternals(router).logger`)\n * @returns true if removal can proceed, false if blocked\n */\nexport function validateRemoveRoute(\n name: string,\n currentStateName: string | undefined,\n logger: RouterLogger,\n): boolean {\n if (currentStateName) {\n const isExactMatch = currentStateName === name;\n // ⚑ The prefix IS the ancestry test again, and that is a consequence of\n // #1763 rather than a step back from #1757.\n //\n // #1757 replaced this line with a walk of the matcher's segment chain,\n // because core accepted a dotted LEAF: a standalone `x.y` declared beside\n // `x` matched `startsWith(\"x.\")` and made `remove(\"x\")` refuse with `it is\n // currently active (current: \"x.y\")` — a sentence that was false about a\n // route nothing was removing — and it fired for a `name` that was not a\n // route at all, masking the not-found report it runs above.\n //\n // #1763 removed the shape instead: a route NAME cannot carry a dot, so a\n // dotted committed name implies its ancestor EXISTS and is a real ancestor.\n // Measured, not assumed — the two forms were probed on every shape still\n // constructible and agree on all of them, which is why the chain walk, its\n // `matcher` parameter and the O(depth) lookup are gone from a cold path.\n //\n // ⚠ The SIBLING half of #1757 is NOT equivalent and stays: `spliceSubtree`\n // still reports the names the splice actually took, because the lifecycle\n // registry is the one registry `add`/`replace` never gated — an external\n // guard can still be registered for a dotted name that is not a route, and\n // clearing it by prefix is the fail-open #1757 was filed for. See\n // `removeRoute.test.ts`.\n const isInRemovedSubtree =\n isExactMatch || currentStateName.startsWith(`${name}.`);\n\n if (isInRemovedSubtree) {\n const suffix = isExactMatch ? \"\" : ` (current: \"${currentStateName}\")`;\n\n logger.warn(\n \"router.removeRoute\",\n `Cannot remove route \"${name}\" — it is currently active${suffix}. Navigate away first.`,\n );\n\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Reports a removal that landed while a navigation was in flight (#1756).\n *\n * ⚠ Called from the `remove()` door AFTER the removal is known to have\n * happened — deliberately NOT from `validateRemoveRoute`, which runs above the\n * existence check. Everything below is a consequence of a route leaving the\n * tree, so for a `name` that is no route there is nothing to report and the\n * door's own \"not found. No changes made.\" is the whole story. Warning from the\n * gate produced two adjacent, contradicting lines out of one call.\n *\n * @param name - Route name that was removed\n * @param logger - Per-router logger instance (from `getInternals(router).logger`)\n */\nexport function warnRemovalDuringNavigation(\n name: string,\n logger: RouterLogger,\n): void {\n // ⚑ Says the MECHANISM, not \"may cause unexpected behavior\" (#1756). The\n // removal proceeds — deliberately: the guard above protects the COMMITTED\n // state, and the route being navigated TO is not it. If the removed subtree\n // is on the in-flight navigation's path, that navigation is cancelled by\n // the commit door instead, and the committed state is left untouched. So\n // the outcome is safe in both directions.\n //\n // ⚠ \"safe\" needs a WELL-FORMED tree, and every tree is one: bare core refuses\n // a dotted route name at registration (#1763), so the shape below is\n // UNCONSTRUCTIBLE rather than merely rare. Kept as the record of why the door\n // and this guard ask different questions. The commit door asks\n // `hasRoute(toState.name)` — the terminal only, never its ancestors — while\n // this guard's refusal covers the whole dotted ancestry. When an ancestor is\n // a SEPARATE definition rather than a `children` entry, removing it does not\n // take the descendant with it, the door sees a live terminal, and the\n // navigation commits with `transition.segments.activated` naming a route\n // `has()` denies: `buildPath` on that segment throws and `isActiveRoute`\n // answers true for it. With nested `children` the subtree goes together and\n // the door refuses, which is the shape this comment describes.\n //\n // ⚠ The gap this closes is NARROWER than \"the caller cannot tell\", and\n // narrower than \"the rejection already carries the removed route's name\":\n // measured, it carries\n // the name the COMMIT DOOR could not find — the navigation's TARGET — on the\n // async arcs as `ROUTE_NOT_FOUND { routeName }` directly and on the sync arc\n // threaded through `asCancellation` as `error.reason`. Those coincide only\n // when you removed the target itself. Remove an ANCESTOR of it — the shape\n // this warning's own parenthetical calls out, and the one #1756 reproduces —\n // and the payload names the descendant, never the route you passed to\n // `remove()`. So nothing in the error connects the failure to the call that\n // caused it, and the WARNING is the only place that can.\n //\n // ⚠ It cannot name WHICH of the two happened: telling \"you removed the\n // route you are navigating to\" from \"you removed an unrelated route\" needs\n // the in-flight target, and `RouterInternals` deliberately exposes no\n // handle on the navigation in flight. Saying both outcomes is the honest\n // form until that changes.\n //\n // ⚠ It prints the code VALUES, not the `errorCodes` keys:\n // `errorCodes.TRANSITION_CANCELLED === \"CANCELLED\"` (`constants.ts`), so a\n // caller who matched the key read out of a log line would never match.\n //\n // ⚠ And it splits the two codes by CHANNEL, not only by arc, because they\n // do not agree on the synchronous one. Measured: the rejected `navigate()`\n // promise carries `\"CANCELLED\"` there while `onTransitionError` carries\n // `\"ROUTE_NOT_FOUND\"` — one failure, two codes, depending on where the\n // caller is listening. `onTransitionCancel` never fires on this path at\n // all (`CANCEL` is sent only by `stop()`/`dispose()` and the\n // external-signal bridge), so the hook is the STABLE predicate of the two\n // and the sentence says which is which. Naming the arc split and then\n // appending the hook reads as \"the hook carries these codes\" — true on the\n // async arc, false on the sync one.\n //\n // ⚠ It names BOTH failure codes, and neither alone is right.\n // `TRANSITION_CANCELLED` holds only\n // while the guard walk is still synchronous, where `handleNavigateError`\n // finds the machine already out of the band and rewraps. Once the walk has\n // gone async the raw `ROUTE_NOT_FOUND` from the commit door reaches the\n // caller unwrapped. Measured on four arcs: sync guard `CANCELLED`, async\n // activate / async deactivate / async `subscribeLeave` all `ROUTE_NOT_FOUND`.\n //\n // ⚠ And it SAYS the removal happened, because it is only reached once it\n // has. Above the existence check this warning contradicts itself one line\n // later: `remove(\"nope\")` mid-navigation reports `Route \"nope\" removed` and\n // then `Route \"nope\" not found. No changes made.` What holds it there is a\n // PROPERTY — no in-flight report when nothing was removed — rather than the\n // wording of this sentence.\n logger.warn(\n \"router.removeRoute\",\n `Route \"${name}\" removed while navigation is in progress. Removing a route the ` +\n `router is navigating to (or an ancestor of it) fails that navigation. The ` +\n `rejected navigate() promise carries \"CANCELLED\" while the guard walk is ` +\n `synchronous and \"ROUTE_NOT_FOUND\" once it has gone async; onTransitionError ` +\n `always reports \"ROUTE_NOT_FOUND\", and onTransitionCancel never fires. The ` +\n `committed state is not affected either way.`,\n );\n}\n\n/** The root path minus its `?`-declared query names — the half that moves paths. */\nfunction pathPartOf(rootPath: string): string {\n const queryAt = rootPath.indexOf(\"?\");\n\n return queryAt === -1 ? rootPath : rootPath.slice(0, queryAt);\n}\n\n/**\n * Validates a `setRootPath` against an in-flight navigation (#1755).\n *\n * `applyRootPath` rebuilds the tree AND the matcher from the same definitions\n * (`routesStore.ts`), so every route name survives and every route's path is\n * REBUILT under the new root — which moves them all at once whenever the root's\n * path half changes. That is the same whole-tree REBUILD `clear` and `replace`\n * are refused for (not destruction: those two can drop names, this one never\n * does), and of the three it was the only one that applied anyway. A\n * navigation's activation guard could move the URL out from under its own\n * transition, and the transition then committed a state naming a route the tree\n * no longer routes that path to — which the same navigation's success announce\n * hands straight to every URL plugin, address bar included.\n *\n * ⚠ \"the only one that applied\" is about the three whole-tree ops, not about\n * the six doors: `add` proceeds with no check at all and `update` proceeds after\n * a log. Their in-flight policy is deliberately different — see the CRUD table\n * in `packages/core/CLAUDE.md`.\n *\n * ⚠ The refusal is `logger.error` + no-op, NOT a throw, and that follows the\n * family's own rule rather than `setRootPath`'s neighbours on `PluginApi`: a\n * condition that clears by itself (a navigation settles) gets a log, one that\n * never does gets a throw. The reentrancy ban beside this one throws for\n * exactly that reason — a `TREE_CHANGED` dispatch is not something you can wait\n * out from inside it.\n *\n * @param currentRootPath - The root path in effect\n * @param nextRootPath - The root path being set\n * @param isNavigating - Whether navigation is in progress\n * @param logger - Per-router logger instance (from `getInternals(router).logger`)\n * @returns true if setRootPath can proceed, false if blocked\n */\nexport function validateSetRootPath(\n currentRootPath: string,\n nextRootPath: string,\n isNavigating: boolean,\n logger: RouterLogger,\n): boolean {\n // Only the PATH half of the root moves route paths; the `?name` half declares\n // query params on every route and moves nothing. Measured, with the gate off:\n // `\"\" → \"/app\"` mid-navigation commits `state.path` the tree cannot match,\n // while `\"\" → \"?lang\"` and `\"?lang\" → \"\"` both commit a state that still\n // round-trips. So the refusal is scoped to the half that does the damage.\n //\n // ⚑ Scoping it is not a nicety — the whole-string form was a REGRESSION.\n // `@real-router/persistent-params-plugin` declares its keys with a query-only\n // root (`setRootPath(\"?lang\")`) and restores the original in `teardown()`. An\n // `unsubscribe()` reached from a guard or a `subscribeLeave` listener would\n // have found that restore silently refused — no throw, so the plugin's own\n // `catch` could not see it — leaving `?lang` declared on a router the caller\n // believes is clean, where a later `navigate(\"x\", { lang })` throws\n // `WRONG_CHANNEL` for a plugin that is no longer installed.\n if (\n isNavigating &&\n pathPartOf(currentRootPath) !== pathPartOf(nextRootPath)\n ) {\n logger.error(\n \"router.setRootPath\",\n \"Cannot change the root PATH while navigation is in progress — it moves every route's path, including the one being navigated to. Wait for navigation to complete. (Changing only the `?`-declared query names is allowed here: it moves no paths.)\",\n );\n\n return false;\n }\n\n return true;\n}\n\n/**\n * Validates clearRoutes operation.\n * Returns false if operation should be blocked (navigation in progress).\n *\n * @param isNavigating - Whether navigation is in progress\n * @param logger - Per-router logger instance (from `getInternals(router).logger`)\n * @returns true if clearRoutes can proceed, false if blocked\n */\nexport function validateClearRoutes(\n isNavigating: boolean,\n logger: RouterLogger,\n): boolean {\n if (isNavigating) {\n logger.error(\n \"router.clearRoutes\",\n \"Cannot clear routes while navigation is in progress. Wait for navigation to complete.\",\n );\n\n return false;\n }\n\n return true;\n}\n","import { assertShippedChannelCorrect } from \"../channels\";\nimport { buildURL, canonicalize, materialize } from \"../pipeline\";\nimport { throwIfDisposed, throwIfReentrantTreeMutation } from \"./helpers\";\nimport { errorCodes } from \"../constants\";\nimport {\n assertEventNameIsValid,\n assertInterceptableSeam,\n assertListenerIsFunction,\n} from \"../guards\";\nimport { adoptChannel } from \"../helpers\";\nimport { getInternals, throwOnMisChanneledKey } from \"../internals\";\nimport { validateSetRootPath } from \"../namespaces/RoutesNamespace/routeGuards\";\nimport { RouterError, freezeThrownError } from \"../RouterError\";\nimport { putField } from \"../utils/ingest\";\n\nimport type { PluginApi } from \"./types\";\nimport type {\n ContextNamespaceClaim,\n DefaultDependencies,\n Params,\n Router,\n SearchParams,\n State,\n} from \"../types\";\n\n/** Captured like the deciding seven, but this one BUILDS the guarantee (#2073). */\nconst freeze = Object.freeze;\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst objectKeys = Object.keys;\n\n// Cache the assembled PluginApi per router — mirrors getNavigator() (#525):\n// avoids re-allocating the closure-bag on each call (plugins call this once\n// at init, but tests + nested plugins poll it).\n//\n// ⚠ A stub belongs one layer DOWN, on `getInternals(router)` (#1805): a spy\n// there intercepts a call made through this surface — measured — while a spy\n// HERE would land on the object nineteen packages share.\n//\n// ⚠ Members fall into THREE classes, and the advice differs per class: one\n// CALLS `ctx.<name>()` and is intercepted whenever the spy stands, one ALIASES\n// `ctx.<name>` and captures it when this cached surface is BUILT — so a spy\n// installed afterwards is missed — and one composes its answer locally and has\n// no seam at all. Which member is which is derived, not listed here:\n// `tests/functional/plugin-api-stub-seam-authority-1805.test.ts` owns the sets\n// and measures the order-dependence, because naming them in prose has been\n// wrong twice.\nconst cache = new WeakMap<object, PluginApi>();\n\nexport function getPluginApi<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(router: Router<Dependencies>): PluginApi {\n const cached = cache.get(router);\n\n if (cached) {\n return cached;\n }\n\n const ctx = getInternals(router);\n const api: PluginApi = {\n makeState: (name, params, search, path) => {\n throwOnMisChanneledKey(ctx, \"makeState\", name, params);\n\n // ⚑ Core's SINGLE read, taken before the validator judges (#2134). The\n // façade doors do the same; these three are the plugin-facing half of the\n // same defect, where the caller is a plugin author rather than an app.\n const ownParams = adoptChannel(params);\n\n ctx.validator?.state.validateMakeStateArgs(name, ownParams, path);\n ctx.validator?.navigation.validateSearch(search, \"makeState\");\n\n // Public PluginApi.makeState carries the query channel (RFC-4 M2 / #1548)\n // so plugins (e.g. browser-plugin popstate restore) can reconstruct a\n // split state from a serialized history entry. It takes no per-segment\n // param-source map: ownership is read from the live matcher by\n // `state.name`, so nothing a caller could supply there would be consulted.\n return ctx.makeState(name, ownParams, search, path);\n },\n forwardState: <\n P extends Params = Params,\n S extends SearchParams = SearchParams,\n >(\n routeName: string,\n routeParams: P,\n routeSearch?: S,\n ) => {\n // ⚠ NO copy here, and the difference from the two doors around it is\n // measured rather than stylistic (#2134). Those PRINT a URL out of the\n // bag, so a read the validator did not see reaches the user; this one\n // hands the container back — by IDENTITY on a clean bag, which\n // `handed-out-containers-1957` pins. Measured on a non-forwarding route,\n // core reads the bag ZERO times through this door: there is no shipped\n // read for a judged one to disagree with, and a copy would buy nothing at\n // the price of that identity.\n ctx.validator?.routes.validateStateBuilderArgs(\n routeName,\n routeParams,\n \"forwardState\",\n );\n ctx.validator?.navigation.validateSearch(routeSearch, \"forwardState\");\n\n return ctx.forwardState<P, S>(routeName, routeParams, routeSearch);\n },\n matchPath: (path) => {\n ctx.validator?.routes.validateMatchPathArgs(path);\n\n return ctx.matchPath(path, ctx.getOptions());\n },\n navigateToState: (state, options) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.navigation.validateNavigateToStateArgs(state);\n\n if (options !== undefined) {\n ctx.validator?.navigation.validateNavigationOptions(\n options,\n \"navigateToState\",\n );\n }\n\n return ctx.navigateToState(state, options);\n },\n setRootPath: (rootPath) => {\n throwIfDisposed(ctx.isDisposed);\n // The sixth tree mutator, and the one that joined the family late (#1751).\n // `applyRootPath` rebuilds tree AND matcher, so a call from inside a\n // `subscribeChanges` handler swaps what the router resolves against while\n // the listeners still queued in that dispatch reason about the payload's\n // tree. Ordered AFTER `throwIfDisposed` deliberately: `dispose()` sends\n // DISPOSE before `clearAll()`, and `clearAll()` leaves `#dispatching`\n // standing (#1164), so both predicates are true during a teardown reached\n // from a handler — `ROUTER_DISPOSED` has to keep winning there.\n throwIfReentrantTreeMutation(ctx.treeChanged.isEmitting);\n\n ctx.validator?.routes.validateSetRootPathArgs(rootPath);\n\n // ⚑ Returns whether it APPLIED, and that is the one place this door\n // departs from its route-CRUD siblings (all `void` + log). It has to: the\n // siblings are application-facing, where a human reads the console, while\n // this one is plugin-facing, and the caller that most needs the answer is\n // a `teardown()`. The refusal's whole justification — \"a condition that\n // clears by itself gets a log\" — is FALSE for a teardown: the plugin will\n // never call again, so a refused restore is permanent and, returning\n // `void`, undetectable. Measured: a plugin holding a path prefix, torn\n // down mid-navigation, leaked that prefix forever.\n //\n // ⚑ The sixth member of the in-flight family rule, and the last to join it\n // (#1755). Validation runs ABOVE it: an argument-shape defect is the\n // caller's bug whatever the router is doing, while this refusal is about\n // timing, and reporting the timing first would hide a `TypeError` behind a\n // log line the caller did not cause.\n //\n // ⚠ That matches `remove` (and `update`'s argument half) and CONTRADICTS\n // `replace`, which puts its in-flight gate above `guardRouteStructure` and\n // every validator. The family is not uniform on this axis, so the ordering\n // is chosen on its merits here rather than copied — do not read it as a\n // convention.\n if (\n !validateSetRootPath(\n ctx.getRootPath(),\n rootPath,\n ctx.isTransitioning(),\n ctx.logger,\n )\n ) {\n return false;\n }\n\n ctx.setRootPath(rootPath);\n\n return true;\n },\n getRootPath: ctx.getRootPath,\n addEventListener: (eventName, cb) => {\n throwIfDisposed(ctx.isDisposed);\n\n assertEventNameIsValid(eventName);\n assertListenerIsFunction(cb);\n ctx.validator?.eventBus.validateListenerArgs(eventName, cb);\n\n return ctx.addEventListener(eventName, cb);\n },\n buildNavigationState: (name, params = {}, search = {}) => {\n throwOnMisChanneledKey(ctx, \"buildNavigationState\", name, params);\n\n const ownParams = adoptChannel(params);\n\n ctx.validator?.navigation.validateSearch(search, \"buildNavigationState\");\n ctx.validator?.routes.validateStateBuilderArgs(\n name,\n ownParams,\n \"buildNavigationState\",\n );\n\n // Stages ① + ③ + the mode gate, one pass through the pipeline\n // (nav-pipeline Phase 2, step 2-4). `search` flows THROUGH the forwardState\n // seam, not past it (#1571) — `port.resolveForward` IS `ctx.forwardState`,\n // so the seam is still where an explicit query value wins over a declared\n // twin the caller rode in `params`, and where a `search-schema`\n // interceptor sees the query channel.\n const canonical = canonicalize(ctx.port(), name, ownParams, search, {\n diagnoseUndeclared: true,\n });\n\n // Existence is checked BEFORE the URL is built, and the order is\n // load-bearing: `buildURL` prints through the matcher, which throws on an\n // unknown route, whereas this entry point answers `undefined` for one —\n // including when a `forwardTo` chain resolves to a target that does not\n // exist. (`canonicalize` itself is total here: a missing route simply has\n // no defaults and no declared query names.)\n if (!ctx.buildStateResolved(canonical.name, canonical.path)) {\n return;\n }\n\n // ⑤a then ⑤b from ONE canonical intent, so `state.search` and `state.path`\n // cannot derive from differently-merged bags.\n assertShippedChannelCorrect(\n \"buildNavigationState\",\n canonical.name,\n canonical.path,\n ctx.port().queryNames(canonical.name),\n );\n\n return materialize(canonical, buildURL(canonical, ctx.port()));\n },\n getOptions: ctx.getOptions,\n getTree: ctx.getTree,\n addInterceptor: (method, fn) => {\n throwIfDisposed(ctx.isDisposed);\n assertInterceptableSeam(method, fn);\n\n let list = ctx.interceptors.get(method);\n\n if (!list) {\n list = [];\n ctx.interceptors.set(method, list);\n }\n\n list.push(fn);\n\n // Idempotency flag (#1198). Without it, a double call would `indexOf(fn)`\n // again and splice a DUPLICATE registration of the same fn — silently\n // deactivating another plugin's interceptor whose own unsubscribe was never\n // called. The `Unsubscribe` contract is documented idempotent. The flag\n // guarantees exactly one splice of a still-present `fn`, so no `index !== -1`\n // guard is needed (it would be dead — the second call returns above).\n let removed = false;\n\n return () => {\n if (removed) {\n return;\n }\n\n removed = true;\n list.splice(list.indexOf(fn), 1);\n };\n },\n getRouteConfig: (name) => {\n const store = ctx.routeGetStore();\n\n // Stryker disable next-line ConditionalExpression,BlockStatement: equivalent — a missing route yields routeCustomFields[name] === undefined, identical to this early return\n if (!store.matcher.hasRoute(name)) {\n return;\n }\n\n return store.routeCustomFields[name];\n },\n extendRouter: (extensions: Record<string, unknown>) => {\n throwIfDisposed(ctx.isDisposed);\n\n const keys = objectKeys(extensions);\n\n for (const key of keys) {\n if (key in router) {\n throw freezeThrownError(\n new RouterError(errorCodes.PLUGIN_CONFLICT, {\n message: `Cannot extend router: property \"${key}\" already exists`,\n }),\n );\n }\n }\n\n // ⚑ Every value is read BEFORE any is written (#1933). `extensions` is\n // the caller's object, so each read is a call into application code, and a\n // loop that reads and writes together installs keys it can then abandon\n // mid-way: nothing tracks them, so no unsubscribe carries them, the\n // `dispose()` safety net walks a record that was never pushed, and every\n // later plugin claiming one of those names is refused for the life of the\n // router. Prepare-then-commit, the same shape route CRUD uses.\n //\n // ⚠ One read per key either way — the reads MOVE, they do not multiply.\n const values = keys.map((key) => extensions[key]);\n\n const extensionRecord = { keys };\n\n for (const [index, key] of keys.entries()) {\n (router as Record<string, unknown>)[key] = values[index];\n }\n\n ctx.routerExtensions.push(extensionRecord);\n\n let removed = false;\n\n return () => {\n if (removed) {\n return;\n }\n\n removed = true;\n\n for (const key of extensionRecord.keys) {\n delete (router as Record<string, unknown>)[key];\n }\n\n const idx = ctx.routerExtensions.indexOf(extensionRecord);\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,UnaryOperator,BlockStatement: equivalent — this splice only tidies the `routerExtensions` TRACKING array; the router INSTANCE is cleaned by the `delete router[key]` loop above, and dispose()'s safety-net re-deletes any leaked key harmlessly. So no mutation of this guard/splice is behaviourally observable (full suite green with `===`, `+1`, and an empty body). Contrast the addInterceptor splice, which IS observable through buildPath and is killed behaviourally by invariantGuardMutants.test.ts.\n if (idx !== -1) {\n ctx.routerExtensions.splice(idx, 1);\n }\n };\n },\n emitTransitionError: (error) => {\n throwIfDisposed(ctx.isDisposed);\n ctx.emitTransitionError(error);\n },\n claimContextNamespace: (namespace: string) => {\n throwIfDisposed(ctx.isDisposed);\n\n // Input-shape guard, symmetric with the other always-on invariant guards\n // (subscribe / start / navigateToNotFound each typeof-check their input).\n // A non-string namespace coerces to an inconsistent key (\"42\"); an empty\n // string is a meaningless namespace (#1191 N4).\n if (typeof namespace !== \"string\" || namespace === \"\") {\n throw new TypeError(\n `[claimContextNamespace] namespace must be a non-empty string, got ${\n typeof namespace === \"string\" ? \"an empty string\" : typeof namespace\n }`,\n );\n }\n\n if (ctx.contextClaimRecords.has(namespace)) {\n throw freezeThrownError(\n new RouterError(errorCodes.CONTEXT_NAMESPACE_ALREADY_CLAIMED, {\n message: `Cannot claim context namespace: \"${namespace}\" is already claimed by another plugin`,\n }),\n );\n }\n\n // ⚑ The record stores the CLAIM, not just its name, so both methods below\n // can ask whether they are still the holder (#2059 / #1929). Without that\n // identity a released claim is indistinguishable from the live one, and\n // the \"two plugins clobber each other\" corruption this mechanism exists to\n // prevent is reachable with the records perfectly consistent.\n const claim: ContextNamespaceClaim = {\n write(state: State, value: unknown) {\n if (ctx.contextClaimRecords.get(namespace) !== claim) {\n return;\n }\n\n // ⚑ `putField`, not the `namespace === \"__proto__\"` special case this\n // replaces (#1191 N3 → #1852). That form closed exactly one LITERAL,\n // and the key here is a plugin's namespace: the names that hurt are\n // the ordinary ones the shipped plugins already use. Measured on a\n // real navigation with an ambient `data` / `rsc` accessor, the outcome\n // was not even an error the caller could see — `claim.write` runs from\n // an `onTransitionSuccess` hook, so the emitter's throw isolation ate\n // it, `start()` resolved, and `getState().context` was `{}`.\n putField(state.context, namespace, value);\n },\n release() {\n if (ctx.contextClaimRecords.get(namespace) !== claim) {\n return;\n }\n\n ctx.contextClaimRecords.delete(namespace);\n },\n };\n\n ctx.contextClaimRecords.set(namespace, claim);\n\n return claim;\n },\n };\n\n // ⚑ FROZEN, and the cache above is what makes it necessary (#1805). One object\n // per router is handed to EVERY consumer, so a single\n // `api.addInterceptor = …` — the shape an \"instrument everything\" line takes —\n // rewires the surface for all of them silently. The consumer count is\n // deliberately not restated, for the reason its twin at `getRoutesApi` gives:\n // it grows with the tier while the hazard is the sharing, which one consumer\n // is enough to have. `getRoutesApi` and\n // `getNavigator` next door are frozen for the same reason; the two UNCACHED\n // factories (`getLifecycleApi`, `getDependenciesApi`) need nothing, because a\n // write to a per-call object cannot reach a second consumer.\n const frozen = freeze(api);\n\n cache.set(router, frozen);\n\n return frozen;\n}\n","import { nodeToDefinition } from \"../engine\";\nimport { throwIfDisposed, throwIfReentrantTreeMutation } from \"./helpers\";\nimport { errorCodes } from \"../constants\";\nimport { guardRouteCallbacks, guardRouteStructure } from \"../guards\";\nimport { getInternals } from \"../internals\";\nimport {\n assertRouteDefaultChannelsFor,\n clearConfigEntries,\n spliceSubtree,\n} from \"../namespaces/RoutesNamespace/helpers\";\nimport {\n validateClearRoutes,\n validateRemoveRoute,\n warnRemovalDuringNavigation,\n} from \"../namespaces/RoutesNamespace/routeGuards\";\nimport {\n adoptRouteArtifacts,\n assertAddable,\n assertNoDuplicateNamesInBatch,\n assertNoDuplicatePathsInBatch,\n assertNoDottedNamesInBatch,\n assertNonEmptyNamesInBatch,\n assertNoInternalNamesInBatch,\n assertNoInternalRouteName,\n buildAddArtifacts,\n buildReplaceArtifacts,\n commitRouteUpdate,\n commitTreeChanges,\n compileArtifactGuards,\n resetStore,\n} from \"../namespaces/RoutesNamespace/routesStore\";\nimport { RouterError, freezeThrownError } from \"../RouterError\";\nimport { getTransitionPath } from \"../transitionPath\";\nimport { putField } from \"../utils/ingest\";\n\nimport type { RoutesApi } from \"./types\";\nimport type { RouteDefinition, RouteTree } from \"../engine\";\nimport type { RouterInternals } from \"../internals\";\nimport type { RouteLifecycleNamespace, RouteConfig } from \"../namespaces\";\nimport type { RoutesStore } from \"../namespaces/RoutesNamespace\";\nimport type {\n DefaultDependencies,\n ForwardToCallback,\n NavigationOptions,\n Params,\n ParamsSearch,\n SearchParams,\n Router,\n RouterLogger,\n State,\n TreeChangedEvent,\n TreeStructuralPatch,\n GuardFnFactory,\n Route,\n} from \"../types\";\n\n/** Captured like the deciding seven, but this one BUILDS the guarantee (#2073). */\nconst freeze = Object.freeze;\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst objectKeys = Object.keys;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Opts attached to the `TRANSITION_SUCCESS` emitted by `replace()` when it\n * revalidates the active state (#950). `replace` does not push history, so it\n * is a replace-type success — matching `navigateToNotFound`'s opts for the\n * dropped-route branch.\n */\nconst REVALIDATE_OPTS: NavigationOptions = Object.freeze({\n replace: true,\n revalidate: true,\n});\n\n/** `removeRoute`'s \"removed, but nobody is listening\" payload. */\nconst EMPTY_SUBTREE: readonly never[] = Object.freeze([]);\n\n/**\n * Clears all config entries and lifecycle handlers for exactly the routes the\n * removal took out of the tree — `removedNames` is the splice's own report, not\n * a name-prefix guess (#1757).\n */\nfunction clearRouteConfigurations<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n removedNames: ReadonlySet<string>,\n config: RouteConfig,\n routeCustomFields: Record<string, Record<string, unknown>>,\n lifecycleNamespace: RouteLifecycleNamespace<Dependencies>,\n): void {\n // ⚑ The set comes from the SPLICE (`spliceSubtree`), not from the name string\n // (#1757). `name === routeName || name.startsWith(routeName + \".\")` asks a\n // strictly wider question: a flat dotted leaf `x.y` declared BESIDE `x` is a\n // standalone node the splice never touches, and the prefix claims it anyway.\n // The route then stays in the tree with its config and its guards\n // unregistered — a FAIL-OPEN, since a blocking `canActivate` simply\n // disappears and the route becomes freely activatable, with no log.\n const shouldClear = (name: string): boolean => removedNames.has(name);\n\n clearConfigEntries(config.decoders, shouldClear);\n clearConfigEntries(config.encoders, shouldClear);\n clearConfigEntries(config.defaultParams, shouldClear);\n clearConfigEntries(config.defaultSearch, shouldClear);\n clearConfigEntries(config.forwardMap, shouldClear);\n clearConfigEntries(config.forwardFnMap, shouldClear);\n clearConfigEntries(routeCustomFields, shouldClear);\n\n // Clear forwardMap entries pointing TO the deleted route (or its descendants)\n clearConfigEntries(config.forwardMap, (key) =>\n shouldClear(config.forwardMap[key]),\n );\n\n // Clear lifecycle handlers\n const [canDeactivateFactories, canActivateFactories] =\n lifecycleNamespace.getFactories();\n\n for (const name of objectKeys(canActivateFactories)) {\n if (shouldClear(name)) {\n // Route removed from the tree — both origin slots go (route no longer exists).\n lifecycleNamespace.clearCanActivate(name, \"both\");\n }\n }\n\n for (const name of objectKeys(canDeactivateFactories)) {\n if (shouldClear(name)) {\n lifecycleNamespace.clearCanDeactivate(name, \"both\");\n }\n }\n}\n\n/**\n * Re-attaches the stored config (forwardTo / defaultParams / encode-decode) and\n * lifecycle guards for `lookupName` onto `route`, then returns it (mutates in\n * place). Shared by {@link enrichRoute} (nested, bare `name`) and\n * {@link buildFlatRoute} (flat, full dotted `name`) — one source of truth for\n * the route-config field set.\n */\nfunction assignRouteConfig<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n route: Route<Dependencies>,\n lookupName: string,\n config: RouteConfig,\n factories: [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ],\n): Route<Dependencies> {\n const forwardToFn = config.forwardFnMap[lookupName];\n const forwardToStr = config.forwardMap[lookupName];\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (forwardToFn !== undefined) {\n route.forwardTo = forwardToFn;\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n } else if (forwardToStr !== undefined) {\n route.forwardTo = forwardToStr;\n }\n\n if (lookupName in config.defaultParams) {\n route.defaultParams = config.defaultParams[lookupName];\n }\n\n if (lookupName in config.defaultSearch) {\n route.defaultSearch = config.defaultSearch[lookupName];\n }\n\n if (lookupName in config.decoders) {\n route.decodeParams = config.decoders[lookupName];\n }\n\n if (lookupName in config.encoders) {\n route.encodeParams = config.encoders[lookupName];\n }\n\n const [canDeactivateFactories, canActivateFactories] = factories;\n\n if (lookupName in canActivateFactories) {\n route.canActivate = canActivateFactories[lookupName];\n }\n\n if (lookupName in canDeactivateFactories) {\n route.canDeactivate = canDeactivateFactories[lookupName];\n }\n\n return route;\n}\n\n/**\n * Builds a full Route object from a bare RouteDefinition by re-attaching\n * config entries and lifecycle factories.\n *\n * RECURSIVE — call with the factories tuple obtained ONCE from\n * `lifecycleNamespace.getFactories()` and pass it through to children.\n */\nfunction enrichRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routeDef: RouteDefinition,\n routeName: string,\n config: RouteConfig,\n factories: [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ],\n): Route<Dependencies> {\n const route: Route<Dependencies> = {\n name: routeDef.name,\n path: routeDef.path,\n };\n\n assignRouteConfig(route, routeName, config, factories);\n\n if (routeDef.children) {\n // ⚑ `putField`, the same write rule the registration walk carries\n // (#1852 / #2139). `route` is a literal with `name` and `path` on it, so\n // `children` has no own slot and a plain assignment walks the prototype.\n // Measured on this door: under an ambient `children` setter `get(name)`\n // returned a route whose children had gone into the setter, and under a\n // getter-only accessor it THREW instead of answering.\n putField(\n route as unknown as Record<string, unknown>,\n \"children\",\n routeDef.children.map((child) =>\n enrichRoute(child, `${routeName}.${child.name}`, config, factories),\n ),\n );\n }\n\n return route;\n}\n\n// ============================================================================\n// TREE_CHANGED payload helpers\n// ============================================================================\n\n/**\n * Builds a single FLAT `Route` for `fullName` from the store config + lifecycle\n * factories — `name` is the FULL dotted name and there is no `children` array\n * (consumers want a flat, by-name list). Frozen on construction.\n */\nfunction buildFlatRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n fullName: string,\n path: string,\n config: RouteConfig,\n factories: [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ],\n): Route<Dependencies> {\n const route: Route<Dependencies> = { name: fullName, path };\n\n assignRouteConfig(route, fullName, config, factories);\n\n return freeze(route);\n}\n\n/**\n * Walks the store's definitions depth-first, building a FLAT\n * `Map<fullName, Route>` for every node whose full dotted name satisfies\n * `include`. Reads the live store, so call it at the right moment relative to\n * the mutation (before for removed, after for added).\n */\nfunction collectFlatRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n include: (fullName: string) => boolean,\n): Map<string, Route<Dependencies>> {\n const result = new Map<string, Route<Dependencies>>();\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const factories = store.lifecycleNamespace!.getFactories();\n\n const walk = (defs: readonly RouteDefinition[], parentName: string): void => {\n for (const def of defs) {\n const fullName = parentName ? `${parentName}.${def.name}` : def.name;\n\n if (include(fullName)) {\n result.set(\n fullName,\n buildFlatRoute(fullName, def.path, store.config, factories),\n );\n }\n\n if (def.children) {\n walk(def.children, fullName);\n }\n }\n };\n\n walk(store.definitions, \"\");\n\n return result;\n}\n\n/**\n * Collects the routes named by `removedNames` as a FLAT, frozen array — the\n * `TREE_CHANGED` payload for a removal.\n *\n * MUST be called AFTER the definitions splice (so the set is known) and BEFORE\n * `clearRouteConfigurations` + `commitTreeChanges` (so the store still carries\n * the config and the tree the payload is built from).\n *\n * ⚑ Driven by the splice's own set rather than by the name prefix (#1757): the\n * prefix form named a flat dotted namesake that `has()` still answers `true`\n * for, i.e. it announced the removal of a live route — the lying-event shape of\n * #1194 manifestation (1), reached through `remove`.\n */\nfunction collectSubtree<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n removedNames: ReadonlySet<string>,\n): readonly Route<Dependencies>[] {\n const subtree = collectFlatRoutes(store, (fullName) =>\n removedNames.has(fullName),\n );\n\n return freeze([...subtree.values()]);\n}\n\n/**\n * Builds the FLAT, frozen payload array for an `add`, walking only the input\n * routes — O(added), not O(tree). `path` is taken from the input verbatim\n * (`sanitizeRoute` never rewrites it); config fields are read from the\n * post-commit store by full name. `add` never removes, so the input subtree is\n * exactly what changed.\n */\nfunction collectAddedRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routes: readonly Route<Dependencies>[],\n parentName: string | undefined,\n store: RoutesStore<Dependencies>,\n): readonly Route<Dependencies>[] {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const factories = store.lifecycleNamespace!.getFactories();\n const result: Route<Dependencies>[] = [];\n\n const walk = (\n input: readonly Route<Dependencies>[],\n parent: string,\n ): void => {\n for (const route of input) {\n const fullName = parent ? `${parent}.${route.name}` : route.name;\n\n result.push(\n buildFlatRoute(fullName, route.path, store.config, factories),\n );\n\n if (route.children) {\n walk(route.children, fullName);\n }\n }\n };\n\n walk(routes, parentName ?? \"\");\n\n return freeze(result);\n}\n\n/** Diffs two flat route maps by full name into frozen removed/added arrays. */\nfunction diffFlatRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n before: ReadonlyMap<string, Route<Dependencies>>,\n after: ReadonlyMap<string, Route<Dependencies>>,\n): {\n removed: readonly Route<Dependencies>[];\n added: readonly Route<Dependencies>[];\n} {\n const removed: Route<Dependencies>[] = [];\n const added: Route<Dependencies>[] = [];\n\n for (const [fullName, route] of before) {\n if (!after.has(fullName)) {\n removed.push(route);\n }\n }\n\n for (const [fullName, route] of after) {\n if (!before.has(fullName)) {\n added.push(route);\n }\n }\n\n return { removed: freeze(removed), added: freeze(added) };\n}\n\n/**\n * Builds the structural subset of an `update()` patch (forwardTo /\n * defaultParams / encodeParams / decodeParams) from the already-destructured\n * update fields — so user getters are not re-invoked. A guard-only patch yields\n * an empty object → the caller emits no TREE_CHANGED (O-7: guards are\n * invoked-on-demand, not cached, so they need no observation channel).\n *\n * The returned envelope is a fresh object (caller's patch untouched) and is\n * frozen on construction. Nested values (e.g. `defaultParams`) are kept by\n * reference — the same objects the router stored — so exotic inputs (circular\n * refs, class instances) are tolerated, matching `update()`'s existing contract.\n */\nfunction buildStructuralPatch<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(fields: {\n forwardTo?: string | ForwardToCallback<Dependencies> | null | undefined;\n defaultParams?: Params | null | undefined;\n defaultSearch?: SearchParams | null | undefined;\n decodeParams?: ((channels: ParamsSearch) => ParamsSearch) | null | undefined;\n encodeParams?: ((channels: ParamsSearch) => ParamsSearch) | null | undefined;\n}): Readonly<TreeStructuralPatch<Dependencies>> {\n const patch: TreeStructuralPatch<Dependencies> = {};\n\n if (fields.forwardTo !== undefined) {\n patch.forwardTo = fields.forwardTo;\n }\n\n if (fields.defaultParams !== undefined) {\n patch.defaultParams = fields.defaultParams;\n }\n\n if (fields.defaultSearch !== undefined) {\n patch.defaultSearch = fields.defaultSearch;\n }\n\n if (fields.encodeParams !== undefined) {\n patch.encodeParams = fields.encodeParams;\n }\n\n if (fields.decodeParams !== undefined) {\n patch.decodeParams = fields.decodeParams;\n }\n\n return freeze(patch);\n}\n\n// ============================================================================\n// CRUD operations\n// ============================================================================\n\n/**\n * Adds one or more routes to the router.\n *\n * Takes the SNAPSHOT, not the caller's array, so every guard below this\n * validates what registration stores.\n */\nfunction addRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n batch: readonly Route<Dependencies>[],\n parentName: string | undefined,\n logger: RouterLogger,\n): void {\n // Prepare-then-commit (issue #698): reject the silent-corruption cases\n // up front (dup name vs existing, missing parent), build the merged tree /\n // config into locals (async/circular forwardTo + invalid constraint throw\n // here), then swap atomically. A rejected add leaves the store untouched.\n assertAddable(store, batch, parentName);\n\n const artifacts = buildAddArtifacts(store, batch, parentName, logger);\n\n // Config-time channel check on the PREPARED artifacts, in PREPARE — the same\n // position `replace` gives it, and for the same reason (a throw must precede\n // every mutation, not merely the swap).\n assertRouteDefaultChannelsFor(\n artifacts.matcher,\n artifacts.config,\n \"addRoute\",\n );\n\n // Pre-flight the #961 handler-limit into PREPARE so a limit-exceeding batch\n // aborts before the swap (#1046). `add` does not clear guards, so the\n // projection runs against the live union count (clearsDefinition = false).\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.preflightHandlerLimit(\n artifacts.pendingCanActivate.keys(),\n artifacts.pendingCanDeactivate.keys(),\n false,\n );\n\n adoptRouteArtifacts(store, artifacts);\n}\n\n/**\n * The route the LIVE tree matches `path` to, or `undefined` when nothing does.\n *\n * Deliberately the RAW matcher rather than `ctx.matchPath`: this asks who the\n * URL belongs to, and it must run no application code — `matchPath` layers the\n * route's `decodeParams`, the `forwardState` seam (dynamic `forwardTo`\n * callbacks and plugin interceptors) and the encoders on top, so asking it here\n * would re-open the very window the caller is guarding. A consequence worth\n * naming: the raw matcher is forward-BLIND, so installing a `forwardTo` changes\n * who the url resolves to without changing who it matches.\n */\nfunction urlOwner<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(store: RoutesStore<Dependencies>, path: string): string | undefined {\n return store.matcher.match(path)?.segments.at(-1)?.fullName;\n}\n\n/**\n * Commits a revalidated state after `replace()` and emits `TRANSITION_SUCCESS`\n * so `router.subscribe` / adapters re-render (#950). The emit carries\n * `REVALIDATE_OPTS` — the single distinguishable marker (`revalidate: true`) a\n * plugin's `onTransitionSuccess` can read to special-case a revalidation vs a\n * real navigation (#1201).\n */\nfunction commitRevalidated<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n ctx: RouterInternals<Dependencies>,\n nextState: State,\n fromState: State,\n ownerBefore: string | undefined,\n): void {\n // The THIRD commit door, and the one whose question is its own (#1753 /\n // #1754): `completeTransition` and `navigateToState` refuse a state whose\n // route no longer exists, this path asks whether the URL's OWNER moved, and\n // `systemCommit` below asks whether the MACHINE may commit — three different\n // questions, deliberately so. ⚠ Not \"is the router alive\": the gate is\n // `canSend(SYSTEM_COMMIT)` (#1644), an edge declared on `READY` alone — so it\n // refuses a perfectly LIVE router that is merely starting or mid-transition\n // (`routerFSM`'s `STARTING` block, whose absence of the edge is\n // compiler-enforced by `DeclaredAbsences`).\n //\n // The window is real on BOTH arms, because both run application code between\n // `matchPath` and here: the survivor arm through the route's own\n // `decodeParams` (invoked by that `matchPath`), the route-identity arm\n // additionally through the activation guards it consults (#1201). Either can\n // reach back into route-CRUD — `isTransitioning()` is false and the\n // `TREE_CHANGED` dispatch has already returned, so nothing else stops them —\n // and the measured shapes were a guard removing the very route it was\n // consulted about, and a NESTED `replace()` from a decoder dropping the route\n // the outer call was about to re-commit (whose own revalidation committed\n // first, so the outer commit then OVERWROTE it with a phantom).\n //\n // `store.matcher` is re-read here rather than captured: a nested `replace()`\n // swaps the field, so the late read is what sees the tree as it stands at the\n // commit. The fall-through is the arm this function's callers already use for\n // \"the URL no longer belongs to a route we can commit\".\n //\n // ⚑ The question is whether the URL's owner MOVED while the window ran\n // (#1754), and both halves of that sentence are load-bearing.\n //\n // OWNERSHIP rather than existence, because `hasRoute(name)` — the question\n // the other two doors ask — closes \"the route is\n // gone\" and nothing else, and the NAME is the one field of `nextState` that\n // the window can leave untouched while invalidating everything around it: a\n // nested `replace()` reusing the name at another path, a `setRootPath` (every\n // name survives, every path moves), an `add` of a more specific route, an\n // `update` installing a `forwardTo`. All four were measured committing a\n // state whose own `path` the live tree no longer routes to its `name` —\n // `buildPath(name)` and `state.path` disagreeing, `matchPath(state.path)`\n // answering `undefined` or a different route.\n //\n // Asking the raw matcher instead answers that directly, and it SUBSUMES the\n // existence check: a name the matcher hands back is a name the matcher holds,\n // so a stable owner implies the route still exists. That is why this replaces\n // the `hasRoute` call rather than joining it — the existence branch would be\n // redundant, and in the ownership-first spelling it would be unreachable and\n // red the 100 % branch gate.\n //\n // ⚠ CHANGED rather than \"still owns it\", and the distinction is measured.\n // Asking `match(nextState.path) === nextState.name` instead silently assumes\n // the committed path BELONGS to the committed name. Two shapes break that\n // assumption before any window runs, and one of them is on DEFAULT options:\n // `rewritePathOnMatch: false` leaves `state.path` as the SOURCE url of a\n // `forwardTo` (`RoutesNamespace.matchPath`), and the #1157 catch does the same\n // when the target's rebuild throws for a missing required param. Both commit\n // `{ name: terminal, path: source }` deliberately and are pinned as such — so\n // an ownership EQUALITY test 404s them on every `replace()`, healthy or not.\n // Measured: an equality test lands both on `UNKNOWN_ROUTE` where they\n // should commit.\n //\n // Comparing the answer against the same question asked BEFORE the window\n // needs no such assumption. A state whose path never belonged to its name\n // keeps a stable answer and commits; a window that removes the route, moves\n // it, or lets another route take the URL changes the answer and is refused.\n // The snapshot is taken in `replaceRoutes` immediately before the revalidating\n // `matchPath`, because that call is itself the first window actor (it invokes\n // the route's `decodeParams`).\n //\n // Two properties make it affordable where re-running `matchPath` would not\n // be. It runs NO application code: the route's `decodeParams`, the\n // `forwardState` seam and the encoders all sit ABOVE it in\n // `RoutesNamespace.matchPath`, and the matcher's own decode/parse hooks are\n // derived from option FLAGS (`deriveMatcherOptions`), never from a caller's\n // function — so the predicate cannot re-open the very window it guards. And\n // it is asked once per `replace()` on a router that has state, a path with no\n // benchmark on it.\n //\n // ⚠ A tier-wide measurement does not clear the equality form, and that is the\n // lesson worth keeping: the tier's shapes are not the reachable shapes. Every\n // case the tier covers has a path rebuilt from the resolved route, so the\n // class where `state.path` is the SOURCE url is invisible to it — 512\n // agreements out of 515 firings say nothing about a class that never fires.\n // The difference form does not depend on that class at all.\n const ownerNow = urlOwner(store, fromState.path);\n\n if (ownerNow !== ownerBefore) {\n ctx.revalidateToNotFound(fromState.path);\n\n return;\n }\n\n // Through the machine (`SYSTEM_COMMIT`), so the write and the announce are\n // one table fact rather than two statements here. No SURVIVING route's\n // external factory is re-invoked between `replace()`'s entry\n // `throwIfDisposed()` and this line — `clearDefinitionGuards`'s re-derivation\n // READS the survivor's stored compiled form instead of re-running its factory\n // (#1192 / #1627 / #1649) — which is what makes unreachable the arc where a\n // `dispose()` from such a factory lets the swap finish and commit on a dead\n // router with zero events. The NEW batch's factories DO run; see below.\n //\n // ⚠ It does NOT follow that `replace()` \"executes nothing of the caller's\n // between the two points\". It executes at LEAST four other things, all\n // above: the NEW batch's\n // guard factories (`compileArtifactGuards` → `compileFactory`, which is\n // `factory(router, getDependency)`), the `TREE_CHANGED` handlers, the route's\n // own `decodeParams` invoked by the revalidating `matchPath`, and the new\n // route's activation guards consulted since #1201. ⚠ A fix's scope is not the\n // window's scope, and reading one for the other is what makes the question\n // above look unnecessary (#1753). ⚑ \"At least four\" on purpose: an\n // enumeration passed off as exhaustive is the failure this very ⚠ names, and\n // the shortest way to commit it is to count what one change touched.\n //\n // ⚑ The liveness this line relies on is KEPT, and deliberately: it covers a\n // router disposed or stopped by some OTHER means between the entry check and\n // here, which `replace()` does not cause but cannot rule out. No separate\n // re-check is needed for it — a dead router simply has no edge to take, and\n // `systemCommit` turns that silent refusal into the\n // throw the callers were already promised — `ROUTER_DISPOSED` after a\n // `dispose()`, `ROUTER_NOT_STARTED` after a `stop()`, since the machine is\n // then IDLE rather than DISPOSED (measured; #1644 split the two codes).\n ctx.systemCommit(nextState, fromState, REVALIDATE_OPTS);\n}\n\n/**\n * Atomically replaces all routes with a new set (HMR / code-splitting).\n * Prepare-then-commit (issue #698): the new set is fully built into locals\n * first — a circular/async forwardTo or invalid path throws here, leaving the\n * existing tree intact — then committed.\n */\nfunction replaceRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n batch: readonly Route<Dependencies>[],\n ctx: RouterInternals<Dependencies>,\n currentState: State | undefined,\n onCommitted?: () => void,\n): void {\n // Reject the silent-corruption cases `assertAddable` catches for `add`, BEFORE\n // building/swapping, so bare-core parity is symmetric (#1047): within-batch\n // duplicate names (#968), reserved \"@@\" names (#954), and within-batch\n // duplicate paths (#955). methodName is \"addRoute\" to match validation-plugin\n // (which reports \"addRoute\" for replace batches too), so the no-plugin error\n // is identical to the with-plugin one.\n assertNoInternalNamesInBatch(batch, \"addRoute\");\n assertNonEmptyNamesInBatch(batch, \"addRoute\");\n assertNoDottedNamesInBatch(batch, \"addRoute\");\n assertNoDuplicateNamesInBatch(batch, \"\", \"addRoute\");\n assertNoDuplicatePathsInBatch(batch, \"\", \"addRoute\");\n\n // Build the whole new set BEFORE touching the store.\n const artifacts = buildReplaceArtifacts(\n batch,\n store.rootPath,\n store.matcherOptions,\n ctx.logger,\n );\n\n // Config-time channel check BEFORE clearDefinitionGuards mutates. Inside\n // `adoptRouteArtifacts`, one line before the swap, is early enough for `add`\n // and too late here: a refused batch would leave the tree intact and the old\n // definition guards ERASED, so a guarded route becomes freely activatable.\n // Same fail-open shape #1046 and #1193 hoisted their own throws out of.\n assertRouteDefaultChannelsFor(\n artifacts.matcher,\n artifacts.config,\n \"addRoute\",\n );\n\n // Pre-flight the #961 handler-limit BEFORE clearDefinitionGuards mutates, so a\n // limit-exceeding batch aborts with BOTH the tree and the definition guards\n // intact (#1046). replace clears definition guards first, so the projection\n // runs against the surviving external guards (clearsDefinition = true).\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.preflightHandlerLimit(\n artifacts.pendingCanActivate.keys(),\n artifacts.pendingCanDeactivate.keys(),\n true,\n );\n\n // Pre-compile the new batch's guard factories in the PREPARE phase — BEFORE\n // clearDefinitionGuards — so a compile-throwing factory (or a non-function)\n // aborts here with BOTH the tree AND the old definition guards intact (#1193,\n // mirror of the #1046 handler-limit hoist). adoptRouteArtifacts then installs\n // these pre-compiled functions without re-running the factories.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const compiledGuards = compileArtifactGuards(artifacts, store.depsStore!);\n\n // Clear definition lifecycle handlers (preserve external guards), then swap.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.clearDefinitionGuards();\n adoptRouteArtifacts(store, artifacts, compiledGuards);\n\n // TREE_CHANGED fires here (O-5): the new tree is committed but state is not\n // yet revalidated, so the handler sees the new tree and the still-old state.\n onCommitted?.();\n\n // Revalidate the active state against the new tree AND notify subscribers\n // (#950). A structural replace can change or drop the currently-active state;\n // emitting TRANSITION_SUCCESS makes router.subscribe / useSyncExternalStore\n // adapters re-render instead of rendering the pre-replace state. (This is the\n // one structural mutation that emits a transition event — clear() stays a\n // silent reset; the asymmetry is deliberate, see #950.)\n if (currentState !== undefined) {\n // Who owns this URL BEFORE any of the revalidation's own application code\n // runs. The comparison at the door is against THIS, not against\n // `currentState.name` — see `commitRevalidated`. It has to be read here\n // rather than inside the door because the very next statement is the first\n // window actor: `matchPath` invokes the route's `decodeParams`.\n const ownerBefore = urlOwner(store, currentState.path);\n\n const revalidated = ctx.matchPath(currentState.path, ctx.getOptions());\n\n if (revalidated) {\n if (revalidated.name === currentState.name) {\n // Survivor — the URL still maps to the route the user was already on.\n // Keep it WITHOUT re-running guards: the user legitimately reached this\n // route via a real navigation, and `replace()` is not a navigation they\n // performed, so re-checking guards here would evict them on a stateful\n // or async guard (parity with `update()`, which never revalidates the\n // active state). Preserve the prior transition meta and emit so\n // subscribers see the revalidated state (#1201). Carry the prior\n // `context` (#1236): the route name and path are unchanged, so the\n // plugin data written into `state.context.<namespace>` (SSR data, rsc,\n // navigation, …) is still valid — the matchPath-rebuilt state would\n // otherwise wipe it, and revalidation re-runs neither the loader nor the\n // start interceptor to bring it back.\n // ⚑ This object is never published: the commit door copies what it is\n // handed and commits its own (#1792), so nothing here freezes — and\n // nothing outside `commitRevalidated` ever holds it. The `context` line\n // still does its job: its CONTENTS are what survive the revalidation,\n // which is what #1236 is about. Its identity does not, so a plugin that\n // cached the context object itself across a `replace()` writes into an\n // object the router no longer holds.\n const nextState: State = {\n ...revalidated,\n context: currentState.context,\n transition: currentState.transition,\n };\n\n commitRevalidated(store, ctx, nextState, currentState, ownerBefore);\n } else {\n // Route-identity change — the URL is now owned by a DIFFERENT route (an\n // ownership reshuffle, or a newly-added `forwardTo` that teleports the\n // state). Consult the new route's ACTIVATION guards (#1201): commit on\n // pass; on a block — or an async guard that cannot be evaluated\n // synchronously (mirrors `canNavigateTo`) — route to not-found rather\n // than silently activating a guarded route.\n //\n // ⚠ ACTIVATION ONLY — the deactivate list is deliberately empty (#1652).\n // `canNavigateTo` collapses both halves into ONE boolean, and this arm\n // routes every `false` to not-found. That reading is right for \"cannot\n // ENTER\" and exactly backwards for \"do not LEAVE\": the guard exists to\n // keep the user where they are, and eviction to a 404 is the worst\n // outcome available. Measured against the routing arm: with no\n // `canDeactivate` the user reaches the new route, WITH a refusing one\n // they land on UNKNOWN_ROUTE — a guard honoured that way makes the\n // result worse than no guard at all.\n //\n // Not asking is what the other two revalidation arms already do, each\n // with its reason written beside it (survivor: the user was legitimately\n // here, #1201; vanished: the route whose guard would speak is gone). So\n // this removes the odd one out rather than adding a mechanism: a tree\n // swap is an operation the APPLICATION performed, not a departure the\n // user chose, and `canDeactivate` has no \"stay\" branch to offer here —\n // after the swap the old route may not exist, or may live at another\n // path, so a retained state would point at a route that no longer owns\n // its URL. Checking for unsaved work before swapping the tree is the\n // caller's job; the router does not promise to veto its own API.\n //\n // Side effect worth naming: the refusal does NOT short-circuit ahead\n // of the activation guards, so \"may the user be on the new route\" is\n // always asked.\n const { toActivate } = getTransitionPath(\n revalidated,\n currentState,\n ctx.getMetaForState,\n );\n\n const allowed =\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.canNavigateTo(\n [],\n toActivate,\n revalidated,\n currentState,\n );\n\n if (allowed) {\n const nextState: State = {\n ...revalidated,\n transition: currentState.transition,\n };\n\n commitRevalidated(store, ctx, nextState, currentState, ownerBefore);\n } else {\n // The REVALIDATION door, and its reason CHANGED with #1652: it is no\n // longer \"the question was already put above\" (it no longer is) but\n // the same rule as the consult itself — revalidation does not consult\n // deactivate guards. Calling the departure door here would let the\n // fallback throw CANNOT_DEACTIVATE out of a route-CRUD call, which is\n // the shape #1643 deliberately kept for user-initiated departures\n // only. Since #1981 that is a different FUNCTION rather than a flag,\n // so the two cannot be confused at the call site.\n ctx.revalidateToNotFound(currentState.path);\n }\n }\n } else {\n // The active route no longer exists in the new tree — surface it as\n // not-found (commits UNKNOWN_ROUTE + emits TRANSITION_SUCCESS) so the\n // change is observable, rather than silently clearing the state.\n //\n // No deactivation consult (#1643): the route whose guard would be asked\n // is the one that just stopped existing. There is nothing to refuse on\n // behalf of, and a guard closure over a removed route is not a contract\n // this can honour.\n ctx.revalidateToNotFound(currentState.path);\n }\n }\n}\n\n/**\n * Removes a route and all its children.\n *\n * @returns the removed subtree when `wantSubtree` is set, an empty array when it\n * is not, and `undefined` when the name is not a route. Three outcomes, so a\n * caller distinguishing \"removed\" from \"not found\" must test for `undefined` —\n * an empty array is a successful removal.\n */\nfunction removeRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n name: string,\n wantSubtree: boolean,\n): readonly Route<Dependencies>[] | undefined {\n // `store.definitions` is a fresh tree-derived snapshot — mutate it locally,\n // then commit the mutated table as the new tree.\n const definitions = store.definitions;\n const removedNames = spliceSubtree(definitions, name);\n\n if (removedNames === undefined) {\n return undefined;\n }\n\n // Between the splice and the two commits below: the store still holds the old\n // tree AND the config the payload reads, which is the only moment either is\n // available together with the set (#1757). Empty — not `undefined` — when\n // nobody is listening, so `undefined` means one thing only: not a route.\n const subtree = wantSubtree\n ? collectSubtree(store, removedNames)\n : EMPTY_SUBTREE;\n\n clearRouteConfigurations(\n removedNames,\n store.config,\n store.routeCustomFields,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n store.lifecycleNamespace!,\n );\n\n commitTreeChanges(store, definitions);\n\n return subtree;\n}\n\n/**\n * Gets a route by name with all its configuration.\n */\nfunction getRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n name: string,\n): Route<Dependencies> | undefined {\n const segments = store.matcher.getSegmentsByName(name);\n\n if (!segments) {\n return undefined;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- segments is non-empty (checked above)\n const targetNode = segments.at(-1)! as RouteTree;\n const definition = nodeToDefinition(targetNode);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const factories = store.lifecycleNamespace!.getFactories();\n\n return enrichRoute(definition, name, store.config, factories);\n}\n\n// ============================================================================\n// API factory\n// ============================================================================\n\n// Cache the assembled RoutesApi per router — mirrors getPluginApi()/getNavigator():\n// avoids re-allocating the 9-closure bag on each call (adapters/plugins poll it\n// from constructors) and gives spy/stub helpers a stable object identity. Closures\n// capture `ctx`/`store`, both stable for the router's lifetime, so caching is safe.\n// Single cast site: the value is stored as `unknown` (RoutesApi is invariant in\n// Dependencies, so one typed map can't hold every instantiation) and cast on read.\nconst cache = new WeakMap<object, unknown>();\n\nexport function getRoutesApi<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(router: Router<Dependencies>): RoutesApi<Dependencies> {\n const cached = cache.get(router);\n\n if (cached) {\n return cached as RoutesApi<Dependencies>;\n }\n\n const ctx = getInternals(router);\n\n const store = ctx.routeGetStore();\n\n // Single cast site: the channel is typed with default Dependencies on\n // RouterInternals (RouterEventMap is non-generic), but payloads are built\n // with this api's Dependencies. The runtime shape is identical.\n const emitChange = (event: TreeChangedEvent<Dependencies>): void => {\n ctx.treeChanged.emit(event as TreeChangedEvent);\n };\n\n const api: RoutesApi<Dependencies> = {\n add: (routes, options) => {\n throwIfDisposed(ctx.isDisposed);\n throwIfReentrantTreeMutation(ctx.treeChanged.isEmitting);\n\n const routeArray = Array.isArray(routes) ? routes : [routes];\n const parentName = options?.parent;\n\n // ⚑ Judged and snapshotted in ONE walk, so guards, validators and\n // registration all decide from this one object (#1899 / #1911 / #2139). A\n // `Proxy` reports an ordinary data descriptor while answering differently\n // per read, so the accessor ban does not reach that shape and only a\n // single read can — of each definition AND of the array holding them.\n // Per-key counts are the `registration · route.*` rows' business.\n const batch = guardRouteStructure(routeArray);\n\n guardRouteCallbacks(batch, ctx.validator);\n\n if (parentName !== undefined) {\n ctx.validator?.routes.validateParentOption(parentName, store.tree);\n }\n\n ctx.validator?.routes.throwIfInternalRouteInArray(batch, \"addRoute\");\n ctx.validator?.routes.validateAddRouteArgs(batch);\n ctx.validator?.routes.validateRoutes(batch, store, parentName);\n\n addRoutes(store, batch, parentName, ctx.logger);\n\n // Built from the post-commit store (O-1), only when someone is listening.\n //\n // ⚑ From the SNAPSHOT, never from `routeArray` (#1931): the caller's array\n // is application code's, and everything between the snapshot and this line\n // — guard factories compiled inside `adoptRouteArtifacts` among them — can\n // change what a second read of it answers.\n if (ctx.treeChanged.listenerCount() > 0) {\n const added = collectAddedRoutes(batch, parentName, store);\n\n emitChange(\n parentName === undefined\n ? { op: \"add\", added }\n : { op: \"add\", added, parent: parentName },\n );\n }\n },\n\n remove: (name) => {\n throwIfDisposed(ctx.isDisposed);\n throwIfReentrantTreeMutation(ctx.treeChanged.isEmitting);\n\n ctx.validator?.routes.validateRemoveRouteArgs(name);\n ctx.validator?.routes.throwIfInternalRoute(name, \"removeRoute\");\n // Always-on parity backstop (#1047 / #238): a reserved \"@@\" name is\n // internal and cannot be removed, with or without the validation-plugin.\n assertNoInternalRouteName(name, \"removeRoute\");\n\n const canRemove = validateRemoveRoute(\n name,\n ctx.getStateName(),\n ctx.logger,\n );\n\n if (!canRemove) {\n return;\n }\n\n const wantSubtree = ctx.treeChanged.listenerCount() > 0;\n // The payload is built INSIDE, between the splice and the commits — the\n // one moment the removed-name set, the old tree and the config coexist\n // (#1757). `undefined` means the name is not a route at all.\n const removedSubtree = removeRoute(store, name, wantSubtree);\n\n if (removedSubtree === undefined) {\n ctx.logger.warn(\n \"router.removeRoute\",\n `Route \"${name}\" not found. No changes made.`,\n );\n\n return;\n }\n\n // Below the existence check on purpose (#1756): everything this reports\n // is a consequence of a route leaving the tree, so it has no subject\n // until one has.\n if (ctx.isTransitioning()) {\n warnRemovalDuringNavigation(name, ctx.logger);\n }\n\n if (wantSubtree) {\n emitChange({ op: \"remove\", name, removedSubtree });\n }\n },\n\n update: (name, updates) => {\n throwIfDisposed(ctx.isDisposed);\n throwIfReentrantTreeMutation(ctx.treeChanged.isEmitting);\n\n ctx.validator?.routes.validateUpdateRouteBasicArgs(name, updates);\n ctx.validator?.routes.throwIfInternalRoute(name, \"updateRoute\");\n // Always-on parity backstop (#1047 / #238): a reserved \"@@\" name is\n // internal and cannot be updated, with or without the validation-plugin.\n assertNoInternalRouteName(name, \"updateRoute\");\n\n ctx.validator?.routes.validateUpdateRoutePropertyTypes(name, updates);\n\n ctx.validator?.routes.validateUpdateRoute(name, updates, store);\n\n // #1205: bare-core existence backstop as a TRUE no-op — NOT a throw\n // (validation is opt-in). Without it, update() of a route that does not\n // exist seeds config.defaultParams + compiles/registers the guard\n // (commitRouteUpdate below) and emits a lying TREE_CHANGED \"update\" event\n // for a route get()/has() cannot see; a future add() of that name then\n // inherits the phantom config + a blocking guard. Skip the commit and the\n // emit entirely when the route is absent. (With the validation-plugin, validateUpdateRoute above\n // already threw a ReferenceError, so this is only reached in bare core.)\n if (!store.matcher.hasRoute(name)) {\n return;\n }\n\n // Below the existence check, for the same reason the removal report is\n // (#1756): it names an action, and `update(\"nope\")` performs none. From\n // above it logged an ERROR announcing an update that never happened, and\n // — unlike `remove()`, which at least contradicts itself out loud one\n // line later — said nothing afterwards.\n if (ctx.isTransitioning()) {\n ctx.logger.error(\n \"router.updateRoute\",\n `Updating route \"${name}\" while navigation is in progress. This may cause unexpected behavior.`,\n );\n }\n\n // Field-patch commit core (NO_TREE_REBUILD) — co-located in routesStore.ts\n // beside the add/replace (adoptRouteArtifacts) / remove (commitTreeChanges)\n // / clear (resetStore) cores. Returns the structural fields for the\n // conditional emit below (each user getter read once inside).\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const lifecycle = store.lifecycleNamespace!;\n const structural = commitRouteUpdate(store, lifecycle, name, updates);\n\n // Conditional emit: structural fields only. A guard-only or empty patch\n // produces no event (O-7 + empty-patch rule).\n if (ctx.treeChanged.listenerCount() > 0) {\n const patch = buildStructuralPatch<Dependencies>(structural);\n\n if (objectKeys(patch).length > 0) {\n emitChange({ op: \"update\", name, patch });\n }\n }\n },\n\n clear: () => {\n throwIfDisposed(ctx.isDisposed);\n throwIfReentrantTreeMutation(ctx.treeChanged.isEmitting);\n\n // `clear()` is a TEARDOWN primitive, and it may only run while there is\n // nothing to tear down out from under anyone (#1612). Dropping the\n // committed state to `undefined` silently leaves every `router.subscribe`\n // consumer rendering a route the router has discarded, and the router\n // `isActive() === true` with no state — a shape that\n // otherwise exists only *during* `start()`, which is why an always-on\n // guard misreads it (path-less `navigateToNotFound()` answers\n // ROUTER_NOT_STARTED on a started router).\n //\n // Announcing the reset instead was considered and rejected: it would make\n // CRUD emit a transition event as a RULE (`replace()` is deliberately \"the\n // one structural mutation that emits\" one) and it would not remove the\n // shape. Refusing removes the crossing entirely — `clear()` stops writing\n // into state it does not own. `replace(routes)` is the spelling for a\n // running router: atomic, notifies subscribers, and preserves external\n // guards. Design note `fsm-as-state-owner-2026-07-31.md` §11.A1, option\n // (c), owner decision 2026-08-01.\n //\n // A THROW rather than the `logger.error` + no-op that `validateClearRoutes`\n // uses below, because the two preconditions are different classes: \"a\n // navigation is in flight\" clears by itself (wait and retry works), while\n // this one never does — the caller has to change the code. That is the\n // same line `REENTRANT_TREE_MUTATION` sits on (#1032).\n if (ctx.getStateName() !== undefined) {\n throw freezeThrownError(\n new RouterError(errorCodes.ROUTER_NOT_STOPPED, {\n message:\n \"[router.clear] Cannot clear routes while a state is committed. \" +\n \"Use replace(routes) to swap the tree on a running router, or stop() first.\",\n }),\n );\n }\n\n const canClear = validateClearRoutes(ctx.isTransitioning(), ctx.logger);\n\n /* v8 ignore next 3 -- @preserve: race condition guard, mirrors Router.clearRoutes() same-path guard tested via validateClearRoutes unit tests */\n if (!canClear) {\n return;\n }\n\n // Snapshot the routes BEFORE the reset empties them. Emitted whenever\n // there is a listener — even for an empty clear (O-4).\n const removed =\n ctx.treeChanged.listenerCount() > 0\n ? freeze([...collectFlatRoutes(store, () => true).values()])\n : undefined;\n\n resetStore(store);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.clearAll();\n\n if (removed !== undefined) {\n emitChange({ op: \"clear\", removed });\n }\n },\n\n has: (name) => {\n ctx.validator?.routes.validateRouteName(name, \"hasRoute\");\n\n return store.matcher.hasRoute(name);\n },\n\n get: (name) => {\n ctx.validator?.routes.validateRouteName(name, \"getRoute\");\n\n return getRoute(store, name);\n },\n\n replace: (routes) => {\n throwIfDisposed(ctx.isDisposed);\n throwIfReentrantTreeMutation(ctx.treeChanged.isEmitting);\n\n const routeArray = Array.isArray(routes) ? routes : [routes];\n\n const canReplace = validateClearRoutes(ctx.isTransitioning(), ctx.logger);\n\n if (!canReplace) {\n return;\n }\n\n // Judged and snapshotted in one walk — same rule as `add`\n // (#1899 / #1911 / #2139).\n const batch = guardRouteStructure(routeArray);\n\n guardRouteCallbacks(batch, ctx.validator);\n\n ctx.validator?.routes.throwIfInternalRouteInArray(batch, \"replaceRoutes\");\n ctx.validator?.routes.validateAddRouteArgs(batch);\n ctx.validator?.routes.validateRoutes(batch, store);\n\n const currentState = router.getState();\n\n // The flat removed/added diff is O(N) — compute it only when someone is\n // listening (Decision 3.B). Snapshot the old tree BEFORE the swap.\n const before =\n ctx.treeChanged.listenerCount() > 0\n ? collectFlatRoutes(store, () => true)\n : undefined;\n\n replaceRoutes(\n store,\n batch,\n ctx,\n currentState,\n before === undefined\n ? undefined\n : () => {\n const after = collectFlatRoutes(store, () => true);\n const { removed, added } = diffFlatRoutes(before, after);\n\n emitChange({ op: \"replace\", removed, added });\n },\n );\n },\n\n subscribeChanges: (handler) => ctx.treeChanged.subscribe(handler),\n };\n\n // ⚑ FROZEN, and the freeze is what the cache above makes necessary (#1805).\n // One object per router is handed to EVERY consumer — first-party plugins and\n // application code alike — so a single `api.add = …` rewires the surface for\n // all of them, silently and with nothing for the next consumer to notice.\n // The consumer count is deliberately not restated: it grows with the tier\n // while the hazard is the sharing, which one consumer is enough to have. `getNavigator` next door has always frozen its cached bag and calls\n // itself \"a frozen read-only subset\"; the two uncached factories\n // (`getLifecycleApi`, `getDependenciesApi`) need nothing, because a write to a\n // per-call object cannot reach a second consumer.\n //\n // ⚠ Measured free: core, all six adapters and every plugin that reaches this\n // door stay green under the freeze. Its twin `getPluginApi` is NOT — tests\n // across the tier spy on that shared surface to inject errors, so freezing it\n // reds them — which is why this half ships alone. Re-run the freeze on\n // `getPluginApi` to see the count rather than trusting one written here.\n const frozen = freeze(api);\n\n cache.set(router, frozen);\n\n return frozen;\n}\n","import { throwIfDisposed } from \"./helpers\";\nimport { ingestDependencies } from \"../guards\";\nimport { dropUnsafeKey } from \"../helpers\";\nimport { getInternals } from \"../internals\";\nimport { storeDependency } from \"../namespaces\";\n\nimport type { DependenciesApi } from \"./types\";\nimport type { DependenciesStore } from \"../namespaces\";\nimport type { DefaultDependencies, Router } from \"../types\";\nimport type { RouterValidator } from \"../types/RouterValidator\";\n\n/** Captured like the deciding seven, but this one BUILDS the guarantee (#2072). */\nconst objectCreate = Object.create;\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst hasOwn = Object.hasOwn;\n\n/**\n * One `ToPropertyKey`, at the door (#1843).\n *\n * A dependency name is used as a PROPERTY KEY, so every bare\n * `store[name]` / `Object.hasOwn(store, name)` is a `toString` call into\n * application code — and `set` made three of them, `remove` two. Nothing pinned\n * the result between them, so the key that was CHECKED was not the key that was\n * written or deleted. Measured through the public API with a name answering\n * `\"alpha\"` then `\"beta\"`: `remove` reported nothing (the check found `alpha`)\n * and deleted `beta`; `set` took the overwrite arm on `alpha` — skipping the\n * new-key limit check — and then added `beta`.\n *\n * The rule is core's own, from `src/engine/CLAUDE.md`: *\"a guard that admits by\n * a computed key must hand the KEY downstream, never the value it computed it\n * from\"*. This file already applies it one level up — `setDependency` captures\n * `store.dependencies` ONCE (#1859) because a validator warning can reach\n * application code that replaces it. The reference was pinned; the key was not.\n *\n * ⚠ A SYMBOL is handed back untouched, and that exemption loses nothing: a\n * symbol already IS a property key, so `ToPropertyKey` is the identity on it and\n * no application code runs — the entire hazard is the non-symbol case. Coercing\n * it instead was written first and measured: `set` and `remove` moved to\n * `\"Symbol(svc)\"` while `has` and `get` kept asking the symbol, so `set(S, 1)`\n * followed by `has(S)` answered **false**. That is a NEW divergence, in a family\n * that is merely incomplete today: a symbol key works through all four doors and\n * comes back from `getAll` (a spread carries own enumerable symbols), but\n * `Object.keys` does not see it, so `validateDependencyCount` never counts one\n * against the limit. Read-count is this fix's subject; symbol support is not,\n * and `set` narrows to `& string` anyway.\n *\n * ⚠ The parameter is `unknown` deliberately. Written as `String(name: string)`,\n * BOTH `@typescript-eslint/no-unnecessary-type-conversion` and\n * `unicorn/no-useless-coercion` reason from the declared type and autofix the\n * coercion away — measured on #1882, where `lint --fix` deleted the same fix\n * twice. `unknown` makes the conversion genuine, so no rule has anything to\n * remove and no disable comment is needed.\n */\nconst asKey = (name: unknown): string | symbol =>\n typeof name === \"symbol\" ? name : String(name);\n\n// =============================================================================\n// Module-private CRUD functions\n// =============================================================================\n\nfunction setDependency(\n store: DependenciesStore,\n dependencyName: string,\n dependencyValue: unknown,\n validator?: RouterValidator | null,\n): void {\n // undefined = \"don't set\" (feature for conditional setting)\n if (dependencyValue === undefined) {\n return;\n }\n\n // ⚑ Captured ONCE, and this is the whole re-entrancy defence (#1859).\n //\n // `validateDependencyCount` and `warnOverwrite` both reach `logger.warn`, i.e.\n // the application's own `LoggerConfig.callback` — public `RouterOptions` API,\n // called synchronously between the reads above and the write below. That\n // callback can `dispose()` or `reset()` the router, and both clear this channel\n // by REPLACING `store.dependencies`. Re-reading the slot afterwards wrote into\n // the fresh post-teardown object, which every clear path then refused to touch\n // (they all `throwIfDisposed` first) while `getAll()` kept answering with it.\n //\n // Holding the reference makes that unreachable rather than merely guarded: the\n // write lands in the object the teardown discarded, so it is garbage by\n // construction. A per-call disposal probe cannot do this — there is a user-code\n // window on either side of it, and it would have to sit in both.\n // ⚠ `PropertyKey`, not `string`: a symbol dependency name reaches here\n // untouched (see `asKey`), and `Record<string, unknown>` would force a\n // `name as string` cast that is simply false about symbols.\n const target = store.dependencies as Record<PropertyKey, unknown>;\n // ⚑ Pinned for the same reason `target` is, one line up (#1843). The four\n // uses below asked the name FOUR times, and each was a `ToPropertyKey` call\n // into application code.\n const key = asKey(dependencyName);\n const isNewKey = !hasOwn(target, key);\n\n if (isNewKey) {\n // Only check limit when adding new keys (overwrites don't increase count)\n validator?.dependencies.validateDependencyCount(store, \"setDependency\");\n } else {\n const oldValue = target[key];\n const isChanging = oldValue !== dependencyValue;\n // Special case for NaN idempotency (NaN !== NaN is always true)\n const bothAreNaN = Number.isNaN(oldValue) && Number.isNaN(dependencyValue);\n\n if (isChanging && !bothAreNaN) {\n // `String` again, and only here: the validator wants a name for a\n // MESSAGE, and this is the opt-in diagnostic path.\n validator?.dependencies.warnOverwrite(String(key), \"setDependency\");\n }\n }\n\n target[key] = dependencyValue;\n}\n\nfunction setMultipleDependencies(\n store: DependenciesStore,\n deps: Record<string, unknown>,\n validator?: RouterValidator | null,\n): void {\n const overwrittenKeys: string[] = [];\n\n // ⚑ Captured ONCE — see `setDependency` above for the mechanism. This loop has\n // TWO user-code windows per key, not one: reading `deps[key]` runs an accessor\n // if the caller supplied one, and `validateDependencyCount` reaches\n // `logger.warn` → the application's `LoggerConfig.callback`. A disposal probe\n // between them closes the first and leaves the second open — measured, the\n // callback route reproduced the leak in full on a bag with no accessors at all.\n // Holding the reference closes both, and closes `reset()` (which replaces the\n // same slot) with them.\n const target = store.dependencies as Record<string, unknown>;\n\n // ⚑ The same walk as the constructor door — and \"the same\" is now literal\n // rather than approximate: both go through `ingestDependencies` (#1860), the\n // ONE door a caller-supplied bag passes, which judges and copies in a SINGLE\n // pass (#1861). Before this, `setAll` reached no structural check at all: a\n // string, an array, a class instance, a `Map` and an own enumerable getter all\n // went straight in, the last of them RUNNING the caller's code.\n ingestDependencies(deps, (key, value) => {\n if (hasOwn(target, key)) {\n overwrittenKeys.push(key);\n } else {\n validator?.dependencies.validateDependencyCount(store, \"setDependencies\");\n }\n\n storeDependency(target, key, value);\n });\n\n if (overwrittenKeys.length > 0) {\n validator?.dependencies.warnBatchOverwrite(\n overwrittenKeys,\n \"setDependencies\",\n );\n }\n}\n\n// =============================================================================\n// Public API factory\n// =============================================================================\n\nexport function getDependenciesApi<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(router: Router<Dependencies>): DependenciesApi<Dependencies> {\n const ctx = getInternals(router);\n\n return {\n get: (name) => {\n ctx.validator?.dependencies.validateDependencyName(name, \"getDependency\");\n\n const store = ctx.dependenciesGetStore();\n const value = (store.dependencies as Record<string, unknown>)[\n name as string\n ];\n\n ctx.validator?.dependencies.validateDependencyExists(\n name as string,\n store,\n );\n\n return value as Dependencies[typeof name];\n },\n getAll: () => {\n // ⚑ A spread, then `dropUnsafeKey` (#1823 / #1957). The store is\n // `Object.create(null)`, so an own `\"__proto__\"` is an ORDINARY key there\n // — but a spread re-defines it on a normal object, and the result is then\n // a prototype-swap primitive for any consumer that merges it with\n // `Object.assign` or a `for…in` copy. `cloneRouter` spreads and is safe;\n // a consumer merging is not, and this is published API.\n //\n // ⚠ Asymmetric with `get(\"__proto__\")`, deliberately: the single read\n // hands back a value, this door hands back a CONTAINER that someone will\n // merge. Same asymmetry the route-config records already carry.\n const source = ctx.dependenciesGetStore().dependencies as Record<\n string,\n unknown\n >;\n // ⚑ SPREAD, not a write loop, and the difference is the whole point of\n // this function. A spread DEFINES each key; `all[key] = value` SETS it,\n // and a `[[Set]]` of an ordinary dependency name that `Object.prototype`\n // happens to carry as an accessor throws instead of storing (#1852).\n // Measured: a write loop here makes `getAll()` throw on such a name,\n // which is what turns an already-immune site into a member of the class.\n //\n // The one key a spread cannot be trusted with: `source` is built with\n // `Object.create(null)`, so `\"__proto__\"` can sit there as an ORDINARY own\n // key. Spreading defines it as an own key here too — harmless in `all`\n // itself, but it makes the returned object a prototype-swap primitive for\n // any consumer that merges it with `Object.assign` or a `for…in` copy.\n //\n // ⚠ The delete is UNCONDITIONAL, and `dropUnsafeKey`'s docblock carries\n // the measurement that says why (a `hasOwn` gate in front of the one line\n // that neutralises the hazard is an intrinsic read an application can\n // re-point). This site is where that reasoning was FOUND (#1823); it now\n // serves three doors (#1957) and lives with the primitive.\n const all: Record<string, unknown> = dropUnsafeKey({ ...source });\n\n return all as ReturnType<DependenciesApi<Dependencies>[\"getAll\"]>;\n },\n set: (name, value) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.dependencies.validateSetDependencyArgs(\n name,\n value,\n \"setDependency\",\n );\n\n setDependency(ctx.dependenciesGetStore(), name, value, ctx.validator);\n\n // ⚑ Again, AFTER the write (#1859). The guard above answers \"was the\n // router alive when you called?\"; this one answers \"was it still alive\n // when the write landed?\". Between them sit `validateDependencyCount` and\n // `warnOverwrite`, which reach `logger.callback` — the application's own\n // code. The write itself is already harmless (the target is captured, so a\n // teardown mid-call sends it to the discarded object); this is what stops\n // the call REPORTING success for a store that no longer exists.\n throwIfDisposed(ctx.isDisposed);\n },\n setAll: (deps) => {\n throwIfDisposed(ctx.isDisposed);\n\n const store = ctx.dependenciesGetStore();\n\n ctx.validator?.dependencies.validateDependenciesObject(\n deps,\n \"setDependencies\",\n );\n\n setMultipleDependencies(\n store,\n deps as Record<string, unknown>,\n ctx.validator,\n );\n\n // ⚑ See `set` above — same reason, same placement.\n throwIfDisposed(ctx.isDisposed);\n },\n remove: (name) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.dependencies.validateDependencyName(\n name,\n \"removeDependency\",\n );\n\n const store = ctx.dependenciesGetStore();\n // ⚑ One coercion (#1843) — the check and the delete asked separately, so\n // a name answering `\"alpha\"` then `\"beta\"` reported nothing and deleted\n // `beta`.\n const key = asKey(name);\n\n if (!hasOwn(store.dependencies, key)) {\n ctx.validator?.dependencies.warnRemoveNonExistent(String(key));\n }\n\n delete (store.dependencies as Record<PropertyKey, unknown>)[key];\n },\n reset: () => {\n throwIfDisposed(ctx.isDisposed);\n const store = ctx.dependenciesGetStore();\n\n store.dependencies = objectCreate(null) as Partial<Dependencies>;\n },\n has: (name) => {\n ctx.validator?.dependencies.validateDependencyName(name, \"hasDependency\");\n\n return hasOwn(ctx.dependenciesGetStore().dependencies, name);\n },\n };\n}\n","import { throwIfDisposed } from \"./helpers\";\nimport { assertRouteNameIsString } from \"../guards\";\nimport { getInternals } from \"../internals\";\n\nimport type { LifecycleApi } from \"./types\";\nimport type { DefaultDependencies, Router } from \"../types\";\n\nexport function getLifecycleApi<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(router: Router<Dependencies>): LifecycleApi<Dependencies> {\n const ctx = getInternals(router);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const lifecycleNamespace = ctx.routeGetStore().lifecycleNamespace!;\n\n return {\n addActivateGuard(name, handler) {\n throwIfDisposed(ctx.isDisposed);\n\n assertRouteNameIsString(name, \"addActivateGuard\");\n ctx.validator?.routes.validateRouteName(name, \"addActivateGuard\");\n ctx.validator?.lifecycle.validateHandler(handler, \"addActivateGuard\");\n\n // Handler-limit enforcement lives at the namespace registration choke point\n // (RouteLifecycleNamespace.#registerHandler) so all paths are bounded\n // uniformly — see #961.\n // `false` — the EXTERNAL lane, named rather than defaulted (#1977).\n lifecycleNamespace.addCanActivate(name, handler, false);\n },\n\n addDeactivateGuard(name, handler) {\n throwIfDisposed(ctx.isDisposed);\n\n assertRouteNameIsString(name, \"addDeactivateGuard\");\n ctx.validator?.routes.validateRouteName(name, \"addDeactivateGuard\");\n ctx.validator?.lifecycle.validateHandler(handler, \"addDeactivateGuard\");\n\n lifecycleNamespace.addCanDeactivate(name, handler, false);\n },\n\n removeActivateGuard(name) {\n throwIfDisposed(ctx.isDisposed);\n\n assertRouteNameIsString(name, \"removeActivateGuard\");\n ctx.validator?.routes.validateRouteName(name, \"removeActivateGuard\");\n\n // Inverse of addActivateGuard (external): clears only the external guard;\n // a route-config (definition) canActivate survives (#1171).\n lifecycleNamespace.clearCanActivate(name, \"external\");\n },\n\n removeDeactivateGuard(name) {\n throwIfDisposed(ctx.isDisposed);\n\n assertRouteNameIsString(name, \"removeDeactivateGuard\");\n ctx.validator?.routes.validateRouteName(name, \"removeDeactivateGuard\");\n\n // Inverse of addDeactivateGuard (external): clears only the external guard;\n // a route-config (definition) canDeactivate survives (#1171).\n lifecycleNamespace.clearCanDeactivate(name, \"external\");\n },\n };\n}\n","import { errorCodes } from \"../constants\";\nimport { routeTreeToDefinitions } from \"../engine\";\nimport { ingestDependencies } from \"../guards\";\nimport { getInternals } from \"../internals\";\nimport { getLifecycleApi } from \"./getLifecycleApi\";\nimport { assignConfigEntries } from \"../namespaces/RoutesNamespace/helpers\";\nimport { adoptForwardState } from \"../namespaces/RoutesNamespace/routesStore\";\nimport { Router as RouterClass } from \"../Router\";\nimport { RouterError, freezeThrownError } from \"../RouterError\";\nimport { putField } from \"../utils/ingest\";\n\nimport type {\n DefaultDependencies,\n LoggerConfig,\n Router,\n Route,\n} from \"../types\";\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst objectEntries = Object.entries;\nconst hasOwn = Object.hasOwn;\n\n/**\n * Per-clone overrides beyond dependencies.\n */\nexport interface CloneOptions {\n /**\n * Per-clone logger config override, merged **over** the base router's resolved\n * logger config. Primary use: per-request `traceId` in SSR — a fresh\n * `callback` closed over the request id, while `level` inherits the base.\n * Omitted keys inherit the base (level / callback / callbackIgnoresLevel).\n *\n * Override is by **config**, not a logger instance: `RouterLogger` is\n * core-internal (only its `{ log, warn, error }` interface is public), so\n * nothing outside core constructs one — configuration is the whole surface.\n */\n logger?: Partial<LoggerConfig>;\n}\n\n/**\n * Build an independent router instance that shares the route tree, options,\n * lifecycle guards, and plugin factories of `router`. The primary use case\n * is **SSR multi-tenancy** — one base router per process, one clone per\n * request.\n *\n * @param router - Source router (must not be disposed).\n * @param dependencies - Optional per-clone overrides merged on top of the\n * base router's dependencies. Always **fresh per call** in the documented\n * SSR pattern: pass per-request state here, never store it in the base.\n *\n * @remarks\n *\n * **Dependency merge — shallow by design.** `base.dependencies` are spread\n * into the clone via `{ ...sourceDeps, ...dependencies }`. Top-level keys\n * are new objects, but **values are shared by reference**: a `Map`, `Set`,\n * class instance, function, or nested plain object stored in\n * `base.dependencies` is the **same instance** in every clone. Mutations\n * in one clone are visible in the base and in every sibling clone.\n *\n * This is intentional. `structuredClone` of dep values is **not** applied\n * because it would:\n * - strip class prototypes (`new DbClient()` → plain object, methods lost)\n * - reject functions and symbols (`DataCloneError`)\n * - fragment singleton pools (one connection pool per request — pool\n * semantics destroyed)\n * - reject circular references\n *\n * **SSR rule of thumb.** Place values in `base.dependencies` according to\n * their lifecycle:\n *\n * - **Singletons / shared services** → `base.dependencies`. Examples: DB\n * client, connection pool, logger, config, feature-flag client. Process-\n * wide pooling depends on sharing these by reference.\n * - **Per-request state** → the `dependencies` override parameter (or\n * `createRequestScope`'s `deps` argument). Examples: `currentUser`,\n * `traceId`, `sessionId`, `abortSignal`. The override is applied last,\n * so it wins over base keys; pass a fresh object per call.\n *\n * Cross-request data leaks are **only possible** when per-request mutable\n * state is incorrectly placed in `base.dependencies`. The override slot is\n * the safe channel.\n *\n * @example\n * ```typescript\n * // Server boot — singletons only\n * const base = createRouter(routes, options, {\n * db: new DbClient(dbUrl),\n * logger,\n * });\n *\n * // Per request — fresh override per call\n * const clone = cloneRouter(base, {\n * currentUser,\n * traceId,\n * });\n * // clone.deps.db === base.deps.db ✓ shared pool (intentional)\n * // clone.deps.currentUser ✓ unique per request\n * ```\n *\n * @see createRequestScope — `@real-router/ssr-utils` SSR helper that\n * wraps this function and injects `abortSignal` automatically.\n */\nexport function cloneRouter<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n router: Router<Dependencies>,\n dependencies?: Dependencies,\n opts?: CloneOptions,\n): RouterClass<Dependencies> {\n const ctx = getInternals(router);\n\n if (ctx.isDisposed()) {\n throw freezeThrownError(new RouterError(errorCodes.ROUTER_DISPOSED));\n }\n\n ctx.validator?.dependencies.validateCloneArgs(dependencies);\n\n // Get source store directly\n const sourceStore = ctx.routeGetStore();\n const routes = routeTreeToDefinitions(sourceStore.tree);\n const routeConfig = sourceStore.config;\n const resolvedForwardMap = sourceStore.resolvedForwardMap;\n const routeCustomFields = sourceStore.routeCustomFields;\n\n const {\n options,\n dependencies: sourceDeps,\n pluginFactories,\n loggerConfig,\n limits: sourceLimits,\n limitKeys,\n } = ctx.getCloneState();\n // Origin-aware factory snapshot — definition guards are re-registered with\n // `isFromDefinition=true` on the clone so `replace()` can still strip them\n // via `clearDefinitionGuards()`. External guards take the public lifecycle\n // API path so they survive `replace()` symmetric with the base.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const sourceLifecycleNamespace = sourceStore.lifecycleNamespace!;\n const { definition: definitionFactories, external: externalFactories } =\n sourceLifecycleNamespace.getFactoriesByOrigin();\n\n // ⚑ The caller's bag goes through the SAME door the constructor uses (#1860),\n // and the merge IS that door's walk (#1861). A spread FIRST would flatten\n // whatever the caller passed into a fresh literal before\n // `guardDependencyShape` ever saw it — leaving the check structurally vacuous with respect to the\n // argument it is meant to judge, so the door accepts a string, an array, a\n // class instance, a `Map` and an own getter that RUNS.\n // `cloneRouter` is the per-request SSR path (`angular/providersFactory`\n // forwards an application-authored `RequestDepsFactory` result straight here),\n // so `Map` silently becoming `{}` lost every dependency with no error at all.\n const mergedDeps = { ...sourceDeps } as Record<string, unknown>;\n\n if (dependencies !== undefined) {\n ingestDependencies(dependencies, (key, value) => {\n // ⚠ `putField`, not `mergedDeps[key] = value`. This destination is an\n // ORDINARY literal (`{ ...sourceDeps }`), so it carries `Object.prototype`\n // and a bare `[[Set]]` of `\"__proto__\"` would dispatch into the inherited\n // setter and swap the prototype instead of storing (#1852). The spread it\n // replaced was immune for free — a spread DEFINES — so writing the loop\n // without this would have been a regression, not a refactor.\n putField(mergedDeps, key, value);\n });\n }\n\n // The clone builds its OWN logger (isolation, #724) but INHERITS the base's\n // resolved config — frozen options don't carry `logger`, so without this the\n // clone would fall back to the default logger and lose the base's\n // callback/level. A per-request\n // `opts.logger` override (e.g. a traceId-bound callback) merges on top.\n //\n // ⚑ ONE read of the caller's slot (#1930). `CloneOptions` is unvalidated and\n // application-owned, so every read of it is a call into application code — and\n // a truthiness test plus a spread are two, deciding the branch on one answer\n // and shipping another. The local is what makes the clone keep the\n // per-request callback rather than falling back to the base's process-wide\n // sink, which is the isolation `createRequestScope` exists for.\n const loggerOverride = opts?.logger;\n const clonedLoggerConfig: Partial<LoggerConfig> = loggerOverride\n ? { ...loggerConfig, ...loggerOverride }\n : loggerConfig;\n\n // ⚑ The base's KEY, not its raw option (#1877). `urlParamsEncoding` is\n // supported input — a `toString`-backed value is legal — and building the\n // clone from `options` coerced it a SECOND time, so a drifting value gave the\n // clone a different encoding, and decoder, from its base. `createRequestScope`\n // clones per request, which is exactly where that lands.\n //\n // ⚑ And the base's resolved QUERY STRATEGIES, by the same rule as every other\n // slot in this literal: `urlParamsEncoding` (#1877), `limits` (#1880 / #1961),\n // `logger` (#1930), `queryParams` (#2171). A clone inherits what the base\n // resolved and re-reads nothing the caller still holds.\n //\n // ⚠ Re-validation refuses what is INVALID and cannot notice a value that is\n // merely DIFFERENT (#2032), so inheriting is what keeps a base and its clone\n // on one strategy. What it gives up is the clone re-running a refusal on a bag\n // its base already validated.\n //\n // ⚠ Consequence, the one #1877 names one paragraph up: the clone's\n // `getOptions().queryParams` carries the four declared names, so an unknown\n // key — a mis-spelled `arrayFromat` that `@real-router/validation-plugin`\n // reports on the BASE — is not in the clone's copy to report. Only the clone\n // honours the documented shape.\n //\n // ⚠ The clone's own `getOptions()` therefore reports the coerced key where the\n // base still reports the caller's value. That is a deliberate consequence, not\n // an oversight: only the clone honours the documented four-literal type.\n const newRouter = new RouterClass<Dependencies>(\n routes as Route<Dependencies>[],\n {\n ...options,\n // Conditional spread for `exactOptionalPropertyTypes`: the slot may be\n // absent, but never present-and-`undefined`.\n ...(sourceStore.matcherOptions?.queryParams !== undefined && {\n queryParams: sourceStore.matcherOptions.queryParams,\n }),\n logger: clonedLoggerConfig,\n // The base's RESOLVED limits, not its raw `options.limits` (#1880).\n // `createLimits`' spread re-invokes an accessor on the caller's bag, so\n // rebuilding from `options` gave a drifting getter a second answer and\n // the clone a different cap. These are already numbers.\n //\n // ⚠ Only the keys the base actually PASSED, resolved — not the whole\n // resolved bag. Substituting wholesale materialises the four unset\n // DEFAULTS into the clone's reported options, and that is not cosmetic:\n // `warnListeners: 1000` beside a SMALL `maxListeners` is a pair\n // `validation-plugin` refuses at install, so `cloneRouter` throws and\n // `createRequestScope` fails on EVERY request. Measured: 1 of 6 partial\n // bags, not all of them — `validators/options.ts` throws only when\n // `warnListeners > maxListeners > 0`, so it needs the base to have passed\n // a `maxListeners` under the 1000 default, and it needs the plugin\n // installed at all.\n //\n // ⚠ `limitKeys` — the base's SNAPSHOT of the names the caller passed, not\n // `Object.keys(options.limits)` (#1961). `options.limits` is the caller's\n // own object and stays mutable — core freezes only the level it owns\n // (#1832). Reading it here meant a `delete` after\n // construction left the base capped and every LATER clone uncapped —\n // measured, 30 subscriptions accepted against a base that throws at 2.\n // Under SSR that is a per-request clone with a different listener cap from\n // its base, i.e. #1880's own shape reopened through the key set.\n //\n // The snapshot is taken with `Object.keys`, mirroring `createLimits`'\n // SPREAD, and that pairing is load-bearing in both directions: the spread\n // skips a NON-ENUMERABLE own key, so the base does not see one, and a\n // snapshot taken with `Object.hasOwn` over the five known names would make\n // the clone stricter than its base. Walking `sourceLimits` and filtering\n // by presence in the caller's bag has the same defect from the other side\n // — it finds the materialised default and ships it. Both are pinned.\n //\n // Neither read invokes the bag's accessors, so #1880 still holds for the\n // VALUES: they all come from `sourceLimits`, which the base resolved once.\n //\n // ⚠ `Object.hasOwn`, NOT `key in sourceLimits`. `in` walks the prototype\n // chain, so it answers true for `\"__proto__\"`, `\"constructor\"`,\n // `\"toString\"` and every other `Object.prototype` member — a caller bag\n // built by `JSON.parse` can carry those as OWN keys, and they would have\n // been copied into the clone's reported options with an `Object.prototype`\n // MEMBER as the value: `Object.prototype` itself for `\"__proto__\"`, the\n // `Object` constructor for `\"constructor\"`, the native method for\n // `\"toString\"`. Measured: all three pass `in`, none passes `hasOwn`.\n //\n // ⚠ The nullish case is decided at the SNAPSHOT now, not here.\n // `limitKeys` is `undefined` exactly when the caller passed no bag, so\n // this skips the substitution for `undefined` AND for `null` — which is\n // the correct answer for `null` rather than a mere guard: `...options`\n // still carries it, and the clone resolves it to the same defaults the\n // base did. The gate this replaced had to say `!= null` rather than\n // `!== undefined` because `Object.keys(null)` THROWS, and a `!== undefined`\n // form made the clone, and only the clone, die on a config the base had\n // accepted: silent at construction, fatal per request inside\n // `createRequestScope`.\n ...(limitKeys !== undefined && {\n limits: Object.fromEntries(\n limitKeys\n .filter((key) => hasOwn(sourceLimits, key))\n .map((key) => [\n key,\n sourceLimits[key as keyof typeof sourceLimits],\n ]),\n ),\n }),\n // ⚠ The spread form is not stylistic — the three obvious alternatives were\n // each tried and each loses. `matcherOptions` and its `urlParamsEncoding`\n // are `| undefined` in the TYPE only (`createRoutesStore` has one caller,\n // always fed `deriveMatcherOptions(...)`, whose `snapshotEncodingKey`\n // returns a string on every path), so: a plain read fails TS2379 under\n // `exactOptionalPropertyTypes`; `?? \"default\"` adds an arm no test can\n // reach and drops branch coverage to 99.95%, which the 100% gate refuses;\n // a non-null assertion still yields `| undefined` and fails TS2379 too.\n // The spread's false arm is unreachable but costs no branch — v8 scores\n // `&&` as an operand pair, both hit.\n //\n // ⚠ It reads the field TWICE — guard, then value — which is structurally\n // the TOCTOU shape #1811 is about. It is safe HERE and only here: the\n // source is core-owned frozen plain data in a sealed slot, not a\n // caller-owned bag. Do not copy the pattern to a caller-owned source.\n ...(sourceStore.matcherOptions?.urlParamsEncoding !== undefined && {\n urlParamsEncoding: sourceStore.matcherOptions.urlParamsEncoding,\n }),\n },\n mergedDeps as Dependencies,\n );\n\n const newCtx = getInternals(newRouter);\n const newStore = newCtx.routeGetStore();\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const newLifecycleNamespace = newStore.lifecycleNamespace!;\n\n // Copy the source config + store-level maps BEFORE re-registering guards\n // (#1331 review): the definition-guard factories re-executed below must\n // observe the fully-built clone (encoders/decoders/defaultParams/custom\n // fields), mirroring the constructor where flushPendingGuards runs after the\n // store is complete. EVERY RouteConfig sub-map goes through a single\n // enumeration so a newly added config field is carried over automatically\n // (#965) — and deliberately UNCOUNTED here: the enumeration carries a new\n // field on its own, so a number beside it would go stale while the code stayed\n // correct (#1548). resolvedForwardMap and routeCustomFields are store-level\n // (not part of RouteConfig) and stay explicit.\n assignConfigEntries(newStore.config, routeConfig);\n // ⚑ Through `adoptForwardState`, not a bare assign (#1800). The forward state\n // is TWO halves — the map and the derived `hasAnyForward` flag — and writing\n // only the first is not enough. The clone's store is built from\n // `routeTreeToDefinitions(sourceStore.tree)`, bare `{name, path, children}`\n // with no `forwardTo`, so it starts at `hasAnyForward = false`; installing the\n // config behind that flag leaves `isActiveRoute` answering `false` for every\n // forwarding route on every clone — and `createRequestScope` clones per\n // request, so that is every SSR render.\n //\n // `Object.assign` stays INSIDE the call: it merges into the clone's own map\n // and returns it, so the clone keeps its own object. Passing\n // `resolvedForwardMap` directly would install the SOURCE's map by reference\n // and alias the two stores.\n adoptForwardState(\n newStore,\n Object.assign(newStore.resolvedForwardMap, resolvedForwardMap),\n );\n Object.assign(newStore.routeCustomFields, routeCustomFields);\n\n // #1175: carry the source rootPath. It lives in the store (not options/config),\n // and neither routeTreeToDefinitions nor getCloneState include it — so a clone\n // of a base configured with `setRootPath(\"/app\")` would otherwise build/match\n // under \"\" and 404 every request of a sub-path SSR deployment. setRootPath\n // rebuilds the tree in place with the just-copied config; the rebuild is only\n // paid when a rootPath is actually set, and it runs before the definition-guard\n // factories below so they observe the fully-built clone (rootPath included).\n if (sourceStore.rootPath !== \"\") {\n newCtx.setRootPath(sourceStore.rootPath);\n }\n\n const [definitionDeactivate, definitionActivate] = definitionFactories;\n const [externalDeactivate, externalActivate] = externalFactories;\n\n for (const [name, handler] of objectEntries(definitionDeactivate)) {\n newLifecycleNamespace.addCanDeactivate(name, handler, true);\n }\n\n for (const [name, handler] of objectEntries(definitionActivate)) {\n newLifecycleNamespace.addCanActivate(name, handler, true);\n }\n\n const lifecycle = getLifecycleApi(newRouter);\n\n for (const [name, handler] of objectEntries(externalDeactivate)) {\n lifecycle.addDeactivateGuard(name, handler);\n }\n\n for (const [name, handler] of objectEntries(externalActivate)) {\n lifecycle.addActivateGuard(name, handler);\n }\n\n // Plugin replay runs last and skips factories that a (contract-violating)\n // definition-guard factory already registered on the clone during the\n // re-compilation above — without the filter every clone would double-apply\n // such a plugin: once via the factory, once via this replay (#1331 review).\n const alreadyRegistered = new Set(newCtx.getCloneState().pluginFactories);\n const pluginsToReplay = pluginFactories.filter(\n (factory) => !alreadyRegistered.has(factory),\n );\n\n // Stryker disable next-line EqualityOperator: equivalent — `>= 0` is always true, but `usePlugin(...[])` with an empty spread is a no-op, so entering the block on an empty list behaves identically to skipping it. (ConditionalExpression stays live: `→false` skips a real plugin list and is killable.)\n if (pluginsToReplay.length > 0) {\n newRouter.usePlugin(...pluginsToReplay);\n }\n\n return newRouter;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"api.js","names":["freezeThrownError","RouterError","errorCodes","freeze","objectKeys","cache","getInternals","adoptChannel","canonicalize","materialize","buildURL","freezeThrownError","RouterError","errorCodes","buildAddArtifacts","buildReplaceArtifacts","compileArtifactGuards","getTransitionPath","spliceSubtree","nodeToDefinition","getInternals","guardRouteStructure","commitRouteUpdate","freezeThrownError","RouterError","errorCodes","hasOwn","getInternals","dropUnsafeKey","getInternals","getInternals","freezeThrownError","RouterError","errorCodes","routeTreeToDefinitions","RouterClass"],"sources":["../../src/api/helpers.ts","../../src/namespaces/RoutesNamespace/routeGuards.ts","../../src/api/getPluginApi.ts","../../src/api/getRoutesApi.ts","../../src/api/getDependenciesApi.ts","../../src/api/getLifecycleApi.ts","../../src/api/cloneRouter.ts"],"sourcesContent":["// packages/core/src/api/helpers.ts\n\nimport { errorCodes } from \"../constants\";\nimport { RouterError, freezeThrownError } from \"../RouterError\";\n\nexport function throwIfDisposed(isDisposed: () => boolean): void {\n if (isDisposed()) {\n throw freezeThrownError(new RouterError(errorCodes.ROUTER_DISPOSED));\n }\n}\n\n/**\n * Bans synchronous reentrant tree mutation: a mutator called while a\n * `TREE_CHANGED` emit is on the stack (i.e. from inside a `subscribeChanges`\n * handler) throws `REENTRANT_TREE_MUTATION` BEFORE mutating — the tree stays\n * atomic (#1032). Six callers, not five: the `getRoutesApi` mutators and\n * `getPluginApi.setRootPath` (#1751), which rebuilds tree and matcher alike.\n * Deferred CRUD (`queueMicrotask` / `await`) runs after the dispatch settles and\n * is unaffected; CRUD from a transition listener is not a TREE_CHANGED dispatch.\n *\n * ⚑ The remedy rides the ERROR, not this docblock (#1665). Kept here alone it is\n * visible to whoever maintains core and not to the application developer who\n * caught the throw — the same omission on the navigation ban produced two docs\n * issues before anyone reached the code.\n */\nexport function throwIfReentrantTreeMutation(\n isEmitting: () => boolean,\n isRevalidating: () => boolean,\n): void {\n if (isEmitting()) {\n throw freezeThrownError(\n new RouterError(errorCodes.REENTRANT_TREE_MUTATION, {\n message:\n \"[router] cannot mutate the route tree from inside a subscribeChanges handler — the mutation would run while a TREE_CHANGED emit is on the stack and the tree must stay atomic. Defer it: queueMicrotask(() => routes.add(...)) or await.\",\n }),\n );\n }\n\n // ⚠ A SECOND window with its own text, for the reason #1665 states about the\n // navigation ban: \"you are inside a subscribeChanges handler\" is false here —\n // the emit has already returned — and a developer told that reads their error\n // as spurious. The two windows are adjacent and the code is the same; the\n // sentence is what tells them apart (#1758).\n if (isRevalidating()) {\n throw freezeThrownError(\n new RouterError(errorCodes.REENTRANT_TREE_MUTATION, {\n message:\n \"[router] cannot mutate the route tree from inside replace()'s revalidation — the tree would move under a state that has not been revalidated yet. Defer it: queueMicrotask(() => routes.replace(...)) or await.\",\n }),\n );\n }\n}\n","import type { RouterLogger } from \"../../types\";\n\n/**\n * Validates removeRoute constraints.\n * Returns false if removal should be blocked (route is active).\n *\n * ⚠ This is the GATE only. The in-flight report is\n * {@link warnRemovalDuringNavigation}, and it deliberately does NOT live here:\n * it describes what a removal did to a navigation, which is not knowable until\n * the removal is known to have happened at all (#1756).\n *\n * @param name - Route name to remove\n * @param currentStateName - Current active route name (or undefined)\n * @param logger - Per-router logger instance (from `getInternals(router).logger`)\n * @returns true if removal can proceed, false if blocked\n */\nexport function validateRemoveRoute(\n name: string,\n currentStateName: string | undefined,\n logger: RouterLogger,\n): boolean {\n if (currentStateName) {\n const isExactMatch = currentStateName === name;\n // ⚑ The prefix IS the ancestry test again, and that is a consequence of\n // #1763 rather than a step back from #1757.\n //\n // #1757 replaced this line with a walk of the matcher's segment chain,\n // because core accepted a dotted LEAF: a standalone `x.y` declared beside\n // `x` matched `startsWith(\"x.\")` and made `remove(\"x\")` refuse with `it is\n // currently active (current: \"x.y\")` — a sentence that was false about a\n // route nothing was removing — and it fired for a `name` that was not a\n // route at all, masking the not-found report it runs above.\n //\n // #1763 removed the shape instead: a route NAME cannot carry a dot, so a\n // dotted committed name implies its ancestor EXISTS and is a real ancestor.\n // Measured, not assumed — the two forms were probed on every shape still\n // constructible and agree on all of them, which is why the chain walk, its\n // `matcher` parameter and the O(depth) lookup are gone from a cold path.\n //\n // ⚠ The SIBLING half of #1757 is NOT equivalent and stays: `spliceSubtree`\n // still reports the names the splice actually took, because the lifecycle\n // registry is the one registry `add`/`replace` never gated — an external\n // guard can still be registered for a dotted name that is not a route, and\n // clearing it by prefix is the fail-open #1757 was filed for. See\n // `removeRoute.test.ts`.\n const isInRemovedSubtree =\n isExactMatch || currentStateName.startsWith(`${name}.`);\n\n if (isInRemovedSubtree) {\n const suffix = isExactMatch ? \"\" : ` (current: \"${currentStateName}\")`;\n\n logger.warn(\n \"router.removeRoute\",\n `Cannot remove route \"${name}\" — it is currently active${suffix}. Navigate away first.`,\n );\n\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Reports a removal that landed while a navigation was in flight (#1756).\n *\n * ⚠ Called from the `remove()` door AFTER the removal is known to have\n * happened — deliberately NOT from `validateRemoveRoute`, which runs above the\n * existence check. Everything below is a consequence of a route leaving the\n * tree, so for a `name` that is no route there is nothing to report and the\n * door's own \"not found. No changes made.\" is the whole story. Warning from the\n * gate produced two adjacent, contradicting lines out of one call.\n *\n * @param name - Route name that was removed\n * @param logger - Per-router logger instance (from `getInternals(router).logger`)\n */\nexport function warnRemovalDuringNavigation(\n name: string,\n logger: RouterLogger,\n): void {\n // ⚑ Says the MECHANISM, not \"may cause unexpected behavior\" (#1756). The\n // removal proceeds — deliberately: the guard above protects the COMMITTED\n // state, and the route being navigated TO is not it. If the removed subtree\n // is on the in-flight navigation's path, that navigation is cancelled by\n // the commit door instead, and the committed state is left untouched. So\n // the outcome is safe in both directions.\n //\n // ⚠ \"safe\" needs a WELL-FORMED tree, and every tree is one: bare core refuses\n // a dotted route name at registration (#1763), so the shape below is\n // UNCONSTRUCTIBLE rather than merely rare. Kept as the record of why the door\n // and this guard ask different questions. The commit door asks\n // `hasRoute(toState.name)` — the terminal only, never its ancestors — while\n // this guard's refusal covers the whole dotted ancestry. When an ancestor is\n // a SEPARATE definition rather than a `children` entry, removing it does not\n // take the descendant with it, the door sees a live terminal, and the\n // navigation commits with `transition.segments.activated` naming a route\n // `has()` denies: `buildPath` on that segment throws and `isActiveRoute`\n // answers true for it. With nested `children` the subtree goes together and\n // the door refuses, which is the shape this comment describes.\n //\n // ⚠ The gap this closes is NARROWER than \"the caller cannot tell\", and\n // narrower than \"the rejection already carries the removed route's name\":\n // measured, it carries\n // the name the COMMIT DOOR could not find — the navigation's TARGET — on the\n // async arcs as `ROUTE_NOT_FOUND { routeName }` directly and on the sync arc\n // threaded through `asCancellation` as `error.reason`. Those coincide only\n // when you removed the target itself. Remove an ANCESTOR of it — the shape\n // this warning's own parenthetical calls out, and the one #1756 reproduces —\n // and the payload names the descendant, never the route you passed to\n // `remove()`. So nothing in the error connects the failure to the call that\n // caused it, and the WARNING is the only place that can.\n //\n // ⚠ It cannot name WHICH of the two happened: telling \"you removed the\n // route you are navigating to\" from \"you removed an unrelated route\" needs\n // the in-flight target, and `RouterInternals` deliberately exposes no\n // handle on the navigation in flight. Saying both outcomes is the honest\n // form until that changes.\n //\n // ⚠ It prints the code VALUES, not the `errorCodes` keys:\n // `errorCodes.TRANSITION_CANCELLED === \"CANCELLED\"` (`constants.ts`), so a\n // caller who matched the key read out of a log line would never match.\n //\n // ⚠ And it splits the two codes by CHANNEL, not only by arc, because they\n // do not agree on the synchronous one. Measured: the rejected `navigate()`\n // promise carries `\"CANCELLED\"` there while `onTransitionError` carries\n // `\"ROUTE_NOT_FOUND\"` — one failure, two codes, depending on where the\n // caller is listening. `onTransitionCancel` never fires on this path at\n // all (`CANCEL` is sent only by `stop()`/`dispose()` and the\n // external-signal bridge), so the hook is the STABLE predicate of the two\n // and the sentence says which is which. Naming the arc split and then\n // appending the hook reads as \"the hook carries these codes\" — true on the\n // async arc, false on the sync one.\n //\n // ⚠ It names BOTH failure codes, and neither alone is right.\n // `TRANSITION_CANCELLED` holds only\n // while the guard walk is still synchronous, where `handleNavigateError`\n // finds the machine already out of the band and rewraps. Once the walk has\n // gone async the raw `ROUTE_NOT_FOUND` from the commit door reaches the\n // caller unwrapped. Measured on four arcs: sync guard `CANCELLED`, async\n // activate / async deactivate / async `subscribeLeave` all `ROUTE_NOT_FOUND`.\n //\n // ⚠ And it SAYS the removal happened, because it is only reached once it\n // has. Above the existence check this warning contradicts itself one line\n // later: `remove(\"nope\")` mid-navigation reports `Route \"nope\" removed` and\n // then `Route \"nope\" not found. No changes made.` What holds it there is a\n // PROPERTY — no in-flight report when nothing was removed — rather than the\n // wording of this sentence.\n logger.warn(\n \"router.removeRoute\",\n `Route \"${name}\" removed while navigation is in progress. Removing a route the ` +\n `router is navigating to (or an ancestor of it) fails that navigation. The ` +\n `rejected navigate() promise carries \"CANCELLED\" while the guard walk is ` +\n `synchronous and \"ROUTE_NOT_FOUND\" once it has gone async; onTransitionError ` +\n `always reports \"ROUTE_NOT_FOUND\", and onTransitionCancel never fires. The ` +\n `committed state is not affected either way.`,\n );\n}\n\n/** The root path minus its `?`-declared query names — the half that moves paths. */\nfunction pathPartOf(rootPath: string): string {\n const queryAt = rootPath.indexOf(\"?\");\n\n return queryAt === -1 ? rootPath : rootPath.slice(0, queryAt);\n}\n\n/**\n * Validates a `setRootPath` against an in-flight navigation (#1755).\n *\n * `applyRootPath` rebuilds the tree AND the matcher from the same definitions\n * (`routesStore.ts`), so every route name survives and every route's path is\n * REBUILT under the new root — which moves them all at once whenever the root's\n * path half changes. That is the same whole-tree REBUILD `clear` and `replace`\n * are refused for (not destruction: those two can drop names, this one never\n * does), and of the three it was the only one that applied anyway. A\n * navigation's activation guard could move the URL out from under its own\n * transition, and the transition then committed a state naming a route the tree\n * no longer routes that path to — which the same navigation's success announce\n * hands straight to every URL plugin, address bar included.\n *\n * ⚠ \"the only one that applied\" is about the three whole-tree ops, not about\n * the six doors: `add` proceeds with no check at all and `update` proceeds after\n * a log. Their in-flight policy is deliberately different — see the CRUD table\n * in `packages/core/CLAUDE.md`.\n *\n * ⚠ The refusal is `logger.error` + no-op, NOT a throw, and that follows the\n * family's own rule rather than `setRootPath`'s neighbours on `PluginApi`: a\n * condition that clears by itself (a navigation settles) gets a log, one that\n * never does gets a throw. The reentrancy ban beside this one throws for\n * exactly that reason — a `TREE_CHANGED` dispatch is not something you can wait\n * out from inside it.\n *\n * @param currentRootPath - The root path in effect\n * @param nextRootPath - The root path being set\n * @param isNavigating - Whether navigation is in progress\n * @param logger - Per-router logger instance (from `getInternals(router).logger`)\n * @returns true if setRootPath can proceed, false if blocked\n */\nexport function validateSetRootPath(\n currentRootPath: string,\n nextRootPath: string,\n isNavigating: boolean,\n logger: RouterLogger,\n): boolean {\n // Only the PATH half of the root moves route paths; the `?name` half declares\n // query params on every route and moves nothing. Measured, with the gate off:\n // `\"\" → \"/app\"` mid-navigation commits `state.path` the tree cannot match,\n // while `\"\" → \"?lang\"` and `\"?lang\" → \"\"` both commit a state that still\n // round-trips. So the refusal is scoped to the half that does the damage.\n //\n // ⚑ Scoping it is not a nicety — the whole-string form was a REGRESSION.\n // `@real-router/persistent-params-plugin` declares its keys with a query-only\n // root (`setRootPath(\"?lang\")`) and restores the original in `teardown()`. An\n // `unsubscribe()` reached from a guard or a `subscribeLeave` listener would\n // have found that restore silently refused — no throw, so the plugin's own\n // `catch` could not see it — leaving `?lang` declared on a router the caller\n // believes is clean, where a later `navigate(\"x\", { lang })` throws\n // `WRONG_CHANNEL` for a plugin that is no longer installed.\n if (\n isNavigating &&\n pathPartOf(currentRootPath) !== pathPartOf(nextRootPath)\n ) {\n logger.error(\n \"router.setRootPath\",\n \"Cannot change the root PATH while navigation is in progress — it moves every route's path, including the one being navigated to. Wait for navigation to complete. (Changing only the `?`-declared query names is allowed here: it moves no paths.)\",\n );\n\n return false;\n }\n\n return true;\n}\n\n/**\n * Validates clearRoutes operation.\n * Returns false if operation should be blocked (navigation in progress).\n *\n * @param isNavigating - Whether navigation is in progress\n * @param logger - Per-router logger instance (from `getInternals(router).logger`)\n * @returns true if clearRoutes can proceed, false if blocked\n */\nexport function validateClearRoutes(\n isNavigating: boolean,\n logger: RouterLogger,\n): boolean {\n if (isNavigating) {\n logger.error(\n \"router.clearRoutes\",\n \"Cannot clear routes while navigation is in progress. Wait for navigation to complete.\",\n );\n\n return false;\n }\n\n return true;\n}\n","import { assertShippedChannelCorrect } from \"../channels\";\nimport { buildURL, canonicalize, materialize } from \"../pipeline\";\nimport { throwIfDisposed, throwIfReentrantTreeMutation } from \"./helpers\";\nimport { errorCodes } from \"../constants\";\nimport {\n assertEventNameIsValid,\n assertInterceptableSeam,\n assertListenerIsFunction,\n} from \"../guards\";\nimport { adoptChannel } from \"../helpers\";\nimport { getInternals, throwOnMisChanneledKey } from \"../internals\";\nimport { validateSetRootPath } from \"../namespaces/RoutesNamespace/routeGuards\";\nimport { RouterError, freezeThrownError } from \"../RouterError\";\nimport { putField } from \"../utils/ingest\";\n\nimport type { PluginApi } from \"./types\";\nimport type {\n ContextNamespaceClaim,\n DefaultDependencies,\n Params,\n Router,\n SearchParams,\n State,\n} from \"../types\";\n\n/** Captured like the deciding seven, but this one BUILDS the guarantee (#2073). */\nconst freeze = Object.freeze;\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst objectKeys = Object.keys;\n\n// Cache the assembled PluginApi per router — mirrors getNavigator() (#525):\n// avoids re-allocating the closure-bag on each call (plugins call this once\n// at init, but tests + nested plugins poll it).\n//\n// ⚠ A stub belongs one layer DOWN, on `getInternals(router)` (#1805): a spy\n// there intercepts a call made through this surface — measured — while a spy\n// HERE would land on the object nineteen packages share.\n//\n// ⚠ Members fall into THREE classes, and the advice differs per class: one\n// CALLS `ctx.<name>()` and is intercepted whenever the spy stands, one ALIASES\n// `ctx.<name>` and captures it when this cached surface is BUILT — so a spy\n// installed afterwards is missed — and one composes its answer locally and has\n// no seam at all. Which member is which is derived, not listed here:\n// `tests/functional/plugin-api-stub-seam-authority-1805.test.ts` owns the sets\n// and measures the order-dependence, because naming them in prose has been\n// wrong twice.\nconst cache = new WeakMap<object, PluginApi>();\n\nexport function getPluginApi<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(router: Router<Dependencies>): PluginApi {\n const cached = cache.get(router);\n\n if (cached) {\n return cached;\n }\n\n const ctx = getInternals(router);\n const api: PluginApi = {\n makeState: (name, params, search, path) => {\n throwOnMisChanneledKey(ctx, \"makeState\", name, params);\n\n // ⚑ Core's SINGLE read, taken before the validator judges (#2134). The\n // façade doors do the same; these three are the plugin-facing half of the\n // same defect, where the caller is a plugin author rather than an app.\n const ownParams = adoptChannel(params);\n\n ctx.validator?.state.validateMakeStateArgs(name, ownParams, path);\n ctx.validator?.navigation.validateSearch(search, \"makeState\");\n\n // Public PluginApi.makeState carries the query channel (RFC-4 M2 / #1548)\n // so plugins (e.g. browser-plugin popstate restore) can reconstruct a\n // split state from a serialized history entry. It takes no per-segment\n // param-source map: ownership is read from the live matcher by\n // `state.name`, so nothing a caller could supply there would be consulted.\n return ctx.makeState(name, ownParams, search, path);\n },\n forwardState: <\n P extends Params = Params,\n S extends SearchParams = SearchParams,\n >(\n routeName: string,\n routeParams: P,\n routeSearch?: S,\n ) => {\n // ⚠ NO copy here, and the difference from the two doors around it is\n // measured rather than stylistic (#2134). Those PRINT a URL out of the\n // bag, so a read the validator did not see reaches the user; this one\n // hands the container back — by IDENTITY on a clean bag, which\n // `handed-out-containers-1957` pins. Measured on a non-forwarding route,\n // core reads the bag ZERO times through this door: there is no shipped\n // read for a judged one to disagree with, and a copy would buy nothing at\n // the price of that identity.\n ctx.validator?.routes.validateStateBuilderArgs(\n routeName,\n routeParams,\n \"forwardState\",\n );\n ctx.validator?.navigation.validateSearch(routeSearch, \"forwardState\");\n\n return ctx.forwardState<P, S>(routeName, routeParams, routeSearch);\n },\n matchPath: (path) => {\n ctx.validator?.routes.validateMatchPathArgs(path);\n\n return ctx.matchPath(path, ctx.getOptions());\n },\n navigateToState: (state, options) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.navigation.validateNavigateToStateArgs(state);\n\n if (options !== undefined) {\n ctx.validator?.navigation.validateNavigationOptions(\n options,\n \"navigateToState\",\n );\n }\n\n return ctx.navigateToState(state, options);\n },\n setRootPath: (rootPath) => {\n throwIfDisposed(ctx.isDisposed);\n // The sixth tree mutator, and the one that joined the family late (#1751).\n // `applyRootPath` rebuilds tree AND matcher, so a call from inside a\n // `subscribeChanges` handler swaps what the router resolves against while\n // the listeners still queued in that dispatch reason about the payload's\n // tree. Ordered AFTER `throwIfDisposed` deliberately: `dispose()` sends\n // DISPOSE before `clearAll()`, and `clearAll()` leaves `#dispatching`\n // standing (#1164), so both predicates are true during a teardown reached\n // from a handler — `ROUTER_DISPOSED` has to keep winning there.\n // ⚠ The revalidation window bans this door too, and it is named in\n // `commitRevalidated`'s own list of what invalidates a committed state:\n // every route name survives a `setRootPath` and every PATH moves, so the\n // committed `state.path` stops belonging to `state.name` (#1758).\n throwIfReentrantTreeMutation(\n ctx.treeChanged.isEmitting,\n () => ctx.routeGetStore().revalidating,\n );\n\n ctx.validator?.routes.validateSetRootPathArgs(rootPath);\n\n // ⚑ Returns whether it APPLIED, and that is the one place this door\n // departs from its route-CRUD siblings (all `void` + log). It has to: the\n // siblings are application-facing, where a human reads the console, while\n // this one is plugin-facing, and the caller that most needs the answer is\n // a `teardown()`. The refusal's whole justification — \"a condition that\n // clears by itself gets a log\" — is FALSE for a teardown: the plugin will\n // never call again, so a refused restore is permanent and, returning\n // `void`, undetectable. Measured: a plugin holding a path prefix, torn\n // down mid-navigation, leaked that prefix forever.\n //\n // ⚑ The sixth member of the in-flight family rule, and the last to join it\n // (#1755). Validation runs ABOVE it: an argument-shape defect is the\n // caller's bug whatever the router is doing, while this refusal is about\n // timing, and reporting the timing first would hide a `TypeError` behind a\n // log line the caller did not cause.\n //\n // ⚠ That matches `remove` (and `update`'s argument half) and CONTRADICTS\n // `replace`, which puts its in-flight gate above `guardRouteStructure` and\n // every validator. The family is not uniform on this axis, so the ordering\n // is chosen on its merits here rather than copied — do not read it as a\n // convention.\n if (\n !validateSetRootPath(\n ctx.getRootPath(),\n rootPath,\n ctx.isTransitioning(),\n ctx.logger,\n )\n ) {\n return false;\n }\n\n ctx.setRootPath(rootPath);\n\n return true;\n },\n getRootPath: ctx.getRootPath,\n addEventListener: (eventName, cb) => {\n throwIfDisposed(ctx.isDisposed);\n\n assertEventNameIsValid(eventName);\n assertListenerIsFunction(cb);\n ctx.validator?.eventBus.validateListenerArgs(eventName, cb);\n\n return ctx.addEventListener(eventName, cb);\n },\n buildNavigationState: (name, params = {}, search = {}) => {\n throwOnMisChanneledKey(ctx, \"buildNavigationState\", name, params);\n\n const ownParams = adoptChannel(params);\n\n ctx.validator?.navigation.validateSearch(search, \"buildNavigationState\");\n ctx.validator?.routes.validateStateBuilderArgs(\n name,\n ownParams,\n \"buildNavigationState\",\n );\n\n // Stages ① + ③ + the mode gate, one pass through the pipeline\n // (nav-pipeline Phase 2, step 2-4). `search` flows THROUGH the forwardState\n // seam, not past it (#1571) — `port.resolveForward` IS `ctx.forwardState`,\n // so the seam is still where an explicit query value wins over a declared\n // twin the caller rode in `params`, and where a `search-schema`\n // interceptor sees the query channel.\n const canonical = canonicalize(ctx.port(), name, ownParams, search, {\n diagnoseUndeclared: true,\n });\n\n // Existence is checked BEFORE the URL is built, and the order is\n // load-bearing: `buildURL` prints through the matcher, which throws on an\n // unknown route, whereas this entry point answers `undefined` for one —\n // including when a `forwardTo` chain resolves to a target that does not\n // exist. (`canonicalize` itself is total here: a missing route simply has\n // no defaults and no declared query names.)\n if (!ctx.buildStateResolved(canonical.name, canonical.path)) {\n return;\n }\n\n // ⑤a then ⑤b from ONE canonical intent, so `state.search` and `state.path`\n // cannot derive from differently-merged bags.\n assertShippedChannelCorrect(\n \"buildNavigationState\",\n canonical.name,\n canonical.path,\n ctx.port().queryNames(canonical.name),\n );\n\n return materialize(canonical, buildURL(canonical, ctx.port()));\n },\n getOptions: ctx.getOptions,\n getTree: ctx.getTree,\n addInterceptor: (method, fn) => {\n throwIfDisposed(ctx.isDisposed);\n assertInterceptableSeam(method, fn);\n\n let list = ctx.interceptors.get(method);\n\n if (!list) {\n list = [];\n ctx.interceptors.set(method, list);\n }\n\n list.push(fn);\n\n // Idempotency flag (#1198). Without it, a double call would `indexOf(fn)`\n // again and splice a DUPLICATE registration of the same fn — silently\n // deactivating another plugin's interceptor whose own unsubscribe was never\n // called. The `Unsubscribe` contract is documented idempotent. The flag\n // guarantees exactly one splice of a still-present `fn`, so no `index !== -1`\n // guard is needed (it would be dead — the second call returns above).\n let removed = false;\n\n return () => {\n if (removed) {\n return;\n }\n\n removed = true;\n list.splice(list.indexOf(fn), 1);\n };\n },\n getRouteConfig: (name) => {\n const store = ctx.routeGetStore();\n\n // Stryker disable next-line ConditionalExpression,BlockStatement: equivalent — a missing route yields routeCustomFields[name] === undefined, identical to this early return\n if (!store.matcher.hasRoute(name)) {\n return;\n }\n\n return store.routeCustomFields[name];\n },\n extendRouter: (extensions: Record<string, unknown>) => {\n throwIfDisposed(ctx.isDisposed);\n\n const keys = objectKeys(extensions);\n\n for (const key of keys) {\n if (key in router) {\n throw freezeThrownError(\n new RouterError(errorCodes.PLUGIN_CONFLICT, {\n message: `Cannot extend router: property \"${key}\" already exists`,\n }),\n );\n }\n }\n\n // ⚑ Every value is read BEFORE any is written (#1933). `extensions` is\n // the caller's object, so each read is a call into application code, and a\n // loop that reads and writes together installs keys it can then abandon\n // mid-way: nothing tracks them, so no unsubscribe carries them, the\n // `dispose()` safety net walks a record that was never pushed, and every\n // later plugin claiming one of those names is refused for the life of the\n // router. Prepare-then-commit, the same shape route CRUD uses.\n //\n // ⚠ One read per key either way — the reads MOVE, they do not multiply.\n const values = keys.map((key) => extensions[key]);\n\n const extensionRecord = { keys };\n\n for (const [index, key] of keys.entries()) {\n (router as Record<string, unknown>)[key] = values[index];\n }\n\n ctx.routerExtensions.push(extensionRecord);\n\n let removed = false;\n\n return () => {\n if (removed) {\n return;\n }\n\n removed = true;\n\n for (const key of extensionRecord.keys) {\n delete (router as Record<string, unknown>)[key];\n }\n\n const idx = ctx.routerExtensions.indexOf(extensionRecord);\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,UnaryOperator,BlockStatement: equivalent — this splice only tidies the `routerExtensions` TRACKING array; the router INSTANCE is cleaned by the `delete router[key]` loop above, and dispose()'s safety-net re-deletes any leaked key harmlessly. So no mutation of this guard/splice is behaviourally observable (full suite green with `===`, `+1`, and an empty body). Contrast the addInterceptor splice, which IS observable through buildPath and is killed behaviourally by invariantGuardMutants.test.ts.\n if (idx !== -1) {\n ctx.routerExtensions.splice(idx, 1);\n }\n };\n },\n emitTransitionError: (error) => {\n throwIfDisposed(ctx.isDisposed);\n ctx.emitTransitionError(error);\n },\n claimContextNamespace: (namespace: string) => {\n throwIfDisposed(ctx.isDisposed);\n\n // Input-shape guard, symmetric with the other always-on invariant guards\n // (subscribe / start / navigateToNotFound each typeof-check their input).\n // A non-string namespace coerces to an inconsistent key (\"42\"); an empty\n // string is a meaningless namespace (#1191 N4).\n if (typeof namespace !== \"string\" || namespace === \"\") {\n throw new TypeError(\n `[claimContextNamespace] namespace must be a non-empty string, got ${\n typeof namespace === \"string\" ? \"an empty string\" : typeof namespace\n }`,\n );\n }\n\n if (ctx.contextClaimRecords.has(namespace)) {\n throw freezeThrownError(\n new RouterError(errorCodes.CONTEXT_NAMESPACE_ALREADY_CLAIMED, {\n message: `Cannot claim context namespace: \"${namespace}\" is already claimed by another plugin`,\n }),\n );\n }\n\n // ⚑ The record stores the CLAIM, not just its name, so both methods below\n // can ask whether they are still the holder (#2059 / #1929). Without that\n // identity a released claim is indistinguishable from the live one, and\n // the \"two plugins clobber each other\" corruption this mechanism exists to\n // prevent is reachable with the records perfectly consistent.\n const claim: ContextNamespaceClaim = {\n write(state: State, value: unknown) {\n if (ctx.contextClaimRecords.get(namespace) !== claim) {\n return;\n }\n\n // ⚑ `putField`, not the `namespace === \"__proto__\"` special case this\n // replaces (#1191 N3 → #1852). That form closed exactly one LITERAL,\n // and the key here is a plugin's namespace: the names that hurt are\n // the ordinary ones the shipped plugins already use. Measured on a\n // real navigation with an ambient `data` / `rsc` accessor, the outcome\n // was not even an error the caller could see — `claim.write` runs from\n // an `onTransitionSuccess` hook, so the emitter's throw isolation ate\n // it, `start()` resolved, and `getState().context` was `{}`.\n putField(state.context, namespace, value);\n },\n release() {\n if (ctx.contextClaimRecords.get(namespace) !== claim) {\n return;\n }\n\n ctx.contextClaimRecords.delete(namespace);\n },\n };\n\n ctx.contextClaimRecords.set(namespace, claim);\n\n return claim;\n },\n };\n\n // ⚑ FROZEN, and the cache above is what makes it necessary (#1805). One object\n // per router is handed to EVERY consumer, so a single\n // `api.addInterceptor = …` — the shape an \"instrument everything\" line takes —\n // rewires the surface for all of them silently. The consumer count is\n // deliberately not restated, for the reason its twin at `getRoutesApi` gives:\n // it grows with the tier while the hazard is the sharing, which one consumer\n // is enough to have. `getRoutesApi` and\n // `getNavigator` next door are frozen for the same reason; the two UNCACHED\n // factories (`getLifecycleApi`, `getDependenciesApi`) need nothing, because a\n // write to a per-call object cannot reach a second consumer.\n const frozen = freeze(api);\n\n cache.set(router, frozen);\n\n return frozen;\n}\n","import { nodeToDefinition } from \"../engine\";\nimport { throwIfDisposed, throwIfReentrantTreeMutation } from \"./helpers\";\nimport { errorCodes } from \"../constants\";\nimport { guardRouteCallbacks, guardRouteStructure } from \"../guards\";\nimport { getInternals } from \"../internals\";\nimport {\n assertRouteDefaultChannelsFor,\n clearConfigEntries,\n spliceSubtree,\n} from \"../namespaces/RoutesNamespace/helpers\";\nimport {\n validateClearRoutes,\n validateRemoveRoute,\n warnRemovalDuringNavigation,\n} from \"../namespaces/RoutesNamespace/routeGuards\";\nimport {\n adoptRouteArtifacts,\n assertAddable,\n assertNoDuplicateNamesInBatch,\n assertNoDuplicatePathsInBatch,\n assertNoDottedNamesInBatch,\n assertNonEmptyNamesInBatch,\n assertNoInternalNamesInBatch,\n assertNoInternalRouteName,\n buildAddArtifacts,\n buildReplaceArtifacts,\n commitRouteUpdate,\n commitTreeChanges,\n compileArtifactGuards,\n resetStore,\n} from \"../namespaces/RoutesNamespace/routesStore\";\nimport { RouterError, freezeThrownError } from \"../RouterError\";\nimport { getTransitionPath } from \"../transitionPath\";\nimport { putField } from \"../utils/ingest\";\n\nimport type { RoutesApi } from \"./types\";\nimport type { RouteDefinition, RouteTree } from \"../engine\";\nimport type { RouterInternals } from \"../internals\";\nimport type { RouteLifecycleNamespace, RouteConfig } from \"../namespaces\";\nimport type { RoutesStore } from \"../namespaces/RoutesNamespace\";\nimport type {\n DefaultDependencies,\n ForwardToCallback,\n NavigationOptions,\n Params,\n ParamsSearch,\n SearchParams,\n Router,\n RouterLogger,\n State,\n TransitionMeta,\n TreeChangedEvent,\n TreeStructuralPatch,\n GuardFnFactory,\n Route,\n} from \"../types\";\n\n/** Captured like the deciding seven, but this one BUILDS the guarantee (#2073). */\nconst freeze = Object.freeze;\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst objectKeys = Object.keys;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Opts attached to the `TRANSITION_SUCCESS` emitted by `replace()` when it\n * revalidates the active state (#950). `replace` does not push history, so it\n * is a replace-type success — matching `navigateToNotFound`'s opts for the\n * dropped-route branch.\n */\nconst REVALIDATE_OPTS: NavigationOptions = Object.freeze({\n replace: true,\n revalidate: true,\n});\n\n/** `removeRoute`'s \"removed, but nobody is listening\" payload. */\nconst EMPTY_SUBTREE: readonly never[] = Object.freeze([]);\n\n/**\n * Clears all config entries and lifecycle handlers for exactly the routes the\n * removal took out of the tree — `removedNames` is the splice's own report, not\n * a name-prefix guess (#1757).\n */\nfunction clearRouteConfigurations<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n removedNames: ReadonlySet<string>,\n config: RouteConfig,\n routeCustomFields: Record<string, Record<string, unknown>>,\n lifecycleNamespace: RouteLifecycleNamespace<Dependencies>,\n): void {\n // ⚑ The set comes from the SPLICE (`spliceSubtree`), not from the name string\n // (#1757). `name === routeName || name.startsWith(routeName + \".\")` asks a\n // strictly wider question: a flat dotted leaf `x.y` declared BESIDE `x` is a\n // standalone node the splice never touches, and the prefix claims it anyway.\n // The route then stays in the tree with its config and its guards\n // unregistered — a FAIL-OPEN, since a blocking `canActivate` simply\n // disappears and the route becomes freely activatable, with no log.\n const shouldClear = (name: string): boolean => removedNames.has(name);\n\n clearConfigEntries(config.decoders, shouldClear);\n clearConfigEntries(config.encoders, shouldClear);\n clearConfigEntries(config.defaultParams, shouldClear);\n clearConfigEntries(config.defaultSearch, shouldClear);\n clearConfigEntries(config.forwardMap, shouldClear);\n clearConfigEntries(config.forwardFnMap, shouldClear);\n clearConfigEntries(routeCustomFields, shouldClear);\n\n // Clear forwardMap entries pointing TO the deleted route (or its descendants)\n clearConfigEntries(config.forwardMap, (key) =>\n shouldClear(config.forwardMap[key]),\n );\n\n // Clear lifecycle handlers\n const [canDeactivateFactories, canActivateFactories] =\n lifecycleNamespace.getFactories();\n\n for (const name of objectKeys(canActivateFactories)) {\n if (shouldClear(name)) {\n // Route removed from the tree — both origin slots go (route no longer exists).\n lifecycleNamespace.clearCanActivate(name, \"both\");\n }\n }\n\n for (const name of objectKeys(canDeactivateFactories)) {\n if (shouldClear(name)) {\n lifecycleNamespace.clearCanDeactivate(name, \"both\");\n }\n }\n}\n\n/**\n * Re-attaches the stored config (forwardTo / defaultParams / encode-decode) and\n * lifecycle guards for `lookupName` onto `route`, then returns it (mutates in\n * place). Shared by {@link enrichRoute} (nested, bare `name`) and\n * {@link buildFlatRoute} (flat, full dotted `name`) — one source of truth for\n * the route-config field set.\n */\nfunction assignRouteConfig<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n route: Route<Dependencies>,\n lookupName: string,\n config: RouteConfig,\n factories: [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ],\n): Route<Dependencies> {\n const forwardToFn = config.forwardFnMap[lookupName];\n const forwardToStr = config.forwardMap[lookupName];\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (forwardToFn !== undefined) {\n route.forwardTo = forwardToFn;\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n } else if (forwardToStr !== undefined) {\n route.forwardTo = forwardToStr;\n }\n\n if (lookupName in config.defaultParams) {\n route.defaultParams = config.defaultParams[lookupName];\n }\n\n if (lookupName in config.defaultSearch) {\n route.defaultSearch = config.defaultSearch[lookupName];\n }\n\n if (lookupName in config.decoders) {\n route.decodeParams = config.decoders[lookupName];\n }\n\n if (lookupName in config.encoders) {\n route.encodeParams = config.encoders[lookupName];\n }\n\n const [canDeactivateFactories, canActivateFactories] = factories;\n\n if (lookupName in canActivateFactories) {\n route.canActivate = canActivateFactories[lookupName];\n }\n\n if (lookupName in canDeactivateFactories) {\n route.canDeactivate = canDeactivateFactories[lookupName];\n }\n\n return route;\n}\n\n/**\n * Builds a full Route object from a bare RouteDefinition by re-attaching\n * config entries and lifecycle factories.\n *\n * RECURSIVE — call with the factories tuple obtained ONCE from\n * `lifecycleNamespace.getFactories()` and pass it through to children.\n */\nfunction enrichRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routeDef: RouteDefinition,\n routeName: string,\n config: RouteConfig,\n factories: [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ],\n): Route<Dependencies> {\n const route: Route<Dependencies> = {\n name: routeDef.name,\n path: routeDef.path,\n };\n\n assignRouteConfig(route, routeName, config, factories);\n\n if (routeDef.children) {\n // ⚑ `putField`, the same write rule the registration walk carries\n // (#1852 / #2139). `route` is a literal with `name` and `path` on it, so\n // `children` has no own slot and a plain assignment walks the prototype.\n // Measured on this door: under an ambient `children` setter `get(name)`\n // returned a route whose children had gone into the setter, and under a\n // getter-only accessor it THREW instead of answering.\n putField(\n route as unknown as Record<string, unknown>,\n \"children\",\n routeDef.children.map((child) =>\n enrichRoute(child, `${routeName}.${child.name}`, config, factories),\n ),\n );\n }\n\n return route;\n}\n\n// ============================================================================\n// TREE_CHANGED payload helpers\n// ============================================================================\n\n/**\n * Builds a single FLAT `Route` for `fullName` from the store config + lifecycle\n * factories — `name` is the FULL dotted name and there is no `children` array\n * (consumers want a flat, by-name list). Frozen on construction.\n */\nfunction buildFlatRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n fullName: string,\n path: string,\n config: RouteConfig,\n factories: [\n Record<string, GuardFnFactory<Dependencies>>,\n Record<string, GuardFnFactory<Dependencies>>,\n ],\n): Route<Dependencies> {\n const route: Route<Dependencies> = { name: fullName, path };\n\n assignRouteConfig(route, fullName, config, factories);\n\n return freeze(route);\n}\n\n/**\n * Walks the store's definitions depth-first, building a FLAT\n * `Map<fullName, Route>` for every node whose full dotted name satisfies\n * `include`. Reads the live store, so call it at the right moment relative to\n * the mutation (before for removed, after for added).\n */\nfunction collectFlatRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n include: (fullName: string) => boolean,\n): Map<string, Route<Dependencies>> {\n const result = new Map<string, Route<Dependencies>>();\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const factories = store.lifecycleNamespace!.getFactories();\n\n const walk = (defs: readonly RouteDefinition[], parentName: string): void => {\n for (const def of defs) {\n const fullName = parentName ? `${parentName}.${def.name}` : def.name;\n\n if (include(fullName)) {\n result.set(\n fullName,\n buildFlatRoute(fullName, def.path, store.config, factories),\n );\n }\n\n if (def.children) {\n walk(def.children, fullName);\n }\n }\n };\n\n walk(store.definitions, \"\");\n\n return result;\n}\n\n/**\n * Collects the routes named by `removedNames` as a FLAT, frozen array — the\n * `TREE_CHANGED` payload for a removal.\n *\n * MUST be called AFTER the definitions splice (so the set is known) and BEFORE\n * `clearRouteConfigurations` + `commitTreeChanges` (so the store still carries\n * the config and the tree the payload is built from).\n *\n * ⚑ Driven by the splice's own set rather than by the name prefix (#1757): the\n * prefix form named a flat dotted namesake that `has()` still answers `true`\n * for, i.e. it announced the removal of a live route — the lying-event shape of\n * #1194 manifestation (1), reached through `remove`.\n */\nfunction collectSubtree<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n removedNames: ReadonlySet<string>,\n): readonly Route<Dependencies>[] {\n const subtree = collectFlatRoutes(store, (fullName) =>\n removedNames.has(fullName),\n );\n\n return freeze([...subtree.values()]);\n}\n\n/**\n * Builds the FLAT, frozen payload array for an `add`, walking only the input\n * routes — O(added), not O(tree). `path` is taken from the input verbatim\n * (`sanitizeRoute` never rewrites it); config fields are read from the\n * post-commit store by full name. `add` never removes, so the input subtree is\n * exactly what changed.\n */\nfunction collectAddedRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routes: readonly Route<Dependencies>[],\n parentName: string | undefined,\n store: RoutesStore<Dependencies>,\n): readonly Route<Dependencies>[] {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const factories = store.lifecycleNamespace!.getFactories();\n const result: Route<Dependencies>[] = [];\n\n const walk = (\n input: readonly Route<Dependencies>[],\n parent: string,\n ): void => {\n for (const route of input) {\n const fullName = parent ? `${parent}.${route.name}` : route.name;\n\n result.push(\n buildFlatRoute(fullName, route.path, store.config, factories),\n );\n\n if (route.children) {\n walk(route.children, fullName);\n }\n }\n };\n\n walk(routes, parentName ?? \"\");\n\n return freeze(result);\n}\n\n/** Diffs two flat route maps by full name into frozen removed/added arrays. */\nfunction diffFlatRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n before: ReadonlyMap<string, Route<Dependencies>>,\n after: ReadonlyMap<string, Route<Dependencies>>,\n): {\n removed: readonly Route<Dependencies>[];\n added: readonly Route<Dependencies>[];\n} {\n const removed: Route<Dependencies>[] = [];\n const added: Route<Dependencies>[] = [];\n\n for (const [fullName, route] of before) {\n if (!after.has(fullName)) {\n removed.push(route);\n }\n }\n\n for (const [fullName, route] of after) {\n if (!before.has(fullName)) {\n added.push(route);\n }\n }\n\n return { removed: freeze(removed), added: freeze(added) };\n}\n\n/**\n * Builds the structural subset of an `update()` patch (forwardTo /\n * defaultParams / encodeParams / decodeParams) from the already-destructured\n * update fields — so user getters are not re-invoked. A guard-only patch yields\n * an empty object → the caller emits no TREE_CHANGED (O-7: guards are\n * invoked-on-demand, not cached, so they need no observation channel).\n *\n * The returned envelope is a fresh object (caller's patch untouched) and is\n * frozen on construction. Nested values (e.g. `defaultParams`) are kept by\n * reference — the same objects the router stored — so exotic inputs (circular\n * refs, class instances) are tolerated, matching `update()`'s existing contract.\n */\nfunction buildStructuralPatch<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(fields: {\n forwardTo?: string | ForwardToCallback<Dependencies> | null | undefined;\n defaultParams?: Params | null | undefined;\n defaultSearch?: SearchParams | null | undefined;\n decodeParams?: ((channels: ParamsSearch) => ParamsSearch) | null | undefined;\n encodeParams?: ((channels: ParamsSearch) => ParamsSearch) | null | undefined;\n}): Readonly<TreeStructuralPatch<Dependencies>> {\n const patch: TreeStructuralPatch<Dependencies> = {};\n\n if (fields.forwardTo !== undefined) {\n patch.forwardTo = fields.forwardTo;\n }\n\n if (fields.defaultParams !== undefined) {\n patch.defaultParams = fields.defaultParams;\n }\n\n if (fields.defaultSearch !== undefined) {\n patch.defaultSearch = fields.defaultSearch;\n }\n\n if (fields.encodeParams !== undefined) {\n patch.encodeParams = fields.encodeParams;\n }\n\n if (fields.decodeParams !== undefined) {\n patch.decodeParams = fields.decodeParams;\n }\n\n return freeze(patch);\n}\n\n// ============================================================================\n// CRUD operations\n// ============================================================================\n\n/**\n * Adds one or more routes to the router.\n *\n * Takes the SNAPSHOT, not the caller's array, so every guard below this\n * validates what registration stores.\n */\nfunction addRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n batch: readonly Route<Dependencies>[],\n parentName: string | undefined,\n logger: RouterLogger,\n): void {\n // Prepare-then-commit (issue #698): reject the silent-corruption cases\n // up front (dup name vs existing, missing parent), build the merged tree /\n // config into locals (async/circular forwardTo + invalid constraint throw\n // here), then swap atomically. A rejected add leaves the store untouched.\n assertAddable(store, batch, parentName);\n\n const artifacts = buildAddArtifacts(store, batch, parentName, logger);\n\n // Config-time channel check on the PREPARED artifacts, in PREPARE — the same\n // position `replace` gives it, and for the same reason (a throw must precede\n // every mutation, not merely the swap).\n assertRouteDefaultChannelsFor(\n artifacts.matcher,\n artifacts.config,\n \"addRoute\",\n );\n\n // Pre-flight the #961 handler-limit into PREPARE so a limit-exceeding batch\n // aborts before the swap (#1046). `add` does not clear guards, so the\n // projection runs against the live union count (clearsDefinition = false).\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.preflightHandlerLimit(\n artifacts.pendingCanActivate.keys(),\n artifacts.pendingCanDeactivate.keys(),\n false,\n );\n\n adoptRouteArtifacts(store, artifacts);\n}\n\n/**\n * Commits a revalidated state after `replace()` and emits `TRANSITION_SUCCESS`\n * so `router.subscribe` / adapters re-render (#950). The emit carries\n * `REVALIDATE_OPTS` — the single distinguishable marker (`revalidate: true`) a\n * plugin's `onTransitionSuccess` can read to special-case a revalidation vs a\n * real navigation (#1201).\n */\nfunction commitRevalidated<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n ctx: RouterInternals<Dependencies>,\n nextState: State,\n fromState: State,\n): void {\n // ⚑ No ownership check here, and that is a consequence of the window (#1758\n // / #1759): every writer of `store.matcher` sits behind a route-CRUD entry\n // point the window refuses, so the URL's owner cannot move between the match\n // and the commit. The property lives where it can still FAIL —\n // `revalidation-window-doors-1758.test.ts` derives the writer set from `src`\n // and asserts each door consults the window.\n //\n // ⚠ The removal is justified BY the ban. Relaxing the window's rule brings\n // the door's question back, and the ratchet is where that would surface.\n\n // Through the machine (`SYSTEM_COMMIT`), so the write and the announce are\n // one table fact rather than two statements here. No SURVIVING route's\n // external factory is re-invoked between `replace()`'s entry\n // `throwIfDisposed()` and this line — `clearDefinitionGuards`'s re-derivation\n // READS the survivor's stored compiled form instead of re-running its factory\n // (#1192 / #1627 / #1649) — which is what makes unreachable the arc where a\n // `dispose()` from such a factory lets the swap finish and commit on a dead\n // router with zero events. The NEW batch's factories DO run; see below.\n //\n // ⚠ It does NOT follow that `replace()` \"executes nothing of the caller's\n // between the two points\". It executes at LEAST four other things, all\n // above: the NEW batch's\n // guard factories (`compileArtifactGuards` → `compileFactory`, which is\n // `factory(router, getDependency)`), the `TREE_CHANGED` handlers, the route's\n // own `decodeParams` invoked by the revalidating `matchPath`, and the new\n // route's activation guards consulted since #1201. ⚠ A fix's scope is not the\n // window's scope, and reading one for the other is what makes the question\n // above look unnecessary (#1753). ⚑ \"At least four\" on purpose: an\n // enumeration passed off as exhaustive is the failure this very ⚠ names, and\n // the shortest way to commit it is to count what one change touched.\n //\n // ⚑ The liveness this line relies on is KEPT, and deliberately: it covers a\n // router disposed or stopped by some OTHER means between the entry check and\n // here, which `replace()` does not cause but cannot rule out. No separate\n // re-check is needed for it — a dead router simply has no edge to take, and\n // `systemCommit` turns that silent refusal into the\n // throw the callers were already promised — `ROUTER_DISPOSED` after a\n // `dispose()`, `ROUTER_NOT_STARTED` after a `stop()`, since the machine is\n // then IDLE rather than DISPOSED (measured; #1644 split the two codes).\n ctx.systemCommit(nextState, fromState, REVALIDATE_OPTS);\n}\n\n/**\n * Atomically replaces all routes with a new set (HMR / code-splitting).\n * Prepare-then-commit (issue #698): the new set is fully built into locals\n * first — a circular/async forwardTo or invalid path throws here, leaving the\n * existing tree intact — then committed.\n */\nfunction replaceRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n batch: readonly Route<Dependencies>[],\n ctx: RouterInternals<Dependencies>,\n currentState: State | undefined,\n onCommitted?: () => void,\n): void {\n // Reject the silent-corruption cases `assertAddable` catches for `add`, BEFORE\n // building/swapping, so bare-core parity is symmetric (#1047): within-batch\n // duplicate names (#968), reserved \"@@\" names (#954), and within-batch\n // duplicate paths (#955). methodName is \"addRoute\" to match validation-plugin\n // (which reports \"addRoute\" for replace batches too), so the no-plugin error\n // is identical to the with-plugin one.\n assertNoInternalNamesInBatch(batch, \"addRoute\");\n assertNonEmptyNamesInBatch(batch, \"addRoute\");\n assertNoDottedNamesInBatch(batch, \"addRoute\");\n assertNoDuplicateNamesInBatch(batch, \"\", \"addRoute\");\n assertNoDuplicatePathsInBatch(batch, \"\", \"addRoute\");\n\n // Build the whole new set BEFORE touching the store.\n const artifacts = buildReplaceArtifacts(\n batch,\n store.rootPath,\n store.matcherOptions,\n ctx.logger,\n );\n\n // Config-time channel check BEFORE clearDefinitionGuards mutates. Inside\n // `adoptRouteArtifacts`, one line before the swap, is early enough for `add`\n // and too late here: a refused batch would leave the tree intact and the old\n // definition guards ERASED, so a guarded route becomes freely activatable.\n // Same fail-open shape #1046 and #1193 hoisted their own throws out of.\n assertRouteDefaultChannelsFor(\n artifacts.matcher,\n artifacts.config,\n \"addRoute\",\n );\n\n // Pre-flight the #961 handler-limit BEFORE clearDefinitionGuards mutates, so a\n // limit-exceeding batch aborts with BOTH the tree and the definition guards\n // intact (#1046). replace clears definition guards first, so the projection\n // runs against the surviving external guards (clearsDefinition = true).\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.preflightHandlerLimit(\n artifacts.pendingCanActivate.keys(),\n artifacts.pendingCanDeactivate.keys(),\n true,\n );\n\n // Pre-compile the new batch's guard factories in the PREPARE phase — BEFORE\n // clearDefinitionGuards — so a compile-throwing factory (or a non-function)\n // aborts here with BOTH the tree AND the old definition guards intact (#1193,\n // mirror of the #1046 handler-limit hoist). adoptRouteArtifacts then installs\n // these pre-compiled functions without re-running the factories.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const compiledGuards = compileArtifactGuards(artifacts, store.depsStore!);\n\n // Clear definition lifecycle handlers (preserve external guards), then swap.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.clearDefinitionGuards();\n adoptRouteArtifacts(store, artifacts, compiledGuards);\n\n // ⚑ The window OPENS here (#1758 / #1759) — before the emit, because the\n // emit is its FIRST actor. Everything until the commit runs application code\n // the router cannot see into (a `subscribeChanges` handler, the route's\n // `decodeParams` invoked by the revalidating `matchPath`, the new route's\n // activation guards) while the tree has already been swapped and the committed\n // state has NOT been revalidated. Unguarded, route-CRUD from there commits a\n // bag the route cannot build (#1758) and a navigation leaves the state on a\n // route the batch drops (#1759).\n //\n // ⚠ ONE window, not a permission per door: what a piece of application code\n // may do follows from the state the router is in, not from which door it\n // arrived through.\n //\n // ⚠ The `finally` is load-bearing exactly as the preparing flag's is: left\n // raised, every later CRUD call and every later navigation on this router is\n // refused. And it cannot be skipped on the happy path — `decodeParams` is not\n // isolated, so a throw from it leaves through `replace()` itself.\n store.revalidating = true;\n\n try {\n // TREE_CHANGED fires here (O-5): the new tree is committed but state is not\n // yet revalidated, so the handler sees the new tree and the still-old state.\n onCommitted?.();\n\n // Revalidate the active state against the new tree AND notify subscribers\n // (#950). A structural replace can change or drop the currently-active state;\n // emitting TRANSITION_SUCCESS makes router.subscribe / useSyncExternalStore\n // adapters re-render instead of rendering the pre-replace state. (This is the\n // one structural mutation that emits a transition event — clear() stays a\n // silent reset; the asymmetry is deliberate, see #950.)\n if (currentState !== undefined) {\n const revalidated = ctx.matchPath(currentState.path, ctx.getOptions());\n\n if (revalidated) {\n if (revalidated.name === currentState.name) {\n // Survivor — the URL still maps to the route the user was already on.\n // Keep it WITHOUT re-running guards: the user legitimately reached this\n // route via a real navigation, and `replace()` is not a navigation they\n // performed, so re-checking guards here would evict them on a stateful\n // or async guard (parity with `update()`, which never revalidates the\n // active state). Preserve the prior transition meta and emit so\n // subscribers see the revalidated state (#1201). Carry the prior\n // `context` (#1236): the route name and path are unchanged, so the\n // plugin data written into `state.context.<namespace>` (SSR data, rsc,\n // navigation, …) is still valid — the matchPath-rebuilt state would\n // otherwise wipe it, and revalidation re-runs neither the loader nor the\n // start interceptor to bring it back.\n // ⚑ This object is never published: the commit door copies what it is\n // handed and commits its own (#1792), so nothing here freezes — and\n // nothing outside `commitRevalidated` ever holds it. The `context` line\n // still does its job: its CONTENTS are what survive the revalidation,\n // which is what #1236 is about. Its identity does not, so a plugin that\n // cached the context object itself across a `replace()` writes into an\n // object the router no longer holds.\n const nextState: State = {\n ...revalidated,\n context: currentState.context,\n transition: currentState.transition,\n };\n\n commitRevalidated(ctx, nextState, currentState);\n } else {\n // Route-identity change — the URL is now owned by a DIFFERENT route (an\n // ownership reshuffle, or a newly-added `forwardTo` that teleports the\n // state). Consult the new route's ACTIVATION guards (#1201): commit on\n // pass; on a block — or an async guard that cannot be evaluated\n // synchronously (mirrors `canNavigateTo`) — route to not-found rather\n // than silently activating a guarded route.\n //\n // ⚠ ACTIVATION ONLY — the deactivate list is deliberately empty (#1652).\n // `canNavigateTo` collapses both halves into ONE boolean, and this arm\n // routes every `false` to not-found. That reading is right for \"cannot\n // ENTER\" and exactly backwards for \"do not LEAVE\": the guard exists to\n // keep the user where they are, and eviction to a 404 is the worst\n // outcome available. Measured against the routing arm: with no\n // `canDeactivate` the user reaches the new route, WITH a refusing one\n // they land on UNKNOWN_ROUTE — a guard honoured that way makes the\n // result worse than no guard at all.\n //\n // Not asking is what the other two revalidation arms already do, each\n // with its reason written beside it (survivor: the user was legitimately\n // here, #1201; vanished: the route whose guard would speak is gone). So\n // this removes the odd one out rather than adding a mechanism: a tree\n // swap is an operation the APPLICATION performed, not a departure the\n // user chose, and `canDeactivate` has no \"stay\" branch to offer here —\n // after the swap the old route may not exist, or may live at another\n // path, so a retained state would point at a route that no longer owns\n // its URL. Checking for unsaved work before swapping the tree is the\n // caller's job; the router does not promise to veto its own API.\n //\n // Side effect worth naming: the refusal does NOT short-circuit ahead\n // of the activation guards, so \"may the user be on the new route\" is\n // always asked.\n const { toDeactivate, toActivate, intersection } = getTransitionPath(\n revalidated,\n currentState,\n ctx.getMetaForState,\n );\n\n const allowed =\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.canNavigateTo(\n [],\n toActivate,\n revalidated,\n currentState,\n );\n\n if (allowed) {\n // ⚑ BUILT rather than copied (#2007). The survivor arm above copies\n // the prior meta because the route did NOT change; here it did, and a\n // copy names a route the new tree no longer holds — `segments` said\n // the departed route had just activated. The vanished arm below\n // already builds one (`commitNotFound`), so this removes the odd one\n // out of three rather than adding a mechanism, and it spends nothing:\n // the three fields it needs were computed one statement up and two of\n // them dropped on the floor.\n //\n // ⚠ `replace` is DERIVED here, not inherited. The vanished arm\n // reaches `systemCommit` with `FROZEN_REPLACE_OPTS`, so a\n // revalidation commit is a replace by construction; the copied value\n // agreed only because `start()` happened to set it, which is the\n // right answer for the wrong reason.\n // Nothing here freezes, for the reason the survivor arm above\n // states: the commit door copies what it is handed and seals its own\n // (#1792 / #2140). Sealing `getTransitionPath`'s answer would reach\n // further than uselessly — those arrays are CACHED and handed to\n // other callers.\n const transition: TransitionMeta = {\n phase: \"activating\",\n from: currentState.name,\n reason: \"success\",\n replace: true,\n segments: {\n deactivated: toDeactivate,\n activated: toActivate,\n intersection,\n },\n };\n\n const nextState: State = { ...revalidated, transition };\n\n commitRevalidated(ctx, nextState, currentState);\n } else {\n // The REVALIDATION door, and its reason CHANGED with #1652: it is no\n // longer \"the question was already put above\" (it no longer is) but\n // the same rule as the consult itself — revalidation does not consult\n // deactivate guards. Calling the departure door here would let the\n // fallback throw CANNOT_DEACTIVATE out of a route-CRUD call, which is\n // the shape #1643 deliberately kept for user-initiated departures\n // only. Since #1981 that is a different FUNCTION rather than a flag,\n // so the two cannot be confused at the call site.\n ctx.revalidateToNotFound(currentState.path);\n }\n }\n } else {\n // The active route no longer exists in the new tree — surface it as\n // not-found (commits UNKNOWN_ROUTE + emits TRANSITION_SUCCESS) so the\n // change is observable, rather than silently clearing the state.\n //\n // No deactivation consult (#1643): the route whose guard would be asked\n // is the one that just stopped existing. There is nothing to refuse on\n // behalf of, and a guard closure over a removed route is not a contract\n // this can honour.\n ctx.revalidateToNotFound(currentState.path);\n }\n }\n } finally {\n store.revalidating = false;\n }\n}\n\n/**\n * Removes a route and all its children.\n *\n * @returns the removed subtree when `wantSubtree` is set, an empty array when it\n * is not, and `undefined` when the name is not a route. Three outcomes, so a\n * caller distinguishing \"removed\" from \"not found\" must test for `undefined` —\n * an empty array is a successful removal.\n */\nfunction removeRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n name: string,\n wantSubtree: boolean,\n): readonly Route<Dependencies>[] | undefined {\n // `store.definitions` is a fresh tree-derived snapshot — mutate it locally,\n // then commit the mutated table as the new tree.\n const definitions = store.definitions;\n const removedNames = spliceSubtree(definitions, name);\n\n if (removedNames === undefined) {\n return undefined;\n }\n\n // Between the splice and the two commits below: the store still holds the old\n // tree AND the config the payload reads, which is the only moment either is\n // available together with the set (#1757). Empty — not `undefined` — when\n // nobody is listening, so `undefined` means one thing only: not a route.\n const subtree = wantSubtree\n ? collectSubtree(store, removedNames)\n : EMPTY_SUBTREE;\n\n clearRouteConfigurations(\n removedNames,\n store.config,\n store.routeCustomFields,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n store.lifecycleNamespace!,\n );\n\n commitTreeChanges(store, definitions);\n\n return subtree;\n}\n\n/**\n * Gets a route by name with all its configuration.\n */\nfunction getRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n name: string,\n): Route<Dependencies> | undefined {\n const segments = store.matcher.getSegmentsByName(name);\n\n if (!segments) {\n return undefined;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- segments is non-empty (checked above)\n const targetNode = segments.at(-1)! as RouteTree;\n const definition = nodeToDefinition(targetNode);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const factories = store.lifecycleNamespace!.getFactories();\n\n return enrichRoute(definition, name, store.config, factories);\n}\n\n// ============================================================================\n// API factory\n// ============================================================================\n\n// Cache the assembled RoutesApi per router — mirrors getPluginApi()/getNavigator():\n// avoids re-allocating the 9-closure bag on each call (adapters/plugins poll it\n// from constructors) and gives spy/stub helpers a stable object identity. Closures\n// capture `ctx`/`store`, both stable for the router's lifetime, so caching is safe.\n// Single cast site: the value is stored as `unknown` (RoutesApi is invariant in\n// Dependencies, so one typed map can't hold every instantiation) and cast on read.\nconst cache = new WeakMap<object, unknown>();\n\nexport function getRoutesApi<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(router: Router<Dependencies>): RoutesApi<Dependencies> {\n const cached = cache.get(router);\n\n if (cached) {\n return cached as RoutesApi<Dependencies>;\n }\n\n const ctx = getInternals(router);\n\n const store = ctx.routeGetStore();\n\n // Single cast site: the channel is typed with default Dependencies on\n // RouterInternals (RouterEventMap is non-generic), but payloads are built\n // with this api's Dependencies. The runtime shape is identical.\n const emitChange = (event: TreeChangedEvent<Dependencies>): void => {\n ctx.treeChanged.emit(event as TreeChangedEvent);\n };\n\n const api: RoutesApi<Dependencies> = {\n add: (routes, options) => {\n throwIfDisposed(ctx.isDisposed);\n throwIfReentrantTreeMutation(\n ctx.treeChanged.isEmitting,\n () => store.revalidating,\n );\n\n const routeArray = Array.isArray(routes) ? routes : [routes];\n const parentName = options?.parent;\n\n // ⚑ Judged and snapshotted in ONE walk, so guards, validators and\n // registration all decide from this one object (#1899 / #1911 / #2139). A\n // `Proxy` reports an ordinary data descriptor while answering differently\n // per read, so the accessor ban does not reach that shape and only a\n // single read can — of each definition AND of the array holding them.\n // Per-key counts are the `registration · route.*` rows' business.\n const batch = guardRouteStructure(routeArray);\n\n guardRouteCallbacks(batch, ctx.validator);\n\n if (parentName !== undefined) {\n ctx.validator?.routes.validateParentOption(parentName, store.tree);\n }\n\n ctx.validator?.routes.throwIfInternalRouteInArray(batch, \"addRoute\");\n ctx.validator?.routes.validateAddRouteArgs(batch);\n ctx.validator?.routes.validateRoutes(batch, store, parentName);\n\n addRoutes(store, batch, parentName, ctx.logger);\n\n // Built from the post-commit store (O-1), only when someone is listening.\n //\n // ⚑ From the SNAPSHOT, never from `routeArray` (#1931): the caller's array\n // is application code's, and everything between the snapshot and this line\n // — guard factories compiled inside `adoptRouteArtifacts` among them — can\n // change what a second read of it answers.\n if (ctx.treeChanged.listenerCount() > 0) {\n const added = collectAddedRoutes(batch, parentName, store);\n\n emitChange(\n parentName === undefined\n ? { op: \"add\", added }\n : { op: \"add\", added, parent: parentName },\n );\n }\n },\n\n remove: (name) => {\n throwIfDisposed(ctx.isDisposed);\n throwIfReentrantTreeMutation(\n ctx.treeChanged.isEmitting,\n () => store.revalidating,\n );\n\n ctx.validator?.routes.validateRemoveRouteArgs(name);\n ctx.validator?.routes.throwIfInternalRoute(name, \"removeRoute\");\n // Always-on parity backstop (#1047 / #238): a reserved \"@@\" name is\n // internal and cannot be removed, with or without the validation-plugin.\n assertNoInternalRouteName(name, \"removeRoute\");\n\n const canRemove = validateRemoveRoute(\n name,\n ctx.getStateName(),\n ctx.logger,\n );\n\n if (!canRemove) {\n return;\n }\n\n const wantSubtree = ctx.treeChanged.listenerCount() > 0;\n // The payload is built INSIDE, between the splice and the commits — the\n // one moment the removed-name set, the old tree and the config coexist\n // (#1757). `undefined` means the name is not a route at all.\n const removedSubtree = removeRoute(store, name, wantSubtree);\n\n if (removedSubtree === undefined) {\n ctx.logger.warn(\n \"router.removeRoute\",\n `Route \"${name}\" not found. No changes made.`,\n );\n\n return;\n }\n\n // Below the existence check on purpose (#1756): everything this reports\n // is a consequence of a route leaving the tree, so it has no subject\n // until one has.\n if (ctx.isTransitioning()) {\n warnRemovalDuringNavigation(name, ctx.logger);\n }\n\n if (wantSubtree) {\n emitChange({ op: \"remove\", name, removedSubtree });\n }\n },\n\n update: (name, updates) => {\n throwIfDisposed(ctx.isDisposed);\n throwIfReentrantTreeMutation(\n ctx.treeChanged.isEmitting,\n () => store.revalidating,\n );\n\n ctx.validator?.routes.validateUpdateRouteBasicArgs(name, updates);\n ctx.validator?.routes.throwIfInternalRoute(name, \"updateRoute\");\n // Always-on parity backstop (#1047 / #238): a reserved \"@@\" name is\n // internal and cannot be updated, with or without the validation-plugin.\n assertNoInternalRouteName(name, \"updateRoute\");\n\n ctx.validator?.routes.validateUpdateRoutePropertyTypes(name, updates);\n\n ctx.validator?.routes.validateUpdateRoute(name, updates, store);\n\n // #1205: bare-core existence backstop as a TRUE no-op — NOT a throw\n // (validation is opt-in). Without it, update() of a route that does not\n // exist seeds config.defaultParams + compiles/registers the guard\n // (commitRouteUpdate below) and emits a lying TREE_CHANGED \"update\" event\n // for a route get()/has() cannot see; a future add() of that name then\n // inherits the phantom config + a blocking guard. Skip the commit and the\n // emit entirely when the route is absent. (With the validation-plugin, validateUpdateRoute above\n // already threw a ReferenceError, so this is only reached in bare core.)\n if (!store.matcher.hasRoute(name)) {\n return;\n }\n\n // Below the existence check, for the same reason the removal report is\n // (#1756): it names an action, and `update(\"nope\")` performs none. From\n // above it logged an ERROR announcing an update that never happened, and\n // — unlike `remove()`, which at least contradicts itself out loud one\n // line later — said nothing afterwards.\n if (ctx.isTransitioning()) {\n ctx.logger.error(\n \"router.updateRoute\",\n `Updating route \"${name}\" while navigation is in progress. This may cause unexpected behavior.`,\n );\n }\n\n // Field-patch commit core (NO_TREE_REBUILD) — co-located in routesStore.ts\n // beside the add/replace (adoptRouteArtifacts) / remove (commitTreeChanges)\n // / clear (resetStore) cores. Returns the structural fields for the\n // conditional emit below (each user getter read once inside).\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const lifecycle = store.lifecycleNamespace!;\n const structural = commitRouteUpdate(store, lifecycle, name, updates);\n\n // Conditional emit: structural fields only. A guard-only or empty patch\n // produces no event (O-7 + empty-patch rule).\n if (ctx.treeChanged.listenerCount() > 0) {\n const patch = buildStructuralPatch<Dependencies>(structural);\n\n if (objectKeys(patch).length > 0) {\n emitChange({ op: \"update\", name, patch });\n }\n }\n },\n\n clear: () => {\n throwIfDisposed(ctx.isDisposed);\n throwIfReentrantTreeMutation(\n ctx.treeChanged.isEmitting,\n () => store.revalidating,\n );\n\n // `clear()` is a TEARDOWN primitive, and it may only run while there is\n // nothing to tear down out from under anyone (#1612). Dropping the\n // committed state to `undefined` silently leaves every `router.subscribe`\n // consumer rendering a route the router has discarded, and the router\n // `isActive() === true` with no state — a shape that\n // otherwise exists only *during* `start()`, which is why an always-on\n // guard misreads it (path-less `navigateToNotFound()` answers\n // ROUTER_NOT_STARTED on a started router).\n //\n // Announcing the reset instead was considered and rejected: it would make\n // CRUD emit a transition event as a RULE (`replace()` is deliberately \"the\n // one structural mutation that emits\" one) and it would not remove the\n // shape. Refusing removes the crossing entirely — `clear()` stops writing\n // into state it does not own. `replace(routes)` is the spelling for a\n // running router: atomic, notifies subscribers, and preserves external\n // guards. Design note `fsm-as-state-owner-2026-07-31.md` §11.A1, option\n // (c), owner decision 2026-08-01.\n //\n // A THROW rather than the `logger.error` + no-op that `validateClearRoutes`\n // uses below, because the two preconditions are different classes: \"a\n // navigation is in flight\" clears by itself (wait and retry works), while\n // this one never does — the caller has to change the code. That is the\n // same line `REENTRANT_TREE_MUTATION` sits on (#1032).\n if (ctx.getStateName() !== undefined) {\n throw freezeThrownError(\n new RouterError(errorCodes.ROUTER_NOT_STOPPED, {\n message:\n \"[router.clear] Cannot clear routes while a state is committed. \" +\n \"Use replace(routes) to swap the tree on a running router, or stop() first.\",\n }),\n );\n }\n\n const canClear = validateClearRoutes(ctx.isTransitioning(), ctx.logger);\n\n /* v8 ignore next 3 -- @preserve: race condition guard, mirrors Router.clearRoutes() same-path guard tested via validateClearRoutes unit tests */\n if (!canClear) {\n return;\n }\n\n // Snapshot the routes BEFORE the reset empties them. Emitted whenever\n // there is a listener — even for an empty clear (O-4).\n const removed =\n ctx.treeChanged.listenerCount() > 0\n ? freeze([...collectFlatRoutes(store, () => true).values()])\n : undefined;\n\n resetStore(store);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.clearAll();\n\n if (removed !== undefined) {\n emitChange({ op: \"clear\", removed });\n }\n },\n\n has: (name) => {\n ctx.validator?.routes.validateRouteName(name, \"hasRoute\");\n\n return store.matcher.hasRoute(name);\n },\n\n get: (name) => {\n ctx.validator?.routes.validateRouteName(name, \"getRoute\");\n\n return getRoute(store, name);\n },\n\n replace: (routes) => {\n throwIfDisposed(ctx.isDisposed);\n throwIfReentrantTreeMutation(\n ctx.treeChanged.isEmitting,\n () => store.revalidating,\n );\n\n const routeArray = Array.isArray(routes) ? routes : [routes];\n\n const canReplace = validateClearRoutes(ctx.isTransitioning(), ctx.logger);\n\n if (!canReplace) {\n return;\n }\n\n // Judged and snapshotted in one walk — same rule as `add`\n // (#1899 / #1911 / #2139).\n const batch = guardRouteStructure(routeArray);\n\n guardRouteCallbacks(batch, ctx.validator);\n\n ctx.validator?.routes.throwIfInternalRouteInArray(batch, \"replaceRoutes\");\n ctx.validator?.routes.validateAddRouteArgs(batch);\n ctx.validator?.routes.validateRoutes(batch, store);\n\n const currentState = router.getState();\n\n // The flat removed/added diff is O(N) — compute it only when someone is\n // listening (Decision 3.B). Snapshot the old tree BEFORE the swap.\n const before =\n ctx.treeChanged.listenerCount() > 0\n ? collectFlatRoutes(store, () => true)\n : undefined;\n\n replaceRoutes(\n store,\n batch,\n ctx,\n currentState,\n before === undefined\n ? undefined\n : () => {\n const after = collectFlatRoutes(store, () => true);\n const { removed, added } = diffFlatRoutes(before, after);\n\n emitChange({ op: \"replace\", removed, added });\n },\n );\n },\n\n subscribeChanges: (handler) => ctx.treeChanged.subscribe(handler),\n };\n\n // ⚑ FROZEN, and the freeze is what the cache above makes necessary (#1805).\n // One object per router is handed to EVERY consumer — first-party plugins and\n // application code alike — so a single `api.add = …` rewires the surface for\n // all of them, silently and with nothing for the next consumer to notice.\n // The consumer count is deliberately not restated: it grows with the tier\n // while the hazard is the sharing, which one consumer is enough to have. `getNavigator` next door has always frozen its cached bag and calls\n // itself \"a frozen read-only subset\"; the two uncached factories\n // (`getLifecycleApi`, `getDependenciesApi`) need nothing, because a write to a\n // per-call object cannot reach a second consumer.\n //\n // ⚠ Measured free: core, all six adapters and every plugin that reaches this\n // door stay green under the freeze. Its twin `getPluginApi` is NOT — tests\n // across the tier spy on that shared surface to inject errors, so freezing it\n // reds them — which is why this half ships alone. Re-run the freeze on\n // `getPluginApi` to see the count rather than trusting one written here.\n const frozen = freeze(api);\n\n cache.set(router, frozen);\n\n return frozen;\n}\n","import { throwIfDisposed } from \"./helpers\";\nimport { ingestDependencies } from \"../guards\";\nimport { dropUnsafeKey } from \"../helpers\";\nimport { getInternals } from \"../internals\";\nimport { storeDependency } from \"../namespaces\";\n\nimport type { DependenciesApi } from \"./types\";\nimport type { DependenciesStore } from \"../namespaces\";\nimport type { DefaultDependencies, Router } from \"../types\";\nimport type { RouterValidator } from \"../types/RouterValidator\";\n\n/** Captured like the deciding seven, but this one BUILDS the guarantee (#2072). */\nconst objectCreate = Object.create;\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst hasOwn = Object.hasOwn;\n\n/**\n * One `ToPropertyKey`, at the door (#1843).\n *\n * A dependency name is used as a PROPERTY KEY, so every bare\n * `store[name]` / `Object.hasOwn(store, name)` is a `toString` call into\n * application code — and `set` made three of them, `remove` two. Nothing pinned\n * the result between them, so the key that was CHECKED was not the key that was\n * written or deleted. Measured through the public API with a name answering\n * `\"alpha\"` then `\"beta\"`: `remove` reported nothing (the check found `alpha`)\n * and deleted `beta`; `set` took the overwrite arm on `alpha` — skipping the\n * new-key limit check — and then added `beta`.\n *\n * The rule is core's own, from `src/engine/CLAUDE.md`: *\"a guard that admits by\n * a computed key must hand the KEY downstream, never the value it computed it\n * from\"*. This file already applies it one level up — `setDependency` captures\n * `store.dependencies` ONCE (#1859) because a validator warning can reach\n * application code that replaces it. The reference was pinned; the key was not.\n *\n * ⚠ A SYMBOL is handed back untouched, and that exemption loses nothing: a\n * symbol already IS a property key, so `ToPropertyKey` is the identity on it and\n * no application code runs — the entire hazard is the non-symbol case. Coercing\n * it instead was written first and measured: `set` and `remove` moved to\n * `\"Symbol(svc)\"` while `has` and `get` kept asking the symbol, so `set(S, 1)`\n * followed by `has(S)` answered **false**. That is a NEW divergence, in a family\n * that is merely incomplete today: a symbol key works through all four doors and\n * comes back from `getAll` (a spread carries own enumerable symbols), but\n * `Object.keys` does not see it, so `validateDependencyCount` never counts one\n * against the limit. Read-count is this fix's subject; symbol support is not,\n * and `set` narrows to `& string` anyway.\n *\n * ⚠ The parameter is `unknown` deliberately. Written as `String(name: string)`,\n * BOTH `@typescript-eslint/no-unnecessary-type-conversion` and\n * `unicorn/no-useless-coercion` reason from the declared type and autofix the\n * coercion away — measured on #1882, where `lint --fix` deleted the same fix\n * twice. `unknown` makes the conversion genuine, so no rule has anything to\n * remove and no disable comment is needed.\n */\nconst asKey = (name: unknown): string | symbol =>\n typeof name === \"symbol\" ? name : String(name);\n\n// =============================================================================\n// Module-private CRUD functions\n// =============================================================================\n\nfunction setDependency(\n store: DependenciesStore,\n dependencyName: string,\n dependencyValue: unknown,\n validator?: RouterValidator | null,\n): void {\n // undefined = \"don't set\" (feature for conditional setting)\n if (dependencyValue === undefined) {\n return;\n }\n\n // ⚑ Captured ONCE, and this is the whole re-entrancy defence (#1859).\n //\n // `validateDependencyCount` and `warnOverwrite` both reach `logger.warn`, i.e.\n // the application's own `LoggerConfig.callback` — public `RouterOptions` API,\n // called synchronously between the reads above and the write below. That\n // callback can `dispose()` or `reset()` the router, and both clear this channel\n // by REPLACING `store.dependencies`. Re-reading the slot afterwards wrote into\n // the fresh post-teardown object, which every clear path then refused to touch\n // (they all `throwIfDisposed` first) while `getAll()` kept answering with it.\n //\n // Holding the reference makes that unreachable rather than merely guarded: the\n // write lands in the object the teardown discarded, so it is garbage by\n // construction. A per-call disposal probe cannot do this — there is a user-code\n // window on either side of it, and it would have to sit in both.\n // ⚠ `PropertyKey`, not `string`: a symbol dependency name reaches here\n // untouched (see `asKey`), and `Record<string, unknown>` would force a\n // `name as string` cast that is simply false about symbols.\n const target = store.dependencies as Record<PropertyKey, unknown>;\n // ⚑ Pinned for the same reason `target` is, one line up (#1843). The four\n // uses below asked the name FOUR times, and each was a `ToPropertyKey` call\n // into application code.\n const key = asKey(dependencyName);\n const isNewKey = !hasOwn(target, key);\n\n if (isNewKey) {\n // Only check limit when adding new keys (overwrites don't increase count)\n validator?.dependencies.validateDependencyCount(store, \"setDependency\");\n } else {\n const oldValue = target[key];\n const isChanging = oldValue !== dependencyValue;\n // Special case for NaN idempotency (NaN !== NaN is always true)\n const bothAreNaN = Number.isNaN(oldValue) && Number.isNaN(dependencyValue);\n\n if (isChanging && !bothAreNaN) {\n // `String` again, and only here: the validator wants a name for a\n // MESSAGE, and this is the opt-in diagnostic path.\n validator?.dependencies.warnOverwrite(String(key), \"setDependency\");\n }\n }\n\n target[key] = dependencyValue;\n}\n\nfunction setMultipleDependencies(\n store: DependenciesStore,\n deps: Record<string, unknown>,\n validator?: RouterValidator | null,\n): void {\n const overwrittenKeys: string[] = [];\n\n // ⚑ Captured ONCE — see `setDependency` above for the mechanism. This loop has\n // TWO user-code windows per key, not one: reading `deps[key]` runs an accessor\n // if the caller supplied one, and `validateDependencyCount` reaches\n // `logger.warn` → the application's `LoggerConfig.callback`. A disposal probe\n // between them closes the first and leaves the second open — measured, the\n // callback route reproduced the leak in full on a bag with no accessors at all.\n // Holding the reference closes both, and closes `reset()` (which replaces the\n // same slot) with them.\n const target = store.dependencies as Record<string, unknown>;\n\n // ⚑ The same walk as the constructor door — and \"the same\" is now literal\n // rather than approximate: both go through `ingestDependencies` (#1860), the\n // ONE door a caller-supplied bag passes, which judges and copies in a SINGLE\n // pass (#1861). Before this, `setAll` reached no structural check at all: a\n // string, an array, a class instance, a `Map` and an own enumerable getter all\n // went straight in, the last of them RUNNING the caller's code.\n ingestDependencies(deps, (key, value) => {\n if (hasOwn(target, key)) {\n overwrittenKeys.push(key);\n } else {\n validator?.dependencies.validateDependencyCount(store, \"setDependencies\");\n }\n\n storeDependency(target, key, value);\n });\n\n if (overwrittenKeys.length > 0) {\n validator?.dependencies.warnBatchOverwrite(\n overwrittenKeys,\n \"setDependencies\",\n );\n }\n}\n\n// =============================================================================\n// Public API factory\n// =============================================================================\n\nexport function getDependenciesApi<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(router: Router<Dependencies>): DependenciesApi<Dependencies> {\n const ctx = getInternals(router);\n\n return {\n get: (name) => {\n ctx.validator?.dependencies.validateDependencyName(name, \"getDependency\");\n\n const store = ctx.dependenciesGetStore();\n const value = (store.dependencies as Record<string, unknown>)[\n name as string\n ];\n\n ctx.validator?.dependencies.validateDependencyExists(\n name as string,\n store,\n );\n\n return value as Dependencies[typeof name];\n },\n getAll: () => {\n // ⚑ A spread, then `dropUnsafeKey` (#1823 / #1957). The store is\n // `Object.create(null)`, so an own `\"__proto__\"` is an ORDINARY key there\n // — but a spread re-defines it on a normal object, and the result is then\n // a prototype-swap primitive for any consumer that merges it with\n // `Object.assign` or a `for…in` copy. `cloneRouter` spreads and is safe;\n // a consumer merging is not, and this is published API.\n //\n // ⚠ Asymmetric with `get(\"__proto__\")`, deliberately: the single read\n // hands back a value, this door hands back a CONTAINER that someone will\n // merge. Same asymmetry the route-config records already carry.\n const source = ctx.dependenciesGetStore().dependencies as Record<\n string,\n unknown\n >;\n // ⚑ SPREAD, not a write loop, and the difference is the whole point of\n // this function. A spread DEFINES each key; `all[key] = value` SETS it,\n // and a `[[Set]]` of an ordinary dependency name that `Object.prototype`\n // happens to carry as an accessor throws instead of storing (#1852).\n // Measured: a write loop here makes `getAll()` throw on such a name,\n // which is what turns an already-immune site into a member of the class.\n //\n // The one key a spread cannot be trusted with: `source` is built with\n // `Object.create(null)`, so `\"__proto__\"` can sit there as an ORDINARY own\n // key. Spreading defines it as an own key here too — harmless in `all`\n // itself, but it makes the returned object a prototype-swap primitive for\n // any consumer that merges it with `Object.assign` or a `for…in` copy.\n //\n // ⚠ The delete is UNCONDITIONAL, and `dropUnsafeKey`'s docblock carries\n // the measurement that says why (a `hasOwn` gate in front of the one line\n // that neutralises the hazard is an intrinsic read an application can\n // re-point). This site is where that reasoning was FOUND (#1823); it now\n // serves three doors (#1957) and lives with the primitive.\n const all: Record<string, unknown> = dropUnsafeKey({ ...source });\n\n return all as ReturnType<DependenciesApi<Dependencies>[\"getAll\"]>;\n },\n set: (name, value) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.dependencies.validateSetDependencyArgs(\n name,\n value,\n \"setDependency\",\n );\n\n setDependency(ctx.dependenciesGetStore(), name, value, ctx.validator);\n\n // ⚑ Again, AFTER the write (#1859). The guard above answers \"was the\n // router alive when you called?\"; this one answers \"was it still alive\n // when the write landed?\". Between them sit `validateDependencyCount` and\n // `warnOverwrite`, which reach `logger.callback` — the application's own\n // code. The write itself is already harmless (the target is captured, so a\n // teardown mid-call sends it to the discarded object); this is what stops\n // the call REPORTING success for a store that no longer exists.\n throwIfDisposed(ctx.isDisposed);\n },\n setAll: (deps) => {\n throwIfDisposed(ctx.isDisposed);\n\n const store = ctx.dependenciesGetStore();\n\n ctx.validator?.dependencies.validateDependenciesObject(\n deps,\n \"setDependencies\",\n );\n\n setMultipleDependencies(\n store,\n deps as Record<string, unknown>,\n ctx.validator,\n );\n\n // ⚑ See `set` above — same reason, same placement.\n throwIfDisposed(ctx.isDisposed);\n },\n remove: (name) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.dependencies.validateDependencyName(\n name,\n \"removeDependency\",\n );\n\n const store = ctx.dependenciesGetStore();\n // ⚑ One coercion (#1843) — the check and the delete asked separately, so\n // a name answering `\"alpha\"` then `\"beta\"` reported nothing and deleted\n // `beta`.\n const key = asKey(name);\n\n if (!hasOwn(store.dependencies, key)) {\n ctx.validator?.dependencies.warnRemoveNonExistent(String(key));\n }\n\n delete (store.dependencies as Record<PropertyKey, unknown>)[key];\n },\n reset: () => {\n throwIfDisposed(ctx.isDisposed);\n const store = ctx.dependenciesGetStore();\n\n store.dependencies = objectCreate(null) as Partial<Dependencies>;\n },\n has: (name) => {\n ctx.validator?.dependencies.validateDependencyName(name, \"hasDependency\");\n\n return hasOwn(ctx.dependenciesGetStore().dependencies, name);\n },\n };\n}\n","import { throwIfDisposed } from \"./helpers\";\nimport { assertRouteNameIsString } from \"../guards\";\nimport { getInternals } from \"../internals\";\n\nimport type { LifecycleApi } from \"./types\";\nimport type { DefaultDependencies, Router } from \"../types\";\n\nexport function getLifecycleApi<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(router: Router<Dependencies>): LifecycleApi<Dependencies> {\n const ctx = getInternals(router);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const lifecycleNamespace = ctx.routeGetStore().lifecycleNamespace!;\n\n return {\n addActivateGuard(name, handler) {\n throwIfDisposed(ctx.isDisposed);\n\n assertRouteNameIsString(name, \"addActivateGuard\");\n ctx.validator?.routes.validateRouteName(name, \"addActivateGuard\");\n ctx.validator?.lifecycle.validateHandler(handler, \"addActivateGuard\");\n\n // Handler-limit enforcement lives at the namespace registration choke point\n // (RouteLifecycleNamespace.#registerHandler) so all paths are bounded\n // uniformly — see #961.\n // `false` — the EXTERNAL lane, named rather than defaulted (#1977).\n lifecycleNamespace.addCanActivate(name, handler, false);\n },\n\n addDeactivateGuard(name, handler) {\n throwIfDisposed(ctx.isDisposed);\n\n assertRouteNameIsString(name, \"addDeactivateGuard\");\n ctx.validator?.routes.validateRouteName(name, \"addDeactivateGuard\");\n ctx.validator?.lifecycle.validateHandler(handler, \"addDeactivateGuard\");\n\n lifecycleNamespace.addCanDeactivate(name, handler, false);\n },\n\n removeActivateGuard(name) {\n throwIfDisposed(ctx.isDisposed);\n\n assertRouteNameIsString(name, \"removeActivateGuard\");\n ctx.validator?.routes.validateRouteName(name, \"removeActivateGuard\");\n\n // Inverse of addActivateGuard (external): clears only the external guard;\n // a route-config (definition) canActivate survives (#1171).\n lifecycleNamespace.clearCanActivate(name, \"external\");\n },\n\n removeDeactivateGuard(name) {\n throwIfDisposed(ctx.isDisposed);\n\n assertRouteNameIsString(name, \"removeDeactivateGuard\");\n ctx.validator?.routes.validateRouteName(name, \"removeDeactivateGuard\");\n\n // Inverse of addDeactivateGuard (external): clears only the external guard;\n // a route-config (definition) canDeactivate survives (#1171).\n lifecycleNamespace.clearCanDeactivate(name, \"external\");\n },\n };\n}\n","import { errorCodes } from \"../constants\";\nimport { routeTreeToDefinitions } from \"../engine\";\nimport { ingestDependencies } from \"../guards\";\nimport { getInternals } from \"../internals\";\nimport { getLifecycleApi } from \"./getLifecycleApi\";\nimport { assignConfigEntries } from \"../namespaces/RoutesNamespace/helpers\";\nimport { adoptForwardState } from \"../namespaces/RoutesNamespace/routesStore\";\nimport { Router as RouterClass } from \"../Router\";\nimport { RouterError, freezeThrownError } from \"../RouterError\";\nimport { putField } from \"../utils/ingest\";\n\nimport type {\n DefaultDependencies,\n LoggerConfig,\n Router,\n Route,\n} from \"../types\";\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst objectEntries = Object.entries;\nconst hasOwn = Object.hasOwn;\n\n/**\n * Per-clone overrides beyond dependencies.\n */\nexport interface CloneOptions {\n /**\n * Per-clone logger config override, merged **over** the base router's resolved\n * logger config. Primary use: per-request `traceId` in SSR — a fresh\n * `callback` closed over the request id, while `level` inherits the base.\n * Omitted keys inherit the base (level / callback / callbackIgnoresLevel).\n *\n * Override is by **config**, not a logger instance: `RouterLogger` is\n * core-internal (only its `{ log, warn, error }` interface is public), so\n * nothing outside core constructs one — configuration is the whole surface.\n */\n logger?: Partial<LoggerConfig>;\n}\n\n/**\n * Build an independent router instance that shares the route tree, options,\n * lifecycle guards, and plugin factories of `router`. The primary use case\n * is **SSR multi-tenancy** — one base router per process, one clone per\n * request.\n *\n * @param router - Source router (must not be disposed).\n * @param dependencies - Optional per-clone overrides merged on top of the\n * base router's dependencies. Always **fresh per call** in the documented\n * SSR pattern: pass per-request state here, never store it in the base.\n *\n * @remarks\n *\n * **Dependency merge — shallow by design.** `base.dependencies` are spread\n * into the clone via `{ ...sourceDeps, ...dependencies }`. Top-level keys\n * are new objects, but **values are shared by reference**: a `Map`, `Set`,\n * class instance, function, or nested plain object stored in\n * `base.dependencies` is the **same instance** in every clone. Mutations\n * in one clone are visible in the base and in every sibling clone.\n *\n * This is intentional. `structuredClone` of dep values is **not** applied\n * because it would:\n * - strip class prototypes (`new DbClient()` → plain object, methods lost)\n * - reject functions and symbols (`DataCloneError`)\n * - fragment singleton pools (one connection pool per request — pool\n * semantics destroyed)\n * - reject circular references\n *\n * **SSR rule of thumb.** Place values in `base.dependencies` according to\n * their lifecycle:\n *\n * - **Singletons / shared services** → `base.dependencies`. Examples: DB\n * client, connection pool, logger, config, feature-flag client. Process-\n * wide pooling depends on sharing these by reference.\n * - **Per-request state** → the `dependencies` override parameter (or\n * `createRequestScope`'s `deps` argument). Examples: `currentUser`,\n * `traceId`, `sessionId`, `abortSignal`. The override is applied last,\n * so it wins over base keys; pass a fresh object per call.\n *\n * Cross-request data leaks are **only possible** when per-request mutable\n * state is incorrectly placed in `base.dependencies`. The override slot is\n * the safe channel.\n *\n * @example\n * ```typescript\n * // Server boot — singletons only\n * const base = createRouter(routes, options, {\n * db: new DbClient(dbUrl),\n * logger,\n * });\n *\n * // Per request — fresh override per call\n * const clone = cloneRouter(base, {\n * currentUser,\n * traceId,\n * });\n * // clone.deps.db === base.deps.db ✓ shared pool (intentional)\n * // clone.deps.currentUser ✓ unique per request\n * ```\n *\n * @see createRequestScope — `@real-router/ssr-utils` SSR helper that\n * wraps this function and injects `abortSignal` automatically.\n */\nexport function cloneRouter<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n router: Router<Dependencies>,\n dependencies?: Dependencies,\n opts?: CloneOptions,\n): RouterClass<Dependencies> {\n const ctx = getInternals(router);\n\n if (ctx.isDisposed()) {\n throw freezeThrownError(new RouterError(errorCodes.ROUTER_DISPOSED));\n }\n\n ctx.validator?.dependencies.validateCloneArgs(dependencies);\n\n // Get source store directly\n const sourceStore = ctx.routeGetStore();\n const routes = routeTreeToDefinitions(sourceStore.tree);\n const routeConfig = sourceStore.config;\n const resolvedForwardMap = sourceStore.resolvedForwardMap;\n const routeCustomFields = sourceStore.routeCustomFields;\n\n const {\n options,\n dependencies: sourceDeps,\n pluginFactories,\n loggerConfig,\n limits: sourceLimits,\n limitKeys,\n } = ctx.getCloneState();\n // Origin-aware factory snapshot — definition guards are re-registered with\n // `isFromDefinition=true` on the clone so `replace()` can still strip them\n // via `clearDefinitionGuards()`. External guards take the public lifecycle\n // API path so they survive `replace()` symmetric with the base.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const sourceLifecycleNamespace = sourceStore.lifecycleNamespace!;\n const { definition: definitionFactories, external: externalFactories } =\n sourceLifecycleNamespace.getFactoriesByOrigin();\n\n // ⚑ The caller's bag goes through the SAME door the constructor uses (#1860),\n // and the merge IS that door's walk (#1861). A spread FIRST would flatten\n // whatever the caller passed into a fresh literal before\n // `guardDependencyShape` ever saw it — leaving the check structurally vacuous with respect to the\n // argument it is meant to judge, so the door accepts a string, an array, a\n // class instance, a `Map` and an own getter that RUNS.\n // `cloneRouter` is the per-request SSR path (`angular/providersFactory`\n // forwards an application-authored `RequestDepsFactory` result straight here),\n // so `Map` silently becoming `{}` lost every dependency with no error at all.\n const mergedDeps = { ...sourceDeps } as Record<string, unknown>;\n\n if (dependencies !== undefined) {\n ingestDependencies(dependencies, (key, value) => {\n // ⚠ `putField`, not `mergedDeps[key] = value`. This destination is an\n // ORDINARY literal (`{ ...sourceDeps }`), so it carries `Object.prototype`\n // and a bare `[[Set]]` of `\"__proto__\"` would dispatch into the inherited\n // setter and swap the prototype instead of storing (#1852). The spread it\n // replaced was immune for free — a spread DEFINES — so writing the loop\n // without this would have been a regression, not a refactor.\n putField(mergedDeps, key, value);\n });\n }\n\n // The clone builds its OWN logger (isolation, #724) but INHERITS the base's\n // resolved config — frozen options don't carry `logger`, so without this the\n // clone would fall back to the default logger and lose the base's\n // callback/level. A per-request\n // `opts.logger` override (e.g. a traceId-bound callback) merges on top.\n //\n // ⚑ ONE read of the caller's slot (#1930). `CloneOptions` is unvalidated and\n // application-owned, so every read of it is a call into application code — and\n // a truthiness test plus a spread are two, deciding the branch on one answer\n // and shipping another. The local is what makes the clone keep the\n // per-request callback rather than falling back to the base's process-wide\n // sink, which is the isolation `createRequestScope` exists for.\n const loggerOverride = opts?.logger;\n const clonedLoggerConfig: Partial<LoggerConfig> = loggerOverride\n ? { ...loggerConfig, ...loggerOverride }\n : loggerConfig;\n\n // ⚑ The base's KEY, not its raw option (#1877). `urlParamsEncoding` is\n // supported input — a `toString`-backed value is legal — and building the\n // clone from `options` coerced it a SECOND time, so a drifting value gave the\n // clone a different encoding, and decoder, from its base. `createRequestScope`\n // clones per request, which is exactly where that lands.\n //\n // ⚑ And the base's resolved QUERY STRATEGIES, by the same rule as every other\n // slot in this literal: `urlParamsEncoding` (#1877), `limits` (#1880 / #1961),\n // `logger` (#1930), `queryParams` (#2171). A clone inherits what the base\n // resolved and re-reads nothing the caller still holds.\n //\n // ⚠ Re-validation refuses what is INVALID and cannot notice a value that is\n // merely DIFFERENT (#2032), so inheriting is what keeps a base and its clone\n // on one strategy. What it gives up is the clone re-running a refusal on a bag\n // its base already validated.\n //\n // ⚠ Consequence, the one #1877 names one paragraph up: the clone's\n // `getOptions().queryParams` carries the four declared names, so an unknown\n // key — a mis-spelled `arrayFromat` that `@real-router/validation-plugin`\n // reports on the BASE — is not in the clone's copy to report. Only the clone\n // honours the documented shape.\n //\n // ⚠ The clone's own `getOptions()` therefore reports the coerced key where the\n // base still reports the caller's value. That is a deliberate consequence, not\n // an oversight: only the clone honours the documented four-literal type.\n const newRouter = new RouterClass<Dependencies>(\n routes as Route<Dependencies>[],\n {\n ...options,\n // Conditional spread for `exactOptionalPropertyTypes`: the slot may be\n // absent, but never present-and-`undefined`.\n ...(sourceStore.matcherOptions?.queryParams !== undefined && {\n queryParams: sourceStore.matcherOptions.queryParams,\n }),\n logger: clonedLoggerConfig,\n // The base's RESOLVED limits, not its raw `options.limits` (#1880).\n // `createLimits`' spread re-invokes an accessor on the caller's bag, so\n // rebuilding from `options` gave a drifting getter a second answer and\n // the clone a different cap. These are already numbers.\n //\n // ⚠ Only the keys the base actually PASSED, resolved — not the whole\n // resolved bag. Substituting wholesale materialises the four unset\n // DEFAULTS into the clone's reported options, and that is not cosmetic:\n // `warnListeners: 1000` beside a SMALL `maxListeners` is a pair\n // `validation-plugin` refuses at install, so `cloneRouter` throws and\n // `createRequestScope` fails on EVERY request. Measured: 1 of 6 partial\n // bags, not all of them — `validators/options.ts` throws only when\n // `warnListeners > maxListeners > 0`, so it needs the base to have passed\n // a `maxListeners` under the 1000 default, and it needs the plugin\n // installed at all.\n //\n // ⚠ `limitKeys` — the base's SNAPSHOT of the names the caller passed, not\n // `Object.keys(options.limits)` (#1961). `options.limits` is the caller's\n // own object and stays mutable — core freezes only the level it owns\n // (#1832). Reading it here meant a `delete` after\n // construction left the base capped and every LATER clone uncapped —\n // measured, 30 subscriptions accepted against a base that throws at 2.\n // Under SSR that is a per-request clone with a different listener cap from\n // its base, i.e. #1880's own shape reopened through the key set.\n //\n // The snapshot is taken with `Object.keys`, mirroring `createLimits`'\n // SPREAD, and that pairing is load-bearing in both directions: the spread\n // skips a NON-ENUMERABLE own key, so the base does not see one, and a\n // snapshot taken with `Object.hasOwn` over the five known names would make\n // the clone stricter than its base. Walking `sourceLimits` and filtering\n // by presence in the caller's bag has the same defect from the other side\n // — it finds the materialised default and ships it. Both are pinned.\n //\n // Neither read invokes the bag's accessors, so #1880 still holds for the\n // VALUES: they all come from `sourceLimits`, which the base resolved once.\n //\n // ⚠ `Object.hasOwn`, NOT `key in sourceLimits`. `in` walks the prototype\n // chain, so it answers true for `\"__proto__\"`, `\"constructor\"`,\n // `\"toString\"` and every other `Object.prototype` member — a caller bag\n // built by `JSON.parse` can carry those as OWN keys, and they would have\n // been copied into the clone's reported options with an `Object.prototype`\n // MEMBER as the value: `Object.prototype` itself for `\"__proto__\"`, the\n // `Object` constructor for `\"constructor\"`, the native method for\n // `\"toString\"`. Measured: all three pass `in`, none passes `hasOwn`.\n //\n // ⚠ The nullish case is decided at the SNAPSHOT now, not here.\n // `limitKeys` is `undefined` exactly when the caller passed no bag, so\n // this skips the substitution for `undefined` AND for `null` — which is\n // the correct answer for `null` rather than a mere guard: `...options`\n // still carries it, and the clone resolves it to the same defaults the\n // base did. The gate this replaced had to say `!= null` rather than\n // `!== undefined` because `Object.keys(null)` THROWS, and a `!== undefined`\n // form made the clone, and only the clone, die on a config the base had\n // accepted: silent at construction, fatal per request inside\n // `createRequestScope`.\n ...(limitKeys !== undefined && {\n limits: Object.fromEntries(\n limitKeys\n .filter((key) => hasOwn(sourceLimits, key))\n .map((key) => [\n key,\n sourceLimits[key as keyof typeof sourceLimits],\n ]),\n ),\n }),\n // ⚠ The spread form is not stylistic — the three obvious alternatives were\n // each tried and each loses. `matcherOptions` and its `urlParamsEncoding`\n // are `| undefined` in the TYPE only (`createRoutesStore` has one caller,\n // always fed `deriveMatcherOptions(...)`, whose `snapshotEncodingKey`\n // returns a string on every path), so: a plain read fails TS2379 under\n // `exactOptionalPropertyTypes`; `?? \"default\"` adds an arm no test can\n // reach and drops branch coverage to 99.95%, which the 100% gate refuses;\n // a non-null assertion still yields `| undefined` and fails TS2379 too.\n // The spread's false arm is unreachable but costs no branch — v8 scores\n // `&&` as an operand pair, both hit.\n //\n // ⚠ It reads the field TWICE — guard, then value — which is structurally\n // the TOCTOU shape #1811 is about. It is safe HERE and only here: the\n // source is core-owned frozen plain data in a sealed slot, not a\n // caller-owned bag. Do not copy the pattern to a caller-owned source.\n ...(sourceStore.matcherOptions?.urlParamsEncoding !== undefined && {\n urlParamsEncoding: sourceStore.matcherOptions.urlParamsEncoding,\n }),\n },\n mergedDeps as Dependencies,\n );\n\n const newCtx = getInternals(newRouter);\n const newStore = newCtx.routeGetStore();\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const newLifecycleNamespace = newStore.lifecycleNamespace!;\n\n // Copy the source config + store-level maps BEFORE re-registering guards\n // (#1331 review): the definition-guard factories re-executed below must\n // observe the fully-built clone (encoders/decoders/defaultParams/custom\n // fields), mirroring the constructor where flushPendingGuards runs after the\n // store is complete. EVERY RouteConfig sub-map goes through a single\n // enumeration so a newly added config field is carried over automatically\n // (#965) — and deliberately UNCOUNTED here: the enumeration carries a new\n // field on its own, so a number beside it would go stale while the code stayed\n // correct (#1548). resolvedForwardMap and routeCustomFields are store-level\n // (not part of RouteConfig) and stay explicit.\n assignConfigEntries(newStore.config, routeConfig);\n // ⚑ Through `adoptForwardState`, not a bare assign (#1800). The forward state\n // is TWO halves — the map and the derived `hasAnyForward` flag — and writing\n // only the first is not enough. The clone's store is built from\n // `routeTreeToDefinitions(sourceStore.tree)`, bare `{name, path, children}`\n // with no `forwardTo`, so it starts at `hasAnyForward = false`; installing the\n // config behind that flag leaves `isActiveRoute` answering `false` for every\n // forwarding route on every clone — and `createRequestScope` clones per\n // request, so that is every SSR render.\n //\n // `Object.assign` stays INSIDE the call: it merges into the clone's own map\n // and returns it, so the clone keeps its own object. Passing\n // `resolvedForwardMap` directly would install the SOURCE's map by reference\n // and alias the two stores.\n adoptForwardState(\n newStore,\n Object.assign(newStore.resolvedForwardMap, resolvedForwardMap),\n );\n Object.assign(newStore.routeCustomFields, routeCustomFields);\n\n // #1175: carry the source rootPath. It lives in the store (not options/config),\n // and neither routeTreeToDefinitions nor getCloneState include it — so a clone\n // of a base configured with `setRootPath(\"/app\")` would otherwise build/match\n // under \"\" and 404 every request of a sub-path SSR deployment. setRootPath\n // rebuilds the tree in place with the just-copied config; the rebuild is only\n // paid when a rootPath is actually set, and it runs before the definition-guard\n // factories below so they observe the fully-built clone (rootPath included).\n if (sourceStore.rootPath !== \"\") {\n newCtx.setRootPath(sourceStore.rootPath);\n }\n\n const [definitionDeactivate, definitionActivate] = definitionFactories;\n const [externalDeactivate, externalActivate] = externalFactories;\n\n for (const [name, handler] of objectEntries(definitionDeactivate)) {\n newLifecycleNamespace.addCanDeactivate(name, handler, true);\n }\n\n for (const [name, handler] of objectEntries(definitionActivate)) {\n newLifecycleNamespace.addCanActivate(name, handler, true);\n }\n\n const lifecycle = getLifecycleApi(newRouter);\n\n for (const [name, handler] of objectEntries(externalDeactivate)) {\n lifecycle.addDeactivateGuard(name, handler);\n }\n\n for (const [name, handler] of objectEntries(externalActivate)) {\n lifecycle.addActivateGuard(name, handler);\n }\n\n // Plugin replay runs last and skips factories that a (contract-violating)\n // definition-guard factory already registered on the clone during the\n // re-compilation above — without the filter every clone would double-apply\n // such a plugin: once via the factory, once via this replay (#1331 review).\n const alreadyRegistered = new Set(newCtx.getCloneState().pluginFactories);\n const pluginsToReplay = pluginFactories.filter(\n (factory) => !alreadyRegistered.has(factory),\n );\n\n // Stryker disable next-line EqualityOperator: equivalent — `>= 0` is always true, but `usePlugin(...[])` with an empty spread is a no-op, so entering the block on an empty list behaves identically to skipping it. (ConditionalExpression stays live: `→false` skips a real plugin list and is killable.)\n if (pluginsToReplay.length > 0) {\n newRouter.usePlugin(...pluginsToReplay);\n }\n\n return newRouter;\n}\n"],"mappings":"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"}