@morojs/moro 1.5.17 → 1.6.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.
- package/README.md +48 -65
- package/dist/core/auth/morojs-adapter.js +12 -16
- package/dist/core/auth/morojs-adapter.js.map +1 -1
- package/dist/core/config/file-loader.d.ts +5 -0
- package/dist/core/config/file-loader.js +171 -0
- package/dist/core/config/file-loader.js.map +1 -1
- package/dist/core/config/index.d.ts +10 -39
- package/dist/core/config/index.js +29 -66
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/loader.d.ts +7 -0
- package/dist/core/config/loader.js +269 -0
- package/dist/core/config/loader.js.map +1 -0
- package/dist/core/config/schema.js +31 -41
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/utils.d.ts +2 -9
- package/dist/core/config/utils.js +32 -19
- package/dist/core/config/utils.js.map +1 -1
- package/dist/core/config/validation.d.ts +17 -0
- package/dist/core/config/validation.js +131 -0
- package/dist/core/config/validation.js.map +1 -0
- package/dist/core/database/adapters/mongodb.d.ts +0 -10
- package/dist/core/database/adapters/mongodb.js +2 -23
- package/dist/core/database/adapters/mongodb.js.map +1 -1
- package/dist/core/database/adapters/mysql.d.ts +0 -11
- package/dist/core/database/adapters/mysql.js +0 -1
- package/dist/core/database/adapters/mysql.js.map +1 -1
- package/dist/core/database/adapters/postgresql.d.ts +1 -9
- package/dist/core/database/adapters/postgresql.js +1 -1
- package/dist/core/database/adapters/postgresql.js.map +1 -1
- package/dist/core/database/adapters/redis.d.ts +0 -9
- package/dist/core/database/adapters/redis.js +4 -14
- package/dist/core/database/adapters/redis.js.map +1 -1
- package/dist/core/framework.d.ts +7 -6
- package/dist/core/framework.js +16 -131
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +0 -12
- package/dist/core/http/http-server.js +23 -151
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/router.d.ts +0 -12
- package/dist/core/http/router.js +36 -114
- package/dist/core/http/router.js.map +1 -1
- package/dist/core/logger/filters.js +4 -12
- package/dist/core/logger/filters.js.map +1 -1
- package/dist/core/logger/index.d.ts +1 -1
- package/dist/core/logger/index.js +1 -2
- package/dist/core/logger/index.js.map +1 -1
- package/dist/core/logger/logger.d.ts +13 -29
- package/dist/core/logger/logger.js +203 -380
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/logger/outputs.js +2 -0
- package/dist/core/logger/outputs.js.map +1 -1
- package/dist/core/middleware/built-in/auth.js +17 -88
- package/dist/core/middleware/built-in/auth.js.map +1 -1
- package/dist/core/middleware/built-in/cache.js +1 -3
- package/dist/core/middleware/built-in/cache.js.map +1 -1
- package/dist/core/middleware/built-in/index.d.ts +0 -1
- package/dist/core/middleware/built-in/index.js +1 -6
- package/dist/core/middleware/built-in/index.js.map +1 -1
- package/dist/core/middleware/built-in/request-logger.js +2 -3
- package/dist/core/middleware/built-in/request-logger.js.map +1 -1
- package/dist/core/middleware/built-in/sse.js +7 -9
- package/dist/core/middleware/built-in/sse.js.map +1 -1
- package/dist/core/modules/auto-discovery.d.ts +0 -17
- package/dist/core/modules/auto-discovery.js +12 -367
- package/dist/core/modules/auto-discovery.js.map +1 -1
- package/dist/core/modules/modules.js +2 -12
- package/dist/core/modules/modules.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.d.ts +1 -1
- package/dist/core/networking/adapters/ws-adapter.js +2 -2
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/service-discovery.js +7 -7
- package/dist/core/networking/service-discovery.js.map +1 -1
- package/dist/core/routing/index.d.ts +0 -20
- package/dist/core/routing/index.js +13 -178
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/runtime/node-adapter.js +6 -12
- package/dist/core/runtime/node-adapter.js.map +1 -1
- package/dist/moro.d.ts +0 -48
- package/dist/moro.js +148 -456
- package/dist/moro.js.map +1 -1
- package/dist/types/config.d.ts +2 -58
- package/dist/types/core.d.ts +40 -34
- package/dist/types/http.d.ts +1 -16
- package/dist/types/logger.d.ts +0 -7
- package/dist/types/module.d.ts +0 -11
- package/package.json +2 -2
- package/src/core/auth/morojs-adapter.ts +13 -18
- package/src/core/config/file-loader.ts +233 -0
- package/src/core/config/index.ts +32 -77
- package/src/core/config/loader.ts +633 -0
- package/src/core/config/schema.ts +31 -41
- package/src/core/config/utils.ts +29 -22
- package/src/core/config/validation.ts +140 -0
- package/src/core/database/README.md +16 -26
- package/src/core/database/adapters/mongodb.ts +2 -30
- package/src/core/database/adapters/mysql.ts +0 -14
- package/src/core/database/adapters/postgresql.ts +2 -12
- package/src/core/database/adapters/redis.ts +4 -27
- package/src/core/framework.ts +23 -163
- package/src/core/http/http-server.ts +36 -176
- package/src/core/http/router.ts +38 -127
- package/src/core/logger/filters.ts +4 -12
- package/src/core/logger/index.ts +0 -1
- package/src/core/logger/logger.ts +216 -427
- package/src/core/logger/outputs.ts +2 -0
- package/src/core/middleware/built-in/auth.ts +17 -98
- package/src/core/middleware/built-in/cache.ts +1 -3
- package/src/core/middleware/built-in/index.ts +0 -8
- package/src/core/middleware/built-in/request-logger.ts +1 -3
- package/src/core/middleware/built-in/sse.ts +7 -9
- package/src/core/modules/auto-discovery.ts +13 -476
- package/src/core/modules/modules.ts +9 -20
- package/src/core/networking/adapters/ws-adapter.ts +5 -2
- package/src/core/networking/service-discovery.ts +7 -6
- package/src/core/routing/index.ts +14 -198
- package/src/core/runtime/node-adapter.ts +6 -12
- package/src/moro.ts +166 -554
- package/src/types/config.ts +2 -59
- package/src/types/core.ts +45 -47
- package/src/types/http.ts +1 -23
- package/src/types/logger.ts +0 -9
- package/src/types/module.ts +0 -12
- package/dist/core/config/config-manager.d.ts +0 -44
- package/dist/core/config/config-manager.js +0 -114
- package/dist/core/config/config-manager.js.map +0 -1
- package/dist/core/config/config-sources.d.ts +0 -21
- package/dist/core/config/config-sources.js +0 -502
- package/dist/core/config/config-sources.js.map +0 -1
- package/dist/core/config/config-validator.d.ts +0 -21
- package/dist/core/config/config-validator.js +0 -765
- package/dist/core/config/config-validator.js.map +0 -1
- package/dist/core/middleware/built-in/jwt-helpers.d.ts +0 -118
- package/dist/core/middleware/built-in/jwt-helpers.js +0 -221
- package/dist/core/middleware/built-in/jwt-helpers.js.map +0 -1
- package/src/core/config/config-manager.ts +0 -133
- package/src/core/config/config-sources.ts +0 -596
- package/src/core/config/config-validator.ts +0 -1078
- package/src/core/middleware/built-in/jwt-helpers.ts +0 -240
|
@@ -11,15 +11,6 @@ interface RedisConfig {
|
|
|
11
11
|
maxRetriesPerRequest?: number;
|
|
12
12
|
retryDelayOnFailover?: number;
|
|
13
13
|
lazyConnect?: boolean;
|
|
14
|
-
tls?: {
|
|
15
|
-
rejectUnauthorized?: boolean;
|
|
16
|
-
ca?: string;
|
|
17
|
-
cert?: string;
|
|
18
|
-
key?: string;
|
|
19
|
-
passphrase?: string;
|
|
20
|
-
servername?: string;
|
|
21
|
-
checkServerIdentity?: boolean;
|
|
22
|
-
};
|
|
23
14
|
cluster?: {
|
|
24
15
|
enableReadyCheck?: boolean;
|
|
25
16
|
redisOptions?: any;
|
|
@@ -39,20 +30,13 @@ export class RedisAdapter implements DatabaseAdapter {
|
|
|
39
30
|
|
|
40
31
|
if (config.cluster) {
|
|
41
32
|
// Redis Cluster
|
|
42
|
-
|
|
33
|
+
this.client = new Redis.Cluster(config.cluster.nodes, {
|
|
43
34
|
enableReadyCheck: config.cluster.enableReadyCheck || false,
|
|
44
35
|
redisOptions: config.cluster.redisOptions || {},
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// Add TLS options to cluster configuration
|
|
48
|
-
if (config.tls) {
|
|
49
|
-
clusterOptions.redisOptions.tls = { ...config.tls };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
this.client = new Redis.Cluster(config.cluster.nodes, clusterOptions);
|
|
36
|
+
});
|
|
53
37
|
} else {
|
|
54
38
|
// Single Redis instance
|
|
55
|
-
|
|
39
|
+
this.client = new Redis({
|
|
56
40
|
host: config.host || 'localhost',
|
|
57
41
|
port: config.port || 6379,
|
|
58
42
|
password: config.password,
|
|
@@ -60,14 +44,7 @@ export class RedisAdapter implements DatabaseAdapter {
|
|
|
60
44
|
maxRetriesPerRequest: config.maxRetriesPerRequest || 3,
|
|
61
45
|
retryDelayOnFailover: config.retryDelayOnFailover || 100,
|
|
62
46
|
lazyConnect: config.lazyConnect || true,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// Add TLS options if provided
|
|
66
|
-
if (config.tls) {
|
|
67
|
-
redisOptions.tls = { ...config.tls };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
this.client = new Redis(redisOptions);
|
|
47
|
+
});
|
|
71
48
|
}
|
|
72
49
|
|
|
73
50
|
this.client.on('error', (err: Error) => {
|
package/src/core/framework.ts
CHANGED
|
@@ -15,17 +15,19 @@ import { MoroEventBus } from './events';
|
|
|
15
15
|
import { createFrameworkLogger, logger as globalLogger } from './logger';
|
|
16
16
|
import { ModuleConfig, InternalRouteDefinition } from '../types/module';
|
|
17
17
|
import { LogLevel, LoggerOptions } from '../types/logger';
|
|
18
|
-
import { MoroOptions as CoreMoroOptions } from '../types/core';
|
|
19
18
|
import { WebSocketAdapter, WebSocketAdapterOptions } from './networking/websocket-adapter';
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
export interface MoroOptions extends CoreMoroOptions {
|
|
20
|
+
export interface MoroOptions {
|
|
23
21
|
http2?: boolean;
|
|
24
22
|
https?: {
|
|
25
23
|
key: string | Buffer;
|
|
26
24
|
cert: string | Buffer;
|
|
27
25
|
ca?: string | Buffer;
|
|
28
26
|
};
|
|
27
|
+
compression?: {
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
threshold?: number;
|
|
30
|
+
};
|
|
29
31
|
websocket?:
|
|
30
32
|
| {
|
|
31
33
|
enabled?: boolean;
|
|
@@ -35,7 +37,7 @@ export interface MoroOptions extends CoreMoroOptions {
|
|
|
35
37
|
options?: WebSocketAdapterOptions;
|
|
36
38
|
}
|
|
37
39
|
| false;
|
|
38
|
-
|
|
40
|
+
logger?: LoggerOptions | boolean;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export class Moro extends EventEmitter {
|
|
@@ -52,12 +54,10 @@ export class Moro extends EventEmitter {
|
|
|
52
54
|
// Framework logger
|
|
53
55
|
private logger: any;
|
|
54
56
|
private options: MoroOptions;
|
|
55
|
-
private config: any;
|
|
56
57
|
|
|
57
58
|
constructor(options: MoroOptions = {}) {
|
|
58
59
|
super();
|
|
59
60
|
this.options = options;
|
|
60
|
-
this.config = options.config || {};
|
|
61
61
|
|
|
62
62
|
// Configure global logger based on options
|
|
63
63
|
if (options.logger !== undefined) {
|
|
@@ -106,11 +106,8 @@ export class Moro extends EventEmitter {
|
|
|
106
106
|
this.container = new Container();
|
|
107
107
|
this.moduleLoader = new ModuleLoader(this.container);
|
|
108
108
|
|
|
109
|
-
// Setup WebSocket adapter if enabled
|
|
110
|
-
if (
|
|
111
|
-
this.config.websocket.enabled ||
|
|
112
|
-
(options.websocket && typeof options.websocket === 'object')
|
|
113
|
-
) {
|
|
109
|
+
// Setup WebSocket adapter if enabled
|
|
110
|
+
if (options.websocket !== false) {
|
|
114
111
|
this.setupWebSockets(options.websocket || {});
|
|
115
112
|
}
|
|
116
113
|
|
|
@@ -134,49 +131,19 @@ export class Moro extends EventEmitter {
|
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
private setupCore() {
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (this.config.security.helmet.enabled || this.options.security?.helmet?.enabled === true) {
|
|
141
|
-
this.httpServer.use(middleware.helmet());
|
|
142
|
-
}
|
|
134
|
+
// Security middleware
|
|
135
|
+
this.httpServer.use(middleware.helmet());
|
|
136
|
+
this.httpServer.use(middleware.cors());
|
|
143
137
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
? this.options.cors
|
|
148
|
-
: this.config.security.cors
|
|
149
|
-
? this.config.security.cors
|
|
150
|
-
: {};
|
|
151
|
-
this.httpServer.use(middleware.cors(corsOptions));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Performance middleware - check config enabled property OR options.performance.*.enabled === true
|
|
155
|
-
if (
|
|
156
|
-
this.config.performance.compression.enabled ||
|
|
157
|
-
this.options.performance?.compression?.enabled === true
|
|
158
|
-
) {
|
|
159
|
-
const compressionOptions =
|
|
160
|
-
typeof this.options.compression === 'object'
|
|
161
|
-
? this.options.compression
|
|
162
|
-
: this.config.performance.compression
|
|
163
|
-
? this.config.performance.compression
|
|
164
|
-
: {};
|
|
165
|
-
this.httpServer.use(middleware.compression(compressionOptions));
|
|
166
|
-
}
|
|
138
|
+
// Performance middleware
|
|
139
|
+
this.httpServer.use(middleware.compression());
|
|
140
|
+
this.httpServer.use(middleware.bodySize({ limit: '10mb' }));
|
|
167
141
|
|
|
168
|
-
//
|
|
169
|
-
this.httpServer.use(
|
|
142
|
+
// Request tracking middleware
|
|
143
|
+
this.httpServer.use(this.requestTrackingMiddleware());
|
|
170
144
|
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
this.httpServer.use(this.requestTrackingMiddleware());
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Error boundary middleware - configurable but recommended to keep enabled
|
|
177
|
-
if (this.config.server.errorBoundary.enabled) {
|
|
178
|
-
this.httpServer.use(this.errorBoundaryMiddleware());
|
|
179
|
-
}
|
|
145
|
+
// Error boundary middleware
|
|
146
|
+
this.httpServer.use(this.errorBoundaryMiddleware());
|
|
180
147
|
}
|
|
181
148
|
|
|
182
149
|
/**
|
|
@@ -444,76 +411,6 @@ export class Moro extends EventEmitter {
|
|
|
444
411
|
throw new Error(`Handler ${route.handler} is not a function`);
|
|
445
412
|
}
|
|
446
413
|
|
|
447
|
-
// Check authentication if auth configuration is provided
|
|
448
|
-
if ((route as any).auth) {
|
|
449
|
-
const auth = (req as any).auth;
|
|
450
|
-
const authConfig = (route as any).auth;
|
|
451
|
-
|
|
452
|
-
if (!auth) {
|
|
453
|
-
res.status(401);
|
|
454
|
-
res.json({
|
|
455
|
-
success: false,
|
|
456
|
-
error: 'Authentication required',
|
|
457
|
-
message: 'You must be logged in to access this resource',
|
|
458
|
-
});
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Check authentication requirement (default is required unless optional: true)
|
|
463
|
-
if (!authConfig.optional && !auth.isAuthenticated) {
|
|
464
|
-
res.status(401);
|
|
465
|
-
res.json({
|
|
466
|
-
success: false,
|
|
467
|
-
error: 'Authentication required',
|
|
468
|
-
message: 'You must be logged in to access this resource',
|
|
469
|
-
});
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Skip further checks if not authenticated but optional
|
|
474
|
-
if (!auth.isAuthenticated && authConfig.optional) {
|
|
475
|
-
// Continue to handler
|
|
476
|
-
} else if (auth.isAuthenticated) {
|
|
477
|
-
const user = auth.user;
|
|
478
|
-
|
|
479
|
-
// Check roles if specified
|
|
480
|
-
if (authConfig.roles && authConfig.roles.length > 0) {
|
|
481
|
-
const userRoles = user?.roles || [];
|
|
482
|
-
const hasRole = authConfig.roles.some((role: string) => userRoles.includes(role));
|
|
483
|
-
|
|
484
|
-
if (!hasRole) {
|
|
485
|
-
res.status(403);
|
|
486
|
-
res.json({
|
|
487
|
-
success: false,
|
|
488
|
-
error: 'Insufficient permissions',
|
|
489
|
-
message: `Required roles: ${authConfig.roles.join(', ')}`,
|
|
490
|
-
userRoles,
|
|
491
|
-
});
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Check permissions if specified
|
|
497
|
-
if (authConfig.permissions && authConfig.permissions.length > 0) {
|
|
498
|
-
const userPermissions = user?.permissions || [];
|
|
499
|
-
const hasPermission = authConfig.permissions.every((permission: string) =>
|
|
500
|
-
userPermissions.includes(permission)
|
|
501
|
-
);
|
|
502
|
-
|
|
503
|
-
if (!hasPermission) {
|
|
504
|
-
res.status(403);
|
|
505
|
-
res.json({
|
|
506
|
-
success: false,
|
|
507
|
-
error: 'Insufficient permissions',
|
|
508
|
-
message: `Required permissions: ${authConfig.permissions.join(', ')}`,
|
|
509
|
-
userPermissions,
|
|
510
|
-
});
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
414
|
// Validate request if validation schema is provided
|
|
518
415
|
if (route.validation) {
|
|
519
416
|
try {
|
|
@@ -619,61 +516,22 @@ export class Moro extends EventEmitter {
|
|
|
619
516
|
this.logger.debug(`Mounting router for basePath: ${basePath}`, 'Router');
|
|
620
517
|
|
|
621
518
|
// Enterprise-grade middleware integration with performance optimization
|
|
622
|
-
// IMPORTANT: Module middleware runs AFTER user middleware (like auth) to ensure proper order
|
|
623
519
|
this.httpServer.use(async (req: HttpRequest, res: HttpResponse, next: () => void) => {
|
|
624
520
|
if (req.path.startsWith(basePath)) {
|
|
625
521
|
this.logger.debug(`Module middleware handling: ${req.method} ${req.path}`, 'Middleware', {
|
|
626
522
|
basePath,
|
|
627
523
|
});
|
|
628
524
|
|
|
629
|
-
// Mark this request as being handled by a module
|
|
630
|
-
(req as any).__moduleBasePath = basePath;
|
|
631
|
-
(req as any).__moduleRouter = router;
|
|
632
|
-
|
|
633
|
-
// Continue to next middleware (including auth) first
|
|
634
|
-
next();
|
|
635
|
-
} else {
|
|
636
|
-
next();
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
this.logger.info(`Router mounted for ${basePath}`, 'Router');
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
private finalModuleHandlerSetup = false;
|
|
644
|
-
|
|
645
|
-
// Setup final module handler that runs after all user middleware
|
|
646
|
-
setupFinalModuleHandler(): void {
|
|
647
|
-
// Prevent duplicate setup
|
|
648
|
-
if (this.finalModuleHandlerSetup) {
|
|
649
|
-
this.logger.debug('Final module handler already set up, skipping', 'ModuleSystem');
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
this.finalModuleHandlerSetup = true;
|
|
653
|
-
|
|
654
|
-
this.logger.info(
|
|
655
|
-
'Setting up final module handler to run after user middleware',
|
|
656
|
-
'ModuleSystem'
|
|
657
|
-
);
|
|
658
|
-
|
|
659
|
-
this.httpServer.use(async (req: HttpRequest, res: HttpResponse, next: () => void) => {
|
|
660
|
-
// Check if this request was marked for module handling
|
|
661
|
-
const moduleBasePath = (req as any).__moduleBasePath;
|
|
662
|
-
const moduleRouter = (req as any).__moduleRouter;
|
|
663
|
-
|
|
664
|
-
if (moduleBasePath && moduleRouter && !res.headersSent) {
|
|
665
|
-
this.logger.debug(`Final module handler processing: ${req.method} ${req.path}`, 'Router');
|
|
666
|
-
|
|
667
525
|
try {
|
|
668
|
-
const handled = await
|
|
669
|
-
this.logger.debug(`Route handled
|
|
526
|
+
const handled = await router.handle(req, res, basePath);
|
|
527
|
+
this.logger.debug(`Route handled: ${handled}`, 'Router');
|
|
670
528
|
|
|
671
529
|
if (!handled) {
|
|
672
530
|
next(); // Let other middleware handle it
|
|
673
531
|
}
|
|
674
532
|
// If handled, the router already sent the response, so don't call next()
|
|
675
533
|
} catch (error) {
|
|
676
|
-
this.logger.error('
|
|
534
|
+
this.logger.error('Router error', 'Router', {
|
|
677
535
|
error: error instanceof Error ? error.message : String(error),
|
|
678
536
|
});
|
|
679
537
|
if (!res.headersSent) {
|
|
@@ -684,6 +542,8 @@ export class Moro extends EventEmitter {
|
|
|
684
542
|
next();
|
|
685
543
|
}
|
|
686
544
|
});
|
|
545
|
+
|
|
546
|
+
this.logger.info(`Router mounted for ${basePath}`, 'Router');
|
|
687
547
|
}
|
|
688
548
|
|
|
689
549
|
private async setupWebSocketHandlers(config: ModuleConfig): Promise<void> {
|
|
@@ -26,11 +26,6 @@ export class MoroHttpServer {
|
|
|
26
26
|
// Request handler pooling to avoid function creation overhead
|
|
27
27
|
private middlewareExecutionCache = new Map<string, Function>();
|
|
28
28
|
|
|
29
|
-
// Response caching for ultra-fast common responses
|
|
30
|
-
private responseCache = new Map<string, Buffer>();
|
|
31
|
-
private responseCacheHits = 0;
|
|
32
|
-
private responseCacheMisses = 0;
|
|
33
|
-
|
|
34
29
|
// String interning for common values (massive memory savings)
|
|
35
30
|
private static readonly INTERNED_METHODS = new Map([
|
|
36
31
|
['GET', 'GET'],
|
|
@@ -286,13 +281,18 @@ export class MoroHttpServer {
|
|
|
286
281
|
await route.handler(httpReq, httpRes);
|
|
287
282
|
} catch (error) {
|
|
288
283
|
// Debug: Log the actual error and where it came from
|
|
289
|
-
this.logger.debug('Request
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
284
|
+
this.logger.debug('🚨 MoroJS Request Error Details:', 'RequestHandler');
|
|
285
|
+
this.logger.debug(`📍 Error type: ${typeof error}`, 'RequestHandler');
|
|
286
|
+
this.logger.debug(
|
|
287
|
+
`📍 Error message: ${error instanceof Error ? error.message : String(error)}`,
|
|
288
|
+
'RequestHandler'
|
|
289
|
+
);
|
|
290
|
+
this.logger.debug(
|
|
291
|
+
`📍 Error stack: ${error instanceof Error ? error.stack : 'No stack trace'}`,
|
|
292
|
+
'RequestHandler'
|
|
293
|
+
);
|
|
294
|
+
this.logger.debug(`📍 Request path: ${req.url}`, 'RequestHandler');
|
|
295
|
+
this.logger.debug(`📍 Request method: ${req.method}`, 'RequestHandler');
|
|
296
296
|
|
|
297
297
|
this.logger.error('Request error', 'RequestHandler', {
|
|
298
298
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -316,10 +316,14 @@ export class MoroHttpServer {
|
|
|
316
316
|
httpRes.setHeader('Content-Type', 'application/json');
|
|
317
317
|
} else {
|
|
318
318
|
// Even setHeader doesn't exist - object is completely wrong
|
|
319
|
-
this.logger.error(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
this.logger.error(
|
|
320
|
+
'❌ Response object is not a proper ServerResponse:',
|
|
321
|
+
'RequestHandler',
|
|
322
|
+
{
|
|
323
|
+
responseType: typeof httpRes,
|
|
324
|
+
responseKeys: Object.keys(httpRes),
|
|
325
|
+
}
|
|
326
|
+
);
|
|
323
327
|
}
|
|
324
328
|
|
|
325
329
|
if (typeof httpRes.end === 'function') {
|
|
@@ -332,7 +336,7 @@ export class MoroHttpServer {
|
|
|
332
336
|
);
|
|
333
337
|
} else {
|
|
334
338
|
this.logger.error(
|
|
335
|
-
'Cannot send error response - end() method missing',
|
|
339
|
+
'❌ Cannot send error response - end() method missing',
|
|
336
340
|
'RequestHandler'
|
|
337
341
|
);
|
|
338
342
|
}
|
|
@@ -497,20 +501,6 @@ export class MoroHttpServer {
|
|
|
497
501
|
httpRes.json = async (data: any) => {
|
|
498
502
|
if (httpRes.headersSent) return;
|
|
499
503
|
|
|
500
|
-
// PERFORMANCE OPTIMIZATION: Check response cache for common patterns
|
|
501
|
-
const cacheKey = this.getResponseCacheKey(data);
|
|
502
|
-
if (cacheKey) {
|
|
503
|
-
const cachedBuffer = this.responseCache.get(cacheKey);
|
|
504
|
-
if (cachedBuffer) {
|
|
505
|
-
this.responseCacheHits++;
|
|
506
|
-
httpRes.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
507
|
-
httpRes.setHeader('Content-Length', cachedBuffer.length);
|
|
508
|
-
httpRes.end(cachedBuffer);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
this.responseCacheMisses++;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
504
|
// Ultra-fast JSON serialization with zero-copy buffers
|
|
515
505
|
let jsonString: string;
|
|
516
506
|
|
|
@@ -597,12 +587,6 @@ export class MoroHttpServer {
|
|
|
597
587
|
});
|
|
598
588
|
|
|
599
589
|
httpRes.end(finalBuffer);
|
|
600
|
-
|
|
601
|
-
// PERFORMANCE OPTIMIZATION: Cache small, common responses
|
|
602
|
-
if (cacheKey && finalBuffer.length < 1024 && this.responseCache.size < 100) {
|
|
603
|
-
this.responseCache.set(cacheKey, Buffer.from(finalBuffer));
|
|
604
|
-
}
|
|
605
|
-
|
|
606
590
|
// Return buffer to pool after response (zero-copy achievement!)
|
|
607
591
|
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
608
592
|
};
|
|
@@ -631,26 +615,6 @@ export class MoroHttpServer {
|
|
|
631
615
|
};
|
|
632
616
|
|
|
633
617
|
httpRes.cookie = (name: string, value: string, options: any = {}) => {
|
|
634
|
-
if (httpRes.headersSent) {
|
|
635
|
-
const isCritical =
|
|
636
|
-
options.critical ||
|
|
637
|
-
name.includes('session') ||
|
|
638
|
-
name.includes('auth') ||
|
|
639
|
-
name.includes('csrf');
|
|
640
|
-
const message = `Cookie '${name}' could not be set - headers already sent`;
|
|
641
|
-
|
|
642
|
-
if (isCritical || options.throwOnLateSet) {
|
|
643
|
-
throw new Error(`${message}. This may cause authentication or security issues.`);
|
|
644
|
-
} else {
|
|
645
|
-
this.logger.warn(message, 'CookieWarning', {
|
|
646
|
-
cookieName: name,
|
|
647
|
-
critical: isCritical,
|
|
648
|
-
stackTrace: new Error().stack,
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
return httpRes;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
618
|
const cookieValue = encodeURIComponent(value);
|
|
655
619
|
let cookieString = `${name}=${cookieValue}`;
|
|
656
620
|
|
|
@@ -714,62 +678,6 @@ export class MoroHttpServer {
|
|
|
714
678
|
}
|
|
715
679
|
};
|
|
716
680
|
|
|
717
|
-
// Header management utilities
|
|
718
|
-
httpRes.hasHeader = (name: string): boolean => {
|
|
719
|
-
return httpRes.getHeader(name) !== undefined;
|
|
720
|
-
};
|
|
721
|
-
|
|
722
|
-
// Note: removeHeader is inherited from ServerResponse, we don't override it
|
|
723
|
-
|
|
724
|
-
httpRes.setBulkHeaders = (headers: Record<string, string | number>) => {
|
|
725
|
-
if (httpRes.headersSent) {
|
|
726
|
-
this.logger.warn('Cannot set headers - headers already sent', 'HeaderWarning', {
|
|
727
|
-
attemptedHeaders: Object.keys(headers),
|
|
728
|
-
});
|
|
729
|
-
return httpRes;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
Object.entries(headers).forEach(([key, value]) => {
|
|
733
|
-
httpRes.setHeader(key, value);
|
|
734
|
-
});
|
|
735
|
-
return httpRes;
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
httpRes.appendHeader = (name: string, value: string | string[]) => {
|
|
739
|
-
if (httpRes.headersSent) {
|
|
740
|
-
this.logger.warn(
|
|
741
|
-
`Cannot append to header '${name}' - headers already sent`,
|
|
742
|
-
'HeaderWarning'
|
|
743
|
-
);
|
|
744
|
-
return httpRes;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
const existing = httpRes.getHeader(name);
|
|
748
|
-
if (existing) {
|
|
749
|
-
const values = Array.isArray(existing) ? existing : [existing.toString()];
|
|
750
|
-
const newValues = Array.isArray(value) ? value : [value];
|
|
751
|
-
httpRes.setHeader(name, [...values, ...newValues]);
|
|
752
|
-
} else {
|
|
753
|
-
httpRes.setHeader(name, value);
|
|
754
|
-
}
|
|
755
|
-
return httpRes;
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
// Response state utilities
|
|
759
|
-
httpRes.canSetHeaders = (): boolean => {
|
|
760
|
-
return !httpRes.headersSent;
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
httpRes.getResponseState = () => {
|
|
764
|
-
return {
|
|
765
|
-
headersSent: httpRes.headersSent,
|
|
766
|
-
statusCode: httpRes.statusCode,
|
|
767
|
-
headers: httpRes.getHeaders ? httpRes.getHeaders() : {},
|
|
768
|
-
finished: httpRes.finished || false,
|
|
769
|
-
writable: httpRes.writable,
|
|
770
|
-
};
|
|
771
|
-
};
|
|
772
|
-
|
|
773
681
|
return httpRes;
|
|
774
682
|
}
|
|
775
683
|
|
|
@@ -1015,9 +923,7 @@ export class MoroHttpServer {
|
|
|
1015
923
|
.then(() => {
|
|
1016
924
|
if (!nextCalled) next();
|
|
1017
925
|
})
|
|
1018
|
-
.catch(
|
|
1019
|
-
reject(error);
|
|
1020
|
-
});
|
|
926
|
+
.catch(reject);
|
|
1021
927
|
}
|
|
1022
928
|
} catch (error) {
|
|
1023
929
|
reject(error);
|
|
@@ -1056,47 +962,6 @@ export class MoroHttpServer {
|
|
|
1056
962
|
getServer(): Server {
|
|
1057
963
|
return this.server;
|
|
1058
964
|
}
|
|
1059
|
-
|
|
1060
|
-
// PERFORMANCE OPTIMIZATION: Generate cache key for common response patterns
|
|
1061
|
-
private getResponseCacheKey(data: any): string | null {
|
|
1062
|
-
// Only cache simple, common responses
|
|
1063
|
-
if (!data || typeof data !== 'object') {
|
|
1064
|
-
// Simple primitives or strings - generate key
|
|
1065
|
-
const key = JSON.stringify(data);
|
|
1066
|
-
return key.length < 100 ? key : null;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// Common API response patterns
|
|
1070
|
-
if ('hello' in data && Object.keys(data).length <= 2) {
|
|
1071
|
-
// Hello world type responses
|
|
1072
|
-
return `hello:${JSON.stringify(data)}`;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
if ('status' in data && Object.keys(data).length <= 3) {
|
|
1076
|
-
// Status responses like {status: "ok", version: "1.0.0"}
|
|
1077
|
-
return `status:${JSON.stringify(data)}`;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
if ('success' in data && 'message' in data && Object.keys(data).length <= 3) {
|
|
1081
|
-
// Simple success/error responses
|
|
1082
|
-
return `msg:${data.success}:${data.message}`;
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// Don't cache complex objects
|
|
1086
|
-
return null;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
// Performance statistics
|
|
1090
|
-
getPerformanceStats() {
|
|
1091
|
-
return {
|
|
1092
|
-
responseCacheHits: this.responseCacheHits,
|
|
1093
|
-
responseCacheMisses: this.responseCacheMisses,
|
|
1094
|
-
responseCacheSize: this.responseCache.size,
|
|
1095
|
-
paramObjectPoolSize: this.paramObjectPool.length,
|
|
1096
|
-
bufferPoolSize: this.bufferPool.length,
|
|
1097
|
-
middlewareExecutionCacheSize: this.middlewareExecutionCache.size,
|
|
1098
|
-
};
|
|
1099
|
-
}
|
|
1100
965
|
}
|
|
1101
966
|
|
|
1102
967
|
// Built-in middleware
|
|
@@ -1153,25 +1018,23 @@ export const middleware = {
|
|
|
1153
1018
|
}
|
|
1154
1019
|
|
|
1155
1020
|
if (acceptEncoding.includes('gzip')) {
|
|
1021
|
+
res.setHeader('Content-Encoding', 'gzip');
|
|
1156
1022
|
zlib.gzip(buffer, { level }, (err: any, compressed: Buffer) => {
|
|
1157
1023
|
if (err) {
|
|
1158
1024
|
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
1159
1025
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
res.setHeader('Content-Length', compressed.length);
|
|
1163
|
-
}
|
|
1026
|
+
res.setHeader('Content-Length', compressed.length);
|
|
1027
|
+
res.writeHead(res.statusCode || 200, res.getHeaders());
|
|
1164
1028
|
res.end(compressed);
|
|
1165
1029
|
});
|
|
1166
1030
|
} else if (acceptEncoding.includes('deflate')) {
|
|
1031
|
+
res.setHeader('Content-Encoding', 'deflate');
|
|
1167
1032
|
zlib.deflate(buffer, { level }, (err: any, compressed: Buffer) => {
|
|
1168
1033
|
if (err) {
|
|
1169
1034
|
return isJson ? originalJson.call(res, data) : originalSend.call(res, data);
|
|
1170
1035
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
res.setHeader('Content-Length', compressed.length);
|
|
1174
|
-
}
|
|
1036
|
+
res.setHeader('Content-Length', compressed.length);
|
|
1037
|
+
res.writeHead(res.statusCode || 200, res.getHeaders());
|
|
1175
1038
|
res.end(compressed);
|
|
1176
1039
|
});
|
|
1177
1040
|
} else {
|
|
@@ -1599,15 +1462,13 @@ export const middleware = {
|
|
|
1599
1462
|
// Only handle SSE requests
|
|
1600
1463
|
if (req.headers.accept?.includes('text/event-stream')) {
|
|
1601
1464
|
// Set SSE headers
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
});
|
|
1610
|
-
}
|
|
1465
|
+
res.writeHead(200, {
|
|
1466
|
+
'Content-Type': 'text/event-stream',
|
|
1467
|
+
'Cache-Control': 'no-cache',
|
|
1468
|
+
Connection: 'keep-alive',
|
|
1469
|
+
'Access-Control-Allow-Origin': options.cors ? '*' : undefined,
|
|
1470
|
+
'Access-Control-Allow-Headers': options.cors ? 'Cache-Control' : undefined,
|
|
1471
|
+
});
|
|
1611
1472
|
|
|
1612
1473
|
// Add SSE methods to response
|
|
1613
1474
|
(res as any).sendEvent = (data: any, event?: string, id?: string) => {
|
|
@@ -1708,8 +1569,7 @@ export const middleware = {
|
|
|
1708
1569
|
const chunkSize = end - start + 1;
|
|
1709
1570
|
|
|
1710
1571
|
if (start >= fileSize || end >= fileSize) {
|
|
1711
|
-
res.status(416);
|
|
1712
|
-
res.setHeader('Content-Range', `bytes */${fileSize}`);
|
|
1572
|
+
res.status(416).setHeader('Content-Range', `bytes */${fileSize}`);
|
|
1713
1573
|
res.json({ success: false, error: 'Range not satisfiable' });
|
|
1714
1574
|
return;
|
|
1715
1575
|
}
|