Giter Site home page Giter Site logo

typeorm-polymorphic's Introduction

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 {}

typeorm-polymorphic's People

Contributors

bashleigh avatar dependabot[bot] avatar jspizziri avatar noukaza avatar rpvsilva avatar rubenmaier avatar solanamonk avatar thiagocardoso1988 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

typeorm-polymorphic's Issues

AbstractPolymorphicRepository.findPolymorphs uses wrong entityType

Getting this error following the Readme:

(node:117404) UnhandledPromiseRejectionWarning: TypeError: val.slice is not a function
    at escapeString (D:\dev\mmdb\node_modules\sqlstring\lib\SqlString.js:202:23)
    at Object.escape (D:\dev\mmdb\node_modules\sqlstring\lib\SqlString.js:56:21)
    at Object.format (D:\dev\mmdb\node_modules\sqlstring\lib\SqlString.js:100:19)
    at PoolConnection.Connection.format (D:\dev\mmdb\node_modules\mysql\lib\Connection.js:271:20)
    at PoolConnection.query (D:\dev\mmdb\node_modules\mysql\lib\Connection.js:189:22)
    at MysqlQueryRunner.<anonymous> (D:\dev\mmdb\src\driver\mysql\MysqlQueryRunner.ts:182:36)
    at step (D:\dev\mmdb\node_modules\tslib\tslib.js:141:27)
    at Object.next (D:\dev\mmdb\node_modules\tslib\tslib.js:122:57)
    at fulfilled (D:\dev\mmdb\node_modules\tslib\tslib.js:112:62)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

Tried debugging,

[entityTypeColumn(options)]: entityType,

Shouldn't this be parent.constructor.name? According to the query log, entityType always ends up as null in the query, as the entity instance isn't resolved to its name, and its the wrong entity anyway, as it references the parent's name in the child's entityType column, not itself.

How to get children of parent

Hi! Something similar to #1 is happening to me, the findOne method is not returning what I know for a fact is in the database!

This is one of the parents:

@Entity()
@Exclude()
@ObjectType()
@Directive('@key(fields: "id")')
export class Institution {
  @PrimaryGeneratedColumn('uuid')
  @Expose()
  @Field(() => ID)
  id: string

  @PolymorphicChildren(() => PlatformConfig, {
    eager: true,
  })
  configurations: PlatformConfig[]
}

And this is the child:

@Entity()
@Index(['entityId', 'entityType', 'key'], { unique: true })
@ObjectType()
export class PlatformConfig implements PolymorphicChildInterface {
  @PrimaryGeneratedColumn('uuid')
  @Expose()
  @Field(() => ID)
  id: string

  @Column()
  @Expose()
  @Field(() => ID)
  entityId: string

  @Column()
  @Expose()
  @Field()
  entityType: string

  @Column()
  @Expose()
  @Field()
  key: string

  @Column({ nullable: true })
  @Expose()
  @Field({ nullable: true })
  value: string

  @PolymorphicParent(() => [Institution, Jurisdiction, Tenant])
  @Transform(
    (value: Institution | Jurisdiction | Tenant) => ({
      ...value,
      type: value.constructor.name,
    }),
    {
      toPlainOnly: true,
    }
  )
  context: Institution | Jurisdiction | Tenant
}

For both of them (+ the other 2 parents) I've extended the PolymorphicRepository like so

import { EntityRepository } from 'typeorm'
import { AbstractPolymorphicRepository } from 'typeorm-polymorphic'
import { Institution } from '../institution.entity'

@EntityRepository(Institution)
export class InstitutionRepository extends AbstractPolymorphicRepository<
  Institution
> {}
 

And added them to the forFeatures export

@NestModule({
  imports: [
    TypeOrmModule.forFeature([
      Institution,
      Tenant,
      Jurisdiction,
      PlatformConfig,
      // Repositories that extend AbstractPolymorphicRepository for polymorphic decorators to take effect
      PlatformConfigRepository,
      JurisdictionRepository,
      TenantRepository,
      InstitutionRepository,
    ]),
  ],
  providers: [
   // providers
  ],
  exports: [
    // services
  ],
})
export class InstitutionsModule {}

