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,373 @@
|
|
|
1
|
+
// 🔥 WebSocket Hook for Live Components
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback, useRef } from 'react'
|
|
4
|
+
import type { WebSocketMessage, WebSocketResponse } from '../../types/types'
|
|
5
|
+
|
|
6
|
+
// Re-export types for easier importing
|
|
7
|
+
export type { WebSocketMessage, WebSocketResponse }
|
|
8
|
+
|
|
9
|
+
export interface UseWebSocketOptions {
|
|
10
|
+
url?: string
|
|
11
|
+
autoConnect?: boolean
|
|
12
|
+
reconnectInterval?: number
|
|
13
|
+
maxReconnectAttempts?: number
|
|
14
|
+
debug?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UseWebSocketReturn {
|
|
18
|
+
connected: boolean
|
|
19
|
+
connecting: boolean
|
|
20
|
+
error: string | null
|
|
21
|
+
connectionId: string | null
|
|
22
|
+
sendMessage: (message: WebSocketMessage) => Promise<WebSocketResponse | null>
|
|
23
|
+
sendMessageAndWait: (message: WebSocketMessage, timeout?: number) => Promise<any>
|
|
24
|
+
close: () => void
|
|
25
|
+
reconnect: () => void
|
|
26
|
+
messageHistory: WebSocketResponse[]
|
|
27
|
+
lastMessage: WebSocketResponse | null
|
|
28
|
+
onMessage: (callback: (message: WebSocketResponse) => void) => () => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketReturn {
|
|
32
|
+
// Get WebSocket URL dynamically based on current environment
|
|
33
|
+
const getWebSocketUrl = () => {
|
|
34
|
+
if (typeof window === 'undefined') return 'ws://localhost:3000/api/live/ws'
|
|
35
|
+
|
|
36
|
+
const hostname = window.location.hostname
|
|
37
|
+
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1'
|
|
38
|
+
|
|
39
|
+
// In production, use current origin with ws/wss protocol
|
|
40
|
+
if (!isLocalhost) {
|
|
41
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
42
|
+
const url = `${protocol}//${window.location.host}/api/live/ws`
|
|
43
|
+
console.log('🔗 [WebSocket] Production URL:', url)
|
|
44
|
+
return url
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// In development, use backend server (port 3000 for integrated mode)
|
|
48
|
+
const url = 'ws://localhost:3000/api/live/ws'
|
|
49
|
+
console.log('🔗 [WebSocket] Development URL:', url)
|
|
50
|
+
return url
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const {
|
|
54
|
+
url = getWebSocketUrl(),
|
|
55
|
+
autoConnect = true,
|
|
56
|
+
reconnectInterval = 300, // Reduced from 3000ms to 1000ms for faster reconnects
|
|
57
|
+
maxReconnectAttempts = 5,
|
|
58
|
+
debug = false
|
|
59
|
+
} = options
|
|
60
|
+
|
|
61
|
+
const [connected, setConnected] = useState(false)
|
|
62
|
+
const [connecting, setConnecting] = useState(false)
|
|
63
|
+
const [error, setError] = useState<string | null>(null)
|
|
64
|
+
const [connectionId, setConnectionId] = useState<string | null>(null)
|
|
65
|
+
const [messageHistory, setMessageHistory] = useState<WebSocketResponse[]>([])
|
|
66
|
+
const [lastMessage, setLastMessage] = useState<WebSocketResponse | null>(null)
|
|
67
|
+
|
|
68
|
+
// Request-Response system
|
|
69
|
+
const pendingRequests = useRef<Map<string, {
|
|
70
|
+
resolve: (value: any) => void
|
|
71
|
+
reject: (error: any) => void
|
|
72
|
+
timeout: NodeJS.Timeout
|
|
73
|
+
}>>(new Map())
|
|
74
|
+
|
|
75
|
+
// Message callbacks for real-time processing
|
|
76
|
+
const messageCallbacksRef = useRef<Set<(message: WebSocketResponse) => void>>(new Set())
|
|
77
|
+
|
|
78
|
+
// Generate unique request ID
|
|
79
|
+
const generateRequestId = useCallback(() => {
|
|
80
|
+
return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
81
|
+
}, [])
|
|
82
|
+
|
|
83
|
+
const wsRef = useRef<WebSocket | null>(null)
|
|
84
|
+
const reconnectAttempts = useRef(0)
|
|
85
|
+
const reconnectTimeout = useRef<number | null>(null)
|
|
86
|
+
const messageCallbacks = useRef<Map<string, (response: WebSocketResponse) => void>>(new Map())
|
|
87
|
+
|
|
88
|
+
const log = useCallback((message: string, data?: any) => {
|
|
89
|
+
if (debug) {
|
|
90
|
+
console.log(`[useWebSocket] ${message}`, data)
|
|
91
|
+
}
|
|
92
|
+
}, [debug])
|
|
93
|
+
|
|
94
|
+
const connect = useCallback(() => {
|
|
95
|
+
if (wsRef.current?.readyState === WebSocket.CONNECTING) {
|
|
96
|
+
log('WebSocket already connecting, skipping...')
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
101
|
+
log('WebSocket already connected, skipping...')
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setConnecting(true)
|
|
106
|
+
setError(null)
|
|
107
|
+
log('Connecting to WebSocket', { url })
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const ws = new WebSocket(url)
|
|
111
|
+
wsRef.current = ws
|
|
112
|
+
|
|
113
|
+
ws.onopen = () => {
|
|
114
|
+
setConnected(true)
|
|
115
|
+
setConnecting(false)
|
|
116
|
+
reconnectAttempts.current = 0
|
|
117
|
+
log('Connected to WebSocket')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
ws.onmessage = (event) => {
|
|
121
|
+
try {
|
|
122
|
+
const response: WebSocketResponse = JSON.parse(event.data)
|
|
123
|
+
log('Received message', response)
|
|
124
|
+
|
|
125
|
+
// Handle connection establishment
|
|
126
|
+
if (response.type === 'CONNECTION_ESTABLISHED') {
|
|
127
|
+
setConnectionId(response.connectionId || null)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Handle request-response system
|
|
131
|
+
if (response.requestId && pendingRequests.current.has(response.requestId)) {
|
|
132
|
+
const request = pendingRequests.current.get(response.requestId)!
|
|
133
|
+
clearTimeout(request.timeout)
|
|
134
|
+
pendingRequests.current.delete(response.requestId)
|
|
135
|
+
|
|
136
|
+
if (response.success !== false) {
|
|
137
|
+
request.resolve(response) // Pass full response, not just result
|
|
138
|
+
} else {
|
|
139
|
+
// Don't reject COMPONENT_REHYDRATION_REQUIRED - let client handle it
|
|
140
|
+
if (response.error?.includes?.('COMPONENT_REHYDRATION_REQUIRED')) {
|
|
141
|
+
request.resolve(response) // Return response so client can handle re-hydration
|
|
142
|
+
} else {
|
|
143
|
+
request.reject(new Error(response.error || 'Request failed'))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return // Don't process further for request-response
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Handle message callbacks (legacy)
|
|
150
|
+
if (response.type === 'MESSAGE_RESPONSE' && response.componentId) {
|
|
151
|
+
const callback = messageCallbacks.current.get(response.componentId)
|
|
152
|
+
if (callback) {
|
|
153
|
+
callback(response)
|
|
154
|
+
messageCallbacks.current.delete(response.componentId)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Update message history and last message
|
|
159
|
+
setMessageHistory(prev => [...prev.slice(-99), response])
|
|
160
|
+
setLastMessage(response)
|
|
161
|
+
|
|
162
|
+
// Call all registered message callbacks immediately
|
|
163
|
+
messageCallbacksRef.current.forEach(callback => {
|
|
164
|
+
try {
|
|
165
|
+
callback(response)
|
|
166
|
+
} catch (error) {
|
|
167
|
+
log('Error in message callback', error)
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
} catch (error) {
|
|
172
|
+
log('Failed to parse WebSocket message', error)
|
|
173
|
+
setError('Failed to parse message')
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
ws.onclose = () => {
|
|
178
|
+
setConnected(false)
|
|
179
|
+
setConnecting(false)
|
|
180
|
+
setConnectionId(null)
|
|
181
|
+
log('WebSocket connection closed')
|
|
182
|
+
|
|
183
|
+
// Auto-reconnect logic
|
|
184
|
+
if (reconnectAttempts.current < maxReconnectAttempts) {
|
|
185
|
+
reconnectAttempts.current++
|
|
186
|
+
log(`Attempting to reconnect (${reconnectAttempts.current}/${maxReconnectAttempts})`)
|
|
187
|
+
|
|
188
|
+
reconnectTimeout.current = window.setTimeout(() => {
|
|
189
|
+
connect()
|
|
190
|
+
}, reconnectInterval)
|
|
191
|
+
} else {
|
|
192
|
+
setError('Max reconnection attempts reached')
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
ws.onerror = (error) => {
|
|
197
|
+
log('WebSocket error', error)
|
|
198
|
+
setError('WebSocket connection error')
|
|
199
|
+
setConnecting(false)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
} catch (error) {
|
|
203
|
+
setConnecting(false)
|
|
204
|
+
setError(error instanceof Error ? error.message : 'Connection failed')
|
|
205
|
+
log('Failed to create WebSocket connection', error)
|
|
206
|
+
}
|
|
207
|
+
}, [url, reconnectInterval, maxReconnectAttempts, log])
|
|
208
|
+
|
|
209
|
+
const sendMessage = useCallback(async (message: WebSocketMessage): Promise<WebSocketResponse | null> => {
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
|
|
212
|
+
reject(new Error('WebSocket is not connected'))
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// CALL_ACTION doesn't expect response - send and resolve immediately
|
|
217
|
+
if (message.type === 'CALL_ACTION') {
|
|
218
|
+
try {
|
|
219
|
+
const messageWithTimestamp = { ...message, timestamp: Date.now() }
|
|
220
|
+
wsRef.current.send(JSON.stringify(messageWithTimestamp))
|
|
221
|
+
log('Sent message', messageWithTimestamp)
|
|
222
|
+
resolve(null) // No response expected
|
|
223
|
+
return
|
|
224
|
+
} catch (error) {
|
|
225
|
+
reject(error)
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Generate unique message ID for response tracking (other message types)
|
|
231
|
+
const messageId = `${message.componentId || 'msg'}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
232
|
+
|
|
233
|
+
// Set up callback for response
|
|
234
|
+
if (message.componentId) {
|
|
235
|
+
messageCallbacks.current.set(message.componentId, (response) => {
|
|
236
|
+
resolve(response)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// Timeout after 10 seconds
|
|
240
|
+
setTimeout(() => {
|
|
241
|
+
if (messageCallbacks.current.has(message.componentId!)) {
|
|
242
|
+
messageCallbacks.current.delete(message.componentId!)
|
|
243
|
+
reject(new Error('Message timeout'))
|
|
244
|
+
}
|
|
245
|
+
}, 10000)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const messageWithTimestamp = {
|
|
250
|
+
...message,
|
|
251
|
+
timestamp: Date.now()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
wsRef.current.send(JSON.stringify(messageWithTimestamp))
|
|
255
|
+
log('Sent message', messageWithTimestamp)
|
|
256
|
+
|
|
257
|
+
// If no component ID, resolve immediately
|
|
258
|
+
if (!message.componentId) {
|
|
259
|
+
resolve(null)
|
|
260
|
+
}
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (message.componentId) {
|
|
263
|
+
messageCallbacks.current.delete(message.componentId)
|
|
264
|
+
}
|
|
265
|
+
reject(error)
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
}, [log])
|
|
269
|
+
|
|
270
|
+
// Send message and wait for response with unique ID
|
|
271
|
+
const sendMessageAndWait = useCallback(async (
|
|
272
|
+
message: WebSocketMessage,
|
|
273
|
+
timeout: number = 10000
|
|
274
|
+
): Promise<any> => {
|
|
275
|
+
return new Promise((resolve, reject) => {
|
|
276
|
+
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
|
|
277
|
+
reject(new Error('WebSocket is not connected'))
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const requestId = generateRequestId()
|
|
282
|
+
|
|
283
|
+
// Set up timeout
|
|
284
|
+
const timeoutHandle = setTimeout(() => {
|
|
285
|
+
pendingRequests.current.delete(requestId)
|
|
286
|
+
reject(new Error(`Request timeout after ${timeout}ms`))
|
|
287
|
+
}, timeout)
|
|
288
|
+
|
|
289
|
+
// Store the pending request
|
|
290
|
+
pendingRequests.current.set(requestId, {
|
|
291
|
+
resolve,
|
|
292
|
+
reject,
|
|
293
|
+
timeout: timeoutHandle
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const messageWithRequestId = {
|
|
298
|
+
...message,
|
|
299
|
+
requestId,
|
|
300
|
+
expectResponse: true,
|
|
301
|
+
timestamp: Date.now()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
wsRef.current.send(JSON.stringify(messageWithRequestId))
|
|
305
|
+
log('Sent message with request ID', { requestId, message: messageWithRequestId })
|
|
306
|
+
} catch (error) {
|
|
307
|
+
// Cleanup on send error
|
|
308
|
+
clearTimeout(timeoutHandle)
|
|
309
|
+
pendingRequests.current.delete(requestId)
|
|
310
|
+
reject(error)
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
}, [log, generateRequestId])
|
|
314
|
+
|
|
315
|
+
const close = useCallback(() => {
|
|
316
|
+
if (reconnectTimeout.current) {
|
|
317
|
+
clearTimeout(reconnectTimeout.current)
|
|
318
|
+
reconnectTimeout.current = null
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (wsRef.current) {
|
|
322
|
+
wsRef.current.close()
|
|
323
|
+
wsRef.current = null
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
reconnectAttempts.current = maxReconnectAttempts // Prevent auto-reconnect
|
|
327
|
+
setConnected(false)
|
|
328
|
+
setConnecting(false)
|
|
329
|
+
setConnectionId(null)
|
|
330
|
+
log('WebSocket connection closed manually')
|
|
331
|
+
}, [maxReconnectAttempts, log])
|
|
332
|
+
|
|
333
|
+
const reconnect = useCallback(() => {
|
|
334
|
+
close()
|
|
335
|
+
reconnectAttempts.current = 0
|
|
336
|
+
setTimeout(connect, 50) // Reduced delay from 100ms to 50ms
|
|
337
|
+
}, [close, connect])
|
|
338
|
+
|
|
339
|
+
// Auto-connect on mount
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
if (autoConnect) {
|
|
342
|
+
connect()
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return () => {
|
|
346
|
+
close()
|
|
347
|
+
}
|
|
348
|
+
}, [autoConnect, connect, close])
|
|
349
|
+
|
|
350
|
+
// Register message callback
|
|
351
|
+
const onMessage = useCallback((callback: (message: WebSocketResponse) => void) => {
|
|
352
|
+
messageCallbacksRef.current.add(callback)
|
|
353
|
+
|
|
354
|
+
// Return cleanup function
|
|
355
|
+
return () => {
|
|
356
|
+
messageCallbacksRef.current.delete(callback)
|
|
357
|
+
}
|
|
358
|
+
}, [])
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
connected,
|
|
362
|
+
connecting,
|
|
363
|
+
error,
|
|
364
|
+
connectionId,
|
|
365
|
+
sendMessage,
|
|
366
|
+
sendMessageAndWait,
|
|
367
|
+
close,
|
|
368
|
+
reconnect,
|
|
369
|
+
messageHistory,
|
|
370
|
+
lastMessage,
|
|
371
|
+
onMessage
|
|
372
|
+
}
|
|
373
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// 🔥 FluxStack Client Core - Main Export
|
|
2
|
+
|
|
3
|
+
// Hooks
|
|
4
|
+
export { useWebSocket } from './hooks/useWebSocket'
|
|
5
|
+
export { useHybridLiveComponent } from './hooks/useHybridLiveComponent'
|
|
6
|
+
export { useChunkedUpload } from './hooks/useChunkedUpload'
|
|
7
|
+
export { StateValidator } from './hooks/state-validator'
|
|
8
|
+
|
|
9
|
+
// Re-export types from core/types/types.ts for convenience
|
|
10
|
+
export type {
|
|
11
|
+
// Live Components types
|
|
12
|
+
LiveMessage,
|
|
13
|
+
ComponentState,
|
|
14
|
+
LiveComponentInstance,
|
|
15
|
+
WebSocketData,
|
|
16
|
+
ComponentDefinition,
|
|
17
|
+
BroadcastMessage,
|
|
18
|
+
LiveComponent,
|
|
19
|
+
|
|
20
|
+
// WebSocket types
|
|
21
|
+
WebSocketMessage,
|
|
22
|
+
WebSocketResponse,
|
|
23
|
+
|
|
24
|
+
// Hybrid Live Component types
|
|
25
|
+
HybridState,
|
|
26
|
+
StateValidation,
|
|
27
|
+
StateConflict,
|
|
28
|
+
HybridComponentOptions,
|
|
29
|
+
|
|
30
|
+
// File Upload types
|
|
31
|
+
FileChunkData,
|
|
32
|
+
FileUploadStartMessage,
|
|
33
|
+
FileUploadChunkMessage,
|
|
34
|
+
FileUploadCompleteMessage,
|
|
35
|
+
FileUploadProgressResponse,
|
|
36
|
+
FileUploadCompleteResponse,
|
|
37
|
+
ActiveUpload,
|
|
38
|
+
|
|
39
|
+
// Utility types
|
|
40
|
+
ComponentActions,
|
|
41
|
+
ComponentProps,
|
|
42
|
+
ActionParameters,
|
|
43
|
+
ActionReturnType
|
|
44
|
+
} from '../types/types'
|
|
45
|
+
|
|
46
|
+
// Hook return types
|
|
47
|
+
export type { UseHybridLiveComponentReturn } from './hooks/useHybridLiveComponent'
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store Factory
|
|
3
|
+
* Core FluxStack state management utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { create } from 'zustand'
|
|
7
|
+
import { persist, createJSONStorage } from 'zustand/middleware'
|
|
8
|
+
|
|
9
|
+
export interface StoreOptions<T> {
|
|
10
|
+
name?: string
|
|
11
|
+
persist?: boolean
|
|
12
|
+
storage?: 'localStorage' | 'sessionStorage'
|
|
13
|
+
version?: number
|
|
14
|
+
migrate?: (persistedState: unknown, version: number) => T
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a Zustand store with FluxStack conventions
|
|
19
|
+
*/
|
|
20
|
+
export function createFluxStore<T>(
|
|
21
|
+
storeFactory: (set: any, get: any) => T,
|
|
22
|
+
options: StoreOptions<T> = {}
|
|
23
|
+
) {
|
|
24
|
+
const { name, persist: shouldPersist = false, storage = 'localStorage', version = 1, migrate } = options
|
|
25
|
+
|
|
26
|
+
if (shouldPersist && name) {
|
|
27
|
+
return create<T>()(
|
|
28
|
+
persist(
|
|
29
|
+
storeFactory,
|
|
30
|
+
{
|
|
31
|
+
name,
|
|
32
|
+
storage: createJSONStorage(() =>
|
|
33
|
+
storage === 'localStorage' ? localStorage : sessionStorage
|
|
34
|
+
),
|
|
35
|
+
version,
|
|
36
|
+
migrate: migrate as any,
|
|
37
|
+
onRehydrateStorage: () => (state) => {
|
|
38
|
+
console.log('FluxStack: Store rehydrated', name, state)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return create<T>()(storeFactory)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Base user store interface
|
|
50
|
+
*/
|
|
51
|
+
export interface BaseUser {
|
|
52
|
+
id: string
|
|
53
|
+
email: string
|
|
54
|
+
name: string
|
|
55
|
+
role: 'admin' | 'user'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface BaseUserStore {
|
|
59
|
+
currentUser: BaseUser | null
|
|
60
|
+
isAuthenticated: boolean
|
|
61
|
+
isLoading: boolean
|
|
62
|
+
error: string | null
|
|
63
|
+
login: (credentials: { email: string; password: string }) => Promise<void>
|
|
64
|
+
register: (data: { email: string; password: string; name: string }) => Promise<void>
|
|
65
|
+
logout: () => void
|
|
66
|
+
updateProfile: (data: Partial<BaseUser>) => Promise<void>
|
|
67
|
+
clearError: () => void
|
|
68
|
+
setLoading: (loading: boolean) => void
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create user store with FluxStack conventions
|
|
73
|
+
*/
|
|
74
|
+
export function createUserStore(options: StoreOptions<BaseUserStore> = {}) {
|
|
75
|
+
return createFluxStore<BaseUserStore>(
|
|
76
|
+
(set, get) => ({
|
|
77
|
+
currentUser: null,
|
|
78
|
+
isAuthenticated: false,
|
|
79
|
+
isLoading: false,
|
|
80
|
+
error: null,
|
|
81
|
+
|
|
82
|
+
login: async (credentials) => {
|
|
83
|
+
set({ isLoading: true, error: null })
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch('/api/auth/login', {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: { 'Content-Type': 'application/json' },
|
|
88
|
+
body: JSON.stringify(credentials)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const error = await response.json()
|
|
93
|
+
throw new Error(error.message || 'Login failed')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const { user } = await response.json()
|
|
97
|
+
set({
|
|
98
|
+
currentUser: user,
|
|
99
|
+
isAuthenticated: true,
|
|
100
|
+
isLoading: false
|
|
101
|
+
})
|
|
102
|
+
} catch (error) {
|
|
103
|
+
set({
|
|
104
|
+
error: error instanceof Error ? error.message : 'Login failed',
|
|
105
|
+
isLoading: false
|
|
106
|
+
})
|
|
107
|
+
throw error
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
register: async (data) => {
|
|
112
|
+
set({ isLoading: true, error: null })
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch('/api/auth/register', {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { 'Content-Type': 'application/json' },
|
|
117
|
+
body: JSON.stringify(data)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const error = await response.json()
|
|
122
|
+
throw new Error(error.message || 'Registration failed')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { user } = await response.json()
|
|
126
|
+
set({
|
|
127
|
+
currentUser: user,
|
|
128
|
+
isAuthenticated: true,
|
|
129
|
+
isLoading: false
|
|
130
|
+
})
|
|
131
|
+
} catch (error) {
|
|
132
|
+
set({
|
|
133
|
+
error: error instanceof Error ? error.message : 'Registration failed',
|
|
134
|
+
isLoading: false
|
|
135
|
+
})
|
|
136
|
+
throw error
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
logout: () => {
|
|
141
|
+
// Call logout API
|
|
142
|
+
fetch('/api/auth/logout', { method: 'POST' }).catch(console.error)
|
|
143
|
+
|
|
144
|
+
set({
|
|
145
|
+
currentUser: null,
|
|
146
|
+
isAuthenticated: false,
|
|
147
|
+
error: null
|
|
148
|
+
})
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
updateProfile: async (data) => {
|
|
152
|
+
const { currentUser } = get()
|
|
153
|
+
if (!currentUser) {
|
|
154
|
+
throw new Error('No user logged in')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
set({ isLoading: true, error: null })
|
|
158
|
+
try {
|
|
159
|
+
const response = await fetch('/api/user/profile', {
|
|
160
|
+
method: 'PUT',
|
|
161
|
+
headers: { 'Content-Type': 'application/json' },
|
|
162
|
+
body: JSON.stringify(data)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
const error = await response.json()
|
|
167
|
+
throw new Error(error.message || 'Profile update failed')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { user } = await response.json()
|
|
171
|
+
set({
|
|
172
|
+
currentUser: user,
|
|
173
|
+
isLoading: false
|
|
174
|
+
})
|
|
175
|
+
} catch (error) {
|
|
176
|
+
set({
|
|
177
|
+
error: error instanceof Error ? error.message : 'Profile update failed',
|
|
178
|
+
isLoading: false
|
|
179
|
+
})
|
|
180
|
+
throw error
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
clearError: () => set({ error: null }),
|
|
185
|
+
setLoading: (loading) => set({ isLoading: loading })
|
|
186
|
+
}),
|
|
187
|
+
{
|
|
188
|
+
name: 'user-store',
|
|
189
|
+
persist: true,
|
|
190
|
+
...options
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
}
|
|
@@ -237,7 +237,7 @@ export class DynamicEnvironmentProcessor {
|
|
|
237
237
|
* Enhanced environment info with dynamic access
|
|
238
238
|
*/
|
|
239
239
|
export function getDynamicEnvironmentInfo() {
|
|
240
|
-
const nodeEnv = env.get('NODE_ENV', 'development')
|
|
240
|
+
const nodeEnv = env.get('NODE_ENV', 'development') as 'development' | 'production' | 'test'
|
|
241
241
|
|
|
242
242
|
return {
|
|
243
243
|
name: nodeEnv,
|
package/core/config/env.ts
CHANGED
|
@@ -46,7 +46,8 @@ export class EnvConverter {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
static toBoolean(value: string | undefined, defaultValue: boolean): boolean {
|
|
49
|
-
if (
|
|
49
|
+
if (value === undefined) return defaultValue
|
|
50
|
+
if (value === '') return false
|
|
50
51
|
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase())
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -177,7 +177,7 @@ export const runtimeConfig = {
|
|
|
177
177
|
* Auto-detect environment and create appropriate config
|
|
178
178
|
*/
|
|
179
179
|
auto(overrides?: Partial<FluxStackConfig>): FluxStackConfig {
|
|
180
|
-
const environment = env.get('NODE_ENV', 'development')
|
|
180
|
+
const environment = env.get('NODE_ENV', 'development') as 'development' | 'production' | 'test'
|
|
181
181
|
|
|
182
182
|
let config: FluxStackConfig
|
|
183
183
|
|
|
@@ -245,7 +245,7 @@ export const configHelpers = {
|
|
|
245
245
|
* Get database URL with validation
|
|
246
246
|
*/
|
|
247
247
|
getDatabaseUrl(): string | null {
|
|
248
|
-
const url = env.get('DATABASE_URL')
|
|
248
|
+
const url = env.get('DATABASE_URL') as string | undefined
|
|
249
249
|
|
|
250
250
|
if (url) {
|
|
251
251
|
envValidation.validate('DATABASE_URL',
|
|
@@ -264,7 +264,7 @@ export const configHelpers = {
|
|
|
264
264
|
const origins = env.array('CORS_ORIGINS')
|
|
265
265
|
|
|
266
266
|
if (origins.length === 0) {
|
|
267
|
-
const environment = env.get('NODE_ENV', 'development')
|
|
267
|
+
const environment = env.get('NODE_ENV', 'development') as 'development' | 'production' | 'test'
|
|
268
268
|
|
|
269
269
|
if (environment === 'development') {
|
|
270
270
|
return ['http://localhost:3000', 'http://localhost:5173']
|