@context-action/core 0.5.0 → 0.7.1
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 +331 -250
- 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 +332 -249
- 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,56 +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
|
-
return
|
|
855
|
-
const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
|
|
856
|
-
if (index !== -1) {
|
|
857
|
-
pipeline.splice(index, 1);
|
|
858
|
-
this.invalidateFilterCache();
|
|
859
|
-
this.log(`Handler unregistered: ${String(action)}`, {
|
|
860
|
-
handlerId,
|
|
861
|
-
remainingHandlers: pipeline.length
|
|
862
|
-
});
|
|
863
|
-
}
|
|
864
|
-
};
|
|
881
|
+
return unregister;
|
|
865
882
|
}
|
|
866
883
|
/**
|
|
867
884
|
* Dispatch an action with optional execution options
|
|
@@ -888,12 +905,30 @@ var ActionRegister = class {
|
|
|
888
905
|
* 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
|
|
889
906
|
*/
|
|
890
907
|
async _performDispatch(action, payload, options) {
|
|
908
|
+
this.log(`Starting dispatch for action '${String(action)}'`, {
|
|
909
|
+
hasPayload: payload !== void 0,
|
|
910
|
+
payloadType: payload?.constructor?.name || typeof payload,
|
|
911
|
+
options: options ? Object.keys(options) : "none",
|
|
912
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
913
|
+
});
|
|
891
914
|
if (payload instanceof Event && process.env.NODE_ENV === "development") console.warn(`Event object passed to action "${String(action)}"`, payload.type);
|
|
892
915
|
const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
|
|
893
916
|
if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
|
|
894
|
-
if (effectiveSignal?.aborted)
|
|
917
|
+
if (effectiveSignal?.aborted) {
|
|
918
|
+
this.log(`Dispatch aborted before execution for '${String(action)}'`);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
895
921
|
const pipeline = this.pipelines.get(action);
|
|
896
|
-
|
|
922
|
+
this.log(`Pipeline lookup for '${String(action)}'`, {
|
|
923
|
+
pipelineExists: Boolean(pipeline),
|
|
924
|
+
handlersCount: pipeline?.length || 0,
|
|
925
|
+
allRegisteredActions: Array.from(this.pipelines.keys()),
|
|
926
|
+
pipelineMap: Object.fromEntries(Array.from(this.pipelines.entries()).map(([k, v]) => [k, v.length]))
|
|
927
|
+
});
|
|
928
|
+
if (!pipeline || pipeline.length === 0) {
|
|
929
|
+
this.log(`No handlers found for action '${String(action)}', dispatch cancelled`, {}, "warn");
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
897
932
|
const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
|
|
898
933
|
const actionKey = String(action);
|
|
899
934
|
let throttleMs;
|
|
@@ -939,6 +974,8 @@ var ActionRegister = class {
|
|
|
939
974
|
context.abortReason = "Action dispatch aborted by signal";
|
|
940
975
|
} : void 0;
|
|
941
976
|
if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
|
|
977
|
+
const contextWithErrors = context;
|
|
978
|
+
contextWithErrors.collectedErrors;
|
|
942
979
|
try {
|
|
943
980
|
await this.executePipeline(context, autoAbortController, options?.autoAbort);
|
|
944
981
|
this.log(`Pipeline execution succeeded for ${String(action)}`);
|
|
@@ -990,6 +1027,7 @@ var ActionRegister = class {
|
|
|
990
1027
|
if (!pipeline || pipeline.length === 0) return {
|
|
991
1028
|
success: true,
|
|
992
1029
|
aborted: false,
|
|
1030
|
+
abortReason: void 0,
|
|
993
1031
|
terminated: false,
|
|
994
1032
|
result: void 0,
|
|
995
1033
|
successResults: [],
|
|
@@ -1008,68 +1046,8 @@ var ActionRegister = class {
|
|
|
1008
1046
|
};
|
|
1009
1047
|
const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
|
|
1010
1048
|
const actionKey = String(action);
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
if (options?.throttle !== void 0) throttleMs = options.throttle;
|
|
1014
|
-
else if (filteredHandlers.length > 0) {
|
|
1015
|
-
for (const handler of filteredHandlers) if (handler.config.throttle !== void 0) {
|
|
1016
|
-
throttleMs = handler.config.throttle;
|
|
1017
|
-
break;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
if (options?.debounce !== void 0) debounceMs = options.debounce;
|
|
1021
|
-
else if (filteredHandlers.length > 0) {
|
|
1022
|
-
for (const handler of filteredHandlers) if (handler.config.debounce !== void 0) {
|
|
1023
|
-
debounceMs = handler.config.debounce;
|
|
1024
|
-
break;
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
if (debounceMs !== void 0) {
|
|
1028
|
-
const shouldProceed = await this.actionGuard.debounce(actionKey, debounceMs);
|
|
1029
|
-
if (!shouldProceed) return {
|
|
1030
|
-
success: false,
|
|
1031
|
-
aborted: true,
|
|
1032
|
-
abortReason: "Debounced execution",
|
|
1033
|
-
terminated: false,
|
|
1034
|
-
result: void 0,
|
|
1035
|
-
successResults: [],
|
|
1036
|
-
results: [],
|
|
1037
|
-
failedResults: [],
|
|
1038
|
-
execution: {
|
|
1039
|
-
duration: Date.now() - _startTime,
|
|
1040
|
-
handlersExecuted: 0,
|
|
1041
|
-
handlersSkipped: pipeline.length,
|
|
1042
|
-
handlersFailed: 0,
|
|
1043
|
-
startTime: _startTime,
|
|
1044
|
-
endTime: Date.now()
|
|
1045
|
-
},
|
|
1046
|
-
handlers: [],
|
|
1047
|
-
errors: []
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
|
-
if (throttleMs !== void 0) {
|
|
1051
|
-
const shouldProceed = this.actionGuard.throttle(actionKey, throttleMs);
|
|
1052
|
-
if (!shouldProceed) return {
|
|
1053
|
-
success: false,
|
|
1054
|
-
aborted: true,
|
|
1055
|
-
abortReason: "Throttled execution",
|
|
1056
|
-
terminated: false,
|
|
1057
|
-
result: void 0,
|
|
1058
|
-
successResults: [],
|
|
1059
|
-
results: [],
|
|
1060
|
-
failedResults: [],
|
|
1061
|
-
execution: {
|
|
1062
|
-
duration: Date.now() - _startTime,
|
|
1063
|
-
handlersExecuted: 0,
|
|
1064
|
-
handlersSkipped: pipeline.length,
|
|
1065
|
-
handlersFailed: 0,
|
|
1066
|
-
startTime: _startTime,
|
|
1067
|
-
endTime: Date.now()
|
|
1068
|
-
},
|
|
1069
|
-
handlers: [],
|
|
1070
|
-
errors: []
|
|
1071
|
-
};
|
|
1072
|
-
}
|
|
1049
|
+
const guardResult = await this.applyActionGuardControlsWithResult(actionKey, filteredHandlers, options, _startTime, pipeline.length);
|
|
1050
|
+
if (guardResult) return guardResult;
|
|
1073
1051
|
const currentExecutionMode = options?.executionMode || this.actionExecutionModes.get(action) || this.executionMode;
|
|
1074
1052
|
const context = {
|
|
1075
1053
|
action: String(action),
|
|
@@ -1086,26 +1064,54 @@ var ActionRegister = class {
|
|
|
1086
1064
|
};
|
|
1087
1065
|
let executionError;
|
|
1088
1066
|
const handlerResults = [];
|
|
1089
|
-
|
|
1067
|
+
filteredHandlers.forEach((handler) => {
|
|
1068
|
+
handlerResults.push({
|
|
1069
|
+
id: handler.config.id,
|
|
1070
|
+
executed: false,
|
|
1071
|
+
duration: void 0,
|
|
1072
|
+
result: void 0,
|
|
1073
|
+
error: void 0,
|
|
1074
|
+
metadata: void 0
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
1090
1077
|
const abortHandler = effectiveSignal ? () => {
|
|
1091
1078
|
context.aborted = true;
|
|
1092
1079
|
context.abortReason = "Action dispatch aborted by signal";
|
|
1093
1080
|
} : void 0;
|
|
1094
1081
|
if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
|
|
1082
|
+
let errors = [];
|
|
1095
1083
|
try {
|
|
1096
1084
|
await this.executePipeline(context, autoAbortController, options?.autoAbort);
|
|
1085
|
+
const contextWithErrors = context;
|
|
1086
|
+
errors = contextWithErrors.collectedErrors || [];
|
|
1087
|
+
const executedCount = Math.min(context.currentIndex + (context.aborted ? 0 : 1), filteredHandlers.length);
|
|
1088
|
+
for (let i = 0; i < executedCount; i++) {
|
|
1089
|
+
const handler = filteredHandlers[i];
|
|
1090
|
+
if (!handler) continue;
|
|
1091
|
+
const handlerResult = handlerResults.find((hr) => hr.id === handler.config.id);
|
|
1092
|
+
if (handlerResult) handlerResult.executed = true;
|
|
1093
|
+
}
|
|
1097
1094
|
} catch (error) {
|
|
1095
|
+
const contextWithErrors = context;
|
|
1096
|
+
errors = contextWithErrors.collectedErrors || [];
|
|
1098
1097
|
executionError = error instanceof Error ? error : new Error(String(error));
|
|
1099
1098
|
errors.push({
|
|
1100
1099
|
handlerId: "pipeline",
|
|
1101
1100
|
error: executionError,
|
|
1102
|
-
timestamp: Date.now()
|
|
1101
|
+
timestamp: Date.now(),
|
|
1102
|
+
severity: "blocking"
|
|
1103
1103
|
});
|
|
1104
|
+
const executedCount = Math.min(context.currentIndex + 1, filteredHandlers.length);
|
|
1105
|
+
for (let i = 0; i < executedCount; i++) {
|
|
1106
|
+
const handler = filteredHandlers[i];
|
|
1107
|
+
if (!handler) continue;
|
|
1108
|
+
const handlerResult = handlerResults.find((hr) => hr.id === handler.config.id);
|
|
1109
|
+
if (handlerResult) handlerResult.executed = true;
|
|
1110
|
+
}
|
|
1104
1111
|
} finally {
|
|
1105
1112
|
cleanup();
|
|
1106
1113
|
}
|
|
1107
1114
|
const endTime = Date.now();
|
|
1108
|
-
!executionError && context.aborted;
|
|
1109
1115
|
const processedResult = this.processResults(context, options?.result);
|
|
1110
1116
|
const successResults = context.results.filter((result) => result !== void 0);
|
|
1111
1117
|
const failedResults = errors.map((err) => ({
|
|
@@ -1124,7 +1130,7 @@ var ActionRegister = class {
|
|
|
1124
1130
|
failedResults,
|
|
1125
1131
|
execution: {
|
|
1126
1132
|
duration: endTime - _startTime,
|
|
1127
|
-
handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
|
|
1133
|
+
handlersExecuted: filteredHandlers.length === 0 ? 0 : context.currentIndex + (context.aborted ? 0 : 1),
|
|
1128
1134
|
handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
|
|
1129
1135
|
handlersFailed: errors.length,
|
|
1130
1136
|
startTime: _startTime,
|
|
@@ -1143,6 +1149,82 @@ var ActionRegister = class {
|
|
|
1143
1149
|
return executionResult;
|
|
1144
1150
|
}
|
|
1145
1151
|
/**
|
|
1152
|
+
* 🔧 Unified method for dispatchWithResult that returns ExecutionResult on guard rejection
|
|
1153
|
+
*/
|
|
1154
|
+
async applyActionGuardControlsWithResult(actionKey, filteredHandlers, options, startTime, pipelineLength) {
|
|
1155
|
+
let throttleMs;
|
|
1156
|
+
let debounceMs;
|
|
1157
|
+
if (options?.throttle !== void 0) throttleMs = options.throttle;
|
|
1158
|
+
else if (filteredHandlers.length > 0) {
|
|
1159
|
+
for (const handler of filteredHandlers) if (handler.config.throttle !== void 0) {
|
|
1160
|
+
throttleMs = handler.config.throttle;
|
|
1161
|
+
break;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
if (options?.debounce !== void 0) debounceMs = options.debounce;
|
|
1165
|
+
else if (filteredHandlers.length > 0) {
|
|
1166
|
+
for (const handler of filteredHandlers) if (handler.config.debounce !== void 0) {
|
|
1167
|
+
debounceMs = handler.config.debounce;
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
if (debounceMs !== void 0) {
|
|
1172
|
+
const shouldProceed = await this.actionGuard.debounce(actionKey, debounceMs);
|
|
1173
|
+
if (!shouldProceed) return {
|
|
1174
|
+
success: false,
|
|
1175
|
+
aborted: true,
|
|
1176
|
+
abortReason: "Debounced execution",
|
|
1177
|
+
terminated: false,
|
|
1178
|
+
result: void 0,
|
|
1179
|
+
successResults: [],
|
|
1180
|
+
results: [],
|
|
1181
|
+
failedResults: [],
|
|
1182
|
+
execution: {
|
|
1183
|
+
duration: Date.now() - startTime,
|
|
1184
|
+
handlersExecuted: 0,
|
|
1185
|
+
handlersSkipped: pipelineLength,
|
|
1186
|
+
handlersFailed: 0,
|
|
1187
|
+
startTime,
|
|
1188
|
+
endTime: Date.now()
|
|
1189
|
+
},
|
|
1190
|
+
handlers: [],
|
|
1191
|
+
errors: []
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
if (throttleMs !== void 0) {
|
|
1195
|
+
const shouldProceed = this.actionGuard.throttle(actionKey, throttleMs);
|
|
1196
|
+
if (!shouldProceed) return {
|
|
1197
|
+
success: false,
|
|
1198
|
+
aborted: true,
|
|
1199
|
+
abortReason: "Throttled execution",
|
|
1200
|
+
terminated: false,
|
|
1201
|
+
result: void 0,
|
|
1202
|
+
successResults: [],
|
|
1203
|
+
results: [],
|
|
1204
|
+
failedResults: [],
|
|
1205
|
+
execution: {
|
|
1206
|
+
duration: Date.now() - startTime,
|
|
1207
|
+
handlersExecuted: 0,
|
|
1208
|
+
handlersSkipped: pipelineLength,
|
|
1209
|
+
handlersFailed: 0,
|
|
1210
|
+
startTime,
|
|
1211
|
+
endTime: Date.now()
|
|
1212
|
+
},
|
|
1213
|
+
handlers: [],
|
|
1214
|
+
errors: []
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* 🔧 Calculate optimal cache size based on current handler count
|
|
1221
|
+
* Note: This caches handler SELECTION metadata only, never execution results
|
|
1222
|
+
*/
|
|
1223
|
+
get filterCacheMaxSize() {
|
|
1224
|
+
const totalHandlers = Array.from(this.pipelines.values()).reduce((sum, pipeline) => sum + pipeline.length, 0);
|
|
1225
|
+
return totalHandlers * 10 || 100;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1146
1228
|
* 🔧 Generate cache key for filter options
|
|
1147
1229
|
*/
|
|
1148
1230
|
generateFilterCacheKey(filterOptions) {
|
|
@@ -1162,6 +1244,48 @@ var ActionRegister = class {
|
|
|
1162
1244
|
invalidateFilterCache() {
|
|
1163
1245
|
this.filterCache.clear();
|
|
1164
1246
|
}
|
|
1247
|
+
/**
|
|
1248
|
+
* 🔧 Create or reuse PipelineController from pool for better performance
|
|
1249
|
+
*/
|
|
1250
|
+
getControllerFromPool(context, autoAbortController, autoAbortOptions) {
|
|
1251
|
+
let controller = this.controllerPool.pop();
|
|
1252
|
+
if (!controller) controller = {};
|
|
1253
|
+
controller.abort = (reason) => {
|
|
1254
|
+
context.aborted = true;
|
|
1255
|
+
context.abortReason = reason;
|
|
1256
|
+
if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
|
|
1257
|
+
};
|
|
1258
|
+
controller.modifyPayload = (modifier) => {
|
|
1259
|
+
context.payload = modifier(context.payload);
|
|
1260
|
+
};
|
|
1261
|
+
controller.getPayload = () => context.payload;
|
|
1262
|
+
controller.jumpToPriority = (priority) => {
|
|
1263
|
+
context.jumpToPriority = priority;
|
|
1264
|
+
};
|
|
1265
|
+
controller.return = (result) => {
|
|
1266
|
+
context.terminated = true;
|
|
1267
|
+
context.terminationResult = result;
|
|
1268
|
+
};
|
|
1269
|
+
controller.setResult = (result) => {
|
|
1270
|
+
context.results.push(result);
|
|
1271
|
+
};
|
|
1272
|
+
controller.getResults = () => {
|
|
1273
|
+
return [...context.results];
|
|
1274
|
+
};
|
|
1275
|
+
controller.mergeResult = (merger) => {
|
|
1276
|
+
const currentResult = context.results[context.results.length - 1];
|
|
1277
|
+
const previousResults = context.results.slice(0, -1);
|
|
1278
|
+
const mergedResult = merger(previousResults, currentResult);
|
|
1279
|
+
context.results[context.results.length - 1] = mergedResult;
|
|
1280
|
+
};
|
|
1281
|
+
return controller;
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* 🔧 Return controller to pool for reuse
|
|
1285
|
+
*/
|
|
1286
|
+
returnControllerToPool(controller) {
|
|
1287
|
+
if (this.controllerPool.length < this.maxControllerPoolSize) this.controllerPool.push(controller);
|
|
1288
|
+
}
|
|
1165
1289
|
filterHandlers(handlers, filterOptions) {
|
|
1166
1290
|
if (!filterOptions) return handlers;
|
|
1167
1291
|
const cacheKey = this.generateFilterCacheKey(filterOptions);
|
|
@@ -1181,9 +1305,10 @@ var ActionRegister = class {
|
|
|
1181
1305
|
return true;
|
|
1182
1306
|
});
|
|
1183
1307
|
if (!filterOptions.custom) {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1308
|
+
const currentMaxSize = this.filterCacheMaxSize;
|
|
1309
|
+
if (this.filterCache.size >= currentMaxSize) {
|
|
1310
|
+
const oldestKey = this.filterCache.keys().next().value;
|
|
1311
|
+
if (oldestKey !== void 0) this.filterCache.delete(oldestKey);
|
|
1187
1312
|
}
|
|
1188
1313
|
this.filterCache.set(cacheKey, filtered);
|
|
1189
1314
|
}
|
|
@@ -1210,36 +1335,7 @@ var ActionRegister = class {
|
|
|
1210
1335
|
}
|
|
1211
1336
|
async executePipeline(context, autoAbortController, autoAbortOptions) {
|
|
1212
1337
|
const createController = (_registration, _index) => {
|
|
1213
|
-
return
|
|
1214
|
-
abort: (reason) => {
|
|
1215
|
-
context.aborted = true;
|
|
1216
|
-
context.abortReason = reason;
|
|
1217
|
-
if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
|
|
1218
|
-
},
|
|
1219
|
-
modifyPayload: (modifier) => {
|
|
1220
|
-
context.payload = modifier(context.payload);
|
|
1221
|
-
},
|
|
1222
|
-
getPayload: () => context.payload,
|
|
1223
|
-
jumpToPriority: (priority) => {
|
|
1224
|
-
context.jumpToPriority = priority;
|
|
1225
|
-
},
|
|
1226
|
-
return: (result) => {
|
|
1227
|
-
context.terminated = true;
|
|
1228
|
-
context.terminationResult = result;
|
|
1229
|
-
},
|
|
1230
|
-
setResult: (result) => {
|
|
1231
|
-
context.results.push(result);
|
|
1232
|
-
},
|
|
1233
|
-
getResults: () => {
|
|
1234
|
-
return [...context.results];
|
|
1235
|
-
},
|
|
1236
|
-
mergeResult: (merger) => {
|
|
1237
|
-
const currentResult = context.results[context.results.length - 1];
|
|
1238
|
-
const previousResults = context.results.slice(0, -1);
|
|
1239
|
-
const mergedResult = merger(previousResults, currentResult);
|
|
1240
|
-
context.results[context.results.length - 1] = mergedResult;
|
|
1241
|
-
}
|
|
1242
|
-
};
|
|
1338
|
+
return this.getControllerFromPool(context, autoAbortController, autoAbortOptions);
|
|
1243
1339
|
};
|
|
1244
1340
|
switch (context.executionMode) {
|
|
1245
1341
|
case "sequential":
|
|
@@ -1401,6 +1497,15 @@ var ActionRegister = class {
|
|
|
1401
1497
|
return Array.from(this.pipelines.keys()).map((action) => this.getActionStats(action)).filter((stats) => stats !== null);
|
|
1402
1498
|
}
|
|
1403
1499
|
/**
|
|
1500
|
+
* Set global execution mode for all actions
|
|
1501
|
+
*
|
|
1502
|
+
* @param mode Execution mode to set
|
|
1503
|
+
*/
|
|
1504
|
+
setExecutionMode(mode) {
|
|
1505
|
+
this.executionMode = mode;
|
|
1506
|
+
if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Global execution mode set to: ${mode}`);
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1404
1509
|
* Set execution mode for a specific action
|
|
1405
1510
|
*
|
|
1406
1511
|
* @param action Action name
|
|
@@ -1445,6 +1550,55 @@ var ActionRegister = class {
|
|
|
1445
1550
|
return this.isDebugMode;
|
|
1446
1551
|
}
|
|
1447
1552
|
/**
|
|
1553
|
+
* Creates a consistent unregister function for a handler
|
|
1554
|
+
*
|
|
1555
|
+
* @param action - Action key
|
|
1556
|
+
* @param handlerId - Handler identifier
|
|
1557
|
+
* @param registration - Handler registration object
|
|
1558
|
+
* @returns Unregister function
|
|
1559
|
+
* @private
|
|
1560
|
+
*/
|
|
1561
|
+
createUnregisterFunction(action, handlerId, registration) {
|
|
1562
|
+
return () => {
|
|
1563
|
+
const pipeline = this.pipelines.get(action);
|
|
1564
|
+
if (!pipeline) return;
|
|
1565
|
+
const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
|
|
1566
|
+
if (index !== -1) {
|
|
1567
|
+
pipeline.splice(index, 1);
|
|
1568
|
+
this.invalidateFilterCache();
|
|
1569
|
+
this.unregisterFunctions.delete(handlerId);
|
|
1570
|
+
if (registration.config.cleanup && typeof registration.config.cleanup === "function") try {
|
|
1571
|
+
registration.config.cleanup();
|
|
1572
|
+
} catch (cleanupError) {
|
|
1573
|
+
this.log(`Cleanup error during unregister: ${String(action)}`, cleanupError, "warn");
|
|
1574
|
+
}
|
|
1575
|
+
this.log(`Handler unregistered: ${String(action)}`, {
|
|
1576
|
+
handlerId,
|
|
1577
|
+
remainingHandlers: pipeline.length
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Gets the total count of registered unregister functions
|
|
1584
|
+
*
|
|
1585
|
+
* @returns Number of unregister functions
|
|
1586
|
+
* @public
|
|
1587
|
+
*/
|
|
1588
|
+
getUnregisterFunctionCount() {
|
|
1589
|
+
return this.unregisterFunctions.size;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Checks if an unregister function exists for the given handler ID
|
|
1593
|
+
*
|
|
1594
|
+
* @param handlerId - Handler identifier to check
|
|
1595
|
+
* @returns True if unregister function exists
|
|
1596
|
+
* @public
|
|
1597
|
+
*/
|
|
1598
|
+
hasUnregisterFunction(handlerId) {
|
|
1599
|
+
return this.unregisterFunctions.has(handlerId);
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1448
1602
|
* 🆕 Destroy method for comprehensive cleanup
|
|
1449
1603
|
*
|
|
1450
1604
|
* Cleans up all internal resources including pipelines, guards, queues, and statistics.
|
|
@@ -1453,11 +1607,20 @@ var ActionRegister = class {
|
|
|
1453
1607
|
* @public
|
|
1454
1608
|
*/
|
|
1455
1609
|
destroy() {
|
|
1610
|
+
this.unregisterFunctions.forEach((unregister) => {
|
|
1611
|
+
try {
|
|
1612
|
+
unregister();
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
this.log("Error during unregister in destroy", error, "error");
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
this.unregisterFunctions.clear();
|
|
1456
1618
|
this.pipelines.clear();
|
|
1457
1619
|
this.actionGuard.destroy();
|
|
1458
1620
|
this.dispatchQueue?.clear?.();
|
|
1459
1621
|
this.actionExecutionModes.clear();
|
|
1460
1622
|
this.filterCache.clear();
|
|
1623
|
+
this.controllerPool.length = 0;
|
|
1461
1624
|
this.log("ActionRegister destroyed");
|
|
1462
1625
|
}
|
|
1463
1626
|
};
|
|
@@ -1534,7 +1697,17 @@ var ActionRegister = class {
|
|
|
1534
1697
|
* @public
|
|
1535
1698
|
*/
|
|
1536
1699
|
function createActionHandler(registry, action, handler, config) {
|
|
1537
|
-
const
|
|
1700
|
+
const timestamp = Date.now();
|
|
1701
|
+
const random = Math.random().toString(36).substr(2, 5);
|
|
1702
|
+
const finalConfig = {
|
|
1703
|
+
priority: config?.priority ?? 0,
|
|
1704
|
+
id: config?.id || `react_${String(action)}_${timestamp}_${random}`,
|
|
1705
|
+
blocking: config?.blocking ?? false,
|
|
1706
|
+
once: config?.once ?? false,
|
|
1707
|
+
debounce: config?.debounce ?? void 0,
|
|
1708
|
+
throttle: config?.throttle ?? void 0,
|
|
1709
|
+
replaceExisting: true
|
|
1710
|
+
};
|
|
1538
1711
|
let currentUnregister;
|
|
1539
1712
|
let isRegistered = false;
|
|
1540
1713
|
return {
|
|
@@ -1562,96 +1735,6 @@ function createActionHandler(registry, action, handler, config) {
|
|
|
1562
1735
|
};
|
|
1563
1736
|
}
|
|
1564
1737
|
/**
|
|
1565
|
-
* 🆕 React handler configuration factory
|
|
1566
|
-
*
|
|
1567
|
-
* Creates optimized handler configurations for React environments with
|
|
1568
|
-
* proper cleanup and unique ID generation.
|
|
1569
|
-
*
|
|
1570
|
-
* @template T - ActionPayloadMap type
|
|
1571
|
-
* @template K - Action key type
|
|
1572
|
-
*
|
|
1573
|
-
* @param action - Action name
|
|
1574
|
-
* @param componentId - Optional component identifier for debugging
|
|
1575
|
-
* @param config - Base handler configuration
|
|
1576
|
-
*
|
|
1577
|
-
* @returns Optimized configuration for React environments
|
|
1578
|
-
*
|
|
1579
|
-
* @example
|
|
1580
|
-
* ```tsx
|
|
1581
|
-
* function MyComponent({ userId }: { userId: string }) {
|
|
1582
|
-
* const registry = useActionRegister();
|
|
1583
|
-
*
|
|
1584
|
-
* useEffect(() => {
|
|
1585
|
-
* const config = createReactHandlerConfig('updateUser', 'MyComponent', {
|
|
1586
|
-
* priority: 10
|
|
1587
|
-
* });
|
|
1588
|
-
*
|
|
1589
|
-
* const unregister = registry.register('updateUser', handler, config);
|
|
1590
|
-
* return unregister;
|
|
1591
|
-
* }, [registry, handler]);
|
|
1592
|
-
* }
|
|
1593
|
-
* ```
|
|
1594
|
-
*
|
|
1595
|
-
* @public
|
|
1596
|
-
*/
|
|
1597
|
-
function createReactHandlerConfig(action, componentId, config = {}) {
|
|
1598
|
-
const timestamp = Date.now();
|
|
1599
|
-
const random = Math.random().toString(36).substr(2, 5);
|
|
1600
|
-
return {
|
|
1601
|
-
priority: config.priority ?? 0,
|
|
1602
|
-
id: config.id || `${componentId || "react"}_${action}_${timestamp}_${random}`,
|
|
1603
|
-
blocking: config.blocking ?? false,
|
|
1604
|
-
once: config.once ?? false,
|
|
1605
|
-
debounce: config.debounce ?? void 0,
|
|
1606
|
-
throttle: config.throttle ?? void 0,
|
|
1607
|
-
replaceExisting: true
|
|
1608
|
-
};
|
|
1609
|
-
}
|
|
1610
|
-
/**
|
|
1611
|
-
* 🆕 React action dispatcher factory
|
|
1612
|
-
*
|
|
1613
|
-
* Creates a dispatcher function optimized for React component usage
|
|
1614
|
-
* with proper error boundaries and async handling.
|
|
1615
|
-
*
|
|
1616
|
-
* @template T - ActionPayloadMap type
|
|
1617
|
-
*
|
|
1618
|
-
* @param registry - ActionRegister instance
|
|
1619
|
-
* @param errorHandler - Optional error handler for unhandled dispatch errors
|
|
1620
|
-
*
|
|
1621
|
-
* @returns Optimized dispatch function for React components
|
|
1622
|
-
*
|
|
1623
|
-
* @example
|
|
1624
|
-
* ```tsx
|
|
1625
|
-
* function MyComponent() {
|
|
1626
|
-
* const registry = useActionRegister();
|
|
1627
|
-
*
|
|
1628
|
-
* const dispatch = createReactDispatcher(registry, (error, action, payload) => {
|
|
1629
|
-
* console.error(`Failed to dispatch ${action}:`, error);
|
|
1630
|
-
* });
|
|
1631
|
-
*
|
|
1632
|
-
* const handleClick = useCallback(() => {
|
|
1633
|
-
* dispatch('userClick', { buttonId: 'submit' });
|
|
1634
|
-
* }, [dispatch]);
|
|
1635
|
-
* }
|
|
1636
|
-
* ```
|
|
1637
|
-
*
|
|
1638
|
-
* @public
|
|
1639
|
-
*/
|
|
1640
|
-
function createReactDispatcher(registry, errorHandler) {
|
|
1641
|
-
return async (action, payload, options) => {
|
|
1642
|
-
try {
|
|
1643
|
-
await registry.dispatch(action, payload, {
|
|
1644
|
-
immediate: false,
|
|
1645
|
-
...options
|
|
1646
|
-
});
|
|
1647
|
-
} catch (error) {
|
|
1648
|
-
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
1649
|
-
if (errorHandler) errorHandler(errorObj, action, payload);
|
|
1650
|
-
else console.error(`[ActionRegister] Dispatch failed for action '${String(action)}':`, errorObj);
|
|
1651
|
-
}
|
|
1652
|
-
};
|
|
1653
|
-
}
|
|
1654
|
-
/**
|
|
1655
1738
|
* 🆕 React development utilities
|
|
1656
1739
|
*
|
|
1657
1740
|
* Provides debugging and development helpers specifically for React environments.
|
|
@@ -1693,7 +1776,7 @@ const ReactDevUtils = {
|
|
|
1693
1776
|
* Utilities for integrating ActionRegister errors with React Error Boundaries.
|
|
1694
1777
|
*/
|
|
1695
1778
|
var ReactActionError = class ReactActionError extends Error {
|
|
1696
|
-
constructor(message, action, payload, handlerId, originalError) {
|
|
1779
|
+
constructor(message, action, payload, handlerId = void 0, originalError) {
|
|
1697
1780
|
super(message);
|
|
1698
1781
|
this.name = "ReactActionError";
|
|
1699
1782
|
this.action = action;
|
|
@@ -1725,8 +1808,6 @@ exports.ActionRegister = ActionRegister;
|
|
|
1725
1808
|
exports.ReactActionError = ReactActionError;
|
|
1726
1809
|
exports.ReactDevUtils = ReactDevUtils;
|
|
1727
1810
|
exports.createActionHandler = createActionHandler;
|
|
1728
|
-
exports.createReactDispatcher = createReactDispatcher;
|
|
1729
|
-
exports.createReactHandlerConfig = createReactHandlerConfig;
|
|
1730
1811
|
exports.executeParallel = executeParallel;
|
|
1731
1812
|
exports.executeRace = executeRace;
|
|
1732
1813
|
exports.executeSequential = executeSequential;
|