The configurations for all of them return undefined

    const institution = await this.polymorphicInstitutionRepository.findOne({
      where: { id },
      relations: ['tenant', 'jurisdiction'],
    })

    const jurisdiction = institution.jurisdiction
    const tenant = institution.tenant

    const configKeys = map(
      [
        jurisdiction.configurations,
        institution.configurations,
        tenant.configurations,
      ],
      keyBy('key')
    )

In my constructor I have

@InjectRepository(InstitutionRepository)
    private polymorphicInstitutionRepository: InstitutionRepository, 

I don't know if it's meant to be used with InjectRepository or my service setup is wrong, how do you use it?

Thanks in advance! This is eating my head up @ work and we've never worked with your plugin before, but we really think it could benefit us from now on ๐Ÿ‘ฏ ๐Ÿ‘ฏ

The implementation in the readme is wrong.

Or at least, I was having problems with it.

I could not use the extended repository without invoking getCustomRepository instead of the standard getRepository as stated in the readme.

@Entity()
export class Address implements PolymorphicChildInterface {
....
}

@EntityRepository(Address)
export class AddressRepository extends AbstractPolymorphicRepository<Address> {}
import { Address, AddressRepository } from '../';
let addressRepository: AbstractPolymorphicRepository<Address>;

addressRepository = await TestConnection.connection.getCustomRepository(AddressRepository);

Children not populated when retrieving parent

Not sure if this is supposed to be a capability of this library or not, but when I add the following unit test to this library to test whether it works:

      describe('parent has children', async () => {
        it('Can find parent with children', async () => {
          const repository = AbstractPolymorphicRepository.createRepository(
            connection,
            AdvertRepository,
          );
          const userRepository = connection.getRepository(UserEntity);

          const user = await userRepository.save(new UserEntity());

          await repository.save([
            repository.create({
              owner: user,
            }),
            repository.create({
              owner: user,
            }),
          ]);

          const result = await userRepository.find();
          result.forEach((res) => {
            expect(res).toBeInstanceOf(UserEntity);
            expect(res.adverts).toBeInstanceOf(Array);
            expect(res.adverts.length).toBe(2);
          });
        });
      });

users returned from userRepository.find() always have res.adverts === undefined. I would've expected this field to be populated when retrieving from the repository.
I also added eager: true to the adverts field of UserEntity but that didn't make any difference.

Am I doing something wrong?

How to use in nestjs?

I have applied this to the entity

import { CustomerEntity } from '@/customer/entities/customer.entity';
import { Mediums } from '@/mediums/types/platform.enum';
import { WorkspaceEntity } from '@/workspace/entities/workspace.entity';
import {
  Entity,
  PrimaryGeneratedColumn,
  ManyToOne,
  JoinColumn,
  Column,
  OneToMany,
  EntityRepository,
} from 'typeorm';
import { WhatsappEntity } from '../whatsapp/entities/whatsapp.entity';
import {
  AbstractPolymorphicRepository,
  PolymorphicParent,
} from 'typeorm-polymorphic';
import { PolymorphicChildInterface } from 'typeorm-polymorphic/dist/polymorphic.interface';

@EntityRepository(MediumEntity)
@Entity('mediums')
export class MediumEntity
  extends AbstractPolymorphicRepository<MediumEntity>
  implements PolymorphicChildInterface
{
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => WorkspaceEntity, (workspace) => workspace.mediums)
  @JoinColumn({ referencedColumnName: 'id' })
  workspace: WorkspaceEntity;

  @Column({
    type: 'enum',
    enum: Mediums,
  })
  type: Mediums;

  @Column()
  name: string;

  @OneToMany(() => CustomerEntity, (customers) => customers.medium, {
    cascade: true,
  })
  customers: CustomerEntity[];

  @PolymorphicParent(() => [WhatsappEntity], {
    cascade: true,
  })
  platform: WhatsappEntity;

  @Column()
  entityId: string;

  @Column()
  entityType: string;
}

and apply it to the service, namely injectRepository

