Skip to main content

What is rich-domain?

@woltz/rich-domain is a TypeScript library that provides building blocks for implementing Domain-Driven Design (DDD) patterns with minimal boilerplate and maximum type safety.

Type-Safe

Full TypeScript support with inference for field paths, filters, and operations

Validation Agnostic

Works with Zod, Valibot, ArkType, or any Standard Schema compatible library

Automatic Change Tracking

Track changes across nested entities and collections without boilerplate

ORM Independent

Use with Prisma, Drizzle, TypeORM, or any persistence layer

Core Concepts

Entities & Aggregates

Entities have identity and lifecycle. Aggregates are consistency boundaries that manage related entities.
class Order extends Aggregate<OrderProps> {
  addItem(product: Product, quantity: number) {
    this.props.items.push(new OrderItem({ product, quantity }));
  }

  get total() {
    return this.props.items.reduce((sum, item) => sum + item.subtotal, 0);
  }
}

Value Objects

Immutable objects compared by value, not identity.
class Money extends ValueObject<{ amount: number; currency: string }> {
  add(other: Money): Money {
    if (this.props.currency !== other.props.currency) {
      throw new Error("Cannot add different currencies");
    }
    return this.clone({ amount: this.props.amount + other.props.amount });
  }
}

Change Tracking

Automatically track all changes for efficient persistence.
const order = new Order({ ... });

order.addItem(product, 2);
order.items[0].updateQuantity(5);

const changes = order.getChanges();
// { creates: [OrderItem], updates: [OrderItem], deletes: [] }

Repository & Criteria

Type-safe queries with a fluent API.
const result = await orderRepo.find(
  Criteria.create<Order>()
    .whereEquals("status", "pending")
    .where("total", "greaterThan", 100)
    .orderByDesc("createdAt")
    .paginate(1, 20)
);

Requirements

  • Node.js >= 20
  • TypeScript >= 4.7

Installation

npm install @woltz/rich-domain

Quick Example

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

// Define schema
const userSchema = z.object({
  id: z.custom<Id>((val) => val instanceof Id),
  name: z.string().min(2),
  email: z.string().email(),
  status: z.enum(["active", "inactive"]),
});

type UserProps = z.infer<typeof userSchema>;

// Create aggregate
class User extends Aggregate<UserProps> {
  protected static validation: EntityValidation<UserProps> = {
    schema: userSchema,
  };

  get name() {
    return this.props.name;
  }
  get email() {
    return this.props.email;
  }

  activate() {
    this.props.status = "active";
  }
  deactivate() {
    this.props.status = "inactive";
  }
}

// Use it
const user = new User({
  name: "John Doe",
  email: "john@example.com",
  status: "active",
});

console.log(user.isNew()); // true
console.log(user.toJSON()); // { id: "...", name: "John Doe", ... }

Next Steps