@morojs/moro 1.6.0 → 1.6.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/dist/core/config/config-sources.js +4 -0
- package/dist/core/config/config-sources.js.map +1 -1
- package/dist/core/config/config-validator.js +3 -0
- package/dist/core/config/config-validator.js.map +1 -1
- package/dist/core/config/file-loader.js +3 -1
- package/dist/core/config/file-loader.js.map +1 -1
- package/dist/core/config/schema.js +4 -1
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/events/event-bus.js +1 -1
- package/dist/core/events/event-bus.js.map +1 -1
- package/dist/core/framework.d.ts +1 -1
- package/dist/core/framework.js +13 -7
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +55 -15
- package/dist/core/http/http-server.js +70 -146
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/index.d.ts +1 -1
- package/dist/core/http/index.js +1 -1
- package/dist/core/http/index.js.map +1 -1
- package/dist/core/http/uws-http-server.d.ts +4 -22
- package/dist/core/http/uws-http-server.js +43 -208
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.d.ts +1 -1
- package/dist/core/networking/adapters/uws-adapter.js +1 -1
- package/dist/core/pooling/object-pool-manager.d.ts +140 -0
- package/dist/core/pooling/object-pool-manager.js +502 -0
- package/dist/core/pooling/object-pool-manager.js.map +1 -0
- package/dist/core/routing/app-integration.d.ts +12 -10
- package/dist/core/routing/app-integration.js +43 -74
- package/dist/core/routing/app-integration.js.map +1 -1
- package/dist/core/routing/index.d.ts +15 -29
- package/dist/core/routing/index.js +43 -390
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/routing/path-matcher.d.ts +67 -0
- package/dist/core/routing/path-matcher.js +182 -0
- package/dist/core/routing/path-matcher.js.map +1 -0
- package/dist/core/{http → routing}/router.d.ts +21 -9
- package/dist/core/routing/router.js +68 -0
- package/dist/core/routing/router.js.map +1 -0
- package/dist/core/routing/unified-router.d.ts +148 -0
- package/dist/core/routing/unified-router.js +684 -0
- package/dist/core/routing/unified-router.js.map +1 -0
- package/dist/moro.d.ts +10 -7
- package/dist/moro.js +90 -41
- package/dist/moro.js.map +1 -1
- package/dist/types/config.d.ts +3 -0
- package/package.json +1 -1
- package/src/core/config/config-sources.ts +4 -0
- package/src/core/config/config-validator.ts +3 -0
- package/src/core/config/file-loader.ts +4 -1
- package/src/core/config/schema.ts +4 -1
- package/src/core/events/event-bus.ts +1 -1
- package/src/core/framework.ts +14 -9
- package/src/core/http/http-server.ts +76 -161
- package/src/core/http/index.ts +1 -1
- package/src/core/http/uws-http-server.ts +43 -246
- package/src/core/networking/adapters/uws-adapter.ts +1 -1
- package/src/core/pooling/object-pool-manager.ts +630 -0
- package/src/core/routing/app-integration.ts +57 -109
- package/src/core/routing/index.ts +62 -473
- package/src/core/routing/path-matcher.ts +222 -0
- package/src/core/routing/router.ts +97 -0
- package/src/core/routing/unified-router.ts +870 -0
- package/src/moro.ts +107 -57
- package/src/types/config.ts +3 -0
- package/dist/core/http/router.js +0 -183
- package/dist/core/http/router.js.map +0 -1
- package/src/core/http/router.ts +0 -230
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
// Unified Router - Consolidates Intelligent Router and Core Router
|
|
2
|
+
// Combines best features from both systems with zero breaking changes
|
|
3
|
+
|
|
4
|
+
import { PathMatcher, CompiledPath } from './path-matcher.js';
|
|
5
|
+
import { ObjectPoolManager } from '../pooling/object-pool-manager.js';
|
|
6
|
+
import { createFrameworkLogger } from '../logger/index.js';
|
|
7
|
+
import { HttpRequest, HttpResponse } from '../../types/http.js';
|
|
8
|
+
import { ValidationSchema } from '../validation/schema-interface.js';
|
|
9
|
+
|
|
10
|
+
const logger = createFrameworkLogger('UnifiedRouter');
|
|
11
|
+
|
|
12
|
+
// ===== Types =====
|
|
13
|
+
|
|
14
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
15
|
+
export type RouteHandler<T = any> = (req: HttpRequest, res: HttpResponse) => T | Promise<T>;
|
|
16
|
+
export type Middleware = (
|
|
17
|
+
req: HttpRequest,
|
|
18
|
+
res: HttpResponse,
|
|
19
|
+
next: () => void
|
|
20
|
+
) => void | Promise<void>;
|
|
21
|
+
|
|
22
|
+
export interface ValidationConfig {
|
|
23
|
+
body?: ValidationSchema;
|
|
24
|
+
query?: ValidationSchema;
|
|
25
|
+
params?: ValidationSchema;
|
|
26
|
+
headers?: ValidationSchema;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AuthConfig {
|
|
30
|
+
roles?: string[];
|
|
31
|
+
permissions?: string[];
|
|
32
|
+
optional?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RateLimitConfig {
|
|
36
|
+
requests: number;
|
|
37
|
+
window: number;
|
|
38
|
+
skipSuccessfulRequests?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CacheConfig {
|
|
42
|
+
ttl: number;
|
|
43
|
+
key?: string;
|
|
44
|
+
tags?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface MiddlewarePhases {
|
|
48
|
+
before?: Middleware[];
|
|
49
|
+
after?: Middleware[];
|
|
50
|
+
transform?: Middleware[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RouteSchema {
|
|
54
|
+
method: HttpMethod;
|
|
55
|
+
path: string;
|
|
56
|
+
handler: RouteHandler;
|
|
57
|
+
validation?: ValidationConfig;
|
|
58
|
+
auth?: AuthConfig;
|
|
59
|
+
rateLimit?: RateLimitConfig;
|
|
60
|
+
cache?: CacheConfig;
|
|
61
|
+
middleware?: MiddlewarePhases | Middleware[];
|
|
62
|
+
description?: string;
|
|
63
|
+
tags?: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Internal route representation
|
|
67
|
+
interface InternalRoute {
|
|
68
|
+
schema: RouteSchema;
|
|
69
|
+
compiledPath: CompiledPath;
|
|
70
|
+
isFastPath: boolean; // No middleware, auth, validation, rate limiting
|
|
71
|
+
executionOrder: string[]; // Ordered list of execution phases
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ===== Route Builder (Chainable API) =====
|
|
75
|
+
|
|
76
|
+
export class RouteBuilder {
|
|
77
|
+
private schema: Partial<RouteSchema>;
|
|
78
|
+
private router: UnifiedRouter;
|
|
79
|
+
|
|
80
|
+
constructor(method: HttpMethod, path: string, router: UnifiedRouter) {
|
|
81
|
+
this.schema = {
|
|
82
|
+
method,
|
|
83
|
+
path,
|
|
84
|
+
middleware: {} as MiddlewarePhases,
|
|
85
|
+
};
|
|
86
|
+
this.router = router;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Validation methods
|
|
90
|
+
validate(config: ValidationConfig): this {
|
|
91
|
+
this.schema.validation = { ...this.schema.validation, ...config };
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
body<T>(schema: ValidationSchema<T>): this {
|
|
96
|
+
if (!this.schema.validation) this.schema.validation = {};
|
|
97
|
+
this.schema.validation.body = schema;
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
query<T>(schema: ValidationSchema<T>): this {
|
|
102
|
+
if (!this.schema.validation) this.schema.validation = {};
|
|
103
|
+
this.schema.validation.query = schema;
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
params<T>(schema: ValidationSchema<T>): this {
|
|
108
|
+
if (!this.schema.validation) this.schema.validation = {};
|
|
109
|
+
this.schema.validation.params = schema;
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
headers<T>(schema: ValidationSchema<T>): this {
|
|
114
|
+
if (!this.schema.validation) this.schema.validation = {};
|
|
115
|
+
this.schema.validation.headers = schema;
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Security methods
|
|
120
|
+
auth(config: AuthConfig): this {
|
|
121
|
+
this.schema.auth = config;
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
rateLimit(config: RateLimitConfig): this {
|
|
126
|
+
this.schema.rateLimit = config;
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Caching
|
|
131
|
+
cache(config: CacheConfig): this {
|
|
132
|
+
this.schema.cache = config;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Custom middleware
|
|
137
|
+
before(...middleware: Middleware[]): this {
|
|
138
|
+
if (!this.schema.middleware) this.schema.middleware = {};
|
|
139
|
+
const phases = this.schema.middleware as MiddlewarePhases;
|
|
140
|
+
phases.before = [...(phases.before || []), ...middleware];
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
after(...middleware: Middleware[]): this {
|
|
145
|
+
if (!this.schema.middleware) this.schema.middleware = {};
|
|
146
|
+
const phases = this.schema.middleware as MiddlewarePhases;
|
|
147
|
+
phases.after = [...(phases.after || []), ...middleware];
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
transform(...middleware: Middleware[]): this {
|
|
152
|
+
if (!this.schema.middleware) this.schema.middleware = {};
|
|
153
|
+
const phases = this.schema.middleware as MiddlewarePhases;
|
|
154
|
+
phases.transform = [...(phases.transform || []), ...middleware];
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
use(...middleware: Middleware[]): this {
|
|
159
|
+
return this.after(...middleware);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Metadata
|
|
163
|
+
describe(description: string): this {
|
|
164
|
+
this.schema.description = description;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
tag(...tags: string[]): this {
|
|
169
|
+
this.schema.tags = [...(this.schema.tags || []), ...tags];
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Terminal method
|
|
174
|
+
handler<T>(handler: RouteHandler<T>): void {
|
|
175
|
+
if (!handler) {
|
|
176
|
+
throw new Error('Handler is required');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const completeSchema: RouteSchema = {
|
|
180
|
+
...(this.schema as RouteSchema),
|
|
181
|
+
handler,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
this.router.registerRoute(completeSchema);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ===== Unified Router =====
|
|
189
|
+
|
|
190
|
+
export class UnifiedRouter {
|
|
191
|
+
private static instance: UnifiedRouter | null = null;
|
|
192
|
+
|
|
193
|
+
private readonly poolManager = ObjectPoolManager.getInstance();
|
|
194
|
+
|
|
195
|
+
// Route storage optimized for different access patterns
|
|
196
|
+
private staticRoutes = new Map<string, InternalRoute>(); // O(1) lookup: "GET:/api/users"
|
|
197
|
+
private dynamicRoutesBySegments = new Map<number, InternalRoute[]>(); // Grouped by segment count
|
|
198
|
+
private fastPathRoutes = new Set<InternalRoute>(); // Routes with no middleware
|
|
199
|
+
private allRoutes: InternalRoute[] = []; // For iteration/inspection
|
|
200
|
+
|
|
201
|
+
// Statistics
|
|
202
|
+
private stats = {
|
|
203
|
+
totalRoutes: 0,
|
|
204
|
+
staticRoutes: 0,
|
|
205
|
+
dynamicRoutes: 0,
|
|
206
|
+
fastPathRoutes: 0,
|
|
207
|
+
requestCount: 0,
|
|
208
|
+
fastPathHits: 0,
|
|
209
|
+
staticHits: 0,
|
|
210
|
+
dynamicHits: 0,
|
|
211
|
+
cacheHits: 0,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
constructor() {
|
|
215
|
+
logger.debug('UnifiedRouter initialized', 'Initialization');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get singleton instance (optional - can still create new instances)
|
|
220
|
+
*/
|
|
221
|
+
static getInstance(): UnifiedRouter {
|
|
222
|
+
if (!this.instance) {
|
|
223
|
+
this.instance = new UnifiedRouter();
|
|
224
|
+
logger.info(`UnifiedRouter initialized (PID: ${process.pid})`, 'Router');
|
|
225
|
+
}
|
|
226
|
+
return this.instance;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Reset singleton (useful for testing)
|
|
231
|
+
*/
|
|
232
|
+
static reset(): void {
|
|
233
|
+
if (this.instance) {
|
|
234
|
+
this.instance.clearAllRoutes();
|
|
235
|
+
}
|
|
236
|
+
this.instance = null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Clear all routes (useful for testing)
|
|
241
|
+
*/
|
|
242
|
+
clearAllRoutes(): void {
|
|
243
|
+
this.staticRoutes.clear();
|
|
244
|
+
this.dynamicRoutesBySegments.clear();
|
|
245
|
+
this.fastPathRoutes.clear();
|
|
246
|
+
this.allRoutes = [];
|
|
247
|
+
this.stats = {
|
|
248
|
+
totalRoutes: 0,
|
|
249
|
+
staticRoutes: 0,
|
|
250
|
+
dynamicRoutes: 0,
|
|
251
|
+
fastPathRoutes: 0,
|
|
252
|
+
requestCount: 0,
|
|
253
|
+
fastPathHits: 0,
|
|
254
|
+
staticHits: 0,
|
|
255
|
+
dynamicHits: 0,
|
|
256
|
+
cacheHits: 0,
|
|
257
|
+
};
|
|
258
|
+
logger.debug('UnifiedRouter routes cleared', 'Reset');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ===== Route Registration =====
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Register a route (internal method)
|
|
265
|
+
*/
|
|
266
|
+
registerRoute(schema: RouteSchema): void {
|
|
267
|
+
// Compile path pattern
|
|
268
|
+
const compiledPath = PathMatcher.compile(schema.path);
|
|
269
|
+
|
|
270
|
+
// Determine if this is a fast-path route
|
|
271
|
+
const isFastPath = this.isFastPathRoute(schema);
|
|
272
|
+
|
|
273
|
+
// Determine execution order
|
|
274
|
+
const executionOrder = this.buildExecutionOrder(schema);
|
|
275
|
+
|
|
276
|
+
const route: InternalRoute = {
|
|
277
|
+
schema,
|
|
278
|
+
compiledPath,
|
|
279
|
+
isFastPath,
|
|
280
|
+
executionOrder,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Store in appropriate structures
|
|
284
|
+
if (compiledPath.isStatic) {
|
|
285
|
+
const key = `${schema.method}:${schema.path}`;
|
|
286
|
+
this.staticRoutes.set(key, route);
|
|
287
|
+
this.stats.staticRoutes++;
|
|
288
|
+
} else {
|
|
289
|
+
// Group by segment count for faster matching
|
|
290
|
+
const segmentCount = compiledPath.segments;
|
|
291
|
+
if (!this.dynamicRoutesBySegments.has(segmentCount)) {
|
|
292
|
+
this.dynamicRoutesBySegments.set(segmentCount, []);
|
|
293
|
+
}
|
|
294
|
+
this.dynamicRoutesBySegments.get(segmentCount)!.push(route);
|
|
295
|
+
this.stats.dynamicRoutes++;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (isFastPath) {
|
|
299
|
+
this.fastPathRoutes.add(route);
|
|
300
|
+
this.stats.fastPathRoutes++;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this.allRoutes.push(route);
|
|
304
|
+
this.stats.totalRoutes++;
|
|
305
|
+
|
|
306
|
+
logger.info(
|
|
307
|
+
`Registered route: ${schema.method} ${schema.path} (PID: ${process.pid}, total: ${this.stats.totalRoutes})`,
|
|
308
|
+
'Registration',
|
|
309
|
+
{
|
|
310
|
+
isStatic: compiledPath.isStatic,
|
|
311
|
+
isFastPath,
|
|
312
|
+
segments: compiledPath.segments,
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Chainable API methods
|
|
319
|
+
*/
|
|
320
|
+
get(path: string): RouteBuilder {
|
|
321
|
+
return new RouteBuilder('GET', path, this);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
post(path: string): RouteBuilder {
|
|
325
|
+
return new RouteBuilder('POST', path, this);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
put(path: string): RouteBuilder {
|
|
329
|
+
return new RouteBuilder('PUT', path, this);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
delete(path: string): RouteBuilder {
|
|
333
|
+
return new RouteBuilder('DELETE', path, this);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
patch(path: string): RouteBuilder {
|
|
337
|
+
return new RouteBuilder('PATCH', path, this);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
head(path: string): RouteBuilder {
|
|
341
|
+
return new RouteBuilder('HEAD', path, this);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
options(path: string): RouteBuilder {
|
|
345
|
+
return new RouteBuilder('OPTIONS', path, this);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Schema-first route registration
|
|
350
|
+
*/
|
|
351
|
+
route(schema: RouteSchema): void {
|
|
352
|
+
this.registerRoute(schema);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Direct API (for backward compatibility)
|
|
357
|
+
*/
|
|
358
|
+
addRoute(
|
|
359
|
+
method: HttpMethod,
|
|
360
|
+
path: string,
|
|
361
|
+
handler: RouteHandler,
|
|
362
|
+
middleware: Middleware[] = []
|
|
363
|
+
): void {
|
|
364
|
+
this.registerRoute({
|
|
365
|
+
method,
|
|
366
|
+
path,
|
|
367
|
+
handler,
|
|
368
|
+
middleware,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ===== Route Matching =====
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Find a matching route for the request
|
|
376
|
+
* Returns boolean (sync) for fast-path routes, Promise<boolean> for others
|
|
377
|
+
*/
|
|
378
|
+
handleRequest(req: HttpRequest, res: HttpResponse): Promise<boolean> | boolean {
|
|
379
|
+
// PERFORMANCE: Only increment stats counter, not individual metrics in hot path
|
|
380
|
+
this.stats.requestCount++;
|
|
381
|
+
|
|
382
|
+
const method = req.method?.toUpperCase() as HttpMethod;
|
|
383
|
+
const path = req.path;
|
|
384
|
+
|
|
385
|
+
// Phase 1: No middleware, auth, validation, or rate limiting
|
|
386
|
+
// Optimized for synchronous execution when possible
|
|
387
|
+
if (this.fastPathRoutes.size > 0) {
|
|
388
|
+
for (const route of this.fastPathRoutes) {
|
|
389
|
+
if (route.schema.method === method) {
|
|
390
|
+
// Inline parameter extraction for speed (avoid function call overhead)
|
|
391
|
+
if (route.compiledPath.isStatic) {
|
|
392
|
+
if (route.compiledPath.path === path) {
|
|
393
|
+
// Static route match
|
|
394
|
+
req.params = {};
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
const result = route.schema.handler(req, res);
|
|
398
|
+
|
|
399
|
+
// Check if result is a promise (optimized check)
|
|
400
|
+
if (result && typeof (result as any).then === 'function') {
|
|
401
|
+
// Async handler - return promise
|
|
402
|
+
return (result as Promise<any>)
|
|
403
|
+
.then(actualResult => {
|
|
404
|
+
if (actualResult !== undefined && !res.headersSent) {
|
|
405
|
+
res.json(actualResult);
|
|
406
|
+
}
|
|
407
|
+
return true;
|
|
408
|
+
})
|
|
409
|
+
.catch(() => {
|
|
410
|
+
if (!res.headersSent) {
|
|
411
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
412
|
+
}
|
|
413
|
+
return true;
|
|
414
|
+
});
|
|
415
|
+
} else {
|
|
416
|
+
// Sync handler - handle synchronously (fastest path!)
|
|
417
|
+
if (result !== undefined && !res.headersSent) {
|
|
418
|
+
res.json(result);
|
|
419
|
+
}
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
} catch (error) {
|
|
423
|
+
if (!res.headersSent) {
|
|
424
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
425
|
+
}
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
// Dynamic route - use regex matching
|
|
431
|
+
const pattern = route.compiledPath.pattern;
|
|
432
|
+
if (pattern) {
|
|
433
|
+
const matches = path.match(pattern);
|
|
434
|
+
if (matches) {
|
|
435
|
+
// Extract params inline (avoid function call)
|
|
436
|
+
const params: Record<string, string> = {};
|
|
437
|
+
const paramNames = route.compiledPath.paramNames;
|
|
438
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
439
|
+
params[paramNames[i]] = matches[i + 1];
|
|
440
|
+
}
|
|
441
|
+
req.params = params;
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
const result = route.schema.handler(req, res);
|
|
445
|
+
|
|
446
|
+
if (result && typeof (result as any).then === 'function') {
|
|
447
|
+
return (result as Promise<any>)
|
|
448
|
+
.then(actualResult => {
|
|
449
|
+
if (actualResult !== undefined && !res.headersSent) {
|
|
450
|
+
res.json(actualResult);
|
|
451
|
+
}
|
|
452
|
+
return true;
|
|
453
|
+
})
|
|
454
|
+
.catch(() => {
|
|
455
|
+
if (!res.headersSent) {
|
|
456
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
457
|
+
}
|
|
458
|
+
return true;
|
|
459
|
+
});
|
|
460
|
+
} else {
|
|
461
|
+
if (result !== undefined && !res.headersSent) {
|
|
462
|
+
res.json(result);
|
|
463
|
+
}
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
if (!res.headersSent) {
|
|
468
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
469
|
+
}
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Phase 2 & 3: Non-fast-path routes (async)
|
|
480
|
+
return (async () => {
|
|
481
|
+
// Phase 2: O(1) static route lookup
|
|
482
|
+
const staticKey = `${method}:${path}`;
|
|
483
|
+
|
|
484
|
+
// Check pool manager cache
|
|
485
|
+
const cachedRoute = this.poolManager.getCachedRoute(staticKey);
|
|
486
|
+
if (cachedRoute) {
|
|
487
|
+
await this.executeRoute(cachedRoute, req, res, { params: {} });
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const staticRoute = this.staticRoutes.get(staticKey);
|
|
492
|
+
if (staticRoute) {
|
|
493
|
+
this.poolManager.cacheRoute(staticKey, staticRoute);
|
|
494
|
+
req.params = {};
|
|
495
|
+
await this.executeRoute(staticRoute, req, res, { params: {} });
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Phase 3: Segment-based dynamic route matching
|
|
500
|
+
const segments = path.split('/').filter(s => s.length > 0);
|
|
501
|
+
const segmentCount = segments.length;
|
|
502
|
+
const candidates = this.dynamicRoutesBySegments.get(segmentCount) || [];
|
|
503
|
+
|
|
504
|
+
for (const route of candidates) {
|
|
505
|
+
if (route.schema.method === method) {
|
|
506
|
+
const matchResult = PathMatcher.match(route.compiledPath, path);
|
|
507
|
+
if (matchResult) {
|
|
508
|
+
this.poolManager.cacheRoute(staticKey, route);
|
|
509
|
+
await this.executeRoute(route, req, res, matchResult);
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// No route found
|
|
516
|
+
return false;
|
|
517
|
+
})();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ===== Route Execution =====
|
|
521
|
+
|
|
522
|
+
private async executeRoute(
|
|
523
|
+
route: InternalRoute,
|
|
524
|
+
req: HttpRequest,
|
|
525
|
+
res: HttpResponse,
|
|
526
|
+
matchResult: { params: Record<string, string> }
|
|
527
|
+
): Promise<void> {
|
|
528
|
+
// Set params from pool
|
|
529
|
+
req.params = this.poolManager.acquireParams();
|
|
530
|
+
Object.assign(req.params, matchResult.params);
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
// Execute middleware phases in order
|
|
534
|
+
for (const phase of route.executionOrder) {
|
|
535
|
+
if (res.headersSent) break;
|
|
536
|
+
await this.executePhase(phase, route, req, res);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Execute handler
|
|
540
|
+
if (!res.headersSent) {
|
|
541
|
+
const result = await route.schema.handler(req, res);
|
|
542
|
+
if (result !== undefined && !res.headersSent) {
|
|
543
|
+
await res.json(result);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
} catch (error) {
|
|
547
|
+
logger.error('Route execution error', 'Execution', {
|
|
548
|
+
error: error instanceof Error ? error.message : String(error),
|
|
549
|
+
route: `${route.schema.method} ${route.schema.path}`,
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
if (!res.headersSent) {
|
|
553
|
+
res.status(500).json({
|
|
554
|
+
success: false,
|
|
555
|
+
error: 'Internal server error',
|
|
556
|
+
requestId: req.requestId,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
} finally {
|
|
560
|
+
// Release params back to pool
|
|
561
|
+
if (req.params) {
|
|
562
|
+
this.poolManager.releaseParams(req.params);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private async executePhase(
|
|
568
|
+
phase: string,
|
|
569
|
+
route: InternalRoute,
|
|
570
|
+
req: HttpRequest,
|
|
571
|
+
res: HttpResponse
|
|
572
|
+
): Promise<void> {
|
|
573
|
+
const schema = route.schema;
|
|
574
|
+
const middleware = schema.middleware;
|
|
575
|
+
|
|
576
|
+
switch (phase) {
|
|
577
|
+
case 'before':
|
|
578
|
+
if (middleware && 'before' in middleware && Array.isArray(middleware.before)) {
|
|
579
|
+
for (const mw of middleware.before) {
|
|
580
|
+
await this.executeMiddleware(mw, req, res);
|
|
581
|
+
if (res.headersSent) return;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
585
|
+
|
|
586
|
+
case 'rateLimit':
|
|
587
|
+
if (schema.rateLimit) {
|
|
588
|
+
await this.executeRateLimit(req, res, schema.rateLimit);
|
|
589
|
+
}
|
|
590
|
+
break;
|
|
591
|
+
|
|
592
|
+
case 'auth':
|
|
593
|
+
if (schema.auth) {
|
|
594
|
+
await this.executeAuth(req, res, schema.auth);
|
|
595
|
+
}
|
|
596
|
+
break;
|
|
597
|
+
|
|
598
|
+
case 'validation':
|
|
599
|
+
if (schema.validation) {
|
|
600
|
+
await this.executeValidation(req, res, schema.validation);
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
603
|
+
|
|
604
|
+
case 'transform':
|
|
605
|
+
if (middleware && 'transform' in middleware && Array.isArray(middleware.transform)) {
|
|
606
|
+
for (const mw of middleware.transform) {
|
|
607
|
+
await this.executeMiddleware(mw, req, res);
|
|
608
|
+
if (res.headersSent) return;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
|
|
613
|
+
case 'cache':
|
|
614
|
+
if (schema.cache) {
|
|
615
|
+
await this.executeCache(req, res, schema.cache);
|
|
616
|
+
}
|
|
617
|
+
break;
|
|
618
|
+
|
|
619
|
+
case 'after':
|
|
620
|
+
if (middleware && 'after' in middleware && Array.isArray(middleware.after)) {
|
|
621
|
+
for (const mw of middleware.after) {
|
|
622
|
+
await this.executeMiddleware(mw, req, res);
|
|
623
|
+
if (res.headersSent) return;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
break;
|
|
627
|
+
|
|
628
|
+
case 'middleware':
|
|
629
|
+
// Handle array-style middleware (backward compatibility)
|
|
630
|
+
if (middleware && Array.isArray(middleware)) {
|
|
631
|
+
for (const mw of middleware) {
|
|
632
|
+
await this.executeMiddleware(mw, req, res);
|
|
633
|
+
if (res.headersSent) return;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
private async executeMiddleware(
|
|
641
|
+
middleware: Middleware,
|
|
642
|
+
req: HttpRequest,
|
|
643
|
+
res: HttpResponse
|
|
644
|
+
): Promise<void> {
|
|
645
|
+
return new Promise((resolve, reject) => {
|
|
646
|
+
let resolved = false;
|
|
647
|
+
|
|
648
|
+
const next = () => {
|
|
649
|
+
if (!resolved) {
|
|
650
|
+
resolved = true;
|
|
651
|
+
resolve();
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
try {
|
|
656
|
+
const result = middleware(req, res, next);
|
|
657
|
+
if (result instanceof Promise) {
|
|
658
|
+
result.then(() => !resolved && next()).catch(reject);
|
|
659
|
+
} else if (!resolved) {
|
|
660
|
+
next();
|
|
661
|
+
}
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (!resolved) {
|
|
664
|
+
resolved = true;
|
|
665
|
+
reject(error);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
private async executeRateLimit(
|
|
672
|
+
_req: HttpRequest,
|
|
673
|
+
_res: HttpResponse,
|
|
674
|
+
_config: RateLimitConfig
|
|
675
|
+
): Promise<void> {
|
|
676
|
+
// Rate limiting implementation placeholder
|
|
677
|
+
// TODO: Implement actual rate limiting logic
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private async executeAuth(
|
|
681
|
+
req: HttpRequest,
|
|
682
|
+
res: HttpResponse,
|
|
683
|
+
config: AuthConfig
|
|
684
|
+
): Promise<void> {
|
|
685
|
+
const auth = (req as any).auth;
|
|
686
|
+
|
|
687
|
+
if (!auth) {
|
|
688
|
+
res.status(500).json({
|
|
689
|
+
success: false,
|
|
690
|
+
error: 'Authentication middleware not configured',
|
|
691
|
+
});
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (!config.optional && !auth.isAuthenticated) {
|
|
696
|
+
res.status(401).json({
|
|
697
|
+
success: false,
|
|
698
|
+
error: 'Authentication required',
|
|
699
|
+
});
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (auth.isAuthenticated && config.roles) {
|
|
704
|
+
const userRoles = auth.user?.roles || [];
|
|
705
|
+
const hasRole = config.roles.some(role => userRoles.includes(role));
|
|
706
|
+
if (!hasRole) {
|
|
707
|
+
res.status(403).json({
|
|
708
|
+
success: false,
|
|
709
|
+
error: 'Insufficient permissions',
|
|
710
|
+
message: `Required roles: ${config.roles.join(', ')}`,
|
|
711
|
+
});
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if (auth.isAuthenticated && config.permissions) {
|
|
717
|
+
const userPermissions = auth.user?.permissions || [];
|
|
718
|
+
const hasPermission = config.permissions.every(permission =>
|
|
719
|
+
userPermissions.includes(permission)
|
|
720
|
+
);
|
|
721
|
+
if (!hasPermission) {
|
|
722
|
+
res.status(403).json({
|
|
723
|
+
success: false,
|
|
724
|
+
error: 'Insufficient permissions',
|
|
725
|
+
message: `Required permissions: ${config.permissions.join(', ')}`,
|
|
726
|
+
});
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
private async executeValidation(
|
|
733
|
+
req: HttpRequest,
|
|
734
|
+
res: HttpResponse,
|
|
735
|
+
config: ValidationConfig
|
|
736
|
+
): Promise<void> {
|
|
737
|
+
try {
|
|
738
|
+
if (config.body && req.body !== undefined) {
|
|
739
|
+
(req as any).validatedBody = await config.body.parseAsync(req.body);
|
|
740
|
+
req.body = (req as any).validatedBody;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (config.query && req.query !== undefined) {
|
|
744
|
+
(req as any).validatedQuery = await config.query.parseAsync(req.query);
|
|
745
|
+
req.query = (req as any).validatedQuery;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (config.params && req.params !== undefined) {
|
|
749
|
+
(req as any).validatedParams = await config.params.parseAsync(req.params);
|
|
750
|
+
req.params = (req as any).validatedParams;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (config.headers && req.headers !== undefined) {
|
|
754
|
+
(req as any).validatedHeaders = await config.headers.parseAsync(req.headers);
|
|
755
|
+
}
|
|
756
|
+
} catch (error: any) {
|
|
757
|
+
const field = error.field || 'unknown';
|
|
758
|
+
if (error.issues) {
|
|
759
|
+
res.status(400).json({
|
|
760
|
+
success: false,
|
|
761
|
+
error: `Validation failed for ${field}`,
|
|
762
|
+
details: error.issues.map((issue: any) => ({
|
|
763
|
+
field: issue.path.length > 0 ? issue.path.join('.') : field,
|
|
764
|
+
message: issue.message,
|
|
765
|
+
code: issue.code,
|
|
766
|
+
})),
|
|
767
|
+
requestId: req.requestId,
|
|
768
|
+
});
|
|
769
|
+
} else {
|
|
770
|
+
res.status(400).json({
|
|
771
|
+
success: false,
|
|
772
|
+
error: `Validation failed for ${field}`,
|
|
773
|
+
requestId: req.requestId,
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
private async executeCache(
|
|
780
|
+
_req: HttpRequest,
|
|
781
|
+
_res: HttpResponse,
|
|
782
|
+
_config: CacheConfig
|
|
783
|
+
): Promise<void> {
|
|
784
|
+
// Cache implementation placeholder
|
|
785
|
+
// TODO: Implement actual caching logic
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// ===== Helper Methods =====
|
|
789
|
+
|
|
790
|
+
private isFastPathRoute(schema: RouteSchema): boolean {
|
|
791
|
+
const middleware = schema.middleware;
|
|
792
|
+
const hasMiddleware =
|
|
793
|
+
(middleware && Array.isArray(middleware) && middleware.length > 0) ||
|
|
794
|
+
(middleware &&
|
|
795
|
+
typeof middleware === 'object' &&
|
|
796
|
+
((middleware as MiddlewarePhases).before?.length ||
|
|
797
|
+
(middleware as MiddlewarePhases).after?.length ||
|
|
798
|
+
(middleware as MiddlewarePhases).transform?.length));
|
|
799
|
+
|
|
800
|
+
return (
|
|
801
|
+
!schema.auth && !schema.validation && !schema.rateLimit && !schema.cache && !hasMiddleware
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
private buildExecutionOrder(schema: RouteSchema): string[] {
|
|
806
|
+
const order: string[] = [];
|
|
807
|
+
const middleware = schema.middleware;
|
|
808
|
+
|
|
809
|
+
// Phase-based middleware
|
|
810
|
+
if (middleware && 'before' in middleware && middleware.before?.length) {
|
|
811
|
+
order.push('before');
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (schema.rateLimit) order.push('rateLimit');
|
|
815
|
+
if (schema.auth) order.push('auth');
|
|
816
|
+
if (schema.validation) order.push('validation');
|
|
817
|
+
|
|
818
|
+
if (middleware && 'transform' in middleware && middleware.transform?.length) {
|
|
819
|
+
order.push('transform');
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (schema.cache) order.push('cache');
|
|
823
|
+
|
|
824
|
+
if (middleware && 'after' in middleware && middleware.after?.length) {
|
|
825
|
+
order.push('after');
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Array-style middleware (backward compatibility)
|
|
829
|
+
if (middleware && Array.isArray(middleware) && middleware.length > 0) {
|
|
830
|
+
order.push('middleware');
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return order;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// ===== Inspection Methods =====
|
|
837
|
+
|
|
838
|
+
getAllRoutes(): RouteSchema[] {
|
|
839
|
+
return this.allRoutes.map(r => r.schema);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
getRouteCount(): number {
|
|
843
|
+
return this.stats.totalRoutes;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
getStats() {
|
|
847
|
+
return {
|
|
848
|
+
...this.stats,
|
|
849
|
+
poolManager: this.poolManager.getPerformanceSummary(),
|
|
850
|
+
pathMatcher: PathMatcher.getStats(),
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
logPerformanceStats(): void {
|
|
855
|
+
const stats = this.getStats();
|
|
856
|
+
logger.info('UnifiedRouter Performance', 'Stats', {
|
|
857
|
+
totalRoutes: stats.totalRoutes,
|
|
858
|
+
staticRoutes: stats.staticRoutes,
|
|
859
|
+
dynamicRoutes: stats.dynamicRoutes,
|
|
860
|
+
fastPathRoutes: stats.fastPathRoutes,
|
|
861
|
+
requests: stats.requestCount,
|
|
862
|
+
poolManager: {
|
|
863
|
+
routeCacheHitRate: stats.poolManager.routeCacheHitRate.toFixed(1) + '%',
|
|
864
|
+
responseCacheHitRate: stats.poolManager.responseCacheHitRate.toFixed(1) + '%',
|
|
865
|
+
paramPoolUtilization: stats.poolManager.paramPoolUtilization.toFixed(1) + '%',
|
|
866
|
+
totalMemoryKB: stats.poolManager.totalMemoryKB.toFixed(1) + ' KB',
|
|
867
|
+
},
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
}
|