@real-router/core 0.56.0 → 0.57.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-BSGzVINO.js +6 -0
- package/dist/cjs/Router-BSGzVINO.js.map +1 -0
- package/dist/cjs/api.d.ts +1 -1
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/{cloneRouter-DRieJvam.js → cloneRouter-7z-60z_f.js} +2 -2
- package/dist/cjs/{cloneRouter-DRieJvam.js.map → cloneRouter-7z-60z_f.js.map} +1 -1
- package/dist/cjs/{index-C-i6vx5Y.d.ts → index-BWUmnecT.d.ts} +1 -2
- package/dist/cjs/index-BWUmnecT.d.ts.map +1 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/utils.js +1 -1
- package/dist/cjs/validation.d.ts +1 -1
- package/dist/esm/Router-B7txWo9N.mjs +6 -0
- package/dist/esm/Router-B7txWo9N.mjs.map +1 -0
- package/dist/esm/api.d.mts +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/{cloneRouter-DHrH6D_z.mjs → cloneRouter-BNCQ7tIa.mjs} +2 -2
- package/dist/esm/{cloneRouter-DHrH6D_z.mjs.map → cloneRouter-BNCQ7tIa.mjs.map} +1 -1
- package/dist/esm/{index-C-i6vx5Y.d.mts → index-BWUmnecT.d.mts} +1 -2
- package/dist/esm/index-BWUmnecT.d.mts.map +1 -0
- package/dist/esm/index.d.mts +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/utils.mjs +1 -1
- package/dist/esm/validation.d.mts +1 -1
- package/package.json +2 -3
- package/dist/cjs/Router-IEGavTKk.js +0 -6
- package/dist/cjs/Router-IEGavTKk.js.map +0 -1
- package/dist/cjs/index-C-i6vx5Y.d.ts.map +0 -1
- package/dist/esm/Router-B3aeavRb.mjs +0 -6
- package/dist/esm/Router-B3aeavRb.mjs.map +0 -1
- package/dist/esm/index-C-i6vx5Y.d.mts.map +0 -1
- package/src/Router.ts +0 -737
- package/src/RouterError.ts +0 -324
- package/src/api/cloneRouter.ts +0 -159
- package/src/api/getDependenciesApi.ts +0 -160
- package/src/api/getLifecycleApi.ts +0 -65
- package/src/api/getPluginApi.ts +0 -228
- package/src/api/getRoutesApi.ts +0 -831
- package/src/api/helpers.ts +0 -10
- package/src/api/index.ts +0 -16
- package/src/api/types.ts +0 -12
- package/src/constants.ts +0 -101
- package/src/createRouter.ts +0 -32
- package/src/fsm/index.ts +0 -5
- package/src/fsm/routerFSM.ts +0 -130
- package/src/getNavigator.ts +0 -30
- package/src/guards.ts +0 -46
- package/src/helpers.ts +0 -197
- package/src/index.ts +0 -66
- package/src/internals.ts +0 -228
- package/src/namespaces/DependenciesNamespace/dependenciesStore.ts +0 -30
- package/src/namespaces/DependenciesNamespace/index.ts +0 -5
- package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +0 -522
- package/src/namespaces/EventBusNamespace/index.ts +0 -5
- package/src/namespaces/EventBusNamespace/types.ts +0 -11
- package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +0 -552
- package/src/namespaces/NavigationNamespace/constants.ts +0 -55
- package/src/namespaces/NavigationNamespace/index.ts +0 -5
- package/src/namespaces/NavigationNamespace/transition/completeTransition.ts +0 -108
- package/src/namespaces/NavigationNamespace/transition/errorHandling.ts +0 -124
- package/src/namespaces/NavigationNamespace/transition/guardPhase.ts +0 -283
- package/src/namespaces/NavigationNamespace/types.ts +0 -110
- package/src/namespaces/OptionsNamespace/OptionsNamespace.ts +0 -28
- package/src/namespaces/OptionsNamespace/constants.ts +0 -19
- package/src/namespaces/OptionsNamespace/helpers.ts +0 -50
- package/src/namespaces/OptionsNamespace/index.ts +0 -7
- package/src/namespaces/OptionsNamespace/validators.ts +0 -13
- package/src/namespaces/PluginsNamespace/PluginsNamespace.ts +0 -291
- package/src/namespaces/PluginsNamespace/constants.ts +0 -34
- package/src/namespaces/PluginsNamespace/index.ts +0 -7
- package/src/namespaces/PluginsNamespace/types.ts +0 -22
- package/src/namespaces/PluginsNamespace/validators.ts +0 -28
- package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +0 -558
- package/src/namespaces/RouteLifecycleNamespace/index.ts +0 -5
- package/src/namespaces/RouteLifecycleNamespace/types.ts +0 -10
- package/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts +0 -81
- package/src/namespaces/RouterLifecycleNamespace/constants.ts +0 -25
- package/src/namespaces/RouterLifecycleNamespace/index.ts +0 -5
- package/src/namespaces/RouterLifecycleNamespace/types.ts +0 -30
- package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +0 -582
- package/src/namespaces/RoutesNamespace/constants.ts +0 -6
- package/src/namespaces/RoutesNamespace/forwardChain.ts +0 -34
- package/src/namespaces/RoutesNamespace/helpers.ts +0 -204
- package/src/namespaces/RoutesNamespace/index.ts +0 -11
- package/src/namespaces/RoutesNamespace/routeGuards.ts +0 -62
- package/src/namespaces/RoutesNamespace/routesStore.ts +0 -566
- package/src/namespaces/RoutesNamespace/types.ts +0 -81
- package/src/namespaces/StateNamespace/StateNamespace.ts +0 -224
- package/src/namespaces/StateNamespace/helpers.ts +0 -24
- package/src/namespaces/StateNamespace/index.ts +0 -5
- package/src/namespaces/StateNamespace/types.ts +0 -15
- package/src/namespaces/index.ts +0 -35
- package/src/stateMetaStore.ts +0 -15
- package/src/transitionPath.ts +0 -440
- package/src/typeGuards.ts +0 -59
- package/src/types/RouterValidator.ts +0 -156
- package/src/types.ts +0 -77
- package/src/utils/createRequestScope.ts +0 -174
- package/src/utils/getStaticPaths.ts +0 -50
- package/src/utils/hydrateRouter.ts +0 -89
- package/src/utils/index.ts +0 -27
- package/src/utils/serializeRouterState.ts +0 -120
- package/src/utils/serializeState.ts +0 -63
- package/src/validation.ts +0 -12
- package/src/wiring/RouterWiringBuilder.ts +0 -275
- package/src/wiring/index.ts +0 -7
- package/src/wiring/types.ts +0 -47
- package/src/wiring/wireRouter.ts +0 -26
|
@@ -1,552 +0,0 @@
|
|
|
1
|
-
import { logger } from "@real-router/logger";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
CACHED_NOT_STARTED_REJECTION,
|
|
5
|
-
CACHED_ROUTE_NOT_FOUND_ERROR,
|
|
6
|
-
CACHED_ROUTE_NOT_FOUND_REJECTION,
|
|
7
|
-
CACHED_SAME_STATES_ERROR,
|
|
8
|
-
CACHED_SAME_STATES_REJECTION,
|
|
9
|
-
} from "./constants";
|
|
10
|
-
import { completeTransition } from "./transition/completeTransition";
|
|
11
|
-
import { routeTransitionError } from "./transition/errorHandling";
|
|
12
|
-
import { executeGuardPipeline } from "./transition/guardPhase";
|
|
13
|
-
import { EMPTY_PARAMS, errorCodes, constants } from "../../constants";
|
|
14
|
-
import { RouterError } from "../../RouterError";
|
|
15
|
-
import { getTransitionPath, nameToIDs } from "../../transitionPath";
|
|
16
|
-
|
|
17
|
-
import type { NavigationContext, NavigationDependencies } from "./types";
|
|
18
|
-
import type { TransitionPath } from "../../transitionPath";
|
|
19
|
-
import type {
|
|
20
|
-
GuardFn,
|
|
21
|
-
NavigationOptions,
|
|
22
|
-
Params,
|
|
23
|
-
State,
|
|
24
|
-
TransitionMeta,
|
|
25
|
-
} from "@real-router/types";
|
|
26
|
-
|
|
27
|
-
const FROZEN_ACTIVATED: string[] = [constants.UNKNOWN_ROUTE];
|
|
28
|
-
|
|
29
|
-
Object.freeze(FROZEN_ACTIVATED);
|
|
30
|
-
const FROZEN_REPLACE_OPTS: NavigationOptions = { replace: true };
|
|
31
|
-
|
|
32
|
-
Object.freeze(FROZEN_REPLACE_OPTS);
|
|
33
|
-
|
|
34
|
-
function forceReplaceFromUnknown(
|
|
35
|
-
opts: NavigationOptions,
|
|
36
|
-
fromState: State | undefined,
|
|
37
|
-
): NavigationOptions {
|
|
38
|
-
return fromState?.name === constants.UNKNOWN_ROUTE && !opts.replace
|
|
39
|
-
? { ...opts, replace: true }
|
|
40
|
-
: opts;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function isSameNavigation(
|
|
44
|
-
fromState: State | undefined,
|
|
45
|
-
opts: NavigationOptions,
|
|
46
|
-
toState: State,
|
|
47
|
-
): boolean {
|
|
48
|
-
return (
|
|
49
|
-
!!fromState &&
|
|
50
|
-
!opts.reload &&
|
|
51
|
-
!opts.force &&
|
|
52
|
-
fromState.path === toState.path
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Independent namespace for managing navigation.
|
|
58
|
-
*
|
|
59
|
-
* Handles navigate(), navigateToDefault(), navigateToNotFound(), and transition state.
|
|
60
|
-
*
|
|
61
|
-
* Performance: navigate() uses optimistic sync execution — guards run synchronously
|
|
62
|
-
* until one returns a Promise, then switches to async. This eliminates Promise/AbortController
|
|
63
|
-
* overhead for the common case (no guards or sync guards).
|
|
64
|
-
*/
|
|
65
|
-
export class NavigationNamespace {
|
|
66
|
-
lastSyncResolved = false;
|
|
67
|
-
lastSyncRejected = false;
|
|
68
|
-
#deps!: NavigationDependencies;
|
|
69
|
-
#currentController: AbortController | null = null;
|
|
70
|
-
#navigationId = 0;
|
|
71
|
-
|
|
72
|
-
// =========================================================================
|
|
73
|
-
// Dependency injection
|
|
74
|
-
// =========================================================================
|
|
75
|
-
|
|
76
|
-
setDependencies(deps: NavigationDependencies): void {
|
|
77
|
-
this.#deps = deps;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// =========================================================================
|
|
81
|
-
// Instance methods
|
|
82
|
-
// =========================================================================
|
|
83
|
-
|
|
84
|
-
navigate(
|
|
85
|
-
name: string,
|
|
86
|
-
params: Params,
|
|
87
|
-
opts: NavigationOptions,
|
|
88
|
-
): Promise<State> {
|
|
89
|
-
this.lastSyncResolved = false;
|
|
90
|
-
const deps = this.#deps;
|
|
91
|
-
|
|
92
|
-
// Fast-path sync rejections: cached error + cached Promise.reject
|
|
93
|
-
// No allocations, no throw/catch overhead, facade skips .catch() suppression
|
|
94
|
-
if (!deps.canNavigate()) {
|
|
95
|
-
this.lastSyncRejected = true;
|
|
96
|
-
|
|
97
|
-
return CACHED_NOT_STARTED_REJECTION;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
let toState: State | undefined;
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
toState = deps.buildNavigateState(name, params);
|
|
104
|
-
} catch (error) {
|
|
105
|
-
/* v8 ignore next 3 -- @preserve: reachable only via validator-driven
|
|
106
|
-
throws from buildNavigateState (validateStateBuilderArgs) — covered
|
|
107
|
-
in @real-router/validation-plugin's suite, not in core. */
|
|
108
|
-
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided buildNavigateState
|
|
109
|
-
return Promise.reject(error);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (!toState) {
|
|
113
|
-
deps.emitTransitionError(
|
|
114
|
-
undefined,
|
|
115
|
-
deps.getState(),
|
|
116
|
-
CACHED_ROUTE_NOT_FOUND_ERROR,
|
|
117
|
-
);
|
|
118
|
-
this.lastSyncRejected = true;
|
|
119
|
-
|
|
120
|
-
return CACHED_ROUTE_NOT_FOUND_REJECTION;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return this.#executeNavigation(toState, opts);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Navigate to a fully-built `State` directly, skipping `buildNavigateState`
|
|
128
|
-
* (forwardState + buildPath + meta lookup). Used by URL plugins after they
|
|
129
|
-
* have already produced a `State` from a browser-initiated event via
|
|
130
|
-
* `api.matchPath(url)` — see issue #525.
|
|
131
|
-
*
|
|
132
|
-
* Semantics vs. `navigate(name, params, opts)`:
|
|
133
|
-
* - `forwardState` is NOT re-applied. matchPath already runs it; reapplying
|
|
134
|
-
* is redundant in the idempotent case and can race in the dynamic case.
|
|
135
|
-
* - `buildPath` is NOT re-run. The caller's `state.path` is used as-is —
|
|
136
|
-
* so `trailingSlash:"preserve"` matchedState paths flow through unchanged
|
|
137
|
-
* (closes #525 Q2). `buildPath` interceptors do NOT run; the URL the
|
|
138
|
-
* user navigated to is the source of truth for this code path.
|
|
139
|
-
* - All other pipeline steps run unchanged: SAME_STATES check, FSM
|
|
140
|
-
* transition, guards, `subscribeLeave`, `completeTransition`,
|
|
141
|
-
* plugin lifecycle hooks.
|
|
142
|
-
*/
|
|
143
|
-
navigateToState(state: State, opts: NavigationOptions): Promise<State> {
|
|
144
|
-
this.lastSyncResolved = false;
|
|
145
|
-
const deps = this.#deps;
|
|
146
|
-
|
|
147
|
-
if (!deps.canNavigate()) {
|
|
148
|
-
this.lastSyncRejected = true;
|
|
149
|
-
|
|
150
|
-
return CACHED_NOT_STARTED_REJECTION;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Reject states whose route no longer exists (e.g. the route tree was
|
|
154
|
-
// mutated between matchPath and navigateToState). UNKNOWN_ROUTE is
|
|
155
|
-
// structurally legal — it is the navigateToNotFound output shape.
|
|
156
|
-
if (state.name !== constants.UNKNOWN_ROUTE && !deps.hasRoute(state.name)) {
|
|
157
|
-
const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {
|
|
158
|
-
routeName: state.name,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
deps.emitTransitionError(undefined, deps.getState(), err);
|
|
162
|
-
this.lastSyncRejected = true;
|
|
163
|
-
|
|
164
|
-
return Promise.reject(err);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// States from `matchPath` are deeply frozen (`freezeStateInPlace`).
|
|
168
|
-
// `completeTransition` mutates `toState.transition` and `context` is
|
|
169
|
-
// intentionally extensible for plugin claim writes, so we hand the
|
|
170
|
-
// pipeline a writable shell — same shape `makeState(skipFreeze=true)`
|
|
171
|
-
// produces. `params` stays referentially shared (already frozen).
|
|
172
|
-
// `transition` is omitted so completeTransition can assign it.
|
|
173
|
-
const writableState = {
|
|
174
|
-
name: state.name,
|
|
175
|
-
params: state.params,
|
|
176
|
-
path: state.path,
|
|
177
|
-
context: { ...state.context },
|
|
178
|
-
} as State;
|
|
179
|
-
|
|
180
|
-
return this.#executeNavigation(writableState, opts);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
navigateToDefault(opts: NavigationOptions): Promise<State> {
|
|
184
|
-
const deps = this.#deps;
|
|
185
|
-
const options = deps.getOptions();
|
|
186
|
-
|
|
187
|
-
if (!options.defaultRoute) {
|
|
188
|
-
return Promise.reject(
|
|
189
|
-
new RouterError(errorCodes.ROUTE_NOT_FOUND, {
|
|
190
|
-
routeName: "defaultRoute not configured",
|
|
191
|
-
}),
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
let route: string;
|
|
196
|
-
let params: Params;
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
({ route, params } = deps.resolveDefault());
|
|
200
|
-
} catch (error) {
|
|
201
|
-
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from user-provided resolveDefault callback
|
|
202
|
-
return Promise.reject(error);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (!route) {
|
|
206
|
-
return Promise.reject(
|
|
207
|
-
new RouterError(errorCodes.ROUTE_NOT_FOUND, {
|
|
208
|
-
routeName: "defaultRoute resolved to empty",
|
|
209
|
-
}),
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return this.navigate(route, params, opts);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
navigateToNotFound(path: string): State {
|
|
217
|
-
this.#abortPreviousNavigation();
|
|
218
|
-
|
|
219
|
-
const fromState = this.#deps.getState();
|
|
220
|
-
const deactivated: string[] = fromState
|
|
221
|
-
? nameToIDs(fromState.name).toReversed()
|
|
222
|
-
: [];
|
|
223
|
-
|
|
224
|
-
Object.freeze(deactivated);
|
|
225
|
-
|
|
226
|
-
const segments: TransitionMeta["segments"] = {
|
|
227
|
-
deactivated,
|
|
228
|
-
activated: FROZEN_ACTIVATED,
|
|
229
|
-
intersection: "",
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
Object.freeze(segments);
|
|
233
|
-
|
|
234
|
-
const transitionMeta: TransitionMeta = {
|
|
235
|
-
phase: "activating",
|
|
236
|
-
...(fromState && { from: fromState.name }),
|
|
237
|
-
reason: "success",
|
|
238
|
-
replace: true,
|
|
239
|
-
segments,
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
Object.freeze(transitionMeta);
|
|
243
|
-
|
|
244
|
-
const state: State = {
|
|
245
|
-
name: constants.UNKNOWN_ROUTE,
|
|
246
|
-
params: EMPTY_PARAMS,
|
|
247
|
-
path,
|
|
248
|
-
transition: transitionMeta,
|
|
249
|
-
context: {},
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
Object.freeze(state);
|
|
253
|
-
|
|
254
|
-
this.#deps.setState(state);
|
|
255
|
-
this.#deps.emitTransitionSuccess(state, fromState, FROZEN_REPLACE_OPTS);
|
|
256
|
-
|
|
257
|
-
return state;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
abortCurrentNavigation(): void {
|
|
261
|
-
this.#currentController?.abort(
|
|
262
|
-
new RouterError(errorCodes.TRANSITION_CANCELLED),
|
|
263
|
-
);
|
|
264
|
-
this.#currentController = null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
#executeNavigation(toState: State, opts: NavigationOptions): Promise<State> {
|
|
268
|
-
const deps = this.#deps;
|
|
269
|
-
let fromState: State | undefined;
|
|
270
|
-
let transitionStarted = false;
|
|
271
|
-
let controller: AbortController | null = null;
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
fromState = deps.getState();
|
|
275
|
-
opts = forceReplaceFromUnknown(opts, fromState);
|
|
276
|
-
|
|
277
|
-
if (isSameNavigation(fromState, opts, toState)) {
|
|
278
|
-
deps.emitTransitionError(toState, fromState, CACHED_SAME_STATES_ERROR);
|
|
279
|
-
this.lastSyncRejected = true;
|
|
280
|
-
|
|
281
|
-
return CACHED_SAME_STATES_REJECTION;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
this.#abortPreviousNavigation(opts.signal);
|
|
285
|
-
|
|
286
|
-
const myId = ++this.#navigationId;
|
|
287
|
-
|
|
288
|
-
deps.startTransition(toState, fromState);
|
|
289
|
-
transitionStarted = true;
|
|
290
|
-
|
|
291
|
-
// Reentrant navigate from TRANSITION_START listener superseded this navigation
|
|
292
|
-
if (this.#navigationId !== myId) {
|
|
293
|
-
throw new RouterError(errorCodes.TRANSITION_CANCELLED);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const [canDeactivateFunctions, canActivateFunctions] =
|
|
297
|
-
deps.getLifecycleFunctions();
|
|
298
|
-
const isUnknownRoute = toState.name === constants.UNKNOWN_ROUTE;
|
|
299
|
-
|
|
300
|
-
const transitionPath = getTransitionPath(toState, fromState);
|
|
301
|
-
const { toDeactivate, toActivate, intersection } = transitionPath;
|
|
302
|
-
|
|
303
|
-
const shouldDeactivate =
|
|
304
|
-
fromState && !opts.forceDeactivate && toDeactivate.length > 0;
|
|
305
|
-
const shouldActivate = !isUnknownRoute && toActivate.length > 0;
|
|
306
|
-
const hasGuards =
|
|
307
|
-
canDeactivateFunctions.size > 0 || canActivateFunctions.size > 0;
|
|
308
|
-
|
|
309
|
-
const confirmedToState = toState;
|
|
310
|
-
|
|
311
|
-
if (!hasGuards) {
|
|
312
|
-
const asyncLeave = this.#handleNoGuardsLeave(
|
|
313
|
-
confirmedToState,
|
|
314
|
-
fromState,
|
|
315
|
-
myId,
|
|
316
|
-
opts,
|
|
317
|
-
transitionPath,
|
|
318
|
-
canDeactivateFunctions,
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
/* v8 ignore start */
|
|
322
|
-
if (asyncLeave !== undefined) {
|
|
323
|
-
return asyncLeave;
|
|
324
|
-
}
|
|
325
|
-
/* v8 ignore stop */
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (hasGuards) {
|
|
329
|
-
controller = new AbortController();
|
|
330
|
-
this.#currentController = controller;
|
|
331
|
-
const isCurrentNav = () =>
|
|
332
|
-
this.#navigationId === myId && deps.isActive();
|
|
333
|
-
|
|
334
|
-
const signal = controller.signal;
|
|
335
|
-
|
|
336
|
-
const emitLeaveApproveCallback = (): Promise<void> | undefined => {
|
|
337
|
-
deps.sendLeaveApprove(confirmedToState, fromState);
|
|
338
|
-
|
|
339
|
-
if (deps.hasLeaveListeners()) {
|
|
340
|
-
return deps.awaitLeaveListeners(
|
|
341
|
-
confirmedToState,
|
|
342
|
-
fromState,
|
|
343
|
-
signal,
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return undefined;
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
const guardCompletion = executeGuardPipeline(
|
|
351
|
-
canDeactivateFunctions,
|
|
352
|
-
canActivateFunctions,
|
|
353
|
-
toDeactivate,
|
|
354
|
-
toActivate,
|
|
355
|
-
!!shouldDeactivate,
|
|
356
|
-
shouldActivate,
|
|
357
|
-
toState,
|
|
358
|
-
fromState,
|
|
359
|
-
signal,
|
|
360
|
-
isCurrentNav,
|
|
361
|
-
emitLeaveApproveCallback,
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
if (guardCompletion !== undefined) {
|
|
365
|
-
return this.#finishAsyncNavigation(
|
|
366
|
-
guardCompletion,
|
|
367
|
-
{
|
|
368
|
-
toState,
|
|
369
|
-
fromState,
|
|
370
|
-
opts,
|
|
371
|
-
toDeactivate,
|
|
372
|
-
toActivate,
|
|
373
|
-
intersection,
|
|
374
|
-
canDeactivateFunctions,
|
|
375
|
-
},
|
|
376
|
-
controller,
|
|
377
|
-
myId,
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (!isCurrentNav()) {
|
|
382
|
-
throw new RouterError(errorCodes.TRANSITION_CANCELLED);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
this.#cleanupController(controller);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
this.lastSyncResolved = true;
|
|
389
|
-
|
|
390
|
-
return Promise.resolve(
|
|
391
|
-
completeTransition(deps, {
|
|
392
|
-
toState,
|
|
393
|
-
fromState,
|
|
394
|
-
opts,
|
|
395
|
-
toDeactivate,
|
|
396
|
-
toActivate,
|
|
397
|
-
intersection,
|
|
398
|
-
canDeactivateFunctions,
|
|
399
|
-
}),
|
|
400
|
-
);
|
|
401
|
-
} catch (error) {
|
|
402
|
-
this.#handleNavigateError(
|
|
403
|
-
error,
|
|
404
|
-
controller,
|
|
405
|
-
transitionStarted,
|
|
406
|
-
toState,
|
|
407
|
-
fromState,
|
|
408
|
-
);
|
|
409
|
-
|
|
410
|
-
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- preserve original throw shape from guards or transition pipeline
|
|
411
|
-
return Promise.reject(error);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
async #finishAsyncNavigation(
|
|
416
|
-
guardCompletion: Promise<void>,
|
|
417
|
-
nav: NavigationContext,
|
|
418
|
-
controller: AbortController,
|
|
419
|
-
myId: number,
|
|
420
|
-
): Promise<State> {
|
|
421
|
-
const deps = this.#deps;
|
|
422
|
-
const isActive = () =>
|
|
423
|
-
this.#navigationId === myId &&
|
|
424
|
-
!controller.signal.aborted &&
|
|
425
|
-
deps.isActive();
|
|
426
|
-
|
|
427
|
-
try {
|
|
428
|
-
if (nav.opts.signal) {
|
|
429
|
-
if (nav.opts.signal.aborted) {
|
|
430
|
-
throw new RouterError(errorCodes.TRANSITION_CANCELLED, {
|
|
431
|
-
reason: nav.opts.signal.reason,
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
nav.opts.signal.addEventListener(
|
|
436
|
-
"abort",
|
|
437
|
-
() => {
|
|
438
|
-
controller.abort(nav.opts.signal?.reason);
|
|
439
|
-
},
|
|
440
|
-
{ once: true, signal: controller.signal },
|
|
441
|
-
);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
await guardCompletion;
|
|
445
|
-
|
|
446
|
-
if (!isActive()) {
|
|
447
|
-
throw new RouterError(errorCodes.TRANSITION_CANCELLED);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return completeTransition(deps, nav);
|
|
451
|
-
} catch (error) {
|
|
452
|
-
routeTransitionError(deps, error, nav.toState, nav.fromState);
|
|
453
|
-
|
|
454
|
-
throw error;
|
|
455
|
-
} finally {
|
|
456
|
-
this.#cleanupController(controller);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
#handleNavigateError(
|
|
461
|
-
error: unknown,
|
|
462
|
-
controller: AbortController | null,
|
|
463
|
-
transitionStarted: boolean,
|
|
464
|
-
toState: State | undefined,
|
|
465
|
-
fromState: State | undefined,
|
|
466
|
-
): void {
|
|
467
|
-
if (controller) {
|
|
468
|
-
this.#cleanupController(controller);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (transitionStarted && toState) {
|
|
472
|
-
routeTransitionError(this.#deps, error, toState, fromState);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
#handleNoGuardsLeave(
|
|
477
|
-
toState: State,
|
|
478
|
-
fromState: State | undefined,
|
|
479
|
-
myId: number,
|
|
480
|
-
opts: NavigationOptions,
|
|
481
|
-
transitionPath: TransitionPath,
|
|
482
|
-
canDeactivateFunctions: Map<string, GuardFn>,
|
|
483
|
-
): Promise<State> | undefined {
|
|
484
|
-
const deps = this.#deps;
|
|
485
|
-
|
|
486
|
-
deps.sendLeaveApprove(toState, fromState);
|
|
487
|
-
|
|
488
|
-
/* v8 ignore start */
|
|
489
|
-
if (deps.hasLeaveListeners()) {
|
|
490
|
-
const controller = new AbortController();
|
|
491
|
-
const leaveResult = deps.awaitLeaveListeners(
|
|
492
|
-
toState,
|
|
493
|
-
fromState,
|
|
494
|
-
controller.signal,
|
|
495
|
-
);
|
|
496
|
-
|
|
497
|
-
if (leaveResult !== undefined) {
|
|
498
|
-
this.#currentController = controller;
|
|
499
|
-
|
|
500
|
-
return this.#finishAsyncNavigation(
|
|
501
|
-
leaveResult,
|
|
502
|
-
{
|
|
503
|
-
toState,
|
|
504
|
-
fromState,
|
|
505
|
-
opts,
|
|
506
|
-
toDeactivate: transitionPath.toDeactivate,
|
|
507
|
-
toActivate: transitionPath.toActivate,
|
|
508
|
-
intersection: transitionPath.intersection,
|
|
509
|
-
canDeactivateFunctions,
|
|
510
|
-
},
|
|
511
|
-
controller,
|
|
512
|
-
myId,
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (this.#navigationId !== myId) {
|
|
518
|
-
throw new RouterError(errorCodes.TRANSITION_CANCELLED);
|
|
519
|
-
}
|
|
520
|
-
/* v8 ignore stop */
|
|
521
|
-
|
|
522
|
-
return undefined;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
#cleanupController(controller: AbortController): void {
|
|
526
|
-
controller.abort();
|
|
527
|
-
|
|
528
|
-
if (this.#currentController === controller) {
|
|
529
|
-
this.#currentController = null;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
#abortPreviousNavigation(externalSignal?: AbortSignal): void {
|
|
534
|
-
if (this.#deps.isTransitioning()) {
|
|
535
|
-
logger.warn(
|
|
536
|
-
"router.navigate",
|
|
537
|
-
"Concurrent navigation detected on shared router instance. " +
|
|
538
|
-
"For SSR, use cloneRouter() to create isolated instance per request.",
|
|
539
|
-
);
|
|
540
|
-
this.#currentController?.abort(
|
|
541
|
-
new RouterError(errorCodes.TRANSITION_CANCELLED),
|
|
542
|
-
);
|
|
543
|
-
this.#deps.cancelNavigation();
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
if (externalSignal?.aborted) {
|
|
547
|
-
throw new RouterError(errorCodes.TRANSITION_CANCELLED, {
|
|
548
|
-
reason: externalSignal.reason,
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// packages/core/src/namespaces/NavigationNamespace/constants.ts
|
|
2
|
-
|
|
3
|
-
import { errorCodes } from "../../constants";
|
|
4
|
-
import { RouterError } from "../../RouterError";
|
|
5
|
-
|
|
6
|
-
import type { State } from "@real-router/types";
|
|
7
|
-
|
|
8
|
-
// =============================================================================
|
|
9
|
-
// Cached Errors & Rejected Promises (Performance Optimization)
|
|
10
|
-
// =============================================================================
|
|
11
|
-
// Pre-create error instances and rejected promises for sync error paths
|
|
12
|
-
// in navigate(). Eliminates per-call allocations:
|
|
13
|
-
// - new RouterError() — object + stack trace capture (~500ns-2μs)
|
|
14
|
-
// - Promise.reject() — promise allocation
|
|
15
|
-
// - .catch(handler) — derived promise from suppression
|
|
16
|
-
//
|
|
17
|
-
// Trade-off: All error instances share the same stack trace (points here).
|
|
18
|
-
// This is acceptable because:
|
|
19
|
-
// 1. These errors indicate expected conditions, not internal bugs
|
|
20
|
-
// 2. Error code and message are sufficient for debugging
|
|
21
|
-
// 3. The facade skips .catch() suppression for cached promises (zero alloc)
|
|
22
|
-
// =============================================================================
|
|
23
|
-
|
|
24
|
-
export const CACHED_NOT_STARTED_ERROR = new RouterError(
|
|
25
|
-
errorCodes.ROUTER_NOT_STARTED,
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
export const CACHED_ROUTE_NOT_FOUND_ERROR = new RouterError(
|
|
29
|
-
errorCodes.ROUTE_NOT_FOUND,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
export const CACHED_SAME_STATES_ERROR = new RouterError(errorCodes.SAME_STATES);
|
|
33
|
-
|
|
34
|
-
// Pre-suppressed rejected promises — .catch() at module load prevents
|
|
35
|
-
// unhandled rejection warnings. The facade skips additional .catch() calls
|
|
36
|
-
// via the lastSyncRejected flag (zero derived-promise allocation).
|
|
37
|
-
export const CACHED_NOT_STARTED_REJECTION: Promise<State> = Promise.reject(
|
|
38
|
-
CACHED_NOT_STARTED_ERROR,
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
export const CACHED_ROUTE_NOT_FOUND_REJECTION: Promise<State> = Promise.reject(
|
|
42
|
-
CACHED_ROUTE_NOT_FOUND_ERROR,
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
export const CACHED_SAME_STATES_REJECTION: Promise<State> = Promise.reject(
|
|
46
|
-
CACHED_SAME_STATES_ERROR,
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// Suppress once at module load — prevents unhandled rejection events.
|
|
50
|
-
// Subsequent .catch() / await by user code still works correctly:
|
|
51
|
-
// a rejected promise stays rejected forever, each .catch() creates
|
|
52
|
-
// its own derived promise and fires its handler.
|
|
53
|
-
CACHED_NOT_STARTED_REJECTION.catch(() => {}); // NOSONAR -- intentional suppression, not a promise chain
|
|
54
|
-
CACHED_ROUTE_NOT_FOUND_REJECTION.catch(() => {}); // NOSONAR
|
|
55
|
-
CACHED_SAME_STATES_REJECTION.catch(() => {}); // NOSONAR
|