@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.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).
@@ -49,6 +41,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
49
41
  async function executeSequential(context, createController) {
50
42
  let i = 0;
51
43
  const nonBlockingPromises = [];
44
+ const errors = [];
52
45
  while (i < context.handlers.length) {
53
46
  if (context.aborted || context.terminated) break;
54
47
  const registration = context.handlers[i];
@@ -57,22 +50,24 @@ async function executeSequential(context, createController) {
57
50
  try {
58
51
  if (context.aborted) break;
59
52
  const result = registration.handler(context.payload, controller);
60
- /** Wait for async handlers if they're blocking */
61
- if (registration.config.blocking && result instanceof Promise) {
62
- const handlerResult = await result;
63
- /** Collect result if handler returned something and wasn't terminated */
53
+ if (registration.config.blocking) {
54
+ const handlerResult = result instanceof Promise ? await result : result;
64
55
  if (handlerResult !== void 0 && !context.terminated) context.results.push(handlerResult);
65
- } else if (result !== void 0 && !context.terminated)
66
- /** Collect synchronous result */
67
- if (result instanceof Promise) {
68
- const promiseWithHandling = result.then((asyncResult) => {
56
+ } else if (result instanceof Promise) {
57
+ const promiseWithErrorHandling = result.then((asyncResult) => {
69
58
  if (asyncResult !== void 0 && !context.terminated) context.results.push(asyncResult);
70
59
  return asyncResult;
71
60
  }).catch((error) => {
72
- 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;
73
68
  });
74
- nonBlockingPromises.push(promiseWithHandling);
75
- } else context.results.push(result);
69
+ nonBlockingPromises.push(promiseWithErrorHandling);
70
+ } else if (result !== void 0 && !context.terminated) context.results.push(result);
76
71
  /** Check if pipeline was terminated by controller.return() */
77
72
  if (context.terminated) break;
78
73
  /** Handle jump to priority AFTER handler execution */
@@ -88,11 +83,20 @@ async function executeSequential(context, createController) {
88
83
  }
89
84
  } else i++;
90
85
  } catch (error) {
91
- if (registration.config.blocking) throw error;
92
- throw error;
86
+ const handlerError = handleExecutionError(error, registration);
87
+ throw handlerError.error;
93
88
  }
94
89
  }
95
- 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
+ }
96
100
  }
97
101
  /**
98
102
  * Execute handlers in parallel mode (all at once)
@@ -135,11 +139,12 @@ async function executeParallel(context, createController) {
135
139
  terminated: context.terminated
136
140
  };
137
141
  } catch (error) {
138
- if (registration.config.blocking) throw error;
142
+ const handlerError = handleExecutionError(error, registration);
143
+ if (handlerError.severity === "blocking") throw handlerError.error;
139
144
  return {
140
145
  success: false,
141
146
  handlerId: registration.id,
142
- error
147
+ error: handlerError.error
143
148
  };
144
149
  }
145
150
  });
@@ -207,10 +212,11 @@ async function executeRace(context, createController) {
207
212
  terminated: context.terminated
208
213
  };
209
214
  } catch (error) {
215
+ const handlerError = handleExecutionError(error, registration);
210
216
  return {
211
217
  success: false,
212
218
  handlerId: registration.id,
213
- error,
219
+ error: handlerError.error,
214
220
  registration
215
221
  };
216
222
  }
@@ -228,67 +234,8 @@ async function executeRace(context, createController) {
228
234
  }
229
235
  }
230
236
 
231
- //#endregion
232
- //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js
233
- 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) => {
234
- function _typeof$2(o) {
235
- "@babel/helpers - typeof";
236
- return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
237
- return typeof o$1;
238
- } : function(o$1) {
239
- return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
240
- }, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
241
- }
242
- module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
243
- }) });
244
-
245
- //#endregion
246
- //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js
247
- 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) => {
248
- var _typeof$1 = require_typeof()["default"];
249
- function toPrimitive$1(t, r) {
250
- if ("object" != _typeof$1(t) || !t) return t;
251
- var e = t[Symbol.toPrimitive];
252
- if (void 0 !== e) {
253
- var i = e.call(t, r || "default");
254
- if ("object" != _typeof$1(i)) return i;
255
- throw new TypeError("@@toPrimitive must return a primitive value.");
256
- }
257
- return ("string" === r ? String : Number)(t);
258
- }
259
- module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
260
- }) });
261
-
262
- //#endregion
263
- //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js
264
- 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) => {
265
- var _typeof = require_typeof()["default"];
266
- var toPrimitive = require_toPrimitive();
267
- function toPropertyKey$1(t) {
268
- var i = toPrimitive(t, "string");
269
- return "symbol" == _typeof(i) ? i : i + "";
270
- }
271
- module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
272
- }) });
273
-
274
- //#endregion
275
- //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.82.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js
276
- 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) => {
277
- var toPropertyKey = require_toPropertyKey();
278
- function _defineProperty$3(e, r, t) {
279
- return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
280
- value: t,
281
- enumerable: !0,
282
- configurable: !0,
283
- writable: !0
284
- }) : e[r] = t, e;
285
- }
286
- module.exports = _defineProperty$3, module.exports.__esModule = true, module.exports["default"] = module.exports;
287
- }) });
288
-
289
237
  //#endregion
290
238
  //#region src/action-guard.ts
291
- var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
292
239
  /**
293
240
  * Action Guard system for managing action execution timing
294
241
  *
@@ -318,8 +265,31 @@ var import_defineProperty$2 = /* @__PURE__ */ __toESM(require_defineProperty(),
318
265
  * @internal
319
266
  */
320
267
  var ActionGuard = class {
321
- constructor() {
322
- (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);
323
293
  }
324
294
  /**
325
295
  * Apply debouncing to an action
@@ -449,10 +419,17 @@ var ActionGuard = class {
449
419
  /** Clear debounce timer if active to prevent memory leaks */
450
420
  if (state.debounceTimer) {
451
421
  clearTimeout(state.debounceTimer);
452
- 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;
453
427
  }
454
428
  /** Clear throttle timer if active to prevent memory leaks */
455
- if (state.throttleTimer) clearTimeout(state.throttleTimer);
429
+ if (state.throttleTimer) {
430
+ clearTimeout(state.throttleTimer);
431
+ state.throttleTimer = void 0;
432
+ }
456
433
  /** Remove guard state from memory */
457
434
  this.guards.delete(actionKey);
458
435
  }
@@ -507,11 +484,42 @@ var ActionGuard = class {
507
484
  getAllGuardStates() {
508
485
  return new Map(this.guards);
509
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
+ }
510
519
  };
511
520
 
512
521
  //#endregion
513
522
  //#region src/concurrency/OperationQueue.ts
514
- var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
515
523
  /**
516
524
  * 작업 큐 관리자
517
525
  *
@@ -520,13 +528,17 @@ var import_defineProperty$1 = /* @__PURE__ */ __toESM(require_defineProperty(),
520
528
  * 2. 우선순위 지원 - 중요한 작업 우선 처리
521
529
  * 3. 에러 처리 - 개별 작업 실패가 전체에 영향 주지 않음
522
530
  * 4. 메모리 관리 - 완료된 작업 자동 정리
531
+ * 5. 🆕 동시성 제어 - maxConcurrency로 동시 실행 제한
523
532
  */
524
533
  var OperationQueue = class {
525
- constructor(name = "OperationQueue") {
534
+ constructor(name = "OperationQueue", maxConcurrency = 1) {
526
535
  this.name = name;
527
- (0, import_defineProperty$1.default)(this, "queue", []);
528
- (0, import_defineProperty$1.default)(this, "isProcessing", false);
529
- (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);
530
542
  }
531
543
  /**
532
544
  * 작업을 큐에 추가하고 실행 결과를 반환
@@ -555,35 +567,56 @@ var OperationQueue = class {
555
567
  });
556
568
  }
557
569
  /**
558
- * 큐 처리 메인 로직
570
+ * 🆕 큐 처리 메인 로직 - 동시성 제어 지원
559
571
  *
560
- * 번에 하나씩 순서대로 작업을 실행하여 동시성 문제 방지
572
+ * maxConcurrency에 따라 동시 실행 작업 수를 제한하여 동시성 문제 방지
561
573
  */
562
574
  async processQueue() {
563
- if (this.isProcessing || this.queue.length === 0) return;
564
- this.isProcessing = true;
575
+ if (this.processingPromise) return this.processingPromise;
576
+ this.processingPromise = this._doProcess();
565
577
  try {
566
- 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) {
567
586
  const operation = this.queue.shift();
568
- try {
569
- const result = await Promise.resolve(operation.operation());
570
- operation.resolve(result);
571
- } catch (error) {
572
- operation.reject(error);
573
- }
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
+ });
574
594
  }
575
- } finally {
576
- this.isProcessing = false;
595
+ if (this.runningOperations.size > 0) await Promise.race(this.runningOperations);
577
596
  }
578
597
  }
579
598
  /**
580
- * 현재 상태 조회 (디버깅용)
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);
607
+ }
608
+ }
609
+ /**
610
+ * 🆕 현재 큐 상태 조회 (디버깅용) - 동시성 정보 포함
581
611
  */
582
612
  getQueueInfo() {
583
613
  return {
584
614
  name: this.name,
585
615
  queueLength: this.queue.length,
586
- isProcessing: this.isProcessing,
616
+ isProcessing: Boolean(this.processingPromise),
617
+ activeOperations: this.activeOperations,
618
+ maxConcurrency: this.maxConcurrency,
619
+ runningOperationsCount: this.runningOperations.size,
587
620
  operations: this.queue.map((op) => ({
588
621
  id: op.id,
589
622
  priority: op.priority,
@@ -592,6 +625,18 @@ var OperationQueue = class {
592
625
  };
593
626
  }
594
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
+ /**
595
640
  * 큐 비우기 (테스트용)
596
641
  */
597
642
  clear() {
@@ -599,7 +644,7 @@ var OperationQueue = class {
599
644
  operation.reject(/* @__PURE__ */ new Error("Queue cleared"));
600
645
  });
601
646
  this.queue = [];
602
- this.isProcessing = false;
647
+ this.processingPromise = null;
603
648
  }
604
649
  /**
605
650
  * 큐 크기 조회
@@ -611,13 +656,12 @@ var OperationQueue = class {
611
656
  * 처리 중 여부 조회
612
657
  */
613
658
  get processing() {
614
- return this.isProcessing;
659
+ return Boolean(this.processingPromise);
615
660
  }
616
661
  };
617
662
 
618
663
  //#endregion
619
664
  //#region src/ActionRegister.ts
620
- var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1);
621
665
  /**
622
666
  * Action Register for managing action handlers with priority-based execution
623
667
  *
@@ -635,27 +679,23 @@ var import_defineProperty = /* @__PURE__ */ __toESM(require_defineProperty(), 1)
635
679
  */
636
680
  var ActionRegister = class {
637
681
  constructor(config = {}) {
638
- (0, import_defineProperty.default)(this, "pipelines", /* @__PURE__ */ new Map());
639
- (0, import_defineProperty.default)(this, "handlerCounter", 0);
640
- (0, import_defineProperty.default)(this, "actionGuard", void 0);
641
- (0, import_defineProperty.default)(this, "executionMode", "sequential");
642
- (0, import_defineProperty.default)(this, "actionExecutionModes", /* @__PURE__ */ new Map());
643
- (0, import_defineProperty.default)(this, "name", void 0);
644
- (0, import_defineProperty.default)(this, "registryConfig", void 0);
645
- (0, import_defineProperty.default)(this, "executionStats", /* @__PURE__ */ new Map());
646
- (0, import_defineProperty.default)(this, "registrationQueue", void 0);
647
- (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;
648
687
  this.name = config.name || "ActionRegister";
649
688
  this.registryConfig = config.registry;
650
- this.actionGuard = new ActionGuard();
651
- this.registrationQueue = new OperationQueue(`${this.name}-Registration`);
652
- 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`);
653
693
  if (this.registryConfig?.defaultExecutionMode) this.executionMode = this.registryConfig.defaultExecutionMode;
654
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 ActionRegister created: ${this.name}`, {
694
+ this.log("ActionRegister initialized", {
655
695
  defaultExecutionMode: this.executionMode,
656
- maxHandlers: this.registryConfig.maxHandlers,
657
- autoCleanup: this.registryConfig.autoCleanup ?? true,
658
- concurrencyProtection: true
696
+ autoCleanup: this.registryConfig?.autoCleanup !== false,
697
+ concurrencyQueue: Boolean(this.dispatchQueue),
698
+ debugMode: this.isDebugMode
659
699
  });
660
700
  }
661
701
  /**
@@ -674,12 +714,82 @@ var ActionRegister = class {
674
714
  * @public
675
715
  */
676
716
  register(action, handler, config = {}) {
677
- const handlerId = config.id || `handler_${++this.handlerCounter}_${Math.random().toString(36).substr(2, 5)}`;
717
+ const handlerId = config.id || this.generateHandlerId(action);
678
718
  const unregisterFn = this._performRegistrationSync(action, handler, config, handlerId);
679
719
  return unregisterFn;
680
720
  }
681
721
  /**
682
- * 🆕 동기적 등록 수행 (개선된 버전)
722
+ * 🆕 Unified logging method with cached debug mode check
723
+ */
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
+ });
784
+ };
785
+ return [
786
+ effectiveSignal,
787
+ autoAbortController,
788
+ cleanup
789
+ ];
790
+ }
791
+ /**
792
+ * 🆕 Perform synchronous handler registration
683
793
  */
684
794
  _performRegistrationSync(action, handler, config, handlerId) {
685
795
  const registration = {
@@ -690,31 +800,65 @@ var ActionRegister = class {
690
800
  blocking: config.blocking ?? false,
691
801
  once: config.once ?? false,
692
802
  debounce: config.debounce ?? void 0,
693
- throttle: config.throttle ?? void 0
803
+ throttle: config.throttle ?? void 0,
804
+ replaceExisting: config.replaceExisting ?? false
694
805
  },
695
806
  id: handlerId
696
807
  };
697
808
  if (!this.pipelines.has(action)) this.pipelines.set(action, []);
698
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
+ }
699
814
  const existingIndex = pipeline.findIndex((reg) => reg.id === handlerId);
700
- if (existingIndex !== -1) return () => {};
701
- 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
+ }
702
846
  pipeline.push(registration);
703
847
  pipeline.sort((a, b) => b.config.priority - a.config.priority);
704
- 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)}`, {
705
850
  handlerId,
706
851
  priority: config.priority,
707
- totalHandlers: pipeline.length,
708
- registry: this.name
852
+ totalHandlers: pipeline.length
709
853
  });
710
854
  return () => {
711
855
  const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
712
856
  if (index !== -1) {
713
857
  pipeline.splice(index, 1);
714
- 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)}`, {
715
860
  handlerId,
716
- remainingHandlers: pipeline.length,
717
- registry: this.name
861
+ remainingHandlers: pipeline.length
718
862
  });
