jac-client 0.1.0__py3-none-any.whl

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 (33) hide show
  1. jac_client/docs/README.md +629 -0
  2. jac_client/docs/advanced-state.md +706 -0
  3. jac_client/docs/imports.md +650 -0
  4. jac_client/docs/lifecycle-hooks.md +554 -0
  5. jac_client/docs/routing.md +530 -0
  6. jac_client/examples/little-x/app.jac +615 -0
  7. jac_client/examples/little-x/package-lock.json +2840 -0
  8. jac_client/examples/little-x/package.json +23 -0
  9. jac_client/examples/little-x/submit-button.jac +8 -0
  10. jac_client/examples/todo-app/README.md +82 -0
  11. jac_client/examples/todo-app/app.jac +683 -0
  12. jac_client/examples/todo-app/package-lock.json +999 -0
  13. jac_client/examples/todo-app/package.json +22 -0
  14. jac_client/plugin/cli.py +328 -0
  15. jac_client/plugin/client.py +41 -0
  16. jac_client/plugin/client_runtime.jac +941 -0
  17. jac_client/plugin/vite_client_bundle.py +470 -0
  18. jac_client/tests/__init__.py +2 -0
  19. jac_client/tests/fixtures/button.jac +6 -0
  20. jac_client/tests/fixtures/client_app.jac +18 -0
  21. jac_client/tests/fixtures/client_app_with_antd.jac +21 -0
  22. jac_client/tests/fixtures/js_import.jac +30 -0
  23. jac_client/tests/fixtures/package-lock.json +329 -0
  24. jac_client/tests/fixtures/package.json +11 -0
  25. jac_client/tests/fixtures/relative_import.jac +13 -0
  26. jac_client/tests/fixtures/test_fragments_spread.jac +44 -0
  27. jac_client/tests/fixtures/utils.js +22 -0
  28. jac_client/tests/test_cl.py +360 -0
  29. jac_client/tests/test_create_jac_app.py +139 -0
  30. jac_client-0.1.0.dist-info/METADATA +126 -0
  31. jac_client-0.1.0.dist-info/RECORD +33 -0
  32. jac_client-0.1.0.dist-info/WHEEL +4 -0
  33. jac_client-0.1.0.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,706 @@
