@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.
Files changed (70) hide show
  1. package/README.md +61 -7
  2. package/dist/core/config/types.d.ts +147 -0
  3. package/dist/core/config/types.js +124 -0
  4. package/dist/core/config/types.js.map +1 -0
  5. package/dist/core/config/typescript-loader.d.ts +6 -0
  6. package/dist/core/config/typescript-loader.js +268 -0
  7. package/dist/core/config/typescript-loader.js.map +1 -0
  8. package/dist/core/config/validation.d.ts +18 -0
  9. package/dist/core/config/validation.js +134 -0
  10. package/dist/core/config/validation.js.map +1 -0
  11. package/dist/core/docs/openapi-generator.js +6 -6
  12. package/dist/core/docs/openapi-generator.js.map +1 -1
  13. package/dist/core/docs/schema-to-openapi.d.ts +7 -0
  14. package/dist/core/docs/schema-to-openapi.js +124 -0
  15. package/dist/core/docs/schema-to-openapi.js.map +1 -0
  16. package/dist/core/docs/zod-to-openapi.d.ts +2 -0
  17. package/dist/core/docs/zod-to-openapi.js.map +1 -1
  18. package/dist/core/framework.d.ts +29 -6
  19. package/dist/core/framework.js +117 -18
  20. package/dist/core/framework.js.map +1 -1
  21. package/dist/core/networking/adapters/index.d.ts +3 -0
  22. package/dist/core/networking/adapters/index.js +10 -0
  23. package/dist/core/networking/adapters/index.js.map +1 -0
  24. package/dist/core/networking/adapters/socketio-adapter.d.ts +16 -0
  25. package/dist/core/networking/adapters/socketio-adapter.js +244 -0
  26. package/dist/core/networking/adapters/socketio-adapter.js.map +1 -0
  27. package/dist/core/networking/adapters/ws-adapter.d.ts +54 -0
  28. package/dist/core/networking/adapters/ws-adapter.js +383 -0
  29. package/dist/core/networking/adapters/ws-adapter.js.map +1 -0
  30. package/dist/core/networking/websocket-adapter.d.ts +171 -0
  31. package/dist/core/networking/websocket-adapter.js +5 -0
  32. package/dist/core/networking/websocket-adapter.js.map +1 -0
  33. package/dist/core/networking/websocket-manager.d.ts +53 -17
  34. package/dist/core/networking/websocket-manager.js +166 -108
  35. package/dist/core/networking/websocket-manager.js.map +1 -1
  36. package/dist/core/routing/index.d.ts +13 -13
  37. package/dist/core/routing/index.js.map +1 -1
  38. package/dist/core/validation/adapters.d.ts +51 -0
  39. package/dist/core/validation/adapters.js +135 -0
  40. package/dist/core/validation/adapters.js.map +1 -0
  41. package/dist/core/validation/index.d.ts +14 -11
  42. package/dist/core/validation/index.js +37 -26
  43. package/dist/core/validation/index.js.map +1 -1
  44. package/dist/core/validation/schema-interface.d.ts +36 -0
  45. package/dist/core/validation/schema-interface.js +68 -0
  46. package/dist/core/validation/schema-interface.js.map +1 -0
  47. package/dist/index.d.ts +6 -1
  48. package/dist/index.js +14 -3
  49. package/dist/index.js.map +1 -1
  50. package/dist/moro.js +8 -2
  51. package/dist/moro.js.map +1 -1
  52. package/package.json +31 -7
  53. package/src/core/config/types.ts +277 -0
  54. package/src/core/config/typescript-loader.ts +571 -0
  55. package/src/core/config/validation.ts +145 -0
  56. package/src/core/docs/openapi-generator.ts +7 -6
  57. package/src/core/docs/schema-to-openapi.ts +148 -0
  58. package/src/core/docs/zod-to-openapi.ts +2 -0
  59. package/src/core/framework.ts +121 -28
  60. package/src/core/networking/adapters/index.ts +16 -0
  61. package/src/core/networking/adapters/socketio-adapter.ts +252 -0
  62. package/src/core/networking/adapters/ws-adapter.ts +425 -0
  63. package/src/core/networking/websocket-adapter.ts +217 -0
  64. package/src/core/networking/websocket-manager.ts +185 -127
  65. package/src/core/routing/index.ts +13 -13
  66. package/src/core/validation/adapters.ts +147 -0
  67. package/src/core/validation/index.ts +60 -38
  68. package/src/core/validation/schema-interface.ts +100 -0
  69. package/src/index.ts +25 -2
  70. package/src/moro.ts +11 -2
