create-fluxstack 1.7.5 → 1.8.3

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 (154) hide show
  1. package/.dockerignore +82 -0
  2. package/.env.example +19 -0
  3. package/Dockerfile +70 -0
  4. package/README.md +6 -3
  5. package/app/client/SIMPLIFICATION.md +140 -0
  6. package/app/client/frontend-only.ts +1 -1
  7. package/app/client/src/App.tsx +148 -283
  8. package/app/client/src/index.css +5 -20
  9. package/app/client/src/lib/eden-api.ts +53 -220
  10. package/app/client/src/main.tsx +2 -3
  11. package/app/server/app.ts +20 -5
  12. package/app/server/backend-only.ts +15 -12
  13. package/app/server/controllers/users.controller.ts +57 -31
  14. package/app/server/index.ts +86 -96
  15. package/app/server/live/register-components.ts +18 -7
  16. package/app/server/routes/env-test.ts +110 -0
  17. package/app/server/routes/index.ts +1 -8
  18. package/app/server/routes/users.routes.ts +192 -91
  19. package/config/app.config.ts +2 -54
  20. package/config/client.config.ts +95 -0
  21. package/config/fluxstack.config.ts +2 -2
  22. package/config/index.ts +57 -22
  23. package/config/monitoring.config.ts +114 -0
  24. package/config/plugins.config.ts +80 -0
  25. package/config/runtime.config.ts +0 -17
  26. package/config/server.config.ts +50 -30
  27. package/core/build/bundler.ts +17 -16
  28. package/core/build/flux-plugins-generator.ts +34 -23
  29. package/core/build/index.ts +32 -31
  30. package/core/build/live-components-generator.ts +44 -30
  31. package/core/build/optimizer.ts +37 -17
  32. package/core/cli/command-registry.ts +4 -14
  33. package/core/cli/commands/plugin-deps.ts +8 -8
  34. package/core/cli/generators/component.ts +3 -3
  35. package/core/cli/generators/controller.ts +4 -4
  36. package/core/cli/generators/index.ts +8 -8
  37. package/core/cli/generators/interactive.ts +4 -4
  38. package/core/cli/generators/plugin.ts +3 -3
  39. package/core/cli/generators/prompts.ts +1 -1
  40. package/core/cli/generators/route.ts +27 -11
  41. package/core/cli/generators/service.ts +5 -5
  42. package/core/cli/generators/template-engine.ts +1 -1
  43. package/core/cli/generators/types.ts +1 -1
  44. package/core/cli/index.ts +158 -189
  45. package/core/cli/plugin-discovery.ts +3 -3
  46. package/core/client/hooks/index.ts +2 -2
  47. package/core/client/hooks/state-validator.ts +1 -1
  48. package/core/client/hooks/useAuth.ts +1 -1
  49. package/core/client/hooks/useChunkedUpload.ts +1 -1
  50. package/core/client/hooks/useHybridLiveComponent.ts +1 -1
  51. package/core/client/hooks/useWebSocket.ts +1 -1
  52. package/core/config/env.ts +5 -1
  53. package/core/config/runtime-config.ts +12 -10
  54. package/core/config/schema.ts +33 -2
  55. package/core/framework/server.ts +30 -14
  56. package/core/framework/types.ts +2 -2
  57. package/core/index.ts +31 -23
  58. package/core/live/ComponentRegistry.ts +1 -1
  59. package/core/plugins/built-in/live-components/commands/create-live-component.ts +1 -1
  60. package/core/plugins/built-in/live-components/index.ts +1 -1
  61. package/core/plugins/built-in/monitoring/index.ts +65 -161
  62. package/core/plugins/built-in/static/index.ts +75 -277
  63. package/core/plugins/built-in/swagger/index.ts +301 -231
  64. package/core/plugins/built-in/vite/index.ts +342 -377
  65. package/core/plugins/config.ts +2 -2
  66. package/core/plugins/dependency-manager.ts +2 -2
  67. package/core/plugins/discovery.ts +1 -1
  68. package/core/plugins/executor.ts +2 -2
  69. package/core/plugins/manager.ts +19 -4
  70. package/core/plugins/module-resolver.ts +1 -1
  71. package/core/plugins/registry.ts +25 -21
  72. package/core/plugins/types.ts +147 -5
  73. package/core/server/backend-entry.ts +51 -0
  74. package/core/server/framework.ts +2 -2
  75. package/core/server/live/ComponentRegistry.ts +9 -26
  76. package/core/server/live/FileUploadManager.ts +1 -1
  77. package/core/server/live/auto-generated-components.ts +26 -0
  78. package/core/server/live/websocket-plugin.ts +211 -19
  79. package/core/server/middleware/errorHandling.ts +1 -1
  80. package/core/server/middleware/index.ts +4 -4
  81. package/core/server/plugins/database.ts +1 -2
  82. package/core/server/plugins/static-files-plugin.ts +259 -231
  83. package/core/server/plugins/swagger.ts +1 -1
  84. package/core/server/services/BaseService.ts +1 -1
  85. package/core/server/services/ServiceContainer.ts +1 -1
  86. package/core/server/services/index.ts +4 -4
  87. package/core/server/standalone.ts +16 -1
  88. package/core/testing/index.ts +1 -1
  89. package/core/testing/setup.ts +1 -1
  90. package/core/types/plugin.ts +6 -0
  91. package/core/utils/build-logger.ts +324 -0
  92. package/core/utils/config-schema.ts +2 -6
  93. package/core/utils/helpers.ts +14 -9
  94. package/core/utils/logger/startup-banner.ts +7 -33
  95. package/core/utils/regenerate-files.ts +69 -0
  96. package/core/utils/version.ts +6 -6
  97. package/create-fluxstack.ts +68 -25
  98. package/fluxstack.config.ts +138 -252
  99. package/package.json +3 -18
  100. package/plugins/crypto-auth/index.ts +52 -47
  101. package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
  102. package/plugins/crypto-auth/server/middlewares/helpers.ts +16 -1
  103. package/vitest.config.ts +17 -26
  104. package/app/client/src/App.css +0 -883
  105. package/app/client/src/components/ErrorBoundary.tsx +0 -107
  106. package/app/client/src/components/ErrorDisplay.css +0 -365
  107. package/app/client/src/components/ErrorDisplay.tsx +0 -258
  108. package/app/client/src/components/FluxStackConfig.tsx +0 -1321
  109. package/app/client/src/components/HybridLiveCounter.tsx +0 -140
  110. package/app/client/src/components/LiveClock.tsx +0 -286
  111. package/app/client/src/components/MainLayout.tsx +0 -388
  112. package/app/client/src/components/SidebarNavigation.tsx +0 -391
  113. package/app/client/src/components/StateDemo.tsx +0 -178
  114. package/app/client/src/components/SystemMonitor.tsx +0 -1044
  115. package/app/client/src/components/UserProfile.tsx +0 -809
  116. package/app/client/src/hooks/useAuth.ts +0 -39
  117. package/app/client/src/hooks/useNotifications.ts +0 -56
  118. package/app/client/src/lib/errors.ts +0 -340
  119. package/app/client/src/lib/hooks/useErrorHandler.ts +0 -258
  120. package/app/client/src/lib/index.ts +0 -45
  121. package/app/client/src/pages/ApiDocs.tsx +0 -182
  122. package/app/client/src/pages/CryptoAuthPage.tsx +0 -394
  123. package/app/client/src/pages/Demo.tsx +0 -174
  124. package/app/client/src/pages/HybridLive.tsx +0 -263
  125. package/app/client/src/pages/Overview.tsx +0 -155
  126. package/app/client/src/store/README.md +0 -43
  127. package/app/client/src/store/index.ts +0 -16
  128. package/app/client/src/store/slices/uiSlice.ts +0 -151
  129. package/app/client/src/store/slices/userSlice.ts +0 -161
  130. package/app/client/src/test/README.md +0 -257
  131. package/app/client/src/test/setup.ts +0 -70
  132. package/app/client/src/test/types.ts +0 -12
  133. package/app/server/live/CounterComponent.ts +0 -191
  134. package/app/server/live/FluxStackConfig.ts +0 -534
  135. package/app/server/live/SidebarNavigation.ts +0 -157
  136. package/app/server/live/SystemMonitor.ts +0 -595
  137. package/app/server/live/SystemMonitorIntegration.ts +0 -151
  138. package/app/server/live/UserProfileComponent.ts +0 -141
  139. package/app/server/middleware/auth.ts +0 -136
  140. package/app/server/middleware/errorHandling.ts +0 -252
  141. package/app/server/middleware/index.ts +0 -10
  142. package/app/server/middleware/rateLimit.ts +0 -193
  143. package/app/server/middleware/requestLogging.ts +0 -215
  144. package/app/server/middleware/validation.ts +0 -270
  145. package/app/server/routes/config.ts +0 -145
  146. package/app/server/routes/crypto-auth-demo.routes.ts +0 -167
  147. package/app/server/routes/example-with-crypto-auth.routes.ts +0 -235
  148. package/app/server/routes/exemplo-posts.routes.ts +0 -161
  149. package/app/server/routes/upload.ts +0 -92
  150. package/app/server/services/NotificationService.ts +0 -302
  151. package/app/server/services/UserService.ts +0 -222
  152. package/app/server/services/index.ts +0 -46
  153. package/app/server/types/index.ts +0 -1
  154. package/config/build.config.ts +0 -24
