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';
|
|
|
|
import {generateId} from './misc';
|
|
|
|
import {getModel, 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
|
|
|
|
2016-11-10 01:15:27 +00:00
|
|
|
let db = {};
|
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!
|
2016-04-28 07:30:26 +00:00
|
|
|
const basePath = electron.remote.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
|
|
|
}
|
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
/**
|
|
|
|
* Initialize the database. Note that this isn't actually async, but might be
|
|
|
|
* in the future!
|
|
|
|
*
|
2016-11-10 01:15:27 +00:00
|
|
|
* @param types
|
2016-10-02 20:57:00 +00:00
|
|
|
* @param config
|
2016-10-21 17:20:36 +00:00
|
|
|
* @param forceReset
|
2016-10-02 20:57:00 +00:00
|
|
|
* @returns {null}
|
|
|
|
*/
|
2016-11-10 01:15:27 +00:00
|
|
|
export async function initDB (types, config = {}, forceReset = false) {
|
2016-10-21 17:20:36 +00:00
|
|
|
if (forceReset) {
|
|
|
|
db = {};
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-05-11 05:43:51 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
// Fill in the defaults
|
2016-11-10 01:15:27 +00:00
|
|
|
for (const modelType of types) {
|
|
|
|
if (db[modelType]) {
|
|
|
|
console.warn(`-- Already initialized DB.${modelType} --`);
|
|
|
|
continue;
|
2016-10-21 17:20:36 +00:00
|
|
|
}
|
|
|
|
|
2016-11-10 01:15:27 +00:00
|
|
|
const filePath = getDBFilePath(modelType);
|
2016-10-21 17:20:36 +00:00
|
|
|
|
2016-11-10 01:15:27 +00:00
|
|
|
db[modelType] = new NeDB(Object.assign({
|
|
|
|
filename: filePath,
|
|
|
|
autoload: true
|
|
|
|
}, config));
|
2016-05-11 05:43:51 +00:00
|
|
|
|
2016-11-10 01:15:27 +00:00
|
|
|
db[modelType].persistence.setAutocompactionInterval(DB_PERSIST_INTERVAL);
|
2016-07-16 07:22:08 +00:00
|
|
|
|
2016-11-10 01:15:27 +00:00
|
|
|
}
|
2016-11-10 21:03:12 +00:00
|
|
|
|
|
|
|
console.log(`-- Initialized DB at ${getDBFilePath('$TYPE')} --`);
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-07-16 07:22:08 +00:00
|
|
|
|
2016-04-26 07:29:24 +00:00
|
|
|
|
2016-09-22 03:32:12 +00:00
|
|
|
// ~~~~~~~~~~~~~~~~ //
|
|
|
|
// Change Listeners //
|
|
|
|
// ~~~~~~~~~~~~~~~~ //
|
|
|
|
|
|
|
|
let bufferingChanges = false;
|
|
|
|
let changeBuffer = [];
|
|
|
|
let changeListeners = [];
|
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function onChange (callback) {
|
2016-09-22 03:32:12 +00:00
|
|
|
console.log(`-- Added DB Listener -- `);
|
|
|
|
changeListeners.push(callback);
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-04-26 07:29:24 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function offChange (callback) {
|
2016-09-22 03:32:12 +00:00
|
|
|
console.log(`-- Removed DB Listener -- `);
|
|
|
|
changeListeners = changeListeners.filter(l => l !== callback);
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-09-22 03:32:12 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function bufferChanges (millis = 1000) {
|
2016-09-22 03:32:12 +00:00
|
|
|
bufferingChanges = true;
|
2016-10-02 20:57:00 +00:00
|
|
|
setTimeout(flushChanges, millis);
|
|
|
|
}
|
2016-09-22 03:32:12 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function flushChanges () {
|
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;
|
|
|
|
}
|
|
|
|
|
2016-11-10 09:00:29 +00:00
|
|
|
changeListeners.map(fn => fn(changes));
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-04-26 07:29:24 +00:00
|
|
|
|
2016-10-24 23:30:37 +00:00
|
|
|
function notifyOfChange (event, doc, fromSync) {
|
|
|
|
changeBuffer.push([event, doc, fromSync]);
|
2016-09-22 03:32:12 +00:00
|
|
|
|
|
|
|
// Flush right away if we're not buffering
|
|
|
|
if (!bufferingChanges) {
|
2016-10-02 20:57:00 +00:00
|
|
|
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 //
|
|
|
|
// ~~~~~~~ //
|
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function getMostRecentlyModified (type, query = {}) {
|
2016-09-08 06:54:35 +00:00
|
|
|
return new Promise(resolve => {
|
2016-09-02 05:45:12 +00:00
|
|
|
db[type].find(query).sort({modified: -1}).limit(1).exec((err, docs) => {
|
|
|
|
resolve(docs.length ? docs[0] : null);
|
|
|
|
})
|
|
|
|
})
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-09-02 05:45:12 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function find (type, query = {}) {
|
2016-07-23 08:22:52 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
db[type].find(query, (err, rawDocs) => {
|
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
|
|
|
|
2016-11-10 01:15:27 +00:00
|
|
|
const modelDefaults = initModel(type);
|
2016-07-23 08:22:52 +00:00
|
|
|
const docs = rawDocs.map(rawDoc => {
|
|
|
|
return Object.assign({}, modelDefaults, rawDoc);
|
|
|
|
});
|
|
|
|
|
|
|
|
resolve(docs);
|
|
|
|
});
|
2016-07-19 16:59:26 +00:00
|
|
|
});
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-07-19 16:59:26 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function all (type) {
|
|
|
|
return find(type);
|
|
|
|
}
|
2016-04-26 07:29:24 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function getWhere (type, query) {
|
2016-07-23 08:22:52 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
db[type].find(query, (err, rawDocs) => {
|
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
2016-05-11 05:43:51 +00:00
|
|
|
|
2016-07-23 08:22:52 +00:00
|
|
|
if (rawDocs.length === 0) {
|
2016-07-25 22:27:29 +00:00
|
|
|
// Not found. Too bad!
|
|
|
|
return resolve(null);
|
2016-07-23 08:22:52 +00:00
|
|
|
}
|
2016-05-11 05:43:51 +00:00
|
|
|
|
2016-11-10 01:15:27 +00:00
|
|
|
const modelDefaults = initModel(type);
|
2016-07-23 08:22:52 +00:00
|
|
|
resolve(Object.assign({}, modelDefaults, rawDocs[0]));
|
2016-10-02 20:57:00 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2016-07-19 16:59:26 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function get (type, id) {
|
|
|
|
return getWhere(type, {_id: id});
|
|
|
|
}
|
2016-07-19 04:01:31 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function count (type, query = {}) {
|
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);
|
|
|
|
});
|
|
|
|
});
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-04-16 23:24:57 +00:00
|
|
|
|
2016-10-28 21:27:05 +00:00
|
|
|
export async function upsert (doc, fromSync = false) {
|
|
|
|
const existingDoc = await get(doc.type, doc._id);
|
|
|
|
if (existingDoc) {
|
|
|
|
return update(doc, fromSync);
|
|
|
|
} else {
|
|
|
|
return insert(doc, fromSync);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-24 23:30:37 +00:00
|
|
|
export function insert (doc, fromSync = false) {
|
2016-07-23 08:22:52 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
db[doc.type].insert(doc, (err, newDoc) => {
|
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
2016-06-18 21:02:27 +00:00
|
|
|
|
2016-10-24 23:30:37 +00:00
|
|
|
notifyOfChange(CHANGE_INSERT, doc, fromSync);
|
2016-10-21 17:20:36 +00:00
|
|
|
|
2016-07-23 08:22:52 +00:00
|
|
|
resolve(newDoc);
|
|
|
|
});
|
|
|
|
});
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-04-17 01:52:10 +00:00
|
|
|
|
2016-10-24 23:30:37 +00:00
|
|
|
export function update (doc, fromSync = false) {
|
2016-07-19 16:59:26 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2016-07-23 08:22:52 +00:00
|
|
|
db[doc.type].update({_id: doc._id}, doc, err => {
|
|
|
|
if (err) {
|
|
|
|
return reject(err);
|
|
|
|
}
|
|
|
|
|
2016-10-24 23:30:37 +00:00
|
|
|
notifyOfChange(CHANGE_UPDATE, doc, fromSync);
|
2016-10-21 17:20:36 +00:00
|
|
|
|
2016-08-15 17:04:36 +00:00
|
|
|
resolve(doc);
|
2016-07-23 08:22:52 +00:00
|
|
|
});
|
2016-07-19 16:59:26 +00:00
|
|
|
});
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-04-26 07:29:24 +00:00
|
|
|
|
2016-10-24 23:30:37 +00:00
|
|
|
export async function remove (doc, fromSync = false) {
|
2016-10-02 20:57:00 +00:00
|
|
|
bufferChanges();
|
2016-09-22 03:32:12 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
const docs = await withDescendants(doc);
|
|
|
|
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
|
|
|
|
|
|
|
flushChanges();
|
|
|
|
}
|
2016-04-16 23:24:57 +00:00
|
|
|
|
2016-09-02 05:45:12 +00:00
|
|
|
/**
|
|
|
|
* Remove a lot of documents quickly and silently
|
|
|
|
*
|
|
|
|
* @param type
|
|
|
|
* @param query
|
|
|
|
* @returns {Promise.<T>}
|
|
|
|
*/
|
2016-10-02 20:57:00 +00:00
|
|
|
export function removeBulkSilently (type, query) {
|
2016-09-02 05:45:12 +00:00
|
|
|
return new Promise(resolve => {
|
|
|
|
db[type].remove(query, {multi: true}, err => resolve());
|
|
|
|
});
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
2016-09-02 05:45:12 +00:00
|
|
|
|
2016-04-16 23:24:57 +00:00
|
|
|
|
2016-04-17 20:35:35 +00:00
|
|
|
// ~~~~~~~~~~~~~~~~~~~ //
|
|
|
|
// DEFAULT MODEL STUFF //
|
|
|
|
// ~~~~~~~~~~~~~~~~~~~ //
|
2016-04-16 23:24:57 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function docUpdate (originalDoc, patch = {}) {
|
2016-04-27 03:17:05 +00:00
|
|
|
const doc = Object.assign(
|
2016-11-10 01:15:27 +00:00
|
|
|
initModel(originalDoc.type),
|
2016-04-27 03:17:05 +00:00
|
|
|
originalDoc,
|
|
|
|
patch,
|
|
|
|
{modified: Date.now()}
|
|
|
|
);
|
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
return update(doc);
|
|
|
|
}
|
2016-07-19 16:15:03 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export function docCreate (type, patch = {}) {
|
2016-11-10 01:15:27 +00:00
|
|
|
const idPrefix = getModel(type).prefix;
|
2016-09-08 06:54:35 +00:00
|
|
|
|
|
|
|
if (!idPrefix) {
|
|
|
|
throw new Error(`No ID prefix for ${type}`)
|
|
|
|
}
|
|
|
|
|
2016-04-26 07:29:24 +00:00
|
|
|
const doc = Object.assign(
|
2016-08-15 22:31:30 +00:00
|
|
|
{_id: generateId(idPrefix)},
|
2016-11-10 01:15:27 +00:00
|
|
|
initModel(type),
|
2016-04-16 23:24:57 +00:00
|
|
|
patch,
|
2016-04-18 04:39:15 +00:00
|
|
|
|
2016-08-15 22:31:30 +00:00
|
|
|
// Fields that the user can't touch
|
2016-04-16 23:24:57 +00:00
|
|
|
{
|
2016-04-18 04:39:15 +00:00
|
|
|
type: type,
|
2016-04-16 23:24:57 +00:00
|
|
|
modified: Date.now()
|
|
|
|
}
|
2016-04-17 01:52:10 +00:00
|
|
|
);
|
2016-04-18 04:39:15 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
return insert(doc);
|
|
|
|
}
|
2016-04-17 20:35:35 +00:00
|
|
|
|
2016-08-15 22:31:30 +00:00
|
|
|
// ~~~~~~~ //
|
|
|
|
// GENERAL //
|
|
|
|
// ~~~~~~~ //
|
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
export async function withDescendants (doc = null) {
|
2016-08-15 22:31:30 +00:00
|
|
|
let docsToReturn = doc ? [doc] : [];
|
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
async function next (docs) {
|
|
|
|
let foundDocs = [];
|
|
|
|
|
|
|
|
for (const d of docs) {
|
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;
|
|
|
|
const more = await find(type, {parentId});
|
|
|
|
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]);
|
|
|
|
}
|
2016-08-15 22:31:30 +00:00
|
|
|
|
2016-10-21 17:20:36 +00:00
|
|
|
export async function withAncestors (doc) {
|
|
|
|
let docsToReturn = doc ? [doc] : [];
|
|
|
|
|
|
|
|
async function next (docs) {
|
|
|
|
let foundDocs = [];
|
|
|
|
|
|
|
|
for (const d of docs) {
|
2016-11-10 01:15:27 +00:00
|
|
|
for (const type of allTypes()) {
|
2016-10-21 17:20:36 +00:00
|
|
|
// If the doc is null, we want to search for parentId === null
|
|
|
|
const more = await find(type, {_id: d.parentId});
|
|
|
|
foundDocs = [...foundDocs, ...more]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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]);
|
|
|
|
}
|
|
|
|
|
2016-10-21 20:00:31 +00:00
|
|
|
export async function duplicate (originalDoc, patch = {}, first = true) {
|
2016-10-02 20:57:00 +00:00
|
|
|
bufferChanges();
|
2016-08-15 22:31:30 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
// 1. Copy the doc
|
|
|
|
const newDoc = Object.assign({}, originalDoc, patch);
|
|
|
|
delete newDoc._id;
|
|
|
|
delete newDoc.created;
|
|
|
|
delete newDoc.modified;
|
2016-08-15 22:31:30 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
const createdDoc = await docCreate(newDoc.type, newDoc);
|
2016-09-08 06:54:35 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
// 2. Get all the children
|
2016-11-10 01:15:27 +00:00
|
|
|
for (const type of allTypes()) {
|
2016-10-02 20:57:00 +00:00
|
|
|
const parentId = originalDoc._id;
|
|
|
|
const children = await find(type, {parentId});
|
|
|
|
for (const doc of children) {
|
2016-10-21 20:00:31 +00:00
|
|
|
await duplicate(doc, {parentId: createdDoc._id}, false)
|
2016-10-02 20:57:00 +00:00
|
|
|
}
|
|
|
|
}
|
2016-09-08 06:54:35 +00:00
|
|
|
|
2016-10-21 20:00:31 +00:00
|
|
|
if (first) {
|
|
|
|
flushChanges();
|
|
|
|
}
|
2016-09-08 06:54:35 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
return createdDoc;
|
|
|
|
}
|