Domain

The @contract-kit/domain package provides small helpers for domain-driven design: entities, value objects, and domain events.

bun add @contract-kit/domain

Value objects

Immutable, validated types that represent a concept with no identity (e.g. an email address, a currency amount):

import { valueObject } from "@contract-kit/domain";
import { z } from "zod";

const Email = valueObject("Email")
  .schema(z.string().email())
  .brand();

const email = Email.create("user@example.com"); // branded string
Email.isValid("not-an-email"); // false

Entities

Domain objects with identity and behavior. Entities are immutable — methods return new instances:

import { entity } from "@contract-kit/domain";
import { z } from "zod";

const Todo = entity("Todo")
  .props(z.object({
    id: z.string(),
    title: z.string(),
    completed: z.boolean(),
  }))
  .methods((self) => ({
    complete: () => self.with({ completed: true }),
    rename: (title: string) => self.with({ title }),
  }))
  .build();

const todo = Todo.create({ id: "1", title: "Buy milk", completed: false });
const done = todo.complete(); // new instance with completed: true

Every entity gets a .with() method for partial updates, returning a new instance.

Domain events

Typed event declarations for use with the application package:

import { domainEvent } from "@contract-kit/domain";
import { z } from "zod";

const todoCreated = domainEvent(
  "todo.created",
  z.object({ id: z.string(), title: z.string() }),
);

const todoCompleted = domainEvent(
  "todo.completed",
  z.object({ id: z.string() }),
);

Domain events are declarations — they describe the shape of something that happened. Use cases declare which events they emit via .emits(), and an event bus port delivers them to subscribers.

const createTodo = define
  .command("createTodo")
  .input(z.object({ title: z.string() }))
  .output(z.object({ id: z.string(), title: z.string() }))
  .emits([todoCreated])
  .run(async ({ input, ctx }) => {
    const todo = await ctx.ports.db.todos.create(input);
    await ctx.ports.eventBus.publish(todoCreated, {
      id: todo.id,
      title: todo.title,
    });
    return todo;
  });

Schema libraries

All three helpers work with any Standard Schema library — Zod, Valibot, ArkType, etc.