@context-action/core 0.6.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.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.config.blocking;
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++) if ((this.queue[i].priority || 0) < priority) {
561
- insertIndex = i;
562
- break;
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.filterCacheMaxSize = 100;
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
- * 🆕 Generate unique handler ID using crypto
743
+ * 🔧 Generate unique handler ID using optimized counter-based approach
731
744
  */
732
745
  generateHandlerId(action) {
733
- const uuid = crypto.randomUUID();
734
- return `${String(action)}_${uuid.slice(0, 8)}`;
746
+ return `${String(action)}_${this.name}_${++this.handlerIdCounter}`;
735
747
  }
736
748
  /**
737
749
  * 🔧 Create and merge AbortSignal instances with proper cleanup
@@ -800,7 +812,8 @@ var ActionRegister = class {
800
812
  once: config.once ?? false,
801
813
  debounce: config.debounce ?? void 0,
802
814
  throttle: config.throttle ?? void 0,
803
- replaceExisting: config.replaceExisting ?? false
815
+ replaceExisting: config.replaceExisting ?? false,
816
+ cleanup: config.cleanup
804
817
  },
805
818
  id: handlerId
806
819
  };
@@ -811,65 +824,60 @@ var ActionRegister = class {
811
824
  return () => {};
812
825
  }
813
826
  const existingIndex = pipeline.findIndex((reg) => reg.id === handlerId);
814
- if (existingIndex !== -1) if (config.replaceExisting) {
815
- const oldRegistration = pipeline[existingIndex];
816
- if (oldRegistration && typeof oldRegistration.cleanup === "function") try {
817
- oldRegistration.cleanup();
818
- } catch (cleanupError) {
819
- this.log(`Cleanup error for replaced handler: ${String(action)}`, cleanupError, "warn");
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
- } else {
839
- this.log(`Handler duplicate ignored: ${String(action)}`, {
840
- handlerId,
841
- note: "Use replaceExisting:true to replace"
842
- }, "warn");
843
- return () => {};
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
- console.log(`🔍 [DEBUG] Action '${String(action)}' pipeline after registration:`, {
854
- totalHandlers: pipeline.length,
855
- handlers: pipeline.map((h) => ({
856
- id: h.config.id,
857
- priority: h.config.priority
858
- })),
859
- pipelineExists: this.pipelines.has(action),
860
- canDispatch: this.hasHandlers(action)
861
- });
862
- return () => {
863
- const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
864
- if (index !== -1) {
865
- pipeline.splice(index, 1);
866
- this.invalidateFilterCache();
867
- this.log(`Handler unregistered: ${String(action)}`, {
868
- handlerId,
869
- remainingHandlers: pipeline.length
870
- });
871
- }
872
- };
880
+ return unregister;
873
881
  }
874
882
  /**
875
883
  * Dispatch an action with optional execution options
@@ -896,7 +904,7 @@ var ActionRegister = class {
896
904
  * 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
897
905
  */
898
906
  async _performDispatch(action, payload, options) {
899
- console.log(`🚀 [DEBUG] Starting dispatch for action '${String(action)}':`, {
907
+ this.log(`Starting dispatch for action '${String(action)}'`, {
900
908
  hasPayload: payload !== void 0,
901
909
  payloadType: payload?.constructor?.name || typeof payload,
902
910
  options: options ? Object.keys(options) : "none",
@@ -906,18 +914,18 @@ var ActionRegister = class {
906
914
  const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
907
915
  if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
908
916
  if (effectiveSignal?.aborted) {
909
- console.log(`🚫 [DEBUG] Dispatch aborted before execution for '${String(action)}'`);
917
+ this.log(`Dispatch aborted before execution for '${String(action)}'`);
910
918
  return;
911
919
  }
912
920
  const pipeline = this.pipelines.get(action);
913
- console.log(`🔍 [DEBUG] Pipeline lookup for '${String(action)}':`, {
921
+ this.log(`Pipeline lookup for '${String(action)}'`, {
914
922
  pipelineExists: Boolean(pipeline),
915
923
  handlersCount: pipeline?.length || 0,
916
924
  allRegisteredActions: Array.from(this.pipelines.keys()),
917
925
  pipelineMap: Object.fromEntries(Array.from(this.pipelines.entries()).map(([k, v]) => [k, v.length]))
918
926
  });
919
927
  if (!pipeline || pipeline.length === 0) {
920
- console.log(`⚠️ [DEBUG] No handlers found for action '${String(action)}', dispatch cancelled`);
928
+ this.log(`No handlers found for action '${String(action)}', dispatch cancelled`, {}, "warn");
921
929
  return;
922
930
  }
923
931
  const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
@@ -965,6 +973,8 @@ var ActionRegister = class {
965
973
  context.abortReason = "Action dispatch aborted by signal";
966
974
  } : void 0;
967
975
  if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
976
+ const contextWithErrors = context;
977
+ contextWithErrors.collectedErrors;
968
978
  try {
969
979
  await this.executePipeline(context, autoAbortController, options?.autoAbort);
970
980
  this.log(`Pipeline execution succeeded for ${String(action)}`);
@@ -1016,6 +1026,7 @@ var ActionRegister = class {
1016
1026
  if (!pipeline || pipeline.length === 0) return {
1017
1027
  success: true,
1018
1028
  aborted: false,
1029
+ abortReason: void 0,
1019
1030
  terminated: false,
1020
1031
  result: void 0,
1021
1032
  successResults: [],
@@ -1034,68 +1045,8 @@ var ActionRegister = class {
1034
1045
  };
1035
1046
  const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
1036
1047
  const actionKey = String(action);
1037
- let throttleMs;
1038
- let debounceMs;
1039
- if (options?.throttle !== void 0) throttleMs = options.throttle;
1040
- else if (filteredHandlers.length > 0) {
1041
- for (const handler of filteredHandlers) if (handler.config.throttle !== void 0) {
1042
- throttleMs = handler.config.throttle;
1043
- break;
1044
- }
1045
- }
1046
- if (options?.debounce !== void 0) debounceMs = options.debounce;
1047
- else if (filteredHandlers.length > 0) {
1048
- for (const handler of filteredHandlers) if (handler.config.debounce !== void 0) {
1049
- debounceMs = handler.config.debounce;
1050
- break;
1051
- }
1052
- }
1053
- if (debounceMs !== void 0) {
1054
- const shouldProceed = await this.actionGuard.debounce(actionKey, debounceMs);
1055
- if (!shouldProceed) return {
1056
- success: false,
1057
- aborted: true,
1058
- abortReason: "Debounced execution",
1059
- terminated: false,
1060
- result: void 0,
1061
- successResults: [],
1062
- results: [],
1063
- failedResults: [],
1064
- execution: {
1065
- duration: Date.now() - _startTime,
1066
- handlersExecuted: 0,
1067
- handlersSkipped: pipeline.length,
1068
- handlersFailed: 0,
1069
- startTime: _startTime,
1070
- endTime: Date.now()
1071
- },
1072
- handlers: [],
1073
- errors: []
1074
- };
1075
- }
1076
- if (throttleMs !== void 0) {
1077
- const shouldProceed = this.actionGuard.throttle(actionKey, throttleMs);
1078
- if (!shouldProceed) return {
1079
- success: false,
1080
- aborted: true,
1081
- abortReason: "Throttled execution",
1082
- terminated: false,
1083
- result: void 0,
1084
- successResults: [],
1085
- results: [],
1086
- failedResults: [],
1087
- execution: {
1088
- duration: Date.now() - _startTime,
1089
- handlersExecuted: 0,
1090
- handlersSkipped: pipeline.length,
1091
- handlersFailed: 0,
1092
- startTime: _startTime,
1093
- endTime: Date.now()
1094
- },
1095
- handlers: [],
1096
- errors: []
1097
- };
1098
- }
1048
+ const guardResult = await this.applyActionGuardControlsWithResult(actionKey, filteredHandlers, options, _startTime, pipeline.length);
1049
+ if (guardResult) return guardResult;
1099
1050
  const currentExecutionMode = options?.executionMode || this.actionExecutionModes.get(action) || this.executionMode;
1100
1051
  const context = {
1101
1052
  action: String(action),
@@ -1112,11 +1063,14 @@ var ActionRegister = class {
1112
1063
  };
1113
1064
  let executionError;
1114
1065
  const handlerResults = [];
1115
- const errors = [];
1116
1066
  filteredHandlers.forEach((handler) => {
1117
1067
  handlerResults.push({
1118
1068
  id: handler.config.id,
1119
- executed: false
1069
+ executed: false,
1070
+ duration: void 0,
1071
+ result: void 0,
1072
+ error: void 0,
1073
+ metadata: void 0
1120
1074
  });
1121
1075
  });
1122
1076
  const abortHandler = effectiveSignal ? () => {
@@ -1124,23 +1078,33 @@ var ActionRegister = class {
1124
1078
  context.abortReason = "Action dispatch aborted by signal";
1125
1079
  } : void 0;
1126
1080
  if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
1081
+ let errors = [];
1127
1082
  try {
1128
1083
  await this.executePipeline(context, autoAbortController, options?.autoAbort);
1084
+ const contextWithErrors = context;
1085
+ errors = contextWithErrors.collectedErrors || [];
1129
1086
  const executedCount = Math.min(context.currentIndex + (context.aborted ? 0 : 1), filteredHandlers.length);
1130
1087
  for (let i = 0; i < executedCount; i++) {
1131
- const handlerResult = handlerResults.find((hr) => hr.id === filteredHandlers[i].config.id);
1088
+ const handler = filteredHandlers[i];
1089
+ if (!handler) continue;
1090
+ const handlerResult = handlerResults.find((hr) => hr.id === handler.config.id);
1132
1091
  if (handlerResult) handlerResult.executed = true;
1133
1092
  }
1134
1093
  } catch (error) {
1094
+ const contextWithErrors = context;
1095
+ errors = contextWithErrors.collectedErrors || [];
1135
1096
  executionError = error instanceof Error ? error : new Error(String(error));
1136
1097
  errors.push({
1137
1098
  handlerId: "pipeline",
1138
1099
  error: executionError,
1139
- timestamp: Date.now()
1100
+ timestamp: Date.now(),
1101
+ severity: "blocking"
1140
1102
  });
1141
1103
  const executedCount = Math.min(context.currentIndex + 1, filteredHandlers.length);
1142
1104
  for (let i = 0; i < executedCount; i++) {
1143
- const handlerResult = handlerResults.find((hr) => hr.id === filteredHandlers[i].config.id);
1105
+ const handler = filteredHandlers[i];
1106
+ if (!handler) continue;
1107
+ const handlerResult = handlerResults.find((hr) => hr.id === handler.config.id);
1144
1108
  if (handlerResult) handlerResult.executed = true;
1145
1109
  }
1146
1110
  } finally {
@@ -1165,7 +1129,7 @@ var ActionRegister = class {
1165
1129
  failedResults,
1166
1130
  execution: {
1167
1131
  duration: endTime - _startTime,
1168
- handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
1132
+ handlersExecuted: filteredHandlers.length === 0 ? 0 : context.currentIndex + (context.aborted ? 0 : 1),
1169
1133
  handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
1170
1134
  handlersFailed: errors.length,
1171
1135
  startTime: _startTime,
@@ -1184,6 +1148,82 @@ var ActionRegister = class {
1184
1148
  return executionResult;
1185
1149
  }
1186
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
+ /**
1187
1227
  * 🔧 Generate cache key for filter options
1188
1228
  */
1189
1229
  generateFilterCacheKey(filterOptions) {
@@ -1203,6 +1243,48 @@ var ActionRegister = class {
1203
1243
  invalidateFilterCache() {
1204
1244
  this.filterCache.clear();
1205
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
+ }
1206
1288
  filterHandlers(handlers, filterOptions) {
1207
1289
  if (!filterOptions) return handlers;
1208
1290
  const cacheKey = this.generateFilterCacheKey(filterOptions);
@@ -1222,9 +1304,10 @@ var ActionRegister = class {
1222
1304
  return true;
1223
1305
  });
1224
1306
  if (!filterOptions.custom) {
1225
- if (this.filterCache.size >= this.filterCacheMaxSize) {
1226
- const firstKey = this.filterCache.keys().next().value;
1227
- if (firstKey) this.filterCache.delete(firstKey);
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);
1228
1311
  }
1229
1312
  this.filterCache.set(cacheKey, filtered);
1230
1313
  }
@@ -1251,36 +1334,7 @@ var ActionRegister = class {
1251
1334
  }
1252
1335
  async executePipeline(context, autoAbortController, autoAbortOptions) {
1253
1336
  const createController = (_registration, _index) => {
1254
- return {
1255
- abort: (reason) => {
1256
- context.aborted = true;
1257
- context.abortReason = reason;
1258
- if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
1259
- },
1260
- modifyPayload: (modifier) => {
1261
- context.payload = modifier(context.payload);
1262
- },
1263
- getPayload: () => context.payload,
1264
- jumpToPriority: (priority) => {
1265
- context.jumpToPriority = priority;
1266
- },
1267
- return: (result) => {
1268
- context.terminated = true;
1269
- context.terminationResult = result;
1270
- },
1271
- setResult: (result) => {
1272
- context.results.push(result);
1273
- },
1274
- getResults: () => {
1275
- return [...context.results];
1276
- },
1277
- mergeResult: (merger) => {
1278
- const currentResult = context.results[context.results.length - 1];
1279
- const previousResults = context.results.slice(0, -1);
1280
- const mergedResult = merger(previousResults, currentResult);
1281
- context.results[context.results.length - 1] = mergedResult;
1282
- }
1283
- };
1337
+ return this.getControllerFromPool(context, autoAbortController, autoAbortOptions);
1284
1338
  };
1285
1339
  switch (context.executionMode) {
1286
1340
  case "sequential":
@@ -1442,6 +1496,15 @@ var ActionRegister = class {
1442
1496
  return Array.from(this.pipelines.keys()).map((action) => this.getActionStats(action)).filter((stats) => stats !== null);
1443
1497
  }
1444
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
+ /**
1445
1508
  * Set execution mode for a specific action
1446
1509
  *
1447
1510
  * @param action Action name
@@ -1486,6 +1549,55 @@ var ActionRegister = class {
1486
1549
  return this.isDebugMode;
1487
1550
  }
1488
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
+ /**
1489
1601
  * 🆕 Destroy method for comprehensive cleanup
1490
1602
  *
1491
1603
  * Cleans up all internal resources including pipelines, guards, queues, and statistics.
@@ -1494,11 +1606,20 @@ var ActionRegister = class {
1494
1606
  * @public
1495
1607
  */
1496
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();
1497
1617
  this.pipelines.clear();
1498
1618
  this.actionGuard.destroy();
1499
1619
  this.dispatchQueue?.clear?.();
1500
1620
  this.actionExecutionModes.clear();
1501
1621
  this.filterCache.clear();
1622
+ this.controllerPool.length = 0;
1502
1623
  this.log("ActionRegister destroyed");
1503
1624
  }
1504
1625
  };
@@ -1575,7 +1696,17 @@ var ActionRegister = class {
1575
1696
  * @public
1576
1697
  */
1577
1698
  function createActionHandler(registry, action, handler, config) {
1578
- const finalConfig = createReactHandlerConfig(String(action), void 0, config);
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
+ };
1579
1710
  let currentUnregister;
1580
1711
  let isRegistered = false;
1581
1712
  return {
@@ -1603,96 +1734,6 @@ function createActionHandler(registry, action, handler, config) {
1603
1734
  };
1604
1735
  }
1605
1736
  /**
1606
- * 🆕 React handler configuration factory
1607
- *
1608
- * Creates optimized handler configurations for React environments with
1609
- * proper cleanup and unique ID generation.
1610
- *
1611
- * @template T - ActionPayloadMap type
1612
- * @template K - Action key type
1613
- *
1614
- * @param action - Action name
1615
- * @param componentId - Optional component identifier for debugging
1616
- * @param config - Base handler configuration
1617
- *
1618
- * @returns Optimized configuration for React environments
1619
- *
1620
- * @example
1621
- * ```tsx
1622
- * function MyComponent({ userId }: { userId: string }) {
1623
- * const registry = useActionRegister();
1624
- *
1625
- * useEffect(() => {
1626
- * const config = createReactHandlerConfig('updateUser', 'MyComponent', {
1627
- * priority: 10
1628
- * });
1629
- *
1630
- * const unregister = registry.register('updateUser', handler, config);
1631
- * return unregister;
1632
- * }, [registry, handler]);
1633
- * }
1634
- * ```
1635
- *
1636
- * @public
1637
- */
1638
- function createReactHandlerConfig(action, componentId, config = {}) {
1639
- const timestamp = Date.now();
1640
- const random = Math.random().toString(36).substr(2, 5);
1641
- return {
1642
- priority: config.priority ?? 0,
1643
- id: config.id || `${componentId || "react"}_${action}_${timestamp}_${random}`,
1644
- blocking: config.blocking ?? false,
1645
- once: config.once ?? false,
1646
- debounce: config.debounce ?? void 0,
1647
- throttle: config.throttle ?? void 0,
1648
- replaceExisting: true
1649
- };
1650
- }
1651
- /**
1652
- * 🆕 React action dispatcher factory
1653
- *
1654
- * Creates a dispatcher function optimized for React component usage
1655
- * with proper error boundaries and async handling.
1656
- *
1657
- * @template T - ActionPayloadMap type
1658
- *
1659
- * @param registry - ActionRegister instance
1660
- * @param errorHandler - Optional error handler for unhandled dispatch errors
1661
- *
1662
- * @returns Optimized dispatch function for React components
1663
- *
1664
- * @example
1665
- * ```tsx
1666
- * function MyComponent() {
1667
- * const registry = useActionRegister();
1668
- *
1669
- * const dispatch = createReactDispatcher(registry, (error, action, payload) => {
1670
- * console.error(`Failed to dispatch ${action}:`, error);
1671
- * });
1672
- *
1673
- * const handleClick = useCallback(() => {
1674
- * dispatch('userClick', { buttonId: 'submit' });
1675
- * }, [dispatch]);
1676
- * }
1677
- * ```
1678
- *
1679
- * @public
1680
- */
1681
- function createReactDispatcher(registry, errorHandler) {
1682
- return async (action, payload, options) => {
1683
- try {
1684
- await registry.dispatch(action, payload, {
1685
- immediate: false,
1686
- ...options
1687
- });
1688
- } catch (error) {
1689
- const errorObj = error instanceof Error ? error : new Error(String(error));
1690
- if (errorHandler) errorHandler(errorObj, action, payload);
1691
- else console.error(`[ActionRegister] Dispatch failed for action '${String(action)}':`, errorObj);
1692
- }
1693
- };
1694
- }
1695
- /**
1696
1737
  * 🆕 React development utilities
1697
1738
  *
1698
1739
  * Provides debugging and development helpers specifically for React environments.
@@ -1734,7 +1775,7 @@ const ReactDevUtils = {
1734
1775
  * Utilities for integrating ActionRegister errors with React Error Boundaries.
1735
1776
  */
1736
1777
  var ReactActionError = class ReactActionError extends Error {
1737
- constructor(message, action, payload, handlerId, originalError) {
1778
+ constructor(message, action, payload, handlerId = void 0, originalError) {
1738
1779
  super(message);
1739
1780
  this.name = "ReactActionError";
1740
1781
  this.action = action;
@@ -1761,5 +1802,5 @@ function isReactActionError(error) {
1761
1802
  }
1762
1803
 
1763
1804
  //#endregion
1764
- export { ActionGuard, ActionRegister, ReactActionError, ReactDevUtils, createActionHandler, createReactDispatcher, createReactHandlerConfig, executeParallel, executeRace, executeSequential, isReactActionError };
1805
+ export { ActionGuard, ActionRegister, ReactActionError, ReactDevUtils, createActionHandler, executeParallel, executeRace, executeSequential, isReactActionError };
1765
1806
  //# sourceMappingURL=index.js.map