create-fluxstack 1.12.0 → 1.13.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 (82) 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/resources/live-auth.md +447 -0
  5. package/LLMD/resources/live-components.md +79 -21
  6. package/LLMD/resources/live-logging.md +158 -0
  7. package/LLMD/resources/live-upload.md +1 -1
  8. package/LLMD/resources/rest-auth.md +290 -0
  9. package/README.md +520 -340
  10. package/app/client/src/App.tsx +11 -0
  11. package/app/client/src/components/AppLayout.tsx +1 -0
  12. package/app/client/src/live/AuthDemo.tsx +332 -0
  13. package/app/client/src/live/RoomChatDemo.tsx +24 -105
  14. package/app/server/auth/AuthManager.ts +213 -0
  15. package/app/server/auth/DevAuthProvider.ts +66 -0
  16. package/app/server/auth/HashManager.ts +123 -0
  17. package/app/server/auth/JWTAuthProvider.example.ts +101 -0
  18. package/app/server/auth/RateLimiter.ts +106 -0
  19. package/app/server/auth/contracts.ts +192 -0
  20. package/app/server/auth/guards/SessionGuard.ts +167 -0
  21. package/app/server/auth/guards/TokenGuard.ts +202 -0
  22. package/app/server/auth/index.ts +174 -0
  23. package/app/server/auth/middleware.ts +163 -0
  24. package/app/server/auth/providers/InMemoryProvider.ts +162 -0
  25. package/app/server/auth/sessions/SessionManager.ts +164 -0
  26. package/app/server/cache/CacheManager.ts +81 -0
  27. package/app/server/cache/MemoryDriver.ts +112 -0
  28. package/app/server/cache/contracts.ts +49 -0
  29. package/app/server/cache/index.ts +42 -0
  30. package/app/server/index.ts +14 -0
  31. package/app/server/live/LiveAdminPanel.ts +173 -0
  32. package/app/server/live/LiveCounter.ts +1 -0
  33. package/app/server/live/LiveLocalCounter.ts +13 -8
  34. package/app/server/live/LiveProtectedChat.ts +150 -0
  35. package/app/server/live/LiveRoomChat.ts +45 -203
  36. package/app/server/routes/auth.routes.ts +278 -0
  37. package/app/server/routes/index.ts +2 -0
  38. package/config/index.ts +8 -0
  39. package/config/system/auth.config.ts +49 -0
  40. package/config/system/session.config.ts +33 -0
  41. package/core/client/LiveComponentsProvider.tsx +76 -5
  42. package/core/client/components/Live.tsx +2 -1
  43. package/core/client/hooks/useLiveComponent.ts +47 -4
  44. package/core/client/index.ts +2 -1
  45. package/core/framework/server.ts +36 -4
  46. package/core/plugins/built-in/live-components/commands/create-live-component.ts +15 -8
  47. package/core/plugins/built-in/monitoring/index.ts +10 -3
  48. package/core/plugins/built-in/vite/index.ts +95 -18
  49. package/core/plugins/config.ts +5 -4
  50. package/core/plugins/discovery.ts +11 -2
  51. package/core/plugins/manager.ts +11 -5
  52. package/core/plugins/module-resolver.ts +1 -1
  53. package/core/plugins/registry.ts +53 -25
  54. package/core/server/live/ComponentRegistry.ts +79 -24
  55. package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
  56. package/core/server/live/LiveLogger.ts +111 -0
  57. package/core/server/live/LiveRoomManager.ts +5 -4
  58. package/core/server/live/StateSignature.ts +644 -643
  59. package/core/server/live/auth/LiveAuthContext.ts +71 -0
  60. package/core/server/live/auth/LiveAuthManager.ts +304 -0
  61. package/core/server/live/auth/index.ts +19 -0
  62. package/core/server/live/auth/types.ts +179 -0
  63. package/core/server/live/auto-generated-components.ts +8 -2
  64. package/core/server/live/index.ts +16 -0
  65. package/core/server/live/websocket-plugin.ts +92 -16
  66. package/core/templates/create-project.ts +0 -3
  67. package/core/types/types.ts +133 -13
  68. package/core/utils/index.ts +17 -17
  69. package/core/utils/logger/index.ts +5 -2
  70. package/core/utils/version.ts +1 -1
  71. package/package.json +1 -8
  72. package/plugins/crypto-auth/index.ts +6 -0
  73. package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
  74. package/plugins/crypto-auth/server/index.ts +24 -21
  75. package/rest-tests/README.md +57 -0
  76. package/rest-tests/auth-token.http +113 -0
  77. package/rest-tests/auth.http +112 -0
  78. package/rest-tests/rooms-token.http +69 -0
  79. package/rest-tests/users-token.http +62 -0
  80. package/.dockerignore +0 -81
  81. package/Dockerfile +0 -70
  82. package/LIVE_COMPONENTS_REVIEW.md +0 -781