1
+ # Advanced State Management in Jac
2
+
3
+ Learn how to combine multiple `createState()` calls, manage complex state patterns, and build scalable state architectures.
4
+
5
+ ---
6
+
7
+ ## 📚 Table of Contents
8
+
9
+ - [Multiple State Instances](#multiple-state-instances)
10
+ - [State Composition Patterns](#state-composition-patterns)
11
+ - [Derived State](#derived-state)
12
+ - [State Management Patterns](#state-management-patterns)
13
+ - [Best Practices](#best-practices)
14
+
15
+ ---
16
+
17
+ ## Multiple State Instances
18
+
19
+ ### Basic Multiple State Pattern
20
+
21
+ Instead of putting everything in one state object, you can split state into multiple instances:
22
+
23
+ ```jac
24
+ cl {
25
+ # Separate concerns into different state instances
26
+ let [todoState, setTodoState] = createState({
27
+ "items": []
28
+ });
29
+
30
+ let [filterState, setFilterState] = createState({
31
+ "filter": "all"
32
+ });
33
+
34
+ let [uiState, setUiState] = createState({
35
+ "loading": False,
36
+ "error": None
37
+ });
38
+
39
+ def TodoApp() -> any {
40
+ todos = todoState();
41
+ filter = filterState();
42
+ ui = uiState();
43
+
44
+ return <div>
45
+ {ui.loading and <div>Loading...</div>}
46
+ {ui.error and <div>Error: {ui.error}</div>}
47
+ {[TodoItem(item) for item in todos.items]}
48
+ </div>;
49
+ }
50
+ }
51
+ ```
52
+
53
+ **Benefits:**
54
+ - **Separation of Concerns**: Each state manages one aspect
55
+ - **Selective Updates**: Only components using specific state re-render
56
+ - **Easier Testing**: Test each state independently
57
+ - **Better Organization**: Clearer code structure
58
+
59
+ ---
60
+
61
+ ## State Composition Patterns
62
+
63
+ ### Pattern 1: Feature-Based State
64
+
65
+ Organize state by feature or domain:
66
+
67
+ ```jac
68
+ cl {
69
+ # User state
70
+ let [userState, setUserState] = createState({
71
+ "profile": None,
72
+ "isLoggedIn": False
73
+ });
74
+
75
+ # Todo state
76
+ let [todoState, setTodoState] = createState({
77
+ "items": [],
78
+ "selectedId": None
79
+ });
80
+
81
+ # UI state
82
+ let [uiState, setUiState] = createState({
83
+ "theme": "light",
84
+ "sidebarOpen": False,
85
+ "modalOpen": False
86
+ });
87
+
88
+ # Settings state
89
+ let [settingsState, setSettingsState] = createState({
90
+ "notifications": True,
91
+ "language": "en"
92
+ });
93
+
94
+ def App() -> any {
95
+ user = userState();
96
+ todos = todoState();
97
+ ui = uiState();
98
+ settings = settingsState();
99
+
100
+ return <div className={ui.theme}>
101
+ {ui.sidebarOpen and <Sidebar />}
102
+ {todos.items.length > 0 and <TodoList items={todos.items} />}
103
+ </div>;
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Pattern 2: Local vs Global State
109
+
110
+ Use different state instances for different scopes:
111
+
112
+ ```jac
113
+ cl {
114
+ # Global application state
115
+ let [appState, setAppState] = createState({
116
+ "currentUser": None,
117
+ "theme": "light"
118
+ });
119
+
120
+ # Component-specific state (can be defined inside components)
121
+ def TodoForm() -> any {
122
+ # Local component state
123
+ let [formState, setFormState] = createState({
124
+ "text": "",
125
+ "valid": False
126
+ });
127
+
128
+ def validate() -> None {
129
+ text = formState().text;
130
+ setFormState({"valid": len(text.trim()) > 0});
131
+ }
132
+
133
+ return <form>
134
+ <input
135
+ value={formState().text}
136
+ onChange={lambda e: any -> None {
137
+ setFormState({"text": e.target.value});
138
+ validate();
139
+ }}
140
+ />
141
+ </form>;
142
+ }
143
+
144
+ def TodoList() -> any {
145
+ # Local list state
146
+ let [listState, setListState] = createState({
147
+ "sortBy": "date",
148
+ "order": "asc"
149
+ });
150
+
151
+ todos = appState().todos;
152
+ sorted = sortTodos(todos, listState());
153
+
154
+ return <div>
155
+ {[TodoItem(item) for item in sorted]}
156
+ </div>;
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### Pattern 3: State Modules
162
+
163
+ Create reusable state modules:
164
+
165
+ ```jac
166
+ cl {
167
+ # State module: User management
168
+ let [userState, setUserState] = createState({
169
+ "currentUser": None,
170
+ "isLoading": False,
171
+ "error": None
172
+ });
173
+
174
+ async def loadUser() -> None {
175
+ setUserState({"isLoading": True, "error": None});
176
+ try {
177
+ user = await __jacSpawn("get_current_user");
178
+ setUserState({"currentUser": user, "isLoading": False});
179
+ } except Exception as err {
180
+ setUserState({"error": str(err), "isLoading": False});
181
+ }
182
+ }
183
+
184
+ def logout() -> None {
185
+ jacLogout();
186
+ setUserState({"currentUser": None});
187
+ }
188
+
189
+ # State module: Todo management
190
+ let [todoState, setTodoState] = createState({
191
+ "items": [],
192
+ "filter": "all",
193
+ "loading": False
194
+ });
195
+
196
+ async def loadTodos() -> None {
197
+ setTodoState({"loading": True});
198
+ try {
199
+ todos = await __jacSpawn("read_todos");
200
+ setTodoState({"items": todos.reports, "loading": False});
201
+ } except Exception as err {
202
+ setTodoState({"loading": False});
203
+ }
204
+ }
205
+
206
+ async def addTodo(text: str) -> None {
207
+ new_todo = await __jacSpawn("create_todo", {"text": text});
208
+ s = todoState();
209
+ setTodoState({"items": s.items.concat([new_todo])});
210
+ }
211
+ }
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Derived State
217
+
218
+ ### Computed Values from Multiple States
219
+
220
+ Combine multiple states to create derived values:
221
+
222
+ ```jac
223
+ cl {
224
+ let [todoState, setTodoState] = createState({
225
+ "items": []
226
+ });
227
+
228
+ let [filterState, setFilterState] = createState({
229
+ "filter": "all"
230
+ });
231
+
232
+ def getFilteredTodos() -> list {
233
+ todos = todoState();
234
+ filter = filterState();
235
+
236
+ if filter == "active" {
237
+ return [item for item in todos.items if not item.done];
238
+ } elif filter == "completed" {
239
+ return [item for item in todos.items if item.done];
240
+ }
241
+ return todos.items;
242
+ }
243
+
244
+ def getStats() -> dict {
245
+ todos = todoState();
246
+ total = len(todos.items);
247
+ active = len([item for item in todos.items if not item.done]);
248
+ completed = total - active;
249
+
250
+ return {
251
+ "total": total,
252
+ "active": active,
253
+ "completed": completed
254
+ };
255
+ }
256
+
257
+ def TodoApp() -> any {
258
+ filtered = getFilteredTodos();
259
+ stats = getStats();
260
+
261
+ return <div>
262
+ <div>
263
+ Total: {stats.total}, Active: {stats.active}, Completed: {stats.completed}
264
+ </div>
265
+ {[TodoItem(item) for item in filtered]}
266
+ </div>;
267
+ }
268
+ }
269
+ ```
270
+
271
+ ### Reactive Derived State
272
+
273
+ Use `createEffect()` for reactive derived state:
274
+
275
+ ```jac
276
+ cl {
277
+ let [todoState, setTodoState] = createState({
278
+ "items": []
279
+ });
280
+
281
+ let [statsState, setStatsState] = createState({
282
+ "total": 0,
283
+ "active": 0,
284
+ "completed": 0
285
+ });
286
+
287
+ # Automatically update stats when todos change
288
+ createEffect(lambda -> None {
289
+ todos = todoState();
290
+ total = len(todos.items);
291
+ active = len([item for item in todos.items if not item.done]);
292
+ completed = total - active;
293
+
294
+ setStatsState({
295
+ "total": total,
296
+ "active": active,
297
+ "completed": completed
298
+ });
299
+ });
300
+
301
+ def TodoApp() -> any {
302
+ todos = todoState();
303
+ stats = statsState();
304
+
305
+ return <div>
306
+ <StatsDisplay stats={stats} />
307
+ {[TodoItem(item) for item in todos.items]}
308
+ </div>;
309
+ }
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## State Management Patterns
316
+
317
+ ### Pattern 1: State Reducers
318
+
319
+ Create reducer-like functions for complex state updates:
320
+
321
+ ```jac
322
+ cl {
323
+ let [todoState, setTodoState] = createState({
324
+ "items": [],
325
+ "filter": "all",
326
+ "input": ""
327
+ });
328
+
329
+ def todoReducer(action: str, payload: any) -> None {
330
+ s = todoState();
331
+
332
+ if action == "ADD_TODO" {
333
+ newItem = {
334
+ "id": payload.id,
335
+ "text": payload.text,
336
+ "done": False
337
+ };
338
+ setTodoState({"items": s.items.concat([newItem])});
339
+
340
+ } elif action == "TOGGLE_TODO" {
341
+ updated = [item for item in s.items {
342
+ if item.id == payload.id {
343
+ return {"id": item.id, "text": item.text, "done": not item.done};
344
+ }
345
+ return item;
346
+ }];
347
+ setTodoState({"items": updated});
348
+
349
+ } elif action == "REMOVE_TODO" {
350
+ remaining = [item for item in s.items if item.id != payload.id];
351
+ setTodoState({"items": remaining});
352
+
353
+ } elif action == "SET_FILTER" {
354
+ setTodoState({"filter": payload.filter});
355
+
356
+ } elif action == "CLEAR_COMPLETED" {
357
+ remaining = [item for item in s.items if not item.done];
358
+ setTodoState({"items": remaining});
359
+ }
360
+ }
361
+
362
+ async def addTodo(text: str) -> None {
363
+ new_todo = await __jacSpawn("create_todo", {"text": text});
364
+ todoReducer("ADD_TODO", {
365
+ "id": new_todo._jac_id,
366
+ "text": new_todo.text
367
+ });
368
+ }
369
+
370
+ async def toggleTodo(id: str) -> None {
371
+ await __jacSpawn("toggle_todo", {}, id);
372
+ todoReducer("TOGGLE_TODO", {"id": id});
373
+ }
374
+
375
+ def setFilter(filter: str) -> None {
376
+ todoReducer("SET_FILTER", {"filter": filter});
377
+ }
378
+ }
379
+ ```
380
+
381
+ ### Pattern 2: State Selectors
382
+
383
+ Create selector functions to extract specific state slices:
384
+
385
+ ```jac
386
+ cl {
387
+ let [todoState, setTodoState] = createState({
388
+ "items": [],
389
+ "filter": "all",
390
+ "loading": False,
391
+ "error": None
392
+ });
393
+
394
+ # Selectors
395
+ def getTodos() -> list {
396
+ return todoState().items;
397
+ }
398
+
399
+ def getActiveTodos() -> list {
400
+ return [item for item in todoState().items if not item.done];
401
+ }
402
+
403
+ def getCompletedTodos() -> list {
404
+ return [item for item in todoState().items if item.done];
405
+ }
406
+
407
+ def getFilteredTodos() -> list {
408
+ s = todoState();
409
+ if s.filter == "active" {
410
+ return getActiveTodos();
411
+ } elif s.filter == "completed" {
412
+ return getCompletedTodos();
413
+ }
414
+ return getTodos();
415
+ }
416
+
417
+ def isLoading() -> bool {
418
+ return todoState().loading;
419
+ }
420
+
421
+ def getError() -> str | None {
422
+ return todoState().error;
423
+ }
424
+ }
425
+ ```
426
+
427
+ ### Pattern 3: State Actions
428
+
429
+ Create action functions that encapsulate state updates:
430
+
431
+ ```jac
432
+ cl {
433
+ let [todoState, setTodoState] = createState({
434
+ "items": [],
435
+ "filter": "all"
436
+ });
437
+
438
+ # Action creators
439
+ async def createTodoAction(text: str) -> None {
440
+ if not text.trim() {
441
+ return;
442
+ }
443
+
444
+ new_todo = await __jacSpawn("create_todo", {"text": text});
445
+ s = todoState();
446
+ newItem = {
447
+ "id": new_todo._jac_id,
448
+ "text": new_todo.text,
449
+ "done": new_todo.done
450
+ };
451
+ setTodoState({"items": s.items.concat([newItem])});
452
+ }
453
+
454
+ async def toggleTodoAction(id: str) -> None {
455
+ await __jacSpawn("toggle_todo", {}, id);
456
+ s = todoState();
457
+ updated = [item for item in s.items {
458
+ if item.id == id {
459
+ return {"id": item.id, "text": item.text, "done": not item.done};
460
+ }
461
+ return item;
462
+ }];
463
+ setTodoState({"items": updated});
464
+ }
465
+
466
+ def removeTodoAction(id: str) -> None {
467
+ s = todoState();
468
+ remaining = [item for item in s.items if item.id != id];
469
+ setTodoState({"items": remaining});
470
+ }
471
+
472
+ def setFilterAction(filter: str) -> None {
473
+ setTodoState({"filter": filter});
474
+ }
475
+
476
+ def clearCompletedAction() -> None {
477
+ s = todoState();
478
+ remaining = [item for item in s.items if not item.done];
479
+ setTodoState({"items": remaining});
480
+ }
481
+ }
482
+ ```
483
+
484
+ ---
485
+
486
+ ## Complete Example: Multi-State Todo App
487
+
488
+ Here's a complete example combining multiple state patterns:
489
+
490
+ ```jac
491
+ cl {
492
+ # User state
493
+ let [userState, setUserState] = createState({
494
+ "profile": None,
495
+ "isLoading": False
496
+ });
497
+
498
+ # Todo state
499
+ let [todoState, setTodoState] = createState({
500
+ "items": [],
501
+ "loading": False,
502
+ "error": None
503
+ });
504
+
505
+ # Filter state
506
+ let [filterState, setFilterState] = createState({
507
+ "filter": "all"
508
+ });
509
+
510
+ # UI state
511
+ let [uiState, setUiState] = createState({
512
+ "sidebarOpen": False,
513
+ "modalOpen": False
514
+ });
515
+
516
+ # Derived state functions
517
+ def getFilteredTodos() -> list {
518
+ todos = todoState();
519
+ filter = filterState();
520
+
521
+ if filter == "active" {
522
+ return [item for item in todos.items if not item.done];
523
+ } elif filter == "completed" {
524
+ return [item for item in todos.items if item.done];
525
+ }
526
+ return todos.items;
527
+ }
528
+
529
+ def getStats() -> dict {
530
+ todos = todoState();
531
+ total = len(todos.items);
532
+ active = len([item for item in todos.items if not item.done]);
533
+ completed = total - active;
534
+
535
+ return {"total": total, "active": active, "completed": completed};
536
+ }
537
+
538
+ # Actions
539
+ async def loadTodos() -> None {
540
+ setTodoState({"loading": True, "error": None});
541
+ try {
542
+ todos = await __jacSpawn("read_todos");
543
+ items = [{
544
+ "id": todo._jac_id,
545
+ "text": todo.text,
546
+ "done": todo.done
547
+ } for todo in todos.reports];
548
+ setTodoState({"items": items, "loading": False});
549
+ } except Exception as err {
550
+ setTodoState({"error": str(err), "loading": False});
551
+ }
552
+ }
553
+
554
+ async def addTodo(text: str) -> None {
555
+ new_todo = await __jacSpawn("create_todo", {"text": text});
556
+ s = todoState();
557
+ newItem = {
558
+ "id": new_todo._jac_id,
559
+ "text": new_todo.text,
560
+ "done": new_todo.done
561
+ };
562
+ setTodoState({"items": s.items.concat([newItem])});
563
+ }
564
+
565
+ def setFilter(filter: str) -> None {
566
+ setFilterState({"filter": filter});
567
+ }
568
+
569
+ def toggleSidebar() -> None {
570
+ s = uiState();
571
+ setUiState({"sidebarOpen": not s.sidebarOpen});
572
+ }
573
+
574
+ # Components
575
+ def TodoApp() -> any {
576
+ onMount(lambda -> None {
577
+ loadTodos();
578
+ });
579
+
580
+ todos = todoState();
581
+ filter = filterState();
582
+ ui = uiState();
583
+
584
+ if todos.loading {
585
+ return <div>Loading...</div>;
586
+ }
587
+
588
+ if todos.error {
589
+ return <div>Error: {todos.error}</div>;
590
+ }
591
+
592
+ filtered = getFilteredTodos();
593
+ stats = getStats();
594
+
595
+ return <div>
596
+ <Header stats={stats} onToggleSidebar={toggleSidebar} />
597
+ {ui.sidebarOpen and <Sidebar filter={filter} onSetFilter={setFilter} />}
598
+ <TodoList items={filtered} />
599
+ </div>;
600
+ }
601
+ }
602
+ ```
603
+
604
+ ---
605
+
606
+ ## Best Practices
607
+
608
+ ### 1. Separate Concerns
609
+
610
+ ```jac
611
+ # ✅ Good: Separate state by concern
612
+ let [userState, setUserState] = createState({"profile": None});
613
+ let [todoState, setTodoState] = createState({"items": []});
614
+ let [uiState, setUiState] = createState({"sidebarOpen": False});
615
+
616
+ # ❌ Avoid: Mixing unrelated state
617
+ let [appState, setAppState] = createState({
618
+ "user": None,
619
+ "todos": [],
620
+ "sidebarOpen": False,
621
+ "theme": "light"
622
+ });
623
+ ```
624
+
625
+ ### 2. Keep State Flat
626
+
627
+ ```jac
628
+ # ✅ Good: Flat state structure
629
+ let [todoState, setTodoState] = createState({
630
+ "items": [],
631
+ "loading": False
632
+ });
633
+
634
+ # ❌ Avoid: Deeply nested state
635
+ let [appState, setAppState] = createState({
636
+ "data": {
637
+ "todos": {
638
+ "items": [],
639
+ "meta": {
640
+ "loading": False
641
+ }
642
+ }
643
+ }
644
+ });
645
+ ```
646
+
647
+ ### 3. Use Selectors for Computed Values
648
+
649
+ ```jac
650
+ # ✅ Good: Computed values via functions
651
+ def getActiveTodos() -> list {
652
+ return [item for item in todoState().items if not item.done];
653
+ }
654
+
655
+ # ❌ Avoid: Storing computed values in state
656
+ let [todoState, setTodoState] = createState({
657
+ "items": [],
658
+ "activeTodos": [] # Computed, shouldn't be in state
659
+ });
660
+ ```
661
+
662
+ ### 4. Encapsulate State Updates
663
+
664
+ ```jac
665
+ # ✅ Good: Action functions
666
+ async def addTodo(text: str) -> None {
667
+ new_todo = await __jacSpawn("create_todo", {"text": text});
668
+ # Update state
669
+ setTodoState(/* ... */);
670
+ }
671
+
672
+ # ❌ Avoid: Direct state updates everywhere
673
+ def Component() -> any {
674
+ # Don't call setTodoState directly here
675
+ # Use action functions instead
676
+ }
677
+ ```
678
+
679
+ ### 5. Handle Loading and Error States
680
+
681
+ ```jac
682
+ # ✅ Good: Include loading/error in state
683
+ let [todoState, setTodoState] = createState({
684
+ "items": [],
685
+ "loading": False,
686
+ "error": None
687
+ });
688
+
689
+ # ❌ Avoid: Missing error handling
690
+ let [todoState, setTodoState] = createState({
691
+ "items": []
692
+ });
693
+ ```
694
+
695
+ ---
696
+
697
+ ## Summary
698
+
699
+ - **Multiple States**: Split state by concern for better organization
700
+ - **State Composition**: Combine multiple states for complex applications
701
+ - **Derived State**: Compute values from multiple states using functions
702
+ - **State Patterns**: Use reducers, selectors, and actions for complex state
703
+ - **Best Practices**: Keep state flat, separate concerns, handle errors
704
+
705
+ Advanced state management helps you build scalable, maintainable applications! 🚀
706
+