implemented entity listeners and refactored orm broadcaster

This commit is contained in:
Umed Khudoiberdiev 2016-03-22 22:27:23 +05:00
parent bc3cfe8aea
commit 2e99b1a4b8
28 changed files with 625 additions and 84 deletions

View File

@ -9,5 +9,9 @@ export class PostCategory {
@Column()
name: string;
constructor(name?: string) {
this.name = name;
}
}

View File

@ -62,7 +62,7 @@ createMysqlConnection(options, [__dirname + "/entity"], [__dirname + "/subscribe
})
.then(loadedPost => {
console.log("---------------------------");
console.log("load removed.");
console.log("post removed.");
})
.catch(error => console.log("Cannot save. Error: ", error.stack ? error.stack : error));

View File

@ -1,4 +1,4 @@
import {OrmEventSubscriber} from "../../../src/decorator/OrmEventSubscriber";
import {OrmEventSubscriber} from "../../../src/decorator/listeners/OrmEventSubscriber";
import {OrmSubscriber} from "../../../src/subscriber/OrmSubscriber";
import {InsertEvent} from "../../../src/subscriber/event/InsertEvent";
import {RemoveEvent} from "../../../src/subscriber/event/RemoveEvent";

View File

@ -0,0 +1,70 @@
import {createMysqlConnection} from "../../src/typeorm";
import {Post} from "./entity/Post";
import {PostCategory} from "./entity/PostCategory";
import {PostAuthor} from "./entity/PostAuthor";
// first create a connection
let options = {
host: "192.168.99.100",
port: 3306,
username: "root",
password: "admin",
database: "test",
autoSchemaCreate: true
};
createMysqlConnection(options, [__dirname + "/entity"], [__dirname + "/subscriber"]).then(connection => {
let category1 = new PostCategory();
category1.name = "post category #1";
let category2 = new PostCategory();
category2.name = "post category #2";
let author = new PostAuthor();
author.name = "Umed";
let post = new Post();
post.text = "Hello how are you?";
post.title = "hello";
post.categories.push(category1, category2);
post.author = author;
let postRepository = connection.getRepository(Post);
postRepository
.persist(post)
.then(post => {
console.log("Post has been saved");
console.log("---------------------------");
return postRepository.findById(post.id);
})
.then(loadedPost => {
console.log("post is loaded. Its uid is " + loadedPost.uid);
console.log("Lets now load it with relations.");
console.log("---------------------------");
return postRepository
.createQueryBuilder("p")
.leftJoinAndSelect("p.author", "author")
.leftJoinAndSelect("p.categories", "categories")
.where("p.id = :id", { id: loadedPost.id })
.getSingleResult();
})
.then(loadedPost => {
console.log("load finished. Now lets update entity");
console.log("---------------------------");
loadedPost.text = "post updated";
loadedPost.author.name = "Bakha";
return postRepository.persist(loadedPost);
})
.then(loadedPost => {
console.log("update finished. Now lets remove entity");
console.log("---------------------------");
return postRepository.remove(loadedPost);
})
.then(loadedPost => {
console.log("post removed.");
})
.catch(error => console.log("Cannot save. Error: ", error.stack ? error.stack : error));
}, error => console.log("Cannot connect: ", error.stack ? error.stack : error));

View File

@ -0,0 +1,78 @@
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
import {Table} from "../../../src/decorator/Tables";
import {ManyToMany} from "../../../src/decorator/Relations";
import {PostCategory} from "./PostCategory";
import {PostAuthor} from "./PostAuthor";
import {ManyToOne} from "../../../src/decorator/relations/ManyToOne";
import {AfterLoad} from "../../../src/decorator/listeners/AfterLoad";
import {AfterInsert} from "../../../src/decorator/listeners/AfterInsert";
import {BeforeInsert} from "../../../src/decorator/listeners/BeforeInsert";
import {BeforeUpdate} from "../../../src/decorator/listeners/BeforeUpdate";
import {AfterUpdate} from "../../../src/decorator/listeners/AfterUpdate";
import {BeforeRemove} from "../../../src/decorator/listeners/BeforeRemove";
import {AfterRemove} from "../../../src/decorator/listeners/AfterRemove";
@Table("sample9_post")
export class Post {
@PrimaryColumn("int", { autoIncrement: true })
id: number;
@Column()
title: string;
@Column()
text: string;
@ManyToOne(type => PostAuthor, post => post.posts, {
cascadeInsert: true,
cascadeUpdate: true
})
author: PostAuthor;
@ManyToMany(type => PostCategory, category => category.posts, {
cascadeInsert: true,
cascadeUpdate: true,
cascadeRemove: true
})
categories: PostCategory[] = [];
uid: number;
@AfterLoad()
generateRandomNumbers() {
console.log(`event: Post "${this.title}" entity has been loaded and callback executed`);
this.uid = Math.ceil(Math.random() * 1000);
}
@BeforeInsert()
doSomethingBeforeInsertion() {
console.log("event: Post entity will be inserted so soon...");
}
@AfterInsert()
doSomethingAfterInsertion() {
console.log("event: Post entity has been inserted and callback executed");
}
@BeforeUpdate()
doSomethingBeforeUpdate() {
console.log("event: Post entity will be updated so soon...");
}
@AfterUpdate()
doSomethingAfterUpdate() {
console.log("event: Post entity has been updated and callback executed");
}
@BeforeRemove()
doSomethingBeforeRemove() {
console.log("event: Post entity will be removed so soon...");
}
@AfterRemove()
doSomethingAfterRemove() {
console.log("event: Post entity has been removed and callback executed");
}
}

