import { isObject } from 'lodash'; import { EventEmitter } from '../../emitter'; import { Database, LocalModel } from '../../types'; import { uuid } from '../../utils'; // These are the operators nedb supports const simpleOperators = { $and: true, $exists: true, $gt: true, $gte: true, $in: true, $lt: true, $lte: true, $ne: true, $nin: true, $not: true, $or: true, $regex: true, $size: true, }; interface Callback { (err?: Error, data?: any): void; } export function createWebWorkerDbFactory(worker: Worker) { let workerEmitter = new EventEmitter(); worker.onmessage = (event: MessageEvent) => { workerEmitter.emit('message', event); }; class WebWorkerDb extends Database { private worker: Worker; private cbMaps: {[id: string]: Callback}; private dbid: string; constructor() { super(); this.worker = worker; this.cbMaps = {}; this.dbid = uuid(); workerEmitter.addListener('message', (event: MessageEvent) => { let data = event.data.data; let error = event.data.error; let id = event.data.id; let cb = this.cbMaps[id]; if (cb) { cb(error, data); } }); } public update(res: LocalModel) { if (res.$deleted) { return this.runWorkerFunction('remove', res.$id); } else { let doc = res.$toObject(); // Stick on the internal id doc.$id = res.$id; return this.runWorkerFunction('update', doc); } } public query(qry: any) { return this.runWorkerFunction('query', qry) as Promise; } public qryIsSimple(qry: any): boolean { let simple = true; if (Array.isArray(qry)) { qry.forEach(val => { let kosher = this.qryIsSimple(val); if (!kosher) { simple = false; return false; } }); } else if (isObject(qry)) { for (let key of Object.keys(qry)) { let val = qry[key]; // The key is fine if it doesn't begin with $ or is a simple operator let kosherKey = (key[0] !== '$') || simpleOperators[key]; if (!kosherKey) { simple = false; break; } let valKosher = this.qryIsSimple(val); if (!valKosher) { simple = false; break; } } } return simple; } private runWorkerFunction(fnName: string, ...args: any[]) { return new Promise((resolve, reject) => { let id = uuid(); this.worker.postMessage({ fnName, id, dbid: this.dbid, args, }); this.cbMaps[id] = (err, resp) => { delete this.cbMaps[id]; if (err) { reject(err); } else { resolve(resp); } }; }); } } return () => { return new WebWorkerDb() as Database; }; }