@@ -0,0 +1,145 @@
1
+ // TypeScript-based Configuration Validation
2
+ // Simple validation functions that replace Zod for config system
3
+
4
+ export class ConfigValidationError extends Error {
5
+ constructor(
6
+ public field: string,
7
+ public value: unknown,
8
+ message: string
9
+ ) {
10
+ super(`Configuration validation failed for '${field}': ${message}`);
11
+ this.name = 'ConfigValidationError';
12
+ }
13
+ }
14
+
15
+ // Type-safe validation functions
16
+ export function validatePort(value: unknown, field = 'port'): number {
17
+ const num = Number(value);
18
+ if (isNaN(num) || num < 1 || num > 65535) {
19
+ throw new ConfigValidationError(field, value, 'Must be a number between 1 and 65535');
20
+ }
21
+ return num;
22
+ }
23
+
24
+ export function validateBoolean(value: unknown, field = 'boolean'): boolean {
25
+ if (value === 'true' || value === true) return true;
26
+ if (value === 'false' || value === false) return false;
27
+ if (value === '1' || value === 1) return true;
28
+ if (value === '0' || value === 0) return false;
29
+ throw new ConfigValidationError(field, value, 'Must be a boolean (true/false) or numeric (1/0)');
30
+ }
31
+
32
+ export function validateNumber(
33
+ value: unknown,
34
+ field = 'number',
35
+ options: { min?: number; max?: number } = {}
36
+ ): number {
37
+ const num = Number(value);
38
+ if (isNaN(num)) {
39
+ throw new ConfigValidationError(field, value, 'Must be a valid number');
40
+ }
41
+ if (options.min !== undefined && num < options.min) {
42
+ throw new ConfigValidationError(field, value, `Must be at least ${options.min}`);
43
+ }
44
+ if (options.max !== undefined && num > options.max) {
45
+ throw new ConfigValidationError(field, value, `Must be at most ${options.max}`);
46
+ }
47
+ return num;
48
+ }
49
+
50
+ export function validateString(value: unknown, field = 'string'): string {
51
+ if (typeof value !== 'string') {
52
+ throw new ConfigValidationError(field, value, 'Must be a string');
53
+ }
54
+ return value;
55
+ }
56
+
57
+ export function validateUrl(value: unknown, field = 'url'): string {
58
+ const str = validateString(value, field);
59
+ try {
60
+ new URL(str);
61
+ return str;
62
+ } catch {
63
+ throw new ConfigValidationError(field, value, 'Must be a valid URL');
64
+ }
65
+ }
66
+
67
+ export function validateEnum<T extends string>(
68
+ value: unknown,
69
+ validValues: readonly T[],
70
+ field = 'enum'
71
+ ): T {
72
+ const str = validateString(value, field);
73
+ if (!validValues.includes(str as T)) {
74
+ throw new ConfigValidationError(field, value, `Must be one of: ${validValues.join(', ')}`);
75
+ }
76
+ return str as T;
77
+ }
78
+
79
+ export function validateArray(value: unknown, field = 'array'): unknown[] {
80
+ if (!Array.isArray(value)) {
81
+ // Try to parse comma-separated string
82
+ if (typeof value === 'string') {
83
+ return value
84
+ .split(',')
85
+ .map(s => s.trim())
86
+ .filter(s => s.length > 0);
87
+ }
88
+ throw new ConfigValidationError(field, value, 'Must be an array or comma-separated string');
89
+ }
90
+ return value;
91
+ }
92
+
93
+ export function validateStringArray(value: unknown, field = 'string array'): string[] {
94
+ const arr = validateArray(value, field);
95
+ return arr.map((item, index) => validateString(item, `${field}[${index}]`));
96
+ }
97
+
98
+ export function validateOptional<T>(
99
+ value: unknown,
100
+ validator: (value: unknown, field: string) => T,
101
+ field: string
102
+ ): T | undefined {
103
+ if (value === undefined || value === null || value === '') {
104
+ return undefined;
105
+ }
106
+ return validator(value, field);
107
+ }
108
+
109
+ // Coercion helpers for environment variables
110
+ export function coerceEnvValue(value: string): unknown {
111
+ // Handle common patterns in environment variables
112
+
113
+ // Null/undefined
114
+ if (value === '' || value === 'null' || value === 'undefined') {
115
+ return undefined;
116
+ }
117
+
118
+ // Boolean
119
+ if (value === 'true' || value === 'false') {
120
+ return value === 'true';
121
+ }
122
+
123
+ // Number (but not if it starts with 0 - could be port, zip code, etc.)
124
+ if (/^-?\d+(\.\d+)?$/.test(value) && !value.startsWith('0')) {
125
+ const num = Number(value);
126
+ if (!isNaN(num)) {
127
+ return num;
128
+ }
129
+ }
130
+
131
+ // JSON (for complex objects/arrays)
132
+ if (
133
+ (value.startsWith('{') && value.endsWith('}')) ||
134
+ (value.startsWith('[') && value.endsWith(']'))
135
+ ) {
136
+ try {
137
+ return JSON.parse(value);
138
+ } catch {
139
+ // Not valid JSON, treat as string
140
+ }
141
+ }
142
+
143
+ // Return as string for all other cases
144
+ return value;
145
+ }
@@ -2,7 +2,8 @@
2
2
  // Extracts route information from intelligent routing and generates OpenAPI 3.0 specs
