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