refactored connection class

This commit is contained in:
Umed Khudoiberdiev 2016-05-22 15:56:40 +05:00
parent f513fc1ba7
commit 1e79a63795
6 changed files with 129 additions and 85 deletions

View File

@ -9,7 +9,7 @@ import {ConstructorFunction} from "../common/ConstructorFunction";
import {EntityListenerMetadata} from "../metadata/EntityListenerMetadata";
import {EntityManager} from "../entity-manager/EntityManager";
import {importClassesFromDirectories} from "../util/DirectoryExportedClassesLoader";
import {defaultMetadataStorage, getContainer} from "../index";
import {defaultMetadataStorage, getFromContainer} from "../index";
import {EntityMetadataBuilder} from "../metadata-storage/EntityMetadataBuilder";
import {DefaultNamingStrategy} from "../naming-strategy/DefaultNamingStrategy";
import {EntityMetadataCollection} from "../metadata/collection/EntityMetadataCollection";
@ -22,11 +22,8 @@ import {ReactiveRepository} from "../repository/ReactiveRepository";
import {ReactiveEntityManager} from "../entity-manager/ReactiveEntityManager";
import {TreeRepository} from "../repository/TreeRepository";
import {ReactiveTreeRepository} from "../repository/ReactiveTreeRepository";
/**
* Temporary type to store and link both repository and its metadata.
*/
type RepositoryAndMetadata = { metadata: EntityMetadata, repository: Repository<any>, reactiveRepository: ReactiveRepository<any> };
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {RepositoryBuilder} from "../repository/RepositoryBuilder";
/**
* A single connection instance to the database. Each connection has its own repositories, subscribers and metadatas.
@ -37,7 +34,8 @@ export class Connection {
// Properties
// -------------------------------------------------------------------------
private repositoryAndMetadatas: RepositoryAndMetadata[] = [];
private repositories: Repository<any>[] = [];
private reactiveRepositories: ReactiveRepository<any>[] = [];
// -------------------------------------------------------------------------
// Readonly properties
@ -143,34 +141,32 @@ export class Connection {
/**
* Performs connection to the database.
*/
connect(): Promise<this> {
async connect(): Promise<this> {
if (this.isConnected)
throw new CannotConnectAlreadyConnectedError(this.name);
return this.driver.connect().then(() => {
// first build all metadata
this.buildMetadatas();
// second build schema
if (this.options.autoSchemaCreate === true)
return this.syncSchema();
return Promise.resolve();
// connect to the database via its driver
await this.driver.connect();
}).then(() => {
this._isConnected = true;
return this;
});
// build all metadatas registered in the current connection
this.buildMetadatas();
// second build schema
if (this.options.autoSchemaCreate === true)
await this.syncSchema();
// set connected status for the current connection
this._isConnected = true;
return this;
}
/**
* Closes this connection.
*/
close(): Promise<void> {
async close(): Promise<void> {
if (!this.isConnected)
throw new CannotCloseNotConnectedError(this.name);
return this.driver.disconnect();
}
@ -184,7 +180,7 @@ export class Connection {
}
/**
* Imports entities from the given paths (directories) for the current connection.
* Imports entities from the given paths (directories) for the current connection.
*/
importEntitiesFromDirectories(paths: string[]): this {
this.importEntities(importClassesFromDirectories(paths));
@ -206,7 +202,7 @@ export class Connection {
this.importEntities(importClassesFromDirectories(paths));
return this;
}
/**
* Imports entities for the current connection.
*/
@ -235,7 +231,7 @@ export class Connection {
importNamingStrategies(strategies: Function[]): this {
if (this.isConnected)
throw new CannotImportAlreadyConnectedError("naming strategies", this.name);
strategies.forEach(cls => this.namingStrategyClasses.push(cls));
return this;
}
@ -264,12 +260,12 @@ export class Connection {
} else {
metadata = this.entityMetadatas.findByTarget(entityClassOrName);
}
const repoMeta = this.repositoryAndMetadatas.find(repoMeta => repoMeta.metadata === metadata);
if (!repoMeta)
const repository = this.repositories.find(repository => Repository.ownsMetadata(repository, metadata));
if (!repository)
throw new RepositoryNotFoundError(this.name, entityClassOrName);
return repoMeta.repository;
return repository;
}
/**
@ -297,13 +293,13 @@ export class Connection {
metadata = this.entityMetadatas.findByTarget(entityClassOrName);
}
const repoMeta = this.repositoryAndMetadatas.find(repoMeta => repoMeta.metadata === metadata);
if (!repoMeta)
const repository = this.repositories.find(repository => Repository.ownsMetadata(repository, metadata));
if (!repository)
throw new RepositoryNotFoundError(this.name, entityClassOrName);
if (!repoMeta.metadata.table.isClosure)
throw new Error(`Cannot get a tree repository. ${repoMeta.metadata.name} is not a tree table. Try to use @ClosureTable decorator instead of @Table.`);
if (!metadata.table.isClosure)
throw new Error(`Cannot get a tree repository. ${metadata.name} is not a tree table. Try to use @ClosureTable decorator instead of @Table.`);
return <TreeRepository<Entity>> repoMeta.repository;
return <TreeRepository<Entity>> repository;
}
/**
@ -314,11 +310,11 @@ export class Connection {
throw new NoConnectionForRepositoryError(this.name);
const metadata = this.entityMetadatas.findByTarget(entityClass);
const repoMeta = this.repositoryAndMetadatas.find(repoMeta => repoMeta.metadata === metadata);
if (!repoMeta)
throw new RepositoryNotFoundError(this.name, entityClass);
const repository = this.reactiveRepositories.find(repository => ReactiveRepository.ownsMetadata(repository, metadata));
if (!repository)
throw new RepositoryNotFoundError(this.name, entityClass); // todo throw ReactiveRepositoryNotFoundError
return repoMeta.reactiveRepository;
return repository;
}
/**
@ -329,13 +325,13 @@ export class Connection {
throw new NoConnectionForRepositoryError(this.name);
const metadata = this.entityMetadatas.findByTarget(entityClass);
const repoMeta = this.repositoryAndMetadatas.find(repoMeta => repoMeta.metadata === metadata);
if (!repoMeta)
const repository = this.reactiveRepositories.find(repository => ReactiveRepository.ownsMetadata(repository, metadata));
if (!repository)
throw new RepositoryNotFoundError(this.name, entityClass);
if (!repoMeta.metadata.table.isClosure)
throw new Error(`Cannot get a tree repository. ${repoMeta.metadata.name} is not a tree table. Try to use @ClosureTable decorator instead of @Table.`);
if (!metadata.table.isClosure)
throw new Error(`Cannot get a tree repository. ${metadata.name} is not a tree table. Try to use @ClosureTable decorator instead of @Table.`);
return <ReactiveTreeRepository<Entity>> repoMeta.reactiveRepository;
return <ReactiveTreeRepository<Entity>> repository;
}
// -------------------------------------------------------------------------
@ -346,7 +342,7 @@ export class Connection {
* Builds all registered metadatas.
*/
private buildMetadatas() {
// first register naming strategies
const metadatas = defaultMetadataStorage().namingStrategyMetadatas.filterByClasses(this.namingStrategyClasses);
metadatas.forEach(cls => this.namingStrategyMetadatas.push(cls));
@ -355,7 +351,7 @@ export class Connection {
const subscribers = defaultMetadataStorage()
.eventSubscriberMetadatas
.filterByClasses(this.subscriberClasses)
.map(metadata => this.createContainerInstance(metadata.target));
.map(metadata => getFromContainer(metadata.target));
this.eventSubscribers.push(...subscribers);
// third register entity and entity listener metadatas
@ -365,49 +361,35 @@ export class Connection {
entityMetadatas.forEach(cls => this.entityMetadatas.push(cls));
entityListenerMetadatas.forEach(cls => this.entityListeners.push(cls));
entityMetadatas.map(metadata => this.createRepoMeta(metadata)).forEach(cls => this.repositoryAndMetadatas.push(cls));
entityMetadatas.forEach(metadata => this.createRepository(metadata));
}
/**
* Gets the naming strategy to be used for this connection.
*/
private createNamingStrategy() {
private createNamingStrategy(): NamingStrategyInterface {
if (!this.options.namingStrategy)
return new DefaultNamingStrategy();
const namingMetadata = this.namingStrategyMetadatas.find(strategy => strategy.name === this.options.namingStrategy);
if (!namingMetadata)
throw new Error(`Naming strategy called "${this.options.namingStrategy}" was not found.`);
return this.createContainerInstance(namingMetadata.target);
return getFromContainer<NamingStrategyInterface>(namingMetadata.target);
}
/**
* Creates a new instance of the given constructor. If service container is registered in the ORM, then it will
* be used, otherwise new instance of naming strategy will be created.
* Creates repository and reactive repository for the given entity metadata.
*/
private createContainerInstance(constructor: Function) {
return getContainer() ? getContainer().get(constructor) : new (<any> constructor)();
}
/**
* Creates a temporary object RepositoryAndMetadata to store relation between repository and metadata.
*/
private createRepoMeta(metadata: EntityMetadata): RepositoryAndMetadata {
private createRepository(metadata: EntityMetadata) {
if (metadata.table.isClosure) {
const repository = new TreeRepository<any>(this, this.entityMetadatas, metadata);
return {
metadata: metadata,
repository: repository,
reactiveRepository: new ReactiveTreeRepository(repository)
};
this.repositories.push(repository);
this.reactiveRepositories.push(new ReactiveTreeRepository(repository));
} else {
const repository = new Repository<any>(this, this.entityMetadatas, metadata);
return {
metadata: metadata,
repository: repository,
reactiveRepository: new ReactiveRepository(repository)
};
this.repositories.push(repository);
this.reactiveRepositories.push(new ReactiveRepository(repository));
}
}

View File

@ -14,21 +14,33 @@ import {CreateConnectionOptions} from "./connection-manager/CreateConnectionOpti
// -------------------------------------------------------------------------
/**
* Container to be used by TypeORM for inversion control.
* Container to be used by this library for inversion control. If container was not implicitly set then by default
* container simply creates a new instance of the given class.
*/
let container: { get(someClass: any): any };
let container: { get<T>(someClass: { new (...args: any[]): T }|Function): T } = new (class {
private instances: any[] = [];
get<T>(someClass: { new (...args: any[]): T }): T {
if (!this.instances[<any>someClass])
this.instances[<any>someClass] = new someClass();
return this.instances[<any>someClass];
}
})();
/**
* Sets container to be used by TypeORM.
*
* Sets container to be used by this library.
*
* @param iocContainer
*/
export function useContainer(iocContainer: { get(someClass: any): any }) {
container = iocContainer;
}
export function getContainer() {
return container;
/**
* Gets the IOC container used by this library.
*/
export function getFromContainer<T>(someClass: { new (...args: any[]): T }|Function): T {
return container.get<T>(someClass);
}
// -------------------------------------------------------------------------

View File

@ -2,6 +2,7 @@ import {QueryBuilder} from "../query-builder/QueryBuilder";
import {FindOptions} from "./FindOptions";
import {Repository} from "./Repository";
import * as Rx from "rxjs/Rx";
import {EntityMetadata} from "../metadata/EntityMetadata";
/**
* Repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc.
@ -208,4 +209,12 @@ export class ReactiveRepository<Entity> {
return Rx.Observable.fromPromise(this.repository.transaction(runInTransaction));
}
// -------------------------------------------------------------------------
// Static Methods
// -------------------------------------------------------------------------
static ownsMetadata(reactiveRepository: ReactiveRepository<any>, metadata: EntityMetadata) {
return Repository.ownsMetadata(reactiveRepository.repository, metadata);
}
}

View File

@ -15,17 +15,17 @@ import {Driver} from "../driver/Driver";
* Repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc.
*/
export class Repository<Entity> {
// -------------------------------------------------------------------------
// Private Properties
// -------------------------------------------------------------------------
private driver: Driver;
private broadcaster: Broadcaster;
private persistOperationExecutor: PersistOperationExecutor;
private entityPersistOperationBuilder: EntityPersistOperationBuilder;
private plainObjectToEntityTransformer: PlainObjectToNewEntityTransformer;
private plainObjectToDatabaseEntityTransformer: PlainObjectToDatabaseEntityTransformer<Entity>;
protected driver: Driver;
protected broadcaster: Broadcaster;
protected persistOperationExecutor: PersistOperationExecutor;
protected entityPersistOperationBuilder: EntityPersistOperationBuilder;
protected plainObjectToEntityTransformer: PlainObjectToNewEntityTransformer;
protected plainObjectToDatabaseEntityTransformer: PlainObjectToDatabaseEntityTransformer<Entity>;
// -------------------------------------------------------------------------
// Constructor
@ -412,4 +412,12 @@ export class Repository<Entity> {
return entity;
}
// -------------------------------------------------------------------------
// Static Methods
// -------------------------------------------------------------------------
static ownsMetadata(repository: Repository<any>, metadata: EntityMetadata) {
return repository.metadata === metadata;
}
}

View File

@ -0,0 +1,33 @@
import {Connection} from "../connection/Connection";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {PlainObjectToNewEntityTransformer} from "../query-builder/transformer/PlainObjectToNewEntityTransformer";
import {PlainObjectToDatabaseEntityTransformer} from "../query-builder/transformer/PlainObjectToDatabaseEntityTransformer";
import {EntityPersistOperationBuilder} from "../persistment/EntityPersistOperationsBuilder";
import {PersistOperationExecutor} from "../persistment/PersistOperationExecutor";
import {EntityWithId} from "../persistment/operation/PersistOperation";
import {FindOptions, FindOptionsUtils} from "./FindOptions";
import {EntityMetadataCollection} from "../metadata/collection/EntityMetadataCollection";
import {Broadcaster} from "../subscriber/Broadcaster";
import {Driver} from "../driver/Driver";
/**
* Repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc.
*/
export class RepositoryBuilder {
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(protected connection: Connection,
protected entityMetadatas: EntityMetadataCollection,
protected metadata: EntityMetadata) {
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
}

View File

@ -2,7 +2,7 @@
"version": "1.8.0",
"compilerOptions": {
"outDir": "build/es5",
"target": "es5",
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,