UNPKG

@magnetarjs/utils

Version:

Magnetar utils like a logger for easier development

114 lines (113 loc) 4.48 kB
import { sort } from 'fast-sort'; import { isArray, isNumber } from 'is-what'; import { getProp } from 'path-to-prop'; import { parseValueForFilters } from './parseValueForFilters.js'; function passesWhere(docData, whereQuery) { const [fieldPath, operator, expectedValue] = whereQuery; const valueAtFieldPath = parseValueForFilters(getProp(docData, fieldPath)); let passes = false; switch (operator) { case '==': passes = valueAtFieldPath == expectedValue; break; case '!=': passes = valueAtFieldPath != expectedValue; break; case '<': passes = valueAtFieldPath < expectedValue; break; case '<=': passes = valueAtFieldPath <= expectedValue; break; case '>': passes = valueAtFieldPath > expectedValue; break; case '>=': passes = valueAtFieldPath >= expectedValue; break; case 'in': passes = isArray(expectedValue) && expectedValue.includes(valueAtFieldPath); break; case 'not-in': passes = isArray(expectedValue) && !expectedValue.includes(valueAtFieldPath); break; case 'array-contains': passes = isArray(valueAtFieldPath) && valueAtFieldPath.includes(expectedValue); break; case 'array-contains-any': passes = isArray(valueAtFieldPath) && valueAtFieldPath.some((v) => isArray(expectedValue) && expectedValue.includes(v)); break; default: throw new Error('invalid operator'); } return passes; } function passesQuery(docData, queryClause) { if ('and' in queryClause) { return queryClause.and.every((clause) => isArray(clause) ? passesWhere(docData, clause) : passesQuery(docData, clause)); } // if ('or' in queryClause) return queryClause.or.some((clause) => isArray(clause) ? passesWhere(docData, clause) : passesQuery(docData, clause)); } /** * Filters a Collection module's data map `Map<string, DocData>` based on provided clauses. */ export function filterDataPerClauses(dataCollectionMap, clauses) { const queryClauses = clauses.query || []; const whereClauses = clauses.where || []; const orderByClauses = clauses.orderBy || []; const { limit, startAfter } = clauses; // return the same dataCollectionMap to be sure to keep reactivity if (!queryClauses.length && !whereClauses.length && !orderByClauses.length && !isNumber(limit) && !startAfter) { return dataCollectionMap; } // all other cases we need to create a new Map() with the results let entries = []; dataCollectionMap.forEach((docData, docId) => { const passedQuery = queryClauses.every((queryClause) => passesQuery(docData, queryClause)); if (!passedQuery) return; const passedWhereFilters = whereClauses.every((whereClause) => passesWhere(docData, whereClause)); if (!passedWhereFilters) return; entries.push([docId, docData]); }); // orderBy const by = orderByClauses.reduce((carry, [path, direction = 'asc']) => { const sorter = { [direction]: (entry) => getProp(entry[1], path), }; carry.push(sorter); return carry; }, []); entries = orderByClauses.length ? sort(entries).by(by) : entries; // startAfter if (startAfter && orderByClauses.length) { const orderByKeys = orderByClauses.map(([path]) => path); const startAfterValues = Array.isArray(startAfter) ? startAfter : orderByKeys.map((key) => startAfter[key]); if (startAfterValues.length > orderByKeys.length) { throw new Error('startAfter must have the same or smaller number of values than orderBy'); } for (const [index, key] of orderByKeys.entries()) { const value = startAfterValues[index]; if (value == null) continue; const startIndex = entries.findIndex(([docId, docData]) => getProp(docData, key) === value); if (startIndex === -1) continue; entries = entries.slice(startIndex + 1); } } // limit entries = isNumber(limit) ? entries.slice(0, limit) : entries; // turn back into MAP return new Map(entries); }