flow-as-code

Flow as code for Amazon Connect.

An Amazon Connect flow is a JSON document full of ARNs, and the ARNs differ per account, per region, per instance. So moving one flow from dev to prod means maintaining a mapping from "the queue I mean" to "the ARN it has in this environment", and something has to walk the flow and swap one for the other before deployment. That mapping table is the thing this tooling deletes.

Open the studio demo See it promote across environments Read the docs The demo needs a desktop-width screen. It is read-only, in your browser, no account and no network calls.

References are tokens

A flow refers to a queue by name, not by ARN:

new UpdateContactTargetQueue({
  id: "set-working-queue",
  queue: Refs.queue("appointments"),
  next: "look-up-appointment",
  onError: "apologize",
});

Refs.queue("appointments") becomes ${cdref:queue:appointments} in the document. It stays a token through authoring, lint, version control and review. It becomes an ARN at exactly one moment: when your IaC tool resolves it, per environment. Under CDK the binding is a CloudFormation intrinsic, a Fn::GetAtt at a resource the stack creates or an Fn::ImportValue of one it does not. Under Terraform it is a templatefile variable your own resource addresses fill in.

That is a lint rule, not a convention: a literal ARN in authored content fails a hard rule, and in the studio a failing hard rule disables saving.

One flow, two environments

The promotion example is that argument as commands rather than prose: one FlowDoc reaching a dev and a prod environment, on both deploy paths. Two facts about it are worth stating because a test asserts each, on every push to main and every pull request.

tests/promoteAcrossEnvironments.test.ts runs the example's own inputs and holds both. The example's README also names what it does not show.

The map that is not that table

The Terraform path does take an --address-map, one small JSON file per environment, and it looks like the table described above. It is a different object. Its values are Terraform addresses of resources your own configuration manages, aws_connect_queue.appointments.arn where the environment creates the queue and data.terraform_remote_state.platform.outputs.appointments_queue_arn where a platform team owns it. The emitter refuses any value matching /arn:aws/i outright, telling you to map it to a terraform address instead, so the file cannot decay into an ARN table.

The CDK path needs no such file because the flows and the resources are objects in one program, so the app already holds the reference and there is nothing to name. HCL has no equivalent, so something has to name the address in text. That, and only that, is why one path takes a map and the other takes none.

What it is

Open-source tooling for Amazon Connect flows: typed flow authoring, lint, simulate-based testing, export from a live instance, and a visual editor, all round-tripping through one interchange format, with CDK and Terraform/OpenTofu code as outputs. Everything but the live commands works offline.

FlowDoc is the pivot. It is a JSON document holding Connect Flow-language content with ${cdref:type:name} reference tokens, plus layout and metadata. Every tool in the set reads and writes it, so there is no second format to keep in sync.

typed TS builder --synth--> FlowDoc --codegen--> typed TS builder
studio canvas   <--edit--> FlowDoc
FlowDoc --cdk--> CDK stacks (CloudFormation tokens)
FlowDoc --tf-->  .tf + .tftpl files (resource references)
live instance --export--> FlowDoc (+ codegen to TS)

Packages

Try it

Published on npm under the @flow-as-code scope. The five packages version together and are released as a set, so install them at matching versions. Node 22.12 or newer.

npm i -D @flow-as-code/cli
npx flow-cli init flows/              # a first flow to open: FlowDoc + .flow.ts

npx flow-cli studio flows/            # visual editor, live-synced
npx flow-cli lint flows/              # the rule set over the document set
npx flow-cli emit flows/ --target tf  # or --target cdk

Every command after the install works on a directory of FlowDocs, so init comes first: it writes a small demo flow and the typed .flow.ts that synthesizes to it. If nothing above the directory is already a package it adds a package.json marking the pair as modules; inside a project that has one it leaves yours alone, and the pair loads either way. Edit either side and the studio keeps them in step.

@flow-as-code/cli depends on the other four, so installing it alone is enough for the CLI. The studio demo above needs no install at all, and the promotion example runs from a clone of the repository.