@context-action/core 0.4.0 → 0.6.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,74 @@ 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,
851
+ totalHandlers: pipeline.length
852
+ });
853
+ console.log(`🔍 [DEBUG] Action '${String(action)}' pipeline after registration:`, {
706
854
  totalHandlers: pipeline.length,
707
- registry: this.name
855
+ handlers: pipeline.map((h) => ({
856
+ id: h.config.id,
857
+ priority: h.config.priority
858
+ })),
859
+ pipelineExists: this.pipelines.has(action),
860
+ canDispatch: this.hasHandlers(action)
708
861
  });
709
862
  return () => {
710
863
  const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
711
864
  if (index !== -1) {
712
865
  pipeline.splice(index, 1);
713
- if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Handler unregistered: ${String(action)}`, {
866
+ this.invalidateFilterCache();
867
+ this.log(`Handler unregistered: ${String(action)}`, {
714
868
  handlerId,
715
- remainingHandlers: pipeline.length,
716
- registry: this.name
869
+ remainingHandlers: pipeline.length
717
870
  });
718
871
  }
719
872
  };
@@ -734,7 +887,8 @@ var ActionRegister = class {
734
887
  * @public
735
888
  */
736
889
  async dispatch(action, payload, options) {
737
- return this.dispatchQueue.enqueue(async () => {
890
+ if (options?.immediate || !this.dispatchQueue) return this._performDispatch(action, payload, options);
891
+ else return this.dispatchQueue.enqueue(async () => {
738
892
  return this._performDispatch(action, payload, options);
739
893
  });
740
894
  }
@@ -742,47 +896,31 @@ var ActionRegister = class {
742
896
  * 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
743
897
  */
744
898
  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
- }
899
+ console.log(`🚀 [DEBUG] Starting dispatch for action '${String(action)}':`, {
900
+ hasPayload: payload !== void 0,
901
+ payloadType: payload?.constructor?.name || typeof payload,
902
+ options: options ? Object.keys(options) : "none",
903
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
904
+ });
905
+ if (payload instanceof Event && process.env.NODE_ENV === "development") console.warn(`Event object passed to action "${String(action)}"`, payload.type);
906
+ const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
907
+ if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
908
+ if (effectiveSignal?.aborted) {
909
+ console.log(`🚫 [DEBUG] Dispatch aborted before execution for '${String(action)}'`);
910
+ return;
781
911
  }
782
- if (effectiveSignal?.aborted) return;
783
912
  const pipeline = this.pipelines.get(action);
784
- if (!pipeline || pipeline.length === 0) return;
785
- const filteredHandlers = this.filterHandlers([...pipeline], options?.filter);
913
+ console.log(`🔍 [DEBUG] Pipeline lookup for '${String(action)}':`, {
914
+ pipelineExists: Boolean(pipeline),
915
+ handlersCount: pipeline?.length || 0,
916
+ allRegisteredActions: Array.from(this.pipelines.keys()),
917
+ pipelineMap: Object.fromEntries(Array.from(this.pipelines.entries()).map(([k, v]) => [k, v.length]))
918
+ });
919
+ if (!pipeline || pipeline.length === 0) {
920
+ console.log(`⚠️ [DEBUG] No handlers found for action '${String(action)}', dispatch cancelled`);
921
+ return;
922
+ }
923
+ const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
786
924
  const actionKey = String(action);
787
925
  let throttleMs;
788
926
  let debounceMs;
@@ -822,8 +960,6 @@ var ActionRegister = class {
822
960
  terminated: false,
823
961
  terminationResult: void 0
824
962
  };
825
- const startTime = Date.now();
826
- let executionSuccess = true;
827
963
  const abortHandler = effectiveSignal ? () => {
828
964
  context.aborted = true;
829
965
  context.abortReason = "Action dispatch aborted by signal";
@@ -831,15 +967,12 @@ var ActionRegister = class {
831
967
  if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
832
968
  try {
833
969
  await this.executePipeline(context, autoAbortController, options?.autoAbort);
834
- console.log(`[ActionRegister] Pipeline execution succeeded for ${String(action)}`);
970
+ this.log(`Pipeline execution succeeded for ${String(action)}`);
835
971
  } catch (error) {
836
- console.log(`[ActionRegister] Pipeline execution failed for ${String(action)}:`, error);
837
- executionSuccess = false;
972
+ this.log(`Pipeline execution failed for ${String(action)}`, error, "error");
838
973
  throw error;
839
974
  } finally {
840
- if (effectiveSignal && abortHandler) effectiveSignal.removeEventListener("abort", abortHandler);
841
- const duration = Date.now() - startTime;
842
- this.updateExecutionStats(action, executionSuccess, duration);
975
+ cleanup();
843
976
  }
844
977
  }
845
978
  /**
@@ -856,36 +989,25 @@ var ActionRegister = class {
856
989
  * @public
857
990
  */
858
991
  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
- }
992
+ const _startTime = Date.now();
993
+ const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
994
+ if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
875
995
  if (effectiveSignal?.aborted) return {
876
996
  success: false,
877
997
  aborted: true,
878
998
  abortReason: "Action dispatch aborted by signal",
879
999
  terminated: false,
880
1000
  result: void 0,
1001
+ successResults: [],
881
1002
  results: [],
1003
+ failedResults: [],
882
1004
  execution: {
883
1005
  duration: 0,
884
1006
  handlersExecuted: 0,
885
1007
  handlersSkipped: 0,
886
1008
  handlersFailed: 0,
887
- startTime,
888
- endTime: startTime
1009
+ startTime: _startTime,
1010
+ endTime: _startTime
889
1011
  },
890
1012
  handlers: [],
891
1013
  errors: []
@@ -896,19 +1018,21 @@ var ActionRegister = class {
896
1018
  aborted: false,
897
1019
  terminated: false,
898
1020
  result: void 0,
1021
+ successResults: [],
899
1022
  results: [],
1023
+ failedResults: [],
900
1024
  execution: {
901
1025
  duration: 0,
902
1026
  handlersExecuted: 0,
903
1027
  handlersSkipped: 0,
904
1028
  handlersFailed: 0,
905
- startTime,
906
- endTime: startTime
1029
+ startTime: _startTime,
1030
+ endTime: _startTime
907
1031
  },
908
1032
  handlers: [],
909
1033
  errors: []
910
1034
  };
911
- const filteredHandlers = this.filterHandlers([...pipeline], options?.filter);
1035
+ const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
912
1036
  const actionKey = String(action);
913
1037
  let throttleMs;
914
1038
  let debounceMs;
@@ -934,13 +1058,15 @@ var ActionRegister = class {
934
1058
  abortReason: "Debounced execution",
935
1059
  terminated: false,
936
1060
  result: void 0,
1061
+ successResults: [],
937
1062
  results: [],
1063
+ failedResults: [],
938
1064
  execution: {
939
- duration: Date.now() - startTime,
1065
+ duration: Date.now() - _startTime,
940
1066
  handlersExecuted: 0,
941
1067
  handlersSkipped: pipeline.length,
942
1068
  handlersFailed: 0,
943
- startTime,
1069
+ startTime: _startTime,
944
1070
  endTime: Date.now()
945
1071
  },
946
1072
  handlers: [],
@@ -955,13 +1081,15 @@ var ActionRegister = class {
955
1081
  abortReason: "Throttled execution",
956
1082
  terminated: false,
957
1083
  result: void 0,
1084
+ successResults: [],
958
1085
  results: [],
1086
+ failedResults: [],
959
1087
  execution: {
960
- duration: Date.now() - startTime,
1088
+ duration: Date.now() - _startTime,
961
1089
  handlersExecuted: 0,
962
1090
  handlersSkipped: pipeline.length,
963
1091
  handlersFailed: 0,
964
- startTime,
1092
+ startTime: _startTime,
965
1093
  endTime: Date.now()
966
1094
  },
967
1095
  handlers: [],
@@ -985,6 +1113,12 @@ var ActionRegister = class {
985
1113
  let executionError;
986
1114
  const handlerResults = [];
987
1115
  const errors = [];
1116
+ filteredHandlers.forEach((handler) => {
1117
+ handlerResults.push({
1118
+ id: handler.config.id,
1119
+ executed: false
1120
+ });
1121
+ });
988
1122
  const abortHandler = effectiveSignal ? () => {
989
1123
  context.aborted = true;
990
1124
  context.abortReason = "Action dispatch aborted by signal";
@@ -992,6 +1126,11 @@ var ActionRegister = class {
992
1126
  if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
993
1127
  try {
994
1128
  await this.executePipeline(context, autoAbortController, options?.autoAbort);
1129
+ const executedCount = Math.min(context.currentIndex + (context.aborted ? 0 : 1), filteredHandlers.length);
1130
+ for (let i = 0; i < executedCount; i++) {
1131
+ const handlerResult = handlerResults.find((hr) => hr.id === filteredHandlers[i].config.id);
1132
+ if (handlerResult) handlerResult.executed = true;
1133
+ }
995
1134
  } catch (error) {
996
1135
  executionError = error instanceof Error ? error : new Error(String(error));
997
1136
  errors.push({
@@ -999,48 +1138,97 @@ var ActionRegister = class {
999
1138
  error: executionError,
1000
1139
  timestamp: Date.now()
1001
1140
  });
1141
+ const executedCount = Math.min(context.currentIndex + 1, filteredHandlers.length);
1142
+ for (let i = 0; i < executedCount; i++) {
1143
+ const handlerResult = handlerResults.find((hr) => hr.id === filteredHandlers[i].config.id);
1144
+ if (handlerResult) handlerResult.executed = true;
1145
+ }
1002
1146
  } finally {
1003
- if (effectiveSignal && abortHandler) effectiveSignal.removeEventListener("abort", abortHandler);
1147
+ cleanup();
1004
1148
  }
1005
1149
  const endTime = Date.now();
1006
- const executionSuccess = !executionError && !context.aborted;
1007
- this.updateExecutionStats(action, executionSuccess, endTime - startTime);
1008
1150
  const processedResult = this.processResults(context, options?.result);
1151
+ const successResults = context.results.filter((result) => result !== void 0);
1152
+ const failedResults = errors.map((err) => ({
1153
+ handlerId: err.handlerId,
1154
+ error: err.error,
1155
+ expectedType: typeof processedResult
1156
+ }));
1009
1157
  const executionResult = {
1010
1158
  success: !executionError && !context.aborted,
1011
1159
  aborted: context.aborted,
1012
1160
  abortReason: context.abortReason,
1013
1161
  terminated: context.terminated,
1014
1162
  result: processedResult,
1163
+ successResults,
1015
1164
  results: context.results,
1165
+ failedResults,
1016
1166
  execution: {
1017
- duration: endTime - startTime,
1167
+ duration: endTime - _startTime,
1018
1168
  handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
1019
1169
  handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
1020
1170
  handlersFailed: errors.length,
1021
- startTime,
1171
+ startTime: _startTime,
1022
1172
  endTime
1023
1173
  },
1024
1174
  handlers: handlerResults,
1025
- errors
1175
+ errors: errors.map((err) => ({
1176
+ handlerId: err.handlerId,
1177
+ error: err.error,
1178
+ timestamp: err.timestamp,
1179
+ severity: "non-blocking"
1180
+ }))
1026
1181
  };
1027
1182
  /** Clean up one-time handlers after execution */
1028
1183
  this.cleanupOneTimeHandlers(action, context.handlers);
1029
1184
  return executionResult;
1030
1185
  }
1186
+ /**
1187
+ * 🔧 Generate cache key for filter options
1188
+ */
1189
+ generateFilterCacheKey(filterOptions) {
1190
+ if (!filterOptions) return "no-filter";
1191
+ const key = [
1192
+ filterOptions.handlerIds?.sort().join(",") || "none",
1193
+ filterOptions.excludeHandlerIds?.sort().join(",") || "none",
1194
+ filterOptions.priority?.min?.toString() || "none",
1195
+ filterOptions.priority?.max?.toString() || "none",
1196
+ filterOptions.custom ? "custom" : "none"
1197
+ ].join("|");
1198
+ return key;
1199
+ }
1200
+ /**
1201
+ * 🔧 Clear filter cache when pipelines change
1202
+ */
1203
+ invalidateFilterCache() {
1204
+ this.filterCache.clear();
1205
+ }
1031
1206
  filterHandlers(handlers, filterOptions) {
1032
1207
  if (!filterOptions) return handlers;
1033
- return handlers.filter((registration) => {
1208
+ const cacheKey = this.generateFilterCacheKey(filterOptions);
1209
+ if (!filterOptions.custom) {
1210
+ const cached = this.filterCache.get(cacheKey);
1211
+ if (cached) return cached;
1212
+ }
1213
+ const filtered = handlers.filter((registration) => {
1034
1214
  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;
1215
+ if (filterOptions.handlerIds?.length && !filterOptions.handlerIds.includes(config.id)) return false;
1216
+ if (filterOptions.excludeHandlerIds?.length && filterOptions.excludeHandlerIds.includes(config.id)) return false;
1217
+ if (filterOptions.priority) {
1218
+ if (filterOptions.priority.min !== void 0 && config.priority < filterOptions.priority.min) return false;
1219
+ if (filterOptions.priority.max !== void 0 && config.priority > filterOptions.priority.max) return false;
1040
1220
  }
1041
1221
  if (filterOptions.custom && !filterOptions.custom(config)) return false;
1042
1222
  return true;
1043
1223
  });
1224
+ if (!filterOptions.custom) {
1225
+ if (this.filterCache.size >= this.filterCacheMaxSize) {
1226
+ const firstKey = this.filterCache.keys().next().value;
1227
+ if (firstKey) this.filterCache.delete(firstKey);
1228
+ }
1229
+ this.filterCache.set(cacheKey, filtered);
1230
+ }
1231
+ return filtered;
1044
1232
  }
1045
1233
  processResults(context, resultOptions) {
1046
1234
  if (!resultOptions || !resultOptions.collect) return void 0;
@@ -1126,26 +1314,6 @@ var ActionRegister = class {
1126
1314
  });
1127
1315
  }
1128
1316
  /**
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
1317
  * Get the number of registered handlers for an action
1150
1318
  *
1151
1319
  * @param action - The action type to count handlers for
@@ -1197,6 +1365,7 @@ var ActionRegister = class {
1197
1365
  */
1198
1366
  clearAction(action) {
1199
1367
  this.pipelines.delete(action);
1368
+ this.invalidateFilterCache();
1200
1369
  }
1201
1370
  /**
1202
1371
  * Remove all handlers for all actions
@@ -1207,6 +1376,7 @@ var ActionRegister = class {
1207
1376
  */
1208
1377
  clearAll() {
1209
1378
  this.pipelines.clear();
1379
+ this.invalidateFilterCache();
1210
1380
  }
1211
1381
  /**
1212
1382
  * Get the name of this action register
@@ -1254,13 +1424,7 @@ var ActionRegister = class {
1254
1424
  priority,
1255
1425
  handlers: handlers.map((h) => ({ id: h.config.id }))
1256
1426
  }));
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;
1427
+ const executionStats = void 0;
1264
1428
  return {
1265
1429
  action,
1266
1430
  handlerCount: pipeline.length,
@@ -1306,22 +1470,6 @@ var ActionRegister = class {
1306
1470
  if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Execution mode reset for action '${String(action)}' to default: ${this.executionMode}`);
1307
1471
  }
