The Composer
The composer is the build-your-own path: instead of a bin/app.ts that instantiates stacks one by one, a composer project makes a single call —
const result = defineAxisApp(app, { selections, envName, envConfig, env, data, auth, api, security,});— and defineAxisApp (from @axis-tech-co/axis-stacks) instantiates exactly the stacks your selections describe, wires them over SSM, validates coherence, and returns every stack instance plus the resolved selections. It never calls app.synth(); your app stays a plain CDK app.
Scaffold one with axs new my-app --composer — see Create a Project.
The two-level selections model
Section titled “The two-level selections model”Selections live in axis.json (the canonical state, under wiring: "composer") and have two levels:
Level 1 — tri-state per stack: "axis" | "custom" | "off"
auth,observability,budget— plusmodefields ondata,api, andsecurity.workeris a two-state optional ("axis" | "off", absent means off).
Level 2 — boolean construct toggles inside the data layer: dynamo, kms, s3, sqs, sns, secrets, eventBridge.
The API selection also carries a kind: api.kind: "rest-v1" | "http-v2" | "graphql" | "none" ("none" is the worker/bare shape).
{ "auth": "axis", "data": { "mode": "axis", "dynamo": true, "kms": true, "s3": true, "sqs": true, "sns": true, "secrets": true, "eventBridge": true }, "api": { "kind": "rest-v1", "mode": "axis" }, "security": { "mode": "axis", "waf": true, "ses": true }, "observability": "axis", "budget": "off"}What each state means
Section titled “What each state means”"axis"— the composer instantiates the framework’s stack for you. Selecting"axis"obliges you to supply the matching props (data.tables,apiconfig, worker configs, …) — a missing pairing is a synth-time[Axis]coherence error, not a silent gap."off"— the stack simply doesn’t exist. The floor is fully composable: with everything off except a worker you get a queue consumer and nothing else."custom"— substitution. The composer skips the stack; you bring your own raw-CDK stack that writes the same SSM contract paths the Axis stack would have written. Downstream stacks don’t know the difference, because cross-stack wiring is SSM-only — anApiStackreadscognito/userPoolIdwhether Cognito came from Axis or from you.
Substitution is the composer’s expression of Axis IS CDK: the escape hatch at application scale, with the SSM parameter paths as the frozen contract.
The synth-time flow
Section titled “The synth-time flow”flowchart TD manifest["axis.json — selections (canonical state)"] --> resolve["resolveSelections — shape · enum · coherence validation"] ctx["--context feature.name=on|off (synth-time overlay)"] --> resolve resolve --> compose["defineAxisApp branches"] compose --> auth["CognitoStack (auth: axis)"] compose --> data["DataStack (data.mode: axis) — toggled construct groups"] compose --> api["Api / HttpApi / GraphqlStack (api.kind + mode: axis)"] compose --> workerS["WorkerStack (worker: axis)"] compose --> skip["custom / off — skipped; your stack writes the same SSM paths"]
The context overlay follows two deliberate rules. It maps to on/off only — "custom" is unreachable via context, because substituting a stack is project state, not a synth experiment. And a context key that targets a stack whose recorded mode is "custom" throws, rather than pretending to toggle a stack the composer doesn’t own.
Guard rails you’ll meet
Section titled “Guard rails you’ll meet”All composer validation fires at synth with [Axis]-prefixed messages:
- Unknown keys, wrong enum values, and missing required blocks are rejected outright — a typo’d selection never half-applies.
- Branch coherence:
api.mode: "axis"without API props,worker: "axis"without worker configs, or a GraphQL kind without resolver configs each name exactly what’s missing. apis(plural) is a reserved key: multi-API coexistence is the named upgrade path, and v1 rejects the key with a versioned message so a future additive release can claim it without a compile break.
Abstract stacks, handled for you
Section titled “Abstract stacks, handled for you”Several Axis stacks are abstract classes with template-method hooks (projects normally subclass them — see Project Structure). The composer instantiates them through internal concrete subclasses whose hooks return your composer inputs verbatim. Logical IDs derive from construct IDs, not class names, so this is invisible to CloudFormation — and fully compatible with Logical-ID Permanence.
© 2026 Axis Tech. Powered by axis-tech.co.