View File

@ -0,0 +1,54 @@
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
import {Table} from "../../../src/decorator/Tables";
import {Post} from "./Post";
import {OneToMany} from "../../../src/decorator/relations/OneToMany";
import {AfterRemove} from "../../../src/decorator/listeners/AfterRemove";
import {BeforeRemove} from "../../../src/decorator/listeners/BeforeRemove";
import {AfterUpdate} from "../../../src/decorator/listeners/AfterUpdate";
import {BeforeUpdate} from "../../../src/decorator/listeners/BeforeUpdate";
import {AfterInsert} from "../../../src/decorator/listeners/AfterInsert";
import {BeforeInsert} from "../../../src/decorator/listeners/BeforeInsert";
@Table("sample9_post_author")
export class PostAuthor {
@PrimaryColumn("int", { autoIncrement: true })
id: number;
@Column()
name: string;
@OneToMany(type => Post, post => post.author)
posts: Post[];
@BeforeInsert()
doSomethingBeforeInsertion() {
console.log("event: PostAuthor entity will be inserted so soon...");
}
@AfterInsert()
doSomethingAfterInsertion() {
console.log("event: PostAuthor entity has been inserted and callback executed");
}
@BeforeUpdate()
doSomethingBeforeUpdate() {
console.log("event: PostAuthor entity will be updated so soon...");
}
@AfterUpdate()
doSomethingAfterUpdate() {
console.log("event: PostAuthor entity has been updated and callback executed");
}
@BeforeRemove()
doSomethingBeforeRemove() {
console.log("event: PostAuthor entity will be removed so soon...");
}
@AfterRemove()
doSomethingAfterRemove() {
console.log("event: PostAuthor entity has been removed and callback executed");
}
}

View File

@ -0,0 +1,58 @@
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
import {Table} from "../../../src/decorator/Tables";
import {Post} from "./Post";
import {ManyToManyInverse} from "../../../src/decorator/relations/ManyToManyInverse";
import {AfterRemove} from "../../../src/decorator/listeners/AfterRemove";
import {BeforeRemove} from "../../../src/decorator/listeners/BeforeRemove";
import {AfterUpdate} from "../../../src/decorator/listeners/AfterUpdate";
import {BeforeUpdate} from "../../../src/decorator/listeners/BeforeUpdate";
import {AfterInsert} from "../../../src/decorator/listeners/AfterInsert";
import {BeforeInsert} from "../../../src/decorator/listeners/BeforeInsert";
@Table("sample9_post_category")
export class PostCategory {
@PrimaryColumn("int", { autoIncrement: true })
id: number;
@Column()
name: string;
@ManyToManyInverse(type => Post, post => post.categories, {
cascadeInsert: true,
cascadeUpdate: true,
cascadeRemove: true
})
posts: Post[] = [];
@BeforeInsert()
doSomethingBeforeInsertion() {
console.log(`event: PostCategory "${this.name}" will be inserted so soon...`);
}
@AfterInsert()
doSomethingAfterInsertion() {
console.log(`event: PostCategory "${this.name}" has been inserted and callback executed`);
}
@BeforeUpdate()
doSomethingBeforeUpdate() {
console.log(`event: PostCategory "${this.name}" will be updated so soon...`);
}
@AfterUpdate()
doSomethingAfterUpdate() {
console.log(`event: PostCategory "${this.name}" has been updated and callback executed`);
}
@BeforeRemove()
doSomethingBeforeRemove() {
console.log(`event: PostCategory "${this.name}" will be removed so soon...`);
}
@AfterRemove()
doSomethingAfterRemove() {
console.log(`event: PostCategory "${this.name}" has been removed and callback executed`);
}
}

View File

