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,278 +0,0 @@
1
- // 🔥 FluxStack Live Room Manager - Gerencia salas para LiveComponents
2
-
3
- import { roomEvents } from './RoomEventBus'
4
- import type { FluxStackWebSocket } from '@core/types/types'
5
- import { liveLog } from './LiveLogger'
6
-
7
- export interface RoomMessage {
8
- type: 'ROOM_JOIN' | 'ROOM_LEAVE' | 'ROOM_EMIT' | 'ROOM_STATE_SET' | 'ROOM_STATE_GET'
9
- componentId: string
10
- roomId: string
11
- event?: string
12
- data?: any
13
- requestId?: string
14
- timestamp: number
15
- }
16
-
17
- interface RoomMember {
18
- componentId: string
19
- ws: FluxStackWebSocket
20
- joinedAt: number
21
- }
22
-
23
- interface Room<TState = any> {
24
- id: string
25
- state: TState
26
- members: Map<string, RoomMember>
27
- createdAt: number
28
- lastActivity: number
29
- }
30
-
31
- class LiveRoomManager {
32
- private rooms = new Map<string, Room>()
33
- private componentRooms = new Map<string, Set<string>>() // componentId -> roomIds
34
-
35
- /**
36
- * Componente entra em uma sala
37
- */
38
- joinRoom<TState = any>(componentId: string, roomId: string, ws: FluxStackWebSocket, initialState?: TState): { state: TState } {
39
- // 🔒 Validate room name format
40
- if (!roomId || !/^[a-zA-Z0-9_:.-]{1,64}$/.test(roomId)) {
41
- throw new Error('Invalid room name. Must be 1-64 alphanumeric characters, hyphens, underscores, dots, or colons.')
42
- }
43
-
44
- // Criar sala se não existir
45
- if (!this.rooms.has(roomId)) {
46
- this.rooms.set(roomId, {
47
- id: roomId,
48
- state: initialState || {},
49
- members: new Map(),
50
- createdAt: Date.now(),
51
- lastActivity: Date.now()
52
- })
53
- liveLog('rooms', componentId, `🏠 Room '${roomId}' created`)
54
- }
55
-
56
- const room = this.rooms.get(roomId)!
57
-
58
- // Adicionar membro
59
- room.members.set(componentId, {
60
- componentId,
61
- ws,
62
- joinedAt: Date.now()
63
- })
64
- room.lastActivity = Date.now()
65
-
66
- // Rastrear salas do componente
67
- if (!this.componentRooms.has(componentId)) {
68
- this.componentRooms.set(componentId, new Set())
69
- }
70
- this.componentRooms.get(componentId)!.add(roomId)
71
-
72
- liveLog('rooms', componentId, `👋 Component '${componentId}' joined room '${roomId}' (${room.members.size} members)`)
73
-
74
- // Notificar outros membros
75
- this.broadcastToRoom(roomId, {
76
- type: 'ROOM_SYSTEM',
77
- componentId,
78
- roomId,
79
- event: '$sub:join',
80
- data: {
81
- subscriberId: componentId,
82
- count: room.members.size
83
- },
84
- timestamp: Date.now()
85
- }, componentId)
86
-
87
- return { state: room.state }
88
- }
89
-
90
- /**
91
- * Componente sai de uma sala
92
- */
93
- leaveRoom(componentId: string, roomId: string): void {
94
- const room = this.rooms.get(roomId)
95
- if (!room) return
96
-
97
- // Remover membro
98
- room.members.delete(componentId)
99
- room.lastActivity = Date.now()
100
-
101
- // Remover do rastreamento
102
- this.componentRooms.get(componentId)?.delete(roomId)
103
-
104
- liveLog('rooms', componentId, `🚶 Component '${componentId}' left room '${roomId}' (${room.members.size} members)`)
105
-
106
- // Notificar outros membros
107
- this.broadcastToRoom(roomId, {
108
- type: 'ROOM_SYSTEM',
109
- componentId,
110
- roomId,
111
- event: '$sub:leave',
112
- data: {
113
- subscriberId: componentId,
114
- count: room.members.size
115
- },
116
- timestamp: Date.now()
117
- })
118
-
119
- // Cleanup sala vazia após delay
120
- if (room.members.size === 0) {
121
- setTimeout(() => {
122
- const currentRoom = this.rooms.get(roomId)
123
- if (currentRoom && currentRoom.members.size === 0) {
124
- this.rooms.delete(roomId)
125
- liveLog('rooms', null, `🗑️ Room '${roomId}' destroyed (empty)`)
126
- }
127
- }, 5 * 60 * 1000) // 5 minutos
128
- }
129
- }
130
-
131
- /**
132
- * Componente desconecta - sai de todas as salas
133
- */
134
- cleanupComponent(componentId: string): void {
135
- const rooms = this.componentRooms.get(componentId)
136
- if (!rooms) return
137
-
138
- for (const roomId of rooms) {
139
- this.leaveRoom(componentId, roomId)
140
- }
141
-
142
- this.componentRooms.delete(componentId)
143
- }
144
-
145
- /**
146
- * Emitir evento para todos na sala
147
- * - Envia via WebSocket para frontends dos outros membros
148
- * - Também dispara eventos no RoomEventBus para handlers server-side
149
- */
150
- emitToRoom(roomId: string, event: string, data: any, excludeComponentId?: string): number {
151
- const room = this.rooms.get(roomId)
152
- if (!room) return 0
153
-
154
- room.lastActivity = Date.now()
155
-
156
- // 1. Emitir no RoomEventBus para handlers server-side (outros LiveComponents)
157
- // Isso permite que componentes do servidor reajam a eventos de outros componentes
158
- // Usa 'room' como tipo genérico (mesmo usado em $room.on)
159
- roomEvents.emit('room', roomId, event, data, excludeComponentId)
160
-
161
- // 2. Broadcast via WebSocket para frontends
162
- return this.broadcastToRoom(roomId, {
163
- type: 'ROOM_EVENT',
164
- componentId: '',
165
- roomId,
166
- event,
167
- data,
168
- timestamp: Date.now()
169
- }, excludeComponentId)
170
- }
171
-
172
- // 🔒 Maximum room state size (10MB) to prevent memory exhaustion attacks
173
- private readonly MAX_ROOM_STATE_SIZE = 10 * 1024 * 1024
174
-
175
- /**
176
- * Atualizar estado da sala
177
- */
178
- setRoomState(roomId: string, updates: any, excludeComponentId?: string): void {
179
- const room = this.rooms.get(roomId)
180
- if (!room) return
181
-
182
- // Merge estado
183
- const newState = { ...room.state, ...updates }
184
-
185
- // 🔒 Validate state size to prevent memory exhaustion
186
- const stateSize = Buffer.byteLength(JSON.stringify(newState), 'utf8')
187
- if (stateSize > this.MAX_ROOM_STATE_SIZE) {
188
- throw new Error('Room state exceeds maximum size limit')
189
- }
190
-
191
- room.state = newState
192
- room.lastActivity = Date.now()
193
-
194
- // Notificar todos os membros
195
- this.broadcastToRoom(roomId, {
196
- type: 'ROOM_STATE',
197
- componentId: '',
198
- roomId,
199
- event: '$state:update',
200
- data: { state: updates },
201
- timestamp: Date.now()
202
- }, excludeComponentId)
203
- }
204
-
205
- /**
206
- * Obter estado da sala
207
- */
208
- getRoomState<TState = any>(roomId: string): TState {
209
- return (this.rooms.get(roomId)?.state || {}) as TState
210
- }
211
-
212
- /**
213
- * Broadcast para todos os membros da sala
214
- */
215
- private broadcastToRoom(roomId: string, message: any, excludeComponentId?: string): number {
216
- const room = this.rooms.get(roomId)
217
- if (!room) return 0
218
-
219
- let sent = 0
220
- for (const [componentId, member] of room.members) {
221
- if (excludeComponentId && componentId === excludeComponentId) continue
222
-
223
- try {
224
- if (member.ws && member.ws.readyState === 1) {
225
- member.ws.send(JSON.stringify({
226
- ...message,
227
- componentId
228
- }))
229
- sent++
230
- }
231
- } catch (error) {
232
- console.error(`Failed to send to ${componentId}:`, error)
233
- }
234
- }
235
-
236
- return sent
237
- }
238
-
239
- /**
240
- * Verificar se componente está em uma sala
241
- */
242
- isInRoom(componentId: string, roomId: string): boolean {
243
- return this.rooms.get(roomId)?.members.has(componentId) ?? false
244
- }
245
-
246
- /**
247
- * Obter salas de um componente
248
- */
249
- getComponentRooms(componentId: string): string[] {
250
- return Array.from(this.componentRooms.get(componentId) || [])
251
- }
252
-
253
- /**
254
- * Estatísticas
255
- */
256
- getStats(): {
257
- totalRooms: number
258
- rooms: Record<string, { members: number; createdAt: number; lastActivity: number }>
259
- } {
260
- const rooms: Record<string, { members: number; createdAt: number; lastActivity: number }> = {}
261
-
262
- for (const [id, room] of this.rooms) {
263
- rooms[id] = {
264
- members: room.members.size,
265
- createdAt: room.createdAt,
266
- lastActivity: room.lastActivity
267
- }
268
- }
269
-
270
- return {
271
- totalRooms: this.rooms.size,
272
- rooms
273
- }
274
- }
275
- }
276
-
277
- export const liveRoomManager = new LiveRoomManager()
278
- export type { Room, RoomMember }
@@ -1,234 +0,0 @@
1
- // 🔥 FluxStack Live - Room Event Bus (Pub/Sub server-side)
2
-
3
- type EventHandler<T = any> = (data: T) => void
4
-
5
- interface RoomSubscription {
6
- roomType: string
7
- roomId: string
8
- event: string
9
- handler: EventHandler
10
- componentId: string
11
- }
12
-
13
- export function createTypedRoomEventBus<TRoomEvents extends Record<string, Record<string, any>>>() {
14
- const subscriptions = new Map<string, Set<RoomSubscription>>()
15
-
16
- const getKey = (roomType: string, roomId: string, event: string) =>
17
- `${roomType}:${roomId}:${event}`
18
-
19
- const getRoomKey = (roomType: string, roomId: string) =>
20
- `${roomType}:${roomId}`
21
-
22
- return {
23
- on<K extends keyof TRoomEvents, E extends keyof TRoomEvents[K]>(
24
- roomType: K,
25
- roomId: string,
26
- event: E,
27
- componentId: string,
28
- handler: EventHandler<TRoomEvents[K][E]>
29
- ): () => void {
30
- const key = getKey(roomType as string, roomId, event as string)
31
-
32
- if (!subscriptions.has(key)) {
33
- subscriptions.set(key, new Set())
34
- }
35
-
36
- const subscription: RoomSubscription = {
37
- roomType: roomType as string,
38
- roomId,
39
- event: event as string,
40
- handler,
41
- componentId
42
- }
43
-
44
- subscriptions.get(key)!.add(subscription)
45
-
46
- return () => {
47
- subscriptions.get(key)?.delete(subscription)
48
- if (subscriptions.get(key)?.size === 0) {
49
- subscriptions.delete(key)
50
- }
51
- }
52
- },
53
-
54
- emit<K extends keyof TRoomEvents, E extends keyof TRoomEvents[K]>(
55
- roomType: K,
56
- roomId: string,
57
- event: E,
58
- data: TRoomEvents[K][E],
59
- excludeComponentId?: string
60
- ): number {
61
- const key = getKey(roomType as string, roomId, event as string)
62
- const subs = subscriptions.get(key)
63
-
64
- if (!subs || subs.size === 0) return 0
65
-
66
- let notified = 0
67
- for (const sub of subs) {
68
- if (excludeComponentId && sub.componentId === excludeComponentId) continue
69
-
70
- try {
71
- sub.handler(data)
72
- notified++
73
- } catch (error) {
74
- console.error(`❌ RoomEventBus error [${key}]:`, error)
75
- }
76
- }
77
-
78
- return notified
79
- },
80
-
81
- unsubscribeAll(componentId: string): number {
82
- let removed = 0
83
-
84
- for (const [key, subs] of subscriptions) {
85
- for (const sub of subs) {
86
- if (sub.componentId === componentId) {
87
- subs.delete(sub)
88
- removed++
89
- }
90
- }
91
- if (subs.size === 0) {
92
- subscriptions.delete(key)
93
- }
94
- }
95
-
96
- return removed
97
- },
98
-
99
- clearRoom<K extends keyof TRoomEvents>(roomType: K, roomId: string): number {
100
- const prefix = getRoomKey(roomType as string, roomId)
101
- let removed = 0
102
-
103
- for (const key of subscriptions.keys()) {
104
- if (key.startsWith(prefix)) {
105
- removed += subscriptions.get(key)?.size ?? 0
106
- subscriptions.delete(key)
107
- }
108
- }
109
-
110
- return removed
111
- },
112
-
113
- getStats(): { totalSubscriptions: number; rooms: Record<string, { events: Record<string, number> }> } {
114
- const rooms: Record<string, { events: Record<string, number> }> = {}
115
- let total = 0
116
-
117
- for (const [key, subs] of subscriptions) {
118
- const [roomType, roomId, event] = key.split(':')
119
- const roomKey = `${roomType}:${roomId}`
120
-
121
- if (!rooms[roomKey]) {
122
- rooms[roomKey] = { events: {} }
123
- }
124
-
125
- rooms[roomKey].events[event] = subs.size
126
- total += subs.size
127
- }
128
-
129
- return { totalSubscriptions: total, rooms }
130
- }
131
- }
132
- }
133
-
134
- class RoomEventBus {
135
- private subscriptions = new Map<string, Set<RoomSubscription>>()
136
-
137
- private getKey(roomType: string, roomId: string, event: string): string {
138
- return `${roomType}:${roomId}:${event}`
139
- }
140
-
141
- on(roomType: string, roomId: string, event: string, componentId: string, handler: EventHandler): () => void {
142
- const key = this.getKey(roomType, roomId, event)
143
-
144
- if (!this.subscriptions.has(key)) {
145
- this.subscriptions.set(key, new Set())
146
- }
147
-
148
- const subscription: RoomSubscription = { roomType, roomId, event, handler, componentId }
149
- this.subscriptions.get(key)!.add(subscription)
150
-
151
- return () => {
152
- this.subscriptions.get(key)?.delete(subscription)
153
- if (this.subscriptions.get(key)?.size === 0) {
154
- this.subscriptions.delete(key)
155
- }
156
- }
157
- }
158
-
159
- emit(roomType: string, roomId: string, event: string, data: any, excludeComponentId?: string): number {
160
- const key = this.getKey(roomType, roomId, event)
161
- const subs = this.subscriptions.get(key)
162
-
163
- if (!subs || subs.size === 0) return 0
164
-
165
- let notified = 0
166
- for (const sub of subs) {
167
- if (excludeComponentId && sub.componentId === excludeComponentId) continue
168
-
169
- try {
170
- sub.handler(data)
171
- notified++
172
- } catch (error) {
173
- console.error(`❌ RoomEventBus error [${key}]:`, error)
174
- }
175
- }
176
-
177
- return notified
178
- }
179
-
180
- unsubscribeAll(componentId: string): number {
181
- let removed = 0
182
-
183
- for (const [key, subs] of this.subscriptions) {
184
- for (const sub of subs) {
185
- if (sub.componentId === componentId) {
186
- subs.delete(sub)
187
- removed++
188
- }
189
- }
190
- if (subs.size === 0) {
191
- this.subscriptions.delete(key)
192
- }
193
- }
194
-
195
- return removed
196
- }
197
-
198
- clearRoom(roomType: string, roomId: string): number {
199
- const prefix = `${roomType}:${roomId}`
200
- let removed = 0
201
-
202
- for (const key of this.subscriptions.keys()) {
203
- if (key.startsWith(prefix)) {
204
- removed += this.subscriptions.get(key)?.size ?? 0
205
- this.subscriptions.delete(key)
206
- }
207
- }
208
-
209
- return removed
210
- }
211
-
212
- getStats() {
213
- const rooms: Record<string, { events: Record<string, number> }> = {}
214
- let total = 0
215
-
216
- for (const [key, subs] of this.subscriptions) {
217
- const [roomType, roomId, event] = key.split(':')
218
- const roomKey = `${roomType}:${roomId}`
219
-
220
- if (!rooms[roomKey]) {
221
- rooms[roomKey] = { events: {} }
222
- }
223
-
224
- rooms[roomKey].events[event] = subs.size
225
- total += subs.size
226
- }
227
-
228
- return { totalSubscriptions: total, rooms }
229
- }
230
- }
231
-
232
- export const roomEvents = new RoomEventBus()
233
-
234
- export type { EventHandler, RoomSubscription }
@@ -1,172 +0,0 @@
1
- // 🔥 FluxStack Live - Room State Manager (In-memory storage per room)
2
-
3
- type RoomStateData = Record<string, any>
4
-
5
- interface RoomInfo {
6
- state: RoomStateData
7
- componentCount: number
8
- createdAt: number
9
- lastUpdate: number
10
- }
11
-
12
- export function createTypedRoomState<TRoomTypes extends Record<string, RoomStateData>>() {
13
- const rooms = new Map<string, RoomInfo>()
14
- const getKey = (type: string, roomId: string) => `${type}:${roomId}`
15
-
16
- return {
17
- get<K extends keyof TRoomTypes>(type: K, roomId: string, defaultState: TRoomTypes[K]): TRoomTypes[K] {
18
- const key = getKey(type as string, roomId)
19
- const room = rooms.get(key)
20
-
21
- if (room) return room.state as TRoomTypes[K]
22
-
23
- rooms.set(key, { state: defaultState, componentCount: 0, createdAt: Date.now(), lastUpdate: Date.now() })
24
- return defaultState
25
- },
26
-
27
- update<K extends keyof TRoomTypes>(type: K, roomId: string, updates: Partial<TRoomTypes[K]>): TRoomTypes[K] {
28
- const key = getKey(type as string, roomId)
29
- const room = rooms.get(key)
30
-
31
- if (room) {
32
- room.state = { ...room.state, ...updates }
33
- room.lastUpdate = Date.now()
34
- return room.state as TRoomTypes[K]
35
- }
36
-
37
- const newState = updates as TRoomTypes[K]
38
- rooms.set(key, { state: newState, componentCount: 0, createdAt: Date.now(), lastUpdate: Date.now() })
39
- return newState
40
- },
41
-
42
- set<K extends keyof TRoomTypes>(type: K, roomId: string, state: TRoomTypes[K]): void {
43
- const key = getKey(type as string, roomId)
44
- const room = rooms.get(key)
45
-
46
- if (room) {
47
- room.state = state
48
- room.lastUpdate = Date.now()
49
- } else {
50
- rooms.set(key, { state, componentCount: 0, createdAt: Date.now(), lastUpdate: Date.now() })
51
- }
52
- },
53
-
54
- join<K extends keyof TRoomTypes>(type: K, roomId: string): void {
55
- const room = rooms.get(getKey(type as string, roomId))
56
- if (room) room.componentCount++
57
- },
58
-
59
- leave<K extends keyof TRoomTypes>(type: K, roomId: string): void {
60
- const key = getKey(type as string, roomId)
61
- const room = rooms.get(key)
62
- if (room) {
63
- room.componentCount--
64
- if (room.componentCount <= 0) {
65
- setTimeout(() => {
66
- const current = rooms.get(key)
67
- if (current && current.componentCount <= 0) {
68
- rooms.delete(key)
69
- }
70
- }, 5 * 60 * 1000)
71
- }
72
- }
73
- },
74
-
75
- has<K extends keyof TRoomTypes>(type: K, roomId: string): boolean {
76
- return rooms.has(getKey(type as string, roomId))
77
- },
78
-
79
- delete<K extends keyof TRoomTypes>(type: K, roomId: string): boolean {
80
- return rooms.delete(getKey(type as string, roomId))
81
- },
82
-
83
- getStats(): { totalRooms: number; rooms: Record<string, { componentCount: number; stateKeys: string[] }> } {
84
- const roomStats: Record<string, { componentCount: number; stateKeys: string[] }> = {}
85
- for (const [key, info] of rooms) {
86
- roomStats[key] = { componentCount: info.componentCount, stateKeys: Object.keys(info.state) }
87
- }
88
- return { totalRooms: rooms.size, rooms: roomStats }
89
- }
90
- }
91
- }
92
-
93
- class RoomStateManager {
94
- private rooms = new Map<string, RoomInfo>()
95
-
96
- get<T extends RoomStateData>(roomId: string, defaultState?: T): T {
97
- const room = this.rooms.get(roomId)
98
- if (room) return room.state as T
99
-
100
- if (defaultState) {
101
- this.rooms.set(roomId, { state: defaultState, componentCount: 0, createdAt: Date.now(), lastUpdate: Date.now() })
102
- return defaultState
103
- }
104
-
105
- return {} as T
106
- }
107
-
108
- update<T extends RoomStateData>(roomId: string, updates: Partial<T>): T {
109
- const room = this.rooms.get(roomId)
110
-
111
- if (room) {
112
- room.state = { ...room.state, ...updates }
113
- room.lastUpdate = Date.now()
114
- return room.state as T
115
- }
116
-
117
- const newState = updates as T
118
- this.rooms.set(roomId, { state: newState, componentCount: 0, createdAt: Date.now(), lastUpdate: Date.now() })
119
- return newState
120
- }
121
-
122
- set<T extends RoomStateData>(roomId: string, state: T): void {
123
- const room = this.rooms.get(roomId)
124
-
125
- if (room) {
126
- room.state = state
127
- room.lastUpdate = Date.now()
128
- } else {
129
- this.rooms.set(roomId, { state, componentCount: 0, createdAt: Date.now(), lastUpdate: Date.now() })
130
- }
131
- }
132
-
133
- join(roomId: string): void {
134
- const room = this.rooms.get(roomId)
135
- if (room) room.componentCount++
136
- }
137
-
138
- leave(roomId: string): void {
139
- const room = this.rooms.get(roomId)
140
- if (room) {
141
- room.componentCount--
142
- if (room.componentCount <= 0) {
143
- setTimeout(() => {
144
- const current = this.rooms.get(roomId)
145
- if (current && current.componentCount <= 0) {
146
- this.rooms.delete(roomId)
147
- }
148
- }, 5 * 60 * 1000)
149
- }
150
- }
151
- }
152
-
153
- has(roomId: string): boolean {
154
- return this.rooms.has(roomId)
155
- }
156
-
157
- delete(roomId: string): boolean {
158
- return this.rooms.delete(roomId)
159
- }
160
-
161
- getStats(): { totalRooms: number; rooms: Record<string, { componentCount: number; stateKeys: string[] }> } {
162
- const rooms: Record<string, { componentCount: number; stateKeys: string[] }> = {}
163
- for (const [roomId, info] of this.rooms) {
164
- rooms[roomId] = { componentCount: info.componentCount, stateKeys: Object.keys(info.state) }
165
- }
166
- return { totalRooms: this.rooms.size, rooms }
167
- }
168
- }
169
-
170
- export const roomState = new RoomStateManager()
171
-
172
- export type { RoomStateData, RoomInfo }
File without changes