npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@contract-kit/domain

v0.1.1

Published

Domain modeling helpers for contract-kit - value objects, entities, and domain events

Downloads

227

Readme

@contract-kit/domain

Domain modeling helpers for Contract Kit - value objects, entities, and domain events

This package provides small, framework-agnostic helpers for domain-driven design patterns. Works with any Standard Schema library (Zod, Valibot, ArkType, etc.).

Installation

npm install @contract-kit/domain

# Use with your preferred Standard Schema library
npm install zod

TypeScript Requirements

This package requires TypeScript 5.0 or higher for proper type inference.

Value Objects

Value objects are immutable, validated types that represent domain concepts:

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

// Simple value object
const Email = valueObject("Email")
  .schema(z.string().email())
  .brand();

type Email = typeof Email.Type;

// Create with validation
const email = await Email.create("[email protected]"); // Returns branded Email type

// Check validity without throwing
const isValid = await Email.isValid("invalid"); // false

// Raw value access
console.log(email); // "[email protected]"

Complex Value Objects

const Money = valueObject("Money")
  .schema(z.object({
    amount: z.number().positive(),
    currency: z.enum(["USD", "EUR", "GBP"]),
  }))
  .brand();

type Money = typeof Money.Type;

const price = await Money.create({ amount: 99.99, currency: "USD" });

Value Object with Transformation

const Slug = valueObject("Slug")
  .schema(
    z.string()
      .min(1)
      .transform((s) => s.toLowerCase().replace(/\s+/g, "-"))
  )
  .brand();

const slug = await Slug.create("Hello World"); // "hello-world"

Entities

Entities are domain objects with identity and behavior:

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

const Todo = entity("Todo")
  .props(z.object({
    id: z.string(),
    title: z.string(),
    description: z.string().optional(),
    completed: z.boolean().default(false),
    createdAt: z.date().default(() => new Date()),
  }))
  .methods((self) => ({
    complete() {
      return self.with({ completed: true });
    },
    rename(title: string) {
      return self.with({ title });
    },
    isOverdue(now: Date) {
      // Read-only method
      return !self.props.completed && self.props.createdAt < now;
    },
  }))
  .build();

type Todo = typeof Todo.Type;

Using Entities

// Create a new entity
const todo = Todo.create({
  id: "1",
  title: "Learn Contract Kit",
});

// Access properties
console.log(todo.id);        // "1"
console.log(todo.title);     // "Learn Contract Kit"
console.log(todo.completed); // false

// Call methods (returns new instance - immutable)
const completed = todo.complete();
console.log(todo.completed);      // false (original unchanged)
console.log(completed.completed); // true

// Chain method calls
const renamed = todo
  .rename("Master Contract Kit")
  .complete();

Entity with Validation

const User = entity("User")
  .props(z.object({
    id: z.string().uuid(),
    email: z.string().email(),
    name: z.string().min(1),
    role: z.enum(["user", "admin"]).default("user"),
  }))
  .methods((self) => ({
    promote() {
      if (self.props.role === "admin") {
        throw new Error("Already an admin");
      }
      return self.with({ role: "admin" as const });
    },
  }))
  .build();

Domain Events

Domain events represent something that happened in your domain:

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

const TodoCompleted = domainEvent(
  "todo.completed",
  z.object({
    todoId: z.string(),
    completedAt: z.string().datetime(),
    completedBy: z.string(),
  })
);

type TodoCompleted = typeof TodoCompleted;

// Create an event instance
const event = {
  type: "todo.completed",
  payload: {
    todoId: "123",
    completedAt: new Date().toISOString(),
    completedBy: "user-1",
  },
};

Using Domain Events with Use Cases

import { createUseCaseFactory } from "@contract-kit/application";
import { TodoCompleted } from "@/domain/events";

const completeTodo = useCase
  .command("todos.complete")
  .input(z.object({ id: z.string() }))
  .output(z.object({ success: z.boolean() }))
  .emits([TodoCompleted]) // Declares event emission
  .run(async ({ ctx, input }) => {
    const todo = await ctx.ports.db.todos.complete(input.id);
    
    await ctx.ports.eventBus.publish({
      type: "todo.completed",
      payload: {
        todoId: todo.id,
        completedAt: new Date().toISOString(),
        completedBy: ctx.user.id,
      },
    });

    return { success: true };
  });

API Reference

valueObject(name)

Creates a value object builder.

valueObject(name: string)
  .schema(schema: StandardSchema)
  .brand() // Returns the value object definition

Value Object Definition

type ValueObjectDef = {
  name: string;
  Type: BrandedType;
  create: (value) => Promise<BrandedType>;
  isValid: (value) => Promise<boolean>;
};

entity(name)

Creates an entity builder.

entity(name: string)
  .props(schema: StandardSchema)
  .methods((self) => ({
    methodName() { return self.with({ ... }); },
  }))
  .build() // Returns the entity definition

Entity Instance

type EntityInstance = {
  // All props are accessible as properties
  [key: string]: value;
  
  // Methods defined in .methods()
  methodName(): EntityInstance;
  
  // Built-in method for updates
  with(updates: Partial<Props>): EntityInstance;
};

domainEvent(type, payloadSchema)

Creates a domain event definition.

const Event = domainEvent(
  type: string,
  payloadSchema: StandardSchema
);

Domain Event Definition

type DomainEventDef = {
  name: string;       // Event type name
  payload: Schema;    // Payload schema for validation
};

Patterns

Aggregate Roots

Use entities as aggregate roots with child entities:

const Order = entity("Order")
  .props(z.object({
    id: z.string(),
    customerId: z.string(),
    items: z.array(OrderItem.schema),
    status: z.enum(["draft", "placed", "shipped", "delivered"]),
  }))
  .methods((self) => ({
    addItem(item: OrderItem) {
      return self.with({
        items: [...self.props.items, item],
      });
    },
    place() {
      if (self.props.items.length === 0) {
        throw new Error("Cannot place empty order");
      }
      return self.with({ status: "placed" });
    },
  }))
  .build();

Rich Domain Model

Encapsulate business logic in entities:

const Account = entity("Account")
  .props(z.object({
    id: z.string(),
    balance: z.number(),
    status: z.enum(["active", "frozen", "closed"]),
  }))
  .methods((self) => ({
    deposit(amount: number) {
      if (self.props.status !== "active") {
        throw new Error("Account is not active");
      }
      return self.with({
        balance: self.props.balance + amount,
      });
    },
    withdraw(amount: number) {
      if (self.props.status !== "active") {
        throw new Error("Account is not active");
      }
      if (self.props.balance < amount) {
        throw new Error("Insufficient funds");
      }
      return self.with({
        balance: self.props.balance - amount,
      });
    },
    freeze() {
      return self.with({ status: "frozen" });
    },
  }))
  .build();

Related Packages

License

MIT