aws-local-stepfunctions 0.6.0 → 0.7.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 +171 -2
- package/build/CLI.cjs +236 -0
- package/build/main.browser.esm.js +48 -34
- package/build/main.d.ts +2 -2
- package/build/main.node.cjs +48 -34
- package/build/main.node.esm.js +48 -34
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -16,6 +16,14 @@ This package lets you run AWS Step Functions completely locally, both in Node.js
|
|
|
16
16
|
- [API](#api)
|
|
17
17
|
- [Constructor](#constructor-new-statemachinedefinition-statemachineoptions)
|
|
18
18
|
- [StateMachine.run](#statemachineruninput-options)
|
|
19
|
+
- [CLI](#cli)
|
|
20
|
+
- [Basic usage](#basic-usage)
|
|
21
|
+
- [Passing input from stdin](#passing-input-from-stdin)
|
|
22
|
+
- [Overriding Task and Wait states](#overriding-task-and-wait-states)
|
|
23
|
+
- [Task state override](#task-state-override)
|
|
24
|
+
- [Wait state override](#wait-state-override)
|
|
25
|
+
- [Disabling ASL validations](#disabling-asl-validations)
|
|
26
|
+
- [Exit codes](#exit-codes)
|
|
19
27
|
- [Examples](#examples)
|
|
20
28
|
- [License](#license)
|
|
21
29
|
|
|
@@ -49,7 +57,7 @@ import { StateMachine } from 'aws-local-stepfunctions';
|
|
|
49
57
|
|
|
50
58
|
You can import the bundled package directly into a browser script as an ES module, from one of the following CDNs:
|
|
51
59
|
|
|
52
|
-
> NOTE: The following examples will import the latest package version. Refer to the CDNs websites to know about other ways in which you can specify the package URL (for example, to import a specific version).
|
|
60
|
+
> NOTE: The following examples will import the latest package version. Refer to the CDNs websites to know about other ways in which you can specify the package URL (for example, to import a specific version or a minified version).
|
|
53
61
|
|
|
54
62
|
#### [unpkg](https://unpkg.com/)
|
|
55
63
|
|
|
@@ -122,7 +130,7 @@ Each execution is independent of all others, meaning that you can concurrently c
|
|
|
122
130
|
- `options?`:
|
|
123
131
|
- `overrides?`: An object to override the behavior of certain states:
|
|
124
132
|
- `taskResourceLocalHandlers?`: An [object that overrides](/docs/feature-support.md#task-state-resource-override) the resource of the specified `Task` states to run a local function.
|
|
125
|
-
- `waitTimeOverrides?`: An [object that overrides](/docs/feature-support.md#wait-state-duration-override) the wait duration of the specified `Wait` states. The
|
|
133
|
+
- `waitTimeOverrides?`: An [object that overrides](/docs/feature-support.md#wait-state-duration-override) the wait duration of the specified `Wait` states. The specified override duration should be in milliseconds.
|
|
126
134
|
- `noThrowOnAbort?`: If this option is set to `true`, aborting the execution will simply return `null` as result instead of throwing.
|
|
127
135
|
|
|
128
136
|
#### Basic example:
|
|
@@ -149,6 +157,167 @@ const result = await execution.result; // wait until the execution finishes to g
|
|
|
149
157
|
console.log(result); // log the result of the execution
|
|
150
158
|
```
|
|
151
159
|
|
|
160
|
+
## CLI
|
|
161
|
+
|
|
162
|
+
In addition to the JavaScript API, `aws-local-stepfunctions` also provides a command-line interface. The CLI allows you to run one or several executions without having to create a Node.js script.
|
|
163
|
+
|
|
164
|
+
To use the CLI as a global shell command, you need to install the package globally:
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
npm install -g aws-local-stepfunctions
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
After installing the package, the command `local-sfn` will be available in your shell.
|
|
171
|
+
|
|
172
|
+
### Basic usage
|
|
173
|
+
|
|
174
|
+
The simplest way to use the CLI is by passing either the `-d, --definition` or the `-f, --definition-file` option, along with the input(s) for the state machine. For example:
|
|
175
|
+
|
|
176
|
+
```sh
|
|
177
|
+
local-sfn \
|
|
178
|
+
-f state-machine.json \
|
|
179
|
+
'{ "num1": 1, "num2": 2 }' \
|
|
180
|
+
'{ "num1": 3, "num2": 4 }' \
|
|
181
|
+
'{ "num1": 5, "num2": 6 }'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
This command would execute the state machine defined in file `state-machine.json` with `'{ "num1": 1, "num2": 2 }'`, `'{ "num1": 3, "num2": 4 }'`, and `'{ "num1": 5, "num2": 6 }'` as inputs. Each input corresponds to a state machine execution, and each execution is run independently, so the failure of one execution doesn't mean the failure of all of the other executions.
|
|
185
|
+
|
|
186
|
+
Now, suppose the state machine in file `state-machine.json` is defined as a single `Task` state that calls a Lambda function that adds `num1` and `num2`:
|
|
187
|
+
|
|
188
|
+
<a id="cli-state-machine"></a>
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"StartAt": "AddNumbers",
|
|
193
|
+
"States": {
|
|
194
|
+
"AddNumbers": {
|
|
195
|
+
"Type": "Task",
|
|
196
|
+
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:AddNumbers",
|
|
197
|
+
"End": true
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Then, the output of the `local-sfn` command above may look something like this:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
3
|
|
207
|
+
7
|
|
208
|
+
11
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Note that each line of the output corresponds to the result of each input, in the same order that the inputs were given to the command.
|
|
212
|
+
|
|
213
|
+
### Passing input from stdin
|
|
214
|
+
|
|
215
|
+
`local-sfn` can also read the execution input from the standard input. For example, assume you have the following text file, called `inputs.txt`, and you want to pass the contents of the file as inputs to `local-sfn`:
|
|
216
|
+
|
|
217
|
+
```txt
|
|
218
|
+
{ "num1": 1, "num2": 2 }
|
|
219
|
+
{ "num1": 3, "num2": 4 }
|
|
220
|
+
{ "num1": 5, "num2": 6 }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
You can then run the following command to pass the inputs of the text file to `local-sfn`:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
cat inputs.txt | local-sfn -f state-machine.json
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Alternatively, using input redirection:
|
|
230
|
+
|
|
231
|
+
```sh
|
|
232
|
+
local-sfn -f state-machine.json < inputs.txt
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
When reading from stdin, `local-sfn` will take each line and use it as an input. Hence, to avoid any parsing errors, make sure the output of the command you're piping into `local-sfn` prints each input in a new line.
|
|
236
|
+
|
|
237
|
+
### Overriding Task and Wait states
|
|
238
|
+
|
|
239
|
+
As explained in the Feature support document, it's possible to override the default actions of [`Task` states](/docs/feature-support.md#task-state-resource-override) and [`Wait` states](/docs/feature-support.md#wait-state-duration-override).
|
|
240
|
+
|
|
241
|
+
#### Task state override
|
|
242
|
+
|
|
243
|
+
To override a `Task` state, pass the `-t, --override-task` option. This option takes as value the name of the `Task` state you want to override, and a path to a script or program that will be executed instead of the resource specified in the state definition. The state name and the path must be separated by a colon `:`.
|
|
244
|
+
|
|
245
|
+
Using the same [state machine definition](#cli-state-machine) as before, if you wanted to override the `AddNumbers` state to run a custom script, you can do it like this:
|
|
246
|
+
|
|
247
|
+
```sh
|
|
248
|
+
local-sfn -f state-machine.json -t AddNumbers:./override.sh '{ "num1": 1, "num2": 2 }'
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
This command would run the state machine, but instead of invoking the Lambda function specified in the `Resource` field of the `AddNumbers` state, the `override.sh` script would be executed.
|
|
252
|
+
|
|
253
|
+
Now, suppose the `override.sh` script is defined like this:
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
#!/bin/sh
|
|
257
|
+
|
|
258
|
+
TASK_INPUT=$1 # First argument is the input to the overridden Task state
|
|
259
|
+
echo "$TASK_INPUT" | jq '.num1 + .num2' # Use jq to add "num1" and "num2", and print result to stdout
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
When overriding a `Task` state, the overriding executable will be passed the input to the `Task` state as first argument, which can then be used to compute the task result. Similarly, the executable must print the task result as a JSON value to the standard output, so `local-sfn` can then read stdout and use the value as the result of the `Task` state. If the executable terminates with an exit code different from `0`, its standard error will be printed and the execution will be marked as a failure.
|
|
263
|
+
|
|
264
|
+
Additionally, you can pass the `-t, --override-task` option multiple times, to override more than one `Task` state. For example:
|
|
265
|
+
|
|
266
|
+
```sh
|
|
267
|
+
local-sfn
|
|
268
|
+
-f state-machine.json \
|
|
269
|
+
-t AddNumbers:./override.sh \
|
|
270
|
+
-t SendRequest:./request.py \
|
|
271
|
+
-t ProcessImage:./proc_image \
|
|
272
|
+
'{ "num1": 1, "num2": 2 }'
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This command would execute the state machine, and override `Task` states `AddNumbers`, `SendRequest`, and `ProcessImage` to run the `override.sh` shell script, the `request.py` Python script, and the `proc_image` program, respectively.
|
|
276
|
+
|
|
277
|
+
#### Wait state override
|
|
278
|
+
|
|
279
|
+
To override the duration of a `Wait` state, pass the `-w, --override-wait` option. This option takes as value the name of the `Wait` state you want to override, and a number that represents the amount in milliseconds that you want to pause the execution for. The state name and the milliseconds amount must be separated by a colon `:`.
|
|
280
|
+
|
|
281
|
+
For example:
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
local-sfn -f state-machine.json -w WaitResponse:1500 '{ "num1": 1, "num2": 2 }'
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
This command would execute the state machine, and when entering the `WaitResponse` `Wait` state, the execution would be paused for 1500 milliseconds (1.5 seconds), disregarding the `Seconds`, `Timestamp`, `SecondsPath`, or `TimestampPath` fields that could've been specified in the definition of `WaitResponse`.
|
|
288
|
+
|
|
289
|
+
In the same way as the `-t, --override-task` option, you can pass the `-w, --override-wait` option multiple times, to override more than one `Wait` state. For example:
|
|
290
|
+
|
|
291
|
+
```sh
|
|
292
|
+
local-sfn \
|
|
293
|
+
-f state-machine.json \
|
|
294
|
+
-w WaitResponse:1500 \
|
|
295
|
+
-w PauseUntilSignal:250 \
|
|
296
|
+
-w Delay:0 \
|
|
297
|
+
'{ "num1": 1, "num2": 2 }'
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
This command would execute the state machine, and override `Wait` states `WaitResponse` and `PauseUntilSignal` to pause the execution for 1500 and 250 milliseconds, respectively. The `Delay` state wouldn't be paused at all, since the override value is set to 0.
|
|
301
|
+
|
|
302
|
+
### Disabling ASL validations
|
|
303
|
+
|
|
304
|
+
Before attempting to run the state machine with the given inputs, the state machine definition itself is validated to check that:
|
|
305
|
+
|
|
306
|
+
- JSONPath strings are valid.
|
|
307
|
+
- ARNs in the `Resource` field of `Task` states are valid.
|
|
308
|
+
|
|
309
|
+
If any of these two checks fail, `local-sfn` will print the validation error and exit. To suppress this behavior, you can pass the `--no-jsonpath-validation` option, to suppress JSONPath validation; and the `--no-arn-validation` option, to suppress ARN validation.
|
|
310
|
+
|
|
311
|
+
### Exit codes
|
|
312
|
+
|
|
313
|
+
`local-sfn` can terminate with the following exit codes:
|
|
314
|
+
|
|
315
|
+
| Exit code | Explanation |
|
|
316
|
+
| :-------: | ------------------------------------------------------------------------------------ |
|
|
317
|
+
| 0 | The state machine was executed, and all executions ran successfully. |
|
|
318
|
+
| 1 | An error occurred before the state machine could be executed (e.g. a parsing error). |
|
|
319
|
+
| 2 | The state machine was executed, but at least one execution had an error. |
|
|
320
|
+
|
|
152
321
|
## Examples
|
|
153
322
|
|
|
154
323
|
You can check more examples and options usage in the [examples](/examples) directory.
|
package/build/CLI.cjs
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/cli/CLI.ts
|
|
32
|
+
var CLI_exports = {};
|
|
33
|
+
__export(CLI_exports, {
|
|
34
|
+
makeProgram: () => makeProgram
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(CLI_exports);
|
|
37
|
+
var import_readline = __toESM(require("readline"), 1);
|
|
38
|
+
var import_commander = require("commander");
|
|
39
|
+
|
|
40
|
+
// package.json
|
|
41
|
+
var version = "0.7.0";
|
|
42
|
+
|
|
43
|
+
// src/cli/ArgumentParsers.ts
|
|
44
|
+
var import_fs = require("fs");
|
|
45
|
+
var import_child_process = require("child_process");
|
|
46
|
+
|
|
47
|
+
// src/util/index.ts
|
|
48
|
+
function tryJSONParse(jsonStr) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(jsonStr);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/cli/ArgumentParsers.ts
|
|
57
|
+
function parseDefinitionOption(command, definition) {
|
|
58
|
+
const jsonOrError = tryJSONParse(definition);
|
|
59
|
+
if (jsonOrError instanceof Error) {
|
|
60
|
+
command.error(
|
|
61
|
+
`error: parsing of state machine definition passed in option '-d, --definition <definition>' failed: ${jsonOrError.message}`,
|
|
62
|
+
{
|
|
63
|
+
exitCode: 1 /* PreExecutionFailure */
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return jsonOrError;
|
|
68
|
+
}
|
|
69
|
+
function parseDefinitionFileOption(command, definitionFile) {
|
|
70
|
+
let file;
|
|
71
|
+
try {
|
|
72
|
+
file = (0, import_fs.readFileSync)(definitionFile).toString();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
command.error(`error: ${error.message}`, { exitCode: 1 /* PreExecutionFailure */ });
|
|
75
|
+
}
|
|
76
|
+
const jsonOrError = tryJSONParse(file);
|
|
77
|
+
if (jsonOrError instanceof Error) {
|
|
78
|
+
command.error(
|
|
79
|
+
`error: parsing of state machine definition in file '${definitionFile}' failed: ${jsonOrError.message}`,
|
|
80
|
+
{ exitCode: 1 /* PreExecutionFailure */ }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return jsonOrError;
|
|
84
|
+
}
|
|
85
|
+
function parseOverrideTaskOption(value, previous = {}) {
|
|
86
|
+
const [taskStateName, scriptPath] = value.split(":");
|
|
87
|
+
previous[taskStateName] = (input) => {
|
|
88
|
+
const spawnResult = (0, import_child_process.spawnSync)(scriptPath, [JSON.stringify(input)]);
|
|
89
|
+
if (spawnResult.error) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Attempt to run task override '${scriptPath}' for state '${taskStateName}' failed: ${spawnResult.error.message}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (spawnResult.status !== 0) {
|
|
95
|
+
throw new Error(`${scriptPath} ('${taskStateName}'): ${spawnResult.stderr.toString()}`);
|
|
96
|
+
}
|
|
97
|
+
const overrideResult = spawnResult.stdout.toString();
|
|
98
|
+
const jsonOrError = tryJSONParse(overrideResult);
|
|
99
|
+
if (jsonOrError instanceof Error) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Parsing of output '${overrideResult}' from task override '${scriptPath}' for state '${taskStateName}' failed: ${jsonOrError.message}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return Promise.resolve(jsonOrError);
|
|
105
|
+
};
|
|
106
|
+
return previous;
|
|
107
|
+
}
|
|
108
|
+
function parseOverrideWaitOption(value, previous = {}) {
|
|
109
|
+
const [waitStateName, duration] = value.split(":");
|
|
110
|
+
previous[waitStateName] = Number(duration);
|
|
111
|
+
return previous;
|
|
112
|
+
}
|
|
113
|
+
function parseInputArguments(command, value, previous = []) {
|
|
114
|
+
const jsonOrError = tryJSONParse(value);
|
|
115
|
+
if (jsonOrError instanceof Error) {
|
|
116
|
+
command.error(`error: parsing of input value '${value}' failed: ${jsonOrError.message}`, {
|
|
117
|
+
exitCode: 1 /* PreExecutionFailure */
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return previous.concat(jsonOrError);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/cli/CommandHandler.ts
|
|
124
|
+
var import_main = require("./main.node.cjs");
|
|
125
|
+
async function commandAction(inputs, options, command) {
|
|
126
|
+
let stateMachine;
|
|
127
|
+
try {
|
|
128
|
+
stateMachine = new import_main.StateMachine(options.definition ?? options.definitionFile, {
|
|
129
|
+
validationOptions: {
|
|
130
|
+
checkPaths: options.jsonpathValidation,
|
|
131
|
+
checkArn: options.arnValidation
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
command.error(`error: ${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
const resultsPromises = inputs.map((input) => {
|
|
138
|
+
const { result } = stateMachine.run(input, {
|
|
139
|
+
overrides: {
|
|
140
|
+
taskResourceLocalHandlers: options.overrideTask,
|
|
141
|
+
waitTimeOverrides: options.overrideWait
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return result;
|
|
145
|
+
});
|
|
146
|
+
const results = await Promise.allSettled(resultsPromises);
|
|
147
|
+
let exitCode = 0 /* Success */;
|
|
148
|
+
for (const result of results) {
|
|
149
|
+
if (result.status === "fulfilled") {
|
|
150
|
+
console.log(result.value);
|
|
151
|
+
} else {
|
|
152
|
+
exitCode = 2 /* StateMachineExecutionFailure */;
|
|
153
|
+
let msg = result.reason.message;
|
|
154
|
+
if (result.reason instanceof import_main.ExecutionTimeoutError) {
|
|
155
|
+
msg = "Execution timed out";
|
|
156
|
+
}
|
|
157
|
+
console.log(msg.trim());
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
process.exitCode = exitCode;
|
|
161
|
+
}
|
|
162
|
+
function preActionHook(thisCommand) {
|
|
163
|
+
const options = thisCommand.opts();
|
|
164
|
+
if (thisCommand.args.length === 0) {
|
|
165
|
+
thisCommand.help();
|
|
166
|
+
}
|
|
167
|
+
if (!options["definition"] && !options["definitionFile"]) {
|
|
168
|
+
thisCommand.error(
|
|
169
|
+
"error: missing either option '-d, --definition <definition>' or option '-f, --definition-file <path>'",
|
|
170
|
+
{ exitCode: 1 /* PreExecutionFailure */ }
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/cli/CLI.ts
|
|
176
|
+
function makeProgram() {
|
|
177
|
+
const command = new import_commander.Command();
|
|
178
|
+
command.name("local-sfn").description(
|
|
179
|
+
`Execute an Amazon States Language state machine with the given inputs.
|
|
180
|
+
The result of each execution will be output in a new line and in the same order as its corresponding input.`
|
|
181
|
+
).helpOption("-h, --help", "Print help for command and exit.").configureHelp({ helpWidth: 80 }).addHelpText(
|
|
182
|
+
"after",
|
|
183
|
+
`
|
|
184
|
+
Exit codes:
|
|
185
|
+
0 All executions ran successfully.
|
|
186
|
+
1 An error occurred before the state machine could be executed.
|
|
187
|
+
2 At least one execution had an error.
|
|
188
|
+
|
|
189
|
+
Example calls:
|
|
190
|
+
$ local-sfn -f state-machine.json '{ "num1": 2, "num2": 2 }'
|
|
191
|
+
$ local-sfn -f state-machine.json -t SendRequest:./override.sh -w WaitResponse:2000 '{ "num1": 2, "num2": 2 }'
|
|
192
|
+
$ cat inputs.txt | local-sfn -f state-machine.json`
|
|
193
|
+
).version(version, "-V, --version", "Print the version number and exit.").addOption(
|
|
194
|
+
new import_commander.Option("-d, --definition <definition>", "A JSON definition of a state machine.").conflicts("definition-file").argParser((value) => parseDefinitionOption(command, value))
|
|
195
|
+
).addOption(
|
|
196
|
+
new import_commander.Option("-f, --definition-file <path>", "Path to a file containing a JSON state machine definition.").conflicts("definition").argParser((value) => parseDefinitionFileOption(command, value))
|
|
197
|
+
).addOption(
|
|
198
|
+
new import_commander.Option(
|
|
199
|
+
"-t, --override-task <mapping>",
|
|
200
|
+
"Override a Task state to run an executable file or script, instead of calling the service specified in the 'Resource' field of the state definition. The mapping value has to be provided in the format [TaskStateToOverride]:[path/to/override/script]. The override script will be passed the input of the Task state as first argument, which can then be used to compute the task result. The script must print the task result as a JSON value to the standard output."
|
|
201
|
+
).argParser(parseOverrideTaskOption)
|
|
202
|
+
).addOption(
|
|
203
|
+
new import_commander.Option(
|
|
204
|
+
"-w, --override-wait <mapping>",
|
|
205
|
+
"Override a Wait state to pause for the specified amount of milliseconds, instead of pausing for the duration specified in the state definition. The mapping value has to be provided in the format [WaitStateToOverride]:[number]."
|
|
206
|
+
).argParser(parseOverrideWaitOption)
|
|
207
|
+
).addOption(
|
|
208
|
+
new import_commander.Option("--no-jsonpath-validation", "Disable validation of JSONPath strings in the state machine definition.")
|
|
209
|
+
).addOption(new import_commander.Option("--no-arn-validation", "Disable validation of ARNs in the state machine definition.")).argument(
|
|
210
|
+
"[inputs...]",
|
|
211
|
+
"Input data for the state machine, can be any valid JSON value. Each input represents a state machine execution. If reading from the standard input, each line will be considered as an input.",
|
|
212
|
+
(value, previous) => parseInputArguments(command, value, previous)
|
|
213
|
+
).hook("preAction", preActionHook).action(commandAction);
|
|
214
|
+
return command;
|
|
215
|
+
}
|
|
216
|
+
if (require.main === module) {
|
|
217
|
+
(async function() {
|
|
218
|
+
const program = makeProgram();
|
|
219
|
+
if (process.stdin.isTTY) {
|
|
220
|
+
await program.parseAsync();
|
|
221
|
+
} else {
|
|
222
|
+
const rl = import_readline.default.createInterface({
|
|
223
|
+
input: process.stdin
|
|
224
|
+
});
|
|
225
|
+
const stdin = [];
|
|
226
|
+
for await (const line of rl) {
|
|
227
|
+
stdin.push(line);
|
|
228
|
+
}
|
|
229
|
+
await program.parseAsync([...process.argv, ...stdin.filter((line) => line)]);
|
|
230
|
+
}
|
|
231
|
+
})();
|
|
232
|
+
}
|
|
233
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
234
|
+
0 && (module.exports = {
|
|
235
|
+
makeProgram
|
|
236
|
+
});
|
|
@@ -19950,7 +19950,7 @@ function validateArgumentType(allowedTypes, argPosition, funcArg, funcName) {
|
|
|
19950
19950
|
if (matchesAllowedType)
|
|
19951
19951
|
break;
|
|
19952
19952
|
}
|
|
19953
|
-
const expectedType = allowedTypes.map((type) => `
|
|
19953
|
+
const expectedType = allowedTypes.map((type) => `'${type}'`).join(" | ");
|
|
19954
19954
|
if (!matchesAllowedType) {
|
|
19955
19955
|
throw new StatesRuntimeError(
|
|
19956
19956
|
`Intrinsic function ${funcName} expected argument ${argPosition} to be of type ${expectedType}, but received ${typeof funcArg}`
|
|
@@ -19978,7 +19978,7 @@ function validateArgumentConstraints(argConstraints, argPosition, funcArg, funcN
|
|
|
19978
19978
|
if (matchesAllConstraints)
|
|
19979
19979
|
break;
|
|
19980
19980
|
}
|
|
19981
|
-
const expectedConstraints = argConstraints.map((constraint) => `
|
|
19981
|
+
const expectedConstraints = argConstraints.map((constraint) => `'${constraint}'`).join(" | ");
|
|
19982
19982
|
if (!matchesAllConstraints) {
|
|
19983
19983
|
throw new StatesRuntimeError(
|
|
19984
19984
|
`Intrinsic function ${funcName} expected argument ${argPosition} to satisfy the following constraints: ${expectedConstraints}`
|
|
@@ -20600,7 +20600,7 @@ function processPayloadTemplate(payloadTemplate, json, context) {
|
|
|
20600
20600
|
let sanitizedKey = key;
|
|
20601
20601
|
let resolvedValue = value;
|
|
20602
20602
|
if (isPlainObj(value)) {
|
|
20603
|
-
resolvedValue = processPayloadTemplate(value, json);
|
|
20603
|
+
resolvedValue = processPayloadTemplate(value, json, context);
|
|
20604
20604
|
}
|
|
20605
20605
|
if (key.endsWith(".$") && typeof value === "string") {
|
|
20606
20606
|
sanitizedKey = key.replace(".$", "");
|
|
@@ -21048,7 +21048,10 @@ var MapStateAction = class extends BaseStateAction {
|
|
|
21048
21048
|
if (!Array.isArray(items)) {
|
|
21049
21049
|
throw new StatesRuntimeError("Input of Map state must be an array or ItemsPath property must point to an array");
|
|
21050
21050
|
}
|
|
21051
|
-
const iteratorStateMachine = new StateMachine(state.Iterator,
|
|
21051
|
+
const iteratorStateMachine = new StateMachine(state.Iterator, {
|
|
21052
|
+
...options?.stateMachineOptions,
|
|
21053
|
+
validationOptions: { _noValidate: true }
|
|
21054
|
+
});
|
|
21052
21055
|
const limit = (0, import_p_limit.default)(state.MaxConcurrency || DEFAULT_MAX_CONCURRENCY);
|
|
21053
21056
|
const processedItemsPromise = items.map(
|
|
21054
21057
|
(item, i3) => limit(() => this.processItem(iteratorStateMachine, item, input, context, i3, options))
|
|
@@ -21077,7 +21080,10 @@ var ParallelStateAction = class extends BaseStateAction {
|
|
|
21077
21080
|
this.executionAbortFuncs = [];
|
|
21078
21081
|
}
|
|
21079
21082
|
processBranch(branch, input, context, options) {
|
|
21080
|
-
const stateMachine = new StateMachine(branch,
|
|
21083
|
+
const stateMachine = new StateMachine(branch, {
|
|
21084
|
+
...options?.stateMachineOptions,
|
|
21085
|
+
validationOptions: { _noValidate: true }
|
|
21086
|
+
});
|
|
21081
21087
|
const execution = stateMachine.run(input, options?.runOptions);
|
|
21082
21088
|
this.executionAbortFuncs.push(execution.abort);
|
|
21083
21089
|
return execution.result;
|
|
@@ -27039,12 +27045,12 @@ var LambdaClient2 = class {
|
|
|
27039
27045
|
if (config) {
|
|
27040
27046
|
if (!config.region) {
|
|
27041
27047
|
throw new StatesRuntimeError(
|
|
27042
|
-
"
|
|
27048
|
+
"'awsConfig' option was specified for state machine, but 'region' property is not set"
|
|
27043
27049
|
);
|
|
27044
27050
|
}
|
|
27045
27051
|
if (config.credentials) {
|
|
27046
27052
|
const credentialsTypes = Object.keys(config.credentials);
|
|
27047
|
-
const credentialsNames = credentialsTypes.map((name) =>
|
|
27053
|
+
const credentialsNames = credentialsTypes.map((name) => `'${name}'`).join(", ");
|
|
27048
27054
|
if (credentialsTypes.length > 1) {
|
|
27049
27055
|
throw new StatesRuntimeError(
|
|
27050
27056
|
`More than one type of AWS credentials were specified: ${credentialsNames}. Only one type may be specified`
|
|
@@ -27078,7 +27084,7 @@ var LambdaClient2 = class {
|
|
|
27078
27084
|
}
|
|
27079
27085
|
if (invocationResult.FunctionError) {
|
|
27080
27086
|
const errorResult = resultValue;
|
|
27081
|
-
throw new FailStateError(errorResult.errorType, `Execution of Lambda function
|
|
27087
|
+
throw new FailStateError(errorResult.errorType, `Execution of Lambda function '${funcNameOrArn}' failed`);
|
|
27082
27088
|
}
|
|
27083
27089
|
return resultValue;
|
|
27084
27090
|
}
|
|
@@ -27389,6 +27395,7 @@ var StateExecutor = class {
|
|
|
27389
27395
|
// src/stateMachine/StateMachine.ts
|
|
27390
27396
|
var import_asl_validator = __toESM(require_validator(), 1);
|
|
27391
27397
|
var import_cloneDeep3 = __toESM(require_cloneDeep(), 1);
|
|
27398
|
+
var DEFAULT_MAX_EXECUTION_TIMEOUT = 2147483647e-3;
|
|
27392
27399
|
var StateMachine = class {
|
|
27393
27400
|
/**
|
|
27394
27401
|
* Constructs a new state machine.
|
|
@@ -27397,14 +27404,16 @@ var StateMachine = class {
|
|
|
27397
27404
|
* These options also apply to state machines defined in the `Iterator` field of `Map` states and in the `Branches` field of `Parallel` states.
|
|
27398
27405
|
*/
|
|
27399
27406
|
constructor(definition, stateMachineOptions) {
|
|
27400
|
-
|
|
27401
|
-
|
|
27402
|
-
|
|
27403
|
-
|
|
27404
|
-
|
|
27405
|
-
|
|
27406
|
-
|
|
27407
|
+
if (!stateMachineOptions?.validationOptions?._noValidate) {
|
|
27408
|
+
const { isValid, errorsText } = (0, import_asl_validator.default)(definition, {
|
|
27409
|
+
checkArn: true,
|
|
27410
|
+
checkPaths: true,
|
|
27411
|
+
...stateMachineOptions?.validationOptions
|
|
27412
|
+
});
|
|
27413
|
+
if (!isValid) {
|
|
27414
|
+
throw new Error(`State machine definition is invalid, see error(s) below:
|
|
27407
27415
|
${errorsText("\n")}`);
|
|
27416
|
+
}
|
|
27408
27417
|
}
|
|
27409
27418
|
this.definition = definition;
|
|
27410
27419
|
this.stateMachineOptions = stateMachineOptions;
|
|
@@ -27422,6 +27431,7 @@ var StateMachine = class {
|
|
|
27422
27431
|
*/
|
|
27423
27432
|
run(input, options) {
|
|
27424
27433
|
const abortController = new AbortController();
|
|
27434
|
+
const timeoutSeconds = this.definition.TimeoutSeconds ?? DEFAULT_MAX_EXECUTION_TIMEOUT;
|
|
27425
27435
|
let onAbortHandler;
|
|
27426
27436
|
const settleOnAbort = new Promise((resolve, reject) => {
|
|
27427
27437
|
if (options?.noThrowOnAbort) {
|
|
@@ -27431,25 +27441,27 @@ var StateMachine = class {
|
|
|
27431
27441
|
}
|
|
27432
27442
|
abortController.signal.addEventListener("abort", onAbortHandler);
|
|
27433
27443
|
});
|
|
27434
|
-
let
|
|
27435
|
-
|
|
27436
|
-
|
|
27437
|
-
|
|
27438
|
-
|
|
27439
|
-
|
|
27440
|
-
|
|
27441
|
-
}, this.definition.TimeoutSeconds * 1e3);
|
|
27442
|
-
});
|
|
27443
|
-
}
|
|
27444
|
-
const executionResult = this.execute(input, {
|
|
27445
|
-
stateMachineOptions: this.stateMachineOptions,
|
|
27446
|
-
runOptions: options,
|
|
27447
|
-
abortSignal: abortController.signal
|
|
27444
|
+
let timeoutId;
|
|
27445
|
+
const rejectOnTimeout = new Promise((_, reject) => {
|
|
27446
|
+
timeoutId = setTimeout(() => {
|
|
27447
|
+
abortController.signal.removeEventListener("abort", onAbortHandler);
|
|
27448
|
+
abortController.abort();
|
|
27449
|
+
reject(new StatesTimeoutError());
|
|
27450
|
+
}, timeoutSeconds * 1e3);
|
|
27448
27451
|
});
|
|
27449
|
-
const
|
|
27450
|
-
|
|
27451
|
-
|
|
27452
|
-
|
|
27452
|
+
const executionResult = this.execute(
|
|
27453
|
+
input,
|
|
27454
|
+
{
|
|
27455
|
+
stateMachineOptions: this.stateMachineOptions,
|
|
27456
|
+
runOptions: options,
|
|
27457
|
+
abortSignal: abortController.signal
|
|
27458
|
+
},
|
|
27459
|
+
() => {
|
|
27460
|
+
abortController.signal.removeEventListener("abort", onAbortHandler);
|
|
27461
|
+
clearTimeout(timeoutId);
|
|
27462
|
+
}
|
|
27463
|
+
);
|
|
27464
|
+
const racingPromises = [executionResult, settleOnAbort, rejectOnTimeout];
|
|
27453
27465
|
const result = Promise.race(racingPromises);
|
|
27454
27466
|
return {
|
|
27455
27467
|
abort: () => abortController.abort(),
|
|
@@ -27459,7 +27471,7 @@ var StateMachine = class {
|
|
|
27459
27471
|
/**
|
|
27460
27472
|
* Helper method that handles the execution of the machine states and the transitions between them.
|
|
27461
27473
|
*/
|
|
27462
|
-
async execute(input, options) {
|
|
27474
|
+
async execute(input, options, cleanupFn) {
|
|
27463
27475
|
let currState = this.definition.States[this.definition.StartAt];
|
|
27464
27476
|
let currStateName = this.definition.StartAt;
|
|
27465
27477
|
let currInput = (0, import_cloneDeep3.default)(input);
|
|
@@ -27477,6 +27489,8 @@ var StateMachine = class {
|
|
|
27477
27489
|
} while (!isEndState && !options.abortSignal.aborted);
|
|
27478
27490
|
} catch (error) {
|
|
27479
27491
|
throw new ExecutionError(error);
|
|
27492
|
+
} finally {
|
|
27493
|
+
cleanupFn();
|
|
27480
27494
|
}
|
|
27481
27495
|
return currResult;
|
|
27482
27496
|
}
|
package/build/main.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FromCognitoIdentityPoolParameters } from '@aws-sdk/credential-provider-cognito-identity/dist-types/fromCognitoIdentityPool';
|
|
2
|
-
import {
|
|
2
|
+
import { AwsCredentialIdentity } from '@aws-sdk/types/dist-types/identity/AwsCredentialIdentity';
|
|
3
3
|
|
|
4
4
|
type StateType = 'Task' | 'Parallel' | 'Map' | 'Pass' | 'Wait' | 'Choice' | 'Succeed' | 'Fail';
|
|
5
5
|
|
|
@@ -197,7 +197,7 @@ interface AWSConfig {
|
|
|
197
197
|
region: string;
|
|
198
198
|
credentials?: {
|
|
199
199
|
cognitoIdentityPool?: FromCognitoIdentityPoolParameters;
|
|
200
|
-
accessKeys?: Omit<
|
|
200
|
+
accessKeys?: Omit<AwsCredentialIdentity, 'expiration'>;
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
203
|
interface StateMachineOptions {
|
package/build/main.node.cjs
CHANGED
|
@@ -207,7 +207,7 @@ function validateArgumentType(allowedTypes, argPosition, funcArg, funcName) {
|
|
|
207
207
|
if (matchesAllowedType)
|
|
208
208
|
break;
|
|
209
209
|
}
|
|
210
|
-
const expectedType = allowedTypes.map((type) => `
|
|
210
|
+
const expectedType = allowedTypes.map((type) => `'${type}'`).join(" | ");
|
|
211
211
|
if (!matchesAllowedType) {
|
|
212
212
|
throw new StatesRuntimeError(
|
|
213
213
|
`Intrinsic function ${funcName} expected argument ${argPosition} to be of type ${expectedType}, but received ${typeof funcArg}`
|
|
@@ -235,7 +235,7 @@ function validateArgumentConstraints(argConstraints, argPosition, funcArg, funcN
|
|
|
235
235
|
if (matchesAllConstraints)
|
|
236
236
|
break;
|
|
237
237
|
}
|
|
238
|
-
const expectedConstraints = argConstraints.map((constraint) => `
|
|
238
|
+
const expectedConstraints = argConstraints.map((constraint) => `'${constraint}'`).join(" | ");
|
|
239
239
|
if (!matchesAllConstraints) {
|
|
240
240
|
throw new StatesRuntimeError(
|
|
241
241
|
`Intrinsic function ${funcName} expected argument ${argPosition} to satisfy the following constraints: ${expectedConstraints}`
|
|
@@ -857,7 +857,7 @@ function processPayloadTemplate(payloadTemplate, json, context) {
|
|
|
857
857
|
let sanitizedKey = key;
|
|
858
858
|
let resolvedValue = value;
|
|
859
859
|
if (isPlainObj(value)) {
|
|
860
|
-
resolvedValue = processPayloadTemplate(value, json);
|
|
860
|
+
resolvedValue = processPayloadTemplate(value, json, context);
|
|
861
861
|
}
|
|
862
862
|
if (key.endsWith(".$") && typeof value === "string") {
|
|
863
863
|
sanitizedKey = key.replace(".$", "");
|
|
@@ -1188,7 +1188,10 @@ var MapStateAction = class extends BaseStateAction {
|
|
|
1188
1188
|
if (!Array.isArray(items)) {
|
|
1189
1189
|
throw new StatesRuntimeError("Input of Map state must be an array or ItemsPath property must point to an array");
|
|
1190
1190
|
}
|
|
1191
|
-
const iteratorStateMachine = new StateMachine(state.Iterator,
|
|
1191
|
+
const iteratorStateMachine = new StateMachine(state.Iterator, {
|
|
1192
|
+
...options?.stateMachineOptions,
|
|
1193
|
+
validationOptions: { _noValidate: true }
|
|
1194
|
+
});
|
|
1192
1195
|
const limit = (0, import_p_limit.default)(state.MaxConcurrency || DEFAULT_MAX_CONCURRENCY);
|
|
1193
1196
|
const processedItemsPromise = items.map(
|
|
1194
1197
|
(item, i) => limit(() => this.processItem(iteratorStateMachine, item, input, context, i, options))
|
|
@@ -1217,7 +1220,10 @@ var ParallelStateAction = class extends BaseStateAction {
|
|
|
1217
1220
|
this.executionAbortFuncs = [];
|
|
1218
1221
|
}
|
|
1219
1222
|
processBranch(branch, input, context, options) {
|
|
1220
|
-
const stateMachine = new StateMachine(branch,
|
|
1223
|
+
const stateMachine = new StateMachine(branch, {
|
|
1224
|
+
...options?.stateMachineOptions,
|
|
1225
|
+
validationOptions: { _noValidate: true }
|
|
1226
|
+
});
|
|
1221
1227
|
const execution = stateMachine.run(input, options?.runOptions);
|
|
1222
1228
|
this.executionAbortFuncs.push(execution.abort);
|
|
1223
1229
|
return execution.result;
|
|
@@ -1273,12 +1279,12 @@ var LambdaClient = class {
|
|
|
1273
1279
|
if (config) {
|
|
1274
1280
|
if (!config.region) {
|
|
1275
1281
|
throw new StatesRuntimeError(
|
|
1276
|
-
"
|
|
1282
|
+
"'awsConfig' option was specified for state machine, but 'region' property is not set"
|
|
1277
1283
|
);
|
|
1278
1284
|
}
|
|
1279
1285
|
if (config.credentials) {
|
|
1280
1286
|
const credentialsTypes = Object.keys(config.credentials);
|
|
1281
|
-
const credentialsNames = credentialsTypes.map((name) =>
|
|
1287
|
+
const credentialsNames = credentialsTypes.map((name) => `'${name}'`).join(", ");
|
|
1282
1288
|
if (credentialsTypes.length > 1) {
|
|
1283
1289
|
throw new StatesRuntimeError(
|
|
1284
1290
|
`More than one type of AWS credentials were specified: ${credentialsNames}. Only one type may be specified`
|
|
@@ -1312,7 +1318,7 @@ var LambdaClient = class {
|
|
|
1312
1318
|
}
|
|
1313
1319
|
if (invocationResult.FunctionError) {
|
|
1314
1320
|
const errorResult = resultValue;
|
|
1315
|
-
throw new FailStateError(errorResult.errorType, `Execution of Lambda function
|
|
1321
|
+
throw new FailStateError(errorResult.errorType, `Execution of Lambda function '${funcNameOrArn}' failed`);
|
|
1316
1322
|
}
|
|
1317
1323
|
return resultValue;
|
|
1318
1324
|
}
|
|
@@ -1623,6 +1629,7 @@ var StateExecutor = class {
|
|
|
1623
1629
|
// src/stateMachine/StateMachine.ts
|
|
1624
1630
|
var import_asl_validator = __toESM(require("asl-validator"), 1);
|
|
1625
1631
|
var import_cloneDeep3 = __toESM(require("lodash/cloneDeep.js"), 1);
|
|
1632
|
+
var DEFAULT_MAX_EXECUTION_TIMEOUT = 2147483647e-3;
|
|
1626
1633
|
var StateMachine = class {
|
|
1627
1634
|
/**
|
|
1628
1635
|
* Constructs a new state machine.
|
|
@@ -1631,14 +1638,16 @@ var StateMachine = class {
|
|
|
1631
1638
|
* These options also apply to state machines defined in the `Iterator` field of `Map` states and in the `Branches` field of `Parallel` states.
|
|
1632
1639
|
*/
|
|
1633
1640
|
constructor(definition, stateMachineOptions) {
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
+
if (!stateMachineOptions?.validationOptions?._noValidate) {
|
|
1642
|
+
const { isValid, errorsText } = (0, import_asl_validator.default)(definition, {
|
|
1643
|
+
checkArn: true,
|
|
1644
|
+
checkPaths: true,
|
|
1645
|
+
...stateMachineOptions?.validationOptions
|
|
1646
|
+
});
|
|
1647
|
+
if (!isValid) {
|
|
1648
|
+
throw new Error(`State machine definition is invalid, see error(s) below:
|
|
1641
1649
|
${errorsText("\n")}`);
|
|
1650
|
+
}
|
|
1642
1651
|
}
|
|
1643
1652
|
this.definition = definition;
|
|
1644
1653
|
this.stateMachineOptions = stateMachineOptions;
|
|
@@ -1656,6 +1665,7 @@ var StateMachine = class {
|
|
|
1656
1665
|
*/
|
|
1657
1666
|
run(input, options) {
|
|
1658
1667
|
const abortController = new AbortController();
|
|
1668
|
+
const timeoutSeconds = this.definition.TimeoutSeconds ?? DEFAULT_MAX_EXECUTION_TIMEOUT;
|
|
1659
1669
|
let onAbortHandler;
|
|
1660
1670
|
const settleOnAbort = new Promise((resolve, reject) => {
|
|
1661
1671
|
if (options?.noThrowOnAbort) {
|
|
@@ -1665,25 +1675,27 @@ var StateMachine = class {
|
|
|
1665
1675
|
}
|
|
1666
1676
|
abortController.signal.addEventListener("abort", onAbortHandler);
|
|
1667
1677
|
});
|
|
1668
|
-
let
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
}, this.definition.TimeoutSeconds * 1e3);
|
|
1676
|
-
});
|
|
1677
|
-
}
|
|
1678
|
-
const executionResult = this.execute(input, {
|
|
1679
|
-
stateMachineOptions: this.stateMachineOptions,
|
|
1680
|
-
runOptions: options,
|
|
1681
|
-
abortSignal: abortController.signal
|
|
1678
|
+
let timeoutId;
|
|
1679
|
+
const rejectOnTimeout = new Promise((_, reject) => {
|
|
1680
|
+
timeoutId = setTimeout(() => {
|
|
1681
|
+
abortController.signal.removeEventListener("abort", onAbortHandler);
|
|
1682
|
+
abortController.abort();
|
|
1683
|
+
reject(new StatesTimeoutError());
|
|
1684
|
+
}, timeoutSeconds * 1e3);
|
|
1682
1685
|
});
|
|
1683
|
-
const
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1686
|
+
const executionResult = this.execute(
|
|
1687
|
+
input,
|
|
1688
|
+
{
|
|
1689
|
+
stateMachineOptions: this.stateMachineOptions,
|
|
1690
|
+
runOptions: options,
|
|
1691
|
+
abortSignal: abortController.signal
|
|
1692
|
+
},
|
|
1693
|
+
() => {
|
|
1694
|
+
abortController.signal.removeEventListener("abort", onAbortHandler);
|
|
1695
|
+
clearTimeout(timeoutId);
|
|
1696
|
+
}
|
|
1697
|
+
);
|
|
1698
|
+
const racingPromises = [executionResult, settleOnAbort, rejectOnTimeout];
|
|
1687
1699
|
const result = Promise.race(racingPromises);
|
|
1688
1700
|
return {
|
|
1689
1701
|
abort: () => abortController.abort(),
|
|
@@ -1693,7 +1705,7 @@ var StateMachine = class {
|
|
|
1693
1705
|
/**
|
|
1694
1706
|
* Helper method that handles the execution of the machine states and the transitions between them.
|
|
1695
1707
|
*/
|
|
1696
|
-
async execute(input, options) {
|
|
1708
|
+
async execute(input, options, cleanupFn) {
|
|
1697
1709
|
let currState = this.definition.States[this.definition.StartAt];
|
|
1698
1710
|
let currStateName = this.definition.StartAt;
|
|
1699
1711
|
let currInput = (0, import_cloneDeep3.default)(input);
|
|
@@ -1711,6 +1723,8 @@ var StateMachine = class {
|
|
|
1711
1723
|
} while (!isEndState && !options.abortSignal.aborted);
|
|
1712
1724
|
} catch (error) {
|
|
1713
1725
|
throw new ExecutionError(error);
|
|
1726
|
+
} finally {
|
|
1727
|
+
cleanupFn();
|
|
1714
1728
|
}
|
|
1715
1729
|
return currResult;
|
|
1716
1730
|
}
|
package/build/main.node.esm.js
CHANGED
|
@@ -169,7 +169,7 @@ function validateArgumentType(allowedTypes, argPosition, funcArg, funcName) {
|
|
|
169
169
|
if (matchesAllowedType)
|
|
170
170
|
break;
|
|
171
171
|
}
|
|
172
|
-
const expectedType = allowedTypes.map((type) => `
|
|
172
|
+
const expectedType = allowedTypes.map((type) => `'${type}'`).join(" | ");
|
|
173
173
|
if (!matchesAllowedType) {
|
|
174
174
|
throw new StatesRuntimeError(
|
|
175
175
|
`Intrinsic function ${funcName} expected argument ${argPosition} to be of type ${expectedType}, but received ${typeof funcArg}`
|
|
@@ -197,7 +197,7 @@ function validateArgumentConstraints(argConstraints, argPosition, funcArg, funcN
|
|
|
197
197
|
if (matchesAllConstraints)
|
|
198
198
|
break;
|
|
199
199
|
}
|
|
200
|
-
const expectedConstraints = argConstraints.map((constraint) => `
|
|
200
|
+
const expectedConstraints = argConstraints.map((constraint) => `'${constraint}'`).join(" | ");
|
|
201
201
|
if (!matchesAllConstraints) {
|
|
202
202
|
throw new StatesRuntimeError(
|
|
203
203
|
`Intrinsic function ${funcName} expected argument ${argPosition} to satisfy the following constraints: ${expectedConstraints}`
|
|
@@ -819,7 +819,7 @@ function processPayloadTemplate(payloadTemplate, json, context) {
|
|
|
819
819
|
let sanitizedKey = key;
|
|
820
820
|
let resolvedValue = value;
|
|
821
821
|
if (isPlainObj(value)) {
|
|
822
|
-
resolvedValue = processPayloadTemplate(value, json);
|
|
822
|
+
resolvedValue = processPayloadTemplate(value, json, context);
|
|
823
823
|
}
|
|
824
824
|
if (key.endsWith(".$") && typeof value === "string") {
|
|
825
825
|
sanitizedKey = key.replace(".$", "");
|
|
@@ -1150,7 +1150,10 @@ var MapStateAction = class extends BaseStateAction {
|
|
|
1150
1150
|
if (!Array.isArray(items)) {
|
|
1151
1151
|
throw new StatesRuntimeError("Input of Map state must be an array or ItemsPath property must point to an array");
|
|
1152
1152
|
}
|
|
1153
|
-
const iteratorStateMachine = new StateMachine(state.Iterator,
|
|
1153
|
+
const iteratorStateMachine = new StateMachine(state.Iterator, {
|
|
1154
|
+
...options?.stateMachineOptions,
|
|
1155
|
+
validationOptions: { _noValidate: true }
|
|
1156
|
+
});
|
|
1154
1157
|
const limit = pLimit(state.MaxConcurrency || DEFAULT_MAX_CONCURRENCY);
|
|
1155
1158
|
const processedItemsPromise = items.map(
|
|
1156
1159
|
(item, i) => limit(() => this.processItem(iteratorStateMachine, item, input, context, i, options))
|
|
@@ -1179,7 +1182,10 @@ var ParallelStateAction = class extends BaseStateAction {
|
|
|
1179
1182
|
this.executionAbortFuncs = [];
|
|
1180
1183
|
}
|
|
1181
1184
|
processBranch(branch, input, context, options) {
|
|
1182
|
-
const stateMachine = new StateMachine(branch,
|
|
1185
|
+
const stateMachine = new StateMachine(branch, {
|
|
1186
|
+
...options?.stateMachineOptions,
|
|
1187
|
+
validationOptions: { _noValidate: true }
|
|
1188
|
+
});
|
|
1183
1189
|
const execution = stateMachine.run(input, options?.runOptions);
|
|
1184
1190
|
this.executionAbortFuncs.push(execution.abort);
|
|
1185
1191
|
return execution.result;
|
|
@@ -1235,12 +1241,12 @@ var LambdaClient = class {
|
|
|
1235
1241
|
if (config) {
|
|
1236
1242
|
if (!config.region) {
|
|
1237
1243
|
throw new StatesRuntimeError(
|
|
1238
|
-
"
|
|
1244
|
+
"'awsConfig' option was specified for state machine, but 'region' property is not set"
|
|
1239
1245
|
);
|
|
1240
1246
|
}
|
|
1241
1247
|
if (config.credentials) {
|
|
1242
1248
|
const credentialsTypes = Object.keys(config.credentials);
|
|
1243
|
-
const credentialsNames = credentialsTypes.map((name) =>
|
|
1249
|
+
const credentialsNames = credentialsTypes.map((name) => `'${name}'`).join(", ");
|
|
1244
1250
|
if (credentialsTypes.length > 1) {
|
|
1245
1251
|
throw new StatesRuntimeError(
|
|
1246
1252
|
`More than one type of AWS credentials were specified: ${credentialsNames}. Only one type may be specified`
|
|
@@ -1274,7 +1280,7 @@ var LambdaClient = class {
|
|
|
1274
1280
|
}
|
|
1275
1281
|
if (invocationResult.FunctionError) {
|
|
1276
1282
|
const errorResult = resultValue;
|
|
1277
|
-
throw new FailStateError(errorResult.errorType, `Execution of Lambda function
|
|
1283
|
+
throw new FailStateError(errorResult.errorType, `Execution of Lambda function '${funcNameOrArn}' failed`);
|
|
1278
1284
|
}
|
|
1279
1285
|
return resultValue;
|
|
1280
1286
|
}
|
|
@@ -1585,6 +1591,7 @@ var StateExecutor = class {
|
|
|
1585
1591
|
// src/stateMachine/StateMachine.ts
|
|
1586
1592
|
import aslValidator from "asl-validator";
|
|
1587
1593
|
import cloneDeep3 from "lodash/cloneDeep.js";
|
|
1594
|
+
var DEFAULT_MAX_EXECUTION_TIMEOUT = 2147483647e-3;
|
|
1588
1595
|
var StateMachine = class {
|
|
1589
1596
|
/**
|
|
1590
1597
|
* Constructs a new state machine.
|
|
@@ -1593,14 +1600,16 @@ var StateMachine = class {
|
|
|
1593
1600
|
* These options also apply to state machines defined in the `Iterator` field of `Map` states and in the `Branches` field of `Parallel` states.
|
|
1594
1601
|
*/
|
|
1595
1602
|
constructor(definition, stateMachineOptions) {
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
+
if (!stateMachineOptions?.validationOptions?._noValidate) {
|
|
1604
|
+
const { isValid, errorsText } = aslValidator(definition, {
|
|
1605
|
+
checkArn: true,
|
|
1606
|
+
checkPaths: true,
|
|
1607
|
+
...stateMachineOptions?.validationOptions
|
|
1608
|
+
});
|
|
1609
|
+
if (!isValid) {
|
|
1610
|
+
throw new Error(`State machine definition is invalid, see error(s) below:
|
|
1603
1611
|
${errorsText("\n")}`);
|
|
1612
|
+
}
|
|
1604
1613
|
}
|
|
1605
1614
|
this.definition = definition;
|
|
1606
1615
|
this.stateMachineOptions = stateMachineOptions;
|
|
@@ -1618,6 +1627,7 @@ var StateMachine = class {
|
|
|
1618
1627
|
*/
|
|
1619
1628
|
run(input, options) {
|
|
1620
1629
|
const abortController = new AbortController();
|
|
1630
|
+
const timeoutSeconds = this.definition.TimeoutSeconds ?? DEFAULT_MAX_EXECUTION_TIMEOUT;
|
|
1621
1631
|
let onAbortHandler;
|
|
1622
1632
|
const settleOnAbort = new Promise((resolve, reject) => {
|
|
1623
1633
|
if (options?.noThrowOnAbort) {
|
|
@@ -1627,25 +1637,27 @@ var StateMachine = class {
|
|
|
1627
1637
|
}
|
|
1628
1638
|
abortController.signal.addEventListener("abort", onAbortHandler);
|
|
1629
1639
|
});
|
|
1630
|
-
let
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
}, this.definition.TimeoutSeconds * 1e3);
|
|
1638
|
-
});
|
|
1639
|
-
}
|
|
1640
|
-
const executionResult = this.execute(input, {
|
|
1641
|
-
stateMachineOptions: this.stateMachineOptions,
|
|
1642
|
-
runOptions: options,
|
|
1643
|
-
abortSignal: abortController.signal
|
|
1640
|
+
let timeoutId;
|
|
1641
|
+
const rejectOnTimeout = new Promise((_, reject) => {
|
|
1642
|
+
timeoutId = setTimeout(() => {
|
|
1643
|
+
abortController.signal.removeEventListener("abort", onAbortHandler);
|
|
1644
|
+
abortController.abort();
|
|
1645
|
+
reject(new StatesTimeoutError());
|
|
1646
|
+
}, timeoutSeconds * 1e3);
|
|
1644
1647
|
});
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1648
|
+
const executionResult = this.execute(
|
|
1649
|
+
input,
|
|
1650
|
+
{
|
|
1651
|
+
stateMachineOptions: this.stateMachineOptions,
|
|
1652
|
+
runOptions: options,
|
|
1653
|
+
abortSignal: abortController.signal
|
|
1654
|
+
},
|
|
1655
|
+
() => {
|
|
1656
|
+
abortController.signal.removeEventListener("abort", onAbortHandler);
|
|
1657
|
+
clearTimeout(timeoutId);
|
|
1658
|
+
}
|
|
1659
|
+
);
|
|
1660
|
+
const racingPromises = [executionResult, settleOnAbort, rejectOnTimeout];
|
|
1649
1661
|
const result = Promise.race(racingPromises);
|
|
1650
1662
|
return {
|
|
1651
1663
|
abort: () => abortController.abort(),
|
|
@@ -1655,7 +1667,7 @@ var StateMachine = class {
|
|
|
1655
1667
|
/**
|
|
1656
1668
|
* Helper method that handles the execution of the machine states and the transitions between them.
|
|
1657
1669
|
*/
|
|
1658
|
-
async execute(input, options) {
|
|
1670
|
+
async execute(input, options, cleanupFn) {
|
|
1659
1671
|
let currState = this.definition.States[this.definition.StartAt];
|
|
1660
1672
|
let currStateName = this.definition.StartAt;
|
|
1661
1673
|
let currInput = cloneDeep3(input);
|
|
@@ -1673,6 +1685,8 @@ var StateMachine = class {
|
|
|
1673
1685
|
} while (!isEndState && !options.abortSignal.aborted);
|
|
1674
1686
|
} catch (error) {
|
|
1675
1687
|
throw new ExecutionError(error);
|
|
1688
|
+
} finally {
|
|
1689
|
+
cleanupFn();
|
|
1676
1690
|
}
|
|
1677
1691
|
return currResult;
|
|
1678
1692
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aws-local-stepfunctions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Execute an AWS Step Function locally",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aws",
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
"browser": "./build/main.browser.esm.js",
|
|
36
36
|
"default": "./build/main.browser.esm.js"
|
|
37
37
|
},
|
|
38
|
+
"bin": {
|
|
39
|
+
"local-sfn": "./build/CLI.cjs"
|
|
40
|
+
},
|
|
38
41
|
"scripts": {
|
|
39
42
|
"test": "jest",
|
|
40
43
|
"build": "tsup",
|
|
@@ -64,6 +67,7 @@
|
|
|
64
67
|
"@aws-sdk/client-lambda": "^3.314.0",
|
|
65
68
|
"@aws-sdk/credential-providers": "^3.312.0",
|
|
66
69
|
"asl-validator": "^3.5.1",
|
|
70
|
+
"commander": "^10.0.1",
|
|
67
71
|
"crypto-js": "^4.1.1",
|
|
68
72
|
"jsonpath-plus": "^7.2.0",
|
|
69
73
|
"lodash": "^4.17.21",
|