@ -3,11 +3,11 @@ import {ConnectionOptions} from "./ConnectionOptions";
import {Repository} from "../repository/Repository";
import {OrmSubscriber} from "../subscriber/OrmSubscriber";
import {RepositoryNotFoundError} from "./error/RepositoryNotFoundError";
import {BroadcasterNotFoundError} from "./error/BroadcasterNotFoundError";
import {EntityMetadata} from "../metadata-builder/metadata/EntityMetadata";
import {SchemaCreator} from "../schema-creator/SchemaCreator";
import {MetadataNotFoundError} from "./error/MetadataNotFoundError";
import {ConstructorFunction} from "../common/ConstructorFunction";
import {EntityListenerMetadata} from "../metadata-builder/metadata/EntityListenerMetadata";
interface RepositoryAndMetadata {
repository: Repository<any>;
@ -25,7 +25,8 @@ export class Connection {
private _name: string;
private _driver: Driver;
private _metadatas: EntityMetadata[] = [];
private _entityMetadatas: EntityMetadata[] = [];
private _entityListenerMetadatas: EntityListenerMetadata[] = [];
private _subscribers: OrmSubscriber<any>[] = [];
private repositoryAndMetadatas: RepositoryAndMetadata[] = [];
private _options: ConnectionOptions;
@ -66,10 +67,17 @@ export class Connection {
}
/**
* All metadatas that are registered for this connection.
* All entity metadatas that are registered for this connection.
*/
get metadatas(): EntityMetadata[] {
return this._metadatas;
get entityMetadatas(): EntityMetadata[] {
return this._entityMetadatas;
}
/**
* All entity listener metadatas that are registered for this connection.
*/
get entityListeners(): EntityListenerMetadata[] {
return this._entityListenerMetadatas;
}
/**
@ -114,9 +122,16 @@ export class Connection {
/**
* Registers entity metadatas for the current connection.
*/
addMetadatas(metadatas: EntityMetadata[]) {
this._metadatas = this._metadatas.concat(metadatas);
this.repositoryAndMetadatas = this.repositoryAndMetadatas.concat(metadatas.map(metadata => this.createRepoMeta(metadata)));
addEntityMetadatas(metadatas: EntityMetadata[]) {
this._entityMetadatas = this._entityMetadatas.concat(metadatas);
this.repositoryAndMetadatas = this.repositoryAndMetadatas.concat(metadatas.map(metadata => this.createRepoMeta(metadata)));
}
/**
* Registers entity listener metadatas for the current connection.
*/
addEntityListenerMetadatas(metadatas: EntityListenerMetadata[]) {
this._entityListenerMetadatas = this._entityListenerMetadatas.concat(metadatas);
}
/**
@ -130,7 +145,7 @@ export class Connection {
* Gets repository for the given entity class.
*/
getRepository<Entity>(entityClass: ConstructorFunction<Entity>|Function): Repository<Entity> {
const metadata = this.getMetadata(entityClass);
const metadata = this.getEntityMetadata(entityClass);
const repoMeta = this.repositoryAndMetadatas.find(repoMeta => repoMeta.metadata === metadata);
if (!repoMeta)
throw new RepositoryNotFoundError(entityClass);
@ -141,8 +156,8 @@ export class Connection {
/**
* Gets the entity metadata for the given entity class.
*/
getMetadata(entityClass: Function): EntityMetadata {
const metadata = this.metadatas.find(metadata => metadata.target === entityClass);
getEntityMetadata(entityClass: Function): EntityMetadata {
const metadata = this.entityMetadatas.find(metadata => metadata.target === entityClass);
if (!metadata)
throw new MetadataNotFoundError(entityClass);

View File

@ -122,8 +122,11 @@ export class ConnectionManager {
entities = <Function[]> connectionNameOrEntities;
}
const metadatas = this.entityMetadataBuilder.build(entities);
this.getConnection(connectionName).addMetadatas(metadatas);
const entityMetadatas = this.entityMetadataBuilder.build(entities);
const entityListenerMetadatas = defaultMetadataStorage.findEntityListenersForClasses(entities);
this.getConnection(connectionName).addEntityMetadatas(entityMetadatas);
this.getConnection(connectionName).addEntityListenerMetadatas(entityListenerMetadatas);
}
// -------------------------------------------------------------------------

View File

@ -0,0 +1,7 @@
export * from "./listeners/OrmEventSubscriber";
export * from "./listeners/AfterInsert";
export * from "./listeners/BeforeInsert";
export * from "./listeners/AfterUpdate";
export * from "./listeners/BeforeUpdate";
export * from "./listeners/AfterRemove";
export * from "./listeners/BeforeRemove";

View File

@ -0,0 +1,16 @@
import {defaultMetadataStorage} from "../../metadata-builder/MetadataStorage";
import {EventListenerTypes} from "../../metadata-builder/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata-builder/metadata/EntityListenerMetadata";
/**
* Calls a method on which this decorator is applied after this entity insertion.
*/
export function AfterInsert() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage.addEntityListenerMetadata(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.AFTER_INSERT
));
};
}

View File

@ -0,0 +1,16 @@
import {defaultMetadataStorage} from "../../metadata-builder/MetadataStorage";
import {EventListenerTypes} from "../../metadata-builder/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata-builder/metadata/EntityListenerMetadata";
/**
* Calls a method on which this decorator is applied after entity is loaded.
*/
export function AfterLoad() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage.addEntityListenerMetadata(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.AFTER_LOAD
));
};
}

View File

@ -0,0 +1,16 @@
import {defaultMetadataStorage} from "../../metadata-builder/MetadataStorage";
import {EventListenerTypes} from "../../metadata-builder/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata-builder/metadata/EntityListenerMetadata";
/**
* Calls a method on which this decorator is applied after this entity removal.
*/
export function AfterRemove() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage.addEntityListenerMetadata(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.AFTER_REMOVE
));
};
}

View File

@ -0,0 +1,16 @@
import {defaultMetadataStorage} from "../../metadata-builder/MetadataStorage";
import {EventListenerTypes} from "../../metadata-builder/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata-builder/metadata/EntityListenerMetadata";
/**
* Calls a method on which this decorator is applied after this entity update.
*/
export function AfterUpdate() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage.addEntityListenerMetadata(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.AFTER_UPDATE
));
};
}

View File

@ -0,0 +1,16 @@
import {defaultMetadataStorage} from "../../metadata-builder/MetadataStorage";
import {EventListenerTypes} from "../../metadata-builder/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata-builder/metadata/EntityListenerMetadata";
/**
* Calls a method on which this decorator is applied before this entity insertion.
*/
export function BeforeInsert() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage.addEntityListenerMetadata(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.BEFORE_INSERT
));
};
}

View File

