create-fluxstack 1.9.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.
Files changed (259) hide show
  1. package/.dockerignore +1 -2
  2. package/Dockerfile +8 -8
  3. package/LIVE_COMPONENTS_REVIEW.md +781 -0
  4. package/LLMD/INDEX.md +64 -0
  5. package/LLMD/MAINTENANCE.md +197 -0
  6. package/LLMD/MIGRATION.md +156 -0
  7. package/LLMD/config/.gitkeep +1 -0
  8. package/LLMD/config/declarative-system.md +268 -0
  9. package/LLMD/config/environment-vars.md +327 -0
  10. package/LLMD/config/runtime-reload.md +401 -0
  11. package/LLMD/core/.gitkeep +1 -0
  12. package/LLMD/core/build-system.md +599 -0
  13. package/LLMD/core/framework-lifecycle.md +229 -0
  14. package/LLMD/core/plugin-system.md +451 -0
  15. package/LLMD/patterns/.gitkeep +1 -0
  16. package/LLMD/patterns/anti-patterns.md +297 -0
  17. package/LLMD/patterns/project-structure.md +264 -0
  18. package/LLMD/patterns/type-safety.md +440 -0
  19. package/LLMD/reference/.gitkeep +1 -0
  20. package/LLMD/reference/cli-commands.md +250 -0
  21. package/LLMD/reference/plugin-hooks.md +357 -0
  22. package/LLMD/reference/routing.md +39 -0
  23. package/LLMD/reference/troubleshooting.md +364 -0
  24. package/LLMD/resources/.gitkeep +1 -0
  25. package/LLMD/resources/controllers.md +465 -0
  26. package/LLMD/resources/live-components.md +703 -0
  27. package/LLMD/resources/live-rooms.md +482 -0
  28. package/LLMD/resources/live-upload.md +130 -0
  29. package/LLMD/resources/plugins-external.md +617 -0
  30. package/LLMD/resources/routes-eden.md +254 -0
  31. package/README.md +37 -17
  32. package/app/client/index.html +0 -1
  33. package/app/client/src/App.tsx +109 -156
  34. package/app/client/src/components/AppLayout.tsx +68 -0
  35. package/app/client/src/components/BackButton.tsx +13 -0
  36. package/app/client/src/components/DemoPage.tsx +20 -0
  37. package/app/client/src/components/LiveUploadWidget.tsx +204 -0
  38. package/app/client/src/lib/eden-api.ts +85 -65
  39. package/app/client/src/live/ChatDemo.tsx +107 -0
  40. package/app/client/src/live/CounterDemo.tsx +206 -0
  41. package/app/client/src/live/FormDemo.tsx +119 -0
  42. package/app/client/src/live/RoomChatDemo.tsx +242 -0
  43. package/app/client/src/live/UploadDemo.tsx +21 -0
  44. package/app/client/src/main.tsx +13 -10
  45. package/app/client/src/pages/ApiTestPage.tsx +108 -0
  46. package/app/client/src/pages/HomePage.tsx +76 -0
  47. package/app/client/src/vite-env.d.ts +1 -1
  48. package/app/server/app.ts +1 -4
  49. package/app/server/controllers/users.controller.ts +36 -44
  50. package/app/server/index.ts +24 -107
  51. package/app/server/live/LiveChat.ts +77 -0
  52. package/app/server/live/LiveCounter.ts +67 -0
  53. package/app/server/live/LiveForm.ts +63 -0
  54. package/app/server/live/LiveLocalCounter.ts +32 -0
  55. package/app/server/live/LiveRoomChat.ts +285 -0
  56. package/app/server/live/LiveUpload.ts +81 -0
  57. package/app/server/live/register-components.ts +19 -19
  58. package/app/server/routes/index.ts +3 -1
  59. package/app/server/routes/room.routes.ts +117 -0
  60. package/app/server/routes/users.routes.ts +35 -27
  61. package/app/shared/types/index.ts +14 -2
  62. package/config/app.config.ts +2 -62
  63. package/config/client.config.ts +2 -95
  64. package/config/database.config.ts +2 -99
  65. package/config/fluxstack.config.ts +25 -45
  66. package/config/index.ts +57 -38
  67. package/config/monitoring.config.ts +2 -114
  68. package/config/plugins.config.ts +2 -80
  69. package/config/server.config.ts +2 -68
  70. package/config/services.config.ts +2 -130
  71. package/config/system/app.config.ts +29 -0
  72. package/config/system/build.config.ts +49 -0
  73. package/config/system/client.config.ts +68 -0
  74. package/config/system/database.config.ts +17 -0
  75. package/config/system/fluxstack.config.ts +114 -0
  76. package/config/{logger.config.ts → system/logger.config.ts} +3 -1
  77. package/config/system/monitoring.config.ts +114 -0
  78. package/config/system/plugins.config.ts +84 -0
  79. package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
  80. package/config/system/server.config.ts +68 -0
  81. package/config/system/services.config.ts +46 -0
  82. package/config/{system.config.ts → system/system.config.ts} +1 -1
  83. package/core/build/bundler.ts +4 -1
  84. package/core/build/flux-plugins-generator.ts +325 -325
  85. package/core/build/index.ts +159 -27
  86. package/core/build/live-components-generator.ts +70 -3
  87. package/core/build/optimizer.ts +235 -235
  88. package/core/cli/command-registry.ts +6 -4
  89. package/core/cli/commands/build.ts +79 -0
  90. package/core/cli/commands/create.ts +54 -0
  91. package/core/cli/commands/dev.ts +101 -0
  92. package/core/cli/commands/help.ts +34 -0
  93. package/core/cli/commands/index.ts +34 -0
  94. package/core/cli/commands/make-plugin.ts +90 -0
  95. package/core/cli/commands/plugin-add.ts +197 -0
  96. package/core/cli/commands/plugin-deps.ts +2 -2
  97. package/core/cli/commands/plugin-list.ts +208 -0
  98. package/core/cli/commands/plugin-remove.ts +170 -0
  99. package/core/cli/generators/component.ts +769 -769
  100. package/core/cli/generators/controller.ts +1 -1
  101. package/core/cli/generators/index.ts +146 -146
  102. package/core/cli/generators/interactive.ts +227 -227
  103. package/core/cli/generators/plugin.ts +2 -2
  104. package/core/cli/generators/prompts.ts +82 -82
  105. package/core/cli/generators/route.ts +6 -6
  106. package/core/cli/generators/service.ts +2 -2
  107. package/core/cli/generators/template-engine.ts +4 -3
  108. package/core/cli/generators/types.ts +2 -2
  109. package/core/cli/generators/utils.ts +191 -191
  110. package/core/cli/index.ts +115 -558
  111. package/core/cli/plugin-discovery.ts +2 -2
  112. package/core/client/LiveComponentsProvider.tsx +63 -17
  113. package/core/client/api/eden.ts +183 -0
  114. package/core/client/api/index.ts +11 -0
  115. package/core/client/components/Live.tsx +104 -0
  116. package/core/client/fluxstack.ts +1 -9
  117. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -0
  118. package/core/client/hooks/state-validator.ts +1 -1
  119. package/core/client/hooks/useAuth.ts +48 -48
  120. package/core/client/hooks/useChunkedUpload.ts +170 -69
  121. package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
  122. package/core/client/hooks/useLiveComponent.ts +800 -0
  123. package/core/client/hooks/useLiveUpload.ts +71 -0
  124. package/core/client/hooks/useRoom.ts +409 -0
  125. package/core/client/hooks/useRoomProxy.ts +382 -0
  126. package/core/client/index.ts +18 -51
  127. package/core/client/standalone-entry.ts +8 -0
  128. package/core/client/standalone.ts +74 -53
  129. package/core/client/state/createStore.ts +192 -192
  130. package/core/client/state/index.ts +14 -14
  131. package/core/config/index.ts +70 -291
  132. package/core/config/schema.ts +42 -723
  133. package/core/framework/client.ts +131 -131
  134. package/core/framework/index.ts +7 -7
  135. package/core/framework/server.ts +227 -47
  136. package/core/framework/types.ts +2 -2
  137. package/core/index.ts +23 -4
  138. package/core/live/ComponentRegistry.ts +7 -3
  139. package/core/live/types.ts +77 -0
  140. package/core/plugins/built-in/index.ts +134 -131
  141. package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1074
  142. package/core/plugins/built-in/live-components/index.ts +1 -1
  143. package/core/plugins/built-in/monitoring/index.ts +111 -47
  144. package/core/plugins/built-in/static/index.ts +1 -1
  145. package/core/plugins/built-in/swagger/index.ts +68 -265
  146. package/core/plugins/built-in/vite/index.ts +94 -306
  147. package/core/plugins/built-in/vite/vite-dev.ts +82 -0
  148. package/core/plugins/config.ts +9 -7
  149. package/core/plugins/dependency-manager.ts +31 -1
  150. package/core/plugins/discovery.ts +19 -7
  151. package/core/plugins/executor.ts +2 -2
  152. package/core/plugins/index.ts +203 -203
  153. package/core/plugins/manager.ts +27 -39
  154. package/core/plugins/module-resolver.ts +19 -8
  155. package/core/plugins/registry.ts +309 -21
  156. package/core/plugins/types.ts +106 -55
  157. package/core/server/framework.ts +66 -43
  158. package/core/server/index.ts +15 -16
  159. package/core/server/live/ComponentRegistry.ts +91 -75
  160. package/core/server/live/FileUploadManager.ts +41 -31
  161. package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
  162. package/core/server/live/LiveRoomManager.ts +261 -0
  163. package/core/server/live/RoomEventBus.ts +234 -0
  164. package/core/server/live/RoomStateManager.ts +172 -0
  165. package/core/server/live/StateSignature.ts +643 -643
  166. package/core/server/live/WebSocketConnectionManager.ts +30 -19
  167. package/core/server/live/auto-generated-components.ts +41 -26
  168. package/core/server/live/index.ts +14 -0
  169. package/core/server/live/websocket-plugin.ts +233 -72
  170. package/core/server/middleware/elysia-helpers.ts +7 -2
  171. package/core/server/middleware/errorHandling.ts +1 -1
  172. package/core/server/middleware/index.ts +31 -31
  173. package/core/server/plugins/database.ts +180 -180
  174. package/core/server/plugins/static-files-plugin.ts +69 -260
  175. package/core/server/plugins/swagger.ts +33 -33
  176. package/core/server/rooms/RoomBroadcaster.ts +357 -0
  177. package/core/server/rooms/RoomSystem.ts +463 -0
  178. package/core/server/rooms/index.ts +13 -0
  179. package/core/server/services/BaseService.ts +1 -1
  180. package/core/server/services/ServiceContainer.ts +1 -1
  181. package/core/server/services/index.ts +8 -8
  182. package/core/templates/create-project.ts +12 -12
  183. package/core/testing/index.ts +9 -9
  184. package/core/testing/setup.ts +73 -73
  185. package/core/types/api.ts +168 -168
  186. package/core/types/build.ts +219 -218
  187. package/core/types/config.ts +56 -26
  188. package/core/types/index.ts +4 -4
  189. package/core/types/plugin.ts +107 -99
  190. package/core/types/types.ts +490 -14
  191. package/core/utils/build-logger.ts +324 -324
  192. package/core/utils/config-schema.ts +480 -480
  193. package/core/utils/env.ts +2 -8
  194. package/core/utils/errors/codes.ts +114 -114
  195. package/core/utils/errors/handlers.ts +36 -1
  196. package/core/utils/errors/index.ts +49 -5
  197. package/core/utils/errors/middleware.ts +113 -113
  198. package/core/utils/helpers.ts +6 -16
  199. package/core/utils/index.ts +17 -17
  200. package/core/utils/logger/colors.ts +114 -114
  201. package/core/utils/logger/config.ts +13 -9
  202. package/core/utils/logger/formatter.ts +82 -82
  203. package/core/utils/logger/group-logger.ts +101 -101
  204. package/core/utils/logger/index.ts +6 -1
  205. package/core/utils/logger/stack-trace.ts +3 -1
  206. package/core/utils/logger/startup-banner.ts +82 -66
  207. package/core/utils/logger/winston-logger.ts +152 -152
  208. package/core/utils/monitoring/index.ts +211 -211
  209. package/core/utils/sync-version.ts +66 -66
  210. package/core/utils/version.ts +1 -1
  211. package/create-fluxstack.ts +8 -7
  212. package/eslint.config.js +23 -23
  213. package/package.json +14 -15
  214. package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
  215. package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
  216. package/plugins/crypto-auth/client/components/index.ts +11 -11
  217. package/plugins/crypto-auth/client/index.ts +11 -11
  218. package/plugins/crypto-auth/config/index.ts +1 -1
  219. package/plugins/crypto-auth/index.ts +4 -4
  220. package/plugins/crypto-auth/package.json +65 -65
  221. package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
  222. package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
  223. package/plugins/crypto-auth/server/index.ts +21 -21
  224. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
  225. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
  226. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
  227. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
  228. package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
  229. package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
  230. package/plugins/crypto-auth/server/middlewares.ts +19 -19
  231. package/tsconfig.api-strict.json +16 -0
  232. package/tsconfig.json +10 -14
  233. package/{app/client/tsconfig.node.json → tsconfig.node.json} +1 -1
  234. package/types/global.d.ts +29 -29
  235. package/types/vitest.d.ts +8 -8
  236. package/vite.config.ts +38 -62
  237. package/vitest.config.live.ts +10 -9
  238. package/vitest.config.ts +29 -17
  239. package/workspace.json +5 -5
  240. package/app/client/README.md +0 -69
  241. package/app/client/SIMPLIFICATION.md +0 -140
  242. package/app/client/frontend-only.ts +0 -12
  243. package/app/client/tsconfig.app.json +0 -44
  244. package/app/client/tsconfig.json +0 -7
  245. package/app/client/zustand-setup.md +0 -65
  246. package/app/server/backend-only.ts +0 -18
  247. package/app/server/live/LiveClockComponent.ts +0 -215
  248. package/app/server/routes/env-test.ts +0 -110
  249. package/core/client/hooks/index.ts +0 -7
  250. package/core/client/hooks/useHybridLiveComponent.ts +0 -631
  251. package/core/client/hooks/useWebSocket.ts +0 -373
  252. package/core/config/env.ts +0 -546
  253. package/core/config/loader.ts +0 -522
  254. package/core/config/runtime-config.ts +0 -327
  255. package/core/config/validator.ts +0 -540
  256. package/core/server/backend-entry.ts +0 -51
  257. package/core/server/standalone.ts +0 -106
  258. package/core/utils/regenerate-files.ts +0 -69
  259. package/fluxstack.config.ts +0 -354
