2017-07-19 00:19:56 +00:00
|
|
|
// @flow
|
2017-07-19 01:55:47 +00:00
|
|
|
import type {BaseModel} from '../models/index';
|
2017-11-17 12:10:37 +00:00
|
|
|
import * as models from '../models/index';
|
2016-10-02 20:57:00 +00:00
|
|
|
import electron from 'electron';
|
|
|
|
import NeDB from 'nedb';
|
|
|
|
import fsPath from 'path';
|
2016-11-10 05:56:23 +00:00
|
|
|
import {DB_PERSIST_INTERVAL} from './constants';
|
2017-06-30 03:30:22 +00:00
|
|
|
import {initModel} from '../models';
|
2016-10-02 20:57:00 +00:00
|
|
|
|
|
|
|
export const CHANGE_INSERT = 'insert';
|
|
|
|
export const CHANGE_UPDATE = 'update';
|
|
|
|
export const CHANGE_REMOVE = 'remove';
|
2016-09-08 06:54:35 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
const database = {};
|
|
|
|
const db = {
|
|
|
|
_empty: true
|
|
|
|
};
|
2016-09-21 21:46:42 +00:00
|
|
|
|
|
|
|
// ~~~~~~~ //
|
|
|
|
// HELPERS //
|
|
|
|
// ~~~~~~~ //
|
2016-08-15 17:04:36 +00:00
|
|
|
|
2016-11-10 01:15:27 +00:00
|
|
|
function allTypes () {
|
|
|
|
return Object.keys(db);
|
|
|
|
}
|
2016-04-16 23:24:57 +00:00
|
|
|
|
2016-07-23 08:22:52 +00:00
|
|
|
function getDBFilePath (modelType) {
|
2016-08-04 04:12:45 +00:00
|
|
|
// NOTE: Do not EVER change this. EVER!
|
2017-11-18 22:47:54 +00:00
|
|
|
const {app} = electron.remote || electron;
|
|
|
|
const basePath = app.getPath('userData');
|
2016-07-23 08:22:52 +00:00
|
|
|
return fsPath.join(basePath, `insomnia.${modelType}.db`);
|
2016-04-28 00:04:29 +00:00
|
|
|
}
|
|
|
|
|
2017-11-18 22:47:54 +00:00
|
|
|
export async function initClient () {
|
|
|
|
electron.ipcRenderer.on('db.changes', async (e, changes) => {
|
|
|
|
for (const fn of changeListeners) {
|
|
|
|
await fn(changes);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
console.debug('[db] Initialized DB client');
|
|
|
|
}
|
|
|
|
|
2017-11-10 11:29:18 +00:00
|
|
|
export async function init (
|
|
|
|
types: Array<string>,
|
|
|
|
config: Object = {},
|
|
|
|
forceReset: boolean = false
|
|
|
|
) {
|
2017-11-18 22:47:54 +00:00
|
|
|
if (forceReset) {
|
|
|
|
changeListeners = [];
|
|
|
|
for (const attr of Object.keys(db)) {
|
|
|
|
if (attr === '_empty') {
|
2017-11-17 12:10:37 +00:00
|
|
|
continue;
|
2016-12-31 19:32:50 +00:00
|
|
|
}
|
2017-11-17 12:10:37 +00:00
|
|
|
|
2017-11-18 22:47:54 +00:00
|
|
|
delete db[attr];
|
|
|
|
}
|
|
|
|
}
|
2017-11-17 12:10:37 +00:00
|
|
|
|
2017-11-18 22:47:54 +00:00
|
|
|
// Fill in the defaults
|
|
|
|
for (const modelType of types) {
|
|
|
|
if (db[modelType]) {
|
|
|
|
console.warn(`[db] Already initialized DB.${modelType}`);
|
|
|
|
continue;
|
2016-12-30 23:06:27 +00:00
|
|
|
}
|
|
|
|
|
2017-11-18 22:47:54 +00:00
|
|
|
const filePath = getDBFilePath(modelType);
|
|
|
|
const collection = new NeDB(Object.assign({
|
|
|
|
autoload: true,
|
|
|
|
filename: filePath
|
|
|
|
}, config));
|
2016-05-11 05:43:51 +00:00
|
|
|
|
2017-11-18 22:47:54 +00:00
|
|
|
collection.persistence.setAutocompactionInterval(DB_PERSIST_INTERVAL);
|
2017-07-19 01:55:47 +00:00
|
|
|
|
2017-11-18 22:47:54 +00:00
|
|
|
db[modelType] = collection;
|
2016-11-10 01:15:27 +00:00
|
|
|
}
|
2017-11-18 22:47:54 +00:00
|
|
|
|
|
|
|
delete db._empty;
|
|
|
|
|
|
|
|
electron.ipcMain.on('db.fn', async (e, fnName, replyChannel, ...args) => {
|
|
|
|
const result = await database[fnName](...args);
|
|
|
|
e.sender.send(replyChannel, result);
|
|
|
|
});
|
|
|
|
|
|
|
|
console.debug(`[db] Initialized DB at ${getDBFilePath('$TYPE')}`);
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-07-16 07:22:08 +00:00
|
|
|
|
2016-09-22 03:32:12 +00:00
|
|
|
// ~~~~~~~~~~~~~~~~ //
|
|
|
|
// Change Listeners //
|
|
|
|
// ~~~~~~~~~~~~~~~~ //
|
|
|
|
|
|
|
|
let bufferingChanges = false;
|
|
|
|
let changeBuffer = [];
|
|
|
|
let changeListeners = [];
|
|
|
|
|
2017-07-19 00:19:56 +00:00
|
|
|
export function onChange (callback: Function): void {
|
2016-09-22 03:32:12 +00:00
|
|
|
changeListeners.push(callback);
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-04-26 07:29:24 +00:00
|
|
|
|
2017-07-19 00:19:56 +00:00
|
|
|
export function offChange (callback: Function): void {
|
2016-09-22 03:32:12 +00:00
|
|
|
changeListeners = changeListeners.filter(l => l !== callback);
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-09-22 03:32:12 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
export const bufferChanges = database.bufferChanges = function (millis: number = 1000): void {
|
|
|
|
if (db._empty) {
|
|
|
|
_send('bufferChanges', ...arguments);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-09-22 03:32:12 +00:00
|
|
|
bufferingChanges = true;
|
2017-11-17 12:10:37 +00:00
|
|
|
setTimeout(database.flushChanges, millis);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const flushChanges = database.flushChanges = async function (): Promise<void> {
|
|
|
|
if (db._empty) return _send('flushChanges', ...arguments);
|
2016-09-22 03:32:12 +00:00
|
|
|
|
|
|
|
bufferingChanges = false;
|
|
|
|
const changes = [...changeBuffer];
|
|
|
|
changeBuffer = [];
|
|
|
|
|
2016-09-22 19:44:28 +00:00
|
|
|
if (changes.length === 0) {
|
|
|
|
// No work to do
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
// Notify local listeners too
|
2017-01-09 21:59:52 +00:00
|
|
|
for (const fn of changeListeners) {
|
|
|
|
await fn(changes);
|
|
|
|
}
|
2017-11-17 12:10:37 +00:00
|
|
|
|
|
|
|
// Notify remote listeners
|
|
|
|
const windows = electron.BrowserWindow.getAllWindows();
|
|
|
|
for (const window of windows) {
|
|
|
|
window.webContents.send('db.changes', changes);
|
|
|
|
}
|
|
|
|
};
|
2016-04-26 07:29:24 +00:00
|
|
|
|
2017-07-19 01:55:47 +00:00
|
|
|
async function notifyOfChange (event: string, doc: BaseModel, fromSync: boolean): Promise<void> {
|
2016-10-24 23:30:37 +00:00
|
|
|
changeBuffer.push([event, doc, fromSync]);
|
2016-09-22 03:32:12 +00:00
|
|
|
|
|
|
|
// Flush right away if we're not buffering
|
|
|
|
if (!bufferingChanges) {
|
2017-11-17 12:10:37 +00:00
|
|
|
await database.flushChanges();
|
2016-09-22 03:32:12 +00:00
|
|
|
}
|
2016-09-08 06:54:35 +00:00
|
|
|
}
|
|
|
|
|
2016-09-22 03:32:12 +00:00
|
|
|
// ~~~~~~~ //
|
|
|
|
// Helpers //
|
|
|
|
// ~~~~~~~ //
|
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
export const getMostRecentlyModified = database.getMostRecentlyModified = async function (
|
2017-07-19 00:19:56 +00:00
|
|
|
type: string,
|
|
|
|
query: Object = {}
|
2017-07-19 01:55:47 +00:00
|
|
|
): Promise<BaseModel | null> {
|
2017-11-17 12:10:37 +00:00
|
|
|
if (db._empty) return _send('getMostRecentlyModified', ...arguments);
|
|
|
|
|
|
|
|
const docs = await database.findMostRecentlyModified(type, query, 1);
|
2016-11-27 21:42:38 +00:00
|
|
|
return docs.length ? docs[0] : null;
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
2016-11-27 21:42:38 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
export const findMostRecentlyModified = database.findMostRecentlyModified = async function (
|
2017-07-19 00:19:56 +00:00
|
|
|
type: string,
|
|
|
|
query: Object = {},
|
|
|
|
limit: number | null = null
|
2017-07-19 01:55:47 +00:00
|
|
|
): Promise<Array<BaseModel>> {
|
2017-11-17 12:10:37 +00:00
|
|
|
if (db._empty) return _send('findMostRecentlyModified', ...arguments);
|
|
|
|
|
2016-09-08 06:54:35 +00:00
|
|
|
return new Promise(resolve => {
|
2017-09-13 06:11:49 +00:00
|
|
|
db[type].find(query).sort({modified: -1}).limit(limit).exec(async (err, rawDocs) => {
|
2017-03-03 20:09:08 +00:00
|
|
|
if (err) {
|
|
|
|
console.warn('[db] Failed to find docs', err);
|
|
|
|
resolve([]);
|
2017-06-30 03:30:22 +00:00
|
|
|
return;
|
2017-03-03 20:09:08 +00:00
|
|
|
}
|
2017-06-30 03:30:22 +00:00
|
|
|
|
2017-09-13 06:11:49 +00:00
|
|
|
const docs = [];
|
|
|
|
for (const rawDoc of rawDocs) {
|
|
|
|
docs.push(await initModel(type, rawDoc));
|
|
|
|
}
|
2017-06-30 03:30:22 +00:00
|
|
|
|
|
|
|
resolve(docs);
|
2017-03-03 20:09:08 +00:00
|
|
|
});
|
|
|
|
});
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
2016-09-02 05:45:12 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
export const find = database.find = async function <T: BaseModel> (
|
2017-07-19 00:19:56 +00:00
|
|
|
type: string,
|
|
|
|
query: Object = {},
|
|
|
|
sort: Object = {created: 1}
|
2017-07-19 01:55:47 +00:00
|
|
|
): Promise<Array<T>> {
|
2017-11-17 12:10:37 +00:00
|
|
|
if (db._empty) return _send('find', ...arguments);
|
|
|
|
|
2016-07-23 08:22:52 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-09-13 06:11:49 +00:00
|
|
|
db[type].find(query).sort(sort).exec(async (err, rawDocs) => {
|
2016-07-23 08:22:52 +00:00
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
|
|
|
|
2017-09-13 06:11:49 +00:00
|
|
|
const docs = [];
|
|
|
|
for (const rawDoc of rawDocs) {
|
|
|
|
docs.push(await initModel(type, rawDoc));
|
|
|
|
}
|
2016-07-23 08:22:52 +00:00
|
|
|
|
|
|
|
resolve(docs);
|
|
|
|
});
|
2016-07-19 16:59:26 +00:00
|
|
|
});
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
2016-07-19 16:59:26 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
export const all = database.all = async function <T: BaseModel> (type: string): Promise<Array<T>> {
|
|
|
|
if (db._empty) return _send('all', ...arguments);
|
|
|
|
|
|
|
|
return database.find(type);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const getWhere = database.getWhere = async function <T: BaseModel> (
|
|
|
|
type: string,
|
|
|
|
query: Object
|
|
|
|
): Promise<T | null> {
|
|
|
|
if (db._empty) return _send('getWhere', ...arguments);
|
2016-04-26 07:29:24 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
const docs = await database.find(type, query);
|
2017-03-30 18:52:07 +00:00
|
|
|
return docs.length ? docs[0] : null;
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export const get = database.get = async function <T: BaseModel> (
|
|
|
|
type: string,
|
|
|
|
id: string
|
|
|
|
): Promise<T | null> {
|
|
|
|
if (db._empty) return _send('get', ...arguments);
|
2016-07-19 16:59:26 +00:00
|
|
|
|
2017-04-21 04:30:52 +00:00
|
|
|
// Short circuit IDs used to represent nothing
|
|
|
|
if (!id || id === 'n/a') {
|
|
|
|
return null;
|
2017-07-19 01:55:47 +00:00
|
|
|
} else {
|
2017-11-17 12:10:37 +00:00
|
|
|
return database.getWhere(type, {_id: id});
|
2017-04-21 04:30:52 +00:00
|
|
|
}
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export const count = database.count = async function (
|
|
|
|
type: string,
|
|
|
|
query: Object = {}
|
|
|
|
): Promise<number> {
|
|
|
|
if (db._empty) return _send('count', ...arguments);
|
2016-07-19 04:01:31 +00:00
|
|
|
|
2016-07-23 08:22:52 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
db[type].count(query, (err, count) => {
|
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
2016-05-11 05:43:51 +00:00
|
|
|
|
2016-07-23 08:22:52 +00:00
|
|
|
resolve(count);
|
|
|
|
});
|
|
|
|
});
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
2016-04-16 23:24:57 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
export const upsert = database.upsert = async function (
|
|
|
|
doc: BaseModel,
|
|
|
|
fromSync: boolean = false
|
|
|
|
): Promise<BaseModel> {
|
|
|
|
if (db._empty) return _send('upsert', ...arguments);
|
|
|
|
|
|
|
|
const existingDoc = await database.get(doc.type, doc._id);
|
2016-10-28 21:27:05 +00:00
|
|
|
if (existingDoc) {
|
2017-11-17 12:10:37 +00:00
|
|
|
return database.update(doc, fromSync);
|
2016-10-28 21:27:05 +00:00
|
|
|
} else {
|
2017-11-17 12:10:37 +00:00
|
|
|
return database.insert(doc, fromSync);
|
2016-10-28 21:27:05 +00:00
|
|
|
}
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export const insert = database.insert = async function <T: BaseModel> (
|
|
|
|
doc: T,
|
|
|
|
fromSync: boolean = false
|
|
|
|
): Promise<T> {
|
|
|
|
if (db._empty) return _send('insert', ...arguments);
|
2016-10-28 21:27:05 +00:00
|
|
|
|
2017-09-13 06:11:49 +00:00
|
|
|
return new Promise(async (resolve, reject) => {
|
|
|
|
const docWithDefaults = await initModel(doc.type, doc);
|
2016-12-01 03:54:26 +00:00
|
|
|
db[doc.type].insert(docWithDefaults, (err, newDoc) => {
|
2016-07-23 08:22:52 +00:00
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
2016-06-18 21:02:27 +00:00
|
|
|
|
2016-07-23 08:22:52 +00:00
|
|
|
resolve(newDoc);
|
2017-11-10 11:29:18 +00:00
|
|
|
|
|
|
|
// NOTE: This needs to be after we resolve
|
|
|
|
notifyOfChange(CHANGE_INSERT, newDoc, fromSync);
|
2016-07-23 08:22:52 +00:00
|
|
|
});
|
|
|
|
});
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export const update = database.update = async function <T: BaseModel> (
|
|
|
|
doc: T,
|
|
|
|
fromSync: boolean = false
|
|
|
|
): Promise<T> {
|
|
|
|
if (db._empty) return _send('update', ...arguments);
|
2016-04-17 01:52:10 +00:00
|
|
|
|
2017-09-13 06:11:49 +00:00
|
|
|
return new Promise(async (resolve, reject) => {
|
|
|
|
const docWithDefaults = await initModel(doc.type, doc);
|
2016-12-01 03:54:26 +00:00
|
|
|
db[doc.type].update({_id: docWithDefaults._id}, docWithDefaults, err => {
|
2016-07-23 08:22:52 +00:00
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
|
|
|
|
2016-12-01 03:54:26 +00:00
|
|
|
resolve(docWithDefaults);
|
2017-11-10 11:29:18 +00:00
|
|
|
|
|
|
|
// NOTE: This needs to be after we resolve
|
|
|
|
notifyOfChange(CHANGE_UPDATE, docWithDefaults, fromSync);
|
2016-07-23 08:22:52 +00:00
|
|
|
});
|
2016-07-19 16:59:26 +00:00
|
|
|
});
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export const remove = database.remove = async function <T: BaseModel> (
|
|
|
|
doc: T,
|
|
|
|
fromSync: boolean = false
|
|
|
|
): Promise<void> {
|
|
|
|
if (db._empty) return _send('remove', ...arguments);
|
2016-04-26 07:29:24 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
database.bufferChanges();
|
2016-09-22 03:32:12 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
const docs = await database.withDescendants(doc);
|
2016-10-02 20:57:00 +00:00
|
|
|
const docIds = docs.map(d => d._id);
|
|
|
|
const types = [...new Set(docs.map(d => d.type))];
|
2016-08-15 22:31:30 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
// Don't really need to wait for this to be over;
|
|
|
|
types.map(t => db[t].remove({_id: {$in: docIds}}, {multi: true}));
|
|
|
|
|
2016-10-24 23:30:37 +00:00
|
|
|
docs.map(d => notifyOfChange(CHANGE_REMOVE, d, fromSync));
|
2016-10-02 20:57:00 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
database.flushChanges();
|
|
|
|
};
|
|
|
|
|
|
|
|
export const removeWhere = database.removeWhere = async function (
|
|
|
|
type: string,
|
|
|
|
query: Object
|
|
|
|
): Promise<void> {
|
|
|
|
if (db._empty) return _send('removeWhere', ...arguments);
|
2016-04-16 23:24:57 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
database.bufferChanges();
|
2017-07-17 18:20:38 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
for (const doc of await database.find(type, query)) {
|
|
|
|
const docs = await database.withDescendants(doc);
|
2017-07-17 18:20:38 +00:00
|
|
|
const docIds = docs.map(d => d._id);
|
|
|
|
const types = [...new Set(docs.map(d => d.type))];
|
|
|
|
|
|
|
|
// Don't really need to wait for this to be over;
|
|
|
|
types.map(t => db[t].remove({_id: {$in: docIds}}, {multi: true}));
|
|
|
|
|
2017-07-19 01:55:47 +00:00
|
|
|
docs.map(d => notifyOfChange(CHANGE_REMOVE, d, false));
|
2017-07-17 18:20:38 +00:00
|
|
|
}
|
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
database.flushChanges();
|
|
|
|
};
|
2016-09-02 05:45:12 +00:00
|
|
|
|
2016-04-17 20:35:35 +00:00
|
|
|
// ~~~~~~~~~~~~~~~~~~~ //
|
|
|
|
// DEFAULT MODEL STUFF //
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~ //
|
2016-04-16 23:24:57 +00:00
|
|
|
|
2017-09-13 06:11:49 +00:00
|
|
|
export async function docUpdate<T: BaseModel> (originalDoc: T, patch: Object = {}): Promise<T> {
|
|
|
|
const doc = await initModel(
|
2016-11-22 19:42:10 +00:00
|
|
|
originalDoc.type,
|
2016-04-27 03:17:05 +00:00
|
|
|
originalDoc,
|
2017-07-21 23:15:57 +00:00
|
|
|
|
|
|
|
// NOTE: This is before `patch` because we want `patch.modified` to win if it has it
|
2016-11-19 07:11:10 +00:00
|
|
|
{modified: Date.now()},
|
2017-07-21 23:15:57 +00:00
|
|
|
|
|
|
|
patch,
|
2016-04-27 03:17:05 +00:00
|
|
|
);
|
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
return database.update(doc);
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-07-19 16:15:03 +00:00
|
|
|
|
2017-11-13 19:05:46 +00:00
|
|
|
export async function docCreateNoMigrate<T: BaseModel> (
|
|
|
|
type: string,
|
|
|
|
...patches: Array<Object>
|
|
|
|
): Promise<T> {
|
|
|
|
const doc = await initModel(
|
|
|
|
type,
|
|
|
|
...patches,
|
|
|
|
|
|
|
|
// Fields that the user can't touch
|
|
|
|
{type: type},
|
|
|
|
{__NO_MIGRATE: true}
|
|
|
|
);
|
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
return database.insert(doc);
|
2017-11-13 19:05:46 +00:00
|
|
|
}
|
|
|
|
|
2017-11-10 11:29:18 +00:00
|
|
|
export async function docCreate<T: BaseModel> (
|
|
|
|
type: string,
|
|
|
|
...patches: Array<Object>
|
|
|
|
): Promise<T> {
|
2017-09-13 06:11:49 +00:00
|
|
|
const doc = await initModel(
|
2016-11-22 19:42:10 +00:00
|
|
|
type,
|
2017-06-30 03:30:22 +00:00
|
|
|
...patches,
|
2016-04-18 04:39:15 +00:00
|
|
|
|
2016-08-15 22:31:30 +00:00
|
|
|
// Fields that the user can't touch
|
2017-07-21 23:17:49 +00:00
|
|
|
{type: type}
|
2016-04-17 01:52:10 +00:00
|
|
|
);
|
2016-04-18 04:39:15 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
return database.insert(doc);
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-04-17 20:35:35 +00:00
|
|
|
|
2016-08-15 22:31:30 +00:00
|
|
|
// ~~~~~~~ //
|
|
|
|
// GENERAL //
|
|
|
|
// ~~~~~~~ //
|
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
export const withDescendants = database.withDescendants = async function <T: BaseModel> (
|
|
|
|
doc: T,
|
2017-07-19 00:19:56 +00:00
|
|
|
stopType: string | null = null
|
2017-11-17 12:10:37 +00:00
|
|
|
): Promise<Array<T>> {
|
|
|
|
if (db._empty) return _send('withDescendants', ...arguments);
|
|
|
|
|
2016-08-15 22:31:30 +00:00
|
|
|
let docsToReturn = doc ? [doc] : [];
|
|
|
|
|
2017-07-19 01:55:47 +00:00
|
|
|
async function next (docs: Array<BaseModel>): Promise<Array<BaseModel>> {
|
2016-10-02 20:57:00 +00:00
|
|
|
let foundDocs = [];
|
|
|
|
|
|
|
|
for (const d of docs) {
|
2017-03-28 22:45:23 +00:00
|
|
|
if (stopType && d.type === stopType) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-10 01:15:27 +00:00
|
|
|
for (const type of allTypes()) {
|
2016-08-15 22:31:30 +00:00
|
|
|
// If the doc is null, we want to search for parentId === null
|
2016-10-02 20:57:00 +00:00
|
|
|
const parentId = d ? d._id : null;
|
2017-11-17 12:10:37 +00:00
|
|
|
const more = await database.find(type, {parentId});
|
2017-03-03 20:09:08 +00:00
|
|
|
foundDocs = [...foundDocs, ...more];
|
2016-08-15 22:31:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
if (foundDocs.length === 0) {
|
|
|
|
// Didn't find anything. We're done
|
|
|
|
return docsToReturn;
|
|
|
|
}
|
2016-08-15 22:31:30 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
// Continue searching for children
|
|
|
|
docsToReturn = [...docsToReturn, ...foundDocs];
|
|
|
|
return await next(foundDocs);
|
|
|
|
}
|
2016-08-15 22:31:30 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
return await next([doc]);
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
2016-08-15 22:31:30 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
export const withAncestors = database.withAncestors = async function (
|
2017-07-19 01:55:47 +00:00
|
|
|
doc: BaseModel | null,
|
2017-07-19 00:19:56 +00:00
|
|
|
types: Array<string> = allTypes()
|
2017-07-19 01:55:47 +00:00
|
|
|
): Promise<Array<BaseModel>> {
|
2017-11-17 12:10:37 +00:00
|
|
|
if (db._empty) return _send('withAncestors', ...arguments);
|
|
|
|
|
2017-07-19 01:55:47 +00:00
|
|
|
if (!doc) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2016-10-21 17:20:36 +00:00
|
|
|
let docsToReturn = doc ? [doc] : [];
|
|
|
|
|
2017-07-19 01:55:47 +00:00
|
|
|
async function next (docs: Array<BaseModel>): Promise<Array<BaseModel>> {
|
2016-10-21 17:20:36 +00:00
|
|
|
let foundDocs = [];
|
2017-07-19 01:55:47 +00:00
|
|
|
for (const d: BaseModel of docs) {
|
2017-04-21 04:30:52 +00:00
|
|
|
for (const type of types) {
|
2016-10-21 17:20:36 +00:00
|
|
|
// If the doc is null, we want to search for parentId === null
|
2017-11-17 12:10:37 +00:00
|
|
|
const another = await database.get(type, d.parentId);
|
2017-04-21 04:30:52 +00:00
|
|
|
another && foundDocs.push(another);
|
2016-10-21 17:20:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (foundDocs.length === 0) {
|
|
|
|
// Didn't find anything. We're done
|
|
|
|
return docsToReturn;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Continue searching for children
|
|
|
|
docsToReturn = [...docsToReturn, ...foundDocs];
|
|
|
|
return await next(foundDocs);
|
|
|
|
}
|
|
|
|
|
|
|
|
return await next([doc]);
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export const duplicate = database.duplicate = async function <T: BaseModel> (
|
|
|
|
originalDoc: T,
|
|
|
|
patch: Object = {}
|
|
|
|
): Promise<T> {
|
|
|
|
if (db._empty) return _send('duplicate', ...arguments);
|
2016-10-21 17:20:36 +00:00
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
database.bufferChanges();
|
2016-08-15 22:31:30 +00:00
|
|
|
|
2017-07-21 23:17:49 +00:00
|
|
|
async function next<T: BaseModel> (docToCopy: T, patch: Object): Promise<T> {
|
2017-06-07 00:07:09 +00:00
|
|
|
// 1. Copy the doc
|
|
|
|
const newDoc = Object.assign({}, docToCopy, patch);
|
|
|
|
delete newDoc._id;
|
|
|
|
delete newDoc.created;
|
|
|
|
delete newDoc.modified;
|
2016-08-15 22:31:30 +00:00
|
|
|
|
2017-11-13 19:05:46 +00:00
|
|
|
const createdDoc = await docCreateNoMigrate(newDoc.type, newDoc);
|
2016-09-08 06:54:35 +00:00
|
|
|
|
2017-06-07 00:07:09 +00:00
|
|
|
// 2. Get all the children
|
|
|
|
for (const type of allTypes()) {
|
|
|
|
// Note: We never want to duplicate a response
|
|
|
|
if (!models.canDuplicate(type)) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-11-27 21:42:38 +00:00
|
|
|
|
2017-06-07 00:07:09 +00:00
|
|
|
const parentId = docToCopy._id;
|
2017-11-17 12:10:37 +00:00
|
|
|
const children = await database.find(type, {parentId});
|
2017-06-07 00:07:09 +00:00
|
|
|
for (const doc of children) {
|
|
|
|
await next(doc, {parentId: createdDoc._id});
|
|
|
|
}
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-09-08 06:54:35 +00:00
|
|
|
|
2017-06-07 00:07:09 +00:00
|
|
|
return createdDoc;
|
2016-10-21 20:00:31 +00:00
|
|
|
}
|
2016-09-08 06:54:35 +00:00
|
|
|
|
2017-06-07 00:07:09 +00:00
|
|
|
const createdDoc = await next(originalDoc, patch);
|
|
|
|
|
2017-11-17 12:10:37 +00:00
|
|
|
database.flushChanges();
|
2017-06-07 00:07:09 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
return createdDoc;
|
2017-11-17 12:10:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// ~~~~~~~ //
|
|
|
|
// Helpers //
|
|
|
|
// ~~~~~~~ //
|
|
|
|
|
|
|
|
async function _send<T> (fnName: string, ...args: Array<any>): Promise<T> {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const replyChannel = `db.fn.reply:${Math.random().toString().replace('0.', '')}`;
|
|
|
|
electron.ipcRenderer.send('db.fn', fnName, replyChannel, ...args);
|
|
|
|
electron.ipcRenderer.once(replyChannel, (e, result) => {
|
|
|
|
resolve(result);
|
|
|
|
});
|
|
|
|
});
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|