aws-local-stepfunctions 1.1.0 → 1.3.0

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.
@@ -97,6 +97,8 @@ function isPlainObj(value) {
97
97
  return !!value && Object.getPrototypeOf(value) === Object.prototype;
98
98
  }
99
99
  function sleep(ms, abortSignal) {
100
+ if (ms === 0)
101
+ return;
100
102
  return new Promise((resolve) => {
101
103
  if (abortSignal?.aborted) {
102
104
  return resolve();
@@ -122,6 +124,13 @@ function isRFC3339Timestamp(date) {
122
124
  function stringifyJSONValue(value) {
123
125
  return JSON.stringify(value);
124
126
  }
127
+ function clamp(value, min, max) {
128
+ if (min && value < min)
129
+ return min;
130
+ if (max && value > max)
131
+ return max;
132
+ return value;
133
+ }
125
134
 
126
135
  // src/stateMachine/jsonPath/JsonPath.ts
127
136
  import { JSONPath as jp } from "jsonpath-plus";
@@ -868,9 +877,18 @@ function processInputPath(path, input, context) {
868
877
  return jsonPathQuery(path, input, context);
869
878
  }
870
879
  function processPayloadTemplate(payloadTemplate, json, context) {
880
+ if (typeof payloadTemplate !== "object" || payloadTemplate === null) {
881
+ return payloadTemplate;
882
+ }
883
+ if (Array.isArray(payloadTemplate)) {
884
+ return payloadTemplate.map((value) => processPayloadTemplate(value, json, context));
885
+ }
871
886
  const resolvedProperties = Object.entries(payloadTemplate).map(([key, value]) => {
872
887
  let sanitizedKey = key;
873
888
  let resolvedValue = value;
889
+ if (Array.isArray(value)) {
890
+ resolvedValue = value.map((innerValue) => processPayloadTemplate(innerValue, json, context));
891
+ }
874
892
  if (isPlainObj(value)) {
875
893
  resolvedValue = processPayloadTemplate(value, json, context);
876
894
  }
@@ -1255,7 +1273,20 @@ var FailStateAction = class extends BaseStateAction {
1255
1273
  super(stateDefinition, stateName);
1256
1274
  }
1257
1275
  async execute(input, context, options) {
1258
- throw new FailStateError(this.stateDefinition.Error, this.stateDefinition.Cause);
1276
+ const state = this.stateDefinition;
1277
+ let error = state.Error;
1278
+ let cause = state.Cause;
1279
+ if (state.ErrorPath) {
1280
+ error = jsonPathQuery(state.ErrorPath, input, context, {
1281
+ constraints: [StringConstraint]
1282
+ });
1283
+ }
1284
+ if (state.CausePath) {
1285
+ cause = jsonPathQuery(state.CausePath, input, context, {
1286
+ constraints: [StringConstraint]
1287
+ });
1288
+ }
1289
+ throw new FailStateError(error, cause);
1259
1290
  }
1260
1291
  };
1261
1292
 
@@ -1270,6 +1301,31 @@ var ArrayConstraint = class extends BaseJSONPathConstraint {
1270
1301
  }
1271
1302
  };
1272
1303
 
1304
+ // src/stateMachine/jsonPath/constraints/IntegerConstraint.ts
1305
+ var IntegerConstraint = class extends BaseJSONPathConstraint {
1306
+ test(value) {
1307
+ if (!Number.isInteger(value)) {
1308
+ throw new StatesRuntimeError(
1309
+ `Path expression '${this.pathExpression}' evaluated to ${stringifyJSONValue(value)}, but expected an integer`
1310
+ );
1311
+ }
1312
+ }
1313
+ static greaterThanOrEqual(n) {
1314
+ return class extends this {
1315
+ test(value) {
1316
+ super.test(value);
1317
+ if (value < n) {
1318
+ throw new StatesRuntimeError(
1319
+ `Path expression '${this.pathExpression}' evaluated to ${stringifyJSONValue(
1320
+ value
1321
+ )}, but expected an integer >= ${n}`
1322
+ );
1323
+ }
1324
+ }
1325
+ };
1326
+ }
1327
+ };
1328
+
1273
1329
  // src/stateMachine/stateActions/MapStateAction.ts
1274
1330
  import pLimit from "p-limit";
1275
1331
  var DEFAULT_MAX_CONCURRENCY = 40;
@@ -1294,8 +1350,9 @@ var MapStateAction = class extends BaseStateAction {
1294
1350
  Value: item
1295
1351
  }
1296
1352
  };
1297
- if (state.Parameters) {
1298
- paramValue = processPayloadTemplate(state.Parameters, input, context);
1353
+ if (state.Parameters || state.ItemSelector) {
1354
+ const parameters = state.ItemSelector ?? state.Parameters;
1355
+ paramValue = processPayloadTemplate(parameters, input, context);
1299
1356
  }
1300
1357
  const execution = stateMachine.run(paramValue ?? item, options.runOptions);
1301
1358
  this.executionAbortFuncs.push(execution.abort);
@@ -1313,11 +1370,19 @@ var MapStateAction = class extends BaseStateAction {
1313
1370
  if (!Array.isArray(items)) {
1314
1371
  throw new StatesRuntimeError("Input of Map state must be an array or ItemsPath property must point to an array");
1315
1372
  }
1316
- const iteratorStateMachine = new StateMachine(state.Iterator, {
1373
+ const iteratorDefinition = state.ItemProcessor ?? state.Iterator;
1374
+ const iteratorStateMachine = new StateMachine(iteratorDefinition, {
1317
1375
  ...options.stateMachineOptions,
1318
1376
  validationOptions: { noValidate: true }
1319
1377
  });
1320
- const limit = pLimit(state.MaxConcurrency || DEFAULT_MAX_CONCURRENCY);
1378
+ let maxConcurrency = state.MaxConcurrency;
1379
+ if (state.MaxConcurrencyPath) {
1380
+ maxConcurrency = jsonPathQuery(state.MaxConcurrencyPath, input, context, {
1381
+ constraints: [IntegerConstraint.greaterThanOrEqual(0)],
1382
+ ignoreDefinedValueConstraint: true
1383
+ });
1384
+ }
1385
+ const limit = pLimit(maxConcurrency || DEFAULT_MAX_CONCURRENCY);
1321
1386
  const processedItemsPromise = items.map(
1322
1387
  (item, i) => limit(() => this.processItem(iteratorStateMachine, item, input, context, i, options))
1323
1388
  );
@@ -1403,6 +1468,14 @@ var SucceedStateAction = class extends BaseStateAction {
1403
1468
  }
1404
1469
  };
1405
1470
 
1471
+ // src/error/predefined/StatesTimeoutError.ts
1472
+ var StatesTimeoutError = class extends RuntimeError {
1473
+ constructor() {
1474
+ super("States.Timeout");
1475
+ this.name = "States.Timeout";
1476
+ }
1477
+ };
1478
+
1406
1479
  // src/aws/LambdaClient.ts
1407
1480
  import { LambdaClient as AWSLambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";
1408
1481
  import { fromCognitoIdentityPool } from "@aws-sdk/credential-providers";
@@ -1486,41 +1559,47 @@ var LambdaClient = class {
1486
1559
  var TaskStateAction = class extends BaseStateAction {
1487
1560
  constructor(stateDefinition, stateName) {
1488
1561
  super(stateDefinition, stateName);
1562
+ this.timeoutAbortController = new AbortController();
1563
+ }
1564
+ createTimeoutPromise(input, context) {
1565
+ const state = this.stateDefinition;
1566
+ if (!state.TimeoutSeconds && !state.TimeoutSecondsPath)
1567
+ return;
1568
+ let timeout;
1569
+ if (state.TimeoutSeconds) {
1570
+ timeout = state.TimeoutSeconds;
1571
+ } else if (state.TimeoutSecondsPath) {
1572
+ timeout = jsonPathQuery(state.TimeoutSecondsPath, input, context, {
1573
+ constraints: [IntegerConstraint.greaterThanOrEqual(1)]
1574
+ });
1575
+ }
1576
+ return new Promise((_, reject) => {
1577
+ const handleTimeoutAbort = () => clearTimeout(timeoutId);
1578
+ const timeoutId = setTimeout(() => {
1579
+ this.timeoutAbortController.signal.removeEventListener("abort", handleTimeoutAbort);
1580
+ reject(new StatesTimeoutError());
1581
+ }, timeout * 1e3);
1582
+ this.timeoutAbortController.signal.addEventListener("abort", handleTimeoutAbort, { once: true });
1583
+ });
1489
1584
  }
1490
1585
  async execute(input, context, options) {
1491
1586
  const state = this.stateDefinition;
1587
+ const racingPromises = [];
1588
+ const timeoutPromise = this.createTimeoutPromise(input, context);
1492
1589
  if (options.overrideFn) {
1493
- const result2 = await options.overrideFn(input);
1494
- return this.buildExecutionResult(result2);
1590
+ const resultPromise = options.overrideFn(input);
1591
+ racingPromises.push(resultPromise);
1592
+ } else {
1593
+ const lambdaClient = new LambdaClient(options.awsConfig);
1594
+ const resultPromise = lambdaClient.invokeFunction(state.Resource, input);
1595
+ racingPromises.push(resultPromise);
1495
1596
  }
1496
- const lambdaClient = new LambdaClient(options.awsConfig);
1497
- const result = await lambdaClient.invokeFunction(state.Resource, input);
1498
- return this.buildExecutionResult(result);
1499
- }
1500
- };
1501
-
1502
- // src/stateMachine/jsonPath/constraints/IntegerConstraint.ts
1503
- var IntegerConstraint = class extends BaseJSONPathConstraint {
1504
- test(value) {
1505
- if (!Number.isInteger(value)) {
1506
- throw new StatesRuntimeError(
1507
- `Path expression '${this.pathExpression}' evaluated to ${stringifyJSONValue(value)}, but expected an integer`
1508
- );
1597
+ if (timeoutPromise) {
1598
+ racingPromises.push(timeoutPromise);
1509
1599
  }
1510
- }
1511
- static greaterThanOrEqual(n) {
1512
- return class extends this {
1513
- test(value) {
1514
- super.test(value);
1515
- if (value < n) {
1516
- throw new StatesRuntimeError(
1517
- `Path expression '${this.pathExpression}' evaluated to ${stringifyJSONValue(
1518
- value
1519
- )}, but expected an integer >= ${n}`
1520
- );
1521
- }
1522
- }
1523
- };
1600
+ const result = await Promise.race(racingPromises);
1601
+ this.timeoutAbortController.abort();
1602
+ return this.buildExecutionResult(result);
1524
1603
  }
1525
1604
  };
1526
1605
 
@@ -1560,19 +1639,12 @@ var WaitStateAction = class extends BaseStateAction {
1560
1639
  }
1561
1640
  };
1562
1641
 
1563
- // src/error/predefined/StatesTimeoutError.ts
1564
- var StatesTimeoutError = class extends RuntimeError {
1565
- constructor() {
1566
- super("States.Timeout");
1567
- this.name = "States.Timeout";
1568
- }
1569
- };
1570
-
1571
1642
  // src/stateMachine/StateExecutor.ts
1572
1643
  import cloneDeep2 from "lodash/cloneDeep.js";
1573
1644
  var DEFAULT_MAX_ATTEMPTS = 3;
1574
1645
  var DEFAULT_INTERVAL_SECONDS = 1;
1575
1646
  var DEFAULT_BACKOFF_RATE = 2;
1647
+ var DEFAULT_JITTER_STRATEGY = "NONE";
1576
1648
  var WILDCARD_ERROR = "States.ALL";
1577
1649
  var TASK_STATE_WILDCARD_ERROR = "States.TaskFailed";
1578
1650
  var StateExecutor = class {
@@ -1620,7 +1692,10 @@ var StateExecutor = class {
1620
1692
  input,
1621
1693
  error
1622
1694
  );
1623
- const { shouldRetry, waitTimeBeforeRetry, retrierIndex } = this.shouldRetry(error);
1695
+ const { shouldRetry, waitTimeBeforeRetry, retrierIndex } = this.shouldRetry(
1696
+ error,
1697
+ options.runOptions?.overrides?.retryIntervalOverrides
1698
+ );
1624
1699
  if (shouldRetry) {
1625
1700
  const stateDefinition = this.stateDefinition;
1626
1701
  await sleep(waitTimeBeforeRetry, options.abortSignal);
@@ -1679,17 +1754,31 @@ var StateExecutor = class {
1679
1754
  /**
1680
1755
  * Decide whether this state should be retried, according to the `Retry` field.
1681
1756
  */
1682
- shouldRetry(error) {
1757
+ shouldRetry(error, retryIntervalOverrides) {
1683
1758
  if (!("Retry" in this.stateDefinition)) {
1684
1759
  return { shouldRetry: false };
1685
1760
  }
1686
1761
  for (let i = 0; i < this.stateDefinition.Retry.length; i++) {
1687
1762
  const retrier = this.stateDefinition.Retry[i];
1763
+ let intervalOverride = null;
1764
+ if (retryIntervalOverrides?.[this.stateName] !== void 0) {
1765
+ const override = retryIntervalOverrides[this.stateName];
1766
+ if (typeof override === "number") {
1767
+ intervalOverride = override / 1e3;
1768
+ } else if (override[i] !== void 0 && override[i] >= 0) {
1769
+ intervalOverride = override[i] / 1e3;
1770
+ }
1771
+ }
1772
+ const jitterStrategy = retrier.JitterStrategy ?? DEFAULT_JITTER_STRATEGY;
1688
1773
  const maxAttempts = retrier.MaxAttempts ?? DEFAULT_MAX_ATTEMPTS;
1689
1774
  const intervalSeconds = retrier.IntervalSeconds ?? DEFAULT_INTERVAL_SECONDS;
1690
1775
  const backoffRate = retrier.BackoffRate ?? DEFAULT_BACKOFF_RATE;
1691
- const waitTimeBeforeRetry = intervalSeconds * Math.pow(backoffRate, this.retrierAttempts[i]) * 1e3;
1776
+ const waitInterval = intervalOverride ?? intervalSeconds * Math.pow(backoffRate, this.retrierAttempts[i]);
1692
1777
  const retryable = error.isRetryable ?? true;
1778
+ let waitTimeBeforeRetry = clamp(waitInterval, 0, retrier.MaxDelaySeconds) * 1e3;
1779
+ if (jitterStrategy === "FULL" && intervalOverride === null) {
1780
+ waitTimeBeforeRetry = getRandomNumber(0, waitTimeBeforeRetry);
1781
+ }
1693
1782
  for (const retrierError of retrier.ErrorEquals) {
1694
1783
  const isErrorMatch = retrierError === error.name;
1695
1784
  const isErrorWildcard = retrierError === WILDCARD_ERROR;
@@ -2160,7 +2249,14 @@ var StateMachine = class {
2160
2249
  */
2161
2250
  async execute(input, options, cleanupFn) {
2162
2251
  options.eventLogger.dispatchExecutionStartedEvent(input);
2163
- const context = options.runOptions?.context ?? {};
2252
+ const context = {
2253
+ ...options.runOptions?.context,
2254
+ Execution: {
2255
+ ...options.runOptions?.context?.Execution,
2256
+ Input: input,
2257
+ StartTime: (/* @__PURE__ */ new Date()).toISOString()
2258
+ }
2259
+ };
2164
2260
  let currState = this.definition.States[this.definition.StartAt];
2165
2261
  let currStateName = this.definition.StartAt;
2166
2262
  let currInput = cloneDeep3(input);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-local-stepfunctions",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Execute an AWS Step Function state machine locally",
5
5
  "keywords": [
6
6
  "aws",
@@ -53,14 +53,14 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "@tsconfig/node16-strictest": "^1.0.4",
56
- "@types/crypto-js": "^4.1.2",
57
- "@types/jest": "^29.5.4",
58
- "@types/lodash": "^4.14.198",
59
- "@types/node": "^18.17.15",
60
- "@types/picomatch": "^2.3.0",
56
+ "@types/crypto-js": "^4.2.1",
57
+ "@types/jest": "^29.5.11",
58
+ "@types/lodash": "^4.14.202",
59
+ "@types/node": "^18.19.3",
60
+ "@types/picomatch": "^2.3.3",
61
61
  "@typescript-eslint/eslint-plugin": "^5.62.0",
62
62
  "@typescript-eslint/parser": "^5.62.0",
63
- "eslint": "^8.49.0",
63
+ "eslint": "^8.55.0",
64
64
  "eslint-config-prettier": "^8.10.0",
65
65
  "eslint-plugin-prettier": "^4.2.1",
66
66
  "prettier": "^2.8.8",
@@ -69,11 +69,11 @@
69
69
  "typescript": "^4.9.5"
70
70
  },
71
71
  "dependencies": {
72
- "@aws-sdk/client-lambda": "^3.409.0",
73
- "@aws-sdk/credential-providers": "^3.409.0",
74
- "asl-validator": "^3.8.1",
75
- "commander": "^11.0.0",
76
- "crypto-js": "^4.1.1",
72
+ "@aws-sdk/client-lambda": "^3.470.0",
73
+ "@aws-sdk/credential-providers": "^3.470.0",
74
+ "asl-validator": "^3.8.2",
75
+ "commander": "^11.1.0",
76
+ "crypto-js": "^4.2.0",
77
77
  "jsonpath-plus": "^7.2.0",
78
78
  "lodash": "^4.17.21",
79
79
  "p-limit": "^3.1.0",