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

@snap-alex/domain-js

v11.0.2

Published

Generics classes for managing entities and collections by async requests

Readme

Domain-js

Domain-js is a library contains generic classes that helps make CRUD operations easy with Domain-Driven Design principles.

  • Typescript support
  • Rest API approach
  • No dependencies

Installation

Domain-js requires Node.js v8.12.0+ to run.

Install the library with npm.

$ npm install --save @snap-alex/domain-js

Or yarn.

$ yarn add @snap-alex/domain-js

Basic Usage

Create a core resource based on url. Example with cross-fetch library;

import { FetchResource } from "@snap-alex/domain-js";
import fetch from "cross-fetch";

const httpResource = new FetchResource('https://www.books.com/api/v0', fetch);
export default httpResource;

Create Entity interface

The collection interface we want to manage

export interface Book {
  id?: number
  title: string
  created_at: string
  updated_at: string
}

Create entity rest resource

Use BaseRestResource class for create Entity resource endpoint. Based on previosly created httpResource instance.

import { BaseRestResource } from "@snap-alex/domain-js";
import httpResource from './core/infrastructure/httpResource';

const bookResource = new BaseRestResource(httpResource, 'books');
export default bookResource;

Already you can use your rest resource for requests

import { BaseRestResource } from "@snap-alex/domain-js";
import bookResource './books/bookResource';

// CREATE on https://www.books.com/api/v0/books
bookResource.create({ title: 'Tom' }) as Promise<any>;

// PUT on https://www.books.com/api/v0/books
bookResource.update({ title: 'Tom' }) as Promise<any>;

// PATCH on https://www.books.com/api/v0/books
bookResource.patch({ title: 'Tom' }) as Promise<any>;

// GET on https://www.books.com/api/v0/books?title=Tom
bookResource.get({ title: 'Tom' }) as Promise<any>;

// DELETE on https://www.books.com/api/v0/books
bookResource.delete({ title: 'Tom' }) as Promise<any>;

// Create child resource with uri - https://www.books.com/api/v0/books/additional
const newChildResource1: BaseRestResource = bookResource.child('additional');

// Create child resource with uri strings - https://www.books.com/api/v0/books/additional/path
const newChildResource2: BaseRestResource = bookResource.child('additional', 'path');

Let's start manage our collection through Repository!

But we have a model User and now we have to create Repository

import { BaseRepository } from "@snap-alex/domain-js";
import bookResource from './books/bookResource';
import { Book } from './books/Book';

const bookRepository = new BaseRepository<Book>(bookResource)

// Check Entity is new (by id existence)
userRepository.isEntityNew({ id: 1, title: 'Tom' }) as boolean;

// Create Book 
// POST on https://www.books.com/api/v0/books 
userRepository.create({ title: 'Tom' }) as Promise<Book>;

// Update Book 
// PUT on https://www.books.com/api/v0/books/1 
userRepository.update({ id: 1, title: 'Tom' }) as Promise<Book>;

// Patch Book 
// PATCH on https://www.books.com/api/v0/books/1 
userRepository.patch({ id: 1, title: 'Tom' }) as Promise<Book>;

// Load Books
// GET on https://www.books.com/api/v0/books
cosnt books = await userRepository.load() as Promise<ArrayMeta<Book>>;
books.forEach((book) => console.log(book.title))
books.meta // containts meta server information

// Load Book by ID
// GET on https://www.books.com/api/v0/books/1
userRepository.loadById(1) as Promise<Book>;

// Delete Book
// DELETE on https://www.books.com/api/v0/books/1
userRepository.delete({ id: 1, title: 'Tom' }) as Promise<void>;

// Search Books
// todo describe search pagination params in Readme

We can setup another Entity Id

import { BaseRepository } from "@snap-alex/domain-js";
import bookResource from './books/bookResource';
import { Book } from './books/Book';

class BookRepository extends BaseRepository {
  entityIdName = 'uuid';
}

const bookRepository = new BookRepository<Book>(bookResource)

// Update Book 
// PUT on https://www.books.com/api/v0/books/101js1mx12jkej
userRepository.update({ uuid: '101js1mx12jkej', title: 'Tom' }) as Promise<Book>;

Build repository for child entities dynamically (RepositoryBuilder)

For example we have users with subscriptions Subscriptions are located at ../api/users/:id/subscriptions

