@morojs/moro 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -7
- package/dist/core/config/types.d.ts +147 -0
- package/dist/core/config/types.js +124 -0
- package/dist/core/config/types.js.map +1 -0
- package/dist/core/config/typescript-loader.d.ts +6 -0
- package/dist/core/config/typescript-loader.js +268 -0
- package/dist/core/config/typescript-loader.js.map +1 -0
- package/dist/core/config/validation.d.ts +18 -0
- package/dist/core/config/validation.js +134 -0
- package/dist/core/config/validation.js.map +1 -0
- package/dist/core/docs/openapi-generator.js +6 -6
- package/dist/core/docs/openapi-generator.js.map +1 -1
- package/dist/core/docs/schema-to-openapi.d.ts +7 -0
- package/dist/core/docs/schema-to-openapi.js +124 -0
- package/dist/core/docs/schema-to-openapi.js.map +1 -0
- package/dist/core/docs/zod-to-openapi.d.ts +2 -0
- package/dist/core/docs/zod-to-openapi.js.map +1 -1
- package/dist/core/framework.d.ts +29 -6
- package/dist/core/framework.js +117 -18
- package/dist/core/framework.js.map +1 -1
- package/dist/core/networking/adapters/index.d.ts +3 -0
- package/dist/core/networking/adapters/index.js +10 -0
- package/dist/core/networking/adapters/index.js.map +1 -0
- package/dist/core/networking/adapters/socketio-adapter.d.ts +16 -0
- package/dist/core/networking/adapters/socketio-adapter.js +244 -0
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -0
- package/dist/core/networking/adapters/ws-adapter.d.ts +54 -0
- package/dist/core/networking/adapters/ws-adapter.js +383 -0
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -0
- package/dist/core/networking/websocket-adapter.d.ts +171 -0
- package/dist/core/networking/websocket-adapter.js +5 -0
- package/dist/core/networking/websocket-adapter.js.map +1 -0
- package/dist/core/networking/websocket-manager.d.ts +53 -17
- package/dist/core/networking/websocket-manager.js +166 -108
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/routing/index.d.ts +13 -13
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/validation/adapters.d.ts +51 -0
- package/dist/core/validation/adapters.js +135 -0
- package/dist/core/validation/adapters.js.map +1 -0
- package/dist/core/validation/index.d.ts +14 -11
- package/dist/core/validation/index.js +37 -26
- package/dist/core/validation/index.js.map +1 -1
- package/dist/core/validation/schema-interface.d.ts +36 -0
- package/dist/core/validation/schema-interface.js +68 -0
- package/dist/core/validation/schema-interface.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/moro.js +8 -2
- package/dist/moro.js.map +1 -1
- package/package.json +31 -7
- package/src/core/config/types.ts +277 -0
- package/src/core/config/typescript-loader.ts +571 -0
- package/src/core/config/validation.ts +145 -0
- package/src/core/docs/openapi-generator.ts +7 -6
- package/src/core/docs/schema-to-openapi.ts +148 -0
- package/src/core/docs/zod-to-openapi.ts +2 -0
- package/src/core/framework.ts +121 -28
- package/src/core/networking/adapters/index.ts +16 -0
- package/src/core/networking/adapters/socketio-adapter.ts +252 -0
- package/src/core/networking/adapters/ws-adapter.ts +425 -0
- package/src/core/networking/websocket-adapter.ts +217 -0
- package/src/core/networking/websocket-manager.ts +185 -127
- package/src/core/routing/index.ts +13 -13
- package/src/core/validation/adapters.ts +147 -0
- package/src/core/validation/index.ts +60 -38
- package/src/core/validation/schema-interface.ts +100 -0
- package/src/index.ts +25 -2
- package/src/moro.ts +11 -2
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// WebSocket Adapter Interface for Moro Framework
|
|
2
|
+
// Provides a common interface for different WebSocket implementations
|
|
3
|
+
|
|
4
|
+
export interface WebSocketAdapterOptions {
|
|
5
|
+
compression?: boolean;
|
|
6
|
+
cors?: {
|
|
7
|
+
origin?: string | string[] | boolean;
|
|
8
|
+
methods?: string[];
|
|
9
|
+
credentials?: boolean;
|
|
10
|
+
};
|
|
11
|
+
path?: string;
|
|
12
|
+
maxPayloadLength?: number;
|
|
13
|
+
idleTimeout?: number;
|
|
14
|
+
[key: string]: any; // Allow adapter-specific options
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Abstract WebSocket adapter interface
|
|
19
|
+
* Allows the framework to work with different WebSocket implementations
|
|
20
|
+
*/
|
|
21
|
+
export interface WebSocketAdapter {
|
|
22
|
+
/**
|
|
23
|
+
* Initialize the WebSocket server with the given HTTP server
|
|
24
|
+
*/
|
|
25
|
+
initialize(httpServer: any, options?: WebSocketAdapterOptions): Promise<void>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a namespace for organizing WebSocket connections
|
|
29
|
+
*/
|
|
30
|
+
createNamespace(namespace: string): WebSocketNamespace;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get the default namespace (usually '/')
|
|
34
|
+
*/
|
|
35
|
+
getDefaultNamespace(): WebSocketNamespace;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Close the WebSocket server and all connections
|
|
39
|
+
*/
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Set compression configuration
|
|
44
|
+
*/
|
|
45
|
+
setCompression(enabled: boolean, options?: any): void;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Set custom ID generator for connections
|
|
49
|
+
*/
|
|
50
|
+
setCustomIdGenerator(generator: () => string): void;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get adapter name/type
|
|
54
|
+
*/
|
|
55
|
+
getAdapterName(): string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get connection count across all namespaces
|
|
59
|
+
*/
|
|
60
|
+
getConnectionCount(): number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* WebSocket namespace interface
|
|
65
|
+
* Represents a logical grouping of WebSocket connections
|
|
66
|
+
*/
|
|
67
|
+
export interface WebSocketNamespace {
|
|
68
|
+
/**
|
|
69
|
+
* Listen for connection events
|
|
70
|
+
*/
|
|
71
|
+
on(event: 'connection', handler: (socket: WebSocketConnection) => void): void;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Emit event to all connected sockets in this namespace
|
|
75
|
+
*/
|
|
76
|
+
emit(event: string, data: any): void;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Emit to specific room(s)
|
|
80
|
+
*/
|
|
81
|
+
to(room: string | string[]): WebSocketEmitter;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Emit to sockets except those in specified room(s)
|
|
85
|
+
*/
|
|
86
|
+
except(room: string | string[]): WebSocketEmitter;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get all connected sockets
|
|
90
|
+
*/
|
|
91
|
+
getSockets(): WebSocketConnection[];
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get connection count for this namespace
|
|
95
|
+
*/
|
|
96
|
+
getConnectionCount(): number;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Use middleware for this namespace
|
|
100
|
+
*/
|
|
101
|
+
use(middleware: (socket: WebSocketConnection, next: (err?: Error) => void) => void): void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* WebSocket connection interface
|
|
106
|
+
* Represents an individual client connection
|
|
107
|
+
*/
|
|
108
|
+
export interface WebSocketConnection {
|
|
109
|
+
/** Unique connection ID */
|
|
110
|
+
id: string;
|
|
111
|
+
|
|
112
|
+
/** Client IP address */
|
|
113
|
+
ip?: string;
|
|
114
|
+
|
|
115
|
+
/** Connection headers */
|
|
116
|
+
headers?: Record<string, string>;
|
|
117
|
+
|
|
118
|
+
/** Custom data storage */
|
|
119
|
+
data: Record<string, any>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Listen for events from this socket
|
|
123
|
+
*/
|
|
124
|
+
on(event: string, handler: (data: any, callback?: (response?: any) => void) => void): void;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Listen for any event from this socket
|
|
128
|
+
*/
|
|
129
|
+
onAny(handler: (event: string, ...args: any[]) => void): void;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Emit event to this socket
|
|
133
|
+
*/
|
|
134
|
+
emit(event: string, data: any): void;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Emit with compression
|
|
138
|
+
*/
|
|
139
|
+
compressedEmit?(event: string, data: any): void;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Join a room
|
|
143
|
+
*/
|
|
144
|
+
join(room: string | string[]): void;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Leave a room
|
|
148
|
+
*/
|
|
149
|
+
leave(room: string | string[]): void;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Emit to specific room(s)
|
|
153
|
+
*/
|
|
154
|
+
to(room: string | string[]): WebSocketEmitter;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Emit to all sockets except this one
|
|
158
|
+
*/
|
|
159
|
+
broadcast: WebSocketEmitter;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get rooms this socket has joined
|
|
163
|
+
*/
|
|
164
|
+
getRooms(): Set<string>;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Disconnect this socket
|
|
168
|
+
*/
|
|
169
|
+
disconnect(close?: boolean): void;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if socket is connected
|
|
173
|
+
*/
|
|
174
|
+
connected: boolean;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* WebSocket emitter interface for chaining operations
|
|
179
|
+
*/
|
|
180
|
+
export interface WebSocketEmitter {
|
|
181
|
+
/**
|
|
182
|
+
* Emit to target sockets
|
|
183
|
+
*/
|
|
184
|
+
emit(event: string, data: any): void;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Target specific room(s)
|
|
188
|
+
*/
|
|
189
|
+
to(room: string | string[]): WebSocketEmitter;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Exclude specific room(s)
|
|
193
|
+
*/
|
|
194
|
+
except(room: string | string[]): WebSocketEmitter;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Use compression for this emit
|
|
198
|
+
*/
|
|
199
|
+
compress(compress: boolean): WebSocketEmitter;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* WebSocket middleware function type
|
|
204
|
+
*/
|
|
205
|
+
export type WebSocketMiddleware = (
|
|
206
|
+
socket: WebSocketConnection,
|
|
207
|
+
next: (err?: Error) => void
|
|
208
|
+
) => void;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* WebSocket event handler type
|
|
212
|
+
*/
|
|
213
|
+
export type WebSocketEventHandler = (
|
|
214
|
+
socket: WebSocketConnection,
|
|
215
|
+
data: any,
|
|
216
|
+
callback?: (response?: any) => void
|
|
217
|
+
) => void | Promise<void>;
|
|
@@ -1,115 +1,114 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// WebSocket Manager for Moro Framework
|
|
2
|
+
// Manages WebSocket connections using pluggable adapters
|
|
3
3
|
import { Container } from '../utilities';
|
|
4
4
|
import { CircuitBreaker } from '../utilities';
|
|
5
5
|
import { ModuleConfig, WebSocketDefinition } from '../../types/module';
|
|
6
|
+
import { WebSocketAdapter, WebSocketNamespace, WebSocketConnection } from './websocket-adapter';
|
|
7
|
+
import { createFrameworkLogger } from '../logger';
|
|
6
8
|
|
|
7
9
|
export class WebSocketManager {
|
|
8
10
|
private circuitBreakers = new Map<string, CircuitBreaker>();
|
|
9
11
|
private rateLimiters = new Map<string, Map<string, { count: number; resetTime: number }>>();
|
|
10
|
-
private
|
|
11
|
-
private customIdGenerator?: () => string;
|
|
12
|
+
private logger = createFrameworkLogger('WebSocketManager');
|
|
12
13
|
|
|
13
14
|
constructor(
|
|
14
|
-
private
|
|
15
|
+
private adapter: WebSocketAdapter,
|
|
15
16
|
private container: Container
|
|
16
17
|
) {
|
|
17
|
-
this.
|
|
18
|
+
this.logger.info(`Initialized with ${adapter.getAdapterName()} adapter`, 'AdapterInit');
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
threshold: 1024,
|
|
26
|
-
concurrencyLimit: 10,
|
|
27
|
-
memLevel: 8,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Custom session ID generation if provided
|
|
32
|
-
if (this.customIdGenerator) {
|
|
33
|
-
(this.io.engine as any).generateId = this.customIdGenerator;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Global WebSocket middleware for advanced features
|
|
37
|
-
this.io.use((socket, next) => {
|
|
38
|
-
// Add binary message handling
|
|
39
|
-
socket.onAny((event, ...args) => {
|
|
40
|
-
// Handle binary frames efficiently
|
|
41
|
-
args.forEach((arg, index) => {
|
|
42
|
-
if (Buffer.isBuffer(arg)) {
|
|
43
|
-
// Process binary data with optimizations
|
|
44
|
-
args[index] = this.processBinaryData(arg);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Add compression per message
|
|
50
|
-
(socket as any).compressedEmit = (event: string, data: any) => {
|
|
51
|
-
if (this.compressionEnabled && this.shouldCompress(data)) {
|
|
52
|
-
socket.compress(true).emit(event, data);
|
|
53
|
-
} else {
|
|
54
|
-
socket.emit(event, data);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Add heartbeat mechanism
|
|
59
|
-
(socket as any).heartbeat = () => {
|
|
60
|
-
socket.emit('heartbeat', { timestamp: Date.now() });
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
next();
|
|
64
|
-
});
|
|
21
|
+
/**
|
|
22
|
+
* Set custom ID generator for the adapter
|
|
23
|
+
*/
|
|
24
|
+
setCustomIdGenerator(generator: () => string): void {
|
|
25
|
+
this.adapter.setCustomIdGenerator(generator);
|
|
65
26
|
}
|
|
66
27
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Enable compression for the adapter
|
|
30
|
+
*/
|
|
31
|
+
enableCompression(options?: any): void {
|
|
32
|
+
this.adapter.setCompression(true, options);
|
|
70
33
|
}
|
|
71
34
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.compressionEnabled = true;
|
|
78
|
-
if (options) {
|
|
79
|
-
(this.io.engine as any).perMessageDeflate = {
|
|
80
|
-
threshold: options.threshold || 1024,
|
|
81
|
-
concurrencyLimit: options.concurrencyLimit || 10,
|
|
82
|
-
memLevel: options.memLevel || 8,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the underlying adapter
|
|
37
|
+
*/
|
|
38
|
+
getAdapter(): WebSocketAdapter {
|
|
39
|
+
return this.adapter;
|
|
85
40
|
}
|
|
86
41
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Get connection count across all namespaces
|
|
44
|
+
*/
|
|
45
|
+
getConnectionCount(): number {
|
|
46
|
+
return this.adapter.getConnectionCount();
|
|
91
47
|
}
|
|
92
48
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Create or get a namespace
|
|
51
|
+
*/
|
|
52
|
+
getNamespace(namespace: string): WebSocketNamespace {
|
|
53
|
+
return this.adapter.createNamespace(namespace);
|
|
97
54
|
}
|
|
98
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Register WebSocket handlers for a module
|
|
58
|
+
*/
|
|
99
59
|
async registerHandler(
|
|
100
|
-
namespace:
|
|
60
|
+
namespace: WebSocketNamespace,
|
|
101
61
|
wsConfig: WebSocketDefinition,
|
|
102
62
|
moduleConfig: ModuleConfig
|
|
103
63
|
): Promise<void> {
|
|
104
|
-
namespace.on('connection', (socket:
|
|
105
|
-
|
|
106
|
-
|
|
64
|
+
namespace.on('connection', (socket: WebSocketConnection) => {
|
|
65
|
+
this.logger.debug(`New connection: ${socket.id}`, 'Connection', {
|
|
66
|
+
namespace: namespace.constructor.name,
|
|
67
|
+
module: moduleConfig.name,
|
|
68
|
+
});
|
|
69
|
+
|
|
107
70
|
this.setupSocketMiddleware(socket, moduleConfig.name);
|
|
71
|
+
this.setupSocketHandlers(socket, wsConfig, moduleConfig);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Setup socket-specific middleware
|
|
77
|
+
*/
|
|
78
|
+
private setupSocketMiddleware(socket: WebSocketConnection, moduleName: string): void {
|
|
79
|
+
// Add module context to socket data
|
|
80
|
+
socket.data.module = moduleName;
|
|
81
|
+
socket.data.connectedAt = Date.now();
|
|
82
|
+
|
|
83
|
+
// Setup heartbeat if supported
|
|
84
|
+
if (socket.compressedEmit) {
|
|
85
|
+
const heartbeatInterval = setInterval(() => {
|
|
86
|
+
if (socket.connected) {
|
|
87
|
+
socket.emit('heartbeat', { timestamp: Date.now() });
|
|
88
|
+
} else {
|
|
89
|
+
clearInterval(heartbeatInterval);
|
|
90
|
+
}
|
|
91
|
+
}, 30000); // 30 seconds
|
|
92
|
+
|
|
93
|
+
socket.on('disconnect', () => {
|
|
94
|
+
clearInterval(heartbeatInterval);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Log disconnection
|
|
99
|
+
socket.on('disconnect', () => {
|
|
100
|
+
this.logger.debug(`Socket disconnected: ${socket.id}`, 'Disconnect', {
|
|
101
|
+
module: moduleName,
|
|
102
|
+
duration: Date.now() - socket.data.connectedAt,
|
|
103
|
+
});
|
|
108
104
|
});
|
|
109
105
|
}
|
|
110
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Setup handlers for a specific WebSocket configuration
|
|
109
|
+
*/
|
|
111
110
|
private setupSocketHandlers(
|
|
112
|
-
socket:
|
|
111
|
+
socket: WebSocketConnection,
|
|
113
112
|
wsConfig: WebSocketDefinition,
|
|
114
113
|
moduleConfig: ModuleConfig
|
|
115
114
|
): void {
|
|
@@ -129,7 +128,7 @@ export class WebSocketManager {
|
|
|
129
128
|
return;
|
|
130
129
|
}
|
|
131
130
|
|
|
132
|
-
// Validation
|
|
131
|
+
// Validation using Zod
|
|
133
132
|
if (wsConfig.validation) {
|
|
134
133
|
try {
|
|
135
134
|
data = wsConfig.validation.parse(data);
|
|
@@ -163,42 +162,56 @@ export class WebSocketManager {
|
|
|
163
162
|
// Handle response
|
|
164
163
|
if (callback) {
|
|
165
164
|
callback({ success: true, data: result });
|
|
166
|
-
} else if (
|
|
167
|
-
socket.
|
|
168
|
-
}
|
|
169
|
-
|
|
165
|
+
} else if (result !== undefined) {
|
|
166
|
+
socket.emit(`${wsConfig.event}:response`, { success: true, data: result });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Handle room operations
|
|
170
|
+
if (wsConfig.rooms) {
|
|
171
|
+
wsConfig.rooms.forEach(room => socket.join(room));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Handle broadcasting
|
|
175
|
+
if (wsConfig.broadcast && result !== undefined) {
|
|
176
|
+
socket.broadcast.emit(wsConfig.event, { success: true, data: result });
|
|
170
177
|
}
|
|
171
178
|
} catch (error) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
this.logger.error('WebSocket handler error', 'HandlerError', {
|
|
180
|
+
handler: handlerKey,
|
|
181
|
+
error: error instanceof Error ? error.message : String(error),
|
|
182
|
+
socketId: socket.id,
|
|
183
|
+
});
|
|
175
184
|
|
|
176
185
|
const errorResponse = {
|
|
177
186
|
success: false,
|
|
178
|
-
error:
|
|
179
|
-
code:
|
|
187
|
+
error: 'Internal server error',
|
|
188
|
+
code: 'HANDLER_ERROR',
|
|
180
189
|
};
|
|
181
190
|
|
|
182
|
-
if (callback)
|
|
183
|
-
|
|
184
|
-
} else {
|
|
185
|
-
socket.emit('error', errorResponse);
|
|
186
|
-
}
|
|
191
|
+
if (callback) callback(errorResponse);
|
|
192
|
+
else socket.emit('error', errorResponse);
|
|
187
193
|
}
|
|
188
194
|
});
|
|
189
195
|
}
|
|
190
196
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Get or create circuit breaker for handler
|
|
199
|
+
*/
|
|
200
|
+
private getCircuitBreaker(handlerKey: string): CircuitBreaker {
|
|
201
|
+
if (!this.circuitBreakers.has(handlerKey)) {
|
|
202
|
+
const circuitBreaker = new CircuitBreaker({
|
|
203
|
+
failureThreshold: 5,
|
|
204
|
+
resetTimeout: 60000,
|
|
205
|
+
monitoringPeriod: 10000,
|
|
206
|
+
});
|
|
207
|
+
this.circuitBreakers.set(handlerKey, circuitBreaker);
|
|
208
|
+
}
|
|
209
|
+
return this.circuitBreakers.get(handlerKey)!;
|
|
200
210
|
}
|
|
201
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Check rate limit for socket and handler
|
|
214
|
+
*/
|
|
202
215
|
private checkRateLimit(
|
|
203
216
|
socketId: string,
|
|
204
217
|
handlerKey: string,
|
|
@@ -210,41 +223,86 @@ export class WebSocketManager {
|
|
|
210
223
|
|
|
211
224
|
const handlerLimiter = this.rateLimiters.get(handlerKey)!;
|
|
212
225
|
const now = Date.now();
|
|
213
|
-
const
|
|
226
|
+
const windowStart = now - rateLimit.window;
|
|
214
227
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
228
|
+
// Clean old entries
|
|
229
|
+
for (const [id, data] of handlerLimiter.entries()) {
|
|
230
|
+
if (data.resetTime < windowStart) {
|
|
231
|
+
handlerLimiter.delete(id);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Check current socket
|
|
236
|
+
const socketData = handlerLimiter.get(socketId);
|
|
237
|
+
if (!socketData) {
|
|
238
|
+
handlerLimiter.set(socketId, { count: 1, resetTime: now });
|
|
220
239
|
return true;
|
|
221
240
|
}
|
|
222
241
|
|
|
223
|
-
if (
|
|
242
|
+
if (socketData.resetTime < windowStart) {
|
|
243
|
+
socketData.count = 1;
|
|
244
|
+
socketData.resetTime = now;
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (socketData.count >= rateLimit.requests) {
|
|
224
249
|
return false;
|
|
225
250
|
}
|
|
226
251
|
|
|
227
|
-
|
|
252
|
+
socketData.count++;
|
|
228
253
|
return true;
|
|
229
254
|
}
|
|
230
255
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Process binary data efficiently
|
|
258
|
+
*/
|
|
259
|
+
private processBinaryData(buffer: Buffer): any {
|
|
260
|
+
// Example binary processing - can be extended based on needs
|
|
261
|
+
if (buffer.length === 0) return buffer;
|
|
262
|
+
|
|
263
|
+
// Simple compression check
|
|
264
|
+
if (buffer[0] === 0x1f && buffer[1] === 0x8b) {
|
|
265
|
+
// This is gzipped data
|
|
266
|
+
try {
|
|
267
|
+
const zlib = require('zlib');
|
|
268
|
+
return zlib.gunzipSync(buffer);
|
|
269
|
+
} catch {
|
|
270
|
+
return buffer;
|
|
271
|
+
}
|
|
241
272
|
}
|
|
242
|
-
|
|
273
|
+
|
|
274
|
+
return buffer;
|
|
243
275
|
}
|
|
244
276
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Check if data should be compressed
|
|
279
|
+
*/
|
|
280
|
+
private shouldCompress(data: any): boolean {
|
|
281
|
+
if (typeof data === 'string') {
|
|
282
|
+
return data.length > 1024;
|
|
283
|
+
}
|
|
284
|
+
if (Buffer.isBuffer(data)) {
|
|
285
|
+
return data.length > 1024;
|
|
286
|
+
}
|
|
287
|
+
if (typeof data === 'object') {
|
|
288
|
+
return JSON.stringify(data).length > 1024;
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Close the WebSocket manager and underlying adapter
|
|
295
|
+
*/
|
|
296
|
+
async close(): Promise<void> {
|
|
297
|
+
this.logger.info('Closing WebSocket manager', 'Shutdown');
|
|
298
|
+
|
|
299
|
+
// Clear rate limiters
|
|
300
|
+
this.rateLimiters.clear();
|
|
301
|
+
|
|
302
|
+
// Clear circuit breakers
|
|
303
|
+
this.circuitBreakers.clear();
|
|
304
|
+
|
|
305
|
+
// Close adapter
|
|
306
|
+
await this.adapter.close();
|
|
249
307
|
}
|
|
250
308
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Intelligent Routing System for Moro Framework
|
|
2
2
|
// Schema-first with automatic middleware ordering and chainable API
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { ValidationSchema } from '../validation/schema-interface';
|
|
5
5
|
import { HttpRequest, HttpResponse } from '../http';
|
|
6
6
|
import { createFrameworkLogger } from '../logger';
|
|
7
7
|
|
|
@@ -18,10 +18,10 @@ export type Middleware = (
|
|
|
18
18
|
|
|
19
19
|
// Configuration interfaces
|
|
20
20
|
export interface ValidationConfig {
|
|
21
|
-
body?:
|
|
22
|
-
query?:
|
|
23
|
-
params?:
|
|
24
|
-
headers?:
|
|
21
|
+
body?: ValidationSchema;
|
|
22
|
+
query?: ValidationSchema;
|
|
23
|
+
params?: ValidationSchema;
|
|
24
|
+
headers?: ValidationSchema;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface AuthConfig {
|
|
@@ -98,10 +98,10 @@ export interface ValidatedRequest<T = any> extends HttpRequest {
|
|
|
98
98
|
export interface RouteBuilder {
|
|
99
99
|
// Validation methods
|
|
100
100
|
validate(config: ValidationConfig): RouteBuilder;
|
|
101
|
-
body<T>(schema:
|
|
102
|
-
query<T>(schema:
|
|
103
|
-
params<T>(schema:
|
|
104
|
-
headers<T>(schema:
|
|
101
|
+
body<T>(schema: ValidationSchema<T>): RouteBuilder;
|
|
102
|
+
query<T>(schema: ValidationSchema<T>): RouteBuilder;
|
|
103
|
+
params<T>(schema: ValidationSchema<T>): RouteBuilder;
|
|
104
|
+
headers<T>(schema: ValidationSchema<T>): RouteBuilder;
|
|
105
105
|
|
|
106
106
|
// Security/Auth methods
|
|
107
107
|
auth(config: AuthConfig): RouteBuilder;
|
|
@@ -148,25 +148,25 @@ export class IntelligentRouteBuilder implements RouteBuilder {
|
|
|
148
148
|
return this;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
body<T>(schema:
|
|
151
|
+
body<T>(schema: ValidationSchema<T>): RouteBuilder {
|
|
152
152
|
if (!this.schema.validation) this.schema.validation = {};
|
|
153
153
|
this.schema.validation.body = schema;
|
|
154
154
|
return this;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
query<T>(schema:
|
|
157
|
+
query<T>(schema: ValidationSchema<T>): RouteBuilder {
|
|
158
158
|
if (!this.schema.validation) this.schema.validation = {};
|
|
159
159
|
this.schema.validation.query = schema;
|
|
160
160
|
return this;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
params<T>(schema:
|
|
163
|
+
params<T>(schema: ValidationSchema<T>): RouteBuilder {
|
|
164
164
|
if (!this.schema.validation) this.schema.validation = {};
|
|
165
165
|
this.schema.validation.params = schema;
|
|
166
166
|
return this;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
headers<T>(schema:
|
|
169
|
+
headers<T>(schema: ValidationSchema<T>): RouteBuilder {
|
|
170
170
|
if (!this.schema.validation) this.schema.validation = {};
|
|
171
171
|
this.schema.validation.headers = schema;
|
|
172
172
|
return this;
|