npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

create-node-ddd-app

v0.6.0

Published

Scaffold an Effect-TS DDD monorepo: npm create node-ddd-app my-app

Downloads

327

Readme

create-node-ddd-app

Scaffold a TypeScript CQRS + Event Sourcing monorepo — Domain-Driven Design, Hexagonal Architecture, and the Effect library — with one command.

Early access (0.x). The scaffolder works end to end — interactive adapter picker, pruned output, LLM workflow harness — but the generated output and CLI flags may still change before 1.0. Feedback and bug reports are very welcome (registration is open): git.marric.quest/riccardo/create-node-ddd-app/issues.

Quick start

npm create node-ddd-app@latest my-app
# or
pnpm create node-ddd-app my-app
# or
npx create-node-ddd-app my-app

The CLI walks you through an interactive picker (one question per port, then the LLM harness tier and an MCP wiring offer), scaffolds the project, and prunes the adapters you did not select out of the build.

Skip the prompts entirely and take the default profile:

npm create node-ddd-app@latest my-app -- --yes

--yes scaffolds the all-memory profile: memory event store, memory projections, memory event bus, no auth, noop telemetry, REST transport, the full harness tier, MCP declined. It installs, builds, tests and boots with no external services — no PostgreSQL, KurrentDB, Casdoor or SigNoz:

cd my-app
pnpm install
pnpm build
pnpm --filter @spr/runner start

Flags

<target-directory>  Where to scaffold the new project

--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 usage and exit

--llm-harness and --mcp apply on both paths: with --yes they override the default profile, without it they pre-answer those two questions.

The picker

Six adapter ports, one question each:

| Port | Choices | | ---- | ------- | | Event store | memory, postgres, kurrentdb | | Projections (multi) | memory, postgres, mongodb (stub) | | Event bus | memory, rabbitmq (stub) | | Authorization | none, casbin, casdoor | | Telemetry | noop, signoz | | Transports (multi) | rest, ssr, grpc (sketch), websocket (sketch) |

Then the LLM workflow harness tier (full / light / none) and whether to wire the ts-morph MCP server. Two hard constraints are enforced: at least one projection and one transport, and any auth other than none requires the rest transport.

What you get

A layered monorepo, not a single flattened app — the point is to keep the architectural boundaries visible and editable in your own tree:

