// tslint:disable only-arrow-functions import { cloneDeep } from 'lodash'; import { isObject } from 'lodash'; import * as Datastore from 'nedb/browser-version/out/nedb'; const me = self as DedicatedWorkerGlobalScope; me.onmessage = handleMessage; const _DB_MAP: {[dbid: string]: Database} = {}; declare type Callback = (err?: Error, val?: any) => void; class Database { private db: Datastore; private RES_TO_DB_ID_MAP: {[id: string]: string}; constructor() { this.db = new Datastore(); this.RES_TO_DB_ID_MAP = {}; // Stick an index on __id - our id field. this.db.ensureIndex({ fieldName: '__id' }, function (err) { if (err) { throw err; } }); } public remove(id: string, callback: Callback) { let dbid = this.RES_TO_DB_ID_MAP[id]; if (dbid) { delete this.RES_TO_DB_ID_MAP[id]; this.db.remove({_id: dbid}, {multi: true}, function(err) { callback(err); }); } else { callback(new Error(`No object to remove with id ${id}`)); } } public update(doc: any, callback: Callback) { // We need to transform the resource by replacing the _id field (nedb uses its own id in that // place). Instead call it __id doc.__id = doc._id; doc.__$id = doc.$id; delete doc._id; delete doc.$id; let dbid = this.RES_TO_DB_ID_MAP[doc.__$id]; if (dbid) { this.db.update({_id: dbid}, doc, {}, function(err) { callback(err); }); } else { this.db.insert(doc, (err, newDoc) => { if (err) { return callback(err); } this.RES_TO_DB_ID_MAP[doc.__$id] = newDoc._id; callback(); }); } } public query(qry: any, callback: Callback) { let find = createDbFind(qry.find); let limit = qry.limit; let skip = qry.skip; let sort = qry.sort; let cur = this.db.find(find); if (sort) { cur = cur.sort(sort); } if (skip) { cur = cur.skip(skip); } if (limit) { // We go + 1 so we can tell if there are any more results cur = cur.limit(limit + 1); } cur.exec(function(err, docs: any[]) { // Now go from our docs to the ids let ids = docs.map(function(doc: any) { return doc.__$id as string; }); callback(err, ids); }); } } // Convert any _id searches to __id (which is where our id moved to) function _createDbFind(qry: any) { if (Array.isArray(qry)) { qry.forEach(function(val) { _createDbFind(val); }); } else if (isObject(qry)) { for (let key of Object.keys(qry)) { let val = qry[key]; // Convert the _id to __id searches if (key === '_id') { qry.__id = val; delete qry._id; } _createDbFind(val); } } } function createDbFind(qry: any) { // Converts the query into the form required for a db search. First clone the object qry = cloneDeep(qry); _createDbFind(qry); return qry; } function createCallback(id: string) { return function callback(err?: Error, resp?: any) { me.postMessage({ id, data: resp, error: err && err.message, }); }; } function getDatabase(dbid: string) { let db = _DB_MAP[dbid]; if (!db) { db = new Database(); _DB_MAP[dbid] = db; } return db; } function handleMessage(event: MessageEvent) { let data = event.data; let dbid = data.dbid; let id = data.id; let args = data.args; let cb = createCallback(id); let db = getDatabase(dbid); switch (data.fnName) { case 'update': db.update(args[0], cb); break; case 'remove': db.remove(args[0], cb); break; case 'query': db.query(args[0], cb); break; default: cb(new Error(`No such method ${data.fnName}`)); break; } }