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 🙏

© 2024 – Pkg Stats / Ryan Hefner

domain-repository

v2.0.12

Published

IDomainRepository is an extension of ORM frameworks (Mongoose or TypeORM) that **automatically maps DB models into Domain models**.

Downloads

536

Readme

Domain Repository

IDomainRepository is an extension of ORM frameworks (Mongoose or TypeORM) that automatically maps DB models into Domain models.

Useful links:

Installation with Mongoose

#1. First uninstall Mongoose and MongoDb.

npm uninstall mongoose mongodb @types/mongoose @types/mongodb

#2. Install latest Mongoose.

npm install mongoose

#3. Only AFTER you have latest Mongoose installed, install domain-repository.

npm install domain-repository

If you do it the other way round, you can have problems with MongoDB BSON dependency!

Installation with TypeORM

#1. First uninstall TypeORM.

npm uninstall typeorm @types/typeorm

#2. Install latest TypeORM.

npm install typeorm

#3. Install domain-repository.

npm install domain-repository

How to use it

1. Define your domain models

Make sure you have domain models defined. Each model should be exported in two versions:

  • Detached (default model without id), for objects not yet persisted in the database
  • Attached (with id), for already persisted objects

This differentiation improves intellisense and debugging. You can call your models whatever you like, as long as you stick to your naming convention. We recommend the following good practices:

  • use IType naming convention for pure model types, to distinguish them from classes
  • add Attached suffix to all of your attached models
  • provide JSDoc comments to your model properties (so that your developers can better understand the domain)
  • add mutable / readonly prefix to your property descriptions
  • if property is optional, explain why it is optional (due to business, technological or temporary reasons)

For example:

export type ICar = {
  /* Mutable name of the car. */
  name: string;

  /* Mutable flag. Equals true for the single, highest ranked car in the system. */
  best: boolean;

  /* Readonly year of production. */
  readonly yearOfProduction: number;

  /* Mutable sale date. Optional means the car was not sold yet. */
  sold?: Date;
};

export type ICarAttached = ICar & { id: string };

An attached model will contain:


2. Define your DB models (in Mongoose or TypeORM).

Let's say you want to use MongoDB as your DB, and Mongoose as your ORM.

Create a new file for my DB model, for example: car.entity.ts:

Because this library uses domain mapping, this model does not have to be the same as your domain model.

export type ICarMongoEntity = {
  _id: mongoose.Types.ObjectId;
  name: string;
  best_of_all: boolean;
  readonly yearOfProduction: number;
  sold?: Date;
};

Now create file car.schema.ts and define your db schema, using mongoose:

//standard MongoDb schema, typed with your db model
export const CarSchema = new Schema<ICarMongoEntity>({
  name: {
    type: String,
    required: true
  },
  best_of_all: {
    type: Boolean,
    required: true
  },
  yearOfProduction: {
    type: Number,
    required: true
  },
  sold: {
    type: Date,
    required: false
  }
});

3. Define mapping between Domain and DB models

Check strict-type-mapper npm package for more details.

import { Mapping } from 'strict-type-mapper';
import { mapToMongoObjectId } from 'domain-repository/db/mongodb';

//define mapping from domain model to db model
export const mongoCarMapping: Mapping<ICarAttached, ICarMongoEntity> = {
  id: mapToMongoObjectId,
  name: 'name',
  best: 'best_of_all',
  yearOfProduction: 'yearOfProduction',
  sold: 'sold'
};

4. Use IDomainRepository in your business services

Use IDomainRepository interface in places, where you would previously use Mongoose collection or TypeORM repository. Type it explicitly with your Domain model type.

import { IDomainRepository } from 'domain-repository';

export class CarService {
  constructor(private readonly carRepository: IDomainRepository<ICar, ICarAttached>) {}

  public async create(car: ICar): Promise<ICarAttached> {
    return this.carRepository.create(car);
  }

  public async findBestCar(): Promise<ICarAttached | undefined> {
    return this.carRepository.findOne({ best: true });
  }
}

