You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
3.9 KiB
JavaScript

'use strict';
const Promise = require("bluebird");
const knex = require("knex");
const fs = Promise.promisifyAll(require("fs"));
const matchObject = require("match-object");
const uuid = require("uuid");
const pick = require("object-pick");
const snakeCase = require("snake-case");
const rfr = require("rfr");
const knexErrors = rfr("lib/db/knex-error-type");
function loadCollection(db, collection) {
return Promise.try(() => {
return db(collection).select();
}).then((results) => {
return results.map((result) => {
return Object.assign(JSON.parse(result.value), {
$id: result.id
});
});
});
}
module.exports = function(path) {
let collections = {};
let db = knex({
client: "sqlite3",
connection: {
filename: path
},
useNullAsDefault: true,
debug: true
});
function createTable(name) {
return db.schema.createTable(name, function(table) {
table.text("id");
table.text("value");
});
}
function getCollection(name) {
let indexes; // TODO
function findIdIndex(id) {
if (id == null) {
return null;
}
return collections[name].findIndex((item) => item.$id === id);
}
return {
find: function(query) {
return collections[name].filter((item) => matchObject(query, item));
},
findOne: function(query) {
let result = collections[name].find((item) => matchObject(query, item));
if (result == null) {
throw new Error("No results found");
} else {
return result;
}
},
insert: function(object, options = {}) {
/* Intentional mutation. */
Object.assign(object, {$id: uuid.v4()});
collections[name].push(object);
return db(name).insert({
id: object.$id,
value: JSON.stringify(object)
}).then(() => {});
},
update: function(object, options = {}) {
let index;
if (index = findIdIndex(object.$id)) {
if (options.patch === true) {
Object.assign(collections[name][index], object);
} else {
collections[name][index] = object;
}
return db(name).where({id: object.$id}).update({
value: JSON.stringify(collections[name][index])
}).then(() => {});
} else {
throw new Error("No such object exists");
}
},
upsert: function(object, options = {}) {
try {
this.update(object, options);
} catch (err) {
this.insert(object, options);
}
},
upsertBy: function(keys, object, options = {}) {
let query = pick(object, keys);
try {
let result = this.findOne(query);
// FIXME: The following shouldn't be in the try block...
object.$id = result.$id;
return this.update(object, options);
} catch (err) {
return this.insert(object, options);
}
},
delete: function(object) {
let index;
if (index = findIdIndex(object.$id)) {
collections[name].splice(index, 1);
return db(name).where({id: object.$id}).delete().then(() => {});
} else {
throw new Error("No such object exists");
}
},
deleteBy: function(query) {
let toRemove = collections[name].filter((item) => matchObject(query, item));
collections[name] = collections[name].filter((item) => (toRemove.indexOf(item) !== -1));
return Promise.map(toRemove, (item) => {
return db(name).where({id: item.$id}).delete();
});
},
ensureIndex: function(property) {
// TODO
}
}
}
let dbAPI = {
close: function() {},
collection: function(name) {
return Promise.try(() => {
let snakeCasedName = snakeCase(name);
if (collections[snakeCasedName] != null) {
return getCollection(snakeCasedName);
} else {
return Promise.try(() => {
return loadCollection(db, snakeCasedName);
}).then((collectionData) => {
collections[snakeCasedName] = collectionData;
}).catch(knexErrors.sqlite.NoSuchTable, (err) => {
collections[snakeCasedName] = [];
return createTable(snakeCasedName);
}).then(() => {
return getCollection(snakeCasedName);
});
}
});
}
}
return dbAPI;
}