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

@zyrohub/domain

v0.0.8

Published

A robust TypeScript library for building Domain Entities and Repositories with built-in dirty checking, reactive state management, and strict type safety.

Downloads

28

Readme

ZyroHub - Domain

This package contains the domain logic of the applications. It defines the essential entities, and repositories that form the foundation of an application.

Table of Contents

Getting Started

To install the domain package, use one of the following package managers:

NPM Repository

# npm
npm install @zyrohub/domain
# yarn
yarn add @zyrohub/domain
# pnpm
pnpm add @zyrohub/domain
# bun
bun add @zyrohub/domain

Entities

Creating a Basic Entity

import { Entity } from '@zyrohub/domain';

export interface UserEntityRaw {
	id?: string; // optional id, for not existing users
	name: string;
	email: string;
	password: string;
}

export class UserEntity extends Entity<UserEntityRaw> {
	constructor(raw: UserEntityRaw) {
		super({
			data: raw
		});
	}

	// Filter out sensitive data or modify the output as needed (see section "#Customizing Entity Output with filterObject")
	filterObject(data: any, options: any): any {
		delete data.password; // Exclude password from the output

		return data;
	}
}

const user = new UserEntity({
	id: '1',
	name: 'John Doe',
	email: '[email protected]',
	password: 'securepassword'
});

// raw data: { id: '1', name: 'John Doe', email: '[email protected]', password: 'securepassword' }
// unwrap() returns the clean raw data (POJO) useful for database persistence, etc.
console.log(user.unwrap());

// processed data without password (to expose in public or other scenarios): { id: '1', name: 'John Doe', email: '[email protected]' }
console.log(user.toObject());

Custom ID Generation

import { Entity } from '@zyrohub/domain';
import { randomUUID } from 'crypto';

export interface UserEntityRaw {
	id?: string; // optional id, for not existing users
	name: string;
	email: string;
	password: string;
}

export class UserEntity extends Entity<UserEntityRaw> {
	constructor(raw: UserEntityRaw) {
		super({
			idGenerator: () => randomUUID(), // Custom ID generator for non-existing users
			data: raw
		});
	}
}

Entity Relations

You can define relations between entities by creating interfaces for the related entities and including them in the main entity's raw data and relations.

import { Entity } from '@zyrohub/domain';

import { UserEntity, UserEntityRaw } from './UserEntity.js';

// Post entity without relations
export interface PostEntityData {
	id: string;
	title: string;
	content: string;
	authorId: string;
}

// Post entity with relations merged
export interface PostEntityRaw extends PostEntityData {
	author?: UserEntityRaw;
}

// Post entity relations interface
export interface PostEntityRelations {
	author?: UserEntity;
}

export class PostEntity extends Entity<PostEntityRaw, PostEntityRelations> {
	constructor(raw: PostEntityRaw) {
		// Destructure to separate relations from raw data
		const { author, ...data } = raw;

		super({
			data: data,
			// Map relations to their respective entity instances
			relations: {
				author: author ? new UserEntity(author) : undefined
			}
		});
	}
}

const post = new PostEntity({
	id: '101',
	title: 'My First Post',
	content: 'Content',
	authorId: '1',
	author: {
		id: '1',
		name: 'John Doe',
		email: '[email protected]'
	}
});

console.log(post.toObject());
// Output: {
//  id: '101',
//  title: 'My First Post',
//  content: 'Content',
//  authorId: '1',
//  relations: {
//    author: { id: '1', name: 'John Doe', email: '[email protected]' }
//  }
// }

Customizing Entity Output with filterObject

You can customize the output of an entity by overriding the filterObject method. This allows you to modify the data before it is returned by the toObject method.

import { Entity, EntityToObjectOptions } from '@zyrohub/domain';

export interface UserEntityRaw {
	id: string;
	name: string;
	email: string;
	password: string;
	age: number;
}

export interface UserEntityToObjectOptions extends EntityToObjectOptions {
	// view: string; is automatically included in EntityToObjectOptions interface (you can use "admin", "public", etc. if needed)