@@ -90,6 +90,36 @@
90
90
  | `BUILD_REMOVE_UNUSED_CSS` | boolean | `false` | Remove unused CSS | build.config.ts |
91
91
  | `BUILD_OPTIMIZE_IMAGES` | boolean | `false` | Optimize images | build.config.ts |
92
92
 
93
+ ## Live Component Logging
94
+
95
+ | Variable | Type | Default | Description | Config File |
96
+ |----------|------|---------|-------------|-------------|
97
+ | `LIVE_LOGGING` | string | `undefined` | Global live component logs: `true`, `false`, or comma-separated categories (`lifecycle,rooms,messages`) | env only |
98
+
99
+ > Per-component logging is controlled via `static logging` on the class. See [Live Logging](../resources/live-logging.md).
100
+
101
+ ## Authentication
102
+
103
+ | Variable | Type | Default | Description | Config File |
104
+ |----------|------|---------|-------------|-------------|
105
+ | `AUTH_DEFAULT_GUARD` | enum | `'session'` | Auth guard: `session`, `token` | auth.config.ts |
106
+ | `AUTH_DEFAULT_PROVIDER` | enum | `'memory'` | User provider: `memory`, `database` | auth.config.ts |
107
+ | `AUTH_HASH_ALGORITHM` | enum | `'bcrypt'` | Hash algorithm: `bcrypt`, `argon2id` | auth.config.ts |
108
+ | `AUTH_BCRYPT_ROUNDS` | number | `10` | Bcrypt cost rounds | auth.config.ts |
109
+ | `AUTH_RATE_LIMIT_MAX_ATTEMPTS` | number | `5` | Max login attempts before lockout | auth.config.ts |
110
+ | `AUTH_RATE_LIMIT_DECAY_SECONDS` | number | `60` | Rate limit window (seconds) | auth.config.ts |
111
+ | `AUTH_TOKEN_TTL` | number | `86400` | Bearer token TTL (seconds, token guard only) | auth.config.ts |
112
+
113
+ ## Session
114
+
115
+ | Variable | Type | Default | Description | Config File |
116
+ |----------|------|---------|-------------|-------------|
117
+ | `SESSION_COOKIE` | string | `'fluxstack_session'` | Session cookie name | session.config.ts |
118
+ | `SESSION_LIFETIME` | number | `7200` | Session duration (seconds) | session.config.ts |
119
+ | `SESSION_HTTP_ONLY` | boolean | `true` | HttpOnly cookie flag | session.config.ts |
120
+ | `SESSION_SECURE` | boolean | `false` | Secure cookie flag (HTTPS only) | session.config.ts |
121
+ | `SESSION_SAME_SITE` | enum | `'lax'` | SameSite policy: `lax`, `strict`, `none` | session.config.ts |
122
+
93
123
  ## Logging
94
124
 
95
125
  | Variable | Type | Default | Description | Config File |
