| name | scaffold-dcb-tanstack-cloudflare |
| description | Scaffold a full-stack event-sourced application using TanStack Start, Cloudflare Workers, and the fmodel-decider DCB pattern backed by PostgreSQL via Hyperdrive. Use when the user wants to create a new project or add new domain use cases (commands, events, views, workflows) following the DCB architecture. Handles project setup, domain modeling, infrastructure wiring, UI pages, and testing with Given/When/Then specs.
|
| license | Apache 2.0 |
| compatibility | Requires Node.js 20+, pnpm, Docker (for local Postgres), and a Cloudflare account for deployment. Uses TanStack Start v1.132+, React 19, Vite 7, Tailwind CSS v4, and @fraktalio/fmodel-decider from JSR.
|
| metadata | {"author":"fraktalio","version":"1.0.0","framework":"tanstack-start","runtime":"cloudflare-workers","pattern":"dcb-event-sourcing","example-repo":"https://github.com/fraktalio/order-management-demo-tanstack"} |
Scaffold DCB + TanStack Start + Cloudflare Workers
Scaffold or extend a full-stack event-sourced application using the Dynamic
Consistency Boundary (DCB) pattern with TanStack Start on Cloudflare Workers.
Full working example:
order-management-demo-tanstack
When to Use
- Creating a new full-stack project with event sourcing and DCB
- Adding new domain use cases (commands, events, deciders, views) to an existing project
- Wiring domain logic through the application layer (command/query handlers, repositories)
- Adding Cloudflare Workflows for multi-step orchestration (sagas, process managers)
- Creating UI pages with TanStack Start server functions
- Setting up infrastructure (Postgres, Hyperdrive, Docker, Wrangler)
Overview
The architecture follows a strict layered structure:
domain/ → Pure logic: deciders, views, types, errors (no deps)
application/ → Wiring: command handlers, query handlers, workflows
infrastructure/→ Persistence: repositories, DB helpers, SQL schema
routes/ → UI pages + REST API endpoints
Each layer depends only on the layers below it. The domain layer has zero
infrastructure dependencies — it's pure TypeScript functions and types.
Step-by-Step Instructions
Step 1: Gather Requirements
Ask the user for:
- Project name (if new project) or confirm extending existing
- Domain use cases — what commands should the system handle?
- Events — what facts does each command produce?
- Read models — what views/projections are needed?
- Workflows — any multi-step orchestrations needed?
- Pages — what UI pages are needed?
If the user provides an Event Model diagram (image), use the
map-event-model-to-code skill first to extract the table, then continue here.
Step 2: Scaffold Project (New Project Only)
If creating a new project, generate these files in order:
package.json — dependencies and scripts
tsconfig.json — TypeScript config with path aliases
vite.config.ts — Vite + Cloudflare + Tailwind + TanStack
wrangler.jsonc — Cloudflare Workers config with Hyperdrive
docker-compose.yml — local Postgres with DCB schema auto-apply
.prettierrc + .prettierignore — formatting config
src/styles.css — Tailwind v4 entry point
src/infrastructure/dcb_schema.sql — PostgreSQL DCB schema
src/infrastructure/db.ts — withDb(env, fn) helper
src/infrastructure/pg-client-adapter.ts — postgres.js → SqlClient adapter
src/server.ts — Cloudflare Worker entrypoint
src/router.tsx — TanStack Router factory
src/routes/__root.tsx — root layout
See references/INFRASTRUCTURE-PATTERNS.md
for exact templates.
Step 3: Define Domain Types (src/domain/api.ts)
For each use case, generate in src/domain/api.ts:
- Branded type IDs — one per unique entity ID
- Domain errors — one per validation/invariant failure
- Value objects — shared types (names, statuses, menus, etc.)
- Command types — discriminated union with
kind field
- Event types — using
TypeSafeEventShape with tagFields
See references/DOMAIN-PATTERNS.md for the
exact type patterns and conventions.
Step 4: Implement Deciders (src/domain/deciders/)
For each command, create a decider file:
- Define the state type — minimal state needed for validation
- Create the
DcbDecider<C, S, Ei, Eo> with:
decide(command, state) — validation + event production
evolve(state, event) — state reconstruction from events
initialState — starting state value
- Determine Ei (input events) from what the decider needs to read
- Determine Eo (output events) from what the decider produces
- Make idempotent commands return
[] (no events) on duplicates
See references/DOMAIN-PATTERNS.md for decider
patterns.
Step 5: Implement Views (src/domain/views/)
For each read model, create a view file:
- Define the view state type — denormalized read model shape
- Define the event union type — events this view subscribes to
- Create the
Projection<S | null, E> with:
evolve(state, event) — fold events into state
initialState — null
- Use exhaustive
switch with never check in default branch
See references/DOMAIN-PATTERNS.md for view
patterns.
Step 6: Write Tests
For each decider, create a co-located .test.ts file using Given/When/Then:
import { DeciderEventSourcedSpec } from '../test-specs.ts';
spec
.given([...events])
.when(command)
.then([...expectedEvents]);
spec
.given([...events])
.when(command)
.thenThrows((e) => e instanceof SomeError);
For each view, create a co-located .test.ts file using Given/Then:
import { ViewSpecification } from '../test-specs.ts';
spec.given([...events]).then(expectedState);
Create shared fixtures in src/domain/fixtures.ts.
Step 7: Wire Infrastructure
For each decider, create:
- Repository (
src/infrastructure/repositories/) — PostgresEventRepository
with query tuples mapping command fields to (tag, eventType) pairs
- Command handler (
src/application/command-handlers/) —
EventSourcedCommandHandler(decider, repository)
For each view, create:
- Query handler (
src/application/query-handlers/) —
EventSourcedQueryHandler(view, PostgresEventLoader)
See references/INFRASTRUCTURE-PATTERNS.md
for exact patterns.
Step 8: Create Routes
For UI pages, create files in src/routes/:
- Define
createServerFn functions that call handlers via withDb(env, ...)
- Create the route with
createFileRoute
- Build React components with Tailwind CSS styling
For REST API endpoints, create files in src/routes/api/:
- Use
server.handlers with HTTP method handlers
- Parse request body, call
handleCommand(fn) wrapper
See references/UI-PATTERNS.md for route patterns.
Step 9: Add Workflows (Optional)
For multi-step orchestrations:
- Create a
WorkflowEntrypoint class in src/application/workflows/
- Use
step.do() for each command handler call (with retries)
- Use
step.waitForEvent() for external signals
- Re-export the class from
src/server.ts
- Add the workflow binding to
wrangler.jsonc
See references/INFRASTRUCTURE-PATTERNS.md
for workflow patterns.
Step 10: Update Barrel Exports
Update src/domain/index.ts, src/infrastructure/index.ts, and
src/application/index.ts to export new artifacts.
Key Conventions
- File naming:
camelCase.ts for all source files
- Test naming:
camelCase.test.ts co-located with source
- Decider naming:
{useCaseName}Decider (e.g., createRestaurantDecider)
- View naming:
{entityName}View (e.g., orderView)
- Handler naming:
{useCaseName}Handler (e.g., createRestaurantHandler)
- Repository naming:
{useCaseName}Repository (e.g., createRestaurantRepository)
- Command kind:
PascalCaseCommand (e.g., CreateRestaurantCommand)
- Event kind:
PascalCaseEvent (e.g., RestaurantCreatedEvent)
- Error naming:
PascalCaseError extends DomainError
- Branded IDs:
type FooId = Brand<string, 'FooId'> with factory fooId(s)
- tagFields: declare which string ID fields should be indexed
- Tags in repositories:
"fieldName:" + value format (e.g., "restaurantId:" + cmd.restaurantId)
- Idempotency: duplicate commands return
[] (empty events), never throw
- Imports: use
@/ path alias for src/, .ts extensions in relative imports