3
3
 
4
4
  import { CompiledRoute, RouteSchema } from '../routing';
5
- import { zodToOpenAPI, generateExampleFromSchema, OpenAPISchema } from './zod-to-openapi';
5
+ import { OpenAPISchema } from './zod-to-openapi';
6
+ import { schemaToOpenAPI, generateExampleFromValidationSchema } from './schema-to-openapi';
6
7
  import { createFrameworkLogger } from '../logger';
7
8
 
8
9
  const logger = createFrameworkLogger('OpenAPIGenerator');
@@ -241,7 +242,7 @@ export class OpenAPIGenerator {
241
242
 
242
243
  // Path parameters
243
244
  if (route.validation?.params) {
244
- const paramSchema = zodToOpenAPI(route.validation.params, this.options);
245
+ const paramSchema = schemaToOpenAPI(route.validation.params, this.options);
245
246
  if (paramSchema.properties) {
246
247
  for (const [name, schema] of Object.entries(paramSchema.properties)) {
247
248
  parameters.push({
@@ -258,7 +259,7 @@ export class OpenAPIGenerator {
258
259
 
259
260
  // Query parameters
260
261
  if (route.validation?.query) {
261
- const querySchema = zodToOpenAPI(route.validation.query, this.options);
262
+ const querySchema = schemaToOpenAPI(route.validation.query, this.options);
262
263
  if (querySchema.properties) {
263
264
  for (const [name, schema] of Object.entries(querySchema.properties)) {
264
265
  const isRequired = querySchema.required?.includes(name) || false;
@@ -276,7 +277,7 @@ export class OpenAPIGenerator {
276
277
 
277
278
  // Header parameters
278
279
  if (route.validation?.headers) {
279
- const headerSchema = zodToOpenAPI(route.validation.headers, this.options);
280
+ const headerSchema = schemaToOpenAPI(route.validation.headers, this.options);
280
281
  if (headerSchema.properties) {
281
282
  for (const [name, schema] of Object.entries(headerSchema.properties)) {
282
283
  const isRequired = headerSchema.required?.includes(name) || false;
@@ -309,9 +310,9 @@ export class OpenAPIGenerator {
309
310
  };
310
311
  }
311
312
 
312
- const bodySchema = zodToOpenAPI(route.validation.body, this.options);
313
+ const bodySchema = schemaToOpenAPI(route.validation.body, this.options);
313
314
  const example = this.options.includeExamples
314
- ? generateExampleFromSchema(route.validation.body)
315
+ ? generateExampleFromValidationSchema(route.validation.body)
315
316
  : undefined;
316
317
 
317
318
  return {
@@ -0,0 +1,148 @@
1
+ // Universal Schema to OpenAPI Converter
2
+ // Converts ValidationSchema (Zod, Joi, etc.) to OpenAPI 3.0 schema definitions
3
+
4
+ import { ValidationSchema } from '../validation/schema-interface';
5
+ import { OpenAPISchema } from './zod-to-openapi';
6
+ import { createFrameworkLogger } from '../logger';
7
+
8
+ const logger = createFrameworkLogger('SchemaToOpenAPI');
9
+
10
+ // Check if a schema is a Zod schema
11
+ function isZodSchema(schema: any): boolean {
12
+ return (
13
+ schema && typeof schema === 'object' && schema._def && typeof schema.parseAsync === 'function'
14
+ );
15
+ }
16
+
17
+ // Check if schema is Joi
18
+ function isJoiSchema(schema: any): boolean {
19
+ return (
20
+ schema &&
21
+ typeof schema === 'object' &&
22
+ schema.type &&
23
+ typeof schema.validateAsync === 'function'
24
+ );
25
+ }
26
+
27
+ // Convert any ValidationSchema to OpenAPI
28
+ export function schemaToOpenAPI(
29
+ schema: ValidationSchema,
30
+ options: { includeExamples?: boolean; includeDescriptions?: boolean } = {}
31
+ ): OpenAPISchema {
32
+ // If it's a Zod schema, use the existing zod converter
33
+ if (isZodSchema(schema)) {
34
+ try {
35
+ // Import zod converter dynamically
36
+ const { zodToOpenAPI } = require('./zod-to-openapi');
37
+ return zodToOpenAPI(schema, options);
38
+ } catch (error) {
39
+ logger.warn('Zod converter not available, using fallback', String(error));
40
+ }
41
+ }
42
+
43
+ // If it's a Joi schema, convert from Joi
44
+ if (isJoiSchema(schema)) {
45
+ return convertJoiToOpenAPI(schema as any, options);
46
+ }
47
+
48
+ // For other schemas (custom validators, etc.), return a generic object schema
49
+ logger.debug('Using generic schema conversion for unknown validation type');
50
+ return {
51
+ type: 'object',
52
+ description: options.includeDescriptions ? 'Validated object' : undefined,
53
+ additionalProperties: true,
54
+ };
55
+ }
56
+
57
+ // Generate example from any ValidationSchema
58
+ export function generateExampleFromValidationSchema(schema: ValidationSchema): any {
59
+ // If it's a Zod schema, use existing example generator
60
+ if (isZodSchema(schema)) {
61
+ try {
62
+ const { generateExampleFromSchema } = require('./zod-to-openapi');
63
+ return generateExampleFromSchema(schema);
64
+ } catch (error) {
65
+ logger.warn('Zod example generator not available', String(error));
66
+ }
67
+ }
68
+
69
+ // For other schemas, return a generic example
70
+ return {
71
+ example: 'Validated data structure',
72
+ };
73
+ }
74
+
75
+ // Convert Joi schema to OpenAPI (basic implementation)
76
+ function convertJoiToOpenAPI(
77
+ joiSchema: any,
78
+ options: { includeDescriptions?: boolean }
79
+ ): OpenAPISchema {
80
+ const schemaType = joiSchema.type;
81
+
82
+ switch (schemaType) {
83
+ case 'string':
84
+ return {
85
+ type: 'string',
86
+ description: options.includeDescriptions ? joiSchema._description : undefined,
87
+ minLength: joiSchema._rules?.find((r: any) => r.name === 'min')?.args?.limit,
88
+ maxLength: joiSchema._rules?.find((r: any) => r.name === 'max')?.args?.limit,
89
+ pattern: joiSchema._rules?.find((r: any) => r.name === 'pattern')?.args?.regex?.source,
90
+ };
91
+
92
+ case 'number':
93
+ return {
94
+ type: 'number',
95
+ description: options.includeDescriptions ? joiSchema._description : undefined,
96
+ minimum: joiSchema._rules?.find((r: any) => r.name === 'min')?.args?.limit,
97
+ maximum: joiSchema._rules?.find((r: any) => r.name === 'max')?.args?.limit,
98
+ };
99
+
100
+ case 'boolean':
101
+ return {
102
+ type: 'boolean',
103
+ description: options.includeDescriptions ? joiSchema._description : undefined,
104
+ };
105
+
106
+ case 'object': {
107
+ const properties: Record<string, OpenAPISchema> = {};
108
+ const required: string[] = [];
109
+
110
+ if (joiSchema._inner?.children) {
111
+ for (const child of joiSchema._inner.children) {
112
+ const key = child.key;
113
+ properties[key] = convertJoiToOpenAPI(child.schema, options);
114
+
115
+ if (child.schema._flags?.presence === 'required') {
116
+ required.push(key);
117
+ }
118
+ }
119
+ }
120
+
121
+ return {
122
+ type: 'object',
123
+ properties,
124
+ required: required.length > 0 ? required : undefined,
125
+ description: options.includeDescriptions ? joiSchema._description : undefined,
126
+ };
127
+ }
128
+
129
+ case 'array':
130
+ return {
131
+ type: 'array',
132
+ items: joiSchema._inner?.items?.[0]
133
+ ? convertJoiToOpenAPI(joiSchema._inner.items[0], options)
134
+ : { type: 'object' },
135
+ description: options.includeDescriptions ? joiSchema._description : undefined,
136
+ minItems: joiSchema._rules?.find((r: any) => r.name === 'min')?.args?.limit,
137
+ maxItems: joiSchema._rules?.find((r: any) => r.name === 'max')?.args?.limit,
138
+ };
139
+
140
+ default:
141
+ logger.warn(`Unsupported Joi schema type: ${schemaType}`);
142
+ return {
143
+ type: 'object',
144
+ additionalProperties: true,
145
+ description: options.includeDescriptions ? 'Complex validation schema' : undefined,
146
+ };
147
+ }
148
+ }
@@ -33,6 +33,8 @@ export interface OpenAPISchema {
33
33
  maximum?: number;
34
34
  minLength?: number;
35
35
  maxLength?: number;
36
+ minItems?: number;
37
+ maxItems?: number;
36
38
  pattern?: string;
37
39
  default?: any;
38
40
  oneOf?: OpenAPISchema[];
@@ -1,10 +1,9 @@
1
- // src/core/framework.ts
1
+ // Core Moro Framework with Pluggable WebSocket Adapters
2
2
  import { createServer, Server } from 'http';
3
3
  import {
4
4
  createSecureServer as createHttp2SecureServer,
5
5
  createServer as createHttp2Server,
6
6
  } from 'http2';
7
- import { Server as SocketIOServer } from 'socket.io';
8
7
  import { EventEmitter } from 'events';
9
8
  import { MoroHttpServer, HttpRequest, HttpResponse, middleware } from './http';
10
9
  import { Router } from './http';
@@ -16,6 +15,7 @@ import { MoroEventBus } from './events';
16
15
  import { createFrameworkLogger, logger as globalLogger } from './logger';
17
16
  import { ModuleConfig, InternalRouteDefinition } from '../types/module';
18
17
  import { LogLevel, LoggerOptions } from '../types/logger';
18
+ import { WebSocketAdapter, WebSocketAdapterOptions } from './networking/websocket-adapter';
19
19
 
20
20
  export interface MoroOptions {
21
21
  http2?: boolean;
@@ -28,23 +28,27 @@ export interface MoroOptions {
28
28
  enabled?: boolean;
29
29
  threshold?: number;
30
30
  };
31
- websocket?: {
32
- compression?: boolean;
33
- customIdGenerator?: () => string;
34
- };
31
+ websocket?:
32
+ | {
33
+ enabled?: boolean;
34
+ adapter?: WebSocketAdapter;
35
+ compression?: boolean;
36
+ customIdGenerator?: () => string;
37
+ options?: WebSocketAdapterOptions;
38
+ }
39
+ | false;
35
40
  logger?: LoggerOptions | boolean;
36
41
  }
37
42
 
38
43
  export class Moro extends EventEmitter {
39
44
  private httpServer: MoroHttpServer;
40
45
  private server: Server | any; // HTTP/2 server type
41
- private io: SocketIOServer;
46
+ private websocketAdapter?: WebSocketAdapter;
42
47
  private container: Container;
43
48
  private moduleLoader: ModuleLoader;
44
- private websocketManager: WebSocketManager;
49
+ private websocketManager?: WebSocketManager;
45
50
  private circuitBreakers = new Map<string, CircuitBreaker>();
46
51
  private rateLimiters = new Map<string, Map<string, { count: number; resetTime: number }>>();
47
- private ioInstance: SocketIOServer;
48
52
  // Enterprise-grade event system
49
53
  private eventBus: MoroEventBus;
50
54
  // Framework logger
@@ -99,23 +103,12 @@ export class Moro extends EventEmitter {
99
103
  this.server = this.httpServer.getServer();
100
104
  }
101
105
 
102
- this.io = new SocketIOServer(this.server, {
103
- cors: { origin: '*' },
104
- path: '/socket.io/',
105
- });
106
-
107
- this.ioInstance = this.io;
108
106
  this.container = new Container();
109
107
  this.moduleLoader = new ModuleLoader(this.container);
110
- this.websocketManager = new WebSocketManager(this.io, this.container);
111
-
112
- // Configure WebSocket advanced features
113
- if (options.websocket?.customIdGenerator) {
114
- this.websocketManager.setCustomIdGenerator(options.websocket.customIdGenerator);
115
- }
116
108
 
117
- if (options.websocket?.compression) {
118
- this.websocketManager.enableCompression();
109
+ // Setup WebSocket adapter if enabled
110
+ if (options.websocket !== false) {
111
+ this.setupWebSockets(options.websocket || {});
119
112
  }
120
113
 
121
114
  // Initialize enterprise event bus
@@ -153,6 +146,71 @@ export class Moro extends EventEmitter {
153
146
  this.httpServer.use(this.errorBoundaryMiddleware());
154
147
  }
155
148
 
149
+ /**
150
+ * Setup WebSocket adapter and manager
151
+ */
152
+ private async setupWebSockets(wsConfig: any): Promise<void> {
153
+ try {
154
+ // Use provided adapter or try to auto-detect
155
+ if (wsConfig.adapter) {
156
+ this.websocketAdapter = wsConfig.adapter;
157
+ } else {
158
+ this.websocketAdapter = (await this.detectWebSocketAdapter()) || undefined;
159
+ }
160
+
161
+ if (this.websocketAdapter) {
162
+ await this.websocketAdapter.initialize(this.server, wsConfig.options);
163
+ this.websocketManager = new WebSocketManager(this.websocketAdapter, this.container);
164
+
165
+ // Configure adapter features
166
+ if (wsConfig.compression) {
167
+ this.websocketAdapter.setCompression(true);
168
+ }
169
+ if (wsConfig.customIdGenerator) {
170
+ this.websocketAdapter.setCustomIdGenerator(wsConfig.customIdGenerator);
171
+ }
172
+
173
+ this.logger.info(
174
+ `WebSocket adapter initialized: ${this.websocketAdapter.getAdapterName()}`,
175
+ 'WebSocketSetup'
176
+ );
177
+ }
178
+ } catch (error) {
179
+ this.logger.warn(
180
+ 'WebSocket setup failed, continuing without WebSocket support',
181
+ 'WebSocketSetup',
182
+ { error: error instanceof Error ? error.message : String(error) }
183
+ );
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Auto-detect available WebSocket adapter
189
+ */
190
+ private async detectWebSocketAdapter(): Promise<WebSocketAdapter | null> {
191
+ // Try socket.io first
192
+ try {
193
+ const { SocketIOAdapter } = await import('./networking/adapters');
194
+ return new SocketIOAdapter();
195
+ } catch {
196
+ // socket.io not available
197
+ }
198
+
199
+ // Try native ws library
200
+ try {
201
+ const { WSAdapter } = await import('./networking/adapters');
202
+ return new WSAdapter();
203
+ } catch {
204
+ // ws not available
205
+ }
206
+
207
+ this.logger.warn(
208
+ 'No WebSocket adapter found. Install socket.io or ws for WebSocket support',
209
+ 'AdapterDetection'
210
+ );
211
+ return null;
212
+ }
213
+
156
214
  private requestTrackingMiddleware() {
157
215
  return (req: HttpRequest, res: HttpResponse, next: () => void) => {
158
216
  const startTime = Date.now();
@@ -206,8 +264,31 @@ export class Moro extends EventEmitter {
206
264
  }
207
265
 
208
266
  // Public API for accessing Socket.IO server
267
+ /**
268
+ * Get WebSocket adapter (for backward compatibility)
269
+ * @deprecated Use getWebSocketAdapter() instead
270
+ */
209
271
  getIOServer() {
210
- return this.io;
272
+ if (!this.websocketAdapter) {
273
+ throw new Error(
274
+ 'WebSocket adapter not available. Install socket.io or configure a WebSocket adapter.'
275
+ );
276
+ }
277
+ return this.websocketAdapter;
278
+ }
279
+
280
+ /**
281
+ * Get the WebSocket adapter
282
+ */
283
+ getWebSocketAdapter(): WebSocketAdapter | undefined {
284
+ return this.websocketAdapter;
285
+ }
286
+
287
+ /**
288
+ * Get the WebSocket manager
289
+ */
290
+ getWebSocketManager(): WebSocketManager | undefined {
291
+ return this.websocketManager;
211
292
  }
212
293
 
213
294
  async loadModule(moduleConfig: ModuleConfig): Promise<void> {
@@ -392,7 +473,7 @@ export class Moro extends EventEmitter {
392
473
  : undefined,
393
474
  events: moduleEventBus, // Use pre-created event bus
394
475
  app: {
395
- get: (key: string) => (key === 'io' ? this.ioInstance : undefined),
476
+ get: (key: string) => (key === 'io' ? this.websocketAdapter : undefined),
396
477
  },
397
478
  };
398
479
  this.logger.debug(`Database available: ${!!requestToUse.database}`, 'Handler', {
@@ -466,7 +547,15 @@ export class Moro extends EventEmitter {
466
547
  }
467
548
 
468
549
  private async setupWebSocketHandlers(config: ModuleConfig): Promise<void> {
469
- const namespace = this.io.of(`/${config.name}`);
550
+ if (!this.websocketAdapter || !this.websocketManager) {
551
+ this.logger.warn(
552
+ `Module ${config.name} defines WebSocket handlers but no WebSocket adapter is available`,
553
+ 'WebSocketSetup'
554
+ );
555
+ return;
556
+ }
557
+
558
+ const namespace = this.websocketAdapter.createNamespace(`/${config.name}`);
470
559
 
471
560
  for (const wsConfig of config.websockets || []) {
472
561
  await this.websocketManager.registerHandler(namespace, wsConfig, config);
@@ -530,13 +619,17 @@ export class Moro extends EventEmitter {
530
619
  // Compatibility method for existing controllers
531
620
  set(key: string, value: any): void {
532
621
  if (key === 'io') {
533
- this.ioInstance = value;
622
+ // Deprecated: Use websocket adapter instead
623
+ this.logger.warn(
624
+ 'Setting io instance is deprecated. Use websocket adapter configuration.',
625
+ 'Deprecated'
626
+ );
534
627
  }
535
628
  }
536
629
 
537
630
  get(key: string): any {
538
631
  if (key === 'io') {
539
- return this.ioInstance;
632
+ return this.websocketAdapter;
540
633
  }
541
634
  return undefined;
542
635
  }
@@ -0,0 +1,16 @@
1
+ // WebSocket Adapters for Moro Framework
2
+ // Export all available adapters from this centralized location
3
+
4
+ export { SocketIOAdapter } from './socketio-adapter';
5
+ export { WSAdapter } from './ws-adapter';
6
+
7
+ // Re-export the adapter interface for convenience
8
+ export type {
9
+ WebSocketAdapter,
10
+ WebSocketAdapterOptions,
11
+ WebSocketNamespace,
12
+ WebSocketConnection,
13
+ WebSocketEmitter,
14
+ WebSocketMiddleware,
15
+ WebSocketEventHandler,
16
+ } from '../websocket-adapter';