create-fluxstack 1.16.0 → 1.17.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 (119) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/app/client/src/App.tsx +8 -0
  3. package/app/client/src/live/AuthDemo.tsx +4 -4
  4. package/core/build/bundler.ts +40 -26
  5. package/core/build/flux-plugins-generator.ts +325 -325
  6. package/core/build/index.ts +92 -21
  7. package/core/cli/command-registry.ts +44 -46
  8. package/core/cli/commands/build.ts +11 -6
  9. package/core/cli/commands/create.ts +7 -5
  10. package/core/cli/commands/dev.ts +6 -5
  11. package/core/cli/commands/help.ts +3 -2
  12. package/core/cli/commands/make-plugin.ts +8 -7
  13. package/core/cli/commands/plugin-add.ts +60 -43
  14. package/core/cli/commands/plugin-deps.ts +73 -57
  15. package/core/cli/commands/plugin-list.ts +44 -41
  16. package/core/cli/commands/plugin-remove.ts +33 -22
  17. package/core/cli/generators/component.ts +770 -769
  18. package/core/cli/generators/controller.ts +9 -8
  19. package/core/cli/generators/index.ts +148 -146
  20. package/core/cli/generators/interactive.ts +228 -227
  21. package/core/cli/generators/plugin.ts +11 -10
  22. package/core/cli/generators/prompts.ts +83 -82
  23. package/core/cli/generators/route.ts +7 -6
  24. package/core/cli/generators/service.ts +10 -9
  25. package/core/cli/generators/template-engine.ts +2 -1
  26. package/core/cli/generators/types.ts +7 -7
  27. package/core/cli/generators/utils.ts +191 -191
  28. package/core/cli/index.ts +9 -8
  29. package/core/cli/plugin-discovery.ts +2 -2
  30. package/core/client/hooks/useAuth.ts +48 -48
  31. package/core/client/standalone.ts +18 -17
  32. package/core/client/state/createStore.ts +192 -192
  33. package/core/client/state/index.ts +14 -14
  34. package/core/config/index.ts +1 -0
  35. package/core/framework/client.ts +131 -131
  36. package/core/framework/index.ts +7 -7
  37. package/core/framework/server.ts +72 -112
  38. package/core/framework/types.ts +2 -2
  39. package/core/plugins/built-in/live-components/commands/create-live-component.ts +6 -3
  40. package/core/plugins/built-in/monitoring/index.ts +110 -68
  41. package/core/plugins/built-in/static/index.ts +2 -2
  42. package/core/plugins/built-in/swagger/index.ts +9 -9
  43. package/core/plugins/built-in/vite/index.ts +3 -3
  44. package/core/plugins/built-in/vite/vite-dev.ts +3 -3
  45. package/core/plugins/config.ts +50 -47
  46. package/core/plugins/discovery.ts +10 -4
  47. package/core/plugins/executor.ts +2 -2
  48. package/core/plugins/index.ts +206 -203
  49. package/core/plugins/manager.ts +21 -20
  50. package/core/plugins/registry.ts +76 -12
  51. package/core/plugins/types.ts +14 -14
  52. package/core/server/framework.ts +3 -189
  53. package/core/server/live/auto-generated-components.ts +11 -29
  54. package/core/server/live/index.ts +41 -31
  55. package/core/server/live/websocket-plugin.ts +11 -1
  56. package/core/server/middleware/elysia-helpers.ts +16 -15
  57. package/core/server/middleware/errorHandling.ts +14 -14
  58. package/core/server/middleware/index.ts +31 -31
  59. package/core/server/plugins/database.ts +181 -180
  60. package/core/server/plugins/static-files-plugin.ts +4 -3
  61. package/core/server/plugins/swagger.ts +11 -8
  62. package/core/server/rooms/RoomBroadcaster.ts +11 -10
  63. package/core/server/rooms/RoomSystem.ts +14 -11
  64. package/core/server/services/BaseService.ts +7 -7
  65. package/core/server/services/ServiceContainer.ts +5 -5
  66. package/core/server/services/index.ts +8 -8
  67. package/core/templates/create-project.ts +28 -27
  68. package/core/testing/index.ts +9 -9
  69. package/core/testing/setup.ts +73 -73
  70. package/core/types/api.ts +168 -168
  71. package/core/types/config.ts +5 -5
  72. package/core/types/index.ts +1 -1
  73. package/core/types/plugin.ts +2 -2
  74. package/core/types/types.ts +3 -3
  75. package/core/utils/build-logger.ts +324 -324
  76. package/core/utils/config-schema.ts +480 -480
  77. package/core/utils/env.ts +10 -8
  78. package/core/utils/errors/codes.ts +114 -114
  79. package/core/utils/errors/handlers.ts +30 -20
  80. package/core/utils/errors/index.ts +54 -46
  81. package/core/utils/errors/middleware.ts +113 -113
  82. package/core/utils/helpers.ts +19 -16
  83. package/core/utils/logger/colors.ts +114 -114
  84. package/core/utils/logger/config.ts +2 -2
  85. package/core/utils/logger/formatter.ts +82 -82
  86. package/core/utils/logger/group-logger.ts +101 -101
  87. package/core/utils/logger/index.ts +13 -3
  88. package/core/utils/logger/startup-banner.ts +2 -2
  89. package/core/utils/logger/winston-logger.ts +152 -152
  90. package/core/utils/monitoring/index.ts +211 -211
  91. package/core/utils/sync-version.ts +67 -66
  92. package/core/utils/version.ts +1 -1
  93. package/package.json +104 -100
  94. package/playwright-report/index.html +85 -0
  95. package/playwright.config.ts +31 -0
  96. package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
  97. package/plugins/crypto-auth/client/components/index.ts +11 -11
  98. package/plugins/crypto-auth/client/index.ts +11 -11
  99. package/plugins/crypto-auth/package.json +65 -65
  100. package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
  101. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +6 -5
  102. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +6 -5
  103. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +3 -3
  104. package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
  105. package/plugins/crypto-auth/server/middlewares.ts +19 -19
  106. package/vite.config.ts +13 -0
  107. package/app/client/.live-stubs/LiveAdminPanel.js +0 -5
  108. package/app/client/.live-stubs/LiveCounter.js +0 -9
  109. package/app/client/.live-stubs/LiveForm.js +0 -11
  110. package/app/client/.live-stubs/LiveLocalCounter.js +0 -8
  111. package/app/client/.live-stubs/LivePingPong.js +0 -10
  112. package/app/client/.live-stubs/LiveRoomChat.js +0 -11
  113. package/app/client/.live-stubs/LiveSharedCounter.js +0 -10
  114. package/app/client/.live-stubs/LiveUpload.js +0 -15
  115. package/app/server/live/register-components.ts +0 -19
  116. package/core/build/live-components-generator.ts +0 -321
  117. package/core/live/ComponentRegistry.ts +0 -403
  118. package/core/live/types.ts +0 -241
  119. package/workspace.json +0 -6
