@context-action/core 0.4.0 → 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.js CHANGED
@@ -1,31 +1,23 @@
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
  //#region src/execution-modes.ts
28
2
  /**
3
+ * Create standardized error handling for handlers
4
+ *
5
+ * @param error - The error that occurred
6
+ * @param registration - The handler registration that failed
7
+ * @returns Standardized HandlerError object
8
+ *
9
+ * @internal
10
+ */
11
+ function handleExecutionError(error, registration) {
12
+ const errorObj = error instanceof Error ? error : new Error(String(error));
13
+ return {
14
+ handlerId: registration.id,
15
+ error: errorObj,
16
+ timestamp: Date.now(),
17
+ severity: registration.config.blocking ? "blocking" : "non-blocking"
18
+ };
19
+ }
20
+ /**
29
21
  * Execute handlers in sequential mode (one after another)
30
22
  *
31
23
  * Executes action handlers one at a time in priority order (highest first).
@@ -48,6 +40,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
48
40
  async function executeSequential(context, createController) {
49
41
  let i = 0;
50
42
  const nonBlockingPromises = [];
43
+ const errors = [];
51
44
  while (i < context.handlers.length) {
52
45
  if (context.aborted || context.terminated) break;
53
46
  const registration = context.handlers[i];
@@ -56,22 +49,24 @@ async function executeSequential(context, createController) {
56
49
  try {
57
50
  if (context.aborted) break;
58
51
  const result = registration.handler(context.payload, controller);
59
- /** Wait for async handlers if they're blocking */
60
- if (registration.config.blocking && result instanceof Promise) {
61
- const handlerResult = await result;
62
- /** Collect result if handler returned something and wasn't terminated */
52
+ if (registration.config.blocking) {
53
+ const handlerResult = result instanceof Promise ? await result : result;
63
54
  if (handlerResult !== void 0 && !context.terminated) context.results.push(handlerResult);
64
- } else if (result !== void 0 && !context.terminated)
65
- /** Collect synchronous result */
66
- if (result instanceof Promise) {
67
- const promiseWithHandling = result.then((asyncResult) => {
55
+ } else if (result instanceof Promise) {
56
+ const promiseWithErrorHandling = result.then((asyncResult) => {
68
57
  if (asyncResult !== void 0 && !context.terminated) context.results.push(asyncResult);
69
58
  return asyncResult;
70
59
  }).catch((error) => {
71
- throw error;
60
+ const handlerError = handleExecutionError(error, registration);
61
+ errors.push({
62
+ handlerId: handlerError.handlerId,
63
+ error: handlerError.error,
64
+ timestamp: handlerError.timestamp
65
+ });
66
+ return void 0;
72
67
  });
73
- nonBlockingPromises.push(promiseWithHandling);
74
- } else context.results.push(result);
68
+ nonBlockingPromises.push(promiseWithErrorHandling);
69
+ } else if (result !== void 0 && !context.terminated) context.results.push(result);
75
70
  /** Check if pipeline was terminated by controller.return() */
76
71
  if (context.terminated) break;
77
72
  /** Handle jump to priority AFTER handler execution */
@@ -87,11 +82,20 @@ async function executeSequential(context, createController) {
87
82
  }
88
83
  } else i++;
89
84
  } catch (error) {
90
- if (registration.config.blocking) throw error;
91
- throw error;
85
+ const handlerError = handleExecutionError(error, registration);
86
+ throw handlerError.error;
92
87
  }
93
88
  }
94
- if (nonBlockingPromises.length > 0) await Promise.all(nonBlockingPromises);
89
+ if (nonBlockingPromises.length > 0) await Promise.allSettled(nonBlockingPromises);
90
+ if (errors.length > 0) {
91
+ const handlerErrors = errors.map((err) => ({
92
+ handlerId: err.handlerId,
93
+ error: err.error,
94
+ timestamp: err.timestamp,
95
+ severity: "non-blocking"
96
+ }));
97
+ context.collectedErrors = handlerErrors;
98
+ }
95
99
  }
96
100
  /**
97
101
  * Execute handlers in parallel mode (all at once)
@@ -134,11 +138,12 @@ async function executeParallel(context, createController) {
134
138
  terminated: context.terminated
135
139
  };
136
140
  } catch (error) {
137
- if (registration.config.blocking) throw error;
141
+ const handlerError = handleExecutionError(error, registration);
142
+ if (handlerError.severity === "blocking") throw handlerError.error;
138
143
  return {
139
144
  success: false,
140
145
  handlerId: registration.id,
141
- error
146
+ error: handlerError.error
142
147
  };
143
148
  }
144
149
  });
@@ -206,10 +211,11 @@ async function executeRace(context, createController) {
206
211
  terminated: context.terminated
207
212
  };
208
213
  } catch (error) {
214
+ const handlerError = handleExecutionError(error, registration);
209
215
  return {
210
216
  success: false,
211
217
  handlerId: registration.id,
212
- error,
218
+ error: handlerError.error,
213
219
  registration
214
220
  };
215
221
  }
@@ -227,67 +233,8 @@ async function executeRace(context, createController) {
227
233
  }
228
234
  }
229
235
 
230
- //#endregion
231
- //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js
232
- 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) => {
233
- function _typeof$2(o) {
234
- "@babel/helpers - typeof";
235
- return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
236
- return typeof o$1;
237
- } : function(o$1) {
238
- return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
239
- }, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
240
- }
241
- module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
242
- }) });
243
-
244
- //#endregion
245
- //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js
246
- 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) => {
247
- var _typeof$1 = require_typeof()["default"];
248
- function toPrimitive$1(t, r) {
249
- if ("object" != _typeof$1(t) || !t) return t;
250
- var e = t[Symbol.toPrimitive];
251
- if (void 0 !== e) {
252
- var i = e.call(t, r || "default");
253
- if ("object" != _typeof$1(i)) return i;
254
- throw new TypeError("@@toPrimitive must return a primitive value.");
255
- }
256
- return ("string" === r ? String : Number)(t);
257
- }
258
- module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
259
- }) });
260
-
261
- //#endregion
262
- //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js
263
- 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) => {
264
- var _typeof = require_typeof()["default"];
265
- var toPrimitive = require_toPrimitive();
266
- function toPropertyKey$1(t) {
267
- var i = toPrimitive(t, "string");
268
- return "symbol" == _typeof(i) ? i : i + "";
269
- }
270
- module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
271
- }) });
272
-
273
- //#endregion
274
- //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js
275
- 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) => {
276
- var toPropertyKey = require_toPropertyKey();
277
- function _defineProperty$3(e, r, t) {
278
- return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
279
- value: t,
280
- enumerable: !0,
281
- configurable: !0,
282
- writable: !0
283
- }) : e[r] = t, e;
284
- }
285
- module.exports = _defineProperty$3, module.exports.__esModule = true, module.exports["default"] = module.exports;
286
- }) });
287
-
288
236
  //#endregion
289
237
  //#region src/action-guard.ts
290
- var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
291
238
  /**
292
239
  * Action Guard system for managing action execution timing
293
240
  *
@@ -317,8 +264,31 @@ var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(),
317
264
  * @internal
318
265
  */
