@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,566 +0,0 @@
|
|
|
1
|
-
// packages/core/src/namespaces/RoutesNamespace/validators.ts
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Static validation functions for RoutesNamespace.
|
|
5
|
-
* Called by Router facade before instance methods.
|
|
6
|
-
*
|
|
7
|
-
* Extracted from RoutesNamespace class for better separation of concerns.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { logger } from "@real-router/logger";
|
|
11
|
-
import { validateRoute } from "route-tree";
|
|
12
|
-
import {
|
|
13
|
-
isString,
|
|
14
|
-
validateRouteName,
|
|
15
|
-
isParams,
|
|
16
|
-
getTypeDescription,
|
|
17
|
-
} from "type-guards";
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
resolveForwardChain,
|
|
21
|
-
validateForwardToTargets,
|
|
22
|
-
validateRouteProperties,
|
|
23
|
-
} from "./forwardToValidation";
|
|
24
|
-
|
|
25
|
-
import type { RouteConfig } from "./types";
|
|
26
|
-
import type { Route, RouteConfigUpdate } from "../../types";
|
|
27
|
-
import type {
|
|
28
|
-
DefaultDependencies,
|
|
29
|
-
ForwardToCallback,
|
|
30
|
-
} from "@real-router/types";
|
|
31
|
-
import type { Matcher, RouteTree } from "route-tree";
|
|
32
|
-
|
|
33
|
-
// SECURITY: Reserved prefix for system routes (e.g., @@router/UNKNOWN_ROUTE).
|
|
34
|
-
// Internal code (RouterWiringBuilder, routesStore) bypasses this check.
|
|
35
|
-
const INTERNAL_ROUTE_PREFIX = "@@";
|
|
36
|
-
|
|
37
|
-
export function throwIfInternalRoute(name: string, methodName: string): void {
|
|
38
|
-
if (name.startsWith(INTERNAL_ROUTE_PREFIX)) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
`[router.${methodName}] Route name "${name}" uses the reserved "${INTERNAL_ROUTE_PREFIX}" prefix. Routes with this prefix are internal and cannot be modified through the public API.`,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function throwIfInternalRouteInArray(
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Route type
|
|
47
|
-
routes: readonly Route<any>[],
|
|
48
|
-
methodName: string,
|
|
49
|
-
): void {
|
|
50
|
-
for (const route of routes) {
|
|
51
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime safety
|
|
52
|
-
if (route && typeof route === "object" && typeof route.name === "string") {
|
|
53
|
-
throwIfInternalRoute(route.name, methodName);
|
|
54
|
-
|
|
55
|
-
if (route.children) {
|
|
56
|
-
throwIfInternalRouteInArray(route.children, methodName);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Validates removeRoute arguments.
|
|
64
|
-
*/
|
|
65
|
-
export function validateRemoveRouteArgs(name: unknown): asserts name is string {
|
|
66
|
-
validateRouteName(name, "removeRoute");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Validates setRootPath arguments.
|
|
71
|
-
*/
|
|
72
|
-
export function validateSetRootPathArgs(
|
|
73
|
-
rootPath: unknown,
|
|
74
|
-
): asserts rootPath is string {
|
|
75
|
-
if (typeof rootPath !== "string") {
|
|
76
|
-
throw new TypeError(
|
|
77
|
-
`[router.setRootPath] rootPath must be a string, got ${getTypeDescription(rootPath)}`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Validates addRoute arguments (route structure and properties).
|
|
84
|
-
* State-dependent validation (duplicates, tree) happens in instance method.
|
|
85
|
-
*/
|
|
86
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Route type
|
|
87
|
-
export function validateAddRouteArgs(routes: readonly Route<any>[]): void {
|
|
88
|
-
for (const route of routes) {
|
|
89
|
-
// First check if route is an object (before accessing route.name)
|
|
90
|
-
// Runtime check for invalid types passed via `as any`
|
|
91
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime check
|
|
92
|
-
if (route === null || typeof route !== "object" || Array.isArray(route)) {
|
|
93
|
-
throw new TypeError(
|
|
94
|
-
`[router.addRoute] Route must be an object, got ${getTypeDescription(route)}`,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Validate route properties (canActivate, canDeactivate, defaultParams, async checks)
|
|
99
|
-
// Note: validateRouteProperties handles children recursively
|
|
100
|
-
validateRouteProperties(route, route.name);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Validates parent option for addRoute.
|
|
106
|
-
*/
|
|
107
|
-
export function validateParentOption(
|
|
108
|
-
parent: unknown,
|
|
109
|
-
): asserts parent is string {
|
|
110
|
-
if (typeof parent !== "string" || parent === "") {
|
|
111
|
-
throw new TypeError(
|
|
112
|
-
`[router.addRoute] parent option must be a non-empty string, got ${getTypeDescription(parent)}`,
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Validate parent is a valid route name format (can contain dots — it's a fullName reference)
|
|
117
|
-
validateRouteName(parent, "addRoute");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Validates isActiveRoute arguments.
|
|
122
|
-
*/
|
|
123
|
-
export function validateIsActiveRouteArgs(
|
|
124
|
-
name: unknown,
|
|
125
|
-
params: unknown,
|
|
126
|
-
strictEquality: unknown,
|
|
127
|
-
ignoreQueryParams: unknown,
|
|
128
|
-
): void {
|
|
129
|
-
// Validate name - non-string throws
|
|
130
|
-
if (!isString(name)) {
|
|
131
|
-
throw new TypeError(`Route name must be a string`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Validate params if provided
|
|
135
|
-
if (params !== undefined && !isParams(params)) {
|
|
136
|
-
throw new TypeError(`[router.isActiveRoute] Invalid params structure`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Validate strictEquality if provided
|
|
140
|
-
if (strictEquality !== undefined && typeof strictEquality !== "boolean") {
|
|
141
|
-
throw new TypeError(
|
|
142
|
-
`[router.isActiveRoute] strictEquality must be a boolean, got ${typeof strictEquality}`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Validate ignoreQueryParams if provided
|
|
147
|
-
if (
|
|
148
|
-
ignoreQueryParams !== undefined &&
|
|
149
|
-
typeof ignoreQueryParams !== "boolean"
|
|
150
|
-
) {
|
|
151
|
-
throw new TypeError(
|
|
152
|
-
`[router.isActiveRoute] ignoreQueryParams must be a boolean, got ${typeof ignoreQueryParams}`,
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Validates forwardState/buildState arguments.
|
|
159
|
-
*/
|
|
160
|
-
export function validateStateBuilderArgs(
|
|
161
|
-
routeName: unknown,
|
|
162
|
-
routeParams: unknown,
|
|
163
|
-
methodName: string,
|
|
164
|
-
): void {
|
|
165
|
-
if (!isString(routeName)) {
|
|
166
|
-
throw new TypeError(
|
|
167
|
-
`[router.${methodName}] Invalid routeName: ${getTypeDescription(routeName)}. Expected string.`,
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!isParams(routeParams)) {
|
|
172
|
-
throw new TypeError(
|
|
173
|
-
`[router.${methodName}] Invalid routeParams: ${getTypeDescription(routeParams)}. Expected plain object.`,
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Validates updateRoute basic arguments (name and updates object structure).
|
|
180
|
-
* Does NOT read property values to allow caller to cache them first.
|
|
181
|
-
*/
|
|
182
|
-
export function validateUpdateRouteBasicArgs<
|
|
183
|
-
Dependencies extends DefaultDependencies,
|
|
184
|
-
>(
|
|
185
|
-
name: unknown,
|
|
186
|
-
updates: unknown,
|
|
187
|
-
): asserts updates is RouteConfigUpdate<Dependencies> {
|
|
188
|
-
// Validate name
|
|
189
|
-
validateRouteName(name, "updateRoute");
|
|
190
|
-
|
|
191
|
-
if (name === "") {
|
|
192
|
-
throw new ReferenceError(
|
|
193
|
-
`[router.updateRoute] Invalid name: empty string. Cannot update root node.`,
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Validate updates is not null
|
|
198
|
-
|
|
199
|
-
if (updates === null) {
|
|
200
|
-
throw new TypeError(
|
|
201
|
-
`[real-router] updateRoute: updates must be an object, got null`,
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Validate updates is an object (not array)
|
|
206
|
-
if (typeof updates !== "object" || Array.isArray(updates)) {
|
|
207
|
-
throw new TypeError(
|
|
208
|
-
`[real-router] updateRoute: updates must be an object, got ${getTypeDescription(updates)}`,
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Asserts that a function is not async (native or transpiled).
|
|
215
|
-
* Checks both constructor name and toString() for __awaiter pattern.
|
|
216
|
-
*/
|
|
217
|
-
/* v8 ignore next 12 -- @preserve: transpiled async (__awaiter) branch tested in addRoute */
|
|
218
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- needs constructor.name access
|
|
219
|
-
function assertNotAsync(value: Function, paramName: string): void {
|
|
220
|
-
if (
|
|
221
|
-
(value as { constructor: { name: string } }).constructor.name ===
|
|
222
|
-
"AsyncFunction" ||
|
|
223
|
-
(value as { toString: () => string }).toString().includes("__awaiter")
|
|
224
|
-
) {
|
|
225
|
-
throw new TypeError(
|
|
226
|
-
`[real-router] updateRoute: ${paramName} cannot be an async function`,
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Validates that a value is a non-async function, if provided.
|
|
233
|
-
*/
|
|
234
|
-
function validateFunctionParam(value: unknown, paramName: string): void {
|
|
235
|
-
if (value === undefined || value === null) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (typeof value !== "function") {
|
|
240
|
-
throw new TypeError(
|
|
241
|
-
`[real-router] updateRoute: ${paramName} must be a function or null, got ${typeof value}`,
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
assertNotAsync(value, paramName);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Validates updateRoute property types using pre-cached values.
|
|
250
|
-
* Called AFTER properties are cached to ensure getters are called only once.
|
|
251
|
-
*/
|
|
252
|
-
export function validateUpdateRoutePropertyTypes(
|
|
253
|
-
forwardTo: unknown,
|
|
254
|
-
defaultParams: unknown,
|
|
255
|
-
decodeParams: unknown,
|
|
256
|
-
encodeParams: unknown,
|
|
257
|
-
): void {
|
|
258
|
-
// Validate forwardTo type (existence check is done by instance method)
|
|
259
|
-
if (forwardTo !== undefined && forwardTo !== null) {
|
|
260
|
-
if (typeof forwardTo !== "string" && typeof forwardTo !== "function") {
|
|
261
|
-
throw new TypeError(
|
|
262
|
-
`[real-router] updateRoute: forwardTo must be a string, function, or null, got ${getTypeDescription(forwardTo)}`,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (typeof forwardTo === "function") {
|
|
267
|
-
assertNotAsync(forwardTo, "forwardTo callback");
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Validate defaultParams
|
|
272
|
-
if (
|
|
273
|
-
defaultParams !== undefined &&
|
|
274
|
-
defaultParams !== null &&
|
|
275
|
-
(typeof defaultParams !== "object" || Array.isArray(defaultParams))
|
|
276
|
-
) {
|
|
277
|
-
throw new TypeError(
|
|
278
|
-
`[real-router] updateRoute: defaultParams must be an object or null, got ${getTypeDescription(defaultParams)}`,
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
validateFunctionParam(decodeParams, "decodeParams");
|
|
283
|
-
validateFunctionParam(encodeParams, "encodeParams");
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Validates buildPath arguments.
|
|
288
|
-
*/
|
|
289
|
-
export function validateBuildPathArgs(route: unknown): asserts route is string {
|
|
290
|
-
if (!isString(route) || route === "") {
|
|
291
|
-
throw new TypeError(
|
|
292
|
-
`[real-router] buildPath: route must be a non-empty string, got ${typeof route === "string" ? '""' : typeof route}`,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Validates matchPath arguments.
|
|
299
|
-
*/
|
|
300
|
-
export function validateMatchPathArgs(path: unknown): asserts path is string {
|
|
301
|
-
if (!isString(path)) {
|
|
302
|
-
throw new TypeError(
|
|
303
|
-
`[real-router] matchPath: path must be a string, got ${typeof path}`,
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Validates shouldUpdateNode arguments.
|
|
310
|
-
*/
|
|
311
|
-
export function validateShouldUpdateNodeArgs(
|
|
312
|
-
nodeName: unknown,
|
|
313
|
-
): asserts nodeName is string {
|
|
314
|
-
if (!isString(nodeName)) {
|
|
315
|
-
throw new TypeError(
|
|
316
|
-
`[router.shouldUpdateNode] nodeName must be a string, got ${typeof nodeName}`,
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Validates routes for addition to the router.
|
|
323
|
-
* Checks parent existence, duplicates, and forwardTo targets/cycles.
|
|
324
|
-
*
|
|
325
|
-
* @param routes - Routes to validate
|
|
326
|
-
* @param tree - Current route tree (optional for initial validation)
|
|
327
|
-
* @param forwardMap - Current forward map for cycle detection
|
|
328
|
-
* @param parentName - Optional parent route fullName for nesting via addRoute({ parent })
|
|
329
|
-
*/
|
|
330
|
-
export function validateRoutes<Dependencies extends DefaultDependencies>(
|
|
331
|
-
routes: Route<Dependencies>[],
|
|
332
|
-
tree?: RouteTree,
|
|
333
|
-
forwardMap?: Record<string, string>,
|
|
334
|
-
parentName?: string,
|
|
335
|
-
): void {
|
|
336
|
-
// Validate parent route exists in tree
|
|
337
|
-
if (parentName && tree) {
|
|
338
|
-
let node: RouteTree | undefined = tree;
|
|
339
|
-
|
|
340
|
-
for (const segment of parentName.split(".")) {
|
|
341
|
-
node = node.children.get(segment);
|
|
342
|
-
|
|
343
|
-
if (!node) {
|
|
344
|
-
throw new Error(
|
|
345
|
-
`[router.addRoute] Parent route "${parentName}" does not exist`,
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Tracking sets for duplicate detection
|
|
352
|
-
const seenNames = new Set<string>();
|
|
353
|
-
const seenPathsByParent = new Map<string, Set<string>>();
|
|
354
|
-
|
|
355
|
-
for (const route of routes) {
|
|
356
|
-
validateRoute(
|
|
357
|
-
route,
|
|
358
|
-
"addRoute",
|
|
359
|
-
tree,
|
|
360
|
-
parentName ?? "",
|
|
361
|
-
seenNames,
|
|
362
|
-
seenPathsByParent,
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (tree && forwardMap) {
|
|
367
|
-
validateForwardToTargets(routes, forwardMap, tree);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// ============================================================================
|
|
372
|
-
// Instance-level validators (moved from routesCrud.ts)
|
|
373
|
-
// ============================================================================
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Collects URL params from segments into a Set.
|
|
377
|
-
*/
|
|
378
|
-
function collectUrlParams(segments: readonly RouteTree[]): Set<string> {
|
|
379
|
-
const params = new Set<string>();
|
|
380
|
-
|
|
381
|
-
for (const segment of segments) {
|
|
382
|
-
for (const param of segment.paramMeta.urlParams) {
|
|
383
|
-
params.add(param);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return params;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Validates removeRoute constraints.
|
|
392
|
-
* Returns false if removal should be blocked (route is active).
|
|
393
|
-
* Logs warnings for edge cases.
|
|
394
|
-
*
|
|
395
|
-
* @param name - Route name to remove
|
|
396
|
-
* @param currentStateName - Current active route name (or undefined)
|
|
397
|
-
* @param isNavigating - Whether navigation is in progress
|
|
398
|
-
* @returns true if removal can proceed, false if blocked
|
|
399
|
-
*/
|
|
400
|
-
export function validateRemoveRoute(
|
|
401
|
-
name: string,
|
|
402
|
-
currentStateName: string | undefined,
|
|
403
|
-
isNavigating: boolean,
|
|
404
|
-
): boolean {
|
|
405
|
-
// Check if trying to remove currently active route (or its parent)
|
|
406
|
-
if (currentStateName) {
|
|
407
|
-
const isExactMatch = currentStateName === name;
|
|
408
|
-
const isParentOfCurrent = currentStateName.startsWith(`${name}.`);
|
|
409
|
-
|
|
410
|
-
if (isExactMatch || isParentOfCurrent) {
|
|
411
|
-
const suffix = isExactMatch ? "" : ` (current: "${currentStateName}")`;
|
|
412
|
-
|
|
413
|
-
logger.warn(
|
|
414
|
-
"router.removeRoute",
|
|
415
|
-
`Cannot remove route "${name}" — it is currently active${suffix}. Navigate away first.`,
|
|
416
|
-
);
|
|
417
|
-
|
|
418
|
-
return false;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Warn if navigation is in progress (but allow removal)
|
|
423
|
-
if (isNavigating) {
|
|
424
|
-
logger.warn(
|
|
425
|
-
"router.removeRoute",
|
|
426
|
-
`Route "${name}" removed while navigation is in progress. This may cause unexpected behavior.`,
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return true;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Validates clearRoutes operation.
|
|
435
|
-
* Returns false if operation should be blocked (navigation in progress).
|
|
436
|
-
*
|
|
437
|
-
* @param isNavigating - Whether navigation is in progress
|
|
438
|
-
* @returns true if clearRoutes can proceed, false if blocked
|
|
439
|
-
*/
|
|
440
|
-
export function validateClearRoutes(isNavigating: boolean): boolean {
|
|
441
|
-
if (isNavigating) {
|
|
442
|
-
logger.error(
|
|
443
|
-
"router.clearRoutes",
|
|
444
|
-
"Cannot clear routes while navigation is in progress. Wait for navigation to complete.",
|
|
445
|
-
);
|
|
446
|
-
|
|
447
|
-
return false;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return true;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Validates that forwardTo target doesn't require params that source doesn't have.
|
|
455
|
-
*
|
|
456
|
-
* @param sourceName - Source route name
|
|
457
|
-
* @param targetName - Target route name
|
|
458
|
-
* @param matcher - Current route matcher
|
|
459
|
-
*/
|
|
460
|
-
export function validateForwardToParamCompatibility(
|
|
461
|
-
sourceName: string,
|
|
462
|
-
targetName: string,
|
|
463
|
-
matcher: Matcher,
|
|
464
|
-
): void {
|
|
465
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
466
|
-
const sourceSegments = matcher.getSegmentsByName(
|
|
467
|
-
sourceName,
|
|
468
|
-
)! as readonly RouteTree[];
|
|
469
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
470
|
-
const targetSegments = matcher.getSegmentsByName(
|
|
471
|
-
targetName,
|
|
472
|
-
)! as readonly RouteTree[];
|
|
473
|
-
|
|
474
|
-
// Get source URL params as a Set for O(1) lookup
|
|
475
|
-
const sourceParams = collectUrlParams(sourceSegments);
|
|
476
|
-
|
|
477
|
-
// Build target URL params array (inline — no separate helper needed)
|
|
478
|
-
const targetParams: string[] = [];
|
|
479
|
-
|
|
480
|
-
for (const segment of targetSegments) {
|
|
481
|
-
for (const param of segment.paramMeta.urlParams) {
|
|
482
|
-
targetParams.push(param);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Check if target requires params that source doesn't have
|
|
487
|
-
const missingParams = targetParams.filter(
|
|
488
|
-
(param) => !sourceParams.has(param),
|
|
489
|
-
);
|
|
490
|
-
|
|
491
|
-
if (missingParams.length > 0) {
|
|
492
|
-
throw new Error(
|
|
493
|
-
`[real-router] forwardTo target "${targetName}" requires params ` +
|
|
494
|
-
`[${missingParams.join(", ")}] that are not available in source route "${sourceName}"`,
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Validates that adding forwardTo doesn't create a cycle.
|
|
501
|
-
* Creates a test map with the new entry and uses resolveForwardChain
|
|
502
|
-
* to detect cycles before any mutation happens.
|
|
503
|
-
*
|
|
504
|
-
* @param sourceName - Source route name
|
|
505
|
-
* @param targetName - Target route name
|
|
506
|
-
* @param config - Current route config (forwardMap read-only in this call)
|
|
507
|
-
*/
|
|
508
|
-
export function validateForwardToCycle(
|
|
509
|
-
sourceName: string,
|
|
510
|
-
targetName: string,
|
|
511
|
-
config: RouteConfig,
|
|
512
|
-
): void {
|
|
513
|
-
// Create a test map with the new entry to validate BEFORE mutation
|
|
514
|
-
const testMap = {
|
|
515
|
-
...config.forwardMap,
|
|
516
|
-
[sourceName]: targetName,
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
// resolveForwardChain will throw if cycle is detected or max depth exceeded
|
|
520
|
-
resolveForwardChain(sourceName, testMap);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Validates updateRoute instance-level constraints (route existence, forwardTo).
|
|
525
|
-
*
|
|
526
|
-
* @param name - Route name (already validated by static method)
|
|
527
|
-
* @param forwardTo - Cached forwardTo value
|
|
528
|
-
* @param hasRoute - Function to check route existence
|
|
529
|
-
* @param matcher - Current route matcher
|
|
530
|
-
* @param config - Current route config
|
|
531
|
-
*/
|
|
532
|
-
export function validateUpdateRoute<
|
|
533
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
534
|
-
>(
|
|
535
|
-
name: string,
|
|
536
|
-
forwardTo: string | ForwardToCallback<Dependencies> | null | undefined,
|
|
537
|
-
hasRoute: (n: string) => boolean,
|
|
538
|
-
matcher: Matcher,
|
|
539
|
-
config: RouteConfig,
|
|
540
|
-
): void {
|
|
541
|
-
// Validate route exists
|
|
542
|
-
if (!hasRoute(name)) {
|
|
543
|
-
throw new ReferenceError(
|
|
544
|
-
`[real-router] updateRoute: route "${name}" does not exist`,
|
|
545
|
-
);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Validate forwardTo target exists and is valid (only for string forwardTo)
|
|
549
|
-
if (
|
|
550
|
-
forwardTo !== undefined &&
|
|
551
|
-
forwardTo !== null &&
|
|
552
|
-
typeof forwardTo === "string"
|
|
553
|
-
) {
|
|
554
|
-
if (!hasRoute(forwardTo)) {
|
|
555
|
-
throw new Error(
|
|
556
|
-
`[real-router] updateRoute: forwardTo target "${forwardTo}" does not exist`,
|
|
557
|
-
);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// Check forwardTo param compatibility
|
|
561
|
-
validateForwardToParamCompatibility(name, forwardTo, matcher);
|
|
562
|
-
|
|
563
|
-
// Check for cycle detection
|
|
564
|
-
validateForwardToCycle(name, forwardTo, config);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// packages/core/src/namespaces/StateNamespace/validators.ts
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Static validation functions for StateNamespace.
|
|
5
|
-
* Called by Router facade before instance methods.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { isString, isParams, getTypeDescription } from "type-guards";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Validates makeState arguments.
|
|
12
|
-
*/
|
|
13
|
-
export function validateMakeStateArgs(
|
|
14
|
-
name: unknown,
|
|
15
|
-
params: unknown,
|
|
16
|
-
path: unknown,
|
|
17
|
-
forceId: unknown,
|
|
18
|
-
): void {
|
|
19
|
-
// Validate name is a string
|
|
20
|
-
if (!isString(name)) {
|
|
21
|
-
throw new TypeError(
|
|
22
|
-
`[router.makeState] Invalid name: ${getTypeDescription(name)}. Expected string.`,
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Validate params if provided
|
|
27
|
-
if (params !== undefined && !isParams(params)) {
|
|
28
|
-
throw new TypeError(
|
|
29
|
-
`[router.makeState] Invalid params: ${getTypeDescription(params)}. Expected plain object.`,
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Validate path if provided
|
|
34
|
-
if (path !== undefined && !isString(path)) {
|
|
35
|
-
throw new TypeError(
|
|
36
|
-
`[router.makeState] Invalid path: ${getTypeDescription(path)}. Expected string.`,
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Validate forceId if provided
|
|
41
|
-
if (forceId !== undefined && typeof forceId !== "number") {
|
|
42
|
-
throw new TypeError(
|
|
43
|
-
`[router.makeState] Invalid forceId: ${getTypeDescription(forceId)}. Expected number.`,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
}
|