create-fluxstack 1.12.1 → 1.14.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 (116) hide show
  1. package/LLMD/INDEX.md +8 -1
  2. package/LLMD/agent.md +867 -0
  3. package/LLMD/config/environment-vars.md +30 -0
  4. package/LLMD/patterns/anti-patterns.md +100 -0
  5. package/LLMD/reference/routing.md +39 -39
  6. package/LLMD/resources/live-auth.md +465 -0
  7. package/LLMD/resources/live-components.md +168 -26
  8. package/LLMD/resources/live-logging.md +220 -0
  9. package/LLMD/resources/live-upload.md +59 -8
  10. package/LLMD/resources/rest-auth.md +290 -0
  11. package/README.md +520 -340
  12. package/app/client/index.html +2 -2
  13. package/app/client/public/favicon.svg +46 -0
  14. package/app/client/src/App.tsx +13 -1
  15. package/app/client/src/assets/fluxstack-static.svg +46 -0
  16. package/app/client/src/assets/fluxstack.svg +183 -0
  17. package/app/client/src/components/AppLayout.tsx +139 -9
  18. package/app/client/src/components/BackButton.tsx +13 -13
  19. package/app/client/src/components/DemoPage.tsx +4 -4
  20. package/app/client/src/live/AuthDemo.tsx +334 -0
  21. package/app/client/src/live/ChatDemo.tsx +2 -2
  22. package/app/client/src/live/CounterDemo.tsx +12 -12
  23. package/app/client/src/live/FormDemo.tsx +2 -2
  24. package/app/client/src/live/LiveDebuggerPanel.tsx +779 -0
  25. package/app/client/src/live/RoomChatDemo.tsx +24 -16
  26. package/app/client/src/main.tsx +13 -13
  27. package/app/client/src/pages/ApiTestPage.tsx +6 -6
  28. package/app/client/src/pages/HomePage.tsx +80 -52
  29. package/app/server/auth/AuthManager.ts +213 -0
  30. package/app/server/auth/DevAuthProvider.ts +66 -0
  31. package/app/server/auth/HashManager.ts +123 -0
  32. package/app/server/auth/JWTAuthProvider.example.ts +101 -0
  33. package/app/server/auth/RateLimiter.ts +106 -0
  34. package/app/server/auth/contracts.ts +192 -0
  35. package/app/server/auth/guards/SessionGuard.ts +167 -0
  36. package/app/server/auth/guards/TokenGuard.ts +202 -0
  37. package/app/server/auth/index.ts +174 -0
  38. package/app/server/auth/middleware.ts +163 -0
  39. package/app/server/auth/providers/InMemoryProvider.ts +162 -0
  40. package/app/server/auth/sessions/SessionManager.ts +164 -0
  41. package/app/server/cache/CacheManager.ts +81 -0
  42. package/app/server/cache/MemoryDriver.ts +112 -0
  43. package/app/server/cache/contracts.ts +49 -0
  44. package/app/server/cache/index.ts +42 -0
  45. package/app/server/index.ts +14 -0
  46. package/app/server/live/LiveAdminPanel.ts +174 -0
  47. package/app/server/live/LiveChat.ts +78 -77
  48. package/app/server/live/LiveCounter.ts +1 -0
  49. package/app/server/live/LiveForm.ts +1 -0
  50. package/app/server/live/LiveLocalCounter.ts +38 -32
  51. package/app/server/live/LiveProtectedChat.ts +151 -0
  52. package/app/server/live/LiveRoomChat.ts +1 -0
  53. package/app/server/live/LiveUpload.ts +1 -0
  54. package/app/server/live/register-components.ts +19 -19
  55. package/app/server/routes/auth.routes.ts +278 -0
  56. package/app/server/routes/index.ts +2 -0
  57. package/config/index.ts +8 -0
  58. package/config/system/auth.config.ts +49 -0
  59. package/config/system/runtime.config.ts +4 -0
  60. package/config/system/session.config.ts +33 -0
  61. package/core/build/optimizer.ts +235 -235
  62. package/core/client/LiveComponentsProvider.tsx +76 -5
  63. package/core/client/components/Live.tsx +17 -10
  64. package/core/client/components/LiveDebugger.tsx +1324 -0
  65. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
  66. package/core/client/hooks/useLiveComponent.ts +58 -5
  67. package/core/client/hooks/useLiveDebugger.ts +392 -0
  68. package/core/client/index.ts +16 -1
  69. package/core/framework/server.ts +36 -4
  70. package/core/plugins/built-in/index.ts +134 -134
  71. package/core/plugins/built-in/live-components/commands/create-live-component.ts +19 -8
  72. package/core/plugins/built-in/monitoring/index.ts +10 -3
  73. package/core/plugins/built-in/vite/index.ts +151 -20
  74. package/core/plugins/config.ts +5 -4
  75. package/core/plugins/discovery.ts +11 -2
  76. package/core/plugins/manager.ts +11 -5
  77. package/core/plugins/module-resolver.ts +1 -1
  78. package/core/plugins/registry.ts +53 -25
  79. package/core/server/index.ts +15 -15
  80. package/core/server/live/ComponentRegistry.ts +134 -50
  81. package/core/server/live/FileUploadManager.ts +188 -24
  82. package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
  83. package/core/server/live/LiveDebugger.ts +462 -0
  84. package/core/server/live/LiveLogger.ts +144 -0
  85. package/core/server/live/LiveRoomManager.ts +22 -5
  86. package/core/server/live/StateSignature.ts +704 -643
  87. package/core/server/live/WebSocketConnectionManager.ts +11 -10
  88. package/core/server/live/auth/LiveAuthContext.ts +71 -0
  89. package/core/server/live/auth/LiveAuthManager.ts +304 -0
  90. package/core/server/live/auth/index.ts +19 -0
  91. package/core/server/live/auth/types.ts +179 -0
  92. package/core/server/live/auto-generated-components.ts +8 -2
  93. package/core/server/live/index.ts +16 -0
  94. package/core/server/live/websocket-plugin.ts +323 -22
  95. package/core/server/plugins/static-files-plugin.ts +179 -69
  96. package/core/templates/create-project.ts +0 -3
  97. package/core/types/build.ts +219 -219
  98. package/core/types/plugin.ts +107 -107
  99. package/core/types/types.ts +278 -22
  100. package/core/utils/index.ts +17 -17
  101. package/core/utils/logger/index.ts +5 -2
  102. package/core/utils/logger/startup-banner.ts +82 -82
  103. package/core/utils/version.ts +6 -6
  104. package/package.json +1 -8
  105. package/plugins/crypto-auth/index.ts +6 -0
  106. package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
  107. package/plugins/crypto-auth/server/index.ts +24 -21
  108. package/rest-tests/README.md +57 -0
  109. package/rest-tests/auth-token.http +113 -0
  110. package/rest-tests/auth.http +112 -0
  111. package/rest-tests/rooms-token.http +69 -0
  112. package/rest-tests/users-token.http +62 -0
  113. package/.dockerignore +0 -81
  114. package/Dockerfile +0 -70
  115. package/LIVE_COMPONENTS_REVIEW.md +0 -781
  116. package/app/client/src/assets/react.svg +0 -1
