@morojs/moro 1.3.0 → 1.5.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/index.d.ts +0 -1
- package/dist/core/config/index.js +0 -4
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/loader.js +219 -226
- package/dist/core/config/loader.js.map +1 -1
- package/dist/core/config/schema.d.ts +30 -335
- package/dist/core/config/schema.js +133 -224
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/utils.d.ts +3 -2
- package/dist/core/config/utils.js.map +1 -1
- package/dist/core/config/validation.d.ts +17 -0
- package/dist/core/config/validation.js +129 -0
- package/dist/core/config/validation.js.map +1 -0
- package/dist/core/docs/index.js +1 -1
- package/dist/core/docs/index.js.map +1 -1
- 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/simple-docs.js +5 -5
- package/dist/core/docs/zod-to-openapi.d.ts +4 -3
- package/dist/core/docs/zod-to-openapi.js +28 -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 +184 -126
- 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 +12 -11
- package/dist/core/validation/index.js +32 -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 +9 -2
- package/dist/index.js +23 -4
- package/dist/index.js.map +1 -1
- package/dist/moro.js +24 -17
- package/dist/moro.js.map +1 -1
- package/dist/types/config.d.ts +146 -0
- package/dist/types/config.js +4 -0
- package/dist/types/config.js.map +1 -0
- package/package.json +30 -7
- package/src/core/config/index.ts +0 -3
- package/src/core/config/loader.ts +571 -247
- package/src/core/config/schema.ts +146 -279
- package/src/core/config/utils.ts +1 -2
- package/src/core/config/validation.ts +140 -0
- package/src/core/docs/index.ts +1 -1
- package/src/core/docs/openapi-generator.ts +7 -6
- package/src/core/docs/schema-to-openapi.ts +148 -0
- package/src/core/docs/simple-docs.ts +5 -5
- package/src/core/docs/zod-to-openapi.ts +52 -20
- 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 +201 -143
- package/src/core/routing/index.ts +13 -13
- package/src/core/validation/adapters.ts +147 -0
- package/src/core/validation/index.ts +54 -38
- package/src/core/validation/schema-interface.ts +100 -0
- package/src/index.ts +36 -3
- package/src/moro.ts +27 -17
- package/src/types/config.ts +157 -0
|
@@ -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
|
+
}
|
|
@@ -132,7 +132,7 @@ export class SimpleDocsGenerator {
|
|
|
132
132
|
<div class="container">
|
|
133
133
|
<h1>${this.options.title}</h1>
|
|
134
134
|
<p>${this.options.description}</p>
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
<div class="example">
|
|
137
137
|
<strong>Interactive Swagger UI:</strong> <a href="${this.options.basePath}" target="_blank">${this.options.basePath}</a><br>
|
|
138
138
|
<strong>OpenAPI JSON:</strong> <a href="${this.options.basePath}/openapi.json" target="_blank">${this.options.basePath}/openapi.json</a>
|
|
@@ -189,11 +189,11 @@ export class SimpleDocsGenerator {
|
|
|
189
189
|
<span class="method ${route.method}">${route.method}</span>
|
|
190
190
|
<span class="path">${route.path}</span>
|
|
191
191
|
</div>
|
|
192
|
-
|
|
192
|
+
|
|
193
193
|
${route.description ? `<div class="description">${route.description}</div>` : ''}
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
${route.tags ? `<div class="tags">${route.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}</div>` : ''}
|
|
196
|
-
|
|
196
|
+
|
|
197
197
|
${this.generateValidationInfo(route)}
|
|
198
198
|
${this.generateAuthInfo(route)}
|
|
199
199
|
${this.generateRateLimitInfo(route)}
|
|
@@ -215,7 +215,7 @@ export class SimpleDocsGenerator {
|
|
|
215
215
|
return `
|
|
216
216
|
<div class="validation">
|
|
217
217
|
<strong>Validation:</strong> ${validationTypes.join(', ')}
|
|
218
|
-
<br><small>Request will be validated with
|
|
218
|
+
<br><small>Request will be validated with Validation schemas for type safety</small>
|
|
219
219
|
</div>`;
|
|
220
220
|
}
|
|
221
221
|
|
|
@@ -1,22 +1,40 @@
|
|
|
1
1
|
// Zod to OpenAPI Schema Converter
|
|
2
2
|
// Transforms Zod schemas into OpenAPI 3.0 schema definitions
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
ZodSchema,
|
|
6
|
-
ZodType,
|
|
7
|
-
ZodObject,
|
|
8
|
-
ZodArray,
|
|
9
|
-
ZodString,
|
|
10
|
-
ZodNumber,
|
|
11
|
-
ZodBoolean,
|
|
12
|
-
ZodEnum,
|
|
13
|
-
ZodOptional,
|
|
14
|
-
ZodDefault,
|
|
15
|
-
ZodUnion,
|
|
16
|
-
ZodLiteral,
|
|
17
|
-
} from 'zod';
|
|
18
4
|
import { createFrameworkLogger } from '../logger';
|
|
19
5
|
|
|
6
|
+
// Dynamic Zod imports (optional dependency)
|
|
7
|
+
let ZodSchema: any,
|
|
8
|
+
ZodType: any,
|
|
9
|
+
ZodObject: any,
|
|
10
|
+
ZodArray: any,
|
|
11
|
+
ZodString: any,
|
|
12
|
+
ZodNumber: any,
|
|
13
|
+
ZodBoolean: any,
|
|
14
|
+
ZodEnum: any,
|
|
15
|
+
ZodOptional: any,
|
|
16
|
+
ZodDefault: any,
|
|
17
|
+
ZodUnion: any,
|
|
18
|
+
ZodLiteral: any;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const zod = require('zod');
|
|
22
|
+
ZodSchema = zod.ZodSchema;
|
|
23
|
+
ZodType = zod.ZodType;
|
|
24
|
+
ZodObject = zod.ZodObject;
|
|
25
|
+
ZodArray = zod.ZodArray;
|
|
26
|
+
ZodString = zod.ZodString;
|
|
27
|
+
ZodNumber = zod.ZodNumber;
|
|
28
|
+
ZodBoolean = zod.ZodBoolean;
|
|
29
|
+
ZodEnum = zod.ZodEnum;
|
|
30
|
+
ZodOptional = zod.ZodOptional;
|
|
31
|
+
ZodDefault = zod.ZodDefault;
|
|
32
|
+
ZodUnion = zod.ZodUnion;
|
|
33
|
+
ZodLiteral = zod.ZodLiteral;
|
|
34
|
+
} catch {
|
|
35
|
+
// Zod not available - that's fine!
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
const logger = createFrameworkLogger('ZodToOpenAPI');
|
|
21
39
|
|
|
22
40
|
// OpenAPI schema types
|
|
@@ -33,6 +51,8 @@ export interface OpenAPISchema {
|
|
|
33
51
|
maximum?: number;
|
|
34
52
|
minLength?: number;
|
|
35
53
|
maxLength?: number;
|
|
54
|
+
minItems?: number;
|
|
55
|
+
maxItems?: number;
|
|
36
56
|
pattern?: string;
|
|
37
57
|
default?: any;
|
|
38
58
|
oneOf?: OpenAPISchema[];
|
|
@@ -50,7 +70,12 @@ export interface ConversionOptions {
|
|
|
50
70
|
}
|
|
51
71
|
|
|
52
72
|
// Main conversion function
|
|
53
|
-
export function zodToOpenAPI(schema:
|
|
73
|
+
export function zodToOpenAPI(schema: any, options: ConversionOptions = {}): OpenAPISchema {
|
|
74
|
+
// Check if Zod is available
|
|
75
|
+
if (!ZodSchema) {
|
|
76
|
+
throw new Error('Zod is not installed. Please install zod to use zodToOpenAPI function.');
|
|
77
|
+
}
|
|
78
|
+
|
|
54
79
|
const opts = {
|
|
55
80
|
includeExamples: true,
|
|
56
81
|
includeDescriptions: true,
|
|
@@ -365,7 +390,7 @@ function convertZodDefault(def: any, options: ConversionOptions): OpenAPISchema
|
|
|
365
390
|
|
|
366
391
|
// Convert ZodUnion
|
|
367
392
|
function convertZodUnion(def: any, options: ConversionOptions): OpenAPISchema {
|
|
368
|
-
const schemas = def.options.map((option:
|
|
393
|
+
const schemas = def.options.map((option: any) => convertZodType(option._def, options));
|
|
369
394
|
|
|
370
395
|
return {
|
|
371
396
|
oneOf: schemas,
|
|
@@ -384,16 +409,23 @@ function convertZodLiteral(def: any, options: ConversionOptions): OpenAPISchema
|
|
|
384
409
|
}
|
|
385
410
|
|
|
386
411
|
// Helper functions
|
|
387
|
-
function isOptionalType(type:
|
|
412
|
+
function isOptionalType(type: any): boolean {
|
|
388
413
|
return (type._def as any).typeName === 'ZodOptional';
|
|
389
414
|
}
|
|
390
415
|
|
|
391
|
-
function hasDefault(type:
|
|
416
|
+
function hasDefault(type: any): boolean {
|
|
392
417
|
return (type._def as any).typeName === 'ZodDefault';
|
|
393
418
|
}
|
|
394
419
|
|
|
395
420
|
// Generate example data from Zod schema
|
|
396
|
-
export function generateExampleFromSchema(schema:
|
|
421
|
+
export function generateExampleFromSchema(schema: any): any {
|
|
422
|
+
// Check if Zod is available
|
|
423
|
+
if (!ZodSchema) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
'Zod is not installed. Please install zod to use generateExampleFromSchema function.'
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
397
429
|
try {
|
|
398
430
|
return generateExample(schema._def);
|
|
399
431
|
} catch (error) {
|
|
@@ -439,7 +471,7 @@ function generateExample(def: any): any {
|
|
|
439
471
|
}
|
|
440
472
|
|
|
441
473
|
for (const [key, value] of Object.entries(shape)) {
|
|
442
|
-
const zodType = value as
|
|
474
|
+
const zodType = value as any;
|
|
443
475
|
if (!isOptionalType(zodType)) {
|
|
444
476
|
example[key] = generateExample(zodType._def);
|
|
445
477
|
}
|
package/src/core/framework.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
//
|
|
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
|
-
|
|
33
|
-
|
|
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
|
|
46
|
+
private websocketAdapter?: WebSocketAdapter;
|
|
42
47
|
private container: Container;
|
|
43
48
|
private moduleLoader: ModuleLoader;
|
|
44
|
-
private 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
|
|
118
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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';
|