From 6e1f689cfd8f820090c4ab15519114f4d3bf929f Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Fri, 10 Apr 2015 21:38:31 -0400 Subject: Overhaul DB impl Make DB schema aware Add autoincrement support Add custom ID field support --- app/node_modules/okdb/index.js | 181 ++++++++++++++++++++++++++++------------- 1 file changed, 123 insertions(+), 58 deletions(-) (limited to 'app/node_modules/okdb/index.js') diff --git a/app/node_modules/okdb/index.js b/app/node_modules/okdb/index.js index 4be646d..c9dca99 100644 --- a/app/node_modules/okdb/index.js +++ b/app/node_modules/okdb/index.js @@ -1,4 +1,6 @@ var Q = require('q'); +var cloneDeep = require('lodash.clonedeep'); +var isobject = require('lodash.isobject'); var format = require('util').format; var low = require('lowdb'); low.mixin(low.mixin(require('underscore-db'))); @@ -10,14 +12,14 @@ low.mixin(low.mixin(require('underscore-db'))); function OKDB(options) { if (!(this instanceof OKDB)) return new OKDB(options); options = options || {}; - var type; + var db; if (typeof options === 'string') - type = options; + db = options; else - type = options.type; - if (!type) - throw new Error('No DB type provided'); - switch (type) { + db = options.db; + if (!db) + throw new Error('No DB db provided to OKDB'); + switch (db) { case 'fs': return FSDB(options); default: @@ -27,88 +29,151 @@ function OKDB(options) { /** * DB implementation backed by a JSON file. - * TODO Incomplete */ function FSDB(options) { if (!(this instanceof FSDB)) return new FSDB(options); options = options || {}; + if (!options.schemas) + throw new Error('No schemas provided to FSDB') + this._schemas = options.schemas; var name = options.name || 'db'; var filename = name + '.json'; this._db = low(filename); } -FSDB.prototype._resolve = function(data, error) { - return Q.Promise(function resolvePromise(resolve, reject) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); +FSDB.prototype.get = function(collection, id) { + var schema = this._schemas[collection]; + if (!schema) + return resolve(null, new Error('No such collection type')); + + var query = getQuery(schema, id); + if (!query) + return resolve(null, new Error('Bad query')); + + var result = this._db(collection).find(query); + return resolve(result ? cloneDeep(result) : result); }; -FSDB.prototype.get = function(collection, query) { - if (!query) { - return this._resolve(null, new Error('No query given')); +/** + * Add a new document to the DB + */ +FSDB.prototype.insert = function(collection, data) { + var schema = this._schemas[collection]; + var wrapped = this._db(collection); + if (!schema) + return resolve(null, new Error('No such collection type')); + // Get detached, clone deep, data sleep, beep beep + data = cloneDeep(data); + // Auto-increment fields + data = autoincrement(wrapped, schema, data); + var result = wrapped.chain().push(data).last().value(); + + if (result) { + return resolve(cloneDeep(result)); } else { - var data = this._db(collection).find(query); - return this._resolve(data); + return resolve(null, new Error('Problem inserting document')); } }; -FSDB.prototype.put = function(collection, query, data) { - data = data || {}; - if (!query) { - return this._resolve(null, new Error('No query given')); - } else if (this._db(collection).find(query)) { - var updated = this._db(collection) - .chain() - .find(query) - .assign(data) - .value(); - return this._resolve(updated); +/** + * Update an existing document in the DB + */ +FSDB.prototype.update = function(collection, id, data) { + var schema = this._schemas[collection]; + if (!schema) + return resolve(null, new Error('No such collection type')); + + var query = getQuery(schema, id); + var wrapped = this._db(collection); + var chain = wrapped.chain().find(query); + + if (!chain.value()) { + return resolve(null, new Error('Cannot update nonexistent entry')); + } + + var result = chain.assign(cloneDeep(data)).value(); + if (result ) { + return resolve(cloneDeep(result)); } else { - return this._resolve(null, new Error('Cannot update nonexistent entry')); + return resolve(null, new Error('Problem updating document')); } }; -FSDB.prototype.create = function(collection, data) { - var created = this._db(collection) - .chain() - .push(data) - .last() - .value(); - return this._resolve(created); -}; +FSDB.prototype.remove = function(collection, id) { + var schema = this._schemas[collection]; + if (!schema) + return resolve(null, new Error('No such collection type')); + + var query = getQuery(schema, id); + var result = this._db(collection).removeWhere(query); -FSDB.prototype.remove = function(collection, query) { - if (!collection || !query) { - return this._resolve(null, new Error('Bad input')); + if (result) { + // Don't need to clone this ref, since it's removed anyway + return resolve(result); } else { - var data = this._db(collection).removeWhere(query); - if (!data) - var error = new Error('Cannot remove nonexistent entry'); - return this._resolve(data, error); + return resolve(null, new Error('Cannot remove nonexistent entry')); } }; -FSDB.prototype.find = function(collection, query) { - if (!collection || !query) { - return this._resolve(null, new Error('Bad input')); - } else { - var data = this._db(collection).find(query); - return this._resolve(data); - } +FSDB.prototype.find = function(collection, id) { + var schema = this._schemas[collection]; + if (!schema) + return resolve(null, new Error('No such collection type')); + + var query = getQuery(schema, id); + var result = this._db(collection).find(query); + + return resolve(cloneDeep(result)); +}; + +FSDB.prototype.all = function(collection) { + var schema = this._schemas[collection]; + if (!schema) + return resolve(null, new Error('No such collection type')); + + var data = this._db(collection).value(); + return resolve(cloneDeep(data || [])); }; FSDB.prototype.getMeta = function() { var data = this._db('meta').first(); - return this._resolve(data || {}); + return resolve(data || {}); }; -FSDB.prototype.getAll = function(collection) { - var data = this._db(collection).toArray(); - return this._resolve(data || []); +/** + * Function implementing DB auto increment support + * Naive implementation, assumes DB is relatively small. + */ +function autoincrement(wrapper, schema, data) { + return schema.autoIncrementFields.reduce(function(data, field) { + var last = wrapper.chain().sort(field).last().value(); + var index = last ? last[field] : -1; + var incremented = {}; + incremented[field] = index + 1; + return assign(data, incremented); + }, data); +} + +function getQuery(schema, id) { + if (schema && id) { + var query = {}; + query[schema.idField] = id; + return query; + } +} + +/** + * Helper function to create promises for DB data + */ +function resolve(data, error) { + return Q.promise(function resolvePromise(resolve, reject) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); }; + module.exports = OKDB; -- cgit v1.2.3-70-g09d2 From fd0d777ee81219577ef9416fab7f985920c3ae29 Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Fri, 10 Apr 2015 22:35:24 -0400 Subject: Fix autoincrement algo bug --- app/node_modules/okdb/index.js | 8 +++++--- app/node_modules/okdb/package.json | 1 + app/node_modules/okschema/index.js | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) (limited to 'app/node_modules/okdb/index.js') diff --git a/app/node_modules/okdb/index.js b/app/node_modules/okdb/index.js index c9dca99..7639ec6 100644 --- a/app/node_modules/okdb/index.js +++ b/app/node_modules/okdb/index.js @@ -1,8 +1,10 @@ -var Q = require('q'); +var assign = require('object-assign') var cloneDeep = require('lodash.clonedeep'); var isobject = require('lodash.isobject'); var format = require('util').format; var low = require('lowdb'); +var Q = require('q'); + low.mixin(low.mixin(require('underscore-db'))); /** @@ -146,10 +148,10 @@ FSDB.prototype.getMeta = function() { */ function autoincrement(wrapper, schema, data) { return schema.autoIncrementFields.reduce(function(data, field) { - var last = wrapper.chain().sort(field).last().value(); + var last = wrapper.chain().sort(field).first().value(); var index = last ? last[field] : -1; var incremented = {}; - incremented[field] = index + 1; + incremented[field] = (parseInt(index) + 1) % Number.MAX_SAFE_INT; return assign(data, incremented); }, data); } diff --git a/app/node_modules/okdb/package.json b/app/node_modules/okdb/package.json index a6d13ff..5184cb6 100644 --- a/app/node_modules/okdb/package.json +++ b/app/node_modules/okdb/package.json @@ -12,6 +12,7 @@ "lodash.clonedeep": "^3.0.0", "lodash.isobject": "^3.0.1", "lowdb": "^0.7.2", + "object-assign": "^2.0.0", "q": "^1.2.0", "underscore-db": "^0.8.1" } diff --git a/app/node_modules/okschema/index.js b/app/node_modules/okschema/index.js index 1528eab..4633e7a 100644 --- a/app/node_modules/okschema/index.js +++ b/app/node_modules/okschema/index.js @@ -97,7 +97,8 @@ function OKSchema(spec) { // Register autoincrement fields // NOTE Does not work for nested fields var autoIncrementFields = specKeys.reduce(function(arr, prop) { - if (spec[prop] === 'autoincrement') { + var specProp = spec[prop]; + if (specProp.autoincrement) { arr.push(prop); } return arr; -- cgit v1.2.3-70-g09d2 From 4021d7846ce164f3f0c3cb37d3a1d82d2de489d9 Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Fri, 10 Apr 2015 23:09:05 -0400 Subject: Give views sorted resources --- app/node_modules/okdb/index.js | 11 +++++++++++ app/node_modules/okquery/index.js | 3 ++- app/node_modules/okresource/index.js | 11 ++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) (limited to 'app/node_modules/okdb/index.js') diff --git a/app/node_modules/okdb/index.js b/app/node_modules/okdb/index.js index 7639ec6..c00087c 100644 --- a/app/node_modules/okdb/index.js +++ b/app/node_modules/okdb/index.js @@ -117,6 +117,17 @@ FSDB.prototype.remove = function(collection, id) { } }; +FSDB.prototype.sortBy = function(collection, prop, descend) { + var schema = this._schemas[collection]; + if (!schema) + return resolve(null, new Error('No such collection type')); + if (!prop) + return resolve(null, new Error('Bad input')); + + var result = this._db(collection).sortByOrder([prop], [!descend]); + return resolve(result); +}; + FSDB.prototype.find = function(collection, id) { var schema = this._schemas[collection]; if (!schema) diff --git a/app/node_modules/okquery/index.js b/app/node_modules/okquery/index.js index 89c8b73..9cc8b78 100644 --- a/app/node_modules/okquery/index.js +++ b/app/node_modules/okquery/index.js @@ -96,7 +96,8 @@ function queryDynamic(resource) { function queryAll(resource) { return function() { - return resource.all(); + // Always return sorted results + return resource.sortBy('__index'); }; } diff --git a/app/node_modules/okresource/index.js b/app/node_modules/okresource/index.js index df89617..8c0bb16 100644 --- a/app/node_modules/okresource/index.js +++ b/app/node_modules/okresource/index.js @@ -122,12 +122,21 @@ OKResource.prototype.update = function(id, data) { reject(new Error('No resource ID provided')); } else if (!data) { reject(new Error('No data provided')); - }else { + } else { db.update(type, id, data).then(resolve).fail(reject);; } }); }; + +/** + * Get all documents in collection sorted by property, + * optionally in descending order + */ +OKResource.prototype.sortBy = function(prop, descend) { + return this._db.sortBy(this.type, prop, descend); +}; + /** * Update all resources with the given ids with the given data */ -- cgit v1.2.3-70-g09d2 From a20297451b88c604b16a35223be4b25528713c6d Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Sat, 11 Apr 2015 01:36:09 -0400 Subject: Implement FSDB.updateBatch and OKResource.updateBatch --- app/node_modules/okdb/index.js | 20 ++++++++++++++++++++ app/node_modules/okresource/index.js | 36 ++++++++++++++---------------------- 2 files changed, 34 insertions(+), 22 deletions(-) (limited to 'app/node_modules/okdb/index.js') diff --git a/app/node_modules/okdb/index.js b/app/node_modules/okdb/index.js index c00087c..ffe3ff1 100644 --- a/app/node_modules/okdb/index.js +++ b/app/node_modules/okdb/index.js @@ -101,6 +101,26 @@ FSDB.prototype.update = function(collection, id, data) { } }; +/** + * TODO Should be atomic ¯\_(ツ)_/¯ + */ +FSDB.prototype.updateBatch = function(collection, ids, datas) { + var schema = this._schemas[collection]; + if (!schema) + return resolve(null, new Error('No such collection type')); + if (!ids || !ids.length || !datas || !datas.length || + ids.length !== datas.length) { + return resolve(null, new Error('Bad input')); + } + + var doc = this._db(collection); + var results = ids.map(function(id, i) { + return doc.chain().find(getQuery(schema, id)).assign(datas[i]).value(); + }); + + return resolve(results); +}; + FSDB.prototype.remove = function(collection, id) { var schema = this._schemas[collection]; if (!schema) diff --git a/app/node_modules/okresource/index.js b/app/node_modules/okresource/index.js index 8c0bb16..c3f9adb 100644 --- a/app/node_modules/okresource/index.js +++ b/app/node_modules/okresource/index.js @@ -128,6 +128,20 @@ OKResource.prototype.update = function(id, data) { }); }; +OKResource.prototype.updateBatch = function(ids, datas) { + var self = this; + var db = this._db; + var type = this.type; + return Q.promise(function(resolve, reject) { + if (!ids || !ids.length || !datas || !datas.length || + ids.length !== datas.length) { + reject(new Error('Bad input')); + } else { + db.updateBatch(type, ids, datas).then(resolve).fail(reject); + } + }); +} + /** * Get all documents in collection sorted by property, @@ -137,28 +151,6 @@ OKResource.prototype.sortBy = function(prop, descend) { return this._db.sortBy(this.type, prop, descend); }; -/** - * Update all resources with the given ids with the given data - */ -OKResource.prototype.updateBatch = function(ids, datas) { - // var type = this.type; - // var db = this._db; - // var idField = this.idField; - // return Q.promise(function(resolve, reject) { - // if (!ids || !ids.length || !datas || !datas.length || - // ids.length !== datas.length) { - // reject(new Error('Bad input')); - // } else { - // var queries = ids.map(function(id, i) { - // var query = {}; - // query[idField] = datas[i][idField]; - // return query; - // }); - // db.putAll(type, queries, datas).then(resolve).fail(reject); - // } - // }); -}; - OKResource.prototype.updateOrCreate = function(id, data) { data = data || {}; var type = this.type; -- cgit v1.2.3-70-g09d2 From f3edb63189f6d0c02131011ca8bd69f4d9f8fe6a Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Sat, 11 Apr 2015 02:03:19 -0400 Subject: Fix NaN bug in autoincrement --- app/node_modules/okdb/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/node_modules/okdb/index.js') diff --git a/app/node_modules/okdb/index.js b/app/node_modules/okdb/index.js index ffe3ff1..2cf6d8b 100644 --- a/app/node_modules/okdb/index.js +++ b/app/node_modules/okdb/index.js @@ -182,7 +182,7 @@ function autoincrement(wrapper, schema, data) { var last = wrapper.chain().sort(field).first().value(); var index = last ? last[field] : -1; var incremented = {}; - incremented[field] = (parseInt(index) + 1) % Number.MAX_SAFE_INT; + incremented[field] = (parseInt(index) + 1); return assign(data, incremented); }, data); } -- cgit v1.2.3-70-g09d2