@@ -1,215 +1,215 @@
1
- // 🚀 Adaptive Chunk Sizing - Dynamic chunk size adjustment based on connection speed
2
- // Automatically optimizes upload speed by adjusting chunk sizes
3
-
4
- export interface AdaptiveChunkConfig {
5
- minChunkSize: number // Minimum chunk size (default: 16KB)
6
- maxChunkSize: number // Maximum chunk size (default: 1MB)
7
- initialChunkSize: number // Starting chunk size (default: 64KB)
8
- targetLatency: number // Target latency per chunk in ms (default: 200ms)
9
- adjustmentFactor: number // How aggressively to adjust (default: 1.5)
10
- measurementWindow: number // Number of chunks to measure (default: 3)
11
- }
12
-
13
- export interface ChunkMetrics {
14
- chunkIndex: number
15
- chunkSize: number
16
- startTime: number
17
- endTime: number
18
- latency: number
19
- throughput: number // bytes per second
20
- success: boolean
21
- }
22
-
23
- export class AdaptiveChunkSizer {
24
- private config: Required<AdaptiveChunkConfig>
25
- private currentChunkSize: number
26
- private metrics: ChunkMetrics[] = []
27
- private consecutiveErrors = 0
28
- private consecutiveSuccesses = 0
29
-
30
- constructor(config: Partial<AdaptiveChunkConfig> = {}) {
31
- this.config = {
32
- minChunkSize: config.minChunkSize ?? 16 * 1024, // 16KB
33
- maxChunkSize: config.maxChunkSize ?? 1024 * 1024, // 1MB
34
- initialChunkSize: config.initialChunkSize ?? 64 * 1024, // 64KB
35
- targetLatency: config.targetLatency ?? 200, // 200ms
36
- adjustmentFactor: config.adjustmentFactor ?? 1.5,
37
- measurementWindow: config.measurementWindow ?? 3
38
- }
39
-
40
- this.currentChunkSize = this.config.initialChunkSize
41
- }
42
-
43
- /**
44
- * Get the current optimal chunk size
45
- */
46
- getChunkSize(): number {
47
- return this.currentChunkSize
48
- }
49
-
50
- /**
51
- * Record the start of a chunk upload
52
- */
53
- recordChunkStart(chunkIndex: number): number {
54
- return Date.now()
55
- }
56
-
57
- /**
58
- * Record the completion of a chunk upload and adjust chunk size
59
- */
60
- recordChunkComplete(
61
- chunkIndex: number,
62
- chunkSize: number,
63
- startTime: number,
64
- success: boolean
65
- ): void {
66
- const endTime = Date.now()
67
- const latency = endTime - startTime
68
- const throughput = success ? (chunkSize / latency) * 1000 : 0 // bytes per second
69
-
70
- const metric: ChunkMetrics = {
71
- chunkIndex,
72
- chunkSize,
73
- startTime,
74
- endTime,
75
- latency,
76
- throughput,
77
- success
78
- }
79
-
80
- this.metrics.push(metric)
81
-
82
- // Keep only recent measurements
83
- if (this.metrics.length > this.config.measurementWindow * 2) {
84
- this.metrics = this.metrics.slice(-this.config.measurementWindow * 2)
85
- }
86
-
87
- if (success) {
88
- this.consecutiveSuccesses++
89
- this.consecutiveErrors = 0
90
- this.adjustChunkSizeUp(latency)
91
- } else {
92
- this.consecutiveErrors++
93
- this.consecutiveSuccesses = 0
94
- this.adjustChunkSizeDown()
95
- }
96
-
97
- console.log(`📊 Adaptive Chunk Stats:`, {
98
- chunkIndex,
99
- currentSize: this.formatBytes(this.currentChunkSize),
100
- latency: `${latency}ms`,
101
- throughput: `${this.formatBytes(throughput)}/s`,
102
- avgThroughput: `${this.formatBytes(this.getAverageThroughput())}/s`,
103
- success
104
- })
105
- }
106
-
107
- /**
108
- * Increase chunk size if connection is fast
109
- */
110
- private adjustChunkSizeUp(latency: number): void {
111
- // Only increase if we have enough successful measurements
112
- if (this.consecutiveSuccesses < 2) return
113
-
114
- // Only increase if latency is below target
115
- if (latency > this.config.targetLatency) return
116
-
117
- // Calculate new chunk size based on how much faster we are than target
118
- const latencyRatio = this.config.targetLatency / latency
119
- let newSize = Math.floor(this.currentChunkSize * Math.min(latencyRatio, this.config.adjustmentFactor))
120
-
121
- // Cap at max chunk size
122
- newSize = Math.min(newSize, this.config.maxChunkSize)
123
-
124
- if (newSize > this.currentChunkSize) {
125
- console.log(`⬆️ Increasing chunk size: ${this.formatBytes(this.currentChunkSize)} → ${this.formatBytes(newSize)}`)
126
- this.currentChunkSize = newSize
127
- }
128
- }
129
-
130
- /**
131
- * Decrease chunk size if connection is slow or unstable
132
- */
133
- private adjustChunkSizeDown(): void {
134
- // Decrease more aggressively on errors
135
- const decreaseFactor = this.consecutiveErrors > 1 ? 2 : this.config.adjustmentFactor
136
-
137
- let newSize = Math.floor(this.currentChunkSize / decreaseFactor)
138
-
139
- // Cap at min chunk size
140
- newSize = Math.max(newSize, this.config.minChunkSize)
141
-
142
- if (newSize < this.currentChunkSize) {
143
- console.log(`⬇️ Decreasing chunk size: ${this.formatBytes(this.currentChunkSize)} → ${this.formatBytes(newSize)}`)
144
- this.currentChunkSize = newSize
145
- }
146
- }
147
-
148
- /**
149
- * Get average throughput from recent measurements
150
- */
151
- getAverageThroughput(): number {
152
- if (this.metrics.length === 0) return 0
153
-
154
- const recentMetrics = this.metrics
155
- .slice(-this.config.measurementWindow)
156
- .filter(m => m.success)
157
-
158
- if (recentMetrics.length === 0) return 0
159
-
160
- const totalThroughput = recentMetrics.reduce((sum, m) => sum + m.throughput, 0)
161
- return totalThroughput / recentMetrics.length
162
- }
163
-
164
- /**
165
- * Get average latency from recent measurements
166
- */
167
- getAverageLatency(): number {
168
- if (this.metrics.length === 0) return 0
169
-
170
- const recentMetrics = this.metrics
171
- .slice(-this.config.measurementWindow)
172
- .filter(m => m.success)
173
-
174
- if (recentMetrics.length === 0) return 0
175
-
176
- const totalLatency = recentMetrics.reduce((sum, m) => sum + m.latency, 0)
177
- return totalLatency / recentMetrics.length
178
- }
179
-
180
- /**
181
- * Get current performance statistics
182
- */
183
- getStats() {
184
- return {
185
- currentChunkSize: this.currentChunkSize,
186
- averageThroughput: this.getAverageThroughput(),
187
- averageLatency: this.getAverageLatency(),
188
- consecutiveSuccesses: this.consecutiveSuccesses,
189
- consecutiveErrors: this.consecutiveErrors,
190
- totalMeasurements: this.metrics.length,
191
- config: this.config
192
- }
193
- }
194
-
195
- /**
196
- * Reset the adaptive chunking state
197
- */
198
- reset(): void {
199
- this.currentChunkSize = this.config.initialChunkSize
200
- this.metrics = []
201
- this.consecutiveErrors = 0
202
- this.consecutiveSuccesses = 0
203
- }
204
-
205
- /**
206
- * Format bytes for display
207
- */
208
- private formatBytes(bytes: number): string {
209
- if (bytes === 0) return '0 B'
210
- const k = 1024
211
- const sizes = ['B', 'KB', 'MB', 'GB']
212
- const i = Math.floor(Math.log(bytes) / Math.log(k))
213
- return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
214
- }
215
- }
1
+ // 🚀 Adaptive Chunk Sizing - Dynamic chunk size adjustment based on connection speed
2
+ // Automatically optimizes upload speed by adjusting chunk sizes
3
+
4
+ export interface AdaptiveChunkConfig {
5
+ minChunkSize: number // Minimum chunk size (default: 16KB)
6
+ maxChunkSize: number // Maximum chunk size (default: 1MB)
7
+ initialChunkSize: number // Starting chunk size (default: 64KB)
8
+ targetLatency: number // Target latency per chunk in ms (default: 200ms)
9
+ adjustmentFactor: number // How aggressively to adjust (default: 1.5)
10
+ measurementWindow: number // Number of chunks to measure (default: 3)
11
+ }
12
+
13
+ export interface ChunkMetrics {
14
+ chunkIndex: number
15
+ chunkSize: number
16
+ startTime: number
17
+ endTime: number
18
+ latency: number
19
+ throughput: number // bytes per second
20
+ success: boolean
21
+ }
22
+
23
+ export class AdaptiveChunkSizer {
24
+ private config: Required<AdaptiveChunkConfig>
25
+ private currentChunkSize: number
26
+ private metrics: ChunkMetrics[] = []
27
+ private consecutiveErrors = 0
28
+ private consecutiveSuccesses = 0
29
+
30
+ constructor(config: Partial<AdaptiveChunkConfig> = {}) {
31
+ this.config = {
32
+ minChunkSize: config.minChunkSize ?? 16 * 1024, // 16KB
33
+ maxChunkSize: config.maxChunkSize ?? 1024 * 1024, // 1MB
34
+ initialChunkSize: config.initialChunkSize ?? 64 * 1024, // 64KB
35
+ targetLatency: config.targetLatency ?? 200, // 200ms
36
+ adjustmentFactor: config.adjustmentFactor ?? 1.5,
37
+ measurementWindow: config.measurementWindow ?? 3
38
+ }
39
+
40
+ this.currentChunkSize = this.config.initialChunkSize
41
+ }
42
+
43
+ /**
44
+ * Get the current optimal chunk size
45
+ */
46
+ getChunkSize(): number {
47
+ return this.currentChunkSize
48
+ }
49
+
50
+ /**
51
+ * Record the start of a chunk upload
52
+ */
53
+ recordChunkStart(chunkIndex: number): number {
54
+ return Date.now()
55
+ }
56
+
57
+ /**
58
+ * Record the completion of a chunk upload and adjust chunk size
59
+ */
60
+ recordChunkComplete(
61
+ chunkIndex: number,
62
+ chunkSize: number,
63
+ startTime: number,
64
+ success: boolean
65
+ ): void {
66
+ const endTime = Date.now()
67
+ const latency = endTime - startTime
68
+ const throughput = success ? (chunkSize / latency) * 1000 : 0 // bytes per second
69
+
70
+ const metric: ChunkMetrics = {
71
+ chunkIndex,
72
+ chunkSize,
73
+ startTime,
74
+ endTime,
75
+ latency,
76
+ throughput,
77
+ success
78
+ }
79
+
80
+ this.metrics.push(metric)
81
+
82
+ // Keep only recent measurements
83
+ if (this.metrics.length > this.config.measurementWindow * 2) {
84
+ this.metrics = this.metrics.slice(-this.config.measurementWindow * 2)
85
+ }
86
+
87
+ if (success) {
88
+ this.consecutiveSuccesses++
89
+ this.consecutiveErrors = 0
90
+ this.adjustChunkSizeUp(latency)
91
+ } else {
92
+ this.consecutiveErrors++
93
+ this.consecutiveSuccesses = 0
94
+ this.adjustChunkSizeDown()
95
+ }
96
+
97
+ console.log(`📊 Adaptive Chunk Stats:`, {
98
+ chunkIndex,
99
+ currentSize: this.formatBytes(this.currentChunkSize),
100
+ latency: `${latency}ms`,
101
+ throughput: `${this.formatBytes(throughput)}/s`,
102
+ avgThroughput: `${this.formatBytes(this.getAverageThroughput())}/s`,
103
+ success
104
+ })
105
+ }
106
+
107
+ /**
108
+ * Increase chunk size if connection is fast
109
+ */
110
+ private adjustChunkSizeUp(latency: number): void {
111
+ // Only increase if we have enough successful measurements
112
+ if (this.consecutiveSuccesses < 2) return
113
+
114
+ // Only increase if latency is below target
115
+ if (latency > this.config.targetLatency) return
116
+
117
+ // Calculate new chunk size based on how much faster we are than target
118
+ const latencyRatio = this.config.targetLatency / latency
119
+ let newSize = Math.floor(this.currentChunkSize * Math.min(latencyRatio, this.config.adjustmentFactor))
120
+
121
+ // Cap at max chunk size
122
+ newSize = Math.min(newSize, this.config.maxChunkSize)
123
+
124
+ if (newSize > this.currentChunkSize) {
125
+ console.log(`⬆️ Increasing chunk size: ${this.formatBytes(this.currentChunkSize)} → ${this.formatBytes(newSize)}`)
126
+ this.currentChunkSize = newSize
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Decrease chunk size if connection is slow or unstable
132
+ */
133
+ private adjustChunkSizeDown(): void {
134
+ // Decrease more aggressively on errors
135
+ const decreaseFactor = this.consecutiveErrors > 1 ? 2 : this.config.adjustmentFactor
136
+
137
+ let newSize = Math.floor(this.currentChunkSize / decreaseFactor)
138
+
139
+ // Cap at min chunk size
140
+ newSize = Math.max(newSize, this.config.minChunkSize)
141
+
142
+ if (newSize < this.currentChunkSize) {
143
+ console.log(`⬇️ Decreasing chunk size: ${this.formatBytes(this.currentChunkSize)} → ${this.formatBytes(newSize)}`)
144
+ this.currentChunkSize = newSize
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Get average throughput from recent measurements
150
+ */
151
+ getAverageThroughput(): number {
152
+ if (this.metrics.length === 0) return 0
153
+
154
+ const recentMetrics = this.metrics
155
+ .slice(-this.config.measurementWindow)
156
+ .filter(m => m.success)
157
+
158
+ if (recentMetrics.length === 0) return 0
159
+
160
+ const totalThroughput = recentMetrics.reduce((sum, m) => sum + m.throughput, 0)
161
+ return totalThroughput / recentMetrics.length
162
+ }
163
+
164
+ /**
165
+ * Get average latency from recent measurements
166
+ */
167
+ getAverageLatency(): number {
168
+ if (this.metrics.length === 0) return 0
169
+
170
+ const recentMetrics = this.metrics
171
+ .slice(-this.config.measurementWindow)
172
+ .filter(m => m.success)
173
+
174
+ if (recentMetrics.length === 0) return 0
175
+
176
+ const totalLatency = recentMetrics.reduce((sum, m) => sum + m.latency, 0)
177
+ return totalLatency / recentMetrics.length
178
+ }
179
+
180
+ /**
181
+ * Get current performance statistics
182
+ */
183
+ getStats() {
184
+ return {
185
+ currentChunkSize: this.currentChunkSize,
186
+ averageThroughput: this.getAverageThroughput(),
187
+ averageLatency: this.getAverageLatency(),
188
+ consecutiveSuccesses: this.consecutiveSuccesses,
189
+ consecutiveErrors: this.consecutiveErrors,
190
+ totalMeasurements: this.metrics.length,
191
+ config: this.config
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Reset the adaptive chunking state
197
+ */
198
+ reset(): void {
199
+ this.currentChunkSize = this.config.initialChunkSize
200
+ this.metrics = []
201
+ this.consecutiveErrors = 0
202
+ this.consecutiveSuccesses = 0
203
+ }
204
+
205
+ /**
206
+ * Format bytes for display
207
+ */
208
+ private formatBytes(bytes: number): string {
209
+ if (bytes === 0) return '0 B'
210
+ const k = 1024
211
+ const sizes = ['B', 'KB', 'MB', 'GB']
212
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
213
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
214
+ }
215
+ }
@@ -67,6 +67,8 @@ export interface LiveComponentProxy<
67
67
  readonly $status: 'synced' | 'disconnected' | 'connecting' | 'reconnecting' | 'loading' | 'mounting' | 'error'
68
68
  readonly $componentId: string | null
69
69
  readonly $dirty: boolean
70
+ /** Whether the WebSocket connection is authenticated on the server */
71
+ readonly $authenticated: boolean
70
72
 
71
73
  // Methods
72
74
  $call: (action: string, payload?: any) => Promise<void>
@@ -159,12 +161,22 @@ export interface UseLiveComponentOptions extends HybridComponentOptions {
159
161
  syncMode?: 'immediate' | 'debounced' | 'manual'
160
162
  /** Persistir estado em localStorage (rehydration). Default: true */
161
163
  persistState?: boolean
164
+ /**
165
+ * Label de debug para identificar esta instância no Live Debugger.
166
+ * Aparece no lugar do componentId no painel de debug.
167
+ * Só tem efeito em development.
168
+ *
169
+ * @example
170
+ * Live.use(LiveCounter, { debugLabel: 'Header Counter' })
171
+ * Live.use(LiveChat, { debugLabel: 'Main Chat' })
172
+ */
173
+ debugLabel?: string
162
174
  }
163
175
 
164
176
  // ===== Propriedades Reservadas =====
165
177
 
166
178
  const RESERVED_PROPS = new Set([
167
- '$state', '$connected', '$loading', '$error', '$status', '$componentId', '$dirty',
179
+ '$state', '$connected', '$loading', '$error', '$status', '$componentId', '$dirty', '$authenticated',
168
180
  '$call', '$callAndWait', '$mount', '$unmount', '$refresh', '$set', '$onBroadcast', '$updateLocal',
169
181
  '$room', '$rooms', '$field', '$sync',
170
182
  'then', 'toJSON', 'valueOf', 'toString',
@@ -262,6 +274,7 @@ export function useLiveComponent<
262
274
  // WebSocket context
263
275
  const {
264
276
  connected,
277
+ authenticated: wsAuthenticated,
265
278
  sendMessage,
266
279
  sendMessageAndWait,
267
280
  registerComponent,
@@ -286,6 +299,7 @@ export function useLiveComponent<
286
299
  const broadcastHandlerRef = useRef<((event: { type: string; data: any }) => void) | null>(null)
287
300
  const roomMessageHandlers = useRef<Set<(msg: RoomServerMessage) => void>>(new Set())
288
301
  const roomManagerRef = useRef<RoomManager | null>(null)
302
+ const mountFnRef = useRef<(() => Promise<void>) | null>(null)
289
303
 
290
304
  // State
291
305
  const stateData = store((s) => s.state)
@@ -295,6 +309,7 @@ export function useLiveComponent<
295
309
  const [error, setError] = useState<string | null>(null)
296
310
  const [rehydrating, setRehydrating] = useState(false)
297
311
  const [mountFailed, setMountFailed] = useState(false) // Previne loop infinito de mount
312
+ const [authDenied, setAuthDenied] = useState(false) // Track if mount failed due to AUTH_DENIED
298
313
 
299
314
  const log = useCallback((msg: string, data?: any) => {
300
315
  if (debug) console.log(`[${componentName}] ${msg}`, data || '')
@@ -348,7 +363,7 @@ export function useLiveComponent<
348
363
  const response = await sendMessageAndWait({
349
364
  type: 'COMPONENT_MOUNT',
350
365
  componentId: instanceId.current,
351
- payload: { component: componentName, props: initialState, room, userId }
366
+ payload: { component: componentName, props: initialState, room, userId, debugLabel: options.debugLabel }
352
367
  }, 5000)
353
368
 
354
369
  if (response?.success && response?.result?.componentId) {
@@ -371,7 +386,11 @@ export function useLiveComponent<
371
386
  }
372
387
  } catch (err: any) {
373
388
  setError(err.message)
374
- setMountFailed(true) // Previne loop infinito
389
+ // Track if auth was the reason for failure
390
+ if (err.message?.includes('AUTH_DENIED')) {
391
+ setAuthDenied(true)
392
+ }
393
+ setMountFailed(true) // Previne loop infinito para TODOS os erros
375
394
  onError?.(err.message)
376
395
  if (!fallbackToLocal) throw err
377
396
  } finally {
@@ -380,6 +399,9 @@ export function useLiveComponent<
380
399
  }
381
400
  }, [connected, componentName, initialState, room, userId, sendMessageAndWait, updateState, log, onMount, onError, fallbackToLocal, mountFailed])
382
401
 
402
+ // Keep mount function ref updated
403
+ mountFnRef.current = mount
404
+
383
405
  // ===== Unmount =====
384
406
  const unmount = useCallback(async () => {
385
407
  if (!componentId || !connected) return
@@ -573,6 +595,14 @@ export function useLiveComponent<
573
595
  }
574
596
  }
575
597
  break
598
+ case 'STATE_DELTA':
599
+ if (message.payload?.delta) {
600
+ const oldState = storeRef.current?.getState().state ?? stateData
601
+ const mergedState = { ...oldState, ...message.payload.delta } as TState
602
+ updateState(mergedState)
603
+ onStateChange?.(mergedState, oldState)
604
+ }
605
+ break
576
606
  case 'STATE_REHYDRATED':
577
607
  if (message.payload?.state && message.payload?.newComponentId) {
578
608
  setComponentId(message.payload.newComponentId)
@@ -620,6 +650,28 @@ export function useLiveComponent<
620
650
  }
621
651
  }, [connected, autoMount, mount, componentId, rehydrating, rehydrate, mountFailed])
622
652
 
653
+ // ===== Auto Re-mount on Auth Change =====
654
+ // When auth changes from false to true and component failed due to AUTH_DENIED, retry mount
655
+ const prevAuthRef = useRef(wsAuthenticated)
656
+ useEffect(() => {
657
+ const wasNotAuthenticated = !prevAuthRef.current
658
+ const isNowAuthenticated = wsAuthenticated
659
+ prevAuthRef.current = wsAuthenticated
660
+
661
+ // Only retry if: auth changed from false→true AND we had an auth denial
662
+ if (wasNotAuthenticated && isNowAuthenticated && authDenied) {
663
+ log('Auth changed to authenticated, retrying mount...')
664
+ // Reset flags to allow retry
665
+ setAuthDenied(false)
666
+ setMountFailed(false)
667
+ setError(null)
668
+ mountedRef.current = false
669
+ mountingRef.current = false
670
+ // Small delay to ensure state is updated, use ref to avoid stale closure
671
+ setTimeout(() => mountFnRef.current?.(), 50)
672
+ }
673
+ }, [wsAuthenticated, authDenied, log])
674
+
623
675
  // ===== Connection Changes =====
624
676
  const prevConnected = useRef(connected)
625
677
  useEffect(() => {
@@ -708,6 +760,7 @@ export function useLiveComponent<
708
760
  case '$status': return getStatus()
709
761
  case '$componentId': return componentId
710
762
  case '$dirty': return pendingChanges.current.size > 0
763
+ case '$authenticated': return wsAuthenticated
711
764
  case '$call': return call
712
765
  case '$callAndWait': return callAndWait
713
766
  case '$mount': return mount
@@ -773,10 +826,10 @@ export function useLiveComponent<
773
826
  },
774
827
 
775
828
  ownKeys() {
776
- return [...Object.keys(stateData), '$state', '$connected', '$loading', '$error', '$status', '$componentId', '$dirty', '$call', '$callAndWait', '$mount', '$unmount', '$refresh', '$set', '$field', '$sync', '$onBroadcast', '$updateLocal', '$room', '$rooms']
829
+ return [...Object.keys(stateData), '$state', '$connected', '$loading', '$error', '$status', '$componentId', '$dirty', '$authenticated', '$call', '$callAndWait', '$mount', '$unmount', '$refresh', '$set', '$field', '$sync', '$onBroadcast', '$updateLocal', '$room', '$rooms']
777
830
  }
778
831
  })
779
- }, [stateData, connected, loading, error, componentId, call, callAndWait, mount, unmount, refresh, setProperty, optimistic, sendMessageAndWait, createFieldBinding, sync, localVersion, roomManager])
832
+ }, [stateData, connected, wsAuthenticated, loading, error, componentId, call, callAndWait, mount, unmount, refresh, setProperty, optimistic, sendMessageAndWait, createFieldBinding, sync, localVersion, roomManager])
780
833
 
781
834
  return proxy
782
835
  }