sigpro 1.0.14 → 1.2.40

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 (112) hide show
  1. package/Readme.md +164 -1008
  2. package/dist/sigpro.db.js +1 -0
  3. package/dist/sigpro.editor.js +1 -0
  4. package/dist/sigpro.grid.js +78 -0
  5. package/dist/sigpro.js +1 -0
  6. package/dist/sigpro.locale.js +1 -0
  7. package/dist/sigpro.router.js +1 -0
  8. package/dist/sigpro.ui.css +2 -0
  9. package/dist/sigpro.ui.js +1 -0
  10. package/dist/sigpro.vite.js +4 -0
  11. package/package.json +94 -13
  12. package/sigpro.d.ts +395 -0
  13. package/sigpro.ui.d.ts +308 -0
  14. package/.github/workflows/publish.yml +0 -25
  15. package/bun.lock +0 -385
  16. package/docs/404.html +0 -22
  17. package/docs/api/components.html +0 -595
  18. package/docs/api/effects.html +0 -787
  19. package/docs/api/fetch.html +0 -873
  20. package/docs/api/pages.html +0 -405
  21. package/docs/api/quick.html +0 -217
  22. package/docs/api/routing.html +0 -628
  23. package/docs/api/signals.html +0 -683
  24. package/docs/api/storage.html +0 -820
  25. package/docs/assets/api_components.md.BlFwj17l.js +0 -571
  26. package/docs/assets/api_components.md.BlFwj17l.lean.js +0 -1
  27. package/docs/assets/api_effects.md.Br_yStBS.js +0 -763
  28. package/docs/assets/api_effects.md.Br_yStBS.lean.js +0 -1
  29. package/docs/assets/api_fetch.md.DQLBJSoq.js +0 -849
  30. package/docs/assets/api_fetch.md.DQLBJSoq.lean.js +0 -1
  31. package/docs/assets/api_pages.md.BP19nHXw.js +0 -381
  32. package/docs/assets/api_pages.md.BP19nHXw.lean.js +0 -1
  33. package/docs/assets/api_quick.md.BDS3ttnt.js +0 -193
  34. package/docs/assets/api_quick.md.BDS3ttnt.lean.js +0 -1
  35. package/docs/assets/api_routing.md.7SNAZXtp.js +0 -604
  36. package/docs/assets/api_routing.md.7SNAZXtp.lean.js +0 -1
  37. package/docs/assets/api_signals.md.CrW68-BA.js +0 -659
  38. package/docs/assets/api_signals.md.CrW68-BA.lean.js +0 -1
  39. package/docs/assets/api_storage.md.COEWBXHk.js +0 -796
  40. package/docs/assets/api_storage.md.COEWBXHk.lean.js +0 -1
  41. package/docs/assets/app.DtmzNmNl.js +0 -1
  42. package/docs/assets/chunks/framework.C8AWLET_.js +0 -19
  43. package/docs/assets/chunks/theme.yfWKMLQM.js +0 -1
  44. package/docs/assets/guide_getting-started.md.BeQpK3vd.js +0 -172
  45. package/docs/assets/guide_getting-started.md.BeQpK3vd.lean.js +0 -1
  46. package/docs/assets/guide_why.md.DXchYMN-.js +0 -23
  47. package/docs/assets/guide_why.md.DXchYMN-.lean.js +0 -1
  48. package/docs/assets/index.md.uvMJmU4o.js +0 -1
  49. package/docs/assets/index.md.uvMJmU4o.lean.js +0 -1
  50. package/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  51. package/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  52. package/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  53. package/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  54. package/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  55. package/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  56. package/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  57. package/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  58. package/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  59. package/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  60. package/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  61. package/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  62. package/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  63. package/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  64. package/docs/assets/style.DJRheFKp.css +0 -1
  65. package/docs/assets/ui_intro.md.gZ21GFqo.js +0 -1
  66. package/docs/assets/ui_intro.md.gZ21GFqo.lean.js +0 -1
  67. package/docs/assets/vite_plugin.md.gDWEi8f0.js +0 -225
  68. package/docs/assets/vite_plugin.md.gDWEi8f0.lean.js +0 -1
  69. package/docs/guide/getting-started.html +0 -196
  70. package/docs/guide/why.html +0 -47
  71. package/docs/hashmap.json +0 -1
  72. package/docs/index.html +0 -25
  73. package/docs/logo.svg +0 -118
  74. package/docs/ui/intro.html +0 -25
  75. package/docs/vite/plugin.html +0 -249
  76. package/docs/vp-icons.css +0 -1
  77. package/index.js +0 -3
  78. package/packages/docs/.vitepress/cache/deps/@theme_index.js +0 -275
  79. package/packages/docs/.vitepress/cache/deps/@theme_index.js.map +0 -7
  80. package/packages/docs/.vitepress/cache/deps/_metadata.json +0 -40
  81. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js +0 -12951
  82. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js.map +0 -7
  83. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js +0 -9719
  84. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js.map +0 -7
  85. package/packages/docs/.vitepress/cache/deps/package.json +0 -3
  86. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4505
  87. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  88. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -583
  89. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  90. package/packages/docs/.vitepress/cache/deps/vue.js +0 -347
  91. package/packages/docs/.vitepress/cache/deps/vue.js.map +0 -7
  92. package/packages/docs/.vitepress/config.js +0 -68
  93. package/packages/docs/api/components.md +0 -760
  94. package/packages/docs/api/effects.md +0 -1039
  95. package/packages/docs/api/fetch.md +0 -998
  96. package/packages/docs/api/pages.md +0 -497
  97. package/packages/docs/api/quick.md +0 -436
  98. package/packages/docs/api/routing.md +0 -784
  99. package/packages/docs/api/signals.md +0 -899
  100. package/packages/docs/api/storage.md +0 -952
  101. package/packages/docs/guide/getting-started.md +0 -308
  102. package/packages/docs/guide/why.md +0 -135
  103. package/packages/docs/index.md +0 -84
  104. package/packages/docs/logo.svg +0 -118
  105. package/packages/docs/public/logo.svg +0 -118
  106. package/packages/docs/ui/intro.md +0 -16
  107. package/packages/docs/vite/plugin.md +0 -423
  108. package/packages/sigpro/plugin.js +0 -91
  109. package/packages/sigpro/plugin.min.js +0 -1
  110. package/packages/sigpro/sigpro.js +0 -631
  111. package/packages/sigpro/sigpro.min.js +0 -1
  112. package/vite.config.js +0 -24