import { 
  BaseRepository, 
  BaseRepositoryBuilder, 
  FetchResource, 
  BaseRestResource, 
  BaseEntity 
} from "@snap-alex/domain-js";

// Create base resource
const httpResource = new FetchResource('https://www.example.com/api/v0');

// Create resource for users
const userResource = new BaseRestResource(httpResource, 'users');

// Describe Subscription entity interface
interface Subscription extends BaseEntity {
  id: number
  expirationDate: string
}

// Create Subscription repository class
class SubscriptionRepository extends BaseRepository<Subscription> {
}

// Create our repository builder with additional method
class SubscriptionRepositoryBuilder extends BaseRepositoryBuilder<Subscription> {
  public buildWithUserId(userId: number): SubscriptionRepository {
    const resource = userResource.child(userId, 'subscriptions');
    return this.build(resource) as SubscriptionRepository;
  }
}

// Create builder instance with
const subscriptionRepositoryBuilder = new SubscriptionRepositoryBuilder(SubscriptionRepository);

// Create repository dynamically
const subscriptionRepository = subscriptionRepositoryBuilder.buildWithUserId(2);

// Load Subscriptions 
// GET on https://www.example.com/api/v0/users/2/subscriptions
subscriptionRepository.load();

Advanced

Using DataMappers

Date mappers are used to encode response from the server to format we need and reverse the conversion before sending Entity to server.

Mapping strategy

Let's define strategy for encode / decode our data

import { BaseMapType } from "@snap-alex/domain-js";

const mappingStrategy = {
  id: BaseMapType.number,
  nickname: BaseMapType.string,
  isOnline: BaseMapType.bool.asAttrMap('is_online'),
  createdAt: BaseMapType.dateTime.asAttrMap('created_at'),
  states: BaseMapType.arrayOf(BaseMapType.string),
  avatar: BaseMapType.shapeOf({
    id: BaseMapType.number,
    url: BaseMapType.string
  }),
  city: BaseMapType.decodeEntityKey()({
    id: BaseMapType.number,
    title: BaseMapType.string
  }),
  ticket: BaseMapType.decodeEntityKey('uuid')({
    uuid: BaseMapType.string,
    cinema: BaseMapType.string
  }),
  roleId: BaseMapType.encodeEntityKey()({
    id: BaseMapType.number,
    title: BaseMapType.string,
  }).asAttrMap('role'),
  customMap: {
    map: 'custom_map',
    encode: (value: any) => value && value.custom_map,
    decode: (value: any) => {
      return value && `${value.customMap.id}.${value.customMap.title}`;
    }
  }
}

We can define encoded / decoded interfaces (optional)

interface Encoded {
  id: number
  nickname: string
  isOnline: boolean
  createdAt: string
  states: string[]
  avatar: {
    id: number
    url: string
  }
  city: {
    id: number
    title: string
  }
  ticket: {
    uuid: string
    cinema: string
  }
  roleId: number
  customMap: {
    id: number
    title: string
  }
}

interface Decoded {
  id: number | string
  nickname: string
  is_online: boolean
  created_at: string
  states: string[]
  avatar: {
    id: number | string
    url: string
  }
  city: {
    id: number | string
    title: string
  }
  ticket: {
    uuid: string
    cinema: string
  }
  role: {
    id: number | string
    title: string
  }
  custom_map: {
    id: number | string
    title: string
  }
}

Create our DataMapper

const testDataMapper = new BaseDataMapper<Encoded, Decoded>(mappingStrategy)

Use it for decode / encode data

const encodedTest = testDataMapper.encode({
  id: '1',
  nickname: 'Bob',
  is_online: false,
  created_at: '2018-02-08',
  states: ['new'],
  avatar: {
    id: 1,
    url: 'url'
  }
})

// return an object
{
  id: 1
  nickname: 'Bob',
  isOnline: false,
  createdAt: '2018-02-08'
  states: ['new']
  avatar: {
    id: 1,
    url: 'url'
  }
}

Use DataMapper with Repository!

bookRepository will automatically use bookDataMapper for encode and decode data before receive / send data.

import { BaseRepository } from "@snap-alex/domain-js";
import bookResource from './books/bookResource';
import bookDataMapper from './boooks/bookDataMapper';
import { Book } from './books/Book';

const bookRepository = new BaseRepository<Book>(bookResource, bookDataMapper)

Todos

  • Implement GraphQL resource

License

MIT