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,319 @@
|
|
|
1
|
+
// 🔥 FluxStack Live Components - Shared Types
|
|
2
|
+
|
|
3
|
+
export interface LiveMessage {
|
|
4
|
+
type: 'COMPONENT_MOUNT' | 'COMPONENT_UNMOUNT' | 'COMPONENT_REHYDRATE' | 'COMPONENT_ACTION' | 'CALL_ACTION' | 'ACTION_RESPONSE' | 'PROPERTY_UPDATE' | 'STATE_UPDATE' | 'STATE_REHYDRATED' | 'ERROR' | 'BROADCAST'
|
|
5
|
+
componentId: string
|
|
6
|
+
action?: string
|
|
7
|
+
property?: string
|
|
8
|
+
payload?: any
|
|
9
|
+
timestamp?: number
|
|
10
|
+
userId?: string
|
|
11
|
+
room?: string
|
|
12
|
+
// Request-Response system
|
|
13
|
+
requestId?: string
|
|
14
|
+
responseId?: string
|
|
15
|
+
expectResponse?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ComponentState {
|
|
19
|
+
[key: string]: any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LiveComponentInstance<TState = ComponentState, TActions = Record<string, Function>> {
|
|
23
|
+
id: string
|
|
24
|
+
state: TState
|
|
25
|
+
call: <T extends keyof TActions>(action: T, ...args: any[]) => Promise<any>
|
|
26
|
+
set: <K extends keyof TState>(property: K, value: TState[K]) => void
|
|
27
|
+
loading: boolean
|
|
28
|
+
errors: Record<string, string>
|
|
29
|
+
connected: boolean
|
|
30
|
+
room?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface WebSocketData {
|
|
34
|
+
components: Map<string, any>
|
|
35
|
+
userId?: string
|
|
36
|
+
subscriptions: Set<string>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ComponentDefinition<TState = ComponentState> {
|
|
40
|
+
name: string
|
|
41
|
+
initialState: TState
|
|
42
|
+
component: new (initialState: TState, ws: any) => LiveComponent<TState>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BroadcastMessage {
|
|
46
|
+
type: string
|
|
47
|
+
payload: any
|
|
48
|
+
room?: string
|
|
49
|
+
excludeUser?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// WebSocket Types for Client
|
|
53
|
+
export interface WebSocketMessage {
|
|
54
|
+
type: string
|
|
55
|
+
componentId?: string
|
|
56
|
+
action?: string
|
|
57
|
+
payload?: any
|
|
58
|
+
timestamp?: number
|
|
59
|
+
userId?: string
|
|
60
|
+
room?: string
|
|
61
|
+
// Request-Response system
|
|
62
|
+
requestId?: string
|
|
63
|
+
responseId?: string
|
|
64
|
+
expectResponse?: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface WebSocketResponse {
|
|
68
|
+
type: 'MESSAGE_RESPONSE' | 'CONNECTION_ESTABLISHED' | 'ERROR' | 'BROADCAST' | 'ACTION_RESPONSE' | 'COMPONENT_MOUNTED' | 'COMPONENT_REHYDRATED' | 'STATE_UPDATE' | 'STATE_REHYDRATED' | 'FILE_UPLOAD_PROGRESS' | 'FILE_UPLOAD_COMPLETE' | 'FILE_UPLOAD_ERROR' | 'FILE_UPLOAD_START_RESPONSE'
|
|
69
|
+
originalType?: string
|
|
70
|
+
componentId?: string
|
|
71
|
+
success?: boolean
|
|
72
|
+
result?: any
|
|
73
|
+
// Request-Response system
|
|
74
|
+
requestId?: string
|
|
75
|
+
responseId?: string
|
|
76
|
+
error?: string
|
|
77
|
+
timestamp?: number
|
|
78
|
+
connectionId?: string
|
|
79
|
+
payload?: any
|
|
80
|
+
// File upload specific fields
|
|
81
|
+
uploadId?: string
|
|
82
|
+
chunkIndex?: number
|
|
83
|
+
totalChunks?: number
|
|
84
|
+
bytesUploaded?: number
|
|
85
|
+
totalBytes?: number
|
|
86
|
+
progress?: number
|
|
87
|
+
filename?: string
|
|
88
|
+
fileUrl?: string
|
|
89
|
+
// Re-hydration specific fields
|
|
90
|
+
signedState?: any
|
|
91
|
+
oldComponentId?: string
|
|
92
|
+
newComponentId?: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Hybrid Live Component Types
|
|
96
|
+
export interface HybridState<T> {
|
|
97
|
+
data: T
|
|
98
|
+
validation: StateValidation
|
|
99
|
+
conflicts: StateConflict[]
|
|
100
|
+
status: 'synced' | 'conflict' | 'disconnected'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface StateValidation {
|
|
104
|
+
checksum: string
|
|
105
|
+
version: number
|
|
106
|
+
source: 'client' | 'server' | 'mount'
|
|
107
|
+
timestamp: number
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface StateConflict {
|
|
111
|
+
property: string
|
|
112
|
+
clientValue: any
|
|
113
|
+
serverValue: any
|
|
114
|
+
timestamp: number
|
|
115
|
+
resolved: boolean
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface HybridComponentOptions {
|
|
119
|
+
fallbackToLocal?: boolean
|
|
120
|
+
room?: string
|
|
121
|
+
userId?: string
|
|
122
|
+
autoMount?: boolean
|
|
123
|
+
debug?: boolean
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export abstract class LiveComponent<TState = ComponentState> {
|
|
127
|
+
public readonly id: string
|
|
128
|
+
public state: TState
|
|
129
|
+
protected ws: any
|
|
130
|
+
public room?: string
|
|
131
|
+
public userId?: string
|
|
132
|
+
public broadcastToRoom: (message: BroadcastMessage) => void = () => {} // Will be injected by registry
|
|
133
|
+
|
|
134
|
+
constructor(initialState: TState, ws: any, options?: { room?: string; userId?: string }) {
|
|
135
|
+
this.id = this.generateId()
|
|
136
|
+
this.state = initialState
|
|
137
|
+
this.ws = ws
|
|
138
|
+
this.room = options?.room
|
|
139
|
+
this.userId = options?.userId
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// State management
|
|
143
|
+
public setState(updates: Partial<TState> | ((prev: TState) => Partial<TState>)) {
|
|
144
|
+
const newUpdates = typeof updates === 'function' ? updates(this.state) : updates
|
|
145
|
+
this.state = { ...this.state, ...newUpdates }
|
|
146
|
+
this.emit('STATE_UPDATE', { state: this.state })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Execute action safely
|
|
150
|
+
public async executeAction(action: string, payload: any): Promise<any> {
|
|
151
|
+
try {
|
|
152
|
+
// Check if method exists
|
|
153
|
+
const method = (this as any)[action]
|
|
154
|
+
if (typeof method !== 'function') {
|
|
155
|
+
throw new Error(`Action '${action}' not found on component`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Execute method
|
|
159
|
+
const result = await method.call(this, payload)
|
|
160
|
+
return result
|
|
161
|
+
} catch (error: any) {
|
|
162
|
+
this.emit('ERROR', {
|
|
163
|
+
action,
|
|
164
|
+
error: error.message,
|
|
165
|
+
stack: error.stack
|
|
166
|
+
})
|
|
167
|
+
throw error
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Send message to client
|
|
172
|
+
protected emit(type: string, payload: any) {
|
|
173
|
+
const message: LiveMessage = {
|
|
174
|
+
type: type as any,
|
|
175
|
+
componentId: this.id,
|
|
176
|
+
payload,
|
|
177
|
+
timestamp: Date.now(),
|
|
178
|
+
userId: this.userId,
|
|
179
|
+
room: this.room
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this.ws && this.ws.send) {
|
|
183
|
+
this.ws.send(JSON.stringify(message))
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Broadcast to all clients in room
|
|
188
|
+
protected broadcast(type: string, payload: any, excludeCurrentUser = false) {
|
|
189
|
+
const message: BroadcastMessage = {
|
|
190
|
+
type,
|
|
191
|
+
payload,
|
|
192
|
+
room: this.room,
|
|
193
|
+
excludeUser: excludeCurrentUser ? this.userId : undefined
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// This will be handled by the registry
|
|
197
|
+
this.broadcastToRoom(message)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Subscribe to room for multi-user features
|
|
201
|
+
protected async subscribeToRoom(roomId: string) {
|
|
202
|
+
this.room = roomId
|
|
203
|
+
// Registry will handle the actual subscription
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Unsubscribe from room
|
|
207
|
+
protected async unsubscribeFromRoom() {
|
|
208
|
+
this.room = undefined
|
|
209
|
+
// Registry will handle the actual unsubscription
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Generate unique ID
|
|
213
|
+
private generateId(): string {
|
|
214
|
+
return `live-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Cleanup when component is destroyed
|
|
218
|
+
public destroy() {
|
|
219
|
+
this.unsubscribeFromRoom()
|
|
220
|
+
// Override in subclasses for custom cleanup
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Get serializable state for client
|
|
224
|
+
public getSerializableState(): TState {
|
|
225
|
+
return this.state
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Utility types for better TypeScript experience
|
|
230
|
+
export type ComponentActions<T> = {
|
|
231
|
+
[K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export type ComponentProps<T extends LiveComponent> = T extends LiveComponent<infer TState> ? TState : never
|
|
235
|
+
|
|
236
|
+
export type ActionParameters<T, K extends keyof T> = T[K] extends (...args: infer P) => any ? P : never
|
|
237
|
+
|
|
238
|
+
export type ActionReturnType<T, K extends keyof T> = T[K] extends (...args: any[]) => infer R ? R : never
|
|
239
|
+
|
|
240
|
+
// File Upload Types for Chunked WebSocket Upload
|
|
241
|
+
export interface FileChunkData {
|
|
242
|
+
uploadId: string
|
|
243
|
+
filename: string
|
|
244
|
+
fileType: string
|
|
245
|
+
fileSize: number
|
|
246
|
+
chunkIndex: number
|
|
247
|
+
totalChunks: number
|
|
248
|
+
chunkSize: number
|
|
249
|
+
data: string // Base64 encoded chunk data
|
|
250
|
+
hash?: string // Optional chunk hash for verification
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface FileUploadStartMessage {
|
|
254
|
+
type: 'FILE_UPLOAD_START'
|
|
255
|
+
componentId: string
|
|
256
|
+
uploadId: string
|
|
257
|
+
filename: string
|
|
258
|
+
fileType: string
|
|
259
|
+
fileSize: number
|
|
260
|
+
chunkSize?: number // Optional, defaults to 64KB
|
|
261
|
+
requestId?: string
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface FileUploadChunkMessage {
|
|
265
|
+
type: 'FILE_UPLOAD_CHUNK'
|
|
266
|
+
componentId: string
|
|
267
|
+
uploadId: string
|
|
268
|
+
chunkIndex: number
|
|
269
|
+
totalChunks: number
|
|
270
|
+
data: string // Base64 encoded chunk
|
|
271
|
+
hash?: string
|
|
272
|
+
requestId?: string
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface FileUploadCompleteMessage {
|
|
276
|
+
type: 'FILE_UPLOAD_COMPLETE'
|
|
277
|
+
componentId: string
|
|
278
|
+
uploadId: string
|
|
279
|
+
requestId?: string
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface FileUploadProgressResponse {
|
|
283
|
+
type: 'FILE_UPLOAD_PROGRESS'
|
|
284
|
+
componentId: string
|
|
285
|
+
uploadId: string
|
|
286
|
+
chunkIndex: number
|
|
287
|
+
totalChunks: number
|
|
288
|
+
bytesUploaded: number
|
|
289
|
+
totalBytes: number
|
|
290
|
+
progress: number // 0-100
|
|
291
|
+
requestId?: string
|
|
292
|
+
timestamp: number
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface FileUploadCompleteResponse {
|
|
296
|
+
type: 'FILE_UPLOAD_COMPLETE'
|
|
297
|
+
componentId: string
|
|
298
|
+
uploadId: string
|
|
299
|
+
success: boolean
|
|
300
|
+
filename?: string
|
|
301
|
+
fileUrl?: string
|
|
302
|
+
error?: string
|
|
303
|
+
requestId?: string
|
|
304
|
+
timestamp: number
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// File Upload Manager for handling uploads
|
|
308
|
+
export interface ActiveUpload {
|
|
309
|
+
uploadId: string
|
|
310
|
+
componentId: string
|
|
311
|
+
filename: string
|
|
312
|
+
fileType: string
|
|
313
|
+
fileSize: number
|
|
314
|
+
totalChunks: number
|
|
315
|
+
receivedChunks: Map<number, string>
|
|
316
|
+
startTime: number
|
|
317
|
+
lastChunkTime: number
|
|
318
|
+
tempFilePath?: string
|
|
319
|
+
}
|
|
@@ -216,6 +216,13 @@ export const validate = {
|
|
|
216
216
|
if (value && !validValues.includes(value)) {
|
|
217
217
|
throw new Error(`${key} must be one of: ${validValues.join(', ')}, got: ${value}`)
|
|
218
218
|
}
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
validate(key: string, validator: (value: string) => boolean, errorMessage: string): void {
|
|
222
|
+
const value = smartEnv.get(key, '')
|
|
223
|
+
if (value && !validator(value)) {
|
|
224
|
+
throw new Error(`${key}: ${errorMessage}`)
|
|
225
|
+
}
|
|
219
226
|
}
|
|
220
227
|
}
|
|
221
228
|
|
|
@@ -1,63 +1,288 @@
|
|
|
1
|
-
import { FluxStackError } from "./index"
|
|
1
|
+
import { FluxStackError, wrapError, type ErrorMetadata, type ErrorSerializedResponse } from "./index"
|
|
2
2
|
import type { Logger } from "../logger/index"
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
3
4
|
|
|
4
5
|
export interface ErrorHandlerContext {
|
|
5
6
|
logger: Logger
|
|
6
7
|
isDevelopment: boolean
|
|
7
8
|
request?: Request
|
|
8
9
|
path?: string
|
|
10
|
+
method?: string
|
|
11
|
+
correlationId?: string
|
|
12
|
+
userId?: string
|
|
13
|
+
userAgent?: string
|
|
14
|
+
ip?: string
|
|
15
|
+
metricsCollector?: ErrorMetricsCollector
|
|
9
16
|
}
|
|
10
17
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
export interface ErrorMetricsCollector {
|
|
19
|
+
recordError(error: FluxStackError, context: ErrorHandlerContext): void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ErrorRecoveryStrategy {
|
|
23
|
+
canRecover(error: FluxStackError): boolean
|
|
24
|
+
recover(error: FluxStackError, context: ErrorHandlerContext): Promise<any> | any
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ErrorHandlerOptions {
|
|
28
|
+
enableStackTrace?: boolean
|
|
29
|
+
enableErrorMetrics?: boolean
|
|
30
|
+
enableCorrelationId?: boolean
|
|
31
|
+
sanitizeErrors?: boolean
|
|
32
|
+
recoveryStrategies?: ErrorRecoveryStrategy[]
|
|
33
|
+
customErrorMessages?: Record<string, string>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class EnhancedErrorHandler {
|
|
37
|
+
private options: Required<ErrorHandlerOptions>
|
|
38
|
+
private recoveryStrategies: ErrorRecoveryStrategy[]
|
|
39
|
+
|
|
40
|
+
constructor(options: ErrorHandlerOptions = {}) {
|
|
41
|
+
this.options = {
|
|
42
|
+
enableStackTrace: options.enableStackTrace ?? true,
|
|
43
|
+
enableErrorMetrics: options.enableErrorMetrics ?? true,
|
|
44
|
+
enableCorrelationId: options.enableCorrelationId ?? true,
|
|
45
|
+
sanitizeErrors: options.sanitizeErrors ?? true,
|
|
46
|
+
recoveryStrategies: options.recoveryStrategies ?? [],
|
|
47
|
+
customErrorMessages: options.customErrorMessages ?? {}
|
|
48
|
+
}
|
|
49
|
+
this.recoveryStrategies = this.options.recoveryStrategies
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async handle(error: Error, context: ErrorHandlerContext): Promise<ErrorSerializedResponse> {
|
|
53
|
+
const { logger, isDevelopment, request, path, method, correlationId, userId, userAgent, ip, metricsCollector } = context
|
|
54
|
+
|
|
55
|
+
// Generate correlation ID if not provided and enabled
|
|
56
|
+
const finalCorrelationId = this.options.enableCorrelationId
|
|
57
|
+
? (correlationId || uuidv4())
|
|
58
|
+
: correlationId
|
|
59
|
+
|
|
60
|
+
// Create metadata from context
|
|
61
|
+
const metadata: ErrorMetadata = {
|
|
62
|
+
correlationId: finalCorrelationId,
|
|
63
|
+
userId,
|
|
64
|
+
userAgent,
|
|
65
|
+
ip,
|
|
66
|
+
path,
|
|
67
|
+
method,
|
|
68
|
+
timestamp: new Date().toISOString()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Convert to FluxStackError if needed
|
|
72
|
+
let fluxError: FluxStackError
|
|
73
|
+
if (error instanceof FluxStackError) {
|
|
74
|
+
fluxError = error.withMetadata(metadata)
|
|
75
|
+
} else {
|
|
76
|
+
fluxError = wrapError(error, metadata)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Try recovery strategies for operational errors
|
|
80
|
+
if (fluxError.isOperational && this.recoveryStrategies.length > 0) {
|
|
81
|
+
for (const strategy of this.recoveryStrategies) {
|
|
82
|
+
if (strategy.canRecover(fluxError)) {
|
|
83
|
+
try {
|
|
84
|
+
const recoveryResult = await strategy.recover(fluxError, context)
|
|
85
|
+
logger.info('Error recovery successful', {
|
|
86
|
+
errorCode: fluxError.code,
|
|
87
|
+
correlationId: finalCorrelationId,
|
|
88
|
+
strategy: strategy.constructor.name
|
|
89
|
+
})
|
|
90
|
+
return recoveryResult
|
|
91
|
+
} catch (recoveryError) {
|
|
92
|
+
logger.warn('Error recovery failed', {
|
|
93
|
+
errorCode: fluxError.code,
|
|
94
|
+
correlationId: finalCorrelationId,
|
|
95
|
+
strategy: strategy.constructor.name,
|
|
96
|
+
recoveryError: recoveryError instanceof Error ? recoveryError.message : recoveryError
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Log the error with appropriate level and context
|
|
104
|
+
this.logError(fluxError, logger, isDevelopment)
|
|
105
|
+
|
|
106
|
+
// Record metrics if enabled
|
|
107
|
+
if (this.options.enableErrorMetrics && metricsCollector) {
|
|
108
|
+
try {
|
|
109
|
+
metricsCollector.recordError(fluxError, context)
|
|
110
|
+
} catch (metricsError) {
|
|
111
|
+
logger.warn('Failed to record error metrics', {
|
|
112
|
+
error: metricsError instanceof Error ? metricsError.message : metricsError
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Generate user-friendly response
|
|
118
|
+
return this.generateErrorResponse(fluxError, isDevelopment)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private logError(error: FluxStackError, logger: Logger, isDevelopment: boolean): void {
|
|
122
|
+
const logLevel = this.getLogLevel(error)
|
|
123
|
+
const logData = {
|
|
18
124
|
code: error.code,
|
|
19
125
|
statusCode: error.statusCode,
|
|
20
126
|
context: error.context,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
stack:
|
|
24
|
-
}
|
|
127
|
+
metadata: error.metadata,
|
|
128
|
+
isOperational: error.isOperational,
|
|
129
|
+
...(isDevelopment && { stack: error.stack })
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Skip logging for certain errors to reduce noise
|
|
133
|
+
if (this.shouldSkipLogging(error)) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
logger[logLevel](error.message, logData)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private getLogLevel(error: FluxStackError): 'error' | 'warn' | 'info' {
|
|
141
|
+
if (!error.isOperational) {
|
|
142
|
+
return 'error'
|
|
143
|
+
}
|
|
25
144
|
|
|
26
|
-
|
|
27
|
-
error
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
145
|
+
if (error.statusCode >= 500) {
|
|
146
|
+
return 'error'
|
|
147
|
+
} else if (error.statusCode >= 400) {
|
|
148
|
+
return 'warn'
|
|
149
|
+
} else {
|
|
150
|
+
return 'info'
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private shouldSkipLogging(error: FluxStackError): boolean {
|
|
155
|
+
// Skip logging for 404 errors unless explicitly enabled
|
|
156
|
+
if (error.code === 'NOT_FOUND' && !process.env.ENABLE_NOT_FOUND_LOGS) {
|
|
157
|
+
return true
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Skip logging for rate limit errors to prevent log spam
|
|
161
|
+
if (error.code === 'RATE_LIMIT_EXCEEDED' && !process.env.ENABLE_RATE_LIMIT_LOGS) {
|
|
162
|
+
return true
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return false
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private generateErrorResponse(error: FluxStackError, isDevelopment: boolean): ErrorSerializedResponse {
|
|
169
|
+
const response = error.toResponse(isDevelopment)
|
|
170
|
+
|
|
171
|
+
// Apply custom error messages if configured
|
|
172
|
+
if (this.options.customErrorMessages[error.code]) {
|
|
173
|
+
response.error.message = this.options.customErrorMessages[error.code]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Sanitize sensitive information in production
|
|
177
|
+
if (!isDevelopment && this.options.sanitizeErrors) {
|
|
178
|
+
response.error = this.sanitizeErrorResponse(response.error)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return response
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private sanitizeErrorResponse(errorResponse: any): any {
|
|
185
|
+
const sanitized = { ...errorResponse }
|
|
186
|
+
|
|
187
|
+
// Remove potentially sensitive fields in production
|
|
188
|
+
if (sanitized.details) {
|
|
189
|
+
// Remove sensitive fields from details
|
|
190
|
+
const sensitiveFields = ['password', 'token', 'secret', 'key', 'credential']
|
|
191
|
+
for (const field of sensitiveFields) {
|
|
192
|
+
if (sanitized.details[field]) {
|
|
193
|
+
sanitized.details[field] = '[REDACTED]'
|
|
194
|
+
}
|
|
33
195
|
}
|
|
34
196
|
}
|
|
197
|
+
|
|
198
|
+
return sanitized
|
|
35
199
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// Skip logging NOT_FOUND errors to reduce noise
|
|
40
|
-
} else {
|
|
41
|
-
logger.error('Unhandled error', {
|
|
42
|
-
error: error.message,
|
|
43
|
-
stack: error.stack,
|
|
44
|
-
path,
|
|
45
|
-
method: request?.method
|
|
46
|
-
})
|
|
200
|
+
|
|
201
|
+
addRecoveryStrategy(strategy: ErrorRecoveryStrategy): void {
|
|
202
|
+
this.recoveryStrategies.push(strategy)
|
|
47
203
|
}
|
|
204
|
+
|
|
205
|
+
removeRecoveryStrategy(strategyClass: new (...args: any[]) => ErrorRecoveryStrategy): void {
|
|
206
|
+
this.recoveryStrategies = this.recoveryStrategies.filter(
|
|
207
|
+
strategy => !(strategy instanceof strategyClass)
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Legacy error handler for backward compatibility
|
|
213
|
+
export const errorHandler = (error: Error, context: ErrorHandlerContext) => {
|
|
214
|
+
const handler = new EnhancedErrorHandler()
|
|
215
|
+
return handler.handle(error, context)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export const createErrorHandler = (
|
|
219
|
+
baseContext: Omit<ErrorHandlerContext, 'request' | 'path' | 'method'>,
|
|
220
|
+
options?: ErrorHandlerOptions
|
|
221
|
+
) => {
|
|
222
|
+
const handler = new EnhancedErrorHandler(options)
|
|
48
223
|
|
|
49
|
-
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
224
|
+
return async (error: Error, request?: Request, path?: string, method?: string) => {
|
|
225
|
+
const context: ErrorHandlerContext = {
|
|
226
|
+
...baseContext,
|
|
227
|
+
request,
|
|
228
|
+
path,
|
|
229
|
+
method: method || request?.method
|
|
55
230
|
}
|
|
231
|
+
|
|
232
|
+
return handler.handle(error, context)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Built-in recovery strategies
|
|
237
|
+
export class RetryRecoveryStrategy implements ErrorRecoveryStrategy {
|
|
238
|
+
constructor(
|
|
239
|
+
private maxRetries: number = 3,
|
|
240
|
+
private retryDelay: number = 1000,
|
|
241
|
+
private retryableCodes: string[] = ['EXTERNAL_SERVICE_ERROR', 'DATABASE_ERROR']
|
|
242
|
+
) {}
|
|
243
|
+
|
|
244
|
+
canRecover(error: FluxStackError): boolean {
|
|
245
|
+
return this.retryableCodes.includes(error.code) &&
|
|
246
|
+
(!error.context?.retryCount || error.context.retryCount < this.maxRetries)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async recover(error: FluxStackError, context: ErrorHandlerContext): Promise<any> {
|
|
250
|
+
const retryCount = (error.context?.retryCount || 0) + 1
|
|
251
|
+
|
|
252
|
+
context.logger.info('Attempting error recovery', {
|
|
253
|
+
errorCode: error.code,
|
|
254
|
+
retryCount,
|
|
255
|
+
maxRetries: this.maxRetries
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// Wait before retry
|
|
259
|
+
await new Promise(resolve => setTimeout(resolve, this.retryDelay * retryCount))
|
|
260
|
+
|
|
261
|
+
// This would typically re-execute the original operation
|
|
262
|
+
// For now, we'll just return a recovery response
|
|
263
|
+
throw error.withMetadata({ ...error.metadata, retryCount })
|
|
56
264
|
}
|
|
57
265
|
}
|
|
58
266
|
|
|
59
|
-
export
|
|
60
|
-
|
|
61
|
-
|
|
267
|
+
export class FallbackRecoveryStrategy implements ErrorRecoveryStrategy {
|
|
268
|
+
constructor(
|
|
269
|
+
private fallbackResponse: any,
|
|
270
|
+
private applicableCodes: string[] = ['EXTERNAL_SERVICE_ERROR']
|
|
271
|
+
) {}
|
|
272
|
+
|
|
273
|
+
canRecover(error: FluxStackError): boolean {
|
|
274
|
+
return this.applicableCodes.includes(error.code)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
recover(error: FluxStackError, context: ErrorHandlerContext): any {
|
|
278
|
+
context.logger.info('Using fallback recovery', {
|
|
279
|
+
errorCode: error.code,
|
|
280
|
+
correlationId: error.metadata.correlationId
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
data: this.fallbackResponse,
|
|
285
|
+
warning: 'Fallback data provided due to service unavailability'
|
|
286
|
+
}
|
|
62
287
|
}
|
|
63
288
|
}
|