create-fluxstack 1.0.13 → 1.0.15

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 (214) hide show
  1. package/.env.example +29 -29
  2. package/app/client/README.md +69 -69
  3. package/app/client/index.html +14 -13
  4. package/app/client/src/App.tsx +157 -524
  5. package/app/client/src/components/ErrorBoundary.tsx +107 -0
  6. package/app/client/src/components/ErrorDisplay.css +365 -0
  7. package/app/client/src/components/ErrorDisplay.tsx +258 -0
  8. package/app/client/src/components/FluxStackConfig.tsx +1321 -0
  9. package/app/client/src/components/HybridLiveCounter.tsx +140 -0
  10. package/app/client/src/components/LiveClock.tsx +286 -0
  11. package/app/client/src/components/MainLayout.tsx +390 -0
  12. package/app/client/src/components/SidebarNavigation.tsx +391 -0
  13. package/app/client/src/components/StateDemo.tsx +178 -0
  14. package/app/client/src/components/SystemMonitor.tsx +1038 -0
  15. package/app/client/src/components/Teste.tsx +104 -0
  16. package/app/client/src/components/UserProfile.tsx +809 -0
  17. package/app/client/src/hooks/useAuth.ts +39 -0
  18. package/app/client/src/hooks/useNotifications.ts +56 -0
  19. package/app/client/src/lib/eden-api.ts +189 -53
  20. package/app/client/src/lib/errors.ts +340 -0
  21. package/app/client/src/lib/hooks/useErrorHandler.ts +258 -0
  22. package/app/client/src/lib/index.ts +45 -0
  23. package/app/client/src/main.tsx +3 -2
  24. package/app/client/src/pages/ApiDocs.tsx +182 -0
  25. package/app/client/src/pages/Demo.tsx +174 -0
  26. package/app/client/src/pages/HybridLive.tsx +263 -0
  27. package/app/client/src/pages/Overview.tsx +155 -0
  28. package/app/client/src/store/README.md +43 -0
  29. package/app/client/src/store/index.ts +16 -0
  30. package/app/client/src/store/slices/uiSlice.ts +151 -0
  31. package/app/client/src/store/slices/userSlice.ts +161 -0
  32. package/app/client/src/test/README.md +257 -0
  33. package/app/client/src/test/setup.ts +70 -0
  34. package/app/client/src/test/types.ts +12 -0
  35. package/app/client/src/vite-env.d.ts +1 -1
  36. package/app/client/tsconfig.app.json +44 -43
  37. package/app/client/tsconfig.json +7 -7
  38. package/app/client/tsconfig.node.json +25 -25
  39. package/app/client/zustand-setup.md +65 -0
  40. package/app/server/controllers/users.controller.ts +68 -68
  41. package/app/server/index.ts +9 -1
  42. package/app/server/live/CounterComponent.ts +191 -0
  43. package/app/server/live/FluxStackConfig.ts +529 -0
  44. package/app/server/live/LiveClockComponent.ts +214 -0
  45. package/app/server/live/SidebarNavigation.ts +156 -0
  46. package/app/server/live/SystemMonitor.ts +594 -0
  47. package/app/server/live/SystemMonitorIntegration.ts +151 -0
  48. package/app/server/live/TesteComponent.ts +87 -0
  49. package/app/server/live/UserProfileComponent.ts +135 -0
  50. package/app/server/live/register-components.ts +28 -0
  51. package/app/server/middleware/auth.ts +136 -0
  52. package/app/server/middleware/errorHandling.ts +250 -0
  53. package/app/server/middleware/index.ts +10 -0
  54. package/app/server/middleware/rateLimit.ts +193 -0
  55. package/app/server/middleware/requestLogging.ts +215 -0
  56. package/app/server/middleware/validation.ts +270 -0
  57. package/app/server/routes/index.ts +14 -2
  58. package/app/server/routes/upload.ts +92 -0
  59. package/app/server/routes/users.routes.ts +2 -9
  60. package/app/server/services/NotificationService.ts +302 -0
  61. package/app/server/services/UserService.ts +222 -0
  62. package/app/server/services/index.ts +46 -0
  63. package/core/cli/commands/plugin-deps.ts +263 -0
  64. package/core/cli/generators/README.md +339 -0
  65. package/core/cli/generators/component.ts +770 -0
  66. package/core/cli/generators/controller.ts +299 -0
  67. package/core/cli/generators/index.ts +144 -0
  68. package/core/cli/generators/interactive.ts +228 -0
  69. package/core/cli/generators/prompts.ts +83 -0
  70. package/core/cli/generators/route.ts +513 -0
  71. package/core/cli/generators/service.ts +465 -0
  72. package/core/cli/generators/template-engine.ts +154 -0
  73. package/core/cli/generators/types.ts +71 -0
  74. package/core/cli/generators/utils.ts +192 -0
  75. package/core/cli/index.ts +69 -0
  76. package/core/cli/plugin-discovery.ts +16 -85
  77. package/core/client/fluxstack.ts +17 -0
  78. package/core/client/hooks/index.ts +7 -0
  79. package/core/client/hooks/state-validator.ts +130 -0
  80. package/core/client/hooks/useAuth.ts +49 -0
  81. package/core/client/hooks/useChunkedUpload.ts +258 -0
  82. package/core/client/hooks/useHybridLiveComponent.ts +967 -0
  83. package/core/client/hooks/useWebSocket.ts +373 -0
  84. package/core/client/index.ts +47 -0
  85. package/core/client/state/createStore.ts +193 -0
  86. package/core/client/state/index.ts +15 -0
  87. package/core/config/env-dynamic.ts +1 -1
  88. package/core/config/env.ts +2 -1
  89. package/core/config/runtime-config.ts +3 -3
  90. package/core/config/schema.ts +84 -49
  91. package/core/framework/server.ts +30 -0
  92. package/core/index.ts +25 -0
  93. package/core/live/ComponentRegistry.ts +399 -0
  94. package/core/live/types.ts +164 -0
  95. package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
  96. package/core/plugins/built-in/live-components/index.ts +27 -0
  97. package/core/plugins/built-in/logger/index.ts +1 -1
  98. package/core/plugins/built-in/monitoring/index.ts +1 -1
  99. package/core/plugins/built-in/static/index.ts +1 -1
  100. package/core/plugins/built-in/swagger/index.ts +1 -1
  101. package/core/plugins/built-in/vite/index.ts +1 -1
  102. package/core/plugins/dependency-manager.ts +384 -0
  103. package/core/plugins/index.ts +5 -1
  104. package/core/plugins/manager.ts +7 -3
  105. package/core/plugins/registry.ts +88 -10
  106. package/core/plugins/types.ts +11 -11
  107. package/core/server/framework.ts +43 -0
  108. package/core/server/index.ts +11 -1
  109. package/core/server/live/ComponentRegistry.ts +1017 -0
  110. package/core/server/live/FileUploadManager.ts +272 -0
  111. package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
  112. package/core/server/live/SingleConnectionManager.ts +0 -0
  113. package/core/server/live/StateSignature.ts +644 -0
  114. package/core/server/live/WebSocketConnectionManager.ts +688 -0
  115. package/core/server/live/websocket-plugin.ts +435 -0
  116. package/core/server/middleware/errorHandling.ts +141 -0
  117. package/core/server/middleware/index.ts +16 -0
  118. package/core/server/plugins/static-files-plugin.ts +232 -0
  119. package/core/server/services/BaseService.ts +95 -0
  120. package/core/server/services/ServiceContainer.ts +144 -0
  121. package/core/server/services/index.ts +9 -0
  122. package/core/templates/create-project.ts +196 -33
  123. package/core/testing/index.ts +10 -0
  124. package/core/testing/setup.ts +74 -0
  125. package/core/types/build.ts +38 -14
  126. package/core/types/types.ts +319 -0
  127. package/core/utils/env-runtime.ts +7 -0
  128. package/core/utils/errors/handlers.ts +264 -39
  129. package/core/utils/errors/index.ts +528 -18
  130. package/core/utils/errors/middleware.ts +114 -0
  131. package/core/utils/logger/formatters.ts +222 -0
  132. package/core/utils/logger/index.ts +167 -48
  133. package/core/utils/logger/middleware.ts +253 -0
  134. package/core/utils/logger/performance.ts +384 -0
  135. package/core/utils/logger/transports.ts +365 -0
  136. package/create-fluxstack.ts +296 -296
  137. package/fluxstack.config.ts +17 -1
  138. package/package-template.json +66 -66
  139. package/package.json +31 -6
  140. package/public/README.md +16 -0
  141. package/vite.config.ts +29 -14
  142. package/.claude/settings.local.json +0 -74
  143. package/.github/workflows/ci-build-tests.yml +0 -480
  144. package/.github/workflows/dependency-management.yml +0 -324
  145. package/.github/workflows/release-validation.yml +0 -355
  146. package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
  147. package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
  148. package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
  149. package/CLAUDE.md +0 -200
  150. package/Dockerfile +0 -58
  151. package/Dockerfile.backend +0 -52
  152. package/Dockerfile.frontend +0 -54
  153. package/README-Docker.md +0 -85
  154. package/ai-context/00-QUICK-START.md +0 -86
  155. package/ai-context/README.md +0 -88
  156. package/ai-context/development/eden-treaty-guide.md +0 -362
  157. package/ai-context/development/patterns.md +0 -382
  158. package/ai-context/development/plugins-guide.md +0 -572
  159. package/ai-context/examples/crud-complete.md +0 -626
  160. package/ai-context/project/architecture.md +0 -399
  161. package/ai-context/project/overview.md +0 -213
  162. package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
  163. package/ai-context/recent-changes/type-inference-fix.md +0 -223
  164. package/ai-context/reference/environment-vars.md +0 -384
  165. package/ai-context/reference/troubleshooting.md +0 -407
  166. package/app/client/src/components/TestPage.tsx +0 -453
  167. package/bun.lock +0 -1063
  168. package/bunfig.toml +0 -16
  169. package/core/__tests__/integration.test.ts +0 -227
  170. package/core/build/index.ts +0 -186
  171. package/core/config/__tests__/config-loader.test.ts +0 -554
  172. package/core/config/__tests__/config-merger.test.ts +0 -657
  173. package/core/config/__tests__/env-converter.test.ts +0 -372
  174. package/core/config/__tests__/env-processor.test.ts +0 -431
  175. package/core/config/__tests__/env.test.ts +0 -452
  176. package/core/config/__tests__/integration.test.ts +0 -418
  177. package/core/config/__tests__/loader.test.ts +0 -331
  178. package/core/config/__tests__/schema.test.ts +0 -129
  179. package/core/config/__tests__/validator.test.ts +0 -318
  180. package/core/framework/__tests__/server.test.ts +0 -233
  181. package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
  182. package/core/plugins/__tests__/manager.test.ts +0 -398
  183. package/core/plugins/__tests__/monitoring.test.ts +0 -401
  184. package/core/plugins/__tests__/registry.test.ts +0 -335
  185. package/core/utils/__tests__/errors.test.ts +0 -139
  186. package/core/utils/__tests__/helpers.test.ts +0 -297
  187. package/core/utils/__tests__/logger.test.ts +0 -141
  188. package/create-test-app.ts +0 -156
  189. package/docker-compose.microservices.yml +0 -75
  190. package/docker-compose.simple.yml +0 -57
  191. package/docker-compose.yml +0 -71
  192. package/eslint.config.js +0 -23
  193. package/flux-cli.ts +0 -214
  194. package/nginx-lb.conf +0 -37
  195. package/publish.sh +0 -63
  196. package/run-clean.ts +0 -26
  197. package/run-env-tests.ts +0 -313
  198. package/tailwind.config.js +0 -34
  199. package/tests/__mocks__/api.ts +0 -56
  200. package/tests/fixtures/users.ts +0 -69
  201. package/tests/integration/api/users.routes.test.ts +0 -221
  202. package/tests/setup.ts +0 -29
  203. package/tests/unit/app/client/App-simple.test.tsx +0 -56
  204. package/tests/unit/app/client/App.test.tsx.skip +0 -237
  205. package/tests/unit/app/client/eden-api.test.ts +0 -186
  206. package/tests/unit/app/client/simple.test.tsx +0 -23
  207. package/tests/unit/app/controllers/users.controller.test.ts +0 -150
  208. package/tests/unit/core/create-project.test.ts.skip +0 -95
  209. package/tests/unit/core/framework.test.ts +0 -144
  210. package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
  211. package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
  212. package/tests/utils/test-helpers.ts +0 -61
  213. package/vitest.config.ts +0 -50
  214. package/workspace.json +0 -6
