@context-action/core 0.2.2 → 0.2.3
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/dist/index.cjs +523 -58
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +523 -58
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -28,9 +28,42 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
//#region src/execution-modes.ts
|
|
29
29
|
/**
|
|
30
30
|
* Execute handlers in sequential mode (one after another)
|
|
31
|
+
*
|
|
32
|
+
* Executes action handlers one at a time in priority order (highest first).
|
|
33
|
+
* Supports both blocking and non-blocking handlers, with proper abort and
|
|
34
|
+
* termination handling. Handlers can modify payload for subsequent handlers
|
|
35
|
+
* and jump to different priority levels.
|
|
36
|
+
*
|
|
37
|
+
* @template T - The payload type for the action
|
|
38
|
+
* @template R - The result type for handlers
|
|
39
|
+
*
|
|
40
|
+
* @param context - Pipeline execution context containing handlers and state
|
|
41
|
+
* @param createController - Factory function for creating pipeline controllers
|
|
42
|
+
*
|
|
43
|
+
* @throws {Error} When a blocking handler fails or validation errors occur
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* // This is called internally by ActionRegister.dispatch()
|
|
48
|
+
* // when executionMode is 'sequential'
|
|
49
|
+
*
|
|
50
|
+
* // Handlers execute in this order (by priority):
|
|
51
|
+
* // 1. Priority 100: Validation handler
|
|
52
|
+
* // 2. Priority 50: Business logic handler
|
|
53
|
+
* // 3. Priority 10: Logging handler
|
|
54
|
+
*
|
|
55
|
+
* await executeSequential(context, (registration, index) => ({
|
|
56
|
+
* abort: (reason) => { context.aborted = true; context.abortReason = reason },
|
|
57
|
+
* modifyPayload: (modifier) => { context.payload = modifier(context.payload) },
|
|
58
|
+
* // ... other controller methods
|
|
59
|
+
* }))
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @public
|
|
31
63
|
*/
|
|
32
64
|
async function executeSequential(context, createController) {
|
|
33
65
|
let i = 0;
|
|
66
|
+
const nonBlockingPromises = [];
|
|
34
67
|
while (i < context.handlers.length) {
|
|
35
68
|
if (context.aborted || context.terminated) break;
|
|
36
69
|
const registration = context.handlers[i];
|
|
@@ -56,10 +89,15 @@ async function executeSequential(context, createController) {
|
|
|
56
89
|
if (handlerResult !== void 0 && !context.terminated) context.results.push(handlerResult);
|
|
57
90
|
} else if (result !== void 0 && !context.terminated)
|
|
58
91
|
/** Collect synchronous result */
|
|
59
|
-
if (result instanceof Promise)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
92
|
+
if (result instanceof Promise) {
|
|
93
|
+
const promiseWithHandling = result.then((asyncResult) => {
|
|
94
|
+
if (asyncResult !== void 0 && !context.terminated) context.results.push(asyncResult);
|
|
95
|
+
return asyncResult;
|
|
96
|
+
}).catch((error) => {
|
|
97
|
+
throw error;
|
|
98
|
+
});
|
|
99
|
+
nonBlockingPromises.push(promiseWithHandling);
|
|
100
|
+
} else context.results.push(result);
|
|
63
101
|
/** Check if pipeline was terminated by controller.return() */
|
|
64
102
|
if (context.terminated) break;
|
|
65
103
|
/** Handle jump to priority AFTER handler execution */
|
|
@@ -76,12 +114,56 @@ async function executeSequential(context, createController) {
|
|
|
76
114
|
} else i++;
|
|
77
115
|
} catch (error) {
|
|
78
116
|
if (registration.config.blocking) throw error;
|
|
79
|
-
|
|
117
|
+
throw error;
|
|
80
118
|
}
|
|
81
119
|
}
|
|
120
|
+
if (nonBlockingPromises.length > 0) await Promise.all(nonBlockingPromises);
|
|
82
121
|
}
|
|
83
122
|
/**
|
|
84
123
|
* Execute handlers in parallel mode (all at once)
|
|
124
|
+
*
|
|
125
|
+
* Executes all qualifying action handlers simultaneously using Promise.allSettled.
|
|
126
|
+
* Supports both blocking and non-blocking handlers. Blocking handlers can still
|
|
127
|
+
* fail the entire pipeline if they throw errors.
|
|
128
|
+
*
|
|
129
|
+
* @template T - The payload type for the action
|
|
130
|
+
* @template R - The result type for handlers
|
|
131
|
+
*
|
|
132
|
+
* @param context - Pipeline execution context containing handlers and state
|
|
133
|
+
* @param createController - Factory function for creating pipeline controllers
|
|
134
|
+
*
|
|
135
|
+
* @throws {Error} When any blocking handler fails
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* // This is called internally by ActionRegister.dispatch()
|
|
140
|
+
* // when executionMode is 'parallel'
|
|
141
|
+
*
|
|
142
|
+
* // All handlers execute simultaneously:
|
|
143
|
+
* // - Analytics handler (non-blocking)
|
|
144
|
+
* // - Validation handler (blocking)
|
|
145
|
+
* // - Update handler (blocking)
|
|
146
|
+
* // - Notification handler (non-blocking)
|
|
147
|
+
*
|
|
148
|
+
* await executeParallel(context, (registration, index) => ({
|
|
149
|
+
* abort: (reason) => { context.aborted = true },
|
|
150
|
+
* setResult: (result) => { context.results.push(result) },
|
|
151
|
+
* // ... other controller methods
|
|
152
|
+
* }))
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @example Use Case
|
|
156
|
+
* ```typescript
|
|
157
|
+
* // Perfect for independent operations
|
|
158
|
+
* register.setActionExecutionMode('logEvent', 'parallel')
|
|
159
|
+
*
|
|
160
|
+
* // These can all run simultaneously:
|
|
161
|
+
* register.register('logEvent', analyticsHandler, { blocking: false })
|
|
162
|
+
* register.register('logEvent', metricsHandler, { blocking: false })
|
|
163
|
+
* register.register('logEvent', auditHandler, { blocking: true })
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* @public
|
|
85
167
|
*/
|
|
86
168
|
async function executeParallel(context, createController) {
|
|
87
169
|
/** Filter handlers that should run */
|
|
@@ -141,6 +223,56 @@ async function executeParallel(context, createController) {
|
|
|
141
223
|
}
|
|
142
224
|
/**
|
|
143
225
|
* Execute handlers in race mode (first to complete wins)
|
|
226
|
+
*
|
|
227
|
+
* Executes all qualifying handlers simultaneously using Promise.race, where
|
|
228
|
+
* the first handler to complete determines the pipeline result. Other handlers
|
|
229
|
+
* are effectively cancelled. Useful for scenarios where you want the fastest
|
|
230
|
+
* response from multiple equivalent handlers.
|
|
231
|
+
*
|
|
232
|
+
* @template T - The payload type for the action
|
|
233
|
+
* @template R - The result type for handlers
|
|
234
|
+
*
|
|
235
|
+
* @param context - Pipeline execution context containing handlers and state
|
|
236
|
+
* @param createController - Factory function for creating pipeline controllers
|
|
237
|
+
*
|
|
238
|
+
* @throws {Error} When the winning handler fails and is blocking
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* // This is called internally by ActionRegister.dispatch()
|
|
243
|
+
* // when executionMode is 'race'
|
|
244
|
+
*
|
|
245
|
+
* // Multiple data sources racing for fastest response:
|
|
246
|
+
* // - Database handler (might be slow)
|
|
247
|
+
* // - Cache handler (usually fast)
|
|
248
|
+
* // - API handler (variable speed)
|
|
249
|
+
* //
|
|
250
|
+
* // Whichever completes first wins
|
|
251
|
+
*
|
|
252
|
+
* await executeRace(context, (registration, index) => ({
|
|
253
|
+
* return: (result) => {
|
|
254
|
+
* context.terminated = true
|
|
255
|
+
* context.terminationResult = result
|
|
256
|
+
* },
|
|
257
|
+
* // ... other controller methods
|
|
258
|
+
* }))
|
|
259
|
+
* ```
|
|
260
|
+
*
|
|
261
|
+
* @example Use Case
|
|
262
|
+
* ```typescript
|
|
263
|
+
* // Race between multiple data sources
|
|
264
|
+
* register.setActionExecutionMode('fetchUserData', 'race')
|
|
265
|
+
*
|
|
266
|
+
* // These handlers race for fastest response:
|
|
267
|
+
* register.register('fetchUserData', cacheHandler) // Usually fastest
|
|
268
|
+
* register.register('fetchUserData', databaseHandler) // Reliable fallback
|
|
269
|
+
* register.register('fetchUserData', apiHandler) // External source
|
|
270
|
+
*
|
|
271
|
+
* // First to complete wins, others are ignored
|
|
272
|
+
* const result = await register.dispatchWithResult('fetchUserData', { id: '123' })
|
|
273
|
+
* ```
|
|
274
|
+
*
|
|
275
|
+
* @public
|
|
144
276
|
*/
|
|
145
277
|
async function executeRace(context, createController) {
|
|
146
278
|
/** Filter handlers that should run */
|
|
@@ -190,8 +322,8 @@ async function executeRace(context, createController) {
|
|
|
190
322
|
}
|
|
191
323
|
|
|
192
324
|
//#endregion
|
|
193
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
194
|
-
var require_typeof = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
325
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js
|
|
326
|
+
var require_typeof = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js": ((exports, module) => {
|
|
195
327
|
function _typeof$2(o) {
|
|
196
328
|
"@babel/helpers - typeof";
|
|
197
329
|
return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
|
|
@@ -204,8 +336,8 @@ var require_typeof = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc
|
|
|
204
336
|
}) });
|
|
205
337
|
|
|
206
338
|
//#endregion
|
|
207
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
208
|
-
var require_toPrimitive = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
339
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js
|
|
340
|
+
var require_toPrimitive = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js": ((exports, module) => {
|
|
209
341
|
var _typeof$1 = require_typeof()["default"];
|
|
210
342
|
function toPrimitive$1(t, r) {
|
|
211
343
|
if ("object" != _typeof$1(t) || !t) return t;
|
|
@@ -221,8 +353,8 @@ var require_toPrimitive = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm
|
|
|
221
353
|
}) });
|
|
222
354
|
|
|
223
355
|
//#endregion
|
|
224
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
225
|
-
var require_toPropertyKey = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
356
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js
|
|
357
|
+
var require_toPropertyKey = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js": ((exports, module) => {
|
|
226
358
|
var _typeof = require_typeof()["default"];
|
|
227
359
|
var toPrimitive = require_toPrimitive();
|
|
228
360
|
function toPropertyKey$1(t) {
|
|
@@ -233,8 +365,8 @@ var require_toPropertyKey = /* @__PURE__ */ __commonJS({ "../../node_modules/.pn
|
|
|
233
365
|
}) });
|
|
234
366
|
|
|
235
367
|
//#endregion
|
|
236
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
237
|
-
var require_defineProperty = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
368
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js
|
|
369
|
+
var require_defineProperty = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js": ((exports, module) => {
|
|
238
370
|
var toPropertyKey = require_toPropertyKey();
|
|
239
371
|
function _defineProperty$3(e, r, t) {
|
|
240
372
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
@@ -252,41 +384,81 @@ var require_defineProperty = /* @__PURE__ */ __commonJS({ "../../node_modules/.p
|
|
|
252
384
|
var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
|
|
253
385
|
/**
|
|
254
386
|
* Action Guard system for managing action execution timing
|
|
255
|
-
* @implements action-guard
|
|
256
|
-
* @implements performance-optimization
|
|
257
|
-
* @implements user-experience-optimization
|
|
258
|
-
* @implements class-naming
|
|
259
|
-
* @memberof core-concepts
|
|
260
|
-
* @internal
|
|
261
|
-
* @since 1.0.0
|
|
262
387
|
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
388
|
+
* Provides performance optimization and user experience enhancement through
|
|
389
|
+
* debouncing and throttling mechanisms. Debouncing waits for a pause in calls
|
|
390
|
+
* before executing, while throttling limits execution frequency.
|
|
265
391
|
*
|
|
266
|
-
* @example
|
|
392
|
+
* @example Debouncing Search Input
|
|
267
393
|
* ```typescript
|
|
268
|
-
* const guard = new ActionGuard(
|
|
394
|
+
* const guard = new ActionGuard()
|
|
269
395
|
*
|
|
270
|
-
* //
|
|
396
|
+
* // Wait 300ms after user stops typing before searching
|
|
397
|
+
* register.register('searchUsers', async (payload, controller) => {
|
|
398
|
+
* const query = payload.query
|
|
399
|
+
* if (query.length < 2) return
|
|
400
|
+
*
|
|
401
|
+
* const results = await userService.search(query)
|
|
402
|
+
* controller.setResult(results)
|
|
403
|
+
* }, {
|
|
404
|
+
* debounce: 300, // Built into ActionRegister via ActionGuard
|
|
405
|
+
* tags: ['search', 'user-input']
|
|
406
|
+
* })
|
|
407
|
+
* ```
|
|
408
|
+
*
|
|
409
|
+
* @example Throttling High-Frequency Events
|
|
410
|
+
* ```typescript
|
|
411
|
+
* // Limit scroll position updates to once per 100ms
|
|
412
|
+
* register.register('updateScrollPosition', (payload, controller) => {
|
|
413
|
+
* scrollState.setValue(payload.position)
|
|
414
|
+
* }, {
|
|
415
|
+
* throttle: 100, // Built into ActionRegister via ActionGuard
|
|
416
|
+
* tags: ['scroll', 'performance']
|
|
417
|
+
* })
|
|
418
|
+
* ```
|
|
419
|
+
*
|
|
420
|
+
* @example Manual Usage (Advanced)
|
|
421
|
+
* ```typescript
|
|
422
|
+
* const guard = new ActionGuard()
|
|
423
|
+
*
|
|
424
|
+
* // Manual debouncing
|
|
271
425
|
* if (await guard.debounce('search', 300)) {
|
|
272
|
-
*
|
|
426
|
+
* performSearch() // Only executes after 300ms pause
|
|
273
427
|
* }
|
|
274
428
|
*
|
|
275
|
-
* //
|
|
429
|
+
* // Manual throttling
|
|
276
430
|
* if (guard.throttle('scroll', 100)) {
|
|
277
|
-
*
|
|
431
|
+
* updateUI() // Max once per 100ms
|
|
278
432
|
* }
|
|
279
433
|
* ```
|
|
434
|
+
*
|
|
435
|
+
* @internal
|
|
280
436
|
*/
|
|
281
437
|
var ActionGuard = class {
|
|
282
438
|
constructor() {
|
|
283
439
|
(0, import_defineProperty$2.default)(this, "guards", /* @__PURE__ */ new Map());
|
|
284
440
|
}
|
|
285
441
|
/**
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
442
|
+
* Apply debouncing to an action
|
|
443
|
+
*
|
|
444
|
+
* Debouncing waits for a specified delay after the last call before allowing
|
|
445
|
+
* execution. Each new call resets the timer. Useful for search inputs, resize
|
|
446
|
+
* handlers, and other high-frequency user interactions.
|
|
447
|
+
*
|
|
448
|
+
* @param actionKey - Unique identifier for the action being debounced
|
|
449
|
+
* @param debounceMs - Delay in milliseconds to wait after the last call
|
|
450
|
+
*
|
|
451
|
+
* @returns Promise resolving to true if execution should proceed, false if cancelled
|
|
452
|
+
*
|
|
453
|
+
* @example Search Input Debouncing
|
|
454
|
+
* ```typescript
|
|
455
|
+
* // Only search after user stops typing for 300ms
|
|
456
|
+
* if (await guard.debounce('userSearch', 300)) {
|
|
457
|
+
* performSearch(query)
|
|
458
|
+
* }
|
|
459
|
+
* ```
|
|
460
|
+
*
|
|
461
|
+
* @internal
|
|
290
462
|
*/
|
|
291
463
|
async debounce(actionKey, debounceMs) {
|
|
292
464
|
/** Get or create guard state for this action */
|
|
@@ -321,10 +493,26 @@ var ActionGuard = class {
|
|
|
321
493
|
});
|
|
322
494
|
}
|
|
323
495
|
/**
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
496
|
+
* Apply throttling to an action
|
|
497
|
+
*
|
|
498
|
+
* Throttling limits execution frequency by ensuring a minimum interval between
|
|
499
|
+
* calls. Unlike debouncing, throttling executes immediately on the first call
|
|
500
|
+
* and then blocks subsequent calls until the interval expires.
|
|
501
|
+
*
|
|
502
|
+
* @param actionKey - Unique identifier for the action being throttled
|
|
503
|
+
* @param throttleMs - Minimum interval in milliseconds between executions
|
|
504
|
+
*
|
|
505
|
+
* @returns True if execution should proceed, false if currently throttled
|
|
506
|
+
*
|
|
507
|
+
* @example Scroll Handler Throttling
|
|
508
|
+
* ```typescript
|
|
509
|
+
* // Update scroll position max once per 100ms
|
|
510
|
+
* if (guard.throttle('scrollUpdate', 100)) {
|
|
511
|
+
* updateScrollPosition()
|
|
512
|
+
* }
|
|
513
|
+
* ```
|
|
514
|
+
*
|
|
515
|
+
* @internal
|
|
328
516
|
*/
|
|
329
517
|
throttle(actionKey, throttleMs) {
|
|
330
518
|
/** Get or create guard state for this action */
|
|
@@ -363,8 +551,14 @@ var ActionGuard = class {
|
|
|
363
551
|
return false;
|
|
364
552
|
}
|
|
365
553
|
/**
|
|
366
|
-
* Clear all
|
|
367
|
-
*
|
|
554
|
+
* Clear all guard state for a specific action
|
|
555
|
+
*
|
|
556
|
+
* Removes debounce and throttle timers for the specified action,
|
|
557
|
+
* preventing memory leaks and allowing immediate re-execution.
|
|
558
|
+
*
|
|
559
|
+
* @param actionKey - Action identifier to clear guards for
|
|
560
|
+
*
|
|
561
|
+
* @internal
|
|
368
562
|
*/
|
|
369
563
|
clearGuards(actionKey) {
|
|
370
564
|
const state = this.guards.get(actionKey);
|
|
@@ -381,7 +575,12 @@ var ActionGuard = class {
|
|
|
381
575
|
}
|
|
382
576
|
}
|
|
383
577
|
/**
|
|
384
|
-
* Clear all
|
|
578
|
+
* Clear all guard states for all actions
|
|
579
|
+
*
|
|
580
|
+
* Removes all active debounce and throttle timers, useful for cleanup
|
|
581
|
+
* when shutting down the action system or resetting state.
|
|
582
|
+
*
|
|
583
|
+
* @internal
|
|
385
584
|
*/
|
|
386
585
|
clearAll() {
|
|
387
586
|
/** Iterate through all guard states and clear their timers */
|
|
@@ -399,14 +598,28 @@ var ActionGuard = class {
|
|
|
399
598
|
this.guards.clear();
|
|
400
599
|
}
|
|
401
600
|
/**
|
|
402
|
-
* Get current guard state for debugging
|
|
403
|
-
*
|
|
601
|
+
* Get current guard state for debugging purposes
|
|
602
|
+
*
|
|
603
|
+
* Returns the internal state for a specific action, including timer
|
|
604
|
+
* information and execution timestamps.
|
|
605
|
+
*
|
|
606
|
+
* @param actionKey - Action identifier to inspect
|
|
607
|
+
* @returns Guard state or undefined if no state exists
|
|
608
|
+
*
|
|
609
|
+
* @internal
|
|
404
610
|
*/
|
|
405
611
|
getGuardState(actionKey) {
|
|
406
612
|
return this.guards.get(actionKey);
|
|
407
613
|
}
|
|
408
614
|
/**
|
|
409
|
-
* Get all active
|
|
615
|
+
* Get all active guard states for debugging purposes
|
|
616
|
+
*
|
|
617
|
+
* Returns a copy of all current guard states, useful for monitoring
|
|
618
|
+
* and debugging rate limiting behavior across all actions.
|
|
619
|
+
*
|
|
620
|
+
* @returns Map of action keys to their guard states
|
|
621
|
+
*
|
|
622
|
+
* @internal
|
|
410
623
|
*/
|
|
411
624
|
getAllGuardStates() {
|
|
412
625
|
return new Map(this.guards);
|
|
@@ -523,33 +736,84 @@ var OperationQueue = class {
|
|
|
523
736
|
//#region src/ActionRegister.ts
|
|
524
737
|
var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
|
|
525
738
|
/**
|
|
526
|
-
*
|
|
739
|
+
* Action Register for managing action handlers with priority-based execution
|
|
527
740
|
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
741
|
+
* Central action registration and dispatch system providing type-safe action pipeline management.
|
|
742
|
+
* Supports sequential, parallel, and race execution modes with advanced handler filtering,
|
|
743
|
+
* throttling, debouncing, and comprehensive result collection.
|
|
531
744
|
*
|
|
532
|
-
* @
|
|
745
|
+
* @template TActionMap - Action payload mapping interface extending ActionPayloadMap
|
|
746
|
+
*
|
|
747
|
+
* @example Basic Usage
|
|
533
748
|
* ```typescript
|
|
534
749
|
* interface AppActions extends ActionPayloadMap {
|
|
535
|
-
* updateUser: { id: string; name: string }
|
|
536
|
-
*
|
|
750
|
+
* updateUser: { id: string; name: string; email: string }
|
|
751
|
+
* deleteUser: { id: string }
|
|
752
|
+
* resetUser: void
|
|
537
753
|
* }
|
|
538
754
|
*
|
|
539
755
|
* const register = new ActionRegister<AppActions>({
|
|
540
756
|
* name: 'AppRegister',
|
|
541
|
-
*
|
|
542
|
-
* })
|
|
757
|
+
* registry: { debug: true, maxHandlers: 10 }
|
|
758
|
+
* })
|
|
543
759
|
*
|
|
544
|
-
* //
|
|
545
|
-
* register.register('updateUser', (
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
* }, { priority: 10 })
|
|
760
|
+
* // Register handler with priority
|
|
761
|
+
* register.register('updateUser', async (payload, controller) => {
|
|
762
|
+
* await userService.update(payload.id, payload)
|
|
763
|
+
* controller.setResult({ success: true, userId: payload.id })
|
|
764
|
+
* }, { priority: 10, tags: ['user', 'crud'] })
|
|
549
765
|
*
|
|
550
|
-
* //
|
|
551
|
-
* await register.dispatch('updateUser', {
|
|
766
|
+
* // Dispatch action
|
|
767
|
+
* await register.dispatch('updateUser', {
|
|
768
|
+
* id: '123',
|
|
769
|
+
* name: 'John Doe',
|
|
770
|
+
* email: 'john@example.com'
|
|
771
|
+
* })
|
|
552
772
|
* ```
|
|
773
|
+
*
|
|
774
|
+
* @example With Multiple Handlers
|
|
775
|
+
* ```typescript
|
|
776
|
+
* // High priority validation handler
|
|
777
|
+
* register.register('updateUser', async (payload, controller) => {
|
|
778
|
+
* if (!payload.email.includes('@')) {
|
|
779
|
+
* controller.abort('Invalid email format')
|
|
780
|
+
* return
|
|
781
|
+
* }
|
|
782
|
+
* }, { priority: 100, category: 'validation' })
|
|
783
|
+
*
|
|
784
|
+
* // Lower priority update handler
|
|
785
|
+
* register.register('updateUser', async (payload, controller) => {
|
|
786
|
+
* const user = await userService.update(payload.id, payload)
|
|
787
|
+
* controller.setResult(user)
|
|
788
|
+
* }, { priority: 50, category: 'business-logic' })
|
|
789
|
+
* ```
|
|
790
|
+
*
|
|
791
|
+
* @example Advanced Configuration
|
|
792
|
+
* ```typescript
|
|
793
|
+
* const register = new ActionRegister<AppActions>({
|
|
794
|
+
* name: 'AdvancedRegister',
|
|
795
|
+
* registry: {
|
|
796
|
+
* debug: true,
|
|
797
|
+
* maxHandlers: 20,
|
|
798
|
+
* defaultExecutionMode: 'parallel',
|
|
799
|
+
* autoCleanup: true
|
|
800
|
+
* }
|
|
801
|
+
* })
|
|
802
|
+
*
|
|
803
|
+
* // Handler with debouncing and tags
|
|
804
|
+
* register.register('searchUsers', async (payload, controller) => {
|
|
805
|
+
* const results = await userService.search(payload.query)
|
|
806
|
+
* controller.setResult(results)
|
|
807
|
+
* }, {
|
|
808
|
+
* priority: 10,
|
|
809
|
+
* debounce: 300,
|
|
810
|
+
* tags: ['search', 'user'],
|
|
811
|
+
* category: 'query',
|
|
812
|
+
* once: false
|
|
813
|
+
* })
|
|
814
|
+
* ```
|
|
815
|
+
*
|
|
816
|
+
* @public
|
|
553
817
|
*/
|
|
554
818
|
var ActionRegister = class {
|
|
555
819
|
constructor(config = {}) {
|
|
@@ -576,6 +840,43 @@ var ActionRegister = class {
|
|
|
576
840
|
concurrencyProtection: true
|
|
577
841
|
});
|
|
578
842
|
}
|
|
843
|
+
/**
|
|
844
|
+
* Register an action handler with optional configuration
|
|
845
|
+
*
|
|
846
|
+
* @param action - The action type to register handler for
|
|
847
|
+
* @param handler - The handler function to execute
|
|
848
|
+
* @param config - Optional handler configuration including priority, tags, etc.
|
|
849
|
+
*
|
|
850
|
+
* @returns Unregister function to remove this handler
|
|
851
|
+
*
|
|
852
|
+
* @throws {Error} When maximum handlers limit is reached
|
|
853
|
+
*
|
|
854
|
+
* @example Basic Registration
|
|
855
|
+
* ```typescript
|
|
856
|
+
* const unregister = register.register('updateUser', async (payload, controller) => {
|
|
857
|
+
* await userService.update(payload.id, payload)
|
|
858
|
+
* })
|
|
859
|
+
*
|
|
860
|
+
* // Later remove the handler
|
|
861
|
+
* unregister()
|
|
862
|
+
* ```
|
|
863
|
+
*
|
|
864
|
+
* @example With Priority and Configuration
|
|
865
|
+
* ```typescript
|
|
866
|
+
* register.register('validateUser', async (payload, controller) => {
|
|
867
|
+
* if (!payload.email) {
|
|
868
|
+
* controller.abort('Email is required')
|
|
869
|
+
* }
|
|
870
|
+
* }, {
|
|
871
|
+
* priority: 100,
|
|
872
|
+
* tags: ['validation'],
|
|
873
|
+
* category: 'security',
|
|
874
|
+
* once: false
|
|
875
|
+
* })
|
|
876
|
+
* ```
|
|
877
|
+
*
|
|
878
|
+
* @public
|
|
879
|
+
*/
|
|
579
880
|
register(action, handler, config = {}) {
|
|
580
881
|
const handlerId = config.id || `handler_${++this.handlerCounter}_${Math.random().toString(36).substr(2, 5)}`;
|
|
581
882
|
const unregisterFn = this._performRegistrationSync(action, handler, config, handlerId);
|
|
@@ -708,6 +1009,48 @@ var ActionRegister = class {
|
|
|
708
1009
|
}
|
|
709
1010
|
};
|
|
710
1011
|
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Dispatch an action with optional execution options
|
|
1014
|
+
*
|
|
1015
|
+
* @param action - The action type to dispatch
|
|
1016
|
+
* @param payload - The action payload data
|
|
1017
|
+
* @param options - Optional dispatch options (execution mode, filters, etc.)
|
|
1018
|
+
*
|
|
1019
|
+
* @returns Promise that resolves when all handlers complete
|
|
1020
|
+
*
|
|
1021
|
+
* @throws {Error} When action dispatching fails
|
|
1022
|
+
*
|
|
1023
|
+
* @example Basic Dispatch
|
|
1024
|
+
* ```typescript
|
|
1025
|
+
* await register.dispatch('updateUser', {
|
|
1026
|
+
* id: '123',
|
|
1027
|
+
* name: 'John Doe',
|
|
1028
|
+
* email: 'john@example.com'
|
|
1029
|
+
* })
|
|
1030
|
+
* ```
|
|
1031
|
+
*
|
|
1032
|
+
* @example With Options
|
|
1033
|
+
* ```typescript
|
|
1034
|
+
* await register.dispatch('updateUser', payload, {
|
|
1035
|
+
* executionMode: 'parallel',
|
|
1036
|
+
* timeout: 5000,
|
|
1037
|
+
* filter: {
|
|
1038
|
+
* tags: ['validation', 'business-logic'],
|
|
1039
|
+
* excludeCategory: 'analytics'
|
|
1040
|
+
* }
|
|
1041
|
+
* })
|
|
1042
|
+
* ```
|
|
1043
|
+
*
|
|
1044
|
+
* @example With Throttling
|
|
1045
|
+
* ```typescript
|
|
1046
|
+
* await register.dispatch('searchUsers', { query: 'john' }, {
|
|
1047
|
+
* throttle: 300,
|
|
1048
|
+
* debounce: 100
|
|
1049
|
+
* })
|
|
1050
|
+
* ```
|
|
1051
|
+
*
|
|
1052
|
+
* @public
|
|
1053
|
+
*/
|
|
711
1054
|
async dispatch(action, payload, options) {
|
|
712
1055
|
return this.dispatchQueue.enqueue(async () => {
|
|
713
1056
|
return this._performDispatch(action, payload, options);
|
|
@@ -784,7 +1127,9 @@ var ActionRegister = class {
|
|
|
784
1127
|
if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
|
|
785
1128
|
try {
|
|
786
1129
|
await this.executePipeline(context, autoAbortController, options?.autoAbort);
|
|
1130
|
+
console.log(`[ActionRegister] Pipeline execution succeeded for ${String(action)}`);
|
|
787
1131
|
} catch (error) {
|
|
1132
|
+
console.log(`[ActionRegister] Pipeline execution failed for ${String(action)}:`, error);
|
|
788
1133
|
executionSuccess = false;
|
|
789
1134
|
throw error;
|
|
790
1135
|
} finally {
|
|
@@ -793,6 +1138,43 @@ var ActionRegister = class {
|
|
|
793
1138
|
this.updateExecutionStats(action, executionSuccess, duration);
|
|
794
1139
|
}
|
|
795
1140
|
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Dispatch an action and return detailed execution results
|
|
1143
|
+
*
|
|
1144
|
+
* @param action - The action type to dispatch
|
|
1145
|
+
* @param payload - The action payload data
|
|
1146
|
+
* @param options - Optional dispatch options including result collection strategy
|
|
1147
|
+
*
|
|
1148
|
+
* @returns Promise resolving to comprehensive execution results
|
|
1149
|
+
*
|
|
1150
|
+
* @example Basic Result Collection
|
|
1151
|
+
* ```typescript
|
|
1152
|
+
* const result = await register.dispatchWithResult('updateUser', payload)
|
|
1153
|
+
*
|
|
1154
|
+
* if (result.success) {
|
|
1155
|
+
* console.log(`Executed ${result.execution.handlersExecuted} handlers`)
|
|
1156
|
+
* console.log(`Duration: ${result.execution.duration}ms`)
|
|
1157
|
+
* }
|
|
1158
|
+
* ```
|
|
1159
|
+
*
|
|
1160
|
+
* @example Advanced Result Processing
|
|
1161
|
+
* ```typescript
|
|
1162
|
+
* const result = await register.dispatchWithResult('processOrder', order, {
|
|
1163
|
+
* result: {
|
|
1164
|
+
* collect: true,
|
|
1165
|
+
* strategy: 'merge',
|
|
1166
|
+
* maxResults: 5,
|
|
1167
|
+
* merger: (results) => results.reduce((acc, curr) => ({ ...acc, ...curr }), {})
|
|
1168
|
+
* }
|
|
1169
|
+
* })
|
|
1170
|
+
*
|
|
1171
|
+
* if (result.terminated) {
|
|
1172
|
+
* console.log('Handler returned early:', result.result)
|
|
1173
|
+
* }
|
|
1174
|
+
* ```
|
|
1175
|
+
*
|
|
1176
|
+
* @public
|
|
1177
|
+
*/
|
|
796
1178
|
async dispatchWithResult(action, payload, options) {
|
|
797
1179
|
const startTime = Date.now();
|
|
798
1180
|
let autoAbortController;
|
|
@@ -1095,22 +1477,105 @@ var ActionRegister = class {
|
|
|
1095
1477
|
if (success) stats.successCount++;
|
|
1096
1478
|
else stats.errorCount++;
|
|
1097
1479
|
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Get the number of registered handlers for an action
|
|
1482
|
+
*
|
|
1483
|
+
* @param action - The action type to count handlers for
|
|
1484
|
+
*
|
|
1485
|
+
* @returns Number of registered handlers
|
|
1486
|
+
*
|
|
1487
|
+
* @example
|
|
1488
|
+
* ```typescript
|
|
1489
|
+
* register.register('updateUser', handler1)
|
|
1490
|
+
* register.register('updateUser', handler2)
|
|
1491
|
+
*
|
|
1492
|
+
* console.log(register.getHandlerCount('updateUser')) // 2
|
|
1493
|
+
* ```
|
|
1494
|
+
*
|
|
1495
|
+
* @public
|
|
1496
|
+
*/
|
|
1098
1497
|
getHandlerCount(action) {
|
|
1099
1498
|
const pipeline = this.pipelines.get(action);
|
|
1100
1499
|
return pipeline ? pipeline.length : 0;
|
|
1101
1500
|
}
|
|
1501
|
+
/**
|
|
1502
|
+
* Check if an action has any registered handlers
|
|
1503
|
+
*
|
|
1504
|
+
* @param action - The action type to check
|
|
1505
|
+
*
|
|
1506
|
+
* @returns True if action has handlers, false otherwise
|
|
1507
|
+
*
|
|
1508
|
+
* @example
|
|
1509
|
+
* ```typescript
|
|
1510
|
+
* if (register.hasHandlers('updateUser')) {
|
|
1511
|
+
* await register.dispatch('updateUser', userData)
|
|
1512
|
+
* }
|
|
1513
|
+
* ```
|
|
1514
|
+
*
|
|
1515
|
+
* @public
|
|
1516
|
+
*/
|
|
1102
1517
|
hasHandlers(action) {
|
|
1103
1518
|
return this.getHandlerCount(action) > 0;
|
|
1104
1519
|
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Get all registered action types
|
|
1522
|
+
*
|
|
1523
|
+
* @returns Array of all registered action types
|
|
1524
|
+
*
|
|
1525
|
+
* @example
|
|
1526
|
+
* ```typescript
|
|
1527
|
+
* const actions = register.getRegisteredActions()
|
|
1528
|
+
* console.log('Registered actions:', actions) // ['updateUser', 'deleteUser', 'resetUser']
|
|
1529
|
+
* ```
|
|
1530
|
+
*
|
|
1531
|
+
* @public
|
|
1532
|
+
*/
|
|
1105
1533
|
getRegisteredActions() {
|
|
1106
1534
|
return Array.from(this.pipelines.keys());
|
|
1107
1535
|
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Remove all handlers for a specific action
|
|
1538
|
+
*
|
|
1539
|
+
* @param action - The action type to clear handlers for
|
|
1540
|
+
*
|
|
1541
|
+
* @example
|
|
1542
|
+
* ```typescript
|
|
1543
|
+
* register.clearAction('updateUser')
|
|
1544
|
+
* console.log(register.hasHandlers('updateUser')) // false
|
|
1545
|
+
* ```
|
|
1546
|
+
*
|
|
1547
|
+
* @public
|
|
1548
|
+
*/
|
|
1108
1549
|
clearAction(action) {
|
|
1109
1550
|
this.pipelines.delete(action);
|
|
1110
1551
|
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Remove all handlers for all actions
|
|
1554
|
+
*
|
|
1555
|
+
* @example
|
|
1556
|
+
* ```typescript
|
|
1557
|
+
* register.clearAll()
|
|
1558
|
+
* console.log(register.getRegisteredActions().length) // 0
|
|
1559
|
+
* ```
|
|
1560
|
+
*
|
|
1561
|
+
* @public
|
|
1562
|
+
*/
|
|
1111
1563
|
clearAll() {
|
|
1112
1564
|
this.pipelines.clear();
|
|
1113
1565
|
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Get the name of this action register
|
|
1568
|
+
*
|
|
1569
|
+
* @returns The register name
|
|
1570
|
+
*
|
|
1571
|
+
* @example
|
|
1572
|
+
* ```typescript
|
|
1573
|
+
* const register = new ActionRegister({ name: 'UserRegister' })
|
|
1574
|
+
* console.log(register.getName()) // 'UserRegister'
|
|
1575
|
+
* ```
|
|
1576
|
+
*
|
|
1577
|
+
* @public
|
|
1578
|
+
*/
|
|
1114
1579
|
getName() {
|
|
1115
1580
|
return this.name;
|
|
1116
1581
|
}
|