• Stars
    star
    199
  • Rank 196,105 (Top 4 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 4 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Typeorm polymorphic relationship management

typeorm-polymorphic

Coverage Status

An extension package for polymorphic relationship management, declaration and repository queries for typeorm

Experiemental package

Install

$ yarn add typeorm-polymorphic

You'll also require typeorm and reflect-metadata if you haven't already installed these

This is a concept I've put together for decorated polymorphic values with typeorm. I've taken a lot of inspiration from laravel's eloquent.

This has worked for my use case however it might not for others. This is an example of how I've used it.

Extend the PolymorphicRepository

import { PolymorphicRepository } from 'typeorm-polymorphic';

@PolymorphicRepository(AdvertEntity)
export class AdvertRepository extends AbstractPolymorphicRepository<
  AdvertEntity
> {}

Then, to instantiate your repository you can call:

import { AbstractPolymorphicRepository } from 'typeorm-polymorphic';

const repository = AbstractPolymorphicRepository.createRepository(
  dataSource, // where `dataSource` is a typeorm DataSource object
  AdvertRepository,
);

The below decorators will only work when using the above abstract repository AbstractPolymorphicRepository

Setup the entities

This is an example of one child, 2 parent types

Parents

@Entity('users')
export class UserEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @PolymorphicChildren(() => AdvertEntity, {
    eager: false,
  })
  adverts: AdvertEntity[];
}
Entity('merchants')
export class MerchantEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @PolymorphicChildren(() => AdvertEntity, {
    eager: false,
  })
  adverts: AdvertEntity[];
}

Children

@Entity('adverts') 
export class AdvertEntity implements PolymorphicChildInterface {
  @PolymorphicParent(() => [UserEntity, MerchantEntity])
  owner: UserEntity | MerchantEntity;

  @Column()
  entityId: number;

  @Column()
  entityType: string;
}

Resulting values

This will result in the adverts table having values

adverts table
==========================
id | entityId | entityType
==========================
 1 | 1        | 'UserEntity'
 2 | 1        | 'MerchantEntity'
 3 | 2        | 'UserEntity'

Decorators

Both PolymorphicChildren and PolymorphicParent are treated same. Currently some of the default values are different but eventually these method should be synonyms of one another. They have different names because it helped me describe the relationship directions which could be explained as 'parent' 'child' in different ways.

PolymorphicRepository allows you to define a custom typeorm repository and then instantiate it later via AbstractPolymorphicRepository.createRepository(...).

Ambiguous direction

Both PolymorphicParent and PolymorphicChildren accepts either an array of types or a singular type

@PolymorphicChildren(() => [ChildEntity, AnotherChildEntity])
@PolymorphicParent(() => [ParentEntity, AnotherParentEntity])

@PolymorphicChildren(() => ChildEntity)
@PolymorphicParent(() => ParentEntity)

Options

key what's it for? default
eager load relationships by default true
cascade save/delete parent/children on save/delete true
deleteBeforeUpdate delete relation/relations before update false
hasMany should return as array? true for child. false for parent

hasMany should really be updated so both parent and child declaration are the same. I've done to hopefully avoid confusion from the names!

Repository Methods

The majority of these methods overwrite the typeorm's Repository class methods to ensure polymorph relationships are handled before/after the parent's method.

save

Saves the given entity and it's parent or children

extends typeorm's Repository.save method

Child
const repository = connection.getRepository(AdvertRepository); // That extends AbstractPolymorphicRepository

const advert = new AdvertEntity();
advert.owner = user;

await repository.save(advert);
Parent
const repository = connection.getRepository(MerchantRepository); // That extends AbstractPolymorphicRepository

const advert = new AdvertEntity();

const merchant = new MerchantEntity();
merchant.adverts = [advert];

await repository.save(merchant);

find

extends typeorm's Repository.find method

const repository = connection.getRepository(MerchantRepository); // That extends AbstractPolymorphicRepository

const results = await repository.find();

// results[0].adverts === AdvertEntity[]

findOne

extends typeorm's Repository.findOne method

create

This method creates the parent or child relations for you so you don't have to manally supply an array of classes.

extends typeorm's Repository.create method

Child
const repository = connection.getRepository(AdvertRepository); // That extends AbstractPolymorphicRepository

const results = await repository.create({
  owner: new UserEntity(), // or MerchantEntity()
});
Parent
const repository = connection.getRepository(UserRepository); // That extends AbstractPolymorphicRepository

const results = await repository.create({
  adverts: [
    {
      name: 'test',
    },
    {
      name: 'test',
    },
  ],
});

/**
 * {
 *   adverts: [
 *     AdvertEntity{
 *       name: 'test',
 *     },
 *     AdvertEntity{
 *       name: 'test',
 *     },
 *   ],
 * }
*/

hydrateMany

Hydreate one entity and get their relations to parent/child

const repository = connection.getRepository(AdvertRepository); // That extends AbstractPolymorphicRepository

const adverts = await repository.find();
// eager to parent (user|merchant) is set to false
adverts[0].owner; // undefined

await repository.hydrateMany(adverts);

adverts[0].owner; // UserEntity | MerchantEntity

