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
@@ -2,6 +2,27 @@
2
2
 
3
3
  import { roomEvents } from '@core/server/live/RoomEventBus'
4
4
  import { liveRoomManager } from '@core/server/live/LiveRoomManager'
5
+ import { ANONYMOUS_CONTEXT } from '@core/server/live/auth/LiveAuthContext'
6
+ import { liveLog, liveWarn } from '@core/server/live/LiveLogger'
7
+
8
+ // ===== Debug Instrumentation (injectable to avoid client-side import) =====
9
+ // The real debugger is injected by ComponentRegistry at server startup.
10
+ // This avoids importing server-only LiveDebugger.ts from this shared types file.
11
+ interface LiveDebuggerInterface {
12
+ trackStateChange(componentId: string, delta: Record<string, unknown>, fullState: Record<string, unknown>, source?: string): void
13
+ trackActionCall(componentId: string, action: string, payload: unknown): void
14
+ trackActionResult(componentId: string, action: string, result: unknown, duration: number): void
15
+ trackActionError(componentId: string, action: string, error: string, duration: number): void
16
+ trackRoomEmit(componentId: string, roomId: string, event: string, data: unknown): void
17
+ }
18
+
19
+ let _liveDebugger: LiveDebuggerInterface | null = null
20
+
21
+ /** @internal Called by ComponentRegistry to inject the debugger instance */
22
+ export function _setLiveDebugger(dbg: LiveDebuggerInterface): void {
23
+ _liveDebugger = dbg
24
+ }
25
+ import type { LiveAuthContext, LiveComponentAuth, LiveActionAuthMap } from '@core/server/live/auth/types'
5
26
  import type { ServerWebSocket } from 'bun'
6
27
 
7
28
  // ============================================
@@ -18,6 +39,8 @@ export interface FluxStackWSData {
18
39
  subscriptions: Set<string>
19
40
  connectedAt: Date
20
41
  userId?: string
42
+ /** Contexto de autenticação da conexão WebSocket */
43
+ authContext?: LiveAuthContext
21
44
  }
22
45
 
23
46
  /**
@@ -46,9 +69,11 @@ export type FluxStackServerWebSocket = ServerWebSocket<FluxStackWSData>
46
69
  export interface LiveMessage {
47
70
  type: 'COMPONENT_MOUNT' | 'COMPONENT_UNMOUNT' |
48
71
  'COMPONENT_REHYDRATE' | 'COMPONENT_ACTION' | 'CALL_ACTION' |
49
- 'ACTION_RESPONSE' | 'PROPERTY_UPDATE' | 'STATE_UPDATE' | 'STATE_REHYDRATED' |
72
+ 'ACTION_RESPONSE' | 'PROPERTY_UPDATE' | 'STATE_UPDATE' | 'STATE_DELTA' | 'STATE_REHYDRATED' |
50
73
  'ERROR' | 'BROADCAST' | 'FILE_UPLOAD_START' | 'FILE_UPLOAD_CHUNK' | 'FILE_UPLOAD_COMPLETE' |
51
74
  'COMPONENT_PING' | 'COMPONENT_PONG' |
75
+ // Auth system message
76
+ 'AUTH' |
52
77
  // Room system messages
53
78
  'ROOM_JOIN' | 'ROOM_LEAVE' | 'ROOM_EMIT' | 'ROOM_STATE_SET' | 'ROOM_STATE_GET'
54
79
  componentId: string
@@ -117,7 +142,9 @@ export interface WebSocketMessage {
117
142
  }
118
143
 
119
144
  export interface WebSocketResponse {
120
- type: 'MESSAGE_RESPONSE' | 'CONNECTION_ESTABLISHED' | 'ERROR' | 'BROADCAST' | 'ACTION_RESPONSE' | 'COMPONENT_MOUNTED' | 'COMPONENT_REHYDRATED' | 'STATE_UPDATE' | 'STATE_REHYDRATED' | 'FILE_UPLOAD_PROGRESS' | 'FILE_UPLOAD_COMPLETE' | 'FILE_UPLOAD_ERROR' | 'FILE_UPLOAD_START_RESPONSE' | 'COMPONENT_PONG' |
145
+ type: 'MESSAGE_RESPONSE' | 'CONNECTION_ESTABLISHED' | 'ERROR' | 'BROADCAST' | 'ACTION_RESPONSE' | 'COMPONENT_MOUNTED' | 'COMPONENT_REHYDRATED' | 'STATE_UPDATE' | 'STATE_DELTA' | 'STATE_REHYDRATED' | 'FILE_UPLOAD_PROGRESS' | 'FILE_UPLOAD_COMPLETE' | 'FILE_UPLOAD_ERROR' | 'FILE_UPLOAD_START_RESPONSE' | 'COMPONENT_PONG' |
146
+ // Auth system response
147
+ 'AUTH_RESPONSE' |
121
148
  // Room system responses
122
149
  'ROOM_EVENT' | 'ROOM_STATE' | 'ROOM_SYSTEM' | 'ROOM_JOINED' | 'ROOM_LEFT'
123
150
  originalType?: string
@@ -208,12 +235,54 @@ export interface ServerRoomProxy<TState = any, TEvents extends Record<string, an
208
235
  setState: (updates: Partial<TState>) => void
209
236
  }
210
237
 
211
- export abstract class LiveComponent<TState = ComponentState> {
238
+ export abstract class LiveComponent<TState = ComponentState, TPrivate extends Record<string, any> = Record<string, any>> {
212
239
  /** Component name for registry lookup - must be defined in subclasses */
