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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@nivinjoseph/n-domain

v3.0.3

Published

Domain Driven Design and Event Sourcing based framework for business layer implementation

Downloads

3,778

Readme

n-domain

Overview

n-domain is a TypeScript framework that provides a robust foundation for implementing business logic using Domain-Driven Design (DDD) and Event Sourcing patterns. It helps you create maintainable and scalable domain models while enforcing best practices in domain-driven design.

Features

  • Domain-Driven Design Support: Built-in abstractions for DDD concepts like Aggregates, Entities, and Domain Events
  • Event Sourcing: Native support for event-sourced aggregates and state management
  • Type Safety: Written in TypeScript with strong typing support
  • Flexible Configuration: Configurable domain contexts and state management
  • Clean Architecture: Promotes separation of concerns and clean architecture principles

Installation

# Using npm
npm install @nivinjoseph/n-domain

# Using yarn
yarn add @nivinjoseph/n-domain

Domain Organization

The framework encourages a clean and organized domain structure. Here's how to organize your domain:

domain/
├── aggregate.ts           # Main aggregate root implementation
├── aggregate-state.ts     # State interface and factory
├── events/                # Domain events
│   ├── aggregate-created.ts
│   ├── aggregate-updated.ts
│   └── aggregate-deleted.ts
└── value-objects/        # Value objects
    ├── description.ts
    └── other-value-objects.ts

Key Components

  1. Aggregate Root (aggregate.ts)

    • Main business entity
    • Handles business logic
    • Manages state changes through events
    • Example: Todo aggregate
  2. State Management (aggregate-state.ts)

    • Defines the state interface
    • Implements state factory
    • Handles state transitions
    • Example: TodoState and TodoStateFactory
  3. Domain Events (events/)

    • Represent state changes
    • Immutable and serializable
    • Follow naming convention: AggregateActionEvent
    • Examples: TodoCreated, TodoUpdated, TodoDeleted
  4. Value Objects (value-objects/)

    • Immutable objects
    • No identity
    • Represent domain concepts
    • Examples: TodoDescription, Address, Money

Core Concepts

Aggregate Roots

Aggregate roots are the main building blocks of your domain model. They encapsulate business logic and ensure consistency boundaries. Example:

import { given } from "@nivinjoseph/n-defensive";
import { AggregateRoot, DomainContext, DomainEvent } from "@nivinjoseph/n-domain";
import { TodoCreated } from "./events/todo-created";
import { TodoState, TodoStateFactory } from "./todo-state";

@serialize("Test")
export class Todo extends AggregateRoot<TodoState, TodoDomainEvent>
{
    public get title(): string { return this.state.title; }
    public get description(): string | null { return this.state.description?.description ?? null; }
    public get isCompleted(): boolean { return this.state.isCompleted; }

    public constructor(domainContext: DomainContext, events: ReadonlyArray<DomainEvent<TodoState>>, state?: TodoState)
    {
        super(domainContext, events, new TodoStateFactory(), state);
    }

    public static create(domainContext: DomainContext, title: string, description: string | null): Todo
    {
        given(domainContext, "domainContext").ensureHasValue().ensureIsObject();
        given(title, "title").ensureHasValue().ensureIsString();
        given(description as string, "description").ensureIsString();

        return new Todo(domainContext, [new TodoCreated({
            todoId: DomainHelper.generateId("tdo"),
            title,
            description: description != null ? TodoDescription.create(description) : null
        })]);
    }

    public updateTitle(title: string): void
    {
        given(title, "title").ensureHasValue().ensureIsString();
        title = title.trim();
        this.applyEvent(new TodoTitleUpdated({ title }));
    }
}

Domain Events

Domain events represent state changes in your aggregates. They are immutable and carry the data necessary to modify the aggregate state:

import { given } from "@nivinjoseph/n-defensive";
import { serialize } from "@nivinjoseph/n-util";
import { DomainEventData } from "@nivinjoseph/n-domain";
import { TodoState } from "../todo-state";


@serialize("Test")
export class TodoCreated extends TodoDomainEvent
{
    private readonly _todoId: string;
    private readonly _title: string;

    @serialize
    public get todoId(): string { return this._todoId; }

    @serialize
    public get title(): string { return this._title; }

    @serialize
    public get description(): TodoDescription | null { return this._description; }

    public constructor(data: EventData)
    {
        given(data, "data").ensureHasValue().ensureIsObject();
        data.$isCreatedEvent = true;
        super(data);

        const { todoId, title } = data;

        given(todoId, "todoId").ensureHasValue().ensureIsString();
        this._todoId = todoId;

        given(title, "title").ensureHasValue().ensureIsString();
        this._title = title;
    }

    protected applyEvent(state: TodoState): void
    {
        given(state, "state").ensureHasValue().ensureIsObject();
        state.id = this._todoId;
        state.title = this._title;
    }
}

interface EventData extends DomainEventData
{
    todoId: string;
    title: string;
}

State Management

State management is handled through state interfaces and factories:

import { AggregateState } from "@nivinjoseph/n-domain";
import { AggregateStateFactory } from "@nivinjoseph/n-domain";
import { TodoDescription } from "./value-objects/todo-description";

export interface TodoState extends AggregateState
{
    title: string;
    description: TodoDescription | null;
    isCompleted: boolean;
}

