@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.
Files changed (89) hide show
  1. package/README.md +27 -5
  2. package/dist/cjs/Router-B-Pev7K2.d.ts +46 -0
  3. package/dist/cjs/RouterValidator-mx2Zooya.d.ts +136 -0
  4. package/dist/cjs/api.d.ts +2 -1
  5. package/dist/cjs/api.js +1 -1
  6. package/dist/cjs/api.js.map +1 -1
  7. package/dist/cjs/index.d-y2b-8_3Y.d.ts +236 -0
  8. package/dist/cjs/index.d.ts +7 -24
  9. package/dist/cjs/index.js +1 -1
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/metafile-cjs.json +1 -1
  12. package/dist/cjs/utils.d.ts +6 -1
  13. package/dist/cjs/utils.js +1 -1
  14. package/dist/cjs/utils.js.map +1 -1
  15. package/dist/cjs/validation.d.ts +184 -0
  16. package/dist/cjs/validation.js +1 -0
  17. package/dist/cjs/validation.js.map +1 -0
  18. package/dist/esm/Router-B-Pev7K2.d.mts +46 -0
  19. package/dist/esm/RouterValidator-mx2Zooya.d.mts +136 -0
  20. package/dist/esm/api.d.mts +2 -1
  21. package/dist/esm/api.mjs +1 -1
  22. package/dist/esm/api.mjs.map +1 -1
  23. package/dist/esm/chunk-5QXFUUDL.mjs +1 -0
  24. package/dist/esm/chunk-5QXFUUDL.mjs.map +1 -0
  25. package/dist/esm/chunk-HHIXK5UM.mjs +1 -0
  26. package/dist/esm/chunk-HHIXK5UM.mjs.map +1 -0
  27. package/dist/esm/chunk-QUUNDESP.mjs +1 -0
  28. package/dist/esm/chunk-QUUNDESP.mjs.map +1 -0
  29. package/dist/esm/chunk-RA5VYM7M.mjs +1 -0
  30. package/dist/esm/chunk-RA5VYM7M.mjs.map +1 -0
  31. package/dist/esm/index.d-y2b-8_3Y.d.mts +236 -0
  32. package/dist/esm/index.d.mts +7 -24
  33. package/dist/esm/index.mjs +1 -1
  34. package/dist/esm/metafile-esm.json +1 -1
  35. package/dist/esm/utils.d.mts +6 -1
  36. package/dist/esm/utils.mjs +1 -1
  37. package/dist/esm/utils.mjs.map +1 -1
  38. package/dist/esm/validation.d.mts +184 -0
  39. package/dist/esm/validation.mjs +1 -0
  40. package/dist/esm/validation.mjs.map +1 -0
  41. package/package.json +18 -5
  42. package/src/Router.ts +73 -99
  43. package/src/api/cloneRouter.ts +1 -30
  44. package/src/api/getDependenciesApi.ts +45 -86
  45. package/src/api/getLifecycleApi.ts +24 -19
  46. package/src/api/getPluginApi.ts +20 -28
  47. package/src/api/getRoutesApi.ts +49 -106
  48. package/src/constants.ts +0 -30
  49. package/src/guards.ts +46 -0
  50. package/src/helpers.ts +0 -17
  51. package/src/index.ts +4 -0
  52. package/src/internals.ts +6 -5
  53. package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +2 -2
  54. package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +0 -25
  55. package/src/namespaces/OptionsNamespace/OptionsNamespace.ts +4 -26
  56. package/src/namespaces/OptionsNamespace/constants.ts +0 -20
  57. package/src/namespaces/OptionsNamespace/index.ts +1 -5
  58. package/src/namespaces/OptionsNamespace/validators.ts +6 -245
  59. package/src/namespaces/PluginsNamespace/PluginsNamespace.ts +18 -59
  60. package/src/namespaces/PluginsNamespace/constants.ts +3 -6
  61. package/src/namespaces/PluginsNamespace/validators.ts +2 -57
  62. package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +27 -84
  63. package/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts +0 -16
  64. package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +3 -12
  65. package/src/namespaces/RoutesNamespace/constants.ts +0 -8
  66. package/src/namespaces/RoutesNamespace/forwardChain.ts +34 -0
  67. package/src/namespaces/RoutesNamespace/index.ts +1 -1
  68. package/src/namespaces/RoutesNamespace/routeGuards.ts +62 -0
  69. package/src/namespaces/RoutesNamespace/routesStore.ts +7 -51
  70. package/src/namespaces/StateNamespace/StateNamespace.ts +0 -33
  71. package/src/namespaces/StateNamespace/helpers.ts +1 -1
  72. package/src/namespaces/index.ts +0 -3
  73. package/src/typeGuards.ts +1 -15
  74. package/src/types/RouterValidator.ts +155 -0
  75. package/src/utils/getStaticPaths.ts +50 -0
  76. package/src/utils/index.ts +4 -0
  77. package/src/validation.ts +12 -0
  78. package/src/wiring/RouterWiringBuilder.ts +32 -9
  79. package/dist/cjs/index.d-DDimDpYc.d.ts +0 -165
  80. package/dist/esm/chunk-CG7TKDP3.mjs +0 -1
  81. package/dist/esm/chunk-CG7TKDP3.mjs.map +0 -1
  82. package/dist/esm/index.d-DDimDpYc.d.mts +0 -165
  83. package/src/namespaces/DependenciesNamespace/validators.ts +0 -103
  84. package/src/namespaces/EventBusNamespace/validators.ts +0 -36
  85. package/src/namespaces/NavigationNamespace/validators.ts +0 -47
  86. package/src/namespaces/RouteLifecycleNamespace/validators.ts +0 -65
  87. package/src/namespaces/RoutesNamespace/forwardToValidation.ts +0 -408
  88. package/src/namespaces/RoutesNamespace/validators.ts +0 -566
  89. 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
- }