Skip to content

Commit 55e809f

Browse files
authored
Merge pull request #71 from nibble-4bits/refactor-14
Refactor 14
2 parents 316859d + 05ba0ac commit 55e809f

19 files changed

+153
-55
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
build/
22
examples/
3+
bin/

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules/
22
build/
3+
bin/

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
build/
2+
bin/

README.md

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This package lets you run AWS Step Functions state machines completely locally,
2525
## Table of contents
2626

2727
- [Features](#features)
28+
- [Use cases](#use-cases)
2829
- [Installation](#installation)
2930
- [Importing](#importing)
3031
- [Node.js](#nodejs)
@@ -50,6 +51,16 @@ This package lets you run AWS Step Functions state machines completely locally,
5051

5152
To see the list of features defined in the specification that have full support, partial support, or no support, refer to [this document](/docs/feature-support.md).
5253

54+
## Use cases
55+
56+
Why would you want to use this package? Below is a non-exhaustive list of use cases for `aws-local-stepfunctions`:
57+
58+
- Testing state machines changes locally before deploying them to AWS.
59+
- Testing the integration between a state machine and the Lambda functions associated with it in `Task` states.
60+
- Debugging the code of associated Lambda functions interactively using the [`Task` state resource override feature](/docs/feature-support.md#task-state-resource-override).
61+
- Debugging a state machine by using the [event logs feature](/docs/feature-support.md#execution-event-logs), to better understand the transitions between states and how data flows between them.
62+
- Running state machines in the browser (not possible with [AWS Step Functions Local](https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local.html)).
63+
5364
## Installation
5465

5566
```sh
@@ -104,7 +115,7 @@ The constructor takes the following parameters:
104115
- `checkPaths`: If set to `false`, won't validate JSONPaths.
105116
- `checkArn`: If set to `false`, won't validate ARN syntax in `Task` states.
106117
- `noValidate`: If set to `true`, will skip validation of the definition entirely.
107-
> NOTE: Use this option at your own risk, there are no guarantees when passing an invalid/non-standard definition to the state machine. Running it might result in undefined behavior.
118+
> NOTE: Use this option at your own risk, there are no guarantees when passing an invalid or non-standard definition to the state machine. Running it might result in undefined/unsupported behavior.
108119
- `awsConfig?`: An object that specifies the [AWS region and credentials](/docs/feature-support.md#providing-aws-credentials-and-region-to-execute-lambda-functions-specified-in-task-states) to use when invoking a Lambda function in a `Task` state. If not set, the AWS config will be resolved based on the [credentials provider chain](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html) of the AWS SDK for JavaScript V3. You don't need to use this option if you have a [shared config/credentials file](https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html) (for example, if you have the [AWS CLI](https://aws.amazon.com/cli/) installed) or if you use a local override for all of your `Task` states.
109120
- `region`: The AWS region where the Lambda functions are created.
110121
- `credentials`: An object that specifies which type of credentials to use.
@@ -138,11 +149,7 @@ const stateMachine = new StateMachine(machineDefinition, {
138149

139150
### `StateMachine.run(input[, options])`
140151

141-
Runs the state machine with the given `input` parameter and returns an object with the following properties:
142-
143-
- `result`: A `Promise` that resolves with the result of the execution once it terminates.
144-
- `abort`: A function that takes no parameters and doesn't return any value. If called, [aborts the execution](/docs/feature-support.md#abort-a-running-execution) and throws an `ExecutionAbortedError`, unless the `noThrowOnAbort` option is set.
145-
- `eventLogs`: An `AsyncGenerator` that [produces a log of events](/docs/feature-support.md#execution-event-logs) as the execution runs. To learn more about the events, their type, and their format, see the [following document](/docs/execution-event-logs.md).
152+
Runs the state machine with the given `input`.
146153

147154
Each execution is independent of all others, meaning that you can concurrently call this method as many times as needed, without worrying about race conditions.
148155

@@ -156,6 +163,14 @@ Each execution is independent of all others, meaning that you can concurrently c
156163
- `noThrowOnAbort?`: If this option is set to `true`, aborting the execution will simply return `null` as result instead of throwing.
157164
- `context?`: An object that will be used as the [Context Object](https://docs.aws.amazon.com/step-functions/latest/dg/input-output-contextobject.html) for the execution. If not passed, the Context Object will default to an empty object. This option is useful to mock the Context Object in case your definition references it in a JSONPath.
158165

166+
#### Return value
167+
168+
Returns an object that has the following properties:
169+
170+
- `result`: A `Promise` that resolves with the result of the execution, if it ends successfully.
171+
- `abort`: A function that takes no parameters and doesn't return any value. If called, [aborts the execution](/docs/feature-support.md#abort-a-running-execution) and throws an `ExecutionAbortedError`, unless the `noThrowOnAbort` option is set.
172+
- `eventLogs`: An `AsyncGenerator` that [produces a log of events](/docs/feature-support.md#execution-event-logs) as the execution runs. To learn more about the events, their type, and their format, see the [following document](/docs/execution-event-logs.md).
173+
159174
#### Basic example:
160175

161176
```js
@@ -383,8 +398,12 @@ Before attempting to run the state machine with the given inputs, the state mach
383398

384399
- JSONPath strings are valid.
385400
- ARNs in the `Resource` field of `Task` states are valid.
401+
- There are no invalid fields.
402+
- All states in the definition can be reached.
403+
404+
If any of these checks fail, `local-sfn` will print the validation error and exit. To partially 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.
386405

387-
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.
406+
Alternatively, if you want to completely disable all validations, you can pass the `--no-validation` option. Be aware that passing this option implies no guarantees if the provided definition is invalid or contains non-standard fields: running it might result in undefined/unsupported behavior, so use at your own risk.
388407

389408
### Exit codes
390409

__tests__/cli/CLI.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('CLI', () => {
3434

3535
await expect(program.parseAsync([], { from: 'user' })).rejects.toThrow();
3636
expect(helpStr).toBe(
37-
'Usage: local-sfn [options] [inputs...]\n\nExecute an Amazon States Language state machine with the given inputs.\nThe result of each execution will be output in a new line and in the same order\nas its corresponding input.\n\nArguments:\n inputs Input data for the state machine, can be any\n valid JSON value. Each input represents a\n state machine execution.\n \n When reading from the standard input, if the\n first line can be parsed as a single JSON\n value, then each line will be considered as an\n input. Otherwise, the entire standard input\n will be considered as a single JSON input.\n\nOptions:\n -V, --version Print the version number and exit.\n -d, --definition <definition> A JSON definition of a state machine.\n -f, --definition-file <path> Path to a file containing a JSON state machine\n definition.\n -t, --override-task <mapping> Override a Task state to run an executable\n file or script, instead of calling the service\n specified in the \'Resource\' field of the state\n definition. The mapping value has to be\n provided in the format\n [TaskStateToOverride]:[path/to/override/script].\n The override script will be passed the input\n of the Task state as first argument, which can\n then be used to compute the task result. The\n script must print the task result as a JSON\n value to the standard output.\n -w, --override-wait <mapping> Override a Wait state to pause for the\n specified amount of milliseconds, instead of\n pausing for the duration specified in the\n state definition. The mapping value has to be\n provided in the format\n [WaitStateToOverride]:[number].\n --context <json> A JSON object that will be passed to each\n execution as the context object.\n --context-file <path> Path to a file containing a JSON object that\n will be passed to each execution as the\n context object.\n --no-jsonpath-validation Disable validation of JSONPath strings in the\n state machine definition.\n --no-arn-validation Disable validation of ARNs in the state\n machine definition.\n -h, --help Print help for command and exit.\n\nExit codes:\n 0 All executions ran successfully.\n 1 An error occurred before the state machine could be executed.\n 2 At least one execution had an error.\n\nExample calls:\n $ local-sfn -f state-machine.json \'{ "num1": 2, "num2": 2 }\'\n $ local-sfn -f state-machine.json -t SendRequest:./override.sh -w WaitResponse:2000 \'{ "num1": 2, "num2": 2 }\'\n $ cat inputs.txt | local-sfn -f state-machine.json\n'
37+
'Usage: local-sfn [options] [inputs...]\n\nExecute an Amazon States Language state machine with the given inputs.\nThe result of each execution will be output in a new line and in the same order\nas its corresponding input.\n\nArguments:\n inputs Input data for the state machine, can be any\n valid JSON value. Each input represents a\n state machine execution.\n \n When reading from the standard input, if the\n first line can be parsed as a single JSON\n value, then each line will be considered as an\n input. Otherwise, the entire standard input\n will be considered as a single JSON input.\n\nOptions:\n -V, --version Print the version number and exit.\n -d, --definition <definition> A JSON definition of a state machine.\n -f, --definition-file <path> Path to a file containing a JSON state machine\n definition.\n -t, --override-task <mapping> Override a Task state to run an executable\n file or script, instead of calling the service\n specified in the \'Resource\' field of the state\n definition. The mapping value has to be\n provided in the format\n [TaskStateToOverride]:[path/to/override/script].\n The override script will be passed the input\n of the Task state as first argument, which can\n then be used to compute the task result. The\n script must print the task result as a JSON\n value to the standard output.\n -w, --override-wait <mapping> Override a Wait state to pause for the\n specified amount of milliseconds, instead of\n pausing for the duration specified in the\n state definition. The mapping value has to be\n provided in the format\n [WaitStateToOverride]:[number].\n --context <json> A JSON object that will be passed to each\n execution as the context object.\n --context-file <path> Path to a file containing a JSON object that\n will be passed to each execution as the\n context object.\n --no-jsonpath-validation Disable validation of JSONPath strings in the\n state machine definition.\n --no-arn-validation Disable validation of ARNs in the state\n machine definition.\n --no-validation Disable validation of the state machine\n definition entirely. Use this option at your\n own risk, there are no guarantees when passing\n an invalid or non-standard definition to the\n state machine. Running it might result in\n undefined/unsupported behavior.\n -h, --help Print help for command and exit.\n\nExit codes:\n 0 All executions ran successfully.\n 1 An error occurred before the state machine could be executed.\n 2 At least one execution had an error.\n\nExample calls:\n $ local-sfn -f state-machine.json \'{ "num1": 2, "num2": 2 }\'\n $ local-sfn -f state-machine.json -t SendRequest:./override.sh -w WaitResponse:2000 \'{ "num1": 2, "num2": 2 }\'\n $ cat inputs.txt | local-sfn -f state-machine.json\n'
3838
);
3939
});
4040
});
@@ -275,6 +275,33 @@ describe('CLI', () => {
275275

276276
expect(consoleLogMock).toHaveBeenCalled();
277277
});
278+
279+
test('should NOT print error when passing a definition that does not conform to the spec and --no-validation option is passed', async () => {
280+
const consoleLogMock = jest.fn();
281+
jest.spyOn(LambdaClient.prototype, 'invokeFunction').mockImplementation(jest.fn());
282+
jest.spyOn(console, 'log').mockImplementation(consoleLogMock);
283+
284+
const definition = `{
285+
"StartAt": "AddNumbers",
286+
"InvalidTopLevelField": false,
287+
"States": {
288+
"AddNumbers": {
289+
"Type": "Task",
290+
"Resource": "invalid-arn",
291+
"End": true
292+
},
293+
"UnreachableState": {
294+
"Type": "CustomType"
295+
}
296+
}
297+
}`;
298+
299+
const program = makeProgram();
300+
301+
await program.parseAsync(['-d', definition, '--no-validation', '{}'], { from: 'user' });
302+
303+
expect(consoleLogMock).toHaveBeenCalled();
304+
});
278305
});
279306

280307
describe('State machine execution', () => {

__tests__/stateActions/WaitStateAction.test.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,13 @@ describe('Wait State', () => {
2727
const abortSignal = new AbortController().signal;
2828
const options = {
2929
abortSignal,
30-
rootAbortSignal: undefined,
3130
waitTimeOverrideOption: undefined,
3231
};
3332

3433
const waitStateAction = new WaitStateAction(definition, stateName);
3534
const { stateResult } = await waitStateAction.execute(input, context, options);
3635

37-
expect(mockSleepFunction).toHaveBeenCalledWith(10000, abortSignal, undefined);
36+
expect(mockSleepFunction).toHaveBeenCalledWith(10000, abortSignal);
3837
expect(stateResult).toEqual({ prop1: 'test', prop2: 12345 });
3938
});
4039

@@ -50,14 +49,13 @@ describe('Wait State', () => {
5049
const abortSignal = new AbortController().signal;
5150
const options = {
5251
abortSignal,
53-
rootAbortSignal: undefined,
5452
waitTimeOverrideOption: undefined,
5553
};
5654

5755
const waitStateAction = new WaitStateAction(definition, stateName);
5856
const { stateResult } = await waitStateAction.execute(input, context, options);
5957

60-
expect(mockSleepFunction).toHaveBeenCalledWith(20700000, abortSignal, undefined);
58+
expect(mockSleepFunction).toHaveBeenCalledWith(20700000, abortSignal);
6159
expect(stateResult).toEqual({ prop1: 'test', prop2: 12345 });
6260
});
6361

@@ -73,14 +71,13 @@ describe('Wait State', () => {
7371
const abortSignal = new AbortController().signal;
7472
const options = {
7573
abortSignal,
76-
rootAbortSignal: undefined,
7774
waitTimeOverrideOption: undefined,
7875
};
7976

8077
const waitStateAction = new WaitStateAction(definition, stateName);
8178
const { stateResult } = await waitStateAction.execute(input, context, options);
8279

83-
expect(mockSleepFunction).toHaveBeenCalledWith(10000, abortSignal, undefined);
80+
expect(mockSleepFunction).toHaveBeenCalledWith(10000, abortSignal);
8481
expect(stateResult).toEqual({ waitFor: 10 });
8582
});
8683

@@ -96,14 +93,13 @@ describe('Wait State', () => {
9693
const abortSignal = new AbortController().signal;
9794
const options = {
9895
abortSignal,
99-
rootAbortSignal: undefined,
10096
waitTimeOverrideOption: undefined,
10197
};
10298

10399
const waitStateAction = new WaitStateAction(definition, stateName);
104100
const { stateResult } = await waitStateAction.execute(input, context, options);
105101

106-
expect(mockSleepFunction).toHaveBeenCalledWith(20700000, abortSignal, undefined);
102+
expect(mockSleepFunction).toHaveBeenCalledWith(20700000, abortSignal);
107103
expect(stateResult).toEqual({ waitUntil: '2022-12-05T05:45:00Z' });
108104
});
109105

@@ -117,14 +113,13 @@ describe('Wait State', () => {
117113
const input = { waitUntil: '2022-12-05T05:45:00Z' };
118114
const context = {};
119115
const abortSignal = new AbortController().signal;
120-
const rootAbortSignal = new AbortController().signal;
121-
const options = { waitTimeOverrideOption: 1500, abortSignal, rootAbortSignal };
116+
const options = { waitTimeOverrideOption: 1500, abortSignal };
122117

123118
const waitStateAction = new WaitStateAction(definition, stateName);
124119
const { stateResult } = await waitStateAction.execute(input, context, options);
125120

126121
expect(mockSleepFunction).toHaveBeenCalledTimes(1);
127-
expect(mockSleepFunction).toHaveBeenCalledWith(1500, abortSignal, rootAbortSignal);
122+
expect(mockSleepFunction).toHaveBeenCalledWith(1500, abortSignal);
128123
expect(stateResult).toEqual({ waitUntil: '2022-12-05T05:45:00Z' });
129124
});
130125

@@ -140,7 +135,6 @@ describe('Wait State', () => {
140135
const abortSignal = new AbortController().signal;
141136
const options = {
142137
abortSignal,
143-
rootAbortSignal: undefined,
144138
waitTimeOverrideOption: undefined,
145139
};
146140

@@ -163,7 +157,6 @@ describe('Wait State', () => {
163157
const abortSignal = new AbortController().signal;
164158
const options = {
165159
abortSignal,
166-
rootAbortSignal: undefined,
167160
waitTimeOverrideOption: undefined,
168161
};
169162

__tests__/util/index.test.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,6 @@ describe('Utils', () => {
4747

4848
await expect(sleepPromise).resolves.not.toThrow();
4949
});
50-
51-
test('should resolve promise when sleep is aborted using `rootAbortController` argument', async () => {
52-
const abortController = new AbortController();
53-
const rootAbortController = new AbortController();
54-
55-
const sleepPromise = sleep(1000, abortController.signal, rootAbortController.signal);
56-
rootAbortController.abort();
57-
58-
await expect(sleepPromise).resolves.not.toThrow();
59-
});
6050
});
6151

6252
describe('isRFC3339Timestamp', () => {

docs/execution-event-logs.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
- [`StateCaught` event](#statecaught-event)
2323
- [Helper data types](#helper-data-types)
2424
- [`StateData`](#statedata)
25+
- [`RetryData`](#retrydata)
26+
- [`CatchData`](#catchdata)
2527

2628
## Events
2729

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
},
2323
"license": "MIT",
2424
"files": [
25-
"build"
25+
"build",
26+
"bin"
2627
],
2728
"type": "module",
2829
"types": "build/main.d.ts",
@@ -40,7 +41,7 @@
4041
"node": ">=16.0.0"
4142
},
4243
"bin": {
43-
"local-sfn": "./build/CLI.cjs"
44+
"local-sfn": "./bin/CLI.cjs"
4445
},
4546
"scripts": {
4647
"test": "jest",

0 commit comments

Comments
 (0)