@Injectable()
export class MediumService {
  constructor(
    @InjectRepository(MediumEntity)
    public mediumRepository: Repository<MediumEntity>,
  ) {}
}

and it gets the error null value in column \"entityId\" of relation \"mediums\" violates not-null constraint\" I'm confused because of the lack of usage examples

Possible bug: polymorphic entity creation not working

Hey, first of all massive thanks for creating this extension. I've grown up with PHP and made Laravel my superpower, so having this Eloquent-inspired feature available in Typeorm has been a great experience so far.

I'm currently having an issue with creating entities using the polymorphic relationships provided by this package, but I've only been doing NestJS/Typeorm for about half a year now, so it's very possible that it is due to my lack of experience with the tooling that I'm running into issues. I'd be more than willing to look into fixing the issue, but I'd like confirmation that it's an actual issue first.

I have the following files/classes available, slightly edited for brevity:


@EntityRepository(Notification)
export class NotificationRepository extends AbstractPolymorphicRepository<Notification> {}

export type Notifiable = Foo | Bar | Baz

@Entity('notifications')
export class Notification  extends TimestampedBaseEntity implements PolymorphicChildInterface {

  @Column()
  entityId: string

  @Column()
  entityType: string

  // TODO: use Notifiable here too
  @PolymorphicParent(() => [Foo, Bar, Baz])
  notifiable: Notifiable

  @ManyToOne(() => User)
  @JoinColumn({ name: 'user_id' })
  @Expose()  user: User
}

@Module({
  imports: [
    TypeOrmModule.forFeature([Notification, User, Foo, Bar, Baz]),
    // specifically not using  NotificationRepository in TypeOrm.forFeature here, that seems to be a documentation error?
    UserModule
  ],
  controllers: [NotificationsController],
  providers: [NotificationsService],
  exports: [NotificationsService]
})
export class NotificationsModule {}

@Injectable()
export class NotificationsService {

  constructor(private readonly notificationsRepository: NotificationRepository) {}
  
  notify(user: User, notifiable: Notifiable): Promise<Notification> {
    return this.notificationsRepository.create({
      user,
      // notifiable, HACK: we shouldn't need this
      entityId: notifiable.id,
      entityType: notifiable.constructor.name
    }).save()
  }
}

For reasons unclear to me the entityId and entityType here are not set automatically when I add the notifiable directly, manually specifying them does work. I tried several permutations of setting the notifiable here, including manually instantiating the entity instead of using the repository, but no dice. I more or less stole this approach from here. As an aside, the readme.md suggests importing the Repository from Typeorm, but that gave me errors. For completeness sake I added it to the sample code.

For what it's worth, I'm using

"@nestjs/typeorm": "^7.1.5"
 "typeorm": "^0.2.29"
"typescript": "^4.0.5"

I'd love to look into fixing this issue, but as mentioned I'm unsure if this is something that's on our end or something that needs to be fixed here.

Relation with property path ** in entity was not found

Paragraphs.entity.ts

import {
  Column,
  PrimaryGeneratedColumn,
  ManyToOne,
  Entity,
  JoinColumn,
  OneToMany,
  JoinTable,
} from 'typeorm';
import { DateAudit } from '@entities/date-audit.entity';
import { Posts as Post } from '@posts/posts.entity';
import { Likes as Like } from '@likes/likes.entity';
import { Comments as Comment } from '@comments/comments.entity';
import { PolymorphicChildren } from 'typeorm-polymorphic';

@Entity()
export class Paragraphs extends DateAudit {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  body: string;

  @Column({
    default: 0,
  })
  viewCount: number;

  @ManyToOne(() => Post, (post) => post.paragraphs)
  @JoinColumn({ name: 'post_id' })
  post: Post;

  @OneToMany(() => Comment, (comment) => comment.paragraph)
  comments: Comment[];

  @PolymorphicChildren(() => Like, { eager: true })
  likes: Like[];
}

likes.entity.ts