packages/domain/*          # aggregates, events, commands, queries — zero deps
packages/application/*     # use cases, port interfaces
packages/infrastructure/*  # only the adapters you selected
apps/runner/                # composition root; wires everything together
sketches/infrastructure/*  # unselected adapters, kept as inert reference source

Every scaffold ships two worked-example bounded contexts, not one, so you see a real inter-context relationship instead of a single isolated aggregate:

  • Debate (packages/domain/debate-domain) — the Post aggregate: create, edit, publish, archive, delete. Publishing and archiving emit PostPublished / PostArchived; delete is terminal.
  • Moderation (packages/domain/moderation-domain) — the ModerationCase aggregate: report a published post, escalate to escalated after three reports, resolve uphold/dismiss, reopen a previously closed case.

Domain events are the contract between them, not a shared table or a direct call. Moderation never imports Debate's aggregate; it reads Debate only through PostPublishStatus, a read model built by replaying Debate's own PostPublished / PostArchived / PostDeleted events — an anti-corruption view. Three stateless policies (ModerationPolicies, run under the same projector supervisor as the read models) act as the process manager: they react to ModerationCase events to keep the moderation queue and a reopened-cases metric current. Both contexts ship as the worked example in every scaffold; there is no command yet to remove one (eject, RFC-0022, still Draft).

A short walkthrough on the default --yes profile (REST enabled, no auth):

# Create and publish a post (Debate)
curl -X POST http://localhost:3000/posts -H "Content-Type: application/json" \
  -d '{"aggregateId": "post-1", "userId": "user-1", "title": "First post", "content": "Hello"}'
curl -X POST http://localhost:3000/posts/post-1/publish

# Report it (Moderation) and check the queue
curl -X POST http://localhost:3000/posts/post-1/report -H "Content-Type: application/json" \
  -d '{"reason": {"category": "spam"}}'
curl http://localhost:3000/moderation/queue

The report call can briefly return 409 { "retryable": true } right after publish — Moderation only accepts a report once its anti-corruption view of the publish event has caught up; retry once it has.

  • CQRS + Event Sourcing on Effect — typed errors, Layer-based dependency injection, Effect.gen service implementations.

  • Moderation's REST surface: POST /posts/:id/report, GET /moderation/queue, GET /moderation/cases/:id, POST /moderation/cases/:id/resolve. Its three read models — ModerationQueue, PostPublishStatus, ReopenedCases — are memory-seeded by default (PROJECTION_MODERATION_QUEUE_TYPE, PROJECTION_POST_PUBLISH_STATUS_TYPE, PROJECTION_REOPENED_CASES_TYPE; the queue also supports postgres). A moderator Casbin role is seeded alongside them — see the Adapter status table below for the one event store this context is not wired on.

  • Pruned to your selection — the runner's wiring, the config parsers' vocabularies, the workspace manifests and .env.example describe only the adapters you picked. The generated README lists exactly what was selected, with an honest status per adapter.

  • sketches/ + add-adapter — adapters you did not select are moved to sketches/infrastructure/ (or hard-deleted with --no-sketches). Nothing installs, builds or typechecks them; node scripts/add-adapter.mjs <port> <adapter> re-integrates one later (restores the package, its workspace dependency and the runner wiring).

  • An LLM workflow harness, in three tiers. Every tier gets a parameterized CLAUDE.md tree (root, plus one per layer and package). The harness files come from a pinned llm-template commit (harness.lock, currently v0.2.1), copied into the package when it is built, so scaffolding never fetches anything:

    • lightAGENTS.md, the code-search skill (.claude/skills/code-search), docs/DOCUMENTATION.md and the docs/templates/ set (RFC, log, plan, spec, story, task-YAML and CLAUDE.md templates), and the structural ast-grep lint (sgconfig.yml, tools/ast-grep/rules/, pnpm lint:arch);
    • full — everything in light, plus:
      • Claude Code hooks: .claude/settings.json, whose SubagentStop hook runs quality-gates.conf (lint and test wired; typecheck left empty, see Known limitations), and the preflight and bootstrap-gate hooks;
      • the bootstrap skill and a .bootstrapped marker;
      • the task system: .tasks/ schema and validators, scripts/tasks-*.js, docs/TASKS.md, and pnpm tasks / tasks:ready / tasks:validate scripts;
      • per-client adapters: opencode.json (skills path, plus the MCP entry with --mcp) and .pi/settings.json (skills path), so opencode and pi read the same .claude/skills as Claude Code;
      • the seeded ADRs and starter backlog below;
      • with --mcp, the read-only ts-morph semantic-analysis MCP server, wired via tsconfig.tsmorph.json + .mcp.json (+ opencode.json);
    • none — no harness files, just the architecture docs.

    The generated README gets an ## LLM harness table listing, for Claude Code, opencode, pi and oh-my-pi, which of the project's files each client reads. Hooks only run in Claude Code; in the other clients the gates are rules you run yourself.

  • Seeded architecture decision records (docs/decisions/, full tier) describing the choices your selection implies, and a starter task backlog (.tasks/tracked/getting-started.yaml, full tier) scoped to what was scaffolded — a first aggregate, and a "graduate this sketch" task per unwired transport. pnpm tasks ready lists the next unblocked task.

  • A /ddd-model skill for Claude Code (.claude/skills/ddd-model, with its ddd-modeler read-only subagent): decomposes requirements into bounded contexts and aggregates, then maps them onto the packages/domain/<context>-domain layout.

MCP wiring (--mcp, full tier only)

Adds ts-morph-readonly-mcp (an exact version from the npm registry) to the generated root devDependencies, registers it in .mcp.json (Claude Code) and under mcp in opencode.json (opencode) with the same environment, and writes the tsconfig.tsmorph.json it reads. The scaffolder never installs anything: the server is fetched by your own pnpm install, and the client picks it up on its next session start.

Adapter status

Every adapter in the table below ships in the template. With a selection, only the selected ones are wired into the generated project; the rest go to sketches/ (or are deleted with --no-sketches). Here is exactly what's real, what's a stub, and what's an unwired sketch — nothing in this table is overclaimed.

| Port | Adapter | Status | Notes | | ---- | ------- | ------ | ----- | | Event store | memory | real | default; write + read side share one Ref | | Event store | postgres | real | requires POSTGRES_EVENTSTORE_* configuration | | Event store | kurrentdb | real | event-native store; KURRENTDB_* configuration; TLS gate in production; does not host the Moderation context — see Known limitations | | Projections | memory | real | default; in-memory read model per projection | | Projections | postgres | real | shared-pool read model; requires PROJECTION_PASSWORD off localhost | | Projections | mongodb | stub | Layer.fail until an adapter lands | | Event bus | memory | real | default; in-process Effect Queue | | Event bus | rabbitmq | stub | Layer.fail until an adapter lands | | Event bus | kafka | stub | Layer.fail until an adapter lands | | Auth | casbin | real | in-process RBAC over the in-memory authentication | | Auth | casdoor | real | external OAuth2/OIDC + ABAC; requires CASDOOR configuration | | Telemetry | noop | real | default; console output only | | Telemetry | signoz | real | OpenTelemetry export to the configured OTLP endpoint | | Transport | rest | real | HTTP REST API + Swagger on HTTP_PORT | | Transport | web-ssr | real | server-rendered demo UI on WEB_PORT | | Transport | grpc | sketch | unwired — no runner wiring exists yet | | Transport | websocket | sketch | unwired — no runner wiring exists yet |

"real" adapters are wired and usable; "stub" means the port exists but the factory returns Layer.fail until an adapter lands; "sketch" means the adapter source ships but no runner wiring exists at all yet.

Requirements

  • Node.js >= 22
  • pnpm (the generated project is a pnpm + Nx workspace)

Next steps after scaffolding

  1. cd into your new project
  2. install dependencies: pnpm install
  3. review .env.example, then copy it to .env
  4. build and test: pnpm build && pnpm test — then run it: pnpm --filter @spr/runner start
  5. production deployments must set NODE_ENV=production — the auth=none baseline refuses to boot under it
  6. read the generated project's own README for its adapter status table and choose a license (the generated package.json ships "license": "UNLICENSED" as a placeholder — the scaffolded source is yours, with no attribution or license obligation)

Selections that need an external service (PostgreSQL, KurrentDB, Casdoor, SigNoz) get an extra hint printed after scaffolding, pointing at the relevant .env.example variables or adapter README.

Known limitations

  • KurrentDB does not host the Moderation context. Select EVENT_STORE=kurrentdb and none of Moderation's projections are registered, none of its REST routes are reachable, and the moderator Casbin role goes unused — the ModerationCase stream is not wired on this event store yet.
  • No eject. Both Debate and Moderation ship as worked examples in every scaffold; there is no command to remove one (RFC-0022 is Draft).
  • Pruned selections may fail pnpm nx run-many -t typecheck with TS2307 (a stripped adapter still referenced from an unfenced line). pnpm build and pnpm test pass for the tested combinations; the typecheck gap is tracked, and for the same reason the TYPECHECK entry in the generated quality-gates.conf is left empty.
  • No rollback if scaffolding fails mid-way: the target directory is left as is. Delete it and re-run.
  • No docker compose is vendored for the postgres, kurrentdb, casdoor or signoz selections — you bring the service; .env.example carries the variables the config parsers read.

How it's built

create-node-ddd-app publishes a single npm package. The layered @spr/* workspace packages are never published to the registry — they ship as vendored source files inside the generated project (kept private: true there). The template/ this CLI scaffolds from is built from the source monorepo at publish time, so it never drifts out of sync with the real adapters it describes.

License

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