@real-router/core 0.79.1 → 0.80.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/api/cloneRouter.d.ts +1 -1
- package/dist/cjs/api/cloneRouter.js.map +1 -1
- package/dist/cjs/api.js +1 -2
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/buildParamMeta-DniluAIV.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +1 -2
- package/dist/cjs/internals.d.ts +1 -2
- package/dist/cjs/internals.d.ts.map +1 -1
- package/dist/cjs/internals.js.map +1 -1
- package/dist/cjs/types/base.d.ts +10 -1
- package/dist/cjs/types/base.d.ts.map +1 -1
- package/dist/cjs/types.d.ts +2 -2
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/validation.js +1 -2
- package/dist/esm/api/cloneRouter.d.mts +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/buildParamMeta-bOLhLF9h.mjs.map +1 -1
- package/dist/esm/index.d.mts +2 -2
- package/dist/esm/internals.d.mts +1 -2
- package/dist/esm/internals.d.mts.map +1 -1
- package/dist/esm/types/base.d.mts +10 -1
- package/dist/esm/types/base.d.mts.map +1 -1
- package/dist/esm/types.d.mts +2 -2
- package/dist/esm/types.d.mts.map +1 -1
- package/package.json +4 -13
- package/dist/cjs/cloneRouter-Ch25z3Z2.js +0 -2
- package/dist/cjs/cloneRouter-Ch25z3Z2.js.map +0 -1
- package/dist/cjs/utils/createRequestScope.d.ts +0 -111
- package/dist/cjs/utils/createRequestScope.d.ts.map +0 -1
- package/dist/cjs/utils/createRequestScope.js +0 -2
- package/dist/cjs/utils/createRequestScope.js.map +0 -1
- package/dist/cjs/utils/getStaticPaths.d.ts +0 -7
- package/dist/cjs/utils/getStaticPaths.d.ts.map +0 -1
- package/dist/cjs/utils/getStaticPaths.js +0 -2
- package/dist/cjs/utils/getStaticPaths.js.map +0 -1
- package/dist/cjs/utils/hydrateRouter.d.ts +0 -69
- package/dist/cjs/utils/hydrateRouter.d.ts.map +0 -1
- package/dist/cjs/utils/hydrateRouter.js +0 -2
- package/dist/cjs/utils/hydrateRouter.js.map +0 -1
- package/dist/cjs/utils/serializeRouterState.d.ts +0 -84
- package/dist/cjs/utils/serializeRouterState.d.ts.map +0 -1
- package/dist/cjs/utils/serializeRouterState.js +0 -2
- package/dist/cjs/utils/serializeRouterState.js.map +0 -1
- package/dist/cjs/utils/serializeState.d.ts +0 -50
- package/dist/cjs/utils/serializeState.d.ts.map +0 -1
- package/dist/cjs/utils/serializeState.js +0 -2
- package/dist/cjs/utils/serializeState.js.map +0 -1
- package/dist/cjs/utils.d.ts +0 -6
- package/dist/cjs/utils.js +0 -2
- package/dist/cjs/utils.js.map +0 -1
- package/dist/esm/cloneRouter-D4rnqmi9.mjs +0 -2
- package/dist/esm/cloneRouter-D4rnqmi9.mjs.map +0 -1
- package/dist/esm/utils/createRequestScope.d.mts +0 -111
- package/dist/esm/utils/createRequestScope.d.mts.map +0 -1
- package/dist/esm/utils/getStaticPaths.d.mts +0 -7
- package/dist/esm/utils/getStaticPaths.d.mts.map +0 -1
- package/dist/esm/utils/hydrateRouter.d.mts +0 -69
- package/dist/esm/utils/hydrateRouter.d.mts.map +0 -1
- package/dist/esm/utils/serializeRouterState.d.mts +0 -84
- package/dist/esm/utils/serializeRouterState.d.mts.map +0 -1
- package/dist/esm/utils/serializeState.d.mts +0 -50
- package/dist/esm/utils/serializeState.d.mts.map +0 -1
- package/dist/esm/utils.d.mts +0 -6
- package/dist/esm/utils.mjs +0 -2
- package/dist/esm/utils.mjs.map +0 -1
|
@@ -77,7 +77,7 @@ interface CloneOptions {
|
|
|
77
77
|
* // clone.deps.currentUser ✓ unique per request
|
|
78
78
|
* ```
|
|
79
79
|
*
|
|
80
|
-
* @see createRequestScope — `@real-router/
|
|
80
|
+
* @see createRequestScope — `@real-router/ssr-utils` SSR helper that
|
|
81
81
|
* wraps this function and injects `abortSignal` automatically.
|
|
82
82
|
*/
|
|
83
83
|
declare function cloneRouter<Dependencies extends DefaultDependencies = DefaultDependencies>(router: Router<Dependencies>, dependencies?: Dependencies, opts?: CloneOptions): Router$1<Dependencies>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloneRouter.js","names":["getInternals","RouterError","errorCodes","routeTreeToDefinitions","RouterClass","getLifecycleApi"],"sources":["../../../src/api/cloneRouter.ts"],"sourcesContent":["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/core/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. The five RouteConfig sub-maps go through a single\n // enumeration so a newly added config field is carried over automatically\n // (#965); resolvedForwardMap and routeCustomFields are store-level (not part\n // 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":"6QA+FA,SAAgB,EAGd,EACA,EACA,EAC2B,CAC3B,IAAM,EAAMA,EAAAA,aAAa,CAAM,EAE/B,GAAI,EAAI,WAAW,EACjB,MAAM,IAAIC,EAAAA,YAAYC,EAAAA,WAAW,eAAe,EAGlD,EAAI,WAAW,aAAa,kBAAkB,CAAY,EAG1D,IAAM,EAAc,EAAI,cAAc,EAChC,EAASC,EAAAA,uBAAuB,EAAY,IAAI,EAChD,EAAc,EAAY,OAC1B,EAAqB,EAAY,mBACjC,EAAoB,EAAY,kBAEhC,CACJ,UACA,aAAc,EACd,kBACA,gBACE,EAAI,cAAc,EAOhB,CAAE,WAAY,EAAqB,SAAU,GADlB,EAAY,mBAElB,qBAAqB,EAE1C,EAAa,CACjB,GAAG,EACH,GAAG,CACL,EAOM,EAA4C,GAAM,OACpD,CAAE,GAAG,EAAc,GAAG,EAAK,MAAO,EAClC,EAEE,EAAY,IAAIC,EAAAA,OACpB,EACA,CAAE,GAAG,EAAS,OAAQ,CAAmB,EACzC,CACF,EAEM,EAASJ,EAAAA,aAAa,CAAS,EAC/B,EAAW,EAAO,cAAc,EAEhC,EAAwB,EAAS,mBAUvC,EAAA,oBAAoB,EAAS,OAAQ,CAAW,EAChD,OAAO,OAAO,EAAS,mBAAoB,CAAkB,EAC7D,OAAO,OAAO,EAAS,kBAAmB,CAAiB,EASvD,EAAY,WAAa,IAC3B,EAAO,YAAY,EAAY,QAAQ,EAGzC,GAAM,CAAC,EAAsB,GAAsB,EAC7C,CAAC,EAAoB,GAAoB,EAE/C,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,CAAoB,EAC/D,EAAsB,iBAAiB,EAAM,EAAS,EAAI,EAG5D,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,CAAkB,EAC7D,EAAsB,eAAe,EAAM,EAAS,EAAI,EAG1D,IAAM,EAAYK,EAAAA,gBAAgB,CAAS,EAE3C,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,CAAkB,EAC7D,EAAU,mBAAmB,EAAM,CAAO,EAG5C,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,CAAgB,EAC3D,EAAU,iBAAiB,EAAM,CAAO,EAO1C,IAAM,EAAoB,IAAI,IAAI,EAAO,cAAc,CAAC,CAAC,eAAe,EAClE,EAAkB,EAAgB,OACrC,GAAY,CAAC,EAAkB,IAAI,CAAO,CAC7C,EAOA,OAJI,EAAgB,OAAS,GAC3B,EAAU,UAAU,GAAG,CAAe,EAGjC,CACT"}
|
|
1
|
+
{"version":3,"file":"cloneRouter.js","names":["getInternals","RouterError","errorCodes","routeTreeToDefinitions","RouterClass","getLifecycleApi"],"sources":["../../../src/api/cloneRouter.ts"],"sourcesContent":["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. The five RouteConfig sub-maps go through a single\n // enumeration so a newly added config field is carried over automatically\n // (#965); resolvedForwardMap and routeCustomFields are store-level (not part\n // 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":"6QA+FA,SAAgB,EAGd,EACA,EACA,EAC2B,CAC3B,IAAM,EAAMA,EAAAA,aAAa,CAAM,EAE/B,GAAI,EAAI,WAAW,EACjB,MAAM,IAAIC,EAAAA,YAAYC,EAAAA,WAAW,eAAe,EAGlD,EAAI,WAAW,aAAa,kBAAkB,CAAY,EAG1D,IAAM,EAAc,EAAI,cAAc,EAChC,EAASC,EAAAA,uBAAuB,EAAY,IAAI,EAChD,EAAc,EAAY,OAC1B,EAAqB,EAAY,mBACjC,EAAoB,EAAY,kBAEhC,CACJ,UACA,aAAc,EACd,kBACA,gBACE,EAAI,cAAc,EAOhB,CAAE,WAAY,EAAqB,SAAU,GADlB,EAAY,mBAElB,qBAAqB,EAE1C,EAAa,CACjB,GAAG,EACH,GAAG,CACL,EAOM,EAA4C,GAAM,OACpD,CAAE,GAAG,EAAc,GAAG,EAAK,MAAO,EAClC,EAEE,EAAY,IAAIC,EAAAA,OACpB,EACA,CAAE,GAAG,EAAS,OAAQ,CAAmB,EACzC,CACF,EAEM,EAASJ,EAAAA,aAAa,CAAS,EAC/B,EAAW,EAAO,cAAc,EAEhC,EAAwB,EAAS,mBAUvC,EAAA,oBAAoB,EAAS,OAAQ,CAAW,EAChD,OAAO,OAAO,EAAS,mBAAoB,CAAkB,EAC7D,OAAO,OAAO,EAAS,kBAAmB,CAAiB,EASvD,EAAY,WAAa,IAC3B,EAAO,YAAY,EAAY,QAAQ,EAGzC,GAAM,CAAC,EAAsB,GAAsB,EAC7C,CAAC,EAAoB,GAAoB,EAE/C,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,CAAoB,EAC/D,EAAsB,iBAAiB,EAAM,EAAS,EAAI,EAG5D,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,CAAkB,EAC7D,EAAsB,eAAe,EAAM,EAAS,EAAI,EAG1D,IAAM,EAAYK,EAAAA,gBAAgB,CAAS,EAE3C,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,CAAkB,EAC7D,EAAU,mBAAmB,EAAM,CAAO,EAG5C,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,CAAgB,EAC3D,EAAU,iBAAiB,EAAM,CAAO,EAO1C,IAAM,EAAoB,IAAI,IAAI,EAAO,cAAc,CAAC,CAAC,eAAe,EAClE,EAAkB,EAAgB,OACrC,GAAY,CAAC,EAAkB,IAAI,CAAO,CAC7C,EAOA,OAJI,EAAgB,OAAS,GAC3B,EAAU,UAAU,GAAG,CAAe,EAGjC,CACT"}
|
package/dist/cjs/api.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./
|
|
2
|
-
//# sourceMappingURL=api.js.map
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./api/getPluginApi.js"),t=require("./api/getRoutesApi.js"),n=require("./api/getDependenciesApi.js"),r=require("./api/getLifecycleApi.js"),i=require("./api/cloneRouter.js");exports.cloneRouter=i.cloneRouter,exports.getDependenciesApi=n.getDependenciesApi,exports.getLifecycleApi=r.getLifecycleApi,exports.getPluginApi=e.getPluginApi,exports.getRoutesApi=t.getRoutesApi;
|
package/dist/cjs/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","names":["buildAddArtifacts","buildReplaceArtifacts","compileArtifactGuards","getTransitionPath","removeFromDefinitions","nodeToDefinition","getInternals","commitRouteUpdate","getInternals"],"sources":["../../src/namespaces/RoutesNamespace/routeGuards.ts","../../src/api/getRoutesApi.ts","../../src/api/getDependenciesApi.ts"],"sourcesContent":["import 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 * @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): boolean {\n if (currentStateName) {\n const isExactMatch = currentStateName === name;\n const isParentOfCurrent = currentStateName.startsWith(`${name}.`);\n\n if (isExactMatch || isParentOfCurrent) {\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 logger.warn(\n \"router.removeRoute\",\n `Route \"${name}\" removed while navigation is in progress. This may cause unexpected behavior.`,\n );\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 { nodeToDefinition } from \"../engine\";\nimport { throwIfDisposed, throwIfReentrantTreeMutation } from \"./helpers\";\nimport { guardRouteStructure } from \"../guards\";\nimport { getInternals } from \"../internals\";\nimport {\n clearConfigEntries,\n removeFromDefinitions,\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 { 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 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/**\n * Clears all config entries and lifecycle handlers for a removed route\n * (and all its descendants).\n */\nfunction clearRouteConfigurations<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routeName: string,\n config: RouteConfig,\n routeCustomFields: Record<string, Record<string, unknown>>,\n lifecycleNamespace: RouteLifecycleNamespace<Dependencies>,\n): void {\n const shouldClear = (name: string): boolean =>\n name === routeName || name.startsWith(`${routeName}.`);\n\n clearConfigEntries(config.decoders, shouldClear);\n clearConfigEntries(config.encoders, shouldClear);\n clearConfigEntries(config.defaultParams, 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.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 route `name` and all of its descendants as a FLAT, frozen array.\n * MUST be called BEFORE the removal mutation — the nodes are gone afterwards.\n */\nfunction collectSubtree<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n name: string,\n): readonly Route<Dependencies>[] {\n const prefix = `${name}.`;\n const subtree = collectFlatRoutes(\n store,\n (fullName) => fullName === name || fullName.startsWith(prefix),\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 decodeParams?: ((params: Params) => Params) | null | undefined;\n encodeParams?: ((params: Params) => Params) | 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.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 // Pre-flight the #961 handler-limit into PREPARE so a limit-exceeding batch\n // aborts before the swap (#1046). `add` does not clear guards, so the\n // projection runs against the live union count (clearsDefinition = false).\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.preflightHandlerLimit(\n artifacts.pendingCanActivate.keys(),\n artifacts.pendingCanDeactivate.keys(),\n false,\n );\n\n adoptRouteArtifacts(store, artifacts);\n}\n\n/**\n * Commits a revalidated state after `replace()` and emits `TRANSITION_SUCCESS`\n * so `router.subscribe` / adapters re-render (#950). The emit carries\n * `REVALIDATE_OPTS` — the single distinguishable marker (`revalidate: true`) a\n * plugin's `onTransitionSuccess` can read to special-case a revalidation vs a\n * real navigation (#1201).\n */\nfunction commitRevalidated<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n ctx: RouterInternals<Dependencies>,\n nextState: State,\n fromState: State,\n): void {\n ctx.setState(nextState);\n ctx.emitTransitionSuccess(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 // 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 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(ctx, nextState, currentState);\n } else {\n // Route-identity change — the URL is now owned by a DIFFERENT route (an\n // ownership reshuffle, or a newly-added `forwardTo` that teleports the\n // state). That is effectively a navigation the user never performed, so\n // consult the new route's guards exactly as `navigate` would (#1201).\n // Commit on pass; on a block — or an async guard that cannot be\n // evaluated synchronously (mirrors `canNavigateTo`) — route to\n // not-found rather than silently activating a guarded route.\n const { toDeactivate, toActivate } = getTransitionPath(\n revalidated,\n currentState,\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 toDeactivate,\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(ctx, nextState, currentState);\n } else {\n ctx.navigateToNotFound(currentState.path);\n }\n }\n } else {\n // The active route no longer exists in the new tree — surface it as\n // not-found (commits UNKNOWN_ROUTE + emits TRANSITION_SUCCESS) so the\n // change is observable, rather than silently clearing the state.\n ctx.navigateToNotFound(currentState.path);\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>(store: RoutesStore<Dependencies>, name: string): boolean {\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 wasRemoved = removeFromDefinitions(definitions, name);\n\n if (!wasRemoved) {\n return false;\n }\n\n clearRouteConfigurations(\n name,\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 true;\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 );\n\n if (!canRemove) {\n return;\n }\n\n // Snapshot the subtree BEFORE the mutation — the nodes are gone after.\n const removedSubtree =\n ctx.treeChanged.listenerCount() > 0\n ? collectSubtree(store, name)\n : undefined;\n const wasRemoved = removeRoute(store, name);\n\n if (!wasRemoved) {\n ctx.logger.warn(\n \"router.removeRoute\",\n `Route \"${name}\" not found. No changes made.`,\n );\n\n return;\n }\n\n if (removedSubtree !== undefined) {\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 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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api.js","names":["RouterError","errorCodes","cache","getInternals","RouterError","errorCodes","buildAddArtifacts","buildReplaceArtifacts","compileArtifactGuards","getTransitionPath","removeFromDefinitions","nodeToDefinition","getInternals","commitRouteUpdate","getInternals","getInternals","getInternals","RouterError","errorCodes","routeTreeToDefinitions","RouterClass"],"sources":["../../src/api/helpers.ts","../../src/api/getPluginApi.ts","../../src/namespaces/RoutesNamespace/routeGuards.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 route-CRUD: a CRUD op called while a `TREE_CHANGED`\n * emit is on the stack (i.e. from inside a `subscribeChanges` handler) throws\n * `REENTRANT_TREE_MUTATION` BEFORE mutating — the tree stays atomic (#1032).\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 */\nexport function throwIfReentrantTreeMutation(isEmitting: () => boolean): void {\n if (isEmitting()) {\n throw new RouterError(errorCodes.REENTRANT_TREE_MUTATION);\n }\n}\n","import { throwIfDisposed } from \"./helpers\";\nimport { errorCodes } from \"../constants\";\nimport { getInternals } from \"../internals\";\nimport { RouterError } from \"../RouterError\";\n\nimport type { PluginApi } from \"./types\";\nimport type {\n ContextNamespaceClaim,\n DefaultDependencies,\n Params,\n Router,\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, path, meta) => {\n ctx.validator?.state.validateMakeStateArgs(name, params, path);\n\n return ctx.makeState(\n name,\n params,\n path,\n meta?.params as\n Record<string, Record<string, \"url\" | \"query\">> | undefined,\n );\n },\n buildState: (routeName, routeParams) => {\n ctx.validator?.routes.validateStateBuilderArgs(\n routeName,\n routeParams,\n \"buildState\",\n );\n\n const { name, params } = ctx.forwardState(routeName, routeParams);\n\n return ctx.buildStateResolved(name, params);\n },\n forwardState: <P extends Params = Params>(\n routeName: string,\n routeParams: P,\n ) => {\n ctx.validator?.routes.validateStateBuilderArgs(\n routeName,\n routeParams,\n \"forwardState\",\n );\n\n return ctx.forwardState(routeName, routeParams);\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\n ctx.validator?.routes.validateSetRootPathArgs(rootPath);\n\n ctx.setRootPath(rootPath);\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 = {}) => {\n ctx.validator?.routes.validateStateBuilderArgs(\n name,\n params,\n \"buildNavigationState\",\n );\n\n const { name: resolvedName, params: resolvedParams } = ctx.forwardState(\n name,\n params,\n );\n const routeInfo = ctx.buildStateResolved(resolvedName, resolvedParams);\n\n if (!routeInfo) {\n return;\n }\n\n return ctx.makeState(\n routeInfo.name,\n routeInfo.params,\n ctx.buildPath(routeInfo.name, routeInfo.params),\n routeInfo.meta,\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 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 * @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): boolean {\n if (currentStateName) {\n const isExactMatch = currentStateName === name;\n const isParentOfCurrent = currentStateName.startsWith(`${name}.`);\n\n if (isExactMatch || isParentOfCurrent) {\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 logger.warn(\n \"router.removeRoute\",\n `Route \"${name}\" removed while navigation is in progress. This may cause unexpected behavior.`,\n );\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 { nodeToDefinition } from \"../engine\";\nimport { throwIfDisposed, throwIfReentrantTreeMutation } from \"./helpers\";\nimport { guardRouteStructure } from \"../guards\";\nimport { getInternals } from \"../internals\";\nimport {\n clearConfigEntries,\n removeFromDefinitions,\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 { 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 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/**\n * Clears all config entries and lifecycle handlers for a removed route\n * (and all its descendants).\n */\nfunction clearRouteConfigurations<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n routeName: string,\n config: RouteConfig,\n routeCustomFields: Record<string, Record<string, unknown>>,\n lifecycleNamespace: RouteLifecycleNamespace<Dependencies>,\n): void {\n const shouldClear = (name: string): boolean =>\n name === routeName || name.startsWith(`${routeName}.`);\n\n clearConfigEntries(config.decoders, shouldClear);\n clearConfigEntries(config.encoders, shouldClear);\n clearConfigEntries(config.defaultParams, 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.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 route `name` and all of its descendants as a FLAT, frozen array.\n * MUST be called BEFORE the removal mutation — the nodes are gone afterwards.\n */\nfunction collectSubtree<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n store: RoutesStore<Dependencies>,\n name: string,\n): readonly Route<Dependencies>[] {\n const prefix = `${name}.`;\n const subtree = collectFlatRoutes(\n store,\n (fullName) => fullName === name || fullName.startsWith(prefix),\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 decodeParams?: ((params: Params) => Params) | null | undefined;\n encodeParams?: ((params: Params) => Params) | 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.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 // Pre-flight the #961 handler-limit into PREPARE so a limit-exceeding batch\n // aborts before the swap (#1046). `add` does not clear guards, so the\n // projection runs against the live union count (clearsDefinition = false).\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n store.lifecycleNamespace!.preflightHandlerLimit(\n artifacts.pendingCanActivate.keys(),\n artifacts.pendingCanDeactivate.keys(),\n false,\n );\n\n adoptRouteArtifacts(store, artifacts);\n}\n\n/**\n * Commits a revalidated state after `replace()` and emits `TRANSITION_SUCCESS`\n * so `router.subscribe` / adapters re-render (#950). The emit carries\n * `REVALIDATE_OPTS` — the single distinguishable marker (`revalidate: true`) a\n * plugin's `onTransitionSuccess` can read to special-case a revalidation vs a\n * real navigation (#1201).\n */\nfunction commitRevalidated<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n ctx: RouterInternals<Dependencies>,\n nextState: State,\n fromState: State,\n): void {\n ctx.setState(nextState);\n ctx.emitTransitionSuccess(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 // 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 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(ctx, nextState, currentState);\n } else {\n // Route-identity change — the URL is now owned by a DIFFERENT route (an\n // ownership reshuffle, or a newly-added `forwardTo` that teleports the\n // state). That is effectively a navigation the user never performed, so\n // consult the new route's guards exactly as `navigate` would (#1201).\n // Commit on pass; on a block — or an async guard that cannot be\n // evaluated synchronously (mirrors `canNavigateTo`) — route to\n // not-found rather than silently activating a guarded route.\n const { toDeactivate, toActivate } = getTransitionPath(\n revalidated,\n currentState,\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 toDeactivate,\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(ctx, nextState, currentState);\n } else {\n ctx.navigateToNotFound(currentState.path);\n }\n }\n } else {\n // The active route no longer exists in the new tree — surface it as\n // not-found (commits UNKNOWN_ROUTE + emits TRANSITION_SUCCESS) so the\n // change is observable, rather than silently clearing the state.\n ctx.navigateToNotFound(currentState.path);\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>(store: RoutesStore<Dependencies>, name: string): boolean {\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 wasRemoved = removeFromDefinitions(definitions, name);\n\n if (!wasRemoved) {\n return false;\n }\n\n clearRouteConfigurations(\n name,\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 true;\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 );\n\n if (!canRemove) {\n return;\n }\n\n // Snapshot the subtree BEFORE the mutation — the nodes are gone after.\n const removedSubtree =\n ctx.treeChanged.listenerCount() > 0\n ? collectSubtree(store, name)\n : undefined;\n const wasRemoved = removeRoute(store, name);\n\n if (!wasRemoved) {\n ctx.logger.warn(\n \"router.removeRoute\",\n `Route \"${name}\" not found. No changes made.`,\n );\n\n return;\n }\n\n if (removedSubtree !== undefined) {\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 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. The five RouteConfig sub-maps go through a single\n // enumeration so a newly added config field is carried over automatically\n // (#965); resolvedForwardMap and routeCustomFields are store-level (not part\n // 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildParamMeta-DniluAIV.js","names":["LT","QUESTION","SLASH"],"sources":["../../src/internals.ts","../../src/engine/path-matcher/parseSegment.ts","../../src/engine/path-matcher/buildParamMeta.ts"],"sourcesContent":["import type { RouteTree } from \"./engine\";\nimport type { DependenciesStore } from \"./namespaces\";\nimport type { RoutesStore } from \"./namespaces/RoutesNamespace\";\nimport type { Router as RouterClass } from \"./Router\";\nimport type {\n DefaultDependencies,\n EventName,\n LoggerConfig,\n NavigationOptions,\n Options,\n Params,\n Plugin,\n Router as RouterInterface,\n RouterLogger,\n RouteTreeState,\n SimpleState,\n State,\n TreeChangedEvent,\n Unsubscribe,\n EventMethodMap,\n PluginFactory,\n} from \"./types\";\nimport type { RouterValidator } from \"./types/RouterValidator\";\nimport type { SerializedRouterState } from \"./utils\";\n\nexport interface RouterInternals<\n D extends DefaultDependencies = DefaultDependencies,\n> {\n readonly makeState: <P extends Params = Params>(\n name: string,\n params?: P,\n path?: string,\n meta?: Record<string, Record<string, \"url\" | \"query\">>,\n ) => State<P>;\n\n readonly forwardState: <P extends Params = Params>(\n routeName: string,\n routeParams: P,\n ) => SimpleState<P>;\n\n readonly buildStateResolved: (\n resolvedName: string,\n resolvedParams: Params,\n ) => RouteTreeState | undefined;\n\n readonly matchPath: <P extends Params = Params>(\n path: string,\n options?: Options,\n ) => State<P> | undefined;\n\n readonly getOptions: () => Options;\n\n readonly addEventListener: <E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n ) => Unsubscribe;\n\n /**\n * Route-tree mutation channel — internal access for the `getRoutesApi`\n * wrapper. A dedicated bridge is required because the public\n * `addEventListener<E extends EventName>` structurally rejects\n * `\"TREE_CHANGED\"` (it is not in the public `EventName` union), is strict on\n * duplicates, and exposes neither `emit` nor `listenerCount`.\n */\n readonly treeChanged: {\n readonly emit: (event: TreeChangedEvent) => void;\n readonly subscribe: (\n handler: (event: TreeChangedEvent) => void,\n ) => Unsubscribe;\n readonly listenerCount: () => number;\n /**\n * True while a `TREE_CHANGED` emit is on the stack — `getRoutesApi` reads it\n * to reject reentrant route-CRUD from a `subscribeChanges` handler (#1032).\n */\n readonly isEmitting: () => boolean;\n };\n\n readonly buildPath: (route: string, params?: Params) => string;\n\n readonly emitTransitionError: (error: Error) => void;\n\n /**\n * Emits `TRANSITION_SUCCESS` directly (no FSM transition) — used by\n * `getRoutesApi().replace()` to notify `router.subscribe` listeners when a\n * structural replace revalidates the active state (#950). Mirrors the success\n * emission `completeTransition` / `navigateToNotFound` perform.\n */\n readonly emitTransitionSuccess: (\n toState: State,\n fromState: State | undefined,\n opts?: NavigationOptions,\n ) => void;\n\n /**\n * Commits the not-found (`UNKNOWN_ROUTE`) state for `path` and emits\n * `TRANSITION_SUCCESS` — the `NavigationNamespace.navigateToNotFound`\n * primitive. `replace()` uses it when a structural replace drops the active\n * route, so subscribers are notified instead of the state silently clearing\n * (#950).\n */\n readonly navigateToNotFound: (path: string) => State;\n\n readonly start: (path: string) => Promise<State>;\n\n /**\n * Plugin-only navigation entry point — delegates to\n * `NavigationNamespace.navigateToState` (`getPluginApi(router).navigateToState`).\n * Hidden from `Router`/`Navigator` to keep the userland surface minimal;\n * see `core-types/src/api.ts` for usage docs.\n */\n readonly navigateToState: (\n state: State,\n options?: NavigationOptions,\n ) => Promise<State>;\n\n /* eslint-disable @typescript-eslint/no-explicit-any -- heterogeneous map: stores different InterceptorFn<M> types under different keys */\n readonly interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >;\n /* eslint-enable @typescript-eslint/no-explicit-any */\n\n readonly setRootPath: (rootPath: string) => void;\n readonly getRootPath: () => string;\n\n readonly getTree: () => RouteTree;\n\n readonly isDisposed: () => boolean;\n\n validator: RouterValidator | null;\n\n // Per-router logger instance (built from `options.logger` in the Router\n // constructor). The facade reads it as `getInternals(this).logger`; namespaces\n // receive it via their deps at wiring; plugins reach it through\n // `getPluginApi(router).logger`. Replaces the former process-global singleton\n // from the standalone `@real-router/logger` package (now folded into\n // `foundation/logger`), whose `configure()` leaked across routers (#724).\n readonly logger: RouterLogger;\n\n // Dependencies (issue #172)\n readonly dependenciesGetStore: () => DependenciesStore<D>;\n\n // Clone support (issue #173, consolidated #964). One accessor for the\n // source-side snapshot a clone carries over besides the route store, so a new\n // clone-relevant subsystem is wired in a single place instead of being spread\n // across separate methods.\n readonly getCloneState: () => {\n options: Options;\n dependencies: Record<string, unknown>;\n pluginFactories: PluginFactory<D>[];\n // Resolved logger config of the base router, so a clone can build its OWN\n // logger inheriting the base's level/callback. Frozen `options` do NOT carry\n // `logger` (stripped in the constructor), so `options` above can't convey it;\n // cloneRouter merges a per-request override (traceId) over this snapshot.\n loggerConfig: LoggerConfig;\n };\n\n // Consolidated route data store (issue #174 Phase 2)\n readonly routeGetStore: () => RoutesStore<D>;\n\n // Cross-namespace state (issue #174)\n readonly getStateName: () => string | undefined;\n readonly isTransitioning: () => boolean;\n readonly clearState: () => void;\n readonly setState: (state: State) => void;\n readonly routerExtensions: { keys: string[] }[];\n readonly contextClaimRecords: Set<string>;\n\n /**\n * One-shot hydration scratchpad populated by `hydrateRouter` immediately\n * before delegating to `router.start(parsed.path)` and cleared in the\n * matching `finally`. SSR loader plugins read this slot directly via\n * `getInternals(router).hydrationState` to short-circuit their own loader\n * call when the server-resolved namespace value is already present in the\n * parsed state (#596). `null` outside of an active `hydrateRouter`\n * invocation.\n */\n hydrationState: SerializedRouterState | null;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- existential type: stores RouterInternals for all Dependencies types\nconst internals = new WeakMap<object, RouterInternals<any>>();\n\nexport function getInternals<D extends DefaultDependencies>(\n router: RouterInterface<D>,\n): RouterInternals<D> {\n const ctx = internals.get(router);\n\n if (!ctx) {\n throw new TypeError(\n \"[real-router] Invalid router instance — not found in internals registry\",\n );\n }\n\n return ctx as RouterInternals<D>;\n}\n\nexport function registerInternals<D extends DefaultDependencies>(\n router: RouterClass<D>,\n ctx: RouterInternals<D>,\n): void {\n internals.set(router, ctx);\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument -- internal chain execution: type safety enforced at public API boundary (PluginApi.addInterceptor) */\nfunction executeInterceptorChain<T>(\n interceptors: ((next: (...args: any[]) => any, ...args: any[]) => any)[],\n original: (...args: any[]) => T,\n args: any[],\n): T {\n let chain = original as (...args: any[]) => any;\n\n for (const interceptor of interceptors) {\n const prev = chain;\n\n chain = (...chainArgs: any[]) => interceptor(prev, ...chainArgs);\n }\n\n return chain(...args) as T;\n}\n\n/**\n * Variadic interceptor wrapper — wraps a function of any arity, returning the\n * same callable type `T`. Use {@link createBinaryInterceptable} instead when the\n * wrapped method takes exactly two args and the caller needs the precise\n * `(a, b) => r` signature preserved (the variadic form widens args to `any[]`).\n */\nexport function createInterceptable<T extends (...args: any[]) => any>(\n name: string,\n original: T,\n interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >,\n): T {\n return ((...args: any[]) => {\n const chain = interceptors.get(name);\n\n if (!chain || chain.length === 0) {\n return original(...args);\n }\n\n return executeInterceptorChain(chain, original, args);\n }) as T;\n}\n\n/**\n * Two-argument interceptor wrapper — preserves the exact `(a: A, b: B) => R`\n * signature, which the variadic {@link createInterceptable} cannot express\n * (it widens args to `any[]`). Used for the binary interceptable methods\n * `forwardState(routeName, routeParams)` and `buildPath(route, params)`.\n */\nexport function createBinaryInterceptable<A, B, R>(\n name: string,\n original: (a: A, b: B) => R,\n interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >,\n): (a: A, b: B) => R {\n return (arg1: A, arg2: B) => {\n const chain = interceptors.get(name);\n\n if (!chain || chain.length === 0) {\n return original(arg1, arg2);\n }\n\n return executeInterceptorChain(chain, original, [arg1, arg2]);\n };\n}\n/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */\n","/**\n * Canonical route-segment tokenizer.\n *\n * The single owner of \"where does a name / marker / constraint end\" for ONE\n * path segment (post-`/`-split). It replaces the five name-boundary compositions\n * of `PARAM_NAME_PATTERN` currently spread across `buildParamMeta` (L1),\n * `registration` (L2 build + L3 trie), and `route-tree`'s validation gate (L4),\n * so those layers can never disagree on a boundary (the gate↔backstop drift\n * class — #858 / #1050 / #1150 / #1311 / #1149 / #1324).\n *\n * A single left-to-right `charCodeAt` scan produces either a token tuple or a\n * typed error. Grammar — **3 tokens only** (`static | :param | *splat`); the\n * grammar has no optional `:x?` or `<re>` constraint forms. Any `<`/`>` or a\n * post-name `?` in the path is a *registration error* carrying a replacement\n * recipe (`optional-removed` / `constraint-removed`), not a token:\n * 1. Leading `:`/`*` → param/splat; otherwise `static` (a marker glued *after* a\n * static prefix ⇒ `fused-marker`; any `<`/`>` (a former constraint) ⇒\n * `constraint-removed`; a trailing `?` on a marker-less segment ⇒ `name-less`\n * — the modifier has no param name, #1241 / `/faq?`).\n * 2. name = any char except `<`/`?` (no `/` remains inside a segment); a name\n * ending in a bare `:`/`*` ⇒ `trailing-marker` (#1324). A *mid* marker stays\n * a name char — `:a:b` → name `a:b`, preserved.\n * 3. empty name ⇒ `name-less` (#858).\n * 4. a `<` after the name (a former `<re>` constraint) ⇒ `constraint-removed`.\n * 5. a post-name `?` (a former optional modifier, on `:param` or `*splat`) ⇒\n * `optional-removed`.\n *\n * @module parseSegment\n */\n\n/* eslint-disable unicorn/prefer-code-point, unicorn/prefer-includes-over-repeated-comparisons, sonarjs/cognitive-complexity -- charCodeAt code-unit scan + a single inlined branchy pass are this RFC's char-scan perf basis (§9); the same deliberate choices as registration/trie.ts hasNonAsciiSegment (#1285) and SegmentMatcher's inlined #traverseFrom. A `[LT,GT,QUESTION].includes(code)` boundary check would allocate an array literal per scanned char. Markers compared are ASCII (`:` `*` `<` `>` `?`, < 0x80). */\n\n/** A successfully tokenized segment (3-token grammar: `static | :param | *splat`). */\nexport type SegmentTokens =\n | { readonly kind: \"static\"; readonly text: string }\n | { readonly kind: \"param\"; readonly name: string }\n | { readonly kind: \"splat\"; readonly name: string };\n\n/** Grammar-shape rejections, each mirroring a registration guard. */\nexport type SegmentErrorCode =\n | \"name-less\" // #858 — a marker with no name\n | \"trailing-marker\" // #1324 — a param name ending in a bare `:`/`*`\n | \"fused-marker\" // #1050 — a marker glued after a static prefix\n | \"optional-removed\" // M1 — a `:x?`/`*x?` optional modifier (removed; two sibling routes)\n | \"constraint-removed\"; // M1 — a `<re>` constraint or stray `<`/`>` (removed; validate in a guard)\n\nexport interface SegmentError {\n readonly error: SegmentErrorCode;\n}\n\nconst COLON = 58; // :\nconst STAR = 42; // *\nconst LT = 60; // <\nconst GT = 62; // >\nconst QUESTION = 63; // ?\nconst SLASH = 47; // /\n\nconst isMarker = (code: number): boolean => code === COLON || code === STAR;\n\n/**\n * Splits a path into its `/`-delimited segments. A plain `/`-split (M1): the\n * 3-token grammar has no `<...>` constraint whose body could legally contain a\n * `/`, so no constraint-awareness is needed — a stray `<`/`>` is a\n * `constraint-removed` error, caught per segment by `parseSegment`. This is the\n * **segmentation** half of the path-grammar unification: `parseSegment` owns the\n * per-segment grammar, `splitPathSegments` owns where a segment begins and ends.\n *\n * @param path - a route path (query already stripped by the caller)\n * @returns the segments in order, including empty leading/trailing/`//` segments\n * (the caller skips empties, matching the current behaviour)\n */\nexport function splitPathSegments(path: string): string[] {\n const segments: string[] = [];\n let start = 0;\n\n for (let i = 0; i < path.length; i += 1) {\n if (path.charCodeAt(i) !== SLASH) {\n continue;\n }\n\n segments.push(path.slice(start, i));\n start = i + 1;\n }\n\n segments.push(path.slice(start));\n\n return segments;\n}\n\n/**\n * Tokenizes a single path segment (already split on `/`).\n *\n * @param segment - one `/`-delimited segment of a route path\n * @returns the segment's tokens, or a typed grammar error\n */\nexport function parseSegment(segment: string): SegmentTokens | SegmentError {\n const length = segment.length;\n\n if (length === 0) {\n return { kind: \"static\", text: \"\" };\n }\n\n // ---- static segment (no leading marker) -------------------------------\n if (!isMarker(segment.charCodeAt(0))) {\n for (let i = 0; i < length; i += 1) {\n const code = segment.charCodeAt(i);\n\n // A `<`/`>` (a former `<re>` constraint or a stray delimiter) is no longer\n // grammar — M1 removed constraints. Reject with the constraint recipe.\n if (code === LT || code === GT) {\n return { error: \"constraint-removed\" };\n }\n\n // A marker glued after a static prefix is extracted as a param by build/meta\n // but compiled as a static literal by the trie (#1050) — reject it as fused.\n // A marker ENDING the segment (a static ending in `:`/`*` — `/a:`, `/a*`, F2)\n // is NOT fused: caught by `i + 1 < length` being false. Every other following\n // char is fused — including a `?` (`a:?`): that shape never reaches the\n // tokenizer through a real path (a `?` after a bare marker is not a valid\n // `:name?` form, so the query mask strips it before `/`-segmentation), so a\n // direct call correctly reports fused-marker. (`a<`/`a>` already returned\n // `constraint-removed` above, so no `<`-follows exception is needed here.)\n if (isMarker(code) && i + 1 < length) {\n return { error: \"fused-marker\" };\n }\n }\n\n // A trailing `?` is a former optional modifier; on a marker-less segment (no\n // param name) it is a modifier-with-no-name — name-less (#858/#1241, `/faq?`),\n // NOT `optional-removed` (there is no param to route to two siblings). The\n // backstop rejects it by the SAME rule: its `endsWith(\"?\")` fork routes the\n // segment to `extractParamName` → this tokenizer. Owning the `?` here (not\n // only in the marker branch) is what lets the gate and backstop agree on it —\n // otherwise the gate reads `faq?` as a valid static (#1324 §4).\n if (segment.charCodeAt(length - 1) === QUESTION) {\n return { error: \"name-less\" };\n }\n\n return { kind: \"static\", text: segment };\n }\n\n const splat = segment.charCodeAt(0) === STAR;\n\n // ---- name: up to the first `<`/`>` (former constraint delimiter, reserved —\n // В1.3) or `?` (former optional). A segment holds no `/`. -----------------\n let cursor = 1;\n\n while (cursor < length) {\n const code = segment.charCodeAt(cursor);\n\n if (code === LT || code === GT || code === QUESTION) {\n break;\n }\n\n cursor += 1;\n }\n\n const name = segment.slice(1, cursor);\n\n if (name.length === 0) {\n return { error: \"name-less\" }; // #858\n }\n\n if (isMarker(name.charCodeAt(name.length - 1))) {\n return { error: \"trailing-marker\" }; // #1324\n }\n\n // ---- former constraint / optional modifiers (removed in M1) ------------\n // The name scan stops at the first `<`/`>` or `?`. Either is a form removed\n // in M1: a `<re>` constraint (also a stray `<`/`>` — В1.3), or a `:x?`/`*x?`\n // optional. Only `?` is the optional; `<`/`>` are the constraint recipe.\n // Reject with the matching replacement recipe rather than tokenize it.\n if (cursor < length) {\n return segment.charCodeAt(cursor) === QUESTION\n ? { error: \"optional-removed\" }\n : { error: \"constraint-removed\" }; // LT or GT\n }\n\n return splat ? { kind: \"splat\", name } : { kind: \"param\", name };\n}\n\n/**\n * Returns the first per-segment grammar error in a path, or `undefined` if every\n * segment tokenizes cleanly.\n *\n * The **validation-facing** entry over the tokenizer: `route-tree`'s\n * `validateRoutePath` calls this instead of re-running its own split+parse loop,\n * so the gate and the matcher's own grammar cannot drift (#1324) and the loop\n * stays single-sourced here — the tokenizer primitives (`parseSegment`,\n * `splitPathSegments`) need not leak into the package's public surface. An empty\n * segment tokenizes as `static` (never an error), so leading/trailing/`//`\n * empties are skipped naturally.\n *\n * @param path - a route path (query already stripped by the caller)\n * @returns the first `SegmentErrorCode` (scanned left to right), or `undefined`\n */\nexport function findSegmentGrammarError(\n path: string,\n): SegmentErrorCode | undefined {\n for (const segment of splitPathSegments(path)) {\n const token = parseSegment(segment);\n\n if (\"error\" in token) {\n return token.error;\n }\n }\n\n return undefined;\n}\n\n/** A removed-form (M1) match, describing the offending segment and — for an\n * optional — the two sibling paths that replace it (path without the optional\n * segment + path with the param made required). The route-tree gate uses this to\n * build a route-contextual replacement recipe; the matcher backstop uses only the\n * error code (a shorter, path-free recipe). */\nexport type RemovedForm =\n | {\n readonly code: \"optional-removed\";\n readonly segment: string;\n readonly withoutSegment: string;\n readonly requiredForm: string;\n }\n | { readonly code: \"constraint-removed\"; readonly segment: string };\n\n/**\n * The rich (route-tree gate) view over the tokenizer for a removed form: finds\n * the first `optional-removed` / `constraint-removed` segment and, for an\n * optional, computes its two replacement sibling paths from the ACTUAL path\n * (dropping the segment → without-form; dropping the trailing `?` → required\n * form). Returns `undefined` if no removed form is present (the gate then uses\n * `findSegmentGrammarError` for a surviving grammar rejection).\n *\n * @param path - a route path (query already stripped by the caller)\n */\nexport function describeRemovedForm(path: string): RemovedForm | undefined {\n const segments = splitPathSegments(path);\n\n for (let i = 0; i < segments.length; i += 1) {\n const token = parseSegment(segments[i]);\n\n if (!(\"error\" in token)) {\n continue;\n }\n\n // First error wins (mirrors `findSegmentGrammarError`): describe it ONLY if\n // it is a removed form, else return undefined so the caller falls to the\n // surviving-code message — this keeps the gate's reason in lockstep with the\n // matcher backstop's first-error verdict.\n if (token.error === \"optional-removed\") {\n const segment = segments[i];\n const required = [...segments];\n\n // Drop the `?` optional modifier AND everything after it (the tokenizer\n // stopped the name at the first `?`, so it is the modifier). Using the `?`\n // index — not a blind `slice(0, -1)` — keeps the required sibling VALID for\n // a reverse/compound form whose `?` is not the last char: `:b?<x>` → `:b`\n // (not `:b?<x`), `:id??` → `:id` (not `:id?`). #1516\n required[i] = segment.slice(0, segment.indexOf(\"?\"));\n\n return {\n code: \"optional-removed\",\n segment,\n withoutSegment: segments.filter((_, j) => j !== i).join(\"/\"),\n requiredForm: required.join(\"/\"),\n };\n }\n\n return token.error === \"constraint-removed\"\n ? { code: \"constraint-removed\", segment: segments[i] }\n : undefined;\n }\n\n return undefined;\n}\n","/**\n * Route Parameter Metadata Extraction.\n *\n * Extracts parameter metadata from route path patterns without requiring\n * a full path-parser instance. Replaces parser.urlParams/queryParams/spatParams.\n *\n * @module buildParamMeta\n */\n\nimport { parseSegment, splitPathSegments } from \"./parseSegment\";\n\nimport type { ParamMeta } from \"./types\";\n\n/**\n * A query-param NAME may not contain `<`/`>` (#1242 §5.1) — a constraint\n * delimiter leaked into the query via a reverse-order modifier typo (`/a/:b?<c>`\n * parses the `?` as the query start, making `<c>` the query name). Consumed by\n * the route-tree gate and the `registerTree` backstop; relocated here from the\n * deleted `constraint-grammar.ts` when M1 removed constraints (query-param name\n * validation is a query concern, and this module owns query extraction).\n */\nexport const INVALID_QUERY_NAME_RGX = /[<>]/u;\n\nconst QUESTION = 0x3f; // ?\nconst SLASH = 0x2f; // /\nconst LT = 0x3c; // <\n\n/**\n * Locates the query separator `?` in a route path — the FIRST `?` whose tail is\n * non-empty and does not begin with `/`, `?`, or `<` (M1 §3.3). The 3-token\n * grammar leaves `?` a single role (there is no optional modifier and no\n * constraint body to hide one), so no length-preserving mask is needed. The three\n * excluded tails keep a REMOVED form in the path part, where `parseSegment`\n * rejects it with a recipe instead of mis-reading it as a query declaration:\n * - end-of-string (`/:id?`) and `/` (`/:id?/edit`) → a bare `:x?` optional;\n * - `?` (`/:id??tab`) → the leading `?` is the optional, the later `?` the query;\n * - `<` (`/a/:b?<x>`) → a reverse-order `:b?<x>` (optional then a former constraint).\n *\n * @param path - a route path\n * @returns the index of the query separator, or -1 if there is none\n */\nfunction findQuerySeparator(path: string): number {\n for (let i = 0; i < path.length; i += 1) {\n if (path.codePointAt(i) !== QUESTION) {\n continue;\n }\n\n // `next` is the code point after the `?`, or the `-1` sentinel at end-of-string.\n // The `-1` sentinel is the SOLE end-of-string guard — the former separate\n // `next !== undefined` conjunct was dead (the ternary bounds the index, so\n // `codePointAt` never returns `undefined`; the `!` is a type assertion, not a\n // runtime branch, so it keeps the scan at 100% coverage). Mirrors `#scanPath`.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- ternary-bounded in-range index; codePointAt is defined\n const next = i + 1 < path.length ? path.codePointAt(i + 1)! : -1;\n\n if (next !== -1 && next !== SLASH && next !== QUESTION && next !== LT) {\n return i;\n }\n }\n\n return -1;\n}\n\n/**\n * Builds parameter metadata from a route path pattern.\n *\n * Extracts URL parameters, query parameters, and splat parameters\n * from the path pattern string.\n *\n * @param path - Route path pattern (e.g., \"/users/:id/posts/:postId?q\")\n * @returns Parameter metadata object\n *\n * @example\n * ```typescript\n * buildParamMeta(\"/users/:id\")\n * // → { urlParams: [\"id\"], queryParams: [], spatParams: [], paramTypeMap: { id: \"url\" } }\n *\n * buildParamMeta(\"/search?q&page\")\n * // → { urlParams: [], queryParams: [\"q\", \"page\"], spatParams: [],\n * // paramTypeMap: { q: \"query\", page: \"query\" } }\n *\n * buildParamMeta(\"/files/*path\")\n * // → { urlParams: [\"path\"], queryParams: [], spatParams: [\"path\"],\n * // paramTypeMap: { path: \"url\" } }\n * ```\n */\n// Shared frozen sentinels for the common no-params case — avoid a fresh empty\n// array/object per route (#1009). ParamMeta fields are Readonly*; match/build\n// only read them, and computeCaches' Object.freeze on the arrays/object is a\n// no-op on an already-frozen shared instance.\nconst EMPTY_PARAM_NAMES: readonly string[] = Object.freeze([]);\nconst EMPTY_PARAM_TYPE_MAP: Readonly<Record<string, \"url\" | \"query\">> =\n Object.freeze({});\n\n// Whole-meta shared sentinel for the fully-static case: every collection is a\n// #1009 sentinel AND pathPattern degenerates to the input path itself (no query\n// to strip), so the wrapper carries zero per-route information. The RETAINING\n// caller (route-tree's computeCaches) swaps a matching fresh result for this\n// instance — buildParamMeta itself keeps returning fresh objects so the\n// validation gate can read the real pathPattern of arbitrary input paths.\n// `pathPattern` is \"\" here; the one stored-meta reader (`registerNode`) falls\n// back to `node.path` on identity match.\nexport const EMPTY_PARAM_META: ParamMeta = Object.freeze({\n urlParams: EMPTY_PARAM_NAMES,\n queryParams: EMPTY_PARAM_NAMES,\n spatParams: EMPTY_PARAM_NAMES,\n paramTypeMap: EMPTY_PARAM_TYPE_MAP,\n pathPattern: \"\",\n});\n\n/**\n * Extracts URL/splat params from a path's segments into the given accumulators\n * via the canonical `parseSegment` tokenizer. Split out of `buildParamMeta` so\n * the builder stays under the cognitive-complexity budget. A malformed segment\n * (token errors) or a `static` segment contributes nothing — a malformed route is\n * rejected downstream before it compiles, so its meta is moot.\n */\nfunction collectUrlParams(\n path: string,\n urlParams: string[],\n spatParams: string[],\n paramTypeMap: Record<string, \"url\" | \"query\">,\n): void {\n for (const segment of splitPathSegments(path)) {\n if (segment.length === 0) {\n continue;\n }\n\n const token = parseSegment(segment);\n\n if (\"error\" in token || token.kind === \"static\") {\n continue;\n }\n\n urlParams.push(token.name);\n paramTypeMap[token.name] = \"url\";\n\n if (token.kind === \"splat\") {\n spatParams.push(token.name);\n }\n }\n}\n\nexport function buildParamMeta(path: string): ParamMeta {\n const urlParams: string[] = [];\n const queryParams: string[] = [];\n const spatParams: string[] = [];\n const paramTypeMap: Record<string, \"url\" | \"query\"> = {};\n\n // Locate the real query separator (M1 §3.3: first `?` whose tail is not a\n // former optional/reverse form).\n const separator = findQuerySeparator(path);\n\n if (separator !== -1) {\n const queryString = path.slice(separator + 1);\n const params = queryString.split(\"&\");\n\n for (const param of params) {\n const paramName = param.trim();\n\n if (paramName.length > 0) {\n queryParams.push(paramName);\n paramTypeMap[paramName] = \"query\";\n }\n }\n\n path = path.slice(0, separator);\n }\n\n collectUrlParams(path, urlParams, spatParams, paramTypeMap);\n\n return shareEmptyCollections(\n urlParams,\n queryParams,\n spatParams,\n paramTypeMap,\n path,\n );\n}\n\n// #1009: swap each freshly-built empty collection for a shared frozen sentinel\n// — factored out of buildParamMeta so the hot builder stays under the cognitive-\n// complexity budget. match/build only read these (Readonly*), and computeCaches'\n// Object.freeze is a no-op on an already-frozen shared instance.\nfunction shareEmptyCollections(\n urlParams: string[],\n queryParams: string[],\n spatParams: string[],\n paramTypeMap: Record<string, \"url\" | \"query\">,\n pathPattern: string,\n): ParamMeta {\n return {\n urlParams: urlParams.length === 0 ? EMPTY_PARAM_NAMES : urlParams,\n queryParams: queryParams.length === 0 ? EMPTY_PARAM_NAMES : queryParams,\n spatParams: spatParams.length === 0 ? EMPTY_PARAM_NAMES : spatParams,\n paramTypeMap:\n urlParams.length === 0 && queryParams.length === 0\n ? EMPTY_PARAM_TYPE_MAP\n : paramTypeMap,\n pathPattern,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"buildParamMeta-DniluAIV.js","names":["LT","QUESTION","SLASH"],"sources":["../../src/internals.ts","../../src/engine/path-matcher/parseSegment.ts","../../src/engine/path-matcher/buildParamMeta.ts"],"sourcesContent":["import type { RouteTree } from \"./engine\";\nimport type { DependenciesStore } from \"./namespaces\";\nimport type { RoutesStore } from \"./namespaces/RoutesNamespace\";\nimport type { Router as RouterClass } from \"./Router\";\nimport type {\n DefaultDependencies,\n EventName,\n LoggerConfig,\n NavigationOptions,\n Options,\n Params,\n Plugin,\n Router as RouterInterface,\n RouterLogger,\n RouteTreeState,\n SerializedRouterState,\n SimpleState,\n State,\n TreeChangedEvent,\n Unsubscribe,\n EventMethodMap,\n PluginFactory,\n} from \"./types\";\nimport type { RouterValidator } from \"./types/RouterValidator\";\n\nexport interface RouterInternals<\n D extends DefaultDependencies = DefaultDependencies,\n> {\n readonly makeState: <P extends Params = Params>(\n name: string,\n params?: P,\n path?: string,\n meta?: Record<string, Record<string, \"url\" | \"query\">>,\n ) => State<P>;\n\n readonly forwardState: <P extends Params = Params>(\n routeName: string,\n routeParams: P,\n ) => SimpleState<P>;\n\n readonly buildStateResolved: (\n resolvedName: string,\n resolvedParams: Params,\n ) => RouteTreeState | undefined;\n\n readonly matchPath: <P extends Params = Params>(\n path: string,\n options?: Options,\n ) => State<P> | undefined;\n\n readonly getOptions: () => Options;\n\n readonly addEventListener: <E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n ) => Unsubscribe;\n\n /**\n * Route-tree mutation channel — internal access for the `getRoutesApi`\n * wrapper. A dedicated bridge is required because the public\n * `addEventListener<E extends EventName>` structurally rejects\n * `\"TREE_CHANGED\"` (it is not in the public `EventName` union), is strict on\n * duplicates, and exposes neither `emit` nor `listenerCount`.\n */\n readonly treeChanged: {\n readonly emit: (event: TreeChangedEvent) => void;\n readonly subscribe: (\n handler: (event: TreeChangedEvent) => void,\n ) => Unsubscribe;\n readonly listenerCount: () => number;\n /**\n * True while a `TREE_CHANGED` emit is on the stack — `getRoutesApi` reads it\n * to reject reentrant route-CRUD from a `subscribeChanges` handler (#1032).\n */\n readonly isEmitting: () => boolean;\n };\n\n readonly buildPath: (route: string, params?: Params) => string;\n\n readonly emitTransitionError: (error: Error) => void;\n\n /**\n * Emits `TRANSITION_SUCCESS` directly (no FSM transition) — used by\n * `getRoutesApi().replace()` to notify `router.subscribe` listeners when a\n * structural replace revalidates the active state (#950). Mirrors the success\n * emission `completeTransition` / `navigateToNotFound` perform.\n */\n readonly emitTransitionSuccess: (\n toState: State,\n fromState: State | undefined,\n opts?: NavigationOptions,\n ) => void;\n\n /**\n * Commits the not-found (`UNKNOWN_ROUTE`) state for `path` and emits\n * `TRANSITION_SUCCESS` — the `NavigationNamespace.navigateToNotFound`\n * primitive. `replace()` uses it when a structural replace drops the active\n * route, so subscribers are notified instead of the state silently clearing\n * (#950).\n */\n readonly navigateToNotFound: (path: string) => State;\n\n readonly start: (path: string) => Promise<State>;\n\n /**\n * Plugin-only navigation entry point — delegates to\n * `NavigationNamespace.navigateToState` (`getPluginApi(router).navigateToState`).\n * Hidden from `Router`/`Navigator` to keep the userland surface minimal;\n * see `core-types/src/api.ts` for usage docs.\n */\n readonly navigateToState: (\n state: State,\n options?: NavigationOptions,\n ) => Promise<State>;\n\n /* eslint-disable @typescript-eslint/no-explicit-any -- heterogeneous map: stores different InterceptorFn<M> types under different keys */\n readonly interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >;\n /* eslint-enable @typescript-eslint/no-explicit-any */\n\n readonly setRootPath: (rootPath: string) => void;\n readonly getRootPath: () => string;\n\n readonly getTree: () => RouteTree;\n\n readonly isDisposed: () => boolean;\n\n validator: RouterValidator | null;\n\n // Per-router logger instance (built from `options.logger` in the Router\n // constructor). The facade reads it as `getInternals(this).logger`; namespaces\n // receive it via their deps at wiring; plugins reach it through\n // `getPluginApi(router).logger`. Replaces the former process-global singleton\n // from the standalone `@real-router/logger` package (now folded into\n // `foundation/logger`), whose `configure()` leaked across routers (#724).\n readonly logger: RouterLogger;\n\n // Dependencies (issue #172)\n readonly dependenciesGetStore: () => DependenciesStore<D>;\n\n // Clone support (issue #173, consolidated #964). One accessor for the\n // source-side snapshot a clone carries over besides the route store, so a new\n // clone-relevant subsystem is wired in a single place instead of being spread\n // across separate methods.\n readonly getCloneState: () => {\n options: Options;\n dependencies: Record<string, unknown>;\n pluginFactories: PluginFactory<D>[];\n // Resolved logger config of the base router, so a clone can build its OWN\n // logger inheriting the base's level/callback. Frozen `options` do NOT carry\n // `logger` (stripped in the constructor), so `options` above can't convey it;\n // cloneRouter merges a per-request override (traceId) over this snapshot.\n loggerConfig: LoggerConfig;\n };\n\n // Consolidated route data store (issue #174 Phase 2)\n readonly routeGetStore: () => RoutesStore<D>;\n\n // Cross-namespace state (issue #174)\n readonly getStateName: () => string | undefined;\n readonly isTransitioning: () => boolean;\n readonly clearState: () => void;\n readonly setState: (state: State) => void;\n readonly routerExtensions: { keys: string[] }[];\n readonly contextClaimRecords: Set<string>;\n\n /**\n * One-shot hydration scratchpad populated by `hydrateRouter` immediately\n * before delegating to `router.start(parsed.path)` and cleared in the\n * matching `finally`. SSR loader plugins read this slot directly via\n * `getInternals(router).hydrationState` to short-circuit their own loader\n * call when the server-resolved namespace value is already present in the\n * parsed state (#596). `null` outside of an active `hydrateRouter`\n * invocation.\n */\n hydrationState: SerializedRouterState | null;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- existential type: stores RouterInternals for all Dependencies types\nconst internals = new WeakMap<object, RouterInternals<any>>();\n\nexport function getInternals<D extends DefaultDependencies>(\n router: RouterInterface<D>,\n): RouterInternals<D> {\n const ctx = internals.get(router);\n\n if (!ctx) {\n throw new TypeError(\n \"[real-router] Invalid router instance — not found in internals registry\",\n );\n }\n\n return ctx as RouterInternals<D>;\n}\n\nexport function registerInternals<D extends DefaultDependencies>(\n router: RouterClass<D>,\n ctx: RouterInternals<D>,\n): void {\n internals.set(router, ctx);\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument -- internal chain execution: type safety enforced at public API boundary (PluginApi.addInterceptor) */\nfunction executeInterceptorChain<T>(\n interceptors: ((next: (...args: any[]) => any, ...args: any[]) => any)[],\n original: (...args: any[]) => T,\n args: any[],\n): T {\n let chain = original as (...args: any[]) => any;\n\n for (const interceptor of interceptors) {\n const prev = chain;\n\n chain = (...chainArgs: any[]) => interceptor(prev, ...chainArgs);\n }\n\n return chain(...args) as T;\n}\n\n/**\n * Variadic interceptor wrapper — wraps a function of any arity, returning the\n * same callable type `T`. Use {@link createBinaryInterceptable} instead when the\n * wrapped method takes exactly two args and the caller needs the precise\n * `(a, b) => r` signature preserved (the variadic form widens args to `any[]`).\n */\nexport function createInterceptable<T extends (...args: any[]) => any>(\n name: string,\n original: T,\n interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >,\n): T {\n return ((...args: any[]) => {\n const chain = interceptors.get(name);\n\n if (!chain || chain.length === 0) {\n return original(...args);\n }\n\n return executeInterceptorChain(chain, original, args);\n }) as T;\n}\n\n/**\n * Two-argument interceptor wrapper — preserves the exact `(a: A, b: B) => R`\n * signature, which the variadic {@link createInterceptable} cannot express\n * (it widens args to `any[]`). Used for the binary interceptable methods\n * `forwardState(routeName, routeParams)` and `buildPath(route, params)`.\n */\nexport function createBinaryInterceptable<A, B, R>(\n name: string,\n original: (a: A, b: B) => R,\n interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >,\n): (a: A, b: B) => R {\n return (arg1: A, arg2: B) => {\n const chain = interceptors.get(name);\n\n if (!chain || chain.length === 0) {\n return original(arg1, arg2);\n }\n\n return executeInterceptorChain(chain, original, [arg1, arg2]);\n };\n}\n/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */\n","/**\n * Canonical route-segment tokenizer.\n *\n * The single owner of \"where does a name / marker / constraint end\" for ONE\n * path segment (post-`/`-split). It replaces the five name-boundary compositions\n * of `PARAM_NAME_PATTERN` currently spread across `buildParamMeta` (L1),\n * `registration` (L2 build + L3 trie), and `route-tree`'s validation gate (L4),\n * so those layers can never disagree on a boundary (the gate↔backstop drift\n * class — #858 / #1050 / #1150 / #1311 / #1149 / #1324).\n *\n * A single left-to-right `charCodeAt` scan produces either a token tuple or a\n * typed error. Grammar — **3 tokens only** (`static | :param | *splat`); the\n * grammar has no optional `:x?` or `<re>` constraint forms. Any `<`/`>` or a\n * post-name `?` in the path is a *registration error* carrying a replacement\n * recipe (`optional-removed` / `constraint-removed`), not a token:\n * 1. Leading `:`/`*` → param/splat; otherwise `static` (a marker glued *after* a\n * static prefix ⇒ `fused-marker`; any `<`/`>` (a former constraint) ⇒\n * `constraint-removed`; a trailing `?` on a marker-less segment ⇒ `name-less`\n * — the modifier has no param name, #1241 / `/faq?`).\n * 2. name = any char except `<`/`?` (no `/` remains inside a segment); a name\n * ending in a bare `:`/`*` ⇒ `trailing-marker` (#1324). A *mid* marker stays\n * a name char — `:a:b` → name `a:b`, preserved.\n * 3. empty name ⇒ `name-less` (#858).\n * 4. a `<` after the name (a former `<re>` constraint) ⇒ `constraint-removed`.\n * 5. a post-name `?` (a former optional modifier, on `:param` or `*splat`) ⇒\n * `optional-removed`.\n *\n * @module parseSegment\n */\n\n/* eslint-disable unicorn/prefer-code-point, unicorn/prefer-includes-over-repeated-comparisons, sonarjs/cognitive-complexity -- charCodeAt code-unit scan + a single inlined branchy pass are this RFC's char-scan perf basis (§9); the same deliberate choices as registration/trie.ts hasNonAsciiSegment (#1285) and SegmentMatcher's inlined #traverseFrom. A `[LT,GT,QUESTION].includes(code)` boundary check would allocate an array literal per scanned char. Markers compared are ASCII (`:` `*` `<` `>` `?`, < 0x80). */\n\n/** A successfully tokenized segment (3-token grammar: `static | :param | *splat`). */\nexport type SegmentTokens =\n | { readonly kind: \"static\"; readonly text: string }\n | { readonly kind: \"param\"; readonly name: string }\n | { readonly kind: \"splat\"; readonly name: string };\n\n/** Grammar-shape rejections, each mirroring a registration guard. */\nexport type SegmentErrorCode =\n | \"name-less\" // #858 — a marker with no name\n | \"trailing-marker\" // #1324 — a param name ending in a bare `:`/`*`\n | \"fused-marker\" // #1050 — a marker glued after a static prefix\n | \"optional-removed\" // M1 — a `:x?`/`*x?` optional modifier (removed; two sibling routes)\n | \"constraint-removed\"; // M1 — a `<re>` constraint or stray `<`/`>` (removed; validate in a guard)\n\nexport interface SegmentError {\n readonly error: SegmentErrorCode;\n}\n\nconst COLON = 58; // :\nconst STAR = 42; // *\nconst LT = 60; // <\nconst GT = 62; // >\nconst QUESTION = 63; // ?\nconst SLASH = 47; // /\n\nconst isMarker = (code: number): boolean => code === COLON || code === STAR;\n\n/**\n * Splits a path into its `/`-delimited segments. A plain `/`-split (M1): the\n * 3-token grammar has no `<...>` constraint whose body could legally contain a\n * `/`, so no constraint-awareness is needed — a stray `<`/`>` is a\n * `constraint-removed` error, caught per segment by `parseSegment`. This is the\n * **segmentation** half of the path-grammar unification: `parseSegment` owns the\n * per-segment grammar, `splitPathSegments` owns where a segment begins and ends.\n *\n * @param path - a route path (query already stripped by the caller)\n * @returns the segments in order, including empty leading/trailing/`//` segments\n * (the caller skips empties, matching the current behaviour)\n */\nexport function splitPathSegments(path: string): string[] {\n const segments: string[] = [];\n let start = 0;\n\n for (let i = 0; i < path.length; i += 1) {\n if (path.charCodeAt(i) !== SLASH) {\n continue;\n }\n\n segments.push(path.slice(start, i));\n start = i + 1;\n }\n\n segments.push(path.slice(start));\n\n return segments;\n}\n\n/**\n * Tokenizes a single path segment (already split on `/`).\n *\n * @param segment - one `/`-delimited segment of a route path\n * @returns the segment's tokens, or a typed grammar error\n */\nexport function parseSegment(segment: string): SegmentTokens | SegmentError {\n const length = segment.length;\n\n if (length === 0) {\n return { kind: \"static\", text: \"\" };\n }\n\n // ---- static segment (no leading marker) -------------------------------\n if (!isMarker(segment.charCodeAt(0))) {\n for (let i = 0; i < length; i += 1) {\n const code = segment.charCodeAt(i);\n\n // A `<`/`>` (a former `<re>` constraint or a stray delimiter) is no longer\n // grammar — M1 removed constraints. Reject with the constraint recipe.\n if (code === LT || code === GT) {\n return { error: \"constraint-removed\" };\n }\n\n // A marker glued after a static prefix is extracted as a param by build/meta\n // but compiled as a static literal by the trie (#1050) — reject it as fused.\n // A marker ENDING the segment (a static ending in `:`/`*` — `/a:`, `/a*`, F2)\n // is NOT fused: caught by `i + 1 < length` being false. Every other following\n // char is fused — including a `?` (`a:?`): that shape never reaches the\n // tokenizer through a real path (a `?` after a bare marker is not a valid\n // `:name?` form, so the query mask strips it before `/`-segmentation), so a\n // direct call correctly reports fused-marker. (`a<`/`a>` already returned\n // `constraint-removed` above, so no `<`-follows exception is needed here.)\n if (isMarker(code) && i + 1 < length) {\n return { error: \"fused-marker\" };\n }\n }\n\n // A trailing `?` is a former optional modifier; on a marker-less segment (no\n // param name) it is a modifier-with-no-name — name-less (#858/#1241, `/faq?`),\n // NOT `optional-removed` (there is no param to route to two siblings). The\n // backstop rejects it by the SAME rule: its `endsWith(\"?\")` fork routes the\n // segment to `extractParamName` → this tokenizer. Owning the `?` here (not\n // only in the marker branch) is what lets the gate and backstop agree on it —\n // otherwise the gate reads `faq?` as a valid static (#1324 §4).\n if (segment.charCodeAt(length - 1) === QUESTION) {\n return { error: \"name-less\" };\n }\n\n return { kind: \"static\", text: segment };\n }\n\n const splat = segment.charCodeAt(0) === STAR;\n\n // ---- name: up to the first `<`/`>` (former constraint delimiter, reserved —\n // В1.3) or `?` (former optional). A segment holds no `/`. -----------------\n let cursor = 1;\n\n while (cursor < length) {\n const code = segment.charCodeAt(cursor);\n\n if (code === LT || code === GT || code === QUESTION) {\n break;\n }\n\n cursor += 1;\n }\n\n const name = segment.slice(1, cursor);\n\n if (name.length === 0) {\n return { error: \"name-less\" }; // #858\n }\n\n if (isMarker(name.charCodeAt(name.length - 1))) {\n return { error: \"trailing-marker\" }; // #1324\n }\n\n // ---- former constraint / optional modifiers (removed in M1) ------------\n // The name scan stops at the first `<`/`>` or `?`. Either is a form removed\n // in M1: a `<re>` constraint (also a stray `<`/`>` — В1.3), or a `:x?`/`*x?`\n // optional. Only `?` is the optional; `<`/`>` are the constraint recipe.\n // Reject with the matching replacement recipe rather than tokenize it.\n if (cursor < length) {\n return segment.charCodeAt(cursor) === QUESTION\n ? { error: \"optional-removed\" }\n : { error: \"constraint-removed\" }; // LT or GT\n }\n\n return splat ? { kind: \"splat\", name } : { kind: \"param\", name };\n}\n\n/**\n * Returns the first per-segment grammar error in a path, or `undefined` if every\n * segment tokenizes cleanly.\n *\n * The **validation-facing** entry over the tokenizer: `route-tree`'s\n * `validateRoutePath` calls this instead of re-running its own split+parse loop,\n * so the gate and the matcher's own grammar cannot drift (#1324) and the loop\n * stays single-sourced here — the tokenizer primitives (`parseSegment`,\n * `splitPathSegments`) need not leak into the package's public surface. An empty\n * segment tokenizes as `static` (never an error), so leading/trailing/`//`\n * empties are skipped naturally.\n *\n * @param path - a route path (query already stripped by the caller)\n * @returns the first `SegmentErrorCode` (scanned left to right), or `undefined`\n */\nexport function findSegmentGrammarError(\n path: string,\n): SegmentErrorCode | undefined {\n for (const segment of splitPathSegments(path)) {\n const token = parseSegment(segment);\n\n if (\"error\" in token) {\n return token.error;\n }\n }\n\n return undefined;\n}\n\n/** A removed-form (M1) match, describing the offending segment and — for an\n * optional — the two sibling paths that replace it (path without the optional\n * segment + path with the param made required). The route-tree gate uses this to\n * build a route-contextual replacement recipe; the matcher backstop uses only the\n * error code (a shorter, path-free recipe). */\nexport type RemovedForm =\n | {\n readonly code: \"optional-removed\";\n readonly segment: string;\n readonly withoutSegment: string;\n readonly requiredForm: string;\n }\n | { readonly code: \"constraint-removed\"; readonly segment: string };\n\n/**\n * The rich (route-tree gate) view over the tokenizer for a removed form: finds\n * the first `optional-removed` / `constraint-removed` segment and, for an\n * optional, computes its two replacement sibling paths from the ACTUAL path\n * (dropping the segment → without-form; dropping the trailing `?` → required\n * form). Returns `undefined` if no removed form is present (the gate then uses\n * `findSegmentGrammarError` for a surviving grammar rejection).\n *\n * @param path - a route path (query already stripped by the caller)\n */\nexport function describeRemovedForm(path: string): RemovedForm | undefined {\n const segments = splitPathSegments(path);\n\n for (let i = 0; i < segments.length; i += 1) {\n const token = parseSegment(segments[i]);\n\n if (!(\"error\" in token)) {\n continue;\n }\n\n // First error wins (mirrors `findSegmentGrammarError`): describe it ONLY if\n // it is a removed form, else return undefined so the caller falls to the\n // surviving-code message — this keeps the gate's reason in lockstep with the\n // matcher backstop's first-error verdict.\n if (token.error === \"optional-removed\") {\n const segment = segments[i];\n const required = [...segments];\n\n // Drop the `?` optional modifier AND everything after it (the tokenizer\n // stopped the name at the first `?`, so it is the modifier). Using the `?`\n // index — not a blind `slice(0, -1)` — keeps the required sibling VALID for\n // a reverse/compound form whose `?` is not the last char: `:b?<x>` → `:b`\n // (not `:b?<x`), `:id??` → `:id` (not `:id?`). #1516\n required[i] = segment.slice(0, segment.indexOf(\"?\"));\n\n return {\n code: \"optional-removed\",\n segment,\n withoutSegment: segments.filter((_, j) => j !== i).join(\"/\"),\n requiredForm: required.join(\"/\"),\n };\n }\n\n return token.error === \"constraint-removed\"\n ? { code: \"constraint-removed\", segment: segments[i] }\n : undefined;\n }\n\n return undefined;\n}\n","/**\n * Route Parameter Metadata Extraction.\n *\n * Extracts parameter metadata from route path patterns without requiring\n * a full path-parser instance. Replaces parser.urlParams/queryParams/spatParams.\n *\n * @module buildParamMeta\n */\n\nimport { parseSegment, splitPathSegments } from \"./parseSegment\";\n\nimport type { ParamMeta } from \"./types\";\n\n/**\n * A query-param NAME may not contain `<`/`>` (#1242 §5.1) — a constraint\n * delimiter leaked into the query via a reverse-order modifier typo (`/a/:b?<c>`\n * parses the `?` as the query start, making `<c>` the query name). Consumed by\n * the route-tree gate and the `registerTree` backstop; relocated here from the\n * deleted `constraint-grammar.ts` when M1 removed constraints (query-param name\n * validation is a query concern, and this module owns query extraction).\n */\nexport const INVALID_QUERY_NAME_RGX = /[<>]/u;\n\nconst QUESTION = 0x3f; // ?\nconst SLASH = 0x2f; // /\nconst LT = 0x3c; // <\n\n/**\n * Locates the query separator `?` in a route path — the FIRST `?` whose tail is\n * non-empty and does not begin with `/`, `?`, or `<` (M1 §3.3). The 3-token\n * grammar leaves `?` a single role (there is no optional modifier and no\n * constraint body to hide one), so no length-preserving mask is needed. The three\n * excluded tails keep a REMOVED form in the path part, where `parseSegment`\n * rejects it with a recipe instead of mis-reading it as a query declaration:\n * - end-of-string (`/:id?`) and `/` (`/:id?/edit`) → a bare `:x?` optional;\n * - `?` (`/:id??tab`) → the leading `?` is the optional, the later `?` the query;\n * - `<` (`/a/:b?<x>`) → a reverse-order `:b?<x>` (optional then a former constraint).\n *\n * @param path - a route path\n * @returns the index of the query separator, or -1 if there is none\n */\nfunction findQuerySeparator(path: string): number {\n for (let i = 0; i < path.length; i += 1) {\n if (path.codePointAt(i) !== QUESTION) {\n continue;\n }\n\n // `next` is the code point after the `?`, or the `-1` sentinel at end-of-string.\n // The `-1` sentinel is the SOLE end-of-string guard — the former separate\n // `next !== undefined` conjunct was dead (the ternary bounds the index, so\n // `codePointAt` never returns `undefined`; the `!` is a type assertion, not a\n // runtime branch, so it keeps the scan at 100% coverage). Mirrors `#scanPath`.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- ternary-bounded in-range index; codePointAt is defined\n const next = i + 1 < path.length ? path.codePointAt(i + 1)! : -1;\n\n if (next !== -1 && next !== SLASH && next !== QUESTION && next !== LT) {\n return i;\n }\n }\n\n return -1;\n}\n\n/**\n * Builds parameter metadata from a route path pattern.\n *\n * Extracts URL parameters, query parameters, and splat parameters\n * from the path pattern string.\n *\n * @param path - Route path pattern (e.g., \"/users/:id/posts/:postId?q\")\n * @returns Parameter metadata object\n *\n * @example\n * ```typescript\n * buildParamMeta(\"/users/:id\")\n * // → { urlParams: [\"id\"], queryParams: [], spatParams: [], paramTypeMap: { id: \"url\" } }\n *\n * buildParamMeta(\"/search?q&page\")\n * // → { urlParams: [], queryParams: [\"q\", \"page\"], spatParams: [],\n * // paramTypeMap: { q: \"query\", page: \"query\" } }\n *\n * buildParamMeta(\"/files/*path\")\n * // → { urlParams: [\"path\"], queryParams: [], spatParams: [\"path\"],\n * // paramTypeMap: { path: \"url\" } }\n * ```\n */\n// Shared frozen sentinels for the common no-params case — avoid a fresh empty\n// array/object per route (#1009). ParamMeta fields are Readonly*; match/build\n// only read them, and computeCaches' Object.freeze on the arrays/object is a\n// no-op on an already-frozen shared instance.\nconst EMPTY_PARAM_NAMES: readonly string[] = Object.freeze([]);\nconst EMPTY_PARAM_TYPE_MAP: Readonly<Record<string, \"url\" | \"query\">> =\n Object.freeze({});\n\n// Whole-meta shared sentinel for the fully-static case: every collection is a\n// #1009 sentinel AND pathPattern degenerates to the input path itself (no query\n// to strip), so the wrapper carries zero per-route information. The RETAINING\n// caller (route-tree's computeCaches) swaps a matching fresh result for this\n// instance — buildParamMeta itself keeps returning fresh objects so the\n// validation gate can read the real pathPattern of arbitrary input paths.\n// `pathPattern` is \"\" here; the one stored-meta reader (`registerNode`) falls\n// back to `node.path` on identity match.\nexport const EMPTY_PARAM_META: ParamMeta = Object.freeze({\n urlParams: EMPTY_PARAM_NAMES,\n queryParams: EMPTY_PARAM_NAMES,\n spatParams: EMPTY_PARAM_NAMES,\n paramTypeMap: EMPTY_PARAM_TYPE_MAP,\n pathPattern: \"\",\n});\n\n/**\n * Extracts URL/splat params from a path's segments into the given accumulators\n * via the canonical `parseSegment` tokenizer. Split out of `buildParamMeta` so\n * the builder stays under the cognitive-complexity budget. A malformed segment\n * (token errors) or a `static` segment contributes nothing — a malformed route is\n * rejected downstream before it compiles, so its meta is moot.\n */\nfunction collectUrlParams(\n path: string,\n urlParams: string[],\n spatParams: string[],\n paramTypeMap: Record<string, \"url\" | \"query\">,\n): void {\n for (const segment of splitPathSegments(path)) {\n if (segment.length === 0) {\n continue;\n }\n\n const token = parseSegment(segment);\n\n if (\"error\" in token || token.kind === \"static\") {\n continue;\n }\n\n urlParams.push(token.name);\n paramTypeMap[token.name] = \"url\";\n\n if (token.kind === \"splat\") {\n spatParams.push(token.name);\n }\n }\n}\n\nexport function buildParamMeta(path: string): ParamMeta {\n const urlParams: string[] = [];\n const queryParams: string[] = [];\n const spatParams: string[] = [];\n const paramTypeMap: Record<string, \"url\" | \"query\"> = {};\n\n // Locate the real query separator (M1 §3.3: first `?` whose tail is not a\n // former optional/reverse form).\n const separator = findQuerySeparator(path);\n\n if (separator !== -1) {\n const queryString = path.slice(separator + 1);\n const params = queryString.split(\"&\");\n\n for (const param of params) {\n const paramName = param.trim();\n\n if (paramName.length > 0) {\n queryParams.push(paramName);\n paramTypeMap[paramName] = \"query\";\n }\n }\n\n path = path.slice(0, separator);\n }\n\n collectUrlParams(path, urlParams, spatParams, paramTypeMap);\n\n return shareEmptyCollections(\n urlParams,\n queryParams,\n spatParams,\n paramTypeMap,\n path,\n );\n}\n\n// #1009: swap each freshly-built empty collection for a shared frozen sentinel\n// — factored out of buildParamMeta so the hot builder stays under the cognitive-\n// complexity budget. match/build only read these (Readonly*), and computeCaches'\n// Object.freeze is a no-op on an already-frozen shared instance.\nfunction shareEmptyCollections(\n urlParams: string[],\n queryParams: string[],\n spatParams: string[],\n paramTypeMap: Record<string, \"url\" | \"query\">,\n pathPattern: string,\n): ParamMeta {\n return {\n urlParams: urlParams.length === 0 ? EMPTY_PARAM_NAMES : urlParams,\n queryParams: queryParams.length === 0 ? EMPTY_PARAM_NAMES : queryParams,\n spatParams: spatParams.length === 0 ? EMPTY_PARAM_NAMES : spatParams,\n paramTypeMap:\n urlParams.length === 0 && queryParams.length === 0\n ? EMPTY_PARAM_TYPE_MAP\n : paramTypeMap,\n pathPattern,\n };\n}\n"],"mappings":"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"}
|