import { Column, ManyToOne, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { DateAudit } from '@entities/date-audit.entity';
import { Paragraphs as Paragraph } from '@paragraphs/paragraphs.entity';
import { PolymorphicChildInterface } from 'typeorm-polymorphic/dist/polymorphic.interface';
import { PolymorphicParent } from 'typeorm-polymorphic';

@Entity()
export class Likes extends DateAudit implements PolymorphicChildInterface {
  @PrimaryGeneratedColumn()
  id: number;

  @PolymorphicParent(() => [Paragraph], { eager: false })
  owner: Paragraph;

  @Column({ name: 'entity_id', nullable: true, type: 'int' })
  entityId: number;

  @Column({ name: 'entity_type', nullable: true })
  entityType: string;
}

posts.entity.ts

import {
  Column,
  Entity,
  JoinColumn,
  ManyToOne,
  OneToMany,
  PrimaryGeneratedColumn,
} from 'typeorm';
import { DateAudit } from '@entities/date-audit.entity';
import { Presses as Press } from '@presses/presses.entity';
import { Paragraphs as Paragraph } from '@paragraphs/paragraphs.entity';
import { Categories as Category } from '@categories/categories.entity';

@Entity()
export class Posts extends DateAudit {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ nullable: true })
  title: string;

  @ManyToOne(() => Press, (press) => press.posts)
  @JoinColumn({ name: 'press_id' })
  press: Press;

  @Column({ nullable: true })
  image: string;

  @OneToMany(() => Paragraph, (paragraph) => paragraph.post)
  paragraphs: Paragraph[];

  @ManyToOne(() => Category, (category) => category.posts)
  @JoinColumn({ name: 'category_id' })
  category: Category;
}

posts.repository.ts

import {
  getConnection,
  EntityRepository,
  In,
  Repository,
  getRepository,
} from 'typeorm';
import { Likes as Like } from '@likes/likes.entity';
import { Comments as Comment } from '@comments/comments.entity';
import { Paragraphs as Paragraph } from '@paragraphs/paragraphs.entity';
import { AbstractPolymorphicRepository } from 'typeorm-polymorphic';
import { Posts as Post } from './posts.entity';

@EntityRepository(Post)
export class PostsRepository extends AbstractPolymorphicRepository<Post> {
  async findById(id: number): Promise<Post> {
    const post = await getRepository(Post)
      .createQueryBuilder('post')
      .leftJoinAndSelect('post.paragraphs', 'paragraphs')
      .innerJoinAndSelect('paragraphs.comments', 'comments')
      .leftJoinAndSelect(
        'paragraphs.likes',
        'likes',
      )
      .where('post.id = :id', { id })
      .getOne();
    return post;
  }
}

i want use 'paragraphs.likes' in PostsRepository
but it always return [ExceptionsHandler] Relation with property path likes in entity was not found.
plz help me i want use this

Property "media" was not found in "User". Make sure your query is correct.

user.entity.ts

@PolymorphicChildren(() => Media, {
        eager: true,
    })
    media: Media[];

media.entity.ts

@PolymorphicParent(() => [User])
    owner: User;
    
    @Column({ length: 191, nullable: true })
    entityType: string;

    @Column({ nullable: true })
    entityId: number;

user.service.ts

return this.usersRepository.findOne({
            where: {
                email: email,
            },
            relations: ['media']
        });

error
[Nest] 9960 - 05/11/2024, 4:26:31 PM ERROR [ExceptionsHandler] Property "media" was not found in "User". Make sure your query is correct.
EntityPropertyNotFoundError: Property "media" was not found in "User". Make sure your query is correct.
at L:\node-js\bloom-apis\src\query-builder\SelectQueryBuilder.ts:3932:23

Polymorphic Inheritance

Hi! I have a question regarding inheritance.

I have a Organization class (not an entity) and many entities that inherit from this class such as Grower, Company, Client, etc.

What I wan't to do is to have a UserOrganizationRole entity that polymorphically points to this different entities but when I add the PolymorphicChildren decorator to Organization it gives me the error "Class extends value undefined is not a constructor or null" this doesn't happen if a I add the decorator separately in every entity.

I'm doing something wrong or the library doesn't support the heritage of the PolymorphicChildren decorator?

Example:

This works:

