@context-action/core 0.3.1 → 0.5.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 CHANGED
@@ -1,32 +1,24 @@
1
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __commonJS = (cb, mod) => function() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
13
- key = keys[i];
14
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
15
- get: ((k) => from[k]).bind(null, key),
16
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
- });
18
- }
19
- return to;
20
- };
21
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
22
- value: mod,
23
- enumerable: true
24
- }) : target, mod));
25
-
26
- //#endregion
27
1
 
28
2
  //#region src/execution-modes.ts
29
3
  /**
4
+ * Create standardized error handling for handlers
5
+ *
6
+ * @param error - The error that occurred
7
+ * @param registration - The handler registration that failed
8
+ * @returns Standardized HandlerError object
9
+ *
10
+ * @internal
11
+ */
12
+ function handleExecutionError(error, registration) {
13
+ const errorObj = error instanceof Error ? error : new Error(String(error));
14
+ return {
15
+ handlerId: registration.id,
16
+ error: errorObj,
17
+ timestamp: Date.now(),
18
+ severity: registration.config.blocking ? "blocking" : "non-blocking"
19
+ };
20
+ }
21
+ /**
30
22
  * Execute handlers in sequential mode (one after another)
31
23
  *
32
24
  * Executes action handlers one at a time in priority order (highest first).
@@ -40,64 +32,42 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
40
32
  * @param context - Pipeline execution context containing handlers and state
41
33
  * @param createController - Factory function for creating pipeline controllers
42
34
  *
43
- * @throws {Error} When a blocking handler fails or validation errors occur
35
+ * @throws {Error} When a blocking handler fails
44
36
  *
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
- * ```
37
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-patterns
61
38
  *
62
39
  * @public
63
40
  */
