{"id":48679139,"url":"https://github.com/dogganidhal/noddde","last_synced_at":"2026-05-24T01:02:19.752Z","repository":{"id":346353641,"uuid":"759950266","full_name":"dogganidhal/noddde","owner":"dogganidhal","description":"Functional DDD framework for TypeScript — Decider pattern, Event Sourcing \u0026 CQRS out of the box","archived":false,"fork":false,"pushed_at":"2026-05-20T22:31:23.000Z","size":3765,"stargazers_count":31,"open_issues_count":6,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-21T04:53:43.466Z","etag":null,"topics":["cqrs","ddd","decider-pattern","domain-driven-design","event-sourcing","framework","javascript","nodejs","typescript"],"latest_commit_sha":null,"homepage":"https://noddde.dev/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dogganidhal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-02-19T16:21:11.000Z","updated_at":"2026-05-20T22:29:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dogganidhal/noddde","commit_stats":null,"previous_names":["dogganidhal/noddde"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/dogganidhal/noddde","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dogganidhal%2Fnoddde","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dogganidhal%2Fnoddde/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dogganidhal%2Fnoddde/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dogganidhal%2Fnoddde/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dogganidhal","download_url":"https://codeload.github.com/dogganidhal/noddde/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dogganidhal%2Fnoddde/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33417489,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"ssl_error","status_checked_at":"2026-05-23T22:14:43.778Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cqrs","ddd","decider-pattern","domain-driven-design","event-sourcing","framework","javascript","nodejs","typescript"],"created_at":"2026-04-10T22:02:48.903Z","updated_at":"2026-05-24T01:02:19.647Z","avatar_url":"https://github.com/dogganidhal.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\".github/logo-light.png\" alt=\"noddde\" width=\"300\"/\u003e\n\n### Type-Safe Domain Modeling \u0026 Event Sourcing for TypeScript\n\n[![CI](https://github.com/dogganidhal/noddde/actions/workflows/ci.yml/badge.svg)](https://github.com/dogganidhal/noddde/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/dogganidhal/noddde/graph/badge.svg)](https://codecov.io/gh/dogganidhal/noddde)\n[![npm](https://img.shields.io/npm/v/@noddde/core)](https://www.npmjs.com/package/@noddde/core)\n[![license](https://img.shields.io/npm/l/@noddde/core)](https://github.com/dogganidhal/noddde/blob/main/LICENSE)\n\n**Domain modeling that stays out of your way. Production guarantees that protect your data.**\n\n[Documentation](https://noddde.dev) • [Getting Started](https://noddde.dev/docs/getting-started/introduction) • [Architecture Specs](https://github.com/dogganidhal/noddde/tree/main/specs)\n\n\u003c/div\u003e\n\n---\n\n\u003e **Status:** Pre-1.0 Release Candidate. The core API is stable. Outbox, Graceful Shutdown, and distributed event bus adapters (RabbitMQ, NATS, Kafka) are shipped. We are currently focused on error isolation and developer ergonomics ahead of v1.0.\n\nBuilding a CQRS and Event Sourced system in TypeScript usually involves significant boilerplate. Developers often end up extending `AggregateRoot` base classes, decorating methods with `@CommandHandler()`, wiring up DI containers, and working around the type system.\n\n**noddde starts from a different premise: an aggregate is just a value.**\n\nBased on the functional [Decider pattern](https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider), noddde relies on pure functions and type inference rather than decorators and reflection. It provides the enterprise-grade infrastructure (Transactional Outbox, Upcasters, Unit of Work) required for real-world deployments.\n\n## Why noDDDe?\n\nMost TypeScript frameworks force you into a corner: either drown in OOP boilerplate (classes, decorators, and DI containers) or commit your entire database to an append-only Event Store. `noddde` offers a pragmatic, functional escape hatch.\n\n- **DDD Without the OOP Boilerplate:** Say goodbye to extending `AggregateRoot` or fighting with `@CommandHandler()` decorators. `noddde` is based entirely on the functional Decider pattern. Aggregates and Sagas are just pure functions, making your core domain incredibly easy to reason about and test.\n- **Pragmatic Hybrid Persistence:** Not every entity needs a historical audit trail. `noddde` lets you mix **State-Stored** aggregates (for simple CRUD entities) and **Event-Sourced** aggregates (for high-value business logic) in the exact same domain, interacting over the same command bus.\n- **The \"Dual-Write\" Problem, Solved:** Saving to a database and publishing an event usually leads to dropped messages if the server crashes. `noddde` solves this natively with a built-in **Transactional Outbox** and **Unit of Work**, ensuring your aggregate state and outgoing events commit in a single ACID transaction.\n- **Bring Your Own ORM:** No need to migrate to a niche database. `noddde` provides production-ready adapters for the tools you already use: **Drizzle, Prisma, and TypeORM** on top of standard Postgres, MySQL, or SQLite.\n- **Fearless Refactoring:** Zero runtime reflection. Because `noddde` relies entirely on strict TypeScript inference, if you change a command payload or an event schema, your IDE instantly highlights the exact projections, sagas, and tests that need updating.\n- **Native Observability:** Built-in [OpenTelemetry](https://opentelemetry.io/) instrumentation with zero required configuration. Install `@opentelemetry/api`, register a provider, and `noddde` automatically creates spans for command dispatch, projections, sagas, queries, and UoW commits — with full W3C Trace Context propagation through the event store. Works with any backend (Datadog, GCP, Jaeger, etc.).\n\n## How does noddde compare to the alternatives?\n\nThe TypeScript ecosystem generally forces you to choose between heavyweight OOP frameworks (like NestJS) or committing your entire architecture to Event Sourcing. **noddde is built for the pragmatic middle ground.** Both `noddde` and excellent frameworks like `Emmett` share the exact same modern domain philosophy: **we both use pure functions and the Decider pattern** to eliminate boilerplate. The difference lies entirely in _infrastructure and persistence_.\n\n`Emmett` is designed to be the ultimate developer experience for pure Event Sourced systems (often pairing with EventStoreDB). `noddde` is designed to bring that same elegant DX to **standard relational databases**, allowing you to choose your persistence strategy on a per-aggregate basis.\n\n| Feature / Philosophy     | NestJS CQRS                                 | Emmett                                                | noddde                                                              |\n| :----------------------- | :------------------------------------------ | :---------------------------------------------------- | :------------------------------------------------------------------ |\n| **Primary Focus**        | Full application framework modularity.      | Dedicated Event Sourcing \u0026 Event-Driven systems.      | **Pragmatic Hybrid DDD \u0026 CQRS.**                                    |\n| **Domain Paradigm**      | Heavy OOP, Base Classes, and `@Decorators`. | Pure Functions (Decider Pattern).                     | **Pure Functions (Decider Pattern).**                               |\n| **Persistence Strategy** | Typically State-Stored (via ORMs).          | Event-Sourced strictly by default.                    | **Hybrid:** Mix State-Stored \u0026 Event-Sourced aggregates.            |\n| **Infrastructure Focus** | Tightly coupled to the NestJS DI container. | Native append-only Event Stores (e.g., EventStoreDB). | **Relational First:** Native Drizzle/Prisma + Transactional Outbox. |\n| **Data Safety**          | Left entirely to the developer.             | Stream Versioning \u0026 Optimistic Concurrency.           | **ACID Unit of Work, Outbox, \u0026 Pessimistic Locks.**                 |\n| **Workflows / Sagas**    | Stateful classes listening to event buses.  | Process Managers reacting to streams.                 | **Pure functions** returning commands (executed in UoW).            |\n| **Observability**        | Manual instrumentation.                     | Manual instrumentation.                               | **Native OTel:** auto spans + W3C Trace Context propagation.        |\n\n### State-Stored or Event-Sourced: You Decide\n\nNot every aggregate requires an audit log. With `noddde`, you can mix and match. A `User` profile might just be state-stored (overwriting rows in Postgres), while a `Wallet` aggregate in the same domain uses full Event Sourcing.\n\n```typescript\n// 1. A State-Stored Aggregate (Just update the state, no events to replay)\nconst UserProfile = defineAggregate\u003cUserDef\u003e({\n  // ...\n});\n\n// 2. An Event-Sourced Aggregate (Emit events, replay history)\n// Same defineAggregate — the persistence strategy is chosen at wiring time\nconst Wallet = defineAggregate\u003cWalletDef\u003e({\n  // ...\n});\n\n// 3. Wire up with a single persistence adapter — it handles everything\nconst db = drizzle(connectionString, { schema });\nconst adapter = new DrizzleAdapter(db);\n\nconst myDomain = defineDomain({\n  writeModel: {\n    aggregates: { UserProfile, Wallet },\n  },\n  readModel: {\n    projections: {},\n  },\n});\n\nconst domain = await wireDomain(myDomain, {\n  persistenceAdapter: adapter,\n  aggregates: {\n    UserProfile: { persistence: \"state-stored\" },\n    Wallet: { persistence: \"event-sourced\" },\n  },\n});\n```\n\n## The API: Pure Functions, Zero Boilerplate\n\nAn aggregate in noddde is a plain object literal defining `initialState`, `commands`, and `apply`.\n\n```typescript\nimport { defineAggregate } from \"@noddde/core\";\n\nconst BankAccount = defineAggregate\u003cBankAccountDef\u003e({\n  initialState: { balance: 0 },\n\n  // Decide handlers decide what happens (Business Logic)\n  decide: {\n    Deposit: (command, state) =\u003e {\n      if (command.payload.amount \u003c= 0) throw new Error(\"Invalid amount\");\n      return {\n        name: \"DepositMade\",\n        payload: { amount: command.payload.amount },\n      };\n    },\n  },\n\n  // Evolve handlers evolve the state (Deterministic Replay)\n  evolve: {\n    DepositMade: (payload, state) =\u003e ({\n      balance: state.balance + payload.amount,\n    }),\n  },\n\n  // Type-safe schema evolution for old events\n  upcasters: bankAccountUpcasters,\n});\n```\n\nThere are no base classes to extend or lifecycle hooks to implement. The `evolve` handlers are pure and synchronous, making event replay deterministic.\n\n## Sagas: Workflows without Side Effects\n\nMost frameworks require Sagas (Process Managers) to inject an event bus and manually dispatch commands. In noddde, Sagas are pure functions that return commands as data.\n\n```typescript\nexport const OrderFulfillmentSaga = defineSaga\u003cOrderSagaDef\u003e({\n  initialState: { orderId: \"\", status: \"pending\" },\n\n  startedBy: [\"PaymentCompleted\"],\n\n  associations: {\n    PaymentCompleted: (event) =\u003e event.payload.orderId,\n  },\n\n  handlers: {\n    PaymentCompleted: (event, state) =\u003e ({\n      // Update saga state\n      state: { ...state, status: \"awaiting_shipment\" },\n      // Return commands to be dispatched atomically\n      commands: [\n        { name: \"ConfirmOrder\", targetAggregateId: state.orderId },\n        {\n          name: \"ArrangeShipment\",\n          targetAggregateId: event.payload.shipmentId,\n        },\n      ],\n    }),\n  },\n});\n```\n\nThe framework wraps the state update and the resulting commands in a single Unit of Work. Testing this requires zero mocking—you simply call the function and assert the returned array.\n\n## Testing: Given / When / Then\n\n`@noddde/testing` provides type-safe test harnesses that express tests in the natural BDD pattern without requiring domain bootstrap or database wiring.\n\n```typescript\nimport { testAggregate } from \"@noddde/testing\";\n\nconst result = await testAggregate(BankAccount)\n  .given(\n    { name: \"AccountCreated\", payload: { id: \"acc-1\" } },\n    { name: \"DepositMade\", payload: { amount: 1000 } },\n  )\n  .when({\n    name: \"Withdraw\",\n    targetAggregateId: \"acc-1\",\n    payload: { amount: 200 },\n  })\n  .execute();\n\nexpect(result.events[0].name).toBe(\"WithdrawalMade\");\nexpect(result.state.balance).toBe(800);\n```\n\n## Packages\n\n### Core\n\n| Package                                                            | Description                                                               |\n| :----------------------------------------------------------------- | :------------------------------------------------------------------------ |\n| [`@noddde/core`](https://www.npmjs.com/package/@noddde/core)       | Types, interfaces, and definition functions — zero runtime deps           |\n| [`@noddde/engine`](https://www.npmjs.com/package/@noddde/engine)   | Runtime engine: domain orchestration and in-memory implementations        |\n| [`@noddde/cli`](https://www.npmjs.com/package/@noddde/cli)         | CLI for scaffolding aggregates, projections, sagas, domains, and projects |\n| [`@noddde/testing`](https://www.npmjs.com/package/@noddde/testing) | Type-safe Given/When/Then test harnesses                                  |\n\n### Persistence Adapters\n\n| Package                                                            | Peer Dependency         | Features                                                         |\n| :----------------------------------------------------------------- | :---------------------- | :--------------------------------------------------------------- |\n| [`@noddde/drizzle`](https://www.npmjs.com/package/@noddde/drizzle) | `drizzle-orm` \u003e= 0.30   | PostgreSQL, MySQL, SQLite; advisory locking; convenience schemas |\n| [`@noddde/prisma`](https://www.npmjs.com/package/@noddde/prisma)   | `@prisma/client` \u003e= 5.0 | Any Prisma-supported database; advisory locking; built-in models |\n| [`@noddde/typeorm`](https://www.npmjs.com/package/@noddde/typeorm) | `typeorm` \u003e= 0.3        | PostgreSQL, MySQL, MariaDB, MSSQL, SQLite; built-in entities     |\n\n### Distributed Event Bus Adapters\n\n| Package                                                              | Peer Dependency   | Features                                                                 |\n| :------------------------------------------------------------------- | :---------------- | :----------------------------------------------------------------------- |\n| [`@noddde/rabbitmq`](https://www.npmjs.com/package/@noddde/rabbitmq) | `amqplib` \u003e= 0.10 | Exchange-based routing, durable queues, exponential backoff reconnection |\n| [`@noddde/nats`](https://www.npmjs.com/package/@noddde/nats)         | `nats` \u003e= 2.0     | JetStream durable consumers, subject-based routing, consumer groups      |\n| [`@noddde/kafka`](https://www.npmjs.com/package/@noddde/kafka)       | `kafkajs` \u003e= 2.0  | Consumer group fan-out, partition key strategy, manual offset commits    |\n\nAll event bus adapters provide at-least-once delivery with manual acknowledgment and configurable retry policies.\n\n## Getting Started\n\n```bash\nyarn add @noddde/core @noddde/engine\nyarn add --dev @noddde/testing\n```\n\nHead to the [Quick Start Guide](https://noddde.dev/docs/getting-started/quick-start) to build your first domain, or explore our production-ready sample applications:\n\n| Sample Domain                                       | ORM + Database       | Event Bus          | Concepts Demonstrated                                            |\n| :-------------------------------------------------- | :------------------- | :----------------- | :--------------------------------------------------------------- |\n| **[Hotel Booking](./samples/sample-hotel-booking)** | Drizzle + PostgreSQL | `@noddde/rabbitmq` | Full-stack: 3 Aggregates, Sagas, Projections, Metadata, HTTP API |\n| **[Auction](./samples/sample-auction)**             | Prisma + SQLite      | In-memory          | Event Upcasting, Projections + ViewStore, CQRS Queries           |\n| **[Flash Sale](./samples/sample-flash-sale)**       | TypeORM + PostgreSQL | `@noddde/nats`     | Optimistic \u0026 Pessimistic Concurrency, Snapshots, Idempotency     |\n\n## Contributing \u0026 Architecture\n\nnoddde is built using a strict spec-driven development pipeline. If you want to contribute, please read our [CLAUDE.md](./CLAUDE.md) and [specs/README.md](./specs/README.md) to understand how we maintain architectural rigor.\n\n---\n\n_License: MIT | Inspired by the [Decider pattern](https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider)._\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdogganidhal%2Fnoddde","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdogganidhal%2Fnoddde","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdogganidhal%2Fnoddde/lists"}