@context-action/core 0.6.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,65 +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
- console.log(`🔍 [DEBUG] Action '${String(action)}' pipeline after registration:`, {
855
- totalHandlers: pipeline.length,
856
- handlers: pipeline.map((h) => ({
857
- id: h.config.id,
858
- priority: h.config.priority
859
- })),
860
- pipelineExists: this.pipelines.has(action),
861
- canDispatch: this.hasHandlers(action)
862
- });
863
- return () => {
864
- const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
865
- if (index !== -1) {
866
- pipeline.splice(index, 1);
867
- this.invalidateFilterCache();
868
- this.log(`Handler unregistered: ${String(action)}`, {
869
- handlerId,
870
- remainingHandlers: pipeline.length
871
- });
872
- }
873
- };
881
+ return unregister;
874
882
  }
875
883
  /**
876
884
  * Dispatch an action with optional execution options
@@ -897,7 +905,7 @@ var ActionRegister = class {
897
905
  * 🆕 실제 디스패치 작업 수행 (큐에서 호출됨)
898
906
  */
899
907
  async _performDispatch(action, payload, options) {
900
- console.log(`🚀 [DEBUG] Starting dispatch for action '${String(action)}':`, {
908
+ this.log(`Starting dispatch for action '${String(action)}'`, {
901
909
  hasPayload: payload !== void 0,
902
910
  payloadType: payload?.constructor?.name || typeof payload,
903
911
  options: options ? Object.keys(options) : "none",
@@ -907,18 +915,18 @@ var ActionRegister = class {
907
915
  const [effectiveSignal, autoAbortController, cleanup] = this.createAbortSignal(options);
908
916
  if (options?.autoAbort?.onControllerCreated && autoAbortController) options.autoAbort.onControllerCreated(autoAbortController);
909
917
  if (effectiveSignal?.aborted) {
910
- console.log(`🚫 [DEBUG] Dispatch aborted before execution for '${String(action)}'`);
918
+ this.log(`Dispatch aborted before execution for '${String(action)}'`);
911
919
  return;
912
920
  }
913
921
  const pipeline = this.pipelines.get(action);
914
- console.log(`🔍 [DEBUG] Pipeline lookup for '${String(action)}':`, {
922
+ this.log(`Pipeline lookup for '${String(action)}'`, {
915
923
  pipelineExists: Boolean(pipeline),
916
924
  handlersCount: pipeline?.length || 0,
917
925
  allRegisteredActions: Array.from(this.pipelines.keys()),
918
926
  pipelineMap: Object.fromEntries(Array.from(this.pipelines.entries()).map(([k, v]) => [k, v.length]))
919
927
  });
920
928
  if (!pipeline || pipeline.length === 0) {
921
- console.log(`⚠️ [DEBUG] No handlers found for action '${String(action)}', dispatch cancelled`);
929
+ this.log(`No handlers found for action '${String(action)}', dispatch cancelled`, {}, "warn");
922
930
  return;
923
931
  }
924
932
  const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
@@ -1017,6 +1025,7 @@ var ActionRegister = class {
1017
1025
  if (!pipeline || pipeline.length === 0) return {
1018
1026
  success: true,
1019
1027
  aborted: false,
1028
+ abortReason: void 0,
1020
1029
  terminated: false,
1021
1030
  result: void 0,
1022
1031
  successResults: [],
@@ -1035,68 +1044,8 @@ var ActionRegister = class {
1035
1044
  };
1036
1045
  const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
1037
1046
  const actionKey = String(action);
1038
- let throttleMs;
1039
- let debounceMs;
1040
- if (options?.throttle !== void 0) throttleMs = options.throttle;
1041
- else if (filteredHandlers.length > 0) {
1042
- for (const handler of filteredHandlers) if (handler.config.throttle !== void 0) {
1043
- throttleMs = handler.config.throttle;
1044
- break;
1045
- }
1046
- }
1047
- if (options?.debounce !== void 0) debounceMs = options.debounce;
1048
- else if (filteredHandlers.length > 0) {
1049
- for (const handler of filteredHandlers) if (handler.config.debounce !== void 0) {
1050
- debounceMs = handler.config.debounce;
1051
- break;
1052
- }
1053
- }
1054
- if (debounceMs !== void 0) {
1055
- const shouldProceed = await this.actionGuard.debounce(actionKey, debounceMs);
1056
- if (!shouldProceed) return {
1057
- success: false,
1058
- aborted: true,
1059
- abortReason: "Debounced execution",
1060
- terminated: false,
1061
- result: void 0,
1062
- successResults: [],
1063
- results: [],
1064
- failedResults: [],
1065
- execution: {
1066
- duration: Date.now() - _startTime,
1067
- handlersExecuted: 0,
1068
- handlersSkipped: pipeline.length,
1069
- handlersFailed: 0,
1070
- startTime: _startTime,
1071
- endTime: Date.now()
1072
- },
1073
- handlers: [],
1074
- errors: []
1075
- };
1076
- }
1077
- if (throttleMs !== void 0) {
1078
- const shouldProceed = this.actionGuard.throttle(actionKey, throttleMs);
1079
- if (!shouldProceed) return {
1080
- success: false,
1081
- aborted: true,
1082
- abortReason: "Throttled execution",
1083
- terminated: false,
1084
- result: void 0,
1085
- successResults: [],
1086
- results: [],
1087
- failedResults: [],
1088
- execution: {
1089
- duration: Date.now() - _startTime,
1090
- handlersExecuted: 0,
1091
- handlersSkipped: pipeline.length,
1092
- handlersFailed: 0,
1093
- startTime: _startTime,
1094
- endTime: Date.now()
1095
- },
1096
- handlers: [],
1097
- errors: []
1098
- };
1099
- }
1047
+ const guardResult = await this.applyActionGuardControlsWithResult(actionKey, filteredHandlers, options, _startTime, pipeline.length);
1048
+ if (guardResult) return guardResult;
1100
1049
  const currentExecutionMode = options?.executionMode || this.actionExecutionModes.get(action) || this.executionMode;
1101
1050
  const context = {
1102
1051
  action: String(action),
@@ -1113,11 +1062,14 @@ var ActionRegister = class {
1113
1062
  };
1114
1063
  let executionError;
1115
1064
  const handlerResults = [];
1116
- const errors = [];
1117
1065
  filteredHandlers.forEach((handler) => {
1118
1066
  handlerResults.push({
1119
1067
  id: handler.config.id,
1120
- executed: false
1068
+ executed: false,
1069
+ duration: void 0,
1070
+ result: void 0,
1071
+ error: void 0,
1072
+ metadata: void 0
1121
1073
  });
1122
1074
  });
1123
1075
  const abortHandler = effectiveSignal ? () => {
@@ -1125,23 +1077,33 @@ var ActionRegister = class {
1125
1077
  context.abortReason = "Action dispatch aborted by signal";
1126
1078
  } : void 0;
1127
1079
  if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
1080
+ let errors = [];
1128
1081
  try {
1129
1082
  await this.executePipeline(context, autoAbortController, options?.autoAbort);
1083
+ const contextWithErrors = context;
1084
+ errors = contextWithErrors.collectedErrors || [];
1130
1085
  const executedCount = Math.min(context.currentIndex + (context.aborted ? 0 : 1), filteredHandlers.length);
1131
1086
  for (let i = 0; i < executedCount; i++) {
1132
- const handlerResult = handlerResults.find((hr) => hr.id === filteredHandlers[i].config.id);
1087
+ const handler = filteredHandlers[i];
1088
+ if (!handler) continue;
1089
+ const handlerResult = handlerResults.find((hr) => hr.id === handler.config.id);
1133
1090
  if (handlerResult) handlerResult.executed = true;
1134
1091
  }
1135
1092
  } catch (error) {
1093
+ const contextWithErrors = context;
1094
+ errors = contextWithErrors.collectedErrors || [];
1136
1095
  executionError = error instanceof Error ? error : new Error(String(error));
1137
1096
  errors.push({
1138
1097
  handlerId: "pipeline",
1139
1098
  error: executionError,
1140
- timestamp: Date.now()
1099
+ timestamp: Date.now(),
1100
+ severity: "blocking"
1141
1101
  });
1142
1102
  const executedCount = Math.min(context.currentIndex + 1, filteredHandlers.length);
1143
1103
  for (let i = 0; i < executedCount; i++) {
1144
- const handlerResult = handlerResults.find((hr) => hr.id === filteredHandlers[i].config.id);
1104
+ const handler = filteredHandlers[i];
1105
+ if (!handler) continue;
1106
+ const handlerResult = handlerResults.find((hr) => hr.id === handler.config.id);
1145
1107
  if (handlerResult) handlerResult.executed = true;
1146
1108
  }
1147
1109
  } finally {
@@ -1166,7 +1128,7 @@ var ActionRegister = class {
1166
1128
  failedResults,
1167
1129
  execution: {
1168
1130
  duration: endTime - _startTime,
1169
- handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
1131
+ handlersExecuted: filteredHandlers.length === 0 ? 0 : context.currentIndex + (context.aborted ? 0 : 1),
1170
1132
  handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
1171
1133
  handlersFailed: errors.length,
1172
1134
  startTime: _startTime,
@@ -1185,6 +1147,82 @@ var ActionRegister = class {
1185
1147
  return executionResult;
1186
1148
  }
1187
1149
  /**
1150
+ * 🔧 Unified method for dispatchWithResult that returns ExecutionResult on guard rejection
1151
+ */
1152
+ async applyActionGuardControlsWithResult(actionKey, filteredHandlers, options, startTime, pipelineLength) {
1153
+ let throttleMs;
1154
+ let debounceMs;
1155
+ if (options?.throttle !== void 0) throttleMs = options.throttle;
1156
+ else if (filteredHandlers.length > 0) {
1157
+ for (const handler of filteredHandlers) if (handler.config.throttle !== void 0) {
1158
+ throttleMs = handler.config.throttle;
1159
+ break;
1160
+ }
1161
+ }
1162
+ if (options?.debounce !== void 0) debounceMs = options.debounce;
1163
+ else if (filteredHandlers.length > 0) {
1164
+ for (const handler of filteredHandlers) if (handler.config.debounce !== void 0) {
1165
+ debounceMs = handler.config.debounce;
1166
+ break;
1167
+ }
1168
+ }
1169
+ if (debounceMs !== void 0) {
1170
+ const shouldProceed = await this.actionGuard.debounce(actionKey, debounceMs);
1171
+ if (!shouldProceed) return {
1172
+ success: false,
1173
+ aborted: true,
1174
+ abortReason: "Debounced execution",
1175
+ terminated: false,
1176
+ result: void 0,
1177
+ successResults: [],
1178
+ results: [],
1179
+ failedResults: [],
1180
+ execution: {
1181
+ duration: Date.now() - startTime,
1182
+ handlersExecuted: 0,
1183
+ handlersSkipped: pipelineLength,
1184
+ handlersFailed: 0,
1185
+ startTime,
1186
+ endTime: Date.now()
1187
+ },
1188
+ handlers: [],
1189
+ errors: []
1190
+ };
1191
+ }
1192
+ if (throttleMs !== void 0) {
1193
+ const shouldProceed = this.actionGuard.throttle(actionKey, throttleMs);
1194
+ if (!shouldProceed) return {
1195
+ success: false,
1196
+ aborted: true,
1197
+ abortReason: "Throttled execution",
1198
+ terminated: false,
1199
+ result: void 0,
1200
+ successResults: [],
1201
+ results: [],
1202
+ failedResults: [],
1203
+ execution: {
1204
+ duration: Date.now() - startTime,
1205
+ handlersExecuted: 0,
1206
+ handlersSkipped: pipelineLength,
1207
+ handlersFailed: 0,
1208
+ startTime,
1209
+ endTime: Date.now()
1210
+ },
1211
+ handlers: [],
1212
+ errors: []
1213
+ };
1214
+ }
1215
+ return null;
1216
+ }
1217
+ /**
1218
+ * 🔧 Calculate optimal cache size based on current handler count
1219
+ * Note: This caches handler SELECTION metadata only, never execution results
1220
+ */
1221
+ get filterCacheMaxSize() {
1222
+ const totalHandlers = Array.from(this.pipelines.values()).reduce((sum, pipeline) => sum + pipeline.length, 0);
1223
+ return totalHandlers * 10 || 100;
1224
+ }
1225
+ /**
1188
1226
  * 🔧 Generate cache key for filter options
1189
1227
  */
1190
1228
  generateFilterCacheKey(filterOptions) {
@@ -1204,6 +1242,48 @@ var ActionRegister = class {
1204
1242
  invalidateFilterCache() {
1205
1243
  this.filterCache.clear();
1206
1244
  }
1245
+ /**
1246
+ * 🔧 Create or reuse PipelineController from pool for better performance
1247
+ */
1248
+ getControllerFromPool(context, autoAbortController, autoAbortOptions) {
1249
+ let controller = this.controllerPool.pop();
1250
+ if (!controller) controller = {};
1251
+ controller.abort = (reason) => {
1252
+ context.aborted = true;
1253
+ context.abortReason = reason;
1254
+ if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
1255
+ };
1256
+ controller.modifyPayload = (modifier) => {
1257
+ context.payload = modifier(context.payload);
1258
+ };
1259
+ controller.getPayload = () => context.payload;
1260
+ controller.jumpToPriority = (priority) => {
1261
+ context.jumpToPriority = priority;
1262
+ };
1263
+ controller.return = (result) => {
1264
+ context.terminated = true;
1265
+ context.terminationResult = result;
1266
+ };
1267
+ controller.setResult = (result) => {
1268
+ context.results.push(result);
1269
+ };
1270
+ controller.getResults = () => {
1271
+ return [...context.results];
1272
+ };
1273
+ controller.mergeResult = (merger) => {
1274
+ const currentResult = context.results[context.results.length - 1];
1275
+ const previousResults = context.results.slice(0, -1);
1276
+ const mergedResult = merger(previousResults, currentResult);
1277
+ context.results[context.results.length - 1] = mergedResult;
1278
+ };
1279
+ return controller;
1280
+ }
1281
+ /**
1282
+ * 🔧 Return controller to pool for reuse
1283
+ */
1284
+ returnControllerToPool(controller) {
1285
+ if (this.controllerPool.length < this.maxControllerPoolSize) this.controllerPool.push(controller);
1286
+ }
1207
1287
  filterHandlers(handlers, filterOptions) {
1208
1288
  if (!filterOptions) return handlers;
1209
1289
  const cacheKey = this.generateFilterCacheKey(filterOptions);
@@ -1223,9 +1303,10 @@ var ActionRegister = class {
1223
1303
  return true;
1224
1304
  });
1225
1305
  if (!filterOptions.custom) {
1226
- if (this.filterCache.size >= this.filterCacheMaxSize) {
1227
- const firstKey = this.filterCache.keys().next().value;
1228
- if (firstKey) this.filterCache.delete(firstKey);
1306
+ const currentMaxSize = this.filterCacheMaxSize;
1307
+ if (this.filterCache.size >= currentMaxSize) {
1308
+ const oldestKey = this.filterCache.keys().next().value;
1309
+ if (oldestKey !== void 0) this.filterCache.delete(oldestKey);
1229
1310
  }
1230
1311
  this.filterCache.set(cacheKey, filtered);
1231
1312
  }
@@ -1252,36 +1333,7 @@ var ActionRegister = class {
1252
1333
  }
1253
1334
  async executePipeline(context, autoAbortController, autoAbortOptions) {
1254
1335
  const createController = (_registration, _index) => {
1255
- return {
1256
- abort: (reason) => {
1257
- context.aborted = true;
1258
- context.abortReason = reason;
1259
- if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
1260
- },
1261
- modifyPayload: (modifier) => {
1262
- context.payload = modifier(context.payload);
1263
- },
1264
- getPayload: () => context.payload,
1265
- jumpToPriority: (priority) => {
1266
- context.jumpToPriority = priority;
1267
- },
1268
- return: (result) => {
1269
- context.terminated = true;
1270
- context.terminationResult = result;
1271
- },
1272
- setResult: (result) => {
1273
- context.results.push(result);
1274
- },
1275
- getResults: () => {
1276
- return [...context.results];
1277
- },
1278
- mergeResult: (merger) => {
1279
- const currentResult = context.results[context.results.length - 1];
1280
- const previousResults = context.results.slice(0, -1);
1281
- const mergedResult = merger(previousResults, currentResult);
1282
- context.results[context.results.length - 1] = mergedResult;
1283
- }
1284
- };
1336
+ return this.getControllerFromPool(context, autoAbortController, autoAbortOptions);
1285
1337
  };
1286
1338
  switch (context.executionMode) {
1287
1339
  case "sequential":
@@ -1443,6 +1495,15 @@ var ActionRegister = class {
1443
1495
  return Array.from(this.pipelines.keys()).map((action) => this.getActionStats(action)).filter((stats) => stats !== null);
1444
1496
  }
1445
1497
  /**
1498
+ * Set global execution mode for all actions
1499
+ *
1500
+ * @param mode Execution mode to set
1501
+ */
1502
+ setExecutionMode(mode) {
1503
+ this.executionMode = mode;
1504
+ if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Global execution mode set to: ${mode}`);
1505
+ }
1506
+ /**
1446
1507
  * Set execution mode for a specific action
1447
1508
  *
1448
1509
  * @param action Action name
@@ -1487,6 +1548,55 @@ var ActionRegister = class {
1487
1548
  return this.isDebugMode;
1488
1549
  }
1489
1550
  /**
1551
+ * Creates a consistent unregister function for a handler
1552
+ *
1553
+ * @param action - Action key
1554
+ * @param handlerId - Handler identifier
1555
+ * @param registration - Handler registration object
1556
+ * @returns Unregister function
1557
+ * @private
1558
+ */
1559
+ createUnregisterFunction(action, handlerId, registration) {
1560
+ return () => {
1561
+ const pipeline = this.pipelines.get(action);
1562
+ if (!pipeline) return;
1563
+ const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
1564
+ if (index !== -1) {
1565
+ pipeline.splice(index, 1);
1566
+ this.invalidateFilterCache();
1567
+ this.unregisterFunctions.delete(handlerId);
1568
+ if (registration.config.cleanup && typeof registration.config.cleanup === "function") try {
1569
+ registration.config.cleanup();
1570
+ } catch (cleanupError) {
1571
+ this.log(`Cleanup error during unregister: ${String(action)}`, cleanupError, "warn");
1572
+ }
1573
+ this.log(`Handler unregistered: ${String(action)}`, {
1574
+ handlerId,
1575
+ remainingHandlers: pipeline.length
1576
+ });
1577
+ }
1578
+ };
1579
+ }
1580
+ /**
1581
+ * Gets the total count of registered unregister functions
1582
+ *
1583
+ * @returns Number of unregister functions
1584
+ * @public
1585
+ */
1586
+ getUnregisterFunctionCount() {
1587
+ return this.unregisterFunctions.size;
1588
+ }
1589
+ /**
1590
+ * Checks if an unregister function exists for the given handler ID
1591
+ *
1592
+ * @param handlerId - Handler identifier to check
1593
+ * @returns True if unregister function exists
1594
+ * @public
1595
+ */
1596
+ hasUnregisterFunction(handlerId) {
1597
+ return this.unregisterFunctions.has(handlerId);
1598
+ }
1599
+ /**
1490
1600
  * 🆕 Destroy method for comprehensive cleanup
1491
1601
  *
1492
1602
  * Cleans up all internal resources including pipelines, guards, queues, and statistics.
@@ -1495,11 +1605,20 @@ var ActionRegister = class {
1495
1605
  * @public
1496
1606
  */
1497
1607
  destroy() {
1608
+ this.unregisterFunctions.forEach((unregister) => {
1609
+ try {
1610
+ unregister();
1611
+ } catch (error) {
1612
+ this.log("Error during unregister in destroy", error, "error");
1613
+ }
1614
+ });
1615
+ this.unregisterFunctions.clear();
1498
1616
  this.pipelines.clear();
1499
1617
  this.actionGuard.destroy();
1500
1618
  this.dispatchQueue?.clear?.();
1501
1619
  this.actionExecutionModes.clear();
1502
1620
  this.filterCache.clear();
1621
+ this.controllerPool.length = 0;
1503
1622
  this.log("ActionRegister destroyed");
1504
1623
  }
1505
1624
  };
@@ -1576,7 +1695,17 @@ var ActionRegister = class {
1576
1695
  * @public
1577
1696
  */
1578
1697
  function createActionHandler(registry, action, handler, config) {
1579
- const finalConfig = createReactHandlerConfig(String(action), void 0, config);
1698
+ const timestamp = Date.now();
1699
+ const random = Math.random().toString(36).substr(2, 5);
1700
+ const finalConfig = {
1701
+ priority: config?.priority ?? 0,
1702
+ id: config?.id || `react_${String(action)}_${timestamp}_${random}`,
1703
+ blocking: config?.blocking ?? false,
1704
+ once: config?.once ?? false,
1705
+ debounce: config?.debounce ?? void 0,
1706
+ throttle: config?.throttle ?? void 0,
1707
+ replaceExisting: true
1708
+ };
1580
1709
  let currentUnregister;
1581
1710
  let isRegistered = false;
1582
1711
  return {
@@ -1604,96 +1733,6 @@ function createActionHandler(registry, action, handler, config) {
1604
1733
  };
1605
1734
  }
1606
1735
  /**
1607
- * 🆕 React handler configuration factory
1608
- *
1609
- * Creates optimized handler configurations for React environments with
1610
- * proper cleanup and unique ID generation.
1611
- *
1612
- * @template T - ActionPayloadMap type
1613
- * @template K - Action key type
1614
- *
1615
- * @param action - Action name
1616
- * @param componentId - Optional component identifier for debugging
1617
- * @param config - Base handler configuration
1618
- *
1619
- * @returns Optimized configuration for React environments
1620
- *
1621
- * @example
1622
- * ```tsx
1623
- * function MyComponent({ userId }: { userId: string }) {
1624
- * const registry = useActionRegister();
1625
- *
1626
- * useEffect(() => {
1627
- * const config = createReactHandlerConfig('updateUser', 'MyComponent', {
1628
- * priority: 10
1629
- * });
1630
- *
1631
- * const unregister = registry.register('updateUser', handler, config);
1632
- * return unregister;
1633
- * }, [registry, handler]);
1634
- * }
1635
- * ```
1636
- *
1637
- * @public
1638
- */
1639
- function createReactHandlerConfig(action, componentId, config = {}) {
1640
- const timestamp = Date.now();
1641
- const random = Math.random().toString(36).substr(2, 5);
1642
- return {
1643
- priority: config.priority ?? 0,
1644
- id: config.id || `${componentId || "react"}_${action}_${timestamp}_${random}`,
1645
- blocking: config.blocking ?? false,
1646
- once: config.once ?? false,
1647
- debounce: config.debounce ?? void 0,
1648
- throttle: config.throttle ?? void 0,
1649
- replaceExisting: true
1650
- };
1651
- }
1652
- /**
1653
- * 🆕 React action dispatcher factory
1654
- *
1655
- * Creates a dispatcher function optimized for React component usage
1656
- * with proper error boundaries and async handling.
1657
- *
1658
- * @template T - ActionPayloadMap type
1659
- *
1660
- * @param registry - ActionRegister instance
1661
- * @param errorHandler - Optional error handler for unhandled dispatch errors
1662
- *
1663
- * @returns Optimized dispatch function for React components
1664
- *
1665
- * @example
1666
- * ```tsx
1667
- * function MyComponent() {
1668
- * const registry = useActionRegister();
1669
- *
1670
- * const dispatch = createReactDispatcher(registry, (error, action, payload) => {
1671
- * console.error(`Failed to dispatch ${action}:`, error);
1672
- * });
1673
- *
1674
- * const handleClick = useCallback(() => {
1675
- * dispatch('userClick', { buttonId: 'submit' });
1676
- * }, [dispatch]);
1677
- * }
1678
- * ```
1679
- *
1680
- * @public
1681
- */
1682
- function createReactDispatcher(registry, errorHandler) {
1683
- return async (action, payload, options) => {
1684
- try {
1685
- await registry.dispatch(action, payload, {
1686
- immediate: false,
1687
- ...options
1688
- });
1689
- } catch (error) {
1690
- const errorObj = error instanceof Error ? error : new Error(String(error));
1691
- if (errorHandler) errorHandler(errorObj, action, payload);
1692
- else console.error(`[ActionRegister] Dispatch failed for action '${String(action)}':`, errorObj);
1693
- }
1694
- };
1695
- }
1696
- /**
1697
1736
  * 🆕 React development utilities
1698
1737
  *
1699
1738
  * Provides debugging and development helpers specifically for React environments.
@@ -1735,7 +1774,7 @@ const ReactDevUtils = {
1735
1774
  * Utilities for integrating ActionRegister errors with React Error Boundaries.
1736
1775
  */
1737
1776
  var ReactActionError = class ReactActionError extends Error {
1738
- constructor(message, action, payload, handlerId, originalError) {
1777
+ constructor(message, action, payload, handlerId = void 0, originalError) {
1739
1778
  super(message);
1740
1779
  this.name = "ReactActionError";
1741
1780
  this.action = action;
@@ -1767,8 +1806,6 @@ exports.ActionRegister = ActionRegister;
1767
1806
  exports.ReactActionError = ReactActionError;
1768
1807
  exports.ReactDevUtils = ReactDevUtils;
1769
1808
  exports.createActionHandler = createActionHandler;
1770
- exports.createReactDispatcher = createReactDispatcher;
1771
- exports.createReactHandlerConfig = createReactHandlerConfig;
1772
1809
  exports.executeParallel = executeParallel;
1773
1810
  exports.executeRace = executeRace;
1774
1811
  exports.executeSequential = executeSequential;