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
@@ -0,0 +1,5 @@
1
+ export class LiveAdminPanel {
2
+ static componentName = 'LiveAdminPanel'
3
+ static defaultState = {}
4
+ static publicActions = ['getAuthInfo', 'init', 'listUsers', 'addUser', 'deleteUser', 'clearAudit']
5
+ }
@@ -0,0 +1,9 @@
1
+ export class LiveCounter {
2
+ static componentName = 'LiveCounter'
3
+ static defaultState = {
4
+ count: 0,
5
+ lastUpdatedBy: null,
6
+ connectedUsers: 0
7
+ }
8
+ static publicActions = ['increment', 'decrement', 'reset']
9
+ }
@@ -0,0 +1,11 @@
1
+ export class LiveForm {
2
+ static componentName = 'LiveForm'
3
+ static defaultState = {
4
+ name: '',
5
+ email: '',
6
+ message: '',
7
+ submitted: false,
8
+ submittedAt: null
9
+ }
10
+ static publicActions = ['submit', 'reset', 'validate', 'setValue']
11
+ }
@@ -0,0 +1,8 @@
1
+ export class LiveLocalCounter {
2
+ static componentName = 'LiveLocalCounter'
3
+ static defaultState = {
4
+ count: 0,
5
+ clicks: 0
6
+ }
7
+ static publicActions = ['increment', 'decrement', 'reset']
8
+ }
@@ -0,0 +1,10 @@
1
+ export class LivePingPong {
2
+ static componentName = 'LivePingPong'
3
+ static defaultState = {
4
+ username: '',
5
+ onlineCount: 0,
6
+ totalPings: 0,
7
+ lastPingBy: null,
8
+ }
9
+ static publicActions = ['ping']
10
+ }
@@ -0,0 +1,11 @@
1
+ export class LiveRoomChat {
2
+ static componentName = 'LiveRoomChat'
3
+ static defaultState = {
4
+ username: '',
5
+ activeRoom: null,
6
+ rooms: [],
7
+ messages: {},
8
+ customRooms: []
9
+ }
10
+ static publicActions = ['createRoom', 'joinRoom', 'leaveRoom', 'switchRoom', 'sendMessage', 'setUsername']
11
+ }
@@ -0,0 +1,10 @@
1
+ export class LiveSharedCounter {
2
+ static componentName = 'LiveSharedCounter'
3
+ static defaultState = {
4
+ username: '',
5
+ count: 0,
6
+ lastUpdatedBy: null,
7
+ onlineCount: 0
8
+ }
9
+ static publicActions = ['increment', 'decrement', 'reset']
10
+ }
@@ -0,0 +1,15 @@
1
+ export class LiveUpload {
2
+ static componentName = 'LiveUpload'
3
+ static defaultState = {
4
+ status: 'idle',
5
+ progress: 0,
6
+ fileName: '',
7
+ fileSize: 0,
8
+ fileType: '',
9
+ fileUrl: '',
10
+ bytesUploaded: 0,
11
+ totalBytes: 0,
12
+ error: null
13
+ }
14
+ static publicActions = ['startUpload', 'updateProgress', 'completeUpload', 'failUpload', 'reset']
15
+ }
@@ -1,13 +1,14 @@
1
1
  import { useState, useEffect } from 'react'
2
2
  import { Routes, Route } from 'react-router'
3
3
  import { api } from './lib/eden-api'
4
- import { LiveComponentsProvider, LiveDebugger } from '@/core/client'
4
+ import { LiveComponentsProvider } from '@/core/client'
5
5
  import { FormDemo } from './live/FormDemo'
6
6
  import { CounterDemo } from './live/CounterDemo'
7
7
  import { UploadDemo } from './live/UploadDemo'
8
- import { ChatDemo } from './live/ChatDemo'
9
8
  import { RoomChatDemo } from './live/RoomChatDemo'
9
+ import { SharedCounterDemo } from './live/SharedCounterDemo'
10
10
  import { AuthDemo } from './live/AuthDemo'
11
+ import { PingPongDemo } from './live/PingPongDemo'
11
12
  import { AppLayout } from './components/AppLayout'
12
13
  import { DemoPage } from './components/DemoPage'
13
14
  import { HomePage } from './pages/HomePage'
@@ -110,10 +111,12 @@ function AppContent() {
110
111
  }
111
112
  />
112
113
  <Route
113
- path="/chat"
114
+ path="/shared-counter"
114
115
  element={
115
- <DemoPage>
116
- <ChatDemo />
116
+ <DemoPage
117
+ note={<>Contador compartilhado usando <code className="text-purple-400">LiveRoom</code> - abra em varias abas!</>}
118
+ >
119
+ <SharedCounterDemo />
117
120
  </DemoPage>
118
121
  }
119
122
  />