export class Organization extends CustomBaseEntity {
  @Column({ unique: true })
  name!: string;
}
@Entity({ name: 'companies' })
export class Company extends Organization {
  @PolymorphicChildren(() => UserOrganizationRole, {
    eager: false,
  })
  users!: UserOrganizationRole[];
}
@Entity({ name: 'clients' })
export class Client extends Organization {
  @PolymorphicChildren(() => UserOrganizationRole, {
    eager: false,
  })
  users!: UserOrganizationRole[];
}

This doesn't works:

export class Organization extends CustomBaseEntity {
  @Column({ unique: true })
  name!: string;

  @PolymorphicChildren(() => UserOrganizationRole, {
    eager: false,
  })
  users!: UserOrganizationRole[];
}
@Entity({ name: 'companies' })
export class Company extends Organization {}
@Entity({ name: 'clients' })
export class Client extends Organization {}

Where is version 0.0.10?

I was checking out the repo in npm and it says the latest version is 0.0.10. I cannot see it in the repo though

image

Update to work with newer TypeORM version

Have a dependency tree issue with typeorm@"^0.3.0-alpha.24

npm WARN using --force Recommended protections disabled.
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! Found: [email protected]
npm ERR! node_modules/typeorm
npm ERR!   typeorm@"^0.3.0-alpha.24" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer typeorm@"^0.2.29" from [email protected]
npm ERR! node_modules/typeorm-polymorphic
npm ERR!   typeorm-polymorphic@"^0.0.10" from the root project

Creates N queries for hydrateMany

This package creates one query per item for hydrateMany, and that is very slow as it would do 50 separate queries for 50 items that you want to hydrate.

It should make one query for all entities and then just map the result to the entities you want to hydrate.

Entity does not have a primary column.

Hello there! I followed along, but ran into a hiccup.

The console says "Primary column is required to have in all your entities. Use @PrimaryColumn decorator to add a primary column to your entity."

I noticed you do not use that decorator on your child entity, so I did the same thing and this issue came up. How did you tackle it?

Entity type is always null when eager loading

Trying this awesome package with Nest but when querying child entities the entityType is always null.

SELECT "Proposal"."id" AS "Proposal_id", "Proposal"."uuid" AS "Proposal_uuid", "Proposal"."refNodeJson" AS "Proposal_refNodeJson", "Proposal"."content" AS "Proposal_content", "Proposal"."contentJson" AS "Proposal_contentJson", "Proposal"."createdAt" AS "Proposal_createdAt", "Proposal"."updatedAt" AS "Proposal_updatedAt", "Proposal"."entityId" AS "Proposal_entityId", "Proposal"."entityType" AS "Proposal_entityType" FROM "proposal" "Proposal" WHERE "Proposal"."entityId" = $1 AND "Proposal"."entityType" = $2 -- PARAMETERS: [1,null]

Every repository is extended from AbstractPolymorphicRepository

Is there any workaround?

Thank you.

Subject is undefined when working with soft-deleted entities.

Hey, I'm using your package for one of my projects and it saved me a ton of time. But I am having a little issue.
I'm using a polymorphic entity called Activity. It has the parent entities User, Property, Contact, CalendarEvent, Role.
Just for clarification, it is used for tracking user activities on the mentioned entities in the system. It works like a charm when the entities are created or updated, but when the entity is deleted, the subject is undefined. That is pretty reasonable since I wasn't using soft-delete. I switched to it and it is still behaving the same. I'm using Nestjs with Postgres.

@EntityRepository(Activity)
@Entity('activities')
export class Activity
    extends AbstractPolymorphicRepository<Activity>
    implements PolymorphicChildInterface
{
    @PolymorphicParent(() => [User, Contact, Role, Property, CalendarEvent], {
        eager: true,
    })
    subject: User | Contact | Role | Property | CalendarEvent;

    @PrimaryGeneratedColumn()
    id: number;

    @Column({
        nullable: true,
    })
    userId: number;

    @Column()
    entityType: string;

    @Column()
    entityId: number;

    @Column({
        nullable: true,
    })
    message: string;

    @CreateDateColumn()
    timestamp: Date;

    @ManyToOne(() => User, { eager: true })
    @JoinColumn({ name: 'userId', referencedColumnName: 'id' })
    user: User;
}

