Construct
Create features and files
Scaffold in the right place, in the right order.
Use create to scaffold files in the right layer with the right names. Nothing is written by an AI model unless you add --llm.
A feature, a layer, or a slice#
# an empty feature with all layer folders
construct create feature billing
# one file in one layer
construct create domain PriceCheck --feature billing
# several layers of one logical unit in a single command
construct create layer Invoice --feature billing --layers domain,hook,page,controller
Real output of the last command:
Created features/billing/domain/Invoice.tsx (0.02s)
Created features/billing/hooks/useInvoice.tsx (0.00s)
Created features/billing/pages/InvoicePage.tsx (0.00s)
Created features/billing/controllers/InvoiceController.tsx (0.00s)
Total: 0.03s
[tool: scaffolded the file(s) above from templates] [llm: 0 calls — filling in the logic is a separate step, by you or whichever LLM you choose]
The layers are domain, service, workflow, hook, component, page and controller. List them in any order; Construct builds them in dependency order.
construct generate layer scaffolds one logical unit across several layers in a single command:
construct generate layer PriceCheck --feature pricing --layers domain,hook,controller
You can list --layers in any order — Construct always generates them in dependency order (domain -> service -> workflow -> hook -> component -> page -> controller) so each file's imports resolve correctly by the time it's created.
This isn't just a convenience — it's backed by a real rule, IMPORT-001: any relative import that doesn't resolve to a file on disk is a validation error, checked both by construct validate and at generation time. A controller's template already imports its same-named page, so construct generate controller X --feature F fails immediately, with a clear message, if pages/XPage.tsx doesn't exist yet — you can't generate out of order and end up with a silently broken import. This is the mechanism, not construct generate layer's ordering alone: any file, generated or hand-written, with a dangling relative import fails construct validate the same way.
Generate a network layer from an OpenAPI spec#
construct create service <name> --feature <feature> --openapi <spec> compiles a real OpenAPI 3.x spec straight into a working Redux Toolkit Query injectEndpoints file — no LLM involved. @hey-api/openapi-ts parses the spec ($ref resolution, allOf/oneOf composition, ...) into correctly-typed request/response TypeScript; Construct's own deterministic template turns that into RTKQ endpoints:
construct create service petStore --feature pet --openapi ./openapi/petstore.yaml
This writes:
features/core/services/client.ts— the sharedapi(RTKQcreateApi) andbaseQuery, re-exported fromfeatures/core/index.tsso other features can consume it through the public API (SLICE-002-clean).features/<feature>/services/<name>/{index.ts,types.gen.ts}— hey-api's generated types for every operation.features/<feature>/services/<name>Api.ts— the RTKQinjectEndpointsfile, onequery/mutationper operation (GET/HEAD -> query, everything else -> mutation), plus its generateduse<Op>Query/use<Op>Mutationhooks.
Which transport client.ts instantiates is controlled by project.dataLayer.provider in architecture.yml — fetchBaseQuery (the default), axios, or a network-free mock adapter for tests/demos:
project:
dataLayer:
provider: axios # or fetchBaseQuery (default), or mock
Switching providers only ever touches client.ts — every generated services/*.ts endpoint file calls the same provider-agnostic buildRequest(method, urlTemplate, data) helper regardless of which adapter is active. Every operation in the spec needs an explicit operationId (used to line up the RTKQ endpoint with the type hey-api generated for it) — a spec without one fails fast with a clear error rather than guessing a name that might not match.
Next#
Run construct validate to check the result, and see Tune the rules if a rule does not suit your project.