Skip to content

tarcisioandrade/rich-domain

Repository files navigation

Rich Domain

Domain-Driven Design toolkit for TypeScript.

License: MIT Node.js Version TypeScript

Documentation · Quick Start · Examples


Packages

Package Version Description
@woltz/rich-domain npm Core library — Entities, Aggregates, Value Objects, Criteria, Change Tracking, Domain Events
@woltz/rich-domain-prisma npm Prisma adapter — Unit of Work, batch operations, @Transactional decorator
@woltz/rich-domain-drizzle npm Drizzle adapter — Unit of Work, batch operations, @Transactional decorator
@woltz/rich-domain-typeorm npm TypeORM adapter — repository, transactions, batch executor
@woltz/rich-domain-export npm Multi-format data export (CSV, JSON, JSONL) with streaming support
@woltz/rich-domain-criteria-zod npm Zod schemas for validating Criteria query params from HTTP requests

Quick Start

Install

npm install @woltz/rich-domain zod

Add an ORM adapter if needed: @woltz/rich-domain-prisma, @woltz/rich-domain-drizzle, or @woltz/rich-domain-typeorm.

Define a Value Object

import { ValueObject, VOValidation } from "@woltz/rich-domain";
import { z } from "zod";

const emailSchema = z.string().email();


class Email extends ValueObject<string> {
  protected static validation<VOValidation<string>> = { schema: emailSchema };

  getDomain(): string {
    return this.value.split("@")[1];
  }
}

const email = new Email("john@example.com");
console.log(email.value);       // "john@example.com"
console.log(email.getDomain()); // "example.com"

Define an Aggregate with Validation

import {
  Aggregate,
  Entity,
  Id,
  type EntityValidation,
  type EntityHooks,
} from "@woltz/rich-domain";
import { z } from "zod";

// Child Entity
const PostSchema = z.object({
  id: z.custom<Id>(),
  title: z.string().min(1),
  content: z.string(),
  published: z.boolean(),
});
type PostProps = z.infer<typeof PostSchema>;

class Post extends Entity<PostProps> {
  protected static validation: EntityValidation<PostProps> = {
    schema: PostSchema,
  };

  publish() {
    this.props.published = true;
  }
  get title() {
    return this.props.title;
  }
  get content() {
    return this.props.content;
  }
}

// Aggregate Root
const UserSchema = z.object({
  id: z.custom<Id>(),
  email: z.custom<Email>(),
  name: z.string().min(2),
  posts: z.array(z.instanceof(Post)),
  createdAt: z.date(),
});
type UserProps = z.infer<typeof UserSchema>;

class User extends Aggregate<UserProps, "createdAt"> {
  protected static validation: EntityValidation<UserProps> = {
    schema: UserSchema,
  };

  protected static hooks: EntityHooks<UserProps, User> = {
    onBeforeCreate: (props) => {
      if (!props.createdAt) props.createdAt = new Date();
    },
  };

  // Recommended pattern for type-safe change tracking
  getTypedChanges() {
    interface Entities {
      Post: Post;
    }
    return this.getChanges<Entities>();
  }

  addPost(title: string, content: string): void {
    this.props.posts.push(new Post({ title, content, published: false }));
  }

  get email() {
    return this.props.email.value;
  }
  get posts() {
    return this.props.posts;
  }
}

Query with Type-Safe Criteria

import { Criteria } from "@woltz/rich-domain";

const criteria = Criteria.create<User>()
  .where("status", "equals", "active")
  .whereContains("email", "@company.com")
  .orderBy("createdAt", "desc")
  .paginate(1, 20);

const result = await userRepository.find(criteria);

Automatic Change Tracking

const user = await userRepository.findById(userId);
user.addPost("Hello", "World");
user.posts[0].publish();

const changes = user.getTypedChanges();
// changes.of("Post").creates  → [Post]
// changes.of("Post").updates  → [{ entity: Post, changed: { published: { from: false, to: true } } }]

await userRepository.save(user); // Only the diff hits the database
user.markAsClean();

Domain Events

Rich Domain provides the IDomainEventBus interface — you bring the implementation (in-memory, BullMQ, Kafka, etc.).

import { DomainEvent, type IDomainEventBus } from "@woltz/rich-domain";

class OrderConfirmedEvent extends DomainEvent<{
  orderId: string;
  total: number;
}> {}

// Inside a use case
const order = Order.create(data);
await orderRepository.save(order);
await order.dispatchAll(eventBus); // Publishes and clears events automatically

Development

Prerequisites: Node.js ≥ 22.12.0

git clone https://github.com/tarcisioandrade/rich-domain.git
cd rich-domain
npm install
npm run build
npm test

Workspace commands:

npm run build --workspace=@woltz/rich-domain    # Build a single package
npm run test --workspace=@woltz/rich-domain     # Test a single package
npm run check                                   # Type-check all packages
npm run lint                                    # Lint all packages
npm run clean                                   # Clean all build artifacts

Documentation

Visit woltz.mintlify.app for the full documentation covering core concepts, validation, criteria queries, repository patterns, ORM integrations, React components, and CLI reference.

Contributing

Contributions are welcome. Please open an issue first to discuss what you'd like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/my-feature)
  3. Commit with conventional commits (git commit -m 'feat: add something')
  4. Run npm run check && npm test && npm format before pushing
  5. Open a Pull Request

License

MIT © Tarcisio Andrade