@real-router/core 0.82.0 → 0.82.2

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.
Files changed (56) hide show
  1. package/dist/cjs/Router-DunclyU6.js +2 -0
  2. package/dist/cjs/Router-DunclyU6.js.map +1 -0
  3. package/dist/cjs/Router.d.ts +13 -0
  4. package/dist/cjs/Router.d.ts.map +1 -1
  5. package/dist/cjs/Router.js +1 -1
  6. package/dist/cjs/Router.js.map +1 -1
  7. package/dist/cjs/api.js +1 -2
  8. package/dist/cjs/helpers.js +1 -1
  9. package/dist/cjs/helpers.js.map +1 -1
  10. package/dist/cjs/index.js +1 -2
  11. package/dist/cjs/namespaces/NavigationNamespace/InFlightNavigation.js +2 -0
  12. package/dist/cjs/namespaces/NavigationNamespace/InFlightNavigation.js.map +1 -0
  13. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +1 -1
  14. package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +1 -1
  15. package/dist/cjs/namespaces/NavigationNamespace/constants.js +1 -1
  16. package/dist/cjs/namespaces/NavigationNamespace/constants.js.map +1 -1
  17. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js +1 -1
  18. package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js.map +1 -1
  19. package/dist/cjs/namespaces/NavigationNamespace/transition/executeNavigation.js +2 -0
  20. package/dist/cjs/namespaces/NavigationNamespace/transition/executeNavigation.js.map +1 -0
  21. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js +1 -1
  22. package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js.map +1 -1
  23. package/dist/cjs/namespaces/NavigationNamespace/transition/navigateToNotFound.js +2 -0
  24. package/dist/cjs/namespaces/NavigationNamespace/transition/navigateToNotFound.js.map +1 -0
  25. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +1 -1
  26. package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +1 -1
  27. package/dist/cjs/namespaces/RoutesNamespace/helpers.js +1 -1
  28. package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +1 -1
  29. package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +19 -0
  30. package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts.map +1 -1
  31. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +1 -1
  32. package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +1 -1
  33. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +1 -1
  34. package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +1 -1
  35. package/dist/cjs/pipeline/canonicalize.js +1 -1
  36. package/dist/cjs/pipeline/canonicalize.js.map +1 -1
  37. package/dist/cjs/pipeline/materialize.js +1 -1
  38. package/dist/cjs/pipeline/materialize.js.map +1 -1
  39. package/dist/cjs/pipeline/port.d.ts +8 -0
  40. package/dist/cjs/pipeline/port.d.ts.map +1 -1
  41. package/dist/cjs/validation.js +1 -2
  42. package/dist/esm/Router-yitiDzP1.mjs +2 -0
  43. package/dist/esm/Router-yitiDzP1.mjs.map +1 -0
  44. package/dist/esm/Router.d.mts +13 -0
  45. package/dist/esm/Router.d.mts.map +1 -1
  46. package/dist/esm/api.mjs +1 -1
  47. package/dist/esm/index.mjs +1 -1
  48. package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +19 -0
  49. package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts.map +1 -1
  50. package/dist/esm/pipeline/port.d.mts +8 -0
  51. package/dist/esm/pipeline/port.d.mts.map +1 -1
  52. package/package.json +1 -1
  53. package/dist/cjs/Router-CCmcEi-S.js +0 -2
  54. package/dist/cjs/Router-CCmcEi-S.js.map +0 -1
  55. package/dist/esm/Router-DEpNuwBW.mjs +0 -2
  56. package/dist/esm/Router-DEpNuwBW.mjs.map +0 -1
@@ -34,6 +34,19 @@ declare class Router<Dependencies extends DefaultDependencies = DefaultDependenc
34
34
  areStatesEqual(state1: State | undefined, state2: State | undefined, ignoreQueryParams?: boolean): boolean;
35
35
  shouldUpdateNode(nodeName: string): (toState: State, fromState?: State) => boolean;
36
36
  isActive(): boolean;
37
+ /**
38
+ * ONE fire-and-forget checkpoint for `start()`, deliberately — the same shape
39
+ * `NavigationNamespace.#settle` gives the navigate family, and for the same
40
+ * reason: a `.catch()` remembered at each `return` site is a thing that can be
41
+ * forgotten, and a forgotten one is invisible until it leaks.
42
+ *
43
+ * It HAD been forgotten (#1605). The `ALREADY_STARTED` rejection left through
44
+ * an early `return` above the suppressor, so a second, unawaited `start()`
45
+ * raised an `unhandledRejection` — process-fatal under Node 22+'s default
46
+ * `--unhandled-rejections=throw`, with a stack pointing at the cached error's
47
+ * module constant rather than at the caller. Every return site now leaves
48
+ * through `#runStart`, so no future early return can reopen it.
49
+ */
37
50
  start(startPath: string): Promise<State>;
38
51
  stop(): this;
