@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.
Files changed (86) hide show
  1. package/README.md +61 -7
  2. package/dist/core/config/index.d.ts +0 -1
  3. package/dist/core/config/index.js +0 -4
  4. package/dist/core/config/index.js.map +1 -1
  5. package/dist/core/config/loader.js +219 -226
  6. package/dist/core/config/loader.js.map +1 -1
  7. package/dist/core/config/schema.d.ts +30 -335
  8. package/dist/core/config/schema.js +133 -224
  9. package/dist/core/config/schema.js.map +1 -1
  10. package/dist/core/config/utils.d.ts +3 -2
  11. package/dist/core/config/utils.js.map +1 -1
  12. package/dist/core/config/validation.d.ts +17 -0
  13. package/dist/core/config/validation.js +129 -0
  14. package/dist/core/config/validation.js.map +1 -0
  15. package/dist/core/docs/index.js +1 -1
  16. package/dist/core/docs/index.js.map +1 -1
  17. package/dist/core/docs/openapi-generator.js +6 -6
  18. package/dist/core/docs/openapi-generator.js.map +1 -1
  19. package/dist/core/docs/schema-to-openapi.d.ts +7 -0
  20. package/dist/core/docs/schema-to-openapi.js +124 -0
  21. package/dist/core/docs/schema-to-openapi.js.map +1 -0
  22. package/dist/core/docs/simple-docs.js +5 -5
  23. package/dist/core/docs/zod-to-openapi.d.ts +4 -3
  24. package/dist/core/docs/zod-to-openapi.js +28 -0
  25. package/dist/core/docs/zod-to-openapi.js.map +1 -1
  26. package/dist/core/framework.d.ts +29 -6
  27. package/dist/core/framework.js +117 -18
  28. package/dist/core/framework.js.map +1 -1
  29. package/dist/core/networking/adapters/index.d.ts +3 -0
  30. package/dist/core/networking/adapters/index.js +10 -0
  31. package/dist/core/networking/adapters/index.js.map +1 -0
  32. package/dist/core/networking/adapters/socketio-adapter.d.ts +16 -0
  33. package/dist/core/networking/adapters/socketio-adapter.js +244 -0
  34. package/dist/core/networking/adapters/socketio-adapter.js.map +1 -0
  35. package/dist/core/networking/adapters/ws-adapter.d.ts +54 -0
  36. package/dist/core/networking/adapters/ws-adapter.js +383 -0
  37. package/dist/core/networking/adapters/ws-adapter.js.map +1 -0
  38. package/dist/core/networking/websocket-adapter.d.ts +171 -0
  39. package/dist/core/networking/websocket-adapter.js +5 -0
  40. package/dist/core/networking/websocket-adapter.js.map +1 -0
  41. package/dist/core/networking/websocket-manager.d.ts +53 -17
  42. package/dist/core/networking/websocket-manager.js +184 -126
  43. package/dist/core/networking/websocket-manager.js.map +1 -1
  44. package/dist/core/routing/index.d.ts +13 -13
  45. package/dist/core/routing/index.js.map +1 -1
  46. package/dist/core/validation/adapters.d.ts +51 -0
  47. package/dist/core/validation/adapters.js +135 -0
  48. package/dist/core/validation/adapters.js.map +1 -0
  49. package/dist/core/validation/index.d.ts +12 -11
  50. package/dist/core/validation/index.js +32 -26
  51. package/dist/core/validation/index.js.map +1 -1
  52. package/dist/core/validation/schema-interface.d.ts +36 -0
  53. package/dist/core/validation/schema-interface.js +68 -0
  54. package/dist/core/validation/schema-interface.js.map +1 -0
  55. package/dist/index.d.ts +9 -2
  56. package/dist/index.js +23 -4
  57. package/dist/index.js.map +1 -1
  58. package/dist/moro.js +24 -17
  59. package/dist/moro.js.map +1 -1
  60. package/dist/types/config.d.ts +146 -0
  61. package/dist/types/config.js +4 -0
  62. package/dist/types/config.js.map +1 -0
  63. package/package.json +30 -7
  64. package/src/core/config/index.ts +0 -3
  65. package/src/core/config/loader.ts +571 -247
  66. package/src/core/config/schema.ts +146 -279
  67. package/src/core/config/utils.ts +1 -2
  68. package/src/core/config/validation.ts +140 -0
  69. package/src/core/docs/index.ts +1 -1
  70. package/src/core/docs/openapi-generator.ts +7 -6
  71. package/src/core/docs/schema-to-openapi.ts +148 -0
  72. package/src/core/docs/simple-docs.ts +5 -5
  73. package/src/core/docs/zod-to-openapi.ts +52 -20
  74. package/src/core/framework.ts +121 -28
  75. package/src/core/networking/adapters/index.ts +16 -0
  76. package/src/core/networking/adapters/socketio-adapter.ts +252 -0
  77. package/src/core/networking/adapters/ws-adapter.ts +425 -0
  78. package/src/core/networking/websocket-adapter.ts +217 -0
  79. package/src/core/networking/websocket-manager.ts +201 -143
  80. package/src/core/routing/index.ts +13 -13
  81. package/src/core/validation/adapters.ts +147 -0
  82. package/src/core/validation/index.ts +54 -38
  83. package/src/core/validation/schema-interface.ts +100 -0
  84. package/src/index.ts +36 -3
  85. package/src/moro.ts +27 -17
  86. package/src/types/config.ts +157 -0
