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