@real-router/core 0.56.0 → 0.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/cjs/Router-Brp6_4FE.js +6 -0
  2. package/dist/cjs/Router-Brp6_4FE.js.map +1 -0
  3. package/dist/cjs/api.d.ts +1 -1
  4. package/dist/cjs/api.js +1 -1
  5. package/dist/cjs/{cloneRouter-DRieJvam.js → cloneRouter-CZx0T0RQ.js} +2 -2
  6. package/dist/cjs/{cloneRouter-DRieJvam.js.map → cloneRouter-CZx0T0RQ.js.map} +1 -1
  7. package/dist/cjs/{index-C-i6vx5Y.d.ts → index-BWUmnecT.d.ts} +1 -2
  8. package/dist/cjs/index-BWUmnecT.d.ts.map +1 -0
  9. package/dist/cjs/index-CYpAZCoc.d.ts.map +1 -1
  10. package/dist/cjs/index.d.ts +1 -1
  11. package/dist/cjs/index.js +1 -1
  12. package/dist/cjs/utils.js +1 -1
  13. package/dist/cjs/utils.js.map +1 -1
  14. package/dist/cjs/validation.d.ts +1 -1
  15. package/dist/esm/Router-LT61erYH.mjs +6 -0
  16. package/dist/esm/Router-LT61erYH.mjs.map +1 -0
  17. package/dist/esm/api.d.mts +1 -1
  18. package/dist/esm/api.mjs +1 -1
  19. package/dist/esm/{cloneRouter-DHrH6D_z.mjs → cloneRouter-DAscsmmF.mjs} +2 -2
  20. package/dist/esm/{cloneRouter-DHrH6D_z.mjs.map → cloneRouter-DAscsmmF.mjs.map} +1 -1
  21. package/dist/esm/{index-C-i6vx5Y.d.mts → index-BWUmnecT.d.mts} +1 -2
  22. package/dist/esm/index-BWUmnecT.d.mts.map +1 -0
  23. package/dist/esm/index-CYpAZCoc.d.mts.map +1 -1
  24. package/dist/esm/index.d.mts +1 -1
  25. package/dist/esm/index.mjs +1 -1
  26. package/dist/esm/utils.mjs +1 -1
  27. package/dist/esm/utils.mjs.map +1 -1
  28. package/dist/esm/validation.d.mts +1 -1
  29. package/package.json +4 -5
  30. package/dist/cjs/Router-IEGavTKk.js +0 -6
  31. package/dist/cjs/Router-IEGavTKk.js.map +0 -1
  32. package/dist/cjs/index-C-i6vx5Y.d.ts.map +0 -1
  33. package/dist/esm/Router-B3aeavRb.mjs +0 -6
  34. package/dist/esm/Router-B3aeavRb.mjs.map +0 -1
  35. package/dist/esm/index-C-i6vx5Y.d.mts.map +0 -1
  36. package/src/Router.ts +0 -737
  37. package/src/RouterError.ts +0 -324
  38. package/src/api/cloneRouter.ts +0 -159
  39. package/src/api/getDependenciesApi.ts +0 -160
  40. package/src/api/getLifecycleApi.ts +0 -65
  41. package/src/api/getPluginApi.ts +0 -228
  42. package/src/api/getRoutesApi.ts +0 -831
  43. package/src/api/helpers.ts +0 -10
  44. package/src/api/index.ts +0 -16
  45. package/src/api/types.ts +0 -12
  46. package/src/constants.ts +0 -101
  47. package/src/createRouter.ts +0 -32
  48. package/src/fsm/index.ts +0 -5
  49. package/src/fsm/routerFSM.ts +0 -130
  50. package/src/getNavigator.ts +0 -30
  51. package/src/guards.ts +0 -46
  52. package/src/helpers.ts +0 -197
  53. package/src/index.ts +0 -66
  54. package/src/internals.ts +0 -228
  55. package/src/namespaces/DependenciesNamespace/dependenciesStore.ts +0 -30
  56. package/src/namespaces/DependenciesNamespace/index.ts +0 -5
  57. package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +0 -522
  58. package/src/namespaces/EventBusNamespace/index.ts +0 -5
  59. package/src/namespaces/EventBusNamespace/types.ts +0 -11
  60. package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +0 -552
  61. package/src/namespaces/NavigationNamespace/constants.ts +0 -55
  62. package/src/namespaces/NavigationNamespace/index.ts +0 -5
  63. package/src/namespaces/NavigationNamespace/transition/completeTransition.ts +0 -108
  64. package/src/namespaces/NavigationNamespace/transition/errorHandling.ts +0 -124
  65. package/src/namespaces/NavigationNamespace/transition/guardPhase.ts +0 -283
  66. package/src/namespaces/NavigationNamespace/types.ts +0 -110
  67. package/src/namespaces/OptionsNamespace/OptionsNamespace.ts +0 -28
  68. package/src/namespaces/OptionsNamespace/constants.ts +0 -19
  69. package/src/namespaces/OptionsNamespace/helpers.ts +0 -50
  70. package/src/namespaces/OptionsNamespace/index.ts +0 -7
  71. package/src/namespaces/OptionsNamespace/validators.ts +0 -13
  72. package/src/namespaces/PluginsNamespace/PluginsNamespace.ts +0 -291
  73. package/src/namespaces/PluginsNamespace/constants.ts +0 -34
  74. package/src/namespaces/PluginsNamespace/index.ts +0 -7
  75. package/src/namespaces/PluginsNamespace/types.ts +0 -22
  76. package/src/namespaces/PluginsNamespace/validators.ts +0 -28
  77. package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +0 -558
  78. package/src/namespaces/RouteLifecycleNamespace/index.ts +0 -5
  79. package/src/namespaces/RouteLifecycleNamespace/types.ts +0 -10
  80. package/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts +0 -81
  81. package/src/namespaces/RouterLifecycleNamespace/constants.ts +0 -25
  82. package/src/namespaces/RouterLifecycleNamespace/index.ts +0 -5
  83. package/src/namespaces/RouterLifecycleNamespace/types.ts +0 -30
  84. package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +0 -582
  85. package/src/namespaces/RoutesNamespace/constants.ts +0 -6
  86. package/src/namespaces/RoutesNamespace/forwardChain.ts +0 -34
  87. package/src/namespaces/RoutesNamespace/helpers.ts +0 -204
  88. package/src/namespaces/RoutesNamespace/index.ts +0 -11
  89. package/src/namespaces/RoutesNamespace/routeGuards.ts +0 -62
  90. package/src/namespaces/RoutesNamespace/routesStore.ts +0 -566
  91. package/src/namespaces/RoutesNamespace/types.ts +0 -81
  92. package/src/namespaces/StateNamespace/StateNamespace.ts +0 -224
  93. package/src/namespaces/StateNamespace/helpers.ts +0 -24
  94. package/src/namespaces/StateNamespace/index.ts +0 -5
  95. package/src/namespaces/StateNamespace/types.ts +0 -15
  96. package/src/namespaces/index.ts +0 -35
  97. package/src/stateMetaStore.ts +0 -15
  98. package/src/transitionPath.ts +0 -440
  99. package/src/typeGuards.ts +0 -59
  100. package/src/types/RouterValidator.ts +0 -156
  101. package/src/types.ts +0 -77
  102. package/src/utils/createRequestScope.ts +0 -174
  103. package/src/utils/getStaticPaths.ts +0 -50
  104. package/src/utils/hydrateRouter.ts +0 -89
  105. package/src/utils/index.ts +0 -27
  106. package/src/utils/serializeRouterState.ts +0 -120
  107. package/src/utils/serializeState.ts +0 -63
  108. package/src/validation.ts +0 -12
  109. package/src/wiring/RouterWiringBuilder.ts +0 -275
  110. package/src/wiring/index.ts +0 -7
  111. package/src/wiring/types.ts +0 -47
  112. 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
@@ -1,5 +0,0 @@
1
- // packages/core/src/namespaces/NavigationNamespace/index.ts
2
-
3
- export { NavigationNamespace } from "./NavigationNamespace";
4
-
5
- export type { NavigationDependencies } from "./types";