@real-router/core 0.36.1 → 0.36.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/metafile-cjs.json +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/chunk-PKKD6URG.mjs +1 -0
- package/dist/esm/chunk-PKKD6URG.mjs.map +1 -0
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/metafile-esm.json +1 -1
- package/package.json +4 -4
- package/src/Router.ts +33 -12
- package/src/api/getPluginApi.ts +10 -4
- package/src/constants.ts +2 -0
- package/src/fsm/routerFSM.ts +2 -21
- package/src/internals.ts +21 -2
- package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +35 -39
- package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +221 -153
- package/src/namespaces/NavigationNamespace/constants.ts +55 -0
- package/src/namespaces/NavigationNamespace/transition/completeTransition.ts +100 -0
- package/src/namespaces/NavigationNamespace/transition/errorHandling.ts +34 -0
- package/src/namespaces/NavigationNamespace/transition/guardPhase.ts +214 -0
- package/src/namespaces/NavigationNamespace/types.ts +14 -30
- package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +6 -1
- package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +36 -35
- package/src/namespaces/RoutesNamespace/forwardToValidation.ts +2 -5
- package/src/namespaces/RoutesNamespace/types.ts +1 -2
- package/src/namespaces/StateNamespace/StateNamespace.ts +13 -17
- package/src/transitionPath.ts +68 -39
- package/src/wiring/RouterWiringBuilder.ts +8 -11
- package/dist/esm/chunk-HZ7RFKT5.mjs +0 -1
- package/dist/esm/chunk-HZ7RFKT5.mjs.map +0 -1
- package/src/namespaces/NavigationNamespace/transition/executeLifecycleGuards.ts +0 -52
- package/src/namespaces/NavigationNamespace/transition/index.ts +0 -93
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { handleGuardError } from "./errorHandling";
|
|
2
|
+
import { errorCodes } from "../../../constants";
|
|
3
|
+
import { RouterError } from "../../../RouterError";
|
|
4
|
+
|
|
5
|
+
import type { GuardFn, State } from "@real-router/types";
|
|
6
|
+
|
|
7
|
+
async function resolveAsyncGuard(
|
|
8
|
+
promise: Promise<boolean>,
|
|
9
|
+
errorCode: string,
|
|
10
|
+
segment: string,
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
let result: boolean;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
result = await promise;
|
|
16
|
+
} catch (error: unknown) {
|
|
17
|
+
handleGuardError(error, errorCode, segment);
|
|
18
|
+
|
|
19
|
+
return; // unreachable — handleGuardError returns never
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!result) {
|
|
23
|
+
throw new RouterError(errorCode, { segment });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function resolveRemainingGuards(
|
|
28
|
+
guards: Map<string, GuardFn>,
|
|
29
|
+
segments: string[],
|
|
30
|
+
errorCode: string,
|
|
31
|
+
toState: State,
|
|
32
|
+
fromState: State | undefined,
|
|
33
|
+
signal: AbortSignal | undefined,
|
|
34
|
+
isActive: () => boolean,
|
|
35
|
+
startIndex: number,
|
|
36
|
+
firstResult: Promise<boolean>,
|
|
37
|
+
firstSegment: string,
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
await resolveAsyncGuard(firstResult, errorCode, firstSegment);
|
|
40
|
+
|
|
41
|
+
for (let i = startIndex; i < segments.length; i++) {
|
|
42
|
+
if (!isActive()) {
|
|
43
|
+
throw new RouterError(errorCodes.TRANSITION_CANCELLED);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const segment = segments[i];
|
|
47
|
+
const guardFn = guards.get(segment);
|
|
48
|
+
|
|
49
|
+
if (!guardFn) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let guardResult: boolean | Promise<boolean> = false;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
guardResult = guardFn(toState, fromState, signal);
|
|
57
|
+
} catch (error: unknown) {
|
|
58
|
+
handleGuardError(error, errorCode, segment);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (guardResult instanceof Promise) {
|
|
62
|
+
await resolveAsyncGuard(guardResult, errorCode, segment);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!guardResult) {
|
|
67
|
+
throw new RouterError(errorCode, { segment });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function finishAsyncPipeline(
|
|
73
|
+
deactivateCompletion: Promise<void>,
|
|
74
|
+
activateGuards: Map<string, GuardFn>,
|
|
75
|
+
toActivate: string[],
|
|
76
|
+
shouldActivate: boolean,
|
|
77
|
+
toState: State,
|
|
78
|
+
fromState: State | undefined,
|
|
79
|
+
signal: AbortSignal,
|
|
80
|
+
isActive: () => boolean,
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
await deactivateCompletion;
|
|
83
|
+
|
|
84
|
+
if (!isActive()) {
|
|
85
|
+
throw new RouterError(errorCodes.TRANSITION_CANCELLED);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (shouldActivate) {
|
|
89
|
+
const pending = runGuards(
|
|
90
|
+
activateGuards,
|
|
91
|
+
toActivate,
|
|
92
|
+
errorCodes.CANNOT_ACTIVATE,
|
|
93
|
+
toState,
|
|
94
|
+
fromState,
|
|
95
|
+
signal,
|
|
96
|
+
isActive,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (pending !== undefined) {
|
|
100
|
+
await pending;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!isActive()) {
|
|
104
|
+
throw new RouterError(errorCodes.TRANSITION_CANCELLED);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function executeGuardPipeline(
|
|
110
|
+
deactivateGuards: Map<string, GuardFn>,
|
|
111
|
+
activateGuards: Map<string, GuardFn>,
|
|
112
|
+
toDeactivate: string[],
|
|
113
|
+
toActivate: string[],
|
|
114
|
+
shouldDeactivate: boolean,
|
|
115
|
+
shouldActivate: boolean,
|
|
116
|
+
toState: State,
|
|
117
|
+
fromState: State | undefined,
|
|
118
|
+
signal: AbortSignal,
|
|
119
|
+
isActive: () => boolean,
|
|
120
|
+
): Promise<void> | undefined {
|
|
121
|
+
if (shouldDeactivate) {
|
|
122
|
+
const pending = runGuards(
|
|
123
|
+
deactivateGuards,
|
|
124
|
+
toDeactivate,
|
|
125
|
+
errorCodes.CANNOT_DEACTIVATE,
|
|
126
|
+
toState,
|
|
127
|
+
fromState,
|
|
128
|
+
signal,
|
|
129
|
+
isActive,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (pending !== undefined) {
|
|
133
|
+
return finishAsyncPipeline(
|
|
134
|
+
pending,
|
|
135
|
+
activateGuards,
|
|
136
|
+
toActivate,
|
|
137
|
+
shouldActivate,
|
|
138
|
+
toState,
|
|
139
|
+
fromState,
|
|
140
|
+
signal,
|
|
141
|
+
isActive,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!isActive()) {
|
|
147
|
+
throw new RouterError(errorCodes.TRANSITION_CANCELLED);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (shouldActivate) {
|
|
151
|
+
return runGuards(
|
|
152
|
+
activateGuards,
|
|
153
|
+
toActivate,
|
|
154
|
+
errorCodes.CANNOT_ACTIVATE,
|
|
155
|
+
toState,
|
|
156
|
+
fromState,
|
|
157
|
+
signal,
|
|
158
|
+
isActive,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function runGuards(
|
|
166
|
+
guards: Map<string, GuardFn>,
|
|
167
|
+
segments: string[],
|
|
168
|
+
errorCode: string,
|
|
169
|
+
toState: State,
|
|
170
|
+
fromState: State | undefined,
|
|
171
|
+
signal: AbortSignal | undefined,
|
|
172
|
+
isActive: () => boolean,
|
|
173
|
+
): Promise<void> | undefined {
|
|
174
|
+
for (const [i, segment] of segments.entries()) {
|
|
175
|
+
if (!isActive()) {
|
|
176
|
+
throw new RouterError(errorCodes.TRANSITION_CANCELLED);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const guardFn = guards.get(segment);
|
|
180
|
+
|
|
181
|
+
if (!guardFn) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let guardResult: boolean | Promise<boolean> = false;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
guardResult = guardFn(toState, fromState, signal);
|
|
189
|
+
} catch (error: unknown) {
|
|
190
|
+
handleGuardError(error, errorCode, segment);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (guardResult instanceof Promise) {
|
|
194
|
+
return resolveRemainingGuards(
|
|
195
|
+
guards,
|
|
196
|
+
segments,
|
|
197
|
+
errorCode,
|
|
198
|
+
toState,
|
|
199
|
+
fromState,
|
|
200
|
+
signal,
|
|
201
|
+
isActive,
|
|
202
|
+
i + 1,
|
|
203
|
+
guardResult,
|
|
204
|
+
segment,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!guardResult) {
|
|
209
|
+
throw new RouterError(errorCode, { segment });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
// packages/core/src/namespaces/NavigationNamespace/types.ts
|
|
2
2
|
|
|
3
|
-
import type { BuildStateResultWithSegments } from "../../types";
|
|
4
3
|
import type {
|
|
5
4
|
GuardFn,
|
|
6
5
|
NavigationOptions,
|
|
7
6
|
Options,
|
|
8
7
|
Params,
|
|
9
8
|
State,
|
|
10
|
-
StateMetaInput,
|
|
11
|
-
TransitionPhase,
|
|
12
9
|
} from "@real-router/types";
|
|
13
10
|
|
|
11
|
+
export interface NavigationContext {
|
|
12
|
+
toState: State;
|
|
13
|
+
fromState: State | undefined;
|
|
14
|
+
opts: NavigationOptions;
|
|
15
|
+
toDeactivate: string[];
|
|
16
|
+
toActivate: string[];
|
|
17
|
+
intersection: string;
|
|
18
|
+
canDeactivateFunctions: Map<string, GuardFn>;
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
/**
|
|
15
22
|
* Dependencies injected into NavigationNamespace.
|
|
16
23
|
*
|
|
@@ -30,22 +37,11 @@ export interface NavigationDependencies {
|
|
|
30
37
|
/** Set router state */
|
|
31
38
|
setState: (state: State) => void;
|
|
32
39
|
|
|
33
|
-
/** Build state
|
|
34
|
-
|
|
40
|
+
/** Build complete navigate state: forwardState + route check + buildPath + makeState in one step */
|
|
41
|
+
buildNavigateState: (
|
|
35
42
|
routeName: string,
|
|
36
|
-
routeParams:
|
|
37
|
-
) =>
|
|
38
|
-
|
|
39
|
-
/** Make state object with path and meta */
|
|
40
|
-
makeState: <P extends Params = Params, MP extends Params = Params>(
|
|
41
|
-
name: string,
|
|
42
|
-
params?: P,
|
|
43
|
-
path?: string,
|
|
44
|
-
meta?: StateMetaInput<MP>,
|
|
45
|
-
) => State<P, MP>;
|
|
46
|
-
|
|
47
|
-
/** Build path from route name and params */
|
|
48
|
-
buildPath: (route: string, params?: Params) => string;
|
|
43
|
+
routeParams: Params,
|
|
44
|
+
) => State | undefined;
|
|
49
45
|
|
|
50
46
|
/** Check if states are equal */
|
|
51
47
|
areStatesEqual: (
|
|
@@ -106,15 +102,3 @@ export interface NavigationDependencies {
|
|
|
106
102
|
/** Clear canDeactivate guard for a route */
|
|
107
103
|
clearCanDeactivate: (name: string) => void;
|
|
108
104
|
}
|
|
109
|
-
|
|
110
|
-
export interface TransitionOutput {
|
|
111
|
-
state: State;
|
|
112
|
-
meta: {
|
|
113
|
-
phase: TransitionPhase;
|
|
114
|
-
segments: {
|
|
115
|
-
deactivated: string[];
|
|
116
|
-
activated: string[];
|
|
117
|
-
intersection: string;
|
|
118
|
-
};
|
|
119
|
-
};
|
|
120
|
-
}
|
|
@@ -42,6 +42,11 @@ export class RouteLifecycleNamespace<
|
|
|
42
42
|
>();
|
|
43
43
|
readonly #canDeactivateFunctions = new Map<string, GuardFn>();
|
|
44
44
|
readonly #canActivateFunctions = new Map<string, GuardFn>();
|
|
45
|
+
// Cached tuple — Maps never change reference, so this is stable
|
|
46
|
+
readonly #functionsTuple: [Map<string, GuardFn>, Map<string, GuardFn>] = [
|
|
47
|
+
this.#canDeactivateFunctions,
|
|
48
|
+
this.#canActivateFunctions,
|
|
49
|
+
];
|
|
45
50
|
|
|
46
51
|
readonly #registering = new Set<string>();
|
|
47
52
|
readonly #definitionActivateGuardNames = new Set<string>();
|
|
@@ -249,7 +254,7 @@ export class RouteLifecycleNamespace<
|
|
|
249
254
|
* @returns Tuple of [canDeactivateFunctions, canActivateFunctions] as Maps
|
|
250
255
|
*/
|
|
251
256
|
getFunctions(): [Map<string, GuardFn>, Map<string, GuardFn>] {
|
|
252
|
-
return
|
|
257
|
+
return this.#functionsTuple;
|
|
253
258
|
}
|
|
254
259
|
|
|
255
260
|
canNavigateTo(
|
|
@@ -14,7 +14,7 @@ import { getTransitionPath } from "../../transitionPath";
|
|
|
14
14
|
|
|
15
15
|
import type { RoutesStore } from "./routesStore";
|
|
16
16
|
import type { RoutesDependencies } from "./types";
|
|
17
|
-
import type {
|
|
17
|
+
import type { Route } from "../../types";
|
|
18
18
|
import type { RouteLifecycleNamespace } from "../RouteLifecycleNamespace";
|
|
19
19
|
import type {
|
|
20
20
|
DefaultDependencies,
|
|
@@ -65,6 +65,11 @@ export function createRouteState<P extends RouteParams = RouteParams>(
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
interface CachedBuildPathOpts {
|
|
69
|
+
readonly trailingSlash?: "always" | "never" | undefined;
|
|
70
|
+
readonly queryParamsMode?: "default" | "strict" | "loose" | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
/**
|
|
69
74
|
* Independent namespace for managing routes.
|
|
70
75
|
*
|
|
@@ -75,6 +80,7 @@ export class RoutesNamespace<
|
|
|
75
80
|
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
76
81
|
> {
|
|
77
82
|
readonly #store: RoutesStore<Dependencies>;
|
|
83
|
+
#cachedBuildPathOpts: CachedBuildPathOpts | undefined;
|
|
78
84
|
|
|
79
85
|
get #deps(): RoutesDependencies<Dependencies> {
|
|
80
86
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -210,13 +216,11 @@ export class RoutesNamespace<
|
|
|
210
216
|
? this.#store.config.encoders[route]({ ...paramsWithDefault })
|
|
211
217
|
: paramsWithDefault;
|
|
212
218
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
queryParamsMode: options?.queryParamsMode,
|
|
219
|
-
});
|
|
219
|
+
return this.#store.matcher.buildPath(
|
|
220
|
+
route,
|
|
221
|
+
encodedParams,
|
|
222
|
+
this.#getBuildPathOptions(options),
|
|
223
|
+
);
|
|
220
224
|
}
|
|
221
225
|
|
|
222
226
|
/**
|
|
@@ -267,9 +271,7 @@ export class RoutesNamespace<
|
|
|
267
271
|
});
|
|
268
272
|
}
|
|
269
273
|
|
|
270
|
-
return this.#deps.makeState<P, MP>(routeName, routeParams, builtPath,
|
|
271
|
-
params: meta as MP,
|
|
272
|
-
});
|
|
274
|
+
return this.#deps.makeState<P, MP>(routeName, routeParams, builtPath, meta);
|
|
273
275
|
}
|
|
274
276
|
|
|
275
277
|
/**
|
|
@@ -359,30 +361,6 @@ export class RoutesNamespace<
|
|
|
359
361
|
);
|
|
360
362
|
}
|
|
361
363
|
|
|
362
|
-
buildStateWithSegmentsResolved<P extends Params = Params>(
|
|
363
|
-
resolvedName: string,
|
|
364
|
-
resolvedParams: P,
|
|
365
|
-
): BuildStateResultWithSegments<P> | undefined {
|
|
366
|
-
const segments = this.#store.matcher.getSegmentsByName(resolvedName);
|
|
367
|
-
|
|
368
|
-
if (!segments) {
|
|
369
|
-
return undefined;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
373
|
-
const meta = this.#store.matcher.getMetaByName(resolvedName)!;
|
|
374
|
-
const state = createRouteState<P>(
|
|
375
|
-
{
|
|
376
|
-
segments: segments as readonly RouteTree[],
|
|
377
|
-
params: resolvedParams,
|
|
378
|
-
meta,
|
|
379
|
-
},
|
|
380
|
-
resolvedName,
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
return { state, segments: segments as readonly RouteTree[] };
|
|
384
|
-
}
|
|
385
|
-
|
|
386
364
|
// =========================================================================
|
|
387
365
|
// Query operations
|
|
388
366
|
// =========================================================================
|
|
@@ -457,6 +435,14 @@ export class RoutesNamespace<
|
|
|
457
435
|
);
|
|
458
436
|
}
|
|
459
437
|
|
|
438
|
+
getMetaForState(
|
|
439
|
+
name: string,
|
|
440
|
+
): Record<string, Record<string, "url" | "query">> | undefined {
|
|
441
|
+
return this.#store.matcher.hasRoute(name)
|
|
442
|
+
? this.#store.matcher.getMetaByName(name)
|
|
443
|
+
: undefined;
|
|
444
|
+
}
|
|
445
|
+
|
|
460
446
|
getUrlParams(name: string): string[] {
|
|
461
447
|
const segments = this.#store.matcher.getSegmentsByName(name);
|
|
462
448
|
|
|
@@ -485,6 +471,21 @@ export class RoutesNamespace<
|
|
|
485
471
|
return params;
|
|
486
472
|
}
|
|
487
473
|
|
|
474
|
+
#getBuildPathOptions(options?: Options): CachedBuildPathOpts {
|
|
475
|
+
if (this.#cachedBuildPathOpts) {
|
|
476
|
+
return this.#cachedBuildPathOpts;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const ts = options?.trailingSlash;
|
|
480
|
+
|
|
481
|
+
this.#cachedBuildPathOpts = Object.freeze({
|
|
482
|
+
trailingSlash: ts === "never" || ts === "always" ? ts : undefined,
|
|
483
|
+
queryParamsMode: options?.queryParamsMode,
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
return this.#cachedBuildPathOpts;
|
|
487
|
+
}
|
|
488
|
+
|
|
488
489
|
#resolveDynamicForward(
|
|
489
490
|
startName: string,
|
|
490
491
|
startFn: ForwardToCallback<Dependencies>,
|
|
@@ -284,11 +284,8 @@ function getTargetParams<Dependencies extends DefaultDependencies>(
|
|
|
284
284
|
routes: readonly Route<Dependencies>[],
|
|
285
285
|
): Set<string> {
|
|
286
286
|
if (existsInTree) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
// toSegments won't be null since we checked existsInTree
|
|
290
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
291
|
-
return getRequiredParams(toSegments!);
|
|
287
|
+
/* v8 ignore next -- @preserve: ?? fallback unreachable — existsInTree guarantees non-null */
|
|
288
|
+
return getRequiredParams(getSegmentsByName(tree, targetRoute) ?? []);
|
|
292
289
|
}
|
|
293
290
|
|
|
294
291
|
// Target is in batch
|
|
@@ -7,7 +7,6 @@ import type {
|
|
|
7
7
|
Params,
|
|
8
8
|
SimpleState,
|
|
9
9
|
State,
|
|
10
|
-
StateMetaInput,
|
|
11
10
|
} from "@real-router/types";
|
|
12
11
|
|
|
13
12
|
/**
|
|
@@ -36,7 +35,7 @@ export interface RoutesDependencies<
|
|
|
36
35
|
name: string,
|
|
37
36
|
params?: P,
|
|
38
37
|
path?: string,
|
|
39
|
-
meta?:
|
|
38
|
+
meta?: Record<string, Record<string, "url" | "query">>,
|
|
40
39
|
) => State<P, MP>;
|
|
41
40
|
|
|
42
41
|
/** Get current router state */
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
import { getTypeDescription, validateState } from "type-guards";
|
|
4
4
|
|
|
5
5
|
import { areParamValuesEqual, getUrlParamsFromMeta } from "./helpers";
|
|
6
|
+
import { EMPTY_PARAMS } from "../../constants";
|
|
6
7
|
import { freezeStateInPlace } from "../../helpers";
|
|
7
8
|
|
|
8
9
|
import type { StateNamespaceDependencies } from "./types";
|
|
9
|
-
import type { Params, State
|
|
10
|
+
import type { Params, State } from "@real-router/types";
|
|
10
11
|
import type { RouteTreeStateMeta } from "route-tree";
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -85,7 +86,7 @@ export class StateNamespace {
|
|
|
85
86
|
get<P extends Params = Params, MP extends Params = Params>():
|
|
86
87
|
| State<P, MP>
|
|
87
88
|
| undefined {
|
|
88
|
-
return this.#frozenState as State<P, MP> | undefined;
|
|
89
|
+
return this.#frozenState as State<P, MP> | undefined; // NOSONAR -- generic narrowing needed for public API
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
/**
|
|
@@ -108,10 +109,8 @@ export class StateNamespace {
|
|
|
108
109
|
/**
|
|
109
110
|
* Returns the previous router state (before the last navigation).
|
|
110
111
|
*/
|
|
111
|
-
getPrevious
|
|
112
|
-
|
|
113
|
-
| undefined {
|
|
114
|
-
return this.#previousState as State<P, MP> | undefined;
|
|
112
|
+
getPrevious(): State | undefined {
|
|
113
|
+
return this.#previousState;
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
reset(): void {
|
|
@@ -144,15 +143,12 @@ export class StateNamespace {
|
|
|
144
143
|
name: string,
|
|
145
144
|
params?: P,
|
|
146
145
|
path?: string,
|
|
147
|
-
meta?:
|
|
146
|
+
meta?: RouteTreeStateMeta,
|
|
148
147
|
forceId?: number,
|
|
148
|
+
skipFreeze?: boolean,
|
|
149
149
|
): State<P, MP> {
|
|
150
150
|
const madeMeta = meta
|
|
151
|
-
? {
|
|
152
|
-
...meta,
|
|
153
|
-
id: forceId ?? ++this.#stateId,
|
|
154
|
-
params: meta.params,
|
|
155
|
-
}
|
|
151
|
+
? { id: forceId ?? ++this.#stateId, params: meta as unknown as MP }
|
|
156
152
|
: undefined;
|
|
157
153
|
|
|
158
154
|
// Optimization: O(1) lookup instead of O(depth) ancestor iteration
|
|
@@ -164,10 +160,10 @@ export class StateNamespace {
|
|
|
164
160
|
|
|
165
161
|
if (hasDefaultParams) {
|
|
166
162
|
mergedParams = { ...defaultParamsConfig[name], ...params } as P;
|
|
167
|
-
} else if (params) {
|
|
168
|
-
mergedParams =
|
|
163
|
+
} else if (!params || params === EMPTY_PARAMS) {
|
|
164
|
+
mergedParams = EMPTY_PARAMS as P;
|
|
169
165
|
} else {
|
|
170
|
-
mergedParams = {
|
|
166
|
+
mergedParams = { ...params };
|
|
171
167
|
}
|
|
172
168
|
|
|
173
169
|
const state: State<P, MP> = {
|
|
@@ -177,7 +173,7 @@ export class StateNamespace {
|
|
|
177
173
|
meta: madeMeta,
|
|
178
174
|
};
|
|
179
175
|
|
|
180
|
-
return freezeStateInPlace(state);
|
|
176
|
+
return skipFreeze ? state : freezeStateInPlace(state);
|
|
181
177
|
}
|
|
182
178
|
|
|
183
179
|
// =========================================================================
|
|
@@ -202,7 +198,7 @@ export class StateNamespace {
|
|
|
202
198
|
}
|
|
203
199
|
|
|
204
200
|
if (ignoreQueryParams) {
|
|
205
|
-
const stateMeta = (state1.meta?.params ?? state2.meta?.params) as
|
|
201
|
+
const stateMeta = (state1.meta?.params ?? state2.meta?.params) as // NOSONAR -- narrowing from Params to RouteTreeStateMeta
|
|
206
202
|
| RouteTreeStateMeta
|
|
207
203
|
| undefined;
|
|
208
204
|
|