Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

mikro-orm

Package Overview
Dependencies
Maintainers
1
Versions
3406
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mikro-orm - npm Package Compare versions

Comparing version 0.2.3 to 0.3.0

dist/decorators/ManyToMany.d.ts

13

dist/BaseEntity.d.ts

@@ -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 = {}));

18

dist/Collection.d.ts
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;

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc