@real-router/core 0.22.0 → 0.23.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.
Files changed (80) hide show
  1. package/README.md +1 -3
  2. package/dist/cjs/index.d.ts +1 -1
  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 +1 -1
  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 +7 -5
  11. package/src/Router.ts +1174 -0
  12. package/src/RouterError.ts +324 -0
  13. package/src/constants.ts +112 -0
  14. package/src/createRouter.ts +32 -0
  15. package/src/fsm/index.ts +5 -0
  16. package/src/fsm/routerFSM.ts +129 -0
  17. package/src/getNavigator.ts +15 -0
  18. package/src/helpers.ts +194 -0
  19. package/src/index.ts +46 -0
  20. package/src/namespaces/CloneNamespace/CloneNamespace.ts +120 -0
  21. package/src/namespaces/CloneNamespace/index.ts +3 -0
  22. package/src/namespaces/CloneNamespace/types.ts +46 -0
  23. package/src/namespaces/DependenciesNamespace/DependenciesNamespace.ts +250 -0
  24. package/src/namespaces/DependenciesNamespace/index.ts +3 -0
  25. package/src/namespaces/DependenciesNamespace/validators.ts +105 -0
  26. package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +272 -0
  27. package/src/namespaces/EventBusNamespace/index.ts +5 -0
  28. package/src/namespaces/EventBusNamespace/types.ts +11 -0
  29. package/src/namespaces/MiddlewareNamespace/MiddlewareNamespace.ts +206 -0
  30. package/src/namespaces/MiddlewareNamespace/index.ts +5 -0
  31. package/src/namespaces/MiddlewareNamespace/types.ts +28 -0
  32. package/src/namespaces/MiddlewareNamespace/validators.ts +96 -0
  33. package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +308 -0
  34. package/src/namespaces/NavigationNamespace/index.ts +5 -0
  35. package/src/namespaces/NavigationNamespace/transition/executeLifecycleHooks.ts +84 -0
  36. package/src/namespaces/NavigationNamespace/transition/executeMiddleware.ts +56 -0
  37. package/src/namespaces/NavigationNamespace/transition/index.ts +107 -0
  38. package/src/namespaces/NavigationNamespace/transition/makeError.ts +37 -0
  39. package/src/namespaces/NavigationNamespace/transition/mergeStates.ts +54 -0
  40. package/src/namespaces/NavigationNamespace/transition/processLifecycleResult.ts +81 -0
  41. package/src/namespaces/NavigationNamespace/transition/wrapSyncError.ts +82 -0
  42. package/src/namespaces/NavigationNamespace/types.ts +129 -0
  43. package/src/namespaces/NavigationNamespace/validators.ts +87 -0
  44. package/src/namespaces/OptionsNamespace/OptionsNamespace.ts +50 -0
  45. package/src/namespaces/OptionsNamespace/constants.ts +41 -0
  46. package/src/namespaces/OptionsNamespace/helpers.ts +51 -0
  47. package/src/namespaces/OptionsNamespace/index.ts +11 -0
  48. package/src/namespaces/OptionsNamespace/validators.ts +252 -0
  49. package/src/namespaces/PluginsNamespace/PluginsNamespace.ts +325 -0
  50. package/src/namespaces/PluginsNamespace/constants.ts +35 -0
  51. package/src/namespaces/PluginsNamespace/index.ts +7 -0
  52. package/src/namespaces/PluginsNamespace/types.ts +32 -0
  53. package/src/namespaces/PluginsNamespace/validators.ts +79 -0
  54. package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +389 -0
  55. package/src/namespaces/RouteLifecycleNamespace/index.ts +5 -0
  56. package/src/namespaces/RouteLifecycleNamespace/types.ts +17 -0
  57. package/src/namespaces/RouteLifecycleNamespace/validators.ts +65 -0
  58. package/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts +140 -0
  59. package/src/namespaces/RouterLifecycleNamespace/constants.ts +25 -0
  60. package/src/namespaces/RouterLifecycleNamespace/index.ts +5 -0
  61. package/src/namespaces/RouterLifecycleNamespace/types.ts +23 -0
  62. package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +1482 -0
  63. package/src/namespaces/RoutesNamespace/constants.ts +14 -0
  64. package/src/namespaces/RoutesNamespace/helpers.ts +532 -0
  65. package/src/namespaces/RoutesNamespace/index.ts +9 -0
  66. package/src/namespaces/RoutesNamespace/stateBuilder.ts +70 -0
  67. package/src/namespaces/RoutesNamespace/types.ts +82 -0
  68. package/src/namespaces/RoutesNamespace/validators.ts +331 -0
  69. package/src/namespaces/StateNamespace/StateNamespace.ts +317 -0
  70. package/src/namespaces/StateNamespace/helpers.ts +43 -0
  71. package/src/namespaces/StateNamespace/index.ts +5 -0
  72. package/src/namespaces/StateNamespace/types.ts +15 -0
  73. package/src/namespaces/index.ts +42 -0
  74. package/src/transitionPath.ts +441 -0
  75. package/src/typeGuards.ts +74 -0
  76. package/src/types.ts +194 -0
  77. package/src/wiring/RouterWiringBuilder.ts +235 -0
  78. package/src/wiring/index.ts +7 -0
  79. package/src/wiring/types.ts +53 -0
  80. package/src/wiring/wireRouter.ts +29 -0