@ -0,0 +1,16 @@
import {defaultMetadataStorage} from "../../metadata-builder/MetadataStorage";
import {EventListenerTypes} from "../../metadata-builder/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata-builder/metadata/EntityListenerMetadata";
/**
* Calls a method on which this decorator is applied before this entity removal.
*/
export function BeforeRemove() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage.addEntityListenerMetadata(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.BEFORE_REMOVE
));
};
}

View File

@ -0,0 +1,16 @@
import {defaultMetadataStorage} from "../../metadata-builder/MetadataStorage";
import {EventListenerTypes} from "../../metadata-builder/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata-builder/metadata/EntityListenerMetadata";
/**
* Calls a method on which this decorator is applied before this entity update.
*/
export function BeforeUpdate() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage.addEntityListenerMetadata(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.BEFORE_UPDATE
));
};
}

View File

@ -1,5 +1,5 @@
import {defaultMetadataStorage} from "../metadata-builder/MetadataStorage";
import {OrmEventSubscriberMetadata} from "../metadata-builder/metadata/OrmEventSubscriberMetadata";
import {defaultMetadataStorage} from "../../metadata-builder/MetadataStorage";
import {OrmEventSubscriberMetadata} from "../../metadata-builder/metadata/OrmEventSubscriberMetadata";
/**
* Subscribers that gonna listen to ORM events must be decorated with this decorator.

View File

@ -6,6 +6,7 @@ import {IndexMetadata} from "./metadata/IndexMetadata";
import {CompoundIndexMetadata} from "./metadata/CompoundIndexMetadata";
import {ColumnMetadata} from "./metadata/ColumnMetadata";
import {OrmEventSubscriberMetadata} from "./metadata/OrmEventSubscriberMetadata";
import {EntityListenerMetadata} from "./metadata/EntityListenerMetadata";
/**
* Storage all metadatas of all available types: tables, fields, subscribers, relations, etc.
@ -21,6 +22,7 @@ export class MetadataStorage {
private _ormEventSubscriberMetadatas: OrmEventSubscriberMetadata[] = [];
private _columnMetadatas: ColumnMetadata[] = [];
private _indexMetadatas: IndexMetadata[] = [];
private _entityListenerMetadatas: EntityListenerMetadata[] = [];
private _compoundIndexMetadatas: CompoundIndexMetadata[] = [];
private _relationMetadatas: RelationMetadata[] = [];
@ -44,6 +46,10 @@ export class MetadataStorage {
return this._indexMetadatas;
}
get entityListenerMetadatas(): EntityListenerMetadata[] {
return this._entityListenerMetadatas;
}
get compoundIndexMetadatas(): CompoundIndexMetadata[] {
return this._compoundIndexMetadatas;
}
@ -110,6 +116,13 @@ export class MetadataStorage {
this.compoundIndexMetadatas.push(metadata);
}
addEntityListenerMetadata(metadata: EntityListenerMetadata) {
if (this.hasFieldMetadataOnProperty(metadata.target, metadata.propertyName))
throw new MetadataAlreadyExistsError("EventListener", metadata.target);
this.entityListenerMetadatas.push(metadata);
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
@ -118,6 +131,10 @@ export class MetadataStorage {
return this.ormEventSubscriberMetadatas.filter(metadata => classes.indexOf(metadata.target) !== -1);
}
findEntityListenersForClasses(classes: Function[]): EntityListenerMetadata[] {
return this.entityListenerMetadatas.filter(metadata => classes.indexOf(metadata.target) !== -1);
}
findTableMetadatasForClasses(classes: Function[]): TableMetadata[] {
return this.tableMetadatas.filter(metadata => classes.indexOf(metadata.target) !== -1 && !metadata.isAbstract);
}

View File

@ -0,0 +1,24 @@
import {PropertyMetadata} from "./PropertyMetadata";
import {EventListenerType} from "../types/EventListenerTypes";
/**
* This metadata interface contains all information about some index on a field.
*/
export class EntityListenerMetadata extends PropertyMetadata {
/**
* The type of the listener.
*/
type: EventListenerType;
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(target: Function, propertyName: string, type: EventListenerType) {
super(target, propertyName);
this.type = type;
}
}

View File

@ -0,0 +1,17 @@
/**
* All types that entity listener can be.
*/
export type EventListenerType = "after-load"|"before-insert"|"after-insert"|"before-update"|"after-update"|"before-remove"|"after-remove";
/**
* Provides a constants for each entity listener type.
*/
export class EventListenerTypes {
static AFTER_LOAD: EventListenerType = "after-load";
static BEFORE_INSERT: EventListenerType = "before-insert";
static AFTER_INSERT: EventListenerType = "after-insert";
static BEFORE_UPDATE: EventListenerType = "before-update";
static AFTER_UPDATE: EventListenerType = "after-update";
static BEFORE_REMOVE: EventListenerType = "before-remove";
static AFTER_REMOVE: EventListenerType = "after-remove";
}

View File