@@ -0,0 +1,447 @@
1
+ # Live Components Authentication
2
+
3
+ **Version:** 1.14.0 | **Updated:** 2025-02-12
4
+
5
+ ## Quick Facts
6
+
7
+ - Declarative auth configuration via `static auth` and `static actionAuth`
8
+ - Role-based access control (RBAC) with OR logic
9
+ - Permission-based access control with AND logic
10
+ - Auto re-mount when authentication changes
11
+ - Pluggable auth providers (JWT, Crypto, Custom)
12
+ - `$auth` helper available in component actions
13
+
14
+ ## Server-Side: Protected Components
15
+
16
+ ### Basic Protection (Auth Required)
17
+
18
+ ```typescript
19
+ // app/server/live/ProtectedChat.ts
20
+ import { LiveComponent } from '@core/types/types'
21
+ import type { LiveComponentAuth } from '@core/server/live/auth/types'
22
+
23
+ export class ProtectedChat extends LiveComponent<typeof ProtectedChat.defaultState> {
24
+ static componentName = 'ProtectedChat'
25
+ static defaultState = {
26
+ messages: [] as string[]
27
+ }
28
+
29
+ // Auth required to mount this component
30
+ static auth: LiveComponentAuth = {
31
+ required: true
32
+ }
33
+
34
+ async sendMessage(payload: { text: string }) {
35
+ // Only authenticated users can call this
36
+ return { success: true }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ### Role-Based Protection
42
+
43
+ ```typescript
44
+ // app/server/live/AdminPanel.ts
45
+ import { LiveComponent } from '@core/types/types'
46
+ import type { LiveComponentAuth } from '@core/server/live/auth/types'
47
+
48
+ export class AdminPanel extends LiveComponent<typeof AdminPanel.defaultState> {
49
+ static componentName = 'AdminPanel'
50
+ static defaultState = {
51
+ users: [] as { id: string; name: string; role: string }[]
52
+ }
53
+
54
+ // Requires auth + admin OR moderator role (OR logic)
55
+ static auth: LiveComponentAuth = {
56
+ required: true,
57
+ roles: ['admin', 'moderator']
58
+ }
59
+
60
+ async deleteUser(payload: { userId: string }) {
61
+ // User info available via $auth
62
+ console.log(`User ${this.$auth.user?.id} deleting ${payload.userId}`)
63
+ return { success: true }
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Permission-Based Protection
69
+
70
+ ```typescript
71
+ // app/server/live/ContentEditor.ts
72
+ import { LiveComponent } from '@core/types/types'
73
+ import type { LiveComponentAuth } from '@core/server/live/auth/types'
74
+
75
+ export class ContentEditor extends LiveComponent<typeof ContentEditor.defaultState> {
76
+ static componentName = 'ContentEditor'
77
+ static defaultState = {
78
+ content: ''
79
+ }
80
+
81
+ // Requires ALL permissions (AND logic)
82
+ static auth: LiveComponentAuth = {
83
+ required: true,
84
+ permissions: ['content.read', 'content.write']
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### Per-Action Protection
90
+
91
+ ```typescript
92
+ // app/server/live/ModerationPanel.ts
93
+ import { LiveComponent } from '@core/types/types'
94
+ import type { LiveComponentAuth, LiveActionAuthMap } from '@core/server/live/auth/types'
95
+
96
+ export class ModerationPanel extends LiveComponent<typeof ModerationPanel.defaultState> {
97
+ static componentName = 'ModerationPanel'
98
+ static defaultState = {
99
+ reports: [] as any[]
100
+ }
101
+
102
+ // Component-level: any authenticated user
103
+ static auth: LiveComponentAuth = {
104
+ required: true
105
+ }
106
+
107
+ // Per-action auth
108
+ static actionAuth: LiveActionAuthMap = {
109
+ deleteReport: { permissions: ['reports.delete'] },
110
+ banUser: { roles: ['admin', 'moderator'] }
111
+ }
112
+
113
+ // Anyone authenticated can view
114
+ async getReports() {
115
+ return { reports: this.state.reports }
116
+ }
117
+
118
+ // Requires reports.delete permission
119
+ async deleteReport(payload: { reportId: string }) {
120
+ return { success: true }
121
+ }
122
+
123
+ // Requires admin OR moderator role
124
+ async banUser(payload: { userId: string }) {
125
+ return { success: true }
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## Using $auth in Actions
131
+
132
+ The `$auth` helper provides access to the authenticated user context:
133
+
134
+ ```typescript
135
+ export class MyComponent extends LiveComponent<State> {
136
+ async myAction() {
137
+ // Check if authenticated
138
+ if (!this.$auth.authenticated) {
139
+ throw new Error('Not authenticated')
140
+ }
141
+
142
+ // Get user info
143
+ const userId = this.$auth.user?.id
144
+ const userName = this.$auth.user?.name
145
+
146
+ // Check roles
147
+ if (this.$auth.hasRole('admin')) {
148
+ // Admin-only logic
149
+ }
150
+
151
+ if (this.$auth.hasAnyRole(['admin', 'moderator'])) {
152
+ // Admin OR moderator logic
153
+ }
154
+
155
+ // Check permissions
156
+ if (this.$auth.hasPermission('users.delete')) {
157
+ // Has specific permission
158
+ }
159
+
160
+ if (this.$auth.hasAllPermissions(['users.read', 'users.write'])) {
161
+ // Has ALL permissions
162
+ }
163
+
164
+ return { userId }
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### $auth API
170
+
171
+ ```typescript
172
+ interface LiveAuthContext {
173
+ readonly authenticated: boolean
174
+ readonly user?: {
175
+ id: string
176
+ roles?: string[]
177
+ permissions?: string[]
178
+ [key: string]: unknown // Custom fields
179
+ }
180
+ readonly token?: string
181
+ readonly authenticatedAt?: number
182
+
183
+ hasRole(role: string): boolean
184
+ hasAnyRole(roles: string[]): boolean
185
+ hasAllRoles(roles: string[]): boolean
186
+ hasPermission(permission: string): boolean
187
+ hasAnyPermission(permissions: string[]): boolean
188
+ hasAllPermissions(permissions: string[]): boolean
189
+ }
190
+ ```
191
+
192
+ ## Client-Side: Authentication
193
+
194
+ ### Authenticate on Connection
195
+
196
+ Pass auth credentials when the WebSocket connects:
197
+
198
+ ```typescript
199
+ // app/client/src/App.tsx
200
+ import { LiveComponentsProvider } from '@/core/client'
201
+
202
+ function App() {
203
+ const token = localStorage.getItem('auth_token')
204
+
205
+ return (
206
+ <LiveComponentsProvider
207
+ auth={{ token }} // Sent as query param on connect
208
+ autoConnect={true}
209
+ >
210
+ <AppContent />
211
+ </LiveComponentsProvider>
212
+ )
213
+ }
214
+ ```
215
+
216
+ ### Dynamic Authentication
217
+
218
+ Authenticate after connection via `useLiveComponents`:
219
+
220
+ ```typescript
221
+ import { useLiveComponents } from '@/core/client'
222
+
223
+ function LoginForm() {
224
+ const { authenticated, authenticate } = useLiveComponents()
225
+ const [token, setToken] = useState('')
226
+
227
+ const handleLogin = async () => {
228
+ const success = await authenticate({ token })
229
+ if (success) {
230
+ // Components with auth errors will auto re-mount
231
+ console.log('Authenticated!')
232
+ }
233
+ }
234
+
235
+ return (
236
+ <div>
237
+ <p>Status: {authenticated ? 'Logged in' : 'Not logged in'}</p>
238
+ <input value={token} onChange={e => setToken(e.target.value)} />
239
+ <button onClick={handleLogin}>Login</button>
240
+ </div>
241
+ )
242
+ }
243
+ ```
244
+
245
+ ### Checking Auth Status in Components
246
+
247
+ ```typescript
248
+ import { Live } from '@/core/client'
249
+ import { AdminPanel } from '@server/live/AdminPanel'
250
+
251
+ function AdminSection() {
252
+ const panel = Live.use(AdminPanel)
253
+
254
+ // Check if authenticated on WebSocket level
255
+ if (!panel.$authenticated) {
256
+ return <p>Please log in</p>
257
+ }
258
+
259
+ // Check for auth errors
260
+ if (panel.$error?.includes('AUTH_DENIED')) {
261
+ return <p>Access denied: {panel.$error}</p>
262
+ }
263
+
264
+ return <div>{/* Admin content */}</div>
265
+ }
266
+ ```
267
+
268
+ ### Auto Re-mount on Auth Change
269
+
270
+ When authentication changes from `false` to `true`, components that failed with `AUTH_DENIED` automatically retry mounting:
271
+
272
+ ```typescript
273
+ // No manual code needed!
274
+ // 1. User tries to mount AdminPanel → AUTH_DENIED
275
+ // 2. User calls authenticate({ token: 'admin-token' })
276
+ // 3. AdminPanel automatically re-mounts with auth context
277
+ ```
278
+
279
+ ## Auth Providers
280
+
281
+ ### Creating a Custom Provider
282
+
283
+ ```typescript
284
+ // app/server/auth/MyAuthProvider.ts
285
+ import type {
286
+ LiveAuthProvider,
287
+ LiveAuthCredentials,
288
+ LiveAuthContext
289
+ } from '@core/server/live/auth/types'
290
+ import { AuthenticatedContext } from '@core/server/live/auth/LiveAuthContext'
291
+
292
+ export class MyAuthProvider implements LiveAuthProvider {
293
+ readonly name = 'my-auth'
294
+
295
+ async authenticate(credentials: LiveAuthCredentials): Promise<LiveAuthContext | null> {
296
+ const token = credentials.token as string
297
+ if (!token) return null
298
+
299
+ // Validate token (JWT decode, database lookup, etc.)
300
+ const user = await validateToken(token)
301
+ if (!user) return null
302
+
303
+ return new AuthenticatedContext(
304
+ {
305
+ id: user.id,
306
+ name: user.name,
307
+ roles: user.roles,
308
+ permissions: user.permissions
309
+ },
310
+ token
311
+ )
312
+ }
313
+
314
+ // Optional: Custom action authorization
315
+ async authorizeAction(
316
+ context: LiveAuthContext,
317
+ componentName: string,
318
+ action: string
319
+ ): Promise<boolean> {
320
+ // Custom logic (rate limiting, business rules, etc.)
321
+ return true
322
+ }
323
+
324
+ // Optional: Custom room authorization
325
+ async authorizeRoom(
326
+ context: LiveAuthContext,
327
+ roomId: string
328
+ ): Promise<boolean> {
329
+ // Example: VIP rooms require premium role
330
+ if (roomId.startsWith('vip-') && !context.hasRole('premium')) {
331
+ return false
332
+ }
333
+ return true
334
+ }
335
+ }
336
+ ```
337
+
338
+ ### Registering Providers
339
+
340
+ ```typescript
341
+ // app/server/index.ts
342
+ import { liveAuthManager } from '@core/server/live/auth'
343
+ import { MyAuthProvider } from './auth/MyAuthProvider'
344
+
345
+ // Register provider
346
+ liveAuthManager.register(new MyAuthProvider())
347
+
348
+ // Optional: Set as default (first registered is default)
349
+ liveAuthManager.setDefault('my-auth')
350
+ ```
351
+
352
+ ### Built-in DevAuthProvider (Development)
353
+
354
+ For testing, a `DevAuthProvider` with simple tokens is available:
355
+
356
+ ```typescript
357
+ // Tokens available in development:
358
+ // - 'admin-token' → role: admin, all permissions
359
+ // - 'user-token' → role: user, basic permissions
360
+ // - 'mod-token' → role: moderator
361
+
362
+ // Already registered in dev mode automatically
363
+ ```
364
+
365
+ ## Auth Flow Diagram
366
+
367
+ ```
368
+ ┌─────────────────────────────────────────────────────────────┐
369
+ │ CLIENT │
370
+ ├─────────────────────────────────────────────────────────────┤
371
+ │ 1. Connect WebSocket (optional: ?token=xxx) │
372
+ │ 2. authenticate({ token }) → AUTH message │
373
+ │ 3. Live.use(ProtectedComponent) → COMPONENT_MOUNT │
374
+ │ 4. If AUTH_DENIED, wait for auth change │
375
+ │ 5. Auth changes → auto re-mount │
376
+ └─────────────────────────────────────────────────────────────┘
377
+
378
+
379
+ ┌─────────────────────────────────────────────────────────────┐
380
+ │ SERVER │
381
+ ├─────────────────────────────────────────────────────────────┤
382
+ │ 1. WebSocket connect → store authContext on ws.data │
383
+ │ 2. AUTH message → liveAuthManager.authenticate() │
384
+ │ 3. COMPONENT_MOUNT → check static auth config │
385
+ │ 4. CALL_ACTION → check static actionAuth config │
386
+ │ 5. Component has access to this.$auth │
387
+ └─────────────────────────────────────────────────────────────┘
388
+ ```
389
+
390
+ ## Configuration Types
391
+
392
+ ```typescript
393
+ // Component-level auth
394
+ interface LiveComponentAuth {
395
+ required?: boolean // Must be authenticated to mount
396
+ roles?: string[] // Required roles (OR logic - any role)
397
+ permissions?: string[] // Required permissions (AND logic - all)
398
+ }
399
+
400
+ // Action-level auth
401
+ interface LiveActionAuth {
402
+ roles?: string[] // Required roles (OR logic)
403
+ permissions?: string[] // Required permissions (AND logic)
404
+ }
405
+
406
+ type LiveActionAuthMap = Record<string, LiveActionAuth>
407
+
408
+ // Credentials from client
409
+ interface LiveAuthCredentials {
410
+ token?: string
411
+ publicKey?: string // For crypto auth
412
+ signature?: string // For crypto auth
413
+ timestamp?: number
414
+ nonce?: string
415
+ [key: string]: unknown // Custom fields
416
+ }
417
+ ```
418
+
419
+ ## Critical Rules
420
+
421
+ **ALWAYS:**
422
+ - Define `static auth` for protected components
423
+ - Define `static actionAuth` for protected actions
424
+ - Use `$auth.hasRole()` / `$auth.hasPermission()` in action logic
425
+ - Register auth providers before server starts
426
+ - Handle `AUTH_DENIED` errors in client UI
427
+
428
+ **NEVER:**
429
+ - Store sensitive data in component state
430
+ - Trust client-side auth checks alone (always verify server-side)
431
+ - Expose tokens in error messages
432
+ - Skip auth on actions that modify data
433
+
434
+ **AUTH LOGIC:**
435
+ ```typescript
436
+ // Roles: OR logic (any role grants access)
437
+ roles: ['admin', 'moderator'] // admin OR moderator
438
+
439
+ // Permissions: AND logic (all permissions required)
440
+ permissions: ['users.read', 'users.write'] // BOTH required
441
+ ```
442
+
443
+ ## Related
444
+
445
+ - [Live Components](./live-components.md) - Base component documentation
446
+ - [Live Rooms](./live-rooms.md) - Room-based communication
447
+ - [Plugin System](../core/plugin-system.md) - Auth as plugin
@@ -1,18 +1,18 @@
1
1
  # Live Components
2
2
 
3
- **Version:** 1.12.0 | **Updated:** 2025-02-09
3
+ **Version:** 1.13.0 | **Updated:** 2025-02-09
4
4
 
5
5
  ## Quick Facts
6
6
 
7
7
  - Server-side state management with WebSocket sync
8
- - **Reactive state Proxy** - `this.state.count++` auto-syncs
8
+ - **Direct state access** - `this.count++` auto-syncs (v1.13.0)
9
9
  - Automatic state persistence and re-hydration
10
10
  - Room-based event system for multi-user sync
11
11
  - Type-safe client-server communication (FluxStackWebSocket)
12
12
  - Built-in connection management and recovery
13
13
  - **Client component links** - Ctrl+Click navigation
14
14
 
15
- ## LiveComponent Class Structure (v1.12.0)
15
+ ## LiveComponent Class Structure (v1.13.0)
16
16
 
17
17
  Server-side component extends `LiveComponent` with **static defaultState**:
18
18
 
@@ -25,28 +25,38 @@ import type { CounterDemo as _Client } from '@client/src/live/CounterDemo'
25
25
 
26
26
  export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState> {
27
27
  static componentName = 'LiveCounter'
28
+ static logging = ['lifecycle', 'messages'] as const // Per-component logging (optional)
28
29
  static defaultState = {
29
30
  count: 0
30
31
  }
31
32
 
32
- // Reactive state - auto-syncs with frontend
33
+ // Declarar propriedades do estado (TypeScript)
34
+ declare count: number
35
+
36
+ // ✅ Direct state access - auto-syncs with frontend
33
37
  async increment() {
34
- this.state.count++
35
- return { success: true, count: this.state.count }
38
+ this.count++
39
+ return { success: true, count: this.count }
36
40
  }
37
41
 
38
42
  async decrement() {
39
- this.state.count--
40
- return { success: true, count: this.state.count }
43
+ this.count--
44
+ return { success: true, count: this.count }
41
45
  }
42
46
 
43
47
  async reset() {
44
- this.state.count = 0
48
+ this.count = 0
45
49
  return { success: true }
46
50
  }
47
51
  }
48
52
  ```
49
53
 
54
+ ### Key Changes in v1.13.0
55
+
56
+ 1. **Direct state access** - `this.count++` instead of `this.state.count++`
57
+ 2. **declare keyword** - TypeScript hint for dynamic properties
58
+ 3. **Cleaner code** - No need to prefix with `this.state.`
59
+
50
60
  ### Key Changes in v1.12.0
51
61
 
52
62
  1. **Static defaultState inside class** - No external export needed
@@ -120,29 +130,67 @@ export class MyComponent extends LiveComponent<typeof MyComponent.defaultState>
120
130
 
121
131
  ## State Management
122
132
 
123
- ### Reactive State (v1.12.0)
133
+ ### Reactive State Proxy (How It Works)
134
+
135
+ State mutations auto-sync with the frontend via two layers:
136
+
137
+ **Layer 1 — Proxy** (`this.state`): A `Proxy` wraps the internal state object. Any `set` on `this.state` compares old vs new value and, if changed, emits `STATE_DELTA` to the client automatically.
138
+
139
+ **Layer 2 — Direct Accessors** (`this.count`): On construction, `createDirectStateAccessors()` defines a getter/setter via `Object.defineProperty` for each key in `defaultState`. The setter delegates to the proxy, so it also triggers `STATE_DELTA`.
140
+
141
+ ```
142
+ this.count++ → accessor setter → proxy set → STATE_DELTA
143
+ this.state.count++ → proxy set → STATE_DELTA
144
+ this.setState({count: 1}) → Object.assign + single STATE_DELTA (batch)
145
+ ```
146
+
147
+ ### Direct State Access (v1.13.0) ✨
124
148
 
125
- State mutations auto-sync with frontend via Proxy:
149
+ State properties are accessible directly on `this`:
126
150
 
127
151
  ```typescript
128
- // Direct mutation - auto-syncs!
129
- this.state.count++
130
- this.state.message = 'Hello'
152
+ // Declare properties for TypeScript
153
+ declare count: number
154
+ declare message: string
155
+
156
+ // ✅ Direct access - auto-syncs via proxy!
157
+ this.count++
158
+ this.message = 'Hello'
131
159
 
132
- // No need for setState() on single property updates
160
+ // Also works (v1.12.0 style) - same proxy underneath
161
+ this.state.count++
133
162
  ```
134
163
 
164
+ > **Performance note:** Each direct assignment emits one `STATE_DELTA`. For multiple properties at once, use `setState` (single emit).
165
+
135
166
  ### setState (Batch Updates)
136
167
 
137
168
  Use `setState` for multiple properties at once (single emit):
138
169
 
139
170
  ```typescript
140
- // ✅ Batch update - one sync event
171
+ // ✅ Batch update - one STATE_DELTA event
141
172
  this.setState({
142
173
  count: newCount,
143
174
  lastUpdatedBy: userId,
144
175
  updatedAt: new Date().toISOString()
145
176
  })
177
+
178
+ // ✅ Function updater (access previous state)
179
+ this.setState(prev => ({
180
+ count: prev.count + 1,
181
+ lastUpdatedBy: userId
182
+ }))
183
+ ```
184
+
185
+ > `setState` writes directly to `_state` (bypasses proxy) and emits a single `STATE_DELTA` with all changed keys. More efficient than N individual assignments.
186
+
187
+ ### setValue (Generic Action)
188
+
189
+ Built-in action to set any state key from the client:
190
+
191
+ ```typescript
192
+ // Client can call this without defining a custom action
193
+ await component.setValue({ key: 'count', value: 42 })
146
194
  ```
147
195
 
148
196
  ### getSerializableState
@@ -483,6 +531,7 @@ app/client/src/live/
483
531
  Each server file contains:
484
532
  - `static componentName` - Component identifier
485
533
  - `static defaultState` - Initial state object
534
+ - `static logging` - Per-component log control (optional, see [Live Logging](./live-logging.md))
486
535
  - Component class extending `LiveComponent`
487
536
  - Client link via `import type { Demo as _Client }`
488
537
 
@@ -536,6 +585,7 @@ export class MyComponent extends LiveComponent<State> {
536
585
  - Define `static componentName` matching class name
537
586
  - Define `static defaultState` inside the class
538
587
  - Use `typeof ClassName.defaultState` for type parameter
588
+ - Use `declare` for each state property (TypeScript type hint)
539
589
  - Call `super.destroy()` in destroy method if overriding
540
590
  - Use `emitRoomEventWithState` for state changes in rooms
541
591
  - Handle errors in actions (throw Error)
@@ -547,17 +597,22 @@ export class MyComponent extends LiveComponent<State> {
547
597
  - Forget `static componentName` (breaks minification)
548
598
  - Emit room events without subscribing first
549
599
  - Store non-serializable data in state
600
+ - Use reserved names for state properties (id, state, ws, room, userId, $room, $rooms, broadcastToRoom, roomType)
550
601
 
551
- **STATE UPDATES (v1.12.0):**
602
+ **STATE UPDATES (v1.13.0) — all auto-sync via Proxy:**
552
603
  ```typescript
553
- // ✅ Single property - use direct mutation
604
+ // ✅ Direct access (1 prop 1 STATE_DELTA)
605
+ declare count: number
606
+ this.count++
607
+
608
+ // ✅ Also works (same proxy underneath)
554
609
  this.state.count++
555
610
 
556
- // ✅ Multiple properties - use setState (one sync)
611
+ // ✅ Multiple properties use setState (1 STATE_DELTA for all)
557
612
  this.setState({ a: 1, b: 2, c: 3 })
558
613
 
559
614
  // ❌ Don't use setState for single property (unnecessary)
560
- this.setState({ count: this.state.count + 1 })
615
+ this.setState({ count: this.count + 1 })
561
616
  ```
562
617
 
563
618
  ---
@@ -697,7 +752,10 @@ export function UploadDemo() {
697
752
 
698
753
  ## Related
699
754
 
755
+ - [Live Auth](./live-auth.md) - Authentication for Live Components
756
+ - [Live Logging](./live-logging.md) - Per-component logging control
757
+ - [Live Rooms](./live-rooms.md) - Multi-room real-time communication
758
+ - [Live Upload](./live-upload.md) - Chunked file upload
700
759
  - [Project Structure](../patterns/project-structure.md)
701
760
  - [Type Safety Patterns](../patterns/type-safety.md)
702
761
  - [WebSocket Plugin](../core/plugin-system.md)
703
- - [Live Upload](./live-upload.md)