UNPKG

blow-data

Version:
217 lines (186 loc) 6.39 kB
'use strict'; import {isUndefined, isNull, isObject} from 'util'; import {Observable} from 'rxjs'; import * as mongodb from 'mongodb'; import {IQueryWhere, IQuery, Query, IQueryObject} from 'blow-query'; import {Adapter} from './Adapter' import { IPersistedModelConstructor, IPersistedAdapter, IModelMetadata } from '../interfaces'; const DEFAULT_ID_PROPERTY_NAME = '_id'; const DEFAULT_ID_PROPERTY_TYPE = 'string'; function connect(url) { return Observable.create(observer => { mongodb.MongoClient.connect(url, (err, db) => { if (err) { observer.error(err); } else { observer.next(db); } }); }) } export class MongoDBAdapter extends Adapter implements IPersistedAdapter { protected _db: mongodb.Db; get idPropertyName(): string { return DEFAULT_ID_PROPERTY_NAME; } get idPropertyType(): any { return DEFAULT_ID_PROPERTY_TYPE; } protected _connect(): Observable<MongoDBAdapter> { return connect(MongoDBAdapter.getConnectionUrl(this._options)) .map(db => { this._db = db; return this; }); } protected _collection(metadata: IModelMetadata): mongodb.Collection { return this._db.collection(metadata.pluralName); } protected _prepareQuery(query: IQuery | IQueryObject): IQueryObject { if (query) { if (query instanceof Query) { query = (<IQuery>query).toJSON(); } } return <IQueryObject>query; } count(metadata: IModelMetadata, where?: IQueryWhere): Observable<number> { return Observable.from(this._collection(metadata).count(where)); } create(metadata: IModelMetadata, data: any): Observable<any> { return Observable.from(this._collection(metadata).insertOne(MongoDBAdapter.toDB(metadata, data))) .map(result => MongoDBAdapter.fromDB(metadata, result['ops'][0])); } destroy(metadata: IModelMetadata, where?: IQueryWhere): Observable<number> { return Observable.from(this._collection(metadata).deleteMany(where)) .map(result => result['deletedCount']); } destroyById(metadata: IModelMetadata, id: any): Observable<boolean> { return this.destroy(metadata, MongoDBAdapter.buildWhereWithId(metadata, id)).map(count => !!count); } exists(metadata: IModelMetadata, id: any): Observable<boolean> { return this.findById(metadata, id).map(row => !!row); } // find(metadata: IModelMetadata, query?: IQuery | IQueryObject): Observable<any> { const q: IQueryObject = this._prepareQuery(query); let exec = this._collection(metadata).find(q.where) if (q.limit) { exec = exec.limit(q.limit); } if (q.skip) { exec = exec.skip(q.skip); } if(q.sort) { exec = exec.sort(q.sort); } return Observable.from(exec.toArray()) .mergeMap(rows => Observable.from(rows)) .map(row => MongoDBAdapter.fromDB(metadata, row)); } findOne(metadata: IModelMetadata, query?: IQuery | IQueryObject): Observable<any> { query = this._prepareQuery(query); query['limit'] = 1; return this.find(metadata, query); } findById(metadata: IModelMetadata, id: any): Observable<any> { const where = MongoDBAdapter.buildWhereWithId(metadata, id); return this.find(metadata, {where, limit: 1}); } findOrCreate(metadata: IModelMetadata, where: IQueryWhere, data: any): Observable<any> { return Observable.create(observer => { const emit = row => { observer.next(row); observer.complete(); } this.findOne(metadata, {where}) .subscribe(emit, err => observer.error(err), () => { this.create(metadata, MongoDBAdapter.toDB(metadata, data)) .subscribe(emit); }); }); } update(metadata: IModelMetadata, where: IQueryWhere, data: any): Observable<number> { return Observable.from(this._collection(metadata).updateMany(where, {$set: data})) .map(result => result['modifiedCount']); } updateOrCreate(metadata: IModelMetadata, data: any): Observable<any> { const idName = metadata.idProperty.name; if(data[idName]) { return this.exists(metadata, data[idName]).mergeMap(exists => { if(exists) { return this.update(metadata, {_id: data[idName]}, data).mapTo(data); } else { return this.create(metadata, data); } }); } else { return this.create(metadata, data); } } static toDB(metadata: IModelMetadata, data: any): any { const idName = metadata.idProperty.name; const idValue = data[idName]; if (isNull(idValue)) { delete data[idName]; } else { data[DEFAULT_ID_PROPERTY_NAME] = this.buildId(idValue); if (idName !== DEFAULT_ID_PROPERTY_NAME) { delete data[idName]; } } return data; } static fromDB(metadata: IModelMetadata, data: any): any { const idName = metadata.idProperty.name; if (!data) { return null; } if(data[DEFAULT_ID_PROPERTY_NAME]) { data[idName] = data[DEFAULT_ID_PROPERTY_NAME].toHexString(); if (idName !== DEFAULT_ID_PROPERTY_NAME) { delete data[DEFAULT_ID_PROPERTY_NAME]; } } return data; } static buildWhereWithId(metadata: IModelMetadata, id): { [key: string]: any } { const idKey = metadata.idProperty.name; const where = {}; where[idKey] = id; return this.buildWhere(metadata, where); } static buildWhere(metadata: IModelMetadata, where: {[key: string]: any}): {[key: string]: any} { where = where || {}; const idKey = metadata.idProperty.name; return Object.keys(where).reduce((w, k) => { if (k === idKey) { w[DEFAULT_ID_PROPERTY_NAME] = this.buildId(where[k]); if (idKey !== DEFAULT_ID_PROPERTY_NAME) { delete w[idKey]; } } else { w[k] = where[k]; } return w; }, {}); } static buildId(id): mongodb.ObjectID { if (id instanceof mongodb.ObjectID) { return id; } return new mongodb.ObjectID(id); } static getConnectionUrl(options): string { if (!isUndefined(options.url)) { return options.url; } else { const auth = (options.user && options.password) ? [options.user, options.password].join(':') : ''; return 'mongodb://' + (auth ? auth + '@' : '') + options.host + ':' + options.port + '/' + options.dbname; } } }