add docs for Cursor, customUtils, Executor, Persistence and utils

pull/11/head
Timothée Rebours 3 years ago
parent 07e60b09dc
commit 90f52e4cda
  1. 56
      lib/cursor.js
  2. 2
      lib/customUtils.js
  3. 47
      lib/executor.js
  4. 109
      lib/persistence.js
  5. 40
      lib/utils.js

@ -33,10 +33,16 @@ class Cursor {
this.query = query || {} this.query = query || {}
if (execFn) this.execFn = execFn if (execFn) this.execFn = execFn
if (async) this.async = true 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 * Set a limit to the number of results
* @param {Number} limit
* @return {Cursor}
*/ */
limit (limit) { limit (limit) {
this._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) { skip (skip) {
this._skip = skip this._skip = skip
@ -53,7 +61,8 @@ class Cursor {
/** /**
* Sort results of the query * 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.<string, number>} sortQuery - sortQuery is { field: order }, field can use the dot-notation, order is 1 for ascending and -1 for descending
* @return {Cursor}
*/ */
sort (sortQuery) { sort (sortQuery) {
this._sort = sortQuery this._sort = sortQuery
@ -62,8 +71,9 @@ class Cursor {
/** /**
* Add the use of a projection * 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 * @param {Object.<string, number>} 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 * { key1: 0, key2: 0 } to omit only key1 and key2. Except _id, you can't mix takes and omits.
* @return {Cursor}
*/ */
projection (projection) { projection (projection) {
this._projection = projection this._projection = projection
@ -72,6 +82,8 @@ class Cursor {
/** /**
* Apply the projection * Apply the projection
* @param {document[]} candidates
* @return {document[]}
*/ */
project (candidates) { project (candidates) {
const res = [] const res = []
@ -118,9 +130,8 @@ class Cursor {
/** /**
* Get all matching elements * Get all matching elements
* Will return pointers to matched elements (shallow copies), returning full copies is the role of find or findOne * 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 * This is an internal function, use execAsync which uses the executor
* * @return {document[]|Promise<*>}
* @param {Function} callback - Signature: err, results
*/ */
async _execAsync () { async _execAsync () {
let res = [] let res = []
@ -180,16 +191,39 @@ class Cursor {
else return res 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) { _exec (_callback) {
callbackify(this._execAsync.bind(this))(_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<document[]|*>}
* @async
*/
execAsync () {
return this.db.executor.pushAsync(() => this._execAsync())
} }
then (onFulfilled, onRejected) { then (onFulfilled, onRejected) {

@ -7,6 +7,8 @@ const crypto = require('crypto')
* that's not an issue here * 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) * 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 * 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))) const uid = len => crypto.randomBytes(Math.ceil(Math.max(8, len * 2)))
.toString('base64') .toString('base64')

@ -1,3 +1,9 @@
/**
* @callback AsyncFunction
* @param {...[*]} args
* @return {Promise<*>}
*/
/** /**
* Responsible for sequentially executing actions on the database * Responsible for sequentially executing actions on the database
* @private * @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 * 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. * the latest version of the guardian.
* @type {Promise<void>} * @type {Promise}
* @private * @private
*/ */
this._guardian = Promise.resolve() 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. * Returns a Promise which resolves when all tasks up to when this function is called are done.
* *
* This Promise cannot reject. * This Promise cannot reject.
* @return {Promise<void>} * @return {Promise}
*/ */
get guardian () { get guardian () {
return this._guardian return this._guardian
} }
/**
* @callback Waterfall~function
* @param {...[*]} args
* @return {Promise<*>}
*/
/** /**
* *
* @param {Waterfall~function} func * @param {AsyncFunction} func
* @return {Waterfall~function} * @return {AsyncFunction}
*/ */
waterfall (func) { waterfall (func) {
return (...args) => { return (...args) => {
this._guardian = this.guardian.then(() => { this._guardian = this.guardian.then(async () => {
return func(...args) return func(...args)
.then(result => ({ error: false, result }), result => ({ error: true, result })) .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) { chain (promise) {
return this.waterfall(() => 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 executor is ready, queue task (and process it immediately if executor was idle)
* If not, buffer task for later processing * If not, buffer task for later processing
* @param {Object} task * @param {Object} task
* task.this - Object to use as this * @param {Object} task.this - Object to use as this
* task.fn - Function to execute * @param {function} task.fn - Function to execute
* task.arguments - Array of arguments, IMPORTANT: only the last argument may be a function (the callback) * @param {Array} task.arguments - Array of arguments, IMPORTANT: only the last argument may be a function
* and the last argument cannot be false/undefined/null * (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 {Boolean} [forceQueuing = false] Optional (defaults to false) force executor to queue task even if it is not ready
*/ */
push (task, forceQueuing) { push (task, forceQueuing) {
const func = async () => { const func = async () => {
@ -102,7 +107,15 @@ class Executor {
this.pushAsync(func, forceQueuing) 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)() if (this.ready || forceQueuing) return this.queue.waterfall(task)()
else return this.buffer.waterfall(task)() else return this.buffer.waterfall(task)()
} }

@ -61,16 +61,29 @@ class Persistence {
} }
} }
/**
* @callback Persistence~persistCachedDatabaseCallback
* @param {Error?} err
*/
/** /**
* Persist cached database * Persist cached database
* This serves as a compaction function since the cache always contains only the number of documents in the collection * 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 * 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 = () => {}) { persistCachedDatabase (callback = () => {}) {
return callbackify(this.persistCachedDatabaseAsync.bind(this))(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<void>}
*/
async persistCachedDatabaseAsync () { async persistCachedDatabaseAsync () {
const lines = [] const lines = []
@ -95,13 +108,23 @@ class Persistence {
this.db.emit('compaction.done') this.db.emit('compaction.done')
} }
/**
* @callback Persistence~compactDataFileCallback
* @param {Error?} err
*/
/** /**
* Queue a rewrite of the datafile * Queue a rewrite of the datafile
* @param {Persistence~compactDataFileCallback} [callback = () => {}] Optional callback, signature: err
*/ */
compactDatafile () { compactDatafile (callback = () => {}) {
this.db.executor.push({ this: this, fn: this.persistCachedDatabase, arguments: [] }) this.db.executor.push({ this: this, fn: this.persistCachedDatabase, arguments: [callback] })
} }
/**
* Queue a rewrite of the datafile
* @async
*/
compactDatafileAsync () { compactDatafileAsync () {
return this.db.executor.pushAsync(() => this.persistCachedDatabaseAsync()) return this.db.executor.pushAsync(() => this.persistCachedDatabaseAsync())
} }
@ -128,16 +151,27 @@ class Persistence {
if (this.autocompactionIntervalId) clearInterval(this.autocompactionIntervalId) 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) * Persist new state for the given newDocs (can be insertion, update or removal)
* Use an append-only format * Use an append-only format
* @param {Array} newDocs Can be empty if no doc was updated/removed * @param {string[]} newDocs Can be empty if no doc was updated/removed
* @param {Function} callback Optional, signature: err * @param {Persistence~persistNewStateCallback} [callback = () => {}] Optional, signature: err
*/ */
persistNewState (newDocs, callback = () => {}) { persistNewState (newDocs, callback = () => {}) {
callbackify(this.persistNewStateAsync.bind(this))(newDocs, err => callback(err)) 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) { async persistNewStateAsync (newDocs) {
let toPersist = '' let toPersist = ''
@ -154,8 +188,16 @@ class Persistence {
} }
/** /**
* From a database's raw data, return the corresponding * @typedef rawIndex
* machine understandable collection * @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.<string, rawIndex>}}
*/ */
treatRawData (rawData) { treatRawData (rawData) {
const data = rawData.split('\n') const data = rawData.split('\n')
@ -190,8 +232,15 @@ class Persistence {
} }
/** /**
* From a database's raw stream, return the corresponding * @callback Persistence~treatRawStreamCallback
* machine understandable collection * @param {Error?} err
* @param {{data: document[], indexes: Object.<string, rawIndex>}} data
*/
/**
* From a database's raw data stream, return the corresponding machine understandable collection
* @param {Readable} rawStream
* @param {Persistence~treatRawStreamCallback} cb
*/ */
treatRawStream (rawStream, cb) { treatRawStream (rawStream, cb) {
const dataById = {} 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.<string, rawIndex>}>}
* @async
*/
treatRawStreamAsync (rawStream) {
return promisify(this.treatRawStream.bind(this))(rawStream) return promisify(this.treatRawStream.bind(this))(rawStream)
} }
/**
* @callback Persistence~loadDatabaseCallback
* @param {Error?} err
*/
/** /**
* Load the database * Load the database
* 1) Create all indexes * 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 * 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 * 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) * 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 = () => {}) { loadDatabase (callback = () => {}) {
callbackify(this.loadDatabaseAsync.bind(this))(err => callback(err)) 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<void>}
*/
async loadDatabaseAsync () { async loadDatabaseAsync () {
this.db.resetIndexes() this.db.resetIndexes()
@ -289,14 +359,25 @@ class Persistence {
this.db.executor.processBuffer() 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 * 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 = () => {}) { static ensureDirectoryExists (dir, callback = () => {}) {
storage.mkdir(dir, { recursive: true }, err => { callback(err) }) 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<void>}
*/
static async ensureDirectoryExistsAsync (dir) { static async ensureDirectoryExistsAsync (dir) {
await storage.mkdirAsync(dir, { recursive: true }) 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 * 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 * data for this application. Probably the best place to store data
* @param {string} appName
* @param {string} relativeFilename
* @return {string}
* @deprecated
*/ */
static getNWAppFilename (appName, relativeFilename) { static getNWAppFilename (appName, relativeFilename) {
return deprecate(() => { return deprecate(() => {

@ -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)] 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 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.uniq = uniq
module.exports.isDate = isDate module.exports.isDate = isDate

Loading…
Cancel
Save