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