UNPKG

ionic-orm-x

Version:

Data-mapper ORM for Ionic WebSQL and SQLite

529 lines (527 loc) 20 kB
import { RelationTypes } from "./types/RelationTypes"; // todo: IDEA. store all entity metadata in the EntityMetadata too? (this will open more features for metadata objects + no need to access connection in lot of places) /** * Contains all entity metadata. */ export class EntityMetadata { // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- constructor(args, lazyRelationsWrapper) { this.lazyRelationsWrapper = lazyRelationsWrapper; /** * Entity's foreign key metadatas. */ this.foreignKeys = []; this.target = args.target; this.tablesPrefix = args.tablesPrefix; this.namingStrategy = args.namingStrategy; this.table = args.tableMetadata; this._columns = args.columnMetadatas || []; this.relations = args.relationMetadatas || []; this.indices = args.indexMetadatas || []; this.foreignKeys = args.foreignKeyMetadatas || []; this.embeddeds = args.embeddedMetadatas || []; this.discriminatorValue = args.discriminatorValue; this.inheritanceType = args.inheritanceType; this.table.entityMetadata = this; this._columns.forEach(column => column.entityMetadata = this); this.relations.forEach(relation => relation.entityMetadata = this); this.foreignKeys.forEach(foreignKey => foreignKey.entityMetadata = this); this.indices.forEach(index => index.entityMetadata = this); this.embeddeds.forEach(embedded => { embedded.entityMetadata = this; embedded.columns.forEach(column => column.entityMetadata = this); }); } // ------------------------------------------------------------------------- // 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() { if (!this.table) throw new Error("No table target set to the entity metadata."); return this.targetName ? this.targetName : this.table.name; } /** * Columns of the entity, including columns that are coming from the embeddeds of this entity. */ get columns() { let allColumns = [].concat(this._columns); this.embeddeds.forEach(embedded => { allColumns = allColumns.concat(embedded.columns); }); return allColumns; } /** * All columns of the entity, including columns that are coming from the embeddeds of this entity, * and including columns from the parent entities. */ get allColumns() { let columns = this.columns; if (this.parentEntityMetadata) columns = columns.concat(this.parentEntityMetadata.columns); return columns; } /** * All relations of the entity, including relations from the parent entities. */ get allRelations() { let relations = this.relations; if (this.parentEntityMetadata) relations = relations.concat(this.parentEntityMetadata.relations); return relations; } /** * Gets the name of the target. */ get targetName() { if (typeof this.target === "string") return this.target; if (this.target instanceof Function) return this.target.name; return ""; } /** * Checks if entity's table has multiple primary columns. */ get hasMultiplePrimaryKeys() { return this.primaryColumns.length > 1; } /** * Gets the primary column. * * @deprecated */ get primaryColumn() { const primaryKey = this.primaryColumns[0]; if (!primaryKey) throw new Error(`Primary key is not set for the ${this.name} entity.`); return primaryKey; } /** * Checks if table has generated column. */ get hasGeneratedColumn() { return !!this.generatedColumnIfExist; } /** * Gets the column with generated flag. */ get generatedColumn() { const generatedColumn = this.generatedColumnIfExist; if (!generatedColumn) throw new Error(`Generated column was not found`); return generatedColumn; } /** * Gets the generated column if it exists, or returns undefined if it does not. */ get generatedColumnIfExist() { return this._columns.find(column => column.isGenerated); } /** * Gets first primary column. In the case if table contains multiple primary columns it * throws error. */ get firstPrimaryColumn() { if (this.hasMultiplePrimaryKeys) throw new Error(`Entity ${this.name} has multiple primary keys. This operation is not supported on entities with multiple primary keys`); return this.primaryColumns[0]; } /** * Checks if entity has any primary columns. get hasPrimaryColumns(): ColumnMetadata[] { }*/ /** * Gets the primary columns. */ get primaryColumns() { // const originalPrimaryColumns = this._columns.filter(column => column.isPrimary); // const parentEntityPrimaryColumns = this.hasParentIdColumn ? [this.parentIdColumn] : []; // return originalPrimaryColumns.concat(parentEntityPrimaryColumns); return this._columns.filter(column => column.isPrimary); // const originalPrimaryColumns = this._columns.filter(column => column.isPrimary); // const parentEntityPrimaryColumns = this.parentEntityMetadata ? this.parentEntityMetadata.primaryColumns : []; // return originalPrimaryColumns.concat(parentEntityPrimaryColumns); } get primaryColumnsWithParentIdColumns() { return this.primaryColumns.concat(this.parentIdColumns); } get primaryColumnsWithParentPrimaryColumns() { return this.primaryColumns.concat(this.parentPrimaryColumns); } /** * Gets the primary columns of the parent entity metadata. * If parent entity metadata does not exist then it simply returns empty array. */ get parentPrimaryColumns() { if (this.parentEntityMetadata) return this.parentEntityMetadata.primaryColumns; return []; } /** * Gets only primary columns owned by this entity. */ get ownPimaryColumns() { return this._columns.filter(column => column.isPrimary); } /** * Checks if entity has a create date column. */ get hasCreateDateColumn() { return !!this._columns.find(column => column.mode === "createDate"); } /** * Gets entity column which contains a create date value. */ get createDateColumn() { const column = this._columns.find(column => column.mode === "createDate"); if (!column) throw new Error(`CreateDateColumn was not found in entity ${this.name}`); return column; } /** * Checks if entity has an update date column. */ get hasUpdateDateColumn() { return !!this._columns.find(column => column.mode === "updateDate"); } /** * Gets entity column which contains an update date value. */ get updateDateColumn() { const column = this._columns.find(column => column.mode === "updateDate"); if (!column) throw new Error(`UpdateDateColumn was not found in entity ${this.name}`); return column; } /** * Checks if entity has a version column. */ get hasVersionColumn() { return !!this._columns.find(column => column.mode === "version"); } /** * Gets entity column which contains an entity version. */ get versionColumn() { const column = this._columns.find(column => column.mode === "version"); if (!column) throw new Error(`VersionColumn was not found in entity ${this.name}`); return column; } /** * Checks if entity has a discriminator column. */ get hasDiscriminatorColumn() { return !!this._columns.find(column => column.mode === "discriminator"); } /** * Gets the discriminator column used to store entity identificator in single-table inheritance tables. */ get discriminatorColumn() { const column = this._columns.find(column => column.mode === "discriminator"); if (!column) throw new Error(`DiscriminatorColumn was not found in entity ${this.name}`); return column; } /** * Checks if entity has a tree level column. */ get hasTreeLevelColumn() { return !!this._columns.find(column => column.mode === "treeLevel"); } get treeLevelColumn() { const column = this._columns.find(column => column.mode === "treeLevel"); if (!column) throw new Error(`TreeLevelColumn was not found in entity ${this.name}`); return column; } /** * Checks if entity has a tree level column. */ get hasParentIdColumn() { return !!this._columns.find(column => column.mode === "parentId"); } get parentIdColumn() { const column = this._columns.find(column => column.mode === "parentId"); if (!column) throw new Error(`Parent id column was not found in entity ${this.name}`); return column; } get parentIdColumns() { return this._columns.filter(column => column.mode === "parentId"); } /** * Gets single (values of which does not contain arrays) relations. */ get singleValueRelations() { return this.relations.filter(relation => { return relation.relationType === RelationTypes.ONE_TO_ONE || relation.relationType === RelationTypes.ONE_TO_MANY; }); } /** * Gets single (values of which does not contain arrays) relations. */ get multiValueRelations() { return this.relations.filter(relation => { return relation.relationType === RelationTypes.ONE_TO_ONE || relation.relationType === RelationTypes.ONE_TO_MANY; }); } /** * Gets only one-to-one relations of the entity. */ get oneToOneRelations() { return this.relations.filter(relation => relation.relationType === RelationTypes.ONE_TO_ONE); } /** * Gets only owner one-to-one relations of the entity. */ get ownerOneToOneRelations() { return this.relations.filter(relation => relation.relationType === RelationTypes.ONE_TO_ONE && relation.isOwning); } /** * Gets only one-to-many relations of the entity. */ get oneToManyRelations() { return this.relations.filter(relation => relation.relationType === RelationTypes.ONE_TO_MANY); } /** * Gets only many-to-one relations of the entity. */ get manyToOneRelations() { return this.relations.filter(relation => relation.relationType === RelationTypes.MANY_TO_ONE); } /** * Gets only many-to-many relations of the entity. */ get manyToManyRelations() { return this.relations.filter(relation => relation.relationType === RelationTypes.MANY_TO_MANY); } /** * Gets only owner many-to-many relations of the entity. */ get ownerManyToManyRelations() { 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); } /** * 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) throw new Error(`TreeParent relation was not found in entity ${this.name}`); 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) throw new Error(`TreeParent relation was not found in entity ${this.name}`); return relation; } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Creates a new entity. */ create() { // if target is set to a function (e.g. class) that can be created then create it if (this.target instanceof Function) return new this.target(); // otherwise simply return a new empty object const newObject = {}; this.relations .filter(relation => relation.isLazy) .forEach(relation => this.lazyRelationsWrapper.wrap(newObject, relation)); return newObject; } /** * Creates an object - map of columns and relations of the entity. */ createPropertiesMap() { const entity = {}; 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) { return typeof nameOrFn === "string" ? nameOrFn : nameOrFn(this.createPropertiesMap()); } getEntityIdMap(entity) { if (!entity) return undefined; const map = {}; if (this.parentEntityMetadata) { this.primaryColumnsWithParentIdColumns.forEach(column => { map[column.propertyName] = entity[column.propertyName]; }); } else { this.primaryColumns.forEach(column => { map[column.propertyName] = entity[column.propertyName]; }); } const hasAllIds = this.primaryColumns.every(primaryColumn => { return map[primaryColumn.propertyName] !== undefined && map[primaryColumn.propertyName] !== null; }); return hasAllIds ? map : undefined; } /** * Same as `getEntityIdMap` but the key of the map will be the column names instead of the property names. */ getEntityIdColumnMap(entity) { return this.transformIdMapToColumnNames(this.getEntityIdMap(entity)); } transformIdMapToColumnNames(idMap) { if (!idMap) { return idMap; } const map = {}; Object.keys(idMap).forEach(propertyName => { const column = this.getColumnByPropertyName(propertyName); if (column) { map[column.name] = idMap[propertyName]; } }); return map; } getColumnByPropertyName(propertyName) { return this._columns.find(column => column.propertyName === propertyName); } /** * Checks if column with the given property name exist. */ hasColumnWithPropertyName(propertyName) { return !!this._columns.find(column => column.propertyName === propertyName); } /** * Checks if column with the given database name exist. */ hasColumnWithDbName(name) { return !!this._columns.find(column => column.name === name); } /** * Checks if relation with the given property name exist. */ hasRelationWithPropertyName(propertyName) { return !!this.relations.find(relation => relation.propertyName === propertyName); } /** * Finds relation with the given property name. */ findRelationWithPropertyName(propertyName) { const relation = this.relations.find(relation => relation.propertyName === propertyName); if (!relation) throw new Error(`Relation with property name ${propertyName} in ${this.name} entity was not found.`); return relation; } /** * Checks if relation with the given name exist. */ hasRelationWithDbName(dbName) { return !!this.relationsWithJoinColumns.find(relation => relation.name === dbName); } /** * Finds relation with the given name. */ findRelationWithDbName(name) { const relation = this.relationsWithJoinColumns.find(relation => relation.name === name); if (!relation) throw new Error(`Relation with name ${name} in ${this.name} entity was not found.`); return relation; } addColumn(column) { this._columns.push(column); column.entityMetadata = this; } extractNonEmptyColumns(object) { return this.columns.filter(column => !!object[column.propertyName]); } extractNonEmptySingleValueRelations(object) { return this.relations.filter(relation => { return (relation.relationType === RelationTypes.ONE_TO_ONE || relation.relationType === RelationTypes.MANY_TO_ONE) && !!object[relation.propertyName]; }); } extractNonEmptyMultiValueRelations(object) { return this.relations.filter(relation => { return (relation.relationType === RelationTypes.MANY_TO_MANY || relation.relationType === RelationTypes.ONE_TO_MANY) && !!object[relation.propertyName]; }); } extractExistSingleValueRelations(object) { return this.relations.filter(relation => { return (relation.relationType === RelationTypes.ONE_TO_ONE || relation.relationType === RelationTypes.MANY_TO_ONE) && object.hasOwnProperty(relation.propertyName); }); } extractExistMultiValueRelations(object) { return this.relations.filter(relation => { return (relation.relationType === RelationTypes.MANY_TO_MANY || relation.relationType === RelationTypes.ONE_TO_MANY) && object.hasOwnProperty(relation.propertyName); }); } checkIfObjectContainsAllPrimaryKeys(object) { return this.primaryColumns.every(primaryColumn => { return object.hasOwnProperty(primaryColumn.propertyName); }); } compareEntities(firstEntity, secondEntity) { const firstEntityIds = this.getEntityIdMap(firstEntity); const secondEntityIds = this.getEntityIdMap(secondEntity); return this.compareIds(firstEntityIds, secondEntityIds); } compareIds(firstIds, secondIds) { if (!firstIds || !secondIds) return false; return Object.keys(firstIds).every(key => { return firstIds[key] === secondIds[key]; }); } /** * Iterates throw entity and finds and extracts all values from relations in the entity. * If relation value is an array its being flattened. */ extractRelationValuesFromEntity(entity, relations) { const relationsAndValues = []; relations.forEach(relation => { const value = relation.getEntityValue(entity); if (value instanceof Array) { value.forEach(subValue => relationsAndValues.push([relation, subValue])); } else if (value) { relationsAndValues.push([relation, value]); } }); return relationsAndValues; } } //# sourceMappingURL=EntityMetadata.js.map