Skip to content

Commit

Permalink
feat(core): simplify entity definition and rework typings of FilterQuery
Browse files Browse the repository at this point in the history
Now it is no longer needed to merge entities with IEntity interface, that was polluting entity's interface with internal methods.
New interfaces IdentifiedEntity<T>, UuidEntity<T> and MongoEntity<T> and introduced, that should be implemented by entities. They
are not adding any new properties or methods, keeping the entity's interface clean.

This also introduces new strictly typed FilterQuery<T> implementation based on information provided by those new interfaces.

BREAKING CHANGES:
IEntity interface no longer has public methods like toJSON(), toObject() or init(). One can use wrap() method provided by ORM that
will enhance property type when needed with those methods (`await wrap(book.author).init()`).
FilterQuery now does not allow using smart query operators. You can either cast your condition as any or use object syntax instead
(instead of `{ 'age:gte': 18 }` use `{ age: { $gte: 18 } }`).

Closes #124, #171
  • Loading branch information
B4nan committed Oct 9, 2019
1 parent cff0dd4 commit 732144c
Show file tree
Hide file tree
Showing 80 changed files with 1,193 additions and 820 deletions.
99 changes: 48 additions & 51 deletions lib/EntityManager.ts

Large diffs are not rendered by default.

21 changes: 6 additions & 15 deletions lib/connections/MongoConnection.ts
@@ -1,19 +1,10 @@
import {
Collection,
Db, DeleteWriteOpResultObject,
InsertOneWriteOpResult,
MongoClient,
MongoClientOptions,
ObjectId,
UpdateWriteOpResult,
} from 'mongodb';
import { Collection, Db, DeleteWriteOpResultObject, InsertOneWriteOpResult, MongoClient, MongoClientOptions, ObjectId, UpdateWriteOpResult, FilterQuery as MongoFilterQuery } from 'mongodb';
import { inspect } from 'util';

import { Connection, ConnectionConfig, QueryResult } from './Connection';
import { Utils } from '../utils';
import { QueryOrder, QueryOrderMap } from '../query';
import { FilterQuery, IEntity } from '..';
import { EntityName } from '../decorators';
import { FilterQuery, IEntity, EntityName } from '../types';

