create-fluxstack 1.10.1 → 1.12.1
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/.dockerignore +1 -2
- package/Dockerfile +8 -8
- package/LLMD/INDEX.md +64 -0
- package/LLMD/MAINTENANCE.md +197 -0
- package/LLMD/MIGRATION.md +156 -0
- package/LLMD/config/.gitkeep +1 -0
- package/LLMD/config/declarative-system.md +268 -0
- package/LLMD/config/environment-vars.md +327 -0
- package/LLMD/config/runtime-reload.md +401 -0
- package/LLMD/core/.gitkeep +1 -0
- package/LLMD/core/build-system.md +599 -0
- package/LLMD/core/framework-lifecycle.md +229 -0
- package/LLMD/core/plugin-system.md +451 -0
- package/LLMD/patterns/.gitkeep +1 -0
- package/LLMD/patterns/anti-patterns.md +297 -0
- package/LLMD/patterns/project-structure.md +264 -0
- package/LLMD/patterns/type-safety.md +440 -0
- package/LLMD/reference/.gitkeep +1 -0
- package/LLMD/reference/cli-commands.md +250 -0
- package/LLMD/reference/plugin-hooks.md +357 -0
- package/LLMD/reference/routing.md +39 -0
- package/LLMD/reference/troubleshooting.md +364 -0
- package/LLMD/resources/.gitkeep +1 -0
- package/LLMD/resources/controllers.md +465 -0
- package/LLMD/resources/live-components.md +703 -0
- package/LLMD/resources/live-rooms.md +482 -0
- package/LLMD/resources/live-upload.md +130 -0
- package/LLMD/resources/plugins-external.md +617 -0
- package/LLMD/resources/routes-eden.md +254 -0
- package/README.md +37 -17
- package/app/client/index.html +0 -1
- package/app/client/src/App.tsx +107 -150
- package/app/client/src/components/AppLayout.tsx +68 -0
- package/app/client/src/components/BackButton.tsx +13 -0
- package/app/client/src/components/DemoPage.tsx +20 -0
- package/app/client/src/components/LiveUploadWidget.tsx +204 -0
- package/app/client/src/lib/eden-api.ts +85 -60
- package/app/client/src/live/ChatDemo.tsx +107 -0
- package/app/client/src/live/CounterDemo.tsx +206 -0
- package/app/client/src/live/FormDemo.tsx +119 -0
- package/app/client/src/live/RoomChatDemo.tsx +161 -0
- package/app/client/src/live/UploadDemo.tsx +21 -0
- package/app/client/src/main.tsx +4 -1
- package/app/client/src/pages/ApiTestPage.tsx +108 -0
- package/app/client/src/pages/HomePage.tsx +76 -0
- package/app/server/app.ts +1 -4
- package/app/server/controllers/users.controller.ts +36 -44
- package/app/server/index.ts +25 -35
- package/app/server/live/LiveChat.ts +77 -0
- package/app/server/live/LiveCounter.ts +67 -0
- package/app/server/live/LiveForm.ts +63 -0
- package/app/server/live/LiveLocalCounter.ts +32 -0
- package/app/server/live/LiveRoomChat.ts +127 -0
- package/app/server/live/LiveUpload.ts +81 -0
- package/app/server/routes/index.ts +3 -1
- package/app/server/routes/room.routes.ts +117 -0
- package/app/server/routes/users.routes.ts +35 -27
- package/app/shared/types/index.ts +14 -2
- package/config/app.config.ts +2 -62
- package/config/client.config.ts +2 -95
- package/config/database.config.ts +2 -99
- package/config/fluxstack.config.ts +25 -45
- package/config/index.ts +57 -38
- package/config/monitoring.config.ts +2 -114
- package/config/plugins.config.ts +2 -80
- package/config/server.config.ts +2 -68
- package/config/services.config.ts +2 -130
- package/config/system/app.config.ts +29 -0
- package/config/system/build.config.ts +49 -0
- package/config/system/client.config.ts +68 -0
- package/config/system/database.config.ts +17 -0
- package/config/system/fluxstack.config.ts +114 -0
- package/config/{logger.config.ts → system/logger.config.ts} +3 -1
- package/config/system/monitoring.config.ts +114 -0
- package/config/system/plugins.config.ts +84 -0
- package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
- package/config/system/server.config.ts +68 -0
- package/config/system/services.config.ts +46 -0
- package/config/{system.config.ts → system/system.config.ts} +1 -1
- package/core/build/flux-plugins-generator.ts +325 -325
- package/core/build/index.ts +39 -27
- package/core/build/live-components-generator.ts +3 -3
- package/core/build/optimizer.ts +235 -235
- package/core/cli/command-registry.ts +6 -4
- package/core/cli/commands/build.ts +79 -0
- package/core/cli/commands/create.ts +54 -0
- package/core/cli/commands/dev.ts +101 -0
- package/core/cli/commands/help.ts +34 -0
- package/core/cli/commands/index.ts +34 -0
- package/core/cli/commands/make-plugin.ts +90 -0
- package/core/cli/commands/plugin-add.ts +197 -0
- package/core/cli/commands/plugin-deps.ts +2 -2
- package/core/cli/commands/plugin-list.ts +208 -0
- package/core/cli/commands/plugin-remove.ts +170 -0
- package/core/cli/generators/component.ts +769 -769
- package/core/cli/generators/controller.ts +1 -1
- package/core/cli/generators/index.ts +146 -146
- package/core/cli/generators/interactive.ts +227 -227
- package/core/cli/generators/plugin.ts +2 -2
- package/core/cli/generators/prompts.ts +82 -82
- package/core/cli/generators/route.ts +6 -6
- package/core/cli/generators/service.ts +2 -2
- package/core/cli/generators/template-engine.ts +4 -3
- package/core/cli/generators/types.ts +2 -2
- package/core/cli/generators/utils.ts +191 -191
- package/core/cli/index.ts +115 -686
- package/core/cli/plugin-discovery.ts +2 -2
- package/core/client/LiveComponentsProvider.tsx +60 -8
- package/core/client/api/eden.ts +183 -0
- package/core/client/api/index.ts +11 -0
- package/core/client/components/Live.tsx +104 -0
- package/core/client/fluxstack.ts +1 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
- package/core/client/hooks/state-validator.ts +1 -1
- package/core/client/hooks/useAuth.ts +48 -48
- package/core/client/hooks/useChunkedUpload.ts +85 -35
- package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
- package/core/client/hooks/useLiveComponent.ts +800 -0
- package/core/client/hooks/useLiveUpload.ts +71 -0
- package/core/client/hooks/useRoom.ts +409 -0
- package/core/client/hooks/useRoomProxy.ts +382 -0
- package/core/client/index.ts +17 -68
- package/core/client/standalone-entry.ts +8 -0
- package/core/client/standalone.ts +74 -53
- package/core/client/state/createStore.ts +192 -192
- package/core/client/state/index.ts +14 -14
- package/core/config/index.ts +70 -291
- package/core/config/schema.ts +42 -723
- package/core/framework/client.ts +131 -131
- package/core/framework/index.ts +7 -7
- package/core/framework/server.ts +47 -40
- package/core/framework/types.ts +2 -2
- package/core/index.ts +23 -4
- package/core/live/ComponentRegistry.ts +3 -3
- package/core/live/types.ts +77 -0
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1066
- package/core/plugins/built-in/live-components/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +111 -47
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +68 -265
- package/core/plugins/built-in/vite/index.ts +85 -185
- package/core/plugins/built-in/vite/vite-dev.ts +10 -16
- package/core/plugins/config.ts +9 -7
- package/core/plugins/dependency-manager.ts +31 -1
- package/core/plugins/discovery.ts +19 -7
- package/core/plugins/executor.ts +2 -2
- package/core/plugins/index.ts +203 -203
- package/core/plugins/manager.ts +27 -39
- package/core/plugins/module-resolver.ts +19 -8
- package/core/plugins/registry.ts +255 -19
- package/core/plugins/types.ts +20 -53
- package/core/server/framework.ts +66 -43
- package/core/server/index.ts +15 -15
- package/core/server/live/ComponentRegistry.ts +78 -71
- package/core/server/live/FileUploadManager.ts +23 -10
- package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
- package/core/server/live/LiveRoomManager.ts +261 -0
- package/core/server/live/RoomEventBus.ts +234 -0
- package/core/server/live/RoomStateManager.ts +172 -0
- package/core/server/live/StateSignature.ts +643 -643
- package/core/server/live/WebSocketConnectionManager.ts +30 -19
- package/core/server/live/auto-generated-components.ts +21 -9
- package/core/server/live/index.ts +14 -0
- package/core/server/live/websocket-plugin.ts +214 -67
- package/core/server/middleware/elysia-helpers.ts +7 -2
- package/core/server/middleware/errorHandling.ts +1 -1
- package/core/server/middleware/index.ts +31 -31
- package/core/server/plugins/database.ts +180 -180
- package/core/server/plugins/static-files-plugin.ts +69 -69
- package/core/server/plugins/swagger.ts +1 -1
- package/core/server/rooms/RoomBroadcaster.ts +357 -0
- package/core/server/rooms/RoomSystem.ts +463 -0
- package/core/server/rooms/index.ts +13 -0
- package/core/server/services/BaseService.ts +1 -1
- package/core/server/services/ServiceContainer.ts +1 -1
- package/core/server/services/index.ts +8 -8
- package/core/templates/create-project.ts +12 -12
- package/core/testing/index.ts +9 -9
- package/core/testing/setup.ts +73 -73
- package/core/types/api.ts +168 -168
- package/core/types/build.ts +219 -219
- package/core/types/config.ts +56 -26
- package/core/types/index.ts +4 -4
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +353 -14
- package/core/utils/build-logger.ts +324 -324
- package/core/utils/config-schema.ts +480 -480
- package/core/utils/env.ts +2 -8
- package/core/utils/errors/codes.ts +114 -114
- package/core/utils/errors/handlers.ts +36 -1
- package/core/utils/errors/index.ts +49 -5
- package/core/utils/errors/middleware.ts +113 -113
- package/core/utils/helpers.ts +6 -16
- package/core/utils/index.ts +17 -17
- package/core/utils/logger/colors.ts +114 -114
- package/core/utils/logger/config.ts +13 -9
- package/core/utils/logger/formatter.ts +82 -82
- package/core/utils/logger/group-logger.ts +101 -101
- package/core/utils/logger/index.ts +6 -1
- package/core/utils/logger/stack-trace.ts +3 -1
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/logger/winston-logger.ts +152 -152
- package/core/utils/monitoring/index.ts +211 -211
- package/core/utils/sync-version.ts +66 -66
- package/core/utils/version.ts +1 -1
- package/create-fluxstack.ts +8 -7
- package/package.json +12 -13
- package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
- package/plugins/crypto-auth/client/components/index.ts +11 -11
- package/plugins/crypto-auth/client/index.ts +11 -11
- package/plugins/crypto-auth/config/index.ts +1 -1
- package/plugins/crypto-auth/index.ts +4 -4
- package/plugins/crypto-auth/package.json +65 -65
- package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
- package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
- package/plugins/crypto-auth/server/index.ts +21 -21
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
- package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
- package/tsconfig.api-strict.json +16 -0
- package/tsconfig.json +48 -52
- package/{app/client/tsconfig.node.json → tsconfig.node.json} +25 -25
- package/types/global.d.ts +29 -29
- package/types/vitest.d.ts +8 -8
- package/vite.config.ts +38 -62
- package/vitest.config.live.ts +10 -9
- package/vitest.config.ts +29 -17
- package/app/client/README.md +0 -69
- package/app/client/SIMPLIFICATION.md +0 -140
- package/app/client/frontend-only.ts +0 -12
- package/app/client/src/live/FileUploadExample.tsx +0 -359
- package/app/client/src/live/MinimalLiveClock.tsx +0 -47
- package/app/client/src/live/QuickUploadTest.tsx +0 -193
- package/app/client/tsconfig.app.json +0 -45
- package/app/client/tsconfig.json +0 -7
- package/app/client/zustand-setup.md +0 -65
- package/app/server/backend-only.ts +0 -18
- package/app/server/live/LiveClockComponent.ts +0 -215
- package/app/server/live/LiveFileUploadComponent.ts +0 -77
- package/app/server/routes/env-test.ts +0 -110
- package/core/client/hooks/index.ts +0 -7
- package/core/client/hooks/useHybridLiveComponent.ts +0 -685
- package/core/client/hooks/useTypedLiveComponent.ts +0 -133
- package/core/client/hooks/useWebSocket.ts +0 -361
- package/core/config/env.ts +0 -546
- package/core/config/loader.ts +0 -522
- package/core/config/runtime-config.ts +0 -327
- package/core/config/validator.ts +0 -540
- package/core/server/backend-entry.ts +0 -51
- package/core/server/standalone.ts +0 -106
- package/core/utils/regenerate-files.ts +0 -69
- package/fluxstack.config.ts +0 -354
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// 🔥 FluxStack Live Components - Enhanced Component Registry
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
LiveComponent,
|
|
5
|
-
LiveMessage,
|
|
6
|
-
BroadcastMessage,
|
|
3
|
+
import type {
|
|
4
|
+
LiveComponent,
|
|
5
|
+
LiveMessage,
|
|
6
|
+
BroadcastMessage,
|
|
7
7
|
ComponentDefinition,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
FluxStackWebSocket,
|
|
9
|
+
FluxStackWSData
|
|
10
|
+
} from '@core/plugins/types'
|
|
10
11
|
import { stateSignature, type SignedState } from './StateSignature'
|
|
11
12
|
import { performanceMonitor } from './LiveComponentPerformanceMonitor'
|
|
12
13
|
|
|
@@ -65,14 +66,14 @@ export interface ServiceContainer {
|
|
|
65
66
|
|
|
66
67
|
export class ComponentRegistry {
|
|
67
68
|
private components = new Map<string, LiveComponent>()
|
|
68
|
-
private definitions = new Map<string, ComponentDefinition
|
|
69
|
+
private definitions = new Map<string, ComponentDefinition<any>>()
|
|
69
70
|
private metadata = new Map<string, ComponentMetadata>()
|
|
70
71
|
private rooms = new Map<string, Set<string>>() // roomId -> componentIds
|
|
71
|
-
private wsConnections = new Map<string,
|
|
72
|
-
private autoDiscoveredComponents = new Map<string, any
|
|
72
|
+
private wsConnections = new Map<string, FluxStackWebSocket>() // componentId -> websocket
|
|
73
|
+
private autoDiscoveredComponents = new Map<string, new (initialState: any, ws: FluxStackWebSocket, options?: { room?: string; userId?: string }) => LiveComponent<any>>() // Auto-discovered component classes
|
|
73
74
|
private dependencies = new Map<string, ComponentDependency[]>()
|
|
74
75
|
private services: ServiceContainer
|
|
75
|
-
private healthCheckInterval
|
|
76
|
+
private healthCheckInterval!: NodeJS.Timeout
|
|
76
77
|
private recoveryStrategies = new Map<string, () => Promise<void>>()
|
|
77
78
|
|
|
78
79
|
constructor() {
|
|
@@ -141,7 +142,7 @@ export class ComponentRegistry {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
// Register component class dynamically
|
|
144
|
-
registerComponentClass(name: string, componentClass: any) {
|
|
145
|
+
registerComponentClass(name: string, componentClass: new (initialState: any, ws: FluxStackWebSocket, options?: { room?: string; userId?: string }) => LiveComponent<any>) {
|
|
145
146
|
this.autoDiscoveredComponents.set(name, componentClass)
|
|
146
147
|
// Logging is handled by autoDiscoverComponents
|
|
147
148
|
}
|
|
@@ -151,11 +152,11 @@ export class ComponentRegistry {
|
|
|
151
152
|
try {
|
|
152
153
|
const fs = await import('fs')
|
|
153
154
|
const path = await import('path')
|
|
154
|
-
const { startGroup, endGroup, logInGroup, groupSummary } = await import('
|
|
155
|
+
const { startGroup, endGroup, logInGroup, groupSummary } = await import('@core/utils/logger/group-logger')
|
|
155
156
|
|
|
156
157
|
if (!fs.existsSync(componentsPath)) {
|
|
157
158
|
// In production, components are already bundled - no need to auto-discover
|
|
158
|
-
const { appConfig } = await import('
|
|
159
|
+
const { appConfig } = await import('@config')
|
|
159
160
|
if (appConfig.env !== 'production') {
|
|
160
161
|
console.log(`⚠️ Components path not found: ${componentsPath}`)
|
|
161
162
|
}
|
|
@@ -215,11 +216,11 @@ export class ComponentRegistry {
|
|
|
215
216
|
|
|
216
217
|
// Enhanced component mounting with lifecycle management
|
|
217
218
|
async mountComponent(
|
|
218
|
-
ws:
|
|
219
|
-
componentName: string,
|
|
220
|
-
props:
|
|
219
|
+
ws: FluxStackWebSocket,
|
|
220
|
+
componentName: string,
|
|
221
|
+
props: Record<string, unknown> = {},
|
|
221
222
|
options?: { room?: string; userId?: string; version?: string }
|
|
222
|
-
): Promise<{ componentId: string; initialState:
|
|
223
|
+
): Promise<{ componentId: string; initialState: unknown; signedState: unknown }> {
|
|
223
224
|
const startTime = Date.now()
|
|
224
225
|
|
|
225
226
|
try {
|
|
@@ -227,38 +228,38 @@ export class ComponentRegistry {
|
|
|
227
228
|
await this.validateDependencies(componentName)
|
|
228
229
|
|
|
229
230
|
// Try to find component definition first
|
|
230
|
-
|
|
231
|
-
let ComponentClass: any = null
|
|
232
|
-
let initialState:
|
|
231
|
+
const definition = this.definitions.get(componentName)
|
|
232
|
+
let ComponentClass: (new (initialState: any, ws: FluxStackWebSocket, options?: { room?: string; userId?: string }) => LiveComponent<any>) | null = null
|
|
233
|
+
let initialState: Record<string, unknown> = {}
|
|
233
234
|
|
|
234
235
|
if (definition) {
|
|
235
236
|
// Use registered definition
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
237
|
+
ComponentClass = definition.component
|
|
238
|
+
initialState = definition.initialState as Record<string, unknown>
|
|
239
|
+
} else {
|
|
240
|
+
// Try auto-discovered components
|
|
241
|
+
ComponentClass = this.autoDiscoveredComponents.get(componentName) ?? null
|
|
242
|
+
if (!ComponentClass) {
|
|
243
|
+
// Try variations of the name
|
|
244
|
+
const variations = [
|
|
245
|
+
componentName + 'Component',
|
|
246
|
+
componentName.charAt(0).toUpperCase() + componentName.slice(1) + 'Component',
|
|
247
|
+
componentName.charAt(0).toUpperCase() + componentName.slice(1)
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
for (const variation of variations) {
|
|
251
|
+
ComponentClass = this.autoDiscoveredComponents.get(variation) ?? null
|
|
252
|
+
if (ComponentClass) break
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!ComponentClass) {
|
|
257
|
+
const availableComponents = [
|
|
258
|
+
...Array.from(this.definitions.keys()),
|
|
259
|
+
...Array.from(this.autoDiscoveredComponents.keys())
|
|
260
|
+
]
|
|
261
|
+
throw new Error(`Component '${componentName}' not found. Available: ${availableComponents.join(', ')}`)
|
|
252
262
|
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (!ComponentClass) {
|
|
256
|
-
const availableComponents = [
|
|
257
|
-
...Array.from(this.definitions.keys()),
|
|
258
|
-
...Array.from(this.autoDiscoveredComponents.keys())
|
|
259
|
-
]
|
|
260
|
-
throw new Error(`Component '${componentName}' not found. Available: ${availableComponents.join(', ')}`)
|
|
261
|
-
}
|
|
262
263
|
|
|
263
264
|
// Create a default initial state for auto-discovered components
|
|
264
265
|
initialState = {}
|
|
@@ -306,13 +307,16 @@ export class ComponentRegistry {
|
|
|
306
307
|
if (!ws || typeof ws !== 'object') {
|
|
307
308
|
throw new Error('Invalid WebSocket object provided')
|
|
308
309
|
}
|
|
309
|
-
|
|
310
|
+
|
|
311
|
+
// Ensure data object exists with proper structure
|
|
310
312
|
if (!ws.data) {
|
|
311
|
-
ws.data = {
|
|
313
|
+
(ws as { data: FluxStackWSData }).data = {
|
|
314
|
+
connectionId: `ws-${Date.now()}`,
|
|
312
315
|
components: new Map(),
|
|
313
316
|
subscriptions: new Set(),
|
|
317
|
+
connectedAt: new Date(),
|
|
314
318
|
userId: options?.userId
|
|
315
|
-
}
|
|
319
|
+
}
|
|
316
320
|
}
|
|
317
321
|
|
|
318
322
|
// Ensure components map exists
|
|
@@ -358,9 +362,9 @@ export class ComponentRegistry {
|
|
|
358
362
|
// Re-hydrate component with signed client state
|
|
359
363
|
async rehydrateComponent(
|
|
360
364
|
componentId: string,
|
|
361
|
-
componentName: string,
|
|
365
|
+
componentName: string,
|
|
362
366
|
signedState: SignedState,
|
|
363
|
-
ws:
|
|
367
|
+
ws: FluxStackWebSocket,
|
|
364
368
|
options?: { room?: string; userId?: string }
|
|
365
369
|
): Promise<{ success: boolean; newComponentId?: string; error?: string }> {
|
|
366
370
|
console.log('🔄 Attempting component re-hydration:', {
|
|
@@ -385,17 +389,17 @@ export class ComponentRegistry {
|
|
|
385
389
|
}
|
|
386
390
|
|
|
387
391
|
// Try to find component definition (same logic as mountComponent)
|
|
388
|
-
|
|
389
|
-
let ComponentClass: any = null
|
|
390
|
-
let initialState:
|
|
392
|
+
const definition = this.definitions.get(componentName)
|
|
393
|
+
let ComponentClass: (new (initialState: any, ws: FluxStackWebSocket, options?: { room?: string; userId?: string }) => LiveComponent<any>) | null = null
|
|
394
|
+
let initialState: Record<string, unknown> = {}
|
|
391
395
|
|
|
392
396
|
if (definition) {
|
|
393
397
|
// Use registered definition
|
|
394
398
|
ComponentClass = definition.component
|
|
395
|
-
initialState = definition.initialState
|
|
399
|
+
initialState = definition.initialState as Record<string, unknown>
|
|
396
400
|
} else {
|
|
397
401
|
// Try auto-discovered components
|
|
398
|
-
ComponentClass = this.autoDiscoveredComponents.get(componentName)
|
|
402
|
+
ComponentClass = this.autoDiscoveredComponents.get(componentName) ?? null
|
|
399
403
|
if (!ComponentClass) {
|
|
400
404
|
// Try variations of the name
|
|
401
405
|
const variations = [
|
|
@@ -403,13 +407,13 @@ export class ComponentRegistry {
|
|
|
403
407
|
componentName.charAt(0).toUpperCase() + componentName.slice(1) + 'Component',
|
|
404
408
|
componentName.charAt(0).toUpperCase() + componentName.slice(1)
|
|
405
409
|
]
|
|
406
|
-
|
|
410
|
+
|
|
407
411
|
for (const variation of variations) {
|
|
408
|
-
ComponentClass = this.autoDiscoveredComponents.get(variation)
|
|
412
|
+
ComponentClass = this.autoDiscoveredComponents.get(variation) ?? null
|
|
409
413
|
if (ComponentClass) break
|
|
410
414
|
}
|
|
411
415
|
}
|
|
412
|
-
|
|
416
|
+
|
|
413
417
|
if (!ComponentClass) {
|
|
414
418
|
const availableComponents = [
|
|
415
419
|
...Array.from(this.definitions.keys()),
|
|
@@ -440,11 +444,13 @@ export class ComponentRegistry {
|
|
|
440
444
|
|
|
441
445
|
// Initialize WebSocket data
|
|
442
446
|
if (!ws.data) {
|
|
443
|
-
ws.data = {
|
|
447
|
+
(ws as { data: FluxStackWSData }).data = {
|
|
448
|
+
connectionId: `ws-${Date.now()}`,
|
|
444
449
|
components: new Map(),
|
|
445
450
|
subscriptions: new Set(),
|
|
451
|
+
connectedAt: new Date(),
|
|
446
452
|
userId: options?.userId
|
|
447
|
-
}
|
|
453
|
+
}
|
|
448
454
|
}
|
|
449
455
|
|
|
450
456
|
// Ensure components map exists
|
|
@@ -467,7 +473,8 @@ export class ComponentRegistry {
|
|
|
467
473
|
signedState.version + 1
|
|
468
474
|
)
|
|
469
475
|
|
|
470
|
-
|
|
476
|
+
// Use type assertion to access protected emit method
|
|
477
|
+
;(component as unknown as { emit: (type: string, payload: unknown) => void }).emit('STATE_REHYDRATED', {
|
|
471
478
|
state: component.getSerializableState(),
|
|
472
479
|
signedState: newSignedState,
|
|
473
480
|
oldComponentId: componentId,
|
|
@@ -494,8 +501,8 @@ export class ComponentRegistry {
|
|
|
494
501
|
if (!component) return
|
|
495
502
|
|
|
496
503
|
// Cleanup
|
|
497
|
-
component.destroy()
|
|
498
|
-
|
|
504
|
+
component.destroy?.()
|
|
505
|
+
|
|
499
506
|
// Remove from room subscriptions
|
|
500
507
|
this.unsubscribeFromAllRooms(componentId)
|
|
501
508
|
|
|
@@ -516,7 +523,7 @@ export class ComponentRegistry {
|
|
|
516
523
|
}
|
|
517
524
|
|
|
518
525
|
try {
|
|
519
|
-
return await component.executeAction(action, payload)
|
|
526
|
+
return await component.executeAction?.(action, payload)
|
|
520
527
|
} catch (error: any) {
|
|
521
528
|
console.error(`❌ Error executing action '${action}' on component '${componentId}':`, error.message)
|
|
522
529
|
throw error
|
|
@@ -532,7 +539,7 @@ export class ComponentRegistry {
|
|
|
532
539
|
|
|
533
540
|
// Update state
|
|
534
541
|
const updates = { [property]: value }
|
|
535
|
-
component.setState(updates)
|
|
542
|
+
component.setState?.(updates)
|
|
536
543
|
|
|
537
544
|
console.log(`📝 Updated property '${property}' on component '${componentId}'`)
|
|
538
545
|
}
|
|
@@ -606,7 +613,7 @@ export class ComponentRegistry {
|
|
|
606
613
|
}
|
|
607
614
|
|
|
608
615
|
// Handle WebSocket message with enhanced metrics and lifecycle tracking
|
|
609
|
-
async handleMessage(ws:
|
|
616
|
+
async handleMessage(ws: FluxStackWebSocket, message: LiveMessage): Promise<{ success: boolean; result?: unknown; error?: string } | null> {
|
|
610
617
|
const startTime = Date.now()
|
|
611
618
|
|
|
612
619
|
try {
|
|
@@ -698,7 +705,7 @@ export class ComponentRegistry {
|
|
|
698
705
|
}
|
|
699
706
|
|
|
700
707
|
// Cleanup when WebSocket disconnects
|
|
701
|
-
cleanupConnection(ws:
|
|
708
|
+
cleanupConnection(ws: FluxStackWebSocket) {
|
|
702
709
|
if (!ws.data?.components) return
|
|
703
710
|
|
|
704
711
|
const componentsToCleanup = Array.from(ws.data.components.keys()) as string[]
|
|
@@ -926,11 +933,11 @@ export class ComponentRegistry {
|
|
|
926
933
|
try {
|
|
927
934
|
console.log(`🔄 Migrating component ${componentId} from v${fromVersion} to v${toVersion}`)
|
|
928
935
|
|
|
929
|
-
const oldState = component.getSerializableState()
|
|
936
|
+
const oldState = component.getSerializableState?.()
|
|
930
937
|
const newState = migrationFn(oldState)
|
|
931
|
-
|
|
938
|
+
|
|
932
939
|
// Update component state
|
|
933
|
-
component.setState(newState)
|
|
940
|
+
component.setState?.(newState)
|
|
934
941
|
|
|
935
942
|
// Record migration
|
|
936
943
|
const migration: StateMigration = {
|
|
@@ -1006,7 +1013,7 @@ export class ComponentRegistry {
|
|
|
1006
1013
|
|
|
1007
1014
|
if (component) {
|
|
1008
1015
|
try {
|
|
1009
|
-
component.destroy()
|
|
1016
|
+
component.destroy?.()
|
|
1010
1017
|
} catch (error) {
|
|
1011
1018
|
console.error(`❌ Error destroying component ${componentId}:`, error)
|
|
1012
1019
|
}
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
FileUploadCompleteMessage,
|
|
9
9
|
FileUploadProgressResponse,
|
|
10
10
|
FileUploadCompleteResponse
|
|
11
|
-
} from '
|
|
11
|
+
} from '@core/types/types'
|
|
12
12
|
|
|
13
13
|
export class FileUploadManager {
|
|
14
14
|
private activeUploads = new Map<string, ActiveUpload>()
|
|
@@ -71,7 +71,7 @@ export class FileUploadManager {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
async receiveChunk(message: FileUploadChunkMessage, ws: any): Promise<FileUploadProgressResponse | null> {
|
|
74
|
+
async receiveChunk(message: FileUploadChunkMessage, ws: any, binaryData: Buffer | null = null): Promise<FileUploadProgressResponse | null> {
|
|
75
75
|
try {
|
|
76
76
|
const { uploadId, chunkIndex, totalChunks, data } = message
|
|
77
77
|
|
|
@@ -89,15 +89,23 @@ export class FileUploadManager {
|
|
|
89
89
|
if (upload.receivedChunks.has(chunkIndex)) {
|
|
90
90
|
console.log(`📦 Chunk ${chunkIndex} already received for upload ${uploadId}`)
|
|
91
91
|
} else {
|
|
92
|
-
// Store chunk data
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
// Store chunk data - use binary data if available, otherwise use base64 string
|
|
93
|
+
let chunkBytes: number
|
|
94
|
+
|
|
95
|
+
if (binaryData) {
|
|
96
|
+
// Binary protocol: store Buffer directly (more efficient)
|
|
97
|
+
upload.receivedChunks.set(chunkIndex, binaryData)
|
|
98
|
+
chunkBytes = binaryData.length
|
|
99
|
+
} else {
|
|
100
|
+
// JSON protocol: store base64 string (legacy support)
|
|
101
|
+
upload.receivedChunks.set(chunkIndex, data as string)
|
|
102
|
+
chunkBytes = Buffer.from(data as string, 'base64').length
|
|
103
|
+
}
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
const chunkBytes = Buffer.from(data, 'base64').length
|
|
105
|
+
upload.lastChunkTime = Date.now()
|
|
98
106
|
upload.bytesReceived += chunkBytes
|
|
99
107
|
|
|
100
|
-
console.log(`📦 Received chunk ${chunkIndex + 1}/${totalChunks} for upload ${uploadId} (${chunkBytes} bytes, total: ${upload.bytesReceived}/${upload.fileSize})`)
|
|
108
|
+
console.log(`📦 Received chunk ${chunkIndex + 1}/${totalChunks} for upload ${uploadId} (${chunkBytes} bytes, total: ${upload.bytesReceived}/${upload.fileSize})${binaryData ? ' [binary]' : ' [base64]'}`)
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
// Calculate progress based on actual bytes received (supports adaptive chunking)
|
|
@@ -213,7 +221,12 @@ export class FileUploadManager {
|
|
|
213
221
|
for (let i = 0; i < upload.totalChunks; i++) {
|
|
214
222
|
const chunkData = upload.receivedChunks.get(i)
|
|
215
223
|
if (chunkData) {
|
|
216
|
-
|
|
224
|
+
// Handle both Buffer (binary protocol) and string (base64 JSON protocol)
|
|
225
|
+
if (Buffer.isBuffer(chunkData)) {
|
|
226
|
+
chunks.push(chunkData)
|
|
227
|
+
} else {
|
|
228
|
+
chunks.push(Buffer.from(chunkData, 'base64'))
|
|
229
|
+
}
|
|
217
230
|
}
|
|
218
231
|
}
|
|
219
232
|
|
|
@@ -266,4 +279,4 @@ export class FileUploadManager {
|
|
|
266
279
|
}
|
|
267
280
|
|
|
268
281
|
// Global instance
|
|
269
|
-
export const fileUploadManager = new FileUploadManager()
|
|
282
|
+
export const fileUploadManager = new FileUploadManager()
|
|
@@ -136,7 +136,7 @@ export class LiveComponentPerformanceMonitor extends EventEmitter {
|
|
|
136
136
|
private alerts = new Map<string, PerformanceAlert[]>() // componentId -> alerts
|
|
137
137
|
private suggestions = new Map<string, OptimizationSuggestion[]>()
|
|
138
138
|
private config: MonitoringConfig
|
|
139
|
-
private dashboardUpdateInterval
|
|
139
|
+
private dashboardUpdateInterval!: NodeJS.Timeout
|
|
140
140
|
private alertCooldowns = new Map<string, number>() // alertType -> lastAlertTime
|
|
141
141
|
private performanceObserver?: any
|
|
142
142
|
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// 🔥 FluxStack Live Room Manager - Gerencia salas para LiveComponents
|
|
2
|
+
|
|
3
|
+
import { roomEvents } from './RoomEventBus'
|
|
4
|
+
import type { FluxStackWebSocket } from '@core/types/types'
|
|
5
|
+
|
|
6
|
+
export interface RoomMessage {
|
|
7
|
+
type: 'ROOM_JOIN' | 'ROOM_LEAVE' | 'ROOM_EMIT' | 'ROOM_STATE_SET' | 'ROOM_STATE_GET'
|
|
8
|
+
componentId: string
|
|
9
|
+
roomId: string
|
|
10
|
+
event?: string
|
|
11
|
+
data?: any
|
|
12
|
+
requestId?: string
|
|
13
|
+
timestamp: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RoomMember {
|
|
17
|
+
componentId: string
|
|
18
|
+
ws: FluxStackWebSocket
|
|
19
|
+
joinedAt: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Room<TState = any> {
|
|
23
|
+
id: string
|
|
24
|
+
state: TState
|
|
25
|
+
members: Map<string, RoomMember>
|
|
26
|
+
createdAt: number
|
|
27
|
+
lastActivity: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class LiveRoomManager {
|
|
31
|
+
private rooms = new Map<string, Room>()
|
|
32
|
+
private componentRooms = new Map<string, Set<string>>() // componentId -> roomIds
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Componente entra em uma sala
|
|
36
|
+
*/
|
|
37
|
+
joinRoom<TState = any>(componentId: string, roomId: string, ws: FluxStackWebSocket, initialState?: TState): { state: TState } {
|
|
38
|
+
// Criar sala se não existir
|
|
39
|
+
if (!this.rooms.has(roomId)) {
|
|
40
|
+
this.rooms.set(roomId, {
|
|
41
|
+
id: roomId,
|
|
42
|
+
state: initialState || {},
|
|
43
|
+
members: new Map(),
|
|
44
|
+
createdAt: Date.now(),
|
|
45
|
+
lastActivity: Date.now()
|
|
46
|
+
})
|
|
47
|
+
console.log(`🏠 Room '${roomId}' created`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const room = this.rooms.get(roomId)!
|
|
51
|
+
|
|
52
|
+
// Adicionar membro
|
|
53
|
+
room.members.set(componentId, {
|
|
54
|
+
componentId,
|
|
55
|
+
ws,
|
|
56
|
+
joinedAt: Date.now()
|
|
57
|
+
})
|
|
58
|
+
room.lastActivity = Date.now()
|
|
59
|
+
|
|
60
|
+
// Rastrear salas do componente
|
|
61
|
+
if (!this.componentRooms.has(componentId)) {
|
|
62
|
+
this.componentRooms.set(componentId, new Set())
|
|
63
|
+
}
|
|
64
|
+
this.componentRooms.get(componentId)!.add(roomId)
|
|
65
|
+
|
|
66
|
+
console.log(`👋 Component '${componentId}' joined room '${roomId}' (${room.members.size} members)`)
|
|
67
|
+
|
|
68
|
+
// Notificar outros membros
|
|
69
|
+
this.broadcastToRoom(roomId, {
|
|
70
|
+
type: 'ROOM_SYSTEM',
|
|
71
|
+
componentId,
|
|
72
|
+
roomId,
|
|
73
|
+
event: '$sub:join',
|
|
74
|
+
data: {
|
|
75
|
+
subscriberId: componentId,
|
|
76
|
+
count: room.members.size
|
|
77
|
+
},
|
|
78
|
+
timestamp: Date.now()
|
|
79
|
+
}, componentId)
|
|
80
|
+
|
|
81
|
+
return { state: room.state }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Componente sai de uma sala
|
|
86
|
+
*/
|
|
87
|
+
leaveRoom(componentId: string, roomId: string): void {
|
|
88
|
+
const room = this.rooms.get(roomId)
|
|
89
|
+
if (!room) return
|
|
90
|
+
|
|
91
|
+
// Remover membro
|
|
92
|
+
room.members.delete(componentId)
|
|
93
|
+
room.lastActivity = Date.now()
|
|
94
|
+
|
|
95
|
+
// Remover do rastreamento
|
|
96
|
+
this.componentRooms.get(componentId)?.delete(roomId)
|
|
97
|
+
|
|
98
|
+
console.log(`🚶 Component '${componentId}' left room '${roomId}' (${room.members.size} members)`)
|
|
99
|
+
|
|
100
|
+
// Notificar outros membros
|
|
101
|
+
this.broadcastToRoom(roomId, {
|
|
102
|
+
type: 'ROOM_SYSTEM',
|
|
103
|
+
componentId,
|
|
104
|
+
roomId,
|
|
105
|
+
event: '$sub:leave',
|
|
106
|
+
data: {
|
|
107
|
+
subscriberId: componentId,
|
|
108
|
+
count: room.members.size
|
|
109
|
+
},
|
|
110
|
+
timestamp: Date.now()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Cleanup sala vazia após delay
|
|
114
|
+
if (room.members.size === 0) {
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
const currentRoom = this.rooms.get(roomId)
|
|
117
|
+
if (currentRoom && currentRoom.members.size === 0) {
|
|
118
|
+
this.rooms.delete(roomId)
|
|
119
|
+
console.log(`🗑️ Room '${roomId}' destroyed (empty)`)
|
|
120
|
+
}
|
|
121
|
+
}, 5 * 60 * 1000) // 5 minutos
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Componente desconecta - sai de todas as salas
|
|
127
|
+
*/
|
|
128
|
+
cleanupComponent(componentId: string): void {
|
|
129
|
+
const rooms = this.componentRooms.get(componentId)
|
|
130
|
+
if (!rooms) return
|
|
131
|
+
|
|
132
|
+
for (const roomId of rooms) {
|
|
133
|
+
this.leaveRoom(componentId, roomId)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.componentRooms.delete(componentId)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Emitir evento para todos na sala
|
|
141
|
+
* - Envia via WebSocket para frontends dos outros membros
|
|
142
|
+
* - Também dispara eventos no RoomEventBus para handlers server-side
|
|
143
|
+
*/
|
|
144
|
+
emitToRoom(roomId: string, event: string, data: any, excludeComponentId?: string): number {
|
|
145
|
+
const room = this.rooms.get(roomId)
|
|
146
|
+
if (!room) return 0
|
|
147
|
+
|
|
148
|
+
room.lastActivity = Date.now()
|
|
149
|
+
|
|
150
|
+
// 1. Emitir no RoomEventBus para handlers server-side (outros LiveComponents)
|
|
151
|
+
// Isso permite que componentes do servidor reajam a eventos de outros componentes
|
|
152
|
+
// Usa 'room' como tipo genérico (mesmo usado em $room.on)
|
|
153
|
+
roomEvents.emit('room', roomId, event, data, excludeComponentId)
|
|
154
|
+
|
|
155
|
+
// 2. Broadcast via WebSocket para frontends
|
|
156
|
+
return this.broadcastToRoom(roomId, {
|
|
157
|
+
type: 'ROOM_EVENT',
|
|
158
|
+
componentId: '',
|
|
159
|
+
roomId,
|
|
160
|
+
event,
|
|
161
|
+
data,
|
|
162
|
+
timestamp: Date.now()
|
|
163
|
+
}, excludeComponentId)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Atualizar estado da sala
|
|
168
|
+
*/
|
|
169
|
+
setRoomState(roomId: string, updates: any, excludeComponentId?: string): void {
|
|
170
|
+
const room = this.rooms.get(roomId)
|
|
171
|
+
if (!room) return
|
|
172
|
+
|
|
173
|
+
// Merge estado
|
|
174
|
+
room.state = { ...room.state, ...updates }
|
|
175
|
+
room.lastActivity = Date.now()
|
|
176
|
+
|
|
177
|
+
// Notificar todos os membros
|
|
178
|
+
this.broadcastToRoom(roomId, {
|
|
179
|
+
type: 'ROOM_STATE',
|
|
180
|
+
componentId: '',
|
|
181
|
+
roomId,
|
|
182
|
+
event: '$state:update',
|
|
183
|
+
data: { state: updates },
|
|
184
|
+
timestamp: Date.now()
|
|
185
|
+
}, excludeComponentId)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Obter estado da sala
|
|
190
|
+
*/
|
|
191
|
+
getRoomState<TState = any>(roomId: string): TState {
|
|
192
|
+
return (this.rooms.get(roomId)?.state || {}) as TState
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Broadcast para todos os membros da sala
|
|
197
|
+
*/
|
|
198
|
+
private broadcastToRoom(roomId: string, message: any, excludeComponentId?: string): number {
|
|
199
|
+
const room = this.rooms.get(roomId)
|
|
200
|
+
if (!room) return 0
|
|
201
|
+
|
|
202
|
+
let sent = 0
|
|
203
|
+
for (const [componentId, member] of room.members) {
|
|
204
|
+
if (excludeComponentId && componentId === excludeComponentId) continue
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
if (member.ws && member.ws.readyState === 1) {
|
|
208
|
+
member.ws.send(JSON.stringify({
|
|
209
|
+
...message,
|
|
210
|
+
componentId
|
|
211
|
+
}))
|
|
212
|
+
sent++
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error(`Failed to send to ${componentId}:`, error)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return sent
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Verificar se componente está em uma sala
|
|
224
|
+
*/
|
|
225
|
+
isInRoom(componentId: string, roomId: string): boolean {
|
|
226
|
+
return this.rooms.get(roomId)?.members.has(componentId) ?? false
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Obter salas de um componente
|
|
231
|
+
*/
|
|
232
|
+
getComponentRooms(componentId: string): string[] {
|
|
233
|
+
return Array.from(this.componentRooms.get(componentId) || [])
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Estatísticas
|
|
238
|
+
*/
|
|
239
|
+
getStats(): {
|
|
240
|
+
totalRooms: number
|
|
241
|
+
rooms: Record<string, { members: number; createdAt: number; lastActivity: number }>
|
|
242
|
+
} {
|
|
243
|
+
const rooms: Record<string, { members: number; createdAt: number; lastActivity: number }> = {}
|
|
244
|
+
|
|
245
|
+
for (const [id, room] of this.rooms) {
|
|
246
|
+
rooms[id] = {
|
|
247
|
+
members: room.members.size,
|
|
248
|
+
createdAt: room.createdAt,
|
|
249
|
+
lastActivity: room.lastActivity
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
totalRooms: this.rooms.size,
|
|
255
|
+
rooms
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export const liveRoomManager = new LiveRoomManager()
|
|
261
|
+
export type { Room, RoomMember }
|