719
863
  }
720
864
  };
@@ -735,7 +879,8 @@ var ActionRegister = class {
735
879
  * @public
736
880
  */
737
881
  async dispatch(action, payload, options) {
738
- return this.dispatchQueue.enqueue(async () => {
882
+ if (options?.immediate || !this.dispatchQueue) return this._performDispatch(action, payload, options);
883
+ else return this.dispatchQueue.enqueue(async () => {
739
884
  return this._performDispatch(action, payload, options);
740
885
  });
741
886
  }
@@ -743,47 +888,13 @@ var ActionRegister = class {
743
888
  * 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
744
889
  */
745
890
  async _performDispatch(action, payload, options) {
746
- if (payload && typeof payload === "object" && payload !== null && typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
747
- payload instanceof Event;
748
- payload instanceof Element;
749
- payload.preventDefault;
750
- payload.stopPropagation;
751
- payload.currentTarget;
752
- const hasTarget = payload.target !== void 0;
753
- hasTarget && payload.target;
754
- hasTarget && payload.target instanceof Element;
755
- if (typeof process !== "undefined" && process.env?.DEBUG_CONTEXT_ACTION || typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
756
- const nestedDOMProperties = [];
757
- Object.keys(payload).forEach((key) => {
758
- const prop = payload[key];
759
- if (prop instanceof Element || prop instanceof Event) nestedDOMProperties.push(`${key}: ${prop instanceof Element ? "Element" : "Event"}`);
760
- });
761
- if (nestedDOMProperties.length > 0) console.debug(`[Context-Action] 📋 Nested DOM objects in action "${String(action)}":`, {
762
- registry: this.name,
763
- nestedDOMProperties,
764
- note: "This is informational - usually not a problem"
765
- });
766
- }
767
- }
768
- let autoAbortController;
769
- let effectiveSignal = options?.signal;
770
- if (options?.autoAbort?.enabled) {
771
- autoAbortController = new AbortController();
772
- effectiveSignal = autoAbortController.signal;
773
- if (options.autoAbort.onControllerCreated) options.autoAbort.onControllerCreated(autoAbortController);
774
- if (options?.signal) {
775
- const originalSignal = options.signal;
776
- if (originalSignal.aborted) autoAbortController.abort();
777
- else {
778
- const abortHandler$1 = () => autoAbortController.abort();
779
- originalSignal.addEventListener("abort", abortHandler$1, { once: true });
780
- }
781
- }
782
- }
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);
783
894
  if (effectiveSignal?.aborted) return;
784
895
  const pipeline = this.pipelines.get(action);
785
896
  if (!pipeline || pipeline.length === 0) return;
786
- const filteredHandlers = this.filterHandlers([...pipeline], options?.filter);
897
+ const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
787
898
  const actionKey = String(action);
788
899
  let throttleMs;
789
900
  let debounceMs;
@@ -823,8 +934,6 @@ var ActionRegister = class {
823
934
  terminated: false,
824
935
  terminationResult: void 0
825
936
  };
826
- const startTime = Date.now();
827
- let executionSuccess = true;
828
937
  const abortHandler = effectiveSignal ? () => {
829
938
  context.aborted = true;
830
939
  context.abortReason = "Action dispatch aborted by signal";
@@ -832,15 +941,12 @@ var ActionRegister = class {
832
941
  if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
833
942
  try {
834
943
  await this.executePipeline(context, autoAbortController, options?.autoAbort);
835
- console.log(`[ActionRegister] Pipeline execution succeeded for ${String(action)}`);
944
+ this.log(`Pipeline execution succeeded for ${String(action)}`);
836
945
  } catch (error) {
837
- console.log(`[ActionRegister] Pipeline execution failed for ${String(action)}:`, error);
838
- executionSuccess = false;
946
+ this.log(`Pipeline execution failed for ${String(action)}`, error, "error");
839
947
  throw error;
840
948
  } finally {
841
- if (effectiveSignal && abortHandler) effectiveSignal.removeEventListener("abort", abortHandler);
842
- const duration = Date.now() - startTime;
843
- this.updateExecutionStats(action, executionSuccess, duration);
949
+ cleanup();
844
950
  }
845
951
  }
846
952
  /**
@@ -857,36 +963,25 @@ var ActionRegister = class {
857
963
  * @public
858
964
  */
859
965
  async dispatchWithResult(action, payload, options) {
860
- const startTime = Date.now();
861
- let autoAbortController;
862
- let effectiveSignal = options?.signal;
863
- if (options?.autoAbort?.enabled) {
864
- autoAbortController = new AbortController();
865
- effectiveSignal = autoAbortController.signal;
866
- if (options.autoAbort.onControllerCreated) options.autoAbort.onControllerCreated(autoAbortController);
867
- if (options?.signal) {
868
- const originalSignal = options.signal;
869
- if (originalSignal.aborted) autoAbortController.abort();
870
- else {
871
- const abortHandler$1 = () => autoAbortController.abort();
872
- originalSignal.addEventListener("abort", abortHandler$1, { once: true });
873
- }
874
- }
875
- }
966
+ const _startTime = Date.now();
967
+ const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
968
+ if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
876
969
  if (effectiveSignal?.aborted) return {
877
970
  success: false,
878
971
  aborted: true,
879
972
  abortReason: "Action dispatch aborted by signal",
880
973
  terminated: false,
881
974
  result: void 0,
975
+ successResults: [],
882
976
  results: [],
977
+ failedResults: [],
883
978
  execution: {
884
979
  duration: 0,
885
980
  handlersExecuted: 0,
886
981
  handlersSkipped: 0,
887
982
  handlersFailed: 0,
888
- startTime,
889
- endTime: startTime
983
+ startTime: _startTime,
984
+ endTime: _startTime
890
985
  },
891
986
  handlers: [],
892
987
  errors: []
@@ -897,19 +992,21 @@ var ActionRegister = class {
897
992
  aborted: false,
898
993
  terminated: false,
899
994
  result: void 0,
995
+ successResults: [],
900
996
  results: [],
997
+ failedResults: [],
901
998
  execution: {
902
999
  duration: 0,
903
1000
  handlersExecuted: 0,
904
1001
  handlersSkipped: 0,
905
1002
  handlersFailed: 0,
906
- startTime,
907
- endTime: startTime
1003
+ startTime: _startTime,
1004
+ endTime: _startTime
908
1005
  },
909
1006
  handlers: [],
910
1007
  errors: []
911
1008
  };
912
- const filteredHandlers = this.filterHandlers([...pipeline], options?.filter);
1009
+ const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
913
1010
  const actionKey = String(action);
914
1011
  let throttleMs;
915
1012
  let debounceMs;
@@ -935,13 +1032,15 @@ var ActionRegister = class {
935
1032
  abortReason: "Debounced execution",
936
1033
  terminated: false,
937
1034
  result: void 0,
1035
+ successResults: [],
938
1036
  results: [],
1037
+ failedResults: [],
939
1038
  execution: {
940
- duration: Date.now() - startTime,
1039
+ duration: Date.now() - _startTime,
941
1040
  handlersExecuted: 0,
942
1041
  handlersSkipped: pipeline.length,
943
1042
  handlersFailed: 0,
944
- startTime,
1043
+ startTime: _startTime,
945
1044
  endTime: Date.now()
946
1045
  },
947
1046
  handlers: [],
@@ -956,13 +1055,15 @@ var ActionRegister = class {
956
1055
  abortReason: "Throttled execution",
957
1056
  terminated: false,
958
1057
  result: void 0,
1058
+ successResults: [],
959
1059
  results: [],
1060
+ failedResults: [],
960
1061
  execution: {
961
- duration: Date.now() - startTime,
1062
+ duration: Date.now() - _startTime,
962
1063
  handlersExecuted: 0,
963
1064
  handlersSkipped: pipeline.length,
964
1065
  handlersFailed: 0,
965
- startTime,
1066
+ startTime: _startTime,
966
1067
  endTime: Date.now()
967
1068
  },
968
1069
  handlers: [],
@@ -1001,47 +1102,92 @@ var ActionRegister = class {
1001
1102
  timestamp: Date.now()
1002
1103
  });
1003
1104
  } finally {
1004
- if (effectiveSignal && abortHandler) effectiveSignal.removeEventListener("abort", abortHandler);
1105
+ cleanup();
1005
1106
  }
1006
1107
  const endTime = Date.now();
1007
- const executionSuccess = !executionError && !context.aborted;
1008
- this.updateExecutionStats(action, executionSuccess, endTime - startTime);
1108
+ !executionError && context.aborted;
1009
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
+ }));
1010
1116
  const executionResult = {
1011
1117
  success: !executionError && !context.aborted,
1012
1118
  aborted: context.aborted,
1013
1119
  abortReason: context.abortReason,
1014
1120
  terminated: context.terminated,
1015
1121
  result: processedResult,
1122
+ successResults,
1016
1123
  results: context.results,
1124
+ failedResults,
1017
1125
  execution: {
1018
- duration: endTime - startTime,
1126
+ duration: endTime - _startTime,
1019
1127
  handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
1020
1128
  handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
1021
1129
  handlersFailed: errors.length,
1022
- startTime,
1130
+ startTime: _startTime,
1023
1131
  endTime
1024
1132
  },
1025
1133
  handlers: handlerResults,
1026
- errors
1134
+ errors: errors.map((err) => ({
1135
+ handlerId: err.handlerId,
1136
+ error: err.error,
1137
+ timestamp: err.timestamp,
1138
+ severity: "non-blocking"
1139
+ }))
1027
1140
  };
1028
1141
  /** Clean up one-time handlers after execution */
1029
1142
  this.cleanupOneTimeHandlers(action, context.handlers);
1030
1143
  return executionResult;
1031
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
+ }
1032
1165
  filterHandlers(handlers, filterOptions) {
1033
1166
  if (!filterOptions) return handlers;
1034
- 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) => {
1035
1173
  const config = registration.config;
1036
- if (filterOptions.handlerIds && filterOptions.handlerIds.length > 0) {
1037
- if (!filterOptions.handlerIds.includes(config.id)) return false;
1038
- }
1039
- if (filterOptions.excludeHandlerIds && filterOptions.excludeHandlerIds.length > 0) {
1040
- 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;
1041
1179
  }
1042
1180
  if (filterOptions.custom && !filterOptions.custom(config)) return false;
1043
1181
  return true;
1044
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;
1045
1191
  }
1046
1192
  processResults(context, resultOptions) {
1047
1193
  if (!resultOptions || !resultOptions.collect) return void 0;
@@ -1127,26 +1273,6 @@ var ActionRegister = class {
1127
1273
  });
1128
1274
  }
1129
1275
  /**
1130
- * Update execution statistics for an action
1131
- *
1132
- * @param action Action name
1133
- * @param success Whether execution was successful
1134
- * @param duration Execution duration in milliseconds
1135
- */
1136
- updateExecutionStats(action, success, duration) {
1137
- if (!this.executionStats.has(action)) this.executionStats.set(action, {
1138
- totalExecutions: 0,
1139
- totalDuration: 0,
1140
- successCount: 0,
1141
- errorCount: 0
1142
- });
1143
- const stats = this.executionStats.get(action);
1144
- stats.totalExecutions++;
1145
- stats.totalDuration += duration;
1146
- if (success) stats.successCount++;
1147
- else stats.errorCount++;
1148
- }
1149
- /**
1150
1276
  * Get the number of registered handlers for an action
1151
1277
  *
1152
1278
  * @param action - The action type to count handlers for
@@ -1198,6 +1324,7 @@ var ActionRegister = class {
1198
1324
  */
1199
1325
  clearAction(action) {
1200
1326
  this.pipelines.delete(action);
1327
+ this.invalidateFilterCache();
1201
1328
  }
1202
1329
  /**
1203
1330
  * Remove all handlers for all actions
@@ -1208,6 +1335,7 @@ var ActionRegister = class {
1208
1335
  */
1209
1336
  clearAll() {
1210
1337
  this.pipelines.clear();
1338
+ this.invalidateFilterCache();
1211
1339
  }
1212
1340
  /**
1213
1341
  * Get the name of this action register
@@ -1255,13 +1383,7 @@ var ActionRegister = class {
1255
1383
  priority,
1256
1384
  handlers: handlers.map((h) => ({ id: h.config.id }))
1257
1385
  }));
1258
- const stats = this.executionStats.get(action);
1259
- const executionStats = stats ? {
1260
- totalExecutions: stats.totalExecutions,
1261
- averageDuration: stats.totalExecutions > 0 ? stats.totalDuration / stats.totalExecutions : 0,
1262
- successRate: stats.totalExecutions > 0 ? stats.successCount / stats.totalExecutions * 100 : 0,
1263
- errorCount: stats.errorCount
1264
- } : void 0;
1386
+ const executionStats = void 0;
1265
1387
  return {
1266
1388
  action,
1267
1389
  handlerCount: pipeline.length,
@@ -1307,22 +1429,6 @@ var ActionRegister = class {
1307
1429
  if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution mode reset for action '${String(action)}' to default: ${this.executionMode}`);
1308
1430
  }
1309
1431
  /**
1310
- * Clear execution statistics for all actions
1311
- */
1312
- clearExecutionStats() {
1313
- this.executionStats.clear();
1314
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution statistics cleared for registry: ${this.name}`);
1315
- }
1316
- /**
1317
- * Clear execution statistics for a specific action
1318
- *
1319
- * @param action Action name
1320
- */
1321
- clearActionExecutionStats(action) {
1322
- this.executionStats.delete(action);
1323
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution statistics cleared for action: ${String(action)}`);
1324
- }
1325
- /**
1326
1432
  * Get registry configuration (for debugging and inspection)
1327
1433
  *
1328
1434
  * @returns Current registry configuration
@@ -1336,13 +1442,292 @@ var ActionRegister = class {
1336
1442
  * @returns Whether debug mode is enabled
1337
1443
  */
1338
1444
  isDebugEnabled() {
1339
- 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");
1340
1462
  }
1341
1463
  };
1342
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
+ };
1688
+ }
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
+ }
1721
+
1343
1722
  //#endregion
1344
1723
  exports.ActionGuard = ActionGuard;
1345
1724
  exports.ActionRegister = ActionRegister;
1725
+ exports.ReactActionError = ReactActionError;
1726
+ exports.ReactDevUtils = ReactDevUtils;
1727
+ exports.createActionHandler = createActionHandler;
1728
+ exports.createReactDispatcher = createReactDispatcher;
1729
+ exports.createReactHandlerConfig = createReactHandlerConfig;
1346
1730
  exports.executeParallel = executeParallel;
1347
1731
  exports.executeRace = executeRace;
1348
- exports.executeSequential = executeSequential;
1732
+ exports.executeSequential = executeSequential;
1733
+ exports.isReactActionError = isReactActionError;