Merge pull request #7 from pleerock/async-await

refactored connection class
This commit is contained in:
Umed Khudoiberdiev 2016-05-26 21:04:09 +05:00
commit 1f716002c3
123 changed files with 2883 additions and 1223 deletions

View File

@ -1,7 +1,7 @@
{
"name": "typeorm",
"private": true,
"version": "0.0.2-alpha.31",
"version": "0.0.2-alpha.32",
"description": "Data-mapper ORM for Typescript",
"license": "Apache-2.0",
"readmeFilename": "README.md",
@ -31,30 +31,31 @@
"gulp-mocha": "^2.2.0",
"gulp-replace": "^0.5.4",
"gulp-shell": "^0.5.1",
"gulp-tslint": "^4.3.5",
"gulpclass": "0.1.0",
"mocha": "^2.3.2",
"sinon": "^1.17.2",
"gulp-tslint": "^5.0.0",
"gulpclass": "0.1.1",
"mocha": "^2.5.3",
"mysql": "^2.10.2",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0",
"tslint": "^3.7.4",
"tslint": "next",
"tslint-stylish": "^2.1.0-beta",
"typescript": "next",
"typings": "^0.7.12"
"typings": "^1.0.4"
},
"dependencies": {
"fs": "^0.0.2",
"glob": "^7.0.3",
"lodash": "^4.11.1",
"lodash": "^4.13.1",
"moment": "^2.13.0",
"mysql": "^2.10.2",
"path": "^0.12.7",
"reflect-metadata": "^0.1.3",
"require-all": "^2.0.0",
"rxjs": "^5.0.0-beta.7",
"rxjs": "^5.0.0-beta.8",
"sha1": "^1.1.1"
},
"scripts": {
"postversion": "./node_modules/.bin/gulp package",
"test": "node_modules/.bin/mocha -w"
"postinstall": "./node_modules/.bin/typings install",
"test": "node_modules/.bin/gulp tests"
}
}

View File

