rittenhop-ghost/versions/5.94.2/node_modules/mingo/dist/mingo.js

4993 lines
131 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! mingo.js 2.5.3
//! Copyright (c) 2020 Francis Asante
//! MIT
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.mingo = factory());
}(this, (function () { 'use strict';
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArrayLimit(arr, i) {
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(n);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
// Javascript native types
var T_NULL = 'null';
var T_UNDEFINED = 'undefined';
var T_BOOL = 'bool';
var T_BOOLEAN = 'boolean';
var T_NUMBER = 'number';
var T_STRING = 'string';
var T_DATE = 'date';
var T_REGEX = 'regex';
var T_REGEXP = 'regexp';
var T_ARRAY = 'array';
var T_OBJECT = 'object';
var T_FUNCTION = 'function'; // no array, object, or function types
var JS_SIMPLE_TYPES = [T_NULL, T_UNDEFINED, T_BOOLEAN, T_NUMBER, T_STRING, T_DATE, T_REGEXP]; // operator classes
var OP_EXPRESSION = 'expression';
var OP_GROUP = 'group';
var OP_PIPELINE = 'pipeline';
var OP_PROJECTION = 'projection';
var OP_QUERY = 'query';
var MISSING = function MISSING() {};
/**
* Utility functions
*/
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function value(valueToFind, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
} // 1. Let O be ? ToObject(this value).
var o = Object(this); // 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0; // 3. If len is 0, return false.
if (len === 0) {
return false;
} // 4. Let n be ? ToInteger(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
var n = fromIndex | 0; // 5. If n ≥ 0, then
// a. Let k be n.
// 6. Else n < 0,
// a. Let k be len + n.
// b. If k < 0, let k be 0.
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
function sameValueZero(x, y) {
return x === y || typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y);
} // 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(valueToFind, elementK) is true, return true.
if (sameValueZero(o[k], valueToFind)) {
return true;
} // c. Increase k by 1.
k++;
} // 8. Return false
return false;
}
});
}
var arrayPush = Array.prototype.push;
function assert(condition, message) {
if (!condition) err(message);
}
/**
* Deep clone an object
*/
function cloneDeep(obj) {
switch (jsType(obj)) {
case T_ARRAY:
return obj.map(cloneDeep);
case T_OBJECT:
return objectMap(obj, cloneDeep);
default:
return obj;
}
}
/**
* Shallow clone an object
*/
function clone(obj) {
switch (jsType(obj)) {
case T_ARRAY:
return into([], obj);
case T_OBJECT:
return Object.assign({}, obj);
default:
return obj;
}
}
function getType(v) {
if (v === null) return 'Null';
if (v === undefined) return 'Undefined';
return v.constructor.name;
}
function jsType(v) {
return getType(v).toLowerCase();
}
function isBoolean(v) {
return jsType(v) === T_BOOLEAN;
}
function isString(v) {
return jsType(v) === T_STRING;
}
function isNumber(v) {
return jsType(v) === T_NUMBER;
}
var isArray = Array.isArray || function (v) {
return !!v && v.constructor === Array;
};
function isObject(v) {
return !!v && v.constructor === Object;
}
function isObjectLike(v) {
return v === Object(v);
} // objects, arrays, functions, date, custom object
function isDate(v) {
return jsType(v) === T_DATE;
}
function isRegExp(v) {
return jsType(v) === T_REGEXP;
}
function isFunction(v) {
return jsType(v) === T_FUNCTION;
}
function isNil(v) {
return v === null || v === undefined;
}
function isNull(v) {
return v === null;
}
function isUndefined(v) {
return v === undefined;
}
function inArray(arr, item) {
return arr.includes(item);
}
function notInArray(arr, item) {
return !inArray(arr, item);
}
function truthy(arg) {
return !!arg;
}
function isEmpty(x) {
return isNil(x) || isArray(x) && x.length === 0 || isObject(x) && keys(x).length === 0 || !x;
} // ensure a value is an array
function ensureArray(x) {
return isArray(x) ? x : [x];
}
function has(obj, prop) {
return obj.hasOwnProperty(prop);
}
function err(s) {
throw new Error(s);
}
var keys = Object.keys; // ////////////////// UTILS ////////////////////
/**
* Iterate over an array or object
* @param {Array|Object} obj An object-like value
* @param {Function} fn The callback to run per item
* @param {*} ctx The object to use a context
* @return {void}
*/
function each(obj, fn, ctx) {
fn = fn.bind(ctx);
if (isArray(obj)) {
for (var i = 0, len = obj.length; i < len; i++) {
if (fn(obj[i], i, obj) === false) break;
}
} else {
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
if (fn(obj[k], k, obj) === false) break;
}
}
}
}
/**
* Transform values in an object
*
* @param {Object} obj An object whose values to transform
* @param {Function} fn The transform function
* @param {*} ctx The value to use as the "this" context for the transform
* @return {Array|Object} Result object after applying the transform
*/
function objectMap(obj, fn, ctx) {
fn = fn.bind(ctx);
var o = {};
var objKeys = keys(obj);
for (var i = 0; i < objKeys.length; i++) {
var k = objKeys[i];
o[k] = fn(obj[k], k);
}
return o;
}
/**
* Deep merge objects or arrays.
* When the inputs have unmergeable types, the source value (right hand side) is returned.
* If inputs are arrays of same length and all elements are mergable, elements in the same position are merged together.
* If any of the elements are unmergeable, elements in the source are appended to the target.
* @param target {Object|Array} the target to merge into
* @param obj {Object|Array} the source object
*/
function merge(target, obj) {
var opt = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
// take care of missing inputs
if (target === MISSING) return obj;
if (obj === MISSING) return target;
var inputs = [target, obj];
if (!(inputs.every(isObject) || inputs.every(isArray))) {
throw Error('mismatched types. must both be array or object');
} // default options
opt.flatten = opt.flatten || false;
if (isArray(target)) {
if (opt.flatten) {
var i = 0;
var j = 0;
while (i < target.length && j < obj.length) {
target[i] = merge(target[i++], obj[j++], opt);
}
while (j < obj.length) {
target.push(obj[j++]);
}
} else {
arrayPush.apply(target, obj);
}
} else {
Object.keys(obj).forEach(function (k) {
if (target.hasOwnProperty(k)) {
target[k] = merge(target[k], obj[k], opt);
} else {
target[k] = obj[k];
}
});
}
return target;
}
/**
* Reduce any array-like object
* @param collection
* @param fn
* @param accumulator
* @returns {*}
*/
function reduce(collection, fn, accumulator) {
if (isArray(collection)) return collection.reduce(fn, accumulator); // array-like objects
each(collection, function (v, k) {
return accumulator = fn(accumulator, v, k, collection);
});
return accumulator;
}
/**
* Returns the intersection between two arrays
*
* @param {Array} xs The first array
* @param {Array} ys The second array
* @return {Array} Result array
*/
function intersection(xs, ys) {
var hashes = ys.map(hashCode);
return xs.filter(function (v) {
return inArray(hashes, hashCode(v));
});
}
/**
* Returns the union of two arrays
*
* @param {Array} xs The first array
* @param {Array} ys The second array
* @return {Array} The result array
*/
function union(xs, ys) {
return into(into([], xs), ys.filter(notInArray.bind(null, xs)));
}
/**
* Flatten the array
*
* @param {Array} xs The array to flatten
* @param {Number} depth The number of nested lists to iterate
*/
function flatten(xs) {
var depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
var arr = [];
function flatten2(ys, iter) {
for (var i = 0, len = ys.length; i < len; i++) {
if (isArray(ys[i]) && (iter > 0 || iter < 0)) {
flatten2(ys[i], Math.max(-1, iter - 1));
} else {
arr.push(ys[i]);
}
}
}
flatten2(xs, depth);
return arr;
}
/**
* Unwrap a single element array to specified depth
* @param {Array} arr
* @param {Number} depth
*/
function unwrap(arr, depth) {
if (depth < 1) return arr;
while (depth-- && isArray(arr) && arr.length === 1) {
arr = arr[0];
}
return arr;
}
/**
* Determine whether two values are the same or strictly equivalent
*
* @param {*} a The first value
* @param {*} b The second value
* @return {Boolean} Result of comparison
*/
function isEqual(a, b) {
var lhs = [a];
var rhs = [b];
while (lhs.length > 0) {
a = lhs.pop();
b = rhs.pop(); // strictly equal must be equal.
if (a === b) continue; // unequal types and functions cannot be equal.
var type = jsType(a);
if (type !== jsType(b) || type === T_FUNCTION) return false; // leverage toString for Date and RegExp types
switch (type) {
case T_ARRAY:
if (a.length !== b.length) return false; //if (a.length === b.length && a.length === 0) continue
into(lhs, a);
into(rhs, b);
break;
case T_OBJECT:
// deep compare objects
var ka = keys(a);
var kb = keys(b); // check length of keys early
if (ka.length !== kb.length) return false; // we know keys are strings so we sort before comparing
ka.sort();
kb.sort(); // compare keys
for (var i = 0, len = ka.length; i < len; i++) {
var temp = ka[i];
if (temp !== kb[i]) {
return false;
} else {
// save later work
lhs.push(a[temp]);
rhs.push(b[temp]);
}
}
break;
default:
// compare encoded values
if (encode(a) !== encode(b)) return false;
}
}
return lhs.length === 0;
}
/**
* Return a new unique version of the collection
* @param {Array} xs The input collection
* @return {Array} A new collection with unique values
*/
function unique(xs) {
var h = {};
var arr = [];
each(xs, function (item) {
var k = hashCode(item);
if (!has(h, k)) {
arr.push(item);
h[k] = 0;
}
});
return arr;
}
/**
* Encode value to string using a simple non-colliding stable scheme.
*
* @param value
* @returns {*}
*/
function encode(value) {
var type = jsType(value);
switch (type) {
case T_BOOLEAN:
case T_NUMBER:
case T_REGEXP:
return value.toString();
case T_STRING:
return JSON.stringify(value);
case T_DATE:
return value.toISOString();
case T_NULL:
case T_UNDEFINED:
return type;
case T_ARRAY:
return '[' + value.map(encode) + ']';
default:
var prefix = type === T_OBJECT ? '' : "".concat(getType(value));
var objKeys = keys(value);
objKeys.sort();
return "".concat(prefix, "{") + objKeys.map(function (k) {
return "".concat(encode(k), ":").concat(encode(value[k]));
}) + '}';
}
}
/**
* Generate hash code
* This selected function is the result of benchmarking various hash functions.
* This version performs well and can hash 10^6 documents in ~3s with on average 100 collisions.
*
* @param value
* @returns {*}
*/
function hashCode(value) {
if (isNil(value)) return null;
var hash = 0;
var s = encode(value);
var i = s.length;
while (i) {
hash = (hash << 5) - hash ^ s.charCodeAt(--i);
}
return hash >>> 0;
}
/**
* Default compare function
* @param {*} a
* @param {*} b
*/
function compare(a, b) {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
/**
* Returns a (stably) sorted copy of list, ranked in ascending order by the results of running each value through iteratee
*
* This implementation treats null/undefined sort keys as less than every other type
*
* @param {Array} collection
* @param {Function} fn The function used to resolve sort keys
* @param {Function} cmp The comparator function to use for comparing values
* @return {Array} Returns a new sorted array by the given iteratee
*/
function sortBy(collection, fn, cmp) {
var sorted = [];
var result = [];
var hash = {};
cmp = cmp || compare;
if (isEmpty(collection)) return collection;
for (var i = 0; i < collection.length; i++) {
var obj = collection[i];
var key = fn(obj, i); // objects with nil keys will go in first
if (isNil(key)) {
result.push(obj);
} else {
if (hash[key]) {
hash[key].push(obj);
} else {
hash[key] = [obj];
}
sorted.push(key);
}
} // use native array sorting but enforce stableness
sorted.sort(cmp);
for (var _i = 0; _i < sorted.length; _i++) {
into(result, hash[sorted[_i]]);
}
return result;
}
/**
* Groups the collection into sets by the returned key
*
* @param collection
* @param fn {Function} to compute the group key of an item in the collection
* @returns {{keys: Array, groups: Array}}
*/
function groupBy(collection, fn) {
var result = {
'keys': [],
'groups': []
};
var lookup = {};
each(collection, function (obj) {
var key = fn(obj);
var hash = hashCode(key);
var index = -1;
if (lookup[hash] === undefined) {
index = result.keys.length;
lookup[hash] = index;
result.keys.push(key);
result.groups.push([]);
}
index = lookup[hash];
result.groups[index].push(obj);
});
return result;
}
/**
* Push elements in given array into target array
*
* @param {*} target The array to push into
* @param {*} xs The array of elements to push
*/
function into(target, xs) {
arrayPush.apply(target, xs);
return target;
}
/**
* Find the insert index for the given key in a sorted array.
*
* @param {*} array The sorted array to search
* @param {*} item The search key
*/
function findInsertIndex(array, item) {
// uses binary search
var lo = 0;
var hi = array.length - 1;
while (lo <= hi) {
var mid = Math.round(lo + (hi - lo) / 2);
if (item < array[mid]) {
hi = mid - 1;
} else if (item > array[mid]) {
lo = mid + 1;
} else {
return mid;
}
}
return lo;
}
/**
* This is a generic memoization function
*
* This implementation uses a cache independent of the function being memoized
* to allow old values to be garbage collected when the memoized function goes out of scope.
*
* @param {*} fn The function object to memoize
*/
function memoize(fn) {
var _this = this;
return function (memo) {
return function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var key = hashCode(args);
if (!has(memo, key)) {
memo[key] = fn.apply(_this, args);
}
return memo[key];
};
}({
/* storage */
});
} // mingo internal
/**
* Retrieve the value of a given key on an object
* @param obj
* @param field
* @returns {*}
* @private
*/
function getValue(obj, field) {
return isObjectLike(obj) ? obj[field] : undefined;
}
/**
* Resolve the value of the field (dot separated) on the given object
* @param obj {Object} the object context
* @param selector {String} dot separated path to field
* @returns {*}
*/
function resolve(obj, selector) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var depth = 0; // options
options.meta = options.meta || false;
function resolve2(o, path) {
var value = o;
for (var i = 0; i < path.length; i++) {
var field = path[i];
var isText = field.match(/^\d+$/) === null;
if (isText && isArray(value)) {
// On the first iteration, we check if we received a stop flag.
// If so, we stop to prevent iterating over a nested array value
// on consecutive object keys in the selector.
if (i === 0 && depth > 0) break;
depth += 1;
path = path.slice(i);
value = reduce(value, function (acc, item) {
var v = resolve2(item, path);
if (v !== undefined) acc.push(v);
return acc;
}, []);
break;
} else {
value = getValue(value, field);
}
if (value === undefined) break;
}
return value;
}
obj = inArray(JS_SIMPLE_TYPES, jsType(obj)) ? obj : resolve2(obj, selector.split('.'));
return options.meta ? {
result: obj,
depth: depth
} : obj;
}
/**
* Returns the full object to the resolved value given by the selector.
* This function excludes empty values as they aren't practically useful.
*
* @param obj {Object} the object context
* @param selector {String} dot separated path to field
*/
function resolveObj(obj, selector) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
// options
options.preserveMissingValues = options.preserveMissingValues || false;
var names = selector.split('.');
var key = names[0]; // get the next part of the selector
var next = names.length === 1 || names.slice(1).join('.');
var isIndex = key.match(/^\d+$/) !== null;
var hasNext = names.length > 1;
var result;
var value;
try {
if (isArray(obj)) {
if (isIndex) {
result = getValue(obj, Number(key));
if (hasNext) {
result = resolveObj(result, next, options);
}
result = [result];
} else {
result = [];
each(obj, function (item) {
value = resolveObj(item, selector, options);
if (options.preserveMissingValues) {
if (value === undefined) {
value = MISSING;
}
result.push(value);
} else if (value !== undefined) {
result.push(value);
}
});
}
} else {
value = getValue(obj, key);
if (hasNext) {
value = resolveObj(value, next, options);
}
assert(value !== undefined);
result = {};
result[key] = value;
}
} catch (e) {
result = undefined;
}
return result;
}
/**
* Filter out all MISSING values from the object in-place
* @param {*} obj The object the filter
*/
function filterMissing(obj) {
if (isArray(obj)) {
for (var i = obj.length - 1; i >= 0; i--) {
if (obj[i] === MISSING) {
obj.splice(i, 1);
} else {
filterMissing(obj[i]);
}
}
} else if (isObject(obj)) {
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
filterMissing(obj[k]);
}
}
}
return obj;
}
/**
* Walk the object graph and execute the given transform function
* @param {Object|Array} obj The object to traverse
* @param {String} selector The selector
* @param {Function} fn Function to execute for value at the end the traversal
* @param {Boolean} force Force generating missing parts of object graph
* @return {*}
*/
function traverse(obj, selector, fn) {
var force = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
var names = selector.split('.');
var key = names[0];
var next = names.length === 1 || names.slice(1).join('.');
if (names.length === 1) {
fn(obj, key);
} else {
// force the rest of the graph while traversing
if (force === true && isNil(obj[key])) {
obj[key] = {};
}
traverse(obj[key], next, fn, force);
}
}
/**
* Set the value of the given object field
*
* @param obj {Object|Array} the object context
* @param selector {String} path to field
* @param value {*} the value to set
*/
function setValue(obj, selector, value) {
traverse(obj, selector, function (item, key) {
item[key] = value;
}, true);
}
function removeValue(obj, selector) {
traverse(obj, selector, function (item, key) {
if (isArray(item) && /^\d+$/.test(key)) {
item.splice(parseInt(key), 1);
} else if (isObject(item)) {
delete item[key];
}
});
}
/**
* Check whether the given name is an operator. We assume any field name starting with '$' is an operator.
* This is cheap and safe to do since keys beginning with '$' should be reserved for internal use.
* @param {String} name
*/
function isOperator(name) {
return !!name && name[0] === '$';
}
/**
* Simplify expression for easy evaluation with query operators map
* @param expr
* @returns {*}
*/
function normalize(expr) {
// normalized primitives
if (inArray(JS_SIMPLE_TYPES, jsType(expr))) {
return isRegExp(expr) ? {
'$regex': expr
} : {
'$eq': expr
};
} // normalize object expression
if (isObjectLike(expr)) {
var exprKeys = keys(expr); // no valid query operator found, so we do simple comparison
if (!exprKeys.some(isOperator)) {
return {
'$eq': expr
};
} // ensure valid regex
if (inArray(exprKeys, '$regex')) {
var regex = expr['$regex'];
var options = expr['$options'] || '';
var modifiers = '';
if (isString(regex)) {
modifiers += regex.ignoreCase || options.indexOf('i') >= 0 ? 'i' : '';
modifiers += regex.multiline || options.indexOf('m') >= 0 ? 'm' : '';
modifiers += regex.global || options.indexOf('g') >= 0 ? 'g' : '';
regex = new RegExp(regex, modifiers);
}
expr['$regex'] = regex;
delete expr['$options'];
}
}
return expr;
}
/**
* Returns a slice of the array
*
* @param {Array} xs
* @param {Number} skip
* @param {Number} limit
* @return {Array}
*/
function slice(xs, skip) {
var limit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
// MongoDB $slice works a bit differently from Array.slice
// Uses single argument for 'limit' and array argument [skip, limit]
if (isNil(limit)) {
if (skip < 0) {
skip = Math.max(0, xs.length + skip);
limit = xs.length - skip + 1;
} else {
limit = skip;
skip = 0;
}
} else {
if (skip < 0) {
skip = Math.max(0, xs.length + skip);
}
assert(limit > 0, 'Invalid argument value for $slice operator. Limit must be a positive number');
limit += skip;
}
return xs.slice(skip, limit);
}
/**
* Compute the standard deviation of the data set
* @param {Array} array of numbers
* @param {Boolean} if true calculates a sample standard deviation, otherwise calculates a population stddev
* @return {Number}
*/
function stddev(data, sampled) {
var sum = reduce(data, function (acc, n) {
return acc + n;
}, 0);
var N = data.length || 1;
var correction = sampled && 1 || 0;
var avg = sum / N;
return Math.sqrt(reduce(data, function (acc, n) {
return acc + Math.pow(n - avg, 2);
}, 0) / (N - correction));
}
/**
* Exported to the users to allow writing custom operators
*/
function moduleApi() {
return {
assert: assert,
clone: clone,
cloneDeep: cloneDeep,
each: each,
err: err,
hashCode: hashCode,
getType: getType,
has: has,
includes: inArray.bind(null),
isArray: isArray,
isBoolean: isBoolean,
isDate: isDate,
isEmpty: isEmpty,
isEqual: isEqual,
isFunction: isFunction,
isNil: isNil,
isNull: isNull,
isNumber: isNumber,
isObject: isObject,
isRegExp: isRegExp,
isString: isString,
isUndefined: isUndefined,
keys: keys,
reduce: reduce,
resolve: resolve,
resolveObj: resolveObj
};
}
var _internal = function _internal() {
return Object.assign({
computeValue: computeValue,
ops: ops
}, moduleApi());
}; // Settings used by Mingo internally
var settings = {
key: '_id'
};
/**
* Setup default settings for Mingo
* @param options
*/
function setup(options) {
Object.assign(settings, options || {});
}
/**
* Implementation of system variables
* @type {Object}
*/
var systemVariables = {
'$$ROOT': function $$ROOT(obj, expr, opt) {
return opt.root;
},
'$$CURRENT': function $$CURRENT(obj, expr, opt) {
return obj;
},
'$$REMOVE': function $$REMOVE(obj, expr, opt) {
return undefined;
}
};
/**
* Implementation of $redact variables
*
* Each function accepts 3 arguments (obj, expr, opt)
*
* @type {Object}
*/
var redactVariables = {
'$$KEEP': function $$KEEP(obj) {
return obj;
},
'$$PRUNE': function $$PRUNE() {
return undefined;
},
'$$DESCEND': function $$DESCEND(obj, expr, opt) {
// traverse nested documents iff there is a $cond
if (!has(expr, '$cond')) return obj;
var result;
each(obj, function (current, key) {
if (isObjectLike(current)) {
if (isArray(current)) {
result = [];
each(current, function (elem) {
if (isObject(elem)) {
elem = redactObj(elem, expr, opt);
}
if (!isNil(elem)) result.push(elem);
});
} else {
result = redactObj(current, expr, opt);
}
if (isNil(result)) {
delete obj[key]; // pruned result
} else {
obj[key] = result;
}
}
});
return obj;
}
}; // system variables
var SYS_VARS = keys(systemVariables);
var REDACT_VARS = keys(redactVariables);
/**
* Returns the key used as the collection's objects ids
*/
function idKey() {
return settings.key;
}
/**
* Returns the operators defined for the given operator classes
*/
function ops() {
// Workaround for browser-compatibility bug: on iPhone 6S Safari (and
// probably some other platforms), `arguments` isn't detected as an array,
// but has a length field, so functions like `reduce` end up including the
// length field in their iteration. Copy to a real array.
var args = Array.prototype.slice.call(arguments);
return reduce(args, function (acc, cls) {
return into(acc, keys(OPERATORS[cls]));
}, []);
}
/**
* Returns the result of evaluating a $group operation over a collection
*
* @param collection
* @param field the name of the aggregate operator or field
* @param expr the expression of the aggregate operator for the field
* @returns {*}
*/
function accumulate(collection, field, expr) {
if (has(OPERATORS[OP_GROUP], field)) {
return OPERATORS[OP_GROUP][field](collection, expr);
}
if (isObject(expr)) {
var result = {};
each(expr, function (val, key) {
result[key] = accumulate(collection, key, expr[key]); // must run ONLY one group operator per expression
// if so, return result of the computed value
if (has(OPERATORS[OP_GROUP], key)) {
result = result[key]; // if there are more keys in expression this is bad
assert(keys(expr).length === 1, "Invalid $group expression '" + JSON.stringify(expr) + "'");
return false; // break
}
});
return result;
}
}
/**
* Computes the actual value of the expression using the given object as context
*
* @param obj the current object from the collection
* @param expr the expression for the given field
* @param operator the operator to resolve the field with
* @param opt {Object} extra options
* @returns {*}
*/
function computeValue(obj, expr) {
var operator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var opt = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
opt.root = opt.root || obj; // if the field of the object is a valid operator
if (has(OPERATORS[OP_EXPRESSION], operator)) {
return OPERATORS[OP_EXPRESSION][operator](obj, expr, opt);
} // we also handle $group accumulator operators
if (has(OPERATORS[OP_GROUP], operator)) {
// we first fully resolve the expression
obj = computeValue(obj, expr, null, opt);
assert(isArray(obj), operator + ' expression must resolve to an array'); // we pass a null expression because all values have been resolved
return OPERATORS[OP_GROUP][operator](obj, null, opt);
} // if expr is a variable for an object field
// field not used in this case
if (isString(expr) && expr.length > 0 && expr[0] === '$') {
// we return system variables as literals
if (inArray(SYS_VARS, expr)) {
return systemVariables[expr](obj, null, opt);
} else if (inArray(REDACT_VARS, expr)) {
return expr;
} // handle selectors with explicit prefix
var sysVar = SYS_VARS.filter(function (v) {
return expr.indexOf(v + '.') === 0;
});
if (sysVar.length === 1) {
sysVar = sysVar[0];
if (sysVar === '$$ROOT') {
obj = opt.root;
}
expr = expr.substr(sysVar.length); // '.' prefix will be sliced off below
}
return resolve(obj, expr.slice(1));
} // check and return value if already in a resolved state
switch (jsType(expr)) {
case T_ARRAY:
return expr.map(function (item) {
return computeValue(obj, item);
});
case T_OBJECT:
var result = {};
each(expr, function (val, key) {
result[key] = computeValue(obj, val, key, opt); // must run ONLY one aggregate operator per expression
// if so, return result of the computed value
if ([OP_EXPRESSION, OP_GROUP].some(function (c) {
return has(OPERATORS[c], key);
})) {
// there should be only one operator
assert(keys(expr).length === 1, "Invalid aggregation expression '" + JSON.stringify(expr) + "'");
result = result[key];
return false; // break
}
});
return result;
default:
return expr;
}
}
/**
* Redact an object
* @param {Object} obj The object to redact
* @param {*} expr The redact expression
* @param {*} opt Options for value
* @return {*} Returns the redacted value
*/
function redactObj(obj, expr) {
var opt = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
opt.root = opt.root || obj;
var result = computeValue(obj, expr, null, opt);
return inArray(REDACT_VARS, result) ? redactVariables[result](obj, expr, opt) : result;
}
/**
* Returns the absolute value of a number.
* https://docs.mongodb.com/manual/reference/operator/aggregation/abs/#exp._S_abs
*
* @param obj
* @param expr
* @return {Number|null|NaN}
*/
function $abs(obj, expr) {
var val = computeValue(obj, expr);
return val === null || val === undefined ? null : Math.abs(val);
}
/**
* Computes the sum of an array of numbers.
*
* @param obj
* @param expr
* @returns {Object}
*/
function $add(obj, expr) {
var args = computeValue(obj, expr);
var foundDate = false;
var result = reduce(args, function (acc, val) {
if (isDate(val)) {
assert(!foundDate, "'$add' can only have one date value");
foundDate = true;
val = val.getTime();
} // assume val is a number
acc += val;
return acc;
}, 0);
return foundDate ? new Date(result) : result;
}
/**
* Returns the smallest integer greater than or equal to the specified number.
*
* @param obj
* @param expr
* @returns {number}
*/
function $ceil(obj, expr) {
var arg = computeValue(obj, expr);
if (isNil(arg)) return null;
assert(isNumber(arg) || isNaN(arg), '$ceil expression must resolve to a number.');
return Math.ceil(arg);
}
/**
* Takes two numbers and divides the first number by the second.
*
* @param obj
* @param expr
* @returns {number}
*/
function $divide(obj, expr) {
var args = computeValue(obj, expr);
return args[0] / args[1];
}
/**
* Raises Eulers number (i.e. e ) to the specified exponent and returns the result.
*
* @param obj
* @param expr
* @returns {number}
*/
function $exp(obj, expr) {
var arg = computeValue(obj, expr);
if (isNil(arg)) return null;
assert(isNumber(arg) || isNaN(arg), '$exp expression must resolve to a number.');
return Math.exp(arg);
}
/**
* Returns the largest integer less than or equal to the specified number.
*
* @param obj
* @param expr
* @returns {number}
*/
function $floor(obj, expr) {
var arg = computeValue(obj, expr);
if (isNil(arg)) return null;
assert(isNumber(arg) || isNaN(arg), '$floor expression must resolve to a number.');
return Math.floor(arg);
}
/**
* Calculates the natural logarithm ln (i.e loge) of a number and returns the result as a double.
*
* @param obj
* @param expr
* @returns {number}
*/
function $ln(obj, expr) {
var arg = computeValue(obj, expr);
if (isNil(arg)) return null;
assert(isNumber(arg) || isNaN(arg), '$ln expression must resolve to a number.');
return Math.log(arg);
}
/**
* Calculates the log of a number in the specified base and returns the result as a double.
*
* @param obj
* @param expr
* @returns {number}
*/
function $log(obj, expr) {
var args = computeValue(obj, expr);
var msg = '$log expression must resolve to array(2) of numbers';
assert(isArray(args) && args.length === 2, msg);
if (args.some(isNil)) return null;
assert(args.some(isNaN) || args.every(isNumber), msg);
return Math.log10(args[0]) / Math.log10(args[1]);
}
/**
* Calculates the log base 10 of a number and returns the result as a double.
*
* @param obj
* @param expr
* @returns {number}
*/
function $log10(obj, expr) {
var arg = computeValue(obj, expr);
if (isNil(arg)) return null;
assert(isNumber(arg) || isNaN(arg), '$log10 expression must resolve to a number.');
return Math.log10(arg);
}
/**
* Takes two numbers and calculates the modulo of the first number divided by the second.
*
* @param obj
* @param expr
* @returns {number}
*/
function $mod(obj, expr) {
var args = computeValue(obj, expr);
return args[0] % args[1];
}
/**
* Computes the product of an array of numbers.
*
* @param obj
* @param expr
* @returns {Object}
*/
function $multiply(obj, expr) {
var args = computeValue(obj, expr);
return reduce(args, function (acc, num) {
return acc * num;
}, 1);
}
/**
* Raises a number to the specified exponent and returns the result.
*
* @param obj
* @param expr
* @returns {Object}
*/
function $pow(obj, expr) {
var args = computeValue(obj, expr);
assert(isArray(args) && args.length === 2 && args.every(isNumber), '$pow expression must resolve to array(2) of numbers');
assert(!(args[0] === 0 && args[1] < 0), '$pow cannot raise 0 to a negative exponent');
return Math.pow(args[0], args[1]);
}
/**
* Rounds a number to to a whole integer or to a specified decimal place.
* @param {*} obj
* @param {*} expr
*/
function $round(obj, expr) {
var args = computeValue(obj, expr);
var num = args[0];
var place = args[1];
if (isNil(num) || isNaN(num) || Math.abs(num) === Infinity) return num;
assert(isNumber(num), '$round expression must resolve to a number.');
return truncate(num, place, true);
}
/**
* Calculates the square root of a positive number and returns the result as a double.
*
* @param obj
* @param expr
* @returns {number}
*/
function $sqrt(obj, expr) {
var n = computeValue(obj, expr);
if (isNil(n)) return null;
assert(isNumber(n) && n > 0 || isNaN(n), '$sqrt expression must resolve to non-negative number.');
return Math.sqrt(n);
}
/**
* Takes an array that contains two numbers or two dates and subtracts the second value from the first.
*
* @param obj
* @param expr
* @returns {number}
*/
function $subtract(obj, expr) {
var args = computeValue(obj, expr);
return args[0] - args[1];
}
/**
* Truncates a number to a whole integer or to a specified decimal place.
*
* @param obj
* @param expr
* @returns {number}
*/
function $trunc(obj, expr) {
var arr = computeValue(obj, expr);
var num = arr[0];
var places = arr[1];
if (isNil(num) || isNaN(num) || Math.abs(num) === Infinity) return num;
assert(isNumber(num), '$trunc expression must resolve to a number.');
assert(isNil(places) || isNumber(places) && places > -20 && places < 100, "$trunc expression has invalid place");
return truncate(num, places, false);
}
/**
* Truncates integer value to number of places. If roundOff is specified round value instead to the number of places
* @param {Number} num
* @param {Number} places
* @param {Boolean} roundOff
*/
function truncate(num, places, roundOff) {
places = places || 0;
var sign = Math.abs(num) === num ? 1 : -1;
num = Math.abs(num);
var result = Math.trunc(num);
var decimals = num - result;
if (places === 0) {
var firstDigit = Math.trunc(10 * decimals);
if (roundOff && (result & 1) === 1 && firstDigit >= 5) {
result++;
}
} else if (places > 0) {
var offset = Math.pow(10, places);
var remainder = Math.trunc(decimals * offset); // last digit before cut off
var lastDigit = Math.trunc(decimals * offset * 10) % 10; // add one if last digit is greater than 5
if (roundOff && lastDigit > 5) {
remainder += 1;
} // compute decimal remainder and add to whole number
result += remainder / offset;
} else if (places < 0) {
// handle negative decimal places
var _offset = Math.pow(10, -1 * places);
var excess = result % _offset;
result = Math.max(0, result - excess); // for negative values the absolute must increase so we round up the last digit if >= 5
if (roundOff && sign === -1) {
while (excess > 10) {
excess -= excess % 10;
}
if (result > 0 && excess >= 5) {
result += _offset;
}
}
}
return result * sign;
}
/**
* Returns the element at the specified array index.
*
* @param {Object} obj
* @param {*} expr
* @return {*}
*/
function $arrayElemAt(obj, expr) {
var arr = computeValue(obj, expr);
assert(isArray(arr) && arr.length === 2, '$arrayElemAt expression must resolve to array(2)');
assert(isArray(arr[0]), 'First operand to $arrayElemAt must resolve to an array');
assert(isNumber(arr[1]), 'Second operand to $arrayElemAt must resolve to an integer');
var idx = arr[1];
arr = arr[0];
if (idx < 0 && Math.abs(idx) <= arr.length) {
return arr[idx + arr.length];
} else if (idx >= 0 && idx < arr.length) {
return arr[idx];
}
return undefined;
}
/**
* Converts an array of key value pairs to a document.
*/
function $arrayToObject(obj, expr) {
var arr = computeValue(obj, expr);
assert(isArray(arr), '$arrayToObject expression must resolve to an array');
return reduce(arr, function (newObj, val) {
if (isArray(val) && val.length == 2) {
newObj[val[0]] = val[1];
} else {
assert(isObject(val) && has(val, 'k') && has(val, 'v'), '$arrayToObject expression is invalid.');
newObj[val.k] = val.v;
}
return newObj;
}, {});
}
/**
* Concatenates arrays to return the concatenated array.
*
* @param {Object} obj
* @param {*} expr
* @return {*}
*/
function $concatArrays(obj, expr) {
var arr = computeValue(obj, expr, null);
assert(isArray(arr), '$concatArrays must resolve to an array');
if (arr.some(isNil)) return null;
return arr.reduce(function (acc, item) {
return into(acc, item);
}, []);
}
/**
* Selects a subset of the array to return an array with only the elements that match the filter condition.
*
* @param {Object} obj [description]
* @param {*} expr [description]
* @return {*} [description]
*/
function $filter(obj, expr) {
var input = computeValue(obj, expr.input);
var asVar = expr['as'];
var condExpr = expr['cond'];
assert(isArray(input), "$filter 'input' expression must resolve to an array");
return input.filter(function (o) {
// inject variable
var tempObj = {};
tempObj['$' + asVar] = o;
return computeValue(tempObj, condExpr) === true;
});
}
/**
* Returns a boolean indicating whether a specified value is in an array.
*
* @param {Object} obj
* @param {Array} expr
*/
function $in(obj, expr) {
var val = computeValue(obj, expr[0]);
var arr = computeValue(obj, expr[1]);
assert(isArray(arr), '$in second argument must be an array');
return arr.some(isEqual.bind(null, val));
}
/**
* Searches an array for an occurrence of a specified value and returns the array index of the first occurrence.
* If the substring is not found, returns -1.
*
* @param {Object} obj
* @param {*} expr
* @return {*}
*/
function $indexOfArray(obj, expr) {
var args = computeValue(obj, expr);
if (isNil(args)) return null;
var arr = args[0];
var searchValue = args[1];
if (isNil(arr)) return null;
assert(isArray(arr), '$indexOfArray expression must resolve to an array.');
var start = args[2] || 0;
var end = args[3];
if (isNil(end)) end = arr.length;
if (start > end) return -1;
assert(start >= 0 && end >= 0, '$indexOfArray expression is invalid');
if (start > 0 || end < arr.length) {
arr = arr.slice(start, end);
}
return arr.findIndex(isEqual.bind(null, searchValue)) + start;
}
/**
* Determines if the operand is an array. Returns a boolean.
*
* @param {Object} obj
* @param {*} expr
* @return {Boolean}
*/
function $isArray(obj, expr) {
return isArray(computeValue(obj, expr[0]));
}
/**
* Applies a sub-expression to each element of an array and returns the array of resulting values in order.
*
* @param obj
* @param expr
* @returns {Array|*}
*/
function $map(obj, expr) {
var inputExpr = computeValue(obj, expr.input);
assert(isArray(inputExpr), "$map 'input' expression must resolve to an array");
var asExpr = expr['as'];
var inExpr = expr['in']; // HACK: add the "as" expression as a value on the object to take advantage of "resolve()"
// which will reduce to that value when invoked. The reference to the as expression will be prefixed with "$$".
// But since a "$" is stripped of before passing the name to "resolve()" we just need to prepend "$" to the key.
var tempKey = '$' + asExpr;
return inputExpr.map(function (item) {
obj[tempKey] = item;
return computeValue(obj, inExpr);
});
}
/**
* Converts a document to an array of documents representing key-value pairs.
*/
function $objectToArray(obj, expr) {
var val = computeValue(obj, expr);
assert(isObject(val), '$objectToArray expression must resolve to an object');
var arr = [];
each(val, function (v, k) {
return arr.push({
k: k,
v: v
});
});
return arr;
}
/**
* Returns an array whose elements are a generated sequence of numbers.
*
* @param {Object} obj
* @param {*} expr
* @return {*}
*/
function $range(obj, expr) {
var arr = computeValue(obj, expr);
var start = arr[0];
var end = arr[1];
var step = arr[2] || 1;
var result = [];
while (start < end && step > 0 || start > end && step < 0) {
result.push(start);
start += step;
}
return result;
}
/**
* Applies an expression to each element in an array and combines them into a single value.
*
* @param {Object} obj
* @param {*} expr
*/
function $reduce(obj, expr) {
var input = computeValue(obj, expr.input);
var initialValue = computeValue(obj, expr.initialValue);
var inExpr = expr['in'];
if (isNil(input)) return null;
assert(isArray(input), "$reduce 'input' expression must resolve to an array");
return reduce(input, function (acc, n) {
return computeValue({
'$value': acc,
'$this': n
}, inExpr);
}, initialValue);
}
/**
* Returns an array with the elements in reverse order.
*
* @param {Object} obj
* @param {*} expr
* @return {*}
*/
function $reverseArray(obj, expr) {
var arr = computeValue(obj, expr);
if (isNil(arr)) return null;
assert(isArray(arr), '$reverseArray expression must resolve to an array');
var result = [];
into(result, arr);
result.reverse();
return result;
}
/**
* Counts and returns the total the number of items in an array.
*
* @param obj
* @param expr
*/
function $size(obj, expr) {
var value = computeValue(obj, expr);
return isArray(value) ? value.length : undefined;
}
/**
* Returns a subset of an array.
*
* @param {Object} obj
* @param {*} expr
* @return {*}
*/
function $slice(obj, expr) {
var arr = computeValue(obj, expr);
return slice(arr[0], arr[1], arr[2]);
}
/**
* Merge two lists together.
*
* Transposes an array of input arrays so that the first element of the output array would be an array containing,
* the first element of the first input array, the first element of the second input array, etc.
*
* @param {Obj} obj
* @param {*} expr
* @return {*}
*/
function $zip(obj, expr) {
var inputs = computeValue(obj, expr.inputs);
var useLongestLength = expr.useLongestLength || false;
assert(isArray(inputs), "'inputs' expression must resolve to an array");
assert(isBoolean(useLongestLength), "'useLongestLength' must be a boolean");
if (isArray(expr.defaults)) {
assert(truthy(useLongestLength), "'useLongestLength' must be set to true to use 'defaults'");
}
var zipCount = 0;
for (var i = 0, len = inputs.length; i < len; i++) {
var arr = inputs[i];
if (isNil(arr)) return null;
assert(isArray(arr), "'inputs' expression values must resolve to an array or null");
zipCount = useLongestLength ? Math.max(zipCount, arr.length) : Math.min(zipCount || arr.length, arr.length);
}
var result = [];
var defaults = expr.defaults || [];
var _loop = function _loop(_i) {
var temp = inputs.map(function (val, index) {
return isNil(val[_i]) ? defaults[index] || null : val[_i];
});
result.push(temp);
};
for (var _i = 0; _i < zipCount; _i++) {
_loop(_i);
}
return result;
}
/**
* Combines multiple documents into a single document.
* @param {*} obj
* @param {*} expr
*/
function $mergeObjects(obj, expr) {
var docs = computeValue(obj, expr);
if (isArray(docs)) {
return reduce(docs, function (memo, o) {
return Object.assign(memo, o);
}, {});
}
return {};
}
/**
* Returns true only when all its expressions evaluate to true. Accepts any number of argument expressions.
*
* @param obj
* @param expr
* @returns {boolean}
*/
function $and(obj, expr) {
var value = computeValue(obj, expr);
return truthy(value) && value.every(truthy);
}
/**
* Returns true when any of its expressions evaluates to true. Accepts any number of argument expressions.
*
* @param obj
* @param expr
* @returns {boolean}
*/
function $or(obj, expr) {
var value = computeValue(obj, expr);
return truthy(value) && value.some(truthy);
}
/**
* Returns the boolean value that is the opposite of its argument expression. Accepts a single argument expression.
*
* @param obj
* @param expr
* @returns {boolean}
*/
function $not(obj, expr) {
return !computeValue(obj, expr[0]);
}
/**
* Returns an iterator
* @param {*} source An iterable source (Array, Function, Object{next:Function})
*/
function Lazy(source) {
return source instanceof Iterator ? source : new Iterator(source);
}
Lazy.isIterator = isIterator;
/**
* Checks whether the given object is compatible with iterator i.e Object{next:Function}
* @param {*} o An object
*/
function isIterator(o) {
return !!o && _typeof(o) === 'object' && isFn(o.next);
}
function isFn(f) {
return !!f && typeof f === 'function';
}
function dropItem(array, i) {
var rest = array.slice(i + 1);
array.splice(i);
Array.prototype.push.apply(array, rest);
} // stop iteration error
var DONE = new Error(); // Lazy function type flags
var LAZY_MAP = 1;
var LAZY_FILTER = 2;
var LAZY_TAKE = 3;
var LAZY_DROP = 4;
function baseIterator(nextFn, iteratees, buffer) {
var done = false;
var index = -1;
var bIndex = 0; // index for the buffer
return function (b) {
// special hack to collect all values into buffer
b = b === buffer;
try {
outer: while (!done) {
var o = nextFn();
index++;
var mIndex = -1;
var mSize = iteratees.length;
var innerDone = false;
while (++mIndex < mSize) {
var member = iteratees[mIndex],
func = member.func,
type = member.type;
switch (type) {
case LAZY_MAP:
o = func(o, index);
break;
case LAZY_FILTER:
if (!func(o, index)) continue outer;
break;
case LAZY_TAKE:
--member.func;
if (!member.func) innerDone = true;
break;
case LAZY_DROP:
--member.func;
if (!member.func) dropItem(iteratees, mIndex);
continue outer;
default:
break outer;
}
}
done = innerDone;
if (b) {
buffer[bIndex++] = o;
} else {
return {
value: o,
done: false
};
}
}
} catch (e) {
if (e !== DONE) throw e;
}
done = true;
return {
done: true
};
};
}
var Iterator = /*#__PURE__*/function () {
/**
* @param {*} source An iterable object or function.
* Array - return one element per cycle
* Object{next:Function} - call next() for the next value (this also handles generator functions)
* Function - call to return the next value
* @param {Function} fn An optional transformation function
*/
function Iterator(source) {
_classCallCheck(this, Iterator);
this.__iteratees = []; // lazy function chain
this.__first = false; // flag whether to return a single value
this.__done = false;
this.__buf = [];
if (isFn(source)) {
// make iterable
source = {
next: source
};
}
if (isIterator(source)) {
var src = source;
source = function source() {
var o = src.next();
if (o.done) throw DONE;
return o.value;
};
} else if (Array.isArray(source)) {
var data = source;
var size = data.length;
var index = 0;
source = function source() {
if (index < size) return data[index++];
throw DONE;
};
} else if (!isFn(source)) {
throw new Error("Source is not iterable. Must be Array, Function or Object{next:Function}");
} // create next function
this.next = baseIterator(source, this.__iteratees, this.__buf);
}
_createClass(Iterator, [{
key: "_validate",
value: function _validate() {
if (this.__first) throw new Error("Cannot add iteratee/transform after `first()`");
}
/**
* Add an iteratee to this lazy sequence
* @param {Object} iteratee
*/
}, {
key: "_push",
value: function _push(iteratee) {
this._validate();
this.__iteratees.push(iteratee);
return this;
} // Iteratees methods
/**
* Transform each item in the sequence to a new value
* @param {Function} f
*/
}, {
key: "map",
value: function map(f) {
return this._push({
type: LAZY_MAP,
func: f
});
}
/**
* Select only items matching the given predicate
* @param {Function} pred
*/
}, {
key: "filter",
value: function filter(pred) {
return this._push({
type: LAZY_FILTER,
func: pred
});
}
/**
* Take given numbe for values from sequence
* @param {Number} n A number greater than 0
*/
}, {
key: "take",
value: function take(n) {
return n > 0 ? this._push({
type: LAZY_TAKE,
func: n
}) : this;
}
/**
* Drop a number of values from the sequence
* @param {Number} n Number of items to drop greater than 0
*/
}, {
key: "drop",
value: function drop(n) {
return n > 0 ? this._push({
type: LAZY_DROP,
func: n
}) : this;
} // Transformations
/**
* Returns a new lazy object with results of the transformation
* The entire sequence is realized.
*
* @param {Function} fn Tranform function of type (Array) => (Any)
*/
}, {
key: "transform",
value: function transform(fn) {
this._validate();
var self = this;
var iter;
return Lazy(function () {
if (!iter) {
iter = Lazy(fn(self.value()));
}
return iter.next();
});
}
/**
* Mark this lazy object to return only the first result on `lazy.value()`.
* No more iteratees or transformations can be added after this method is called.
*/
}, {
key: "first",
value: function first() {
this.take(1);
this.__first = true;
return this;
} // Terminal methods
/**
* Returns the fully realized values of the iterators.
* The return value will be an array unless `lazy.first()` was used.
* The realized values are cached for subsequent calls
*/
}, {
key: "value",
value: function value() {
if (!this.__done) {
this.__done = this.next(this.__buf).done;
}
return this.__first ? this.__buf[0] : this.__buf;
}
/**
* Execute the funcion for each value. Will stop when an execution returns false.
* @param {Function} f
* @returns {Boolean} false iff `f` return false for any execution, otherwise true
*/
}, {
key: "each",
value: function each(f) {
while (1) {
var o = this.next();
if (o.done) break;
if (f(o.value) === false) return false;
}
return true;
}
/**
* Returns the reduction of sequence according the reducing function
*
* @param {*} f a reducing function
* @param {*} init
*/
}, {
key: "reduce",
value: function reduce(f, init) {
var o = this.next();
var i = 0;
if (init === undefined && !o.done) {
init = o.value;
o = this.next();
i++;
}
while (!o.done) {
init = f(init, o.value, i++);
o = this.next();
}
return init;
}
/**
* Returns the number of matched items in the sequence
*/
}, {
key: "size",
value: function size() {
return this.reduce(function (acc, n) {
return ++acc;
}, 0);
}
}]);
return Iterator;
}();
if (typeof Symbol === 'function') {
Iterator.prototype[Symbol.iterator] = function () {
return this;
};
}
/**
* Aggregator for defining filter using mongoDB aggregation pipeline syntax
*
* @param operators an Array of pipeline operators
* @constructor
*/
var Aggregator = /*#__PURE__*/function () {
function Aggregator(operators, options) {
_classCallCheck(this, Aggregator);
this.__operators = operators;
this.__options = options;
}
/**
* Returns an `Lazy` iterator for processing results of pipeline
*
* @param {*} collection An array or iterator object
* @param {Query} query the `Query` object to use as context
* @returns {Iterator} an iterator object
*/
_createClass(Aggregator, [{
key: "stream",
value: function stream(collection, query) {
var _this = this;
collection = Lazy(collection);
var pipelineOperators = OPERATORS[OP_PIPELINE];
if (!isEmpty(this.__operators)) {
// run aggregation pipeline
each(this.__operators, function (operator) {
var key = keys(operator);
assert(key.length === 1 && inArray(ops(OP_PIPELINE), key[0]), "invalid aggregation operator ".concat(key));
key = key[0];
if (query && query instanceof Query) {
collection = pipelineOperators[key].call(query, collection, operator[key], _this.__options);
} else {
collection = pipelineOperators[key](collection, operator[key], _this.__options);
}
});
}
return collection;
}
/**
* Return the results of the aggregation as an array.
* @param {*} collection
* @param {*} query
*/
}, {
key: "run",
value: function run(collection, query) {
return this.stream(collection, query).value();
}
}]);
return Aggregator;
}();
/**
* Return the result collection after running the aggregation pipeline for the given collection.
* Shorthand for `(new Aggregator(pipeline, options)).run(collection)`
*
* @param {Array} collection Collection or stream of objects
* @param {Array} pipeline The pipeline operators to use
* @returns {Array}
*/
function aggregate(collection, pipeline, options) {
assert(isArray(pipeline), 'Aggregation pipeline must be an array');
return new Aggregator(pipeline, options).run(collection);
}
/**
* Cursor to iterate and perform filtering on matched objects
* @param collection
* @param query
* @param projection
* @constructor
*/
var Cursor = /*#__PURE__*/function () {
function Cursor(source, query, projection) {
_classCallCheck(this, Cursor);
this.__filterFn = query.test.bind(query);
this.__query = query;
this.__source = source;
this.__projection = projection || query.__projection;
this.__operators = [];
this.__result = null;
this.__stack = [];
this.__options = {};
}
_createClass(Cursor, [{
key: "_fetch",
value: function _fetch() {
if (!!this.__result) return this.__result; // add projection operator
if (isObject(this.__projection)) this.__operators.push({
'$project': this.__projection
}); // filter collection
this.__result = Lazy(this.__source).filter(this.__filterFn);
if (this.__operators.length > 0) {
this.__result = new Aggregator(this.__operators, this.__options).stream(this.__result, this.__query);
}
return this.__result;
}
/**
* Return remaining objects in the cursor as an array. This method exhausts the cursor
* @returns {Array}
*/
}, {
key: "all",
value: function all() {
return this._fetch().value();
}
/**
* Returns the number of objects return in the cursor. This method exhausts the cursor
* @returns {Number}
*/
}, {
key: "count",
value: function count() {
return this.all().length;
}
/**
* Returns a cursor that begins returning results only after passing or skipping a number of documents.
* @param {Number} n the number of results to skip.
* @return {Cursor} Returns the cursor, so you can chain this call.
*/
}, {
key: "skip",
value: function skip(n) {
this.__operators.push({
'$skip': n
});
return this;
}
/**
* Constrains the size of a cursor's result set.
* @param {Number} n the number of results to limit to.
* @return {Cursor} Returns the cursor, so you can chain this call.
*/
}, {
key: "limit",
value: function limit(n) {
this.__operators.push({
'$limit': n
});
return this;
}
/**
* Returns results ordered according to a sort specification.
* @param {Object} modifier an object of key and values specifying the sort order. 1 for ascending and -1 for descending
* @return {Cursor} Returns the cursor, so you can chain this call.
*/
}, {
key: "sort",
value: function sort(modifier) {
this.__operators.push({
'$sort': modifier
});
return this;
}
/**
* Specifies the collation for the cursor returned by the `mingo.Query.find`
* @param {*} options
*/
}, {
key: "collation",
value: function collation(options) {
this.__options['collation'] = options;
return this;
}
/**
* Returns the next document in a cursor.
* @returns {Object | Boolean}
*/
}, {
key: "next",
value: function next() {
if (!this.__stack) return; // done
if (this.__stack.length > 0) return this.__stack.pop(); // yield value obtains in hasNext()
var o = this._fetch().next();
if (!o.done) return o.value;
this.__stack = null;
return;
}
/**
* Returns true if the cursor has documents and can be iterated.
* @returns {boolean}
*/
}, {
key: "hasNext",
value: function hasNext() {
if (!this.__stack) return false; // done
if (this.__stack.length > 0) return true; // there is a value on stack
var o = this._fetch().next();
if (!o.done) {
this.__stack.push(o.value);
} else {
this.__stack = null;
}
return !!this.__stack;
}
/**
* Applies a function to each document in a cursor and collects the return values in an array.
* @param callback
* @returns {Array}
*/
}, {
key: "map",
value: function map(callback) {
return this._fetch().map(callback).value();
}
/**
* Applies a JavaScript function for every document in a cursor.
* @param callback
*/
}, {
key: "forEach",
value: function forEach(callback) {
this._fetch().each(callback);
}
}]);
return Cursor;
}();
if (typeof Symbol === 'function') {
/**
* Applies an [ES2015 Iteration protocol][] compatible implementation
* [ES2015 Iteration protocol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
* @returns {Object}
*/
Cursor.prototype[Symbol.iterator] = function () {
return this._fetch();
};
}
/**
* Query object to test collection elements with
* @param criteria the pass criteria for the query
* @param projection optional projection specifiers
* @constructor
*/
var Query = /*#__PURE__*/function () {
function Query(criteria, projection) {
_classCallCheck(this, Query);
this.__criteria = criteria;
this.__projection = projection || {};
this.__compiled = [];
this._compile();
}
_createClass(Query, [{
key: "_compile",
value: function _compile() {
var _this = this;
assert(isObject(this.__criteria), 'query criteria must be an object');
var whereOperator;
each(this.__criteria, function (expr, field) {
// save $where operators to be executed after other operators
if ('$where' === field) {
whereOperator = {
field: field,
expr: expr
};
} else if ('$expr' === field) {
_this._processOperator(field, field, expr);
} else if (inArray(['$and', '$or', '$nor'], field)) {
_this._processOperator(field, field, expr);
} else {
// normalize expression
assert(!isOperator(field), "unknown top level operator: ".concat(field));
expr = normalize(expr);
each(expr, function (val, op) {
_this._processOperator(field, op, val);
});
}
if (isObject(whereOperator)) {
_this._processOperator(whereOperator.field, whereOperator.field, whereOperator.expr);
}
});
}
}, {
key: "_processOperator",
value: function _processOperator(field, operator, value) {
assert(has(OPERATORS[OP_QUERY], operator), "invalid query operator ".concat(operator, " detected"));
this.__compiled.push(OPERATORS[OP_QUERY][operator](field, value));
}
/**
* Checks if the object passes the query criteria. Returns true if so, false otherwise.
* @param obj
* @returns {boolean}
*/
}, {
key: "test",
value: function test(obj) {
for (var i = 0, len = this.__compiled.length; i < len; i++) {
if (!this.__compiled[i](obj)) {
return false;
}
}
return true;
}
/**
* Performs a query on a collection and returns a cursor object.
* @param collection
* @param projection
* @returns {Cursor}
*/
}, {
key: "find",
value: function find(collection, projection) {
return new Cursor(collection, this, projection);
}
/**
* Remove matched documents from the collection returning the remainder
* @param collection
* @returns {Array}
*/
}, {
key: "remove",
value: function remove(collection) {
var _this2 = this;
return reduce(collection, function (acc, obj) {
if (!_this2.test(obj)) acc.push(obj);
return acc;
}, []);
}
}]);
return Query;
}();
/**
* Performs a query on a collection and returns a cursor object.
*
* @param collection
* @param criteria
* @param projection
* @returns {Cursor}
*/
function find(collection, criteria, projection) {
return new Query(criteria).find(collection, projection);
}
/**
* Returns a new array without objects which match the criteria
*
* @param collection
* @param criteria
* @returns {Array}
*/
function remove(collection, criteria) {
return new Query(criteria).remove(collection);
}
/**
* Query and Projection Operators. https://docs.mongodb.com/manual/reference/operator/query/
*/
/**
* Checks that two values are equal.
*
* @param a The lhs operand as resolved from the object by the given selector
* @param b The rhs operand provided by the user
* @returns {*}
*/
function $eq(a, b) {
// start with simple equality check
if (isEqual(a, b)) return true; // https://docs.mongodb.com/manual/tutorial/query-for-null-fields/
if (isNil(a) && isNil(b)) return true; // check
if (isArray(a)) {
var eq = isEqual.bind(null, b);
return a.some(eq) || flatten(a, 1).some(eq);
}
return false;
}
/**
* Matches all values that are not equal to the value specified in the query.
*
* @param a
* @param b
* @returns {boolean}
*/
function $ne(a, b) {
return !$eq(a, b);
}
/**
* Matches any of the values that exist in an array specified in the query.
*
* @param a
* @param b
* @returns {*}
*/
function $in$1(a, b) {
// queries for null should be able to find undefined fields
if (isNil(a)) return b.some(isNull);
return intersection(ensureArray(a), b).length > 0;
}
/**
* Matches values that do not exist in an array specified to the query.
*
* @param a
* @param b
* @returns {*|boolean}
*/
function $nin(a, b) {
return !$in$1(a, b);
}
/**
* Matches values that are less than the value specified in the query.
*
* @param a
* @param b
* @returns {boolean}
*/
function $lt(a, b) {
return compare$1(a, b, function (x, y) {
return x < y;
});
}
/**
* Matches values that are less than or equal to the value specified in the query.
*
* @param a
* @param b
* @returns {boolean}
*/
function $lte(a, b) {
return compare$1(a, b, function (x, y) {
return x <= y;
});
}
/**
* Matches values that are greater than the value specified in the query.
*
* @param a
* @param b
* @returns {boolean}
*/
function $gt(a, b) {
return compare$1(a, b, function (x, y) {
return x > y;
});
}
/**
* Matches values that are greater than or equal to the value specified in the query.
*
* @param a
* @param b
* @returns {boolean}
*/
function $gte(a, b) {
return compare$1(a, b, function (x, y) {
return x >= y;
});
}
/**
* Performs a modulo operation on the value of a field and selects documents with a specified result.
*
* @param a
* @param b
* @returns {boolean}
*/
function $mod$1(a, b) {
return ensureArray(a).some(function (x) {
return isNumber(x) && isArray(b) && b.length === 2 && x % b[0] === b[1];
});
}
/**
* Selects documents where values match a specified regular expression.
*
* @param a
* @param b
* @returns {boolean}
*/
function $regex(a, b) {
a = ensureArray(a);
var match = function match(x) {
return isString(x) && !!x.match(b);
};
return a.some(match) || flatten(a, 1).some(match);
}
/**
* Matches documents that have the specified field.
*
* @param a
* @param b
* @returns {boolean}
*/
function $exists(a, b) {
return (b === false || b === 0) && a === undefined || (b === true || b === 1) && a !== undefined;
}
/**
* Matches arrays that contain all elements specified in the query.
*
* @param a
* @param b
* @returns boolean
*/
function $all(a, b) {
var matched = false;
if (isArray(a) && isArray(b)) {
for (var i = 0, len = b.length; i < len; i++) {
if (isObject(b[i]) && inArray(keys(b[i]), '$elemMatch')) {
matched = matched || $elemMatch(a, b[i].$elemMatch);
} else {
// order of arguments matter
return intersection(b, a).length === len;
}
}
}
return matched;
}
/**
* Selects documents if the array field is a specified size.
*
* @param a
* @param b
* @returns {*|boolean}
*/
function $size$1(a, b) {
return isArray(a) && isNumber(b) && a.length === b;
}
/**
* Selects documents if element in the array field matches all the specified $elemMatch condition.
*
* @param a {Array} element to match against
* @param b {Object} subquery
*/
function $elemMatch(a, b) {
if (isArray(a) && !isEmpty(a)) {
var format = function format(x) {
return x;
};
var criteria = b; // If we find an operator in the subquery, we fake a field to point to it.
// This is an attempt to ensure that it a valid criteria.
if (keys(b).every(isOperator)) {
criteria = {
temp: b
};
format = function format(x) {
return {
temp: x
};
};
}
var query = new Query(criteria);
for (var i = 0, len = a.length; i < len; i++) {
if (query.test(format(a[i]))) {
return true;
}
}
}
return false;
}
/**
* Selects documents if a field is of the specified type.
*
* @param a
* @param b
* @returns {boolean}
*/
function $type(a, b) {
switch (b) {
case 1:
case 'double':
return isNumber(a) && (a + '').indexOf('.') !== -1;
case 2:
case T_STRING:
return isString(a);
case 3:
case T_OBJECT:
return isObject(a);
case 4:
case T_ARRAY:
return isArray(a);
case 6:
case T_UNDEFINED:
return isNil(a);
case 8:
case T_BOOL:
return isBoolean(a);
case 9:
case T_DATE:
return isDate(a);
case 10:
case T_NULL:
return isNull(a);
case 11:
case T_REGEX:
return isRegExp(a);
case 16:
case 'int':
return isNumber(a) && a <= 2147483647 && (a + '').indexOf('.') === -1;
case 18:
case 'long':
return isNumber(a) && a > 2147483647 && a <= 9223372036854775807 && (a + '').indexOf('.') === -1;
case 19:
case 'decimal':
return isNumber(a);
default:
return false;
}
}
function compare$1(a, b, f) {
return ensureArray(a).some(function (x) {
return getType(x) === getType(b) && f(x, b);
});
}
function createComparison(f) {
return function (obj, expr) {
var args = computeValue(obj, expr);
return f(args[0], args[1]);
};
}
var $eq$1 = createComparison($eq);
var $ne$1 = createComparison($ne);
var $gt$1 = createComparison($gt);
var $lt$1 = createComparison($lt);
var $gte$1 = createComparison($gte);
var $lte$1 = createComparison($lte);
var $nin$1 = createComparison($nin);
/**
* Compares two values and returns the result of the comparison as an integer.
*
* @param obj
* @param expr
* @returns {number}
*/
function $cmp(obj, expr) {
var args = computeValue(obj, expr);
if (args[0] > args[1]) return 1;
if (args[0] < args[1]) return -1;
return 0;
}
/**
* Conditional operators
*/
/**
* A ternary operator that evaluates one expression,
* and depending on the result returns the value of one following expressions.
*
* @param obj
* @param expr
*/
function $cond(obj, expr) {
var ifExpr, thenExpr, elseExpr;
var errorMsg = '$cond: invalid arguments';
if (isArray(expr)) {
assert(expr.length === 3, errorMsg);
ifExpr = expr[0];
thenExpr = expr[1];
elseExpr = expr[2];
} else {
assert(isObject(expr), errorMsg);
ifExpr = expr['if'];
thenExpr = expr['then'];
elseExpr = expr['else'];
}
var condition = computeValue(obj, ifExpr);
return condition ? computeValue(obj, thenExpr) : computeValue(obj, elseExpr);
}
/**
* An operator that evaluates a series of case expressions. When it finds an expression which
* evaluates to true, it returns the resulting expression for that case. If none of the cases
* evaluate to true, it returns the default expression.
*
* @param obj
* @param expr
*/
function $switch(obj, expr) {
var errorMsg = 'Invalid arguments for $switch operator';
assert(expr.branches, errorMsg);
var validBranch = expr.branches.find(function (branch) {
assert(branch['case'] && branch['then'], errorMsg);
return computeValue(obj, branch['case']);
});
if (validBranch) {
return computeValue(obj, validBranch.then);
} else {
assert(expr['default'], errorMsg);
return computeValue(obj, expr["default"]);
}
}
/**
* Evaluates an expression and returns the first expression if it evaluates to a non-null value.
* Otherwise, $ifNull returns the second expression's value.
*
* @param obj
* @param expr
* @returns {*}
*/
function $ifNull(obj, expr) {
assert(isArray(expr) && expr.length === 2, '$ifNull expression must resolve to array(2)');
var args = computeValue(obj, expr);
return isNil(args[0]) ? args[1] : args[0];
}
/**
* Returns the day of the year for a date as a number between 1 and 366 (leap year).
* @param obj
* @param expr
*/
function $dayOfYear(obj, expr) {
var d = computeValue(obj, expr);
var start = new Date(d.getFullYear(), 0, 0);
var diff = d - start;
var oneDay = 1000 * 60 * 60 * 24;
return Math.round(diff / oneDay);
}
/**
* Returns the day of the month for a date as a number between 1 and 31.
* @param obj
* @param expr
*/
function $dayOfMonth(obj, expr) {
var d = computeValue(obj, expr);
return d.getDate();
}
/**
* Returns the day of the week for a date as a number between 1 (Sunday) and 7 (Saturday).
* @param obj
* @param expr
*/
function $dayOfWeek(obj, expr) {
var d = computeValue(obj, expr);
return d.getDay() + 1;
}
/**
* Returns the year for a date as a number (e.g. 2014).
* @param obj
* @param expr
*/
function $year(obj, expr) {
var d = computeValue(obj, expr);
return d.getFullYear();
}
/**
* Returns the month for a date as a number between 1 (January) and 12 (December).
* @param obj
* @param expr
*/
function $month(obj, expr) {
var d = computeValue(obj, expr);
return d.getMonth() + 1;
}
/**
* Returns the week number for a date as a number between 0
* (the partial week that precedes the first Sunday of the year) and 53 (leap year).
* @param obj
* @param expr
*/
function $week(obj, expr) {
// source: http://stackoverflow.com/a/6117889/1370481
var d = computeValue(obj, expr); // Copy date so don't modify original
d = new Date(+d);
d.setHours(0, 0, 0); // Set to nearest Thursday: current date + 4 - current day number
// Make Sunday's day number 7
d.setDate(d.getDate() + 4 - (d.getDay() || 7)); // Get first day of year
var yearStart = new Date(d.getFullYear(), 0, 1); // Calculate full weeks to nearest Thursday
return Math.floor(((d - yearStart) / 8.64e7 + 1) / 7);
}
/**
* Returns the hour for a date as a number between 0 and 23.
* @param obj
* @param expr
*/
function $hour(obj, expr) {
var d = computeValue(obj, expr);
return d.getUTCHours();
}
/**
* Returns the minute for a date as a number between 0 and 59.
* @param obj
* @param expr
*/
function $minute(obj, expr) {
var d = computeValue(obj, expr);
return d.getMinutes();
}
/**
* Returns the seconds for a date as a number between 0 and 60 (leap seconds).
* @param obj
* @param expr
*/
function $second(obj, expr) {
var d = computeValue(obj, expr);
return d.getSeconds();
}
/**
* Returns the milliseconds of a date as a number between 0 and 999.
* @param obj
* @param expr
*/
function $millisecond(obj, expr) {
var d = computeValue(obj, expr);
return d.getMilliseconds();
} // used for formatting dates in $dateToString operator
var DATE_SYM_TABLE = {
'%Y': [$year, 4],
'%m': [$month, 2],
'%d': [$dayOfMonth, 2],
'%H': [$hour, 2],
'%M': [$minute, 2],
'%S': [$second, 2],
'%L': [$millisecond, 3],
'%j': [$dayOfYear, 3],
'%w': [$dayOfWeek, 1],
'%U': [$week, 2],
'%%': '%'
};
/**
* Returns the date as a formatted string.
*
* %Y Year (4 digits, zero padded) 0000-9999
* %m Month (2 digits, zero padded) 01-12
* %d Day of Month (2 digits, zero padded) 01-31
* %H Hour (2 digits, zero padded, 24-hour clock) 00-23
* %M Minute (2 digits, zero padded) 00-59
* %S Second (2 digits, zero padded) 00-60
* %L Millisecond (3 digits, zero padded) 000-999
* %j Day of year (3 digits, zero padded) 001-366
* %w Day of week (1-Sunday, 7-Saturday) 1-7
* %U Week of year (2 digits, zero padded) 00-53
* %% Percent Character as a Literal %
*
* @param obj current object
* @param expr operator expression
*/
function $dateToString(obj, expr) {
var fmt = expr['format'];
var date = computeValue(obj, expr['date']);
var matches = fmt.match(/(%%|%Y|%m|%d|%H|%M|%S|%L|%j|%w|%U)/g);
for (var i = 0, len = matches.length; i < len; i++) {
var hdlr = DATE_SYM_TABLE[matches[i]];
var value = hdlr;
if (isArray(hdlr)) {
// reuse date operators
var fn = hdlr[0];
var pad = hdlr[1];
value = padDigits(fn(obj, date), pad);
} // replace the match with resolved value
fmt = fmt.replace(matches[i], value);
}
return fmt;
}
function padDigits(number, digits) {
return new Array(Math.max(digits - String(number).length + 1, 0)).join('0') + number;
}
/**
* Return a value without parsing.
* @param obj
* @param expr
*/
function $literal(obj, expr) {
return expr;
}
/**
* Returns true if two sets have the same elements.
* @param obj
* @param expr
*/
function $setEquals(obj, expr) {
var args = computeValue(obj, expr);
var xs = unique(args[0]);
var ys = unique(args[1]);
return xs.length === ys.length && xs.length === intersection(xs, ys).length;
}
/**
* Returns the common elements of the input sets.
* @param obj
* @param expr
*/
function $setIntersection(obj, expr) {
var args = computeValue(obj, expr);
return intersection(args[0], args[1]);
}
/**
* Returns elements of a set that do not appear in a second set.
* @param obj
* @param expr
*/
function $setDifference(obj, expr) {
var args = computeValue(obj, expr);
return args[0].filter(notInArray.bind(null, args[1]));
}
/**
* Returns a set that holds all elements of the input sets.
* @param obj
* @param expr
*/
function $setUnion(obj, expr) {
var args = computeValue(obj, expr);
return union(args[0], args[1]);
}
/**
* Returns true if all elements of a set appear in a second set.
* @param obj
* @param expr
*/
function $setIsSubset(obj, expr) {
var args = computeValue(obj, expr);
return intersection(args[0], args[1]).length === args[0].length;
}
/**
* Returns true if any elements of a set evaluate to true, and false otherwise.
* @param obj
* @param expr
*/
function $anyElementTrue(obj, expr) {
// mongodb nests the array expression in another
var args = computeValue(obj, expr)[0];
return args.some(truthy);
}
/**
* Returns true if all elements of a set evaluate to true, and false otherwise.
* @param obj
* @param expr
*/
function $allElementsTrue(obj, expr) {
// mongodb nests the array expression in another
var args = computeValue(obj, expr)[0];
return args.every(truthy);
}
/**
* Concatenates two strings.
*
* @param obj
* @param expr
* @returns {string|*}
*/
function $concat(obj, expr) {
var args = computeValue(obj, expr); // does not allow concatenation with nulls
if ([null, undefined].some(inArray.bind(null, args))) return null;
return args.join('');
}
/**
* Searches a string for an occurrence of a substring and returns the UTF-8 code point index of the first occurence.
* If the substring is not found, returns -1.
*
* @param {Object} obj
* @param {*} expr
* @return {*}
*/
function $indexOfBytes(obj, expr) {
var arr = computeValue(obj, expr);
var errorMsg = '$indexOfBytes expression resolves to invalid an argument';
if (isNil(arr[0])) return null;
assert(isString(arr[0]) && isString(arr[1]), errorMsg);
var str = arr[0];
var searchStr = arr[1];
var start = arr[2];
var end = arr[3];
var valid = isNil(start) || isNumber(start) && start >= 0 && Math.round(start) === start;
valid = valid && (isNil(end) || isNumber(end) && end >= 0 && Math.round(end) === end);
assert(valid, errorMsg);
start = start || 0;
end = end || str.length;
if (start > end) return -1;
var index = str.substring(start, end).indexOf(searchStr);
return index > -1 ? index + start : index;
}
/**
* Splits a string into substrings based on a delimiter.
* If the delimiter is not found within the string, returns an array containing the original string.
*
* @param {Object} obj
* @param {Array} expr
* @return {Array} Returns an array of substrings.
*/
function $split(obj, expr) {
var args = computeValue(obj, expr);
if (isNil(args[0])) return null;
assert(args.every(isString), '$split expression must result to array(2) of strings');
return args[0].split(args[1]);
}
/**
* Returns the number of UTF-8 encoded bytes in the specified string.
*
* @param {Object} obj
* @param {String} expr
* @return {Number}
*/
function $strLenBytes(obj, expr) {
return ~-encodeURI(computeValue(obj, expr)).split(/%..|./).length;
}
/**
* Returns the number of UTF-8 code points in the specified string.
*
* @param {Object} obj
* @param {String} expr
* @return {Number}
*/
function $strLenCP(obj, expr) {
return computeValue(obj, expr).length;
}
/**
* Compares two strings and returns an integer that reflects the comparison.
*
* @param obj
* @param expr
* @returns {number}
*/
function $strcasecmp(obj, expr) {
var args = computeValue(obj, expr);
var a = args[0];
var b = args[1];
if (isEqual(a, b) || args.every(isNil)) return 0;
assert(args.every(isString), '$strcasecmp must resolve to array(2) of strings');
a = a.toUpperCase();
b = b.toUpperCase();
return a > b && 1 || a < b && -1 || 0;
}
/**
* Returns a substring of a string, starting at a specified index position and including the specified number of characters.
* The index is zero-based.
*
* @param obj
* @param expr
* @returns {string}
*/
function $substrBytes(obj, expr) {
var args = computeValue(obj, expr);
var s = args[0];
var index = args[1];
var count = args[2];
assert(isString(s) && isNumber(index) && index >= 0 && isNumber(count) && count >= 0, '$substrBytes: invalid arguments');
var buf = utf8Encode(s);
var validIndex = [];
var acc = 0;
for (var i = 0; i < buf.length; i++) {
validIndex.push(acc);
acc += buf[i].length;
}
var begin = validIndex.indexOf(index);
var end = validIndex.indexOf(index + count);
assert(begin > -1 && end > -1, '$substrBytes: invalid range, start or end index is a UTF-8 continuation byte.');
return s.substring(begin, end);
}
/**
* Returns a substring of a string, starting at a specified index position and including the specified number of characters.
* The index is zero-based.
*
* @param obj
* @param expr
* @returns {string}
*/
function $substr(obj, expr) {
var args = computeValue(obj, expr);
var s = args[0];
var index = args[1];
var count = args[2];
if (isString(s)) {
if (index < 0) {
return '';
} else if (count < 0) {
return s.substr(index);
} else {
return s.substr(index, count);
}
}
return '';
}
function $substrCP(obj, expr) {
return $substr(obj, expr);
}
/**
* Converts a string to lowercase.
*
* @param obj
* @param expr
* @returns {string}
*/
function $toLower(obj, expr) {
var value = computeValue(obj, expr);
return isEmpty(value) ? '' : value.toLowerCase();
}
/**
* Converts a string to uppercase.
*
* @param obj
* @param expr
* @returns {string}
*/
function $toUpper(obj, expr) {
var value = computeValue(obj, expr);
return isEmpty(value) ? '' : value.toUpperCase();
}
var UTF8_MASK = [0xC0, 0xE0, 0xF0]; // encodes a unicode code point to a utf8 byte sequence
// https://encoding.spec.whatwg.org/#utf-8
function toUtf8(n) {
if (n < 0x80) return [n];
var count = n < 0x0800 && 1 || n < 0x10000 && 2 || 3;
var offset = UTF8_MASK[count - 1];
var utf8 = [(n >> 6 * count) + offset];
while (count > 0) {
utf8.push(0x80 | n >> 6 * --count & 0x3F);
}
return utf8;
}
function utf8Encode(s) {
var buf = [];
for (var i = 0, len = s.length; i < len; i++) {
buf.push(toUtf8(s.codePointAt(i)));
}
return buf;
}
/**
* Aggregation framework variable operators
*/
/**
* Defines variables for use within the scope of a sub-expression and returns the result of the sub-expression.
*
* @param obj
* @param expr
* @returns {*}
*/
function $let(obj, expr) {
var varsExpr = expr['vars'];
var inExpr = expr['in']; // resolve vars
var varsKeys = keys(varsExpr);
each(varsKeys, function (key) {
var val = computeValue(obj, varsExpr[key]);
var tempKey = '$' + key;
obj[tempKey] = val;
});
return computeValue(obj, inExpr);
}
var expressionOperators = /*#__PURE__*/Object.freeze({
__proto__: null,
$abs: $abs,
$add: $add,
$ceil: $ceil,
$divide: $divide,
$exp: $exp,
$floor: $floor,
$ln: $ln,
$log: $log,
$log10: $log10,
$mod: $mod,
$multiply: $multiply,
$pow: $pow,
$round: $round,
$sqrt: $sqrt,
$subtract: $subtract,
$trunc: $trunc,
$arrayElemAt: $arrayElemAt,
$arrayToObject: $arrayToObject,
$concatArrays: $concatArrays,
$filter: $filter,
$in: $in,
$indexOfArray: $indexOfArray,
$isArray: $isArray,
$map: $map,
$objectToArray: $objectToArray,
$range: $range,
$reduce: $reduce,
$reverseArray: $reverseArray,
$size: $size,
$slice: $slice,
$zip: $zip,
$mergeObjects: $mergeObjects,
$and: $and,
$or: $or,
$not: $not,
$eq: $eq$1,
$ne: $ne$1,
$gt: $gt$1,
$lt: $lt$1,
$gte: $gte$1,
$lte: $lte$1,
$nin: $nin$1,
$cmp: $cmp,
$cond: $cond,
$switch: $switch,
$ifNull: $ifNull,
$dayOfYear: $dayOfYear,
$dayOfMonth: $dayOfMonth,
$dayOfWeek: $dayOfWeek,
$year: $year,
$month: $month,
$week: $week,
$hour: $hour,
$minute: $minute,
$second: $second,
$millisecond: $millisecond,
$dateToString: $dateToString,
$literal: $literal,
$setEquals: $setEquals,
$setIntersection: $setIntersection,
$setDifference: $setDifference,
$setUnion: $setUnion,
$setIsSubset: $setIsSubset,
$anyElementTrue: $anyElementTrue,
$allElementsTrue: $allElementsTrue,
$concat: $concat,
$indexOfBytes: $indexOfBytes,
$split: $split,
$strLenBytes: $strLenBytes,
$strLenCP: $strLenCP,
$strcasecmp: $strcasecmp,
$substrBytes: $substrBytes,
$substr: $substr,
$substrCP: $substrCP,
$toLower: $toLower,
$toUpper: $toUpper,
$let: $let
});
/**
* Returns an array of all values for the selected field among for each document in that group.
*
* @param collection
* @param expr
* @returns {Array|*}
*/
function $push(collection, expr) {
if (isNil(expr)) return collection;
return collection.map(function (obj) {
return computeValue(obj, expr);
});
}
/**
* Returns an array of all the unique values for the selected field among for each document in that group.
*
* @param collection
* @param expr
* @returns {*}
*/
function $addToSet(collection, expr) {
return unique($push(collection, expr));
}
/**
* Returns an average of all the values in a group.
*
* @param collection
* @param expr
* @returns {number}
*/
function $avg(collection, expr) {
var data = $push(collection, expr).filter(isNumber);
var sum = reduce(data, function (acc, n) {
return acc + n;
}, 0);
return sum / (data.length || 1);
}
/**
* Returns the first value in a group.
*
* @param collection
* @param expr
* @returns {*}
*/
function $first(collection, expr) {
return collection.length > 0 ? computeValue(collection[0], expr) : undefined;
}
/**
* Returns the last value in a group.
*
* @param collection
* @param expr
* @returns {*}
*/
function $last(collection, expr) {
return collection.length > 0 ? computeValue(collection[collection.length - 1], expr) : undefined;
}
/**
* Returns the highest value in a group.
*
* @param collection
* @param expr
* @returns {*}
*/
function $max(collection, expr) {
return reduce($push(collection, expr), function (acc, n) {
return isNil(acc) || n > acc ? n : acc;
}, undefined);
}
/**
* Combines multiple documents into a single document.
*
* @param collection
* @param expr
* @returns {Array|*}
*/
function $mergeObjects$1(collection, expr) {
return reduce(collection, function (memo, o) {
return Object.assign(memo, computeValue(o, expr));
}, {});
}
/**
* Returns the lowest value in a group.
*
* @param collection
* @param expr
* @returns {*}
*/
function $min(collection, expr) {
return reduce($push(collection, expr), function (acc, n) {
return isNil(acc) || n < acc ? n : acc;
}, undefined);
}
/**
* Returns the population standard deviation of the input values.
*
* @param {Array} collection
* @param {Object} expr
* @return {Number}
*/
function $stdDevPop(collection, expr) {
return stddev($push(collection, expr).filter(isNumber), false);
}
/**
* Returns the sample standard deviation of the input values.
* @param {Array} collection
* @param {Object} expr
* @return {Number|null}
*/
function $stdDevSamp(collection, expr) {
return stddev($push(collection, expr).filter(isNumber), true);
}
/**
* Returns the sum of all the values in a group.
*
* @param collection
* @param expr
* @returns {*}
*/
function $sum(collection, expr) {
if (!isArray(collection)) return 0; // take a short cut if expr is number literal
if (isNumber(expr)) return collection.length * expr;
return reduce($push(collection, expr).filter(isNumber), function (acc, n) {
return acc + n;
}, 0);
}
/**
* Group stage Accumulator Operators. https://docs.mongodb.com/manual/reference/operator/aggregation-
*/
var groupOperators = /*#__PURE__*/Object.freeze({
__proto__: null,
$addToSet: $addToSet,
$avg: $avg,
$first: $first,
$last: $last,
$max: $max,
$mergeObjects: $mergeObjects$1,
$min: $min,
$push: $push,
$stdDevPop: $stdDevPop,
$stdDevSamp: $stdDevSamp,
$sum: $sum
});
/**
* Adds new fields to documents.
* Outputs documents that contain all existing fields from the input documents and newly added fields.
*
* @param {Array} collection
* @param {*} expr
* @param {Object} opt Pipeline options
*/
function $addFields(collection, expr, opt) {
var newFields = keys(expr);
if (newFields.length === 0) return collection;
return collection.map(function (obj) {
var newObj = cloneDeep(obj);
each(newFields, function (field) {
var newValue = computeValue(obj, expr[field]);
setValue(newObj, field, newValue);
});
return newObj;
});
}
/**
* Alias for $addFields.
*/
var $set = $addFields;
/**
* Categorizes incoming documents into groups, called buckets, based on a specified expression and bucket boundaries.
* https://docs.mongodb.com/manual/reference/operator/aggregation/bucket/
*
* @param {*} collection
* @param {*} expr
* @param {Object} opt Pipeline options
*/
function $bucket(collection, expr, opt) {
var boundaries = expr.boundaries;
var defaultKey = expr['default'];
var lower = boundaries[0]; // inclusive
var upper = boundaries[boundaries.length - 1]; // exclusive
var outputExpr = expr.output || {
'count': {
'$sum': 1
}
};
assert(boundaries.length > 2, "$bucket 'boundaries' expression must have at least 3 elements");
var boundType = getType(lower);
for (var i = 0, len = boundaries.length - 1; i < len; i++) {
assert(boundType === getType(boundaries[i + 1]), "$bucket 'boundaries' must all be of the same type");
assert(boundaries[i] < boundaries[i + 1], "$bucket 'boundaries' must be sorted in ascending order");
}
!isNil(defaultKey) && getType(expr["default"]) === getType(lower) && assert(lower > expr["default"] || upper < expr["default"], "$bucket 'default' expression must be out of boundaries range");
var grouped = {};
each(boundaries, function (k) {
return grouped[k] = [];
}); // add default key if provided
if (!isNil(defaultKey)) grouped[defaultKey] = [];
var iterator = false;
return Lazy(function () {
if (!iterator) {
collection.each(function (obj) {
var key = computeValue(obj, expr.groupBy);
if (isNil(key) || key < lower || key >= upper) {
assert(!isNil(defaultKey), '$bucket require a default for out of range values');
grouped[defaultKey].push(obj);
} else {
assert(key >= lower && key < upper, "$bucket 'groupBy' expression must resolve to a value in range of boundaries");
var index = findInsertIndex(boundaries, key);
var boundKey = boundaries[Math.max(0, index - 1)];
grouped[boundKey].push(obj);
}
}); // upper bound is exclusive so we remove it
boundaries.pop();
if (!isNil(defaultKey)) boundaries.push(defaultKey);
iterator = Lazy(boundaries).map(function (key) {
var acc = accumulate(grouped[key], null, outputExpr);
return Object.assign(acc, {
'_id': key
});
});
}
return iterator.next();
});
}
/**
* Categorizes incoming documents into a specific number of groups, called buckets,
* based on a specified expression. Bucket boundaries are automatically determined
* in an attempt to evenly distribute the documents into the specified number of buckets.
* https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/
*
* @param {*} collection
* @param {*} expr
* @param {*} opt Pipeline options
*/
function $bucketAuto(collection, expr, opt) {
var outputExpr = expr.output || {
'count': {
'$sum': 1
}
};
var groupByExpr = expr.groupBy;
var bucketCount = expr.buckets;
assert(bucketCount > 0, "The $bucketAuto 'buckets' field must be greater than 0, but found: " + bucketCount);
return collection.transform(function (coll) {
var approxBucketSize = Math.max(1, Math.round(coll.length / bucketCount));
var computeValueOptimized = memoize(computeValue);
var grouped = {};
var remaining = [];
var sorted = sortBy(coll, function (o) {
var key = computeValueOptimized(o, groupByExpr);
if (isNil(key)) {
remaining.push(o);
} else {
grouped[key] || (grouped[key] = []);
grouped[key].push(o);
}
return key;
});
var ID_KEY = idKey();
var result = [];
var index = 0; // counter for sorted collection
for (var i = 0, len = sorted.length; i < bucketCount && index < len; i++) {
var boundaries = {};
var bucketItems = [];
for (var j = 0; j < approxBucketSize && index < len; j++) {
var key = computeValueOptimized(sorted[index], groupByExpr);
if (isNil(key)) key = null; // populate current bucket with all values for current key
into(bucketItems, isNil(key) ? remaining : grouped[key]); // increase sort index by number of items added
index += isNil(key) ? remaining.length : grouped[key].length; // set the min key boundary if not already present
if (!has(boundaries, 'min')) boundaries.min = key;
if (result.length > 0) {
var lastBucket = result[result.length - 1];
lastBucket[ID_KEY].max = boundaries.min;
}
} // if is last bucket add remaining items
if (i == bucketCount - 1) {
into(bucketItems, sorted.slice(index));
}
result.push(Object.assign(accumulate(bucketItems, null, outputExpr), {
'_id': boundaries
}));
}
if (result.length > 0) {
result[result.length - 1][ID_KEY].max = computeValueOptimized(sorted[sorted.length - 1], groupByExpr);
}
return result;
});
}
/**
* Returns a document that contains a count of the number of documents input to the stage.
*
* @param {Array} collection
* @param {String} expr
* @param {Object} opt Pipeline options
* @return {Object}
*/
function $count(collection, expr, opt) {
assert(isString(expr) && expr.trim() !== '' && expr.indexOf('.') === -1 && expr.trim()[0] !== '$', 'Invalid expression value for $count');
return Lazy(function () {
var o = {};
o[expr] = collection.size();
return {
value: o,
done: false
};
}).first();
}
/**
* Processes multiple aggregation pipelines within a single stage on the same set of input documents.
* Enables the creation of multi-faceted aggregations capable of characterizing data across multiple dimensions, or facets, in a single stage.
*/
function $facet(collection, expr, opt) {
return collection.transform(function (array) {
return [objectMap(expr, function (pipeline) {
return aggregate(array, pipeline);
})];
});
}
/**
* Groups documents together for the purpose of calculating aggregate values based on a collection of documents.
*
* @param collection
* @param expr
* @param opt Pipeline options
* @returns {Array}
*/
function $group(collection, expr, opt) {
// lookup key for grouping
var ID_KEY = idKey();
var id = expr[ID_KEY];
return collection.transform(function (coll) {
var partitions = groupBy(coll, function (obj) {
return computeValue(obj, id, id);
}); // remove the group key
expr = clone(expr);
delete expr[ID_KEY];
var i = -1;
var size = partitions.keys.length;
return function () {
if (++i === size) return {
done: true
};
var value = partitions.keys[i];
var obj = {}; // exclude undefined key value
if (value !== undefined) {
obj[ID_KEY] = value;
} // compute remaining keys in expression
each(expr, function (val, key) {
obj[key] = accumulate(partitions.groups[i], key, val);
});
return {
value: obj,
done: false
};
};
});
}
/**
* Restricts the number of documents in an aggregation pipeline.
*
* @param collection
* @param value
* @param opt
* @returns {Object|*}
*/
function $limit(collection, value, opt) {
return collection.take(value);
}
/**
* Performs a left outer join to another collection in the same database to filter in documents from the “joined” collection for processing.
*
* @param collection
* @param expr
* @param opt
*/
function $lookup(collection, expr, opt) {
var joinColl = expr.from;
var localField = expr.localField;
var foreignField = expr.foreignField;
var asField = expr.as;
assert(isArray(joinColl) && isString(foreignField) && isString(localField) && isString(asField), '$lookup: invalid argument');
var hash = {};
each(joinColl, function (obj) {
var k = hashCode(resolve(obj, foreignField));
hash[k] = hash[k] || [];
hash[k].push(obj);
});
return collection.map(function (obj) {
var k = hashCode(resolve(obj, localField));
var newObj = clone(obj);
newObj[asField] = hash[k] || [];
return newObj;
});
}
/**
* Filters the document stream, and only allows matching documents to pass into the next pipeline stage.
* $match uses standard MongoDB queries.
*
* @param collection
* @param expr
* @param opt
* @returns {Array|*}
*/
function $match(collection, expr, opt) {
var q = new Query(expr);
return collection.filter(function (o) {
return q.test(o);
});
}
/**
* Takes the documents returned by the aggregation pipeline and writes them to a specified collection.
*
* Unlike the $out operator in MongoDB, this operator can appear in any position in the pipeline and is
* useful for collecting intermediate results of an aggregation operation.
*
* @param collection
* @param expr
* @param opt
* @returns {*}
*/
function $out(collection, expr, opt) {
assert(isArray(expr), '$out expression must be an array');
return collection.map(function (o) {
expr.push(o);
return o; // passthrough
});
}
/**
* Reshapes a document stream.
* $project can rename, add, or remove fields as well as create computed values and sub-documents.
*
* @param collection
* @param expr
* @param opt
* @returns {Array}
*/
function $project(collection, expr, opt) {
if (isEmpty(expr)) return collection; // result collection
var expressionKeys = keys(expr);
var idOnlyExcludedExpression = false;
var ID_KEY = idKey(); // validate inclusion and exclusion
validateExpression(expr);
if (inArray(expressionKeys, ID_KEY)) {
var id = expr[ID_KEY];
if (id === 0 || id === false) {
expressionKeys = expressionKeys.filter(notInArray.bind(null, [ID_KEY]));
assert(notInArray(expressionKeys, ID_KEY), 'Must not contain collections id key');
idOnlyExcludedExpression = isEmpty(expressionKeys);
}
} else {
// if not specified the add the ID field
expressionKeys.push(ID_KEY);
}
return collection.map(function (obj) {
return processObject(obj, expr, expressionKeys, idOnlyExcludedExpression);
});
}
/**
* Process the expression value for $project operators
*
* @param {Object} obj The object to use as context
* @param {Object} expr The experssion object of $project operator
* @param {Array} expressionKeys The key in the 'expr' object
* @param {Boolean} idOnlyExcludedExpression Boolean value indicating whether only the ID key is excluded
*/
function processObject(obj, expr, expressionKeys, idOnlyExcludedExpression) {
var ID_KEY = idKey();
var newObj = {};
var foundSlice = false;
var foundExclusion = false;
var dropKeys = [];
if (idOnlyExcludedExpression) {
dropKeys.push(ID_KEY);
}
expressionKeys.forEach(function (key) {
// final computed value of the key
var value; // expression to associate with key
var subExpr = expr[key];
if (key !== ID_KEY && inArray([0, false], subExpr)) {
foundExclusion = true;
}
if (key === ID_KEY && isEmpty(subExpr)) {
// tiny optimization here to skip over id
value = obj[key];
} else if (isString(subExpr)) {
value = computeValue(obj, subExpr, key);
} else if (inArray([1, true], subExpr)) ; else if (isArray(subExpr)) {
value = subExpr.map(function (v) {
var r = computeValue(obj, v);
if (isNil(r)) return null;
return r;
});
} else if (isObject(subExpr)) {
var subExprKeys = keys(subExpr);
var operator = subExprKeys.length > 1 ? false : subExprKeys[0];
if (inArray(ops(OP_PROJECTION), operator)) {
var projectionOperators = OPERATORS[OP_PROJECTION]; // apply the projection operator on the operator expression for the key
if (operator === '$slice') {
// $slice is handled differently for aggregation and projection operations
if (ensureArray(subExpr[operator]).every(isNumber)) {
// $slice for projection operation
value = projectionOperators[operator](obj, subExpr[operator], key);
foundSlice = true;
} else {
// $slice for aggregation operation
value = computeValue(obj, subExpr, key);
}
} else {
value = projectionOperators[operator](obj, subExpr[operator], key);
}
} else {
// compute the value for the sub expression for the key
if (has(obj, key)) {
validateExpression(subExpr);
var nestedObj = obj[key];
value = isArray(nestedObj) ? nestedObj.map(function (o) {
return processObject(o, subExpr, subExprKeys, false);
}) : processObject(nestedObj, subExpr, subExprKeys, false);
} else {
value = computeValue(obj, subExpr, key);
}
}
} else {
dropKeys.push(key);
return;
} // get value with object graph
var objPathValue = resolveObj(obj, key, {
preserveMissingValues: true
}); // add the value at the path
if (objPathValue !== undefined) {
merge(newObj, objPathValue, {
flatten: true
});
} // if computed add/or remove accordingly
if (notInArray([0, 1, false, true], subExpr)) {
if (value === undefined) {
removeValue(newObj, key);
} else {
setValue(newObj, key, value);
}
}
}); // filter out all missing values preserved to support correct merging
filterMissing(newObj); // if projection included $slice operator
// Also if exclusion fields are found or we want to exclude only the id field
// include keys that were not explicitly excluded
if (foundSlice || foundExclusion || idOnlyExcludedExpression) {
newObj = Object.assign({}, obj, newObj);
if (dropKeys.length > 0) {
newObj = cloneDeep(newObj);
each(dropKeys, function (k) {
return removeValue(newObj, k);
});
}
}
return newObj;
}
/**
* Validate inclusion and exclusion values in expression
*
* @param {Object} expr The expression given for the projection
*/
function validateExpression(expr) {
var ID_KEY = idKey();
var check = [false, false];
each(expr, function (v, k) {
if (k === ID_KEY) return;
if (v === 0 || v === false) {
check[0] = true;
} else if (v === 1 || v === true) {
check[1] = true;
}
assert(!(check[0] && check[1]), 'Projection cannot have a mix of inclusion and exclusion.');
});
}
/**
* Restricts the contents of the documents based on information stored in the documents themselves.
*
* https://docs.mongodb.com/manual/reference/operator/aggregation/redact/
*/
function $redact(collection, expr, opt) {
return collection.map(function (obj) {
return redactObj(cloneDeep(obj), expr);
});
}
/**
* Replaces a document with the specified embedded document or new one.
* The replacement document can be any valid expression that resolves to a document.
*
* https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/
*
* @param {Array} collection
* @param {Object} expr
* @param {Object} opt
* @return {*}
*/
function $replaceRoot(collection, expr, opt) {
return collection.map(function (obj) {
obj = computeValue(obj, expr.newRoot);
assert(isObject(obj), '$replaceRoot expression must return an object');
return obj;
});
}
/**
* Randomly selects the specified number of documents from its input.
* https://docs.mongodb.com/manual/reference/operator/aggregation/sample/
*
* @param {Array} collection
* @param {Object} expr
* @param {Object} opt
* @return {*}
*/
function $sample(collection, expr, opt) {
var size = expr.size;
assert(isNumber(size), '$sample size must be a positive integer');
return collection.transform(function (xs) {
var len = xs.length;
var i = -1;
return function () {
if (++i === size) return {
done: true
};
var n = Math.floor(Math.random() * len);
return {
value: xs[n],
done: false
};
};
});
}
/**
* Skips over a specified number of documents from the pipeline and returns the rest.
*
* @param collection
* @param value
* @param {Object} opt
* @returns {*}
*/
function $skip(collection, value, opt) {
return collection.drop(value);
}
/**
* Takes all input documents and returns them in a stream of sorted documents.
*
* @param collection
* @param sortKeys
* @param {Object} opt
* @returns {*}
*/
function $sort(collection, sortKeys, opt) {
if (isEmpty(sortKeys) || !isObject(sortKeys)) return collection;
opt = opt || {};
var cmp = compare;
var collationSpec = opt['collation']; // use collation comparator if provided
if (isObject(collationSpec) && isString(collationSpec.locale)) {
cmp = collationComparator(collationSpec);
}
return collection.transform(function (coll) {
var modifiers = keys(sortKeys);
each(modifiers.reverse(), function (key) {
var grouped = groupBy(coll, function (obj) {
return resolve(obj, key);
});
var sortedIndex = {};
var indexKeys = sortBy(grouped.keys, function (k, i) {
sortedIndex[k] = i;
return k;
}, cmp);
if (sortKeys[key] === -1) indexKeys.reverse();
coll = [];
each(indexKeys, function (k) {
return into(coll, grouped.groups[sortedIndex[k]]);
});
});
return coll;
});
} // MongoDB collation strength to JS localeCompare sensitivity mapping.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
var COLLATION_STRENGTH = {
// Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A.
1: 'base',
// Only strings that differ in base letters or accents and other diacritic marks compare as unequal.
// Examples: a ≠ b, a ≠ á, a = A.
2: 'accent',
// Strings that differ in base letters, accents and other diacritic marks, or case compare as unequal.
// Other differences may also be taken into consideration. Examples: a ≠ b, a ≠ á, a ≠ A
3: 'variant' // case - Only strings that differ in base letters or case compare as unequal. Examples: a ≠ b, a = á, a ≠ A.
};
/**
* Creates a comparator function for the given collation spec. See https://docs.mongodb.com/manual/reference/collation/
*
* @param spec {Object} The MongoDB collation spec.
* {
* locale: <string>,
* caseLevel: <boolean>,
* caseFirst: <string>,
* strength: <int>,
* numericOrdering: <boolean>,
* alternate: <string>,
* maxVariable: <string>, // unsupported
* backwards: <boolean> // unsupported
* }
*/
function collationComparator(spec) {
var localeOpt = {
sensitivity: COLLATION_STRENGTH[spec.strength || 3],
caseFirst: spec.caseFirst === 'off' ? 'false' : spec.caseFirst || 'false',
numeric: spec.numericOrdering || false,
ignorePunctuation: spec.alternate === 'shifted'
}; // when caseLevel is true for strength 1:base and 2:accent, bump sensitivity to the nearest that supports case comparison
if ((spec.caseLevel || false) === true) {
if (localeOpt.sensitivity === 'base') localeOpt.sensitivity = 'case';
if (localeOpt.sensitivity === 'accent') localeOpt.sensitivity = 'variant';
}
var collator = new Intl.Collator(spec.locale, localeOpt);
return function (a, b) {
// non strings
if (!isString(a) || !isString(b)) return compare(a, b); // only for strings
var i = collator.compare(a, b);
if (i < 0) return -1;
if (i > 0) return 1;
return 0;
};
}
/**
* Groups incoming documents based on the value of a specified expression,
* then computes the count of documents in each distinct group.
*
* https://docs.mongodb.com/manual/reference/operator/aggregation/sortByCount/
*
* @param {Array} collection
* @param {Object} expr
* @param {Object} opt
* @return {*}
*/
function $sortByCount(collection, expr, opt) {
var newExpr = {
count: {
$sum: 1
}
};
newExpr[idKey()] = expr;
return $sort($group(collection, newExpr), {
count: -1
}, opt);
}
/**
* Takes an array of documents and returns them as a stream of documents.
*
* @param collection
* @param expr
* @param {Object} opt
* @returns {Array}
*/
function $unwind(collection, expr, opt) {
if (isString(expr)) {
expr = {
path: expr
};
}
var field = expr.path.substr(1);
var includeArrayIndex = expr.includeArrayIndex || false;
var preserveNullAndEmptyArrays = expr.preserveNullAndEmptyArrays || false;
var format = function format(o, i) {
if (includeArrayIndex !== false) o[includeArrayIndex] = i;
return o;
};
var value;
return Lazy(function () {
var _loop = function _loop() {
// take from lazy sequence if available
if (Lazy.isIterator(value)) {
var tmp = value.next();
if (!tmp.done) return {
v: tmp
};
} // fetch next object
var obj = collection.next();
if (obj.done) return {
v: obj
}; // unwrap value
obj = obj.value; // get the value of the field to unwind
value = resolve(obj, field); // throw error if value is not an array???
if (isArray(value)) {
if (value.length === 0 && preserveNullAndEmptyArrays === true) {
value = null; // reset unwind value
var _tmp = cloneDeep(obj);
removeValue(_tmp, field);
return {
v: {
value: format(_tmp, null),
done: false
}
};
} else {
// construct a lazy sequence for elements per value
value = Lazy(value).map(function (item, i) {
var tmp = cloneDeep(obj);
setValue(tmp, field, item);
return format(tmp, i);
});
}
} else if (!isEmpty(value) || preserveNullAndEmptyArrays === true) {
var _tmp2 = cloneDeep(obj);
return {
v: {
value: format(_tmp2, null),
done: false
}
};
}
};
while (true) {
var _ret = _loop();
if (_typeof(_ret) === "object") return _ret.v;
}
});
}
/**
* Pipeline Aggregation Stages. https://docs.mongodb.com/manual/reference/operator/aggregation-
*/
var pipelineOperators = /*#__PURE__*/Object.freeze({
__proto__: null,
$addFields: $addFields,
$set: $set,
$bucket: $bucket,
$bucketAuto: $bucketAuto,
$count: $count,
$facet: $facet,
$group: $group,
$limit: $limit,
$lookup: $lookup,
$match: $match,
$out: $out,
$project: $project,
$redact: $redact,
$replaceRoot: $replaceRoot,
$sample: $sample,
$skip: $skip,
$sort: $sort,
$sortByCount: $sortByCount,
$unwind: $unwind
});
/**
* Projection Operators. https://docs.mongodb.com/manual/reference/operator/projection/
*/
/**
* Projects the first element in an array that matches the query condition.
*
* @param obj
* @param field
* @param expr
*/
function $(obj, expr, field) {
err('$ not implemented');
}
/**
* Projects only the first element from an array that matches the specified $elemMatch condition.
*
* @param obj
* @param field
* @param expr
* @returns {*}
*/
function $elemMatch$1(obj, expr, field) {
var arr = resolve(obj, field);
var query = new Query(expr);
assert(isArray(arr), '$elemMatch: invalid argument');
for (var i = 0; i < arr.length; i++) {
if (query.test(arr[i])) return [arr[i]];
}
return undefined;
}
/**
* Limits the number of elements projected from an array. Supports skip and limit slices.
*
* @param obj
* @param field
* @param expr
*/
function $slice$1(obj, expr, field) {
var xs = resolve(obj, field);
if (!isArray(xs)) return xs;
if (isArray(expr)) {
return slice(xs, expr[0], expr[1]);
} else {
assert(isNumber(expr), '$slice: invalid arguments for projection');
return slice(xs, expr);
}
}
var projectionOperators = /*#__PURE__*/Object.freeze({
__proto__: null,
$: $,
$elemMatch: $elemMatch$1,
$slice: $slice$1
});
// Query and Projection Operators. https://docs.mongodb.com/manual/reference/operator/query/
function createQueryOperator(pred) {
return function (selector, value) {
return function (obj) {
// value of field must be fully resolved.
var lhs = resolve(obj, selector, {
meta: true
});
lhs = unwrap(lhs.result, lhs.depth);
return pred(lhs, value);
};
};
}
var $all$1 = createQueryOperator($all);
var $elemMatch$2 = createQueryOperator($elemMatch);
var $eq$2 = createQueryOperator($eq);
var $exists$1 = createQueryOperator($exists);
var $gt$2 = createQueryOperator($gt);
var $gte$2 = createQueryOperator($gte);
var $in$2 = createQueryOperator($in$1);
var $lt$2 = createQueryOperator($lt);
var $lte$2 = createQueryOperator($lte);
var $mod$2 = createQueryOperator($mod$1);
var $ne$2 = createQueryOperator($ne);
var $nin$2 = createQueryOperator($nin);
var $regex$1 = createQueryOperator($regex);
var $size$2 = createQueryOperator($size$1);
var $type$1 = createQueryOperator($type);
/**
* Joins query clauses with a logical AND returns all documents that match the conditions of both clauses.
*
* @param selector
* @param value
* @returns {Function}
*/
function $and$1(selector, value) {
assert(isArray(value), 'Invalid expression: $and expects value to be an Array');
var queries = [];
each(value, function (expr) {
return queries.push(new Query(expr));
});
return function (obj) {
for (var i = 0; i < queries.length; i++) {
if (!queries[i].test(obj)) {
return false;
}
}
return true;
};
}
/**
* Joins query clauses with a logical OR returns all documents that match the conditions of either clause.
*
* @param selector
* @param value
* @returns {Function}
*/
function $or$1(selector, value) {
assert(isArray(value), 'Invalid expression. $or expects value to be an Array');
var queries = [];
each(value, function (expr) {
return queries.push(new Query(expr));
});
return function (obj) {
for (var i = 0; i < queries.length; i++) {
if (queries[i].test(obj)) {
return true;
}
}
return false;
};
}
/**
* Joins query clauses with a logical NOR returns all documents that fail to match both clauses.
*
* @param selector
* @param value
* @returns {Function}
*/
function $nor(selector, value) {
assert(isArray(value), 'Invalid expression. $nor expects value to be an Array');
var f = $or$1('$or', value);
return function (obj) {
return !f(obj);
};
}
/**
* Inverts the effect of a query expression and returns documents that do not match the query expression.
*
* @param selector
* @param value
* @returns {Function}
*/
function $not$1(selector, value) {
var criteria = {};
criteria[selector] = normalize(value);
var query = new Query(criteria);
return function (obj) {
return !query.test(obj);
};
}
/**
* Matches documents that satisfy a JavaScript expression.
*
* @param selector
* @param value
* @returns {Function}
*/
function $where(selector, value) {
if (!isFunction(value)) {
value = new Function('return ' + value + ';');
}
return function (obj) {
return value.call(obj) === true;
};
}
/**
* Allows the use of aggregation expressions within the query language.
*
* @param selector
* @param value
* @returns {Function}
*/
function $expr(selector, value) {
return function (obj) {
return computeValue(obj, value);
};
}
var queryOperators = /*#__PURE__*/Object.freeze({
__proto__: null,
$all: $all$1,
$elemMatch: $elemMatch$2,
$eq: $eq$2,
$exists: $exists$1,
$gt: $gt$2,
$gte: $gte$2,
$in: $in$2,
$lt: $lt$2,
$lte: $lte$2,
$mod: $mod$2,
$ne: $ne$2,
$nin: $nin$2,
$regex: $regex$1,
$size: $size$2,
$type: $type$1,
$and: $and$1,
$or: $or$1,
$nor: $nor,
$not: $not$1,
$where: $where,
$expr: $expr
});
var OPERATORS = {};
OPERATORS[OP_EXPRESSION] = {};
OPERATORS[OP_GROUP] = {};
OPERATORS[OP_PIPELINE] = {};
OPERATORS[OP_PROJECTION] = {};
OPERATORS[OP_QUERY] = {};
var SYSTEM_OPERATORS = [[OP_EXPRESSION, expressionOperators], [OP_GROUP, groupOperators], [OP_PIPELINE, pipelineOperators], [OP_PROJECTION, projectionOperators], [OP_QUERY, queryOperators]];
/**
* Enables the default operators of the system
*/
function enableSystemOperators() {
each(SYSTEM_OPERATORS, function (arr) {
var _arr = _slicedToArray(arr, 2),
cls = _arr[0],
values = _arr[1];
Object.assign(OPERATORS[cls], values);
});
}
/**
* Add new operators
*
* @param opClass the operator class to extend
* @param fn a function returning an object of new operators
*/
function addOperators(opClass, fn) {
var newOperators = fn(_internal()); // ensure correct type specified
assert(has(OPERATORS, opClass), "Invalid operator class ".concat(opClass));
var operators = OPERATORS[opClass]; // check for existing operators
each(newOperators, function (_, op) {
assert(/^\$[a-zA-Z0-9_]*$/.test(op), "Invalid operator name ".concat(op));
assert(!has(operators, op), "".concat(op, " already exists for '").concat(opClass, "' operators"));
});
var wrapped = {};
switch (opClass) {
case OP_QUERY:
each(newOperators, function (fn, op) {
fn = fn.bind(newOperators);
wrapped[op] = function (selector, value) {
return function (obj) {
// value of field must be fully resolved.
var lhs = resolve(obj, selector);
var result = fn(selector, lhs, value);
assert(isBoolean(result), "".concat(op, " must return a boolean"));
return result;
};
};
});
break;
case OP_PROJECTION:
each(newOperators, function (fn, op) {
fn = fn.bind(newOperators);
wrapped[op] = function (obj, expr, selector) {
var lhs = resolve(obj, selector);
return fn(selector, lhs, expr);
};
});
break;
default:
each(newOperators, function (fn, op) {
wrapped[op] = function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return fn.apply(newOperators, args);
};
});
} // toss the operator salad :)
Object.assign(OPERATORS[opClass], wrapped);
}
/**
* Mixin for Collection types that provide a method `toJSON() -> Array[Object]`
*/
var CollectionMixin = {
/**
* Runs a query and returns a cursor to the result
* @param criteria
* @param projection
* @returns {Cursor}
*/
query: function query(criteria, projection) {
return new Query(criteria).find(this.toJSON(), projection);
},
/**
* Runs the given aggregation operators on this collection
* @params pipeline
* @returns {Array}
*/
aggregate: function aggregate(pipeline) {
return new Aggregator(pipeline).run(this.toJSON());
}
};
enableSystemOperators();
var VERSION = '2.5.3'; // mingo!
var index = {
_internal: _internal,
Aggregator: Aggregator,
CollectionMixin: CollectionMixin,
Cursor: Cursor,
Lazy: Lazy,
OP_EXPRESSION: OP_EXPRESSION,
OP_GROUP: OP_GROUP,
OP_PIPELINE: OP_PIPELINE,
OP_PROJECTION: OP_PROJECTION,
OP_QUERY: OP_QUERY,
Query: Query,
VERSION: VERSION,
addOperators: addOperators,
aggregate: aggregate,
find: find,
remove: remove,
setup: setup
};
return index;
})));