From 89bbeb04fb4bfda0506ebf16326908efb99bb5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ad=C3=A1mek?= Date: Thu, 17 Oct 2019 19:55:30 +0200 Subject: [PATCH] feat(core): do not require entity attribute in collection decorators (#207) Now the type from `prop: Collection` is sniffed via reflection, so you do not have to define `entity` or `type` attributes in collection decorators (@ManyToMany and @OneToMany). --- README.md | 2 +- docs/defining-entities.md | 5 ++++- docs/relationships.md | 4 ++-- lib/decorators/ManyToMany.ts | 9 ++------- lib/decorators/OneToMany.ts | 6 +----- lib/metadata/TypeScriptMetadataProvider.ts | 16 +++++++++++----- tests/decorators.test.ts | 3 --- tests/entities-sql/Author2.ts | 2 +- tests/entities-sql/FooBaz2.ts | 2 +- tests/entities-sql/Publisher2.ts | 4 ++-- tests/entities-sql/Test2.ts | 2 +- tests/entities/Author.ts | 2 +- tests/entities/Book.ts | 2 +- tests/entities/Publisher.ts | 4 ++-- 14 files changed, 30 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index ea0f220f3e49..ad0faa7074a4 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ export class MongoBook implements MongoEntity { @ManyToOne() author: Author; - @ManyToMany(() => BookTag, tag => tag.books, { owner: true }) + @ManyToMany() tags = new Collection(this); constructor(title: string, author: Author) { diff --git a/docs/defining-entities.md b/docs/defining-entities.md index 71afc254cba2..c9581bd5b5a0 100644 --- a/docs/defining-entities.md +++ b/docs/defining-entities.md @@ -32,7 +32,7 @@ export class Book implements IdEntity { @ManyToOne(() => Publisher) // or you can specify the entity as class reference or string name publisher?: Publisher; - @ManyToMany(() => BookTag, tag => tag.books, { owner: true }) + @ManyToMany() // owning side can be simple as this! tags = new Collection(this); constructor(title: string, author: Author) { @@ -96,6 +96,9 @@ export class Author implements MongoEntity { @OneToMany(() => Book, book => book.author) books = new Collection(this); + @ManyToMany() + friends = new Collection(this); + @ManyToOne() favouriteBook: Book; diff --git a/docs/relationships.md b/docs/relationships.md index fdf8807172e7..343391299c92 100644 --- a/docs/relationships.md +++ b/docs/relationships.md @@ -62,7 +62,7 @@ export class Author implements IdEntity { @OneToMany('Book', 'author') books2 = new Collection(this); - @OneToMany({ entity: () => Book, mappedBy: book => book.author }) + @OneToMany({ mappedBy: book => book.author }) // referenced entity type can be sniffer too books3 = new Collection(this); @OneToMany({ entity: () => Book, mappedBy: 'author', orphanRemoval: true }) @@ -137,7 +137,7 @@ Here are examples of how you can define ManyToMany relationship: export class Book implements IdEntity { // when none of `owner/inverseBy/mappedBy` is provided, it will be considered owning side - @ManyToMany(() => BookTag) + @ManyToMany() tags1 = new Collection(this); @ManyToMany(() => BookTag, 'books', { owner: true }) diff --git a/lib/decorators/ManyToMany.ts b/lib/decorators/ManyToMany.ts index d29f1f4c6e50..92496e6035b1 100644 --- a/lib/decorators/ManyToMany.ts +++ b/lib/decorators/ManyToMany.ts @@ -6,7 +6,7 @@ import { EntityName, EntityProperty, AnyEntity } from '../types'; import { QueryOrder } from '../query'; export function ManyToMany>( - entity: ManyToManyOptions | string | (() => EntityName), + entity?: ManyToManyOptions | string | (() => EntityName), mappedBy?: (string & keyof T) | ((e: T) => any), options: Partial> = {}, ) { @@ -24,18 +24,13 @@ export function ManyToMany>( const meta = MetadataStorage.getMetadata(target.constructor.name); Utils.lookupPathFromDecorator(meta); - - if (!options.entity) { - throw new Error(`'@ManyToMany({ entity: string | Function })' is required in '${target.constructor.name}.${propertyName}'`); - } - const property = { name: propertyName, reference: ReferenceType.MANY_TO_MANY, owner: !!options.inversedBy, cascade: [Cascade.PERSIST, Cascade.MERGE] } as EntityProperty; meta.properties[propertyName] = Object.assign(property, options); }; } export interface ManyToManyOptions> extends ReferenceOptions { - entity: string | (() => EntityName); + entity?: string | (() => EntityName); owner?: boolean; inversedBy?: (string & keyof T) | ((e: T) => any); mappedBy?: (string & keyof T) | ((e: T) => any); diff --git a/lib/decorators/OneToMany.ts b/lib/decorators/OneToMany.ts index 526d2e69f26d..b202e1712a5a 100644 --- a/lib/decorators/OneToMany.ts +++ b/lib/decorators/OneToMany.ts @@ -25,10 +25,6 @@ export function createOneToDecorator>( Utils.lookupPathFromDecorator(meta); if (reference === ReferenceType.ONE_TO_MANY) { - if (!options.entity) { - throw new Error(`'@OneToMany({ entity: string | Function })' is required in '${target.constructor.name}.${propertyName}'`); - } - if ((options as any).fk) { throw new Error(`@OneToMany({ fk })' is deprecated, use 'mappedBy' instead in '${target.constructor.name}.${propertyName}'`); } @@ -56,7 +52,7 @@ export function createOneToDecorator>( } export type OneToManyOptions> = ReferenceOptions & { - entity: string | (() => EntityName); + entity?: string | (() => EntityName); orphanRemoval?: boolean; orderBy?: { [field: string]: QueryOrder }; joinColumn?: string; diff --git a/lib/metadata/TypeScriptMetadataProvider.ts b/lib/metadata/TypeScriptMetadataProvider.ts index 2ed6a0e85305..1db1baa619ef 100644 --- a/lib/metadata/TypeScriptMetadataProvider.ts +++ b/lib/metadata/TypeScriptMetadataProvider.ts @@ -44,7 +44,8 @@ export class TypeScriptMetadataProvider extends MetadataProvider { prop.nullable = true; } - this.processReferenceWrapper(prop); + this.processWrapper(prop, 'IdentifiedReference'); + this.processWrapper(prop, 'Collection'); } private async readTypeFromSource(file: string, name: string, prop: EntityProperty): Promise<{ type: string; optional?: boolean }> { @@ -71,11 +72,16 @@ export class TypeScriptMetadataProvider extends MetadataProvider { return source; } - private processReferenceWrapper(prop: EntityProperty): void { - const m = prop.type.match(/^IdentifiedReference<(\w+),?.*>$/); + private processWrapper(prop: EntityProperty, wrapper: string): void { + const m = prop.type.match(new RegExp(`^${wrapper}<(\\w+),?.*>$`)); - if (m) { - prop.type = m[1]; + if (!m) { + return; + } + + prop.type = m[1]; + + if (wrapper === 'IdentifiedReference') { prop.wrappedReference = true; } } diff --git a/tests/decorators.test.ts b/tests/decorators.test.ts index 95cf0bb64e7c..31b4f248e975 100644 --- a/tests/decorators.test.ts +++ b/tests/decorators.test.ts @@ -16,8 +16,6 @@ class Test6 implements AnyEntity {} describe('decorators', () => { test('ManyToMany', () => { - expect(() => ManyToMany({} as any)(new Test(), 'test')).toThrowError(`@ManyToMany({ entity: string | Function })' is required in 'Test.test`); - const storage = MetadataStorage.getMetadata(); ManyToMany({ entity: () => Test })(new Test2(), 'test0'); expect(storage.Test2.properties.test0).toMatchObject({ @@ -61,7 +59,6 @@ describe('decorators', () => { }); test('OneToMany', () => { - expect(() => OneToMany({} as any)(new Test(), 'test')).toThrowError(`@OneToMany({ entity: string | Function })' is required in 'Test.test`); expect(() => OneToMany({ entity: () => Test, fk: 'test' } as any)(new Test(), 'test')).toThrowError(`@OneToMany({ fk })' is deprecated, use 'mappedBy' instead in 'Test.test`); const storage = MetadataStorage.getMetadata(); diff --git a/tests/entities-sql/Author2.ts b/tests/entities-sql/Author2.ts index f7242b4769c7..e2e51e6b1c6c 100644 --- a/tests/entities-sql/Author2.ts +++ b/tests/entities-sql/Author2.ts @@ -36,7 +36,7 @@ export class Author2 extends BaseEntity2 { @Property({ length: 0 }) born?: Date; - @OneToMany('Book2', 'author', { orderBy: { title: QueryOrder.ASC } }) + @OneToMany({ mappedBy: 'author', orderBy: { title: QueryOrder.ASC } }) books!: Collection; @ManyToOne() diff --git a/tests/entities-sql/FooBaz2.ts b/tests/entities-sql/FooBaz2.ts index 16480680d0c7..4c322d99af14 100644 --- a/tests/entities-sql/FooBaz2.ts +++ b/tests/entities-sql/FooBaz2.ts @@ -10,7 +10,7 @@ export class FooBaz2 implements IdEntity { @Property() name: string; - @OneToOne(() => FooBar2, 'baz') + @OneToOne({ mappedBy: 'baz' }) bar?: FooBar2; @Property({ version: true }) diff --git a/tests/entities-sql/Publisher2.ts b/tests/entities-sql/Publisher2.ts index 34c5f0e73696..58aff8cd2ce9 100644 --- a/tests/entities-sql/Publisher2.ts +++ b/tests/entities-sql/Publisher2.ts @@ -9,10 +9,10 @@ export class Publisher2 extends BaseEntity2 { @Property() name: string; - @OneToMany({ entity: () => Book2, mappedBy: 'publisher' }) + @OneToMany({ mappedBy: 'publisher' }) books!: Collection; - @ManyToMany({ entity: () => Test2, fixedOrder: true }) + @ManyToMany({ fixedOrder: true }) tests!: Collection; @Property({ type: 'string', length: 10 }) diff --git a/tests/entities-sql/Test2.ts b/tests/entities-sql/Test2.ts index a8987910f148..5ef32ee696bb 100644 --- a/tests/entities-sql/Test2.ts +++ b/tests/entities-sql/Test2.ts @@ -10,7 +10,7 @@ export class Test2 implements IdEntity { @Property() name?: string; - @OneToOne({ cascade: [], inversedBy: 'test' }) + @OneToOne({ entity: 'Book2', cascade: [], inversedBy: 'test' }) book?: Book2; @Property({ version: true }) diff --git a/tests/entities/Author.ts b/tests/entities/Author.ts index 955e5b4aac33..50a5702a8bdd 100644 --- a/tests/entities/Author.ts +++ b/tests/entities/Author.ts @@ -34,7 +34,7 @@ export class Author extends BaseEntity { @OneToMany(() => Book, book => book.author, { referenceColumnName: '_id', cascade: [Cascade.PERSIST], orphanRemoval: true }) books = new Collection(this); - @ManyToMany(() => Author) + @ManyToMany() friends: Collection = new Collection(this); @ManyToOne() diff --git a/tests/entities/Book.ts b/tests/entities/Book.ts index cca7da107c0e..68cbd04dc267 100644 --- a/tests/entities/Book.ts +++ b/tests/entities/Book.ts @@ -30,7 +30,7 @@ export class Book extends BaseEntity3 { @ManyToOne({ cascade: [Cascade.PERSIST, Cascade.REMOVE] }) publisher!: IdentifiedReference; - @ManyToMany(() => BookTag) + @ManyToMany() tags = new Collection(this); @Property() diff --git a/tests/entities/Publisher.ts b/tests/entities/Publisher.ts index 22c83a5e0b2a..68af7a030da4 100644 --- a/tests/entities/Publisher.ts +++ b/tests/entities/Publisher.ts @@ -16,10 +16,10 @@ export class Publisher implements MongoEntity { @Property() name: string; - @OneToMany({ entity: () => Book.name, mappedBy: 'publisher' }) + @OneToMany({ mappedBy: 'publisher' }) books = new Collection(this); - @ManyToMany({ entity: () => Test.name, owner: true, eager: true }) + @ManyToMany({ eager: true }) tests = new Collection(this); @Property()