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