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