import { Utils } from '../lib/Utils'; import db from '../models/Database'; import * as mongodb from 'mongodb'; import { ObjectId, FindCursor } from 'mongodb'; export class ObjectFactory { static create(__type: any, o: any): any { if (__type.fromObject) { return __type.fromObject(o); } const newObject = new __type(); return Object.assign(newObject, o); } } export class MongoObject { protected static __type; // The current class protected static __collectionName: string; // The Mongo collection name protected static __idField: string = "_id"; // Default id used to findOne protected static __wlistJsonAttrs: string[] = []; // Whitelist of attributes to serialize. // Json serialization. private toJsonRepr(): object { return Utils.pick(this, (this.constructor).__wlistJsonAttrs); } toJson(): string { return JSON.stringify(this.toJsonRepr()); } /// Find family of methods static async findOne(id: string | ObjectId | mongodb.Filter, options?: mongodb.FindOptions): Promise { const q = (typeof id === 'string' || id instanceof ObjectId) ? { [this.__idField]: id } : id; const o = await db.collection(this.__collectionName).findOne(q, options); if (o) { return ObjectFactory.create(this.__type, o); } return null; } static async findOneAndUpdate(filter: mongodb.Filter, update: Object, options: mongodb.FindOneAndUpdateOptions = {}): Promise { const o = await db.collection(this.__collectionName).findOneAndUpdate(filter, update, options); if (o && o.value) { return ObjectFactory.create(this.__type, o.value); } return null; } static find(query: mongodb.Filter = {}, options?: mongodb.FindOptions): HfCursor { const cursor = db.collection(this.__collectionName).find(query, options); return HfCursor.cast(cursor as any, this.__type); } } export class HfCursor extends FindCursor { protected __type; static cast(cursor: FindCursor, type: any): HfCursor { // “The use of __proto__ is controversial, and has been discouraged.” // see stackoverflow.com/a/32186367 // see developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto (cursor).__proto__ = HfCursor.prototype; (cursor).__type = type; return cursor as unknown as HfCursor; } toArray(): Promise { return super.toArray().then((objs) => { return objs.map((o) => { return ObjectFactory.create(this.__type, o); }); }); } forEach(__iterator: (doc: T) => boolean | void): Promise { return super.forEach((o) => { const newObject = ObjectFactory.create(this.__type, o); return __iterator(newObject); }); } on(event: string, listener: (...args) => void): this { if (event === 'data') { super.on('data', (o) => { const newObject = ObjectFactory.create(this.__type, o); listener(newObject); }); } else { super.on(event, listener); } return this; } once(event: string, listener: (...args) => void): this { if (event === 'data') { super.once('data', (o) => { const newObject = ObjectFactory.create(this.__type, o); listener(newObject); }); } else { super.once(event, listener); } return this; } // // Below: cursor methods are only here to make Typescript // know that they return the HfCursor object itself. // (We have checked that the mongo driver does the right thing underneath) // // limit(value: number): HfCursor { // return super.limit(value) as HfCursor; // } // skip(value: number): HfCursor { // return super.skip(value) as HfCursor; // } // sort(keyOrList: string | Object[] | Object, direction?: number): HfCursor { // return super.sort(keyOrList, direction) as HfCursor; // } // stream(options?: { transform?: Function }): HfCursor { // return super.stream(options) as HfCursor; // } }