@real-router/core 0.25.3 → 0.26.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 +163 -325
- package/dist/cjs/index.d.ts +47 -178
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/metafile-cjs.json +1 -1
- package/dist/esm/index.d.mts +47 -178
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/metafile-esm.json +1 -1
- package/package.json +3 -3
- package/src/Router.ts +84 -574
- package/src/api/cloneRouter.ts +106 -0
- package/src/api/getDependenciesApi.ts +216 -0
- package/src/api/getLifecycleApi.ts +67 -0
- package/src/api/getPluginApi.ts +118 -0
- package/src/api/getRoutesApi.ts +566 -0
- package/src/api/index.ts +16 -0
- package/src/api/types.ts +7 -0
- package/src/getNavigator.ts +5 -2
- package/src/index.ts +17 -3
- package/src/internals.ts +115 -0
- package/src/namespaces/DependenciesNamespace/dependenciesStore.ts +30 -0
- package/src/namespaces/DependenciesNamespace/index.ts +3 -1
- package/src/namespaces/DependenciesNamespace/validators.ts +2 -4
- package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +1 -20
- package/src/namespaces/EventBusNamespace/validators.ts +36 -0
- package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +1 -10
- package/src/namespaces/NavigationNamespace/transition/errorHandling.ts +2 -0
- package/src/namespaces/NavigationNamespace/transition/{executeLifecycleHooks.ts → executeLifecycleGuards.ts} +9 -7
- package/src/namespaces/NavigationNamespace/transition/index.ts +3 -3
- package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +1 -16
- package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +133 -1089
- package/src/namespaces/RoutesNamespace/forwardToValidation.ts +411 -0
- package/src/namespaces/RoutesNamespace/helpers.ts +1 -407
- package/src/namespaces/RoutesNamespace/index.ts +2 -0
- package/src/namespaces/RoutesNamespace/routesStore.ts +388 -0
- package/src/namespaces/RoutesNamespace/validators.ts +209 -3
- package/src/namespaces/StateNamespace/StateNamespace.ts +1 -44
- package/src/namespaces/StateNamespace/validators.ts +46 -0
- package/src/namespaces/index.ts +3 -5
- package/src/types.ts +12 -138
- package/src/wiring/RouterWiringBuilder.ts +30 -36
- package/src/wiring/types.ts +3 -6
- package/src/wiring/wireRouter.ts +0 -1
- package/src/namespaces/CloneNamespace/CloneNamespace.ts +0 -120
- package/src/namespaces/CloneNamespace/index.ts +0 -3
- package/src/namespaces/CloneNamespace/types.ts +0 -42
- package/src/namespaces/DependenciesNamespace/DependenciesNamespace.ts +0 -248
- package/src/namespaces/RoutesNamespace/stateBuilder.ts +0 -70
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import { logger } from "@real-router/logger";
|
|
2
|
+
import { validateRouteName } from "type-guards";
|
|
3
|
+
|
|
4
|
+
import { errorCodes } from "../constants";
|
|
5
|
+
import { getInternals } from "../internals";
|
|
6
|
+
import {
|
|
7
|
+
clearConfigEntries,
|
|
8
|
+
removeFromDefinitions,
|
|
9
|
+
sanitizeRoute,
|
|
10
|
+
} from "../namespaces/RoutesNamespace/helpers";
|
|
11
|
+
import {
|
|
12
|
+
refreshForwardMap,
|
|
13
|
+
registerAllRouteHandlers,
|
|
14
|
+
} from "../namespaces/RoutesNamespace/routesStore";
|
|
15
|
+
import {
|
|
16
|
+
validateAddRouteArgs,
|
|
17
|
+
validateClearRoutes,
|
|
18
|
+
validateParentOption,
|
|
19
|
+
validateRemoveRoute,
|
|
20
|
+
validateRemoveRouteArgs,
|
|
21
|
+
validateUpdateRoute,
|
|
22
|
+
validateUpdateRouteBasicArgs,
|
|
23
|
+
validateUpdateRoutePropertyTypes,
|
|
24
|
+
} from "../namespaces/RoutesNamespace/validators";
|
|
25
|
+
import { RouterError } from "../RouterError";
|
|
26
|
+
|
|
27
|
+
import type { RoutesApi } from "./types";
|
|
28
|
+
import type { RouteLifecycleNamespace } from "../namespaces/RouteLifecycleNamespace";
|
|
29
|
+
import type { RoutesStore } from "../namespaces/RoutesNamespace/routesStore";
|
|
30
|
+
import type { RouteConfig } from "../namespaces/RoutesNamespace/types";
|
|
31
|
+
import type { GuardFnFactory, Route } from "../types";
|
|
32
|
+
import type {
|
|
33
|
+
DefaultDependencies,
|
|
34
|
+
ForwardToCallback,
|
|
35
|
+
Params,
|
|
36
|
+
Router,
|
|
37
|
+
} from "@real-router/types";
|
|
38
|
+
import type { RouteDefinition, RouteTree } from "route-tree";
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Helpers
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Recursively finds a route definition by its full dotted name.
|
|
46
|
+
*/
|
|
47
|
+
function findDefinition(
|
|
48
|
+
definitions: RouteDefinition[],
|
|
49
|
+
fullName: string,
|
|
50
|
+
parentPrefix = "",
|
|
51
|
+
): RouteDefinition | undefined {
|
|
52
|
+
for (const def of definitions) {
|
|
53
|
+
const currentFullName = parentPrefix
|
|
54
|
+
? `${parentPrefix}.${def.name}`
|
|
55
|
+
: def.name;
|
|
56
|
+
|
|
57
|
+
if (currentFullName === fullName) {
|
|
58
|
+
return def;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (def.children && fullName.startsWith(`${currentFullName}.`)) {
|
|
62
|
+
return findDefinition(def.children, fullName, currentFullName);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* v8 ignore next -- @preserve: defensive return, callers validate route exists before calling */
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Clears all config entries and lifecycle handlers for a removed route
|
|
72
|
+
* (and all its descendants).
|
|
73
|
+
*/
|
|
74
|
+
function clearRouteConfigurations<
|
|
75
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
76
|
+
>(
|
|
77
|
+
routeName: string,
|
|
78
|
+
config: RouteConfig,
|
|
79
|
+
routeCustomFields: Record<string, Record<string, unknown>>,
|
|
80
|
+
lifecycleNamespace: RouteLifecycleNamespace<Dependencies>,
|
|
81
|
+
): void {
|
|
82
|
+
const shouldClear = (n: string): boolean =>
|
|
83
|
+
n === routeName || n.startsWith(`${routeName}.`);
|
|
84
|
+
|
|
85
|
+
clearConfigEntries(config.decoders, shouldClear);
|
|
86
|
+
clearConfigEntries(config.encoders, shouldClear);
|
|
87
|
+
clearConfigEntries(config.defaultParams, shouldClear);
|
|
88
|
+
clearConfigEntries(config.forwardMap, shouldClear);
|
|
89
|
+
clearConfigEntries(config.forwardFnMap, shouldClear);
|
|
90
|
+
clearConfigEntries(routeCustomFields, shouldClear);
|
|
91
|
+
|
|
92
|
+
// Clear forwardMap entries pointing TO the deleted route (or its descendants)
|
|
93
|
+
clearConfigEntries(config.forwardMap, (key) =>
|
|
94
|
+
shouldClear(config.forwardMap[key]),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Clear lifecycle handlers
|
|
98
|
+
const [canDeactivateFactories, canActivateFactories] =
|
|
99
|
+
lifecycleNamespace.getFactories();
|
|
100
|
+
|
|
101
|
+
for (const n of Object.keys(canActivateFactories)) {
|
|
102
|
+
if (shouldClear(n)) {
|
|
103
|
+
lifecycleNamespace.clearCanActivate(n);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const n of Object.keys(canDeactivateFactories)) {
|
|
108
|
+
if (shouldClear(n)) {
|
|
109
|
+
lifecycleNamespace.clearCanDeactivate(n);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Updates forwardTo for a route in config and returns the refreshed resolved
|
|
116
|
+
* forward map (REPLACE semantics — caller must call ctx.setResolvedForwardMap).
|
|
117
|
+
*/
|
|
118
|
+
function updateForwardTo<
|
|
119
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
120
|
+
>(
|
|
121
|
+
name: string,
|
|
122
|
+
forwardTo: string | ForwardToCallback<Dependencies> | null,
|
|
123
|
+
config: RouteConfig,
|
|
124
|
+
noValidate: boolean,
|
|
125
|
+
refreshForwardMapFn: (
|
|
126
|
+
config: RouteConfig,
|
|
127
|
+
noValidate: boolean,
|
|
128
|
+
) => Record<string, string>,
|
|
129
|
+
): Record<string, string> {
|
|
130
|
+
if (forwardTo === null) {
|
|
131
|
+
delete config.forwardMap[name];
|
|
132
|
+
delete config.forwardFnMap[name];
|
|
133
|
+
} else if (typeof forwardTo === "string") {
|
|
134
|
+
delete config.forwardFnMap[name];
|
|
135
|
+
config.forwardMap[name] = forwardTo;
|
|
136
|
+
} else {
|
|
137
|
+
delete config.forwardMap[name];
|
|
138
|
+
config.forwardFnMap[name] = forwardTo;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return refreshForwardMapFn(config, noValidate);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Builds a full Route object from a bare RouteDefinition by re-attaching
|
|
146
|
+
* config entries and lifecycle factories.
|
|
147
|
+
*
|
|
148
|
+
* RECURSIVE — call with the factories tuple obtained ONCE from
|
|
149
|
+
* `lifecycleNamespace.getFactories()` and pass it through to children.
|
|
150
|
+
*/
|
|
151
|
+
function enrichRoute<
|
|
152
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
153
|
+
>(
|
|
154
|
+
routeDef: RouteDefinition,
|
|
155
|
+
routeName: string,
|
|
156
|
+
config: RouteConfig,
|
|
157
|
+
factories: [
|
|
158
|
+
Record<string, GuardFnFactory<Dependencies>>,
|
|
159
|
+
Record<string, GuardFnFactory<Dependencies>>,
|
|
160
|
+
],
|
|
161
|
+
): Route<Dependencies> {
|
|
162
|
+
const route: Route<Dependencies> = {
|
|
163
|
+
name: routeDef.name,
|
|
164
|
+
path: routeDef.path,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const forwardToFn = config.forwardFnMap[routeName];
|
|
168
|
+
const forwardToStr = config.forwardMap[routeName];
|
|
169
|
+
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
171
|
+
if (forwardToFn !== undefined) {
|
|
172
|
+
route.forwardTo = forwardToFn;
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
174
|
+
} else if (forwardToStr !== undefined) {
|
|
175
|
+
route.forwardTo = forwardToStr;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (routeName in config.defaultParams) {
|
|
179
|
+
route.defaultParams = config.defaultParams[routeName];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (routeName in config.decoders) {
|
|
183
|
+
route.decodeParams = config.decoders[routeName];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (routeName in config.encoders) {
|
|
187
|
+
route.encodeParams = config.encoders[routeName];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const [canDeactivateFactories, canActivateFactories] = factories;
|
|
191
|
+
|
|
192
|
+
if (routeName in canActivateFactories) {
|
|
193
|
+
route.canActivate = canActivateFactories[routeName];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (routeName in canDeactivateFactories) {
|
|
197
|
+
route.canDeactivate = canDeactivateFactories[routeName];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (routeDef.children) {
|
|
201
|
+
route.children = routeDef.children.map((child) =>
|
|
202
|
+
enrichRoute(child, `${routeName}.${child.name}`, config, factories),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return route;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ============================================================================
|
|
210
|
+
// CRUD operations
|
|
211
|
+
// ============================================================================
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Adds one or more routes to the router.
|
|
215
|
+
* Input already validated by facade.
|
|
216
|
+
*/
|
|
217
|
+
function addRoutes<
|
|
218
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
219
|
+
>(
|
|
220
|
+
store: RoutesStore<Dependencies>,
|
|
221
|
+
noValidate: boolean,
|
|
222
|
+
routes: Route<Dependencies>[],
|
|
223
|
+
parentName?: string,
|
|
224
|
+
): void {
|
|
225
|
+
if (parentName) {
|
|
226
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
227
|
+
const parentDef = findDefinition(store.definitions, parentName)!;
|
|
228
|
+
|
|
229
|
+
parentDef.children ??= [];
|
|
230
|
+
|
|
231
|
+
for (const route of routes) {
|
|
232
|
+
parentDef.children.push(sanitizeRoute(route));
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
for (const route of routes) {
|
|
236
|
+
store.definitions.push(sanitizeRoute(route));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
registerAllRouteHandlers(
|
|
241
|
+
routes,
|
|
242
|
+
store.config,
|
|
243
|
+
store.routeCustomFields,
|
|
244
|
+
store.pendingCanActivate,
|
|
245
|
+
store.pendingCanDeactivate,
|
|
246
|
+
store.depsStore,
|
|
247
|
+
parentName ?? "",
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
store.treeOperations.commitTreeChanges(store, noValidate);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Removes a route and all its children.
|
|
255
|
+
*
|
|
256
|
+
* @returns true if removed, false if not found
|
|
257
|
+
*/
|
|
258
|
+
function removeRoute<
|
|
259
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
260
|
+
>(
|
|
261
|
+
store: RoutesStore<Dependencies>,
|
|
262
|
+
noValidate: boolean,
|
|
263
|
+
name: string,
|
|
264
|
+
): boolean {
|
|
265
|
+
const wasRemoved = removeFromDefinitions(store.definitions, name);
|
|
266
|
+
|
|
267
|
+
if (!wasRemoved) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
clearRouteConfigurations(
|
|
272
|
+
name,
|
|
273
|
+
store.config,
|
|
274
|
+
store.routeCustomFields,
|
|
275
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
276
|
+
store.lifecycleNamespace!,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
store.treeOperations.commitTreeChanges(store, noValidate);
|
|
280
|
+
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Updates a route's configuration in place.
|
|
286
|
+
*/
|
|
287
|
+
function updateRouteConfig<
|
|
288
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
289
|
+
>(
|
|
290
|
+
store: RoutesStore<Dependencies>,
|
|
291
|
+
noValidate: boolean,
|
|
292
|
+
name: string,
|
|
293
|
+
updates: {
|
|
294
|
+
forwardTo?: string | ForwardToCallback<Dependencies> | null | undefined;
|
|
295
|
+
defaultParams?: Params | null | undefined;
|
|
296
|
+
decodeParams?: ((params: Params) => Params) | null | undefined;
|
|
297
|
+
encodeParams?: ((params: Params) => Params) | null | undefined;
|
|
298
|
+
},
|
|
299
|
+
): void {
|
|
300
|
+
if (updates.forwardTo !== undefined) {
|
|
301
|
+
store.resolvedForwardMap = updateForwardTo(
|
|
302
|
+
name,
|
|
303
|
+
updates.forwardTo,
|
|
304
|
+
store.config,
|
|
305
|
+
noValidate,
|
|
306
|
+
refreshForwardMap,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (updates.defaultParams !== undefined) {
|
|
311
|
+
if (updates.defaultParams === null) {
|
|
312
|
+
delete store.config.defaultParams[name];
|
|
313
|
+
} else {
|
|
314
|
+
store.config.defaultParams[name] = updates.defaultParams;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (updates.decodeParams !== undefined) {
|
|
319
|
+
if (updates.decodeParams === null) {
|
|
320
|
+
delete store.config.decoders[name];
|
|
321
|
+
} else {
|
|
322
|
+
const decoder = updates.decodeParams;
|
|
323
|
+
|
|
324
|
+
store.config.decoders[name] = (params: Params): Params =>
|
|
325
|
+
(decoder(params) as Params | undefined) ?? params;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (updates.encodeParams !== undefined) {
|
|
330
|
+
if (updates.encodeParams === null) {
|
|
331
|
+
delete store.config.encoders[name];
|
|
332
|
+
} else {
|
|
333
|
+
const encoder = updates.encodeParams;
|
|
334
|
+
|
|
335
|
+
store.config.encoders[name] = (params: Params): Params =>
|
|
336
|
+
(encoder(params) as Params | undefined) ?? params;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Gets a route by name with all its configuration.
|
|
343
|
+
*/
|
|
344
|
+
function getRoute<
|
|
345
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
346
|
+
>(
|
|
347
|
+
store: RoutesStore<Dependencies>,
|
|
348
|
+
name: string,
|
|
349
|
+
): Route<Dependencies> | undefined {
|
|
350
|
+
const segments = store.matcher.getSegmentsByName(name);
|
|
351
|
+
|
|
352
|
+
if (!segments) {
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- segments is non-empty (checked above)
|
|
357
|
+
const targetNode = segments.at(-1)! as RouteTree;
|
|
358
|
+
const definition = store.treeOperations.nodeToDefinition(targetNode);
|
|
359
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
360
|
+
const factories = store.lifecycleNamespace!.getFactories();
|
|
361
|
+
|
|
362
|
+
return enrichRoute(definition, name, store.config, factories);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Gets the custom config fields for a route.
|
|
367
|
+
*/
|
|
368
|
+
function getRouteConfig<
|
|
369
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
370
|
+
>(
|
|
371
|
+
store: RoutesStore<Dependencies>,
|
|
372
|
+
name: string,
|
|
373
|
+
): Record<string, unknown> | undefined {
|
|
374
|
+
if (!store.matcher.hasRoute(name)) {
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return store.routeCustomFields[name];
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ============================================================================
|
|
382
|
+
// API factory
|
|
383
|
+
// ============================================================================
|
|
384
|
+
|
|
385
|
+
function throwIfDisposed(isDisposed: () => boolean): void {
|
|
386
|
+
if (isDisposed()) {
|
|
387
|
+
throw new RouterError(errorCodes.ROUTER_DISPOSED);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function getRoutesApi<
|
|
392
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
393
|
+
>(router: Router<Dependencies>): RoutesApi<Dependencies> {
|
|
394
|
+
const ctx = getInternals(router);
|
|
395
|
+
|
|
396
|
+
const store = ctx.routeGetStore();
|
|
397
|
+
const noValidate = ctx.noValidate;
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
add: (routes, options) => {
|
|
401
|
+
throwIfDisposed(ctx.isDisposed);
|
|
402
|
+
|
|
403
|
+
const routeArray = Array.isArray(routes) ? routes : [routes];
|
|
404
|
+
const parentName = options?.parent;
|
|
405
|
+
|
|
406
|
+
if (!ctx.noValidate) {
|
|
407
|
+
if (parentName !== undefined) {
|
|
408
|
+
validateParentOption(parentName);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
validateAddRouteArgs(routeArray);
|
|
412
|
+
|
|
413
|
+
store.treeOperations.validateRoutes(
|
|
414
|
+
routeArray,
|
|
415
|
+
store.tree,
|
|
416
|
+
store.config.forwardMap,
|
|
417
|
+
parentName,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
addRoutes(store, noValidate, routeArray, parentName);
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
remove: (name) => {
|
|
425
|
+
throwIfDisposed(ctx.isDisposed);
|
|
426
|
+
|
|
427
|
+
if (!ctx.noValidate) {
|
|
428
|
+
validateRemoveRouteArgs(name);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const canRemove = validateRemoveRoute(
|
|
432
|
+
name,
|
|
433
|
+
ctx.getStateName(),
|
|
434
|
+
ctx.isTransitioning(),
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
if (!canRemove) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const wasRemoved = removeRoute(store, noValidate, name);
|
|
442
|
+
|
|
443
|
+
if (!wasRemoved) {
|
|
444
|
+
logger.warn(
|
|
445
|
+
"router.removeRoute",
|
|
446
|
+
`Route "${name}" not found. No changes made.`,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
update: (name, updates) => {
|
|
452
|
+
throwIfDisposed(ctx.isDisposed);
|
|
453
|
+
|
|
454
|
+
if (!ctx.noValidate) {
|
|
455
|
+
validateUpdateRouteBasicArgs(name, updates);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const {
|
|
459
|
+
forwardTo,
|
|
460
|
+
defaultParams,
|
|
461
|
+
decodeParams,
|
|
462
|
+
encodeParams,
|
|
463
|
+
canActivate,
|
|
464
|
+
canDeactivate,
|
|
465
|
+
} = updates;
|
|
466
|
+
|
|
467
|
+
if (!ctx.noValidate) {
|
|
468
|
+
validateUpdateRoutePropertyTypes(
|
|
469
|
+
forwardTo,
|
|
470
|
+
defaultParams,
|
|
471
|
+
decodeParams,
|
|
472
|
+
encodeParams,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/* v8 ignore next 6 -- @preserve: race condition guard, mirrors Router.updateRoute() same-path guard tested via Router.ts unit tests */
|
|
477
|
+
if (ctx.isTransitioning()) {
|
|
478
|
+
logger.error(
|
|
479
|
+
"router.updateRoute",
|
|
480
|
+
`Updating route "${name}" while navigation is in progress. This may cause unexpected behavior.`,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (!ctx.noValidate) {
|
|
485
|
+
validateUpdateRoute(
|
|
486
|
+
name,
|
|
487
|
+
forwardTo,
|
|
488
|
+
(n) => store.matcher.hasRoute(n),
|
|
489
|
+
store.matcher,
|
|
490
|
+
store.config,
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
updateRouteConfig(store, noValidate, name, {
|
|
495
|
+
forwardTo,
|
|
496
|
+
defaultParams,
|
|
497
|
+
decodeParams,
|
|
498
|
+
encodeParams,
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
if (canActivate !== undefined) {
|
|
502
|
+
if (canActivate === null) {
|
|
503
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
504
|
+
store.lifecycleNamespace!.clearCanActivate(name);
|
|
505
|
+
} else {
|
|
506
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
507
|
+
store.lifecycleNamespace!.addCanActivate(
|
|
508
|
+
name,
|
|
509
|
+
canActivate,
|
|
510
|
+
noValidate,
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (canDeactivate !== undefined) {
|
|
516
|
+
if (canDeactivate === null) {
|
|
517
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
518
|
+
store.lifecycleNamespace!.clearCanDeactivate(name);
|
|
519
|
+
} else {
|
|
520
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
521
|
+
store.lifecycleNamespace!.addCanDeactivate(
|
|
522
|
+
name,
|
|
523
|
+
canDeactivate,
|
|
524
|
+
noValidate,
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
|
|
530
|
+
clear: () => {
|
|
531
|
+
throwIfDisposed(ctx.isDisposed);
|
|
532
|
+
|
|
533
|
+
const canClear = validateClearRoutes(ctx.isTransitioning());
|
|
534
|
+
|
|
535
|
+
/* v8 ignore next 3 -- @preserve: race condition guard, mirrors Router.clearRoutes() same-path guard tested via validateClearRoutes unit tests */
|
|
536
|
+
if (!canClear) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
store.treeOperations.resetStore(store);
|
|
541
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
542
|
+
store.lifecycleNamespace!.clearAll();
|
|
543
|
+
ctx.clearState();
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
has: (name) => {
|
|
547
|
+
if (!ctx.noValidate) {
|
|
548
|
+
validateRouteName(name, "hasRoute");
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return store.matcher.hasRoute(name);
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
get: (name) => {
|
|
555
|
+
if (!ctx.noValidate) {
|
|
556
|
+
validateRouteName(name, "getRoute");
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return getRoute(store, name);
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
getConfig: (name) => {
|
|
563
|
+
return getRouteConfig(store, name);
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
}
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { getPluginApi } from "./getPluginApi";
|
|
2
|
+
|
|
3
|
+
export { getRoutesApi } from "./getRoutesApi";
|
|
4
|
+
|
|
5
|
+
export { getDependenciesApi } from "./getDependenciesApi";
|
|
6
|
+
|
|
7
|
+
export { getLifecycleApi } from "./getLifecycleApi";
|
|
8
|
+
|
|
9
|
+
export { cloneRouter } from "./cloneRouter";
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
PluginApi,
|
|
13
|
+
RoutesApi,
|
|
14
|
+
DependenciesApi,
|
|
15
|
+
LifecycleApi,
|
|
16
|
+
} from "./types";
|
package/src/api/types.ts
ADDED
package/src/getNavigator.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
Navigator,
|
|
3
|
+
DefaultDependencies,
|
|
4
|
+
Router,
|
|
5
|
+
} from "@real-router/types";
|
|
3
6
|
|
|
4
7
|
export const getNavigator = <
|
|
5
8
|
Dependencies extends DefaultDependencies = DefaultDependencies,
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// packages/core/src/index.ts
|
|
2
2
|
|
|
3
|
-
// Router-dependent types (
|
|
3
|
+
// Router-dependent types (re-exported from @real-router/types)
|
|
4
4
|
export type {
|
|
5
|
-
ActivationFnFactory,
|
|
6
5
|
BuildStateResultWithSegments,
|
|
7
6
|
GuardFnFactory,
|
|
8
7
|
PluginFactory,
|
|
@@ -15,7 +14,6 @@ export { Router } from "./Router";
|
|
|
15
14
|
|
|
16
15
|
// Types (re-exported from core-types - no Router dependency)
|
|
17
16
|
export type {
|
|
18
|
-
ActivationFn,
|
|
19
17
|
Config,
|
|
20
18
|
DefaultDependencies,
|
|
21
19
|
GuardFn,
|
|
@@ -44,3 +42,19 @@ export { RouterError } from "./RouterError";
|
|
|
44
42
|
export { createRouter } from "./createRouter";
|
|
45
43
|
|
|
46
44
|
export { getNavigator } from "./getNavigator";
|
|
45
|
+
|
|
46
|
+
// Factory API (Phase 0: parallel API for modular architecture)
|
|
47
|
+
export {
|
|
48
|
+
getPluginApi,
|
|
49
|
+
getRoutesApi,
|
|
50
|
+
getDependenciesApi,
|
|
51
|
+
getLifecycleApi,
|
|
52
|
+
cloneRouter,
|
|
53
|
+
} from "./api";
|
|
54
|
+
|
|
55
|
+
export type {
|
|
56
|
+
PluginApi,
|
|
57
|
+
RoutesApi,
|
|
58
|
+
DependenciesApi,
|
|
59
|
+
LifecycleApi,
|
|
60
|
+
} from "./api";
|