	// Add any additional options specific to UserEntity here
	includeAge?: boolean;
}

export class UserEntity extends Entity<UserEntityRaw> {
	constructor(raw: UserEntityRaw) {
		super({
			data: raw
		});
	}

	filterObject(data: any, options: UserEntityToObjectOptions): any {
		// Exclude password from the output
		delete data.password;

		// Conditionally include age based on options
		if (!options.includeAge) {
			delete data.age;
		}

		// Using different views (if needed)
		if (options.view !== 'admin') {
			delete data.email; // Exclude email in public view
		}

		return data;
	}
}

// Example usage
const user = new UserEntity({
	id: '1',
	name: 'John Doe',
	email: '[email protected]',
	password: 'securepassword',
	age: 30
});

// Default view without age
console.log(user.toObject({ view: 'default' })); // { id: '1', name: 'John Doe' }
// Default view with age
console.log(user.toObject({ view: 'default', includeAge: true })); // { id: '1', name: 'John Doe', age: 30 }
// Admin view with age
console.log(user.toObject({ view: 'admin', includeAge: true })); // { id: '1', name: 'John Doe', email: '[email protected]', age: 30 }
// Admin view without age
console.log(user.toObject({ view: 'admin' })); // { id: '1', name: 'John Doe', email: '[email protected]' }

Collecting list of changes

The Entity class tracks changes made to its data. You can retrieve the list of changes using the getChanges method and reset the changes tracker with the commit method.

import { UserEntity } from './UserEntity.js';

const user = new UserEntity({
	id: '1',
	name: 'John Doe',
	email: '[email protected]',
	password: 'securepassword',
	age: 30
});

user.data.name = 'Jane Doe';
user.data.age = 31;

console.log(user.getChanges()); // { name: 'Jane Doe', age: 31 }

// do something with the changes...

user.commit(); // resets the changes tracker

Group of Entities (EntityGroup)

You can manage a collection of entities using the EntityGroup class. This class provides methods to manipulate and access multiple entities as a group.

⚠️ Note: The EntityGroup class is a extension of the native JavaScript Array class, so you can use all standard array methods on it. (e.g., map, filter, forEach, etc.)

import { EntityGroup } from '@zyrohub/domain';

import { UserEntity } from './UserEntity.js';

// Creating an EntityGroup from a list of UserEntity instances
const usersGroupFromEntities = EntityGroup.fromList([
	new UserEntity({
		id: '1',
		name: 'John Doe',
		email: '[email protected]',
		password: 'securepassword',
		age: 30
	}),
	new UserEntity({
		id: '2',
		name: 'Jane Smith',
		email: '[email protected]',
		password: 'anothersecurepassword',
		age: 25
	})
]);

// Creating a new EntityGroup directly from raw data (database data)
// Entity automatically add the fromList static method to create groups from raw data
const usersGroupFromRaw = UserEntity.fromList([
	{
		id: '1',
		name: 'John Doe',
		email: '[email protected]',
		password: 'securepassword',
		age: 30
	},
	{
		id: '2',
		name: 'Jane Smith',
		email: '[email protected]',
		password: 'anothersecurepassword',
		age: 25
	}
]);

Getting an Entity by ID

You can retrieve an entity from the group by its ID using the getById method.

const user = usersGroup.getById('1');

console.log(user); // UserEntity instance for John Doe

Removing an Entity by ID

You can remove an entity from the group by its ID using the removeById method.

usersGroup.removeById('2');

console.log(usersGroup.length); // 1 (only John Doe remains)

Repositories

Defining the Repository Interface

First, define the repository interface for your entity by extending the generic Repository interface from the domain package.

import { Repository } from '@zyrohub/domain';

import { UserEntity } from './UserEntity.js';

export interface IUserRepository extends Repository<UserEntity> {
	// "Repository" interface already includes as required the methods:
	// create(entity: TEntity): Promise<void>;
	// update(entity: TEntity): Promise<void>;
	// delete(id: EntityId): Promise<boolean>;