@ -43,10 +43,10 @@ export class Post {
secondaryImages: Image[];
@ManyToOne(type => Cover, cover => cover.posts, {
name: "coverId",
cascadeInsert: true,
cascadeRemove: true
})
@JoinColumn({ name: "coverId" })
cover: Cover;
@Column("int", {

View File

@ -1,59 +1,29 @@
import * as _ from "lodash";
import {NamingStrategyInterface} from "../../../src/naming-strategy/NamingStrategyInterface";
import {NamingStrategy} from "../../../src/decorator/NamingStrategy";
import {DefaultNamingStrategy} from "../../../src/naming-strategy/DefaultNamingStrategy";
@NamingStrategy("custom_strategy")
export class CustomNamingStrategy implements NamingStrategyInterface {
export class CustomNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
tableName(className: string): string {
return _.snakeCase(className);
}
tableNameCustomized(customName: string): string {
return customName;
}
columnName(propertyName: string): string {
return _.snakeCase(propertyName);
}
columnNameCustomized(customName: string): string {
return customName;
}
relationName(propertyName: string): string {
return _.snakeCase(propertyName);
}
indexName(target: Function, name: string, columns: string[]): string {
if (name)
return name;
return "index" + columns.join("_");
}
joinColumnInverseSideName(joinColumnName: string, propertyName: string): string {
if (joinColumnName)
return joinColumnName;
return propertyName;
}
joinTableName(firstTableName: string,
secondTableName: string,
firstColumnName: string,
secondColumnName: string,
firstPropertyName: string,
secondPropertyName: string): string {
return _.snakeCase(firstTableName + "_" + firstColumnName + "_" + secondTableName + "_" + secondPropertyName);
}
joinTableColumnName(tableName: string, columnName: string, secondTableName: string, secondColumnName: string): string {
const column1 = tableName + "_" + columnName;
const column2 = secondTableName + "_" + secondColumnName;
return column1 === column2 ? column1 + "_1" : column1;
}
joinTableInverseColumnName(tableName: string, columnName: string, secondTableName: string, secondColumnName: string): string {
const column1 = tableName + "_" + columnName;
const column2 = secondTableName + "_" + secondColumnName;
return column1 === column2 ? column1 + "_2" : column1;
}
closureJunctionTableName(tableName: string): string {
return tableName + "_closure";
}
}

View File

@ -4,15 +4,14 @@ import {Repository} from "../repository/Repository";
import {EventSubscriberInterface} from "../subscriber/EventSubscriberInterface";
import {RepositoryNotFoundError} from "./error/RepositoryNotFoundError";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {SchemaCreator} from "../schema-creator/SchemaCreator";
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 {EntityMetadataBuilder} from "../metadata-storage/EntityMetadataBuilder";
import {getMetadataArgsStorage, getFromContainer} from "../index";
import {EntityMetadataBuilder} from "../metadata-builder/EntityMetadataBuilder";
import {DefaultNamingStrategy} from "../naming-strategy/DefaultNamingStrategy";
import {EntityMetadataCollection} from "../metadata/collection/EntityMetadataCollection";
import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetadataCollection";
import {NamingStrategyMetadata} from "../metadata/NamingStrategyMetadata";
import {NoConnectionForRepositoryError} from "./error/NoConnectionForRepositoryError";
import {CannotImportAlreadyConnectedError} from "./error/CannotImportAlreadyConnectedError";
@ -22,11 +21,13 @@ 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 {NamingStrategyNotFoundError} from "./error/NamingStrategyNotFoundError";
import {EntityManagerFactory} from "../entity-manager/EntityManagerFactory";
import {RepositoryFactory} from "../repository/RepositoryFactory";
import {SchemaCreatorFactory} from "../schema-creator/SchemaCreatorFactory";
import {ReactiveRepositoryNotFoundError} from "./error/ReactiveRepositoryNotFoundError";
import {RepositoryNotTreeError} from "./error/RepositoryNotTreeError";
/**
* A single connection instance to the database. Each connection has its own repositories, subscribers and metadatas.
@ -37,7 +38,15 @@ export class Connection {
// Properties
// -------------------------------------------------------------------------
private repositoryAndMetadatas: RepositoryAndMetadata[] = [];
/**
* All connection's repositories.
*/
private repositories: Repository<any>[] = [];
/**
* All connection's reactive repositories.
*/
private reactiveRepositories: ReactiveRepository<any>[] = [];
// -------------------------------------------------------------------------
// Readonly properties
@ -121,8 +130,8 @@ export class Connection {
this.driver = driver;
this.driver.connectionOptions = options;
this.options = options;
this.entityManager = new EntityManager(this);
this.reactiveEntityManager = new ReactiveEntityManager(this);
this.entityManager = getFromContainer(EntityManagerFactory).createEntityManager(this);
this.reactiveEntityManager = getFromContainer(EntityManagerFactory).createReactiveEntityManager(this);
}
// -------------------------------------------------------------------------
@ -143,48 +152,50 @@ 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();
}
/**
* Creates database schema for all entities registered in this connection.
*/
syncSchema() {
async syncSchema(dropBeforeSync: boolean = false): Promise<void> {
if (dropBeforeSync)
await this.driver.clearDatabase();
const schemaBuilder = this.driver.createSchemaBuilder();
const schemaCreator = new SchemaCreator(schemaBuilder, this.entityMetadatas);
const schemaCreatorFactory = getFromContainer(SchemaCreatorFactory);
const schemaCreator = schemaCreatorFactory.create(schemaBuilder, this.entityMetadatas);
return schemaCreator.create();
}
/**
* 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 +217,7 @@ export class Connection {
this.importEntities(importClassesFromDirectories(paths));
return this;
}
/**
* Imports entities for the current connection.
*/
@ -235,7 +246,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;
}
@ -258,18 +269,12 @@ export class Connection {
if (!this.isConnected)
throw new NoConnectionForRepositoryError(this.name);
let metadata: EntityMetadata;
if (typeof entityClassOrName === "string") {
metadata = this.entityMetadatas.findByName(entityClassOrName);
} else {
metadata = this.entityMetadatas.findByTarget(entityClassOrName);
}
const repoMeta = this.repositoryAndMetadatas.find(repoMeta => repoMeta.metadata === metadata);
if (!repoMeta)
const metadata = this.entityMetadatas.findByNameOrTarget(entityClassOrName);
const repository = this.repositories.find(repository => Repository.ownsMetadata(repository, metadata));
if (!repository)
throw new RepositoryNotFoundError(this.name, entityClassOrName);
return repoMeta.repository;
return repository;
}
/**
@ -290,52 +295,46 @@ export class Connection {
if (!this.isConnected)
throw new NoConnectionForRepositoryError(this.name);
let metadata: EntityMetadata;
if (typeof entityClassOrName === "string") {
metadata = this.entityMetadatas.findByName(entityClassOrName);
} else {
metadata = this.entityMetadatas.findByTarget(entityClassOrName);
}
const repoMeta = this.repositoryAndMetadatas.find(repoMeta => repoMeta.metadata === metadata);
if (!repoMeta)
const metadata = this.entityMetadatas.findByNameOrTarget(entityClassOrName);
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 RepositoryNotTreeError(entityClassOrName);
return <TreeRepository<Entity>> repoMeta.repository;
return <TreeRepository<Entity>> repository;
}
/**
* Gets reactive repository for the given entity class.
*/
getReactiveRepository<Entity>(entityClass: ConstructorFunction<Entity>|Function): ReactiveRepository<Entity> {
getReactiveRepository<Entity>(entityClass: ConstructorFunction<Entity>|Function|string): ReactiveRepository<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);
const metadata = this.entityMetadatas.findByNameOrTarget(entityClass);
const repository = this.reactiveRepositories.find(repository => ReactiveRepository.ownsMetadata(repository, metadata));
if (!repository)
throw new ReactiveRepositoryNotFoundError(this.name, entityClass);
return repoMeta.reactiveRepository;
return repository;
}
/**
* Gets reactive tree repository for the given entity class.
*/
getReactiveTreeRepository<Entity>(entityClass: ConstructorFunction<Entity>|Function): ReactiveTreeRepository<Entity> {
getReactiveTreeRepository<Entity>(entityClass: ConstructorFunction<Entity>|Function|string): ReactiveTreeRepository<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)
const metadata = this.entityMetadatas.findByNameOrTarget(entityClass);
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 RepositoryNotTreeError(entityClass);
return <ReactiveTreeRepository<Entity>> repoMeta.reactiveRepository;
return <ReactiveTreeRepository<Entity>> repository;
}
// -------------------------------------------------------------------------
@ -346,68 +345,64 @@ 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));
// second register subscriber metadatas
const subscribers = defaultMetadataStorage()
.eventSubscriberMetadatas
// take imported naming strategy metadatas
getMetadataArgsStorage()
.namingStrategies
.filterByClasses(this.namingStrategyClasses)
.forEach(metadata => this.namingStrategyMetadatas.push(new NamingStrategyMetadata(metadata)));
// take imported event subscribers
getMetadataArgsStorage()
.eventSubscribers
.filterByClasses(this.subscriberClasses)
.map(metadata => this.createContainerInstance(metadata.target));
this.eventSubscribers.push(...subscribers);
.map(metadata => getFromContainer(metadata.target))
.forEach(subscriber => this.eventSubscribers.push(subscriber));
// third register entity and entity listener metadatas
const entityMetadataBuilder = new EntityMetadataBuilder(this.createNamingStrategy());
const entityMetadatas = entityMetadataBuilder.build(this.entityClasses);
const entityListenerMetadatas = defaultMetadataStorage().entityListenerMetadatas.filterByClasses(this.entityClasses);
// take imported entity listeners
getMetadataArgsStorage()
.entityListeners
.filterByClasses(this.entityClasses)
.forEach(metadata => this.entityListeners.push(new EntityListenerMetadata(metadata)));
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));
// build entity metadatas for the current connection
getFromContainer(EntityMetadataBuilder)
.build(this.createNamingStrategy(), this.entityClasses)
.forEach(layout => {
this.entityMetadatas.push(layout);
this.createRepository(layout);
});
}
/**
* Gets the naming strategy to be used for this connection.
* Creates a naming strategy to be used for this connection.
*/
private createNamingStrategy() {
private createNamingStrategy(): NamingStrategyInterface {
if (!this.options.namingStrategy)
return new DefaultNamingStrategy();
return getFromContainer(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);
throw new NamingStrategyNotFoundError(this.options.namingStrategy, this.name);
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 {
if (metadata.table.isClosure) {
const repository = new TreeRepository<any>(this, this.entityMetadatas, metadata);
return {
metadata: metadata,
repository: repository,
reactiveRepository: new ReactiveTreeRepository(repository)
};
private createRepository(entityLayout: EntityMetadata) {
const repositoryFactory = getFromContainer(RepositoryFactory);
if (entityLayout.table.isClosure) {
const repository = repositoryFactory.createRepository(this, this.entityMetadatas, entityLayout);
const reactiveRepository = repositoryFactory.createReactiveRepository(repository);
this.repositories.push(repository);
this.reactiveRepositories.push(reactiveRepository);
} else {
const repository = new Repository<any>(this, this.entityMetadatas, metadata);
return {
metadata: metadata,
repository: repository,
reactiveRepository: new ReactiveRepository(repository)
};
const repository = repositoryFactory.createTreeRepository(this, this.entityMetadatas, entityLayout);
const reactiveRepository = repositoryFactory.createReactiveTreeRepository(repository);
this.repositories.push(repository);
this.reactiveRepositories.push(reactiveRepository);
}
}

View File

@ -1,5 +1,5 @@
/**
* Connection options passed to the document.
* Connection options passed to the connection.
*/
export interface ConnectionOptions {

View File

@ -0,0 +1,13 @@
/**
* @internal
*/
export class NamingStrategyNotFoundError extends Error {
name = "NamingStrategyNotFoundError";
constructor(strategyName: string, connectionName: string) {
super();
this.message = `Naming strategy named "${strategyName}" was not found. Looks like this naming strategy does not ` +
`exist or it was not registered in current "${connectionName}" connection?`;
}
}

View File

@ -0,0 +1,14 @@
/**
* @internal
*/
export class ReactiveRepositoryNotFoundError extends Error {
name = "ReactiveRepositoryNotFoundError";
constructor(connectionName: string, entityClass: Function|string) {
super();
const targetName = typeof entityClass === "function" && (<any> entityClass).name ? (<any> entityClass).name : entityClass;
this.message = `No reactive 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 RepositoryNotTreeError extends Error {
name = "RepositoryNotTreeError";
constructor(entityClass: Function|string) {
super();
const targetName = typeof entityClass === "function" && (<any> entityClass).name ? (<any> entityClass).name : entityClass;
this.message = `Repository of the "${targetName}" class is not a TreeRepository. Try to use @ClosureTable decorator instead of @Table.`;
}
}

View File

@ -1,5 +1,5 @@
import {NamingStrategyMetadata} from "../metadata/NamingStrategyMetadata";
import {defaultMetadataStorage} from "../index";
import {getMetadataArgsStorage} from "../index";
import {NamingStrategyMetadataArgs} from "../metadata-args/NamingStrategyMetadataArgs";
/**
* Decorator registers a new naming strategy to be used in naming things.
@ -7,6 +7,10 @@ import {defaultMetadataStorage} from "../index";
export function NamingStrategy(name?: string): Function {
return function (target: Function) {
const strategyName = name ? name : (<any> target).name;
defaultMetadataStorage().namingStrategyMetadatas.add(new NamingStrategyMetadata(target, strategyName));
const args: NamingStrategyMetadataArgs = {
target: target,
name: strategyName
};
getMetadataArgsStorage().namingStrategies.add(args);
};
}

View File

@ -1,9 +1,9 @@
import {ColumnOptions} from "../../metadata/options/ColumnOptions";
import {ColumnOptions} from "../options/ColumnOptions";
import {ColumnTypeUndefinedError} from "../error/ColumnTypeUndefinedError";
import {AutoIncrementOnlyForPrimaryError} from "../error/AutoIncrementOnlyForPrimaryError";
import {defaultMetadataStorage} from "../../index";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {getMetadataArgsStorage} from "../../index";
import {ColumnType, ColumnTypes} from "../../metadata/types/ColumnTypes";
import {ColumnMetadataArgs} from "../../metadata-args/ColumnMetadataArgs";
/**
* Column decorator is used to mark a specific class property as a table column. Only properties decorated with this
@ -31,11 +31,11 @@ export function Column(typeOrOptions?: ColumnType|ColumnOptions, options?: Colum
return function (object: Object, propertyName: string) {
// todo: need to store not string type, but original type instead? (like in relation metadata)
const reflectedType = ColumnTypes.typeToString((<any> Reflect).getMetadata("design:type", object, propertyName));
const reflectedType = ColumnTypes.typeToString((Reflect as any).getMetadata("design:type", object, propertyName));
// if type is not given implicitly then try to guess it
if (!type)
type = ColumnTypes.determineTypeFromFunction((<any> Reflect).getMetadata("design:type", object, propertyName));
type = ColumnTypes.determineTypeFromFunction((Reflect as any).getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options) options = {} as ColumnOptions;
@ -53,11 +53,13 @@ export function Column(typeOrOptions?: ColumnType|ColumnOptions, options?: Colum
throw new AutoIncrementOnlyForPrimaryError(object, propertyName);
// create and register a new column metadata
defaultMetadataStorage().columnMetadatas.add(new ColumnMetadata({
const args: ColumnMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
mode: "regular",
options: options
}));
};
getMetadataArgsStorage().columns.add(args);
};
}

View File

@ -1,7 +1,8 @@
import {ColumnOptions} from "../../metadata/options/ColumnOptions";
import {ColumnType, ColumnTypes} from "../../metadata/types/ColumnTypes";
import {defaultMetadataStorage} from "../../index";
import {ColumnOptions} from "../options/ColumnOptions";
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
import {getMetadataArgsStorage} from "../../index";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {ColumnMetadataArgs} from "../../metadata-args/ColumnMetadataArgs";
/**
* This column will store a creation date of the inserted object. Creation date is generated and inserted only once,
@ -10,7 +11,7 @@ import {ColumnMetadata} from "../../metadata/ColumnMetadata";
export function CreateDateColumn(options?: ColumnOptions): Function {
return function (object: Object, propertyName: string) {
const reflectedType = ColumnTypes.typeToString((<any> Reflect).getMetadata("design:type", object, propertyName));
const reflectedType = ColumnTypes.typeToString((Reflect as any).getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options) options = {} as ColumnOptions;
@ -19,13 +20,14 @@ export function CreateDateColumn(options?: ColumnOptions): Function {
options.type = ColumnTypes.DATETIME;
// create and register a new column metadata
defaultMetadataStorage().columnMetadatas.add(new ColumnMetadata({
const args: ColumnMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
mode: "createDate",
options: options
}));
};
getMetadataArgsStorage().columns.add(args);
};
}

View File

@ -1,9 +1,9 @@
import {ColumnOptions} from "../../metadata/options/ColumnOptions";
import {ColumnOptions} from "../options/ColumnOptions";
import {ColumnType, ColumnTypes} from "../../metadata/types/ColumnTypes";
import {ColumnTypeUndefinedError} from "../error/ColumnTypeUndefinedError";
import {defaultMetadataStorage} from "../../index";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {getMetadataArgsStorage} from "../../index";
import {PrimaryColumnCannotBeNullableError} from "../error/PrimaryColumnCannotBeNullableError";
import {ColumnMetadataArgs} from "../../metadata-args/ColumnMetadataArgs";
/**
* Column decorator is used to mark a specific class property as a table column. Only properties decorated with this
@ -33,11 +33,11 @@ export function PrimaryColumn(typeOrOptions?: ColumnType|ColumnOptions, options?
}
return function (object: Object, propertyName: string) {
const reflectedType = ColumnTypes.typeToString((<any> Reflect).getMetadata("design:type", object, propertyName));
const reflectedType = ColumnTypes.typeToString((Reflect as any).getMetadata("design:type", object, propertyName));
// if type is not given implicitly then try to guess it
if (!type)
type = ColumnTypes.determineTypeFromFunction((<any> Reflect).getMetadata("design:type", object, propertyName));
type = ColumnTypes.determineTypeFromFunction((Reflect as any).getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options) options = {} as ColumnOptions;
@ -55,13 +55,14 @@ export function PrimaryColumn(typeOrOptions?: ColumnType|ColumnOptions, options?
throw new PrimaryColumnCannotBeNullableError(object, propertyName);
// create and register a new column metadata
defaultMetadataStorage().columnMetadatas.add(new ColumnMetadata({
const args: ColumnMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
isPrimaryKey: true,
mode: "primary",
options: options
}));
};
getMetadataArgsStorage().columns.add(args);
};
}

View File

@ -1,5 +1,5 @@
import {defaultMetadataStorage} from "../../index";
import {RelationsCountMetadata} from "../../metadata/RelationsCountMetadata";
import {getMetadataArgsStorage} from "../../index";
import {RelationsCountMetadataArgs} from "../../metadata-args/RelationsCountMetadataArgs";
/**
* Holds a number of children in the closure table of the column.
@ -8,10 +8,15 @@ export function RelationsCountColumn<T>(relation: string|((object: T) => any)):
return function (object: Object, propertyName: string) {
// todo: need to check if property type is number?
// const reflectedType = ColumnTypes.typeToString((<any> Reflect).getMetadata("design:type", object, propertyName));
// const reflectedType = ColumnTypes.typeToString((Reflect as any).getMetadata("design:type", object, propertyName));
// create and register a new column metadata
defaultMetadataStorage().relationCountMetadatas.add(new RelationsCountMetadata(object.constructor, propertyName, relation));
const args: RelationsCountMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
relation: relation
};
getMetadataArgsStorage().relationCounts.add(args);
};
}

View File

@ -1,7 +1,7 @@
import {ColumnOptions} from "../../metadata/options/ColumnOptions";
import {ColumnOptions} from "../options/ColumnOptions";
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
import {defaultMetadataStorage} from "../../index";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {getMetadataArgsStorage} from "../../index";
import {ColumnMetadataArgs} from "../../metadata-args/ColumnMetadataArgs";
/**
* This column will store an update date of the updated object. This date is being updated each time you persist the
@ -10,7 +10,7 @@ import {ColumnMetadata} from "../../metadata/ColumnMetadata";
export function UpdateDateColumn(options?: ColumnOptions): Function {
return function (object: Object, propertyName: string) {
const reflectedType = ColumnTypes.typeToString((<any> Reflect).getMetadata("design:type", object, propertyName));
const reflectedType = ColumnTypes.typeToString((Reflect as any).getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options) options = {} as ColumnOptions;
@ -19,13 +19,14 @@ export function UpdateDateColumn(options?: ColumnOptions): Function {
options.type = ColumnTypes.DATETIME;
// create and register a new column metadata
defaultMetadataStorage().columnMetadatas.add(new ColumnMetadata({
const args: ColumnMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
mode: "updateDate",
options: options
}));
};
getMetadataArgsStorage().columns.add(args);
};
}

View File

@ -1,7 +1,7 @@
import {ColumnOptions} from "../../metadata/options/ColumnOptions";
import {ColumnOptions} from "../options/ColumnOptions";
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
import {defaultMetadataStorage} from "../../index";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {getMetadataArgsStorage} from "../../index";
import {ColumnMetadataArgs} from "../../metadata-args/ColumnMetadataArgs";
/**
* This column will store a number - version of the entity. Every time your entity will be persisted, this number will
@ -10,7 +10,7 @@ import {ColumnMetadata} from "../../metadata/ColumnMetadata";
export function VersionColumn(options?: ColumnOptions): Function {
return function (object: Object, propertyName: string) {
const reflectedType = ColumnTypes.typeToString((<any> Reflect).getMetadata("design:type", object, propertyName));
const reflectedType = ColumnTypes.typeToString((Reflect as any).getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options) options = {} as ColumnOptions;
@ -21,13 +21,14 @@ export function VersionColumn(options?: ColumnOptions): Function {
// todo: check if reflectedType is number too
// create and register a new column metadata
defaultMetadataStorage().columnMetadatas.add(new ColumnMetadata({
const args: ColumnMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
mode: "version",
options: options
}));
};
getMetadataArgsStorage().columns.add(args);
};
}

View File

@ -1,14 +1,30 @@
import {CompositeIndexMetadata} from "../../metadata/CompositeIndexMetadata";
import {defaultMetadataStorage} from "../../index";
import {CompositeIndexOptions} from "../../metadata/options/CompositeIndexOptions";
import {getMetadataArgsStorage} from "../../index";
import {CompositeIndexOptions} from "../options/CompositeIndexOptions";
import {IndexMetadataArgs} from "../../metadata-args/IndexMetadataArgs";
/**
* Composite indexes must be set on entity classes and must specify fields to be indexed.
* Composite index must be set on entity classes and must specify entity's fields to be indexed.
*/
export function CompositeIndex(name: string, fields: string[], options?: CompositeIndexOptions): Function;
/**
* Composite index must be set on entity classes and must specify entity's fields to be indexed.
*/
export function CompositeIndex(fields: string[], options?: CompositeIndexOptions): Function;
/**
* Composite index must be set on entity classes and must specify entity's fields to be indexed.
*/
export function CompositeIndex(fields: (object: any) => any[], options?: CompositeIndexOptions): Function;
/**
* Composite index must be set on entity classes and must specify entity's fields to be indexed.
*/
export function CompositeIndex(name: string, fields: (object: any) => any[], options?: CompositeIndexOptions): Function;
/**
* Composite index must be set on entity classes and must specify entity's fields to be indexed.
*/
export function CompositeIndex(nameOrFields: string|string[]|((object: any) => any[]),
maybeFieldsOrOptions?: ((object: any) => any[])|CompositeIndexOptions|string[],
maybeOptions?: CompositeIndexOptions): Function {
@ -17,6 +33,12 @@ export function CompositeIndex(nameOrFields: string|string[]|((object: any) => a
const options = typeof maybeFieldsOrOptions === "object" ? <CompositeIndexOptions> maybeFieldsOrOptions : maybeOptions;
return function (cls: Function) {
defaultMetadataStorage().compositeIndexMetadatas.add(new CompositeIndexMetadata(cls, name, fields, options));
const args: IndexMetadataArgs = {
target: cls,
name: name,
columns: fields,
unique: options && options.unique ? true : false
};
getMetadataArgsStorage().indices.add(args);
};
}

View File

@ -1,11 +1,17 @@
import {defaultMetadataStorage} from "../../index";
import {IndexMetadata} from "../../metadata/IndexMetadata";
import {getMetadataArgsStorage} from "../../index";
import {IndexMetadataArgs} from "../../metadata-args/IndexMetadataArgs";
/**
* Fields that needs to be indexed must be marked with this decorator.
*/
export function Index(name?: string) {
export function Index(name?: string, options?: { unique: boolean }) {
return function (object: Object, propertyName: string) {
defaultMetadataStorage().indexMetadatas.add(new IndexMetadata(object.constructor, propertyName, name));
const args: IndexMetadataArgs = {
name: name,
target: object.constructor,
columns: [propertyName],
unique: options && options.unique ? true : false
};
getMetadataArgsStorage().indices.add(args);
};
}

View File

@ -1,16 +1,18 @@
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {EventListenerTypes} from "../../metadata/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata/EntityListenerMetadata";
import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs";
/**
* Calls a method on which this decorator is applied after this entity insertion.
*/
export function AfterInsert() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage().entityListenerMetadatas.add(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.AFTER_INSERT
));
const args: EntityListenerMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.AFTER_INSERT
};
getMetadataArgsStorage().entityListeners.add(args);
};
}

View File

@ -1,16 +1,17 @@
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {EventListenerTypes} from "../../metadata/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata/EntityListenerMetadata";
import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs";
/**
* Calls a method on which this decorator is applied after entity is loaded.
*/
export function AfterLoad() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage().entityListenerMetadatas.add(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.AFTER_LOAD
));
const args: EntityListenerMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.AFTER_LOAD
};
getMetadataArgsStorage().entityListeners.add(args);
};
}

View File

@ -1,16 +1,17 @@
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {EventListenerTypes} from "../../metadata/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata/EntityListenerMetadata";
import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs";
/**
* Calls a method on which this decorator is applied after this entity removal.
*/
export function AfterRemove() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage().entityListenerMetadatas.add(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.AFTER_REMOVE
));
const args: EntityListenerMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.AFTER_REMOVE
};
getMetadataArgsStorage().entityListeners.add(args);
};
}

View File

@ -1,16 +1,17 @@
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {EventListenerTypes} from "../../metadata/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata/EntityListenerMetadata";
import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs";
/**
* Calls a method on which this decorator is applied after this entity update.
*/
export function AfterUpdate() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage().entityListenerMetadatas.add(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.AFTER_UPDATE
));
const args: EntityListenerMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.AFTER_UPDATE
};
getMetadataArgsStorage().entityListeners.add(args);
};
}

View File

@ -1,16 +1,17 @@
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {EventListenerTypes} from "../../metadata/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata/EntityListenerMetadata";
import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs";
/**
* Calls a method on which this decorator is applied before this entity insertion.
*/
export function BeforeInsert() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage().entityListenerMetadatas.add(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.BEFORE_INSERT
));
const args: EntityListenerMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.BEFORE_INSERT
};
getMetadataArgsStorage().entityListeners.add(args);
};
}

View File

@ -1,16 +1,17 @@
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {EventListenerTypes} from "../../metadata/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata/EntityListenerMetadata";
import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs";
/**
* Calls a method on which this decorator is applied before this entity removal.
*/
export function BeforeRemove() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage().entityListenerMetadatas.add(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.BEFORE_REMOVE
));
const args: EntityListenerMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.BEFORE_REMOVE
};
getMetadataArgsStorage().entityListeners.add(args);
};
}

View File

@ -1,16 +1,17 @@
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {EventListenerTypes} from "../../metadata/types/EventListenerTypes";
import {EntityListenerMetadata} from "../../metadata/EntityListenerMetadata";
import {EntityListenerMetadataArgs} from "../../metadata-args/EntityListenerMetadataArgs";
/**
* Calls a method on which this decorator is applied before this entity update.
*/
export function BeforeUpdate() {
return function (object: Object, propertyName: string) {
defaultMetadataStorage().entityListenerMetadatas.add(new EntityListenerMetadata(
object.constructor,
propertyName,
EventListenerTypes.BEFORE_UPDATE
));
const args: EntityListenerMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.BEFORE_UPDATE
};
getMetadataArgsStorage().entityListeners.add(args);
};
}

View File

@ -1,5 +1,5 @@
import {defaultMetadataStorage} from "../../index";
import {EventSubscriberMetadata} from "../../metadata/EventSubscriberMetadata";
import {getMetadataArgsStorage} from "../../index";
import {EventSubscriberMetadataArgs} from "../../metadata-args/EventSubscriberMetadataArgs";
/**
* Classes decorated with this decorator will listen to ORM events and their methods will be triggered when event
@ -7,6 +7,9 @@ import {EventSubscriberMetadata} from "../../metadata/EventSubscriberMetadata";
*/
export function EventSubscriber() {
return function (target: Function) {
defaultMetadataStorage().eventSubscriberMetadatas.add(new EventSubscriberMetadata(target));
const args: EventSubscriberMetadataArgs = {
target: target
};
getMetadataArgsStorage().eventSubscribers.add(args);
};
}

View File

@ -1,72 +1,78 @@
import {ColumnType} from "../types/ColumnTypes";
import {ColumnType} from "../../metadata/types/ColumnTypes";
/**
* Describes all column's options.
*/
export interface ColumnOptions {
/**
* Column name.
*/
name?: string;
/**
* Column type. Must be one of the value from the ColumnTypes class.
*/
type?: ColumnType;
/**
* Column name.
*/
readonly name?: string;
/**
* Column type's length. For example type = "string" and length = 100 means that ORM will create a column with
* type varchar(100).
*/
length?: string;
readonly length?: string;
/**
* Specifies if this column will use AUTO_INCREMENT or not (e.g. generated number).
*/
generated?: boolean;
readonly generated?: boolean;
/**
* Specifies if column's value must be unique or not.
*/
unique?: boolean;
readonly unique?: boolean;
/**
* Indicates if column's value can be set to NULL.
*/
nullable?: boolean;
readonly nullable?: boolean;
/**
* Extra column definition. Should be used only in emergency situations. Note that if you'll use this property
* auto schema generation will not work properly anymore.
*/
columnDefinition?: string;
readonly columnDefinition?: string;
/**
* Column comment.
*/
comment?: string;
readonly comment?: string;
/**
* Column name used previously for this column. Used to make safe schema updates. Experimental and most probably
* will be removed in the future. Avoid using it.
*/
oldColumnName?: string;
readonly oldColumnName?: string;
/**
* The precision for a decimal (exact numeric) column (applies only for decimal column), which is the maximum
* number of digits that are stored for the values.
*/
precision?: number;
readonly precision?: number;
/**
* The scale for a decimal (exact numeric) column (applies only for decimal column), which represents the number
* of digits to the right of the decimal point and must not be greater than precision.
*/
scale?: number;
readonly scale?: number;
/**
* Column collation. Note that not all databases support it.
*/
collation?: string;
readonly collation?: string; // todo: looks like this is not used
/**
* Indicates if this column is order id column.
*/
readonly isOrderId?: boolean; // todo: looks like this is not implemented yet
}

View File

@ -6,6 +6,6 @@ export interface CompositeIndexOptions {
/**
* Indicates if this composite index must be unique or not.
*/
unique?: boolean;
readonly unique?: boolean;
}

View File

@ -6,11 +6,11 @@ export interface JoinColumnOptions {
/**
* Name of the column.
*/
name?: string;
readonly name?: string;
/**
* Name of the column in the entity to which this column is referenced.
*/
referencedColumnName?: string;
readonly referencedColumnName?: string;
}

View File

@ -9,16 +9,16 @@ export interface JoinTableOptions {
* Name of the table that will be created to store values of the both tables (join table).
* By default is auto generated.
*/
name?: string;
readonly name?: string;
/**
* First column of the join table.
*/
joinColumn?: JoinColumnOptions;
readonly joinColumn?: JoinColumnOptions;
/**
* Second (inverse) column of the join table.
*/
inverseJoinColumn?: JoinColumnOptions;
readonly inverseJoinColumn?: JoinColumnOptions;
}

View File

@ -5,46 +5,41 @@ import {OnDeleteType} from "../../metadata/ForeignKeyMetadata";
*/
export interface RelationOptions {
/**
* Field name to be used in the database.
*/
name?: string;
/**
* If set to true then it means that related object can be allowed to be inserted / updated / removed to the db.
* This is option a shortcut if you would like to set cascadeInsert, cascadeUpdate and cascadeRemove to true.
*/
cascadeAll?: boolean;
readonly cascadeAll?: boolean;
/**
* If set to true then it means that related object can be allowed to be inserted to the db.
*/
cascadeInsert?: boolean;
readonly cascadeInsert?: boolean;
/**
* If set to true then it means that related object can be allowed to be updated in the db.
*/
cascadeUpdate?: boolean;
readonly cascadeUpdate?: boolean;
/**
* If set to true then it means that related object can be allowed to be remove from the db.
*/
cascadeRemove?: boolean;
readonly cascadeRemove?: boolean;
/**
* Column name used previously for this column. Used to make safe schema updates. Experimental and most probably
* will be removed in the future. Avoid using it.
*/
oldColumnName?: string;
readonly oldColumnName?: string;
/**
* Indicates if relation column value can be nullable or not.
*/
nullable?: boolean;
readonly nullable?: boolean;
/**
* Database cascade action on delete.
*/
onDelete?: OnDeleteType;
readonly onDelete?: OnDeleteType;
}

View File

@ -1,14 +1,19 @@
import {defaultMetadataStorage} from "../../index";
import {JoinColumnMetadata} from "../../metadata/JoinColumnMetadata";
import {JoinColumnOptions} from "../../metadata/options/JoinColumnOptions";
import {getMetadataArgsStorage} from "../../index";
import {JoinColumnOptions} from "../options/JoinColumnOptions";
import {JoinColumnMetadataArgs} from "../../metadata-args/JoinColumnMetadataArgs";
/**
*/
export function JoinColumn(options?: JoinColumnOptions): Function {
return function (object: Object, propertyName: string) {
options = options || {} as JoinColumnOptions;
const metadata = new JoinColumnMetadata(object.constructor, propertyName, options);
defaultMetadataStorage().joinColumnMetadatas.add(metadata);
const args: JoinColumnMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
name: options.name,
referencedColumnName: options.referencedColumnName
};
getMetadataArgsStorage().joinColumns.add(args);
};
}

View File

@ -1,14 +1,20 @@
import {defaultMetadataStorage} from "../../index";
import {JoinTableOptions} from "../../metadata/options/JoinTableOptions";
import {JoinTableMetadata} from "../../metadata/JoinTableMetadata";
import {getMetadataArgsStorage} from "../../index";
import {JoinTableOptions} from "../options/JoinTableOptions";
import {JoinTableMetadataArgs} from "../../metadata-args/JoinTableMetadataArgs";
/**
*/
export function JoinTable(options?: JoinTableOptions): Function {
return function (object: Object, propertyName: string) {
options = options || {} as JoinTableOptions;
const metadata = new JoinTableMetadata(object.constructor, propertyName, options);
defaultMetadataStorage().joinTableMetadatas.add(metadata);
const args: JoinTableMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
name: options.name,
joinColumn: options.joinColumn,
inverseJoinColumn: options.inverseJoinColumn
};
getMetadataArgsStorage().joinTables.add(args);
};
}

View File

@ -1,8 +1,8 @@
import {RelationMetadata} from "../../metadata/RelationMetadata";
import {RelationOptions} from "../../metadata/options/RelationOptions";
import {RelationOptions} from "../options/RelationOptions";
import {RelationTypes} from "../../metadata/types/RelationTypes";
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {ConstructorFunction} from "../../common/ConstructorFunction";
import {RelationMetadataArgs} from "../../metadata-args/RelationMetadataArgs";
/**
* Many-to-many is a type of relationship when Entity1 can have multiple instances of Entity2, and Entity2 can have
@ -38,9 +38,9 @@ export function ManyToMany<T>(typeFunction: (type?: any) => ConstructorFunction<
return function (object: Object, propertyName: string) {
if (!options) options = {} as RelationOptions;
const reflectedType = (<any> Reflect).getMetadata("design:type", object, propertyName);
const reflectedType = (Reflect as any).getMetadata("design:type", object, propertyName);
defaultMetadataStorage().relationMetadatas.add(new RelationMetadata({
const args: RelationMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
@ -48,7 +48,8 @@ export function ManyToMany<T>(typeFunction: (type?: any) => ConstructorFunction<
type: typeFunction,
inverseSideProperty: inverseSideProperty,
options: options
}));
};
getMetadataArgsStorage().relations.add(args);
};
}

View File

@ -1,8 +1,8 @@
import {RelationMetadata} from "../../metadata/RelationMetadata";
import {RelationOptions} from "../../metadata/options/RelationOptions";
import {RelationOptions} from "../options/RelationOptions";
import {RelationTypes} from "../../metadata/types/RelationTypes";
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {ConstructorFunction} from "../../common/ConstructorFunction";
import {RelationMetadataArgs} from "../../metadata-args/RelationMetadataArgs";
/**
* Many-to-one relation allows to create type of relation when Entity1 can have single instance of Entity2, but
@ -38,9 +38,9 @@ export function ManyToOne<T>(typeFunction: (type?: any) => ConstructorFunction<T
return function (object: Object, propertyName: string) {
if (!options) options = {} as RelationOptions;
const reflectedType = (<any> Reflect).getMetadata("design:type", object, propertyName);
const reflectedType = (Reflect as any).getMetadata("design:type", object, propertyName);
defaultMetadataStorage().relationMetadatas.add(new RelationMetadata({
const args: RelationMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
@ -48,6 +48,7 @@ export function ManyToOne<T>(typeFunction: (type?: any) => ConstructorFunction<T
type: typeFunction,
inverseSideProperty: inverseSideProperty,
options: options
}));
};
getMetadataArgsStorage().relations.add(args);
};
}

View File

@ -1,8 +1,8 @@
import {RelationMetadata} from "../../metadata/RelationMetadata";
import {RelationOptions} from "../../metadata/options/RelationOptions";
import {RelationOptions} from "../options/RelationOptions";
import {RelationTypes} from "../../metadata/types/RelationTypes";
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {ConstructorFunction} from "../../common/ConstructorFunction";
import {RelationMetadataArgs} from "../../metadata-args/RelationMetadataArgs";
// todo: make decorators which use inverse side string separate
@ -40,9 +40,9 @@ export function OneToMany<T>(typeFunction: (type?: any) => ConstructorFunction<T
return function (object: Object, propertyName: string) {
if (!options) options = {} as RelationOptions;
const reflectedType = (<any> Reflect).getMetadata("design:type", object, propertyName);
const reflectedType = (Reflect as any).getMetadata("design:type", object, propertyName);
defaultMetadataStorage().relationMetadatas.add(new RelationMetadata({
const args: RelationMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
@ -50,7 +50,8 @@ export function OneToMany<T>(typeFunction: (type?: any) => ConstructorFunction<T
type: typeFunction,
inverseSideProperty: inverseSideProperty,
options: options
}));
};
getMetadataArgsStorage().relations.add(args);
};
}

View File

@ -1,8 +1,8 @@
import {RelationMetadata} from "../../metadata/RelationMetadata";
import {RelationOptions} from "../../metadata/options/RelationOptions";
import {RelationOptions} from "../options/RelationOptions";
import {RelationTypes} from "../../metadata/types/RelationTypes";
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {ConstructorFunction} from "../../common/ConstructorFunction";
import {RelationMetadataArgs} from "../../metadata-args/RelationMetadataArgs";
/**
* One-to-one relation allows to create direct relation between two entities. Entity1 have only one Entity2.
@ -35,9 +35,9 @@ export function OneToOne<T>(typeFunction: (type?: any) => ConstructorFunction<T>
return function (object: Object, propertyName: string) {
if (!options) options = {} as RelationOptions;
const reflectedType = (<any> Reflect).getMetadata("design:type", object, propertyName);
const reflectedType = (Reflect as any).getMetadata("design:type", object, propertyName);
defaultMetadataStorage().relationMetadatas.add(new RelationMetadata({
const args: RelationMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
@ -45,6 +45,7 @@ export function OneToOne<T>(typeFunction: (type?: any) => ConstructorFunction<T>
type: typeFunction,
inverseSideProperty: inverseSideProperty,
options: options
}));
};
getMetadataArgsStorage().relations.add(args);
};
}

View File

@ -1,11 +1,16 @@
import {TableMetadata} from "../../metadata/TableMetadata";
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {TableMetadataArgs} from "../../metadata-args/TableMetadataArgs";
/**
* Allows to use columns and relations data from the inherited metadata.
*/
export function AbstractTable() {
return function (cls: Function) {
defaultMetadataStorage().tableMetadatas.add(new TableMetadata(cls, undefined, "abstract"));
return function (target: Function) {
const args: TableMetadataArgs = {
target: target,
name: undefined,
type: "abstract"
};
getMetadataArgsStorage().tables.add(args);
};
}

View File

@ -1,12 +1,17 @@
import {defaultMetadataStorage} from "../../index";
import {TableMetadata} from "../../metadata/TableMetadata";
import {getMetadataArgsStorage} from "../../index";
import {TableMetadataArgs} from "../../metadata-args/TableMetadataArgs";
/**
* This decorator is used to mark classes that will be a tables. Database schema will be created for all classes
* decorated with it, and Repository can be retrieved and used for it.
*/
export function ClosureTable(name?: string) {
return function (cls: Function) {
defaultMetadataStorage().tableMetadatas.add(new TableMetadata(cls, name, "closure"));
return function (target: Function) {
const args: TableMetadataArgs = {
target: target,
name: name,
type: "closure"
};
getMetadataArgsStorage().tables.add(args);
};
}

View File

@ -1,12 +1,17 @@
import {defaultMetadataStorage} from "../../index";
import {TableMetadata} from "../../metadata/TableMetadata";
import {getMetadataArgsStorage} from "../../index";
import {TableMetadataArgs} from "../../metadata-args/TableMetadataArgs";
/**
* This decorator is used to mark classes that will be a tables. Database schema will be created for all classes
* decorated with it, and Repository can be retrieved and used for it.
*/
export function Table(name?: string) {
return function (cls: Function) {
defaultMetadataStorage().tableMetadatas.add(new TableMetadata(cls, name));
return function (target: Function) {
const args: TableMetadataArgs = {
target: target,
name: name,
type: "regular"
};
getMetadataArgsStorage().tables.add(args);
};
}

View File

@ -1,7 +1,7 @@
import {defaultMetadataStorage} from "../../index";
import {RelationOptions} from "../../metadata/options/RelationOptions";
import {RelationMetadata} from "../../metadata/RelationMetadata";
import {getMetadataArgsStorage} from "../../index";
import {RelationOptions} from "../options/RelationOptions";
import {RelationTypes} from "../../metadata/types/RelationTypes";
import {RelationMetadataArgs} from "../../metadata-args/RelationMetadataArgs";
/**
* Marks a specific property of the class as a children of the tree.
@ -10,10 +10,10 @@ export function TreeChildren(options?: RelationOptions): Function {
return function (object: Object, propertyName: string) {
if (!options) options = {} as RelationOptions;
const reflectedType = (<any> Reflect).getMetadata("design:type", object, propertyName);
const reflectedType = (Reflect as any).getMetadata("design:type", object, propertyName);
// add one-to-many relation for this
defaultMetadataStorage().relationMetadatas.add(new RelationMetadata({
const args: RelationMetadataArgs = {
isTreeChildren: true,
target: object.constructor,
propertyName: propertyName,
@ -21,7 +21,8 @@ export function TreeChildren(options?: RelationOptions): Function {
relationType: RelationTypes.ONE_TO_MANY,
type: () => object.constructor,
options: options
}));
};
getMetadataArgsStorage().relations.add(args);
};
}

View File

@ -1,7 +1,7 @@
import {defaultMetadataStorage} from "../../index";
import {getMetadataArgsStorage} from "../../index";
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
import {ColumnOptions} from "../../metadata/options/ColumnOptions";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {ColumnOptions} from "../options/ColumnOptions";
import {ColumnMetadataArgs} from "../../metadata-args/ColumnMetadataArgs";
/**
* Creates a "level"/"length" column to the table that holds a closure table.
@ -9,7 +9,7 @@ import {ColumnMetadata} from "../../metadata/ColumnMetadata";
export function TreeLevelColumn(): Function {
return function (object: Object, propertyName: string) {
const reflectedType = ColumnTypes.typeToString((<any> Reflect).getMetadata("design:type", object, propertyName));
const reflectedType = ColumnTypes.typeToString((Reflect as any).getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
const options: ColumnOptions = {};
@ -18,13 +18,14 @@ export function TreeLevelColumn(): Function {
options.type = ColumnTypes.INTEGER;
// create and register a new column metadata
defaultMetadataStorage().columnMetadatas.add(new ColumnMetadata({
const args: ColumnMetadataArgs = {
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
mode: "treeLevel",
options: options
}));
};
getMetadataArgsStorage().columns.add(args);
};
}

View File

@ -1,7 +1,7 @@
import {defaultMetadataStorage} from "../../index";
import {RelationOptions} from "../../metadata/options/RelationOptions";
import {RelationMetadata} from "../../metadata/RelationMetadata";
import {getMetadataArgsStorage} from "../../index";
import {RelationOptions} from "../options/RelationOptions";
import {RelationTypes} from "../../metadata/types/RelationTypes";
import {RelationMetadataArgs} from "../../metadata-args/RelationMetadataArgs";
/**
* Marks a specific property of the class as a parent of the tree.
@ -10,8 +10,8 @@ export function TreeParent(options?: RelationOptions): Function {
return function (object: Object, propertyName: string) {
if (!options) options = {} as RelationOptions;
const reflectedType = (<any> Reflect).getMetadata("design:type", object, propertyName);
defaultMetadataStorage().relationMetadatas.add(new RelationMetadata({
const reflectedType = (Reflect as any).getMetadata("design:type", object, propertyName);
const args: RelationMetadataArgs = {
isTreeParent: true,
target: object.constructor,
propertyName: propertyName,
@ -19,7 +19,8 @@ export function TreeParent(options?: RelationOptions): Function {
relationType: RelationTypes.MANY_TO_ONE,
type: () => object.constructor,
options: options
}));
};
getMetadataArgsStorage().relations.add(args);
};
}

View File

@ -254,9 +254,9 @@ export class MysqlDriver extends BaseDriver implements Driver {
case ColumnTypes.DATE:
return moment(value).format("YYYY-MM-DD");
case ColumnTypes.TIME:
return moment(value).format("hh:mm:ss");
return moment(value).format("HH:mm:ss");
case ColumnTypes.DATETIME:
return moment(value).format("YYYY-MM-DD hh:mm:ss");
return moment(value).format("YYYY-MM-DD HH:mm:ss");
case ColumnTypes.JSON:
return JSON.stringify(value);
case ColumnTypes.SIMPLE_ARRAY:
@ -273,6 +273,9 @@ export class MysqlDriver extends BaseDriver implements Driver {
*/
prepareHydratedValue(value: any, column: ColumnMetadata): any {
switch (column.type) {
case ColumnTypes.BOOLEAN:
return value ? true : false;
case ColumnTypes.DATE:
if (value instanceof Date)
return value;
@ -280,13 +283,13 @@ export class MysqlDriver extends BaseDriver implements Driver {
return moment(value, "YYYY-MM-DD").toDate();
case ColumnTypes.TIME:
return moment(value, "hh:mm:ss").toDate();
return moment(value, "HH:mm:ss").toDate();
case ColumnTypes.DATETIME:
if (value instanceof Date)
return value;
return moment(value, "YYYY-MM-DD hh:mm:ss").toDate();
return moment(value, "YYYY-MM-DD HH:mm:ss").toDate();
case ColumnTypes.JSON:
return JSON.parse(value);

View File

@ -268,6 +268,66 @@ export class EntityManager {
.then(() => runInTransactionResult);
}
/**
* Sets given relatedEntityId to the value of the relation of the entity with entityId id.
* Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
setRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string, entityId: any, relatedEntityId: any): Promise<void>;
setRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: ((t: Entity) => string|any), entityId: any, relatedEntityId: any): Promise<void>;
setRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityId: any): Promise<void> {
return this.getRepository(entityClass).setRelation(relationName as any, entityId, relatedEntityId);
}
/**
* Adds a new relation between two entities into relation's many-to-many table.
* Should be used when you want quickly and efficiently add a relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
addToRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string, entityId: any, relatedEntityIds: any[]): Promise<void>;
addToRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void>;
addToRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void> {
return this.getRepository(entityClass).addToRelation(relationName as any, entityId, relatedEntityIds);
}
/**
* Removes a relation between two entities from relation's many-to-many table.
* Should be used when you want quickly and efficiently remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string, entityId: any, relatedEntityIds: any[]): Promise<void>;
removeFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void>;
removeFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void> {
return this.getRepository(entityClass).removeFromRelation(relationName as any, entityId, relatedEntityIds);
}
/**
* Performs both #addToRelation and #removeFromRelation operations.
* Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
addAndRemoveFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relation: string, entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise<void>;
addAndRemoveFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relation: ((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise<void>;
addAndRemoveFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relation: string|((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise<void> {
return this.getRepository(entityClass).addAndRemoveFromRelation(relation as any, entityId, addRelatedEntityIds, removeRelatedEntityIds);
}
/**
* Removes entity with the given id.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeById<Entity>(entityClass: ConstructorFunction<Entity>|Function, id: any): Promise<void> {
return this.getRepository(entityClass).removeById(id);
}
/**
* Removes all entities with the given ids.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeByIds<Entity>(entityClass: ConstructorFunction<Entity>|Function, ids: any[]): Promise<void> {
return this.getRepository(entityClass).removeByIds(ids);
}
/**
* Roots are entities that have no ancestors. Finds them all.
*/
@ -331,5 +391,4 @@ export class EntityManager {
return this.getTreeRepository(entityClass).countAncestors(entity);
}
}

View File

@ -0,0 +1,23 @@
import {Connection} from "../connection/Connection";
import {EntityManager} from "./EntityManager";
import {ReactiveEntityManager} from "./ReactiveEntityManager";
/**
* Entity manager supposed to work with any entity, automatically find its repository and call its method, whatever
* entity type are you passing.
*/
export class EntityManagerFactory {
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
createEntityManager(connection: Connection) {
return new EntityManager(connection);
}
createReactiveEntityManager(connection: Connection) {
return new ReactiveEntityManager(connection);
}
}

View File

@ -241,10 +241,70 @@ export class ReactiveEntityManager {
.then(() => runInTransactionResult));
}
/**
* Sets given relatedEntityId to the value of the relation of the entity with entityId id.
* Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
setRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string, entityId: any, relatedEntityId: any): Rx.Observable<void>;
setRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: ((t: Entity) => string|any), entityId: any, relatedEntityId: any): Rx.Observable<void>;
setRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityId: any): Rx.Observable<void> {
return this.getReactiveRepository(entityClass).setRelation(relationName as any, entityId, relatedEntityId);
}
/**
* Adds a new relation between two entities into relation's many-to-many table.
* Should be used when you want quickly and efficiently add a relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
addToRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string, entityId: any, relatedEntityIds: any[]): Rx.Observable<void>;
addToRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Rx.Observable<void>;
addToRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Rx.Observable<void> {
return this.getReactiveRepository(entityClass).addToRelation(relationName as any, entityId, relatedEntityIds);
}
/**
* Removes a relation between two entities from relation's many-to-many table.
* Should be used when you want quickly and efficiently remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string, entityId: any, relatedEntityIds: any[]): Rx.Observable<void>;
removeFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Rx.Observable<void>;
removeFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Rx.Observable<void> {
return this.getReactiveRepository(entityClass).removeFromRelation(relationName as any, entityId, relatedEntityIds);
}
/**
* Performs both #addToRelation and #removeFromRelation operations.
* Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
addAndRemoveFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relation: string, entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Rx.Observable<void>;
addAndRemoveFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relation: ((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Rx.Observable<void>;
addAndRemoveFromRelation<Entity>(entityClass: ConstructorFunction<Entity>|Function, relation: string|((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Rx.Observable<void> {
return this.getReactiveRepository(entityClass).addAndRemoveFromRelation(relation as any, entityId, addRelatedEntityIds, removeRelatedEntityIds);
}
/**
* Removes entity with the given id.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeById<Entity>(entityClass: ConstructorFunction<Entity>|Function, id: any): Rx.Observable<void> {
return this.getReactiveRepository(entityClass).removeById(id);
}
/**
* Removes all entities with the given ids.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeByIds<Entity>(entityClass: ConstructorFunction<Entity>|Function, ids: any[]): Rx.Observable<void> {
return this.getReactiveRepository(entityClass).removeByIds(ids);
}
/**
* Roots are entities that have no ancestors. Finds them all.
*/
findRoots<Entity>(entityClass: ConstructorFunction<Entity>|Function): Promise<Entity[]> {
findRoots<Entity>(entityClass: ConstructorFunction<Entity>|Function): Rx.Observable<Entity[]> {
return this.getReactiveTreeRepository(entityClass).findRoots();
}
@ -258,21 +318,21 @@ export class ReactiveEntityManager {
/**
* Gets all children (descendants) of the given entity. Returns them all in a flat array.
*/
findDescendants<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Promise<Entity[]> {
findDescendants<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Rx.Observable<Entity[]> {
return this.getReactiveTreeRepository(entityClass).findDescendants(entity);
}
/**
* Gets all children (descendants) of the given entity. Returns them in a tree - nested into each other.
*/
findDescendantsTree<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Promise<Entity> {
findDescendantsTree<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Rx.Observable<Entity> {
return this.getReactiveTreeRepository(entityClass).findDescendantsTree(entity);
}
/**
* Gets number of descendants of the entity.
*/
countDescendants<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Promise<number> {
countDescendants<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Rx.Observable<number> {
return this.getReactiveTreeRepository(entityClass).countDescendants(entity);
}
@ -286,21 +346,21 @@ export class ReactiveEntityManager {
/**
* Gets all parents (ancestors) of the given entity. Returns them all in a flat array.
*/
findAncestors<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Promise<Entity[]> {
findAncestors<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Rx.Observable<Entity[]> {
return this.getReactiveTreeRepository(entityClass).findAncestors(entity);
}
/**
* Gets all parents (ancestors) of the given entity. Returns them in a tree - nested into each other.
*/
findAncestorsTree<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Promise<Entity> {
findAncestorsTree<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Rx.Observable<Entity> {
return this.getReactiveTreeRepository(entityClass).findAncestorsTree(entity);
}
/**
* Gets number of ancestors of the entity.
*/
countAncestors<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Promise<number> {
countAncestors<Entity>(entityClass: ConstructorFunction<Entity>|Function, entity: Entity): Rx.Observable<number> {
return this.getReactiveTreeRepository(entityClass).countAncestors(entity);
}

View File

@ -6,7 +6,7 @@ import {ConnectionOptions} from "./connection/ConnectionOptions";
import {ConnectionManager} from "./connection-manager/ConnectionManager";
import {Connection} from "./connection/Connection";
import {MysqlDriver} from "./driver/MysqlDriver";
import {MetadataStorage} from "./metadata-storage/MetadataStorage";
import {MetadataArgsStorage} from "./metadata-args/MetadataArgsStorage";
import {CreateConnectionOptions} from "./connection-manager/CreateConnectionOptions";
// -------------------------------------------------------------------------
@ -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);
}
// -------------------------------------------------------------------------
@ -38,17 +50,20 @@ export function getContainer() {
/**
* Default metadata storage used as singleton and can be used to storage all metadatas in the system.
*/
let metadataStorage: MetadataStorage;
let metadataArgsStorage: MetadataArgsStorage;
export function defaultMetadataStorage() {
if (!metadataStorage && container) {
metadataStorage = container.get(MetadataStorage);
/**
* Gets metadata args storage.
*/
export function getMetadataArgsStorage() {
if (!metadataArgsStorage && container) {
metadataArgsStorage = container.get(MetadataArgsStorage);
} else if (!metadataStorage) {
metadataStorage = new MetadataStorage();
} else if (!metadataArgsStorage) {
metadataArgsStorage = new MetadataArgsStorage();
}
return metadataStorage;
return metadataArgsStorage;
}
// -------------------------------------------------------------------------

View File

@ -0,0 +1,34 @@
import {ColumnOptions} from "../decorator/options/ColumnOptions";
import {ColumnMode} from "../metadata/ColumnMetadata";
/**
* Arguments for ColumnMetadata class.
*/
export interface ColumnMetadataArgs {
/**
* Class to which column is applied.
*/
readonly target?: Function;
/**
* Class's property name to which column is applied.
*/
readonly propertyName?: string;
/**
* Class's property type (reflected) to which column is applied.
*/
readonly propertyType: string;
/**
* Column mode in which column will work.
*/
readonly mode: ColumnMode;
/**
* Extra column options.
*/
readonly options: ColumnOptions;
}

View File

@ -0,0 +1,23 @@
import {EventListenerType} from "../metadata/types/EventListenerTypes";
/**
* Arguments for EntityListenerMetadata class.
*/
export interface EntityListenerMetadataArgs {
/**
* Class to which listener is applied.
*/
readonly target: Function;
/**
* Class's property name to which listener is applied.
*/
readonly propertyName: string;
/**
* The type of the listener.
*/
readonly type: EventListenerType;
}

View File

@ -0,0 +1,12 @@
/**
* Arguments for EventSubscriberMetadata class.
*/
export interface EventSubscriberMetadataArgs {
/**
* Class to which subscriber is applied.
*/
readonly target: Function;
}

View File

@ -0,0 +1,27 @@
/**
* Arguments for IndexMetadata class.
*/
export interface IndexMetadataArgs {
/**
* Class to which index is applied.
*/
readonly target: Function;
/**
* Index name.
*/
readonly name?: string;
/**
* Columns combination to be used as index.
*/
readonly columns: ((object: any) => any[])|string[];
/**
* Indicates if index must be unique or not.
*/
readonly unique: boolean;
}

View File

@ -0,0 +1,26 @@
/**
* Arguments for JoinColumnMetadata class.
*/
export interface JoinColumnMetadataArgs {
/**
* Class to which this column is applied.
*/
readonly target: Function;
/**
* Class's property name to which this column is applied.
*/
readonly propertyName: string;
/**
* Name of the column.
*/
readonly name?: string;
/**
* Name of the column in the entity to which this column is referenced.
*/
readonly referencedColumnName?: string;
}

View File

@ -0,0 +1,34 @@
import {JoinColumnOptions} from "../decorator/options/JoinColumnOptions";
/**
* Arguments for JoinTableMetadata class.
*/
export interface JoinTableMetadataArgs {
/**
* Class to which this column is applied.
*/
readonly target: Function;
/**
* Class's property name to which this column is applied.
*/
readonly propertyName: string;
/**
* Name of the table that will be created to store values of the both tables (join table).
* By default is auto generated.
*/
readonly name?: string;
/**
* First column of the join table.
*/
readonly joinColumn?: JoinColumnOptions;
/**
* Second (inverse) column of the join table.
*/
readonly inverseJoinColumn?: JoinColumnOptions;
}

View File

@ -0,0 +1,128 @@
import {TargetMetadataArgsCollection} from "./collection/TargetMetadataArgsCollection";
import {PropertyMetadataArgsCollection} from "./collection/PropertyMetadataArgsCollection";
import {RelationMetadataArgs} from "./RelationMetadataArgs";
import {ColumnMetadataArgs} from "./ColumnMetadataArgs";
import {RelationsCountMetadataArgs} from "./RelationsCountMetadataArgs";
import {IndexMetadataArgs} from "./IndexMetadataArgs";
import {EntityListenerMetadataArgs} from "./EntityListenerMetadataArgs";
import {TableMetadataArgs} from "./TableMetadataArgs";
import {NamingStrategyMetadataArgs} from "./NamingStrategyMetadataArgs";
import {EventSubscriberMetadataArgs} from "./EventSubscriberMetadataArgs";
import {JoinTableMetadataArgs} from "./JoinTableMetadataArgs";
import {JoinColumnMetadataArgs} from "./JoinColumnMetadataArgs";
/**
* Storage all metadatas of all available types: tables, fields, subscribers, relations, etc.
* Each metadata represents some specifications of what it represents.
*/
export class MetadataArgsStorage {
// todo: type in function validation, inverse side function validation
// todo: check on build for duplicate names, since naming checking was removed from MetadataStorage
// todo: duplicate name checking for: table, relation, column, index, naming strategy, join tables/columns?
// todo: check for duplicate targets too since this check has been removed too
// -------------------------------------------------------------------------
// Properties
// -------------------------------------------------------------------------
readonly tables = new TargetMetadataArgsCollection<TableMetadataArgs>();
readonly namingStrategies = new TargetMetadataArgsCollection<NamingStrategyMetadataArgs>();
readonly eventSubscribers = new TargetMetadataArgsCollection<EventSubscriberMetadataArgs>();
readonly indices = new TargetMetadataArgsCollection<IndexMetadataArgs>();
readonly columns = new PropertyMetadataArgsCollection<ColumnMetadataArgs>();
readonly relations = new PropertyMetadataArgsCollection<RelationMetadataArgs>();
readonly joinColumns = new PropertyMetadataArgsCollection<JoinColumnMetadataArgs>();
readonly joinTables = new PropertyMetadataArgsCollection<JoinTableMetadataArgs>();
readonly entityListeners = new PropertyMetadataArgsCollection<EntityListenerMetadataArgs>();
readonly relationCounts = new PropertyMetadataArgsCollection<RelationsCountMetadataArgs>();
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Gets merged (with all abstract classes) table metadatas for the given classes.
*/
getMergedTableMetadatas(classes: Function[]) {
const allTableMetadataArgs = this.tables.filterByClasses(classes);
const tableMetadatas = this.tables.filterByClasses(classes).filter(table => table.type !== "abstract");
return tableMetadatas.map(tableMetadata => {
return this.mergeWithAbstract(allTableMetadataArgs, tableMetadata);
});
}
// -------------------------------------------------------------------------
// Private Methods
// -------------------------------------------------------------------------
/**
* Creates a new copy of the MetadataStorage with same metadatas as in current metadata storage, but filtered
* by classes.
*/
private mergeWithAbstract(allTableMetadatas: TargetMetadataArgsCollection<TableMetadataArgs>,
tableMetadata: TableMetadataArgs) {
const indices = this.indices.filterByClass(tableMetadata.target);
const columns = this.columns.filterByClass(tableMetadata.target);
const relations = this.relations.filterByClass(tableMetadata.target);
const joinColumns = this.joinColumns.filterByClass(tableMetadata.target);
const joinTables = this.joinTables.filterByClass(tableMetadata.target);
const entityListeners = this.entityListeners.filterByClass(tableMetadata.target);
const relationCounts = this.relationCounts.filterByClass(tableMetadata.target);
allTableMetadatas
.filter(metadata => this.isInherited(tableMetadata.target, metadata.target))
.forEach(parentMetadata => {
const metadatasFromAbstract = this.mergeWithAbstract(allTableMetadatas, parentMetadata);
metadatasFromAbstract.columns
.filterRepeatedMetadatas(columns)
.forEach(metadata => columns.push(metadata));
metadatasFromAbstract.relations
.filterRepeatedMetadatas(relations)
.forEach(metadata => relations.push(metadata));
metadatasFromAbstract.joinColumns
.filterRepeatedMetadatas(joinColumns)
.forEach(metadata => joinColumns.push(metadata));
metadatasFromAbstract.joinTables
.filterRepeatedMetadatas(joinTables)
.forEach(metadata => joinTables.push(metadata));
metadatasFromAbstract.entityListeners
.filterRepeatedMetadatas(entityListeners)
.forEach(metadata => entityListeners.push(metadata));
metadatasFromAbstract.relationCounts
.filterRepeatedMetadatas(relationCounts)
.forEach(metadata => relationCounts.push(metadata));
});
return {
table: tableMetadata,
indices: indices,
columns: columns,
relations: relations,
joinColumns: joinColumns,
joinTables: joinTables,
entityListeners: entityListeners,
relationCounts: relationCounts
};
}
/**
* Checks if this table is inherited from another table.
*/
private isInherited(target1: Function, target2: Function) {
// we cannot use instanceOf in this method, because we need order of inherited tables, to ensure that
// properties get inherited in a right order. To achieve it we can only check a first parent of the class
// return this.target.prototype instanceof anotherTable.target;
return Object.getPrototypeOf(target1.prototype).constructor === target2;
}
}

View File

@ -0,0 +1,16 @@
/**
* Arguments for NamingStrategyMetadata class.
*/
export interface NamingStrategyMetadataArgs {
/**
* Class to which this column is applied.
*/
readonly target: Function;
/**
* Strategy name.
*/
readonly name: string;
}

View File

@ -1,56 +1,56 @@
import {RelationType} from "../types/RelationTypes";
import {RelationOptions} from "../options/RelationOptions";
import {PropertyTypeInFunction, RelationTypeInFunction} from "../RelationMetadata";
import {RelationType} from "../metadata/types/RelationTypes";
import {RelationOptions} from "../decorator/options/RelationOptions";
import {PropertyTypeInFunction, RelationTypeInFunction} from "../metadata/RelationMetadata";
/**
* Relation metadata constructor arguments.
* Arguments for RelationMetadata class.
*/
export interface RelationMetadataArgs {
/**
* Class to which this relation is applied.
*/
target: Function;
readonly target: Function;
/**
* Class's property name to which this relation is applied.
*/
propertyName: string;
readonly propertyName: string;
/**
* Original (reflected) class's property type.
*/
propertyType: any;
readonly propertyType: any;
/**
* Type of relation. Can be one of the value of the RelationTypes class.
*/
relationType: RelationType;
readonly relationType: RelationType;
/**
* Type of the relation. This type is in function because of language specifics and problems with recursive
* referenced classes.
*/
type: RelationTypeInFunction;
readonly type: RelationTypeInFunction;
/**
* Inverse side of the relation.
*/
inverseSideProperty?: PropertyTypeInFunction<any>;
readonly inverseSideProperty?: PropertyTypeInFunction<any>;
/**
* Additional relation options.
*/
options: RelationOptions;
readonly options: RelationOptions;
/**
* Indicates if this is a parent (can be only many-to-one relation) relation in the tree tables.
*/
isTreeParent?: boolean;
readonly isTreeParent?: boolean;
/**
* Indicates if this is a children (can be only one-to-many relation) relation in the tree tables.
*/
isTreeChildren?: boolean;
readonly isTreeChildren?: boolean;
}

View File

@ -0,0 +1,21 @@
/**
* Arguments for RelationsCountMetadata class.
*/
export interface RelationsCountMetadataArgs {
/**
* Class to which this column is applied.
*/
readonly target: Function;
/**
* Class's property name to which this column is applied.
*/
readonly propertyName: string;
/**
* Target's relation which it should count.
*/
readonly relation: string|((object: any) => any);
}

View File

@ -0,0 +1,23 @@
import {TableType} from "../metadata/TableMetadata";
/**
* Arguments for TableMetadata class.
*/
export interface TableMetadataArgs {
/**
* Class to which table is applied.
*/
readonly target: Function;
/**
* Table name.
*/
readonly name?: string;
/**
* Table type.
*/
readonly type: TableType;
}

View File

@ -1,4 +1,4 @@
import {EntityMetadata} from "../EntityMetadata";
import {EntityMetadata} from "../../metadata/EntityMetadata";
import {EntityMetadataNotFound} from "../error/EntityMetadataNotFound";
/**
@ -21,6 +21,14 @@ export class EntityMetadataCollection extends Array<EntityMetadata> {
return metadata;
}
findByNameOrTarget(nameOrTarget: Function|string) {
if (typeof nameOrTarget === "string") {
return this.findByName(nameOrTarget);
} else {
return this.findByTarget(nameOrTarget);
}
}
findByName(name: string) {
const metadata = this.find(metadata => metadata.name === name);

View File

@ -1,7 +1,6 @@
import {TargetMetadataCollection} from "./TargetMetadataCollection";
import {PropertyMetadata} from "../PropertyMetadata";
import {TargetMetadataArgsCollection} from "./TargetMetadataArgsCollection";
export class PropertyMetadataCollection<T extends PropertyMetadata> extends TargetMetadataCollection<T> {
export class PropertyMetadataArgsCollection<T extends { target?: Function, propertyName?: string }> extends TargetMetadataArgsCollection<T> {
// -------------------------------------------------------------------------
// Public Methods

View File

@ -1,7 +1,6 @@
import {TargetMetadata} from "../TargetMetadata";
import {MetadataAlreadyExistsError} from "../../metadata-storage/error/MetadataAlreadyExistsError";
import {MetadataAlreadyExistsError} from "../../metadata-builder/error/MetadataAlreadyExistsError";
export class TargetMetadataCollection<T extends TargetMetadata> extends Array<T> {
export class TargetMetadataArgsCollection<T extends { target?: Function }> extends Array<T> {
// -------------------------------------------------------------------------
// Public Methods
@ -13,21 +12,23 @@ export class TargetMetadataCollection<T extends TargetMetadata> extends Array<T>
filterByClasses(classes: Function[]): this {
const collection = new (<any> this.constructor)();
this.filter(metadata => classes.indexOf(metadata.target) !== -1)
this
.filter(metadata => {
if (!metadata.target) return false;
return classes.indexOf(metadata.target) !== -1;
})
.forEach(metadata => collection.add(metadata));
return collection;
}
add(metadata: T, checkForDuplicateTargets = false) {
if (checkForDuplicateTargets && this.hasWithTarget(metadata.target))
throw new MetadataAlreadyExistsError((<any> metadata.constructor).name, metadata.target);
if (checkForDuplicateTargets) {
if (!metadata.target)
throw new Error(`Target is not set in the given metadata.`);
this.push(metadata);
}
addUniq(metadata: T) {
if (this.hasWithTarget(metadata.target))
throw new MetadataAlreadyExistsError((<any> metadata.constructor).name, metadata.target);
if (this.hasWithTarget(metadata.target))
throw new MetadataAlreadyExistsError((<any> metadata.constructor).name, metadata.target);
}
this.push(metadata);
}

View File

@ -1,18 +1,20 @@
import {EntityMetadata} from "../metadata/EntityMetadata";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {ColumnOptions} from "../metadata/options/ColumnOptions";
import {ColumnOptions} from "../decorator/options/ColumnOptions";
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
import {EntityMetadataValidator} from "./EntityMetadataValidator";
import {IndexMetadata} from "../metadata/IndexMetadata";
import {CompositeIndexMetadata} from "../metadata/CompositeIndexMetadata";
import {PropertyMetadataCollection} from "../metadata/collection/PropertyMetadataCollection";
import {TargetMetadataCollection} from "../metadata/collection/TargetMetadataCollection";
import {JoinColumnMetadata} from "../metadata/JoinColumnMetadata";
import {JoinColumnOptions} from "../metadata/options/JoinColumnOptions";
import {JoinColumnOptions} from "../decorator/options/JoinColumnOptions";
import {TableMetadata} from "../metadata/TableMetadata";
import {ColumnTypes} from "../metadata/types/ColumnTypes";
import {defaultMetadataStorage} from "../index";
import {getMetadataArgsStorage} from "../index";
import {RelationMetadata} from "../metadata/RelationMetadata";
import {JoinTableMetadata} from "../metadata/JoinTableMetadata";
import {JoinTableMetadataArgs} from "../metadata-args/JoinTableMetadataArgs";
import {PropertyMetadataArgsCollection} from "../metadata-args/collection/PropertyMetadataArgsCollection";
import {ColumnMetadataArgs} from "../metadata-args/ColumnMetadataArgs";
/**
* Aggregates all metadata: table, column, relation into one collection grouped by tables for a given set of classes.
@ -23,105 +25,71 @@ export class EntityMetadataBuilder {
// todo: type in function validation, inverse side function validation
// todo: check on build for duplicate names, since naming checking was removed from MetadataStorage
// todo: duplicate name checking for: table, relation, column, index, naming strategy, join tables/columns?
// todo: check if multiple tree parent metadatas in validator
// todo: tree decorators can be used only on closure table (validation)
// todo: throw error if parent tree metadata was not specified in a closure table
private entityValidator = new EntityMetadataValidator();
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(private namingStrategy: NamingStrategyInterface) {
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
private mergeIndicesAndCompositeIndices(indices: PropertyMetadataCollection<IndexMetadata>,
compositeIndices: TargetMetadataCollection<CompositeIndexMetadata>) {
indices.forEach(index => {
const compositeIndex = new CompositeIndexMetadata(index.target, index.name, [index.propertyName]);
compositeIndex.namingStrategy = index.namingStrategy;
compositeIndices.add(compositeIndex);
});
// later need to check if no duplicate keys in composite indices?
}
/**
* Builds a complete metadata aggregations for the given entity classes.
*/
build(entityClasses: Function[]): EntityMetadata[] {
build(namingStrategy: NamingStrategyInterface, entityClasses: Function[]): EntityMetadata[] {
const allMetadataStorage = defaultMetadataStorage();
const entityMetadatas = getMetadataArgsStorage().getMergedTableMetadatas(entityClasses).map(mergedArgs => {
// filter the only metadata we need - those which are bind to the given table classes
const allTableMetadatas = allMetadataStorage.tableMetadatas.filterByClasses(entityClasses);
const tableMetadatas = allTableMetadatas.filterByClasses(entityClasses).filter(table => !table.isAbstract);
// create metadatas from args
const table = new TableMetadata(mergedArgs.table);
const columns = mergedArgs.columns.map(args => new ColumnMetadata(args));
const relations = mergedArgs.relations.map(args => new RelationMetadata(args));
const indices = mergedArgs.indices.map(args => new IndexMetadata(args));
// set naming strategy
// allMetadataStorage.tableMetadatas.forEach(tableMetadata => tableMetadata.namingStrategy = this.namingStrategy);
// allTableMetadatas.forEach(column => column.namingStrategy = this.namingStrategy);
// entityMetadata.relations.forEach(relation => relation.namingStrategy = this.namingStrategy);
const entityMetadatas = tableMetadatas.map(tableMetadata => {
const mergedMetadata = allMetadataStorage.mergeWithAbstract(allTableMetadatas, tableMetadata);
// set naming strategy
// tableMetadata.namingStrategy = this.namingStrategy;
mergedMetadata.columnMetadatas.forEach(column => column.namingStrategy = this.namingStrategy);
mergedMetadata.relationMetadatas.forEach(relation => relation.namingStrategy = this.namingStrategy);
mergedMetadata.indexMetadatas.forEach(relation => relation.namingStrategy = this.namingStrategy);
mergedMetadata.compositeIndexMetadatas.forEach(relation => relation.namingStrategy = this.namingStrategy);
// merge indices and composite indices because simple indices actually are compose indices with only one column
this.mergeIndicesAndCompositeIndices(mergedMetadata.indexMetadatas, mergedMetadata.compositeIndexMetadatas);
// todo: check if multiple tree parent metadatas in validator
// todo: tree decorators can be used only on closure table (validation)
// todo: throw error if parent tree metadata was not specified in a closure table
// create a new entity metadata
const entityMetadata = new EntityMetadata(
this.namingStrategy,
tableMetadata,
mergedMetadata.columnMetadatas,
mergedMetadata.relationMetadatas,
mergedMetadata.compositeIndexMetadatas
);
const entityMetadata = new EntityMetadata(namingStrategy, table, columns, relations, indices);
// set entity metadata everywhere its used
table.entityMetadata = entityMetadata;
columns.forEach(column => column.entityMetadata = entityMetadata);
relations.forEach(relation => relation.entityMetadata = entityMetadata);
indices.forEach(index => index.entityMetadata = entityMetadata);
// create entity's relations join tables
entityMetadata.manyToManyRelations.forEach(relation => {
const joinTable = mergedMetadata.joinTableMetadatas.findByProperty(relation.propertyName);
if (joinTable) {
const joinTableMetadata = mergedArgs.joinTables.findByProperty(relation.propertyName);
if (joinTableMetadata) {
const joinTable = new JoinTableMetadata(joinTableMetadata);
relation.joinTable = joinTable;
joinTable.relation = relation;
}
});
// create entity's relations join columns
entityMetadata.relations.forEach(relation => {
const joinColumn = mergedMetadata.joinColumnMetadatas.findByProperty(relation.propertyName);
if (joinColumn) {
relation.joinColumn = joinColumn;
joinColumn.relation = relation;
}
});
entityMetadata.oneToOneRelations
.concat(entityMetadata.manyToOneRelations)
.forEach(relation => {
// since for many-to-one relations having JoinColumn is not required on decorators level, we need to go
// throw all of them which don't have JoinColumn decorators and create it for them
let joinColumnMetadata = mergedArgs.joinColumns.findByProperty(relation.propertyName);
if (!joinColumnMetadata && relation.isManyToOne) {
joinColumnMetadata = {
target: relation.target,
propertyName: relation.propertyName
};
}
if (joinColumnMetadata) {
const joinColumn = new JoinColumnMetadata(joinColumnMetadata);
relation.joinColumn = joinColumn;
joinColumn.relation = relation;
}
});
// since for many-to-one relations having JoinColumn is not required on decorators level, we need to go
// throw all of them which don't have JoinColumn decorators and create it for them
entityMetadata.manyToOneRelations.forEach(relation => {
let joinColumn = mergedMetadata.joinColumnMetadatas.findByProperty(relation.propertyName);
if (!joinColumn) {
joinColumn = new JoinColumnMetadata(relation.target, relation.propertyName, <JoinColumnOptions> {});
relation.joinColumn = joinColumn;
joinColumn.relation = relation;
}
});
return entityMetadata;
});
@ -153,11 +121,11 @@ export class EntityMetadataBuilder {
oldColumnName: relation.oldColumnName,
nullable: relation.isNullable
};
relationalColumn = new ColumnMetadata({
relationalColumn = new ColumnMetadata(metadata, {
target: metadata.target,
propertyName: relation.name,
propertyType: inverseSideColumn.propertyType,
isVirtual: true,
mode: "virtual",
options: options
});
metadata.columns.push(relationalColumn);
@ -165,6 +133,7 @@ export class EntityMetadataBuilder {
// create and add foreign key
const foreignKey = new ForeignKeyMetadata(
metadata,
metadata.table,
[relationalColumn],
relation.inverseEntityMetadata.table,
@ -180,42 +149,50 @@ export class EntityMetadataBuilder {
entityMetadatas
.filter(metadata => metadata.table.isClosure)
.forEach(metadata => {
const closureTableName = this.namingStrategy.closureJunctionTableName(metadata.table.name);
const closureTableName = namingStrategy.closureJunctionTableName(metadata.table.name);
const closureJunctionTableMetadata = new TableMetadata(undefined, closureTableName, "closureJunction");
const column1Args: ColumnMetadataArgs = {
propertyType: metadata.primaryColumn.type,
mode: "virtual",
options: <ColumnOptions> {
length: metadata.primaryColumn.length,
type: metadata.primaryColumn.type,
name: "ancestor"
}
};
const column2Args: ColumnMetadataArgs = {
propertyType: metadata.primaryColumn.type,
mode: "virtual",
options: <ColumnOptions> {
length: metadata.primaryColumn.length,
type: metadata.primaryColumn.type,
name: "descendant"
}
};
const column3Args: ColumnMetadataArgs = {
propertyType: ColumnTypes.INTEGER,
mode: "virtual",
options: {
type: ColumnTypes.INTEGER,
name: "level"
}
};
const columns = [
new ColumnMetadata({
propertyType: metadata.primaryColumn.type,
options: {
length: metadata.primaryColumn.length,
type: metadata.primaryColumn.type,
name: "ancestor"
}
}),
new ColumnMetadata({
propertyType: metadata.primaryColumn.type,
options: {
length: metadata.primaryColumn.length,
type: metadata.primaryColumn.type,
name: "descendant"
}
})
new ColumnMetadata(metadata, column1Args),
new ColumnMetadata(metadata, column2Args)
];
if (metadata.hasTreeLevelColumn) {
columns.push(new ColumnMetadata({
propertyType: ColumnTypes.INTEGER,
options: {
type: ColumnTypes.INTEGER,
name: "level"
}
}));
}
if (metadata.hasTreeLevelColumn)
columns.push(new ColumnMetadata(metadata, column3Args));
const closureJunctionEntityMetadata = new EntityMetadata(this.namingStrategy, closureJunctionTableMetadata, columns, [], []);
const closureJunctionEntityMetadata = new EntityMetadata(namingStrategy, closureJunctionTableMetadata, columns, [], []);
closureJunctionTableMetadata.entityMetadata = closureJunctionEntityMetadata;
closureJunctionEntityMetadata.foreignKeys.push(
new ForeignKeyMetadata(closureJunctionTableMetadata, [columns[0]], metadata.table, [metadata.primaryColumn]),
new ForeignKeyMetadata(closureJunctionTableMetadata, [columns[1]], metadata.table, [metadata.primaryColumn])
new ForeignKeyMetadata(closureJunctionEntityMetadata, closureJunctionTableMetadata, [columns[0]], metadata.table, [metadata.primaryColumn]),
new ForeignKeyMetadata(closureJunctionEntityMetadata, closureJunctionTableMetadata, [columns[1]], metadata.table, [metadata.primaryColumn])
);
closureJunctionEntityMetadatas.push(closureJunctionEntityMetadata);
@ -226,7 +203,11 @@ export class EntityMetadataBuilder {
const junctionEntityMetadatas: EntityMetadata[] = [];
entityMetadatas.forEach(metadata => {
metadata.ownerManyToManyRelations.map(relation => {
const tableMetadata = new TableMetadata(undefined, relation.joinTable.name, "junction");
const tableMetadata = new TableMetadata({
target: Function,
name: relation.joinTable.name,
type: "junction"
});
const column1 = relation.joinTable.referencedColumn;
const column2 = relation.joinTable.inverseReferencedColumn;
@ -241,19 +222,26 @@ export class EntityMetadataBuilder {
name: relation.joinTable.inverseJoinColumnName // inverseSideMetadata.table.name + "_" + column2.name
};
const columns = [
new ColumnMetadata({
new ColumnMetadata(metadata, {
target: Function, // todo: temp, fix it later
propertyName: "", // todo: temp, fix it later
propertyType: column2.type,
mode: "regular", // or virtual?
options: column1options
}),
new ColumnMetadata({
new ColumnMetadata(metadata, {
target: Function, // todo: temp, fix it later
propertyName: "", // todo: temp, fix it later
propertyType: column2.type,
mode: "regular", // or virtual?
options: column2options
})
];
const junctionEntityMetadata = new EntityMetadata(this.namingStrategy, tableMetadata, columns, [], []);
const junctionEntityMetadata = new EntityMetadata(namingStrategy, tableMetadata, columns, [], []);
tableMetadata.entityMetadata = junctionEntityMetadata;
junctionEntityMetadata.foreignKeys.push(
new ForeignKeyMetadata(tableMetadata, [columns[0]], metadata.table, [column1]),
new ForeignKeyMetadata(tableMetadata, [columns[1]], relation.inverseEntityMetadata.table, [column2])
new ForeignKeyMetadata(junctionEntityMetadata, tableMetadata, [columns[0]], metadata.table, [column1]),
new ForeignKeyMetadata(junctionEntityMetadata, tableMetadata, [columns[1]], relation.inverseEntityMetadata.table, [column2])
);
junctionEntityMetadatas.push(junctionEntityMetadata);
relation.junctionEntityMetadata = junctionEntityMetadata;
@ -267,4 +255,9 @@ export class EntityMetadataBuilder {
.concat(closureJunctionEntityMetadatas);
}
// -------------------------------------------------------------------------
// Private Methods
// -------------------------------------------------------------------------
}

View File

@ -0,0 +1,7 @@
export class EntityMetadataFactory {
createEntityMetadataBuilder() {
}
}

View File

@ -11,7 +11,7 @@ export class MissingJoinColumnError extends Error {
super();
if (relation.hasInverseSide) {
this.message = `JoinColumn is missing on both sides of ${entityMetadata.name}#${relation.propertyName} and ` +
`${relation.inverseEntityMetadata.name}#${relation.inverseRelation.name} one-to-one relationship. ` +
`${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} one-to-one relationship. ` +
`You need to put JoinColumn decorator on one of the sides.`;
} else {
this.message = `JoinColumn is missing on ${entityMetadata.name}#${relation.propertyName} one-to-one relationship. ` +

View File

@ -11,11 +11,11 @@ export class MissingJoinTableError extends Error {
super();
if (relation.hasInverseSide) {
this.message = `JoinTable is missing on both sides of ${entityMetadata.name}#${relation.name} and ` +
`${relation.inverseEntityMetadata.name}#${relation.inverseRelation.name} many-to-many relationship. ` +
this.message = `JoinTable is missing on both sides of ${entityMetadata.name}#${relation.propertyName} and ` +
`${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} many-to-many relationship. ` +
`You need to put decorator decorator on one of the sides.`;
} else {
this.message = `JoinTable is missing on ${entityMetadata.name}#${relation.name} many-to-many relationship. ` +
this.message = `JoinTable is missing on ${entityMetadata.name}#${relation.propertyName} many-to-many relationship. ` +
`You need to put JoinTable decorator on it.`;
}
}

View File

@ -9,7 +9,7 @@ export class UsingJoinColumnIsNotAllowedError extends Error {
constructor(entityMetadata: EntityMetadata, relation: RelationMetadata) {
super();
this.message = `Using JoinColumn on ${entityMetadata.name}#${relation.name} is wrong. ` +
this.message = `Using JoinColumn on ${entityMetadata.name}#${relation.propertyName} is wrong. ` +
`You can use JoinColumn only on one-to-one and many-to-one relations.`;
}

View File

@ -10,7 +10,7 @@ export class UsingJoinColumnOnlyOnOneSideAllowedError extends Error {
constructor(entityMetadata: EntityMetadata, relation: RelationMetadata) {
super();
this.message = `Using JoinColumn is allowed only on one side of the one-to-one relationship. ` +
`Both ${entityMetadata.name}#${relation.name} and ${relation.inverseEntityMetadata.name}#${relation.inverseRelation.name} ` +
`Both ${entityMetadata.name}#${relation.propertyName} and ${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} ` +
`has JoinTable decorators. Choose one of them and left JoinTable decorator only on it.`;
}

View File

@ -9,8 +9,8 @@ export class UsingJoinTableIsNotAllowedError extends Error {
constructor(entityMetadata: EntityMetadata, relation: RelationMetadata) {
super();
this.message = `Using JoinTable on ${entityMetadata.name}#${relation.name} is wrong. ` +
`${entityMetadata.name}#${relation.name} has ${relation.relationType} relation, ` +
this.message = `Using JoinTable on ${entityMetadata.name}#${relation.propertyName} is wrong. ` +
`${entityMetadata.name}#${relation.propertyName} has ${relation.relationType} relation, ` +
`however you can use JoinTable only on many-to-many relations.`;
}

View File

@ -10,7 +10,7 @@ export class UsingJoinTableOnlyOnOneSideAllowedError extends Error {
constructor(entityMetadata: EntityMetadata, relation: RelationMetadata) {
super();
this.message = `Using JoinTable is allowed only on one side of the many-to-many relationship. ` +
`Both ${entityMetadata.name}#${relation.name} and ${relation.inverseEntityMetadata.name}#${relation.inverseRelation.name} ` +
`Both ${entityMetadata.name}#${relation.propertyName} and ${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} ` +
`has JoinTable decorators. Choose one of them and left JoinColumn decorator only on it.`;
}

View File

@ -1,115 +0,0 @@
import {TableMetadata} from "../metadata/TableMetadata";
import {RelationMetadata} from "../metadata/RelationMetadata";
import {IndexMetadata} from "../metadata/IndexMetadata";
import {CompositeIndexMetadata} from "../metadata/CompositeIndexMetadata";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {EventSubscriberMetadata} from "../metadata/EventSubscriberMetadata";
import {EntityListenerMetadata} from "../metadata/EntityListenerMetadata";
import {NamingStrategyMetadata} from "../metadata/NamingStrategyMetadata";
import {JoinColumnMetadata} from "../metadata/JoinColumnMetadata";
import {JoinTableMetadata} from "../metadata/JoinTableMetadata";
import {TargetMetadataCollection} from "../metadata/collection/TargetMetadataCollection";
import {PropertyMetadataCollection} from "../metadata/collection/PropertyMetadataCollection";
import {RelationsCountMetadata} from "../metadata/RelationsCountMetadata";
/**
* Storage all metadatas of all available types: tables, fields, subscribers, relations, etc.
* Each metadata represents some specifications of what it represents.
*/
export class MetadataStorage {
// todo: type in function validation, inverse side function validation
// todo: check on build for duplicate names, since naming checking was removed from MetadataStorage
// todo: duplicate name checking for: table, relation, column, index, naming strategy, join tables/columns?
// todo: check for duplicate targets too since this check has been removed too
// -------------------------------------------------------------------------
// Properties
// -------------------------------------------------------------------------
readonly tableMetadatas = new TargetMetadataCollection<TableMetadata>();
readonly namingStrategyMetadatas = new TargetMetadataCollection<NamingStrategyMetadata>();
readonly eventSubscriberMetadatas = new TargetMetadataCollection<EventSubscriberMetadata>();
readonly compositeIndexMetadatas = new TargetMetadataCollection<CompositeIndexMetadata>();
readonly columnMetadatas = new PropertyMetadataCollection<ColumnMetadata>();
readonly relationMetadatas = new PropertyMetadataCollection<RelationMetadata>();
readonly joinColumnMetadatas = new PropertyMetadataCollection<JoinColumnMetadata>();
readonly joinTableMetadatas = new PropertyMetadataCollection<JoinTableMetadata>();
readonly indexMetadatas = new PropertyMetadataCollection<IndexMetadata>();
readonly entityListenerMetadatas = new PropertyMetadataCollection<EntityListenerMetadata>();
readonly relationCountMetadatas = new PropertyMetadataCollection<RelationsCountMetadata>();
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor() {
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Creates a new copy of the MetadataStorage with same metadatas as in current metadata storage, but filtered
* by classes.
*/
mergeWithAbstract(allTableMetadatas: TargetMetadataCollection<TableMetadata>,
tableMetadata: TableMetadata) {
const compositeIndexMetadatas = this.compositeIndexMetadatas.filterByClass(tableMetadata.target);
const columnMetadatas = this.columnMetadatas.filterByClass(tableMetadata.target);
const relationMetadatas = this.relationMetadatas.filterByClass(tableMetadata.target);
const joinColumnMetadatas = this.joinColumnMetadatas.filterByClass(tableMetadata.target);
const joinTableMetadatas = this.joinTableMetadatas.filterByClass(tableMetadata.target);
const indexMetadatas = this.indexMetadatas.filterByClass(tableMetadata.target);
const entityListenerMetadatas = this.entityListenerMetadatas.filterByClass(tableMetadata.target);
const relationCountMetadatas = this.relationCountMetadatas.filterByClass(tableMetadata.target);
allTableMetadatas
.filter(metadata => tableMetadata.isInherited(metadata))
.forEach(parentMetadata => {
const metadatasFromAbstract = this.mergeWithAbstract(allTableMetadatas, parentMetadata);
metadatasFromAbstract.columnMetadatas
.filterRepeatedMetadatas(columnMetadatas)
.forEach(metadata => columnMetadatas.push(metadata));
metadatasFromAbstract.relationMetadatas
.filterRepeatedMetadatas(relationMetadatas)
.forEach(metadata => relationMetadatas.push(metadata));
metadatasFromAbstract.joinColumnMetadatas
.filterRepeatedMetadatas(joinColumnMetadatas)
.forEach(metadata => joinColumnMetadatas.push(metadata));
metadatasFromAbstract.joinTableMetadatas
.filterRepeatedMetadatas(joinTableMetadatas)
.forEach(metadata => joinTableMetadatas.push(metadata));
metadatasFromAbstract.indexMetadatas
.filterRepeatedMetadatas(indexMetadatas)
.forEach(metadata => indexMetadatas.push(metadata));
metadatasFromAbstract.entityListenerMetadatas
.filterRepeatedMetadatas(entityListenerMetadatas)
.forEach(metadata => entityListenerMetadatas.push(metadata));
metadatasFromAbstract.relationCountMetadatas
.filterRepeatedMetadatas(relationCountMetadatas)
.forEach(metadata => relationCountMetadatas.push(metadata));
});
return {
compositeIndexMetadatas: compositeIndexMetadatas,
columnMetadatas: columnMetadatas,
relationMetadatas: relationMetadatas,
joinColumnMetadatas: joinColumnMetadatas,
joinTableMetadatas: joinTableMetadatas,
indexMetadatas: indexMetadatas,
entityListenerMetadatas: entityListenerMetadatas,
relationCountMetadatas: relationCountMetadatas
};
}
}

View File

@ -1,12 +1,16 @@
import {PropertyMetadata} from "./PropertyMetadata";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {ColumnMetadataArgs} from "./args/ColumnMetadataArgs";
import {ColumnMetadataArgs} from "../metadata-args/ColumnMetadataArgs";
import {ColumnType} from "./types/ColumnTypes";
export type ColumnMode = "regular"|"createDate"|"updateDate"|"version"|"treeChildrenCount"|"treeLevel";
import {EntityMetadata} from "./EntityMetadata";
/**
* This metadata contains all information about class's column.
* Kinda type of the column. Not a type in the database, but locally used type to determine what kind of column
* we are working with.
*/
export type ColumnMode = "regular"|"virtual"|"primary"|"createDate"|"updateDate"|"version"|"treeChildrenCount"|"treeLevel";
/**
* This metadata contains all information about entity's column.
*/
export class ColumnMetadata extends PropertyMetadata {
@ -15,12 +19,12 @@ export class ColumnMetadata extends PropertyMetadata {
// ---------------------------------------------------------------------
/**
* Naming strategy to be used to generate column name.
* Entity metadata where this column metadata is.
*/
namingStrategy: NamingStrategyInterface;
entityMetadata: EntityMetadata;
// ---------------------------------------------------------------------
// Readonly Properties
// Public Readonly Properties
// ---------------------------------------------------------------------
/**
@ -29,47 +33,38 @@ export class ColumnMetadata extends PropertyMetadata {
readonly propertyType: string;
/**
* The type of the column.
* The database type of the column.
*/
readonly type: ColumnType;
/**
* The mode of the column.
* Column's mode in which this column is working.
*/
readonly mode: ColumnMode;
/**
* Maximum length in the database.
* Type's length in the database.
*/
readonly length = "";
/**
* Indicates if this column is primary key.
* Indicates if this column is generated (auto increment or generated other way).
*/
readonly isPrimary = false;
readonly isGenerated = false;
/**
* Indicates if this column is auto increment.
*/
readonly isAutoIncrement = false;
/**
* Indicates if value should be unique or not.
* Indicates if value in the database should be unique or not.
*/
readonly isUnique = false;
/**
* Indicates if can contain nulls or not.
* Indicates if column can contain nulls or not.
*/
readonly isNullable = false;
/**
* Indicates if column is virtual. Virtual columns are not mapped to the entity.
*/
readonly isVirtual = false;
/**
* Extra sql definition for the given column.
* Extra sql definition for the given column.
* Can be used to make temporary tweaks. Not recommended to use.
*/
readonly columnDefinition = "";
@ -79,7 +74,8 @@ export class ColumnMetadata extends PropertyMetadata {
readonly comment = "";
/**
* Old column name. Used to correctly alter tables when column name is changed.
* Old column name. Used to correctly alter tables when column name is changed.
* Can be used to make temporary tweaks. Not recommended to use.
*/
readonly oldColumnName: string;
@ -113,15 +109,22 @@ export class ColumnMetadata extends PropertyMetadata {
// Constructor
// ---------------------------------------------------------------------
constructor(args: ColumnMetadataArgs) {
super(args.target, args.propertyName);
constructor(args: ColumnMetadataArgs);
constructor(entityMetadata: EntityMetadata, args: ColumnMetadataArgs);
constructor(entityMetadataOrArgs: EntityMetadata|ColumnMetadataArgs, args?: ColumnMetadataArgs) {
super(
args ? args.target : (entityMetadataOrArgs as ColumnMetadataArgs).target,
args ? args.propertyName : (entityMetadataOrArgs as ColumnMetadataArgs).propertyName
);
if (entityMetadataOrArgs && args) {
this.entityMetadata = entityMetadataOrArgs as EntityMetadata;
}
args = args ? args : entityMetadataOrArgs as ColumnMetadataArgs;
if (args.isPrimaryKey)
this.isPrimary = args.isPrimaryKey;
if (args.mode)
this.mode = args.mode;
if (args.isVirtual)
this.isVirtual = args.isVirtual;
if (args.propertyType)
this.propertyType = args.propertyType.toLowerCase();
if (args.options.name)
@ -132,7 +135,7 @@ export class ColumnMetadata extends PropertyMetadata {
if (args.options.length)
this.length = args.options.length;
if (args.options.generated)
this.isAutoIncrement = args.options.generated;
this.isGenerated = args.options.generated;
if (args.options.unique)
this.isUnique = args.options.unique;
if (args.options.nullable)
@ -159,20 +162,46 @@ export class ColumnMetadata extends PropertyMetadata {
* Column name in the database.
*/
get name(): string {
if (this._name)
return this._name;
return this.namingStrategy ? this.namingStrategy.columnName(this.propertyName) : this.propertyName;
// if custom column name is set implicitly then return it
if (this._name)
return this.entityMetadata.namingStrategy.columnNameCustomized(this._name);
// if there is a naming strategy then use it to normalize propertyName as column name
return this.entityMetadata.namingStrategy.columnName(this.propertyName);
}
get isUpdateDate() {
return this.mode === "updateDate";
/**
* Indicates if this column is a primary key.
*/
get isPrimary() {
return this.mode === "primary";
}
/**
* Indicates if column is virtual. Virtual columns are not mapped to the entity.
*/
get isVirtual() {
return this.mode === "virtual";
}
/**
* Indicates if this column contains an entity creation date.
*/
get isCreateDate() {
return this.mode === "createDate";
}
/**
* Indicates if this column contains an entity update date.
*/
get isUpdateDate() {
return this.mode === "updateDate";
}
/**
* Indicates if this column contains an entity version.
*/
get isVersion() {
return this.mode === "version";
}

View File

@ -1,83 +0,0 @@
import {TargetMetadata} from "./TargetMetadata";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {EntityMetadata} from "./EntityMetadata";
import {CompositeIndexOptions} from "./options/CompositeIndexOptions";
/**
* This metadata interface contains all information about table's composite index.
*/
export class CompositeIndexMetadata extends TargetMetadata {
// ---------------------------------------------------------------------
// Public Properties
// ---------------------------------------------------------------------
/**
* Naming strategy used to generate and normalize index name.
*/
namingStrategy: NamingStrategyInterface;
/**
* Entity metadata of the class to which this index is applied.
*/
entityMetadata: EntityMetadata;
// ---------------------------------------------------------------------
// Readonly Properties
// ---------------------------------------------------------------------
/**
* Indicates if this index must be unique.
*/
readonly isUnique: boolean;
// ---------------------------------------------------------------------
// Private Properties
// ---------------------------------------------------------------------
/**
* Composite index name.
*/
private readonly _name: string;
/**
* Columns combination to be used as index.
*/
private readonly _columns: ((object: any) => any[])|string[];
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(target: Function,
name: string|undefined,
columns: ((object: any) => any[])|string[],
options?: CompositeIndexOptions) {
super(target);
this._columns = columns;
if (name)
this._name = name;
if (options && options.unique)
this.isUnique = options.unique;
}
// ---------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------
get name() { // throw exception if naming strategy is not set
return this.namingStrategy.indexName(this.target, this._name, this.columns);
}
get columns() {
// if columns already an array of string then simply return it
if (this._columns instanceof Array)
return this._columns;
// if columns is a function that returns array of field names then execute it and get columns names from it
const propertiesMap = this.entityMetadata.createPropertiesMap();
return this._columns(propertiesMap).map((i: any) => String(i));
}
}

View File

@ -1,8 +1,9 @@
import {PropertyMetadata} from "./PropertyMetadata";
import {EventListenerType} from "./types/EventListenerTypes";
import {EntityListenerMetadataArgs} from "../metadata-args/EntityListenerMetadataArgs";
/**
* This metadata interface contains all information about some index on a field.
* This metadata contains all information about entity's listeners.
*/
export class EntityListenerMetadata extends PropertyMetadata {
@ -19,9 +20,9 @@ export class EntityListenerMetadata extends PropertyMetadata {
// Constructor
// ---------------------------------------------------------------------
constructor(target: Function, propertyName: string, type: EventListenerType) {
super(target, propertyName);
this.type = type;
constructor(args: EntityListenerMetadataArgs) {
super(args.target, args.propertyName);
this.type = args.type;
}

View File

@ -1,11 +1,10 @@
import {TableMetadata} from "./TableMetadata";
import {ColumnMetadata} from "./ColumnMetadata";
import {RelationMetadata} from "./RelationMetadata";
import {CompositeIndexMetadata} from "./CompositeIndexMetadata";
import {RelationMetadata, PropertyTypeInFunction} from "./RelationMetadata";
import {IndexMetadata} from "./IndexMetadata";
import {RelationTypes} from "./types/RelationTypes";
import {ForeignKeyMetadata} from "./ForeignKeyMetadata";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {PropertyMetadata} from "./PropertyMetadata";
/**
* Contains all entity metadata.
@ -16,21 +15,43 @@ export class EntityMetadata {
// Properties
// -------------------------------------------------------------------------
/**
* If entity's table is a closure-typed table, then this entity will have a closure junction table metadata.
*/
closureJunctionTable: EntityMetadata;
// -------------------------------------------------------------------------
// Readonly Properties
// Public Readonly Properties
// -------------------------------------------------------------------------
/**
* Naming strategy used to generate and normalize column name.
* Naming strategy used to generate and normalize names.
*/
readonly namingStrategy: NamingStrategyInterface;
/**
* Entity's table metadata.
*/
readonly table: TableMetadata;
/**
* Entity's column metadatas.
*/
readonly columns: ColumnMetadata[];
/**
* Entity's relation metadatas.
*/
readonly relations: RelationMetadata[];
readonly compositeIndices: CompositeIndexMetadata[];
/**
* Entity's index metadatas.
*/
readonly indices: IndexMetadata[];
/**
* Entity's foreign key metadatas.
*/
readonly foreignKeys: ForeignKeyMetadata[] = [];
// -------------------------------------------------------------------------
@ -38,72 +59,52 @@ export class EntityMetadata {
// -------------------------------------------------------------------------
constructor(namingStrategy: NamingStrategyInterface,
tableMetadata: TableMetadata,
columnMetadatas: ColumnMetadata[],
relationMetadatas: RelationMetadata[],
compositeIndexMetadatas: CompositeIndexMetadata[]) {
table: TableMetadata,
columns: ColumnMetadata[],
relations: RelationMetadata[],
indices: IndexMetadata[]) {
this.namingStrategy = namingStrategy;
this.table = tableMetadata;
this.columns = columnMetadatas;
this.relations = relationMetadatas;
this.compositeIndices = compositeIndexMetadatas;
this.table.entityMetadata = this;
this.relations.forEach(relation => relation.entityMetadata = this);
this.compositeIndices.forEach(index => index.entityMetadata = this);
this.table = table;
this.columns = columns;
this.relations = relations;
this.indices = indices;
}
// -------------------------------------------------------------------------
// Accessors
// -------------------------------------------------------------------------
/**
* Entity's name. Equal to entity target class's name if target is set to table, or equals to table name if its set.
*/
get name(): string {
if (!this.table) {
if (!this.table)
throw new Error("No table target set to the entity metadata.");
}
if (this.table.target)
return (<any> this.table.target).name;
if (this.target)
return (<any> this.target).name;
return this.table.name;
}
/**
* Target class to which this entity metadata is bind.
*/
get target(): Function {
return this.table.target;
}
get oneToOneRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.ONE_TO_ONE);
}
get ownerOneToOneRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.ONE_TO_ONE && relation.isOwning);
}
get oneToManyRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.ONE_TO_MANY);
}
get manyToOneRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.MANY_TO_ONE);
}
get manyToManyRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.MANY_TO_MANY);
}
get ownerManyToManyRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.MANY_TO_MANY && relation.isOwning);
}
get relationsWithJoinColumns() {
return this.ownerOneToOneRelations.concat(this.manyToOneRelations);
}
/**
* Checks if entity has a primary column. All user's entities must have a primary column.
* Special entity metadatas like for junction tables and closure junction tables don't have a primary column.
*/
get hasPrimaryColumn(): boolean {
return !!this.columns.find(column => column.isPrimary);
}
/**
* Gets the primary column.
*/
get primaryColumn(): ColumnMetadata {
const primaryKey = this.columns.find(column => column.isPrimary);
if (!primaryKey)
@ -111,11 +112,17 @@ export class EntityMetadata {
return primaryKey;
}
/**
* Checks if entity has a create date column.
*/
get hasCreateDateColumn(): boolean {
return !!this.columns.find(column => column.mode === "createDate");
}
/**
* Gets entity column which contains a create date value.
*/
get createDateColumn(): ColumnMetadata {
const column = this.columns.find(column => column.mode === "createDate");
if (!column)
@ -124,10 +131,16 @@ export class EntityMetadata {
return column;
}
/**
* Checks if entity has an update date column.
*/
get hasUpdateDateColumn(): boolean {
return !!this.columns.find(column => column.mode === "updateDate");
}
/**
* Gets entity column which contains an update date value.
*/
get updateDateColumn(): ColumnMetadata {
const column = this.columns.find(column => column.mode === "updateDate");
if (!column)
@ -136,10 +149,16 @@ export class EntityMetadata {
return column;
}
/**
* Checks if entity has a version column.
*/
get hasVersionColumn(): boolean {
return !!this.columns.find(column => column.mode === "version");
}
/**
* Gets entity column which contains an entity version.
*/
get versionColumn(): ColumnMetadata {
const column = this.columns.find(column => column.mode === "version");
if (!column)
@ -148,18 +167,9 @@ export class EntityMetadata {
return column;
}
get hasTreeChildrenCountColumn(): boolean {
return !!this.columns.find(column => column.mode === "treeChildrenCount");
}
get treeChildrenCountColumn(): ColumnMetadata {
const column = this.columns.find(column => column.mode === "treeChildrenCount");
if (!column)
throw new Error(`TreeChildrenCountColumn was not found in entity ${this.name}`);
return column;
}
/**
* Checks if entity has a tree level column.
*/
get hasTreeLevelColumn(): boolean {
return !!this.columns.find(column => column.mode === "treeLevel");
}
@ -171,9 +181,54 @@ export class EntityMetadata {
return column;
}
get hasPrimaryKey(): boolean {
return !!this.primaryColumn;
/**
* Gets only one-to-one relations of the entity.
*/
get oneToOneRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.ONE_TO_ONE);
}
/**
* Gets only owner one-to-one relations of the entity.
*/
get ownerOneToOneRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.ONE_TO_ONE && relation.isOwning);
}
/**
* Gets only one-to-many relations of the entity.
*/
get oneToManyRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.ONE_TO_MANY);
}
/**
* Gets only many-to-one relations of the entity.
*/
get manyToOneRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.MANY_TO_ONE);
}
/**
* Gets only many-to-many relations of the entity.
*/
get manyToManyRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.MANY_TO_MANY);
}
/**
* Gets only owner many-to-many relations of the entity.
*/
get ownerManyToManyRelations(): RelationMetadata[] {
return this.relations.filter(relation => relation.relationType === RelationTypes.MANY_TO_MANY && relation.isOwning);
}
/**
* Gets only owner one-to-one and many-to-one relations.
*/
get relationsWithJoinColumns() {
return this.ownerOneToOneRelations.concat(this.manyToOneRelations);
}
// -------------------------------------------------------------------------
@ -187,29 +242,54 @@ export class EntityMetadata {
return new (<any> this.table.target)();
}
createPropertiesMap(): any {
const entity: any = {};
/**
* Creates an object - map of columns and relations of the entity.
*/
createPropertiesMap(): { [name: string]: string|any } {
const entity: { [name: string]: string|any } = {};
this.columns.forEach(column => entity[column.propertyName] = column.propertyName);
this.relations.forEach(relation => entity[relation.propertyName] = relation.propertyName);
return entity;
}
/**
* Computes property name of the entity using given PropertyTypeInFunction.
*/
computePropertyName(nameOrFn: PropertyTypeInFunction<any>) {
return typeof nameOrFn === "string" ? nameOrFn : nameOrFn(this.createPropertiesMap());
}
/**
* Returns entity id of the given entity.
*/
getEntityId(entity: any) {
return entity[this.primaryColumn.propertyName];
}
/**
* Checks if column with the given property name exist.
*/
hasColumnWithPropertyName(propertyName: string): boolean {
return !!this.columns.find(column => column.propertyName === propertyName);
}
/**
* Checks if column with the given database name exist.
*/
hasColumnWithDbName(name: string): boolean {
return !!this.columns.find(column => column.name === name);
}
/**
* Checks if relation with the given property name exist.
*/
hasRelationWithPropertyName(propertyName: string): boolean {
return !!this.relations.find(relation => relation.propertyName === propertyName);
}
/**
* Finds relation with the given property name.
*/
findRelationWithPropertyName(propertyName: string): RelationMetadata {
const relation = this.relations.find(relation => relation.propertyName === propertyName);
if (!relation)
@ -218,70 +298,34 @@ export class EntityMetadata {
return relation;
}
/**
* Checks if relation with the given name exist.
*/
hasRelationWithDbName(dbName: string): boolean {
return !!this.relations.find(relation => relation.name === dbName);
return !!this.relationsWithJoinColumns.find(relation => relation.name === dbName);
}
findRelationWithDbName(propertyName: string): RelationMetadata {
const relation = this.relations.find(relation => relation.name === propertyName);
/**
* Finds relation with the given name.
*/
findRelationWithDbName(name: string): RelationMetadata {
const relation = this.relationsWithJoinColumns.find(relation => relation.name === name);
if (!relation)
throw new Error(`Relation with name ${propertyName} in ${this.name} entity was not found.`);
return relation;
}
hasRelationWithOneWithPropertyName(propertyName: string): boolean {
return !!this.relations.find(relation => relation.propertyName === propertyName && (relation.isOneToMany || relation.isOneToOne));
}
findRelationWithOneWithPropertyName(propertyName: string): RelationMetadata {
const relation = this.relations.find(relation => relation.propertyName === propertyName && (relation.isOneToMany || relation.isOneToOne));
if (!relation)
throw new Error(`Relation with one with property name ${propertyName} in ${this.name} entity was not found.`);
return relation;
}
hasRelationWithOneWithDbName(name: string): boolean {
return !!this.relations.find(relation => relation.name === name && (relation.isOneToMany || relation.isOneToOne));
}
findRelationWithOneWithDbName(name: string): RelationMetadata {
const relation = this.relations.find(relation => relation.name === name && (relation.isOneToMany || relation.isOneToOne));
if (!relation)
throw new Error(`Relation with one with name ${name} in ${this.name} entity was not found.`);
return relation;
}
hasRelationWithManyWithPropertyName(name: string): boolean {
return !!this.relations.find(relation => relation.propertyName === name && (relation.isManyToOne || relation.isManyToMany));
}
findRelationWithManyWithPropertyName(name: string): RelationMetadata {
const relation = this.relations.find(relation => relation.propertyName === name && (relation.isManyToOne || relation.isManyToMany));
if (!relation)
throw new Error(`Relation with many with property name ${name} in ${this.name} entity was not found.`);
return relation;
}
hasRelationWithManyWithDbName(name: string): boolean {
return !!this.relations.find(relation => relation.name === name && (relation.isManyToOne || relation.isManyToMany));
}
findRelationWithManyWithDbName(name: string): RelationMetadata {
const relation = this.relations.find(relation => relation.name === name && (relation.isManyToOne || relation.isManyToMany));
if (!relation)
throw new Error(`Relation with many with name ${name} in ${this.name} entity was not found.`);
throw new Error(`Relation with name ${name} in ${this.name} entity was not found.`);
return relation;
}
/**
* Checks if there is a tree parent relation. Used only in tree-tables.
*/
get hasTreeParentRelation() {
return !!this.relations.find(relation => relation.isTreeParent);
}
/**
* Tree parent relation. Used only in tree-tables.
*/
get treeParentRelation() {
const relation = this.relations.find(relation => relation.isTreeParent);
if (!relation)
@ -290,10 +334,16 @@ export class EntityMetadata {
return relation;
}
/**
* Checks if there is a tree children relation. Used only in tree-tables.
*/
get hasTreeChildrenRelation() {
return !!this.relations.find(relation => relation.isTreeChildren);
}
/**
* Tree children relation. Used only in tree-tables.
*/
get treeChildrenRelation() {
const relation = this.relations.find(relation => relation.isTreeChildren);
if (!relation)

View File

@ -1,8 +1,13 @@
import {TargetMetadata} from "./TargetMetadata";
import {EventSubscriberMetadataArgs} from "../metadata-args/EventSubscriberMetadataArgs";
/**
* Contains metadata information about ORM event subscribers.
*/
export class EventSubscriberMetadata extends TargetMetadata {
constructor(args: EventSubscriberMetadataArgs) {
super(args.target);
}
}

View File

@ -1,24 +1,33 @@
import {ColumnMetadata} from "./ColumnMetadata";
import {TableMetadata} from "./TableMetadata";
import {EntityMetadata} from "./EntityMetadata";
/**
* ON_DELETE type to be used to specify delete strategy when some relation is being deleted from the database.
*/
export type OnDeleteType = "RESTRICT"|"CASCADE"|"SET NULL";
/**
* This metadata interface contains all information foreign keys.
* Contains all information about entity's foreign key.
*/
export class ForeignKeyMetadata {
// -------------------------------------------------------------------------
// Readonly Properties
// Public Readonly Properties
// -------------------------------------------------------------------------
/**
* Entity metadata where this foreign key is.
*/
readonly entityMetadata: EntityMetadata;
/**
* Table to which this foreign key is applied.
*/
readonly table: TableMetadata;
/**
* Array of columns.
* Array of columns of this foreign key.
*/
readonly columns: ColumnMetadata[];
@ -41,11 +50,13 @@ export class ForeignKeyMetadata {
// Constructor
// -------------------------------------------------------------------------
constructor(table: TableMetadata,
constructor(entityMetadata: EntityMetadata,
table: TableMetadata,
columns: ColumnMetadata[],
referencedTable: TableMetadata,
referencedColumns: ColumnMetadata[],
onDelete?: OnDeleteType) {
this.entityMetadata = entityMetadata;
this.table = table;
this.columns = columns;
this.referencedTable = referencedTable;
@ -59,27 +70,24 @@ export class ForeignKeyMetadata {
// -------------------------------------------------------------------------
/**
* Array of column names.
* Gets array of column names.
*/
get columnNames(): string[] {
return this.columns.map(column => column.name);
}
/**
* Array of referenced column names.
* Gets array of referenced column names.
*/
get referencedColumnNames(): string[] {
return this.referencedColumns.map(column => column.name);
}
/**
* Foreign key name.
* Gets foreign key name.
*/
get name() {
// todo: use naming strategy
const key = `${this.table.name}_${this.columnNames.join("_")}` +
`_${this.referencedTable.name}_${this.referencedColumnNames.join("_")}`;
return "fk_" + require("sha1")(key); // todo: use crypto instead?
return this.entityMetadata.namingStrategy.foreignKeyName(this.table.name, this.columnNames, this.referencedTable.name, this.referencedColumnNames);
}
}

View File

@ -1,39 +1,78 @@
import {PropertyMetadata} from "./PropertyMetadata";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {TargetMetadata} from "./TargetMetadata";
import {EntityMetadata} from "./EntityMetadata";
import {IndexMetadataArgs} from "../metadata-args/IndexMetadataArgs";
/**
* This metadata interface contains all information about some index on a field.
* Index metadata contains all information about table's index.
*/
export class IndexMetadata extends PropertyMetadata {
export class IndexMetadata extends TargetMetadata {
// ---------------------------------------------------------------------
// Public Properties
// ---------------------------------------------------------------------
/**
* Naming strategy used to generate and normalize index name.
* Entity metadata of the class to which this index is applied.
*/
namingStrategy: NamingStrategyInterface;
entityMetadata: EntityMetadata;
// ---------------------------------------------------------------------
// Readonly Properties
// ---------------------------------------------------------------------
/**
* The name of the index.
* Indicates if this index must be unique.
*/
readonly name: string;
readonly isUnique: boolean;
// ---------------------------------------------------------------------
// Private Properties
// ---------------------------------------------------------------------
/**
* Composite index name.
*/
private readonly _name: string|undefined;
/**
* Columns combination to be used as index.
*/
private readonly _columns: ((object: any) => any[])|string[];
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(target: Function, propertyName: string, name?: string) {
super(target, propertyName);
if (name)
this.name = name; // todo: if there is no name, then generate it (using naming strategy?)
constructor(args: IndexMetadataArgs) {
super(args.target);
this._columns = args.columns;
this._name = args.name;
this.isUnique = args.unique;
}
// ---------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------
/**
* Gets index's name.
*/
get name() {
return this.entityMetadata.namingStrategy.indexName(this.target, this._name, this.columns);
}
/**
* Gets the column names which are in this index.
*/
get columns(): string[] {
// if columns already an array of string then simply return it
if (this._columns instanceof Array)
return this._columns;
// if columns is a function that returns array of field names then execute it and get columns names from it
const propertiesMap = this.entityMetadata.createPropertiesMap();
return this._columns(propertiesMap).map((i: any) => String(i));
}
}

View File

@ -1,10 +1,10 @@
import {PropertyMetadata} from "./PropertyMetadata";
import {JoinColumnOptions} from "./options/JoinColumnOptions";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {RelationMetadata} from "./RelationMetadata";
import {ColumnMetadata} from "./ColumnMetadata";
import {JoinColumnMetadataArgs} from "../metadata-args/JoinColumnMetadataArgs";
/**
* JoinColumnMetadata contains all information about relation's join column.
*/
export class JoinColumnMetadata extends PropertyMetadata {
@ -24,26 +24,21 @@ export class JoinColumnMetadata extends PropertyMetadata {
/**
* Join column name.
*/
private readonly _name: string;
private readonly _name: string|undefined;
/**
* Join column referenced column name.
*/
private readonly _referencedColumnName: string;
private readonly referencedColumnName: string|undefined;
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(target: Function,
propertyName: string,
options: JoinColumnOptions) {
super(target, propertyName);
if (options.name)
this._name = options.name;
if (options.referencedColumnName)
this._referencedColumnName = options.referencedColumnName;
constructor(args: JoinColumnMetadataArgs) {
super(args.target, args.propertyName);
this._name = args.name;
this.referencedColumnName = args.referencedColumnName;
}
// ---------------------------------------------------------------------
@ -61,10 +56,10 @@ export class JoinColumnMetadata extends PropertyMetadata {
* Referenced join column.
*/
get referencedColumn(): ColumnMetadata {
if (this._referencedColumnName) {
const referencedColumn = this.relation.inverseEntityMetadata.columns.find(column => column.name === this._referencedColumnName);
if (this.referencedColumnName) {
const referencedColumn = this.relation.inverseEntityMetadata.columns.find(column => column.name === this.referencedColumnName);
if (!referencedColumn)
throw new Error(`Referenced column ${this._referencedColumnName} was not found in entity ${this.name}`);
throw new Error(`Referenced column ${this.referencedColumnName} was not found in entity ${this.name}`);
}
return this.relation.inverseEntityMetadata.primaryColumn;

View File

@ -1,9 +1,10 @@
import {PropertyMetadata} from "./PropertyMetadata";
import {JoinTableOptions} from "./options/JoinTableOptions";
import {RelationMetadata} from "./RelationMetadata";
import {ColumnMetadata} from "./ColumnMetadata";
import {JoinTableMetadataArgs} from "../metadata-args/JoinTableMetadataArgs";
/**
* JoinTableMetadata contains all information about relation's join table.
*/
export class JoinTableMetadata extends PropertyMetadata {
@ -49,24 +50,24 @@ export class JoinTableMetadata extends PropertyMetadata {
// Constructor
// ---------------------------------------------------------------------
constructor(target: Function, propertyName: string, options: JoinTableOptions) {
super(target, propertyName);
constructor(args: JoinTableMetadataArgs) {
super(args.target, args.propertyName);
if (options.name)
this._name = options.name;
if (args.name)
this._name = args.name;
if (options.joinColumn) {
if (options.joinColumn.name)
this._joinColumnName = options.joinColumn.name;
if (options.joinColumn.referencedColumnName)
this._joinColumnReferencedColumnName = options.joinColumn.referencedColumnName;
if (args.joinColumn) {
if (args.joinColumn.name)
this._joinColumnName = args.joinColumn.name;
if (args.joinColumn.referencedColumnName)
this._joinColumnReferencedColumnName = args.joinColumn.referencedColumnName;
}
if (options.inverseJoinColumn) {
if (options.inverseJoinColumn.name)
this._inverseJoinColumnName = options.inverseJoinColumn.name;
if (options.inverseJoinColumn.referencedColumnName)
this._inverseJoinColumnReferencedColumnName = options.inverseJoinColumn.referencedColumnName;
if (args.inverseJoinColumn) {
if (args.inverseJoinColumn.name)
this._inverseJoinColumnName = args.inverseJoinColumn.name;
if (args.inverseJoinColumn.referencedColumnName)
this._inverseJoinColumnReferencedColumnName = args.inverseJoinColumn.referencedColumnName;
}
}
@ -84,8 +85,8 @@ export class JoinTableMetadata extends PropertyMetadata {
return this.relation.entityMetadata.namingStrategy.joinTableName(
this.relation.entityMetadata.table.name,
this.relation.inverseEntityMetadata.table.name,
this.relation.name,
this.relation.hasInverseSide ? this.relation.inverseRelation.name : "",
this.relation.propertyName,
this.relation.hasInverseSide ? this.relation.inverseRelation.propertyName : "",
this.referencedColumn.name,
this.inverseReferencedColumn.name
);

View File

@ -1,4 +1,5 @@
import {TargetMetadata} from "./TargetMetadata";
import {NamingStrategyMetadataArgs} from "../metadata-args/NamingStrategyMetadataArgs";
/**
* This metadata interface contains all information about naming strategy.
@ -18,9 +19,9 @@ export class NamingStrategyMetadata extends TargetMetadata {
// Constructor
// ---------------------------------------------------------------------
constructor(target: Function, name: string) {
super(target);
this.name = name;
constructor(args: NamingStrategyMetadataArgs) {
super(args.target);
this.name = args.name;
}
}

View File

@ -10,7 +10,7 @@ export class PropertyMetadata extends TargetMetadata {
// ---------------------------------------------------------------------
/**
* Class's property name to which this decorator is applied.
* Target's property name to which this metadata is applied.
*/
readonly propertyName: string;

View File

@ -1,17 +1,17 @@
import {PropertyMetadata} from "./PropertyMetadata";
import {RelationTypes, RelationType} from "./types/RelationTypes";
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategyInterface";
import {EntityMetadata} from "./EntityMetadata";
import {OnDeleteType} from "./ForeignKeyMetadata";
import {JoinTableMetadata} from "./JoinTableMetadata";
import {JoinColumnMetadata} from "./JoinColumnMetadata";
import {RelationMetadataArgs} from "./args/RelationMetadataArgs";
import {RelationMetadataArgs} from "../metadata-args/RelationMetadataArgs";
/**
* Function that returns a type of the field. Returned value must be a class used on the relation.
*/
export type RelationTypeInFunction = ((type?: any) => Function);
/**
* Contains the name of the property of the object, or the function that returns this name.
*/
@ -19,7 +19,7 @@ export type PropertyTypeInFunction<T> = string|((t: T) => string|any);
/**
* This metadata interface contains all information about some document's relation.
* Contains all information about some entity's relation.
*/
export class RelationMetadata extends PropertyMetadata {
@ -27,11 +27,6 @@ export class RelationMetadata extends PropertyMetadata {
// Public Properties
// ---------------------------------------------------------------------
/**
* Naming strategy used to generate and normalize column name.
*/
namingStrategy: NamingStrategyInterface;
/**
* Its own entity metadata.
*/
@ -115,11 +110,6 @@ export class RelationMetadata extends PropertyMetadata {
// Private Properties
// ---------------------------------------------------------------------
/**
* Column name for this relation.
*/
private _name: string;
/**
* The type of the field.
*/
@ -140,8 +130,6 @@ export class RelationMetadata extends PropertyMetadata {
if (args.inverseSideProperty)
this._inverseSideProperty = args.inverseSideProperty;
if (args.options.name)
this._name = args.options.name;
if (args.propertyType)
this.propertyType = args.propertyType;
if (args.options.cascadeInsert || args.options.cascadeAll)
@ -163,87 +151,150 @@ export class RelationMetadata extends PropertyMetadata {
if (!this._type)
this._type = args.type;
if (!this._name)
this._name = args.propertyName;
}
// ---------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------
/**
* Gets the name of column in the database.
* Cannot be used with many-to-many relations since they don't have a column in the database.
* Also only owning sides of the relations have this property.
*/
get name(): string {
if (this.joinColumn && this.joinColumn.name)
return this.joinColumn.name;
if (!this.isOwning || this.isManyToMany)
throw new Error(`Relation name cannot be retrieved for many-to-many relations or not owning relations.`);
return this.namingStrategy ? this.namingStrategy.relationName(this._name) : this._name;
return this.joinColumn.name;
}
/**
* Gets the name of column to which this relation is referenced.
* Cannot be used with many-to-many relations since all referenced are in the junction table.
* Also only owning sides of the relations have this property.
*/
get referencedColumnName(): string {
if (!this.isOwning)
throw new Error(`Only owning side of the relations can have information about referenced column names.`);
// for many-to-one and owner one-to-one relations we get referenced column from join column
if (this.joinColumn && this.joinColumn.referencedColumn && this.joinColumn.referencedColumn.name)
return this.joinColumn.referencedColumn.name;
return this.inverseEntityMetadata.primaryColumn.propertyName;
// for many-to-many relation we give referenced column depend of owner side
if (this.joinTable) { // need to check if this algorithm works correctly
if (this.isOwning) {
return this.joinTable.referencedColumn.name;
} else {
return this.joinTable.inverseReferencedColumn.name;
}
}
// this should not be possible, but anyway throw error
throw new Error(`Cannot get referenced column name of the relation ${this.entityMetadata.name}#${this.name}`);
}
/**
* Gets the property's type to which this relation is applied.
*/
get type(): Function {
return this._type();
}
/**
* Checks if this relation is lazy-load style relation.
*/
get isLazy(): boolean {
return this.propertyType && this.propertyType.name && this.propertyType.name.toLowerCase() === "promise";
}
/**
* Indicates if this side is an owner of this relation.
*/
get isOwning() {
return this.isManyToOne ||
(this.isManyToMany && this.joinTable) ||
(this.isOneToOne && this.joinColumn);
}
get type(): Function {
return this._type();
}
get inverseSideProperty(): string {
if (this._inverseSideProperty) {
return this.computeInverseSide(this._inverseSideProperty);
} else if (this.isTreeParent) {
return this.entityMetadata.treeChildrenRelation.propertyName;
} else if (this.isTreeChildren) {
return this.entityMetadata.treeParentRelation.propertyName;
}
return "";
}
get inverseRelation(): RelationMetadata {
const relation = this.inverseEntityMetadata.findRelationWithPropertyName(this.inverseSideProperty);
if (!relation)
throw new Error(`Inverse side was not found in the relation ${this.entityMetadata.name}#${this.inverseSideProperty}`);
return relation;
return !!(this.isManyToOne ||
(this.isManyToMany && this.joinTable) ||
(this.isOneToOne && this.joinColumn));
}
/**
* Checks if this relation's type is "one-to-one".
*/
get isOneToOne(): boolean {
return this.relationType === RelationTypes.ONE_TO_ONE;
}
/**
* Checks if this relation is owner side of the "one-to-one" relation.
*/
get isOneToOneOwner(): boolean {
return this.isOneToOne && this.isOwning;
}
/**
* Checks if this relation is NOT owner side of the "one-to-one" relation.
*/
get isOneToOneNotOwner(): boolean {
return this.isOneToOne && !this.isOwning;
}
/**
* Checks if this relation's type is "one-to-many".
*/
get isOneToMany(): boolean {
return this.relationType === RelationTypes.ONE_TO_MANY;
}
/**
* Checks if this relation's type is "many-to-one".
*/
get isManyToOne(): boolean {
return this.relationType === RelationTypes.MANY_TO_ONE;
}
/**
* Checks if this relation's type is "many-to-many".
*/
get isManyToMany(): boolean {
return this.relationType === RelationTypes.MANY_TO_MANY;
}
/**
* Checks if inverse side is specified by a relation.
*/
get hasInverseSide(): boolean {
return this.inverseEntityMetadata && this.inverseEntityMetadata.hasRelationWithPropertyName(this.inverseSideProperty);
}
get isLazy(): boolean {
return this.propertyType && this.propertyType.name && this.propertyType.name.toLowerCase() === "promise";
/**
* Gets the property name of the inverse side of the relation.
*/
get inverseSideProperty(): string {
if (this._inverseSideProperty) {
return this.computeInverseSide(this._inverseSideProperty);
} else if (this.isTreeParent && this.entityMetadata.hasTreeChildrenRelation) {
return this.entityMetadata.treeChildrenRelation.propertyName;
} else if (this.isTreeChildren && this.entityMetadata.hasTreeParentRelation) {
return this.entityMetadata.treeParentRelation.propertyName;
}
return "";
}
/**
* Gets the relation metadata of the inverse side of this relation.
*/
get inverseRelation(): RelationMetadata {
const relation = this.inverseEntityMetadata.findRelationWithPropertyName(this.inverseSideProperty);
if (!relation)
throw new Error(`Inverse side was not found in the relation ${this.entityMetadata.name}#${this.inverseSideProperty}`);
return relation;
}
// ---------------------------------------------------------------------

View File

@ -1,6 +1,8 @@
import {PropertyMetadata} from "./PropertyMetadata";
import {RelationsCountMetadataArgs} from "../metadata-args/RelationsCountMetadataArgs";
/**
* Contains all information about entity's relation count.
*/
export class RelationsCountMetadata extends PropertyMetadata {
@ -9,7 +11,7 @@ export class RelationsCountMetadata extends PropertyMetadata {
// ---------------------------------------------------------------------
/**
* The real reflected property type.
* Relation which need to count.
*/
readonly relation: string|((object: any) => any);
@ -17,9 +19,9 @@ export class RelationsCountMetadata extends PropertyMetadata {
// Constructor
// ---------------------------------------------------------------------
constructor(target: Function, propertyName: string, relation: string|((object: any) => any)) {
super(target, propertyName);
this.relation = relation;
constructor(args: RelationsCountMetadataArgs) {
super(args.target, args.propertyName);
this.relation = args.relation;
}
}

View File

@ -1,5 +1,6 @@
import {TargetMetadata} from "./TargetMetadata";
import {EntityMetadata} from "./EntityMetadata";
import {TableMetadataArgs} from "../metadata-args/TableMetadataArgs";
/**
* Table type.
@ -27,35 +28,51 @@ export class TableMetadata extends TargetMetadata {
/**
* Indicates if this table is abstract or not. Regular tables can inherit columns from abstract tables.
*/
private readonly tableType: TableType;
private readonly tableType: TableType = "regular";
/**
* Table name in the database.
*/
private readonly _name: string;
private readonly _name: string|undefined;
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(target?: Function, name?: string, type: TableType = "regular") {
super(target);
if (name)
constructor(target?: Function, name?: string, type?: TableType);
constructor(args: TableMetadataArgs);
constructor(argsOrTarget: TableMetadataArgs|Function|undefined, name?: string, type: TableType = "regular") {
super(arguments.length === 1 ? (argsOrTarget as TableMetadataArgs).target : argsOrTarget as Function);
if (arguments.length === 1) {
const metadata = argsOrTarget as TableMetadataArgs;
this.tableType = metadata.type;
this._name = metadata.name;
} else {
this._name = name;
if (type)
this.tableType = type;
}
}
// ---------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------
/**
* Checks if this table is abstract.
* Table name in the database.
*/
get isAbstract() {
return this.tableType === "abstract";
get name() {
// if custom name is given then use it
if (this._name)
return this.entityMetadata.namingStrategy.tableNameCustomized(this._name);
// otherwise use target's table name
if (this.target)
return this.entityMetadata.namingStrategy.tableName((this.target as any).name);
// in the case if error
throw new Error("Table does not have neither table name neither target specified.");
}
/**
@ -65,35 +82,18 @@ export class TableMetadata extends TargetMetadata {
return this.tableType === "regular";
}
/**
* Checks if this table is abstract.
*/
get isAbstract() {
return this.tableType === "abstract";
}
/**
* Checks if this table is a closure table.
*/
get isClosure() {
return this.tableType === "closure";
}
/**
* Table name in the database.
*/
get name() {
if (this._name)
return this._name;
return this.entityMetadata.namingStrategy.tableName((<any>this.target).name);
}
// ---------------------------------------------------------------------
// Public Methods
// ---------------------------------------------------------------------
/**
* Checks if this table is inherited from another table.
*/
isInherited(anotherTable: TableMetadata) {
return Object.getPrototypeOf(this.target.prototype).constructor === anotherTable.target;
// we cannot use instanceOf in this method, because we need order of inherited tables, to ensure that
// properties get inherited in a right order. To achieve it we can only check a first parent of the class
// return this.target.prototype instanceof anotherTable.target;
}
}

View File

@ -6,7 +6,10 @@ export abstract class TargetMetadata {
// ---------------------------------------------------------------------
// Readonly Properties
// ---------------------------------------------------------------------
/**
* Target class to which metadata is applied.
*/
readonly target: Function;
// ---------------------------------------------------------------------

View File

@ -1,48 +0,0 @@
import {ColumnOptions} from "../options/ColumnOptions";
import {ColumnMode} from "../ColumnMetadata";
/**
* Constructor arguments for ColumnMetadata class.
*/
export interface ColumnMetadataArgs {
/**
* Class to which this column is applied.
*/
target?: Function;
/**
* Class's property name to which this column is applied.
*/
propertyName?: string;
/**
* Class's property type (reflected) to which this column is applied.
*/
propertyType: string;
/**
* Indicates if this column is primary key or not.
*/
isPrimaryKey?: boolean;
/**
* Indicates if this column is virtual or not.
*/
isVirtual?: boolean;
/**
* Column mode.
*/
mode?: ColumnMode;
/**
* Indicates if this column is order id column.
*/
isOrderId?: boolean;
/**
* Extra column options.
*/
options: ColumnOptions;
}

View File

@ -10,15 +10,27 @@ export class DefaultNamingStrategy implements NamingStrategyInterface {
return _.snakeCase(className);
}
tableNameCustomized(customName: string): string {
return customName;
}
columnName(propertyName: string): string {
return propertyName;
}
columnNameCustomized(customName: string): string {
return customName;
}
relationName(propertyName: string): string {
return propertyName;
}
indexName(target: Function, name: string, columns: string[]): string {
relationNameCustomized(customName: string): string {
return customName;
}
indexName(target: Function, name: string|undefined, columns: string[]): string {
if (name)
return name;
@ -34,11 +46,11 @@ export class DefaultNamingStrategy implements NamingStrategyInterface {
joinTableName(firstTableName: string,
secondTableName: string,
firstColumnName: string,
secondColumnName: string,
firstPropertyName: string,
secondPropertyName: string): string {
return _.snakeCase(firstTableName + "_" + firstColumnName + "_" + secondTableName + "_" + secondPropertyName);
secondPropertyName: string,
firstColumnName: string,
secondColumnName: string): string {
return _.snakeCase(firstTableName + "_" + firstPropertyName + "_" + secondTableName + "_" + secondColumnName);
}
joinTableColumnName(tableName: string, columnName: string, secondTableName: string, secondColumnName: string): string {
@ -56,5 +68,10 @@ export class DefaultNamingStrategy implements NamingStrategyInterface {
closureJunctionTableName(tableName: string): string {
return tableName + "_closure";
}
foreignKeyName(tableName: string, columnNames: string[], referencedTableName: string, referencedColumnNames: string[]): string {
const key = `${tableName}_${columnNames.join("_")}_${referencedTableName}_${referencedColumnNames.join("_")}`;
return "fk_" + require("sha1")(key); // todo: use crypto instead?
}
}

View File

@ -9,20 +9,35 @@ export interface NamingStrategyInterface {
*/
tableName(className: string): string;
/**
* Gets the table name from the given custom table name.
*/
tableNameCustomized(customName: string): string;
/**
* Gets the table's column name from the given property name.
*/
columnName(propertyName: string): string;
/**
* Gets the column name from the given custom column name.
*/
columnNameCustomized(customName: string): string;
/**
* Gets the table's relation name from the given property name.
*/
relationName(propertyName: string): string;
/**
* Gets the relation name from the given custom relation name.
*/
relationNameCustomized(customName: string): string;
/**
* Gets the name of the index - simple and compose index.
*/
indexName(target: Function, name: string, columns: string[]): string;
indexName(target: Function, name: string|undefined, columns: string[]): string;
/**
* Gets the name of the join column used in the one-to-one and many-to-one relations.
@ -33,11 +48,11 @@ export interface NamingStrategyInterface {
* Gets the name of the join table used in the many-to-many relations.
*/
joinTableName(firstTableName: string,
secondTableName: string,
firstColumnName: string,
secondColumnName: string,
secondTableName: string,
firstPropertyName: string,
secondPropertyName: string): string;
secondPropertyName: string,
firstColumnName: string,
secondColumnName: string): string;
/**
* Gets the name of the column used for columns in the junction tables.
@ -53,5 +68,10 @@ export interface NamingStrategyInterface {
* Gets the name for the closure junction table.
*/
closureJunctionTableName(tableName: string): string;
/**
* Gets the name of the foreign key.
*/
foreignKeyName(tableName: string, columnNames: string[], referencedTableName: string, referencedColumnNames: string[]): string;
}

View File

@ -7,7 +7,7 @@ import {JunctionInsertOperation} from "./operation/JunctionInsertOperation";
import {UpdateOperation} from "./operation/UpdateOperation";
import {CascadesNotAllowedError} from "./error/CascadesNotAllowedError";
import {RemoveOperation} from "./operation/RemoveOperation";
import {EntityMetadataCollection} from "../metadata/collection/EntityMetadataCollection";
import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetadataCollection";
import {UpdateByInverseSideOperation} from "./operation/UpdateByInverseSideOperation";
/**

View File

@ -6,7 +6,7 @@ import {InsertOperation} from "./operation/InsertOperation";
import {JunctionRemoveOperation} from "./operation/JunctionRemoveOperation";
import {UpdateByRelationOperation} from "./operation/UpdateByRelationOperation";
import {Broadcaster} from "../subscriber/Broadcaster";
import {EntityMetadataCollection} from "../metadata/collection/EntityMetadataCollection";
import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetadataCollection";
import {Driver} from "../driver/Driver";
import {UpdateByInverseSideOperation} from "./operation/UpdateByInverseSideOperation";
@ -251,9 +251,9 @@ export class PersistOperationExecutor {
// const parentLevel = parentEntity ? (parentEntity[metadata.treeLevelColumn.propertyName] || 0) : 0;
insertOperation.entity[metadata.treeLevelColumn.propertyName] = insertOperation.treeLevel;
}
if (metadata.hasTreeChildrenCountColumn) {
/*if (metadata.hasTreeChildrenCountColumn) {
insertOperation.entity[metadata.treeChildrenCountColumn.propertyName] = 0;
}
}*/
});
persistOperation.updates.forEach(updateOperation => {
const metadata = this.entityMetadatas.findByTarget(updateOperation.entity.constructor);
@ -439,10 +439,10 @@ export class PersistOperationExecutor {
allValues.push(parentLevel + 1);
}
if (metadata.hasTreeChildrenCountColumn) {
/*if (metadata.hasTreeChildrenCountColumn) {
allColumns.push(metadata.treeChildrenCountColumn.name);
allValues.push(0);
}
}*/
return this.driver.insert(metadata.table.name, this.zipObject(allColumns, allValues));
}

View File

@ -2,7 +2,7 @@ import {Alias} from "./alias/Alias";
import {AliasMap} from "./alias/AliasMap";
import {RawSqlResultsToEntityTransformer} from "./transformer/RawSqlResultsToEntityTransformer";
import {Broadcaster} from "../subscriber/Broadcaster";
import {EntityMetadataCollection} from "../metadata/collection/EntityMetadataCollection";
import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetadataCollection";
import {Driver} from "../driver/Driver";
/**
@ -574,11 +574,13 @@ export class QueryBuilder<Entity> {
.filter(join => this.selects.indexOf(join.alias.name) !== -1)
.forEach(join => {
const joinMetadata = this.aliasMap.getEntityMetadataByAlias(join.alias);
if (!joinMetadata)
throw new Error("Cannot get entity metadata for the given alias " + join.alias.name);
joinMetadata.columns.forEach(column => {
allSelects.push(join.alias.name + "." + column.name + " AS " + join.alias.name + "_" + column.propertyName);
});
if (joinMetadata) {
joinMetadata.columns.forEach(column => {
allSelects.push(join.alias.name + "." + column.name + " AS " + join.alias.name + "_" + column.propertyName);
});
} else {
allSelects.push(join.alias.name);
}
});
// add all other selects
@ -595,7 +597,7 @@ export class QueryBuilder<Entity> {
case "select":
return "SELECT " + allSelects.join(", ") + " FROM " + tableName + " " + alias;
case "delete":
return "DELETE FROM " + tableName + " " + (alias ? alias : "");
return "DELETE " + (alias ? alias : "") + " FROM " + tableName + " " + (alias ? alias : "");
case "update":
const updateSet = Object.keys(this.updateQuerySet).map(key => key + "=:updateQuerySet_" + key);
const params = Object.keys(this.updateQuerySet).reduce((object, key) => {
@ -633,10 +635,10 @@ export class QueryBuilder<Entity> {
const metadata = this.aliasMap.getEntityMetadataByAlias(alias);
if (!metadata) return;
metadata.columns.forEach(column => {
statement = statement.replace(new RegExp(alias.name + "." + column.propertyName, 'g'), alias.name + "." + column.name);
statement = statement.replace(new RegExp(alias.name + "." + column.propertyName, "g"), alias.name + "." + column.name);
});
metadata.relations.forEach(relation => {
statement = statement.replace(new RegExp(alias.name + "." + relation.propertyName, 'g'), alias.name + "." + relation.name);
metadata.relationsWithJoinColumns.forEach(relation => {
statement = statement.replace(new RegExp(alias.name + "." + relation.propertyName, "g"), alias.name + "." + relation.name);
});
});
return statement;
@ -775,9 +777,8 @@ export class QueryBuilder<Entity> {
if (entityOrProperty instanceof Function) {
aliasObj.target = entityOrProperty;
} else if (typeof entityOrProperty === "string" && entityOrProperty.indexOf(".") !== -1) {
aliasObj.parentAliasName = entityOrProperty.split(".")[0];
aliasObj.parentPropertyName = entityOrProperty.split(".")[1];
} else if (this.isPropertyAlias(entityOrProperty)) {
[aliasObj.parentAliasName, aliasObj.parentPropertyName] = entityOrProperty.split(".");
} else if (typeof entityOrProperty === "string") {
tableName = entityOrProperty;
@ -791,4 +792,24 @@ export class QueryBuilder<Entity> {
return this;
}
private isPropertyAlias(str: any): str is string {
if (!(typeof str === "string"))
return false;
if (str.indexOf(".") === -1)
return false;
const aliasName = str.split(".")[0];
const propertyName = str.split(".")[1];
if (!aliasName || !propertyName)
return false;
const aliasNameRegexp = /^[a-zA-Z0-9_-]+$/;
const propertyNameRegexp = aliasNameRegexp;
if (!aliasNameRegexp.test(aliasName) || !propertyNameRegexp.test(propertyName))
return false;
return true;
}
}

View File

@ -24,7 +24,7 @@ export class PlainObjectToDatabaseEntityTransformer<Entity> {
transform(object: any, metadata: EntityMetadata, queryBuilder: QueryBuilder<Entity>): Promise<Entity> {
// if object does not have id then nothing to load really
if (!metadata.hasPrimaryKey || !object[metadata.primaryColumn.name])
if (!metadata.hasPrimaryColumn || !object[metadata.primaryColumn.name])
return Promise.reject<Entity>("Given object does not have a primary column, cannot transform it to database entity.");
const alias = queryBuilder.alias;

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.
@ -93,7 +94,7 @@ export class ReactiveRepository<Entity> {
find(conditions: Object): Rx.Observable<Entity[]>;
/**
* Finds entities with .
* Finds entities that match given find options.
*/
find(options: FindOptions): Rx.Observable<Entity[]>;
@ -208,4 +209,72 @@ export class ReactiveRepository<Entity> {
return Rx.Observable.fromPromise(this.repository.transaction(runInTransaction));
}
/**
* Sets given relatedEntityId to the value of the relation of the entity with entityId id.
* Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
setRelation(relationName: string, entityId: any, relatedEntityId: any): Rx.Observable<void>;
setRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityId: any): Rx.Observable<void>;
setRelation(relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityId: any): Rx.Observable<void> {
return Rx.Observable.fromPromise(this.repository.setRelation(relationName as any, entityId, relatedEntityId));
}
/**
* Adds a new relation between two entities into relation's many-to-many table.
* Should be used when you want quickly and efficiently add a relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
addToRelation(relationName: string, entityId: any, relatedEntityIds: any[]): Rx.Observable<void>;
addToRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Rx.Observable<void>;
addToRelation(relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Rx.Observable<void> {
return Rx.Observable.fromPromise(this.repository.addToRelation(relationName as any, entityId, relatedEntityIds));
}
/**
* Removes a relation between two entities from relation's many-to-many table.
* Should be used when you want quickly and efficiently remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeFromRelation(relationName: string, entityId: any, relatedEntityIds: any[]): Rx.Observable<void>;
removeFromRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Rx.Observable<void>;
removeFromRelation(relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Rx.Observable<void> {
return Rx.Observable.fromPromise(this.repository.removeFromRelation(relationName as any, entityId, relatedEntityIds));
}
/**
* Performs both #addToRelation and #removeFromRelation operations.
* Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
addAndRemoveFromRelation(relation: string, entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Rx.Observable<void>;
addAndRemoveFromRelation(relation: ((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Rx.Observable<void>;
addAndRemoveFromRelation(relation: string|((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Rx.Observable<void> {
return Rx.Observable.fromPromise(this.repository.addAndRemoveFromRelation(relation as any, entityId, addRelatedEntityIds, removeRelatedEntityIds));
}
/**
* Removes entity with the given id.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeById(id: any): Rx.Observable<void> {
return Rx.Observable.fromPromise(this.repository.removeById(id));
}
/**
* Removes all entities with the given ids.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeByIds(ids: any[]): Rx.Observable<void> {
return Rx.Observable.fromPromise(this.repository.removeByIds(ids));
}
// -------------------------------------------------------------------------
// Static Methods
// -------------------------------------------------------------------------
static ownsMetadata(reactiveRepository: ReactiveRepository<any>, metadata: EntityMetadata) {
return Repository.ownsMetadata(reactiveRepository.repository, metadata);
}
}

View File

@ -1,6 +1,7 @@
import {ReactiveRepository} from "./ReactiveRepository";
import {TreeRepository} from "./TreeRepository";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import * as Rx from "rxjs/Rx";
/**
* Tree repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc.
@ -23,8 +24,8 @@ export class ReactiveTreeRepository<Entity> extends ReactiveRepository<Entity> {
/**
* Roots are entities that have no ancestors. Finds them all.
*/
findRoots(): Promise<Entity[]> {
return this.repository.findRoots();
findRoots(): Rx.Observable<Entity[]> {
return Rx.Observable.fromPromise(this.repository.findRoots());
}
/**
@ -37,22 +38,22 @@ export class ReactiveTreeRepository<Entity> extends ReactiveRepository<Entity> {
/**
* Gets all children (descendants) of the given entity. Returns them all in a flat array.
*/
findDescendants(entity: Entity): Promise<Entity[]> {
return this.repository.findDescendants(entity);
findDescendants(entity: Entity): Rx.Observable<Entity[]> {
return Rx.Observable.fromPromise(this.repository.findDescendants(entity));
}
/**
* Gets all children (descendants) of the given entity. Returns them in a tree - nested into each other.
*/
findDescendantsTree(entity: Entity): Promise<Entity> {
return this.repository.findDescendantsTree(entity);
findDescendantsTree(entity: Entity): Rx.Observable<Entity> {
return Rx.Observable.fromPromise(this.repository.findDescendantsTree(entity));
}
/**
* Gets number of descendants of the entity.
*/
countDescendants(entity: Entity): Promise<number> {
return this.repository.countDescendants(entity);
countDescendants(entity: Entity): Rx.Observable<number> {
return Rx.Observable.fromPromise(this.repository.countDescendants(entity));
}
/**
@ -65,28 +66,28 @@ export class ReactiveTreeRepository<Entity> extends ReactiveRepository<Entity> {
/**
* Gets all parents (ancestors) of the given entity. Returns them all in a flat array.
*/
findAncestors(entity: Entity): Promise<Entity[]> {
return this.repository.findAncestors(entity);
findAncestors(entity: Entity): Rx.Observable<Entity[]> {
return Rx.Observable.fromPromise(this.repository.findAncestors(entity));
}
/**
* Gets all parents (ancestors) of the given entity. Returns them in a tree - nested into each other.
*/
findAncestorsTree(entity: Entity): Promise<Entity> {
return this.repository.findAncestorsTree(entity);
findAncestorsTree(entity: Entity): Rx.Observable<Entity> {
return Rx.Observable.fromPromise(this.repository.findAncestorsTree(entity));
}
/**
* Gets number of ancestors of the entity.
*/
countAncestors(entity: Entity): Promise<number> {
return this.repository.countAncestors(entity);
countAncestors(entity: Entity): Rx.Observable<number> {
return Rx.Observable.fromPromise(this.repository.countAncestors(entity));
}
/**
* Moves entity to the children of then given entity.
*
move(entity: Entity, to: Entity): Promise<void> {
move(entity: Entity, to: Entity): Rx.Observable<void> {
return this.repository.move(entity, to);
}
*/

View File

@ -7,7 +7,7 @@ import {EntityPersistOperationBuilder} from "../persistment/EntityPersistOperati
import {PersistOperationExecutor} from "../persistment/PersistOperationExecutor";
import {EntityWithId} from "../persistment/operation/PersistOperation";
import {FindOptions, FindOptionsUtils} from "./FindOptions";
import {EntityMetadataCollection} from "../metadata/collection/EntityMetadataCollection";
import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetadataCollection";
import {Broadcaster} from "../subscriber/Broadcaster";
import {Driver} from "../driver/Driver";
@ -15,23 +15,23 @@ 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
// -------------------------------------------------------------------------
constructor(protected connection: Connection,
constructor(protected connection: Connection,
protected entityMetadatas: EntityMetadataCollection,
protected metadata: EntityMetadata) {
this.driver = connection.driver;
@ -270,6 +270,141 @@ export class Repository<Entity> {
.then(() => runInTransactionResult);
}
/**
* Sets given relatedEntityId to the value of the relation of the entity with entityId id.
* Should be used when you want quickly and efficiently set a relation (for many-to-one and one-to-many) to some entity.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
setRelation(relationName: string, entityId: any, relatedEntityId: any): Promise<void>;
setRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityId: any): Promise<void>;
setRelation(relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityId: any): Promise<void> {
const propertyName = this.metadata.computePropertyName(relationName);
if (!this.metadata.hasRelationWithPropertyName(propertyName))
throw new Error(`Relation ${propertyName} was not found in the ${this.metadata.name} entity.`);
const relation = this.metadata.findRelationWithPropertyName(propertyName);
// if (relation.isManyToMany || relation.isOneToMany || relation.isOneToOneNotOwner)
// throw new Error(`Only many-to-one and one-to-one with join column are supported for this operation. ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`);
if (relation.isManyToMany)
throw new Error(`Many-to-many relation is not supported for this operation. Use #addToRelation method for many-to-many relations.`);
let table: string, values: any = {}, conditions: any = {};
if (relation.isOwning) {
table = relation.entityMetadata.table.name;
values[relation.name] = relatedEntityId;
conditions[relation.joinColumn.referencedColumn.name] = entityId;
} else {
table = relation.inverseEntityMetadata.table.name;
values[relation.inverseRelation.name] = relatedEntityId;
conditions[relation.inverseRelation.joinColumn.referencedColumn.name] = entityId;
}
return this.driver.update(table, values, conditions).then(() => {});
}
/**
* Adds a new relation between two entities into relation's many-to-many table.
* Should be used when you want quickly and efficiently add a relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
addToRelation(relationName: string, entityId: any, relatedEntityIds: any[]): Promise<void>;
addToRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void>;
addToRelation(relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void> {
const propertyName = this.metadata.computePropertyName(relationName);
if (!this.metadata.hasRelationWithPropertyName(propertyName))
throw new Error(`Relation ${propertyName} was not found in the ${this.metadata.name} entity.`);
const relation = this.metadata.findRelationWithPropertyName(propertyName);
if (!relation.isManyToMany)
throw new Error(`Only many-to-many relation supported for this operation. However ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`);
const insertPromises = relatedEntityIds.map(relatedEntityId => {
const values: any = { };
if (relation.isOwning) {
values[relation.junctionEntityMetadata.columns[0].name] = entityId;
values[relation.junctionEntityMetadata.columns[1].name] = relatedEntityId;
} else {
values[relation.junctionEntityMetadata.columns[1].name] = entityId;
values[relation.junctionEntityMetadata.columns[0].name] = relatedEntityId;
}
return this.driver.insert(relation.junctionEntityMetadata.table.name, values);
});
return Promise.all(insertPromises).then(() => {});
}
/**
* Removes a relation between two entities from relation's many-to-many table.
* Should be used when you want quickly and efficiently remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeFromRelation(relationName: string, entityId: any, relatedEntityIds: any[]): Promise<void>;
removeFromRelation(relationName: ((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void>;
removeFromRelation(relationName: string|((t: Entity) => string|any), entityId: any, relatedEntityIds: any[]): Promise<void> {
const propertyName = this.metadata.computePropertyName(relationName);
if (!this.metadata.hasRelationWithPropertyName(propertyName))
throw new Error(`Relation ${propertyName} was not found in the ${this.metadata.name} entity.`);
const relation = this.metadata.findRelationWithPropertyName(propertyName);
if (!relation.isManyToMany)
throw new Error(`Only many-to-many relation supported for this operation. However ${this.metadata.name}#${propertyName} relation type is ${relation.relationType}`);
const qb = this.createQueryBuilder("junctionEntity")
.delete(relation.junctionEntityMetadata.table.name);
const firstColumnName = relation.isOwning ? relation.junctionEntityMetadata.columns[0].name : relation.junctionEntityMetadata.columns[1].name;
const secondColumnName = relation.isOwning ? relation.junctionEntityMetadata.columns[1].name : relation.junctionEntityMetadata.columns[0].name;
relatedEntityIds.forEach((relatedEntityId, index) => {
qb.orWhere(`(${firstColumnName}=:entityId AND ${secondColumnName}=:relatedEntity_${index})`)
.setParameter("relatedEntity_" + index, relatedEntityId);
});
return qb
.setParameter("entityId", entityId)
.execute()
.then(() => {});
}
/**
* Performs both #addToRelation and #removeFromRelation operations.
* Should be used when you want quickly and efficiently and and remove a many-to-many relation between two entities.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
addAndRemoveFromRelation(relation: string, entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise<void>;
addAndRemoveFromRelation(relation: ((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise<void>;
addAndRemoveFromRelation(relation: string|((t: Entity) => string|any), entityId: any, addRelatedEntityIds: any[], removeRelatedEntityIds: any[]): Promise<void> {
return Promise.all([
this.addToRelation(relation as any, entityId, addRelatedEntityIds),
this.removeFromRelation(relation as any, entityId, removeRelatedEntityIds)
]).then(() => {});
}
/**
* Removes entity with the given id.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeById(id: any) {
const alias = this.metadata.table.name;
return this.createQueryBuilder(alias)
.delete()
.where(alias + "." + this.metadata.primaryColumn.propertyName + "=:id", { id: id })
.execute()
.then(() => {});
}
/**
* Removes all entities with the given ids.
* Note that event listeners and event subscribers won't work (and will not send any events) when using this operation.
*/
removeByIds(ids: any[]) {
const alias = this.metadata.table.name;
return this.createQueryBuilder(alias)
.delete()
.where(alias + "." + this.metadata.primaryColumn.propertyName + " IN (:ids)", { ids: ids })
.execute()
.then(() => {});
}
// -------------------------------------------------------------------------
// Private Methods
// -------------------------------------------------------------------------
@ -412,4 +547,12 @@ export class Repository<Entity> {
return entity;
}
// -------------------------------------------------------------------------
// Static Methods
// -------------------------------------------------------------------------
static ownsMetadata(repository: Repository<any>, metadata: EntityMetadata) {
return repository.metadata === metadata;
}
}

Some files were not shown because too many files have changed in this diff Show More