319
266
  var ActionGuard = class {
320
- constructor() {
321
- (0, import_defineProperty$2.default)(this, "guards", /* @__PURE__ */ new Map());
267
+ constructor(autoCleanup = true) {
268
+ this.guards = /* @__PURE__ */ new Map();
269
+ this.maxIdleTime = 6e4;
270
+ this.cleanupIntervalMs = 3e4;
271
+ if (autoCleanup) this.startAutoCleanup();
272
+ }
273
+ /**
274
+ * Start automatic cleanup of idle guard states
275
+ *
276
+ * @internal
277
+ */
278
+ startAutoCleanup() {
279
+ this.cleanupInterval = setInterval(() => {
280
+ const now = Date.now();
281
+ const keysToDelete = [];
282
+ this.guards.forEach((state, key) => {
283
+ const isIdle = now - state.lastExecuted > this.maxIdleTime;
284
+ const hasActiveTimers = state.debounceTimer || state.throttleTimer;
285
+ if (isIdle && !hasActiveTimers) keysToDelete.push(key);
286
+ });
287
+ if (keysToDelete.length > 0) {
288
+ keysToDelete.forEach((key) => this.guards.delete(key));
289
+ if (typeof process !== "undefined" && process.env?.DEBUG_CONTEXT_ACTION) console.debug(`[ActionGuard] Cleaned up ${keysToDelete.length} idle guards`);
290
+ }
291
+ }, this.cleanupIntervalMs);
322
292
  }
323
293
  /**
324
294
  * Apply debouncing to an action
@@ -448,10 +418,17 @@ var ActionGuard = class {
448
418
  /** Clear debounce timer if active to prevent memory leaks */
449
419
  if (state.debounceTimer) {
450
420
  clearTimeout(state.debounceTimer);
451
- if (state.debounceResolve) state.debounceResolve(false);
421
+ if (state.debounceResolve) {
422
+ state.debounceResolve(false);
423
+ state.debounceResolve = void 0;
424
+ }
425
+ state.debounceTimer = void 0;
452
426
  }
453
427
  /** Clear throttle timer if active to prevent memory leaks */
454
- if (state.throttleTimer) clearTimeout(state.throttleTimer);
428
+ if (state.throttleTimer) {
429
+ clearTimeout(state.throttleTimer);
430
+ state.throttleTimer = void 0;
431
+ }
455
432
  /** Remove guard state from memory */
456
433
  this.guards.delete(actionKey);
457
434
  }
@@ -506,11 +483,42 @@ var ActionGuard = class {
506
483
  getAllGuardStates() {
507
484
  return new Map(this.guards);
508
485
  }
486
+ /**
487
+ * 🆕 Explicit destroy method for comprehensive cleanup
488
+ *
489
+ * Cleans up all timers, promises, and intervals to prevent memory leaks.
490
+ * Should be called when ActionGuard is no longer needed.
491
+ *
492
+ * @internal
493
+ */
494
+ destroy() {
495
+ if (this.cleanupInterval) {
496
+ clearInterval(this.cleanupInterval);
497
+ this.cleanupInterval = void 0;
498
+ }
499
+ this.clearAll();
500
+ }
501
+ /**
502
+ * 🆕 Get statistics about active guards
503
+ *
504
+ * @returns Statistics about guard usage
505
+ *
506
+ * @internal
507
+ */
508
+ getStats() {
509
+ let withTimers = 0;
510
+ this.guards.forEach((state) => {
511
+ if (state.debounceTimer || state.throttleTimer) withTimers++;
512
+ });
513
+ return {
514
+ activeGuards: this.guards.size,
515
+ withTimers
516
+ };
517
+ }
509
518
  };
510
519
 
511
520
  //#endregion
512
521
  //#region src/concurrency/OperationQueue.ts
513
- var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
514
522
  /**
515
523
  * 작업 큐 관리자
516
524
  *
@@ -519,13 +527,17 @@ var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(),
519
527
  * 2. 우선순위 지원 - 중요한 작업 우선 처리
520
528
  * 3. 에러 처리 - 개별 작업 실패가 전체에 영향 주지 않음
521
529
  * 4. 메모리 관리 - 완료된 작업 자동 정리
530
+ * 5. 🆕 동시성 제어 - maxConcurrency로 동시 실행 제한
522
531
  */
523
532
  var OperationQueue = class {
524
- constructor(name = "OperationQueue") {
533
+ constructor(name = "OperationQueue", maxConcurrency = 1) {
525
534
  this.name = name;
526
- (0, import_defineProperty$1.default)(this, "queue", []);
527
- (0, import_defineProperty$1.default)(this, "isProcessing", false);
528
- (0, import_defineProperty$1.default)(this, "operationCounter", 0);
535
+ this.queue = [];
536
+ this.processingPromise = null;
537
+ this.operationCounter = 0;
538
+ this.activeOperations = 0;
539
+ this.runningOperations = /* @__PURE__ */ new Set();
540
+ this.maxConcurrency = Math.max(1, maxConcurrency);
529
541
  }
530
542
  /**
531
543
  * 작업을 큐에 추가하고 실행 결과를 반환
@@ -554,35 +566,56 @@ var OperationQueue = class {
554
566
  });
555
567
  }
556
568
  /**
557
- * 큐 처리 메인 로직
569
+ * 🆕 큐 처리 메인 로직 - 동시성 제어 지원
558
570
  *
559
- * 번에 하나씩 순서대로 작업을 실행하여 동시성 문제 방지
571
+ * maxConcurrency에 따라 동시 실행 작업 수를 제한하여 동시성 문제 방지
560
572
  */
561
573
  async processQueue() {
562
- if (this.isProcessing || this.queue.length === 0) return;
563
- this.isProcessing = true;
574
+ if (this.processingPromise) return this.processingPromise;
575
+ this.processingPromise = this._doProcess();
564
576
  try {
565
- while (this.queue.length > 0) {
577
+ await this.processingPromise;
578
+ } finally {
579
+ this.processingPromise = null;
580
+ }
581
+ }
582
+ async _doProcess() {
583
+ while (this.queue.length > 0 || this.runningOperations.size > 0) {
584
+ while (this.queue.length > 0 && this.activeOperations < this.maxConcurrency) {
566
585
  const operation = this.queue.shift();
567
- try {
568
- const result = await Promise.resolve(operation.operation());
569
- operation.resolve(result);
570
- } catch (error) {
571
- operation.reject(error);
572
- }
586
+ this.activeOperations++;
587
+ const operationPromise = this.executeOperation(operation);
588
+ this.runningOperations.add(operationPromise);
589
+ operationPromise.finally(() => {
590
+ this.activeOperations--;
591
+ this.runningOperations.delete(operationPromise);
592
+ });
573
593
  }
574
- } finally {
575
- this.isProcessing = false;
594
+ if (this.runningOperations.size > 0) await Promise.race(this.runningOperations);
576
595
  }
577
596
  }
578
597
  /**
579
- * 현재 상태 조회 (디버깅용)
598
+ * 🆕 개별 작업 실행 로직
599
+ */
600
+ async executeOperation(operation) {
601
+ try {
602
+ const result = await Promise.resolve(operation.operation());
603
+ operation.resolve(result);
604
+ } catch (error) {
605
+ operation.reject(error);
606
+ }
607
+ }
608
+ /**
609
+ * 🆕 현재 큐 상태 조회 (디버깅용) - 동시성 정보 포함
580
610
  */
581
611
  getQueueInfo() {
582
612
  return {
583
613
  name: this.name,
584
614
  queueLength: this.queue.length,
585
- isProcessing: this.isProcessing,
615
+ isProcessing: Boolean(this.processingPromise),
616
+ activeOperations: this.activeOperations,
617
+ maxConcurrency: this.maxConcurrency,
618
+ runningOperationsCount: this.runningOperations.size,
586
619
  operations: this.queue.map((op) => ({
587
620
  id: op.id,
588
621
  priority: op.priority,
@@ -591,6 +624,18 @@ var OperationQueue = class {
591
624
  };
592
625
  }
593
626
  /**
627
+ * 🆕 동시성 설정 조회
628
+ */
629
+ getConcurrencyInfo() {
630
+ return {
631
+ maxConcurrency: this.maxConcurrency,
632
+ activeOperations: this.activeOperations,
633
+ availableSlots: this.maxConcurrency - this.activeOperations,
634
+ queuedOperations: this.queue.length,
635
+ efficiency: this.activeOperations / this.maxConcurrency
636
+ };
637
+ }
638
+ /**
594
639
  * 큐 비우기 (테스트용)
595
640
  */
596
641
  clear() {
@@ -598,7 +643,7 @@ var OperationQueue = class {
598
643
  operation.reject(/* @__PURE__ */ new Error("Queue cleared"));
599
644
  });
600
645
  this.queue = [];
601
- this.isProcessing = false;
646
+ this.processingPromise = null;
602
647
  }
603
648
  /**
604
649
  * 큐 크기 조회
@@ -610,13 +655,12 @@ var OperationQueue = class {
610
655
  * 처리 중 여부 조회
611
656
  */
612
657
  get processing() {
613
- return this.isProcessing;
658
+ return Boolean(this.processingPromise);
614
659
  }
615
660
  };
616
661
 
617
662
  //#endregion
618
663
  //#region src/ActionRegister.ts
619
- var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
620
664
  /**
621
665
  * Action Register for managing action handlers with priority-based execution
622
666
  *
@@ -634,27 +678,23 @@ var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1)
634
678
  */
635
679
  var ActionRegister = class {
636
680
  constructor(config = {}) {
637
- (0, import_defineProperty.default)(this, "pipelines", /* @__PURE__ */ new Map());
638
- (0, import_defineProperty.default)(this, "handlerCounter", 0);
639
- (0, import_defineProperty.default)(this, "actionGuard", void 0);
640
- (0, import_defineProperty.default)(this, "executionMode", "sequential");
641
- (0, import_defineProperty.default)(this, "actionExecutionModes", /* @__PURE__ */ new Map());
642
- (0, import_defineProperty.default)(this, "name", void 0);
643
- (0, import_defineProperty.default)(this, "registryConfig", void 0);
644
- (0, import_defineProperty.default)(this, "executionStats", /* @__PURE__ */ new Map());
645
- (0, import_defineProperty.default)(this, "registrationQueue", void 0);
646
- (0, import_defineProperty.default)(this, "dispatchQueue", void 0);
681
+ this.pipelines = /* @__PURE__ */ new Map();
682
+ this.executionMode = "sequential";
683
+ this.actionExecutionModes = /* @__PURE__ */ new Map();
684
+ this.filterCache = /* @__PURE__ */ new Map();
685
+ this.filterCacheMaxSize = 100;
647
686
  this.name = config.name || "ActionRegister";
648
687
  this.registryConfig = config.registry;
649
- this.actionGuard = new ActionGuard();
650
- this.registrationQueue = new OperationQueue(`${this.name}-Registration`);
651
- this.dispatchQueue = new OperationQueue(`${this.name}-Dispatch`);
688
+ this.maxHandlersPerAction = config.registry?.maxHandlersPerAction ?? 1e3;
689
+ this.isDebugMode = Boolean(this.registryConfig?.debug && process.env.NODE_ENV === "development");
690
+ this.actionGuard = new ActionGuard(this.registryConfig?.autoCleanup !== false);
691
+ if (config.registry?.useConcurrencyQueue !== false) this.dispatchQueue = new OperationQueue(`${this.name}-Dispatch`);
652
692
  if (this.registryConfig?.defaultExecutionMode) this.executionMode = this.registryConfig.defaultExecutionMode;
653
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 ActionRegister created: ${this.name}`, {
693
+ this.log("ActionRegister initialized", {
654
694
  defaultExecutionMode: this.executionMode,
655
- maxHandlers: this.registryConfig.maxHandlers,
656
- autoCleanup: this.registryConfig.autoCleanup ?? true,
657
- concurrencyProtection: true
695
+ autoCleanup: this.registryConfig?.autoCleanup !== false,
696
+ concurrencyQueue: Boolean(this.dispatchQueue),
697
+ debugMode: this.isDebugMode
658
698
  });
659
699
  }
660
700
  /**
@@ -673,12 +713,82 @@ var ActionRegister = class {
673
713
  * @public
674
714
  */
675
715
  register(action, handler, config = {}) {
676
- const handlerId = config.id || `handler_${++this.handlerCounter}_${Math.random().toString(36).substr(2, 5)}`;
716
+ const handlerId = config.id || this.generateHandlerId(action);
677
717
  const unregisterFn = this._performRegistrationSync(action, handler, config, handlerId);
678
718
  return unregisterFn;
679
719
  }
680
720
  /**
681
- * 🆕 동기적 등록 수행 (개선된 버전)
721
+ * 🆕 Unified logging method with cached debug mode check
722
+ */
723
+ log(message, data, level = "log") {
724
+ if (this.isDebugMode) {
725
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
726
+ console[level](`🎯 [${timestamp}] [${this.name}] ${message}`, data || "");
727
+ }
728
+ }
729
+ /**
730
+ * 🆕 Generate unique handler ID using crypto
731
+ */
732
+ generateHandlerId(action) {
733
+ const uuid = crypto.randomUUID();
734
+ return `${String(action)}_${uuid.slice(0, 8)}`;
735
+ }
736
+ /**
737
+ * 🔧 Create and merge AbortSignal instances with proper cleanup
738
+ *
739
+ * @param options Dispatch options containing signal and autoAbort configuration
740
+ * @returns [effectiveSignal, autoAbortController, cleanupFunction]
741
+ */
742
+ createAbortSignal(options) {
743
+ const signals = [];
744
+ const cleanups = [];
745
+ let autoAbortController;
746
+ if (options?.signal) signals.push(options.signal);
747
+ if (options?.autoAbort?.enabled) {
748
+ autoAbortController = new AbortController();
749
+ signals.push(autoAbortController.signal);
750
+ }
751
+ if (signals.length === 0) return [
752
+ void 0,
753
+ autoAbortController,
754
+ () => {}
755
+ ];
756
+ if (signals.length === 1) return [
757
+ signals[0],
758
+ autoAbortController,
759
+ () => cleanups.forEach((c) => c())
760
+ ];
761
+ let effectiveSignal;
762
+ if (typeof AbortSignal.any === "function") effectiveSignal = AbortSignal.any(signals);
763
+ else {
764
+ const mergedController = new AbortController();
765
+ effectiveSignal = mergedController.signal;
766
+ signals.forEach((signal) => {
767
+ if (signal.aborted) mergedController.abort();
768
+ else {
769
+ const abortHandler = () => mergedController.abort();
770
+ signal.addEventListener("abort", abortHandler, { once: true });
771
+ cleanups.push(() => signal.removeEventListener("abort", abortHandler));
772
+ }
773
+ });
774
+ }
775
+ const cleanup = () => {
776
+ cleanups.forEach((c) => {
777
+ try {
778
+ c();
779
+ } catch (error) {
780
+ this.log("Cleanup error during AbortSignal cleanup", error, "warn");
781
+ }
782
+ });
783
+ };
784
+ return [
785
+ effectiveSignal,
786
+ autoAbortController,
787
+ cleanup
788
+ ];
789
+ }
790
+ /**
791
+ * 🆕 Perform synchronous handler registration
682
792
  */
683
793
  _performRegistrationSync(action, handler, config, handlerId) {
684
794
  const registration = {
@@ -689,31 +799,65 @@ var ActionRegister = class {
689
799
  blocking: config.blocking ?? false,
690
800
  once: config.once ?? false,
691
801
  debounce: config.debounce ?? void 0,
692
- throttle: config.throttle ?? void 0
802
+ throttle: config.throttle ?? void 0,
803
+ replaceExisting: config.replaceExisting ?? false
693
804
  },
694
805
  id: handlerId
695
806
  };
696
807
  if (!this.pipelines.has(action)) this.pipelines.set(action, []);
697
808
  const pipeline = this.pipelines.get(action);
809
+ if (pipeline.length >= this.maxHandlersPerAction) {
810
+ console.warn(`Handler limit (${this.maxHandlersPerAction}) reached for action "${String(action)}". Registration ignored.`);
811
+ return () => {};
812
+ }
698
813
  const existingIndex = pipeline.findIndex((reg) => reg.id === handlerId);
699
- if (existingIndex !== -1) return () => {};
700
- 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}'`);
814
+ if (existingIndex !== -1) if (config.replaceExisting) {
815
+ const oldRegistration = pipeline[existingIndex];
816
+ if (oldRegistration && typeof oldRegistration.cleanup === "function") try {
817
+ oldRegistration.cleanup();
818
+ } catch (cleanupError) {
819
+ this.log(`Cleanup error for replaced handler: ${String(action)}`, cleanupError, "warn");
820
+ }
821
+ pipeline[existingIndex] = registration;
822
+ pipeline.sort((a, b) => b.config.priority - a.config.priority);
823
+ this.invalidateFilterCache();
824
+ this.log(`Handler replaced: ${String(action)}`, {
825
+ handlerId,
826
+ priority: config.priority,
827
+ totalHandlers: pipeline.length,
828
+ oldHandlerCleaned: Boolean(oldRegistration.cleanup)
829
+ });
830
+ return () => {
831
+ const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
832
+ if (index !== -1) {
833
+ pipeline.splice(index, 1);
834
+ this.invalidateFilterCache();
835
+ this.log(`Replaced handler unregistered: ${String(action)}`, { handlerId });
836
+ }
837
+ };
838
+ } else {
839
+ this.log(`Handler duplicate ignored: ${String(action)}`, {
840
+ handlerId,
841
+ note: "Use replaceExisting:true to replace"
842
+ }, "warn");
843
+ return () => {};
844
+ }
701
845
  pipeline.push(registration);
702
846
  pipeline.sort((a, b) => b.config.priority - a.config.priority);
703
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler registered: ${String(action)}`, {
847
+ this.invalidateFilterCache();
848
+ this.log(`Handler registered: ${String(action)}`, {
704
849
  handlerId,
705
850
  priority: config.priority,
706
- totalHandlers: pipeline.length,
707
- registry: this.name
851
+ totalHandlers: pipeline.length
708
852
  });
709
853
  return () => {
710
854
  const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
711
855
  if (index !== -1) {
712
856
  pipeline.splice(index, 1);
713
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler unregistered: ${String(action)}`, {
857
+ this.invalidateFilterCache();
858
+ this.log(`Handler unregistered: ${String(action)}`, {
714
859
  handlerId,
715
- remainingHandlers: pipeline.length,
716
- registry: this.name
860
+ remainingHandlers: pipeline.length
717
861
  });
718
862
  }
719
863
  };
@@ -734,7 +878,8 @@ var ActionRegister = class {
734
878
  * @public
735
879
  */
736
880
  async dispatch(action, payload, options) {
737
- return this.dispatchQueue.enqueue(async () => {
881
+ if (options?.immediate || !this.dispatchQueue) return this._performDispatch(action, payload, options);
882
+ else return this.dispatchQueue.enqueue(async () => {
738
883
  return this._performDispatch(action, payload, options);
739
884
  });
740
885
  }
@@ -742,47 +887,13 @@ var ActionRegister = class {
742
887
  * 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
743
888
  */
744
889
  async _performDispatch(action, payload, options) {
745
- if (payload && typeof payload === "object" && payload !== null && typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
746
- payload instanceof Event;
747
- payload instanceof Element;
748
- payload.preventDefault;
749
- payload.stopPropagation;
750
- payload.currentTarget;
751
- const hasTarget = payload.target !== void 0;
752
- hasTarget && payload.target;
753
- hasTarget && payload.target instanceof Element;
754
- if (typeof process !== "undefined" && process.env?.DEBUG_CONTEXT_ACTION || typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
755
- const nestedDOMProperties = [];
756
- Object.keys(payload).forEach((key) => {
757
- const prop = payload[key];
758
- if (prop instanceof Element || prop instanceof Event) nestedDOMProperties.push(`${key}: ${prop instanceof Element ? "Element" : "Event"}`);
759
- });
760
- if (nestedDOMProperties.length > 0) console.debug(`[Context-Action] 📋 Nested DOM objects in action "${String(action)}":`, {
761
- registry: this.name,
762
- nestedDOMProperties,
763
- note: "This is informational - usually not a problem"
764
- });
765
- }
766
- }
767
- let autoAbortController;
768
- let effectiveSignal = options?.signal;
769
- if (options?.autoAbort?.enabled) {
770
- autoAbortController = new AbortController();
771
- effectiveSignal = autoAbortController.signal;
772
- if (options.autoAbort.onControllerCreated) options.autoAbort.onControllerCreated(autoAbortController);
773
- if (options?.signal) {
774
- const originalSignal = options.signal;
775
- if (originalSignal.aborted) autoAbortController.abort();
776
- else {
777
- const abortHandler$1 = () => autoAbortController.abort();
778
- originalSignal.addEventListener("abort", abortHandler$1, { once: true });
779
- }
780
- }
781
- }
890
+ if (payload instanceof Event && process.env.NODE_ENV === "development") console.warn(`Event object passed to action "${String(action)}"`, payload.type);
891
+ const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
892
+ if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
782
893
  if (effectiveSignal?.aborted) return;
783
894
  const pipeline = this.pipelines.get(action);
784
895
  if (!pipeline || pipeline.length === 0) return;
785
- const filteredHandlers = this.filterHandlers([...pipeline], options?.filter);
896
+ const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
786
897
  const actionKey = String(action);
787
898
  let throttleMs;
788
899
  let debounceMs;
@@ -822,8 +933,6 @@ var ActionRegister = class {
822
933
  terminated: false,
823
934
  terminationResult: void 0
824
935
  };
825
- const startTime = Date.now();
826
- let executionSuccess = true;
827
936
  const abortHandler = effectiveSignal ? () => {
828
937
  context.aborted = true;
829
938
  context.abortReason = "Action dispatch aborted by signal";
@@ -831,15 +940,12 @@ var ActionRegister = class {
831
940
  if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
832
941
  try {
833
942
  await this.executePipeline(context, autoAbortController, options?.autoAbort);
834
- console.log(`[ActionRegister] Pipeline execution succeeded for ${String(action)}`);
943
+ this.log(`Pipeline execution succeeded for ${String(action)}`);
835
944
  } catch (error) {
836
- console.log(`[ActionRegister] Pipeline execution failed for ${String(action)}:`, error);
837
- executionSuccess = false;
945
+ this.log(`Pipeline execution failed for ${String(action)}`, error, "error");
838
946
  throw error;
839
947
  } finally {
840
- if (effectiveSignal && abortHandler) effectiveSignal.removeEventListener("abort", abortHandler);
841
- const duration = Date.now() - startTime;
842
- this.updateExecutionStats(action, executionSuccess, duration);
948
+ cleanup();
843
949
  }
844
950
  }
845
951
  /**
@@ -856,36 +962,25 @@ var ActionRegister = class {
856
962
  * @public
857
963
  */
858
964
  async dispatchWithResult(action, payload, options) {
859
- const startTime = Date.now();
860
- let autoAbortController;
861
- let effectiveSignal = options?.signal;
862
- if (options?.autoAbort?.enabled) {
863
- autoAbortController = new AbortController();
864
- effectiveSignal = autoAbortController.signal;
865
- if (options.autoAbort.onControllerCreated) options.autoAbort.onControllerCreated(autoAbortController);
866
- if (options?.signal) {
867
- const originalSignal = options.signal;
868
- if (originalSignal.aborted) autoAbortController.abort();
869
- else {
870
- const abortHandler$1 = () => autoAbortController.abort();
871
- originalSignal.addEventListener("abort", abortHandler$1, { once: true });
872
- }
873
- }
874
- }
965
+ const _startTime = Date.now();
966
+ const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
967
+ if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
875
968
  if (effectiveSignal?.aborted) return {
876
969
  success: false,
877
970
  aborted: true,
878
971
  abortReason: "Action dispatch aborted by signal",
879
972
  terminated: false,
880
973
  result: void 0,
974
+ successResults: [],
881
975
  results: [],
976
+ failedResults: [],
882
977
  execution: {
883
978
  duration: 0,
884
979
  handlersExecuted: 0,
885
980
  handlersSkipped: 0,
886
981
  handlersFailed: 0,
887
- startTime,
888
- endTime: startTime
982
+ startTime: _startTime,
983
+ endTime: _startTime
889
984
  },
890
985
  handlers: [],
891
986
  errors: []
@@ -896,19 +991,21 @@ var ActionRegister = class {
896
991
  aborted: false,
897
992
  terminated: false,
898
993
  result: void 0,
994
+ successResults: [],
899
995
  results: [],
996
+ failedResults: [],
900
997
  execution: {
901
998
  duration: 0,
902
999
  handlersExecuted: 0,
903
1000
  handlersSkipped: 0,
904
1001
  handlersFailed: 0,
905
- startTime,
906
- endTime: startTime
1002
+ startTime: _startTime,
1003
+ endTime: _startTime
907
1004
  },
908
1005
  handlers: [],
909
1006
  errors: []
910
1007
  };
911
- const filteredHandlers = this.filterHandlers([...pipeline], options?.filter);
1008
+ const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
912
1009
  const actionKey = String(action);
913
1010
  let throttleMs;
914
1011
  let debounceMs;
@@ -934,13 +1031,15 @@ var ActionRegister = class {
934
1031
  abortReason: "Debounced execution",
935
1032
  terminated: false,
936
1033
  result: void 0,
1034
+ successResults: [],
937
1035
  results: [],
1036
+ failedResults: [],
938
1037
  execution: {
939
- duration: Date.now() - startTime,
1038
+ duration: Date.now() - _startTime,
940
1039
  handlersExecuted: 0,
941
1040
  handlersSkipped: pipeline.length,
942
1041
  handlersFailed: 0,
943
- startTime,
1042
+ startTime: _startTime,
944
1043
  endTime: Date.now()
945
1044
  },
946
1045
  handlers: [],
@@ -955,13 +1054,15 @@ var ActionRegister = class {
955
1054
  abortReason: "Throttled execution",
956
1055
  terminated: false,
957
1056
  result: void 0,
1057
+ successResults: [],
958
1058
  results: [],
1059
+ failedResults: [],
959
1060
  execution: {
960
- duration: Date.now() - startTime,
1061
+ duration: Date.now() - _startTime,
961
1062
  handlersExecuted: 0,
962
1063
  handlersSkipped: pipeline.length,
963
1064
  handlersFailed: 0,
964
- startTime,
1065
+ startTime: _startTime,
965
1066
  endTime: Date.now()
966
1067
  },
967
1068
  handlers: [],
@@ -1000,47 +1101,92 @@ var ActionRegister = class {
1000
1101
  timestamp: Date.now()
1001
1102
  });
1002
1103
  } finally {
1003
- if (effectiveSignal && abortHandler) effectiveSignal.removeEventListener("abort", abortHandler);
1104
+ cleanup();
1004
1105
  }
1005
1106
  const endTime = Date.now();
1006
- const executionSuccess = !executionError && !context.aborted;
1007
- this.updateExecutionStats(action, executionSuccess, endTime - startTime);
1107
+ !executionError && context.aborted;
1008
1108
  const processedResult = this.processResults(context, options?.result);
1109
+ const successResults = context.results.filter((result) => result !== void 0);
1110
+ const failedResults = errors.map((err) => ({
1111
+ handlerId: err.handlerId,
1112
+ error: err.error,
1113
+ expectedType: typeof processedResult
1114
+ }));
1009
1115
  const executionResult = {
1010
1116
  success: !executionError && !context.aborted,
1011
1117
  aborted: context.aborted,
1012
1118
  abortReason: context.abortReason,
1013
1119
  terminated: context.terminated,
1014
1120
  result: processedResult,
1121
+ successResults,
1015
1122
  results: context.results,
1123
+ failedResults,
1016
1124
  execution: {
1017
- duration: endTime - startTime,
1125
+ duration: endTime - _startTime,
1018
1126
  handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
1019
1127
  handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
1020
1128
  handlersFailed: errors.length,
1021
- startTime,
1129
+ startTime: _startTime,
1022
1130
  endTime
1023
1131
  },
1024
1132
  handlers: handlerResults,
1025
- errors
1133
+ errors: errors.map((err) => ({
1134
+ handlerId: err.handlerId,
1135
+ error: err.error,
1136
+ timestamp: err.timestamp,
1137
+ severity: "non-blocking"
1138
+ }))
1026
1139
  };
1027
1140
  /** Clean up one-time handlers after execution */
1028
1141
  this.cleanupOneTimeHandlers(action, context.handlers);
1029
1142
  return executionResult;
1030
1143
  }
1144
+ /**
1145
+ * 🔧 Generate cache key for filter options
1146
+ */
1147
+ generateFilterCacheKey(filterOptions) {
1148
+ if (!filterOptions) return "no-filter";
1149
+ const key = [
1150
+ filterOptions.handlerIds?.sort().join(",") || "none",
1151
+ filterOptions.excludeHandlerIds?.sort().join(",") || "none",
1152
+ filterOptions.priority?.min?.toString() || "none",
1153
+ filterOptions.priority?.max?.toString() || "none",
1154
+ filterOptions.custom ? "custom" : "none"
1155
+ ].join("|");
1156
+ return key;
1157
+ }
1158
+ /**
1159
+ * 🔧 Clear filter cache when pipelines change
1160
+ */
1161
+ invalidateFilterCache() {
1162
+ this.filterCache.clear();
1163
+ }
1031
1164
  filterHandlers(handlers, filterOptions) {
1032
1165
  if (!filterOptions) return handlers;
1033
- return handlers.filter((registration) => {
1166
+ const cacheKey = this.generateFilterCacheKey(filterOptions);
1167
+ if (!filterOptions.custom) {
1168
+ const cached = this.filterCache.get(cacheKey);
1169
+ if (cached) return cached;
1170
+ }
1171
+ const filtered = handlers.filter((registration) => {
1034
1172
  const config = registration.config;
1035
- if (filterOptions.handlerIds && filterOptions.handlerIds.length > 0) {
1036
- if (!filterOptions.handlerIds.includes(config.id)) return false;
1037
- }
1038
- if (filterOptions.excludeHandlerIds && filterOptions.excludeHandlerIds.length > 0) {
1039
- if (filterOptions.excludeHandlerIds.includes(config.id)) return false;
1173
+ if (filterOptions.handlerIds?.length && !filterOptions.handlerIds.includes(config.id)) return false;
1174
+ if (filterOptions.excludeHandlerIds?.length && filterOptions.excludeHandlerIds.includes(config.id)) return false;
1175
+ if (filterOptions.priority) {
1176
+ if (filterOptions.priority.min !== void 0 && config.priority < filterOptions.priority.min) return false;
1177
+ if (filterOptions.priority.max !== void 0 && config.priority > filterOptions.priority.max) return false;
1040
1178
  }
1041
1179
  if (filterOptions.custom && !filterOptions.custom(config)) return false;
1042
1180
  return true;
1043
1181
  });
1182
+ if (!filterOptions.custom) {
1183
+ if (this.filterCache.size >= this.filterCacheMaxSize) {
1184
+ const firstKey = this.filterCache.keys().next().value;
1185
+ if (firstKey) this.filterCache.delete(firstKey);
1186
+ }
1187
+ this.filterCache.set(cacheKey, filtered);
1188
+ }
1189
+ return filtered;
1044
1190
  }
1045
1191
  processResults(context, resultOptions) {
1046
1192
  if (!resultOptions || !resultOptions.collect) return void 0;
@@ -1126,26 +1272,6 @@ var ActionRegister = class {
1126
1272
  });
1127
1273
  }
1128
1274
  /**
1129
- * Update execution statistics for an action
1130
- *
1131
- * @param action Action name
1132
- * @param success Whether execution was successful
1133
- * @param duration Execution duration in milliseconds
1134
- */
1135
- updateExecutionStats(action, success, duration) {
1136
- if (!this.executionStats.has(action)) this.executionStats.set(action, {
1137
- totalExecutions: 0,
1138
- totalDuration: 0,
1139
- successCount: 0,
1140
- errorCount: 0
1141
- });
1142
- const stats = this.executionStats.get(action);
1143
- stats.totalExecutions++;
1144
- stats.totalDuration += duration;
1145
- if (success) stats.successCount++;
1146
- else stats.errorCount++;
1147
- }
1148
- /**
1149
1275
  * Get the number of registered handlers for an action
1150
1276
  *
1151
1277
  * @param action - The action type to count handlers for
@@ -1197,6 +1323,7 @@ var ActionRegister = class {
1197
1323
  */
1198
1324
  clearAction(action) {
1199
1325
  this.pipelines.delete(action);
1326
+ this.invalidateFilterCache();
1200
1327
  }
1201
1328
  /**
1202
1329
  * Remove all handlers for all actions
@@ -1207,6 +1334,7 @@ var ActionRegister = class {
1207
1334
  */
1208
1335
  clearAll() {
1209
1336
  this.pipelines.clear();
1337
+ this.invalidateFilterCache();
1210
1338
  }
1211
1339
  /**
1212
1340
  * Get the name of this action register
@@ -1254,13 +1382,7 @@ var ActionRegister = class {
1254
1382
  priority,
1255
1383
  handlers: handlers.map((h) => ({ id: h.config.id }))
1256
1384
  }));
1257
- const stats = this.executionStats.get(action);
1258
- const executionStats = stats ? {
1259
- totalExecutions: stats.totalExecutions,
1260
- averageDuration: stats.totalExecutions > 0 ? stats.totalDuration / stats.totalExecutions : 0,
1261
- successRate: stats.totalExecutions > 0 ? stats.successCount / stats.totalExecutions * 100 : 0,
1262
- errorCount: stats.errorCount
1263
- } : void 0;
1385
+ const executionStats = void 0;
1264
1386
  return {
1265
1387
  action,
1266
1388
  handlerCount: pipeline.length,
@@ -1306,22 +1428,6 @@ var ActionRegister = class {
1306
1428
  if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution mode reset for action '${String(action)}' to default: ${this.executionMode}`);
1307
1429
  }
1308
1430
  /**
1309
- * Clear execution statistics for all actions
1310
- */
1311
- clearExecutionStats() {
1312
- this.executionStats.clear();
1313
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution statistics cleared for registry: ${this.name}`);
1314
- }
1315
- /**
1316
- * Clear execution statistics for a specific action
1317
- *
1318
- * @param action Action name
1319
- */
1320
- clearActionExecutionStats(action) {
1321
- this.executionStats.delete(action);
1322
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution statistics cleared for action: ${String(action)}`);
1323
- }
1324
- /**
1325
1431
  * Get registry configuration (for debugging and inspection)
1326
1432
  *
1327
1433
  * @returns Current registry configuration
@@ -1335,10 +1441,283 @@ var ActionRegister = class {
1335
1441
  * @returns Whether debug mode is enabled
1336
1442
  */
1337
1443
  isDebugEnabled() {
1338
- return Boolean(this.registryConfig?.debug && process.env.NODE_ENV === "development");
1444
+ return this.isDebugMode;
1445
+ }
1446
+ /**
1447
+ * 🆕 Destroy method for comprehensive cleanup
1448
+ *
1449
+ * Cleans up all internal resources including pipelines, guards, queues, and statistics.
1450
+ * Should be called when the ActionRegister is no longer needed to prevent memory leaks.
1451
+ *
1452
+ * @public
1453
+ */
1454
+ destroy() {
1455
+ this.pipelines.clear();
1456
+ this.actionGuard.destroy();
1457
+ this.dispatchQueue?.clear?.();
1458
+ this.actionExecutionModes.clear();
1459
+ this.filterCache.clear();
1460
+ this.log("ActionRegister destroyed");
1339
1461
  }
1340
1462
  };
1341
1463
 
1342
1464
  //#endregion
1343
- export { ActionGuard, ActionRegister, executeParallel, executeRace, executeSequential };
1465
+ //#region src/react-helpers.ts
1466
+ /**
1467
+ * 🔧 Create action handler registration configuration for React components
1468
+ *
1469
+ * Creates a configuration object that can be used with React's useEffect to properly
1470
+ * register and unregister action handlers with lifecycle management and cleanup.
1471
+ * This is NOT a hook - it's a factory function for React hook integration.
1472
+ *
1473
+ * @template T - ActionPayloadMap type
1474
+ * @template K - Action key type
1475
+ *
1476
+ * @param registry - ActionRegister instance
1477
+ * @param action - Action name to register handler for
1478
+ * @param handler - Handler function (should be memoized with useCallback)
1479
+ * @param config - Handler configuration
1480
+ *
1481
+ * @returns Configuration object with register/unregister functions
1482
+ *
1483
+ * @example Basic Usage with useEffect
1484
+ * ```tsx
1485
+ * import { useCallback, useEffect } from 'react';
1486
+ * import { createActionHandler } from '@context-action/core/react-helpers';
1487
+ *
1488
+ * function MyComponent() {
1489
+ * const registry = useActionRegister();
1490
+ *
1491
+ * const handleUserUpdate = useCallback(async (payload, controller) => {
1492
+ * // Handler logic here
1493
+ * }, []);
1494
+ *
1495
+ * useEffect(() => {
1496
+ * const { register, unregister } = createActionHandler(
1497
+ * registry,
1498
+ * 'updateUser',
1499
+ * handleUserUpdate,
1500
+ * { priority: 10 }
1501
+ * );
1502
+ *
1503
+ * const cleanup = register();
1504
+ * return () => {
1505
+ * cleanup();
1506
+ * unregister();
1507
+ * };
1508
+ * }, [registry, handleUserUpdate]);
1509
+ * }
1510
+ * ```
1511
+ *
1512
+ * @example With Automatic Cleanup
1513
+ * ```tsx
1514
+ * const [userId, setUserId] = useState('123');
1515
+ *
1516
+ * const handleUserUpdate = useCallback(async (payload, controller) => {
1517
+ * console.log('Updating user:', userId, payload);
1518
+ * }, [userId]);
1519
+ *
1520
+ * useEffect(() => {
1521
+ * const handlerManager = createActionHandler(
1522
+ * registry,
1523
+ * 'updateUser',
1524
+ * handleUserUpdate,
1525
+ * { priority: 10 }
1526
+ * );
1527
+ *
1528
+ * // Simplified registration with automatic cleanup
1529
+ * return handlerManager.registerWithCleanup();
1530
+ * }, [registry, handleUserUpdate, userId]);
1531
+ * ```
1532
+ *
1533
+ * @public
1534
+ */
1535
+ function createActionHandler(registry, action, handler, config) {
1536
+ const finalConfig = createReactHandlerConfig(String(action), void 0, config);
1537
+ let currentUnregister;
1538
+ let isRegistered = false;
1539
+ return {
1540
+ register() {
1541
+ if (isRegistered && currentUnregister) currentUnregister();
1542
+ currentUnregister = registry.register(action, handler, finalConfig);
1543
+ isRegistered = true;
1544
+ return currentUnregister;
1545
+ },
1546
+ unregister() {
1547
+ if (isRegistered && currentUnregister) {
1548
+ currentUnregister();
1549
+ currentUnregister = void 0;
1550
+ isRegistered = false;
1551
+ }
1552
+ },
1553
+ registerWithCleanup() {
1554
+ const unregisterFn = this.register();
1555
+ return () => {
1556
+ unregisterFn();
1557
+ this.unregister();
1558
+ };
1559
+ },
1560
+ config: finalConfig
1561
+ };
1562
+ }
1563
+ /**
1564
+ * 🆕 React handler configuration factory
1565
+ *
1566
+ * Creates optimized handler configurations for React environments with
1567
+ * proper cleanup and unique ID generation.
1568
+ *
1569
+ * @template T - ActionPayloadMap type
1570
+ * @template K - Action key type
1571
+ *
1572
+ * @param action - Action name
1573
+ * @param componentId - Optional component identifier for debugging
1574
+ * @param config - Base handler configuration
1575
+ *
1576
+ * @returns Optimized configuration for React environments
1577
+ *
1578
+ * @example
1579
+ * ```tsx
1580
+ * function MyComponent({ userId }: { userId: string }) {
1581
+ * const registry = useActionRegister();
1582
+ *
1583
+ * useEffect(() => {
1584
+ * const config = createReactHandlerConfig('updateUser', 'MyComponent', {
1585
+ * priority: 10
1586
+ * });
1587
+ *
1588
+ * const unregister = registry.register('updateUser', handler, config);
1589
+ * return unregister;
1590
+ * }, [registry, handler]);
1591
+ * }
1592
+ * ```
1593
+ *
1594
+ * @public
1595
+ */
1596
+ function createReactHandlerConfig(action, componentId, config = {}) {
1597
+ const timestamp = Date.now();
1598
+ const random = Math.random().toString(36).substr(2, 5);
1599
+ return {
1600
+ priority: config.priority ?? 0,
1601
+ id: config.id || `${componentId || "react"}_${action}_${timestamp}_${random}`,
1602
+ blocking: config.blocking ?? false,
1603
+ once: config.once ?? false,
1604
+ debounce: config.debounce ?? void 0,
1605
+ throttle: config.throttle ?? void 0,
1606
+ replaceExisting: true
1607
+ };
1608
+ }
1609
+ /**
1610
+ * 🆕 React action dispatcher factory
1611
+ *
1612
+ * Creates a dispatcher function optimized for React component usage
1613
+ * with proper error boundaries and async handling.
1614
+ *
1615
+ * @template T - ActionPayloadMap type
1616
+ *
1617
+ * @param registry - ActionRegister instance
1618
+ * @param errorHandler - Optional error handler for unhandled dispatch errors
1619
+ *
1620
+ * @returns Optimized dispatch function for React components
1621
+ *
1622
+ * @example
1623
+ * ```tsx
1624
+ * function MyComponent() {
1625
+ * const registry = useActionRegister();
1626
+ *
1627
+ * const dispatch = createReactDispatcher(registry, (error, action, payload) => {
1628
+ * console.error(`Failed to dispatch ${action}:`, error);
1629
+ * });
1630
+ *
1631
+ * const handleClick = useCallback(() => {
1632
+ * dispatch('userClick', { buttonId: 'submit' });
1633
+ * }, [dispatch]);
1634
+ * }
1635
+ * ```
1636
+ *
1637
+ * @public
1638
+ */
1639
+ function createReactDispatcher(registry, errorHandler) {
1640
+ return async (action, payload, options) => {
1641
+ try {
1642
+ await registry.dispatch(action, payload, {
1643
+ immediate: false,
1644
+ ...options
1645
+ });
1646
+ } catch (error) {
1647
+ const errorObj = error instanceof Error ? error : new Error(String(error));
1648
+ if (errorHandler) errorHandler(errorObj, action, payload);
1649
+ else console.error(`[ActionRegister] Dispatch failed for action '${String(action)}':`, errorObj);
1650
+ }
1651
+ };
1652
+ }
1653
+ /**
1654
+ * 🆕 React development utilities
1655
+ *
1656
+ * Provides debugging and development helpers specifically for React environments.
1657
+ */
1658
+ const ReactDevUtils = {
1659
+ enableDebugMode() {
1660
+ if (typeof window !== "undefined") window.__CONTEXT_ACTION_REACT_DEBUG__ = true;
1661
+ },
1662
+ disableDebugMode() {
1663
+ if (typeof window !== "undefined") window.__CONTEXT_ACTION_REACT_DEBUG__ = false;
1664
+ },
1665
+ isDebugMode() {
1666
+ return typeof window !== "undefined" && Boolean(window.__CONTEXT_ACTION_REACT_DEBUG__);
1667
+ },
1668
+ log(component, action, message, data) {
1669
+ if (this.isDebugMode()) console.log(`🎯 [React-ActionRegister] [${component}] ${action}: ${message}`, data || "");
1670
+ },
1671
+ getStats(registry) {
1672
+ const registryInfo = registry.getRegistryInfo();
1673
+ let reactHandlers = 0;
1674
+ registry.getRegisteredActions().forEach((action) => {
1675
+ const stats = registry.getActionStats(action);
1676
+ if (stats) stats.handlersByPriority.forEach((priorityGroup) => {
1677
+ priorityGroup.handlers.forEach((handler) => {
1678
+ if (handler.id.includes("react")) reactHandlers++;
1679
+ });
1680
+ });
1681
+ });
1682
+ return {
1683
+ totalHandlers: registryInfo.totalHandlers,
1684
+ reactHandlers,
1685
+ registryInfo
1686
+ };
1687
+ }
1688
+ };
1689
+ /**
1690
+ * 🆕 React Error Boundary integration
1691
+ *
1692
+ * Utilities for integrating ActionRegister errors with React Error Boundaries.
1693
+ */
1694
+ var ReactActionError = class ReactActionError extends Error {
1695
+ constructor(message, action, payload, handlerId, originalError) {
1696
+ super(message);
1697
+ this.name = "ReactActionError";
1698
+ this.action = action;
1699
+ this.payload = payload;
1700
+ this.handlerId = handlerId;
1701
+ this.timestamp = Date.now();
1702
+ if (originalError && originalError.stack) this.stack = originalError.stack;
1703
+ }
1704
+ /**
1705
+ * Create a React Error Boundary compatible error
1706
+ */
1707
+ static fromActionError(originalError, action, payload, handlerId) {
1708
+ return new ReactActionError(`Action '${action}' failed: ${originalError.message}`, action, payload, handlerId, originalError);
1709
+ }
1710
+ };
1711
+ /**
1712
+ * 🆕 Type guard for React Action Errors
1713
+ *
1714
+ * @param error - Error to check
1715
+ * @returns True if error is a ReactActionError
1716
+ */
1717
+ function isReactActionError(error) {
1718
+ return error instanceof ReactActionError;
1719
+ }
1720
+
1721
+ //#endregion
1722
+ export { ActionGuard, ActionRegister, ReactActionError, ReactDevUtils, createActionHandler, createReactDispatcher, createReactHandlerConfig, executeParallel, executeRace, executeSequential, isReactActionError };
1344
1723
  //# sourceMappingURL=index.js.map