create-fluxstack 1.10.1 → 1.12.0
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 +242 -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 +285 -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
package/core/types/plugin.ts
CHANGED
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin system types
|
|
3
|
-
* Comprehensive type definitions for the plugin system
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// Import namespace for type alias
|
|
7
|
-
import type { FluxStack } from "../plugins/types"
|
|
8
|
-
|
|
9
|
-
// Re-export plugin types
|
|
10
|
-
export type {
|
|
11
|
-
FluxStack,
|
|
12
|
-
PluginContext,
|
|
13
|
-
PluginUtils,
|
|
14
|
-
RequestContext,
|
|
15
|
-
ResponseContext,
|
|
16
|
-
ErrorContext,
|
|
17
|
-
BuildContext,
|
|
18
|
-
ConfigLoadContext,
|
|
19
|
-
RouteContext,
|
|
20
|
-
ValidationContext,
|
|
21
|
-
TransformContext,
|
|
22
|
-
BuildAssetContext,
|
|
23
|
-
BuildErrorContext,
|
|
24
|
-
PluginEventContext
|
|
25
|
-
} from "../plugins/types"
|
|
26
|
-
|
|
27
|
-
// Export Plugin as a standalone type for convenience
|
|
28
|
-
export type Plugin = FluxStack.Plugin
|
|
29
|
-
|
|
30
|
-
// Additional plugin-related types
|
|
31
|
-
export interface PluginManifest {
|
|
32
|
-
name: string
|
|
33
|
-
version: string
|
|
34
|
-
description: string
|
|
35
|
-
author: string
|
|
36
|
-
license: string
|
|
37
|
-
homepage?: string
|
|
38
|
-
repository?: string
|
|
39
|
-
keywords: string[]
|
|
40
|
-
dependencies: Record<string, string>
|
|
41
|
-
peerDependencies?: Record<string, string>
|
|
42
|
-
fluxstack: {
|
|
43
|
-
version: string
|
|
44
|
-
hooks: string[]
|
|
45
|
-
config?: any
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface PluginLoadResult {
|
|
50
|
-
success: boolean
|
|
51
|
-
plugin?: Plugin
|
|
52
|
-
error?: string
|
|
53
|
-
warnings?: string[]
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface PluginRegistryState {
|
|
57
|
-
plugins: Map<string, Plugin>
|
|
58
|
-
loadOrder: string[]
|
|
59
|
-
dependencies: Map<string, string[]>
|
|
60
|
-
conflicts: string[]
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface PluginHookResult {
|
|
64
|
-
success: boolean
|
|
65
|
-
error?: Error
|
|
66
|
-
duration: number
|
|
67
|
-
plugin: string
|
|
68
|
-
hook: string
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface PluginMetrics {
|
|
72
|
-
loadTime: number
|
|
73
|
-
setupTime: number
|
|
74
|
-
hookExecutions: Map<string, number>
|
|
75
|
-
errors: number
|
|
76
|
-
warnings: number
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export type PluginHook =
|
|
80
|
-
| 'setup'
|
|
81
|
-
| 'onServerStart'
|
|
82
|
-
| 'onServerStop'
|
|
83
|
-
| 'onRequest'
|
|
84
|
-
| 'onResponse'
|
|
85
|
-
| 'onError'
|
|
86
|
-
|
|
87
|
-
export type PluginPriority = 'highest' | 'high' | 'normal' | 'low' | 'lowest' | number
|
|
88
|
-
|
|
89
|
-
export interface PluginConfigSchema {
|
|
90
|
-
type: 'object'
|
|
91
|
-
properties: Record<string, any>
|
|
92
|
-
required?: string[]
|
|
93
|
-
additionalProperties?: boolean
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface PluginDiscoveryOptions {
|
|
97
|
-
directories?: string[]
|
|
98
|
-
patterns?: string[]
|
|
99
|
-
includeBuiltIn?: boolean
|
|
100
|
-
includeExternal?: boolean
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export interface PluginInstallOptions {
|
|
104
|
-
version?: string
|
|
105
|
-
registry?: string
|
|
106
|
-
force?: boolean
|
|
107
|
-
dev?: boolean
|
|
1
|
+
/**
|
|
2
|
+
* Plugin system types
|
|
3
|
+
* Comprehensive type definitions for the plugin system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import namespace for type alias
|
|
7
|
+
import type { FluxStack } from "../plugins/types"
|
|
8
|
+
|
|
9
|
+
// Re-export plugin types
|
|
10
|
+
export type {
|
|
11
|
+
FluxStack,
|
|
12
|
+
PluginContext,
|
|
13
|
+
PluginUtils,
|
|
14
|
+
RequestContext,
|
|
15
|
+
ResponseContext,
|
|
16
|
+
ErrorContext,
|
|
17
|
+
BuildContext,
|
|
18
|
+
ConfigLoadContext,
|
|
19
|
+
RouteContext,
|
|
20
|
+
ValidationContext,
|
|
21
|
+
TransformContext,
|
|
22
|
+
BuildAssetContext,
|
|
23
|
+
BuildErrorContext,
|
|
24
|
+
PluginEventContext
|
|
25
|
+
} from "../plugins/types"
|
|
26
|
+
|
|
27
|
+
// Export Plugin as a standalone type for convenience
|
|
28
|
+
export type Plugin = FluxStack.Plugin
|
|
29
|
+
|
|
30
|
+
// Additional plugin-related types
|
|
31
|
+
export interface PluginManifest {
|
|
32
|
+
name: string
|
|
33
|
+
version: string
|
|
34
|
+
description: string
|
|
35
|
+
author: string
|
|
36
|
+
license: string
|
|
37
|
+
homepage?: string
|
|
38
|
+
repository?: string
|
|
39
|
+
keywords: string[]
|
|
40
|
+
dependencies: Record<string, string>
|
|
41
|
+
peerDependencies?: Record<string, string>
|
|
42
|
+
fluxstack: {
|
|
43
|
+
version: string
|
|
44
|
+
hooks: string[]
|
|
45
|
+
config?: any
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface PluginLoadResult {
|
|
50
|
+
success: boolean
|
|
51
|
+
plugin?: Plugin
|
|
52
|
+
error?: string
|
|
53
|
+
warnings?: string[]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PluginRegistryState {
|
|
57
|
+
plugins: Map<string, Plugin>
|
|
58
|
+
loadOrder: string[]
|
|
59
|
+
dependencies: Map<string, string[]>
|
|
60
|
+
conflicts: string[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface PluginHookResult {
|
|
64
|
+
success: boolean
|
|
65
|
+
error?: Error
|
|
66
|
+
duration: number
|
|
67
|
+
plugin: string
|
|
68
|
+
hook: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface PluginMetrics {
|
|
72
|
+
loadTime: number
|
|
73
|
+
setupTime: number
|
|
74
|
+
hookExecutions: Map<string, number>
|
|
75
|
+
errors: number
|
|
76
|
+
warnings: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type PluginHook =
|
|
80
|
+
| 'setup'
|
|
81
|
+
| 'onServerStart'
|
|
82
|
+
| 'onServerStop'
|
|
83
|
+
| 'onRequest'
|
|
84
|
+
| 'onResponse'
|
|
85
|
+
| 'onError'
|
|
86
|
+
|
|
87
|
+
export type PluginPriority = 'highest' | 'high' | 'normal' | 'low' | 'lowest' | number
|
|
88
|
+
|
|
89
|
+
export interface PluginConfigSchema {
|
|
90
|
+
type: 'object'
|
|
91
|
+
properties: Record<string, any>
|
|
92
|
+
required?: string[]
|
|
93
|
+
additionalProperties?: boolean
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface PluginDiscoveryOptions {
|
|
97
|
+
directories?: string[]
|
|
98
|
+
patterns?: string[]
|
|
99
|
+
includeBuiltIn?: boolean
|
|
100
|
+
includeExternal?: boolean
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface PluginInstallOptions {
|
|
104
|
+
version?: string
|
|
105
|
+
registry?: string
|
|
106
|
+
force?: boolean
|
|
107
|
+
dev?: boolean
|
|
108
108
|
}
|
package/core/types/types.ts
CHANGED
|
@@ -1,11 +1,56 @@
|
|
|
1
1
|
// 🔥 FluxStack Live Components - Shared Types
|
|
2
2
|
|
|
3
|
+
import { roomEvents } from '@core/server/live/RoomEventBus'
|
|
4
|
+
import { liveRoomManager } from '@core/server/live/LiveRoomManager'
|
|
5
|
+
import type { ServerWebSocket } from 'bun'
|
|
6
|
+
|
|
7
|
+
// ============================================
|
|
8
|
+
// 🔌 WebSocket Types for Server-Side
|
|
9
|
+
// ============================================
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* WebSocket data stored on each connection
|
|
13
|
+
* This is attached to ws.data by the WebSocket plugin
|
|
14
|
+
*/
|
|
15
|
+
export interface FluxStackWSData {
|
|
16
|
+
connectionId: string
|
|
17
|
+
components: Map<string, LiveComponent>
|
|
18
|
+
subscriptions: Set<string>
|
|
19
|
+
connectedAt: Date
|
|
20
|
+
userId?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Type-safe WebSocket interface for FluxStack Live Components
|
|
25
|
+
* Compatible with both Elysia's ElysiaWS and Bun's ServerWebSocket
|
|
26
|
+
*/
|
|
27
|
+
export interface FluxStackWebSocket {
|
|
28
|
+
/** Send data to the client */
|
|
29
|
+
send(data: string | BufferSource, compress?: boolean): number
|
|
30
|
+
/** Close the connection */
|
|
31
|
+
close(code?: number, reason?: string): void
|
|
32
|
+
/** Connection data storage */
|
|
33
|
+
data: FluxStackWSData
|
|
34
|
+
/** Remote address of the client */
|
|
35
|
+
readonly remoteAddress: string
|
|
36
|
+
/** Current ready state */
|
|
37
|
+
readonly readyState: 0 | 1 | 2 | 3
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Raw ServerWebSocket from Bun with FluxStack data
|
|
42
|
+
* Use this when you need access to all Bun WebSocket methods
|
|
43
|
+
*/
|
|
44
|
+
export type FluxStackServerWebSocket = ServerWebSocket<FluxStackWSData>
|
|
45
|
+
|
|
3
46
|
export interface LiveMessage {
|
|
4
47
|
type: 'COMPONENT_MOUNT' | 'COMPONENT_UNMOUNT' |
|
|
5
48
|
'COMPONENT_REHYDRATE' | 'COMPONENT_ACTION' | 'CALL_ACTION' |
|
|
6
49
|
'ACTION_RESPONSE' | 'PROPERTY_UPDATE' | 'STATE_UPDATE' | 'STATE_REHYDRATED' |
|
|
7
50
|
'ERROR' | 'BROADCAST' | 'FILE_UPLOAD_START' | 'FILE_UPLOAD_CHUNK' | 'FILE_UPLOAD_COMPLETE' |
|
|
8
|
-
'COMPONENT_PING' | 'COMPONENT_PONG'
|
|
51
|
+
'COMPONENT_PING' | 'COMPONENT_PONG' |
|
|
52
|
+
// Room system messages
|
|
53
|
+
'ROOM_JOIN' | 'ROOM_LEAVE' | 'ROOM_EMIT' | 'ROOM_STATE_SET' | 'ROOM_STATE_GET'
|
|
9
54
|
componentId: string
|
|
10
55
|
action?: string
|
|
11
56
|
property?: string
|
|
@@ -34,6 +79,9 @@ export interface LiveComponentInstance<TState = ComponentState, TActions = Recor
|
|
|
34
79
|
room?: string
|
|
35
80
|
}
|
|
36
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @deprecated Use FluxStackWSData instead
|
|
84
|
+
*/
|
|
37
85
|
export interface WebSocketData {
|
|
38
86
|
components: Map<string, any>
|
|
39
87
|
userId?: string
|
|
@@ -43,7 +91,7 @@ export interface WebSocketData {
|
|
|
43
91
|
export interface ComponentDefinition<TState = ComponentState> {
|
|
44
92
|
name: string
|
|
45
93
|
initialState: TState
|
|
46
|
-
component: new (initialState: TState, ws:
|
|
94
|
+
component: new (initialState: TState, ws: FluxStackWebSocket, options?: { room?: string; userId?: string }) => LiveComponent<TState>
|
|
47
95
|
}
|
|
48
96
|
|
|
49
97
|
export interface BroadcastMessage {
|
|
@@ -69,7 +117,9 @@ export interface WebSocketMessage {
|
|
|
69
117
|
}
|
|
70
118
|
|
|
71
119
|
export interface WebSocketResponse {
|
|
72
|
-
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' | 'COMPONENT_PONG'
|
|
120
|
+
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' | 'COMPONENT_PONG' |
|
|
121
|
+
// Room system responses
|
|
122
|
+
'ROOM_EVENT' | 'ROOM_STATE' | 'ROOM_SYSTEM' | 'ROOM_JOINED' | 'ROOM_LEFT'
|
|
73
123
|
originalType?: string
|
|
74
124
|
componentId?: string
|
|
75
125
|
success?: boolean
|
|
@@ -135,27 +185,214 @@ export interface HybridComponentOptions {
|
|
|
135
185
|
onStateChange?: (newState: any, oldState: any) => void
|
|
136
186
|
}
|
|
137
187
|
|
|
188
|
+
// Interface para handle de sala no servidor
|
|
189
|
+
export interface ServerRoomHandle<TState = any, TEvents extends Record<string, any> = Record<string, any>> {
|
|
190
|
+
readonly id: string
|
|
191
|
+
readonly state: TState
|
|
192
|
+
join: (initialState?: TState) => void
|
|
193
|
+
leave: () => void
|
|
194
|
+
emit: <K extends keyof TEvents>(event: K, data: TEvents[K]) => number
|
|
195
|
+
on: <K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void) => () => void
|
|
196
|
+
setState: (updates: Partial<TState>) => void
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Proxy para $room no servidor
|
|
200
|
+
export interface ServerRoomProxy<TState = any, TEvents extends Record<string, any> = Record<string, any>> {
|
|
201
|
+
(roomId: string): ServerRoomHandle<TState, TEvents>
|
|
202
|
+
readonly id: string | undefined
|
|
203
|
+
readonly state: TState
|
|
204
|
+
join: (initialState?: TState) => void
|
|
205
|
+
leave: () => void
|
|
206
|
+
emit: <K extends keyof TEvents>(event: K, data: TEvents[K]) => number
|
|
207
|
+
on: <K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void) => () => void
|
|
208
|
+
setState: (updates: Partial<TState>) => void
|
|
209
|
+
}
|
|
210
|
+
|
|
138
211
|
export abstract class LiveComponent<TState = ComponentState> {
|
|
212
|
+
/** Component name for registry lookup - must be defined in subclasses */
|
|
213
|
+
static componentName: string
|
|
214
|
+
/** Default state - must be defined in subclasses */
|
|
215
|
+
static defaultState: any
|
|
216
|
+
|
|
139
217
|
public readonly id: string
|
|
140
|
-
|
|
141
|
-
|
|
218
|
+
private _state: TState
|
|
219
|
+
public state: TState // Proxy wrapper
|
|
220
|
+
protected ws: FluxStackWebSocket
|
|
142
221
|
public room?: string
|
|
143
222
|
public userId?: string
|
|
144
223
|
public broadcastToRoom: (message: BroadcastMessage) => void = () => {} // Will be injected by registry
|
|
145
224
|
|
|
146
|
-
|
|
225
|
+
// Room event subscriptions (cleaned up on destroy)
|
|
226
|
+
private roomEventUnsubscribers: (() => void)[] = []
|
|
227
|
+
private joinedRooms: Set<string> = new Set()
|
|
228
|
+
|
|
229
|
+
// Room type for typed events (override in subclass)
|
|
230
|
+
protected roomType: string = 'default'
|
|
231
|
+
|
|
232
|
+
// Cached room handles
|
|
233
|
+
private roomHandles: Map<string, ServerRoomHandle> = new Map()
|
|
234
|
+
|
|
235
|
+
constructor(initialState: Partial<TState>, ws: FluxStackWebSocket, options?: { room?: string; userId?: string }) {
|
|
147
236
|
this.id = this.generateId()
|
|
148
|
-
|
|
237
|
+
// Merge defaultState with initialState - subclass defaultState takes precedence for missing fields
|
|
238
|
+
const ctor = this.constructor as typeof LiveComponent
|
|
239
|
+
this._state = { ...ctor.defaultState, ...initialState } as TState
|
|
240
|
+
|
|
241
|
+
// Create reactive proxy that auto-syncs on mutation
|
|
242
|
+
this.state = this.createStateProxy(this._state)
|
|
243
|
+
|
|
149
244
|
this.ws = ws
|
|
150
245
|
this.room = options?.room
|
|
151
246
|
this.userId = options?.userId
|
|
247
|
+
|
|
248
|
+
// Auto-join default room if specified
|
|
249
|
+
if (this.room) {
|
|
250
|
+
this.joinedRooms.add(this.room)
|
|
251
|
+
liveRoomManager.joinRoom(this.id, this.room, this.ws)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Create a Proxy that auto-emits STATE_UPDATE on any mutation
|
|
256
|
+
private createStateProxy(state: TState): TState {
|
|
257
|
+
const self = this
|
|
258
|
+
return new Proxy(state as object, {
|
|
259
|
+
set(target, prop, value) {
|
|
260
|
+
const oldValue = (target as any)[prop]
|
|
261
|
+
if (oldValue !== value) {
|
|
262
|
+
(target as any)[prop] = value
|
|
263
|
+
// Auto-sync to frontend
|
|
264
|
+
self.emit('STATE_UPDATE', { state: self._state })
|
|
265
|
+
}
|
|
266
|
+
return true
|
|
267
|
+
},
|
|
268
|
+
get(target, prop) {
|
|
269
|
+
return (target as any)[prop]
|
|
270
|
+
}
|
|
271
|
+
}) as TState
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ========================================
|
|
275
|
+
// 🔥 $room - Sistema de Salas Unificado
|
|
276
|
+
// ========================================
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Acessa uma sala específica ou a sala padrão
|
|
280
|
+
* @example
|
|
281
|
+
* // Sala padrão
|
|
282
|
+
* this.$room.emit('typing', { user: 'João' })
|
|
283
|
+
* this.$room.on('message:new', handler)
|
|
284
|
+
*
|
|
285
|
+
* // Outra sala
|
|
286
|
+
* this.$room('sala-vip').join()
|
|
287
|
+
* this.$room('sala-vip').emit('typing', { user: 'João' })
|
|
288
|
+
*/
|
|
289
|
+
public get $room(): ServerRoomProxy {
|
|
290
|
+
const self = this
|
|
291
|
+
|
|
292
|
+
const createHandle = (roomId: string): ServerRoomHandle => {
|
|
293
|
+
// Retornar handle cacheado
|
|
294
|
+
if (this.roomHandles.has(roomId)) {
|
|
295
|
+
return this.roomHandles.get(roomId)!
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const handle: ServerRoomHandle = {
|
|
299
|
+
get id() { return roomId },
|
|
300
|
+
get state() { return liveRoomManager.getRoomState(roomId) },
|
|
301
|
+
|
|
302
|
+
join: (initialState?: any) => {
|
|
303
|
+
if (self.joinedRooms.has(roomId)) return
|
|
304
|
+
self.joinedRooms.add(roomId)
|
|
305
|
+
liveRoomManager.joinRoom(self.id, roomId, self.ws, initialState)
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
leave: () => {
|
|
309
|
+
if (!self.joinedRooms.has(roomId)) return
|
|
310
|
+
self.joinedRooms.delete(roomId)
|
|
311
|
+
liveRoomManager.leaveRoom(self.id, roomId)
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
emit: (event: string, data: any): number => {
|
|
315
|
+
return liveRoomManager.emitToRoom(roomId, event, data, self.id)
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
on: (event: string, handler: (data: any) => void): (() => void) => {
|
|
319
|
+
// Usar 'room' como tipo genérico e roomId como identificador
|
|
320
|
+
// Isso permite que emitToRoom encontre os handlers corretamente
|
|
321
|
+
const unsubscribe = roomEvents.on(
|
|
322
|
+
'room', // Tipo genérico para todas as salas
|
|
323
|
+
roomId,
|
|
324
|
+
event,
|
|
325
|
+
self.id,
|
|
326
|
+
handler
|
|
327
|
+
)
|
|
328
|
+
self.roomEventUnsubscribers.push(unsubscribe)
|
|
329
|
+
return unsubscribe
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
setState: (updates: any) => {
|
|
333
|
+
liveRoomManager.setRoomState(roomId, updates, self.id)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
this.roomHandles.set(roomId, handle)
|
|
338
|
+
return handle
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Criar proxy que funciona como função e objeto
|
|
342
|
+
const proxyFn = ((roomId: string) => createHandle(roomId)) as ServerRoomProxy
|
|
343
|
+
|
|
344
|
+
const defaultHandle = this.room ? createHandle(this.room) : null
|
|
345
|
+
|
|
346
|
+
Object.defineProperties(proxyFn, {
|
|
347
|
+
id: { get: () => self.room },
|
|
348
|
+
state: { get: () => defaultHandle?.state ?? {} },
|
|
349
|
+
join: {
|
|
350
|
+
value: (initialState?: any) => {
|
|
351
|
+
if (!defaultHandle) throw new Error('No default room set')
|
|
352
|
+
defaultHandle.join(initialState)
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
leave: {
|
|
356
|
+
value: () => {
|
|
357
|
+
if (!defaultHandle) throw new Error('No default room set')
|
|
358
|
+
defaultHandle.leave()
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
emit: {
|
|
362
|
+
value: (event: string, data: any) => {
|
|
363
|
+
if (!defaultHandle) throw new Error('No default room set')
|
|
364
|
+
return defaultHandle.emit(event, data)
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
on: {
|
|
368
|
+
value: (event: string, handler: (data: any) => void) => {
|
|
369
|
+
if (!defaultHandle) throw new Error('No default room set')
|
|
370
|
+
return defaultHandle.on(event, handler)
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
setState: {
|
|
374
|
+
value: (updates: any) => {
|
|
375
|
+
if (!defaultHandle) throw new Error('No default room set')
|
|
376
|
+
defaultHandle.setState(updates)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
return proxyFn
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Lista de IDs das salas que este componente está participando
|
|
386
|
+
*/
|
|
387
|
+
public get $rooms(): string[] {
|
|
388
|
+
return Array.from(this.joinedRooms)
|
|
152
389
|
}
|
|
153
390
|
|
|
154
|
-
// State management
|
|
391
|
+
// State management (batch update - single emit)
|
|
155
392
|
public setState(updates: Partial<TState> | ((prev: TState) => Partial<TState>)) {
|
|
156
|
-
const newUpdates = typeof updates === 'function' ? updates(this.
|
|
157
|
-
this.
|
|
158
|
-
this.emit('STATE_UPDATE', { state: this.
|
|
393
|
+
const newUpdates = typeof updates === 'function' ? updates(this._state) : updates
|
|
394
|
+
Object.assign(this._state as object, newUpdates)
|
|
395
|
+
this.emit('STATE_UPDATE', { state: this._state })
|
|
159
396
|
}
|
|
160
397
|
|
|
161
398
|
// Generic setValue action - set any state key with type safety
|
|
@@ -204,8 +441,13 @@ export abstract class LiveComponent<TState = ComponentState> {
|
|
|
204
441
|
}
|
|
205
442
|
}
|
|
206
443
|
|
|
207
|
-
// Broadcast to all clients in room
|
|
444
|
+
// Broadcast to all clients in room (via WebSocket)
|
|
208
445
|
protected broadcast(type: string, payload: any, excludeCurrentUser = false) {
|
|
446
|
+
if (!this.room) {
|
|
447
|
+
console.warn(`⚠️ [${this.id}] Cannot broadcast '${type}' - no room set`)
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
|
|
209
451
|
const message: BroadcastMessage = {
|
|
210
452
|
type,
|
|
211
453
|
payload,
|
|
@@ -213,10 +455,84 @@ export abstract class LiveComponent<TState = ComponentState> {
|
|
|
213
455
|
excludeUser: excludeCurrentUser ? this.userId : undefined
|
|
214
456
|
}
|
|
215
457
|
|
|
458
|
+
console.log(`📤 [${this.id}] Broadcasting '${type}' to room '${this.room}'`)
|
|
459
|
+
|
|
216
460
|
// This will be handled by the registry
|
|
217
461
|
this.broadcastToRoom(message)
|
|
218
462
|
}
|
|
219
463
|
|
|
464
|
+
// ========================================
|
|
465
|
+
// 🔥 Room Events - Internal Server Events
|
|
466
|
+
// ========================================
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Emite um evento para todos os componentes da sala (server-side)
|
|
470
|
+
* Cada componente inscrito pode reagir e atualizar seu próprio cliente
|
|
471
|
+
*
|
|
472
|
+
* @param event - Nome do evento
|
|
473
|
+
* @param data - Dados do evento
|
|
474
|
+
* @param notifySelf - Se true, este componente também recebe (default: false)
|
|
475
|
+
*/
|
|
476
|
+
protected emitRoomEvent(event: string, data: any, notifySelf = false): number {
|
|
477
|
+
if (!this.room) {
|
|
478
|
+
console.warn(`⚠️ [${this.id}] Cannot emit room event '${event}' - no room set`)
|
|
479
|
+
return 0
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const excludeId = notifySelf ? undefined : this.id
|
|
483
|
+
const notified = roomEvents.emit(this.roomType, this.room, event, data, excludeId)
|
|
484
|
+
|
|
485
|
+
console.log(`📡 [${this.id}] Room event '${event}' → ${notified} components`)
|
|
486
|
+
return notified
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Inscreve este componente em um evento da sala
|
|
491
|
+
* Handler é chamado quando outro componente emite o evento
|
|
492
|
+
*
|
|
493
|
+
* @param event - Nome do evento para escutar
|
|
494
|
+
* @param handler - Função chamada quando evento é recebido
|
|
495
|
+
*/
|
|
496
|
+
protected onRoomEvent<T = any>(event: string, handler: (data: T) => void): void {
|
|
497
|
+
if (!this.room) {
|
|
498
|
+
console.warn(`⚠️ [${this.id}] Cannot subscribe to room event '${event}' - no room set`)
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const unsubscribe = roomEvents.on(
|
|
503
|
+
this.roomType,
|
|
504
|
+
this.room,
|
|
505
|
+
event,
|
|
506
|
+
this.id,
|
|
507
|
+
handler
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
// Guardar para cleanup no destroy
|
|
511
|
+
this.roomEventUnsubscribers.push(unsubscribe)
|
|
512
|
+
|
|
513
|
+
console.log(`👂 [${this.id}] Subscribed to room event '${event}'`)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Helper: Emite evento E atualiza estado local + envia pro cliente
|
|
518
|
+
* Útil para o componente que origina a ação
|
|
519
|
+
*
|
|
520
|
+
* @param event - Nome do evento
|
|
521
|
+
* @param data - Dados do evento
|
|
522
|
+
* @param stateUpdates - Atualizações de estado para aplicar localmente
|
|
523
|
+
*/
|
|
524
|
+
protected emitRoomEventWithState(
|
|
525
|
+
event: string,
|
|
526
|
+
data: any,
|
|
527
|
+
stateUpdates: Partial<TState>
|
|
528
|
+
): number {
|
|
529
|
+
// 1. Atualiza estado local (envia pro cliente deste componente)
|
|
530
|
+
this.setState(stateUpdates)
|
|
531
|
+
|
|
532
|
+
// 2. Emite evento para outros componentes da sala
|
|
533
|
+
return this.emitRoomEvent(event, data, false)
|
|
534
|
+
}
|
|
535
|
+
|
|
220
536
|
// Subscribe to room for multi-user features
|
|
221
537
|
protected async subscribeToRoom(roomId: string) {
|
|
222
538
|
this.room = roomId
|
|
@@ -236,6 +552,19 @@ export abstract class LiveComponent<TState = ComponentState> {
|
|
|
236
552
|
|
|
237
553
|
// Cleanup when component is destroyed
|
|
238
554
|
public destroy() {
|
|
555
|
+
// Limpa todas as inscrições de room events
|
|
556
|
+
for (const unsubscribe of this.roomEventUnsubscribers) {
|
|
557
|
+
unsubscribe()
|
|
558
|
+
}
|
|
559
|
+
this.roomEventUnsubscribers = []
|
|
560
|
+
|
|
561
|
+
// Sai de todas as salas
|
|
562
|
+
for (const roomId of this.joinedRooms) {
|
|
563
|
+
liveRoomManager.leaveRoom(this.id, roomId)
|
|
564
|
+
}
|
|
565
|
+
this.joinedRooms.clear()
|
|
566
|
+
this.roomHandles.clear()
|
|
567
|
+
|
|
239
568
|
this.unsubscribeFromRoom()
|
|
240
569
|
// Override in subclasses for custom cleanup
|
|
241
570
|
}
|
|
@@ -407,11 +736,21 @@ export interface FileUploadChunkMessage {
|
|
|
407
736
|
uploadId: string
|
|
408
737
|
chunkIndex: number
|
|
409
738
|
totalChunks: number
|
|
410
|
-
data: string // Base64
|
|
739
|
+
data: string | Buffer // Base64 string (JSON) or Buffer (binary protocol)
|
|
411
740
|
hash?: string
|
|
412
741
|
requestId?: string
|
|
413
742
|
}
|
|
414
743
|
|
|
744
|
+
// Binary protocol header for chunk uploads
|
|
745
|
+
export interface BinaryChunkHeader {
|
|
746
|
+
type: 'FILE_UPLOAD_CHUNK'
|
|
747
|
+
componentId: string
|
|
748
|
+
uploadId: string
|
|
749
|
+
chunkIndex: number
|
|
750
|
+
totalChunks: number
|
|
751
|
+
requestId?: string
|
|
752
|
+
}
|
|
753
|
+
|
|
415
754
|
export interface FileUploadCompleteMessage {
|
|
416
755
|
type: 'FILE_UPLOAD_COMPLETE'
|
|
417
756
|
componentId: string
|
|
@@ -452,7 +791,7 @@ export interface ActiveUpload {
|
|
|
452
791
|
fileType: string
|
|
453
792
|
fileSize: number
|
|
454
793
|
totalChunks: number
|
|
455
|
-
receivedChunks: Map<number, string>
|
|
794
|
+
receivedChunks: Map<number, string | Buffer> // Base64 string or raw Buffer (binary protocol)
|
|
456
795
|
bytesReceived: number // Track actual bytes received for adaptive chunking
|
|
457
796
|
startTime: number
|
|
458
797
|
lastChunkTime: number
|