blow-data
Version:
Data access layer for Blow.
217 lines (186 loc) • 6.39 kB
text/typescript
'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;
}
}
}