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/README.md +11 -1
- package/bin/CLI.cjs +1 -1
- package/build/main.browser.esm.js +1261 -1055
- package/build/main.d.ts +32 -6
- package/build/main.node.cjs +142 -46
- package/build/main.node.esm.js +142 -46
- package/package.json +12 -12
package/build/main.node.esm.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1494
|
-
|
|
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
|
-
|
|
1497
|
-
|
|
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
|
-
|
|
1512
|
-
return
|
|
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(
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
|
57
|
-
"@types/jest": "^29.5.
|
|
58
|
-
"@types/lodash": "^4.14.
|
|
59
|
-
"@types/node": "^18.
|
|
60
|
-
"@types/picomatch": "^2.3.
|
|
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.
|
|
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.
|
|
73
|
-
"@aws-sdk/credential-providers": "^3.
|
|
74
|
-
"asl-validator": "^3.8.
|
|
75
|
-
"commander": "^11.
|
|
76
|
-
"crypto-js": "^4.
|
|
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",
|