hydrateOne

Hydreate one entity and get their relations to parent/child

const repository = connection.getRepository(AdvertRepository); // That extends AbstractPolymorphicRepository

const advert = await repository.findOne(1);
// eager to parent (user|merchant) is set to false
advert.owner; // undefined

await repository.hydrateOne(advert);

advert.owner; // UserEntity | MerchantEntity

Class-transformer

We recommend if you're working with polymorphic relationships that you use class-transformers's Transform decorator to distinguish the different types on the frontend when returning your entities from a http call

@Entity('adverts') 
export class AdvertEntity implements PolymorphicChildInterface {
  @PolymorphicParent(() => [UserEntity, MerchantEntity])
  @Transform(
    (value: UserEntity | MerchantEntity) => ({
      ...value,
      type: value.constructor.name,
    }),
    {
      toPlainOnly: true,
    },
  )
  owner: UserEntity | MerchantEntity;

  @Column()
  entityId: number;

  @Column()
  entityType: string;
}

The owner property object's type property will now either be string value of UserEntity or MerchantEntity

Possible relations

Singular parent, different children

This is an example of having the need of different types of children for a singular parent type

class RestaurantEntity {
  @PolymorphicChildren(() => [WaiterEntity, ChefEntity])
  staff: (WaiterEntity | ChefEntity)[];
}

class WaiterEntity implements PolymorphicChildInterface {
  @Column()
  entityId: string;

  @Column()
  entityType: string;

  @PolymorphicParent(() => RestaurantEntity)
  restaurant: RestaurantEntity;
}

class ChefEntity implements PolymorphicChildInterface {
  @Column()
  entityId: string;

  @Column()
  entityType: string;

  @PolymorphicParent(() => RestaurantEntity)
  restaurant: RestaurantEntity;
}

Singular child, different parent

This is an example of having the need of a singular child shared between different types of parents

class AdvertEntity implements PolymorphicChildInterface {
  @PolymorphicParent(() => [UserEntity, MerchantEntity])
  owner: UserEntity | MerchantEntity;
}

class MerchantEntity {
  @PolymorphicChildren(() => AdvertEntity)
  adverts: AdvertEntity[];
}

class UserEntity {
  @PolymorphicChildren(() => AdvertEntity)
  adverts: AdvertEntity[];
}

Notes

I think Perf might have some suggestions on how to improve things (sorry I have replied been mega busy!)

Nestjs

If you're using nestjs, don't forgot to include your repository into the entities array in forFeature

@Module({
  imports: [
    TypeOrmModule.forFeature([
      AdvertEntity,
      AdvertRepository,
    ]),
  ],
  providers: [AdvertService, CategoryService, TagService, AdvertPolicy],
  exports: [TypeOrmModule, AdvertService],
})
export class AdvertModule {}

More Repositories

1

nestjs-blog

Blog example made with nestJS
TypeScript
293
star
2

another-portfolio

My portfolio at https://ashleighsimonelli.co.uk
TypeScript
39
star
3

shleemy

πŸ•™ Human readable datetime values for JavaScript and TypeScript.
TypeScript
30
star
4

nestjs-config-v2-prototype

An example/prototype of v2 for nestjs-config
TypeScript
7
star
5

GPIO

GPIO pin environment manager
PHP
5
star
6

wave-func

A strictly typed order of all the things that make up our reality as we know it. βš›οΈ Ξ¨
TypeScript
5
star
7

css-cheat-sheet

5
star
8

stackerbot

Stackoverflow search github bot
TypeScript
4
star
9

areyouinfordinner-api

API for Are You In For Dinner 🍴
TypeScript
4
star
10

lineup

A football line up builder
TypeScript
3
star
11

teaching

Teaching session for Southend high school for boys
3
star
12

robot-car

A python project to build a pet car robot
Python
3
star
13

kli

TypeScript
3
star
14

reapit-react-test

TypeScript
2
star
15

shleemy-react

Date interval management for react
TypeScript
2
star
16

chipz

2
star
17

everblue-ui

A user-friendly, open source UI library built using styled-components and React.
TypeScript
2
star
18

beth-lessons

2
star
19

communication-experiment

An experiment of different methods of communication using nestjs
TypeScript
2
star
20

areyouinfordinner-app

App for communicating with the are you in for dinner API 🍴
TypeScript
2
star
21

wave

TypeScript
2
star
22

node-copter-controller

Controller for node copter
JavaScript
2
star
23

ludicrous

TypeScript
2
star
24

dotfiles

My dotfiles to keep my environments between machines similar
Shell
1
star
25

twig-test

Quick test for chunked array forming
TypeScript
1
star
26

LaraValidator

Laravel validation service provider to allow creation of validation classes
PHP
1
star
27

bashleigh

1
star
28

docker-inspector

Rust
1
star
29

typeormpaginate

TypeScript
1
star
30

smart-pi-dashboard-server

An open source smart dashboard project using a raspberry pi
TypeScript
1
star
31

electron-game-experiment

JavaScript
1
star
32

electron-md-editor

Edit your markdown files in a pretty electron application tutorial for Auth0
JavaScript
1
star