connection and connection manager refactorings

This commit is contained in:
Umed Khudoiberdiev 2016-04-26 16:16:14 +05:00
parent e00c5ffed3
commit 901248cfa4
14 changed files with 158 additions and 36 deletions

View File

@ -9,10 +9,10 @@ You start using ORM by creating a connection with the database. In this section
### Connection Manager
Connection manager allows to create a new connections and retrive previously created connections. Also it allows to import
Connection manager allows to create a new connections and retrieve previously created connections. Also it allows to import
entities and subscribers into specific connection. These are main public methods of the `ConnectionManager`:
* `createConnection(connectionName: string = "default", driver: Driver, options: ConnectionOptions): Connection`
* `createConnection(options: CreateConnectionOptions): Connection`
Creates a new connection and registers it in the connection manager. It returns a newly created connection.
New connection will have a given *connection name*. If connection name is not given then "default" will be used as a

View File

@ -47,7 +47,7 @@ export class ConnectionManager {
if (options.namingStrategies)
connection.importNamingStrategies(options.namingStrategies);
return connection.connect().then(() => connection);
return connection.connect();
}
/**

View File

@ -14,6 +14,10 @@ import {EntityMetadataBuilder} from "../metadata-builder/EntityMetadataBuilder";
import {DefaultNamingStrategy} from "../naming-strategy/DefaultNamingStrategy";
import {EntityMetadataArray} from "../metadata-builder/metadata/EntityMetadataArray";
import {NamingStrategyMetadata} from "../metadata-builder/metadata/NamingStrategyMetadata";
import {NoConnectionForRepositoryError} from "./error/NoConnectionForRepositoryError";
import {CannotImportAlreadyConnectedError} from "./error/CannotImportAlreadyConnectedError";
import {CannotCloseNotConnectedError} from "./error/CannotCloseNotConnectedError";
import {CannotConnectAlreadyConnectedError} from "./error/CannotConnectAlreadyConnectedError";
/**
* Temporary type to store and link both repository and its metadata.
@ -94,6 +98,11 @@ export class Connection {
*/
private readonly namingStrategyClasses: Function[] = [];
/**
* Indicates if connection has been done or not.
*/
private _isConnected = false;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
@ -106,6 +115,17 @@ export class Connection {
this.entityManager = new EntityManager(this);
}
// -------------------------------------------------------------------------
// Accessors
// -------------------------------------------------------------------------
/**
* Returns true if connection to the database already established for this connection.
*/
get isConnected() {
return this._isConnected;
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
@ -113,17 +133,24 @@ export class Connection {
/**
* Performs connection to the database.
*/
connect(): Promise<void> {
connect(): Promise<this> {
if (this.isConnected)
throw new CannotConnectAlreadyConnectedError(this.name);
return this.driver.connect().then(() => {
// build all metadata
this.registerMetadatas();
// first build all metadata
this.buildMetadatas();
// second build schema
if (this.options.autoSchemaCreate === true)
return this.createSchema();
return this.syncSchema();
return undefined;
return Promise.resolve();
}).then(() => {
this._isConnected = true;
return this;
});
}
@ -131,55 +158,52 @@ export class Connection {
* Closes this connection.
*/
close(): Promise<void> {
if (!this.isConnected)
throw new CannotCloseNotConnectedError(this.name);
return this.driver.disconnect();
}
/**
* Creates database schema for all entities registered in this connection.
*/
createSchema() {
syncSchema() {
const schemaBuilder = this.driver.createSchemaBuilder();
const schemaCreator = new SchemaCreator(schemaBuilder, this.entityMetadatas);
return schemaCreator.create();
}
/**
* Gets repository for the given entity class.
*/
getRepository<Entity>(entityClass: ConstructorFunction<Entity>|Function): Repository<Entity> {
const metadata = this.entityMetadatas.findByTarget(entityClass);
const repoMeta = this.repositoryAndMetadatas.find(repoMeta => repoMeta.metadata === metadata);
if (!repoMeta)
throw new RepositoryNotFoundError(entityClass);
return repoMeta.repository;
}
/**
* Imports entities from the given paths (directories) for the current connection.
*/
importEntitiesFromDirectories(paths: string[]): void {
importEntitiesFromDirectories(paths: string[]): this {
this.importEntities(importClassesFromDirectories(paths));
return this;
}
/**
* Imports subscribers from the given paths (directories) for the current connection.
*/
importSubscribersFromDirectories(paths: string[]): void {
importSubscribersFromDirectories(paths: string[]): this {
this.importSubscribers(importClassesFromDirectories(paths));
return this;
}
/**
* Imports naming strategies from the given paths (directories) for the current connection.
*/
importNamingStrategiesFromDirectories(paths: string[]): void {
importNamingStrategiesFromDirectories(paths: string[]): this {
this.importEntities(importClassesFromDirectories(paths));
return this;
}
/**
* Imports entities for the current connection.
*/
importEntities(entities: Function[]): this {
if (this.isConnected)
throw new CannotImportAlreadyConnectedError("entities", this.name);
this.entityClasses.push(...entities);
return this;
}
@ -188,6 +212,9 @@ export class Connection {
* Imports entities for the given connection. If connection name is not given then default connection is used.
*/
importSubscribers(subscriberClasses: Function[]): this {
if (this.isConnected)
throw new CannotImportAlreadyConnectedError("event subscribers", this.name);
this.subscriberClasses.push(...subscriberClasses);
return this;
}
@ -196,15 +223,36 @@ export class Connection {
* Imports entities for the current connection.
*/
importNamingStrategies(strategies: Function[]): this {
if (this.isConnected)
throw new CannotImportAlreadyConnectedError("naming strategies", this.name);
this.namingStrategyClasses.push(...strategies);
return this;
}
/**
* Gets repository for the given entity class.
*/
getRepository<Entity>(entityClass: ConstructorFunction<Entity>|Function): Repository<Entity> {
if (!this.isConnected)
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);
return repoMeta.repository;
}
// -------------------------------------------------------------------------
// Private Methods
// -------------------------------------------------------------------------
private registerMetadatas() {
/**
* Builds all registered metadatas.
*/
private buildMetadatas() {
// first register naming strategies
const metadatas = defaultMetadataStorage().findNamingStrategiesForClasses(this.namingStrategyClasses);
@ -227,7 +275,7 @@ export class Connection {
}
/**
* Gets the naming strategy
* Gets the naming strategy to be used for this connection.
*/
private createNamingStrategy() {
if (!this.options.namingStrategy)
@ -238,14 +286,15 @@ export class Connection {
}
/**
* Creates a new instance of the given constructor. If IOC Container is registered in the ORM
* 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.
*/
private createContainerInstance(constructor: Function) {
return getContainer() ? getContainer().get(constructor) : new (<any> constructor)();
}
/**
* Creates a temporary object RepositoryAndMetadata to store mapping between repository and metadata.
* Creates a temporary object RepositoryAndMetadata to store relation between repository and metadata.
*/
private createRepoMeta(metadata: EntityMetadata): RepositoryAndMetadata {
return {

View File

@ -0,0 +1,12 @@
/**
* @internal
*/
export class CannotCloseNotConnectedError extends Error {
name = "CannotCloseNotConnectedError";
constructor(connectionName: string) {
super();
this.message = `Cannot close "${connectionName}" connection because connection is not yet established.`;
}
}

View File

@ -0,0 +1,12 @@
/**
* @internal
*/
export class CannotConnectAlreadyConnectedError extends Error {
name = "CannotConnectAlreadyConnectedError";
constructor(connectionName: string) {
super();
this.message = `Cannot create a "${connectionName}" connection because connection to the database already established.`;
}
}

View File

@ -0,0 +1,12 @@
/**
* @internal
*/
export class CannotImportAlreadyConnectedError extends Error {
name = "CannotImportAlreadyConnected";
constructor(importStuff: string, connectionName: string) {
super();
this.message = `Cannot import ${importStuff} for "${connectionName}" connection because connection to the database already established.`;
}
}

View File

@ -0,0 +1,13 @@
/**
* @internal
*/
export class NoConnectionForRepositoryError extends Error {
name = "NoConnectionForRepositoryError";
constructor(connectionName: string) {
super();
this.message = `Cannot get a Repository for "${connectionName} connection, because connection with the database ` +
`is not established yet. Call connection#connect method to establish connection.`;
}
}

View File

@ -4,9 +4,11 @@
export class RepositoryNotFoundError extends Error {
name = "RepositoryNotFoundError";
constructor(entityClass: Function) {
constructor(connectionName: string, entityClass: Function) {
super();
this.message = `No repository for "${entityClass}" was found. Looks like this entity is not registered in your connection?`;
const targetName = (<any> entityClass).name ? (<any> entityClass).name : entityClass;
this.message = `No repository for "${targetName}" was found. Looks like this entity is not registered in ` +
`current "${connectionName}" connection?`;
}
}

View File

@ -0,0 +1,13 @@
/**
* @internal
*/
export class EntityMetadataNotFound extends Error {
name = "EntityMetadataNotFound";
constructor(target: Function) {
super();
const targetName = (<any> target).name ? (<any> target).name : target;
this.message = `No metadata for "${targetName}" was found.`;
}
}

View File

@ -1,4 +1,5 @@
import {EntityMetadata} from "./EntityMetadata";
import {EntityMetadataNotFound} from "../error/EntityMetadataNotFound";
/**
* Array for the entity metadatas.
@ -11,8 +12,16 @@ export class EntityMetadataArray extends Array<EntityMetadata> {
// Public Methods
// -------------------------------------------------------------------------
hasTarget(target: Function) {
return !!this.find(metadata => metadata.target === target);
}
findByTarget(target: Function) {
return this.find(metadata => metadata.target === target);
const metadata = this.find(metadata => metadata.target === target);
if (!metadata)
throw new EntityMetadataNotFound(target);
return metadata;
}
}

View File

@ -42,7 +42,7 @@ describe("insertion", function() {
function reloadDatabase() {
return connection.driver
.clearDatabase()
.then(() => connection.createSchema())
.then(() => connection.syncSchema())
.catch(e => console.log("Error during schema re-creation: ", e));
}

View File

@ -51,7 +51,7 @@ describe("one-to-one", function() {
function reloadDatabase() {
return connection.driver
.clearDatabase()
.then(() => connection.createSchema());
.then(() => connection.syncSchema());
}
let postRepository: Repository<Post>,

View File

@ -48,7 +48,7 @@ describe("many-to-one", function() {
function reloadDatabase() {
return connection.driver
.clearDatabase()
.then(() => connection.createSchema());
.then(() => connection.syncSchema());
}
let postRepository: Repository<Post>,

View File

@ -51,7 +51,7 @@ describe("many-to-many", function() {
function reloadDatabase() {
return connection.driver
.clearDatabase()
.then(() => connection.createSchema());
.then(() => connection.syncSchema());
}
let postRepository: Repository<Post>,