@real-router/core 0.44.2 → 0.45.1
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/api.js +1 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/package.json +6 -7
- package/src/Router.ts +0 -684
- package/src/RouterError.ts +0 -324
- package/src/api/cloneRouter.ts +0 -77
- package/src/api/getDependenciesApi.ts +0 -168
- package/src/api/getLifecycleApi.ts +0 -65
- package/src/api/getPluginApi.ts +0 -167
- package/src/api/getRoutesApi.ts +0 -562
- 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 -87
- package/src/createRouter.ts +0 -32
- package/src/fsm/index.ts +0 -5
- package/src/fsm/routerFSM.ts +0 -120
- package/src/getNavigator.ts +0 -30
- package/src/guards.ts +0 -46
- package/src/helpers.ts +0 -179
- package/src/index.ts +0 -50
- package/src/internals.ts +0 -173
- package/src/namespaces/DependenciesNamespace/dependenciesStore.ts +0 -30
- package/src/namespaces/DependenciesNamespace/index.ts +0 -5
- package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +0 -311
- package/src/namespaces/EventBusNamespace/index.ts +0 -5
- package/src/namespaces/EventBusNamespace/types.ts +0 -11
- package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +0 -405
- package/src/namespaces/NavigationNamespace/constants.ts +0 -55
- package/src/namespaces/NavigationNamespace/index.ts +0 -5
- package/src/namespaces/NavigationNamespace/transition/completeTransition.ts +0 -100
- package/src/namespaces/NavigationNamespace/transition/errorHandling.ts +0 -124
- package/src/namespaces/NavigationNamespace/transition/guardPhase.ts +0 -221
- package/src/namespaces/NavigationNamespace/types.ts +0 -100
- 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 -377
- 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 -26
- package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +0 -535
- package/src/namespaces/RoutesNamespace/constants.ts +0 -6
- package/src/namespaces/RoutesNamespace/forwardChain.ts +0 -34
- package/src/namespaces/RoutesNamespace/helpers.ts +0 -126
- package/src/namespaces/RoutesNamespace/index.ts +0 -11
- package/src/namespaces/RoutesNamespace/routeGuards.ts +0 -62
- package/src/namespaces/RoutesNamespace/routesStore.ts +0 -346
- package/src/namespaces/RoutesNamespace/types.ts +0 -81
- package/src/namespaces/StateNamespace/StateNamespace.ts +0 -211
- 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 -436
- package/src/typeGuards.ts +0 -59
- package/src/types/RouterValidator.ts +0 -154
- package/src/types.ts +0 -69
- package/src/utils/getStaticPaths.ts +0 -50
- package/src/utils/index.ts +0 -5
- package/src/utils/serializeState.ts +0 -22
- package/src/validation.ts +0 -12
- package/src/wiring/RouterWiringBuilder.ts +0 -261
- package/src/wiring/index.ts +0 -7
- package/src/wiring/types.ts +0 -47
- package/src/wiring/wireRouter.ts +0 -26
|
@@ -1,535 +0,0 @@
|
|
|
1
|
-
// packages/core/src/namespaces/RoutesNamespace/RoutesNamespace.ts
|
|
2
|
-
|
|
3
|
-
import { DEFAULT_ROUTE_NAME } from "./constants";
|
|
4
|
-
import { paramsMatch, paramsMatchExcluding } from "./helpers";
|
|
5
|
-
import {
|
|
6
|
-
createRoutesStore,
|
|
7
|
-
rebuildTreeInPlace,
|
|
8
|
-
resetStore,
|
|
9
|
-
} from "./routesStore";
|
|
10
|
-
import { constants } from "../../constants";
|
|
11
|
-
import { getTransitionPath } from "../../transitionPath";
|
|
12
|
-
|
|
13
|
-
import type { RoutesStore } from "./routesStore";
|
|
14
|
-
import type { RoutesDependencies } from "./types";
|
|
15
|
-
import type { Route } from "../../types";
|
|
16
|
-
import type { RouteLifecycleNamespace } from "../RouteLifecycleNamespace";
|
|
17
|
-
import type {
|
|
18
|
-
DefaultDependencies,
|
|
19
|
-
ForwardToCallback,
|
|
20
|
-
Options,
|
|
21
|
-
Params,
|
|
22
|
-
State,
|
|
23
|
-
} from "@real-router/types";
|
|
24
|
-
import type {
|
|
25
|
-
CreateMatcherOptions,
|
|
26
|
-
RouteParams,
|
|
27
|
-
RouteTree,
|
|
28
|
-
RouteTreeState,
|
|
29
|
-
} from "route-tree";
|
|
30
|
-
|
|
31
|
-
function collectUrlParamsArray(segments: readonly RouteTree[]): string[] {
|
|
32
|
-
const params: string[] = [];
|
|
33
|
-
|
|
34
|
-
for (const segment of segments) {
|
|
35
|
-
for (const param of segment.paramMeta.urlParams) {
|
|
36
|
-
params.push(param);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return params;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function buildNameFromSegments(
|
|
44
|
-
segments: readonly { fullName: string }[],
|
|
45
|
-
): string {
|
|
46
|
-
return segments.at(-1)?.fullName ?? "";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function createRouteState<P extends RouteParams = RouteParams>(
|
|
50
|
-
matchResult: {
|
|
51
|
-
readonly segments: readonly { fullName: string }[];
|
|
52
|
-
readonly params: Readonly<Record<string, unknown>>;
|
|
53
|
-
readonly meta: Readonly<Record<string, Record<string, "url" | "query">>>;
|
|
54
|
-
},
|
|
55
|
-
name?: string,
|
|
56
|
-
): RouteTreeState<P> {
|
|
57
|
-
const resolvedName = name ?? buildNameFromSegments(matchResult.segments);
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
name: resolvedName,
|
|
61
|
-
params: matchResult.params as P,
|
|
62
|
-
meta: matchResult.meta as Record<string, Record<string, "url" | "query">>,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface CachedBuildPathOpts {
|
|
67
|
-
readonly trailingSlash?: "always" | "never" | undefined;
|
|
68
|
-
readonly queryParamsMode?: "default" | "strict" | "loose" | undefined;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Independent namespace for managing routes.
|
|
73
|
-
*
|
|
74
|
-
* Static methods handle validation (called by facade).
|
|
75
|
-
* Instance methods handle storage and business logic.
|
|
76
|
-
*/
|
|
77
|
-
export class RoutesNamespace<
|
|
78
|
-
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
79
|
-
> {
|
|
80
|
-
readonly #store: RoutesStore<Dependencies>;
|
|
81
|
-
#cachedBuildPathOpts: CachedBuildPathOpts | undefined;
|
|
82
|
-
|
|
83
|
-
get #deps(): RoutesDependencies<Dependencies> {
|
|
84
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
85
|
-
return this.#store.depsStore!;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
constructor(
|
|
89
|
-
routes: Route<Dependencies>[] = [],
|
|
90
|
-
matcherOptions?: CreateMatcherOptions,
|
|
91
|
-
) {
|
|
92
|
-
this.#store = createRoutesStore(routes, matcherOptions);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Creates a predicate function to check if a route node should be updated.
|
|
97
|
-
* Note: Argument validation is done by facade (Router.ts) via validateShouldUpdateNodeArgs.
|
|
98
|
-
*/
|
|
99
|
-
static shouldUpdateNode(
|
|
100
|
-
nodeName: string,
|
|
101
|
-
): (toState: State, fromState?: State) => boolean {
|
|
102
|
-
return (toState: State, fromState?: State): boolean => {
|
|
103
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
104
|
-
if (!(toState && typeof toState === "object" && "name" in toState)) {
|
|
105
|
-
throw new TypeError(
|
|
106
|
-
"[router.shouldUpdateNode] toState must be valid State object",
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (toState.transition?.reload) {
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (nodeName === DEFAULT_ROUTE_NAME && !fromState) {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const { intersection, toActivate, toDeactivate } = getTransitionPath(
|
|
119
|
-
toState,
|
|
120
|
-
fromState,
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
if (nodeName === intersection) {
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (toActivate.includes(nodeName)) {
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return toDeactivate.includes(nodeName);
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// =========================================================================
|
|
136
|
-
// Dependency injection
|
|
137
|
-
// =========================================================================
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Sets dependencies and registers pending canActivate handlers.
|
|
141
|
-
* canActivate handlers from initial routes are deferred until deps are set.
|
|
142
|
-
*/
|
|
143
|
-
setDependencies(deps: RoutesDependencies<Dependencies>): void {
|
|
144
|
-
this.#store.depsStore = deps;
|
|
145
|
-
|
|
146
|
-
for (const [routeName, handler] of this.#store.pendingCanActivate) {
|
|
147
|
-
deps.addActivateGuard(routeName, handler);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
this.#store.pendingCanActivate.clear();
|
|
151
|
-
|
|
152
|
-
for (const [routeName, handler] of this.#store.pendingCanDeactivate) {
|
|
153
|
-
deps.addDeactivateGuard(routeName, handler);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
this.#store.pendingCanDeactivate.clear();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Sets the lifecycle namespace reference.
|
|
161
|
-
*/
|
|
162
|
-
setLifecycleNamespace(
|
|
163
|
-
namespace: RouteLifecycleNamespace<Dependencies> | undefined,
|
|
164
|
-
): void {
|
|
165
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
166
|
-
this.#store.lifecycleNamespace = namespace!;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// =========================================================================
|
|
170
|
-
// Route tree operations
|
|
171
|
-
// =========================================================================
|
|
172
|
-
|
|
173
|
-
setRootPath(newRootPath: string): void {
|
|
174
|
-
this.#store.rootPath = newRootPath;
|
|
175
|
-
rebuildTreeInPlace(this.#store);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
hasRoute(name: string): boolean {
|
|
179
|
-
return this.#store.matcher.hasRoute(name);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
clearRoutes(): void {
|
|
183
|
-
resetStore(this.#store);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// =========================================================================
|
|
187
|
-
// Path operations
|
|
188
|
-
// =========================================================================
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Builds a URL path for a route.
|
|
192
|
-
* Note: Argument validation is done by facade (Router.ts) via validateBuildPathArgs.
|
|
193
|
-
*
|
|
194
|
-
* @param route - Route name
|
|
195
|
-
* @param params - Route parameters
|
|
196
|
-
* @param options - Router options
|
|
197
|
-
*/
|
|
198
|
-
buildPath(route: string, params?: Params, options?: Options): string {
|
|
199
|
-
if (route === constants.UNKNOWN_ROUTE) {
|
|
200
|
-
return typeof params?.path === "string" ? params.path : "";
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const paramsWithDefault = Object.hasOwn(
|
|
204
|
-
this.#store.config.defaultParams,
|
|
205
|
-
route,
|
|
206
|
-
)
|
|
207
|
-
? { ...this.#store.config.defaultParams[route], ...params }
|
|
208
|
-
: /* v8 ignore next -- @preserve: V8 can't track ?? branch in ternary; covered by buildPath tests without params */ (params ??
|
|
209
|
-
{});
|
|
210
|
-
|
|
211
|
-
const encodedParams =
|
|
212
|
-
typeof this.#store.config.encoders[route] === "function"
|
|
213
|
-
? this.#store.config.encoders[route]({ ...paramsWithDefault })
|
|
214
|
-
: paramsWithDefault;
|
|
215
|
-
|
|
216
|
-
return this.#store.matcher.buildPath(
|
|
217
|
-
route,
|
|
218
|
-
encodedParams,
|
|
219
|
-
this.#getBuildPathOptions(options),
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Matches a URL path to a route in the tree.
|
|
225
|
-
* Note: Argument validation is done by facade (Router.ts) via validateMatchPathArgs.
|
|
226
|
-
*/
|
|
227
|
-
matchPath<P extends Params = Params>(
|
|
228
|
-
path: string,
|
|
229
|
-
options?: Options,
|
|
230
|
-
): State<P> | undefined {
|
|
231
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Router.ts always passes options
|
|
232
|
-
const opts = options!;
|
|
233
|
-
|
|
234
|
-
const matchResult = this.#store.matcher.match(path);
|
|
235
|
-
|
|
236
|
-
if (!matchResult) {
|
|
237
|
-
return undefined;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const routeState = createRouteState(matchResult);
|
|
241
|
-
const { name, params, meta } = routeState;
|
|
242
|
-
|
|
243
|
-
const decodedParams =
|
|
244
|
-
typeof this.#store.config.decoders[name] === "function"
|
|
245
|
-
? this.#store.config.decoders[name](params as Params)
|
|
246
|
-
: params;
|
|
247
|
-
|
|
248
|
-
const { name: routeName, params: routeParams } = this.#deps.forwardState<P>(
|
|
249
|
-
name,
|
|
250
|
-
decodedParams as P,
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
let builtPath = path;
|
|
254
|
-
|
|
255
|
-
if (opts.rewritePathOnMatch) {
|
|
256
|
-
const buildParams =
|
|
257
|
-
typeof this.#store.config.encoders[routeName] === "function"
|
|
258
|
-
? this.#store.config.encoders[routeName]({
|
|
259
|
-
...(routeParams as Params),
|
|
260
|
-
})
|
|
261
|
-
: (routeParams as Record<string, unknown>);
|
|
262
|
-
|
|
263
|
-
const ts = opts.trailingSlash;
|
|
264
|
-
|
|
265
|
-
builtPath = this.#store.matcher.buildPath(routeName, buildParams, {
|
|
266
|
-
trailingSlash: ts === "never" || ts === "always" ? ts : undefined,
|
|
267
|
-
queryParamsMode: opts.queryParamsMode,
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return this.#deps.makeState<P>(routeName, routeParams, builtPath, meta);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Applies forwardTo and returns resolved state with merged defaultParams.
|
|
276
|
-
*
|
|
277
|
-
* Merges params in order:
|
|
278
|
-
* 1. Source route defaultParams
|
|
279
|
-
* 2. Provided params
|
|
280
|
-
* 3. Target route defaultParams (after resolving forwardTo)
|
|
281
|
-
*/
|
|
282
|
-
forwardState<P extends Params = Params>(
|
|
283
|
-
name: string,
|
|
284
|
-
params: P,
|
|
285
|
-
): { name: string; params: P } {
|
|
286
|
-
if (Object.hasOwn(this.#store.config.forwardFnMap, name)) {
|
|
287
|
-
const paramsWithSourceDefaults = this.#mergeDefaultParams(name, params);
|
|
288
|
-
const dynamicForward = this.#store.config.forwardFnMap[name];
|
|
289
|
-
const resolved = this.#resolveDynamicForward(
|
|
290
|
-
name,
|
|
291
|
-
dynamicForward,
|
|
292
|
-
params,
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
return {
|
|
296
|
-
name: resolved,
|
|
297
|
-
params: this.#mergeDefaultParams(resolved, paramsWithSourceDefaults),
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const staticForward = this.#store.resolvedForwardMap[name] ?? name;
|
|
302
|
-
|
|
303
|
-
if (
|
|
304
|
-
staticForward !== name &&
|
|
305
|
-
Object.hasOwn(this.#store.config.forwardFnMap, staticForward)
|
|
306
|
-
) {
|
|
307
|
-
const paramsWithSourceDefaults = this.#mergeDefaultParams(name, params);
|
|
308
|
-
const targetDynamicForward =
|
|
309
|
-
this.#store.config.forwardFnMap[staticForward];
|
|
310
|
-
const resolved = this.#resolveDynamicForward(
|
|
311
|
-
staticForward,
|
|
312
|
-
targetDynamicForward,
|
|
313
|
-
params,
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
return {
|
|
317
|
-
name: resolved,
|
|
318
|
-
params: this.#mergeDefaultParams(resolved, paramsWithSourceDefaults),
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (staticForward !== name) {
|
|
323
|
-
const paramsWithSourceDefaults = this.#mergeDefaultParams(name, params);
|
|
324
|
-
|
|
325
|
-
return {
|
|
326
|
-
name: staticForward,
|
|
327
|
-
params: this.#mergeDefaultParams(
|
|
328
|
-
staticForward,
|
|
329
|
-
paramsWithSourceDefaults,
|
|
330
|
-
),
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return { name, params: this.#mergeDefaultParams(name, params) };
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Builds a RouteTreeState from already-resolved route name and params.
|
|
339
|
-
* Called by Router.buildState after forwardState is applied at facade level.
|
|
340
|
-
* This allows plugins to intercept forwardState.
|
|
341
|
-
*/
|
|
342
|
-
buildStateResolved(
|
|
343
|
-
resolvedName: string,
|
|
344
|
-
resolvedParams: Params,
|
|
345
|
-
): RouteTreeState | undefined {
|
|
346
|
-
const segments = this.#store.matcher.getSegmentsByName(resolvedName);
|
|
347
|
-
|
|
348
|
-
if (!segments) {
|
|
349
|
-
return undefined;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
353
|
-
const meta = this.#store.matcher.getMetaByName(resolvedName)!;
|
|
354
|
-
|
|
355
|
-
return createRouteState(
|
|
356
|
-
{ segments, params: resolvedParams, meta },
|
|
357
|
-
resolvedName,
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// =========================================================================
|
|
362
|
-
// Query operations
|
|
363
|
-
// =========================================================================
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Checks if a route is currently active.
|
|
367
|
-
*/
|
|
368
|
-
isActiveRoute(
|
|
369
|
-
name: string,
|
|
370
|
-
params: Params = {},
|
|
371
|
-
strictEquality = false,
|
|
372
|
-
ignoreQueryParams = true,
|
|
373
|
-
): boolean {
|
|
374
|
-
// Note: empty string check is handled by Router.ts facade
|
|
375
|
-
const activeState = this.#deps.getState();
|
|
376
|
-
|
|
377
|
-
if (!activeState) {
|
|
378
|
-
return false;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const activeName = activeState.name;
|
|
382
|
-
|
|
383
|
-
// Fast path: check if routes are related before expensive operations
|
|
384
|
-
if (
|
|
385
|
-
activeName !== name &&
|
|
386
|
-
!activeName.startsWith(`${name}.`) &&
|
|
387
|
-
!name.startsWith(`${activeName}.`)
|
|
388
|
-
) {
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const defaultParams = this.#store.config.defaultParams[name] as
|
|
393
|
-
| Params
|
|
394
|
-
| undefined;
|
|
395
|
-
|
|
396
|
-
// Exact match case
|
|
397
|
-
if (strictEquality || activeName === name) {
|
|
398
|
-
const effectiveParams = defaultParams
|
|
399
|
-
? { ...defaultParams, ...params }
|
|
400
|
-
: params;
|
|
401
|
-
|
|
402
|
-
const targetState: State = {
|
|
403
|
-
name,
|
|
404
|
-
params: effectiveParams,
|
|
405
|
-
path: "",
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
return this.#deps.areStatesEqual(
|
|
409
|
-
targetState,
|
|
410
|
-
activeState,
|
|
411
|
-
ignoreQueryParams,
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Hierarchical check: activeState is a descendant of target (name)
|
|
416
|
-
const activeParams = activeState.params;
|
|
417
|
-
|
|
418
|
-
if (!paramsMatch(params, activeParams)) {
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Check defaultParams (skip keys already in params)
|
|
423
|
-
return (
|
|
424
|
-
!defaultParams ||
|
|
425
|
-
paramsMatchExcluding(defaultParams, activeParams, params)
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
getMetaForState(
|
|
430
|
-
name: string,
|
|
431
|
-
): Record<string, Record<string, "url" | "query">> | undefined {
|
|
432
|
-
return this.#store.matcher.hasRoute(name)
|
|
433
|
-
? this.#store.matcher.getMetaByName(name)
|
|
434
|
-
: undefined;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
getUrlParams(name: string): string[] {
|
|
438
|
-
const segments = this.#store.matcher.getSegmentsByName(name);
|
|
439
|
-
|
|
440
|
-
if (!segments) {
|
|
441
|
-
return [];
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return collectUrlParamsArray(segments as readonly RouteTree[]);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
getStore(): RoutesStore<Dependencies> {
|
|
448
|
-
return this.#store;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
#mergeDefaultParams<P extends Params = Params>(
|
|
452
|
-
routeName: string,
|
|
453
|
-
params: P,
|
|
454
|
-
): P {
|
|
455
|
-
if (Object.hasOwn(this.#store.config.defaultParams, routeName)) {
|
|
456
|
-
return {
|
|
457
|
-
...this.#store.config.defaultParams[routeName],
|
|
458
|
-
...params,
|
|
459
|
-
} as P;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return params;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
#getBuildPathOptions(options?: Options): CachedBuildPathOpts {
|
|
466
|
-
if (this.#cachedBuildPathOpts) {
|
|
467
|
-
return this.#cachedBuildPathOpts;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const ts = options?.trailingSlash;
|
|
471
|
-
|
|
472
|
-
this.#cachedBuildPathOpts = Object.freeze({
|
|
473
|
-
trailingSlash: ts === "never" || ts === "always" ? ts : undefined,
|
|
474
|
-
queryParamsMode: options?.queryParamsMode,
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
return this.#cachedBuildPathOpts;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
#resolveDynamicForward(
|
|
481
|
-
startName: string,
|
|
482
|
-
startFn: ForwardToCallback<Dependencies>,
|
|
483
|
-
params: Params,
|
|
484
|
-
): string {
|
|
485
|
-
const visited = new Set<string>([startName]);
|
|
486
|
-
|
|
487
|
-
let current = startFn(this.#deps.getDependency, params);
|
|
488
|
-
let depth = 0;
|
|
489
|
-
const MAX_DEPTH = 100;
|
|
490
|
-
|
|
491
|
-
if (typeof current !== "string") {
|
|
492
|
-
throw new TypeError(
|
|
493
|
-
`forwardTo callback must return a string, got ${typeof current}`,
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
while (depth < MAX_DEPTH) {
|
|
498
|
-
if (this.#store.matcher.getSegmentsByName(current) === undefined) {
|
|
499
|
-
throw new Error(`Route "${current}" does not exist`);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (visited.has(current)) {
|
|
503
|
-
const chain = [...visited, current].join(" → ");
|
|
504
|
-
|
|
505
|
-
throw new Error(`Circular forwardTo detected: ${chain}`);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
visited.add(current);
|
|
509
|
-
|
|
510
|
-
if (Object.hasOwn(this.#store.config.forwardFnMap, current)) {
|
|
511
|
-
const fn = this.#store.config.forwardFnMap[
|
|
512
|
-
current
|
|
513
|
-
] as ForwardToCallback<Dependencies>;
|
|
514
|
-
|
|
515
|
-
current = fn(this.#deps.getDependency, params);
|
|
516
|
-
|
|
517
|
-
depth++;
|
|
518
|
-
continue;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const staticForward = this.#store.config.forwardMap[current];
|
|
522
|
-
|
|
523
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
524
|
-
if (staticForward !== undefined) {
|
|
525
|
-
current = staticForward;
|
|
526
|
-
depth++;
|
|
527
|
-
continue;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
return current;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
throw new Error(`forwardTo exceeds maximum depth of ${MAX_DEPTH}`);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// packages/core/src/namespaces/RoutesNamespace/forwardChain.ts
|
|
2
|
-
|
|
3
|
-
export function resolveForwardChain(
|
|
4
|
-
startRoute: string,
|
|
5
|
-
forwardMap: Record<string, string>,
|
|
6
|
-
maxDepth = 100,
|
|
7
|
-
): string {
|
|
8
|
-
const visited = new Set<string>();
|
|
9
|
-
const chain: string[] = [startRoute];
|
|
10
|
-
let current = startRoute;
|
|
11
|
-
|
|
12
|
-
while (forwardMap[current]) {
|
|
13
|
-
const next = forwardMap[current];
|
|
14
|
-
|
|
15
|
-
if (visited.has(next)) {
|
|
16
|
-
const cycleStart = chain.indexOf(next);
|
|
17
|
-
const cycle = [...chain.slice(cycleStart), next];
|
|
18
|
-
|
|
19
|
-
throw new Error(`Circular forwardTo: ${cycle.join(" → ")}`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
visited.add(current);
|
|
23
|
-
chain.push(next);
|
|
24
|
-
current = next;
|
|
25
|
-
|
|
26
|
-
if (chain.length > maxDepth) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
`forwardTo chain exceeds maximum depth (${maxDepth}): ${chain.join(" → ")}`,
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return current;
|
|
34
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// packages/core/src/namespaces/RoutesNamespace/helpers.ts
|
|
2
|
-
|
|
3
|
-
import type { RouteConfig } from "./types";
|
|
4
|
-
import type { Route } from "../../types";
|
|
5
|
-
import type {
|
|
6
|
-
DefaultDependencies,
|
|
7
|
-
ForwardToCallback,
|
|
8
|
-
Params,
|
|
9
|
-
} from "@real-router/types";
|
|
10
|
-
import type { RouteDefinition } from "route-tree";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Creates an empty RouteConfig.
|
|
14
|
-
*/
|
|
15
|
-
export function createEmptyConfig(): RouteConfig {
|
|
16
|
-
return {
|
|
17
|
-
decoders: Object.create(null) as Record<string, (params: Params) => Params>,
|
|
18
|
-
encoders: Object.create(null) as Record<string, (params: Params) => Params>,
|
|
19
|
-
defaultParams: Object.create(null) as Record<string, Params>,
|
|
20
|
-
forwardMap: Object.create(null) as Record<string, string>,
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
-
forwardFnMap: Object.create(null) as Record<string, ForwardToCallback<any>>,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ============================================================================
|
|
27
|
-
// Route Tree Helpers
|
|
28
|
-
// ============================================================================
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Checks if all params from source exist with same values in target.
|
|
32
|
-
* Small function body allows V8 inlining.
|
|
33
|
-
*/
|
|
34
|
-
export function paramsMatch(source: Params, target: Params): boolean {
|
|
35
|
-
for (const key in source) {
|
|
36
|
-
if (source[key] !== target[key]) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Checks params match, skipping keys present in skipKeys.
|
|
46
|
-
*/
|
|
47
|
-
export function paramsMatchExcluding(
|
|
48
|
-
source: Params,
|
|
49
|
-
target: Params,
|
|
50
|
-
skipKeys: Params,
|
|
51
|
-
): boolean {
|
|
52
|
-
for (const key in source) {
|
|
53
|
-
if (key in skipKeys) {
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (source[key] !== target[key]) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Sanitizes a route by keeping only essential properties.
|
|
66
|
-
*/
|
|
67
|
-
export function sanitizeRoute<Dependencies extends DefaultDependencies>(
|
|
68
|
-
route: Route<Dependencies>,
|
|
69
|
-
): RouteDefinition {
|
|
70
|
-
const sanitized: RouteDefinition = {
|
|
71
|
-
name: route.name,
|
|
72
|
-
path: route.path,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
if (route.children) {
|
|
76
|
-
sanitized.children = route.children.map((child) => sanitizeRoute(child));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return sanitized;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Recursively removes a route from definitions array.
|
|
84
|
-
*/
|
|
85
|
-
export function removeFromDefinitions(
|
|
86
|
-
definitions: RouteDefinition[],
|
|
87
|
-
routeName: string,
|
|
88
|
-
parentPrefix = "",
|
|
89
|
-
): boolean {
|
|
90
|
-
for (let i = 0; i < definitions.length; i++) {
|
|
91
|
-
const route = definitions[i];
|
|
92
|
-
const fullName = parentPrefix
|
|
93
|
-
? `${parentPrefix}.${route.name}`
|
|
94
|
-
: route.name;
|
|
95
|
-
|
|
96
|
-
if (fullName === routeName) {
|
|
97
|
-
definitions.splice(i, 1);
|
|
98
|
-
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
route.children &&
|
|
104
|
-
routeName.startsWith(`${fullName}.`) &&
|
|
105
|
-
removeFromDefinitions(route.children, routeName, fullName)
|
|
106
|
-
) {
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Clears configuration entries that match the predicate.
|
|
116
|
-
*/
|
|
117
|
-
export function clearConfigEntries<T>(
|
|
118
|
-
config: Record<string, T>,
|
|
119
|
-
matcher: (key: string) => boolean,
|
|
120
|
-
): void {
|
|
121
|
-
for (const key of Object.keys(config)) {
|
|
122
|
-
if (matcher(key)) {
|
|
123
|
-
delete config[key];
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// packages/core/src/namespaces/RoutesNamespace/index.ts
|
|
2
|
-
|
|
3
|
-
export { RoutesNamespace } from "./RoutesNamespace";
|
|
4
|
-
|
|
5
|
-
export { DEFAULT_ROUTE_NAME } from "./constants";
|
|
6
|
-
|
|
7
|
-
export { createEmptyConfig } from "./helpers";
|
|
8
|
-
|
|
9
|
-
export type { RouteConfig, RoutesDependencies } from "./types";
|
|
10
|
-
|
|
11
|
-
export type { RoutesStore } from "./routesStore";
|