This is my polymorphic entity

@Entity('roles')
export class Role {
    @ApiModelProperty({ readOnly: true })
    @PrimaryGeneratedColumn()
    id: number;

    @ApiModelProperty()
    @Column({
        unique: true,
    })
    @Validator.IsNotEmpty()
    @Validator.IsString()
    @Validator.MinLength(2, {
        message: 'Name should be at least three characters long',
    })
    name: string;

    @ApiModelProperty()
    @Column()
    permissions: string;

    @Exclude()
    @DeleteDateColumn()
    deletedAt: Date;

    @OneToMany(() => User, (user) => user.role)
    @JoinColumn({ name: 'id', referencedColumnName: 'roleId' })
    users: User[];

    @PolymorphicChildren(() => Activity, {
        eager: false,
    })
    activities: Activity[];
}

This is my role entity with polymorphic implementation

@Injectable()
@EntityRepository(Activity)
export class ActivitiesService extends AbstractPolymorphicRepository<Activity> {
    constructor(
        @InjectRepository(Activity)
        private readonly repository: Repository<Activity>,
    ) {
        super();
    }

    async getAllActivities(): Promise<Activity[] | HttpException> {
        const activities = await this.repository.find({
            withDeleted: true,
        });
        if (activities) {
            return activities;
        } else {
            return new HttpException(
                'No activities found',
                HttpStatus.NOT_FOUND,
            );
        }
    }
}

Activities service

{
        "id": 2,
        "userId": 2,
        "entityType": "Role",
        "entityId": 12,
        "message": "Role has been created",
        "timestamp": "2021-08-30T20:38:46.992Z",
        "user": {
            "id": 2,
            "roleId": 1,
            "name": "John",
            "email": "[email protected]",
            "role": {
                "id": 1,
                "name": "Agent",
                "permissions": ""
            }
        },
        "subject": {
            "id": 12,
            "name": "TestRole",
            "permissions": "create,update"
        }
    },
    {
        "id": 3,
        "userId": 2,
        "entityType": "Role",
        "entityId": 2,
        "message": "Role has been deleted",
        "timestamp": "2021-08-30T20:38:50.980Z",
        "user": {
            "id": 2,
            "roleId": 1,
            "name": "John",
            "email": "[email protected]",
            "role": {
                "id": 1,
                "name": "Agent",
                "permissions": ""
            }
        }
    }

Postman testing JSON response where I have previously created a Role entity and then soft-deleted one. The first one has a subject. Second one's subject is undefined.
Is there any way to include them from the polymorphic service or repo? I can include consoled queries if needed. They are pretty long so I didn't add them initially. The same goes for the Role service. Thanks for the help! :)

EntityMetadataNotFoundError: No metadata for "null" was found

How to implement nullable relationship?

I'm implementing this package with NestJS, all work fine except quering when relationship is null.

Entity schema:

@Entity({
  name: 'businesses',
})
export class Business extends Base implements BusinessInterface {
  @ApiProperty({
    description: 'The business name',
    type: 'string',
    example: 'Plass',
  })
  @Column({
    nullable: false,
  })
  name!: string;

  @ApiProperty({
    description: 'Business active',
    type: 'boolean',
    example: true,
  })
  @Column({
    nullable: false,
    default: true,
  })
  active!: boolean;

  @Column({
    nullable: true,
    enum: IntegrableEnum,
  })
  integrableType?: string;

  @Column({
    nullable: true,
  })
  integrableId?: string;

  @PolymorphicParent(() => [SiigoIntegration], {
    entityTypeColumn: 'integrableType',
    entityTypeId: 'integrableId',
    eager: false,
  })
  public integrable?: SiigoIntegration;
}

My repository find looks like:

@PolymorphicRepository(Business)
export class BusinessPolymorphicRepository extends AbstractPolymorphicRepository<Business> {}
private getRepository(): BusinessPolymorphicRepository {
    return AbstractPolymorphicRepository.createRepository(
      this.dataSource,
      BusinessPolymorphicRepository,
    );
  }

