Installation
npm install @woltz/rich-domain
You’ll also need a validation library that implements Standard Schema. We recommend Zod:
@woltz/rich-domain works with any Standard Schema compatible library: Zod,
Valibot, ArkType, and others.
Create Your First Aggregate
Let’s build a simple Product aggregate with validation and change tracking.
1. Define the Schema
import { z } from "zod";
import { Id } from "@woltz/rich-domain";
const productSchema = z.object({
id: z.custom<Id>((val) => val instanceof Id),
name: z.string().min(3, "Name must be at least 3 characters"),
price: z.number().positive("Price must be positive"),
stock: z.number().int().min(0, "Stock cannot be negative"),
status: z.enum(["active", "inactive"]),
});
type ProductProps = z.infer<typeof productSchema>;
2. Create the Aggregate
import { Aggregate, EntityValidation, Id } from "@woltz/rich-domain";
class Product extends Aggregate<ProductProps> {
protected static validation: EntityValidation<ProductProps> = {
schema: productSchema,
config: {
onCreate: true,
onUpdate: true,
throwOnError: true,
},
};
get name() {
return this.props.name;
}
get price() {
return this.props.price;
}
get stock() {
return this.props.stock;
}
get status() {
return this.props.status;
}
updatePrice(newPrice: number) {
this.props.price = newPrice;
}
}
3. Use It
// Create a new product
const product = new Product({
name: "Mechanical Keyboard",
price: 149.99,
stock: 50,
status: "active",
});
console.log(product.isNew()); // true - ID was auto-generated
console.log(product.id.value); // "550e8400-e29b-..." (UUID)
// Update properties (validated automatically)
product.updatePrice(129.99);
product.decreaseStock(5);
// Get changes for persistence
const changes = product.getChanges();
console.log(changes.hasUpdates()); // true
// Serialize to JSON
const json = product.toJSON();
// { id: "550e...", name: "Mechanical Keyboard", price: 129.99, stock: 45, status: "active" }
Validation in Action
Validation runs automatically on creation and updates:
// Invalid on creation - throws ValidationError
const invalid = new Product({
name: "AB", // too short
price: -10, // negative
stock: 50,
status: "active",
});
// ❌ ValidationError: Name must be at least 3 characters, Price must be positive
// Invalid on update - throws ValidationError
const product = new Product({
name: "Keyboard",
price: 100,
stock: 10,
status: "active",
});
product.decreaseStock(20); // stock becomes -10
// ❌ ValidationError: Stock cannot be negative
Lifecycle Hooks
Hooks let you run custom logic during entity lifecycle:
import {
Aggregate,
EntityValidation,
EntityHooks,
DomainError,
} from "@woltz/rich-domain";
class Product extends Aggregate<ProductProps> {
protected static validation: EntityValidation<ProductProps> = {
schema: productSchema,
config: {
onCreate: true,
onUpdate: true,
throwOnError: true,
},
};
protected static hooks: EntityHooks<ProductProps, Product> = {
// Runs after entity is created
onCreate: (product) => {
console.log(`Product created: ${product.name}`);
},
// Runs before any property update
// Return false to block the change
onBeforeUpdate: (product, snapshot) => {
// Prevent price changes on inactive products
if (snapshot.status === "inactive" && product.price !== snapshot.price) {
return false;
}
return true;
},
// Custom business rules (runs on create and update)
rules: (product) => {
// Premium products must have stock
if (product.price > 1000 && product.stock === 0) {
throw new DomainError(
"stock",
"Premium products must have stock available"
);
}
},
};
// ... getters and methods
}
Hooks in Action
// onCreate - logs "Product created: Laptop"
const product = new Product({
name: "Laptop",
price: 1500,
stock: 10,
status: "active",
});
// onBeforeUpdate - blocks price change on inactive product
product.deactivate();
product.updatePrice(1200); // silently blocked, price stays 1500
// rules - throws on business rule violation
const invalid = new Product({
name: "Gaming PC",
price: 2000,
stock: 0, // premium with no stock
status: "active",
});
// ❌ ValidationError: Premium products must have stock available
Next Steps