upgraded typescript version and fixed all strictNullChecks flag errors

This commit is contained in:
Umed Khudoiberdiev 2016-04-24 17:32:35 +05:00
parent bcb684dced
commit bd36765c54
45 changed files with 240 additions and 190 deletions

View File

@ -791,6 +791,8 @@ Feel free to contribute ;)
* send entity changeset in update event
* create a gulp task for schema update
* fixtures and migrations
* lazy loading of properties throw promises? Property can have a Promise\<Entity\> type.
* cover everything with tests
[1]: https://en.wikipedia.org/wiki/Object-relational_mapping

View File

@ -46,7 +46,7 @@ createConnection(options).then(connection => {
.persist(entity)
.then(entity => {
console.log("EverythingEntity has been saved. Lets insert a new one to update it later");
entity.id = null;
delete entity.id;
return postRepository.persist(entity);
})
.then(entity => {
@ -76,7 +76,7 @@ createConnection(options).then(connection => {
.then(entity => {
console.log("Entity has been updated. Persist once again to make find and remove then");
entity.id = null;
delete entity.id;
return postRepository.persist(entity);
})
.then(entity => {

View File

@ -47,7 +47,7 @@ export class Post {
@OneToOne(type => PostMetadata, metadata => metadata.post, {
cascadeRemove: true
})
metadata: PostMetadata;
metadata: PostMetadata|undefined;
// post has relation with details. full cascades here
@OneToOne(type => PostInformation, information => information.post, {

View File

@ -47,7 +47,7 @@ export class Post {
@ManyToOne(type => PostMetadata, metadata => metadata.posts, {
cascadeRemove: true
})
metadata: PostMetadata;
metadata: PostMetadata|undefined;
// post has relation with details. full cascades here
@ManyToOne(type => PostInformation, information => information.posts, {

View File

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

View File

@ -107,6 +107,8 @@ export class Connection {
return this._driver.connect().then(() => {
if (this._options.autoSchemaCreate === true)
return schemaCreator.create();
return undefined;
});
}

View File

@ -57,15 +57,13 @@ export class ConnectionManager {
* Creates and adds a new connection with given driver.
*/
createConnection(driver: Driver, options: ConnectionOptions): Connection;
createConnection(name: string, driver: Driver, options: ConnectionOptions): Connection;
createConnection(nameOrDriver: string|Driver, driver?: Driver|ConnectionOptions, options?: ConnectionOptions): Connection {
if (typeof nameOrDriver === "object") {
options = <ConnectionOptions> driver;
driver = <Driver> nameOrDriver;
}
const name = typeof nameOrDriver === "string" ? <string> nameOrDriver : "default";
createConnection(name: string|undefined, driver: Driver, options: ConnectionOptions): Connection;
createConnection(nameOrDriver: string|undefined|Driver, driverOrOptions?: Driver|ConnectionOptions, maybeOptions?: ConnectionOptions): Connection {
const name = typeof nameOrDriver === "string" ? nameOrDriver : "default";
const driver = typeof nameOrDriver === "object" ? <Driver> nameOrDriver : <Driver> driverOrOptions;
const options = typeof nameOrDriver === "object" ? <ConnectionOptions> driverOrOptions : <ConnectionOptions> maybeOptions;
return this.connections.createAndPush(name, <Driver> driver, options);
return this.connections.createAndPush(name, driver, options);
}
/**
@ -86,14 +84,10 @@ export class ConnectionManager {
* then default connection is used.
*/
importEntitiesFromDirectories(paths: string[]): void;
importEntitiesFromDirectories(connectionName: string, paths: string[]): void;
importEntitiesFromDirectories(connectionNameOrPaths: string|string[], paths?: string[]): void {
let connectionName = "default";
if (paths) {
connectionName = <string> connectionNameOrPaths;
} else {
paths = <string[]> connectionNameOrPaths;
}
importEntitiesFromDirectories(connectionName: string|undefined, paths: string[]): void;
importEntitiesFromDirectories(connectionNameOrPaths: string|string[]|undefined, maybePaths?: string[]): void {
const connectionName = typeof connectionNameOrPaths === "string" ? connectionNameOrPaths : "default";
const paths = maybePaths ? <string[]> maybePaths : <string[]> connectionNameOrPaths;
this.importEntities(connectionName, importClassesFromDirectories(paths));
}
@ -103,14 +97,10 @@ export class ConnectionManager {
* then default connection is used.
*/
importSubscribersFromDirectories(paths: string[]): void;
importSubscribersFromDirectories(connectionName: string, paths: string[]): void;
importSubscribersFromDirectories(connectionNameOrPaths: string|string[], paths?: string[]): void {
let connectionName = "default";
if (paths) {
connectionName = <string> connectionNameOrPaths;
} else {
paths = <string[]> connectionNameOrPaths;
}
importSubscribersFromDirectories(connectionName: string|undefined, paths: string[]): void;
importSubscribersFromDirectories(connectionNameOrPaths: string|string[]|undefined, maybePaths?: string[]): void {
const connectionName = typeof connectionNameOrPaths === "string" ? connectionNameOrPaths : "default";
const paths = maybePaths ? <string[]> maybePaths : <string[]> connectionNameOrPaths;
this.importSubscribers(connectionName, importClassesFromDirectories(paths));
}
@ -119,14 +109,13 @@ export class ConnectionManager {
* Imports entities for the given connection. If connection name is not given then default connection is used.
*/
importEntities(entities: Function[]): void;
importEntities(connectionName: string, entities: Function[]): void;
importEntities(connectionNameOrEntities: string|Function[], entities?: Function[]): void {
let connectionName = "default";
if (entities) {
connectionName = <string> connectionNameOrEntities;
} else {
entities = <Function[]> connectionNameOrEntities;
}
importEntities(connectionName: string|undefined, entities: Function[]): void;
importEntities(connectionNameOrEntities: string|undefined|Function[], maybeEntities?: Function[]): void {
const connectionName = typeof connectionNameOrEntities === "string" ? connectionNameOrEntities : "default";
const entities = maybeEntities ? <Function[]> maybeEntities : <Function[]> connectionNameOrEntities;
// console.log("entities", entities);
const entityMetadatas = this.entityMetadataBuilder.build(entities);
const entityListenerMetadatas = defaultMetadataStorage.findEntityListenersForClasses(entities);
@ -139,14 +128,10 @@ export class ConnectionManager {
* Imports entities for the given connection. If connection name is not given then default connection is used.
*/
importSubscribers(subscriberClasses: Function[]): void;
importSubscribers(connectionName: string, subscriberClasses: Function[]): void;
importSubscribers(connectionNameOrSubscriberClasses: string|Function[], subscriberClasses?: Function[]): void {
let connectionName = "default";
if (subscriberClasses) {
connectionName = <string> connectionNameOrSubscriberClasses;
} else {
subscriberClasses = <Function[]> connectionNameOrSubscriberClasses;
}
importSubscribers(connectionName: string|undefined, subscriberClasses: Function[]): void;
importSubscribers(connectionNameOrSubscriberClasses: string|undefined|Function[], maybeSubscriberClasses?: Function[]): void {
const connectionName = typeof connectionNameOrSubscriberClasses === "string" ? connectionNameOrSubscriberClasses : "default";
const subscriberClasses = maybeSubscriberClasses ? <Function[]> maybeSubscriberClasses : <Function[]> connectionNameOrSubscriberClasses;
const subscribers = defaultMetadataStorage
.findEventSubscribersForClasses(subscriberClasses)

View File

@ -38,19 +38,18 @@ export function Column(typeOrOptions?: ColumnType|ColumnOptions, options?: Colum
type = ColumnTypes.determineTypeFromFunction(Reflect.getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options)
options = {};
const columnOptions = options ? options : {} as ColumnOptions;
// check if there is no type in column options then set type from first function argument, or guessed one
if (!options.type)
options.type = type;
if (!columnOptions.type)
columnOptions.type = type;
// if we still don't have a type then we need to give error to user that type is required
if (!options.type)
if (!columnOptions.type)
throw new ColumnTypeUndefinedError(object, propertyName);
// check if auto increment is not set for simple column
if (options.autoIncrement)
if (columnOptions.autoIncrement)
throw new AutoIncrementOnlyForPrimaryError(object, propertyName);
// create and register a new column metadata
@ -58,7 +57,7 @@ export function Column(typeOrOptions?: ColumnType|ColumnOptions, options?: Colum
target: object.constructor,
propertyName: propertyName,
propertyType: reflectedType,
options: options
options: columnOptions
}));
};
}

View File

@ -14,11 +14,10 @@ export function CreateDateColumn(options?: ColumnOptions): Function {
const reflectedType = ColumnTypes.typeToString(Reflect.getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options)
options = {};
const columnOptions = options ? options : {} as ColumnOptions;
// implicitly set a type, because this column's type cannot be anything else except date
options.type = <ColumnType> ColumnTypes.DATETIME;
columnOptions.type = ColumnTypes.DATETIME;
// create and register a new column metadata
defaultMetadataStorage.addColumnMetadata(new ColumnMetadata({
@ -26,7 +25,7 @@ export function CreateDateColumn(options?: ColumnOptions): Function {
propertyName: propertyName,
propertyType: reflectedType,
isCreateDate: true,
options: options
options: columnOptions
}));
};
}

View File

@ -41,19 +41,18 @@ export function PrimaryColumn(typeOrOptions?: ColumnType|ColumnOptions, options?
type = ColumnTypes.determineTypeFromFunction(Reflect.getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options)
options = {};
const columnOptions = options ? options : {} as ColumnOptions;
// check if there is no type in column options then set type from first function argument, or guessed one
if (!options.type)
options.type = type;
if (!columnOptions.type)
columnOptions.type = type;
// if we still don't have a type then we need to give error to user that type is required
if (!options.type)
if (!columnOptions.type)
throw new ColumnTypeUndefinedError(object, propertyName);
// check if column is not nullable, because we cannot allow a primary key to be nullable
if (options.nullable)
if (columnOptions.nullable)
throw new PrimaryColumnCannotBeNullableError(object, propertyName);
// create and register a new column metadata
@ -62,7 +61,7 @@ export function PrimaryColumn(typeOrOptions?: ColumnType|ColumnOptions, options?
propertyName: propertyName,
propertyType: reflectedType,
isPrimaryKey: true,
options: options
options: columnOptions
}));
};
}

View File

@ -14,11 +14,10 @@ export function UpdateDateColumn(options?: ColumnOptions): Function {
const reflectedType = ColumnTypes.typeToString(Reflect.getMetadata("design:type", object, propertyName));
// if column options are not given then create a new empty options
if (!options)
options = {};
const columnOptions = options ? options : {} as ColumnOptions;
// implicitly set a type, because this column's type cannot be anything else except date
options.type = <ColumnType> ColumnTypes.DATETIME;
columnOptions.type = ColumnTypes.DATETIME;
// create and register a new column metadata
defaultMetadataStorage.addColumnMetadata(new ColumnMetadata({
@ -26,7 +25,7 @@ export function UpdateDateColumn(options?: ColumnOptions): Function {
propertyName: propertyName,
propertyType: reflectedType,
isUpdateDate: true,
options: options
options: columnOptions
}));
};
}

View File

@ -37,8 +37,7 @@ export function ManyToMany<T>(typeFunction: (type?: any) => ConstructorFunction<
return function (object: Object, propertyName: string) {
if (!options)
options = {};
const relationOptions = options ? options : {} as RelationOptions;
defaultMetadataStorage.addRelationMetadata(new RelationMetadata({
target: object.constructor,
@ -47,7 +46,7 @@ export function ManyToMany<T>(typeFunction: (type?: any) => ConstructorFunction<
type: typeFunction,
inverseSideProperty: inverseSideProperty,
isOwning: true,
options: options
options: relationOptions
}));
};
}

View File

@ -37,8 +37,7 @@ export function ManyToManyInverse<T>(typeFunction: (type?: any) => ConstructorFu
return function (object: Object, propertyName: string) {
if (!options)
options = {};
const relationOptions = options ? options : {} as RelationOptions;
defaultMetadataStorage.addRelationMetadata(new RelationMetadata({
target: object.constructor,
@ -47,7 +46,7 @@ export function ManyToManyInverse<T>(typeFunction: (type?: any) => ConstructorFu
type: typeFunction,
inverseSideProperty: inverseSideProperty,
isOwning: false,
options: options
options: relationOptions
}));
};
}

View File

@ -37,8 +37,7 @@ export function ManyToOne<T>(typeFunction: (type?: any) => ConstructorFunction<T
return function (object: Object, propertyName: string) {
if (!options)
options = {};
const relationOptions = options ? options : {} as RelationOptions;
defaultMetadataStorage.addRelationMetadata(new RelationMetadata({
target: object.constructor,
@ -47,7 +46,7 @@ export function ManyToOne<T>(typeFunction: (type?: any) => ConstructorFunction<T
type: typeFunction,
inverseSideProperty: inverseSideProperty,
isOwning: true,
options: options
options: relationOptions
}));
};
}

View File

@ -34,8 +34,7 @@ export function OneToMany<T>(typeFunction: (type?: any) => ConstructorFunction<T
return function (object: Object, propertyName: string) {
if (!options)
options = {};
const relationOptions = options ? options : {} as RelationOptions;
defaultMetadataStorage.addRelationMetadata(new RelationMetadata({
target: object.constructor,
@ -44,7 +43,7 @@ export function OneToMany<T>(typeFunction: (type?: any) => ConstructorFunction<T
type: typeFunction,
inverseSideProperty: inverseSideProperty,
isOwning: false,
options: options
options: relationOptions
}));
};
}

View File

@ -34,8 +34,7 @@ export function OneToOne<T>(typeFunction: (type?: any) => ConstructorFunction<T>
return function (object: Object, propertyName: string) {
if (!options)
options = {};
const relationOptions = options ? options : {} as RelationOptions;
defaultMetadataStorage.addRelationMetadata(new RelationMetadata({
target: object.constructor,
@ -44,7 +43,7 @@ export function OneToOne<T>(typeFunction: (type?: any) => ConstructorFunction<T>
type: typeFunction,
inverseSideProperty: inverseSideProperty,
isOwning: true,
options: options
options: relationOptions
}));
};
}

View File

@ -37,8 +37,7 @@ export function OneToOneInverse<T>(typeFunction: (type?: any) => ConstructorFunc
return function (object: Object, propertyName: string) {
if (!options)
options = {};
const relationOptions = options ? options : {} as RelationOptions;
defaultMetadataStorage.addRelationMetadata(new RelationMetadata({
target: object.constructor,
@ -47,7 +46,7 @@ export function OneToOneInverse<T>(typeFunction: (type?: any) => ConstructorFunc
type: typeFunction,
inverseSideProperty: inverseSideProperty,
isOwning: false,
options: options
options: relationOptions
}));
};
}

View File

@ -6,6 +6,6 @@ import {defaultMetadataStorage} from "../../metadata-builder/MetadataStorage";
*/
export function AbstractTable() {
return function (cls: Function) {
defaultMetadataStorage.addTableMetadata(new TableMetadata(cls, undefined, true));
defaultMetadataStorage.addTableMetadata(new TableMetadata(cls, true));
};
}

View File

@ -7,6 +7,6 @@ import {TableMetadata} from "../../metadata-builder/metadata/TableMetadata";
*/
export function Table(name?: string) {
return function (cls: Function) {
defaultMetadataStorage.addTableMetadata(new TableMetadata(cls, name, false));
defaultMetadataStorage.addTableMetadata(new TableMetadata(cls, name));
};
}

View File

@ -35,7 +35,7 @@ export abstract class BaseDriver {
protected log(message: any, level: "log"|"debug"|"info"|"error") {
if (!this.connection.options.logging) return;
if ( this.connection.options.logging.logger) {
if (this.connection.options && this.connection.options.logging && this.connection.options.logging.logger) {
this.connection.options.logging.logger(message, level);
} else {
switch (level) {

View File

@ -59,7 +59,14 @@ export class MysqlDriver extends BaseDriver implements Driver {
* Database name to which this connection is made.
*/
get db(): string {
return this.connection.options.database;
if (this.mysqlConnection && this.mysqlConnection.config.database)
return this.mysqlConnection.config.database;
if (this.connection.options.database)
return this.connection.options.database;
throw new Error("Cannot get the database name. Since database name is not explicitly given in configuration " +
"(maybe connection url is used?), database name cannot be retrieved until connection is made.");
}
// -------------------------------------------------------------------------

View File

@ -6,6 +6,7 @@ import {NamingStrategy} from "../naming-strategy/NamingStrategy";
import {ColumnMetadata} from "./metadata/ColumnMetadata";
import {ColumnOptions} from "./options/ColumnOptions";
import {ForeignKeyMetadata} from "./metadata/ForeignKeyMetadata";
import {JunctionTableMetadata} from "./metadata/JunctionTableMetadata";
/**
* Aggregates all metadata: table, column, relation into one collection grouped by tables for a given set of classes.
@ -130,7 +131,7 @@ export class EntityMetadataBuilder {
const tableName = metadata.table.name + "_" + relation.name + "_" +
inverseSideMetadata.table.name + "_" + inverseSideMetadata.primaryColumn.name;
const tableMetadata = new TableMetadata(null, tableName, false);
const tableMetadata = new JunctionTableMetadata(tableName);
const column1options: ColumnOptions = {
length: metadata.primaryColumn.length,
type: metadata.primaryColumn.type,
@ -143,14 +144,10 @@ export class EntityMetadataBuilder {
};
const columns = [
new ColumnMetadata({
target: null,
propertyName: null,
propertyType: inverseSideMetadata.primaryColumn.type,
options: column1options
}),
new ColumnMetadata({
target: null,
propertyName: null,
propertyType: inverseSideMetadata.primaryColumn.type,
options: column2options
})
@ -180,9 +177,7 @@ export class EntityMetadataBuilder {
private filterObjectPropertyMetadatasIfNotExist<T extends PropertyMetadata>(newMetadatas: T[], existsMetadatas: T[]): T[] {
return newMetadatas.filter(fieldFromMapped => {
return existsMetadatas.reduce((found, fieldFromDocument) => {
return fieldFromDocument.propertyName === fieldFromMapped.propertyName ? fieldFromDocument : found;
}, null) === null;
return !!existsMetadatas.find(fieldFromDocument => fieldFromDocument.propertyName === fieldFromMapped.propertyName);
});
}

View File

@ -11,12 +11,12 @@ export interface ColumnMetadataArgs {
/**
* Class to which this column is applied.
*/
target: Function;
target?: Function;
/**
* Class's property name to which this column is applied.
*/
propertyName: string;
propertyName?: string;
/**
* Class's property type (reflected) to which this column is applied.

View File

@ -50,7 +50,8 @@ export class ForeignKeyMetadata {
this._columns = columns;
this._referencedTable = referencedTable;
this._referencedColumns = referencedColumns;
this._onDelete = onDelete;
if (onDelete)
this._onDelete = onDelete;
}
// -------------------------------------------------------------------------

View File

@ -8,7 +8,7 @@ export class IndexMetadata extends PropertyMetadata {
/**
* The name of the index.
*/
name: string;
name: string|undefined;
// ---------------------------------------------------------------------
// Constructor

View File

@ -0,0 +1,16 @@
import {TableMetadata} from "./TableMetadata";
/**
* This metadata interface contains all information about junction table.
*/
export class JunctionTableMetadata extends TableMetadata {
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(name: string) {
super(undefined, name);
}
}

View File

@ -21,9 +21,12 @@ export abstract class PropertyMetadata {
// Constructor
// ---------------------------------------------------------------------
constructor(target: Function, propertyName: string) {
this._target = target;
this._propertyName = propertyName;
constructor(target?: Function, propertyName?: string) {
if (target)
this._target = target;
if (propertyName)
this._propertyName = propertyName;
}
// ---------------------------------------------------------------------

View File

@ -265,7 +265,8 @@ export class RelationMetadata extends PropertyMetadata {
if (typeof inverseSide === "string")
return <string> inverseSide;
return null;
// throw new Error("Cannot compute inverse side of the relation");
return "";
}
}

View File

@ -31,16 +31,23 @@ export class TableMetadata {
/**
* Indicates if this table is abstract or not. Regular tables can inherit columns from abstract tables.
*/
private _isAbstract: boolean;
private _isAbstract = false;
// ---------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------
constructor(target: Function, name: string, isAbstract: boolean) {
this._target = target;
this._name = name;
this._isAbstract = isAbstract;
constructor(target?: Function, name?: string);
constructor(target: Function, isAbstract: boolean);
constructor(target: Function, nameOrIsAbstract?: string|boolean, maybeIsAbstract?: boolean) {
if (target)
this._target = target;
if (typeof nameOrIsAbstract === "string")
this._name = nameOrIsAbstract;
if (typeof nameOrIsAbstract === "boolean")
this._isAbstract = nameOrIsAbstract;
if (typeof maybeIsAbstract === "boolean")
this._isAbstract = maybeIsAbstract;
}
// ---------------------------------------------------------------------

View File

@ -148,7 +148,8 @@ export class ColumnTypes {
return ColumnTypes.JSON;
}
return undefined;
throw new Error(`Column type of ${type} cannot be determined.`);
// return undefined;
}
static typeToString(type: Function) {

View File

@ -68,16 +68,16 @@ export class EntityPersistOperationBuilder {
persistOperation.persistedEntity = persistedEntity;
persistOperation.allDbEntities = dbEntities;
persistOperation.allPersistedEntities = allPersistedEntities;
persistOperation.inserts = this.findCascadeInsertedEntities(persistedEntity, dbEntities, null);
persistOperation.updates = this.findCascadeUpdateEntities(metadata, dbEntity, persistedEntity, null);
persistOperation.inserts = this.findCascadeInsertedEntities(persistedEntity, dbEntities);
persistOperation.updates = this.findCascadeUpdateEntities(metadata, dbEntity, persistedEntity);
persistOperation.junctionInserts = this.findJunctionInsertOperations(metadata, persistedEntity, dbEntities);
persistOperation.updatesByRelations = this.updateRelations(persistOperation.inserts, persistedEntity);
persistOperation.removes = this.findCascadeRemovedEntities(metadata, dbEntity, allPersistedEntities, null, null, null);
persistOperation.removes = this.findCascadeRemovedEntities(metadata, dbEntity, allPersistedEntities, undefined, undefined, undefined);
persistOperation.junctionRemoves = this.findJunctionRemoveOperations(metadata, dbEntity, allPersistedEntities);
return persistOperation;
}
/**
* Finds columns and relations from entity2 which does not exist or does not match in entity1.
*/
@ -88,31 +88,31 @@ export class EntityPersistOperationBuilder {
allPersistedEntities: EntityWithId[]): PersistOperation {
// const dbEntities = this.extractObjectsById(dbEntity, metadata);
// const allEntities = this.extractObjectsById(newEntity, metadata);
const persistOperation = new PersistOperation();
persistOperation.dbEntity = dbEntity;
persistOperation.persistedEntity = persistedEntity;
persistOperation.allDbEntities = dbEntities;
persistOperation.allPersistedEntities = allPersistedEntities;
persistOperation.removes = this.findCascadeRemovedEntities(metadata, dbEntity, allPersistedEntities, null, null, null);
persistOperation.removes = this.findCascadeRemovedEntities(metadata, dbEntity, allPersistedEntities, undefined, undefined, undefined);
persistOperation.junctionRemoves = this.findJunctionRemoveOperations(metadata, dbEntity, allPersistedEntities);
return persistOperation;
}
// -------------------------------------------------------------------------
// Private Methods
// -------------------------------------------------------------------------
private findCascadeInsertedEntities(newEntity: any,
dbEntities: EntityWithId[],
fromRelation: RelationMetadata,
private findCascadeInsertedEntities(newEntity: any,
dbEntities: EntityWithId[],
fromRelation?: RelationMetadata,
operations: InsertOperation[] = []): InsertOperation[] {
const metadata = this.connection.getEntityMetadata(newEntity.constructor);
const isObjectNew = !this.findEntityWithId(dbEntities, metadata.target, newEntity[metadata.primaryColumn.name]);
// if object is new and should be inserted, we check if cascades are allowed before add it to operations list
if (isObjectNew && !this.checkCascadesAllowed("insert", metadata, fromRelation)) {
if (isObjectNew && fromRelation && !this.checkCascadesAllowed("insert", metadata, fromRelation)) {
return operations; // looks like object is new here, but cascades are not allowed - then we should stop iteration
} else if (isObjectNew && !operations.find(o => o.entity === newEntity)) { // object is new and cascades are allow here
@ -131,22 +131,22 @@ export class EntityPersistOperationBuilder {
this.findCascadeInsertedEntities(value, dbEntities, relation, operations);
}
});
return operations;
}
private findCascadeUpdateEntities(metadata: EntityMetadata,
dbEntity: any,
newEntity: any,
fromRelation: RelationMetadata): UpdateOperation[] {
fromRelation?: RelationMetadata): UpdateOperation[] {
let operations: UpdateOperation[] = [];
if (!dbEntity)
return operations;
const diff = this.diffColumns(metadata, newEntity, dbEntity);
if (diff.length && !this.checkCascadesAllowed("update", metadata, fromRelation)) {
if (diff.length && fromRelation && !this.checkCascadesAllowed("update", metadata, fromRelation)) {
return operations;
} else if (diff.length) {
const entityId = newEntity[metadata.primaryColumn.name];
operations.push(new UpdateOperation(newEntity, entityId, diff));
@ -159,7 +159,7 @@ export class EntityPersistOperationBuilder {
const relationIdColumnName = relMetadata.primaryColumn.name;
const value = newEntity[relation.propertyName];
const dbValue = dbEntity[relation.propertyName];
if (value instanceof Array) {
value.forEach((subEntity: any) => {
const subDbEntity = dbValue.find((subDbEntity: any) => {
@ -173,15 +173,15 @@ export class EntityPersistOperationBuilder {
operations = operations.concat(relationOperations);
}
});
return operations;
}
private findCascadeRemovedEntities(metadata: EntityMetadata,
dbEntity: any,
allPersistedEntities: EntityWithId[],
fromRelation: RelationMetadata,
fromMetadata: EntityMetadata,
fromRelation: RelationMetadata|undefined,
fromMetadata: EntityMetadata|undefined,
fromEntityId: any,
parentAlreadyRemoved: boolean = false): RemoveOperation[] {
let operations: RemoveOperation[] = [];
@ -192,11 +192,11 @@ export class EntityPersistOperationBuilder {
const isObjectRemoved = parentAlreadyRemoved || !this.findEntityWithId(allPersistedEntities, metadata.target, entityId);
// if object is removed and should be removed, we check if cascades are allowed before add it to operations list
if (isObjectRemoved && !this.checkCascadesAllowed("remove", metadata, fromRelation)) {
if (isObjectRemoved && fromRelation && !this.checkCascadesAllowed("remove", metadata, fromRelation)) {
return operations; // looks like object is removed here, but cascades are not allowed - then we should stop iteration
} else if (isObjectRemoved) { // object is remove and cascades are allow here
operations.push(new RemoveOperation(dbEntity, entityId, fromMetadata, fromRelation, fromEntityId));
operations.push(new RemoveOperation(dbEntity, entityId, <EntityMetadata> fromMetadata, fromRelation, fromEntityId));
}
metadata.relations
@ -257,7 +257,7 @@ export class EntityPersistOperationBuilder {
return operations;
}, <UpdateByRelationOperation[]> []);
}
private findJunctionInsertOperations(metadata: EntityMetadata, newEntity: any, dbEntities: EntityWithId[]): JunctionInsertOperation[] {
const dbEntity = dbEntities.find(dbEntity => {
return dbEntity.id === newEntity[metadata.primaryColumn.name] && dbEntity.entity.constructor === metadata.target;
@ -288,11 +288,11 @@ export class EntityPersistOperationBuilder {
return operations;
}, <JunctionInsertOperation[]> []);
}
private findJunctionRemoveOperations(metadata: EntityMetadata, dbEntity: any, newEntities: EntityWithId[]): JunctionInsertOperation[] {
if (!dbEntity) // if new entity is persisted then it does not have anything to be deleted
return [];
const newEntity = newEntities.find(newEntity => {
return newEntity.id === dbEntity[metadata.primaryColumn.name] && newEntity.entity.constructor === metadata.target;
});
@ -334,9 +334,6 @@ export class EntityPersistOperationBuilder {
}
private checkCascadesAllowed(type: "insert"|"update"|"remove", metadata: EntityMetadata, relation: RelationMetadata) {
if (!relation)
return true;
let isAllowed = false;
switch (type) {
case "insert":

View File

@ -150,7 +150,9 @@ export class PersistOperationExecutor {
*/
private executeRemoveRelationOperations(persistOperation: PersistOperation) {
return Promise.all(persistOperation.removes
.filter(operation => operation.relation && !operation.relation.isManyToMany && !operation.relation.isOneToMany)
.filter(operation => {
return !!(operation.relation && !operation.relation.isManyToMany && !operation.relation.isOneToMany);
})
.map(operation => {
return this.updateDeletedRelations(operation);
})
@ -228,11 +230,15 @@ export class PersistOperationExecutor {
}
private updateDeletedRelations(removeOperation: RemoveOperation) { // todo: check if both many-to-one deletions work too
return this.connection.driver.update(
removeOperation.fromMetadata.table.name,
{ [removeOperation.relation.name]: null },
{ [removeOperation.fromMetadata.primaryColumn.name]: removeOperation.fromEntityId }
);
if (removeOperation.relation) {
return this.connection.driver.update(
removeOperation.fromMetadata.table.name,
{ [removeOperation.relation.name]: null },
{ [removeOperation.fromMetadata.primaryColumn.name]: removeOperation.fromEntityId }
);
}
throw new Error("Remove operation relation is not set"); // todo: find out how its possible
}
private delete(entity: any) {

View File

@ -5,7 +5,7 @@ export class RemoveOperation {
constructor(public entity: any,
public entityId: any,
public fromMetadata: EntityMetadata, // todo: use relation.metadata instead?
public relation: RelationMetadata,
public relation: RelationMetadata|undefined,
public fromEntityId: any) {
}
}

View File

@ -77,7 +77,9 @@ export class QueryBuilder<Entity> {
update(updateSet: Object): this;
update(entity: Function, updateSet: Object): this;
update(tableName: string, updateSet: Object): this;
update(tableNameOrEntityOrUpdateSet?: string|Function, updateSet?: Object): this {
update(tableNameOrEntityOrUpdateSet?: string|Function|Object, maybeUpdateSet?: Object): this {
const updateSet = maybeUpdateSet ? maybeUpdateSet : <Object> tableNameOrEntityOrUpdateSet;
if (tableNameOrEntityOrUpdateSet instanceof Function) {
const aliasName = (<any> tableNameOrEntityOrUpdateSet).name;
const aliasObj = new Alias(aliasName);
@ -85,9 +87,6 @@ export class QueryBuilder<Entity> {
this._aliasMap.addMainAlias(aliasObj);
this.fromEntity = { alias: aliasObj };
} else if (typeof tableNameOrEntityOrUpdateSet === "object") {
updateSet = <Object> tableNameOrEntityOrUpdateSet;
} else if (typeof tableNameOrEntityOrUpdateSet === "string") {
this.fromTableName = <string> tableNameOrEntityOrUpdateSet;
}
@ -141,34 +140,34 @@ export class QueryBuilder<Entity> {
innerJoinAndSelect(property: string, alias: string, conditionType?: "on"|"with", condition?: string): this;
innerJoinAndSelect(entity: Function, alias: string, conditionType?: "on"|"with", condition?: string): this;
innerJoinAndSelect(entityOrProperty: Function|string, alias: string, conditionType?: "on"|"with", condition?: string): this {
innerJoinAndSelect(entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition: string = ""): this {
this.addSelect(alias);
return this.join("inner", entityOrProperty, alias, conditionType, condition);
}
innerJoin(property: string, alias: string, conditionType?: "on"|"with", condition?: string): this;
innerJoin(entity: Function, alias: string, conditionType?: "on"|"with", condition?: string): this;
innerJoin(entityOrProperty: Function|string, alias: string, conditionType?: "on"|"with", condition?: string): this {
innerJoin(entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition: string = ""): this {
return this.join("inner", entityOrProperty, alias, conditionType, condition);
}
leftJoinAndSelect(property: string, alias: string, conditionType?: "on"|"with", condition?: string): this;
leftJoinAndSelect(entity: Function, alias: string, conditionType?: "on"|"with", condition?: string): this;
leftJoinAndSelect(entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition?: string): this {
leftJoinAndSelect(entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition: string = ""): this {
this.addSelect(alias);
return this.join("left", entityOrProperty, alias, conditionType, condition);
}
leftJoin(property: string, alias: string, conditionType?: "on"|"with", condition?: string): this;
leftJoin(entity: Function, alias: string, conditionType?: "on"|"with", condition?: string): this;
leftJoin(entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition?: string): this {
leftJoin(entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition: string = ""): this {
return this.join("left", entityOrProperty, alias, conditionType, condition);
}
join(joinType: "inner"|"left", property: string, alias: string, conditionType?: "on"|"with", condition?: string): this;
join(joinType: "inner"|"left", entity: Function, alias: string, conditionType?: "on"|"with", condition?: string): this;
join(joinType: "inner"|"left", entityOrProperty: Function|string, alias: string, conditionType: "on"|"with", condition: string): this;
join(joinType: "inner"|"left", entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition?: string): this {
join(joinType: "inner"|"left", entityOrProperty: Function|string, alias: string, conditionType: "on"|"with" = "on", condition: string = ""): this {
const aliasObj = new Alias(alias);
this._aliasMap.addAlias(aliasObj);

View File

@ -20,7 +20,7 @@ export class PlainObjectToDatabaseEntityTransformer<Entity> {
// if object does not have id then nothing to load really
if (!metadata.hasPrimaryKey || !object[metadata.primaryColumn.name])
return null;
return Promise.reject<Entity>("Given object does not have a primary column, cannot transform it to database entity.");
const alias = queryBuilder.aliasMap.mainAlias.name;
const needToLoad = this.buildLoadMap(object, metadata, true);

View File

@ -112,7 +112,15 @@ export class EntityManager {
* Finds entities that match given conditions.
*/
find<Entity>(entityClass: ConstructorFunction<Entity>|Function, conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Promise<Entity[]> {
return this.getRepository(entityClass).find(conditionsOrFindOptions, options);
if (conditionsOrFindOptions && options) {
return this.getRepository(entityClass).find(conditionsOrFindOptions, options);
} else if (conditionsOrFindOptions) {
return this.getRepository(entityClass).find(conditionsOrFindOptions);
} else {
return this.getRepository(entityClass).find();
}
}
/**
@ -139,7 +147,15 @@ export class EntityManager {
* Finds entities that match given conditions.
*/
findAndCount<Entity>(entityClass: ConstructorFunction<Entity>|Function, conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Promise<[Entity[], number]> {
return this.getRepository(entityClass).findAndCount(conditionsOrFindOptions, options);
if (conditionsOrFindOptions && options) {
return this.getRepository(entityClass).findAndCount(conditionsOrFindOptions, options);
} else if (conditionsOrFindOptions) {
return this.getRepository(entityClass).findAndCount(conditionsOrFindOptions);
} else {
return this.getRepository(entityClass).findAndCount();
}
}
/**
@ -166,7 +182,15 @@ export class EntityManager {
* Finds first entity that matches given conditions.
*/
findOne<Entity>(entityClass: ConstructorFunction<Entity>|Function, conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Promise<Entity> {
return this.getRepository(entityClass).findOne(conditionsOrFindOptions, options);
if (conditionsOrFindOptions && options) {
return this.getRepository(entityClass).findOne(conditionsOrFindOptions, options);
} else if (conditionsOrFindOptions) {
return this.getRepository(entityClass).findOne(conditionsOrFindOptions);
} else {
return this.getRepository(entityClass).findOne();
}
}
/**

View File

@ -1,4 +1,5 @@
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {ConnectionOptions} from "../connection/ConnectionOptions";
/**
* Options to be passed to find methods.
@ -207,16 +208,28 @@ export class FindOptionsUtils {
options.groupBy.forEach(groupBy => qb.addGroupBy(groupBy));
if (options.leftJoin)
Object.keys(options.leftJoin).forEach(key => qb.leftJoin(options.leftJoin[key], key));
Object.keys(options.leftJoin).forEach(key => {
if (options.leftJoin) // this check because of tsc bug
qb.leftJoin(options.leftJoin[key], key);
});
if (options.innerJoin)
Object.keys(options.innerJoin).forEach(key => qb.innerJoin(options.innerJoin[key], key));
Object.keys(options.innerJoin).forEach(key => {
if (options.innerJoin) // this check because of tsc bug
qb.innerJoin(options.innerJoin[key], key);
});
if (options.leftJoinAndSelect)
Object.keys(options.leftJoinAndSelect).forEach(key => qb.leftJoinAndSelect(options.leftJoinAndSelect[key], key));
Object.keys(options.leftJoinAndSelect).forEach(key => {
if (options.leftJoinAndSelect) // this check because of tsc bug
qb.leftJoinAndSelect(options.leftJoinAndSelect[key], key);
});
if (options.innerJoinAndSelect)
Object.keys(options.innerJoinAndSelect).forEach(key => qb.innerJoinAndSelect(options.innerJoinAndSelect[key], key));
Object.keys(options.innerJoinAndSelect).forEach(key => {
if (options.innerJoinAndSelect) // this check because of tsc bug
qb.innerJoinAndSelect(options.innerJoinAndSelect[key], key);
});
if (options.parameters)
qb.addParameters(options.parameters);

View File

@ -92,11 +92,12 @@ export class Repository<Entity> {
const persister = new PersistOperationExecutor(this.connection);
const builder = new EntityPersistOperationBuilder(this.connection);
const allPersistedEntities = this.extractObjectsById(entity, this.metadata);
const promise = !this.hasId(entity) ? Promise.resolve(null) : this.initialize(entity);
const promise: Promise<Entity> = !this.hasId(entity) ? Promise.resolve<Entity|null>(null) : this.initialize(entity);
return promise
.then(dbEntity => {
loadedDbEntity = dbEntity;
return this.findNotLoadedIds(this.extractObjectsById(dbEntity, this.metadata), allPersistedEntities);
const entityWithIds = dbEntity ? this.extractObjectsById(dbEntity, this.metadata) : [];
return this.findNotLoadedIds(entityWithIds, allPersistedEntities);
}) // need to find db entities that were not loaded by initialize method
.then(allDbEntities => {
const persistOperation = builder.buildFullPersistment(this.metadata, loadedDbEntity, entity, allDbEntities, allPersistedEntities);
@ -238,15 +239,10 @@ export class Repository<Entity> {
// -------------------------------------------------------------------------
private createFindQueryBuilder(conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions) {
let findOptions: FindOptions, conditions: Object;
if (FindOptionsUtils.isFindOptions(conditionsOrFindOptions)) {
findOptions = conditionsOrFindOptions;
} else {
findOptions = options;
conditions = conditionsOrFindOptions;
}
const findOptions = FindOptionsUtils.isFindOptions(conditionsOrFindOptions) ? conditionsOrFindOptions : <FindOptions> options;
const conditions = FindOptionsUtils.isFindOptions(conditionsOrFindOptions) ? undefined : conditionsOrFindOptions;
const alias = findOptions ? options.alias : this.metadata.table.name;
const alias = findOptions ? findOptions.alias : this.metadata.table.name;
const qb = this.createQueryBuilder(alias);
if (findOptions) {
FindOptionsUtils.applyOptionsToQueryBuilder(qb, findOptions);
@ -294,9 +290,6 @@ export class Repository<Entity> {
* Extracts unique objects from given entity and all its downside relations.
*/
private extractObjectsById(entity: any, metadata: EntityMetadata, entityWithIds: EntityWithId[] = []): EntityWithId[] {
if (!entity)
return entityWithIds;
metadata.relations
.filter(relation => !!entity[relation.propertyName])
.forEach(relation => {

View File

@ -111,6 +111,8 @@ export class SchemaCreator {
return this.schemaBuilder.checkIfTableExist(table.name).then(exist => {
if (!exist)
return this.schemaBuilder.createTableQuery(table, columns);
return undefined;
});
}
@ -213,6 +215,8 @@ export class SchemaCreator {
return this.schemaBuilder.getPrimaryConstraintName(table.name).then(constraintName => {
if (constraintName)
return this.schemaBuilder.dropIndex(table.name, constraintName);
return undefined;
});
}

View File

@ -1,3 +1,7 @@
/*!
*/
import {ConnectionOptions} from "./connection/ConnectionOptions";
import {ConnectionManager} from "./connection/ConnectionManager";
import {Connection} from "./connection/Connection";

View File

@ -77,7 +77,7 @@ describe("insertion", function() {
});
it("should have inserted post in the database", function() {
postRepository.findById(savedPost.id).then(p => console.log(p));
// postRepository.findById(savedPost.id).then(p => console.log(p));
return postRepository.findById(savedPost.id).should.eventually.eql({
id: savedPost.id,
text: "Hello post",

View File

@ -327,7 +327,7 @@ describe("one-to-one", function() {
});
it("should ignore updates in the model and do not update the db when entity is updated", function () {
newPost.details = null;
delete newPost.details;
return postRepository.persist(newPost).then(updatedPost => {
return postRepository
.createQueryBuilder("post")
@ -421,7 +421,7 @@ describe("one-to-one", function() {
.getSingleResult();
}).then(loadedPost => {
loadedPost.metadata = null;
loadedPost.metadata = undefined;
return postRepository.persist(loadedPost);
}).then(() => {
@ -433,7 +433,7 @@ describe("one-to-one", function() {
.getSingleResult();
}).then(reloadedPost => {
expect(reloadedPost.metadata).to.be.empty;
expect(reloadedPost.metadata).to.not.exist;
});
});

View File

@ -326,7 +326,7 @@ describe("many-to-one", function() {
});
it("should ignore updates in the model and do not update the db when entity is updated", function () {
newPost.details = null;
delete newPost.details;
return postRepository.persist(newPost).then(updatedPost => {
return postRepository
.createQueryBuilder("post")
@ -420,7 +420,7 @@ describe("many-to-one", function() {
.getSingleResult();
}).then(loadedPost => {
loadedPost.metadata = null;
loadedPost.metadata = undefined;
return postRepository.persist(loadedPost);
}).then(() => {

View File

@ -346,7 +346,7 @@ describe("many-to-many", function() {
});
it("should remove relation however should not remove details itself", function () {
newPost.details = null;
newPost.details = [];
return postRepository.persist(newPost).then(updatedPost => {
return postRepository
.createQueryBuilder("post")
@ -450,7 +450,7 @@ describe("many-to-many", function() {
.getSingleResult();
}).then(loadedPost => {
loadedPost.metadatas = null;
loadedPost.metadatas = [];
return postRepository.persist(loadedPost);
}).then(() => {

View File

@ -9,12 +9,16 @@
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitAny": true,
"declaration": true
"declaration": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"stripInternal": true,
"pretty": true,
"strictNullChecks": true
},
"exclude": [
"build",
"node_modules",
"odmhelpers",
"typings/browser.d.ts",
"typings/browser"
]