@alwatr/flux 9.16.0 → 9.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -68
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ No magic. No hidden re-renders. No performance cliffs.
|
|
|
52
52
|
|
|
53
53
|
### 3. **Absolute Type Safety**
|
|
54
54
|
|
|
55
|
-
Through TypeScript's **Declaration Merging**, the entire action bus is fully typed
|
|
55
|
+
Through TypeScript's **Declaration Merging**, the entire action bus is fully typed. Every action is a single **`Action<K>`** object (Alwatr Flux Standard Action — AFSA) carrying `type`, `payload`, `context`, and `meta`:
|
|
56
56
|
|
|
57
57
|
```typescript
|
|
58
58
|
// Define your actions once
|
|
@@ -64,14 +64,16 @@ declare module '@alwatr/flux' {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// Get compile-time safety everywhere
|
|
68
|
-
onAction('add_to_cart', (
|
|
69
|
-
//
|
|
70
|
-
cartService.add(
|
|
67
|
+
// Get compile-time safety everywhere — handler receives the full Action object
|
|
68
|
+
onAction('add_to_cart', (action) => {
|
|
69
|
+
// action.payload is typed as {productId: number; qty: number}
|
|
70
|
+
cartService.add(action.payload.productId, action.payload.qty);
|
|
71
|
+
// action.context is the nearest [action-context] ancestor value (or undefined)
|
|
72
|
+
console.log(action.context); // e.g. 'product-list'
|
|
71
73
|
});
|
|
72
74
|
|
|
73
|
-
dispatchAction('add_to_cart', {productId: 42, qty: 1}); // ✅
|
|
74
|
-
dispatchAction('add_to_cart', 'wrong'); // ❌ Compile error
|
|
75
|
+
dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}}); // ✅
|
|
76
|
+
dispatchAction({type: 'add_to_cart', payload: 'wrong'}); // ❌ Compile error
|
|
75
77
|
```
|
|
76
78
|
|
|
77
79
|
---
|
|
@@ -120,7 +122,7 @@ lastName.set('Smith'); // Only fullName and the effect re-run — nothing else
|
|
|
120
122
|
|
|
121
123
|
### 🧩 **Declarative HTML Syntax**
|
|
122
124
|
|
|
123
|
-
Connect DOM events to typed actions without writing JavaScript:
|
|
125
|
+
Connect DOM events to typed actions without writing JavaScript. Wrap elements in `[action-context]` to scope the same action type to different UI regions:
|
|
124
126
|
|
|
125
127
|
```html
|
|
126
128
|
<!-- Simple action -->
|
|
@@ -153,6 +155,28 @@ Connect DOM events to typed actions without writing JavaScript:
|
|
|
153
155
|
|
|
154
156
|
<!-- Fire once and remove -->
|
|
155
157
|
<button on-click="track_impression:hero_banner; once">Learn More</button>
|
|
158
|
+
|
|
159
|
+
<!-- Context scoping — same action type, different regions -->
|
|
160
|
+
<section action-context="volume">
|
|
161
|
+
<input
|
|
162
|
+
type="range"
|
|
163
|
+
on-input="slider:change:$value"
|
|
164
|
+
/>
|
|
165
|
+
</section>
|
|
166
|
+
<section action-context="brightness">
|
|
167
|
+
<input
|
|
168
|
+
type="range"
|
|
169
|
+
on-input="slider:change:$value"
|
|
170
|
+
/>
|
|
171
|
+
</section>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// Handler receives the full Action object — payload, context, and meta together
|
|
176
|
+
onAction('slider:change', (action) => {
|
|
177
|
+
if (action.context === 'volume') audioService.setVolume(Number(action.payload));
|
|
178
|
+
if (action.context === 'brightness') displayService.setBrightness(Number(action.payload));
|
|
179
|
+
});
|
|
156
180
|
```
|
|
157
181
|
|
|
158
182
|
**Built-in modifiers:**
|
|
@@ -308,55 +332,59 @@ createEffect({
|
|
|
308
332
|
Flux implements a **strict layered architecture** where each layer has a single responsibility:
|
|
309
333
|
|
|
310
334
|
```
|
|
311
|
-
|
|
312
|
-
│ VIEW LAYER
|
|
313
|
-
│ (HTML templates, Directives, lit-html rendering)
|
|
314
|
-
│
|
|
315
|
-
│ • Reads state from Signals
|
|
316
|
-
│ • Dispatches Actions via on-<event> attributes
|
|
317
|
-
│ • Never manipulates state directly
|
|
318
|
-
|
|
335
|
+
┌───────────────────────────────────────────────────────────┐
|
|
336
|
+
│ VIEW LAYER │
|
|
337
|
+
│ (HTML templates, Directives, lit-html rendering) │
|
|
338
|
+
│ │
|
|
339
|
+
│ • Reads state from Signals │
|
|
340
|
+
│ • Dispatches Actions via on-<event> attributes │
|
|
341
|
+
│ • Never manipulates state directly │
|
|
342
|
+
└──────────────────┬────────────────────────────────────────┘
|
|
319
343
|
│ on-click="add_to_cart:42"
|
|
320
344
|
▼
|
|
321
|
-
|
|
322
|
-
│ ACTION LAYER
|
|
323
|
-
│ (@alwatr/action — Global Event Delegation)
|
|
324
|
-
│
|
|
325
|
-
│ • Captures DOM events via document.body listener
|
|
326
|
-
│ •
|
|
327
|
-
│ •
|
|
328
|
-
│ •
|
|
329
|
-
│ •
|
|
330
|
-
|
|
331
|
-
|
|
345
|
+
┌───────────────────────────────────────────────────────────┐
|
|
346
|
+
│ ACTION LAYER │
|
|
347
|
+
│ (@alwatr/action — Global Event Delegation + AFSA) │
|
|
348
|
+
│ │
|
|
349
|
+
│ • Captures DOM events via document.body listener │
|
|
350
|
+
│ • Resolves [action-context] ancestor → action.context │
|
|
351
|
+
│ • Parses on-<event> attributes │
|
|
352
|
+
│ • Runs modifiers (prevent, validate, once) │
|
|
353
|
+
│ • Modifiers may enrich action.meta │
|
|
354
|
+
│ • Resolves payload ($value, $formdata) │
|
|
355
|
+
│ • Dispatches full Action {type, payload, context, meta} │
|
|
356
|
+
└──────────────────┬────────────────────────────────────────┘
|
|
357
|
+
│ dispatchAction({type: 'add_to_cart', payload: 42, context: 'cart'})
|
|
332
358
|
▼
|
|
333
|
-
|
|
334
|
-
│ CONTROLLER LAYER
|
|
335
|
-
│ (Business Logic, Services, Use Cases)
|
|
336
|
-
│
|
|
337
|
-
│ • Subscribes to Actions via onAction()
|
|
338
|
-
│ •
|
|
339
|
-
│
|
|
340
|
-
│ •
|
|
341
|
-
|
|
359
|
+
┌───────────────────────────────────────────────────────────┐
|
|
360
|
+
│ CONTROLLER LAYER │
|
|
361
|
+
│ (Business Logic, Services, Use Cases) │
|
|
362
|
+
│ │
|
|
363
|
+
│ • Subscribes to Actions via onAction() │
|
|
364
|
+
│ • Receives full Action object (type, payload, context, │
|
|
365
|
+
│ meta) — no need to pass context separately │
|
|
366
|
+
│ • Executes business logic │
|
|
367
|
+
│ • Updates State via Signal.set() │
|
|
368
|
+
│ • Never touches DOM directly │
|
|
369
|
+
└──────────────────┬────────────────────────────────────────┘
|
|
342
370
|
│ cartSignal.set(newCart)
|
|
343
371
|
▼
|
|
344
|
-
|
|
345
|
-
│ STATE LAYER
|
|
346
|
-
│ (@alwatr/signal — Reactive State Management)
|
|
347
|
-
│
|
|
348
|
-
│ • StateSignal — mutable state
|
|
349
|
-
│ • ComputedSignal — derived state (memoized)
|
|
350
|
-
│ • EffectSignal — side effects
|
|
351
|
-
│ • PersistentStateSignal — localStorage sync
|
|
352
|
-
│ • SessionStateSignal — sessionStorage sync
|
|
353
|
-
|
|
372
|
+
┌───────────────────────────────────────────────────────────┐
|
|
373
|
+
│ STATE LAYER │
|
|
374
|
+
│ (@alwatr/signal — Reactive State Management) │
|
|
375
|
+
│ │
|
|
376
|
+
│ • StateSignal — mutable state │
|
|
377
|
+
│ • ComputedSignal — derived state (memoized) │
|
|
378
|
+
│ • EffectSignal — side effects │
|
|
379
|
+
│ • PersistentStateSignal — localStorage sync │
|
|
380
|
+
│ • SessionStateSignal — sessionStorage sync │
|
|
381
|
+
└──────────────────┬────────────────────────────────────────┘
|
|
354
382
|
│ signal.subscribe(render)
|
|
355
383
|
▼
|
|
356
|
-
|
|
357
|
-
│ VIEW LAYER
|
|
358
|
-
│ (Re-render only affected DOM nodes)
|
|
359
|
-
|
|
384
|
+
┌───────────────────────────────────────────────────────────┐
|
|
385
|
+
│ VIEW LAYER │
|
|
386
|
+
│ (Re-render only affected DOM nodes) │
|
|
387
|
+
└───────────────────────────────────────────────────────────┘
|
|
360
388
|
```
|
|
361
389
|
|
|
362
390
|
**Key architectural benefits:**
|
|
@@ -369,6 +397,63 @@ Flux implements a **strict layered architecture** where each layer has a single
|
|
|
369
397
|
|
|
370
398
|
---
|
|
371
399
|
|
|
400
|
+
## 🎯 The Action Object (AFSA)
|
|
401
|
+
|
|
402
|
+
Every action flowing through the bus — whether triggered from HTML attributes or dispatched programmatically — is a single **`Action<K>`** object (Alwatr Flux Standard Action):
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
interface Action<K extends keyof ActionRecord> {
|
|
406
|
+
/** Action identifier — must be a key of ActionRecord. */
|
|
407
|
+
type: K;
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* DOM context from the nearest [action-context] ancestor.
|
|
411
|
+
* undefined for programmatic dispatches or when no ancestor exists.
|
|
412
|
+
* Example: 'product-list', 'checkout-form', 'volume-slider'
|
|
413
|
+
*/
|
|
414
|
+
context?: string;
|
|
415
|
+
|
|
416
|
+
/** Business payload — type is inferred from ActionRecord[K]. */
|
|
417
|
+
payload: ActionRecord[K];
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Open-ended metadata bag for cross-cutting concerns.
|
|
421
|
+
* Modifiers in the delegation pipeline may write to this before
|
|
422
|
+
* the action reaches subscribers.
|
|
423
|
+
*/
|
|
424
|
+
meta?: Record<string, unknown>;
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
This unified structure replaces the previous two-argument `(id, payload)` API. Every handler now receives the full picture:
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
onAction('add_to_cart', (action) => {
|
|
432
|
+
console.log(action.type); // 'add_to_cart'
|
|
433
|
+
console.log(action.payload); // {productId: 42, qty: 1} — fully typed
|
|
434
|
+
console.log(action.context); // 'product-list' — from [action-context] ancestor
|
|
435
|
+
console.log(action.meta); // {traceId: '…'} — set by modifiers, or undefined
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Modifiers can enrich `meta` before the action reaches subscribers:
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import {registerModifier} from '@alwatr/flux';
|
|
443
|
+
|
|
444
|
+
registerModifier('trace', (_event, _element, action) => {
|
|
445
|
+
action.meta ??= {};
|
|
446
|
+
action.meta['traceId'] = crypto.randomUUID();
|
|
447
|
+
return true;
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
```html
|
|
452
|
+
<button on-click="submit_order:42; trace">Place Order</button>
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
372
457
|
## 📦 Installation
|
|
373
458
|
|
|
374
459
|
```bash
|
|
@@ -443,8 +528,9 @@ onAction('decrement', () => {
|
|
|
443
528
|
counterSignal.update((count) => count - 1);
|
|
444
529
|
});
|
|
445
530
|
|
|
446
|
-
|
|
447
|
-
|
|
531
|
+
// Handler receives the full Action object — payload is typed from ActionRecord
|
|
532
|
+
onAction('set_count', (action) => {
|
|
533
|
+
counterSignal.set(action.payload); // action.payload: number
|
|
448
534
|
});
|
|
449
535
|
```
|
|
450
536
|
|
|
@@ -612,39 +698,56 @@ setupActionDelegation();
|
|
|
612
698
|
setupActionDelegation([...DEFAULT_DELEGATED_EVENTS, 'keydown', 'focus']);
|
|
613
699
|
```
|
|
614
700
|
|
|
615
|
-
#### `onAction<K>(
|
|
701
|
+
#### `onAction<K>(type, handler)`
|
|
616
702
|
|
|
617
|
-
Subscribes to a typed action.
|
|
703
|
+
Subscribes to a typed action. The handler receives the full `Action<K>` object.
|
|
618
704
|
|
|
619
705
|
```typescript
|
|
620
|
-
const sub = onAction('add_to_cart', (
|
|
621
|
-
cartService.add(
|
|
706
|
+
const sub = onAction('add_to_cart', (action) => {
|
|
707
|
+
cartService.add(action.payload.productId, action.payload.qty);
|
|
708
|
+
console.log(action.context); // e.g. 'product-list' or undefined
|
|
709
|
+
console.log(action.meta); // any metadata set by modifiers
|
|
622
710
|
});
|
|
623
711
|
|
|
624
712
|
sub.unsubscribe(); // Clean up when done
|
|
625
713
|
```
|
|
626
714
|
|
|
627
|
-
#### `dispatchAction<K>(
|
|
715
|
+
#### `dispatchAction<K>(action)`
|
|
628
716
|
|
|
629
|
-
Dispatches a typed action programmatically.
|
|
717
|
+
Dispatches a typed action programmatically. Takes a full `Action<K>` object.
|
|
630
718
|
|
|
631
719
|
```typescript
|
|
632
|
-
dispatchAction('navigate', '/home');
|
|
633
|
-
dispatchAction('logout'); // void payload
|
|
720
|
+
dispatchAction({type: 'navigate', payload: '/home'});
|
|
721
|
+
dispatchAction({type: 'logout', payload: undefined}); // void payload
|
|
722
|
+
|
|
723
|
+
// With context and meta
|
|
724
|
+
dispatchAction({
|
|
725
|
+
type: 'add_to_cart',
|
|
726
|
+
payload: {productId: 42, qty: 1},
|
|
727
|
+
context: 'product-list',
|
|
728
|
+
meta: {source: 'recommendation'},
|
|
729
|
+
});
|
|
634
730
|
```
|
|
635
731
|
|
|
636
732
|
#### `registerModifier(name, handler)`
|
|
637
733
|
|
|
638
|
-
Adds a custom modifier for `on-<event>` attributes.
|
|
734
|
+
Adds a custom modifier for `on-<event>` attributes. The handler receives the mutable `action` object and may write to `action.meta`.
|
|
639
735
|
|
|
640
736
|
```typescript
|
|
641
737
|
registerModifier('confirm', () => {
|
|
642
738
|
return window.confirm('Are you sure?');
|
|
643
739
|
});
|
|
740
|
+
|
|
741
|
+
// A modifier that stamps a trace ID into meta
|
|
742
|
+
registerModifier('trace', (_event, _element, action) => {
|
|
743
|
+
action.meta ??= {};
|
|
744
|
+
action.meta['traceId'] = crypto.randomUUID();
|
|
745
|
+
return true;
|
|
746
|
+
});
|
|
644
747
|
```
|
|
645
748
|
|
|
646
749
|
```html
|
|
647
|
-
<button on-click="delete_item:42; confirm">Delete</button>
|
|
750
|
+
<button on-click="delete_item:42; confirm,trace">Delete</button>
|
|
648
751
|
```
|
|
649
752
|
|
|
650
753
|
#### `registerPayloadResolver(name, resolver)`
|
|
@@ -889,23 +992,23 @@ import {todosSignal} from './state.js';
|
|
|
889
992
|
|
|
890
993
|
let nextId = 1;
|
|
891
994
|
|
|
892
|
-
onAction('add_todo', (
|
|
995
|
+
onAction('add_todo', (action) => {
|
|
893
996
|
todosSignal.update((todos) => [
|
|
894
997
|
...todos,
|
|
895
|
-
{id: nextId++, text, done: false},
|
|
998
|
+
{id: nextId++, text: action.payload, done: false},
|
|
896
999
|
]);
|
|
897
1000
|
});
|
|
898
1001
|
|
|
899
|
-
onAction('toggle_todo', (
|
|
1002
|
+
onAction('toggle_todo', (action) => {
|
|
900
1003
|
todosSignal.update((todos) =>
|
|
901
1004
|
todos.map((todo) =>
|
|
902
|
-
todo.id ===
|
|
1005
|
+
todo.id === action.payload ? {...todo, done: !todo.done} : todo
|
|
903
1006
|
)
|
|
904
1007
|
);
|
|
905
1008
|
});
|
|
906
1009
|
|
|
907
|
-
onAction('remove_todo', (
|
|
908
|
-
todosSignal.update((todos) => todos.filter((t) => t.id !==
|
|
1010
|
+
onAction('remove_todo', (action) => {
|
|
1011
|
+
todosSignal.update((todos) => todos.filter((t) => t.id !== action.payload));
|
|
909
1012
|
});
|
|
910
1013
|
|
|
911
1014
|
// view.html
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/* 📦 @alwatr/flux v9.
|
|
1
|
+
/* 📦 @alwatr/flux v9.17.0 */
|
|
2
2
|
export*from"@alwatr/signal";export*from"@alwatr/action";export*from"@alwatr/directive";export*from"@alwatr/render-state";export*from"@alwatr/local-storage";export*from"@alwatr/session-storage";export*from"@alwatr/page-ready";
|
|
3
3
|
|
|
4
|
-
//# debugId=
|
|
4
|
+
//# debugId=0386D7BA1F615EBF64756E2164756E21
|
|
5
5
|
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"// UI and reactive bundle — signals, actions, directives, and client-side storage.\n// This package aggregates all UI-layer nanolibs for convenient single-import usage.\n\nexport * from '@alwatr/signal';\nexport * from '@alwatr/action';\nexport * from '@alwatr/directive';\nexport * from '@alwatr/render-state';\nexport * from '@alwatr/local-storage';\nexport * from '@alwatr/session-storage';\nexport * from '@alwatr/page-ready';\nexport type * from '@alwatr/type-helper';\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": ";AAGA,4BACA,4BACA,+BACA,kCACA,mCACA,qCACA",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "0386D7BA1F615EBF64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alwatr/flux",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.17.0",
|
|
4
4
|
"description": "UI and reactive library bundle for ECMAScript (JavaScript/TypeScript) projects — signals, actions, directives, and storage.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"sideEffects": false,
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@alwatr/action": "9.
|
|
24
|
+
"@alwatr/action": "9.17.0",
|
|
25
25
|
"@alwatr/directive": "9.16.0",
|
|
26
26
|
"@alwatr/local-storage": "9.16.0",
|
|
27
27
|
"@alwatr/page-ready": "9.16.0",
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"ui",
|
|
82
82
|
"unidirectional-data-flow"
|
|
83
83
|
],
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "782563375f55c55d29719cbcfebaca251d69ddcd"
|
|
85
85
|
}
|