You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
568 lines
9.7 KiB
568 lines
9.7 KiB
'use strict';
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
|
|
|
exports.default = sift;
|
|
exports.indexOf = indexOf;
|
|
exports.compare = compare;
|
|
/*
|
|
*
|
|
* Copryright 2018, Craig Condon
|
|
* Licensed under MIT
|
|
*
|
|
* Filter JavaScript objects with mongodb queries
|
|
*/
|
|
|
|
/**
|
|
*/
|
|
|
|
function isFunction(value) {
|
|
return typeof value === 'function';
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function isArray(value) {
|
|
return Object.prototype.toString.call(value) === '[object Array]';
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function comparable(value) {
|
|
if (value instanceof Date) {
|
|
return value.getTime();
|
|
} else if (isArray(value)) {
|
|
return value.map(comparable);
|
|
} else if (value && typeof value.toJSON === 'function') {
|
|
return value.toJSON();
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function get(obj, key) {
|
|
return isFunction(obj.get) ? obj.get(key) : obj[key];
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function or(validator) {
|
|
return function (a, b) {
|
|
if (!isArray(b) || !b.length) {
|
|
return validator(a, b);
|
|
}
|
|
for (var i = 0, n = b.length; i < n; i++) {
|
|
if (validator(a, get(b, i))) return true;
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function and(validator) {
|
|
return function (a, b) {
|
|
if (!isArray(b) || !b.length) {
|
|
return validator(a, b);
|
|
}
|
|
for (var i = 0, n = b.length; i < n; i++) {
|
|
if (!validator(a, get(b, i))) return false;
|
|
}
|
|
return true;
|
|
};
|
|
}
|
|
|
|
function validate(validator, b, k, o) {
|
|
return validator.v(validator.a, b, k, o);
|
|
}
|
|
|
|
var OPERATORS = {
|
|
|
|
/**
|
|
*/
|
|
|
|
$eq: or(function (a, b) {
|
|
return a(b);
|
|
}),
|
|
|
|
/**
|
|
*/
|
|
|
|
$ne: and(function (a, b) {
|
|
return !a(b);
|
|
}),
|
|
|
|
/**
|
|
*/
|
|
|
|
$gt: or(function (a, b) {
|
|
return compare(comparable(b), a) > 0;
|
|
}),
|
|
|
|
/**
|
|
*/
|
|
|
|
$gte: or(function (a, b) {
|
|
return compare(comparable(b), a) >= 0;
|
|
}),
|
|
|
|
/**
|
|
*/
|
|
|
|
$lt: or(function (a, b) {
|
|
return compare(comparable(b), a) < 0;
|
|
}),
|
|
|
|
/**
|
|
*/
|
|
|
|
$lte: or(function (a, b) {
|
|
return compare(comparable(b), a) <= 0;
|
|
}),
|
|
|
|
/**
|
|
*/
|
|
|
|
$mod: or(function (a, b) {
|
|
return b % a[0] == a[1];
|
|
}),
|
|
|
|
/**
|
|
*/
|
|
|
|
$in: function $in(a, b) {
|
|
|
|
if (b instanceof Array) {
|
|
for (var i = b.length; i--;) {
|
|
if (~a.indexOf(comparable(get(b, i)))) {
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
var comparableB = comparable(b);
|
|
if (comparableB === b && (typeof b === 'undefined' ? 'undefined' : _typeof(b)) === 'object') {
|
|
for (var i = a.length; i--;) {
|
|
if (String(a[i]) === String(b) && String(b) !== '[object Object]') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Handles documents that are undefined, whilst also
|
|
having a 'null' element in the parameters to $in.
|
|
*/
|
|
if (typeof comparableB == 'undefined') {
|
|
for (var i = a.length; i--;) {
|
|
if (a[i] == null) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Handles the case of {'field': {$in: [/regexp1/, /regexp2/, ...]}}
|
|
*/
|
|
for (var i = a.length; i--;) {
|
|
var validator = createRootValidator(get(a, i), undefined);
|
|
var result = validate(validator, b, i, a);
|
|
if (result && String(result) !== '[object Object]' && String(b) !== '[object Object]') {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return !!~a.indexOf(comparableB);
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$nin: function $nin(a, b, k, o) {
|
|
return !OPERATORS.$in(a, b, k, o);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$not: function $not(a, b, k, o) {
|
|
return !validate(a, b, k, o);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$type: function $type(a, b) {
|
|
return b != void 0 ? b instanceof a || b.constructor == a : false;
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$all: function $all(a, b, k, o) {
|
|
return OPERATORS.$and(a, b, k, o);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$size: function $size(a, b) {
|
|
return b ? a === b.length : false;
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$or: function $or(a, b, k, o) {
|
|
for (var i = 0, n = a.length; i < n; i++) {
|
|
if (validate(get(a, i), b, k, o)) return true;
|
|
}return false;
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$nor: function $nor(a, b, k, o) {
|
|
return !OPERATORS.$or(a, b, k, o);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$and: function $and(a, b, k, o) {
|
|
for (var i = 0, n = a.length; i < n; i++) {
|
|
if (!validate(get(a, i), b, k, o)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$regex: or(function (a, b) {
|
|
return typeof b === 'string' && a.test(b);
|
|
}),
|
|
|
|
/**
|
|
*/
|
|
|
|
$where: function $where(a, b, k, o) {
|
|
return a.call(b, b, k, o);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$elemMatch: function $elemMatch(a, b, k, o) {
|
|
if (isArray(b)) {
|
|
return !!~search(b, a);
|
|
}
|
|
return validate(a, b, k, o);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$exists: function $exists(a, b, k, o) {
|
|
return o.hasOwnProperty(k) === a;
|
|
}
|
|
};
|
|
|
|
/**
|
|
*/
|
|
|
|
var prepare = {
|
|
|
|
/**
|
|
*/
|
|
|
|
$eq: function $eq(a) {
|
|
|
|
if (a instanceof RegExp) {
|
|
return function (b) {
|
|
return typeof b === 'string' && a.test(b);
|
|
};
|
|
} else if (a instanceof Function) {
|
|
return a;
|
|
} else if (isArray(a) && !a.length) {
|
|
// Special case of a == []
|
|
return function (b) {
|
|
return isArray(b) && !b.length;
|
|
};
|
|
} else if (a === null) {
|
|
return function (b) {
|
|
//will match both null and undefined
|
|
return b == null;
|
|
};
|
|
}
|
|
|
|
return function (b) {
|
|
return compare(comparable(b), comparable(a)) === 0;
|
|
};
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$ne: function $ne(a) {
|
|
return prepare.$eq(a);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$and: function $and(a) {
|
|
return a.map(parse);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$all: function $all(a) {
|
|
return prepare.$and(a);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$or: function $or(a) {
|
|
return a.map(parse);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$nor: function $nor(a) {
|
|
return a.map(parse);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$not: function $not(a) {
|
|
return parse(a);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$regex: function $regex(a, query) {
|
|
return new RegExp(a, query.$options);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$where: function $where(a) {
|
|
return typeof a === 'string' ? new Function('obj', 'return ' + a) : a;
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$elemMatch: function $elemMatch(a) {
|
|
return parse(a);
|
|
},
|
|
|
|
/**
|
|
*/
|
|
|
|
$exists: function $exists(a) {
|
|
return !!a;
|
|
}
|
|
};
|
|
|
|
/**
|
|
*/
|
|
|
|
function search(array, validator) {
|
|
|
|
for (var i = 0; i < array.length; i++) {
|
|
var result = get(array, i);
|
|
if (validate(validator, get(array, i))) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function createValidator(a, validate) {
|
|
return { a: a, v: validate };
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function nestedValidator(a, b) {
|
|
var values = [];
|
|
findValues(b, a.k, 0, b, values);
|
|
|
|
if (values.length === 1) {
|
|
var first = values[0];
|
|
return validate(a.nv, first[0], first[1], first[2]);
|
|
}
|
|
|
|
// If the query contains $ne, need to test all elements ANDed together
|
|
var inclusive = a && a.q && typeof a.q.$ne !== 'undefined';
|
|
var allValid = inclusive;
|
|
for (var i = 0; i < values.length; i++) {
|
|
var result = values[i];
|
|
var isValid = validate(a.nv, result[0], result[1], result[2]);
|
|
if (inclusive) {
|
|
allValid &= isValid;
|
|
} else {
|
|
allValid |= isValid;
|
|
}
|
|
}
|
|
return allValid;
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function findValues(current, keypath, index, object, values) {
|
|
|
|
if (index === keypath.length || current == void 0) {
|
|
|
|
values.push([current, keypath[index - 1], object]);
|
|
return;
|
|
}
|
|
|
|
var k = get(keypath, index);
|
|
|
|
// ensure that if current is an array, that the current key
|
|
// is NOT an array index. This sort of thing needs to work:
|
|
// sift({'foo.0':42}, [{foo: [42]}]);
|
|
if (isArray(current) && isNaN(Number(k))) {
|
|
for (var i = 0, n = current.length; i < n; i++) {
|
|
findValues(get(current, i), keypath, index, current, values);
|
|
}
|
|
} else {
|
|
findValues(get(current, k), keypath, index + 1, current, values);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function createNestedValidator(keypath, a, q) {
|
|
return { a: { k: keypath, nv: a, q: q }, v: nestedValidator };
|
|
}
|
|
|
|
/**
|
|
* flatten the query
|
|
*/
|
|
|
|
function isVanillaObject(value) {
|
|
return value && value.constructor === Object;
|
|
}
|
|
|
|
function parse(query) {
|
|
query = comparable(query);
|
|
|
|
if (!query || !isVanillaObject(query)) {
|
|
// cross browser support
|
|
query = { $eq: query };
|
|
}
|
|
|
|
var validators = [];
|
|
|
|
for (var key in query) {
|
|
var a = query[key];
|
|
|
|
if (key === '$options') {
|
|
continue;
|
|
}
|
|
|
|
if (OPERATORS[key]) {
|
|
if (prepare[key]) a = prepare[key](a, query);
|
|
validators.push(createValidator(comparable(a), OPERATORS[key]));
|
|
} else {
|
|
|
|
if (key.charCodeAt(0) === 36) {
|
|
throw new Error('Unknown operation ' + key);
|
|
}
|
|
validators.push(createNestedValidator(key.split('.'), parse(a), a));
|
|
}
|
|
}
|
|
|
|
return validators.length === 1 ? validators[0] : createValidator(validators, OPERATORS.$and);
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function createRootValidator(query, getter) {
|
|
var validator = parse(query);
|
|
if (getter) {
|
|
validator = {
|
|
a: validator,
|
|
v: function v(a, b, k, o) {
|
|
return validate(a, getter(b), k, o);
|
|
}
|
|
};
|
|
}
|
|
return validator;
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function sift(query, array, getter) {
|
|
|
|
if (isFunction(array)) {
|
|
getter = array;
|
|
array = void 0;
|
|
}
|
|
|
|
var validator = createRootValidator(query, getter);
|
|
|
|
function filter(b, k, o) {
|
|
return validate(validator, b, k, o);
|
|
}
|
|
|
|
if (array) {
|
|
return array.filter(filter);
|
|
}
|
|
|
|
return filter;
|
|
}
|
|
|
|
/**
|
|
*/
|
|
|
|
function indexOf(query, array, getter) {
|
|
return search(array, createRootValidator(query, getter));
|
|
};
|
|
|
|
/**
|
|
*/
|
|
|
|
function compare(a, b) {
|
|
if (a === b) return 0;
|
|
if ((typeof a === 'undefined' ? 'undefined' : _typeof(a)) === (typeof b === 'undefined' ? 'undefined' : _typeof(b))) {
|
|
if (a > b) {
|
|
return 1;
|
|
}
|
|
if (a < b) {
|
|
return -1;
|
|
}
|
|
}
|
|
};
|
|
|
|
|