Step Functions

Lightweight, dependency-free fake of AWS Step Functions (Amazon States Language / SFN) that speaks the real Step Functions AWS JSON 1.0 wire protocol, so application code using @aws-sdk/client-sfn can run against it with zero cost and zero side effects. It ships a compact but real Amazon States Language interpreter, so executions actually run.

KeyValue
Port4577
ProtocolStep Functions AWS JSON 1.0 over HTTP/1.1 (X-Amz-Target: AWSStepFunctions.<Op>)
Compatible client@aws-sdk/client-sfn (v3)
Imageparlel/stepfunctions:0.1
Size~95 KB
Startup< 100ms
StateIn-memory, ephemeral, resettable

Host prefix. On real AWS, StartSyncExecution and TestState use a sync- host prefix (e.g. sync-states.us-east-1.amazonaws.com). When pointing the SDK at this fake, construct the client with disableHostPrefix: true so those two operations reach the single listener. Every operation is served from POST /.

Quick Start

Start the server:

import { StepfunctionsServer } from "./services/stepfunctions/src/server.js";

const server = new StepfunctionsServer(4577);
await server.start();
// ... use it ...
await server.stop();

Connect with the real AWS SDK client:

import {
  SFNClient,
  CreateStateMachineCommand,
  StartExecutionCommand,
  DescribeExecutionCommand,
} from "@aws-sdk/client-sfn";

const sfn = new SFNClient({
  region: "us-east-1",
  endpoint: "http://127.0.0.1:4577",
  credentials: { accessKeyId: "parlel", secretAccessKey: "parlel" },
  disableHostPrefix: true, // needed for StartSyncExecution / TestState
});

const definition = JSON.stringify({
  StartAt: "Greet",
  States: {
    Greet: { Type: "Pass", Result: { greeting: "hello" }, ResultPath: "$.added", Next: "Done" },
    Done: { Type: "Succeed" },
  },
});

// Create a state machine.
const { stateMachineArn } = await sfn.send(
  new CreateStateMachineCommand({
    name: "greeter",
    definition,
    roleArn: "arn:aws:iam::123456789012:role/parlel",
  }),
);

// Start an execution.
const { executionArn } = await sfn.send(
  new StartExecutionCommand({ stateMachineArn, input: JSON.stringify({ a: 1 }) }),
);

// Inspect the result.
const result = await sfn.send(new DescribeExecutionCommand({ executionArn }));
console.log(result.status); // "SUCCEEDED"
console.log(JSON.parse(result.output)); // { a: 1, added: { greeting: "hello" } }

Implemented operations

All 37 operations exposed by @aws-sdk/client-sfn are implemented.

State machines

Versions

Aliases

Executions

Activities

Task tokens (callback pattern)

Map runs (distributed map)

State testing

Tags

Amazon States Language interpreter

StartExecution / StartSyncExecution actually execute the state machine. Supported state types and features:

FeatureSupported
Pass state (Result, Parameters, ResultPath, OutputPath)Yes
Task state (identity by default, pluggable resolvers)Yes
Task .waitForTaskToken callback patternYes
Choice state (And/Or/Not, all comparators, *Path)Yes
Wait state (Seconds / SecondsPath / Timestamp / TimestampPath, capped at 2s)Yes
Succeed / Fail statesYes
Parallel state (branches run concurrently)Yes
Map state (inline) — ItemsPath, ItemSelector/Parameters, MaxConcurrencyYes
Map state (distributed) — creates a MapRun recordYes
Retry (ErrorEquals, MaxAttempts, IntervalSeconds, BackoffRate)Yes (delays sped up)
Catch (ErrorEquals, Next, ResultPath)Yes
InputPath / OutputPath / ResultPath / ResultSelector / ParametersYes
JSONPath ($, $.a.b, $['a'], $.a[0], $$ context object)Yes (subset)
Intrinsic functionsYes (see below)
Execution history eventsYes

Intrinsic functions

States.Format, States.StringToJson, States.JsonToString, States.Array, States.ArrayLength, States.ArrayGetItem, States.ArrayContains, States.ArrayRange, States.ArrayPartition, States.ArrayUnique, States.MathAdd, States.MathRandom, States.StringSplit, States.UUID, States.Base64Encode, States.Base64Decode, States.Hash, States.JsonMerge.

Task resolution (parlel extension)

By default a Task state is an identity task: it returns its effective input as the result, which is enough to test most flow logic. To return real values, register a resolver keyed by the Resource ARN on the server instance:

server.taskResolvers.set(
  "arn:aws:lambda:us-east-1:123456789012:function:work",
  async (input, ctx) => ({ doubled: input.value * 2 }),
);

For .waitForTaskToken resources, complete the task out of band with SendTaskSuccess / SendTaskFailure using the token surfaced to your resolver (or to a paired activity via GetActivityTask).

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
STANDARD & EXPRESS state machinesSupported
Synchronous express executions (StartSyncExecution)Supported
Versions, aliases, weighted routingSupported
Activities + callback task tokensSupported
Distributed Map runs (records, counts)Supported (simplified)
Execution history eventsSupported (core event types)
Real AWS service integrations (Lambda invoke, DynamoDB, SNS, SQS, …)Identity task unless a resolver is registered
CloudWatch Logs / X-Ray deliveryConfig accepted; no external delivery
KMS encryptionConfig accepted; data not actually encrypted
IAM authorization / SigV4 verificationNot enforced (any credentials accepted)
Long-poll semantics of GetActivityTask (60s)Returns immediately
Quotas / throttlingNot enforced

Error codes & shapes

Errors are returned with a non-2xx status, the x-amzn-errortype: <Code> header, and a JSON body:

{ "__type": "StateMachineDoesNotExist", "message": "State Machine Does Not Exist: '<arn>'." }

Modeled error codes returned by this fake:

CodeWhen
InvalidArnMalformed ARN
InvalidNameBad state machine / activity / execution name
InvalidDefinitionASL definition fails JSON / schema validation
InvalidExecutionInputExecution input is not valid JSON
InvalidTokenMissing task token
InvalidOutputSendTaskSuccess output is not valid JSON
MissingRequiredParameterRequired field absent (e.g. roleArn, empty update)
StateMachineAlreadyExistsRe-create with different definition
StateMachineDoesNotExistUnknown state machine ARN
StateMachineTypeNotSupportedStartSyncExecution on a STANDARD machine
ExecutionAlreadyExistsDuplicate execution name with different input
ExecutionDoesNotExistUnknown execution ARN
ExecutionNotRedrivableRedrive a running or non-redrivable execution
ActivityDoesNotExistUnknown activity ARN
TaskDoesNotExistUnknown / already-settled task token
ResourceNotFoundUnknown alias / map run / taggable resource
ConflictExceptionAlias already exists
ValidationExceptionGeneric validation failure (e.g. routing weights ≠ 100)
TooManyTagsMore than 50 tags
InternalServerExceptionUnexpected server fault (500)

Internal endpoints

Two non-AWS helper endpoints are exposed for test orchestration:

You can also reset programmatically with server.reset().

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

AWS_ACCESS_KEY_ID=parlel
AWS_SECRET_ACCESS_KEY=parlel
AWS_REGION=us-east-1
AWS_ENDPOINT_URL_STEP_FUNCTIONS=http://parlel-bridge:4577
AWS_ENDPOINT_URL=http://parlel-bridge:4577
<!-- parlel:testenv:end -->