@real-router/core 0.24.0 → 0.25.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.
Files changed (43) hide show
  1. package/README.md +0 -15
  2. package/dist/cjs/index.d.ts +4 -10
  3. package/dist/cjs/index.js +1 -1
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/metafile-cjs.json +1 -1
  6. package/dist/esm/index.d.mts +4 -10
  7. package/dist/esm/index.mjs +1 -1
  8. package/dist/esm/index.mjs.map +1 -1
  9. package/dist/esm/metafile-esm.json +1 -1
  10. package/package.json +4 -4
  11. package/src/Router.ts +11 -54
  12. package/src/RouterError.ts +1 -1
  13. package/src/constants.ts +1 -3
  14. package/src/helpers.ts +1 -1
  15. package/src/index.ts +1 -3
  16. package/src/namespaces/CloneNamespace/CloneNamespace.ts +6 -6
  17. package/src/namespaces/CloneNamespace/types.ts +3 -7
  18. package/src/namespaces/DependenciesNamespace/DependenciesNamespace.ts +2 -4
  19. package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +1 -0
  20. package/src/namespaces/NavigationNamespace/transition/{wrapSyncError.ts → errorHandling.ts} +23 -17
  21. package/src/namespaces/NavigationNamespace/transition/executeLifecycleHooks.ts +6 -6
  22. package/src/namespaces/NavigationNamespace/transition/index.ts +6 -27
  23. package/src/namespaces/NavigationNamespace/types.ts +0 -4
  24. package/src/namespaces/PluginsNamespace/PluginsNamespace.ts +2 -2
  25. package/src/namespaces/RouteLifecycleNamespace/validators.ts +1 -1
  26. package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +50 -31
  27. package/src/namespaces/RoutesNamespace/stateBuilder.ts +1 -1
  28. package/src/namespaces/index.ts +0 -2
  29. package/src/transitionPath.ts +4 -40
  30. package/src/typeGuards.ts +1 -1
  31. package/src/types.ts +0 -12
  32. package/src/wiring/RouterWiringBuilder.ts +1 -17
  33. package/src/wiring/types.ts +0 -3
  34. package/src/wiring/wireRouter.ts +0 -1
  35. package/src/namespaces/MiddlewareNamespace/MiddlewareNamespace.ts +0 -221
  36. package/src/namespaces/MiddlewareNamespace/constants.ts +0 -3
  37. package/src/namespaces/MiddlewareNamespace/index.ts +0 -5
  38. package/src/namespaces/MiddlewareNamespace/types.ts +0 -28
  39. package/src/namespaces/MiddlewareNamespace/validators.ts +0 -95
  40. package/src/namespaces/NavigationNamespace/transition/executeMiddleware.ts +0 -56
  41. package/src/namespaces/NavigationNamespace/transition/makeError.ts +0 -37
  42. package/src/namespaces/NavigationNamespace/transition/mergeStates.ts +0 -54
  43. package/src/namespaces/NavigationNamespace/transition/processLifecycleResult.ts +0 -69
