mikro-orm
Advanced tools
Comparing version 0.2.3 to 0.3.0
@@ -10,2 +10,3 @@ import { ObjectID } from 'bson'; | ||
private _initialized; | ||
protected constructor(); | ||
id: string; | ||
@@ -17,2 +18,8 @@ isInitialized(): boolean; | ||
} | ||
export declare enum ReferenceType { | ||
SCALAR = 0, | ||
MANY_TO_ONE = 1, | ||
ONE_TO_MANY = 2, | ||
MANY_TO_MANY = 3, | ||
} | ||
export interface EntityProperty { | ||
@@ -23,7 +30,9 @@ name: string; | ||
type: string; | ||
reference: boolean; | ||
collection: boolean; | ||
reference: ReferenceType; | ||
attributes?: { | ||
[attribute: string]: any; | ||
}; | ||
owner?: boolean; | ||
inversedBy: string; | ||
mappedBy: string; | ||
} | ||
@@ -30,0 +39,0 @@ export interface EntityMetadata { |
@@ -11,2 +11,10 @@ "use strict"; | ||
this._initialized = false; | ||
const metadata = MikroORM_1.getMetadataStorage(); | ||
const meta = metadata[this.constructor.name]; | ||
const props = meta.properties; | ||
Object.keys(props).forEach(prop => { | ||
if ([ReferenceType.ONE_TO_MANY, ReferenceType.MANY_TO_MANY].includes(props[prop].reference)) { | ||
this[prop] = new Collection_1.Collection(props[prop], this, []); | ||
} | ||
}); | ||
} | ||
@@ -26,3 +34,3 @@ get id() { | ||
} | ||
toObject(parent = null) { | ||
toObject(parent = this) { | ||
const metadata = MikroORM_1.getMetadataStorage(); | ||
@@ -60,1 +68,8 @@ const meta = metadata[this.constructor.name]; | ||
exports.BaseEntity = BaseEntity; | ||
var ReferenceType; | ||
(function (ReferenceType) { | ||
ReferenceType[ReferenceType["SCALAR"] = 0] = "SCALAR"; | ||
ReferenceType[ReferenceType["MANY_TO_ONE"] = 1] = "MANY_TO_ONE"; | ||
ReferenceType[ReferenceType["ONE_TO_MANY"] = 2] = "ONE_TO_MANY"; | ||
ReferenceType[ReferenceType["MANY_TO_MANY"] = 3] = "MANY_TO_MANY"; | ||
})(ReferenceType = exports.ReferenceType || (exports.ReferenceType = {})); |
import { BaseEntity, EntityProperty } from './BaseEntity'; | ||
import { EntityManager } from './EntityManager'; | ||
import { ObjectID } from 'bson'; | ||
export declare class Collection<T extends BaseEntity> { | ||
@@ -7,11 +8,18 @@ private readonly property; | ||
private initialized; | ||
private items; | ||
constructor(property: EntityProperty, owner: BaseEntity); | ||
private dirty; | ||
private readonly items; | ||
constructor(property: EntityProperty, owner: BaseEntity, items?: T[]); | ||
isInitialized(): boolean; | ||
isDirty(): boolean; | ||
init(em: EntityManager): Promise<Collection<T>>; | ||
getItems(): T[]; | ||
getIdentifiers(): ObjectID[]; | ||
add(...items: T[]): void; | ||
remove(...items: T[]): void; | ||
removeAll(): void; | ||
contains(item: T): boolean; | ||
count(): number; | ||
[Symbol.iterator](): IterableIterator<T>; | ||
private checkInitialized(); | ||
private handleInverseSide(item, method); | ||
} | ||
export interface CollectionAttributes { | ||
[attribute: string]: any; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const BaseEntity_1 = require("./BaseEntity"); | ||
class Collection { | ||
constructor(property, owner) { | ||
constructor(property, owner, items = null) { | ||
this.property = property; | ||
this.owner = owner; | ||
this.initialized = false; | ||
this.dirty = false; | ||
this.items = []; | ||
if (items) { | ||
this.initialized = true; | ||
this.items = items; | ||
} | ||
} | ||
@@ -13,14 +19,67 @@ isInitialized() { | ||
} | ||
isDirty() { | ||
return this.dirty; | ||
} | ||
async init(em) { | ||
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; | ||
} | ||
} | ||
this.items.length = 0; | ||
this.items.push(...(await em.find(this.property.type, { [this.property.fk]: this.owner._id }))); | ||
const items = await em.find(this.property.type, cond); | ||
this.items.push(...items); | ||
this.initialized = true; | ||
this.dirty = false; | ||
return this; | ||
} | ||
getItems() { | ||
if (!this.isInitialized()) { | ||
throw new Error(`Collection ${this.property.type}[] of entity ${this.owner.id} not initialized`); | ||
} | ||
this.checkInitialized(); | ||
return this.items; | ||
} | ||
getIdentifiers() { | ||
return this.getItems().map(i => i._id); | ||
} | ||
add(...items) { | ||
this.checkInitialized(); | ||
for (const item of items) { | ||
this.handleInverseSide(item, 'add'); | ||
this.items.push(item); | ||
} | ||
this.dirty = this.property.owner; // set dirty flag only to owning side | ||
} | ||
remove(...items) { | ||
this.checkInitialized(); | ||
for (const item of items) { | ||
this.handleInverseSide(item, 'remove'); | ||
const idx = this.items.findIndex(i => i.id === item.id); | ||
if (idx !== -1) { | ||
this.items.splice(idx, 1); | ||
} | ||
} | ||
this.dirty = this.property.owner; // set dirty flag only to owning side | ||
} | ||
removeAll() { | ||
this.checkInitialized(); | ||
if (this.property.owner && this.property.inversedBy && this.items.length > 0) { | ||
this.items[0][this.property.inversedBy].length = 0; | ||
} | ||
this.items.length = 0; | ||
this.dirty = this.property.owner; // set dirty flag only to owning side | ||
} | ||
contains(item) { | ||
this.checkInitialized(); | ||
return !!this.items.find(i => i.id === item.id); | ||
} | ||
count() { | ||
this.checkInitialized(); | ||
return this.items.length; | ||
} | ||
*[Symbol.iterator]() { | ||
@@ -31,3 +90,13 @@ for (const item of this.items) { | ||
} | ||
checkInitialized() { | ||
if (!this.isInitialized()) { | ||
throw new Error(`Collection ${this.property.type}[] of entity ${this.owner.constructor.name}[${this.owner.id}] not initialized`); | ||
} | ||
} | ||
handleInverseSide(item, method) { | ||
if (this.property.owner && this.property.inversedBy && item[this.property.inversedBy].isInitialized()) { | ||
item[this.property.inversedBy][method](this.owner); | ||
} | ||
} | ||
} | ||
exports.Collection = Collection; |
@@ -6,3 +6,2 @@ import { PropertyOptions } from './Property'; | ||
fk?: string; | ||
cascade?: string[]; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const BaseEntity_1 = require("../BaseEntity"); | ||
const MikroORM_1 = require("../MikroORM"); | ||
@@ -9,2 +10,3 @@ function ManyToOne(options) { | ||
const meta = storage[entity]; | ||
meta.properties = meta.properties || {}; | ||
const reflectMetadataType = Reflect.getMetadata('design:type', target, propertyName); | ||
@@ -14,4 +16,2 @@ if (!options.type && reflectMetadataType) { | ||
} | ||
options.array = reflectMetadataType === Array; | ||
meta.properties = meta.properties || {}; | ||
if (!options.entity) { | ||
@@ -23,9 +23,3 @@ throw new Error(`'@ManyToOne({ entity: string })' is required in '${target.constructor.name}.${propertyName}'`); | ||
} | ||
const attributes = {}; | ||
Object.keys(options).forEach(k => { | ||
if (['cascade'].includes(k)) { | ||
attributes[k] = options[k]; | ||
} | ||
}); | ||
const property = { name: propertyName, reference: true, collection: false, attributes }; | ||
const property = { name: propertyName, reference: BaseEntity_1.ReferenceType.MANY_TO_ONE }; | ||
meta.properties[propertyName] = Object.assign(property, options); | ||
@@ -32,0 +26,0 @@ }; |
@@ -6,3 +6,2 @@ import { PropertyOptions } from './Property'; | ||
fk: string; | ||
cascade?: string[]; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const BaseEntity_1 = require("../BaseEntity"); | ||
const MikroORM_1 = require("../MikroORM"); | ||
@@ -9,7 +10,2 @@ function OneToMany(options) { | ||
const meta = storage[entity]; | ||
const reflectMetadataType = Reflect.getMetadata('design:type', target, propertyName); | ||
if (!options.type && reflectMetadataType) { | ||
options.type = reflectMetadataType; | ||
} | ||
options.array = reflectMetadataType === Array; | ||
meta.properties = meta.properties || {}; | ||
@@ -19,9 +15,3 @@ if (!options.entity) { | ||
} | ||
const attributes = {}; | ||
Object.keys(options).forEach(k => { | ||
if (['cascade'].includes(k)) { | ||
attributes[k] = options[k]; | ||
} | ||
}); | ||
const property = { name: propertyName, reference: true, collection: true, attributes }; | ||
const property = { name: propertyName, reference: BaseEntity_1.ReferenceType.ONE_TO_MANY }; | ||
meta.properties[propertyName] = Object.assign(property, options); | ||
@@ -28,0 +18,0 @@ }; |
@@ -5,4 +5,3 @@ export declare function Property(options?: PropertyOptions): Function; | ||
type?: any; | ||
array?: boolean; | ||
[prop: string]: any; | ||
}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const BaseEntity_1 = require("../BaseEntity"); | ||
const MikroORM_1 = require("../MikroORM"); | ||
@@ -14,7 +15,6 @@ function Property(options = {}) { | ||
options.name = propertyName; | ||
options.array = type === Array; | ||
meta.properties = meta.properties || {}; | ||
meta.properties[propertyName] = Object.assign({}, options, { reference: false, collection: false }); | ||
meta.properties[propertyName] = Object.assign({ reference: BaseEntity_1.ReferenceType.SCALAR }, options); | ||
}; | ||
} | ||
exports.Property = Property; |
@@ -12,5 +12,5 @@ import { EntityManager } from './EntityManager'; | ||
}; | ||
create<T extends BaseEntity>(entityName: string, data: any): T; | ||
create<T extends BaseEntity>(entityName: string, data: any, initialized?: boolean): T; | ||
createReference<T extends BaseEntity>(entityName: string, id: string): T; | ||
initEntity<T extends BaseEntity>(entity: T, properties: any, data: any, exclude?: string[]): void; | ||
initEntity<T extends BaseEntity>(entity: T, properties: any, data: any, exclude?: string[], found?: boolean): void; | ||
/** | ||
@@ -17,0 +17,0 @@ * returns parameters for entity constructor, creating references from plain ids |
@@ -7,2 +7,4 @@ "use strict"; | ||
const Collection_1 = require("./Collection"); | ||
const BaseEntity_1 = require("./BaseEntity"); | ||
const Utils_1 = require("./Utils"); | ||
class EntityFactory { | ||
@@ -19,5 +21,6 @@ constructor(em) { | ||
} | ||
create(entityName, data) { | ||
create(entityName, data, initialized = true) { | ||
const meta = this.metadata[entityName]; | ||
const exclude = []; | ||
let found = false; | ||
let entity; | ||
@@ -35,2 +38,3 @@ // TODO test those conditions if we really need them both | ||
entity = this.em.identityMap[`${entityName}-${data._id}`]; | ||
found = true; | ||
} | ||
@@ -43,3 +47,9 @@ else { | ||
} | ||
this.initEntity(entity, meta.properties, data, exclude); | ||
this.initEntity(entity, meta.properties, data, exclude, found); | ||
if (initialized) { | ||
delete entity['_initialized']; | ||
} | ||
else { | ||
entity['_initialized'] = initialized; | ||
} | ||
return entity; | ||
@@ -51,7 +61,5 @@ } | ||
} | ||
const ref = this.create(entityName, { id }); | ||
ref['_initialized'] = false; | ||
return ref; | ||
return this.create(entityName, { id }, false); | ||
} | ||
initEntity(entity, properties, data, exclude = []) { | ||
initEntity(entity, properties, data, exclude = [], found = false) { | ||
// process base entity properties first | ||
@@ -69,16 +77,23 @@ ['_id', 'createdAt', 'updatedAt'].forEach(k => { | ||
} | ||
if (prop.collection && !entity[p]) { | ||
entity[p] = new Collection_1.Collection(prop, entity); | ||
if (prop.reference === BaseEntity_1.ReferenceType.ONE_TO_MANY && !data[p]) { | ||
return entity[p] = new Collection_1.Collection(prop, entity); | ||
} | ||
else if (prop.reference && !prop.collection) { | ||
if (prop.reference === BaseEntity_1.ReferenceType.MANY_TO_MANY && !prop.owner && !found && (!entity[p] || !data[p])) { | ||
return entity[p] = new Collection_1.Collection(prop, entity); | ||
} | ||
if (prop.reference === BaseEntity_1.ReferenceType.MANY_TO_MANY && prop.owner && Utils_1.Utils.isArray(data[p])) { | ||
const items = data[p].map((id) => this.createReference(prop.type, id.toHexString())); | ||
return entity[p] = new Collection_1.Collection(prop, entity, items); | ||
} | ||
if (prop.reference === BaseEntity_1.ReferenceType.MANY_TO_ONE) { | ||
if (data[p] instanceof bson_1.ObjectID) { | ||
entity[p] = this.createReference(prop.type, data[p]); | ||
entity[p] = this.createReference(prop.type, data[p].toHexString()); | ||
this.em.addToIdentityMap(entity[p]); | ||
} | ||
return; | ||
} | ||
else if (data[p] && !prop.reference) { | ||
if (prop.reference === BaseEntity_1.ReferenceType.SCALAR && data[p]) { | ||
entity[p] = data[p]; | ||
} | ||
}); | ||
delete entity['_initialized']; | ||
} | ||
@@ -91,3 +106,3 @@ /** | ||
return meta.constructorParams.map((k) => { | ||
if (meta.properties[k].reference && !meta.properties[k].collection && data[k]) { | ||
if (meta.properties[k].reference === BaseEntity_1.ReferenceType.MANY_TO_ONE && data[k]) { | ||
return this.em.getReference(meta.properties[k].type, data[k]); | ||
@@ -94,0 +109,0 @@ } |
@@ -37,3 +37,2 @@ import { Collection as MongoCollection, Db, FilterQuery } from 'mongodb'; | ||
clear(): void; | ||
create<T extends BaseEntity>(entityName: string, data: any): T; | ||
addToIdentityMap(entity: BaseEntity): void; | ||
@@ -40,0 +39,0 @@ canPopulate(entityName: string, property: string): boolean; |
@@ -79,3 +79,6 @@ "use strict"; | ||
merge(entityName, data) { | ||
const entity = data instanceof BaseEntity_1.BaseEntity ? data : this.entityFactory.create(entityName, data); | ||
if (!data.id && !data._id) { | ||
throw new Error('You cannot merge entity without id!'); | ||
} | ||
const entity = data instanceof BaseEntity_1.BaseEntity ? data : this.entityFactory.create(entityName, data, true); | ||
if (this.identityMap[`${entityName}-${entity.id}`]) { | ||
@@ -135,7 +138,2 @@ // TODO populate missing references and rehydrate | ||
} | ||
create(entityName, data) { | ||
const entity = this.entityFactory.create(entityName, data); | ||
entity['_initialized'] = false; | ||
return entity; | ||
} | ||
addToIdentityMap(entity) { | ||
@@ -147,3 +145,3 @@ this.identityMap[`${entity.constructor.name}-${entity.id}`] = entity; | ||
const props = this.metadata[entityName].properties; | ||
return property in props && props[property].reference; | ||
return property in props && !!props[property].reference; | ||
} | ||
@@ -150,0 +148,0 @@ /** |
@@ -18,4 +18,3 @@ import { FilterQuery } from 'mongodb'; | ||
canPopulate(property: string): boolean; | ||
create(data: any): T; | ||
count(where: any): Promise<number>; | ||
count(where?: any): Promise<number>; | ||
} |
@@ -26,6 +26,3 @@ "use strict"; | ||
} | ||
create(data) { | ||
return this.em.create(this.entityName, data); | ||
} | ||
async count(where) { | ||
async count(where = {}) { | ||
return this.em.count(this.entityName, where); | ||
@@ -32,0 +29,0 @@ } |
@@ -9,4 +9,5 @@ export * from './MikroORM'; | ||
export * from './decorators/ManyToOne'; | ||
export * from './decorators/ManyToMany'; | ||
export * from './decorators/Property'; | ||
export * from './decorators/hooks'; | ||
export { ObjectID } from 'bson'; |
@@ -14,2 +14,3 @@ "use strict"; | ||
__export(require("./decorators/ManyToOne")); | ||
__export(require("./decorators/ManyToMany")); | ||
__export(require("./decorators/Property")); | ||
@@ -16,0 +17,0 @@ __export(require("./decorators/hooks")); |
@@ -14,2 +14,5 @@ import { EntityManager } from './EntityManager'; | ||
private processReferences(changeSet, meta); | ||
private processManyToOne(changeSet, prop); | ||
private processOneToMany(changeSet, prop); | ||
private processManyToMany(changeSet, prop); | ||
private removeUnknownProperties(changeSet, meta); | ||
@@ -16,0 +19,0 @@ private immediateCommit(changeSet, removeFromStack?); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Utils_1 = require("./Utils"); | ||
const Collection_1 = require("./Collection"); | ||
const BaseEntity_1 = require("./BaseEntity"); | ||
class UnitOfWork { | ||
@@ -52,15 +52,41 @@ constructor(em) { | ||
const prop = meta.properties[p]; | ||
if (prop.collection) { | ||
// TODO cascade persist... | ||
delete changeSet.payload[prop.name]; | ||
if (prop.reference === BaseEntity_1.ReferenceType.ONE_TO_MANY) { | ||
await this.processOneToMany(changeSet, prop); | ||
} | ||
else if (prop.reference && changeSet.entity[prop.name]) { | ||
else if (prop.reference === BaseEntity_1.ReferenceType.MANY_TO_MANY) { | ||
await this.processManyToMany(changeSet, prop); | ||
} | ||
else if (prop.reference === BaseEntity_1.ReferenceType.MANY_TO_ONE && changeSet.entity[prop.name]) { | ||
await this.processManyToOne(changeSet, prop); | ||
} | ||
} | ||
} | ||
async processManyToOne(changeSet, prop) { | ||
// when new entity found in reference, cascade persist it first so we have its id | ||
if (!changeSet.entity[prop.name]._id) { | ||
const propChangeSet = await this.persist(changeSet.entity[prop.name]); | ||
await this.immediateCommit(propChangeSet); | ||
} | ||
changeSet.payload[prop.name] = changeSet.entity[prop.name]._id; | ||
} | ||
async processOneToMany(changeSet, prop) { | ||
if (changeSet.entity[prop.name].isDirty()) { | ||
// TODO cascade persist... | ||
} | ||
delete changeSet.payload[prop.name]; | ||
} | ||
async processManyToMany(changeSet, prop) { | ||
if (prop.owner && changeSet.entity[prop.name].isDirty()) { | ||
for (const item of changeSet.entity[prop.name].getItems()) { | ||
// when new entity found in reference, cascade persist it first so we have its id | ||
if (!changeSet.entity[prop.name]._id) { | ||
const propChangeSet = await this.persist(changeSet.entity[prop.name]); | ||
await this.immediateCommit(propChangeSet); | ||
if (!item._id) { | ||
const itemChangeSet = await this.persist(item); | ||
await this.immediateCommit(itemChangeSet); | ||
} | ||
changeSet.payload[prop.name] = changeSet.entity[prop.name]._id; | ||
} | ||
changeSet.payload[prop.name] = changeSet.entity[prop.name].getIdentifiers(); | ||
} | ||
else { | ||
delete changeSet.payload[prop.name]; | ||
} | ||
} | ||
@@ -83,16 +109,20 @@ removeUnknownProperties(changeSet, meta) { | ||
const prop = meta.properties[p]; | ||
if (prop.reference && !prop.collection && changeSet.entity[prop.name]) { | ||
if (!changeSet.entity[prop.name]._id) { | ||
const propChangeSet = await this.persist(changeSet.entity[prop.name]); | ||
const reference = changeSet.entity[prop.name]; | ||
if (prop.reference === BaseEntity_1.ReferenceType.MANY_TO_ONE && reference) { | ||
if (!reference._id) { | ||
const propChangeSet = await this.persist(reference); | ||
await this.immediateCommit(propChangeSet); | ||
} | ||
changeSet.payload[prop.name] = changeSet.entity[prop.name]._id; | ||
changeSet.payload[prop.name] = reference._id; | ||
} | ||
if (prop.reference && prop.collection) { | ||
if (!changeSet.entity[prop.name]) { // create collection when missing (e.g. when persisting new entity) | ||
changeSet.entity[prop.name] = new Collection_1.Collection(prop, changeSet.entity); | ||
} | ||
// TODO many to one collection cascade support | ||
if (prop.reference === BaseEntity_1.ReferenceType.ONE_TO_MANY) { | ||
// TODO one to many collection cascade support | ||
// ... | ||
reference.dirty = false; | ||
} | ||
if (prop.reference === BaseEntity_1.ReferenceType.MANY_TO_MANY && prop.owner) { | ||
// TODO many to many collection cascade support | ||
// ... | ||
reference.dirty = false; | ||
} | ||
} | ||
@@ -99,0 +129,0 @@ // persist the entity itself |
@@ -16,2 +16,14 @@ import { ObjectID } from 'bson'; | ||
protected constructor() { | ||
const metadata = getMetadataStorage(); | ||
const meta = metadata[this.constructor.name]; | ||
const props = meta.properties; | ||
Object.keys(props).forEach(prop => { | ||
if ([ReferenceType.ONE_TO_MANY, ReferenceType.MANY_TO_MANY].includes(props[prop].reference)) { | ||
this[prop] = new Collection(props[prop], this, []); | ||
} | ||
}); | ||
} | ||
get id(): string { | ||
@@ -35,3 +47,3 @@ return this._id ? this._id.toHexString() : null; | ||
toObject(parent: BaseEntity = null): any { | ||
toObject(parent: BaseEntity = this): any { | ||
const metadata = getMetadataStorage(); | ||
@@ -78,2 +90,9 @@ const meta = metadata[this.constructor.name]; | ||
export enum ReferenceType { | ||
SCALAR = 0, | ||
MANY_TO_ONE = 1, | ||
ONE_TO_MANY = 2, | ||
MANY_TO_MANY = 3, | ||
} | ||
export interface EntityProperty { | ||
@@ -84,5 +103,7 @@ name: string; | ||
type: string; | ||
reference: boolean; | ||
collection: boolean; | ||
reference: ReferenceType; | ||
attributes?: { [attribute: string]: any }; | ||
owner?: boolean; | ||
inversedBy: string; | ||
mappedBy: string; | ||
} | ||
@@ -89,0 +110,0 @@ |
@@ -1,3 +0,4 @@ | ||
import { BaseEntity, EntityProperty } from './BaseEntity'; | ||
import { BaseEntity, EntityProperty, ReferenceType } from './BaseEntity'; | ||
import { EntityManager } from './EntityManager'; | ||
import { ObjectID } from 'bson'; | ||
@@ -7,6 +8,13 @@ export class Collection<T extends BaseEntity> { | ||
private initialized = false; | ||
private items: T[] = []; | ||
private dirty = false; | ||
private readonly items: T[] = []; | ||
constructor(private readonly property: EntityProperty, | ||
private readonly owner: BaseEntity) { } | ||
private readonly owner: BaseEntity, | ||
items: T[] = null) { | ||
if (items) { | ||
this.initialized = true; | ||
this.items = items; | ||
} | ||
} | ||
@@ -17,6 +25,24 @@ isInitialized(): boolean { | ||
isDirty(): boolean { | ||
return this.dirty; | ||
} | ||
async init(em: EntityManager): Promise<Collection<T>> { | ||
const cond = {} as 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; | ||
} | ||
} | ||
this.items.length = 0; | ||
this.items.push(...(await em.find<T>(this.property.type, { [this.property.fk]: this.owner._id }))); | ||
const items = await em.find<T>(this.property.type, cond); | ||
this.items.push(...items); | ||
this.initialized = true; | ||
this.dirty = false; | ||
@@ -27,9 +53,57 @@ return this; | ||
getItems(): T[] { | ||
if (!this.isInitialized()) { | ||
throw new Error(`Collection ${this.property.type}[] of entity ${this.owner.id} not initialized`); | ||
this.checkInitialized(); | ||
return this.items; | ||
} | ||
getIdentifiers(): ObjectID[] { | ||
return this.getItems().map(i => i._id); | ||
} | ||
add(...items: T[]): void { | ||
this.checkInitialized(); | ||
for (const item of items) { | ||
this.handleInverseSide(item, 'add'); | ||
this.items.push(item); | ||
} | ||
return this.items; | ||
this.dirty = this.property.owner; // set dirty flag only to owning side | ||
} | ||
remove(...items: T[]): void { | ||
this.checkInitialized(); | ||
for (const item of items) { | ||
this.handleInverseSide(item, 'remove'); | ||
const idx = this.items.findIndex(i => i.id === item.id); | ||
if (idx !== -1) { | ||
this.items.splice(idx, 1); | ||
} | ||
} | ||
this.dirty = this.property.owner; // set dirty flag only to owning side | ||
} | ||
removeAll(): void { | ||
this.checkInitialized(); | ||
if (this.property.owner && this.property.inversedBy && this.items.length > 0) { | ||
this.items[0][this.property.inversedBy].length = 0; | ||
} | ||
this.items.length = 0; | ||
this.dirty = this.property.owner; // set dirty flag only to owning side | ||
} | ||
contains(item: T): boolean { | ||
this.checkInitialized(); | ||
return !!this.items.find(i => i.id === item.id); | ||
} | ||
count(): number { | ||
this.checkInitialized(); | ||
return this.items.length; | ||
} | ||
*[Symbol.iterator](): IterableIterator<T> { | ||
@@ -41,6 +115,15 @@ for (const item of this.items) { | ||
private checkInitialized(): void { | ||
if (!this.isInitialized()) { | ||
throw new Error(`Collection ${this.property.type}[] of entity ${this.owner.constructor.name}[${this.owner.id}] not initialized`); | ||
} | ||
} | ||
private handleInverseSide(item: T, method: string) { | ||
if (this.property.owner && this.property.inversedBy && item[this.property.inversedBy].isInitialized()) { | ||
item[this.property.inversedBy][method](this.owner); | ||
} | ||
} | ||
} | ||
export interface CollectionAttributes { | ||
[attribute: string]: any; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { BaseEntity, EntityProperty } from '../BaseEntity'; | ||
import { BaseEntity, EntityProperty, ReferenceType } from '../BaseEntity'; | ||
import { getMetadataStorage } from '../MikroORM'; | ||
@@ -11,2 +11,3 @@ import { PropertyOptions } from './Property'; | ||
const meta = storage[entity]; | ||
meta.properties = meta.properties || {}; | ||
const reflectMetadataType = Reflect.getMetadata('design:type', target, propertyName); | ||
@@ -18,5 +19,2 @@ | ||
options.array = reflectMetadataType === Array; | ||
meta.properties = meta.properties || {}; | ||
if (!options.entity) { | ||
@@ -30,10 +28,3 @@ throw new Error(`'@ManyToOne({ entity: string })' is required in '${target.constructor.name}.${propertyName}'`); | ||
const attributes = {} as any; | ||
Object.keys(options).forEach(k => { | ||
if (['cascade'].includes(k)) { | ||
attributes[k] = options[k]; | ||
} | ||
}); | ||
const property = { name: propertyName, reference: true, collection: false, attributes }; | ||
const property = { name: propertyName, reference: ReferenceType.MANY_TO_ONE }; | ||
meta.properties[propertyName] = Object.assign(property, options) as EntityProperty; | ||
@@ -46,3 +37,2 @@ }; | ||
fk?: string; | ||
cascade?: string[]; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { BaseEntity, EntityProperty } from '../BaseEntity'; | ||
import { BaseEntity, EntityProperty, ReferenceType } from '../BaseEntity'; | ||
import { getMetadataStorage } from '../MikroORM'; | ||
@@ -11,9 +11,2 @@ import { PropertyOptions } from './Property'; | ||
const meta = storage[entity]; | ||
const reflectMetadataType = Reflect.getMetadata('design:type', target, propertyName); | ||
if (!options.type && reflectMetadataType) { | ||
options.type = reflectMetadataType; | ||
} | ||
options.array = reflectMetadataType === Array; | ||
meta.properties = meta.properties || {}; | ||
@@ -25,10 +18,3 @@ | ||
const attributes = {} as any; | ||
Object.keys(options).forEach(k => { | ||
if (['cascade'].includes(k)) { | ||
attributes[k] = options[k]; | ||
} | ||
}); | ||
const property = { name: propertyName, reference: true, collection: true, attributes }; | ||
const property = { name: propertyName, reference: ReferenceType.ONE_TO_MANY }; | ||
meta.properties[propertyName] = Object.assign(property, options) as EntityProperty; | ||
@@ -41,3 +27,2 @@ }; | ||
fk: string; | ||
cascade?: string[]; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { BaseEntity, EntityProperty } from '../BaseEntity'; | ||
import { BaseEntity, EntityProperty, ReferenceType } from '../BaseEntity'; | ||
import { getMetadataStorage } from '../MikroORM'; | ||
@@ -17,6 +17,4 @@ | ||
options.name = propertyName; | ||
options.array = type === Array; | ||
meta.properties = meta.properties || {}; | ||
meta.properties[propertyName] = Object.assign({}, options, { reference: false, collection: false }) as EntityProperty; | ||
meta.properties[propertyName] = Object.assign({ reference: ReferenceType.SCALAR }, options) as EntityProperty; | ||
}; | ||
@@ -28,4 +26,3 @@ } | ||
type?: any; | ||
array?: boolean; | ||
[prop: string]: any; | ||
} |
@@ -7,3 +7,4 @@ import { readdirSync } from 'fs'; | ||
import { EntityManager } from './EntityManager'; | ||
import { BaseEntity, EntityMetadata } from './BaseEntity'; | ||
import { BaseEntity, EntityMetadata, EntityProperty, ReferenceType } from './BaseEntity'; | ||
import { Utils } from './Utils'; | ||
@@ -24,5 +25,6 @@ export class EntityFactory { | ||
create<T extends BaseEntity>(entityName: string, data: any): T { | ||
create<T extends BaseEntity>(entityName: string, data: any, initialized = true): T { | ||
const meta = this.metadata[entityName]; | ||
const exclude = []; | ||
let found = false; | ||
let entity; | ||
@@ -43,2 +45,3 @@ | ||
entity = this.em.identityMap[`${entityName}-${data._id}`]; | ||
found = true; | ||
} else { | ||
@@ -51,4 +54,10 @@ const params = this.extractConstructorParams<T>(meta, data); | ||
this.initEntity(entity, meta.properties, data, exclude); | ||
this.initEntity(entity, meta.properties, data, exclude, found); | ||
if (initialized) { | ||
delete entity['_initialized']; | ||
} else { | ||
entity['_initialized'] = initialized; | ||
} | ||
return entity; | ||
@@ -62,9 +71,6 @@ } | ||
const ref = this.create<T>(entityName, { id }); | ||
ref['_initialized'] = false; | ||
return ref; | ||
return this.create<T>(entityName, { id }, false); | ||
} | ||
initEntity<T extends BaseEntity>(entity: T, properties: any, data: any, exclude: string[] = []): void { | ||
initEntity<T extends BaseEntity>(entity: T, properties: any, data: any, exclude: string[] = [], found = false): void { | ||
// process base entity properties first | ||
@@ -79,3 +85,3 @@ ['_id', 'createdAt', 'updatedAt'].forEach(k => { | ||
Object.keys(properties).forEach(p => { | ||
const prop = properties[p]; | ||
const prop = properties[p] as EntityProperty; | ||
@@ -86,15 +92,28 @@ if (exclude.includes(p)) { | ||
if (prop.collection && !entity[p]) { | ||
entity[p] = new Collection<T>(prop, entity); | ||
} else if (prop.reference && !prop.collection) { | ||
if (prop.reference === ReferenceType.ONE_TO_MANY && !data[p]) { | ||
return entity[p] = new Collection<T>(prop, entity); | ||
} | ||
if (prop.reference === ReferenceType.MANY_TO_MANY && !prop.owner && !found && (!entity[p] || !data[p])) { | ||
return entity[p] = new Collection<T>(prop, entity); | ||
} | ||
if (prop.reference === ReferenceType.MANY_TO_MANY && prop.owner && Utils.isArray(data[p])) { | ||
const items = data[p].map((id: ObjectID) => this.createReference(prop.type, id.toHexString())); | ||
return entity[p] = new Collection<T>(prop, entity, items); | ||
} | ||
if (prop.reference === ReferenceType.MANY_TO_ONE) { | ||
if (data[p] instanceof ObjectID) { | ||
entity[p] = this.createReference(prop.type, data[p]); | ||
entity[p] = this.createReference(prop.type, data[p].toHexString()); | ||
this.em.addToIdentityMap(entity[p]); | ||
} | ||
} else if (data[p] && !prop.reference) { | ||
return; | ||
} | ||
if (prop.reference === ReferenceType.SCALAR && data[p]) { | ||
entity[p] = data[p]; | ||
} | ||
}); | ||
delete entity['_initialized']; | ||
} | ||
@@ -108,3 +127,3 @@ | ||
return meta.constructorParams.map((k: string) => { | ||
if (meta.properties[k].reference && !meta.properties[k].collection && data[k]) { | ||
if (meta.properties[k].reference === ReferenceType.MANY_TO_ONE && data[k]) { | ||
return this.em.getReference<T>(meta.properties[k].type, data[k]); | ||
@@ -111,0 +130,0 @@ } |
@@ -96,4 +96,8 @@ import { Collection as MongoCollection, Db, FilterQuery } from 'mongodb'; | ||
merge<T extends BaseEntity>(entityName: string, data: any): T { | ||
const entity = data instanceof BaseEntity ? data : this.entityFactory.create<T>(entityName, data); | ||
if (!data.id && !data._id) { | ||
throw new Error('You cannot merge entity without id!'); | ||
} | ||
const entity = data instanceof BaseEntity ? data : this.entityFactory.create<T>(entityName, data, true); | ||
if (this.identityMap[`${entityName}-${entity.id}`]) { | ||
@@ -169,9 +173,2 @@ // TODO populate missing references and rehydrate | ||
create<T extends BaseEntity>(entityName: string, data: any): T { | ||
const entity = this.entityFactory.create<T>(entityName, data); | ||
entity['_initialized'] = false; | ||
return entity; | ||
} | ||
addToIdentityMap(entity: BaseEntity) { | ||
@@ -184,3 +181,3 @@ this.identityMap[`${entity.constructor.name}-${entity.id}`] = entity; | ||
const props = this.metadata[entityName].properties; | ||
return property in props && props[property].reference; | ||
return property in props && !!props[property].reference; | ||
} | ||
@@ -187,0 +184,0 @@ |
@@ -34,7 +34,3 @@ import { FilterQuery } from 'mongodb'; | ||
create(data: any): T { | ||
return this.em.create<T>(this.entityName, data); | ||
} | ||
async count(where: any): Promise<number> { | ||
async count(where: any = {}): Promise<number> { | ||
return this.em.count(this.entityName, where); | ||
@@ -41,0 +37,0 @@ } |
@@ -9,4 +9,5 @@ export * from './MikroORM'; | ||
export * from './decorators/ManyToOne'; | ||
export * from './decorators/ManyToMany'; | ||
export * from './decorators/Property'; | ||
export * from './decorators/hooks'; | ||
export { ObjectID } from 'bson'; |
import { Utils } from './Utils'; | ||
import { EntityManager } from './EntityManager'; | ||
import { BaseEntity, EntityMetadata } from './BaseEntity'; | ||
import { Collection } from './Collection'; | ||
import { BaseEntity, EntityMetadata, EntityProperty, ReferenceType } from './BaseEntity'; | ||
@@ -67,14 +66,43 @@ export class UnitOfWork { | ||
if (prop.collection) { | ||
// TODO cascade persist... | ||
delete changeSet.payload[prop.name]; | ||
} else if (prop.reference && changeSet.entity[prop.name]) { | ||
if (prop.reference === ReferenceType.ONE_TO_MANY) { | ||
await this.processOneToMany(changeSet, prop); | ||
} else if (prop.reference === ReferenceType.MANY_TO_MANY) { | ||
await this.processManyToMany(changeSet, prop); | ||
} else if (prop.reference === ReferenceType.MANY_TO_ONE && changeSet.entity[prop.name]) { | ||
await this.processManyToOne(changeSet, prop); | ||
} | ||
} | ||
} | ||
private async processManyToOne(changeSet: ChangeSet, prop: EntityProperty) { | ||
// when new entity found in reference, cascade persist it first so we have its id | ||
if (!changeSet.entity[prop.name]._id) { | ||
const propChangeSet = await this.persist(changeSet.entity[prop.name]); | ||
await this.immediateCommit(propChangeSet); | ||
} | ||
changeSet.payload[prop.name] = changeSet.entity[prop.name]._id; | ||
} | ||
private async processOneToMany(changeSet: ChangeSet, prop: EntityProperty) { | ||
if (changeSet.entity[prop.name].isDirty()) { | ||
// TODO cascade persist... | ||
} | ||
delete changeSet.payload[prop.name]; | ||
} | ||
private async processManyToMany(changeSet: ChangeSet, prop: EntityProperty) { | ||
if (prop.owner && changeSet.entity[prop.name].isDirty()) { | ||
for (const item of changeSet.entity[prop.name].getItems()) { | ||
// when new entity found in reference, cascade persist it first so we have its id | ||
if (!changeSet.entity[prop.name]._id) { | ||
const propChangeSet = await this.persist(changeSet.entity[prop.name]); | ||
await this.immediateCommit(propChangeSet); | ||
if (!item._id) { | ||
const itemChangeSet = await this.persist(item); | ||
await this.immediateCommit(itemChangeSet); | ||
} | ||
} | ||
changeSet.payload[prop.name] = changeSet.entity[prop.name]._id; | ||
} | ||
changeSet.payload[prop.name] = changeSet.entity[prop.name].getIdentifiers(); | ||
} else { | ||
delete changeSet.payload[prop.name]; | ||
} | ||
@@ -103,19 +131,25 @@ } | ||
const prop = meta.properties[p]; | ||
const reference = changeSet.entity[prop.name]; | ||
if (prop.reference && !prop.collection && changeSet.entity[prop.name]) { | ||
if (!changeSet.entity[prop.name]._id) { | ||
const propChangeSet = await this.persist(changeSet.entity[prop.name]); | ||
if (prop.reference === ReferenceType.MANY_TO_ONE && reference) { | ||
if (!reference._id) { | ||
const propChangeSet = await this.persist(reference); | ||
await this.immediateCommit(propChangeSet); | ||
} | ||
changeSet.payload[prop.name] = changeSet.entity[prop.name]._id; | ||
changeSet.payload[prop.name] = reference._id; | ||
} | ||
if (prop.reference && prop.collection) { | ||
if (!changeSet.entity[prop.name]) { // create collection when missing (e.g. when persisting new entity) | ||
changeSet.entity[prop.name] = new Collection(prop, changeSet.entity); | ||
} | ||
if (prop.reference === ReferenceType.ONE_TO_MANY) { | ||
// TODO one to many collection cascade support | ||
// ... | ||
// TODO many to one collection cascade support | ||
reference.dirty = false; | ||
} | ||
if (prop.reference === ReferenceType.MANY_TO_MANY && prop.owner) { | ||
// TODO many to many collection cascade support | ||
// ... | ||
reference.dirty = false; | ||
} | ||
@@ -122,0 +156,0 @@ } |
{ | ||
"name": "mikro-orm", | ||
"version": "0.2.3", | ||
"version": "0.3.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", |
@@ -59,5 +59,5 @@ # mikro-orm | ||
`$ yarn add mikro-orm` | ||
or | ||
or | ||
`$ npm install mikro-orm` | ||
@@ -67,2 +67,4 @@ | ||
For more examples, take a look at `tests/EntityManager.test.ts`. | ||
```typescript | ||
@@ -133,5 +135,10 @@ import { MikroORM, Collection } from 'mikro-orm'; | ||
- support for string id (now we require object id) in EM/repositories | ||
- add query logging | ||
- add nativeUpdate and nativeDelete (without hooks support), allow only entities in EM#remove | ||
- remove references on other entities when deleting entity (e.g. from M:N collection) | ||
## TODO docs | ||
- 1:M / M:1 collections | ||
- many to many collections | ||
- custom repository | ||
@@ -138,0 +145,0 @@ - cascading |
@@ -24,3 +24,3 @@ import { | ||
@OneToMany({ entity: () => Book.name, fk: 'author', cascade: ['persist', 'remove'] }) | ||
@OneToMany({ entity: () => Book.name, fk: 'author' }) | ||
books: Collection<Book>; | ||
@@ -27,0 +27,0 @@ |
@@ -1,4 +0,5 @@ | ||
import { BaseEntity, Entity, ManyToOne, Property } from '../../lib'; | ||
import { BaseEntity, Collection, Entity, ManyToMany, ManyToOne, Property } from '../../lib'; | ||
import { Publisher } from './Publisher'; | ||
import { Author } from './Author'; | ||
import { BookTag } from './BookTag'; | ||
@@ -17,2 +18,5 @@ @Entity({ collection: 'books-table' }) | ||
@ManyToMany({ entity: () => BookTag.name, inversedBy: 'books' }) | ||
tags: Collection<BookTag>; | ||
@Property() | ||
@@ -19,0 +23,0 @@ metaObject: object; |
import { EntityFactory } from '../lib/EntityFactory'; | ||
import { Book } from './entities/Book'; | ||
import { Author } from './entities/Author'; | ||
import { EntityManager } from '../lib'; | ||
import { ReferenceType, EntityManager } from '../lib'; | ||
@@ -33,7 +33,5 @@ const Mock = jest.fn<EntityManager>(() => ({ | ||
expect(metadata[Author.name].properties['books'].type).toBe(Book.name); | ||
expect(metadata[Author.name].properties['books'].reference).toBe(true); | ||
expect(metadata[Author.name].properties['books'].collection).toBe(true); | ||
expect(metadata[Author.name].properties['books'].reference).toBe(ReferenceType.ONE_TO_MANY); | ||
expect(metadata[Book.name].properties['author'].type).toBe(Author.name); | ||
expect(metadata[Book.name].properties['author'].reference).toBe(true); | ||
expect(metadata[Book.name].properties['author'].collection).toBe(false); | ||
expect(metadata[Book.name].properties['author'].reference).toBe(ReferenceType.MANY_TO_ONE); | ||
}); | ||
@@ -40,0 +38,0 @@ |
@@ -1,3 +0,2 @@ | ||
'use strict'; | ||
import { ObjectID } from 'bson'; | ||
import { MikroORM, EntityManager, Collection } from '../lib'; | ||
@@ -8,2 +7,3 @@ import { Author } from './entities/Author'; | ||
import { AuthorRepository } from './repositories/AuthorRepository'; | ||
import { BookTag } from './entities/BookTag'; | ||
@@ -23,5 +23,8 @@ let orm: MikroORM; | ||
}); | ||
}); | ||
beforeEach(async () => { | ||
await orm.em.getRepository<Author>(Author.name).remove({}); | ||
await orm.em.getRepository<Book>(Book.name).remove({}); | ||
await orm.em.getRepository<BookTag>(BookTag.name).remove({}); | ||
await orm.em.getRepository<Publisher>(Publisher.name).remove({}); | ||
@@ -66,2 +69,6 @@ }); | ||
// count test | ||
const count = await authorRepository.count(); | ||
expect(count).toBe(authors.length); | ||
// identity map test | ||
@@ -82,3 +89,3 @@ authors.shift(); // shift the god away, as that entity is detached from IM | ||
], | ||
favouriteBook: { author: god.toObject(jon), title: 'Bible' }, | ||
favouriteBook: { author: { name: 'God' }, title: 'Bible' }, | ||
born: jon.born, | ||
@@ -89,2 +96,4 @@ email: 'snow@wall.st', | ||
expect(jon.toJSON()).toEqual(o); | ||
expect(jon.books.getIdentifiers()).toBeInstanceOf(Array); | ||
expect(jon.books.getIdentifiers()[0]).toBeInstanceOf(ObjectID); | ||
@@ -136,2 +145,98 @@ for (const author of authors) { | ||
test('many to many relation', 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); | ||
await orm.em.persist(book2); | ||
await orm.em.persist(book3, true); | ||
expect(tag1._id).toBeDefined(); | ||
expect(tag2._id).toBeDefined(); | ||
expect(tag3._id).toBeDefined(); | ||
expect(tag4._id).toBeDefined(); | ||
expect(tag5._id).toBeDefined(); | ||
// test inverse side | ||
const tagRepository = orm.em.getRepository<BookTag>(BookTag.name); | ||
let tags = await tagRepository.findAll(); | ||
expect(tags).toBeInstanceOf(Array); | ||
expect(tags.length).toBe(5); | ||
expect(tags[0]).toBeInstanceOf(BookTag); | ||
expect(tags[0].name).toBe('silly'); | ||
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); | ||
orm.em.clear(); | ||
tags = await tagRepository.findAll(); | ||
expect(tags[0].books.isInitialized()).toBe(false); | ||
expect(tags[0].books.isDirty()).toBe(false); | ||
expect(() => tags[0].books.getItems()).toThrowError(/Collection Book\[] of entity BookTag\[\w{24}] not initialized/); | ||
expect(() => tags[0].books.add(book1)).toThrowError(/Collection Book\[] of entity BookTag\[\w{24}] not initialized/); | ||
expect(() => tags[0].books.remove(book1, book2)).toThrowError(/Collection Book\[] of entity BookTag\[\w{24}] not initialized/); | ||
expect(() => tags[0].books.removeAll()).toThrowError(/Collection Book\[] of entity BookTag\[\w{24}] not initialized/); | ||
expect(() => tags[0].books.contains(book1)).toThrowError(/Collection Book\[] of entity BookTag\[\w{24}] not initialized/); | ||
// test M:N lazy init | ||
orm.em.clear(); | ||
await tags[0].books.init(orm.em); | ||
expect(tags[0].books.count()).toBe(2); | ||
expect(tags[0].books.getItems()[0]).toBeInstanceOf(Book); | ||
expect(tags[0].books.getItems()[0]._id).toBeDefined(); | ||
expect(tags[0].books.getItems()[0].isInitialized()).toBe(true); | ||
// test M:N lazy init | ||
orm.em.clear(); | ||
let book = await orm.em.findOne<Book>(Book.name, { tags: tag1._id }); | ||
expect(book.tags.isInitialized()).toBe(true); // owning side is always initialized | ||
expect(book.tags.count()).toBe(2); | ||
expect(book.tags.getItems()[0]).toBeInstanceOf(BookTag); | ||
expect(book.tags.getItems()[0]._id).toBeDefined(); | ||
expect(book.tags.getItems()[0].isInitialized()).toBe(false); | ||
await book.tags.init(orm.em); | ||
expect(book.tags.getItems()[0].isInitialized()).toBe(true); | ||
// test collection CRUD | ||
// remove | ||
expect(book.tags.count()).toBe(2); | ||
book.tags.remove(tag1); | ||
await orm.em.persist(book, true); | ||
orm.em.clear(); | ||
book = await orm.em.findOne<Book>(Book.name, book._id); | ||
expect(book.tags.count()).toBe(1); | ||
// add | ||
book.tags.add(tag1); | ||
await orm.em.persist(book, true); | ||
orm.em.clear(); | ||
book = await orm.em.findOne<Book>(Book.name, book._id); | ||
expect(book.tags.count()).toBe(2); | ||
// contains | ||
expect(book.tags.contains(tag1)).toBe(true); | ||
expect(book.tags.contains(tag2)).toBe(false); | ||
expect(book.tags.contains(tag3)).toBe(true); | ||
expect(book.tags.contains(tag4)).toBe(false); | ||
expect(book.tags.contains(tag5)).toBe(false); | ||
// removeAll | ||
book.tags.removeAll(); | ||
await orm.em.persist(book, true); | ||
orm.em.clear(); | ||
book = await orm.em.findOne<Book>(Book.name, book._id); | ||
expect(book.tags.count()).toBe(0); | ||
}); | ||
test('hooks', async () => { | ||
@@ -138,0 +243,0 @@ Author.beforeDestroyCalled = 0; |
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
114881
68
2854
145