mikro-orm
Advanced tools
Comparing version 0.7.4 to 0.8.0
@@ -17,3 +17,3 @@ "use strict"; | ||
if ([ReferenceType.ONE_TO_MANY, ReferenceType.MANY_TO_MANY].includes(props[prop].reference)) { | ||
this[prop] = new Collection_1.Collection(props[prop], this, []); | ||
this[prop] = new Collection_1.Collection(this, props[prop], []); | ||
} | ||
@@ -20,0 +20,0 @@ }); |
import { ObjectID } from 'bson'; | ||
import { BaseEntity, EntityProperty } from './BaseEntity'; | ||
export declare class Collection<T extends BaseEntity> { | ||
readonly owner: BaseEntity; | ||
private readonly property; | ||
private readonly owner; | ||
private initialized; | ||
private dirty; | ||
private readonly items; | ||
constructor(property: EntityProperty, owner: BaseEntity, items?: T[]); | ||
isInitialized(): boolean; | ||
constructor(owner: BaseEntity, property: EntityProperty, items?: T[]); | ||
isInitialized(fully?: boolean): boolean; | ||
isDirty(): boolean; | ||
@@ -16,3 +16,3 @@ init(): Promise<Collection<T>>; | ||
add(...items: T[]): void; | ||
set(items: T[]): void; | ||
set(items: T[], initialize?: boolean): void; | ||
remove(...items: T[]): void; | ||
@@ -25,2 +25,3 @@ removeAll(): void; | ||
private handleInverseSide; | ||
private createCondition; | ||
} |
@@ -6,5 +6,5 @@ "use strict"; | ||
class Collection { | ||
constructor(property, owner, items = null) { | ||
constructor(owner, property, items = null) { | ||
this.owner = owner; | ||
this.property = property; | ||
this.owner = owner; | ||
this.initialized = false; | ||
@@ -18,3 +18,6 @@ this.dirty = false; | ||
} | ||
isInitialized() { | ||
isInitialized(fully = false) { | ||
if (fully) { | ||
return this.initialized && this.items.every(i => i.isInitialized()); | ||
} | ||
return this.initialized; | ||
@@ -26,16 +29,10 @@ } | ||
async init() { | ||
const cond = {}; | ||
const order = []; | ||
if (this.property.reference === BaseEntity_1.ReferenceType.ONE_TO_MANY) { | ||
cond[this.property.fk] = this.owner._id; | ||
// do not make db call if we know we will get no results | ||
if (this.property.reference === BaseEntity_1.ReferenceType.MANY_TO_MANY && this.property.owner && this.items.length === 0) { | ||
this.initialized = true; | ||
this.dirty = false; | ||
return this; | ||
} | ||
else if (this.property.reference === BaseEntity_1.ReferenceType.MANY_TO_MANY) { | ||
if (this.property.owner) { | ||
const order = this.items.map(item => item._id); | ||
cond._id = { $in: order }; | ||
} | ||
else { | ||
cond[this.property.mappedBy] = this.owner._id; | ||
} | ||
} | ||
const cond = this.createCondition(); | ||
const order = this.items.map(item => item._id); | ||
this.items.length = 0; | ||
@@ -72,5 +69,11 @@ const em = MikroORM_1.getEntityManager(); | ||
} | ||
set(items) { | ||
set(items, initialize = false) { | ||
if (initialize) { | ||
this.initialized = true; | ||
} | ||
this.removeAll(); | ||
this.add(...items); | ||
if (initialize) { | ||
this.dirty = false; | ||
} | ||
} | ||
@@ -119,3 +122,18 @@ remove(...items) { | ||
} | ||
createCondition() { | ||
const cond = {}; | ||
if (this.property.reference === BaseEntity_1.ReferenceType.ONE_TO_MANY) { | ||
cond[this.property.fk] = this.owner._id; | ||
} | ||
else if (this.property.reference === BaseEntity_1.ReferenceType.MANY_TO_MANY) { | ||
if (this.property.owner) { | ||
cond._id = { $in: this.items.map(item => item._id) }; | ||
} | ||
else { | ||
cond[this.property.mappedBy] = this.owner._id; | ||
} | ||
} | ||
return cond; | ||
} | ||
} | ||
exports.Collection = Collection; |
@@ -22,3 +22,2 @@ "use strict"; | ||
const meta = this.metadata[entityName]; | ||
const exclude = []; | ||
let entity; | ||
@@ -34,4 +33,5 @@ data._id = new bson_1.ObjectID(data.id || data._id); | ||
entity = Object.create(Entity.prototype); | ||
this.em.identityMap[`${entityName}-${data._id}`] = entity; | ||
} | ||
this.initEntity(entity, meta.properties, data, exclude); | ||
this.initEntity(entity, meta.properties, data); | ||
if (initialized) { | ||
@@ -51,3 +51,3 @@ delete entity['_initialized']; | ||
} | ||
initEntity(entity, properties, data, exclude = []) { | ||
initEntity(entity, properties, data) { | ||
// process base entity properties first | ||
@@ -62,7 +62,4 @@ ['_id', 'createdAt', 'updatedAt'].forEach(k => { | ||
const prop = properties[p]; | ||
if (exclude.includes(p)) { | ||
return; | ||
} | ||
if (prop.reference === BaseEntity_1.ReferenceType.ONE_TO_MANY && !data[p]) { | ||
return entity[p] = new Collection_1.Collection(prop, entity); | ||
return entity[p] = new Collection_1.Collection(entity, prop); | ||
} | ||
@@ -72,6 +69,6 @@ if (prop.reference === BaseEntity_1.ReferenceType.MANY_TO_MANY) { | ||
const items = data[p].map((id) => this.createReference(prop.type, id.toHexString())); | ||
return entity[p] = new Collection_1.Collection(prop, entity, items); | ||
return entity[p] = new Collection_1.Collection(entity, prop, items); | ||
} | ||
else if (!entity[p]) { | ||
return entity[p] = new Collection_1.Collection(prop, entity, prop.owner ? [] : null); | ||
return entity[p] = new Collection_1.Collection(entity, prop, prop.owner ? [] : null); | ||
} | ||
@@ -78,0 +75,0 @@ } |
@@ -24,2 +24,5 @@ import { Collection as MongoCollection, Db, FilterQuery } from 'mongodb'; | ||
merge<T extends BaseEntity>(entityName: string, data: any): T; | ||
/** | ||
* gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded | ||
*/ | ||
getReference<T extends BaseEntity>(entityName: string, id: string): T; | ||
@@ -40,5 +43,9 @@ remove(entityName: string, where: BaseEntity | any): Promise<number>; | ||
canPopulate(entityName: string, property: string): boolean; | ||
private processPopulate; | ||
private populateOne; | ||
/** | ||
* preload everything in one call (this will update already existing references in IM) | ||
*/ | ||
private populateMany; | ||
private buildQuery; | ||
private runHooks; | ||
} |
@@ -39,20 +39,8 @@ "use strict"; | ||
async find(entityName, where = {}, populate = [], orderBy = {}, limit = null, offset = null) { | ||
Utils_1.Utils.renameKey(where, 'id', '_id'); | ||
let query = `db.getCollection("${this.metadata[entityName].collection}").find(${JSON.stringify(where)})`; | ||
where = Utils_1.Utils.convertObjectIds(where); | ||
const resultSet = this.getCollection(entityName).find(where); | ||
if (Object.keys(orderBy).length > 0) { | ||
query += `.sort(${JSON.stringify(orderBy)})`; | ||
resultSet.sort(orderBy); | ||
} | ||
if (limit !== null) { | ||
query += `.limit(${limit})`; | ||
resultSet.limit(limit); | ||
} | ||
if (offset !== null) { | ||
query += `.skip(${offset})`; | ||
resultSet.skip(offset); | ||
} | ||
const { query, resultSet } = this.buildQuery(entityName, where, orderBy, limit, offset); | ||
this.options.logger(`[query-logger] ${query}.toArray();`); | ||
const results = await resultSet.toArray(); | ||
if (results.length === 0) { | ||
return []; | ||
} | ||
const ret = []; | ||
@@ -63,3 +51,5 @@ for (const data of results) { | ||
} | ||
await this.processPopulate(ret, populate); | ||
for (const field of populate) { | ||
await this.populateMany(entityName, ret, field); | ||
} | ||
return ret; | ||
@@ -69,3 +59,3 @@ } | ||
if (!where || (typeof where === 'object' && Object.keys(where).length === 0)) { | ||
return null; | ||
throw new Error(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`); | ||
} | ||
@@ -76,2 +66,3 @@ if (where instanceof mongodb_1.ObjectID) { | ||
if (Utils_1.Utils.isString(where) && this.identityMap[`${entityName}-${where}`] && this.identityMap[`${entityName}-${where}`].isInitialized()) { | ||
await this.populateOne(entityName, this.identityMap[`${entityName}-${where}`], populate); | ||
return this.identityMap[`${entityName}-${where}`]; | ||
@@ -91,3 +82,3 @@ } | ||
const entity = this.merge(entityName, data); | ||
await this.processPopulate(entity, populate); | ||
await this.populateOne(entityName, entity, populate); | ||
return entity; | ||
@@ -102,6 +93,12 @@ } | ||
entity.assign(data); | ||
this.unitOfWork.addToIdentityMap(entity); | ||
} | ||
this.addToIdentityMap(entity); | ||
else { | ||
this.addToIdentityMap(entity); | ||
} | ||
return entity; | ||
} | ||
/** | ||
* gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded | ||
*/ | ||
getReference(entityName, id) { | ||
@@ -173,25 +170,26 @@ if (this.identityMap[`${entityName}-${id}`]) { | ||
} | ||
async processPopulate(entity, populate) { | ||
if (entity instanceof BaseEntity_1.BaseEntity) { | ||
for (const field of populate) { | ||
if (entity[field] instanceof Collection_1.Collection && !entity[field].isInitialized()) { | ||
await entity[field].init(); | ||
} | ||
if (entity[field] instanceof BaseEntity_1.BaseEntity && !entity[field].isInitialized()) { | ||
await entity[field].init(); | ||
} | ||
async populateOne(entityName, entity, populate) { | ||
for (const field of populate) { | ||
if (!this.canPopulate(entityName, field)) { | ||
throw new Error(`Entity '${entityName}' does not have property '${field}'`); | ||
} | ||
return; | ||
if (entity[field] instanceof Collection_1.Collection && !entity[field].isInitialized(true)) { | ||
await entity[field].init(); | ||
} | ||
if (entity[field] instanceof BaseEntity_1.BaseEntity && !entity[field].isInitialized()) { | ||
await entity[field].init(); | ||
} | ||
} | ||
if (entity.length === 0) { | ||
return; | ||
} | ||
/** | ||
* preload everything in one call (this will update already existing references in IM) | ||
*/ | ||
async populateMany(entityName, entities, field) { | ||
if (!this.canPopulate(entityName, field)) { | ||
throw new Error(`Entity '${entityName}' does not have property '${field}'`); | ||
} | ||
for (const field of populate) { | ||
await this.populateMany(entity, field); | ||
} | ||
} | ||
async populateMany(entities, field) { | ||
if (entities[0][field] instanceof Collection_1.Collection) { | ||
const meta = this.metadata[entityName].properties[field]; | ||
if (meta.reference === BaseEntity_1.ReferenceType.MANY_TO_MANY && !meta.owner) { | ||
for (const entity of entities) { | ||
if (entity[field] instanceof Collection_1.Collection && !entity[field].isInitialized()) { | ||
if (!entity[field].isInitialized()) { | ||
await entity[field].init(); | ||
@@ -202,11 +200,48 @@ } | ||
} | ||
const children = entities.filter(e => e[field] instanceof BaseEntity_1.BaseEntity && !e[field].isInitialized()); | ||
const children = []; | ||
let fk = '_id'; | ||
if (meta.reference === BaseEntity_1.ReferenceType.ONE_TO_MANY) { | ||
const filtered = entities.filter(e => e[field] instanceof Collection_1.Collection); | ||
children.push(...filtered.map(e => e[field].owner)); | ||
fk = meta.fk; | ||
} | ||
else if (meta.reference === BaseEntity_1.ReferenceType.MANY_TO_MANY) { | ||
const filtered = entities.filter(e => e[field] instanceof Collection_1.Collection && !e[field].isInitialized(true)); | ||
children.push(...filtered.reduce((a, b) => [...a, ...b[field].getItems()], [])); | ||
} | ||
else { | ||
children.push(...entities.filter(e => e[field] instanceof BaseEntity_1.BaseEntity && !e[field].isInitialized()).map(e => e[field])); | ||
} | ||
if (children.length === 0) { | ||
return; | ||
} | ||
// preload everything in one call (this will update already existing references in IM) | ||
const ids = Utils_1.Utils.unique(children.map(e => e[field].id)); | ||
const meta = this.metadata[entities[0].constructor.name].properties[field]; | ||
await this.find(meta.type, { _id: { $in: ids } }); | ||
const ids = Utils_1.Utils.unique(children.map(e => e.id)); | ||
const data = await this.find(meta.type, { [fk]: { $in: ids } }); | ||
// initialize collections for one to many | ||
if (meta.reference === BaseEntity_1.ReferenceType.ONE_TO_MANY) { | ||
for (const entity of entities) { | ||
const items = data.filter(child => child[fk] === entity); | ||
entity[field].set(items, true); | ||
} | ||
} | ||
} | ||
buildQuery(entityName, where, orderBy, limit, offset) { | ||
Utils_1.Utils.renameKey(where, 'id', '_id'); | ||
let query = `db.getCollection("${this.metadata[entityName].collection}").find(${JSON.stringify(where)})`; | ||
where = Utils_1.Utils.convertObjectIds(where); | ||
const resultSet = this.getCollection(entityName).find(where); | ||
if (Object.keys(orderBy).length > 0) { | ||
query += `.sort(${JSON.stringify(orderBy)})`; | ||
resultSet.sort(orderBy); | ||
} | ||
if (limit !== null) { | ||
query += `.limit(${limit})`; | ||
resultSet.limit(limit); | ||
} | ||
if (offset !== null) { | ||
query += `.skip(${offset})`; | ||
resultSet.skip(offset); | ||
} | ||
return { query, resultSet }; | ||
} | ||
runHooks(type, entity) { | ||
@@ -213,0 +248,0 @@ const hooks = this.metadata[entity.constructor.name].hooks; |
@@ -8,2 +8,3 @@ import { FilterQuery } from 'mongodb'; | ||
constructor(em: EntityManager, entityName: string); | ||
getReference<T extends BaseEntity>(id: string): T; | ||
persist(entity: T, flush?: boolean): Promise<void>; | ||
@@ -10,0 +11,0 @@ findOne(where: FilterQuery<T> | string, populate?: string[]): Promise<T>; |
@@ -8,2 +8,5 @@ "use strict"; | ||
} | ||
getReference(id) { | ||
return this.em.getReference(this.entityName, id); | ||
} | ||
async persist(entity, flush = true) { | ||
@@ -10,0 +13,0 @@ return this.em.persist(entity, flush); |
@@ -22,3 +22,3 @@ import { getMetadataStorage, getEntityManager } from './MikroORM'; | ||
if ([ReferenceType.ONE_TO_MANY, ReferenceType.MANY_TO_MANY].includes(props[prop].reference)) { | ||
this[prop] = new Collection(props[prop], this, []); | ||
this[prop] = new Collection(this, props[prop], []); | ||
} | ||
@@ -25,0 +25,0 @@ }); |
@@ -11,4 +11,4 @@ import { ObjectID } from 'bson'; | ||
constructor(private readonly property: EntityProperty, | ||
private readonly owner: BaseEntity, | ||
constructor(public readonly owner: BaseEntity, | ||
private readonly property: EntityProperty, | ||
items: T[] = null) { | ||
@@ -21,3 +21,7 @@ if (items) { | ||
isInitialized(): boolean { | ||
isInitialized(fully = false): boolean { | ||
if (fully) { | ||
return this.initialized && this.items.every(i => i.isInitialized()); | ||
} | ||
return this.initialized; | ||
@@ -31,16 +35,13 @@ } | ||
async init(): Promise<Collection<T>> { | ||
const cond = {} as any; | ||
const order = [] as ObjectID[]; | ||
// do not make db call if we know we will get no results | ||
if (this.property.reference === ReferenceType.MANY_TO_MANY && this.property.owner && this.items.length === 0) { | ||
this.initialized = true; | ||
this.dirty = false; | ||
if (this.property.reference === ReferenceType.ONE_TO_MANY) { | ||
cond[this.property.fk] = this.owner._id; | ||
} else if (this.property.reference === ReferenceType.MANY_TO_MANY) { | ||
if (this.property.owner) { | ||
const order = this.items.map(item => item._id); | ||
cond._id = { $in: order }; | ||
} else { | ||
cond[this.property.mappedBy] = this.owner._id; | ||
} | ||
return this; | ||
} | ||
const cond = this.createCondition(); | ||
const order = this.items.map(item => item._id); | ||
this.items.length = 0; | ||
@@ -86,5 +87,13 @@ const em = getEntityManager(); | ||
set(items: T[]): void { | ||
set(items: T[], initialize = false): void { | ||
if (initialize) { | ||
this.initialized = true; | ||
} | ||
this.removeAll(); | ||
this.add(...items); | ||
if (initialize) { | ||
this.dirty = false; | ||
} | ||
} | ||
@@ -140,3 +149,3 @@ | ||
private handleInverseSide(item: T, method: string) { | ||
private handleInverseSide(item: T, method: string): void { | ||
if (this.property.owner && this.property.inversedBy && item[this.property.inversedBy].isInitialized()) { | ||
@@ -147,3 +156,19 @@ item[this.property.inversedBy][method](this.owner); | ||
private createCondition(): any { | ||
const cond: any = {}; | ||
if (this.property.reference === ReferenceType.ONE_TO_MANY) { | ||
cond[this.property.fk] = this.owner._id; | ||
} else if (this.property.reference === ReferenceType.MANY_TO_MANY) { | ||
if (this.property.owner) { | ||
cond._id = { $in: this.items.map(item => item._id) }; | ||
} else { | ||
cond[this.property.mappedBy] = this.owner._id; | ||
} | ||
} | ||
return cond; | ||
} | ||
} | ||
@@ -26,3 +26,2 @@ import { readdirSync } from 'fs'; | ||
const meta = this.metadata[entityName]; | ||
const exclude: string[] = []; | ||
let entity: T; | ||
@@ -39,5 +38,6 @@ | ||
entity = Object.create(Entity.prototype); | ||
this.em.identityMap[`${entityName}-${data._id}`] = entity; | ||
} | ||
this.initEntity(entity, meta.properties, data, exclude); | ||
this.initEntity(entity, meta.properties, data); | ||
@@ -61,3 +61,3 @@ if (initialized) { | ||
private initEntity<T extends BaseEntity>(entity: T, properties: any, data: any, exclude: string[] = []): void { | ||
private initEntity<T extends BaseEntity>(entity: T, properties: any, data: any): void { | ||
// process base entity properties first | ||
@@ -74,8 +74,4 @@ ['_id', 'createdAt', 'updatedAt'].forEach(k => { | ||
if (exclude.includes(p)) { | ||
return; | ||
} | ||
if (prop.reference === ReferenceType.ONE_TO_MANY && !data[p]) { | ||
return entity[p] = new Collection<T>(prop, entity); | ||
return entity[p] = new Collection<T>(entity, prop); | ||
} | ||
@@ -86,5 +82,5 @@ | ||
const items = data[p].map((id: ObjectID) => this.createReference(prop.type, id.toHexString())); | ||
return entity[p] = new Collection<T>(prop, entity, items); | ||
return entity[p] = new Collection<T>(entity, prop, items); | ||
} else if (!entity[p]) { | ||
return entity[p] = new Collection<T>(prop, entity, prop.owner ? [] : null); | ||
return entity[p] = new Collection<T>(entity, prop, prop.owner ? [] : null); | ||
} | ||
@@ -91,0 +87,0 @@ } |
import { Collection as MongoCollection, Db, FilterQuery, ObjectID } from 'mongodb'; | ||
import { BaseEntity, EntityMetadata } from './BaseEntity'; | ||
import { BaseEntity, EntityMetadata, ReferenceType } from './BaseEntity'; | ||
import { EntityRepository } from './EntityRepository'; | ||
@@ -43,24 +43,10 @@ import { EntityFactory } from './EntityFactory'; | ||
async find<T extends BaseEntity>(entityName: string, where = {} as FilterQuery<T>, populate: string[] = [], orderBy: { [k: string]: 1 | -1 } = {}, limit: number = null, offset: number = null): Promise<T[]> { | ||
Utils.renameKey(where, 'id', '_id'); | ||
let query = `db.getCollection("${this.metadata[entityName].collection}").find(${JSON.stringify(where)})`; | ||
where = Utils.convertObjectIds(where); | ||
const resultSet = this.getCollection(entityName).find(where); | ||
const { query, resultSet } = this.buildQuery<T>(entityName, where, orderBy, limit, offset); | ||
this.options.logger(`[query-logger] ${query}.toArray();`); | ||
const results = await resultSet.toArray(); | ||
if (Object.keys(orderBy).length > 0) { | ||
query += `.sort(${JSON.stringify(orderBy)})`; | ||
resultSet.sort(orderBy); | ||
if (results.length === 0) { | ||
return []; | ||
} | ||
if (limit !== null) { | ||
query += `.limit(${limit})`; | ||
resultSet.limit(limit); | ||
} | ||
if (offset !== null) { | ||
query += `.skip(${offset})`; | ||
resultSet.skip(offset); | ||
} | ||
this.options.logger(`[query-logger] ${query}.toArray();`); | ||
const results = await resultSet.toArray(); | ||
const ret: T[] = []; | ||
@@ -73,3 +59,5 @@ | ||
await this.processPopulate(ret, populate); | ||
for (const field of populate) { | ||
await this.populateMany(entityName, ret, field); | ||
} | ||
@@ -81,3 +69,3 @@ return ret; | ||
if (!where || (typeof where === 'object' && Object.keys(where).length === 0)) { | ||
return null; | ||
throw new Error(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`); | ||
} | ||
@@ -90,2 +78,3 @@ | ||
if (Utils.isString(where) && this.identityMap[`${entityName}-${where}`] && this.identityMap[`${entityName}-${where}`].isInitialized()) { | ||
await this.populateOne(entityName, this.identityMap[`${entityName}-${where}`], populate); | ||
return this.identityMap[`${entityName}-${where}`] as T; | ||
@@ -109,3 +98,3 @@ } | ||
const entity = this.merge(entityName, data) as T; | ||
await this.processPopulate(entity, populate); | ||
await this.populateOne(entityName, entity, populate); | ||
@@ -124,9 +113,13 @@ return entity; | ||
entity.assign(data); | ||
this.unitOfWork.addToIdentityMap(entity); | ||
} else { | ||
this.addToIdentityMap(entity); | ||
} | ||
this.addToIdentityMap(entity); | ||
return entity as T; | ||
} | ||
/** | ||
* gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded | ||
*/ | ||
getReference<T extends BaseEntity>(entityName: string, id: string): T { | ||
@@ -212,30 +205,31 @@ if (this.identityMap[`${entityName}-${id}`]) { | ||
private async processPopulate(entity: BaseEntity | BaseEntity[], populate: string[]): Promise<void> { | ||
if (entity instanceof BaseEntity) { | ||
for (const field of populate) { | ||
if (entity[field] instanceof Collection && !entity[field].isInitialized()) { | ||
await (entity[field] as Collection<BaseEntity>).init(); | ||
} | ||
private async populateOne(entityName: string, entity: BaseEntity, populate: string[]): Promise<void> { | ||
for (const field of populate) { | ||
if (!this.canPopulate(entityName, field)) { | ||
throw new Error(`Entity '${entityName}' does not have property '${field}'`); | ||
} | ||
if (entity[field] instanceof BaseEntity && !entity[field].isInitialized()) { | ||
await (entity[field] as BaseEntity).init(); | ||
} | ||
if (entity[field] instanceof Collection && !entity[field].isInitialized(true)) { | ||
await (entity[field] as Collection<BaseEntity>).init(); | ||
} | ||
return; | ||
if (entity[field] instanceof BaseEntity && !entity[field].isInitialized()) { | ||
await (entity[field] as BaseEntity).init(); | ||
} | ||
} | ||
} | ||
if (entity.length === 0) { | ||
return; | ||
/** | ||
* preload everything in one call (this will update already existing references in IM) | ||
*/ | ||
private async populateMany(entityName: string, entities: BaseEntity[], field: string): Promise<void> { | ||
if (!this.canPopulate(entityName, field)) { | ||
throw new Error(`Entity '${entityName}' does not have property '${field}'`); | ||
} | ||
for (const field of populate) { | ||
await this.populateMany(entity, field); | ||
} | ||
} | ||
const meta = this.metadata[entityName].properties[field]; | ||
private async populateMany(entities: BaseEntity[], field: string): Promise<void> { | ||
if (entities[0][field] instanceof Collection) { | ||
if (meta.reference === ReferenceType.MANY_TO_MANY && !meta.owner) { | ||
for (const entity of entities) { | ||
if (entity[field] instanceof Collection && !entity[field].isInitialized()) { | ||
if (!entity[field].isInitialized()) { | ||
await (entity[field] as Collection<BaseEntity>).init(); | ||
@@ -248,4 +242,16 @@ } | ||
const children = entities.filter(e => e[field] instanceof BaseEntity && !e[field].isInitialized()); | ||
const children: BaseEntity[] = []; | ||
let fk = '_id'; | ||
if (meta.reference === ReferenceType.ONE_TO_MANY) { | ||
const filtered = entities.filter(e => e[field] instanceof Collection); | ||
children.push(...filtered.map(e => e[field].owner)); | ||
fk = meta.fk; | ||
} else if (meta.reference === ReferenceType.MANY_TO_MANY) { | ||
const filtered = entities.filter(e => e[field] instanceof Collection && !e[field].isInitialized(true)); | ||
children.push(...filtered.reduce((a, b) => [...a, ...b[field].getItems()], [])); | ||
} else { | ||
children.push(...entities.filter(e => e[field] instanceof BaseEntity && !e[field].isInitialized()).map(e => e[field])); | ||
} | ||
if (children.length === 0) { | ||
@@ -255,8 +261,38 @@ return; | ||
// preload everything in one call (this will update already existing references in IM) | ||
const ids = Utils.unique(children.map(e => e[field].id)); | ||
const meta = this.metadata[entities[0].constructor.name].properties[field]; | ||
await this.find<BaseEntity>(meta.type, { _id: { $in: ids } }); | ||
const ids = Utils.unique(children.map(e => e.id)); | ||
const data = await this.find<BaseEntity>(meta.type, { [fk]: { $in: ids } }); | ||
// initialize collections for one to many | ||
if (meta.reference === ReferenceType.ONE_TO_MANY) { | ||
for (const entity of entities) { | ||
const items = data.filter(child => child[fk] === entity); | ||
(entity[field] as Collection<BaseEntity>).set(items, true); | ||
} | ||
} | ||
} | ||
private buildQuery<T extends BaseEntity>(entityName: string, where: FilterQuery<T>, orderBy: { [p: string]: 1 | -1 }, limit: number, offset: number): { query: string; resultSet: any } { | ||
Utils.renameKey(where, 'id', '_id'); | ||
let query = `db.getCollection("${this.metadata[entityName].collection}").find(${JSON.stringify(where)})`; | ||
where = Utils.convertObjectIds(where); | ||
const resultSet = this.getCollection(entityName).find(where); | ||
if (Object.keys(orderBy).length > 0) { | ||
query += `.sort(${JSON.stringify(orderBy)})`; | ||
resultSet.sort(orderBy); | ||
} | ||
if (limit !== null) { | ||
query += `.limit(${limit})`; | ||
resultSet.limit(limit); | ||
} | ||
if (offset !== null) { | ||
query += `.skip(${offset})`; | ||
resultSet.skip(offset); | ||
} | ||
return { query, resultSet }; | ||
} | ||
private runHooks(type: string, entity: BaseEntity) { | ||
@@ -263,0 +299,0 @@ const hooks = this.metadata[entity.constructor.name].hooks; |
@@ -10,2 +10,6 @@ import { FilterQuery } from 'mongodb'; | ||
getReference<T extends BaseEntity>(id: string): T { | ||
return this.em.getReference(this.entityName, id); | ||
} | ||
async persist(entity: T, flush = true): Promise<void> { | ||
@@ -12,0 +16,0 @@ return this.em.persist(entity, flush); |
{ | ||
"name": "mikro-orm", | ||
"version": "0.7.4", | ||
"version": "0.8.0", | ||
"description": "Simple typescript mongo ORM for node.js based on data-mapper, unit-of-work and identity-map patterns", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -34,3 +34,2 @@ import { EntityFactory } from '../lib/EntityFactory'; | ||
const metadata = factory.getMetadata(); | ||
// console.log(require('util').inspect(metadata, true, 5, true)); | ||
expect(metadata).toBeInstanceOf(Object); | ||
@@ -37,0 +36,0 @@ expect(metadata[Author.name]).toBeInstanceOf(Object); |
@@ -9,2 +9,3 @@ import { ObjectID } from 'bson'; | ||
import { initORM, wipeDatabase } from './bootstrap'; | ||
import { Test } from './entities/Test'; | ||
@@ -103,2 +104,4 @@ /** | ||
expect(jon.books.getIdentifiers()[0]).toBeInstanceOf(ObjectID); | ||
expect(jon.books.getIdentifiers('id')).toBeInstanceOf(Array); | ||
expect(typeof jon.books.getIdentifiers('id')[0]).toBe('string'); | ||
@@ -155,8 +158,8 @@ for (const author of authors) { | ||
test('findOne with empty where will return null', async () => { | ||
expect(await orm.em.findOne<Author>(Author.name, '')).toBeNull(); | ||
expect(await orm.em.findOne<Author>(Author.name, {})).toBeNull(); | ||
expect(await orm.em.findOne<Author>(Author.name, [])).toBeNull(); | ||
expect(await orm.em.findOne<Author>(Author.name, undefined)).toBeNull(); | ||
expect(await orm.em.findOne<Author>(Author.name, null)).toBeNull(); | ||
test('findOne with empty where will throw', async () => { | ||
await expect(orm.em.findOne<Author>(Author.name, '')).rejects.toThrowError(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`); | ||
await expect(orm.em.findOne<Author>(Author.name, {})).rejects.toThrowError(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`); | ||
await expect(orm.em.findOne<Author>(Author.name, [])).rejects.toThrowError(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`); | ||
await expect(orm.em.findOne<Author>(Author.name, undefined)).rejects.toThrowError(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`); | ||
await expect(orm.em.findOne<Author>(Author.name, null)).rejects.toThrowError(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`); | ||
}); | ||
@@ -328,2 +331,54 @@ | ||
test('populating many to many relation', async () => { | ||
const p1 = new Publisher('foo'); | ||
expect(p1.tests).toBeInstanceOf(Collection); | ||
expect(p1.tests.isInitialized()).toBe(true); | ||
expect(p1.tests.isDirty()).toBe(false); | ||
expect(p1.tests.count()).toBe(0); | ||
const p2 = new Publisher('bar'); | ||
p2.tests.add(new Test(), new Test()); | ||
await orm.em.persist([p1, p2]); | ||
const repo = orm.em.getRepository<Publisher>(Publisher.name); | ||
orm.em.clear(); | ||
const publishers = await repo.findAll(['tests']); | ||
expect(publishers).toBeInstanceOf(Array); | ||
expect(publishers.length).toBe(2); | ||
expect(publishers[0]).toBeInstanceOf(Publisher); | ||
expect(publishers[0].tests).toBeInstanceOf(Collection); | ||
expect(publishers[0].tests.isInitialized()).toBe(true); | ||
expect(publishers[0].tests.isDirty()).toBe(false); | ||
expect(publishers[0].tests.count()).toBe(0); | ||
await publishers[0].tests.init(); // empty many to many on owning side should not make db calls | ||
expect(publishers[1].tests.getItems()[0].isInitialized()).toBe(true); | ||
}); | ||
test('populating many to many relation on inverse side', async () => { | ||
const author = new Author('Jon Snow', 'snow@wall.st'); | ||
const book1 = new Book('My Life on The Wall, part 1', author); | ||
const book2 = new Book('My Life on The Wall, part 2', author); | ||
const book3 = new Book('My Life on The Wall, part 3', author); | ||
const tag1 = new BookTag('silly'); | ||
const tag2 = new BookTag('funny'); | ||
const tag3 = new BookTag('sick'); | ||
const tag4 = new BookTag('strange'); | ||
const tag5 = new BookTag('sexy'); | ||
book1.tags.add(tag1, tag3); | ||
book2.tags.add(tag1, tag2, tag5); | ||
book3.tags.add(tag2, tag4, tag5); | ||
await orm.em.persist([book1, book2, book3]); | ||
const repo = orm.em.getRepository<BookTag>(BookTag.name); | ||
orm.em.clear(); | ||
const tags = await repo.findAll(['books']); | ||
expect(tags).toBeInstanceOf(Array); | ||
expect(tags.length).toBe(5); | ||
expect(tags[0]).toBeInstanceOf(BookTag); | ||
expect(tags[0].books).toBeInstanceOf(Collection); | ||
expect(tags[0].books.isInitialized()).toBe(true); | ||
expect(tags[0].books.isDirty()).toBe(false); | ||
expect(tags[0].books.count()).toBe(2); | ||
expect(tags[0].books.getItems()[0].isInitialized()).toBe(true); | ||
}); | ||
test('hooks', async () => { | ||
@@ -368,2 +423,12 @@ Author.beforeDestroyCalled = 0; | ||
}); | ||
test('trying to populate non-existing or non-reference property will throw', async () => { | ||
const repo = orm.em.getRepository<Author>(Author.name); | ||
const author = new Author('Johny Cash', 'johny@cash.com'); | ||
await repo.persist(author); | ||
orm.em.clear(); | ||
await expect(repo.findAll(['tests'])).rejects.toThrowError(`Entity 'Author' does not have property 'tests'`); | ||
await expect(repo.findOne(author.id, ['tests'])).rejects.toThrowError(`Entity 'Author' does not have property 'tests'`); | ||
}); | ||
}); | ||
@@ -370,0 +435,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { MikroORM, Options, EntityManager } from '../lib'; | ||
import { MikroORM, Options, EntityManager, getEntityManager } from '../lib'; | ||
@@ -8,2 +8,6 @@ /** | ||
test('should throw when trying to get EM before initialisation', async () => { | ||
await expect(() => getEntityManager()).toThrowError('Call MikroORM.init() first!'); | ||
}); | ||
test('should throw when not enough options provided', async () => { | ||
@@ -10,0 +14,0 @@ await expect(() => new MikroORM({ entitiesDirs: ['entities'] } as Options)).toThrowError('No database specified, please fill in `dbName` option'); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
156023
71
3529