public async find(
    value: string,
    key: keyof FindOptionsWhere<Business> = 'id',
  ): Promise<Business | null> {
    return this.getRepository().findOne({
      where: {
        [key]: value,
      },
    });
  }

Throw this exception:

query: SELECT "Business"."id" AS "Business_id", "Business"."created_at" AS "Business_created_at", "Business"."updated_at" AS "Business_updated_at", "Business"."deleted_at" AS "Business_deleted_at", "Business"."name" AS "Business_name", "Business"."active" AS "Business_active", "Business"."integrableType" AS "Business_integrableType", "Business"."integrableId" AS "Business_integrableId" FROM "businesses" "Business" WHERE ( (("Business"."id" = $1)) ) AND ( "Business"."deleted_at" IS NULL ) LIMIT 1 -- PARAMETERS: ["8cc49d2f-a2cf-416f-891b-bc40f21a13bd"]
[AllExceptionsFilter] EntityMetadataNotFoundError: No metadata for "null" was found.
    at DataSource.getMetadata (/Users/macbookpro/Projects/nb/business-ms/src/data-source/DataSource.ts:450:30)
    at Repository.get metadata [as metadata] (/Users/macbookpro/Projects/nb/business-ms/src/repository/Repository.ts:53:40)
    at Repository.findOne (/Users/macbookpro/Projects/nb/business-ms/src/repository/Repository.ts:597:42)
    at BusinessPolymorphicRepository.<anonymous> (/Users/macbookpro/Projects/nb/business-ms/node_modules/typeorm-polymorphic/dist/polymorphic.repository.js:101:68)
    at Generator.next (<anonymous>)
    at /Users/macbookpro/Projects/nb/business-ms/node_modules/typeorm-polymorphic/dist/polymorphic.repository.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/macbookpro/Projects/nb/business-ms/node_modules/typeorm-polymorphic/dist/polymorphic.repository.js:4:12)
    at BusinessPolymorphicRepository.findPolymorphs (/Users/macbookpro/Projects/nb/business-ms/node_modules/typeorm-polymorphic/dist/polymorphic.repository.js:99:16)
    at /Users/macbookpro/Projects/nb/business-ms/node_modules/typeorm-polymorphic/dist/polymorphic.repository.js:85:78

Dependencies:

"@nestjs/typeorm": "^10.0.2",
"typeorm-polymorphic": "^1.0.0"

Note:

I'm setting eager: false in all places and anyway this query the relationship

How to generate migration?

I added PolymorphicParent and PolymorphicChildren decorators to entities but when i try to generate migration like this

typeorm-ts-node-esm migration:generate -d src/migrations-data-source.ts src/migrations/$PG_MIGRATION_NAME

and my migrations generated without new table and columns for polymoprh relations.

My migrations-data-source.ts looks like

import { DataSource } from "typeorm";
export const PGDataSource = new DataSource({
....
})