@@ -1,193 +0,0 @@
1
- /**
2
- * Rate Limiting Middleware
3
- * Implements rate limiting to prevent abuse
4
- */
5
-
6
- import type { Context } from 'elysia'
7
-
8
- export interface RateLimitConfig {
9
- windowMs: number // Time window in milliseconds
10
- maxRequests: number // Maximum requests per window
11
- keyGenerator?: (context: Context) => string // Custom key generator
12
- skipSuccessfulRequests?: boolean // Don't count successful requests
13
- skipFailedRequests?: boolean // Don't count failed requests
14
- message?: string // Custom error message
15
- }
16
-
17
- interface RateLimitEntry {
18
- count: number
19
- resetTime: number
20
- }
21
-
22
- /**
23
- * In-memory rate limit store
24
- * In production, you'd want to use Redis or another distributed store
25
- */
26
- class MemoryStore {
27
- private store = new Map<string, RateLimitEntry>()
28
-
29
- get(key: string): RateLimitEntry | undefined {
30
- const entry = this.store.get(key)
31
-
32
- // Clean up expired entries
33
- if (entry && entry.resetTime < Date.now()) {
34
- this.store.delete(key)
35
- return undefined
36
- }
37
-
38
- return entry
39
- }
40
-
41
- set(key: string, entry: RateLimitEntry): void {
42
- this.store.set(key, entry)
43
- }
44
-
45
- increment(key: string, windowMs: number): RateLimitEntry {
46
- const now = Date.now()
47
- const existing = this.get(key)
48
-
49
- if (existing) {
50
- existing.count++
51
- return existing
52
- } else {
53
- const newEntry: RateLimitEntry = {
54
- count: 1,
55
- resetTime: now + windowMs
56
- }
57
- this.set(key, newEntry)
58
- return newEntry
59
- }
60
- }
61
-
62
- cleanup(): void {
63
- const now = Date.now()
64
- for (const [key, entry] of this.store.entries()) {
65
- if (entry.resetTime < now) {
66
- this.store.delete(key)
67
- }
68
- }
69
- }
70
- }
71
-
72
- const store = new MemoryStore()
73
-
74
- // Cleanup expired entries every minute
75
- setInterval(() => {
76
- store.cleanup()
77
- }, 60000)
78
-
79
- /**
80
- * Create rate limiting middleware
81
- */
82
- export const rateLimitMiddleware = (config: RateLimitConfig) => ({
83
- name: 'rate-limit',
84
-
85
- beforeHandle: async (context: Context) => {
86
- const key = config.keyGenerator
87
- ? config.keyGenerator(context)
88
- : getDefaultKey(context)
89
-
90
- const entry = store.increment(key, config.windowMs)
91
-
92
- // Add rate limit headers
93
- const headers = {
94
- 'X-RateLimit-Limit': config.maxRequests.toString(),
95
- 'X-RateLimit-Remaining': Math.max(0, config.maxRequests - entry.count).toString(),
96
- 'X-RateLimit-Reset': Math.ceil(entry.resetTime / 1000).toString()
97
- }
98
-
99
- // Check if rate limit exceeded
100
- if (entry.count > config.maxRequests) {
101
- return new Response(
102
- JSON.stringify({
103
- error: config.message || 'Too many requests',
104
- retryAfter: Math.ceil((entry.resetTime - Date.now()) / 1000)
105
- }),
106
- {
107
- status: 429,
108
- headers: {
109
- 'Content-Type': 'application/json',
110
- 'Retry-After': Math.ceil((entry.resetTime - Date.now()) / 1000).toString(),
111
- ...headers
112
- }
113
- }
114
- )
115
- }
116
-
117
- // Add headers to successful responses
118
- context.set.headers = { ...context.set.headers, ...headers }
119
- }
120
- })
121
-
122
- /**
123
- * Default key generator (IP-based)
124
- */
125
- function getDefaultKey(context: Context): string {
126
- // Try to get real IP from various headers
127
- const forwarded = context.headers['x-forwarded-for']
128
- const realIp = context.headers['x-real-ip']
129
- const cfConnectingIp = context.headers['cf-connecting-ip']
130
-
131
- let ip = 'unknown'
132
-
133
- if (forwarded) {
134
- ip = forwarded.split(',')[0].trim()
135
- } else if (realIp) {
136
- ip = realIp
137
- } else if (cfConnectingIp) {
138
- ip = cfConnectingIp
139
- }
140
-
141
- return `rate_limit:${ip}`
142
- }
143
-
144
- /**
145
- * User-based key generator
146
- */
147
- export const userKeyGenerator = (context: any): string => {
148
- const userId = context.user?.id
149
- return userId ? `rate_limit:user:${userId}` : getDefaultKey(context)
150
- }
151
-
152
- /**
153
- * Endpoint-based key generator
154
- */
155
- export const endpointKeyGenerator = (context: Context): string => {
156
- const ip = getDefaultKey(context)
157
- const path = context.path
158
- return `${ip}:${path}`
159
- }
160
-
161
- /**
162
- * Common rate limit configurations
163
- */
164
- export const rateLimitConfigs = {
165
- // General API rate limit
166
- general: {
167
- windowMs: 15 * 60 * 1000, // 15 minutes
168
- maxRequests: 100,
169
- message: 'Too many requests from this IP, please try again later'
170
- },
171
-
172
- // Strict rate limit for authentication endpoints
173
- auth: {
174
- windowMs: 15 * 60 * 1000, // 15 minutes
175
- maxRequests: 5,
176
- message: 'Too many authentication attempts, please try again later'
177
- },
178
-
179
- // Lenient rate limit for public endpoints
180
- public: {
181
- windowMs: 15 * 60 * 1000, // 15 minutes
182
- maxRequests: 1000,
183
- message: 'Rate limit exceeded for public API'
184
- },
185
-
186
- // Per-user rate limit
187
- perUser: {
188
- windowMs: 60 * 1000, // 1 minute
189
- maxRequests: 60,
190
- keyGenerator: userKeyGenerator,
191
- message: 'Too many requests, please slow down'
192
- }
193
- }
@@ -1,215 +0,0 @@
1
- /**
2
- * Request Logging Middleware
3
- * Logs HTTP requests with timing and context information
4
- */
5
-
6
- import type { Context } from 'elysia'
7
-
8
- export interface RequestLogConfig {
9
- logLevel?: 'debug' | 'info' | 'warn' | 'error'
10
- includeBody?: boolean
11
- includeHeaders?: boolean
12
- excludePaths?: string[]
13
- excludeHeaders?: string[]
14
- maxBodyLength?: number
15
- }
16
-
17
- /**
18
- * Request logging middleware
19
- */
20
- export const requestLoggingMiddleware = (config: RequestLogConfig = {}) => ({
21
- name: 'request-logging',
22
-
23
- beforeHandle: async (context: Context) => {
24
- // Skip logging for excluded paths
25
- if (config.excludePaths?.includes(context.path)) {
26
- return
27
- }
28
-
29
- const startTime = Date.now()
30
-
31
- // Store start time for duration calculation
32
- context.store = { ...context.store, startTime }
33
-
34
- // Log request start
35
- const requestId = generateRequestId()
36
- context.store = { ...context.store, requestId }
37
-
38
- const logData: any = {
39
- requestId,
40
- method: context.request.method,
41
- path: context.path,
42
- query: context.query,
43
- userAgent: context.headers['user-agent'],
44
- ip: getClientIp(context),
45
- timestamp: new Date().toISOString()
46
- }
47
-
48
- // Include headers if configured
49
- if (config.includeHeaders) {
50
- const headers = { ...context.headers }
51
-
52
- // Remove sensitive headers
53
- const excludeHeaders = config.excludeHeaders || [
54
- 'authorization',
55
- 'cookie',
56
- 'x-api-key'
57
- ]
58
-
59
- excludeHeaders.forEach(header => {
60
- delete headers[header.toLowerCase()]
61
- })
62
-
63
- logData.headers = headers
64
- }
65
-
66
- // Include body if configured
67
- if (config.includeBody && context.body) {
68
- let body = context.body
69
-
70
- // Truncate large bodies
71
- if (config.maxBodyLength && typeof body === 'string') {
72
- if (body.length > config.maxBodyLength) {
73
- body = body.substring(0, config.maxBodyLength) + '...[truncated]'
74
- }
75
- }
76
-
77
- logData.body = body
78
- }
79
-
80
- console.log('📥 Request started', logData)
81
- },
82
-
83
- afterHandle: async (context: Context, response: Response) => {
84
- const startTime = context.store?.startTime
85
- const requestId = context.store?.requestId
86
-
87
- if (!startTime) return
88
-
89
- const duration = Date.now() - startTime
90
-
91
- const logData: any = {
92
- requestId,
93
- method: context.request.method,
94
- path: context.path,
95
- status: response.status,
96
- duration: `${duration}ms`,
97
- timestamp: new Date().toISOString()
98
- }
99
-
100
- // Determine log level based on status code
101
- let logLevel = config.logLevel || 'info'
102
-
103
- if (response.status >= 500) {
104
- logLevel = 'error'
105
- } else if (response.status >= 400) {
106
- logLevel = 'warn'
107
- }
108
-
109
- // Add performance warning for slow requests
110
- if (duration > 1000) {
111
- logData.warning = 'Slow request detected'
112
- logLevel = 'warn'
113
- }
114
-
115
- const logMessage = `📤 Request completed - ${context.request.method} ${context.path} ${response.status} (${duration}ms)`
116
-
117
- switch (logLevel) {
118
- case 'error':
119
- console.error(logMessage, logData)
120
- break
121
- case 'warn':
122
- console.warn(logMessage, logData)
123
- break
124
- case 'debug':
125
- console.debug(logMessage, logData)
126
- break
127
- default:
128
- console.log(logMessage, logData)
129
- }
130
- },
131
-
132
- onError: async (context: Context, error: Error) => {
133
- const startTime = context.store?.startTime
134
- const requestId = context.store?.requestId
135
- const duration = startTime ? Date.now() - startTime : 0
136
-
137
- const logData = {
138
- requestId,
139
- method: context.request.method,
140
- path: context.path,
141
- error: error.message,
142
- stack: error.stack,
143
- duration: `${duration}ms`,
144
- timestamp: new Date().toISOString()
145
- }
146
-
147
- console.error('💥 Request failed', logData)
148
- }
149
- })
150
-
151
- /**
152
- * Generate unique request ID
153
- */
154
- function generateRequestId(): string {
155
- return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
156
- }
157
-
158
- /**
159
- * Get client IP address
160
- */
161
- function getClientIp(context: Context): string {
162
- // Try to get real IP from various headers
163
- const forwarded = context.headers['x-forwarded-for']
164
- const realIp = context.headers['x-real-ip']
165
- const cfConnectingIp = context.headers['cf-connecting-ip']
166
-
167
- if (forwarded) {
168
- return forwarded.split(',')[0].trim()
169
- }
170
-
171
- if (realIp) {
172
- return realIp
173
- }
174
-
175
- if (cfConnectingIp) {
176
- return cfConnectingIp
177
- }
178
-
179
- return 'unknown'
180
- }
181
-
182
- /**
183
- * Predefined configurations
184
- */
185
- export const requestLoggingConfigs = {
186
- // Development configuration - verbose logging
187
- development: {
188
- logLevel: 'debug' as const,
189
- includeBody: true,
190
- includeHeaders: true,
191
- maxBodyLength: 1000,
192
- excludeHeaders: ['authorization', 'cookie']
193
- },
194
-
195
- // Production configuration - minimal logging
196
- production: {
197
- logLevel: 'info' as const,
198
- includeBody: false,
199
- includeHeaders: false,
200
- excludePaths: ['/health', '/metrics']
201
- },
202
-
203
- // Security-focused configuration
204
- security: {
205
- logLevel: 'warn' as const,
206
- includeBody: false,
207
- includeHeaders: true,
208
- excludeHeaders: [
209
- 'authorization',
210
- 'cookie',
211
- 'x-api-key',
212
- 'x-auth-token'
213
- ]
214
- }
215
- }
@@ -1,270 +0,0 @@
1
- /**
2
- * Validation Middleware
3
- * Provides request validation using schemas
4
- */
5
-
6
- import type { Context } from 'elysia'
7
-
8
- export interface ValidationSchema {
9
- body?: Record<string, any>
10
- query?: Record<string, any>
11
- params?: Record<string, any>
12
- }
13
-
14
- export interface ValidationError {
15
- field: string
16
- message: string
17
- value?: any
18
- }
19
-
20
- /**
21
- * Create validation middleware for a specific schema
22
- */
23
- export const validationMiddleware = (schema: ValidationSchema) => ({
24
- name: 'validation',
25
-
26
- beforeHandle: async (context: Context) => {
27
- const errors: ValidationError[] = []
28
-
29
- // Validate body
30
- if (schema.body && context.body) {
31
- const bodyErrors = validateObject(context.body, schema.body, 'body')
32
- errors.push(...bodyErrors)
33
- }
34
-
35
- // Validate query parameters
36
- if (schema.query && context.query) {
37
- const queryErrors = validateObject(context.query, schema.query, 'query')
38
- errors.push(...queryErrors)
39
- }
40
-
41
- // Validate path parameters
42
- if (schema.params && context.params) {
43
- const paramErrors = validateObject(context.params, schema.params, 'params')
44
- errors.push(...paramErrors)
45
- }
46
-
47
- if (errors.length > 0) {
48
- return new Response(
49
- JSON.stringify({
50
- error: 'Validation failed',
51
- details: errors
52
- }),
53
- {
54
- status: 400,
55
- headers: { 'Content-Type': 'application/json' }
56
- }
57
- )
58
- }
59
- }
60
- })
61
-
62
- /**
63
- * Validate an object against a schema
64
- */
65
- function validateObject(
66
- obj: any,
67
- schema: Record<string, any>,
68
- prefix: string
69
- ): ValidationError[] {
70
- const errors: ValidationError[] = []
71
-
72
- for (const [field, rules] of Object.entries(schema)) {
73
- const value = obj[field]
74
- const fieldPath = `${prefix}.${field}`
75
-
76
- // Check required fields
77
- if (rules.required && (value === undefined || value === null || value === '')) {
78
- errors.push({
79
- field: fieldPath,
80
- message: `${field} is required`,
81
- value
82
- })
83
- continue
84
- }
85
-
86
- // Skip validation if field is not required and not present
87
- if (!rules.required && (value === undefined || value === null)) {
88
- continue
89
- }
90
-
91
- // Type validation
92
- if (rules.type) {
93
- const typeError = validateType(value, rules.type, fieldPath)
94
- if (typeError) {
95
- errors.push(typeError)
96
- continue
97
- }
98
- }
99
-
100
- // String validations
101
- if (rules.type === 'string' && typeof value === 'string') {
102
- if (rules.minLength && value.length < rules.minLength) {
103
- errors.push({
104
- field: fieldPath,
105
- message: `${field} must be at least ${rules.minLength} characters`,
106
- value
107
- })
108
- }
109
-
110
- if (rules.maxLength && value.length > rules.maxLength) {
111
- errors.push({
112
- field: fieldPath,
113
- message: `${field} must be no more than ${rules.maxLength} characters`,
114
- value
115
- })
116
- }
117
-
118
- if (rules.pattern && !new RegExp(rules.pattern).test(value)) {
119
- errors.push({
120
- field: fieldPath,
121
- message: `${field} format is invalid`,
122
- value
123
- })
124
- }
125
-
126
- if (rules.email && !isValidEmail(value)) {
127
- errors.push({
128
- field: fieldPath,
129
- message: `${field} must be a valid email address`,
130
- value
131
- })
132
- }
133
- }
134
-
135
- // Number validations
136
- if (rules.type === 'number' && typeof value === 'number') {
137
- if (rules.min !== undefined && value < rules.min) {
138
- errors.push({
139
- field: fieldPath,
140
- message: `${field} must be at least ${rules.min}`,
141
- value
142
- })
143
- }
144
-
145
- if (rules.max !== undefined && value > rules.max) {
146
- errors.push({
147
- field: fieldPath,
148
- message: `${field} must be no more than ${rules.max}`,
149
- value
150
- })
151
- }
152
- }
153
-
154
- // Array validations
155
- if (rules.type === 'array' && Array.isArray(value)) {
156
- if (rules.minItems && value.length < rules.minItems) {
157
- errors.push({
158
- field: fieldPath,
159
- message: `${field} must have at least ${rules.minItems} items`,
160
- value
161
- })
162
- }
163
-
164
- if (rules.maxItems && value.length > rules.maxItems) {
165
- errors.push({
166
- field: fieldPath,
167
- message: `${field} must have no more than ${rules.maxItems} items`,
168
- value
169
- })
170
- }
171
- }
172
-
173
- // Enum validation
174
- if (rules.enum && !rules.enum.includes(value)) {
175
- errors.push({
176
- field: fieldPath,
177
- message: `${field} must be one of: ${rules.enum.join(', ')}`,
178
- value
179
- })
180
- }
181
- }
182
-
183
- return errors
184
- }
185
-
186
- /**
187
- * Validate value type
188
- */
189
- function validateType(value: any, expectedType: string, fieldPath: string): ValidationError | null {
190
- const actualType = Array.isArray(value) ? 'array' : typeof value
191
-
192
- if (actualType !== expectedType) {
193
- return {
194
- field: fieldPath,
195
- message: `Expected ${expectedType}, got ${actualType}`,
196
- value
197
- }
198
- }
199
-
200
- return null
201
- }
202
-
203
- /**
204
- * Validate email format
205
- */
206
- function isValidEmail(email: string): boolean {
207
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
208
- return emailRegex.test(email)
209
- }
210
-
211
- /**
212
- * Common validation schemas
213
- */
214
- export const commonSchemas = {
215
- createUser: {
216
- body: {
217
- name: {
218
- type: 'string',
219
- required: true,
220
- minLength: 2,
221
- maxLength: 100
222
- },
223
- email: {
224
- type: 'string',
225
- required: true,
226
- email: true,
227
- maxLength: 255
228
- }
229
- }
230
- },
231
-
232
- updateUser: {
233
- params: {
234
- id: {
235
- type: 'string',
236
- required: true,
237
- pattern: '^\\d+$'
238
- }
239
- },
240
- body: {
241
- name: {
242
- type: 'string',
243
- required: false,
244
- minLength: 2,
245
- maxLength: 100
246
- },
247
- email: {
248
- type: 'string',
249
- required: false,
250
- email: true,
251
- maxLength: 255
252
- }
253
- }
254
- },
255
-
256
- pagination: {
257
- query: {
258
- page: {
259
- type: 'string',
260
- required: false,
261
- pattern: '^\\d+$'
262
- },
263
- limit: {
264
- type: 'string',
265
- required: false,
266
- pattern: '^\\d+$'
267
- }
268
- }
269
- }
270
- }