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,39 +0,0 @@
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
- }
@@ -1,56 +0,0 @@
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
- }
@@ -1,340 +0,0 @@
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
- }
@@ -1,258 +0,0 @@
1
- import React, { useState, useCallback, useRef } from 'react'
2
- import { logClientError } from '../errors'
3
- import { getErrorMessage, isRetryableError, shouldShowErrorToUser } from '../eden-api'
4
-
5
- export interface ErrorState {
6
- error: Error | null
7
- isRetrying: boolean
8
- retryCount: number
9
- canRetry: boolean
10
- userMessage: string | null
11
- }
12
-
13
- export interface UseErrorHandlerOptions {
14
- maxRetries?: number
15
- showUserFriendlyMessages?: boolean
16
- logErrors?: boolean
17
- onError?: (error: Error) => void
18
- onRetry?: (retryCount: number) => void
19
- onMaxRetriesReached?: (error: Error) => void
20
- }
21
-
22
- export function useErrorHandler(options: UseErrorHandlerOptions = {}) {
23
- const {
24
- maxRetries = 3,
25
- showUserFriendlyMessages = true,
26
- logErrors = true,
27
- onError,
28
- onRetry,
29
- onMaxRetriesReached
30
- } = options
31
-
32
- const [errorState, setErrorState] = useState<ErrorState>({
33
- error: null,
34
- isRetrying: false,
35
- retryCount: 0,
36
- canRetry: false,
37
- userMessage: null
38
- })
39
-
40
- const retryTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined)
41
-
42
- const handleError = useCallback((error: Error) => {
43
- if (logErrors) {
44
- logClientError(error, undefined)
45
- }
46
-
47
- setErrorState(prevState => {
48
- const currentRetryCount = prevState?.retryCount || 0
49
- const canRetry = isRetryableError(error) && currentRetryCount < maxRetries
50
- const userMessage = showUserFriendlyMessages && shouldShowErrorToUser(error)
51
- ? getErrorMessage(error)
52
- : null
53
-
54
- return {
55
- error,
56
- isRetrying: false,
57
- retryCount: currentRetryCount,
58
- canRetry,
59
- userMessage
60
- }
61
- })
62
-
63
- onError?.(error)
64
- }, [maxRetries, showUserFriendlyMessages, logErrors, onError])
65
-
66
- const retry = useCallback(async (operation: () => Promise<any>) => {
67
- if (!errorState.canRetry || errorState.isRetrying) {
68
- return
69
- }
70
-
71
- const newRetryCount = (errorState?.retryCount || 0) + 1
72
-
73
- setErrorState(prev => ({
74
- ...prev,
75
- isRetrying: true,
76
- retryCount: newRetryCount
77
- }))
78
-
79
- onRetry?.(newRetryCount)
80
-
81
- try {
82
- // Add exponential backoff delay
83
- const delay = Math.min(1000 * Math.pow(2, newRetryCount - 1), 10000)
84
- await new Promise(resolve => {
85
- retryTimeoutRef.current = setTimeout(resolve, delay)
86
- })
87
-
88
- const result = await operation()
89
-
90
- // Clear error state on successful retry
91
- setErrorState({
92
- error: null,
93
- isRetrying: false,
94
- retryCount: 0,
95
- canRetry: false,
96
- userMessage: null
97
- })
98
-
99
- return result
100
- } catch (error) {
101
- const canRetryAgain = isRetryableError(error as Error) && newRetryCount < maxRetries
102
-
103
- setErrorState(prev => ({
104
- ...prev,
105
- error: error as Error,
106
- isRetrying: false,
107
- retryCount: newRetryCount,
108
- canRetry: canRetryAgain,
109
- userMessage: showUserFriendlyMessages && shouldShowErrorToUser(error)
110
- ? getErrorMessage(error)
111
- : null
112
- }))
113
-
114
- if (!canRetryAgain) {
115
- onMaxRetriesReached?.(error as Error)
116
- }
117
-
118
- throw error
119
- }
120
- }, [errorState?.retryCount, errorState?.canRetry, errorState?.isRetrying, maxRetries, showUserFriendlyMessages, onRetry, onMaxRetriesReached])
121
-
122
- const clearError = useCallback(() => {
123
- if (retryTimeoutRef.current) {
124
- clearTimeout(retryTimeoutRef.current)
125
- }
126
-
127
- setErrorState({
128
- error: null,
129
- isRetrying: false,
130
- retryCount: 0,
131
- canRetry: false,
132
- userMessage: null
133
- })
134
- }, [])
135
-
136
- const executeWithErrorHandling = useCallback(async <T>(
137
- operation: () => Promise<T>
138
- ): Promise<T | null> => {
139
- try {
140
- clearError()
141
- return await operation()
142
- } catch (error) {
143
- handleError(error as Error)
144
- return null
145
- }
146
- }, [handleError, clearError])
147
-
148
- return {
149
- ...errorState,
150
- handleError,
151
- retry,
152
- clearError,
153
- executeWithErrorHandling
154
- }
155
- }
156
-
157
- // Hook for handling API calls with automatic error handling
158
- export function useApiCall<T>(
159
- apiCall: () => Promise<T>,
160
- options: UseErrorHandlerOptions & {
161
- immediate?: boolean
162
- dependencies?: any[]
163
- } = {}
164
- ) {
165
- const { immediate = false, dependencies = [], ...errorOptions } = options
166
-
167
- const [data, setData] = useState<T | null>(null)
168
- const [loading, setLoading] = useState(immediate)
169
-
170
- const errorHandler = useErrorHandler(errorOptions)
171
-
172
- const execute = useCallback(async (): Promise<T | null> => {
173
- setLoading(true)
174
-
175
- try {
176
- const result = await apiCall()
177
- setData(result)
178
- errorHandler.clearError()
179
- return result
180
- } catch (error) {
181
- errorHandler.handleError(error as Error)
182
- return null
183
- } finally {
184
- setLoading(false)
185
- }
186
- }, [apiCall, errorHandler])
187
-
188
- const retryCall = useCallback(async () => {
189
- return errorHandler.retry(async () => {
190
- setLoading(true)
191
- try {
192
- const result = await apiCall()
193
- setData(result)
194
- return result
195
- } finally {
196
- setLoading(false)
197
- }
198
- })
199
- }, [apiCall, errorHandler])
200
-
201
- // Execute immediately if requested
202
- React.useEffect(() => {
203
- if (immediate) {
204
- execute()
205
- }
206
- }, [immediate, ...dependencies])
207
-
208
- const { retry: errorHandlerRetry, ...restErrorHandler } = errorHandler
209
-
210
- return {
211
- data,
212
- loading,
213
- execute,
214
- retry: retryCall,
215
- ...restErrorHandler
216
- }
217
- }
218
-
219
- // Hook for form submission with error handling
220
- export function useFormSubmission<T>(
221
- submitFunction: (data: any) => Promise<T>,
222
- options: UseErrorHandlerOptions = {}
223
- ) {
224
- const [isSubmitting, setIsSubmitting] = useState(false)
225
- const [submitData, setSubmitData] = useState<T | null>(null)
226
-
227
- const errorHandler = useErrorHandler(options)
228
-
229
- const submit = useCallback(async (formData: any): Promise<T | null> => {
230
- setIsSubmitting(true)
231
-
232
- try {
233
- const result = await submitFunction(formData)
234
- setSubmitData(result)
235
- errorHandler.clearError()
236
- return result
237
- } catch (error) {
238
- errorHandler.handleError(error as Error)
239
- return null
240
- } finally {
241
- setIsSubmitting(false)
242
- }
243
- }, [submitFunction, errorHandler])
244
-
245
- const retrySubmit = useCallback(async (formData: any) => {
246
- return errorHandler.retry(() => submit(formData))
247
- }, [submit, errorHandler])
248
-
249
- const { retry: errorHandlerRetry, ...restErrorHandler } = errorHandler
250
-
251
- return {
252
- submit,
253
- retry: retrySubmit,
254
- isSubmitting,
255
- submitData,
256
- ...restErrorHandler
257
- }
258
- }