No description
  • TypeScript 88.4%
  • JavaScript 9.1%
  • Shell 2.3%
  • Makefile 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Riccardo Agnoletto 460ba6a6ad
All checks were successful
CI / Core Gates (build · typecheck · test · lint · tasks) (push) Successful in 3m49s
CI / Integration Gates (postgres · kurrentdb · runner) (push) Successful in 2m33s
CI / Scaffold Smoke (template · scaffold · install · build · test) (push) Successful in 4m25s
Scaffold Integration Matrix / Scaffold Leg a (template · scaffold · install · build) (push) Successful in 3m18s
Scaffold Integration Matrix / Scaffold Leg b (template · scaffold · install · build) (push) Successful in 2m59s
Scaffold Integration Matrix / Scaffold Leg c (template · scaffold · install · build) (push) Successful in 3m22s
Scaffold Integration Matrix / Scaffold Leg d (template · scaffold · install · build) (push) Successful in 3m21s
docs(root): wiki ingest and status after the 1.0.0 cut slice 2a
2026-10-29 15:14:33 +01:00
.claude fix(root): address the slice 1b sweep findings 2026-10-29 12:49:51 +01:00
.github ci(root): drop the github-script comment step from the drift check 2026-10-29 12:32:51 +01:00
.husky docs(root): make agents.md the canonical rules file 2026-10-07 18:26:04 +02:00
.omp docs(root): wiki ingest and status after the 1.0.0 cut slice 2a 2026-10-29 15:14:33 +01:00
.playwright-mcp initial commit 2026-01-20 12:31:07 +01:00
.serena refactor(infrastructure): delete dead middleware, gauge and config code 2026-10-10 11:44:13 +02:00
.tasks fix(infrastructure): read the kurrentdb event stream so the runner boots 2026-10-29 14:48:03 +01:00
.vscode initial commit 2026-01-20 12:31:07 +01:00
apps fix(root): address the slice 2a sweep findings 2026-10-29 15:08:44 +01:00
docker feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
docs docs(root): wiki ingest and status after the 1.0.0 cut slice 2a 2026-10-29 15:14:33 +01:00
packages fix(root): address the slice 2a sweep findings 2026-10-29 15:08:44 +01:00
scripts fix(root): port error-docs checker to the effect 4 httpapi dsl 2026-10-28 11:21:36 +01:00
tools fix(root): say what nocontent and unmapped errors answer 2026-10-28 19:10:44 +01:00
.dockerignore chore(root): remove stray root smoke scripts and the stale todo dir 2026-09-23 17:07:02 +02:00
.env.example feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
.gitignore docs(root): add the llm wiki, lint and skill 2026-10-07 18:23:12 +02:00
.mcp.json chore(root): make the ts-morph mcp config portable with relative paths 2026-09-23 17:14:14 +02:00
.prettierignore initial commit 2026-01-20 12:31:07 +01:00
.prettierrc initial commit 2026-01-20 12:31:07 +01:00
AGENTS.md docs(root): re-plan the last leg as the 1.0.0 cut epic 2026-10-29 10:06:33 +01:00
CLAUDE.md feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
commitlint.config.js feat(moderation): add moderation-domain and the moderationcase aggregate 2026-09-18 18:30:51 +02:00
CONTRIBUTING.md feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
docker-compose.override.yml.dist feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
docker-compose.yml feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
docker.sh feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
Dockerfile feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
eslint.config.mjs fix(root): unbreak runner lint and the combo-a connection guard 2026-09-21 12:37:05 +02:00
LICENSE docs(root): apply mit license and fix doc drift 2026-07-06 11:24:53 +02:00
Makefile feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
nx.json feat(root): vendor monorepo into create-node-ddd-app template (lite) 2026-09-08 21:25:51 +02:00
opencode.json chore(root): make the ts-morph mcp config portable with relative paths 2026-09-23 17:14:14 +02:00
package.json build(deps): bump effect and @effect/* to ~4.0.6 on effect-4 2026-10-28 10:13:03 +01:00
pnpm-lock.yaml fix(infrastructure): read the kurrentdb event stream so the runner boots 2026-10-29 14:48:03 +01:00
pnpm-workspace.yaml build(root): move to node 24 and pnpm 12 2026-10-27 20:58:44 +01:00
README.md feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
renovate.json build(deps): add the renovate config with weekly grouped updates 2026-10-29 13:08:12 +01:00
sgconfig.yml chore(root): harvest ast-grep, ts-morph daemon, infra-shared-kernel 2026-06-05 11:57:18 +02:00
tsconfig.base.json initial commit 2026-01-20 12:31:07 +01:00
tsconfig.json initial commit 2026-01-20 12:31:07 +01:00
tsconfig.tsmorph.json feat(root): stop offering the five stub and sketch adapters 2026-10-29 13:40:16 +01:00
tsconfig.tsmorph.with-tests.json feat(root): ts-morph integration 2026-02-11 13:22:04 +01:00

Effect CQRS + Event Sourcing Template

A production-ready TypeScript template implementing CQRS (Command Query Responsibility Segregation) and Event Sourcing patterns using Domain-Driven Design (DDD) and Hexagonal Architecture. Built with Effect and Nx.

🎯 What is This?

This is a template project demonstrating:

  • ✅ DDD - Domain-Driven Design with bounded contexts
  • ✅ CQRS - Separate write (commands) and read (queries) models
  • ✅ Event Sourcing - Complete audit trail of state changes
  • ✅ Hexagonal Architecture - Pluggable adapters for infrastructure
  • ✅ Clean Architecture - Testable, maintainable, technology-agnostic
  • ✅ Type Safety - Full TypeScript with Effect for typed error handling

Perfect for building scalable, maintainable event-driven applications.

📦 Scaffold a New Project

Generate your own copy of this architecture (pick the adapters you need; the rest are pruned) with the scaffolder:

npm create node-ddd-app <target-directory> [options]
# or: npx create-node-ddd-app <target-directory> [options]

Options:

--yes           Skip prompts and scaffold the default all-memory profile (no external services needed)
--force         Allow scaffolding into a non-empty directory
--no-sketches   Hard-delete unselected adapters instead of keeping them in sketches/
--llm-harness   LLM workflow harness tier (full|light|none)
--mcp           Wire the ts-morph-readonly MCP (tsconfig.tsmorph.json + .mcp.json)
--help          Print this usage and exit

See packages/create-node-ddd-app/README.md for the full guide.

🚀 Quick Start

# Clone the repository
git clone <repo-url>
cd create-node-ddd-app

# Start with in-memory adapters (fastest)
./docker.sh dev

# Or start with PostgreSQL (event store + projections)
./docker.sh postgres

# View all available commands
./docker.sh

The API will be available at:

Visit /docs for interactive Swagger API documentation.

Local Development

# Install dependencies
pnpm install

# Build the application
pnpm nx run runner:build

# Run the application
node apps/runner/dist/index.js

Expected output (abridged):

🔧 Application Configuration:
  ├─ Environment: development
  ├─ Event Store: memory
  ├─ Event Bus: memory
  ├─ Auth Provider: casbin
  ├─ Telemetry: noop
  ├─ Projections:
  │   ├─ ModerationQueue: memory
  │   ├─ PostListView: memory
  │   ├─ PostPublishStatus: memory
  │   └─ ReopenedCases: memory
  └─ Servers:
      ├─ HTTP REST: enabled (port 3000)
      └─ Web SSR: enabled (port 3001)

✅ Runner: Application started successfully
🚀 CQRS + Event Sourcing architecture active
   🌐 REST API: http://localhost:3000
   📚 Swagger Docs: http://localhost:3000/docs

With zero configuration, the runner seeds a canonical in-memory PostListView projection plus the three moderation read models (ModerationQueue, PostPublishStatus, ReopenedCases); set PROJECTION_DEFAULT=none to opt out of seeding.

📋 Table of Contents

Core Documentation

Product & Planning

Architecture Decisions

For LLM Agents

  • AGENTS.md - binding rules and workflow for every coding agent
  • CLAUDE.md - architecture, navigation and conventions (Claude Code imports AGENTS.md from it)

🔄 CQRS + Event Sourcing Flow

┌─────────────┐
│   Client    │
└──────┬──────┘
       │
       ▼
┌─────────────────────────────────────┐
│    REST API (port 3000)              │
│  Commands       │       Queries      │
└────────┬────────┴────────┬───────────┘
         │                 │
         ▼                 ▼
┌──────────────────┐  ┌──────────────┐
│  Write Model     │  │  Read Model  │
│ (PostService)    │  │(QueryDB)     │
│      ↓           │  │      ↑       │
│  EventStore ─────┼──┼─→ Projector  │
└──────────────────┘  └──────────────┘
         │                 ▲
         └───────────────┬─┘
               EventBus
          (Effect Queue)

💾 API Examples

Only the GET /posts routes are public. Write routes need a bearer token, and so do the moderation reads (GET /moderation/queue, GET /moderation/cases/:id), which need case:read. The in-memory auth adapter accepts the dev tokens alice-token (admin), bob-token (editor), charlie-token (viewer) and dave-token (moderator; needed for case:read and case:resolve, which admin also holds).

Create a Post (Command)

curl -X POST http://localhost:3000/posts \
  -H "Authorization: Bearer alice-token" \
  -H "Content-Type: application/json" \
  -d '{
    "aggregateId": "post-001",
    "userId": "user-123",
    "title": "My First Post",
    "content": "Testing CQRS and Event Sourcing!"
  }'

Get a Post (Query)

curl http://localhost:3000/posts/post-001

List Posts (Query)

Returns the first 50 posts, newest first (GET /posts takes no paging parameters).

curl http://localhost:3000/posts

Publish a Post (Command)

curl -X POST http://localhost:3000/posts/post-001/publish \
  -H "Authorization: Bearer alice-token"

🏗️ Architecture at a Glance

packages/
├── domain/
│   ├── shared-kernel/          # Core domain primitives (zero dependencies)
│   ├── debate-domain/          # Post aggregate, events, commands, queries
│   ├── moderation-domain/      # ModerationCase aggregate (second bounded context)
│   └── auth-domain/            # Authentication models
├── application/
│   ├── app/                    # Cross-cutting ports, API definitions, error mapping
│   ├── debate-application/     # PostService, PostProjector, contract tests
│   ├── moderation-application/ # ModerationService, policies, DebateGateway
│   └── auth-application/       # AuthService, AuthorizationService
└── infrastructure/
    ├── eventstore-inmemory-adapter/   # Event persistence (dev/test)
    ├── eventstore-postgres-adapter/   # Event persistence (production)
    ├── eventstore-kurrentdb-adapter/  # Event persistence (KurrentDB / EventStoreDB)
    ├── database-inmemory-adapter/     # CQRS read model (dev/test)
    ├── database-postgres-adapter/     # CQRS read model (production)
    ├── event-queue-adapter/           # Event bus (Effect Queue)
    ├── http-rest-adapter/             # REST API + Swagger
    └── ...                            # auth, telemetry, web-ssr

apps/
├── runner/                     # Composition root (wires everything)
└── runner-e2e/                 # End-to-end tests against the running app

packages/create-node-ddd-app/   # The scaffolder (npm create node-ddd-app)

Key Principle: Domain is stable. Infrastructure is replaceable. Runner is sacrificial.

🔧 Configuration

The application is configured via environment variables:

# Event persistence (write model)
EVENT_STORE=memory              # memory, postgres, eventstoredb

# Async event distribution
EVENT_BUS=memory                # memory (the only event bus)

# Read model projections (one block per projection)
# Zero-config boots a canonical in-memory PostListView projection.
PROJECTION_DEFAULT=memory                # memory, postgres, none (opt out of seeding)
PROJECTION_POST_LIST_VIEW_TYPE=memory    # memory, postgres
# For postgres: PROJECTION_POST_LIST_VIEW_HOST / _PORT / _DATABASE / _USER / _PASSWORD

# Server configuration
HTTP_ENABLED=true
HTTP_PORT=3000

# Telemetry
TELEMETRY_PROVIDER=noop         # noop, signoz

See SETUP.md for detailed configuration guide.

🧪 Testing

# Run all tests
pnpm nx run-many -t test

# Run tests for a specific package
pnpm nx run @spr/debate-application:test

# Run tests with coverage
pnpm --filter @spr/shared-kernel run test:coverage

# End-to-end tests
pnpm test:e2e

See TESTING.md for the testing strategy (contract tests, integration tests, coverage expectations per package).

📦 Build and Deploy

Build

pnpm nx run-many -t build

Docker

# Development
./docker.sh dev

# Production setup with PostgreSQL
./docker.sh postgres

# Infrastructure only (for local Node.js debugging)
./docker.sh infra postgres signoz

See SETUP.md for complete Docker documentation.

🎓 Learning Resources

For Architects

Start with ARCHITECTURE.md to understand:

  • System design philosophy
  • Design patterns used
  • Dependency management
  • Why each architectural decision was made

For Developers

Start with SETUP.md to:

  • Get your environment running
  • Understand the development workflow
  • Learn Docker setup and commands
  • Configure environment variables

For QA Engineers

See TESTING.md for:

  • How to run tests
  • Testing strategies
  • API endpoint testing examples
  • CQRS flow verification

🛠️ Common Tasks

Run the app locally with debugger

# Terminal 1: Start infrastructure
./docker.sh infra postgres

# Terminal 2: Generate environment config (refuses to overwrite an existing
# .env.local; pass --force to replace it)
./docker.sh env:local

# Terminal 3: Build and run with inspector
pnpm nx run runner:build
node --inspect apps/runner/dist/index.js

# Connect debugger in Chrome: chrome://inspect

Test multiple adapters

# In-memory (no infrastructure needed)
pnpm nx run-many -t test

# PostgreSQL setup (runner integration tests)
./docker.sh infra postgres
pnpm --filter @spr/runner run test:integration

# SigNoz observability
./docker.sh infra postgres signoz
# View traces at http://localhost:8080

Add a new feature

  1. Define domain logic in packages/domain/
  2. Define application services and ports in packages/application/
  3. Implement adapter in packages/infrastructure/ if needed
  4. Wire in apps/runner/src/lib/factories.ts

🐛 Troubleshooting

Port Already in Use

HTTP_PORT=4000 node apps/runner/dist/index.js

"Adapter Not Implemented" Error

Use in-memory adapters:

EVENT_STORE=memory EVENT_BUS=memory PROJECTION_DEFAULT=memory node apps/runner/dist/index.js

Docker Issues

# Clean up everything
./docker.sh clean

# Check service health
./docker.sh health

# View logs
./docker.sh logs

📚 Additional Documentation

All documentation is organized under the docs/ folder:

Category Description
docs/ARCHITECTURE.md System design, patterns, principles
docs/TESTING.md Testing strategy and execution
docs/SETUP.md Development setup and environment
docs/SECURITY.md Security guidelines
docs/TELEMETRY.md Observability with SigNoz
docs/decisions/ Architecture Decision Records
docs/rfcs/ Request for Comments
docs/product/ Vision, roadmap, user stories
docs/templates/ Document templates (RFC, ADR, LOG)
docs/logs/ Operational logs
docs/archive/ Historical documentation
docker/README.md Docker reference

🤝 Contributing

Contributions welcome! Please ensure:

  • Architecture principles are maintained
  • Tests pass and coverage doesn't decrease
  • Documentation is updated
  • Commits follow the project style

📄 License

MIT © Riccardo Agnoletto — see ADR-2026-07-06-repository-license.

Projects generated with create-node-ddd-app are yours: no attribution or license obligation attaches to scaffolded output.


Built with ❤️ using Effect, Nx, and TypeScript