@real-router/core 0.90.1 → 0.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Router-D_dwJLCj.js +2 -0
- package/dist/cjs/Router-D_dwJLCj.js.map +1 -0
- package/dist/cjs/api/getRoutesApi.d.ts.map +1 -1
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/esm/Router-C62cnt7M.mjs +2 -0
- package/dist/esm/Router-C62cnt7M.mjs.map +1 -0
- package/dist/esm/api/getRoutesApi.d.mts.map +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/cjs/Router-S2GVIigJ.js +0 -2
- package/dist/cjs/Router-S2GVIigJ.js.map +0 -1
- package/dist/esm/Router-vZz1iGx7.mjs +0 -2
- package/dist/esm/Router-vZz1iGx7.mjs.map +0 -1
package/dist/cjs/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","names":["RouterError","errorCodes","cache","getInternals","canonicalize","materialize","buildURL","RouterError","errorCodes","buildAddArtifacts","buildReplaceArtifacts","compileArtifactGuards","getTransitionPath","spliceSubtree","nodeToDefinition","getInternals","commitRouteUpdate","RouterError","errorCodes","getInternals","getInternals","getInternals","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 } from \"../RouterError\";\n\nexport function throwIfDisposed(isDisposed: () => boolean): void {\n if (isDisposed()) {\n throw 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). It used to live only\n * here — visible to whoever maintains core, not to the application developer\n * who caught the throw — and the same omission on the navigation ban produced\n * two docs issues before anyone reached the code.\n */\nexport function throwIfReentrantTreeMutation(isEmitting: () => boolean): void {\n if (isEmitting()) {\n throw 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","import type { Matcher } from \"../../engine\";\nimport type { RouterLogger } from \"../../types\";\n\n/**\n * Validates removeRoute constraints.\n * Returns false if removal should be blocked (route is active).\n * Logs warnings for edge cases.\n *\n * @param name - Route name to remove\n * @param currentStateName - Current active route name (or undefined)\n * @param isNavigating - Whether navigation is in progress\n * @param logger - Per-router logger instance (from `getInternals(router).logger`)\n * @param matcher - The live matcher — asked whether the committed route is\n * INSIDE the subtree being removed\n * @returns true if removal can proceed, false if blocked\n */\nexport function validateRemoveRoute(\n name: string,\n currentStateName: string | undefined,\n isNavigating: boolean,\n logger: RouterLogger,\n matcher: Matcher,\n): boolean {\n if (currentStateName) {\n const isExactMatch = currentStateName === name;\n // ⚑ Asked of the TREE, not of the name string (#1757). The segment chain of\n // the committed route contains `name` exactly when `name` is one of its\n // ANCESTORS — which is the question the refusal means. `startsWith(name +\n // \".\")` answered a wider one: core accepts a dotted LEAF, so a standalone\n // `x.y` declared beside `x` matched the prefix and made `remove(\"x\")` refuse\n // with `it is currently active (current: \"x.y\")` — a sentence that is false\n // about a route nothing was removing. It also fired for a `name` that is not\n // a route AT ALL, and since it runs above the existence check the caller was\n // told \"currently active\" instead of \"not found\"; a chain lookup returns\n // `undefined` there and the not-found report survives.\n //\n // ⚠ The `isExactMatch ||` in front is a SHORT-CIRCUIT, not a second rule:\n // a route's own chain ends with itself, so the lookup answers `true` for the\n // exact case too and dropping the term leaves the whole tier green (checked\n // — it is an equivalent mutant). It stays because it is the cheap answer to\n // the common case, and because it is the ONE reading that does not depend on\n // the committed route still being in the matcher.\n const isInRemovedSubtree =\n isExactMatch ||\n (matcher\n .getSegmentsByName(currentStateName)\n ?.some((segment) => segment.fullName === name) ??\n false);\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 if (isNavigating) {\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\" holds for a WELL-FORMED tree and is measured to fail under flat\n // dotted names (#1194, closed but still reproducing). 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\": measured,\n // the rejection already carries the removed route's name — on the async arcs\n // as `ROUTE_NOT_FOUND { routeName }` directly, on the sync arc threaded\n // through `asCancellation` as `error.reason`, and `onTransitionError` fires\n // with the route name on both. What was missing is only that the WARNING\n // stopped at \"may cause unexpected behavior\" and never said a navigation\n // could die of it, so a caller reading the log had no reason to go looking\n // at `error.reason` in the first place.\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. The previous draft named the arc\n // split and then appended the hook, which reads as \"the hook carries these\n // codes\" — true on the async arc, false on the sync one.\n //\n // ⚠ It names BOTH failure codes, and that is a correction rather than\n // thoroughness. The first draft promised `TRANSITION_CANCELLED` — true 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 does NOT promise the removal happened: this guard runs ABOVE the\n // existence check, so `remove(\"nope\")` mid-navigation reaches here too and\n // is followed by \"not found. No changes made.\" The first draft said \"the\n // removal is applied\" and contradicted the very next log line.\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 return true;\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 { buildURL, canonicalize, materialize } from \"../pipeline\";\nimport { throwIfDisposed, throwIfReentrantTreeMutation } from \"./helpers\";\nimport { errorCodes } from \"../constants\";\nimport { getInternals, throwOnMisChanneledKey } from \"../internals\";\nimport { validateSetRootPath } from \"../namespaces/RoutesNamespace/routeGuards\";\nimport { RouterError } from \"../RouterError\";\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// 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), and gives spy/stub helpers\n// a stable object identity to attach to (e.g. spying on\n// `getPluginApi(router).navigateToState` to inject errors in popstate\n// recovery tests).\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 ctx.validator?.state.validateMakeStateArgs(name, params, path);\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. The former `meta` argument\n // (per-segment param-source map) was dropped when the `stateMetaStore`\n // WeakMap was removed — ownership is now read from the live matcher by\n // `state.name`, so a caller-supplied meta had no effect and is gone.\n return ctx.makeState(name, params, 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 ctx.validator?.routes.validateStateBuilderArgs(\n routeName,\n routeParams,\n \"forwardState\",\n );\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 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 ctx.validator?.routes.validateStateBuilderArgs(\n name,\n params,\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, params, 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. `buildURL` is usable here for\n // the same reason it is in `canNavigateTo`: this point is not the one the\n // port prints through, so there is no recursion (contrast `buildPath`).\n return materialize(canonical, {\n path: buildURL(canonical, ctx.port()),\n });\n },\n getOptions: ctx.getOptions,\n getTree: ctx.getTree,\n addInterceptor: (method, fn) => {\n throwIfDisposed(ctx.isDisposed);\n ctx.validator?.plugins.validateAddInterceptorArgs(method, fn);\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 = Object.keys(extensions);\n\n for (const key of keys) {\n if (key in router) {\n throw new RouterError(errorCodes.PLUGIN_CONFLICT, {\n message: `Cannot extend router: property \"${key}\" already exists`,\n });\n }\n }\n\n for (const key of keys) {\n (router as Record<string, unknown>)[key] = extensions[key];\n }\n\n const extensionRecord = { keys };\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 new RouterError(errorCodes.CONTEXT_NAMESPACE_ALREADY_CLAIMED, {\n message: `Cannot claim context namespace: \"${namespace}\" is already claimed by another plugin`,\n });\n }\n\n ctx.contextClaimRecords.add(namespace);\n\n return {\n write(state: State, value: unknown) {\n // `state.context[namespace] = value` dispatches into the inherited\n // Object.prototype.__proto__ setter for the literal key \"__proto__\",\n // swapping the prototype instead of creating an own entry — the data\n // then vanishes from Object.keys / serializeRouterState (#1191 N3).\n // Mirror search-params' assignParam: defineProperty writes a genuine\n // own property; normal names keep the plain-assignment fast path.\n if (namespace === \"__proto__\") {\n Object.defineProperty(state.context, namespace, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n } else {\n state.context[namespace] = value;\n }\n },\n release() {\n ctx.contextClaimRecords.delete(namespace);\n },\n } satisfies ContextNamespaceClaim;\n },\n };\n\n cache.set(router, api);\n\n return api;\n}\n","import { nodeToDefinition } from \"../engine\";\nimport { throwIfDisposed, throwIfReentrantTreeMutation } from \"./helpers\";\nimport { errorCodes } from \"../constants\";\nimport { 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} from \"../namespaces/RoutesNamespace/routeGuards\";\nimport {\n adoptRouteArtifacts,\n assertAddable,\n assertNoDuplicateNamesInBatch,\n assertNoDuplicatePathsInBatch,\n assertNoInternalNamesInBatch,\n assertNoInternalRouteName,\n buildAddArtifacts,\n buildReplaceArtifacts,\n commitRouteUpdate,\n commitTreeChanges,\n compileArtifactGuards,\n resetStore,\n} from \"../namespaces/RoutesNamespace/routesStore\";\nimport { RouterError } from \"../RouterError\";\nimport { getTransitionPath } from \"../transitionPath\";\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// ============================================================================\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). It used to be `name === routeName || name.startsWith(routeName +\n // \".\")`, which is a strictly wider question: a flat dotted leaf `x.y`\n // declared BESIDE `x` is a standalone node the splice never touches, and the\n // prefix claimed it. The route stayed in the tree with its config and its\n // guards unregistered — a FAIL-OPEN, since a blocking `canActivate` simply\n // disappeared and the route became 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 Object.keys(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 Object.keys(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 route.children = routeDef.children.map((child) =>\n enrichRoute(child, `${routeName}.${child.name}`, config, factories),\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 Object.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 Object.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 Object.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: Object.freeze(removed), added: Object.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 (О-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 Object.freeze(patch);\n}\n\n// ============================================================================\n// CRUD operations\n// ============================================================================\n\n/**\n * Adds one or more routes to the router.\n * Input already validated by facade.\n */\nfunction addRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n routes: 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, routes, parentName);\n\n const artifacts = buildAddArtifacts(store, routes, 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 that shipped without the question the\n // other two ask (#1753): `completeTransition` and `navigateToState` both\n // refuse a state whose route no longer exists, and this path refused nothing\n // — `systemCommit` below asks whether the MACHINE may commit, which is a\n // different question and deliberately so. ⚠ Not \"is the router alive\": that\n // was #1186's predicate, and #1644 replaced it with `canSend(SYSTEM_COMMIT)`,\n // an edge declared on `READY` alone — so it refuses a perfectly LIVE router\n // that is merely starting or mid-transition (`routerFSM.ts:686-688`).\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)` — what\n // #1753 shipped, and what 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 that distinction is a measured\n // correction, not a refinement. The first version asked\n // `match(nextState.path) === nextState.name` — which silently assumes the\n // 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: both landed `UNKNOWN_ROUTE` where they used to 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 // ⚠ The equality form WAS measured before being trusted — instrumented over\n // the whole tier, 515 firings and 512 agreements — and the measurement was\n // still not enough, which is the lesson worth keeping: the tier's shapes are\n // not the reachable shapes. Every case it covered had a path rebuilt from the\n // resolved route, so the whole class where `state.path` is the SOURCE url was\n // invisible to it. 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.navigateToNotFound(fromState.path, { skipDeactivation: true });\n\n return;\n }\n\n // Through the machine now (`SYSTEM_COMMIT`), so the write and the announce\n // are one table fact rather than two statements here. `replace()` USED TO run\n // application code between its entry `throwIfDisposed()` and this line —\n // `clearDefinitionGuards()` recompiled the compiled slot by invoking a\n // surviving EXTERNAL factory (#1192) — and a `dispose()` / `stop()` from\n // there let the swap finish and commit on a dead router with zero events\n // (#1627). #1649 removed THAT at the root: `clearDefinitionGuards`'s\n // re-derivation READS the survivor's stored compiled form instead of\n // re-running its factory.\n //\n // ⚠ It does not follow — and this comment used to claim it did — that\n // `replace()` \"no longer executes anything of the caller's between the two\n // points\". It executes at LEAST four other things, all 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. That sentence is what made\n // the missing existence check above look unnecessary (#1753) — a fix's scope\n // written up as the window's scope. ⚑ Written \"at least four\" on purpose: the\n // first draft of THIS correction said \"two other things, both above\" and\n // reproduced the very failure it names — an enumeration passed off as\n // exhaustive.\n //\n // ⚑ The liveness this line relies on is KEPT anyway, and deliberately: it now\n // covers a router disposed or stopped by some OTHER means between the entry\n // check and here, which `replace()` can no longer cause but cannot rule out.\n // The interim re-check that once did the job is gone — a dead router simply\n // has no edge to take, and `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 routes: 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(routes, \"addRoute\");\n assertNoDuplicateNamesInBatch(routes, \"\", \"addRoute\");\n assertNoDuplicatePathsInBatch(routes, \"\", \"addRoute\");\n\n // Build the whole new set BEFORE touching the store.\n const artifacts = buildReplaceArtifacts(\n routes,\n store.rootPath,\n store.matcherOptions,\n ctx.logger,\n );\n\n // Config-time channel check BEFORE clearDefinitionGuards mutates. It used to\n // live inside `adoptRouteArtifacts`, one line before the swap — early enough\n // for `add`, too late here: a refused batch left the tree intact and the old\n // definition guards ERASED, so a guarded route became freely activatable. Same\n // fail-open shape #1046 and #1193 hoisted their own throws out of, now for the\n // third throwing step this path grew.\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 (О-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 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 before the fix: with no `canDeactivate`\n // the user landed on the new route, WITH a refusing one on\n // UNKNOWN_ROUTE — a guard that cannot be honoured was making the result\n // 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, and an improvement: the refusal used to short-circuit\n // before the activation guards ran at all, so \"may the user be on the\n // new route\" went unasked. Now it is 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 // `skipDeactivation` stays, and its reason CHANGED with #1652: it is\n // no 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. Dropping it would let the fallback throw\n // CANNOT_DEACTIVATE out of a route-CRUD call, which is the shape\n // #1643 deliberately kept for user-initiated departures only.\n ctx.navigateToNotFound(currentState.path, { skipDeactivation: true });\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.navigateToNotFound(currentState.path, { skipDeactivation: true });\n }\n }\n}\n\n/**\n * Removes a route and all its children.\n *\n * @returns true if removed, false if not found\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 guardRouteStructure(routeArray, ctx.validator);\n\n if (parentName !== undefined) {\n ctx.validator?.routes.validateParentOption(parentName, store.tree);\n }\n\n ctx.validator?.routes.throwIfInternalRouteInArray(routeArray, \"addRoute\");\n ctx.validator?.routes.validateAddRouteArgs(routeArray);\n ctx.validator?.routes.validateRoutes(routeArray, store, parentName);\n\n addRoutes(store, routeArray, parentName, ctx.logger);\n\n // Built from the post-commit store (О-1), only when someone is listening.\n if (ctx.treeChanged.listenerCount() > 0) {\n const added = collectAddedRoutes(routeArray, 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.isTransitioning(),\n ctx.logger,\n store.matcher,\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 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 /* v8 ignore next 6 -- @preserve: race condition guard, mirrors Router.updateRoute() same-path guard tested via Router.ts unit tests */\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 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). update() of a route that does not exist used to\n // seed config.defaultParams + compile/register the guard (commitRouteUpdate\n // below) and emit a lying TREE_CHANGED \"update\" event for a route get()/\n // has() cannot see; a future add() of that name then inherited the phantom\n // config + a blocking guard. Skip the commit and the emit entirely when the\n // 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 // 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 (О-7 + empty-patch rule).\n if (ctx.treeChanged.listenerCount() > 0) {\n const patch = buildStructuralPatch<Dependencies>(structural);\n\n if (Object.keys(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). It used to drop the\n // committed state to `undefined` silently: every `router.subscribe`\n // consumer kept rendering a route the router had already discarded, and\n // the router was left `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 // (в), 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 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 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 (О-4).\n const removed =\n ctx.treeChanged.listenerCount() > 0\n ? Object.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 ctx.clearState();\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 guardRouteStructure(routeArray, ctx.validator);\n\n ctx.validator?.routes.throwIfInternalRouteInArray(\n routeArray,\n \"replaceRoutes\",\n );\n ctx.validator?.routes.validateAddRouteArgs(routeArray);\n ctx.validator?.routes.validateRoutes(routeArray, 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 (Решение 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 routeArray,\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 cache.set(router, api);\n\n return api;\n}\n","import { throwIfDisposed } from \"./helpers\";\nimport { getInternals } from \"../internals\";\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// =============================================================================\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 const isNewKey = !Object.hasOwn(store.dependencies, dependencyName);\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 = (store.dependencies as Record<string, unknown>)[\n dependencyName\n ];\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 validator?.dependencies.warnOverwrite(dependencyName, \"setDependency\");\n }\n }\n\n (store.dependencies as Record<string, unknown>)[dependencyName] =\n 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 for (const key in deps) {\n if (deps[key] === undefined) {\n continue;\n }\n\n if (Object.hasOwn(store.dependencies, key)) {\n overwrittenKeys.push(key);\n } else {\n validator?.dependencies.validateDependencyCount(store, \"setDependencies\");\n }\n\n (store.dependencies as Record<string, unknown>)[key] = deps[key];\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: () => ({ ...ctx.dependenciesGetStore().dependencies }),\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 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 remove: (name) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.dependencies.validateDependencyName(\n name,\n \"removeDependency\",\n );\n\n const store = ctx.dependenciesGetStore();\n\n if (!Object.hasOwn(store.dependencies, name)) {\n ctx.validator?.dependencies.warnRemoveNonExistent(name);\n }\n\n delete (store.dependencies as Record<string, unknown>)[name as string];\n },\n reset: () => {\n throwIfDisposed(ctx.isDisposed);\n const store = ctx.dependenciesGetStore();\n\n store.dependencies = Object.create(null) as Partial<Dependencies>;\n },\n has: (name) => {\n ctx.validator?.dependencies.validateDependencyName(name, \"hasDependency\");\n\n return Object.hasOwn(ctx.dependenciesGetStore().dependencies, name);\n },\n };\n}\n","import { throwIfDisposed } from \"./helpers\";\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 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 lifecycleNamespace.addCanActivate(name, handler);\n },\n\n addDeactivateGuard(name, handler) {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.routes.validateRouteName(name, \"addDeactivateGuard\");\n ctx.validator?.lifecycle.validateHandler(handler, \"addDeactivateGuard\");\n\n lifecycleNamespace.addCanDeactivate(name, handler);\n },\n\n removeActivateGuard(name) {\n throwIfDisposed(ctx.isDisposed);\n\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 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 { getInternals } from \"../internals\";\nimport { getLifecycleApi } from \"./getLifecycleApi\";\nimport { assignConfigEntries } from \"../namespaces/RoutesNamespace/helpers\";\nimport { Router as RouterClass } from \"../Router\";\nimport { RouterError } from \"../RouterError\";\n\nimport type {\n DefaultDependencies,\n LoggerConfig,\n Router,\n Route,\n} from \"../types\";\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 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 } = 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 const mergedDeps = {\n ...sourceDeps,\n ...dependencies,\n } as Dependencies;\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 (an M1 regression the singleton used to mask). A per-request\n // `opts.logger` override (e.g. a traceId-bound callback) merges on top.\n const clonedLoggerConfig: Partial<LoggerConfig> = opts?.logger\n ? { ...loggerConfig, ...opts.logger }\n : loggerConfig;\n\n const newRouter = new RouterClass<Dependencies>(\n routes as Route<Dependencies>[],\n { ...options, logger: clonedLoggerConfig },\n mergedDeps,\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) — deliberately uncounted here: this sentence said \"five\" until\n // defaultSearch made six (#1548), while the enumeration had already been\n // carrying it. resolvedForwardMap and routeCustomFields are store-level (not\n // part of RouteConfig) and stay explicit.\n assignConfigEntries(newStore.config, routeConfig);\n Object.assign(newStore.resolvedForwardMap, resolvedForwardMap);\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 Object.entries(definitionDeactivate)) {\n newLifecycleNamespace.addCanDeactivate(name, handler, true);\n }\n\n for (const [name, handler] of Object.entries(definitionActivate)) {\n newLifecycleNamespace.addCanActivate(name, handler, true);\n }\n\n const lifecycle = getLifecycleApi(newRouter);\n\n for (const [name, handler] of Object.entries(externalDeactivate)) {\n lifecycle.addDeactivateGuard(name, handler);\n }\n\n for (const [name, handler] of Object.entries(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":["RouterError","errorCodes","cache","getInternals","canonicalize","materialize","buildURL","RouterError","errorCodes","buildAddArtifacts","buildReplaceArtifacts","compileArtifactGuards","getTransitionPath","spliceSubtree","nodeToDefinition","getInternals","commitRouteUpdate","RouterError","errorCodes","getInternals","getInternals","getInternals","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 } from \"../RouterError\";\n\nexport function throwIfDisposed(isDisposed: () => boolean): void {\n if (isDisposed()) {\n throw 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). It used to live only\n * here — visible to whoever maintains core, not to the application developer\n * who caught the throw — and the same omission on the navigation ban produced\n * two docs issues before anyone reached the code.\n */\nexport function throwIfReentrantTreeMutation(isEmitting: () => boolean): void {\n if (isEmitting()) {\n throw 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","import type { Matcher } from \"../../engine\";\nimport type { RouterLogger } from \"../../types\";\n\n/**\n * Validates removeRoute constraints.\n * Returns false if removal should be blocked (route is active).\n * Logs warnings for edge cases.\n *\n * @param name - Route name to remove\n * @param currentStateName - Current active route name (or undefined)\n * @param isNavigating - Whether navigation is in progress\n * @param logger - Per-router logger instance (from `getInternals(router).logger`)\n * @param matcher - The live matcher — asked whether the committed route is\n * INSIDE the subtree being removed\n * @returns true if removal can proceed, false if blocked\n */\nexport function validateRemoveRoute(\n name: string,\n currentStateName: string | undefined,\n isNavigating: boolean,\n logger: RouterLogger,\n matcher: Matcher,\n): boolean {\n if (currentStateName) {\n const isExactMatch = currentStateName === name;\n // ⚑ Asked of the TREE, not of the name string (#1757). The segment chain of\n // the committed route contains `name` exactly when `name` is one of its\n // ANCESTORS — which is the question the refusal means. `startsWith(name +\n // \".\")` answered a wider one: core accepts a dotted LEAF, so a standalone\n // `x.y` declared beside `x` matched the prefix and made `remove(\"x\")` refuse\n // with `it is currently active (current: \"x.y\")` — a sentence that is false\n // about a route nothing was removing. It also fired for a `name` that is not\n // a route AT ALL, and since it runs above the existence check the caller was\n // told \"currently active\" instead of \"not found\"; a chain lookup returns\n // `undefined` there and the not-found report survives.\n //\n // ⚠ The `isExactMatch ||` in front is a SHORT-CIRCUIT, not a second rule:\n // a route's own chain ends with itself, so the lookup answers `true` for the\n // exact case too and dropping the term leaves the whole tier green (checked\n // — it is an equivalent mutant). It stays because it is the cheap answer to\n // the common case, and because it is the ONE reading that does not depend on\n // the committed route still being in the matcher.\n const isInRemovedSubtree =\n isExactMatch ||\n (matcher\n .getSegmentsByName(currentStateName)\n ?.some((segment) => segment.fullName === name) ??\n false);\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 if (isNavigating) {\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\" used to hold only for a WELL-FORMED tree, and every tree is one\n // now: bare core refuses a dotted route name at registration (#1763), so the\n // shape below is UNCONSTRUCTIBLE rather than merely rare. Kept as the record\n // of why the door 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\": measured,\n // the rejection already carries the removed route's name — on the async arcs\n // as `ROUTE_NOT_FOUND { routeName }` directly, on the sync arc threaded\n // through `asCancellation` as `error.reason`, and `onTransitionError` fires\n // with the route name on both. What was missing is only that the WARNING\n // stopped at \"may cause unexpected behavior\" and never said a navigation\n // could die of it, so a caller reading the log had no reason to go looking\n // at `error.reason` in the first place.\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. The previous draft named the arc\n // split and then appended the hook, which reads as \"the hook carries these\n // codes\" — true on the async arc, false on the sync one.\n //\n // ⚠ It names BOTH failure codes, and that is a correction rather than\n // thoroughness. The first draft promised `TRANSITION_CANCELLED` — true 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 does NOT promise the removal happened: this guard runs ABOVE the\n // existence check, so `remove(\"nope\")` mid-navigation reaches here too and\n // is followed by \"not found. No changes made.\" The first draft said \"the\n // removal is applied\" and contradicted the very next log line.\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 return true;\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 { buildURL, canonicalize, materialize } from \"../pipeline\";\nimport { throwIfDisposed, throwIfReentrantTreeMutation } from \"./helpers\";\nimport { errorCodes } from \"../constants\";\nimport { getInternals, throwOnMisChanneledKey } from \"../internals\";\nimport { validateSetRootPath } from \"../namespaces/RoutesNamespace/routeGuards\";\nimport { RouterError } from \"../RouterError\";\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// 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), and gives spy/stub helpers\n// a stable object identity to attach to (e.g. spying on\n// `getPluginApi(router).navigateToState` to inject errors in popstate\n// recovery tests).\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 ctx.validator?.state.validateMakeStateArgs(name, params, path);\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. The former `meta` argument\n // (per-segment param-source map) was dropped when the `stateMetaStore`\n // WeakMap was removed — ownership is now read from the live matcher by\n // `state.name`, so a caller-supplied meta had no effect and is gone.\n return ctx.makeState(name, params, 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 ctx.validator?.routes.validateStateBuilderArgs(\n routeName,\n routeParams,\n \"forwardState\",\n );\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 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 ctx.validator?.routes.validateStateBuilderArgs(\n name,\n params,\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, params, 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. `buildURL` is usable here for\n // the same reason it is in `canNavigateTo`: this point is not the one the\n // port prints through, so there is no recursion (contrast `buildPath`).\n return materialize(canonical, {\n path: buildURL(canonical, ctx.port()),\n });\n },\n getOptions: ctx.getOptions,\n getTree: ctx.getTree,\n addInterceptor: (method, fn) => {\n throwIfDisposed(ctx.isDisposed);\n ctx.validator?.plugins.validateAddInterceptorArgs(method, fn);\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 = Object.keys(extensions);\n\n for (const key of keys) {\n if (key in router) {\n throw new RouterError(errorCodes.PLUGIN_CONFLICT, {\n message: `Cannot extend router: property \"${key}\" already exists`,\n });\n }\n }\n\n for (const key of keys) {\n (router as Record<string, unknown>)[key] = extensions[key];\n }\n\n const extensionRecord = { keys };\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 new RouterError(errorCodes.CONTEXT_NAMESPACE_ALREADY_CLAIMED, {\n message: `Cannot claim context namespace: \"${namespace}\" is already claimed by another plugin`,\n });\n }\n\n ctx.contextClaimRecords.add(namespace);\n\n return {\n write(state: State, value: unknown) {\n // `state.context[namespace] = value` dispatches into the inherited\n // Object.prototype.__proto__ setter for the literal key \"__proto__\",\n // swapping the prototype instead of creating an own entry — the data\n // then vanishes from Object.keys / serializeRouterState (#1191 N3).\n // Mirror search-params' assignParam: defineProperty writes a genuine\n // own property; normal names keep the plain-assignment fast path.\n if (namespace === \"__proto__\") {\n Object.defineProperty(state.context, namespace, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n } else {\n state.context[namespace] = value;\n }\n },\n release() {\n ctx.contextClaimRecords.delete(namespace);\n },\n } satisfies ContextNamespaceClaim;\n },\n };\n\n cache.set(router, api);\n\n return api;\n}\n","import { nodeToDefinition } from \"../engine\";\nimport { throwIfDisposed, throwIfReentrantTreeMutation } from \"./helpers\";\nimport { errorCodes } from \"../constants\";\nimport { 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} from \"../namespaces/RoutesNamespace/routeGuards\";\nimport {\n adoptRouteArtifacts,\n assertAddable,\n assertNoDuplicateNamesInBatch,\n assertNoDuplicatePathsInBatch,\n assertNoDottedNamesInBatch,\n assertNoInternalNamesInBatch,\n assertNoInternalRouteName,\n buildAddArtifacts,\n buildReplaceArtifacts,\n commitRouteUpdate,\n commitTreeChanges,\n compileArtifactGuards,\n resetStore,\n} from \"../namespaces/RoutesNamespace/routesStore\";\nimport { RouterError } from \"../RouterError\";\nimport { getTransitionPath } from \"../transitionPath\";\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// ============================================================================\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). It used to be `name === routeName || name.startsWith(routeName +\n // \".\")`, which is a strictly wider question: a flat dotted leaf `x.y`\n // declared BESIDE `x` is a standalone node the splice never touches, and the\n // prefix claimed it. The route stayed in the tree with its config and its\n // guards unregistered — a FAIL-OPEN, since a blocking `canActivate` simply\n // disappeared and the route became 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 Object.keys(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 Object.keys(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 route.children = routeDef.children.map((child) =>\n enrichRoute(child, `${routeName}.${child.name}`, config, factories),\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 Object.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 Object.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 Object.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: Object.freeze(removed), added: Object.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 (О-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 Object.freeze(patch);\n}\n\n// ============================================================================\n// CRUD operations\n// ============================================================================\n\n/**\n * Adds one or more routes to the router.\n * Input already validated by facade.\n */\nfunction addRoutes<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n routes: 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, routes, parentName);\n\n const artifacts = buildAddArtifacts(store, routes, 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 that shipped without the question the\n // other two ask (#1753): `completeTransition` and `navigateToState` both\n // refuse a state whose route no longer exists, and this path refused nothing\n // — `systemCommit` below asks whether the MACHINE may commit, which is a\n // different question and deliberately so. ⚠ Not \"is the router alive\": that\n // was #1186's predicate, and #1644 replaced it with `canSend(SYSTEM_COMMIT)`,\n // an edge declared on `READY` alone — so it refuses a perfectly LIVE router\n // that is merely starting or mid-transition (`routerFSM.ts:686-688`).\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)` — what\n // #1753 shipped, and what 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 that distinction is a measured\n // correction, not a refinement. The first version asked\n // `match(nextState.path) === nextState.name` — which silently assumes the\n // 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: both landed `UNKNOWN_ROUTE` where they used to 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 // ⚠ The equality form WAS measured before being trusted — instrumented over\n // the whole tier, 515 firings and 512 agreements — and the measurement was\n // still not enough, which is the lesson worth keeping: the tier's shapes are\n // not the reachable shapes. Every case it covered had a path rebuilt from the\n // resolved route, so the whole class where `state.path` is the SOURCE url was\n // invisible to it. 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.navigateToNotFound(fromState.path, { skipDeactivation: true });\n\n return;\n }\n\n // Through the machine now (`SYSTEM_COMMIT`), so the write and the announce\n // are one table fact rather than two statements here. `replace()` USED TO run\n // application code between its entry `throwIfDisposed()` and this line —\n // `clearDefinitionGuards()` recompiled the compiled slot by invoking a\n // surviving EXTERNAL factory (#1192) — and a `dispose()` / `stop()` from\n // there let the swap finish and commit on a dead router with zero events\n // (#1627). #1649 removed THAT at the root: `clearDefinitionGuards`'s\n // re-derivation READS the survivor's stored compiled form instead of\n // re-running its factory.\n //\n // ⚠ It does not follow — and this comment used to claim it did — that\n // `replace()` \"no longer executes anything of the caller's between the two\n // points\". It executes at LEAST four other things, all 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. That sentence is what made\n // the missing existence check above look unnecessary (#1753) — a fix's scope\n // written up as the window's scope. ⚑ Written \"at least four\" on purpose: the\n // first draft of THIS correction said \"two other things, both above\" and\n // reproduced the very failure it names — an enumeration passed off as\n // exhaustive.\n //\n // ⚑ The liveness this line relies on is KEPT anyway, and deliberately: it now\n // covers a router disposed or stopped by some OTHER means between the entry\n // check and here, which `replace()` can no longer cause but cannot rule out.\n // The interim re-check that once did the job is gone — a dead router simply\n // has no edge to take, and `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 routes: 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(routes, \"addRoute\");\n assertNoDottedNamesInBatch(routes, \"addRoute\");\n assertNoDuplicateNamesInBatch(routes, \"\", \"addRoute\");\n assertNoDuplicatePathsInBatch(routes, \"\", \"addRoute\");\n\n // Build the whole new set BEFORE touching the store.\n const artifacts = buildReplaceArtifacts(\n routes,\n store.rootPath,\n store.matcherOptions,\n ctx.logger,\n );\n\n // Config-time channel check BEFORE clearDefinitionGuards mutates. It used to\n // live inside `adoptRouteArtifacts`, one line before the swap — early enough\n // for `add`, too late here: a refused batch left the tree intact and the old\n // definition guards ERASED, so a guarded route became freely activatable. Same\n // fail-open shape #1046 and #1193 hoisted their own throws out of, now for the\n // third throwing step this path grew.\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 (О-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 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 before the fix: with no `canDeactivate`\n // the user landed on the new route, WITH a refusing one on\n // UNKNOWN_ROUTE — a guard that cannot be honoured was making the result\n // 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, and an improvement: the refusal used to short-circuit\n // before the activation guards ran at all, so \"may the user be on the\n // new route\" went unasked. Now it is 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 // `skipDeactivation` stays, and its reason CHANGED with #1652: it is\n // no 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. Dropping it would let the fallback throw\n // CANNOT_DEACTIVATE out of a route-CRUD call, which is the shape\n // #1643 deliberately kept for user-initiated departures only.\n ctx.navigateToNotFound(currentState.path, { skipDeactivation: true });\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.navigateToNotFound(currentState.path, { skipDeactivation: true });\n }\n }\n}\n\n/**\n * Removes a route and all its children.\n *\n * @returns true if removed, false if not found\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 guardRouteStructure(routeArray, ctx.validator);\n\n if (parentName !== undefined) {\n ctx.validator?.routes.validateParentOption(parentName, store.tree);\n }\n\n ctx.validator?.routes.throwIfInternalRouteInArray(routeArray, \"addRoute\");\n ctx.validator?.routes.validateAddRouteArgs(routeArray);\n ctx.validator?.routes.validateRoutes(routeArray, store, parentName);\n\n addRoutes(store, routeArray, parentName, ctx.logger);\n\n // Built from the post-commit store (О-1), only when someone is listening.\n if (ctx.treeChanged.listenerCount() > 0) {\n const added = collectAddedRoutes(routeArray, 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.isTransitioning(),\n ctx.logger,\n store.matcher,\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 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 /* v8 ignore next 6 -- @preserve: race condition guard, mirrors Router.updateRoute() same-path guard tested via Router.ts unit tests */\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 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). update() of a route that does not exist used to\n // seed config.defaultParams + compile/register the guard (commitRouteUpdate\n // below) and emit a lying TREE_CHANGED \"update\" event for a route get()/\n // has() cannot see; a future add() of that name then inherited the phantom\n // config + a blocking guard. Skip the commit and the emit entirely when the\n // 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 // 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 (О-7 + empty-patch rule).\n if (ctx.treeChanged.listenerCount() > 0) {\n const patch = buildStructuralPatch<Dependencies>(structural);\n\n if (Object.keys(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). It used to drop the\n // committed state to `undefined` silently: every `router.subscribe`\n // consumer kept rendering a route the router had already discarded, and\n // the router was left `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 // (в), 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 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 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 (О-4).\n const removed =\n ctx.treeChanged.listenerCount() > 0\n ? Object.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 ctx.clearState();\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 guardRouteStructure(routeArray, ctx.validator);\n\n ctx.validator?.routes.throwIfInternalRouteInArray(\n routeArray,\n \"replaceRoutes\",\n );\n ctx.validator?.routes.validateAddRouteArgs(routeArray);\n ctx.validator?.routes.validateRoutes(routeArray, 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 (Решение 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 routeArray,\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 cache.set(router, api);\n\n return api;\n}\n","import { throwIfDisposed } from \"./helpers\";\nimport { getInternals } from \"../internals\";\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// =============================================================================\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 const isNewKey = !Object.hasOwn(store.dependencies, dependencyName);\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 = (store.dependencies as Record<string, unknown>)[\n dependencyName\n ];\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 validator?.dependencies.warnOverwrite(dependencyName, \"setDependency\");\n }\n }\n\n (store.dependencies as Record<string, unknown>)[dependencyName] =\n 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 for (const key in deps) {\n if (deps[key] === undefined) {\n continue;\n }\n\n if (Object.hasOwn(store.dependencies, key)) {\n overwrittenKeys.push(key);\n } else {\n validator?.dependencies.validateDependencyCount(store, \"setDependencies\");\n }\n\n (store.dependencies as Record<string, unknown>)[key] = deps[key];\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: () => ({ ...ctx.dependenciesGetStore().dependencies }),\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 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 remove: (name) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.dependencies.validateDependencyName(\n name,\n \"removeDependency\",\n );\n\n const store = ctx.dependenciesGetStore();\n\n if (!Object.hasOwn(store.dependencies, name)) {\n ctx.validator?.dependencies.warnRemoveNonExistent(name);\n }\n\n delete (store.dependencies as Record<string, unknown>)[name as string];\n },\n reset: () => {\n throwIfDisposed(ctx.isDisposed);\n const store = ctx.dependenciesGetStore();\n\n store.dependencies = Object.create(null) as Partial<Dependencies>;\n },\n has: (name) => {\n ctx.validator?.dependencies.validateDependencyName(name, \"hasDependency\");\n\n return Object.hasOwn(ctx.dependenciesGetStore().dependencies, name);\n },\n };\n}\n","import { throwIfDisposed } from \"./helpers\";\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 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 lifecycleNamespace.addCanActivate(name, handler);\n },\n\n addDeactivateGuard(name, handler) {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.routes.validateRouteName(name, \"addDeactivateGuard\");\n ctx.validator?.lifecycle.validateHandler(handler, \"addDeactivateGuard\");\n\n lifecycleNamespace.addCanDeactivate(name, handler);\n },\n\n removeActivateGuard(name) {\n throwIfDisposed(ctx.isDisposed);\n\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 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 { getInternals } from \"../internals\";\nimport { getLifecycleApi } from \"./getLifecycleApi\";\nimport { assignConfigEntries } from \"../namespaces/RoutesNamespace/helpers\";\nimport { Router as RouterClass } from \"../Router\";\nimport { RouterError } from \"../RouterError\";\n\nimport type {\n DefaultDependencies,\n LoggerConfig,\n Router,\n Route,\n} from \"../types\";\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 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 } = 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 const mergedDeps = {\n ...sourceDeps,\n ...dependencies,\n } as Dependencies;\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 (an M1 regression the singleton used to mask). A per-request\n // `opts.logger` override (e.g. a traceId-bound callback) merges on top.\n const clonedLoggerConfig: Partial<LoggerConfig> = opts?.logger\n ? { ...loggerConfig, ...opts.logger }\n : loggerConfig;\n\n const newRouter = new RouterClass<Dependencies>(\n routes as Route<Dependencies>[],\n { ...options, logger: clonedLoggerConfig },\n mergedDeps,\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) — deliberately uncounted here: this sentence said \"five\" until\n // defaultSearch made six (#1548), while the enumeration had already been\n // carrying it. resolvedForwardMap and routeCustomFields are store-level (not\n // part of RouteConfig) and stay explicit.\n assignConfigEntries(newStore.config, routeConfig);\n Object.assign(newStore.resolvedForwardMap, resolvedForwardMap);\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 Object.entries(definitionDeactivate)) {\n newLifecycleNamespace.addCanDeactivate(name, handler, true);\n }\n\n for (const [name, handler] of Object.entries(definitionActivate)) {\n newLifecycleNamespace.addCanActivate(name, handler, true);\n }\n\n const lifecycle = getLifecycleApi(newRouter);\n\n for (const [name, handler] of Object.entries(externalDeactivate)) {\n lifecycle.addDeactivateGuard(name, handler);\n }\n\n for (const [name, handler] of Object.entries(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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"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./Router-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./Router-D_dwJLCj.js"),t=(t=[],n={},r={})=>new e.t(t,n,r),n=new WeakMap,r=e=>{let t=n.get(e);return t||(t=Object.freeze({navigate:e.navigate,getState:e.getState,isActiveRoute:e.isActiveRoute,canNavigateTo:e.canNavigateTo,subscribe:e.subscribe,subscribeLeave:e.subscribeLeave,isLeaveApproved:e.isLeaveApproved}),n.set(e,t)),t};exports.Router=e.t,exports.RouterError=e.n,exports.UNKNOWN_ROUTE=e.O,exports.constants=e.k,exports.createRouter=t,exports.errorCodes=e.A,exports.events=e.j,exports.getNavigator=r,exports.resolveForwardChain=e._;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|