@@ -1,49 +1,49 @@
1
- /**
2
- * Authentication Hook
3
- * Core FluxStack authentication utilities
4
- */
5
-
6
- import type { BaseUser, BaseUserStore } from '../state/index'
7
-
8
- /**
9
- * Create authentication hook for a user store
10
- */
11
- export function createAuthHook(useUserStore: () => BaseUserStore) {
12
- return function useAuth() {
13
- const store = useUserStore()
14
-
15
- return {
16
- // State
17
- currentUser: store.currentUser,
18
- isAuthenticated: store.isAuthenticated,
19
- isLoading: store.isLoading,
20
- error: store.error,
21
-
22
- // Computed
23
- isAdmin: store.currentUser?.role === 'admin',
24
-
25
- // Actions
26
- login: store.login,
27
- register: store.register,
28
- logout: store.logout,
29
- updateProfile: store.updateProfile,
30
- clearError: store.clearError
31
- }
32
- }
33
- }
34
-
35
- /**
36
- * Base auth hook interface
37
- */
38
- export interface AuthHook {
39
- currentUser: BaseUser | null
40
- isAuthenticated: boolean
41
- isLoading: boolean
42
- error: string | null
43
- isAdmin: boolean
44
- login: (credentials: { email: string; password: string }) => Promise<void>
45
- register: (data: { email: string; password: string; name: string }) => Promise<void>
46
- logout: () => void
47
- updateProfile: (data: Partial<BaseUser>) => Promise<void>
48
- clearError: () => void
1
+ /**
2
+ * Authentication Hook
3
+ * Core FluxStack authentication utilities
4
+ */
5
+
6
+ import type { BaseUser, BaseUserStore } from '../state/index'
7
+
8
+ /**
9
+ * Create authentication hook for a user store
10
+ */
11
+ export function createAuthHook(useUserStore: () => BaseUserStore) {
12
+ return function useAuth() {
13
+ const store = useUserStore()
14
+
15
+ return {
16
+ // State
17
+ currentUser: store.currentUser,
18
+ isAuthenticated: store.isAuthenticated,
19
+ isLoading: store.isLoading,
20
+ error: store.error,
21
+
22
+ // Computed
23
+ isAdmin: store.currentUser?.role === 'admin',
24
+
25
+ // Actions
26
+ login: store.login,
27
+ register: store.register,
28
+ logout: store.logout,
29
+ updateProfile: store.updateProfile,
30
+ clearError: store.clearError
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Base auth hook interface
37
+ */
38
+ export interface AuthHook {
39
+ currentUser: BaseUser | null
40
+ isAuthenticated: boolean
41
+ isLoading: boolean
42
+ error: string | null
43
+ isAdmin: boolean
44
+ login: (credentials: { email: string; password: string }) => Promise<void>
45
+ register: (data: { email: string; password: string; name: string }) => Promise<void>
46
+ logout: () => void
47
+ updateProfile: (data: Partial<BaseUser>) => Promise<void>
48
+ clearError: () => void
49
49
  }
@@ -1,20 +1,54 @@
1
1
  import { useState, useCallback, useRef } from 'react'
2
- import type {
3
- FileUploadStartMessage,
4
- FileUploadChunkMessage,
2
+ import { AdaptiveChunkSizer, type AdaptiveChunkConfig } from './AdaptiveChunkSizer'
3
+ import type {
4
+ FileUploadStartMessage,
5
+ FileUploadChunkMessage,
5
6
  FileUploadCompleteMessage,
6
7
  FileUploadProgressResponse,
7
- FileUploadCompleteResponse
8
- } from '@/core/types/types'
8
+ FileUploadCompleteResponse,
9
+ BinaryChunkHeader
10
+ } from '@core/types/types'
9
11
 
10
12
  export interface ChunkedUploadOptions {
11
- chunkSize?: number // Default 64KB
13
+ chunkSize?: number // Default 64KB (used as initial if adaptive is enabled)
12
14
  maxFileSize?: number // Default 50MB
13
15
  allowedTypes?: string[]
14
- sendMessageAndWait?: (message: any, timeout?: number) => Promise<any> // WebSocket send function
16
+ sendMessageAndWait?: (message: any, timeout?: number) => Promise<any> // WebSocket send function for JSON
17
+ sendBinaryAndWait?: (data: ArrayBuffer, requestId: string, timeout?: number) => Promise<any> // WebSocket send function for binary
15
18
  onProgress?: (progress: number, bytesUploaded: number, totalBytes: number) => void
16
19
  onComplete?: (response: FileUploadCompleteResponse) => void
17
20
  onError?: (error: string) => void
21
+ // Adaptive chunking options
22
+ adaptiveChunking?: boolean // Enable adaptive chunk sizing (default: false)
23
+ adaptiveConfig?: Partial<AdaptiveChunkConfig> // Adaptive chunking configuration
24
+ // Binary protocol (more efficient, ~33% less data)
25
+ useBinaryProtocol?: boolean // Enable binary chunk protocol (default: true)
26
+ }
27
+
28
+ /**
29
+ * Creates a binary message with header + data
30
+ * Format: [4 bytes header length][JSON header][binary data]
31
+ */
32
+ function createBinaryChunkMessage(header: BinaryChunkHeader, chunkData: Uint8Array): ArrayBuffer {
33
+ const headerJson = JSON.stringify(header)
34
+ const headerBytes = new TextEncoder().encode(headerJson)
35
+
36
+ // Total size: 4 bytes (header length) + header + data
37
+ const totalSize = 4 + headerBytes.length + chunkData.length
38
+ const buffer = new ArrayBuffer(totalSize)
39
+ const view = new DataView(buffer)
40
+ const uint8View = new Uint8Array(buffer)
41
+
42
+ // Write header length (little-endian)
43
+ view.setUint32(0, headerBytes.length, true)
44
+
45
+ // Write header
46
+ uint8View.set(headerBytes, 4)
47
+
48
+ // Write chunk data
49
+ uint8View.set(chunkData, 4 + headerBytes.length)
50
+
51
+ return buffer
18
52
  }
19
53
 
20
54
  export interface ChunkedUploadState {
@@ -27,7 +61,7 @@ export interface ChunkedUploadState {
27
61
  }
28
62
 
29
63
  export function useChunkedUpload(componentId: string, options: ChunkedUploadOptions = {}) {
30
-
64
+
31
65
  const [state, setState] = useState<ChunkedUploadState>({
32
66
  uploading: false,
33
67
  progress: 0,
@@ -42,44 +76,30 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
42
76
  maxFileSize = 50 * 1024 * 1024, // 50MB default
43
77
  allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'],
44
78
  sendMessageAndWait,
79
+ sendBinaryAndWait,
45
80
  onProgress,
46
81
  onComplete,
47
- onError
82
+ onError,
83
+ adaptiveChunking = false,
84
+ adaptiveConfig,
85
+ useBinaryProtocol = true // Default to binary for efficiency
48
86
  } = options
49
87
 
88
+ // Determine if we can use binary protocol
89
+ const canUseBinary = useBinaryProtocol && sendBinaryAndWait
90
+
50
91
  const abortControllerRef = useRef<AbortController | null>(null)
92
+ const adaptiveSizerRef = useRef<AdaptiveChunkSizer | null>(null)
51
93
 
52
- // Convert file to base64 chunks
53
- const fileToChunks = useCallback(async (file: File): Promise<string[]> => {
54
- return new Promise((resolve, reject) => {
55
- const reader = new FileReader()
56
-
57
- reader.onload = () => {
58
- const arrayBuffer = reader.result as ArrayBuffer
59
- const uint8Array = new Uint8Array(arrayBuffer)
60
-
61
- // Split binary data into chunks first, then convert each chunk to base64
62
- const chunks: string[] = []
63
- for (let i = 0; i < uint8Array.length; i += chunkSize) {
64
- const chunkEnd = Math.min(i + chunkSize, uint8Array.length)
65
- const chunkBytes = uint8Array.slice(i, chunkEnd)
66
-
67
- // Convert chunk to base64
68
- let binary = ''
69
- for (let j = 0; j < chunkBytes.length; j++) {
70
- binary += String.fromCharCode(chunkBytes[j])
71
- }
72
- const base64Chunk = btoa(binary)
73
- chunks.push(base64Chunk)
74
- }
75
-
76
- resolve(chunks)
77
- }
78
-
79
- reader.onerror = () => reject(new Error('Failed to read file'))
80
- reader.readAsArrayBuffer(file)
94
+ // Initialize adaptive chunk sizer if enabled
95
+ if (adaptiveChunking && !adaptiveSizerRef.current) {
96
+ adaptiveSizerRef.current = new AdaptiveChunkSizer({
97
+ initialChunkSize: chunkSize,
98
+ minChunkSize: chunkSize, // Do not go below initial chunk size by default
99
+ maxChunkSize: 1024 * 1024, // 1MB max
100
+ ...adaptiveConfig
81
101
  })
82
- }, [chunkSize])
102
+ }
83
103
 
84
104
  // Start chunked upload
85
105
  const uploadFile = useCallback(async (file: File) => {
@@ -90,8 +110,8 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
90
110
  return
91
111
  }
92
112
 
93
- // Validate file
94
- if (!allowedTypes.includes(file.type)) {
113
+ // Validate file type (skip if allowedTypes is empty = accept all)
114
+ if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
95
115
  const error = `Invalid file type: ${file.type}. Allowed: ${allowedTypes.join(', ')}`
96
116
  setState(prev => ({ ...prev, error }))
97
117
  onError?.(error)
@@ -120,13 +140,23 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
120
140
  totalBytes: file.size
121
141
  })
122
142
 
123
- console.log('🚀 Starting chunked upload:', { uploadId, filename: file.name, size: file.size })
143
+ console.log('🚀 Starting chunked upload:', {
144
+ uploadId,
145
+ filename: file.name,
146
+ size: file.size,
147
+ adaptiveChunking,
148
+ protocol: canUseBinary ? 'binary' : 'base64'
149
+ })
124
150
 
125
- // Convert file to chunks
126
- const chunks = await fileToChunks(file)
127
- const totalChunks = chunks.length
151
+ // Reset adaptive sizer for new upload
152
+ if (adaptiveSizerRef.current) {
153
+ adaptiveSizerRef.current.reset()
154
+ }
155
+
156
+ // Get initial chunk size (adaptive or fixed)
157
+ const initialChunkSize = adaptiveSizerRef.current?.getChunkSize() ?? chunkSize
128
158
 
129
- console.log(`📦 File split into ${totalChunks} chunks of ~${chunkSize} bytes each`)
159
+ console.log(`📦 Initial chunk size: ${initialChunkSize} bytes${adaptiveChunking ? ' (adaptive)' : ''}`)
130
160
 
131
161
  // Start upload
132
162
  const startMessage: FileUploadStartMessage = {
@@ -147,35 +177,106 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
147
177
 
148
178
  console.log('✅ Upload started successfully')
149
179
 
150
- // Send chunks sequentially
151
- for (let i = 0; i < chunks.length; i++) {
180
+ let offset = 0
181
+ let chunkIndex = 0
182
+ const estimatedTotalChunks = Math.ceil(file.size / initialChunkSize)
183
+
184
+ // Send chunks dynamically with adaptive sizing (read slice per chunk)
185
+ while (offset < file.size) {
152
186
  if (abortControllerRef.current?.signal.aborted) {
153
187
  throw new Error('Upload cancelled')
154
188
  }
155
189
 
156
- const chunkMessage: FileUploadChunkMessage = {
157
- type: 'FILE_UPLOAD_CHUNK',
158
- componentId,
159
- uploadId,
160
- chunkIndex: i,
161
- totalChunks,
162
- data: chunks[i],
163
- requestId: `chunk-${uploadId}-${i}`
190
+ // Get current chunk size (adaptive or fixed)
191
+ const currentChunkSize = adaptiveSizerRef.current?.getChunkSize() ?? chunkSize
192
+ const chunkEnd = Math.min(offset + currentChunkSize, file.size)
193
+ const sliceBuffer = await file.slice(offset, chunkEnd).arrayBuffer()
194
+ const chunkBytes = new Uint8Array(sliceBuffer)
195
+
196
+ // Record chunk start time for adaptive sizing
197
+ const chunkStartTime = adaptiveSizerRef.current?.recordChunkStart(chunkIndex) ?? 0
198
+ const requestId = `chunk-${uploadId}-${chunkIndex}`
199
+
200
+ console.log(`📤 Sending chunk ${chunkIndex + 1} (size: ${chunkBytes.length} bytes)${canUseBinary ? ' [binary]' : ' [base64]'}`)
201
+
202
+ try {
203
+ let progressResponse: FileUploadProgressResponse | undefined
204
+
205
+ if (canUseBinary) {
206
+ // Binary protocol: Send header + raw bytes (more efficient)
207
+ const header: BinaryChunkHeader = {
208
+ type: 'FILE_UPLOAD_CHUNK',
209
+ componentId,
210
+ uploadId,
211
+ chunkIndex,
212
+ totalChunks: estimatedTotalChunks,
213
+ requestId
214
+ }
215
+
216
+ const binaryMessage = createBinaryChunkMessage(header, chunkBytes)
217
+ progressResponse = await sendBinaryAndWait!(binaryMessage, requestId, 10000) as FileUploadProgressResponse
218
+ } else {
219
+ // JSON protocol: Convert to base64 (legacy/fallback)
220
+ let binary = ''
221
+ for (let j = 0; j < chunkBytes.length; j++) {
222
+ binary += String.fromCharCode(chunkBytes[j])
223
+ }
224
+ const base64Chunk = btoa(binary)
225
+
226
+ const chunkMessage: FileUploadChunkMessage = {
227
+ type: 'FILE_UPLOAD_CHUNK',
228
+ componentId,
229
+ uploadId,
230
+ chunkIndex,
231
+ totalChunks: estimatedTotalChunks,
232
+ data: base64Chunk,
233
+ requestId
234
+ }
235
+
236
+ progressResponse = await sendMessageAndWait!(chunkMessage, 10000) as FileUploadProgressResponse
237
+ }
238
+
239
+ if (progressResponse) {
240
+ const { progress, bytesUploaded } = progressResponse
241
+ setState(prev => ({ ...prev, progress, bytesUploaded }))
242
+ onProgress?.(progress, bytesUploaded, file.size)
243
+ }
244
+
245
+ // Record successful chunk upload for adaptive sizing
246
+ if (adaptiveSizerRef.current) {
247
+ adaptiveSizerRef.current.recordChunkComplete(
248
+ chunkIndex,
249
+ chunkBytes.length,
250
+ chunkStartTime,
251
+ true
252
+ )
253
+ }
254
+ } catch (error) {
255
+ // Record failed chunk for adaptive sizing
256
+ if (adaptiveSizerRef.current) {
257
+ adaptiveSizerRef.current.recordChunkComplete(
258
+ chunkIndex,
259
+ chunkBytes.length,
260
+ chunkStartTime,
261
+ false
262
+ )
263
+ }
264
+ throw error
164
265
  }
165
266
 
166
- console.log(`📤 Sending chunk ${i + 1}/${totalChunks}`)
267
+ offset += chunkBytes.length
268
+ chunkIndex++
167
269
 
168
- // Send chunk and wait for progress response
169
- const progressResponse = await sendMessageAndWait(chunkMessage, 10000) as FileUploadProgressResponse
170
-
171
- if (progressResponse) {
172
- const { progress, bytesUploaded } = progressResponse
173
- setState(prev => ({ ...prev, progress, bytesUploaded }))
174
- onProgress?.(progress, bytesUploaded, file.size)
270
+ // Small delay to prevent overwhelming the server (only for fixed chunking)
271
+ if (!adaptiveChunking) {
272
+ await new Promise(resolve => setTimeout(resolve, 10))
175
273
  }
274
+ }
176
275
 
177
- // Small delay to prevent overwhelming the server
178
- await new Promise(resolve => setTimeout(resolve, 10))
276
+ // Log final adaptive stats
277
+ if (adaptiveSizerRef.current) {
278
+ const stats = adaptiveSizerRef.current.getStats()
279
+ console.log('📊 Final Adaptive Chunking Stats:', stats)
179
280
  }
180
281
 
181
282
  // Complete upload
@@ -219,10 +320,10 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
219
320
  maxFileSize,
220
321
  chunkSize,
221
322
  sendMessageAndWait,
222
- fileToChunks,
223
323
  onProgress,
224
324
  onComplete,
225
- onError
325
+ onError,
326
+ adaptiveChunking
226
327
  ])
227
328
 
228
329
  // Cancel upload
@@ -255,4 +356,4 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
255
356
  cancelUpload,
256
357
  reset
257
358
  }
258
- }
359
+ }
@@ -0,0 +1,87 @@
1
+ import { useMemo } from 'react'
2
+ import { useLiveComponents } from '../LiveComponentsProvider'
3
+ import { useChunkedUpload } from './useChunkedUpload'
4
+ import type { ChunkedUploadOptions } from './useChunkedUpload'
5
+ import type { FileUploadCompleteResponse } from '@core/types/types'
6
+
7
+ type LiveUploadActions = {
8
+ $componentId: string | null
9
+ startUpload: (payload: { fileName: string; fileSize: number; fileType: string }) => Promise<any>
10
+ updateProgress: (payload: { progress: number; bytesUploaded: number; totalBytes: number }) => Promise<any>
11
+ completeUpload: (payload: { fileUrl: string }) => Promise<any>
12
+ failUpload: (payload: { error: string }) => Promise<any>
13
+ reset: () => Promise<any>
14
+ }
15
+
16
+ export interface LiveChunkedUploadOptions extends Omit<ChunkedUploadOptions, 'sendMessageAndWait' | 'onProgress' | 'onComplete' | 'onError'> {
17
+ onProgress?: (progress: number, bytesUploaded: number, totalBytes: number) => void
18
+ onComplete?: (response: FileUploadCompleteResponse) => void
19
+ onError?: (error: string) => void
20
+ fileUrlResolver?: (fileUrl: string) => string
21
+ }
22
+
23
+ export function useLiveChunkedUpload(live: LiveUploadActions, options: LiveChunkedUploadOptions = {}) {
24
+ const { sendMessageAndWait, sendBinaryAndWait } = useLiveComponents()
25
+
26
+ const {
27
+ onProgress,
28
+ onComplete,
29
+ onError,
30
+ fileUrlResolver,
31
+ ...chunkedOptions
32
+ } = options
33
+
34
+ const componentId = live.$componentId ?? ''
35
+
36
+ const base = useChunkedUpload(componentId, {
37
+ ...chunkedOptions,
38
+ sendMessageAndWait,
39
+ sendBinaryAndWait, // Enable binary protocol for efficient uploads
40
+ onProgress: (pct, uploaded, total) => {
41
+ void live.updateProgress({ progress: pct, bytesUploaded: uploaded, totalBytes: total }).catch(() => {})
42
+ onProgress?.(pct, uploaded, total)
43
+ },
44
+ onComplete: (response) => {
45
+ const rawUrl = response.fileUrl || ''
46
+ const resolvedUrl = fileUrlResolver ? fileUrlResolver(rawUrl) : rawUrl
47
+ void live.completeUpload({ fileUrl: resolvedUrl }).catch(() => {})
48
+ onComplete?.(response)
49
+ },
50
+ onError: (error) => {
51
+ void live.failUpload({ error }).catch(() => {})
52
+ onError?.(error)
53
+ }
54
+ })
55
+
56
+ const uploadFile = useMemo(() => {
57
+ return async (file: File) => {
58
+ if (!live.$componentId) {
59
+ const msg = 'WebSocket not ready. Wait a moment and try again.'
60
+ void live.failUpload({ error: msg }).catch(() => {})
61
+ onError?.(msg)
62
+ return
63
+ }
64
+
65
+ await live.startUpload({
66
+ fileName: file.name,
67
+ fileSize: file.size,
68
+ fileType: file.type || 'application/octet-stream'
69
+ })
70
+
71
+ await base.uploadFile(file)
72
+ }
73
+ }, [base, live, onError])
74
+
75
+ const reset = useMemo(() => {
76
+ return async () => {
77
+ await live.reset()
78
+ base.reset()
79
+ }
80
+ }, [base, live])
81
+
82
+ return {
83
+ ...base,
84
+ uploadFile,
85
+ reset
86
+ }
87
+ }