@real-router/core 0.45.1 → 0.45.2
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/package.json +7 -10
- package/src/Router.ts +684 -0
- package/src/RouterError.ts +324 -0
- package/src/api/cloneRouter.ts +77 -0
- package/src/api/getDependenciesApi.ts +168 -0
- package/src/api/getLifecycleApi.ts +65 -0
- package/src/api/getPluginApi.ts +167 -0
- package/src/api/getRoutesApi.ts +573 -0
- package/src/api/helpers.ts +10 -0
- package/src/api/index.ts +16 -0
- package/src/api/types.ts +12 -0
- package/src/constants.ts +87 -0
- package/src/createRouter.ts +32 -0
- package/src/fsm/index.ts +5 -0
- package/src/fsm/routerFSM.ts +120 -0
- package/src/getNavigator.ts +30 -0
- package/src/guards.ts +46 -0
- package/src/helpers.ts +179 -0
- package/src/index.ts +50 -0
- package/src/internals.ts +173 -0
- package/src/namespaces/DependenciesNamespace/dependenciesStore.ts +30 -0
- package/src/namespaces/DependenciesNamespace/index.ts +5 -0
- package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +311 -0
- package/src/namespaces/EventBusNamespace/index.ts +5 -0
- package/src/namespaces/EventBusNamespace/types.ts +11 -0
- package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +405 -0
- package/src/namespaces/NavigationNamespace/constants.ts +55 -0
- package/src/namespaces/NavigationNamespace/index.ts +5 -0
- package/src/namespaces/NavigationNamespace/transition/completeTransition.ts +100 -0
- package/src/namespaces/NavigationNamespace/transition/errorHandling.ts +124 -0
- package/src/namespaces/NavigationNamespace/transition/guardPhase.ts +221 -0
- package/src/namespaces/NavigationNamespace/types.ts +100 -0
- package/src/namespaces/OptionsNamespace/OptionsNamespace.ts +28 -0
- package/src/namespaces/OptionsNamespace/constants.ts +19 -0
- package/src/namespaces/OptionsNamespace/helpers.ts +50 -0
- package/src/namespaces/OptionsNamespace/index.ts +7 -0
- package/src/namespaces/OptionsNamespace/validators.ts +13 -0
- package/src/namespaces/PluginsNamespace/PluginsNamespace.ts +291 -0
- package/src/namespaces/PluginsNamespace/constants.ts +34 -0
- package/src/namespaces/PluginsNamespace/index.ts +7 -0
- package/src/namespaces/PluginsNamespace/types.ts +22 -0
- package/src/namespaces/PluginsNamespace/validators.ts +28 -0
- package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +377 -0
- package/src/namespaces/RouteLifecycleNamespace/index.ts +5 -0
- package/src/namespaces/RouteLifecycleNamespace/types.ts +10 -0
- package/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts +81 -0
- package/src/namespaces/RouterLifecycleNamespace/constants.ts +25 -0
- package/src/namespaces/RouterLifecycleNamespace/index.ts +5 -0
- package/src/namespaces/RouterLifecycleNamespace/types.ts +26 -0
- package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +535 -0
- package/src/namespaces/RoutesNamespace/constants.ts +6 -0
- package/src/namespaces/RoutesNamespace/forwardChain.ts +34 -0
- package/src/namespaces/RoutesNamespace/helpers.ts +126 -0
- package/src/namespaces/RoutesNamespace/index.ts +11 -0
- package/src/namespaces/RoutesNamespace/routeGuards.ts +62 -0
- package/src/namespaces/RoutesNamespace/routesStore.ts +346 -0
- package/src/namespaces/RoutesNamespace/types.ts +81 -0
- package/src/namespaces/StateNamespace/StateNamespace.ts +211 -0
- package/src/namespaces/StateNamespace/helpers.ts +24 -0
- package/src/namespaces/StateNamespace/index.ts +5 -0
- package/src/namespaces/StateNamespace/types.ts +15 -0
- package/src/namespaces/index.ts +35 -0
- package/src/stateMetaStore.ts +15 -0
- package/src/transitionPath.ts +436 -0
- package/src/typeGuards.ts +59 -0
- package/src/types/RouterValidator.ts +154 -0
- package/src/types.ts +69 -0
- package/src/utils/getStaticPaths.ts +50 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/serializeState.ts +22 -0
- package/src/validation.ts +12 -0
- package/src/wiring/RouterWiringBuilder.ts +261 -0
- package/src/wiring/index.ts +7 -0
- package/src/wiring/types.ts +47 -0
- package/src/wiring/wireRouter.ts +26 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// packages/core/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_LIMITS } from "../../constants";
|
|
4
|
+
|
|
5
|
+
import type { RouteLifecycleDependencies } from "./types";
|
|
6
|
+
import type { GuardFnFactory, Limits } from "../../types";
|
|
7
|
+
import type { RouterValidator } from "../../types/RouterValidator";
|
|
8
|
+
import type { DefaultDependencies, GuardFn, State } from "@real-router/types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Converts a boolean value to a guard function factory.
|
|
12
|
+
* Used for the shorthand syntax where true/false is passed instead of a function.
|
|
13
|
+
*/
|
|
14
|
+
function booleanToFactory<Dependencies extends DefaultDependencies>(
|
|
15
|
+
value: boolean,
|
|
16
|
+
): GuardFnFactory<Dependencies> {
|
|
17
|
+
const guardFn: GuardFn = () => value;
|
|
18
|
+
|
|
19
|
+
return () => guardFn;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Independent namespace for managing route lifecycle handlers.
|
|
24
|
+
*
|
|
25
|
+
* Static methods handle input validation (called by facade).
|
|
26
|
+
* Instance methods handle state-dependent validation, storage and business logic.
|
|
27
|
+
*/
|
|
28
|
+
export class RouteLifecycleNamespace<
|
|
29
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
30
|
+
> {
|
|
31
|
+
readonly #canDeactivateFactories = new Map<
|
|
32
|
+
string,
|
|
33
|
+
GuardFnFactory<Dependencies>
|
|
34
|
+
>();
|
|
35
|
+
readonly #canActivateFactories = new Map<
|
|
36
|
+
string,
|
|
37
|
+
GuardFnFactory<Dependencies>
|
|
38
|
+
>();
|
|
39
|
+
readonly #canDeactivateFunctions = new Map<string, GuardFn>();
|
|
40
|
+
readonly #canActivateFunctions = new Map<string, GuardFn>();
|
|
41
|
+
// Cached tuple — Maps never change reference, so this is stable
|
|
42
|
+
readonly #functionsTuple: [Map<string, GuardFn>, Map<string, GuardFn>] = [
|
|
43
|
+
this.#canDeactivateFunctions,
|
|
44
|
+
this.#canActivateFunctions,
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
readonly #registering = new Set<string>();
|
|
48
|
+
readonly #definitionActivateGuardNames = new Set<string>();
|
|
49
|
+
readonly #definitionDeactivateGuardNames = new Set<string>();
|
|
50
|
+
|
|
51
|
+
#deps!: RouteLifecycleDependencies<Dependencies>;
|
|
52
|
+
#limits: Limits = DEFAULT_LIMITS;
|
|
53
|
+
#getValidator: (() => RouterValidator | null) | null = null;
|
|
54
|
+
|
|
55
|
+
setDependencies(deps: RouteLifecycleDependencies<Dependencies>): void {
|
|
56
|
+
this.#deps = deps;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Updates handler registration limits (max lifecycle handlers threshold).
|
|
61
|
+
*
|
|
62
|
+
* @param limits - Limits configuration with maxLifecycleHandlers
|
|
63
|
+
*/
|
|
64
|
+
setLimits(limits: Limits): void {
|
|
65
|
+
this.#limits = limits;
|
|
66
|
+
// eslint-disable-next-line sonarjs/void-use -- @preserve: Wave 3 validator reads limits via RouterInternals; void suppresses TS6133 until then
|
|
67
|
+
void this.#limits;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setValidatorGetter(getter: () => RouterValidator | null): void {
|
|
71
|
+
this.#getValidator = getter;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getHandlerCount(type: "activate" | "deactivate"): number {
|
|
75
|
+
return type === "activate"
|
|
76
|
+
? this.#canActivateFactories.size
|
|
77
|
+
: this.#canDeactivateFactories.size;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// =========================================================================
|
|
81
|
+
// Instance methods
|
|
82
|
+
// =========================================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Adds a canActivate guard for a route.
|
|
86
|
+
* Handles overwrite detection and registration.
|
|
87
|
+
*
|
|
88
|
+
* @param name - Route name (input-validated by facade)
|
|
89
|
+
* @param handler - Guard function or boolean (input-validated by facade)
|
|
90
|
+
* @param isFromDefinition - True when guard comes from route definition (tracked for HMR replace)
|
|
91
|
+
*/
|
|
92
|
+
addCanActivate(
|
|
93
|
+
name: string,
|
|
94
|
+
handler: GuardFnFactory<Dependencies> | boolean,
|
|
95
|
+
isFromDefinition = false,
|
|
96
|
+
): void {
|
|
97
|
+
if (isFromDefinition) {
|
|
98
|
+
this.#definitionActivateGuardNames.add(name);
|
|
99
|
+
} else {
|
|
100
|
+
this.#definitionActivateGuardNames.delete(name);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const isOverwrite = this.#canActivateFactories.has(name);
|
|
104
|
+
|
|
105
|
+
this.#registerHandler(
|
|
106
|
+
"activate",
|
|
107
|
+
name,
|
|
108
|
+
handler,
|
|
109
|
+
this.#canActivateFactories,
|
|
110
|
+
this.#canActivateFunctions,
|
|
111
|
+
"canActivate",
|
|
112
|
+
isOverwrite,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Adds a canDeactivate guard for a route.
|
|
118
|
+
* Handles overwrite detection and registration.
|
|
119
|
+
*
|
|
120
|
+
* @param name - Route name (input-validated by facade)
|
|
121
|
+
* @param handler - Guard function or boolean (input-validated by facade)
|
|
122
|
+
* @param isFromDefinition - True when guard comes from route definition (tracked for HMR replace)
|
|
123
|
+
*/
|
|
124
|
+
addCanDeactivate(
|
|
125
|
+
name: string,
|
|
126
|
+
handler: GuardFnFactory<Dependencies> | boolean,
|
|
127
|
+
isFromDefinition = false,
|
|
128
|
+
): void {
|
|
129
|
+
if (isFromDefinition) {
|
|
130
|
+
this.#definitionDeactivateGuardNames.add(name);
|
|
131
|
+
} else {
|
|
132
|
+
this.#definitionDeactivateGuardNames.delete(name);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const isOverwrite = this.#canDeactivateFactories.has(name);
|
|
136
|
+
|
|
137
|
+
this.#registerHandler(
|
|
138
|
+
"deactivate",
|
|
139
|
+
name,
|
|
140
|
+
handler,
|
|
141
|
+
this.#canDeactivateFactories,
|
|
142
|
+
this.#canDeactivateFunctions,
|
|
143
|
+
"canDeactivate",
|
|
144
|
+
isOverwrite,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Removes a canActivate guard for a route.
|
|
150
|
+
* Input already validated by facade (not registering).
|
|
151
|
+
*
|
|
152
|
+
* @param name - Route name (already validated by facade)
|
|
153
|
+
*/
|
|
154
|
+
clearCanActivate(name: string): void {
|
|
155
|
+
this.#canActivateFactories.delete(name);
|
|
156
|
+
this.#canActivateFunctions.delete(name);
|
|
157
|
+
this.#definitionActivateGuardNames.delete(name);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Removes a canDeactivate guard for a route.
|
|
162
|
+
* Input already validated by facade (not registering).
|
|
163
|
+
*
|
|
164
|
+
* @param name - Route name (already validated by facade)
|
|
165
|
+
*/
|
|
166
|
+
clearCanDeactivate(name: string): void {
|
|
167
|
+
this.#canDeactivateFactories.delete(name);
|
|
168
|
+
this.#canDeactivateFunctions.delete(name);
|
|
169
|
+
this.#definitionDeactivateGuardNames.delete(name);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Clears all lifecycle handlers (canActivate and canDeactivate).
|
|
174
|
+
* Used by clearRoutes to reset all lifecycle state.
|
|
175
|
+
*/
|
|
176
|
+
clearAll(): void {
|
|
177
|
+
this.#canActivateFactories.clear();
|
|
178
|
+
this.#canActivateFunctions.clear();
|
|
179
|
+
this.#canDeactivateFactories.clear();
|
|
180
|
+
this.#canDeactivateFunctions.clear();
|
|
181
|
+
this.#definitionActivateGuardNames.clear();
|
|
182
|
+
this.#definitionDeactivateGuardNames.clear();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Clears only lifecycle handlers that were registered from route definitions.
|
|
187
|
+
* Used by HMR to remove definition-sourced guards without touching externally-added guards.
|
|
188
|
+
*/
|
|
189
|
+
clearDefinitionGuards(): void {
|
|
190
|
+
for (const name of this.#definitionActivateGuardNames) {
|
|
191
|
+
this.#canActivateFactories.delete(name);
|
|
192
|
+
this.#canActivateFunctions.delete(name);
|
|
193
|
+
}
|
|
194
|
+
for (const name of this.#definitionDeactivateGuardNames) {
|
|
195
|
+
this.#canDeactivateFactories.delete(name);
|
|
196
|
+
this.#canDeactivateFunctions.delete(name);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.#definitionActivateGuardNames.clear();
|
|
200
|
+
this.#definitionDeactivateGuardNames.clear();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Returns lifecycle factories as records for cloning.
|
|
205
|
+
*
|
|
206
|
+
* @returns Tuple of [canDeactivateFactories, canActivateFactories]
|
|
207
|
+
*/
|
|
208
|
+
getFactories(): [
|
|
209
|
+
Record<string, GuardFnFactory<Dependencies>>,
|
|
210
|
+
Record<string, GuardFnFactory<Dependencies>>,
|
|
211
|
+
] {
|
|
212
|
+
const deactivateRecord: Record<string, GuardFnFactory<Dependencies>> = {};
|
|
213
|
+
const activateRecord: Record<string, GuardFnFactory<Dependencies>> = {};
|
|
214
|
+
|
|
215
|
+
for (const [name, factory] of this.#canDeactivateFactories) {
|
|
216
|
+
deactivateRecord[name] = factory;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const [name, factory] of this.#canActivateFactories) {
|
|
220
|
+
activateRecord[name] = factory;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return [deactivateRecord, activateRecord];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Returns compiled lifecycle functions for transition execution.
|
|
228
|
+
*
|
|
229
|
+
* @returns Tuple of [canDeactivateFunctions, canActivateFunctions] as Maps
|
|
230
|
+
*/
|
|
231
|
+
getFunctions(): [Map<string, GuardFn>, Map<string, GuardFn>] {
|
|
232
|
+
return this.#functionsTuple;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
canNavigateTo(
|
|
236
|
+
toDeactivate: string[],
|
|
237
|
+
toActivate: string[],
|
|
238
|
+
toState: State,
|
|
239
|
+
fromState: State | undefined,
|
|
240
|
+
): boolean {
|
|
241
|
+
for (const segment of toDeactivate) {
|
|
242
|
+
if (
|
|
243
|
+
!this.#checkGuardSync(
|
|
244
|
+
this.#canDeactivateFunctions,
|
|
245
|
+
segment,
|
|
246
|
+
toState,
|
|
247
|
+
fromState,
|
|
248
|
+
"canNavigateTo",
|
|
249
|
+
)
|
|
250
|
+
) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
for (const segment of toActivate) {
|
|
256
|
+
if (
|
|
257
|
+
!this.#checkGuardSync(
|
|
258
|
+
this.#canActivateFunctions,
|
|
259
|
+
segment,
|
|
260
|
+
toState,
|
|
261
|
+
fromState,
|
|
262
|
+
"canNavigateTo",
|
|
263
|
+
)
|
|
264
|
+
) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// =========================================================================
|
|
273
|
+
// Private methods (business logic)
|
|
274
|
+
// =========================================================================
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Registers a handler.
|
|
278
|
+
* Handles overwrite warning, count threshold warnings, and factory compilation.
|
|
279
|
+
*
|
|
280
|
+
* @param type - Guard type for log messages ("activate" or "deactivate")
|
|
281
|
+
* @param name - Route name to register the guard for
|
|
282
|
+
* @param handler - Guard factory function or boolean shorthand
|
|
283
|
+
* @param factories - Target factory map (canActivate or canDeactivate)
|
|
284
|
+
* @param functions - Target compiled functions map (canActivate or canDeactivate)
|
|
285
|
+
* @param methodName - Public API method name for error/warning messages
|
|
286
|
+
* @param isOverwrite - Whether this replaces an existing guard for the same route
|
|
287
|
+
*/
|
|
288
|
+
#registerHandler(
|
|
289
|
+
type: "activate" | "deactivate",
|
|
290
|
+
name: string,
|
|
291
|
+
handler: GuardFnFactory<Dependencies> | boolean,
|
|
292
|
+
factories: Map<string, GuardFnFactory<Dependencies>>,
|
|
293
|
+
functions: Map<string, GuardFn>,
|
|
294
|
+
methodName: string,
|
|
295
|
+
isOverwrite: boolean,
|
|
296
|
+
): void {
|
|
297
|
+
// Emit warnings
|
|
298
|
+
if (isOverwrite) {
|
|
299
|
+
this.#getValidator?.()?.lifecycle.warnOverwrite(name, type, methodName);
|
|
300
|
+
} else {
|
|
301
|
+
this.#getValidator?.()?.lifecycle.validateCountThresholds(
|
|
302
|
+
factories.size + 1,
|
|
303
|
+
methodName,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Convert boolean to factory if needed
|
|
308
|
+
const factory =
|
|
309
|
+
typeof handler === "boolean"
|
|
310
|
+
? booleanToFactory<Dependencies>(handler)
|
|
311
|
+
: handler;
|
|
312
|
+
|
|
313
|
+
// Store factory
|
|
314
|
+
factories.set(name, factory);
|
|
315
|
+
|
|
316
|
+
// Mark route as being registered before calling user factory
|
|
317
|
+
this.#registering.add(name);
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const fn = this.#deps.compileFactory(factory);
|
|
321
|
+
|
|
322
|
+
if (typeof fn !== "function") {
|
|
323
|
+
throw new TypeError(
|
|
324
|
+
`[router.${methodName}] Factory must return a function, got ${typeof fn}`,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
functions.set(name, fn);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
// Rollback on failure to maintain consistency
|
|
331
|
+
factories.delete(name);
|
|
332
|
+
|
|
333
|
+
throw error;
|
|
334
|
+
} finally {
|
|
335
|
+
this.#registering.delete(name);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Shared implementation for synchronous guard checks.
|
|
341
|
+
* Warns if a guard returns a Promise (async guards are not supported in sync mode).
|
|
342
|
+
* Catches exceptions and treats them as navigation-blocking (`false`).
|
|
343
|
+
*
|
|
344
|
+
* @param functions - Map of compiled guard functions to look up
|
|
345
|
+
* @param name - Route name to check the guard for
|
|
346
|
+
* @param toState - Target navigation state
|
|
347
|
+
* @param fromState - Current state (`undefined` on initial navigation)
|
|
348
|
+
* @param methodName - Public API method name for warning messages
|
|
349
|
+
*/
|
|
350
|
+
#checkGuardSync(
|
|
351
|
+
functions: Map<string, GuardFn>,
|
|
352
|
+
name: string,
|
|
353
|
+
toState: State,
|
|
354
|
+
fromState: State | undefined,
|
|
355
|
+
methodName: string,
|
|
356
|
+
): boolean {
|
|
357
|
+
const guardFn = functions.get(name);
|
|
358
|
+
|
|
359
|
+
if (!guardFn) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const result = guardFn(toState, fromState);
|
|
365
|
+
|
|
366
|
+
if (typeof result === "boolean") {
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
this.#getValidator?.()?.lifecycle.warnAsyncGuardSync(name, methodName);
|
|
371
|
+
|
|
372
|
+
return false;
|
|
373
|
+
} catch {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// packages/core/src/namespaces/RouteLifecycleNamespace/types.ts
|
|
2
|
+
|
|
3
|
+
import type { GuardFnFactory } from "../../types";
|
|
4
|
+
import type { DefaultDependencies, GuardFn } from "@real-router/types";
|
|
5
|
+
|
|
6
|
+
export interface RouteLifecycleDependencies<
|
|
7
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
8
|
+
> {
|
|
9
|
+
compileFactory: (factory: GuardFnFactory<Dependencies>) => GuardFn;
|
|
10
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// packages/core/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts
|
|
2
|
+
|
|
3
|
+
import { errorCodes } from "../../constants";
|
|
4
|
+
import { RouterError } from "../../RouterError";
|
|
5
|
+
|
|
6
|
+
import type { RouterLifecycleDependencies } from "./types";
|
|
7
|
+
import type { NavigationOptions, State } from "@real-router/types";
|
|
8
|
+
|
|
9
|
+
const REPLACE_OPTS: NavigationOptions = { replace: true };
|
|
10
|
+
|
|
11
|
+
Object.freeze(REPLACE_OPTS);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Independent namespace for managing router lifecycle.
|
|
15
|
+
*
|
|
16
|
+
* Handles start() and stop(). Lifecycle state (isActive, isStarted) is managed
|
|
17
|
+
* by RouterFSM in the facade (Router.ts).
|
|
18
|
+
*/
|
|
19
|
+
export class RouterLifecycleNamespace {
|
|
20
|
+
#deps!: RouterLifecycleDependencies;
|
|
21
|
+
|
|
22
|
+
// =========================================================================
|
|
23
|
+
// Dependency injection
|
|
24
|
+
// =========================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Sets dependencies for lifecycle operations.
|
|
28
|
+
* Must be called before using lifecycle methods.
|
|
29
|
+
*/
|
|
30
|
+
setDependencies(deps: RouterLifecycleDependencies): void {
|
|
31
|
+
this.#deps = deps;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// =========================================================================
|
|
35
|
+
// Instance methods
|
|
36
|
+
// =========================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Starts the router with the given path.
|
|
40
|
+
*
|
|
41
|
+
* Guards (concurrent start, already started) are handled by the facade via
|
|
42
|
+
* RouterFSM state checks before this method is called.
|
|
43
|
+
*/
|
|
44
|
+
async start(startPath: string): Promise<State> {
|
|
45
|
+
const deps = this.#deps;
|
|
46
|
+
const options = deps.getOptions();
|
|
47
|
+
|
|
48
|
+
const matchedState = deps.matchPath(startPath);
|
|
49
|
+
|
|
50
|
+
if (!matchedState && !options.allowNotFound) {
|
|
51
|
+
const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, {
|
|
52
|
+
path: startPath,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
deps.emitTransitionError(undefined, undefined, err);
|
|
56
|
+
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
deps.completeStart();
|
|
61
|
+
|
|
62
|
+
if (matchedState) {
|
|
63
|
+
return deps.navigate(
|
|
64
|
+
matchedState.name,
|
|
65
|
+
matchedState.params,
|
|
66
|
+
REPLACE_OPTS,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return deps.navigateToNotFound(startPath);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Stops the router and resets state.
|
|
75
|
+
*
|
|
76
|
+
* Called only for READY/TRANSITION_STARTED states (facade handles STARTING/IDLE/DISPOSED).
|
|
77
|
+
*/
|
|
78
|
+
stop(): void {
|
|
79
|
+
this.#deps.clearState();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// packages/core/src/namespaces/RouterLifecycleNamespace/constants.ts
|
|
2
|
+
|
|
3
|
+
import { errorCodes } from "../../constants";
|
|
4
|
+
import { RouterError } from "../../RouterError";
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Cached Errors (Performance Optimization)
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Pre-create error instances to avoid object allocation on hot paths.
|
|
10
|
+
// Error creation involves: new object, stack trace capture (~500ns-2μs).
|
|
11
|
+
// Cached errors skip this overhead entirely.
|
|
12
|
+
//
|
|
13
|
+
// Trade-off: All error instances share the same stack trace (points here).
|
|
14
|
+
// This is acceptable because:
|
|
15
|
+
// 1. These errors indicate user misconfiguration, not internal bugs
|
|
16
|
+
// 2. Error code and message are sufficient for debugging
|
|
17
|
+
// 3. Performance gain (~80% for error paths) outweighs stack trace loss
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Cached error for start() called when router is already started/starting.
|
|
22
|
+
*/
|
|
23
|
+
export const CACHED_ALREADY_STARTED_ERROR = new RouterError(
|
|
24
|
+
errorCodes.ROUTER_ALREADY_STARTED,
|
|
25
|
+
);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// packages/core/src/namespaces/RouterLifecycleNamespace/types.ts
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
NavigationOptions,
|
|
5
|
+
Options,
|
|
6
|
+
Params,
|
|
7
|
+
State,
|
|
8
|
+
} from "@real-router/types";
|
|
9
|
+
|
|
10
|
+
export interface RouterLifecycleDependencies {
|
|
11
|
+
getOptions: () => Options;
|
|
12
|
+
navigate: (
|
|
13
|
+
name: string,
|
|
14
|
+
params: Params,
|
|
15
|
+
opts: NavigationOptions,
|
|
16
|
+
) => Promise<State>;
|
|
17
|
+
navigateToNotFound: (path: string) => State;
|
|
18
|
+
clearState: () => void;
|
|
19
|
+
matchPath: <P extends Params = Params>(path: string) => State<P> | undefined;
|
|
20
|
+
completeStart: () => void;
|
|
21
|
+
emitTransitionError: (
|
|
22
|
+
toState: State | undefined,
|
|
23
|
+
fromState: State | undefined,
|
|
24
|
+
error: Error,
|
|
25
|
+
) => void;
|
|
26
|
+
}
|