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.
- package/.env.example +29 -29
- package/app/client/README.md +69 -69
- package/app/client/index.html +14 -13
- package/app/client/src/App.tsx +157 -524
- package/app/client/src/components/ErrorBoundary.tsx +107 -0
- package/app/client/src/components/ErrorDisplay.css +365 -0
- package/app/client/src/components/ErrorDisplay.tsx +258 -0
- package/app/client/src/components/FluxStackConfig.tsx +1321 -0
- package/app/client/src/components/HybridLiveCounter.tsx +140 -0
- package/app/client/src/components/LiveClock.tsx +286 -0
- package/app/client/src/components/MainLayout.tsx +390 -0
- package/app/client/src/components/SidebarNavigation.tsx +391 -0
- package/app/client/src/components/StateDemo.tsx +178 -0
- package/app/client/src/components/SystemMonitor.tsx +1038 -0
- package/app/client/src/components/Teste.tsx +104 -0
- package/app/client/src/components/UserProfile.tsx +809 -0
- package/app/client/src/hooks/useAuth.ts +39 -0
- package/app/client/src/hooks/useNotifications.ts +56 -0
- package/app/client/src/lib/eden-api.ts +189 -53
- package/app/client/src/lib/errors.ts +340 -0
- package/app/client/src/lib/hooks/useErrorHandler.ts +258 -0
- package/app/client/src/lib/index.ts +45 -0
- package/app/client/src/main.tsx +3 -2
- package/app/client/src/pages/ApiDocs.tsx +182 -0
- package/app/client/src/pages/Demo.tsx +174 -0
- package/app/client/src/pages/HybridLive.tsx +263 -0
- package/app/client/src/pages/Overview.tsx +155 -0
- package/app/client/src/store/README.md +43 -0
- package/app/client/src/store/index.ts +16 -0
- package/app/client/src/store/slices/uiSlice.ts +151 -0
- package/app/client/src/store/slices/userSlice.ts +161 -0
- package/app/client/src/test/README.md +257 -0
- package/app/client/src/test/setup.ts +70 -0
- package/app/client/src/test/types.ts +12 -0
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +44 -43
- package/app/client/tsconfig.json +7 -7
- package/app/client/tsconfig.node.json +25 -25
- package/app/client/zustand-setup.md +65 -0
- package/app/server/controllers/users.controller.ts +68 -68
- package/app/server/index.ts +9 -1
- package/app/server/live/CounterComponent.ts +191 -0
- package/app/server/live/FluxStackConfig.ts +529 -0
- package/app/server/live/LiveClockComponent.ts +214 -0
- package/app/server/live/SidebarNavigation.ts +156 -0
- package/app/server/live/SystemMonitor.ts +594 -0
- package/app/server/live/SystemMonitorIntegration.ts +151 -0
- package/app/server/live/TesteComponent.ts +87 -0
- package/app/server/live/UserProfileComponent.ts +135 -0
- package/app/server/live/register-components.ts +28 -0
- package/app/server/middleware/auth.ts +136 -0
- package/app/server/middleware/errorHandling.ts +250 -0
- package/app/server/middleware/index.ts +10 -0
- package/app/server/middleware/rateLimit.ts +193 -0
- package/app/server/middleware/requestLogging.ts +215 -0
- package/app/server/middleware/validation.ts +270 -0
- package/app/server/routes/index.ts +14 -2
- package/app/server/routes/upload.ts +92 -0
- package/app/server/routes/users.routes.ts +2 -9
- package/app/server/services/NotificationService.ts +302 -0
- package/app/server/services/UserService.ts +222 -0
- package/app/server/services/index.ts +46 -0
- package/core/cli/commands/plugin-deps.ts +263 -0
- package/core/cli/generators/README.md +339 -0
- package/core/cli/generators/component.ts +770 -0
- package/core/cli/generators/controller.ts +299 -0
- package/core/cli/generators/index.ts +144 -0
- package/core/cli/generators/interactive.ts +228 -0
- package/core/cli/generators/prompts.ts +83 -0
- package/core/cli/generators/route.ts +513 -0
- package/core/cli/generators/service.ts +465 -0
- package/core/cli/generators/template-engine.ts +154 -0
- package/core/cli/generators/types.ts +71 -0
- package/core/cli/generators/utils.ts +192 -0
- package/core/cli/index.ts +69 -0
- package/core/cli/plugin-discovery.ts +16 -85
- package/core/client/fluxstack.ts +17 -0
- package/core/client/hooks/index.ts +7 -0
- package/core/client/hooks/state-validator.ts +130 -0
- package/core/client/hooks/useAuth.ts +49 -0
- package/core/client/hooks/useChunkedUpload.ts +258 -0
- package/core/client/hooks/useHybridLiveComponent.ts +967 -0
- package/core/client/hooks/useWebSocket.ts +373 -0
- package/core/client/index.ts +47 -0
- package/core/client/state/createStore.ts +193 -0
- package/core/client/state/index.ts +15 -0
- package/core/config/env-dynamic.ts +1 -1
- package/core/config/env.ts +2 -1
- package/core/config/runtime-config.ts +3 -3
- package/core/config/schema.ts +84 -49
- package/core/framework/server.ts +30 -0
- package/core/index.ts +25 -0
- package/core/live/ComponentRegistry.ts +399 -0
- package/core/live/types.ts +164 -0
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
- package/core/plugins/built-in/live-components/index.ts +27 -0
- package/core/plugins/built-in/logger/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +1 -1
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +1 -1
- package/core/plugins/built-in/vite/index.ts +1 -1
- package/core/plugins/dependency-manager.ts +384 -0
- package/core/plugins/index.ts +5 -1
- package/core/plugins/manager.ts +7 -3
- package/core/plugins/registry.ts +88 -10
- package/core/plugins/types.ts +11 -11
- package/core/server/framework.ts +43 -0
- package/core/server/index.ts +11 -1
- package/core/server/live/ComponentRegistry.ts +1017 -0
- package/core/server/live/FileUploadManager.ts +272 -0
- package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
- package/core/server/live/SingleConnectionManager.ts +0 -0
- package/core/server/live/StateSignature.ts +644 -0
- package/core/server/live/WebSocketConnectionManager.ts +688 -0
- package/core/server/live/websocket-plugin.ts +435 -0
- package/core/server/middleware/errorHandling.ts +141 -0
- package/core/server/middleware/index.ts +16 -0
- package/core/server/plugins/static-files-plugin.ts +232 -0
- package/core/server/services/BaseService.ts +95 -0
- package/core/server/services/ServiceContainer.ts +144 -0
- package/core/server/services/index.ts +9 -0
- package/core/templates/create-project.ts +196 -33
- package/core/testing/index.ts +10 -0
- package/core/testing/setup.ts +74 -0
- package/core/types/build.ts +38 -14
- package/core/types/types.ts +319 -0
- package/core/utils/env-runtime.ts +7 -0
- package/core/utils/errors/handlers.ts +264 -39
- package/core/utils/errors/index.ts +528 -18
- package/core/utils/errors/middleware.ts +114 -0
- package/core/utils/logger/formatters.ts +222 -0
- package/core/utils/logger/index.ts +167 -48
- package/core/utils/logger/middleware.ts +253 -0
- package/core/utils/logger/performance.ts +384 -0
- package/core/utils/logger/transports.ts +365 -0
- package/create-fluxstack.ts +296 -296
- package/fluxstack.config.ts +17 -1
- package/package-template.json +66 -66
- package/package.json +31 -6
- package/public/README.md +16 -0
- package/vite.config.ts +29 -14
- package/.claude/settings.local.json +0 -74
- package/.github/workflows/ci-build-tests.yml +0 -480
- package/.github/workflows/dependency-management.yml +0 -324
- package/.github/workflows/release-validation.yml +0 -355
- package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
- package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
- package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
- package/CLAUDE.md +0 -200
- package/Dockerfile +0 -58
- package/Dockerfile.backend +0 -52
- package/Dockerfile.frontend +0 -54
- package/README-Docker.md +0 -85
- package/ai-context/00-QUICK-START.md +0 -86
- package/ai-context/README.md +0 -88
- package/ai-context/development/eden-treaty-guide.md +0 -362
- package/ai-context/development/patterns.md +0 -382
- package/ai-context/development/plugins-guide.md +0 -572
- package/ai-context/examples/crud-complete.md +0 -626
- package/ai-context/project/architecture.md +0 -399
- package/ai-context/project/overview.md +0 -213
- package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
- package/ai-context/recent-changes/type-inference-fix.md +0 -223
- package/ai-context/reference/environment-vars.md +0 -384
- package/ai-context/reference/troubleshooting.md +0 -407
- package/app/client/src/components/TestPage.tsx +0 -453
- package/bun.lock +0 -1063
- package/bunfig.toml +0 -16
- package/core/__tests__/integration.test.ts +0 -227
- package/core/build/index.ts +0 -186
- package/core/config/__tests__/config-loader.test.ts +0 -554
- package/core/config/__tests__/config-merger.test.ts +0 -657
- package/core/config/__tests__/env-converter.test.ts +0 -372
- package/core/config/__tests__/env-processor.test.ts +0 -431
- package/core/config/__tests__/env.test.ts +0 -452
- package/core/config/__tests__/integration.test.ts +0 -418
- package/core/config/__tests__/loader.test.ts +0 -331
- package/core/config/__tests__/schema.test.ts +0 -129
- package/core/config/__tests__/validator.test.ts +0 -318
- package/core/framework/__tests__/server.test.ts +0 -233
- package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
- package/core/plugins/__tests__/manager.test.ts +0 -398
- package/core/plugins/__tests__/monitoring.test.ts +0 -401
- package/core/plugins/__tests__/registry.test.ts +0 -335
- package/core/utils/__tests__/errors.test.ts +0 -139
- package/core/utils/__tests__/helpers.test.ts +0 -297
- package/core/utils/__tests__/logger.test.ts +0 -141
- package/create-test-app.ts +0 -156
- package/docker-compose.microservices.yml +0 -75
- package/docker-compose.simple.yml +0 -57
- package/docker-compose.yml +0 -71
- package/eslint.config.js +0 -23
- package/flux-cli.ts +0 -214
- package/nginx-lb.conf +0 -37
- package/publish.sh +0 -63
- package/run-clean.ts +0 -26
- package/run-env-tests.ts +0 -313
- package/tailwind.config.js +0 -34
- package/tests/__mocks__/api.ts +0 -56
- package/tests/fixtures/users.ts +0 -69
- package/tests/integration/api/users.routes.test.ts +0 -221
- package/tests/setup.ts +0 -29
- package/tests/unit/app/client/App-simple.test.tsx +0 -56
- package/tests/unit/app/client/App.test.tsx.skip +0 -237
- package/tests/unit/app/client/eden-api.test.ts +0 -186
- package/tests/unit/app/client/simple.test.tsx +0 -23
- package/tests/unit/app/controllers/users.controller.test.ts +0 -150
- package/tests/unit/core/create-project.test.ts.skip +0 -95
- package/tests/unit/core/framework.test.ts +0 -144
- package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
- package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
- package/tests/utils/test-helpers.ts +0 -61
- package/vitest.config.ts +0 -50
- package/workspace.json +0 -6
|
@@ -0,0 +1,258 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Enhanced error handling exports
|
|
2
|
+
export * from './errors'
|
|
3
|
+
export * from './eden-api'
|
|
4
|
+
export * from './hooks/useErrorHandler'
|
|
5
|
+
|
|
6
|
+
// Re-export components
|
|
7
|
+
export { ErrorBoundary, useErrorBoundary } from '../components/ErrorBoundary'
|
|
8
|
+
export {
|
|
9
|
+
ErrorDisplay,
|
|
10
|
+
ErrorToast,
|
|
11
|
+
InlineError,
|
|
12
|
+
LoadingWithError,
|
|
13
|
+
ErrorSummary
|
|
14
|
+
} from '../components/ErrorDisplay'
|
|
15
|
+
|
|
16
|
+
// Convenience exports for common patterns
|
|
17
|
+
export {
|
|
18
|
+
apiCall,
|
|
19
|
+
simpleApiCall,
|
|
20
|
+
criticalApiCall,
|
|
21
|
+
backgroundApiCall,
|
|
22
|
+
userActionApiCall,
|
|
23
|
+
getErrorMessage,
|
|
24
|
+
isRetryableError,
|
|
25
|
+
shouldShowErrorToUser,
|
|
26
|
+
getCircuitBreakerState,
|
|
27
|
+
resetCircuitBreaker
|
|
28
|
+
} from './eden-api'
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
ClientAPIError,
|
|
32
|
+
NetworkError,
|
|
33
|
+
TimeoutError,
|
|
34
|
+
withRetry,
|
|
35
|
+
withFallback,
|
|
36
|
+
CircuitBreaker,
|
|
37
|
+
getDefaultUserMessage,
|
|
38
|
+
logClientError
|
|
39
|
+
} from './errors'
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
useErrorHandler,
|
|
43
|
+
useApiCall,
|
|
44
|
+
useFormSubmission
|
|
45
|
+
} from './hooks/useErrorHandler'
|
package/app/client/src/main.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { StrictMode } from 'react'
|
|
2
2
|
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { BrowserRouter } from 'react-router-dom'
|
|
3
4
|
import './index.css'
|
|
4
5
|
import App from './App.tsx'
|
|
5
6
|
|
|
6
7
|
createRoot(document.getElementById('root')!).render(
|
|
7
|
-
<
|
|
8
|
+
<BrowserRouter>
|
|
8
9
|
<App />
|
|
9
|
-
</
|
|
10
|
+
</BrowserRouter>,
|
|
10
11
|
)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FaBook, FaClipboardList, FaRocket, FaFileAlt, FaCog, FaLock, FaSync, FaCode, FaEye,
|
|
3
|
+
FaBolt
|
|
4
|
+
} from 'react-icons/fa';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export function ApiDocsPage() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="space-y-8">
|
|
10
|
+
{/* Header */}
|
|
11
|
+
<div className="text-center">
|
|
12
|
+
<div className="flex items-center justify-center gap-3 mb-4">
|
|
13
|
+
<FaBook className="text-3xl text-blue-500" />
|
|
14
|
+
<h2 className="text-3xl font-bold text-gray-900">Documentação da API</h2>
|
|
15
|
+
</div>
|
|
16
|
+
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
|
17
|
+
Documentação interativa gerada automaticamente com Swagger UI
|
|
18
|
+
</p>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
{/* Quick Links */}
|
|
22
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
23
|
+
<div className="group bg-white rounded-2xl p-6 shadow-lg border border-gray-200 hover:shadow-xl hover:border-blue-300 transition-all duration-300">
|
|
24
|
+
<div className="text-center">
|
|
25
|
+
<div className="text-4xl mb-4">
|
|
26
|
+
<FaClipboardList className="text-blue-500 mx-auto" />
|
|
27
|
+
</div>
|
|
28
|
+
<h3 className="text-xl font-semibold text-gray-900 mb-2">Swagger UI Interativo</h3>
|
|
29
|
+
<p className="text-gray-600 mb-6">Interface completa para testar todos os endpoints da API</p>
|
|
30
|
+
<a
|
|
31
|
+
href="/swagger"
|
|
32
|
+
target="_blank"
|
|
33
|
+
rel="noopener noreferrer"
|
|
34
|
+
className="inline-flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-medium rounded-xl hover:from-blue-700 hover:to-blue-800 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-all duration-200 shadow-lg hover:shadow-xl"
|
|
35
|
+
>
|
|
36
|
+
<FaRocket className="w-4 h-4" />
|
|
37
|
+
Abrir em Nova Aba
|
|
38
|
+
</a>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div className="group bg-white rounded-2xl p-6 shadow-lg border border-gray-200 hover:shadow-xl hover:border-purple-300 transition-all duration-300">
|
|
43
|
+
<div className="text-center">
|
|
44
|
+
<div className="text-4xl mb-4">
|
|
45
|
+
<FaFileAlt className="text-purple-500 mx-auto" />
|
|
46
|
+
</div>
|
|
47
|
+
<h3 className="text-xl font-semibold text-gray-900 mb-2">OpenAPI Spec (JSON)</h3>
|
|
48
|
+
<p className="text-gray-600 mb-6">Especificação OpenAPI em formato JSON para integração</p>
|
|
49
|
+
<a
|
|
50
|
+
href="/swagger/json"
|
|
51
|
+
target="_blank"
|
|
52
|
+
rel="noopener noreferrer"
|
|
53
|
+
className="inline-flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-purple-600 to-purple-700 text-white font-medium rounded-xl hover:from-purple-700 hover:to-purple-800 focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition-all duration-200 shadow-lg hover:shadow-xl"
|
|
54
|
+
>
|
|
55
|
+
<FaClipboardList className="w-4 h-4" />
|
|
56
|
+
Ver JSON
|
|
57
|
+
</a>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{/* Embedded Swagger */}
|
|
63
|
+
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 overflow-hidden">
|
|
64
|
+
<div className="bg-gradient-to-r from-gray-50 to-slate-100 px-6 py-4 border-b border-gray-200">
|
|
65
|
+
<div className="flex items-center gap-2">
|
|
66
|
+
<FaCog className="text-gray-900" />
|
|
67
|
+
<h3 className="text-lg font-semibold text-gray-900">Documentação Integrada</h3>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
<iframe
|
|
71
|
+
src="/swagger"
|
|
72
|
+
className="w-full h-[600px] border-0"
|
|
73
|
+
title="Swagger UI"
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Eden Treaty Guide */}
|
|
78
|
+
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 overflow-hidden">
|
|
79
|
+
<div className="bg-gradient-to-r from-gray-50 to-slate-100 px-6 py-4 border-b border-gray-200">
|
|
80
|
+
<div className="flex items-center gap-2">
|
|
81
|
+
<FaCog className="text-gray-900" />
|
|
82
|
+
<h3 className="text-lg font-semibold text-gray-900">Como usar Eden Treaty</h3>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<div className="p-6 space-y-6">
|
|
86
|
+
<div>
|
|
87
|
+
<h4 className="text-base font-semibold text-gray-900 mb-3">Configuração do Cliente:</h4>
|
|
88
|
+
<pre className="bg-gray-900 text-gray-100 p-4 rounded-xl overflow-x-auto text-sm border border-gray-700">
|
|
89
|
+
{`import { treaty } from '@elysiajs/eden'
|
|
90
|
+
import type { App } from './server'
|
|
91
|
+
|
|
92
|
+
const client = treaty<App>('http://localhost:3000')
|
|
93
|
+
export const api = client.api`}
|
|
94
|
+
</pre>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div>
|
|
98
|
+
<h4 className="text-base font-semibold text-gray-900 mb-3">Exemplos de Uso:</h4>
|
|
99
|
+
<pre className="bg-gray-900 text-gray-100 p-4 rounded-xl overflow-x-auto text-sm border border-gray-700">
|
|
100
|
+
{`// Listar usuários
|
|
101
|
+
const users = await api.users.get()
|
|
102
|
+
|
|
103
|
+
// Criar usuário
|
|
104
|
+
const newUser = await api.users.post({
|
|
105
|
+
name: "João Silva",
|
|
106
|
+
email: "joao@example.com"
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Deletar usuário
|
|
110
|
+
await api.users["1"].delete()
|
|
111
|
+
|
|
112
|
+
// Health check
|
|
113
|
+
const health = await api.health.get()`}
|
|
114
|
+
</pre>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div>
|
|
118
|
+
<h4 className="text-base font-semibold text-gray-900 mb-3">Com tratamento de erros:</h4>
|
|
119
|
+
<pre className="bg-gray-900 text-gray-100 p-4 rounded-xl overflow-x-auto text-sm border border-gray-700">
|
|
120
|
+
{`try {
|
|
121
|
+
const result = await apiCall(api.users.post({
|
|
122
|
+
name: "Maria Silva",
|
|
123
|
+
email: "maria@example.com"
|
|
124
|
+
}))
|
|
125
|
+
|
|
126
|
+
if (result.success) {
|
|
127
|
+
console.log('Usuário criado:', result.user)
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('Erro:', getErrorMessage(error))
|
|
131
|
+
}`}
|
|
132
|
+
</pre>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{/* Features */}
|
|
138
|
+
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 overflow-hidden">
|
|
139
|
+
<div className="bg-gradient-to-r from-gray-50 to-slate-100 px-6 py-4 border-b border-gray-200">
|
|
140
|
+
<div className="flex items-center justify-center gap-2">
|
|
141
|
+
<FaBolt className="text-gray-900" />
|
|
142
|
+
<h3 className="text-lg font-semibold text-gray-900">Funcionalidades</h3>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
<div className="p-6">
|
|
146
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
147
|
+
{[
|
|
148
|
+
{
|
|
149
|
+
icon: <FaLock className="text-blue-500" />,
|
|
150
|
+
title: "Type Safety",
|
|
151
|
+
description: "Tipos TypeScript inferidos automaticamente"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
icon: <FaBolt className="text-yellow-500" />,
|
|
155
|
+
title: "Auto-complete",
|
|
156
|
+
description: "IntelliSense completo no editor"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
icon: <FaSync className="text-green-500" />,
|
|
160
|
+
title: "Sincronização",
|
|
161
|
+
description: "Mudanças no backend refletem automaticamente no frontend"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
icon: <FaCode className="text-purple-500" />,
|
|
165
|
+
title: "Debugging",
|
|
166
|
+
description: "Erros de tipo detectados em tempo de compilação"
|
|
167
|
+
}
|
|
168
|
+
].map((feature, index) => (
|
|
169
|
+
<div key={index} className="flex items-start gap-4 p-4 rounded-xl bg-gray-50 hover:bg-gray-100 transition-colors duration-200">
|
|
170
|
+
<div className="text-2xl">{feature.icon}</div>
|
|
171
|
+
<div>
|
|
172
|
+
<h4 className="font-semibold text-gray-900 mb-1">{feature.title}</h4>
|
|
173
|
+
<p className="text-gray-600 text-sm">{feature.description}</p>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
))}
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
FaFire, FaRocket, FaCheckCircle, FaTimesCircle, FaSpinner, FaSync, FaUsers, FaPlus, FaTrash
|
|
4
|
+
} from 'react-icons/fa';
|
|
5
|
+
|
|
6
|
+
// This component will receive a lot of props from App.tsx initially.
|
|
7
|
+
// A good next step would be to move this state to a shared context.
|
|
8
|
+
export function DemoPage({
|
|
9
|
+
users,
|
|
10
|
+
apiStatus,
|
|
11
|
+
loading,
|
|
12
|
+
submitting,
|
|
13
|
+
name,
|
|
14
|
+
email,
|
|
15
|
+
setName,
|
|
16
|
+
setEmail,
|
|
17
|
+
handleSubmit,
|
|
18
|
+
handleDelete,
|
|
19
|
+
loadUsers,
|
|
20
|
+
getInitials
|
|
21
|
+
}) {
|
|
22
|
+
return (
|
|
23
|
+
<div className="space-y-8">
|
|
24
|
+
{/* Header */}
|
|
25
|
+
<div className="text-center">
|
|
26
|
+
<div className="flex items-center justify-center gap-3 mb-4">
|
|
27
|
+
<FaFire className="text-3xl text-orange-500" />
|
|
28
|
+
<h2 className="text-3xl font-bold text-gray-900">Demo Interativo</h2>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="flex items-center justify-center gap-2">
|
|
31
|
+
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
|
32
|
+
Teste a API em tempo real com hot reload coordenado e Eden Treaty
|
|
33
|
+
</p>
|
|
34
|
+
<FaRocket className="text-lg text-blue-500" />
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
{/* Stats Cards */}
|
|
39
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
40
|
+
<div className="bg-gradient-to-br from-blue-50 to-blue-100 border border-blue-200 rounded-2xl p-6 text-center">
|
|
41
|
+
<div className="text-4xl font-bold text-blue-600 mb-2">{users.length}</div>
|
|
42
|
+
<div className="text-sm font-medium text-blue-700 uppercase tracking-wide">Usuários</div>
|
|
43
|
+
</div>
|
|
44
|
+
<div className={`bg-gradient-to-br ${apiStatus === 'online' ? 'from-emerald-50 to-emerald-100 border-emerald-200' : 'from-red-50 to-red-100 border-red-200'} border rounded-2xl p-6 text-center`}>
|
|
45
|
+
<div className="text-4xl mb-2">
|
|
46
|
+
{apiStatus === 'online' ? (
|
|
47
|
+
<FaCheckCircle className="text-emerald-500 mx-auto" />
|
|
48
|
+
) : (
|
|
49
|
+
<FaTimesCircle className="text-red-500 mx-auto" />
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
<div className={`text-sm font-medium uppercase tracking-wide ${apiStatus === 'online' ? 'text-emerald-700' : 'text-red-700'}`}>
|
|
53
|
+
API {apiStatus === 'online' ? 'Online' : 'Offline'}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="bg-gradient-to-br from-purple-50 to-purple-100 border border-purple-200 rounded-2xl p-6 text-center">
|
|
57
|
+
<div className="text-4xl mb-2">
|
|
58
|
+
<FaRocket className="text-purple-500 mx-auto" />
|
|
59
|
+
</div>
|
|
60
|
+
<div className="text-sm font-medium text-purple-700 uppercase tracking-wide">Eden Treaty</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
{/* Add User Form */}
|
|
65
|
+
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 overflow-hidden">
|
|
66
|
+
<div className="bg-gradient-to-r from-gray-50 to-slate-100 px-6 py-4 border-b border-gray-200">
|
|
67
|
+
<h3 className="text-lg font-semibold text-gray-900">Adicionar Usuário</h3>
|
|
68
|
+
</div>
|
|
69
|
+
<div className="p-6">
|
|
70
|
+
<form onSubmit={handleSubmit} className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6">
|
|
71
|
+
<div className="space-y-2">
|
|
72
|
+
<label className="block text-sm font-medium text-gray-700">Nome</label>
|
|
73
|
+
<input
|
|
74
|
+
type="text"
|
|
75
|
+
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 placeholder-gray-400"
|
|
76
|
+
value={name}
|
|
77
|
+
onChange={(e) => setName(e.target.value)}
|
|
78
|
+
placeholder="Nome completo"
|
|
79
|
+
required
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
<div className="space-y-2">
|
|
83
|
+
<label className="block text-sm font-medium text-gray-700">Email</label>
|
|
84
|
+
<input
|
|
85
|
+
type="email"
|
|
86
|
+
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 placeholder-gray-400"
|
|
87
|
+
value={email}
|
|
88
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
89
|
+
placeholder="email@exemplo.com"
|
|
90
|
+
required
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="md:col-span-2">
|
|
94
|
+
<button
|
|
95
|
+
type="submit"
|
|
96
|
+
className="w-full md:w-auto px-8 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white font-medium rounded-xl hover:from-blue-700 hover:to-blue-800 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-lg hover:shadow-xl"
|
|
97
|
+
disabled={submitting || !name.trim() || !email.trim()}
|
|
98
|
+
>
|
|
99
|
+
{submitting ? (
|
|
100
|
+
<span className="flex items-center gap-2">
|
|
101
|
+
<FaSpinner className="w-4 h-4 animate-spin" />
|
|
102
|
+
Adicionando...
|
|
103
|
+
</span>
|
|
104
|
+
) : (
|
|
105
|
+
<span className="flex items-center gap-2">
|
|
106
|
+
<FaPlus className="w-4 h-4" />
|
|
107
|
+
Adicionar Usuário
|
|
108
|
+
</span>
|
|
109
|
+
)}
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
</form>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
{/* Users List */}
|
|
117
|
+
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 overflow-hidden">
|
|
118
|
+
<div className="bg-gradient-to-r from-gray-50 to-slate-100 px-6 py-4 border-b border-gray-200 flex justify-between items-center">
|
|
119
|
+
<h3 className="text-lg font-semibold text-gray-900">Usuários ({users.length})</h3>
|
|
120
|
+
<button
|
|
121
|
+
className="flex items-center gap-2 px-4 py-2 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 transition-all duration-200"
|
|
122
|
+
onClick={loadUsers}
|
|
123
|
+
disabled={loading}
|
|
124
|
+
>
|
|
125
|
+
{loading ? (
|
|
126
|
+
<FaSpinner className="w-4 h-4 animate-spin" />
|
|
127
|
+
) : (
|
|
128
|
+
<FaSync className="w-4 h-4" />
|
|
129
|
+
)}
|
|
130
|
+
Atualizar
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div className="p-6">
|
|
135
|
+
{loading ? (
|
|
136
|
+
<div className="text-center py-12">
|
|
137
|
+
<FaSpinner className="w-8 h-8 text-blue-600 animate-spin mx-auto mb-4" />
|
|
138
|
+
<p className="text-gray-600">Carregando usuários...</p>
|
|
139
|
+
</div>
|
|
140
|
+
) : users.length === 0 ? (
|
|
141
|
+
<div className="text-center py-12">
|
|
142
|
+
<FaUsers className="text-6xl text-gray-400 mx-auto mb-4" />
|
|
143
|
+
<h4 className="text-lg font-medium text-gray-900 mb-2">Nenhum usuário encontrado</h4>
|
|
144
|
+
<p className="text-gray-600">Adicione o primeiro usuário usando o formulário acima</p>
|
|
145
|
+
</div>
|
|
146
|
+
) : (
|
|
147
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
148
|
+
{users.map(user => (
|
|
149
|
+
<div key={user.id} className="group bg-gray-50 border border-gray-200 rounded-xl p-4 hover:shadow-lg hover:border-blue-300 transition-all duration-200">
|
|
150
|
+
<div className="flex items-start gap-3">
|
|
151
|
+
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
|
|
152
|
+
{getInitials(user.name)}
|
|
153
|
+
</div>
|
|
154
|
+
<div className="flex-1 min-w-0">
|
|
155
|
+
<h4 className="font-medium text-gray-900 truncate">{user.name}</h4>
|
|
156
|
+
<p className="text-sm text-gray-600 truncate">{user.email}</p>
|
|
157
|
+
<button
|
|
158
|
+
className="mt-3 w-full px-3 py-2 bg-red-50 text-red-700 border border-red-200 rounded-lg hover:bg-red-100 hover:border-red-300 focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition-all duration-200 text-sm font-medium flex items-center justify-center gap-2"
|
|
159
|
+
onClick={() => handleDelete(user.id, user.name)}
|
|
160
|
+
>
|
|
161
|
+
<FaTrash className="w-3 h-3" />
|
|
162
|
+
Remover
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
))}
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
}
|