should i change migrations-data-source.ts for example or something else? i dont understand :(

[Question] Does this library support many-to-many polymorphic relation?

HI thanks for the library.

Does it support many-to-many polymorphic relation like tags <->videos, images? Tagging(Junction table) -> entityId, entityType, tagId something like this? If not, how would approach it? Do you prefer creating a custom junction table and use this library to have like OneTOMany from video, images -> Tagging <- Tags?

how can i get children of parent

hi
thanks for your great code.

i used your solution . it works for saving relations. but when i want to fetch relations with parent i got undefined .

here is my code :

import {PolymorphicChildInterface} from "../polymorphic/polymorphic.interface";
import {PolymorphicParent} from "../polymorphic/decorators";
import {Product} from "./Product.entity";
import {Column, Entity, PrimaryGeneratedColumn} from "typeorm";
import {Transform} from "class-transformer";
import {User} from "./User.entity";
import {Shop} from "./Shop.entity";
import {Common} from "./Common";
import {Company} from "./Company.entity";
import {Brand} from "./Brand.entity";

@Entity('attachments')
export class Attachment implements PolymorphicChildInterface {

    @PrimaryGeneratedColumn()
    id: number;

    @PolymorphicParent(() => [Shop, Product, User, Company, Brand])
    @Transform(
        (value: Shop | User | Product | Company | Brand) => ({
            ...value,
            type: value.constructor.name,
        }),
        {
            toPlainOnly: true,
        },
    )
    owner: Shop | User | Product | Company | Brand;

    @Column()
    entityId: number;

    @Column()
    entityType: string;

    @Column()
    fileName: string;

    @Column()
    mimeType: string;

    @Column()
    size: number;

    @Column(type => Common)
    common: Common;
}
import {Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn} from "typeorm";
import {Common} from "./Common";
import {Seo} from "./Seo";
import {User} from "./User.entity";
import {Brand} from "./Brand.entity";
import {Field, ID, ObjectType} from "@nestjs/graphql";
import {PolymorphicChildren} from "../polymorphic/decorators";
import {Attachment} from "./Attachment.entity";

export enum CompnayStatus {
    ACTIVE = "active",
    INACTIVE = "inActive"
}

@ObjectType()
@Entity('companies')
export class Company {

    @Field(type => ID)
    @PrimaryGeneratedColumn()
    id: number;

    @Field()
    @Column({nullable: false})
    name: string;

    @Field()
    @Column({nullable: true})
    description: string;


    @ManyToOne(type => User, user => user.createdCompanies, {nullable: false})
    creator: User;

    @OneToMany(type => Brand, brand => brand.company)
    brands: Brand[];

    @Field()
    @Column({
        type: "enum",
        enum: CompnayStatus,
        default: CompnayStatus.ACTIVE
    })
    status: CompnayStatus;

    @PolymorphicChildren(() => Attachment, {
        eager: true,
    })
    files: Attachment[];


    @Column(type => Common)
    common: Common;

    @Column(type => Seo)
    seo: Seo

}
import {CompaniesService} from './companies.service';
import {CompaniesController} from "./companies.controller";
import {TypeOrmModule} from "@nestjs/typeorm";
import {Company} from "../entity/Company.entity";
import {CompaniesResolver} from "./companies.resolver";
import {AttachmentRepository} from "../attachments/attachment.repository";
import {AttachmentsService} from "../attachments/attachments.service";


@Module({
    imports: [TypeOrmModule.forFeature([Company, AttachmentRepository])],
    controllers: [CompaniesController],
    providers: [CompaniesService, AttachmentsService, CompaniesResolver],
    exports: [TypeOrmModule]
})
export class CompaniesModule {

}
import {Module} from '@nestjs/common';
import {TypeOrmModule} from "@nestjs/typeorm";
import {Attachment} from "../entity/Attachment.entity";
import {AttachmentRepository} from "./attachment.repository";
import {AttachmentsService} from "./attachments.service";

@Module({
    imports: [TypeOrmModule.forFeature([Attachment, AttachmentRepository])],
    providers: [AttachmentsService],
    exports: [TypeOrmModule, AttachmentsService]
})
export class AttachmentsModule {

}
    async create(createCompanyDto: CreateCompanyDto): Promise<Company> {
        const company = new Company();
        company.name = createCompanyDto.name;
        const creator = new User();
        creator.id = createCompanyDto.creatorId;
        company.creator = creator;
        company.description = createCompanyDto.description;

        const finalCompany = await this.companiesRepository.save(company);

        if (createCompanyDto.logo) {
            await this.attachmentRepository.save({
                owner: finalCompany,
                fileName: createCompanyDto.logo.filename,
                size: createCompanyDto.logo.size,
                mimeType: createCompanyDto.logo.mimetype
            });
        }
        return finalCompany
    }
async findOne(id: number): Promise<Company> {
        const company = await this.companiesRepository.findOne(id);
        console.log(company.files)
        return company;
    }

any idea?

Massive refactor for clean up required

I built this in a rush, the original I wrote on a plane, this version was written on a train. Yea it wasn't that extravigant. But the codebase is seriously poor. Needs more than prettier. Could use some help but it is a bit... ...my shitty mess ๐Ÿ˜‚

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.