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