@real-router/core 0.80.0 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -55
- package/dist/cjs/Router-CCmcEi-S.js +2 -0
- package/dist/cjs/Router-CCmcEi-S.js.map +1 -0
- package/dist/cjs/Router.d.ts +7 -6
- package/dist/cjs/Router.d.ts.map +1 -1
- package/dist/cjs/Router.js +1 -1
- package/dist/cjs/Router.js.map +1 -1
- package/dist/cjs/api/getPluginApi.d.ts.map +1 -1
- package/dist/cjs/api/getPluginApi.js +1 -1
- package/dist/cjs/api/getPluginApi.js.map +1 -1
- package/dist/cjs/api/getRoutesApi.d.ts.map +1 -1
- package/dist/cjs/api/getRoutesApi.js +1 -1
- package/dist/cjs/api/getRoutesApi.js.map +1 -1
- package/dist/cjs/api.js +2 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/buildParamMeta-0m11OIJS.js +2 -0
- package/dist/cjs/buildParamMeta-0m11OIJS.js.map +1 -0
- package/dist/cjs/channels/defaults.js +2 -0
- package/dist/cjs/channels/defaults.js.map +1 -0
- package/dist/cjs/channels/guard.js +2 -0
- package/dist/cjs/channels/guard.js.map +1 -0
- package/dist/cjs/channels/modeGate.js +2 -0
- package/dist/cjs/channels/modeGate.js.map +1 -0
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +1 -1
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/createRouter.d.ts +1 -1
- package/dist/cjs/createRouter.d.ts.map +1 -1
- package/dist/cjs/createRouter.js.map +1 -1
- package/dist/cjs/engine/builder/buildTree.js +1 -1
- package/dist/cjs/engine/builder/buildTree.js.map +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts +14 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts.map +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/buildParts.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/context.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/context.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/errors.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/errors.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/index.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/index.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +1 -1
- package/dist/cjs/engine/path-matcher/types.d.ts +3 -0
- package/dist/cjs/engine/path-matcher/types.d.ts.map +1 -1
- package/dist/cjs/engine/search-params/strategies/number.js +1 -1
- package/dist/cjs/engine/search-params/strategies/number.js.map +1 -1
- package/dist/cjs/engine/validation/routes.js +1 -1
- package/dist/cjs/engine/validation/routes.js.map +1 -1
- package/dist/cjs/helpers.js +1 -1
- package/dist/cjs/helpers.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internals.d.ts +30 -8
- package/dist/cjs/internals.d.ts.map +1 -1
- package/dist/cjs/internals.js +1 -1
- package/dist/cjs/internals.js.map +1 -1
- package/dist/cjs/limits.js +2 -0
- package/dist/cjs/limits.js.map +1 -0
- package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +1 -1
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +1 -1
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/constants.js +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +8 -0
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/types.d.ts +24 -7
- package/dist/cjs/namespaces/RoutesNamespace/types.d.ts.map +1 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +1 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +1 -1
- package/dist/cjs/pipeline/buildURL.js +2 -0
- package/dist/cjs/pipeline/buildURL.js.map +1 -0
- package/dist/cjs/pipeline/canonicalize.js +2 -0
- package/dist/cjs/pipeline/canonicalize.js.map +1 -0
- package/dist/cjs/pipeline/materialize.js +2 -0
- package/dist/cjs/pipeline/materialize.js.map +1 -0
- package/dist/cjs/pipeline/port.d.ts +147 -0
- package/dist/cjs/pipeline/port.d.ts.map +1 -0
- package/dist/cjs/routerFSM.js +1 -1
- package/dist/cjs/routerFSM.js.map +1 -1
- package/dist/cjs/transitionPath.js +1 -1
- package/dist/cjs/transitionPath.js.map +1 -1
- package/dist/cjs/types/RouterValidator.d.ts +14 -0
- package/dist/cjs/types/RouterValidator.d.ts.map +1 -1
- package/dist/cjs/types/api.d.ts +20 -11
- package/dist/cjs/types/api.d.ts.map +1 -1
- package/dist/cjs/types/base.d.ts +63 -4
- package/dist/cjs/types/base.d.ts.map +1 -1
- package/dist/cjs/types/constants.d.ts +1 -0
- package/dist/cjs/types/constants.d.ts.map +1 -1
- package/dist/cjs/types/router.d.ts +112 -23
- package/dist/cjs/types/router.d.ts.map +1 -1
- package/dist/cjs/types/tree-changed.d.ts +1 -1
- package/dist/cjs/types/tree-changed.d.ts.map +1 -1
- package/dist/cjs/types.d.ts +20 -11
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/utils/event-emitter/EventEmitter.js.map +1 -0
- package/dist/cjs/utils/fsm/fsm.js.map +1 -0
- package/dist/cjs/utils/logger/RouterLogger.js.map +1 -0
- package/dist/cjs/utils/logger/constants.js.map +1 -0
- package/dist/cjs/validation.js +2 -1
- package/dist/cjs/validation.js.map +1 -1
- package/dist/cjs/wiring/wireNamespaces.js +1 -1
- package/dist/cjs/wiring/wireNamespaces.js.map +1 -1
- package/dist/esm/Router-DEpNuwBW.mjs +2 -0
- package/dist/esm/Router-DEpNuwBW.mjs.map +1 -0
- package/dist/esm/Router.d.mts +7 -6
- package/dist/esm/Router.d.mts.map +1 -1
- package/dist/esm/api/getPluginApi.d.mts.map +1 -1
- package/dist/esm/api/getRoutesApi.d.mts.map +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/buildParamMeta-D8td47fL.mjs +2 -0
- package/dist/esm/buildParamMeta-D8td47fL.mjs.map +1 -0
- package/dist/esm/constants.d.mts.map +1 -1
- package/dist/esm/createRouter.d.mts +1 -1
- package/dist/esm/createRouter.d.mts.map +1 -1
- package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts +14 -1
- package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts.map +1 -1
- package/dist/esm/engine/path-matcher/types.d.mts +3 -0
- package/dist/esm/engine/path-matcher/types.d.mts.map +1 -1
- package/dist/esm/index.d.mts +3 -3
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/internals.d.mts +30 -8
- package/dist/esm/internals.d.mts.map +1 -1
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +8 -0
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts.map +1 -1
- package/dist/esm/namespaces/RoutesNamespace/types.d.mts +24 -7
- package/dist/esm/namespaces/RoutesNamespace/types.d.mts.map +1 -1
- package/dist/esm/pipeline/port.d.mts +147 -0
- package/dist/esm/pipeline/port.d.mts.map +1 -0
- package/dist/esm/types/RouterValidator.d.mts +14 -0
- package/dist/esm/types/RouterValidator.d.mts.map +1 -1
- package/dist/esm/types/api.d.mts +20 -11
- package/dist/esm/types/api.d.mts.map +1 -1
- package/dist/esm/types/base.d.mts +63 -4
- package/dist/esm/types/base.d.mts.map +1 -1
- package/dist/esm/types/constants.d.mts +1 -0
- package/dist/esm/types/constants.d.mts.map +1 -1
- package/dist/esm/types/router.d.mts +112 -23
- package/dist/esm/types/router.d.mts.map +1 -1
- package/dist/esm/types/tree-changed.d.mts +1 -1
- package/dist/esm/types/tree-changed.d.mts.map +1 -1
- package/dist/esm/types.d.mts +20 -11
- package/dist/esm/types.d.mts.map +1 -1
- package/dist/esm/validation.mjs +1 -1
- package/dist/esm/validation.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/cjs/Router-DGUed2F4.js +0 -2
- package/dist/cjs/Router-DGUed2F4.js.map +0 -1
- package/dist/cjs/buildParamMeta-DniluAIV.js +0 -2
- package/dist/cjs/buildParamMeta-DniluAIV.js.map +0 -1
- package/dist/cjs/foundation/event-emitter/EventEmitter.js.map +0 -1
- package/dist/cjs/foundation/fsm/fsm.js.map +0 -1
- package/dist/cjs/foundation/logger/RouterLogger.js.map +0 -1
- package/dist/cjs/foundation/logger/constants.js.map +0 -1
- package/dist/cjs/namespaces/StateNamespace/helpers.js +0 -2
- package/dist/cjs/namespaces/StateNamespace/helpers.js.map +0 -1
- package/dist/cjs/stateMetaStore.js +0 -2
- package/dist/cjs/stateMetaStore.js.map +0 -1
- package/dist/esm/Router-Bk6PfSE8.mjs +0 -2
- package/dist/esm/Router-Bk6PfSE8.mjs.map +0 -1
- package/dist/esm/buildParamMeta-bOLhLF9h.mjs +0 -2
- package/dist/esm/buildParamMeta-bOLhLF9h.mjs.map +0 -1
- /package/dist/cjs/{foundation → utils}/event-emitter/EventEmitter.js +0 -0
- /package/dist/cjs/{foundation → utils}/fsm/fsm.js +0 -0
- /package/dist/cjs/{foundation → utils}/logger/RouterLogger.js +0 -0
- /package/dist/cjs/{foundation → utils}/logger/constants.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventBusNamespace.js","names":["RouterError","errorCodes","#fsm","#emitter","#abortController","#leaveListeners","#currentToState","#setupFSMActions","events","#dispatchDepth","routerEvents","#pendingToState","#pendingFromState","#pendingError","#pendingCancelReason","routerStates","#checkListenerThreshold","#getValidator","#emitPendingError"],"sources":["../../../../src/namespaces/EventBusNamespace/EventBusNamespace.ts"],"sourcesContent":["// packages/core/src/namespaces/EventBusNamespace/EventBusNamespace.ts\n\nimport { errorCodes, events } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\nimport { routerEvents, routerStates } from \"../../routerFSM\";\n\nimport type { EventBusOptions } from \"./types\";\nimport type { EventEmitter } from \"../../foundation/event-emitter\";\nimport type { FSM } from \"../../foundation/fsm\";\nimport type { RouterEvent, RouterPayloads, RouterState } from \"../../routerFSM\";\nimport type {\n EventName,\n LeaveFn,\n LeaveState,\n NavigationOptions,\n Plugin,\n State,\n SubscribeFn,\n TreeChangedEvent,\n Unsubscribe,\n EventMethodMap,\n} from \"../../types\";\nimport type { RouterEventMap } from \"../../types/internal\";\nimport type { RouterValidator } from \"../../types/RouterValidator\";\n\n/**\n * Internal-only event key for route-tree mutations. Lives on the same\n * `EventEmitter` as the 7 transition events but never enters the public\n * `EventName` union — reachable only through\n * `getRoutesApi(router).subscribeChanges()`.\n */\nconst TREE_CHANGED = \"TREE_CHANGED\";\n\nfunction ensureError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\nfunction settleLeavePromises(\n promises: Promise<void>[],\n firstSyncError: unknown,\n signal: AbortSignal,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n // #1197 — canonicalize the abort outcome: reject with a\n // `RouterError(TRANSITION_CANCELLED)` carrying the external reason, so the\n // no-guards leave path classifies identically to the guard path (no raw\n // reject, no spurious TRANSITION_ERROR). Internal sources\n // (supersede/stop/dispose) already abort with such a RouterError — thread it\n // through unchanged so the #943 reason is preserved.\n const onAbort = (): void => {\n const reason: unknown = signal.reason;\n\n reject(\n reason instanceof RouterError &&\n reason.code === errorCodes.TRANSITION_CANCELLED\n ? reason\n : new RouterError(errorCodes.TRANSITION_CANCELLED, { reason }),\n );\n };\n\n if (signal.aborted) {\n onAbort();\n\n return;\n }\n\n // Stryker disable next-line ObjectLiteral,BooleanLiteral: equivalent — `{ once: true }` is redundant: onAbort fires at most once (a signal aborts once) and the success path explicitly removeEventListener's it, so dropping `once` is unobservable. StringLiteral sibling stays live (the \"abort\" event name is killed).\n signal.addEventListener(\"abort\", onAbort, { once: true });\n\n void Promise.allSettled(promises).then((results) => {\n // Stryker disable next-line StringLiteral: equivalent — this cleanup name is redundant: onAbort is registered with `{ once: true }` and the per-navigation signal is discarded unaborted on success, so failing to remove the listener leaks nothing observable.\n signal.removeEventListener(\"abort\", onAbort);\n\n // Stryker disable next-line BlockStatement: equivalent — emptying the post-allSettled abort-race early-return falls through to resolve()/reject(), but the abort handler already settled the promise, so the extra settle is a no-op. CE sibling stays live (→true hangs the pipeline = killed via timeout).\n if (signal.aborted) {\n // Race lost to abort — the abort handler already rejected; do nothing\n return;\n }\n\n if (firstSyncError !== undefined) {\n reject(ensureError(firstSyncError));\n\n return;\n }\n\n const rejected = results.find(\n (result): result is PromiseRejectedResult =>\n result.status === \"rejected\",\n );\n\n if (rejected !== undefined) {\n reject(ensureError(rejected.reason));\n\n return;\n }\n\n resolve();\n });\n });\n}\n\nexport class EventBusNamespace {\n readonly #fsm: FSM<RouterState, RouterEvent, null, RouterPayloads>;\n readonly #emitter: EventEmitter<RouterEventMap>;\n // Effect of the FSM CANCEL action: aborts the in-flight navigation's\n // controller. Wired to NavigationNamespace.\n readonly #abortController: (reason?: unknown) => void;\n // Lazy accessor for the opt-in RouterValidator (wired by wireNamespaces).\n // Returns `null` until validation-plugin is registered — so the proactive\n // listener-count threshold (#1188) costs the no-plugin path nothing.\n #getValidator: (() => RouterValidator | null) | undefined;\n readonly #leaveListeners: LeaveFn[] = [];\n\n // Depth of the synchronous transition-dispatch window — elevated while a\n // transition event is being emitted (`emitTransition*`) or a `subscribeLeave`\n // listener batch runs. `isProcessing()` reads it so the navigation facade can\n // reject a synchronous reentrant navigate() from inside a transition listener\n // with REENTRANT_NAVIGATION (RFC navigation-cancellation-unification §4). A\n // counter (not a boolean) tolerates legitimately-nested transition emits. The\n // ceiling-bounded \"allow reentrant navigate, throw RecursionDepthError at\n // `maxEventDepth`\" behaviour (#935/#945) is gone: such a navigate now throws\n // REENTRANT_NAVIGATION at depth 1, before it can recurse. (The emitter's old\n // `maxEventDepth` depth-bound is gone too — re-entrant emits are coalesced to a\n // no-op at the emitter, #1033 — so no event can re-enter its own dispatch.)\n #dispatchDepth = 0;\n\n #currentToState: State | undefined;\n #pendingToState: State | undefined;\n #pendingFromState: State | undefined;\n #pendingError: unknown;\n // Abort reason for the pending CANCEL — read by handleCancel, set by sendCancel.\n #pendingCancelReason: unknown;\n\n constructor(options: EventBusOptions) {\n this.#fsm = options.routerFSM;\n this.#emitter = options.emitter;\n this.#abortController = options.abortController;\n this.#currentToState = undefined;\n this.#setupFSMActions();\n }\n\n static validateSubscribeListener(listener: unknown): void {\n if (typeof listener !== \"function\") {\n throw new TypeError(\n \"[router.subscribe] Expected a function. \" +\n \"For Observable pattern use observable(router) from @real-router/rx\",\n );\n }\n }\n\n /**\n * Validates the `subscribeLeave` listener. Unlike\n * {@link validateSubscribeListener}, the error carries **no**\n * `@real-router/rx` hint — rx exposes the Observable pattern for *success*\n * transitions (`observable(router)`, `state$`, `events$`), not for leave\n * events, so steering leave-listener misuse toward rx would mislead. The\n * asymmetry is intentional (mirrored in `core/CLAUDE.md`).\n */\n static validateSubscribeLeaveListener(listener: unknown): void {\n if (typeof listener !== \"function\") {\n throw new TypeError(\"[router.subscribeLeave] Expected a function\");\n }\n }\n\n emitRouterStart(): void {\n this.#emitter.emit(events.ROUTER_START);\n }\n\n emitRouterStop(): void {\n this.#emitter.emit(events.ROUTER_STOP);\n }\n\n emitTransitionStart(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_START, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionSuccess(\n toState: State,\n fromState?: State,\n opts?: NavigationOptions,\n ): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_SUCCESS, toState, fromState, opts);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionError(\n toState?: State,\n fromState?: State,\n error?: RouterError,\n ): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_ERROR, toState, fromState, error);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionCancel(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_CANCEL, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionLeaveApprove(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_LEAVE_APPROVE, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n /**\n * True while a transition event is being dispatched synchronously — an\n * `emitTransition*` call or a `subscribeLeave` listener batch is on the stack.\n * The navigation facade reads this to reject a synchronous reentrant\n * navigate() from inside a transition listener (RFC §4).\n */\n isProcessing(): boolean {\n return this.#dispatchDepth > 0;\n }\n\n /**\n * Emits the internal `TREE_CHANGED` event after a structural route-tree\n * mutation. Reuses the shared `EventEmitter` — so re-entrancy coalescing\n * (#1033) and per-listener error isolation (`onListenerError`) apply\n * automatically.\n */\n emitTreeChanged(event: TreeChangedEvent): void {\n this.#emitter.emit(TREE_CHANGED, event);\n }\n\n /**\n * True while a `TREE_CHANGED` event is being dispatched synchronously.\n * Delegates to the emitter's own in-flight tracking (#1034) — `getRoutesApi`\n * reads this to reject reentrant route-CRUD from a `subscribeChanges` handler\n * (#1032).\n */\n isEmittingTreeChanged(): boolean {\n return this.#emitter.isDispatching(TREE_CHANGED);\n }\n\n /**\n * Subscribes to `TREE_CHANGED`. **Lenient** duplicate semantics (mirrors\n * {@link subscribe}): each call wraps the handler in a fresh closure, so N\n * registrations of the same reference produce N independent subscriptions.\n */\n subscribeTreeChanged(\n handler: (event: TreeChangedEvent) => void,\n ): Unsubscribe {\n // Same disposed-state enforcement as subscribe()/subscribeLeave() (#946),\n // completing the guard across all three subscription primitives — extended\n // here to the internal route-tree channel (#982). A `subscribeChanges`\n // reference bound before dispose() (`const s = routes.subscribeChanges\n // .bind(routes)`) reaches this method via the getRoutesApi delegate, which\n // — unlike its add/remove/update siblings — does not itself check\n // isDisposed(). Without this guard, `emitter.on` would re-register a\n // TREE_CHANGED listener that can never fire (clearAll already ran, the FSM\n // is DISPOSED, the route tree is torn down, no future emit) — a silent\n // no-op, the internal-channel counterpart of the #946 hazard.\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n return this.#emitter.on(TREE_CHANGED, (event: TreeChangedEvent) => {\n handler(event);\n });\n }\n\n /** Number of active `TREE_CHANGED` listeners (drives conditional emit). */\n treeChangedListenerCount(): number {\n return this.#emitter.listenerCount(TREE_CHANGED);\n }\n\n sendStart(): void {\n this.#fsm.send(routerEvents.START);\n }\n\n sendStop(): void {\n this.#fsm.send(routerEvents.STOP);\n }\n\n sendDispose(): void {\n this.#fsm.send(routerEvents.DISPOSE);\n }\n\n sendStarted(): void {\n this.#fsm.send(routerEvents.STARTED);\n }\n\n sendNavigate(toState: State, fromState?: State): void {\n this.#currentToState = toState;\n // Table-driven: the FSM action emits TRANSITION_START (#1169 D-full). A\n // NAVIGATE that the table rejects is a no-op — the FSM never leaves an\n // invalid state and no event fires.\n this.#fsm.send(routerEvents.NAVIGATE, { toState, fromState });\n }\n\n sendComplete(\n state: State,\n fromState?: State,\n opts: NavigationOptions = {},\n ): void {\n // Table-driven: the FSM action emits TRANSITION_SUCCESS (#1169 D-full).\n // COMPLETE from IDLE/DISPOSED (a listener stopped/disposed mid-transition)\n // is a table no-op — no resurrection, no phantom success emit.\n this.#fsm.send(routerEvents.COMPLETE, { toState: state, fromState, opts });\n\n // Nav committed — clear so a later stop()/dispose() cannot cancel a finished\n // navigation. Unconditional now that synchronous reentrant navigate is banned\n // (RFC §4): nothing can replace #currentToState during the emit above, so the\n // #308 reentrant-preserve guard is no longer needed.\n this.#currentToState = undefined;\n }\n\n sendLeaveApprove(toState: State, fromState?: State): void {\n // Table-driven: the FSM action emits TRANSITION_LEAVE_APPROVE (#1169 D-full).\n // LEAVE_APPROVE from IDLE/DISPOSED is a table no-op — no resurrection.\n this.#fsm.send(routerEvents.LEAVE_APPROVE, { toState, fromState });\n }\n\n sendFail(toState?: State, fromState?: State, error?: unknown): void {\n this.#pendingToState = toState;\n this.#pendingFromState = fromState;\n this.#pendingError = error;\n this.#fsm.send(routerEvents.FAIL);\n\n // Nav failed — clear (unconditional; synchronous reentrant navigate is\n // banned (RFC §4), so nothing replaces #currentToState during the emit).\n this.#currentToState = undefined;\n }\n\n /**\n * Surfaces a `TRANSITION_ERROR` for callers that do **not** know — or do not\n * control — the current FSM state: the plugin-facing `emitTransitionError`\n * primitive (`getPluginApi`), the dispose chain, and validator / same-state\n * rejections. It is the state-agnostic counterpart to {@link sendFail}.\n *\n * **What \"Safe\" means here.** The error event is never *dropped*, whatever the\n * FSM state — it does **not** mean the method catches every error. Errors\n * thrown *inside* a `TRANSITION_ERROR` listener are isolated by the\n * `EventEmitter`'s per-listener `onListenerError` sink, not by this method.\n *\n * **Why it branches on its own FSM state.** When the FSM is settled in `READY`\n * (no transition in flight) it routes through the FSM `FAIL` action via\n * {@link sendFail}, so the error rides the normal FSM-driven emit. Otherwise —\n * the router may be starting, mid-transition, or torn down — it emits\n * `TRANSITION_ERROR` directly: a fire-and-forget error report from an unknown\n * state must not drive a second FSM transition that could collide with an\n * in-flight one. Both branches guarantee the event reaches subscribers.\n */\n sendFailSafe(toState?: State, fromState?: State, error?: unknown): void {\n if (this.isReady()) {\n this.sendFail(toState, fromState, error);\n } else {\n this.emitTransitionError(toState, fromState, error as RouterError);\n }\n }\n\n sendCancel(toState: State, fromState?: State, reason?: unknown): void {\n this.#pendingToState = toState;\n this.#pendingFromState = fromState;\n this.#pendingCancelReason = reason;\n this.#fsm.send(routerEvents.CANCEL);\n\n // Nav cancelled — clear (unconditional; synchronous reentrant navigate is\n // banned (RFC §4), so nothing replaces #currentToState during the emit).\n this.#currentToState = undefined;\n }\n\n canBeginTransition(): boolean {\n return this.#fsm.canSend(routerEvents.NAVIGATE);\n }\n\n canStart(): boolean {\n return this.#fsm.canSend(routerEvents.START);\n }\n\n canCancel(): boolean {\n return this.#fsm.canSend(routerEvents.CANCEL);\n }\n\n isActive(): boolean {\n const fsmState = this.#fsm.getState();\n\n return fsmState !== routerStates.IDLE && fsmState !== routerStates.DISPOSED;\n }\n\n isDisposed(): boolean {\n return this.#fsm.getState() === routerStates.DISPOSED;\n }\n\n isTransitioning(): boolean {\n const state = this.#fsm.getState();\n\n return (\n state === routerStates.TRANSITION_STARTED ||\n state === routerStates.LEAVE_APPROVED\n );\n }\n\n isLeaveApproved(): boolean {\n return this.#fsm.getState() === routerStates.LEAVE_APPROVED;\n }\n\n isReady(): boolean {\n return this.#fsm.getState() === routerStates.READY;\n }\n\n isStarting(): boolean {\n return this.#fsm.getState() === routerStates.STARTING;\n }\n\n isIdle(): boolean {\n return this.#fsm.getState() === routerStates.IDLE;\n }\n\n /**\n * Plugin-author API for subscribing to internal router events.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — strict (throws).** Passing the same\n * callback reference twice for the same event throws\n * `Error(\"Duplicate listener for ...\")` from the underlying `EventEmitter`.\n * This is loud-on-misuse by design: plugin code is expected to register\n * each callback once. The contract differs from {@link subscribe} /\n * {@link subscribeLeave}, which are end-user surfaces and silently accept\n * duplicates.\n */\n addEventListener<E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n ): Unsubscribe {\n this.#checkListenerThreshold(eventName, \"addEventListener\");\n\n return this.#emitter.on(\n eventName,\n cb as (...args: RouterEventMap[typeof eventName]) => void,\n );\n }\n\n /**\n * End-user / UI-binding API for subscribing to successful transitions.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — independent.** Each call wraps\n * `listener` in a fresh closure and registers it as a distinct internal\n * slot. `router.subscribe(fn)` twice produces **two** active subscriptions;\n * `fn` fires twice per `TRANSITION_SUCCESS`. The returned `Unsubscribe` is\n * paired with its specific call — invoking it removes exactly that\n * registration.\n *\n * This contract differs from {@link addEventListener} (plugin API, throws\n * on duplicate). End-user code that wants idempotent registration must\n * gate itself, e.g. `if (!unsub) unsub = router.subscribe(fn);`.\n */\n subscribe(listener: SubscribeFn): Unsubscribe {\n // Enforce the disposed state HERE, not only on the facade. A reference\n // bound before dispose() (`const s = router.subscribe.bind(router)`)\n // bypasses the facade's #markDisposed swap and reaches this method\n // directly. Without this guard, `emitter.on` would silently re-register a\n // listener that can never fire (clearAll already ran, FSM is DISPOSED, no\n // future emit) — a silent no-op / stuck-UI hazard (#946).\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#checkListenerThreshold(events.TRANSITION_SUCCESS, \"subscribe\");\n\n // `subscribe` is fire-and-forget; the listener's return value is ignored at\n // the type level (`SubscribeFn` is `=> void`). A void-typed async listener\n // still returns a rejecting Promise at runtime, so return it to the emitter,\n // whose central isolation routes the rejection to the same `onListenerError`\n // sink a sync throw flows through — instead of leaking an `unhandledRejection`\n // (fatal under `--unhandled-rejections=strict`, the Node 22+ default). This\n // is #944, now folded into the emitter's central #1412 async isolation — the\n // former per-site `.catch` here is redundant (symmetric with `subscribeLeave`,\n // which isolates via `Promise.allSettled`).\n return this.#emitter.on(\n events.TRANSITION_SUCCESS,\n (toState: State, fromState?: State) => {\n // Return the listener's runtime value to the emitter so its central\n // isolation catches an async listener's rejection (#944/#1412). Read the\n // void-typed result into `unknown` first — a returned `void` expression\n // is forbidden, but returning the read `unknown` is fine.\n // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -- read the void-typed listener's runtime value so it can be returned for central async isolation (#944/#1412)\n const result: unknown = listener({\n route: toState,\n previousRoute: fromState,\n });\n\n return result;\n },\n );\n }\n\n /**\n * End-user / UI-binding API for subscribing to **approved** route departures\n * (`LEAVE_APPROVED` phase): all `canDeactivate` guards have passed, but the\n * departure is **tentative, not committed** — an activation (`canActivate`)\n * guard can still reject (or the target route be removed mid-transition),\n * leaving the user on the current route (#932). Treat the leave as tentative\n * for non-idempotent side-effects and use the payload `signal` (which aborts\n * with the failure reason, #943) to roll back when the navigation does not\n * commit. Async listeners block the activation phase.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — independent.** Each call pushes\n * `listener` onto the internal array; `router.subscribeLeave(fn)` twice\n * produces two entries and `fn` fires twice per leave. Each returned\n * `Unsubscribe` is **idempotent** (a `removed` flag, #1349) and removes\n * exactly ONE entry — the first still matching the reference (`indexOf`\n * semantic). So a repeated call of one unsubscribe is a true no-op and does\n * **not** touch the other registration; N subscribes + M *distinct*\n * unsubscribes leave N − M entries. Which physical entry survives is\n * irrelevant — the reference is the same.\n *\n * Contract differs from {@link addEventListener} (throws on duplicate).\n * For idempotent *registration* (one active subscription), gate at the call site.\n */\n subscribeLeave(listener: LeaveFn): Unsubscribe {\n // Same disposed-state enforcement as subscribe() (#946): a pre-bound\n // reference would otherwise push onto #leaveListeners after dispose() and\n // silently never fire (FSM is DISPOSED, no LEAVE_APPROVE emit).\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#leaveListeners.push(listener);\n\n // Idempotency flag (#1349), mirroring extendRouter / addInterceptor (#1198).\n // Without it, a double call would `indexOf(listener)` again and splice a\n // DUPLICATE registration of the same fn — silently deactivating another\n // subscriber whose own unsubscribe was never called. The `Unsubscribe`\n // contract names subscribeLeave as idempotent. (Unlike addInterceptor, the\n // `idx !== -1` guard stays: `dispose()` empties `#leaveListeners` via\n // `clearAll`, so an unsubscribe called after dispose reaches this with\n // idx === -1.)\n let removed = false;\n\n return () => {\n if (removed) {\n return;\n }\n\n removed = true;\n\n const idx = this.#leaveListeners.indexOf(listener);\n\n if (idx !== -1) {\n this.#leaveListeners.splice(idx, 1);\n }\n };\n }\n\n hasLeaveListeners(): boolean {\n return this.#leaveListeners.length > 0;\n }\n\n /**\n * True when a plugin listener runs in a PRE-COMMIT transition window where a\n * synchronous `stop()`/`dispose()` can supersede the in-flight navigation\n * before it commits — i.e. `onTransitionStart` (TRANSITION_START) or\n * `onTransitionLeaveApprove` (TRANSITION_LEAVE_APPROVE). (`subscribeLeave` is\n * tracked separately via `hasLeaveListeners`; `onTransitionSuccess` fires\n * post-commit and cannot cancel.) Read into the pre-`startTransition` liveness\n * snapshot so the commit-gate (#1169) fires for these windows too.\n */\n hasPreCommitListeners(): boolean {\n return (\n this.#emitter.listenerCount(events.TRANSITION_START) > 0 ||\n this.#emitter.listenerCount(events.TRANSITION_LEAVE_APPROVE) > 0\n );\n }\n\n awaitLeaveListeners(\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n ): Promise<void> | undefined {\n if (fromState === undefined) {\n return undefined;\n }\n\n // Freeze the payload wrapper so listeners cannot mutate it (`payload.route`\n // is already deep-frozen via the State immutability invariant; this closes\n // the wrapper-mutation gap surfaced by audit `probe-05-payload-frozen`).\n const leaveState: LeaveState = Object.freeze({\n route: fromState,\n nextRoute: toState,\n signal,\n });\n\n let promises: Promise<void>[] | undefined;\n let firstSyncError: unknown;\n\n // Snapshot before iteration — a listener that reentrantly calls\n // `subscribeLeave(newFn)` or its own `unsubscribe()` must not affect the\n // current emit cycle. Symmetric with the EventEmitter snapshot invariant\n // (PR #666 / #659).\n const snapshot = [...this.#leaveListeners];\n\n // Elevated across the SYNC leave-listener dispatch: `isProcessing()` reads\n // it, so a sync subscribeLeave listener that calls navigate() is rejected\n // with REENTRANT_NAVIGATION at the facade (RFC §4). The `finally` restores it\n // before any async tail (returned promise) runs, so a DEFERRED navigate from\n // an async listener (after its first `await`) sees depth 0 and is allowed.\n this.#dispatchDepth++;\n\n try {\n for (const listener of snapshot) {\n try {\n const result = listener(leaveState);\n\n if (result !== undefined && typeof result.then === \"function\") {\n promises ??= [];\n promises.push(result);\n }\n } catch (error: unknown) {\n if (firstSyncError === undefined) {\n firstSyncError = error;\n }\n }\n }\n } finally {\n this.#dispatchDepth--;\n }\n\n if (promises === undefined) {\n if (firstSyncError !== undefined) {\n throw ensureError(firstSyncError);\n }\n\n return undefined;\n }\n\n return settleLeavePromises(promises, firstSyncError, signal);\n }\n\n clearAll(): void {\n this.#emitter.clearAll();\n this.#leaveListeners.length = 0;\n }\n\n setLimits(limits: { maxListeners: number; warnListeners: number }): void {\n this.#emitter.setLimits(limits);\n }\n\n /**\n * Injects the lazy validator accessor (wireNamespaces), mirroring\n * `PluginsNamespace` / `RouteLifecycleNamespace`. The closure reads the live\n * `RouterInternals.validator`, so a validation-plugin registered AFTER wiring\n * is still observed on the next `subscribe` / `addEventListener`.\n */\n setValidatorAccessor(getValidator: () => RouterValidator | null): void {\n this.#getValidator = getValidator;\n }\n\n // Single guarded entry point for routing a cancel into the FSM `CANCEL` action\n // — used by every source: stop/dispose (RouterLifecycle) pass no reason;\n // supersede / external `opts.signal` (via the wiring `cancelNavigation` dep)\n // pass the abort reason (#943). `canCancel()` makes it a no-op outside a\n // cancellable FSM state (#1034: was a second, unguarded `cancelNavigation` path).\n sendCancelIfPossible(fromState: State | undefined, reason?: unknown): void {\n const toState = this.#currentToState;\n\n if (!this.canCancel() || toState === undefined) {\n return;\n }\n\n this.sendCancel(toState, fromState, reason);\n }\n\n /**\n * Proactive listener-count threshold (#1188) — mirrors the plugins /\n * lifecycle / dependencies counters. Opt-in: the emitter's per-event count is\n * read ONLY when the validator is installed, so the bare-core hot path pays\n * nothing. `count` is the POST-add size (`listenerCount + 1`), matching\n * `RouteLifecycleNamespace`'s `count + 1`, so warn/error fire exactly when the\n * new listener reaches the threshold. Core keeps the emitter's bare-`Error`\n * hard cap; this only surfaces an actionable signal well before it.\n */\n #checkListenerThreshold(eventName: EventName, methodName: string): void {\n const validator = this.#getValidator?.();\n\n if (validator) {\n validator.eventBus.validateCountThresholds(\n this.#emitter.listenerCount(eventName) + 1,\n eventName,\n methodName,\n );\n }\n }\n\n #emitPendingError(): void {\n this.emitTransitionError(\n this.#pendingToState,\n this.#pendingFromState,\n this.#pendingError as RouterError | undefined,\n );\n\n // Clear the pending payload once this FAIL action has consumed it. `#pending*`\n // is only meaningful in the window between the sendFail()/sendFailSafe() that\n // sets it and this emit; keeping it afterwards pins a stale State/RouterError\n // on the instance and leaves an implicit \"valid only in this window\" coupling\n // (#949). Hygiene only — every consumer overwrites the fields before\n // re-reading (handleCancel reads what its own sendCancel just set), so there\n // is no observable behaviour change.\n this.#pendingToState = undefined;\n this.#pendingFromState = undefined;\n this.#pendingError = undefined;\n }\n\n #setupFSMActions(): void {\n const fsm = this.#fsm;\n\n fsm.on(routerStates.STARTING, routerEvents.STARTED, () => {\n this.emitRouterStart();\n });\n\n fsm.on(routerStates.READY, routerEvents.STOP, () => {\n this.emitRouterStop();\n });\n\n // NAVIGATE / LEAVE_APPROVE / COMPLETE emit their transition event as the FSM\n // action (payload = the transition states), so `send()` from an invalid\n // state (e.g. COMPLETE from IDLE after a listener's stop()/dispose()) is a\n // table no-op that emits nothing — the FSM table is the sole authority over\n // state, no `forceState` resurrection (#1169 D-full). NAVIGATE fires from\n // READY plus the TRANSITION_STARTED / LEAVE_APPROVED self-loops (supersede).\n const emitNavigate = (payload: RouterPayloads[\"NAVIGATE\"]): void => {\n this.emitTransitionStart(payload.toState, payload.fromState);\n };\n\n fsm.on(routerStates.READY, routerEvents.NAVIGATE, emitNavigate);\n fsm.on(\n routerStates.TRANSITION_STARTED,\n routerEvents.NAVIGATE,\n emitNavigate,\n );\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.NAVIGATE, emitNavigate);\n\n fsm.on(\n routerStates.TRANSITION_STARTED,\n routerEvents.LEAVE_APPROVE,\n (payload) => {\n this.emitTransitionLeaveApprove(payload.toState, payload.fromState);\n },\n );\n\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.COMPLETE, (payload) => {\n this.emitTransitionSuccess(\n payload.toState,\n payload.fromState,\n payload.opts,\n );\n });\n\n const handleCancel = () => {\n const toState = this.#pendingToState;\n const reason = this.#pendingCancelReason;\n\n this.#pendingCancelReason = undefined;\n\n // (RFC navigation-cancellation-unification §5): the FSM CANCEL\n // action OWNS the abort. Aborting the in-flight controller wakes the parked\n // async pipeline (#1018) and rejects navigate() — the post-race local\n // isActive() sees signal.aborted; `reason` surfaces as the leave signal's\n // reason (#943). Order: (i) abort (wake) then (ii) emit TRANSITION_CANCEL.\n // No cycle: onInternalAbort is wake-only, it does not re-enter cancel.\n this.#abortController(reason);\n\n /* v8 ignore next -- @preserve: #pendingToState guaranteed set by sendCancel before send() */\n if (toState === undefined) {\n return;\n }\n\n this.emitTransitionCancel(toState, this.#pendingFromState);\n };\n\n fsm.on(routerStates.TRANSITION_STARTED, routerEvents.CANCEL, handleCancel);\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.CANCEL, handleCancel);\n\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.STARTING, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.READY, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.TRANSITION_STARTED, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EventBusNamespace.js","names":["RouterError","errorCodes","#fsm","#emitter","#abortController","#leaveListeners","#currentToState","#setupFSMActions","events","#dispatchDepth","routerEvents","#pendingToState","#pendingFromState","#pendingError","#pendingCancelReason","routerStates","#checkListenerThreshold","#getValidator","#emitPendingError"],"sources":["../../../../src/namespaces/EventBusNamespace/EventBusNamespace.ts"],"sourcesContent":["// packages/core/src/namespaces/EventBusNamespace/EventBusNamespace.ts\n\nimport { errorCodes, events } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\nimport { routerEvents, routerStates } from \"../../routerFSM\";\n\nimport type { EventBusOptions } from \"./types\";\nimport type { RouterEvent, RouterPayloads, RouterState } from \"../../routerFSM\";\nimport type {\n EventName,\n LeaveFn,\n LeaveState,\n NavigationOptions,\n Plugin,\n State,\n SubscribeFn,\n TreeChangedEvent,\n Unsubscribe,\n EventMethodMap,\n} from \"../../types\";\nimport type { RouterEventMap } from \"../../types/internal\";\nimport type { RouterValidator } from \"../../types/RouterValidator\";\nimport type { EventEmitter } from \"../../utils/event-emitter\";\nimport type { FSM } from \"../../utils/fsm\";\n\n/**\n * Internal-only event key for route-tree mutations. Lives on the same\n * `EventEmitter` as the 7 transition events but never enters the public\n * `EventName` union — reachable only through\n * `getRoutesApi(router).subscribeChanges()`.\n */\nconst TREE_CHANGED = \"TREE_CHANGED\";\n\nfunction ensureError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\nfunction settleLeavePromises(\n promises: Promise<void>[],\n firstSyncError: unknown,\n signal: AbortSignal,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n // #1197 — canonicalize the abort outcome: reject with a\n // `RouterError(TRANSITION_CANCELLED)` carrying the external reason, so the\n // no-guards leave path classifies identically to the guard path (no raw\n // reject, no spurious TRANSITION_ERROR). Internal sources\n // (supersede/stop/dispose) already abort with such a RouterError — thread it\n // through unchanged so the #943 reason is preserved.\n const onAbort = (): void => {\n const reason: unknown = signal.reason;\n\n reject(\n reason instanceof RouterError &&\n reason.code === errorCodes.TRANSITION_CANCELLED\n ? reason\n : new RouterError(errorCodes.TRANSITION_CANCELLED, { reason }),\n );\n };\n\n if (signal.aborted) {\n onAbort();\n\n return;\n }\n\n // Stryker disable next-line ObjectLiteral,BooleanLiteral: equivalent — `{ once: true }` is redundant: onAbort fires at most once (a signal aborts once) and the success path explicitly removeEventListener's it, so dropping `once` is unobservable. StringLiteral sibling stays live (the \"abort\" event name is killed).\n signal.addEventListener(\"abort\", onAbort, { once: true });\n\n void Promise.allSettled(promises).then((results) => {\n // Stryker disable next-line StringLiteral: equivalent — this cleanup name is redundant: onAbort is registered with `{ once: true }` and the per-navigation signal is discarded unaborted on success, so failing to remove the listener leaks nothing observable.\n signal.removeEventListener(\"abort\", onAbort);\n\n // Stryker disable next-line BlockStatement: equivalent — emptying the post-allSettled abort-race early-return falls through to resolve()/reject(), but the abort handler already settled the promise, so the extra settle is a no-op. CE sibling stays live (→true hangs the pipeline = killed via timeout).\n if (signal.aborted) {\n // Race lost to abort — the abort handler already rejected; do nothing\n return;\n }\n\n if (firstSyncError !== undefined) {\n reject(ensureError(firstSyncError));\n\n return;\n }\n\n const rejected = results.find(\n (result): result is PromiseRejectedResult =>\n result.status === \"rejected\",\n );\n\n if (rejected !== undefined) {\n reject(ensureError(rejected.reason));\n\n return;\n }\n\n resolve();\n });\n });\n}\n\nexport class EventBusNamespace {\n readonly #fsm: FSM<RouterState, RouterEvent, null, RouterPayloads>;\n readonly #emitter: EventEmitter<RouterEventMap>;\n // Effect of the FSM CANCEL action: aborts the in-flight navigation's\n // controller. Wired to NavigationNamespace.\n readonly #abortController: (reason?: unknown) => void;\n // Lazy accessor for the opt-in RouterValidator (wired by wireNamespaces).\n // Returns `null` until validation-plugin is registered — so the proactive\n // listener-count threshold (#1188) costs the no-plugin path nothing.\n #getValidator: (() => RouterValidator | null) | undefined;\n readonly #leaveListeners: LeaveFn[] = [];\n\n // Depth of the synchronous transition-dispatch window — elevated while a\n // transition event is being emitted (`emitTransition*`) or a `subscribeLeave`\n // listener batch runs. `isProcessing()` reads it so the navigation facade can\n // reject a synchronous reentrant navigate() from inside a transition listener\n // with REENTRANT_NAVIGATION (RFC navigation-cancellation-unification §4). A\n // counter (not a boolean) tolerates legitimately-nested transition emits. The\n // ceiling-bounded \"allow reentrant navigate, throw RecursionDepthError at\n // `maxEventDepth`\" behaviour (#935/#945) is gone: such a navigate now throws\n // REENTRANT_NAVIGATION at depth 1, before it can recurse. (The emitter's old\n // `maxEventDepth` depth-bound is gone too — re-entrant emits are coalesced to a\n // no-op at the emitter, #1033 — so no event can re-enter its own dispatch.)\n #dispatchDepth = 0;\n\n #currentToState: State | undefined;\n #pendingToState: State | undefined;\n #pendingFromState: State | undefined;\n #pendingError: unknown;\n // Abort reason for the pending CANCEL — read by handleCancel, set by sendCancel.\n #pendingCancelReason: unknown;\n\n constructor(options: EventBusOptions) {\n this.#fsm = options.routerFSM;\n this.#emitter = options.emitter;\n this.#abortController = options.abortController;\n this.#currentToState = undefined;\n this.#setupFSMActions();\n }\n\n static validateSubscribeListener(listener: unknown): void {\n if (typeof listener !== \"function\") {\n throw new TypeError(\n \"[router.subscribe] Expected a function. \" +\n \"For Observable pattern use observable(router) from @real-router/rx\",\n );\n }\n }\n\n /**\n * Validates the `subscribeLeave` listener. Unlike\n * {@link validateSubscribeListener}, the error carries **no**\n * `@real-router/rx` hint — rx exposes the Observable pattern for *success*\n * transitions (`observable(router)`, `state$`, `events$`), not for leave\n * events, so steering leave-listener misuse toward rx would mislead. The\n * asymmetry is intentional (mirrored in `core/CLAUDE.md`).\n */\n static validateSubscribeLeaveListener(listener: unknown): void {\n if (typeof listener !== \"function\") {\n throw new TypeError(\"[router.subscribeLeave] Expected a function\");\n }\n }\n\n emitRouterStart(): void {\n this.#emitter.emit(events.ROUTER_START);\n }\n\n emitRouterStop(): void {\n this.#emitter.emit(events.ROUTER_STOP);\n }\n\n emitTransitionStart(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_START, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionSuccess(\n toState: State,\n fromState?: State,\n opts?: NavigationOptions,\n ): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_SUCCESS, toState, fromState, opts);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionError(\n toState?: State,\n fromState?: State,\n error?: RouterError,\n ): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_ERROR, toState, fromState, error);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionCancel(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_CANCEL, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n emitTransitionLeaveApprove(toState: State, fromState?: State): void {\n this.#dispatchDepth++;\n try {\n this.#emitter.emit(events.TRANSITION_LEAVE_APPROVE, toState, fromState);\n } finally {\n this.#dispatchDepth--;\n }\n }\n\n /**\n * True while a transition event is being dispatched synchronously — an\n * `emitTransition*` call or a `subscribeLeave` listener batch is on the stack.\n * The navigation facade reads this to reject a synchronous reentrant\n * navigate() from inside a transition listener (RFC §4).\n */\n isProcessing(): boolean {\n return this.#dispatchDepth > 0;\n }\n\n /**\n * Emits the internal `TREE_CHANGED` event after a structural route-tree\n * mutation. Reuses the shared `EventEmitter` — so re-entrancy coalescing\n * (#1033) and per-listener error isolation (`onListenerError`) apply\n * automatically.\n */\n emitTreeChanged(event: TreeChangedEvent): void {\n this.#emitter.emit(TREE_CHANGED, event);\n }\n\n /**\n * True while a `TREE_CHANGED` event is being dispatched synchronously.\n * Delegates to the emitter's own in-flight tracking (#1034) — `getRoutesApi`\n * reads this to reject reentrant route-CRUD from a `subscribeChanges` handler\n * (#1032).\n */\n isEmittingTreeChanged(): boolean {\n return this.#emitter.isDispatching(TREE_CHANGED);\n }\n\n /**\n * Subscribes to `TREE_CHANGED`. **Lenient** duplicate semantics (mirrors\n * {@link subscribe}): each call wraps the handler in a fresh closure, so N\n * registrations of the same reference produce N independent subscriptions.\n */\n subscribeTreeChanged(\n handler: (event: TreeChangedEvent) => void,\n ): Unsubscribe {\n // Same disposed-state enforcement as subscribe()/subscribeLeave() (#946),\n // completing the guard across all three subscription primitives — extended\n // here to the internal route-tree channel (#982). A `subscribeChanges`\n // reference bound before dispose() (`const s = routes.subscribeChanges\n // .bind(routes)`) reaches this method via the getRoutesApi delegate, which\n // — unlike its add/remove/update siblings — does not itself check\n // isDisposed(). Without this guard, `emitter.on` would re-register a\n // TREE_CHANGED listener that can never fire (clearAll already ran, the FSM\n // is DISPOSED, the route tree is torn down, no future emit) — a silent\n // no-op, the internal-channel counterpart of the #946 hazard.\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n return this.#emitter.on(TREE_CHANGED, (event: TreeChangedEvent) => {\n handler(event);\n });\n }\n\n /** Number of active `TREE_CHANGED` listeners (drives conditional emit). */\n treeChangedListenerCount(): number {\n return this.#emitter.listenerCount(TREE_CHANGED);\n }\n\n sendStart(): void {\n this.#fsm.send(routerEvents.START);\n }\n\n sendStop(): void {\n this.#fsm.send(routerEvents.STOP);\n }\n\n sendDispose(): void {\n this.#fsm.send(routerEvents.DISPOSE);\n }\n\n sendStarted(): void {\n this.#fsm.send(routerEvents.STARTED);\n }\n\n sendNavigate(toState: State, fromState?: State): void {\n this.#currentToState = toState;\n // Table-driven: the FSM action emits TRANSITION_START (#1169 D-full). A\n // NAVIGATE that the table rejects is a no-op — the FSM never leaves an\n // invalid state and no event fires.\n this.#fsm.send(routerEvents.NAVIGATE, { toState, fromState });\n }\n\n sendComplete(\n state: State,\n fromState?: State,\n opts: NavigationOptions = {},\n ): void {\n // Table-driven: the FSM action emits TRANSITION_SUCCESS (#1169 D-full).\n // COMPLETE from IDLE/DISPOSED (a listener stopped/disposed mid-transition)\n // is a table no-op — no resurrection, no phantom success emit.\n this.#fsm.send(routerEvents.COMPLETE, { toState: state, fromState, opts });\n\n // Nav committed — clear so a later stop()/dispose() cannot cancel a finished\n // navigation. Unconditional now that synchronous reentrant navigate is banned\n // (RFC §4): nothing can replace #currentToState during the emit above, so the\n // #308 reentrant-preserve guard is no longer needed.\n this.#currentToState = undefined;\n }\n\n sendLeaveApprove(toState: State, fromState?: State): void {\n // Table-driven: the FSM action emits TRANSITION_LEAVE_APPROVE (#1169 D-full).\n // LEAVE_APPROVE from IDLE/DISPOSED is a table no-op — no resurrection.\n this.#fsm.send(routerEvents.LEAVE_APPROVE, { toState, fromState });\n }\n\n sendFail(toState?: State, fromState?: State, error?: unknown): void {\n this.#pendingToState = toState;\n this.#pendingFromState = fromState;\n this.#pendingError = error;\n this.#fsm.send(routerEvents.FAIL);\n\n // Nav failed — clear (unconditional; synchronous reentrant navigate is\n // banned (RFC §4), so nothing replaces #currentToState during the emit).\n this.#currentToState = undefined;\n }\n\n /**\n * Surfaces a `TRANSITION_ERROR` for callers that do **not** know — or do not\n * control — the current FSM state: the plugin-facing `emitTransitionError`\n * primitive (`getPluginApi`), the dispose chain, and validator / same-state\n * rejections. It is the state-agnostic counterpart to {@link sendFail}.\n *\n * **What \"Safe\" means here.** The error event is never *dropped*, whatever the\n * FSM state — it does **not** mean the method catches every error. Errors\n * thrown *inside* a `TRANSITION_ERROR` listener are isolated by the\n * `EventEmitter`'s per-listener `onListenerError` sink, not by this method.\n *\n * **Why it branches on its own FSM state.** When the FSM is settled in `READY`\n * (no transition in flight) it routes through the FSM `FAIL` action via\n * {@link sendFail}, so the error rides the normal FSM-driven emit. Otherwise —\n * the router may be starting, mid-transition, or torn down — it emits\n * `TRANSITION_ERROR` directly: a fire-and-forget error report from an unknown\n * state must not drive a second FSM transition that could collide with an\n * in-flight one. Both branches guarantee the event reaches subscribers.\n */\n sendFailSafe(toState?: State, fromState?: State, error?: unknown): void {\n if (this.isReady()) {\n this.sendFail(toState, fromState, error);\n } else {\n this.emitTransitionError(toState, fromState, error as RouterError);\n }\n }\n\n sendCancel(toState: State, fromState?: State, reason?: unknown): void {\n this.#pendingToState = toState;\n this.#pendingFromState = fromState;\n this.#pendingCancelReason = reason;\n this.#fsm.send(routerEvents.CANCEL);\n\n // Nav cancelled — clear (unconditional; synchronous reentrant navigate is\n // banned (RFC §4), so nothing replaces #currentToState during the emit).\n this.#currentToState = undefined;\n }\n\n canBeginTransition(): boolean {\n return this.#fsm.canSend(routerEvents.NAVIGATE);\n }\n\n canStart(): boolean {\n return this.#fsm.canSend(routerEvents.START);\n }\n\n canCancel(): boolean {\n return this.#fsm.canSend(routerEvents.CANCEL);\n }\n\n isActive(): boolean {\n const fsmState = this.#fsm.getState();\n\n return fsmState !== routerStates.IDLE && fsmState !== routerStates.DISPOSED;\n }\n\n isDisposed(): boolean {\n return this.#fsm.getState() === routerStates.DISPOSED;\n }\n\n isTransitioning(): boolean {\n const state = this.#fsm.getState();\n\n return (\n state === routerStates.TRANSITION_STARTED ||\n state === routerStates.LEAVE_APPROVED\n );\n }\n\n isLeaveApproved(): boolean {\n return this.#fsm.getState() === routerStates.LEAVE_APPROVED;\n }\n\n isReady(): boolean {\n return this.#fsm.getState() === routerStates.READY;\n }\n\n isStarting(): boolean {\n return this.#fsm.getState() === routerStates.STARTING;\n }\n\n isIdle(): boolean {\n return this.#fsm.getState() === routerStates.IDLE;\n }\n\n /**\n * Plugin-author API for subscribing to internal router events.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — strict (throws).** Passing the same\n * callback reference twice for the same event throws\n * `Error(\"Duplicate listener for ...\")` from the underlying `EventEmitter`.\n * This is loud-on-misuse by design: plugin code is expected to register\n * each callback once. The contract differs from {@link subscribe} /\n * {@link subscribeLeave}, which are end-user surfaces and silently accept\n * duplicates.\n */\n addEventListener<E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n ): Unsubscribe {\n this.#checkListenerThreshold(eventName, \"addEventListener\");\n\n return this.#emitter.on(\n eventName,\n cb as (...args: RouterEventMap[typeof eventName]) => void,\n );\n }\n\n /**\n * End-user / UI-binding API for subscribing to successful transitions.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — independent.** Each call wraps\n * `listener` in a fresh closure and registers it as a distinct internal\n * slot. `router.subscribe(fn)` twice produces **two** active subscriptions;\n * `fn` fires twice per `TRANSITION_SUCCESS`. The returned `Unsubscribe` is\n * paired with its specific call — invoking it removes exactly that\n * registration.\n *\n * This contract differs from {@link addEventListener} (plugin API, throws\n * on duplicate). End-user code that wants idempotent registration must\n * gate itself, e.g. `if (!unsub) unsub = router.subscribe(fn);`.\n */\n subscribe(listener: SubscribeFn): Unsubscribe {\n // Enforce the disposed state HERE, not only on the facade. A reference\n // bound before dispose() (`const s = router.subscribe.bind(router)`)\n // bypasses the facade's #markDisposed swap and reaches this method\n // directly. Without this guard, `emitter.on` would silently re-register a\n // listener that can never fire (clearAll already ran, FSM is DISPOSED, no\n // future emit) — a silent no-op / stuck-UI hazard (#946).\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#checkListenerThreshold(events.TRANSITION_SUCCESS, \"subscribe\");\n\n // `subscribe` is fire-and-forget; the listener's return value is ignored at\n // the type level (`SubscribeFn` is `=> void`). A void-typed async listener\n // still returns a rejecting Promise at runtime, so return it to the emitter,\n // whose central isolation routes the rejection to the same `onListenerError`\n // sink a sync throw flows through — instead of leaking an `unhandledRejection`\n // (fatal under `--unhandled-rejections=strict`, the Node 22+ default). This\n // is #944, now folded into the emitter's central #1412 async isolation — the\n // former per-site `.catch` here is redundant (symmetric with `subscribeLeave`,\n // which isolates via `Promise.allSettled`).\n return this.#emitter.on(\n events.TRANSITION_SUCCESS,\n (toState: State, fromState?: State) => {\n // Return the listener's runtime value to the emitter so its central\n // isolation catches an async listener's rejection (#944/#1412). Read the\n // void-typed result into `unknown` first — a returned `void` expression\n // is forbidden, but returning the read `unknown` is fine.\n // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -- read the void-typed listener's runtime value so it can be returned for central async isolation (#944/#1412)\n const result: unknown = listener({\n route: toState,\n previousRoute: fromState,\n });\n\n return result;\n },\n );\n }\n\n /**\n * End-user / UI-binding API for subscribing to **approved** route departures\n * (`LEAVE_APPROVED` phase): all `canDeactivate` guards have passed, but the\n * departure is **tentative, not committed** — an activation (`canActivate`)\n * guard can still reject (or the target route be removed mid-transition),\n * leaving the user on the current route (#932). Treat the leave as tentative\n * for non-idempotent side-effects and use the payload `signal` (which aborts\n * with the failure reason, #943) to roll back when the navigation does not\n * commit. Async listeners block the activation phase.\n *\n * @remarks\n *\n * **Duplicate-registration semantics — independent.** Each call pushes\n * `listener` onto the internal array; `router.subscribeLeave(fn)` twice\n * produces two entries and `fn` fires twice per leave. Each returned\n * `Unsubscribe` is **idempotent** (a `removed` flag, #1349) and removes\n * exactly ONE entry — the first still matching the reference (`indexOf`\n * semantic). So a repeated call of one unsubscribe is a true no-op and does\n * **not** touch the other registration; N subscribes + M *distinct*\n * unsubscribes leave N − M entries. Which physical entry survives is\n * irrelevant — the reference is the same.\n *\n * Contract differs from {@link addEventListener} (throws on duplicate).\n * For idempotent *registration* (one active subscription), gate at the call site.\n */\n subscribeLeave(listener: LeaveFn): Unsubscribe {\n // Same disposed-state enforcement as subscribe() (#946): a pre-bound\n // reference would otherwise push onto #leaveListeners after dispose() and\n // silently never fire (FSM is DISPOSED, no LEAVE_APPROVE emit).\n if (this.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#leaveListeners.push(listener);\n\n // Idempotency flag (#1349), mirroring extendRouter / addInterceptor (#1198).\n // Without it, a double call would `indexOf(listener)` again and splice a\n // DUPLICATE registration of the same fn — silently deactivating another\n // subscriber whose own unsubscribe was never called. The `Unsubscribe`\n // contract names subscribeLeave as idempotent. (Unlike addInterceptor, the\n // `idx !== -1` guard stays: `dispose()` empties `#leaveListeners` via\n // `clearAll`, so an unsubscribe called after dispose reaches this with\n // idx === -1.)\n let removed = false;\n\n return () => {\n if (removed) {\n return;\n }\n\n removed = true;\n\n const idx = this.#leaveListeners.indexOf(listener);\n\n if (idx !== -1) {\n this.#leaveListeners.splice(idx, 1);\n }\n };\n }\n\n hasLeaveListeners(): boolean {\n return this.#leaveListeners.length > 0;\n }\n\n /**\n * True when a plugin listener runs in a PRE-COMMIT transition window where a\n * synchronous `stop()`/`dispose()` can supersede the in-flight navigation\n * before it commits — i.e. `onTransitionStart` (TRANSITION_START) or\n * `onTransitionLeaveApprove` (TRANSITION_LEAVE_APPROVE). (`subscribeLeave` is\n * tracked separately via `hasLeaveListeners`; `onTransitionSuccess` fires\n * post-commit and cannot cancel.) Read into the pre-`startTransition` liveness\n * snapshot so the commit-gate (#1169) fires for these windows too.\n */\n hasPreCommitListeners(): boolean {\n return (\n this.#emitter.listenerCount(events.TRANSITION_START) > 0 ||\n this.#emitter.listenerCount(events.TRANSITION_LEAVE_APPROVE) > 0\n );\n }\n\n awaitLeaveListeners(\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n ): Promise<void> | undefined {\n if (fromState === undefined) {\n return undefined;\n }\n\n // Freeze the payload wrapper so listeners cannot mutate it (`payload.route`\n // is already deep-frozen via the State immutability invariant; this closes\n // the wrapper-mutation gap surfaced by audit `probe-05-payload-frozen`).\n const leaveState: LeaveState = Object.freeze({\n route: fromState,\n nextRoute: toState,\n signal,\n });\n\n let promises: Promise<void>[] | undefined;\n let firstSyncError: unknown;\n\n // Snapshot before iteration — a listener that reentrantly calls\n // `subscribeLeave(newFn)` or its own `unsubscribe()` must not affect the\n // current emit cycle. Symmetric with the EventEmitter snapshot invariant\n // (PR #666 / #659).\n const snapshot = [...this.#leaveListeners];\n\n // Elevated across the SYNC leave-listener dispatch: `isProcessing()` reads\n // it, so a sync subscribeLeave listener that calls navigate() is rejected\n // with REENTRANT_NAVIGATION at the facade (RFC §4). The `finally` restores it\n // before any async tail (returned promise) runs, so a DEFERRED navigate from\n // an async listener (after its first `await`) sees depth 0 and is allowed.\n this.#dispatchDepth++;\n\n try {\n for (const listener of snapshot) {\n try {\n const result = listener(leaveState);\n\n if (result !== undefined && typeof result.then === \"function\") {\n promises ??= [];\n promises.push(result);\n }\n } catch (error: unknown) {\n if (firstSyncError === undefined) {\n firstSyncError = error;\n }\n }\n }\n } finally {\n this.#dispatchDepth--;\n }\n\n if (promises === undefined) {\n if (firstSyncError !== undefined) {\n throw ensureError(firstSyncError);\n }\n\n return undefined;\n }\n\n return settleLeavePromises(promises, firstSyncError, signal);\n }\n\n clearAll(): void {\n this.#emitter.clearAll();\n this.#leaveListeners.length = 0;\n }\n\n setLimits(limits: { maxListeners: number; warnListeners: number }): void {\n this.#emitter.setLimits(limits);\n }\n\n /**\n * Injects the lazy validator accessor (wireNamespaces), mirroring\n * `PluginsNamespace` / `RouteLifecycleNamespace`. The closure reads the live\n * `RouterInternals.validator`, so a validation-plugin registered AFTER wiring\n * is still observed on the next `subscribe` / `addEventListener`.\n */\n setValidatorAccessor(getValidator: () => RouterValidator | null): void {\n this.#getValidator = getValidator;\n }\n\n // Single guarded entry point for routing a cancel into the FSM `CANCEL` action\n // — used by every source: stop/dispose (RouterLifecycle) pass no reason;\n // supersede / external `opts.signal` (via the wiring `cancelNavigation` dep)\n // pass the abort reason (#943). `canCancel()` makes it a no-op outside a\n // cancellable FSM state (#1034: was a second, unguarded `cancelNavigation` path).\n sendCancelIfPossible(fromState: State | undefined, reason?: unknown): void {\n const toState = this.#currentToState;\n\n if (!this.canCancel() || toState === undefined) {\n return;\n }\n\n this.sendCancel(toState, fromState, reason);\n }\n\n /**\n * Proactive listener-count threshold (#1188) — mirrors the plugins /\n * lifecycle / dependencies counters. Opt-in: the emitter's per-event count is\n * read ONLY when the validator is installed, so the bare-core hot path pays\n * nothing. `count` is the POST-add size (`listenerCount + 1`), matching\n * `RouteLifecycleNamespace`'s `count + 1`, so warn/error fire exactly when the\n * new listener reaches the threshold. Core keeps the emitter's bare-`Error`\n * hard cap; this only surfaces an actionable signal well before it.\n */\n #checkListenerThreshold(eventName: EventName, methodName: string): void {\n const validator = this.#getValidator?.();\n\n if (validator) {\n validator.eventBus.validateCountThresholds(\n this.#emitter.listenerCount(eventName) + 1,\n eventName,\n methodName,\n );\n }\n }\n\n #emitPendingError(): void {\n this.emitTransitionError(\n this.#pendingToState,\n this.#pendingFromState,\n this.#pendingError as RouterError | undefined,\n );\n\n // Clear the pending payload once this FAIL action has consumed it. `#pending*`\n // is only meaningful in the window between the sendFail()/sendFailSafe() that\n // sets it and this emit; keeping it afterwards pins a stale State/RouterError\n // on the instance and leaves an implicit \"valid only in this window\" coupling\n // (#949). Hygiene only — every consumer overwrites the fields before\n // re-reading (handleCancel reads what its own sendCancel just set), so there\n // is no observable behaviour change.\n this.#pendingToState = undefined;\n this.#pendingFromState = undefined;\n this.#pendingError = undefined;\n }\n\n #setupFSMActions(): void {\n const fsm = this.#fsm;\n\n fsm.on(routerStates.STARTING, routerEvents.STARTED, () => {\n this.emitRouterStart();\n });\n\n fsm.on(routerStates.READY, routerEvents.STOP, () => {\n this.emitRouterStop();\n });\n\n // NAVIGATE / LEAVE_APPROVE / COMPLETE emit their transition event as the FSM\n // action (payload = the transition states), so `send()` from an invalid\n // state (e.g. COMPLETE from IDLE after a listener's stop()/dispose()) is a\n // table no-op that emits nothing — the FSM table is the sole authority over\n // state, no `forceState` resurrection (#1169 D-full). NAVIGATE fires from\n // READY plus the TRANSITION_STARTED / LEAVE_APPROVED self-loops (supersede).\n const emitNavigate = (payload: RouterPayloads[\"NAVIGATE\"]): void => {\n this.emitTransitionStart(payload.toState, payload.fromState);\n };\n\n fsm.on(routerStates.READY, routerEvents.NAVIGATE, emitNavigate);\n fsm.on(\n routerStates.TRANSITION_STARTED,\n routerEvents.NAVIGATE,\n emitNavigate,\n );\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.NAVIGATE, emitNavigate);\n\n fsm.on(\n routerStates.TRANSITION_STARTED,\n routerEvents.LEAVE_APPROVE,\n (payload) => {\n this.emitTransitionLeaveApprove(payload.toState, payload.fromState);\n },\n );\n\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.COMPLETE, (payload) => {\n this.emitTransitionSuccess(\n payload.toState,\n payload.fromState,\n payload.opts,\n );\n });\n\n const handleCancel = () => {\n const toState = this.#pendingToState;\n const reason = this.#pendingCancelReason;\n\n this.#pendingCancelReason = undefined;\n\n // (RFC navigation-cancellation-unification §5): the FSM CANCEL\n // action OWNS the abort. Aborting the in-flight controller wakes the parked\n // async pipeline (#1018) and rejects navigate() — the post-race local\n // isActive() sees signal.aborted; `reason` surfaces as the leave signal's\n // reason (#943). Order: (i) abort (wake) then (ii) emit TRANSITION_CANCEL.\n // No cycle: onInternalAbort is wake-only, it does not re-enter cancel.\n this.#abortController(reason);\n\n /* v8 ignore next -- @preserve: #pendingToState guaranteed set by sendCancel before send() */\n if (toState === undefined) {\n return;\n }\n\n this.emitTransitionCancel(toState, this.#pendingFromState);\n };\n\n fsm.on(routerStates.TRANSITION_STARTED, routerEvents.CANCEL, handleCancel);\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.CANCEL, handleCancel);\n\n fsm.on(routerStates.LEAVE_APPROVED, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.STARTING, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.READY, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n\n fsm.on(routerStates.TRANSITION_STARTED, routerEvents.FAIL, () => {\n this.#emitPendingError();\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../
|
|
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;
|
|
2
2
|
//# sourceMappingURL=NavigationNamespace.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationNamespace.js","names":["constants","#deps","CACHED_NOT_STARTED_REJECTION","CACHED_ROUTE_NOT_FOUND_ERROR","CACHED_ROUTE_NOT_FOUND_REJECTION","#executeNavigation","RouterError","errorCodes","getStateMetaParams","#abortPreviousNavigation","nameToIDs","EMPTY_PARAMS","#currentController","CACHED_SAME_STATES_ERROR","CACHED_SAME_STATES_REJECTION","#navigationId","getTransitionPath","#handleNoGuardsLeave","executeGuardPipeline","#finishAsyncNavigation","#cleanupController","completeTransition","#handleNavigateError"],"sources":["../../../../src/namespaces/NavigationNamespace/NavigationNamespace.ts"],"sourcesContent":["import {\n CACHED_NOT_STARTED_REJECTION,\n CACHED_ROUTE_NOT_FOUND_ERROR,\n CACHED_ROUTE_NOT_FOUND_REJECTION,\n CACHED_SAME_STATES_ERROR,\n CACHED_SAME_STATES_REJECTION,\n} from \"./constants\";\nimport { completeTransition } from \"./transition/completeTransition\";\nimport { routeTransitionError } from \"./transition/errorHandling\";\nimport { executeGuardPipeline } from \"./transition/guardPhase\";\nimport { EMPTY_PARAMS, errorCodes, constants } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\nimport { getStateMetaParams, setStateMetaParams } from \"../../stateMetaStore\";\nimport { getTransitionPath, nameToIDs } from \"../../transitionPath\";\n\nimport type { NavigationContext, NavigationDependencies } from \"./types\";\nimport type { TransitionPath } from \"../../transitionPath\";\nimport type {\n GuardFn,\n NavigationOptions,\n Params,\n State,\n TransitionMeta,\n} from \"../../types\";\n\nconst FROZEN_ACTIVATED: string[] = Object.freeze([\n constants.UNKNOWN_ROUTE,\n]) as unknown as string[];\nconst FROZEN_REPLACE_OPTS: NavigationOptions = Object.freeze({ replace: true });\n\nfunction forceReplaceFromUnknown(\n opts: NavigationOptions,\n fromState: State | undefined,\n): NavigationOptions {\n return fromState?.name === constants.UNKNOWN_ROUTE && !opts.replace\n ? { ...opts, replace: true }\n : opts;\n}\n\nfunction isSameNavigation(\n fromState: State | undefined,\n opts: NavigationOptions,\n toState: State,\n): boolean {\n return (\n !!fromState &&\n !opts.reload &&\n !opts.force &&\n fromState.path === toState.path\n );\n}\n\n/**\n * Independent namespace for managing navigation.\n *\n * Handles navigate(), navigateToDefault(), navigateToNotFound(), and transition state.\n *\n * Performance: navigate() uses optimistic sync execution — guards run synchronously\n * until one returns a Promise, then switches to async. This eliminates Promise/AbortController\n * overhead for the common case (no guards or sync guards).\n */\nexport class NavigationNamespace {\n // Stryker disable next-line BooleanLiteral: equivalent — reset to false at the top of every navigate()/navigateToState()/navigateToDefault(), so the initializer value is never observed.\n lastSyncResolved = false;\n lastSyncRejected = false;\n #deps!: NavigationDependencies;\n #currentController: AbortController | null = null;\n #navigationId = 0;\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n setDependencies(deps: NavigationDependencies): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n navigate(\n name: string,\n params: Params,\n opts: NavigationOptions,\n ): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n // Fast-path sync rejections: cached error + cached Promise.reject\n // No allocations, no throw/catch overhead, facade skips .catch() suppression\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag, not a correctness gate. Not flagging the cached (pre-suppressed) rejection routes the facade to the else-branch, which re-attaches a harmless .catch; no observable difference.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n let toState: State | undefined;\n\n try {\n toState = deps.buildNavigateState(name, params);\n } catch (error) {\n /* v8 ignore next 3 -- @preserve: reachable only via validator-driven\n throws from buildNavigateState (validateStateBuilderArgs) — covered\n in @real-router/validation-plugin's suite, not in core. */\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided buildNavigateState\n return Promise.reject(error);\n }\n\n if (!toState) {\n deps.emitTransitionError(\n undefined,\n deps.getState(),\n CACHED_ROUTE_NOT_FOUND_ERROR,\n );\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_ROUTE_NOT_FOUND_REJECTION;\n }\n\n return this.#executeNavigation(toState, opts);\n }\n\n /**\n * Navigate to a fully-built `State` directly, skipping `buildNavigateState`\n * (forwardState + buildPath + meta lookup). Used by URL plugins after they\n * have already produced a `State` from a browser-initiated event via\n * `api.matchPath(url)` — see issue #525.\n *\n * Semantics vs. `navigate(name, params, opts)`:\n * - `forwardState` is NOT re-applied. matchPath already runs it; reapplying\n * is redundant in the idempotent case and can race in the dynamic case.\n * - `buildPath` is NOT re-run. The caller's `state.path` is used as-is —\n * so `trailingSlash:\"preserve\"` matchedState paths flow through unchanged\n * (closes #525 Q2). `buildPath` interceptors do NOT run; the URL the\n * user navigated to is the source of truth for this code path.\n * - All other pipeline steps run unchanged: SAME_STATES check, FSM\n * transition, guards, `subscribeLeave`, `completeTransition`,\n * plugin lifecycle hooks.\n */\n navigateToState(state: State, opts: NavigationOptions): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n // Reject states whose route no longer exists (e.g. the route tree was\n // mutated between matchPath and navigateToState). UNKNOWN_ROUTE is\n // structurally legal — it is the navigateToNotFound output shape.\n if (state.name !== constants.UNKNOWN_ROUTE && !deps.hasRoute(state.name)) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: state.name,\n });\n\n deps.emitTransitionError(undefined, deps.getState(), err);\n\n // This is a FRESH reject (carries `routeName`), not one of the\n // pre-suppressed CACHED_*_REJECTION singletons. `lastSyncRejected`\n // contractually means \"I returned a pre-suppressed cached rejection —\n // skip your .catch()\", so leaving it unset lets the facade attach its\n // own suppression. Setting it here leaked an unhandledRejection on\n // fire-and-forget calls (#721).\n return Promise.reject(err);\n }\n\n // States from `matchPath` are deeply frozen (`freezeStateInPlace`).\n // `completeTransition` mutates `toState.transition` and `context` is\n // intentionally extensible for plugin claim writes, so we hand the\n // pipeline a writable shell — same shape `makeState(skipFreeze=true)`\n // produces. `params` stays referentially shared (already frozen).\n // `transition` is omitted so completeTransition can assign it.\n const writableState = {\n name: state.name,\n params: state.params,\n path: state.path,\n context: { ...state.context },\n } as State;\n\n // Carry the route-meta binding (#1170). `matchPath` / `makeState` attach it\n // to the frozen source `state` via a WeakMap keyed by object reference, so\n // the fresh writable shell would otherwise be meta-less. Without it, two\n // consecutive popstate navigations make both `toState` AND `fromState`\n // meta-less, dropping `getTransitionPath` to FAST PATH 3 (full chains):\n // ancestor guards re-run and browser-back can block where `navigate()`\n // succeeds.\n const meta = getStateMetaParams(state);\n\n if (meta !== undefined) {\n setStateMetaParams(writableState, meta);\n }\n\n return this.#executeNavigation(writableState, opts);\n }\n\n navigateToDefault(opts: NavigationOptions): Promise<State> {\n // Reset the sync-resolution flag on entry, mirroring navigate() and\n // navigateToState(). start() leaves `lastSyncResolved = true`, and the\n // early reject paths below return before delegating to navigate(), so a\n // stale `true` would make the facade take the \"already resolved\" branch\n // and skip .catch() suppression — leaking an unhandledRejection on\n // fire-and-forget calls (#721).\n this.lastSyncResolved = false;\n const deps = this.#deps;\n const options = deps.getOptions();\n\n if (!options.defaultRoute) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute not configured\",\n }),\n );\n }\n\n let route: string;\n let params: Params;\n\n try {\n ({ route, params } = deps.resolveDefault());\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided resolveDefault callback\n return Promise.reject(error);\n }\n\n if (!route) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute resolved to empty\",\n }),\n );\n }\n\n return this.navigate(route, params, opts);\n }\n\n navigateToNotFound(path: string): State {\n // #1186 — liveness gate. This internal commit primitive has no FSM\n // transition of its own, so without this check a `dispose()` that lands\n // while a start-interceptor is parked (FSM already DISPOSED) would let the\n // resuming pipeline commit an UNKNOWN_ROUTE state on the disposed router and\n // `start()` resolve. Symmetric with `navigateToState`'s `canNavigate()` gate\n // (the matched-route branch is already protected). `!isActive()` also covers\n // a merely-stopped (IDLE) router: the only reachable path to that is a direct\n // `router.navigateToNotFound()` on a stopped instance (internal callers run\n // during STARTING, which is active), so the ROUTER_DISPOSED code is slightly\n // broad there — fail-closed is deliberate (committing on a stopped router is\n // out of contract), and the disposed race is the case that matters.\n if (!this.#deps.isActive()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#abortPreviousNavigation();\n\n const fromState = this.#deps.getState();\n const deactivated: string[] = fromState\n ? nameToIDs(fromState.name).toReversed()\n : [];\n\n Object.freeze(deactivated);\n\n const segments: TransitionMeta[\"segments\"] = {\n deactivated,\n activated: FROZEN_ACTIVATED,\n intersection: \"\",\n };\n\n Object.freeze(segments);\n\n const transitionMeta: TransitionMeta = {\n phase: \"activating\",\n ...(fromState && { from: fromState.name }),\n reason: \"success\",\n replace: true,\n segments,\n };\n\n Object.freeze(transitionMeta);\n\n const state: State = {\n name: constants.UNKNOWN_ROUTE,\n params: EMPTY_PARAMS,\n path,\n transition: transitionMeta,\n context: {},\n };\n\n Object.freeze(state);\n\n this.#deps.setState(state);\n this.#deps.emitTransitionSuccess(state, fromState, FROZEN_REPLACE_OPTS);\n\n return state;\n }\n\n /**\n * Aborts and releases the in-flight navigation's `AbortController` (waking the\n * parked async pipeline via `onInternalAbort`). This is the\n * **effect** of the FSM `CANCEL` action (`handleCancel` → injected\n * `deps.abortCurrentController`), not something cancellation sources call\n * directly — so \"FSM `CANCEL` ⟹ controller aborted\" holds in one place (RFC\n * navigation-cancellation-unification §5). `reason` (e.g. an external\n * `opts.signal`'s reason, #943) becomes the controller's `signal.reason`;\n * defaults to `TRANSITION_CANCELLED`.\n */\n abortCurrentController(reason?: unknown): void {\n this.#currentController?.abort(\n reason ?? new RouterError(errorCodes.TRANSITION_CANCELLED),\n );\n this.#currentController = null;\n }\n\n #executeNavigation(toState: State, opts: NavigationOptions): Promise<State> {\n const deps = this.#deps;\n let fromState: State | undefined;\n let transitionStarted = false;\n let controller: AbortController | null = null;\n\n try {\n fromState = deps.getState();\n opts = forceReplaceFromUnknown(opts, fromState);\n\n if (isSameNavigation(fromState, opts, toState)) {\n deps.emitTransitionError(toState, fromState, CACHED_SAME_STATES_ERROR);\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_SAME_STATES_REJECTION;\n }\n\n this.#abortPreviousNavigation(opts.signal);\n\n // Stryker disable next-line UpdateOperator: equivalent — `#navigationId` is only ever compared by identity (`!== myId`) to detect supersession; uniqueness per navigation is all that matters, so `--` (decreasing ids) is indistinguishable from `++`.\n const myId = ++this.#navigationId;\n\n // #1169 commit-gate — liveness snapshot captured BEFORE the pre-commit\n // listener windows. A listener's `stop()`/`dispose()` runs `clearAll()`,\n // which empties the listener lists, so the marker must be read now, not at\n // the commit site (that self-destruct was the QB/QE hole, RFC §5-bis).\n // `suspendable` is true only when a synchronous supersede is reachable — an\n // external `opts.signal`, `subscribeLeave` listeners, or a pre-commit\n // plugin listener (`onTransitionStart` / `onTransitionLeaveApprove`); the\n // pure synchronous navigate (none of these) is uncancellable and skips the\n // gate, keeping the #307 hot path perf-neutral.\n const suspendable =\n opts.signal !== undefined ||\n deps.hasLeaveListeners() ||\n deps.hasPreCommitListeners();\n\n deps.startTransition(toState, fromState);\n transitionStarted = true;\n\n // Post-`startTransition` supersession is now caught at the commit-gate\n // below (before `completeTransition`'s setState): a `stop()`/`dispose()`/\n // external-abort from the TRANSITION_START listener leaves the FSM in\n // IDLE/DISPOSED, which `!deps.isActive()` detects. (Async supersession is\n // additionally caught in `#finishAsyncNavigation` / the guard pipeline's\n // `isCurrentNav`; a reentrant navigate() is banned — REENTRANT_NAVIGATION.)\n\n const [canDeactivateFunctions, canActivateFunctions] =\n deps.getLifecycleFunctions();\n const isUnknownRoute = toState.name === constants.UNKNOWN_ROUTE;\n\n const transitionPath = getTransitionPath(toState, fromState);\n const { toDeactivate, toActivate, intersection } = transitionPath;\n\n const shouldDeactivate =\n fromState && !opts.forceDeactivate && toDeactivate.length > 0;\n const shouldActivate = !isUnknownRoute && toActivate.length > 0;\n const hasGuards =\n canDeactivateFunctions.size > 0 || canActivateFunctions.size > 0;\n\n const confirmedToState = toState;\n\n if (!hasGuards) {\n const asyncLeave = this.#handleNoGuardsLeave(\n confirmedToState,\n fromState,\n myId,\n opts,\n transitionPath,\n canDeactivateFunctions,\n );\n\n if (asyncLeave !== undefined) {\n return asyncLeave;\n }\n }\n\n // Stryker disable next-line ConditionalExpression: equivalent — running the guard pipeline on the no-guards path does not double-emit LEAVE_APPROVE (full suite green with `if (true)`); the BlockStatement mutant stays live (killed by guarded-route tests).\n // eslint-disable-next-line unicorn/prefer-else-if -- two exhaustive `if`s read clearer here than an else-if; merging cascades into no-negated-condition / no-unnecessary-condition in this hot guard-setup branch\n if (hasGuards) {\n controller = new AbortController();\n this.#currentController = controller;\n const isCurrentNav = () =>\n this.#navigationId === myId && deps.isActive();\n\n const signal = controller.signal;\n\n const emitLeaveApproveCallback = (): Promise<void> | undefined => {\n deps.sendLeaveApprove(confirmedToState, fromState);\n\n if (deps.hasLeaveListeners()) {\n return deps.awaitLeaveListeners(\n confirmedToState,\n fromState,\n signal,\n );\n }\n\n return undefined;\n };\n\n const guardCompletion = executeGuardPipeline(\n canDeactivateFunctions,\n canActivateFunctions,\n toDeactivate,\n toActivate,\n !!shouldDeactivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isCurrentNav,\n emitLeaveApproveCallback,\n );\n\n if (guardCompletion !== undefined) {\n return this.#finishAsyncNavigation(\n guardCompletion,\n {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n if (!isCurrentNav()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n this.#cleanupController(controller, false);\n }\n\n // #1169 commit-gate — refuse to commit a navigation cancelled or\n // terminated during a listener window, BEFORE `completeTransition`'s\n // setState. The FSM table (D-full) already prevents the forceState\n // resurrection; this prevents the state commit that precedes the emit.\n // Gated on `suspendable` so the pure sync hot path pays nothing. A\n // `stop()`/`dispose()` from the listener lands the FSM in IDLE/DISPOSED\n // (caught by `!isActive()`); an external `opts.signal` abort is caught\n // directly. No `#navigationId` check: a reentrant navigate() (the only\n // thing that could bump it synchronously) is banned (REENTRANT_NAVIGATION,\n // §4), so on this sync path `#navigationId === myId` always holds — async\n // supersede is caught in `#finishAsyncNavigation`'s `isCurrentNav`.\n if (suspendable && (!deps.isActive() || opts.signal?.aborted === true)) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const finalState = completeTransition(deps, {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n });\n\n // Mark sync-resolution only AFTER completeTransition returns. It emits\n // TRANSITION_SUCCESS; listener throws are all isolated via onListenerError\n // (the emitter re-throws nothing now that re-entrant emits are coalesced,\n // #1033, and reentrant navigate/CRUD are banned), so the emit itself does\n // not throw. Should completeTransition throw for any other reason, setting\n // the flag optimistically BEFORE it would leave the flag stale-true, so the\n // facade would read lastSyncResolved and skip its suppressing `.catch()` —\n // the rejection would then leak as a Node unhandledRejection. Post-emit\n // placement keeps the flag false on a throw, so control falls to catch and\n // the facade attaches its `.catch`.\n // Stryker disable next-line BooleanLiteral: equivalent — flipping to false routes the facade to the else-branch, which attaches a harmless .catch to an already-resolved promise; there is no rejection to suppress.\n this.lastSyncResolved = true;\n\n return Promise.resolve(finalState);\n } catch (error) {\n this.#handleNavigateError(\n error,\n controller,\n transitionStarted,\n toState,\n fromState,\n );\n\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from guards or transition pipeline\n return Promise.reject(error);\n }\n }\n\n async #finishAsyncNavigation(\n guardCompletion: Promise<void>,\n nav: NavigationContext,\n controller: AbortController,\n myId: number,\n ): Promise<State> {\n const deps = this.#deps;\n const isActive = () =>\n this.#navigationId === myId &&\n !controller.signal.aborted &&\n deps.isActive();\n\n const externalSignal = nav.opts.signal;\n let onExternalAbort: (() => void) | undefined;\n let onInternalAbort: (() => void) | undefined;\n let succeeded = false;\n let failureReason: unknown;\n\n // #1018: race the guard completion against the controller's abort so a\n // non-cooperative guard whose Promise never settles (and ignores `signal`)\n // cannot wedge navigate() forever. `abortRace` RESOLVES on abort, so the\n // post-race `isActive()` check below throws TRANSITION_CANCELLED — the same\n // path that already handles a guard which swallows the abort and resolves\n // `true`. `stop()`/`dispose()`/supersede all abort the controller. Mirrors\n // the leave-path protection `settleLeavePromises` (#663/#673).\n const abortRace = new Promise<void>((resolve) => {\n if (controller.signal.aborted) {\n resolve();\n\n return;\n }\n\n onInternalAbort = () => {\n resolve();\n };\n\n controller.signal.addEventListener(\"abort\", onInternalAbort, {\n once: true,\n });\n });\n\n // Consume `guardCompletion` when the abort wins the race: a slow or\n // never-settling guard that settles later then has no awaiter, which would\n // surface as an unhandled rejection without this catch.\n guardCompletion.catch(() => {\n /* settlement consumed — the race already decided the navigation */\n });\n\n try {\n if (externalSignal) {\n if (externalSignal.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n\n // Bridge an external `{ signal }` abort onto the internal controller.\n // It is NOT scoped to `controller.signal` (the old `{ signal }` option)\n // because success no longer aborts the controller (#722) — the listener\n // is detached explicitly in `finally` instead.\n onExternalAbort = () => {\n // (#1030): route the external abort through the FSM. The\n // `CANCEL` action aborts the internal controller (waking THIS pipeline)\n // with the external `reason` (#943 — surfaces via the leave signal) AND\n // returns the FSM to READY, atomically. No direct `controller.abort`\n // here — \"FSM CANCEL ⟹ controller aborted\" lives in one place\n // (handleCancel). onExternalAbort only fires while the navigation is in\n // flight (the listener is removed in `finally` once it settles), so the\n // FSM is always cancellable here.\n deps.cancelNavigation(externalSignal.reason);\n };\n // Stryker disable next-line ObjectLiteral: equivalent — `{ once: true }` is redundant: the per-navigation signal aborts at most once and is discarded unaborted on success, and the `finally` block explicitly removeEventListener's it.\n externalSignal.addEventListener(\"abort\", onExternalAbort, {\n // Stryker disable next-line BooleanLiteral: equivalent — `once` redundant (see ObjectLiteral above); the listener is explicitly removed in `finally`.\n once: true,\n });\n }\n\n await Promise.race([guardCompletion, abortRace]);\n\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const state = completeTransition(deps, nav);\n\n succeeded = true;\n\n return state;\n } catch (error) {\n failureReason = error;\n\n routeTransitionError(deps, error, nav.toState, nav.fromState);\n\n throw error;\n // NB: the `} finally {}` BlockStatement mutant SURVIVES but is EQUIVALENT —\n // emptying the finally only skips #cleanupController, which is unobservable\n // (defense-in-depth: on a CANCEL the FSM CANCEL action already aborted+nulled\n // the controller via abortCurrentController, RFC §5; the success-path\n // ref-release is proven unobservable — see #cleanupController's disable). It\n // cannot be inline-`Stryker disable`d: the catch `}` and finally `{` share one\n // line, so there is no comment position that targets the finally body. Left documented.\n } finally {\n // Stryker disable next-line ConditionalExpression,BlockStatement: equivalent — listener cleanup is redundant: the per-navigation signal is discarded on completion, so skipping the removeEventListener leaks nothing observable.\n if (onExternalAbort) {\n // Stryker disable next-line StringLiteral: equivalent — cleanup event name is redundant (listener is `{ once: true }` and the signal is discarded), so a wrong name removes nothing observable.\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n\n // Detach the abort-race listener before #cleanupController aborts the\n // controller below, so the cleanup abort cannot re-fire it. `undefined`\n // only when the controller was already aborted at setup (the early-resolve\n // branch above registered no listener).\n if (onInternalAbort) {\n controller.signal.removeEventListener(\"abort\", onInternalAbort);\n }\n\n // Success drops the controller without aborting (the subscribeLeave signal\n // must stay unaborted); cancel/error aborts it with the originating reason\n // so captured signals expose the real cause via `signal.reason` (#943).\n this.#cleanupController(controller, !succeeded, failureReason);\n }\n }\n\n #handleNavigateError(\n error: unknown,\n controller: AbortController | null,\n transitionStarted: boolean,\n toState: State | undefined,\n fromState: State | undefined,\n ): void {\n if (controller) {\n this.#cleanupController(controller, true, error);\n }\n\n if (transitionStarted && toState) {\n routeTransitionError(this.#deps, error, toState, fromState);\n }\n }\n\n #handleNoGuardsLeave(\n toState: State,\n fromState: State | undefined,\n myId: number,\n opts: NavigationOptions,\n transitionPath: TransitionPath,\n canDeactivateFunctions: Map<string, GuardFn>,\n ): Promise<State> | undefined {\n const deps = this.#deps;\n\n deps.sendLeaveApprove(toState, fromState);\n\n if (deps.hasLeaveListeners()) {\n const controller = new AbortController();\n\n // Track as the current navigation BEFORE listeners run so a reentrant\n // navigate() / stop() / dispose() from a sync listener aborts THIS leave\n // signal — parity with the guard path (#722). On success the controller is\n // released without aborting (see #cleanupController).\n this.#currentController = controller;\n\n let leaveResult: Promise<void> | undefined;\n\n try {\n leaveResult = deps.awaitLeaveListeners(\n toState,\n fromState,\n controller.signal,\n );\n } catch (error) {\n // A sync listener threw — the navigation fails; abort the leave signal\n // with the thrown value so a listener that captured the signal sees the\n // real cause via `signal.reason`, not a generic AbortError (#943).\n this.#cleanupController(controller, true, error);\n\n throw error;\n }\n\n if (leaveResult !== undefined) {\n return this.#finishAsyncNavigation(\n leaveResult,\n {\n toState,\n fromState,\n opts,\n toDeactivate: transitionPath.toDeactivate,\n toActivate: transitionPath.toActivate,\n intersection: transitionPath.intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n // Sync listeners settled. A synchronous reentrant navigate() can no longer\n // supersede here (banned, RFC §4), so the leave always succeeds: release the\n // controller WITHOUT aborting (the subscribeLeave signal must stay live).\n this.#cleanupController(controller, false);\n\n return undefined;\n }\n\n // No leave listeners: nothing synchronous could have superseded this\n // navigation during the LEAVE_APPROVE emit (reentrant navigate is banned).\n return undefined;\n }\n\n /**\n * Release a navigation's AbortController. The same `controller.signal` is\n * handed to `subscribeLeave` listeners, so it must abort **only** when the\n * navigation is cancelled or errors — never on success (#722). On the success\n * path pass `cancelled = false`: the reference is dropped without aborting, so\n * a listener that captured the signal still sees `aborted === false`.\n *\n * On the failure/cancellation path (`cancelled = true`) pass the originating\n * `reason` so `signal.reason` carries router/error context (a `RouterError`,\n * or the value a sync leave listener threw) — consistent with the cancellation\n * abort `RouterError(TRANSITION_CANCELLED)`, not a generic `AbortError` (#943).\n * `abort()` is idempotent: a controller already aborted by a superseding\n * navigation keeps its first (also-meaningful) reason.\n */\n #cleanupController(\n controller: AbortController,\n cancelled: boolean,\n reason?: unknown,\n ): void {\n if (cancelled) {\n controller.abort(reason);\n }\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — controller identity-guard; cleanup correctness is enforced by #abortPreviousNavigation + the navigationId/isCurrentNav checks. Full suite stays green with `=== → !==` (nulls the wrong controller) and with the body removed (ref never nulled), so no mutant here is observable.\n if (this.#currentController === controller) {\n this.#currentController = null;\n }\n }\n\n #abortPreviousNavigation(externalSignal?: AbortSignal): void {\n if (this.#deps.isTransitioning()) {\n this.#deps.logger.warn(\n \"router.navigate\",\n \"Concurrent navigation detected on shared router instance. \" +\n \"For SSR, use cloneRouter() to create isolated instance per request.\",\n );\n // The FSM CANCEL action aborts the previous controller — no\n // direct controller.abort here (RFC navigation-cancellation-unification §5).\n this.#deps.cancelNavigation();\n }\n\n if (externalSignal?.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NavigationNamespace.js","names":["constants","#deps","CACHED_NOT_STARTED_REJECTION","CACHED_ROUTE_NOT_FOUND_ERROR","CACHED_ROUTE_NOT_FOUND_REJECTION","#executeNavigation","RouterError","errorCodes","findMisChanneledKey","misChanneledKeyMessage","#abortPreviousNavigation","nameToIDs","EMPTY_PARAMS","EMPTY_SEARCH","#currentController","CACHED_SAME_STATES_ERROR","CACHED_SAME_STATES_REJECTION","#navigationId","getTransitionPath","#handleNoGuardsLeave","executeGuardPipeline","#finishAsyncNavigation","#cleanupController","completeTransition","#handleNavigateError"],"sources":["../../../../src/namespaces/NavigationNamespace/NavigationNamespace.ts"],"sourcesContent":["import {\n CACHED_NOT_STARTED_REJECTION,\n CACHED_ROUTE_NOT_FOUND_ERROR,\n CACHED_ROUTE_NOT_FOUND_REJECTION,\n CACHED_SAME_STATES_ERROR,\n CACHED_SAME_STATES_REJECTION,\n} from \"./constants\";\nimport { completeTransition } from \"./transition/completeTransition\";\nimport { routeTransitionError } from \"./transition/errorHandling\";\nimport { executeGuardPipeline } from \"./transition/guardPhase\";\nimport { findMisChanneledKey, misChanneledKeyMessage } from \"../../channels\";\nimport {\n EMPTY_PARAMS,\n EMPTY_SEARCH,\n errorCodes,\n constants,\n} from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\nimport { getTransitionPath, nameToIDs } from \"../../transitionPath\";\n\nimport type { NavigationContext, NavigationDependencies } from \"./types\";\nimport type { TransitionPath } from \"../../transitionPath\";\nimport type {\n GuardFn,\n NavigationOptions,\n Params,\n SearchParams,\n State,\n TransitionMeta,\n} from \"../../types\";\n\nconst FROZEN_ACTIVATED: string[] = Object.freeze([\n constants.UNKNOWN_ROUTE,\n]) as unknown as string[];\nconst FROZEN_REPLACE_OPTS: NavigationOptions = Object.freeze({ replace: true });\n\nfunction forceReplaceFromUnknown(\n opts: NavigationOptions,\n fromState: State | undefined,\n): NavigationOptions {\n return fromState?.name === constants.UNKNOWN_ROUTE && !opts.replace\n ? { ...opts, replace: true }\n : opts;\n}\n\nfunction isSameNavigation(\n fromState: State | undefined,\n opts: NavigationOptions,\n toState: State,\n): boolean {\n return (\n !!fromState &&\n !opts.reload &&\n !opts.force &&\n fromState.path === toState.path\n );\n}\n\n/**\n * Independent namespace for managing navigation.\n *\n * Handles navigate(), navigateToDefault(), navigateToNotFound(), and transition state.\n *\n * Performance: navigate() uses optimistic sync execution — guards run synchronously\n * until one returns a Promise, then switches to async. This eliminates Promise/AbortController\n * overhead for the common case (no guards or sync guards).\n */\nexport class NavigationNamespace {\n // Stryker disable next-line BooleanLiteral: equivalent — reset to false at the top of every navigate()/navigateToState()/navigateToDefault(), so the initializer value is never observed.\n lastSyncResolved = false;\n lastSyncRejected = false;\n #deps!: NavigationDependencies;\n #currentController: AbortController | null = null;\n #navigationId = 0;\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n setDependencies(deps: NavigationDependencies): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n navigate(\n name: string,\n params: Params,\n search: SearchParams | undefined,\n opts: NavigationOptions,\n ): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n // Fast-path sync rejections: cached error + cached Promise.reject\n // No allocations, no throw/catch overhead, facade skips .catch() suppression\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag, not a correctness gate. Not flagging the cached (pre-suppressed) rejection routes the facade to the else-branch, which re-attaches a harmless .catch; no observable difference.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n let toState: State | undefined;\n\n try {\n toState = deps.buildNavigateState(name, params, search);\n } catch (error) {\n /* v8 ignore next 3 -- @preserve: reachable only via validator-driven\n throws from buildNavigateState (validateStateBuilderArgs) — covered\n in @real-router/validation-plugin's suite, not in core. */\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided buildNavigateState\n return Promise.reject(error);\n }\n\n if (!toState) {\n deps.emitTransitionError(\n undefined,\n deps.getState(),\n CACHED_ROUTE_NOT_FOUND_ERROR,\n );\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_ROUTE_NOT_FOUND_REJECTION;\n }\n\n return this.#executeNavigation(toState, opts);\n }\n\n /**\n * Navigate to a fully-built `State` directly, skipping `buildNavigateState`\n * (forwardState + buildPath + meta lookup). Used by URL plugins after they\n * have already produced a `State` from a browser-initiated event via\n * `api.matchPath(url)` — see issue #525.\n *\n * Semantics vs. `navigate(name, params, opts)`:\n * - `forwardState` is NOT re-applied. matchPath already runs it; reapplying\n * is redundant in the idempotent case and can race in the dynamic case.\n * - `buildPath` is NOT re-run. The caller's `state.path` is used as-is —\n * so `trailingSlash:\"preserve\"` matchedState paths flow through unchanged\n * (closes #525 Q2). `buildPath` interceptors do NOT run; the URL the\n * user navigated to is the source of truth for this code path.\n * - All other pipeline steps run unchanged: SAME_STATES check, FSM\n * transition, guards, `subscribeLeave`, `completeTransition`,\n * plugin lifecycle hooks.\n */\n navigateToState(state: State, opts: NavigationOptions): Promise<State> {\n this.lastSyncResolved = false;\n const deps = this.#deps;\n\n if (!deps.canNavigate()) {\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n // Reject states whose route no longer exists (e.g. the route tree was\n // mutated between matchPath and navigateToState). UNKNOWN_ROUTE is\n // structurally legal — it is the navigateToNotFound output shape.\n if (state.name !== constants.UNKNOWN_ROUTE && !deps.hasRoute(state.name)) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: state.name,\n });\n\n deps.emitTransitionError(undefined, deps.getState(), err);\n\n // This is a FRESH reject (carries `routeName`), not one of the\n // pre-suppressed CACHED_*_REJECTION singletons. `lastSyncRejected`\n // contractually means \"I returned a pre-suppressed cached rejection —\n // skip your .catch()\", so leaving it unset lets the facade attach its\n // own suppression. Setting it here leaked an unhandledRejection on\n // fire-and-forget calls (#721).\n return Promise.reject(err);\n }\n\n // Channel guard, position P3 (#1572). `navigateToState` is the ONE producer\n // that takes a ready-made `State` instead of a `params` argument, so the\n // predicate reads `state.params ∩ queryNames(state.name)`. What it commits\n // becomes `getState()`, so a pre-M2 layout would be silent corruption: the\n // key sits in `state.params` and never reaches `state.path`.\n //\n // Costs nothing on healthy flows — a state produced by core (`matchPath`,\n // `makeState`) is channel-correct by construction, so the predicate is\n // empty on every popstate / memory-restore / SSR-hydration commit. `start()`\n // commits THROUGH here (`RouterLifecycleNamespace`), which is why the guard\n // lives in the namespace rather than on the plugin-API door.\n //\n // Rejects rather than throwing, mirroring the ROUTE_NOT_FOUND guard above:\n // this method returns `Promise<State>` and its URL-plugin callers invoke it\n // from popstate handlers, where a new synchronous throw would be a change\n // of failure shape rather than a new failure.\n const misChanneled = findMisChanneledKey(\n state.params,\n deps.getQueryParams(state.name),\n );\n\n if (misChanneled !== undefined) {\n const err = new RouterError(errorCodes.WRONG_CHANNEL, {\n routeName: state.name,\n message: `[router.navigateToState] ${misChanneledKeyMessage(\n state.name,\n misChanneled,\n \"`state.params`\",\n )}`,\n });\n\n deps.emitTransitionError(undefined, deps.getState(), err);\n\n return Promise.reject(err);\n }\n\n // States from `matchPath` are deeply frozen (`freezeStateInPlace`).\n // `completeTransition` mutates `toState.transition` and `context` is\n // intentionally extensible for plugin claim writes, so we hand the\n // pipeline a writable shell — same shape `makeState(skipFreeze=true)`\n // produces. `params` stays referentially shared (already frozen).\n // `transition` is omitted so completeTransition can assign it.\n const writableState = {\n name: state.name,\n params: state.params,\n // Carry the query channel through the writable shell (RFC-4 M2 / #1548) —\n // without this, start()'s navigateToState(matchPath(...)) would drop the\n // matched query from the committed state.\n search: state.search,\n path: state.path,\n context: { ...state.context },\n } as State;\n\n // No route-meta to carry any more (RFC-4 M2 / #1548): ownership is read from\n // the live matcher by `state.name` (`getTransitionPath`'s `getMeta`), not\n // from a per-State WeakMap. The former #1170 carry — which existed only so a\n // matchPath-derived writable shell stayed non-meta-less across consecutive\n // popstate navs — is obsolete: any state whose name is in the tree takes the\n // STANDARD PATH regardless of object identity.\n\n return this.#executeNavigation(writableState, opts);\n }\n\n navigateToDefault(opts: NavigationOptions): Promise<State> {\n // Reset the sync-resolution flag on entry, mirroring navigate() and\n // navigateToState(). start() leaves `lastSyncResolved = true`, and the\n // early reject paths below return before delegating to navigate(), so a\n // stale `true` would make the facade take the \"already resolved\" branch\n // and skip .catch() suppression — leaking an unhandledRejection on\n // fire-and-forget calls (#721).\n this.lastSyncResolved = false;\n const deps = this.#deps;\n const options = deps.getOptions();\n\n if (!options.defaultRoute) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute not configured\",\n }),\n );\n }\n\n let route: string;\n let params: Params;\n let search: SearchParams;\n\n try {\n ({ route, params, search } = deps.resolveDefault());\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided resolveDefault callback\n return Promise.reject(error);\n }\n\n if (!route) {\n return Promise.reject(\n new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: \"defaultRoute resolved to empty\",\n }),\n );\n }\n\n // Both channels, never one bag (RFC-4 M2 / #1548). The query slot took\n // `undefined` until `defaultSearch` existed as a router option, so a\n // query-declared name in `defaultParams` reached the URL only via the\n // `forwardState` seam's channel re-separation — the repair the pipeline\n // design removes. Passing the query here makes the default route's query\n // defaults independent of that stage.\n return this.navigate(route, params, search, opts);\n }\n\n navigateToNotFound(path: string): State {\n // #1186 — liveness gate. This internal commit primitive has no FSM\n // transition of its own, so without this check a `dispose()` that lands\n // while a start-interceptor is parked (FSM already DISPOSED) would let the\n // resuming pipeline commit an UNKNOWN_ROUTE state on the disposed router and\n // `start()` resolve. Symmetric with `navigateToState`'s `canNavigate()` gate\n // (the matched-route branch is already protected). `!isActive()` also covers\n // a merely-stopped (IDLE) router: the only reachable path to that is a direct\n // `router.navigateToNotFound()` on a stopped instance (internal callers run\n // during STARTING, which is active), so the ROUTER_DISPOSED code is slightly\n // broad there — fail-closed is deliberate (committing on a stopped router is\n // out of contract), and the disposed race is the case that matters.\n if (!this.#deps.isActive()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n this.#abortPreviousNavigation();\n\n const fromState = this.#deps.getState();\n const deactivated: string[] = fromState\n ? nameToIDs(fromState.name).toReversed()\n : [];\n\n Object.freeze(deactivated);\n\n const segments: TransitionMeta[\"segments\"] = {\n deactivated,\n activated: FROZEN_ACTIVATED,\n intersection: \"\",\n };\n\n Object.freeze(segments);\n\n const transitionMeta: TransitionMeta = {\n phase: \"activating\",\n ...(fromState && { from: fromState.name }),\n reason: \"success\",\n replace: true,\n segments,\n };\n\n Object.freeze(transitionMeta);\n\n const state: State = {\n name: constants.UNKNOWN_ROUTE,\n params: EMPTY_PARAMS,\n search: EMPTY_SEARCH,\n path,\n transition: transitionMeta,\n context: {},\n };\n\n Object.freeze(state);\n\n this.#deps.setState(state);\n this.#deps.emitTransitionSuccess(state, fromState, FROZEN_REPLACE_OPTS);\n\n return state;\n }\n\n /**\n * Aborts and releases the in-flight navigation's `AbortController` (waking the\n * parked async pipeline via `onInternalAbort`). This is the\n * **effect** of the FSM `CANCEL` action (`handleCancel` → injected\n * `deps.abortCurrentController`), not something cancellation sources call\n * directly — so \"FSM `CANCEL` ⟹ controller aborted\" holds in one place (RFC\n * navigation-cancellation-unification §5). `reason` (e.g. an external\n * `opts.signal`'s reason, #943) becomes the controller's `signal.reason`;\n * defaults to `TRANSITION_CANCELLED`.\n */\n abortCurrentController(reason?: unknown): void {\n this.#currentController?.abort(\n reason ?? new RouterError(errorCodes.TRANSITION_CANCELLED),\n );\n this.#currentController = null;\n }\n\n #executeNavigation(toState: State, opts: NavigationOptions): Promise<State> {\n const deps = this.#deps;\n let fromState: State | undefined;\n let transitionStarted = false;\n let controller: AbortController | null = null;\n\n try {\n fromState = deps.getState();\n opts = forceReplaceFromUnknown(opts, fromState);\n\n if (isSameNavigation(fromState, opts, toState)) {\n deps.emitTransitionError(toState, fromState, CACHED_SAME_STATES_ERROR);\n // Stryker disable next-line BooleanLiteral: equivalent — #721 optimization flag (see L95): the cached rejection is suppressed by the facade else-branch regardless of the flag.\n this.lastSyncRejected = true;\n\n return CACHED_SAME_STATES_REJECTION;\n }\n\n this.#abortPreviousNavigation(opts.signal);\n\n // Stryker disable next-line UpdateOperator: equivalent — `#navigationId` is only ever compared by identity (`!== myId`) to detect supersession; uniqueness per navigation is all that matters, so `--` (decreasing ids) is indistinguishable from `++`.\n const myId = ++this.#navigationId;\n\n // #1169 commit-gate — liveness snapshot captured BEFORE the pre-commit\n // listener windows. A listener's `stop()`/`dispose()` runs `clearAll()`,\n // which empties the listener lists, so the marker must be read now, not at\n // the commit site (that self-destruct was the QB/QE hole, RFC §5-bis).\n // `suspendable` is true only when a synchronous supersede is reachable — an\n // external `opts.signal`, `subscribeLeave` listeners, or a pre-commit\n // plugin listener (`onTransitionStart` / `onTransitionLeaveApprove`); the\n // pure synchronous navigate (none of these) is uncancellable and skips the\n // gate, keeping the #307 hot path perf-neutral.\n const suspendable =\n opts.signal !== undefined ||\n deps.hasLeaveListeners() ||\n deps.hasPreCommitListeners();\n\n deps.startTransition(toState, fromState);\n transitionStarted = true;\n\n // Post-`startTransition` supersession is now caught at the commit-gate\n // below (before `completeTransition`'s setState): a `stop()`/`dispose()`/\n // external-abort from the TRANSITION_START listener leaves the FSM in\n // IDLE/DISPOSED, which `!deps.isActive()` detects. (Async supersession is\n // additionally caught in `#finishAsyncNavigation` / the guard pipeline's\n // `isCurrentNav`; a reentrant navigate() is banned — REENTRANT_NAVIGATION.)\n\n const [canDeactivateFunctions, canActivateFunctions] =\n deps.getLifecycleFunctions();\n const isUnknownRoute = toState.name === constants.UNKNOWN_ROUTE;\n\n const transitionPath = getTransitionPath(toState, fromState, (name) =>\n deps.getMetaForState(name),\n );\n const { toDeactivate, toActivate, intersection } = transitionPath;\n\n const shouldDeactivate =\n fromState && !opts.forceDeactivate && toDeactivate.length > 0;\n const shouldActivate = !isUnknownRoute && toActivate.length > 0;\n const hasGuards =\n canDeactivateFunctions.size > 0 || canActivateFunctions.size > 0;\n\n const confirmedToState = toState;\n\n if (!hasGuards) {\n const asyncLeave = this.#handleNoGuardsLeave(\n confirmedToState,\n fromState,\n myId,\n opts,\n transitionPath,\n canDeactivateFunctions,\n );\n\n if (asyncLeave !== undefined) {\n return asyncLeave;\n }\n }\n\n // Stryker disable next-line ConditionalExpression: equivalent — running the guard pipeline on the no-guards path does not double-emit LEAVE_APPROVE (full suite green with `if (true)`); the BlockStatement mutant stays live (killed by guarded-route tests).\n // eslint-disable-next-line unicorn/prefer-else-if -- two exhaustive `if`s read clearer here than an else-if; merging cascades into no-negated-condition / no-unnecessary-condition in this hot guard-setup branch\n if (hasGuards) {\n controller = new AbortController();\n this.#currentController = controller;\n const isCurrentNav = () =>\n this.#navigationId === myId && deps.isActive();\n\n const signal = controller.signal;\n\n const emitLeaveApproveCallback = (): Promise<void> | undefined => {\n deps.sendLeaveApprove(confirmedToState, fromState);\n\n if (deps.hasLeaveListeners()) {\n return deps.awaitLeaveListeners(\n confirmedToState,\n fromState,\n signal,\n );\n }\n\n return undefined;\n };\n\n const guardCompletion = executeGuardPipeline(\n canDeactivateFunctions,\n canActivateFunctions,\n toDeactivate,\n toActivate,\n !!shouldDeactivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isCurrentNav,\n emitLeaveApproveCallback,\n );\n\n if (guardCompletion !== undefined) {\n return this.#finishAsyncNavigation(\n guardCompletion,\n {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n if (!isCurrentNav()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n this.#cleanupController(controller, false);\n }\n\n // #1169 commit-gate — refuse to commit a navigation cancelled or\n // terminated during a listener window, BEFORE `completeTransition`'s\n // setState. The FSM table (D-full) already prevents the forceState\n // resurrection; this prevents the state commit that precedes the emit.\n // Gated on `suspendable` so the pure sync hot path pays nothing. A\n // `stop()`/`dispose()` from the listener lands the FSM in IDLE/DISPOSED\n // (caught by `!isActive()`); an external `opts.signal` abort is caught\n // directly. No `#navigationId` check: a reentrant navigate() (the only\n // thing that could bump it synchronously) is banned (REENTRANT_NAVIGATION,\n // §4), so on this sync path `#navigationId === myId` always holds — async\n // supersede is caught in `#finishAsyncNavigation`'s `isCurrentNav`.\n if (suspendable && (!deps.isActive() || opts.signal?.aborted === true)) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const finalState = completeTransition(deps, {\n toState,\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n canDeactivateFunctions,\n });\n\n // Mark sync-resolution only AFTER completeTransition returns. It emits\n // TRANSITION_SUCCESS; listener throws are all isolated via onListenerError\n // (the emitter re-throws nothing now that re-entrant emits are coalesced,\n // #1033, and reentrant navigate/CRUD are banned), so the emit itself does\n // not throw. Should completeTransition throw for any other reason, setting\n // the flag optimistically BEFORE it would leave the flag stale-true, so the\n // facade would read lastSyncResolved and skip its suppressing `.catch()` —\n // the rejection would then leak as a Node unhandledRejection. Post-emit\n // placement keeps the flag false on a throw, so control falls to catch and\n // the facade attaches its `.catch`.\n // Stryker disable next-line BooleanLiteral: equivalent — flipping to false routes the facade to the else-branch, which attaches a harmless .catch to an already-resolved promise; there is no rejection to suppress.\n this.lastSyncResolved = true;\n\n return Promise.resolve(finalState);\n } catch (error) {\n this.#handleNavigateError(\n error,\n controller,\n transitionStarted,\n toState,\n fromState,\n );\n\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from guards or transition pipeline\n return Promise.reject(error);\n }\n }\n\n async #finishAsyncNavigation(\n guardCompletion: Promise<void>,\n nav: NavigationContext,\n controller: AbortController,\n myId: number,\n ): Promise<State> {\n const deps = this.#deps;\n const isActive = () =>\n this.#navigationId === myId &&\n !controller.signal.aborted &&\n deps.isActive();\n\n const externalSignal = nav.opts.signal;\n let onExternalAbort: (() => void) | undefined;\n let onInternalAbort: (() => void) | undefined;\n let succeeded = false;\n let failureReason: unknown;\n\n // #1018: race the guard completion against the controller's abort so a\n // non-cooperative guard whose Promise never settles (and ignores `signal`)\n // cannot wedge navigate() forever. `abortRace` RESOLVES on abort, so the\n // post-race `isActive()` check below throws TRANSITION_CANCELLED — the same\n // path that already handles a guard which swallows the abort and resolves\n // `true`. `stop()`/`dispose()`/supersede all abort the controller. Mirrors\n // the leave-path protection `settleLeavePromises` (#663/#673).\n const abortRace = new Promise<void>((resolve) => {\n if (controller.signal.aborted) {\n resolve();\n\n return;\n }\n\n onInternalAbort = () => {\n resolve();\n };\n\n controller.signal.addEventListener(\"abort\", onInternalAbort, {\n once: true,\n });\n });\n\n // Consume `guardCompletion` when the abort wins the race: a slow or\n // never-settling guard that settles later then has no awaiter, which would\n // surface as an unhandled rejection without this catch.\n guardCompletion.catch(() => {\n /* settlement consumed — the race already decided the navigation */\n });\n\n try {\n if (externalSignal) {\n if (externalSignal.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n\n // Bridge an external `{ signal }` abort onto the internal controller.\n // It is NOT scoped to `controller.signal` (the old `{ signal }` option)\n // because success no longer aborts the controller (#722) — the listener\n // is detached explicitly in `finally` instead.\n onExternalAbort = () => {\n // (#1030): route the external abort through the FSM. The\n // `CANCEL` action aborts the internal controller (waking THIS pipeline)\n // with the external `reason` (#943 — surfaces via the leave signal) AND\n // returns the FSM to READY, atomically. No direct `controller.abort`\n // here — \"FSM CANCEL ⟹ controller aborted\" lives in one place\n // (handleCancel). onExternalAbort only fires while the navigation is in\n // flight (the listener is removed in `finally` once it settles), so the\n // FSM is always cancellable here.\n deps.cancelNavigation(externalSignal.reason);\n };\n // Stryker disable next-line ObjectLiteral: equivalent — `{ once: true }` is redundant: the per-navigation signal aborts at most once and is discarded unaborted on success, and the `finally` block explicitly removeEventListener's it.\n externalSignal.addEventListener(\"abort\", onExternalAbort, {\n // Stryker disable next-line BooleanLiteral: equivalent — `once` redundant (see ObjectLiteral above); the listener is explicitly removed in `finally`.\n once: true,\n });\n }\n\n await Promise.race([guardCompletion, abortRace]);\n\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n const state = completeTransition(deps, nav);\n\n succeeded = true;\n\n return state;\n } catch (error) {\n failureReason = error;\n\n routeTransitionError(deps, error, nav.toState, nav.fromState);\n\n throw error;\n // NB: the `} finally {}` BlockStatement mutant SURVIVES but is EQUIVALENT —\n // emptying the finally only skips #cleanupController, which is unobservable\n // (defense-in-depth: on a CANCEL the FSM CANCEL action already aborted+nulled\n // the controller via abortCurrentController, RFC §5; the success-path\n // ref-release is proven unobservable — see #cleanupController's disable). It\n // cannot be inline-`Stryker disable`d: the catch `}` and finally `{` share one\n // line, so there is no comment position that targets the finally body. Left documented.\n } finally {\n // Stryker disable next-line ConditionalExpression,BlockStatement: equivalent — listener cleanup is redundant: the per-navigation signal is discarded on completion, so skipping the removeEventListener leaks nothing observable.\n if (onExternalAbort) {\n // Stryker disable next-line StringLiteral: equivalent — cleanup event name is redundant (listener is `{ once: true }` and the signal is discarded), so a wrong name removes nothing observable.\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n\n // Detach the abort-race listener before #cleanupController aborts the\n // controller below, so the cleanup abort cannot re-fire it. `undefined`\n // only when the controller was already aborted at setup (the early-resolve\n // branch above registered no listener).\n if (onInternalAbort) {\n controller.signal.removeEventListener(\"abort\", onInternalAbort);\n }\n\n // Success drops the controller without aborting (the subscribeLeave signal\n // must stay unaborted); cancel/error aborts it with the originating reason\n // so captured signals expose the real cause via `signal.reason` (#943).\n this.#cleanupController(controller, !succeeded, failureReason);\n }\n }\n\n #handleNavigateError(\n error: unknown,\n controller: AbortController | null,\n transitionStarted: boolean,\n toState: State | undefined,\n fromState: State | undefined,\n ): void {\n if (controller) {\n this.#cleanupController(controller, true, error);\n }\n\n if (transitionStarted && toState) {\n routeTransitionError(this.#deps, error, toState, fromState);\n }\n }\n\n #handleNoGuardsLeave(\n toState: State,\n fromState: State | undefined,\n myId: number,\n opts: NavigationOptions,\n transitionPath: TransitionPath,\n canDeactivateFunctions: Map<string, GuardFn>,\n ): Promise<State> | undefined {\n const deps = this.#deps;\n\n deps.sendLeaveApprove(toState, fromState);\n\n if (deps.hasLeaveListeners()) {\n const controller = new AbortController();\n\n // Track as the current navigation BEFORE listeners run so a reentrant\n // navigate() / stop() / dispose() from a sync listener aborts THIS leave\n // signal — parity with the guard path (#722). On success the controller is\n // released without aborting (see #cleanupController).\n this.#currentController = controller;\n\n let leaveResult: Promise<void> | undefined;\n\n try {\n leaveResult = deps.awaitLeaveListeners(\n toState,\n fromState,\n controller.signal,\n );\n } catch (error) {\n // A sync listener threw — the navigation fails; abort the leave signal\n // with the thrown value so a listener that captured the signal sees the\n // real cause via `signal.reason`, not a generic AbortError (#943).\n this.#cleanupController(controller, true, error);\n\n throw error;\n }\n\n if (leaveResult !== undefined) {\n return this.#finishAsyncNavigation(\n leaveResult,\n {\n toState,\n fromState,\n opts,\n toDeactivate: transitionPath.toDeactivate,\n toActivate: transitionPath.toActivate,\n intersection: transitionPath.intersection,\n canDeactivateFunctions,\n },\n controller,\n myId,\n );\n }\n\n // Sync listeners settled. A synchronous reentrant navigate() can no longer\n // supersede here (banned, RFC §4), so the leave always succeeds: release the\n // controller WITHOUT aborting (the subscribeLeave signal must stay live).\n this.#cleanupController(controller, false);\n\n return undefined;\n }\n\n // No leave listeners: nothing synchronous could have superseded this\n // navigation during the LEAVE_APPROVE emit (reentrant navigate is banned).\n return undefined;\n }\n\n /**\n * Release a navigation's AbortController. The same `controller.signal` is\n * handed to `subscribeLeave` listeners, so it must abort **only** when the\n * navigation is cancelled or errors — never on success (#722). On the success\n * path pass `cancelled = false`: the reference is dropped without aborting, so\n * a listener that captured the signal still sees `aborted === false`.\n *\n * On the failure/cancellation path (`cancelled = true`) pass the originating\n * `reason` so `signal.reason` carries router/error context (a `RouterError`,\n * or the value a sync leave listener threw) — consistent with the cancellation\n * abort `RouterError(TRANSITION_CANCELLED)`, not a generic `AbortError` (#943).\n * `abort()` is idempotent: a controller already aborted by a superseding\n * navigation keeps its first (also-meaningful) reason.\n */\n #cleanupController(\n controller: AbortController,\n cancelled: boolean,\n reason?: unknown,\n ): void {\n if (cancelled) {\n controller.abort(reason);\n }\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — controller identity-guard; cleanup correctness is enforced by #abortPreviousNavigation + the navigationId/isCurrentNav checks. Full suite stays green with `=== → !==` (nulls the wrong controller) and with the body removed (ref never nulled), so no mutant here is observable.\n if (this.#currentController === controller) {\n this.#currentController = null;\n }\n }\n\n #abortPreviousNavigation(externalSignal?: AbortSignal): void {\n if (this.#deps.isTransitioning()) {\n this.#deps.logger.warn(\n \"router.navigate\",\n \"Concurrent navigation detected on shared router instance. \" +\n \"For SSR, use cloneRouter() to create isolated instance per request.\",\n );\n // The FSM CANCEL action aborts the previous controller — no\n // direct controller.abort here (RFC navigation-cancellation-unification §5).\n this.#deps.cancelNavigation();\n }\n\n if (externalSignal?.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OptionsNamespace.js","names":["#options","deepFreeze","defaultOptions"],"sources":["../../../../src/namespaces/OptionsNamespace/OptionsNamespace.ts"],"sourcesContent":["// packages/core/src/namespaces/OptionsNamespace/OptionsNamespace.ts\n\nimport { defaultOptions } from \"./constants\";\nimport { deepFreeze } from \"./helpers\";\nimport { validateOptionsIsObject } from \"./validators\";\n\nimport type { Options } from \"../../types\";\n\nexport class OptionsNamespace {\n readonly #options: Readonly<Options
|
|
1
|
+
{"version":3,"file":"OptionsNamespace.js","names":["#options","deepFreeze","defaultOptions"],"sources":["../../../../src/namespaces/OptionsNamespace/OptionsNamespace.ts"],"sourcesContent":["// packages/core/src/namespaces/OptionsNamespace/OptionsNamespace.ts\n\nimport { defaultOptions } from \"./constants\";\nimport { deepFreeze } from \"./helpers\";\nimport { validateOptionsIsObject } from \"./validators\";\n\nimport type { DefaultDependencies, Options } from \"../../types\";\n\nexport class OptionsNamespace<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n readonly #options: Readonly<Options<Dependencies>>;\n\n constructor(initialOptions: Partial<Options<Dependencies>> = {}) {\n this.#options = deepFreeze({\n ...defaultOptions,\n ...initialOptions,\n });\n }\n\n static validateOptionsIsObject(\n options: unknown,\n ): asserts options is Record<string, unknown> {\n validateOptionsIsObject(options);\n }\n\n get(): Readonly<Options<Dependencies>> {\n return this.#options;\n }\n}\n"],"mappings":"yFAQA,IAAa,EAAb,KAEE,CACA,GAEA,YAAY,EAAiD,CAAC,EAAG,CAC/D,KAAKA,GAAWC,EAAAA,WAAW,CACzB,GAAGC,EAAAA,eACH,GAAG,CACL,CAAC,CACH,CAEA,OAAO,wBACL,EAC4C,CAC5C,EAAA,wBAAwB,CAAO,CACjC,CAEA,KAAuC,CACrC,OAAO,KAAKF,EACd,CACF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e={defaultRoute:``,defaultParams:{},trailingSlash:`preserve`,caseSensitive:!0,queryParamsMode:`loose`,queryParams:require("../../engine/search-params/encode.js").DEFAULT_QUERY_PARAMS,urlParamsEncoding:`default`,allowNotFound:!0,rewritePathOnMatch:!0};exports.defaultOptions=e;
|
|
1
|
+
const e={defaultRoute:``,defaultParams:{},defaultSearch:{},trailingSlash:`preserve`,caseSensitive:!0,queryParamsMode:`loose`,queryParams:require("../../engine/search-params/encode.js").DEFAULT_QUERY_PARAMS,urlParamsEncoding:`default`,allowNotFound:!0,rewritePathOnMatch:!0};exports.defaultOptions=e;
|
|
2
2
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":["DEFAULT_QUERY_PARAMS"],"sources":["../../../../src/namespaces/OptionsNamespace/constants.ts"],"sourcesContent":["// packages/core/src/namespaces/OptionsNamespace/constants.ts\n\nimport { DEFAULT_QUERY_PARAMS } from \"../../engine\";\n\nimport type { Options } from \"../../types\";\n\n/**\n * Default options for the router.\n */\nexport const defaultOptions
|
|
1
|
+
{"version":3,"file":"constants.js","names":["DEFAULT_QUERY_PARAMS"],"sources":["../../../../src/namespaces/OptionsNamespace/constants.ts"],"sourcesContent":["// packages/core/src/namespaces/OptionsNamespace/constants.ts\n\nimport { DEFAULT_QUERY_PARAMS } from \"../../engine\";\n\nimport type { Options } from \"../../types\";\n\n/**\n * Default options for the router.\n */\n// No explicit `: Options` annotation — that would widen every field to the\n// union `Options<DefaultDependencies>` declares, including the CALLBACK arm of\n// `defaultRoute` / `defaultParams` / `defaultSearch`. `Options` is generic over\n// the dependency map now, and a callback typed against `object` does not flow\n// into an `Options<Deps>`. `satisfies` keeps the check while letting the\n// inferred (callback-free) literal types stay assignable to any instantiation.\nexport const defaultOptions = {\n defaultRoute: \"\",\n defaultParams: {},\n defaultSearch: {},\n trailingSlash: \"preserve\",\n caseSensitive: true,\n queryParamsMode: \"loose\",\n queryParams: DEFAULT_QUERY_PARAMS,\n urlParamsEncoding: \"default\",\n allowNotFound: true,\n rewritePathOnMatch: true,\n} satisfies Options;\n"],"mappings":"AAeA,MAAa,EAAiB,CAC5B,aAAc,GACd,cAAe,CAAC,EAChB,cAAe,CAAC,EAChB,cAAe,WACf,cAAe,GACf,gBAAiB,QACjB,0DAAaA,CAAAA,CAAAA,qBACb,kBAAmB,UACnB,cAAe,GACf,mBAAoB,EACtB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","names":[],"sources":["../../../../src/namespaces/OptionsNamespace/helpers.ts"],"sourcesContent":["// packages/core/src/namespaces/OptionsNamespace/helpers.ts\n\nimport type {
|
|
1
|
+
{"version":3,"file":"helpers.js","names":[],"sources":["../../../../src/namespaces/OptionsNamespace/helpers.ts"],"sourcesContent":["// packages/core/src/namespaces/OptionsNamespace/helpers.ts\n\nimport type {\n DefaultDependencies,\n Options,\n Params,\n SearchParams,\n} from \"../../types\";\n\n/**\n * Recursively freezes an object and all nested objects.\n * Only freezes plain objects, not primitives or special objects.\n */\nexport function deepFreeze<T extends object>(obj: T): Readonly<T> {\n Object.freeze(obj);\n\n for (const value of Object.values(obj as Record<string, unknown>)) {\n if (value && typeof value === \"object\" && value.constructor === Object) {\n deepFreeze(value);\n }\n }\n\n return obj;\n}\n\n/**\n * Resolves an option value that can be static or a callback.\n * If the value is a function, calls it with getDependency and returns the result.\n * Otherwise, returns the value as-is.\n */\nexport function resolveOption<D extends DefaultDependencies>(\n value: Options<D>[\"defaultRoute\"],\n getDependency: (name: string) => unknown,\n): string;\n\nexport function resolveOption<D extends DefaultDependencies>(\n value: Options<D>[\"defaultParams\"],\n getDependency: (name: string) => unknown,\n): Params;\n\nexport function resolveOption<D extends DefaultDependencies>(\n value: Options<D>[\"defaultSearch\"],\n getDependency: (name: string) => unknown,\n): SearchParams;\n\nexport function resolveOption<D extends DefaultDependencies>(\n value:\n | Options<D>[\"defaultRoute\"]\n | Options<D>[\"defaultParams\"]\n | Options<D>[\"defaultSearch\"],\n getDependency: (name: string) => unknown,\n): string | Params | SearchParams {\n if (typeof value === \"function\") {\n // Runtime getDependency is (name: string) => unknown, but DefaultRouteCallback<object>\n // expects <K extends keyof object>(name: K) => object[K] where keyof object = never.\n // Cast needed to bridge generic constraint mismatch.\n return value(getDependency as never);\n }\n\n return value;\n}\n"],"mappings":"AAaA,SAAgB,EAA6B,EAAqB,CAChE,OAAO,OAAO,CAAG,EAEjB,IAAK,IAAM,KAAS,OAAO,OAAO,CAA8B,EAC1D,GAAS,OAAO,GAAU,UAAY,EAAM,cAAgB,QAC9D,EAAW,CAAK,EAIpB,OAAO,CACT,CAsBA,SAAgB,EACd,EAIA,EACgC,CAQhC,OAPI,OAAO,GAAU,WAIZ,EAAM,CAAsB,EAG9B,CACT"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../
|
|
1
|
+
const e=require("../../channels/guard.js"),t=require("../../constants.js"),n=require("../../helpers.js"),r=require("../../pipeline/canonicalize.js"),i=require("../../pipeline/materialize.js"),a=require("./helpers.js"),o=require("./routesStore.js"),s=require("../../transitionPath.js");function c(e,t){return{name:t??e.segments.at(-1).fullName,params:e.params,search:e.search,meta:e.meta}}var l=class{#e;#t;#n;get#r(){return this.#e.depsStore}constructor(e,t,n){this.#e=o.createRoutesStore(e,t,n)}static shouldUpdateNode(e,t){return(n,r)=>{if(!(n&&typeof n==`object`&&`name`in n))throw TypeError(`[router.shouldUpdateNode] toState must be valid State object`);if(n.transition.reload||e===``)return!0;let{intersection:i,toActivate:a,toDeactivate:o}=s.getTransitionPath(n,r,t);return e===i||a.includes(e)?!0:o.includes(e)}}setDependencies(e){this.#e.depsStore=e}flushPendingGuards(){let e=this.#r;for(let[t,n]of this.#e.pendingCanActivate)e.addActivateGuard(t,n);this.#e.pendingCanActivate.clear();for(let[t,n]of this.#e.pendingCanDeactivate)e.addDeactivateGuard(t,n);this.#e.pendingCanDeactivate.clear()}setLifecycleNamespace(e){this.#e.lifecycleNamespace=e}setRootPath(e){o.applyRootPath(this.#e,e)}hasRoute(e){return this.#e.matcher.hasRoute(e)}clearRoutes(){o.resetStore(this.#e)}buildPath(e,n,i,a){if(e===t.constants.UNKNOWN_ROUTE)return typeof n.path==`string`?n.path:``;let o=r.canonicalize(this.#r.port,e,n,i,{resolveForward:!1});if(typeof this.#e.config.encoders[e]==`function`){let t=this.#e.config.encoders[e]({params:{...o.path},search:{...o.query}});return this.#e.matcher.buildPath(e,t.params,t.search,this.#c(a))}return this.#e.matcher.buildPath(e,o.path,o.query,this.#c(a))}matchPath(t,n){let o=n,s=this.#e.matcher.match(t);if(!s)return;let l=c(s),{name:u,params:d}=l,f=l.search,p=this.#e.config.decoders[u],m;typeof p==`function`?(m=p({params:d,search:f}),this.#r.getValidator()?.routes.validateStateBuilderArgs(u,m.params,`matchPath`),e.assertChannelCorrect(`matchPath`,u,m.params,this.getQueryParams(u),"the `params` returned by this route's `decodeParams`")):m={params:d,search:f};let h=r.canonicalize(this.#r.port,u,m.params,m.search),g=h.name,_=h.path,v=h.query,y=t;if(o.rewritePathOnMatch){let e=typeof this.#e.config.encoders[g]==`function`?this.#e.config.encoders[g]({params:_,search:v}):{params:_,search:v},n=o.trailingSlash;try{y=this.#e.matcher.buildPath(g,e.params,e.search,{trailingSlash:n===`never`||n===`always`?n:void 0,queryParamsMode:o.queryParamsMode}),n===`preserve`&&(y=a.matchSourceTrailingSlash(t,y))}catch{y=t}}return i.materialize(h,{path:y})}forwardState(e,n,r){let i=r??t.EMPTY_SEARCH;if(Object.hasOwn(this.#e.config.forwardFnMap,e)){let t=this.#e.config.forwardFnMap[e],{target:r,chain:a}=this.#l(e,t,n);return this.#s(r,a,n,i)}let a=this.#e.resolvedForwardMap[e]??e;if(a!==e&&Object.hasOwn(this.#e.config.forwardFnMap,a)){let t=this.#e.config.forwardFnMap[a],{target:r,chain:o}=this.#l(a,t,n);return this.#s(r,[...this.#o(e),...o],n,i)}return a===e?{name:e,params:n,search:i}:this.#s(a,this.#o(e),n,i)}buildStateResolved(e,t){let n=this.#e.matcher.getSegmentsByName(e);if(n)return c({segments:n,params:t,search:{},meta:this.#e.matcher.getMetaByName(e)},e)}isActiveRoute(e,n=t.EMPTY_PARAMS,r=t.EMPTY_SEARCH,i=!1,a=!0){if(this.#i(e,n,r,i,a))return!0;if(!Object.hasOwn(this.#e.config.forwardMap,e)&&!Object.hasOwn(this.#e.config.forwardFnMap,e))return!1;let o;try{o=this.forwardState(e,n,r)}catch(t){return this.#r.logger.warn(`router.isActiveRoute`,`Dynamic forwardTo of route "${e}" threw while resolving the active-link destination; treating the link as inactive.`,t),!1}return this.#i(o.name,o.params,o.search,i,a)}getMetaForState(e){return this.#e.matcher.hasRoute(e)?this.#e.matcher.getMetaByName(e):void 0}getUrlParams(e){return a.urlParamsOf(this.#e,e)}getQueryParams(e){return a.queryParamsOf(this.#e,e)}getStore(){return this.#e}getPort(){return this.#r.port}#i(e,t,n,r,i){try{return this.#a(e,t,n,r,i)}catch(t){return this.#r.logger.warn(`router.isActiveRoute`,`Reading the arguments for route "${e}" threw while resolving the active-link state; treating the link as inactive.`,t),!1}}#a(e,t,n,o,s){let c=this.#r.getState();if(!c)return!1;let l=c.name;if(l!==e&&!l.startsWith(`${e}.`)&&!e.startsWith(`${l}.`))return!1;let u=r.canonicalize(this.#r.port,e,t,n,{resolveForward:!1});return o||l===e?this.#r.areStatesEqual(i.materialize(u,{path:``,skipFreeze:!0}),c,s):!(!l.startsWith(`${e}.`)||!a.paramsMatch(u.path,c.params)||!s&&!a.paramsMatch(u.query,c.search))}#o(e){let t=[],n=e;for(;Object.hasOwn(this.#e.config.forwardMap,n);)t.push(n),n=this.#e.config.forwardMap[n];return t}#s(e,t,r,i){let a,o;for(let e of t)a=n.mergeDefined(this.#e.config.defaultParams[e],a),o=n.mergeDefined(this.#e.config.defaultSearch[e],o);return{name:e,params:n.mergeDefined(a,r),search:n.mergeDefined(o,i)}}#c(e){if(this.#t)return e!==this.#n&&this.#r.logger.warn(`router.buildPath`,"`options` differs from the cached source reference; router options are immutable per router instance, so the first-cached buildPath options are reused (#957)."),this.#t;this.#n=e;let t=e?.trailingSlash;return this.#t=Object.freeze({trailingSlash:t===`never`||t===`always`?t:void 0,queryParamsMode:e?.queryParamsMode}),this.#t}#l(e,t,n){let r=new Set([e]),i=[e],a=t(this.#r.getDependency,n),o=0;if(typeof a!=`string`)throw TypeError(`forwardTo callback must return a string, got ${typeof a}`);for(;o<100;){if(this.#e.matcher.getSegmentsByName(a)===void 0)throw Error(`Route "${a}" does not exist`);if(r.has(a)){let e=[...r,a].join(` → `);throw Error(`Circular forwardTo: ${e}`)}if(r.add(a),Object.hasOwn(this.#e.config.forwardFnMap,a)){let e=this.#e.config.forwardFnMap[a];i.push(a),a=e(this.#r.getDependency,n),o++;continue}let e=this.#e.config.forwardMap[a];if(e!==void 0){i.push(a),a=e,o++;continue}return{target:a,chain:i}}throw Error(`forwardTo exceeds maximum depth of 100`)}};exports.RoutesNamespace=l;
|
|
2
2
|
//# sourceMappingURL=RoutesNamespace.js.map
|