export class TodoStateFactory extends AggregateStateFactory<TodoState>
{
    public create(): TodoState
    {
        return {
            ...this.createDefaultAggregateState(),
            title: null as any,
            description: null,
            isCompleted: false
        };
    }
}

API Reference

AggregateRoot

Base class for aggregate roots in your domain model.

Properties:

  • context: DomainContext - The domain context
  • id: string - Unique identifier for the aggregate
  • retroEvents: ReadonlyArray<DomainEvent> - Historical events
  • retroVersion: number - Version of historical events
  • currentEvents: ReadonlyArray<DomainEvent> - Current uncommitted events
  • currentVersion: number - Current version of the aggregate
  • events: ReadonlyArray<DomainEvent> - All events (historical + current)
  • version: number - Current version of the aggregate
  • createdAt: number - Creation timestamp
  • updatedAt: number - Last update timestamp
  • isNew: boolean - Whether the aggregate is newly created
  • hasChanges: boolean - Whether there are uncommitted changes
  • isReconstructed: boolean - Whether the aggregate was reconstructed
  • reconstructedFromVersion: number - Version from which the aggregate was reconstructed
  • isRebased: boolean - Whether the aggregate was rebased
  • rebasedFromVersion: number - Version from which the aggregate was rebased

Key Methods:

  • deserializeFromEvents(domainContext: DomainContext, aggregateType: new (...args: Array<any>) => TAggregate, eventData: ReadonlyArray<DomainEventData>): Static method to reconstruct an aggregate from events
  • deserializeFromSnapshot(domainContext: DomainContext, aggregateType: new (...args: Array<any>) => TAggregate, stateFactory: AggregateStateFactory<TAggregateState>, stateSnapshot: TAggregateState | object): Static method to reconstruct an aggregate from a snapshot
  • snapshot(...cloneKeys: ReadonlyArray<string>): Create a snapshot of the current state
  • constructVersion(version: number): Construct the aggregate at a specific version
  • constructBefore(dateTime: number): Construct the aggregate before a specific timestamp
  • hasEventOfType(eventType: new (...args: Array<any>) => TEventType): Check if any event of a specific type exists
  • hasRetroEventOfType(eventType: new (...args: Array<any>) => TEventType): Check if any historical event of a specific type exists
  • hasCurrentEventOfType(eventType: new (...args: Array<any>) => TEventType): Check if any current event of a specific type exists
  • getEventsOfType(eventType: new (...args: Array<any>) => TEventType): Get all events of a specific type
  • getRetroEventsOfType(eventType: new (...args: Array<any>) => TEventType): Get all historical events of a specific type
  • getCurrentEventsOfType(eventType: new (...args: Array<any>) => TEventType): Get all current events of a specific type
  • clone(domainContext: DomainContext, createdEvent: DomainEvent<T>, serializedEventMutatorAndFilter?: (event: { $name: string; }) => boolean): Create a clone of the aggregate
  • rebase(version: number, rebasedEventFactoryFunc: (defaultState: object, rebaseState: object, rebaseVersion: number) => TDomainEvent): Rebase the aggregate to a specific version
  • applyEvent(event: TDomainEvent): Apply a new event to the aggregate

DomainEvent

Base class for domain events.

Properties:

  • aggregateId: string - ID of the aggregate this event belongs to
  • id: string - Unique identifier for the event
  • userId: string - ID of the user who triggered the event
  • name: string - Name of the event type
  • partitionKey: string - Same as aggregateId (for n-eda compatibility)
  • refId: string - Same as aggregateId (for n-eda compatibility)
  • refType: string - Abstract property to be implemented (for n-eda compatibility)
  • occurredAt: number - Timestamp when the event occurred
  • version: number - Version number of the event
  • isCreatedEvent: boolean - Whether this is a creation event

Key Methods:

  • apply(aggregate: AggregateRoot<T, DomainEvent<T>>, domainContext: DomainContext, state: T): Apply the event to an aggregate
  • applyEvent(state: T): Abstract method to be implemented for applying event-specific changes to state

AggregateState

Base interface for aggregate state.

Properties:

  • id: string - Unique identifier for the aggregate
  • version: number - Current version of the aggregate
  • createdAt: number - Creation timestamp
  • updatedAt: number - Last update timestamp
  • isDeleted: boolean - Whether the aggregate is deleted
  • isRebased: boolean - Whether the aggregate was rebased
  • rebasedFromVersion: number - Version from which the aggregate was rebased

DomainContext

Interface for domain context.

Properties:

  • userId: string - ID of the current user

ConfigurableDomainContext

Class for configuring domain context.

Properties:

  • userId: string - ID of the current user

Methods:

  • configure(userId: string): Configure the domain context with a user ID

Best Practices

  1. Event Design

    • Keep events immutable
    • Include only necessary data
    • Use meaningful event names
    • Use @serialize decorator for serialization
    • Implement proper validation using given
  2. Aggregate Design

    • Keep aggregates focused and cohesive
    • Maintain consistency boundaries
    • Use event sourcing for complex state management
    • Implement proper validation in all public methods
    • Use static factory methods for creation
  3. State Management

    • Use the built-in state management helpers
    • Implement proper event handlers
    • Consider performance implications of event history
    • Use proper typing for state interfaces
  4. Domain Organization

    • Keep related files close together
    • Use clear naming conventions
    • Separate concerns into appropriate directories
    • Maintain a flat structure for better discoverability

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

For issues and feature requests, please use the GitHub issue tracker.