213
240
  static componentName: string
214
241
  /** Default state - must be defined in subclasses */
215
242
  static defaultState: any
216
243
 
244
+ /**
245
+ * Per-component logging control. Silent by default.
246
+ *
247
+ * @example
248
+ * // Enable all log categories
249
+ * static logging = true
250
+ *
251
+ * // Enable specific categories only
252
+ * static logging = ['lifecycle', 'messages'] as const
253
+ *
254
+ * // Disabled (default — omit or set false)
255
+ * static logging = false
256
+ *
257
+ * Categories: 'lifecycle' | 'messages' | 'state' | 'performance' | 'rooms' | 'websocket'
258
+ */
259
+ static logging?: boolean | readonly ('lifecycle' | 'messages' | 'state' | 'performance' | 'rooms' | 'websocket')[]
260
+
261
+ /**
262
+ * Configuração de autenticação do componente.
263
+ * Define se auth é obrigatória e quais roles/permissions são necessárias.
264
+ *
265
+ * @example
266
+ * static auth: LiveComponentAuth = {
267
+ * required: true,
268
+ * roles: ['admin', 'moderator'],
269
+ * permissions: ['chat.read'],
270
+ * }
271
+ */
272
+ static auth?: LiveComponentAuth
273
+
274
+ /**
275
+ * Configuração de autenticação por action.
276
+ * Permite controle granular de permissões por método.
277
+ *
278
+ * @example
279
+ * static actionAuth: LiveActionAuthMap = {
280
+ * deleteMessage: { permissions: ['chat.admin'] },
281
+ * sendMessage: { permissions: ['chat.write'] },
282
+ * }
283
+ */
284
+ static actionAuth?: LiveActionAuthMap
285
+
217
286
  public readonly id: string
218
287
  private _state: TState
219
288
  public state: TState // Proxy wrapper
@@ -222,6 +291,12 @@ export abstract class LiveComponent<TState = ComponentState> {
222
291
  public userId?: string
223
292
  public broadcastToRoom: (message: BroadcastMessage) => void = () => {} // Will be injected by registry
224
293
 
294
+ // 🔒 Server-only private state (NEVER sent to client)
295
+ private _privateState: TPrivate = {} as TPrivate
296
+
297
+ // Auth context (injected by registry during mount)
298
+ private _authContext: LiveAuthContext = ANONYMOUS_CONTEXT
299
+
225
300
  // Room event subscriptions (cleaned up on destroy)
226
301
  private roomEventUnsubscribers: (() => void)[] = []
227
302
  private joinedRooms: Set<string> = new Set()
@@ -250,9 +325,39 @@ export abstract class LiveComponent<TState = ComponentState> {
250
325
  this.joinedRooms.add(this.room)
251
326
  liveRoomManager.joinRoom(this.id, this.room, this.ws)
252
327
  }
328
+
329
+ // 🔥 Create direct property accessors (this.count instead of this.state.count)
330
+ this.createDirectStateAccessors()
331
+ }
332
+
333
+ // Create getters/setters for each state property directly on `this`
334
+ private createDirectStateAccessors() {
335
+ // Properties that should NOT become state accessors
336
+ const forbidden = new Set([
337
+ // Instance properties
338
+ ...Object.keys(this),
339
+ // Prototype methods
340
+ ...Object.getOwnPropertyNames(Object.getPrototypeOf(this)),
341
+ // Known internal properties
342
+ 'state', '_state', 'ws', 'id', 'room', 'userId', 'broadcastToRoom',
343
+ '$private', '_privateState',
344
+ '$room', '$rooms', 'roomType', 'roomHandles', 'joinedRooms', 'roomEventUnsubscribers'
345
+ ])
346
+
347
+ // Create accessor for each state key
348
+ for (const key of Object.keys(this._state as object)) {
349
+ if (!forbidden.has(key)) {
350
+ Object.defineProperty(this, key, {
351
+ get: () => (this._state as any)[key],
352
+ set: (value) => { (this.state as any)[key] = value }, // Uses proxy for auto-sync
353
+ enumerable: true,
354
+ configurable: true
355
+ })
356
+ }
357
+ }
253
358
  }
