@alwatr/action 9.26.0 → 9.28.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 +103 -208
- package/dist/action-service.d.ts +178 -0
- package/dist/action-service.d.ts.map +1 -0
- package/dist/main.d.ts +92 -69
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +5 -7
- package/package.json +2 -2
- package/src/action-service.ts +397 -0
- package/src/main.ts +116 -69
- package/dist/delegate.d.ts +0 -126
- package/dist/delegate.d.ts.map +0 -1
- package/dist/lib_.d.ts +0 -24
- package/dist/lib_.d.ts.map +0 -1
- package/dist/method.d.ts +0 -169
- package/dist/method.d.ts.map +0 -1
- package/dist/registry_.d.ts +0 -24
- package/dist/registry_.d.ts.map +0 -1
- package/src/delegate.ts +0 -359
- package/src/lib_.ts +0 -27
- package/src/method.ts +0 -219
- package/src/registry_.ts +0 -109
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
**Declarative DOM action-dispatch — the Action layer for Unidirectional Data Flow.**
|
|
4
4
|
|
|
5
|
-
`@alwatr/action` bridges HTML `on-<eventType>` attributes to typed signal handlers using **global event delegation**.
|
|
5
|
+
`@alwatr/action` bridges HTML `on-<eventType>` attributes to typed signal handlers using **global event delegation**. It is implemented as a factory service (`ActionService`) with all state and listeners encapsulated. A pre-instantiated singleton `actionService` is exported for immediate use.
|
|
6
|
+
|
|
7
|
+
One listener on `document.body` covers every element on the page — including elements added dynamically after bootstrap — with O(1) initialization cost regardless of how many elements exist.
|
|
6
8
|
|
|
7
9
|
---
|
|
8
10
|
|
|
@@ -40,7 +42,7 @@ document.body capture listener (1 listener per event type)
|
|
|
40
42
|
parse attribute → 'ui_add_to_cart:42'
|
|
41
43
|
run modifiers → none
|
|
42
44
|
resolve payload → '42'
|
|
43
|
-
|
|
45
|
+
actionService.dispatch({
|
|
44
46
|
type: 'ui_add_to_cart',
|
|
45
47
|
payload: '42',
|
|
46
48
|
context: 'product-list',
|
|
@@ -99,15 +101,24 @@ declare module '@alwatr/action' {
|
|
|
99
101
|
|
|
100
102
|
### 2. Bootstrap delegation
|
|
101
103
|
|
|
104
|
+
Use the pre-instantiated singleton `actionService`:
|
|
105
|
+
|
|
102
106
|
```ts
|
|
103
|
-
import {
|
|
107
|
+
import {actionService} from '@alwatr/action';
|
|
104
108
|
import './action-record.js'; // ensure the declaration is loaded
|
|
105
109
|
|
|
106
|
-
|
|
110
|
+
// Initialize global event delegation
|
|
111
|
+
actionService.setupDelegation();
|
|
107
112
|
|
|
108
113
|
// The handler receives the full Action<K> object — payload, context, and meta in one place.
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
actionService.on('ui_open_drawer', (action) => openDrawer(action.payload)); // action.payload: string
|
|
115
|
+
|
|
116
|
+
// Subscribe to multiple action types using an array
|
|
117
|
+
actionService.on(['ui_open_drawer', 'ui_close_drawer'], (action) => {
|
|
118
|
+
console.log(action.type, action.payload);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
actionService.on('ui_add_to_cart', (action) => {
|
|
111
122
|
cartService.add(action.payload.productId, action.payload.qty); // fully typed
|
|
112
123
|
console.log(action.context); // e.g. 'product-list' — from nearest [action-context] ancestor
|
|
113
124
|
});
|
|
@@ -167,7 +178,7 @@ Wrap a group of elements in an `[action-context]` container to scope their actio
|
|
|
167
178
|
```
|
|
168
179
|
|
|
169
180
|
```ts
|
|
170
|
-
|
|
181
|
+
actionService.on('ui_slider_change', (action) => {
|
|
171
182
|
if (action.context === 'volume') audioService.setVolume(Number(action.payload));
|
|
172
183
|
if (action.context === 'brightness') displayService.setBrightness(Number(action.payload));
|
|
173
184
|
});
|
|
@@ -177,20 +188,20 @@ Context is `undefined` when no `[action-context]` ancestor exists — programmat
|
|
|
177
188
|
|
|
178
189
|
### 5. Programmatic dispatch
|
|
179
190
|
|
|
180
|
-
Use `
|
|
191
|
+
Use `actionService.dispatch` for code-originated actions (after async operations, from services, etc.).
|
|
181
192
|
These actions should **not** use the `ui_` prefix — that prefix is reserved for DOM-originated actions.
|
|
182
193
|
|
|
183
194
|
```ts
|
|
184
|
-
import {
|
|
195
|
+
import {actionService} from '@alwatr/action';
|
|
185
196
|
|
|
186
197
|
// Code-originated actions — no 'ui_' prefix
|
|
187
198
|
await uploadFile(file);
|
|
188
|
-
|
|
199
|
+
actionService.dispatch({type: 'upload_complete', payload: fileId});
|
|
189
200
|
|
|
190
|
-
|
|
201
|
+
actionService.dispatch({type: 'navigate', payload: '/dashboard'});
|
|
191
202
|
|
|
192
203
|
// With explicit context and meta
|
|
193
|
-
|
|
204
|
+
actionService.dispatch({
|
|
194
205
|
type: 'slider_change',
|
|
195
206
|
payload: 75,
|
|
196
207
|
context: 'volume',
|
|
@@ -260,10 +271,10 @@ interface Action<K extends keyof ActionRecord> {
|
|
|
260
271
|
Modifiers in the delegation pipeline receive the mutable `action` object and can enrich `meta` before the action reaches subscribers:
|
|
261
272
|
|
|
262
273
|
```ts
|
|
263
|
-
import {
|
|
274
|
+
import {actionService} from '@alwatr/action';
|
|
264
275
|
|
|
265
276
|
// A modifier that stamps a trace ID into meta
|
|
266
|
-
registerModifier('trace', (_event, _element, action) => {
|
|
277
|
+
actionService.registerModifier('trace', (_event, _element, action) => {
|
|
267
278
|
action.meta ??= {};
|
|
268
279
|
action.meta['traceId'] = crypto.randomUUID();
|
|
269
280
|
return true;
|
|
@@ -275,7 +286,7 @@ registerModifier('trace', (_event, _element, action) => {
|
|
|
275
286
|
```
|
|
276
287
|
|
|
277
288
|
```ts
|
|
278
|
-
|
|
289
|
+
actionService.on('ui_submit_order', (action) => {
|
|
279
290
|
console.log(action.meta?.['traceId']); // e.g. 'a1b2-c3d4-…'
|
|
280
291
|
});
|
|
281
292
|
```
|
|
@@ -284,182 +295,101 @@ onAction('ui_submit_order', (action) => {
|
|
|
284
295
|
|
|
285
296
|
## API Reference
|
|
286
297
|
|
|
287
|
-
### `
|
|
298
|
+
### `actionService` (Singleton instance of `ActionService`)
|
|
288
299
|
|
|
289
|
-
The
|
|
300
|
+
The pre-instantiated factory service exported for standard usage.
|
|
290
301
|
|
|
291
|
-
|
|
292
|
-
import type {Action} from '@alwatr/action';
|
|
293
|
-
|
|
294
|
-
// Reading fields in a handler
|
|
295
|
-
onAction('ui_add_to_cart', (action: Action<'ui_add_to_cart'>) => {
|
|
296
|
-
console.log(action.type); // 'ui_add_to_cart'
|
|
297
|
-
console.log(action.payload); // {productId: number; qty: number}
|
|
298
|
-
console.log(action.context); // string | undefined
|
|
299
|
-
console.log(action.meta); // Record<string, unknown> | undefined
|
|
300
|
-
});
|
|
301
|
-
```
|
|
302
|
+
#### `actionService.on(type, handler)`
|
|
302
303
|
|
|
303
|
-
|
|
304
|
+
Subscribes to a single action or an array of actions. O(1) routing via `ChannelSignal`.
|
|
304
305
|
|
|
305
|
-
|
|
306
|
+
```ts
|
|
307
|
+
actionService.on<K extends keyof ActionRecord>(
|
|
308
|
+
type: K | K[],
|
|
309
|
+
handler: (action: Action<K>) => Awaitable<void>
|
|
310
|
+
): SubscribeResult;
|
|
306
311
|
|
|
307
|
-
|
|
312
|
+
// Usage:
|
|
313
|
+
// Subscribe to a single action
|
|
314
|
+
actionService.on('ui_open_drawer', (action) => { ... });
|
|
308
315
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
interface ActionRecord {
|
|
312
|
-
ui_open_drawer: string;
|
|
313
|
-
ui_logout: void;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
+
// Subscribe to multiple action types
|
|
317
|
+
actionService.on(['ui_open_drawer', 'ui_close_drawer'], (action) => { ... });
|
|
316
318
|
```
|
|
317
319
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
### `setupActionDelegation(eventTypes?)`
|
|
320
|
+
#### `actionService.dispatch(action)`
|
|
321
321
|
|
|
322
|
-
|
|
322
|
+
Dispatches an action to all subscribers.
|
|
323
323
|
|
|
324
324
|
```ts
|
|
325
|
-
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
Defaults to `DEFAULT_DELEGATED_EVENTS`: `['click', 'submit', 'input', 'change']`.
|
|
325
|
+
actionService.dispatch<K extends keyof ActionRecord>(action: DispatchParam<K>): void;
|
|
329
326
|
|
|
330
|
-
|
|
331
|
-
|
|
327
|
+
// Usage:
|
|
328
|
+
// Dispatch a typed action (payload is required)
|
|
329
|
+
actionService.dispatch({type: 'upload_complete', payload: 'file-123'});
|
|
332
330
|
|
|
333
|
-
|
|
331
|
+
// Dispatch a void action (payload can be omitted)
|
|
332
|
+
actionService.dispatch({type: 'auth_expired'});
|
|
334
333
|
```
|
|
335
334
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
### `teardownActionDelegation()`
|
|
335
|
+
#### `actionService.setupDelegation(eventTypes?)`
|
|
339
336
|
|
|
340
|
-
|
|
337
|
+
Registers global capture listeners on `document.body`. Defaults to `['click', 'submit', 'input', 'change']`.
|
|
341
338
|
|
|
342
339
|
```ts
|
|
343
|
-
|
|
340
|
+
actionService.setupDelegation(eventTypes?: readonly string[]): void;
|
|
344
341
|
```
|
|
345
342
|
|
|
346
|
-
|
|
343
|
+
#### `actionService.teardownDelegation()`
|
|
347
344
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
Subscribes to a single named action or an array of actions. O(1) routing via `ChannelSignal`. The handler receives the full `Action<K>` object.
|
|
345
|
+
Unregisters all delegation listeners and clears the descriptor cache.
|
|
351
346
|
|
|
352
347
|
```ts
|
|
353
|
-
|
|
354
|
-
type: K | K[],
|
|
355
|
-
handler: (action: Action<K>) => Awaitable<void>,
|
|
356
|
-
): SubscribeResult;
|
|
348
|
+
actionService.teardownDelegation(): void;
|
|
357
349
|
```
|
|
358
350
|
|
|
359
|
-
|
|
360
|
-
// Subscribe to a single action
|
|
361
|
-
const sub = onAction('ui_open_drawer', (action) => {
|
|
362
|
-
openDrawer(action.payload); // payload: string
|
|
363
|
-
console.log(action.context); // e.g. 'sidebar' or undefined
|
|
364
|
-
});
|
|
351
|
+
#### `actionService.registerModifier(name, handler)`
|
|
365
352
|
|
|
366
|
-
|
|
367
|
-
const multiSub = onAction(['ui_increment', 'ui_decrement'], (action) => {
|
|
368
|
-
console.log('Action triggered:', action.type);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
sub.unsubscribe(); // prevent memory leaks
|
|
372
|
-
multiSub.unsubscribe();
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
---
|
|
376
|
-
|
|
377
|
-
### `dispatchAction(action)`
|
|
378
|
-
|
|
379
|
-
Dispatches a named action. Payload type is enforced by `ActionRecord`.
|
|
353
|
+
Registers custom modifiers.
|
|
380
354
|
|
|
381
355
|
```ts
|
|
382
|
-
|
|
356
|
+
actionService.registerModifier(name: string, handler: ModifierHandler): void;
|
|
383
357
|
```
|
|
384
358
|
|
|
385
|
-
|
|
386
|
-
// With payload (code-originated — no 'ui_' prefix)
|
|
387
|
-
dispatchAction({type: 'navigate', payload: 'settings'});
|
|
359
|
+
#### `actionService.registerPayloadResolver(name, resolver)`
|
|
388
360
|
|
|
389
|
-
|
|
390
|
-
dispatchAction({type: 'auth_expired', payload: undefined});
|
|
361
|
+
Registers custom payload resolvers.
|
|
391
362
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
type: 'navigate',
|
|
395
|
-
payload: 'settings',
|
|
396
|
-
context: 'header',
|
|
397
|
-
meta: {triggeredBy: 'keyboard'},
|
|
398
|
-
});
|
|
363
|
+
```ts
|
|
364
|
+
actionService.registerPayloadResolver(name: string, resolver: PayloadResolver): void;
|
|
399
365
|
```
|
|
400
366
|
|
|
401
367
|
---
|
|
402
368
|
|
|
403
|
-
### `
|
|
404
|
-
|
|
405
|
-
Registers a custom modifier. Return `false` to cancel the dispatch.
|
|
369
|
+
### `ActionService` (Class)
|
|
406
370
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
The handler receives the mutable `action` object and may write to `action.meta`.
|
|
371
|
+
The class constructor, allowing creation of independent instances of the action bus and delegation pipeline if needed (e.g. in multi-app or test micro-environments).
|
|
410
372
|
|
|
411
373
|
```ts
|
|
412
|
-
import {
|
|
413
|
-
|
|
414
|
-
registerModifier('not_disabled', (_event, element) => {
|
|
415
|
-
return !(element as HTMLButtonElement).disabled;
|
|
416
|
-
});
|
|
374
|
+
import {ActionService} from '@alwatr/action';
|
|
417
375
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
action.meta ??= {};
|
|
421
|
-
action.meta['ts'] = Date.now();
|
|
422
|
-
return true;
|
|
423
|
-
});
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
```html
|
|
427
|
-
<button
|
|
428
|
-
on-click="ui_select_item:$data_id; not_disabled,timestamp"
|
|
429
|
-
data-id="42"
|
|
430
|
-
>
|
|
431
|
-
Select
|
|
432
|
-
</button>
|
|
376
|
+
const myService = new ActionService();
|
|
377
|
+
myService.setupDelegation(['click']);
|
|
433
378
|
```
|
|
434
379
|
|
|
435
380
|
---
|
|
436
381
|
|
|
437
|
-
###
|
|
382
|
+
### Deprecated Global Functions (Backwards Compatibility)
|
|
438
383
|
|
|
439
|
-
|
|
384
|
+
For backwards compatibility with previous versions, the following wrapper functions are exported. They delegate directly to the `actionService` singleton instance and are marked as **deprecated**.
|
|
440
385
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
registerPayloadResolver(
|
|
447
|
-
|
|
448
|
-
});
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
```html
|
|
452
|
-
<input
|
|
453
|
-
type="checkbox"
|
|
454
|
-
on-change="ui_toggle_feature:$checked"
|
|
455
|
-
/>
|
|
456
|
-
<li
|
|
457
|
-
on-click="ui_select_item:$data_id"
|
|
458
|
-
data-id="42"
|
|
459
|
-
>
|
|
460
|
-
Item
|
|
461
|
-
</li>
|
|
462
|
-
```
|
|
386
|
+
- `onAction(type, handler)` (deprecated wrapper for `actionService.on`)
|
|
387
|
+
- `dispatchAction(action)` (deprecated wrapper for `actionService.dispatch`)
|
|
388
|
+
- `setupActionDelegation(eventTypes?)` (deprecated wrapper for `actionService.setupDelegation`)
|
|
389
|
+
- `teardownActionDelegation()` (deprecated wrapper for `actionService.teardownDelegation`)
|
|
390
|
+
- `registerModifier(name, handler)` (deprecated wrapper for `actionService.registerModifier`)
|
|
391
|
+
- `registerPayloadResolver(name, resolver)` (deprecated wrapper for `actionService.registerPayloadResolver`)
|
|
392
|
+
- `DEFAULT_DELEGATED_EVENTS` (constant mapped to `ActionService.DEFAULT_DELEGATED_EVENTS`)
|
|
463
393
|
|
|
464
394
|
---
|
|
465
395
|
|
|
@@ -472,8 +402,8 @@ registerPayloadResolver('$data_id', (_event, element) => {
|
|
|
472
402
|
│ <button on-click="ui_add_to_cart:42">Add</button> │
|
|
473
403
|
│ </section> │
|
|
474
404
|
└─────────────────────────┬──────────────────────────────────┘
|
|
475
|
-
|
|
476
|
-
|
|
405
|
+
│ DOM event bubbles to body
|
|
406
|
+
▼
|
|
477
407
|
┌────────────────────────────────────────────────────────────┐
|
|
478
408
|
│ Action Layer (@alwatr/action) │
|
|
479
409
|
│ document.body capture listener (1 per event type) │
|
|
@@ -481,26 +411,26 @@ registerPayloadResolver('$data_id', (_event, element) => {
|
|
|
481
411
|
│ → closest('[action-context]') → context = 'cart' │
|
|
482
412
|
│ → run modifiers (may enrich action.meta) │
|
|
483
413
|
│ → resolve payload → '42' │
|
|
484
|
-
│ → dispatch
|
|
414
|
+
│ → actionService.dispatch(Action) [O(1)] │
|
|
485
415
|
└─────────────────────────┬──────────────────────────────────┘
|
|
486
|
-
|
|
487
|
-
|
|
416
|
+
│ O(1) routing via ChannelSignal
|
|
417
|
+
▼
|
|
488
418
|
┌────────────────────────────────────────────────────────────┐
|
|
489
419
|
│ Business Logic Layer │
|
|
490
|
-
│
|
|
420
|
+
│ actionService.on('ui_add_to_cart', (action) => { │
|
|
491
421
|
│ cartService.add(action.payload); │
|
|
492
422
|
│ // action.context === 'cart' │
|
|
493
423
|
│ }) │
|
|
494
424
|
└─────────────────────────┬──────────────────────────────────┘
|
|
495
|
-
|
|
496
|
-
|
|
425
|
+
│ state update
|
|
426
|
+
▼
|
|
497
427
|
┌────────────────────────────────────────────────────────────┐
|
|
498
428
|
│ State Layer (@alwatr/signal) │
|
|
499
429
|
│ cartSignal.set(newCartState) │
|
|
500
430
|
└─────────────────────────┬──────────────────────────────────┘
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
431
|
+
│ state flows down to UI
|
|
432
|
+
▼
|
|
433
|
+
UI re-renders
|
|
504
434
|
```
|
|
505
435
|
|
|
506
436
|
---
|
|
@@ -555,60 +485,25 @@ onAction('ui_add_to_cart', (action) => {
|
|
|
555
485
|
});
|
|
556
486
|
```
|
|
557
487
|
|
|
558
|
-
###
|
|
559
|
-
|
|
560
|
-
Modifier handlers now receive a third `action` argument for `meta` enrichment.
|
|
561
|
-
|
|
562
|
-
**Before:**
|
|
563
|
-
|
|
564
|
-
```ts
|
|
565
|
-
registerModifier('not_disabled', (_event, element) => {
|
|
566
|
-
return !(element as HTMLButtonElement).disabled;
|
|
567
|
-
});
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
**After (backward-compatible — third arg is optional to use):**
|
|
488
|
+
### Automated AI Migration Prompt
|
|
571
489
|
|
|
572
|
-
|
|
573
|
-
registerModifier('not_disabled', (_event, element, _action) => {
|
|
574
|
-
return !(element as HTMLButtonElement).disabled;
|
|
575
|
-
});
|
|
576
|
-
```
|
|
490
|
+
If you are using an AI coding assistant (like Cursor, Gemini, Copilot, or Antigravity) to migrate your files to the new `actionService` API, you can use the following prompt to automate the refactoring:
|
|
577
491
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
The event type is now encoded in the **attribute name** instead of the value, and modifiers are listed after a semicolon instead of dot-chained before the arrow.
|
|
581
|
-
|
|
582
|
-
**Before:**
|
|
492
|
+
```text
|
|
493
|
+
Refactor this file to migrate from the deprecated global `@alwatr/action` (or `@alwatr/flux`) functions to the new `actionService` singleton API.
|
|
583
494
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
495
|
+
Follow these rules:
|
|
496
|
+
1. Replace imports of `onAction`, `dispatchAction`, `setupActionDelegation`, `teardownActionDelegation`, `registerModifier`, or `registerPayloadResolver` from `@alwatr/action` (or `@alwatr/flux`) with `actionService`.
|
|
497
|
+
2. Convert all calls:
|
|
498
|
+
- `onAction(...)` ➔ `actionService.on(...)`
|
|
499
|
+
- `dispatchAction(...)` ➔ `actionService.dispatch(...)`
|
|
500
|
+
- `setupActionDelegation(...)` ➔ `actionService.setupDelegation(...)`
|
|
501
|
+
- `teardownActionDelegation(...)` ➔ `actionService.teardownDelegation(...)`
|
|
502
|
+
- `registerModifier(...)` ➔ `actionService.registerModifier(...)`
|
|
503
|
+
- `registerPayloadResolver(...)` ➔ `actionService.registerPayloadResolver(...)`
|
|
504
|
+
3. Maintain exact type safety, callback parameter types, and business logic.
|
|
593
505
|
```
|
|
594
506
|
|
|
595
|
-
**After:**
|
|
596
|
-
|
|
597
|
-
```html
|
|
598
|
-
<button on-click="ui_open_drawer:main">Open</button>
|
|
599
|
-
<form
|
|
600
|
-
on-submit="ui_submit_form:$formdata; prevent,validate"
|
|
601
|
-
novalidate
|
|
602
|
-
>
|
|
603
|
-
…
|
|
604
|
-
</form>
|
|
605
|
-
<button on-click="ui_welcome_dismissed; once">Got it</button>
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
### `page-ready` moved to `@alwatr/page-ready`
|
|
609
|
-
|
|
610
|
-
`dispatchPageId` / `onPageReady` are no longer part of this package.
|
|
611
|
-
|
|
612
507
|
---
|
|
613
508
|
|
|
614
509
|
## 🌊 Part of Alwatr Flux
|
|
@@ -620,7 +515,7 @@ View (HTML on-<event> attributes + action-context)
|
|
|
620
515
|
↓
|
|
621
516
|
Action Layer (@alwatr/action) — global delegation, O(1) routing, AFSA objects
|
|
622
517
|
↓
|
|
623
|
-
Controller (business logic via
|
|
518
|
+
Controller (business logic via actionService.on — receives full Action object)
|
|
624
519
|
↓
|
|
625
520
|
State Layer (@alwatr/signal) — fine-grained reactivity
|
|
626
521
|
↓
|
|
@@ -633,10 +528,10 @@ View (re-render only affected nodes)
|
|
|
633
528
|
|
|
634
529
|
```typescript
|
|
635
530
|
// Use @alwatr/flux for the complete architecture
|
|
636
|
-
import {
|
|
531
|
+
import {actionService, createStateSignal} from '@alwatr/flux';
|
|
637
532
|
|
|
638
533
|
// Or use @alwatr/action standalone for just the action bus
|
|
639
|
-
import {
|
|
534
|
+
import {actionService} from '@alwatr/action';
|
|
640
535
|
```
|
|
641
536
|
|
|
642
537
|
→ [View the complete Flux documentation](https://github.com/Alwatr/alwatr/tree/next/pkg/flux)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { SubscribeResult } from '@alwatr/signal';
|
|
2
|
+
import type { Awaitable } from '@alwatr/type-helper';
|
|
3
|
+
import type { Action, ActionRecord, DispatchParam, ModifierHandler, PayloadResolver } from './type.js';
|
|
4
|
+
/**
|
|
5
|
+
* Parsed representation of an action attribute descriptor.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
interface ActionDescriptor {
|
|
9
|
+
readonly modifiers: ReadonlySet<string>;
|
|
10
|
+
readonly actionId: string;
|
|
11
|
+
readonly payload: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Service to manage declarative DOM actions, programmatic dispatch,
|
|
15
|
+
* modifiers, payload resolvers, and global event delegation.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import {ActionService} from '@alwatr/action';
|
|
20
|
+
*
|
|
21
|
+
* const customActionService = new ActionService();
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare class ActionService {
|
|
25
|
+
/**
|
|
26
|
+
* Default DOM event types that cover the vast majority of interactive elements.
|
|
27
|
+
*/
|
|
28
|
+
static readonly DEFAULT_DELEGATED_EVENTS: readonly string[];
|
|
29
|
+
protected readonly logger_: import("@alwatr/logger").AlwatrLogger;
|
|
30
|
+
/**
|
|
31
|
+
* Internal ChannelSignal used for routing dispatched actions.
|
|
32
|
+
* @protected
|
|
33
|
+
*/
|
|
34
|
+
protected readonly internalChannel_: import("@alwatr/signal").ChannelSignal<Record<string, Action<never>>>;
|
|
35
|
+
/**
|
|
36
|
+
* Registry mapping custom modifiers to their handlers.
|
|
37
|
+
* @protected
|
|
38
|
+
*/
|
|
39
|
+
protected readonly modifierRegistry_: Map<string, ModifierHandler>;
|
|
40
|
+
/**
|
|
41
|
+
* Registry mapping custom payload resolvers to their functions.
|
|
42
|
+
* @protected
|
|
43
|
+
*/
|
|
44
|
+
protected readonly payloadRegistry_: Map<string, PayloadResolver>;
|
|
45
|
+
/**
|
|
46
|
+
* Cache of parsed action descriptors to prevent redundant regex evaluation.
|
|
47
|
+
* @protected
|
|
48
|
+
*/
|
|
49
|
+
protected readonly descriptorCache_: Map<string, ActionDescriptor | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Tracked event types currently delegated to `document.body`.
|
|
52
|
+
* @protected
|
|
53
|
+
*/
|
|
54
|
+
protected readonly delegatedEventTypes_: Set<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Bound delegation handler for add/removeEventListener.
|
|
57
|
+
* @private
|
|
58
|
+
*/
|
|
59
|
+
private readonly handleDelegatedEventBound__;
|
|
60
|
+
constructor();
|
|
61
|
+
/**
|
|
62
|
+
* Subscribes to a named action dispatched anywhere in the application.
|
|
63
|
+
*
|
|
64
|
+
* @template K - A key of ActionRecord.
|
|
65
|
+
* @param type - Action type or array of action types to subscribe to.
|
|
66
|
+
* @param handler - Callback invoked with the full Action object.
|
|
67
|
+
* @returns SubscribeResult containing an `unsubscribe` method.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* // Subscribe to a single action
|
|
72
|
+
* const sub1 = actionService.on('ui_open_drawer', (action) => {
|
|
73
|
+
* console.log(action.payload);
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* // Subscribe to multiple action types
|
|
77
|
+
* const sub2 = actionService.on(['ui_open_drawer', 'ui_close_drawer'], (action) => {
|
|
78
|
+
* console.log(action.type, action.payload);
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
on<K extends keyof ActionRecord>(type: K | K[], handler: (action: Action<K>) => Awaitable<void>): SubscribeResult;
|
|
83
|
+
/**
|
|
84
|
+
* Dispatches an action to all subscribers matching `action.type`.
|
|
85
|
+
*
|
|
86
|
+
* @template K - A key of ActionRecord.
|
|
87
|
+
* @param action - Action object containing `type` and `payload`.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* // Dispatches a typed action (payload is required)
|
|
92
|
+
* actionService.dispatch({type: 'upload_complete', payload: 'file-123'});
|
|
93
|
+
*
|
|
94
|
+
* // Dispatches a void action (payload can be omitted)
|
|
95
|
+
* actionService.dispatch({type: 'auth_expired'});
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
dispatch<K extends keyof ActionRecord>(action: DispatchParam<K>): void;
|
|
99
|
+
/**
|
|
100
|
+
* Registers a custom modifier to enrich or filter actions before dispatch.
|
|
101
|
+
*
|
|
102
|
+
* @param name - Modifier name (lowercase, alphanumeric).
|
|
103
|
+
* @param handler - Function called when modifier is invoked.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* actionService.registerModifier('trace', (_event, _element, action) => {
|
|
108
|
+
* action.meta ??= {};
|
|
109
|
+
* action.meta['time'] = Date.now();
|
|
110
|
+
* return true;
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
registerModifier(name: string, handler: ModifierHandler): void;
|
|
115
|
+
/**
|
|
116
|
+
* Registers a custom payload resolver to map DOM state to action payload.
|
|
117
|
+
*
|
|
118
|
+
* @param name - Resolver token (by convention starting with `$`).
|
|
119
|
+
* @param resolver - Function yielding payload from the event and element.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* actionService.registerPayloadResolver('$data-id', (_event, element) => {
|
|
124
|
+
* return element.dataset.id;
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
registerPayloadResolver(name: string, resolver: PayloadResolver): void;
|
|
129
|
+
/**
|
|
130
|
+
* Registers global event delegation listeners on `document.body`.
|
|
131
|
+
*
|
|
132
|
+
* @param eventTypes - List of event types to delegate. Defaults to ActionService.DEFAULT_DELEGATED_EVENTS.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* actionService.setupDelegation();
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
setupDelegation(eventTypes?: readonly string[]): void;
|
|
140
|
+
/**
|
|
141
|
+
* Unregisters all global event delegation listeners.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* actionService.teardownDelegation();
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
teardownDelegation(): void;
|
|
149
|
+
/**
|
|
150
|
+
* Parses attribute values into action descriptor, utilizing the internal cache.
|
|
151
|
+
* @protected
|
|
152
|
+
*/
|
|
153
|
+
protected parseDescriptor_(attributeValue: string): ActionDescriptor | null;
|
|
154
|
+
/**
|
|
155
|
+
* Global event delegation handler.
|
|
156
|
+
* @protected
|
|
157
|
+
*/
|
|
158
|
+
protected handleDelegatedEvent_(event: Event): void;
|
|
159
|
+
/**
|
|
160
|
+
* Registers default modifiers and resolvers.
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
private registerDefaultModifiersAndResolvers__;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Singleton instance of the ActionService.
|
|
167
|
+
* Ready for immediate use.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* import {actionService} from '@alwatr/action';
|
|
172
|
+
*
|
|
173
|
+
* actionService.setupDelegation();
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export declare const actionService: ActionService;
|
|
177
|
+
export {};
|
|
178
|
+
//# sourceMappingURL=action-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-service.d.ts","sourceRoot":"","sources":["../src/action-service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAC,SAAS,EAAW,MAAM,qBAAqB,CAAC;AAE7D,OAAO,KAAK,EAAC,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAC,MAAM,WAAW,CAAC;AAErG;;;GAGG;AACH,UAAU,gBAAgB;IACxB,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAQD;;;;;;;;;;GAUG;AACH,qBAAa,aAAa;IACxB;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,wBAAwB,EAAE,SAAS,MAAM,EAAE,CAA0C;IAErG,SAAS,CAAC,QAAQ,CAAC,OAAO,wCAAkC;IAE5D;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,gBAAgB,wEAAyE;IAE5G;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,iBAAiB,+BAAsC;IAE1E;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,gBAAgB,+BAAsC;IAEzE;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,gBAAgB,uCAA8C;IAEjF;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,oBAAoB,cAAqB;IAE5D;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAyC;;IAOrF;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,YAAY,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,GAAG,eAAe;IAuBjH;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,CAAC,SAAS,MAAM,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI;IAKtE;;;;;;;;;;;;;;OAcG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI;IAS9D;;;;;;;;;;;;OAYG;IACH,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI;IAStE;;;;;;;;;OASG;IACH,eAAe,CAAC,UAAU,GAAE,SAAS,MAAM,EAA2C,GAAG,IAAI;IAc7F;;;;;;;OAOG;IACH,kBAAkB,IAAI,IAAI;IAY1B;;;OAGG;IACH,SAAS,CAAC,gBAAgB,CAAC,cAAc,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAuB3E;;;OAGG;IACH,SAAS,CAAC,qBAAqB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAkFnD;;;OAGG;IACH,OAAO,CAAC,sCAAsC;CA6B/C;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,eAAsB,CAAC"}
|