UNPKG

@openhps/mongodb

Version:

Open Hybrid Positioning System - MongoDB Database component

234 lines (218 loc) 7.85 kB
import { DataSerializer, DataServiceDriver, FilterQuery, FindOptions, Constructor, DataSerializerUtils, } from '@openhps/core'; import { MongoClient, Db, Collection } from 'mongodb'; import { DatabaseOptions } from './DatabaseOptions'; /** * Data service driver for MongoDB * * ### Usage * You can use a ```MongoDataServiceDriver``` in a data service such as a ```DataObjectService``` to * use the driver to store data objects of a specific type. * * ```typescript * import { ModelBuilder, DataObjectService, DataObject, ReferenceSpace } from '@openhps/core'; * import { MongoDataServiceDriver } from '@openhps/mongodb'; * * ModelBuilder.create() * .addService(new DataObjectService(new MongoDataServiceDriver(DataObject, { * dbURL: "mongodb://mongo:27017", * dbName: "myobjects" * }))) * .addService(new DataObjectService(new MongoDataServiceDriver(ReferenceSpace, { * dbURL: "mongodb://mongo:27017", * dbName: "myspaces" * }))) * .addShape(\/* ... *\/) * .build().then(model => { * \/* ... *\/ * }); * ``` * * @category Service */ export class MongoDataServiceDriver<I, T> extends DataServiceDriver<I, T> { protected options: DatabaseOptions; private _db: Db; private _client: MongoClient; private _collection: Collection<any>; constructor(dataType: Constructor<T>, options: DatabaseOptions) { super(dataType); this.options = options; this.options.collectionName = this.options.collectionName || this.uid.toLowerCase(); this.once('build', this.connect.bind(this)); this.once('destroy', this.disconnect.bind(this)); } /** * Connect to the MongoDB service * * @returns {Promise<void>} Promise of connection */ connect(): Promise<void> { if (this._client !== undefined) { return Promise.resolve(); } return new Promise((resolve, reject) => { MongoClient.connect(this.options.dbURL, { auth: this.options.auth, }) .then((client: MongoClient) => { this._client = client; this._db = client.db(this.options.dbName); this._collection = this._db.collection(this.options.collectionName); const indexes: Array<Promise<void>> = Array.from( DataSerializerUtils.getRootMetadata(this.dataType).dataMembers.values(), ) .filter((dataMember: any) => dataMember.index) .map(this.createIndex.bind(this)); return Promise.all(indexes); }) .then(() => resolve()) .catch(reject); }); } /** * Create a new index * * @param {any} dataMember Data member to create index for * @returns {Promise<void>} Index created promise */ createIndex(dataMember: any): Promise<void> { return new Promise((resolve, reject) => { this._collection .createIndex(dataMember.key, { unique: dataMember.unique ? true : false, }) .then(() => { resolve(); }) .catch(reject); }); } /** * Disconnect from the MongoDB database * * @returns {Promise<void>} Promise of disconnect */ disconnect(): Promise<void> { if (this._client === undefined) { return Promise.resolve(); } return this._client.close(); } findByUID(id: I): Promise<T> { return new Promise((resolve, reject) => { this.findOne({ _id: id }) .then((object) => { if (object === undefined) { return reject(`${this.dataType.name} with identifier #${id} not found!`); } resolve(object); }) .catch(reject); }); } findOne(query?: FilterQuery<T>, options?: FindOptions): Promise<T> { return new Promise<T>((resolve, reject) => { this._checkIfReady(reject); this._collection .findOne(query, options) .then((serializedObject) => { if (!serializedObject) { return resolve(undefined); } resolve(DataSerializer.deserialize(serializedObject, this.dataType as any)); }) .catch(reject); }); } findAll(query?: FilterQuery<T>, options?: FindOptions): Promise<T[]> { return new Promise<T[]>((resolve, reject) => { this._checkIfReady(reject); this._collection .find(query, options) .toArray() .then((result: any) => { const deserializedResults: any[] = []; result.forEach((r: any) => { deserializedResults.push(DataSerializer.deserialize(r, this.dataType as any)); }); resolve(deserializedResults); }) .catch(reject); }); } insert(id: I, object: T): Promise<T> { return new Promise<T>((resolve, reject) => { this._checkIfReady(reject); this._collection .findOne({ _id: id }) .then((existingObject) => { const preparedObject = DataSerializer.serialize(object); preparedObject._id = id; if (!existingObject) { this._collection .insertOne(preparedObject) .then(() => { resolve(object); }) .catch(() => { // Ignore insert error - possible race condition resolve(object); }); } else { this._collection .updateOne({ _id: id }, { $set: preparedObject }) .then(() => { resolve(object); }) .catch(reject); } }) .catch((ex) => { reject(ex); }); }); } count(query?: FilterQuery<T>): Promise<number> { return new Promise((resolve, reject) => { this._checkIfReady(reject); this._collection.count(query).then(resolve).catch(reject); }); } delete(id: I): Promise<void> { return new Promise<void>((resolve, reject) => { this._checkIfReady(reject); this._collection .deleteOne({ _id: id }) .then(() => { resolve(); }) .catch(reject); }); } deleteAll(query?: FilterQuery<T>): Promise<void> { return new Promise<void>((resolve, reject) => { this._checkIfReady(reject); this._collection .deleteMany(query) .then(() => { resolve(); }) .catch(reject); }); } private _checkIfReady(reject: (reason: any) => void): void { if (this._collection === undefined) { return reject( new Error(`MongoDB connection not ready! Most likely the service was accessed before the connection was completed.`), ); } } }