A reusable TypeScript monorepo foundation for constructing AI-powered workflow applications.
This repository is intentionally small. It provides a proven starting structure for applications that commonly need:
- a shared domain package
- Node.js Lambda services
- AWS CDK infrastructure
- a web client
- consistent repository tooling
- an incremental validation process
It does not attempt to anticipate every future application. Product-specific schemas, infrastructure, integrations, and abstractions should be added only when the process being implemented requires them.
The purpose of this starter is to remove repeated setup work without predetermining the product.
It establishes the repository boundaries, tooling, and validation process that have proved useful across multiple workflow applications while leaving the business process free to determine what should be built next.
The starter is designed to answer:
What foundation do we repeatedly need before meaningful product construction can begin?
It is not designed to answer:
What architecture, infrastructure, or user experience will every future application require?
Those decisions belong to the application.
A workflow application assists a user in progressing from one meaningful state to another through an intentional sequence of interactions.
Unlike general-purpose software, workflow applications are organized around accomplishing an outcome rather than exposing functionality.
Examples include:
- interviews
- assessments
- document review
- due diligence
- onboarding
- guided analysis
- operational procedures
- decision support
The process determines the interaction.
The interaction determines the interface.
The interface determines the technology.
This repository is guided by three engineering principles.
Technology selection begins with the process that must produce the intended outcome.
Before selecting frameworks, cloud services, databases, orchestration systems, or AI models, define:
- what the user is trying to accomplish
- what information is required
- what sequence produces a reliable result
- what can occur independently
- where evidence must be combined
- where human judgment is required
- what constitutes completion
Only then should the implementation technology be selected.
Every workspace, dependency, helper, and abstraction should solve a demonstrated problem.
The starter deliberately avoids speculative infrastructure, premature abstractions, and framework conveniences that have not yet justified their existence through repeated use.
Software should be organized around responsibilities rather than technologies.
Before deciding where code belongs, determine who owns the responsibility.
Ownership determines organization.
Organization determines maintainability.
When responsibilities are clear, architectural boundaries become obvious. When responsibilities are unclear, architecture becomes accidental.
The objective is not simply to separate files.
The objective is to preserve clear ownership as the system grows.
The default tools in this repository are starting decisions, not universal prescriptions.
When beginning a new application, ask:
- What interaction is the user participating in?
- Is the experience primarily sequential, page-oriented, or request/response driven?
- Is the interface a cohesive product experience or a collection of independent modules?
- Where does state primarily live?
- Does the application need progressive enhancement or server-controlled mutations?
- Is there an organizational ecosystem, component library, or team standard that should outweigh greenfield preferences?
The answers should determine the client technology.
SvelteKit is a strong fit when the application is primarily:
- a guided workflow
- an interview
- document intake and review
- a sequence of forms or decisions
- a focused customer-facing product
- a route-oriented operational process
Its file-based routing maps naturally to process stages, and its component model keeps focused applications direct and relatively low-ceremony.
SvelteKit is the default client in this starter because many of the applications it is intended to support are cohesive, workflow-driven products.
React is a strong fit when the application is primarily:
- an enterprise application shell
- a collection of reusable modules
- an admin or operations portal
- a large design system
- an embedded application or widget
- a product developed across multiple independent teams
Its ecosystem and component architecture are especially valuable when the interface is assembled from independently maintained features or must integrate with an established React organization.
Remix is a strong fit when the application is fundamentally organized around web requests, server-owned data, forms, mutations, and progressive enhancement.
It is especially compelling when:
- the server is the primary source of truth
- route loaders and actions closely mirror the business process
- forms should work with the platform rather than around it
- navigation and data mutations are central to the application model
Many workflow applications can fit this model well.
Do not select a client framework only because it is:
- familiar
- popular
- newer
- perceived as more scalable
- preferred in the abstract
Select the tool whose model best supports the process, the users, and the maintenance environment.
This repository is built using a systematic and methodical approach to software construction.
Rather than attempting to design an entire application before writing code, each capability is introduced as a small, independently verifiable increment. Every new dependency, abstraction, helper, or workspace must justify its existence by solving a demonstrated problem.
This approach provides several benefits:
- Problems are isolated to the most recent change.
- Every commit leaves the repository in a deployable state.
- Design decisions can be evaluated while their context is still fresh.
- Infrastructure, tooling, and architecture evolve from repeated need rather than anticipation.
- Complexity grows only when the process being implemented requires it.
The objective is not simply to produce working software, but to construct software whose evolution remains understandable.
Every increment follows the same validation pipeline before construction continues:
Create
↓
Format
↓
Lint (0 warnings)
↓
Type Check
↓
Build
↓
Synth (Infrastructure)
↓
Continue
The repository is organized around architectural responsibilities rather than implementation details.
.
├── client/ # User interface
├── domain/ # Shared business language
├── infra/ # AWS infrastructure
├── lambdas/ # Server-side workflow execution
├── tooling/ # Repository construction tooling
│
├── project.config.ts # Project identity
├── package.json
├── tsconfig.base.json
├── eslint.config.js
└── prettier.config.js
Each workspace has a single responsibility.
The root defines the repository.
It owns:
- repository tooling
- shared TypeScript configuration
- formatting
- linting
- workspace management
- project identity
The root should not contain application logic.
The tooling workspace owns repository construction.
Responsibilities include:
- project initialization
- repository tooling
- developer workflows
- future validation and diagnostics
The tooling workspace should improve the engineering experience without introducing application behavior.
The domain package defines the language shared across the application.
Examples include:
- schemas
- contracts
- shared types
- domain primitives
- validation
The domain package should remain independent of AWS, SvelteKit, Lambda, or infrastructure concerns.
If multiple workspaces need the same business concept, it belongs here.
The Lambda workspace implements server-side workflow behavior.
Responsibilities include:
- API handlers
- workflow orchestration
- AI integrations
- persistence
- external service communication
Lambda code depends on the domain package but should not define domain concepts itself.
Infrastructure provisions the execution environment.
Responsibilities include:
- Lambda deployment
- API Gateway
- EventBridge
- DynamoDB
- S3
- CloudFront
- Cognito
Infrastructure should construct resources rather than implement business logic.
The client implements the user experience.
Its responsibility is to present workflows clearly and collect information required by the process.
It should consume domain models rather than redefine them.
One architectural rule governs every workspace in this repository:
Dependencies flow inward toward the domain.
Domain
↗ ↑ ↖
/ │ \
Client Lambdas MCP
\ │
\ │
Infrastructure
The domain defines the business language.
Everything else consumes that language.
The domain should not know about:
- AWS
- Lambda
- API Gateway
- CDK
- SvelteKit
- React
- Remix
- databases
- infrastructure
The domain represents the business, not the technology.
When a new concept is required, ask:
Is this a business concept or an implementation detail?
If it is a business concept that multiple workspaces understand, it belongs in the domain package.
If it exists only because of the chosen technology, it belongs in the consuming workspace.
This direction allows infrastructure, user interfaces, and execution environments to evolve independently while preserving a single business language across the application.
Each workspace owns a single responsibility.
When introducing new code, determine who owns the responsibility before deciding where the implementation belongs.
Ownership is more important than location.
A file can always be moved.
Poorly defined responsibilities are much harder to untangle.
The repository centralizes project identity in a single configuration file.
This includes values such as:
- project name
- display name
- package scope
- CloudFormation stack name
- API name
Project identity is established by the repository initializer.
Run:
npm run initialize- Create a repository from this template.
- Run
npm run initialize. - Review
project.config.ts. - Run the validation pipeline:
npm run validate. - Begin construction.
Run:
Format ↓ Format Check ↓ Lint ↓ Type Check ↓ Build ↓ Synth
Only begin product construction after the repository is green.
Install dependencies:
npm installThe repository follows a common validation pipeline. Run these commands from the repository root.
Construct a new workflow project identity.
npm run initializenpm run dev -w clientRun the complete repository validation pipeline.
npm run validateEquivalent to:
npm run format
npm run format:check
npm run lint
npm run check
npm run build
npm run synthEvery increment should successfully complete this pipeline before construction continues.
Validate a single workspace when working within it.
npm run lint --workspace=@constructional-workflow/domain
npm run check --workspace=@constructional-workflow/domain
npm run build --workspace=@constructional-workflow/domainReplace domain with:
clientlambdasinfra
as appropriate.
Generate the CloudFormation template without deploying resources.
npm run synth --workspace=@constructional-workflow/infraDeployment intentionally remains a project decision rather than part of the starter.
Every change follows the same cycle.
Think
↓
Construct
↓
Validate
↓
Reflect
↓
Continue
The objective is not to produce the largest amount of code.
The objective is to produce the next verified increment.
Large systems are constructed by repeatedly producing small, validated improvements whose responsibilities are clearly understood.
The repository intentionally evolves through small, verified increments. Future capabilities will be introduced only after they have earned their place through repeated use.
- Derive project identity from a single project name.
- Generate project configuration suggestions.
- Populate
project.config.ts. - Update workspace package names.
- Update project references throughout the repository.
- Rename the Git repository automatically.
- Install dependencies.
- Create projects directly from the template.
- Validate project configuration.
- Doctor command for repository diagnostics.
- Guide technology selection through constructional questions.
Future directions are intentionally documented rather than immediately implemented.
Capabilities are introduced only after repeated use demonstrates that they have earned their place.
Future tooling will continue to reinforce the construction methodology described throughout this repository rather than simply automate tasks.
Good software rarely emerges from selecting the perfect technology.
It emerges from understanding the process, defining clear responsibilities, introducing complexity only when it is justified, and validating every increment before continuing.
The objective of this starter is not simply to accelerate development.
Its objective is to encourage a disciplined way of constructing software that remains understandable as it grows.