@context-action/core 0.2.2 → 0.3.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/dist/index.cjs +554 -62
- package/dist/index.d.cts +15 -6
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +15 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +554 -62
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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 */
|
|
@@ -97,8 +179,10 @@ async function executeParallel(context, createController) {
|
|
|
97
179
|
try {
|
|
98
180
|
const result = registration.handler(context.payload, controller);
|
|
99
181
|
let handlerResult;
|
|
100
|
-
if (result instanceof Promise)
|
|
101
|
-
|
|
182
|
+
if (result instanceof Promise) {
|
|
183
|
+
const resolved = await result;
|
|
184
|
+
handlerResult = resolved;
|
|
185
|
+
} else handlerResult = result;
|
|
102
186
|
/** Collect result if handler returned something and pipeline wasn't terminated */
|
|
103
187
|
if (handlerResult !== void 0 && !context.terminated) context.results.push(handlerResult);
|
|
104
188
|
return {
|
|
@@ -140,6 +224,56 @@ async function executeParallel(context, createController) {
|
|
|
140
224
|
}
|
|
141
225
|
/**
|
|
142
226
|
* Execute handlers in race mode (first to complete wins)
|
|
227
|
+
*
|
|
228
|
+
* Executes all qualifying handlers simultaneously using Promise.race, where
|
|
229
|
+
* the first handler to complete determines the pipeline result. Other handlers
|
|
230
|
+
* are effectively cancelled. Useful for scenarios where you want the fastest
|
|
231
|
+
* response from multiple equivalent handlers.
|
|
232
|
+
*
|
|
233
|
+
* @template T - The payload type for the action
|
|
234
|
+
* @template R - The result type for handlers
|
|
235
|
+
*
|
|
236
|
+
* @param context - Pipeline execution context containing handlers and state
|
|
237
|
+
* @param createController - Factory function for creating pipeline controllers
|
|
238
|
+
*
|
|
239
|
+
* @throws {Error} When the winning handler fails and is blocking
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* // This is called internally by ActionRegister.dispatch()
|
|
244
|
+
* // when executionMode is 'race'
|
|
245
|
+
*
|
|
246
|
+
* // Multiple data sources racing for fastest response:
|
|
247
|
+
* // - Database handler (might be slow)
|
|
248
|
+
* // - Cache handler (usually fast)
|
|
249
|
+
* // - API handler (variable speed)
|
|
250
|
+
* //
|
|
251
|
+
* // Whichever completes first wins
|
|
252
|
+
*
|
|
253
|
+
* await executeRace(context, (registration, index) => ({
|
|
254
|
+
* return: (result) => {
|
|
255
|
+
* context.terminated = true
|
|
256
|
+
* context.terminationResult = result
|
|
257
|
+
* },
|
|
258
|
+
* // ... other controller methods
|
|
259
|
+
* }))
|
|
260
|
+
* ```
|
|
261
|
+
*
|
|
262
|
+
* @example Use Case
|
|
263
|
+
* ```typescript
|
|
264
|
+
* // Race between multiple data sources
|
|
265
|
+
* register.setActionExecutionMode('fetchUserData', 'race')
|
|
266
|
+
*
|
|
267
|
+
* // These handlers race for fastest response:
|
|
268
|
+
* register.register('fetchUserData', cacheHandler) // Usually fastest
|
|
269
|
+
* register.register('fetchUserData', databaseHandler) // Reliable fallback
|
|
270
|
+
* register.register('fetchUserData', apiHandler) // External source
|
|
271
|
+
*
|
|
272
|
+
* // First to complete wins, others are ignored
|
|
273
|
+
* const result = await register.dispatchWithResult('fetchUserData', { id: '123' })
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
276
|
+
* @public
|
|
143
277
|
*/
|
|
144
278
|
async function executeRace(context, createController) {
|
|
145
279
|
/** Filter handlers that should run */
|
|
@@ -157,8 +291,10 @@ async function executeRace(context, createController) {
|
|
|
157
291
|
try {
|
|
158
292
|
const result = registration.handler(context.payload, controller);
|
|
159
293
|
let handlerResult;
|
|
160
|
-
if (result instanceof Promise)
|
|
161
|
-
|
|
294
|
+
if (result instanceof Promise) {
|
|
295
|
+
const resolved = await result;
|
|
296
|
+
handlerResult = resolved;
|
|
297
|
+
} else handlerResult = result;
|
|
162
298
|
return {
|
|
163
299
|
success: true,
|
|
164
300
|
handlerId: registration.id,
|
|
@@ -189,8 +325,8 @@ async function executeRace(context, createController) {
|
|
|
189
325
|
}
|
|
190
326
|
|
|
191
327
|
//#endregion
|
|
192
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
193
|
-
var require_typeof = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
328
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js
|
|
329
|
+
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
330
|
function _typeof$2(o) {
|
|
195
331
|
"@babel/helpers - typeof";
|
|
196
332
|
return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
|
|
@@ -203,8 +339,8 @@ var require_typeof = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc
|
|
|
203
339
|
}) });
|
|
204
340
|
|
|
205
341
|
//#endregion
|
|
206
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
207
|
-
var require_toPrimitive = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
342
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js
|
|
343
|
+
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
344
|
var _typeof$1 = require_typeof()["default"];
|
|
209
345
|
function toPrimitive$1(t, r) {
|
|
210
346
|
if ("object" != _typeof$1(t) || !t) return t;
|
|
@@ -220,8 +356,8 @@ var require_toPrimitive = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm
|
|
|
220
356
|
}) });
|
|
221
357
|
|
|
222
358
|
//#endregion
|
|
223
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
224
|
-
var require_toPropertyKey = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
359
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js
|
|
360
|
+
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
361
|
var _typeof = require_typeof()["default"];
|
|
226
362
|
var toPrimitive = require_toPrimitive();
|
|
227
363
|
function toPropertyKey$1(t) {
|
|
@@ -232,8 +368,8 @@ var require_toPropertyKey = /* @__PURE__ */ __commonJS({ "../../node_modules/.pn
|
|
|
232
368
|
}) });
|
|
233
369
|
|
|
234
370
|
//#endregion
|
|
235
|
-
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
236
|
-
var require_defineProperty = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.
|
|
371
|
+
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js
|
|
372
|
+
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
373
|
var toPropertyKey = require_toPropertyKey();
|
|
238
374
|
function _defineProperty$3(e, r, t) {
|
|
239
375
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
@@ -251,41 +387,81 @@ var require_defineProperty = /* @__PURE__ */ __commonJS({ "../../node_modules/.p
|
|
|
251
387
|
var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
|
|
252
388
|
/**
|
|
253
389
|
* 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
390
|
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
391
|
+
* Provides performance optimization and user experience enhancement through
|
|
392
|
+
* debouncing and throttling mechanisms. Debouncing waits for a pause in calls
|
|
393
|
+
* before executing, while throttling limits execution frequency.
|
|
264
394
|
*
|
|
265
|
-
* @example
|
|
395
|
+
* @example Debouncing Search Input
|
|
266
396
|
* ```typescript
|
|
267
|
-
* const guard = new ActionGuard(
|
|
397
|
+
* const guard = new ActionGuard()
|
|
398
|
+
*
|
|
399
|
+
* // Wait 300ms after user stops typing before searching
|
|
400
|
+
* register.register('searchUsers', async (payload, controller) => {
|
|
401
|
+
* const query = payload.query
|
|
402
|
+
* if (query.length < 2) return
|
|
403
|
+
*
|
|
404
|
+
* const results = await userService.search(query)
|
|
405
|
+
* controller.setResult(results)
|
|
406
|
+
* }, {
|
|
407
|
+
* debounce: 300, // Built into ActionRegister via ActionGuard
|
|
408
|
+
* tags: ['search', 'user-input']
|
|
409
|
+
* })
|
|
410
|
+
* ```
|
|
268
411
|
*
|
|
269
|
-
*
|
|
412
|
+
* @example Throttling High-Frequency Events
|
|
413
|
+
* ```typescript
|
|
414
|
+
* // Limit scroll position updates to once per 100ms
|
|
415
|
+
* register.register('updateScrollPosition', (payload, controller) => {
|
|
416
|
+
* scrollState.setValue(payload.position)
|
|
417
|
+
* }, {
|
|
418
|
+
* throttle: 100, // Built into ActionRegister via ActionGuard
|
|
419
|
+
* tags: ['scroll', 'performance']
|
|
420
|
+
* })
|
|
421
|
+
* ```
|
|
422
|
+
*
|
|
423
|
+
* @example Manual Usage (Advanced)
|
|
424
|
+
* ```typescript
|
|
425
|
+
* const guard = new ActionGuard()
|
|
426
|
+
*
|
|
427
|
+
* // Manual debouncing
|
|
270
428
|
* if (await guard.debounce('search', 300)) {
|
|
271
|
-
*
|
|
429
|
+
* performSearch() // Only executes after 300ms pause
|
|
272
430
|
* }
|
|
273
431
|
*
|
|
274
|
-
* //
|
|
432
|
+
* // Manual throttling
|
|
275
433
|
* if (guard.throttle('scroll', 100)) {
|
|
276
|
-
*
|
|
434
|
+
* updateUI() // Max once per 100ms
|
|
277
435
|
* }
|
|
278
436
|
* ```
|
|
437
|
+
*
|
|
438
|
+
* @internal
|
|
279
439
|
*/
|
|
280
440
|
var ActionGuard = class {
|
|
281
441
|
constructor() {
|
|
282
442
|
(0, import_defineProperty$2.default)(this, "guards", /* @__PURE__ */ new Map());
|
|
283
443
|
}
|
|
284
444
|
/**
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
445
|
+
* Apply debouncing to an action
|
|
446
|
+
*
|
|
447
|
+
* Debouncing waits for a specified delay after the last call before allowing
|
|
448
|
+
* execution. Each new call resets the timer. Useful for search inputs, resize
|
|
449
|
+
* handlers, and other high-frequency user interactions.
|
|
450
|
+
*
|
|
451
|
+
* @param actionKey - Unique identifier for the action being debounced
|
|
452
|
+
* @param debounceMs - Delay in milliseconds to wait after the last call
|
|
453
|
+
*
|
|
454
|
+
* @returns Promise resolving to true if execution should proceed, false if cancelled
|
|
455
|
+
*
|
|
456
|
+
* @example Search Input Debouncing
|
|
457
|
+
* ```typescript
|
|
458
|
+
* // Only search after user stops typing for 300ms
|
|
459
|
+
* if (await guard.debounce('userSearch', 300)) {
|
|
460
|
+
* performSearch(query)
|
|
461
|
+
* }
|
|
462
|
+
* ```
|
|
463
|
+
*
|
|
464
|
+
* @internal
|
|
289
465
|
*/
|
|
290
466
|
async debounce(actionKey, debounceMs) {
|
|
291
467
|
/** Get or create guard state for this action */
|
|
@@ -320,10 +496,26 @@ var ActionGuard = class {
|
|
|
320
496
|
});
|
|
321
497
|
}
|
|
322
498
|
/**
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
499
|
+
* Apply throttling to an action
|
|
500
|
+
*
|
|
501
|
+
* Throttling limits execution frequency by ensuring a minimum interval between
|
|
502
|
+
* calls. Unlike debouncing, throttling executes immediately on the first call
|
|
503
|
+
* and then blocks subsequent calls until the interval expires.
|
|
504
|
+
*
|
|
505
|
+
* @param actionKey - Unique identifier for the action being throttled
|
|
506
|
+
* @param throttleMs - Minimum interval in milliseconds between executions
|
|
507
|
+
*
|
|
508
|
+
* @returns True if execution should proceed, false if currently throttled
|
|
509
|
+
*
|
|
510
|
+
* @example Scroll Handler Throttling
|
|
511
|
+
* ```typescript
|
|
512
|
+
* // Update scroll position max once per 100ms
|
|
513
|
+
* if (guard.throttle('scrollUpdate', 100)) {
|
|
514
|
+
* updateScrollPosition()
|
|
515
|
+
* }
|
|
516
|
+
* ```
|
|
517
|
+
*
|
|
518
|
+
* @internal
|
|
327
519
|
*/
|
|
328
520
|
throttle(actionKey, throttleMs) {
|
|
329
521
|
/** Get or create guard state for this action */
|
|
@@ -362,8 +554,14 @@ var ActionGuard = class {
|
|
|
362
554
|
return false;
|
|
363
555
|
}
|
|
364
556
|
/**
|
|
365
|
-
* Clear all
|
|
366
|
-
*
|
|
557
|
+
* Clear all guard state for a specific action
|
|
558
|
+
*
|
|
559
|
+
* Removes debounce and throttle timers for the specified action,
|
|
560
|
+
* preventing memory leaks and allowing immediate re-execution.
|
|
561
|
+
*
|
|
562
|
+
* @param actionKey - Action identifier to clear guards for
|
|
563
|
+
*
|
|
564
|
+
* @internal
|
|
367
565
|
*/
|
|
368
566
|
clearGuards(actionKey) {
|
|
369
567
|
const state = this.guards.get(actionKey);
|
|
@@ -380,7 +578,12 @@ var ActionGuard = class {
|
|
|
380
578
|
}
|
|
381
579
|
}
|
|
382
580
|
/**
|
|
383
|
-
* Clear all
|
|
581
|
+
* Clear all guard states for all actions
|
|
582
|
+
*
|
|
583
|
+
* Removes all active debounce and throttle timers, useful for cleanup
|
|
584
|
+
* when shutting down the action system or resetting state.
|
|
585
|
+
*
|
|
586
|
+
* @internal
|
|
384
587
|
*/
|
|
385
588
|
clearAll() {
|
|
386
589
|
/** Iterate through all guard states and clear their timers */
|
|
@@ -398,14 +601,28 @@ var ActionGuard = class {
|
|
|
398
601
|
this.guards.clear();
|
|
399
602
|
}
|
|
400
603
|
/**
|
|
401
|
-
* Get current guard state for debugging
|
|
402
|
-
*
|
|
604
|
+
* Get current guard state for debugging purposes
|
|
605
|
+
*
|
|
606
|
+
* Returns the internal state for a specific action, including timer
|
|
607
|
+
* information and execution timestamps.
|
|
608
|
+
*
|
|
609
|
+
* @param actionKey - Action identifier to inspect
|
|
610
|
+
* @returns Guard state or undefined if no state exists
|
|
611
|
+
*
|
|
612
|
+
* @internal
|
|
403
613
|
*/
|
|
404
614
|
getGuardState(actionKey) {
|
|
405
615
|
return this.guards.get(actionKey);
|
|
406
616
|
}
|
|
407
617
|
/**
|
|
408
|
-
* Get all active
|
|
618
|
+
* Get all active guard states for debugging purposes
|
|
619
|
+
*
|
|
620
|
+
* Returns a copy of all current guard states, useful for monitoring
|
|
621
|
+
* and debugging rate limiting behavior across all actions.
|
|
622
|
+
*
|
|
623
|
+
* @returns Map of action keys to their guard states
|
|
624
|
+
*
|
|
625
|
+
* @internal
|
|
409
626
|
*/
|
|
410
627
|
getAllGuardStates() {
|
|
411
628
|
return new Map(this.guards);
|
|
@@ -522,33 +739,84 @@ var OperationQueue = class {
|
|
|
522
739
|
//#region src/ActionRegister.ts
|
|
523
740
|
var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
|
|
524
741
|
/**
|
|
525
|
-
*
|
|
742
|
+
* Action Register for managing action handlers with priority-based execution
|
|
526
743
|
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
744
|
+
* Central action registration and dispatch system providing type-safe action pipeline management.
|
|
745
|
+
* Supports sequential, parallel, and race execution modes with advanced handler filtering,
|
|
746
|
+
* throttling, debouncing, and comprehensive result collection.
|
|
530
747
|
*
|
|
531
|
-
* @
|
|
748
|
+
* @template TActionMap - Action payload mapping interface extending ActionPayloadMap
|
|
749
|
+
*
|
|
750
|
+
* @example Basic Usage
|
|
532
751
|
* ```typescript
|
|
533
752
|
* interface AppActions extends ActionPayloadMap {
|
|
534
|
-
* updateUser: { id: string; name: string }
|
|
535
|
-
*
|
|
753
|
+
* updateUser: { id: string; name: string; email: string }
|
|
754
|
+
* deleteUser: { id: string }
|
|
755
|
+
* resetUser: void
|
|
536
756
|
* }
|
|
537
757
|
*
|
|
538
758
|
* const register = new ActionRegister<AppActions>({
|
|
539
759
|
* name: 'AppRegister',
|
|
540
|
-
*
|
|
541
|
-
* })
|
|
760
|
+
* registry: { debug: true, maxHandlers: 10 }
|
|
761
|
+
* })
|
|
762
|
+
*
|
|
763
|
+
* // Register handler with priority
|
|
764
|
+
* register.register('updateUser', async (payload, controller) => {
|
|
765
|
+
* await userService.update(payload.id, payload)
|
|
766
|
+
* controller.setResult({ success: true, userId: payload.id })
|
|
767
|
+
* }, { priority: 10, tags: ['user', 'crud'] })
|
|
768
|
+
*
|
|
769
|
+
* // Dispatch action
|
|
770
|
+
* await register.dispatch('updateUser', {
|
|
771
|
+
* id: '123',
|
|
772
|
+
* name: 'John Doe',
|
|
773
|
+
* email: 'john@example.com'
|
|
774
|
+
* })
|
|
775
|
+
* ```
|
|
542
776
|
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
777
|
+
* @example With Multiple Handlers
|
|
778
|
+
* ```typescript
|
|
779
|
+
* // High priority validation handler
|
|
780
|
+
* register.register('updateUser', async (payload, controller) => {
|
|
781
|
+
* if (!payload.email.includes('@')) {
|
|
782
|
+
* controller.abort('Invalid email format')
|
|
783
|
+
* return
|
|
784
|
+
* }
|
|
785
|
+
* }, { priority: 100, category: 'validation' })
|
|
548
786
|
*
|
|
549
|
-
* //
|
|
550
|
-
*
|
|
787
|
+
* // Lower priority update handler
|
|
788
|
+
* register.register('updateUser', async (payload, controller) => {
|
|
789
|
+
* const user = await userService.update(payload.id, payload)
|
|
790
|
+
* controller.setResult(user)
|
|
791
|
+
* }, { priority: 50, category: 'business-logic' })
|
|
551
792
|
* ```
|
|
793
|
+
*
|
|
794
|
+
* @example Advanced Configuration
|
|
795
|
+
* ```typescript
|
|
796
|
+
* const register = new ActionRegister<AppActions>({
|
|
797
|
+
* name: 'AdvancedRegister',
|
|
798
|
+
* registry: {
|
|
799
|
+
* debug: true,
|
|
800
|
+
* maxHandlers: 20,
|
|
801
|
+
* defaultExecutionMode: 'parallel',
|
|
802
|
+
* autoCleanup: true
|
|
803
|
+
* }
|
|
804
|
+
* })
|
|
805
|
+
*
|
|
806
|
+
* // Handler with debouncing and tags
|
|
807
|
+
* register.register('searchUsers', async (payload, controller) => {
|
|
808
|
+
* const results = await userService.search(payload.query)
|
|
809
|
+
* controller.setResult(results)
|
|
810
|
+
* }, {
|
|
811
|
+
* priority: 10,
|
|
812
|
+
* debounce: 300,
|
|
813
|
+
* tags: ['search', 'user'],
|
|
814
|
+
* category: 'query',
|
|
815
|
+
* once: false
|
|
816
|
+
* })
|
|
817
|
+
* ```
|
|
818
|
+
*
|
|
819
|
+
* @public
|
|
552
820
|
*/
|
|
553
821
|
var ActionRegister = class {
|
|
554
822
|
constructor(config = {}) {
|
|
@@ -575,6 +843,43 @@ var ActionRegister = class {
|
|
|
575
843
|
concurrencyProtection: true
|
|
576
844
|
});
|
|
577
845
|
}
|
|
846
|
+
/**
|
|
847
|
+
* Register an action handler with optional configuration
|
|
848
|
+
*
|
|
849
|
+
* @param action - The action type to register handler for
|
|
850
|
+
* @param handler - The handler function to execute
|
|
851
|
+
* @param config - Optional handler configuration including priority, tags, etc.
|
|
852
|
+
*
|
|
853
|
+
* @returns Unregister function to remove this handler
|
|
854
|
+
*
|
|
855
|
+
* @throws {Error} When maximum handlers limit is reached
|
|
856
|
+
*
|
|
857
|
+
* @example Basic Registration
|
|
858
|
+
* ```typescript
|
|
859
|
+
* const unregister = register.register('updateUser', async (payload, controller) => {
|
|
860
|
+
* await userService.update(payload.id, payload)
|
|
861
|
+
* })
|
|
862
|
+
*
|
|
863
|
+
* // Later remove the handler
|
|
864
|
+
* unregister()
|
|
865
|
+
* ```
|
|
866
|
+
*
|
|
867
|
+
* @example With Priority and Configuration
|
|
868
|
+
* ```typescript
|
|
869
|
+
* register.register('validateUser', async (payload, controller) => {
|
|
870
|
+
* if (!payload.email) {
|
|
871
|
+
* controller.abort('Email is required')
|
|
872
|
+
* }
|
|
873
|
+
* }, {
|
|
874
|
+
* priority: 100,
|
|
875
|
+
* tags: ['validation'],
|
|
876
|
+
* category: 'security',
|
|
877
|
+
* once: false
|
|
878
|
+
* })
|
|
879
|
+
* ```
|
|
880
|
+
*
|
|
881
|
+
* @public
|
|
882
|
+
*/
|
|
578
883
|
register(action, handler, config = {}) {
|
|
579
884
|
const handlerId = config.id || `handler_${++this.handlerCounter}_${Math.random().toString(36).substr(2, 5)}`;
|
|
580
885
|
const unregisterFn = this._performRegistrationSync(action, handler, config, handlerId);
|
|
@@ -707,6 +1012,48 @@ var ActionRegister = class {
|
|
|
707
1012
|
}
|
|
708
1013
|
};
|
|
709
1014
|
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Dispatch an action with optional execution options
|
|
1017
|
+
*
|
|
1018
|
+
* @param action - The action type to dispatch
|
|
1019
|
+
* @param payload - The action payload data
|
|
1020
|
+
* @param options - Optional dispatch options (execution mode, filters, etc.)
|
|
1021
|
+
*
|
|
1022
|
+
* @returns Promise that resolves when all handlers complete
|
|
1023
|
+
*
|
|
1024
|
+
* @throws {Error} When action dispatching fails
|
|
1025
|
+
*
|
|
1026
|
+
* @example Basic Dispatch
|
|
1027
|
+
* ```typescript
|
|
1028
|
+
* await register.dispatch('updateUser', {
|
|
1029
|
+
* id: '123',
|
|
1030
|
+
* name: 'John Doe',
|
|
1031
|
+
* email: 'john@example.com'
|
|
1032
|
+
* })
|
|
1033
|
+
* ```
|
|
1034
|
+
*
|
|
1035
|
+
* @example With Options
|
|
1036
|
+
* ```typescript
|
|
1037
|
+
* await register.dispatch('updateUser', payload, {
|
|
1038
|
+
* executionMode: 'parallel',
|
|
1039
|
+
* timeout: 5000,
|
|
1040
|
+
* filter: {
|
|
1041
|
+
* tags: ['validation', 'business-logic'],
|
|
1042
|
+
* excludeCategory: 'analytics'
|
|
1043
|
+
* }
|
|
1044
|
+
* })
|
|
1045
|
+
* ```
|
|
1046
|
+
*
|
|
1047
|
+
* @example With Throttling
|
|
1048
|
+
* ```typescript
|
|
1049
|
+
* await register.dispatch('searchUsers', { query: 'john' }, {
|
|
1050
|
+
* throttle: 300,
|
|
1051
|
+
* debounce: 100
|
|
1052
|
+
* })
|
|
1053
|
+
* ```
|
|
1054
|
+
*
|
|
1055
|
+
* @public
|
|
1056
|
+
*/
|
|
710
1057
|
async dispatch(action, payload, options) {
|
|
711
1058
|
return this.dispatchQueue.enqueue(async () => {
|
|
712
1059
|
return this._performDispatch(action, payload, options);
|
|
@@ -716,6 +1063,28 @@ var ActionRegister = class {
|
|
|
716
1063
|
* 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
|
|
717
1064
|
*/
|
|
718
1065
|
async _performDispatch(action, payload, options) {
|
|
1066
|
+
if (payload && typeof payload === "object" && payload !== null && typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
|
|
1067
|
+
payload instanceof Event;
|
|
1068
|
+
payload instanceof Element;
|
|
1069
|
+
payload.preventDefault;
|
|
1070
|
+
payload.stopPropagation;
|
|
1071
|
+
payload.currentTarget;
|
|
1072
|
+
const hasTarget = payload.target !== void 0;
|
|
1073
|
+
hasTarget && payload.target;
|
|
1074
|
+
hasTarget && payload.target instanceof Element;
|
|
1075
|
+
if (typeof process !== "undefined" && process.env?.DEBUG_CONTEXT_ACTION || typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
|
|
1076
|
+
const nestedDOMProperties = [];
|
|
1077
|
+
Object.keys(payload).forEach((key) => {
|
|
1078
|
+
const prop = payload[key];
|
|
1079
|
+
if (prop instanceof Element || prop instanceof Event) nestedDOMProperties.push(`${key}: ${prop instanceof Element ? "Element" : "Event"}`);
|
|
1080
|
+
});
|
|
1081
|
+
if (nestedDOMProperties.length > 0) console.debug(`[Context-Action] 📋 Nested DOM objects in action "${String(action)}":`, {
|
|
1082
|
+
registry: this.name,
|
|
1083
|
+
nestedDOMProperties,
|
|
1084
|
+
note: "This is informational - usually not a problem"
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
719
1088
|
let autoAbortController;
|
|
720
1089
|
let effectiveSignal = options?.signal;
|
|
721
1090
|
if (options?.autoAbort?.enabled) {
|
|
@@ -783,7 +1152,9 @@ var ActionRegister = class {
|
|
|
783
1152
|
if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
|
|
784
1153
|
try {
|
|
785
1154
|
await this.executePipeline(context, autoAbortController, options?.autoAbort);
|
|
1155
|
+
console.log(`[ActionRegister] Pipeline execution succeeded for ${String(action)}`);
|
|
786
1156
|
} catch (error) {
|
|
1157
|
+
console.log(`[ActionRegister] Pipeline execution failed for ${String(action)}:`, error);
|
|
787
1158
|
executionSuccess = false;
|
|
788
1159
|
throw error;
|
|
789
1160
|
} finally {
|
|
@@ -792,6 +1163,43 @@ var ActionRegister = class {
|
|
|
792
1163
|
this.updateExecutionStats(action, executionSuccess, duration);
|
|
793
1164
|
}
|
|
794
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Dispatch an action and return detailed execution results
|
|
1168
|
+
*
|
|
1169
|
+
* @param action - The action type to dispatch
|
|
1170
|
+
* @param payload - The action payload data
|
|
1171
|
+
* @param options - Optional dispatch options including result collection strategy
|
|
1172
|
+
*
|
|
1173
|
+
* @returns Promise resolving to comprehensive execution results
|
|
1174
|
+
*
|
|
1175
|
+
* @example Basic Result Collection
|
|
1176
|
+
* ```typescript
|
|
1177
|
+
* const result = await register.dispatchWithResult('updateUser', payload)
|
|
1178
|
+
*
|
|
1179
|
+
* if (result.success) {
|
|
1180
|
+
* console.log(`Executed ${result.execution.handlersExecuted} handlers`)
|
|
1181
|
+
* console.log(`Duration: ${result.execution.duration}ms`)
|
|
1182
|
+
* }
|
|
1183
|
+
* ```
|
|
1184
|
+
*
|
|
1185
|
+
* @example Advanced Result Processing
|
|
1186
|
+
* ```typescript
|
|
1187
|
+
* const result = await register.dispatchWithResult('processOrder', order, {
|
|
1188
|
+
* result: {
|
|
1189
|
+
* collect: true,
|
|
1190
|
+
* strategy: 'merge',
|
|
1191
|
+
* maxResults: 5,
|
|
1192
|
+
* merger: (results) => results.reduce((acc, curr) => ({ ...acc, ...curr }), {})
|
|
1193
|
+
* }
|
|
1194
|
+
* })
|
|
1195
|
+
*
|
|
1196
|
+
* if (result.terminated) {
|
|
1197
|
+
* console.log('Handler returned early:', result.result)
|
|
1198
|
+
* }
|
|
1199
|
+
* ```
|
|
1200
|
+
*
|
|
1201
|
+
* @public
|
|
1202
|
+
*/
|
|
795
1203
|
async dispatchWithResult(action, payload, options) {
|
|
796
1204
|
const startTime = Date.now();
|
|
797
1205
|
let autoAbortController;
|
|
@@ -1094,22 +1502,105 @@ var ActionRegister = class {
|
|
|
1094
1502
|
if (success) stats.successCount++;
|
|
1095
1503
|
else stats.errorCount++;
|
|
1096
1504
|
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Get the number of registered handlers for an action
|
|
1507
|
+
*
|
|
1508
|
+
* @param action - The action type to count handlers for
|
|
1509
|
+
*
|
|
1510
|
+
* @returns Number of registered handlers
|
|
1511
|
+
*
|
|
1512
|
+
* @example
|
|
1513
|
+
* ```typescript
|
|
1514
|
+
* register.register('updateUser', handler1)
|
|
1515
|
+
* register.register('updateUser', handler2)
|
|
1516
|
+
*
|
|
1517
|
+
* console.log(register.getHandlerCount('updateUser')) // 2
|
|
1518
|
+
* ```
|
|
1519
|
+
*
|
|
1520
|
+
* @public
|
|
1521
|
+
*/
|
|
1097
1522
|
getHandlerCount(action) {
|
|
1098
1523
|
const pipeline = this.pipelines.get(action);
|
|
1099
1524
|
return pipeline ? pipeline.length : 0;
|
|
1100
1525
|
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Check if an action has any registered handlers
|
|
1528
|
+
*
|
|
1529
|
+
* @param action - The action type to check
|
|
1530
|
+
*
|
|
1531
|
+
* @returns True if action has handlers, false otherwise
|
|
1532
|
+
*
|
|
1533
|
+
* @example
|
|
1534
|
+
* ```typescript
|
|
1535
|
+
* if (register.hasHandlers('updateUser')) {
|
|
1536
|
+
* await register.dispatch('updateUser', userData)
|
|
1537
|
+
* }
|
|
1538
|
+
* ```
|
|
1539
|
+
*
|
|
1540
|
+
* @public
|
|
1541
|
+
*/
|
|
1101
1542
|
hasHandlers(action) {
|
|
1102
1543
|
return this.getHandlerCount(action) > 0;
|
|
1103
1544
|
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Get all registered action types
|
|
1547
|
+
*
|
|
1548
|
+
* @returns Array of all registered action types
|
|
1549
|
+
*
|
|
1550
|
+
* @example
|
|
1551
|
+
* ```typescript
|
|
1552
|
+
* const actions = register.getRegisteredActions()
|
|
1553
|
+
* console.log('Registered actions:', actions) // ['updateUser', 'deleteUser', 'resetUser']
|
|
1554
|
+
* ```
|
|
1555
|
+
*
|
|
1556
|
+
* @public
|
|
1557
|
+
*/
|
|
1104
1558
|
getRegisteredActions() {
|
|
1105
1559
|
return Array.from(this.pipelines.keys());
|
|
1106
1560
|
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Remove all handlers for a specific action
|
|
1563
|
+
*
|
|
1564
|
+
* @param action - The action type to clear handlers for
|
|
1565
|
+
*
|
|
1566
|
+
* @example
|
|
1567
|
+
* ```typescript
|
|
1568
|
+
* register.clearAction('updateUser')
|
|
1569
|
+
* console.log(register.hasHandlers('updateUser')) // false
|
|
1570
|
+
* ```
|
|
1571
|
+
*
|
|
1572
|
+
* @public
|
|
1573
|
+
*/
|
|
1107
1574
|
clearAction(action) {
|
|
1108
1575
|
this.pipelines.delete(action);
|
|
1109
1576
|
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Remove all handlers for all actions
|
|
1579
|
+
*
|
|
1580
|
+
* @example
|
|
1581
|
+
* ```typescript
|
|
1582
|
+
* register.clearAll()
|
|
1583
|
+
* console.log(register.getRegisteredActions().length) // 0
|
|
1584
|
+
* ```
|
|
1585
|
+
*
|
|
1586
|
+
* @public
|
|
1587
|
+
*/
|
|
1110
1588
|
clearAll() {
|
|
1111
1589
|
this.pipelines.clear();
|
|
1112
1590
|
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Get the name of this action register
|
|
1593
|
+
*
|
|
1594
|
+
* @returns The register name
|
|
1595
|
+
*
|
|
1596
|
+
* @example
|
|
1597
|
+
* ```typescript
|
|
1598
|
+
* const register = new ActionRegister({ name: 'UserRegister' })
|
|
1599
|
+
* console.log(register.getName()) // 'UserRegister'
|
|
1600
|
+
* ```
|
|
1601
|
+
*
|
|
1602
|
+
* @public
|
|
1603
|
+
*/
|
|
1113
1604
|
getName() {
|
|
1114
1605
|
return this.name;
|
|
1115
1606
|
}
|
|
@@ -1163,6 +1654,7 @@ var ActionRegister = class {
|
|
|
1163
1654
|
return {
|
|
1164
1655
|
action,
|
|
1165
1656
|
handlerCount: pipeline.length,
|
|
1657
|
+
totalHandlers: pipeline.length,
|
|
1166
1658
|
handlersByPriority,
|
|
1167
1659
|
executionStats
|
|
1168
1660
|
};
|