@@ -0,0 +1,319 @@
1
+ // 🔥 FluxStack Live Components - Shared Types
2
+
3
+ export interface LiveMessage {
4
+ type: 'COMPONENT_MOUNT' | 'COMPONENT_UNMOUNT' | 'COMPONENT_REHYDRATE' | 'COMPONENT_ACTION' | 'CALL_ACTION' | 'ACTION_RESPONSE' | 'PROPERTY_UPDATE' | 'STATE_UPDATE' | 'STATE_REHYDRATED' | 'ERROR' | 'BROADCAST'
5
+ componentId: string
6
+ action?: string
7
+ property?: string
8
+ payload?: any
9
+ timestamp?: number
10
+ userId?: string
11
+ room?: string
12
+ // Request-Response system
13
+ requestId?: string
14
+ responseId?: string
15
+ expectResponse?: boolean
16
+ }
17
+
18
+ export interface ComponentState {
19
+ [key: string]: any
20
+ }
21
+
22
+ export interface LiveComponentInstance<TState = ComponentState, TActions = Record<string, Function>> {
23
+ id: string
24
+ state: TState
25
+ call: <T extends keyof TActions>(action: T, ...args: any[]) => Promise<any>
26
+ set: <K extends keyof TState>(property: K, value: TState[K]) => void
27
+ loading: boolean
28
+ errors: Record<string, string>
29
+ connected: boolean
30
+ room?: string
31
+ }
32
+
33
+ export interface WebSocketData {
34
+ components: Map<string, any>
35
+ userId?: string
36
+ subscriptions: Set<string>
37
+ }
38
+
39
+ export interface ComponentDefinition<TState = ComponentState> {
40
+ name: string
41
+ initialState: TState
42
+ component: new (initialState: TState, ws: any) => LiveComponent<TState>
43
+ }
44
+
45
+ export interface BroadcastMessage {
46
+ type: string
47
+ payload: any
48
+ room?: string
49
+ excludeUser?: string
50
+ }
51
+
52
+ // WebSocket Types for Client
53
+ export interface WebSocketMessage {
54
+ type: string
55
+ componentId?: string
56
+ action?: string
57
+ payload?: any
58
+ timestamp?: number
59
+ userId?: string
60
+ room?: string
61
+ // Request-Response system
62
+ requestId?: string
63
+ responseId?: string
64
+ expectResponse?: boolean
65
+ }
66
+
67
+ export interface WebSocketResponse {
68
+ type: 'MESSAGE_RESPONSE' | 'CONNECTION_ESTABLISHED' | 'ERROR' | 'BROADCAST' | 'ACTION_RESPONSE' | 'COMPONENT_MOUNTED' | 'COMPONENT_REHYDRATED' | 'STATE_UPDATE' | 'STATE_REHYDRATED' | 'FILE_UPLOAD_PROGRESS' | 'FILE_UPLOAD_COMPLETE' | 'FILE_UPLOAD_ERROR' | 'FILE_UPLOAD_START_RESPONSE'
69
+ originalType?: string
70
+ componentId?: string
71
+ success?: boolean
72
+ result?: any
73
+ // Request-Response system
74
+ requestId?: string
75
+ responseId?: string
76
+ error?: string
77
+ timestamp?: number
78
+ connectionId?: string
79
+ payload?: any
80
+ // File upload specific fields
81
+ uploadId?: string
82
+ chunkIndex?: number
83
+ totalChunks?: number
84
+ bytesUploaded?: number
85
+ totalBytes?: number
86
+ progress?: number
87
+ filename?: string
88
+ fileUrl?: string
89
+ // Re-hydration specific fields
90
+ signedState?: any
91
+ oldComponentId?: string
92
+ newComponentId?: string
93
+ }
94
+
95
+ // Hybrid Live Component Types
96
+ export interface HybridState<T> {
97
+ data: T
98
+ validation: StateValidation
99
+ conflicts: StateConflict[]
100
+ status: 'synced' | 'conflict' | 'disconnected'
101
+ }
102
+
103
+ export interface StateValidation {
104
+ checksum: string
105
+ version: number
106
+ source: 'client' | 'server' | 'mount'
107
+ timestamp: number
108
+ }
109
+
110
+ export interface StateConflict {
111
+ property: string
112
+ clientValue: any
113
+ serverValue: any
114
+ timestamp: number
115
+ resolved: boolean
116
+ }
117
+
118
+ export interface HybridComponentOptions {
119
+ fallbackToLocal?: boolean
120
+ room?: string
121
+ userId?: string
122
+ autoMount?: boolean
123
+ debug?: boolean
124
+ }
125
+
126
+ export abstract class LiveComponent<TState = ComponentState> {
127
+ public readonly id: string
128
+ public state: TState
129
+ protected ws: any
130
+ public room?: string
131
+ public userId?: string
132
+ public broadcastToRoom: (message: BroadcastMessage) => void = () => {} // Will be injected by registry
133
+
134
+ constructor(initialState: TState, ws: any, options?: { room?: string; userId?: string }) {
135
+ this.id = this.generateId()
136
+ this.state = initialState
137
+ this.ws = ws
138
+ this.room = options?.room
139
+ this.userId = options?.userId
140
+ }
141
+
142
+ // State management
143
+ public setState(updates: Partial<TState> | ((prev: TState) => Partial<TState>)) {
144
+ const newUpdates = typeof updates === 'function' ? updates(this.state) : updates
145
+ this.state = { ...this.state, ...newUpdates }
146
+ this.emit('STATE_UPDATE', { state: this.state })
147
+ }
148
+
149
+ // Execute action safely
150
+ public async executeAction(action: string, payload: any): Promise<any> {
151
+ try {
152
+ // Check if method exists
153
+ const method = (this as any)[action]
154
+ if (typeof method !== 'function') {
155
+ throw new Error(`Action '${action}' not found on component`)
156
+ }
157
+
158
+ // Execute method
159
+ const result = await method.call(this, payload)
160
+ return result
161
+ } catch (error: any) {
162
+ this.emit('ERROR', {
163
+ action,
164
+ error: error.message,
165
+ stack: error.stack
166
+ })
167
+ throw error
168
+ }
169
+ }
170
+
171
+ // Send message to client
172
+ protected emit(type: string, payload: any) {
173
+ const message: LiveMessage = {
174
+ type: type as any,
175
+ componentId: this.id,
176
+ payload,
177
+ timestamp: Date.now(),
178
+ userId: this.userId,
179
+ room: this.room
180
+ }
181
+
182
+ if (this.ws && this.ws.send) {
183
+ this.ws.send(JSON.stringify(message))
184
+ }
185
+ }
186
+
187
+ // Broadcast to all clients in room
188
+ protected broadcast(type: string, payload: any, excludeCurrentUser = false) {
189
+ const message: BroadcastMessage = {
190
+ type,
191
+ payload,
192
+ room: this.room,
193
+ excludeUser: excludeCurrentUser ? this.userId : undefined
194
+ }
195
+
196
+ // This will be handled by the registry
197
+ this.broadcastToRoom(message)
198
+ }
199
+
200
+ // Subscribe to room for multi-user features
201
+ protected async subscribeToRoom(roomId: string) {
202
+ this.room = roomId
203
+ // Registry will handle the actual subscription
204
+ }
205
+
206
+ // Unsubscribe from room
207
+ protected async unsubscribeFromRoom() {
208
+ this.room = undefined
209
+ // Registry will handle the actual unsubscription
210
+ }
211
+
212
+ // Generate unique ID
213
+ private generateId(): string {
214
+ return `live-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
215
+ }
216
+
217
+ // Cleanup when component is destroyed
218
+ public destroy() {
219
+ this.unsubscribeFromRoom()
220
+ // Override in subclasses for custom cleanup
221
+ }
222
+
223
+ // Get serializable state for client
224
+ public getSerializableState(): TState {
225
+ return this.state
226
+ }
227
+ }
228
+
229
+ // Utility types for better TypeScript experience
230
+ export type ComponentActions<T> = {
231
+ [K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never
232
+ }
233
+
234
+ export type ComponentProps<T extends LiveComponent> = T extends LiveComponent<infer TState> ? TState : never
235
+
236
+ export type ActionParameters<T, K extends keyof T> = T[K] extends (...args: infer P) => any ? P : never
237
+
238
+ export type ActionReturnType<T, K extends keyof T> = T[K] extends (...args: any[]) => infer R ? R : never
239
+
240
+ // File Upload Types for Chunked WebSocket Upload
241
+ export interface FileChunkData {
242
+ uploadId: string
243
+ filename: string
244
+ fileType: string
245
+ fileSize: number
246
+ chunkIndex: number
247
+ totalChunks: number
248
+ chunkSize: number
249
+ data: string // Base64 encoded chunk data
250
+ hash?: string // Optional chunk hash for verification
251
+ }
252
+
253
+ export interface FileUploadStartMessage {
254
+ type: 'FILE_UPLOAD_START'
255
+ componentId: string
256
+ uploadId: string
257
+ filename: string
258
+ fileType: string
259
+ fileSize: number
260
+ chunkSize?: number // Optional, defaults to 64KB
261
+ requestId?: string
262
+ }
263
+
264
+ export interface FileUploadChunkMessage {
265
+ type: 'FILE_UPLOAD_CHUNK'
266
+ componentId: string
267
+ uploadId: string
268
+ chunkIndex: number
269
+ totalChunks: number
270
+ data: string // Base64 encoded chunk
271
+ hash?: string
272
+ requestId?: string
273
+ }
274
+
275
+ export interface FileUploadCompleteMessage {
276
+ type: 'FILE_UPLOAD_COMPLETE'
277
+ componentId: string
278
+ uploadId: string
279
+ requestId?: string
280
+ }
281
+
282
+ export interface FileUploadProgressResponse {
283
+ type: 'FILE_UPLOAD_PROGRESS'
284
+ componentId: string
285
+ uploadId: string
286
+ chunkIndex: number
287
+ totalChunks: number
288
+ bytesUploaded: number
289
+ totalBytes: number
290
+ progress: number // 0-100
291
+ requestId?: string
292
+ timestamp: number
293
+ }
294
+
295
+ export interface FileUploadCompleteResponse {
296
+ type: 'FILE_UPLOAD_COMPLETE'
297
+ componentId: string
298
+ uploadId: string
299
+ success: boolean
300
+ filename?: string
301
+ fileUrl?: string
302
+ error?: string
303
+ requestId?: string
304
+ timestamp: number
305
+ }
306
+
307
+ // File Upload Manager for handling uploads
308
+ export interface ActiveUpload {
309
+ uploadId: string
310
+ componentId: string
311
+ filename: string
312
+ fileType: string
313
+ fileSize: number
314
+ totalChunks: number
315
+ receivedChunks: Map<number, string>
316
+ startTime: number
317
+ lastChunkTime: number
318
+ tempFilePath?: string
319
+ }
@@ -216,6 +216,13 @@ export const validate = {
216
216
  if (value && !validValues.includes(value)) {
217
217
  throw new Error(`${key} must be one of: ${validValues.join(', ')}, got: ${value}`)
218
218
  }
219
+ },
220
+
221
+ validate(key: string, validator: (value: string) => boolean, errorMessage: string): void {
222
+ const value = smartEnv.get(key, '')
223
+ if (value && !validator(value)) {
224
+ throw new Error(`${key}: ${errorMessage}`)
225
+ }
219
226
  }
220
227
  }
221
228
 
@@ -1,63 +1,288 @@
1
- import { FluxStackError } from "./index"
1
+ import { FluxStackError, wrapError, type ErrorMetadata, type ErrorSerializedResponse } from "./index"
2
2
  import type { Logger } from "../logger/index"
3
+ import { v4 as uuidv4 } from 'uuid'
3
4
 
4
5
  export interface ErrorHandlerContext {
5
6
  logger: Logger
6
7
  isDevelopment: boolean
7
8
  request?: Request
8
9
  path?: string
10
+ method?: string
11
+ correlationId?: string
12
+ userId?: string
13
+ userAgent?: string
14
+ ip?: string
15
+ metricsCollector?: ErrorMetricsCollector
9
16
  }
10
17
 
11
- export const errorHandler = (error: Error, context: ErrorHandlerContext) => {
12
- const { logger, isDevelopment, request, path } = context
13
-
14
- if (error instanceof FluxStackError) {
15
- // Log FluxStack errors with appropriate level
16
- const logLevel = error.statusCode >= 500 ? 'error' : 'warn'
17
- logger[logLevel](error.message, {
18
+ export interface ErrorMetricsCollector {
19
+ recordError(error: FluxStackError, context: ErrorHandlerContext): void
20
+ }
21
+
22
+ export interface ErrorRecoveryStrategy {
23
+ canRecover(error: FluxStackError): boolean
24
+ recover(error: FluxStackError, context: ErrorHandlerContext): Promise<any> | any
25
+ }
26
+
27
+ export interface ErrorHandlerOptions {
28
+ enableStackTrace?: boolean
29
+ enableErrorMetrics?: boolean
30
+ enableCorrelationId?: boolean
31
+ sanitizeErrors?: boolean
32
+ recoveryStrategies?: ErrorRecoveryStrategy[]
33
+ customErrorMessages?: Record<string, string>
34
+ }
35
+
36
+ export class EnhancedErrorHandler {
37
+ private options: Required<ErrorHandlerOptions>
38
+ private recoveryStrategies: ErrorRecoveryStrategy[]
39
+
40
+ constructor(options: ErrorHandlerOptions = {}) {
41
+ this.options = {
42
+ enableStackTrace: options.enableStackTrace ?? true,
43
+ enableErrorMetrics: options.enableErrorMetrics ?? true,
44
+ enableCorrelationId: options.enableCorrelationId ?? true,
45
+ sanitizeErrors: options.sanitizeErrors ?? true,
46
+ recoveryStrategies: options.recoveryStrategies ?? [],
47
+ customErrorMessages: options.customErrorMessages ?? {}
48
+ }
49
+ this.recoveryStrategies = this.options.recoveryStrategies
50
+ }
51
+
52
+ async handle(error: Error, context: ErrorHandlerContext): Promise<ErrorSerializedResponse> {
53
+ const { logger, isDevelopment, request, path, method, correlationId, userId, userAgent, ip, metricsCollector } = context
54
+
55
+ // Generate correlation ID if not provided and enabled
56
+ const finalCorrelationId = this.options.enableCorrelationId
57
+ ? (correlationId || uuidv4())
58
+ : correlationId
59
+
60
+ // Create metadata from context
61
+ const metadata: ErrorMetadata = {
62
+ correlationId: finalCorrelationId,
63
+ userId,
64
+ userAgent,
65
+ ip,
66
+ path,
67
+ method,
68
+ timestamp: new Date().toISOString()
69
+ }
70
+
71
+ // Convert to FluxStackError if needed
72
+ let fluxError: FluxStackError
73
+ if (error instanceof FluxStackError) {
74
+ fluxError = error.withMetadata(metadata)
75
+ } else {
76
+ fluxError = wrapError(error, metadata)
77
+ }
78
+
79
+ // Try recovery strategies for operational errors
80
+ if (fluxError.isOperational && this.recoveryStrategies.length > 0) {
81
+ for (const strategy of this.recoveryStrategies) {
82
+ if (strategy.canRecover(fluxError)) {
83
+ try {
84
+ const recoveryResult = await strategy.recover(fluxError, context)
85
+ logger.info('Error recovery successful', {
86
+ errorCode: fluxError.code,
87
+ correlationId: finalCorrelationId,
88
+ strategy: strategy.constructor.name
89
+ })
90
+ return recoveryResult
91
+ } catch (recoveryError) {
92
+ logger.warn('Error recovery failed', {
93
+ errorCode: fluxError.code,
94
+ correlationId: finalCorrelationId,
95
+ strategy: strategy.constructor.name,
96
+ recoveryError: recoveryError instanceof Error ? recoveryError.message : recoveryError
97
+ })
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ // Log the error with appropriate level and context
104
+ this.logError(fluxError, logger, isDevelopment)
105
+
106
+ // Record metrics if enabled
107
+ if (this.options.enableErrorMetrics && metricsCollector) {
108
+ try {
109
+ metricsCollector.recordError(fluxError, context)
110
+ } catch (metricsError) {
111
+ logger.warn('Failed to record error metrics', {
112
+ error: metricsError instanceof Error ? metricsError.message : metricsError
113
+ })
114
+ }
115
+ }
116
+
117
+ // Generate user-friendly response
118
+ return this.generateErrorResponse(fluxError, isDevelopment)
119
+ }
120
+
121
+ private logError(error: FluxStackError, logger: Logger, isDevelopment: boolean): void {
122
+ const logLevel = this.getLogLevel(error)
123
+ const logData = {
18
124
  code: error.code,
19
125
  statusCode: error.statusCode,
20
126
  context: error.context,
21
- path,
22
- method: request?.method,
23
- stack: isDevelopment ? error.stack : undefined
24
- })
127
+ metadata: error.metadata,
128
+ isOperational: error.isOperational,
129
+ ...(isDevelopment && { stack: error.stack })
130
+ }
131
+
132
+ // Skip logging for certain errors to reduce noise
133
+ if (this.shouldSkipLogging(error)) {
134
+ return
135
+ }
136
+
137
+ logger[logLevel](error.message, logData)
138
+ }
139
+
140
+ private getLogLevel(error: FluxStackError): 'error' | 'warn' | 'info' {
141
+ if (!error.isOperational) {
142
+ return 'error'
143
+ }
25
144
 
26
- return {
27
- error: {
28
- message: error.message,
29
- code: error.code,
30
- statusCode: error.statusCode,
31
- ...(error.context && { details: error.context }),
32
- ...(isDevelopment && { stack: error.stack })
145
+ if (error.statusCode >= 500) {
146
+ return 'error'
147
+ } else if (error.statusCode >= 400) {
148
+ return 'warn'
149
+ } else {
150
+ return 'info'
151
+ }
152
+ }
153
+
154
+ private shouldSkipLogging(error: FluxStackError): boolean {
155
+ // Skip logging for 404 errors unless explicitly enabled
156
+ if (error.code === 'NOT_FOUND' && !process.env.ENABLE_NOT_FOUND_LOGS) {
157
+ return true
158
+ }
159
+
160
+ // Skip logging for rate limit errors to prevent log spam
161
+ if (error.code === 'RATE_LIMIT_EXCEEDED' && !process.env.ENABLE_RATE_LIMIT_LOGS) {
162
+ return true
163
+ }
164
+
165
+ return false
166
+ }
167
+
168
+ private generateErrorResponse(error: FluxStackError, isDevelopment: boolean): ErrorSerializedResponse {
169
+ const response = error.toResponse(isDevelopment)
170
+
171
+ // Apply custom error messages if configured
172
+ if (this.options.customErrorMessages[error.code]) {
173
+ response.error.message = this.options.customErrorMessages[error.code]
174
+ }
175
+
176
+ // Sanitize sensitive information in production
177
+ if (!isDevelopment && this.options.sanitizeErrors) {
178
+ response.error = this.sanitizeErrorResponse(response.error)
179
+ }
180
+
181
+ return response
182
+ }
183
+
184
+ private sanitizeErrorResponse(errorResponse: any): any {
185
+ const sanitized = { ...errorResponse }
186
+
187
+ // Remove potentially sensitive fields in production
188
+ if (sanitized.details) {
189
+ // Remove sensitive fields from details
190
+ const sensitiveFields = ['password', 'token', 'secret', 'key', 'credential']
191
+ for (const field of sensitiveFields) {
192
+ if (sanitized.details[field]) {
193
+ sanitized.details[field] = '[REDACTED]'
194
+ }
33
195
  }
34
196
  }
197
+
198
+ return sanitized
35
199
  }
36
-
37
- // Handle unknown errors - skip logging for NOT_FOUND unless explicitly enabled
38
- if (error.message === 'NOT_FOUND' && !process.env.ENABLE_NOT_FOUND_LOGS) {
39
- // Skip logging NOT_FOUND errors to reduce noise
40
- } else {
41
- logger.error('Unhandled error', {
42
- error: error.message,
43
- stack: error.stack,
44
- path,
45
- method: request?.method
46
- })
200
+
201
+ addRecoveryStrategy(strategy: ErrorRecoveryStrategy): void {
202
+ this.recoveryStrategies.push(strategy)
47
203
  }
204
+
205
+ removeRecoveryStrategy(strategyClass: new (...args: any[]) => ErrorRecoveryStrategy): void {
206
+ this.recoveryStrategies = this.recoveryStrategies.filter(
207
+ strategy => !(strategy instanceof strategyClass)
208
+ )
209
+ }
210
+ }
211
+
212
+ // Legacy error handler for backward compatibility
213
+ export const errorHandler = (error: Error, context: ErrorHandlerContext) => {
214
+ const handler = new EnhancedErrorHandler()
215
+ return handler.handle(error, context)
216
+ }
217
+
218
+ export const createErrorHandler = (
219
+ baseContext: Omit<ErrorHandlerContext, 'request' | 'path' | 'method'>,
220
+ options?: ErrorHandlerOptions
221
+ ) => {
222
+ const handler = new EnhancedErrorHandler(options)
48
223
 
49
- return {
50
- error: {
51
- message: isDevelopment ? error.message : 'Internal server error',
52
- code: 'INTERNAL_ERROR',
53
- statusCode: 500,
54
- ...(isDevelopment && { stack: error.stack })
224
+ return async (error: Error, request?: Request, path?: string, method?: string) => {
225
+ const context: ErrorHandlerContext = {
226
+ ...baseContext,
227
+ request,
228
+ path,
229
+ method: method || request?.method
55
230
  }
231
+
232
+ return handler.handle(error, context)
233
+ }
234
+ }
235
+
236
+ // Built-in recovery strategies
237
+ export class RetryRecoveryStrategy implements ErrorRecoveryStrategy {
238
+ constructor(
239
+ private maxRetries: number = 3,
240
+ private retryDelay: number = 1000,
241
+ private retryableCodes: string[] = ['EXTERNAL_SERVICE_ERROR', 'DATABASE_ERROR']
242
+ ) {}
243
+
244
+ canRecover(error: FluxStackError): boolean {
245
+ return this.retryableCodes.includes(error.code) &&
246
+ (!error.context?.retryCount || error.context.retryCount < this.maxRetries)
247
+ }
248
+
249
+ async recover(error: FluxStackError, context: ErrorHandlerContext): Promise<any> {
250
+ const retryCount = (error.context?.retryCount || 0) + 1
251
+
252
+ context.logger.info('Attempting error recovery', {
253
+ errorCode: error.code,
254
+ retryCount,
255
+ maxRetries: this.maxRetries
256
+ })
257
+
258
+ // Wait before retry
259
+ await new Promise(resolve => setTimeout(resolve, this.retryDelay * retryCount))
260
+
261
+ // This would typically re-execute the original operation
262
+ // For now, we'll just return a recovery response
263
+ throw error.withMetadata({ ...error.metadata, retryCount })
56
264
  }
57
265
  }
58
266
 
59
- export const createErrorHandler = (context: Omit<ErrorHandlerContext, 'request' | 'path'>) => {
60
- return (error: Error, request?: Request, path?: string) => {
61
- return errorHandler(error, { ...context, request, path })
267
+ export class FallbackRecoveryStrategy implements ErrorRecoveryStrategy {
268
+ constructor(
269
+ private fallbackResponse: any,
270
+ private applicableCodes: string[] = ['EXTERNAL_SERVICE_ERROR']
271
+ ) {}
272
+
273
+ canRecover(error: FluxStackError): boolean {
274
+ return this.applicableCodes.includes(error.code)
275
+ }
276
+
277
+ recover(error: FluxStackError, context: ErrorHandlerContext): any {
278
+ context.logger.info('Using fallback recovery', {
279
+ errorCode: error.code,
280
+ correlationId: error.metadata.correlationId
281
+ })
282
+
283
+ return {
284
+ data: this.fallbackResponse,
285
+ warning: 'Fallback data provided due to service unavailability'
286
+ }
62
287
  }
63
288
  }