@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/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.config.blocking;
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++) if ((this.queue[i].priority || 0) < priority) {
562
- insertIndex = i;
563
- break;
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.filterCacheMaxSize = 100;
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
- * 🆕 Generate unique handler ID using crypto
744
+ * 🔧 Generate unique handler ID using optimized counter-based approach
732
745
  */
733
746
  generateHandlerId(action) {
734
- const uuid = crypto.randomUUID();
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) if (config.replaceExisting) {
816
- const oldRegistration = pipeline[existingIndex];
817
- if (oldRegistration && typeof oldRegistration.cleanup === "function") try {
818
- oldRegistration.cleanup();
819
- } catch (cleanupError) {
820
- this.log(`Cleanup error for replaced handler: ${String(action)}`, cleanupError, "warn");
821
- }
822
- pipeline[existingIndex] = registration;
823
- pipeline.sort((a, b) => b.config.priority - a.config.priority);
824
- this.invalidateFilterCache();
825
- this.log(`Handler replaced: ${String(action)}`, {
826
- handlerId,
827
- priority: config.priority,
828
- totalHandlers: pipeline.length,
829
- oldHandlerCleaned: Boolean(oldRegistration.cleanup)
830
- });
831
- return () => {
832
- const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
833
- if (index !== -1) {
834
- pipeline.splice(index, 1);
835
- this.invalidateFilterCache();
836
- this.log(`Replaced handler unregistered: ${String(action)}`, { handlerId });
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
- } else {
840
- this.log(`Handler duplicate ignored: ${String(action)}`, {
841
- handlerId,
842
- note: "Use replaceExisting:true to replace"
843
- }, "warn");
844
- return () => {};
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) return;
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
- if (!pipeline || pipeline.length === 0) return;
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
- let throttleMs;
1012
- let debounceMs;
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
- const errors = [];
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
- if (this.filterCache.size >= this.filterCacheMaxSize) {
1185
- const firstKey = this.filterCache.keys().next().value;
1186
- if (firstKey) this.filterCache.delete(firstKey);
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 finalConfig = createReactHandlerConfig(String(action), void 0, config);
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;