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,39 @@
1
+ /**
2
+ * Authentication hooks
3
+ * App-specific authentication hook using FluxStack core
4
+ */
5
+
6
+ import { useUserStore } from '../store/slices/userSlice'
7
+
8
+ export function useAuth() {
9
+ const {
10
+ currentUser,
11
+ isAuthenticated,
12
+ isLoading,
13
+ error,
14
+ login,
15
+ register,
16
+ logout,
17
+ updateProfile,
18
+ clearError
19
+ } = useUserStore()
20
+
21
+ // Computed values
22
+ const isAdmin = currentUser?.role === 'admin'
23
+
24
+ return {
25
+ // State
26
+ currentUser,
27
+ isAuthenticated,
28
+ isLoading,
29
+ error,
30
+ isAdmin,
31
+
32
+ // Actions
33
+ login,
34
+ register,
35
+ logout,
36
+ updateProfile,
37
+ clearError
38
+ }
39
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Notification management hooks
3
+ * Provides easy-to-use hooks for managing notifications using Zustand
4
+ */
5
+
6
+ import { useCallback } from 'react'
7
+ import { useUIStore } from '../store/slices/uiSlice'
8
+
9
+ export function useNotifications() {
10
+ const {
11
+ notifications,
12
+ addNotification,
13
+ removeNotification,
14
+ clearNotifications
15
+ } = useUIStore()
16
+
17
+ // Convenience methods for different notification types
18
+ const success = useCallback(
19
+ (title: string, message: string, duration?: number) => {
20
+ addNotification({ type: 'success', title, message, duration })
21
+ },
22
+ [addNotification]
23
+ )
24
+
25
+ const error = useCallback(
26
+ (title: string, message: string, duration?: number) => {
27
+ addNotification({ type: 'error', title, message, duration })
28
+ },
29
+ [addNotification]
30
+ )
31
+
32
+ const warning = useCallback(
33
+ (title: string, message: string, duration?: number) => {
34
+ addNotification({ type: 'warning', title, message, duration })
35
+ },
36
+ [addNotification]
37
+ )
38
+
39
+ const info = useCallback(
40
+ (title: string, message: string, duration?: number) => {
41
+ addNotification({ type: 'info', title, message, duration })
42
+ },
43
+ [addNotification]
44
+ )
45
+
46
+ return {
47
+ notifications,
48
+ addNotification,
49
+ removeNotification,
50
+ clearNotifications,
51
+ success,
52
+ error,
53
+ warning,
54
+ info
55
+ }
56
+ }
@@ -21,7 +21,20 @@ const client = treaty<App>(getBaseUrl())
21
21
  // Export the client's API directly to get proper type inference
22
22
  export const api = client.api
23
23
 
24
- // Enhanced error handling
24
+ // Enhanced error handling with retry and recovery strategies
25
+ import {
26
+ ClientAPIError,
27
+ NetworkError,
28
+ TimeoutError,
29
+ withRetry,
30
+ withFallback,
31
+ CircuitBreaker,
32
+ getDefaultUserMessage,
33
+ type RetryOptions,
34
+ type FallbackOptions
35
+ } from './errors'
36
+
37
+ // Legacy interface for backward compatibility
25
38
  export interface APIError {
26
39
  message: string
27
40
  status: number
@@ -29,6 +42,7 @@ export interface APIError {
29
42
  details?: any
30
43
  }
31
44
 
45
+ // Legacy class for backward compatibility
32
46
  export class APIException extends Error {
33
47
  status: number
34
48
  code?: string
@@ -43,68 +57,190 @@ export class APIException extends Error {
43
57
  }
44
58
  }
45
59
 
46
- // Minimal wrapper that preserves Eden's automatic type inference
47
- export async function apiCall(apiPromise: Promise<any>) {
48
- try {
49
- const { data, error } = await apiPromise
50
-
51
- if (error) {
52
- throw new APIException({
53
- message: error.value?.message || 'API Error',
54
- status: error.status,
55
- code: error.value?.code,
56
- details: error.value
57
- })
58
- }
59
-
60
- return data // ✨ Preserva a inferência automática do Eden
61
- } catch (error) {
62
- // Handle network errors and other promise rejections
63
- if (error instanceof APIException) {
64
- throw error // Re-throw APIException
65
- }
66
-
67
- if (error instanceof Error) {
68
- throw new APIException({
69
- message: error.message,
70
- status: 500,
71
- code: 'NETWORK_ERROR'
72
- })
73
- }
74
-
75
- // Handle unknown error types
76
- throw new APIException({
77
- message: 'Unknown error',
78
- status: 500,
79
- code: 'NETWORK_ERROR'
60
+ // Global circuit breaker for API calls
61
+ const apiCircuitBreaker = new CircuitBreaker(5, 60000, 60000)
62
+
63
+ // Enhanced API call wrapper with retry and recovery
64
+ export async function apiCall<T>(
65
+ apiPromise: Promise<any>,
66
+ options: {
67
+ retry?: Partial<RetryOptions>
68
+ fallback?: FallbackOptions<T>
69
+ timeout?: number
70
+ useCircuitBreaker?: boolean
71
+ } = {}
72
+ ): Promise<T> {
73
+ const { retry, fallback, timeout = 30000, useCircuitBreaker = true } = options
74
+
75
+ const executeCall = async (): Promise<T> => {
76
+ // Add timeout to the API call
77
+ const timeoutPromise = new Promise<never>((_, reject) => {
78
+ setTimeout(() => {
79
+ reject(new TimeoutError(timeout))
80
+ }, timeout)
80
81
  })
82
+
83
+ try {
84
+ const result = await Promise.race([apiPromise, timeoutPromise])
85
+ const { data, error } = result
86
+
87
+ if (error) {
88
+ const correlationId = error.value?.correlationId
89
+
90
+ throw new ClientAPIError(
91
+ error.value?.message || 'API Error',
92
+ error.value?.code || 'API_ERROR',
93
+ error.status,
94
+ error.value?.details || error.value,
95
+ correlationId,
96
+ error.value?.userMessage
97
+ )
98
+ }
99
+
100
+ return data
101
+ } catch (error) {
102
+ // Handle different types of errors
103
+ if (error instanceof ClientAPIError || error instanceof TimeoutError) {
104
+ throw error
105
+ }
106
+
107
+ if (error instanceof Error) {
108
+ // Check if it's a network error
109
+ if (error.message.includes('fetch') || error.message.includes('network')) {
110
+ throw new NetworkError(error.message)
111
+ }
112
+
113
+ throw new ClientAPIError(
114
+ error.message,
115
+ 'NETWORK_ERROR',
116
+ 0,
117
+ undefined,
118
+ undefined,
119
+ 'Unable to connect to the server. Please check your connection.'
120
+ )
121
+ }
122
+
123
+ throw new ClientAPIError(
124
+ 'Unknown error occurred',
125
+ 'UNKNOWN_ERROR',
126
+ 500,
127
+ error
128
+ )
129
+ }
130
+ }
131
+
132
+ // Wrap with circuit breaker if enabled
133
+ const callWithCircuitBreaker = useCircuitBreaker
134
+ ? () => apiCircuitBreaker.execute(executeCall)
135
+ : executeCall
136
+
137
+ // Apply retry logic if specified
138
+ const callWithRetry = retry
139
+ ? () => withRetry(callWithCircuitBreaker, retry)
140
+ : callWithCircuitBreaker
141
+
142
+ // Apply fallback if specified
143
+ if (fallback) {
144
+ return withFallback(callWithRetry, fallback)
81
145
  }
146
+
147
+ return callWithRetry()
148
+ }
149
+
150
+ // Simplified API call for basic usage (backward compatibility)
151
+ export async function simpleApiCall(apiPromise: Promise<any>) {
152
+ return apiCall(apiPromise)
82
153
  }
83
154
 
84
- // User-friendly error messages
155
+ // Specialized API calls for different scenarios
156
+ export async function criticalApiCall<T>(apiPromise: Promise<any>): Promise<T> {
157
+ return apiCall(apiPromise, {
158
+ retry: {
159
+ maxRetries: 5,
160
+ baseDelay: 2000,
161
+ maxDelay: 30000
162
+ },
163
+ timeout: 60000,
164
+ useCircuitBreaker: true
165
+ })
166
+ }
167
+
168
+ export async function backgroundApiCall<T>(
169
+ apiPromise: Promise<any>,
170
+ fallbackValue: T
171
+ ): Promise<T> {
172
+ return apiCall(apiPromise, {
173
+ retry: {
174
+ maxRetries: 2,
175
+ baseDelay: 1000
176
+ },
177
+ fallback: {
178
+ fallbackValue,
179
+ showFallbackMessage: false
180
+ },
181
+ timeout: 15000,
182
+ useCircuitBreaker: false
183
+ })
184
+ }
185
+
186
+ export async function userActionApiCall<T>(apiPromise: Promise<any>): Promise<T> {
187
+ return apiCall(apiPromise, {
188
+ retry: {
189
+ maxRetries: 3,
190
+ baseDelay: 1000,
191
+ retryableStatusCodes: [408, 429, 500, 502, 503, 504]
192
+ },
193
+ timeout: 30000,
194
+ useCircuitBreaker: true
195
+ })
196
+ }
197
+
198
+ // User-friendly error messages (enhanced)
85
199
  export function getErrorMessage(error: unknown): string {
200
+ if (error instanceof ClientAPIError) {
201
+ return error.getUserFriendlyMessage()
202
+ }
203
+
86
204
  if (error instanceof APIException) {
87
- switch (error.status) {
88
- case 400:
89
- return error.message || 'Dados inválidos fornecidos'
90
- case 401:
91
- return 'Acesso não autorizado'
92
- case 403:
93
- return 'Acesso negado'
94
- case 404:
95
- return 'Recurso não encontrado'
96
- case 422:
97
- return 'Dados de entrada inválidos'
98
- case 500:
99
- return 'Erro interno do servidor'
100
- default:
101
- return error.message || 'Erro desconhecido'
102
- }
205
+ return getDefaultUserMessage(error.code || 'UNKNOWN_ERROR', error.status)
103
206
  }
104
207
 
105
208
  if (error instanceof Error) {
106
209
  return error.message
107
210
  }
108
211
 
109
- return 'Erro desconhecido'
212
+ return 'An unexpected error occurred'
213
+ }
214
+
215
+ // Error recovery utilities
216
+ export function isRetryableError(error: unknown): boolean {
217
+ if (error instanceof ClientAPIError) {
218
+ return error.isRetryable
219
+ }
220
+
221
+ if (error instanceof APIException) {
222
+ const retryableStatusCodes = [408, 429, 500, 502, 503, 504]
223
+ return retryableStatusCodes.includes(error.status)
224
+ }
225
+
226
+ return false
227
+ }
228
+
229
+ export function shouldShowErrorToUser(error: unknown): boolean {
230
+ if (error instanceof ClientAPIError) {
231
+ // Don't show technical errors to users
232
+ const technicalCodes = ['DATABASE_ERROR', 'EXTERNAL_SERVICE_ERROR', 'INTERNAL_SERVER_ERROR']
233
+ return !technicalCodes.includes(error.code)
234
+ }
235
+
236
+ return true
237
+ }
238
+
239
+ // Circuit breaker utilities
240
+ export function getCircuitBreakerState(): string {
241
+ return apiCircuitBreaker.getState()
242
+ }
243
+
244
+ export function resetCircuitBreaker(): void {
245
+ apiCircuitBreaker.reset()
110
246
  }
@@ -0,0 +1,340 @@
1
+ // Client-side error handling utilities
2
+ export interface ClientError {
3
+ message: string
4
+ code: string
5
+ statusCode: number
6
+ details?: any
7
+ timestamp: string
8
+ correlationId?: string
9
+ userMessage?: string
10
+ }
11
+
12
+ export interface RetryOptions {
13
+ maxRetries: number
14
+ baseDelay: number
15
+ maxDelay: number
16
+ backoffFactor: number
17
+ retryableStatusCodes: number[]
18
+ retryableErrorCodes: string[]
19
+ }
20
+
21
+ export interface FallbackOptions<T> {
22
+ fallbackValue?: T
23
+ fallbackFunction?: () => T | Promise<T>
24
+ showFallbackMessage?: boolean
25
+ }
26
+
27
+ export class ClientAPIError extends Error {
28
+ public readonly code: string
29
+ public readonly statusCode: number
30
+ public readonly details?: any
31
+ public readonly timestamp: Date
32
+ public readonly correlationId?: string
33
+ public readonly userMessage?: string
34
+ public readonly isRetryable: boolean
35
+
36
+ constructor(
37
+ message: string,
38
+ code: string,
39
+ statusCode: number,
40
+ details?: any,
41
+ correlationId?: string,
42
+ userMessage?: string
43
+ ) {
44
+ super(message)
45
+ this.name = 'ClientAPIError'
46
+ this.code = code
47
+ this.statusCode = statusCode
48
+ this.details = details
49
+ this.timestamp = new Date()
50
+ this.correlationId = correlationId
51
+ this.userMessage = userMessage
52
+ this.isRetryable = this.determineRetryability()
53
+ }
54
+
55
+ private determineRetryability(): boolean {
56
+ // Network errors and server errors are generally retryable
57
+ const retryableStatusCodes = [408, 429, 500, 502, 503, 504]
58
+ const retryableErrorCodes = [
59
+ 'NETWORK_ERROR',
60
+ 'TIMEOUT_ERROR',
61
+ 'EXTERNAL_SERVICE_ERROR',
62
+ 'DATABASE_ERROR',
63
+ 'SERVICE_UNAVAILABLE',
64
+ 'RATE_LIMIT_EXCEEDED'
65
+ ]
66
+
67
+ return retryableStatusCodes.includes(this.statusCode) ||
68
+ retryableErrorCodes.includes(this.code)
69
+ }
70
+
71
+ toJSON(): ClientError {
72
+ return {
73
+ message: this.message,
74
+ code: this.code,
75
+ statusCode: this.statusCode,
76
+ details: this.details,
77
+ timestamp: this.timestamp.toISOString(),
78
+ correlationId: this.correlationId,
79
+ userMessage: this.userMessage
80
+ }
81
+ }
82
+
83
+ getUserFriendlyMessage(): string {
84
+ if (this.userMessage) {
85
+ return this.userMessage
86
+ }
87
+
88
+ return getDefaultUserMessage(this.code, this.statusCode)
89
+ }
90
+ }
91
+
92
+ export class NetworkError extends ClientAPIError {
93
+ constructor(message: string = 'Network connection failed', correlationId?: string) {
94
+ super(
95
+ message,
96
+ 'NETWORK_ERROR',
97
+ 0,
98
+ undefined,
99
+ correlationId,
100
+ 'Unable to connect to the server. Please check your internet connection and try again.'
101
+ )
102
+ this.name = 'NetworkError'
103
+ }
104
+ }
105
+
106
+ export class TimeoutError extends ClientAPIError {
107
+ constructor(timeout: number, correlationId?: string) {
108
+ super(
109
+ `Request timed out after ${timeout}ms`,
110
+ 'TIMEOUT_ERROR',
111
+ 408,
112
+ { timeout },
113
+ correlationId,
114
+ 'The request is taking longer than expected. Please try again.'
115
+ )
116
+ this.name = 'TimeoutError'
117
+ }
118
+ }
119
+
120
+ // Default user-friendly messages
121
+ export function getDefaultUserMessage(code: string, statusCode: number): string {
122
+ // First check by error code
123
+ const codeMessages: Record<string, string> = {
124
+ 'VALIDATION_ERROR': 'Please check your input and try again.',
125
+ 'INVALID_INPUT': 'The information provided is not valid.',
126
+ 'MISSING_REQUIRED_FIELD': 'Please fill in all required fields.',
127
+ 'UNAUTHORIZED': 'Please log in to access this resource.',
128
+ 'INVALID_TOKEN': 'Your session has expired. Please log in again.',
129
+ 'TOKEN_EXPIRED': 'Your session has expired. Please log in again.',
130
+ 'FORBIDDEN': 'You do not have permission to perform this action.',
131
+ 'INSUFFICIENT_PERMISSIONS': 'You do not have the required permissions.',
132
+ 'NOT_FOUND': 'The requested resource could not be found.',
133
+ 'RESOURCE_NOT_FOUND': 'The requested item could not be found.',
134
+ 'ENDPOINT_NOT_FOUND': 'The requested service is not available.',
135
+ 'CONFLICT': 'There was a conflict with the current state.',
136
+ 'RESOURCE_ALREADY_EXISTS': 'This item already exists.',
137
+ 'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again later.',
138
+ 'INTERNAL_SERVER_ERROR': 'An unexpected error occurred. Please try again later.',
139
+ 'DATABASE_ERROR': 'A database error occurred. Please try again later.',
140
+ 'EXTERNAL_SERVICE_ERROR': 'An external service is currently unavailable.',
141
+ 'SERVICE_UNAVAILABLE': 'The service is temporarily unavailable.',
142
+ 'MAINTENANCE_MODE': 'The service is under maintenance. Please try again later.',
143
+ 'NETWORK_ERROR': 'Unable to connect to the server. Please check your connection.',
144
+ 'TIMEOUT_ERROR': 'The request timed out. Please try again.'
145
+ }
146
+
147
+ if (codeMessages[code]) {
148
+ return codeMessages[code]
149
+ }
150
+
151
+ // Fallback to status code messages
152
+ const statusMessages: Record<number, string> = {
153
+ 400: 'Invalid request. Please check your input.',
154
+ 401: 'Authentication required. Please log in.',
155
+ 403: 'Access denied. You do not have permission.',
156
+ 404: 'Resource not found.',
157
+ 409: 'Conflict with existing data.',
158
+ 422: 'Invalid data provided.',
159
+ 429: 'Too many requests. Please try again later.',
160
+ 500: 'Server error. Please try again later.',
161
+ 502: 'Service temporarily unavailable.',
162
+ 503: 'Service temporarily unavailable.',
163
+ 504: 'Request timeout. Please try again.'
164
+ }
165
+
166
+ return statusMessages[statusCode] || 'An unexpected error occurred.'
167
+ }
168
+
169
+ // Retry utility with exponential backoff
170
+ export async function withRetry<T>(
171
+ operation: () => Promise<T>,
172
+ options: Partial<RetryOptions> = {}
173
+ ): Promise<T> {
174
+ const config: RetryOptions = {
175
+ maxRetries: 3,
176
+ baseDelay: 1000,
177
+ maxDelay: 10000,
178
+ backoffFactor: 2,
179
+ retryableStatusCodes: [408, 429, 500, 502, 503, 504],
180
+ retryableErrorCodes: [
181
+ 'NETWORK_ERROR',
182
+ 'TIMEOUT_ERROR',
183
+ 'EXTERNAL_SERVICE_ERROR',
184
+ 'DATABASE_ERROR',
185
+ 'SERVICE_UNAVAILABLE',
186
+ 'RATE_LIMIT_EXCEEDED'
187
+ ],
188
+ ...options
189
+ }
190
+
191
+ let lastError: Error | undefined
192
+
193
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
194
+ try {
195
+ return await operation()
196
+ } catch (error) {
197
+ lastError = error as Error
198
+
199
+ // Don't retry on the last attempt
200
+ if (attempt === config.maxRetries) {
201
+ break
202
+ }
203
+
204
+ // Check if error is retryable
205
+ if (error instanceof ClientAPIError) {
206
+ const isRetryableStatus = config.retryableStatusCodes.includes(error.statusCode)
207
+ const isRetryableCode = config.retryableErrorCodes.includes(error.code)
208
+
209
+ if (!isRetryableStatus && !isRetryableCode) {
210
+ break // Don't retry non-retryable errors
211
+ }
212
+ } else if (!(error instanceof NetworkError || error instanceof TimeoutError)) {
213
+ break // Don't retry unknown errors
214
+ }
215
+
216
+ // Calculate delay with exponential backoff
217
+ const delay = Math.min(
218
+ config.baseDelay * Math.pow(config.backoffFactor, attempt),
219
+ config.maxDelay
220
+ )
221
+
222
+ // Add jitter to prevent thundering herd
223
+ const jitteredDelay = delay + Math.random() * 1000
224
+
225
+ await new Promise(resolve => setTimeout(resolve, jitteredDelay))
226
+ }
227
+ }
228
+
229
+ throw lastError!
230
+ }
231
+
232
+ // Fallback utility
233
+ export async function withFallback<T>(
234
+ operation: () => Promise<T>,
235
+ fallbackOptions: FallbackOptions<T>
236
+ ): Promise<T> {
237
+ try {
238
+ return await operation()
239
+ } catch (error) {
240
+ console.warn('Operation failed, using fallback:', error)
241
+
242
+ if (fallbackOptions.fallbackFunction) {
243
+ return await fallbackOptions.fallbackFunction()
244
+ }
245
+
246
+ if (fallbackOptions.fallbackValue !== undefined) {
247
+ return fallbackOptions.fallbackValue
248
+ }
249
+
250
+ throw error // Re-throw if no fallback provided
251
+ }
252
+ }
253
+
254
+ // Circuit breaker pattern for preventing cascading failures
255
+ export class CircuitBreaker {
256
+ private failures = 0
257
+ private lastFailureTime = 0
258
+ private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED'
259
+
260
+ private failureThreshold: number
261
+ private recoveryTimeout: number
262
+
263
+ constructor(
264
+ failureThreshold: number = 5,
265
+ recoveryTimeout: number = 60000
266
+ ) {
267
+ this.failureThreshold = failureThreshold
268
+ this.recoveryTimeout = recoveryTimeout
269
+ }
270
+
271
+ async execute<T>(operation: () => Promise<T>): Promise<T> {
272
+ if (this.state === 'OPEN') {
273
+ if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
274
+ this.state = 'HALF_OPEN'
275
+ } else {
276
+ throw new ClientAPIError(
277
+ 'Circuit breaker is open',
278
+ 'CIRCUIT_BREAKER_OPEN',
279
+ 503,
280
+ undefined,
281
+ undefined,
282
+ 'Service is temporarily unavailable due to repeated failures'
283
+ )
284
+ }
285
+ }
286
+
287
+ try {
288
+ const result = await operation()
289
+ this.onSuccess()
290
+ return result
291
+ } catch (error) {
292
+ this.onFailure()
293
+ throw error
294
+ }
295
+ }
296
+
297
+ private onSuccess(): void {
298
+ this.failures = 0
299
+ this.state = 'CLOSED'
300
+ }
301
+
302
+ private onFailure(): void {
303
+ this.failures++
304
+ this.lastFailureTime = Date.now()
305
+
306
+ if (this.failures >= this.failureThreshold) {
307
+ this.state = 'OPEN'
308
+ }
309
+ }
310
+
311
+ getState(): string {
312
+ return this.state
313
+ }
314
+
315
+ reset(): void {
316
+ this.failures = 0
317
+ this.lastFailureTime = 0
318
+ this.state = 'CLOSED'
319
+ }
320
+ }
321
+
322
+ // Error boundary helper for React components
323
+ export interface ErrorInfo {
324
+ error: Error
325
+ errorInfo: { componentStack: string }
326
+ }
327
+
328
+ export function logClientError(error: Error, errorInfo?: { componentStack: string }): void {
329
+ console.error('Client error:', {
330
+ message: error.message,
331
+ stack: error.stack,
332
+ componentStack: errorInfo?.componentStack,
333
+ timestamp: new Date().toISOString(),
334
+ userAgent: navigator.userAgent,
335
+ url: window.location.href
336
+ })
337
+
338
+ // In a real application, you might want to send this to an error tracking service
339
+ // Example: Sentry, LogRocket, etc.
340
+ }