254
359
 
255
- // Create a Proxy that auto-emits STATE_UPDATE on any mutation
360
+ // Create a Proxy that auto-emits STATE_DELTA on any mutation
256
361
  private createStateProxy(state: TState): TState {
257
362
  const self = this
258
363
  return new Proxy(state as object, {
@@ -260,8 +365,15 @@ export abstract class LiveComponent<TState = ComponentState> {
260
365
  const oldValue = (target as any)[prop]
261
366
  if (oldValue !== value) {
262
367
  (target as any)[prop] = value
263
- // Auto-sync to frontend
264
- self.emit('STATE_UPDATE', { state: self._state })
368
+ // Delta sync - send only the changed property
369
+ self.emit('STATE_DELTA', { delta: { [prop]: value } })
370
+ // Debug: track proxy mutation
371
+ _liveDebugger?.trackStateChange(
372
+ self.id,
373
+ { [prop]: value } as Record<string, unknown>,
374
+ target as Record<string, unknown>,
375
+ 'proxy'
376
+ )
265
377
  }
266
378
  return true
267
379
  },
@@ -271,6 +383,33 @@ export abstract class LiveComponent<TState = ComponentState> {
271
383
  }) as TState
272
384
  }
273
385
 
386
+ // ========================================
387
+ // 🔒 $private - Server-Only State
388
+ // ========================================
389
+
390
+ /**
391
+ * Server-only state that is NEVER synchronized with the client.
392
+ * Use this for sensitive data like tokens, API keys, internal IDs, etc.
393
+ *
394
+ * Unlike `this.state`, mutations to `$private` do NOT trigger
395
+ * STATE_DELTA or STATE_UPDATE messages.
396
+ *
397
+ * ⚠️ Private state is lost on rehydration (since it's never sent to client).
398
+ * Re-populate it in your action handlers as needed.
399
+ *
400
+ * @example
401
+ * async connect(payload: { token: string }) {
402
+ * this.$private.token = payload.token
403
+ * this.$private.apiKey = await getKey()
404
+ *
405
+ * // Only UI-relevant data goes to state (synced with client)
406
+ * this.state.messages = await fetchMessages(this.$private.token)
407
+ * }
408
+ */
409
+ public get $private(): TPrivate {
410
+ return this._privateState
411
+ }
412
+
274
413
  // ========================================
275
414
  // 🔥 $room - Sistema de Salas Unificado
276
415
  // ========================================
@@ -388,11 +527,55 @@ export abstract class LiveComponent<TState = ComponentState> {
388
527
  return Array.from(this.joinedRooms)
389
528
  }
390
529
 
391
- // State management (batch update - single emit)
530
+ // ========================================
531
+ // 🔒 $auth - Contexto de Autenticação
532
+ // ========================================
533
+
534
+ /**
535
+ * Acessa o contexto de autenticação do usuário atual.
536
+ * Disponível após o mount do componente.
537
+ *
538
+ * @example
539
+ * async sendMessage(payload: { text: string }) {
540
+ * if (!this.$auth.authenticated) {
541
+ * throw new Error('Login required')
542
+ * }
543
+ *
544
+ * const userId = this.$auth.user!.id
545
+ * const isAdmin = this.$auth.hasRole('admin')
546
+ * const canDelete = this.$auth.hasPermission('chat.admin')
547
+ * }
548
+ */
549
+ public get $auth(): LiveAuthContext {
550
+ return this._authContext
551
+ }
552
+
553
+ /**
554
+ * Injeta o contexto de autenticação no componente.
555
+ * Chamado internamente pelo ComponentRegistry durante o mount.
556
+ * @internal
557
+ */
558
+ public setAuthContext(context: LiveAuthContext): void {
559
+ this._authContext = context
560
+ // Atualiza userId se disponível no auth context
561
+ if (context.authenticated && context.user?.id && !this.userId) {
562
+ this.userId = context.user.id
563
+ }
564
+ }
565
+
566
+ // State management (batch update - single emit with delta)
392
567
  public setState(updates: Partial<TState> | ((prev: TState) => Partial<TState>)) {
393
568
  const newUpdates = typeof updates === 'function' ? updates(this._state) : updates
394
569
  Object.assign(this._state as object, newUpdates)
395
- this.emit('STATE_UPDATE', { state: this._state })
570
+ // Delta sync - send only the changed properties
571
+ this.emit('STATE_DELTA', { delta: newUpdates })
572
+ // Debug: track state change
573
+ _liveDebugger?.trackStateChange(
574
+ this.id,
575
+ newUpdates as Record<string, unknown>,
576
+ this._state as Record<string, unknown>,
577
+ 'setState'
578
+ )
396
579
  }
397
580
 
398
581
  // Generic setValue action - set any state key with type safety
@@ -403,23 +586,86 @@ export abstract class LiveComponent<TState = ComponentState> {
403
586
  return { success: true, key, value }
404
587
  }
405
588
 
406
- // Execute action safely
589
+ /**
590
+ * 🔒 REQUIRED: List of methods that are explicitly callable from the client.
591
+ * ONLY these methods can be called via CALL_ACTION.
592
+ * Components without publicActions will deny ALL remote actions (secure by default).
593
+ *
594
+ * @example
595
+ * static publicActions = ['sendMessage', 'deleteMessage', 'join'] as const
596
+ */
597
+ static publicActions?: readonly string[]
598
+
599
+ // Internal methods that must NEVER be callable from the client
600
+ private static readonly BLOCKED_ACTIONS: ReadonlySet<string> = new Set([
601
+ // Lifecycle & internal
602
+ 'constructor', 'destroy', 'executeAction', 'getSerializableState',
603
+ // State management internals
604
+ 'setState', 'emit', 'broadcast', 'broadcastToRoom',
605
+ 'createStateProxy', 'createDirectStateAccessors', 'generateId',
606
+ // Auth internals
607
+ 'setAuthContext', '$auth',
608
+ // Private state internals
609
+ '$private', '_privateState',
610
+ // Room internals
611
+ '$room', '$rooms', 'subscribeToRoom', 'unsubscribeFromRoom',
612
+ 'emitRoomEvent', 'onRoomEvent', 'emitRoomEventWithState',
613
+ ])
614
+
615
+ // Execute action safely with security validation
407
616
  public async executeAction(action: string, payload: any): Promise<any> {
617
+ const actionStart = Date.now()
408
618
  try {
409
- // Check if method exists
619
+ // 🔒 Security: Block internal/protected methods from being called remotely
620
+ if ((LiveComponent.BLOCKED_ACTIONS as Set<string>).has(action)) {
621
+ throw new Error(`Action '${action}' is not callable`)
622
+ }
623
+
624
+ // 🔒 Security: Block private methods (prefixed with _ or #)
625
+ if (action.startsWith('_') || action.startsWith('#')) {
626
+ throw new Error(`Action '${action}' is not callable`)
627
+ }
628
+
629
+ // 🔒 Security: publicActions whitelist is MANDATORY
630
+ // Components without publicActions deny ALL remote actions (secure by default)
631
+ const componentClass = this.constructor as typeof LiveComponent
632
+ const publicActions = componentClass.publicActions
633
+ if (!publicActions) {
634
+ console.warn(`🔒 [SECURITY] Component '${componentClass.componentName || componentClass.name}' has no publicActions defined. All remote actions are blocked. Define static publicActions to allow specific actions.`)
635
+ throw new Error(`Action '${action}' is not callable - component has no publicActions defined`)
636
+ }
637
+ if (!publicActions.includes(action)) {
638
+ throw new Error(`Action '${action}' is not callable`)
639
+ }
640
+
641
+ // Check if method exists on the instance
410
642
  const method = (this as any)[action]
411
643
  if (typeof method !== 'function') {
412
644
  throw new Error(`Action '${action}' not found on component`)
413
645
  }
414
646
 
647
+ // 🔒 Security: Block inherited Object.prototype methods
648
+ if (Object.prototype.hasOwnProperty.call(Object.prototype, action)) {
649
+ throw new Error(`Action '${action}' is not callable`)
650
+ }
651
+
652
+ // Debug: track action call
653
+ _liveDebugger?.trackActionCall(this.id, action, payload)
654
+
415
655
  // Execute method
416
656
  const result = await method.call(this, payload)
657
+
658
+ // Debug: track action result
659
+ _liveDebugger?.trackActionResult(this.id, action, result, Date.now() - actionStart)
660
+
417
661
  return result
418
662
  } catch (error: any) {
419
- this.emit('ERROR', {
420
- action,
421
- error: error.message,
422
- stack: error.stack
663
+ // Debug: track action error
664
+ _liveDebugger?.trackActionError(this.id, action, error.message, Date.now() - actionStart)
665
+
666
+ this.emit('ERROR', {
667
+ action,
668
+ error: error.message
423
669
  })
424
670
  throw error
425
671
  }
@@ -444,7 +690,7 @@ export abstract class LiveComponent<TState = ComponentState> {
444
690
  // Broadcast to all clients in room (via WebSocket)
445
691
  protected broadcast(type: string, payload: any, excludeCurrentUser = false) {
446
692
  if (!this.room) {
447
- console.warn(`⚠️ [${this.id}] Cannot broadcast '${type}' - no room set`)
693
+ liveWarn('rooms', this.id, `⚠️ [${this.id}] Cannot broadcast '${type}' - no room set`)
448
694
  return
449
695
  }
450
696
 
@@ -455,7 +701,7 @@ export abstract class LiveComponent<TState = ComponentState> {
455
701
  excludeUser: excludeCurrentUser ? this.userId : undefined
456
702
  }
457
703
 
458
- console.log(`📤 [${this.id}] Broadcasting '${type}' to room '${this.room}'`)
704
+ liveLog('rooms', this.id, `📤 [${this.id}] Broadcasting '${type}' to room '${this.room}'`)
459
705
 
460
706
  // This will be handled by the registry
461
707
  this.broadcastToRoom(message)
@@ -475,14 +721,18 @@ export abstract class LiveComponent<TState = ComponentState> {
475
721
  */
476
722
  protected emitRoomEvent(event: string, data: any, notifySelf = false): number {
477
723
  if (!this.room) {
478
- console.warn(`⚠️ [${this.id}] Cannot emit room event '${event}' - no room set`)
724
+ liveWarn('rooms', this.id, `⚠️ [${this.id}] Cannot emit room event '${event}' - no room set`)
479
725
  return 0
480
726
  }
481
727
 
482
728
  const excludeId = notifySelf ? undefined : this.id
483
729
  const notified = roomEvents.emit(this.roomType, this.room, event, data, excludeId)
484
730
 
485
- console.log(`📡 [${this.id}] Room event '${event}' → ${notified} components`)
731
+ liveLog('rooms', this.id, `📡 [${this.id}] Room event '${event}' → ${notified} components`)
732
+
733
+ // Debug: track room emit
734
+ _liveDebugger?.trackRoomEmit(this.id, this.room, event, data)
735
+
486
736
  return notified
487
737
  }
488
738
 
@@ -495,7 +745,7 @@ export abstract class LiveComponent<TState = ComponentState> {
495
745
  */
496
746
  protected onRoomEvent<T = any>(event: string, handler: (data: T) => void): void {
497
747
  if (!this.room) {
498
- console.warn(`⚠️ [${this.id}] Cannot subscribe to room event '${event}' - no room set`)
748
+ liveWarn('rooms', this.id, `⚠️ [${this.id}] Cannot subscribe to room event '${event}' - no room set`)
499
749
  return
500
750
  }
501
751
 
@@ -510,7 +760,7 @@ export abstract class LiveComponent<TState = ComponentState> {
510
760
  // Guardar para cleanup no destroy
511
761
  this.roomEventUnsubscribers.push(unsubscribe)
512
762
 
513
- console.log(`👂 [${this.id}] Subscribed to room event '${event}'`)
763
+ liveLog('rooms', this.id, `👂 [${this.id}] Subscribed to room event '${event}'`)
514
764
  }
515
765
 
516
766
  /**
@@ -545,9 +795,9 @@ export abstract class LiveComponent<TState = ComponentState> {
545
795
  // Registry will handle the actual unsubscription
546
796
  }
547
797
 
548
- // Generate unique ID
798
+ // Generate unique ID using cryptographically secure randomness
549
799
  private generateId(): string {
550
- return `live-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
800
+ return `live-${crypto.randomUUID()}`
551
801
  }
552
802
 
553
803
  // Cleanup when component is destroyed
@@ -564,6 +814,7 @@ export abstract class LiveComponent<TState = ComponentState> {
564
814
  }
565
815
  this.joinedRooms.clear()
566
816
  this.roomHandles.clear()
817
+ this._privateState = {} as TPrivate
567
818
 
568
819
  this.unsubscribeFromRoom()
569
820
  // Override in subclasses for custom cleanup
@@ -638,6 +889,11 @@ export type ActionReturn<
638
889
  */
639
890
  export type InferComponentState<T extends LiveComponent<any>> = T extends LiveComponent<infer S> ? S : never
640
891
 
892
+ /**
893
+ * Get the private state type from a LiveComponent class
894
+ */
895
+ export type InferPrivateState<T extends LiveComponent<any, any>> = T extends LiveComponent<any, infer P> ? P : never
896
+
641
897
  /**
642
898
  * Type-safe call signature for a component
643
899
  * Provides autocomplete for action names and validates payload types
@@ -1,18 +1,18 @@
1
- /**
2
- * FluxStack Utilities
3
- * Main exports for utility functions and classes
4
- */
5
-
6
- // Logger utilities
7
- export { logger, log } from "./logger"
8
- export type { Logger } from "./logger/index"
9
-
10
- // Error handling
11
- export * from "./errors"
12
-
13
- // Monitoring
14
- export { MetricsCollector } from "./monitoring"
15
- export type * from "./monitoring"
16
-
17
- // General helpers
1
+ /**
2
+ * FluxStack Utilities
3
+ * Main exports for utility functions and classes
4
+ */
5
+
6
+ // Logger utilities
7
+ export { logger, log } from "./logger"
8
+ export type { Logger } from "./logger/index"
9
+
10
+ // Error handling
11
+ export * from "./errors"
12
+
13
+ // Monitoring
14
+ export { MetricsCollector } from "./monitoring"
15
+ export type * from "./monitoring"
16
+
17
+ // General helpers
18
18
  export * from "./helpers"
@@ -127,7 +127,7 @@ export function SECTION(sectionName: string, callback: () => void): void {
127
127
  /**
128
128
  * HTTP request logging with colors and formatting
129
129
  */
130
- export function request(method: string, path: string, status?: number, duration?: number): void {
130
+ export function request(method: string, path: string, status?: number, duration?: number, ip?: string): void {
131
131
  const { file: callerFile } = getCallerInfo()
132
132
  const logger = getLoggerForModule(callerFile)
133
133
 
@@ -159,12 +159,15 @@ export function request(method: string, path: string, status?: number, duration?
159
159
  durationStr = ` ${durationColor(`(${duration}ms)`)}`
160
160
  }
161
161
 
162
+ // Format IP address
163
+ const ipStr = ip ? chalk.dim(`[${ip}]`) + ' ' : ''
164
+
162
165
  // Build log message
163
166
  const methodStr = methodColor(method.padEnd(7))
164
167
  const pathStr = chalk.white(path.padEnd(30))
165
168
  const statusStr = status ? `→ ${statusColor(status.toString())}` : ''
166
169
 
167
- const message = `${methodStr} ${pathStr} ${statusStr}${durationStr}`
170
+ const message = `${ipStr}${methodStr} ${pathStr} ${statusStr}${durationStr}`
168
171
 
169
172
  // Use appropriate log level based on status
170
173
  const level = status && status >= 500 ? 'error' : status && status >= 400 ? 'warn' : 'info'