@flow-as-code/cdk

npm i @flow-as-code/cdk
        

Apache-2.0, Node 22.12 or newer. Peers on aws-cdk-lib and constructs, both optional so a CLI-only install does not fetch them.

Site and docs: https://flow-as-code.dev/. This package on npm: https://www.npmjs.com/package/@flow-as-code/cdk.

CDK binding for @flow-as-code/core. Two exports, plus a scaffold generator on a subpath:

Usage

import { FlowSet } from "@flow-as-code/cdk";

        new FlowSet(stack, "Flows", {
          instanceArn: instance.attrArn,
          source: "flows/", // or FlowDoc[]
          binder: {
            queue: (name) => queues[name].attrQueueArn,
            hours: (name) => hours[name].attrHoursOfOperationArn,
            lambda: (name) => fns[name].functionArn,
            lex: (name) => lexAliases[name].attrBotAliasArn,
            prompt: (name) => prompts[name].attrPromptArn,
            // flow?(name) and module?(name, alias): only for a flow or module that is
            // not in this set, see "References inside and outside the set".
          },
        });
        

References inside and outside the set

A reference that names a document in the set is resolved by FlowSet itself, the way @flow-as-code/tf and @flow-as-code/hcl resolve their own documents; the binder is not consulted for it:

Documents are created in dependency order over both kinds of reference, and the order is stable, so the template is byte-identical however the files were read. A set whose flow references form a cycle (two flows transferring to each other, a hook pointing back at its caller) fails at synth with the cycle spelled out, Flow reference cycle: day-line -> night-line -> day-line, because each flow would need the other's ARN and one CloudFormation template cannot create that; at deploy it would surface as a Circular dependency naming logical IDs. Deploy one side from another stack and bind it through flow().

A reference that names a document outside the set goes to the binder's optional method:

Without the method, synth fails naming the token, the document and both remedies (add the document to the set, or implement the method). A Lazy.string is a fine return value: FlowSet materializes content in its constructor, so a binder pointing at a flow another FlowSet in the same stack manages defers the lookup with Lazy.string({ produce: () => other.flows.get(name)!.attrContactFlowArn }), and CDK resolves it at synth, when both constructs exist.

The scaffold writes flow and module methods only for names the set does not hold.

The scaffold (@flow-as-code/cdk/scaffold)

Writing that stack by hand starts with the same file every time, so cdkScaffold({ docs, source }) generates it: a FlowStack constructing a FlowSet over the FlowDoc directory, and a TokenBinder carrying one TODO per reference type the documents actually use, with the referenced names listed. Types the set does not use still get a method, because the interface requires them, but theirs throws instead of carrying a TODO.

The subpath exists because two tools emit this file and must emit it identically: flow-cli emit --target cdk and the studio's CDK export button. It is pure and imports no node builtin, so the studio can bundle it for a browser; source is the FLOW_DOCS expression (., ..), which is the one thing that needs a path calculation, and sourceForDepth computes it where node's path.relative is unavailable. The emitted file resolves that expression against itself with new URL(source, import.meta.url), so the stack reads the same directory whatever the CDK app's working directory is. Output is byte-stable and independent of the order the documents were read.

Peer dependencies

aws-cdk-lib ^2.267.0 and constructs ^10.8.1, both optional. A project that has them pinned below that floor gets an ERESOLVE from npm 7 and later rather than a silent mismatch; a project that has neither installs neither. The floor is deliberate: FlowSet builds CfnContactFlowModuleVersion and CfnContactFlowModuleAlias (see the versioning model below), which are recent additions to aws-cdk-lib/aws-connect, and 2.267 is the version this package is developed and tested against. Lowering it means finding the release that introduced both L1s and running the aws-cdk-lib/assertions tests against it, not just widening the range.

They are optional because the CDK entry is not the only entry. @flow-as-code/cli and the studio reach this package for @flow-as-code/cdk/scaffold alone, which imports nothing but @flow-as-code/core: aws-cdk-lib appears in that module as a line of the stack source it generates, not as an import. Non-optional peers are auto-installed, so with these declared as ordinary peers every npm install @flow-as-code/cli would fetch 166 MB of CDK that nothing in that install loads. Importing FlowSet or the binder still needs both packages present, and tests/packaging.test.ts holds the scaffold subpath to its one bare import.

The cost of optional is that npm says nothing at install time, so the package says it itself. Importing @flow-as-code/cdk with aws-cdk-lib absent throws:

@flow-as-code/cdk needs the CDK at runtime, but aws-cdk-lib is not
        installed. It and constructs are optional peers so that a CLI-only install
        stays small, which means npm does not warn about them at install time. Install
        them with: npm install aws-cdk-lib@^2.267.0 constructs@^10.8.1
        

The entry checks for the peer before it loads FlowSet, so this replaces the bare ERR_MODULE_NOT_FOUND that used to come out of dist/flow-set.js. The ./scaffold subpath is unaffected and imports with no CDK installed. src/index.test.ts runs the entry in a directory with no node_modules and asserts the message, including the version floor read from package.json.

Versioning model

Validation

FlowSet refuses, at synth time: duplicate document names, kind/connectType mismatches, flow and module reference cycles (named), and any document failing a hard @flow-as-code/core lint rule (no-literal-arn, no-unresolved-token). Binder gaps, including a module or flow that is neither in the set nor bound, fail with an error naming the token, the document, and the missing method.

No instance/queue provisioning constructs here; users bring their own or use L1s.

Testing

Offline tests assert on synthesized templates with aws-cdk-lib/assertions. The live-deploy test in src/integration.test.ts runs only when both FLOW_TEST_INSTANCE_ARN and FLOW_TEST_DEPLOY=1 are set, deploys the demo flow plus a versioned module to that instance through the AWS CLI, and tears the stack down. It writes to the account, so naming an instance is not enough on its own: use an empty sandbox.

That test has run green against a live Connect instance (2026-09-02). The test asserts only that the deploy exited zero, and it deletes the stack in its finally, so the content check was a separate step: the same synthesized template was deployed again and left up, and its flows were read back with DescribeContactFlow and DescribeContactFlowModule and compared against what the FlowDocs materialize to. That comparison is structural rather than byte-for-byte, because deploying resolves each ${cdref:...} reference to a real ARN: every other position matched exactly, and the reference positions were checked against the stack's own resources instead, each resolving to a resource of the right kind and the module reference resolving to the alias rather than the version. The stack needs no CDK bootstrap: it is asset-free and uses CliCredentialsStackSynthesizer. tasks/A07-flow-cdk.md records both.