@seald-io/nedb
Advanced tools
Comparing version 2.0.0 to 2.0.1
@@ -10,3 +10,3 @@ /** | ||
*/ | ||
function randomBytes (size) { | ||
const randomBytes = size => { | ||
const bytes = new Array(size) | ||
@@ -26,3 +26,3 @@ | ||
*/ | ||
function byteArrayToBase64 (uint8) { | ||
const byteArrayToBase64 = uint8 => { | ||
const lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' | ||
@@ -32,11 +32,7 @@ const extraBytes = uint8.length % 3 // if we have 1 byte left, pad 2 bytes | ||
let temp | ||
let length | ||
let i | ||
function tripletToBase64 (num) { | ||
return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F] | ||
} | ||
const tripletToBase64 = num => lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F] | ||
// go through the array every three bytes, we'll deal with trailing stuff later | ||
for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { | ||
for (let i = 0, length = uint8.length - extraBytes; i < length; i += 3) { | ||
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) | ||
@@ -47,16 +43,13 @@ output += tripletToBase64(temp) | ||
// pad the end with zeros, but make sure to not forget the extra bytes | ||
switch (extraBytes) { | ||
case 1: | ||
temp = uint8[uint8.length - 1] | ||
output += lookup[temp >> 2] | ||
output += lookup[(temp << 4) & 0x3F] | ||
output += '==' | ||
break | ||
case 2: | ||
temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]) | ||
output += lookup[temp >> 10] | ||
output += lookup[(temp >> 4) & 0x3F] | ||
output += lookup[(temp << 2) & 0x3F] | ||
output += '=' | ||
break | ||
if (extraBytes === 1) { | ||
temp = uint8[uint8.length - 1] | ||
output += lookup[temp >> 2] | ||
output += lookup[(temp << 4) & 0x3F] | ||
output += '==' | ||
} else if (extraBytes === 2) { | ||
temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]) | ||
output += lookup[temp >> 10] | ||
output += lookup[(temp >> 4) & 0x3F] | ||
output += lookup[(temp << 2) & 0x3F] | ||
output += '=' | ||
} | ||
@@ -75,6 +68,4 @@ | ||
*/ | ||
function uid (len) { | ||
return byteArrayToBase64(randomBytes(Math.ceil(Math.max(8, len * 2)))).replace(/[+/]/g, '').slice(0, len) | ||
} | ||
const uid = len => byteArrayToBase64(randomBytes(Math.ceil(Math.max(8, len * 2)))).replace(/[+/]/g, '').slice(0, len) | ||
module.exports.uid = uid |
@@ -16,3 +16,3 @@ /** | ||
function exists (filename, cback) { | ||
const exists = (filename, cback) => { | ||
// eslint-disable-next-line node/handle-callback-err | ||
@@ -25,3 +25,3 @@ store.getItem(filename, (err, value) => { | ||
function rename (filename, newFilename, callback) { | ||
const rename = (filename, newFilename, callback) => { | ||
// eslint-disable-next-line node/handle-callback-err | ||
@@ -38,3 +38,3 @@ store.getItem(filename, (err, value) => { | ||
function writeFile (filename, contents, options, callback) { | ||
const writeFile = (filename, contents, options, callback) => { | ||
// Options do not matter in browser setup | ||
@@ -45,3 +45,3 @@ if (typeof options === 'function') { callback = options } | ||
function appendFile (filename, toAppend, options, callback) { | ||
const appendFile = (filename, toAppend, options, callback) => { | ||
// Options do not matter in browser setup | ||
@@ -58,3 +58,3 @@ if (typeof options === 'function') { callback = options } | ||
function readFile (filename, options, callback) { | ||
const readFile = (filename, options, callback) => { | ||
// Options do not matter in browser setup | ||
@@ -66,3 +66,3 @@ if (typeof options === 'function') { callback = options } | ||
function unlink (filename, callback) { | ||
const unlink = (filename, callback) => { | ||
store.removeItem(filename, () => callback()) | ||
@@ -72,10 +72,6 @@ } | ||
// Nothing to do, no directories will be used on the browser | ||
function mkdir (dir, options, callback) { | ||
return callback() | ||
} | ||
const mkdir = (dir, options, callback) => callback() | ||
// Nothing to do, no data corruption possible in the brower | ||
function ensureDatafileIntegrity (filename, callback) { | ||
return callback(null) | ||
} | ||
// Nothing to do, no data corruption possible in the browser | ||
const ensureDatafileIntegrity = (filename, callback) => callback(null) | ||
@@ -82,0 +78,0 @@ // Interface |
@@ -9,2 +9,10 @@ # Changelog | ||
## [2.0.1] - 2021-05-19 | ||
### Changed | ||
- bump `@seald-io/binary-search-tree` to 1.0.2, which does not depend | ||
on `underscore`; | ||
- replace use of `underscore` by pure JS. | ||
## [2.0.0] - 2021-05-18 | ||
@@ -17,2 +25,3 @@ | ||
### Changed | ||
- Update `homepage` & `repository` fields in the `package.json` | ||
@@ -19,0 +28,0 @@ - New maintainer [seald](https://github.com/seald/) and new package |
/** | ||
* Manage access to data, be it to find, update or remove it | ||
*/ | ||
const _ = require('underscore') | ||
const model = require('./model.js') | ||
@@ -60,3 +59,2 @@ | ||
const res = [] | ||
const self = this | ||
let action | ||
@@ -69,19 +67,20 @@ | ||
const keepId = this._projection._id !== 0 | ||
this._projection = _.omit(this._projection, '_id') | ||
const { _id, ...rest } = this._projection | ||
this._projection = rest | ||
// Check for consistency | ||
const keys = Object.keys(this._projection) | ||
keys.forEach(function (k) { | ||
if (action !== undefined && self._projection[k] !== action) { throw new Error('Can\'t both keep and omit fields except for _id') } | ||
action = self._projection[k] | ||
keys.forEach(k => { | ||
if (action !== undefined && this._projection[k] !== action) throw new Error('Can\'t both keep and omit fields except for _id') | ||
action = this._projection[k] | ||
}) | ||
// Do the actual projection | ||
candidates.forEach(function (candidate) { | ||
candidates.forEach(candidate => { | ||
let toPush | ||
if (action === 1) { // pick-type projection | ||
toPush = { $set: {} } | ||
keys.forEach(function (k) { | ||
keys.forEach(k => { | ||
toPush.$set[k] = model.getDotValue(candidate, k) | ||
if (toPush.$set[k] === undefined) { delete toPush.$set[k] } | ||
if (toPush.$set[k] === undefined) delete toPush.$set[k] | ||
}) | ||
@@ -91,10 +90,7 @@ toPush = model.modify({}, toPush) | ||
toPush = { $unset: {} } | ||
keys.forEach(function (k) { toPush.$unset[k] = true }) | ||
keys.forEach(k => { toPush.$unset[k] = true }) | ||
toPush = model.modify(candidate, toPush) | ||
} | ||
if (keepId) { | ||
toPush._id = candidate._id | ||
} else { | ||
delete toPush._id | ||
} | ||
if (keepId) toPush._id = candidate._id | ||
else delete toPush._id | ||
res.push(toPush) | ||
@@ -117,34 +113,26 @@ }) | ||
let skipped = 0 | ||
const self = this | ||
let error = null | ||
let i | ||
let keys | ||
let key | ||
function callback (error, res) { | ||
if (self.execFn) { | ||
return self.execFn(error, res, _callback) | ||
} else { | ||
return _callback(error, res) | ||
} | ||
const callback = (error, res) => { | ||
if (this.execFn) return this.execFn(error, res, _callback) | ||
else return _callback(error, res) | ||
} | ||
this.db.getCandidates(this.query, function (err, candidates) { | ||
if (err) { return callback(err) } | ||
this.db.getCandidates(this.query, (err, candidates) => { | ||
if (err) return callback(err) | ||
try { | ||
for (i = 0; i < candidates.length; i += 1) { | ||
if (model.match(candidates[i], self.query)) { | ||
for (const candidate of candidates) { | ||
if (model.match(candidate, this.query)) { | ||
// If a sort is defined, wait for the results to be sorted before applying limit and skip | ||
if (!self._sort) { | ||
if (self._skip && self._skip > skipped) { | ||
skipped += 1 | ||
} else { | ||
res.push(candidates[i]) | ||
if (!this._sort) { | ||
if (this._skip && this._skip > skipped) skipped += 1 | ||
else { | ||
res.push(candidate) | ||
added += 1 | ||
if (self._limit && self._limit <= added) { break } | ||
if (this._limit && this._limit <= added) break | ||
} | ||
} else { | ||
res.push(candidates[i]) | ||
} | ||
} else res.push(candidate) | ||
} | ||
@@ -157,21 +145,15 @@ } | ||
// Apply all sorts | ||
if (self._sort) { | ||
keys = Object.keys(self._sort) | ||
if (this._sort) { | ||
keys = Object.keys(this._sort) | ||
// Sorting | ||
const criteria = [] | ||
for (i = 0; i < keys.length; i++) { | ||
key = keys[i] | ||
criteria.push({ key: key, direction: self._sort[key] }) | ||
} | ||
res.sort(function (a, b) { | ||
let criterion | ||
let compare | ||
let i | ||
for (i = 0; i < criteria.length; i++) { | ||
criterion = criteria[i] | ||
compare = criterion.direction * model.compareThings(model.getDotValue(a, criterion.key), model.getDotValue(b, criterion.key), self.db.compareStrings) | ||
if (compare !== 0) { | ||
return compare | ||
} | ||
keys.forEach(item => { | ||
key = item | ||
criteria.push({ key: key, direction: this._sort[key] }) | ||
}) | ||
res.sort((a, b) => { | ||
for (const criterion of criteria) { | ||
const compare = criterion.direction * model.compareThings(model.getDotValue(a, criterion.key), model.getDotValue(b, criterion.key), this.db.compareStrings) | ||
if (compare !== 0) return compare | ||
} | ||
@@ -182,4 +164,4 @@ return 0 | ||
// Applying limit and skip | ||
const limit = self._limit || res.length | ||
const skip = self._skip || 0 | ||
const limit = this._limit || res.length | ||
const skip = this._skip || 0 | ||
@@ -191,3 +173,3 @@ res = res.slice(skip, skip + limit) | ||
try { | ||
res = self.project(res) | ||
res = this.project(res) | ||
} catch (e) { | ||
@@ -194,0 +176,0 @@ error = e |
@@ -11,10 +11,8 @@ const crypto = require('crypto') | ||
*/ | ||
function uid (len) { | ||
return crypto.randomBytes(Math.ceil(Math.max(8, len * 2))) | ||
.toString('base64') | ||
.replace(/[+/]/g, '') | ||
.slice(0, len) | ||
} | ||
const uid = len => crypto.randomBytes(Math.ceil(Math.max(8, len * 2))) | ||
.toString('base64') | ||
.replace(/[+/]/g, '') | ||
.slice(0, len) | ||
// Interface | ||
module.exports.uid = uid |
const { EventEmitter } = require('events') | ||
const util = require('util') | ||
const async = require('async') | ||
const _ = require('underscore') | ||
const Cursor = require('./cursor.js') | ||
@@ -68,3 +67,3 @@ const customUtils = require('./customUtils.js') | ||
this.executor = new Executor() | ||
if (this.inMemoryOnly) { this.executor.ready = true } | ||
if (this.inMemoryOnly) this.executor.ready = true | ||
@@ -81,5 +80,5 @@ // Indexed by field name, dot notation can be used | ||
if (this.autoload) { | ||
this.loadDatabase(options.onload || function (err) { | ||
if (err) { throw err } | ||
}) | ||
this.loadDatabase(options.onload || (err => { | ||
if (err) throw err | ||
})) | ||
} | ||
@@ -106,7 +105,5 @@ } | ||
resetIndexes (newData) { | ||
const self = this | ||
Object.keys(this.indexes).forEach(function (i) { | ||
self.indexes[i].reset(newData) | ||
}) | ||
for (const index of Object.values(this.indexes)) { | ||
index.reset(newData) | ||
} | ||
} | ||
@@ -118,2 +115,3 @@ | ||
* We use an async API for consistency with the rest of the code | ||
* @param {Object} options | ||
* @param {String} options.fieldName | ||
@@ -123,19 +121,14 @@ * @param {Boolean} options.unique | ||
* @param {Number} options.expireAfterSeconds - Optional, if set this index becomes a TTL index (only works on Date fields, not arrays of Date) | ||
* @param {Function} cb Optional callback, signature: err | ||
* @param {Function} callback Optional callback, signature: err | ||
*/ | ||
ensureIndex (options, cb) { | ||
let err | ||
const callback = cb || function () {} | ||
options = options || {} | ||
ensureIndex (options = {}, callback = () => {}) { | ||
if (!options.fieldName) { | ||
err = new Error('Cannot create an index without a fieldName') | ||
const err = new Error('Cannot create an index without a fieldName') | ||
err.missingFieldName = true | ||
return callback(err) | ||
} | ||
if (this.indexes[options.fieldName]) { return callback(null) } | ||
if (this.indexes[options.fieldName]) return callback(null) | ||
this.indexes[options.fieldName] = new Index(options) | ||
if (options.expireAfterSeconds !== undefined) { this.ttlIndexes[options.fieldName] = options.expireAfterSeconds } // With this implementation index creation is not necessary to ensure TTL but we stick with MongoDB's API here | ||
if (options.expireAfterSeconds !== undefined) this.ttlIndexes[options.fieldName] = options.expireAfterSeconds // With this implementation index creation is not necessary to ensure TTL but we stick with MongoDB's API here | ||
@@ -150,4 +143,4 @@ try { | ||
// We may want to force all options to be persisted including defaults, not just the ones passed the index creation function | ||
this.persistence.persistNewState([{ $$indexCreated: options }], function (err) { | ||
if (err) { return callback(err) } | ||
this.persistence.persistNewState([{ $$indexCreated: options }], err => { | ||
if (err) return callback(err) | ||
return callback(null) | ||
@@ -160,11 +153,9 @@ }) | ||
* @param {String} fieldName | ||
* @param {Function} cb Optional callback, signature: err | ||
* @param {Function} callback Optional callback, signature: err | ||
*/ | ||
removeIndex (fieldName, cb) { | ||
const callback = cb || function () {} | ||
removeIndex (fieldName, callback = () => {}) { | ||
delete this.indexes[fieldName] | ||
this.persistence.persistNewState([{ $$indexRemoved: fieldName }], function (err) { | ||
if (err) { return callback(err) } | ||
this.persistence.persistNewState([{ $$indexRemoved: fieldName }], err => { | ||
if (err) return callback(err) | ||
return callback(null) | ||
@@ -178,3 +169,2 @@ }) | ||
addToIndexes (doc) { | ||
let i | ||
let failingIndex | ||
@@ -184,3 +174,3 @@ let error | ||
for (i = 0; i < keys.length; i += 1) { | ||
for (let i = 0; i < keys.length; i += 1) { | ||
try { | ||
@@ -197,3 +187,3 @@ this.indexes[keys[i]].insert(doc) | ||
if (error) { | ||
for (i = 0; i < failingIndex; i += 1) { | ||
for (let i = 0; i < failingIndex; i += 1) { | ||
this.indexes[keys[i]].remove(doc) | ||
@@ -210,7 +200,5 @@ } | ||
removeFromIndexes (doc) { | ||
const self = this | ||
Object.keys(this.indexes).forEach(function (i) { | ||
self.indexes[i].remove(doc) | ||
}) | ||
for (const index of Object.values(this.indexes)) { | ||
index.remove(doc) | ||
} | ||
} | ||
@@ -224,3 +212,2 @@ | ||
updateIndexes (oldDoc, newDoc) { | ||
let i | ||
let failingIndex | ||
@@ -230,3 +217,3 @@ let error | ||
for (i = 0; i < keys.length; i += 1) { | ||
for (let i = 0; i < keys.length; i += 1) { | ||
try { | ||
@@ -243,3 +230,3 @@ this.indexes[keys[i]].update(oldDoc, newDoc) | ||
if (error) { | ||
for (i = 0; i < failingIndex; i += 1) { | ||
for (let i = 0; i < failingIndex; i += 1) { | ||
this.indexes[keys[i]].revertUpdate(oldDoc, newDoc) | ||
@@ -267,3 +254,2 @@ } | ||
const indexNames = Object.keys(this.indexes) | ||
const self = this | ||
let usableQueryKeys | ||
@@ -278,6 +264,6 @@ | ||
// STEP 1: get candidates list by checking indexes from most to least frequent usecase | ||
function (cb) { | ||
cb => { | ||
// For a basic match | ||
usableQueryKeys = [] | ||
Object.keys(query).forEach(function (k) { | ||
Object.keys(query).forEach(k => { | ||
if (typeof query[k] === 'string' || typeof query[k] === 'number' || typeof query[k] === 'boolean' || util.types.isDate(query[k]) || query[k] === null) { | ||
@@ -287,5 +273,5 @@ usableQueryKeys.push(k) | ||
}) | ||
usableQueryKeys = _.intersection(usableQueryKeys, indexNames) | ||
usableQueryKeys = usableQueryKeys.filter(k => indexNames.includes(k)) | ||
if (usableQueryKeys.length > 0) { | ||
return cb(null, self.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]])) | ||
return cb(null, this.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]])) | ||
} | ||
@@ -295,3 +281,3 @@ | ||
usableQueryKeys = [] | ||
Object.keys(query).forEach(function (k) { | ||
Object.keys(query).forEach(k => { | ||
if (query[k] && Object.prototype.hasOwnProperty.call(query[k], '$in')) { | ||
@@ -301,5 +287,5 @@ usableQueryKeys.push(k) | ||
}) | ||
usableQueryKeys = _.intersection(usableQueryKeys, indexNames) | ||
usableQueryKeys = usableQueryKeys.filter(k => indexNames.includes(k)) | ||
if (usableQueryKeys.length > 0) { | ||
return cb(null, self.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]].$in)) | ||
return cb(null, this.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]].$in)) | ||
} | ||
@@ -309,3 +295,3 @@ | ||
usableQueryKeys = [] | ||
Object.keys(query).forEach(function (k) { | ||
Object.keys(query).forEach(k => { | ||
if (query[k] && (Object.prototype.hasOwnProperty.call(query[k], '$lt') || Object.prototype.hasOwnProperty.call(query[k], '$lte') || Object.prototype.hasOwnProperty.call(query[k], '$gt') || Object.prototype.hasOwnProperty.call(query[k], '$gte'))) { | ||
@@ -315,35 +301,36 @@ usableQueryKeys.push(k) | ||
}) | ||
usableQueryKeys = _.intersection(usableQueryKeys, indexNames) | ||
usableQueryKeys = usableQueryKeys.filter(k => indexNames.includes(k)) | ||
if (usableQueryKeys.length > 0) { | ||
return cb(null, self.indexes[usableQueryKeys[0]].getBetweenBounds(query[usableQueryKeys[0]])) | ||
return cb(null, this.indexes[usableQueryKeys[0]].getBetweenBounds(query[usableQueryKeys[0]])) | ||
} | ||
// By default, return all the DB data | ||
return cb(null, self.getAllData()) | ||
return cb(null, this.getAllData()) | ||
}, | ||
// STEP 2: remove all expired documents | ||
function (docs) { | ||
if (dontExpireStaleDocs) { return callback(null, docs) } | ||
docs => { | ||
if (dontExpireStaleDocs) return callback(null, docs) | ||
const expiredDocsIds = [] | ||
const validDocs = [] | ||
const ttlIndexesFieldNames = Object.keys(self.ttlIndexes) | ||
const ttlIndexesFieldNames = Object.keys(this.ttlIndexes) | ||
docs.forEach(function (doc) { | ||
docs.forEach(doc => { | ||
let valid = true | ||
ttlIndexesFieldNames.forEach(function (i) { | ||
if (doc[i] !== undefined && util.types.isDate(doc[i]) && Date.now() > doc[i].getTime() + self.ttlIndexes[i] * 1000) { | ||
ttlIndexesFieldNames.forEach(i => { | ||
if (doc[i] !== undefined && util.types.isDate(doc[i]) && Date.now() > doc[i].getTime() + this.ttlIndexes[i] * 1000) { | ||
valid = false | ||
} | ||
}) | ||
if (valid) { validDocs.push(doc) } else { expiredDocsIds.push(doc._id) } | ||
if (valid) validDocs.push(doc) | ||
else expiredDocsIds.push(doc._id) | ||
}) | ||
async.eachSeries(expiredDocsIds, function (_id, cb) { | ||
self._remove({ _id: _id }, {}, function (err) { | ||
if (err) { return callback(err) } | ||
async.eachSeries(expiredDocsIds, (_id, cb) => { | ||
this._remove({ _id: _id }, {}, err => { | ||
if (err) return callback(err) | ||
return cb() | ||
}) | ||
// eslint-disable-next-line node/handle-callback-err | ||
}, function (err) { | ||
}, err => { | ||
// TODO: handle error | ||
@@ -357,8 +344,8 @@ return callback(null, validDocs) | ||
* Insert a new document | ||
* @param {Function} cb Optional callback, signature: err, insertedDoc | ||
* @param {Document} newDoc | ||
* @param {Function} callback Optional callback, signature: err, insertedDoc | ||
* | ||
* @api private Use Datastore.insert which has the same signature | ||
*/ | ||
_insert (newDoc, cb) { | ||
const callback = cb || function () {} | ||
_insert (newDoc, callback = () => {}) { | ||
let preparedDoc | ||
@@ -373,4 +360,4 @@ | ||
this.persistence.persistNewState(Array.isArray(preparedDoc) ? preparedDoc : [preparedDoc], function (err) { | ||
if (err) { return callback(err) } | ||
this.persistence.persistNewState(Array.isArray(preparedDoc) ? preparedDoc : [preparedDoc], err => { | ||
if (err) return callback(err) | ||
return callback(null, model.deepCopy(preparedDoc)) | ||
@@ -384,8 +371,6 @@ }) | ||
createNewId () { | ||
let tentativeId = customUtils.uid(16) | ||
let attemptId = customUtils.uid(16) | ||
// Try as many times as needed to get an unused _id. As explained in customUtils, the probability of this ever happening is extremely small, so this is O(1) | ||
if (this.indexes._id.getMatching(tentativeId).length > 0) { | ||
tentativeId = this.createNewId() | ||
} | ||
return tentativeId | ||
if (this.indexes._id.getMatching(attemptId).length > 0) attemptId = this.createNewId() | ||
return attemptId | ||
} | ||
@@ -400,13 +385,12 @@ | ||
let preparedDoc | ||
const self = this | ||
if (Array.isArray(newDoc)) { | ||
preparedDoc = [] | ||
newDoc.forEach(function (doc) { preparedDoc.push(self.prepareDocumentForInsertion(doc)) }) | ||
newDoc.forEach(doc => { preparedDoc.push(this.prepareDocumentForInsertion(doc)) }) | ||
} else { | ||
preparedDoc = model.deepCopy(newDoc) | ||
if (preparedDoc._id === undefined) { preparedDoc._id = this.createNewId() } | ||
if (preparedDoc._id === undefined) preparedDoc._id = this.createNewId() | ||
const now = new Date() | ||
if (this.timestampData && preparedDoc.createdAt === undefined) { preparedDoc.createdAt = now } | ||
if (this.timestampData && preparedDoc.updatedAt === undefined) { preparedDoc.updatedAt = now } | ||
if (this.timestampData && preparedDoc.createdAt === undefined) preparedDoc.createdAt = now | ||
if (this.timestampData && preparedDoc.updatedAt === undefined) preparedDoc.updatedAt = now | ||
model.checkObject(preparedDoc) | ||
@@ -423,7 +407,4 @@ } | ||
_insertInCache (preparedDoc) { | ||
if (Array.isArray(preparedDoc)) { | ||
this._insertMultipleDocsInCache(preparedDoc) | ||
} else { | ||
this.addToIndexes(preparedDoc) | ||
} | ||
if (Array.isArray(preparedDoc)) this._insertMultipleDocsInCache(preparedDoc) | ||
else this.addToIndexes(preparedDoc) | ||
} | ||
@@ -437,7 +418,6 @@ | ||
_insertMultipleDocsInCache (preparedDocs) { | ||
let i | ||
let failingI | ||
let failingIndex | ||
let error | ||
for (i = 0; i < preparedDocs.length; i += 1) { | ||
for (let i = 0; i < preparedDocs.length; i += 1) { | ||
try { | ||
@@ -447,3 +427,3 @@ this.addToIndexes(preparedDocs[i]) | ||
error = e | ||
failingI = i | ||
failingIndex = i | ||
break | ||
@@ -454,3 +434,3 @@ } | ||
if (error) { | ||
for (i = 0; i < failingI; i += 1) { | ||
for (let i = 0; i < failingIndex; i += 1) { | ||
this.removeFromIndexes(preparedDocs[i]) | ||
@@ -470,2 +450,3 @@ } | ||
* @param {Object} query MongoDB-style query | ||
* @param {Function} callback Optional callback, signature: err, count | ||
*/ | ||
@@ -478,7 +459,4 @@ count (query, callback) { | ||
if (typeof callback === 'function') { | ||
cursor.exec(callback) | ||
} else { | ||
return cursor | ||
} | ||
if (typeof callback === 'function') cursor.exec(callback) | ||
else return cursor | ||
} | ||
@@ -491,26 +469,20 @@ | ||
* @param {Object} projection MongoDB-style projection | ||
* @param {Function} callback Optional callback, signature: err, docs | ||
*/ | ||
find (query, projection, callback) { | ||
switch (arguments.length) { | ||
case 1: | ||
if (arguments.length === 1) { | ||
projection = {} | ||
// callback is undefined, will return a cursor | ||
} else if (arguments.length === 2) { | ||
if (typeof projection === 'function') { | ||
callback = projection | ||
projection = {} | ||
// callback is undefined, will return a cursor | ||
break | ||
case 2: | ||
if (typeof projection === 'function') { | ||
callback = projection | ||
projection = {} | ||
} // If not assume projection is an object and callback undefined | ||
break | ||
} // If not assume projection is an object and callback undefined | ||
} | ||
const cursor = new Cursor(this, query, function (err, docs, callback) { | ||
const res = [] | ||
let i | ||
if (err) { return callback(err) } | ||
for (i = 0; i < docs.length; i += 1) { | ||
res.push(model.deepCopy(docs[i])) | ||
} | ||
const res = docs.map(doc => model.deepCopy(doc)) | ||
return callback(null, res) | ||
@@ -520,7 +492,4 @@ }) | ||
cursor.projection(projection) | ||
if (typeof callback === 'function') { | ||
cursor.exec(callback) | ||
} else { | ||
return cursor | ||
} | ||
if (typeof callback === 'function') cursor.exec(callback) | ||
else return cursor | ||
} | ||
@@ -532,32 +501,24 @@ | ||
* @param {Object} projection MongoDB-style projection | ||
* @param {Function} callback Optional callback, signature: err, doc | ||
*/ | ||
findOne (query, projection, callback) { | ||
switch (arguments.length) { | ||
case 1: | ||
if (arguments.length === 1) { | ||
projection = {} | ||
// callback is undefined, will return a cursor | ||
} else if (arguments.length === 2) { | ||
if (typeof projection === 'function') { | ||
callback = projection | ||
projection = {} | ||
// callback is undefined, will return a cursor | ||
break | ||
case 2: | ||
if (typeof projection === 'function') { | ||
callback = projection | ||
projection = {} | ||
} // If not assume projection is an object and callback undefined | ||
break | ||
} // If not assume projection is an object and callback undefined | ||
} | ||
const cursor = new Cursor(this, query, function (err, docs, callback) { | ||
if (err) { return callback(err) } | ||
if (docs.length === 1) { | ||
return callback(null, model.deepCopy(docs[0])) | ||
} else { | ||
return callback(null, null) | ||
} | ||
const cursor = new Cursor(this, query, (err, docs, callback) => { | ||
if (err) return callback(err) | ||
if (docs.length === 1) return callback(null, model.deepCopy(docs[0])) | ||
else return callback(null, null) | ||
}) | ||
cursor.projection(projection).limit(1) | ||
if (typeof callback === 'function') { | ||
cursor.exec(callback) | ||
} else { | ||
return cursor | ||
} | ||
if (typeof callback === 'function') cursor.exec(callback) | ||
else return cursor | ||
} | ||
@@ -591,6 +552,2 @@ | ||
_update (query, updateQuery, options, cb) { | ||
const self = this | ||
let numReplaced = 0 | ||
let i | ||
if (typeof options === 'function') { | ||
@@ -600,3 +557,3 @@ cb = options | ||
} | ||
const callback = cb || function () {} | ||
const callback = cb || (() => {}) | ||
const multi = options.multi !== undefined ? options.multi : false | ||
@@ -606,12 +563,11 @@ const upsert = options.upsert !== undefined ? options.upsert : false | ||
async.waterfall([ | ||
function (cb) { // If upsert option is set, check whether we need to insert the doc | ||
if (!upsert) { return cb() } | ||
cb => { // If upsert option is set, check whether we need to insert the doc | ||
if (!upsert) return cb() | ||
// Need to use an internal function not tied to the executor to avoid deadlock | ||
const cursor = new Cursor(self, query) | ||
cursor.limit(1)._exec(function (err, docs) { | ||
if (err) { return callback(err) } | ||
if (docs.length === 1) { | ||
return cb() | ||
} else { | ||
const cursor = new Cursor(this, query) | ||
cursor.limit(1)._exec((err, docs) => { | ||
if (err) return callback(err) | ||
if (docs.length === 1) return cb() | ||
else { | ||
let toBeInserted | ||
@@ -633,4 +589,4 @@ | ||
return self._insert(toBeInserted, function (err, newDoc) { | ||
if (err) { return callback(err) } | ||
return this._insert(toBeInserted, (err, newDoc) => { | ||
if (err) return callback(err) | ||
return callback(null, 1, newDoc, true) | ||
@@ -641,3 +597,4 @@ }) | ||
}, | ||
function () { // Perform the update | ||
() => { // Perform the update | ||
let numReplaced = 0 | ||
let modifiedDoc | ||
@@ -647,4 +604,4 @@ const modifications = [] | ||
self.getCandidates(query, function (err, candidates) { | ||
if (err) { return callback(err) } | ||
this.getCandidates(query, (err, candidates) => { | ||
if (err) return callback(err) | ||
@@ -654,12 +611,12 @@ // Preparing update (if an error is thrown here neither the datafile nor | ||
try { | ||
for (i = 0; i < candidates.length; i += 1) { | ||
if (model.match(candidates[i], query) && (multi || numReplaced === 0)) { | ||
for (const candidate of candidates) { | ||
if (model.match(candidate, query) && (multi || numReplaced === 0)) { | ||
numReplaced += 1 | ||
if (self.timestampData) { createdAt = candidates[i].createdAt } | ||
modifiedDoc = model.modify(candidates[i], updateQuery) | ||
if (self.timestampData) { | ||
if (this.timestampData) { createdAt = candidate.createdAt } | ||
modifiedDoc = model.modify(candidate, updateQuery) | ||
if (this.timestampData) { | ||
modifiedDoc.createdAt = createdAt | ||
modifiedDoc.updatedAt = new Date() | ||
} | ||
modifications.push({ oldDoc: candidates[i], newDoc: modifiedDoc }) | ||
modifications.push({ oldDoc: candidate, newDoc: modifiedDoc }) | ||
} | ||
@@ -673,3 +630,3 @@ } | ||
try { | ||
self.updateIndexes(modifications) | ||
this.updateIndexes(modifications) | ||
} catch (err) { | ||
@@ -680,5 +637,5 @@ return callback(err) | ||
// Update the datafile | ||
const updatedDocs = _.pluck(modifications, 'newDoc') | ||
self.persistence.persistNewState(updatedDocs, function (err) { | ||
if (err) { return callback(err) } | ||
const updatedDocs = modifications.map(x => x.newDoc) | ||
this.persistence.persistNewState(updatedDocs, err => { | ||
if (err) return callback(err) | ||
if (!options.returnUpdatedDocs) { | ||
@@ -688,4 +645,4 @@ return callback(null, numReplaced) | ||
let updatedDocsDC = [] | ||
updatedDocs.forEach(function (doc) { updatedDocsDC.push(model.deepCopy(doc)) }) | ||
if (!multi) { updatedDocsDC = updatedDocsDC[0] } | ||
updatedDocs.forEach(doc => { updatedDocsDC.push(model.deepCopy(doc)) }) | ||
if (!multi) updatedDocsDC = updatedDocsDC[0] | ||
return callback(null, numReplaced, updatedDocsDC) | ||
@@ -713,6 +670,2 @@ } | ||
_remove (query, options, cb) { | ||
const self = this | ||
let numRemoved = 0 | ||
const removedDocs = [] | ||
if (typeof options === 'function') { | ||
@@ -722,20 +675,24 @@ cb = options | ||
} | ||
const callback = cb || function () {} | ||
const callback = cb || (() => {}) | ||
const multi = options.multi !== undefined ? options.multi : false | ||
this.getCandidates(query, true, function (err, candidates) { | ||
if (err) { return callback(err) } | ||
this.getCandidates(query, true, (err, candidates) => { | ||
if (err) return callback(err) | ||
const removedDocs = [] | ||
let numRemoved = 0 | ||
try { | ||
candidates.forEach(function (d) { | ||
candidates.forEach(d => { | ||
if (model.match(d, query) && (multi || numRemoved === 0)) { | ||
numRemoved += 1 | ||
removedDocs.push({ $$deleted: true, _id: d._id }) | ||
self.removeFromIndexes(d) | ||
this.removeFromIndexes(d) | ||
} | ||
}) | ||
} catch (err) { return callback(err) } | ||
} catch (err) { | ||
return callback(err) | ||
} | ||
self.persistence.persistNewState(removedDocs, function (err) { | ||
if (err) { return callback(err) } | ||
this.persistence.persistNewState(removedDocs, err => { | ||
if (err) return callback(err) | ||
return callback(null, numRemoved) | ||
@@ -742,0 +699,0 @@ }) |
@@ -12,9 +12,8 @@ /** | ||
// This queue will execute all commands, one-by-one in order | ||
this.queue = async.queue(function (task, cb) { | ||
const newArguments = [] | ||
this.queue = async.queue((task, cb) => { | ||
// task.arguments is an array-like object on which adding a new field doesn't work, so we transform it into a real array | ||
for (let i = 0; i < task.arguments.length; i += 1) { newArguments.push(task.arguments[i]) } | ||
const lastArg = task.arguments[task.arguments.length - 1] | ||
const newArguments = Array.from(task.arguments) | ||
const lastArg = newArguments[newArguments.length - 1] | ||
// Always tell the queue task is complete. Execute callback if any was given. | ||
@@ -33,6 +32,6 @@ if (typeof lastArg === 'function') { | ||
// false/undefined/null supplied as callback | ||
newArguments[newArguments.length - 1] = function () { cb() } | ||
newArguments[newArguments.length - 1] = () => { cb() } | ||
} else { | ||
// Nothing supplied as callback | ||
newArguments.push(function () { cb() }) | ||
newArguments.push(() => { cb() }) | ||
} | ||
@@ -55,7 +54,4 @@ | ||
push (task, forceQueuing) { | ||
if (this.ready || forceQueuing) { | ||
this.queue.push(task) | ||
} else { | ||
this.buffer.push(task) | ||
} | ||
if (this.ready || forceQueuing) this.queue.push(task) | ||
else this.buffer.push(task) | ||
} | ||
@@ -68,5 +64,4 @@ | ||
processBuffer () { | ||
let i | ||
this.ready = true | ||
for (i = 0; i < this.buffer.length; i += 1) { this.queue.push(this.buffer[i]) } | ||
this.buffer.forEach(task => { this.queue.push(task) }) | ||
this.buffer = [] | ||
@@ -73,0 +68,0 @@ } |
@@ -1,4 +0,5 @@ | ||
const _ = require('underscore') | ||
const util = require('util') | ||
const BinarySearchTree = require('@seald-io/binary-search-tree').BinarySearchTree | ||
const model = require('./model.js') | ||
const { uniq } = require('./utils.js') | ||
@@ -8,5 +9,3 @@ /** | ||
*/ | ||
function checkValueEquality (a, b) { | ||
return a === b | ||
} | ||
const checkValueEquality = (a, b) => a === b | ||
@@ -17,7 +16,7 @@ /** | ||
function projectForUnique (elt) { | ||
if (elt === null) { return '$null' } | ||
if (typeof elt === 'string') { return '$string' + elt } | ||
if (typeof elt === 'boolean') { return '$boolean' + elt } | ||
if (typeof elt === 'number') { return '$number' + elt } | ||
if (Array.isArray(elt)) { return '$date' + elt.getTime() } | ||
if (elt === null) return '$null' | ||
if (typeof elt === 'string') return '$string' + elt | ||
if (typeof elt === 'boolean') return '$boolean' + elt | ||
if (typeof elt === 'number') return '$number' + elt | ||
if (util.types.isDate(elt)) return '$date' + elt.getTime() | ||
@@ -54,3 +53,3 @@ return elt // Arrays and objects, will check for pointer equality | ||
if (newData) { this.insert(newData) } | ||
if (newData) this.insert(newData) | ||
} | ||
@@ -65,4 +64,3 @@ | ||
let keys | ||
let i | ||
let failingI | ||
let failingIndex | ||
let error | ||
@@ -78,11 +76,10 @@ | ||
// We don't index documents that don't contain the field if the index is sparse | ||
if (key === undefined && this.sparse) { return } | ||
if (key === undefined && this.sparse) return | ||
if (!Array.isArray(key)) { | ||
this.tree.insert(key, doc) | ||
} else { | ||
if (!Array.isArray(key)) this.tree.insert(key, doc) | ||
else { | ||
// If an insert fails due to a unique constraint, roll back all inserts before it | ||
keys = _.uniq(key, projectForUnique) | ||
keys = uniq(key, projectForUnique) | ||
for (i = 0; i < keys.length; i += 1) { | ||
for (let i = 0; i < keys.length; i += 1) { | ||
try { | ||
@@ -92,3 +89,3 @@ this.tree.insert(keys[i], doc) | ||
error = e | ||
failingI = i | ||
failingIndex = i | ||
break | ||
@@ -99,3 +96,3 @@ } | ||
if (error) { | ||
for (i = 0; i < failingI; i += 1) { | ||
for (let i = 0; i < failingIndex; i += 1) { | ||
this.tree.delete(keys[i], doc) | ||
@@ -116,7 +113,6 @@ } | ||
insertMultipleDocs (docs) { | ||
let i | ||
let error | ||
let failingI | ||
let failingIndex | ||
for (i = 0; i < docs.length; i += 1) { | ||
for (let i = 0; i < docs.length; i += 1) { | ||
try { | ||
@@ -126,3 +122,3 @@ this.insert(docs[i]) | ||
error = e | ||
failingI = i | ||
failingIndex = i | ||
break | ||
@@ -133,3 +129,3 @@ } | ||
if (error) { | ||
for (i = 0; i < failingI; i += 1) { | ||
for (let i = 0; i < failingIndex; i += 1) { | ||
this.remove(docs[i]) | ||
@@ -149,6 +145,4 @@ } | ||
remove (doc) { | ||
const self = this | ||
if (Array.isArray(doc)) { | ||
doc.forEach(function (d) { self.remove(d) }) | ||
doc.forEach(d => { this.remove(d) }) | ||
return | ||
@@ -159,3 +153,3 @@ } | ||
if (key === undefined && this.sparse) { return } | ||
if (key === undefined && this.sparse) return | ||
@@ -165,4 +159,4 @@ if (!Array.isArray(key)) { | ||
} else { | ||
_.uniq(key, projectForUnique).forEach(function (_key) { | ||
self.tree.delete(_key, doc) | ||
uniq(key, projectForUnique).forEach(_key => { | ||
this.tree.delete(_key, doc) | ||
}) | ||
@@ -202,11 +196,10 @@ } | ||
updateMultipleDocs (pairs) { | ||
let i | ||
let failingI | ||
let failingIndex | ||
let error | ||
for (i = 0; i < pairs.length; i += 1) { | ||
for (let i = 0; i < pairs.length; i += 1) { | ||
this.remove(pairs[i].oldDoc) | ||
} | ||
for (i = 0; i < pairs.length; i += 1) { | ||
for (let i = 0; i < pairs.length; i += 1) { | ||
try { | ||
@@ -216,3 +209,3 @@ this.insert(pairs[i].newDoc) | ||
error = e | ||
failingI = i | ||
failingIndex = i | ||
break | ||
@@ -224,7 +217,7 @@ } | ||
if (error) { | ||
for (i = 0; i < failingI; i += 1) { | ||
for (let i = 0; i < failingIndex; i += 1) { | ||
this.remove(pairs[i].newDoc) | ||
} | ||
for (i = 0; i < pairs.length; i += 1) { | ||
for (let i = 0; i < pairs.length; i += 1) { | ||
this.insert(pairs[i].oldDoc) | ||
@@ -243,6 +236,5 @@ } | ||
if (!Array.isArray(oldDoc)) { | ||
this.update(newDoc, oldDoc) | ||
} else { | ||
oldDoc.forEach(function (pair) { | ||
if (!Array.isArray(oldDoc)) this.update(newDoc, oldDoc) | ||
else { | ||
oldDoc.forEach(pair => { | ||
revert.push({ oldDoc: pair.newDoc, newDoc: pair.oldDoc }) | ||
@@ -260,12 +252,9 @@ }) | ||
getMatching (value) { | ||
const self = this | ||
if (!Array.isArray(value)) { | ||
return self.tree.search(value) | ||
} else { | ||
if (!Array.isArray(value)) return this.tree.search(value) | ||
else { | ||
const _res = {} | ||
const res = [] | ||
value.forEach(function (v) { | ||
self.getMatching(v).forEach(function (doc) { | ||
value.forEach(v => { | ||
this.getMatching(v).forEach(doc => { | ||
_res[doc._id] = doc | ||
@@ -275,3 +264,3 @@ }) | ||
Object.keys(_res).forEach(function (_id) { | ||
Object.keys(_res).forEach(_id => { | ||
res.push(_res[_id]) | ||
@@ -301,8 +290,4 @@ }) | ||
this.tree.executeOnEveryNode(function (node) { | ||
let i | ||
for (i = 0; i < node.data.length; i += 1) { | ||
res.push(node.data[i]) | ||
} | ||
this.tree.executeOnEveryNode(node => { | ||
res.push(...node.data) | ||
}) | ||
@@ -309,0 +294,0 @@ |
582
lib/model.js
@@ -8,3 +8,3 @@ /** | ||
const util = require('util') | ||
const _ = require('underscore') | ||
const { uniq } = require('./utils.js') | ||
const modifierFunctions = {} | ||
@@ -24,14 +24,14 @@ const lastStepModifierFunctions = {} | ||
*/ | ||
function checkKey (k, v) { | ||
if (typeof k === 'number') { | ||
k = k.toString() | ||
} | ||
const checkKey = (k, v) => { | ||
if (typeof k === 'number') k = k.toString() | ||
if (k[0] === '$' && !(k === '$$date' && typeof v === 'number') && !(k === '$$deleted' && v === true) && !(k === '$$indexCreated') && !(k === '$$indexRemoved')) { | ||
throw new Error('Field names cannot begin with the $ character') | ||
} | ||
if ( | ||
k[0] === '$' && | ||
!(k === '$$date' && typeof v === 'number') && | ||
!(k === '$$deleted' && v === true) && | ||
!(k === '$$indexCreated') && | ||
!(k === '$$indexRemoved') | ||
) throw new Error('Field names cannot begin with the $ character') | ||
if (k.indexOf('.') !== -1) { | ||
throw new Error('Field names cannot contain a .') | ||
} | ||
if (k.indexOf('.') !== -1) throw new Error('Field names cannot contain a .') | ||
} | ||
@@ -43,5 +43,5 @@ | ||
*/ | ||
function checkObject (obj) { | ||
const checkObject = obj => { | ||
if (Array.isArray(obj)) { | ||
obj.forEach(function (o) { | ||
obj.forEach(o => { | ||
checkObject(o) | ||
@@ -52,6 +52,8 @@ }) | ||
if (typeof obj === 'object' && obj !== null) { | ||
Object.keys(obj).forEach(function (k) { | ||
checkKey(k, obj[k]) | ||
checkObject(obj[k]) | ||
}) | ||
for (const k in obj) { | ||
if (Object.prototype.hasOwnProperty.call(obj, k)) { | ||
checkKey(k, obj[k]) | ||
checkObject(obj[k]) | ||
} | ||
} | ||
} | ||
@@ -68,17 +70,15 @@ } | ||
*/ | ||
function serialize (obj) { | ||
const res = JSON.stringify(obj, function (k, v) { | ||
const serialize = obj => { | ||
return JSON.stringify(obj, function (k, v) { | ||
checkKey(k, v) | ||
if (v === undefined) { return undefined } | ||
if (v === null) { return null } | ||
if (v === undefined) return undefined | ||
if (v === null) return null | ||
// Hackish way of checking if object is Date (this way it works between execution contexts in node-webkit). | ||
// We can't use value directly because for dates it is already string in this function (date.toJSON was already called), so we use this | ||
if (typeof this[k].getTime === 'function') { return { $$date: this[k].getTime() } } | ||
if (typeof this[k].getTime === 'function') return { $$date: this[k].getTime() } | ||
return v | ||
}) | ||
return res | ||
} | ||
@@ -90,11 +90,14 @@ | ||
*/ | ||
function deserialize (rawData) { | ||
return JSON.parse(rawData, function (k, v) { | ||
if (k === '$$date') { return new Date(v) } | ||
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null) { return v } | ||
if (v && v.$$date) { return v.$$date } | ||
const deserialize = rawData => JSON.parse(rawData, function (k, v) { | ||
if (k === '$$date') return new Date(v) | ||
if ( | ||
typeof v === 'string' || | ||
typeof v === 'number' || | ||
typeof v === 'boolean' || | ||
v === null | ||
) return v | ||
if (v && v.$$date) return v.$$date | ||
return v | ||
}) | ||
} | ||
return v | ||
}) | ||
@@ -107,25 +110,22 @@ /** | ||
function deepCopy (obj, strictKeys) { | ||
let res | ||
if (typeof obj === 'boolean' || | ||
if ( | ||
typeof obj === 'boolean' || | ||
typeof obj === 'number' || | ||
typeof obj === 'string' || | ||
obj === null || | ||
(util.types.isDate(obj))) { | ||
return obj | ||
} | ||
(util.types.isDate(obj)) | ||
) return obj | ||
if (Array.isArray(obj)) { | ||
res = [] | ||
obj.forEach(function (o) { res.push(deepCopy(o, strictKeys)) }) | ||
return res | ||
} | ||
if (Array.isArray(obj)) return obj.map(o => deepCopy(o, strictKeys)) | ||
if (typeof obj === 'object') { | ||
res = {} | ||
Object.keys(obj).forEach(function (k) { | ||
if (!strictKeys || (k[0] !== '$' && k.indexOf('.') === -1)) { | ||
const res = {} | ||
for (const k in obj) { | ||
if ( | ||
Object.prototype.hasOwnProperty.call(obj, k) && | ||
(!strictKeys || (k[0] !== '$' && k.indexOf('.') === -1)) | ||
) { | ||
res[k] = deepCopy(obj[k], strictKeys) | ||
} | ||
}) | ||
} | ||
return res | ||
@@ -141,10 +141,10 @@ } | ||
*/ | ||
function isPrimitiveType (obj) { | ||
return (typeof obj === 'boolean' || | ||
typeof obj === 'number' || | ||
typeof obj === 'string' || | ||
obj === null || | ||
util.types.isDate(obj) || | ||
Array.isArray(obj)) | ||
} | ||
const isPrimitiveType = obj => ( | ||
typeof obj === 'boolean' || | ||
typeof obj === 'number' || | ||
typeof obj === 'string' || | ||
obj === null || | ||
util.types.isDate(obj) || | ||
Array.isArray(obj) | ||
) | ||
@@ -156,16 +156,14 @@ /** | ||
*/ | ||
function compareNSB (a, b) { | ||
if (a < b) { return -1 } | ||
if (a > b) { return 1 } | ||
const compareNSB = (a, b) => { | ||
if (a < b) return -1 | ||
if (a > b) return 1 | ||
return 0 | ||
} | ||
function compareArrays (a, b) { | ||
let i | ||
let comp | ||
const compareArrays = (a, b) => { | ||
const minLength = Math.min(a.length, b.length) | ||
for (let i = 0; i < minLength; i += 1) { | ||
const comp = compareThings(a[i], b[i]) | ||
for (i = 0; i < Math.min(a.length, b.length); i += 1) { | ||
comp = compareThings(a[i], b[i]) | ||
if (comp !== 0) { return comp } | ||
if (comp !== 0) return comp | ||
} | ||
@@ -187,34 +185,32 @@ | ||
*/ | ||
function compareThings (a, b, _compareStrings) { | ||
let comp | ||
let i | ||
const compareThings = (a, b, _compareStrings) => { | ||
const compareStrings = _compareStrings || compareNSB | ||
// undefined | ||
if (a === undefined) { return b === undefined ? 0 : -1 } | ||
if (b === undefined) { return a === undefined ? 0 : 1 } | ||
if (a === undefined) return b === undefined ? 0 : -1 | ||
if (b === undefined) return 1 // no need to test if a === undefined | ||
// null | ||
if (a === null) { return b === null ? 0 : -1 } | ||
if (b === null) { return a === null ? 0 : 1 } | ||
if (a === null) return b === null ? 0 : -1 | ||
if (b === null) return 1 // no need to test if a === null | ||
// Numbers | ||
if (typeof a === 'number') { return typeof b === 'number' ? compareNSB(a, b) : -1 } | ||
if (typeof b === 'number') { return typeof a === 'number' ? compareNSB(a, b) : 1 } | ||
if (typeof a === 'number') return typeof b === 'number' ? compareNSB(a, b) : -1 | ||
if (typeof b === 'number') return typeof a === 'number' ? compareNSB(a, b) : 1 | ||
// Strings | ||
if (typeof a === 'string') { return typeof b === 'string' ? compareStrings(a, b) : -1 } | ||
if (typeof b === 'string') { return typeof a === 'string' ? compareStrings(a, b) : 1 } | ||
if (typeof a === 'string') return typeof b === 'string' ? compareStrings(a, b) : -1 | ||
if (typeof b === 'string') return typeof a === 'string' ? compareStrings(a, b) : 1 | ||
// Booleans | ||
if (typeof a === 'boolean') { return typeof b === 'boolean' ? compareNSB(a, b) : -1 } | ||
if (typeof b === 'boolean') { return typeof a === 'boolean' ? compareNSB(a, b) : 1 } | ||
if (typeof a === 'boolean') return typeof b === 'boolean' ? compareNSB(a, b) : -1 | ||
if (typeof b === 'boolean') return typeof a === 'boolean' ? compareNSB(a, b) : 1 | ||
// Dates | ||
if (util.types.isDate(a)) { return util.types.isDate(b) ? compareNSB(a.getTime(), b.getTime()) : -1 } | ||
if (util.types.isDate(b)) { return util.types.isDate(a) ? compareNSB(a.getTime(), b.getTime()) : 1 } | ||
if (util.types.isDate(a)) return util.types.isDate(b) ? compareNSB(a.getTime(), b.getTime()) : -1 | ||
if (util.types.isDate(b)) return util.types.isDate(a) ? compareNSB(a.getTime(), b.getTime()) : 1 | ||
// Arrays (first element is most significant and so on) | ||
if (Array.isArray(a)) { return Array.isArray(b) ? compareArrays(a, b) : -1 } | ||
if (Array.isArray(b)) { return Array.isArray(a) ? compareArrays(a, b) : 1 } | ||
if (Array.isArray(a)) return Array.isArray(b) ? compareArrays(a, b) : -1 | ||
if (Array.isArray(b)) return Array.isArray(a) ? compareArrays(a, b) : 1 | ||
@@ -225,6 +221,6 @@ // Objects | ||
for (i = 0; i < Math.min(aKeys.length, bKeys.length); i += 1) { | ||
comp = compareThings(a[aKeys[i]], b[bKeys[i]]) | ||
for (let i = 0; i < Math.min(aKeys.length, bKeys.length); i += 1) { | ||
const comp = compareThings(a[aKeys[i]], b[bKeys[i]]) | ||
if (comp !== 0) { return comp } | ||
if (comp !== 0) return comp | ||
} | ||
@@ -251,3 +247,3 @@ | ||
*/ | ||
lastStepModifierFunctions.$set = function (obj, field, value) { | ||
lastStepModifierFunctions.$set = (obj, field, value) => { | ||
obj[field] = value | ||
@@ -259,3 +255,3 @@ } | ||
*/ | ||
lastStepModifierFunctions.$unset = function (obj, field, value) { | ||
lastStepModifierFunctions.$unset = (obj, field, value) => { | ||
delete obj[field] | ||
@@ -270,25 +266,30 @@ } | ||
*/ | ||
lastStepModifierFunctions.$push = function (obj, field, value) { | ||
lastStepModifierFunctions.$push = (obj, field, value) => { | ||
// Create the array if it doesn't exist | ||
if (!Object.prototype.hasOwnProperty.call(obj, field)) { obj[field] = [] } | ||
if (!Object.prototype.hasOwnProperty.call(obj, field)) obj[field] = [] | ||
if (!Array.isArray(obj[field])) { throw new Error('Can\'t $push an element on non-array values') } | ||
if (!Array.isArray(obj[field])) throw new Error('Can\'t $push an element on non-array values') | ||
if (value !== null && typeof value === 'object' && value.$slice && value.$each === undefined) { | ||
value.$each = [] | ||
} | ||
if ( | ||
value !== null && | ||
typeof value === 'object' && | ||
value.$slice && | ||
value.$each === undefined | ||
) value.$each = [] | ||
if (value !== null && typeof value === 'object' && value.$each) { | ||
if (Object.keys(value).length >= 3 || (Object.keys(value).length === 2 && value.$slice === undefined)) { throw new Error('Can only use $slice in cunjunction with $each when $push to array') } | ||
if (!Array.isArray(value.$each)) { throw new Error('$each requires an array value') } | ||
if ( | ||
Object.keys(value).length >= 3 || | ||
(Object.keys(value).length === 2 && value.$slice === undefined) | ||
) throw new Error('Can only use $slice in cunjunction with $each when $push to array') | ||
if (!Array.isArray(value.$each)) throw new Error('$each requires an array value') | ||
value.$each.forEach(function (v) { | ||
value.$each.forEach(v => { | ||
obj[field].push(v) | ||
}) | ||
if (value.$slice === undefined || typeof value.$slice !== 'number') { return } | ||
if (value.$slice === undefined || typeof value.$slice !== 'number') return | ||
if (value.$slice === 0) { | ||
obj[field] = [] | ||
} else { | ||
if (value.$slice === 0) obj[field] = [] | ||
else { | ||
let start | ||
@@ -316,22 +317,21 @@ let end | ||
*/ | ||
lastStepModifierFunctions.$addToSet = function (obj, field, value) { | ||
let addToSet = true | ||
lastStepModifierFunctions.$addToSet = (obj, field, value) => { | ||
// Create the array if it doesn't exist | ||
if (!Object.prototype.hasOwnProperty.call(obj, field)) { obj[field] = [] } | ||
if (!Array.isArray(obj[field])) { throw new Error('Can\'t $addToSet an element on non-array values') } | ||
if (!Array.isArray(obj[field])) throw new Error('Can\'t $addToSet an element on non-array values') | ||
if (value !== null && typeof value === 'object' && value.$each) { | ||
if (Object.keys(value).length > 1) { throw new Error('Can\'t use another field in conjunction with $each') } | ||
if (!Array.isArray(value.$each)) { throw new Error('$each requires an array value') } | ||
if (Object.keys(value).length > 1) throw new Error('Can\'t use another field in conjunction with $each') | ||
if (!Array.isArray(value.$each)) throw new Error('$each requires an array value') | ||
value.$each.forEach(function (v) { | ||
value.$each.forEach(v => { | ||
lastStepModifierFunctions.$addToSet(obj, field, v) | ||
}) | ||
} else { | ||
obj[field].forEach(function (v) { | ||
if (compareThings(v, value) === 0) { addToSet = false } | ||
let addToSet = true | ||
obj[field].forEach(v => { | ||
if (compareThings(v, value) === 0) addToSet = false | ||
}) | ||
if (addToSet) { obj[field].push(value) } | ||
if (addToSet) obj[field].push(value) | ||
} | ||
@@ -343,12 +343,9 @@ } | ||
*/ | ||
lastStepModifierFunctions.$pop = function (obj, field, value) { | ||
if (!Array.isArray(obj[field])) { throw new Error('Can\'t $pop an element from non-array values') } | ||
if (typeof value !== 'number') { throw new Error(value + ' isn\'t an integer, can\'t use it with $pop') } | ||
if (value === 0) { return } | ||
lastStepModifierFunctions.$pop = (obj, field, value) => { | ||
if (!Array.isArray(obj[field])) throw new Error('Can\'t $pop an element from non-array values') | ||
if (typeof value !== 'number') throw new Error(`${value} isn't an integer, can't use it with $pop`) | ||
if (value === 0) return | ||
if (value > 0) { | ||
obj[field] = obj[field].slice(0, obj[field].length - 1) | ||
} else { | ||
obj[field] = obj[field].slice(1) | ||
} | ||
if (value > 0) obj[field] = obj[field].slice(0, obj[field].length - 1) | ||
else obj[field] = obj[field].slice(1) | ||
} | ||
@@ -359,10 +356,8 @@ | ||
*/ | ||
lastStepModifierFunctions.$pull = function (obj, field, value) { | ||
if (!Array.isArray(obj[field])) { throw new Error('Can\'t $pull an element from non-array values') } | ||
lastStepModifierFunctions.$pull = (obj, field, value) => { | ||
if (!Array.isArray(obj[field])) throw new Error('Can\'t $pull an element from non-array values') | ||
const arr = obj[field] | ||
for (let i = arr.length - 1; i >= 0; i -= 1) { | ||
if (match(arr[i], value)) { | ||
arr.splice(i, 1) | ||
} | ||
if (match(arr[i], value)) arr.splice(i, 1) | ||
} | ||
@@ -374,14 +369,9 @@ } | ||
*/ | ||
lastStepModifierFunctions.$inc = function (obj, field, value) { | ||
if (typeof value !== 'number') { throw new Error(value + ' must be a number') } | ||
lastStepModifierFunctions.$inc = (obj, field, value) => { | ||
if (typeof value !== 'number') throw new Error(`${value} must be a number`) | ||
if (typeof obj[field] !== 'number') { | ||
if (!_.has(obj, field)) { | ||
obj[field] = value | ||
} else { | ||
throw new Error('Don\'t use the $inc modifier on non-number fields') | ||
} | ||
} else { | ||
obj[field] += value | ||
} | ||
if (!Object.prototype.hasOwnProperty.call(obj, field)) obj[field] = value | ||
else throw new Error('Don\'t use the $inc modifier on non-number fields') | ||
} else obj[field] += value | ||
} | ||
@@ -392,8 +382,5 @@ | ||
*/ | ||
lastStepModifierFunctions.$max = function (obj, field, value) { | ||
if (typeof obj[field] === 'undefined') { | ||
obj[field] = value | ||
} else if (value > obj[field]) { | ||
obj[field] = value | ||
} | ||
lastStepModifierFunctions.$max = (obj, field, value) => { | ||
if (typeof obj[field] === 'undefined') obj[field] = value | ||
else if (value > obj[field]) obj[field] = value | ||
} | ||
@@ -404,24 +391,18 @@ | ||
*/ | ||
lastStepModifierFunctions.$min = function (obj, field, value) { | ||
if (typeof obj[field] === 'undefined') { | ||
obj[field] = value | ||
} else if (value < obj[field]) { | ||
obj[field] = value | ||
} | ||
lastStepModifierFunctions.$min = (obj, field, value) => { | ||
if (typeof obj[field] === 'undefined') obj[field] = value | ||
else if (value < obj[field]) obj[field] = value | ||
} | ||
// Given its name, create the complete modifier function | ||
function createModifierFunction (modifier) { | ||
return function (obj, field, value) { | ||
const fieldParts = typeof field === 'string' ? field.split('.') : field | ||
const createModifierFunction = modifier => (obj, field, value) => { | ||
const fieldParts = typeof field === 'string' ? field.split('.') : field | ||
if (fieldParts.length === 1) { | ||
lastStepModifierFunctions[modifier](obj, field, value) | ||
} else { | ||
if (obj[fieldParts[0]] === undefined) { | ||
if (modifier === '$unset') { return } // Bad looking specific fix, needs to be generalized modifiers that behave like $unset are implemented | ||
obj[fieldParts[0]] = {} | ||
} | ||
modifierFunctions[modifier](obj[fieldParts[0]], fieldParts.slice(1), value) | ||
if (fieldParts.length === 1) lastStepModifierFunctions[modifier](obj, field, value) | ||
else { | ||
if (obj[fieldParts[0]] === undefined) { | ||
if (modifier === '$unset') return // Bad looking specific fix, needs to be generalized modifiers that behave like $unset are implemented | ||
obj[fieldParts[0]] = {} | ||
} | ||
modifierFunctions[modifier](obj[fieldParts[0]], fieldParts.slice(1), value) | ||
} | ||
@@ -431,3 +412,3 @@ } | ||
// Actually create all modifier functions | ||
Object.keys(lastStepModifierFunctions).forEach(function (modifier) { | ||
Object.keys(lastStepModifierFunctions).forEach(modifier => { | ||
modifierFunctions[modifier] = createModifierFunction(modifier) | ||
@@ -439,14 +420,12 @@ }) | ||
*/ | ||
function modify (obj, updateQuery) { | ||
const modify = (obj, updateQuery) => { | ||
const keys = Object.keys(updateQuery) | ||
const firstChars = _.map(keys, function (item) { return item[0] }) | ||
const dollarFirstChars = _.filter(firstChars, function (c) { return c === '$' }) | ||
const firstChars = keys.map(item => item[0]) | ||
const dollarFirstChars = firstChars.filter(c => c === '$') | ||
let newDoc | ||
let modifiers | ||
if (keys.indexOf('_id') !== -1 && updateQuery._id !== obj._id) { throw new Error('You cannot change a document\'s _id') } | ||
if (keys.indexOf('_id') !== -1 && updateQuery._id !== obj._id) throw new Error('You cannot change a document\'s _id') | ||
if (dollarFirstChars.length !== 0 && dollarFirstChars.length !== firstChars.length) { | ||
throw new Error('You cannot mix modifiers and normal fields') | ||
} | ||
if (dollarFirstChars.length !== 0 && dollarFirstChars.length !== firstChars.length) throw new Error('You cannot mix modifiers and normal fields') | ||
@@ -459,15 +438,13 @@ if (dollarFirstChars.length === 0) { | ||
// Apply modifiers | ||
modifiers = _.uniq(keys) | ||
modifiers = uniq(keys) | ||
newDoc = deepCopy(obj) | ||
modifiers.forEach(function (m) { | ||
if (!modifierFunctions[m]) { throw new Error('Unknown modifier ' + m) } | ||
modifiers.forEach(m => { | ||
if (!modifierFunctions[m]) throw new Error(`Unknown modifier ${m}`) | ||
// Can't rely on Object.keys throwing on non objects since ES6 | ||
// Not 100% satisfying as non objects can be interpreted as objects but no false negatives so we can live with it | ||
if (typeof updateQuery[m] !== 'object') { | ||
throw new Error('Modifier ' + m + '\'s argument must be an object') | ||
} | ||
if (typeof updateQuery[m] !== 'object') throw new Error(`Modifier ${m}'s argument must be an object`) | ||
const keys = Object.keys(updateQuery[m]) | ||
keys.forEach(function (k) { | ||
keys.forEach(k => { | ||
modifierFunctions[m](newDoc, k, updateQuery[m][k]) | ||
@@ -481,3 +458,3 @@ }) | ||
if (obj._id !== newDoc._id) { throw new Error('You can\'t change a document\'s _id') } | ||
if (obj._id !== newDoc._id) throw new Error('You can\'t change a document\'s _id') | ||
return newDoc | ||
@@ -495,29 +472,19 @@ } | ||
*/ | ||
function getDotValue (obj, field) { | ||
const getDotValue = (obj, field) => { | ||
const fieldParts = typeof field === 'string' ? field.split('.') : field | ||
let i | ||
let objs | ||
if (!obj) { return undefined } // field cannot be empty so that means we should return undefined so that nothing can match | ||
if (!obj) return undefined // field cannot be empty so that means we should return undefined so that nothing can match | ||
if (fieldParts.length === 0) { return obj } | ||
if (fieldParts.length === 0) return obj | ||
if (fieldParts.length === 1) { return obj[fieldParts[0]] } | ||
if (fieldParts.length === 1) return obj[fieldParts[0]] | ||
if (Array.isArray(obj[fieldParts[0]])) { | ||
// If the next field is an integer, return only this item of the array | ||
i = parseInt(fieldParts[1], 10) | ||
if (typeof i === 'number' && !isNaN(i)) { | ||
return getDotValue(obj[fieldParts[0]][i], fieldParts.slice(2)) | ||
} | ||
const i = parseInt(fieldParts[1], 10) | ||
if (typeof i === 'number' && !isNaN(i)) return getDotValue(obj[fieldParts[0]][i], fieldParts.slice(2)) | ||
// Return the array of values | ||
objs = [] | ||
for (i = 0; i < obj[fieldParts[0]].length; i += 1) { | ||
objs.push(getDotValue(obj[fieldParts[0]][i], fieldParts.slice(1))) | ||
} | ||
return objs | ||
} else { | ||
return getDotValue(obj[fieldParts[0]], fieldParts.slice(1)) | ||
} | ||
return obj[fieldParts[0]].map(el => getDotValue(el, fieldParts.slice(1))) | ||
} else return getDotValue(obj[fieldParts[0]], fieldParts.slice(1)) | ||
} | ||
@@ -531,20 +498,29 @@ | ||
*/ | ||
function areThingsEqual (a, b) { | ||
let aKeys | ||
let bKeys | ||
let i | ||
const areThingsEqual = (a, b) => { | ||
// Strings, booleans, numbers, null | ||
if (a === null || typeof a === 'string' || typeof a === 'boolean' || typeof a === 'number' || | ||
b === null || typeof b === 'string' || typeof b === 'boolean' || typeof b === 'number') { return a === b } | ||
if ( | ||
a === null || | ||
typeof a === 'string' || | ||
typeof a === 'boolean' || | ||
typeof a === 'number' || | ||
b === null || | ||
typeof b === 'string' || | ||
typeof b === 'boolean' || | ||
typeof b === 'number' | ||
) return a === b | ||
// Dates | ||
if (util.types.isDate(a) || util.types.isDate(b)) { return util.types.isDate(a) && util.types.isDate(b) && a.getTime() === b.getTime() } | ||
if (util.types.isDate(a) || util.types.isDate(b)) return util.types.isDate(a) && util.types.isDate(b) && a.getTime() === b.getTime() | ||
// Arrays (no match since arrays are used as a $in) | ||
// undefined (no match since they mean field doesn't exist and can't be serialized) | ||
if ((!(Array.isArray(a) && Array.isArray(b)) && (Array.isArray(a) || Array.isArray(b))) || a === undefined || b === undefined) { return false } | ||
if ( | ||
(!(Array.isArray(a) && Array.isArray(b)) && (Array.isArray(a) || Array.isArray(b))) || | ||
a === undefined || b === undefined | ||
) return false | ||
// General objects (check for deep equality) | ||
// a and b should be objects at this point | ||
let aKeys | ||
let bKeys | ||
try { | ||
@@ -557,6 +533,6 @@ aKeys = Object.keys(a) | ||
if (aKeys.length !== bKeys.length) { return false } | ||
for (i = 0; i < aKeys.length; i += 1) { | ||
if (bKeys.indexOf(aKeys[i]) === -1) { return false } | ||
if (!areThingsEqual(a[aKeys[i]], b[aKeys[i]])) { return false } | ||
if (aKeys.length !== bKeys.length) return false | ||
for (const el of aKeys) { | ||
if (bKeys.indexOf(el) === -1) return false | ||
if (!areThingsEqual(a[el], b[el])) return false | ||
} | ||
@@ -569,9 +545,13 @@ return true | ||
*/ | ||
function areComparable (a, b) { | ||
if (typeof a !== 'string' && typeof a !== 'number' && !util.types.isDate(a) && | ||
typeof b !== 'string' && typeof b !== 'number' && !util.types.isDate(b)) { | ||
return false | ||
} | ||
const areComparable = (a, b) => { | ||
if ( | ||
typeof a !== 'string' && | ||
typeof a !== 'number' && | ||
!util.types.isDate(a) && | ||
typeof b !== 'string' && | ||
typeof b !== 'number' && | ||
!util.types.isDate(b) | ||
) return false | ||
if (typeof a !== typeof b) { return false } | ||
if (typeof a !== typeof b) return false | ||
@@ -586,30 +566,17 @@ return true | ||
*/ | ||
comparisonFunctions.$lt = function (a, b) { | ||
return areComparable(a, b) && a < b | ||
} | ||
comparisonFunctions.$lt = (a, b) => areComparable(a, b) && a < b | ||
comparisonFunctions.$lte = function (a, b) { | ||
return areComparable(a, b) && a <= b | ||
} | ||
comparisonFunctions.$lte = (a, b) => areComparable(a, b) && a <= b | ||
comparisonFunctions.$gt = function (a, b) { | ||
return areComparable(a, b) && a > b | ||
} | ||
comparisonFunctions.$gt = (a, b) => areComparable(a, b) && a > b | ||
comparisonFunctions.$gte = function (a, b) { | ||
return areComparable(a, b) && a >= b | ||
} | ||
comparisonFunctions.$gte = (a, b) => areComparable(a, b) && a >= b | ||
comparisonFunctions.$ne = function (a, b) { | ||
if (a === undefined) { return true } | ||
return !areThingsEqual(a, b) | ||
} | ||
comparisonFunctions.$ne = (a, b) => a === undefined || !areThingsEqual(a, b) | ||
comparisonFunctions.$in = function (a, b) { | ||
let i | ||
comparisonFunctions.$in = (a, b) => { | ||
if (!Array.isArray(b)) throw new Error('$in operator called with a non-array') | ||
if (!Array.isArray(b)) { throw new Error('$in operator called with a non-array') } | ||
for (i = 0; i < b.length; i += 1) { | ||
if (areThingsEqual(a, b[i])) { return true } | ||
for (const el of b) { | ||
if (areThingsEqual(a, el)) return true | ||
} | ||
@@ -620,4 +587,4 @@ | ||
comparisonFunctions.$nin = function (a, b) { | ||
if (!Array.isArray(b)) { throw new Error('$nin operator called with a non-array') } | ||
comparisonFunctions.$nin = (a, b) => { | ||
if (!Array.isArray(b)) throw new Error('$nin operator called with a non-array') | ||
@@ -627,45 +594,32 @@ return !comparisonFunctions.$in(a, b) | ||
comparisonFunctions.$regex = function (a, b) { | ||
if (!util.types.isRegExp(b)) { throw new Error('$regex operator called with non regular expression') } | ||
comparisonFunctions.$regex = (a, b) => { | ||
if (!util.types.isRegExp(b)) throw new Error('$regex operator called with non regular expression') | ||
if (typeof a !== 'string') { | ||
return false | ||
} else { | ||
return b.test(a) | ||
} | ||
if (typeof a !== 'string') return false | ||
else return b.test(a) | ||
} | ||
comparisonFunctions.$exists = function (value, exists) { | ||
if (exists || exists === '') { // This will be true for all values of stat except false, null, undefined and 0 | ||
exists = true // That's strange behaviour (we should only use true/false) but that's the way Mongo does it... | ||
} else { | ||
exists = false | ||
} | ||
comparisonFunctions.$exists = (value, exists) => { | ||
// This will be true for all values of stat except false, null, undefined and 0 | ||
// That's strange behaviour (we should only use true/false) but that's the way Mongo does it... | ||
if (exists || exists === '') exists = true | ||
else exists = false | ||
if (value === undefined) { | ||
return !exists | ||
} else { | ||
return exists | ||
} | ||
if (value === undefined) return !exists | ||
else return exists | ||
} | ||
// Specific to arrays | ||
comparisonFunctions.$size = function (obj, value) { | ||
if (!Array.isArray(obj)) { return false } | ||
if (value % 1 !== 0) { throw new Error('$size operator called without an integer') } | ||
comparisonFunctions.$size = (obj, value) => { | ||
if (!Array.isArray(obj)) return false | ||
if (value % 1 !== 0) throw new Error('$size operator called without an integer') | ||
return obj.length === value | ||
} | ||
comparisonFunctions.$elemMatch = function (obj, value) { | ||
if (!Array.isArray(obj)) { return false } | ||
let i = obj.length | ||
let result = false // Initialize result | ||
while (i--) { | ||
if (match(obj[i], value)) { // If match for array element, return true | ||
result = true | ||
break | ||
} | ||
} | ||
return result | ||
comparisonFunctions.$elemMatch = (obj, value) => { | ||
if (!Array.isArray(obj)) return false | ||
return obj.some(el => match(el, value)) | ||
} | ||
arrayComparisonFunctions.$size = true | ||
@@ -679,9 +633,7 @@ arrayComparisonFunctions.$elemMatch = true | ||
*/ | ||
logicalOperators.$or = function (obj, query) { | ||
let i | ||
logicalOperators.$or = (obj, query) => { | ||
if (!Array.isArray(query)) throw new Error('$or operator used without an array') | ||
if (!Array.isArray(query)) { throw new Error('$or operator used without an array') } | ||
for (i = 0; i < query.length; i += 1) { | ||
if (match(obj, query[i])) { return true } | ||
for (let i = 0; i < query.length; i += 1) { | ||
if (match(obj, query[i])) return true | ||
} | ||
@@ -697,9 +649,7 @@ | ||
*/ | ||
logicalOperators.$and = function (obj, query) { | ||
let i | ||
logicalOperators.$and = (obj, query) => { | ||
if (!Array.isArray(query)) throw new Error('$and operator used without an array') | ||
if (!Array.isArray(query)) { throw new Error('$and operator used without an array') } | ||
for (i = 0; i < query.length; i += 1) { | ||
if (!match(obj, query[i])) { return false } | ||
for (let i = 0; i < query.length; i += 1) { | ||
if (!match(obj, query[i])) return false | ||
} | ||
@@ -715,5 +665,3 @@ | ||
*/ | ||
logicalOperators.$not = function (obj, query) { | ||
return !match(obj, query) | ||
} | ||
logicalOperators.$not = (obj, query) => !match(obj, query) | ||
@@ -725,7 +673,7 @@ /** | ||
*/ | ||
logicalOperators.$where = function (obj, fn) { | ||
if (!_.isFunction(fn)) { throw new Error('$where operator used without a function') } | ||
logicalOperators.$where = (obj, fn) => { | ||
if (typeof fn !== 'function') throw new Error('$where operator used without a function') | ||
const result = fn.call(obj) | ||
if (!_.isBoolean(result)) { throw new Error('$where function must return boolean') } | ||
if (typeof result !== 'boolean') throw new Error('$where function must return boolean') | ||
@@ -740,25 +688,16 @@ return result | ||
*/ | ||
function match (obj, query) { | ||
let queryKey | ||
let queryValue | ||
let i | ||
const match = (obj, query) => { | ||
// Primitive query against a primitive type | ||
// This is a bit of a hack since we construct an object with an arbitrary key only to dereference it later | ||
// But I don't have time for a cleaner implementation now | ||
if (isPrimitiveType(obj) || isPrimitiveType(query)) { | ||
return matchQueryPart({ needAKey: obj }, 'needAKey', query) | ||
} | ||
if (isPrimitiveType(obj) || isPrimitiveType(query)) return matchQueryPart({ needAKey: obj }, 'needAKey', query) | ||
// Normal query | ||
const queryKeys = Object.keys(query) | ||
for (i = 0; i < queryKeys.length; i += 1) { | ||
queryKey = queryKeys[i] | ||
queryValue = query[queryKey] | ||
if (queryKey[0] === '$') { | ||
if (!logicalOperators[queryKey]) { throw new Error('Unknown logical operator ' + queryKey) } | ||
if (!logicalOperators[queryKey](obj, queryValue)) { return false } | ||
} else { | ||
if (!matchQueryPart(obj, queryKey, queryValue)) { return false } | ||
for (const queryKey in query) { | ||
if (Object.prototype.hasOwnProperty.call(query, queryKey)) { | ||
const queryValue = query[queryKey] | ||
if (queryKey[0] === '$') { | ||
if (!logicalOperators[queryKey]) throw new Error(`Unknown logical operator ${queryKey}`) | ||
if (!logicalOperators[queryKey](obj, queryValue)) return false | ||
} else if (!matchQueryPart(obj, queryKey, queryValue)) return false | ||
} | ||
@@ -776,6 +715,2 @@ } | ||
const objValue = getDotValue(obj, queryKey) | ||
let i | ||
let keys | ||
let firstChars | ||
let dollarFirstChars | ||
@@ -785,11 +720,8 @@ // Check if the value is an array if we don't force a treatment as value | ||
// If the queryValue is an array, try to perform an exact match | ||
if (Array.isArray(queryValue)) { | ||
return matchQueryPart(obj, queryKey, queryValue, true) | ||
} | ||
if (Array.isArray(queryValue)) return matchQueryPart(obj, queryKey, queryValue, true) | ||
// Check if we are using an array-specific comparison function | ||
if (queryValue !== null && typeof queryValue === 'object' && !util.types.isRegExp(queryValue)) { | ||
keys = Object.keys(queryValue) | ||
for (i = 0; i < keys.length; i += 1) { | ||
if (arrayComparisonFunctions[keys[i]]) { return matchQueryPart(obj, queryKey, queryValue, true) } | ||
for (const key in queryValue) { | ||
if (Object.prototype.hasOwnProperty.call(queryValue, key) && arrayComparisonFunctions[key]) { return matchQueryPart(obj, queryKey, queryValue, true) } | ||
} | ||
@@ -799,4 +731,4 @@ } | ||
// If not, treat it as an array of { obj, query } where there needs to be at least one match | ||
for (i = 0; i < objValue.length; i += 1) { | ||
if (matchQueryPart({ k: objValue[i] }, 'k', queryValue)) { return true } // k here could be any string | ||
for (const el of objValue) { | ||
if (matchQueryPart({ k: el }, 'k', queryValue)) return true // k here could be any string | ||
} | ||
@@ -809,16 +741,14 @@ return false | ||
if (queryValue !== null && typeof queryValue === 'object' && !util.types.isRegExp(queryValue) && !Array.isArray(queryValue)) { | ||
keys = Object.keys(queryValue) | ||
firstChars = _.map(keys, function (item) { return item[0] }) | ||
dollarFirstChars = _.filter(firstChars, function (c) { return c === '$' }) | ||
const keys = Object.keys(queryValue) | ||
const firstChars = keys.map(item => item[0]) | ||
const dollarFirstChars = firstChars.filter(c => c === '$') | ||
if (dollarFirstChars.length !== 0 && dollarFirstChars.length !== firstChars.length) { | ||
throw new Error('You cannot mix operators and normal fields') | ||
} | ||
if (dollarFirstChars.length !== 0 && dollarFirstChars.length !== firstChars.length) throw new Error('You cannot mix operators and normal fields') | ||
// queryValue is an object of this form: { $comparisonOperator1: value1, ... } | ||
if (dollarFirstChars.length > 0) { | ||
for (i = 0; i < keys.length; i += 1) { | ||
if (!comparisonFunctions[keys[i]]) { throw new Error('Unknown comparison function ' + keys[i]) } | ||
for (const key of keys) { | ||
if (!comparisonFunctions[key]) throw new Error(`Unknown comparison function ${key}`) | ||
if (!comparisonFunctions[keys[i]](objValue, queryValue[keys[i]])) { return false } | ||
if (!comparisonFunctions[key](objValue, queryValue[key])) return false | ||
} | ||
@@ -830,9 +760,7 @@ return true | ||
// Using regular expressions with basic querying | ||
if (util.types.isRegExp(queryValue)) { return comparisonFunctions.$regex(objValue, queryValue) } | ||
if (util.types.isRegExp(queryValue)) return comparisonFunctions.$regex(objValue, queryValue) | ||
// queryValue is either a native value or a normal object | ||
// Basic matching is possible | ||
if (!areThingsEqual(objValue, queryValue)) { return false } | ||
return true | ||
return areThingsEqual(objValue, queryValue) | ||
} | ||
@@ -839,0 +767,0 @@ |
@@ -22,6 +22,2 @@ /** | ||
constructor (options) { | ||
let i | ||
let j | ||
let randomString | ||
this.db = options.db | ||
@@ -32,18 +28,24 @@ this.inMemoryOnly = this.db.inMemoryOnly | ||
if (!this.inMemoryOnly && this.filename && this.filename.charAt(this.filename.length - 1) === '~') { | ||
throw new Error('The datafile name can\'t end with a ~, which is reserved for crash safe backup files') | ||
} | ||
if ( | ||
!this.inMemoryOnly && | ||
this.filename && | ||
this.filename.charAt(this.filename.length - 1) === '~' | ||
) throw new Error('The datafile name can\'t end with a ~, which is reserved for crash safe backup files') | ||
// After serialization and before deserialization hooks with some basic sanity checks | ||
if (options.afterSerialization && !options.beforeDeserialization) { | ||
throw new Error('Serialization hook defined but deserialization hook undefined, cautiously refusing to start NeDB to prevent dataloss') | ||
} | ||
if (!options.afterSerialization && options.beforeDeserialization) { | ||
throw new Error('Serialization hook undefined but deserialization hook defined, cautiously refusing to start NeDB to prevent dataloss') | ||
} | ||
this.afterSerialization = options.afterSerialization || function (s) { return s } | ||
this.beforeDeserialization = options.beforeDeserialization || function (s) { return s } | ||
for (i = 1; i < 30; i += 1) { | ||
for (j = 0; j < 10; j += 1) { | ||
randomString = customUtils.uid(i) | ||
if ( | ||
options.afterSerialization && | ||
!options.beforeDeserialization | ||
) throw new Error('Serialization hook defined but deserialization hook undefined, cautiously refusing to start NeDB to prevent dataloss') | ||
if ( | ||
!options.afterSerialization && | ||
options.beforeDeserialization | ||
) throw new Error('Serialization hook undefined but deserialization hook defined, cautiously refusing to start NeDB to prevent dataloss') | ||
this.afterSerialization = options.afterSerialization || (s => s) | ||
this.beforeDeserialization = options.beforeDeserialization || (s => s) | ||
for (let i = 1; i < 30; i += 1) { | ||
for (let j = 0; j < 10; j += 1) { | ||
const randomString = customUtils.uid(i) | ||
if (this.beforeDeserialization(this.afterSerialization(randomString)) !== randomString) { | ||
@@ -72,21 +74,19 @@ throw new Error('beforeDeserialization is not the reverse of afterSerialization, cautiously refusing to start NeDB to prevent dataloss') | ||
* while the data file is append-only so it may grow larger | ||
* @param {Function} cb Optional callback, signature: err | ||
* @param {Function} callback Optional callback, signature: err | ||
*/ | ||
persistCachedDatabase (cb) { | ||
const callback = cb || function () {} | ||
persistCachedDatabase (callback = () => {}) { | ||
let toPersist = '' | ||
const self = this | ||
if (this.inMemoryOnly) { return callback(null) } | ||
if (this.inMemoryOnly) return callback(null) | ||
this.db.getAllData().forEach(function (doc) { | ||
toPersist += self.afterSerialization(model.serialize(doc)) + '\n' | ||
this.db.getAllData().forEach(doc => { | ||
toPersist += this.afterSerialization(model.serialize(doc)) + '\n' | ||
}) | ||
Object.keys(this.db.indexes).forEach(function (fieldName) { | ||
Object.keys(this.db.indexes).forEach(fieldName => { | ||
if (fieldName !== '_id') { // The special _id index is managed by datastore.js, the others need to be persisted | ||
toPersist += self.afterSerialization(model.serialize({ | ||
toPersist += this.afterSerialization(model.serialize({ | ||
$$indexCreated: { | ||
fieldName: fieldName, | ||
unique: self.db.indexes[fieldName].unique, | ||
sparse: self.db.indexes[fieldName].sparse | ||
unique: this.db.indexes[fieldName].unique, | ||
sparse: this.db.indexes[fieldName].sparse | ||
} | ||
@@ -97,5 +97,5 @@ })) + '\n' | ||
storage.crashSafeWriteFile(this.filename, toPersist, function (err) { | ||
if (err) { return callback(err) } | ||
self.db.emit('compaction.done') | ||
storage.crashSafeWriteFile(this.filename, toPersist, err => { | ||
if (err) return callback(err) | ||
this.db.emit('compaction.done') | ||
return callback(null) | ||
@@ -117,3 +117,2 @@ }) | ||
setAutocompactionInterval (interval) { | ||
const self = this | ||
const minInterval = 5000 | ||
@@ -124,4 +123,4 @@ const realInterval = Math.max(interval || 0, minInterval) | ||
this.autocompactionIntervalId = setInterval(function () { | ||
self.compactDatafile() | ||
this.autocompactionIntervalId = setInterval(() => { | ||
this.compactDatafile() | ||
}, realInterval) | ||
@@ -134,3 +133,3 @@ } | ||
stopAutocompaction () { | ||
if (this.autocompactionIntervalId) { clearInterval(this.autocompactionIntervalId) } | ||
if (this.autocompactionIntervalId) clearInterval(this.autocompactionIntervalId) | ||
} | ||
@@ -142,21 +141,17 @@ | ||
* @param {Array} newDocs Can be empty if no doc was updated/removed | ||
* @param {Function} cb Optional, signature: err | ||
* @param {Function} callback Optional, signature: err | ||
*/ | ||
persistNewState (newDocs, cb) { | ||
const self = this | ||
persistNewState (newDocs, callback = () => {}) { | ||
let toPersist = '' | ||
const callback = cb || function () {} | ||
// In-memory only datastore | ||
if (self.inMemoryOnly) { return callback(null) } | ||
if (this.inMemoryOnly) return callback(null) | ||
newDocs.forEach(function (doc) { | ||
toPersist += self.afterSerialization(model.serialize(doc)) + '\n' | ||
newDocs.forEach(doc => { | ||
toPersist += this.afterSerialization(model.serialize(doc)) + '\n' | ||
}) | ||
if (toPersist.length === 0) { return callback(null) } | ||
if (toPersist.length === 0) return callback(null) | ||
storage.appendFile(self.filename, toPersist, 'utf8', function (err) { | ||
return callback(err) | ||
}) | ||
storage.appendFile(this.filename, toPersist, 'utf8', err => callback(err)) | ||
} | ||
@@ -172,22 +167,13 @@ | ||
const tdata = [] | ||
let i | ||
const indexes = {} | ||
let corruptItems = -1 | ||
for (i = 0; i < data.length; i += 1) { | ||
let doc | ||
for (const datum of data) { | ||
try { | ||
doc = model.deserialize(this.beforeDeserialization(data[i])) | ||
const doc = model.deserialize(this.beforeDeserialization(datum)) | ||
if (doc._id) { | ||
if (doc.$$deleted === true) { | ||
delete dataById[doc._id] | ||
} else { | ||
dataById[doc._id] = doc | ||
} | ||
} else if (doc.$$indexCreated && doc.$$indexCreated.fieldName != null) { | ||
indexes[doc.$$indexCreated.fieldName] = doc.$$indexCreated | ||
} else if (typeof doc.$$indexRemoved === 'string') { | ||
delete indexes[doc.$$indexRemoved] | ||
} | ||
if (doc.$$deleted === true) delete dataById[doc._id] | ||
else dataById[doc._id] = doc | ||
} else if (doc.$$indexCreated && doc.$$indexCreated.fieldName != null) indexes[doc.$$indexCreated.fieldName] = doc.$$indexCreated | ||
else if (typeof doc.$$indexRemoved === 'string') delete indexes[doc.$$indexRemoved] | ||
} catch (e) { | ||
@@ -199,9 +185,8 @@ corruptItems += 1 | ||
// A bit lenient on corruption | ||
if (data.length > 0 && corruptItems / data.length > this.corruptAlertThreshold) { | ||
throw new Error('More than ' + Math.floor(100 * this.corruptAlertThreshold) + '% of the data file is corrupt, the wrong beforeDeserialization hook may be used. Cautiously refusing to start NeDB to prevent dataloss') | ||
} | ||
if ( | ||
data.length > 0 && | ||
corruptItems / data.length > this.corruptAlertThreshold | ||
) throw new Error(`More than ${Math.floor(100 * this.corruptAlertThreshold)}% of the data file is corrupt, the wrong beforeDeserialization hook may be used. Cautiously refusing to start NeDB to prevent dataloss`) | ||
Object.keys(dataById).forEach(function (k) { | ||
tdata.push(dataById[k]) | ||
}) | ||
tdata.push(...Object.values(dataById)) | ||
@@ -219,26 +204,23 @@ return { data: tdata, indexes: indexes } | ||
* This operation is very quick at startup for a big collection (60ms for ~10k docs) | ||
* @param {Function} cb Optional callback, signature: err | ||
* @param {Function} callback Optional callback, signature: err | ||
*/ | ||
loadDatabase (cb) { | ||
const callback = cb || function () {} | ||
const self = this | ||
loadDatabase (callback = () => {}) { | ||
this.db.resetIndexes() | ||
self.db.resetIndexes() | ||
// In-memory only datastore | ||
if (self.inMemoryOnly) { return callback(null) } | ||
if (this.inMemoryOnly) return callback(null) | ||
async.waterfall([ | ||
function (cb) { | ||
cb => { | ||
// eslint-disable-next-line node/handle-callback-err | ||
Persistence.ensureDirectoryExists(path.dirname(self.filename), function (err) { | ||
Persistence.ensureDirectoryExists(path.dirname(this.filename), err => { | ||
// TODO: handle error | ||
// eslint-disable-next-line node/handle-callback-err | ||
storage.ensureDatafileIntegrity(self.filename, function (err) { | ||
storage.ensureDatafileIntegrity(this.filename, err => { | ||
// TODO: handle error | ||
storage.readFile(self.filename, 'utf8', function (err, rawData) { | ||
if (err) { return cb(err) } | ||
storage.readFile(this.filename, 'utf8', (err, rawData) => { | ||
if (err) return cb(err) | ||
let treatedData | ||
try { | ||
treatedData = self.treatRawData(rawData) | ||
treatedData = this.treatRawData(rawData) | ||
} catch (e) { | ||
@@ -249,4 +231,4 @@ return cb(e) | ||
// Recreate all indexes in the datafile | ||
Object.keys(treatedData.indexes).forEach(function (key) { | ||
self.db.indexes[key] = new Index(treatedData.indexes[key]) | ||
Object.keys(treatedData.indexes).forEach(key => { | ||
this.db.indexes[key] = new Index(treatedData.indexes[key]) | ||
}) | ||
@@ -256,9 +238,9 @@ | ||
try { | ||
self.db.resetIndexes(treatedData.data) | ||
this.db.resetIndexes(treatedData.data) | ||
} catch (e) { | ||
self.db.resetIndexes() // Rollback any index which didn't fail | ||
this.db.resetIndexes() // Rollback any index which didn't fail | ||
return cb(e) | ||
} | ||
self.db.persistence.persistCachedDatabase(cb) | ||
this.db.persistence.persistCachedDatabase(cb) | ||
}) | ||
@@ -268,6 +250,6 @@ }) | ||
} | ||
], function (err) { | ||
if (err) { return callback(err) } | ||
], err => { | ||
if (err) return callback(err) | ||
self.db.executor.processBuffer() | ||
this.db.executor.processBuffer() | ||
return callback(null) | ||
@@ -281,5 +263,3 @@ }) | ||
*/ | ||
static ensureDirectoryExists (dir, cb) { | ||
const callback = cb || function () {} | ||
static ensureDirectoryExists (dir, callback = () => {}) { | ||
storage.mkdir(dir, { recursive: true }, err => { callback(err) }) | ||
@@ -295,22 +275,15 @@ } | ||
switch (process.platform) { | ||
case 'win32': | ||
case 'win64': | ||
home = process.env.LOCALAPPDATA || process.env.APPDATA | ||
if (!home) { throw new Error('Couldn\'t find the base application data folder') } | ||
home = path.join(home, appName) | ||
break | ||
case 'darwin': | ||
home = process.env.HOME | ||
if (!home) { throw new Error('Couldn\'t find the base application data directory') } | ||
home = path.join(home, 'Library', 'Application Support', appName) | ||
break | ||
case 'linux': | ||
home = process.env.HOME | ||
if (!home) { throw new Error('Couldn\'t find the base application data directory') } | ||
home = path.join(home, '.config', appName) | ||
break | ||
default: | ||
throw new Error('Can\'t use the Node Webkit relative path for platform ' + process.platform) | ||
} | ||
if (process.platform === 'win32' || process.platform === 'win64') { | ||
home = process.env.LOCALAPPDATA || process.env.APPDATA | ||
if (!home) throw new Error('Couldn\'t find the base application data folder') | ||
home = path.join(home, appName) | ||
} else if (process.platform === 'darwin') { | ||
home = process.env.HOME | ||
if (!home) throw new Error('Couldn\'t find the base application data directory') | ||
home = path.join(home, 'Library', 'Application Support', appName) | ||
} else if (process.platform === 'linux') { | ||
home = process.env.HOME | ||
if (!home) throw new Error('Couldn\'t find the base application data directory') | ||
home = path.join(home, '.config', appName) | ||
} else throw new Error(`Can't use the Node Webkit relative path for platform ${process.platform}`) | ||
@@ -317,0 +290,0 @@ return path.join(home, 'nedb-data', relativeFilename) |
@@ -26,7 +26,7 @@ /** | ||
*/ | ||
storage.ensureFileDoesntExist = function (file, callback) { | ||
storage.exists(file, function (exists) { | ||
if (!exists) { return callback(null) } | ||
storage.ensureFileDoesntExist = (file, callback) => { | ||
storage.exists(file, exists => { | ||
if (!exists) return callback(null) | ||
storage.unlink(file, function (err) { return callback(err) }) | ||
storage.unlink(file, err => callback(err)) | ||
}) | ||
@@ -41,3 +41,3 @@ } | ||
*/ | ||
storage.flushToStorage = function (options, callback) { | ||
storage.flushToStorage = (options, callback) => { | ||
let filename | ||
@@ -55,8 +55,8 @@ let flags | ||
// except in the very rare event of the first time database is loaded and a crash happens | ||
if (flags === 'r' && (process.platform === 'win32' || process.platform === 'win64')) { return callback(null) } | ||
if (flags === 'r' && (process.platform === 'win32' || process.platform === 'win64')) return callback(null) | ||
fs.open(filename, flags, function (err, fd) { | ||
if (err) { return callback(err) } | ||
fs.fsync(fd, function (errFS) { | ||
fs.close(fd, function (errC) { | ||
fs.open(filename, flags, (err, fd) => { | ||
if (err) return callback(err) | ||
fs.fsync(fd, errFS => { | ||
fs.close(fd, errC => { | ||
if (errFS || errC) { | ||
@@ -79,6 +79,5 @@ const e = new Error('Failed to flush to storage') | ||
* @param {String} data | ||
* @param {Function} cb Optional callback, signature: err | ||
* @param {Function} callback Optional callback, signature: err | ||
*/ | ||
storage.crashSafeWriteFile = function (filename, data, cb) { | ||
const callback = cb || function () {} | ||
storage.crashSafeWriteFile = (filename, data, callback = () => {}) => { | ||
const tempFilename = filename + '~' | ||
@@ -88,20 +87,17 @@ | ||
async.apply(storage.flushToStorage, { filename: path.dirname(filename), isDir: true }), | ||
function (cb) { | ||
storage.exists(filename, function (exists) { | ||
if (exists) { | ||
storage.flushToStorage(filename, function (err) { return cb(err) }) | ||
} else { | ||
return cb() | ||
} | ||
cb => { | ||
storage.exists(filename, exists => { | ||
if (exists) storage.flushToStorage(filename, err => cb(err)) | ||
else return cb() | ||
}) | ||
}, | ||
function (cb) { | ||
storage.writeFile(tempFilename, data, function (err) { return cb(err) }) | ||
cb => { | ||
storage.writeFile(tempFilename, data, err => cb(err)) | ||
}, | ||
async.apply(storage.flushToStorage, tempFilename), | ||
function (cb) { | ||
storage.rename(tempFilename, filename, function (err) { return cb(err) }) | ||
cb => { | ||
storage.rename(tempFilename, filename, err => cb(err)) | ||
}, | ||
async.apply(storage.flushToStorage, { filename: path.dirname(filename), isDir: true }) | ||
], function (err) { return callback(err) }) | ||
], err => callback(err)) | ||
} | ||
@@ -114,17 +110,15 @@ | ||
*/ | ||
storage.ensureDatafileIntegrity = function (filename, callback) { | ||
storage.ensureDatafileIntegrity = (filename, callback) => { | ||
const tempFilename = filename + '~' | ||
storage.exists(filename, function (filenameExists) { | ||
storage.exists(filename, filenameExists => { | ||
// Write was successful | ||
if (filenameExists) { return callback(null) } | ||
if (filenameExists) return callback(null) | ||
storage.exists(tempFilename, function (oldFilenameExists) { | ||
storage.exists(tempFilename, oldFilenameExists => { | ||
// New database | ||
if (!oldFilenameExists) { | ||
return storage.writeFile(filename, '', 'utf8', function (err) { callback(err) }) | ||
} | ||
if (!oldFilenameExists) return storage.writeFile(filename, '', 'utf8', err => { callback(err) }) | ||
// Write failed, use old version | ||
storage.rename(tempFilename, filename, function (err) { return callback(err) }) | ||
storage.rename(tempFilename, filename, err => callback(err)) | ||
}) | ||
@@ -131,0 +125,0 @@ }) |
{ | ||
"name": "@seald-io/nedb", | ||
"version": "2.0.0", | ||
"version": "2.0.1", | ||
"files": [ | ||
@@ -37,6 +37,5 @@ "lib/**/*.js", | ||
"dependencies": { | ||
"@seald-io/binary-search-tree": "^1.0.0", | ||
"@seald-io/binary-search-tree": "^1.0.2", | ||
"async": "0.2.10", | ||
"localforage": "^1.9.0", | ||
"underscore": "^1.13.1" | ||
"localforage": "^1.9.0" | ||
}, | ||
@@ -43,0 +42,0 @@ "devDependencies": { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
3
18
9
579387
11276
- Removedunderscore@^1.13.1
- Removedunderscore@1.13.7(transitive)