39
52
  dispose(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"Router.d.ts","names":[],"sources":["../../src/Router.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;cAkGa,OACX,qBAAqB,sBAAsB,gCAChC,SAAgB;;GAC1B;;;;;;EA4CC,YAAA,SAAQ,MAAM,iBACd,UAAS,QAAQ,QAAQ,gBACzB,eAAc;EAsXhB,cACE,cACA,SAAS,QACT,SAAS,cACT,0BACA;EAmCF,UAAU,eAAe,SAAS,QAAQ,SAAS;EAgBnD,SAAS,UAAU,SAAS,WAAW,MAAM;EAI7C,oBAAoB;EAIpB,eACE,QAAQ,mBACR,QAAQ,mBACR;EAWF,iBACE,oBACE,SAAS,OAAO,YAAY;EAYhC;EAIA,MAAM,oBAAoB,QAAQ;EA4DlC;EAyBA;EAsDA,cAAc,cAAc,SAAS,QAAQ,SAAS;EAuItD,aACK,UAAU,cAAc,8CAC1B;EAyCH,UAAU,UAAU,cAAc;EAMlC,eAAe,UAAU,UAAU;EAMnC;EAQA,SACE,QAAQ,kBACR,UAAU,oBACT,QAAQ;EACX,SACE,mBACA,cAAc,QACd,cAAc,cACd,UAAU,oBACT,QAAQ;EA8DX,kBAAkB,UAAU,oBAAoB,QAAQ;EA2BxD,mBAAmB,gBAAgB"}
1
+ {"version":3,"file":"Router.d.ts","names":[],"sources":["../../src/Router.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;cAmFa,OACX,qBAAqB,sBAAsB,gCAChC,SAAgB;;GAC1B;;;;;;EA4CC,YAAA,SAAQ,MAAM,iBACd,UAAS,QAAQ,QAAQ,gBACzB,eAAc;EAoWhB,cACE,cACA,SAAS,QACT,SAAS,cACT,0BACA;EAmCF,UAAU,eAAe,SAAS,QAAQ,SAAS;EAgBnD,SAAS,UAAU,SAAS,WAAW,MAAM;EAI7C,oBAAoB;EAIpB,eACE,QAAQ,mBACR,QAAQ,mBACR;EAWF,iBACE,oBACE,SAAS,OAAO,YAAY;EAYhC;;;;;;;;;;;;;;EAiBA,MAAM,oBAAoB,QAAQ;EAQlC;EAyBA;EAsDA,cAAc,cAAc,SAAS,QAAQ,SAAS;EAuItD,aACK,UAAU,cAAc,8CAC1B;EAyCH,UAAU,UAAU,cAAc;EAMlC,eAAe,UAAU,UAAU;EAMnC;EAQA,SACE,QAAQ,kBACR,UAAU,oBACT,QAAQ;EACX,SACE,mBACA,cAAc,QACd,cAAc,cACd,UAAU,oBACT,QAAQ;EAqDX,kBAAkB,UAAU,oBAAoB,QAAQ;EAiBxD,mBAAmB,gBAAgB"}
@@ -1,2 +1,2 @@
1
- const e=require("./channels/guard.js"),t=require("./constants.js"),n=require("./guards.js"),r=require("./helpers.js"),i=require("./internals.js"),a=require("./limits.js"),o=require("./namespaces/DependenciesNamespace/dependenciesStore.js"),s=require("./namespaces/OptionsNamespace/OptionsNamespace.js"),c=require("./pipeline/canonicalize.js"),l=require("./pipeline/buildURL.js"),u=require("./pipeline/materialize.js"),d=require("./namespaces/StateNamespace/StateNamespace.js"),f=require("./namespaces/PluginsNamespace/PluginsNamespace.js"),p=require("./namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js"),m=require("./transitionPath.js"),h=require("./namespaces/RoutesNamespace/RoutesNamespace.js"),g=require("./RouterError.js"),_=require("./namespaces/NavigationNamespace/NavigationNamespace.js"),v=require("./namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js"),y=require("./routerFSM.js"),b=require("./namespaces/EventBusNamespace/EventBusNamespace.js"),x=require("./namespaces/RouterLifecycleNamespace/constants.js"),S=require("./utils/event-emitter/EventEmitter.js"),C=require("./utils/logger/RouterLogger.js"),w=require("./wiring/wireNamespaces.js"),T=Object.freeze({}),E=new Set([t.errorCodes.SAME_STATES,t.errorCodes.TRANSITION_CANCELLED,t.errorCodes.ROUTER_NOT_STARTED,t.errorCodes.ROUTE_NOT_FOUND,t.errorCodes.CANNOT_ACTIVATE,t.errorCodes.CANNOT_DEACTIVATE]);var D=class D{#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u;#d;constructor(r=[],c={},l={}){let{logger:u,...m}=c;u&&n.assertLoggerConfig(u);let g=new C.RouterLogger(u);this.#u=e=>{D.#f(e)||g.error(`router.navigate`,`Unexpected navigation error`,e)},this.#d=e=>{D.#f(e)||g.error(`router.start`,`Unexpected start error`,e)},s.OptionsNamespace.validateOptionsIsObject(c),n.guardDependencies(l),r.length>0&&n.guardRouteStructure(r),this.#e=new s.OptionsNamespace(m),this.#t=a.createLimits(m.limits),this.#n=o.createDependenciesStore(l),this.#r=new d.StateNamespace,this.#i=new h.RoutesNamespace(r,k(this.#e.get()),g),this.#a=new p.RouteLifecycleNamespace,this.#o=new f.PluginsNamespace,this.#s=new _.NavigationNamespace,this.#c=new v.RouterLifecycleNamespace;let x=y.createRouterFSM(),E=new S.EventEmitter({onListenerError:(e,t)=>{g.error(`Router`,`Error in listener for ${e}:`,t)},onListenerWarn:(e,t)=>{g.warn(`router.addEventListener`,`Event "${e}" has ${t} listeners — possible memory leak`)}});this.#l=new b.EventBusNamespace({routerFSM:x,emitter:E,abortController:e=>{this.#s.abortCurrentController(e)}});let O=new Map,A=i.createTernaryInterceptable(`forwardState`,(e,t,n)=>this.#i.forwardState(e,t,n),O);i.registerInternals(this,{logger:g,makeState:(e,t,n,r)=>this.#r.makeState(e,t,n,r),getMetaForState:e=>this.#i.getMetaForState(e),getQueryParams:e=>this.#i.getQueryParams(e),forwardState:((n,r,i)=>{let a=A(n,r,i);return e.assertChannelCorrect(`forwardState`,a.name,a.params,this.#i.getQueryParams(a.name),()=>a.name===n?"the `params` bag leaving the forwardState chain":`the \`params\` bag leaving the forwardState chain (forwarded here from "${n}")`),{name:a.name,params:a.params??t.EMPTY_PARAMS,search:a.search}}),buildStateResolved:(e,t)=>this.#i.buildStateResolved(e,t),port:()=>this.#i.getPort(),matchPath:(e,t)=>this.#i.matchPath(e,t),getOptions:()=>this.#e.get(),addEventListener:(e,t)=>this.#l.addEventListener(e,t),treeChanged:{emit:e=>{this.#l.emitTreeChanged(e)},subscribe:e=>this.#l.subscribeTreeChanged(e),listenerCount:()=>this.#l.treeChangedListenerCount(),isEmitting:()=>this.#l.isEmittingTreeChanged()},buildPath:i.createTernaryInterceptable(`buildPath`,(e,n,r)=>this.#i.buildPath(e,n??t.EMPTY_PARAMS,r,this.#e.get()),O),emitTransitionError:e=>{this.#l.sendFailSafe(void 0,this.#r.get(),e)},emitTransitionSuccess:(e,t,n)=>{this.#l.emitTransitionSuccess(e,t,n)},navigateToNotFound:e=>this.#s.navigateToNotFound(e),start:i.createInterceptable(`start`,e=>this.#c.start(e),O),navigateToState:(e,t)=>{this.#m();let n=this.#s.navigateToState(e,t??T);return this.#s.lastSyncResolved?this.#s.lastSyncResolved=!1:this.#s.lastSyncRejected?this.#s.lastSyncRejected=!1:this.#p(n),n},interceptors:O,setRootPath:e=>{this.#i.setRootPath(e)},getRootPath:()=>this.#i.getStore().rootPath,getTree:()=>this.#i.getStore().tree,isDisposed:()=>this.#l.isDisposed(),validator:null,dependenciesGetStore:()=>this.#n,getCloneState:()=>({options:{...this.#e.get()},dependencies:{...this.#n.dependencies},pluginFactories:this.#o.getAll(),loggerConfig:g.getConfig()}),routeGetStore:()=>this.#i.getStore(),getStateName:()=>this.#r.get()?.name,isTransitioning:()=>this.#l.isTransitioning(),clearState:()=>{this.#r.set(void 0)},setState:e=>{this.#r.set(e)},routerExtensions:[],contextClaimRecords:new Set,hydrationState:null}),w.wireNamespaces({router:this,options:this.#e,limits:this.#t,dependenciesStore:this.#n,state:this.#r,routes:this.#i,routeLifecycle:this.#a,plugins:this.#o,navigation:this.#s,lifecycle:this.#c,eventBus:this.#l}),this.isActiveRoute=this.isActiveRoute.bind(this),this.buildPath=this.buildPath.bind(this),this.getState=this.getState.bind(this),this.getPreviousState=this.getPreviousState.bind(this),this.areStatesEqual=this.areStatesEqual.bind(this),this.shouldUpdateNode=this.shouldUpdateNode.bind(this),this.isActive=this.isActive.bind(this),this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.dispose=this.dispose.bind(this),this.canNavigateTo=this.canNavigateTo.bind(this),this.usePlugin=this.usePlugin.bind(this),this.navigate=this.navigate.bind(this),this.navigateToDefault=this.navigateToDefault.bind(this),this.navigateToNotFound=this.navigateToNotFound.bind(this),this.subscribe=this.subscribe.bind(this),this.subscribeLeave=this.subscribeLeave.bind(this),this.isLeaveApproved=this.isLeaveApproved.bind(this);try{this.#i.flushPendingGuards()}catch(e){throw this.dispose(),e}}isActiveRoute(e,t,n,r,a){return i.getInternals(this).validator?.routes.validateIsActiveRouteArgs(e,t,r,a),i.getInternals(this).validator?.routes.validateRouteName(e,`isActiveRoute`),e===``?(i.getInternals(this).logger.warn(`real-router`,`isActiveRoute("") called with empty string. Root node is not considered a parent of any route.`),!1):this.#i.isActiveRoute(e,t,n,r,a)}buildPath(e,t,n){let a=i.getInternals(this);return a.validator?.routes.validateBuildPathArgs(e),a.validator?.navigation.validateParams(t,`buildPath`),a.buildPath(e,r.normalizeParams(t),n)}getState(){return this.#r.get()}getPreviousState(){return this.#r.getPrevious()}areStatesEqual(e,t,n=!0){return i.getInternals(this).validator?.state.validateAreStatesEqualArgs(e,t,n),this.#r.areStatesEqual(e,t,n)}shouldUpdateNode(e){return i.getInternals(this).validator?.routes.validateShouldUpdateNodeArgs(e),h.RoutesNamespace.shouldUpdateNode(e,e=>this.#i.getMetaForState(e))}isActive(){return this.#l.isActive()}start(e){if(!this.#l.canStart())return Promise.reject(x.CACHED_ALREADY_STARTED_ERROR);i.getInternals(this).validator?.navigation.validateStartArgs(e),this.#l.sendStart();let t;try{let n=i.getInternals(this).start(e);t=typeof n?.then==`function`?n:Promise.reject(TypeError("[router.start] a `start` interceptor returned without calling next(). Every start interceptor must return `next(path)`."))}catch(e){t=Promise.reject(e)}let n=t.catch(e=>this.#h(e));return this.#p(n,this.#d),n}stop(){return this.#l.sendCancelIfPossible(this.#r.get()),!this.#l.isReady()&&!this.#l.isTransitioning()&&!this.#l.isStarting()?this:(this.#c.stop(),this.#l.sendStop(),this)}dispose(){if(this.#l.isDisposed())return;this.#l.sendCancelIfPossible(this.#r.get()),(this.#l.isReady()||this.#l.isTransitioning())&&(this.#c.stop(),this.#l.sendStop()),this.#l.sendDispose(),this.#l.clearAll(),this.#o.disposeAll();let e=i.getInternals(this);for(let t of e.routerExtensions)for(let e of t.keys)delete this[e];e.routerExtensions.length=0,e.contextClaimRecords.clear(),e.interceptors.clear(),this.#i.clearRoutes(),this.#a.clearAll(),this.#r.reset(),this.#n.dependencies=Object.create(null),this.#g()}canNavigateTo(n,r,a){let o=i.getInternals(this);if(o.validator?.routes.validateRouteName(n,`canNavigateTo`),o.validator?.navigation.validateParams(r,`canNavigateTo`),!this.#i.hasRoute(n)||e.findMisChanneledKey(r,this.#i.getQueryParams(n))!==void 0)return!1;let s=this.#i.getPort(),d;try{d=c.canonicalize(s,n,r??t.EMPTY_PARAMS,a)}catch(e){return o.logger.warn(`router.canNavigateTo`,`Resolving route "${n}" threw while answering the predicate; treating the route as unreachable.`,e),!1}let f;try{f=u.materialize(d,{path:l.buildURL(d,s),skipFreeze:!0})}catch{return!1}let p=this.#r.get(),{toDeactivate:h,toActivate:g}=m.getTransitionPath(f,p,e=>this.#i.getMetaForState(e));return this.#a.canNavigateTo(h,g,f,p)}usePlugin(...e){if(this.#l.isDisposed())throw new g.RouterError(t.errorCodes.ROUTER_DISPOSED);let n=e.filter(Boolean);if(n.length===0)return()=>{};let r=i.getInternals(this);r.validator?.plugins.validatePluginLimit(this.#o.count(),this.#t);for(let e of n)r.validator?.plugins.validateNoDuplicatePlugins(e,this.#o.getAll());return this.#o.use(...n)}subscribe(e){return b.EventBusNamespace.validateSubscribeListener(e),this.#l.subscribe(e)}subscribeLeave(e){return b.EventBusNamespace.validateSubscribeLeaveListener(e),this.#l.subscribeLeave(e)}isLeaveApproved(){return this.#l.isLeaveApproved()}navigate(e,n,r,a){this.#m();let o=i.getInternals(this),s,c,l,u;typeof e==`object`&&e?(s=e.name,c=e.params,l=e.search,u=n??T):(s=e,c=n,l=r,u=a??T),i.throwOnMisChanneledKey(o,`navigate`,s,c),o.validator?.navigation.validateNavigateArgs(s),o.validator?.navigation.validateParams(c,`navigate`),o.validator?.navigation.validateNavigationOptions(u,`navigate`);let d=this.#s.navigate(s,c??t.EMPTY_PARAMS,l,u);return this.#s.lastSyncResolved?this.#s.lastSyncResolved=!1:this.#s.lastSyncRejected?this.#s.lastSyncRejected=!1:this.#p(d),d}navigateToDefault(e){this.#m();let t=i.getInternals(this);t.validator?.navigation.validateNavigateToDefaultArgs(e);let n=e??T;t.validator?.navigation.validateNavigationOptions(n,`navigateToDefault`);let r=this.#s.navigateToDefault(n);return this.#s.lastSyncResolved?this.#s.lastSyncResolved=!1:this.#s.lastSyncRejected?this.#s.lastSyncRejected=!1:this.#p(r),r}navigateToNotFound(e){if(this.#m(),!this.#l.isActive())throw new g.RouterError(t.errorCodes.ROUTER_NOT_STARTED);if(e!==void 0&&typeof e!=`string`)throw TypeError(`[router.navigateToNotFound] path must be a string, got ${typeof e}`);if(e!==void 0)return this.#s.navigateToNotFound(e);let n=this.#r.get();if(n===void 0)throw new g.RouterError(t.errorCodes.ROUTER_NOT_STARTED,{message:`[router.navigateToNotFound] cannot derive the path before the start navigation commits — pass an explicit path`});return this.#s.navigateToNotFound(n.path)}static#f(e){return e instanceof g.RouterError&&E.has(e.code)}#p(e,t=this.#u){e.catch(t)}#m(){if(this.#l.isProcessing())throw new g.RouterError(t.errorCodes.REENTRANT_NAVIGATION)}#h(e){throw this.#l.isReady()&&this.#r.get()===void 0?(this.#c.stop(),this.#l.sendStop()):this.#l.isStarting()&&this.#l.sendFail(void 0,void 0,e),e}#g(){this.navigate=O,this.navigateToDefault=O,this.navigateToNotFound=O,this.start=O,this.stop=O,this.usePlugin=O,this.subscribe=O,this.subscribeLeave=O,this.canNavigateTo=O}};function O(){throw new g.RouterError(t.errorCodes.ROUTER_DISPOSED)}function k(e){return{strictTrailingSlash:e.trailingSlash===`strict`,caseSensitive:e.caseSensitive,strictQueryParams:e.queryParamsMode===`strict`,urlParamsEncoding:e.urlParamsEncoding,queryParams:e.queryParams}}exports.Router=D;
1
+ const e=require("./channels/guard.js"),t=require("./constants.js"),n=require("./guards.js"),r=require("./helpers.js"),i=require("./internals.js"),a=require("./limits.js"),o=require("./namespaces/DependenciesNamespace/dependenciesStore.js"),s=require("./namespaces/OptionsNamespace/OptionsNamespace.js"),c=require("./pipeline/canonicalize.js"),l=require("./pipeline/buildURL.js"),u=require("./pipeline/materialize.js"),d=require("./namespaces/StateNamespace/StateNamespace.js"),f=require("./namespaces/PluginsNamespace/PluginsNamespace.js"),p=require("./namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js"),m=require("./transitionPath.js"),h=require("./namespaces/RoutesNamespace/RoutesNamespace.js"),g=require("./RouterError.js"),_=require("./namespaces/NavigationNamespace/constants.js"),v=require("./namespaces/NavigationNamespace/NavigationNamespace.js"),y=require("./namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js"),b=require("./routerFSM.js"),x=require("./namespaces/EventBusNamespace/EventBusNamespace.js"),S=require("./namespaces/RouterLifecycleNamespace/constants.js"),C=require("./utils/event-emitter/EventEmitter.js"),w=require("./utils/logger/RouterLogger.js"),T=require("./wiring/wireNamespaces.js"),E=Object.freeze({});var D=class D{#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u;constructor(r=[],c={},l={}){let{logger:u,...m}=c;u&&n.assertLoggerConfig(u);let g=new w.RouterLogger(u);this.#u=e=>{_.isExpectedRejection(e)||g.error(`router.start`,`Unexpected start error`,e)},s.OptionsNamespace.validateOptionsIsObject(c),n.guardDependencies(l),r.length>0&&n.guardRouteStructure(r),this.#e=new s.OptionsNamespace(m),this.#t=a.createLimits(m.limits),this.#n=o.createDependenciesStore(l),this.#r=new d.StateNamespace,this.#i=new h.RoutesNamespace(r,k(this.#e.get()),g),this.#a=new p.RouteLifecycleNamespace,this.#o=new f.PluginsNamespace,this.#s=new v.NavigationNamespace,this.#c=new y.RouterLifecycleNamespace;let S=b.createRouterFSM(),O=new C.EventEmitter({onListenerError:(e,t)=>{g.error(`Router`,`Error in listener for ${e}:`,t)},onListenerWarn:(e,t)=>{g.warn(`router.addEventListener`,`Event "${e}" has ${t} listeners — possible memory leak`)}});this.#l=new x.EventBusNamespace({routerFSM:S,emitter:O,abortController:e=>{this.#s.abortCurrentController(e)}});let A=new Map,j=i.createTernaryInterceptable(`forwardState`,(e,t,n)=>this.#i.forwardState(e,t,n),A);i.registerInternals(this,{logger:g,makeState:(e,t,n,r)=>this.#r.makeState(e,t,n,r),getMetaForState:e=>this.#i.getMetaForState(e),getQueryParams:e=>this.#i.getQueryParams(e),forwardState:((n,r,i)=>{let a=j(n,r,i);return e.assertChannelCorrect(`forwardState`,a.name,a.params,this.#i.getQueryParams(a.name),()=>a.name===n?"the `params` bag leaving the forwardState chain":`the \`params\` bag leaving the forwardState chain (forwarded here from "${n}")`),{name:a.name,params:a.params??t.EMPTY_PARAMS,search:a.search}}),buildStateResolved:(e,t)=>this.#i.buildStateResolved(e,t),port:()=>this.#i.getPort(),matchPath:(e,t)=>this.#i.matchPath(e,t),getOptions:()=>this.#e.get(),addEventListener:(e,t)=>this.#l.addEventListener(e,t),treeChanged:{emit:e=>{this.#l.emitTreeChanged(e)},subscribe:e=>this.#l.subscribeTreeChanged(e),listenerCount:()=>this.#l.treeChangedListenerCount(),isEmitting:()=>this.#l.isEmittingTreeChanged()},buildPath:i.createTernaryInterceptable(`buildPath`,(e,n,r)=>this.#i.buildPath(e,n??t.EMPTY_PARAMS,r,this.#e.get()),A),emitTransitionError:e=>{this.#l.sendFailSafe(void 0,this.#r.get(),e)},emitTransitionSuccess:(e,t,n)=>{this.#l.emitTransitionSuccess(e,t,n)},navigateToNotFound:e=>this.#s.navigateToNotFound(e),start:i.createInterceptable(`start`,e=>this.#c.start(e),A),navigateToState:(e,t)=>(this.#p(),D.#d(this.#s.navigateToState(e,t??E))),interceptors:A,setRootPath:e=>{this.#i.setRootPath(e)},getRootPath:()=>this.#i.getStore().rootPath,getTree:()=>this.#i.getStore().tree,isDisposed:()=>this.#l.isDisposed(),validator:null,dependenciesGetStore:()=>this.#n,getCloneState:()=>({options:{...this.#e.get()},dependencies:{...this.#n.dependencies},pluginFactories:this.#o.getAll(),loggerConfig:g.getConfig()}),routeGetStore:()=>this.#i.getStore(),getStateName:()=>this.#r.get()?.name,isTransitioning:()=>this.#l.isTransitioning(),clearState:()=>{this.#r.set(void 0)},setState:e=>{this.#r.set(e)},routerExtensions:[],contextClaimRecords:new Set,hydrationState:null}),T.wireNamespaces({router:this,options:this.#e,limits:this.#t,dependenciesStore:this.#n,state:this.#r,routes:this.#i,routeLifecycle:this.#a,plugins:this.#o,navigation:this.#s,lifecycle:this.#c,eventBus:this.#l}),this.isActiveRoute=this.isActiveRoute.bind(this),this.buildPath=this.buildPath.bind(this),this.getState=this.getState.bind(this),this.getPreviousState=this.getPreviousState.bind(this),this.areStatesEqual=this.areStatesEqual.bind(this),this.shouldUpdateNode=this.shouldUpdateNode.bind(this),this.isActive=this.isActive.bind(this),this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.dispose=this.dispose.bind(this),this.canNavigateTo=this.canNavigateTo.bind(this),this.usePlugin=this.usePlugin.bind(this),this.navigate=this.navigate.bind(this),this.navigateToDefault=this.navigateToDefault.bind(this),this.navigateToNotFound=this.navigateToNotFound.bind(this),this.subscribe=this.subscribe.bind(this),this.subscribeLeave=this.subscribeLeave.bind(this),this.isLeaveApproved=this.isLeaveApproved.bind(this);try{this.#i.flushPendingGuards()}catch(e){throw this.dispose(),e}}isActiveRoute(e,t,n,r,a){return i.getInternals(this).validator?.routes.validateIsActiveRouteArgs(e,t,r,a),i.getInternals(this).validator?.routes.validateRouteName(e,`isActiveRoute`),e===``?(i.getInternals(this).logger.warn(`real-router`,`isActiveRoute("") called with empty string. Root node is not considered a parent of any route.`),!1):this.#i.isActiveRoute(e,t,n,r,a)}buildPath(e,t,n){let a=i.getInternals(this);return a.validator?.routes.validateBuildPathArgs(e),a.validator?.navigation.validateParams(t,`buildPath`),a.buildPath(e,r.normalizeParams(t),n)}getState(){return this.#r.get()}getPreviousState(){return this.#r.getPrevious()}areStatesEqual(e,t,n=!0){return i.getInternals(this).validator?.state.validateAreStatesEqualArgs(e,t,n),this.#r.areStatesEqual(e,t,n)}shouldUpdateNode(e){return i.getInternals(this).validator?.routes.validateShouldUpdateNodeArgs(e),h.RoutesNamespace.shouldUpdateNode(e,e=>this.#i.getMetaForState(e))}isActive(){return this.#l.isActive()}start(e){let t=this.#f(e);return t.catch(this.#u),t}stop(){return this.#l.sendCancelIfPossible(this.#r.get()),!this.#l.isReady()&&!this.#l.isTransitioning()&&!this.#l.isStarting()?this:(this.#c.stop(),this.#l.sendStop(),this)}dispose(){if(this.#l.isDisposed())return;this.#l.sendCancelIfPossible(this.#r.get()),(this.#l.isReady()||this.#l.isTransitioning())&&(this.#c.stop(),this.#l.sendStop()),this.#l.sendDispose(),this.#l.clearAll(),this.#o.disposeAll();let e=i.getInternals(this);for(let t of e.routerExtensions)for(let e of t.keys)delete this[e];e.routerExtensions.length=0,e.contextClaimRecords.clear(),e.interceptors.clear(),this.#i.clearRoutes(),this.#a.clearAll(),this.#r.reset(),this.#n.dependencies=Object.create(null),this.#h()}canNavigateTo(n,r,a){let o=i.getInternals(this);if(o.validator?.routes.validateRouteName(n,`canNavigateTo`),o.validator?.navigation.validateParams(r,`canNavigateTo`),!this.#i.hasRoute(n)||e.findMisChanneledKey(r,this.#i.getQueryParams(n))!==void 0)return!1;let s=this.#i.getPort(),d;try{d=c.canonicalize(s,n,r??t.EMPTY_PARAMS,a)}catch(e){return o.logger.warn(`router.canNavigateTo`,`Resolving route "${n}" threw while answering the predicate; treating the route as unreachable.`,e),!1}let f;try{f=u.materialize(d,{path:l.buildURL(d,s),skipFreeze:!0})}catch{return!1}let p=this.#r.get(),{toDeactivate:h,toActivate:g}=m.getTransitionPath(f,p,e=>this.#i.getMetaForState(e));return this.#a.canNavigateTo(h,g,f,p)}usePlugin(...e){if(this.#l.isDisposed())throw new g.RouterError(t.errorCodes.ROUTER_DISPOSED);let n=e.filter(Boolean);if(n.length===0)return()=>{};let r=i.getInternals(this);r.validator?.plugins.validatePluginLimit(this.#o.count(),this.#t);for(let e of n)r.validator?.plugins.validateNoDuplicatePlugins(e,this.#o.getAll());return this.#o.use(...n)}subscribe(e){return x.EventBusNamespace.validateSubscribeListener(e),this.#l.subscribe(e)}subscribeLeave(e){return x.EventBusNamespace.validateSubscribeLeaveListener(e),this.#l.subscribeLeave(e)}isLeaveApproved(){return this.#l.isLeaveApproved()}navigate(e,n,r,a){this.#p();let o=i.getInternals(this),s,c,l,u;return typeof e==`object`&&e?(s=e.name,c=e.params,l=e.search,u=n??E):(s=e,c=n,l=r,u=a??E),i.throwOnMisChanneledKey(o,`navigate`,s,c),o.validator?.navigation.validateNavigateArgs(s),o.validator?.navigation.validateParams(c,`navigate`),o.validator?.navigation.validateNavigationOptions(u,`navigate`),D.#d(this.#s.navigate(s,c??t.EMPTY_PARAMS,l,u))}navigateToDefault(e){this.#p();let t=i.getInternals(this);t.validator?.navigation.validateNavigateToDefaultArgs(e);let n=e??E;return t.validator?.navigation.validateNavigationOptions(n,`navigateToDefault`),D.#d(this.#s.navigateToDefault(n))}navigateToNotFound(e){if(this.#p(),!this.#l.isActive())throw new g.RouterError(t.errorCodes.ROUTER_NOT_STARTED);if(e!==void 0&&typeof e!=`string`)throw TypeError(`[router.navigateToNotFound] path must be a string, got ${typeof e}`);if(e!==void 0)return this.#s.navigateToNotFound(e);let n=this.#r.get();if(n===void 0)throw new g.RouterError(t.errorCodes.ROUTER_NOT_STARTED,{message:`[router.navigateToNotFound] cannot derive the path before the start navigation commits — pass an explicit path`});return this.#s.navigateToNotFound(n.path)}static#d(e){return e instanceof Promise?e:Promise.resolve(e)}#f(e){if(!this.#l.canStart())return Promise.reject(S.CACHED_ALREADY_STARTED_ERROR);i.getInternals(this).validator?.navigation.validateStartArgs(e),this.#l.sendStart();let t;try{let n=i.getInternals(this).start(e);t=typeof n?.then==`function`?n:Promise.reject(TypeError("[router.start] a `start` interceptor returned without calling next(). Every start interceptor must return `next(path)`."))}catch(e){t=Promise.reject(e)}return t.catch(e=>this.#m(e))}#p(){if(this.#l.isProcessing())throw new g.RouterError(t.errorCodes.REENTRANT_NAVIGATION)}#m(e){throw this.#l.isReady()&&this.#r.get()===void 0?(this.#c.stop(),this.#l.sendStop()):this.#l.isStarting()&&this.#l.sendFail(void 0,void 0,e),e}#h(){this.navigate=O,this.navigateToDefault=O,this.navigateToNotFound=O,this.start=O,this.stop=O,this.usePlugin=O,this.subscribe=O,this.subscribeLeave=O,this.canNavigateTo=O}};function O(){throw new g.RouterError(t.errorCodes.ROUTER_DISPOSED)}function k(e){return{strictTrailingSlash:e.trailingSlash===`strict`,caseSensitive:e.caseSensitive,strictQueryParams:e.queryParamsMode===`strict`,urlParamsEncoding:e.urlParamsEncoding,queryParams:e.queryParams}}exports.Router=D;
2
2
  //# sourceMappingURL=Router.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Router.js","names":["errorCodes","#options","#limits","#dependenciesStore","#state","#routes","#routeLifecycle","#plugins","#navigation","#lifecycle","#eventBus","#onSuppressedNavigateError","#onSuppressedStartError","RouterLogger","#isExpectedRejection","OptionsNamespace","createLimits","createDependenciesStore","StateNamespace","RoutesNamespace","RouteLifecycleNamespace","PluginsNamespace","NavigationNamespace","RouterLifecycleNamespace","createRouterFSM","EventEmitter","EventBusNamespace","createTernaryInterceptable","EMPTY_PARAMS","createInterceptable","#assertNotReentrant","#suppressUnhandledRejection","getInternals","normalizeParams","CACHED_ALREADY_STARTED_ERROR","#unwindFailedStart","#markDisposed","findMisChanneledKey","canonicalize","materialize","buildURL","getTransitionPath","RouterError"],"sources":["../../src/Router.ts"],"sourcesContent":["// packages/core/src/Router.ts\n\n/**\n * Router class - facade with integrated namespaces.\n *\n * All functionality is now provided by namespace classes.\n */\n\nimport { assertChannelCorrect, findMisChanneledKey } from \"./channels\";\nimport { EMPTY_PARAMS, errorCodes } from \"./constants\";\nimport {\n assertLoggerConfig,\n guardDependencies,\n guardRouteStructure,\n} from \"./guards\";\nimport { normalizeParams } from \"./helpers\";\nimport {\n createInterceptable,\n createTernaryInterceptable,\n getInternals,\n registerInternals,\n throwOnMisChanneledKey,\n} from \"./internals\";\nimport { createLimits } from \"./limits\";\nimport {\n EventBusNamespace,\n NavigationNamespace,\n OptionsNamespace,\n PluginsNamespace,\n RouteLifecycleNamespace,\n RouterLifecycleNamespace,\n RoutesNamespace,\n StateNamespace,\n createDependenciesStore,\n} from \"./namespaces\";\nimport { CACHED_ALREADY_STARTED_ERROR } from \"./namespaces/RouterLifecycleNamespace/constants\";\nimport { buildURL, canonicalize, materialize } from \"./pipeline\";\nimport { RouterError } from \"./RouterError\";\nimport { createRouterFSM } from \"./routerFSM\";\nimport { getTransitionPath } from \"./transitionPath\";\nimport { EventEmitter } from \"./utils/event-emitter\";\nimport { RouterLogger } from \"./utils/logger\";\nimport { wireNamespaces } from \"./wiring\";\n\nimport type { CreateMatcherOptions } from \"./engine\";\nimport type { RouterInternals } from \"./internals\";\nimport type { DependenciesStore } from \"./namespaces\";\nimport type {\n DefaultDependencies,\n LeaveFn,\n NavigationOptions,\n NavigationTarget,\n Options,\n Params,\n Router as RouterInterface,\n SearchParams,\n State,\n SubscribeFn,\n Unsubscribe,\n PluginFactory,\n Route,\n} from \"./types\";\nimport type { Limits, RouterEventMap } from \"./types/internal\";\n\nconst EMPTY_OPTS: Readonly<NavigationOptions> = Object.freeze({});\n\n// Module-level so #isExpectedRejection allocates nothing per navigate()/start() call.\n// These are expected navigation outcomes owned by the caller, not internal\n// bugs — the safety net stays silent for them and lets awaiting callers see\n// the rejection. CANNOT_ACTIVATE / CANNOT_DEACTIVATE belong here: a guard\n// blocking (or a plugin's guard-blocked back()/forward()) is a normal result,\n// so a fire-and-forget call must not emit a spurious \"Unexpected navigation\n// error\" (#721).\nconst SUPPRESSED_ERROR_CODES: ReadonlySet<string> = new Set([\n errorCodes.SAME_STATES,\n errorCodes.TRANSITION_CANCELLED,\n errorCodes.ROUTER_NOT_STARTED,\n errorCodes.ROUTE_NOT_FOUND,\n errorCodes.CANNOT_ACTIVATE,\n errorCodes.CANNOT_DEACTIVATE,\n]);\n\n/**\n * Router class with integrated namespace architecture.\n *\n * All functionality is provided by namespace classes:\n * - OptionsNamespace: getOptions (immutable)\n * - DependenciesStore: get/set/remove dependencies\n * - EventEmitter: subscribe\n * - StateNamespace: state storage (getState, setState, getPreviousState)\n * - RoutesNamespace: route tree operations\n * - RouteLifecycleNamespace: canActivate/canDeactivate guards\n * - PluginsNamespace: plugin lifecycle\n * - NavigationNamespace: navigate\n * - RouterLifecycleNamespace: start, stop, isStarted\n *\n * @internal This class implementation is internal. Use createRouter() instead.\n */\nexport class Router<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> implements RouterInterface<Dependencies> {\n [key: string]: unknown;\n\n // ============================================================================\n // Namespaces\n // ============================================================================\n\n readonly #options: OptionsNamespace<Dependencies>;\n readonly #limits: Limits;\n readonly #dependenciesStore: DependenciesStore<Dependencies>;\n readonly #state: StateNamespace;\n readonly #routes: RoutesNamespace<Dependencies>;\n readonly #routeLifecycle: RouteLifecycleNamespace<Dependencies>;\n readonly #plugins: PluginsNamespace<Dependencies>;\n readonly #navigation: NavigationNamespace;\n readonly #lifecycle: RouterLifecycleNamespace;\n\n readonly #eventBus: EventBusNamespace;\n\n /**\n * Per-instance suppressors for fire-and-forget navigate / start. They log\n * through THIS router's logger (built in the constructor) — so, unlike the\n * former static \"one allocation per class\" closures, they are two closures per\n * instance. Trade accepted with the per-router logger (#724): the logger is\n * per-instance, so its suppressors must be too. The #931 category split is\n * preserved — navigate failures log under \"router.navigate\", start failures\n * under \"router.start\" (a start interceptor throwing a plain Error after\n * next() committed, #763, or a cryptic path TypeError — neither a suppressed\n * RouterError). The log line IS reachable: a subscribeLeave listener that\n * throws rejects navigate() with the original NON-suppressed error, and a\n * Symbol path-param's stringify TypeError is likewise non-suppressed.\n */\n readonly #onSuppressedNavigateError: (error: unknown) => void;\n readonly #onSuppressedStartError: (error: unknown) => void;\n\n // ============================================================================\n // Constructor\n // ============================================================================\n\n /**\n * @param routes - Route definitions\n * @param options - Router options\n * @param dependencies - DI dependencies\n */\n constructor(\n routes: Route<Dependencies>[] = [],\n options: Partial<Options<Dependencies>> = {},\n dependencies: Dependencies = {} as Dependencies,\n ) {\n // Extract the logger config WITHOUT mutating the caller's `options` object\n // (#724). `routerOptions` is the logger-stripped view handed to the options\n // pipeline so `logger` never lands in the frozen router options.\n const { logger: loggerConfig, ...routerOptions } = options;\n\n if (loggerConfig) {\n assertLoggerConfig(loggerConfig);\n }\n\n // Per-router logger instance — replaces the former process-global singleton\n // whose configure() leaked across every router in the process, last\n // createRouter winning (#724). Stored on ctx (registerInternals below), so\n // the facade reads getInternals(this).logger; namespaces receive it via\n // their deps at wiring; plugins reach it through getPluginApi(router).logger.\n const logger = new RouterLogger(loggerConfig);\n\n // Per-instance fire-and-forget suppressors (see field declarations): they\n // log through THIS router's logger, so they are built here, not static.\n this.#onSuppressedNavigateError = (error: unknown): void => {\n if (Router.#isExpectedRejection(error)) {\n return;\n }\n\n logger.error(\"router.navigate\", \"Unexpected navigation error\", error);\n };\n this.#onSuppressedStartError = (error: unknown): void => {\n if (Router.#isExpectedRejection(error)) {\n return;\n }\n\n logger.error(\"router.start\", \"Unexpected start error\", error);\n };\n\n // =========================================================================\n // Validate inputs before creating namespaces\n // =========================================================================\n\n // Always validate the caller's options (catches non-object / array inputs)\n OptionsNamespace.validateOptionsIsObject(options);\n\n // Unconditional guard-level validation before creating namespaces\n guardDependencies(dependencies);\n\n // Stryker disable next-line EqualityOperator: equivalent — `>= 0` is always true, but `guardRouteStructure([])` on an empty array is a no-op, so validating an empty list behaves identically to skipping it. (ConditionalExpression stays live: `→false` skips validation of a real route list and is killable.)\n if (routes.length > 0) {\n guardRouteStructure(routes);\n }\n\n // =========================================================================\n // Create Namespaces\n // =========================================================================\n\n this.#options = new OptionsNamespace(routerOptions);\n this.#limits = createLimits(routerOptions.limits);\n this.#dependenciesStore =\n createDependenciesStore<Dependencies>(dependencies);\n this.#state = new StateNamespace();\n this.#routes = new RoutesNamespace<Dependencies>(\n routes,\n deriveMatcherOptions(this.#options.get()),\n logger,\n );\n this.#routeLifecycle = new RouteLifecycleNamespace<Dependencies>();\n this.#plugins = new PluginsNamespace<Dependencies>();\n this.#navigation = new NavigationNamespace();\n this.#lifecycle = new RouterLifecycleNamespace();\n\n // =========================================================================\n // Initialize EventBus\n // =========================================================================\n\n const routerFSM = createRouterFSM();\n\n const emitter = new EventEmitter<RouterEventMap>({\n // Shared per-listener error sink: EventEmitter reports synchronous listener\n // throws here, and EventBusNamespace.subscribe routes an async listener's\n // rejected Promise through the SAME sink (#944) — both land in one place.\n onListenerError: (eventName, error) => {\n logger.error(\"Router\", `Error in listener for ${eventName}:`, error);\n },\n onListenerWarn: (eventName, count) => {\n logger.warn(\n \"router.addEventListener\",\n `Event \"${eventName}\" has ${count} listeners — possible memory leak`,\n );\n },\n });\n\n this.#eventBus = new EventBusNamespace({\n routerFSM,\n emitter,\n // The FSM CANCEL action aborts the in-flight\n // navigation controller via this injected effect — \"FSM CANCEL ⟹\n // controller aborted\" in one place. `#navigation` is constructed above.\n abortController: (reason) => {\n this.#navigation.abortCurrentController(reason);\n },\n });\n\n // =========================================================================\n // Register Internals (WeakMap for plugin/infrastructure access)\n // =========================================================================\n // Registered BEFORE wiring (#1331) so every namespace's deps-closure sees a\n // router already present in the internals registry — `getInternals(router)`\n // never throws during wiring, and guard factories flushed at the end of the\n // constructor see a fully-registered instance.\n\n const interceptorsMap: RouterInternals[\"interceptors\"] = new Map();\n\n // THE single forwardState boundary (#1548/#1549). The interceptable resolves\n // the route (forwardTo) and runs the whole interceptor chain — a plugin\n // injecting params, a search-schema validation, etc. The outer layer then\n // CHECKS the channels once, keyed on the RESOLVED route's `?`-declaration.\n //\n // It used to REPAIR them instead (`separateChannels`, stage ②): a declared\n // query key left in the params bag was moved into the query channel behind\n // the producer's back. Three things were wrong with that. The producer kept\n // believing the bag it wrote was the one that shipped. A plugin could\n // inject past a validation that had already run — search-schema documented\n // exactly that leak, with a test named LEAKS. And the caller's own\n // mis-channelled key and a chain default's query half landed in DIFFERENT\n // channels, where no merge ranks them, so the default silently won (#1570).\n // Refusing is the whole fix: whoever names the route knows its declaration.\n //\n // `as unknown as` is required: the closure is non-generic, but\n // RouterInternals[\"forwardState\"] is declared generic `<P, S>`, which tsc\n // will not infer from a non-generic source (Sonar S4325 misclassifies this\n // as a redundant cast).\n const rawForwardState = createTernaryInterceptable(\n \"forwardState\",\n (name: string, params: Params, search?: SearchParams) =>\n this.#routes.forwardState(name, params, search),\n interceptorsMap,\n );\n\n const forwardState = ((\n name: string,\n params: Params,\n search?: SearchParams,\n ) => {\n const forwarded = rawForwardState(name, params, search);\n\n // The DECLARATION that matters is the RESOLVED route's — it owns the URL\n // that gets printed. When a chain resolved to a different route, say so:\n // a caller who wrote `navigate(\"src\", { lang })` looked at `src`'s config,\n // where `lang` is undeclared and legitimate, and needs to be told that the\n // hop landed somewhere that spells it `?lang`. Naming only the target\n // would read as a message about a route they never mentioned.\n assertChannelCorrect(\n \"forwardState\",\n forwarded.name,\n forwarded.params,\n this.#routes.getQueryParams(forwarded.name),\n () =>\n forwarded.name === name\n ? \"the `params` bag leaving the forwardState chain\"\n : `the \\`params\\` bag leaving the forwardState chain (forwarded here from \"${name}\")`,\n );\n\n return {\n name: forwarded.name,\n // The type says `params: P`, and across THIS boundary the type is a\n // contract, not a guarantee: `rawForwardState` is an interceptable, so\n // the value has passed through user code that can spread a partial\n // result. The net used to be reached by stage ②'s split (an all-query\n // bag left the path half undefined) and is now reached only by that\n // contract violation — still worth surviving rather than putting\n // `undefined` into `state.params`. Pinned by \"normalises a params bag an\n // interceptor dropped to `undefined`\" in forwardState.test.ts, which\n // fails if this is removed.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- see above: the declared type cannot model an interceptor's runtime return\n params: forwarded.params ?? EMPTY_PARAMS,\n search: forwarded.search,\n };\n }) as unknown as RouterInternals[\"forwardState\"];\n\n registerInternals(this, {\n logger,\n makeState: (name, params, search, path) =>\n this.#state.makeState(name, params, search, path),\n getMetaForState: (name) => this.#routes.getMetaForState(name),\n getQueryParams: (name) => this.#routes.getQueryParams(name),\n forwardState,\n buildStateResolved: (name, params) =>\n this.#routes.buildStateResolved(name, params),\n port: () => this.#routes.getPort(),\n matchPath: (path, matchOptions) =>\n this.#routes.matchPath(path, matchOptions),\n getOptions: () => this.#options.get(),\n addEventListener: (eventName, cb) =>\n this.#eventBus.addEventListener(eventName, cb),\n treeChanged: {\n emit: (event) => {\n this.#eventBus.emitTreeChanged(event);\n },\n subscribe: (handler) => this.#eventBus.subscribeTreeChanged(handler),\n listenerCount: () => this.#eventBus.treeChangedListenerCount(),\n isEmitting: () => this.#eventBus.isEmittingTreeChanged(),\n },\n buildPath: createTernaryInterceptable(\n \"buildPath\",\n (route: string, params?: Params, search?: SearchParams) =>\n this.#routes.buildPath(\n route,\n params ?? EMPTY_PARAMS,\n search,\n this.#options.get(),\n ),\n interceptorsMap,\n ),\n emitTransitionError: (error) => {\n this.#eventBus.sendFailSafe(undefined, this.#state.get(), error);\n },\n emitTransitionSuccess: (toState, fromState, opts) => {\n this.#eventBus.emitTransitionSuccess(toState, fromState, opts);\n },\n navigateToNotFound: (path) => this.#navigation.navigateToNotFound(path),\n start: createInterceptable(\n \"start\",\n (path: string) => {\n return this.#lifecycle.start(path);\n },\n interceptorsMap,\n ),\n navigateToState: (state, navOpts) => {\n // Plugin-only navigation primitive (#525). Mirrors the same\n // unhandled-rejection suppression and lastSync* bookkeeping used by\n // the public Router.navigate facade so plugin call-sites can\n // fire-and-forget the returned promise (popstate handlers do).\n this.#assertNotReentrant();\n\n const promiseState = this.#navigation.navigateToState(\n state,\n navOpts ?? EMPTY_OPTS,\n );\n\n if (this.#navigation.lastSyncResolved) {\n this.#navigation.lastSyncResolved = false;\n } else if (this.#navigation.lastSyncRejected) {\n this.#navigation.lastSyncRejected = false;\n } else {\n this.#suppressUnhandledRejection(promiseState);\n }\n\n return promiseState;\n },\n interceptors: interceptorsMap,\n setRootPath: (rootPath) => {\n this.#routes.setRootPath(rootPath);\n },\n getRootPath: () => this.#routes.getStore().rootPath,\n getTree: () => this.#routes.getStore().tree,\n isDisposed: () => this.#eventBus.isDisposed(),\n validator: null,\n // Dependencies (issue #172)\n dependenciesGetStore: () => this.#dependenciesStore,\n // Clone support (issue #173)\n getCloneState: () => ({\n options: { ...this.#options.get() },\n dependencies: { ...this.#dependenciesStore.dependencies },\n pluginFactories: this.#plugins.getAll(),\n // `logger` is a const in this constructor's scope (a RouterLogger class\n // instance), so getConfig() yields the resolved config a clone inherits\n // — frozen options don't carry `logger`, so cloneRouter reads it here.\n loggerConfig: logger.getConfig(),\n }),\n routeGetStore: () => this.#routes.getStore(),\n // Cross-namespace state (issue #174)\n getStateName: () => this.#state.get()?.name,\n isTransitioning: () => this.#eventBus.isTransitioning(),\n clearState: () => {\n this.#state.set(undefined);\n },\n setState: (state) => {\n this.#state.set(state);\n },\n routerExtensions: [],\n contextClaimRecords: new Set(),\n hydrationState: null,\n });\n\n // =========================================================================\n // Wire Dependencies\n // =========================================================================\n\n wireNamespaces<Dependencies>({\n router: this,\n options: this.#options,\n limits: this.#limits,\n dependenciesStore: this.#dependenciesStore,\n state: this.#state,\n routes: this.#routes,\n routeLifecycle: this.#routeLifecycle,\n plugins: this.#plugins,\n navigation: this.#navigation,\n lifecycle: this.#lifecycle,\n eventBus: this.#eventBus,\n });\n\n // =========================================================================\n // Bind Public Methods\n // =========================================================================\n // All public methods that access private fields must be bound to preserve\n // `this` context when methods are extracted as references.\n // See: https://github.com/tc39/proposal-bind-operator\n // =========================================================================\n\n // Path & State Building\n this.isActiveRoute = this.isActiveRoute.bind(this);\n this.buildPath = this.buildPath.bind(this);\n\n // State Management\n this.getState = this.getState.bind(this);\n this.getPreviousState = this.getPreviousState.bind(this);\n this.areStatesEqual = this.areStatesEqual.bind(this);\n this.shouldUpdateNode = this.shouldUpdateNode.bind(this);\n\n // Router Lifecycle\n this.isActive = this.isActive.bind(this);\n this.start = this.start.bind(this);\n this.stop = this.stop.bind(this);\n this.dispose = this.dispose.bind(this);\n\n // Route Lifecycle (Guards)\n this.canNavigateTo = this.canNavigateTo.bind(this);\n\n // Plugins\n this.usePlugin = this.usePlugin.bind(this);\n\n // Navigation\n this.navigate = this.navigate.bind(this);\n this.navigateToDefault = this.navigateToDefault.bind(this);\n this.navigateToNotFound = this.navigateToNotFound.bind(this);\n\n // Subscription\n this.subscribe = this.subscribe.bind(this);\n this.subscribeLeave = this.subscribeLeave.bind(this);\n this.isLeaveApproved = this.isLeaveApproved.bind(this);\n\n // =========================================================================\n // Flush initial-route guard factories\n // =========================================================================\n // Deferred out of wiring (#1331): the pending canActivate/canDeactivate\n // factories from initial route definitions are compiled and executed HERE,\n // on the fully-built and bound router — a factory calling read-only methods\n // (`buildPath()`, `isActiveRoute()`, `getState()`) no longer hits a\n // half-assembled instance. Side-effectful calls (`navigate`, `usePlugin`,\n // route-CRUD) stay OUT OF CONTRACT: factories re-execute outside the\n // constructor (cloneRouter re-compiles definition guards per clone;\n // #recompileSlot re-runs a factory after a definition-only clear), so any\n // side effect would duplicate per re-execution — see CLAUDE.md. Runtime\n // add()/replace() compile guards in their own PREPARE phase and never touch\n // these pending maps.\n //\n // Fail-closed on a factory throw: by this point a router reference leaked\n // from an earlier factory is fully operational, while later guards would\n // stay silently unregistered — a fail-open guard bypass. Disposing before\n // the rethrow turns any leaked reference into a ROUTER_DISPOSED-throwing\n // husk (pre-#1331 such a reference was inert because getInternals threw).\n try {\n this.#routes.flushPendingGuards();\n } catch (error) {\n this.dispose();\n\n throw error;\n }\n }\n\n // ============================================================================\n // Path & State Building\n // ============================================================================\n\n isActiveRoute(\n name: string,\n params?: Params,\n search?: SearchParams,\n strictEquality?: boolean,\n ignoreQueryParams?: boolean,\n ): boolean {\n getInternals(this).validator?.routes.validateIsActiveRouteArgs(\n name,\n params,\n strictEquality,\n ignoreQueryParams,\n );\n\n getInternals(this).validator?.routes.validateRouteName(\n name,\n \"isActiveRoute\",\n );\n\n // Empty string is special case - warn and return false (root node is not a parent)\n if (name === \"\") {\n getInternals(this).logger.warn(\n \"real-router\",\n 'isActiveRoute(\"\") called with empty string. Root node is not considered a parent of any route.',\n );\n\n return false;\n }\n\n // Slot-shift (RFC-4 M2 / #1548): `search` is the explicit query channel at\n // position 3; `strictEquality` / `ignoreQueryParams` shift to 4 / 5.\n return this.#routes.isActiveRoute(\n name,\n params,\n search,\n strictEquality,\n ignoreQueryParams,\n );\n }\n\n buildPath(route: string, params?: Params, search?: SearchParams): string {\n const ctx = getInternals(this);\n\n ctx.validator?.routes.validateBuildPathArgs(route);\n ctx.validator?.navigation.validateParams(params, \"buildPath\");\n\n // `search` (RFC-4 M2 / #1548) is the explicit query channel; the matcher\n // builds the query string from it and the path from `params`, resolving a\n // colliding name (`/items/:id?id`). Omitted → the v1 single-bag path.\n return ctx.buildPath(route, normalizeParams(params), search);\n }\n\n // ============================================================================\n // State Management (delegated to StateNamespace)\n // ============================================================================\n\n getState<P extends Params = Params>(): State<P> | undefined {\n return this.#state.get<P>();\n }\n\n getPreviousState(): State | undefined {\n return this.#state.getPrevious();\n }\n\n areStatesEqual(\n state1: State | undefined,\n state2: State | undefined,\n ignoreQueryParams = true,\n ): boolean {\n getInternals(this).validator?.state.validateAreStatesEqualArgs(\n state1,\n state2,\n ignoreQueryParams,\n );\n\n return this.#state.areStatesEqual(state1, state2, ignoreQueryParams);\n }\n\n shouldUpdateNode(\n nodeName: string,\n ): (toState: State, fromState?: State) => boolean {\n getInternals(this).validator?.routes.validateShouldUpdateNodeArgs(nodeName);\n\n return RoutesNamespace.shouldUpdateNode(nodeName, (name) =>\n this.#routes.getMetaForState(name),\n );\n }\n\n // ============================================================================\n // Router Lifecycle\n // ============================================================================\n\n isActive(): boolean {\n return this.#eventBus.isActive();\n }\n\n start(startPath: string): Promise<State> {\n if (!this.#eventBus.canStart()) {\n return Promise.reject(CACHED_ALREADY_STARTED_ERROR);\n }\n\n getInternals(this).validator?.navigation.validateStartArgs(startPath);\n\n // FSM bookkeeping is split across the facade and RouterLifecycleNamespace by\n // design, NOT a missed consolidation (#940): `sendStart()` runs HERE, before\n // the interceptor chain, so the STARTING window spans the whole start\n // pipeline. A pre-`next()` interceptor throw then unwinds via STARTING →\n // `sendFail`, which emits TRANSITION_ERROR from STARTING (EventBusNamespace\n // FAIL action) for `onTransitionError` plugins. Moving `sendStart()` into the\n // namespace (the interceptor *target*) would skip STARTING on a pre-`next()`\n // throw — the namespace is never reached — silently dropping that\n // TRANSITION_ERROR: a #668 regression. The commit (`completeStart`) lives in\n // the namespace; recovery needs facade state (`#state`, `#lifecycle`), so it\n // stays here in `#unwindFailedStart`.\n this.#eventBus.sendStart();\n\n // Convert sync interceptor throws to rejections so the recovery path is\n // reachable; otherwise the throw escapes synchronously, the FSM is left in\n // STARTING, and the router is permanently bricked (#668).\n let internalStart: Promise<State>;\n\n try {\n const chainResult: unknown = getInternals(this).start(startPath);\n\n // A `start` interceptor that returns without calling next() yields a\n // non-thenable (typically undefined); the `.catch` below would then throw\n // a cryptic `TypeError: ...reading 'catch'` and leave the FSM stuck in\n // STARTING. Reject with an actionable message so recovery unwinds via\n // #unwindFailedStart — the same deferred-crash class as the #939\n // start-path guard (#1411).\n internalStart =\n typeof (chainResult as { then?: unknown } | null | undefined)?.then ===\n \"function\"\n ? (chainResult as Promise<State>)\n : Promise.reject(\n new TypeError(\n \"[router.start] a `start` interceptor returned without calling next(). Every start interceptor must return `next(path)`.\",\n ),\n );\n } catch (syncError: unknown) {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided start interceptor\n internalStart = Promise.reject(syncError);\n }\n\n const promiseState = internalStart.catch((error: unknown) =>\n this.#unwindFailedStart(error),\n );\n\n this.#suppressUnhandledRejection(\n promiseState,\n this.#onSuppressedStartError,\n );\n\n return promiseState;\n }\n\n stop(): this {\n // SendCancelIfPossible → FSM CANCEL → the CANCEL action\n // aborts the in-flight controller (waking the pipeline). No separate abort.\n this.#eventBus.sendCancelIfPossible(this.#state.get());\n\n // `isStarting()` is included (#1185): a stop() while `start()` is parked in\n // an async start-interceptor (FSM STARTING, before `next()`) must cancel the\n // start, not silently no-op. `sendStop()` takes STARTING → IDLE (FSM table),\n // and `RouterLifecycleNamespace.start` re-checks `isIdle()` after the\n // interceptor chain and rejects with TRANSITION_CANCELLED — mirroring the\n // guard-phase behavior (which already cancels from TRANSITION_STARTED).\n if (\n !this.#eventBus.isReady() &&\n !this.#eventBus.isTransitioning() &&\n !this.#eventBus.isStarting()\n ) {\n return this;\n }\n\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n\n return this;\n }\n\n dispose(): void {\n // Stryker disable next-line BlockStatement: equivalent — emptying the early-return re-runs the dispose body on a 2nd call, but it is fully idempotent (FSM `send(DISPOSE)` no-ops from DISPOSED, `disposeAll()` already cleared `#unsubscribes`, every clear is idempotent). (ConditionalExpression stays live: `→true` always-returns and never disposes = killed.)\n if (this.#eventBus.isDisposed()) {\n return;\n }\n\n // the FSM CANCEL action aborts the in-flight controller.\n this.#eventBus.sendCancelIfPossible(this.#state.get());\n\n if (this.#eventBus.isReady() || this.#eventBus.isTransitioning()) {\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n }\n\n this.#eventBus.sendDispose();\n this.#eventBus.clearAll();\n\n this.#plugins.disposeAll();\n\n // Safety net: clean up extensions plugins failed to remove in teardown\n const ctx = getInternals(this);\n\n for (const extension of ctx.routerExtensions) {\n for (const key of extension.keys) {\n delete (this as Record<string, unknown>)[key];\n }\n }\n\n ctx.routerExtensions.length = 0;\n\n // Safety net: release context namespace claims plugins failed to release in teardown\n ctx.contextClaimRecords.clear();\n\n // Safety net: drop interceptors plugins failed to remove in teardown (#1199).\n // The third per-plugin registration channel — symmetric with routerExtensions\n // / contextClaimRecords above. `buildPath` is not method-swapped by dispose\n // and reads this Map live, so a leaked interceptor would otherwise still run\n // on the disposed router.\n ctx.interceptors.clear();\n\n this.#routes.clearRoutes();\n this.#routeLifecycle.clearAll();\n this.#state.reset();\n this.#dependenciesStore.dependencies = Object.create(\n null,\n ) as Partial<Dependencies>;\n\n this.#markDisposed();\n }\n\n // ============================================================================\n // Route Lifecycle (Guards)\n // ============================================================================\n\n canNavigateTo(name: string, params?: Params, search?: SearchParams): boolean {\n const ctx = getInternals(this);\n\n ctx.validator?.routes.validateRouteName(name, \"canNavigateTo\");\n ctx.validator?.navigation.validateParams(params, \"canNavigateTo\");\n\n if (!this.#routes.hasRoute(name)) {\n return false;\n }\n\n // Mirror EVERY way `navigate` refuses these same arguments, not only the\n // guard verdict (#1576). A declared query key handed in the PATH bag makes\n // `navigate` throw synchronously at the facade (channel guard P1, #1572), so\n // the route is unreachable with this input — exactly the situation invariant\n // canNavigateTo #5 already answers `false` to for an unbuildable path (#725).\n // Answering `true` here promised a navigation that throws on the click.\n //\n // The RAW caller bag, before `forwardState`: the same argument, the same\n // registry and the same name P1 reads, so the predicate cannot be stricter\n // OR laxer than the verb. The `/items/:id?id` collision is absent from\n // `queryNames` by construction (#843 / #1549), so it stays navigable in both.\n //\n // A `false` rather than a rethrow: a capability predicate answers, it never\n // throws (#725), and it runs on every `<Link>` render across six adapters —\n // which is exactly why P1 does not instrument the predicates (#1572).\n if (\n findMisChanneledKey(params, this.#routes.getQueryParams(name)) !==\n undefined\n ) {\n return false;\n }\n\n // Resolution runs USER code and must not escape as an exception (#1577):\n // a dynamic `forwardTo` callback, a plugin's `forwardState` interceptor, and\n // the caller's own bag (the merge walks it key by key, so an accessor-backed\n // key throws here — the channel guard itself does NOT, it catches its own\n // read) all sit on this one call. The\n // predicate is documented TOTAL — it answers, it never throws (INVARIANTS\n // canNavigateTo #5, #725) — and its sibling `isActiveRoute` has wrapped the\n // very same primitive since #1573 (`RoutesNamespace.ts:631-645`). Leaving\n // this one bare made the two render-path predicates disagree about what a\n // throwing resolution means.\n //\n // A separate `try` rather than widening the one below: that one is SILENT by\n // design (an unbuildable path is a normal \"unreachable with this input\"\n // answer, #725), while user code crashing is an operational fault that must\n // never vanish — the same split #959 draws for a throwing guard.\n // Stages ① + ③ + the mode gate, one pass through the pipeline (nav-pipeline\n // Phase 2, step 2-3). `canonicalize` reaches the same `forwardState` seam\n // this method used to call directly (`port.resolveForward` IS\n // `ctx.forwardState`), so the resolution, the interceptor zone and the\n // channel CHECK on the seam are all unchanged — what the pipeline\n // replaces is the hand-rolled composition that followed.\n // Read ONCE (#1589): this predicate reached for the port twice — here and\n // again for `buildURL` below — on every `<Link>` render. The port is one\n // object per router, created at wiring time, so the second read could only\n // ever return the same reference.\n const port = this.#routes.getPort();\n\n let canonical;\n\n try {\n canonical = canonicalize(\n port,\n name,\n // The singleton, not a fresh `{}` (#1589): this predicate runs on every\n // `<Link>` render too, and `normalizeParams` recognises `EMPTY_PARAMS` by\n // identity — a literal makes it walk and re-allocate instead.\n params ?? EMPTY_PARAMS,\n search,\n );\n } catch (error) {\n ctx.logger.warn(\n \"router.canNavigateTo\",\n `Resolving route \"${name}\" threw while answering the predicate; treating the route as unreachable.`,\n error,\n );\n\n return false;\n }\n\n // Build `toState` exactly as `buildNavigateState` does — WITH route-meta and\n // normalized params — so `getTransitionPath` takes its STANDARD PATH and\n // trims the shared ancestor, mirroring navigate's guard set (#970). A\n // meta-less `toState` makes both sides meta-less (the committed `getState()`\n // carries no meta after a path-matched `start()`), so `getTransitionPath`\n // takes FAST PATH 3 and (de)activates the WHOLE chain incl. shared ancestors\n // → false-negative (\"Link disabled though the click would succeed\").\n // `normalizeParams` also aligns the params guards observe with navigate's.\n // `skipFreeze` (5th arg) mirrors the navigate guard phase, where guards see\n // an unfrozen, transition-less `toState` (freeze happens later in\n // `completeTransition`).\n //\n // A capability predicate must answer, not throw: if the target path can't be\n // built from these params (e.g. a required path param is missing), the route\n // is simply unreachable with this input — return `false` rather than letting\n // `buildPath` throw (#725).\n let toState: State;\n\n try {\n // ⑤a then ⑤b. `buildURL` is usable HERE (unlike in `buildPath` itself,\n // where it would recurse through the interceptable `ctx.buildPath` that\n // wraps that very method): this point is not the one the port prints\n // through, so the URL is built by the pipeline and the state materialised\n // from the SAME canonical intent — `toState.search` and `toState.path`\n // cannot drift. `skipFreeze` mirrors the navigate guard phase, where\n // guards see an unfrozen, transition-less `toState`.\n toState = materialize(canonical, {\n path: buildURL(canonical, port),\n skipFreeze: true,\n });\n } catch {\n return false;\n }\n\n const fromState = this.#state.get();\n\n const { toDeactivate, toActivate } = getTransitionPath(\n toState,\n fromState,\n (routeName) => this.#routes.getMetaForState(routeName),\n );\n\n return this.#routeLifecycle.canNavigateTo(\n toDeactivate,\n toActivate,\n toState,\n fromState,\n );\n }\n\n // ============================================================================\n // Plugins\n // ============================================================================\n\n usePlugin(\n ...plugins: (PluginFactory<Dependencies> | false | null | undefined)[]\n ): Unsubscribe {\n // Post-dispose guard, mirroring #946 for subscribe/subscribeLeave. A\n // reference captured before dispose() (`const up = router.usePlugin`)\n // bypasses the #markDisposed method swap, so the swap alone is not enough:\n // without this, the factory would run on a disposed router (real side\n // effects), listeners would land in the cleared emitter, and teardown would\n // never fire — a silent zombie plugin (#1196).\n if (this.#eventBus.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n const filtered = plugins.filter(Boolean) as PluginFactory<Dependencies>[];\n\n if (filtered.length === 0) {\n return () => {};\n }\n\n const ctx = getInternals(this);\n\n ctx.validator?.plugins.validatePluginLimit(\n this.#plugins.count(),\n this.#limits,\n );\n for (const plugin of filtered) {\n // `getAll()` sits inside the optional-chain argument on purpose: with no\n // validator installed (production default) the `?.` short-circuits and the\n // array is never allocated. Hoisting it out would either allocate on the\n // no-validator hot path or push the dev-only branch out of coverage.\n ctx.validator?.plugins.validateNoDuplicatePlugins(\n plugin,\n this.#plugins.getAll(),\n );\n }\n\n return this.#plugins.use(...filtered);\n }\n\n // ============================================================================\n // Subscription (backed by EventEmitter)\n // ============================================================================\n\n subscribe(listener: SubscribeFn): Unsubscribe {\n EventBusNamespace.validateSubscribeListener(listener);\n\n return this.#eventBus.subscribe(listener);\n }\n\n subscribeLeave(listener: LeaveFn): Unsubscribe {\n EventBusNamespace.validateSubscribeLeaveListener(listener);\n\n return this.#eventBus.subscribeLeave(listener);\n }\n\n isLeaveApproved(): boolean {\n return this.#eventBus.isLeaveApproved();\n }\n\n // ============================================================================\n // Navigation\n // ============================================================================\n\n navigate(\n target: NavigationTarget,\n options?: NavigationOptions,\n ): Promise<State>;\n navigate(\n routeName: string,\n routeParams?: Params,\n routeSearch?: SearchParams,\n options?: NavigationOptions,\n ): Promise<State>;\n navigate(\n nameOrTarget: string | NavigationTarget,\n paramsOrOptions?: Params | NavigationOptions,\n routeSearch?: SearchParams,\n options?: NavigationOptions,\n ): Promise<State> {\n this.#assertNotReentrant();\n\n const ctx = getInternals(this);\n\n // Two equal-standing forms (RFC-4 M2 / #1548): the descriptor\n // `navigate(target, opts)` (opts at position 2) and the positional\n // `navigate(name, params, search, opts)` (opts at position 4). The v1\n // `navigate(name, params, opts)` form is gone — its position-3 opts is now\n // the `search` slot; unpack whichever form the caller used into one path.\n let routeName: string;\n let routeParams: Params | undefined;\n let search: SearchParams | undefined;\n let opts: NavigationOptions;\n\n // The static type excludes null, but `navigate(null)` is a real runtime\n // misuse that must stay graceful (ROUTE_NOT_FOUND, not a crash on\n // `null.name`) — the null check routes it to the positional branch.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime null guard for navigate(null)\n if (typeof nameOrTarget === \"object\" && nameOrTarget !== null) {\n routeName = nameOrTarget.name;\n routeParams = nameOrTarget.params;\n search = nameOrTarget.search;\n opts = (paramsOrOptions as NavigationOptions | undefined) ?? EMPTY_OPTS;\n } else {\n routeName = nameOrTarget;\n routeParams = paramsOrOptions as Params | undefined;\n search = routeSearch;\n opts = options ?? EMPTY_OPTS;\n }\n\n throwOnMisChanneledKey(ctx, \"navigate\", routeName, routeParams);\n\n ctx.validator?.navigation.validateNavigateArgs(routeName);\n ctx.validator?.navigation.validateParams(routeParams, \"navigate\");\n ctx.validator?.navigation.validateNavigationOptions(opts, \"navigate\");\n\n const promiseState = this.#navigation.navigate(\n routeName,\n routeParams ?? EMPTY_PARAMS,\n search,\n opts,\n );\n\n if (this.#navigation.lastSyncResolved) {\n this.#navigation.lastSyncResolved = false;\n } else if (this.#navigation.lastSyncRejected) {\n // Cached rejection — already pre-suppressed at module load, skip .catch()\n this.#navigation.lastSyncRejected = false;\n } else {\n this.#suppressUnhandledRejection(promiseState);\n }\n\n return promiseState;\n }\n\n navigateToDefault(options?: NavigationOptions): Promise<State> {\n this.#assertNotReentrant();\n\n const ctx = getInternals(this);\n\n ctx.validator?.navigation.validateNavigateToDefaultArgs(options);\n\n const opts = options ?? EMPTY_OPTS;\n\n ctx.validator?.navigation.validateNavigationOptions(\n opts,\n \"navigateToDefault\",\n );\n\n const promiseState = this.#navigation.navigateToDefault(opts);\n\n if (this.#navigation.lastSyncResolved) {\n this.#navigation.lastSyncResolved = false;\n } else if (this.#navigation.lastSyncRejected) {\n this.#navigation.lastSyncRejected = false;\n } else {\n this.#suppressUnhandledRejection(promiseState);\n }\n\n return promiseState;\n }\n\n navigateToNotFound(path?: string): State {\n this.#assertNotReentrant();\n\n if (!this.#eventBus.isActive()) {\n throw new RouterError(errorCodes.ROUTER_NOT_STARTED);\n }\n\n if (path !== undefined && typeof path !== \"string\") {\n throw new TypeError(\n `[router.navigateToNotFound] path must be a string, got ${typeof path}`,\n );\n }\n\n if (path !== undefined) {\n return this.#navigation.navigateToNotFound(path);\n }\n\n // #1172: a path-less call derives the default path from the committed state.\n // During the two-phase start window the router is active (`isActive()` true)\n // while `getState()` is still undefined, so throw an actionable RouterError\n // instead of a cryptic `TypeError` from dereferencing the absent state —\n // same class as the #939 always-on invariant guards.\n const current = this.#state.get();\n\n if (current === undefined) {\n throw new RouterError(errorCodes.ROUTER_NOT_STARTED, {\n message:\n \"[router.navigateToNotFound] cannot derive the path before the start navigation commits — pass an explicit path\",\n });\n }\n\n return this.#navigation.navigateToNotFound(current.path);\n }\n\n /**\n * Classifies a fire-and-forget rejection as an EXPECTED outcome that must\n * stay silent (no log). Shared by the navigate and start suppressors so the\n * suppression contract lives in one place.\n *\n * A suppressed RouterError code is a normal caller-owned navigation result\n * (a guard block, SAME_STATES, ROUTER_NOT_STARTED, …) — see\n * SUPPRESSED_ERROR_CODES (#721).\n *\n * The #945 RecursionDepthError carve-out is gone: a reentrant navigate() from a\n * listener can no longer self-feed — it throws REENTRANT_NAVIGATION\n * synchronously at the facade (RFC navigation-cancellation-unification §4), so\n * navigate()'s promise never rejects with a recursion error (re-entrant emits\n * are coalesced at the emitter, #1033; reentrant route-CRUD throws\n * REENTRANT_TREE_MUTATION to the CRUD caller, #1032 — not through a navigate\n * promise).\n */\n static #isExpectedRejection(error: unknown): boolean {\n return (\n error instanceof RouterError && SUPPRESSED_ERROR_CODES.has(error.code)\n );\n }\n\n /**\n * Fire-and-forget safety: prevents unhandled rejection warnings when\n * navigate/navigateToDefault/start is called without await. Expected errors\n * are silently suppressed; unexpected ones are logged under `onSuppressed`'s\n * category — navigate by default; start() passes #onSuppressedStartError so\n * its failures are logged as \"router.start\", not \"router.navigate\" (#931).\n */\n #suppressUnhandledRejection(\n promise: Promise<State>,\n onSuppressed: (error: unknown) => void = this.#onSuppressedNavigateError,\n ): void {\n promise.catch(onSuppressed);\n }\n\n /**\n * Rejects a synchronous reentrant navigation — `navigate` /\n * `navigateToDefault` / `navigateToState` / `navigateToNotFound` called from\n * inside a transition-event listener while a transition is being dispatched\n * (RFC navigation-cancellation-unification §4). Throws synchronously: inside a\n * listener the emit's `onListenerError` isolation surfaces it (visible,\n * non-fatal); a DEFERRED (async / microtask) navigate from a listener runs\n * after dispatch settles and is allowed. Always-on core invariant guard (not\n * validator-gated).\n */\n #assertNotReentrant(): void {\n if (this.#eventBus.isProcessing()) {\n throw new RouterError(errorCodes.REENTRANT_NAVIGATION);\n }\n }\n\n /**\n * Settles the FSM after a failed start pipeline, then re-throws so the\n * rejection still surfaces to the caller. Three cases, by what the pipeline\n * reached before throwing:\n *\n * - **Pre-commit, READY** (`isReady()` and no committed state): an interceptor\n * threw after `completeStart()` reached READY but before any state committed\n * (e.g. an activation guard blocked the start navigation) — return READY →\n * IDLE via `stop()` so the router is reusable.\n * - **Pre-commit, STARTING** (`isStarting()`): the pipeline threw before\n * `completeStart()` — a sync interceptor throw before `next()`, or a throw\n * inside the namespace before commit — so unwind STARTING → IDLE via\n * `sendFail`, which also emits TRANSITION_ERROR from STARTING (#668).\n * - **Post-commit, READY with committed state** (neither branch fires): a\n * loader/interceptor threw AFTER `navigateToState` committed and emitted\n * TRANSITION_SUCCESS (the SSR/RSC loader window). Keep the committed state —\n * rolling back would retract an observed success (\"phantom success\", #763);\n * the error still surfaces via the re-throw.\n */\n #unwindFailedStart(error: unknown): never {\n if (this.#eventBus.isReady() && this.#state.get() === undefined) {\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n } else if (this.#eventBus.isStarting()) {\n this.#eventBus.sendFail(undefined, undefined, error);\n }\n\n throw error;\n }\n\n #markDisposed(): void {\n this.navigate = throwDisposed;\n this.navigateToDefault = throwDisposed;\n this.navigateToNotFound = throwDisposed;\n this.start = throwDisposed;\n this.stop = throwDisposed;\n this.usePlugin = throwDisposed;\n\n this.subscribe = throwDisposed;\n this.subscribeLeave = throwDisposed;\n this.canNavigateTo = throwDisposed;\n }\n}\n\nfunction throwDisposed(): never {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n}\n\n/**\n * Derives CreateMatcherOptions from router Options.\n * Maps core option names to matcher option names.\n */\nfunction deriveMatcherOptions<Dependencies extends DefaultDependencies>(\n options: Readonly<Options<Dependencies>>,\n): CreateMatcherOptions {\n return {\n strictTrailingSlash: options.trailingSlash === \"strict\",\n caseSensitive: options.caseSensitive,\n strictQueryParams: options.queryParamsMode === \"strict\",\n urlParamsEncoding: options.urlParamsEncoding,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n queryParams: options.queryParams!,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Router.js","names":["#options","#limits","#dependenciesStore","#state","#routes","#routeLifecycle","#plugins","#navigation","#lifecycle","#eventBus","#onSuppressedStartError","RouterLogger","isExpectedRejection","OptionsNamespace","createLimits","createDependenciesStore","StateNamespace","RoutesNamespace","RouteLifecycleNamespace","PluginsNamespace","NavigationNamespace","RouterLifecycleNamespace","createRouterFSM","EventEmitter","EventBusNamespace","createTernaryInterceptable","EMPTY_PARAMS","createInterceptable","#assertNotReentrant","#asPromise","getInternals","normalizeParams","#runStart","#markDisposed","findMisChanneledKey","canonicalize","materialize","buildURL","getTransitionPath","RouterError","errorCodes","CACHED_ALREADY_STARTED_ERROR","#unwindFailedStart"],"sources":["../../src/Router.ts"],"sourcesContent":["// packages/core/src/Router.ts\n\n/**\n * Router class - facade with integrated namespaces.\n *\n * All functionality is now provided by namespace classes.\n */\n\nimport { assertChannelCorrect, findMisChanneledKey } from \"./channels\";\nimport { EMPTY_PARAMS, errorCodes } from \"./constants\";\nimport {\n assertLoggerConfig,\n guardDependencies,\n guardRouteStructure,\n} from \"./guards\";\nimport { normalizeParams } from \"./helpers\";\nimport {\n createInterceptable,\n createTernaryInterceptable,\n getInternals,\n registerInternals,\n throwOnMisChanneledKey,\n} from \"./internals\";\nimport { createLimits } from \"./limits\";\nimport {\n EventBusNamespace,\n NavigationNamespace,\n OptionsNamespace,\n PluginsNamespace,\n RouteLifecycleNamespace,\n RouterLifecycleNamespace,\n RoutesNamespace,\n StateNamespace,\n createDependenciesStore,\n} from \"./namespaces\";\nimport { isExpectedRejection } from \"./namespaces/NavigationNamespace/constants\";\nimport { CACHED_ALREADY_STARTED_ERROR } from \"./namespaces/RouterLifecycleNamespace/constants\";\nimport { buildURL, canonicalize, materialize } from \"./pipeline\";\nimport { RouterError } from \"./RouterError\";\nimport { createRouterFSM } from \"./routerFSM\";\nimport { getTransitionPath } from \"./transitionPath\";\nimport { EventEmitter } from \"./utils/event-emitter\";\nimport { RouterLogger } from \"./utils/logger\";\nimport { wireNamespaces } from \"./wiring\";\n\nimport type { CreateMatcherOptions } from \"./engine\";\nimport type { RouterInternals } from \"./internals\";\nimport type { DependenciesStore } from \"./namespaces\";\nimport type {\n DefaultDependencies,\n LeaveFn,\n NavigationOptions,\n NavigationTarget,\n Options,\n Params,\n Router as RouterInterface,\n SearchParams,\n State,\n SubscribeFn,\n Unsubscribe,\n PluginFactory,\n Route,\n} from \"./types\";\nimport type { Limits, RouterEventMap } from \"./types/internal\";\n\nconst EMPTY_OPTS: Readonly<NavigationOptions> = Object.freeze({});\n\n/**\n * Router class with integrated namespace architecture.\n *\n * All functionality is provided by namespace classes:\n * - OptionsNamespace: getOptions (immutable)\n * - DependenciesStore: get/set/remove dependencies\n * - EventEmitter: subscribe\n * - StateNamespace: state storage (getState, setState, getPreviousState)\n * - RoutesNamespace: route tree operations\n * - RouteLifecycleNamespace: canActivate/canDeactivate guards\n * - PluginsNamespace: plugin lifecycle\n * - NavigationNamespace: navigate\n * - RouterLifecycleNamespace: start, stop, isStarted\n *\n * @internal This class implementation is internal. Use createRouter() instead.\n */\nexport class Router<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> implements RouterInterface<Dependencies> {\n [key: string]: unknown;\n\n // ============================================================================\n // Namespaces\n // ============================================================================\n\n readonly #options: OptionsNamespace<Dependencies>;\n readonly #limits: Limits;\n readonly #dependenciesStore: DependenciesStore<Dependencies>;\n readonly #state: StateNamespace;\n readonly #routes: RoutesNamespace<Dependencies>;\n readonly #routeLifecycle: RouteLifecycleNamespace<Dependencies>;\n readonly #plugins: PluginsNamespace<Dependencies>;\n readonly #navigation: NavigationNamespace;\n readonly #lifecycle: RouterLifecycleNamespace;\n\n readonly #eventBus: EventBusNamespace;\n\n /**\n * Per-instance suppressor for fire-and-forget `start()`. It logs through THIS\n * router's logger (built in the constructor), so it cannot be static (#724).\n *\n * Only start, since Step 0: the navigate/navigateToState/navigateToDefault\n * suppressor moved to `NavigationNamespace`, which is where those promises are\n * created and therefore the only layer that knows which of them are already\n * pre-suppressed. Start keeps its own because it suppresses a promise the\n * FACADE builds (`internalStart.catch(#unwindFailedStart)`), and because the\n * #931 category split survives — start failures log under \"router.start\" (a\n * start interceptor throwing a plain Error after next() committed, #763, or a\n * cryptic path TypeError — neither a suppressed RouterError). Both sides still\n * classify through ONE shared policy, `isExpectedRejection`.\n */\n readonly #onSuppressedStartError: (error: unknown) => void;\n\n // ============================================================================\n // Constructor\n // ============================================================================\n\n /**\n * @param routes - Route definitions\n * @param options - Router options\n * @param dependencies - DI dependencies\n */\n constructor(\n routes: Route<Dependencies>[] = [],\n options: Partial<Options<Dependencies>> = {},\n dependencies: Dependencies = {} as Dependencies,\n ) {\n // Extract the logger config WITHOUT mutating the caller's `options` object\n // (#724). `routerOptions` is the logger-stripped view handed to the options\n // pipeline so `logger` never lands in the frozen router options.\n const { logger: loggerConfig, ...routerOptions } = options;\n\n if (loggerConfig) {\n assertLoggerConfig(loggerConfig);\n }\n\n // Per-router logger instance — replaces the former process-global singleton\n // whose configure() leaked across every router in the process, last\n // createRouter winning (#724). Stored on ctx (registerInternals below), so\n // the facade reads getInternals(this).logger; namespaces receive it via\n // their deps at wiring; plugins reach it through getPluginApi(router).logger.\n const logger = new RouterLogger(loggerConfig);\n\n // Per-instance fire-and-forget suppressor (see the field declaration): it\n // logs through THIS router's logger, so it is built here, not static.\n this.#onSuppressedStartError = (error: unknown): void => {\n if (isExpectedRejection(error)) {\n return;\n }\n\n logger.error(\"router.start\", \"Unexpected start error\", error);\n };\n\n // =========================================================================\n // Validate inputs before creating namespaces\n // =========================================================================\n\n // Always validate the caller's options (catches non-object / array inputs)\n OptionsNamespace.validateOptionsIsObject(options);\n\n // Unconditional guard-level validation before creating namespaces\n guardDependencies(dependencies);\n\n // Stryker disable next-line EqualityOperator: equivalent — `>= 0` is always true, but `guardRouteStructure([])` on an empty array is a no-op, so validating an empty list behaves identically to skipping it. (ConditionalExpression stays live: `→false` skips validation of a real route list and is killable.)\n if (routes.length > 0) {\n guardRouteStructure(routes);\n }\n\n // =========================================================================\n // Create Namespaces\n // =========================================================================\n\n this.#options = new OptionsNamespace(routerOptions);\n this.#limits = createLimits(routerOptions.limits);\n this.#dependenciesStore =\n createDependenciesStore<Dependencies>(dependencies);\n this.#state = new StateNamespace();\n this.#routes = new RoutesNamespace<Dependencies>(\n routes,\n deriveMatcherOptions(this.#options.get()),\n logger,\n );\n this.#routeLifecycle = new RouteLifecycleNamespace<Dependencies>();\n this.#plugins = new PluginsNamespace<Dependencies>();\n this.#navigation = new NavigationNamespace();\n this.#lifecycle = new RouterLifecycleNamespace();\n\n // =========================================================================\n // Initialize EventBus\n // =========================================================================\n\n const routerFSM = createRouterFSM();\n\n const emitter = new EventEmitter<RouterEventMap>({\n // Shared per-listener error sink: EventEmitter reports synchronous listener\n // throws here, and EventBusNamespace.subscribe routes an async listener's\n // rejected Promise through the SAME sink (#944) — both land in one place.\n onListenerError: (eventName, error) => {\n logger.error(\"Router\", `Error in listener for ${eventName}:`, error);\n },\n onListenerWarn: (eventName, count) => {\n logger.warn(\n \"router.addEventListener\",\n `Event \"${eventName}\" has ${count} listeners — possible memory leak`,\n );\n },\n });\n\n this.#eventBus = new EventBusNamespace({\n routerFSM,\n emitter,\n // The FSM CANCEL action aborts the in-flight\n // navigation controller via this injected effect — \"FSM CANCEL ⟹\n // controller aborted\" in one place. `#navigation` is constructed above.\n abortController: (reason) => {\n this.#navigation.abortCurrentController(reason);\n },\n });\n\n // =========================================================================\n // Register Internals (WeakMap for plugin/infrastructure access)\n // =========================================================================\n // Registered BEFORE wiring (#1331) so every namespace's deps-closure sees a\n // router already present in the internals registry — `getInternals(router)`\n // never throws during wiring, and guard factories flushed at the end of the\n // constructor see a fully-registered instance.\n\n const interceptorsMap: RouterInternals[\"interceptors\"] = new Map();\n\n // THE single forwardState boundary (#1548/#1549). The interceptable resolves\n // the route (forwardTo) and runs the whole interceptor chain — a plugin\n // injecting params, a search-schema validation, etc. The outer layer then\n // CHECKS the channels once, keyed on the RESOLVED route's `?`-declaration.\n //\n // It used to REPAIR them instead (`separateChannels`, stage ②): a declared\n // query key left in the params bag was moved into the query channel behind\n // the producer's back. Three things were wrong with that. The producer kept\n // believing the bag it wrote was the one that shipped. A plugin could\n // inject past a validation that had already run — search-schema documented\n // exactly that leak, with a test named LEAKS. And the caller's own\n // mis-channelled key and a chain default's query half landed in DIFFERENT\n // channels, where no merge ranks them, so the default silently won (#1570).\n // Refusing is the whole fix: whoever names the route knows its declaration.\n //\n // `as unknown as` is required: the closure is non-generic, but\n // RouterInternals[\"forwardState\"] is declared generic `<P, S>`, which tsc\n // will not infer from a non-generic source (Sonar S4325 misclassifies this\n // as a redundant cast).\n const rawForwardState = createTernaryInterceptable(\n \"forwardState\",\n (name: string, params: Params, search?: SearchParams) =>\n this.#routes.forwardState(name, params, search),\n interceptorsMap,\n );\n\n const forwardState = ((\n name: string,\n params: Params,\n search?: SearchParams,\n ) => {\n const forwarded = rawForwardState(name, params, search);\n\n // The DECLARATION that matters is the RESOLVED route's — it owns the URL\n // that gets printed. When a chain resolved to a different route, say so:\n // a caller who wrote `navigate(\"src\", { lang })` looked at `src`'s config,\n // where `lang` is undeclared and legitimate, and needs to be told that the\n // hop landed somewhere that spells it `?lang`. Naming only the target\n // would read as a message about a route they never mentioned.\n assertChannelCorrect(\n \"forwardState\",\n forwarded.name,\n forwarded.params,\n this.#routes.getQueryParams(forwarded.name),\n () =>\n forwarded.name === name\n ? \"the `params` bag leaving the forwardState chain\"\n : `the \\`params\\` bag leaving the forwardState chain (forwarded here from \"${name}\")`,\n );\n\n return {\n name: forwarded.name,\n // The type says `params: P`, and across THIS boundary the type is a\n // contract, not a guarantee: `rawForwardState` is an interceptable, so\n // the value has passed through user code that can spread a partial\n // result. The net used to be reached by stage ②'s split (an all-query\n // bag left the path half undefined) and is now reached only by that\n // contract violation — still worth surviving rather than putting\n // `undefined` into `state.params`. Pinned by \"normalises a params bag an\n // interceptor dropped to `undefined`\" in forwardState.test.ts, which\n // fails if this is removed.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- see above: the declared type cannot model an interceptor's runtime return\n params: forwarded.params ?? EMPTY_PARAMS,\n search: forwarded.search,\n };\n }) as unknown as RouterInternals[\"forwardState\"];\n\n registerInternals(this, {\n logger,\n makeState: (name, params, search, path) =>\n this.#state.makeState(name, params, search, path),\n getMetaForState: (name) => this.#routes.getMetaForState(name),\n getQueryParams: (name) => this.#routes.getQueryParams(name),\n forwardState,\n buildStateResolved: (name, params) =>\n this.#routes.buildStateResolved(name, params),\n port: () => this.#routes.getPort(),\n matchPath: (path, matchOptions) =>\n this.#routes.matchPath(path, matchOptions),\n getOptions: () => this.#options.get(),\n addEventListener: (eventName, cb) =>\n this.#eventBus.addEventListener(eventName, cb),\n treeChanged: {\n emit: (event) => {\n this.#eventBus.emitTreeChanged(event);\n },\n subscribe: (handler) => this.#eventBus.subscribeTreeChanged(handler),\n listenerCount: () => this.#eventBus.treeChangedListenerCount(),\n isEmitting: () => this.#eventBus.isEmittingTreeChanged(),\n },\n buildPath: createTernaryInterceptable(\n \"buildPath\",\n (route: string, params?: Params, search?: SearchParams) =>\n this.#routes.buildPath(\n route,\n params ?? EMPTY_PARAMS,\n search,\n this.#options.get(),\n ),\n interceptorsMap,\n ),\n emitTransitionError: (error) => {\n this.#eventBus.sendFailSafe(undefined, this.#state.get(), error);\n },\n emitTransitionSuccess: (toState, fromState, opts) => {\n this.#eventBus.emitTransitionSuccess(toState, fromState, opts);\n },\n navigateToNotFound: (path) => this.#navigation.navigateToNotFound(path),\n start: createInterceptable(\n \"start\",\n (path: string) => {\n return this.#lifecycle.start(path);\n },\n interceptorsMap,\n ),\n navigateToState: (state, navOpts) => {\n // Plugin-only navigation primitive (#525). Fire-and-forget safe like the\n // public facade methods — popstate handlers call it without awaiting —\n // but the safety now belongs to the namespace that creates the promise,\n // so this closure only owes callers the Promise shape.\n this.#assertNotReentrant();\n\n return Router.#asPromise(\n this.#navigation.navigateToState(state, navOpts ?? EMPTY_OPTS),\n );\n },\n interceptors: interceptorsMap,\n setRootPath: (rootPath) => {\n this.#routes.setRootPath(rootPath);\n },\n getRootPath: () => this.#routes.getStore().rootPath,\n getTree: () => this.#routes.getStore().tree,\n isDisposed: () => this.#eventBus.isDisposed(),\n validator: null,\n // Dependencies (issue #172)\n dependenciesGetStore: () => this.#dependenciesStore,\n // Clone support (issue #173)\n getCloneState: () => ({\n options: { ...this.#options.get() },\n dependencies: { ...this.#dependenciesStore.dependencies },\n pluginFactories: this.#plugins.getAll(),\n // `logger` is a const in this constructor's scope (a RouterLogger class\n // instance), so getConfig() yields the resolved config a clone inherits\n // — frozen options don't carry `logger`, so cloneRouter reads it here.\n loggerConfig: logger.getConfig(),\n }),\n routeGetStore: () => this.#routes.getStore(),\n // Cross-namespace state (issue #174)\n getStateName: () => this.#state.get()?.name,\n isTransitioning: () => this.#eventBus.isTransitioning(),\n clearState: () => {\n this.#state.set(undefined);\n },\n setState: (state) => {\n this.#state.set(state);\n },\n routerExtensions: [],\n contextClaimRecords: new Set(),\n hydrationState: null,\n });\n\n // =========================================================================\n // Wire Dependencies\n // =========================================================================\n\n wireNamespaces<Dependencies>({\n router: this,\n options: this.#options,\n limits: this.#limits,\n dependenciesStore: this.#dependenciesStore,\n state: this.#state,\n routes: this.#routes,\n routeLifecycle: this.#routeLifecycle,\n plugins: this.#plugins,\n navigation: this.#navigation,\n lifecycle: this.#lifecycle,\n eventBus: this.#eventBus,\n });\n\n // =========================================================================\n // Bind Public Methods\n // =========================================================================\n // All public methods that access private fields must be bound to preserve\n // `this` context when methods are extracted as references.\n // See: https://github.com/tc39/proposal-bind-operator\n // =========================================================================\n\n // Path & State Building\n this.isActiveRoute = this.isActiveRoute.bind(this);\n this.buildPath = this.buildPath.bind(this);\n\n // State Management\n this.getState = this.getState.bind(this);\n this.getPreviousState = this.getPreviousState.bind(this);\n this.areStatesEqual = this.areStatesEqual.bind(this);\n this.shouldUpdateNode = this.shouldUpdateNode.bind(this);\n\n // Router Lifecycle\n this.isActive = this.isActive.bind(this);\n this.start = this.start.bind(this);\n this.stop = this.stop.bind(this);\n this.dispose = this.dispose.bind(this);\n\n // Route Lifecycle (Guards)\n this.canNavigateTo = this.canNavigateTo.bind(this);\n\n // Plugins\n this.usePlugin = this.usePlugin.bind(this);\n\n // Navigation\n this.navigate = this.navigate.bind(this);\n this.navigateToDefault = this.navigateToDefault.bind(this);\n this.navigateToNotFound = this.navigateToNotFound.bind(this);\n\n // Subscription\n this.subscribe = this.subscribe.bind(this);\n this.subscribeLeave = this.subscribeLeave.bind(this);\n this.isLeaveApproved = this.isLeaveApproved.bind(this);\n\n // =========================================================================\n // Flush initial-route guard factories\n // =========================================================================\n // Deferred out of wiring (#1331): the pending canActivate/canDeactivate\n // factories from initial route definitions are compiled and executed HERE,\n // on the fully-built and bound router — a factory calling read-only methods\n // (`buildPath()`, `isActiveRoute()`, `getState()`) no longer hits a\n // half-assembled instance. Side-effectful calls (`navigate`, `usePlugin`,\n // route-CRUD) stay OUT OF CONTRACT: factories re-execute outside the\n // constructor (cloneRouter re-compiles definition guards per clone;\n // #recompileSlot re-runs a factory after a definition-only clear), so any\n // side effect would duplicate per re-execution — see CLAUDE.md. Runtime\n // add()/replace() compile guards in their own PREPARE phase and never touch\n // these pending maps.\n //\n // Fail-closed on a factory throw: by this point a router reference leaked\n // from an earlier factory is fully operational, while later guards would\n // stay silently unregistered — a fail-open guard bypass. Disposing before\n // the rethrow turns any leaked reference into a ROUTER_DISPOSED-throwing\n // husk (pre-#1331 such a reference was inert because getInternals threw).\n try {\n this.#routes.flushPendingGuards();\n } catch (error) {\n this.dispose();\n\n throw error;\n }\n }\n\n // ============================================================================\n // Path & State Building\n // ============================================================================\n\n isActiveRoute(\n name: string,\n params?: Params,\n search?: SearchParams,\n strictEquality?: boolean,\n ignoreQueryParams?: boolean,\n ): boolean {\n getInternals(this).validator?.routes.validateIsActiveRouteArgs(\n name,\n params,\n strictEquality,\n ignoreQueryParams,\n );\n\n getInternals(this).validator?.routes.validateRouteName(\n name,\n \"isActiveRoute\",\n );\n\n // Empty string is special case - warn and return false (root node is not a parent)\n if (name === \"\") {\n getInternals(this).logger.warn(\n \"real-router\",\n 'isActiveRoute(\"\") called with empty string. Root node is not considered a parent of any route.',\n );\n\n return false;\n }\n\n // Slot-shift (RFC-4 M2 / #1548): `search` is the explicit query channel at\n // position 3; `strictEquality` / `ignoreQueryParams` shift to 4 / 5.\n return this.#routes.isActiveRoute(\n name,\n params,\n search,\n strictEquality,\n ignoreQueryParams,\n );\n }\n\n buildPath(route: string, params?: Params, search?: SearchParams): string {\n const ctx = getInternals(this);\n\n ctx.validator?.routes.validateBuildPathArgs(route);\n ctx.validator?.navigation.validateParams(params, \"buildPath\");\n\n // `search` (RFC-4 M2 / #1548) is the explicit query channel; the matcher\n // builds the query string from it and the path from `params`, resolving a\n // colliding name (`/items/:id?id`). Omitted → the v1 single-bag path.\n return ctx.buildPath(route, normalizeParams(params), search);\n }\n\n // ============================================================================\n // State Management (delegated to StateNamespace)\n // ============================================================================\n\n getState<P extends Params = Params>(): State<P> | undefined {\n return this.#state.get<P>();\n }\n\n getPreviousState(): State | undefined {\n return this.#state.getPrevious();\n }\n\n areStatesEqual(\n state1: State | undefined,\n state2: State | undefined,\n ignoreQueryParams = true,\n ): boolean {\n getInternals(this).validator?.state.validateAreStatesEqualArgs(\n state1,\n state2,\n ignoreQueryParams,\n );\n\n return this.#state.areStatesEqual(state1, state2, ignoreQueryParams);\n }\n\n shouldUpdateNode(\n nodeName: string,\n ): (toState: State, fromState?: State) => boolean {\n getInternals(this).validator?.routes.validateShouldUpdateNodeArgs(nodeName);\n\n return RoutesNamespace.shouldUpdateNode(nodeName, (name) =>\n this.#routes.getMetaForState(name),\n );\n }\n\n // ============================================================================\n // Router Lifecycle\n // ============================================================================\n\n isActive(): boolean {\n return this.#eventBus.isActive();\n }\n\n /**\n * ONE fire-and-forget checkpoint for `start()`, deliberately — the same shape\n * `NavigationNamespace.#settle` gives the navigate family, and for the same\n * reason: a `.catch()` remembered at each `return` site is a thing that can be\n * forgotten, and a forgotten one is invisible until it leaks.\n *\n * It HAD been forgotten (#1605). The `ALREADY_STARTED` rejection left through\n * an early `return` above the suppressor, so a second, unawaited `start()`\n * raised an `unhandledRejection` — process-fatal under Node 22+'s default\n * `--unhandled-rejections=throw`, with a stack pointing at the cached error's\n * module constant rather than at the caller. Every return site now leaves\n * through `#runStart`, so no future early return can reopen it.\n */\n start(startPath: string): Promise<State> {\n const promiseState = this.#runStart(startPath);\n\n promiseState.catch(this.#onSuppressedStartError);\n\n return promiseState;\n }\n\n stop(): this {\n // SendCancelIfPossible → FSM CANCEL → the CANCEL action\n // aborts the in-flight controller (waking the pipeline). No separate abort.\n this.#eventBus.sendCancelIfPossible(this.#state.get());\n\n // `isStarting()` is included (#1185): a stop() while `start()` is parked in\n // an async start-interceptor (FSM STARTING, before `next()`) must cancel the\n // start, not silently no-op. `sendStop()` takes STARTING → IDLE (FSM table),\n // and `RouterLifecycleNamespace.start` re-checks `isIdle()` after the\n // interceptor chain and rejects with TRANSITION_CANCELLED — mirroring the\n // guard-phase behavior (which already cancels from TRANSITION_STARTED).\n if (\n !this.#eventBus.isReady() &&\n !this.#eventBus.isTransitioning() &&\n !this.#eventBus.isStarting()\n ) {\n return this;\n }\n\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n\n return this;\n }\n\n dispose(): void {\n // Stryker disable next-line BlockStatement: equivalent — emptying the early-return re-runs the dispose body on a 2nd call, but it is fully idempotent (FSM `send(DISPOSE)` no-ops from DISPOSED, `disposeAll()` already cleared `#unsubscribes`, every clear is idempotent). (ConditionalExpression stays live: `→true` always-returns and never disposes = killed.)\n if (this.#eventBus.isDisposed()) {\n return;\n }\n\n // the FSM CANCEL action aborts the in-flight controller.\n this.#eventBus.sendCancelIfPossible(this.#state.get());\n\n if (this.#eventBus.isReady() || this.#eventBus.isTransitioning()) {\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n }\n\n this.#eventBus.sendDispose();\n this.#eventBus.clearAll();\n\n this.#plugins.disposeAll();\n\n // Safety net: clean up extensions plugins failed to remove in teardown\n const ctx = getInternals(this);\n\n for (const extension of ctx.routerExtensions) {\n for (const key of extension.keys) {\n delete (this as Record<string, unknown>)[key];\n }\n }\n\n ctx.routerExtensions.length = 0;\n\n // Safety net: release context namespace claims plugins failed to release in teardown\n ctx.contextClaimRecords.clear();\n\n // Safety net: drop interceptors plugins failed to remove in teardown (#1199).\n // The third per-plugin registration channel — symmetric with routerExtensions\n // / contextClaimRecords above. `buildPath` is not method-swapped by dispose\n // and reads this Map live, so a leaked interceptor would otherwise still run\n // on the disposed router.\n ctx.interceptors.clear();\n\n this.#routes.clearRoutes();\n this.#routeLifecycle.clearAll();\n this.#state.reset();\n this.#dependenciesStore.dependencies = Object.create(\n null,\n ) as Partial<Dependencies>;\n\n this.#markDisposed();\n }\n\n // ============================================================================\n // Route Lifecycle (Guards)\n // ============================================================================\n\n canNavigateTo(name: string, params?: Params, search?: SearchParams): boolean {\n const ctx = getInternals(this);\n\n ctx.validator?.routes.validateRouteName(name, \"canNavigateTo\");\n ctx.validator?.navigation.validateParams(params, \"canNavigateTo\");\n\n if (!this.#routes.hasRoute(name)) {\n return false;\n }\n\n // Mirror EVERY way `navigate` refuses these same arguments, not only the\n // guard verdict (#1576). A declared query key handed in the PATH bag makes\n // `navigate` throw synchronously at the facade (channel guard P1, #1572), so\n // the route is unreachable with this input — exactly the situation invariant\n // canNavigateTo #5 already answers `false` to for an unbuildable path (#725).\n // Answering `true` here promised a navigation that throws on the click.\n //\n // The RAW caller bag, before `forwardState`: the same argument, the same\n // registry and the same name P1 reads, so the predicate cannot be stricter\n // OR laxer than the verb. The `/items/:id?id` collision is absent from\n // `queryNames` by construction (#843 / #1549), so it stays navigable in both.\n //\n // A `false` rather than a rethrow: a capability predicate answers, it never\n // throws (#725), and it runs on every `<Link>` render across six adapters —\n // which is exactly why P1 does not instrument the predicates (#1572).\n if (\n findMisChanneledKey(params, this.#routes.getQueryParams(name)) !==\n undefined\n ) {\n return false;\n }\n\n // Resolution runs USER code and must not escape as an exception (#1577):\n // a dynamic `forwardTo` callback, a plugin's `forwardState` interceptor, and\n // the caller's own bag (the merge walks it key by key, so an accessor-backed\n // key throws here — the channel guard itself does NOT, it catches its own\n // read) all sit on this one call. The\n // predicate is documented TOTAL — it answers, it never throws (INVARIANTS\n // canNavigateTo #5, #725) — and its sibling `isActiveRoute` has wrapped the\n // very same primitive since #1573 (`RoutesNamespace.ts:631-645`). Leaving\n // this one bare made the two render-path predicates disagree about what a\n // throwing resolution means.\n //\n // A separate `try` rather than widening the one below: that one is SILENT by\n // design (an unbuildable path is a normal \"unreachable with this input\"\n // answer, #725), while user code crashing is an operational fault that must\n // never vanish — the same split #959 draws for a throwing guard.\n // Stages ① + ③ + the mode gate, one pass through the pipeline (nav-pipeline\n // Phase 2, step 2-3). `canonicalize` reaches the same `forwardState` seam\n // this method used to call directly (`port.resolveForward` IS\n // `ctx.forwardState`), so the resolution, the interceptor zone and the\n // channel CHECK on the seam are all unchanged — what the pipeline\n // replaces is the hand-rolled composition that followed.\n // Read ONCE (#1589): this predicate reached for the port twice — here and\n // again for `buildURL` below — on every `<Link>` render. The port is one\n // object per router, created at wiring time, so the second read could only\n // ever return the same reference.\n const port = this.#routes.getPort();\n\n let canonical;\n\n try {\n canonical = canonicalize(\n port,\n name,\n // The singleton, not a fresh `{}` (#1589): this predicate runs on every\n // `<Link>` render too, and `normalizeParams` recognises `EMPTY_PARAMS` by\n // identity — a literal makes it walk and re-allocate instead.\n params ?? EMPTY_PARAMS,\n search,\n );\n } catch (error) {\n ctx.logger.warn(\n \"router.canNavigateTo\",\n `Resolving route \"${name}\" threw while answering the predicate; treating the route as unreachable.`,\n error,\n );\n\n return false;\n }\n\n // Build `toState` exactly as `buildNavigateState` does — WITH route-meta and\n // normalized params — so `getTransitionPath` takes its STANDARD PATH and\n // trims the shared ancestor, mirroring navigate's guard set (#970). A\n // meta-less `toState` makes both sides meta-less (the committed `getState()`\n // carries no meta after a path-matched `start()`), so `getTransitionPath`\n // takes FAST PATH 3 and (de)activates the WHOLE chain incl. shared ancestors\n // → false-negative (\"Link disabled though the click would succeed\").\n // `normalizeParams` also aligns the params guards observe with navigate's.\n // `skipFreeze` (5th arg) mirrors the navigate guard phase, where guards see\n // an unfrozen, transition-less `toState` (freeze happens later in\n // `completeTransition`).\n //\n // A capability predicate must answer, not throw: if the target path can't be\n // built from these params (e.g. a required path param is missing), the route\n // is simply unreachable with this input — return `false` rather than letting\n // `buildPath` throw (#725).\n let toState: State;\n\n try {\n // ⑤a then ⑤b. `buildURL` is usable HERE (unlike in `buildPath` itself,\n // where it would recurse through the interceptable `ctx.buildPath` that\n // wraps that very method): this point is not the one the port prints\n // through, so the URL is built by the pipeline and the state materialised\n // from the SAME canonical intent — `toState.search` and `toState.path`\n // cannot drift. `skipFreeze` mirrors the navigate guard phase, where\n // guards see an unfrozen, transition-less `toState`.\n toState = materialize(canonical, {\n path: buildURL(canonical, port),\n skipFreeze: true,\n });\n } catch {\n return false;\n }\n\n const fromState = this.#state.get();\n\n const { toDeactivate, toActivate } = getTransitionPath(\n toState,\n fromState,\n (routeName) => this.#routes.getMetaForState(routeName),\n );\n\n return this.#routeLifecycle.canNavigateTo(\n toDeactivate,\n toActivate,\n toState,\n fromState,\n );\n }\n\n // ============================================================================\n // Plugins\n // ============================================================================\n\n usePlugin(\n ...plugins: (PluginFactory<Dependencies> | false | null | undefined)[]\n ): Unsubscribe {\n // Post-dispose guard, mirroring #946 for subscribe/subscribeLeave. A\n // reference captured before dispose() (`const up = router.usePlugin`)\n // bypasses the #markDisposed method swap, so the swap alone is not enough:\n // without this, the factory would run on a disposed router (real side\n // effects), listeners would land in the cleared emitter, and teardown would\n // never fire — a silent zombie plugin (#1196).\n if (this.#eventBus.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n const filtered = plugins.filter(Boolean) as PluginFactory<Dependencies>[];\n\n if (filtered.length === 0) {\n return () => {};\n }\n\n const ctx = getInternals(this);\n\n ctx.validator?.plugins.validatePluginLimit(\n this.#plugins.count(),\n this.#limits,\n );\n for (const plugin of filtered) {\n // `getAll()` sits inside the optional-chain argument on purpose: with no\n // validator installed (production default) the `?.` short-circuits and the\n // array is never allocated. Hoisting it out would either allocate on the\n // no-validator hot path or push the dev-only branch out of coverage.\n ctx.validator?.plugins.validateNoDuplicatePlugins(\n plugin,\n this.#plugins.getAll(),\n );\n }\n\n return this.#plugins.use(...filtered);\n }\n\n // ============================================================================\n // Subscription (backed by EventEmitter)\n // ============================================================================\n\n subscribe(listener: SubscribeFn): Unsubscribe {\n EventBusNamespace.validateSubscribeListener(listener);\n\n return this.#eventBus.subscribe(listener);\n }\n\n subscribeLeave(listener: LeaveFn): Unsubscribe {\n EventBusNamespace.validateSubscribeLeaveListener(listener);\n\n return this.#eventBus.subscribeLeave(listener);\n }\n\n isLeaveApproved(): boolean {\n return this.#eventBus.isLeaveApproved();\n }\n\n // ============================================================================\n // Navigation\n // ============================================================================\n\n navigate(\n target: NavigationTarget,\n options?: NavigationOptions,\n ): Promise<State>;\n navigate(\n routeName: string,\n routeParams?: Params,\n routeSearch?: SearchParams,\n options?: NavigationOptions,\n ): Promise<State>;\n navigate(\n nameOrTarget: string | NavigationTarget,\n paramsOrOptions?: Params | NavigationOptions,\n routeSearch?: SearchParams,\n options?: NavigationOptions,\n ): Promise<State> {\n this.#assertNotReentrant();\n\n const ctx = getInternals(this);\n\n // Two equal-standing forms (RFC-4 M2 / #1548): the descriptor\n // `navigate(target, opts)` (opts at position 2) and the positional\n // `navigate(name, params, search, opts)` (opts at position 4). The v1\n // `navigate(name, params, opts)` form is gone — its position-3 opts is now\n // the `search` slot; unpack whichever form the caller used into one path.\n let routeName: string;\n let routeParams: Params | undefined;\n let search: SearchParams | undefined;\n let opts: NavigationOptions;\n\n // The static type excludes null, but `navigate(null)` is a real runtime\n // misuse that must stay graceful (ROUTE_NOT_FOUND, not a crash on\n // `null.name`) — the null check routes it to the positional branch.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime null guard for navigate(null)\n if (typeof nameOrTarget === \"object\" && nameOrTarget !== null) {\n routeName = nameOrTarget.name;\n routeParams = nameOrTarget.params;\n search = nameOrTarget.search;\n opts = (paramsOrOptions as NavigationOptions | undefined) ?? EMPTY_OPTS;\n } else {\n routeName = nameOrTarget;\n routeParams = paramsOrOptions as Params | undefined;\n search = routeSearch;\n opts = options ?? EMPTY_OPTS;\n }\n\n throwOnMisChanneledKey(ctx, \"navigate\", routeName, routeParams);\n\n ctx.validator?.navigation.validateNavigateArgs(routeName);\n ctx.validator?.navigation.validateParams(routeParams, \"navigate\");\n ctx.validator?.navigation.validateNavigationOptions(opts, \"navigate\");\n\n return Router.#asPromise(\n this.#navigation.navigate(\n routeName,\n routeParams ?? EMPTY_PARAMS,\n search,\n opts,\n ),\n );\n }\n\n navigateToDefault(options?: NavigationOptions): Promise<State> {\n this.#assertNotReentrant();\n\n const ctx = getInternals(this);\n\n ctx.validator?.navigation.validateNavigateToDefaultArgs(options);\n\n const opts = options ?? EMPTY_OPTS;\n\n ctx.validator?.navigation.validateNavigationOptions(\n opts,\n \"navigateToDefault\",\n );\n\n return Router.#asPromise(this.#navigation.navigateToDefault(opts));\n }\n\n navigateToNotFound(path?: string): State {\n this.#assertNotReentrant();\n\n if (!this.#eventBus.isActive()) {\n throw new RouterError(errorCodes.ROUTER_NOT_STARTED);\n }\n\n if (path !== undefined && typeof path !== \"string\") {\n throw new TypeError(\n `[router.navigateToNotFound] path must be a string, got ${typeof path}`,\n );\n }\n\n if (path !== undefined) {\n return this.#navigation.navigateToNotFound(path);\n }\n\n // #1172: a path-less call derives the default path from the committed state.\n // During the two-phase start window the router is active (`isActive()` true)\n // while `getState()` is still undefined, so throw an actionable RouterError\n // instead of a cryptic `TypeError` from dereferencing the absent state —\n // same class as the #939 always-on invariant guards.\n const current = this.#state.get();\n\n if (current === undefined) {\n throw new RouterError(errorCodes.ROUTER_NOT_STARTED, {\n message:\n \"[router.navigateToNotFound] cannot derive the path before the start navigation commits — pass an explicit path\",\n });\n }\n\n return this.#navigation.navigateToNotFound(current.path);\n }\n\n /**\n * Hands the namespace's result back as the `Promise<State>` the public API\n * owes, and does nothing else.\n *\n * A non-Promise means the navigation already settled synchronously — the\n * return TYPE says so, which is what retired `lastSyncResolved`. Suppression is\n * not the facade's business any more: the namespace attaches it where the\n * promise is created, the only layer that can tell a fresh rejection from one\n * of its own pre-suppressed singletons.\n *\n * The Promise wrap is not a new cost — the namespace used to allocate exactly\n * this one and return it.\n */\n static #asPromise(result: State | Promise<State>): Promise<State> {\n return result instanceof Promise ? result : Promise.resolve(result);\n }\n\n #runStart(startPath: string): Promise<State> {\n if (!this.#eventBus.canStart()) {\n return Promise.reject(CACHED_ALREADY_STARTED_ERROR);\n }\n\n getInternals(this).validator?.navigation.validateStartArgs(startPath);\n\n // FSM bookkeeping is split across the facade and RouterLifecycleNamespace by\n // design, NOT a missed consolidation (#940): `sendStart()` runs HERE, before\n // the interceptor chain, so the STARTING window spans the whole start\n // pipeline. A pre-`next()` interceptor throw then unwinds via STARTING →\n // `sendFail`, which emits TRANSITION_ERROR from STARTING (EventBusNamespace\n // FAIL action) for `onTransitionError` plugins. Moving `sendStart()` into the\n // namespace (the interceptor *target*) would skip STARTING on a pre-`next()`\n // throw — the namespace is never reached — silently dropping that\n // TRANSITION_ERROR: a #668 regression. The commit (`completeStart`) lives in\n // the namespace; recovery needs facade state (`#state`, `#lifecycle`), so it\n // stays here in `#unwindFailedStart`.\n this.#eventBus.sendStart();\n\n // Convert sync interceptor throws to rejections so the recovery path is\n // reachable; otherwise the throw escapes synchronously, the FSM is left in\n // STARTING, and the router is permanently bricked (#668).\n let internalStart: Promise<State>;\n\n try {\n const chainResult: unknown = getInternals(this).start(startPath);\n\n // A `start` interceptor that returns without calling next() yields a\n // non-thenable (typically undefined); the `.catch` below would then throw\n // a cryptic `TypeError: ...reading 'catch'` and leave the FSM stuck in\n // STARTING. Reject with an actionable message so recovery unwinds via\n // #unwindFailedStart — the same deferred-crash class as the #939\n // start-path guard (#1411).\n internalStart =\n typeof (chainResult as { then?: unknown } | null | undefined)?.then ===\n \"function\"\n ? (chainResult as Promise<State>)\n : Promise.reject(\n new TypeError(\n \"[router.start] a `start` interceptor returned without calling next(). Every start interceptor must return `next(path)`.\",\n ),\n );\n } catch (syncError: unknown) {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided start interceptor\n internalStart = Promise.reject(syncError);\n }\n\n return internalStart.catch((error: unknown) =>\n this.#unwindFailedStart(error),\n );\n }\n\n /**\n * Rejects a synchronous reentrant navigation — `navigate` /\n * `navigateToDefault` / `navigateToState` / `navigateToNotFound` called from\n * inside a transition-event listener while a transition is being dispatched\n * (RFC navigation-cancellation-unification §4). Throws synchronously: inside a\n * listener the emit's `onListenerError` isolation surfaces it (visible,\n * non-fatal); a DEFERRED (async / microtask) navigate from a listener runs\n * after dispatch settles and is allowed. Always-on core invariant guard (not\n * validator-gated).\n */\n #assertNotReentrant(): void {\n if (this.#eventBus.isProcessing()) {\n throw new RouterError(errorCodes.REENTRANT_NAVIGATION);\n }\n }\n\n /**\n * Settles the FSM after a failed start pipeline, then re-throws so the\n * rejection still surfaces to the caller. Three cases, by what the pipeline\n * reached before throwing:\n *\n * - **Pre-commit, READY** (`isReady()` and no committed state): an interceptor\n * threw after `completeStart()` reached READY but before any state committed\n * (e.g. an activation guard blocked the start navigation) — return READY →\n * IDLE via `stop()` so the router is reusable.\n * - **Pre-commit, STARTING** (`isStarting()`): the pipeline threw before\n * `completeStart()` — a sync interceptor throw before `next()`, or a throw\n * inside the namespace before commit — so unwind STARTING → IDLE via\n * `sendFail`, which also emits TRANSITION_ERROR from STARTING (#668).\n * - **Post-commit, READY with committed state** (neither branch fires): a\n * loader/interceptor threw AFTER `navigateToState` committed and emitted\n * TRANSITION_SUCCESS (the SSR/RSC loader window). Keep the committed state —\n * rolling back would retract an observed success (\"phantom success\", #763);\n * the error still surfaces via the re-throw.\n */\n #unwindFailedStart(error: unknown): never {\n if (this.#eventBus.isReady() && this.#state.get() === undefined) {\n this.#lifecycle.stop();\n this.#eventBus.sendStop();\n } else if (this.#eventBus.isStarting()) {\n this.#eventBus.sendFail(undefined, undefined, error);\n }\n\n throw error;\n }\n\n #markDisposed(): void {\n this.navigate = throwDisposed;\n this.navigateToDefault = throwDisposed;\n this.navigateToNotFound = throwDisposed;\n this.start = throwDisposed;\n this.stop = throwDisposed;\n this.usePlugin = throwDisposed;\n\n this.subscribe = throwDisposed;\n this.subscribeLeave = throwDisposed;\n this.canNavigateTo = throwDisposed;\n }\n}\n\nfunction throwDisposed(): never {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n}\n\n/**\n * Derives CreateMatcherOptions from router Options.\n * Maps core option names to matcher option names.\n */\nfunction deriveMatcherOptions<Dependencies extends DefaultDependencies>(\n options: Readonly<Options<Dependencies>>,\n): CreateMatcherOptions {\n return {\n strictTrailingSlash: options.trailingSlash === \"strict\",\n caseSensitive: options.caseSensitive,\n strictQueryParams: options.queryParamsMode === \"strict\",\n urlParamsEncoding: options.urlParamsEncoding,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n queryParams: options.queryParams!,\n };\n}\n"],"mappings":"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"}
package/dist/cjs/api.js CHANGED
@@ -1,2 +1 @@
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;
2
- //# sourceMappingURL=api.js.map
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./api/getPluginApi.js"),t=require("./api/getRoutesApi.js"),n=require("./api/getDependenciesApi.js"),r=require("./api/getLifecycleApi.js"),i=require("./api/cloneRouter.js");exports.cloneRouter=i.cloneRouter,exports.getDependenciesApi=n.getDependenciesApi,exports.getLifecycleApi=r.getLifecycleApi,exports.getPluginApi=e.getPluginApi,exports.getRoutesApi=t.getRoutesApi;
@@ -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.freezeStateInPlace=o,exports.mergeDefined=t,exports.mergeWithDefault=s,exports.normalizeParams=c;
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
@@ -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 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./Router-CCmcEi-S.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
- //# sourceMappingURL=index.js.map
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./constants.js"),t=require("./namespaces/RoutesNamespace/forwardChain.js"),n=require("./RouterError.js"),r=require("./Router.js"),i=require("./createRouter.js"),a=require("./getNavigator.js");exports.Router=r.Router,exports.RouterError=n.RouterError,exports.UNKNOWN_ROUTE=e.UNKNOWN_ROUTE,exports.constants=e.constants,exports.createRouter=i.createRouter,exports.errorCodes=e.errorCodes,exports.events=e.events,exports.getNavigator=a.getNavigator,exports.resolveForwardChain=t.resolveForwardChain;
@@ -0,0 +1,2 @@
1
+ const e=require("../../constants.js"),t=require("../../RouterError.js");var n=class{#e=null;#t=0;begin(){return++this.#t}isCurrent(e){return this.#t===e}adopt(e){this.#e=e}release(e,t,n){t&&e.abort(n),this.#e===e&&(this.#e=null)}abort(n){this.#e?.abort(n??new t.RouterError(e.errorCodes.TRANSITION_CANCELLED)),this.#e=null}};exports.InFlightNavigation=n;
2
+ //# sourceMappingURL=InFlightNavigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InFlightNavigation.js","names":["#id","#controller","RouterError","errorCodes"],"sources":["../../../../src/namespaces/NavigationNamespace/InFlightNavigation.ts"],"sourcesContent":["import { errorCodes } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\n\n/**\n * The lifecycle of ONE in-flight navigation: its supersession token and its\n * `AbortController`.\n *\n * These two fields were the namespace's only sub-domain with a small owner set —\n * four members touched the controller, three the token, against thirteen that\n * need the DI bag. Naming that sub-domain is what lets the orchestration around\n * it become functions over `(deps, plan)`: the behaviour has no other mutable\n * state to drag along (#1607).\n *\n * **One instance per ROUTER, not per navigation.** The token is a counter and\n * the controller is a single slot, so nothing here allocates on the hot path.\n *\n * **The controller is adopted, never manufactured.** A `take()` that created one\n * would allocate on the arcs that have nothing to hand a signal to — an external\n * `opts.signal` or a pre-commit listener makes a navigation *suspendable*\n * without making it need a controller — and that is precisely the regression\n * Step 1b of #1588 refused the `#handleNoGuardsLeave` fold for, by measurement.\n * The two creation sites keep their own conditions; this object only tracks\n * WHICH controller is current. Pinned by `controller-allocation.test.ts`.\n */\nexport class InFlightNavigation {\n #controller: AbortController | null = null;\n #id = 0;\n\n /**\n * Reserve the token for a navigation that is starting, superseding whatever\n * held it before. Identity is all that matters — the value is never ordered.\n */\n begin(): number {\n // Stryker disable next-line UpdateOperator: equivalent — the token is only ever compared by identity (`isCurrent`) to detect supersession; uniqueness per navigation is all that matters, so `--` (decreasing ids) is indistinguishable from `++`.\n return ++this.#id;\n }\n\n /** Is `id` still the navigation in flight, or has a newer one taken over? */\n isCurrent(id: number): boolean {\n return this.#id === id;\n }\n\n /**\n * Track a controller the CALLER created as the current navigation's. Done\n * before any listener runs, so a reentrant `navigate()` / `stop()` /\n * `dispose()` from a synchronous listener aborts THIS navigation's signal\n * (#722).\n */\n adopt(controller: AbortController): void {\n this.#controller = controller;\n }\n\n /**\n * Release a navigation's controller. The same `controller.signal` is handed to\n * `subscribeLeave` listeners, so it must abort **only** when the navigation is\n * cancelled or errors — never on success (#722). On the success path pass\n * `cancelled = false`: the reference is dropped without aborting, so a listener\n * 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 release(\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 the supersede path + the token/isCurrent 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.#controller === controller) {\n this.#controller = null;\n }\n }\n\n /**\n * Abort and release whatever is in flight (waking a parked async pipeline via\n * its `onInternalAbort`). This is the **effect** of the FSM `CANCEL` action\n * (`handleCancel` → injected `deps.abortCurrentController`), not something\n * cancellation sources call directly — so \"FSM `CANCEL` ⟹ controller aborted\"\n * holds in one place (RFC navigation-cancellation-unification §5). `reason`\n * (e.g. an external `opts.signal`'s reason, #943) becomes the controller's\n * `signal.reason`; defaults to `TRANSITION_CANCELLED`.\n */\n abort(reason?: unknown): void {\n this.#controller?.abort(\n reason ?? new RouterError(errorCodes.TRANSITION_CANCELLED),\n );\n this.#controller = null;\n }\n}\n"],"mappings":"wEAwBA,IAAa,EAAb,KAAgC,CAC9B,GAAsC,KACtC,GAAM,EAMN,OAAgB,CAEd,MAAO,EAAE,KAAKA,EAChB,CAGA,UAAU,EAAqB,CAC7B,OAAO,KAAKA,KAAQ,CACtB,CAQA,MAAM,EAAmC,CACvC,KAAKC,GAAc,CACrB,CAgBA,QACE,EACA,EACA,EACM,CACF,GACF,EAAW,MAAM,CAAM,EAIrB,KAAKA,KAAgB,IACvB,KAAKA,GAAc,KAEvB,CAWA,MAAM,EAAwB,CAC5B,KAAKA,IAAa,MAChB,GAAU,IAAIC,EAAAA,YAAYC,EAAAA,WAAW,oBAAoB,CAC3D,EACA,KAAKF,GAAc,IACrB,CACF"}
@@ -1,2 +1,2 @@
1
- const e=require("../../channels/guard.js"),t=require("../../constants.js"),n=require("../../transitionPath.js"),r=require("../../RouterError.js"),i=require("./constants.js"),a=require("./transition/completeTransition.js"),o=require("./transition/errorHandling.js"),s=require("./transition/guardPhase.js"),c=Object.freeze([t.constants.UNKNOWN_ROUTE]),l=Object.freeze({replace:!0});function u(e,n){return n?.name===t.constants.UNKNOWN_ROUTE&&!e.replace?{...e,replace:!0}:e}function d(e,t,n){return!!e&&!t.reload&&!t.force&&e.path===n.path}var f=class{lastSyncResolved=!1;lastSyncRejected=!1;#e;#t=null;#n=0;setDependencies(e){this.#e=e}navigate(e,t,n,r){this.lastSyncResolved=!1;let a=this.#e;if(!a.canNavigate())return this.lastSyncRejected=!0,i.CACHED_NOT_STARTED_REJECTION;let o;try{o=a.buildNavigateState(e,t,n)}catch(e){return Promise.reject(e)}return o?this.#r(o,r):(a.emitTransitionError(void 0,a.getState(),i.CACHED_ROUTE_NOT_FOUND_ERROR),this.lastSyncRejected=!0,i.CACHED_ROUTE_NOT_FOUND_REJECTION)}navigateToState(n,a){this.lastSyncResolved=!1;let o=this.#e;if(!o.canNavigate())return this.lastSyncRejected=!0,i.CACHED_NOT_STARTED_REJECTION;if(n.name!==t.constants.UNKNOWN_ROUTE&&!o.hasRoute(n.name)){let e=new r.RouterError(t.errorCodes.ROUTE_NOT_FOUND,{routeName:n.name});return o.emitTransitionError(void 0,o.getState(),e),Promise.reject(e)}let s=e.findMisChanneledKey(n.params,o.getQueryParams(n.name));if(s!==void 0){let i=new r.RouterError(t.errorCodes.WRONG_CHANNEL,{routeName:n.name,message:`[router.navigateToState] ${e.misChanneledKeyMessage(n.name,s,"`state.params`")}`});return o.emitTransitionError(void 0,o.getState(),i),Promise.reject(i)}let c={name:n.name,params:n.params,search:n.search,path:n.path,context:{...n.context}};return this.#r(c,a)}navigateToDefault(e){this.lastSyncResolved=!1;let n=this.#e;if(!n.getOptions().defaultRoute)return Promise.reject(new r.RouterError(t.errorCodes.ROUTE_NOT_FOUND,{routeName:`defaultRoute not configured`}));let i,a,o;try{({route:i,params:a,search:o}=n.resolveDefault())}catch(e){return Promise.reject(e)}return i?this.navigate(i,a,o,e):Promise.reject(new r.RouterError(t.errorCodes.ROUTE_NOT_FOUND,{routeName:`defaultRoute resolved to empty`}))}navigateToNotFound(e){if(!this.#e.isActive())throw new r.RouterError(t.errorCodes.ROUTER_DISPOSED);this.#c();let i=this.#e.getState(),a=i?n.nameToIDs(i.name).toReversed():[];Object.freeze(a);let o={deactivated:a,activated:c,intersection:``};Object.freeze(o);let s={phase:`activating`,...i&&{from:i.name},reason:`success`,replace:!0,segments:o};Object.freeze(s);let u={name:t.constants.UNKNOWN_ROUTE,params:t.EMPTY_PARAMS,search:t.EMPTY_SEARCH,path:e,transition:s,context:{}};return Object.freeze(u),this.#e.setState(u),this.#e.emitTransitionSuccess(u,i,l),u}abortCurrentController(e){this.#t?.abort(e??new r.RouterError(t.errorCodes.TRANSITION_CANCELLED)),this.#t=null}#r(e,o){let c=this.#e,l,f=!1,p=null;try{if(l=c.getState(),o=u(o,l),d(l,o,e))return c.emitTransitionError(e,l,i.CACHED_SAME_STATES_ERROR),this.lastSyncRejected=!0,i.CACHED_SAME_STATES_REJECTION;this.#c(o.signal);let m=++this.#n,h=o.signal!==void 0||c.hasLeaveListeners()||c.hasPreCommitListeners();c.startTransition(e,l),f=!0;let[g,_]=c.getLifecycleFunctions(),v=e.name===t.constants.UNKNOWN_ROUTE,y=n.getTransitionPath(e,l,e=>c.getMetaForState(e)),{toDeactivate:b,toActivate:x,intersection:S}=y,C=l&&!o.forceDeactivate&&b.length>0,w=!v&&x.length>0,T=g.size>0||_.size>0,E=e;if(!T){let e=this.#o(E,l,m,o,y,g);if(e!==void 0)return e}if(T){p=new AbortController,this.#t=p;let n=()=>this.#n===m&&c.isActive(),i=p.signal,a=s.executeGuardPipeline(g,_,b,x,!!C,w,e,l,i,n,()=>{if(c.sendLeaveApprove(E,l),c.hasLeaveListeners())return c.awaitLeaveListeners(E,l,i)});if(a!==void 0)return this.#i(a,{toState:e,fromState:l,opts:o,toDeactivate:b,toActivate:x,intersection:S,canDeactivateFunctions:g},p,m);if(!n())throw new r.RouterError(t.errorCodes.TRANSITION_CANCELLED);this.#s(p,!1)}if(h&&(!c.isActive()||o.signal?.aborted===!0))throw new r.RouterError(t.errorCodes.TRANSITION_CANCELLED);let D=a.completeTransition(c,{toState:e,fromState:l,opts:o,toDeactivate:b,toActivate:x,intersection:S,canDeactivateFunctions:g});return this.lastSyncResolved=!0,Promise.resolve(D)}catch(t){return this.#a(t,p,f,e,l),Promise.reject(t)}}async#i(e,n,i,s){let c=this.#e,l=()=>this.#n===s&&!i.signal.aborted&&c.isActive(),u=n.opts.signal,d,f,p=!1,m,h=new Promise(e=>{if(i.signal.aborted){e();return}f=()=>{e()},i.signal.addEventListener(`abort`,f,{once:!0})});e.catch(()=>{});try{if(u){if(u.aborted)throw new r.RouterError(t.errorCodes.TRANSITION_CANCELLED,{reason:u.reason});d=()=>{c.cancelNavigation(u.reason)},u.addEventListener(`abort`,d,{once:!0})}if(await Promise.race([e,h]),!l())throw new r.RouterError(t.errorCodes.TRANSITION_CANCELLED);let i=a.completeTransition(c,n);return p=!0,i}catch(e){throw m=e,o.routeTransitionError(c,e,n.toState,n.fromState),e}finally{d&&u?.removeEventListener(`abort`,d),f&&i.signal.removeEventListener(`abort`,f),this.#s(i,!p,m)}}#a(e,t,n,r,i){t&&this.#s(t,!0,e),n&&r&&o.routeTransitionError(this.#e,e,r,i)}#o(e,t,n,r,i,a){let o=this.#e;if(o.sendLeaveApprove(e,t),o.hasLeaveListeners()){let s=new AbortController;this.#t=s;let c;try{c=o.awaitLeaveListeners(e,t,s.signal)}catch(e){throw this.#s(s,!0,e),e}if(c!==void 0)return this.#i(c,{toState:e,fromState:t,opts:r,toDeactivate:i.toDeactivate,toActivate:i.toActivate,intersection:i.intersection,canDeactivateFunctions:a},s,n);this.#s(s,!1);return}}#s(e,t,n){t&&e.abort(n),this.#t===e&&(this.#t=null)}#c(e){if(this.#e.isTransitioning()&&(this.#e.logger.warn(`router.navigate`,`Concurrent navigation detected on shared router instance. For SSR, use cloneRouter() to create isolated instance per request.`),this.#e.cancelNavigation()),e?.aborted)throw new r.RouterError(t.errorCodes.TRANSITION_CANCELLED,{reason:e.reason})}};exports.NavigationNamespace=f;
1
+ const e=require("../../channels/guard.js"),t=require("../../constants.js"),n=require("../../RouterError.js"),r=require("./constants.js"),i=require("./InFlightNavigation.js"),a=require("./transition/executeNavigation.js"),o=require("./transition/navigateToNotFound.js");var s=class{#e;#t;#n=new i.InFlightNavigation;setDependencies(e){this.#e=e,this.#t=t=>{r.isExpectedRejection(t)||e.logger.error(`router.navigate`,`Unexpected navigation error`,t)}}navigate(e,t,n,r){return this.#r(this.#i(e,t,n,r))}navigateToState(e,t){return this.#r(this.#a(e,t))}navigateToDefault(e){return this.#r(this.#o(e))}navigateToNotFound(e){return o.navigateToNotFound(this.#e,e)}abortCurrentController(e){this.#n.abort(e)}#r(e){return e instanceof Promise&&!r.PRE_SUPPRESSED.has(e)&&e.catch(this.#t),e}#i(e,t,n,i){let o=this.#e;if(!o.canNavigate())return r.CACHED_NOT_STARTED_REJECTION;let s;try{s=o.buildNavigateState(e,t,n)}catch(e){return Promise.reject(e)}return s?a.executeNavigation(this.#e,this.#n,s,i):(o.emitTransitionError(void 0,o.getState(),r.CACHED_ROUTE_NOT_FOUND_ERROR),r.CACHED_ROUTE_NOT_FOUND_REJECTION)}#a(i,o){let s=this.#e;if(!s.canNavigate())return r.CACHED_NOT_STARTED_REJECTION;if(i.name!==t.constants.UNKNOWN_ROUTE&&!s.hasRoute(i.name)){let e=new n.RouterError(t.errorCodes.ROUTE_NOT_FOUND,{routeName:i.name});return s.emitTransitionError(void 0,s.getState(),e),Promise.reject(e)}let c=e.findMisChanneledKey(i.params,s.getQueryParams(i.name));if(c!==void 0){let r=new n.RouterError(t.errorCodes.WRONG_CHANNEL,{routeName:i.name,message:`[router.navigateToState] ${e.misChanneledKeyMessage(i.name,c,"`state.params`")}`});return s.emitTransitionError(void 0,s.getState(),r),Promise.reject(r)}let l={name:i.name,params:i.params,search:i.search,path:i.path,context:{...i.context}};return a.executeNavigation(this.#e,this.#n,l,o)}#o(e){let r=this.#e;if(!r.getOptions().defaultRoute)return Promise.reject(new n.RouterError(t.errorCodes.ROUTE_NOT_FOUND,{routeName:`defaultRoute not configured`}));let i,a,o;try{({route:i,params:a,search:o}=r.resolveDefault())}catch(e){return Promise.reject(e)}return i?this.#i(i,a,o,e):Promise.reject(new n.RouterError(t.errorCodes.ROUTE_NOT_FOUND,{routeName:`defaultRoute resolved to empty`}))}};exports.NavigationNamespace=s;
2
2
  //# sourceMappingURL=NavigationNamespace.js.map