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