	// save(entity: TEntity): Promise<void>; is already included in BaseRepository class

	// Define custom methods for UserRepository here
	findByEmail(email: string): Promise<UserEntity | null>;
}

Creating a Repository

Then, implement the repository by extending the BaseRepository class and implementing your repository interface.

import { BaseRepository } from '@zyrohub/domain';

import { IUserRepository } from './IUserRepository.js';
import { UserEntity } from './UserEntity.js';

// An example implementation of UserRepository using an in-memory array
export class InMemoryUserRepository extends BaseRepository<UserEntity> implements IUserRepository {
	// In-memory user storage (for demonstration purposes)
	private users: UserEntity[] = [];

	// Required method from Repository interface
	async create(entity: UserEntity): Promise<void> {
		this.users.push(entity.unwrap()); // store raw data of the entity
		entity.commit(); // commit changes after creation
		entity.exists = true; // mark entity as existing
	}

	// Required method from Repository interface
	async update(entity: UserEntity): Promise<void> {
		const index = this.users.findIndex(user => user.id === entity.id);

		if (index !== -1) {
			this.users[index] = {
				...this.users[index],

				...entity.getChanges() // apply only changes
			};

			entity.commit(); // commit changes after update
		} else {
			throw new Error('User not found');
		}
	}

	// Required method from Repository interface
	async delete(id: string): Promise<boolean> {
		this.users.splice(
			this.users.findIndex(user => user.id === id),
			1
		);

		return true;
	}

	// Your custom methods from UserRepository interface
	async findByEmail(email: string): Promise<UserEntity | null> {
		const user = this.users.find(user => user.data.email === email);

		return user || null;
	}
}

In Memory Repository

The InMemoryRepository class provides a simple in-memory data management solution for entities. It extends the BaseRepository class and implements basic CRUD operations using an internal array to store entities.

import { InMemoryRepository } from '@zyrohub/domain';

import { IUserRepository } from './IUserRepository.js';
import { UserEntity } from './UserEntity.js';

export class InMemoryUserRepository extends InMemoryRepository<UserEntity> implements IUserRepository {
	// create, update, delete and save methods are already implemented in InMemoryRepository automatically
	// also add some useful methods like "clear"(to use in tests), etc.

	// Your custom methods from UserRepository interface
	async findByEmail(email: string): Promise<UserEntity | null> {
		const user = this.items.find(user => user.data.email === email);

		return user || null;
	}
}

Repository Example Usage

Save

The BaseRepository class provides a save method that automatically detects whether to create a new entity or update an existing one based on the entity's exists property.

import { InMemoryUserRepository } from './InMemoryUserRepository.js';

const userRepository = new InMemoryUserRepository();

const user = new UserEntity({
	id: '1',
	name: 'John Doe',
	email: '[email protected]',
	password: 'securepassword',
	age: 30
});

await userRepository.save(user); // auto-detects create or update using "entity.exists" property

Create

You can create a new entity using the create method provided by the BaseRepository class. (if you want to explicitly create without save auto-detection)

import { InMemoryUserRepository } from './InMemoryUserRepository.js';

const userRepository = new InMemoryUserRepository();

const user = new UserEntity({
	name: 'John Doe',
	email: '[email protected]',
	password: 'securepassword',
	age: 30
});

const createdUser = await userRepository.create(user); // returns the created UserEntity instance if you want to use it

Update

You can update an existing entity using the update method provided by the BaseRepository class. (if you want to explicitly update without save auto-detection)

import { InMemoryUserRepository } from './InMemoryUserRepository.js';

const userRepository = new InMemoryUserRepository();

const user = await userRepository.findByEmail('[email protected]');

if (user) {
	user.data.name = 'Jane Doe';

	await userRepository.update(user); // returns the updated UserEntity instance if you want to use it
}

Delete

You can delete an entity using the delete method provided by the BaseRepository class.

import { InMemoryUserRepository } from './InMemoryUserRepository.js';

const userRepository = new InMemoryUserRepository();

const success = await userRepository.delete('1'); // returns true if deletion was successful