@@ -0,0 +1,105 @@
1
+ // packages/core/src/namespaces/DependenciesNamespace/validators.ts
2
+
3
+ /**
4
+ * Static validation functions for DependenciesNamespace.
5
+ * Called by Router facade before instance methods.
6
+ *
7
+ * Extracted from DependenciesNamespace class for better separation of concerns.
8
+ */
9
+
10
+ import { getTypeDescription } from "type-guards";
11
+
12
+ import { DEFAULT_LIMITS } from "../../constants";
13
+
14
+ /**
15
+ * Validates that dependency name is a string.
16
+ * Called by facade before get/remove/has operations.
17
+ */
18
+ export function validateDependencyName(
19
+ name: unknown,
20
+ methodName: string,
21
+ ): asserts name is string {
22
+ if (typeof name !== "string") {
23
+ throw new TypeError(
24
+ `[router.${methodName}]: dependency name must be a string, got ${typeof name}`,
25
+ );
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Validates setDependency name argument.
31
+ * Value is not validated - any value is valid.
32
+ */
33
+ export function validateSetDependencyArgs(
34
+ name: unknown,
35
+ ): asserts name is string {
36
+ if (typeof name !== "string") {
37
+ throw new TypeError(
38
+ `[router.setDependency]: dependency name must be a string, got ${typeof name}`,
39
+ );
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Validates that dependencies object is a plain object without getters.
45
+ * Called by facade before setMultiple/constructor.
46
+ */
47
+ export function validateDependenciesObject(
48
+ deps: unknown,
49
+ methodName: string,
50
+ ): asserts deps is Record<string, unknown> {
51
+ // Reject non-plain objects (classes, Date, Map, Array)
52
+ if (!(deps && typeof deps === "object" && deps.constructor === Object)) {
53
+ throw new TypeError(
54
+ `[router.${methodName}] Invalid argument: expected plain object, received ${getTypeDescription(deps)}`,
55
+ );
56
+ }
57
+
58
+ // Getters can throw, return different values, or have side effects
59
+ for (const key in deps) {
60
+ if (Object.getOwnPropertyDescriptor(deps, key)?.get) {
61
+ throw new TypeError(
62
+ `[router.${methodName}] Getters not allowed: "${key}"`,
63
+ );
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Validates that dependency exists (not undefined).
70
+ * Throws ReferenceError if dependency is not found.
71
+ */
72
+ export function validateDependencyExists(
73
+ value: unknown,
74
+ dependencyName: string,
75
+ ): asserts value is NonNullable<unknown> {
76
+ if (value === undefined) {
77
+ throw new ReferenceError(
78
+ `[router.getDependency]: dependency "${dependencyName}" not found`,
79
+ );
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Validates that adding dependencies won't exceed the hard limit.
85
+ * Called before bulk operations to ensure atomicity.
86
+ */
87
+ export function validateDependencyLimit(
88
+ currentCount: number,
89
+ newCount: number,
90
+ methodName: string,
91
+ maxDependencies: number = DEFAULT_LIMITS.maxDependencies,
92
+ ): void {
93
+ if (maxDependencies === 0) {
94
+ return;
95
+ }
96
+
97
+ const totalCount = currentCount + newCount;
98
+
99
+ if (totalCount >= maxDependencies) {
100
+ throw new Error(
101
+ `[router.${methodName}] Dependency limit exceeded (${maxDependencies}). ` +
102
+ `Current: ${totalCount}. This is likely a bug in your code.`,
103
+ );
104
+ }
105
+ }
@@ -0,0 +1,272 @@
1
+ // packages/core/src/namespaces/EventBusNamespace/EventBusNamespace.ts
2
+
3
+ import { events, validEventNames } from "../../constants";
4
+ import { routerEvents, routerStates } from "../../fsm";
5
+
6
+ import type { EventBusOptions } from "./types";
7
+ import type { RouterEvent, RouterPayloads, RouterState } from "../../fsm";
8
+ import type { RouterError } from "../../RouterError";
9
+ import type { EventMethodMap, RouterEventMap } from "../../types";
10
+ import type { FSM } from "@real-router/fsm";
11
+ import type {
12
+ EventName,
13
+ NavigationOptions,
14
+ Plugin,
15
+ State,
16
+ SubscribeFn,
17
+ Unsubscribe,
18
+ } from "@real-router/types";
19
+ import type { EventEmitter } from "event-emitter";
20
+
21
+ export class EventBusNamespace {
22
+ readonly #fsm: FSM<RouterState, RouterEvent, null, RouterPayloads>;
23
+ readonly #emitter: EventEmitter<RouterEventMap>;
24
+
25
+ #currentToState: State | undefined;
26
+
27
+ constructor(options: EventBusOptions) {
28
+ this.#fsm = options.routerFSM;
29
+ this.#emitter = options.emitter;
30
+ this.#currentToState = undefined;
31
+ this.#setupFSMActions();
32
+ }
33
+
34
+ static validateEventName(eventName: unknown): void {
35
+ if (!validEventNames.has(eventName as EventName)) {
36
+ throw new Error(`Invalid event name: ${String(eventName)}`);
37
+ }
38
+ }
39
+
40
+ static validateListenerArgs<E extends EventName>(
41
+ eventName: E,
42
+ cb: Plugin[EventMethodMap[E]],
43
+ ): void {
44
+ EventBusNamespace.validateEventName(eventName);
45
+
46
+ if (typeof cb !== "function") {
47
+ throw new TypeError(
48
+ `Expected callback to be a function for event ${eventName}`,
49
+ );
50
+ }
51
+ }
52
+
53
+ static validateSubscribeListener(listener: unknown): void {
54
+ if (typeof listener !== "function") {
55
+ throw new TypeError(
56
+ "[router.subscribe] Expected a function. " +
57
+ "For Observable pattern use @real-router/rx package",
58
+ );
59
+ }
60
+ }
61
+
62
+ emitRouterStart(): void {
63
+ this.#emitter.emit(events.ROUTER_START);
64
+ }
65
+
66
+ emitRouterStop(): void {
67
+ this.#emitter.emit(events.ROUTER_STOP);
68
+ }
69
+
70
+ emitTransitionStart(toState: State, fromState?: State): void {
71
+ this.#emitter.emit(events.TRANSITION_START, toState, fromState);
72
+ }
73
+
74
+ emitTransitionSuccess(
75
+ toState: State,
76
+ fromState?: State,
77
+ opts?: NavigationOptions,
78
+ ): void {
79
+ this.#emitter.emit(events.TRANSITION_SUCCESS, toState, fromState, opts);
80
+ }
81
+
82
+ emitTransitionError(
83
+ toState?: State,
84
+ fromState?: State,
85
+ error?: RouterError,
86
+ ): void {
87
+ this.#emitter.emit(events.TRANSITION_ERROR, toState, fromState, error);
88
+ }
89
+
90
+ emitTransitionCancel(toState: State, fromState?: State): void {
91
+ this.#emitter.emit(events.TRANSITION_CANCEL, toState, fromState);
92
+ }
93
+
94
+ sendStart(): void {
95
+ this.#fsm.send(routerEvents.START);
96
+ }
97
+
98
+ sendStop(): void {
99
+ this.#fsm.send(routerEvents.STOP);
100
+ }
101
+
102
+ sendDispose(): void {
103
+ this.#fsm.send(routerEvents.DISPOSE);
104
+ }
105
+
106
+ completeStart(): void {
107
+ this.#fsm.send(routerEvents.STARTED);
108
+ }
109
+
110
+ beginTransition(toState: State, fromState?: State): void {
111
+ this.#currentToState = toState;
112
+ this.#fsm.send(routerEvents.NAVIGATE, { toState, fromState });
113
+ }
114
+
115
+ completeTransition(
116
+ state: State,
117
+ fromState?: State,
118
+ opts: NavigationOptions = {},
119
+ ): void {
120
+ this.#fsm.send(routerEvents.COMPLETE, {
121
+ state,
122
+ fromState,
123
+ opts,
124
+ });
125
+ this.#currentToState = undefined;
126
+ }
127
+
128
+ failTransition(toState?: State, fromState?: State, error?: unknown): void {
129
+ this.#fsm.send(routerEvents.FAIL, { toState, fromState, error });
130
+ this.#currentToState = undefined;
131
+ }
132
+
133
+ cancelTransition(toState: State, fromState?: State): void {
134
+ this.#fsm.send(routerEvents.CANCEL, { toState, fromState });
135
+ this.#currentToState = undefined;
136
+ }
137
+
138
+ emitOrFailTransitionError(
139
+ toState?: State,
140
+ fromState?: State,
141
+ error?: unknown,
142
+ ): void {
143
+ if (this.#fsm.getState() === routerStates.READY) {
144
+ this.#fsm.send(routerEvents.FAIL, { toState, fromState, error });
145
+ } else {
146
+ // TRANSITIONING: concurrent navigation with invalid args.
147
+ // Direct emit to avoid disturbing the ongoing transition.
148
+ this.emitTransitionError(toState, fromState, error as RouterError);
149
+ }
150
+ }
151
+
152
+ canBeginTransition(): boolean {
153
+ return this.#fsm.canSend(routerEvents.NAVIGATE);
154
+ }
155
+
156
+ canStart(): boolean {
157
+ return this.#fsm.canSend(routerEvents.START);
158
+ }
159
+
160
+ canCancel(): boolean {
161
+ return this.#fsm.canSend(routerEvents.CANCEL);
162
+ }
163
+
164
+ isActive(): boolean {
165
+ const s = this.#fsm.getState();
166
+
167
+ return s !== routerStates.IDLE && s !== routerStates.DISPOSED;
168
+ }
169
+
170
+ isDisposed(): boolean {
171
+ return this.#fsm.getState() === routerStates.DISPOSED;
172
+ }
173
+
174
+ isTransitioning(): boolean {
175
+ return this.#fsm.getState() === routerStates.TRANSITIONING;
176
+ }
177
+
178
+ isReady(): boolean {
179
+ return this.#fsm.getState() === routerStates.READY;
180
+ }
181
+
182
+ getCurrentToState(): State | undefined {
183
+ return this.#currentToState;
184
+ }
185
+
186
+ addEventListener<E extends EventName>(
187
+ eventName: E,
188
+ cb: Plugin[EventMethodMap[E]],
189
+ ): Unsubscribe {
190
+ return this.#emitter.on(
191
+ eventName,
192
+ cb as (...args: RouterEventMap[typeof eventName]) => void,
193
+ );
194
+ }
195
+
196
+ subscribe(listener: SubscribeFn): Unsubscribe {
197
+ return this.#emitter.on(
198
+ events.TRANSITION_SUCCESS,
199
+ (toState: State, fromState?: State) => {
200
+ listener({ route: toState, previousRoute: fromState });
201
+ },
202
+ );
203
+ }
204
+
205
+ clearAll(): void {
206
+ this.#emitter.clearAll();
207
+ }
208
+
209
+ setLimits(limits: {
210
+ maxListeners: number;
211
+ warnListeners: number;
212
+ maxEventDepth: number;
213
+ }): void {
214
+ this.#emitter.setLimits(limits);
215
+ }
216
+
217
+ cancelTransitionIfRunning(fromState: State | undefined): void {
218
+ if (!this.canCancel()) {
219
+ return;
220
+ }
221
+
222
+ this.cancelTransition(this.#currentToState!, fromState); // eslint-disable-line @typescript-eslint/no-non-null-assertion -- guaranteed set before TRANSITIONING
223
+ }
224
+
225
+ #setupFSMActions(): void {
226
+ const fsm = this.#fsm;
227
+
228
+ fsm.on(routerStates.STARTING, routerEvents.STARTED, () => {
229
+ this.emitRouterStart();
230
+ });
231
+
232
+ fsm.on(routerStates.READY, routerEvents.STOP, () => {
233
+ this.emitRouterStop();
234
+ });
235
+
236
+ fsm.on(routerStates.READY, routerEvents.NAVIGATE, (params) => {
237
+ this.emitTransitionStart(params.toState, params.fromState);
238
+ });
239
+
240
+ fsm.on(routerStates.TRANSITIONING, routerEvents.COMPLETE, (params) => {
241
+ this.emitTransitionSuccess(params.state, params.fromState, params.opts);
242
+ });
243
+
244
+ fsm.on(routerStates.TRANSITIONING, routerEvents.CANCEL, (params) => {
245
+ this.emitTransitionCancel(params.toState, params.fromState);
246
+ });
247
+
248
+ fsm.on(routerStates.STARTING, routerEvents.FAIL, (params) => {
249
+ this.emitTransitionError(
250
+ params.toState,
251
+ params.fromState,
252
+ params.error as RouterError | undefined,
253
+ );
254
+ });
255
+
256
+ fsm.on(routerStates.READY, routerEvents.FAIL, (params) => {
257
+ this.emitTransitionError(
258
+ params.toState,
259
+ params.fromState,
260
+ params.error as RouterError | undefined,
261
+ );
262
+ });
263
+
264
+ fsm.on(routerStates.TRANSITIONING, routerEvents.FAIL, (params) => {
265
+ this.emitTransitionError(
266
+ params.toState,
267
+ params.fromState,
268
+ params.error as RouterError | undefined,
269
+ );
270
+ });
271
+ }
272
+ }
@@ -0,0 +1,5 @@
1
+ // packages/core/src/namespaces/EventBusNamespace/index.ts
2
+
3
+ export { EventBusNamespace } from "./EventBusNamespace";
4
+
5
+ export type { EventBusOptions } from "./types";
@@ -0,0 +1,11 @@
1
+ // packages/core/src/namespaces/EventBusNamespace/types.ts
2
+
3
+ import type { RouterEvent, RouterPayloads, RouterState } from "../../fsm";
4
+ import type { RouterEventMap } from "../../types";
5
+ import type { FSM } from "@real-router/fsm";
6
+ import type { EventEmitter } from "event-emitter";
7
+
8
+ export interface EventBusOptions {
9
+ routerFSM: FSM<RouterState, RouterEvent, null, RouterPayloads>;
10
+ emitter: EventEmitter<RouterEventMap>;
11
+ }
@@ -0,0 +1,206 @@
1
+ // packages/core/src/namespaces/MiddlewareNamespace/MiddlewareNamespace.ts
2
+
3
+ import { logger } from "@real-router/logger";
4
+
5
+ import {
6
+ validateMiddleware,
7
+ validateMiddlewareLimit,
8
+ validateNoDuplicates,
9
+ validateUseMiddlewareArgs,
10
+ } from "./validators";
11
+ import { DEFAULT_LIMITS } from "../../constants";
12
+ import { computeThresholds } from "../../helpers";
13
+
14
+ import type { InitializedMiddleware, MiddlewareDependencies } from "./types";
15
+ import type { Router } from "../../Router";
16
+ import type { Limits, MiddlewareFactory } from "../../types";
17
+ import type {
18
+ DefaultDependencies,
19
+ Middleware,
20
+ Unsubscribe,
21
+ } from "@real-router/types";
22
+
23
+ /**
24
+ * Independent namespace for managing middleware.
25
+ *
26
+ * Static methods handle validation (called by facade).
27
+ * Instance methods handle storage and business logic.
28
+ */
29
+ export class MiddlewareNamespace<
30
+ Dependencies extends DefaultDependencies = DefaultDependencies,
31
+ > {
32
+ readonly #factories = new Set<MiddlewareFactory<Dependencies>>();
33
+ readonly #factoryToMiddleware = new Map<
34
+ MiddlewareFactory<Dependencies>,
35
+ Middleware
36
+ >();
37
+
38
+ #router!: Router<Dependencies>;
39
+ #deps!: MiddlewareDependencies<Dependencies>;
40
+ #limits: Limits = DEFAULT_LIMITS;
41
+
42
+ // =========================================================================
43
+ // Static validation methods (called by facade before instance methods)
44
+ // Proxy to functions in validators.ts for separation of concerns
45
+ // =========================================================================
46
+
47
+ static validateUseMiddlewareArgs<D extends DefaultDependencies>(
48
+ middlewares: unknown[],
49
+ ): asserts middlewares is MiddlewareFactory<D>[] {
50
+ validateUseMiddlewareArgs<D>(middlewares);
51
+ }
52
+
53
+ static validateMiddleware<D extends DefaultDependencies>(
54
+ middleware: unknown,
55
+ factory: MiddlewareFactory<D>,
56
+ ): asserts middleware is Middleware {
57
+ validateMiddleware<D>(middleware, factory);
58
+ }
59
+
60
+ static validateNoDuplicates<D extends DefaultDependencies>(
61
+ newFactories: MiddlewareFactory<D>[],
62
+ existingFactories: MiddlewareFactory<D>[],
63
+ ): void {
64
+ validateNoDuplicates<D>(newFactories, existingFactories);
65
+ }
66
+
67
+ static validateMiddlewareLimit(
68
+ currentCount: number,
69
+ newCount: number,
70
+ maxMiddleware?: number,
71
+ ): void {
72
+ validateMiddlewareLimit(currentCount, newCount, maxMiddleware);
73
+ }
74
+
75
+ // =========================================================================
76
+ // Dependency injection
77
+ // =========================================================================
78
+
79
+ setRouter(router: Router<Dependencies>): void {
80
+ this.#router = router;
81
+ }
82
+
83
+ setDependencies(deps: MiddlewareDependencies<Dependencies>): void {
84
+ this.#deps = deps;
85
+ }
86
+
87
+ setLimits(limits: Limits): void {
88
+ this.#limits = limits;
89
+ }
90
+
91
+ // =========================================================================
92
+ // Instance methods (trust input - already validated by facade)
93
+ // =========================================================================
94
+
95
+ /**
96
+ * Returns the current number of registered middleware.
97
+ */
98
+ count(): number {
99
+ return this.#factories.size;
100
+ }
101
+
102
+ /**
103
+ * Initializes middleware factories without committing to storage.
104
+ * Returns array of initialized middleware for validation by facade.
105
+ *
106
+ * @param factories - Already validated by facade
107
+ */
108
+ initialize(
109
+ ...factories: MiddlewareFactory<Dependencies>[]
110
+ ): InitializedMiddleware<Dependencies>[] {
111
+ const initialized: InitializedMiddleware<Dependencies>[] = [];
112
+
113
+ for (const factory of factories) {
114
+ // Middleware factories receive full router as part of their public API
115
+ const middleware = factory(this.#router, this.#deps.getDependency);
116
+
117
+ initialized.push({ factory, middleware });
118
+ }
119
+
120
+ return initialized;
121
+ }
122
+
123
+ /**
124
+ * Commits initialized middleware to storage.
125
+ * Returns unsubscribe function to remove all added middleware.
126
+ *
127
+ * @param initialized - Already validated by facade
128
+ */
129
+ commit(initialized: InitializedMiddleware<Dependencies>[]): Unsubscribe {
130
+ // Check count thresholds and log warnings if needed
131
+ this.#checkCountThresholds(initialized.length);
132
+
133
+ // Add to storage
134
+ for (const { factory, middleware } of initialized) {
135
+ this.#factories.add(factory);
136
+ this.#factoryToMiddleware.set(factory, middleware);
137
+ }
138
+
139
+ // Return unsubscribe function specific to THIS call's middleware
140
+ let unsubscribed = false;
141
+
142
+ return (): void => {
143
+ if (unsubscribed) {
144
+ return;
145
+ }
146
+
147
+ unsubscribed = true;
148
+
149
+ for (const { factory } of initialized) {
150
+ this.#factories.delete(factory);
151
+ this.#factoryToMiddleware.delete(factory);
152
+ }
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Returns a copy of registered middleware factories.
158
+ * Preserves insertion order.
159
+ */
160
+ getFactories(): MiddlewareFactory<Dependencies>[] {
161
+ return [...this.#factories];
162
+ }
163
+
164
+ /**
165
+ * Returns the actual middleware functions in execution order.
166
+ */
167
+ getFunctions(): Middleware[] {
168
+ return [...this.#factoryToMiddleware.values()];
169
+ }
170
+
171
+ clearAll(): void {
172
+ this.#factories.clear();
173
+ this.#factoryToMiddleware.clear();
174
+ }
175
+
176
+ // =========================================================================
177
+ // Private methods
178
+ // =========================================================================
179
+
180
+ #checkCountThresholds(newCount: number): void {
181
+ const maxMiddleware = this.#limits.maxMiddleware;
182
+
183
+ if (maxMiddleware === 0) {
184
+ return;
185
+ }
186
+
187
+ const totalSize = newCount + this.#factories.size;
188
+
189
+ const { warn, error } = computeThresholds(maxMiddleware);
190
+
191
+ if (totalSize >= error) {
192
+ logger.error(
193
+ "router.useMiddleware",
194
+ `${totalSize} middleware registered! ` +
195
+ `This is excessive and will impact performance. ` +
196
+ `Hard limit at ${maxMiddleware}.`,
197
+ );
198
+ } else if (totalSize >= warn) {
199
+ logger.warn(
200
+ "router.useMiddleware",
201
+ `${totalSize} middleware registered. ` +
202
+ `Consider if all are necessary.`,
203
+ );
204
+ }
205
+ }
206
+ }
@@ -0,0 +1,5 @@
1
+ // packages/core/src/namespaces/MiddlewareNamespace/index.ts
2
+
3
+ export { MiddlewareNamespace } from "./MiddlewareNamespace";
4
+
5
+ export type { MiddlewareDependencies } from "./types";
@@ -0,0 +1,28 @@
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
+ }