@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.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;
@@ -1016,6 +1024,7 @@ var ActionRegister = class {
1016
1024
  if (!pipeline || pipeline.length === 0) return {
1017
1025
  success: true,
1018
1026
  aborted: false,
1027
+ abortReason: void 0,
1019
1028
  terminated: false,
1020
1029
  result: void 0,
1021
1030
  successResults: [],
@@ -1034,68 +1043,8 @@ var ActionRegister = class {
1034
1043
  };
1035
1044
  const filteredHandlers = options?.filter ? this.filterHandlers(pipeline, options.filter) : pipeline;
1036
1045
  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
- }
1046
+ const guardResult = await this.applyActionGuardControlsWithResult(actionKey, filteredHandlers, options, _startTime, pipeline.length);
1047
+ if (guardResult) return guardResult;
1099
1048
  const currentExecutionMode = options?.executionMode || this.actionExecutionModes.get(action) || this.executionMode;
1100
1049
  const context = {
1101
1050
  action: String(action),
@@ -1112,11 +1061,14 @@ var ActionRegister = class {
1112
1061
  };
1113
1062
  let executionError;
1114
1063
  const handlerResults = [];
1115
- const errors = [];
1116
1064
  filteredHandlers.forEach((handler) => {
1117
1065
  handlerResults.push({
1118
1066
  id: handler.config.id,
1119
- executed: false
1067
+ executed: false,
1068
+ duration: void 0,
1069
+ result: void 0,
1070
+ error: void 0,
1071
+ metadata: void 0
1120
1072
  });
1121
1073
  });
1122
1074
  const abortHandler = effectiveSignal ? () => {
@@ -1124,23 +1076,33 @@ var ActionRegister = class {
1124
1076
  context.abortReason = "Action dispatch aborted by signal";
1125
1077
  } : void 0;
1126
1078
  if (effectiveSignal && abortHandler) effectiveSignal.addEventListener("abort", abortHandler);
1079
+ let errors = [];
1127
1080
  try {
1128
1081
  await this.executePipeline(context, autoAbortController, options?.autoAbort);
1082
+ const contextWithErrors = context;
1083
+ errors = contextWithErrors.collectedErrors || [];
1129
1084
  const executedCount = Math.min(context.currentIndex + (context.aborted ? 0 : 1), filteredHandlers.length);
1130
1085
  for (let i = 0; i < executedCount; i++) {
1131
- const handlerResult = handlerResults.find((hr) => hr.id === filteredHandlers[i].config.id);
1086
+ const handler = filteredHandlers[i];
1087
+ if (!handler) continue;
1088
+ const handlerResult = handlerResults.find((hr) => hr.id === handler.config.id);
1132
1089
  if (handlerResult) handlerResult.executed = true;
1133
1090
  }
1134
1091
  } catch (error) {
1092
+ const contextWithErrors = context;
1093
+ errors = contextWithErrors.collectedErrors || [];
1135
1094
  executionError = error instanceof Error ? error : new Error(String(error));
1136
1095
  errors.push({
1137
1096
  handlerId: "pipeline",
1138
1097
  error: executionError,
1139
- timestamp: Date.now()
1098
+ timestamp: Date.now(),
1099
+ severity: "blocking"
1140
1100
  });
1141
1101
  const executedCount = Math.min(context.currentIndex + 1, filteredHandlers.length);
1142
1102
  for (let i = 0; i < executedCount; i++) {
1143
- const handlerResult = handlerResults.find((hr) => hr.id === filteredHandlers[i].config.id);
1103
+ const handler = filteredHandlers[i];
1104
+ if (!handler) continue;
1105
+ const handlerResult = handlerResults.find((hr) => hr.id === handler.config.id);
1144
1106
  if (handlerResult) handlerResult.executed = true;
1145
1107
  }
1146
1108
  } finally {
@@ -1165,7 +1127,7 @@ var ActionRegister = class {
1165
1127
  failedResults,
1166
1128
  execution: {
1167
1129
  duration: endTime - _startTime,
1168
- handlersExecuted: context.currentIndex + (context.aborted ? 0 : 1),
1130
+ handlersExecuted: filteredHandlers.length === 0 ? 0 : context.currentIndex + (context.aborted ? 0 : 1),
1169
1131
  handlersSkipped: Math.max(0, filteredHandlers.length - (context.currentIndex + 1)),
1170
1132
  handlersFailed: errors.length,
1171
1133
  startTime: _startTime,
@@ -1184,6 +1146,82 @@ var ActionRegister = class {
1184
1146
  return executionResult;
1185
1147
  }
1186
1148
  /**
1149
+ * 🔧 Unified method for dispatchWithResult that returns ExecutionResult on guard rejection
1150
+ */
1151
+ async applyActionGuardControlsWithResult(actionKey, filteredHandlers, options, startTime, pipelineLength) {
1152
+ let throttleMs;
1153
+ let debounceMs;
1154
+ if (options?.throttle !== void 0) throttleMs = options.throttle;
1155
+ else if (filteredHandlers.length > 0) {
1156
+ for (const handler of filteredHandlers) if (handler.config.throttle !== void 0) {
1157
+ throttleMs = handler.config.throttle;
1158
+ break;
1159
+ }
1160
+ }
1161
+ if (options?.debounce !== void 0) debounceMs = options.debounce;
1162
+ else if (filteredHandlers.length > 0) {
1163
+ for (const handler of filteredHandlers) if (handler.config.debounce !== void 0) {
1164
+ debounceMs = handler.config.debounce;
1165
+ break;
1166
+ }
1167
+ }
1168
+ if (debounceMs !== void 0) {
1169
+ const shouldProceed = await this.actionGuard.debounce(actionKey, debounceMs);
1170
+ if (!shouldProceed) return {
1171
+ success: false,
1172
+ aborted: true,
1173
+ abortReason: "Debounced execution",
1174
+ terminated: false,
1175
+ result: void 0,
1176
+ successResults: [],
1177
+ results: [],
1178
+ failedResults: [],
1179
+ execution: {
1180
+ duration: Date.now() - startTime,
1181
+ handlersExecuted: 0,
1182
+ handlersSkipped: pipelineLength,
1183
+ handlersFailed: 0,
1184
+ startTime,
1185
+ endTime: Date.now()
1186
+ },
1187
+ handlers: [],
1188
+ errors: []
1189
+ };
1190
+ }
1191
+ if (throttleMs !== void 0) {
1192
+ const shouldProceed = this.actionGuard.throttle(actionKey, throttleMs);
1193
+ if (!shouldProceed) return {
1194
+ success: false,
1195
+ aborted: true,
1196
+ abortReason: "Throttled execution",
1197
+ terminated: false,
1198
+ result: void 0,
1199
+ successResults: [],
1200
+ results: [],
1201
+ failedResults: [],
1202
+ execution: {
1203
+ duration: Date.now() - startTime,
1204
+ handlersExecuted: 0,
1205
+ handlersSkipped: pipelineLength,
1206
+ handlersFailed: 0,
1207
+ startTime,
1208
+ endTime: Date.now()
1209
+ },
1210
+ handlers: [],
1211
+ errors: []
1212
+ };
1213
+ }
1214
+ return null;
1215
+ }
1216
+ /**
1217
+ * 🔧 Calculate optimal cache size based on current handler count
1218
+ * Note: This caches handler SELECTION metadata only, never execution results
1219
+ */
1220
+ get filterCacheMaxSize() {
1221
+ const totalHandlers = Array.from(this.pipelines.values()).reduce((sum, pipeline) => sum + pipeline.length, 0);
1222
+ return totalHandlers * 10 || 100;
1223
+ }
1224
+ /**
1187
1225
  * 🔧 Generate cache key for filter options
1188
1226
  */
1189
1227
  generateFilterCacheKey(filterOptions) {
@@ -1203,6 +1241,48 @@ var ActionRegister = class {
1203
1241
  invalidateFilterCache() {
1204
1242
  this.filterCache.clear();
1205
1243
  }
1244
+ /**
1245
+ * 🔧 Create or reuse PipelineController from pool for better performance
1246
+ */
1247
+ getControllerFromPool(context, autoAbortController, autoAbortOptions) {
1248
+ let controller = this.controllerPool.pop();
1249
+ if (!controller) controller = {};
1250
+ controller.abort = (reason) => {
1251
+ context.aborted = true;
1252
+ context.abortReason = reason;
1253
+ if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
1254
+ };
1255
+ controller.modifyPayload = (modifier) => {
1256
+ context.payload = modifier(context.payload);
1257
+ };
1258
+ controller.getPayload = () => context.payload;
1259
+ controller.jumpToPriority = (priority) => {
1260
+ context.jumpToPriority = priority;
1261
+ };
1262
+ controller.return = (result) => {
1263
+ context.terminated = true;
1264
+ context.terminationResult = result;
1265
+ };
1266
+ controller.setResult = (result) => {
1267
+ context.results.push(result);
1268
+ };
1269
+ controller.getResults = () => {
1270
+ return [...context.results];
1271
+ };
1272
+ controller.mergeResult = (merger) => {
1273
+ const currentResult = context.results[context.results.length - 1];
1274
+ const previousResults = context.results.slice(0, -1);
1275
+ const mergedResult = merger(previousResults, currentResult);
1276
+ context.results[context.results.length - 1] = mergedResult;
1277
+ };
1278
+ return controller;
1279
+ }
1280
+ /**
1281
+ * 🔧 Return controller to pool for reuse
1282
+ */
1283
+ returnControllerToPool(controller) {
1284
+ if (this.controllerPool.length < this.maxControllerPoolSize) this.controllerPool.push(controller);
1285
+ }
1206
1286
  filterHandlers(handlers, filterOptions) {
1207
1287
  if (!filterOptions) return handlers;
1208
1288
  const cacheKey = this.generateFilterCacheKey(filterOptions);
@@ -1222,9 +1302,10 @@ var ActionRegister = class {
1222
1302
  return true;
1223
1303
  });
1224
1304
  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);
1305
+ const currentMaxSize = this.filterCacheMaxSize;
1306
+ if (this.filterCache.size >= currentMaxSize) {
1307
+ const oldestKey = this.filterCache.keys().next().value;
1308
+ if (oldestKey !== void 0) this.filterCache.delete(oldestKey);
1228
1309
  }
1229
1310
  this.filterCache.set(cacheKey, filtered);
1230
1311
  }
@@ -1251,36 +1332,7 @@ var ActionRegister = class {
1251
1332
  }
1252
1333
  async executePipeline(context, autoAbortController, autoAbortOptions) {
1253
1334
  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
- };
1335
+ return this.getControllerFromPool(context, autoAbortController, autoAbortOptions);
1284
1336
  };
1285
1337
  switch (context.executionMode) {
1286
1338
  case "sequential":
@@ -1442,6 +1494,15 @@ var ActionRegister = class {
1442
1494
  return Array.from(this.pipelines.keys()).map((action) => this.getActionStats(action)).filter((stats) => stats !== null);
1443
1495
  }
1444
1496
  /**
1497
+ * Set global execution mode for all actions
1498
+ *
1499
+ * @param mode Execution mode to set
1500
+ */
1501
+ setExecutionMode(mode) {
1502
+ this.executionMode = mode;
1503
+ if (this.registryConfig?.debug && process.env.NODE_ENV === "development") console.log(`🎯 Global execution mode set to: ${mode}`);
1504
+ }
1505
+ /**
1445
1506
  * Set execution mode for a specific action
1446
1507
  *
1447
1508
  * @param action Action name
@@ -1486,6 +1547,55 @@ var ActionRegister = class {
1486
1547
  return this.isDebugMode;
1487
1548
  }
1488
1549
  /**
1550
+ * Creates a consistent unregister function for a handler
1551
+ *
1552
+ * @param action - Action key
1553
+ * @param handlerId - Handler identifier
1554
+ * @param registration - Handler registration object
1555
+ * @returns Unregister function
1556
+ * @private
1557
+ */
1558
+ createUnregisterFunction(action, handlerId, registration) {
1559
+ return () => {
1560
+ const pipeline = this.pipelines.get(action);
1561
+ if (!pipeline) return;
1562
+ const index = pipeline.findIndex((reg) => reg.id === handlerId && reg === registration);
1563
+ if (index !== -1) {
1564
+ pipeline.splice(index, 1);
1565
+ this.invalidateFilterCache();
1566
+ this.unregisterFunctions.delete(handlerId);
1567
+ if (registration.config.cleanup && typeof registration.config.cleanup === "function") try {
1568
+ registration.config.cleanup();
1569
+ } catch (cleanupError) {
1570
+ this.log(`Cleanup error during unregister: ${String(action)}`, cleanupError, "warn");
1571
+ }
1572
+ this.log(`Handler unregistered: ${String(action)}`, {
1573
+ handlerId,
1574
+ remainingHandlers: pipeline.length
1575
+ });
1576
+ }
1577
+ };
1578
+ }
1579
+ /**
1580
+ * Gets the total count of registered unregister functions
1581
+ *
1582
+ * @returns Number of unregister functions
1583
+ * @public
1584
+ */
1585
+ getUnregisterFunctionCount() {
1586
+ return this.unregisterFunctions.size;
1587
+ }
1588
+ /**
1589
+ * Checks if an unregister function exists for the given handler ID
1590
+ *
1591
+ * @param handlerId - Handler identifier to check
1592
+ * @returns True if unregister function exists
1593
+ * @public
1594
+ */
1595
+ hasUnregisterFunction(handlerId) {
1596
+ return this.unregisterFunctions.has(handlerId);
1597
+ }
1598
+ /**
1489
1599
  * 🆕 Destroy method for comprehensive cleanup
1490
1600
  *
1491
1601
  * Cleans up all internal resources including pipelines, guards, queues, and statistics.
@@ -1494,11 +1604,20 @@ var ActionRegister = class {
1494
1604
  * @public
1495
1605
  */
1496
1606
  destroy() {
1607
+ this.unregisterFunctions.forEach((unregister) => {
1608
+ try {
1609
+ unregister();
1610
+ } catch (error) {
1611
+ this.log("Error during unregister in destroy", error, "error");
1612
+ }
1613
+ });
1614
+ this.unregisterFunctions.clear();
1497
1615
  this.pipelines.clear();
1498
1616
  this.actionGuard.destroy();
1499
1617
  this.dispatchQueue?.clear?.();
1500
1618
  this.actionExecutionModes.clear();
1501
1619
  this.filterCache.clear();
1620
+ this.controllerPool.length = 0;
1502
1621
  this.log("ActionRegister destroyed");
1503
1622
  }
1504
1623
  };
@@ -1575,7 +1694,17 @@ var ActionRegister = class {
1575
1694
  * @public
1576
1695
  */
1577
1696
  function createActionHandler(registry, action, handler, config) {
1578
- const finalConfig = createReactHandlerConfig(String(action), void 0, config);
1697
+ const timestamp = Date.now();
1698
+ const random = Math.random().toString(36).substr(2, 5);
1699
+ const finalConfig = {
1700
+ priority: config?.priority ?? 0,
1701
+ id: config?.id || `react_${String(action)}_${timestamp}_${random}`,
1702
+ blocking: config?.blocking ?? false,
1703
+ once: config?.once ?? false,
1704
+ debounce: config?.debounce ?? void 0,
1705
+ throttle: config?.throttle ?? void 0,
1706
+ replaceExisting: true
1707
+ };
1579
1708
  let currentUnregister;
1580
1709
  let isRegistered = false;
1581
1710
  return {
@@ -1603,96 +1732,6 @@ function createActionHandler(registry, action, handler, config) {
1603
1732
  };
1604
1733
  }
1605
1734
  /**
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
1735
  * 🆕 React development utilities
1697
1736
  *
1698
1737
  * Provides debugging and development helpers specifically for React environments.
@@ -1734,7 +1773,7 @@ const ReactDevUtils = {
1734
1773
  * Utilities for integrating ActionRegister errors with React Error Boundaries.
1735
1774
  */
1736
1775
  var ReactActionError = class ReactActionError extends Error {
1737
- constructor(message, action, payload, handlerId, originalError) {
1776
+ constructor(message, action, payload, handlerId = void 0, originalError) {
1738
1777
  super(message);
1739
1778
  this.name = "ReactActionError";
1740
1779
  this.action = action;
@@ -1761,5 +1800,5 @@ function isReactActionError(error) {
1761
1800
  }
1762
1801
 
1763
1802
  //#endregion
1764
- export { ActionGuard, ActionRegister, ReactActionError, ReactDevUtils, createActionHandler, createReactDispatcher, createReactHandlerConfig, executeParallel, executeRace, executeSequential, isReactActionError };
1803
+ export { ActionGuard, ActionRegister, ReactActionError, ReactDevUtils, createActionHandler, executeParallel, executeRace, executeSequential, isReactActionError };
1765
1804
  //# sourceMappingURL=index.js.map