@@ -121,7 +124,7 @@ function AppContent() {
121
124
  path="/room-chat"
122
125
  element={
123
126
  <DemoPage
124
- note={<>🚀 Chat com múltiplas salas usando o novo sistema <code className="text-purple-400">$room</code>!</>}
127
+ note={<>Chat com múltiplas salas usando o sistema <code className="text-purple-400">$room</code>.</>}
125
128
  >
126
129
  <RoomChatDemo />
127
130
  </DemoPage>
@@ -137,6 +140,16 @@ function AppContent() {
137
140
  </DemoPage>
138
141
  }
139
142
  />
143
+ <Route
144
+ path="/ping-pong"
145
+ element={
146
+ <DemoPage
147
+ note={<>Latency demo com <code className="text-cyan-400">msgpack</code> binary codec - mensagens binárias no WebSocket!</>}
148
+ >
149
+ <PingPongDemo />
150
+ </DemoPage>
151
+ }
152
+ />
140
153
  <Route path="*" element={<HomePage apiStatus={apiStatus} />} />
141
154
  </Route>
142
155
  </Routes>
@@ -153,7 +166,6 @@ function App() {
153
166
  debug={false}
154
167
  >
155
168
  <AppContent />
156
- {import.meta.env.DEV && <LiveDebugger />}
157
169
  </LiveComponentsProvider>
158
170
  )
159
171
  }
@@ -9,9 +9,10 @@ const navItems = [
9
9
  { to: '/counter', label: 'Counter' },
10
10
  { to: '/form', label: 'Form' },
11
11
  { to: '/upload', label: 'Upload' },
12
- { to: '/chat', label: 'Chat' },
12
+ { to: '/shared-counter', label: 'Shared Counter' },
13
13
  { to: '/room-chat', label: 'Room Chat' },
14
14
  { to: '/auth', label: 'Auth' },
15
+ { to: '/ping-pong', label: 'Ping Pong' },
15
16
  { to: '/api-test', label: 'API Test' }
16
17
  ]
17
18
 
@@ -20,12 +21,16 @@ const routeFlameHue: Record<string, string> = {
20
21
  '/counter': '180deg', // ciano
21
22
  '/form': '300deg', // rosa
22
23
  '/upload': '60deg', // amarelo
23
- '/chat': '120deg', // verde
24
+ '/shared-counter': '120deg', // verde
24
25
  '/room-chat': '240deg', // azul
25
26
  '/auth': '330deg', // vermelho
27
+ '/ping-pong': '200deg', // ciano-azul
26
28
  '/api-test': '90deg', // lima
27
29
  }
28
30
 
31
+ // Cache favicon blob URLs by hue to avoid recreating blobs on every navigation
32
+ const faviconUrlCache = new Map<string, string>()
33
+
29
34
  export function AppLayout() {
30
35
  const location = useLocation()
31
36
  const [menuOpen, setMenuOpen] = useState(false)
@@ -34,14 +39,18 @@ export function AppLayout() {
34
39
  const current = navItems.find(item => item.to === location.pathname)
35
40
  document.title = current ? `${current.label} - FluxStack` : 'FluxStack'
36
41
 
37
- // Dynamic favicon with hue-rotate
42
+ // Dynamic favicon with hue-rotate (cached per hue value)
38
43
  const hue = routeFlameHue[location.pathname] || '0deg'
39
- const colored = faviconSvg.replace(
40
- '<svg ',
41
- `<svg style="filter: hue-rotate(${hue})" `
42
- )
43
- const blob = new Blob([colored], { type: 'image/svg+xml' })
44
- const url = URL.createObjectURL(blob)
44
+ let url = faviconUrlCache.get(hue)
45
+ if (!url) {
46
+ const colored = faviconSvg.replace(
47
+ '<svg ',
48
+ `<svg style="filter: hue-rotate(${hue})" `
49
+ )
50
+ const blob = new Blob([colored], { type: 'image/svg+xml' })
51
+ url = URL.createObjectURL(blob)
52
+ faviconUrlCache.set(hue, url)
53
+ }
45
54
  let link = document.querySelector<HTMLLinkElement>('link[rel="icon"]')
46
55
  if (!link) {
47
56
  link = document.createElement('link')
@@ -50,7 +59,6 @@ export function AppLayout() {
50
59
  }
51
60
  link.type = 'image/svg+xml'
52
61
  link.href = url
53
- return () => URL.revokeObjectURL(url)
54
62
  }, [location.pathname])
55
63
 
56
64
  return (
@@ -0,0 +1,199 @@
1
+ // PingPongDemo - Demo de Binary Codec (msgpack)
2
+ //
3
+ // Mostra latencia round-trip de mensagens binárias.
4
+ // Cada ping viaja como msgpack binário pelo WebSocket.
5
+ // Abra em varias abas para ver o onlineCount e totalPings compartilhados.
6
+
7
+ import { useMemo, useState, useEffect, useRef, useCallback } from 'react'
8
+ import { Live } from '@/core/client'
9
+ import { LivePingPong } from '@server/live/LivePingPong'
10
+ import type { PingRoom } from '@server/live/rooms/PingRoom'
11
+
12
+ interface PingEntry {
13
+ seq: number
14
+ sentAt: number
15
+ rtt: number | null
16
+ }
17
+
18
+ export function PingPongDemo() {
19
+ const username = useMemo(() => {
20
+ const adj = ['Swift', 'Rapid', 'Quick', 'Turbo', 'Flash'][Math.floor(Math.random() * 5)]
21
+ const noun = ['Ping', 'Bolt', 'Wave', 'Pulse', 'Beam'][Math.floor(Math.random() * 5)]
22
+ return `${adj}${noun}${Math.floor(Math.random() * 100)}`
23
+ }, [])
24
+
25
+ const live = Live.use(LivePingPong, {
26
+ initialState: { ...LivePingPong.defaultState, username },
27
+ })
28
+
29
+ const [pings, setPings] = useState<PingEntry[]>([])
30
+ const [avgRtt, setAvgRtt] = useState<number | null>(null)
31
+ const [minRtt, setMinRtt] = useState<number | null>(null)
32
+ const [maxRtt, setMaxRtt] = useState<number | null>(null)
33
+ const seqRef = useRef(0)
34
+ const pendingRef = useRef<Map<number, number>>(new Map())
35
+
36
+ // Listen for pong events (binary msgpack from server)
37
+ useEffect(() => {
38
+ const unsub = live.$room<PingRoom>('ping:global').on('pong', (data) => {
39
+ const sentAt = pendingRef.current.get(data.seq)
40
+ if (sentAt == null) return
41
+ pendingRef.current.delete(data.seq)
42
+
43
+ const rtt = Date.now() - sentAt
44
+
45
+ setPings(prev => {
46
+ const updated = [{ seq: data.seq, sentAt, rtt }, ...prev].slice(0, 20)
47
+ // Compute stats
48
+ const rtts = updated.filter(p => p.rtt != null).map(p => p.rtt!)
49
+ if (rtts.length > 0) {
50
+ setAvgRtt(Math.round(rtts.reduce((a, b) => a + b, 0) / rtts.length))
51
+ setMinRtt(Math.min(...rtts))
52
+ setMaxRtt(Math.max(...rtts))
53
+ }
54
+ return updated
55
+ })
56
+ })
57
+ return unsub
58
+ }, [])
59
+
60
+ const sendPing = useCallback(() => {
61
+ const seq = ++seqRef.current
62
+ pendingRef.current.set(seq, Date.now())
63
+ live.ping({ seq })
64
+ }, [live])
65
+
66
+ // Auto-ping mode
67
+ const [autoPing, setAutoPing] = useState(false)
68
+ const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
69
+
70
+ useEffect(() => {
71
+ if (autoPing && live.$connected) {
72
+ intervalRef.current = setInterval(sendPing, 500)
73
+ }
74
+ return () => {
75
+ if (intervalRef.current) clearInterval(intervalRef.current)
76
+ }
77
+ }, [autoPing, live.$connected, sendPing])
78
+
79
+ const onlineCount = live.$state.onlineCount
80
+ const totalPings = live.$state.totalPings
81
+ const lastPingBy = live.$state.lastPingBy
82
+
83
+ const rttColor = (rtt: number) => {
84
+ if (rtt < 10) return 'text-emerald-400'
85
+ if (rtt < 50) return 'text-yellow-400'
86
+ return 'text-red-400'
87
+ }
88
+
89
+ return (
90
+ <div className="flex flex-col items-center gap-8 w-full max-w-lg mx-auto">
91
+ {/* Header */}
92
+ <div className="text-center">
93
+ <h2 className="text-2xl font-bold text-white mb-2">Ping Pong Binary</h2>
94
+ <p className="text-sm text-gray-400">
95
+ Mensagens binárias via <code className="text-cyan-400">msgpack</code> — round-trip latency demo
96
+ </p>
97
+ </div>
98
+
99
+ {/* Status bar */}
100
+ <div className="flex items-center gap-4 flex-wrap justify-center">
101
+ <div className="flex items-center gap-2">
102
+ <div className={`w-2 h-2 rounded-full ${live.$connected ? 'bg-emerald-400' : 'bg-red-400'}`} />
103
+ <span className="text-sm text-gray-400">{live.$connected ? 'Conectado' : 'Desconectado'}</span>
104
+ </div>
105
+ <div className="flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10">
106
+ <span className="text-sm text-gray-400">{onlineCount} online</span>
107
+ </div>
108
+ <div className="px-3 py-1 rounded-full bg-cyan-500/10 border border-cyan-500/20">
109
+ <span className="text-xs text-cyan-300">{username}</span>
110
+ </div>
111
+ </div>
112
+
113
+ {/* Stats */}
114
+ <div className="grid grid-cols-3 gap-4 w-full">
115
+ <div className="bg-gray-800/50 border border-white/10 rounded-xl p-4 text-center">
116
+ <div className="text-2xl font-bold text-white tabular-nums">
117
+ {avgRtt != null ? `${avgRtt}ms` : '--'}
118
+ </div>
119
+ <div className="text-xs text-gray-500 mt-1">AVG RTT</div>
120
+ </div>
121
+ <div className="bg-gray-800/50 border border-white/10 rounded-xl p-4 text-center">
122
+ <div className="text-2xl font-bold text-emerald-400 tabular-nums">
123
+ {minRtt != null ? `${minRtt}ms` : '--'}
124
+ </div>
125
+ <div className="text-xs text-gray-500 mt-1">MIN RTT</div>
126
+ </div>
127
+ <div className="bg-gray-800/50 border border-white/10 rounded-xl p-4 text-center">
128
+ <div className="text-2xl font-bold text-red-400 tabular-nums">
129
+ {maxRtt != null ? `${maxRtt}ms` : '--'}
130
+ </div>
131
+ <div className="text-xs text-gray-500 mt-1">MAX RTT</div>
132
+ </div>
133
+ </div>
134
+
135
+ {/* Controls */}
136
+ <div className="flex items-center gap-3">
137
+ <button
138
+ onClick={sendPing}
139
+ disabled={!live.$connected || live.$loading}
140
+ className="px-8 h-14 rounded-2xl bg-cyan-500/20 border border-cyan-500/30 text-cyan-300 text-lg font-bold hover:bg-cyan-500/30 active:scale-95 disabled:opacity-50 transition-all"
141
+ >
142
+ Ping!
143
+ </button>
144
+ <button
145
+ onClick={() => setAutoPing(!autoPing)}
146
+ disabled={!live.$connected}
147
+ className={`px-6 h-14 rounded-2xl border text-sm font-medium transition-all ${
148
+ autoPing
149
+ ? 'bg-yellow-500/20 border-yellow-500/30 text-yellow-300 hover:bg-yellow-500/30'
150
+ : 'bg-white/10 border-white/20 text-gray-300 hover:bg-white/20'
151
+ }`}
152
+ >
153
+ {autoPing ? 'Auto ON' : 'Auto OFF'}
154
+ </button>
155
+ </div>
156
+
157
+ {/* Global stats */}
158
+ <div className="flex items-center gap-6 text-sm text-gray-500">
159
+ <span>Total pings: <span className="text-white font-mono">{totalPings}</span></span>
160
+ {lastPingBy && <span>Ultimo: <span className="text-gray-300">{lastPingBy}</span></span>}
161
+ </div>
162
+
163
+ {/* Ping log */}
164
+ <div className="w-full bg-gray-800/30 border border-white/10 rounded-xl overflow-hidden">
165
+ <div className="px-4 py-2 bg-white/5 border-b border-white/10 flex items-center justify-between">
166
+ <span className="text-xs text-gray-400 font-medium">Ping Log</span>
167
+ <span className="text-xs text-gray-600">
168
+ wire format: <code className="text-cyan-400">msgpack</code> (binary)
169
+ </span>
170
+ </div>
171
+ <div className="max-h-60 overflow-y-auto">
172
+ {pings.length === 0 ? (
173
+ <div className="px-4 py-8 text-center text-gray-600 text-sm">
174
+ Clique Ping! para enviar uma mensagem binaria
175
+ </div>
176
+ ) : (
177
+ pings.map((p) => (
178
+ <div
179
+ key={p.seq}
180
+ className="px-4 py-2 border-b border-white/5 flex items-center justify-between text-sm"
181
+ >
182
+ <span className="text-gray-500 font-mono">#{p.seq}</span>
183
+ <span className={`font-mono font-bold ${p.rtt != null ? rttColor(p.rtt) : 'text-gray-600'}`}>
184
+ {p.rtt != null ? `${p.rtt}ms` : 'pending...'}
185
+ </span>
186
+ </div>
187
+ ))
188
+ )}
189
+ </div>
190
+ </div>
191
+
192
+ {/* Info */}
193
+ <div className="text-center text-xs text-gray-600 space-y-1">
194
+ <p>Powered by <code className="text-purple-400">LiveRoom</code> + <code className="text-cyan-400">msgpack codec</code></p>
195
+ <p>Wire format: binary frames <code className="text-cyan-400">0x02</code> (event) / <code className="text-cyan-400">0x03</code> (state)</p>
196
+ </div>
197
+ </div>
198
+ )
199
+ }