create-fluxstack 1.14.0 → 1.16.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 (76) hide show
  1. package/LLMD/INDEX.md +4 -3
  2. package/LLMD/resources/live-binary-delta.md +507 -0
  3. package/LLMD/resources/live-components.md +208 -12
  4. package/LLMD/resources/live-rooms.md +731 -333
  5. package/app/client/.live-stubs/LiveAdminPanel.js +5 -0
  6. package/app/client/.live-stubs/LiveCounter.js +9 -0
  7. package/app/client/.live-stubs/LiveForm.js +11 -0
  8. package/app/client/.live-stubs/LiveLocalCounter.js +8 -0
  9. package/app/client/.live-stubs/LivePingPong.js +10 -0
  10. package/app/client/.live-stubs/LiveRoomChat.js +11 -0
  11. package/app/client/.live-stubs/LiveSharedCounter.js +10 -0
  12. package/app/client/.live-stubs/LiveUpload.js +15 -0
  13. package/app/client/src/App.tsx +19 -7
  14. package/app/client/src/components/AppLayout.tsx +18 -10
  15. package/app/client/src/live/PingPongDemo.tsx +199 -0
  16. package/app/client/src/live/RoomChatDemo.tsx +187 -22
  17. package/app/client/src/live/SharedCounterDemo.tsx +142 -0
  18. package/app/server/auth/DevAuthProvider.ts +2 -2
  19. package/app/server/auth/JWTAuthProvider.example.ts +2 -2
  20. package/app/server/index.ts +2 -2
  21. package/app/server/live/LiveAdminPanel.ts +1 -1
  22. package/app/server/live/LivePingPong.ts +61 -0
  23. package/app/server/live/LiveProtectedChat.ts +1 -1
  24. package/app/server/live/LiveRoomChat.ts +106 -38
  25. package/app/server/live/LiveSharedCounter.ts +73 -0
  26. package/app/server/live/rooms/ChatRoom.ts +68 -0
  27. package/app/server/live/rooms/CounterRoom.ts +51 -0
  28. package/app/server/live/rooms/DirectoryRoom.ts +42 -0
  29. package/app/server/live/rooms/PingRoom.ts +40 -0
  30. package/app/server/routes/room.routes.ts +1 -2
  31. package/core/build/live-components-generator.ts +11 -2
  32. package/core/build/vite-plugins.ts +28 -0
  33. package/core/client/hooks/useLiveUpload.ts +3 -4
  34. package/core/client/index.ts +25 -35
  35. package/core/framework/server.ts +1 -1
  36. package/core/server/index.ts +1 -2
  37. package/core/server/live/auto-generated-components.ts +5 -8
  38. package/core/server/live/index.ts +90 -21
  39. package/core/server/live/websocket-plugin.ts +54 -1079
  40. package/core/types/types.ts +76 -1025
  41. package/core/utils/version.ts +1 -1
  42. package/create-fluxstack.ts +1 -1
  43. package/package.json +100 -95
  44. package/plugins/crypto-auth/index.ts +1 -1
  45. package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +2 -2
  46. package/tsconfig.json +4 -1
  47. package/vite.config.ts +40 -12
  48. package/app/client/src/live/ChatDemo.tsx +0 -107
  49. package/app/client/src/live/LiveDebuggerPanel.tsx +0 -779
  50. package/app/server/live/LiveChat.ts +0 -78
  51. package/core/client/LiveComponentsProvider.tsx +0 -531
  52. package/core/client/components/Live.tsx +0 -111
  53. package/core/client/components/LiveDebugger.tsx +0 -1324
  54. package/core/client/hooks/AdaptiveChunkSizer.ts +0 -215
  55. package/core/client/hooks/state-validator.ts +0 -130
  56. package/core/client/hooks/useChunkedUpload.ts +0 -359
  57. package/core/client/hooks/useLiveChunkedUpload.ts +0 -87
  58. package/core/client/hooks/useLiveComponent.ts +0 -853
  59. package/core/client/hooks/useLiveDebugger.ts +0 -392
  60. package/core/client/hooks/useRoom.ts +0 -409
  61. package/core/client/hooks/useRoomProxy.ts +0 -382
  62. package/core/server/live/ComponentRegistry.ts +0 -1128
  63. package/core/server/live/FileUploadManager.ts +0 -446
  64. package/core/server/live/LiveComponentPerformanceMonitor.ts +0 -931
  65. package/core/server/live/LiveDebugger.ts +0 -462
  66. package/core/server/live/LiveLogger.ts +0 -144
  67. package/core/server/live/LiveRoomManager.ts +0 -278
  68. package/core/server/live/RoomEventBus.ts +0 -234
  69. package/core/server/live/RoomStateManager.ts +0 -172
  70. package/core/server/live/SingleConnectionManager.ts +0 -0
  71. package/core/server/live/StateSignature.ts +0 -705
  72. package/core/server/live/WebSocketConnectionManager.ts +0 -710
  73. package/core/server/live/auth/LiveAuthContext.ts +0 -71
  74. package/core/server/live/auth/LiveAuthManager.ts +0 -304
  75. package/core/server/live/auth/index.ts +0 -19
  76. package/core/server/live/auth/types.ts +0 -179
