@alwatr/action 9.14.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 +335 -95
- package/dist/action-record.d.ts +4 -4
- package/dist/action.d.ts +93 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/delegate.d.ts +33 -10
- package/dist/delegate.d.ts.map +1 -1
- package/dist/lib.d.ts +8 -6
- package/dist/lib.d.ts.map +1 -1
- package/dist/main.d.ts +47 -8
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +6 -6
- package/dist/method.d.ts +74 -57
- package/dist/method.d.ts.map +1 -1
- package/dist/registry.d.ts +28 -16
- package/dist/registry.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/action-record.ts +4 -4
- package/src/action.ts +97 -0
- package/src/delegate.ts +131 -86
- package/src/lib.ts +9 -6
- package/src/main.ts +47 -8
- package/src/method.ts +84 -66
- package/src/registry.ts +55 -24
package/dist/method.d.ts
CHANGED
|
@@ -2,18 +2,22 @@ import type { Awaitable } from '@alwatr/type-helper';
|
|
|
2
2
|
import type { SubscribeResult } from '@alwatr/signal';
|
|
3
3
|
import { type ModifierHandler, type PayloadResolver } from './registry.js';
|
|
4
4
|
import type { ActionRecord } from './action-record.js';
|
|
5
|
+
import type { Action } from './action.js';
|
|
5
6
|
export type { ModifierHandler, PayloadResolver };
|
|
7
|
+
export type { Action };
|
|
6
8
|
/**
|
|
7
9
|
* Subscribes to a named action dispatched anywhere in the application.
|
|
8
10
|
*
|
|
9
|
-
* `
|
|
10
|
-
*
|
|
11
|
-
* generic annotation needed
|
|
11
|
+
* `type` must be a key of `ActionRecord`. The handler receives the full
|
|
12
|
+
* `Action<K>` object — giving access to `payload`, `context`, and `meta`
|
|
13
|
+
* in one place. No manual generic annotation is needed; the compiler infers
|
|
14
|
+
* the correct `payload` type from `ActionRecord`:
|
|
12
15
|
*
|
|
13
16
|
* ```ts
|
|
14
|
-
* // ActionRecord declares: '
|
|
15
|
-
* onAction('
|
|
16
|
-
* cartService.add(
|
|
17
|
+
* // ActionRecord declares: 'add_to_cart': {productId: number; qty: number}
|
|
18
|
+
* onAction('add_to_cart', (action) => {
|
|
19
|
+
* cartService.add(action.payload.productId, action.payload.qty); // fully typed
|
|
20
|
+
* console.log(action.context); // e.g. 'product-list' (from DOM) or undefined
|
|
17
21
|
* });
|
|
18
22
|
* ```
|
|
19
23
|
*
|
|
@@ -24,7 +28,7 @@ export type { ModifierHandler, PayloadResolver };
|
|
|
24
28
|
* // src/action-record.ts
|
|
25
29
|
* declare module '@alwatr/action' {
|
|
26
30
|
* interface ActionRecord {
|
|
27
|
-
* '
|
|
31
|
+
* 'open_drawer': string;
|
|
28
32
|
* }
|
|
29
33
|
* }
|
|
30
34
|
* ```
|
|
@@ -32,83 +36,87 @@ export type { ModifierHandler, PayloadResolver };
|
|
|
32
36
|
* Internally delegates to `ChannelSignal.on()` for **O(1) routing** — dispatching
|
|
33
37
|
* action `'A'` never invokes handlers registered for action `'B'`.
|
|
34
38
|
*
|
|
35
|
-
* @param
|
|
36
|
-
* @param handler
|
|
39
|
+
* @param type - A key of `ActionRecord`.
|
|
40
|
+
* @param handler - Callback invoked with the full `Action<K>` on each dispatch.
|
|
37
41
|
* @returns A `SubscribeResult` with an `unsubscribe()` method for cleanup.
|
|
38
42
|
*
|
|
39
43
|
* @example
|
|
40
44
|
* ```ts
|
|
41
45
|
* import {onAction} from '@alwatr/action';
|
|
42
46
|
*
|
|
43
|
-
* const sub = onAction('
|
|
44
|
-
* router.setPage(
|
|
47
|
+
* const sub = onAction('page_ready', (action) => {
|
|
48
|
+
* router.setPage(action.payload); // payload: string — inferred from ActionRecord
|
|
45
49
|
* });
|
|
46
50
|
*
|
|
47
51
|
* sub.unsubscribe(); // stop listening when no longer needed
|
|
48
52
|
* ```
|
|
49
53
|
*/
|
|
50
|
-
export declare function onAction<K extends keyof ActionRecord>(
|
|
54
|
+
export declare function onAction<K extends keyof ActionRecord>(type: K, handler: (action: Action<K>) => Awaitable<void>): SubscribeResult;
|
|
51
55
|
/**
|
|
52
|
-
* Dispatches
|
|
56
|
+
* Dispatches an action to all `onAction` subscribers with a matching `type`.
|
|
53
57
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
58
|
+
* Accepts a full `Action<K>` object. The `payload` field is automatically
|
|
59
|
+
* typed from `ActionRecord[K]` — passing the wrong shape is a **compile error**:
|
|
56
60
|
*
|
|
57
61
|
* ```ts
|
|
58
|
-
* // ActionRecord declares: '
|
|
59
|
-
* dispatchAction('
|
|
60
|
-
* dispatchAction('
|
|
61
|
-
* dispatchAction('
|
|
62
|
+
* // ActionRecord declares: 'add_to_cart': {productId: number; qty: number}
|
|
63
|
+
* dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}}); // ✅
|
|
64
|
+
* dispatchAction({type: 'add_to_cart', payload: 'wrong'}); // ❌ compile error
|
|
65
|
+
* dispatchAction({type: 'unknown_action', payload: 'x'}); // ❌ compile error
|
|
62
66
|
* ```
|
|
63
67
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* // src/action-record.ts
|
|
68
|
-
* declare module '@alwatr/action' {
|
|
69
|
-
* interface ActionRecord {
|
|
70
|
-
* 'navigate': string;
|
|
71
|
-
* 'logout': void;
|
|
72
|
-
* }
|
|
73
|
-
* }
|
|
74
|
-
* ```
|
|
68
|
+
* The `context` and `meta` fields are optional. When dispatching from code
|
|
69
|
+
* (not from the DOM), omit `context` — it is only meaningful for DOM-originated
|
|
70
|
+
* actions where an `[action-context]` ancestor exists.
|
|
75
71
|
*
|
|
76
72
|
* Use `dispatchAction` when triggering an action from code — e.g. after an
|
|
77
73
|
* async operation, from a service layer, or in tests. For DOM-driven actions,
|
|
78
|
-
* use the `on
|
|
74
|
+
* use the `on-<eventType>` HTML attribute with `setupActionDelegation`.
|
|
79
75
|
*
|
|
80
|
-
* @param
|
|
81
|
-
* @param actionPayload - The payload; type is enforced by `ActionRecord`.
|
|
76
|
+
* @param action - A full `Action<K>` object with at minimum `type` and `payload`.
|
|
82
77
|
*
|
|
83
78
|
* @example — with payload
|
|
84
79
|
* ```ts
|
|
85
80
|
* import {dispatchAction} from '@alwatr/action';
|
|
86
81
|
*
|
|
87
|
-
* dispatchAction('
|
|
88
|
-
* dispatchAction('
|
|
82
|
+
* dispatchAction({type: 'navigate', payload: '/dashboard'});
|
|
83
|
+
* dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});
|
|
89
84
|
* ```
|
|
90
85
|
*
|
|
91
|
-
* @example — void payload
|
|
86
|
+
* @example — void payload
|
|
92
87
|
* ```ts
|
|
93
|
-
* dispatchAction('logout');
|
|
88
|
+
* dispatchAction({type: 'logout', payload: undefined});
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @example — with context and meta
|
|
92
|
+
* ```ts
|
|
93
|
+
* dispatchAction({
|
|
94
|
+
* type: 'slider_change',
|
|
95
|
+
* payload: 75,
|
|
96
|
+
* context: 'volume_slider',
|
|
97
|
+
* meta: {traceId: 'abc-123'},
|
|
98
|
+
* });
|
|
94
99
|
* ```
|
|
95
100
|
*/
|
|
96
|
-
export declare function dispatchAction<K extends keyof ActionRecord>(
|
|
101
|
+
export declare function dispatchAction<K extends keyof ActionRecord>(action: Action<K>): void;
|
|
97
102
|
/**
|
|
98
|
-
* Registers a custom modifier that can be used in `on
|
|
103
|
+
* Registers a custom modifier that can be used in `on-<eventType>` attribute syntax.
|
|
99
104
|
*
|
|
100
|
-
* A modifier is a
|
|
101
|
-
* (e.g. `click
|
|
105
|
+
* A modifier is a comma-separated token placed after the `;` separator
|
|
106
|
+
* (e.g. `on-click="action-id; mymod"`). Its handler runs before the payload is
|
|
102
107
|
* resolved and the action is dispatched. Returning `false` cancels the dispatch.
|
|
103
108
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
109
|
+
* The handler also receives the **mutable** `action` object being built, so it
|
|
110
|
+
* can attach data to `action.meta` before the action reaches subscribers.
|
|
111
|
+
*
|
|
112
|
+
* Built-in modifiers (`prevent`, `validate`, `once`) are always available.
|
|
113
|
+
* This function lets you add domain-specific ones.
|
|
106
114
|
*
|
|
107
115
|
* Registering the same name twice logs an accident and overwrites the previous
|
|
108
116
|
* handler — avoid duplicate registrations in production code.
|
|
109
117
|
*
|
|
110
|
-
* @param name - The modifier token (lowercase, no
|
|
111
|
-
* @param handler - A `ModifierHandler` receiving `(event, element)`.
|
|
118
|
+
* @param name - The modifier token (lowercase, no special characters).
|
|
119
|
+
* @param handler - A `ModifierHandler` receiving `(event, element, action)`.
|
|
112
120
|
*
|
|
113
121
|
* @example — a `confirm` modifier that shows a browser dialog
|
|
114
122
|
* ```ts
|
|
@@ -117,20 +125,29 @@ export declare function dispatchAction<K extends keyof ActionRecord>(...args: Ac
|
|
|
117
125
|
* registerModifier('confirm', () => window.confirm('Are you sure?'));
|
|
118
126
|
* ```
|
|
119
127
|
* ```html
|
|
120
|
-
* <button on-
|
|
128
|
+
* <button on-click="delete_item:42; confirm">Delete</button>
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* @example — a `trace` modifier that stamps a trace ID into meta
|
|
132
|
+
* ```ts
|
|
133
|
+
* registerModifier('trace', (_event, _element, action) => {
|
|
134
|
+
* action.meta ??= {};
|
|
135
|
+
* action.meta['traceId'] = crypto.randomUUID();
|
|
136
|
+
* return true;
|
|
137
|
+
* });
|
|
121
138
|
* ```
|
|
122
139
|
*/
|
|
123
140
|
export declare function registerModifier(name: string, handler: ModifierHandler): void;
|
|
124
141
|
/**
|
|
125
|
-
* Registers a custom payload resolver that can be used in `on
|
|
142
|
+
* Registers a custom payload resolver that can be used in `on-<eventType>` attribute syntax.
|
|
126
143
|
*
|
|
127
|
-
* A payload resolver is a colon-
|
|
128
|
-
* (e.g. `click
|
|
129
|
-
* with
|
|
130
|
-
*
|
|
144
|
+
* A payload resolver is a colon-prefixed token in the attribute value
|
|
145
|
+
* (e.g. `on-click="action-id:$mytoken"`). Its function is called at dispatch time
|
|
146
|
+
* with the DOM event and the element. The return value becomes the `payload`
|
|
147
|
+
* field of the `Action` object passed to `onAction` subscribers.
|
|
131
148
|
*
|
|
132
|
-
* Built-in resolvers (`$value`, `$formdata`) are always available.
|
|
133
|
-
* lets you add domain-specific ones.
|
|
149
|
+
* Built-in resolvers (`$value`, `$formdata`, `$checked`) are always available.
|
|
150
|
+
* This function lets you add domain-specific ones.
|
|
134
151
|
*
|
|
135
152
|
* Registering the same name twice logs an accident and overwrites the previous
|
|
136
153
|
* resolver — avoid duplicate registrations in production code.
|
|
@@ -138,16 +155,16 @@ export declare function registerModifier(name: string, handler: ModifierHandler)
|
|
|
138
155
|
* @param name - The resolver token (should start with `$` by convention).
|
|
139
156
|
* @param resolver - A `PayloadResolver` receiving `(event, element)`.
|
|
140
157
|
*
|
|
141
|
-
* @example — a `$
|
|
158
|
+
* @example — a `$data-id` resolver that reads a data attribute
|
|
142
159
|
* ```ts
|
|
143
160
|
* import {registerPayloadResolver} from '@alwatr/action';
|
|
144
161
|
*
|
|
145
|
-
* registerPayloadResolver('$
|
|
146
|
-
* return (element as
|
|
162
|
+
* registerPayloadResolver('$data-id', (_event, element) => {
|
|
163
|
+
* return (element as HTMLElement).dataset.id ?? null;
|
|
147
164
|
* });
|
|
148
165
|
* ```
|
|
149
166
|
* ```html
|
|
150
|
-
* <
|
|
167
|
+
* <button on-click="select_item:$data-id" data-id="42">Select</button>
|
|
151
168
|
* ```
|
|
152
169
|
*/
|
|
153
170
|
export declare function registerPayloadResolver(name: string, resolver: PayloadResolver): void;
|
package/dist/method.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"method.d.ts","sourceRoot":"","sources":["../src/method.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"method.d.ts","sourceRoot":"","sources":["../src/method.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAGpD,OAAO,EAAoC,KAAK,eAAe,EAAE,KAAK,eAAe,EAAC,MAAM,eAAe,CAAC;AAC5G,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAGxC,YAAY,EAAC,eAAe,EAAE,eAAe,EAAC,CAAC;AAC/C,YAAY,EAAC,MAAM,EAAC,CAAC;AAIrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,YAAY,EACnD,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,GAC9C,eAAe,CAMjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAGpF;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAM7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAMrF"}
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,35 +1,47 @@
|
|
|
1
|
+
import type { Action } from './action.js';
|
|
1
2
|
/**
|
|
2
|
-
* A modifier handler used in `on
|
|
3
|
+
* A modifier handler used in `on-<eventType>` attribute syntax.
|
|
3
4
|
*
|
|
4
|
-
* Receives the triggering DOM `event
|
|
5
|
-
* `on
|
|
6
|
-
*
|
|
5
|
+
* Receives the triggering DOM `event`, the `element` that owns the
|
|
6
|
+
* `on-<eventType>` attribute, and the **mutable** `action` object being built.
|
|
7
|
+
* The handler may mutate `action.meta` to attach cross-cutting data (e.g. a
|
|
8
|
+
* trace ID, a timestamp, or an A/B flag) before the action reaches subscribers.
|
|
9
|
+
*
|
|
10
|
+
* Return `true` (or any truthy value) to allow the action to proceed, or
|
|
11
|
+
* `false` to cancel the dispatch entirely.
|
|
7
12
|
*
|
|
8
13
|
* Using explicit parameters instead of `this` binding makes handlers
|
|
9
14
|
* compatible with arrow functions and easier to test in isolation.
|
|
10
15
|
*
|
|
11
|
-
* @example
|
|
16
|
+
* @example — a modifier that stamps a timestamp into meta
|
|
17
|
+
* ```ts
|
|
18
|
+
* const timestampHandler: ModifierHandler = (_event, _element, action) => {
|
|
19
|
+
* action.meta ??= {};
|
|
20
|
+
* action.meta['timestamp'] = Date.now();
|
|
21
|
+
* return true;
|
|
22
|
+
* };
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example — a modifier that cancels dispatch when the element is disabled
|
|
12
26
|
* ```ts
|
|
13
|
-
* // A modifier that only allows the action when the element is not disabled
|
|
14
27
|
* const notDisabledHandler: ModifierHandler = (_event, element) => {
|
|
15
28
|
* return !(element as HTMLButtonElement).disabled;
|
|
16
29
|
* };
|
|
17
30
|
* ```
|
|
18
31
|
*/
|
|
19
|
-
export type ModifierHandler = (event: Event, element: HTMLElement) => boolean;
|
|
32
|
+
export type ModifierHandler = (event: Event, element: HTMLElement, action: Action) => boolean;
|
|
20
33
|
/**
|
|
21
|
-
* A payload resolver used in `on
|
|
34
|
+
* A payload resolver used in `on-<eventType>` attribute syntax.
|
|
22
35
|
*
|
|
23
36
|
* Receives the triggering DOM `event` and the `element` that owns the
|
|
24
|
-
* `on
|
|
25
|
-
*
|
|
37
|
+
* `on-<eventType>` attribute. The return value becomes the `payload` field of
|
|
38
|
+
* the `Action` object passed to `onAction` subscribers.
|
|
26
39
|
*
|
|
27
40
|
* Using explicit parameters instead of `this` binding makes resolvers
|
|
28
41
|
* compatible with arrow functions and easier to test in isolation.
|
|
29
42
|
*
|
|
30
|
-
* @example
|
|
43
|
+
* @example — a resolver that returns the element's dataset id
|
|
31
44
|
* ```ts
|
|
32
|
-
* // A resolver that returns the element's dataset id
|
|
33
45
|
* const dataIdResolver: PayloadResolver = (_event, element) => {
|
|
34
46
|
* return (element as HTMLElement).dataset.id ?? null;
|
|
35
47
|
* };
|
|
@@ -39,8 +51,8 @@ export type PayloadResolver = (event: Event, element: HTMLElement) => unknown;
|
|
|
39
51
|
/**
|
|
40
52
|
* Registry of all named modifier handlers.
|
|
41
53
|
*
|
|
42
|
-
* Keys are modifier names used in the `on
|
|
43
|
-
* (e.g. `click
|
|
54
|
+
* Keys are modifier names used in the `on-<eventType>` attribute syntax
|
|
55
|
+
* (e.g. `on-click="action-id; prevent"`). Values are `ModifierHandler` functions.
|
|
44
56
|
* Populated at module load with built-in modifiers; extended at runtime via
|
|
45
57
|
* `registerModifier`.
|
|
46
58
|
*
|
|
@@ -50,8 +62,8 @@ export declare const modifierRegistry: Map<string, ModifierHandler>;
|
|
|
50
62
|
/**
|
|
51
63
|
* Registry of all named payload resolvers.
|
|
52
64
|
*
|
|
53
|
-
* Keys are resolver tokens used in the `on
|
|
54
|
-
* (e.g. `
|
|
65
|
+
* Keys are resolver tokens used in the `on-<eventType>` attribute syntax
|
|
66
|
+
* (e.g. `on-input="search_query:$value"`). Values are `PayloadResolver` functions.
|
|
55
67
|
* Populated at module load with built-in resolvers; extended at runtime via
|
|
56
68
|
* `registerPayloadResolver`.
|
|
57
69
|
*
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAIxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;AAE9F;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;AAI9E;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,8BAAqC,CAAC;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,8BAAqC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alwatr/action",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.17.0",
|
|
4
4
|
"description": "Declarative DOM action-dispatch — bridge HTML attributes to typed signal handlers.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
},
|
|
22
22
|
"sideEffects": false,
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@alwatr/logger": "9.
|
|
25
|
-
"@alwatr/signal": "9.
|
|
24
|
+
"@alwatr/logger": "9.16.0",
|
|
25
|
+
"@alwatr/signal": "9.16.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@alwatr/nano-build": "9.14.0",
|
|
29
|
-
"@alwatr/standard": "9.
|
|
29
|
+
"@alwatr/standard": "9.16.0",
|
|
30
30
|
"@alwatr/type-helper": "9.14.0",
|
|
31
31
|
"typescript": "^6.0.3"
|
|
32
32
|
},
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
"vanilla-js",
|
|
80
80
|
"web-development"
|
|
81
81
|
],
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "782563375f55c55d29719cbcfebaca251d69ddcd"
|
|
83
83
|
}
|
package/src/action-record.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* // In your package: src/action-record.ts
|
|
14
14
|
* declare module '@alwatr/action' {
|
|
15
15
|
* interface ActionRecord {
|
|
16
|
-
* '
|
|
17
|
-
* '
|
|
16
|
+
* 'open_drawer': string;
|
|
17
|
+
* 'add_to_cart': {productId: number; qty: number};
|
|
18
18
|
* 'logout': void;
|
|
19
19
|
* }
|
|
20
20
|
* }
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
* // pkg/my-feature/src/action-record.ts
|
|
44
44
|
* declare module '@alwatr/action' {
|
|
45
45
|
* interface ActionRecord {
|
|
46
|
-
* '
|
|
47
|
-
* '
|
|
46
|
+
* 'open_drawer': string;
|
|
47
|
+
* 'add_to_cart': {productId: number; qty: number};
|
|
48
48
|
* 'logout': void;
|
|
49
49
|
* }
|
|
50
50
|
* }
|
package/src/action.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file action.ts
|
|
3
|
+
*
|
|
4
|
+
* Defines the Alwatr Flux Standard Action (AFSA) — the single, unified data
|
|
5
|
+
* structure that flows through the entire action bus for both dispatch and
|
|
6
|
+
* subscription.
|
|
7
|
+
*
|
|
8
|
+
* ## Why a single Action object?
|
|
9
|
+
*
|
|
10
|
+
* Previously, `dispatchAction(id, payload)` and `onAction(id, handler(payload))`
|
|
11
|
+
* treated the action as two separate concerns: an identifier and a raw payload.
|
|
12
|
+
* This made it impossible to carry cross-cutting metadata (context, trace IDs,
|
|
13
|
+
* timestamps) without breaking every call site.
|
|
14
|
+
*
|
|
15
|
+
* AFSA wraps everything into one object:
|
|
16
|
+
* - `type` — the action identifier (replaces the first positional argument)
|
|
17
|
+
* - `payload` — the business data (replaces the second positional argument)
|
|
18
|
+
* - `context` — the DOM context extracted from the nearest `[action-context]`
|
|
19
|
+
* ancestor at delegation time; `undefined` for programmatic dispatches
|
|
20
|
+
* - `meta` — open-ended bag for future cross-cutting concerns (trace IDs,
|
|
21
|
+
* timestamps, A/B flags, etc.) without breaking the typed API
|
|
22
|
+
*
|
|
23
|
+
* Modifiers in the delegation pipeline can also mutate `meta` before the action
|
|
24
|
+
* reaches subscribers — e.g. a `trace` modifier could stamp a request ID.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type {DictionaryOpt} from '@alwatr/type-helper';
|
|
28
|
+
import type {ActionRecord} from './action-record.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Alwatr Flux Standard Action (AFSA).
|
|
32
|
+
*
|
|
33
|
+
* The single, canonical object passed to every `dispatchAction` call and
|
|
34
|
+
* received by every `onAction` handler. Keeping all action data in one
|
|
35
|
+
* structure makes the bus extensible without breaking existing call sites.
|
|
36
|
+
*
|
|
37
|
+
* @template K - A key of `ActionRecord`; constrains `type` and `payload` together.
|
|
38
|
+
*
|
|
39
|
+
* @example — dispatching
|
|
40
|
+
* ```ts
|
|
41
|
+
* dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example — subscribing
|
|
45
|
+
* ```ts
|
|
46
|
+
* onAction('add_to_cart', (action) => {
|
|
47
|
+
* console.log(action.type); // 'add_to_cart'
|
|
48
|
+
* console.log(action.payload); // {productId: 42, qty: 1}
|
|
49
|
+
* console.log(action.context); // e.g. 'product-list' (from DOM) or undefined
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export interface Action<K extends keyof ActionRecord = keyof ActionRecord> {
|
|
54
|
+
/**
|
|
55
|
+
* Unique action identifier — must be a key of `ActionRecord`.
|
|
56
|
+
*
|
|
57
|
+
* @example 'cart:add-item', 'open_drawer', 'logout'
|
|
58
|
+
*/
|
|
59
|
+
readonly type: K;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The DOM context in which the action was triggered.
|
|
63
|
+
*
|
|
64
|
+
* Extracted at delegation time from the nearest ancestor element that carries
|
|
65
|
+
* an `action-context` attribute. Useful for scoping the same action type to
|
|
66
|
+
* different UI regions (e.g. two sliders on the same page both dispatching
|
|
67
|
+
* `'slider:change'` but with different context values).
|
|
68
|
+
*
|
|
69
|
+
* `undefined` when the action is dispatched programmatically (no DOM involved)
|
|
70
|
+
* or when no `[action-context]` ancestor exists.
|
|
71
|
+
*
|
|
72
|
+
* @example 'slider-123', 'product-list', 'checkout-form'
|
|
73
|
+
*/
|
|
74
|
+
readonly context?: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The pure business payload carried by this action.
|
|
78
|
+
*
|
|
79
|
+
* Type is inferred from `ActionRecord[K]` — the compiler enforces the correct
|
|
80
|
+
* shape at every call site. No manual generic annotation is needed.
|
|
81
|
+
*/
|
|
82
|
+
readonly payload: ActionRecord[K];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Open-ended metadata bag for cross-cutting concerns.
|
|
86
|
+
*
|
|
87
|
+
* Intentionally untyped so that future infrastructure layers (tracing,
|
|
88
|
+
* analytics, A/B testing) can attach data without touching the typed API.
|
|
89
|
+
* Modifiers in the delegation pipeline may also write to `meta` before the
|
|
90
|
+
* action reaches subscribers.
|
|
91
|
+
*
|
|
92
|
+
* Treat values here as `unknown` and validate before use.
|
|
93
|
+
*
|
|
94
|
+
* @example {traceId: 'abc-123', timestamp: Date.now()}
|
|
95
|
+
*/
|
|
96
|
+
meta?: DictionaryOpt<unknown>;
|
|
97
|
+
}
|