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.
package/build/main.d.ts CHANGED
@@ -15,15 +15,15 @@ type JSONObject = {
15
15
  };
16
16
  type JSONValue = JSONPrimitiveValue | JSONObject | JSONArray;
17
17
 
18
- type PayloadTemplate = JSONObject;
18
+ type PayloadTemplate = JSONObject | JSONArray;
19
19
  interface CanHaveInputPath {
20
20
  InputPath?: string | null;
21
21
  }
22
22
  interface CanHaveParameters {
23
- Parameters?: PayloadTemplate;
23
+ Parameters?: JSONValue;
24
24
  }
25
25
  interface CanHaveResultSelector {
26
- ResultSelector?: PayloadTemplate;
26
+ ResultSelector?: JSONValue;
27
27
  }
28
28
  interface CanHaveResultPath {
29
29
  ResultPath?: string | null;
@@ -101,7 +101,9 @@ type TerminalState = EndableState | SucceedOrFailState;
101
101
  interface BaseFailState extends BaseState {
102
102
  Type: 'Fail';
103
103
  Cause?: string;
104
+ CausePath?: string;
104
105
  Error?: string;
106
+ ErrorPath?: string;
105
107
  }
106
108
  type FailState = TerminalState & BaseFailState;
107
109
 
@@ -110,6 +112,8 @@ type Retrier = {
110
112
  IntervalSeconds?: number;
111
113
  MaxAttempts?: number;
112
114
  BackoffRate?: number;
115
+ MaxDelaySeconds?: number;
116
+ JitterStrategy?: 'NONE' | 'FULL';
113
117
  };
114
118
  interface RetryableState {
115
119
  Retry?: Retrier[];
@@ -129,11 +133,15 @@ interface NextableState extends BaseState {
129
133
  }
130
134
  type IntermediateState = NextableState;
131
135
 
132
- interface BaseMapState extends BaseState, CanHaveInputPath, CanHaveParameters, CanHaveResultSelector, CanHaveResultPath, CanHaveOutputPath, RetryableState, CatchableState {
136
+ interface BaseMapState extends BaseState, CanHaveInputPath, CanHaveResultSelector, CanHaveResultPath, CanHaveOutputPath, RetryableState, CatchableState {
133
137
  Type: 'Map';
134
- Iterator: Omit<StateMachineDefinition, 'Version' | 'TimeoutSeconds'>;
138
+ Iterator?: Omit<StateMachineDefinition, 'Version' | 'TimeoutSeconds'>;
139
+ ItemProcessor?: Omit<StateMachineDefinition, 'Version' | 'TimeoutSeconds'>;
140
+ Parameters?: PayloadTemplate;
141
+ ItemSelector?: PayloadTemplate;
135
142
  ItemsPath?: string;
136
143
  MaxConcurrency?: number;
144
+ MaxConcurrencyPath?: string;
137
145
  }
138
146
  type MapState = (IntermediateState | TerminalState) & BaseMapState;
139
147
 
@@ -157,6 +165,8 @@ type SucceedState = TerminalState & BaseSucceedState;
157
165
  interface BaseTaskState extends BaseState, CanHaveInputPath, CanHaveParameters, CanHaveResultSelector, CanHaveResultPath, CanHaveOutputPath, RetryableState, CatchableState {
158
166
  Type: 'Task';
159
167
  Resource: string;
168
+ TimeoutSeconds?: number;
169
+ TimeoutSecondsPath?: string;
160
170
  }
161
171
  type TaskState = (IntermediateState | TerminalState) & BaseTaskState;
162
172
 
@@ -179,7 +189,15 @@ interface StateMachineDefinition {
179
189
  TimeoutSeconds?: number;
180
190
  }
181
191
 
182
- type Context = Record<string, unknown>;
192
+ type ContextExecution = {
193
+ Input?: JSONValue;
194
+ StartTime?: string;
195
+ [other: string]: unknown;
196
+ };
197
+ type Context = {
198
+ Execution?: ContextExecution;
199
+ [other: string]: unknown;
200
+ };
183
201
 
184
202
  declare class ErrorWithCause extends Error {
185
203
  #private;
@@ -322,6 +340,9 @@ type TaskStateResourceLocalHandler = {
322
340
  type WaitStateTimeOverride = {
323
341
  [waitStateName: string]: number;
324
342
  };
343
+ type RetryIntervalOverrides = {
344
+ [retryableStateName: string]: number | number[];
345
+ };
325
346
  interface Overrides {
326
347
  /**
327
348
  * Pass an object to this option to override a `Task` state to run a local function,
@@ -333,6 +354,11 @@ interface Overrides {
333
354
  * instead of pausing for the duration specified by the `Seconds`, `Timestamp`, `SecondsPath`, or `TimestampPath` fields.
334
355
  */
335
356
  waitTimeOverrides?: WaitStateTimeOverride;
357
+ /**
358
+ * Pass an object to this option to override the duration in milliseconds a retrier in a `Retry` field waits before retrying the state,
359
+ * instead of pausing for the duration calculated by the `IntervalSeconds`, `BackoffRate`, `MaxDelaySeconds`, and `JitterStrategy` fields.
360
+ */
361
+ retryIntervalOverrides?: RetryIntervalOverrides;
336
362
  }
337
363
  interface ValidationOptions {
338
364
  /**
@@ -135,6 +135,8 @@ function isPlainObj(value) {
135
135
  return !!value && Object.getPrototypeOf(value) === Object.prototype;
136
136
  }
137
137
  function sleep(ms, abortSignal) {
138
+ if (ms === 0)
139
+ return;
138
140
  return new Promise((resolve) => {
139
141
  if (abortSignal?.aborted) {
140
142
  return resolve();
@@ -160,6 +162,13 @@ function isRFC3339Timestamp(date) {
160
162
  function stringifyJSONValue(value) {
161
163
  return JSON.stringify(value);
162
164
  }
165
+ function clamp(value, min, max) {
166
+ if (min && value < min)
167
+ return min;
168
+ if (max && value > max)
169
+ return max;
170
+ return value;
171
+ }
163
172
 
164
173
  // src/stateMachine/jsonPath/JsonPath.ts
165
174
  var import_jsonpath_plus = require("jsonpath-plus");
@@ -906,9 +915,18 @@ function processInputPath(path, input, context) {
906
915
  return jsonPathQuery(path, input, context);
907
916
  }
908
917
  function processPayloadTemplate(payloadTemplate, json, context) {
918
+ if (typeof payloadTemplate !== "object" || payloadTemplate === null) {
919
+ return payloadTemplate;
920
+ }
921
+ if (Array.isArray(payloadTemplate)) {
922
+ return payloadTemplate.map((value) => processPayloadTemplate(value, json, context));
923
+ }
909
924
  const resolvedProperties = Object.entries(payloadTemplate).map(([key, value]) => {
910
925
  let sanitizedKey = key;
911
926
  let resolvedValue = value;
927
+ if (Array.isArray(value)) {
928
+ resolvedValue = value.map((innerValue) => processPayloadTemplate(innerValue, json, context));
929
+ }
912
930
  if (isPlainObj(value)) {
913
931
  resolvedValue = processPayloadTemplate(value, json, context);
914
932
  }
@@ -1293,7 +1311,20 @@ var FailStateAction = class extends BaseStateAction {
1293
1311
  super(stateDefinition, stateName);
1294
1312
  }
1295
1313
  async execute(input, context, options) {
1296
- throw new FailStateError(this.stateDefinition.Error, this.stateDefinition.Cause);
1314
+ const state = this.stateDefinition;
1315
+ let error = state.Error;
1316
+ let cause = state.Cause;
1317
+ if (state.ErrorPath) {
1318
+ error = jsonPathQuery(state.ErrorPath, input, context, {
1319
+ constraints: [StringConstraint]
1320
+ });
1321
+ }
1322
+ if (state.CausePath) {
1323
+ cause = jsonPathQuery(state.CausePath, input, context, {
1324
+ constraints: [StringConstraint]
1325
+ });
1326
+ }
1327
+ throw new FailStateError(error, cause);
1297
1328
  }
1298
1329
  };
1299
1330
 
@@ -1308,6 +1339,31 @@ var ArrayConstraint = class extends BaseJSONPathConstraint {
1308
1339
  }
1309
1340
  };
1310
1341
 
1342
+ // src/stateMachine/jsonPath/constraints/IntegerConstraint.ts
1343
+ var IntegerConstraint = class extends BaseJSONPathConstraint {
1344
+ test(value) {
1345
+ if (!Number.isInteger(value)) {
1346
+ throw new StatesRuntimeError(
1347
+ `Path expression '${this.pathExpression}' evaluated to ${stringifyJSONValue(value)}, but expected an integer`
1348
+ );
1349
+ }
1350
+ }
1351
+ static greaterThanOrEqual(n) {
1352
+ return class extends this {
1353
+ test(value) {
1354
+ super.test(value);
1355
+ if (value < n) {
1356
+ throw new StatesRuntimeError(
1357
+ `Path expression '${this.pathExpression}' evaluated to ${stringifyJSONValue(
1358
+ value
1359
+ )}, but expected an integer >= ${n}`
1360
+ );
1361
+ }
1362
+ }
1363
+ };
1364
+ }
1365
+ };
1366
+
1311
1367
  // src/stateMachine/stateActions/MapStateAction.ts
1312
1368
  var import_p_limit = __toESM(require("p-limit"), 1);
1313
1369
  var DEFAULT_MAX_CONCURRENCY = 40;
@@ -1332,8 +1388,9 @@ var MapStateAction = class extends BaseStateAction {
1332
1388
  Value: item
1333
1389
  }
1334
1390
  };
1335
- if (state.Parameters) {
1336
- paramValue = processPayloadTemplate(state.Parameters, input, context);
1391
+ if (state.Parameters || state.ItemSelector) {
1392
+ const parameters = state.ItemSelector ?? state.Parameters;
1393
+ paramValue = processPayloadTemplate(parameters, input, context);
1337
1394
  }
1338
1395
  const execution = stateMachine.run(paramValue ?? item, options.runOptions);
1339
1396
  this.executionAbortFuncs.push(execution.abort);
@@ -1351,11 +1408,19 @@ var MapStateAction = class extends BaseStateAction {
1351
1408
  if (!Array.isArray(items)) {
1352
1409
  throw new StatesRuntimeError("Input of Map state must be an array or ItemsPath property must point to an array");
1353
1410
  }
1354
- const iteratorStateMachine = new StateMachine(state.Iterator, {
1411
+ const iteratorDefinition = state.ItemProcessor ?? state.Iterator;
1412
+ const iteratorStateMachine = new StateMachine(iteratorDefinition, {
1355
1413
  ...options.stateMachineOptions,
1356
1414
  validationOptions: { noValidate: true }
1357
1415
  });
1358
- const limit = (0, import_p_limit.default)(state.MaxConcurrency || DEFAULT_MAX_CONCURRENCY);
1416
+ let maxConcurrency = state.MaxConcurrency;
1417
+ if (state.MaxConcurrencyPath) {
1418
+ maxConcurrency = jsonPathQuery(state.MaxConcurrencyPath, input, context, {
1419
+ constraints: [IntegerConstraint.greaterThanOrEqual(0)],
1420
+ ignoreDefinedValueConstraint: true
1421
+ });
1422
+ }
1423
+ const limit = (0, import_p_limit.default)(maxConcurrency || DEFAULT_MAX_CONCURRENCY);
1359
1424
  const processedItemsPromise = items.map(
1360
1425
  (item, i) => limit(() => this.processItem(iteratorStateMachine, item, input, context, i, options))
1361
1426
  );
@@ -1441,6 +1506,14 @@ var SucceedStateAction = class extends BaseStateAction {
1441
1506
  }
1442
1507
  };
1443
1508
 
1509
+ // src/error/predefined/StatesTimeoutError.ts
1510
+ var StatesTimeoutError = class extends RuntimeError {
1511
+ constructor() {
1512
+ super("States.Timeout");
1513
+ this.name = "States.Timeout";
1514
+ }
1515
+ };
1516
+
1444
1517
  // src/aws/LambdaClient.ts
1445
1518
  var import_client_lambda = require("@aws-sdk/client-lambda");
1446
1519
  var import_credential_providers = require("@aws-sdk/credential-providers");
@@ -1524,41 +1597,47 @@ var LambdaClient = class {
1524
1597
  var TaskStateAction = class extends BaseStateAction {
1525
1598
  constructor(stateDefinition, stateName) {
1526
1599
  super(stateDefinition, stateName);
1600
+ this.timeoutAbortController = new AbortController();
1601
+ }
1602
+ createTimeoutPromise(input, context) {
1603
+ const state = this.stateDefinition;
1604
+ if (!state.TimeoutSeconds && !state.TimeoutSecondsPath)
1605
+ return;
1606
+ let timeout;
1607
+ if (state.TimeoutSeconds) {
1608
+ timeout = state.TimeoutSeconds;
1609
+ } else if (state.TimeoutSecondsPath) {
1610
+ timeout = jsonPathQuery(state.TimeoutSecondsPath, input, context, {
1611
+ constraints: [IntegerConstraint.greaterThanOrEqual(1)]
1612
+ });
1613
+ }
1614
+ return new Promise((_, reject) => {
1615
+ const handleTimeoutAbort = () => clearTimeout(timeoutId);
1616
+ const timeoutId = setTimeout(() => {
1617
+ this.timeoutAbortController.signal.removeEventListener("abort", handleTimeoutAbort);
1618
+ reject(new StatesTimeoutError());
1619
+ }, timeout * 1e3);
1620
+ this.timeoutAbortController.signal.addEventListener("abort", handleTimeoutAbort, { once: true });
1621
+ });
1527
1622
  }
1528
1623
  async execute(input, context, options) {
1529
1624
  const state = this.stateDefinition;
1625
+ const racingPromises = [];
1626
+ const timeoutPromise = this.createTimeoutPromise(input, context);
1530
1627
  if (options.overrideFn) {
1531
- const result2 = await options.overrideFn(input);
1532
- return this.buildExecutionResult(result2);
1628
+ const resultPromise = options.overrideFn(input);
1629
+ racingPromises.push(resultPromise);
1630
+ } else {
1631
+ const lambdaClient = new LambdaClient(options.awsConfig);
1632
+ const resultPromise = lambdaClient.invokeFunction(state.Resource, input);
1633
+ racingPromises.push(resultPromise);
1533
1634
  }
1534
- const lambdaClient = new LambdaClient(options.awsConfig);
1535
- const result = await lambdaClient.invokeFunction(state.Resource, input);
1536
- return this.buildExecutionResult(result);
1537
- }
1538
- };
1539
-
1540
- // src/stateMachine/jsonPath/constraints/IntegerConstraint.ts
1541
- var IntegerConstraint = class extends BaseJSONPathConstraint {
1542
- test(value) {
1543
- if (!Number.isInteger(value)) {
1544
- throw new StatesRuntimeError(
1545
- `Path expression '${this.pathExpression}' evaluated to ${stringifyJSONValue(value)}, but expected an integer`
1546
- );
1635
+ if (timeoutPromise) {
1636
+ racingPromises.push(timeoutPromise);
1547
1637
  }
1548
- }
1549
- static greaterThanOrEqual(n) {
1550
- return class extends this {
1551
- test(value) {
1552
- super.test(value);
1553
- if (value < n) {
1554
- throw new StatesRuntimeError(
1555
- `Path expression '${this.pathExpression}' evaluated to ${stringifyJSONValue(
1556
- value
1557
- )}, but expected an integer >= ${n}`
1558
- );
1559
- }
1560
- }
1561
- };
1638
+ const result = await Promise.race(racingPromises);
1639
+ this.timeoutAbortController.abort();
1640
+ return this.buildExecutionResult(result);
1562
1641
  }
1563
1642
  };
1564
1643
 
@@ -1598,19 +1677,12 @@ var WaitStateAction = class extends BaseStateAction {
1598
1677
  }
1599
1678
  };
1600
1679
 
1601
- // src/error/predefined/StatesTimeoutError.ts
1602
- var StatesTimeoutError = class extends RuntimeError {
1603
- constructor() {
1604
- super("States.Timeout");
1605
- this.name = "States.Timeout";
1606
- }
1607
- };
1608
-
1609
1680
  // src/stateMachine/StateExecutor.ts
1610
1681
  var import_cloneDeep2 = __toESM(require("lodash/cloneDeep.js"), 1);
1611
1682
  var DEFAULT_MAX_ATTEMPTS = 3;
1612
1683
  var DEFAULT_INTERVAL_SECONDS = 1;
1613
1684
  var DEFAULT_BACKOFF_RATE = 2;
1685
+ var DEFAULT_JITTER_STRATEGY = "NONE";
1614
1686
  var WILDCARD_ERROR = "States.ALL";
1615
1687
  var TASK_STATE_WILDCARD_ERROR = "States.TaskFailed";
1616
1688
  var StateExecutor = class {
@@ -1658,7 +1730,10 @@ var StateExecutor = class {
1658
1730
  input,
1659
1731
  error
1660
1732
  );
1661
- const { shouldRetry, waitTimeBeforeRetry, retrierIndex } = this.shouldRetry(error);
1733
+ const { shouldRetry, waitTimeBeforeRetry, retrierIndex } = this.shouldRetry(
1734
+ error,
1735
+ options.runOptions?.overrides?.retryIntervalOverrides
1736
+ );
1662
1737
  if (shouldRetry) {
1663
1738
  const stateDefinition = this.stateDefinition;
1664
1739
  await sleep(waitTimeBeforeRetry, options.abortSignal);
@@ -1717,17 +1792,31 @@ var StateExecutor = class {
1717
1792
  /**
1718
1793
  * Decide whether this state should be retried, according to the `Retry` field.
1719
1794
  */
1720
- shouldRetry(error) {
1795
+ shouldRetry(error, retryIntervalOverrides) {
1721
1796
  if (!("Retry" in this.stateDefinition)) {
1722
1797
  return { shouldRetry: false };
1723
1798
  }
1724
1799
  for (let i = 0; i < this.stateDefinition.Retry.length; i++) {
1725
1800
  const retrier = this.stateDefinition.Retry[i];
1801
+ let intervalOverride = null;
1802
+ if (retryIntervalOverrides?.[this.stateName] !== void 0) {
1803
+ const override = retryIntervalOverrides[this.stateName];
1804
+ if (typeof override === "number") {
1805
+ intervalOverride = override / 1e3;
1806
+ } else if (override[i] !== void 0 && override[i] >= 0) {
1807
+ intervalOverride = override[i] / 1e3;
1808
+ }
1809
+ }
1810
+ const jitterStrategy = retrier.JitterStrategy ?? DEFAULT_JITTER_STRATEGY;
1726
1811
  const maxAttempts = retrier.MaxAttempts ?? DEFAULT_MAX_ATTEMPTS;
1727
1812
  const intervalSeconds = retrier.IntervalSeconds ?? DEFAULT_INTERVAL_SECONDS;
1728
1813
  const backoffRate = retrier.BackoffRate ?? DEFAULT_BACKOFF_RATE;
1729
- const waitTimeBeforeRetry = intervalSeconds * Math.pow(backoffRate, this.retrierAttempts[i]) * 1e3;
1814
+ const waitInterval = intervalOverride ?? intervalSeconds * Math.pow(backoffRate, this.retrierAttempts[i]);
1730
1815
  const retryable = error.isRetryable ?? true;
1816
+ let waitTimeBeforeRetry = clamp(waitInterval, 0, retrier.MaxDelaySeconds) * 1e3;
1817
+ if (jitterStrategy === "FULL" && intervalOverride === null) {
1818
+ waitTimeBeforeRetry = getRandomNumber(0, waitTimeBeforeRetry);
1819
+ }
1731
1820
  for (const retrierError of retrier.ErrorEquals) {
1732
1821
  const isErrorMatch = retrierError === error.name;
1733
1822
  const isErrorWildcard = retrierError === WILDCARD_ERROR;
@@ -2198,7 +2287,14 @@ var StateMachine = class {
2198
2287
  */
2199
2288
  async execute(input, options, cleanupFn) {
2200
2289
  options.eventLogger.dispatchExecutionStartedEvent(input);
2201
- const context = options.runOptions?.context ?? {};
2290
+ const context = {
2291
+ ...options.runOptions?.context,
2292
+ Execution: {
2293
+ ...options.runOptions?.context?.Execution,
2294
+ Input: input,
2295
+ StartTime: (/* @__PURE__ */ new Date()).toISOString()
2296
+ }
2297
+ };
2202
2298
  let currState = this.definition.States[this.definition.StartAt];
2203
2299
  let currStateName = this.definition.StartAt;
2204
2300
  let currInput = (0, import_cloneDeep3.default)(input);