64
41
  async function executeSequential(context, createController) {
65
42
  let i = 0;
66
43
  const nonBlockingPromises = [];
44
+ const errors = [];
67
45
  while (i < context.handlers.length) {
68
46
  if (context.aborted || context.terminated) break;
69
47
  const registration = context.handlers[i];
70
48
  context.currentIndex = i;
71
- /** Check condition if provided */
72
- if (registration.config.condition && !registration.config.condition()) {
73
- i++;
74
- continue;
75
- }
76
- /** Check validation if provided */
77
- if (registration.config.validation && !registration.config.validation(context.payload)) {
78
- i++;
79
- continue;
80
- }
81
49
  const controller = createController(registration, i);
82
50
  try {
83
51
  if (context.aborted) break;
84
52
  const result = registration.handler(context.payload, controller);
85
- /** Wait for async handlers if they're blocking */
86
- if (registration.config.blocking && result instanceof Promise) {
87
- const handlerResult = await result;
88
- /** Collect result if handler returned something and wasn't terminated */
53
+ if (registration.config.blocking) {
54
+ const handlerResult = result instanceof Promise ? await result : result;
89
55
  if (handlerResult !== void 0 && !context.terminated) context.results.push(handlerResult);
90
- } else if (result !== void 0 && !context.terminated)
91
- /** Collect synchronous result */
92
- if (result instanceof Promise) {
93
- const promiseWithHandling = result.then((asyncResult) => {
56
+ } else if (result instanceof Promise) {
57
+ const promiseWithErrorHandling = result.then((asyncResult) => {
94
58
  if (asyncResult !== void 0 && !context.terminated) context.results.push(asyncResult);
95
59
  return asyncResult;
96
60
  }).catch((error) => {
97
- throw error;
61
+ const handlerError = handleExecutionError(error, registration);
62
+ errors.push({
63
+ handlerId: handlerError.handlerId,
64
+ error: handlerError.error,
65
+ timestamp: handlerError.timestamp
66
+ });
67
+ return void 0;
98
68
  });
99
- nonBlockingPromises.push(promiseWithHandling);
100
- } else context.results.push(result);
69
+ nonBlockingPromises.push(promiseWithErrorHandling);
70
+ } else if (result !== void 0 && !context.terminated) context.results.push(result);
101
71
  /** Check if pipeline was terminated by controller.return() */
102
72
  if (context.terminated) break;
103
73
  /** Handle jump to priority AFTER handler execution */
@@ -113,11 +83,20 @@ async function executeSequential(context, createController) {
113
83
  }
114
84
  } else i++;
115
85
  } catch (error) {
116
- if (registration.config.blocking) throw error;
117
- throw error;
86
+ const handlerError = handleExecutionError(error, registration);
87
+ throw handlerError.error;
118
88
  }
119
89
  }
120
- if (nonBlockingPromises.length > 0) await Promise.all(nonBlockingPromises);
90
+ if (nonBlockingPromises.length > 0) await Promise.allSettled(nonBlockingPromises);
91
+ if (errors.length > 0) {
92
+ const handlerErrors = errors.map((err) => ({
93
+ handlerId: err.handlerId,
94
+ error: err.error,
95
+ timestamp: err.timestamp,
96
+ severity: "non-blocking"
97
+ }));
98
+ context.collectedErrors = handlerErrors;
99
+ }
121
100
  }
122
101
  /**
123
102
  * Execute handlers in parallel mode (all at once)
@@ -134,46 +113,13 @@ async function executeSequential(context, createController) {
134
113
  *
135
114
  * @throws {Error} When any blocking handler fails
136
115
  *
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
- * ```
116
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-patterns#parallel-execution
165
117
  *
166
118
  * @public
167
119
  */
168
120
  async function executeParallel(context, createController) {
169
- /** Filter handlers that should run */
170
- const runnableHandlers = context.handlers.filter((registration, _index) => {
171
- /** Check condition */
172
- if (registration.config.condition && !registration.config.condition()) return false;
173
- /** Check validation */
174
- if (registration.config.validation && !registration.config.validation(context.payload)) return false;
175
- return true;
176
- });
121
+ /** All handlers are runnable */
122
+ const runnableHandlers = context.handlers;
177
123
  /** Create promises for all handlers */
178
124
  const handlerPromises = runnableHandlers.map(async (registration, _index) => {
179
125
  const controller = createController(registration, _index);
@@ -193,11 +139,12 @@ async function executeParallel(context, createController) {
193
139
  terminated: context.terminated
194
140
  };
195
141
  } catch (error) {
196
- if (registration.config.blocking) throw error;
142
+ const handlerError = handleExecutionError(error, registration);
143
+ if (handlerError.severity === "blocking") throw handlerError.error;
197
144
  return {
198
145
  success: false,
199
146
  handlerId: registration.id,
200
- error
147
+ error: handlerError.error
201
148
  };
202
149
  }
203
150
  });
@@ -239,52 +186,13 @@ async function executeParallel(context, createController) {
239
186
  *
240
187
  * @throws {Error} When the winning handler fails and is blocking
241
188
  *
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
- * ```
189
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/dispatch-patterns#race-execution
276
190
  *
277
191
  * @public
278
192
  */
279
193
  async function executeRace(context, createController) {
280
- /** Filter handlers that should run */
281
- const runnableHandlers = context.handlers.filter((registration, _index) => {
282
- /** Check condition */
283
- if (registration.config.condition && !registration.config.condition()) return false;
284
- /** Check validation */
285
- if (registration.config.validation && !registration.config.validation(context.payload)) return false;
286
- return true;
287
- });
194
+ /** All handlers are runnable */
195
+ const runnableHandlers = context.handlers;
288
196
  if (runnableHandlers.length === 0) return;
289
197
  /** Create promises for all handlers */
290
198
  const handlerPromises = runnableHandlers.map(async (registration, _index) => {
@@ -304,10 +212,11 @@ async function executeRace(context, createController) {
304
212
  terminated: context.terminated
305
213
  };
306
214
  } catch (error) {
215
+ const handlerError = handleExecutionError(error, registration);
307
216
  return {
308
217
  success: false,
309
218
  handlerId: registration.id,
310
- error,
219
+ error: handlerError.error,
311
220
  registration
312
221
  };
313
222
  }
@@ -325,67 +234,8 @@ async function executeRace(context, createController) {
325
234
  }
326
235
  }
327
236
 
328
- //#endregion
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) => {
331
- function _typeof$2(o) {
332
- "@babel/helpers - typeof";
333
- return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
334
- return typeof o$1;
335
- } : function(o$1) {
336
- return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
337
- }, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
338
- }
339
- module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
340
- }) });
341
-
342
- //#endregion
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) => {
345
- var _typeof$1 = require_typeof()["default"];
346
- function toPrimitive$1(t, r) {
347
- if ("object" != _typeof$1(t) || !t) return t;
348
- var e = t[Symbol.toPrimitive];
349
- if (void 0 !== e) {
350
- var i = e.call(t, r || "default");
351
- if ("object" != _typeof$1(i)) return i;
352
- throw new TypeError("@@toPrimitive must return a primitive value.");
353
- }
354
- return ("string" === r ? String : Number)(t);
355
- }
356
- module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
357
- }) });
358
-
359
- //#endregion
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) => {
362
- var _typeof = require_typeof()["default"];
363
- var toPrimitive = require_toPrimitive();
364
- function toPropertyKey$1(t) {
365
- var i = toPrimitive(t, "string");
366
- return "symbol" == _typeof(i) ? i : i + "";
367
- }
368
- module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
369
- }) });
370
-
371
- //#endregion
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) => {
374
- var toPropertyKey = require_toPropertyKey();
375
- function _defineProperty$3(e, r, t) {
376
- return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
377
- value: t,
378
- enumerable: !0,
379
- configurable: !0,
380
- writable: !0
381
- }) : e[r] = t, e;
382
- }
383
- module.exports = _defineProperty$3, module.exports.__esModule = true, module.exports["default"] = module.exports;
384
- }) });
385
-
386
237
  //#endregion
387
238
  //#region src/action-guard.ts
388
- var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
389
239
  /**
390
240
  * Action Guard system for managing action execution timing
391
241
  *
@@ -393,33 +243,9 @@ var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(),
393
243
  * debouncing and throttling mechanisms. Debouncing waits for a pause in calls
394
244
  * before executing, while throttling limits execution frequency.
395
245
  *
396
- * @example Debouncing Search Input
397
- * ```typescript
398
- * const guard = new ActionGuard()
246
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
399
247
  *
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
- * ```
412
- *
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
- * ```
248
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
423
249
  *
424
250
  * @example Manual Usage (Advanced)
425
251
  * ```typescript
@@ -439,8 +265,31 @@ var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(),
439
265
  * @internal
440
266
  */
441
267
  var ActionGuard = class {
442
- constructor() {
443
- (0, import_defineProperty$2.default)(this, "guards", /* @__PURE__ */ new Map());
268
+ constructor(autoCleanup = true) {
269
+ this.guards = /* @__PURE__ */ new Map();
270
+ this.maxIdleTime = 6e4;
271
+ this.cleanupIntervalMs = 3e4;
272
+ if (autoCleanup) this.startAutoCleanup();
273
+ }
274
+ /**
275
+ * Start automatic cleanup of idle guard states
276
+ *
277
+ * @internal
278
+ */
279
+ startAutoCleanup() {
280
+ this.cleanupInterval = setInterval(() => {
281
+ const now = Date.now();
282
+ const keysToDelete = [];
283
+ this.guards.forEach((state, key) => {
284
+ const isIdle = now - state.lastExecuted > this.maxIdleTime;
285
+ const hasActiveTimers = state.debounceTimer || state.throttleTimer;
286
+ if (isIdle && !hasActiveTimers) keysToDelete.push(key);
287
+ });
288
+ if (keysToDelete.length > 0) {
289
+ keysToDelete.forEach((key) => this.guards.delete(key));
290
+ if (typeof process !== "undefined" && process.env?.DEBUG_CONTEXT_ACTION) console.debug(`[ActionGuard] Cleaned up ${keysToDelete.length} idle guards`);
291
+ }
292
+ }, this.cleanupIntervalMs);
444
293
  }
445
294
  /**
446
295
  * Apply debouncing to an action
@@ -570,10 +419,17 @@ var ActionGuard = class {
570
419
  /** Clear debounce timer if active to prevent memory leaks */
571
420
  if (state.debounceTimer) {
572
421
  clearTimeout(state.debounceTimer);
573
- if (state.debounceResolve) state.debounceResolve(false);
422
+ if (state.debounceResolve) {
423
+ state.debounceResolve(false);
424
+ state.debounceResolve = void 0;
425
+ }
426
+ state.debounceTimer = void 0;
574
427
  }
575
428
  /** Clear throttle timer if active to prevent memory leaks */
576
- if (state.throttleTimer) clearTimeout(state.throttleTimer);
429
+ if (state.throttleTimer) {
430
+ clearTimeout(state.throttleTimer);
431
+ state.throttleTimer = void 0;
432
+ }
577
433
  /** Remove guard state from memory */
578
434
  this.guards.delete(actionKey);
579
435
  }
@@ -589,7 +445,7 @@ var ActionGuard = class {
589
445
  clearAll() {
590
446
  /** Iterate through all guard states and clear their timers */
591
447
  /** This prevents memory leaks when clearing the entire guard system */
592
- for (const [, state] of this.guards) {
448
+ this.guards.forEach((state) => {
593
449
  /** Clear any active debounce timers */
594
450
  if (state.debounceTimer) {
595
451
  clearTimeout(state.debounceTimer);
@@ -597,7 +453,7 @@ var ActionGuard = class {
597
453
  }
598
454
  /** Clear any active throttle timers */
599
455
  if (state.throttleTimer) clearTimeout(state.throttleTimer);
600
- }
456
+ });
601
457
  /** Remove all guard states from memory */
602
458
  this.guards.clear();
603
459
  }
@@ -628,11 +484,42 @@ var ActionGuard = class {
628
484
  getAllGuardStates() {
629
485
  return new Map(this.guards);
630
486
  }
487
+ /**
488
+ * 🆕 Explicit destroy method for comprehensive cleanup
489
+ *
490
+ * Cleans up all timers, promises, and intervals to prevent memory leaks.
491
+ * Should be called when ActionGuard is no longer needed.
492
+ *
493
+ * @internal
494
+ */
495
+ destroy() {
496
+ if (this.cleanupInterval) {
497
+ clearInterval(this.cleanupInterval);
498
+ this.cleanupInterval = void 0;
499
+ }
500
+ this.clearAll();
501
+ }
502
+ /**
503
+ * 🆕 Get statistics about active guards
504
+ *
505
+ * @returns Statistics about guard usage
506
+ *
507
+ * @internal
508
+ */
509
+ getStats() {
510
+ let withTimers = 0;
511
+ this.guards.forEach((state) => {
512
+ if (state.debounceTimer || state.throttleTimer) withTimers++;
513
+ });
514
+ return {
515
+ activeGuards: this.guards.size,
516
+ withTimers
517
+ };
518
+ }
631
519
  };
632
520
 
633
521
  //#endregion
634
522
  //#region src/concurrency/OperationQueue.ts
635
- var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
636
523
  /**
637
524
  * 작업 큐 관리자
638
525
  *
@@ -641,13 +528,17 @@ var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(),
641
528
  * 2. 우선순위 지원 - 중요한 작업 우선 처리
642
529
  * 3. 에러 처리 - 개별 작업 실패가 전체에 영향 주지 않음
643
530
  * 4. 메모리 관리 - 완료된 작업 자동 정리
531
+ * 5. 🆕 동시성 제어 - maxConcurrency로 동시 실행 제한
644
532
  */
645
533
  var OperationQueue = class {
646
- constructor(name = "OperationQueue") {
534
+ constructor(name = "OperationQueue", maxConcurrency = 1) {
647
535
  this.name = name;
648
- (0, import_defineProperty$1.default)(this, "queue", []);
649
- (0, import_defineProperty$1.default)(this, "isProcessing", false);
650
- (0, import_defineProperty$1.default)(this, "operationCounter", 0);
536
+ this.queue = [];
537
+ this.processingPromise = null;
538
+ this.operationCounter = 0;
539
+ this.activeOperations = 0;
540
+ this.runningOperations = /* @__PURE__ */ new Set();
541
+ this.maxConcurrency = Math.max(1, maxConcurrency);
651
542
  }
652
543
  /**
653
544
  * 작업을 큐에 추가하고 실행 결과를 반환
@@ -676,35 +567,56 @@ var OperationQueue = class {
676
567
  });
677
568
  }
678
569
  /**
679
- * 큐 처리 메인 로직
570
+ * 🆕 큐 처리 메인 로직 - 동시성 제어 지원
680
571
  *
681
- * 번에 하나씩 순서대로 작업을 실행하여 동시성 문제 방지
572
+ * maxConcurrency에 따라 동시 실행 작업 수를 제한하여 동시성 문제 방지
682
573
  */
683
574
  async processQueue() {
684
- if (this.isProcessing || this.queue.length === 0) return;
685
- this.isProcessing = true;
575
+ if (this.processingPromise) return this.processingPromise;
576
+ this.processingPromise = this._doProcess();
686
577
  try {
687
- while (this.queue.length > 0) {
578
+ await this.processingPromise;
579
+ } finally {
580
+ this.processingPromise = null;
581
+ }
582
+ }
583
+ async _doProcess() {
584
+ while (this.queue.length > 0 || this.runningOperations.size > 0) {
585
+ while (this.queue.length > 0 && this.activeOperations < this.maxConcurrency) {
688
586
  const operation = this.queue.shift();
689
- try {
690
- const result = await Promise.resolve(operation.operation());
691
- operation.resolve(result);
692
- } catch (error) {
693
- operation.reject(error);
694
- }
587
+ this.activeOperations++;
588
+ const operationPromise = this.executeOperation(operation);
589
+ this.runningOperations.add(operationPromise);
590
+ operationPromise.finally(() => {
591
+ this.activeOperations--;
592
+ this.runningOperations.delete(operationPromise);
593
+ });
695
594
  }
696
- } finally {
697
- this.isProcessing = false;
595
+ if (this.runningOperations.size > 0) await Promise.race(this.runningOperations);
596
+ }
597
+ }
598
+ /**
599
+ * 🆕 개별 작업 실행 로직
600
+ */
601
+ async executeOperation(operation) {
602
+ try {
603
+ const result = await Promise.resolve(operation.operation());
604
+ operation.resolve(result);
605
+ } catch (error) {
606
+ operation.reject(error);
698
607
  }
699
608
  }
700
609
  /**
701
- * 현재 큐 상태 조회 (디버깅용)
610
+ * 🆕 현재 큐 상태 조회 (디버깅용) - 동시성 정보 포함
702
611
  */
703
612
  getQueueInfo() {
704
613
  return {
705
614
  name: this.name,
706
615
  queueLength: this.queue.length,
707
- isProcessing: this.isProcessing,
616
+ isProcessing: Boolean(this.processingPromise),
617
+ activeOperations: this.activeOperations,
618
+ maxConcurrency: this.maxConcurrency,
619
+ runningOperationsCount: this.runningOperations.size,
708
620
  operations: this.queue.map((op) => ({
709
621
  id: op.id,
710
622
  priority: op.priority,
@@ -713,6 +625,18 @@ var OperationQueue = class {
713
625
  };
714
626
  }
715
627
  /**
628
+ * 🆕 동시성 설정 조회
629
+ */
630
+ getConcurrencyInfo() {
631
+ return {
632
+ maxConcurrency: this.maxConcurrency,
633
+ activeOperations: this.activeOperations,
634
+ availableSlots: this.maxConcurrency - this.activeOperations,
635
+ queuedOperations: this.queue.length,
636
+ efficiency: this.activeOperations / this.maxConcurrency
637
+ };
638
+ }
639
+ /**
716
640
  * 큐 비우기 (테스트용)
717
641
  */
718
642
  clear() {
@@ -720,7 +644,7 @@ var OperationQueue = class {
720
644
  operation.reject(/* @__PURE__ */ new Error("Queue cleared"));
721
645
  });
722
646
  this.queue = [];
723
- this.isProcessing = false;
647
+ this.processingPromise = null;
724
648
  }
725
649
  /**
726
650
  * 큐 크기 조회
@@ -732,13 +656,12 @@ var OperationQueue = class {
732
656
  * 처리 중 여부 조회
733
657
  */
734
658
  get processing() {
735
- return this.isProcessing;
659
+ return Boolean(this.processingPromise);
736
660
  }
737
661
  };
738
662
 
739
663
  //#endregion
740
664
  //#region src/ActionRegister.ts
741
- var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
742
665
  /**
743
666
  * Action Register for managing action handlers with priority-based execution
744
667
  *
@@ -748,100 +671,31 @@ var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1)
748
671
  *
749
672
  * @template TActionMap - Action payload mapping interface extending ActionPayloadMap
750
673
  *
751
- * @example Basic Usage
752
- * ```typescript
753
- * interface AppActions extends ActionPayloadMap {
754
- * updateUser: { id: string; name: string; email: string }
755
- * deleteUser: { id: string }
756
- * resetUser: void
757
- * }
758
- *
759
- * const register = new ActionRegister<AppActions>({
760
- * name: 'AppRegister',
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
- * ```
777
- *
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' })
787
- *
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' })
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
- * ```
674
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/
675
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
676
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/register-delegation
819
677
  *
820
678
  * @public
821
679
  */
822
680
  var ActionRegister = class {
823
681
  constructor(config = {}) {
824
- (0, import_defineProperty.default)(this, "pipelines", /* @__PURE__ */ new Map());
825
- (0, import_defineProperty.default)(this, "handlerCounter", 0);
826
- (0, import_defineProperty.default)(this, "actionGuard", void 0);
827
- (0, import_defineProperty.default)(this, "executionMode", "sequential");
828
- (0, import_defineProperty.default)(this, "actionExecutionModes", /* @__PURE__ */ new Map());
829
- (0, import_defineProperty.default)(this, "name", void 0);
830
- (0, import_defineProperty.default)(this, "registryConfig", void 0);
831
- (0, import_defineProperty.default)(this, "executionStats", /* @__PURE__ */ new Map());
832
- (0, import_defineProperty.default)(this, "registrationQueue", void 0);
833
- (0, import_defineProperty.default)(this, "dispatchQueue", void 0);
682
+ this.pipelines = /* @__PURE__ */ new Map();
683
+ this.executionMode = "sequential";
684
+ this.actionExecutionModes = /* @__PURE__ */ new Map();
685
+ this.filterCache = /* @__PURE__ */ new Map();
686
+ this.filterCacheMaxSize = 100;
834
687
  this.name = config.name || "ActionRegister";
835
688
  this.registryConfig = config.registry;
836
- this.actionGuard = new ActionGuard();
837
- this.registrationQueue = new OperationQueue(`${this.name}-Registration`);
838
- this.dispatchQueue = new OperationQueue(`${this.name}-Dispatch`);
689
+ this.maxHandlersPerAction = config.registry?.maxHandlersPerAction ?? 1e3;
690
+ this.isDebugMode = Boolean(this.registryConfig?.debug && process.env.NODE_ENV === "development");
691
+ this.actionGuard = new ActionGuard(this.registryConfig?.autoCleanup !== false);
692
+ if (config.registry?.useConcurrencyQueue !== false) this.dispatchQueue = new OperationQueue(`${this.name}-Dispatch`);
839
693
  if (this.registryConfig?.defaultExecutionMode) this.executionMode = this.registryConfig.defaultExecutionMode;
840
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 ActionRegister created: ${this.name}`, {
694
+ this.log("ActionRegister initialized", {
841
695
  defaultExecutionMode: this.executionMode,
842
- maxHandlers: this.registryConfig.maxHandlers,
843
- autoCleanup: this.registryConfig.autoCleanup ?? true,
844
- concurrencyProtection: true
696
+ autoCleanup: this.registryConfig?.autoCleanup !== false,
697
+ concurrencyQueue: Boolean(this.dispatchQueue),
698
+ debugMode: this.isDebugMode
845
699
  });
846
700
  }
847
701
  /**
@@ -855,105 +709,89 @@ var ActionRegister = class {
855
709
  *
856
710
  * @throws {Error} When maximum handlers limit is reached
857
711
  *
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
- * ```
712
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
881
713
  *
882
714
  * @public
883
715
  */
884
716
  register(action, handler, config = {}) {
885
- const handlerId = config.id || `handler_${++this.handlerCounter}_${Math.random().toString(36).substr(2, 5)}`;
717
+ const handlerId = config.id || this.generateHandlerId(action);
886
718
  const unregisterFn = this._performRegistrationSync(action, handler, config, handlerId);
887
719
  return unregisterFn;
888
720
  }
889
721
  /**
890
- * 🆕 동기적 등록 수행 (개선된 버전)
722
+ * 🆕 Unified logging method with cached debug mode check
891
723
  */
892
- _performRegistrationSync(action, handler, config, handlerId) {
893
- const registration = {
894
- handler,
895
- config: {
896
- priority: config.priority ?? 0,
897
- id: handlerId,
898
- blocking: config.blocking ?? false,
899
- once: config.once ?? false,
900
- condition: config.condition || (() => true),
901
- debounce: config.debounce ?? void 0,
902
- throttle: config.throttle ?? void 0,
903
- validation: config.validation ?? void 0,
904
- middleware: config.middleware ?? false,
905
- tags: config.tags ?? [],
906
- category: config.category ?? void 0,
907
- description: config.description ?? void 0,
908
- version: config.version ?? void 0,
909
- returnType: config.returnType ?? "value",
910
- timeout: config.timeout ?? void 0,
911
- retries: config.retries ?? 0,
912
- dependencies: config.dependencies ?? [],
913
- conflicts: config.conflicts ?? [],
914
- environment: config.environment ?? void 0,
915
- feature: config.feature ?? void 0,
916
- metrics: config.metrics ?? {
917
- collectTiming: false,
918
- collectErrors: false,
919
- customMetrics: {}
920
- },
921
- metadata: config.metadata ?? {}
922
- },
923
- id: handlerId
924
- };
925
- if (!this.pipelines.has(action)) this.pipelines.set(action, []);
926
- const pipeline = this.pipelines.get(action);
927
- const existingIndex = pipeline.findIndex((reg) => reg.id === handlerId);
928
- if (existingIndex !== -1) return () => {};
929
- if (this.registryConfig?.maxHandlers && pipeline.length >= this.registryConfig.maxHandlers) throw new Error(`Maximum number of handlers (${this.registryConfig.maxHandlers}) reached for action '${String(action)}' in registry '${this.name}'`);
930
- pipeline.push(registration);
931
- pipeline.sort((a, b) => b.config.priority - a.config.priority);
932
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler registered: ${String(action)}`, {
933
- handlerId,
934
- priority: config.priority,
935
- tags: config.tags,
936
- category: config.category,
937
- totalHandlers: pipeline.length,
938
- registry: this.name
939
- });
940
- return () => {
941
- const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
942
- if (index !== -1) {
943
- pipeline.splice(index, 1);
944
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler unregistered: ${String(action)}`, {
945
- handlerId,
946
- remainingHandlers: pipeline.length,
947
- registry: this.name
948
- });
949
- }
724
+ log(message, data, level = "log") {
725
+ if (this.isDebugMode) {
726
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
727
+ console[level](`🎯 [${timestamp}] [${this.name}] ${message}`, data || "");
728
+ }
729
+ }
730
+ /**
731
+ * 🆕 Generate unique handler ID using crypto
732
+ */
733
+ generateHandlerId(action) {
734
+ const uuid = crypto.randomUUID();
735
+ return `${String(action)}_${uuid.slice(0, 8)}`;
736
+ }
737
+ /**
738
+ * 🔧 Create and merge AbortSignal instances with proper cleanup
739
+ *
740
+ * @param options Dispatch options containing signal and autoAbort configuration
741
+ * @returns [effectiveSignal, autoAbortController, cleanupFunction]
742
+ */
743
+ createAbortSignal(options) {
744
+ const signals = [];
745
+ const cleanups = [];
746
+ let autoAbortController;
747
+ if (options?.signal) signals.push(options.signal);
748
+ if (options?.autoAbort?.enabled) {
749
+ autoAbortController = new AbortController();
750
+ signals.push(autoAbortController.signal);
751
+ }
752
+ if (signals.length === 0) return [
753
+ void 0,
754
+ autoAbortController,
755
+ () => {}
756
+ ];
757
+ if (signals.length === 1) return [
758
+ signals[0],
759
+ autoAbortController,
760
+ () => cleanups.forEach((c) => c())
761
+ ];
762
+ let effectiveSignal;
763
+ if (typeof AbortSignal.any === "function") effectiveSignal = AbortSignal.any(signals);
764
+ else {
765
+ const mergedController = new AbortController();
766
+ effectiveSignal = mergedController.signal;
767
+ signals.forEach((signal) => {
768
+ if (signal.aborted) mergedController.abort();
769
+ else {
770
+ const abortHandler = () => mergedController.abort();
771
+ signal.addEventListener("abort", abortHandler, { once: true });
772
+ cleanups.push(() => signal.removeEventListener("abort", abortHandler));
773
+ }
774
+ });
775
+ }
776
+ const cleanup = () => {
777
+ cleanups.forEach((c) => {
778
+ try {
779
+ c();
780
+ } catch (error) {
781
+ this.log("Cleanup error during AbortSignal cleanup", error, "warn");
782
+ }
783
+ });
950
784
  };
785
+ return [
786
+ effectiveSignal,
787
+ autoAbortController,
788
+ cleanup
789
+ ];
951
790
  }
952
791
  /**
953
- * 🆕 실제 등록 작업 수행 (큐에서 호출됨)
954
- * @deprecated Currently unused - reserved for future queue-based registration
792
+ * 🆕 Perform synchronous handler registration
955
793
  */
956
- _performRegistration(action, handler, config, handlerId) {
794
+ _performRegistrationSync(action, handler, config, handlerId) {
957
795
  const registration = {
958
796
  handler,
959
797
  config: {
@@ -961,54 +799,66 @@ var ActionRegister = class {
961
799
  id: handlerId,
962
800
  blocking: config.blocking ?? false,
963
801
  once: config.once ?? false,
964
- condition: config.condition || (() => true),
965
802
  debounce: config.debounce ?? void 0,
966
803
  throttle: config.throttle ?? void 0,
967
- validation: config.validation ?? void 0,
968
- middleware: config.middleware ?? false,
969
- tags: config.tags ?? [],
970
- category: config.category ?? void 0,
971
- description: config.description ?? void 0,
972
- version: config.version ?? void 0,
973
- returnType: config.returnType ?? "value",
974
- timeout: config.timeout ?? void 0,
975
- retries: config.retries ?? 0,
976
- dependencies: config.dependencies ?? [],
977
- conflicts: config.conflicts ?? [],
978
- environment: config.environment ?? void 0,
979
- feature: config.feature ?? void 0,
980
- metrics: config.metrics ?? {
981
- collectTiming: false,
982
- collectErrors: false,
983
- customMetrics: {}
984
- },
985
- metadata: config.metadata ?? {}
804
+ replaceExisting: config.replaceExisting ?? false
986
805
  },
987
806
  id: handlerId
988
807
  };
989
808
  if (!this.pipelines.has(action)) this.pipelines.set(action, []);
990
809
  const pipeline = this.pipelines.get(action);
810
+ if (pipeline.length >= this.maxHandlersPerAction) {
811
+ console.warn(`Handler limit (${this.maxHandlersPerAction}) reached for action "${String(action)}". Registration ignored.`);
812
+ return () => {};
813
+ }
991
814
  const existingIndex = pipeline.findIndex((reg) => reg.id === handlerId);
992
- if (existingIndex !== -1) return () => {};
993
- if (this.registryConfig?.maxHandlers && pipeline.length >= this.registryConfig.maxHandlers) throw new Error(`Maximum number of handlers (${this.registryConfig.maxHandlers}) reached for action '${String(action)}' in registry '${this.name}'`);
815
+ if (existingIndex !== -1) if (config.replaceExisting) {
816
+ const oldRegistration = pipeline[existingIndex];
817
+ if (oldRegistration && typeof oldRegistration.cleanup === "function") try {
818
+ oldRegistration.cleanup();
819
+ } catch (cleanupError) {
820
+ this.log(`Cleanup error for replaced handler: ${String(action)}`, cleanupError, "warn");
821
+ }
822
+ pipeline[existingIndex] = registration;
823
+ pipeline.sort((a, b) => b.config.priority - a.config.priority);
824
+ this.invalidateFilterCache();
825
+ this.log(`Handler replaced: ${String(action)}`, {
826
+ handlerId,
827
+ priority: config.priority,
828
+ totalHandlers: pipeline.length,
829
+ oldHandlerCleaned: Boolean(oldRegistration.cleanup)
830
+ });
831
+ return () => {
832
+ const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
833
+ if (index !== -1) {
834
+ pipeline.splice(index, 1);
835
+ this.invalidateFilterCache();
836
+ this.log(`Replaced handler unregistered: ${String(action)}`, { handlerId });
837
+ }
838
+ };
839
+ } else {
840
+ this.log(`Handler duplicate ignored: ${String(action)}`, {
841
+ handlerId,
842
+ note: "Use replaceExisting:true to replace"
843
+ }, "warn");
844
+ return () => {};
845
+ }
994
846
  pipeline.push(registration);
995
847
  pipeline.sort((a, b) => b.config.priority - a.config.priority);
996
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler registered: ${String(action)}`, {
848
+ this.invalidateFilterCache();
849
+ this.log(`Handler registered: ${String(action)}`, {
997
850
  handlerId,
998
851
  priority: config.priority,
999
- tags: config.tags,
1000
- category: config.category,
1001
- totalHandlers: pipeline.length,
1002
- registry: this.name
852
+ totalHandlers: pipeline.length
1003
853
  });
1004
854
  return () => {
1005
855
  const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
1006
856
  if (index !== -1) {
1007
857
  pipeline.splice(index, 1);
1008
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler unregistered: ${String(action)}`, {
858
+ this.invalidateFilterCache();
859
+ this.log(`Handler unregistered: ${String(action)}`, {
1009
860
  handlerId,
1010
- remainingHandlers: pipeline.length,
1011
- registry: this.name
861
+ remainingHandlers: pipeline.length
1012
862
  });
1013
863
  }
1014
864
  };
@@ -1024,39 +874,13 @@ var ActionRegister = class {
1024
874
  *
1025
875
  * @throws {Error} When action dispatching fails
1026
876
  *
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
- * ```
877
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
1055
878
  *
1056
879
  * @public
1057
880
  */
1058
881
  async dispatch(action, payload, options) {
1059
- return this.dispatchQueue.enqueue(async () => {
882
+ if (options?.immediate || !this.dispatchQueue) return this._performDispatch(action, payload, options);
883
+ else return this.dispatchQueue.enqueue(async () => {
1060
884
  return this._performDispatch(action, payload, options);
1061
885
  });
1062
886
  }
@@ -1064,47 +888,13 @@ var ActionRegister = class {
1064
888
  * 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
1065
889
  */
1066
890
  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
- }
1089
- let autoAbortController;
1090
- let effectiveSignal = options?.signal;
1091
- if (options?.autoAbort?.enabled) {
1092
- autoAbortController = new AbortController();
1093
- effectiveSignal = autoAbortController.signal;
1094
- if (options.autoAbort.onControllerCreated) options.autoAbort.onControllerCreated(autoAbortController);
1095
- if (options?.signal) {
1096
- const originalSignal = options.signal;
1097
- if (originalSignal.aborted) autoAbortController.abort();
1098
- else {
1099
- const abortHandler$1 = () => autoAbortController.abort();
1100
- originalSignal.addEventListener("abort", abortHandler$1, { once: true });
1101
- }
1102
- }
1103
- }
891
+ if (payload instanceof Event && process.env.NODE_ENV === "development") console.warn(`Event object passed to action "${String(action)}"`, payload.type);
892
+ const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
893
+ if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
1104
894
  if (effectiveSignal?.aborted) return;
1105
895
  const pipeline = this.pipelines.get(action);
1106
896
  if (!pipeline || pipeline.length === 0) return;
1107
- const filteredHandlers = this.filterHandlers([...pipeline], options?.filter);
897
+ const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
1108
898
  const actionKey = String(action);
1109
899
  let throttleMs;
1110
900
  let debounceMs;
@@ -1144,8 +934,6 @@ var ActionRegister = class {
1144
934
  terminated: false,
1145
935
  terminationResult: void 0
1146
936
  };
1147
- const startTime = Date.now();
1148
- let executionSuccess = true;
1149
937
  const abortHandler = effectiveSignal ? () => {
1150
938
  context.aborted = true;
1151
939
  context.abortReason = "Action dispatch aborted by signal";
@@ -1153,15 +941,12 @@ var ActionRegister = class {
1153
941
  if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
1154
942
  try {
1155
943
  await this.executePipeline(context, autoAbortController, options?.autoAbort);
1156
- console.log(`[ActionRegister] Pipeline execution succeeded for ${String(action)}`);
944
+ this.log(`Pipeline execution succeeded for ${String(action)}`);
1157
945
  } catch (error) {
1158
- console.log(`[ActionRegister] Pipeline execution failed for ${String(action)}:`, error);
1159
- executionSuccess = false;
946
+ this.log(`Pipeline execution failed for ${String(action)}`, error, "error");
1160
947
  throw error;
1161
948
  } finally {
1162
- if (effectiveSignal && abortHandler) effectiveSignal.removeEventListener("abort", abortHandler);
1163
- const duration = Date.now() - startTime;
1164
- this.updateExecutionStats(action, executionSuccess, duration);
949
+ cleanup();
1165
950
  }
1166
951
  }
1167
952
  /**
@@ -1173,65 +958,30 @@ var ActionRegister = class {
1173
958
  *
1174
959
  * @returns Promise resolving to comprehensive execution results
1175
960
  *
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
- * ```
961
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
1201
962
  *
1202
963
  * @public
1203
964
  */
1204
965
  async dispatchWithResult(action, payload, options) {
1205
- const startTime = Date.now();
1206
- let autoAbortController;
1207
- let effectiveSignal = options?.signal;
1208
- if (options?.autoAbort?.enabled) {
1209
- autoAbortController = new AbortController();
1210
- effectiveSignal = autoAbortController.signal;
1211
- if (options.autoAbort.onControllerCreated) options.autoAbort.onControllerCreated(autoAbortController);
1212
- if (options?.signal) {
1213
- const originalSignal = options.signal;
1214
- if (originalSignal.aborted) autoAbortController.abort();
1215
- else {
1216
- const abortHandler$1 = () => autoAbortController.abort();
1217
- originalSignal.addEventListener("abort", abortHandler$1, { once: true });
1218
- }
1219
- }
1220
- }
966
+ const _startTime = Date.now();
967
+ const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
968
+ if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
1221
969
  if (effectiveSignal?.aborted) return {
1222
970
  success: false,
1223
971
  aborted: true,
1224
972
  abortReason: "Action dispatch aborted by signal",
1225
973
  terminated: false,
1226
974
  result: void 0,
975
+ successResults: [],
1227
976
  results: [],
977
+ failedResults: [],
1228
978
  execution: {
1229
979
  duration: 0,
1230
980
  handlersExecuted: 0,
1231
981
  handlersSkipped: 0,
1232
982
  handlersFailed: 0,
1233
- startTime,
1234
- endTime: startTime
983
+ startTime: _startTime,
984
+ endTime: _startTime
1235
985
  },
1236
986
  handlers: [],
1237
987
  errors: []
@@ -1242,19 +992,21 @@ var ActionRegister = class {
1242
992
  aborted: false,
1243
993
  terminated: false,
1244
994
  result: void 0,
995
+ successResults: [],
1245
996
  results: [],
997
+ failedResults: [],
1246
998
  execution: {
1247
999
  duration: 0,
1248
1000
  handlersExecuted: 0,
1249
1001
  handlersSkipped: 0,
1250
1002
  handlersFailed: 0,
1251
- startTime,
1252
- endTime: startTime
1003
+ startTime: _startTime,
1004
+ endTime: _startTime
1253
1005
  },
1254
1006
  handlers: [],
1255
1007
  errors: []
1256
1008
  };
1257
- const filteredHandlers = this.filterHandlers([...pipeline], options?.filter);
1009
+ const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
1258
1010
  const actionKey = String(action);
1259
1011
  let throttleMs;
1260
1012
  let debounceMs;
@@ -1280,13 +1032,15 @@ var ActionRegister = class {
1280
1032
  abortReason: "Debounced execution",
1281
1033
  terminated: false,
1282
1034
  result: void 0,
1035
+ successResults: [],
1283
1036
  results: [],
1037
+ failedResults: [],
1284
1038
  execution: {
1285
- duration: Date.now() - startTime,
1039
+ duration: Date.now() - _startTime,
1286
1040
  handlersExecuted: 0,
1287
1041
  handlersSkipped: pipeline.length,
1288
1042
  handlersFailed: 0,
1289
- startTime,
1043
+ startTime: _startTime,
1290
1044
  endTime: Date.now()
1291
1045
  },
1292
1046
  handlers: [],
@@ -1301,13 +1055,15 @@ var ActionRegister = class {
1301
1055
  abortReason: "Throttled execution",
1302
1056
  terminated: false,
1303
1057
  result: void 0,
1058
+ successResults: [],
1304
1059
  results: [],
1060
+ failedResults: [],
1305
1061
  execution: {
1306
- duration: Date.now() - startTime,
1062
+ duration: Date.now() - _startTime,
1307
1063
  handlersExecuted: 0,
1308
1064
  handlersSkipped: pipeline.length,
1309
1065
  handlersFailed: 0,
1310
- startTime,
1066
+ startTime: _startTime,
1311
1067
  endTime: Date.now()
1312
1068
  },
1313
1069
  handlers: [],
@@ -1346,59 +1102,92 @@ var ActionRegister = class {
1346
1102
  timestamp: Date.now()
1347
1103
  });
1348
1104
  } finally {
1349
- if (effectiveSignal && abortHandler) effectiveSignal.removeEventListener("abort", abortHandler);
1105
+ cleanup();
1350
1106
  }
1351
1107
  const endTime = Date.now();
1352
- const executionSuccess = !executionError && !context.aborted;
1353
- this.updateExecutionStats(action, executionSuccess, endTime - startTime);
1108
+ !executionError && context.aborted;
1354
1109
  const processedResult = this.processResults(context, options?.result);
1110
+ const successResults = context.results.filter((result) => result !== void 0);
1111
+ const failedResults = errors.map((err) => ({
1112
+ handlerId: err.handlerId,
1113
+ error: err.error,
1114
+ expectedType: typeof processedResult
1115
+ }));
1355
1116
  const executionResult = {
1356
1117
  success: !executionError && !context.aborted,
1357
1118
  aborted: context.aborted,
1358
1119
  abortReason: context.abortReason,
1359
1120
  terminated: context.terminated,
1360
1121
  result: processedResult,
1122
+ successResults,
1361
1123
  results: context.results,
1124
+ failedResults,
1362
1125
  execution: {
1363
- duration: endTime - startTime,
1126
+ duration: endTime - _startTime,
1364
1127
  handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
1365
1128
  handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
1366
1129
  handlersFailed: errors.length,
1367
- startTime,
1130
+ startTime: _startTime,
1368
1131
  endTime
1369
1132
  },
1370
1133
  handlers: handlerResults,
1371
- errors
1134
+ errors: errors.map((err) => ({
1135
+ handlerId: err.handlerId,
1136
+ error: err.error,
1137
+ timestamp: err.timestamp,
1138
+ severity: "non-blocking"
1139
+ }))
1372
1140
  };
1373
1141
  /** Clean up one-time handlers after execution */
1374
1142
  this.cleanupOneTimeHandlers(action, context.handlers);
1375
1143
  return executionResult;
1376
1144
  }
1145
+ /**
1146
+ * 🔧 Generate cache key for filter options
1147
+ */
1148
+ generateFilterCacheKey(filterOptions) {
1149
+ if (!filterOptions) return "no-filter";
1150
+ const key = [
1151
+ filterOptions.handlerIds?.sort().join(",") || "none",
1152
+ filterOptions.excludeHandlerIds?.sort().join(",") || "none",
1153
+ filterOptions.priority?.min?.toString() || "none",
1154
+ filterOptions.priority?.max?.toString() || "none",
1155
+ filterOptions.custom ? "custom" : "none"
1156
+ ].join("|");
1157
+ return key;
1158
+ }
1159
+ /**
1160
+ * 🔧 Clear filter cache when pipelines change
1161
+ */
1162
+ invalidateFilterCache() {
1163
+ this.filterCache.clear();
1164
+ }
1377
1165
  filterHandlers(handlers, filterOptions) {
1378
1166
  if (!filterOptions) return handlers;
1379
- return handlers.filter((registration) => {
1167
+ const cacheKey = this.generateFilterCacheKey(filterOptions);
1168
+ if (!filterOptions.custom) {
1169
+ const cached = this.filterCache.get(cacheKey);
1170
+ if (cached) return cached;
1171
+ }
1172
+ const filtered = handlers.filter((registration) => {
1380
1173
  const config = registration.config;
1381
- if (filterOptions.tags && filterOptions.tags.length > 0) {
1382
- const hasMatchingTag = filterOptions.tags.some((tag) => config.tags.includes(tag));
1383
- if (!hasMatchingTag) return false;
1384
- }
1385
- if (filterOptions.category && config.category !== filterOptions.category) return false;
1386
- if (filterOptions.handlerIds && filterOptions.handlerIds.length > 0) {
1387
- if (!filterOptions.handlerIds.includes(config.id)) return false;
1388
- }
1389
- if (filterOptions.environment && config.environment !== filterOptions.environment) return false;
1390
- if (filterOptions.feature && config.feature !== filterOptions.feature) return false;
1391
- if (filterOptions.excludeTags && filterOptions.excludeTags.length > 0) {
1392
- const hasExcludedTag = filterOptions.excludeTags.some((tag) => config.tags.includes(tag));
1393
- if (hasExcludedTag) return false;
1394
- }
1395
- if (filterOptions.excludeCategory && config.category === filterOptions.excludeCategory) return false;
1396
- if (filterOptions.excludeHandlerIds && filterOptions.excludeHandlerIds.length > 0) {
1397
- if (filterOptions.excludeHandlerIds.includes(config.id)) return false;
1174
+ if (filterOptions.handlerIds?.length && !filterOptions.handlerIds.includes(config.id)) return false;
1175
+ if (filterOptions.excludeHandlerIds?.length && filterOptions.excludeHandlerIds.includes(config.id)) return false;
1176
+ if (filterOptions.priority) {
1177
+ if (filterOptions.priority.min !== void 0 && config.priority < filterOptions.priority.min) return false;
1178
+ if (filterOptions.priority.max !== void 0 && config.priority > filterOptions.priority.max) return false;
1398
1179
  }
1399
1180
  if (filterOptions.custom && !filterOptions.custom(config)) return false;
1400
1181
  return true;
1401
1182
  });
1183
+ if (!filterOptions.custom) {
1184
+ if (this.filterCache.size >= this.filterCacheMaxSize) {
1185
+ const firstKey = this.filterCache.keys().next().value;
1186
+ if (firstKey) this.filterCache.delete(firstKey);
1187
+ }
1188
+ this.filterCache.set(cacheKey, filtered);
1189
+ }
1190
+ return filtered;
1402
1191
  }
1403
1192
  processResults(context, resultOptions) {
1404
1193
  if (!resultOptions || !resultOptions.collect) return void 0;
@@ -1484,39 +1273,13 @@ var ActionRegister = class {
1484
1273
  });
1485
1274
  }
1486
1275
  /**
1487
- * Update execution statistics for an action
1488
- *
1489
- * @param action Action name
1490
- * @param success Whether execution was successful
1491
- * @param duration Execution duration in milliseconds
1492
- */
1493
- updateExecutionStats(action, success, duration) {
1494
- if (!this.executionStats.has(action)) this.executionStats.set(action, {
1495
- totalExecutions: 0,
1496
- totalDuration: 0,
1497
- successCount: 0,
1498
- errorCount: 0
1499
- });
1500
- const stats = this.executionStats.get(action);
1501
- stats.totalExecutions++;
1502
- stats.totalDuration += duration;
1503
- if (success) stats.successCount++;
1504
- else stats.errorCount++;
1505
- }
1506
- /**
1507
1276
  * Get the number of registered handlers for an action
1508
1277
  *
1509
1278
  * @param action - The action type to count handlers for
1510
1279
  *
1511
1280
  * @returns Number of registered handlers
1512
1281
  *
1513
- * @example
1514
- * ```typescript
1515
- * register.register('updateUser', handler1)
1516
- * register.register('updateUser', handler2)
1517
- *
1518
- * console.log(register.getHandlerCount('updateUser')) // 2
1519
- * ```
1282
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
1520
1283
  *
1521
1284
  * @public
1522
1285
  */
@@ -1531,12 +1294,7 @@ var ActionRegister = class {
1531
1294
  *
1532
1295
  * @returns True if action has handlers, false otherwise
1533
1296
  *
1534
- * @example
1535
- * ```typescript
1536
- * if (register.hasHandlers('updateUser')) {
1537
- * await register.dispatch('updateUser', userData)
1538
- * }
1539
- * ```
1297
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
1540
1298
  *
1541
1299
  * @public
1542
1300
  */
@@ -1548,11 +1306,7 @@ var ActionRegister = class {
1548
1306
  *
1549
1307
  * @returns Array of all registered action types
1550
1308
  *
1551
- * @example
1552
- * ```typescript
1553
- * const actions = register.getRegisteredActions()
1554
- * console.log('Registered actions:', actions) // ['updateUser', 'deleteUser', 'resetUser']
1555
- * ```
1309
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
1556
1310
  *
1557
1311
  * @public
1558
1312
  */
@@ -1564,41 +1318,31 @@ var ActionRegister = class {
1564
1318
  *
1565
1319
  * @param action - The action type to clear handlers for
1566
1320
  *
1567
- * @example
1568
- * ```typescript
1569
- * register.clearAction('updateUser')
1570
- * console.log(register.hasHandlers('updateUser')) // false
1571
- * ```
1321
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
1572
1322
  *
1573
1323
  * @public
1574
1324
  */
1575
1325
  clearAction(action) {
1576
1326
  this.pipelines.delete(action);
1327
+ this.invalidateFilterCache();
1577
1328
  }
1578
1329
  /**
1579
1330
  * Remove all handlers for all actions
1580
1331
  *
1581
- * @example
1582
- * ```typescript
1583
- * register.clearAll()
1584
- * console.log(register.getRegisteredActions().length) // 0
1585
- * ```
1332
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
1586
1333
  *
1587
1334
  * @public
1588
1335
  */
1589
1336
  clearAll() {
1590
1337
  this.pipelines.clear();
1338
+ this.invalidateFilterCache();
1591
1339
  }
1592
1340
  /**
1593
1341
  * Get the name of this action register
1594
1342
  *
1595
1343
  * @returns The register name
1596
1344
  *
1597
- * @example
1598
- * ```typescript
1599
- * const register = new ActionRegister({ name: 'UserRegister' })
1600
- * console.log(register.getName()) // 'UserRegister'
1601
- * ```
1345
+ * @see https://mineclover.github.io/context-action/en/guide/patterns/action/basic-usage
1602
1346
  *
1603
1347
  * @public
1604
1348
  */
@@ -1637,21 +1381,9 @@ var ActionRegister = class {
1637
1381
  });
1638
1382
  const handlersByPriority = Array.from(priorityMap.entries()).sort(([a], [b]) => b - a).map(([priority, handlers]) => ({
1639
1383
  priority,
1640
- handlers: handlers.map((h) => ({
1641
- id: h.config.id,
1642
- tags: h.config.tags,
1643
- category: h.config.category,
1644
- description: h.config.description,
1645
- version: h.config.version
1646
- }))
1384
+ handlers: handlers.map((h) => ({ id: h.config.id }))
1647
1385
  }));
1648
- const stats = this.executionStats.get(action);
1649
- const executionStats = stats ? {
1650
- totalExecutions: stats.totalExecutions,
1651
- averageDuration: stats.totalExecutions > 0 ? stats.totalDuration / stats.totalExecutions : 0,
1652
- successRate: stats.totalExecutions > 0 ? stats.successCount / stats.totalExecutions * 100 : 0,
1653
- errorCount: stats.errorCount
1654
- } : void 0;
1386
+ const executionStats = void 0;
1655
1387
  return {
1656
1388
  action,
1657
1389
  handlerCount: pipeline.length,
@@ -1669,34 +1401,6 @@ var ActionRegister = class {
1669
1401
  return Array.from(this.pipelines.keys()).map((action) => this.getActionStats(action)).filter((stats) => stats !== null);
1670
1402
  }
1671
1403
  /**
1672
- * Get handlers by tag across all actions
1673
- *
1674
- * @param tag Tag to filter handlers by
1675
- * @returns Map of actions to handlers with the specified tag
1676
- */
1677
- getHandlersByTag(tag) {
1678
- const result = /* @__PURE__ */ new Map();
1679
- for (const [action, pipeline] of this.pipelines.entries()) {
1680
- const matchingHandlers = pipeline.filter((handler) => handler.config.tags.includes(tag));
1681
- if (matchingHandlers.length > 0) result.set(action, matchingHandlers);
1682
- }
1683
- return result;
1684
- }
1685
- /**
1686
- * Get handlers by category across all actions
1687
- *
1688
- * @param category Category to filter handlers by
1689
- * @returns Map of actions to handlers with the specified category
1690
- */
1691
- getHandlersByCategory(category) {
1692
- const result = /* @__PURE__ */ new Map();
1693
- for (const [action, pipeline] of this.pipelines.entries()) {
1694
- const matchingHandlers = pipeline.filter((handler) => handler.config.category === category);
1695
- if (matchingHandlers.length > 0) result.set(action, matchingHandlers);
1696
- }
1697
- return result;
1698
- }
1699
- /**
1700
1404
  * Set execution mode for a specific action
1701
1405
  *
1702
1406
  * @param action Action name
@@ -1725,22 +1429,6 @@ var ActionRegister = class {
1725
1429
  if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution mode reset for action '${String(action)}' to default: ${this.executionMode}`);
1726
1430
  }
1727
1431
  /**
1728
- * Clear execution statistics for all actions
1729
- */
1730
- clearExecutionStats() {
1731
- this.executionStats.clear();
1732
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution statistics cleared for registry: ${this.name}`);
1733
- }
1734
- /**
1735
- * Clear execution statistics for a specific action
1736
- *
1737
- * @param action Action name
1738
- */
1739
- clearActionExecutionStats(action) {
1740
- this.executionStats.delete(action);
1741
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution statistics cleared for action: ${String(action)}`);
1742
- }
1743
- /**
1744
1432
  * Get registry configuration (for debugging and inspection)
1745
1433
  *
1746
1434
  * @returns Current registry configuration
@@ -1754,13 +1442,292 @@ var ActionRegister = class {
1754
1442
  * @returns Whether debug mode is enabled
1755
1443
  */
1756
1444
  isDebugEnabled() {
1757
- return Boolean(this.registryConfig?.debug && process.env.NODE_ENV === "development");
1445
+ return this.isDebugMode;
1446
+ }
1447
+ /**
1448
+ * 🆕 Destroy method for comprehensive cleanup
1449
+ *
1450
+ * Cleans up all internal resources including pipelines, guards, queues, and statistics.
1451
+ * Should be called when the ActionRegister is no longer needed to prevent memory leaks.
1452
+ *
1453
+ * @public
1454
+ */
1455
+ destroy() {
1456
+ this.pipelines.clear();
1457
+ this.actionGuard.destroy();
1458
+ this.dispatchQueue?.clear?.();
1459
+ this.actionExecutionModes.clear();
1460
+ this.filterCache.clear();
1461
+ this.log("ActionRegister destroyed");
1462
+ }
1463
+ };
1464
+
1465
+ //#endregion
1466
+ //#region src/react-helpers.ts
1467
+ /**
1468
+ * 🔧 Create action handler registration configuration for React components
1469
+ *
1470
+ * Creates a configuration object that can be used with React's useEffect to properly
1471
+ * register and unregister action handlers with lifecycle management and cleanup.
1472
+ * This is NOT a hook - it's a factory function for React hook integration.
1473
+ *
1474
+ * @template T - ActionPayloadMap type
1475
+ * @template K - Action key type
1476
+ *
1477
+ * @param registry - ActionRegister instance
1478
+ * @param action - Action name to register handler for
1479
+ * @param handler - Handler function (should be memoized with useCallback)
1480
+ * @param config - Handler configuration
1481
+ *
1482
+ * @returns Configuration object with register/unregister functions
1483
+ *
1484
+ * @example Basic Usage with useEffect
1485
+ * ```tsx
1486
+ * import { useCallback, useEffect } from 'react';
1487
+ * import { createActionHandler } from '@context-action/core/react-helpers';
1488
+ *
1489
+ * function MyComponent() {
1490
+ * const registry = useActionRegister();
1491
+ *
1492
+ * const handleUserUpdate = useCallback(async (payload, controller) => {
1493
+ * // Handler logic here
1494
+ * }, []);
1495
+ *
1496
+ * useEffect(() => {
1497
+ * const { register, unregister } = createActionHandler(
1498
+ * registry,
1499
+ * 'updateUser',
1500
+ * handleUserUpdate,
1501
+ * { priority: 10 }
1502
+ * );
1503
+ *
1504
+ * const cleanup = register();
1505
+ * return () => {
1506
+ * cleanup();
1507
+ * unregister();
1508
+ * };
1509
+ * }, [registry, handleUserUpdate]);
1510
+ * }
1511
+ * ```
1512
+ *
1513
+ * @example With Automatic Cleanup
1514
+ * ```tsx
1515
+ * const [userId, setUserId] = useState('123');
1516
+ *
1517
+ * const handleUserUpdate = useCallback(async (payload, controller) => {
1518
+ * console.log('Updating user:', userId, payload);
1519
+ * }, [userId]);
1520
+ *
1521
+ * useEffect(() => {
1522
+ * const handlerManager = createActionHandler(
1523
+ * registry,
1524
+ * 'updateUser',
1525
+ * handleUserUpdate,
1526
+ * { priority: 10 }
1527
+ * );
1528
+ *
1529
+ * // Simplified registration with automatic cleanup
1530
+ * return handlerManager.registerWithCleanup();
1531
+ * }, [registry, handleUserUpdate, userId]);
1532
+ * ```
1533
+ *
1534
+ * @public
1535
+ */
1536
+ function createActionHandler(registry, action, handler, config) {
1537
+ const finalConfig = createReactHandlerConfig(String(action), void 0, config);
1538
+ let currentUnregister;
1539
+ let isRegistered = false;
1540
+ return {
1541
+ register() {
1542
+ if (isRegistered && currentUnregister) currentUnregister();
1543
+ currentUnregister = registry.register(action, handler, finalConfig);
1544
+ isRegistered = true;
1545
+ return currentUnregister;
1546
+ },
1547
+ unregister() {
1548
+ if (isRegistered && currentUnregister) {
1549
+ currentUnregister();
1550
+ currentUnregister = void 0;
1551
+ isRegistered = false;
1552
+ }
1553
+ },
1554
+ registerWithCleanup() {
1555
+ const unregisterFn = this.register();
1556
+ return () => {
1557
+ unregisterFn();
1558
+ this.unregister();
1559
+ };
1560
+ },
1561
+ config: finalConfig
1562
+ };
1563
+ }
1564
+ /**
1565
+ * 🆕 React handler configuration factory
1566
+ *
1567
+ * Creates optimized handler configurations for React environments with
1568
+ * proper cleanup and unique ID generation.
1569
+ *
1570
+ * @template T - ActionPayloadMap type
1571
+ * @template K - Action key type
1572
+ *
1573
+ * @param action - Action name
1574
+ * @param componentId - Optional component identifier for debugging
1575
+ * @param config - Base handler configuration
1576
+ *
1577
+ * @returns Optimized configuration for React environments
1578
+ *
1579
+ * @example
1580
+ * ```tsx
1581
+ * function MyComponent({ userId }: { userId: string }) {
1582
+ * const registry = useActionRegister();
1583
+ *
1584
+ * useEffect(() => {
1585
+ * const config = createReactHandlerConfig('updateUser', 'MyComponent', {
1586
+ * priority: 10
1587
+ * });
1588
+ *
1589
+ * const unregister = registry.register('updateUser', handler, config);
1590
+ * return unregister;
1591
+ * }, [registry, handler]);
1592
+ * }
1593
+ * ```
1594
+ *
1595
+ * @public
1596
+ */
1597
+ function createReactHandlerConfig(action, componentId, config = {}) {
1598
+ const timestamp = Date.now();
1599
+ const random = Math.random().toString(36).substr(2, 5);
1600
+ return {
1601
+ priority: config.priority ?? 0,
1602
+ id: config.id || `${componentId || "react"}_${action}_${timestamp}_${random}`,
1603
+ blocking: config.blocking ?? false,
1604
+ once: config.once ?? false,
1605
+ debounce: config.debounce ?? void 0,
1606
+ throttle: config.throttle ?? void 0,
1607
+ replaceExisting: true
1608
+ };
1609
+ }
1610
+ /**
1611
+ * 🆕 React action dispatcher factory
1612
+ *
1613
+ * Creates a dispatcher function optimized for React component usage
1614
+ * with proper error boundaries and async handling.
1615
+ *
1616
+ * @template T - ActionPayloadMap type
1617
+ *
1618
+ * @param registry - ActionRegister instance
1619
+ * @param errorHandler - Optional error handler for unhandled dispatch errors
1620
+ *
1621
+ * @returns Optimized dispatch function for React components
1622
+ *
1623
+ * @example
1624
+ * ```tsx
1625
+ * function MyComponent() {
1626
+ * const registry = useActionRegister();
1627
+ *
1628
+ * const dispatch = createReactDispatcher(registry, (error, action, payload) => {
1629
+ * console.error(`Failed to dispatch ${action}:`, error);
1630
+ * });
1631
+ *
1632
+ * const handleClick = useCallback(() => {
1633
+ * dispatch('userClick', { buttonId: 'submit' });
1634
+ * }, [dispatch]);
1635
+ * }
1636
+ * ```
1637
+ *
1638
+ * @public
1639
+ */
1640
+ function createReactDispatcher(registry, errorHandler) {
1641
+ return async (action, payload, options) => {
1642
+ try {
1643
+ await registry.dispatch(action, payload, {
1644
+ immediate: false,
1645
+ ...options
1646
+ });
1647
+ } catch (error) {
1648
+ const errorObj = error instanceof Error ? error : new Error(String(error));
1649
+ if (errorHandler) errorHandler(errorObj, action, payload);
1650
+ else console.error(`[ActionRegister] Dispatch failed for action '${String(action)}':`, errorObj);
1651
+ }
1652
+ };
1653
+ }
1654
+ /**
1655
+ * 🆕 React development utilities
1656
+ *
1657
+ * Provides debugging and development helpers specifically for React environments.
1658
+ */
1659
+ const ReactDevUtils = {
1660
+ enableDebugMode() {
1661
+ if (typeof window !== "undefined") window.__CONTEXT_ACTION_REACT_DEBUG__ = true;
1662
+ },
1663
+ disableDebugMode() {
1664
+ if (typeof window !== "undefined") window.__CONTEXT_ACTION_REACT_DEBUG__ = false;
1665
+ },
1666
+ isDebugMode() {
1667
+ return typeof window !== "undefined" && Boolean(window.__CONTEXT_ACTION_REACT_DEBUG__);
1668
+ },
1669
+ log(component, action, message, data) {
1670
+ if (this.isDebugMode()) console.log(`🎯 [React-ActionRegister] [${component}] ${action}: ${message}`, data || "");
1671
+ },
1672
+ getStats(registry) {
1673
+ const registryInfo = registry.getRegistryInfo();
1674
+ let reactHandlers = 0;
1675
+ registry.getRegisteredActions().forEach((action) => {
1676
+ const stats = registry.getActionStats(action);
1677
+ if (stats) stats.handlersByPriority.forEach((priorityGroup) => {
1678
+ priorityGroup.handlers.forEach((handler) => {
1679
+ if (handler.id.includes("react")) reactHandlers++;
1680
+ });
1681
+ });
1682
+ });
1683
+ return {
1684
+ totalHandlers: registryInfo.totalHandlers,
1685
+ reactHandlers,
1686
+ registryInfo
1687
+ };
1758
1688
  }
1759
1689
  };
1690
+ /**
1691
+ * 🆕 React Error Boundary integration
1692
+ *
1693
+ * Utilities for integrating ActionRegister errors with React Error Boundaries.
1694
+ */
1695
+ var ReactActionError = class ReactActionError extends Error {
1696
+ constructor(message, action, payload, handlerId, originalError) {
1697
+ super(message);
1698
+ this.name = "ReactActionError";
1699
+ this.action = action;
1700
+ this.payload = payload;
1701
+ this.handlerId = handlerId;
1702
+ this.timestamp = Date.now();
1703
+ if (originalError && originalError.stack) this.stack = originalError.stack;
1704
+ }
1705
+ /**
1706
+ * Create a React Error Boundary compatible error
1707
+ */
1708
+ static fromActionError(originalError, action, payload, handlerId) {
1709
+ return new ReactActionError(`Action '${action}' failed: ${originalError.message}`, action, payload, handlerId, originalError);
1710
+ }
1711
+ };
1712
+ /**
1713
+ * 🆕 Type guard for React Action Errors
1714
+ *
1715
+ * @param error - Error to check
1716
+ * @returns True if error is a ReactActionError
1717
+ */
1718
+ function isReactActionError(error) {
1719
+ return error instanceof ReactActionError;
1720
+ }
1760
1721
 
1761
1722
  //#endregion
1762
1723
  exports.ActionGuard = ActionGuard;
1763
1724
  exports.ActionRegister = ActionRegister;
1725
+ exports.ReactActionError = ReactActionError;
1726
+ exports.ReactDevUtils = ReactDevUtils;
1727
+ exports.createActionHandler = createActionHandler;
1728
+ exports.createReactDispatcher = createReactDispatcher;
1729
+ exports.createReactHandlerConfig = createReactHandlerConfig;
1764
1730
  exports.executeParallel = executeParallel;
1765
1731
  exports.executeRace = executeRace;
1766
- exports.executeSequential = executeSequential;
1732
+ exports.executeSequential = executeSequential;
1733
+ exports.isReactActionError = isReactActionError;