export class MongoConnection extends Connection {

Expand Down Expand Up @@ -74,7 +65,7 @@ export class MongoConnection extends Connection {
options.projection = fields.reduce((o, k) => ({ ...o, [k]: 1 }), {});
}

const resultSet = this.getCollection(collection).find(where, options);
const resultSet = this.getCollection(collection).find<T>(where, options);
let query = `db.getCollection('${collection}').find(${this.logObject(where)}, ${this.logObject(options)})`;

if (orderBy && Object.keys(orderBy).length > 0) {
Expand Down Expand Up @@ -149,12 +140,12 @@ export class MongoConnection extends Connection {
case 'updateMany':
const payload = Object.keys(data).some(k => k.startsWith('$')) ? data : { $set: data };
query = `db.getCollection('${collection}').updateMany(${this.logObject(where)}, ${this.logObject(payload)});`;
res = await this.getCollection(collection).updateMany(where, payload);
res = await this.getCollection(collection).updateMany(where as MongoFilterQuery<T>, payload);
break;
case 'deleteMany':
case 'countDocuments':
query = `db.getCollection('${collection}').${method}(${this.logObject(where)});`;
res = await this.getCollection(collection)[method as 'deleteMany'](where); // cast to deleteMany to fix some typing weirdness
res = await this.getCollection(collection)[method as 'deleteMany'](where as MongoFilterQuery<T>); // cast to deleteMany to fix some typing weirdness
break;
}

Expand Down Expand Up @@ -203,7 +194,7 @@ export class MongoConnection extends Connection {
return meta ? meta.collection : name;
}

private logObject(o: object): string {
private logObject(o: any): string {
return inspect(o, { depth: 5, compact: true, breakLength: 300 });
}

Expand Down
101 changes: 6 additions & 95 deletions lib/decorators/Entity.ts
@@ -1,13 +1,11 @@
import { MetadataStorage } from '../metadata';
import { EntityManager } from '../EntityManager';
import { IPrimaryKey } from './PrimaryKey';
import { AssignOptions, Cascade, Collection, EntityRepository, ReferenceType } from '../entity';
import { EntityRepository } from '../entity';
import { Utils } from '../utils';
import { QueryOrder } from '../query';
import { LockMode } from '../unit-of-work';
import { EntityName, IEntity } from '../types';

export function Entity(options: EntityOptions = {}): Function {
return function <T extends { new(...args: any[]): IEntity }>(target: T) {
export function Entity(options: EntityOptions<any> = {}): Function {
return function <T extends { new(...args: any[]): IEntity<T> }>(target: T) {
const meta = MetadataStorage.getMetadata(target.name);
Utils.merge(meta, options);
meta.name = target.name;
Expand All @@ -19,94 +17,7 @@ export function Entity(options: EntityOptions = {}): Function {
};
}

export type EntityOptions = {
export type EntityOptions<T extends IEntity<T>> = {
collection?: string;
customRepository?: () => { new (em: EntityManager, entityName: EntityName<IEntity>): EntityRepository<IEntity> };
customRepository?: () => { new (em: EntityManager, entityName: EntityName<T>): EntityRepository<T> };
};

export interface IEntity<K = number | string> {
id: K;
isInitialized(): boolean;
populated(populated?: boolean): void;
init(populated?: boolean, lockMode?: LockMode): Promise<this>;
toObject(ignoreFields?: string[]): Record<string, any>;
toJSON(...args: any[]): Record<string, any>;
assign(data: any, options?: AssignOptions | boolean): this;
__uuid: string;
__meta: EntityMetadata;
__em: EntityManager;
__initialized?: boolean;
__populated: boolean;
__lazyInitialized: boolean;
__primaryKey: K;
__primaryKeyField: string & keyof IEntity;
__serializedPrimaryKey: string & keyof IEntity;
__serializedPrimaryKeyField: string;
}

export type IEntityType<T> = { [k in keyof T]: IEntity | Collection<IEntity> | any; } & IEntity;

export type EntityClass<T extends IEntityType<T>> = Function & { prototype: T };

export type EntityClassGroup<T extends IEntityType<T>> = {
entity: EntityClass<T>;
schema: EntityMetadata<T>;
};

export type EntityName<T extends IEntityType<T>> = string | EntityClass<T>;

export type EntityData<T extends IEntityType<T>> = { [P in keyof T]?: T[P] | IPrimaryKey; } & Record<string, any>;

export interface EntityProperty<T extends IEntityType<T> = any> {
name: string & keyof T;
entity: () => EntityName<T>;
type: string;
columnType: string;
primary: boolean;
length?: any;
reference: ReferenceType;
wrappedReference?: boolean;
fieldName: string;
default?: any;
unique?: boolean;
nullable?: boolean;
unsigned: boolean;
persist?: boolean;
hidden?: boolean;
version?: boolean;
eager?: boolean;
setter?: boolean;
getter?: boolean;
getterName?: keyof T;
cascade: Cascade[];
orphanRemoval?: boolean;
onUpdate?: () => any;
owner: boolean;
inversedBy: string;
mappedBy: string;
orderBy?: { [field: string]: QueryOrder };
pivotTable: string;
joinColumn: string;
inverseJoinColumn: string;
referenceColumnName: string;
referencedTableName: string;
}

export type HookType = 'onInit' | 'beforeCreate' | 'afterCreate' | 'beforeUpdate' | 'afterUpdate' | 'beforeDelete' | 'afterDelete';

export interface EntityMetadata<T extends IEntityType<T> = any> {
name: string;
className: string;
constructorParams: (keyof T & string)[];
toJsonParams: string[];
extends: string;
collection: string;
path: string;
primaryKey: keyof T & string;
versionProperty: keyof T & string;
serializedPrimaryKey: keyof T & string;
properties: { [K in keyof T & string]: EntityProperty<T> };
customRepository: () => { new (em: EntityManager, entityName: EntityName<T>): EntityRepository<T> };
hooks: Partial<Record<HookType, (string & keyof T)[]>>;
prototype: T;
}
6 changes: 3 additions & 3 deletions lib/decorators/ManyToMany.ts
@@ -1,10 +1,10 @@
import { ReferenceOptions } from './Property';
import { EntityName, EntityProperty, IEntity, IEntityType } from './Entity';
import { MetadataStorage } from '../metadata';
import { Utils } from '../utils';
import { Cascade, ReferenceType } from '../entity';
import { EntityName, EntityProperty, IEntity } from '../types';

export function ManyToMany<T extends IEntityType<T>>(
export function ManyToMany<T extends IEntity<T>>(
entity: ManyToManyOptions<T> | string | (() => EntityName<T>),
mappedBy?: (string & keyof T) | ((e: T) => any),
options: Partial<ManyToManyOptions<T>> = {},
Expand Down Expand Up @@ -32,7 +32,7 @@ export function ManyToMany<T extends IEntityType<T>>(
};
}

export interface ManyToManyOptions<T extends IEntityType<T>> extends ReferenceOptions<T> {
export interface ManyToManyOptions<T extends IEntity<T>> extends ReferenceOptions<T> {
entity: string | (() => EntityName<T>);
owner?: boolean;
inversedBy?: (string & keyof T) | ((e: T) => any);
Expand Down
6 changes: 3 additions & 3 deletions lib/decorators/ManyToOne.ts
@@ -1,10 +1,10 @@
import { ReferenceOptions } from './Property';
import { EntityName, EntityProperty, IEntity, IEntityType } from './Entity';
import { MetadataStorage } from '../metadata';
import { Utils } from '../utils';
import { Cascade, ReferenceType } from '../entity';
import { EntityName, EntityProperty, IEntity } from '../types';

export function ManyToOne<T extends IEntityType<T>>(
export function ManyToOne<T extends IEntity<T>>(
entity: ManyToOneOptions<T> | string | ((e?: any) => EntityName<T>) = {},
options: Partial<ManyToOneOptions<T>> = {},
) {
Expand All @@ -24,7 +24,7 @@ export function ManyToOne<T extends IEntityType<T>>(
};
}

export interface ManyToOneOptions<T extends IEntityType<T>> extends ReferenceOptions<T> {
export interface ManyToOneOptions<T extends IEntity<T>> extends ReferenceOptions<T> {
entity?: string | (() => EntityName<T>);
inversedBy?: (string & keyof T) | ((e: T) => any);
wrappedReference?: boolean;
Expand Down
8 changes: 4 additions & 4 deletions lib/decorators/OneToMany.ts
@@ -1,19 +1,19 @@
import { ReferenceOptions } from './Property';
import { EntityName, EntityProperty, IEntity, IEntityType } from './Entity';
import { MetadataStorage } from '../metadata';
import { Utils } from '../utils';
import { Cascade, ReferenceType } from '../entity';
import { QueryOrder } from '../query';
import { EntityName, EntityProperty, IEntity } from '../types';

export function OneToMany<T extends IEntityType<T>>(
export function OneToMany<T extends IEntity<T>>(
entity: OneToManyOptions<T> | string | ((e?: any) => EntityName<T>),
mappedBy?: (string & keyof T) | ((e: T) => any),
options: Partial<OneToManyOptions<T>> = {},
) {
return createOneToDecorator(entity, mappedBy, options, ReferenceType.ONE_TO_MANY);
}

export function createOneToDecorator<T extends IEntityType<T>>(
export function createOneToDecorator<T extends IEntity<T>>(
entity?: OneToManyOptions<T> | string | ((e?: any) => EntityName<T>),
mappedBy?: (string & keyof T) | ((e: T) => any),
options?: Partial<OneToManyOptions<T>>,
Expand Down Expand Up @@ -55,7 +55,7 @@ export function createOneToDecorator<T extends IEntityType<T>>(
};
}

export type OneToManyOptions<T extends IEntityType<T>> = ReferenceOptions<T> & {
export type OneToManyOptions<T extends IEntity<T>> = ReferenceOptions<T> & {
entity: string | (() => EntityName<T>);
orphanRemoval?: boolean;
orderBy?: { [field: string]: QueryOrder };
Expand Down
7 changes: 4 additions & 3 deletions lib/decorators/OneToOne.ts
@@ -1,16 +1,17 @@
import { EntityName, IEntityType } from './Entity';

import { ReferenceType } from '../entity';
import { createOneToDecorator, OneToManyOptions } from './OneToMany';
import { EntityName, IEntity } from '../types';

export function OneToOne<T extends IEntityType<T>>(
export function OneToOne<T extends IEntity<T>>(
entity?: OneToOneOptions<T> | string | ((e?: any) => EntityName<T>),
mappedBy?: (string & keyof T) | ((e: T) => any),
options: Partial<OneToOneOptions<T>> = {},
) {
return createOneToDecorator<T>(entity as string, mappedBy, options, ReferenceType.ONE_TO_ONE);
}

export interface OneToOneOptions<T extends IEntityType<T>> extends Partial<Omit<OneToManyOptions<T>, 'orderBy'>> {
export interface OneToOneOptions<T extends IEntity<T>> extends Partial<Omit<OneToManyOptions<T>, 'orderBy'>> {
owner?: boolean;
inversedBy?: (string & keyof T) | ((e: T) => any);
wrappedReference?: boolean;
Expand Down
4 changes: 2 additions & 2 deletions lib/decorators/PrimaryKey.ts
Expand Up @@ -14,8 +14,8 @@ export function PrimaryKey(options: PrimaryKeyOptions = {}): Function {
}

export interface PrimaryKeyOptions extends PropertyOptions {
name?: string;
type?: any;
}

export type IPrimaryKey = number | string | { toString?(): string; toHexString?(): string };
export type IPrimaryKeyValue = number | string | { toHexString(): string };
export type IPrimaryKey<T extends IPrimaryKeyValue = IPrimaryKeyValue> = T;
5 changes: 3 additions & 2 deletions lib/decorators/Property.ts
@@ -1,7 +1,8 @@
import { EntityName, EntityProperty, IEntity, IEntityType } from './Entity';

import { MetadataStorage } from '../metadata';
import { Utils } from '../utils';
import { Cascade, ReferenceType } from '../entity';
import { EntityName, EntityProperty, IEntity } from '../types';

export function Property(options: PropertyOptions = {}): Function {
return function (target: IEntity, propertyName: string) {
Expand Down Expand Up @@ -40,7 +41,7 @@ export type PropertyOptions = {
version?: boolean;
};

export interface ReferenceOptions<T extends IEntityType<T>> extends PropertyOptions {
export interface ReferenceOptions<T extends IEntity<T>> extends PropertyOptions {
entity?: string | (() => EntityName<T>);
cascade?: Cascade[];
eager?: boolean;
Expand Down
18 changes: 18 additions & 0 deletions lib/decorators/SerializedPrimaryKey.ts
@@ -0,0 +1,18 @@
import { MetadataStorage } from '../metadata';
import { ReferenceType } from '../entity';
import { EntityProperty, IEntity, PropertyOptions } from '.';
import { Utils } from '../utils';

export function SerializedPrimaryKey(options: SerializedPrimaryKeyOptions = {}): Function {
return function (target: IEntity, propertyName: string) {
const meta = MetadataStorage.getMetadata(target.constructor.name);
options.name = propertyName;
meta.serializedPrimaryKey = propertyName;
meta.properties[propertyName] = Object.assign({ reference: ReferenceType.SCALAR }, options) as EntityProperty;
Utils.lookupPathFromDecorator(meta);
};
}

export interface SerializedPrimaryKeyOptions extends PropertyOptions {
type?: any;
}
2 changes: 1 addition & 1 deletion lib/decorators/hooks.ts
@@ -1,5 +1,5 @@
import { MetadataStorage } from '../metadata';
import { HookType } from './Entity';
import { HookType } from '../types';

export function BeforeCreate() {
return hook('beforeCreate');
Expand Down
2 changes: 2 additions & 0 deletions lib/decorators/index.ts
@@ -1,8 +1,10 @@
export * from './PrimaryKey';
export * from './SerializedPrimaryKey';
export * from './Entity';
export * from './OneToOne';
export * from './ManyToOne';
export * from './ManyToMany';
export { OneToMany, OneToManyOptions } from './OneToMany';
export * from './Property';
export * from './hooks';
export * from '../types';

0 comments on commit 732144c

Please sign in to comment.