create-fluxstack 1.9.1 → 1.10.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/LIVE_COMPONENTS_REVIEW.md +781 -0
- package/app/client/src/App.tsx +39 -43
- package/app/client/src/lib/eden-api.ts +2 -7
- package/app/client/src/live/FileUploadExample.tsx +359 -0
- package/app/client/src/live/MinimalLiveClock.tsx +47 -0
- package/app/client/src/live/QuickUploadTest.tsx +193 -0
- package/app/client/src/main.tsx +10 -10
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +45 -44
- package/app/client/tsconfig.node.json +25 -25
- package/app/server/index.ts +30 -103
- package/app/server/live/LiveFileUploadComponent.ts +77 -0
- package/app/server/live/register-components.ts +19 -19
- package/core/build/bundler.ts +4 -1
- package/core/build/index.ts +124 -4
- package/core/build/live-components-generator.ts +68 -1
- package/core/cli/index.ts +163 -35
- package/core/client/LiveComponentsProvider.tsx +3 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -0
- package/core/client/hooks/useChunkedUpload.ts +112 -61
- package/core/client/hooks/useHybridLiveComponent.ts +80 -26
- package/core/client/hooks/useTypedLiveComponent.ts +133 -0
- package/core/client/hooks/useWebSocket.ts +4 -16
- package/core/client/index.ts +20 -2
- package/core/framework/server.ts +181 -8
- package/core/live/ComponentRegistry.ts +5 -1
- package/core/plugins/built-in/index.ts +8 -5
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +55 -63
- package/core/plugins/built-in/vite/index.ts +75 -187
- package/core/plugins/built-in/vite/vite-dev.ts +88 -0
- package/core/plugins/registry.ts +54 -2
- package/core/plugins/types.ts +86 -2
- package/core/server/index.ts +1 -2
- package/core/server/live/ComponentRegistry.ts +14 -5
- package/core/server/live/FileUploadManager.ts +22 -25
- package/core/server/live/auto-generated-components.ts +29 -26
- package/core/server/live/websocket-plugin.ts +19 -5
- package/core/server/plugins/static-files-plugin.ts +49 -240
- package/core/server/plugins/swagger.ts +33 -33
- package/core/types/build.ts +1 -0
- package/core/types/plugin.ts +9 -1
- package/core/types/types.ts +137 -0
- package/core/utils/logger/startup-banner.ts +20 -4
- package/core/utils/version.ts +1 -1
- package/eslint.config.js +23 -23
- package/package.json +3 -3
- package/plugins/crypto-auth/server/middlewares.ts +19 -19
- package/tsconfig.json +52 -52
- package/workspace.json +5 -5
|
@@ -165,7 +165,13 @@ export function useHybridLiveComponent<T = any>(
|
|
|
165
165
|
room,
|
|
166
166
|
userId,
|
|
167
167
|
autoMount = true,
|
|
168
|
-
debug = false
|
|
168
|
+
debug = false,
|
|
169
|
+
onConnect,
|
|
170
|
+
onMount,
|
|
171
|
+
onDisconnect,
|
|
172
|
+
onRehydrate,
|
|
173
|
+
onError,
|
|
174
|
+
onStateChange
|
|
169
175
|
} = options
|
|
170
176
|
|
|
171
177
|
// Use Live Components context (singleton WebSocket connection)
|
|
@@ -226,9 +232,13 @@ export function useHybridLiveComponent<T = any>(
|
|
|
226
232
|
case 'STATE_UPDATE':
|
|
227
233
|
if (message.payload?.state) {
|
|
228
234
|
const newState = message.payload.state
|
|
235
|
+
const oldState = stateData
|
|
229
236
|
updateState(newState, 'server')
|
|
230
237
|
setLastServerState(newState)
|
|
231
238
|
|
|
239
|
+
// Call onStateChange callback
|
|
240
|
+
onStateChange?.(newState, oldState)
|
|
241
|
+
|
|
232
242
|
if (message.payload?.signedState) {
|
|
233
243
|
setCurrentSignedState(message.payload.signedState)
|
|
234
244
|
persistComponentState(componentName, message.payload.signedState, room, userId)
|
|
@@ -255,6 +265,9 @@ export function useHybridLiveComponent<T = any>(
|
|
|
255
265
|
|
|
256
266
|
setRehydrating(false)
|
|
257
267
|
setError(null)
|
|
268
|
+
|
|
269
|
+
// Call onRehydrate callback
|
|
270
|
+
onRehydrate?.()
|
|
258
271
|
}
|
|
259
272
|
break
|
|
260
273
|
|
|
@@ -266,10 +279,17 @@ export function useHybridLiveComponent<T = any>(
|
|
|
266
279
|
lastKnownComponentIdRef.current = message.result.newComponentId
|
|
267
280
|
setRehydrating(false)
|
|
268
281
|
setError(null)
|
|
282
|
+
|
|
283
|
+
// Call onRehydrate callback
|
|
284
|
+
onRehydrate?.()
|
|
269
285
|
} else if (!message.success) {
|
|
270
286
|
log('❌ Re-hydration failed', message.error)
|
|
271
287
|
setRehydrating(false)
|
|
272
|
-
|
|
288
|
+
const errorMessage = message.error || 'Re-hydration failed'
|
|
289
|
+
setError(errorMessage)
|
|
290
|
+
|
|
291
|
+
// Call onError callback
|
|
292
|
+
onError?.(errorMessage)
|
|
273
293
|
}
|
|
274
294
|
break
|
|
275
295
|
|
|
@@ -283,14 +303,16 @@ export function useHybridLiveComponent<T = any>(
|
|
|
283
303
|
break
|
|
284
304
|
|
|
285
305
|
case 'ERROR':
|
|
286
|
-
const
|
|
287
|
-
if (
|
|
306
|
+
const errorMsg = message.payload?.error || 'Unknown error'
|
|
307
|
+
if (errorMsg.includes('COMPONENT_REHYDRATION_REQUIRED')) {
|
|
288
308
|
log('🔄 Component re-hydration required from ERROR')
|
|
289
309
|
if (!rehydrating) {
|
|
290
310
|
attemptRehydration()
|
|
291
311
|
}
|
|
292
312
|
} else {
|
|
293
|
-
setError(
|
|
313
|
+
setError(errorMsg)
|
|
314
|
+
// Call onError callback
|
|
315
|
+
onError?.(errorMsg)
|
|
294
316
|
}
|
|
295
317
|
break
|
|
296
318
|
|
|
@@ -305,7 +327,7 @@ export function useHybridLiveComponent<T = any>(
|
|
|
305
327
|
log('🗑️ Unregistering component from WebSocket context')
|
|
306
328
|
unregister()
|
|
307
329
|
}
|
|
308
|
-
}, [componentId, registerComponent, unregisterComponent, log, updateState, componentName, room, userId, rehydrating])
|
|
330
|
+
}, [componentId, registerComponent, unregisterComponent, log, updateState, componentName, room, userId, rehydrating, stateData, onStateChange, onRehydrate, onError])
|
|
309
331
|
|
|
310
332
|
// Automatic re-hydration on reconnection
|
|
311
333
|
const attemptRehydration = useCallback(async () => {
|
|
@@ -361,10 +383,20 @@ export function useHybridLiveComponent<T = any>(
|
|
|
361
383
|
if (response?.success && response?.result?.newComponentId) {
|
|
362
384
|
setComponentId(response.result.newComponentId)
|
|
363
385
|
lastKnownComponentIdRef.current = response.result.newComponentId
|
|
386
|
+
mountedRef.current = true
|
|
387
|
+
|
|
388
|
+
// Call onRehydrate callback after React has processed the state update
|
|
389
|
+
// This ensures the component is registered to receive messages before the callback runs
|
|
390
|
+
setTimeout(() => {
|
|
391
|
+
onRehydrate?.()
|
|
392
|
+
}, 0)
|
|
393
|
+
|
|
364
394
|
return true
|
|
365
395
|
} else {
|
|
366
396
|
clearPersistedState(componentName)
|
|
367
|
-
|
|
397
|
+
const errorMsg = response?.error || 'Re-hydration failed'
|
|
398
|
+
setError(errorMsg)
|
|
399
|
+
onError?.(errorMsg)
|
|
368
400
|
return false
|
|
369
401
|
}
|
|
370
402
|
|
|
@@ -383,7 +415,7 @@ export function useHybridLiveComponent<T = any>(
|
|
|
383
415
|
globalRehydrationAttempts.set(componentName, rehydrationPromise)
|
|
384
416
|
|
|
385
417
|
return await rehydrationPromise
|
|
386
|
-
}, [connected, rehydrating, componentName, contextSendMessageAndWait, log])
|
|
418
|
+
}, [connected, rehydrating, componentName, contextSendMessageAndWait, log, onRehydrate, onError])
|
|
387
419
|
|
|
388
420
|
// Mount component
|
|
389
421
|
const mount = useCallback(async () => {
|
|
@@ -412,6 +444,7 @@ export function useHybridLiveComponent<T = any>(
|
|
|
412
444
|
if (response?.success && response?.result?.componentId) {
|
|
413
445
|
const newComponentId = response.result.componentId
|
|
414
446
|
setComponentId(newComponentId)
|
|
447
|
+
lastKnownComponentIdRef.current = newComponentId
|
|
415
448
|
mountedRef.current = true
|
|
416
449
|
|
|
417
450
|
if (response.result.signedState) {
|
|
@@ -425,6 +458,12 @@ export function useHybridLiveComponent<T = any>(
|
|
|
425
458
|
}
|
|
426
459
|
|
|
427
460
|
log('✅ Component mounted successfully', { componentId: newComponentId })
|
|
461
|
+
|
|
462
|
+
// Call onMount callback after React has processed the state update
|
|
463
|
+
// This ensures the component is registered to receive messages before the callback runs
|
|
464
|
+
setTimeout(() => {
|
|
465
|
+
onMount?.()
|
|
466
|
+
}, 0)
|
|
428
467
|
} else {
|
|
429
468
|
throw new Error(response?.error || 'No component ID returned from server')
|
|
430
469
|
}
|
|
@@ -433,6 +472,9 @@ export function useHybridLiveComponent<T = any>(
|
|
|
433
472
|
setError(errorMessage)
|
|
434
473
|
log('❌ Mount failed', err)
|
|
435
474
|
|
|
475
|
+
// Call onError callback
|
|
476
|
+
onError?.(errorMessage)
|
|
477
|
+
|
|
436
478
|
if (!fallbackToLocal) {
|
|
437
479
|
throw err
|
|
438
480
|
}
|
|
@@ -440,7 +482,7 @@ export function useHybridLiveComponent<T = any>(
|
|
|
440
482
|
setMountLoading(false)
|
|
441
483
|
mountingRef.current = false
|
|
442
484
|
}
|
|
443
|
-
}, [connected, componentName, initialState, room, userId, contextSendMessageAndWait, log, fallbackToLocal, updateState])
|
|
485
|
+
}, [connected, componentName, initialState, room, userId, contextSendMessageAndWait, log, fallbackToLocal, updateState, onMount, onError])
|
|
444
486
|
|
|
445
487
|
// Unmount component
|
|
446
488
|
const unmount = useCallback(async () => {
|
|
@@ -465,14 +507,16 @@ export function useHybridLiveComponent<T = any>(
|
|
|
465
507
|
|
|
466
508
|
// Server-only actions
|
|
467
509
|
const call = useCallback(async (action: string, payload?: any): Promise<void> => {
|
|
468
|
-
|
|
510
|
+
// Use ref as fallback for componentId (handles timing issues after rehydration)
|
|
511
|
+
const currentComponentId = componentId || lastKnownComponentIdRef.current
|
|
512
|
+
if (!currentComponentId || !connected) {
|
|
469
513
|
throw new Error('Component not mounted or WebSocket not connected')
|
|
470
514
|
}
|
|
471
515
|
|
|
472
516
|
try {
|
|
473
517
|
const message: WebSocketMessage = {
|
|
474
518
|
type: 'CALL_ACTION',
|
|
475
|
-
componentId,
|
|
519
|
+
componentId: currentComponentId,
|
|
476
520
|
action,
|
|
477
521
|
payload
|
|
478
522
|
}
|
|
@@ -482,10 +526,11 @@ export function useHybridLiveComponent<T = any>(
|
|
|
482
526
|
if (!response.success && response.error?.includes?.('COMPONENT_REHYDRATION_REQUIRED')) {
|
|
483
527
|
const rehydrated = await attemptRehydration()
|
|
484
528
|
if (rehydrated) {
|
|
485
|
-
//
|
|
529
|
+
// Use updated ref for retry
|
|
530
|
+
const retryComponentId = lastKnownComponentIdRef.current || currentComponentId
|
|
486
531
|
const retryMessage: WebSocketMessage = {
|
|
487
532
|
type: 'CALL_ACTION',
|
|
488
|
-
componentId,
|
|
533
|
+
componentId: retryComponentId,
|
|
489
534
|
action,
|
|
490
535
|
payload
|
|
491
536
|
}
|
|
@@ -505,14 +550,16 @@ export function useHybridLiveComponent<T = any>(
|
|
|
505
550
|
|
|
506
551
|
// Call action and wait for specific return value
|
|
507
552
|
const callAndWait = useCallback(async (action: string, payload?: any, timeout?: number): Promise<any> => {
|
|
508
|
-
|
|
553
|
+
// Use ref as fallback for componentId (handles timing issues after rehydration)
|
|
554
|
+
const currentComponentId = componentId || lastKnownComponentIdRef.current
|
|
555
|
+
if (!currentComponentId || !connected) {
|
|
509
556
|
throw new Error('Component not mounted or WebSocket not connected')
|
|
510
557
|
}
|
|
511
558
|
|
|
512
559
|
try {
|
|
513
560
|
const message: WebSocketMessage = {
|
|
514
561
|
type: 'CALL_ACTION',
|
|
515
|
-
componentId,
|
|
562
|
+
componentId: currentComponentId,
|
|
516
563
|
action,
|
|
517
564
|
payload
|
|
518
565
|
}
|
|
@@ -550,24 +597,31 @@ export function useHybridLiveComponent<T = any>(
|
|
|
550
597
|
if (wasConnected && !isConnected && mountedRef.current) {
|
|
551
598
|
mountedRef.current = false
|
|
552
599
|
setComponentId(null)
|
|
600
|
+
// Call onDisconnect callback
|
|
601
|
+
onDisconnect?.()
|
|
553
602
|
}
|
|
554
603
|
|
|
555
|
-
if (!wasConnected && isConnected
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const persistedState = getPersistedState(componentName)
|
|
604
|
+
if (!wasConnected && isConnected) {
|
|
605
|
+
// Call onConnect callback when WebSocket connects
|
|
606
|
+
onConnect?.()
|
|
559
607
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
608
|
+
if (!mountedRef.current && !mountingRef.current && !rehydrating) {
|
|
609
|
+
setTimeout(() => {
|
|
610
|
+
if (!mountedRef.current && !mountingRef.current && !rehydrating) {
|
|
611
|
+
const persistedState = getPersistedState(componentName)
|
|
612
|
+
|
|
613
|
+
if (persistedState?.signedState) {
|
|
614
|
+
attemptRehydration()
|
|
615
|
+
} else {
|
|
616
|
+
mount()
|
|
617
|
+
}
|
|
564
618
|
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
619
|
+
}, 100)
|
|
620
|
+
}
|
|
567
621
|
}
|
|
568
622
|
|
|
569
623
|
prevConnectedRef.current = connected
|
|
570
|
-
}, [connected, mount, componentId, attemptRehydration, componentName, rehydrating])
|
|
624
|
+
}, [connected, mount, componentId, attemptRehydration, componentName, rehydrating, onDisconnect, onConnect])
|
|
571
625
|
|
|
572
626
|
// Unmount on cleanup
|
|
573
627
|
useEffect(() => {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// 🔥 Typed Live Component Hook - Full Type Inference for Actions
|
|
2
|
+
// Similar to Eden Treaty - automatic type inference from backend components
|
|
3
|
+
|
|
4
|
+
import { useHybridLiveComponent } from './useHybridLiveComponent'
|
|
5
|
+
import type { UseHybridLiveComponentReturn } from './useHybridLiveComponent'
|
|
6
|
+
import type {
|
|
7
|
+
LiveComponent,
|
|
8
|
+
InferComponentState,
|
|
9
|
+
HybridComponentOptions,
|
|
10
|
+
UseTypedLiveComponentReturn,
|
|
11
|
+
ActionNames,
|
|
12
|
+
ActionPayload,
|
|
13
|
+
ActionReturn
|
|
14
|
+
} from '@/core/types/types'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Type-safe Live Component hook with automatic action inference
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Backend component definition
|
|
21
|
+
* class LiveClockComponent extends LiveComponent<LiveClockState> {
|
|
22
|
+
* async setTimeFormat(payload: { format: '12h' | '24h' }) { ... }
|
|
23
|
+
* async toggleSeconds(payload?: { showSeconds?: boolean }) { ... }
|
|
24
|
+
* async getServerInfo() { ... }
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* // Frontend usage with full type inference
|
|
28
|
+
* const { state, call, callAndWait } = useTypedLiveComponent<LiveClockComponent>(
|
|
29
|
+
* 'LiveClock',
|
|
30
|
+
* initialState
|
|
31
|
+
* )
|
|
32
|
+
*
|
|
33
|
+
* // ✅ Autocomplete for action names
|
|
34
|
+
* await call('setTimeFormat', { format: '12h' })
|
|
35
|
+
*
|
|
36
|
+
* // ✅ Type error if wrong payload
|
|
37
|
+
* await call('setTimeFormat', { format: 'invalid' }) // Error!
|
|
38
|
+
*
|
|
39
|
+
* // ✅ Return type is inferred
|
|
40
|
+
* const result = await callAndWait('getServerInfo')
|
|
41
|
+
* // result is: { success: boolean; info: { serverTime: string; ... } }
|
|
42
|
+
*/
|
|
43
|
+
export function useTypedLiveComponent<T extends LiveComponent<any>>(
|
|
44
|
+
componentName: string,
|
|
45
|
+
initialState: InferComponentState<T>,
|
|
46
|
+
options: HybridComponentOptions = {}
|
|
47
|
+
): UseTypedLiveComponentReturn<T> {
|
|
48
|
+
// Use the original hook
|
|
49
|
+
const result = useHybridLiveComponent<InferComponentState<T>>(
|
|
50
|
+
componentName,
|
|
51
|
+
initialState,
|
|
52
|
+
options
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
// Create convenience setValue helper
|
|
56
|
+
const setValue = async <K extends keyof InferComponentState<T>>(
|
|
57
|
+
key: K,
|
|
58
|
+
value: InferComponentState<T>[K]
|
|
59
|
+
): Promise<void> => {
|
|
60
|
+
await result.call('setValue', { key, value })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Return with typed call functions and setValue helper
|
|
64
|
+
// The types are enforced at compile time, runtime behavior is the same
|
|
65
|
+
return {
|
|
66
|
+
...result,
|
|
67
|
+
setValue
|
|
68
|
+
} as unknown as UseTypedLiveComponentReturn<T>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Helper type to create a component registry map
|
|
73
|
+
* Maps component names to their class types for even better DX
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // Define your component map
|
|
77
|
+
* type MyComponents = {
|
|
78
|
+
* LiveClock: LiveClockComponent
|
|
79
|
+
* LiveCounter: LiveCounterComponent
|
|
80
|
+
* LiveChat: LiveChatComponent
|
|
81
|
+
* }
|
|
82
|
+
*
|
|
83
|
+
* // Create a typed hook for your app
|
|
84
|
+
* function useMyComponent<K extends keyof MyComponents>(
|
|
85
|
+
* name: K,
|
|
86
|
+
* initialState: ComponentState<MyComponents[K]>,
|
|
87
|
+
* options?: HybridComponentOptions
|
|
88
|
+
* ) {
|
|
89
|
+
* return useTypedLiveComponent<MyComponents[K]>(name, initialState, options)
|
|
90
|
+
* }
|
|
91
|
+
*
|
|
92
|
+
* // Usage
|
|
93
|
+
* const clock = useMyComponent('LiveClock', { ... })
|
|
94
|
+
* // TypeScript knows exactly which actions are available!
|
|
95
|
+
*/
|
|
96
|
+
export type ComponentRegistry<T extends Record<string, LiveComponent<any>>> = {
|
|
97
|
+
[K in keyof T]: T[K]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create a factory for typed live component hooks
|
|
102
|
+
* Useful when you have many components and want simpler imports
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* // In your app/client/src/lib/live.ts
|
|
106
|
+
* import { createTypedLiveComponentHook } from '@/core/client/hooks/useTypedLiveComponent'
|
|
107
|
+
* import type { LiveClockComponent } from '@/app/server/live/LiveClockComponent'
|
|
108
|
+
*
|
|
109
|
+
* export const useLiveClock = createTypedLiveComponentHook<LiveClockComponent>('LiveClock')
|
|
110
|
+
*
|
|
111
|
+
* // Usage in component
|
|
112
|
+
* const { state, call } = useLiveClock({ currentTime: '', ... })
|
|
113
|
+
*/
|
|
114
|
+
export function createTypedLiveComponentHook<T extends LiveComponent<any>>(
|
|
115
|
+
componentName: string
|
|
116
|
+
) {
|
|
117
|
+
return function useComponent(
|
|
118
|
+
initialState: InferComponentState<T>,
|
|
119
|
+
options: HybridComponentOptions = {}
|
|
120
|
+
): UseTypedLiveComponentReturn<T> {
|
|
121
|
+
return useTypedLiveComponent<T>(componentName, initialState, options)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Re-export types for convenience
|
|
126
|
+
export type {
|
|
127
|
+
InferComponentState,
|
|
128
|
+
ActionNames,
|
|
129
|
+
ActionPayload,
|
|
130
|
+
ActionReturn,
|
|
131
|
+
UseTypedLiveComponentReturn,
|
|
132
|
+
HybridComponentOptions
|
|
133
|
+
}
|
|
@@ -32,22 +32,10 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
|
|
|
32
32
|
// Get WebSocket URL dynamically based on current environment
|
|
33
33
|
const getWebSocketUrl = () => {
|
|
34
34
|
if (typeof window === 'undefined') return 'ws://localhost:3000/api/live/ws'
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
// In production, use current origin with ws/wss protocol
|
|
40
|
-
if (!isLocalhost) {
|
|
41
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
42
|
-
const url = `${protocol}//${window.location.host}/api/live/ws`
|
|
43
|
-
console.log('🔗 [WebSocket] Production URL:', url)
|
|
44
|
-
return url
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// In development, use backend server (port 3000 for integrated mode)
|
|
48
|
-
const url = 'ws://localhost:3000/api/live/ws'
|
|
49
|
-
console.log('🔗 [WebSocket] Development URL:', url)
|
|
50
|
-
return url
|
|
35
|
+
|
|
36
|
+
// Always use current host - works for both dev and production
|
|
37
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
38
|
+
return `${protocol}//${window.location.host}/api/live/ws`
|
|
51
39
|
}
|
|
52
40
|
|
|
53
41
|
const {
|
package/core/client/index.ts
CHANGED
|
@@ -19,9 +19,15 @@ export type {
|
|
|
19
19
|
// Hooks
|
|
20
20
|
export { useWebSocket } from './hooks/useWebSocket'
|
|
21
21
|
export { useHybridLiveComponent } from './hooks/useHybridLiveComponent'
|
|
22
|
+
export { useTypedLiveComponent, createTypedLiveComponentHook } from './hooks/useTypedLiveComponent'
|
|
22
23
|
export { useChunkedUpload } from './hooks/useChunkedUpload'
|
|
24
|
+
export { AdaptiveChunkSizer } from './hooks/AdaptiveChunkSizer'
|
|
23
25
|
export { StateValidator } from './hooks/state-validator'
|
|
24
26
|
|
|
27
|
+
// Hook types
|
|
28
|
+
export type { AdaptiveChunkConfig, ChunkMetrics } from './hooks/AdaptiveChunkSizer'
|
|
29
|
+
export type { ChunkedUploadOptions, ChunkedUploadState } from './hooks/useChunkedUpload'
|
|
30
|
+
|
|
25
31
|
// Re-export types from core/types/types.ts for convenience
|
|
26
32
|
export type {
|
|
27
33
|
// Live Components types
|
|
@@ -56,8 +62,20 @@ export type {
|
|
|
56
62
|
ComponentActions,
|
|
57
63
|
ComponentProps,
|
|
58
64
|
ActionParameters,
|
|
59
|
-
ActionReturnType
|
|
65
|
+
ActionReturnType,
|
|
66
|
+
|
|
67
|
+
// Type inference system (similar to Eden Treaty)
|
|
68
|
+
ExtractActions,
|
|
69
|
+
ActionNames,
|
|
70
|
+
ActionPayload,
|
|
71
|
+
ActionReturn,
|
|
72
|
+
InferComponentState,
|
|
73
|
+
TypedCall,
|
|
74
|
+
TypedCallAndWait,
|
|
75
|
+
TypedSetValue,
|
|
76
|
+
UseTypedLiveComponentReturn
|
|
60
77
|
} from '../types/types'
|
|
61
78
|
|
|
62
79
|
// Hook return types
|
|
63
|
-
export type { UseHybridLiveComponentReturn } from './hooks/useHybridLiveComponent'
|
|
80
|
+
export type { UseHybridLiveComponentReturn } from './hooks/useHybridLiveComponent'
|
|
81
|
+
export type { ComponentRegistry } from './hooks/useTypedLiveComponent'
|