@context-action/core 0.3.1 → 0.5.0

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