@@ -1,221 +0,0 @@
1
- // packages/core/src/namespaces/MiddlewareNamespace/MiddlewareNamespace.ts
2
-
3
- import { logger } from "@real-router/logger";
4
-
5
- import { LOGGER_CONTEXT } from "./constants";
6
- import {
7
- validateMiddleware,
8
- validateMiddlewareLimit,
9
- validateNoDuplicates,
10
- validateUseMiddlewareArgs,
11
- } from "./validators";
12
- import { DEFAULT_LIMITS } from "../../constants";
13
- import { computeThresholds } from "../../helpers";
14
-
15
- import type { InitializedMiddleware, MiddlewareDependencies } from "./types";
16
- import type { Router } from "../../Router";
17
- import type { Limits, MiddlewareFactory } from "../../types";
18
- import type {
19
- DefaultDependencies,
20
- Middleware,
21
- Unsubscribe,
22
- } from "@real-router/types";
23
-
24
- /**
25
- * Independent namespace for managing middleware.
26
- *
27
- * Static methods handle validation (called by facade).
28
- * Instance methods handle storage and business logic.
29
- */
30
- export class MiddlewareNamespace<
31
- Dependencies extends DefaultDependencies = DefaultDependencies,
32
- > {
33
- readonly #factories = new Set<MiddlewareFactory<Dependencies>>();
34
- readonly #factoryToMiddleware = new Map<
35
- MiddlewareFactory<Dependencies>,
36
- Middleware
37
- >();
38
-
39
- #router!: Router<Dependencies>;
40
- #deps!: MiddlewareDependencies<Dependencies>;
41
- #limits: Limits = DEFAULT_LIMITS;
42
-
43
- // =========================================================================
44
- // Static validation methods (called by facade before instance methods)
45
- // Proxy to functions in validators.ts for separation of concerns
46
- // =========================================================================
47
-
48
- static validateUseMiddlewareArgs<D extends DefaultDependencies>(
49
- middlewares: unknown[],
50
- ): asserts middlewares is MiddlewareFactory<D>[] {
51
- validateUseMiddlewareArgs<D>(middlewares);
52
- }
53
-
54
- static validateMiddleware<D extends DefaultDependencies>(
55
- middleware: unknown,
56
- factory: MiddlewareFactory<D>,
57
- ): asserts middleware is Middleware {
58
- validateMiddleware<D>(middleware, factory);
59
- }
60
-
61
- static validateNoDuplicates<D extends DefaultDependencies>(
62
- newFactories: MiddlewareFactory<D>[],
63
- has: (factory: MiddlewareFactory<D>) => boolean,
64
- ): void {
65
- validateNoDuplicates<D>(newFactories, has);
66
- }
67
-
68
- static validateMiddlewareLimit(
69
- currentCount: number,
70
- newCount: number,
71
- maxMiddleware?: number,
72
- ): void {
73
- validateMiddlewareLimit(currentCount, newCount, maxMiddleware);
74
- }
75
-
76
- // =========================================================================
77
- // Dependency injection
78
- // =========================================================================
79
-
80
- setRouter(router: Router<Dependencies>): void {
81
- this.#router = router;
82
- }
83
-
84
- setDependencies(deps: MiddlewareDependencies<Dependencies>): void {
85
- this.#deps = deps;
86
- }
87
-
88
- setLimits(limits: Limits): void {
89
- this.#limits = limits;
90
- }
91
-
92
- // =========================================================================
93
- // Instance methods (trust input - already validated by facade)
94
- // =========================================================================
95
-
96
- /**
97
- * Returns the current number of registered middleware.
98
- */
99
- count(): number {
100
- return this.#factories.size;
101
- }
102
-
103
- /**
104
- * Initializes middleware factories without committing to storage.
105
- * Returns array of initialized middleware for validation by facade.
106
- *
107
- * @param factories - Already validated by facade
108
- */
109
- initialize(
110
- ...factories: MiddlewareFactory<Dependencies>[]
111
- ): InitializedMiddleware<Dependencies>[] {
112
- const initialized: InitializedMiddleware<Dependencies>[] = [];
113
-
114
- for (const factory of factories) {
115
- // Middleware factories receive full router as part of their public API
116
- const middleware = factory(this.#router, this.#deps.getDependency);
117
-
118
- initialized.push({ factory, middleware });
119
- }
120
-
121
- return initialized;
122
- }
123
-
124
- /**
125
- * Commits initialized middleware to storage.
126
- * Returns unsubscribe function to remove all added middleware.
127
- *
128
- * @param initialized - Already validated by facade
129
- */
130
- commit(initialized: InitializedMiddleware<Dependencies>[]): Unsubscribe {
131
- // Check count thresholds and log warnings if needed
132
- this.#checkCountThresholds(initialized.length);
133
-
134
- // Add to storage
135
- for (const { factory, middleware } of initialized) {
136
- this.#factories.add(factory);
137
- this.#factoryToMiddleware.set(factory, middleware);
138
- }
139
-
140
- // Return unsubscribe function specific to THIS call's middleware
141
- let unsubscribed = false;
142
-
143
- return (): void => {
144
- if (unsubscribed) {
145
- return;
146
- }
147
-
148
- unsubscribed = true;
149
-
150
- for (const { factory } of initialized) {
151
- this.#factories.delete(factory);
152
- this.#factoryToMiddleware.delete(factory);
153
- }
154
- };
155
- }
156
-
157
- /**
158
- * Returns a copy of registered middleware factories.
159
- * Preserves insertion order.
160
- */
161
- getFactories(): MiddlewareFactory<Dependencies>[] {
162
- return [...this.#factories];
163
- }
164
-
165
- /**
166
- * Checks if a middleware factory is registered.
167
- * Used internally by validation to avoid array allocation.
168
- */
169
- has(factory: MiddlewareFactory<Dependencies>): boolean {
170
- return this.#factories.has(factory);
171
- }
172
-
173
- /**
174
- * Returns the actual middleware functions in execution order.
175
- */
176
- getFunctions(): Middleware[] {
177
- return [...this.#factoryToMiddleware.values()];
178
- }
179
-
180
- /**
181
- * Clears all registered middleware factories and their initialized functions.
182
- * Unlike {@link disposeAll} in PluginsNamespace, no teardown is needed —
183
- * middleware are pure functions with no event subscriptions or lifecycle.
184
- * Named "clear" (not "dispose") because there is nothing active to dispose.
185
- */
186
- clearAll(): void {
187
- this.#factories.clear();
188
- this.#factoryToMiddleware.clear();
189
- }
190
-
191
- // =========================================================================
192
- // Private methods
193
- // =========================================================================
194
-
195
- #checkCountThresholds(newCount: number): void {
196
- const maxMiddleware = this.#limits.maxMiddleware;
197
-
198
- if (maxMiddleware === 0) {
199
- return;
200
- }
201
-
202
- const totalSize = newCount + this.#factories.size;
203
-
204
- const { warn, error } = computeThresholds(maxMiddleware);
205
-
206
- if (totalSize >= error) {
207
- logger.error(
208
- LOGGER_CONTEXT,
209
- `${totalSize} middleware registered! ` +
210
- `This is excessive and will impact performance. ` +
211
- `Hard limit at ${maxMiddleware}.`,
212
- );
213
- } else if (totalSize >= warn) {
214
- logger.warn(
215
- LOGGER_CONTEXT,
216
- `${totalSize} middleware registered. ` +
217
- `Consider if all are necessary.`,
218
- );
219
- }
220
- }
221
- }
@@ -1,3 +0,0 @@
1
- // packages/core/src/namespaces/MiddlewareNamespace/constants.ts
2
-
3
- export const LOGGER_CONTEXT = "router.useMiddleware";
@@ -1,5 +0,0 @@
1
- // packages/core/src/namespaces/MiddlewareNamespace/index.ts
2
-
3
- export { MiddlewareNamespace } from "./MiddlewareNamespace";
4
-
5
- export type { MiddlewareDependencies } from "./types";
@@ -1,28 +0,0 @@
1
- // packages/core/src/namespaces/MiddlewareNamespace/types.ts
2
-
3
- import type { MiddlewareFactory } from "../../types";
4
- import type { DefaultDependencies, Middleware } from "@real-router/types";
5
-
6
- /**
7
- * Dependencies injected into MiddlewareNamespace.
8
- *
9
- * Note: Middleware factories still receive the router object directly
10
- * as they need access to various router methods. This interface
11
- * only covers the internal namespace operations.
12
- */
13
- export interface MiddlewareDependencies<
14
- Dependencies extends DefaultDependencies = DefaultDependencies,
15
- > {
16
- /** Get dependency value for middleware factory */
17
- getDependency: <K extends keyof Dependencies>(key: K) => Dependencies[K];
18
- }
19
-
20
- /**
21
- * Initialized middleware entry returned by initialize().
22
- */
23
- export interface InitializedMiddleware<
24
- Dependencies extends DefaultDependencies,
25
- > {
26
- factory: MiddlewareFactory<Dependencies>;
27
- middleware: Middleware;
28
- }
@@ -1,95 +0,0 @@
1
- // packages/core/src/namespaces/MiddlewareNamespace/validators.ts
2
-
3
- /**
4
- * Static validation functions for MiddlewareNamespace.
5
- * Called by Router facade before instance methods.
6
- */
7
-
8
- import { getTypeDescription } from "type-guards";
9
-
10
- import { LOGGER_CONTEXT } from "./constants";
11
- import { DEFAULT_LIMITS } from "../../constants";
12
-
13
- import type { MiddlewareFactory } from "../../types";
14
- import type { DefaultDependencies, Middleware } from "@real-router/types";
15
-
16
- /**
17
- * Gets a displayable name for a factory function.
18
- */
19
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
20
- function getFactoryName(factory: Function): string {
21
- return factory.name || "anonymous";
22
- }
23
-
24
- /**
25
- * Validates useMiddleware arguments - each must be a function.
26
- */
27
- export function validateUseMiddlewareArgs<D extends DefaultDependencies>(
28
- middlewares: unknown[],
29
- ): asserts middlewares is MiddlewareFactory<D>[] {
30
- for (const [i, middleware] of middlewares.entries()) {
31
- if (typeof middleware !== "function") {
32
- throw new TypeError(
33
- `[${LOGGER_CONTEXT}] Expected middleware factory function at index ${i}, ` +
34
- `got ${getTypeDescription(middleware)}`,
35
- );
36
- }
37
- }
38
- }
39
-
40
- /**
41
- * Validates that a middleware factory returned a valid middleware function.
42
- */
43
- export function validateMiddleware<D extends DefaultDependencies>(
44
- middleware: unknown,
45
- factory: MiddlewareFactory<D>,
46
- ): asserts middleware is Middleware {
47
- if (typeof middleware !== "function") {
48
- throw new TypeError(
49
- `[${LOGGER_CONTEXT}] Middleware factory must return a function, ` +
50
- `got ${getTypeDescription(middleware)}. ` +
51
- `Factory: ${getFactoryName(factory)}`,
52
- );
53
- }
54
- }
55
-
56
- /**
57
- * Validates that no duplicate factories are being registered.
58
- */
59
- export function validateNoDuplicates<D extends DefaultDependencies>(
60
- newFactories: MiddlewareFactory<D>[],
61
- has: (factory: MiddlewareFactory<D>) => boolean,
62
- ): void {
63
- for (const factory of newFactories) {
64
- if (has(factory)) {
65
- throw new Error(
66
- `[${LOGGER_CONTEXT}] Middleware factory already registered. ` +
67
- `To re-register, first unsubscribe the existing middleware. ` +
68
- `Factory: ${getFactoryName(factory)}`,
69
- );
70
- }
71
- }
72
- }
73
-
74
- /**
75
- * Validates that adding middleware won't exceed the hard limit.
76
- */
77
- export function validateMiddlewareLimit(
78
- currentCount: number,
79
- newCount: number,
80
- maxMiddleware: number = DEFAULT_LIMITS.maxMiddleware,
81
- ): void {
82
- if (maxMiddleware === 0) {
83
- return;
84
- }
85
-
86
- const totalSize = currentCount + newCount;
87
-
88
- if (totalSize > maxMiddleware) {
89
- throw new Error(
90
- `[${LOGGER_CONTEXT}] Middleware limit exceeded (${maxMiddleware}). ` +
91
- `Current: ${currentCount}, Attempting to add: ${newCount}. ` +
92
- `This indicates an architectural problem. Consider consolidating middleware.`,
93
- );
94
- }
95
- }
@@ -1,56 +0,0 @@
1
- // packages/real-router/modules/transition/executeMiddleware.ts
2
-
3
- import { logger } from "@real-router/logger";
4
- import { isState } from "type-guards";
5
-
6
- import { rethrowAsRouterError } from "./makeError";
7
- import { mergeStates } from "./mergeStates";
8
- import { processLifecycleResult } from "./processLifecycleResult";
9
- import { errorCodes } from "../../../constants";
10
- import { RouterError } from "../../../RouterError";
11
-
12
- import type { State, ActivationFn } from "@real-router/types";
13
-
14
- // Helper: processing middleware
15
- export const executeMiddleware = async (
16
- middlewareFunctions: ActivationFn[],
17
- toState: State,
18
- fromState: State | undefined,
19
- isCancelled: () => boolean,
20
- ): Promise<State> => {
21
- let currentState = toState;
22
-
23
- for (const middlewareFn of middlewareFunctions) {
24
- if (isCancelled()) {
25
- throw new RouterError(errorCodes.TRANSITION_CANCELLED);
26
- }
27
-
28
- try {
29
- const result = middlewareFn(currentState, fromState);
30
- const newState = await processLifecycleResult(result, currentState);
31
-
32
- // Optimization: Early return for undefined newState (most common case ~90%+)
33
- // This avoids isState() call and subsequent checks
34
- if (newState !== currentState && isState(newState)) {
35
- const hasChanged =
36
- newState.name !== currentState.name ||
37
- newState.params !== currentState.params ||
38
- newState.path !== currentState.path;
39
-
40
- if (hasChanged) {
41
- logger.error(
42
- "core:middleware",
43
- "Warning: State mutated during middleware execution",
44
- { from: currentState, to: newState },
45
- );
46
- }
47
-
48
- currentState = mergeStates(newState, currentState);
49
- }
50
- } catch (error: unknown) {
51
- rethrowAsRouterError(error, errorCodes.TRANSITION_ERR);
52
- }
53
- }
54
-
55
- return currentState;
56
- };
@@ -1,37 +0,0 @@
1
- // packages/real-router/modules/transition/makeError.ts
2
-
3
- import { wrapSyncError } from "./wrapSyncError";
4
- import { RouterError } from "../../../RouterError";
5
-
6
- // Helper: Creating an error with code
7
- export const makeError = (
8
- code: string,
9
- err?: RouterError,
10
- ): RouterError | undefined => {
11
- if (!err) {
12
- return undefined;
13
- }
14
-
15
- err.setCode(code);
16
-
17
- return err;
18
- };
19
-
20
- /**
21
- * Re-throws a caught error as a RouterError with the given error code.
22
- * If the error is already a RouterError, sets the code directly.
23
- * Otherwise wraps it with wrapSyncError metadata.
24
- */
25
- export function rethrowAsRouterError(
26
- error: unknown,
27
- errorCode: string,
28
- segment?: string,
29
- ): never {
30
- if (error instanceof RouterError) {
31
- error.setCode(errorCode);
32
-
33
- throw error;
34
- }
35
-
36
- throw new RouterError(errorCode, wrapSyncError(error, segment));
37
- }
@@ -1,54 +0,0 @@
1
- // packages/real-router/modules/transition/mergeStates.ts
2
-
3
- import type { Params, State, StateMeta } from "@real-router/types";
4
-
5
- /**
6
- * Merges two states with toState taking priority over fromState.
7
- *
8
- * Priority order for state fields: toState > fromState
9
- * Priority order for meta fields: toState.meta > fromState.meta > defaults
10
- *
11
- * Special case: meta.params are merged (not replaced):
12
- * { ...toState.meta.params, ...fromState.meta.params }
13
- *
14
- * @param toState - Target state (higher priority)
15
- * @param fromState - Source state (lower priority)
16
- * @returns New merged state object
17
- */
18
- export const mergeStates = (toState: State, fromState: State): State => {
19
- const toMeta = toState.meta;
20
- const fromMeta = fromState.meta;
21
-
22
- // Optimization #1: Conditional merge for params
23
- // Use spread only when both are defined
24
- const toParams = toMeta?.params;
25
- const fromParams = fromMeta?.params;
26
-
27
- // Both have params - need to merge; otherwise use whichever is defined
28
- const metaParams: Params =
29
- toParams && fromParams
30
- ? { ...toParams, ...fromParams }
31
- : (toParams ?? fromParams ?? {});
32
-
33
- // Optimization #2: Build meta with defaults, then apply fromMeta, then toMeta
34
- // Note: StateMeta can have custom fields added by guards/middleware, so we preserve them
35
- const resultMeta: StateMeta = {
36
- // Defaults first
37
- id: 1,
38
- options: {},
39
- // fromMeta fields (lower priority, may include custom fields)
40
- ...fromMeta,
41
- // toMeta fields (higher priority, may include custom fields)
42
- ...toMeta,
43
- // Explicitly set params to our merged version (override spread)
44
- params: metaParams,
45
- };
46
-
47
- // Optimization #4: Copy all toState fields (including custom ones)
48
- // then explicitly set meta to our merged version
49
- // Note: State can have custom fields added by middleware, so we must preserve them
50
- return {
51
- ...toState,
52
- meta: resultMeta,
53
- };
54
- };
@@ -1,69 +0,0 @@
1
- // packages/real-router/modules/transition/processLifecycleResult.ts
2
-
3
- import { isState } from "type-guards";
4
-
5
- import { errorCodes } from "../../../constants";
6
- import { RouterError } from "../../../RouterError";
7
-
8
- import type { SyncErrorMetadata } from "./wrapSyncError";
9
- import type { State, ActivationFn } from "@real-router/types";
10
-
11
- /**
12
- * Builds error metadata from a caught promise rejection.
13
- * Extracts message, stack, and cause from Error instances.
14
- */
15
- function buildErrorMetadata(
16
- error: unknown,
17
- errorData: SyncErrorMetadata,
18
- ): SyncErrorMetadata {
19
- if (error instanceof Error) {
20
- return {
21
- ...errorData,
22
- message: error.message,
23
- stack: error.stack,
24
- // Error.cause requires ES2022+ - safely access it if present
25
- ...("cause" in error &&
26
- error.cause !== undefined && { cause: error.cause }),
27
- };
28
- }
29
-
30
- if (error && typeof error === "object") {
31
- return { ...errorData, ...error };
32
- }
33
-
34
- return errorData;
35
- }
36
-
37
- // Helper: Lifecycle results Processing Function
38
- export const processLifecycleResult = async (
39
- result: ReturnType<ActivationFn>,
40
- currentState: State,
41
- ): Promise<State> => {
42
- if (result === undefined) {
43
- return currentState;
44
- }
45
-
46
- if (typeof result === "boolean") {
47
- if (result) {
48
- return currentState;
49
- } else {
50
- throw new RouterError(errorCodes.TRANSITION_ERR, {});
51
- }
52
- }
53
-
54
- if (isState(result)) {
55
- return result;
56
- }
57
-
58
- // Optimization: single try/catch instead of .then(onFulfill, onReject)
59
- try {
60
- const resVal = await (result as Promise<State | boolean | undefined>);
61
-
62
- return await processLifecycleResult(resVal, currentState);
63
- } catch (error: unknown) {
64
- throw new RouterError(
65
- errorCodes.TRANSITION_ERR,
66
- buildErrorMetadata(error, {}),
67
- );
68
- }
69
- };