Skip to content

Commit

Permalink
feat(core): pass entity as parameter in onCreate and onUpdate (#564)
Browse files Browse the repository at this point in the history
```typescript
@Property({ onCreate: (bar: FooBar) => '...return new value based on the entity state...' })
onCreateTest?: string;
```
  • Loading branch information
B4nan committed Aug 9, 2020
1 parent 7703cc5 commit 3044a19
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 68 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/decorators/Enum.ts
Expand Up @@ -3,14 +3,14 @@ import { ReferenceType } from '../entity';
import { PropertyOptions } from '.';
import { EntityProperty, AnyEntity, Dictionary } from '../typings';

export function Enum(options: EnumOptions | (() => Dictionary) = {}): Function {
export function Enum(options: EnumOptions<AnyEntity> | (() => Dictionary) = {}): Function {
return function (target: AnyEntity, propertyName: string) {
const meta = MetadataStorage.getMetadataFromDecorator(target.constructor);
options = options instanceof Function ? { items: options } : options;
meta.properties[propertyName] = Object.assign({ name: propertyName, reference: ReferenceType.SCALAR, enum: true }, options) as EntityProperty;
};
}

export interface EnumOptions extends PropertyOptions {
export interface EnumOptions<T> extends PropertyOptions<T> {
items?: (number | string)[] | (() => Dictionary);
}
10 changes: 5 additions & 5 deletions packages/core/src/decorators/ManyToMany.ts
Expand Up @@ -5,21 +5,21 @@ import { EntityValidator, ReferenceType } from '../entity';
import { EntityName, EntityProperty, AnyEntity } from '../typings';
import { QueryOrder } from '../enums';

export function ManyToMany<T extends AnyEntity<T>>(
entity?: ManyToManyOptions<T> | string | (() => EntityName<T>),
export function ManyToMany<T extends AnyEntity<T>, O extends AnyEntity<O>>(
entity?: ManyToManyOptions<T, O> | string | (() => EntityName<T>),
mappedBy?: (string & keyof T) | ((e: T) => any),
options: Partial<ManyToManyOptions<T>> = {},
options: Partial<ManyToManyOptions<T, O>> = {},
) {
return function (target: AnyEntity, propertyName: string) {
options = Utils.isObject<ManyToManyOptions<T>>(entity) ? entity : { ...options, entity, mappedBy };
options = Utils.isObject<ManyToManyOptions<T, O>>(entity) ? entity : { ...options, entity, mappedBy };
const meta = MetadataStorage.getMetadataFromDecorator(target.constructor);
EntityValidator.validateSingleDecorator(meta, propertyName);
const property = { name: propertyName, reference: ReferenceType.MANY_TO_MANY } as EntityProperty<T>;
meta.properties[propertyName] = Object.assign(property, options);
};
}

export interface ManyToManyOptions<T extends AnyEntity<T>> extends ReferenceOptions<T> {
export interface ManyToManyOptions<T extends AnyEntity<T>, O extends AnyEntity<O>> extends ReferenceOptions<T, O> {
owner?: boolean;
inversedBy?: (string & keyof T) | ((e: T) => any);
mappedBy?: (string & keyof T) | ((e: T) => any);
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/decorators/ManyToOne.ts
Expand Up @@ -4,20 +4,20 @@ import { Utils } from '../utils';
import { EntityValidator, ReferenceType } from '../entity';
import { AnyEntity, EntityName, EntityProperty } from '../typings';

export function ManyToOne<T extends AnyEntity<T>>(
entity: ManyToOneOptions<T> | string | ((e?: any) => EntityName<T>) = {},
options: Partial<ManyToOneOptions<T>> = {},
export function ManyToOne<T extends AnyEntity<T>, O extends AnyEntity<O>>(
entity: ManyToOneOptions<T, O> | string | ((e?: any) => EntityName<T>) = {},
options: Partial<ManyToOneOptions<T, O>> = {},
) {
return function (target: AnyEntity, propertyName: string) {
options = Utils.isObject<ManyToOneOptions<T>>(entity) ? entity : { ...options, entity };
options = Utils.isObject<ManyToOneOptions<T, O>>(entity) ? entity : { ...options, entity };
const meta = MetadataStorage.getMetadataFromDecorator(target.constructor);
EntityValidator.validateSingleDecorator(meta, propertyName);
const property = { name: propertyName, reference: ReferenceType.MANY_TO_ONE } as EntityProperty;
meta.properties[propertyName] = Object.assign(property, options);
};
}

export interface ManyToOneOptions<T extends AnyEntity<T>> extends ReferenceOptions<T> {
export interface ManyToOneOptions<T extends AnyEntity<T>, O extends AnyEntity<O>> extends ReferenceOptions<T, O> {
inversedBy?: (string & keyof T) | ((e: T) => any);
wrappedReference?: boolean;
primary?: boolean;
Expand Down
16 changes: 8 additions & 8 deletions packages/core/src/decorators/OneToMany.ts
Expand Up @@ -5,14 +5,14 @@ import { EntityValidator, ReferenceType } from '../entity';
import { QueryOrder } from '../enums';
import { EntityName, EntityProperty, AnyEntity } from '../typings';

export function createOneToDecorator<T extends AnyEntity<T>>(
entity?: OneToManyOptions<T> | string | ((e?: any) => EntityName<T>),
export function createOneToDecorator<T extends AnyEntity<T>, O extends AnyEntity<O>>(
entity?: OneToManyOptions<T, O> | string | ((e?: any) => EntityName<T>),
mappedBy?: (string & keyof T) | ((e: T) => any),
options?: Partial<OneToManyOptions<T>>,
options?: Partial<OneToManyOptions<T, O>>,
reference?: ReferenceType,
) {
return function (target: AnyEntity, propertyName: string) {
options = Utils.isObject<OneToManyOptions<T>>(entity) ? entity : { ...options, entity, mappedBy };
options = Utils.isObject<OneToManyOptions<T, O>>(entity) ? entity : { ...options, entity, mappedBy };
const meta = MetadataStorage.getMetadataFromDecorator(target.constructor);
EntityValidator.validateSingleDecorator(meta, propertyName);

Expand All @@ -22,15 +22,15 @@ export function createOneToDecorator<T extends AnyEntity<T>>(
};
}

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

export type OneToManyOptions<T extends AnyEntity<T>> = ReferenceOptions<T> & {
export type OneToManyOptions<T extends AnyEntity<T>, O extends AnyEntity<O>> = ReferenceOptions<T, O> & {
entity?: string | (() => EntityName<T>);
orphanRemoval?: boolean;
orderBy?: { [field: string]: QueryOrder };
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/decorators/OneToOne.ts
Expand Up @@ -2,15 +2,15 @@ import { ReferenceType } from '../entity';
import { createOneToDecorator, OneToManyOptions } from './OneToMany';
import { EntityName, AnyEntity } from '../typings';

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

export interface OneToOneOptions<T extends AnyEntity<T>> extends Partial<Omit<OneToManyOptions<T>, 'orderBy'>> {
export interface OneToOneOptions<T extends AnyEntity<T>, O extends AnyEntity<O>> extends Partial<Omit<OneToManyOptions<T, O>, 'orderBy'>> {
owner?: boolean;
inversedBy?: (string & keyof T) | ((e: T) => any);
wrappedReference?: boolean;
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/decorators/PrimaryKey.ts
Expand Up @@ -3,7 +3,7 @@ import { ReferenceType } from '../entity';
import { PropertyOptions } from '.';
import { AnyEntity, EntityProperty } from '../typings';

function createDecorator(options: PrimaryKeyOptions | SerializedPrimaryKeyOptions, serialized: boolean): Function {
function createDecorator<T extends AnyEntity<T>>(options: PrimaryKeyOptions<T> | SerializedPrimaryKeyOptions<T>, serialized: boolean): Function {
return function (target: AnyEntity, propertyName: string) {
const meta = MetadataStorage.getMetadataFromDecorator(target.constructor);
const k = serialized ? 'serializedPrimaryKey' as const : 'primary' as const;
Expand All @@ -12,16 +12,16 @@ function createDecorator(options: PrimaryKeyOptions | SerializedPrimaryKeyOption
};
}

export function PrimaryKey(options: PrimaryKeyOptions = {}): Function {
export function PrimaryKey<T extends AnyEntity<T>>(options: PrimaryKeyOptions<T> = {}): Function {
return createDecorator(options, false);
}

export function SerializedPrimaryKey(options: SerializedPrimaryKeyOptions = {}): Function {
export function SerializedPrimaryKey<T extends AnyEntity<T>>(options: SerializedPrimaryKeyOptions<T> = {}): Function {
return createDecorator(options, true);
}

export interface PrimaryKeyOptions extends PropertyOptions { }
export interface PrimaryKeyOptions<T> extends PropertyOptions<T> { }

export interface SerializedPrimaryKeyOptions extends PropertyOptions {
export interface SerializedPrimaryKeyOptions<T> extends PropertyOptions<T> {
type?: any;
}
12 changes: 6 additions & 6 deletions packages/core/src/decorators/Property.ts
Expand Up @@ -4,7 +4,7 @@ import { Cascade, EntityValidator, ReferenceType } from '../entity';
import { EntityName, EntityProperty, AnyEntity, Constructor } from '../typings';
import { Type } from '../types';

export function Property(options: PropertyOptions = {}): Function {
export function Property<T extends AnyEntity<T>>(options: PropertyOptions<T> = {}): Function {
return function (target: AnyEntity, propertyName: string) {
const meta = MetadataStorage.getMetadataFromDecorator(target.constructor);
const desc = Object.getOwnPropertyDescriptor(target, propertyName) || {};
Expand Down Expand Up @@ -32,16 +32,16 @@ export function Property(options: PropertyOptions = {}): Function {
};
}

export type PropertyOptions = {
export type PropertyOptions<T extends AnyEntity<T>> = {
name?: string;
fieldName?: string;
fieldNames?: string[];
customType?: Type<any>;
columnType?: string;
type?: 'string' | 'number' | 'boolean' | 'bigint' | 'ObjectId' | string | object | bigint | Date | Constructor<Type<any>> | Type<any>;
length?: any;
onCreate?: () => any;
onUpdate?: () => any;
length?: number;
onCreate?: (entity: T) => any;
onUpdate?: (entity: T) => any;
default?: string | number | boolean | null;
defaultRaw?: string;
formula?: string | ((alias: string) => string);
Expand All @@ -56,7 +56,7 @@ export type PropertyOptions = {
serializedPrimaryKey?: boolean;
};

export interface ReferenceOptions<T extends AnyEntity<T>> extends PropertyOptions {
export interface ReferenceOptions<T extends AnyEntity<T>, O extends AnyEntity<O>> extends PropertyOptions<O> {
entity?: string | (() => EntityName<T>);
cascade?: Cascade[];
eager?: boolean;
Expand Down
50 changes: 25 additions & 25 deletions packages/core/src/metadata/EntitySchema.ts
Expand Up @@ -10,19 +10,19 @@ import { Utils } from '../utils';
type CollectionItem<T> = T extends Collection<infer K> ? K : T;
type TypeType = string | NumberConstructor | StringConstructor | BooleanConstructor | DateConstructor | ArrayConstructor | Constructor<Type<any>>;
type TypeDef<T> = { type: TypeType } | { customType: Type<any> } | { entity: string | (() => string | EntityName<T>) };
type Property<T> =
| ({ reference: ReferenceType.MANY_TO_ONE | 'm:1' } & TypeDef<T> & ManyToOneOptions<T>)
| ({ reference: ReferenceType.ONE_TO_ONE | '1:1' } & TypeDef<T> & OneToOneOptions<T>)
| ({ reference: ReferenceType.ONE_TO_MANY | '1:m' } & TypeDef<T> & OneToManyOptions<T>)
| ({ reference: ReferenceType.MANY_TO_MANY | 'm:n' } & TypeDef<T> & ManyToManyOptions<T>)
| ({ reference: ReferenceType.EMBEDDED | 'embedded' } & TypeDef<T> & EmbeddedOptions & PropertyOptions)
| ({ enum: true } & EnumOptions)
| (TypeDef<T> & PropertyOptions);
type Property<T, O> =
| ({ reference: ReferenceType.MANY_TO_ONE | 'm:1' } & TypeDef<T> & ManyToOneOptions<T, O>)
| ({ reference: ReferenceType.ONE_TO_ONE | '1:1' } & TypeDef<T> & OneToOneOptions<T, O>)
| ({ reference: ReferenceType.ONE_TO_MANY | '1:m' } & TypeDef<T> & OneToManyOptions<T, O>)
| ({ reference: ReferenceType.MANY_TO_MANY | 'm:n' } & TypeDef<T> & ManyToManyOptions<T, O>)
| ({ reference: ReferenceType.EMBEDDED | 'embedded' } & TypeDef<T> & EmbeddedOptions & PropertyOptions<O>)
| ({ enum: true } & EnumOptions<O>)
| (TypeDef<T> & PropertyOptions<O>);
type PropertyKey<T, U> = NonFunctionPropertyNames<Omit<T, keyof U>>;
type Metadata<T, U> =
& Omit<Partial<EntityMetadata<T>>, 'name' | 'properties'>
& ({ name: string } | { class: Constructor<T>; name?: string })
& { properties?: { [K in PropertyKey<T, U> & string]-?: Property<CollectionItem<NonNullable<T[K]>>> } };
& { properties?: { [K in PropertyKey<T, U> & string]-?: Property<CollectionItem<NonNullable<T[K]>>, T> } };

export class EntitySchema<T extends AnyEntity<T> = AnyEntity, U extends AnyEntity<T> | undefined = undefined> {

Expand All @@ -48,7 +48,7 @@ export class EntitySchema<T extends AnyEntity<T> = AnyEntity, U extends AnyEntit
return schema;
}

addProperty(name: string & keyof T, type?: TypeType, options: PropertyOptions | EntityProperty = {}): void {
addProperty(name: string & keyof T, type?: TypeType, options: PropertyOptions<T> | EntityProperty = {}): void {
const rename = <U> (data: U, from: string, to: string): void => {
if (options[from] && !options[to]) {
options[to] = [options[from]];
Expand Down Expand Up @@ -80,7 +80,7 @@ export class EntitySchema<T extends AnyEntity<T> = AnyEntity, U extends AnyEntit
this._meta.properties[name] = prop;
}

addEnum(name: string & keyof T, type?: TypeType, options: EnumOptions = {}): void {
addEnum(name: string & keyof T, type?: TypeType, options: EnumOptions<T> = {}): void {
if (options.items instanceof Function) {
options.items = Utils.extractEnumValues(options.items());
}
Expand All @@ -89,15 +89,15 @@ export class EntitySchema<T extends AnyEntity<T> = AnyEntity, U extends AnyEntit
this.addProperty(name, this.internal ? type : type || 'enum', prop);
}

addVersion(name: string & keyof T, type: TypeType, options: PropertyOptions = {}): void {
addVersion(name: string & keyof T, type: TypeType, options: PropertyOptions<T> = {}): void {
this.addProperty(name, type, { version: true, ...options });
}

addPrimaryKey(name: string & keyof T, type: TypeType, options: PrimaryKeyOptions = {}): void {
addPrimaryKey(name: string & keyof T, type: TypeType, options: PrimaryKeyOptions<T> = {}): void {
this.addProperty(name, type, { primary: true, ...options });
}

addSerializedPrimaryKey(name: string & keyof T, type: TypeType, options: SerializedPrimaryKeyOptions = {}): void {
addSerializedPrimaryKey(name: string & keyof T, type: TypeType, options: SerializedPrimaryKeyOptions<T> = {}): void {
this._meta.serializedPrimaryKey = name;
this.addProperty(name, type, options);
}
Expand All @@ -112,8 +112,8 @@ export class EntitySchema<T extends AnyEntity<T> = AnyEntity, U extends AnyEntit
} as EntityProperty<T>;
}

addManyToOne<K = object>(name: string & keyof T, type: TypeType, options: ManyToOneOptions<K>): void {
const prop = { reference: ReferenceType.MANY_TO_ONE, cascade: [Cascade.PERSIST, Cascade.MERGE], ...options };
addManyToOne<K = object>(name: string & keyof T, type: TypeType, options: ManyToOneOptions<K, T>): void {
const prop = { reference: ReferenceType.MANY_TO_ONE, cascade: [Cascade.PERSIST, Cascade.MERGE], ...options } as unknown as EntityProperty<T>;
Utils.defaultValue(prop, 'nullable', prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL));

if (prop.joinColumns && !prop.fieldNames) {
Expand All @@ -127,7 +127,7 @@ export class EntitySchema<T extends AnyEntity<T> = AnyEntity, U extends AnyEntit
this.addProperty(name, type, prop);
}

addManyToMany<K = object>(name: string & keyof T, type: TypeType, options: ManyToManyOptions<K>): void {
addManyToMany<K = object>(name: string & keyof T, type: TypeType, options: ManyToManyOptions<K, T>): void {
options.fixedOrder = options.fixedOrder || !!options.fixedOrderColumn;

if (!options.owner && !options.mappedBy) {
Expand All @@ -138,17 +138,17 @@ export class EntitySchema<T extends AnyEntity<T> = AnyEntity, U extends AnyEntit
Utils.renameKey(options, 'mappedBy', 'inversedBy');
}

const prop = { reference: ReferenceType.MANY_TO_MANY, cascade: [Cascade.PERSIST, Cascade.MERGE], ...options };
const prop = { reference: ReferenceType.MANY_TO_MANY, cascade: [Cascade.PERSIST, Cascade.MERGE], ...options } as PropertyOptions<T>;
this.addProperty(name, type, prop);
}

addOneToMany<K = object>(name: string & keyof T, type: TypeType, options: OneToManyOptions<K>): void {
const prop = { reference: ReferenceType.ONE_TO_MANY, cascade: [Cascade.PERSIST, Cascade.MERGE], ...options };
addOneToMany<K = object>(name: string & keyof T, type: TypeType, options: OneToManyOptions<K, T>): void {
const prop = { reference: ReferenceType.ONE_TO_MANY, cascade: [Cascade.PERSIST, Cascade.MERGE], ...options } as PropertyOptions<T>;
this.addProperty(name, type, prop);
}

addOneToOne<K = object>(name: string & keyof T, type: TypeType, options: OneToOneOptions<K>): void {
const prop = { reference: ReferenceType.ONE_TO_ONE, cascade: [Cascade.PERSIST, Cascade.MERGE], ...options };
addOneToOne<K = object>(name: string & keyof T, type: TypeType, options: OneToOneOptions<K, T>): void {
const prop = { reference: ReferenceType.ONE_TO_ONE, cascade: [Cascade.PERSIST, Cascade.MERGE], ...options } as unknown as EntityProperty<T>;
Utils.defaultValue(prop, 'nullable', prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL));
Utils.defaultValue(prop, 'owner', !!prop.inversedBy || !prop.mappedBy);
Utils.defaultValue(prop, 'unique', prop.owner);
Expand Down Expand Up @@ -230,15 +230,15 @@ export class EntitySchema<T extends AnyEntity<T> = AnyEntity, U extends AnyEntit
}

private initProperties(): void {
Object.entries<Property<T[keyof T]>>(this._meta.properties).forEach(([name, options]) => {
Object.entries<Property<T, unknown>>(this._meta.properties as Dictionary).forEach(([name, options]) => {
options.type = 'customType' in options ? options.customType!.constructor.name : options.type;

switch ((options as EntityProperty).reference) {
case ReferenceType.ONE_TO_ONE:
this.addOneToOne(name as keyof T & string, options.type as string, options);
break;
case ReferenceType.ONE_TO_MANY:
this.addOneToMany(name as keyof T & string, options.type as string, options as OneToManyOptions<T>);
this.addOneToMany(name as keyof T & string, options.type as string, options as OneToManyOptions<T, AnyEntity>);
break;
case ReferenceType.MANY_TO_ONE:
this.addManyToOne(name as keyof T & string, options.type as string, options);
Expand Down Expand Up @@ -285,7 +285,7 @@ export class EntitySchema<T extends AnyEntity<T> = AnyEntity, U extends AnyEntit
}
}

private normalizeType(options: PropertyOptions | EntityProperty, type?: string | any | Constructor<Type>) {
private normalizeType(options: PropertyOptions<T> | EntityProperty, type?: string | any | Constructor<Type>) {
if ('entity' in options) {
if (Utils.isString(options.entity)) {
type = options.type = options.entity;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/typings.ts
Expand Up @@ -131,8 +131,8 @@ export interface EntityProperty<T extends AnyEntity<T> = any> {
getterName?: keyof T;
cascade: Cascade[];
orphanRemoval?: boolean;
onCreate?: () => any;
onUpdate?: () => any;
onCreate?: (entity: T) => any;
onUpdate?: (entity: T) => any;
onDelete?: 'cascade' | 'no action' | 'set null' | 'set default' | string;
onUpdateIntegrity?: 'cascade' | 'no action' | 'set null' | 'set default' | string;
owner: boolean;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/unit-of-work/ChangeSetPersister.ts
Expand Up @@ -88,15 +88,15 @@ export class ChangeSetPersister {
}

if (prop.onCreate && changeSet.type === ChangeSetType.CREATE) {
changeSet.entity[prop.name] = changeSet.payload[prop.name] = prop.onCreate();
changeSet.entity[prop.name] = changeSet.payload[prop.name] = prop.onCreate(changeSet.entity);

if (prop.primary) {
this.mapPrimaryKey(changeSet.entity.__meta, changeSet.entity[prop.name] as unknown as IPrimaryKey, changeSet);
}
}

if (prop.onUpdate && changeSet.type === ChangeSetType.UPDATE) {
changeSet.entity[prop.name] = changeSet.payload[prop.name] = prop.onUpdate();
changeSet.entity[prop.name] = changeSet.payload[prop.name] = prop.onUpdate(changeSet.entity);
}
}

Expand Down

0 comments on commit 3044a19

Please sign in to comment.