@real-router/core 0.82.0 → 0.82.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Router-Cp-yrK2y.js +2 -0
- package/dist/cjs/Router-Cp-yrK2y.js.map +1 -0
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/helpers.js +1 -1
- package/dist/cjs/helpers.js.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +19 -0
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +1 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +1 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +1 -1
- package/dist/cjs/pipeline/canonicalize.js +1 -1
- package/dist/cjs/pipeline/canonicalize.js.map +1 -1
- package/dist/cjs/pipeline/materialize.js +1 -1
- package/dist/cjs/pipeline/materialize.js.map +1 -1
- package/dist/cjs/pipeline/port.d.ts +8 -0
- package/dist/cjs/pipeline/port.d.ts.map +1 -1
- package/dist/esm/Router-CREhSnNo.mjs +2 -0
- package/dist/esm/Router-CREhSnNo.mjs.map +1 -0
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +19 -0
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts.map +1 -1
- package/dist/esm/pipeline/port.d.mts +8 -0
- package/dist/esm/pipeline/port.d.mts.map +1 -1
- package/package.json +1 -1
- package/dist/cjs/Router-CCmcEi-S.js +0 -2
- package/dist/cjs/Router-CCmcEi-S.js.map +0 -1
- package/dist/esm/Router-DEpNuwBW.mjs +0 -2
- package/dist/esm/Router-DEpNuwBW.mjs.map +0 -1
package/dist/cjs/api.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./buildParamMeta-0m11OIJS.js"),t=require("./Router-CCmcEi-S.js");function n(e){if(e())throw new t.n(t.k.ROUTER_DISPOSED)}function r(e){if(e())throw new t.n(t.k.REENTRANT_TREE_MUTATION)}const i=new WeakMap;function a(r){let a=i.get(r);if(a)return a;let o=e.u(r),s={makeState:(t,n,r,i)=>(e.f(o,`makeState`,t,n),o.validator?.state.validateMakeStateArgs(t,n,i),o.makeState(t,n,r,i)),forwardState:(e,t,n)=>(o.validator?.routes.validateStateBuilderArgs(e,t,`forwardState`),o.forwardState(e,t,n)),matchPath:e=>(o.validator?.routes.validateMatchPathArgs(e),o.matchPath(e,o.getOptions())),navigateToState:(e,t)=>(n(o.isDisposed),o.validator?.navigation.validateNavigateToStateArgs(e),t!==void 0&&o.validator?.navigation.validateNavigationOptions(t,`navigateToState`),o.navigateToState(e,t)),setRootPath:e=>{n(o.isDisposed),o.validator?.routes.validateSetRootPathArgs(e),o.setRootPath(e)},getRootPath:o.getRootPath,addEventListener:(e,t)=>(n(o.isDisposed),o.validator?.eventBus.validateListenerArgs(e,t),o.addEventListener(e,t)),buildNavigationState:(n,r={},i={})=>{e.f(o,`buildNavigationState`,n,r),o.validator?.routes.validateStateBuilderArgs(n,r,`buildNavigationState`);let a=t.C(o.port(),n,r,i,{diagnoseUndeclared:!0});if(o.buildStateResolved(a.name,a.path))return t.x(a,{path:t.S(a,o.port())})},getOptions:o.getOptions,getTree:o.getTree,addInterceptor:(e,t)=>{n(o.isDisposed),o.validator?.plugins.validateAddInterceptorArgs(e,t);let r=o.interceptors.get(e);r||(r=[],o.interceptors.set(e,r)),r.push(t);let i=!1;return()=>{i||(i=!0,r.splice(r.indexOf(t),1))}},getRouteConfig:e=>{let t=o.routeGetStore();if(t.matcher.hasRoute(e))return t.routeCustomFields[e]},extendRouter:e=>{n(o.isDisposed);let i=Object.keys(e);for(let e of i)if(e in r)throw new t.n(t.k.PLUGIN_CONFLICT,{message:`Cannot extend router: property "${e}" already exists`});for(let t of i)r[t]=e[t];let a={keys:i};o.routerExtensions.push(a);let s=!1;return()=>{if(s)return;s=!0;for(let e of a.keys)delete r[e];let e=o.routerExtensions.indexOf(a);e!==-1&&o.routerExtensions.splice(e,1)}},emitTransitionError:e=>{n(o.isDisposed),o.emitTransitionError(e)},claimContextNamespace:e=>{if(n(o.isDisposed),typeof e!=`string`||e===``)throw TypeError(`[claimContextNamespace] namespace must be a non-empty string, got ${typeof e==`string`?`an empty string`:typeof e}`);if(o.contextClaimRecords.has(e))throw new t.n(t.k.CONTEXT_NAMESPACE_ALREADY_CLAIMED,{message:`Cannot claim context namespace: "${e}" is already claimed by another plugin`});return o.contextClaimRecords.add(e),{write(t,n){e===`__proto__`?Object.defineProperty(t.context,e,{value:n,writable:!0,enumerable:!0,configurable:!0}):t.context[e]=n},release(){o.contextClaimRecords.delete(e)}}}};return i.set(r,s),s}function o(e,t,n,r){if(t){let n=t===e,i=t.startsWith(`${e}.`);if(n||i){let i=n?``:` (current: "${t}")`;return r.warn(`router.removeRoute`,`Cannot remove route "${e}" — it is currently active${i}. Navigate away first.`),!1}}return n&&r.warn(`router.removeRoute`,`Route "${e}" removed while navigation is in progress. This may cause unexpected behavior.`),!0}function s(e,t){return e?(t.error(`router.clearRoutes`,`Cannot clear routes while navigation is in progress. Wait for navigation to complete.`),!1):!0}const c=Object.freeze({replace:!0,revalidate:!0});function l(e,n,r,i){let a=t=>t===e||t.startsWith(`${e}.`);t.y(n.decoders,a),t.y(n.encoders,a),t.y(n.defaultParams,a),t.y(n.defaultSearch,a),t.y(n.forwardMap,a),t.y(n.forwardFnMap,a),t.y(r,a),t.y(n.forwardMap,e=>a(n.forwardMap[e]));let[o,s]=i.getFactories();for(let e of Object.keys(s))a(e)&&i.clearCanActivate(e,`both`);for(let e of Object.keys(o))a(e)&&i.clearCanDeactivate(e,`both`)}function u(e,t,n,r){let i=n.forwardFnMap[t],a=n.forwardMap[t];i===void 0?a!==void 0&&(e.forwardTo=a):e.forwardTo=i,t in n.defaultParams&&(e.defaultParams=n.defaultParams[t]),t in n.defaultSearch&&(e.defaultSearch=n.defaultSearch[t]),t in n.decoders&&(e.decodeParams=n.decoders[t]),t in n.encoders&&(e.encodeParams=n.encoders[t]);let[o,s]=r;return t in s&&(e.canActivate=s[t]),t in o&&(e.canDeactivate=o[t]),e}function d(e,t,n,r){let i={name:e.name,path:e.path};return u(i,t,n,r),e.children&&(i.children=e.children.map(e=>d(e,`${t}.${e.name}`,n,r))),i}function f(e,t,n,r){let i={name:e,path:t};return u(i,e,n,r),Object.freeze(i)}function p(e,t){let n=new Map,r=e.lifecycleNamespace.getFactories(),i=(a,o)=>{for(let s of a){let a=o?`${o}.${s.name}`:s.name;t(a)&&n.set(a,f(a,s.path,e.config,r)),s.children&&i(s.children,a)}};return i(e.definitions,``),n}function m(e,t){let n=`${t}.`,r=p(e,e=>e===t||e.startsWith(n));return Object.freeze([...r.values()])}function h(e,t,n){let r=n.lifecycleNamespace.getFactories(),i=[],a=(e,t)=>{for(let o of e){let e=t?`${t}.${o.name}`:o.name;i.push(f(e,o.path,n.config,r)),o.children&&a(o.children,e)}};return a(e,t??``),Object.freeze(i)}function g(e,t){let n=[],r=[];for(let[r,i]of e)t.has(r)||n.push(i);for(let[n,i]of t)e.has(n)||r.push(i);return{removed:Object.freeze(n),added:Object.freeze(r)}}function _(e){let t={};return e.forwardTo!==void 0&&(t.forwardTo=e.forwardTo),e.defaultParams!==void 0&&(t.defaultParams=e.defaultParams),e.defaultSearch!==void 0&&(t.defaultSearch=e.defaultSearch),e.encodeParams!==void 0&&(t.encodeParams=e.encodeParams),e.decodeParams!==void 0&&(t.decodeParams=e.decodeParams),Object.freeze(t)}function v(e,n,r,i){t.a(e,n,r);let a=t.u(e,n,r,i);t._(a.matcher,a.config,`addRoute`),e.lifecycleNamespace.preflightHandlerLimit(a.pendingCanActivate.keys(),a.pendingCanDeactivate.keys(),!1),t.i(e,a)}function y(e,t,n){e.setState(t),e.emitTransitionSuccess(t,n,c)}function b(e,n,r,i,a){t.c(n,`addRoute`),t.o(n,``,`addRoute`),t.s(n,``,`addRoute`);let o=t.d(n,e.rootPath,e.matcherOptions,r.logger);t._(o.matcher,o.config,`addRoute`),e.lifecycleNamespace.preflightHandlerLimit(o.pendingCanActivate.keys(),o.pendingCanDeactivate.keys(),!0);let s=t.m(o,e.depsStore);if(e.lifecycleNamespace.clearDefinitionGuards(),t.i(e,o,s),a?.(),i!==void 0){let n=r.matchPath(i.path,r.getOptions());if(n)if(n.name===i.name)y(r,{...n,context:i.context,transition:i.transition},i);else{let{toDeactivate:a,toActivate:o}=t.r(n,i,r.getMetaForState);e.lifecycleNamespace.canNavigateTo(a,o,n,i)?y(r,{...n,transition:i.transition},i):r.navigateToNotFound(i.path)}else r.navigateToNotFound(i.path)}}function x(e,n){let r=e.definitions;return t.b(r,n)?(l(n,e.config,e.routeCustomFields,e.lifecycleNamespace),t.p(e,r),!0):!1}function S(e,n){let r=e.matcher.getSegmentsByName(n);if(!r)return;let i=t.w(r.at(-1)),a=e.lifecycleNamespace.getFactories();return d(i,n,e.config,a)}const C=new WeakMap;function w(i){let a=C.get(i);if(a)return a;let c=e.u(i),l=c.routeGetStore(),u=e=>{c.treeChanged.emit(e)},d={add:(e,i)=>{n(c.isDisposed),r(c.treeChanged.isEmitting);let a=Array.isArray(e)?e:[e],o=i?.parent;if(t.E(a,c.validator),o!==void 0&&c.validator?.routes.validateParentOption(o,l.tree),c.validator?.routes.throwIfInternalRouteInArray(a,`addRoute`),c.validator?.routes.validateAddRouteArgs(a),c.validator?.routes.validateRoutes(a,l,o),v(l,a,o,c.logger),c.treeChanged.listenerCount()>0){let e=h(a,o,l);u(o===void 0?{op:`add`,added:e}:{op:`add`,added:e,parent:o})}},remove:e=>{if(n(c.isDisposed),r(c.treeChanged.isEmitting),c.validator?.routes.validateRemoveRouteArgs(e),c.validator?.routes.throwIfInternalRoute(e,`removeRoute`),t.l(e,`removeRoute`),!o(e,c.getStateName(),c.isTransitioning(),c.logger))return;let i=c.treeChanged.listenerCount()>0?m(l,e):void 0;if(!x(l,e)){c.logger.warn(`router.removeRoute`,`Route "${e}" not found. No changes made.`);return}i!==void 0&&u({op:`remove`,name:e,removedSubtree:i})},update:(e,i)=>{if(n(c.isDisposed),r(c.treeChanged.isEmitting),c.validator?.routes.validateUpdateRouteBasicArgs(e,i),c.validator?.routes.throwIfInternalRoute(e,`updateRoute`),t.l(e,`updateRoute`),c.validator?.routes.validateUpdateRoutePropertyTypes(e,i),c.isTransitioning()&&c.logger.error(`router.updateRoute`,`Updating route "${e}" while navigation is in progress. This may cause unexpected behavior.`),c.validator?.routes.validateUpdateRoute(e,i,l),!l.matcher.hasRoute(e))return;let a=l.lifecycleNamespace,o=t.f(l,a,e,i);if(c.treeChanged.listenerCount()>0){let t=_(o);Object.keys(t).length>0&&u({op:`update`,name:e,patch:t})}},clear:()=>{if(n(c.isDisposed),r(c.treeChanged.isEmitting),!s(c.isTransitioning(),c.logger))return;let e=c.treeChanged.listenerCount()>0?Object.freeze([...p(l,()=>!0).values()]):void 0;t.h(l),l.lifecycleNamespace.clearAll(),c.clearState(),e!==void 0&&u({op:`clear`,removed:e})},has:e=>(c.validator?.routes.validateRouteName(e,`hasRoute`),l.matcher.hasRoute(e)),get:e=>(c.validator?.routes.validateRouteName(e,`getRoute`),S(l,e)),replace:e=>{n(c.isDisposed),r(c.treeChanged.isEmitting);let a=Array.isArray(e)?e:[e];if(!s(c.isTransitioning(),c.logger))return;t.E(a,c.validator),c.validator?.routes.throwIfInternalRouteInArray(a,`replaceRoutes`),c.validator?.routes.validateAddRouteArgs(a),c.validator?.routes.validateRoutes(a,l);let o=i.getState(),d=c.treeChanged.listenerCount()>0?p(l,()=>!0):void 0;b(l,a,c,o,d===void 0?void 0:()=>{let e=p(l,()=>!0),{removed:t,added:n}=g(d,e);u({op:`replace`,removed:t,added:n})})},subscribeChanges:e=>c.treeChanged.subscribe(e)};return C.set(i,d),d}function T(e,t,n,r){if(n!==void 0){if(!Object.hasOwn(e.dependencies,t))r?.dependencies.validateDependencyCount(e,`setDependency`);else{let i=e.dependencies[t];i!==n&&!(Number.isNaN(i)&&Number.isNaN(n))&&r?.dependencies.warnOverwrite(t,`setDependency`)}e.dependencies[t]=n}}function E(e,t,n){let r=[];for(let i in t)t[i]!==void 0&&(Object.hasOwn(e.dependencies,i)?r.push(i):n?.dependencies.validateDependencyCount(e,`setDependencies`),e.dependencies[i]=t[i]);r.length>0&&n?.dependencies.warnBatchOverwrite(r,`setDependencies`)}function D(t){let r=e.u(t);return{get:e=>{r.validator?.dependencies.validateDependencyName(e,`getDependency`);let t=r.dependenciesGetStore(),n=t.dependencies[e];return r.validator?.dependencies.validateDependencyExists(e,t),n},getAll:()=>({...r.dependenciesGetStore().dependencies}),set:(e,t)=>{n(r.isDisposed),r.validator?.dependencies.validateSetDependencyArgs(e,t,`setDependency`),T(r.dependenciesGetStore(),e,t,r.validator)},setAll:e=>{n(r.isDisposed);let t=r.dependenciesGetStore();r.validator?.dependencies.validateDependenciesObject(e,`setDependencies`),E(t,e,r.validator)},remove:e=>{n(r.isDisposed),r.validator?.dependencies.validateDependencyName(e,`removeDependency`);let t=r.dependenciesGetStore();Object.hasOwn(t.dependencies,e)||r.validator?.dependencies.warnRemoveNonExistent(e),delete t.dependencies[e]},reset:()=>{n(r.isDisposed);let e=r.dependenciesGetStore();e.dependencies=Object.create(null)},has:e=>(r.validator?.dependencies.validateDependencyName(e,`hasDependency`),Object.hasOwn(r.dependenciesGetStore().dependencies,e))}}function O(t){let r=e.u(t),i=r.routeGetStore().lifecycleNamespace;return{addActivateGuard(e,t){n(r.isDisposed),r.validator?.routes.validateRouteName(e,`addActivateGuard`),r.validator?.lifecycle.validateHandler(t,`addActivateGuard`),i.addCanActivate(e,t)},addDeactivateGuard(e,t){n(r.isDisposed),r.validator?.routes.validateRouteName(e,`addDeactivateGuard`),r.validator?.lifecycle.validateHandler(t,`addDeactivateGuard`),i.addCanDeactivate(e,t)},removeActivateGuard(e){n(r.isDisposed),r.validator?.routes.validateRouteName(e,`removeActivateGuard`),i.clearCanActivate(e,`external`)},removeDeactivateGuard(e){n(r.isDisposed),r.validator?.routes.validateRouteName(e,`removeDeactivateGuard`),i.clearCanDeactivate(e,`external`)}}}function k(n,r,i){let a=e.u(n);if(a.isDisposed())throw new t.n(t.k.ROUTER_DISPOSED);a.validator?.dependencies.validateCloneArgs(r);let o=a.routeGetStore(),s=t.T(o.tree),c=o.config,l=o.resolvedForwardMap,u=o.routeCustomFields,{options:d,dependencies:f,pluginFactories:p,loggerConfig:m}=a.getCloneState(),{definition:h,external:g}=o.lifecycleNamespace.getFactoriesByOrigin(),_={...f,...r},v=i?.logger?{...m,...i.logger}:m,y=new t.t(s,{...d,logger:v},_),b=e.u(y),x=b.routeGetStore(),S=x.lifecycleNamespace;t.v(x.config,c),Object.assign(x.resolvedForwardMap,l),Object.assign(x.routeCustomFields,u),o.rootPath!==``&&b.setRootPath(o.rootPath);let[C,w]=h,[T,E]=g;for(let[e,t]of Object.entries(C))S.addCanDeactivate(e,t,!0);for(let[e,t]of Object.entries(w))S.addCanActivate(e,t,!0);let D=O(y);for(let[e,t]of Object.entries(T))D.addDeactivateGuard(e,t);for(let[e,t]of Object.entries(E))D.addActivateGuard(e,t);let k=new Set(b.getCloneState().pluginFactories),A=p.filter(e=>!k.has(e));return A.length>0&&y.usePlugin(...A),y}exports.cloneRouter=k,exports.getDependenciesApi=D,exports.getLifecycleApi=O,exports.getPluginApi=a,exports.getRoutesApi=w;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./buildParamMeta-0m11OIJS.js"),t=require("./Router-Cp-yrK2y.js");function n(e){if(e())throw new t.n(t.k.ROUTER_DISPOSED)}function r(e){if(e())throw new t.n(t.k.REENTRANT_TREE_MUTATION)}const i=new WeakMap;function a(r){let a=i.get(r);if(a)return a;let o=e.u(r),s={makeState:(t,n,r,i)=>(e.f(o,`makeState`,t,n),o.validator?.state.validateMakeStateArgs(t,n,i),o.makeState(t,n,r,i)),forwardState:(e,t,n)=>(o.validator?.routes.validateStateBuilderArgs(e,t,`forwardState`),o.forwardState(e,t,n)),matchPath:e=>(o.validator?.routes.validateMatchPathArgs(e),o.matchPath(e,o.getOptions())),navigateToState:(e,t)=>(n(o.isDisposed),o.validator?.navigation.validateNavigateToStateArgs(e),t!==void 0&&o.validator?.navigation.validateNavigationOptions(t,`navigateToState`),o.navigateToState(e,t)),setRootPath:e=>{n(o.isDisposed),o.validator?.routes.validateSetRootPathArgs(e),o.setRootPath(e)},getRootPath:o.getRootPath,addEventListener:(e,t)=>(n(o.isDisposed),o.validator?.eventBus.validateListenerArgs(e,t),o.addEventListener(e,t)),buildNavigationState:(n,r={},i={})=>{e.f(o,`buildNavigationState`,n,r),o.validator?.routes.validateStateBuilderArgs(n,r,`buildNavigationState`);let a=t.C(o.port(),n,r,i,{diagnoseUndeclared:!0});if(o.buildStateResolved(a.name,a.path))return t.x(a,{path:t.S(a,o.port())})},getOptions:o.getOptions,getTree:o.getTree,addInterceptor:(e,t)=>{n(o.isDisposed),o.validator?.plugins.validateAddInterceptorArgs(e,t);let r=o.interceptors.get(e);r||(r=[],o.interceptors.set(e,r)),r.push(t);let i=!1;return()=>{i||(i=!0,r.splice(r.indexOf(t),1))}},getRouteConfig:e=>{let t=o.routeGetStore();if(t.matcher.hasRoute(e))return t.routeCustomFields[e]},extendRouter:e=>{n(o.isDisposed);let i=Object.keys(e);for(let e of i)if(e in r)throw new t.n(t.k.PLUGIN_CONFLICT,{message:`Cannot extend router: property "${e}" already exists`});for(let t of i)r[t]=e[t];let a={keys:i};o.routerExtensions.push(a);let s=!1;return()=>{if(s)return;s=!0;for(let e of a.keys)delete r[e];let e=o.routerExtensions.indexOf(a);e!==-1&&o.routerExtensions.splice(e,1)}},emitTransitionError:e=>{n(o.isDisposed),o.emitTransitionError(e)},claimContextNamespace:e=>{if(n(o.isDisposed),typeof e!=`string`||e===``)throw TypeError(`[claimContextNamespace] namespace must be a non-empty string, got ${typeof e==`string`?`an empty string`:typeof e}`);if(o.contextClaimRecords.has(e))throw new t.n(t.k.CONTEXT_NAMESPACE_ALREADY_CLAIMED,{message:`Cannot claim context namespace: "${e}" is already claimed by another plugin`});return o.contextClaimRecords.add(e),{write(t,n){e===`__proto__`?Object.defineProperty(t.context,e,{value:n,writable:!0,enumerable:!0,configurable:!0}):t.context[e]=n},release(){o.contextClaimRecords.delete(e)}}}};return i.set(r,s),s}function o(e,t,n,r){if(t){let n=t===e,i=t.startsWith(`${e}.`);if(n||i){let i=n?``:` (current: "${t}")`;return r.warn(`router.removeRoute`,`Cannot remove route "${e}" — it is currently active${i}. Navigate away first.`),!1}}return n&&r.warn(`router.removeRoute`,`Route "${e}" removed while navigation is in progress. This may cause unexpected behavior.`),!0}function s(e,t){return e?(t.error(`router.clearRoutes`,`Cannot clear routes while navigation is in progress. Wait for navigation to complete.`),!1):!0}const c=Object.freeze({replace:!0,revalidate:!0});function l(e,n,r,i){let a=t=>t===e||t.startsWith(`${e}.`);t.y(n.decoders,a),t.y(n.encoders,a),t.y(n.defaultParams,a),t.y(n.defaultSearch,a),t.y(n.forwardMap,a),t.y(n.forwardFnMap,a),t.y(r,a),t.y(n.forwardMap,e=>a(n.forwardMap[e]));let[o,s]=i.getFactories();for(let e of Object.keys(s))a(e)&&i.clearCanActivate(e,`both`);for(let e of Object.keys(o))a(e)&&i.clearCanDeactivate(e,`both`)}function u(e,t,n,r){let i=n.forwardFnMap[t],a=n.forwardMap[t];i===void 0?a!==void 0&&(e.forwardTo=a):e.forwardTo=i,t in n.defaultParams&&(e.defaultParams=n.defaultParams[t]),t in n.defaultSearch&&(e.defaultSearch=n.defaultSearch[t]),t in n.decoders&&(e.decodeParams=n.decoders[t]),t in n.encoders&&(e.encodeParams=n.encoders[t]);let[o,s]=r;return t in s&&(e.canActivate=s[t]),t in o&&(e.canDeactivate=o[t]),e}function d(e,t,n,r){let i={name:e.name,path:e.path};return u(i,t,n,r),e.children&&(i.children=e.children.map(e=>d(e,`${t}.${e.name}`,n,r))),i}function f(e,t,n,r){let i={name:e,path:t};return u(i,e,n,r),Object.freeze(i)}function p(e,t){let n=new Map,r=e.lifecycleNamespace.getFactories(),i=(a,o)=>{for(let s of a){let a=o?`${o}.${s.name}`:s.name;t(a)&&n.set(a,f(a,s.path,e.config,r)),s.children&&i(s.children,a)}};return i(e.definitions,``),n}function m(e,t){let n=`${t}.`,r=p(e,e=>e===t||e.startsWith(n));return Object.freeze([...r.values()])}function h(e,t,n){let r=n.lifecycleNamespace.getFactories(),i=[],a=(e,t)=>{for(let o of e){let e=t?`${t}.${o.name}`:o.name;i.push(f(e,o.path,n.config,r)),o.children&&a(o.children,e)}};return a(e,t??``),Object.freeze(i)}function g(e,t){let n=[],r=[];for(let[r,i]of e)t.has(r)||n.push(i);for(let[n,i]of t)e.has(n)||r.push(i);return{removed:Object.freeze(n),added:Object.freeze(r)}}function _(e){let t={};return e.forwardTo!==void 0&&(t.forwardTo=e.forwardTo),e.defaultParams!==void 0&&(t.defaultParams=e.defaultParams),e.defaultSearch!==void 0&&(t.defaultSearch=e.defaultSearch),e.encodeParams!==void 0&&(t.encodeParams=e.encodeParams),e.decodeParams!==void 0&&(t.decodeParams=e.decodeParams),Object.freeze(t)}function v(e,n,r,i){t.a(e,n,r);let a=t.u(e,n,r,i);t._(a.matcher,a.config,`addRoute`),e.lifecycleNamespace.preflightHandlerLimit(a.pendingCanActivate.keys(),a.pendingCanDeactivate.keys(),!1),t.i(e,a)}function y(e,t,n){e.setState(t),e.emitTransitionSuccess(t,n,c)}function b(e,n,r,i,a){t.c(n,`addRoute`),t.o(n,``,`addRoute`),t.s(n,``,`addRoute`);let o=t.d(n,e.rootPath,e.matcherOptions,r.logger);t._(o.matcher,o.config,`addRoute`),e.lifecycleNamespace.preflightHandlerLimit(o.pendingCanActivate.keys(),o.pendingCanDeactivate.keys(),!0);let s=t.m(o,e.depsStore);if(e.lifecycleNamespace.clearDefinitionGuards(),t.i(e,o,s),a?.(),i!==void 0){let n=r.matchPath(i.path,r.getOptions());if(n)if(n.name===i.name)y(r,{...n,context:i.context,transition:i.transition},i);else{let{toDeactivate:a,toActivate:o}=t.r(n,i,r.getMetaForState);e.lifecycleNamespace.canNavigateTo(a,o,n,i)?y(r,{...n,transition:i.transition},i):r.navigateToNotFound(i.path)}else r.navigateToNotFound(i.path)}}function x(e,n){let r=e.definitions;return t.b(r,n)?(l(n,e.config,e.routeCustomFields,e.lifecycleNamespace),t.p(e,r),!0):!1}function S(e,n){let r=e.matcher.getSegmentsByName(n);if(!r)return;let i=t.w(r.at(-1)),a=e.lifecycleNamespace.getFactories();return d(i,n,e.config,a)}const C=new WeakMap;function w(i){let a=C.get(i);if(a)return a;let c=e.u(i),l=c.routeGetStore(),u=e=>{c.treeChanged.emit(e)},d={add:(e,i)=>{n(c.isDisposed),r(c.treeChanged.isEmitting);let a=Array.isArray(e)?e:[e],o=i?.parent;if(t.E(a,c.validator),o!==void 0&&c.validator?.routes.validateParentOption(o,l.tree),c.validator?.routes.throwIfInternalRouteInArray(a,`addRoute`),c.validator?.routes.validateAddRouteArgs(a),c.validator?.routes.validateRoutes(a,l,o),v(l,a,o,c.logger),c.treeChanged.listenerCount()>0){let e=h(a,o,l);u(o===void 0?{op:`add`,added:e}:{op:`add`,added:e,parent:o})}},remove:e=>{if(n(c.isDisposed),r(c.treeChanged.isEmitting),c.validator?.routes.validateRemoveRouteArgs(e),c.validator?.routes.throwIfInternalRoute(e,`removeRoute`),t.l(e,`removeRoute`),!o(e,c.getStateName(),c.isTransitioning(),c.logger))return;let i=c.treeChanged.listenerCount()>0?m(l,e):void 0;if(!x(l,e)){c.logger.warn(`router.removeRoute`,`Route "${e}" not found. No changes made.`);return}i!==void 0&&u({op:`remove`,name:e,removedSubtree:i})},update:(e,i)=>{if(n(c.isDisposed),r(c.treeChanged.isEmitting),c.validator?.routes.validateUpdateRouteBasicArgs(e,i),c.validator?.routes.throwIfInternalRoute(e,`updateRoute`),t.l(e,`updateRoute`),c.validator?.routes.validateUpdateRoutePropertyTypes(e,i),c.isTransitioning()&&c.logger.error(`router.updateRoute`,`Updating route "${e}" while navigation is in progress. This may cause unexpected behavior.`),c.validator?.routes.validateUpdateRoute(e,i,l),!l.matcher.hasRoute(e))return;let a=l.lifecycleNamespace,o=t.f(l,a,e,i);if(c.treeChanged.listenerCount()>0){let t=_(o);Object.keys(t).length>0&&u({op:`update`,name:e,patch:t})}},clear:()=>{if(n(c.isDisposed),r(c.treeChanged.isEmitting),!s(c.isTransitioning(),c.logger))return;let e=c.treeChanged.listenerCount()>0?Object.freeze([...p(l,()=>!0).values()]):void 0;t.h(l),l.lifecycleNamespace.clearAll(),c.clearState(),e!==void 0&&u({op:`clear`,removed:e})},has:e=>(c.validator?.routes.validateRouteName(e,`hasRoute`),l.matcher.hasRoute(e)),get:e=>(c.validator?.routes.validateRouteName(e,`getRoute`),S(l,e)),replace:e=>{n(c.isDisposed),r(c.treeChanged.isEmitting);let a=Array.isArray(e)?e:[e];if(!s(c.isTransitioning(),c.logger))return;t.E(a,c.validator),c.validator?.routes.throwIfInternalRouteInArray(a,`replaceRoutes`),c.validator?.routes.validateAddRouteArgs(a),c.validator?.routes.validateRoutes(a,l);let o=i.getState(),d=c.treeChanged.listenerCount()>0?p(l,()=>!0):void 0;b(l,a,c,o,d===void 0?void 0:()=>{let e=p(l,()=>!0),{removed:t,added:n}=g(d,e);u({op:`replace`,removed:t,added:n})})},subscribeChanges:e=>c.treeChanged.subscribe(e)};return C.set(i,d),d}function T(e,t,n,r){if(n!==void 0){if(!Object.hasOwn(e.dependencies,t))r?.dependencies.validateDependencyCount(e,`setDependency`);else{let i=e.dependencies[t];i!==n&&!(Number.isNaN(i)&&Number.isNaN(n))&&r?.dependencies.warnOverwrite(t,`setDependency`)}e.dependencies[t]=n}}function E(e,t,n){let r=[];for(let i in t)t[i]!==void 0&&(Object.hasOwn(e.dependencies,i)?r.push(i):n?.dependencies.validateDependencyCount(e,`setDependencies`),e.dependencies[i]=t[i]);r.length>0&&n?.dependencies.warnBatchOverwrite(r,`setDependencies`)}function D(t){let r=e.u(t);return{get:e=>{r.validator?.dependencies.validateDependencyName(e,`getDependency`);let t=r.dependenciesGetStore(),n=t.dependencies[e];return r.validator?.dependencies.validateDependencyExists(e,t),n},getAll:()=>({...r.dependenciesGetStore().dependencies}),set:(e,t)=>{n(r.isDisposed),r.validator?.dependencies.validateSetDependencyArgs(e,t,`setDependency`),T(r.dependenciesGetStore(),e,t,r.validator)},setAll:e=>{n(r.isDisposed);let t=r.dependenciesGetStore();r.validator?.dependencies.validateDependenciesObject(e,`setDependencies`),E(t,e,r.validator)},remove:e=>{n(r.isDisposed),r.validator?.dependencies.validateDependencyName(e,`removeDependency`);let t=r.dependenciesGetStore();Object.hasOwn(t.dependencies,e)||r.validator?.dependencies.warnRemoveNonExistent(e),delete t.dependencies[e]},reset:()=>{n(r.isDisposed);let e=r.dependenciesGetStore();e.dependencies=Object.create(null)},has:e=>(r.validator?.dependencies.validateDependencyName(e,`hasDependency`),Object.hasOwn(r.dependenciesGetStore().dependencies,e))}}function O(t){let r=e.u(t),i=r.routeGetStore().lifecycleNamespace;return{addActivateGuard(e,t){n(r.isDisposed),r.validator?.routes.validateRouteName(e,`addActivateGuard`),r.validator?.lifecycle.validateHandler(t,`addActivateGuard`),i.addCanActivate(e,t)},addDeactivateGuard(e,t){n(r.isDisposed),r.validator?.routes.validateRouteName(e,`addDeactivateGuard`),r.validator?.lifecycle.validateHandler(t,`addDeactivateGuard`),i.addCanDeactivate(e,t)},removeActivateGuard(e){n(r.isDisposed),r.validator?.routes.validateRouteName(e,`removeActivateGuard`),i.clearCanActivate(e,`external`)},removeDeactivateGuard(e){n(r.isDisposed),r.validator?.routes.validateRouteName(e,`removeDeactivateGuard`),i.clearCanDeactivate(e,`external`)}}}function k(n,r,i){let a=e.u(n);if(a.isDisposed())throw new t.n(t.k.ROUTER_DISPOSED);a.validator?.dependencies.validateCloneArgs(r);let o=a.routeGetStore(),s=t.T(o.tree),c=o.config,l=o.resolvedForwardMap,u=o.routeCustomFields,{options:d,dependencies:f,pluginFactories:p,loggerConfig:m}=a.getCloneState(),{definition:h,external:g}=o.lifecycleNamespace.getFactoriesByOrigin(),_={...f,...r},v=i?.logger?{...m,...i.logger}:m,y=new t.t(s,{...d,logger:v},_),b=e.u(y),x=b.routeGetStore(),S=x.lifecycleNamespace;t.v(x.config,c),Object.assign(x.resolvedForwardMap,l),Object.assign(x.routeCustomFields,u),o.rootPath!==``&&b.setRootPath(o.rootPath);let[C,w]=h,[T,E]=g;for(let[e,t]of Object.entries(C))S.addCanDeactivate(e,t,!0);for(let[e,t]of Object.entries(w))S.addCanActivate(e,t,!0);let D=O(y);for(let[e,t]of Object.entries(T))D.addDeactivateGuard(e,t);for(let[e,t]of Object.entries(E))D.addActivateGuard(e,t);let k=new Set(b.getCloneState().pluginFactories),A=p.filter(e=>!k.has(e));return A.length>0&&y.usePlugin(...A),y}exports.cloneRouter=k,exports.getDependenciesApi=D,exports.getLifecycleApi=O,exports.getPluginApi=a,exports.getRoutesApi=w;
|
|
2
2
|
//# sourceMappingURL=api.js.map
|
package/dist/cjs/helpers.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./constants.js");function t(e,t){if(e===void 0)return n(t);let r={};for(let t in e)Object.hasOwn(e,t)&&e[t]!==void 0&&(r[t]=e[t]);if(t!==void 0)for(let e in t)Object.hasOwn(t,e)&&t[e]!==void 0&&(r[e]=t[e]);return r}function n(e){if(e===void 0)return;let t;for(let n in e)Object.hasOwn(e,n)&&e[n]===void 0&&(t??={...e},delete t[n]);return t??e}const r=new Set([`string`,`number`,`boolean`]);function i(e){return r.has(typeof e)}function a(e,t){if(e===t)return!0;if(Array.isArray(e)){if(!Array.isArray(t))return e.length===1&&a(e[0],t);if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(!a(e[n],t[n]))return!1;return!0}return Array.isArray(t)?t.length===1&&a(e,t[0]):i(e)&&i(t)&&String(e)===String(t)}function o(e){return Object.freeze(e)}function s(e,n,r,i=!1){if(e!==void 0)return Object.freeze(t(e,n));if(n===void 0||n===r)return r;if(i)return Object.freeze(n);let a=t(void 0,n);return Object.freeze(a===n?{...n}:a)}function c(t){if(t===void 0)return t;let n;for(let e in t){if(!Object.hasOwn(t,e))continue;let r=t[e];r!==void 0&&(n??={},n[e]=r)}return n??e.EMPTY_PARAMS}exports.areParamValuesEqual=a,exports.
|
|
1
|
+
const e=require("./constants.js");function t(e,t){if(e===void 0)return n(t);let r={};for(let t in e)Object.hasOwn(e,t)&&e[t]!==void 0&&(r[t]=e[t]);if(t!==void 0)for(let e in t)Object.hasOwn(t,e)&&t[e]!==void 0&&(r[e]=t[e]);return r}function n(e){if(e===void 0)return;let t;for(let n in e)Object.hasOwn(e,n)&&e[n]===void 0&&(t??={...e},delete t[n]);return t??e}const r=new Set([`string`,`number`,`boolean`]);function i(e){return r.has(typeof e)}function a(e,t){if(e===t)return!0;if(Array.isArray(e)){if(!Array.isArray(t))return e.length===1&&a(e[0],t);if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(!a(e[n],t[n]))return!1;return!0}return Array.isArray(t)?t.length===1&&a(e,t[0]):i(e)&&i(t)&&String(e)===String(t)}function o(e){return Object.freeze(e)}function s(e,n,r,i=!1){if(e!==void 0)return Object.freeze(t(e,n));if(n===void 0||n===r)return r;if(i)return Object.freeze(n);let a=t(void 0,n);return Object.freeze(a===n?{...n}:a)}function c(t){if(t===void 0)return t;let n;for(let e in t){if(!Object.hasOwn(t,e))continue;let r=t[e];r!==void 0&&(n??={},n[e]=r)}return n??e.EMPTY_PARAMS}exports.areParamValuesEqual=a,exports.freezeStateShell=o,exports.mergeDefined=t,exports.mergeWithDefault=s,exports.normalizeParams=c;
|
|
2
2
|
//# sourceMappingURL=helpers.js.map
|
package/dist/cjs/helpers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","names":["EMPTY_PARAMS"],"sources":["../../src/helpers.ts"],"sourcesContent":["// packages/core/src/helpers.ts\n\nimport { EMPTY_PARAMS } from \"./constants\";\n\nimport type { Params, State } from \"./types\";\n\n// =============================================================================\n// Default merge — `undefined` ≡ absence (#1550 / #1551)\n// =============================================================================\n\n/**\n * Merges a route default UNDER a value (the value wins), treating `undefined` as\n * **absence on both sides** (#1550 / #1551).\n *\n * A key survives only when its winning value is defined:\n * - `mergeDefined({ page: \"1\" }, { page: undefined })` → `{ page: \"1\" }` — an\n * explicit `undefined` from the caller does not outrank the default (this is\n * what the path channel always did via `normalizeParams`, and what the query\n * channel did not, #1550);\n * - `mergeDefined({ q: undefined }, undefined)` → `{}` — a default that itself\n * carries `undefined` behaves exactly like no default entry, instead of\n * leaking an `undefined`-valued own key into the frozen state (#1551).\n *\n * Because the rule lives in the merge rather than in a separately-ordered\n * \"normalize\" stage, it holds for every producer and cannot be reintroduced by\n * whichever side is merged last.\n *\n * Allocation contract: **may return the `value` argument itself** when there is\n * no default and nothing to strip (the hot path — callers pass an\n * already-normalized bag), so a caller that freezes or stores the result must\n * copy it first. `undefined` in ⇒ `undefined` out when there is no default, which\n * keeps the matcher's single-bag fallback (`search ?? params`) reachable.\n */\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T,\n value: T | undefined,\n): T;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T,\n): T;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T | undefined,\n): T | undefined;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T | undefined,\n): T | undefined {\n if (defaultValue === undefined) {\n return stripUndefined(value);\n }\n\n const merged: Record<string, unknown> = {};\n\n for (const key in defaultValue) {\n if (Object.hasOwn(defaultValue, key) && defaultValue[key] !== undefined) {\n merged[key] = defaultValue[key];\n }\n }\n\n if (value !== undefined) {\n for (const key in value) {\n if (!Object.hasOwn(value, key)) {\n continue;\n }\n\n // `undefined` means \"I said nothing\", so the default keeps the slot.\n if (value[key] === undefined) {\n continue;\n }\n\n merged[key] = value[key];\n }\n }\n\n return merged as T;\n}\n\n/**\n * Drops `undefined`-valued own keys, returning the input **unchanged** when there\n * are none (no allocation on the common path). `undefined` in ⇒ `undefined` out —\n * unlike {@link normalizeParams}, which collapses an all-`undefined` bag to the\n * shared `EMPTY_PARAMS` singleton and is the path-channel entry guard.\n */\nfunction stripUndefined<T extends Record<string, unknown>>(\n value: T | undefined,\n): T | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n let stripped: Record<string, unknown> | undefined;\n\n for (const key in value) {\n if (!(Object.hasOwn(value, key) && value[key] === undefined)) {\n continue;\n }\n\n stripped ??= { ...value };\n\n delete stripped[key];\n }\n\n return (stripped as T | undefined) ?? value;\n}\n\n// =============================================================================\n// Param value comparison (#1554)\n// =============================================================================\n\n/** The value types a channel prints into (and parses back from) a URL. */\nconst PRINTABLE_TYPES = new Set([\"string\", \"number\", \"boolean\"]);\n\n/** A value the two channels can carry across a URL round-trip. */\nfunction isPrintableScalar(value: unknown): value is string | number | boolean {\n return PRINTABLE_TYPES.has(typeof value);\n}\n\n/**\n * Compares two param / query values for equality **independently of where they\n * came from** (#1554).\n *\n * The two directions produce different value DOMAINS for the same location: the\n * URL direction parses (`?page=2` → `2`, `?a=1&a=2` → `[1, 2]`, a path slot is\n * always a string), the intent direction keeps whatever the caller supplied\n * (`{ page: \"2\" }` stays a string). Both build the SAME `state.path`, so a\n * `===`-based comparison reported a URL-derived state and an intent-derived\n * state on one location as UNEQUAL — an active link rendered inactive.\n *\n * The rule is therefore \"equal when both values print the same query string\":\n * - **scalars** (string / number / boolean) compare by their printed form, so\n * `2 ≡ \"2\"` and `true ≡ \"true\"`;\n * - **arrays** compare element-wise under the same rule, and a **singleton\n * array** compares against a bare scalar (`[\"1\"]` and `1` both print `?a=1`);\n * - everything else (`null`, `undefined`, objects) keeps strict semantics —\n * those print differently (`?a` vs `?a=` vs nothing at all), so tolerating\n * them would equate genuinely different URLs.\n *\n * Value normalization is deliberately NOT done: `state.search` keeps the mixed\n * domain (RFC-4 M2 / §10.14 decision (б)) and comparison is the single place\n * that knows the two domains describe the same location. Unifying the domain\n * itself belongs to the typed search-schema stage.\n */\nexport function areParamValuesEqual(val1: unknown, val2: unknown): boolean {\n if (val1 === val2) {\n return true;\n }\n\n if (Array.isArray(val1)) {\n // A singleton array prints exactly like its element (`[\"1\"]` and `1` both\n // print `?a=1`), so compare across the shape instead of rejecting on it.\n if (!Array.isArray(val2)) {\n return val1.length === 1 && areParamValuesEqual(val1[0], val2);\n }\n\n if (val1.length !== val2.length) {\n return false;\n }\n\n // eslint-disable-next-line unicorn/no-for-loop -- hot path: for-of entries() allocates iterator per recursive call\n for (let i = 0; i < val1.length; i++) {\n if (!areParamValuesEqual(val1[i], val2[i])) {\n return false;\n }\n }\n\n return true;\n }\n\n if (Array.isArray(val2)) {\n return val2.length === 1 && areParamValuesEqual(val1, val2[0]);\n }\n\n return (\n isPrintableScalar(val1) &&\n isPrintableScalar(val2) &&\n String(val1) === String(val2)\n );\n}\n\n// =============================================================================\n// State Helpers\n// =============================================================================\n\n/**\n * Shallow-freezes a State object in place.\n *\n * Freezes only the top-level State object (blocks reassignment of `name`,\n * `params`, `path`, `transition`, `context`). Nested objects (`params`,\n * `transition`, `transition.segments`, `transition.segments.{deactivated,activated}`)\n * are expected to be **already frozen at creation time** by their producers:\n *\n * - `params` frozen in `makeState()` / `navigateToNotFound()`\n * - `transition`, `segments`, `deactivated`, `activated` frozen in\n * `buildTransitionMeta()` (or inline in `navigateToNotFound()`)\n *\n * `state.context` is **intentionally not frozen** — plugins write to it via\n * `claim.write(state, value)` after state creation.\n *\n * @internal\n */\nexport function freezeStateInPlace<T extends State>(state: T): T {\n // `Object.freeze` returns non-objects (incl. null/undefined) unchanged, so the\n // former `if (!state) return state` guard was redundant — callers also gate it\n // (`state ? freezeStateInPlace(state) : undefined`) and `T extends State` is\n // typed non-null.\n return Object.freeze(state);\n}\n\n/**\n * Merges a channel's route default UNDER a routed value (the value wins) and\n * freezes the result. Reuses the shared frozen `empty` singleton (EMPTY_PARAMS /\n * EMPTY_SEARCH, #1027) when there is neither a default nor a value — so the hot\n * path (no defaults, empty params) allocates zero objects. A defaulted channel\n * always spreads (a fresh frozen object); an undefined-default channel freezes a\n * copy of the value (never the caller's object).\n *\n * `undefined` is absence on BOTH sides (`mergeDefined`, #1550 / #1551): an\n * explicitly-`undefined` caller value leaves the default in place, and a default\n * that carries `undefined` behaves like no entry — so the frozen state never\n * exposes an `undefined`-valued own key on either channel.\n *\n * `valueIsOwned` says the caller minted `value` itself and nothing else holds a\n * reference — then the defensive copy is skipped and the bag is frozen in place\n * (#1589). Only `canonicalize`'s PATH channel may pass it, because only there is\n * the value the fresh object `normalizeParams` just returned. Passing it for a\n * bag that came from user code would freeze the caller's object.\n *\n * Lives here, not in a namespace, because the rule outlived its call count:\n * stage ③ (`applyDefaults`) had TWO callers when the pipeline landed\n * (`StateNamespace.makeState` and `pipeline/canonicalize`) and has ONE since\n * Phase 4 folded the first onto the second — but the chain fold in\n * `RoutesNamespace` still layers hop defaults through `mergeDefined` directly,\n * so a second copy of \"default under value\" would be a second source of truth\n * for the rule, the same drift trap #1550/#1551 closed\n * by collapsing the four merge sites onto `mergeDefined`.\n *\n * @internal\n */\nexport function mergeWithDefault(\n defaultValue: Record<string, unknown> | undefined,\n value: Record<string, unknown> | undefined,\n empty: Readonly<Record<string, never>>,\n valueIsOwned = false,\n): Readonly<Record<string, unknown>> {\n if (defaultValue !== undefined) {\n return Object.freeze(mergeDefined(defaultValue, value));\n }\n\n if (value === undefined || value === empty) {\n return empty;\n }\n\n // OWNED value: freeze in place. The copy below exists solely so the CALLER's\n // bag is never frozen out from under it — when the bag was minted one line\n // earlier by `normalizeParams` (which always returns a fresh object or the\n // frozen `empty` singleton, never its input) there is no caller to protect,\n // and `undefined` values are already stripped, so `mergeDefined`'s walk is\n // redundant too. Measured on #1589: without this the path channel is copied\n // TWICE per producer call — once to normalize, once to freeze — on `navigate`,\n // `buildPath`, `matchPath`, `isActiveRoute` and `canNavigateTo` alike.\n if (valueIsOwned) {\n return Object.freeze(value);\n }\n\n // `mergeDefined` returns the argument itself when there is nothing to strip,\n // so copy before freezing — the caller's bag must never be frozen.\n const defined = mergeDefined(undefined, value);\n\n return Object.freeze(defined === value ? { ...value } : defined);\n}\n\n// =============================================================================\n// Params Helpers\n// =============================================================================\n\n/**\n * Strips `undefined` values from a params object before handoff to the query\n * string engine and state storage.\n *\n * **Why this exists:** `router.navigate(name, { x: undefined })` must not put\n * `x` into the resulting URL (publicly documented contract). The underlying\n * query engine (`search-params`) already does this, but the contract belongs\n * to `@real-router/core` — this function guarantees it at the core boundary\n * so that:\n * - Plugin interceptors on `forwardState` that inject `undefined` values are\n * caught before they reach the engine\n * - `state.params` never contains `undefined` values (roundtrip consistent\n * with URL)\n * - The contract is verifiable at core's own test surface (doesn't depend on\n * engine behavior for regression detection)\n *\n * Single pass. When nothing survives (empty input, or every value `undefined`)\n * it returns the shared frozen `EMPTY_PARAMS` singleton, so the merge's\n * `value === empty` reuse branch (`mergeWithDefault`) fires and an empty-params\n * navigation allocates zero transient `{}` (#1027); a non-empty input returns a fresh\n * object. Either way reference identity is not preserved across calls, and the\n * result MUST be treated as read-only — callers must not mutate it (the empty\n * case is a shared frozen singleton).\n */\nexport function normalizeParams(params: Params): Params;\n\nexport function normalizeParams(params: undefined): undefined;\n\nexport function normalizeParams(params: Params | undefined): Params | undefined;\n\nexport function normalizeParams(\n params: Params | undefined,\n): Params | undefined {\n if (params === undefined) {\n return params;\n }\n\n let normalized: Params | undefined;\n\n for (const key in params) {\n if (!Object.hasOwn(params, key)) {\n continue;\n }\n\n const value = params[key];\n\n if (value !== undefined) {\n // Lazy allocation: an all-empty / all-undefined input costs zero objects.\n normalized ??= {};\n normalized[key] = value;\n }\n }\n\n // Reuse the shared singleton when nothing survived so the merge's\n // `value === empty` reuse branch fires (#1027).\n return normalized ?? EMPTY_PARAMS;\n}\n"],"mappings":"kCAgDA,SAAgB,EACd,EACA,EACe,CACf,GAAI,IAAiB,IAAA,GACnB,OAAO,EAAe,CAAK,EAG7B,IAAM,EAAkC,CAAC,EAEzC,IAAK,IAAM,KAAO,EACZ,OAAO,OAAO,EAAc,CAAG,GAAK,EAAa,KAAS,IAAA,KAC5D,EAAO,GAAO,EAAa,IAI/B,GAAI,IAAU,IAAA,GACZ,IAAK,IAAM,KAAO,EACX,OAAO,OAAO,EAAO,CAAG,GAKzB,EAAM,KAAS,IAAA,KAInB,EAAO,GAAO,EAAM,IAIxB,OAAO,CACT,CAQA,SAAS,EACP,EACe,CACf,GAAI,IAAU,IAAA,GACZ,OAGF,IAAI,EAEJ,IAAK,IAAM,KAAO,EACV,OAAO,OAAO,EAAO,CAAG,GAAK,EAAM,KAAS,IAAA,KAIlD,IAAa,CAAE,GAAG,CAAM,EAExB,OAAO,EAAS,IAGlB,OAAQ,GAA8B,CACxC,CAOA,MAAM,EAAkB,IAAI,IAAI,CAAC,SAAU,SAAU,SAAS,CAAC,EAG/D,SAAS,EAAkB,EAAoD,CAC7E,OAAO,EAAgB,IAAI,OAAO,CAAK,CACzC,CA2BA,SAAgB,EAAoB,EAAe,EAAwB,CACzE,GAAI,IAAS,EACX,MAAO,GAGT,GAAI,MAAM,QAAQ,CAAI,EAAG,CAGvB,GAAI,CAAC,MAAM,QAAQ,CAAI,EACrB,OAAO,EAAK,SAAW,GAAK,EAAoB,EAAK,GAAI,CAAI,EAG/D,GAAI,EAAK,SAAW,EAAK,OACvB,MAAO,GAIT,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,IAC/B,GAAI,CAAC,EAAoB,EAAK,GAAI,EAAK,EAAE,EACvC,MAAO,GAIX,MAAO,EACT,CAMA,OAJI,MAAM,QAAQ,CAAI,EACb,EAAK,SAAW,GAAK,EAAoB,EAAM,EAAK,EAAE,EAI7D,EAAkB,CAAI,GACtB,EAAkB,CAAI,GACtB,OAAO,CAAI,IAAM,OAAO,CAAI,CAEhC,CAuBA,SAAgB,EAAoC,EAAa,CAK/D,OAAO,OAAO,OAAO,CAAK,CAC5B,CAgCA,SAAgB,EACd,EACA,EACA,EACA,EAAe,GACoB,CACnC,GAAI,IAAiB,IAAA,GACnB,OAAO,OAAO,OAAO,EAAa,EAAc,CAAK,CAAC,EAGxD,GAAI,IAAU,IAAA,IAAa,IAAU,EACnC,OAAO,EAWT,GAAI,EACF,OAAO,OAAO,OAAO,CAAK,EAK5B,IAAM,EAAU,EAAa,IAAA,GAAW,CAAK,EAE7C,OAAO,OAAO,OAAO,IAAY,EAAQ,CAAE,GAAG,CAAM,EAAI,CAAO,CACjE,CAoCA,SAAgB,EACd,EACoB,CACpB,GAAI,IAAW,IAAA,GACb,OAAO,EAGT,IAAI,EAEJ,IAAK,IAAM,KAAO,EAAQ,CACxB,GAAI,CAAC,OAAO,OAAO,EAAQ,CAAG,EAC5B,SAGF,IAAM,EAAQ,EAAO,GAEjB,IAAU,IAAA,KAEZ,IAAe,CAAC,EAChB,EAAW,GAAO,EAEtB,CAIA,OAAO,GAAcA,EAAAA,YACvB"}
|
|
1
|
+
{"version":3,"file":"helpers.js","names":["EMPTY_PARAMS"],"sources":["../../src/helpers.ts"],"sourcesContent":["// packages/core/src/helpers.ts\n\nimport { EMPTY_PARAMS } from \"./constants\";\n\nimport type { Params, State } from \"./types\";\n\n// =============================================================================\n// Default merge — `undefined` ≡ absence (#1550 / #1551)\n// =============================================================================\n\n/**\n * Merges a route default UNDER a value (the value wins), treating `undefined` as\n * **absence on both sides** (#1550 / #1551).\n *\n * A key survives only when its winning value is defined:\n * - `mergeDefined({ page: \"1\" }, { page: undefined })` → `{ page: \"1\" }` — an\n * explicit `undefined` from the caller does not outrank the default (this is\n * what the path channel always did via `normalizeParams`, and what the query\n * channel did not, #1550);\n * - `mergeDefined({ q: undefined }, undefined)` → `{}` — a default that itself\n * carries `undefined` behaves exactly like no default entry, instead of\n * leaking an `undefined`-valued own key into the frozen state (#1551).\n *\n * Because the rule lives in the merge rather than in a separately-ordered\n * \"normalize\" stage, it holds for every producer and cannot be reintroduced by\n * whichever side is merged last.\n *\n * Allocation contract: **may return the `value` argument itself** when there is\n * no default and nothing to strip (the hot path — callers pass an\n * already-normalized bag), so a caller that freezes or stores the result must\n * copy it first. `undefined` in ⇒ `undefined` out when there is no default, which\n * keeps the matcher's single-bag fallback (`search ?? params`) reachable.\n */\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T,\n value: T | undefined,\n): T;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T,\n): T;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T | undefined,\n): T | undefined;\n\nexport function mergeDefined<T extends Record<string, unknown>>(\n defaultValue: T | undefined,\n value: T | undefined,\n): T | undefined {\n if (defaultValue === undefined) {\n return stripUndefined(value);\n }\n\n const merged: Record<string, unknown> = {};\n\n for (const key in defaultValue) {\n if (Object.hasOwn(defaultValue, key) && defaultValue[key] !== undefined) {\n merged[key] = defaultValue[key];\n }\n }\n\n if (value !== undefined) {\n for (const key in value) {\n if (!Object.hasOwn(value, key)) {\n continue;\n }\n\n // `undefined` means \"I said nothing\", so the default keeps the slot.\n if (value[key] === undefined) {\n continue;\n }\n\n merged[key] = value[key];\n }\n }\n\n return merged as T;\n}\n\n/**\n * Drops `undefined`-valued own keys, returning the input **unchanged** when there\n * are none (no allocation on the common path). `undefined` in ⇒ `undefined` out —\n * unlike {@link normalizeParams}, which collapses an all-`undefined` bag to the\n * shared `EMPTY_PARAMS` singleton and is the path-channel entry guard.\n */\nfunction stripUndefined<T extends Record<string, unknown>>(\n value: T | undefined,\n): T | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n let stripped: Record<string, unknown> | undefined;\n\n for (const key in value) {\n if (!(Object.hasOwn(value, key) && value[key] === undefined)) {\n continue;\n }\n\n stripped ??= { ...value };\n\n delete stripped[key];\n }\n\n return (stripped as T | undefined) ?? value;\n}\n\n// =============================================================================\n// Param value comparison (#1554)\n// =============================================================================\n\n/** The value types a channel prints into (and parses back from) a URL. */\nconst PRINTABLE_TYPES = new Set([\"string\", \"number\", \"boolean\"]);\n\n/** A value the two channels can carry across a URL round-trip. */\nfunction isPrintableScalar(value: unknown): value is string | number | boolean {\n return PRINTABLE_TYPES.has(typeof value);\n}\n\n/**\n * Compares two param / query values for equality **independently of where they\n * came from** (#1554).\n *\n * The two directions produce different value DOMAINS for the same location: the\n * URL direction parses (`?page=2` → `2`, `?a=1&a=2` → `[1, 2]`, a path slot is\n * always a string), the intent direction keeps whatever the caller supplied\n * (`{ page: \"2\" }` stays a string). Both build the SAME `state.path`, so a\n * `===`-based comparison reported a URL-derived state and an intent-derived\n * state on one location as UNEQUAL — an active link rendered inactive.\n *\n * The rule is therefore \"equal when both values print the same query string\":\n * - **scalars** (string / number / boolean) compare by their printed form, so\n * `2 ≡ \"2\"` and `true ≡ \"true\"`;\n * - **arrays** compare element-wise under the same rule, and a **singleton\n * array** compares against a bare scalar (`[\"1\"]` and `1` both print `?a=1`);\n * - everything else (`null`, `undefined`, objects) keeps strict semantics —\n * those print differently (`?a` vs `?a=` vs nothing at all), so tolerating\n * them would equate genuinely different URLs.\n *\n * Value normalization is deliberately NOT done: `state.search` keeps the mixed\n * domain (RFC-4 M2 / §10.14 decision (б)) and comparison is the single place\n * that knows the two domains describe the same location. Unifying the domain\n * itself belongs to the typed search-schema stage.\n */\nexport function areParamValuesEqual(val1: unknown, val2: unknown): boolean {\n if (val1 === val2) {\n return true;\n }\n\n if (Array.isArray(val1)) {\n // A singleton array prints exactly like its element (`[\"1\"]` and `1` both\n // print `?a=1`), so compare across the shape instead of rejecting on it.\n if (!Array.isArray(val2)) {\n return val1.length === 1 && areParamValuesEqual(val1[0], val2);\n }\n\n if (val1.length !== val2.length) {\n return false;\n }\n\n // eslint-disable-next-line unicorn/no-for-loop -- hot path: for-of entries() allocates iterator per recursive call\n for (let i = 0; i < val1.length; i++) {\n if (!areParamValuesEqual(val1[i], val2[i])) {\n return false;\n }\n }\n\n return true;\n }\n\n if (Array.isArray(val2)) {\n return val2.length === 1 && areParamValuesEqual(val1, val2[0]);\n }\n\n return (\n isPrintableScalar(val1) &&\n isPrintableScalar(val2) &&\n String(val1) === String(val2)\n );\n}\n\n// =============================================================================\n// State Helpers\n// =============================================================================\n\n/**\n * Freezes the State object's own level — the SHELL, not the state.\n *\n * Named for what it does after #1599: it used to be called\n * `freezeStateInPlace`, which promised a depth it has never delivered, and\n * `CLAUDE.md` described it as \"consolidated into one recursive traversal\" long\n * after the traversal was gone. It blocks reassignment of `name` / `params` /\n * `search` / `path` / `transition` / `context` and nothing more.\n *\n * **The depth is a POLICY, not this function's job: every object is frozen once,\n * where it is created.** That is deliberate and measured — re-freezing an\n * already-frozen object costs ~8 ns, so a recursive walk would pay per node for\n * work its producers already did. The four producers and what each owns:\n *\n * - `params` — {@link mergeWithDefault} on the slow path; on the fast one there is\n * no merge to freeze it, so `pipeline/materialize` does at the publication\n * boundary (#1598), before its own `skipFreeze` branch\n * - `search` — the `EMPTY_SEARCH` singleton, or `admittedSearch`\n * (`channels/modeGate.ts`) on its DROP branch, the one branch that builds a bag\n * the caller did not already freeze\n * - `transition` + nested — `buildTransitionMeta()` (or inline in\n * `navigateToNotFound()`)\n * - the shell — here\n *\n * `state.context` is **intentionally not frozen** — plugins write to it via\n * `claim.write(state, value)` after state creation.\n *\n * The whole matrix is pinned black-box in\n * `tests/functional/error/helpers.test.ts` (\"state immutability across every\n * producer\"), mutationally validated against all four sites. Before #1599 two of\n * them were unguarded: deleting the `canonicalize` freeze left the entire suite\n * green, and the mode gate's freeze was reachable only under a non-`loose` mode\n * with one key dropped AND one admitted.\n *\n * @internal\n */\nexport function freezeStateShell<T extends State>(state: T): T {\n // `Object.freeze` returns non-objects (incl. null/undefined) unchanged, so the\n // former `if (!state) return state` guard was redundant — callers also gate it\n // (`state ? freezeStateShell(state) : undefined`) and `T extends State` is\n // typed non-null.\n return Object.freeze(state);\n}\n\n/**\n * Merges a channel's route default UNDER a routed value (the value wins) and\n * freezes the result. Reuses the shared frozen `empty` singleton (EMPTY_PARAMS /\n * EMPTY_SEARCH, #1027) when there is neither a default nor a value — so the hot\n * path (no defaults, empty params) allocates zero objects. A defaulted channel\n * always spreads (a fresh frozen object); an undefined-default channel freezes a\n * copy of the value (never the caller's object).\n *\n * `undefined` is absence on BOTH sides (`mergeDefined`, #1550 / #1551): an\n * explicitly-`undefined` caller value leaves the default in place, and a default\n * that carries `undefined` behaves like no entry — so the frozen state never\n * exposes an `undefined`-valued own key on either channel.\n *\n * `valueIsOwned` says the caller minted `value` itself and nothing else holds a\n * reference — then the defensive copy is skipped and the bag is frozen in place\n * (#1589). Only `canonicalize`'s PATH channel may pass it, because only there is\n * the value the fresh object `normalizeParams` just returned. Passing it for a\n * bag that came from user code would freeze the caller's object.\n *\n * Lives here, not in a namespace, because the rule outlived its call count:\n * stage ③ (`applyDefaults`) had TWO callers when the pipeline landed\n * (`StateNamespace.makeState` and `pipeline/canonicalize`) and has ONE since\n * Phase 4 folded the first onto the second — but the chain fold in\n * `RoutesNamespace` still layers hop defaults through `mergeDefined` directly,\n * so a second copy of \"default under value\" would be a second source of truth\n * for the rule, the same drift trap #1550/#1551 closed\n * by collapsing the four merge sites onto `mergeDefined`.\n *\n * @internal\n */\nexport function mergeWithDefault(\n defaultValue: Record<string, unknown> | undefined,\n value: Record<string, unknown> | undefined,\n empty: Readonly<Record<string, never>>,\n valueIsOwned = false,\n): Readonly<Record<string, unknown>> {\n if (defaultValue !== undefined) {\n return Object.freeze(mergeDefined(defaultValue, value));\n }\n\n if (value === undefined || value === empty) {\n return empty;\n }\n\n // OWNED value: freeze in place. The copy below exists solely so the CALLER's\n // bag is never frozen out from under it — when the bag was minted one line\n // earlier by `normalizeParams` (which always returns a fresh object or the\n // frozen `empty` singleton, never its input) there is no caller to protect,\n // and `undefined` values are already stripped, so `mergeDefined`'s walk is\n // redundant too. Measured on #1589: without this the path channel is copied\n // TWICE per producer call — once to normalize, once to freeze — on `navigate`,\n // `buildPath`, `matchPath`, `isActiveRoute` and `canNavigateTo` alike.\n if (valueIsOwned) {\n return Object.freeze(value);\n }\n\n // `mergeDefined` returns the argument itself when there is nothing to strip,\n // so copy before freezing — the caller's bag must never be frozen.\n const defined = mergeDefined(undefined, value);\n\n return Object.freeze(defined === value ? { ...value } : defined);\n}\n\n// =============================================================================\n// Params Helpers\n// =============================================================================\n\n/**\n * Strips `undefined` values from a params object before handoff to the query\n * string engine and state storage.\n *\n * **Why this exists:** `router.navigate(name, { x: undefined })` must not put\n * `x` into the resulting URL (publicly documented contract). The underlying\n * query engine (`search-params`) already does this, but the contract belongs\n * to `@real-router/core` — this function guarantees it at the core boundary\n * so that:\n * - Plugin interceptors on `forwardState` that inject `undefined` values are\n * caught before they reach the engine\n * - `state.params` never contains `undefined` values (roundtrip consistent\n * with URL)\n * - The contract is verifiable at core's own test surface (doesn't depend on\n * engine behavior for regression detection)\n *\n * Single pass. When nothing survives (empty input, or every value `undefined`)\n * it returns the shared frozen `EMPTY_PARAMS` singleton, so the merge's\n * `value === empty` reuse branch (`mergeWithDefault`) fires and an empty-params\n * navigation allocates zero transient `{}` (#1027); a non-empty input returns a fresh\n * object. Either way reference identity is not preserved across calls, and the\n * result MUST be treated as read-only — callers must not mutate it (the empty\n * case is a shared frozen singleton).\n */\nexport function normalizeParams(params: Params): Params;\n\nexport function normalizeParams(params: undefined): undefined;\n\nexport function normalizeParams(params: Params | undefined): Params | undefined;\n\nexport function normalizeParams(\n params: Params | undefined,\n): Params | undefined {\n if (params === undefined) {\n return params;\n }\n\n let normalized: Params | undefined;\n\n for (const key in params) {\n if (!Object.hasOwn(params, key)) {\n continue;\n }\n\n const value = params[key];\n\n if (value !== undefined) {\n // Lazy allocation: an all-empty / all-undefined input costs zero objects.\n normalized ??= {};\n normalized[key] = value;\n }\n }\n\n // Reuse the shared singleton when nothing survived so the merge's\n // `value === empty` reuse branch fires (#1027).\n return normalized ?? EMPTY_PARAMS;\n}\n"],"mappings":"kCAgDA,SAAgB,EACd,EACA,EACe,CACf,GAAI,IAAiB,IAAA,GACnB,OAAO,EAAe,CAAK,EAG7B,IAAM,EAAkC,CAAC,EAEzC,IAAK,IAAM,KAAO,EACZ,OAAO,OAAO,EAAc,CAAG,GAAK,EAAa,KAAS,IAAA,KAC5D,EAAO,GAAO,EAAa,IAI/B,GAAI,IAAU,IAAA,GACZ,IAAK,IAAM,KAAO,EACX,OAAO,OAAO,EAAO,CAAG,GAKzB,EAAM,KAAS,IAAA,KAInB,EAAO,GAAO,EAAM,IAIxB,OAAO,CACT,CAQA,SAAS,EACP,EACe,CACf,GAAI,IAAU,IAAA,GACZ,OAGF,IAAI,EAEJ,IAAK,IAAM,KAAO,EACV,OAAO,OAAO,EAAO,CAAG,GAAK,EAAM,KAAS,IAAA,KAIlD,IAAa,CAAE,GAAG,CAAM,EAExB,OAAO,EAAS,IAGlB,OAAQ,GAA8B,CACxC,CAOA,MAAM,EAAkB,IAAI,IAAI,CAAC,SAAU,SAAU,SAAS,CAAC,EAG/D,SAAS,EAAkB,EAAoD,CAC7E,OAAO,EAAgB,IAAI,OAAO,CAAK,CACzC,CA2BA,SAAgB,EAAoB,EAAe,EAAwB,CACzE,GAAI,IAAS,EACX,MAAO,GAGT,GAAI,MAAM,QAAQ,CAAI,EAAG,CAGvB,GAAI,CAAC,MAAM,QAAQ,CAAI,EACrB,OAAO,EAAK,SAAW,GAAK,EAAoB,EAAK,GAAI,CAAI,EAG/D,GAAI,EAAK,SAAW,EAAK,OACvB,MAAO,GAIT,IAAK,IAAI,EAAI,EAAG,EAAI,EAAK,OAAQ,IAC/B,GAAI,CAAC,EAAoB,EAAK,GAAI,EAAK,EAAE,EACvC,MAAO,GAIX,MAAO,EACT,CAMA,OAJI,MAAM,QAAQ,CAAI,EACb,EAAK,SAAW,GAAK,EAAoB,EAAM,EAAK,EAAE,EAI7D,EAAkB,CAAI,GACtB,EAAkB,CAAI,GACtB,OAAO,CAAI,IAAM,OAAO,CAAI,CAEhC,CA0CA,SAAgB,EAAkC,EAAa,CAK7D,OAAO,OAAO,OAAO,CAAK,CAC5B,CAgCA,SAAgB,EACd,EACA,EACA,EACA,EAAe,GACoB,CACnC,GAAI,IAAiB,IAAA,GACnB,OAAO,OAAO,OAAO,EAAa,EAAc,CAAK,CAAC,EAGxD,GAAI,IAAU,IAAA,IAAa,IAAU,EACnC,OAAO,EAWT,GAAI,EACF,OAAO,OAAO,OAAO,CAAK,EAK5B,IAAM,EAAU,EAAa,IAAA,GAAW,CAAK,EAE7C,OAAO,OAAO,OAAO,IAAY,EAAQ,CAAE,GAAG,CAAM,EAAI,CAAO,CACjE,CAoCA,SAAgB,EACd,EACoB,CACpB,GAAI,IAAW,IAAA,GACb,OAAO,EAGT,IAAI,EAEJ,IAAK,IAAM,KAAO,EAAQ,CACxB,GAAI,CAAC,OAAO,OAAO,EAAQ,CAAG,EAC5B,SAGF,IAAM,EAAQ,EAAO,GAEjB,IAAU,IAAA,KAEZ,IAAe,CAAC,EAChB,EAAW,GAAO,EAEtB,CAIA,OAAO,GAAcA,EAAAA,YACvB"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./Router-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./Router-Cp-yrK2y.js"),t=(t=[],n={},r={})=>new e.t(t,n,r),n=new WeakMap,r=e=>{let t=n.get(e);return t||(t=Object.freeze({navigate:e.navigate,getState:e.getState,isActiveRoute:e.isActiveRoute,canNavigateTo:e.canNavigateTo,subscribe:e.subscribe,subscribeLeave:e.subscribeLeave,isLeaveApproved:e.isLeaveApproved}),n.set(e,t)),t};exports.Router=e.t,exports.RouterError=e.n,exports.UNKNOWN_ROUTE=e.D,exports.constants=e.O,exports.createRouter=t,exports.errorCodes=e.k,exports.events=e.A,exports.getNavigator=r,exports.resolveForwardChain=e.g;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationNamespace.js","names":["constants","#deps","CACHED_NOT_STARTED_REJECTION","CACHED_ROUTE_NOT_FOUND_ERROR","CACHED_ROUTE_NOT_FOUND_REJECTION","#executeNavigation","RouterError","errorCodes","findMisChanneledKey","misChanneledKeyMessage","#abortPreviousNavigation","nameToIDs","EMPTY_PARAMS","EMPTY_SEARCH","#currentController","CACHED_SAME_STATES_ERROR","CACHED_SAME_STATES_REJECTION","#navigationId","getTransitionPath","#handleNoGuardsLeave","executeGuardPipeline","#finishAsyncNavigation","#cleanupController","completeTransition","#handleNavigateError"],"sources":["../../../../src/namespaces/NavigationNamespace/NavigationNamespace.ts"],"sourcesContent":["import {\n CACHED_NOT_STARTED_REJECTION,\n CACHED_ROUTE_NOT_FOUND_ERROR,\n CACHED_ROUTE_NOT_FOUND_REJECTION,\n CACHED_SAME_STATES_ERROR,\n CACHED_SAME_STATES_REJECTION,\n} from \"./constants\";\nimport { completeTransition } from \"./transition/completeTransition\";\nimport { routeTransitionError } from \"./transition/errorHandling\";\nimport { executeGuardPipeline } from \"./transition/guardPhase\";\nimport { findMisChanneledKey, misChanneledKeyMessage } from \"../../channels\";\nimport {\n EMPTY_PARAMS,\n EMPTY_SEARCH,\n errorCodes,\n constants,\n} from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\nimport { getTransitionPath, nameToIDs } from \"../../transitionPath\";\n\nimport type { NavigationContext, NavigationDependencies } from \"./types\";\nimport type { TransitionPath } from \"../../transitionPath\";\nimport type {\n GuardFn,\n NavigationOptions,\n Params,\n SearchParams,\n State,\n TransitionMeta,\n} from \"../../types\";\n\nconst FROZEN_ACTIVATED: string[] = Object.freeze([\n constants.UNKNOWN_ROUTE,\n]) as unknown as string[];\nconst FROZEN_REPLACE_OPTS: NavigationOptions = Object.freeze({ replace: true });\n\nfunction forceReplaceFromUnknown(\n opts: NavigationOptions,\n fromState: State | undefined,\n): NavigationOptions {\n return fromState?.name === constants.UNKNOWN_ROUTE && !opts.replace\n ? { ...opts, replace: true }\n : opts;\n}\n\nfunction isSameNavigation(\n fromState: State | undefined,\n opts: NavigationOptions,\n toState: State,\n): boolean {\n return (\n !!fromState &&\n !opts.reload &&\n !opts.force &&\n fromState.path === toState.path\n );\n}\n\n/**\n * Independent namespace for managing navigation.\n *\n * Handles navigate(), navigateToDefault(), navigateToNotFound(), and transition state.\n *\n * Performance: navigate() uses optimistic sync execution — guards run synchronously\n * until one returns a Promise, then switches to async. This eliminates Promise/AbortController\n * overhead for the common case (no guards or sync guards).\n */\nexport class NavigationNamespace {\n // Stryker disable next-line BooleanLiteral: equivalent — reset to false at the top of every navigate()/navigateToState()/navigateToDefault(), so the initializer value is never observed.\n lastSyncResolved = false;\n lastSyncRejected = false;\n #deps!: NavigationDependencies;\n #currentController: AbortController | null = null;\n #navigationId = 0;\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n setDependencies(deps: NavigationDependencies): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n navigate(\n name: string,\n params: Params,\n search: SearchParams | undefined,\n opts: NavigationOptions,\n ): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n // Fast-path sync rejections: cached error + cached Promise.reject\n // No allocations, no throw/catch overhead, facade skips .catch() suppression\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag, not a correctness gate. Not flagging the cached (pre-suppressed) rejection routes the facade to the else-branch, which re-attaches a harmless .catch; no observable difference.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n let toState: State | undefined;\n\n try {\n toState = deps.buildNavigateState(name, params, search);\n } catch (error) {\n /* v8 ignore next 3 -- @preserve: reachable only via validator-driven\n throws from buildNavigateState (validateStateBuilderArgs) — covered\n in @real-router/validation-plugin's suite, not in core. */\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided buildNavigateState\n return Promise.reject(error);\n }\n\n if (!toState) {\n deps.emitTransitionError(\n undefined,\n deps.getState(),\n CACHED_ROUTE_NOT_FOUND_ERROR,\n );\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_ROUTE_NOT_FOUND_REJECTION;\n }\n\n return this.#executeNavigation(toState, opts);\n }\n\n /**\n * Navigate to a fully-built `State` directly, skipping `buildNavigateState`\n * (forwardState + buildPath + meta lookup). Used by URL plugins after they\n * have already produced a `State` from a browser-initiated event via\n * `api.matchPath(url)` — see issue #525.\n *\n * Semantics vs. `navigate(name, params, opts)`:\n * - `forwardState` is NOT re-applied. matchPath already runs it; reapplying\n * is redundant in the idempotent case and can race in the dynamic case.\n * - `buildPath` is NOT re-run. The caller's `state.path` is used as-is —\n * so `trailingSlash:\"preserve\"` matchedState paths flow through unchanged\n * (closes #525 Q2). `buildPath` interceptors do NOT run; the URL the\n * user navigated to is the source of truth for this code path.\n * - All other pipeline steps run unchanged: SAME_STATES check, FSM\n * transition, guards, `subscribeLeave`, `completeTransition`,\n * plugin lifecycle hooks.\n */\n navigateToState(state: State, opts: NavigationOptions): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n // Reject states whose route no longer exists (e.g. the route tree was\n // mutated between matchPath and navigateToState). UNKNOWN_ROUTE is\n // structurally legal — it is the navigateToNotFound output shape.\n if (state.name !== constants.UNKNOWN_ROUTE && !deps.hasRoute(state.name)) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: state.name,\n });\n\n deps.emitTransitionError(undefined, deps.getState(), err);\n\n // This is a FRESH reject (carries `routeName`), not one of the\n // pre-suppressed CACHED_*_REJECTION singletons. `lastSyncRejected`\n // contractually means \"I returned a pre-suppressed cached rejection —\n // skip your .catch()\", so leaving it unset lets the facade attach its\n // own suppression. Setting it here leaked an unhandledRejection on\n // fire-and-forget calls (#721).\n return Promise.reject(err);\n }\n\n // Channel guard, position P3 (#1572). `navigateToState` is the ONE producer\n // that takes a ready-made `State` instead of a `params` argument, so the\n // predicate reads `state.params ∩ queryNames(state.name)`. What it commits\n // becomes `getState()`, so a pre-M2 layout would be silent corruption: the\n // key sits in `state.params` and never reaches `state.path`.\n //\n // Costs nothing on healthy flows — a state produced by core (`matchPath`,\n // `makeState`) is channel-correct by construction, so the predicate is\n // empty on every popstate / memory-restore / SSR-hydration commit. `start()`\n // commits THROUGH here (`RouterLifecycleNamespace`), which is why the guard\n // lives in the namespace rather than on the plugin-API door.\n //\n // Rejects rather than throwing, mirroring the ROUTE_NOT_FOUND guard above:\n // this method returns `Promise<State>` and its URL-plugin callers invoke it\n // from popstate handlers, where a new synchronous throw would be a change\n // of failure shape rather than a new failure.\n const misChanneled = findMisChanneledKey(\n state.params,\n deps.getQueryParams(state.name),\n );\n\n if (misChanneled !== undefined) {\n const err = new RouterError(errorCodes.WRONG_CHANNEL, {\n routeName: state.name,\n message: `[router.navigateToState] ${misChanneledKeyMessage(\n state.name,\n misChanneled,\n \"`state.params`\",\n )}`,\n });\n\n deps.emitTransitionError(undefined, deps.getState(), err);\n\n return Promise.reject(err);\n }\n\n // States from `matchPath` are deeply frozen (`freezeStateInPlace`).\n // `completeTransition` mutates `toState.transition` and `context` is\n // intentionally extensible for plugin claim writes, so we hand the\n // pipeline a writable shell — same shape `makeState(skipFreeze=true)`\n // produces. `params` stays referentially shared (already frozen).\n // `transition` is omitted so completeTransition can assign it.\n const writableState = {\n name: state.name,\n params: state.params,\n // Carry the query channel through the writable shell (RFC-4 M2 / #1548) —\n // without this, start()'s navigateToState(matchPath(...)) would drop the\n // matched query from the committed state.\n search: state.search,\n path: state.path,\n context: { ...state.context },\n } as State;\n\n // No route-meta to carry any more (RFC-4 M2 / #1548): ownership is read from\n // the live matcher by `state.name` (`getTransitionPath`'s `getMeta`), not\n // from a per-State WeakMap. The former #1170 carry — which existed only so a\n // matchPath-derived writable shell stayed non-meta-less across consecutive\n // popstate navs — is obsolete: any state whose name is in the tree takes the\n // STANDARD PATH regardless of object identity.\n\n return this.#executeNavigation(writableState, opts);\n }\n\n navigateToDefault(opts: NavigationOptions): Promise<State> {\n // Reset the sync-resolution flag on entry, mirroring navigate() and\n // navigateToState(). start() leaves `lastSyncResolved = true`, and the\n // early reject paths below return before delegating to navigate(), so a\n // stale `true` would make the facade take the \"already resolved\" branch\n // and skip .catch() suppression — leaking an unhandledRejection on\n // fire-and-forget calls (#721).\n this.lastSyncResolved = false;\n const deps = this.#deps;\n const options = deps.getOptions();\n\n if (!options.defaultRoute) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute not configured\",\n }),\n );\n }\n\n let route: string;\n let params: Params;\n let search: SearchParams;\n\n try {\n ({ route, params, search } = deps.resolveDefault());\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided resolveDefault callback\n return Promise.reject(error);\n }\n\n if (!route) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute resolved to empty\",\n }),\n );\n }\n\n // Both channels, never one bag (RFC-4 M2 / #1548). The query slot took\n // `undefined` until `defaultSearch` existed as a router option, so a\n // query-declared name in `defaultParams` reached the URL only via the\n // `forwardState` seam's channel re-separation — the repair the pipeline\n // design removes. Passing the query here makes the default route's query\n // defaults independent of that stage.\n return this.navigate(route, params, search, opts);\n }\n\n navigateToNotFound(path: string): State {\n // #1186 — liveness gate. This internal commit primitive has no FSM\n // transition of its own, so without this check a `dispose()` that lands\n // while a start-interceptor is parked (FSM already DISPOSED) would let the\n // resuming pipeline commit an UNKNOWN_ROUTE state on the disposed router and\n // `start()` resolve. Symmetric with `navigateToState`'s `canNavigate()` gate\n // (the matched-route branch is already protected). `!isActive()` also covers\n // a merely-stopped (IDLE) router: the only reachable path to that is a direct\n // `router.navigateToNotFound()` on a stopped instance (internal callers run\n // during STARTING, which is active), so the ROUTER_DISPOSED code is slightly\n // broad there — fail-closed is deliberate (committing on a stopped router is\n // out of contract), and the disposed race is the case that matters.\n if (!this.#deps.isActive()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#abortPreviousNavigation();\n\n const fromState = this.#deps.getState();\n const deactivated: string[] = fromState\n ? nameToIDs(fromState.name).toReversed()\n : [];\n\n Object.freeze(deactivated);\n\n const segments: TransitionMeta[\"segments\"] = {\n deactivated,\n activated: FROZEN_ACTIVATED,\n intersection: \"\",\n };\n\n Object.freeze(segments);\n\n const transitionMeta: TransitionMeta = {\n phase: \"activating\",\n ...(fromState && { from: fromState.name }),\n reason: \"success\",\n replace: true,\n segments,\n };\n\n Object.freeze(transitionMeta);\n\n const state: State = {\n name: constants.UNKNOWN_ROUTE,\n params: EMPTY_PARAMS,\n search: EMPTY_SEARCH,\n path,\n transition: transitionMeta,\n context: {},\n };\n\n Object.freeze(state);\n\n this.#deps.setState(state);\n this.#deps.emitTransitionSuccess(state, fromState, FROZEN_REPLACE_OPTS);\n\n return state;\n }\n\n /**\n * Aborts and releases the in-flight navigation's `AbortController` (waking the\n * parked async pipeline via `onInternalAbort`). This is the\n * **effect** of the FSM `CANCEL` action (`handleCancel` → injected\n * `deps.abortCurrentController`), not something cancellation sources call\n * directly — so \"FSM `CANCEL` ⟹ controller aborted\" holds in one place (RFC\n * navigation-cancellation-unification §5). `reason` (e.g. an external\n * `opts.signal`'s reason, #943) becomes the controller's `signal.reason`;\n * defaults to `TRANSITION_CANCELLED`.\n */\n abortCurrentController(reason?: unknown): void {\n this.#currentController?.abort(\n reason ?? new RouterError(errorCodes.TRANSITION_CANCELLED),\n );\n this.#currentController = null;\n }\n\n #executeNavigation(toState: State, opts: NavigationOptions): Promise<State> {\n const deps = this.#deps;\n let fromState: State | undefined;\n let transitionStarted = false;\n let controller: AbortController | null = null;\n\n try {\n fromState = deps.getState();\n opts = forceReplaceFromUnknown(opts, fromState);\n\n if (isSameNavigation(fromState, opts, toState)) {\n deps.emitTransitionError(toState, fromState, CACHED_SAME_STATES_ERROR);\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_SAME_STATES_REJECTION;\n }\n\n this.#abortPreviousNavigation(opts.signal);\n\n // Stryker disable next-line UpdateOperator: equivalent — `#navigationId` is only ever compared by identity (`!== myId`) to detect supersession; uniqueness per navigation is all that matters, so `--` (decreasing ids) is indistinguishable from `++`.\n const myId = ++this.#navigationId;\n\n // #1169 commit-gate — liveness snapshot captured BEFORE the pre-commit\n // listener windows. A listener's `stop()`/`dispose()` runs `clearAll()`,\n // which empties the listener lists, so the marker must be read now, not at\n // the commit site (that self-destruct was the QB/QE hole, RFC §5-bis).\n // `suspendable` is true only when a synchronous supersede is reachable — an\n // external `opts.signal`, `subscribeLeave` listeners, or a pre-commit\n // plugin listener (`onTransitionStart` / `onTransitionLeaveApprove`); the\n // pure synchronous navigate (none of these) is uncancellable and skips the\n // gate, keeping the #307 hot path perf-neutral.\n const suspendable =\n opts.signal !== undefined ||\n deps.hasLeaveListeners() ||\n deps.hasPreCommitListeners();\n\n deps.startTransition(toState, fromState);\n transitionStarted = true;\n\n // Post-`startTransition` supersession is now caught at the commit-gate\n // below (before `completeTransition`'s setState): a `stop()`/`dispose()`/\n // external-abort from the TRANSITION_START listener leaves the FSM in\n // IDLE/DISPOSED, which `!deps.isActive()` detects. (Async supersession is\n // additionally caught in `#finishAsyncNavigation` / the guard pipeline's\n // `isCurrentNav`; a reentrant navigate() is banned — REENTRANT_NAVIGATION.)\n\n const [canDeactivateFunctions, canActivateFunctions] =\n deps.getLifecycleFunctions();\n const isUnknownRoute = toState.name === constants.UNKNOWN_ROUTE;\n\n const transitionPath = getTransitionPath(toState, fromState, (name) =>\n deps.getMetaForState(name),\n );\n const { toDeactivate, toActivate, intersection } = transitionPath;\n\n const shouldDeactivate =\n fromState && !opts.forceDeactivate && toDeactivate.length > 0;\n const shouldActivate = !isUnknownRoute && toActivate.length > 0;\n const hasGuards =\n canDeactivateFunctions.size > 0 || canActivateFunctions.size > 0;\n\n const confirmedToState = toState;\n\n if (!hasGuards) {\n const asyncLeave = this.#handleNoGuardsLeave(\n confirmedToState,\n fromState,\n myId,\n opts,\n transitionPath,\n canDeactivateFunctions,\n );\n\n if (asyncLeave !== undefined) {\n return asyncLeave;\n }\n }\n\n // Stryker disable next-line ConditionalExpression: equivalent — running the guard pipeline on the no-guards path does not double-emit LEAVE_APPROVE (full suite green with `if (true)`); the BlockStatement mutant stays live (killed by guarded-route tests).\n // eslint-disable-next-line unicorn/prefer-else-if -- two exhaustive `if`s read clearer here than an else-if; merging cascades into no-negated-condition / no-unnecessary-condition in this hot guard-setup branch\n if (hasGuards) {\n controller = new AbortController();\n this.#currentController = controller;\n const isCurrentNav = () =>\n this.#navigationId === myId && deps.isActive();\n\n const signal = controller.signal;\n\n const emitLeaveApproveCallback = (): Promise<void> | undefined => {\n deps.sendLeaveApprove(confirmedToState, fromState);\n\n if (deps.hasLeaveListeners()) {\n return deps.awaitLeaveListeners(\n confirmedToState,\n fromState,\n signal,\n );\n }\n\n return undefined;\n };\n\n const guardCompletion = executeGuardPipeline(\n canDeactivateFunctions,\n canActivateFunctions,\n toDeactivate,\n toActivate,\n !!shouldDeactivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isCurrentNav,\n emitLeaveApproveCallback,\n );\n\n if (guardCompletion !== undefined) {\n return this.#finishAsyncNavigation(\n guardCompletion,\n {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n if (!isCurrentNav()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n this.#cleanupController(controller, false);\n }\n\n // #1169 commit-gate — refuse to commit a navigation cancelled or\n // terminated during a listener window, BEFORE `completeTransition`'s\n // setState. The FSM table (D-full) already prevents the forceState\n // resurrection; this prevents the state commit that precedes the emit.\n // Gated on `suspendable` so the pure sync hot path pays nothing. A\n // `stop()`/`dispose()` from the listener lands the FSM in IDLE/DISPOSED\n // (caught by `!isActive()`); an external `opts.signal` abort is caught\n // directly. No `#navigationId` check: a reentrant navigate() (the only\n // thing that could bump it synchronously) is banned (REENTRANT_NAVIGATION,\n // §4), so on this sync path `#navigationId === myId` always holds — async\n // supersede is caught in `#finishAsyncNavigation`'s `isCurrentNav`.\n if (suspendable && (!deps.isActive() || opts.signal?.aborted === true)) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const finalState = completeTransition(deps, {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n });\n\n // Mark sync-resolution only AFTER completeTransition returns. It emits\n // TRANSITION_SUCCESS; listener throws are all isolated via onListenerError\n // (the emitter re-throws nothing now that re-entrant emits are coalesced,\n // #1033, and reentrant navigate/CRUD are banned), so the emit itself does\n // not throw. Should completeTransition throw for any other reason, setting\n // the flag optimistically BEFORE it would leave the flag stale-true, so the\n // facade would read lastSyncResolved and skip its suppressing `.catch()` —\n // the rejection would then leak as a Node unhandledRejection. Post-emit\n // placement keeps the flag false on a throw, so control falls to catch and\n // the facade attaches its `.catch`.\n // Stryker disable next-line BooleanLiteral: equivalent — flipping to false routes the facade to the else-branch, which attaches a harmless .catch to an already-resolved promise; there is no rejection to suppress.\n this.lastSyncResolved = true;\n\n return Promise.resolve(finalState);\n } catch (error) {\n this.#handleNavigateError(\n error,\n controller,\n transitionStarted,\n toState,\n fromState,\n );\n\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from guards or transition pipeline\n return Promise.reject(error);\n }\n }\n\n async #finishAsyncNavigation(\n guardCompletion: Promise<void>,\n nav: NavigationContext,\n controller: AbortController,\n myId: number,\n ): Promise<State> {\n const deps = this.#deps;\n const isActive = () =>\n this.#navigationId === myId &&\n !controller.signal.aborted &&\n deps.isActive();\n\n const externalSignal = nav.opts.signal;\n let onExternalAbort: (() => void) | undefined;\n let onInternalAbort: (() => void) | undefined;\n let succeeded = false;\n let failureReason: unknown;\n\n // #1018: race the guard completion against the controller's abort so a\n // non-cooperative guard whose Promise never settles (and ignores `signal`)\n // cannot wedge navigate() forever. `abortRace` RESOLVES on abort, so the\n // post-race `isActive()` check below throws TRANSITION_CANCELLED — the same\n // path that already handles a guard which swallows the abort and resolves\n // `true`. `stop()`/`dispose()`/supersede all abort the controller. Mirrors\n // the leave-path protection `settleLeavePromises` (#663/#673).\n const abortRace = new Promise<void>((resolve) => {\n if (controller.signal.aborted) {\n resolve();\n\n return;\n }\n\n onInternalAbort = () => {\n resolve();\n };\n\n controller.signal.addEventListener(\"abort\", onInternalAbort, {\n once: true,\n });\n });\n\n // Consume `guardCompletion` when the abort wins the race: a slow or\n // never-settling guard that settles later then has no awaiter, which would\n // surface as an unhandled rejection without this catch.\n guardCompletion.catch(() => {\n /* settlement consumed — the race already decided the navigation */\n });\n\n try {\n if (externalSignal) {\n if (externalSignal.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n\n // Bridge an external `{ signal }` abort onto the internal controller.\n // It is NOT scoped to `controller.signal` (the old `{ signal }` option)\n // because success no longer aborts the controller (#722) — the listener\n // is detached explicitly in `finally` instead.\n onExternalAbort = () => {\n // (#1030): route the external abort through the FSM. The\n // `CANCEL` action aborts the internal controller (waking THIS pipeline)\n // with the external `reason` (#943 — surfaces via the leave signal) AND\n // returns the FSM to READY, atomically. No direct `controller.abort`\n // here — \"FSM CANCEL ⟹ controller aborted\" lives in one place\n // (handleCancel). onExternalAbort only fires while the navigation is in\n // flight (the listener is removed in `finally` once it settles), so the\n // FSM is always cancellable here.\n deps.cancelNavigation(externalSignal.reason);\n };\n // Stryker disable next-line ObjectLiteral: equivalent — `{ once: true }` is redundant: the per-navigation signal aborts at most once and is discarded unaborted on success, and the `finally` block explicitly removeEventListener's it.\n externalSignal.addEventListener(\"abort\", onExternalAbort, {\n // Stryker disable next-line BooleanLiteral: equivalent — `once` redundant (see ObjectLiteral above); the listener is explicitly removed in `finally`.\n once: true,\n });\n }\n\n await Promise.race([guardCompletion, abortRace]);\n\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const state = completeTransition(deps, nav);\n\n succeeded = true;\n\n return state;\n } catch (error) {\n failureReason = error;\n\n routeTransitionError(deps, error, nav.toState, nav.fromState);\n\n throw error;\n // NB: the `} finally {}` BlockStatement mutant SURVIVES but is EQUIVALENT —\n // emptying the finally only skips #cleanupController, which is unobservable\n // (defense-in-depth: on a CANCEL the FSM CANCEL action already aborted+nulled\n // the controller via abortCurrentController, RFC §5; the success-path\n // ref-release is proven unobservable — see #cleanupController's disable). It\n // cannot be inline-`Stryker disable`d: the catch `}` and finally `{` share one\n // line, so there is no comment position that targets the finally body. Left documented.\n } finally {\n // Stryker disable next-line ConditionalExpression,BlockStatement: equivalent — listener cleanup is redundant: the per-navigation signal is discarded on completion, so skipping the removeEventListener leaks nothing observable.\n if (onExternalAbort) {\n // Stryker disable next-line StringLiteral: equivalent — cleanup event name is redundant (listener is `{ once: true }` and the signal is discarded), so a wrong name removes nothing observable.\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n\n // Detach the abort-race listener before #cleanupController aborts the\n // controller below, so the cleanup abort cannot re-fire it. `undefined`\n // only when the controller was already aborted at setup (the early-resolve\n // branch above registered no listener).\n if (onInternalAbort) {\n controller.signal.removeEventListener(\"abort\", onInternalAbort);\n }\n\n // Success drops the controller without aborting (the subscribeLeave signal\n // must stay unaborted); cancel/error aborts it with the originating reason\n // so captured signals expose the real cause via `signal.reason` (#943).\n this.#cleanupController(controller, !succeeded, failureReason);\n }\n }\n\n #handleNavigateError(\n error: unknown,\n controller: AbortController | null,\n transitionStarted: boolean,\n toState: State | undefined,\n fromState: State | undefined,\n ): void {\n if (controller) {\n this.#cleanupController(controller, true, error);\n }\n\n if (transitionStarted && toState) {\n routeTransitionError(this.#deps, error, toState, fromState);\n }\n }\n\n #handleNoGuardsLeave(\n toState: State,\n fromState: State | undefined,\n myId: number,\n opts: NavigationOptions,\n transitionPath: TransitionPath,\n canDeactivateFunctions: Map<string, GuardFn>,\n ): Promise<State> | undefined {\n const deps = this.#deps;\n\n deps.sendLeaveApprove(toState, fromState);\n\n if (deps.hasLeaveListeners()) {\n const controller = new AbortController();\n\n // Track as the current navigation BEFORE listeners run so a reentrant\n // navigate() / stop() / dispose() from a sync listener aborts THIS leave\n // signal — parity with the guard path (#722). On success the controller is\n // released without aborting (see #cleanupController).\n this.#currentController = controller;\n\n let leaveResult: Promise<void> | undefined;\n\n try {\n leaveResult = deps.awaitLeaveListeners(\n toState,\n fromState,\n controller.signal,\n );\n } catch (error) {\n // A sync listener threw — the navigation fails; abort the leave signal\n // with the thrown value so a listener that captured the signal sees the\n // real cause via `signal.reason`, not a generic AbortError (#943).\n this.#cleanupController(controller, true, error);\n\n throw error;\n }\n\n if (leaveResult !== undefined) {\n return this.#finishAsyncNavigation(\n leaveResult,\n {\n toState,\n fromState,\n opts,\n toDeactivate: transitionPath.toDeactivate,\n toActivate: transitionPath.toActivate,\n intersection: transitionPath.intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n // Sync listeners settled. A synchronous reentrant navigate() can no longer\n // supersede here (banned, RFC §4), so the leave always succeeds: release the\n // controller WITHOUT aborting (the subscribeLeave signal must stay live).\n this.#cleanupController(controller, false);\n\n return undefined;\n }\n\n // No leave listeners: nothing synchronous could have superseded this\n // navigation during the LEAVE_APPROVE emit (reentrant navigate is banned).\n return undefined;\n }\n\n /**\n * Release a navigation's AbortController. The same `controller.signal` is\n * handed to `subscribeLeave` listeners, so it must abort **only** when the\n * navigation is cancelled or errors — never on success (#722). On the success\n * path pass `cancelled = false`: the reference is dropped without aborting, so\n * a listener that captured the signal still sees `aborted === false`.\n *\n * On the failure/cancellation path (`cancelled = true`) pass the originating\n * `reason` so `signal.reason` carries router/error context (a `RouterError`,\n * or the value a sync leave listener threw) — consistent with the cancellation\n * abort `RouterError(TRANSITION_CANCELLED)`, not a generic `AbortError` (#943).\n * `abort()` is idempotent: a controller already aborted by a superseding\n * navigation keeps its first (also-meaningful) reason.\n */\n #cleanupController(\n controller: AbortController,\n cancelled: boolean,\n reason?: unknown,\n ): void {\n if (cancelled) {\n controller.abort(reason);\n }\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — controller identity-guard; cleanup correctness is enforced by #abortPreviousNavigation + the navigationId/isCurrentNav checks. Full suite stays green with `=== → !==` (nulls the wrong controller) and with the body removed (ref never nulled), so no mutant here is observable.\n if (this.#currentController === controller) {\n this.#currentController = null;\n }\n }\n\n #abortPreviousNavigation(externalSignal?: AbortSignal): void {\n if (this.#deps.isTransitioning()) {\n this.#deps.logger.warn(\n \"router.navigate\",\n \"Concurrent navigation detected on shared router instance. \" +\n \"For SSR, use cloneRouter() to create isolated instance per request.\",\n );\n // The FSM CANCEL action aborts the previous controller — no\n // direct controller.abort here (RFC navigation-cancellation-unification §5).\n this.#deps.cancelNavigation();\n }\n\n if (externalSignal?.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NavigationNamespace.js","names":["constants","#deps","CACHED_NOT_STARTED_REJECTION","CACHED_ROUTE_NOT_FOUND_ERROR","CACHED_ROUTE_NOT_FOUND_REJECTION","#executeNavigation","RouterError","errorCodes","findMisChanneledKey","misChanneledKeyMessage","#abortPreviousNavigation","nameToIDs","EMPTY_PARAMS","EMPTY_SEARCH","#currentController","CACHED_SAME_STATES_ERROR","CACHED_SAME_STATES_REJECTION","#navigationId","getTransitionPath","#handleNoGuardsLeave","executeGuardPipeline","#finishAsyncNavigation","#cleanupController","completeTransition","#handleNavigateError"],"sources":["../../../../src/namespaces/NavigationNamespace/NavigationNamespace.ts"],"sourcesContent":["import {\n CACHED_NOT_STARTED_REJECTION,\n CACHED_ROUTE_NOT_FOUND_ERROR,\n CACHED_ROUTE_NOT_FOUND_REJECTION,\n CACHED_SAME_STATES_ERROR,\n CACHED_SAME_STATES_REJECTION,\n} from \"./constants\";\nimport { completeTransition } from \"./transition/completeTransition\";\nimport { routeTransitionError } from \"./transition/errorHandling\";\nimport { executeGuardPipeline } from \"./transition/guardPhase\";\nimport { findMisChanneledKey, misChanneledKeyMessage } from \"../../channels\";\nimport {\n EMPTY_PARAMS,\n EMPTY_SEARCH,\n errorCodes,\n constants,\n} from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\nimport { getTransitionPath, nameToIDs } from \"../../transitionPath\";\n\nimport type { NavigationContext, NavigationDependencies } from \"./types\";\nimport type { TransitionPath } from \"../../transitionPath\";\nimport type {\n GuardFn,\n NavigationOptions,\n Params,\n SearchParams,\n State,\n TransitionMeta,\n} from \"../../types\";\n\nconst FROZEN_ACTIVATED: string[] = Object.freeze([\n constants.UNKNOWN_ROUTE,\n]) as unknown as string[];\nconst FROZEN_REPLACE_OPTS: NavigationOptions = Object.freeze({ replace: true });\n\nfunction forceReplaceFromUnknown(\n opts: NavigationOptions,\n fromState: State | undefined,\n): NavigationOptions {\n return fromState?.name === constants.UNKNOWN_ROUTE && !opts.replace\n ? { ...opts, replace: true }\n : opts;\n}\n\nfunction isSameNavigation(\n fromState: State | undefined,\n opts: NavigationOptions,\n toState: State,\n): boolean {\n return (\n !!fromState &&\n !opts.reload &&\n !opts.force &&\n fromState.path === toState.path\n );\n}\n\n/**\n * Independent namespace for managing navigation.\n *\n * Handles navigate(), navigateToDefault(), navigateToNotFound(), and transition state.\n *\n * Performance: navigate() uses optimistic sync execution — guards run synchronously\n * until one returns a Promise, then switches to async. This eliminates Promise/AbortController\n * overhead for the common case (no guards or sync guards).\n */\nexport class NavigationNamespace {\n // Stryker disable next-line BooleanLiteral: equivalent — reset to false at the top of every navigate()/navigateToState()/navigateToDefault(), so the initializer value is never observed.\n lastSyncResolved = false;\n lastSyncRejected = false;\n #deps!: NavigationDependencies;\n #currentController: AbortController | null = null;\n #navigationId = 0;\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n setDependencies(deps: NavigationDependencies): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n navigate(\n name: string,\n params: Params,\n search: SearchParams | undefined,\n opts: NavigationOptions,\n ): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n // Fast-path sync rejections: cached error + cached Promise.reject\n // No allocations, no throw/catch overhead, facade skips .catch() suppression\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag, not a correctness gate. Not flagging the cached (pre-suppressed) rejection routes the facade to the else-branch, which re-attaches a harmless .catch; no observable difference.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n let toState: State | undefined;\n\n try {\n toState = deps.buildNavigateState(name, params, search);\n } catch (error) {\n /* v8 ignore next 3 -- @preserve: reachable only via validator-driven\n throws from buildNavigateState (validateStateBuilderArgs) — covered\n in @real-router/validation-plugin's suite, not in core. */\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided buildNavigateState\n return Promise.reject(error);\n }\n\n if (!toState) {\n deps.emitTransitionError(\n undefined,\n deps.getState(),\n CACHED_ROUTE_NOT_FOUND_ERROR,\n );\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_ROUTE_NOT_FOUND_REJECTION;\n }\n\n return this.#executeNavigation(toState, opts);\n }\n\n /**\n * Navigate to a fully-built `State` directly, skipping `buildNavigateState`\n * (forwardState + buildPath + meta lookup). Used by URL plugins after they\n * have already produced a `State` from a browser-initiated event via\n * `api.matchPath(url)` — see issue #525.\n *\n * Semantics vs. `navigate(name, params, opts)`:\n * - `forwardState` is NOT re-applied. matchPath already runs it; reapplying\n * is redundant in the idempotent case and can race in the dynamic case.\n * - `buildPath` is NOT re-run. The caller's `state.path` is used as-is —\n * so `trailingSlash:\"preserve\"` matchedState paths flow through unchanged\n * (closes #525 Q2). `buildPath` interceptors do NOT run; the URL the\n * user navigated to is the source of truth for this code path.\n * - All other pipeline steps run unchanged: SAME_STATES check, FSM\n * transition, guards, `subscribeLeave`, `completeTransition`,\n * plugin lifecycle hooks.\n */\n navigateToState(state: State, opts: NavigationOptions): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n // Reject states whose route no longer exists (e.g. the route tree was\n // mutated between matchPath and navigateToState). UNKNOWN_ROUTE is\n // structurally legal — it is the navigateToNotFound output shape.\n if (state.name !== constants.UNKNOWN_ROUTE && !deps.hasRoute(state.name)) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: state.name,\n });\n\n deps.emitTransitionError(undefined, deps.getState(), err);\n\n // This is a FRESH reject (carries `routeName`), not one of the\n // pre-suppressed CACHED_*_REJECTION singletons. `lastSyncRejected`\n // contractually means \"I returned a pre-suppressed cached rejection —\n // skip your .catch()\", so leaving it unset lets the facade attach its\n // own suppression. Setting it here leaked an unhandledRejection on\n // fire-and-forget calls (#721).\n return Promise.reject(err);\n }\n\n // Channel guard, position P3 (#1572). `navigateToState` is the ONE producer\n // that takes a ready-made `State` instead of a `params` argument, so the\n // predicate reads `state.params ∩ queryNames(state.name)`. What it commits\n // becomes `getState()`, so a pre-M2 layout would be silent corruption: the\n // key sits in `state.params` and never reaches `state.path`.\n //\n // Costs nothing on healthy flows — a state produced by core (`matchPath`,\n // `makeState`) is channel-correct by construction, so the predicate is\n // empty on every popstate / memory-restore / SSR-hydration commit. `start()`\n // commits THROUGH here (`RouterLifecycleNamespace`), which is why the guard\n // lives in the namespace rather than on the plugin-API door.\n //\n // Rejects rather than throwing, mirroring the ROUTE_NOT_FOUND guard above:\n // this method returns `Promise<State>` and its URL-plugin callers invoke it\n // from popstate handlers, where a new synchronous throw would be a change\n // of failure shape rather than a new failure.\n const misChanneled = findMisChanneledKey(\n state.params,\n deps.getQueryParams(state.name),\n );\n\n if (misChanneled !== undefined) {\n const err = new RouterError(errorCodes.WRONG_CHANNEL, {\n routeName: state.name,\n message: `[router.navigateToState] ${misChanneledKeyMessage(\n state.name,\n misChanneled,\n \"`state.params`\",\n )}`,\n });\n\n deps.emitTransitionError(undefined, deps.getState(), err);\n\n return Promise.reject(err);\n }\n\n // States from `matchPath` are deeply frozen (`freezeStateShell`).\n // `completeTransition` mutates `toState.transition` and `context` is\n // intentionally extensible for plugin claim writes, so we hand the\n // pipeline a writable shell — same shape `makeState(skipFreeze=true)`\n // produces. `params` stays referentially shared (already frozen).\n // `transition` is omitted so completeTransition can assign it.\n const writableState = {\n name: state.name,\n params: state.params,\n // Carry the query channel through the writable shell (RFC-4 M2 / #1548) —\n // without this, start()'s navigateToState(matchPath(...)) would drop the\n // matched query from the committed state.\n search: state.search,\n path: state.path,\n context: { ...state.context },\n } as State;\n\n // No route-meta to carry any more (RFC-4 M2 / #1548): ownership is read from\n // the live matcher by `state.name` (`getTransitionPath`'s `getMeta`), not\n // from a per-State WeakMap. The former #1170 carry — which existed only so a\n // matchPath-derived writable shell stayed non-meta-less across consecutive\n // popstate navs — is obsolete: any state whose name is in the tree takes the\n // STANDARD PATH regardless of object identity.\n\n return this.#executeNavigation(writableState, opts);\n }\n\n navigateToDefault(opts: NavigationOptions): Promise<State> {\n // Reset the sync-resolution flag on entry, mirroring navigate() and\n // navigateToState(). start() leaves `lastSyncResolved = true`, and the\n // early reject paths below return before delegating to navigate(), so a\n // stale `true` would make the facade take the \"already resolved\" branch\n // and skip .catch() suppression — leaking an unhandledRejection on\n // fire-and-forget calls (#721).\n this.lastSyncResolved = false;\n const deps = this.#deps;\n const options = deps.getOptions();\n\n if (!options.defaultRoute) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute not configured\",\n }),\n );\n }\n\n let route: string;\n let params: Params;\n let search: SearchParams;\n\n try {\n ({ route, params, search } = deps.resolveDefault());\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided resolveDefault callback\n return Promise.reject(error);\n }\n\n if (!route) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute resolved to empty\",\n }),\n );\n }\n\n // Both channels, never one bag (RFC-4 M2 / #1548). The query slot took\n // `undefined` until `defaultSearch` existed as a router option, so a\n // query-declared name in `defaultParams` reached the URL only via the\n // `forwardState` seam's channel re-separation — the repair the pipeline\n // design removes. Passing the query here makes the default route's query\n // defaults independent of that stage.\n return this.navigate(route, params, search, opts);\n }\n\n navigateToNotFound(path: string): State {\n // #1186 — liveness gate. This internal commit primitive has no FSM\n // transition of its own, so without this check a `dispose()` that lands\n // while a start-interceptor is parked (FSM already DISPOSED) would let the\n // resuming pipeline commit an UNKNOWN_ROUTE state on the disposed router and\n // `start()` resolve. Symmetric with `navigateToState`'s `canNavigate()` gate\n // (the matched-route branch is already protected). `!isActive()` also covers\n // a merely-stopped (IDLE) router: the only reachable path to that is a direct\n // `router.navigateToNotFound()` on a stopped instance (internal callers run\n // during STARTING, which is active), so the ROUTER_DISPOSED code is slightly\n // broad there — fail-closed is deliberate (committing on a stopped router is\n // out of contract), and the disposed race is the case that matters.\n if (!this.#deps.isActive()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#abortPreviousNavigation();\n\n const fromState = this.#deps.getState();\n const deactivated: string[] = fromState\n ? nameToIDs(fromState.name).toReversed()\n : [];\n\n Object.freeze(deactivated);\n\n const segments: TransitionMeta[\"segments\"] = {\n deactivated,\n activated: FROZEN_ACTIVATED,\n intersection: \"\",\n };\n\n Object.freeze(segments);\n\n const transitionMeta: TransitionMeta = {\n phase: \"activating\",\n ...(fromState && { from: fromState.name }),\n reason: \"success\",\n replace: true,\n segments,\n };\n\n Object.freeze(transitionMeta);\n\n const state: State = {\n name: constants.UNKNOWN_ROUTE,\n params: EMPTY_PARAMS,\n search: EMPTY_SEARCH,\n path,\n transition: transitionMeta,\n context: {},\n };\n\n Object.freeze(state);\n\n this.#deps.setState(state);\n this.#deps.emitTransitionSuccess(state, fromState, FROZEN_REPLACE_OPTS);\n\n return state;\n }\n\n /**\n * Aborts and releases the in-flight navigation's `AbortController` (waking the\n * parked async pipeline via `onInternalAbort`). This is the\n * **effect** of the FSM `CANCEL` action (`handleCancel` → injected\n * `deps.abortCurrentController`), not something cancellation sources call\n * directly — so \"FSM `CANCEL` ⟹ controller aborted\" holds in one place (RFC\n * navigation-cancellation-unification §5). `reason` (e.g. an external\n * `opts.signal`'s reason, #943) becomes the controller's `signal.reason`;\n * defaults to `TRANSITION_CANCELLED`.\n */\n abortCurrentController(reason?: unknown): void {\n this.#currentController?.abort(\n reason ?? new RouterError(errorCodes.TRANSITION_CANCELLED),\n );\n this.#currentController = null;\n }\n\n #executeNavigation(toState: State, opts: NavigationOptions): Promise<State> {\n const deps = this.#deps;\n let fromState: State | undefined;\n let transitionStarted = false;\n let controller: AbortController | null = null;\n\n try {\n fromState = deps.getState();\n opts = forceReplaceFromUnknown(opts, fromState);\n\n if (isSameNavigation(fromState, opts, toState)) {\n deps.emitTransitionError(toState, fromState, CACHED_SAME_STATES_ERROR);\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_SAME_STATES_REJECTION;\n }\n\n this.#abortPreviousNavigation(opts.signal);\n\n // Stryker disable next-line UpdateOperator: equivalent — `#navigationId` is only ever compared by identity (`!== myId`) to detect supersession; uniqueness per navigation is all that matters, so `--` (decreasing ids) is indistinguishable from `++`.\n const myId = ++this.#navigationId;\n\n // #1169 commit-gate — liveness snapshot captured BEFORE the pre-commit\n // listener windows. A listener's `stop()`/`dispose()` runs `clearAll()`,\n // which empties the listener lists, so the marker must be read now, not at\n // the commit site (that self-destruct was the QB/QE hole, RFC §5-bis).\n // `suspendable` is true only when a synchronous supersede is reachable — an\n // external `opts.signal`, `subscribeLeave` listeners, or a pre-commit\n // plugin listener (`onTransitionStart` / `onTransitionLeaveApprove`); the\n // pure synchronous navigate (none of these) is uncancellable and skips the\n // gate, keeping the #307 hot path perf-neutral.\n const suspendable =\n opts.signal !== undefined ||\n deps.hasLeaveListeners() ||\n deps.hasPreCommitListeners();\n\n deps.startTransition(toState, fromState);\n transitionStarted = true;\n\n // Post-`startTransition` supersession is now caught at the commit-gate\n // below (before `completeTransition`'s setState): a `stop()`/`dispose()`/\n // external-abort from the TRANSITION_START listener leaves the FSM in\n // IDLE/DISPOSED, which `!deps.isActive()` detects. (Async supersession is\n // additionally caught in `#finishAsyncNavigation` / the guard pipeline's\n // `isCurrentNav`; a reentrant navigate() is banned — REENTRANT_NAVIGATION.)\n\n const [canDeactivateFunctions, canActivateFunctions] =\n deps.getLifecycleFunctions();\n const isUnknownRoute = toState.name === constants.UNKNOWN_ROUTE;\n\n const transitionPath = getTransitionPath(toState, fromState, (name) =>\n deps.getMetaForState(name),\n );\n const { toDeactivate, toActivate, intersection } = transitionPath;\n\n const shouldDeactivate =\n fromState && !opts.forceDeactivate && toDeactivate.length > 0;\n const shouldActivate = !isUnknownRoute && toActivate.length > 0;\n const hasGuards =\n canDeactivateFunctions.size > 0 || canActivateFunctions.size > 0;\n\n const confirmedToState = toState;\n\n if (!hasGuards) {\n const asyncLeave = this.#handleNoGuardsLeave(\n confirmedToState,\n fromState,\n myId,\n opts,\n transitionPath,\n canDeactivateFunctions,\n );\n\n if (asyncLeave !== undefined) {\n return asyncLeave;\n }\n }\n\n // Stryker disable next-line ConditionalExpression: equivalent — running the guard pipeline on the no-guards path does not double-emit LEAVE_APPROVE (full suite green with `if (true)`); the BlockStatement mutant stays live (killed by guarded-route tests).\n // eslint-disable-next-line unicorn/prefer-else-if -- two exhaustive `if`s read clearer here than an else-if; merging cascades into no-negated-condition / no-unnecessary-condition in this hot guard-setup branch\n if (hasGuards) {\n controller = new AbortController();\n this.#currentController = controller;\n const isCurrentNav = () =>\n this.#navigationId === myId && deps.isActive();\n\n const signal = controller.signal;\n\n const emitLeaveApproveCallback = (): Promise<void> | undefined => {\n deps.sendLeaveApprove(confirmedToState, fromState);\n\n if (deps.hasLeaveListeners()) {\n return deps.awaitLeaveListeners(\n confirmedToState,\n fromState,\n signal,\n );\n }\n\n return undefined;\n };\n\n const guardCompletion = executeGuardPipeline(\n canDeactivateFunctions,\n canActivateFunctions,\n toDeactivate,\n toActivate,\n !!shouldDeactivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isCurrentNav,\n emitLeaveApproveCallback,\n );\n\n if (guardCompletion !== undefined) {\n return this.#finishAsyncNavigation(\n guardCompletion,\n {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n if (!isCurrentNav()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n this.#cleanupController(controller, false);\n }\n\n // #1169 commit-gate — refuse to commit a navigation cancelled or\n // terminated during a listener window, BEFORE `completeTransition`'s\n // setState. The FSM table (D-full) already prevents the forceState\n // resurrection; this prevents the state commit that precedes the emit.\n // Gated on `suspendable` so the pure sync hot path pays nothing. A\n // `stop()`/`dispose()` from the listener lands the FSM in IDLE/DISPOSED\n // (caught by `!isActive()`); an external `opts.signal` abort is caught\n // directly. No `#navigationId` check: a reentrant navigate() (the only\n // thing that could bump it synchronously) is banned (REENTRANT_NAVIGATION,\n // §4), so on this sync path `#navigationId === myId` always holds — async\n // supersede is caught in `#finishAsyncNavigation`'s `isCurrentNav`.\n if (suspendable && (!deps.isActive() || opts.signal?.aborted === true)) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const finalState = completeTransition(deps, {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n });\n\n // Mark sync-resolution only AFTER completeTransition returns. It emits\n // TRANSITION_SUCCESS; listener throws are all isolated via onListenerError\n // (the emitter re-throws nothing now that re-entrant emits are coalesced,\n // #1033, and reentrant navigate/CRUD are banned), so the emit itself does\n // not throw. Should completeTransition throw for any other reason, setting\n // the flag optimistically BEFORE it would leave the flag stale-true, so the\n // facade would read lastSyncResolved and skip its suppressing `.catch()` —\n // the rejection would then leak as a Node unhandledRejection. Post-emit\n // placement keeps the flag false on a throw, so control falls to catch and\n // the facade attaches its `.catch`.\n // Stryker disable next-line BooleanLiteral: equivalent — flipping to false routes the facade to the else-branch, which attaches a harmless .catch to an already-resolved promise; there is no rejection to suppress.\n this.lastSyncResolved = true;\n\n return Promise.resolve(finalState);\n } catch (error) {\n this.#handleNavigateError(\n error,\n controller,\n transitionStarted,\n toState,\n fromState,\n );\n\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from guards or transition pipeline\n return Promise.reject(error);\n }\n }\n\n async #finishAsyncNavigation(\n guardCompletion: Promise<void>,\n nav: NavigationContext,\n controller: AbortController,\n myId: number,\n ): Promise<State> {\n const deps = this.#deps;\n const isActive = () =>\n this.#navigationId === myId &&\n !controller.signal.aborted &&\n deps.isActive();\n\n const externalSignal = nav.opts.signal;\n let onExternalAbort: (() => void) | undefined;\n let onInternalAbort: (() => void) | undefined;\n let succeeded = false;\n let failureReason: unknown;\n\n // #1018: race the guard completion against the controller's abort so a\n // non-cooperative guard whose Promise never settles (and ignores `signal`)\n // cannot wedge navigate() forever. `abortRace` RESOLVES on abort, so the\n // post-race `isActive()` check below throws TRANSITION_CANCELLED — the same\n // path that already handles a guard which swallows the abort and resolves\n // `true`. `stop()`/`dispose()`/supersede all abort the controller. Mirrors\n // the leave-path protection `settleLeavePromises` (#663/#673).\n const abortRace = new Promise<void>((resolve) => {\n if (controller.signal.aborted) {\n resolve();\n\n return;\n }\n\n onInternalAbort = () => {\n resolve();\n };\n\n controller.signal.addEventListener(\"abort\", onInternalAbort, {\n once: true,\n });\n });\n\n // Consume `guardCompletion` when the abort wins the race: a slow or\n // never-settling guard that settles later then has no awaiter, which would\n // surface as an unhandled rejection without this catch.\n guardCompletion.catch(() => {\n /* settlement consumed — the race already decided the navigation */\n });\n\n try {\n if (externalSignal) {\n if (externalSignal.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n\n // Bridge an external `{ signal }` abort onto the internal controller.\n // It is NOT scoped to `controller.signal` (the old `{ signal }` option)\n // because success no longer aborts the controller (#722) — the listener\n // is detached explicitly in `finally` instead.\n onExternalAbort = () => {\n // (#1030): route the external abort through the FSM. The\n // `CANCEL` action aborts the internal controller (waking THIS pipeline)\n // with the external `reason` (#943 — surfaces via the leave signal) AND\n // returns the FSM to READY, atomically. No direct `controller.abort`\n // here — \"FSM CANCEL ⟹ controller aborted\" lives in one place\n // (handleCancel). onExternalAbort only fires while the navigation is in\n // flight (the listener is removed in `finally` once it settles), so the\n // FSM is always cancellable here.\n deps.cancelNavigation(externalSignal.reason);\n };\n // Stryker disable next-line ObjectLiteral: equivalent — `{ once: true }` is redundant: the per-navigation signal aborts at most once and is discarded unaborted on success, and the `finally` block explicitly removeEventListener's it.\n externalSignal.addEventListener(\"abort\", onExternalAbort, {\n // Stryker disable next-line BooleanLiteral: equivalent — `once` redundant (see ObjectLiteral above); the listener is explicitly removed in `finally`.\n once: true,\n });\n }\n\n await Promise.race([guardCompletion, abortRace]);\n\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const state = completeTransition(deps, nav);\n\n succeeded = true;\n\n return state;\n } catch (error) {\n failureReason = error;\n\n routeTransitionError(deps, error, nav.toState, nav.fromState);\n\n throw error;\n // NB: the `} finally {}` BlockStatement mutant SURVIVES but is EQUIVALENT —\n // emptying the finally only skips #cleanupController, which is unobservable\n // (defense-in-depth: on a CANCEL the FSM CANCEL action already aborted+nulled\n // the controller via abortCurrentController, RFC §5; the success-path\n // ref-release is proven unobservable — see #cleanupController's disable). It\n // cannot be inline-`Stryker disable`d: the catch `}` and finally `{` share one\n // line, so there is no comment position that targets the finally body. Left documented.\n } finally {\n // Stryker disable next-line ConditionalExpression,BlockStatement: equivalent — listener cleanup is redundant: the per-navigation signal is discarded on completion, so skipping the removeEventListener leaks nothing observable.\n if (onExternalAbort) {\n // Stryker disable next-line StringLiteral: equivalent — cleanup event name is redundant (listener is `{ once: true }` and the signal is discarded), so a wrong name removes nothing observable.\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n\n // Detach the abort-race listener before #cleanupController aborts the\n // controller below, so the cleanup abort cannot re-fire it. `undefined`\n // only when the controller was already aborted at setup (the early-resolve\n // branch above registered no listener).\n if (onInternalAbort) {\n controller.signal.removeEventListener(\"abort\", onInternalAbort);\n }\n\n // Success drops the controller without aborting (the subscribeLeave signal\n // must stay unaborted); cancel/error aborts it with the originating reason\n // so captured signals expose the real cause via `signal.reason` (#943).\n this.#cleanupController(controller, !succeeded, failureReason);\n }\n }\n\n #handleNavigateError(\n error: unknown,\n controller: AbortController | null,\n transitionStarted: boolean,\n toState: State | undefined,\n fromState: State | undefined,\n ): void {\n if (controller) {\n this.#cleanupController(controller, true, error);\n }\n\n if (transitionStarted && toState) {\n routeTransitionError(this.#deps, error, toState, fromState);\n }\n }\n\n #handleNoGuardsLeave(\n toState: State,\n fromState: State | undefined,\n myId: number,\n opts: NavigationOptions,\n transitionPath: TransitionPath,\n canDeactivateFunctions: Map<string, GuardFn>,\n ): Promise<State> | undefined {\n const deps = this.#deps;\n\n deps.sendLeaveApprove(toState, fromState);\n\n if (deps.hasLeaveListeners()) {\n const controller = new AbortController();\n\n // Track as the current navigation BEFORE listeners run so a reentrant\n // navigate() / stop() / dispose() from a sync listener aborts THIS leave\n // signal — parity with the guard path (#722). On success the controller is\n // released without aborting (see #cleanupController).\n this.#currentController = controller;\n\n let leaveResult: Promise<void> | undefined;\n\n try {\n leaveResult = deps.awaitLeaveListeners(\n toState,\n fromState,\n controller.signal,\n );\n } catch (error) {\n // A sync listener threw — the navigation fails; abort the leave signal\n // with the thrown value so a listener that captured the signal sees the\n // real cause via `signal.reason`, not a generic AbortError (#943).\n this.#cleanupController(controller, true, error);\n\n throw error;\n }\n\n if (leaveResult !== undefined) {\n return this.#finishAsyncNavigation(\n leaveResult,\n {\n toState,\n fromState,\n opts,\n toDeactivate: transitionPath.toDeactivate,\n toActivate: transitionPath.toActivate,\n intersection: transitionPath.intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n // Sync listeners settled. A synchronous reentrant navigate() can no longer\n // supersede here (banned, RFC §4), so the leave always succeeds: release the\n // controller WITHOUT aborting (the subscribeLeave signal must stay live).\n this.#cleanupController(controller, false);\n\n return undefined;\n }\n\n // No leave listeners: nothing synchronous could have superseded this\n // navigation during the LEAVE_APPROVE emit (reentrant navigate is banned).\n return undefined;\n }\n\n /**\n * Release a navigation's AbortController. The same `controller.signal` is\n * handed to `subscribeLeave` listeners, so it must abort **only** when the\n * navigation is cancelled or errors — never on success (#722). On the success\n * path pass `cancelled = false`: the reference is dropped without aborting, so\n * a listener that captured the signal still sees `aborted === false`.\n *\n * On the failure/cancellation path (`cancelled = true`) pass the originating\n * `reason` so `signal.reason` carries router/error context (a `RouterError`,\n * or the value a sync leave listener threw) — consistent with the cancellation\n * abort `RouterError(TRANSITION_CANCELLED)`, not a generic `AbortError` (#943).\n * `abort()` is idempotent: a controller already aborted by a superseding\n * navigation keeps its first (also-meaningful) reason.\n */\n #cleanupController(\n controller: AbortController,\n cancelled: boolean,\n reason?: unknown,\n ): void {\n if (cancelled) {\n controller.abort(reason);\n }\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — controller identity-guard; cleanup correctness is enforced by #abortPreviousNavigation + the navigationId/isCurrentNav checks. Full suite stays green with `=== → !==` (nulls the wrong controller) and with the body removed (ref never nulled), so no mutant here is observable.\n if (this.#currentController === controller) {\n this.#currentController = null;\n }\n }\n\n #abortPreviousNavigation(externalSignal?: AbortSignal): void {\n if (this.#deps.isTransitioning()) {\n this.#deps.logger.warn(\n \"router.navigate\",\n \"Concurrent navigation detected on shared router instance. \" +\n \"For SSR, use cloneRouter() to create isolated instance per request.\",\n );\n // The FSM CANCEL action aborts the previous controller — no\n // direct controller.abort here (RFC navigation-cancellation-unification §5).\n this.#deps.cancelNavigation();\n }\n\n if (externalSignal?.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../channels/guard.js"),t=require("../../constants.js"),n=require("../../helpers.js"),r=require("../../pipeline/canonicalize.js"),i=require("../../pipeline/materialize.js"),a=require("./helpers.js"),o=require("./routesStore.js"),s=require("../../transitionPath.js");function c(e,t){return{name:t??e.segments.at(-1).fullName,params:e.params,search:e.search,meta:e.meta}}var l=class{#e;#t;#n;get#r(){return this.#e.depsStore}constructor(e,t,n){this.#e=o.createRoutesStore(e,t,n)}static shouldUpdateNode(e,t){return(n,r)=>{if(!(n&&typeof n==`object`&&`name`in n))throw TypeError(`[router.shouldUpdateNode] toState must be valid State object`);if(n.transition.reload||e===``)return!0;let{intersection:i,toActivate:a,toDeactivate:o}=s.getTransitionPath(n,r,t);return e===i||a.includes(e)?!0:o.includes(e)}}setDependencies(e){this.#e.depsStore=e}flushPendingGuards(){let e=this.#r;for(let[t,n]of this.#e.pendingCanActivate)e.addActivateGuard(t,n);this.#e.pendingCanActivate.clear();for(let[t,n]of this.#e.pendingCanDeactivate)e.addDeactivateGuard(t,n);this.#e.pendingCanDeactivate.clear()}setLifecycleNamespace(e){this.#e.lifecycleNamespace=e}setRootPath(e){o.applyRootPath(this.#e,e)}hasRoute(e){return this.#e.matcher.hasRoute(e)}clearRoutes(){o.resetStore(this.#e)}buildPath(e,n,i,a){if(e===t.constants.UNKNOWN_ROUTE)return typeof n.path==`string`?n.path:``;let o=r.canonicalize(this.#r.port,e,n,i,{resolveForward:!1});if(typeof this.#e.config.encoders[e]==`function`){let t=this.#e.config.encoders[e]({params:{...o.path},search:{...o.query}});return this.#e.matcher.buildPath(e,t.params,t.search,this.#c(a))}return this.#e.matcher.buildPath(e,o.path,o.query,this.#c(a))}matchPath(t,n){let o=n,s=this.#e.matcher.match(t);if(!s)return;let l=c(s),{name:u,params:d}=l,f=l.search,p=this.#e.config.decoders[u],m;typeof p==`function`?(m=p({params:d,search:f}),this.#r.getValidator()?.routes.validateStateBuilderArgs(u,m.params,`matchPath`),e.assertChannelCorrect(`matchPath`,u,m.params,this.getQueryParams(u),"the `params` returned by this route's `decodeParams`")):m={params:d,search:f};let h=r.canonicalize(this.#r.port,u,m.params,m.search),g=h.name,_=h.path,v=h.query,y=t;if(o.rewritePathOnMatch){let e=typeof this.#e.config.encoders[g]==`function`?this.#e.config.encoders[g]({params:_,search:v}):{params:_,search:v},n=o.trailingSlash;try{y=this.#e.matcher.buildPath(g,e.params,e.search,{trailingSlash:n===`never`||n===`always`?n:void 0,queryParamsMode:o.queryParamsMode}),n===`preserve`&&(y=a.matchSourceTrailingSlash(t,y))}catch{y=t}}return i.materialize(h,{path:y})}forwardState(e,n,r){let i=r??t.EMPTY_SEARCH;if(Object.hasOwn(this.#e.config.forwardFnMap,e)){let t=this.#e.config.forwardFnMap[e],{target:r,chain:a}=this.#l(e,t,n);return this.#s(r,a,n,i)}let a=this.#e.resolvedForwardMap[e]??e;if(a!==e&&Object.hasOwn(this.#e.config.forwardFnMap,a)){let t=this.#e.config.forwardFnMap[a],{target:r,chain:o}=this.#l(a,t,n);return this.#s(r,[...this.#o(e),...o],n,i)}return a===e?{name:e,params:n,search:i}:this.#s(a,this.#o(e),n,i)}buildStateResolved(e,t){let n=this.#e.matcher.getSegmentsByName(e);if(n)return c({segments:n,params:t,search:{},meta:this.#e.matcher.getMetaByName(e)},e)}isActiveRoute(e,n=t.EMPTY_PARAMS,r=t.EMPTY_SEARCH,i=!1,a=!0){if(this.#i(e,n,r,i,a))return!0;if(!Object.hasOwn(this.#e.config.forwardMap,e)&&!Object.hasOwn(this.#e.config.forwardFnMap,e))return!1;let o;try{o=this.forwardState(e,n,r)}catch(t){return this.#r.logger.warn(`router.isActiveRoute`,`Dynamic forwardTo of route "${e}" threw while resolving the active-link destination; treating the link as inactive.`,t),!1}return this.#i(o.name,o.params,o.search,i,a)}getMetaForState(e){return this.#e.matcher.hasRoute(e)?this.#e.matcher.getMetaByName(e):void 0}getUrlParams(e){return a.urlParamsOf(this.#e,e)}getQueryParams(e){return a.queryParamsOf(this.#e,e)}getStore(){return this.#e}getPort(){return this.#r.port}#i(e,t,n,r,i){try{return this.#a(e,t,n,r,i)}catch(t){return this.#r.logger.warn(`router.isActiveRoute`,`Reading the arguments for route "${e}" threw while resolving the active-link state; treating the link as inactive.`,t),!1}}#a(e,t,n,o,s){let c=this.#r.getState();if(!c)return!1;let l=c.name;if(l!==e&&!l.startsWith(`${e}.`)&&!e.startsWith(`${l}.`))return!1;let u=r.canonicalize(this.#r.port,e,t,n,{resolveForward:!1});return o||l===e?this.#r.areStatesEqual(i.materialize(u,{path:``,skipFreeze:!0}),c,s):!(!l.startsWith(`${e}.`)||!a.paramsMatch(u.path,c.params)||!s&&!a.paramsMatch(u.query,c.search))}#o(e){let t=[],n=e;for(;Object.hasOwn(this.#e.config.forwardMap,n);)t.push(n),n=this.#e.config.forwardMap[n];return t}#s(e,t,r,i){let a,o;for(let e of t)a=n.mergeDefined(this.#e.config.defaultParams[e],a),o=n.mergeDefined(this.#e.config.defaultSearch[e],o);return{name:e,params:n.mergeDefined(a,r),search:n.mergeDefined(o,i)}}#c(e){if(this.#t)return e!==this.#n&&this.#r.logger.warn(`router.buildPath`,"`options` differs from the cached source reference; router options are immutable per router instance, so the first-cached buildPath options are reused (#957)."),this.#t;this.#n=e;let t=e?.trailingSlash;return this.#t=Object.freeze({trailingSlash:t===`never`||t===`always`?t:void 0,queryParamsMode:e?.queryParamsMode}),this.#t}#l(e,t,n){let r=new Set([e]),i=[e],a=t(this.#r.getDependency,n),o=0;if(typeof a!=`string`)throw TypeError(`forwardTo callback must return a string, got ${typeof a}`);for(;o<100;){if(this.#e.matcher.getSegmentsByName(a)===void 0)throw Error(`Route "${a}" does not exist`);if(r.has(a)){let e=[...r,a].join(` → `);throw Error(`Circular forwardTo: ${e}`)}if(r.add(a),Object.hasOwn(this.#e.config.forwardFnMap,a)){let e=this.#e.config.forwardFnMap[a];i.push(a),a=e(this.#r.getDependency,n),o++;continue}let e=this.#e.config.forwardMap[a];if(e!==void 0){i.push(a),a=e,o++;continue}return{target:a,chain:i}}throw Error(`forwardTo exceeds maximum depth of 100`)}};exports.RoutesNamespace=l;
|
|
1
|
+
const e=require("../../channels/guard.js"),t=require("../../constants.js"),n=require("../../helpers.js"),r=require("../../pipeline/canonicalize.js"),i=require("../../pipeline/materialize.js"),a=require("./helpers.js"),o=require("./routesStore.js"),s=require("../../transitionPath.js");function c(e,t){return{name:t??e.segments.at(-1).fullName,params:e.params,search:e.search,meta:e.meta}}var l=class{#e;#t;#n;get#r(){return this.#e.depsStore}constructor(e,t,n){this.#e=o.createRoutesStore(e,t,n)}static shouldUpdateNode(e,t){return(n,r)=>{if(!(n&&typeof n==`object`&&`name`in n))throw TypeError(`[router.shouldUpdateNode] toState must be valid State object`);if(n.transition.reload||e===``)return!0;let{intersection:i,toActivate:a,toDeactivate:o}=s.getTransitionPath(n,r,t);return e===i||a.includes(e)?!0:o.includes(e)}}setDependencies(e){this.#e.depsStore=e}flushPendingGuards(){let e=this.#r;for(let[t,n]of this.#e.pendingCanActivate)e.addActivateGuard(t,n);this.#e.pendingCanActivate.clear();for(let[t,n]of this.#e.pendingCanDeactivate)e.addDeactivateGuard(t,n);this.#e.pendingCanDeactivate.clear()}setLifecycleNamespace(e){this.#e.lifecycleNamespace=e}setRootPath(e){o.applyRootPath(this.#e,e)}hasRoute(e){return this.#e.matcher.hasRoute(e)}clearRoutes(){o.resetStore(this.#e)}buildPath(e,n,i,a){if(e===t.constants.UNKNOWN_ROUTE)return typeof n.path==`string`?n.path:``;let o=r.canonicalize(this.#r.port,e,n,i,{resolveForward:!1});if(typeof this.#e.config.encoders[e]==`function`){let t=this.#e.config.encoders[e]({params:{...o.path},search:{...o.query}});return this.#e.matcher.buildPath(e,t.params,t.search,this.#c(a))}return this.#e.matcher.buildPath(e,o.path,o.query,this.#c(a))}matchPath(t,n){let o=n,s=this.#e.matcher.match(t);if(!s)return;let l=c(s),{name:u,params:d}=l,f=l.search,p=this.#e.config.decoders[u],m;typeof p==`function`?(m=p({params:d,search:f}),this.#r.getValidator()?.routes.validateStateBuilderArgs(u,m.params,`matchPath`),e.assertChannelCorrect(`matchPath`,u,m.params,this.getQueryParams(u),"the `params` returned by this route's `decodeParams`")):m={params:d,search:f};let h=r.canonicalize(this.#r.port,u,m.params,m.search),g=h.name,_=h.path,v=h.query,y=t;if(o.rewritePathOnMatch){let e=typeof this.#e.config.encoders[g]==`function`?this.#e.config.encoders[g]({params:_,search:v}):{params:_,search:v},n=o.trailingSlash;try{y=this.#e.matcher.buildPath(g,e.params,e.search,{trailingSlash:n===`never`||n===`always`?n:void 0,queryParamsMode:o.queryParamsMode}),n===`preserve`&&(y=a.matchSourceTrailingSlash(t,y))}catch{y=t}}return i.materialize(h,{path:y})}forwardState(e,n,r){let i=r??t.EMPTY_SEARCH;if(Object.hasOwn(this.#e.config.forwardFnMap,e)){let t=this.#e.config.forwardFnMap[e],{target:r,chain:a}=this.#l(e,t,n);return this.#s(r,a,n,i)}let a=this.#e.resolvedForwardMap[e]??e;if(a!==e&&Object.hasOwn(this.#e.config.forwardFnMap,a)){let t=this.#e.config.forwardFnMap[a],{target:r,chain:o}=this.#l(a,t,n);return this.#s(r,[...this.#o(e),...o],n,i)}return a===e?{name:e,params:n,search:i}:this.#s(a,this.#o(e),n,i)}buildStateResolved(e,t){let n=this.#e.matcher.getSegmentsByName(e);if(n)return c({segments:n,params:t,search:{},meta:this.#e.matcher.getMetaByName(e)},e)}isActiveRoute(e,n=t.EMPTY_PARAMS,r=t.EMPTY_SEARCH,i=!1,a=!0){if(this.#i(e,n,r,i,a))return!0;if(!this.#e.hasAnyForward||!Object.hasOwn(this.#e.config.forwardMap,e)&&!Object.hasOwn(this.#e.config.forwardFnMap,e))return!1;let o;try{o=this.forwardState(e,n,r)}catch(t){return this.#r.logger.warn(`router.isActiveRoute`,`Dynamic forwardTo of route "${e}" threw while resolving the active-link destination; treating the link as inactive.`,t),!1}return this.#i(o.name,o.params,o.search,i,a)}getMetaForState(e){return this.#e.matcher.hasRoute(e)?this.#e.matcher.getMetaByName(e):void 0}getUrlParams(e){return a.urlParamsOf(this.#e,e)}getQueryParams(e){return a.queryParamsOf(this.#e,e)}getStore(){return this.#e}getPort(){return this.#r.port}#i(e,t,n,r,i){try{return this.#a(e,t,n,r,i)}catch(t){return this.#r.logger.warn(`router.isActiveRoute`,`Reading the arguments for route "${e}" threw while resolving the active-link state; treating the link as inactive.`,t),!1}}#a(e,t,n,o,s){let c=this.#r.getState();if(!c)return!1;let l=c.name;if(l!==e&&!l.startsWith(`${e}.`)&&!e.startsWith(`${l}.`))return!1;let u=r.canonicalize(this.#r.port,e,t,n,{resolveForward:!1});return o||l===e?this.#r.areStatesEqual(i.materialize(u,{path:``,skipFreeze:!0}),c,s):!(!l.startsWith(`${e}.`)||!a.paramsMatch(u.path,c.params)||!s&&!a.paramsMatch(u.query,c.search))}#o(e){let t=[],n=e;for(;Object.hasOwn(this.#e.config.forwardMap,n);)t.push(n),n=this.#e.config.forwardMap[n];return t}#s(e,t,r,i){let a,o;for(let e of t)a=n.mergeDefined(this.#e.config.defaultParams[e],a),o=n.mergeDefined(this.#e.config.defaultSearch[e],o);return{name:e,params:n.mergeDefined(a,r),search:n.mergeDefined(o,i)}}#c(e){if(this.#t)return e!==this.#n&&this.#r.logger.warn(`router.buildPath`,"`options` differs from the cached source reference; router options are immutable per router instance, so the first-cached buildPath options are reused (#957)."),this.#t;this.#n=e;let t=e?.trailingSlash;return this.#t=Object.freeze({trailingSlash:t===`never`||t===`always`?t:void 0,queryParamsMode:e?.queryParamsMode}),this.#t}#l(e,t,n){let r=new Set([e]),i=[e],a=t(this.#r.getDependency,n),o=0;if(typeof a!=`string`)throw TypeError(`forwardTo callback must return a string, got ${typeof a}`);for(;o<100;){if(this.#e.matcher.getSegmentsByName(a)===void 0)throw Error(`Route "${a}" does not exist`);if(r.has(a)){let e=[...r,a].join(` → `);throw Error(`Circular forwardTo: ${e}`)}if(r.add(a),Object.hasOwn(this.#e.config.forwardFnMap,a)){let e=this.#e.config.forwardFnMap[a];i.push(a),a=e(this.#r.getDependency,n),o++;continue}let e=this.#e.config.forwardMap[a];if(e!==void 0){i.push(a),a=e,o++;continue}return{target:a,chain:i}}throw Error(`forwardTo exceeds maximum depth of 100`)}};exports.RoutesNamespace=l;
|
|
2
2
|
//# sourceMappingURL=RoutesNamespace.js.map
|