@ -108,7 +108,7 @@ export class EntityPersistOperationBuilder {
dbEntities: EntityWithId[],
fromRelation: RelationMetadata,
operations: InsertOperation[] = []): InsertOperation[] {
const metadata = this.connection.getMetadata(newEntity.constructor);
const metadata = this.connection.getEntityMetadata(newEntity.constructor);
const isObjectNew = !this.findEntityWithId(dbEntities, metadata.target, newEntity[metadata.primaryColumn.name]);
// if object is new and should be inserted, we check if cascades are allowed before add it to operations list
@ -188,15 +188,15 @@ export class EntityPersistOperationBuilder {
if (!dbEntity)
return operations;
const relationId = dbEntity[metadata.primaryColumn.name];
const isObjectRemoved = parentAlreadyRemoved || !this.findEntityWithId(allPersistedEntities, metadata.target, relationId);
const entityId = dbEntity[metadata.primaryColumn.name];
const isObjectRemoved = parentAlreadyRemoved || !this.findEntityWithId(allPersistedEntities, metadata.target, entityId);
// if object is removed and should be removed, we check if cascades are allowed before add it to operations list
if (isObjectRemoved && !this.checkCascadesAllowed("remove", metadata, fromRelation)) {
return operations; // looks like object is removed here, but cascades are not allowed - then we should stop iteration
} else if (isObjectRemoved) { // object is remove and cascades are allow here
operations.push(new RemoveOperation(dbEntity, relationId, fromMetadata, fromRelation, fromEntityId));
operations.push(new RemoveOperation(dbEntity, entityId, fromMetadata, fromRelation, fromEntityId));
}
metadata.relations
@ -233,7 +233,7 @@ export class EntityPersistOperationBuilder {
}
private findRelationsWithEntityInside(insertOperation: InsertOperation, entityToSearchIn: any) {
const metadata = this.connection.getMetadata(entityToSearchIn.constructor);
const metadata = this.connection.getEntityMetadata(entityToSearchIn.constructor);
return metadata.relations.reduce((operations, relation) => {
const value = entityToSearchIn[relation.propertyName];

View File

@ -29,6 +29,7 @@ export class PersistOperationExecutor {
*/
executePersistOperation(persistOperation: PersistOperation) {
const broadcaster = new OrmBroadcaster(this.connection);
// persistOperation.log();
return Promise.resolve()
.then(() => this.broadcastBeforeEvents(broadcaster, persistOperation))
@ -56,16 +57,17 @@ export class PersistOperationExecutor {
private broadcastBeforeEvents(broadcaster: OrmBroadcaster, persistOperation: PersistOperation) {
const insertEvents = persistOperation.inserts.map(insertOperation => {
const persistedEntity = persistOperation.allPersistedEntities.find(e => e.id === insertOperation.entityId);
return broadcaster.broadcastBeforeInsertEvent(persistedEntity);
const persistedEntityWithId = persistOperation.allPersistedEntities.find(e => e.entity === insertOperation.entity);
return broadcaster.broadcastBeforeInsertEvent(persistedEntityWithId.entity);
});
const updateEvents = persistOperation.updates.map(updateOperation => {
const persistedEntity = persistOperation.allPersistedEntities.find(e => e.id === updateOperation.entityId);
return broadcaster.broadcastBeforeUpdateEvent(persistedEntity, updateOperation.columns);
const persistedEntityWithId = persistOperation.allPersistedEntities.find(e => e.entity === updateOperation.entity);
return broadcaster.broadcastBeforeUpdateEvent(persistedEntityWithId.entity, updateOperation.columns);
});
const removeEvents = persistOperation.removes.map(removeOperation => {
const persistedEntity = persistOperation.allPersistedEntities.find(e => e.id === removeOperation.entityId);
return broadcaster.broadcastBeforeRemoveEvent(persistedEntity, removeOperation.entityId);
// we can send here only dbEntity, not entity from the persisted object, since entity from the persisted
// object does not exist anymore - its removed, and there is no way to find this removed object
return broadcaster.broadcastBeforeRemoveEvent(removeOperation.entity, removeOperation.entityId);
});
return Promise.all(insertEvents)
@ -79,16 +81,17 @@ export class PersistOperationExecutor {
private broadcastAfterEvents(broadcaster: OrmBroadcaster, persistOperation: PersistOperation) {
const insertEvents = persistOperation.inserts.map(insertOperation => {
const persistedEntity = persistOperation.allPersistedEntities.find(e => e.id === insertOperation.entityId);
return broadcaster.broadcastAfterInsertEvent(persistedEntity);
const persistedEntity = persistOperation.allPersistedEntities.find(e => e.entity === insertOperation.entity);
return broadcaster.broadcastAfterInsertEvent(persistedEntity.entity);
});
const updateEvents = persistOperation.updates.map(updateOperation => {
const persistedEntity = persistOperation.allPersistedEntities.find(e => e.id === updateOperation.entityId);
return broadcaster.broadcastAfterUpdateEvent(persistedEntity, updateOperation.columns);
const persistedEntityWithId = persistOperation.allPersistedEntities.find(e => e.entity === updateOperation.entity);
return broadcaster.broadcastAfterUpdateEvent(persistedEntityWithId.entity, updateOperation.columns);
});
const removeEvents = persistOperation.removes.map(removeOperation => {
const persistedEntity = persistOperation.allPersistedEntities.find(e => e.id === removeOperation.entityId);
return broadcaster.broadcastAfterRemoveEvent(persistedEntity, removeOperation.entityId);
// we can send here only dbEntity, not entity from the persisted object, since entity from the persisted
// object does not exist anymore - its removed, and there is no way to find this removed object
return broadcaster.broadcastAfterRemoveEvent(removeOperation.entity, removeOperation.entityId);
});
return Promise.all(insertEvents)
@ -169,7 +172,7 @@ export class PersistOperationExecutor {
*/
private updateIdsOfInsertedEntities(persistOperation: PersistOperation) {
persistOperation.inserts.forEach(insertOperation => {
const metadata = this.connection.getMetadata(insertOperation.entity.constructor);
const metadata = this.connection.getEntityMetadata(insertOperation.entity.constructor);
insertOperation.entity[metadata.primaryColumn.name] = insertOperation.entityId;
});
}
@ -179,7 +182,7 @@ export class PersistOperationExecutor {
*/
private updateIdsOfRemovedEntities(persistOperation: PersistOperation) {
persistOperation.removes.forEach(removeOperation => {
const metadata = this.connection.getMetadata(removeOperation.entity.constructor);
const metadata = this.connection.getEntityMetadata(removeOperation.entity.constructor);
const removedEntity = persistOperation.allPersistedEntities.find(allNewEntity => {
return allNewEntity.entity.constructor === removeOperation.entity.constructor && allNewEntity.id === removeOperation.entity[metadata.primaryColumn.name];
});
@ -192,7 +195,7 @@ export class PersistOperationExecutor {
let tableName: string, relationName: string, relationId: any, idColumn: string, id: any;
const idInInserts = insertOperations.find(o => o.entity === operation.targetEntity).entityId;
if (operation.updatedRelation.isOneToMany) {
const metadata = this.connection.getMetadata(operation.insertOperation.entity.constructor);
const metadata = this.connection.getEntityMetadata(operation.insertOperation.entity.constructor);
tableName = metadata.table.name;
relationName = operation.updatedRelation.inverseRelation.name;
relationId = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts;
@ -200,7 +203,7 @@ export class PersistOperationExecutor {
id = operation.insertOperation.entityId;
} else {
const metadata = this.connection.getMetadata(operation.targetEntity.constructor);
const metadata = this.connection.getEntityMetadata(operation.targetEntity.constructor);
tableName = metadata.table.name;
relationName = operation.updatedRelation.name;
relationId = operation.insertOperation.entityId;
@ -212,7 +215,7 @@ export class PersistOperationExecutor {
private update(updateOperation: UpdateOperation) {
const entity = updateOperation.entity;
const metadata = this.connection.getMetadata(entity.constructor);
const metadata = this.connection.getEntityMetadata(entity.constructor);
const values = updateOperation.columns.reduce((object, column) => {
(<any> object)[column.name] = entity[column.propertyName];
return object;
@ -229,12 +232,12 @@ export class PersistOperationExecutor {
}
private delete(entity: any) {
const metadata = this.connection.getMetadata(entity.constructor);
const metadata = this.connection.getEntityMetadata(entity.constructor);
return this.connection.driver.delete(metadata.table.name, { [metadata.primaryColumn.name]: entity[metadata.primaryColumn.propertyName] });
}
private insert(entity: any) {
const metadata = this.connection.getMetadata(entity.constructor);
const metadata = this.connection.getEntityMetadata(entity.constructor);
const columns = metadata.columns
.filter(column => !column.isVirtual)
.filter(column => entity.hasOwnProperty(column.propertyName))
@ -263,8 +266,8 @@ export class PersistOperationExecutor {
private insertJunctions(junctionOperation: JunctionInsertOperation, insertOperations: InsertOperation[]) {
const junctionMetadata = junctionOperation.metadata;
const metadata1 = this.connection.getMetadata(junctionOperation.entity1.constructor);
const metadata2 = this.connection.getMetadata(junctionOperation.entity2.constructor);
const metadata1 = this.connection.getEntityMetadata(junctionOperation.entity1.constructor);
const metadata2 = this.connection.getEntityMetadata(junctionOperation.entity2.constructor);
const columns = junctionMetadata.columns.map(column => column.name);
const id1 = junctionOperation.entity1[metadata1.primaryColumn.name] || insertOperations.find(o => o.entity === junctionOperation.entity1).entityId;
const id2 = junctionOperation.entity2[metadata2.primaryColumn.name] || insertOperations.find(o => o.entity === junctionOperation.entity2).entityId;
@ -274,8 +277,8 @@ export class PersistOperationExecutor {
private removeJunctions(junctionOperation: JunctionRemoveOperation) {
const junctionMetadata = junctionOperation.metadata;
const metadata1 = this.connection.getMetadata(junctionOperation.entity1.constructor);
const metadata2 = this.connection.getMetadata(junctionOperation.entity2.constructor);
const metadata1 = this.connection.getEntityMetadata(junctionOperation.entity1.constructor);
const metadata2 = this.connection.getEntityMetadata(junctionOperation.entity2.constructor);
const columns = junctionMetadata.columns.map(column => column.name);
const id1 = junctionOperation.entity1[metadata1.primaryColumn.name];
const id2 = junctionOperation.entity2[metadata2.primaryColumn.name];

View File

@ -41,7 +41,7 @@ export class QueryBuilder<Entity> {
// -------------------------------------------------------------------------
constructor(private connection: Connection) {
this._aliasMap = new AliasMap(connection.metadatas);
this._aliasMap = new AliasMap(connection.entityMetadatas);
this.broadcaster = new OrmBroadcaster(connection);
}
@ -301,7 +301,7 @@ export class QueryBuilder<Entity> {
getResults(): Promise<Entity[]> {
const mainAlias = this.aliasMap.mainAlias.name;
if (this.firstResult || this.maxResults) {
const metadata = this.connection.getMetadata(this.fromEntity.alias.target);
const metadata = this.connection.getEntityMetadata(this.fromEntity.alias.target);
const idsQuery = this.clone()
.select(`DISTINCT(${mainAlias}.${metadata.primaryColumn.name}) as ids`)
.setOffset(this.firstResult)
@ -317,19 +317,13 @@ export class QueryBuilder<Entity> {
return this.connection.driver.query<any[]>(queryWithIds);
})
.then(results => this.rawResultsToEntities(results))
.then(results => {
this.broadcaster.broadcastLoadEventsForAll(results);
return results;
});
.then(results => this.broadcaster.broadcastLoadEventsForAll(results).then(() => results));
} else {
return this.connection.driver
.query<any[]>(this.getSql())
.then(results => this.rawResultsToEntities(results))
.then(results => {
this.broadcaster.broadcastLoadEventsForAll(results);
return results;
});
.then(results => this.broadcaster.broadcastLoadEventsForAll(results).then(() => results));
}
}
@ -339,7 +333,7 @@ export class QueryBuilder<Entity> {
getCount(): Promise<number> {
const mainAlias = this.aliasMap.mainAlias.name;
const metadata = this.connection.getMetadata(this.fromEntity.alias.target);
const metadata = this.connection.getEntityMetadata(this.fromEntity.alias.target);
const countQuery = this.clone()
.select(`COUNT(DISTINCT(${mainAlias}.${metadata.primaryColumn.name})) as cnt`)
.getSql();

View File

@ -198,7 +198,7 @@ export class Repository<Entity> {
.filter(entityWithId => entityWithId.id !== null && entityWithId.id !== undefined)
.filter(entityWithId => !dbEntities.find(dbEntity => dbEntity.entity.constructor === entityWithId.entity.constructor && dbEntity.id === entityWithId.id))
.map(entityWithId => {
const metadata = this.connection.getMetadata(entityWithId.entity.constructor);
const metadata = this.connection.getEntityMetadata(entityWithId.entity.constructor);
const repository = this.connection.getRepository(entityWithId.entity.constructor);
return repository.findById(entityWithId.id).then(loadedEntity => {
if (!loadedEntity) return undefined;

View File

@ -34,7 +34,7 @@ export class SchemaCreator {
* Creates complete schemas for the given entity metadatas.
*/
create(): Promise<void> {
const metadatas = this.connection.metadatas;
const metadatas = this.connection.entityMetadatas;
return Promise.resolve()
.then(_ => this.dropForeignKeysForAll(metadatas))
.then(_ => this.createTablesForAll(metadatas))

View File

@ -1,6 +1,7 @@
import {OrmSubscriber} from "./OrmSubscriber";
import {Connection} from "../connection/Connection";
import {ColumnMetadata} from "../metadata-builder/metadata/ColumnMetadata";
import {EventListenerTypes} from "../metadata-builder/types/EventListenerTypes";
/**
* Broadcaster provides a helper methods to broadcast events to the subscribers.
@ -18,72 +19,135 @@ export class OrmBroadcaster {
// Accessors
// -------------------------------------------------------------------------
broadcastBeforeInsertEvent(entity: any) {
this.connection
broadcastBeforeInsertEvent(entity: any): Promise<void> {
const subscribers = this.connection
.subscribers
.filter(subscriber => this.isAllowedSubscribers(subscriber, entity))
.filter(subscriber => !!subscriber.beforeInsert)
.forEach(subscriber => subscriber.beforeInsert({ entity: entity }));
.map(subscriber => subscriber.beforeInsert({ entity: entity }));
const listeners = this.connection.entityListeners
.filter(listener => listener.type === EventListenerTypes.BEFORE_INSERT)
.filter(listener => listener.target === entity.constructor)
.map(entityListener => entity[entityListener.propertyName]());
return Promise.all(subscribers.concat(listeners)).then(() => {});
}
broadcastBeforeUpdateEvent(entity: any, updatedColumns: ColumnMetadata[]) {
this.connection
broadcastBeforeUpdateEvent(entity: any, updatedColumns: ColumnMetadata[]): Promise<void> {
const subscribers = this.connection
.subscribers
.filter(subscriber => this.isAllowedSubscribers(subscriber, entity))
.filter(subscriber => !!subscriber.beforeUpdate)
.forEach(subscriber => subscriber.beforeUpdate({ entity: entity, updatedColumns: updatedColumns }));
.map(subscriber => subscriber.beforeUpdate({ entity: entity, updatedColumns: updatedColumns }));
const listeners = this.connection.entityListeners
.filter(listener => listener.type === EventListenerTypes.BEFORE_UPDATE)
.filter(listener => listener.target === entity.constructor)
.map(entityListener => entity[entityListener.propertyName]());
return Promise.all(subscribers.concat(listeners)).then(() => {});
}
broadcastBeforeRemoveEvent(entity: any, entityId: any) {
this.connection
broadcastBeforeRemoveEvent(entity: any, entityId: any): Promise<void> {
const subscribers = this.connection
.subscribers
.filter(subscriber => this.isAllowedSubscribers(subscriber, entity))
.filter(subscriber => !!subscriber.beforeRemove)
.forEach(subscriber => subscriber.beforeRemove({ entity: entity, entityId: entityId }));
.map(subscriber => subscriber.beforeRemove({ entity: entity, entityId: entityId }));
const listeners = this.connection.entityListeners
.filter(listener => listener.type === EventListenerTypes.BEFORE_REMOVE)
.filter(listener => listener.target === entity.constructor)
.map(entityListener => entity[entityListener.propertyName]());
return Promise.all(subscribers.concat(listeners)).then(() => {});
}
broadcastAfterInsertEvent(entity: any) {
this.connection
broadcastAfterInsertEvent(entity: any): Promise<void> {
const subscribers = this.connection
.subscribers
.filter(subscriber => this.isAllowedSubscribers(subscriber, entity))
.filter(subscriber => !!subscriber.afterInsert)
.forEach(subscriber => subscriber.afterInsert({ entity: entity }));
.map(subscriber => subscriber.afterInsert({ entity: entity }));
const listeners = this.connection.entityListeners
.filter(listener => listener.type === EventListenerTypes.AFTER_INSERT)
.filter(listener => listener.target === entity.constructor)
.map(entityListener => entity[entityListener.propertyName]());
return Promise.all(subscribers.concat(listeners)).then(() => {});
}
broadcastAfterUpdateEvent(entity: any, updatedColumns: ColumnMetadata[]) {
this.connection
broadcastAfterUpdateEvent(entity: any, updatedColumns: ColumnMetadata[]): Promise<void> {
const subscribers = this.connection
.subscribers
.filter(subscriber => this.isAllowedSubscribers(subscriber, entity))
.filter(subscriber => !!subscriber.afterUpdate)
.forEach(subscriber => subscriber.afterUpdate({ entity: entity, updatedColumns: updatedColumns }));
.map(subscriber => subscriber.afterUpdate({ entity: entity, updatedColumns: updatedColumns }));
const listeners = this.connection.entityListeners
.filter(listener => listener.type === EventListenerTypes.AFTER_UPDATE)
.filter(listener => listener.target === entity.constructor)
.map(entityListener => entity[entityListener.propertyName]());
return Promise.all(subscribers.concat(listeners)).then(() => {});
}
broadcastAfterRemoveEvent(entity: any, entityId: any) {
this.connection
broadcastAfterRemoveEvent(entity: any, entityId: any): Promise<void> {
const subscribers = this.connection
.subscribers
.filter(subscriber => this.isAllowedSubscribers(subscriber, entity))
.filter(subscriber => !!subscriber.afterRemove)
.forEach(subscriber => subscriber.afterRemove({ entity: entity, entityId: entityId }));
.map(subscriber => subscriber.afterRemove({ entity: entity, entityId: entityId }));
const listeners = this.connection.entityListeners
.filter(listener => listener.type === EventListenerTypes.AFTER_REMOVE)
.filter(listener => listener.target === entity.constructor)
.map(entityListener => entity[entityListener.propertyName]());
return Promise.all(subscribers.concat(listeners)).then(() => {});
}
broadcastLoadEventsForAll(entities: any[]) {
entities.forEach(entity => this.broadcastLoadEvents(entity));
}
broadcastLoadEvents(entity: any) {
const metadata = this.connection.getMetadata(entity.constructor);
broadcastLoadEvents(entity: any): Promise<void> {
const metadata = this.connection.getEntityMetadata(entity.constructor);
let promises: Promise<any>[] = [];
metadata
.relations
.filter(relation => entity.hasOwnProperty(relation.propertyName))
.map(relation => entity[relation.propertyName])
.forEach(value => value instanceof Array ? this.broadcastLoadEventsForAll(value) : this.broadcastLoadEvents(value));
.map(value => {
if (value instanceof Array) {
promises = promises.concat(this.broadcastLoadEventsForAll(value));
} else {
promises.push(this.broadcastLoadEvents(value));
}
});
this.connection
.subscribers
.filter(subscriber => this.isAllowedSubscribers(subscriber, entity))
.filter(subscriber => !!subscriber.afterLoad)
.forEach(subscriber => subscriber.afterLoad(entity));
.forEach(subscriber => promises.push(<any> subscriber.afterLoad(entity)));
this.connection
.entityListeners
.filter(listener => listener.type === EventListenerTypes.AFTER_LOAD)
.filter(listener => listener.target === entity.constructor)
.forEach(listener => promises.push(<any> entity[listener.propertyName]()));
return Promise.all(promises).then(() => {});
}
broadcastLoadEventsForAll(entities: any[]): Promise<void> {
return Promise.all(entities.map(entity => this.broadcastLoadEvents(entity))).then(() => {});
}
// -------------------------------------------------------------------------

View File

@ -262,6 +262,27 @@ describe("many-to-many", function() {
.getSingleResult()
.should.be.rejectedWith(Error);*/ // not working, find fix
});
it("should remove category from post ", function() {
//const qb =
return postRepository
.createQueryBuilder("p")
.leftJoinAndSelect("p.categories", "categories")
.where("p.id=:id", { id: savedPost.id })
.getSingleResult()
.then(loadedPost => {
// console.log("loaded post: ", loadedPost);
loadedPost.categories.splice(0, 1);
// loadedPost.categories.push(new PostCategory("blbla"));
return postRepository.persist(loadedPost);
}).then(updatedPost => {
console.log("updated post: ", updatedPost);
return postCategoryRepository.find({ name : "technology" });
}).then(foundCategory => {
console.log("found category: ", foundCategory);
// todo: expect(foundCategory).to.be.empty;
});
});
});