1308
1472
  /**
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
1473
  * Get registry configuration (for debugging and inspection)
1326
1474
  *
1327
1475
  * @returns Current registry configuration
@@ -1335,10 +1483,283 @@ var ActionRegister = class {
1335
1483
  * @returns Whether debug mode is enabled
1336
1484
  */
1337
1485
  isDebugEnabled() {
1338
- return Boolean(this.registryConfig?.debug && process.env.NODE_ENV === "development");
1486
+ return this.isDebugMode;
1487
+ }
1488
+ /**
1489
+ * 🆕 Destroy method for comprehensive cleanup
1490
+ *
1491
+ * Cleans up all internal resources including pipelines, guards, queues, and statistics.
1492
+ * Should be called when the ActionRegister is no longer needed to prevent memory leaks.
1493
+ *
1494
+ * @public
1495
+ */
1496
+ destroy() {
1497
+ this.pipelines.clear();
1498
+ this.actionGuard.destroy();
1499
+ this.dispatchQueue?.clear?.();
1500
+ this.actionExecutionModes.clear();
1501
+ this.filterCache.clear();
1502
+ this.log("ActionRegister destroyed");
1503
+ }
1504
+ };
1505
+
1506
+ //#endregion
1507
+ //#region src/react-helpers.ts
1508
+ /**
1509
+ * 🔧 Create action handler registration configuration for React components
1510
+ *
1511
+ * Creates a configuration object that can be used with React's useEffect to properly
1512
+ * register and unregister action handlers with lifecycle management and cleanup.
1513
+ * This is NOT a hook - it's a factory function for React hook integration.
1514
+ *
1515
+ * @template T - ActionPayloadMap type
1516
+ * @template K - Action key type
1517
+ *
1518
+ * @param registry - ActionRegister instance
1519
+ * @param action - Action name to register handler for
1520
+ * @param handler - Handler function (should be memoized with useCallback)
1521
+ * @param config - Handler configuration
1522
+ *
1523
+ * @returns Configuration object with register/unregister functions
1524
+ *
1525
+ * @example Basic Usage with useEffect
1526
+ * ```tsx
1527
+ * import { useCallback, useEffect } from 'react';
1528
+ * import { createActionHandler } from '@context-action/core/react-helpers';
1529
+ *
1530
+ * function MyComponent() {
1531
+ * const registry = useActionRegister();
1532
+ *
1533
+ * const handleUserUpdate = useCallback(async (payload, controller) => {
1534
+ * // Handler logic here
1535
+ * }, []);
1536
+ *
1537
+ * useEffect(() => {
1538
+ * const { register, unregister } = createActionHandler(
1539
+ * registry,
1540
+ * 'updateUser',
1541
+ * handleUserUpdate,
1542
+ * { priority: 10 }
1543
+ * );
1544
+ *
1545
+ * const cleanup = register();
1546
+ * return () => {
1547
+ * cleanup();
1548
+ * unregister();
1549
+ * };
1550
+ * }, [registry, handleUserUpdate]);
1551
+ * }
1552
+ * ```
1553
+ *
1554
+ * @example With Automatic Cleanup
1555
+ * ```tsx
1556
+ * const [userId, setUserId] = useState('123');
1557
+ *
1558
+ * const handleUserUpdate = useCallback(async (payload, controller) => {
1559
+ * console.log('Updating user:', userId, payload);
1560
+ * }, [userId]);
1561
+ *
1562
+ * useEffect(() => {
1563
+ * const handlerManager = createActionHandler(
1564
+ * registry,
1565
+ * 'updateUser',
1566
+ * handleUserUpdate,
1567
+ * { priority: 10 }
1568
+ * );
1569
+ *
1570
+ * // Simplified registration with automatic cleanup
1571
+ * return handlerManager.registerWithCleanup();
1572
+ * }, [registry, handleUserUpdate, userId]);
1573
+ * ```
1574
+ *
1575
+ * @public
1576
+ */
1577
+ function createActionHandler(registry, action, handler, config) {
1578
+ const finalConfig = createReactHandlerConfig(String(action), void 0, config);
1579
+ let currentUnregister;
1580
+ let isRegistered = false;
1581
+ return {
1582
+ register() {
1583
+ if (isRegistered && currentUnregister) currentUnregister();
1584
+ currentUnregister = registry.register(action, handler, finalConfig);
1585
+ isRegistered = true;
1586
+ return currentUnregister;
1587
+ },
1588
+ unregister() {
1589
+ if (isRegistered && currentUnregister) {
1590
+ currentUnregister();
1591
+ currentUnregister = void 0;
1592
+ isRegistered = false;
1593
+ }
1594
+ },
1595
+ registerWithCleanup() {
1596
+ const unregisterFn = this.register();
1597
+ return () => {
1598
+ unregisterFn();
1599
+ this.unregister();
1600
+ };
1601
+ },
1602
+ config: finalConfig
1603
+ };
1604
+ }
1605
+ /**
1606
+ * 🆕 React handler configuration factory
1607
+ *
1608
+ * Creates optimized handler configurations for React environments with
1609
+ * proper cleanup and unique ID generation.
1610
+ *
1611
+ * @template T - ActionPayloadMap type
1612
+ * @template K - Action key type
1613
+ *
1614
+ * @param action - Action name
1615
+ * @param componentId - Optional component identifier for debugging
1616
+ * @param config - Base handler configuration
1617
+ *
1618
+ * @returns Optimized configuration for React environments
1619
+ *
1620
+ * @example
1621
+ * ```tsx
1622
+ * function MyComponent({ userId }: { userId: string }) {
1623
+ * const registry = useActionRegister();
1624
+ *
1625
+ * useEffect(() => {
1626
+ * const config = createReactHandlerConfig('updateUser', 'MyComponent', {
1627
+ * priority: 10
1628
+ * });
1629
+ *
1630
+ * const unregister = registry.register('updateUser', handler, config);
1631
+ * return unregister;
1632
+ * }, [registry, handler]);
1633
+ * }
1634
+ * ```
1635
+ *
1636
+ * @public
1637
+ */
1638
+ function createReactHandlerConfig(action, componentId, config = {}) {
1639
+ const timestamp = Date.now();
1640
+ const random = Math.random().toString(36).substr(2, 5);
1641
+ return {
1642
+ priority: config.priority ?? 0,
1643
+ id: config.id || `${componentId || "react"}_${action}_${timestamp}_${random}`,
1644
+ blocking: config.blocking ?? false,
1645
+ once: config.once ?? false,
1646
+ debounce: config.debounce ?? void 0,
1647
+ throttle: config.throttle ?? void 0,
1648
+ replaceExisting: true
1649
+ };
1650
+ }
1651
+ /**
1652
+ * 🆕 React action dispatcher factory
1653
+ *
1654
+ * Creates a dispatcher function optimized for React component usage
1655
+ * with proper error boundaries and async handling.
1656
+ *
1657
+ * @template T - ActionPayloadMap type
1658
+ *
1659
+ * @param registry - ActionRegister instance
1660
+ * @param errorHandler - Optional error handler for unhandled dispatch errors
1661
+ *
1662
+ * @returns Optimized dispatch function for React components
1663
+ *
1664
+ * @example
1665
+ * ```tsx
1666
+ * function MyComponent() {
1667
+ * const registry = useActionRegister();
1668
+ *
1669
+ * const dispatch = createReactDispatcher(registry, (error, action, payload) => {
1670
+ * console.error(`Failed to dispatch ${action}:`, error);
1671
+ * });
1672
+ *
1673
+ * const handleClick = useCallback(() => {
1674
+ * dispatch('userClick', { buttonId: 'submit' });
1675
+ * }, [dispatch]);
1676
+ * }
1677
+ * ```
1678
+ *
1679
+ * @public
1680
+ */
1681
+ function createReactDispatcher(registry, errorHandler) {
1682
+ return async (action, payload, options) => {
1683
+ try {
1684
+ await registry.dispatch(action, payload, {
1685
+ immediate: false,
1686
+ ...options
1687
+ });
1688
+ } catch (error) {
1689
+ const errorObj = error instanceof Error ? error : new Error(String(error));
1690
+ if (errorHandler) errorHandler(errorObj, action, payload);
1691
+ else console.error(`[ActionRegister] Dispatch failed for action '${String(action)}':`, errorObj);
1692
+ }
1693
+ };
1694
+ }
1695
+ /**
1696
+ * 🆕 React development utilities
1697
+ *
1698
+ * Provides debugging and development helpers specifically for React environments.
1699
+ */
1700
+ const ReactDevUtils = {
1701
+ enableDebugMode() {
1702
+ if (typeof window !== "undefined") window.__CONTEXT_ACTION_REACT_DEBUG__ = true;
1703
+ },
1704
+ disableDebugMode() {
1705
+ if (typeof window !== "undefined") window.__CONTEXT_ACTION_REACT_DEBUG__ = false;
1706
+ },
1707
+ isDebugMode() {
1708
+ return typeof window !== "undefined" && Boolean(window.__CONTEXT_ACTION_REACT_DEBUG__);
1709
+ },
1710
+ log(component, action, message, data) {
1711
+ if (this.isDebugMode()) console.log(`🎯 [React-ActionRegister] [${component}] ${action}: ${message}`, data || "");
1712
+ },
1713
+ getStats(registry) {
1714
+ const registryInfo = registry.getRegistryInfo();
1715
+ let reactHandlers = 0;
1716
+ registry.getRegisteredActions().forEach((action) => {
1717
+ const stats = registry.getActionStats(action);
1718
+ if (stats) stats.handlersByPriority.forEach((priorityGroup) => {
1719
+ priorityGroup.handlers.forEach((handler) => {
1720
+ if (handler.id.includes("react")) reactHandlers++;
1721
+ });
1722
+ });
1723
+ });
1724
+ return {
1725
+ totalHandlers: registryInfo.totalHandlers,
1726
+ reactHandlers,
1727
+ registryInfo
1728
+ };
1339
1729
  }
1340
1730
  };
