@real-router/core 0.56.0 → 0.57.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/dist/cjs/Router-BSGzVINO.js +6 -0
- package/dist/cjs/Router-BSGzVINO.js.map +1 -0
- package/dist/cjs/api.d.ts +1 -1
- package/dist/cjs/api.js +1 -1
- package/dist/cjs/{cloneRouter-DRieJvam.js → cloneRouter-7z-60z_f.js} +2 -2
- package/dist/cjs/{cloneRouter-DRieJvam.js.map → cloneRouter-7z-60z_f.js.map} +1 -1
- package/dist/cjs/{index-C-i6vx5Y.d.ts → index-BWUmnecT.d.ts} +1 -2
- package/dist/cjs/index-BWUmnecT.d.ts.map +1 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/utils.js +1 -1
- package/dist/cjs/validation.d.ts +1 -1
- package/dist/esm/Router-B7txWo9N.mjs +6 -0
- package/dist/esm/Router-B7txWo9N.mjs.map +1 -0
- package/dist/esm/api.d.mts +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/{cloneRouter-DHrH6D_z.mjs → cloneRouter-BNCQ7tIa.mjs} +2 -2
- package/dist/esm/{cloneRouter-DHrH6D_z.mjs.map → cloneRouter-BNCQ7tIa.mjs.map} +1 -1
- package/dist/esm/{index-C-i6vx5Y.d.mts → index-BWUmnecT.d.mts} +1 -2
- package/dist/esm/index-BWUmnecT.d.mts.map +1 -0
- package/dist/esm/index.d.mts +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/utils.mjs +1 -1
- package/dist/esm/validation.d.mts +1 -1
- package/package.json +2 -3
- package/dist/cjs/Router-IEGavTKk.js +0 -6
- package/dist/cjs/Router-IEGavTKk.js.map +0 -1
- package/dist/cjs/index-C-i6vx5Y.d.ts.map +0 -1
- package/dist/esm/Router-B3aeavRb.mjs +0 -6
- package/dist/esm/Router-B3aeavRb.mjs.map +0 -1
- package/dist/esm/index-C-i6vx5Y.d.mts.map +0 -1
- package/src/Router.ts +0 -737
- package/src/RouterError.ts +0 -324
- package/src/api/cloneRouter.ts +0 -159
- package/src/api/getDependenciesApi.ts +0 -160
- package/src/api/getLifecycleApi.ts +0 -65
- package/src/api/getPluginApi.ts +0 -228
- package/src/api/getRoutesApi.ts +0 -831
- package/src/api/helpers.ts +0 -10
- package/src/api/index.ts +0 -16
- package/src/api/types.ts +0 -12
- package/src/constants.ts +0 -101
- package/src/createRouter.ts +0 -32
- package/src/fsm/index.ts +0 -5
- package/src/fsm/routerFSM.ts +0 -130
- package/src/getNavigator.ts +0 -30
- package/src/guards.ts +0 -46
- package/src/helpers.ts +0 -197
- package/src/index.ts +0 -66
- package/src/internals.ts +0 -228
- package/src/namespaces/DependenciesNamespace/dependenciesStore.ts +0 -30
- package/src/namespaces/DependenciesNamespace/index.ts +0 -5
- package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +0 -522
- package/src/namespaces/EventBusNamespace/index.ts +0 -5
- package/src/namespaces/EventBusNamespace/types.ts +0 -11
- package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +0 -552
- package/src/namespaces/NavigationNamespace/constants.ts +0 -55
- package/src/namespaces/NavigationNamespace/index.ts +0 -5
- package/src/namespaces/NavigationNamespace/transition/completeTransition.ts +0 -108
- package/src/namespaces/NavigationNamespace/transition/errorHandling.ts +0 -124
- package/src/namespaces/NavigationNamespace/transition/guardPhase.ts +0 -283
- package/src/namespaces/NavigationNamespace/types.ts +0 -110
- package/src/namespaces/OptionsNamespace/OptionsNamespace.ts +0 -28
- package/src/namespaces/OptionsNamespace/constants.ts +0 -19
- package/src/namespaces/OptionsNamespace/helpers.ts +0 -50
- package/src/namespaces/OptionsNamespace/index.ts +0 -7
- package/src/namespaces/OptionsNamespace/validators.ts +0 -13
- package/src/namespaces/PluginsNamespace/PluginsNamespace.ts +0 -291
- package/src/namespaces/PluginsNamespace/constants.ts +0 -34
- package/src/namespaces/PluginsNamespace/index.ts +0 -7
- package/src/namespaces/PluginsNamespace/types.ts +0 -22
- package/src/namespaces/PluginsNamespace/validators.ts +0 -28
- package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +0 -558
- package/src/namespaces/RouteLifecycleNamespace/index.ts +0 -5
- package/src/namespaces/RouteLifecycleNamespace/types.ts +0 -10
- package/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts +0 -81
- package/src/namespaces/RouterLifecycleNamespace/constants.ts +0 -25
- package/src/namespaces/RouterLifecycleNamespace/index.ts +0 -5
- package/src/namespaces/RouterLifecycleNamespace/types.ts +0 -30
- package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +0 -582
- package/src/namespaces/RoutesNamespace/constants.ts +0 -6
- package/src/namespaces/RoutesNamespace/forwardChain.ts +0 -34
- package/src/namespaces/RoutesNamespace/helpers.ts +0 -204
- package/src/namespaces/RoutesNamespace/index.ts +0 -11
- package/src/namespaces/RoutesNamespace/routeGuards.ts +0 -62
- package/src/namespaces/RoutesNamespace/routesStore.ts +0 -566
- package/src/namespaces/RoutesNamespace/types.ts +0 -81
- package/src/namespaces/StateNamespace/StateNamespace.ts +0 -224
- package/src/namespaces/StateNamespace/helpers.ts +0 -24
- package/src/namespaces/StateNamespace/index.ts +0 -5
- package/src/namespaces/StateNamespace/types.ts +0 -15
- package/src/namespaces/index.ts +0 -35
- package/src/stateMetaStore.ts +0 -15
- package/src/transitionPath.ts +0 -440
- package/src/typeGuards.ts +0 -59
- package/src/types/RouterValidator.ts +0 -156
- package/src/types.ts +0 -77
- package/src/utils/createRequestScope.ts +0 -174
- package/src/utils/getStaticPaths.ts +0 -50
- package/src/utils/hydrateRouter.ts +0 -89
- package/src/utils/index.ts +0 -27
- package/src/utils/serializeRouterState.ts +0 -120
- package/src/utils/serializeState.ts +0 -63
- package/src/validation.ts +0 -12
- package/src/wiring/RouterWiringBuilder.ts +0 -275
- package/src/wiring/index.ts +0 -7
- package/src/wiring/types.ts +0 -47
- package/src/wiring/wireRouter.ts +0 -26
package/src/api/getRoutesApi.ts
DELETED
|
@@ -1,831 +0,0 @@
|
|
|
1
|
-
import { logger } from "@real-router/logger";
|
|
2
|
-
|
|
3
|
-
import { throwIfDisposed } from "./helpers";
|
|
4
|
-
import { guardRouteStructure } from "../guards";
|
|
5
|
-
import { getInternals } from "../internals";
|
|
6
|
-
import {
|
|
7
|
-
clearConfigEntries,
|
|
8
|
-
removeFromDefinitions,
|
|
9
|
-
} from "../namespaces/RoutesNamespace/helpers";
|
|
10
|
-
import {
|
|
11
|
-
validateClearRoutes,
|
|
12
|
-
validateRemoveRoute,
|
|
13
|
-
} from "../namespaces/RoutesNamespace/routeGuards";
|
|
14
|
-
import {
|
|
15
|
-
adoptRouteArtifacts,
|
|
16
|
-
assertAddable,
|
|
17
|
-
buildAddArtifacts,
|
|
18
|
-
buildReplaceArtifacts,
|
|
19
|
-
refreshForwardMap,
|
|
20
|
-
} from "../namespaces/RoutesNamespace/routesStore";
|
|
21
|
-
|
|
22
|
-
import type { RoutesApi } from "./types";
|
|
23
|
-
import type { RouterInternals } from "../internals";
|
|
24
|
-
import type { RouteLifecycleNamespace, RouteConfig } from "../namespaces";
|
|
25
|
-
import type { RoutesStore } from "../namespaces/RoutesNamespace";
|
|
26
|
-
import type { GuardFnFactory, Route } from "../types";
|
|
27
|
-
import type {
|
|
28
|
-
DefaultDependencies,
|
|
29
|
-
ForwardToCallback,
|
|
30
|
-
Params,
|
|
31
|
-
Router,
|
|
32
|
-
TransitionMeta,
|
|
33
|
-
TreeChangedEvent,
|
|
34
|
-
TreeStructuralPatch,
|
|
35
|
-
} from "@real-router/types";
|
|
36
|
-
import type { RouteDefinition, RouteTree } from "route-tree";
|
|
37
|
-
|
|
38
|
-
// ============================================================================
|
|
39
|
-
// Helpers
|
|
40
|
-
// ============================================================================
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Clears all config entries and lifecycle handlers for a removed route
|
|
44
|
-
* (and all its descendants).
|
|
45
|
-
*/
|
|
46
|
-
function clearRouteConfigurations<
|
|
47
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
48
|
-
>(
|
|
49
|
-
routeName: string,
|
|
50
|
-
config: RouteConfig,
|
|
51
|
-
routeCustomFields: Record<string, Record<string, unknown>>,
|
|
52
|
-
lifecycleNamespace: RouteLifecycleNamespace<Dependencies>,
|
|
53
|
-
): void {
|
|
54
|
-
const shouldClear = (name: string): boolean =>
|
|
55
|
-
name === routeName || name.startsWith(`${routeName}.`);
|
|
56
|
-
|
|
57
|
-
clearConfigEntries(config.decoders, shouldClear);
|
|
58
|
-
clearConfigEntries(config.encoders, shouldClear);
|
|
59
|
-
clearConfigEntries(config.defaultParams, shouldClear);
|
|
60
|
-
clearConfigEntries(config.forwardMap, shouldClear);
|
|
61
|
-
clearConfigEntries(config.forwardFnMap, shouldClear);
|
|
62
|
-
clearConfigEntries(routeCustomFields, shouldClear);
|
|
63
|
-
|
|
64
|
-
// Clear forwardMap entries pointing TO the deleted route (or its descendants)
|
|
65
|
-
clearConfigEntries(config.forwardMap, (key) =>
|
|
66
|
-
shouldClear(config.forwardMap[key]),
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
// Clear lifecycle handlers
|
|
70
|
-
const [canDeactivateFactories, canActivateFactories] =
|
|
71
|
-
lifecycleNamespace.getFactories();
|
|
72
|
-
|
|
73
|
-
for (const name of Object.keys(canActivateFactories)) {
|
|
74
|
-
if (shouldClear(name)) {
|
|
75
|
-
lifecycleNamespace.clearCanActivate(name);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
for (const name of Object.keys(canDeactivateFactories)) {
|
|
80
|
-
if (shouldClear(name)) {
|
|
81
|
-
lifecycleNamespace.clearCanDeactivate(name);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Updates forwardTo for a route in config and returns the refreshed resolved
|
|
88
|
-
* forward map (REPLACE semantics — caller must call ctx.setResolvedForwardMap).
|
|
89
|
-
*/
|
|
90
|
-
function updateForwardTo<
|
|
91
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
92
|
-
>(
|
|
93
|
-
name: string,
|
|
94
|
-
forwardTo: string | ForwardToCallback<Dependencies> | null,
|
|
95
|
-
config: RouteConfig,
|
|
96
|
-
): Record<string, string> {
|
|
97
|
-
// Prepare-then-commit (issue #698): apply the change to CLONES of the forward
|
|
98
|
-
// maps, resolve the chain (a cycle throws here), and only then swap the clones
|
|
99
|
-
// in — so a rejected update never leaves config.forwardMap poisoned.
|
|
100
|
-
const forwardMap = Object.assign(
|
|
101
|
-
Object.create(null) as RouteConfig["forwardMap"],
|
|
102
|
-
config.forwardMap,
|
|
103
|
-
);
|
|
104
|
-
const forwardFnMap = Object.assign(
|
|
105
|
-
Object.create(null) as RouteConfig["forwardFnMap"],
|
|
106
|
-
config.forwardFnMap,
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
if (forwardTo === null) {
|
|
110
|
-
delete forwardMap[name];
|
|
111
|
-
delete forwardFnMap[name];
|
|
112
|
-
} else if (typeof forwardTo === "string") {
|
|
113
|
-
delete forwardFnMap[name];
|
|
114
|
-
forwardMap[name] = forwardTo;
|
|
115
|
-
} else {
|
|
116
|
-
delete forwardMap[name];
|
|
117
|
-
forwardFnMap[name] = forwardTo;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const resolved = refreshForwardMap({ ...config, forwardMap });
|
|
121
|
-
|
|
122
|
-
config.forwardMap = forwardMap;
|
|
123
|
-
config.forwardFnMap = forwardFnMap;
|
|
124
|
-
|
|
125
|
-
return resolved;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Re-attaches the stored config (forwardTo / defaultParams / encode-decode) and
|
|
130
|
-
* lifecycle guards for `lookupName` onto `route`, then returns it (mutates in
|
|
131
|
-
* place). Shared by {@link enrichRoute} (nested, bare `name`) and
|
|
132
|
-
* {@link buildFlatRoute} (flat, full dotted `name`) — one source of truth for
|
|
133
|
-
* the route-config field set.
|
|
134
|
-
*/
|
|
135
|
-
function assignRouteConfig<
|
|
136
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
137
|
-
>(
|
|
138
|
-
route: Route<Dependencies>,
|
|
139
|
-
lookupName: string,
|
|
140
|
-
config: RouteConfig,
|
|
141
|
-
factories: [
|
|
142
|
-
Record<string, GuardFnFactory<Dependencies>>,
|
|
143
|
-
Record<string, GuardFnFactory<Dependencies>>,
|
|
144
|
-
],
|
|
145
|
-
): Route<Dependencies> {
|
|
146
|
-
const forwardToFn = config.forwardFnMap[lookupName];
|
|
147
|
-
const forwardToStr = config.forwardMap[lookupName];
|
|
148
|
-
|
|
149
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
150
|
-
if (forwardToFn !== undefined) {
|
|
151
|
-
route.forwardTo = forwardToFn;
|
|
152
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
153
|
-
} else if (forwardToStr !== undefined) {
|
|
154
|
-
route.forwardTo = forwardToStr;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (lookupName in config.defaultParams) {
|
|
158
|
-
route.defaultParams = config.defaultParams[lookupName];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (lookupName in config.decoders) {
|
|
162
|
-
route.decodeParams = config.decoders[lookupName];
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (lookupName in config.encoders) {
|
|
166
|
-
route.encodeParams = config.encoders[lookupName];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const [canDeactivateFactories, canActivateFactories] = factories;
|
|
170
|
-
|
|
171
|
-
if (lookupName in canActivateFactories) {
|
|
172
|
-
route.canActivate = canActivateFactories[lookupName];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (lookupName in canDeactivateFactories) {
|
|
176
|
-
route.canDeactivate = canDeactivateFactories[lookupName];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return route;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Builds a full Route object from a bare RouteDefinition by re-attaching
|
|
184
|
-
* config entries and lifecycle factories.
|
|
185
|
-
*
|
|
186
|
-
* RECURSIVE — call with the factories tuple obtained ONCE from
|
|
187
|
-
* `lifecycleNamespace.getFactories()` and pass it through to children.
|
|
188
|
-
*/
|
|
189
|
-
function enrichRoute<
|
|
190
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
191
|
-
>(
|
|
192
|
-
routeDef: RouteDefinition,
|
|
193
|
-
routeName: string,
|
|
194
|
-
config: RouteConfig,
|
|
195
|
-
factories: [
|
|
196
|
-
Record<string, GuardFnFactory<Dependencies>>,
|
|
197
|
-
Record<string, GuardFnFactory<Dependencies>>,
|
|
198
|
-
],
|
|
199
|
-
): Route<Dependencies> {
|
|
200
|
-
const route: Route<Dependencies> = {
|
|
201
|
-
name: routeDef.name,
|
|
202
|
-
path: routeDef.path,
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
assignRouteConfig(route, routeName, config, factories);
|
|
206
|
-
|
|
207
|
-
if (routeDef.children) {
|
|
208
|
-
route.children = routeDef.children.map((child) =>
|
|
209
|
-
enrichRoute(child, `${routeName}.${child.name}`, config, factories),
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return route;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// ============================================================================
|
|
217
|
-
// TREE_CHANGED payload helpers
|
|
218
|
-
// ============================================================================
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Builds a single FLAT `Route` for `fullName` from the store config + lifecycle
|
|
222
|
-
* factories — `name` is the FULL dotted name and there is no `children` array
|
|
223
|
-
* (consumers want a flat, by-name list). Frozen on construction.
|
|
224
|
-
*/
|
|
225
|
-
function buildFlatRoute<
|
|
226
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
227
|
-
>(
|
|
228
|
-
fullName: string,
|
|
229
|
-
path: string,
|
|
230
|
-
config: RouteConfig,
|
|
231
|
-
factories: [
|
|
232
|
-
Record<string, GuardFnFactory<Dependencies>>,
|
|
233
|
-
Record<string, GuardFnFactory<Dependencies>>,
|
|
234
|
-
],
|
|
235
|
-
): Route<Dependencies> {
|
|
236
|
-
const route: Route<Dependencies> = { name: fullName, path };
|
|
237
|
-
|
|
238
|
-
assignRouteConfig(route, fullName, config, factories);
|
|
239
|
-
|
|
240
|
-
return Object.freeze(route);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Walks the store's definitions depth-first, building a FLAT
|
|
245
|
-
* `Map<fullName, Route>` for every node whose full dotted name satisfies
|
|
246
|
-
* `include`. Reads the live store, so call it at the right moment relative to
|
|
247
|
-
* the mutation (before for removed, after for added).
|
|
248
|
-
*/
|
|
249
|
-
function collectFlatRoutes<
|
|
250
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
251
|
-
>(
|
|
252
|
-
store: RoutesStore<Dependencies>,
|
|
253
|
-
include: (fullName: string) => boolean,
|
|
254
|
-
): Map<string, Route<Dependencies>> {
|
|
255
|
-
const result = new Map<string, Route<Dependencies>>();
|
|
256
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
257
|
-
const factories = store.lifecycleNamespace!.getFactories();
|
|
258
|
-
|
|
259
|
-
const walk = (defs: readonly RouteDefinition[], parentName: string): void => {
|
|
260
|
-
for (const def of defs) {
|
|
261
|
-
const fullName = parentName ? `${parentName}.${def.name}` : def.name;
|
|
262
|
-
|
|
263
|
-
if (include(fullName)) {
|
|
264
|
-
result.set(
|
|
265
|
-
fullName,
|
|
266
|
-
buildFlatRoute(fullName, def.path, store.config, factories),
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (def.children) {
|
|
271
|
-
walk(def.children, fullName);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
walk(store.definitions, "");
|
|
277
|
-
|
|
278
|
-
return result;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Collects the route `name` and all of its descendants as a FLAT, frozen array.
|
|
283
|
-
* MUST be called BEFORE the removal mutation — the nodes are gone afterwards.
|
|
284
|
-
*/
|
|
285
|
-
function collectSubtree<
|
|
286
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
287
|
-
>(
|
|
288
|
-
store: RoutesStore<Dependencies>,
|
|
289
|
-
name: string,
|
|
290
|
-
): readonly Route<Dependencies>[] {
|
|
291
|
-
const prefix = `${name}.`;
|
|
292
|
-
const subtree = collectFlatRoutes(
|
|
293
|
-
store,
|
|
294
|
-
(fullName) => fullName === name || fullName.startsWith(prefix),
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
return Object.freeze([...subtree.values()]);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Builds the FLAT, frozen payload array for an `add`, walking only the input
|
|
302
|
-
* routes — O(added), not O(tree). `path` is taken from the input verbatim
|
|
303
|
-
* (`sanitizeRoute` never rewrites it); config fields are read from the
|
|
304
|
-
* post-commit store by full name. `add` never removes, so the input subtree is
|
|
305
|
-
* exactly what changed.
|
|
306
|
-
*/
|
|
307
|
-
function collectAddedRoutes<
|
|
308
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
309
|
-
>(
|
|
310
|
-
routes: readonly Route<Dependencies>[],
|
|
311
|
-
parentName: string | undefined,
|
|
312
|
-
store: RoutesStore<Dependencies>,
|
|
313
|
-
): readonly Route<Dependencies>[] {
|
|
314
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
315
|
-
const factories = store.lifecycleNamespace!.getFactories();
|
|
316
|
-
const result: Route<Dependencies>[] = [];
|
|
317
|
-
|
|
318
|
-
const walk = (
|
|
319
|
-
input: readonly Route<Dependencies>[],
|
|
320
|
-
parent: string,
|
|
321
|
-
): void => {
|
|
322
|
-
for (const route of input) {
|
|
323
|
-
const fullName = parent ? `${parent}.${route.name}` : route.name;
|
|
324
|
-
|
|
325
|
-
result.push(
|
|
326
|
-
buildFlatRoute(fullName, route.path, store.config, factories),
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
if (route.children) {
|
|
330
|
-
walk(route.children, fullName);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
walk(routes, parentName ?? "");
|
|
336
|
-
|
|
337
|
-
return Object.freeze(result);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/** Diffs two flat route maps by full name into frozen removed/added arrays. */
|
|
341
|
-
function diffFlatRoutes<
|
|
342
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
343
|
-
>(
|
|
344
|
-
before: ReadonlyMap<string, Route<Dependencies>>,
|
|
345
|
-
after: ReadonlyMap<string, Route<Dependencies>>,
|
|
346
|
-
): {
|
|
347
|
-
removed: readonly Route<Dependencies>[];
|
|
348
|
-
added: readonly Route<Dependencies>[];
|
|
349
|
-
} {
|
|
350
|
-
const removed: Route<Dependencies>[] = [];
|
|
351
|
-
const added: Route<Dependencies>[] = [];
|
|
352
|
-
|
|
353
|
-
for (const [fullName, route] of before) {
|
|
354
|
-
if (!after.has(fullName)) {
|
|
355
|
-
removed.push(route);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
for (const [fullName, route] of after) {
|
|
360
|
-
if (!before.has(fullName)) {
|
|
361
|
-
added.push(route);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return { removed: Object.freeze(removed), added: Object.freeze(added) };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Builds the structural subset of an `update()` patch (forwardTo /
|
|
370
|
-
* defaultParams / encodeParams / decodeParams) from the already-destructured
|
|
371
|
-
* update fields — so user getters are not re-invoked. A guard-only patch yields
|
|
372
|
-
* an empty object → the caller emits no TREE_CHANGED (О-7: guards are
|
|
373
|
-
* invoked-on-demand, not cached, so they need no observation channel).
|
|
374
|
-
*
|
|
375
|
-
* The returned envelope is a fresh object (caller's patch untouched) and is
|
|
376
|
-
* frozen on construction. Nested values (e.g. `defaultParams`) are kept by
|
|
377
|
-
* reference — the same objects the router stored — so exotic inputs (circular
|
|
378
|
-
* refs, class instances) are tolerated, matching `update()`'s existing contract.
|
|
379
|
-
*/
|
|
380
|
-
function buildStructuralPatch<
|
|
381
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
382
|
-
>(fields: {
|
|
383
|
-
forwardTo?: string | ForwardToCallback<Dependencies> | null | undefined;
|
|
384
|
-
defaultParams?: Params | null | undefined;
|
|
385
|
-
decodeParams?: ((params: Params) => Params) | null | undefined;
|
|
386
|
-
encodeParams?: ((params: Params) => Params) | null | undefined;
|
|
387
|
-
}): Readonly<TreeStructuralPatch<Dependencies>> {
|
|
388
|
-
const patch: TreeStructuralPatch<Dependencies> = {};
|
|
389
|
-
|
|
390
|
-
if (fields.forwardTo !== undefined) {
|
|
391
|
-
patch.forwardTo = fields.forwardTo;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (fields.defaultParams !== undefined) {
|
|
395
|
-
patch.defaultParams = fields.defaultParams;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (fields.encodeParams !== undefined) {
|
|
399
|
-
patch.encodeParams = fields.encodeParams;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (fields.decodeParams !== undefined) {
|
|
403
|
-
patch.decodeParams = fields.decodeParams;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return Object.freeze(patch);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// ============================================================================
|
|
410
|
-
// CRUD operations
|
|
411
|
-
// ============================================================================
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Adds one or more routes to the router.
|
|
415
|
-
* Input already validated by facade.
|
|
416
|
-
*/
|
|
417
|
-
function addRoutes<
|
|
418
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
419
|
-
>(
|
|
420
|
-
store: RoutesStore<Dependencies>,
|
|
421
|
-
routes: Route<Dependencies>[],
|
|
422
|
-
parentName?: string,
|
|
423
|
-
): void {
|
|
424
|
-
// Prepare-then-commit (issue #698): reject the silent-corruption cases
|
|
425
|
-
// up front (dup name vs existing, missing parent), build the merged tree /
|
|
426
|
-
// config into locals (async/circular forwardTo + invalid constraint throw
|
|
427
|
-
// here), then swap atomically. A rejected add leaves the store untouched.
|
|
428
|
-
assertAddable(store, routes, parentName);
|
|
429
|
-
adoptRouteArtifacts(store, buildAddArtifacts(store, routes, parentName));
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Atomically replaces all routes with a new set (HMR / code-splitting).
|
|
434
|
-
* Prepare-then-commit (issue #698): the new set is fully built into locals
|
|
435
|
-
* first — a circular/async forwardTo or invalid path throws here, leaving the
|
|
436
|
-
* existing tree intact — then committed.
|
|
437
|
-
*/
|
|
438
|
-
function replaceRoutes<
|
|
439
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
440
|
-
>(
|
|
441
|
-
store: RoutesStore<Dependencies>,
|
|
442
|
-
routes: Route<Dependencies>[],
|
|
443
|
-
ctx: RouterInternals<Dependencies>,
|
|
444
|
-
currentPath: string | undefined,
|
|
445
|
-
previousTransition: TransitionMeta | undefined,
|
|
446
|
-
onCommitted?: () => void,
|
|
447
|
-
): void {
|
|
448
|
-
// Build the whole new set BEFORE touching the store.
|
|
449
|
-
const artifacts = buildReplaceArtifacts(
|
|
450
|
-
routes,
|
|
451
|
-
store.rootPath,
|
|
452
|
-
store.matcherOptions,
|
|
453
|
-
);
|
|
454
|
-
|
|
455
|
-
// Clear definition lifecycle handlers (preserve external guards), then swap.
|
|
456
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
457
|
-
store.lifecycleNamespace!.clearDefinitionGuards();
|
|
458
|
-
adoptRouteArtifacts(store, artifacts);
|
|
459
|
-
|
|
460
|
-
// TREE_CHANGED fires here (О-5): the new tree is committed but state is not
|
|
461
|
-
// yet revalidated, so the handler sees the new tree and the still-old state.
|
|
462
|
-
onCommitted?.();
|
|
463
|
-
|
|
464
|
-
// Revalidate state (preserve transition from previous state)
|
|
465
|
-
if (currentPath !== undefined) {
|
|
466
|
-
const revalidated = ctx.matchPath(currentPath, ctx.getOptions());
|
|
467
|
-
|
|
468
|
-
if (revalidated) {
|
|
469
|
-
ctx.setState({
|
|
470
|
-
...revalidated,
|
|
471
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- previousTransition is guaranteed defined: currentPath is only set when getState() returned a state, which always has transition
|
|
472
|
-
transition: previousTransition!,
|
|
473
|
-
});
|
|
474
|
-
} else {
|
|
475
|
-
ctx.clearState();
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Removes a route and all its children.
|
|
482
|
-
*
|
|
483
|
-
* @returns true if removed, false if not found
|
|
484
|
-
*/
|
|
485
|
-
function removeRoute<
|
|
486
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
487
|
-
>(store: RoutesStore<Dependencies>, name: string): boolean {
|
|
488
|
-
const wasRemoved = removeFromDefinitions(store.definitions, name);
|
|
489
|
-
|
|
490
|
-
if (!wasRemoved) {
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
clearRouteConfigurations(
|
|
495
|
-
name,
|
|
496
|
-
store.config,
|
|
497
|
-
store.routeCustomFields,
|
|
498
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
499
|
-
store.lifecycleNamespace!,
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
store.treeOperations.commitTreeChanges(store);
|
|
503
|
-
|
|
504
|
-
return true;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Updates a route's configuration in place.
|
|
509
|
-
*/
|
|
510
|
-
function updateRouteConfig<
|
|
511
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
512
|
-
>(
|
|
513
|
-
store: RoutesStore<Dependencies>,
|
|
514
|
-
name: string,
|
|
515
|
-
updates: {
|
|
516
|
-
forwardTo?: string | ForwardToCallback<Dependencies> | null | undefined;
|
|
517
|
-
defaultParams?: Params | null | undefined;
|
|
518
|
-
decodeParams?: ((params: Params) => Params) | null | undefined;
|
|
519
|
-
encodeParams?: ((params: Params) => Params) | null | undefined;
|
|
520
|
-
},
|
|
521
|
-
): void {
|
|
522
|
-
if (updates.forwardTo !== undefined) {
|
|
523
|
-
store.resolvedForwardMap = updateForwardTo(
|
|
524
|
-
name,
|
|
525
|
-
updates.forwardTo,
|
|
526
|
-
store.config,
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
if (updates.defaultParams !== undefined) {
|
|
531
|
-
if (updates.defaultParams === null) {
|
|
532
|
-
delete store.config.defaultParams[name];
|
|
533
|
-
} else {
|
|
534
|
-
store.config.defaultParams[name] = updates.defaultParams;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
if (updates.decodeParams !== undefined) {
|
|
539
|
-
if (updates.decodeParams === null) {
|
|
540
|
-
delete store.config.decoders[name];
|
|
541
|
-
} else {
|
|
542
|
-
const decoder = updates.decodeParams;
|
|
543
|
-
|
|
544
|
-
store.config.decoders[name] = (params: Params): Params =>
|
|
545
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime fallback if user-provided decoder violates its return type
|
|
546
|
-
decoder(params) ?? params;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
if (updates.encodeParams !== undefined) {
|
|
551
|
-
if (updates.encodeParams === null) {
|
|
552
|
-
delete store.config.encoders[name];
|
|
553
|
-
} else {
|
|
554
|
-
const encoder = updates.encodeParams;
|
|
555
|
-
|
|
556
|
-
store.config.encoders[name] = (params: Params): Params =>
|
|
557
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime fallback if user-provided encoder violates its return type
|
|
558
|
-
encoder(params) ?? params;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* Gets a route by name with all its configuration.
|
|
565
|
-
*/
|
|
566
|
-
function getRoute<
|
|
567
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
568
|
-
>(
|
|
569
|
-
store: RoutesStore<Dependencies>,
|
|
570
|
-
name: string,
|
|
571
|
-
): Route<Dependencies> | undefined {
|
|
572
|
-
const segments = store.matcher.getSegmentsByName(name);
|
|
573
|
-
|
|
574
|
-
if (!segments) {
|
|
575
|
-
return undefined;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- segments is non-empty (checked above)
|
|
579
|
-
const targetNode = segments.at(-1)! as RouteTree;
|
|
580
|
-
const definition = store.treeOperations.nodeToDefinition(targetNode);
|
|
581
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
582
|
-
const factories = store.lifecycleNamespace!.getFactories();
|
|
583
|
-
|
|
584
|
-
return enrichRoute(definition, name, store.config, factories);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// ============================================================================
|
|
588
|
-
// API factory
|
|
589
|
-
// ============================================================================
|
|
590
|
-
|
|
591
|
-
export function getRoutesApi<
|
|
592
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
593
|
-
>(router: Router<Dependencies>): RoutesApi<Dependencies> {
|
|
594
|
-
const ctx = getInternals(router);
|
|
595
|
-
|
|
596
|
-
const store = ctx.routeGetStore();
|
|
597
|
-
|
|
598
|
-
// Single cast site: the channel is typed with default Dependencies on
|
|
599
|
-
// RouterInternals (RouterEventMap is non-generic), but payloads are built
|
|
600
|
-
// with this api's Dependencies. The runtime shape is identical.
|
|
601
|
-
const emitChange = (event: TreeChangedEvent<Dependencies>): void => {
|
|
602
|
-
ctx.treeChanged.emit(event as TreeChangedEvent);
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
return {
|
|
606
|
-
add: (routes, options) => {
|
|
607
|
-
throwIfDisposed(ctx.isDisposed);
|
|
608
|
-
|
|
609
|
-
const routeArray = Array.isArray(routes) ? routes : [routes];
|
|
610
|
-
const parentName = options?.parent;
|
|
611
|
-
|
|
612
|
-
guardRouteStructure(routeArray, ctx.validator);
|
|
613
|
-
|
|
614
|
-
if (parentName !== undefined) {
|
|
615
|
-
ctx.validator?.routes.validateParentOption(parentName, store.tree);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
ctx.validator?.routes.throwIfInternalRouteInArray(routeArray, "addRoute");
|
|
619
|
-
ctx.validator?.routes.validateAddRouteArgs(routeArray);
|
|
620
|
-
ctx.validator?.routes.validateRoutes(routeArray, store);
|
|
621
|
-
|
|
622
|
-
addRoutes(store, routeArray, parentName);
|
|
623
|
-
|
|
624
|
-
// Built from the post-commit store (О-1), only when someone is listening.
|
|
625
|
-
if (ctx.treeChanged.listenerCount() > 0) {
|
|
626
|
-
const added = collectAddedRoutes(routeArray, parentName, store);
|
|
627
|
-
|
|
628
|
-
emitChange(
|
|
629
|
-
parentName === undefined
|
|
630
|
-
? { op: "add", added }
|
|
631
|
-
: { op: "add", added, parent: parentName },
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
},
|
|
635
|
-
|
|
636
|
-
remove: (name) => {
|
|
637
|
-
throwIfDisposed(ctx.isDisposed);
|
|
638
|
-
|
|
639
|
-
ctx.validator?.routes.validateRemoveRouteArgs(name);
|
|
640
|
-
ctx.validator?.routes.throwIfInternalRoute(name, "removeRoute");
|
|
641
|
-
|
|
642
|
-
const canRemove = validateRemoveRoute(
|
|
643
|
-
name,
|
|
644
|
-
ctx.getStateName(),
|
|
645
|
-
ctx.isTransitioning(),
|
|
646
|
-
);
|
|
647
|
-
|
|
648
|
-
if (!canRemove) {
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Snapshot the subtree BEFORE the mutation — the nodes are gone after.
|
|
653
|
-
const removedSubtree =
|
|
654
|
-
ctx.treeChanged.listenerCount() > 0
|
|
655
|
-
? collectSubtree(store, name)
|
|
656
|
-
: undefined;
|
|
657
|
-
const wasRemoved = removeRoute(store, name);
|
|
658
|
-
|
|
659
|
-
if (!wasRemoved) {
|
|
660
|
-
logger.warn(
|
|
661
|
-
"router.removeRoute",
|
|
662
|
-
`Route "${name}" not found. No changes made.`,
|
|
663
|
-
);
|
|
664
|
-
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
if (removedSubtree !== undefined) {
|
|
669
|
-
emitChange({ op: "remove", name, removedSubtree });
|
|
670
|
-
}
|
|
671
|
-
},
|
|
672
|
-
|
|
673
|
-
update: (name, updates) => {
|
|
674
|
-
throwIfDisposed(ctx.isDisposed);
|
|
675
|
-
|
|
676
|
-
ctx.validator?.routes.validateUpdateRouteBasicArgs(name, updates);
|
|
677
|
-
ctx.validator?.routes.throwIfInternalRoute(name, "updateRoute");
|
|
678
|
-
|
|
679
|
-
const {
|
|
680
|
-
forwardTo,
|
|
681
|
-
defaultParams,
|
|
682
|
-
decodeParams,
|
|
683
|
-
encodeParams,
|
|
684
|
-
canActivate,
|
|
685
|
-
canDeactivate,
|
|
686
|
-
} = updates;
|
|
687
|
-
|
|
688
|
-
ctx.validator?.routes.validateUpdateRoutePropertyTypes(name, updates);
|
|
689
|
-
|
|
690
|
-
/* v8 ignore next 6 -- @preserve: race condition guard, mirrors Router.updateRoute() same-path guard tested via Router.ts unit tests */
|
|
691
|
-
if (ctx.isTransitioning()) {
|
|
692
|
-
logger.error(
|
|
693
|
-
"router.updateRoute",
|
|
694
|
-
`Updating route "${name}" while navigation is in progress. This may cause unexpected behavior.`,
|
|
695
|
-
);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
ctx.validator?.routes.validateUpdateRoute(name, updates, store);
|
|
699
|
-
|
|
700
|
-
updateRouteConfig(store, name, {
|
|
701
|
-
forwardTo,
|
|
702
|
-
defaultParams,
|
|
703
|
-
decodeParams,
|
|
704
|
-
encodeParams,
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
if (canActivate !== undefined) {
|
|
708
|
-
if (canActivate === null) {
|
|
709
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
710
|
-
store.lifecycleNamespace!.clearCanActivate(name);
|
|
711
|
-
} else {
|
|
712
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
713
|
-
store.lifecycleNamespace!.addCanActivate(name, canActivate, true);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
if (canDeactivate !== undefined) {
|
|
718
|
-
if (canDeactivate === null) {
|
|
719
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
720
|
-
store.lifecycleNamespace!.clearCanDeactivate(name);
|
|
721
|
-
} else {
|
|
722
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
723
|
-
store.lifecycleNamespace!.addCanDeactivate(name, canDeactivate, true);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Conditional emit: structural fields only, built from the destructured
|
|
728
|
-
// locals (so user getters are not re-invoked). A guard-only or empty
|
|
729
|
-
// patch produces no event (О-7 + empty-patch rule).
|
|
730
|
-
if (ctx.treeChanged.listenerCount() > 0) {
|
|
731
|
-
const patch = buildStructuralPatch<Dependencies>({
|
|
732
|
-
forwardTo,
|
|
733
|
-
defaultParams,
|
|
734
|
-
encodeParams,
|
|
735
|
-
decodeParams,
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
if (Object.keys(patch).length > 0) {
|
|
739
|
-
emitChange({ op: "update", name, patch });
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
},
|
|
743
|
-
|
|
744
|
-
clear: () => {
|
|
745
|
-
throwIfDisposed(ctx.isDisposed);
|
|
746
|
-
|
|
747
|
-
const canClear = validateClearRoutes(ctx.isTransitioning());
|
|
748
|
-
|
|
749
|
-
/* v8 ignore next 3 -- @preserve: race condition guard, mirrors Router.clearRoutes() same-path guard tested via validateClearRoutes unit tests */
|
|
750
|
-
if (!canClear) {
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Snapshot the routes BEFORE the reset empties them. Emitted whenever
|
|
755
|
-
// there is a listener — even for an empty clear (О-4).
|
|
756
|
-
const removed =
|
|
757
|
-
ctx.treeChanged.listenerCount() > 0
|
|
758
|
-
? Object.freeze([...collectFlatRoutes(store, () => true).values()])
|
|
759
|
-
: undefined;
|
|
760
|
-
|
|
761
|
-
store.treeOperations.resetStore(store);
|
|
762
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring
|
|
763
|
-
store.lifecycleNamespace!.clearAll();
|
|
764
|
-
ctx.clearState();
|
|
765
|
-
|
|
766
|
-
if (removed !== undefined) {
|
|
767
|
-
emitChange({ op: "clear", removed });
|
|
768
|
-
}
|
|
769
|
-
},
|
|
770
|
-
|
|
771
|
-
has: (name) => {
|
|
772
|
-
ctx.validator?.routes.validateRouteName(name, "hasRoute");
|
|
773
|
-
|
|
774
|
-
return store.matcher.hasRoute(name);
|
|
775
|
-
},
|
|
776
|
-
|
|
777
|
-
get: (name) => {
|
|
778
|
-
ctx.validator?.routes.validateRouteName(name, "getRoute");
|
|
779
|
-
|
|
780
|
-
return getRoute(store, name);
|
|
781
|
-
},
|
|
782
|
-
|
|
783
|
-
replace: (routes) => {
|
|
784
|
-
throwIfDisposed(ctx.isDisposed);
|
|
785
|
-
|
|
786
|
-
const routeArray = Array.isArray(routes) ? routes : [routes];
|
|
787
|
-
|
|
788
|
-
const canReplace = validateClearRoutes(ctx.isTransitioning());
|
|
789
|
-
|
|
790
|
-
if (!canReplace) {
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
guardRouteStructure(routeArray, ctx.validator);
|
|
795
|
-
|
|
796
|
-
ctx.validator?.routes.throwIfInternalRouteInArray(
|
|
797
|
-
routeArray,
|
|
798
|
-
"replaceRoutes",
|
|
799
|
-
);
|
|
800
|
-
ctx.validator?.routes.validateAddRouteArgs(routeArray);
|
|
801
|
-
ctx.validator?.routes.validateRoutes(routeArray, store);
|
|
802
|
-
|
|
803
|
-
const currentState = router.getState();
|
|
804
|
-
|
|
805
|
-
// The flat removed/added diff is O(N) — compute it only when someone is
|
|
806
|
-
// listening (Решение 3.B). Snapshot the old tree BEFORE the swap.
|
|
807
|
-
const before =
|
|
808
|
-
ctx.treeChanged.listenerCount() > 0
|
|
809
|
-
? collectFlatRoutes(store, () => true)
|
|
810
|
-
: undefined;
|
|
811
|
-
|
|
812
|
-
replaceRoutes(
|
|
813
|
-
store,
|
|
814
|
-
routeArray,
|
|
815
|
-
ctx,
|
|
816
|
-
currentState?.path,
|
|
817
|
-
currentState?.transition,
|
|
818
|
-
before === undefined
|
|
819
|
-
? undefined
|
|
820
|
-
: () => {
|
|
821
|
-
const after = collectFlatRoutes(store, () => true);
|
|
822
|
-
const { removed, added } = diffFlatRoutes(before, after);
|
|
823
|
-
|
|
824
|
-
emitChange({ op: "replace", removed, added });
|
|
825
|
-
},
|
|
826
|
-
);
|
|
827
|
-
},
|
|
828
|
-
|
|
829
|
-
subscribeChanges: (handler) => ctx.treeChanged.subscribe(handler),
|
|
830
|
-
};
|
|
831
|
-
}
|