@context-action/core 0.6.0 → 0.7.2
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 +8 -5
- package/dist/index.cjs +300 -263
- package/dist/index.d.cts +30 -20
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +30 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +301 -262
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -45,6 +45,7 @@ async function executeSequential(context, createController) {
|
|
|
45
45
|
while (i < context.handlers.length) {
|
|
46
46
|
if (context.aborted || context.terminated) break;
|
|
47
47
|
const registration = context.handlers[i];
|
|
48
|
+
if (!registration) continue;
|
|
48
49
|
context.currentIndex = i;
|
|
49
50
|
const controller = createController(registration, i);
|
|
50
51
|
try {
|
|
@@ -62,7 +63,8 @@ async function executeSequential(context, createController) {
|
|
|
62
63
|
errors.push({
|
|
63
64
|
handlerId: handlerError.handlerId,
|
|
64
65
|
error: handlerError.error,
|
|
65
|
-
timestamp: handlerError.timestamp
|
|
66
|
+
timestamp: handlerError.timestamp,
|
|
67
|
+
severity: "non-blocking"
|
|
66
68
|
});
|
|
67
69
|
return void 0;
|
|
68
70
|
});
|
|
@@ -154,7 +156,7 @@ async function executeParallel(context, createController) {
|
|
|
154
156
|
const failures = results.filter((result, index) => {
|
|
155
157
|
if (result.status === "rejected") {
|
|
156
158
|
const registration = runnableHandlers[index];
|
|
157
|
-
return registration
|
|
159
|
+
return registration?.config.blocking ?? false;
|
|
158
160
|
}
|
|
159
161
|
return false;
|
|
160
162
|
});
|
|
@@ -320,7 +322,11 @@ var ActionGuard = class {
|
|
|
320
322
|
/** Initialize new guard state with default values */
|
|
321
323
|
state = {
|
|
322
324
|
lastExecuted: 0,
|
|
323
|
-
isThrottled: false
|
|
325
|
+
isThrottled: false,
|
|
326
|
+
debounceTimer: void 0,
|
|
327
|
+
throttleTimer: void 0,
|
|
328
|
+
debouncePromise: void 0,
|
|
329
|
+
debounceResolve: void 0
|
|
324
330
|
};
|
|
325
331
|
this.guards.set(actionKey, state);
|
|
326
332
|
}
|
|
@@ -374,7 +380,11 @@ var ActionGuard = class {
|
|
|
374
380
|
/** Initialize new guard state with default values */
|
|
375
381
|
state = {
|
|
376
382
|
lastExecuted: 0,
|
|
377
|
-
isThrottled: false
|
|
383
|
+
isThrottled: false,
|
|
384
|
+
debounceTimer: void 0,
|
|
385
|
+
throttleTimer: void 0,
|
|
386
|
+
debouncePromise: void 0,
|
|
387
|
+
debounceResolve: void 0
|
|
378
388
|
};
|
|
379
389
|
this.guards.set(actionKey, state);
|
|
380
390
|
}
|
|
@@ -416,7 +426,6 @@ var ActionGuard = class {
|
|
|
416
426
|
clearGuards(actionKey) {
|
|
417
427
|
const state = this.guards.get(actionKey);
|
|
418
428
|
if (state) {
|
|
419
|
-
/** Clear debounce timer if active to prevent memory leaks */
|
|
420
429
|
if (state.debounceTimer) {
|
|
421
430
|
clearTimeout(state.debounceTimer);
|
|
422
431
|
if (state.debounceResolve) {
|
|
@@ -425,12 +434,10 @@ var ActionGuard = class {
|
|
|
425
434
|
}
|
|
426
435
|
state.debounceTimer = void 0;
|
|
427
436
|
}
|
|
428
|
-
/** Clear throttle timer if active to prevent memory leaks */
|
|
429
437
|
if (state.throttleTimer) {
|
|
430
438
|
clearTimeout(state.throttleTimer);
|
|
431
439
|
state.throttleTimer = void 0;
|
|
432
440
|
}
|
|
433
|
-
/** Remove guard state from memory */
|
|
434
441
|
this.guards.delete(actionKey);
|
|
435
442
|
}
|
|
436
443
|
}
|
|
@@ -558,9 +565,12 @@ var OperationQueue = class {
|
|
|
558
565
|
timestamp: Date.now()
|
|
559
566
|
};
|
|
560
567
|
let insertIndex = this.queue.length;
|
|
561
|
-
for (let i = 0; i < this.queue.length; i++)
|
|
562
|
-
|
|
563
|
-
|
|
568
|
+
for (let i = 0; i < this.queue.length; i++) {
|
|
569
|
+
const item = this.queue[i];
|
|
570
|
+
if (item && (item.priority || 0) < priority) {
|
|
571
|
+
insertIndex = i;
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
564
574
|
}
|
|
565
575
|
this.queue.splice(insertIndex, 0, queuedOperation);
|
|
566
576
|
this.processQueue();
|
|
@@ -682,8 +692,11 @@ var ActionRegister = class {
|
|
|
682
692
|
this.pipelines = /* @__PURE__ */ new Map();
|
|
683
693
|
this.executionMode = "sequential";
|
|
684
694
|
this.actionExecutionModes = /* @__PURE__ */ new Map();
|
|
695
|
+
this.unregisterFunctions = /* @__PURE__ */ new Map();
|
|
685
696
|
this.filterCache = /* @__PURE__ */ new Map();
|
|
686
|
-
this.
|
|
697
|
+
this.handlerIdCounter = 0;
|
|
698
|
+
this.controllerPool = [];
|
|
699
|
+
this.maxControllerPoolSize = 10;
|
|
687
700
|
this.name = config.name || "ActionRegister";
|
|
688
701
|
this.registryConfig = config.registry;
|
|
689
702
|
this.maxHandlersPerAction = config.registry?.maxHandlersPerAction ?? 1e3;
|
|
@@ -728,11 +741,10 @@ var ActionRegister = class {
|
|
|
728
741
|
}
|
|
729
742
|
}
|
|
730
743
|
/**
|
|
731
|
-
*
|
|
744
|
+
* 🔧 Generate unique handler ID using optimized counter-based approach
|
|
732
745
|
*/
|
|
733
746
|
generateHandlerId(action) {
|
|
734
|
-
|
|
735
|
-
return `${String(action)}_${uuid.slice(0, 8)}`;
|
|
747
|
+
return `${String(action)}_${this.name}_${++this.handlerIdCounter}`;
|
|
736
748
|
}
|
|
737
749
|
/**
|
|
738
750
|
* 🔧 Create and merge AbortSignal instances with proper cleanup
|
|
@@ -801,7 +813,8 @@ var ActionRegister = class {
|
|
|
801
813
|
once: config.once ?? false,
|
|
802
814
|
debounce: config.debounce ?? void 0,
|
|
803
815
|
throttle: config.throttle ?? void 0,
|
|
804
|
-
replaceExisting: config.replaceExisting ?? false
|
|
816
|
+
replaceExisting: config.replaceExisting ?? false,
|
|
817
|
+
cleanup: config.cleanup
|
|
805
818
|
},
|
|
806
819
|
id: handlerId
|
|
807
820
|
};
|
|
@@ -812,65 +825,60 @@ var ActionRegister = class {
|
|
|
812
825
|
return () => {};
|
|
813
826
|
}
|
|
814
827
|
const existingIndex = pipeline.findIndex((reg) => reg.id === handlerId);
|
|
815
|
-
if (existingIndex !== -1)
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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 });
|
|
828
|
+
if (existingIndex !== -1) {
|
|
829
|
+
const existing = pipeline[existingIndex];
|
|
830
|
+
const existingUnregister = this.unregisterFunctions.get(handlerId);
|
|
831
|
+
if (config.replaceExisting) {
|
|
832
|
+
if (existingUnregister) {
|
|
833
|
+
existingUnregister();
|
|
834
|
+
this.unregisterFunctions.delete(handlerId);
|
|
837
835
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
836
|
+
if (existing && typeof existing.cleanup === "function") try {
|
|
837
|
+
existing.cleanup();
|
|
838
|
+
} catch (cleanupError) {
|
|
839
|
+
this.log(`Cleanup error for replaced handler: ${String(action)}`, cleanupError, "warn");
|
|
840
|
+
}
|
|
841
|
+
pipeline[existingIndex] = registration;
|
|
842
|
+
pipeline.sort((a, b) => b.config.priority - a.config.priority);
|
|
843
|
+
this.invalidateFilterCache();
|
|
844
|
+
const newUnregister = this.createUnregisterFunction(action, handlerId, registration);
|
|
845
|
+
this.unregisterFunctions.set(handlerId, newUnregister);
|
|
846
|
+
this.log(`Handler replaced: ${String(action)}`, {
|
|
847
|
+
handlerId,
|
|
848
|
+
priority: config.priority,
|
|
849
|
+
totalHandlers: pipeline.length,
|
|
850
|
+
hadExistingUnregister: Boolean(existingUnregister)
|
|
851
|
+
});
|
|
852
|
+
return newUnregister;
|
|
853
|
+
} else {
|
|
854
|
+
if (!existing) throw new Error("Internal error: existing handler should be defined in duplicate handler block");
|
|
855
|
+
this.log(`Handler duplicate ignored, returning existing unregister: ${String(action)}`, {
|
|
856
|
+
handlerId,
|
|
857
|
+
existingPriority: existing.config.priority,
|
|
858
|
+
newPriority: config.priority,
|
|
859
|
+
existingBlocking: existing.config.blocking,
|
|
860
|
+
newBlocking: config.blocking,
|
|
861
|
+
note: "Use replaceExisting:true to replace"
|
|
862
|
+
}, "warn");
|
|
863
|
+
if (existingUnregister) return existingUnregister;
|
|
864
|
+
else {
|
|
865
|
+
const newUnregister = this.createUnregisterFunction(action, handlerId, existing);
|
|
866
|
+
this.unregisterFunctions.set(handlerId, newUnregister);
|
|
867
|
+
return newUnregister;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
845
870
|
}
|
|
846
871
|
pipeline.push(registration);
|
|
847
872
|
pipeline.sort((a, b) => b.config.priority - a.config.priority);
|
|
848
873
|
this.invalidateFilterCache();
|
|
874
|
+
const unregister = this.createUnregisterFunction(action, handlerId, registration);
|
|
875
|
+
this.unregisterFunctions.set(handlerId, unregister);
|
|
849
876
|
this.log(`Handler registered: ${String(action)}`, {
|
|
850
877
|
handlerId,
|
|
851
878
|
priority: config.priority,
|
|
852
879
|
totalHandlers: pipeline.length
|
|
853
880
|
});
|
|
854
|
-
|
|
855
|
-
totalHandlers: pipeline.length,
|
|
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)
|
|
862
|
-
});
|
|
863
|
-
return () => {
|
|
864
|
-
const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
|
|
865
|
-
if (index !== -1) {
|
|
866
|
-
pipeline.splice(index, 1);
|
|
867
|
-
this.invalidateFilterCache();
|
|
868
|
-
this.log(`Handler unregistered: ${String(action)}`, {
|
|
869
|
-
handlerId,
|
|
870
|
-
remainingHandlers: pipeline.length
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
|
-
};
|
|
881
|
+
return unregister;
|
|
874
882
|
}
|
|
875
883
|
/**
|
|
876
884
|
* Dispatch an action with optional execution options
|
|
@@ -897,7 +905,7 @@ var ActionRegister = class {
|
|
|
897
905
|
* 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
|
|
898
906
|
*/
|
|
899
907
|
async _performDispatch(action, payload, options) {
|
|
900
|
-
|
|
908
|
+
this.log(`Starting dispatch for action '${String(action)}'`, {
|
|
901
909
|
hasPayload: payload !== void 0,
|
|
902
910
|
payloadType: payload?.constructor?.name || typeof payload,
|
|
903
911
|
options: options ? Object.keys(options) : "none",
|
|
@@ -907,18 +915,18 @@ var ActionRegister = class {
|
|
|
907
915
|
const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
|
|
908
916
|
if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
|
|
909
917
|
if (effectiveSignal?.aborted) {
|
|
910
|
-
|
|
918
|
+
this.log(`Dispatch aborted before execution for '${String(action)}'`);
|
|
911
919
|
return;
|
|
912
920
|
}
|
|
913
921
|
const pipeline = this.pipelines.get(action);
|
|
914
|
-
|
|
922
|
+
this.log(`Pipeline lookup for '${String(action)}'`, {
|
|
915
923
|
pipelineExists: Boolean(pipeline),
|
|
916
924
|
handlersCount: pipeline?.length || 0,
|
|
917
925
|
allRegisteredActions: Array.from(this.pipelines.keys()),
|
|
918
926
|
pipelineMap: Object.fromEntries(Array.from(this.pipelines.entries()).map(([k, v]) => [k, v.length]))
|
|
919
927
|
});
|
|
920
928
|
if (!pipeline || pipeline.length === 0) {
|
|
921
|
-
|
|
929
|
+
this.log(`No handlers found for action '${String(action)}', dispatch cancelled`, {}, "warn");
|
|
922
930
|
return;
|
|
923
931
|
}
|
|
924
932
|
const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
|
|
@@ -1017,6 +1025,7 @@ var ActionRegister = class {
|
|
|
1017
1025
|
if (!pipeline || pipeline.length === 0) return {
|
|
1018
1026
|
success: true,
|
|
1019
1027
|
aborted: false,
|
|
1028
|
+
abortReason: void 0,
|
|
1020
1029
|
terminated: false,
|
|
1021
1030
|
result: void 0,
|
|
1022
1031
|
successResults: [],
|
|
@@ -1035,68 +1044,8 @@ var ActionRegister = class {
|
|
|
1035
1044
|
};
|
|
1036
1045
|
const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
|
|
1037
1046
|
const actionKey = String(action);
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
if (options?.throttle !== void 0) throttleMs = options.throttle;
|
|
1041
|
-
else if (filteredHandlers.length > 0) {
|
|
1042
|
-
for (const handler of filteredHandlers) if (handler.config.throttle !== void 0) {
|
|
1043
|
-
throttleMs = handler.config.throttle;
|
|
1044
|
-
break;
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
if (options?.debounce !== void 0) debounceMs = options.debounce;
|
|
1048
|
-
else if (filteredHandlers.length > 0) {
|
|
1049
|
-
for (const handler of filteredHandlers) if (handler.config.debounce !== void 0) {
|
|
1050
|
-
debounceMs = handler.config.debounce;
|
|
1051
|
-
break;
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
if (debounceMs !== void 0) {
|
|
1055
|
-
const shouldProceed = await this.actionGuard.debounce(actionKey, debounceMs);
|
|
1056
|
-
if (!shouldProceed) return {
|
|
1057
|
-
success: false,
|
|
1058
|
-
aborted: true,
|
|
1059
|
-
abortReason: "Debounced execution",
|
|
1060
|
-
terminated: false,
|
|
1061
|
-
result: void 0,
|
|
1062
|
-
successResults: [],
|
|
1063
|
-
results: [],
|
|
1064
|
-
failedResults: [],
|
|
1065
|
-
execution: {
|
|
1066
|
-
duration: Date.now() - _startTime,
|
|
1067
|
-
handlersExecuted: 0,
|
|
1068
|
-
handlersSkipped: pipeline.length,
|
|
1069
|
-
handlersFailed: 0,
|
|
1070
|
-
startTime: _startTime,
|
|
1071
|
-
endTime: Date.now()
|
|
1072
|
-
},
|
|
1073
|
-
handlers: [],
|
|
1074
|
-
errors: []
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
if (throttleMs !== void 0) {
|
|
1078
|
-
const shouldProceed = this.actionGuard.throttle(actionKey, throttleMs);
|
|
1079
|
-
if (!shouldProceed) return {
|
|
1080
|
-
success: false,
|
|
1081
|
-
aborted: true,
|
|
1082
|
-
abortReason: "Throttled execution",
|
|
1083
|
-
terminated: false,
|
|
1084
|
-
result: void 0,
|
|
1085
|
-
successResults: [],
|
|
1086
|
-
results: [],
|
|
1087
|
-
failedResults: [],
|
|
1088
|
-
execution: {
|
|
1089
|
-
duration: Date.now() - _startTime,
|
|
1090
|
-
handlersExecuted: 0,
|
|
1091
|
-
handlersSkipped: pipeline.length,
|
|
1092
|
-
handlersFailed: 0,
|
|
1093
|
-
startTime: _startTime,
|
|
1094
|
-
endTime: Date.now()
|
|
1095
|
-
},
|
|
1096
|
-
handlers: [],
|
|
1097
|
-
errors: []
|
|
1098
|
-
};
|
|
1099
|
-
}
|
|
1047
|
+
const guardResult = await this.applyActionGuardControlsWithResult(actionKey, filteredHandlers, options, _startTime, pipeline.length);
|
|
1048
|
+
if (guardResult) return guardResult;
|
|
1100
1049
|
const currentExecutionMode = options?.executionMode || this.actionExecutionModes.get(action) || this.executionMode;
|
|
1101
1050
|
const context = {
|
|
1102
1051
|
action: String(action),
|
|
@@ -1113,11 +1062,14 @@ var ActionRegister = class {
|
|
|
1113
1062
|
};
|
|
1114
1063
|
let executionError;
|
|
1115
1064
|
const handlerResults = [];
|
|
1116
|
-
const errors = [];
|
|
1117
1065
|
filteredHandlers.forEach((handler) => {
|
|
1118
1066
|
handlerResults.push({
|
|
1119
1067
|
id: handler.config.id,
|
|
1120
|
-
executed: false
|
|
1068
|
+
executed: false,
|
|
1069
|
+
duration: void 0,
|
|
1070
|
+
result: void 0,
|
|
1071
|
+
error: void 0,
|
|
1072
|
+
metadata: void 0
|
|
1121
1073
|
});
|
|
1122
1074
|
});
|
|
1123
1075
|
const abortHandler = effectiveSignal ? () => {
|
|
@@ -1125,23 +1077,33 @@ var ActionRegister = class {
|
|
|
1125
1077
|
context.abortReason = "Action dispatch aborted by signal";
|
|
1126
1078
|
} : void 0;
|
|
1127
1079
|
if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
|
|
1080
|
+
let errors = [];
|
|
1128
1081
|
try {
|
|
1129
1082
|
await this.executePipeline(context, autoAbortController, options?.autoAbort);
|
|
1083
|
+
const contextWithErrors = context;
|
|
1084
|
+
errors = contextWithErrors.collectedErrors || [];
|
|
1130
1085
|
const executedCount = Math.min(context.currentIndex + (context.aborted ? 0 : 1), filteredHandlers.length);
|
|
1131
1086
|
for (let i = 0; i < executedCount; i++) {
|
|
1132
|
-
const
|
|
1087
|
+
const handler = filteredHandlers[i];
|
|
1088
|
+
if (!handler) continue;
|
|
1089
|
+
const handlerResult = handlerResults.find((hr) => hr.id === handler.config.id);
|
|
1133
1090
|
if (handlerResult) handlerResult.executed = true;
|
|
1134
1091
|
}
|
|
1135
1092
|
} catch (error) {
|
|
1093
|
+
const contextWithErrors = context;
|
|
1094
|
+
errors = contextWithErrors.collectedErrors || [];
|
|
1136
1095
|
executionError = error instanceof Error ? error : new Error(String(error));
|
|
1137
1096
|
errors.push({
|
|
1138
1097
|
handlerId: "pipeline",
|
|
1139
1098
|
error: executionError,
|
|
1140
|
-
timestamp: Date.now()
|
|
1099
|
+
timestamp: Date.now(),
|
|
1100
|
+
severity: "blocking"
|
|
1141
1101
|
});
|
|
1142
1102
|
const executedCount = Math.min(context.currentIndex + 1, filteredHandlers.length);
|
|
1143
1103
|
for (let i = 0; i < executedCount; i++) {
|
|
1144
|
-
const
|
|
1104
|
+
const handler = filteredHandlers[i];
|
|
1105
|
+
if (!handler) continue;
|
|
1106
|
+
const handlerResult = handlerResults.find((hr) => hr.id === handler.config.id);
|
|
1145
1107
|
if (handlerResult) handlerResult.executed = true;
|
|
1146
1108
|
}
|
|
1147
1109
|
} finally {
|
|
@@ -1166,7 +1128,7 @@ var ActionRegister = class {
|
|
|
1166
1128
|
failedResults,
|
|
1167
1129
|
execution: {
|
|
1168
1130
|
duration: endTime - _startTime,
|
|
1169
|
-
handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
|
|
1131
|
+
handlersExecuted: filteredHandlers.length === 0 ? 0 : context.currentIndex + (context.aborted ? 0 : 1),
|
|
1170
1132
|
handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
|
|
1171
1133
|
handlersFailed: errors.length,
|
|
1172
1134
|
startTime: _startTime,
|
|
@@ -1185,6 +1147,82 @@ var ActionRegister = class {
|
|
|
1185
1147
|
return executionResult;
|
|
1186
1148
|
}
|
|
1187
1149
|
/**
|
|
1150
|
+
* 🔧 Unified method for dispatchWithResult that returns ExecutionResult on guard rejection
|
|
1151
|
+
*/
|
|
1152
|
+
async applyActionGuardControlsWithResult(actionKey, filteredHandlers, options, startTime, pipelineLength) {
|
|
1153
|
+
let throttleMs;
|
|
1154
|
+
let debounceMs;
|
|
1155
|
+
if (options?.throttle !== void 0) throttleMs = options.throttle;
|
|
1156
|
+
else if (filteredHandlers.length > 0) {
|
|
1157
|
+
for (const handler of filteredHandlers) if (handler.config.throttle !== void 0) {
|
|
1158
|
+
throttleMs = handler.config.throttle;
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (options?.debounce !== void 0) debounceMs = options.debounce;
|
|
1163
|
+
else if (filteredHandlers.length > 0) {
|
|
1164
|
+
for (const handler of filteredHandlers) if (handler.config.debounce !== void 0) {
|
|
1165
|
+
debounceMs = handler.config.debounce;
|
|
1166
|
+
break;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (debounceMs !== void 0) {
|
|
1170
|
+
const shouldProceed = await this.actionGuard.debounce(actionKey, debounceMs);
|
|
1171
|
+
if (!shouldProceed) return {
|
|
1172
|
+
success: false,
|
|
1173
|
+
aborted: true,
|
|
1174
|
+
abortReason: "Debounced execution",
|
|
1175
|
+
terminated: false,
|
|
1176
|
+
result: void 0,
|
|
1177
|
+
successResults: [],
|
|
1178
|
+
results: [],
|
|
1179
|
+
failedResults: [],
|
|
1180
|
+
execution: {
|
|
1181
|
+
duration: Date.now() - startTime,
|
|
1182
|
+
handlersExecuted: 0,
|
|
1183
|
+
handlersSkipped: pipelineLength,
|
|
1184
|
+
handlersFailed: 0,
|
|
1185
|
+
startTime,
|
|
1186
|
+
endTime: Date.now()
|
|
1187
|
+
},
|
|
1188
|
+
handlers: [],
|
|
1189
|
+
errors: []
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
if (throttleMs !== void 0) {
|
|
1193
|
+
const shouldProceed = this.actionGuard.throttle(actionKey, throttleMs);
|
|
1194
|
+
if (!shouldProceed) return {
|
|
1195
|
+
success: false,
|
|
1196
|
+
aborted: true,
|
|
1197
|
+
abortReason: "Throttled execution",
|
|
1198
|
+
terminated: false,
|
|
1199
|
+
result: void 0,
|
|
1200
|
+
successResults: [],
|
|
1201
|
+
results: [],
|
|
1202
|
+
failedResults: [],
|
|
1203
|
+
execution: {
|
|
1204
|
+
duration: Date.now() - startTime,
|
|
1205
|
+
handlersExecuted: 0,
|
|
1206
|
+
handlersSkipped: pipelineLength,
|
|
1207
|
+
handlersFailed: 0,
|
|
1208
|
+
startTime,
|
|
1209
|
+
endTime: Date.now()
|
|
1210
|
+
},
|
|
1211
|
+
handlers: [],
|
|
1212
|
+
errors: []
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* 🔧 Calculate optimal cache size based on current handler count
|
|
1219
|
+
* Note: This caches handler SELECTION metadata only, never execution results
|
|
1220
|
+
*/
|
|
1221
|
+
get filterCacheMaxSize() {
|
|
1222
|
+
const totalHandlers = Array.from(this.pipelines.values()).reduce((sum, pipeline) => sum + pipeline.length, 0);
|
|
1223
|
+
return totalHandlers * 10 || 100;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1188
1226
|
* 🔧 Generate cache key for filter options
|
|
1189
1227
|
*/
|
|
1190
1228
|
generateFilterCacheKey(filterOptions) {
|
|
@@ -1204,6 +1242,48 @@ var ActionRegister = class {
|
|
|
1204
1242
|
invalidateFilterCache() {
|
|
1205
1243
|
this.filterCache.clear();
|
|
1206
1244
|
}
|
|
1245
|
+
/**
|
|
1246
|
+
* 🔧 Create or reuse PipelineController from pool for better performance
|
|
1247
|
+
*/
|
|
1248
|
+
getControllerFromPool(context, autoAbortController, autoAbortOptions) {
|
|
1249
|
+
let controller = this.controllerPool.pop();
|
|
1250
|
+
if (!controller) controller = {};
|
|
1251
|
+
controller.abort = (reason) => {
|
|
1252
|
+
context.aborted = true;
|
|
1253
|
+
context.abortReason = reason;
|
|
1254
|
+
if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
|
|
1255
|
+
};
|
|
1256
|
+
controller.modifyPayload = (modifier) => {
|
|
1257
|
+
context.payload = modifier(context.payload);
|
|
1258
|
+
};
|
|
1259
|
+
controller.getPayload = () => context.payload;
|
|
1260
|
+
controller.jumpToPriority = (priority) => {
|
|
1261
|
+
context.jumpToPriority = priority;
|
|
1262
|
+
};
|
|
1263
|
+
controller.return = (result) => {
|
|
1264
|
+
context.terminated = true;
|
|
1265
|
+
context.terminationResult = result;
|
|
1266
|
+
};
|
|
1267
|
+
controller.setResult = (result) => {
|
|
1268
|
+
context.results.push(result);
|
|
1269
|
+
};
|
|
1270
|
+
controller.getResults = () => {
|
|
1271
|
+
return [...context.results];
|
|
1272
|
+
};
|
|
1273
|
+
controller.mergeResult = (merger) => {
|
|
1274
|
+
const currentResult = context.results[context.results.length - 1];
|
|
1275
|
+
const previousResults = context.results.slice(0, -1);
|
|
1276
|
+
const mergedResult = merger(previousResults, currentResult);
|
|
1277
|
+
context.results[context.results.length - 1] = mergedResult;
|
|
1278
|
+
};
|
|
1279
|
+
return controller;
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* 🔧 Return controller to pool for reuse
|
|
1283
|
+
*/
|
|
1284
|
+
returnControllerToPool(controller) {
|
|
1285
|
+
if (this.controllerPool.length < this.maxControllerPoolSize) this.controllerPool.push(controller);
|
|
1286
|
+
}
|
|
1207
1287
|
filterHandlers(handlers, filterOptions) {
|
|
1208
1288
|
if (!filterOptions) return handlers;
|
|
1209
1289
|
const cacheKey = this.generateFilterCacheKey(filterOptions);
|
|
@@ -1223,9 +1303,10 @@ var ActionRegister = class {
|
|
|
1223
1303
|
return true;
|
|
1224
1304
|
});
|
|
1225
1305
|
if (!filterOptions.custom) {
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1306
|
+
const currentMaxSize = this.filterCacheMaxSize;
|
|
1307
|
+
if (this.filterCache.size >= currentMaxSize) {
|
|
1308
|
+
const oldestKey = this.filterCache.keys().next().value;
|
|
1309
|
+
if (oldestKey !== void 0) this.filterCache.delete(oldestKey);
|
|
1229
1310
|
}
|
|
1230
1311
|
this.filterCache.set(cacheKey, filtered);
|
|
1231
1312
|
}
|
|
@@ -1252,36 +1333,7 @@ var ActionRegister = class {
|
|
|
1252
1333
|
}
|
|
1253
1334
|
async executePipeline(context, autoAbortController, autoAbortOptions) {
|
|
1254
1335
|
const createController = (_registration, _index) => {
|
|
1255
|
-
return
|
|
1256
|
-
abort: (reason) => {
|
|
1257
|
-
context.aborted = true;
|
|
1258
|
-
context.abortReason = reason;
|
|
1259
|
-
if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
|
|
1260
|
-
},
|
|
1261
|
-
modifyPayload: (modifier) => {
|
|
1262
|
-
context.payload = modifier(context.payload);
|
|
1263
|
-
},
|
|
1264
|
-
getPayload: () => context.payload,
|
|
1265
|
-
jumpToPriority: (priority) => {
|
|
1266
|
-
context.jumpToPriority = priority;
|
|
1267
|
-
},
|
|
1268
|
-
return: (result) => {
|
|
1269
|
-
context.terminated = true;
|
|
1270
|
-
context.terminationResult = result;
|
|
1271
|
-
},
|
|
1272
|
-
setResult: (result) => {
|
|
1273
|
-
context.results.push(result);
|
|
1274
|
-
},
|
|
1275
|
-
getResults: () => {
|
|
1276
|
-
return [...context.results];
|
|
1277
|
-
},
|
|
1278
|
-
mergeResult: (merger) => {
|
|
1279
|
-
const currentResult = context.results[context.results.length - 1];
|
|
1280
|
-
const previousResults = context.results.slice(0, -1);
|
|
1281
|
-
const mergedResult = merger(previousResults, currentResult);
|
|
1282
|
-
context.results[context.results.length - 1] = mergedResult;
|
|
1283
|
-
}
|
|
1284
|
-
};
|
|
1336
|
+
return this.getControllerFromPool(context, autoAbortController, autoAbortOptions);
|
|
1285
1337
|
};
|
|
1286
1338
|
switch (context.executionMode) {
|
|
1287
1339
|
case "sequential":
|
|
@@ -1443,6 +1495,15 @@ var ActionRegister = class {
|
|
|
1443
1495
|
return Array.from(this.pipelines.keys()).map((action) => this.getActionStats(action)).filter((stats) => stats !== null);
|
|
1444
1496
|
}
|
|
1445
1497
|
/**
|
|
1498
|
+
* Set global execution mode for all actions
|
|
1499
|
+
*
|
|
1500
|
+
* @param mode Execution mode to set
|
|
1501
|
+
*/
|
|
1502
|
+
setExecutionMode(mode) {
|
|
1503
|
+
this.executionMode = mode;
|
|
1504
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Global execution mode set to: ${mode}`);
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1446
1507
|
* Set execution mode for a specific action
|
|
1447
1508
|
*
|
|
1448
1509
|
* @param action Action name
|
|
@@ -1487,6 +1548,55 @@ var ActionRegister = class {
|
|
|
1487
1548
|
return this.isDebugMode;
|
|
1488
1549
|
}
|
|
1489
1550
|
/**
|
|
1551
|
+
* Creates a consistent unregister function for a handler
|
|
1552
|
+
*
|
|
1553
|
+
* @param action - Action key
|
|
1554
|
+
* @param handlerId - Handler identifier
|
|
1555
|
+
* @param registration - Handler registration object
|
|
1556
|
+
* @returns Unregister function
|
|
1557
|
+
* @private
|
|
1558
|
+
*/
|
|
1559
|
+
createUnregisterFunction(action, handlerId, registration) {
|
|
1560
|
+
return () => {
|
|
1561
|
+
const pipeline = this.pipelines.get(action);
|
|
1562
|
+
if (!pipeline) return;
|
|
1563
|
+
const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
|
|
1564
|
+
if (index !== -1) {
|
|
1565
|
+
pipeline.splice(index, 1);
|
|
1566
|
+
this.invalidateFilterCache();
|
|
1567
|
+
this.unregisterFunctions.delete(handlerId);
|
|
1568
|
+
if (registration.config.cleanup && typeof registration.config.cleanup === "function") try {
|
|
1569
|
+
registration.config.cleanup();
|
|
1570
|
+
} catch (cleanupError) {
|
|
1571
|
+
this.log(`Cleanup error during unregister: ${String(action)}`, cleanupError, "warn");
|
|
1572
|
+
}
|
|
1573
|
+
this.log(`Handler unregistered: ${String(action)}`, {
|
|
1574
|
+
handlerId,
|
|
1575
|
+
remainingHandlers: pipeline.length
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Gets the total count of registered unregister functions
|
|
1582
|
+
*
|
|
1583
|
+
* @returns Number of unregister functions
|
|
1584
|
+
* @public
|
|
1585
|
+
*/
|
|
1586
|
+
getUnregisterFunctionCount() {
|
|
1587
|
+
return this.unregisterFunctions.size;
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Checks if an unregister function exists for the given handler ID
|
|
1591
|
+
*
|
|
1592
|
+
* @param handlerId - Handler identifier to check
|
|
1593
|
+
* @returns True if unregister function exists
|
|
1594
|
+
* @public
|
|
1595
|
+
*/
|
|
1596
|
+
hasUnregisterFunction(handlerId) {
|
|
1597
|
+
return this.unregisterFunctions.has(handlerId);
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1490
1600
|
* 🆕 Destroy method for comprehensive cleanup
|
|
1491
1601
|
*
|
|
1492
1602
|
* Cleans up all internal resources including pipelines, guards, queues, and statistics.
|
|
@@ -1495,11 +1605,20 @@ var ActionRegister = class {
|
|
|
1495
1605
|
* @public
|
|
1496
1606
|
*/
|
|
1497
1607
|
destroy() {
|
|
1608
|
+
this.unregisterFunctions.forEach((unregister) => {
|
|
1609
|
+
try {
|
|
1610
|
+
unregister();
|
|
1611
|
+
} catch (error) {
|
|
1612
|
+
this.log("Error during unregister in destroy", error, "error");
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
this.unregisterFunctions.clear();
|
|
1498
1616
|
this.pipelines.clear();
|
|
1499
1617
|
this.actionGuard.destroy();
|
|
1500
1618
|
this.dispatchQueue?.clear?.();
|
|
1501
1619
|
this.actionExecutionModes.clear();
|
|
1502
1620
|
this.filterCache.clear();
|
|
1621
|
+
this.controllerPool.length = 0;
|
|
1503
1622
|
this.log("ActionRegister destroyed");
|
|
1504
1623
|
}
|
|
1505
1624
|
};
|
|
@@ -1576,7 +1695,17 @@ var ActionRegister = class {
|
|
|
1576
1695
|
* @public
|
|
1577
1696
|
*/
|
|
1578
1697
|
function createActionHandler(registry, action, handler, config) {
|
|
1579
|
-
const
|
|
1698
|
+
const timestamp = Date.now();
|
|
1699
|
+
const random = Math.random().toString(36).substr(2, 5);
|
|
1700
|
+
const finalConfig = {
|
|
1701
|
+
priority: config?.priority ?? 0,
|
|
1702
|
+
id: config?.id || `react_${String(action)}_${timestamp}_${random}`,
|
|
1703
|
+
blocking: config?.blocking ?? false,
|
|
1704
|
+
once: config?.once ?? false,
|
|
1705
|
+
debounce: config?.debounce ?? void 0,
|
|
1706
|
+
throttle: config?.throttle ?? void 0,
|
|
1707
|
+
replaceExisting: true
|
|
1708
|
+
};
|
|
1580
1709
|
let currentUnregister;
|
|
1581
1710
|
let isRegistered = false;
|
|
1582
1711
|
return {
|
|
@@ -1604,96 +1733,6 @@ function createActionHandler(registry, action, handler, config) {
|
|
|
1604
1733
|
};
|
|
1605
1734
|
}
|
|
1606
1735
|
/**
|
|
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
1736
|
* 🆕 React development utilities
|
|
1698
1737
|
*
|
|
1699
1738
|
* Provides debugging and development helpers specifically for React environments.
|
|
@@ -1735,7 +1774,7 @@ const ReactDevUtils = {
|
|
|
1735
1774
|
* Utilities for integrating ActionRegister errors with React Error Boundaries.
|
|
1736
1775
|
*/
|
|
1737
1776
|
var ReactActionError = class ReactActionError extends Error {
|
|
1738
|
-
constructor(message, action, payload, handlerId, originalError) {
|
|
1777
|
+
constructor(message, action, payload, handlerId = void 0, originalError) {
|
|
1739
1778
|
super(message);
|
|
1740
1779
|
this.name = "ReactActionError";
|
|
1741
1780
|
this.action = action;
|
|
@@ -1767,8 +1806,6 @@ exports.ActionRegister = ActionRegister;
|
|
|
1767
1806
|
exports.ReactActionError = ReactActionError;
|
|
1768
1807
|
exports.ReactDevUtils = ReactDevUtils;
|
|
1769
1808
|
exports.createActionHandler = createActionHandler;
|
|
1770
|
-
exports.createReactDispatcher = createReactDispatcher;
|
|
1771
|
-
exports.createReactHandlerConfig = createReactHandlerConfig;
|
|
1772
1809
|
exports.executeParallel = executeParallel;
|
|
1773
1810
|
exports.executeRace = executeRace;
|
|
1774
1811
|
exports.executeSequential = executeSequential;
|