@real-router/core 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -5
- package/dist/cjs/Router-B-Pev7K2.d.ts +46 -0
- package/dist/cjs/RouterValidator-mx2Zooya.d.ts +136 -0
- package/dist/cjs/api.d.ts +2 -1
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/index.d-y2b-8_3Y.d.ts +236 -0
- package/dist/cjs/index.d.ts +7 -24
- 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/cjs/utils.d.ts +6 -1
- package/dist/cjs/utils.js +1 -1
- package/dist/cjs/utils.js.map +1 -1
- package/dist/cjs/validation.d.ts +184 -0
- package/dist/cjs/validation.js +1 -0
- package/dist/cjs/validation.js.map +1 -0
- package/dist/esm/Router-B-Pev7K2.d.mts +46 -0
- package/dist/esm/RouterValidator-mx2Zooya.d.mts +136 -0
- package/dist/esm/api.d.mts +2 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/chunk-5QXFUUDL.mjs +1 -0
- package/dist/esm/chunk-5QXFUUDL.mjs.map +1 -0
- package/dist/esm/chunk-HHIXK5UM.mjs +1 -0
- package/dist/esm/chunk-HHIXK5UM.mjs.map +1 -0
- package/dist/esm/chunk-QUUNDESP.mjs +1 -0
- package/dist/esm/chunk-QUUNDESP.mjs.map +1 -0
- package/dist/esm/chunk-RA5VYM7M.mjs +1 -0
- package/dist/esm/chunk-RA5VYM7M.mjs.map +1 -0
- package/dist/esm/index.d-y2b-8_3Y.d.mts +236 -0
- package/dist/esm/index.d.mts +7 -24
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/metafile-esm.json +1 -1
- package/dist/esm/utils.d.mts +6 -1
- package/dist/esm/utils.mjs +1 -1
- package/dist/esm/utils.mjs.map +1 -1
- package/dist/esm/validation.d.mts +184 -0
- package/dist/esm/validation.mjs +1 -0
- package/dist/esm/validation.mjs.map +1 -0
- package/package.json +18 -5
- package/src/Router.ts +73 -99
- package/src/api/cloneRouter.ts +1 -30
- package/src/api/getDependenciesApi.ts +45 -86
- package/src/api/getLifecycleApi.ts +24 -19
- package/src/api/getPluginApi.ts +20 -28
- package/src/api/getRoutesApi.ts +49 -106
- package/src/constants.ts +0 -30
- package/src/guards.ts +46 -0
- package/src/helpers.ts +0 -17
- package/src/index.ts +4 -0
- package/src/internals.ts +6 -5
- package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +2 -2
- package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +0 -25
- package/src/namespaces/OptionsNamespace/OptionsNamespace.ts +4 -26
- package/src/namespaces/OptionsNamespace/constants.ts +0 -20
- package/src/namespaces/OptionsNamespace/index.ts +1 -5
- package/src/namespaces/OptionsNamespace/validators.ts +6 -245
- package/src/namespaces/PluginsNamespace/PluginsNamespace.ts +18 -59
- package/src/namespaces/PluginsNamespace/constants.ts +3 -6
- package/src/namespaces/PluginsNamespace/validators.ts +2 -57
- package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +27 -84
- package/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts +0 -16
- package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +3 -12
- package/src/namespaces/RoutesNamespace/constants.ts +0 -8
- package/src/namespaces/RoutesNamespace/forwardChain.ts +34 -0
- package/src/namespaces/RoutesNamespace/index.ts +1 -1
- package/src/namespaces/RoutesNamespace/routeGuards.ts +62 -0
- package/src/namespaces/RoutesNamespace/routesStore.ts +7 -51
- package/src/namespaces/StateNamespace/StateNamespace.ts +0 -33
- package/src/namespaces/StateNamespace/helpers.ts +1 -1
- package/src/namespaces/index.ts +0 -3
- package/src/typeGuards.ts +1 -15
- package/src/types/RouterValidator.ts +155 -0
- package/src/utils/getStaticPaths.ts +50 -0
- package/src/utils/index.ts +4 -0
- package/src/validation.ts +12 -0
- package/src/wiring/RouterWiringBuilder.ts +32 -9
- package/dist/cjs/index.d-DDimDpYc.d.ts +0 -165
- package/dist/esm/chunk-CG7TKDP3.mjs +0 -1
- package/dist/esm/chunk-CG7TKDP3.mjs.map +0 -1
- package/dist/esm/index.d-DDimDpYc.d.mts +0 -165
- package/src/namespaces/DependenciesNamespace/validators.ts +0 -103
- package/src/namespaces/EventBusNamespace/validators.ts +0 -36
- package/src/namespaces/NavigationNamespace/validators.ts +0 -47
- package/src/namespaces/RouteLifecycleNamespace/validators.ts +0 -65
- package/src/namespaces/RoutesNamespace/forwardToValidation.ts +0 -408
- package/src/namespaces/RoutesNamespace/validators.ts +0 -566
- package/src/namespaces/StateNamespace/validators.ts +0 -46
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
// packages/core/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts
|
|
2
2
|
|
|
3
|
-
import { logger } from "@real-router/logger";
|
|
4
|
-
import { isBoolean, getTypeDescription } from "type-guards";
|
|
5
|
-
|
|
6
|
-
import { validateHandlerLimit, validateNotRegistering } from "./validators";
|
|
7
3
|
import { DEFAULT_LIMITS } from "../../constants";
|
|
8
|
-
import { computeThresholds } from "../../helpers";
|
|
9
4
|
|
|
10
5
|
import type { RouteLifecycleDependencies } from "./types";
|
|
11
6
|
import type { GuardFnFactory, Limits } from "../../types";
|
|
7
|
+
import type { RouterValidator } from "../../types/RouterValidator";
|
|
12
8
|
import type { DefaultDependencies, GuardFn, State } from "@real-router/types";
|
|
13
9
|
|
|
14
10
|
/**
|
|
@@ -54,6 +50,7 @@ export class RouteLifecycleNamespace<
|
|
|
54
50
|
|
|
55
51
|
#deps!: RouteLifecycleDependencies<Dependencies>;
|
|
56
52
|
#limits: Limits = DEFAULT_LIMITS;
|
|
53
|
+
#getValidator: (() => RouterValidator | null) | null = null;
|
|
57
54
|
|
|
58
55
|
setDependencies(deps: RouteLifecycleDependencies<Dependencies>): void {
|
|
59
56
|
this.#deps = deps;
|
|
@@ -66,6 +63,18 @@ export class RouteLifecycleNamespace<
|
|
|
66
63
|
*/
|
|
67
64
|
setLimits(limits: Limits): void {
|
|
68
65
|
this.#limits = limits;
|
|
66
|
+
// eslint-disable-next-line sonarjs/void-use -- @preserve: Wave 3 validator reads limits via RouterInternals; void suppresses TS6133 until then
|
|
67
|
+
void this.#limits;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setValidatorGetter(getter: () => RouterValidator | null): void {
|
|
71
|
+
this.#getValidator = getter;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getHandlerCount(type: "activate" | "deactivate"): number {
|
|
75
|
+
return type === "activate"
|
|
76
|
+
? this.#canActivateFactories.size
|
|
77
|
+
: this.#canDeactivateFactories.size;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
// =========================================================================
|
|
@@ -74,17 +83,15 @@ export class RouteLifecycleNamespace<
|
|
|
74
83
|
|
|
75
84
|
/**
|
|
76
85
|
* Adds a canActivate guard for a route.
|
|
77
|
-
* Handles
|
|
86
|
+
* Handles overwrite detection and registration.
|
|
78
87
|
*
|
|
79
88
|
* @param name - Route name (input-validated by facade)
|
|
80
89
|
* @param handler - Guard function or boolean (input-validated by facade)
|
|
81
|
-
* @param skipValidation - True when called during route config building (#noValidate)
|
|
82
90
|
* @param isFromDefinition - True when guard comes from route definition (tracked for HMR replace)
|
|
83
91
|
*/
|
|
84
92
|
addCanActivate(
|
|
85
93
|
name: string,
|
|
86
94
|
handler: GuardFnFactory<Dependencies> | boolean,
|
|
87
|
-
skipValidation: boolean,
|
|
88
95
|
isFromDefinition = false,
|
|
89
96
|
): void {
|
|
90
97
|
if (isFromDefinition) {
|
|
@@ -92,24 +99,9 @@ export class RouteLifecycleNamespace<
|
|
|
92
99
|
} else {
|
|
93
100
|
this.#definitionActivateGuardNames.delete(name);
|
|
94
101
|
}
|
|
95
|
-
if (!skipValidation) {
|
|
96
|
-
validateNotRegistering(
|
|
97
|
-
this.#registering.has(name),
|
|
98
|
-
name,
|
|
99
|
-
"addActivateGuard",
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
102
|
|
|
103
103
|
const isOverwrite = this.#canActivateFactories.has(name);
|
|
104
104
|
|
|
105
|
-
if (!isOverwrite && !skipValidation) {
|
|
106
|
-
validateHandlerLimit(
|
|
107
|
-
this.#canActivateFactories.size + 1,
|
|
108
|
-
"addActivateGuard",
|
|
109
|
-
this.#limits.maxLifecycleHandlers,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
105
|
this.#registerHandler(
|
|
114
106
|
"activate",
|
|
115
107
|
name,
|
|
@@ -123,17 +115,15 @@ export class RouteLifecycleNamespace<
|
|
|
123
115
|
|
|
124
116
|
/**
|
|
125
117
|
* Adds a canDeactivate guard for a route.
|
|
126
|
-
* Handles
|
|
118
|
+
* Handles overwrite detection and registration.
|
|
127
119
|
*
|
|
128
120
|
* @param name - Route name (input-validated by facade)
|
|
129
121
|
* @param handler - Guard function or boolean (input-validated by facade)
|
|
130
|
-
* @param skipValidation - True when called during route config building (#noValidate)
|
|
131
122
|
* @param isFromDefinition - True when guard comes from route definition (tracked for HMR replace)
|
|
132
123
|
*/
|
|
133
124
|
addCanDeactivate(
|
|
134
125
|
name: string,
|
|
135
126
|
handler: GuardFnFactory<Dependencies> | boolean,
|
|
136
|
-
skipValidation: boolean,
|
|
137
127
|
isFromDefinition = false,
|
|
138
128
|
): void {
|
|
139
129
|
if (isFromDefinition) {
|
|
@@ -141,24 +131,9 @@ export class RouteLifecycleNamespace<
|
|
|
141
131
|
} else {
|
|
142
132
|
this.#definitionDeactivateGuardNames.delete(name);
|
|
143
133
|
}
|
|
144
|
-
if (!skipValidation) {
|
|
145
|
-
validateNotRegistering(
|
|
146
|
-
this.#registering.has(name),
|
|
147
|
-
name,
|
|
148
|
-
"addDeactivateGuard",
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
134
|
|
|
152
135
|
const isOverwrite = this.#canDeactivateFactories.has(name);
|
|
153
136
|
|
|
154
|
-
if (!isOverwrite && !skipValidation) {
|
|
155
|
-
validateHandlerLimit(
|
|
156
|
-
this.#canDeactivateFactories.size + 1,
|
|
157
|
-
"addDeactivateGuard",
|
|
158
|
-
this.#limits.maxLifecycleHandlers,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
137
|
this.#registerHandler(
|
|
163
138
|
"deactivate",
|
|
164
139
|
name,
|
|
@@ -321,18 +296,19 @@ export class RouteLifecycleNamespace<
|
|
|
321
296
|
): void {
|
|
322
297
|
// Emit warnings
|
|
323
298
|
if (isOverwrite) {
|
|
324
|
-
|
|
325
|
-
`router.${methodName}`,
|
|
326
|
-
`Overwriting existing ${type} handler for route "${name}"`,
|
|
327
|
-
);
|
|
299
|
+
this.#getValidator?.()?.lifecycle.warnOverwrite(name, type, methodName);
|
|
328
300
|
} else {
|
|
329
|
-
this.#
|
|
301
|
+
this.#getValidator?.()?.lifecycle.validateCountThresholds(
|
|
302
|
+
factories.size + 1,
|
|
303
|
+
methodName,
|
|
304
|
+
);
|
|
330
305
|
}
|
|
331
306
|
|
|
332
307
|
// Convert boolean to factory if needed
|
|
333
|
-
const factory =
|
|
334
|
-
|
|
335
|
-
|
|
308
|
+
const factory =
|
|
309
|
+
typeof handler === "boolean"
|
|
310
|
+
? booleanToFactory<Dependencies>(handler)
|
|
311
|
+
: handler;
|
|
336
312
|
|
|
337
313
|
// Store factory
|
|
338
314
|
factories.set(name, factory);
|
|
@@ -345,7 +321,7 @@ export class RouteLifecycleNamespace<
|
|
|
345
321
|
|
|
346
322
|
if (typeof fn !== "function") {
|
|
347
323
|
throw new TypeError(
|
|
348
|
-
`[router.${methodName}] Factory must return a function, got ${
|
|
324
|
+
`[router.${methodName}] Factory must return a function, got ${typeof fn}`,
|
|
349
325
|
);
|
|
350
326
|
}
|
|
351
327
|
|
|
@@ -391,44 +367,11 @@ export class RouteLifecycleNamespace<
|
|
|
391
367
|
return result;
|
|
392
368
|
}
|
|
393
369
|
|
|
394
|
-
|
|
395
|
-
`router.${methodName}`,
|
|
396
|
-
`Guard for "${name}" returned a Promise. Sync check cannot resolve async guards — returning false.`,
|
|
397
|
-
);
|
|
370
|
+
this.#getValidator?.()?.lifecycle.warnAsyncGuardSync(name, methodName);
|
|
398
371
|
|
|
399
372
|
return false;
|
|
400
373
|
} catch {
|
|
401
374
|
return false;
|
|
402
375
|
}
|
|
403
376
|
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Emits warn/error log messages when handler count approaches the configured limit.
|
|
407
|
-
*
|
|
408
|
-
* @param currentSize - Current handler count (after adding the new one)
|
|
409
|
-
* @param methodName - Public API method name for warning messages
|
|
410
|
-
*/
|
|
411
|
-
#checkCountThresholds(currentSize: number, methodName: string): void {
|
|
412
|
-
const maxLifecycleHandlers = this.#limits.maxLifecycleHandlers;
|
|
413
|
-
|
|
414
|
-
if (maxLifecycleHandlers === 0) {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const { warn, error } = computeThresholds(maxLifecycleHandlers);
|
|
419
|
-
|
|
420
|
-
if (currentSize >= error) {
|
|
421
|
-
logger.error(
|
|
422
|
-
`router.${methodName}`,
|
|
423
|
-
`${currentSize} lifecycle handlers registered! ` +
|
|
424
|
-
`This is excessive. Hard limit at ${maxLifecycleHandlers}.`,
|
|
425
|
-
);
|
|
426
|
-
} else if (currentSize >= warn) {
|
|
427
|
-
logger.warn(
|
|
428
|
-
`router.${methodName}`,
|
|
429
|
-
`${currentSize} lifecycle handlers registered. ` +
|
|
430
|
-
`Consider consolidating logic.`,
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
377
|
}
|
|
@@ -19,22 +19,6 @@ Object.freeze(REPLACE_OPTS);
|
|
|
19
19
|
export class RouterLifecycleNamespace {
|
|
20
20
|
#deps!: RouterLifecycleDependencies;
|
|
21
21
|
|
|
22
|
-
// =========================================================================
|
|
23
|
-
// Static validation methods (called by facade before instance methods)
|
|
24
|
-
// =========================================================================
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Validates start() arguments.
|
|
28
|
-
*/
|
|
29
|
-
static validateStartArgs(args: unknown[]): void {
|
|
30
|
-
/* v8 ignore next 4 -- @preserve: facade enforces 1 arg via TypeScript signature */
|
|
31
|
-
if (args.length !== 1 || typeof args[0] !== "string") {
|
|
32
|
-
throw new Error(
|
|
33
|
-
"[router.start] Expected exactly 1 string argument (startPath).",
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
22
|
// =========================================================================
|
|
39
23
|
// Dependency injection
|
|
40
24
|
// =========================================================================
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// packages/core/src/namespaces/RoutesNamespace/RoutesNamespace.ts
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { DEFAULT_ROUTE_NAME, validatedRouteNames } from "./constants";
|
|
3
|
+
import { DEFAULT_ROUTE_NAME } from "./constants";
|
|
6
4
|
import { paramsMatch, paramsMatchExcluding } from "./helpers";
|
|
7
5
|
import {
|
|
8
6
|
createRoutesStore,
|
|
@@ -89,10 +87,9 @@ export class RoutesNamespace<
|
|
|
89
87
|
|
|
90
88
|
constructor(
|
|
91
89
|
routes: Route<Dependencies>[] = [],
|
|
92
|
-
noValidate = false,
|
|
93
90
|
matcherOptions?: CreateMatcherOptions,
|
|
94
91
|
) {
|
|
95
|
-
this.#store = createRoutesStore(routes,
|
|
92
|
+
this.#store = createRoutesStore(routes, matcherOptions);
|
|
96
93
|
}
|
|
97
94
|
|
|
98
95
|
/**
|
|
@@ -200,7 +197,7 @@ export class RoutesNamespace<
|
|
|
200
197
|
*/
|
|
201
198
|
buildPath(route: string, params?: Params, options?: Options): string {
|
|
202
199
|
if (route === constants.UNKNOWN_ROUTE) {
|
|
203
|
-
return
|
|
200
|
+
return typeof params?.path === "string" ? params.path : "";
|
|
204
201
|
}
|
|
205
202
|
|
|
206
203
|
const paramsWithDefault = Object.hasOwn(
|
|
@@ -374,12 +371,6 @@ export class RoutesNamespace<
|
|
|
374
371
|
strictEquality = false,
|
|
375
372
|
ignoreQueryParams = true,
|
|
376
373
|
): boolean {
|
|
377
|
-
// Fast path: skip regex validation for already-validated route names
|
|
378
|
-
if (!validatedRouteNames.has(name)) {
|
|
379
|
-
validateRouteName(name, "isActiveRoute");
|
|
380
|
-
validatedRouteNames.add(name);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
374
|
// Note: empty string check is handled by Router.ts facade
|
|
384
375
|
const activeState = this.#deps.getState();
|
|
385
376
|
|
|
@@ -4,11 +4,3 @@
|
|
|
4
4
|
* Default route name for the root node.
|
|
5
5
|
*/
|
|
6
6
|
export const DEFAULT_ROUTE_NAME = "";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Cache for validated route names to skip regex validation on repeated calls.
|
|
10
|
-
* Key insight: validateRouteName() regex takes ~40ns, but cache lookup is ~1ns.
|
|
11
|
-
* This cache is module-level (shared across all router instances) since route name
|
|
12
|
-
* validity is independent of router instance.
|
|
13
|
-
*/
|
|
14
|
-
export const validatedRouteNames = new Set<string>();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// packages/core/src/namespaces/RoutesNamespace/forwardChain.ts
|
|
2
|
+
|
|
3
|
+
export function resolveForwardChain(
|
|
4
|
+
startRoute: string,
|
|
5
|
+
forwardMap: Record<string, string>,
|
|
6
|
+
maxDepth = 100,
|
|
7
|
+
): string {
|
|
8
|
+
const visited = new Set<string>();
|
|
9
|
+
const chain: string[] = [startRoute];
|
|
10
|
+
let current = startRoute;
|
|
11
|
+
|
|
12
|
+
while (forwardMap[current]) {
|
|
13
|
+
const next = forwardMap[current];
|
|
14
|
+
|
|
15
|
+
if (visited.has(next)) {
|
|
16
|
+
const cycleStart = chain.indexOf(next);
|
|
17
|
+
const cycle = [...chain.slice(cycleStart), next];
|
|
18
|
+
|
|
19
|
+
throw new Error(`Circular forwardTo: ${cycle.join(" → ")}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
visited.add(current);
|
|
23
|
+
chain.push(next);
|
|
24
|
+
current = next;
|
|
25
|
+
|
|
26
|
+
if (chain.length > maxDepth) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`forwardTo chain exceeds maximum depth (${maxDepth}): ${chain.join(" → ")}`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return current;
|
|
34
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { logger } from "@real-router/logger";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validates removeRoute constraints.
|
|
5
|
+
* Returns false if removal should be blocked (route is active).
|
|
6
|
+
* Logs warnings for edge cases.
|
|
7
|
+
*
|
|
8
|
+
* @param name - Route name to remove
|
|
9
|
+
* @param currentStateName - Current active route name (or undefined)
|
|
10
|
+
* @param isNavigating - Whether navigation is in progress
|
|
11
|
+
* @returns true if removal can proceed, false if blocked
|
|
12
|
+
*/
|
|
13
|
+
export function validateRemoveRoute(
|
|
14
|
+
name: string,
|
|
15
|
+
currentStateName: string | undefined,
|
|
16
|
+
isNavigating: boolean,
|
|
17
|
+
): boolean {
|
|
18
|
+
if (currentStateName) {
|
|
19
|
+
const isExactMatch = currentStateName === name;
|
|
20
|
+
const isParentOfCurrent = currentStateName.startsWith(`${name}.`);
|
|
21
|
+
|
|
22
|
+
if (isExactMatch || isParentOfCurrent) {
|
|
23
|
+
const suffix = isExactMatch ? "" : ` (current: "${currentStateName}")`;
|
|
24
|
+
|
|
25
|
+
logger.warn(
|
|
26
|
+
"router.removeRoute",
|
|
27
|
+
`Cannot remove route "${name}" — it is currently active${suffix}. Navigate away first.`,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (isNavigating) {
|
|
35
|
+
logger.warn(
|
|
36
|
+
"router.removeRoute",
|
|
37
|
+
`Route "${name}" removed while navigation is in progress. This may cause unexpected behavior.`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validates clearRoutes operation.
|
|
46
|
+
* Returns false if operation should be blocked (navigation in progress).
|
|
47
|
+
*
|
|
48
|
+
* @param isNavigating - Whether navigation is in progress
|
|
49
|
+
* @returns true if clearRoutes can proceed, false if blocked
|
|
50
|
+
*/
|
|
51
|
+
export function validateClearRoutes(isNavigating: boolean): boolean {
|
|
52
|
+
if (isNavigating) {
|
|
53
|
+
logger.error(
|
|
54
|
+
"router.clearRoutes",
|
|
55
|
+
"Cannot clear routes while navigation is in progress. Wait for navigation to complete.",
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
@@ -4,9 +4,8 @@ import { logger } from "@real-router/logger";
|
|
|
4
4
|
import { createMatcher, createRouteTree, nodeToDefinition } from "route-tree";
|
|
5
5
|
|
|
6
6
|
import { DEFAULT_ROUTE_NAME } from "./constants";
|
|
7
|
-
import { resolveForwardChain } from "./
|
|
7
|
+
import { resolveForwardChain } from "./forwardChain";
|
|
8
8
|
import { createEmptyConfig, sanitizeRoute } from "./helpers";
|
|
9
|
-
import { validateRoutes } from "./validators";
|
|
10
9
|
|
|
11
10
|
import type { RouteConfig, RoutesDependencies } from "./types";
|
|
12
11
|
import type { GuardFnFactory, Route } from "../../types";
|
|
@@ -39,18 +38,9 @@ export interface RoutesStore<
|
|
|
39
38
|
readonly pendingCanActivate: Map<string, GuardFnFactory<Dependencies>>;
|
|
40
39
|
readonly pendingCanDeactivate: Map<string, GuardFnFactory<Dependencies>>;
|
|
41
40
|
readonly treeOperations: {
|
|
42
|
-
readonly commitTreeChanges: (
|
|
43
|
-
store: RoutesStore<Dependencies>,
|
|
44
|
-
noValidate: boolean,
|
|
45
|
-
) => void;
|
|
41
|
+
readonly commitTreeChanges: (store: RoutesStore<Dependencies>) => void;
|
|
46
42
|
readonly resetStore: (store: RoutesStore<Dependencies>) => void;
|
|
47
43
|
readonly nodeToDefinition: (node: RouteTree) => RouteDefinition;
|
|
48
|
-
readonly validateRoutes: (
|
|
49
|
-
routes: Route<Dependencies>[],
|
|
50
|
-
tree?: RouteTree,
|
|
51
|
-
forwardMap?: Record<string, string>,
|
|
52
|
-
parentName?: string,
|
|
53
|
-
) => void;
|
|
54
44
|
};
|
|
55
45
|
}
|
|
56
46
|
|
|
@@ -73,7 +63,7 @@ function rebuildTree(
|
|
|
73
63
|
|
|
74
64
|
export function commitTreeChanges<
|
|
75
65
|
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
76
|
-
>(store: RoutesStore<Dependencies
|
|
66
|
+
>(store: RoutesStore<Dependencies>): void {
|
|
77
67
|
const result = rebuildTree(
|
|
78
68
|
store.definitions,
|
|
79
69
|
store.rootPath,
|
|
@@ -82,7 +72,7 @@ export function commitTreeChanges<
|
|
|
82
72
|
|
|
83
73
|
store.tree = result.tree;
|
|
84
74
|
store.matcher = result.matcher;
|
|
85
|
-
store.resolvedForwardMap = refreshForwardMap(store.config
|
|
75
|
+
store.resolvedForwardMap = refreshForwardMap(store.config);
|
|
86
76
|
}
|
|
87
77
|
|
|
88
78
|
export function rebuildTreeInPlace<
|
|
@@ -135,20 +125,7 @@ export function clearRouteData<
|
|
|
135
125
|
// Forward map
|
|
136
126
|
// =============================================================================
|
|
137
127
|
|
|
138
|
-
export function refreshForwardMap(
|
|
139
|
-
config: RouteConfig,
|
|
140
|
-
noValidate: boolean,
|
|
141
|
-
): Record<string, string> {
|
|
142
|
-
if (noValidate) {
|
|
143
|
-
return cacheForwardMap(config);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return validateAndCacheForwardMap(config);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function validateAndCacheForwardMap(
|
|
150
|
-
config: RouteConfig,
|
|
151
|
-
): Record<string, string> {
|
|
128
|
+
export function refreshForwardMap(config: RouteConfig): Record<string, string> {
|
|
152
129
|
const map = Object.create(null) as Record<string, string>;
|
|
153
130
|
|
|
154
131
|
for (const fromRoute of Object.keys(config.forwardMap)) {
|
|
@@ -158,22 +135,6 @@ function validateAndCacheForwardMap(
|
|
|
158
135
|
return map;
|
|
159
136
|
}
|
|
160
137
|
|
|
161
|
-
function cacheForwardMap(config: RouteConfig): Record<string, string> {
|
|
162
|
-
const map = Object.create(null) as Record<string, string>;
|
|
163
|
-
|
|
164
|
-
for (const fromRoute of Object.keys(config.forwardMap)) {
|
|
165
|
-
let current = fromRoute;
|
|
166
|
-
|
|
167
|
-
while (config.forwardMap[current]) {
|
|
168
|
-
current = config.forwardMap[current];
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
map[fromRoute] = current;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return map;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
138
|
// =============================================================================
|
|
178
139
|
// Route handler registration
|
|
179
140
|
// =============================================================================
|
|
@@ -209,7 +170,6 @@ function registerForwardTo<Dependencies extends DefaultDependencies>(
|
|
|
209
170
|
);
|
|
210
171
|
}
|
|
211
172
|
|
|
212
|
-
// Async validation ALWAYS runs (even with noValidate=true)
|
|
213
173
|
if (typeof route.forwardTo === "function") {
|
|
214
174
|
const isNativeAsync =
|
|
215
175
|
(route.forwardTo as { constructor: { name: string } }).constructor
|
|
@@ -254,7 +214,7 @@ function registerSingleRouteHandlers<Dependencies extends DefaultDependencies>(
|
|
|
254
214
|
"defaultParams",
|
|
255
215
|
]);
|
|
256
216
|
const customFields = Object.fromEntries(
|
|
257
|
-
Object.entries(route).filter(([
|
|
217
|
+
Object.entries(route).filter(([key]) => !standardKeys.has(key)),
|
|
258
218
|
);
|
|
259
219
|
|
|
260
220
|
if (Object.keys(customFields).length > 0) {
|
|
@@ -342,7 +302,6 @@ export function createRoutesStore<
|
|
|
342
302
|
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
343
303
|
>(
|
|
344
304
|
routes: Route<Dependencies>[],
|
|
345
|
-
noValidate: boolean,
|
|
346
305
|
matcherOptions?: CreateMatcherOptions,
|
|
347
306
|
): RoutesStore<Dependencies> {
|
|
348
307
|
const definitions: RouteDefinition[] = [];
|
|
@@ -370,9 +329,7 @@ export function createRoutesStore<
|
|
|
370
329
|
"",
|
|
371
330
|
);
|
|
372
331
|
|
|
373
|
-
const resolvedForwardMap
|
|
374
|
-
? cacheForwardMap(config)
|
|
375
|
-
: validateAndCacheForwardMap(config);
|
|
332
|
+
const resolvedForwardMap = refreshForwardMap(config);
|
|
376
333
|
|
|
377
334
|
return {
|
|
378
335
|
definitions,
|
|
@@ -391,7 +348,6 @@ export function createRoutesStore<
|
|
|
391
348
|
commitTreeChanges,
|
|
392
349
|
resetStore,
|
|
393
350
|
nodeToDefinition,
|
|
394
|
-
validateRoutes,
|
|
395
351
|
},
|
|
396
352
|
};
|
|
397
353
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// packages/core/src/namespaces/StateNamespace/StateNamespace.ts
|
|
2
2
|
|
|
3
|
-
import { getTypeDescription, validateState } from "type-guards";
|
|
4
|
-
|
|
5
3
|
import { areParamValuesEqual, getUrlParamsFromMeta } from "./helpers";
|
|
6
4
|
import { EMPTY_PARAMS } from "../../constants";
|
|
7
5
|
import { freezeStateInPlace } from "../../helpers";
|
|
@@ -42,37 +40,6 @@ export class StateNamespace {
|
|
|
42
40
|
*/
|
|
43
41
|
readonly #urlParamsCache = new Map<string, string[]>();
|
|
44
42
|
|
|
45
|
-
// =========================================================================
|
|
46
|
-
// Static validation methods (called by facade before instance methods)
|
|
47
|
-
// =========================================================================
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Validates areStatesEqual arguments.
|
|
51
|
-
*/
|
|
52
|
-
static validateAreStatesEqualArgs(
|
|
53
|
-
state1: unknown,
|
|
54
|
-
state2: unknown,
|
|
55
|
-
ignoreQueryParams: unknown,
|
|
56
|
-
): void {
|
|
57
|
-
// null/undefined are valid (represent "no state")
|
|
58
|
-
if (state1 != null) {
|
|
59
|
-
validateState(state1, "areStatesEqual");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (state2 != null) {
|
|
63
|
-
validateState(state2, "areStatesEqual");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
ignoreQueryParams !== undefined &&
|
|
68
|
-
typeof ignoreQueryParams !== "boolean"
|
|
69
|
-
) {
|
|
70
|
-
throw new TypeError(
|
|
71
|
-
`[router.areStatesEqual] Invalid ignoreQueryParams: ${getTypeDescription(ignoreQueryParams)}. Expected boolean.`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
43
|
// =========================================================================
|
|
77
44
|
// Instance methods (trust input - already validated by facade)
|
|
78
45
|
// =========================================================================
|
|
@@ -36,7 +36,7 @@ export function areParamValuesEqual(val1: unknown, val2: unknown): boolean {
|
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
return val1.every((
|
|
39
|
+
return val1.every((value, i) => areParamValuesEqual(value, val2[i]));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
return false;
|
package/src/namespaces/index.ts
CHANGED
|
@@ -8,8 +8,6 @@ export {
|
|
|
8
8
|
deepFreeze,
|
|
9
9
|
defaultOptions,
|
|
10
10
|
OptionsNamespace,
|
|
11
|
-
VALID_OPTION_VALUES,
|
|
12
|
-
VALID_QUERY_PARAMS,
|
|
13
11
|
} from "./OptionsNamespace";
|
|
14
12
|
|
|
15
13
|
export { StateNamespace } from "./StateNamespace";
|
|
@@ -25,7 +23,6 @@ export { RouteLifecycleNamespace } from "./RouteLifecycleNamespace";
|
|
|
25
23
|
export {
|
|
26
24
|
RoutesNamespace,
|
|
27
25
|
DEFAULT_ROUTE_NAME,
|
|
28
|
-
validatedRouteNames,
|
|
29
26
|
createEmptyConfig,
|
|
30
27
|
} from "./RoutesNamespace";
|
|
31
28
|
|
package/src/typeGuards.ts
CHANGED
|
@@ -1,23 +1,9 @@
|
|
|
1
1
|
// packages/core/src/typeGuards.ts
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Re-export common type guards from centralized type-guards package
|
|
5
|
-
*/
|
|
6
|
-
import type { LoggerConfig, LogLevelConfig } from "@real-router/logger";
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
isObjKey,
|
|
10
|
-
isString,
|
|
11
|
-
isState,
|
|
12
|
-
isParams,
|
|
13
|
-
isNavigationOptions,
|
|
14
|
-
isBoolean,
|
|
15
|
-
validateRouteName,
|
|
16
|
-
} from "type-guards";
|
|
17
|
-
|
|
18
3
|
/**
|
|
19
4
|
* RealRouter-specific type guards for logger configuration
|
|
20
5
|
*/
|
|
6
|
+
import type { LoggerConfig, LogLevelConfig } from "@real-router/logger";
|
|
21
7
|
|
|
22
8
|
const VALID_LEVELS_SET = new Set<string>(["all", "warn-error", "error-only"]);
|
|
23
9
|
|