@context-action/core 0.7.3 → 0.7.4

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
@@ -49,6 +49,16 @@ async function executeSequential(context, createController) {
49
49
  const controller = createController(registration, i);
50
50
  try {
51
51
  if (context.aborted) break;
52
+ if (registration.config.condition) try {
53
+ const shouldExecute = registration.config.condition(context.payload);
54
+ if (!shouldExecute) {
55
+ i++;
56
+ continue;
57
+ }
58
+ } catch {
59
+ i++;
60
+ continue;
61
+ }
52
62
  const result = registration.handler(context.payload, controller);
53
63
  if (registration.config.blocking) {
54
64
  const handlerResult = result instanceof Promise ? await result : result;
@@ -73,8 +83,20 @@ async function executeSequential(context, createController) {
73
83
  if (context.terminated) break;
74
84
  /** Handle jump to priority AFTER handler execution */
75
85
  if (context.jumpToPriority !== void 0) {
76
- const jumpIndex = context.handlers.findIndex((handler) => handler.config.priority === context.jumpToPriority);
77
- if (jumpIndex !== -1) {
86
+ context.jumpCount = (context.jumpCount || 0) + 1;
87
+ if (context.jumpCount > (context.maxJumps || 10)) {
88
+ console.error(`[ActionRegister] ERROR: Maximum jump limit (${context.maxJumps || 10}) exceeded. Aborting to prevent infinite loop. Check your jumpToPriority logic and conditions.`);
89
+ context.aborted = true;
90
+ context.abortReason = `Maximum jump limit exceeded (${context.jumpCount} jumps)`;
91
+ context.jumpToPriority = void 0;
92
+ break;
93
+ }
94
+ const jumpIndex = context.handlers.findIndex((handler) => (handler.config.priority || 0) <= context.jumpToPriority);
95
+ if (jumpIndex !== -1 && jumpIndex !== i) {
96
+ if (jumpIndex < i) {
97
+ const targetHandler = context.handlers[jumpIndex];
98
+ if (targetHandler && !targetHandler.config.condition) console.warn(`[ActionRegister] WARNING: Backward jumpToPriority to handler '${targetHandler.config.id || "unnamed"}' without condition. This may cause infinite loops! Consider adding a condition to prevent re-execution. Jump count: ${context.jumpCount}/${context.maxJumps || 10}`);
99
+ }
78
100
  i = jumpIndex;
79
101
  context.jumpToPriority = void 0;
80
102
  continue;
@@ -85,7 +107,9 @@ async function executeSequential(context, createController) {
85
107
  } else i++;
86
108
  } catch (error) {
87
109
  const handlerError = handleExecutionError(error, registration);
88
- throw handlerError.error;
110
+ errors.push(handlerError);
111
+ if (registration.config.blocking) throw handlerError.error;
112
+ i++;
89
113
  }
90
114
  }
91
115
  if (nonBlockingPromises.length > 0) await Promise.allSettled(nonBlockingPromises);
@@ -125,6 +149,24 @@ async function executeParallel(context, createController) {
125
149
  const handlerPromises = runnableHandlers.map(async (registration, _index) => {
126
150
  const controller = createController(registration, _index);
127
151
  try {
152
+ if (registration.config.condition) try {
153
+ const shouldExecute = registration.config.condition(context.payload);
154
+ if (!shouldExecute) return {
155
+ success: true,
156
+ handlerId: registration.id,
157
+ result: void 0,
158
+ terminated: false,
159
+ skipped: true
160
+ };
161
+ } catch {
162
+ return {
163
+ success: true,
164
+ handlerId: registration.id,
165
+ result: void 0,
166
+ terminated: false,
167
+ skipped: true
168
+ };
169
+ }
128
170
  const result = registration.handler(context.payload, controller);
129
171
  let handlerResult;
130
172
  if (result instanceof Promise) {
@@ -199,6 +241,26 @@ async function executeRace(context, createController) {
199
241
  const handlerPromises = runnableHandlers.map(async (registration, _index) => {
200
242
  const controller = createController(registration, _index);
201
243
  try {
244
+ if (registration.config.condition) try {
245
+ const shouldExecute = registration.config.condition(context.payload);
246
+ if (!shouldExecute) return {
247
+ success: true,
248
+ handlerId: registration.id,
249
+ registration,
250
+ result: void 0,
251
+ terminated: false,
252
+ skipped: true
253
+ };
254
+ } catch {
255
+ return {
256
+ success: true,
257
+ handlerId: registration.id,
258
+ registration,
259
+ result: void 0,
260
+ terminated: false,
261
+ skipped: true
262
+ };
263
+ }
202
264
  const result = registration.handler(context.payload, controller);
203
265
  let handlerResult;
204
266
  if (result instanceof Promise) {
@@ -725,6 +787,7 @@ var ActionRegister = class {
725
787
  this.executionMode = "sequential";
726
788
  this.actionExecutionModes = /* @__PURE__ */ new Map();
727
789
  this.unregisterFunctions = /* @__PURE__ */ new Map();
790
+ this.lastRegisteredTimestamps = /* @__PURE__ */ new Map();
728
791
  this.filterCacheDisabled = true;
729
792
  this.handlerIdCounter = 0;
730
793
  this.controllerPool = [];
@@ -845,8 +908,9 @@ var ActionRegister = class {
845
908
  once: config.once ?? false,
846
909
  debounce: config.debounce ?? void 0,
847
910
  throttle: config.throttle ?? void 0,
848
- replaceExisting: config.replaceExisting ?? false,
849
- cleanup: config.cleanup
911
+ replaceExisting: config.replaceExisting ?? true,
912
+ cleanup: config.cleanup,
913
+ condition: config.condition
850
914
  },
851
915
  id: handlerId
852
916
  };
@@ -860,18 +924,16 @@ var ActionRegister = class {
860
924
  if (existingIndex !== -1) {
861
925
  const existing = pipeline[existingIndex];
862
926
  const existingUnregister = this.unregisterFunctions.get(handlerId);
863
- if (config.replaceExisting) {
864
- if (existingUnregister) {
865
- existingUnregister();
866
- this.unregisterFunctions.delete(handlerId);
867
- }
868
- if (existing && typeof existing.cleanup === "function") try {
869
- existing.cleanup();
927
+ if (registration.config.replaceExisting) {
928
+ if (existing && existing.config.cleanup && typeof existing.config.cleanup === "function") try {
929
+ existing.config.cleanup();
870
930
  } catch (cleanupError) {
871
931
  this.log(`Cleanup error for replaced handler: ${String(action)}`, cleanupError, "warn");
872
932
  }
933
+ if (existingUnregister) this.unregisterFunctions.delete(handlerId);
873
934
  pipeline[existingIndex] = registration;
874
935
  pipeline.sort((a, b) => b.config.priority - a.config.priority);
936
+ this.lastRegisteredTimestamps.set(action, /* @__PURE__ */ new Date());
875
937
  const newUnregister = this.createUnregisterFunction(action, handlerId, registration);
876
938
  this.unregisterFunctions.set(handlerId, newUnregister);
877
939
  this.log(`Handler replaced: ${String(action)}`, {
@@ -901,6 +963,7 @@ var ActionRegister = class {
901
963
  }
902
964
  pipeline.push(registration);
903
965
  pipeline.sort((a, b) => b.config.priority - a.config.priority);
966
+ this.lastRegisteredTimestamps.set(action, /* @__PURE__ */ new Date());
904
967
  const unregister = this.createUnregisterFunction(action, handlerId, registration);
905
968
  this.unregisterFunctions.set(handlerId, unregister);
906
969
  this.log(`Handler registered: ${String(action)}`, {
@@ -994,6 +1057,8 @@ var ActionRegister = class {
994
1057
  abortReason: void 0,
995
1058
  currentIndex: 0,
996
1059
  jumpToPriority: void 0,
1060
+ jumpCount: 0,
1061
+ maxJumps: 10,
997
1062
  executionMode: currentExecutionMode,
998
1063
  results: [],
999
1064
  terminated: false,
@@ -1085,6 +1150,8 @@ var ActionRegister = class {
1085
1150
  abortReason: void 0,
1086
1151
  currentIndex: 0,
1087
1152
  jumpToPriority: void 0,
1153
+ jumpCount: 0,
1154
+ maxJumps: 10,
1088
1155
  executionMode: currentExecutionMode,
1089
1156
  results: [],
1090
1157
  terminated: false,
@@ -1271,7 +1338,11 @@ var ActionRegister = class {
1271
1338
  if (autoAbortController && autoAbortOptions?.allowHandlerAbort) autoAbortController.abort(reason);
1272
1339
  };
1273
1340
  controller.modifyPayload = (modifier) => {
1274
- context.payload = modifier(context.payload);
1341
+ try {
1342
+ context.payload = modifier(context.payload);
1343
+ } catch (modificationError) {
1344
+ this.log("Payload modification error", modificationError, "warn");
1345
+ }
1275
1346
  };
1276
1347
  controller.getPayload = () => context.payload;
1277
1348
  controller.jumpToPriority = (priority) => {
@@ -1320,9 +1391,10 @@ var ActionRegister = class {
1320
1391
  return filtered;
1321
1392
  }
1322
1393
  processResults(context, resultOptions) {
1323
- if (!resultOptions || !resultOptions.collect) return void 0;
1324
1394
  const results = context.results;
1325
1395
  if (context.terminated && context.terminationResult !== void 0) return context.terminationResult;
1396
+ if (!resultOptions) return results.length > 0 ? results[results.length - 1] : void 0;
1397
+ if (!resultOptions.collect && !resultOptions.strategy) return void 0;
1326
1398
  const limitedResults = resultOptions.maxResults ? results.slice(0, resultOptions.maxResults) : results;
1327
1399
  if (limitedResults.length === 0) return void 0;
1328
1400
  switch (resultOptions.strategy) {
@@ -1335,7 +1407,9 @@ var ActionRegister = class {
1335
1407
  case "custom":
1336
1408
  if (resultOptions.merger) return resultOptions.merger(limitedResults);
1337
1409
  throw new Error("Custom result strategy requires a merger function");
1338
- default: return limitedResults;
1410
+ default:
1411
+ if (resultOptions.collect) return limitedResults;
1412
+ return limitedResults[limitedResults.length - 1];
1339
1413
  }
1340
1414
  }
1341
1415
  async executePipeline(context, autoAbortController, autoAbortOptions) {
@@ -1372,6 +1446,10 @@ var ActionRegister = class {
1372
1446
  });
1373
1447
  }
1374
1448
  });
1449
+ if (pipeline.length === 0) {
1450
+ this.pipelines.delete(action);
1451
+ this.lastRegisteredTimestamps.delete(action);
1452
+ }
1375
1453
  }
1376
1454
  /**
1377
1455
  * Get the number of registered handlers for an action
@@ -1425,6 +1503,7 @@ var ActionRegister = class {
1425
1503
  */
1426
1504
  clearAction(action) {
1427
1505
  this.pipelines.delete(action);
1506
+ this.lastRegisteredTimestamps.delete(action);
1428
1507
  }
1429
1508
  /**
1430
1509
  * Remove all handlers for all actions
@@ -1435,6 +1514,7 @@ var ActionRegister = class {
1435
1514
  */
1436
1515
  clearAll() {
1437
1516
  this.pipelines.clear();
1517
+ this.lastRegisteredTimestamps.clear();
1438
1518
  }
1439
1519
  /**
1440
1520
  * Get the name of this action register
@@ -1488,7 +1568,8 @@ var ActionRegister = class {
1488
1568
  handlerCount: pipeline.length,
1489
1569
  totalHandlers: pipeline.length,
1490
1570
  handlersByPriority,
1491
- executionStats
1571
+ executionStats,
1572
+ lastRegistered: this.lastRegisteredTimestamps.get(action)
1492
1573
  };
1493
1574
  }
1494
1575
  /**
@@ -1569,6 +1650,10 @@ var ActionRegister = class {
1569
1650
  if (index !== -1) {
1570
1651
  pipeline.splice(index, 1);
1571
1652
  this.unregisterFunctions.delete(handlerId);
1653
+ if (pipeline.length === 0) {
1654
+ this.pipelines.delete(action);
1655
+ this.lastRegisteredTimestamps.delete(action);
1656
+ }
1572
1657
  if (registration.config.cleanup && typeof registration.config.cleanup === "function") try {
1573
1658
  registration.config.cleanup();
1574
1659
  } catch (cleanupError) {
@@ -1576,7 +1661,8 @@ var ActionRegister = class {
1576
1661
  }
1577
1662
  this.log(`Handler unregistered: ${String(action)}`, {
1578
1663
  handlerId,
1579
- remainingHandlers: pipeline.length
1664
+ remainingHandlers: pipeline.length,
1665
+ actionRemoved: pipeline.length === 0
1580
1666
  });
1581
1667
  }
1582
1668
  };
@@ -1609,15 +1695,14 @@ var ActionRegister = class {
1609
1695
  * @public
1610
1696
  */
1611
1697
  destroy() {
1612
- this.unregisterFunctions.forEach((unregister) => {
1613
- try {
1614
- unregister();
1615
- } catch (error) {
1616
- this.log("Error during unregister in destroy", error, "error");
1617
- }
1618
- });
1619
1698
  this.unregisterFunctions.clear();
1699
+ for (const [action, pipeline] of this.pipelines.entries()) for (const registration of pipeline) if (registration.config.cleanup && typeof registration.config.cleanup === "function") try {
1700
+ registration.config.cleanup();
1701
+ } catch (cleanupError) {
1702
+ this.log(`Cleanup error for handler during destroy: ${String(action)}`, cleanupError, "warn");
1703
+ }
1620
1704
  this.pipelines.clear();
1705
+ this.lastRegisteredTimestamps.clear();
1621
1706
  this.actionGuard.destroy();
1622
1707
  this.dispatchQueue?.clear?.();
1623
1708
  this.actionExecutionModes.clear();