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 || {}
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.<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) {
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.<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.
* @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<document[]|*>}
* @async
*/
execAsync () {
return this.db.executor.pushAsync(() => this._execAsync())
}
then (onFulfilled, onRejected) {

@ -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')

@ -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<void>}
* @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<void>}
* @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)()
}

@ -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<void>}
*/
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.<string, rawIndex>}}
*/
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.<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) {
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)
}
/**
* @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<void>}
*/
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<void>}
*/
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(() => {

@ -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

Loading…
Cancel
Save