@@ -1,403 +0,0 @@
1
- // 🔥 FluxStack Live Components - Component Registry
2
-
3
- import type {
4
- LiveComponent,
5
- LiveMessage,
6
- BroadcastMessage,
7
- ComponentDefinition,
8
- WebSocketData
9
- } from '@core/types/types'
10
-
11
- export class ComponentRegistry {
12
- private components = new Map<string, LiveComponent>()
13
- private definitions = new Map<string, ComponentDefinition>()
14
- private rooms = new Map<string, Set<string>>() // roomId -> componentIds
15
- private wsConnections = new Map<string, any>() // componentId -> websocket
16
- private autoDiscoveredComponents = new Map<string, any>() // Auto-discovered component classes
17
-
18
- // Register component definition
19
- registerComponent<TState>(definition: ComponentDefinition<TState>) {
20
- this.definitions.set(definition.name, definition as unknown as ComponentDefinition)
21
- console.log(`📝 Registered component: ${definition.name}`)
22
- }
23
-
24
- // Register component class dynamically
25
- registerComponentClass(name: string, componentClass: any) {
26
- this.autoDiscoveredComponents.set(name, componentClass)
27
- console.log(`🔍 Auto-discovered component: ${name}`)
28
- }
29
-
30
- // Auto-discover components from directory
31
- async autoDiscoverComponents(componentsPath: string) {
32
- try {
33
- const fs = await import('fs')
34
- const path = await import('path')
35
-
36
- if (!fs.existsSync(componentsPath)) {
37
- // In production, components are already bundled - no need to auto-discover
38
- const { appConfig } = await import('@config')
39
- if (appConfig.env !== 'production') {
40
- console.log(`⚠️ Components path not found: ${componentsPath}`)
41
- }
42
- return
43
- }
44
-
45
- const files = fs.readdirSync(componentsPath)
46
-
47
- for (const file of files) {
48
- if (file.endsWith('.ts') || file.endsWith('.js')) {
49
- try {
50
- const fullPath = path.join(componentsPath, file)
51
- const module = await import(/* @vite-ignore */ fullPath)
52
-
53
- // Look for exported classes that extend LiveComponent
54
- Object.keys(module).forEach(exportName => {
55
- const exportedItem = module[exportName]
56
- if (typeof exportedItem === 'function' &&
57
- exportedItem.prototype &&
58
- this.isLiveComponentClass(exportedItem)) {
59
-
60
- const componentName = exportName.replace(/Component$/, '')
61
- this.registerComponentClass(componentName, exportedItem)
62
- }
63
- })
64
- } catch (error) {
65
- console.warn(`⚠️ Failed to load component from ${file}:`, error)
66
- }
67
- }
68
- }
69
- } catch (error) {
70
- console.error('❌ Auto-discovery failed:', error)
71
- }
72
- }
73
-
74
- // Check if a class extends LiveComponent
75
- private isLiveComponentClass(cls: any): boolean {
76
- try {
77
- let prototype = cls.prototype
78
- while (prototype) {
79
- if (prototype.constructor.name === 'LiveComponent') {
80
- return true
81
- }
82
- prototype = Object.getPrototypeOf(prototype)
83
- }
84
- return false
85
- } catch {
86
- return false
87
- }
88
- }
89
-
90
- // Mount component instance
91
- async mountComponent(
92
- ws: any,
93
- componentName: string,
94
- props: any = {},
95
- options?: { room?: string; userId?: string }
96
- ): Promise<string> {
97
- // Try to find component definition first
98
- let definition = this.definitions.get(componentName)
99
- let ComponentClass: any = null
100
- let initialState: any = {}
101
-
102
- if (definition) {
103
- // Use registered definition
104
- ComponentClass = definition.component
105
- initialState = definition.initialState
106
- } else {
107
- // Try auto-discovered components
108
- ComponentClass = this.autoDiscoveredComponents.get(componentName)
109
- if (!ComponentClass) {
110
- // Try variations of the name
111
- const variations = [
112
- componentName + 'Component',
113
- componentName.charAt(0).toUpperCase() + componentName.slice(1) + 'Component',
114
- componentName.charAt(0).toUpperCase() + componentName.slice(1)
115
- ]
116
-
117
- for (const variation of variations) {
118
- ComponentClass = this.autoDiscoveredComponents.get(variation)
119
- if (ComponentClass) break
120
- }
121
- }
122
-
123
- if (!ComponentClass) {
124
- const availableComponents = [
125
- ...Array.from(this.definitions.keys()),
126
- ...Array.from(this.autoDiscoveredComponents.keys())
127
- ]
128
- throw new Error(`Component '${componentName}' not found. Available: ${availableComponents.join(', ')}`)
129
- }
130
-
131
- // Create a default initial state for auto-discovered components
132
- initialState = {}
133
- }
134
-
135
- // Create component instance with registry methods
136
- const component = new ComponentClass(
137
- { ...initialState, ...props },
138
- ws,
139
- options
140
- )
141
-
142
- // Inject registry methods
143
- component.broadcastToRoom = (message: BroadcastMessage) => {
144
- this.broadcastToRoom(message, component.id)
145
- }
146
-
147
- // Store component and connection
148
- this.components.set(component.id, component)
149
- this.wsConnections.set(component.id, ws)
150
-
151
- // Subscribe to room if specified
152
- if (options?.room) {
153
- this.subscribeToRoom(component.id, options.room)
154
- }
155
-
156
- // Initialize WebSocket data if needed
157
- if (!ws.data) {
158
- ws.data = {
159
- components: new Map(),
160
- subscriptions: new Set(),
161
- userId: options?.userId
162
- } as WebSocketData
163
- }
164
-
165
- ws.data.components.set(component.id, component)
166
-
167
- console.log(`🚀 Mounted component: ${componentName} (${component.id})`)
168
-
169
- // Send initial state to client
170
- component.emit('STATE_UPDATE', { state: component.getSerializableState() })
171
-
172
- return component.id
173
- }
174
-
175
- // Unmount component
176
- async unmountComponent(componentId: string) {
177
- const component = this.components.get(componentId)
178
- if (!component) return
179
-
180
- // Cleanup
181
- component.destroy()
182
-
183
- // Remove from room subscriptions
184
- this.unsubscribeFromAllRooms(componentId)
185
-
186
- // Remove from maps
187
- this.components.delete(componentId)
188
- this.wsConnections.delete(componentId)
189
-
190
- console.log(`🗑️ Unmounted component: ${componentId}`)
191
- }
192
-
193
- // Execute action on component
194
- async executeAction(componentId: string, action: string, payload: any): Promise<any> {
195
- const component = this.components.get(componentId)
196
- if (!component) {
197
- throw new Error(`Component '${componentId}' not found`)
198
- }
199
-
200
- try {
201
- return await component.executeAction(action, payload)
202
- } catch (error: any) {
203
- console.error(`❌ Error executing action '${action}' on component '${componentId}':`, error.message)
204
- throw error
205
- }
206
- }
207
-
208
- // Update component property
209
- updateProperty(componentId: string, property: string, value: any) {
210
- const component = this.components.get(componentId)
211
- if (!component) {
212
- throw new Error(`Component '${componentId}' not found`)
213
- }
214
-
215
- // Update state
216
- const updates = { [property]: value }
217
- component.setState(updates)
218
-
219
- console.log(`📝 Updated property '${property}' on component '${componentId}'`)
220
- }
221
-
222
- // Subscribe component to room
223
- subscribeToRoom(componentId: string, roomId: string) {
224
- if (!this.rooms.has(roomId)) {
225
- this.rooms.set(roomId, new Set())
226
- }
227
-
228
- this.rooms.get(roomId)!.add(componentId)
229
- console.log(`📡 Component '${componentId}' subscribed to room '${roomId}'`)
230
- }
231
-
232
- // Unsubscribe component from room
233
- unsubscribeFromRoom(componentId: string, roomId: string) {
234
- const room = this.rooms.get(roomId)
235
- if (room) {
236
- room.delete(componentId)
237
- if (room.size === 0) {
238
- this.rooms.delete(roomId)
239
- }
240
- }
241
- console.log(`📡 Component '${componentId}' unsubscribed from room '${roomId}'`)
242
- }
243
-
244
- // Unsubscribe from all rooms
245
- private unsubscribeFromAllRooms(componentId: string) {
246
- for (const [roomId, components] of Array.from(this.rooms.entries())) {
247
- if (components.has(componentId)) {
248
- this.unsubscribeFromRoom(componentId, roomId)
249
- }
250
- }
251
- }
252
-
253
- // Broadcast message to room
254
- broadcastToRoom(message: BroadcastMessage, senderComponentId?: string) {
255
- if (!message.room) return
256
-
257
- const roomComponents = this.rooms.get(message.room)
258
- if (!roomComponents) return
259
-
260
- const broadcastMessage: LiveMessage = {
261
- type: 'BROADCAST',
262
- componentId: senderComponentId || 'system',
263
- payload: {
264
- type: message.type,
265
- data: message.payload
266
- },
267
- timestamp: Date.now(),
268
- room: message.room
269
- }
270
-
271
- let broadcastCount = 0
272
-
273
- for (const componentId of Array.from(roomComponents)) {
274
- // Skip sender if excludeUser is specified
275
- const component = this.components.get(componentId)
276
- if (message.excludeUser && component?.userId === message.excludeUser) {
277
- continue
278
- }
279
-
280
- const ws = this.wsConnections.get(componentId)
281
- if (ws && ws.send) {
282
- ws.send(JSON.stringify(broadcastMessage))
283
- broadcastCount++
284
- }
285
- }
286
-
287
- console.log(`📡 Broadcast '${message.type}' to room '${message.room}': ${broadcastCount} recipients`)
288
- }
289
-
290
- // Handle WebSocket message
291
- async handleMessage(ws: any, message: LiveMessage): Promise<any> {
292
- try {
293
- switch (message.type) {
294
- case 'COMPONENT_MOUNT':
295
- const componentId = await this.mountComponent(
296
- ws,
297
- message.payload.component,
298
- message.payload.props,
299
- {
300
- room: message.payload.room,
301
- userId: message.userId
302
- }
303
- )
304
- return { success: true, result: { componentId } }
305
-
306
- case 'COMPONENT_UNMOUNT':
307
- await this.unmountComponent(message.componentId)
308
- return { success: true }
309
-
310
- case 'CALL_ACTION':
311
- // Execute action - response depends on expectResponse flag
312
- const actionResult = await this.executeAction(
313
- message.componentId,
314
- message.action!,
315
- message.payload
316
- )
317
-
318
- // If client expects response, return it
319
- if (message.expectResponse) {
320
- return { success: true, result: actionResult }
321
- }
322
-
323
- // Otherwise no return - if state changed, component will emit STATE_UPDATE automatically
324
- return null
325
-
326
- case 'PROPERTY_UPDATE':
327
- this.updateProperty(
328
- message.componentId,
329
- message.property!,
330
- message.payload.value
331
- )
332
- return { success: true }
333
-
334
- default:
335
- console.warn(`⚠️ Unknown message type: ${message.type}`)
336
- return { success: false, error: 'Unknown message type' }
337
- }
338
- } catch (error: any) {
339
- console.error('❌ Registry error:', error.message)
340
-
341
- // Send error back to client
342
- const errorMessage: LiveMessage = {
343
- type: 'ERROR',
344
- componentId: message.componentId || 'system',
345
- payload: {
346
- error: error.message,
347
- action: message.action,
348
- originalMessage: message.type
349
- },
350
- timestamp: Date.now()
351
- }
352
-
353
- ws.send(JSON.stringify(errorMessage))
354
-
355
- return { success: false, error: error.message }
356
- }
357
- }
358
-
359
- // Cleanup when WebSocket disconnects
360
- cleanupConnection(ws: any) {
361
- if (!ws.data?.components) return
362
-
363
- const componentsToCleanup = Array.from(ws.data.components.keys()) as string[]
364
-
365
- for (const componentId of componentsToCleanup) {
366
- this.unmountComponent(componentId)
367
- }
368
-
369
- console.log(`🧹 Cleaned up ${componentsToCleanup.length} components from disconnected WebSocket`)
370
- }
371
-
372
- // Get statistics
373
- getStats() {
374
- return {
375
- components: this.components.size,
376
- definitions: this.definitions.size,
377
- rooms: this.rooms.size,
378
- connections: this.wsConnections.size,
379
- roomDetails: Object.fromEntries(
380
- Array.from(this.rooms.entries()).map(([roomId, components]) => [
381
- roomId,
382
- components.size
383
- ])
384
- )
385
- }
386
- }
387
-
388
- // Get component by ID
389
- getComponent(componentId: string): LiveComponent | undefined {
390
- return this.components.get(componentId)
391
- }
392
-
393
- // Get all components in room
394
- getRoomComponents(roomId: string): LiveComponent[] {
395
- const componentIds = this.rooms.get(roomId) || new Set()
396
- return Array.from(componentIds)
397
- .map(id => this.components.get(id))
398
- .filter(Boolean) as LiveComponent[]
399
- }
400
- }
401
-
402
- // Global registry instance
403
- export const componentRegistry = new ComponentRegistry()
@@ -1,241 +0,0 @@
1
- // 🔥 FluxStack Live Components - Core Types
2
-
3
- export interface LiveMessage {
4
- type: 'COMPONENT_MOUNT' | 'COMPONENT_UNMOUNT' | 'COMPONENT_ACTION' | 'CALL_ACTION' | 'ACTION_RESPONSE' | 'PROPERTY_UPDATE' | 'STATE_UPDATE' | 'ERROR' | 'BROADCAST'
5
- componentId: string
6
- action?: string
7
- property?: string
8
- payload?: any
9
- timestamp?: number
10
- userId?: string
11
- room?: string
12
- // Request-Response system
13
- requestId?: string
14
- responseId?: string
15
- expectResponse?: boolean
16
- }
17
-
18
- export interface ComponentState {
19
- [key: string]: any
20
- }
21
-
22
- export interface LiveComponentInstance<TState = ComponentState, TActions = Record<string, Function>> {
23
- id: string
24
- state: TState
25
- call: <T extends keyof TActions>(action: T, ...args: any[]) => Promise<any>
26
- set: <K extends keyof TState>(property: K, value: TState[K]) => void
27
- loading: boolean
28
- errors: Record<string, string>
29
- connected: boolean
30
- room?: string
31
- }
32
-
33
- export interface WebSocketData {
34
- components: Map<string, any>
35
- userId?: string
36
- subscriptions: Set<string>
37
- }
38
-
39
- export interface ComponentDefinition<TState = ComponentState> {
40
- name: string
41
- initialState: TState
42
- component: new (initialState: TState, ws: any) => LiveComponent<TState>
43
- }
44
-
45
- export interface BroadcastMessage {
46
- type: string
47
- payload: any
48
- room?: string
49
- excludeUser?: string
50
- }
51
-
52
- export abstract class LiveComponent<TState = ComponentState> {
53
- public readonly id: string
54
- public state: TState
55
- protected ws: any
56
- protected room?: string
57
- protected userId?: string
58
- public broadcastToRoom: (message: BroadcastMessage) => void = () => {} // Will be injected by registry
59
-
60
- constructor(initialState: TState, ws: any, options?: { room?: string; userId?: string }) {
61
- this.id = this.generateId()
62
- this.state = initialState
63
- this.ws = ws
64
- this.room = options?.room
65
- this.userId = options?.userId
66
- }
67
-
68
- // State management
69
- protected setState(updates: Partial<TState> | ((prev: TState) => Partial<TState>)) {
70
- const newUpdates = typeof updates === 'function' ? updates(this.state) : updates
71
- this.state = { ...this.state, ...newUpdates }
72
- this.emit('STATE_UPDATE', { state: this.state })
73
- }
74
-
75
- // Execute action safely
76
- public async executeAction(action: string, payload: any): Promise<any> {
77
- try {
78
- // Check if method exists
79
- const method = (this as any)[action]
80
- if (typeof method !== 'function') {
81
- throw new Error(`Action '${action}' not found on component`)
82
- }
83
-
84
- // Execute method
85
- const result = await method.call(this, payload)
86
- return result
87
- } catch (error: any) {
88
- this.emit('ERROR', {
89
- action,
90
- error: error.message,
91
- stack: error.stack
92
- })
93
- throw error
94
- }
95
- }
96
-
97
- // Send message to client
98
- protected emit(type: string, payload: any) {
99
- const message: LiveMessage = {
100
- type: type as any,
101
- componentId: this.id,
102
- payload,
103
- timestamp: Date.now(),
104
- userId: this.userId,
105
- room: this.room
106
- }
107
-
108
- if (this.ws && this.ws.send) {
109
- this.ws.send(JSON.stringify(message))
110
- }
111
- }
112
-
113
- // Broadcast to all clients in room
114
- protected broadcast(type: string, payload: any, excludeCurrentUser = false) {
115
- const message: BroadcastMessage = {
116
- type,
117
- payload,
118
- room: this.room,
119
- excludeUser: excludeCurrentUser ? this.userId : undefined
120
- }
121
-
122
- // This will be handled by the registry
123
- this.broadcastToRoom(message)
124
- }
125
-
126
- // Subscribe to room for multi-user features
127
- protected async subscribeToRoom(roomId: string) {
128
- this.room = roomId
129
- // Registry will handle the actual subscription
130
- }
131
-
132
- // Unsubscribe from room
133
- protected async unsubscribeFromRoom() {
134
- this.room = undefined
135
- // Registry will handle the actual unsubscription
136
- }
137
-
138
- // Generate unique ID
139
- private generateId(): string {
140
- return `live-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
141
- }
142
-
143
- // Cleanup when component is destroyed
144
- public destroy() {
145
- this.unsubscribeFromRoom()
146
- // Override in subclasses for custom cleanup
147
- }
148
-
149
- // Get serializable state for client
150
- public getSerializableState(): TState {
151
- return this.state
152
- }
153
-
154
- // ===== Livewire-style Actions =====
155
- // These are automatically available for all LiveComponents
156
- // Used by useLivewire() hook for transparent property access
157
-
158
- /**
159
- * Set a single state property value
160
- * Used by useLivewire() proxy for transparent property assignment
161
- *
162
- * @example
163
- * // Frontend with useLivewire:
164
- * clock.format = '12h' // Automatically calls setValue({ key: 'format', value: '12h' })
165
- */
166
- async setValue<K extends keyof TState>(payload: { key: K; value: TState[K] }): Promise<{ success: boolean; key: K; value: TState[K] }> {
167
- const { key, value } = payload
168
-
169
- // Validate that the key exists in state
170
- const stateObj = this.state as Record<string, unknown>
171
- if (!(String(key) in stateObj)) {
172
- throw new Error(`Property '${String(key)}' does not exist in component state`)
173
- }
174
-
175
- this.setState({ [key]: value } as unknown as Partial<TState>)
176
-
177
- return { success: true, key, value }
178
- }
179
-
180
- /**
181
- * Set multiple state properties at once
182
- * Useful for batch updates
183
- *
184
- * @example
185
- * await clock.$call('setValues', { format: '12h', showSeconds: false })
186
- */
187
- async setValues(payload: Partial<TState>): Promise<{ success: boolean; updated: (keyof TState)[] }> {
188
- const stateObj = this.state as Record<string, unknown>
189
- const validKeys = Object.keys(payload).filter(key => key in stateObj) as (keyof TState)[]
190
-
191
- if (validKeys.length === 0) {
192
- throw new Error('No valid properties to update')
193
- }
194
-
195
- const updates = validKeys.reduce((acc, key) => {
196
- acc[key] = payload[key] as TState[keyof TState]
197
- return acc
198
- }, {} as Partial<TState>)
199
-
200
- this.setState(updates)
201
-
202
- return { success: true, updated: validKeys }
203
- }
204
-
205
- /**
206
- * Get a single state property value
207
- * Useful for getting computed/derived values from server
208
- */
209
- async getValue<K extends keyof TState>(payload: { key: K }): Promise<{ success: boolean; key: K; value: TState[K] }> {
210
- const { key } = payload
211
-
212
- const stateObj = this.state as Record<string, unknown>
213
- if (!(String(key) in stateObj)) {
214
- throw new Error(`Property '${String(key)}' does not exist in component state`)
215
- }
216
-
217
- return { success: true, key, value: this.state[key] }
218
- }
219
-
220
- /**
221
- * Get all state values (snapshot)
222
- */
223
- async getSnapshot(): Promise<{ success: boolean; state: TState; timestamp: number }> {
224
- return {
225
- success: true,
226
- state: this.getSerializableState(),
227
- timestamp: Date.now()
228
- }
229
- }
230
- }
231
-
232
- // Utility types for better TypeScript experience
233
- export type ComponentActions<T> = {
234
- [K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never
235
- }
236
-
237
- export type ComponentProps<T extends LiveComponent> = T extends LiveComponent<infer TState> ? TState : never
238
-
239
- export type ActionParameters<T, K extends keyof T> = T[K] extends (...args: infer P) => any ? P : never
240
-
241
- export type ActionReturnType<T, K extends keyof T> = T[K] extends (...args: any[]) => infer R ? R : never
package/workspace.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "name": "fluxstack-workspace",
3
- "workspaces": [
4
- "./packages/*"
5
- ]
6
- }