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,629 @@
1
+ # Todo App - Beginner's Guide to Building with Jac
2
+
3
+ Welcome to the Todo App example! This guide will walk you through building a full-stack web application with Jac, from setup to deployment. Jac simplifies web development by eliminating the need for separate frontend and backend technologies, HTTP clients, and complex build configurations.
4
+
5
+ ---
6
+
7
+ ## 📦 1. Creating the Application
8
+
9
+ ### Installation
10
+
11
+ First, install the Jac client package:
12
+
13
+ ```bash
14
+ pip install jac-client
15
+ ```
16
+
17
+ ### Create a New Jac App
18
+
19
+ Use the `jac create_jac_app` command to scaffold a new application: (* we can name our app however we want, here we are using `todo-app)
20
+
21
+ ```bash
22
+ jac create_jac_app todo-app
23
+ ```
24
+
25
+ This command will:
26
+ - Create a new directory with your project name
27
+ - Set up the basic project structure
28
+ - Initialize npm and install Vite (for development server)
29
+ - Create a starter `app.jac` file with a sample component
30
+
31
+ **What gets created:**
32
+ ```
33
+ my-app/
34
+ ├── app.jac # Your main application file
35
+ ├── package.json # Node.js dependencies
36
+ └── node_modules/ # Dependencies (after npm install)
37
+ ```
38
+
39
+ ### Running Your App
40
+
41
+ Navigate to your project directory and start the development server:
42
+
43
+ ```bash
44
+ cd todo-app
45
+ jac serve app.jac
46
+ ```
47
+
48
+ This starts both:
49
+ - **Backend server**: Handles your Jac graph operations and walkers
50
+ - **Frontend development server**: Serves your React components
51
+
52
+ You can access your app at `http://localhost:8000`
53
+
54
+ ---
55
+
56
+ ## 🚪 2. Entry Point of the App
57
+
58
+ Every Jac client application needs an entry point function. This is where your app starts rendering.
59
+
60
+ ### The `jac_app()` Function
61
+
62
+ At the bottom of your `app.jac` file, you'll find the entry point:
63
+
64
+ ```jac
65
+ cl {
66
+ def App() -> any {
67
+ # Your main app component with routing
68
+ return <div>Hello World</div>;
69
+ }
70
+
71
+ def jac_app() -> any {
72
+ return App();
73
+ }
74
+ }
75
+ ```
76
+
77
+ **Key Points:**
78
+ - `jac_app()` is the **required entry point** that Jac looks for
79
+ - It should return your root component (usually called `App`)
80
+ - The `cl` (client) block indicates this code runs in the browser
81
+ - This function gets called automatically when the page loads
82
+
83
+ **Example from Todo App:**
84
+ ```jac
85
+ def App() -> any {
86
+ login_route = {"path": "/login", "component": lambda -> any { return LoginForm(); }, "guard": None};
87
+ signup_route = {"path": "/signup", "component": lambda -> any { return SignupForm(); }, "guard": None};
88
+ todos_route = {"path": "/todos", "component": lambda -> any { return TodoApp(); }, "guard": jacIsLoggedIn};
89
+
90
+ routes = [login_route, signup_route, todos_route];
91
+ router = initRouter(routes, "/login");
92
+
93
+ currentPath = router.path();
94
+ return <div>
95
+ {Nav(currentPath)}
96
+ {router.render()}
97
+ </div>;
98
+ }
99
+
100
+ def jac_app() -> any {
101
+ return App();
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ## 🧩 3. Creating Components
108
+
109
+ Components in Jac are functions that return JSX (JavaScript XML). They're similar to React components but written in pure Jac syntax.
110
+
111
+ ### Basic Component Structure
112
+
113
+ ```jac
114
+ cl {
115
+ def MyComponent() -> any {
116
+ return <div>
117
+ <h1>Hello from Jac!</h1>
118
+ </div>;
119
+ }
120
+ }
121
+ ```
122
+
123
+ ### Component with Props
124
+
125
+ Components can accept parameters (props):
126
+
127
+ ```jac
128
+ def TodoItem(item: dict) -> any {
129
+ return <li key={item.id}>
130
+ <span>{item.text}</span>
131
+ <button onClick={lambda -> None { removeTodo(item.id); }}>
132
+ Remove
133
+ </button>
134
+ </li>;
135
+ }
136
+ ```
137
+
138
+ **Component Features:**
139
+ - **JSX Syntax**: Write HTML-like syntax directly in Jac
140
+ - **Inline Styles**: Use JavaScript objects for styling
141
+ - **Event Handlers**: Attach functions to user interactions
142
+ - **Reusability**: Components can call other components
143
+
144
+ ### Example: TodoItem Component
145
+
146
+ ```jac
147
+ def TodoItem(item: dict) -> any {
148
+ return <li key={item.id} style={{
149
+ "display": "flex",
150
+ "gap": "12px",
151
+ "alignItems": "center",
152
+ "padding": "12px",
153
+ "background": "#FFFFFF",
154
+ "borderRadius": "10px"
155
+ }}>
156
+ <input
157
+ type="checkbox"
158
+ checked={item.done}
159
+ onChange={lambda -> None { toggleTodo(item.id); }}
160
+ />
161
+ <span style={{
162
+ "textDecoration": ("line-through" if item.done else "none")
163
+ }}>
164
+ {item.text}
165
+ </span>
166
+ <button onClick={lambda -> None { removeTodo(item.id); }}>
167
+ Remove
168
+ </button>
169
+ </li>;
170
+ }
171
+ ```
172
+
173
+ **Breaking it down:**
174
+ - `item: dict` - Component receives a dictionary (todo item) as a prop
175
+ - `style={{...}}` - Inline styles using JavaScript objects
176
+ - `checked={item.done}` - Dynamic attribute binding
177
+ - `onChange={lambda -> None {...}}` - Event handler using lambda
178
+ - `{item.text}` - Interpolation of JavaScript expressions in JSX
179
+
180
+ ---
181
+
182
+ ## 🗄️ 4. Adding State
183
+
184
+ State management in Jac uses `createState()`, which provides reactive state similar to React hooks but simpler.
185
+
186
+ ### Understanding `createState()`
187
+
188
+ ```jac
189
+ let [todoState, setTodoState] = createState({
190
+ "items": [],
191
+ "filter": "all",
192
+ "input": ""
193
+ });
194
+ ```
195
+
196
+ **How it works:**
197
+ - `createState()` returns a tuple: `[getter, setter]`
198
+ - **Getter** (`todoState`): A function that returns the current state value
199
+ - **Setter** (`setTodoState`): A function that updates the state and triggers re-renders
200
+
201
+ ### Reading State
202
+
203
+ Always call the getter as a function:
204
+
205
+ ```jac
206
+ def TodoApp() -> any {
207
+ s = todoState(); # Call getter function
208
+
209
+ return <div>
210
+ <p>Total todos: {s.items.length}</p>
211
+ <p>Current filter: {s.filter}</p>
212
+ </div>;
213
+ }
214
+ ```
215
+
216
+ ### Updating State
217
+
218
+ The setter merges updates with existing state:
219
+
220
+ ```jac
221
+ # Update single property
222
+ setTodoState({"filter": "active"});
223
+
224
+ # Update multiple properties
225
+ setTodoState({
226
+ "filter": "completed",
227
+ "input": "New todo text"
228
+ });
229
+
230
+ # Update arrays (create new array)
231
+ s = todoState();
232
+ setTodoState({"items": s.items.concat([newItem])});
233
+ ```
234
+
235
+ **Important:** The setter performs a **shallow merge**, meaning:
236
+ - Existing properties are preserved
237
+ - Only specified properties are updated
238
+ - Arrays and objects are replaced, not merged deeply
239
+
240
+ ### Complete State Example
241
+
242
+ ```jac
243
+ cl {
244
+ # Initialize state
245
+ let [todoState, setTodoState] = createState({
246
+ "items": [],
247
+ "filter": "all",
248
+ "input": ""
249
+ });
250
+
251
+ # Read state in component
252
+ def TodoApp() -> any {
253
+ s = todoState();
254
+ itemsArr = s.items;
255
+
256
+ return <div>
257
+ <input
258
+ value={s.input}
259
+ onChange={lambda e: any -> None {
260
+ setTodoState({"input": e.target.value});
261
+ }}
262
+ />
263
+ <div>
264
+ {[TodoItem(item) for item in itemsArr]}
265
+ </div>
266
+ </div>;
267
+ }
268
+
269
+ # Update state in event handler
270
+ async def onAddTodo(e: any) -> None {
271
+ e.preventDefault();
272
+ text = document.getElementById("todo-input").value.trim();
273
+
274
+ if not text { return; }
275
+
276
+ # Create todo on backend
277
+ new_todo = await __jacSpawn("create_todo", {"text": text});
278
+
279
+ # Update frontend state
280
+ s = todoState();
281
+ newItem = {
282
+ "id": new_todo._jac_id,
283
+ "text": new_todo.text,
284
+ "done": new_todo.done
285
+ };
286
+ setTodoState({"items": s.items.concat([newItem])});
287
+ }
288
+ }
289
+ ```
290
+
291
+ ---
292
+
293
+ ## 🎯 5. Event Handling
294
+
295
+ Event handling in Jac works just like React, but with Jac's lambda syntax.
296
+
297
+ ### Basic Event Handlers
298
+
299
+ ```jac
300
+ def Button() -> any {
301
+ return <button onClick={lambda -> None {
302
+ console.log("Button clicked!");
303
+ }}>
304
+ Click Me
305
+ </button>;
306
+ }
307
+ ```
308
+
309
+ ### Event Handlers with Event Object
310
+
311
+ ```jac
312
+ def InputField() -> any {
313
+ return <input
314
+ type="text"
315
+ onChange={lambda e: any -> None {
316
+ console.log("Input value:", e.target.value);
317
+ setTodoState({"input": e.target.value});
318
+ }}
319
+ />;
320
+ }
321
+ ```
322
+
323
+ ### Form Submission
324
+
325
+ ```jac
326
+ def TodoForm() -> any {
327
+ return <form onSubmit={onAddTodo}>
328
+ <input id="todo-input" type="text" />
329
+ <button type="submit">Add Todo</button>
330
+ </form>;
331
+ }
332
+
333
+ async def onAddTodo(e: any) -> None {
334
+ e.preventDefault(); # Prevent page refresh
335
+ inputEl = document.getElementById("todo-input");
336
+ text = inputEl.value.trim();
337
+
338
+ if not text { return; }
339
+
340
+ # Handle form submission
341
+ new_todo = await __jacSpawn("create_todo", {"text": text});
342
+ # ... update state
343
+ }
344
+ ```
345
+
346
+ ### Common Event Types
347
+
348
+ | Event | Syntax | Use Case |
349
+ |-------|--------|----------|
350
+ | `onClick` | `onClick={lambda -> None {...}}` | Button clicks |
351
+ | `onChange` | `onChange={lambda e: any -> None {...}}` | Input changes |
352
+ | `onSubmit` | `onSubmit={lambda e: any -> None {...}}` | Form submission |
353
+ | `onKeyPress` | `onKeyPress={lambda e: any -> None {...}}` | Keyboard input |
354
+
355
+ ### Advanced: Conditional Event Handlers
356
+
357
+ ```jac
358
+ def FilterButton(filterType: str) -> any {
359
+ s = todoState();
360
+ isActive = s.filter == filterType;
361
+
362
+ return <button
363
+ onClick={lambda -> None {
364
+ setFilter(filterType);
365
+ }}
366
+ style={{
367
+ "background": ("#7C3AED" if isActive else "#FFFFFF"),
368
+ "color": ("#FFFFFF" if isActive else "#7C3AED")
369
+ }}
370
+ >
371
+ {filterType}
372
+ </button>;
373
+ }
374
+ ```
375
+
376
+ ---
377
+
378
+ ## ✨ 6. Magic: No More Axios/Fetch!
379
+
380
+ One of Jac's most powerful features is **seamless backend communication** without writing HTTP requests, fetch calls, or axios code.
381
+
382
+ ### The `__jacSpawn()` Function
383
+
384
+ Instead of writing:
385
+ ```javascript
386
+ // Traditional approach
387
+ const response = await fetch('/api/todos', {
388
+ method: 'POST',
389
+ headers: { 'Content-Type': 'application/json' },
390
+ body: JSON.stringify({ text: 'New todo' })
391
+ });
392
+ const data = await response.json();
393
+ ```
394
+
395
+ You simply write:
396
+ ```jac
397
+ new_todo = await __jacSpawn("create_todo", {"text": "New todo"});
398
+ ```
399
+
400
+ ### How `__jacSpawn()` Works
401
+
402
+ ```jac
403
+ # Call a walker on the backend
404
+ result = await __jacSpawn(
405
+ walker_name, # Name of the walker to execute
406
+ fields, # Dictionary of parameters to pass
407
+ node_id # Optional: specific node ID (defaults to "root")
408
+ );
409
+ ```
410
+
411
+ **Example from Todo App:**
412
+ ```jac
413
+ # Create a new todo
414
+ new_todo = await __jacSpawn("create_todo", {"text": text});
415
+
416
+ # Toggle todo status
417
+ toggled_todo = await __jacSpawn("toggle_todo", {}, todo_id);
418
+
419
+ # Read all todos
420
+ todos = await __jacSpawn("read_todos");
421
+ ```
422
+
423
+ ### Backend Walkers
424
+
425
+ Walkers are defined in the same `app.jac` file (outside the `cl` block):
426
+
427
+ ```jac
428
+ # Node definition (data model)
429
+ node Todo {
430
+ has text: str;
431
+ has done: bool = False;
432
+ }
433
+
434
+ # Walker: Create a new todo
435
+ walker create_todo {
436
+ can create with `root entry {
437
+ new_todo = here ++> Todo(text="Example Todo");
438
+ report new_todo;
439
+ }
440
+ }
441
+
442
+ # Walker: Toggle todo status
443
+ walker toggle_todo {
444
+ can toggle with Todo entry {
445
+ here.done = not here.done;
446
+ report here;
447
+ }
448
+ }
449
+
450
+ # Walker: Read all todos
451
+ walker read_todos {
452
+ can read with `root entry {
453
+ visit [-->(`?Todo)];
454
+ }
455
+
456
+ can report_todos with exit {
457
+ report here;
458
+ }
459
+ }
460
+ ```
461
+
462
+ ### Complete Example: Creating a Todo
463
+
464
+ **Frontend (in `cl` block):**
465
+ ```jac
466
+ async def onAddTodo(e: any) -> None {
467
+ e.preventDefault();
468
+ text = document.getElementById("todo-input").value.trim();
469
+ if not text { return; }
470
+
471
+ # Call backend walker - no fetch/axios needed!
472
+ new_todo = await __jacSpawn("create_todo", {"text": text});
473
+
474
+ # Update frontend state
475
+ s = todoState();
476
+ newItem = {
477
+ "id": new_todo._jac_id,
478
+ "text": new_todo.text,
479
+ "done": new_todo.done
480
+ };
481
+ setTodoState({"items": s.items.concat([newItem])});
482
+ }
483
+ ```
484
+
485
+ **Backend (outside `cl` block):**
486
+ ```jac
487
+ walker create_todo {
488
+ can create with `root entry {
489
+ # 'text' comes from the fields dictionary passed to __jacSpawn
490
+ text = context.text;
491
+ new_todo = here ++> Todo(text=text);
492
+ report new_todo;
493
+ }
494
+ }
495
+ ```
496
+
497
+ ### Benefits of `__jacSpawn()`
498
+
499
+ ✅ **No HTTP Configuration**: No need to set up API endpoints, CORS, or request/response formats
500
+ ✅ **Type Safety**: Jac handles serialization automatically
501
+ ✅ **Authentication**: Built-in token management via `jacLogin()` / `jacLogout()`
502
+ ✅ **Error Handling**: Exceptions are properly propagated
503
+ ✅ **Graph Operations**: Direct access to graph-based data operations
504
+ ✅ **Less Code**: Eliminates boilerplate HTTP client code
505
+
506
+ ### Authentication Helpers
507
+
508
+ Jac also provides built-in auth functions:
509
+
510
+ ```jac
511
+ # Sign up
512
+ result = await jacSignup(username, password);
513
+ if result["success"] {
514
+ navigate("/todos");
515
+ }
516
+
517
+ # Log in
518
+ success = await jacLogin(username, password);
519
+ if success {
520
+ navigate("/todos");
521
+ }
522
+
523
+ # Log out
524
+ jacLogout();
525
+ navigate("/login");
526
+
527
+ # Check if logged in
528
+ if jacIsLoggedIn() {
529
+ return <div>Welcome!</div>;
530
+ }
531
+ ```
532
+
533
+ ---
534
+
535
+ ## 🎨 Complete Example: Todo App Structure
536
+
537
+ Here's how all the pieces fit together:
538
+
539
+ ```jac
540
+ # ============================================================================
541
+ # BACKEND: Data Models and Walkers
542
+ # ============================================================================
543
+ node Todo {
544
+ has text: str;
545
+ has done: bool = False;
546
+ }
547
+
548
+ walker create_todo {
549
+ can create with `root entry {
550
+ new_todo = here ++> Todo(text=context.text);
551
+ report new_todo;
552
+ }
553
+ }
554
+
555
+ # ============================================================================
556
+ # FRONTEND: Components, State, and Events
557
+ # ============================================================================
558
+ cl {
559
+ # State
560
+ let [todoState, setTodoState] = createState({
561
+ "items": [],
562
+ "filter": "all"
563
+ });
564
+
565
+ # Component
566
+ def TodoApp() -> any {
567
+ s = todoState();
568
+ return <div>
569
+ <h2>My Todos</h2>
570
+ <form onSubmit={onAddTodo}>
571
+ <input id="todo-input" type="text" />
572
+ <button type="submit">Add Todo</button>
573
+ </form>
574
+ {[TodoItem(item) for item in s.items]}
575
+ </div>;
576
+ }
577
+
578
+ # Event Handler
579
+ async def onAddTodo(e: any) -> None {
580
+ e.preventDefault();
581
+ text = document.getElementById("todo-input").value;
582
+ new_todo = await __jacSpawn("create_todo", {"text": text});
583
+ # Update state...
584
+ }
585
+
586
+ # Entry Point
587
+ def jac_app() -> any {
588
+ return TodoApp();
589
+ }
590
+ }
591
+ ```
592
+
593
+ ---
594
+
595
+ ## 🚀 Running the Todo App
596
+
597
+ To run this example:
598
+
599
+ ```bash
600
+ # From the todo-app directory
601
+ jac serve app.jac
602
+ ```
603
+
604
+ Then visit `http://localhost:8000` in your browser.
605
+
606
+ ---
607
+
608
+ ## 📚 Next Steps
609
+
610
+ Ready to dive deeper? Explore these advanced topics:
611
+
612
+ - **[Routing](routing.md)**: Learn about `initRouter()` for multi-page applications
613
+ - **[Lifecycle Hooks](lifecycle-hooks.md)**: Use `onMount()` for initialization logic
614
+ - **[Advanced State](advanced-state.md)**: Combine multiple `createState()` calls for complex apps
615
+ - **[Imports](imports.md)**: Import third-party libraries, other Jac files, and JavaScript modules
616
+ - **[Learn JAC](https://www.jac-lang.org)**: Explore Jac's graph-based data modeling
617
+
618
+ ---
619
+
620
+ ## 💡 Key Takeaways
621
+
622
+ 1. **Single Language**: Write frontend and backend in Jac
623
+ 2. **No HTTP Client**: Use `__jacSpawn()` instead of fetch/axios
624
+ 3. **Reactive State**: `createState()` manages UI updates automatically
625
+ 4. **Component-Based**: Build reusable UI components with JSX
626
+ 5. **Type Safety**: Jac provides type checking across frontend and backend
627
+ 6. **Graph Database**: Built-in graph data model eliminates need for SQL/NoSQL
628
+
629
+ Happy coding with Jac! 🎉