@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,408 +0,0 @@
|
|
|
1
|
-
// packages/core/src/namespaces/RoutesNamespace/forwardToValidation.ts
|
|
2
|
-
|
|
3
|
-
import { getSegmentsByName } from "route-tree";
|
|
4
|
-
import { getTypeDescription } from "type-guards";
|
|
5
|
-
|
|
6
|
-
import type { Route } from "../../types";
|
|
7
|
-
import type { DefaultDependencies } from "@real-router/types";
|
|
8
|
-
import type { RouteTree } from "route-tree";
|
|
9
|
-
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// Route Property Validation
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Validates forwardTo property type and async status.
|
|
16
|
-
*/
|
|
17
|
-
function validateForwardToProperty(forwardTo: unknown, fullName: string): void {
|
|
18
|
-
if (forwardTo === undefined) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (typeof forwardTo === "function") {
|
|
23
|
-
const isNativeAsync =
|
|
24
|
-
(forwardTo as { constructor: { name: string } }).constructor.name ===
|
|
25
|
-
"AsyncFunction";
|
|
26
|
-
const isTranspiledAsync = forwardTo.toString().includes("__awaiter");
|
|
27
|
-
|
|
28
|
-
if (isNativeAsync || isTranspiledAsync) {
|
|
29
|
-
throw new TypeError(
|
|
30
|
-
`[router.addRoute] forwardTo callback cannot be async for route "${fullName}". ` +
|
|
31
|
-
`Async functions break matchPath/buildPath.`,
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Validates route properties for addRoute.
|
|
39
|
-
* Throws TypeError if any property is invalid.
|
|
40
|
-
*
|
|
41
|
-
* @param route - Route to validate
|
|
42
|
-
* @param fullName - Full route name (with parent prefix)
|
|
43
|
-
* @throws {TypeError} If canActivate/canDeactivate is not a function
|
|
44
|
-
* @throws {TypeError} If defaultParams is not a plain object
|
|
45
|
-
* @throws {TypeError} If decodeParams/encodeParams is async
|
|
46
|
-
*/
|
|
47
|
-
export function validateRouteProperties<
|
|
48
|
-
Dependencies extends DefaultDependencies,
|
|
49
|
-
>(route: Route<Dependencies>, fullName: string): void {
|
|
50
|
-
// Validate canActivate is a function
|
|
51
|
-
if (
|
|
52
|
-
route.canActivate !== undefined &&
|
|
53
|
-
typeof route.canActivate !== "function"
|
|
54
|
-
) {
|
|
55
|
-
throw new TypeError(
|
|
56
|
-
`[router.addRoute] canActivate must be a function for route "${fullName}", ` +
|
|
57
|
-
`got ${getTypeDescription(route.canActivate)}`,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Validate canDeactivate is a function
|
|
62
|
-
if (
|
|
63
|
-
route.canDeactivate !== undefined &&
|
|
64
|
-
typeof route.canDeactivate !== "function"
|
|
65
|
-
) {
|
|
66
|
-
throw new TypeError(
|
|
67
|
-
`[router.addRoute] canDeactivate must be a function for route "${fullName}", ` +
|
|
68
|
-
`got ${getTypeDescription(route.canDeactivate)}`,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Validate defaultParams is a plain object
|
|
73
|
-
// Runtime check for invalid types passed via `as any`
|
|
74
|
-
if (route.defaultParams !== undefined) {
|
|
75
|
-
const params: unknown = route.defaultParams;
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
params === null ||
|
|
79
|
-
typeof params !== "object" ||
|
|
80
|
-
Array.isArray(params)
|
|
81
|
-
) {
|
|
82
|
-
throw new TypeError(
|
|
83
|
-
`[router.addRoute] defaultParams must be an object for route "${fullName}", ` +
|
|
84
|
-
`got ${getTypeDescription(route.defaultParams)}`,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Validate decodeParams is not async (sync required for matchPath/buildPath)
|
|
90
|
-
if (route.decodeParams?.constructor.name === "AsyncFunction") {
|
|
91
|
-
throw new TypeError(
|
|
92
|
-
`[router.addRoute] decodeParams cannot be async for route "${fullName}". Async functions break matchPath/buildPath.`,
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Validate encodeParams is not async (sync required for matchPath/buildPath)
|
|
97
|
-
if (route.encodeParams?.constructor.name === "AsyncFunction") {
|
|
98
|
-
throw new TypeError(
|
|
99
|
-
`[router.addRoute] encodeParams cannot be async for route "${fullName}". Async functions break matchPath/buildPath.`,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Validate forwardTo type and async
|
|
104
|
-
validateForwardToProperty(route.forwardTo, fullName);
|
|
105
|
-
|
|
106
|
-
// Recursively validate children
|
|
107
|
-
if (route.children) {
|
|
108
|
-
for (const child of route.children) {
|
|
109
|
-
const childFullName = `${fullName}.${child.name}`;
|
|
110
|
-
|
|
111
|
-
validateRouteProperties(child, childFullName);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ============================================================================
|
|
117
|
-
// ForwardTo Validation
|
|
118
|
-
// ============================================================================
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Extracts parameter names from a path string.
|
|
122
|
-
* Matches :param and *splat patterns.
|
|
123
|
-
*/
|
|
124
|
-
function extractParamsFromPath(path: string): Set<string> {
|
|
125
|
-
const params = new Set<string>();
|
|
126
|
-
const paramRegex = /[*:]([A-Z_a-z]\w*)/g;
|
|
127
|
-
let match;
|
|
128
|
-
|
|
129
|
-
while ((match = paramRegex.exec(path)) !== null) {
|
|
130
|
-
params.add(match[1]);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return params;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Extracts all parameters from multiple path segments.
|
|
138
|
-
*/
|
|
139
|
-
function extractParamsFromPaths(paths: readonly string[]): Set<string> {
|
|
140
|
-
const params = new Set<string>();
|
|
141
|
-
|
|
142
|
-
for (const path of paths) {
|
|
143
|
-
for (const param of extractParamsFromPath(path)) {
|
|
144
|
-
params.add(param);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return params;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Collects all path segments for a route from batch definitions.
|
|
153
|
-
* Traverses parent routes to include inherited path segments.
|
|
154
|
-
*/
|
|
155
|
-
function collectPathsToRoute<Dependencies extends DefaultDependencies>(
|
|
156
|
-
routes: readonly Route<Dependencies>[],
|
|
157
|
-
routeName: string,
|
|
158
|
-
parentName = "",
|
|
159
|
-
paths: string[] = [],
|
|
160
|
-
): string[] {
|
|
161
|
-
for (const route of routes) {
|
|
162
|
-
const fullName = parentName ? `${parentName}.${route.name}` : route.name;
|
|
163
|
-
const currentPaths = [...paths, route.path];
|
|
164
|
-
|
|
165
|
-
if (fullName === routeName) {
|
|
166
|
-
return currentPaths;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (route.children && routeName.startsWith(`${fullName}.`)) {
|
|
170
|
-
return collectPathsToRoute(
|
|
171
|
-
route.children,
|
|
172
|
-
routeName,
|
|
173
|
-
fullName,
|
|
174
|
-
currentPaths,
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/* v8 ignore next -- @preserve unreachable: callers validate existence */
|
|
180
|
-
throw new Error(
|
|
181
|
-
`[internal] collectPathsToRoute: route "${routeName}" not found`,
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Collects all route names from a batch of routes (including children).
|
|
187
|
-
*/
|
|
188
|
-
function collectRouteNames<Dependencies extends DefaultDependencies>(
|
|
189
|
-
routes: readonly Route<Dependencies>[],
|
|
190
|
-
parentName = "",
|
|
191
|
-
): Set<string> {
|
|
192
|
-
const names = new Set<string>();
|
|
193
|
-
|
|
194
|
-
for (const route of routes) {
|
|
195
|
-
const fullName = parentName ? `${parentName}.${route.name}` : route.name;
|
|
196
|
-
|
|
197
|
-
names.add(fullName);
|
|
198
|
-
|
|
199
|
-
if (route.children) {
|
|
200
|
-
for (const childName of collectRouteNames(route.children, fullName)) {
|
|
201
|
-
names.add(childName);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return names;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Collects all forwardTo mappings from a batch of routes (including children).
|
|
211
|
-
* Only collects string forwardTo values; callbacks are handled separately.
|
|
212
|
-
*/
|
|
213
|
-
function collectForwardMappings<Dependencies extends DefaultDependencies>(
|
|
214
|
-
routes: readonly Route<Dependencies>[],
|
|
215
|
-
parentName = "",
|
|
216
|
-
): Map<string, string> {
|
|
217
|
-
const mappings = new Map<string, string>();
|
|
218
|
-
|
|
219
|
-
for (const route of routes) {
|
|
220
|
-
const fullName = parentName ? `${parentName}.${route.name}` : route.name;
|
|
221
|
-
|
|
222
|
-
if (route.forwardTo && typeof route.forwardTo === "string") {
|
|
223
|
-
mappings.set(fullName, route.forwardTo);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (route.children) {
|
|
227
|
-
for (const [key, value] of collectForwardMappings(
|
|
228
|
-
route.children,
|
|
229
|
-
fullName,
|
|
230
|
-
)) {
|
|
231
|
-
mappings.set(key, value);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return mappings;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Extracts required path parameters from route segments.
|
|
241
|
-
*/
|
|
242
|
-
function getRequiredParams(segments: readonly RouteTree[]): Set<string> {
|
|
243
|
-
const params = new Set<string>();
|
|
244
|
-
|
|
245
|
-
for (const segment of segments) {
|
|
246
|
-
// Named routes always have parsers (null only for root without path)
|
|
247
|
-
for (const param of segment.paramMeta.urlParams) {
|
|
248
|
-
params.add(param);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
for (const param of segment.paramMeta.spatParams) {
|
|
252
|
-
params.add(param);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return params;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Checks if a route exists in the tree by navigating through children Map.
|
|
261
|
-
*/
|
|
262
|
-
function routeExistsInTree(tree: RouteTree, routeName: string): boolean {
|
|
263
|
-
const segments = routeName.split(".");
|
|
264
|
-
let current: RouteTree | undefined = tree;
|
|
265
|
-
|
|
266
|
-
for (const segment of segments) {
|
|
267
|
-
current = current.children.get(segment);
|
|
268
|
-
|
|
269
|
-
if (!current) {
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return true;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Gets the target route parameters for validation.
|
|
279
|
-
*/
|
|
280
|
-
function getTargetParams<Dependencies extends DefaultDependencies>(
|
|
281
|
-
targetRoute: string,
|
|
282
|
-
existsInTree: boolean,
|
|
283
|
-
tree: RouteTree,
|
|
284
|
-
routes: readonly Route<Dependencies>[],
|
|
285
|
-
): Set<string> {
|
|
286
|
-
if (existsInTree) {
|
|
287
|
-
/* v8 ignore next -- @preserve: ?? fallback unreachable — existsInTree guarantees non-null */
|
|
288
|
-
return getRequiredParams(getSegmentsByName(tree, targetRoute) ?? []);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Target is in batch
|
|
292
|
-
return extractParamsFromPaths(collectPathsToRoute(routes, targetRoute));
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Validates a single forward mapping for target existence and param compatibility.
|
|
297
|
-
*/
|
|
298
|
-
function validateSingleForward<Dependencies extends DefaultDependencies>(
|
|
299
|
-
fromRoute: string,
|
|
300
|
-
targetRoute: string,
|
|
301
|
-
routes: readonly Route<Dependencies>[],
|
|
302
|
-
batchNames: Set<string>,
|
|
303
|
-
tree: RouteTree,
|
|
304
|
-
): void {
|
|
305
|
-
const existsInTree = routeExistsInTree(tree, targetRoute);
|
|
306
|
-
const existsInBatch = batchNames.has(targetRoute);
|
|
307
|
-
|
|
308
|
-
if (!existsInTree && !existsInBatch) {
|
|
309
|
-
throw new Error(
|
|
310
|
-
`[router.addRoute] forwardTo target "${targetRoute}" does not exist ` +
|
|
311
|
-
`for route "${fromRoute}"`,
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Get source params
|
|
316
|
-
const fromParams = extractParamsFromPaths(
|
|
317
|
-
collectPathsToRoute(routes, fromRoute),
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
// Get target params
|
|
321
|
-
const toParams = getTargetParams(targetRoute, existsInTree, tree, routes);
|
|
322
|
-
|
|
323
|
-
// Check for missing params
|
|
324
|
-
const missingParams = [...toParams].filter((p) => !fromParams.has(p));
|
|
325
|
-
|
|
326
|
-
if (missingParams.length > 0) {
|
|
327
|
-
throw new Error(
|
|
328
|
-
`[router.addRoute] forwardTo target "${targetRoute}" requires params ` +
|
|
329
|
-
`[${missingParams.join(", ")}] that are not available in source route "${fromRoute}"`,
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Resolves a forwardTo chain to its final destination.
|
|
336
|
-
* Detects cycles and enforces max depth.
|
|
337
|
-
*/
|
|
338
|
-
export function resolveForwardChain(
|
|
339
|
-
startRoute: string,
|
|
340
|
-
forwardMap: Record<string, string>,
|
|
341
|
-
maxDepth = 100,
|
|
342
|
-
): string {
|
|
343
|
-
const visited = new Set<string>();
|
|
344
|
-
const chain: string[] = [startRoute];
|
|
345
|
-
let current = startRoute;
|
|
346
|
-
|
|
347
|
-
while (forwardMap[current]) {
|
|
348
|
-
const next = forwardMap[current];
|
|
349
|
-
|
|
350
|
-
if (visited.has(next)) {
|
|
351
|
-
const cycleStart = chain.indexOf(next);
|
|
352
|
-
const cycle = [...chain.slice(cycleStart), next];
|
|
353
|
-
|
|
354
|
-
throw new Error(`Circular forwardTo: ${cycle.join(" → ")}`);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
visited.add(current);
|
|
358
|
-
chain.push(next);
|
|
359
|
-
current = next;
|
|
360
|
-
|
|
361
|
-
if (chain.length > maxDepth) {
|
|
362
|
-
throw new Error(
|
|
363
|
-
`forwardTo chain exceeds maximum depth (${maxDepth}): ${chain.join(" → ")}`,
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return current;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Validates forwardTo targets and cycles BEFORE any modifications.
|
|
373
|
-
* This ensures atomicity - if validation fails, no routes are added.
|
|
374
|
-
*
|
|
375
|
-
* @param routes - Routes to validate
|
|
376
|
-
* @param existingForwardMap - Current forwardMap from router.config
|
|
377
|
-
* @param tree - Current route tree
|
|
378
|
-
*
|
|
379
|
-
* @throws {Error} If forwardTo target doesn't exist
|
|
380
|
-
* @throws {Error} If circular forwardTo is detected
|
|
381
|
-
*/
|
|
382
|
-
export function validateForwardToTargets<
|
|
383
|
-
Dependencies extends DefaultDependencies,
|
|
384
|
-
>(
|
|
385
|
-
routes: readonly Route<Dependencies>[],
|
|
386
|
-
existingForwardMap: Record<string, string>,
|
|
387
|
-
tree: RouteTree,
|
|
388
|
-
): void {
|
|
389
|
-
const batchNames = collectRouteNames(routes);
|
|
390
|
-
const batchForwards = collectForwardMappings(routes);
|
|
391
|
-
|
|
392
|
-
// Merge with existing forwardMap for cycle detection
|
|
393
|
-
const combinedForwardMap: Record<string, string> = { ...existingForwardMap };
|
|
394
|
-
|
|
395
|
-
for (const [from, to] of batchForwards) {
|
|
396
|
-
combinedForwardMap[from] = to;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Validate each forwardTo target exists and params are compatible
|
|
400
|
-
for (const [fromRoute, targetRoute] of batchForwards) {
|
|
401
|
-
validateSingleForward(fromRoute, targetRoute, routes, batchNames, tree);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Check for cycles in the combined forwardMap
|
|
405
|
-
for (const fromRoute of Object.keys(combinedForwardMap)) {
|
|
406
|
-
resolveForwardChain(fromRoute, combinedForwardMap);
|
|
407
|
-
}
|
|
408
|
-
}
|