diff --git a/lib/cursor.js b/lib/cursor.js index 603b844..0882d88 100755 --- a/lib/cursor.js +++ b/lib/cursor.js @@ -33,10 +33,16 @@ class Cursor { this.query = query || {} if (execFn) this.execFn = execFn if (async) this.async = true + this._limit = null + this._skip = null + this._sort = null + this._projection = null } /** * Set a limit to the number of results + * @param {Number} limit + * @return {Cursor} */ limit (limit) { this._limit = limit @@ -44,7 +50,9 @@ class Cursor { } /** - * Skip a the number of results + * Skip a number of results + * @param {Number} skip + * @return {Cursor} */ skip (skip) { this._skip = skip @@ -53,7 +61,8 @@ class Cursor { /** * Sort results of the query - * @param {SortQuery} sortQuery - SortQuery is { field: order }, field can use the dot-notation, order is 1 for ascending and -1 for descending + * @param {Object.} sortQuery - sortQuery is { field: order }, field can use the dot-notation, order is 1 for ascending and -1 for descending + * @return {Cursor} */ sort (sortQuery) { this._sort = sortQuery @@ -62,8 +71,9 @@ class Cursor { /** * Add the use of a projection - * @param {Object} projection - MongoDB-style projection. {} means take all fields. Then it's { key1: 1, key2: 1 } to take only key1 and key2 - * { key1: 0, key2: 0 } to omit only key1 and key2. Except _id, you can't mix takes and omits + * @param {Object.} projection - MongoDB-style projection. {} means take all fields. Then it's { key1: 1, key2: 1 } to take only key1 and key2 + * { key1: 0, key2: 0 } to omit only key1 and key2. Except _id, you can't mix takes and omits. + * @return {Cursor} */ projection (projection) { this._projection = projection @@ -72,6 +82,8 @@ class Cursor { /** * Apply the projection + * @param {document[]} candidates + * @return {document[]} */ project (candidates) { const res = [] @@ -118,9 +130,8 @@ class Cursor { /** * Get all matching elements * Will return pointers to matched elements (shallow copies), returning full copies is the role of find or findOne - * This is an internal function, use exec which uses the executor - * - * @param {Function} callback - Signature: err, results + * This is an internal function, use execAsync which uses the executor + * @return {document[]|Promise<*>} */ async _execAsync () { let res = [] @@ -180,16 +191,39 @@ class Cursor { else return res } + /** + * @callback Cursor~execCallback + * @param {Error} err + * @param {document[]|*} res If an execFn was given to the Cursor, then the type of this parameter is the one returned by the execFn. + */ + + /** + * Get all matching elements + * Will return pointers to matched elements (shallow copies), returning full copies is the role of find or findOne + * This is an internal function, use exec which uses the executor + * @param { Cursor~execCallback} _callback + */ _exec (_callback) { callbackify(this._execAsync.bind(this))(_callback) } - exec (...args) { - this.db.executor.push({ this: this, fn: this._exec, arguments: args }) + /** + * Get all matching elements + * Will return pointers to matched elements (shallow copies), returning full copies is the role of find or findOne + * @param { Cursor~execCallback} _callback + */ + exec (_callback) { + this.db.executor.push({ this: this, fn: this._exec, arguments: [_callback] }) } - execAsync (...args) { - return this.db.executor.pushAsync(() => this._execAsync(...args)) + /** + * Get all matching elements + * Will return pointers to matched elements (shallow copies), returning full copies is the role of find or findOne + * @return {Promise} + * @async + */ + execAsync () { + return this.db.executor.pushAsync(() => this._execAsync()) } then (onFulfilled, onRejected) { diff --git a/lib/customUtils.js b/lib/customUtils.js index da538e7..a3fc1f0 100755 --- a/lib/customUtils.js +++ b/lib/customUtils.js @@ -7,6 +7,8 @@ const crypto = require('crypto') * that's not an issue here * The probability of a collision is extremely small (need 3*10^12 documents to have one chance in a million of a collision) * See http://en.wikipedia.org/wiki/Birthday_problem + * @param {number} len + * @return {string} */ const uid = len => crypto.randomBytes(Math.ceil(Math.max(8, len * 2))) .toString('base64') diff --git a/lib/executor.js b/lib/executor.js index 61275be..872553f 100755 --- a/lib/executor.js +++ b/lib/executor.js @@ -1,3 +1,9 @@ +/** + * @callback AsyncFunction + * @param {...[*]} args + * @return {Promise<*>} + */ + /** * Responsible for sequentially executing actions on the database * @private @@ -9,7 +15,7 @@ class Waterfall { * * It will change any time `this.waterfall` is called, this is why there is a getter `this.guardian` which retrieves * the latest version of the guardian. - * @type {Promise} + * @type {Promise} * @private */ this._guardian = Promise.resolve() @@ -19,26 +25,20 @@ class Waterfall { * Returns a Promise which resolves when all tasks up to when this function is called are done. * * This Promise cannot reject. - * @return {Promise} + * @return {Promise} */ get guardian () { return this._guardian } - /** - * @callback Waterfall~function - * @param {...[*]} args - * @return {Promise<*>} - */ - /** * - * @param {Waterfall~function} func - * @return {Waterfall~function} + * @param {AsyncFunction} func + * @return {AsyncFunction} */ waterfall (func) { return (...args) => { - this._guardian = this.guardian.then(() => { + this._guardian = this.guardian.then(async () => { return func(...args) .then(result => ({ error: false, result }), result => ({ error: true, result })) }) @@ -49,6 +49,11 @@ class Waterfall { } } + /** + * Shorthand for chaining a promise to the Waterfall + * @param promise + * @return {Promise} + */ chain (promise) { return this.waterfall(() => promise)() } @@ -68,11 +73,11 @@ class Executor { * If executor is ready, queue task (and process it immediately if executor was idle) * If not, buffer task for later processing * @param {Object} task - * task.this - Object to use as this - * task.fn - Function to execute - * task.arguments - Array of arguments, IMPORTANT: only the last argument may be a function (the callback) - * and the last argument cannot be false/undefined/null - * @param {Boolean} forceQueuing Optional (defaults to false) force executor to queue task even if it is not ready + * @param {Object} task.this - Object to use as this + * @param {function} task.fn - Function to execute + * @param {Array} task.arguments - Array of arguments, IMPORTANT: only the last argument may be a function + * (the callback) and the last argument cannot be false/undefined/null + * @param {Boolean} [forceQueuing = false] Optional (defaults to false) force executor to queue task even if it is not ready */ push (task, forceQueuing) { const func = async () => { @@ -102,7 +107,15 @@ class Executor { this.pushAsync(func, forceQueuing) } - pushAsync (task, forceQueuing) { + /** + * If executor is ready, queue task (and process it immediately if executor was idle) + * If not, buffer task for later processing + * @param {AsyncFunction} task + * @param {boolean} [forceQueuing = false] + * @return {Promise<*>} + * @async + */ + pushAsync (task, forceQueuing = false) { if (this.ready || forceQueuing) return this.queue.waterfall(task)() else return this.buffer.waterfall(task)() } diff --git a/lib/persistence.js b/lib/persistence.js index ac22eb4..93731c6 100755 --- a/lib/persistence.js +++ b/lib/persistence.js @@ -61,16 +61,29 @@ class Persistence { } } + /** + * @callback Persistence~persistCachedDatabaseCallback + * @param {Error?} err + */ + /** * Persist cached database * This serves as a compaction function since the cache always contains only the number of documents in the collection * while the data file is append-only so it may grow larger - * @param {Function} callback Optional callback, signature: err + * This is an internal function, use compactDataFile which uses the executor + * @param {Persistence~persistCachedDatabaseCallback} callback Optional callback, signature: err */ persistCachedDatabase (callback = () => {}) { return callbackify(this.persistCachedDatabaseAsync.bind(this))(callback) } + /** + * Persist cached database + * This serves as a compaction function since the cache always contains only the number of documents in the collection + * while the data file is append-only so it may grow larger + * This is an internal function, use compactDataFileAsync which uses the executor + * @return {Promise} + */ async persistCachedDatabaseAsync () { const lines = [] @@ -95,13 +108,23 @@ class Persistence { this.db.emit('compaction.done') } + /** + * @callback Persistence~compactDataFileCallback + * @param {Error?} err + */ + /** * Queue a rewrite of the datafile + * @param {Persistence~compactDataFileCallback} [callback = () => {}] Optional callback, signature: err */ - compactDatafile () { - this.db.executor.push({ this: this, fn: this.persistCachedDatabase, arguments: [] }) + compactDatafile (callback = () => {}) { + this.db.executor.push({ this: this, fn: this.persistCachedDatabase, arguments: [callback] }) } + /** + * Queue a rewrite of the datafile + * @async + */ compactDatafileAsync () { return this.db.executor.pushAsync(() => this.persistCachedDatabaseAsync()) } @@ -128,16 +151,27 @@ class Persistence { if (this.autocompactionIntervalId) clearInterval(this.autocompactionIntervalId) } + /** + * @callback Persistence~persistNewStateCallback + * @param {Error?} err + */ + /** * Persist new state for the given newDocs (can be insertion, update or removal) * Use an append-only format - * @param {Array} newDocs Can be empty if no doc was updated/removed - * @param {Function} callback Optional, signature: err + * @param {string[]} newDocs Can be empty if no doc was updated/removed + * @param {Persistence~persistNewStateCallback} [callback = () => {}] Optional, signature: err */ persistNewState (newDocs, callback = () => {}) { callbackify(this.persistNewStateAsync.bind(this))(newDocs, err => callback(err)) } + /** + * Persist new state for the given newDocs (can be insertion, update or removal) + * Use an append-only format + * @param {string[]} newDocs Can be empty if no doc was updated/removed + * @return {Promise} + */ async persistNewStateAsync (newDocs) { let toPersist = '' @@ -154,8 +188,16 @@ class Persistence { } /** - * From a database's raw data, return the corresponding - * machine understandable collection + * @typedef rawIndex + * @property {string} fieldName + * @property {boolean} [unique] + * @property {boolean} [sparse] + */ + + /** + * From a database's raw data, return the corresponding machine understandable collection + * @param {string} rawData database file + * @return {{data: document[], indexes: Object.}} */ treatRawData (rawData) { const data = rawData.split('\n') @@ -190,8 +232,15 @@ class Persistence { } /** - * From a database's raw stream, return the corresponding - * machine understandable collection + * @callback Persistence~treatRawStreamCallback + * @param {Error?} err + * @param {{data: document[], indexes: Object.}} data + */ + + /** + * From a database's raw data stream, return the corresponding machine understandable collection + * @param {Readable} rawStream + * @param {Persistence~treatRawStreamCallback} cb */ treatRawStream (rawStream, cb) { const dataById = {} @@ -236,10 +285,21 @@ class Persistence { }) } - async treatRawStreamAsync (rawStream) { + /** + * From a database's raw data stream, return the corresponding machine understandable collection + * @param {Readable} rawStream + * @return {Promise<{data: document[], indexes: Object.}>} + * @async + */ + treatRawStreamAsync (rawStream) { return promisify(this.treatRawStream.bind(this))(rawStream) } + /** + * @callback Persistence~loadDatabaseCallback + * @param {Error?} err + */ + /** * Load the database * 1) Create all indexes @@ -248,12 +308,22 @@ class Persistence { * This means pulling data out of the data file or creating it if it doesn't exist * Also, all data is persisted right away, which has the effect of compacting the database file * This operation is very quick at startup for a big collection (60ms for ~10k docs) - * @param {Function} callback Optional callback, signature: err + * @param {Persistence~loadDatabaseCallback} callback Optional callback, signature: err */ loadDatabase (callback = () => {}) { callbackify(this.loadDatabaseAsync.bind(this))(err => callback(err)) } + /** + * Load the database + * 1) Create all indexes + * 2) Insert all data + * 3) Compact the database + * This means pulling data out of the data file or creating it if it doesn't exist + * Also, all data is persisted right away, which has the effect of compacting the database file + * This operation is very quick at startup for a big collection (60ms for ~10k docs) + * @return {Promise} + */ async loadDatabaseAsync () { this.db.resetIndexes() @@ -289,14 +359,25 @@ class Persistence { this.db.executor.processBuffer() } + /** + * @callback Persistence~ensureDirectoryExistsCallback + * @param {Error?} err + */ + /** * Check if a directory stat and create it on the fly if it is not the case - * cb is optional, signature: err + * @param {string} dir + * @param {Persistence~ensureDirectoryExistsCallback} [callback = () => {}] optional callback, signature: err */ static ensureDirectoryExists (dir, callback = () => {}) { storage.mkdir(dir, { recursive: true }, err => { callback(err) }) } + /** + * Check if a directory stat and create it on the fly if it is not the case + * @param {string} dir + * @return {Promise} + */ static async ensureDirectoryExistsAsync (dir) { await storage.mkdirAsync(dir, { recursive: true }) } @@ -304,6 +385,10 @@ class Persistence { /** * Return the path the datafile if the given filename is relative to the directory where Node Webkit stores * data for this application. Probably the best place to store data + * @param {string} appName + * @param {string} relativeFilename + * @return {string} + * @deprecated */ static getNWAppFilename (appName, relativeFilename) { return deprecate(() => { diff --git a/lib/utils.js b/lib/utils.js index d3d1735..2f86f70 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,15 +1,41 @@ -const uniq = (array, iterator) => { - if (iterator) return [...(new Map(array.map(x => [iterator(x), x]))).values()] +/** + * Produces a duplicate-free version of the array, using === to test object equality. In particular only the first + * occurrence of each value is kept. If you want to compute unique items based on a transformation, pass an iteratee + * function. + * Heavily inspired by https://underscorejs.org/#uniq + * @param {Array} array + * @param {function} [iteratee] transformation applied to every element before checking for duplicates. This will not + * transform the items in the result. + * @return {Array} + */ +const uniq = (array, iteratee) => { + if (iteratee) return [...(new Map(array.map(x => [iteratee(x), x]))).values()] else return [...new Set(array)] } - -const objectToString = o => Object.prototype.toString.call(o) - +/** + * Returns true if arg is an Object. Note that JavaScript arrays and functions are objects, while (normal) strings + * and numbers are not. + * Heavily inspired by https://underscorejs.org/#isObject + * @param {*} arg + * @return {boolean} + */ const isObject = arg => typeof arg === 'object' && arg !== null -const isDate = d => isObject(d) && objectToString(d) === '[object Date]' +/** + * Returns true if d is a Date. + * Heavily inspired by https://underscorejs.org/#isDate + * @param {*} d + * @return {boolean} + */ +const isDate = d => isObject(d) && Object.prototype.toString.call(d) === '[object Date]' -const isRegExp = re => isObject(re) && objectToString(re) === '[object RegExp]' +/** + * Returns true if re is a RegExp. + * Heavily inspired by https://underscorejs.org/#isRegExp + * @param {*} re + * @return {boolean} + */ +const isRegExp = re => isObject(re) && Object.prototype.toString.call(re) === '[object RegExp]' module.exports.uniq = uniq module.exports.isDate = isDate