@real-router/core 0.84.0 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/{Router-CUtHMhRV.js → Router-BOyV6_me.js} +2 -2
- package/dist/cjs/{Router-CUtHMhRV.js.map → Router-BOyV6_me.js.map} +1 -1
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/esm/Router-A9Vko7z1.mjs +2 -0
- package/dist/esm/{Router-sWkNlsWW.mjs.map → Router-A9Vko7z1.mjs.map} +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/index.mjs +1 -1
- package/package.json +2 -2
- package/dist/cjs/Router.js +0 -2
- package/dist/cjs/Router.js.map +0 -1
- package/dist/cjs/RouterError.js +0 -2
- package/dist/cjs/RouterError.js.map +0 -1
- package/dist/cjs/api/cloneRouter.js +0 -2
- package/dist/cjs/api/cloneRouter.js.map +0 -1
- package/dist/cjs/api/getDependenciesApi.js +0 -2
- package/dist/cjs/api/getDependenciesApi.js.map +0 -1
- package/dist/cjs/api/getLifecycleApi.js +0 -2
- package/dist/cjs/api/getLifecycleApi.js.map +0 -1
- package/dist/cjs/api/getPluginApi.js +0 -2
- package/dist/cjs/api/getPluginApi.js.map +0 -1
- package/dist/cjs/api/getRoutesApi.js +0 -2
- package/dist/cjs/api/getRoutesApi.js.map +0 -1
- package/dist/cjs/api/helpers.js +0 -2
- package/dist/cjs/api/helpers.js.map +0 -1
- package/dist/cjs/channels/defaults.js +0 -2
- package/dist/cjs/channels/defaults.js.map +0 -1
- package/dist/cjs/channels/guard.js +0 -2
- package/dist/cjs/channels/guard.js.map +0 -1
- package/dist/cjs/channels/modeGate.js +0 -2
- package/dist/cjs/channels/modeGate.js.map +0 -1
- package/dist/cjs/constants.js +0 -2
- package/dist/cjs/constants.js.map +0 -1
- package/dist/cjs/createRouter.js +0 -2
- package/dist/cjs/createRouter.js.map +0 -1
- package/dist/cjs/engine/builder/buildTree.js +0 -2
- package/dist/cjs/engine/builder/buildTree.js.map +0 -1
- package/dist/cjs/engine/builder/computeCaches.js +0 -2
- package/dist/cjs/engine/builder/computeCaches.js.map +0 -1
- package/dist/cjs/engine/builder/createRouteTree.js +0 -2
- package/dist/cjs/engine/builder/createRouteTree.js.map +0 -1
- package/dist/cjs/engine/createMatcher.js +0 -2
- package/dist/cjs/engine/createMatcher.js.map +0 -1
- package/dist/cjs/engine/operations/routeTreeToDefinitions.js +0 -2
- package/dist/cjs/engine/operations/routeTreeToDefinitions.js.map +0 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js +0 -2
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +0 -1
- package/dist/cjs/engine/path-matcher/buildParamMeta.js +0 -2
- package/dist/cjs/engine/path-matcher/buildParamMeta.js.map +0 -1
- package/dist/cjs/engine/path-matcher/encoding.js +0 -2
- package/dist/cjs/engine/path-matcher/encoding.js.map +0 -1
- package/dist/cjs/engine/path-matcher/parseSegment.js +0 -2
- package/dist/cjs/engine/path-matcher/parseSegment.js.map +0 -1
- package/dist/cjs/engine/path-matcher/pathUtils.js +0 -2
- package/dist/cjs/engine/path-matcher/pathUtils.js.map +0 -1
- package/dist/cjs/engine/path-matcher/percentEncoding.js +0 -2
- package/dist/cjs/engine/path-matcher/percentEncoding.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/buildParts.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/context.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/context.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/errors.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/errors.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/index.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/index.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/trie.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/trie.js.map +0 -1
- package/dist/cjs/engine/path-matcher/registration/trieNodes.js +0 -2
- package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +0 -1
- package/dist/cjs/engine/search-params/decode.js +0 -2
- package/dist/cjs/engine/search-params/decode.js.map +0 -1
- package/dist/cjs/engine/search-params/encode.js +0 -2
- package/dist/cjs/engine/search-params/encode.js.map +0 -1
- package/dist/cjs/engine/search-params/searchParams.js +0 -2
- package/dist/cjs/engine/search-params/searchParams.js.map +0 -1
- package/dist/cjs/engine/search-params/strategies/array.js +0 -2
- package/dist/cjs/engine/search-params/strategies/array.js.map +0 -1
- package/dist/cjs/engine/search-params/strategies/boolean.js +0 -2
- package/dist/cjs/engine/search-params/strategies/boolean.js.map +0 -1
- package/dist/cjs/engine/search-params/strategies/index.js +0 -2
- package/dist/cjs/engine/search-params/strategies/index.js.map +0 -1
- package/dist/cjs/engine/search-params/strategies/null.js +0 -2
- package/dist/cjs/engine/search-params/strategies/null.js.map +0 -1
- package/dist/cjs/engine/search-params/strategies/number.js +0 -2
- package/dist/cjs/engine/search-params/strategies/number.js.map +0 -1
- package/dist/cjs/engine/search-params/utils.js +0 -2
- package/dist/cjs/engine/search-params/utils.js.map +0 -1
- package/dist/cjs/engine/validation/route-batch.js +0 -2
- package/dist/cjs/engine/validation/route-batch.js.map +0 -1
- package/dist/cjs/engine/validation/routes.js +0 -2
- package/dist/cjs/engine/validation/routes.js.map +0 -1
- package/dist/cjs/getNavigator.js +0 -2
- package/dist/cjs/getNavigator.js.map +0 -1
- package/dist/cjs/guards.js +0 -2
- package/dist/cjs/guards.js.map +0 -1
- package/dist/cjs/helpers.js +0 -2
- package/dist/cjs/helpers.js.map +0 -1
- package/dist/cjs/internals.js +0 -2
- package/dist/cjs/internals.js.map +0 -1
- package/dist/cjs/limits.js +0 -2
- package/dist/cjs/limits.js.map +0 -1
- package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js +0 -2
- package/dist/cjs/namespaces/DependenciesNamespace/dependenciesStore.js.map +0 -1
- package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js +0 -2
- package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/InFlightNavigation.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/InFlightNavigation.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/constants.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/constants.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/transition/completeTransition.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/transition/errorHandling.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/transition/executeNavigation.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/transition/executeNavigation.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/transition/guardPhase.js.map +0 -1
- package/dist/cjs/namespaces/NavigationNamespace/transition/navigateToNotFound.js +0 -2
- package/dist/cjs/namespaces/NavigationNamespace/transition/navigateToNotFound.js.map +0 -1
- package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js +0 -2
- package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +0 -1
- package/dist/cjs/namespaces/OptionsNamespace/constants.js +0 -2
- package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +0 -1
- package/dist/cjs/namespaces/OptionsNamespace/helpers.js +0 -2
- package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +0 -1
- package/dist/cjs/namespaces/OptionsNamespace/validators.js +0 -2
- package/dist/cjs/namespaces/OptionsNamespace/validators.js.map +0 -1
- package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js +0 -2
- package/dist/cjs/namespaces/PluginsNamespace/PluginsNamespace.js.map +0 -1
- package/dist/cjs/namespaces/PluginsNamespace/constants.js +0 -2
- package/dist/cjs/namespaces/PluginsNamespace/constants.js.map +0 -1
- package/dist/cjs/namespaces/PluginsNamespace/validators.js +0 -2
- package/dist/cjs/namespaces/PluginsNamespace/validators.js.map +0 -1
- package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js +0 -2
- package/dist/cjs/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.js.map +0 -1
- package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js +0 -2
- package/dist/cjs/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.js.map +0 -1
- package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js +0 -2
- package/dist/cjs/namespaces/RouterLifecycleNamespace/constants.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/constants.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/constants.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/forwardChain.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/routeGuards.js.map +0 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +0 -2
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +0 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +0 -2
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +0 -1
- package/dist/cjs/pipeline/buildURL.js +0 -2
- package/dist/cjs/pipeline/buildURL.js.map +0 -1
- package/dist/cjs/pipeline/canonicalize.js +0 -2
- package/dist/cjs/pipeline/canonicalize.js.map +0 -1
- package/dist/cjs/pipeline/materialize.js +0 -2
- package/dist/cjs/pipeline/materialize.js.map +0 -1
- package/dist/cjs/routerFSM.js +0 -2
- package/dist/cjs/routerFSM.js.map +0 -1
- package/dist/cjs/transitionPath.js +0 -2
- package/dist/cjs/transitionPath.js.map +0 -1
- package/dist/cjs/utils/event-emitter/EventEmitter.js +0 -2
- package/dist/cjs/utils/event-emitter/EventEmitter.js.map +0 -1
- package/dist/cjs/utils/fsm/fsm.js +0 -2
- package/dist/cjs/utils/fsm/fsm.js.map +0 -1
- package/dist/cjs/utils/logger/RouterLogger.js +0 -2
- package/dist/cjs/utils/logger/RouterLogger.js.map +0 -1
- package/dist/cjs/utils/logger/constants.js +0 -2
- package/dist/cjs/utils/logger/constants.js.map +0 -1
- package/dist/cjs/wiring/wireNamespaces.js +0 -2
- package/dist/cjs/wiring/wireNamespaces.js.map +0 -1
- package/dist/esm/Router-sWkNlsWW.mjs +0 -2
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
1
|
-
const e=require("../../constants.js"),t=require("../../RouterError.js");var n=class{#e=null;#t=0;begin(){return++this.#t}isCurrent(e){return this.#t===e}adopt(e){this.#e=e}release(e,t,n){t&&e.abort(n),this.#e===e&&(this.#e=null)}abort(n){this.#e?.abort(n??new t.RouterError(e.errorCodes.TRANSITION_CANCELLED)),this.#e=null}};exports.InFlightNavigation=n;
|
|
2
|
-
//# sourceMappingURL=InFlightNavigation.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InFlightNavigation.js","names":["#id","#controller","RouterError","errorCodes"],"sources":["../../../../src/namespaces/NavigationNamespace/InFlightNavigation.ts"],"sourcesContent":["import { errorCodes } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\n\n/**\n * The lifecycle of ONE in-flight navigation: its supersession token and its\n * `AbortController`.\n *\n * These two fields were the namespace's only sub-domain with a small owner set —\n * four members touched the controller, three the token, against thirteen that\n * need the DI bag. Naming that sub-domain is what lets the orchestration around\n * it become functions over `(deps, plan)`: the behaviour has no other mutable\n * state to drag along (#1607).\n *\n * **One instance per ROUTER, not per navigation.** The token is a counter and\n * the controller is a single slot, so nothing here allocates on the hot path.\n *\n * **The controller is adopted, never manufactured.** A `take()` that created one\n * would allocate on the arcs that have nothing to hand a signal to — an external\n * `opts.signal` or a pre-commit listener makes a navigation *suspendable*\n * without making it need a controller — and that is precisely the regression\n * Step 1b of #1588 refused the `#handleNoGuardsLeave` fold for, by measurement.\n * The two creation sites keep their own conditions; this object only tracks\n * WHICH controller is current. Pinned by `controller-allocation.test.ts`.\n */\nexport class InFlightNavigation {\n #controller: AbortController | null = null;\n #id = 0;\n\n /**\n * Reserve the token for a navigation that is starting, superseding whatever\n * held it before. Identity is all that matters — the value is never ordered.\n */\n begin(): number {\n // Stryker disable next-line UpdateOperator: equivalent — the token is only ever compared by identity (`isCurrent`) to detect supersession; uniqueness per navigation is all that matters, so `--` (decreasing ids) is indistinguishable from `++`.\n return ++this.#id;\n }\n\n /** Is `id` still the navigation in flight, or has a newer one taken over? */\n isCurrent(id: number): boolean {\n return this.#id === id;\n }\n\n /**\n * Track a controller the CALLER created as the current navigation's. Done\n * before any listener runs, so a reentrant `navigate()` / `stop()` /\n * `dispose()` from a synchronous listener aborts THIS navigation's signal\n * (#722).\n */\n adopt(controller: AbortController): void {\n this.#controller = controller;\n }\n\n /**\n * Release a navigation's controller. The same `controller.signal` is handed to\n * `subscribeLeave` listeners, so it must abort **only** when the navigation is\n * cancelled or errors — never on success (#722). On the success path pass\n * `cancelled = false`: the reference is dropped without aborting, so a listener\n * that captured the signal still sees `aborted === false`.\n *\n * On the failure/cancellation path (`cancelled = true`) pass the originating\n * `reason` so `signal.reason` carries router/error context (a `RouterError`,\n * or the value a sync leave listener threw) — consistent with the cancellation\n * abort `RouterError(TRANSITION_CANCELLED)`, not a generic `AbortError` (#943).\n * `abort()` is idempotent: a controller already aborted by a superseding\n * navigation keeps its first (also-meaningful) reason.\n */\n release(\n controller: AbortController,\n cancelled: boolean,\n reason?: unknown,\n ): void {\n if (cancelled) {\n controller.abort(reason);\n }\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,BlockStatement: equivalent — controller identity-guard; cleanup correctness is enforced by the supersede path + the token/isCurrent checks. Full suite stays green with `=== → !==` (nulls the wrong controller) and with the body removed (ref never nulled), so no mutant here is observable.\n if (this.#controller === controller) {\n this.#controller = null;\n }\n }\n\n /**\n * Abort and release whatever is in flight (waking a parked async pipeline via\n * its `onInternalAbort`). This is the **effect** of the FSM `CANCEL` action\n * (`handleCancel` → injected `deps.abortCurrentController`), not something\n * cancellation sources call directly — so \"FSM `CANCEL` ⟹ controller aborted\"\n * holds in one place (RFC navigation-cancellation-unification §5). `reason`\n * (e.g. an external `opts.signal`'s reason, #943) becomes the controller's\n * `signal.reason`; defaults to `TRANSITION_CANCELLED`.\n */\n abort(reason?: unknown): void {\n this.#controller?.abort(\n reason ?? new RouterError(errorCodes.TRANSITION_CANCELLED),\n );\n this.#controller = null;\n }\n}\n"],"mappings":"wEAwBA,IAAa,EAAb,KAAgC,CAC9B,GAAsC,KACtC,GAAM,EAMN,OAAgB,CAEd,MAAO,EAAE,KAAKA,EAChB,CAGA,UAAU,EAAqB,CAC7B,OAAO,KAAKA,KAAQ,CACtB,CAQA,MAAM,EAAmC,CACvC,KAAKC,GAAc,CACrB,CAgBA,QACE,EACA,EACA,EACM,CACF,GACF,EAAW,MAAM,CAAM,EAIrB,KAAKA,KAAgB,IACvB,KAAKA,GAAc,KAEvB,CAWA,MAAM,EAAwB,CAC5B,KAAKA,IAAa,MAChB,GAAU,IAAIC,EAAAA,YAAYC,EAAAA,WAAW,oBAAoB,CAC3D,EACA,KAAKF,GAAc,IACrB,CACF"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../../channels/guard.js"),t=require("../../constants.js"),n=require("../../RouterError.js"),r=require("./constants.js"),i=require("./InFlightNavigation.js"),a=require("./transition/executeNavigation.js"),o=require("./transition/navigateToNotFound.js");var s=class{#e;#t;#n=new i.InFlightNavigation;#r=0;setDependencies(e){this.#e=e,this.#t=t=>{r.isExpectedRejection(t)||e.logger.error(`router.navigate`,`Unexpected navigation error`,t)}}navigate(e,t,n,r){return this.#i(this.#a(e,t,n,r))}navigateToState(e,t){return this.#i(this.#o(e,t))}navigateToDefault(e){return this.#i(this.#s(e))}navigateToNotFound(e){return o.navigateToNotFound(this.#e,e)}abortCurrentController(e){this.#n.abort(e)}isPreparing(){return this.#r>0}#i(e){return e instanceof Promise&&!r.PRE_SUPPRESSED.has(e)&&e.catch(this.#t),e}#a(e,t,n,i){let o=this.#e;if(!o.canNavigate())return r.CACHED_NOT_STARTED_REJECTION;let s;this.#r++;try{s=o.buildNavigateState(e,t,n)}catch(e){return Promise.reject(e)}finally{this.#r--}return s?a.executeNavigation(this.#e,this.#n,s,i):(o.emitTransitionError(void 0,o.getState(),r.CACHED_ROUTE_NOT_FOUND_ERROR),r.CACHED_ROUTE_NOT_FOUND_REJECTION)}#o(i,o){let s=this.#e;if(!s.canNavigate())return r.CACHED_NOT_STARTED_REJECTION;if(i.name!==t.constants.UNKNOWN_ROUTE&&!s.hasRoute(i.name)){let e=new n.RouterError(t.errorCodes.ROUTE_NOT_FOUND,{routeName:i.name});return s.emitTransitionError(void 0,s.getState(),e),Promise.reject(e)}let c=e.findMisChanneledKey(i.params,s.getQueryParams(i.name));if(c!==void 0){let r=new n.RouterError(t.errorCodes.WRONG_CHANNEL,{routeName:i.name,message:`[router.navigateToState] ${e.misChanneledKeyMessage(i.name,c,"`state.params`")}`});return s.emitTransitionError(void 0,s.getState(),r),Promise.reject(r)}let l={name:i.name,params:i.params,search:i.search,path:i.path,context:{...i.context}};return a.executeNavigation(this.#e,this.#n,l,o)}#s(e){let r=this.#e;if(!r.getOptions().defaultRoute)return Promise.reject(new n.RouterError(t.errorCodes.ROUTE_NOT_FOUND,{routeName:`defaultRoute not configured`}));let i,a,o;this.#r++;try{({route:i,params:a,search:o}=r.resolveDefault())}catch(e){return Promise.reject(e)}finally{this.#r--}return i?this.#a(i,a,o,e):Promise.reject(new n.RouterError(t.errorCodes.ROUTE_NOT_FOUND,{routeName:`defaultRoute resolved to empty`}))}};exports.NavigationNamespace=s;
|
|
2
|
-
//# sourceMappingURL=NavigationNamespace.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationNamespace.js","names":["#inFlight","InFlightNavigation","#deps","#onSuppressed","isExpectedRejection","#settle","#navigate","#navigateToState","#navigateToDefault","navigateToNotFound","#preparingDepth","PRE_SUPPRESSED","CACHED_NOT_STARTED_REJECTION","CACHED_ROUTE_NOT_FOUND_ERROR","CACHED_ROUTE_NOT_FOUND_REJECTION","executeNavigation","constants","RouterError","errorCodes","findMisChanneledKey","misChanneledKeyMessage"],"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 isExpectedRejection,\n PRE_SUPPRESSED,\n} from \"./constants\";\nimport { InFlightNavigation } from \"./InFlightNavigation\";\nimport { executeNavigation } from \"./transition/executeNavigation\";\nimport { navigateToNotFound } from \"./transition/navigateToNotFound\";\nimport { findMisChanneledKey, misChanneledKeyMessage } from \"../../channels\";\nimport { errorCodes, constants } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\n\nimport type { NavigationDependencies } from \"./types\";\nimport type {\n NavigationOptions,\n Params,\n SearchParams,\n State,\n} from \"../../types\";\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 #deps!: NavigationDependencies;\n #onSuppressed!: (error: unknown) => void;\n // The controller + supersession token of the navigation in flight, owned as\n // one thing (#1607). Built here, one per router — never per navigation.\n readonly #inFlight = new InFlightNavigation();\n // Depth of the PRE-START window — see `#prepare`. Interim form of what\n // becomes a machine state in the state-ownership plan (§10, phase 4).\n #preparingDepth = 0;\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n setDependencies(deps: NavigationDependencies): void {\n this.#deps = deps;\n // Built once here rather than per call: the closure needs THIS router's\n // logger, so it cannot be static, and `setDependencies` is a pure\n // assignment that runs exactly once at wiring (#1331).\n this.#onSuppressed = (error: unknown): void => {\n if (isExpectedRejection(error)) {\n return;\n }\n\n deps.logger.error(\n \"router.navigate\",\n \"Unexpected navigation error\",\n error,\n );\n };\n }\n\n // =========================================================================\n // Instance methods\n // =========================================================================\n\n navigate(\n name: string,\n params: Params,\n search: SearchParams | undefined,\n opts: NavigationOptions,\n ): State | Promise<State> {\n return this.#settle(this.#navigate(name, params, search, 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(\n state: State,\n opts: NavigationOptions,\n ): State | Promise<State> {\n return this.#settle(this.#navigateToState(state, opts));\n }\n\n navigateToDefault(opts: NavigationOptions): State | Promise<State> {\n return this.#settle(this.#navigateToDefault(opts));\n }\n\n navigateToNotFound(path: string): State {\n return navigateToNotFound(this.#deps, path);\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.#inFlight.abort(reason);\n }\n\n /**\n * Is a navigation between its entry point and its announce — the PRE-START\n * window (#1610)?\n *\n * Two stretches raise `#preparingDepth`, and both are application code running\n * before `TRANSITION_START`: `buildNavigateState` (the `forwardState` and\n * `buildPath` interceptor chains plus the route's own codecs) and\n * `resolveDefault` (`defaultRoute` / `defaultParams` / `defaultSearch` may\n * each be a dependency-resolved callback). That is precisely why the\n * reentrancy ban did not reach them: `Router.#assertNotReentrant` keys off the\n * emitter's dispatch depth, and there has been no emit yet — so a nested\n * `navigate()` ran to completion here, committing a state the outer navigation\n * overwrote a tick later and leaving the outer transition to report departing\n * from wherever the nested one had stopped.\n *\n * Those two calls are the whole window. Everything after them and before the\n * announce is core's own code, except the `CANCEL` emit inside\n * `abortPreviousNavigation` — which the dispatch depth already covers.\n *\n * A DEPTH rather than a boolean, raised inline rather than through a wrapper\n * taking a callback: the wrapper allocated a closure per navigation on the\n * #307 hot path, and the depth makes the two sites' relationship a\n * non-question (they are sequential today, and nesting would be safe anyway).\n * Each site lowers it in a `finally`, because every exit has to — the early\n * refusals live in this same window (`ROUTE_NOT_FOUND` when no state comes\n * back, `WRONG_CHANNEL` from the always-on channel guard, any throw from user\n * code), and a marker left raised deadlocks the router against its own next\n * call.\n *\n * Read by `Router.#assertNotReentrant` alongside `EventBus.isProcessing()`;\n * between them they span every window in which application code runs inside a\n * navigation core has not finished setting up. A GUARD is deliberately\n * neither: it runs after the announce, so the guard-redirect stays a supersede.\n *\n * ⚠ Interim form. It is absorbed by pre-start becoming a STATE of the machine\n * — then a nested navigation is an ordinary supersede for the table and needs\n * no marker at all. See `packages/core/.claude/fsm-as-state-owner-2026-07-31.md`\n * §8 (why that is 10b and not 10a) and the plan in §10, phase 4.\n */\n isPreparing(): boolean {\n return this.#preparingDepth > 0;\n }\n\n /**\n * The producer's own fire-and-forget guarantee (#721): whatever leaves a\n * public method here is safe to drop on the floor.\n *\n * ONE checkpoint per public method, deliberately — not a `.catch()` at each of\n * the six return sites. A forgotten site is invisible until it leaks, which is\n * the bug this replaces; a single choke point cannot be forgotten. And the\n * discriminator is the returned VALUE's identity, not a flag the facade reads\n * afterwards: `PRE_SUPPRESSED` promises already carry a module-load handler, so\n * re-suppressing them buys nothing and costs a derived promise.\n *\n * A synchronously-returned `State` needs nothing at all — there is no rejection\n * to suppress, which is precisely what makes `lastSyncResolved` unnecessary:\n * the TYPE now carries what the flag used to announce.\n */\n #settle(result: State | Promise<State>): State | Promise<State> {\n if (result instanceof Promise && !PRE_SUPPRESSED.has(result)) {\n result.catch(this.#onSuppressed);\n }\n\n return result;\n }\n\n /**\n * `navigate`'s body, minus the fire-and-forget checkpoint.\n *\n * Split out because `navigateToDefault` delegates HERE, not to the public\n * method: routing it through `navigate` would run `#settle` twice per default\n * navigation and attach a second, pointless `.catch()` — an extra derived\n * promise on a path that has none today.\n */\n #navigate(\n name: string,\n params: Params,\n search: SearchParams | undefined,\n opts: NavigationOptions,\n ): State | Promise<State> {\n const deps = this.#deps;\n\n // Fast-path sync rejections: cached error + cached Promise.reject.\n // No allocations, no throw/catch overhead; `#settle` recognises the\n // singleton by identity and skips its `.catch()`.\n if (!deps.canNavigate()) {\n return CACHED_NOT_STARTED_REJECTION;\n }\n\n let toState: State | undefined;\n\n // PRE-START window (#1610) — user code runs in here, before any emit. See\n // `isPreparing`. The `finally` is load-bearing: the early refusals exit\n // through it too.\n this.#preparingDepth++;\n\n try {\n toState = deps.buildNavigateState(name, params, search);\n } catch (error) {\n // No `v8 ignore` here any more: the comment that used to sit above this\n // line claimed the path was \"reachable only via validator-driven throws\n // … covered in @real-router/validation-plugin's suite, not in core\", and\n // that stopped being true when the always-on channel guard (#1572) began\n // throwing from core's own `buildNavigateState`. Measured: with the ignore\n // removed, core's suite still reports 100% — it was masking a live region.\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 } finally {\n this.#preparingDepth--;\n }\n\n if (!toState) {\n deps.emitTransitionError(\n undefined,\n deps.getState(),\n CACHED_ROUTE_NOT_FOUND_ERROR,\n );\n\n return CACHED_ROUTE_NOT_FOUND_REJECTION;\n }\n\n return executeNavigation(this.#deps, this.#inFlight, toState, opts);\n }\n\n #navigateToState(\n state: State,\n opts: NavigationOptions,\n ): State | Promise<State> {\n const deps = this.#deps;\n\n if (!deps.canNavigate()) {\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 // A FRESH reject (it carries `routeName`), so it is deliberately NOT in\n // `PRE_SUPPRESSED` and `#settle` will attach its `.catch()`. Adding it to\n // that set — the modern shape of the mistake that caused #721 — would skip\n // suppression on a promise nobody else handles and leak it.\n return Promise.reject(err);\n }\n\n // Channel guard, position P3 (#1572). `navigateToState` is the ONE producer\n // that takes a ready-made `State` instead of a `params` argument, so the\n // predicate reads `state.params ∩ queryNames(state.name)`. What it commits\n // becomes `getState()`, so a pre-M2 layout would be silent corruption: the\n // key sits in `state.params` and never reaches `state.path`.\n //\n // Costs nothing on healthy flows — a state produced by core (`matchPath`,\n // `makeState`) is channel-correct by construction, so the predicate is\n // empty on every popstate / memory-restore / SSR-hydration commit. `start()`\n // commits THROUGH here (`RouterLifecycleNamespace`), which is why the guard\n // lives in the namespace rather than on the plugin-API door.\n //\n // Rejects rather than throwing, mirroring the ROUTE_NOT_FOUND guard above:\n // this method returns `Promise<State>` and its URL-plugin callers invoke it\n // from popstate handlers, where a new synchronous throw would be a change\n // of failure shape rather than a new failure.\n const misChanneled = findMisChanneledKey(\n state.params,\n deps.getQueryParams(state.name),\n );\n\n if (misChanneled !== undefined) {\n const err = new RouterError(errorCodes.WRONG_CHANNEL, {\n routeName: state.name,\n message: `[router.navigateToState] ${misChanneledKeyMessage(\n state.name,\n misChanneled,\n \"`state.params`\",\n )}`,\n });\n\n deps.emitTransitionError(undefined, deps.getState(), err);\n\n return Promise.reject(err);\n }\n\n // States from `matchPath` are deeply frozen (`freezeStateShell`).\n // `completeTransition` mutates `toState.transition` and `context` is\n // intentionally extensible for plugin claim writes, so we hand the\n // pipeline a writable shell — same shape `makeState(skipFreeze=true)`\n // produces. `params` stays referentially shared (already frozen).\n // `transition` is omitted so completeTransition can assign it.\n const writableState = {\n name: state.name,\n params: state.params,\n // Carry the query channel through the writable shell (RFC-4 M2 / #1548) —\n // without this, start()'s navigateToState(matchPath(...)) would drop the\n // matched query from the committed state.\n search: state.search,\n path: state.path,\n context: { ...state.context },\n } as State;\n\n // No route-meta to carry any more (RFC-4 M2 / #1548): ownership is read from\n // the live matcher by `state.name` (`getTransitionPath`'s `getMeta`), not\n // from a per-State WeakMap. The former #1170 carry — which existed only so a\n // matchPath-derived writable shell stayed non-meta-less across consecutive\n // popstate navs — is obsolete: any state whose name is in the tree takes the\n // STANDARD PATH regardless of object identity.\n\n return executeNavigation(this.#deps, this.#inFlight, writableState, opts);\n }\n\n #navigateToDefault(opts: NavigationOptions): State | Promise<State> {\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 // PRE-START window too, and for the same reason (#1610): `defaultRoute` /\n // `defaultParams` / `defaultSearch` may each be a dependency-resolved\n // CALLBACK, so this runs user code before there is even a route name to\n // navigate to. Sequential with `#navigate`'s window below, never nested.\n this.#preparingDepth++;\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 } finally {\n this.#preparingDepth--;\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 //\n // Delegates to the PRIVATE core: the public `navigate` would run `#settle`\n // here and again in this method's own wrapper, costing a second `.catch()`\n // per default navigation.\n return this.#navigate(route, params, search, opts);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../../constants.js"),t=require("../../RouterError.js"),n=new t.RouterError(e.errorCodes.ROUTER_NOT_STARTED),r=new t.RouterError(e.errorCodes.ROUTE_NOT_FOUND),i=new t.RouterError(e.errorCodes.SAME_STATES),a=Promise.reject(n),o=Promise.reject(r),s=Promise.reject(i);a.catch(()=>{}),o.catch(()=>{}),s.catch(()=>{});const c=new Set([e.errorCodes.SAME_STATES,e.errorCodes.TRANSITION_CANCELLED,e.errorCodes.ROUTER_NOT_STARTED,e.errorCodes.ROUTE_NOT_FOUND,e.errorCodes.CANNOT_ACTIVATE,e.errorCodes.CANNOT_DEACTIVATE,e.errorCodes.ROUTER_ALREADY_STARTED]);function l(e){return e instanceof t.RouterError&&c.has(e.code)}const u=new Set([a,o,s]);exports.CACHED_NOT_STARTED_ERROR=n,exports.CACHED_NOT_STARTED_REJECTION=a,exports.CACHED_ROUTE_NOT_FOUND_ERROR=r,exports.CACHED_ROUTE_NOT_FOUND_REJECTION=o,exports.CACHED_SAME_STATES_ERROR=i,exports.CACHED_SAME_STATES_REJECTION=s,exports.PRE_SUPPRESSED=u,exports.SUPPRESSED_ERROR_CODES=c,exports.isExpectedRejection=l;
|
|
2
|
-
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":["RouterError","errorCodes"],"sources":["../../../../src/namespaces/NavigationNamespace/constants.ts"],"sourcesContent":["// packages/core/src/namespaces/NavigationNamespace/constants.ts\n\nimport { errorCodes } from \"../../constants\";\nimport { RouterError } from \"../../RouterError\";\n\nimport type { State } from \"../../types\";\n\n// =============================================================================\n// Cached Errors & Rejected Promises (Performance Optimization)\n// =============================================================================\n// Pre-create error instances and rejected promises for sync error paths\n// in navigate(). Eliminates per-call allocations:\n// - new RouterError() — object + stack trace capture (~500ns-2μs)\n// - Promise.reject() — promise allocation\n// - .catch(handler) — derived promise from suppression\n//\n// Trade-off: All error instances share the same stack trace (points here).\n// This is acceptable because:\n// 1. These errors indicate expected conditions, not internal bugs\n// 2. Error code and message are sufficient for debugging\n// 3. The producer skips .catch() suppression for these promises (zero alloc) —\n// see PRE_SUPPRESSED at the bottom of this file\n// =============================================================================\n\nexport const CACHED_NOT_STARTED_ERROR = new RouterError(\n errorCodes.ROUTER_NOT_STARTED,\n);\n\nexport const CACHED_ROUTE_NOT_FOUND_ERROR = new RouterError(\n errorCodes.ROUTE_NOT_FOUND,\n);\n\nexport const CACHED_SAME_STATES_ERROR = new RouterError(errorCodes.SAME_STATES);\n\n// Pre-suppressed rejected promises — .catch() at module load prevents\n// unhandled rejection warnings. `NavigationNamespace.#settle` skips additional\n// .catch() calls by recognising these three by IDENTITY (`PRE_SUPPRESSED`\n// below), so no derived promise is allocated. The retired `lastSyncRejected`\n// flag used to carry that signal to the facade instead.\nexport const CACHED_NOT_STARTED_REJECTION: Promise<State> = Promise.reject(\n CACHED_NOT_STARTED_ERROR,\n);\n\nexport const CACHED_ROUTE_NOT_FOUND_REJECTION: Promise<State> = Promise.reject(\n CACHED_ROUTE_NOT_FOUND_ERROR,\n);\n\nexport const CACHED_SAME_STATES_REJECTION: Promise<State> = Promise.reject(\n CACHED_SAME_STATES_ERROR,\n);\n\n// Suppress once at module load — prevents unhandled rejection events.\n// Subsequent .catch() / await by user code still works correctly:\n// a rejected promise stays rejected forever, each .catch() creates\n// its own derived promise and fires its handler.\nCACHED_NOT_STARTED_REJECTION.catch(() => {}); // NOSONAR -- intentional suppression, not a promise chain\nCACHED_ROUTE_NOT_FOUND_REJECTION.catch(() => {}); // NOSONAR\nCACHED_SAME_STATES_REJECTION.catch(() => {}); // NOSONAR\n\n// =============================================================================\n// Fire-and-forget suppression policy (#721) — shared, not per-caller\n// =============================================================================\n\n/**\n * Rejection codes that are EXPECTED, caller-owned outcomes, not internal bugs.\n * The fire-and-forget safety net stays silent for them and lets an awaiting\n * caller see the rejection. `CANNOT_ACTIVATE` / `CANNOT_DEACTIVATE` belong here:\n * a guard blocking (or a plugin's guard-blocked `back()`/`forward()`) is a\n * normal result, so a call without `await` must not emit a spurious \"Unexpected\n * navigation error\".\n *\n * Most of them are navigation outcomes, but not all — `ROUTER_ALREADY_STARTED`\n * is a LIFECYCLE precondition (#1605), and it is the same kind of thing:\n * `start()` called twice says \"already done\", exactly as `SAME_STATES` does for\n * `navigate()`. Silence is the symmetric answer; logging it would report a\n * caller's own no-op as an internal fault. It costs nothing on the navigation\n * side — `navigate()` cannot produce that code.\n *\n * Lives here rather than moving into the namespace with navigate-suppression,\n * because it is genuinely SHARED: `Router.start()` classifies its own failures\n * by the same policy (`#onSuppressedStartError`), and start commits through\n * `navigateToState`, so its rejections are navigation rejections. One owner, two\n * readers — duplicating the classifier is the copy this refactor exists to\n * remove.\n */\nexport const SUPPRESSED_ERROR_CODES: ReadonlySet<string> = new Set([\n errorCodes.SAME_STATES,\n errorCodes.TRANSITION_CANCELLED,\n errorCodes.ROUTER_NOT_STARTED,\n errorCodes.ROUTE_NOT_FOUND,\n errorCodes.CANNOT_ACTIVATE,\n errorCodes.CANNOT_DEACTIVATE,\n errorCodes.ROUTER_ALREADY_STARTED,\n]);\n\n/** Module-level, so classifying allocates nothing per navigate()/start(). */\nexport function isExpectedRejection(error: unknown): boolean {\n return error instanceof RouterError && SUPPRESSED_ERROR_CODES.has(error.code);\n}\n\n/**\n * The three cached rejections ABOVE, by identity.\n *\n * They already carry a `.catch()` from module load, so they can never raise an\n * `unhandledRejection` — a second `.catch()` on them prevents nothing and only\n * allocates a derived promise (measured: ~40 ns, which is ~12.5% of a\n * SAME_STATES `navigate()`). This set is what lets the producer skip that work\n * without a mutable cross-layer flag.\n *\n * Identity, not a flag, because the two fail in OPPOSITE directions: a missed\n * identity costs 40 ns and nothing else, while a flag left stale-true skips\n * suppression on a LATER navigation and leaks the rejection — #721 exactly. The\n * mechanism is therefore fail-safe by construction: anything not recognised here\n * gets suppressed.\n */\nexport const PRE_SUPPRESSED: ReadonlySet<unknown> = new Set([\n CACHED_NOT_STARTED_REJECTION,\n CACHED_ROUTE_NOT_FOUND_REJECTION,\n CACHED_SAME_STATES_REJECTION,\n]);\n"],"mappings":"wEAwBa,EAA2B,IAAIA,EAAAA,YAC1CC,EAAAA,WAAW,kBACb,EAEa,EAA+B,IAAID,EAAAA,YAC9CC,EAAAA,WAAW,eACb,EAEa,EAA2B,IAAID,EAAAA,YAAYC,EAAAA,WAAW,WAAW,EAOjE,EAA+C,QAAQ,OAClE,CACF,EAEa,EAAmD,QAAQ,OACtE,CACF,EAEa,EAA+C,QAAQ,OAClE,CACF,EAMA,EAA6B,UAAY,CAAC,CAAC,EAC3C,EAAiC,UAAY,CAAC,CAAC,EAC/C,EAA6B,UAAY,CAAC,CAAC,EA4B3C,MAAa,EAA8C,IAAI,IAAI,CACjEA,EAAAA,WAAW,YACXA,EAAAA,WAAW,qBACXA,EAAAA,WAAW,mBACXA,EAAAA,WAAW,gBACXA,EAAAA,WAAW,gBACXA,EAAAA,WAAW,kBACXA,EAAAA,WAAW,sBACb,CAAC,EAGD,SAAgB,EAAoB,EAAyB,CAC3D,OAAO,aAAiBD,EAAAA,aAAe,EAAuB,IAAI,EAAM,IAAI,CAC9E,CAiBA,MAAa,EAAuC,IAAI,IAAI,CAC1D,EACA,EACA,CACF,CAAC"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../../../constants.js"),t=require("../../../RouterError.js");function n(e,t,n,r,i){Object.freeze(n),Object.freeze(r);let a={phase:`activating`,reason:`success`,segments:Object.freeze({deactivated:n,activated:r,intersection:i})};return e?.name!==void 0&&(a.from=e.name),t.reload!==void 0&&(a.reload=t.reload),t.replace!==void 0&&(a.replace=t.replace),t.redirected!==void 0&&(a.redirected=t.redirected),Object.freeze(a)}function r({signal:e,...t}){return t}function i(i,a){let{toState:o,fromState:s,opts:c,toDeactivate:l,toActivate:u,intersection:d}=a;if(o.name!==e.constants.UNKNOWN_ROUTE&&!i.hasRoute(o.name)){let n=new t.RouterError(e.errorCodes.ROUTE_NOT_FOUND,{routeName:o.name});throw i.sendTransitionFail(o,s,n),n}if(s){let n=!1;for(let e of l)u.includes(e)||!a.canDeactivateFunctions.has(e)||(i.clearCanDeactivate(e),n=!0);if(n&&!i.isTransitioning())throw new t.RouterError(e.errorCodes.TRANSITION_CANCELLED)}o.transition=n(s,c,l,u,d);let f=Object.freeze(o);i.setState(f);let p=c.signal===void 0?c:r(c);return i.sendTransitionDone(f,s,p),f}exports.completeTransition=i;
|
|
2
|
-
//# sourceMappingURL=completeTransition.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"completeTransition.js","names":["constants","RouterError","errorCodes"],"sources":["../../../../../src/namespaces/NavigationNamespace/transition/completeTransition.ts"],"sourcesContent":["import { errorCodes, constants } from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\n\nimport type { NavigationOptions, State, TransitionMeta } from \"../../../types\";\nimport type { NavigationDependencies, NavigationContext } from \"../types\";\n\ntype MutableTransitionMeta = {\n -readonly [K in keyof TransitionMeta]: TransitionMeta[K];\n};\n\nfunction buildTransitionMeta(\n fromState: State | undefined,\n opts: NavigationOptions,\n toDeactivate: string[],\n toActivate: string[],\n intersection: string,\n): TransitionMeta {\n Object.freeze(toDeactivate);\n Object.freeze(toActivate);\n\n const segments = Object.freeze({\n deactivated: toDeactivate,\n activated: toActivate,\n intersection,\n });\n\n const meta: MutableTransitionMeta = {\n phase: \"activating\",\n reason: \"success\",\n segments,\n };\n\n if (fromState?.name !== undefined) {\n meta.from = fromState.name;\n }\n\n if (opts.reload !== undefined) {\n meta.reload = opts.reload;\n }\n\n if (opts.replace !== undefined) {\n meta.replace = opts.replace;\n }\n\n if (opts.redirected !== undefined) {\n meta.redirected = opts.redirected;\n }\n\n return Object.freeze(meta);\n}\n\nfunction stripSignal({\n signal: _,\n ...rest\n}: NavigationOptions): NavigationOptions {\n return rest;\n}\n\nexport function completeTransition(\n deps: NavigationDependencies,\n nav: NavigationContext,\n): State {\n const { toState, fromState, opts, toDeactivate, toActivate, intersection } =\n nav;\n\n if (\n toState.name !== constants.UNKNOWN_ROUTE &&\n !deps.hasRoute(toState.name)\n ) {\n const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {\n routeName: toState.name,\n });\n\n deps.sendTransitionFail(toState, fromState, err);\n\n throw err;\n }\n\n if (fromState) {\n let cleared = false;\n\n for (const name of toDeactivate) {\n if (toActivate.includes(name) || !nav.canDeactivateFunctions.has(name)) {\n continue;\n }\n\n deps.clearCanDeactivate(name);\n cleared = true;\n }\n\n // The one place this function runs USER code before committing (#1611).\n // Clearing the external guard recompiles the slot from a surviving\n // DEFINITION factory, and that factory is the application's: a `dispose()`\n // or `stop()` from it terminated the router one statement before `setState`,\n // and the commit landed anyway — `navigate()` resolved while `COMPLETE` from\n // `DISPOSED`/`IDLE` was a table no-op, so no subscriber was ever told. The\n // #1169 commit-gate cannot cover it: it sits BEFORE this function, i.e. on\n // the far side of that user code, and is `suspendable`-gated while this\n // reproduces on the uncancellable `completeImmediate` arc.\n //\n // Gated on `cleared` so the question is asked only when the factory could\n // actually have run — a navigation that clears nothing pays a boolean, not\n // an `isTransitioning()` call, on a path #307 counts nanoseconds on. The\n // gate is a PERF gate and behaviourally equivalent (measured: removing it\n // leaves the whole suite green — nothing ran that could change liveness),\n // the same shape as `suspendable` gating the #1169 commit-gate.\n //\n // `isTransitioning()`, not `isActive()`: the question is \"is the machine\n // still inside a transition\", and it is strictly the better one here. It\n // catches the reported `dispose()` (DISPOSED) and `stop()` (IDLE), and for\n // free the factory that starts a NESTED navigation which runs to completion\n // — that leaves the FSM in READY, so `isActive()` would wave it through and\n // this commit would silently overwrite the nested one's. Measured: with\n // `isActive()` the nested case commits the outer state over the inner\n // result; with this, the inner result stands.\n //\n // ⚠ Interim form; absorbed when the commit is asked THROUGH the FSM right\n // before `setState` (RFC-10a §16.7 / §9.9, plan §10 phase 3). Two cells stay\n // open and are deliberately NOT patched here — both need a token this\n // function is not given: a nested navigation still IN FLIGHT (the FSM is in\n // ITS transition, so `isTransitioning()` is true for somebody else), and the\n // `replace()`-revalidation commit, which is a different commit path.\n if (cleared && !deps.isTransitioning()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n }\n\n (toState as { transition: TransitionMeta }).transition = buildTransitionMeta(\n fromState,\n opts,\n toDeactivate,\n toActivate,\n intersection,\n );\n\n const finalState = Object.freeze(toState);\n\n deps.setState(finalState);\n\n const transitionOpts = opts.signal === undefined ? opts : stripSignal(opts);\n\n deps.sendTransitionDone(finalState, fromState, transitionOpts);\n\n return finalState;\n}\n"],"mappings":"8EAUA,SAAS,EACP,EACA,EACA,EACA,EACA,EACgB,CAChB,OAAO,OAAO,CAAY,EAC1B,OAAO,OAAO,CAAU,EAQxB,IAAM,EAA8B,CAClC,MAAO,aACP,OAAQ,UACR,SATe,OAAO,OAAO,CAC7B,YAAa,EACb,UAAW,EACX,cACF,CAKS,CACT,EAkBA,OAhBI,GAAW,OAAS,IAAA,KACtB,EAAK,KAAO,EAAU,MAGpB,EAAK,SAAW,IAAA,KAClB,EAAK,OAAS,EAAK,QAGjB,EAAK,UAAY,IAAA,KACnB,EAAK,QAAU,EAAK,SAGlB,EAAK,aAAe,IAAA,KACtB,EAAK,WAAa,EAAK,YAGlB,OAAO,OAAO,CAAI,CAC3B,CAEA,SAAS,EAAY,CACnB,OAAQ,EACR,GAAG,GACoC,CACvC,OAAO,CACT,CAEA,SAAgB,EACd,EACA,EACO,CACP,GAAM,CAAE,UAAS,YAAW,OAAM,eAAc,aAAY,gBAC1D,EAEF,GACE,EAAQ,OAASA,EAAAA,UAAU,eAC3B,CAAC,EAAK,SAAS,EAAQ,IAAI,EAC3B,CACA,IAAM,EAAM,IAAIC,EAAAA,YAAYC,EAAAA,WAAW,gBAAiB,CACtD,UAAW,EAAQ,IACrB,CAAC,EAID,MAFA,EAAK,mBAAmB,EAAS,EAAW,CAAG,EAEzC,CACR,CAEA,GAAI,EAAW,CACb,IAAI,EAAU,GAEd,IAAK,IAAM,KAAQ,EACb,EAAW,SAAS,CAAI,GAAK,CAAC,EAAI,uBAAuB,IAAI,CAAI,IAIrE,EAAK,mBAAmB,CAAI,EAC5B,EAAU,IAmCZ,GAAI,GAAW,CAAC,EAAK,gBAAgB,EACnC,MAAM,IAAID,EAAAA,YAAYC,EAAAA,WAAW,oBAAoB,CAEzD,CAEA,EAA4C,WAAa,EACvD,EACA,EACA,EACA,EACA,CACF,EAEA,IAAM,EAAa,OAAO,OAAO,CAAO,EAExC,EAAK,SAAS,CAAU,EAExB,IAAM,EAAiB,EAAK,SAAW,IAAA,GAAY,EAAO,EAAY,CAAI,EAI1E,OAFA,EAAK,mBAAmB,EAAY,EAAW,CAAc,EAEtD,CACT"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../../../constants.js"),t=require("../../../RouterError.js");function n(n){return n instanceof t.RouterError&&n.code===e.errorCodes.TRANSITION_CANCELLED}function r(r){return n(r)?r:new t.RouterError(e.errorCodes.TRANSITION_CANCELLED,{reason:r})}function i(t,n,r,i){let a=n;a.code!==e.errorCodes.TRANSITION_CANCELLED&&a.code!==e.errorCodes.ROUTE_NOT_FOUND&&t.sendTransitionFail(r,i,a)}function a(r,i,a){if(r instanceof DOMException&&r.name===`AbortError`)throw new t.RouterError(e.errorCodes.TRANSITION_CANCELLED);if(n(r))throw r;o(r,i,a)}function o(e,n,r){throw e instanceof t.RouterError?(e.setCode(n),e):new t.RouterError(n,c(e,r))}const s=new Set([`code`,`segment`,`path`,`then`]);function c(e,t){let n={segment:t};if(e instanceof Error)return{...n,message:e.message,stack:e.stack,...`cause`in e&&e.cause!==void 0&&{cause:e.cause}};if(e&&typeof e==`object`){let t={};for(let[n,r]of Object.entries(e))s.has(n)||(t[n]=r);return{...n,...t}}return n}async function l(e,n,r){let i;try{i=await e}catch(e){a(e,n,r);return}if(!i)throw new t.RouterError(n,{segment:r})}exports.asCancellation=r,exports.handleGuardError=a,exports.resolveAsyncGuard=l,exports.rethrowAsRouterError=o,exports.routeTransitionError=i,exports.wrapSyncError=c;
|
|
2
|
-
//# sourceMappingURL=errorHandling.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errorHandling.js","names":["RouterError","errorCodes"],"sources":["../../../../../src/namespaces/NavigationNamespace/transition/errorHandling.ts"],"sourcesContent":["// packages/core/src/namespaces/NavigationNamespace/transition/errorHandling.ts\n\nimport { errorCodes } from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\n\nimport type { State } from \"../../../types\";\nimport type { NavigationDependencies } from \"../types\";\n\n/**\n * Is this thrown value already the router's quiet-cancel outcome?\n *\n * Asked by {@link handleGuardError} (a guard signalling a quiet cancel by\n * throwing the RouterError directly, #933) and by {@link asCancellation}, which\n * must not re-wrap one.\n */\nfunction isTransitionCancelled(error: unknown): boolean {\n return (\n error instanceof RouterError &&\n error.code === errorCodes.TRANSITION_CANCELLED\n );\n}\n\n/**\n * Restate a failure as the cancellation it actually was (#1609).\n *\n * Called by both failure arcs for a navigation that has LOST liveness — and\n * every way to lose it (superseded, aborted, the router stopped, the FSM already\n * out of the transition band) is a cancellation, one the navigation has already\n * emitted `TRANSITION_CANCEL` to announce. Whatever verdict its guard or\n * listener reached in the meantime is about a navigation nobody is waiting for:\n * carrying it let a `FAIL` land in the FSM under a navigation that was still\n * running, because {@link routeTransitionError} filters by error CODE and\n * `CANNOT_ACTIVATE` is not `TRANSITION_CANCELLED` — and it left the caller's\n * promise contradicting the event stream for the same navigation.\n *\n * The liveness question itself stays at the call sites: the two arcs answer it\n * from different facts (see `finishAsyncNavigation` and `handleNavigateError`).\n *\n * A value that ALREADY carries the code is returned untouched, so #1197's\n * canonicalized leave rejection keeps its `reason` (#943) and the cancellation\n * the resolve path throws is not wrapped in a second one.\n */\nexport function asCancellation(error: unknown): unknown {\n return isTransitionCancelled(error)\n ? error\n : new RouterError(errorCodes.TRANSITION_CANCELLED, { reason: error });\n}\n\nexport function routeTransitionError(\n deps: NavigationDependencies,\n error: unknown,\n toState: State,\n fromState: State | undefined,\n): void {\n const routerError = error as RouterError;\n\n if (\n routerError.code === errorCodes.TRANSITION_CANCELLED ||\n routerError.code === errorCodes.ROUTE_NOT_FOUND\n ) {\n return;\n }\n\n deps.sendTransitionFail(toState, fromState, routerError);\n}\n\nexport function handleGuardError(\n error: unknown,\n errorCode: string,\n segment: string,\n): never {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n // A guard can also signal a quiet cancel by throwing\n // RouterError(TRANSITION_CANCELLED) directly — the same intent as a thrown\n // AbortError. Preserve it as-is instead of letting rethrowAsRouterError\n // overwrite the code with CANNOT_ACTIVATE / CANNOT_DEACTIVATE: that code\n // drives the downstream suppression (routeTransitionError early-returns,\n // fire-and-forget stays silent), so re-coding would surface the intended\n // quiet cancel as a reported transition error (#933).\n if (isTransitionCancelled(error)) {\n throw error;\n }\n\n rethrowAsRouterError(error, errorCode, segment);\n}\n\n/**\n * Error metadata structure for transition errors.\n * Contains information extracted from caught exceptions.\n */\nexport interface SyncErrorMetadata {\n [key: string]: unknown;\n message?: string;\n stack?: string | undefined;\n cause?: unknown;\n segment?: string;\n}\n\n/**\n * Re-throws a caught error as a RouterError with the given error code.\n * If the error is already a RouterError, sets the code directly.\n * Otherwise wraps it with wrapSyncError metadata.\n */\nexport function rethrowAsRouterError(\n error: unknown,\n errorCode: string,\n segment: string,\n): never {\n if (error instanceof RouterError) {\n error.setCode(errorCode);\n\n throw error;\n }\n\n throw new RouterError(errorCode, wrapSyncError(error, segment));\n}\n\n// Own-enumerable keys that must never be copied from a thrown object onto the\n// RouterError metadata:\n// - `code` / `segment` / `path` are reserved — the RouterError constructor\n// throws a TypeError on them (#39).\n// - `then` would make the RouterError itself thenable, so a consumer that\n// awaits it (or passes it through Promise.resolve / returns it from an async\n// function) would have it assimilated as a Promise instead of treated as a\n// plain rejection reason (#947).\nconst reservedRouterErrorProps = new Set([\"code\", \"segment\", \"path\", \"then\"]);\n\n/**\n * Wraps a synchronously thrown value into structured error metadata.\n *\n * This helper extracts useful debugging information from various thrown values:\n * - Error instances: extracts message, stack, and cause (ES2022+)\n * - Plain objects: spreads properties into metadata\n * - Primitives (string, number, etc.): returns minimal metadata\n *\n * @param thrown - The value caught in a try-catch block\n * @param segment - Route segment name (for lifecycle hooks)\n * @returns Structured error metadata for RouterError\n */\nexport function wrapSyncError(\n thrown: unknown,\n segment: string,\n): SyncErrorMetadata {\n const base: SyncErrorMetadata = { segment };\n\n // Handle Error instances - extract all useful properties\n if (thrown instanceof Error) {\n return {\n ...base,\n message: thrown.message,\n stack: thrown.stack,\n // Error.cause requires ES2022+ - safely access if present\n ...(\"cause\" in thrown &&\n thrown.cause !== undefined && { cause: thrown.cause }),\n };\n }\n\n // Handle plain objects - spread properties into metadata, filtering reserved props\n if (thrown && typeof thrown === \"object\") {\n const filtered: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(thrown)) {\n // Skip reserved / hazardous keys: #39 (constructor TypeError on code/\n // segment/path) and #947 (`then` would make the error thenable).\n if (!reservedRouterErrorProps.has(key)) {\n filtered[key] = value;\n }\n }\n\n return { ...base, ...filtered };\n }\n\n // Primitives (string, number, boolean, null, undefined, symbol, bigint)\n // Return base metadata only - the primitive value isn't useful as metadata\n return base;\n}\n\n/**\n * Settle a guard's Promise into the pipeline's terms: `false` and a rejection\n * both become the phase's `RouterError`, nothing else escapes.\n *\n * Lives here rather than with the interpreter because that IS this module's\n * concern — turning a guard's refusal into the right error (#1607). The\n * interpreter only decides WHERE it stopped.\n */\nexport async function resolveAsyncGuard(\n promise: Promise<boolean>,\n errorCode: string,\n segment: string,\n): Promise<void> {\n let result: boolean;\n\n try {\n result = await promise;\n } catch (error: unknown) {\n handleGuardError(error, errorCode, segment);\n\n return; // unreachable — handleGuardError returns never\n }\n\n if (!result) {\n throw new RouterError(errorCode, { segment });\n }\n}\n"],"mappings":"8EAeA,SAAS,EAAsB,EAAyB,CACtD,OACE,aAAiBA,EAAAA,aACjB,EAAM,OAASC,EAAAA,WAAW,oBAE9B,CAsBA,SAAgB,EAAe,EAAyB,CACtD,OAAO,EAAsB,CAAK,EAC9B,EACA,IAAID,EAAAA,YAAYC,EAAAA,WAAW,qBAAsB,CAAE,OAAQ,CAAM,CAAC,CACxE,CAEA,SAAgB,EACd,EACA,EACA,EACA,EACM,CACN,IAAM,EAAc,EAGlB,EAAY,OAASA,EAAAA,WAAW,sBAChC,EAAY,OAASA,EAAAA,WAAW,iBAKlC,EAAK,mBAAmB,EAAS,EAAW,CAAW,CACzD,CAEA,SAAgB,EACd,EACA,EACA,EACO,CACP,GAAI,aAAiB,cAAgB,EAAM,OAAS,aAClD,MAAM,IAAID,EAAAA,YAAYC,EAAAA,WAAW,oBAAoB,EAUvD,GAAI,EAAsB,CAAK,EAC7B,MAAM,EAGR,EAAqB,EAAO,EAAW,CAAO,CAChD,CAmBA,SAAgB,EACd,EACA,EACA,EACO,CAOP,MANI,aAAiBD,EAAAA,aACnB,EAAM,QAAQ,CAAS,EAEjB,GAGF,IAAIA,EAAAA,YAAY,EAAW,EAAc,EAAO,CAAO,CAAC,CAChE,CAUA,MAAM,EAA2B,IAAI,IAAI,CAAC,OAAQ,UAAW,OAAQ,MAAM,CAAC,EAc5E,SAAgB,EACd,EACA,EACmB,CACnB,IAAM,EAA0B,CAAE,SAAQ,EAG1C,GAAI,aAAkB,MACpB,MAAO,CACL,GAAG,EACH,QAAS,EAAO,QAChB,MAAO,EAAO,MAEd,GAAI,UAAW,GACb,EAAO,QAAU,IAAA,IAAa,CAAE,MAAO,EAAO,KAAM,CACxD,EAIF,GAAI,GAAU,OAAO,GAAW,SAAU,CACxC,IAAM,EAAoC,CAAC,EAE3C,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAM,EAGzC,EAAyB,IAAI,CAAG,IACnC,EAAS,GAAO,GAIpB,MAAO,CAAE,GAAG,EAAM,GAAG,CAAS,CAChC,CAIA,OAAO,CACT,CAUA,eAAsB,EACpB,EACA,EACA,EACe,CACf,IAAI,EAEJ,GAAI,CACF,EAAS,MAAM,CACjB,OAAS,EAAgB,CACvB,EAAiB,EAAO,EAAW,CAAO,EAE1C,MACF,CAEA,GAAI,CAAC,EACH,MAAM,IAAIA,EAAAA,YAAY,EAAW,CAAE,SAAQ,CAAC,CAEhD"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../../../constants.js"),t=require("../../../transitionPath.js"),n=require("../../../RouterError.js"),r=require("../constants.js"),i=require("./completeTransition.js"),a=require("./errorHandling.js"),o=require("./guardPhase.js"),s=Object.freeze([]),c=new Map;function l(t,n){return n?.name===e.constants.UNKNOWN_ROUTE&&!t.replace?{...t,replace:!0}:t}function u(e,t,n){return!!e&&!t.reload&&!t.force&&e.path===n.path}function d(e,t){if(e.size===0)return!1;for(let n of t)if(e.has(n))return!0;return!1}function f(e,t,n,r,i){y(e,i.signal);let a={toState:n,fromState:r,opts:i,myId:t.begin(),suspendable:i.signal!==void 0||e.hasLeaveListeners()||e.hasPreCommitListeners(),toDeactivate:s,toActivate:s,intersection:``,canDeactivateFunctions:c,canActivateFunctions:c,shouldDeactivate:!1,shouldActivate:!1,hasGuards:!1};return e.startTransition(n,r),a}function p(e,t){return e.sendLeaveApprove(t.toState,t.fromState),i.completeTransition(e,t)}function m(n,r){let[i,a]=n.getLifecycleFunctions(),{toDeactivate:o,toActivate:s,intersection:c}=t.getTransitionPath(r.toState,r.fromState,e=>n.getMetaForState(e));r.canDeactivateFunctions=i,r.canActivateFunctions=a,r.toDeactivate=o,r.toActivate=s,r.intersection=c,r.shouldDeactivate=!!r.fromState&&!r.opts.forceDeactivate&&o.length>0,r.shouldActivate=r.toState.name!==e.constants.UNKNOWN_ROUTE&&s.length>0,r.hasGuards=r.shouldDeactivate&&d(i,o)||r.shouldActivate&&d(a,s)}function h(t,a,s,c){let d,h=0,y=null;try{if(d=t.getState(),c=l(c,d),u(d,c,s))return t.emitTransitionError(s,d,r.CACHED_SAME_STATES_ERROR),r.CACHED_SAME_STATES_REJECTION;let _=f(t,a,s,d,c);if(h=_.myId,m(t,_),!_.hasGuards&&!_.suspendable)return p(t,_);let{suspendable:b,canDeactivateFunctions:x,canActivateFunctions:S,toDeactivate:C,toActivate:w,shouldDeactivate:T,shouldActivate:E,hasGuards:D}=_,O=s;if(!D){let e=v(t,a,_);if(e!==void 0)return e}if(D){y=new AbortController,a.adopt(y);let r=()=>a.isCurrent(h)&&t.isActive(),i=y.signal,c=o.executeGuardPipeline(x,S,C,w,T,E,s,d,i,r,()=>{if(t.sendLeaveApprove(O,d),t.hasLeaveListeners())return t.awaitLeaveListeners(O,d,i)});if(c!==void 0)return g(t,a,c,_,y,h);if(!r())throw new n.RouterError(e.errorCodes.TRANSITION_CANCELLED);a.release(y,!1)}if(b&&(!t.isActive()||c.signal?.aborted===!0))throw new n.RouterError(e.errorCodes.TRANSITION_CANCELLED);return i.completeTransition(t,_)}catch(e){let n=_(t,a,e,y,h,s,d);return Promise.reject(n)}}async function g(t,r,o,s,c,l){let u=()=>r.isCurrent(l)&&!c.signal.aborted&&t.isActive(),d=s.opts.signal,f,p,m=!1,h,g=new Promise(e=>{if(c.signal.aborted){e();return}p=()=>{e()},c.signal.addEventListener(`abort`,p,{once:!0})});o.catch(()=>{});try{if(d){if(d.aborted)throw new n.RouterError(e.errorCodes.TRANSITION_CANCELLED,{reason:d.reason});f=()=>{t.cancelNavigation(d.reason)},d.addEventListener(`abort`,f,{once:!0})}if(await Promise.race([o,g]),!u())throw new n.RouterError(e.errorCodes.TRANSITION_CANCELLED);let r=i.completeTransition(t,s);return m=!0,r}catch(e){let n=u()?e:a.asCancellation(e);throw h=n,a.routeTransitionError(t,n,s.toState,s.fromState),n}finally{f&&d?.removeEventListener(`abort`,f),p&&c.signal.removeEventListener(`abort`,p),r.release(c,!m,h)}}function _(e,t,n,r,i,o,s){if(r&&t.release(r,!0,n),i!==0&&o){let r=t.isCurrent(i)&&e.isTransitioning()?n:a.asCancellation(n);return a.routeTransitionError(e,r,o,s),r}return n}function v(e,t,n){let{toState:r,fromState:i,myId:a}=n;if(e.sendLeaveApprove(r,i),e.hasLeaveListeners()){let o=new AbortController;t.adopt(o);let s;try{s=e.awaitLeaveListeners(r,i,o.signal)}catch(e){throw t.release(o,!0,e),e}if(s!==void 0)return g(e,t,s,n,o,a);t.release(o,!1);return}}function y(t,r){if(t.isTransitioning()&&(t.logger.warn(`router.navigate`,`Concurrent navigation detected on shared router instance. For SSR, use cloneRouter() to create isolated instance per request.`),t.cancelNavigation()),r?.aborted)throw new n.RouterError(e.errorCodes.TRANSITION_CANCELLED,{reason:r.reason})}exports.abortPreviousNavigation=y,exports.executeNavigation=h;
|
|
2
|
-
//# sourceMappingURL=executeNavigation.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"executeNavigation.js","names":["constants","completeTransition","getTransitionPath","CACHED_SAME_STATES_ERROR","CACHED_SAME_STATES_REJECTION","executeGuardPipeline","RouterError","errorCodes","asCancellation"],"sources":["../../../../../src/namespaces/NavigationNamespace/transition/executeNavigation.ts"],"sourcesContent":["import { completeTransition } from \"./completeTransition\";\nimport { asCancellation, routeTransitionError } from \"./errorHandling\";\nimport { executeGuardPipeline } from \"./guardPhase\";\nimport { errorCodes, constants } from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\nimport { getTransitionPath } from \"../../../transitionPath\";\nimport {\n CACHED_SAME_STATES_ERROR,\n CACHED_SAME_STATES_REJECTION,\n} from \"../constants\";\n\nimport type { GuardFn, NavigationOptions, State } from \"../../../types\";\nimport type { InFlightNavigation } from \"../InFlightNavigation\";\nimport type {\n NavigationContext,\n NavigationDependencies,\n NavigationPlan,\n} from \"../types\";\n\n/**\n * The orchestration of one navigation, end to end.\n *\n * These were methods on `NavigationNamespace` until the per-navigation state\n * they shared was named (#1607). With the controller and the supersession token\n * owned by {@link InFlightNavigation}, nothing here needs `this`: every function\n * is over `(deps, inFlight, plan)`, and the namespace above is left with what it\n * actually is — the entry points, their fire-and-forget checkpoint, and the DI\n * bag.\n *\n * Parameter lists stay flat rather than growing a context object: the plan IS\n * the per-navigation bag already (`NavigationPlan` extends `NavigationContext`),\n * and a second one would be an allocation on the #307 hot path — the same reason\n * `guardPhase.ts` carries its own flat signatures.\n */\n\n// Write-once placeholders for `NavigationPlan`'s pass-2 fields. Module-level so\n// building a plan allocates nothing beyond the plan itself; never mutated —\n// `planPhases` overwrites the SLOTS, it does not write through them.\nconst NO_SEGMENTS: string[] = Object.freeze([]) as unknown as string[];\nconst NO_GUARDS = new Map<string, GuardFn>();\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 * Does any segment this phase walks carry a guard?\n *\n * The empty-Map check first, so a router with no guards at all pays one load —\n * and a router that HAS guards pays one `Map.has` per segment on the path,\n * against the ~40 ns + 482 B an AbortController costs when the answer is no.\n */\nfunction hasGuardOnPath(\n guards: Map<string, GuardFn>,\n segments: string[],\n): boolean {\n if (guards.size === 0) {\n return false;\n }\n\n for (const segment of segments) {\n if (guards.has(segment)) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Pass 1 of the shared prologue: reserve the navigation, then announce it.\n *\n * Ends with `startTransition` DELIBERATELY, and nothing follows it here. That\n * makes \"this threw\" strictly equivalent to \"TRANSITION_START never fired\", so\n * the caller can set its `transitionStarted` marker immediately after the call\n * and stay exactly as accurate as the inline version was.\n *\n * The three statements before it are ordered, not incidental:\n * `#abortPreviousNavigation` may run FSM `CANCEL` (whose listeners can add or\n * drop subscriptions), so `suspendable` has to be read after it — and before\n * `startTransition`, whose listeners a `stop()` would use to empty those same\n * lists (#1169, the QB/QE hole).\n */\nfunction beginTransition(\n deps: NavigationDependencies,\n inFlight: InFlightNavigation,\n toState: State,\n fromState: State | undefined,\n opts: NavigationOptions,\n): NavigationPlan {\n abortPreviousNavigation(deps, opts.signal);\n\n const myId = inFlight.begin();\n\n // `suspendable` is true only when a synchronous supersede is reachable — an\n // external `opts.signal`, `subscribeLeave` listeners, or a pre-commit plugin\n // listener (`onTransitionStart` / `onTransitionLeaveApprove`); the pure\n // synchronous navigate (none of these) is uncancellable and skips the\n // commit-gate, keeping the #307 hot path perf-neutral.\n const plan: NavigationPlan = {\n toState,\n fromState,\n opts,\n myId,\n suspendable:\n opts.signal !== undefined ||\n deps.hasLeaveListeners() ||\n deps.hasPreCommitListeners(),\n // Write-once placeholders — pass 2 fills them (see `NavigationPlan`).\n toDeactivate: NO_SEGMENTS,\n toActivate: NO_SEGMENTS,\n intersection: \"\",\n canDeactivateFunctions: NO_GUARDS,\n canActivateFunctions: NO_GUARDS,\n shouldDeactivate: false,\n shouldActivate: false,\n hasGuards: false,\n };\n\n deps.startTransition(toState, fromState);\n\n return plan;\n}\n\n/**\n * The uninterruptible navigation, end to end (RFC §5.1, разрез А).\n *\n * Reached only when `!hasGuards && !suspendable` — no guards to run, no\n * `subscribeLeave` listener, no caller `signal`, no pre-commit plugin\n * listener. Nothing can cancel it and nothing in it can suspend, so the\n * cancellation machinery is not *skipped* here: it is ABSENT. No\n * `AbortController`, no `isCurrentNav` closure, no commit-gate, and the return\n * type is a plain `State` rather than a Promise — being unable to suspend is a\n * property of the code, not a fact one has to remember.\n *\n * The `LEAVE_APPROVE` emit stays: it is an FSM transition every navigation\n * makes, and with no leave listeners there is nothing to await behind it.\n */\nfunction completeImmediate(\n deps: NavigationDependencies,\n plan: NavigationPlan,\n): State {\n deps.sendLeaveApprove(plan.toState, plan.fromState);\n\n return completeTransition(deps, plan);\n}\n\n/**\n * Pass 2: work out the shape of the transition, now that it is announced.\n *\n * Runs AFTER `startTransition` because a `TRANSITION_START` listener may still\n * register a guard, and the guard maps must reflect that.\n */\nfunction planPhases(deps: NavigationDependencies, plan: NavigationPlan): void {\n const [canDeactivateFunctions, canActivateFunctions] =\n deps.getLifecycleFunctions();\n\n const { toDeactivate, toActivate, intersection } = getTransitionPath(\n plan.toState,\n plan.fromState,\n (name) => deps.getMetaForState(name),\n );\n\n plan.canDeactivateFunctions = canDeactivateFunctions;\n plan.canActivateFunctions = canActivateFunctions;\n plan.toDeactivate = toDeactivate;\n plan.toActivate = toActivate;\n plan.intersection = intersection;\n plan.shouldDeactivate =\n !!plan.fromState && !plan.opts.forceDeactivate && toDeactivate.length > 0;\n plan.shouldActivate =\n plan.toState.name !== constants.UNKNOWN_ROUTE && toActivate.length > 0;\n // The guards of THIS transition, not of the router. Asking the Maps for\n // their size answered a different question — \"does the app have a guard\n // anywhere\" — so one `canActivate` on an admin page armed the cancellation\n // machinery for every public navigation: an AbortController, the liveness\n // closure, and a walk that found no guard on any of its steps (measured:\n // +643 B and +84 ns per navigation that never touches the guarded route).\n //\n // The predicate mirrors what the interpreter would actually do — a phase\n // whose short-circuit is false runs no step, so its guards cannot fire —\n // which is what keeps this a fast-path gate and not a second policy.\n plan.hasGuards =\n (plan.shouldDeactivate &&\n hasGuardOnPath(canDeactivateFunctions, toDeactivate)) ||\n (plan.shouldActivate && hasGuardOnPath(canActivateFunctions, toActivate));\n}\n\nexport function executeNavigation(\n deps: NavigationDependencies,\n inFlight: InFlightNavigation,\n toState: State,\n opts: NavigationOptions,\n): State | Promise<State> {\n let fromState: State | undefined;\n // The token of the navigation THIS call announced — `0` until `beginTransition`\n // returns, i.e. exactly the \"did TRANSITION_START fire?\" marker this used to be\n // a boolean for. It carries the id because the error path needs both facts: it\n // may only report for a navigation that was announced AND is still the one in\n // flight (#1609).\n let myId = 0;\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\n return CACHED_SAME_STATES_REJECTION;\n }\n\n const plan = beginTransition(deps, inFlight, toState, fromState, opts);\n\n myId = plan.myId;\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 planPhases(deps, plan);\n\n // Разрез А (RFC §5.1). `immediate` is the RFC's four-term predicate\n // written in the terms that already exist: `suspendable` IS\n // `signal || leaveListeners || preCommitListeners`, so the whole thing is\n // `!hasGuards && !suspendable`. Nothing can interrupt such a navigation and\n // nothing in it can suspend, so the machinery for both is not skipped —\n // `#completeImmediate` does not contain it.\n //\n // Decided HERE and not at the entry point, deliberately: `hasGuards` is\n // only knowable after `startTransition`, because a `TRANSITION_START`\n // listener may still register a guard. Hoisting the read would change\n // behaviour, not just shape.\n if (!plan.hasGuards && !plan.suspendable) {\n return completeImmediate(deps, plan);\n }\n\n const {\n suspendable,\n canDeactivateFunctions,\n canActivateFunctions,\n toDeactivate,\n toActivate,\n shouldDeactivate,\n shouldActivate,\n hasGuards,\n } = plan;\n\n const confirmedToState = toState;\n\n if (!hasGuards) {\n const asyncLeave = handleNoGuardsLeave(deps, inFlight, plan);\n\n if (asyncLeave !== undefined) {\n return asyncLeave;\n }\n }\n\n // NOT equivalent, and the `Stryker disable: equivalent` that used to sit\n // here was wrong: with `if (true)` the guard branch runs AFTER\n // `#handleNoGuardsLeave` already emitted LEAVE_APPROVE, so\n // `emitLeaveApproveCallback` dispatches every `subscribeLeave` listener a\n // SECOND time (measured: 2 calls, 1 expected). The whole suite stayed\n // green because the shared `createTestRouter` fixture carries definition\n // guards, so no test ever reached this branch with `hasGuards` false —\n // `guard-phase-emit-leave-approve.test.ts` now builds a guard-free router\n // and kills both mutants.\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 inFlight.adopt(controller);\n const isCurrentNav = () => inFlight.isCurrent(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(confirmedToState, fromState, signal);\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 // The plan IS the `NavigationContext` (a superset of it), so the\n // second literal this used to build is gone — one bag per navigation.\n return finishAsyncNavigation(\n deps,\n inFlight,\n guardCompletion,\n plan,\n controller,\n myId,\n );\n }\n\n if (!isCurrentNav()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n inFlight.release(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 supersession check: a reentrant navigate() (the only thing\n // that could bump the token synchronously) is banned\n // (REENTRANT_NAVIGATION, §4), so on this sync path the token still 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, plan);\n\n // A bare `State`, not `Promise.resolve(state)` — the RETURN TYPE is what\n // announces \"this navigation already settled, synchronously\", which used\n // to be `lastSyncResolved`'s job. The ordering hazard the flag carried is\n // gone with it: there is no window in which a value says \"resolved\" while\n // `completeTransition` may still throw, because the value only exists once\n // it returned. The Promise wrap moves up to the facade, which owes callers\n // `Promise<State>`; the allocation is the same one, one frame higher.\n return finalState;\n } catch (error) {\n const outcome = handleNavigateError(\n deps,\n inFlight,\n error,\n controller,\n myId,\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(outcome);\n }\n}\n\nasync function finishAsyncNavigation(\n deps: NavigationDependencies,\n inFlight: InFlightNavigation,\n guardCompletion: Promise<void>,\n nav: NavigationContext,\n controller: AbortController,\n myId: number,\n): Promise<State> {\n const isActive = () =>\n inFlight.isCurrent(myId) && !controller.signal.aborted && 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 // The race settles two ways and BOTH have to consult liveness. Only this\n // arm did: a rejection throws straight past the check into the `catch`,\n // which is where #1609 lived.\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 // Liveness on the OTHER arm of the race (#1609). A guard that rejects one\n // or two microtasks before a superseding `navigate()` used to report FAIL\n // for a navigation cancelled several microtasks earlier —\n // `routeTransitionError` filters by error CODE, and `CANNOT_ACTIVATE` is\n // not `TRANSITION_CANCELLED`. Into a `READY` FSM that was observability\n // noise (a terminal event for a dead navigation); into the LIVE one it was\n // silent corruption, because `TRANSITION_STARTED --FAIL--> READY` is a real\n // edge, so the superseding navigation's later `COMPLETE` became a table\n // no-op: state committed, `TRANSITION_SUCCESS` never emitted, subscribers\n // never notified.\n const outcome = isActive() ? error : asCancellation(error);\n\n failureReason = outcome;\n\n routeTransitionError(deps, outcome, nav.toState, nav.fromState);\n\n throw outcome;\n // NB: the `} finally {}` BlockStatement mutant SURVIVES but is EQUIVALENT —\n // emptying the finally only skips the controller release, 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 InFlightNavigation.release'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 the release below 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 inFlight.release(controller, !succeeded, failureReason);\n }\n}\n\n/**\n * Settle a failed navigation on the SYNCHRONOUS arc: release its controller,\n * report it only while it is still the navigation in flight, and hand back the\n * outcome the caller's promise should carry.\n *\n * `myId === 0` means `TRANSITION_START` never fired, so there is no announced\n * navigation for a terminal event to pair with — the error goes back untouched.\n *\n * Liveness asks the precise question — **does the FSM still hold MY\n * transition?** — because that is the precondition for sending `FAIL` at all:\n * the token says no newer navigation took over, and `isTransitioning()` says the\n * FSM has not already left the transition band. `isActive()` would be the looser\n * approximation and gets two cases wrong: a listener that runs `stop()` followed\n * by a `start()` PARKED in an async interceptor bumps no token and puts the FSM\n * in `STARTING`, where `isActive()` is true again — for a different lifecycle,\n * whose start the stale `FAIL` would then kill (`STARTING --FAIL--> IDLE`).\n * `#finishAsyncNavigation` reads the same fact off `controller.signal.aborted`,\n * which this arc cannot: the guard-free leave arc keeps its controller local and\n * has already released it by the time an error arrives here.\n */\nfunction handleNavigateError(\n deps: NavigationDependencies,\n inFlight: InFlightNavigation,\n error: unknown,\n controller: AbortController | null,\n myId: number,\n toState: State | undefined,\n fromState: State | undefined,\n): unknown {\n if (controller) {\n inFlight.release(controller, true, error);\n }\n\n if (myId !== 0 && toState) {\n const outcome =\n inFlight.isCurrent(myId) && deps.isTransitioning()\n ? error\n : asCancellation(error);\n\n routeTransitionError(deps, outcome, toState, fromState);\n\n return outcome;\n }\n\n return error;\n}\n\n/**\n * The leave phase for a navigation with no guards, but with something that\n * can suspend it. Takes the `plan` whole rather than six projections of it:\n * every argument it used to receive was a field of that same bag, and the bag\n * IS a `NavigationContext`, so handing it straight to `#finishAsyncNavigation`\n * keeps this arc at ONE context object per navigation — the allocation\n * neutrality the extracted prologue was supposed to buy everywhere, not only\n * on the guard path.\n */\nfunction handleNoGuardsLeave(\n deps: NavigationDependencies,\n inFlight: InFlightNavigation,\n plan: NavigationPlan,\n): Promise<State> | undefined {\n const { toState, fromState, myId } = plan;\n\n deps.sendLeaveApprove(toState, fromState);\n\n if (deps.hasLeaveListeners()) {\n const controller = new AbortController();\n\n // Adopted BEFORE listeners run so a reentrant navigate() / stop() /\n // dispose() from a sync listener aborts THIS leave signal — parity with\n // the guard path (#722).\n inFlight.adopt(controller);\n\n let leaveResult: Promise<void> | undefined;\n\n // This catches the SYNCHRONOUS throw only — a leave listener that throws\n // inline, before any Promise exists (three tests in\n // `leave-signal-cancellation.test.ts` fail without it: the sync-throw\n // rejection, its `signal.reason` (#943), and the reentrant-navigate abort).\n // A rejection of the RETURNED Promise is deliberately not caught here: it is\n // awaited in `finishAsyncNavigation`, which routes it through\n // `routeTransitionError` with the controller released as cancelled. Sonar's\n // S4822 flags the shape (a promise-returning call inside `try`) and cannot\n // see that split.\n try {\n // NOSONAR -- S4822: the try is for the sync listener throw, never for the Promise\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 inFlight.release(controller, true, error);\n\n throw error;\n }\n\n if (leaveResult !== undefined) {\n return finishAsyncNavigation(\n deps,\n inFlight,\n leaveResult,\n plan,\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 inFlight.release(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\nexport function abortPreviousNavigation(\n deps: NavigationDependencies,\n externalSignal?: AbortSignal,\n): void {\n if (deps.isTransitioning()) {\n 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 deps.cancelNavigation();\n }\n\n if (externalSignal?.aborted) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED, {\n reason: externalSignal.reason,\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../../../constants.js"),t=require("../../../RouterError.js"),n=require("./errorHandling.js");function r(r,i,a,o,s,c,l,u,d,f){if(!d())throw new t.RouterError(e.errorCodes.TRANSITION_CANCELLED);if(r===1){let e=f();return e===void 0?void 0:{phase:r,index:i+1,pending:e}}let p=a[i],m=o.get(p);if(!m)return;let h=!1;try{h=m(c,l,u)}catch(e){n.handleGuardError(e,s,p)}if(h instanceof Promise)return{phase:r,index:i+1,pending:h};if(!h)throw new t.RouterError(s,{segment:p})}function i(t,n,i,a,o,s,c,l,u,d,f,p,m){let h=t===1,g=t===0;if(!h&&!(g?c:l))return;let _=g?o:s,v=h?1:_.length,y=g?i:a,b=g?e.errorCodes.CANNOT_DEACTIVATE:e.errorCodes.CANNOT_ACTIVATE;for(let e=n;e<v;e++){let n=r(t,e,_,y,b,u,d,f,p,m);if(n!==void 0)return n}}function a(e,t,n,r,a,o,s,c,l,u,d,f,p){for(let m=f;m<=2;m++){let h=i(m,m===f?p:0,e,t,n,r,a,o,s,c,l,u,d);if(h!==void 0)return h}}async function o(t,r,i,o,s,c,l,u,d,f,p,m){let h=t;for(;h!==void 0;){if(h.phase===1)await h.pending;else{let t=h.phase===0;await n.resolveAsyncGuard(h.pending,t?e.errorCodes.CANNOT_DEACTIVATE:e.errorCodes.CANNOT_ACTIVATE,(t?o:s)[h.index-1])}h=a(r,i,o,s,c,l,u,d,f,p,m,h.phase,h.index)}}function s(e,t,n,r,i,s,c,l,u,d,f){let p=a(e,t,n,r,i,s,c,l,u,d,f,0,0);return p===void 0?void 0:o(p,e,t,n,r,i,s,c,l,u,d,f)}exports.executeGuardPipeline=s;
|
|
2
|
-
//# sourceMappingURL=guardPhase.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"guardPhase.js","names":["RouterError","errorCodes","resolveAsyncGuard"],"sources":["../../../../../src/namespaces/NavigationNamespace/transition/guardPhase.ts"],"sourcesContent":["import { handleGuardError, resolveAsyncGuard } from \"./errorHandling\";\nimport { errorCodes } from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\n\nimport type { GuardFn, State } from \"../../../types\";\n\n/**\n * The guard pipeline as ONE program and TWO interpreters (RFC two-pipelines,\n * разрез Б).\n *\n * The program is three fixed phases — deactivate, leave, activate — walked by a\n * cursor of two numbers. `runFrom` is the synchronous interpreter: it walks\n * until a step hands back a Promise, then stops and reports where it stopped.\n * `resumeFrom` is the asynchronous one: it settles that Promise and hands the\n * cursor straight back to `runFrom`. Switching pipelines is therefore a single\n * act — give up the cursor — rather than four continuation functions each wired\n * to its own entry point.\n *\n * What that buys, and it is the reason the step exists: **one cancellation\n * check**. There were eight, and five of them were mutationally unkillable —\n * their breakage was as unobservable as their removal, because a navigation\n * reaching them was already covered by the liveness check one layer up. A single\n * check in the head of the step runs at every position that mattered, and it\n * sits where nothing else guards it, so it is killable again (removing it fails\n * four tests).\n *\n * It does NOT reproduce the five redundant positions, deliberately: there is no\n * check after the LAST activation guard settles, nor after an async leave when\n * `shouldActivate` is false, because the walk simply ends there. Those were two\n * of the five, and `#finishAsyncNavigation`'s liveness check still covers both.\n */\n\nconst PHASE_DEACTIVATE = 0;\nconst PHASE_LEAVE = 1;\nconst PHASE_ACTIVATE = 2;\n\n/**\n * Where the synchronous interpreter stopped, and what it is waiting on.\n *\n * Allocated ONLY when a step actually suspends — a fully synchronous navigation\n * never builds one, which is what keeps the hot path where it was. `index` is\n * already the cursor for the NEXT step, so resuming needs no off-by-one\n * reasoning.\n */\ninterface Suspension {\n phase: number;\n index: number;\n pending: Promise<unknown>;\n}\n\n/**\n * ONE step of the program: the cancellation check, then the step's own work.\n *\n * Returns a {@link Suspension} when the step handed back a Promise, `undefined`\n * when it finished synchronously, and throws when it refused — a guard returning\n * `false`, a guard throwing, or the navigation having been cancelled.\n */\nfunction runStep( // NOSONAR -- params kept flat to avoid object allocation on hot path\n phase: number,\n index: number,\n segments: string[],\n guards: Map<string, GuardFn>,\n errorCode: string,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal | undefined,\n isActive: () => boolean,\n emitLeaveApprove: () => Promise<void> | undefined,\n): Suspension | undefined {\n // THE cancellation check — the only one left in this file, and literally in\n // the head of a step. Eight became one, and unlike five of those eight it is\n // killable: nothing else guards this position.\n if (!isActive()) {\n throw new RouterError(errorCodes.TRANSITION_CANCELLED);\n }\n\n if (phase === PHASE_LEAVE) {\n const leaveResult = emitLeaveApprove();\n\n return leaveResult === undefined\n ? undefined\n : { phase, index: index + 1, pending: leaveResult };\n }\n\n const segment = segments[index];\n const guardFn = guards.get(segment);\n\n if (!guardFn) {\n return undefined;\n }\n\n // Stryker disable next-line BooleanLiteral: equivalent — guardResult is unconditionally reassigned by guardFn() on the next line, or the catch calls handleGuardError() (returns never), so the init value is never read.\n let guardResult: boolean | Promise<boolean> = false;\n\n try {\n guardResult = guardFn(toState, fromState, signal);\n } catch (error: unknown) {\n handleGuardError(error, errorCode, segment);\n }\n\n if (guardResult instanceof Promise) {\n return { phase, index: index + 1, pending: guardResult };\n }\n\n if (!guardResult) {\n throw new RouterError(errorCode, { segment });\n }\n\n return undefined;\n}\n\n/**\n * One PHASE of the program: its short-circuit, then its steps.\n *\n * Split from the walk so neither function carries the whole program — phase-level\n * and step-level nesting in one body is what pushed the first draft past the\n * complexity budget, and past a lint rule against `continue` across nested loops.\n */\nfunction runPhase( // NOSONAR -- params kept flat to avoid object allocation on hot path\n phase: number,\n from: number,\n deactivateGuards: Map<string, GuardFn>,\n activateGuards: Map<string, GuardFn>,\n toDeactivate: string[],\n toActivate: string[],\n shouldDeactivate: boolean,\n shouldActivate: boolean,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal | undefined,\n isActive: () => boolean,\n emitLeaveApprove: () => Promise<void> | undefined,\n): Suspension | undefined {\n const isLeave = phase === PHASE_LEAVE;\n const isDeactivate = phase === PHASE_DEACTIVATE;\n\n // Today's per-phase short-circuits, reproduced. `shouldDeactivate` carries\n // `!opts.forceDeactivate`, so this is a user-facing contract and not merely an\n // emptiness test — forcing it on is measurably NOT equivalent.\n if (!isLeave && !(isDeactivate ? shouldDeactivate : shouldActivate)) {\n return undefined;\n }\n\n const segments = isDeactivate ? toDeactivate : toActivate;\n // The leave phase is a single step, so one loop shape covers all three phases\n // — which is what gives the cancellation check exactly one home.\n const stepCount = isLeave ? 1 : segments.length;\n const guards = isDeactivate ? deactivateGuards : activateGuards;\n const errorCode = isDeactivate\n ? errorCodes.CANNOT_DEACTIVATE\n : errorCodes.CANNOT_ACTIVATE;\n\n for (let index = from; index < stepCount; index++) {\n const suspension = runStep(\n phase,\n index,\n segments,\n guards,\n errorCode,\n toState,\n fromState,\n signal,\n isActive,\n emitLeaveApprove,\n );\n\n if (suspension !== undefined) {\n return suspension;\n }\n }\n\n return undefined;\n}\n\n/**\n * The synchronous interpreter: walk the program from `startPhase`/`startIndex`\n * until a step suspends (returns the {@link Suspension}) or the program ends\n * (returns `undefined` — done).\n *\n * Parameters are flat rather than a context object on purpose: a bag would be an\n * allocation on every guarded navigation, and this is the #307 hot path.\n */\nfunction runFrom( // NOSONAR -- params kept flat to avoid object allocation on hot path\n deactivateGuards: Map<string, GuardFn>,\n activateGuards: Map<string, GuardFn>,\n toDeactivate: string[],\n toActivate: string[],\n shouldDeactivate: boolean,\n shouldActivate: boolean,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal | undefined,\n isActive: () => boolean,\n emitLeaveApprove: () => Promise<void> | undefined,\n startPhase: number,\n startIndex: number,\n): Suspension | undefined {\n for (let phase = startPhase; phase <= PHASE_ACTIVATE; phase++) {\n const suspension = runPhase(\n phase,\n phase === startPhase ? startIndex : 0,\n deactivateGuards,\n activateGuards,\n toDeactivate,\n toActivate,\n shouldDeactivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isActive,\n emitLeaveApprove,\n );\n\n if (suspension !== undefined) {\n return suspension;\n }\n }\n\n return undefined;\n}\n\n/**\n * The asynchronous interpreter: settle what the synchronous one stopped on, then\n * hand the cursor straight back to it.\n *\n * It owns no phase logic of its own, and that is the point — everything it knows\n * about the program it learns from the cursor, so the two interpreters cannot\n * drift apart the way the three continuation functions did.\n */\nasync function resumeFrom( // NOSONAR -- params kept flat to avoid object allocation on hot path\n suspension: Suspension,\n deactivateGuards: Map<string, GuardFn>,\n activateGuards: Map<string, GuardFn>,\n toDeactivate: string[],\n toActivate: string[],\n shouldDeactivate: boolean,\n shouldActivate: boolean,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal | undefined,\n isActive: () => boolean,\n emitLeaveApprove: () => Promise<void> | undefined,\n): Promise<void> {\n let at: Suspension | undefined = suspension;\n\n while (at !== undefined) {\n if (at.phase === PHASE_LEAVE) {\n await at.pending;\n } else {\n const isDeactivate = at.phase === PHASE_DEACTIVATE;\n\n await resolveAsyncGuard(\n at.pending as Promise<boolean>,\n isDeactivate\n ? errorCodes.CANNOT_DEACTIVATE\n : errorCodes.CANNOT_ACTIVATE,\n (isDeactivate ? toDeactivate : toActivate)[at.index - 1],\n );\n }\n\n at = runFrom(\n deactivateGuards,\n activateGuards,\n toDeactivate,\n toActivate,\n shouldDeactivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isActive,\n emitLeaveApprove,\n at.phase,\n at.index,\n );\n }\n}\n\n/**\n * Runs the guard program. Returns `undefined` when it completed synchronously,\n * or the Promise that finishes it otherwise — the same contract the three\n * orchestrators used to provide between them.\n */\nexport function executeGuardPipeline( // NOSONAR -- params kept flat to avoid object allocation on hot path\n deactivateGuards: Map<string, GuardFn>,\n activateGuards: Map<string, GuardFn>,\n toDeactivate: string[],\n toActivate: string[],\n shouldDeactivate: boolean,\n shouldActivate: boolean,\n toState: State,\n fromState: State | undefined,\n signal: AbortSignal,\n isActive: () => boolean,\n emitLeaveApprove: () => Promise<void> | undefined,\n): Promise<void> | undefined {\n const suspension = runFrom(\n deactivateGuards,\n activateGuards,\n toDeactivate,\n toActivate,\n shouldDeactivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isActive,\n emitLeaveApprove,\n PHASE_DEACTIVATE,\n 0,\n );\n\n return suspension === undefined\n ? undefined\n : resumeFrom(\n suspension,\n deactivateGuards,\n activateGuards,\n toDeactivate,\n toActivate,\n shouldDeactivate,\n shouldActivate,\n toState,\n fromState,\n signal,\n isActive,\n emitLeaveApprove,\n );\n}\n"],"mappings":"8GAyDA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACwB,CAIxB,GAAI,CAAC,EAAS,EACZ,MAAM,IAAIA,EAAAA,YAAYC,EAAAA,WAAW,oBAAoB,EAGvD,GAAI,IAAU,EAAa,CACzB,IAAM,EAAc,EAAiB,EAErC,OAAO,IAAgB,IAAA,GACnB,IAAA,GACA,CAAE,QAAO,MAAO,EAAQ,EAAG,QAAS,CAAY,CACtD,CAEA,IAAM,EAAU,EAAS,GACnB,EAAU,EAAO,IAAI,CAAO,EAElC,GAAI,CAAC,EACH,OAIF,IAAI,EAA0C,GAE9C,GAAI,CACF,EAAc,EAAQ,EAAS,EAAW,CAAM,CAClD,OAAS,EAAgB,CACvB,EAAA,iBAAiB,EAAO,EAAW,CAAO,CAC5C,CAEA,GAAI,aAAuB,QACzB,MAAO,CAAE,QAAO,MAAO,EAAQ,EAAG,QAAS,CAAY,EAGzD,GAAI,CAAC,EACH,MAAM,IAAID,EAAAA,YAAY,EAAW,CAAE,SAAQ,CAAC,CAIhD,CASA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACwB,CACxB,IAAM,EAAU,IAAU,EACpB,EAAe,IAAU,EAK/B,GAAI,CAAC,GAAW,EAAE,EAAe,EAAmB,GAClD,OAGF,IAAM,EAAW,EAAe,EAAe,EAGzC,EAAY,EAAU,EAAI,EAAS,OACnC,EAAS,EAAe,EAAmB,EAC3C,EAAY,EACdC,EAAAA,WAAW,kBACXA,EAAAA,WAAW,gBAEf,IAAK,IAAI,EAAQ,EAAM,EAAQ,EAAW,IAAS,CACjD,IAAM,EAAa,EACjB,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,CACF,EAEA,GAAI,IAAe,IAAA,GACjB,OAAO,CAEX,CAGF,CAUA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACwB,CACxB,IAAK,IAAI,EAAQ,EAAY,GAAS,EAAgB,IAAS,CAC7D,IAAM,EAAa,EACjB,EACA,IAAU,EAAa,EAAa,EACpC,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,CACF,EAEA,GAAI,IAAe,IAAA,GACjB,OAAO,CAEX,CAGF,CAUA,eAAe,EACb,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACe,CACf,IAAI,EAA6B,EAEjC,KAAO,IAAO,IAAA,IAAW,CACvB,GAAI,EAAG,QAAU,EACf,MAAM,EAAG,YACJ,CACL,IAAM,EAAe,EAAG,QAAU,EAElC,MAAMC,EAAAA,kBACJ,EAAG,QACH,EACID,EAAAA,WAAW,kBACXA,EAAAA,WAAW,iBACd,EAAe,EAAe,EAAA,CAAY,EAAG,MAAQ,EACxD,CACF,CAEA,EAAK,EACH,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EAAG,MACH,EAAG,KACL,CACF,CACF,CAOA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EAC2B,CAC3B,IAAM,EAAa,EACjB,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,CACF,EAEA,OAAO,IAAe,IAAA,GAClB,IAAA,GACA,EACE,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,CACF,CACN"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../../../constants.js"),t=require("../../../transitionPath.js"),n=require("../../../RouterError.js"),r=require("./executeNavigation.js"),i=Object.freeze([e.constants.UNKNOWN_ROUTE]),a=Object.freeze({replace:!0});function o(o,s){if(!o.isActive())throw new n.RouterError(e.errorCodes.ROUTER_DISPOSED);r.abortPreviousNavigation(o);let c=o.getState(),l=c?t.nameToIDs(c.name).toReversed():[];Object.freeze(l);let u={deactivated:l,activated:i,intersection:``};Object.freeze(u);let d={phase:`activating`,...c&&{from:c.name},reason:`success`,replace:!0,segments:u};Object.freeze(d);let f={name:e.constants.UNKNOWN_ROUTE,params:e.EMPTY_PARAMS,search:e.EMPTY_SEARCH,path:s,transition:d,context:{}};return Object.freeze(f),o.setState(f),o.emitTransitionSuccess(f,c,a),f}exports.navigateToNotFound=o;
|
|
2
|
-
//# sourceMappingURL=navigateToNotFound.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"navigateToNotFound.js","names":["constants","RouterError","errorCodes","nameToIDs","EMPTY_PARAMS","EMPTY_SEARCH"],"sources":["../../../../../src/namespaces/NavigationNamespace/transition/navigateToNotFound.ts"],"sourcesContent":["import { abortPreviousNavigation } from \"./executeNavigation\";\nimport {\n EMPTY_PARAMS,\n EMPTY_SEARCH,\n errorCodes,\n constants,\n} from \"../../../constants\";\nimport { RouterError } from \"../../../RouterError\";\nimport { nameToIDs } from \"../../../transitionPath\";\n\nimport type { NavigationOptions, State, TransitionMeta } from \"../../../types\";\nimport type { NavigationDependencies } from \"../types\";\n\n/**\n * The one commit primitive that is NOT a transition.\n *\n * It hand-builds an `UNKNOWN_ROUTE` state, freezes it, commits it and emits only\n * `TRANSITION_SUCCESS` — no guards, no FSM transition, no controller, no plan.\n * That is why it sat oddly among the transition machinery it shared a file with\n * (#1607); the one thing it does share is supersession, since committing a\n * not-found state must displace whatever navigation is in flight.\n */\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\nexport function navigateToNotFound(\n deps: NavigationDependencies,\n path: string,\n): 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 (!deps.isActive()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n abortPreviousNavigation(deps);\n\n const fromState = 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 deps.setState(state);\n deps.emitTransitionSuccess(state, fromState, FROZEN_REPLACE_OPTS);\n\n return state;\n}\n"],"mappings":"0JAuBM,EAA6B,OAAO,OAAO,CAC/CA,EAAAA,UAAU,aACZ,CAAC,EACK,EAAyC,OAAO,OAAO,CAAE,QAAS,EAAK,CAAC,EAE9E,SAAgB,EACd,EACA,EACO,CAYP,GAAI,CAAC,EAAK,SAAS,EACjB,MAAM,IAAIC,EAAAA,YAAYC,EAAAA,WAAW,eAAe,EAGlD,EAAA,wBAAwB,CAAI,EAE5B,IAAM,EAAY,EAAK,SAAS,EAC1B,EAAwB,EAC1BC,EAAAA,UAAU,EAAU,IAAI,CAAC,CAAC,WAAW,EACrC,CAAC,EAEL,OAAO,OAAO,CAAW,EAEzB,IAAM,EAAuC,CAC3C,cACA,UAAW,EACX,aAAc,EAChB,EAEA,OAAO,OAAO,CAAQ,EAEtB,IAAM,EAAiC,CACrC,MAAO,aACP,GAAI,GAAa,CAAE,KAAM,EAAU,IAAK,EACxC,OAAQ,UACR,QAAS,GACT,UACF,EAEA,OAAO,OAAO,CAAc,EAE5B,IAAM,EAAe,CACnB,KAAMH,EAAAA,UAAU,cAChB,OAAQI,EAAAA,aACR,OAAQC,EAAAA,aACR,OACA,WAAY,EACZ,QAAS,CAAC,CACZ,EAOA,OALA,OAAO,OAAO,CAAK,EAEnB,EAAK,SAAS,CAAK,EACnB,EAAK,sBAAsB,EAAO,EAAW,CAAmB,EAEzD,CACT"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./constants.js"),t=require("./helpers.js"),n=require("./validators.js");var r=class{#e;constructor(n={}){this.#e=t.deepFreeze({...e.defaultOptions,...n})}static validateOptionsIsObject(e){n.validateOptionsIsObject(e)}get(){return this.#e}};exports.OptionsNamespace=r;
|
|
2
|
-
//# sourceMappingURL=OptionsNamespace.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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
|
-
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validators.js","names":[],"sources":["../../../../src/namespaces/OptionsNamespace/validators.ts"],"sourcesContent":["// packages/core/src/namespaces/OptionsNamespace/validators.ts\n\n/**\n * Minimal crash guard for options.\n * Full DX validation moved to @real-router/validation-plugin (retrospective pattern).\n */\nexport function validateOptionsIsObject(\n options: unknown,\n): asserts options is Record<string, unknown> {\n if (!options || typeof options !== \"object\" || Array.isArray(options)) {\n throw new TypeError(\"[router.constructor] options must be a plain object\");\n }\n}\n"],"mappings":"AAMA,SAAgB,EACd,EAC4C,CAC5C,GAAI,CAAC,GAAW,OAAO,GAAY,UAAY,MAAM,QAAQ,CAAO,EAClE,MAAU,UAAU,qDAAqD,CAE7E"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./constants.js"),t=require("./validators.js");var n=class n{#e=new Set;#t=new Set;#n;static validatePlugin(e){t.validatePlugin(e)}setDependencies(e){this.#n=e}count(){return this.#e.size}use(...t){if(this.#n.getValidator()?.plugins.validateCountThresholds(this.#e.size+t.length),t.length===1){let n=t[0],r=this.#i(n);this.#e.add(n);let i=!1,a=()=>{if(!i){i=!0,this.#e.delete(n),this.#t.delete(a);try{r()}catch(t){this.#n.logger.error(e.LOGGER_CONTEXT,`Error during cleanup:`,t)}}};return this.#t.add(a),a}let n=this.#r(t),r=[];try{for(let e of n){let t=this.#i(e);r.push({factory:e,cleanup:t})}}catch(t){for(let{cleanup:t}of r)try{t()}catch(t){this.#n.logger.error(e.LOGGER_CONTEXT,`Cleanup error:`,t)}throw t}for(let{factory:e}of r)this.#e.add(e);let i=!1,a=()=>{if(!i){i=!0,this.#t.delete(a);for(let{factory:e}of r)this.#e.delete(e);for(let{cleanup:t}of r)try{t()}catch(t){this.#n.logger.error(e.LOGGER_CONTEXT,`Error during cleanup:`,t)}}};return this.#t.add(a),a}getAll(){return[...this.#e]}has(e){return this.#e.has(e)}disposeAll(){for(let e of this.#t)e();this.#e.clear(),this.#t.clear()}#r(e){let t=new Set;for(let n of e)t.has(n)?this.#n.getValidator()?.plugins.warnBatchDuplicates(e):t.add(n);return t}#i(t){let r=this.#n.compileFactory(t);n.validatePlugin(r),this.#n.getValidator()?.plugins.validatePluginKeys(r),Object.freeze(r);let i=[];for(let t of e.EVENT_METHOD_NAMES)t in r&&(typeof r[t]==`function`?(i.push(this.#n.addEventListener(e.EVENTS_MAP[t],r[t])),t===`onStart`&&this.#n.canNavigate()&&this.#n.getValidator()?.plugins.warnPluginAfterStart(t)):this.#n.getValidator()?.plugins.warnPluginMethodType(t));return()=>{for(let e of i)e();typeof r.teardown==`function`&&r.teardown()}}};exports.PluginsNamespace=n;
|
|
2
|
-
//# sourceMappingURL=PluginsNamespace.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PluginsNamespace.js","names":["#plugins","#unsubscribes","#deps","#startPlugin","LOGGER_CONTEXT","#deduplicateBatch","EVENT_METHOD_NAMES","EVENTS_MAP"],"sources":["../../../../src/namespaces/PluginsNamespace/PluginsNamespace.ts"],"sourcesContent":["// packages/core/src/namespaces/PluginsNamespace/PluginsNamespace.ts\n\nimport { EVENTS_MAP, EVENT_METHOD_NAMES, LOGGER_CONTEXT } from \"./constants\";\nimport { validatePlugin } from \"./validators\";\n\nimport type { PluginsDependencies } from \"./types\";\nimport type {\n DefaultDependencies,\n Plugin,\n Unsubscribe,\n PluginFactory,\n} from \"../../types\";\n\n/**\n * Independent namespace for managing plugins.\n *\n * Static methods handle validation (called by facade).\n * Instance methods handle storage and business logic.\n */\nexport class PluginsNamespace<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> {\n readonly #plugins = new Set<PluginFactory<Dependencies>>();\n readonly #unsubscribes = new Set<Unsubscribe>();\n\n #deps!: PluginsDependencies<Dependencies>;\n\n // =========================================================================\n // Static validation methods (called by facade before instance methods)\n // Proxy to functions in validators.ts for separation of concerns\n // =========================================================================\n\n static validatePlugin(plugin: Plugin): void {\n validatePlugin(plugin);\n }\n\n // =========================================================================\n // Dependency injection\n // =========================================================================\n\n setDependencies(deps: PluginsDependencies<Dependencies>): void {\n this.#deps = deps;\n }\n\n // =========================================================================\n // Instance methods (trust input - already validated by facade)\n // =========================================================================\n\n /**\n * Returns the number of registered plugins.\n * Used by facade for limit validation.\n */\n /* v8 ignore next 3 -- @preserve: only called via validator interface (ctx.validator?.plugins.validatePluginLimit), not reachable without validation plugin */\n count(): number {\n return this.#plugins.size;\n }\n\n /**\n * Registers one or more plugin factories.\n * Returns unsubscribe function to remove all added plugins.\n * Input already validated by facade (limit, duplicates).\n *\n * @param factories - Already validated by facade\n */\n use(...factories: PluginFactory<Dependencies>[]): Unsubscribe {\n // Emit warnings for count thresholds (not validation, just warnings)\n this.#deps\n .getValidator()\n ?.plugins.validateCountThresholds(this.#plugins.size + factories.length);\n\n // Fast path for single plugin (common case)\n if (factories.length === 1) {\n const factory = factories[0];\n const cleanup = this.#startPlugin(factory);\n\n this.#plugins.add(factory);\n\n let unsubscribed = false;\n\n const unsubscribe: Unsubscribe = () => {\n if (unsubscribed) {\n return;\n }\n\n unsubscribed = true;\n this.#plugins.delete(factory);\n this.#unsubscribes.delete(unsubscribe);\n try {\n cleanup();\n } catch (error) {\n this.#deps.logger.error(\n LOGGER_CONTEXT,\n \"Error during cleanup:\",\n error,\n );\n }\n };\n\n this.#unsubscribes.add(unsubscribe);\n\n return unsubscribe;\n }\n\n // Deduplicate batch with warning (validation already done by facade)\n const seenInBatch = this.#deduplicateBatch(factories);\n\n // Track successfully initialized plugins for cleanup\n const initializedPlugins: {\n factory: PluginFactory<Dependencies>;\n cleanup: Unsubscribe;\n }[] = [];\n\n // Initialize deduplicated plugins sequentially\n try {\n for (const plugin of seenInBatch) {\n const cleanup = this.#startPlugin(plugin);\n\n initializedPlugins.push({ factory: plugin, cleanup });\n }\n } catch (error) {\n // Rollback on failure - cleanup all initialized plugins\n for (const { cleanup } of initializedPlugins) {\n try {\n cleanup();\n } catch (cleanupError) {\n this.#deps.logger.error(\n LOGGER_CONTEXT,\n \"Cleanup error:\",\n cleanupError,\n );\n }\n }\n\n throw error;\n }\n\n // Commit phase - add to registry\n for (const { factory } of initializedPlugins) {\n this.#plugins.add(factory);\n }\n\n // Return unsubscribe function\n let unsubscribed = false;\n\n const unsubscribe: Unsubscribe = () => {\n if (unsubscribed) {\n return;\n }\n\n unsubscribed = true;\n this.#unsubscribes.delete(unsubscribe);\n\n for (const { factory } of initializedPlugins) {\n this.#plugins.delete(factory);\n }\n\n for (const { cleanup } of initializedPlugins) {\n try {\n cleanup();\n } catch (error) {\n this.#deps.logger.error(\n LOGGER_CONTEXT,\n \"Error during cleanup:\",\n error,\n );\n }\n }\n };\n\n this.#unsubscribes.add(unsubscribe);\n\n return unsubscribe;\n }\n\n /**\n * Returns registered plugin factories.\n */\n getAll(): PluginFactory<Dependencies>[] {\n return [...this.#plugins];\n }\n\n /**\n * Checks if a plugin factory is registered.\n * Used internally by validation to avoid array allocation.\n */\n /* v8 ignore next 3 -- @preserve: only called via validator interface, not reachable without validation plugin */\n has(factory: PluginFactory<Dependencies>): boolean {\n return this.#plugins.has(factory);\n }\n\n /**\n * Disposes all registered plugins by running their teardown callbacks\n * and removing event listener subscriptions.\n * Active disposal is required because plugins have an active lifecycle\n * (event subscriptions, teardown hooks).\n * Named \"dispose\" (not \"clear\") because there is active cleanup to perform.\n */\n disposeAll(): void {\n for (const unsubscribe of this.#unsubscribes) {\n unsubscribe();\n }\n\n this.#plugins.clear();\n this.#unsubscribes.clear();\n }\n\n // =========================================================================\n // Private methods\n // =========================================================================\n\n /**\n * Deduplicates batch with warning for duplicates within batch.\n * Validation (existing duplicates) is done by facade.\n */\n #deduplicateBatch(\n plugins: PluginFactory<Dependencies>[],\n ): Set<PluginFactory<Dependencies>> {\n const seenInBatch = new Set<PluginFactory<Dependencies>>();\n\n for (const plugin of plugins) {\n if (seenInBatch.has(plugin)) {\n this.#deps.getValidator()?.plugins.warnBatchDuplicates(plugins);\n } else {\n seenInBatch.add(plugin);\n }\n }\n\n return seenInBatch;\n }\n\n #startPlugin(pluginFactory: PluginFactory<Dependencies>): Unsubscribe {\n const appliedPlugin = this.#deps.compileFactory(pluginFactory);\n\n PluginsNamespace.validatePlugin(appliedPlugin);\n this.#deps.getValidator()?.plugins.validatePluginKeys(appliedPlugin);\n\n Object.freeze(appliedPlugin);\n\n // Collect all unsubscribe functions\n const removeEventListeners: Unsubscribe[] = [];\n\n // Subscribe plugin methods to corresponding router events\n for (const methodName of EVENT_METHOD_NAMES) {\n if (methodName in appliedPlugin) {\n if (typeof appliedPlugin[methodName] === \"function\") {\n removeEventListeners.push(\n this.#deps.addEventListener(\n EVENTS_MAP[methodName],\n appliedPlugin[methodName],\n ),\n );\n\n if (methodName === \"onStart\" && this.#deps.canNavigate()) {\n this.#deps.getValidator()?.plugins.warnPluginAfterStart(methodName);\n }\n } else {\n this.#deps.getValidator()?.plugins.warnPluginMethodType(methodName);\n }\n }\n }\n\n // Return composite cleanup function\n return () => {\n for (const removeListener of removeEventListeners) {\n removeListener();\n }\n\n if (typeof appliedPlugin.teardown === \"function\") {\n appliedPlugin.teardown();\n }\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../../constants.js"),t={[e.plugins.ROUTER_START]:e.events.ROUTER_START,[e.plugins.ROUTER_STOP]:e.events.ROUTER_STOP,[e.plugins.TRANSITION_SUCCESS]:e.events.TRANSITION_SUCCESS,[e.plugins.TRANSITION_START]:e.events.TRANSITION_START,[e.plugins.TRANSITION_LEAVE_APPROVE]:e.events.TRANSITION_LEAVE_APPROVE,[e.plugins.TRANSITION_ERROR]:e.events.TRANSITION_ERROR,[e.plugins.TRANSITION_CANCEL]:e.events.TRANSITION_CANCEL},n=Object.keys(t);exports.EVENTS_MAP=t,exports.EVENT_METHOD_NAMES=n,exports.LOGGER_CONTEXT=`router.usePlugin`;
|
|
2
|
-
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":["PLUGINS_CONST","EVENTS_CONST"],"sources":["../../../../src/namespaces/PluginsNamespace/constants.ts"],"sourcesContent":["// packages/core/src/namespaces/PluginsNamespace/constants.ts\n\nimport {\n events as EVENTS_CONST,\n plugins as PLUGINS_CONST,\n} from \"../../constants\";\n\nimport type { EventName } from \"../../types\";\n\n/**\n * Maps plugin method names to router event names.\n */\nexport const EVENTS_MAP = {\n [PLUGINS_CONST.ROUTER_START]: EVENTS_CONST.ROUTER_START,\n [PLUGINS_CONST.ROUTER_STOP]: EVENTS_CONST.ROUTER_STOP,\n [PLUGINS_CONST.TRANSITION_SUCCESS]: EVENTS_CONST.TRANSITION_SUCCESS,\n [PLUGINS_CONST.TRANSITION_START]: EVENTS_CONST.TRANSITION_START,\n [PLUGINS_CONST.TRANSITION_LEAVE_APPROVE]:\n EVENTS_CONST.TRANSITION_LEAVE_APPROVE,\n [PLUGINS_CONST.TRANSITION_ERROR]: EVENTS_CONST.TRANSITION_ERROR,\n [PLUGINS_CONST.TRANSITION_CANCEL]: EVENTS_CONST.TRANSITION_CANCEL,\n} as const satisfies Record<\n (typeof PLUGINS_CONST)[keyof typeof PLUGINS_CONST],\n EventName\n>;\n\n/**\n * Plugin method names that correspond to router events.\n */\nexport const EVENT_METHOD_NAMES = Object.keys(\n EVENTS_MAP,\n) as (keyof typeof EVENTS_MAP)[];\n\nexport const LOGGER_CONTEXT = \"router.usePlugin\";\n"],"mappings":"sCAYa,EAAa,EACvBA,EAAAA,QAAc,cAAeC,EAAAA,OAAa,cAC1CD,EAAAA,QAAc,aAAcC,EAAAA,OAAa,aACzCD,EAAAA,QAAc,oBAAqBC,EAAAA,OAAa,oBAChDD,EAAAA,QAAc,kBAAmBC,EAAAA,OAAa,kBAC9CD,EAAAA,QAAc,0BACbC,EAAAA,OAAa,0BACdD,EAAAA,QAAc,kBAAmBC,EAAAA,OAAa,kBAC9CD,EAAAA,QAAc,mBAAoBC,EAAAA,OAAa,iBAClD,EAQa,EAAqB,OAAO,KACvC,CACF"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
function e(e){if(!(e&&typeof e==`object`)||Array.isArray(e))throw TypeError(`[router.usePlugin] Plugin factory must return an object, got ${typeof e}`);if(typeof e.then==`function`)throw TypeError(`[router.usePlugin] Async plugin factories are not supported. Factory returned a Promise instead of a plugin object.`)}exports.validatePlugin=e;
|
|
2
|
-
//# sourceMappingURL=validators.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validators.js","names":[],"sources":["../../../../src/namespaces/PluginsNamespace/validators.ts"],"sourcesContent":["// packages/core/src/namespaces/PluginsNamespace/validators.ts\n\n/**\n * Static validation functions for PluginsNamespace.\n * Called by Router facade before instance methods.\n */\n\nimport type { Plugin } from \"../../types\";\n\n/**\n * Validates that a plugin factory returned a valid plugin object.\n */\nexport function validatePlugin(plugin: Plugin): void {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!(plugin && typeof plugin === \"object\") || Array.isArray(plugin)) {\n throw new TypeError(\n `[router.usePlugin] Plugin factory must return an object, got ${typeof plugin}`,\n );\n }\n\n // Detect async factory (returns Promise)\n if (typeof (plugin as unknown as { then?: unknown }).then === \"function\") {\n throw new TypeError(\n `[router.usePlugin] Async plugin factories are not supported. ` +\n `Factory returned a Promise instead of a plugin object.`,\n );\n }\n}\n"],"mappings":"AAYA,SAAgB,EAAe,EAAsB,CAEnD,GAAI,EAAE,GAAU,OAAO,GAAW,WAAa,MAAM,QAAQ,CAAM,EACjE,MAAU,UACR,gEAAgE,OAAO,GACzE,EAIF,GAAI,OAAQ,EAAyC,MAAS,WAC5D,MAAU,UACR,qHAEF,CAEJ"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=()=>!0,t=()=>!1,n=()=>e,r=()=>t;function i(e){return e?n:r}var a=class{#e=new Map;#t=new Map;#n=new Map;#r=new Map;#i=new Map;#a=new Map;#o=[this.#i,this.#a];#s;setDependencies(e){this.#s=e}getHandlerCount(e){let t=e===`activate`?this.#e:this.#n,n=e===`activate`?this.#t:this.#r;if(t.size===0)return n.size;if(n.size===0)return t.size;let r=new Set(t.keys());for(let e of n.keys())r.add(e);return r.size}preflightHandlerLimit(e,t,n){let r=this.#s.getValidator();if(!r)return;let i=(e,t,i)=>{let{definition:a,external:o}=this.#d(e),s=0;for(let e of t)(n?o.has(e):a.has(e)||o.has(e))||s++;if(s===0)return;let c=n?o.size:this.getHandlerCount(e);r.lifecycle.validateHandlerLimit(c+s-1,i)};i(`activate`,e,`canActivate`),i(`deactivate`,t,`canDeactivate`)}addCanActivate(e,t,n=!1,r){this.#c(`activate`,e,t,n,`canActivate`,r)}addCanDeactivate(e,t,n=!1,r){this.#c(`deactivate`,e,t,n,`canDeactivate`,r)}clearCanActivate(e,t){this.#l(`activate`,e,t)}clearCanDeactivate(e,t){this.#l(`deactivate`,e,t)}clearAll(){this.#e.clear(),this.#t.clear(),this.#n.clear(),this.#r.clear(),this.#a.clear(),this.#i.clear()}clearDefinitionGuards(){for(let e of this.#e.keys())this.#t.has(e)?this.#u(`activate`,e):this.#a.delete(e);for(let e of this.#n.keys())this.#r.has(e)?this.#u(`deactivate`,e):this.#i.delete(e);this.#e.clear(),this.#n.clear()}getFactories(){let e={},t={};for(let[t,n]of this.#n)e[t]=n;for(let[t,n]of this.#r)e[t]=n;for(let[e,n]of this.#e)t[e]=n;for(let[e,n]of this.#t)t[e]=n;return[e,t]}getFactoriesByOrigin(){let e={},t={},n={},r={};for(let[t,n]of this.#n)e[t]=n;for(let[e,n]of this.#e)t[e]=n;for(let[e,t]of this.#r)n[e]=t;for(let[e,t]of this.#t)r[e]=t;return{definition:[e,t],external:[n,r]}}getFunctions(){return this.#o}canNavigateTo(e,t,n,r){for(let t of e)if(!this.#f(this.#i,t,n,r,`canNavigateTo`))return!1;for(let e of t)if(!this.#f(this.#a,e,n,r,`canNavigateTo`))return!1;return!0}compileGuardFactory(e,t){let n=typeof e==`boolean`?i(e):e,r=this.#s.compileFactory(n);if(typeof r!=`function`)throw TypeError(`[router.${t}] Factory must return a function, got ${typeof r}`);return r}#c(e,t,n,r,a,o){let s=this.#d(e),c=e===`activate`?this.#a:this.#i,l=r?s.definition:s.external,u=r?s.external:s.definition;if(l.has(t)||u.has(t))this.#s.getValidator()?.lifecycle.warnOverwrite(t,e,a);else{let t=this.#s.getValidator();if(t){let n=this.getHandlerCount(e);t.lifecycle.validateHandlerLimit(n,a),t.lifecycle.validateCountThresholds(n+1,a)}}let d=typeof n==`boolean`?i(n):n,f=l.get(t);l.set(t,d);let p=r&&u.has(t);try{let e=o??this.compileGuardFactory(d,a);p||c.set(t,e)}catch(n){throw f===void 0?l.delete(t):l.set(t,f),this.#u(e,t),n}}#l(e,t,n){let{definition:r,external:i}=this.#d(e),a=n!==`external`&&r.delete(t),o=n!==`definition`&&i.delete(t);(a||o)&&this.#u(e,t)}#u(e,t){let n=this.#d(e),r=e===`activate`?this.#a:this.#i,i=n.external.get(t)??n.definition.get(t);if(!i){r.delete(t);return}try{let e=this.#s.compileFactory(i);if(typeof e!=`function`){r.delete(t);return}r.set(t,e)}catch{r.delete(t)}}#d(e){return e===`activate`?{definition:this.#e,external:this.#t}:{definition:this.#n,external:this.#r}}#f(e,t,n,r,i){let a=e.get(t);if(!a)return!0;try{let e=a(n,r);return typeof e==`boolean`?e:(this.#s.getValidator()?.lifecycle.warnAsyncGuardSync(t,i),!1)}catch(e){return this.#s.logger.warn(`router.${i}`,`Guard for "${t}" threw — treated as navigation-blocking (returned false)`,e),!1}}};exports.RouteLifecycleNamespace=a;
|
|
2
|
-
//# sourceMappingURL=RouteLifecycleNamespace.js.map
|