UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

128 lines (127 loc) 5.13 kB
import { isArray, limitArray, requireLast } from "./array.js"; import { getDataProp } from "./data.js"; import { isArrayWith, isEqual, isEqualGreater, isEqualLess, isGreater, isInArray, isLess, notEqual, notInArray } from "./equal.js"; import { limitItems } from "./iterate.js"; import { getProps } from "./object.js"; import { compareAscending, compareDescending, sortArray } from "./sort.js"; import { isDefined } from "./undefined.js"; /** Map `Filter` operators to corresponding `Match` function. */ const MATCHERS = { is: isEqual, not: notEqual, in: isInArray, out: notInArray, contains: isArrayWith, lt: isLess, lte: isEqualLess, gt: isGreater, gte: isEqualGreater, }; /** Get the `Filter` objects for a query. */ export function getFilters(query) { return getProps(query).map(_getFilters).filter(isDefined); } function _getFilters([key, value]) { if (key !== "$order" && key !== "$limit" && value !== undefined) { if (key.startsWith("!")) return isArray(value) ? { key: key.slice(1), operator: "out", value } : { key: key.slice(1), operator: "not", value }; if (key.endsWith("[]")) return { key: key.slice(0, -2), operator: "contains", value }; if (key.endsWith(">")) return { key: key.slice(0, -1), operator: "gt", value }; if (key.endsWith(">=")) return { key: key.slice(0, -2), operator: "gte", value }; if (key.endsWith("<")) return { key: key.slice(0, -1), operator: "lt", value }; if (key.endsWith("<=")) return { key: key.slice(0, -2), operator: "lte", value }; return isArray(value) ? { key, operator: "in", value } : { key: key, operator: "is", value }; } } /** Get the `Order` objects for a query. */ export function getOrders({ $order }) { return (isArray($order) ? $order : [$order]).filter(isDefined).map(_getOrder); } function _getOrder(key) { if (key.startsWith("!")) return { key: key.slice(1), direction: "desc" }; return { key, direction: "asc" }; } /** Get the limit for a query. */ export function getLimit({ $limit }) { return $limit; } /** Query a set of data items using a query. */ export function queryItems(items, query) { return limitQueryItems(sortQueryItems(filterQueryItems(items, getFilters(query)), getOrders(query)), getLimit(query)); } /** * Query a set of data items for writing using a query. * - If no limit is set on the data sorting can be avoided too for performance reasons. */ export function queryWritableItems(items, query) { return getLimit(query) === undefined ? filterQueryItems(items, getFilters(query)) : queryItems(items, query); } /** Match a single data item againt a set of filters. */ export function matchQueryItem(item, filters) { for (const { key, operator, value } of filters) { const matcher = MATCHERS[operator]; if (!matcher(getDataProp(item, key), value)) return false; } return true; } /** Filter a set of data items using a set of filters. */ export function* filterQueryItems(items, filters) { if (filters.length) { for (const item of items) if (matchQueryItem(item, filters)) yield item; } else { yield* items; } } /** Compare two data items using a set of orders. */ export function compareQueryItems(left, right, orders) { for (const { key, direction } of orders) { const l = getDataProp(left, key); const r = getDataProp(right, key); const c = direction === "asc" ? compareAscending(l, r) : compareDescending(l, r); if (c !== 0) return c; } return 0; } /** Sort a set of data items using a set of orders. */ export function sortQueryItems(items, orders) { return orders.length ? sortArray(items, compareQueryItems, orders) : items; } /** LImit a set of data items using a set of limit. */ export function limitQueryItems(items, limit) { return typeof limit !== "number" ? items : isArray(items) ? limitArray(items, limit) : limitItems(items, limit); } /** Get a query for items that appear before a specified item. */ export function getBeforeQuery(query, item) { const sorts = getOrders(query); const lastSort = requireLast(sorts); const newQuery = { ...query }; for (const sort of sorts) { const { key, direction } = sort; const value = getDataProp(item, key); newQuery[direction === "asc" ? (sort === lastSort ? `${key}>` : `${key}>=`) : sort === lastSort ? `${key}<` : `${key}<=`] = value; } return newQuery; } /** Get a query for items that appear after a specified item. */ export function getAfterQuery(query, item) { const sorts = getOrders(query); const lastSort = requireLast(sorts); const newQuery = { ...query }; for (const sort of sorts) { const { key, direction } = sort; const value = getDataProp(item, key); newQuery[direction === "asc" ? (sort === lastSort ? `${key}<` : `${key}<=`) : sort === lastSort ? `${key}>` : `${key}>=`] = value; } return newQuery; }