UNPKG

@irrelon/forerunnerdb-core

Version:

ForerunnerDB core utilities for operating on JSON data.

263 lines (214 loc) 7.5 kB
import {get as pathGet} from "@irrelon/path"; export const gates = ["$and", "$or", "$not", "$nor"]; export const matchPipeline = (pipeline, data, extraInfo = {"originalQuery": {}}) => { const opFunc = operationLookup[pipeline.op]; if (!opFunc) { throw new Error(`Unknown operation "${pipeline.op}"`); } return opFunc(data, pipeline.value, {"originalQuery": extraInfo.originalQuery, "operation": pipeline}); }; export const $and = (dataItem, opArr, extraInfo = {"originalQuery": {}}) => { // Match true on ALL operations to pass, if any are // returned then we have found a NON MATCHING entity return opArr.every((opData) => { let dataValue; let opValue; let opFunc; if (gates.indexOf(opData.op) > -1) { // The operation is a gate return operationLookup[opData.op](dataItem, opData.value, extraInfo); } dataValue = pathGet(dataItem, opData.path, undefined, {"arrayTraversal": true, "arrayExpansion": true}); opFunc = operationLookup[opData.op]; opValue = opData.value; if (!opFunc) { throw new Error(`Unknown operation "${opData.op}" in operation ${JSON.stringify(opData)}`); } return opFunc(dataValue, opValue, {"originalQuery": extraInfo.originalQuery, "operation": opData}); }); }; export const $not = (data, query, extraInfo = {"originalQuery": {}}) => { // Not operator return !$and(data, query, extraInfo); }; export const $or = (dataItem, opArr, extraInfo = {"originalQuery": {}}) => { // Match true on ANY operations to pass, if any are // returned then we have found a NON MATCHING entity return opArr.some((opData) => { let dataValue; let opValue; let opFunc; if (gates.indexOf(opData.op) > -1) { // The operation is a gate return operationLookup[opData.op](dataItem, opData.value, extraInfo); } dataValue = pathGet(dataItem, opData.path, undefined, {"arrayTraversal": true}); opFunc = operationLookup[opData.op]; opValue = opData.value; if (!opFunc) { throw new Error(`Unknown operation "${opData.op}"`); } return opFunc(dataValue, opValue, {"originalQuery": extraInfo.originalQuery, "operation": opData}); }); }; const normalise = (data) => { if (data instanceof Date) { return data.toISOString(); } return data; }; export const $gt = (data, query, extraInfo = {}) => { // Greater than return normalise(data) > normalise(query); }; export const $gte = (data, query, extraInfo = {}) => { // Greater than or equal return normalise(data) >= normalise(query); }; export const $lt = (data, query, extraInfo = {}) => { // Less than return normalise(data) < normalise(query); }; export const $lte = (data, query, extraInfo = {}) => { // Less than or equal return normalise(data) <= normalise(query); }; export const $exists = (data, query, extraInfo = {}) => { // Property exists return (data === undefined) !== normalise(query); }; export const $eq = (data, query, extraInfo = {}) => { // Equals // eslint-disable-next-line eqeqeq return normalise(data) == normalise(query); // jshint ignore:line }; export const $eeq = (data, query, extraInfo = {}) => { // Equals equals return normalise(data) === normalise(query); }; export const $ne = (data, query, extraInfo = {}) => { // Not equals // eslint-disable-next-line eqeqeq return normalise(data) != normalise(query); // eslint ignore:line }; // Not equals equals export const $nee = (data, query, extraInfo = {}) => { return normalise(data) !== normalise(query); }; export const $in = (data, query, {originalQuery, operation} = {"originalQuery": undefined, "operation": undefined}) => { // Check that the in query is an array if (Array.isArray(query)) { let inArr = query, inArrCount = inArr.length, inArrIndex; for (inArrIndex = 0; inArrIndex < inArrCount; inArrIndex++) { if ($eeq(data, inArr[inArrIndex], {originalQuery, operation})) { return true; } } return false; } console.log(`Cannot use an $in operator on non-array data in query ${JSON.stringify(originalQuery)}`); return false; }; export const $nin = (data, query, {originalQuery, operation} = {"originalQuery": undefined, "operation": undefined}) => { // Check that the in query is an array if (Array.isArray(query)) { let inArr = query, inArrCount = inArr.length, inArrIndex; for (inArrIndex = 0; inArrIndex < inArrCount; inArrIndex++) { if ($eeq(data, inArr[inArrIndex], {originalQuery, operation})) { return false; } } return true; } console.log(`Cannot use an $in operator on non-array data in query ${JSON.stringify(originalQuery)}`); return false; }; export const $fastIn = (data, query, {originalQuery, operation} = {"originalQuery": undefined, "operation": undefined}) => { if (query instanceof Array) { // Data is a string or number, use indexOf to identify match in array return query.indexOf(data) !== -1; } console.log(`Cannot use an $in operator on non-array data in query ${JSON.stringify(originalQuery)}`); return false; }; export const $fastNin = (data, query, {originalQuery, operation} = {"originalQuery": undefined, "operation": undefined}) => { if (query instanceof Array) { // Data is a string or number, use indexOf to identify match in array return query.indexOf(data) === -1; } console.log(`Cannot use an $in operator on non-array data in query ${JSON.stringify(originalQuery)}`); return false; }; export const $distinct = (data, query) => { let lookupPath, value, finalDistinctProp; // Ensure options holds a distinct lookup options.$rootData["//distinctLookup"] = options.$rootData["//distinctLookup"] || {}; for (const distinctProp in query) { if (query.hasOwnProperty(distinctProp)) { if (typeof query[distinctProp] === "object") { // Get the path string from the object lookupPath = this.sharedPathSolver.parse(query)[0].path; // Use the path string to find the lookup value from the data data value = this.sharedPathSolver.get(data, lookupPath); finalDistinctProp = lookupPath; } else { value = data[distinctProp]; finalDistinctProp = distinctProp; } options.$rootData["//distinctLookup"][finalDistinctProp] = options.$rootData["//distinctLookup"][finalDistinctProp] || {}; // Check if the options distinct lookup has this field's value if (options.$rootData["//distinctLookup"][finalDistinctProp][value]) { // Value is already in use return false; } else { // Set the value in the lookup options.$rootData["//distinctLookup"][finalDistinctProp][value] = true; // Allow the item in the results return true; } } } }; export const $count = (data, query) => { let countKey, countArr, countVal; // Iterate the count object's keys for (countKey in query) { if (Object.hasOwnProperty.call(query, countKey)) { // Check the property exists and is an array. If the property being counted is not // an array (or doesn't exist) then use a value of zero in any further count logic countArr = data[countKey]; if (typeof countArr === "object" && countArr instanceof Array) { countVal = countArr.length; } else { countVal = 0; } // Now recurse down the query chain further to satisfy the query for this key (countKey) if (!this._match(countVal, query[countKey], queryOptions, "and", options)) { return false; } } } // Allow the item in the results return true; }; export const operationLookup = { $eq, $eeq, $ne, $nee, $gt, $gte, $lt, $lte, $and, $or, $in, $nin, $fastIn, $fastNin, $exists };