1731
+ /**
1732
+ * 🆕 React Error Boundary integration
1733
+ *
1734
+ * Utilities for integrating ActionRegister errors with React Error Boundaries.
1735
+ */
1736
+ var ReactActionError = class ReactActionError extends Error {
1737
+ constructor(message, action, payload, handlerId, originalError) {
1738
+ super(message);
1739
+ this.name = "ReactActionError";
1740
+ this.action = action;
1741
+ this.payload = payload;
1742
+ this.handlerId = handlerId;
1743
+ this.timestamp = Date.now();
1744
+ if (originalError && originalError.stack) this.stack = originalError.stack;
1745
+ }
1746
+ /**
1747
+ * Create a React Error Boundary compatible error
1748
+ */
1749
+ static fromActionError(originalError, action, payload, handlerId) {
1750
+ return new ReactActionError(`Action '${action}' failed: ${originalError.message}`, action, payload, handlerId, originalError);
1751
+ }
1752
+ };
1753
+ /**
1754
+ * 🆕 Type guard for React Action Errors
1755
+ *
1756
+ * @param error - Error to check
1757
+ * @returns True if error is a ReactActionError
1758
+ */
1759
+ function isReactActionError(error) {
1760
+ return error instanceof ReactActionError;
1761
+ }
1341
1762
 
1342
1763
  //#endregion
1343
- export { ActionGuard, ActionRegister, executeParallel, executeRace, executeSequential };
1764
+ export { ActionGuard, ActionRegister, ReactActionError, ReactDevUtils, createActionHandler, createReactDispatcher, createReactHandlerConfig, executeParallel, executeRace, executeSequential, isReactActionError };
1344
1765
  //# sourceMappingURL=index.js.map