@@ -1,283 +1,150 @@
1
1
  // Core Configuration Schema for Moro Framework
2
- import { z } from 'zod';
3
2
 
4
- // Server Configuration Schema
5
- const ServerConfigSchema = z.object({
6
- port: z.coerce
7
- .number()
8
- .min(1, 'Port must be at least 1')
9
- .max(65535, 'Port must be at most 65535')
10
- .default(3001)
11
- .describe('Server port to listen on'),
12
-
13
- host: z.string().default('localhost').describe('Server host to bind to'),
14
-
15
- environment: z
16
- .enum(['development', 'staging', 'production'])
17
- .default('development')
18
- .describe('Application environment'),
19
-
20
- maxConnections: z.coerce.number().min(1).default(1000).describe('Maximum concurrent connections'),
21
-
22
- timeout: z.coerce.number().min(1000).default(30000).describe('Request timeout in milliseconds'),
23
- });
24
-
25
- // Service Discovery Configuration Schema
26
- const ServiceDiscoveryConfigSchema = z.object({
27
- enabled: z.coerce.boolean().default(false).describe('Enable service discovery'),
28
-
29
- type: z
30
- .enum(['memory', 'consul', 'kubernetes'])
31
- .default('memory')
32
- .describe('Service discovery backend type'),
33
-
34
- consulUrl: z
35
- .string()
36
- .url('Must be a valid URL')
37
- .default('http://localhost:8500')
38
- .describe('Consul server URL'),
39
-
40
- kubernetesNamespace: z
41
- .string()
42
- .default('default')
43
- .describe('Kubernetes namespace for service discovery'),
44
-
45
- healthCheckInterval: z.coerce
46
- .number()
47
- .min(1000)
48
- .default(30000)
49
- .describe('Health check interval in milliseconds'),
50
-
51
- retryAttempts: z.coerce
52
- .number()
53
- .min(0)
54
- .default(3)
55
- .describe('Number of retry attempts for failed health checks'),
56
- });
57
-
58
- // Database Configuration Schema
59
- const DatabaseConfigSchema = z.object({
60
- url: z.string().optional().describe('Primary database connection URL'),
61
-
62
- redis: z.object({
63
- url: z.string().default('redis://localhost:6379').describe('Redis connection URL'),
64
-
65
- maxRetries: z.coerce
66
- .number()
67
- .min(0)
68
- .default(3)
69
- .describe('Maximum Redis connection retry attempts'),
70
-
71
- retryDelay: z.coerce
72
- .number()
73
- .min(100)
74
- .default(1000)
75
- .describe('Redis retry delay in milliseconds'),
76
-
77
- keyPrefix: z.string().default('moro:').describe('Redis key prefix'),
78
- }),
79
-
80
- mysql: z
81
- .object({
82
- host: z.string().default('localhost'),
83
- port: z.coerce.number().min(1).max(65535).default(3306),
84
- database: z.string().optional(),
85
- username: z.string().optional(),
86
- password: z.string().optional(),
87
- connectionLimit: z.coerce.number().min(1).default(10),
88
- acquireTimeout: z.coerce.number().min(1000).default(60000),
89
- timeout: z.coerce.number().min(1000).default(60000),
90
- })
91
- .optional(),
92
- });
93
-
94
- // Module Defaults Configuration Schema
95
- const ModuleDefaultsConfigSchema = z.object({
96
- cache: z.object({
97
- enabled: z.coerce.boolean().default(true).describe('Enable caching by default'),
98
-
99
- defaultTtl: z.coerce.number().min(0).default(300).describe('Default cache TTL in seconds'),
100
-
101
- maxSize: z.coerce.number().min(1).default(1000).describe('Maximum cache entries'),
102
-
103
- strategy: z.enum(['lru', 'lfu', 'fifo']).default('lru').describe('Cache eviction strategy'),
104
- }),
105
-
106
- rateLimit: z.object({
107
- enabled: z.coerce.boolean().default(true).describe('Enable rate limiting by default'),
108
-
109
- defaultRequests: z.coerce.number().min(1).default(100).describe('Default requests per window'),
110
-
111
- defaultWindow: z.coerce
112
- .number()
113
- .min(1000)
114
- .default(60000)
115
- .describe('Default rate limit window in milliseconds'),
116
-
117
- skipSuccessfulRequests: z.coerce
118
- .boolean()
119
- .default(false)
120
- .describe('Skip successful requests in rate limit counting'),
121
-
122
- skipFailedRequests: z.coerce
123
- .boolean()
124
- .default(false)
125
- .describe('Skip failed requests in rate limit counting'),
126
- }),
127
-
128
- validation: z.object({
129
- enabled: z.coerce.boolean().default(true).describe('Enable validation by default'),
130
-
131
- stripUnknown: z.coerce
132
- .boolean()
133
- .default(true)
134
- .describe('Strip unknown properties from validated data'),
135
-
136
- abortEarly: z.coerce.boolean().default(false).describe('Stop validation on first error'),
137
- }),
138
- });
139
-
140
- // Logging Configuration Schema
141
- const LoggingConfigSchema = z.object({
142
- level: z
143
- .enum(['debug', 'info', 'warn', 'error', 'fatal'])
144
- .default('info')
145
- .describe('Minimum log level'),
146
-
147
- format: z.enum(['pretty', 'json', 'compact']).default('pretty').describe('Log output format'),
148
-
149
- enableColors: z.coerce.boolean().default(true).describe('Enable colored log output'),
150
-
151
- enableTimestamp: z.coerce.boolean().default(true).describe('Include timestamp in logs'),
152
-
153
- enableContext: z.coerce.boolean().default(true).describe('Include context information in logs'),
154
-
155
- outputs: z.object({
156
- console: z.coerce.boolean().default(true),
157
- file: z.object({
158
- enabled: z.coerce.boolean().default(false),
159
- path: z.string().default('./logs/moro.log'),
160
- maxSize: z.string().default('10MB'),
161
- maxFiles: z.coerce.number().default(5),
162
- }),
163
- webhook: z.object({
164
- enabled: z.coerce.boolean().default(false),
165
- url: z.string().url().optional(),
166
- headers: z.record(z.string(), z.string()).default({}),
167
- }),
168
- }),
169
- });
170
-
171
- // Security Configuration Schema
172
- const SecurityConfigSchema = z.object({
173
- cors: z.object({
174
- enabled: z.coerce.boolean().default(true),
175
- origin: z.union([z.string(), z.array(z.string()), z.boolean()]).default('*'),
176
- methods: z.array(z.string()).default(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']),
177
- allowedHeaders: z.array(z.string()).default(['Content-Type', 'Authorization']),
178
- credentials: z.coerce.boolean().default(false),
179
- }),
180
-
181
- helmet: z.object({
182
- enabled: z.coerce.boolean().default(true),
183
- contentSecurityPolicy: z.coerce.boolean().default(true),
184
- hsts: z.coerce.boolean().default(true),
185
- noSniff: z.coerce.boolean().default(true),
186
- frameguard: z.coerce.boolean().default(true),
187
- }),
188
-
189
- rateLimit: z.object({
190
- global: z.object({
191
- enabled: z.coerce.boolean().default(false),
192
- requests: z.coerce.number().min(1).default(1000),
193
- window: z.coerce.number().min(1000).default(60000),
194
- }),
195
- }),
196
- });
197
-
198
- // External Services Configuration Schema
199
- const ExternalServicesConfigSchema = z.object({
200
- stripe: z
201
- .object({
202
- secretKey: z.string().optional(),
203
- publishableKey: z.string().optional(),
204
- webhookSecret: z.string().optional(),
205
- apiVersion: z.string().default('2023-10-16'),
206
- })
207
- .optional(),
208
-
209
- paypal: z
210
- .object({
211
- clientId: z.string().optional(),
212
- clientSecret: z.string().optional(),
213
- webhookId: z.string().optional(),
214
- environment: z.enum(['sandbox', 'production']).default('sandbox'),
215
- })
216
- .optional(),
217
-
218
- smtp: z
219
- .object({
220
- host: z.string().optional(),
221
- port: z.coerce.number().min(1).max(65535).default(587),
222
- secure: z.coerce.boolean().default(false),
223
- username: z.string().optional(),
224
- password: z.string().optional(),
225
- })
226
- .optional(),
227
- });
228
-
229
- // Performance Configuration Schema
230
- const PerformanceConfigSchema = z.object({
231
- compression: z.object({
232
- enabled: z.coerce.boolean().default(true),
233
- level: z.coerce.number().min(1).max(9).default(6),
234
- threshold: z.coerce.number().min(0).default(1024),
235
- }),
236
-
237
- circuitBreaker: z.object({
238
- enabled: z.coerce.boolean().default(true),
239
- failureThreshold: z.coerce.number().min(1).default(5),
240
- resetTimeout: z.coerce.number().min(1000).default(60000),
241
- monitoringPeriod: z.coerce.number().min(1000).default(10000),
242
- }),
243
-
244
- clustering: z.object({
245
- enabled: z.coerce.boolean().default(false),
246
- workers: z.union([z.coerce.number().min(1), z.literal('auto')]).default(1),
247
- }),
248
- });
249
-
250
- // Main Configuration Schema
251
- export const ConfigSchema = z.object({
252
- server: ServerConfigSchema,
253
- serviceDiscovery: ServiceDiscoveryConfigSchema,
254
- database: DatabaseConfigSchema,
255
- modules: ModuleDefaultsConfigSchema,
256
- logging: LoggingConfigSchema,
257
- security: SecurityConfigSchema,
258
- external: ExternalServicesConfigSchema,
259
- performance: PerformanceConfigSchema,
260
- });
261
-
262
- // Inferred TypeScript types
263
- export type AppConfig = z.infer<typeof ConfigSchema>;
264
- export type ServerConfig = z.infer<typeof ServerConfigSchema>;
265
- export type ServiceDiscoveryConfig = z.infer<typeof ServiceDiscoveryConfigSchema>;
266
- export type DatabaseConfig = z.infer<typeof DatabaseConfigSchema>;
267
- export type ModuleDefaultsConfig = z.infer<typeof ModuleDefaultsConfigSchema>;
268
- export type LoggingConfig = z.infer<typeof LoggingConfigSchema>;
269
- export type SecurityConfig = z.infer<typeof SecurityConfigSchema>;
270
- export type ExternalServicesConfig = z.infer<typeof ExternalServicesConfigSchema>;
271
- export type PerformanceConfig = z.infer<typeof PerformanceConfigSchema>;
3
+ import { AppConfig } from '../../types/config';
4
+
5
+ // Default configuration values
6
+ export const DEFAULT_CONFIG: AppConfig = {
7
+ server: {
8
+ port: 3001,
9
+ host: 'localhost',
10
+ environment: 'development',
11
+ maxConnections: 1000,
12
+ timeout: 30000,
13
+ },
14
+ serviceDiscovery: {
15
+ enabled: false,
16
+ type: 'memory',
17
+ consulUrl: 'http://localhost:8500',
18
+ kubernetesNamespace: 'default',
19
+ healthCheckInterval: 30000,
20
+ retryAttempts: 3,
21
+ },
22
+ database: {
23
+ redis: {
24
+ url: 'redis://localhost:6379',
25
+ maxRetries: 3,
26
+ retryDelay: 1000,
27
+ keyPrefix: 'moro:',
28
+ },
29
+ },
30
+ modules: {
31
+ cache: {
32
+ enabled: true,
33
+ defaultTtl: 300,
34
+ maxSize: 1000,
35
+ strategy: 'lru',
36
+ },
37
+ rateLimit: {
38
+ enabled: true,
39
+ defaultRequests: 100,
40
+ defaultWindow: 60000,
41
+ skipSuccessfulRequests: false,
42
+ skipFailedRequests: false,
43
+ },
44
+ validation: {
45
+ enabled: true,
46
+ stripUnknown: true,
47
+ abortEarly: false,
48
+ },
49
+ },
50
+ logging: {
51
+ level: 'info',
52
+ format: 'pretty',
53
+ enableColors: true,
54
+ enableTimestamp: true,
55
+ enableContext: true,
56
+ outputs: {
57
+ console: true,
58
+ file: {
59
+ enabled: false,
60
+ path: './logs/moro.log',
61
+ maxSize: '10MB',
62
+ maxFiles: 5,
63
+ },
64
+ webhook: {
65
+ enabled: false,
66
+ headers: {},
67
+ },
68
+ },
69
+ },
70
+ security: {
71
+ cors: {
72
+ enabled: true,
73
+ origin: '*',
74
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
75
+ allowedHeaders: ['Content-Type', 'Authorization'],
76
+ credentials: false,
77
+ },
78
+ helmet: {
79
+ enabled: true,
80
+ contentSecurityPolicy: true,
81
+ hsts: true,
82
+ noSniff: true,
83
+ frameguard: true,
84
+ },
85
+ rateLimit: {
86
+ global: {
87
+ enabled: false,
88
+ requests: 1000,
89
+ window: 60000,
90
+ },
91
+ },
92
+ },
93
+ external: {
94
+ stripe: {
95
+ apiVersion: '2023-10-16',
96
+ },
97
+ paypal: {
98
+ environment: 'sandbox',
99
+ },
100
+ smtp: {
101
+ port: 587,
102
+ secure: false,
103
+ },
104
+ },
105
+ performance: {
106
+ compression: {
107
+ enabled: true,
108
+ level: 6,
109
+ threshold: 1024,
110
+ },
111
+ circuitBreaker: {
112
+ enabled: true,
113
+ failureThreshold: 5,
114
+ resetTimeout: 60000,
115
+ monitoringPeriod: 10000,
116
+ },
117
+ clustering: {
118
+ enabled: false,
119
+ workers: 1,
120
+ },
121
+ },
122
+ };
272
123
 
273
- // Export individual schemas for module-specific configuration
274
- export {
275
- ServerConfigSchema,
276
- ServiceDiscoveryConfigSchema,
277
- DatabaseConfigSchema,
278
- ModuleDefaultsConfigSchema,
279
- LoggingConfigSchema,
280
- SecurityConfigSchema,
281
- ExternalServicesConfigSchema,
282
- PerformanceConfigSchema,
124
+ // Simple compatibility export - just return the config as-is
125
+ export const ConfigSchema = {
126
+ parse: (data: any): AppConfig => data as AppConfig,
283
127
  };
128
+
129
+ // Re-export types for backward compatibility
130
+ export type {
131
+ AppConfig,
132
+ ServerConfig,
133
+ ServiceDiscoveryConfig,
134
+ DatabaseConfig,
135
+ ModuleDefaultsConfig,
136
+ LoggingConfig,
137
+ SecurityConfig,
138
+ ExternalServicesConfig,
139
+ PerformanceConfig,
140
+ } from '../../types/config';
141
+
142
+ // For backward compatibility with modules that expect schema objects
143
+ export const ServerConfigSchema = { parse: (data: any) => data };
144
+ export const ServiceDiscoveryConfigSchema = { parse: (data: any) => data };
145
+ export const DatabaseConfigSchema = { parse: (data: any) => data };
146
+ export const ModuleDefaultsConfigSchema = { parse: (data: any) => data };
147
+ export const LoggingConfigSchema = { parse: (data: any) => data };
148
+ export const SecurityConfigSchema = { parse: (data: any) => data };
149
+ export const ExternalServicesConfigSchema = { parse: (data: any) => data };
150
+ export const PerformanceConfigSchema = { parse: (data: any) => data };
@@ -1,5 +1,4 @@
1
1
  // Configuration Utilities for Modules and Environment Handling
2
- import { z } from 'zod';
3
2
  import { AppConfig } from './schema';
4
3
  import { createFrameworkLogger } from '../logger';
5
4
 
@@ -66,7 +65,7 @@ function coerceEnvironmentValue(value: string): any {
66
65
  * Create module-specific configuration with environment override support
67
66
  */
68
67
  export function createModuleConfig<T>(
69
- schema: z.ZodSchema<T>,
68
+ schema: { parse: (data: any) => T },
70
69
  defaultConfig: Partial<T>,
71
70
  envPrefix?: string
72
71
  ): T {
@@ -0,0 +1,140 @@
1
+ // Configuration Validation Functions
2
+ // Validation for config system with simple TypeScript functions
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 for configuration
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 validateStringArray(value: unknown, field = 'string array'): string[] {
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.map((item, index) => validateString(item, `${field}[${index}]`));
91
+ }
92
+
93
+ export function validateOptional<T>(
94
+ value: unknown,
95
+ validator: (value: unknown, field: string) => T,
96
+ field: string
97
+ ): T | undefined {
98
+ if (value === undefined || value === null || value === '') {
99
+ return undefined;
100
+ }
101
+ return validator(value, field);
102
+ }
103
+
104
+ // Coercion helpers for environment variables
105
+ export function coerceEnvValue(value: string): unknown {
106
+ // Handle common patterns in environment variables
107
+
108
+ // Null/undefined
109
+ if (value === '' || value === 'null' || value === 'undefined') {
110
+ return undefined;
111
+ }
112
+
113
+ // Boolean
114
+ if (value === 'true' || value === 'false') {
115
+ return value === 'true';
116
+ }
117
+
118
+ // Number (but not if it starts with 0 - could be port, zip code, etc.)
119
+ if (/^-?\d+(\.\d+)?$/.test(value) && !value.startsWith('0')) {
120
+ const num = Number(value);
121
+ if (!isNaN(num)) {
122
+ return num;
123
+ }
124
+ }
125
+
126
+ // JSON (for complex objects/arrays)
127
+ if (
128
+ (value.startsWith('{') && value.endsWith('}')) ||
129
+ (value.startsWith('[') && value.endsWith(']'))
130
+ ) {
131
+ try {
132
+ return JSON.parse(value);
133
+ } catch {
134
+ // Not valid JSON, treat as string
135
+ }
136
+ }
137
+
138
+ // Return as string for all other cases
139
+ return value;
140
+ }
@@ -1,5 +1,5 @@
1
1
  // Moro Framework Documentation System
2
- // Automatic API documentation generation from intelligent routes and Zod schemas
2
+ // Automatic API documentation generation from intelligent routes and Validation schemas
3
3
 
4
4
  import { CompiledRoute } from '../routing';
5
5
  import { IntelligentRoutingManager } from '../routing/app-integration';
@@ -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 {