@@ -1,12 +1,16 @@
1
1
  # Live Components
2
2
 
3
- **Version:** 1.13.0 | **Updated:** 2025-02-09
3
+ **Version:** 1.14.0 | **Updated:** 2025-02-27
4
4
 
5
5
  ## Quick Facts
6
6
 
7
7
  - Server-side state management with WebSocket sync
8
8
  - **Direct state access** - `this.count++` auto-syncs (v1.13.0)
9
+ - **Lifecycle hooks** - `onMount()` / `onDestroy()` for proper initialization and cleanup (v1.14.0)
10
+ - **HMR persistence** - `static persistent` + `this.$persistent` survives hot reloads (v1.14.0)
11
+ - **Singleton components** - `static singleton = true` for shared server-side instances (v1.14.0)
9
12
  - **Mandatory `publicActions`** - Only whitelisted methods are callable from client (secure by default)
13
+ - **Helpful error messages** - Forgotten `publicActions` entries show exactly what to fix (v1.14.0)
10
14
  - Automatic state persistence and re-hydration (with anti-replay nonces)
11
15
  - Room-based event system for multi-user sync
12
16
  - Type-safe client-server communication (FluxStackWebSocket)
@@ -53,6 +57,13 @@ export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState>
53
57
  }
54
58
  ```
55
59
 
60
+ ### Key Changes in v1.14.0
61
+
62
+ 1. **Lifecycle hooks** - `onMount()` (async) and `onDestroy()` (sync) replace constructor/destroy workarounds
63
+ 2. **HMR persistence** - `static persistent` + `this.$persistent` for data that survives hot module reloads
64
+ 3. **Singleton components** - `static singleton = true` for shared state across all connected clients
65
+ 4. **Better publicActions errors** - Clear message when a method exists but is missing from `publicActions`
66
+
56
67
  ### Key Changes in v1.13.0
57
68
 
58
69
  1. **Direct state access** - `this.count++` instead of `this.state.count++`
@@ -111,27 +122,207 @@ export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState>
111
122
  }
112
123
  ```
113
124
 
114
- ## Lifecycle Methods
125
+ ## Lifecycle Hooks (v1.14.0)
126
+
127
+ Full lifecycle hook system — no more constructor workarounds:
115
128
 
