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