@@ -1,998 +0,0 @@
1
- # Fetch API 🌐
2
-
3
- SigPro provides a simple, lightweight wrapper around the native Fetch API that integrates seamlessly with signals for loading state management. It's designed for common use cases with sensible defaults.
4
-
5
- ## Core Concepts
6
-
7
- ### What is `$.fetch`?
8
-
9
- A ultra-simple fetch wrapper that:
10
- - **Automatically handles JSON** serialization and parsing
11
- - **Integrates with signals** for loading state
12
- - **Returns `null` on error** (no try/catch needed for basic usage)
13
- - **Works great with effects** for reactive data fetching
14
-
15
- ## `$.fetch(url, data, [loading])`
16
-
17
- Makes a POST request with JSON data and optional loading signal.
18
-
19
- ```javascript
20
- import { $ } from 'sigpro';
21
-
22
- const loading = $(false);
23
-
24
- async function loadUser() {
25
- const user = await $.fetch('/api/user', { id: 123 }, loading);
26
- if (user) {
27
- console.log('User loaded:', user);
28
- }
29
- }
30
- ```
31
-
32
- ## 📋 API Reference
33
-
34
- ### Parameters
35
-
36
- | Parameter | Type | Description |
37
- |-----------|------|-------------|
38
- | `url` | `string` | Endpoint URL |
39
- | `data` | `Object` | Data to send (automatically JSON.stringify'd) |
40
- | `loading` | `Function` (optional) | Signal function to track loading state |
41
-
42
- ### Returns
43
-
44
- | Return | Description |
45
- |--------|-------------|
46
- | `Promise<Object\|null>` | Parsed JSON response or `null` on error |
47
-
48
- ## 🎯 Basic Examples
49
-
50
- ### Simple Data Fetching
51
-
52
- ```javascript
53
- import { $ } from 'sigpro';
54
-
55
- const userData = $(null);
56
-
57
- async function fetchUser(id) {
58
- const data = await $.fetch('/api/user', { id });
59
- if (data) {
60
- userData(data);
61
- }
62
- }
63
-
64
- fetchUser(123);
65
- ```
66
-
67
- ### With Loading State
68
-
69
- ```javascript
70
- import { $, html } from 'sigpro';
71
-
72
- const user = $(null);
73
- const loading = $(false);
74
-
75
- async function loadUser(id) {
76
- const data = await $.fetch('/api/user', { id }, loading);
77
- if (data) user(data);
78
- }
79
-
80
- // In your template
81
- html`
82
- <div>
83
- ${() => loading() ? html`
84
- <div class="spinner">Loading...</div>
85
- ` : user() ? html`
86
- <div>
87
- <h2>${user().name}</h2>
88
- <p>Email: ${user().email}</p>
89
- </div>
90
- ` : html`
91
- <p>No user found</p>
92
- `}
93
- </div>
94
- `;
95
- ```
96
-
97
- ### In an Effect
98
-
99
- ```javascript
100
- import { $ } from 'sigpro';
101
-
102
- const userId = $(1);
103
- const user = $(null);
104
- const loading = $(false);
105
-
106
- $.effect(() => {
107
- const id = userId();
108
- if (id) {
109
- $.fetch(`/api/users/${id}`, null, loading).then(data => {
110
- if (data) user(data);
111
- });
112
- }
113
- });
114
-
115
- userId(2); // Automatically fetches new user
116
- ```
117
-
118
- ## 🚀 Advanced Examples
119
-
120
- ### User Profile with Loading States
121
-
122
- ```javascript
123
- import { $, html } from 'sigpro';
124
-
125
- const Profile = () => {
126
- const userId = $(1);
127
- const user = $(null);
128
- const loading = $(false);
129
- const error = $(null);
130
-
131
- const fetchUser = async (id) => {
132
- error(null);
133
- const data = await $.fetch('/api/user', { id }, loading);
134
- if (data) {
135
- user(data);
136
- } else {
137
- error('Failed to load user');
138
- }
139
- };
140
-
141
- // Fetch when userId changes
142
- $.effect(() => {
143
- fetchUser(userId());
144
- });
145
-
146
- return html`
147
- <div class="profile">
148
- <div class="user-selector">
149
- <button @click=${() => userId(1)}>User 1</button>
150
- <button @click=${() => userId(2)}>User 2</button>
151
- <button @click=${() => userId(3)}>User 3</button>
152
- </div>
153
-
154
- ${() => {
155
- if (loading()) {
156
- return html`<div class="spinner">Loading profile...</div>`;
157
- }
158
-
159
- if (error()) {
160
- return html`<div class="error">${error()}</div>`;
161
- }
162
-
163
- if (user()) {
164
- return html`
165
- <div class="user-info">
166
- <h2>${user().name}</h2>
167
- <p>Email: ${user().email}</p>
168
- <p>Role: ${user().role}</p>
169
- <p>Joined: ${new Date(user().joined).toLocaleDateString()}</p>
170
- </div>
171
- `;
172
- }
173
-
174
- return html`<p>Select a user</p>`;
175
- }}
176
- </div>
177
- `;
178
- };
179
- ```
180
-
181
- ### Todo List with API
182
-
183
- ```javascript
184
- import { $, html } from 'sigpro';
185
-
186
- const TodoApp = () => {
187
- const todos = $([]);
188
- const loading = $(false);
189
- const newTodo = $('');
190
- const filter = $('all'); // 'all', 'active', 'completed'
191
-
192
- // Load todos
193
- const loadTodos = async () => {
194
- const data = await $.fetch('/api/todos', {}, loading);
195
- if (data) todos(data);
196
- };
197
-
198
- // Add todo
199
- const addTodo = async () => {
200
- if (!newTodo().trim()) return;
201
-
202
- const todo = await $.fetch('/api/todos', {
203
- text: newTodo(),
204
- completed: false
205
- });
206
-
207
- if (todo) {
208
- todos([...todos(), todo]);
209
- newTodo('');
210
- }
211
- };
212
-
213
- // Toggle todo
214
- const toggleTodo = async (id, completed) => {
215
- const updated = await $.fetch(`/api/todos/${id}`, {
216
- completed: !completed
217
- });
218
-
219
- if (updated) {
220
- todos(todos().map(t =>
221
- t.id === id ? updated : t
222
- ));
223
- }
224
- };
225
-
226
- // Delete todo
227
- const deleteTodo = async (id) => {
228
- const result = await $.fetch(`/api/todos/${id}/delete`, {});
229
- if (result) {
230
- todos(todos().filter(t => t.id !== id));
231
- }
232
- };
233
-
234
- // Filtered todos
235
- const filteredTodos = $(() => {
236
- const currentFilter = filter();
237
- if (currentFilter === 'all') return todos();
238
- if (currentFilter === 'active') {
239
- return todos().filter(t => !t.completed);
240
- }
241
- return todos().filter(t => t.completed);
242
- });
243
-
244
- // Load on mount
245
- loadTodos();
246
-
247
- return html`
248
- <div class="todo-app">
249
- <h1>Todo List</h1>
250
-
251
- <div class="add-todo">
252
- <input
253
- type="text"
254
- :value=${newTodo}
255
- @keydown.enter=${addTodo}
256
- placeholder="Add a new todo..."
257
- />
258
- <button @click=${addTodo}>Add</button>
259
- </div>
260
-
261
- <div class="filters">
262
- <button
263
- class:active=${() => filter() === 'all'}
264
- @click=${() => filter('all')}
265
- >
266
- All
267
- </button>
268
- <button
269
- class:active=${() => filter() === 'active'}
270
- @click=${() => filter('active')}
271
- >
272
- Active
273
- </button>
274
- <button
275
- class:active=${() => filter() === 'completed'}
276
- @click=${() => filter('completed')}
277
- >
278
- Completed
279
- </button>
280
- </div>
281
-
282
- ${() => loading() ? html`
283
- <div class="spinner">Loading todos...</div>
284
- ) : html`
285
- <ul class="todo-list">
286
- ${filteredTodos().map(todo => html`
287
- <li class="todo-item">
288
- <input
289
- type="checkbox"
290
- :checked=${todo.completed}
291
- @change=${() => toggleTodo(todo.id, todo.completed)}
292
- />
293
- <span class:completed=${todo.completed}>${todo.text}</span>
294
- <button @click=${() => deleteTodo(todo.id)}>🗑️</button>
295
- </li>
296
- `)}
297
- </ul>
298
- `}
299
- </div>
300
- `;
301
- };
302
- ```
303
-
304
- ### Infinite Scroll with Pagination
305
-
306
- ```javascript
307
- import { $, html } from 'sigpro';
308
-
309
- const InfiniteScroll = () => {
310
- const posts = $([]);
311
- const page = $(1);
312
- const loading = $(false);
313
- const hasMore = $(true);
314
- const error = $(null);
315
-
316
- const loadMore = async () => {
317
- if (loading() || !hasMore()) return;
318
-
319
- const data = await $.fetch('/api/posts', {
320
- page: page(),
321
- limit: 10
322
- }, loading);
323
-
324
- if (data) {
325
- if (data.posts.length === 0) {
326
- hasMore(false);
327
- } else {
328
- posts([...posts(), ...data.posts]);
329
- page(p => p + 1);
330
- }
331
- } else {
332
- error('Failed to load posts');
333
- }
334
- };
335
-
336
- // Intersection Observer for infinite scroll
337
- $.effect(() => {
338
- const observer = new IntersectionObserver(
339
- (entries) => {
340
- if (entries[0].isIntersecting) {
341
- loadMore();
342
- }
343
- },
344
- { threshold: 0.1 }
345
- );
346
-
347
- const sentinel = document.getElementById('sentinel');
348
- if (sentinel) observer.observe(sentinel);
349
-
350
- return () => observer.disconnect();
351
- });
352
-
353
- // Initial load
354
- loadMore();
355
-
356
- return html`
357
- <div class="infinite-scroll">
358
- <h1>Posts</h1>
359
-
360
- <div class="posts">
361
- ${posts().map(post => html`
362
- <article class="post">
363
- <h2>${post.title}</h2>
364
- <p>${post.body}</p>
365
- <small>By ${post.author}</small>
366
- </article>
367
- `)}
368
- </div>
369
-
370
- <div id="sentinel" class="sentinel">
371
- ${() => {
372
- if (loading()) {
373
- return html`<div class="spinner">Loading more...</div>`;
374
- }
375
- if (error()) {
376
- return html`<div class="error">${error()}</div>`;
377
- }
378
- if (!hasMore()) {
379
- return html`<div class="end">No more posts</div>`;
380
- }
381
- return '';
382
- }}
383
- </div>
384
- </div>
385
- `;
386
- };
387
- ```
388
-
389
- ### Search with Debounce
390
-
391
- ```javascript
392
- import { $, html } from 'sigpro';
393
-
394
- const SearchComponent = () => {
395
- const query = $('');
396
- const results = $([]);
397
- const loading = $(false);
398
- const error = $(null);
399
- let searchTimeout;
400
-
401
- const performSearch = async (searchQuery) => {
402
- if (!searchQuery.trim()) {
403
- results([]);
404
- return;
405
- }
406
-
407
- const data = await $.fetch('/api/search', {
408
- q: searchQuery
409
- }, loading);
410
-
411
- if (data) {
412
- results(data);
413
- } else {
414
- error('Search failed');
415
- }
416
- };
417
-
418
- // Debounced search
419
- $.effect(() => {
420
- const searchQuery = query();
421
-
422
- clearTimeout(searchTimeout);
423
-
424
- if (searchQuery.length < 2) {
425
- results([]);
426
- return;
427
- }
428
-
429
- searchTimeout = setTimeout(() => {
430
- performSearch(searchQuery);
431
- }, 300);
432
-
433
- return () => clearTimeout(searchTimeout);
434
- });
435
-
436
- return html`
437
- <div class="search">
438
- <div class="search-box">
439
- <input
440
- type="search"
441
- :value=${query}
442
- placeholder="Search..."
443
- class="search-input"
444
- />
445
- ${() => loading() ? html`
446
- <span class="spinner-small">⌛</span>
447
- ) : ''}
448
- </div>
449
-
450
- ${() => {
451
- if (error()) {
452
- return html`<div class="error">${error()}</div>`;
453
- }
454
-
455
- if (results().length > 0) {
456
- return html`
457
- <ul class="results">
458
- ${results().map(item => html`
459
- <li class="result-item">
460
- <h3>${item.title}</h3>
461
- <p>${item.description}</p>
462
- </li>
463
- `)}
464
- </ul>
465
- `;
466
- }
467
-
468
- if (query().length >= 2 && !loading()) {
469
- return html`<p class="no-results">No results found</p>`;
470
- }
471
-
472
- return '';
473
- }}
474
- </div>
475
- `;
476
- };
477
- ```
478
-
479
- ### Form Submission
480
-
481
- ```javascript
482
- import { $, html } from 'sigpro';
483
-
484
- const ContactForm = () => {
485
- const formData = $({
486
- name: '',
487
- email: '',
488
- message: ''
489
- });
490
-
491
- const submitting = $(false);
492
- const submitError = $(null);
493
- const submitSuccess = $(false);
494
-
495
- const handleSubmit = async (e) => {
496
- e.preventDefault();
497
-
498
- submitError(null);
499
- submitSuccess(false);
500
-
501
- const result = await $.fetch('/api/contact', formData(), submitting);
502
-
503
- if (result) {
504
- submitSuccess(true);
505
- formData({ name: '', email: '', message: '' });
506
- } else {
507
- submitError('Failed to send message. Please try again.');
508
- }
509
- };
510
-
511
- const updateField = (field, value) => {
512
- formData({
513
- ...formData(),
514
- [field]: value
515
- });
516
- };
517
-
518
- return html`
519
- <form class="contact-form" @submit=${handleSubmit}>
520
- <h2>Contact Us</h2>
521
-
522
- <div class="form-group">
523
- <label for="name">Name:</label>
524
- <input
525
- type="text"
526
- id="name"
527
- :value=${() => formData().name}
528
- @input=${(e) => updateField('name', e.target.value)}
529
- required
530
- ?disabled=${submitting}
531
- />
532
- </div>
533
-
534
- <div class="form-group">
535
- <label for="email">Email:</label>
536
- <input
537
- type="email"
538
- id="email"
539
- :value=${() => formData().email}
540
- @input=${(e) => updateField('email', e.target.value)}
541
- required
542
- ?disabled=${submitting}
543
- />
544
- </div>
545
-
546
- <div class="form-group">
547
- <label for="message">Message:</label>
548
- <textarea
549
- id="message"
550
- :value=${() => formData().message}
551
- @input=${(e) => updateField('message', e.target.value)}
552
- required
553
- rows="5"
554
- ?disabled=${submitting}
555
- ></textarea>
556
- </div>
557
-
558
- ${() => {
559
- if (submitting()) {
560
- return html`<div class="submitting">Sending...</div>`;
561
- }
562
-
563
- if (submitError()) {
564
- return html`<div class="error">${submitError()}</div>`;
565
- }
566
-
567
- if (submitSuccess()) {
568
- return html`<div class="success">Message sent successfully!</div>`;
569
- }
570
-
571
- return '';
572
- }}
573
-
574
- <button
575
- type="submit"
576
- ?disabled=${submitting}
577
- >
578
- Send Message
579
- </button>
580
- </form>
581
- `;
582
- };
583
- ```
584
-
585
- ### Real-time Dashboard with Multiple Endpoints
586
-
587
- ```javascript
588
- import { $, html } from 'sigpro';
589
-
590
- const Dashboard = () => {
591
- // Multiple data streams
592
- const metrics = $({});
593
- const alerts = $([]);
594
- const logs = $([]);
595
-
596
- const loading = $({
597
- metrics: false,
598
- alerts: false,
599
- logs: false
600
- });
601
-
602
- const refreshInterval = $(5000); // 5 seconds
603
-
604
- const fetchMetrics = async () => {
605
- const data = await $.fetch('/api/metrics', {}, loading().metrics);
606
- if (data) metrics(data);
607
- };
608
-
609
- const fetchAlerts = async () => {
610
- const data = await $.fetch('/api/alerts', {}, loading().alerts);
611
- if (data) alerts(data);
612
- };
613
-
614
- const fetchLogs = async () => {
615
- const data = await $.fetch('/api/logs', {
616
- limit: 50
617
- }, loading().logs);
618
- if (data) logs(data);
619
- };
620
-
621
- // Auto-refresh all data
622
- $.effect(() => {
623
- fetchMetrics();
624
- fetchAlerts();
625
- fetchLogs();
626
-
627
- const interval = setInterval(() => {
628
- fetchMetrics();
629
- fetchAlerts();
630
- }, refreshInterval());
631
-
632
- return () => clearInterval(interval);
633
- });
634
-
635
- return html`
636
- <div class="dashboard">
637
- <header>
638
- <h1>System Dashboard</h1>
639
- <div class="refresh-control">
640
- <label>
641
- Refresh interval:
642
- <select :value=${refreshInterval} @change=${(e) => refreshInterval(parseInt(e.target.value))}>
643
- <option value="2000">2 seconds</option>
644
- <option value="5000">5 seconds</option>
645
- <option value="10000">10 seconds</option>
646
- <option value="30000">30 seconds</option>
647
- </select>
648
- </label>
649
- </div>
650
- </header>
651
-
652
- <div class="dashboard-grid">
653
- <!-- Metrics Panel -->
654
- <div class="panel metrics">
655
- <h2>System Metrics</h2>
656
- ${() => loading().metrics ? html`
657
- <div class="spinner">Loading metrics...</div>
658
- ) : html`
659
- <div class="metrics-grid">
660
- <div class="metric">
661
- <label>CPU</label>
662
- <span>${metrics().cpu || 0}%</span>
663
- </div>
664
- <div class="metric">
665
- <label>Memory</label>
666
- <span>${metrics().memory || 0}%</span>
667
- </div>
668
- <div class="metric">
669
- <label>Requests</label>
670
- <span>${metrics().requests || 0}/s</span>
671
- </div>
672
- </div>
673
- `}
674
- </div>
675
-
676
- <!-- Alerts Panel -->
677
- <div class="panel alerts">
678
- <h2>Active Alerts</h2>
679
- ${() => loading().alerts ? html`
680
- <div class="spinner">Loading alerts...</div>
681
- ) : alerts().length > 0 ? html`
682
- <ul>
683
- ${alerts().map(alert => html`
684
- <li class="alert ${alert.severity}">
685
- <strong>${alert.type}</strong>
686
- <p>${alert.message}</p>
687
- <small>${new Date(alert.timestamp).toLocaleTimeString()}</small>
688
- </li>
689
- `)}
690
- </ul>
691
- ) : html`
692
- <p class="no-data">No active alerts</p>
693
- `}
694
- </div>
695
-
696
- <!-- Logs Panel -->
697
- <div class="panel logs">
698
- <h2>Recent Logs</h2>
699
- ${() => loading().logs ? html`
700
- <div class="spinner">Loading logs...</div>
701
- ) : html`
702
- <ul>
703
- ${logs().map(log => html`
704
- <li class="log ${log.level}">
705
- <span class="timestamp">${new Date(log.timestamp).toLocaleTimeString()}</span>
706
- <span class="message">${log.message}</span>
707
- </li>
708
- `)}
709
- </ul>
710
- `}
711
- </div>
712
- </div>
713
- </div>
714
- `;
715
- };
716
- ```
717
-
718
- ### File Upload
719
-
720
- ```javascript
721
- import { $, html } from 'sigpro';
722
-
723
- const FileUploader = () => {
724
- const files = $([]);
725
- const uploading = $(false);
726
- const uploadProgress = $({});
727
- const uploadResults = $([]);
728
-
729
- const handleFileSelect = (e) => {
730
- files([...e.target.files]);
731
- };
732
-
733
- const uploadFiles = async () => {
734
- if (files().length === 0) return;
735
-
736
- uploading(true);
737
- uploadResults([]);
738
-
739
- for (const file of files()) {
740
- const formData = new FormData();
741
- formData.append('file', file);
742
-
743
- // Track progress for this file
744
- uploadProgress({
745
- ...uploadProgress(),
746
- [file.name]: 0
747
- });
748
-
749
- try {
750
- // Custom fetch for FormData
751
- const response = await fetch('/api/upload', {
752
- method: 'POST',
753
- body: formData
754
- });
755
-
756
- const result = await response.json();
757
-
758
- uploadResults([
759
- ...uploadResults(),
760
- { file: file.name, success: true, result }
761
- ]);
762
- } catch (error) {
763
- uploadResults([
764
- ...uploadResults(),
765
- { file: file.name, success: false, error: error.message }
766
- ]);
767
- }
768
-
769
- uploadProgress({
770
- ...uploadProgress(),
771
- [file.name]: 100
772
- });
773
- }
774
-
775
- uploading(false);
776
- };
777
-
778
- return html`
779
- <div class="file-uploader">
780
- <h2>Upload Files</h2>
781
-
782
- <input
783
- type="file"
784
- multiple
785
- @change=${handleFileSelect}
786
- ?disabled=${uploading}
787
- />
788
-
789
- ${() => files().length > 0 ? html`
790
- <div class="file-list">
791
- <h3>Selected Files:</h3>
792
- <ul>
793
- ${files().map(file => html`
794
- <li>
795
- ${file.name} (${(file.size / 1024).toFixed(2)} KB)
796
- ${() => uploadProgress()[file.name] ? html`
797
- <progress value="${uploadProgress()[file.name]}" max="100"></progress>
798
- ) : ''}
799
- </li>
800
- `)}
801
- </ul>
802
-
803
- <button
804
- @click=${uploadFiles}
805
- ?disabled=${uploading}
806
- >
807
- ${() => uploading() ? 'Uploading...' : 'Upload Files'}
808
- </button>
809
- </div>
810
- ` : ''}
811
-
812
- ${() => uploadResults().length > 0 ? html`
813
- <div class="upload-results">
814
- <h3>Upload Results:</h3>
815
- <ul>
816
- ${uploadResults().map(result => html`
817
- <li class="${result.success ? 'success' : 'error'}">
818
- ${result.file}:
819
- ${result.success ? 'Uploaded successfully' : `Failed: ${result.error}`}
820
- </li>
821
- `)}
822
- </ul>
823
- </div>
824
- ` : ''}
825
- </div>
826
- `;
827
- };
828
- ```
829
-
830
- ### Retry Logic
831
-
832
- ```javascript
833
- import { $ } from 'sigpro';
834
-
835
- // Enhanced fetch with retry
836
- const fetchWithRetry = async (url, data, loading, maxRetries = 3) => {
837
- let lastError;
838
-
839
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
840
- try {
841
- if (loading) loading(true);
842
-
843
- const result = await $.fetch(url, data);
844
- if (result !== null) {
845
- return result;
846
- }
847
-
848
- // If we get null but no error, wait and retry
849
- if (attempt < maxRetries) {
850
- await new Promise(resolve =>
851
- setTimeout(resolve, Math.pow(2, attempt) * 1000) // Exponential backoff
852
- );
853
- }
854
- } catch (error) {
855
- lastError = error;
856
- console.warn(`Attempt ${attempt} failed:`, error);
857
-
858
- if (attempt < maxRetries) {
859
- await new Promise(resolve =>
860
- setTimeout(resolve, Math.pow(2, attempt) * 1000)
861
- );
862
- }
863
- } finally {
864
- if (attempt === maxRetries && loading) {
865
- loading(false);
866
- }
867
- }
868
- }
869
-
870
- console.error('All retry attempts failed:', lastError);
871
- return null;
872
- };
873
-
874
- // Usage
875
- const loading = $(false);
876
- const data = await fetchWithRetry('/api/unreliable-endpoint', {}, loading, 5);
877
- ```
878
-
879
- ## 🎯 Best Practices
880
-
881
- ### 1. Always Handle Null Responses
882
-
883
- ```javascript
884
- // ❌ Don't assume success
885
- const data = await $.fetch('/api/data');
886
- console.log(data.property); // Might throw if data is null
887
-
888
- // ✅ Check for null
889
- const data = await $.fetch('/api/data');
890
- if (data) {
891
- console.log(data.property);
892
- } else {
893
- showError('Failed to load data');
894
- }
895
- ```
896
-
897
- ### 2. Use with Effects for Reactivity
898
-
899
- ```javascript
900
- // ❌ Manual fetching
901
- button.addEventListener('click', async () => {
902
- const data = await $.fetch('/api/data');
903
- updateUI(data);
904
- });
905
-
906
- // ✅ Reactive fetching
907
- const trigger = $(false);
908
-
909
- $.effect(() => {
910
- if (trigger()) {
911
- $.fetch('/api/data').then(data => {
912
- if (data) updateUI(data);
913
- });
914
- }
915
- });
916
-
917
- trigger(true); // Triggers fetch
918
- ```
919
-
920
- ### 3. Combine with Loading Signals
921
-
922
- ```javascript
923
- // ✅ Always show loading state
924
- const loading = $(false);
925
- const data = $(null);
926
-
927
- async function load() {
928
- const result = await $.fetch('/api/data', {}, loading);
929
- if (result) data(result);
930
- }
931
-
932
- // In template
933
- html`
934
- <div>
935
- ${() => loading() ? '<Spinner />' :
936
- data() ? '<Data />' :
937
- '<Empty />'}
938
- </div>
939
- `;
940
- ```
941
-
942
- ### 4. Cancel In-flight Requests
943
-
944
- ```javascript
945
- // ✅ Use AbortController with effects
946
- let controller;
947
-
948
- $.effect(() => {
949
- if (controller) {
950
- controller.abort();
951
- }
952
-
953
- controller = new AbortController();
954
-
955
- fetch(url, { signal: controller.signal })
956
- .then(res => res.json())
957
- .then(data => {
958
- if (!controller.signal.aborted) {
959
- updateData(data);
960
- }
961
- });
962
-
963
- return () => controller.abort();
964
- });
965
- ```
966
-
967
- ## 📊 Error Handling
968
-
969
- ### Basic Error Handling
970
-
971
- ```javascript
972
- const data = await $.fetch('/api/data');
973
- if (!data) {
974
- // Handle error (show message, retry, etc.)
975
- }
976
- ```
977
-
978
- ### With Error Signal
979
-
980
- ```javascript
981
- const data = $(null);
982
- const error = $(null);
983
- const loading = $(false);
984
-
985
- async function loadData() {
986
- error(null);
987
- const result = await $.fetch('/api/data', {}, loading);
988
-
989
- if (result) {
990
- data(result);
991
- } else {
992
- error('Failed to load data');
993
- }
994
- }
995
- ```
996
- ---
997
-
998
- > **Pro Tip:** Combine `$.fetch` with `$.effect` and loading signals for a complete reactive data fetching solution. The loading signal integration makes it trivial to show loading states in your UI.