116
129
  ```typescript
117
130
  export class MyComponent extends LiveComponent<typeof MyComponent.defaultState> {
118
131
  static componentName = 'MyComponent'
132
+ static publicActions = ['doWork'] as const
133
+ static defaultState = { users: [] as string[], ready: false, currentRoom: '' }
134
+
135
+ private _pollTimer?: NodeJS.Timeout
136
+
137
+ // 1️⃣ Called when WebSocket connection is established (before onMount)
138
+ protected onConnect() {
139
+ console.log('WebSocket connected for this component')
140
+ }
141
+
142
+ // 2️⃣ Called AFTER component is fully mounted (rooms, auth, injections ready)
143
+ // Can be async!
144
+ protected async onMount() {
145
+ this.$room.join()
146
+ this.$room.on('user:joined', (user) => {
147
+ this.state.users = [...this.state.users, user]
148
+ })
149
+ const data = await fetchInitialData(this.$auth.user?.id)
150
+ this.state.ready = true
151
+ this._pollTimer = setInterval(() => this.poll(), 5000)
152
+ }
153
+
154
+ // Called after state is restored from localStorage (rehydration)
155
+ protected onRehydrate(previousState: typeof MyComponent.defaultState) {
156
+ if (!previousState.ready) {
157
+ this.state.ready = false // Re-validate stale state
158
+ }
159
+ }
160
+
161
+ // Called after any state mutation (proxy or setState)
162
+ protected onStateChange(changes: Partial<typeof MyComponent.defaultState>) {
163
+ if ('users' in changes) {
164
+ console.log(`User count: ${this.state.users.length}`)
165
+ }
166
+ }
167
+
168
+ // Called when joining a room
169
+ protected onRoomJoin(roomId: string) {
170
+ this.state.currentRoom = roomId
171
+ }
172
+
173
+ // Called when leaving a room
174
+ protected onRoomLeave(roomId: string) {
175
+ if (this.state.currentRoom === roomId) this.state.currentRoom = ''
176
+ }
177
+
178
+ // Called before each action — return false to cancel
179
+ protected onAction(action: string, payload: any) {
180
+ console.log(`[${this.id}] ${action}`, payload)
181
+ // return false // ← would cancel the action
182
+ }
183
+
184
+ // Called when WebSocket drops (NOT on intentional unmount)
185
+ protected onDisconnect() {
186
+ console.log('Connection lost — saving recovery data')
187
+ }
188
+
189
+ // Called BEFORE internal cleanup (sync only)
190
+ protected onDestroy() {
191
+ clearInterval(this._pollTimer)
192
+ }
193
+
194
+ async doWork() { /* ... */ }
195
+ private poll() { /* ... */ }
196
+ }
197
+ ```
198
+
199
+ ### Lifecycle Order
200
+
201
+ ```
202
+ WebSocket connects
203
+ └→ onConnect()
204
+ └→ onMount() ← async, rooms/auth ready
205
+ └→ [component active]
206
+ ├→ onAction(action, payload) ← before each action (return false to cancel)
207
+ ├→ onStateChange(changes) ← after each state mutation
208
+ ├→ onRoomJoin(roomId) ← when joining a room
209
+ └→ onRoomLeave(roomId) ← when leaving a room
210
+
211
+ Connection drops:
212
+ └→ onDisconnect() ← only on unexpected disconnect
213
+ └→ onDestroy() ← sync, before internal cleanup
214
+
215
+ Rehydration (reconnect with saved state):
216
+ └→ onConnect()
217
+ └→ onRehydrate(previousState)
218
+ └→ onMount()
219
+ ```
220
+
221
+ ### Rules
222
+
223
+ | Hook | Async? | When |
224
+ |------|--------|------|
225
+ | `onConnect()` | No | WebSocket established, before mount |
226
+ | `onMount()` | **Yes** | After all setup (rooms, auth, DI) |
227
+ | `onRehydrate(prevState)` | No | After state restored from localStorage |
228
+ | `onStateChange(changes)` | No | After every state mutation |
229
+ | `onRoomJoin(roomId)` | No | After `$room.join()` |
230
+ | `onRoomLeave(roomId)` | No | After `$room.leave()` |
231
+ | `onAction(action, payload)` | **Yes** | Before action execution (return `false` to cancel) |
232
+ | `onDisconnect()` | No | Connection lost (NOT intentional unmount) |
233
+ | `onDestroy()` | No | Before internal cleanup |
234
+
235
+ - All hooks are optional — override only what you need
236
+ - All hook errors are caught and logged — they never break the system
237
+ - Constructor is still needed ONLY for `this.onRoomEvent()` subscriptions
238
+ - All hooks are in BLOCKED_ACTIONS — clients cannot call them remotely
239
+
240
+ ## HMR Persistence (v1.14.0)
241
+
242
+ Data in `static persistent` survives Hot Module Replacement reloads via `globalThis`:
243
+
244
+ ```typescript
245
+ export class LiveMigration extends LiveComponent<typeof LiveMigration.defaultState> {
246
+ static componentName = 'LiveMigration'
247
+ static publicActions = ['runMigration'] as const
248
+ static defaultState = { status: 'idle', lastResult: '' }
249
+
250
+ // Define shape and defaults for persistent data
251
+ static persistent = {
252
+ cache: {} as Record<string, any>,
253
+ runCount: 0
254
+ }
255
+
256
+ protected onMount() {
257
+ this.$persistent.runCount++
258
+ console.log(`Mount #${this.$persistent.runCount}`) // Survives HMR!
259
+ }
260
+
261
+ async runMigration(payload: { key: string }) {
262
+ // Check HMR-safe cache
263
+ if (this.$persistent.cache[payload.key]) {
264
+ return { cached: true, result: this.$persistent.cache[payload.key] }
265
+ }
266
+
267
+ const result = await expensiveComputation(payload.key)
268
+ this.$persistent.cache[payload.key] = result
269
+ this.state.lastResult = result
270
+ return { cached: false, result }
271
+ }
272
+ }
273
+ ```
274
+
275
+ **Key facts:**
276
+ - `this.$persistent` reads from `globalThis.__fluxstack_persistent_{ComponentName}`
277
+ - Each component class has its own namespace
278
+ - Defaults come from `static persistent` — initialized once, then persisted
279
+ - Not sent to client — server-only
280
+ - `$persistent` is in BLOCKED_ACTIONS (can't be called from client)
281
+
282
+ ## Singleton Components (v1.14.0)
283
+
284
+ When `static singleton = true`, only ONE server-side instance exists. All clients share the same state:
285
+
286
+ ```typescript
287
+ export class LiveDashboard extends LiveComponent<typeof LiveDashboard.defaultState> {
288
+ static componentName = 'LiveDashboard'
289
+ static singleton = true // All clients share this instance
290
+ static publicActions = ['refresh', 'addAlert'] as const
119
291
  static defaultState = {
120
- // Define state here
292
+ visitors: 0,
293
+ alerts: [] as string[],
294
+ lastRefresh: ''
121
295
  }
122
296
 
123
- // Constructor ONLY needed if:
124
- // - Subscribing to room events
125
- // - Custom initialization logic
126
- // Otherwise, omit it entirely!
297
+ protected async onMount() {
298
+ this.state.visitors++
299
+ this.state.lastRefresh = new Date().toISOString()
300
+ }
127
301
 
128
- destroy() {
129
- // Cleanup subscriptions, timers, etc.
130
- super.destroy()
302
+ async refresh() {
303
+ const data = await fetchDashboardData()
304
+ this.setState(data) // Broadcasts to ALL connected clients
305
+ return { success: true }
306
+ }
307
+
308
+ async addAlert(payload: { message: string }) {
309
+ this.state.alerts = [...this.state.alerts, payload.message]
310
+ // All clients see the new alert instantly
311
+ return { success: true }
131
312
  }
132
313
  }
133
314
  ```
134
315
 
316
+ **How it works:**
317
+ - First client to mount creates the singleton instance
318
+ - Subsequent clients join the existing instance and receive current state
319
+ - `emit` / `setState` / `this.state.x = y` broadcast to ALL connected WebSockets
320
+ - When a client disconnects, it's removed from the singleton's connections
321
+ - When the LAST client disconnects, the singleton is destroyed
322
+ - Stats visible at `/api/live/stats` (shows singleton connection counts)
323
+
324
+ **Use cases:** Shared dashboards, global migration state, admin panels, live counters
325
+
135
326
  ## State Management
136
327
 
137
328
  ### Reactive State Proxy (How It Works)
@@ -665,19 +856,23 @@ export class MyComponent extends LiveComponent<State> {
665
856
  - Define `static defaultState` inside the class
666
857
  - Use `typeof ClassName.defaultState` for type parameter
667
858
  - Use `declare` for each state property (TypeScript type hint)
668
- - Call `super.destroy()` in destroy method if overriding
859
+ - Use `onMount()` for async initialization (rooms, auth, data fetching)
860
+ - Use `onDestroy()` for cleanup (timers, connections) — sync only
669
861
  - Use `emitRoomEventWithState` for state changes in rooms
670
862
  - Handle errors in actions (throw Error)
671
863
  - Add client link: `import type { Demo as _Client } from '@client/...'`
864
+ - Use `$persistent` for data that should survive HMR reloads
865
+ - Use `static singleton = true` for shared cross-client state
672
866
 
673
867
  **NEVER:**
674
868
  - Omit `static publicActions` (component will deny ALL remote actions)
675
869
  - Export separate `defaultState` constant (use static)
676
870
  - Create constructor just to call super() (not needed)
677
871
  - Forget `static componentName` (breaks minification)
872
+ - Override `destroy()` directly — use `onDestroy()` instead (v1.14.0)
678
873
  - Emit room events without subscribing first
679
874
  - Store non-serializable data in state
680
- - Use reserved names for state properties (id, state, ws, room, userId, $room, $rooms, $private, broadcastToRoom, roomType)
875
+ - Use reserved names for state properties (id, state, ws, room, userId, $room, $rooms, $private, $persistent, broadcastToRoom, roomType)
681
876
  - Include `setValue` in `publicActions` unless you trust clients to modify any state key
682
877
  - Store sensitive data (tokens, API keys, secrets) in `state` — use `$private` instead
683
878
 
@@ -840,6 +1035,7 @@ export function UploadDemo() {
840
1035
  - [Live Logging](./live-logging.md) - Per-component logging control
841
1036
  - [Live Rooms](./live-rooms.md) - Multi-room real-time communication
842
1037
  - [Live Upload](./live-upload.md) - Chunked file upload
1038
+ - [Live Binary Delta](./live-binary-delta.md) - High-frequency binary state sync
843
1039
  - [Project Structure](../patterns/project-structure.md)
844
1040
  - [Type Safety Patterns](../patterns/type-safety.md)
845
1041
  - [WebSocket Plugin](../core/plugin-system.md)