If you only need to read or write data you can also use narrowed versions of interfaces: IReadDomainRepository or IWriteDomainRepository (SOLID's Interface segregation principle).


5. Write unit tests (Test-driven-development)

Here lies the greatest benefit of using IDomainRepository. You can easily test your services using MockedDbRepository implementation.

No more difficult mocking of db methods!

This way you can focus on your business code and test only that (this is one of the principal guidelines of unit testing).

import { MockedDBRepository } from 'domain-repository';

describe('CarService', () => {
  const initialData: ICarAttached[] = [
    { id: '1', name: 'Volvo', best: false, yearOfProduction: 2000 },
    {
      id: '2',
      name: 'Toyota',
      best: true,
      yearOfProduction: 2010,
      sold: new Date()
    }
  ];

  const mockedRepository = new MockedDBRepository<ICar, ICarAttached>(initialData);
  const carService = new CarService(mockedRepository);

  it('should find best car', async () => {
    const car = await carService.findBestCar();

    expect(car).toBeDefined();
    expect(car!.name).toEqual('Toyota');
  });
});

6. Supply your services with proper repository implemenation for your target DB.

Now depending on your db and ORM layer, you need to create ORM repository and pass it to our implementation of IDomainRepository.

MongoDb example:

import { MongoDbRepository } from 'domain-repository/db/mongodb';

const runMongoTest = async (): Promise<void> => {
  await new Promise<void>((resolve) => {
    mongoose.connect('mongodb://127.0.0.1:27017/testdb', {});
    mongoose.connection.on('open', () => resolve());
  });

  const carRepository = new MongoDbRepository<ICar, ICarAttached, ICarMongoEntity>(
    mongoose.model<ICarMongoEntity>('cars', CarSchema),
    mongoCarMapping
  );

  const carService = new CarService(carRepository);

  await carService.create({
    name: 'Toyota',
    best: true,
    yearOfProduction: 2010,
    sold: new Date()
  });

  const bestCar = await carService.findBestCar();
  console.log(bestCar);
};

runMongoTest();

Output:

{
  id: '63b8091cdd1f0c4927ca4725',
  name: 'Toyota',
  best: true,
  yearOfProduction: 2010,
  sold: 2023-01-06T11:42:20.836Z
}

MongoDB data (see best_of_all renamed property):

{
  "_id": {
    "$oid": "63b8091cdd1f0c4927ca4725"
  },
  "name": "Toyota",
  "best_of_all": true,
  "yearOfProduction": 2010,
  "sold": {
    "$date": "2023-01-06T11:42:20.836Z"
  },
  "__v": 0
}

PostgreSQL example

Db model:

export type ICarSqlEntity = {
  id: number;
  name: string;
  best_of_all: boolean;
  readonly yearOfProduction: number;
  sold?: Date;
};

Db schema and mapping:

import { Mapping } from 'strict-type-mapper';
import { mapToSqlIntId } from 'domain-repository/db/postgresql';

//you can put ! next to the properties, to prevent Typescript no-initializer warnings
@Entity('cars')
export class SqlCarEntity implements ICarSqlEntity {
  @PrimaryGeneratedColumn()
  readonly id!: number;

  @Column('text')
  name!: string;

  @Column('bool')
  best_of_all!: boolean;

  @Column('int')
  readonly yearOfProduction!: number;

  @Column('text', { nullable: true })
  sold?: Date;
}

export const sqlCarMapping: Mapping<ICarAttached, ICarSqlEntity> = {
  id: mapToSqlIntId,
  name: 'name',
  best: 'best_of_all',
  yearOfProduction: 'yearOfProduction',
  sold: 'sold'
};

Test code:

import { PostgreSQLDbRepository } from 'domain-repository/db/postgresql';

const runPostgresTest = async (): Promise<void> => {
  const dataSource = new DataSource({
    type: 'postgres',
    host: '127.0.0.1',
    port: 5432,
    database: 'mydb',
    username: 'postgres',
    password: 'admin',
    synchronize: true, //for local testing
    entities: [SqlCarEntity]
  });

  await dataSource.initialize();

  const carRepository = new PostgreSQLDbRepository<ICar, ICarAttached, ICarSqlEntity>(
    dataSource.getRepository(SqlCarEntity),
    sqlCarMapping
  );

  const carService = new CarService(carRepository);

  await carService.create({
    name: 'Toyota',
    best: true,
    yearOfProduction: 2010,
    sold: new Date()
  });

  const bestCar = await carService.findBestCar();
  console.log(bestCar);
};

runPostgresTest();

Output:

{
  id: '146',
  name: 'Toyota',
  best: true,
  yearOfProduction: 2010,
  sold: '2023-01-06T13:11:43.685+01:00'
}

PostgreSQL data (see best_of_all renamed property):

id,"name","best_of_all","yearOfProduction","sold"
146,"Toyota",True,2010,"2023-01-06T13:11:43.685+01:00"