UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

147 lines (146 loc) 5.82 kB
import { isArray, limitArray, requireLast } from "./array.js"; import { getDataProp, joinDataPath, splitDataPath } 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"; /** 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 getQueryFilters(query) { return Array.from(yieldQueryFilters(query)); } function* yieldQueryFilters(query) { for (const [key, value] of getProps(query)) { if (key === "$order" || key === "$limit" || value === undefined) continue; if (key.startsWith("!")) yield isArray(value) ? { key: splitDataPath(key.slice(1)), operator: "out", value } : { key: splitDataPath(key.slice(1)), operator: "not", value }; else if (key.endsWith("[]")) yield { key: splitDataPath(key.slice(0, -2)), operator: "contains", value }; else if (key.endsWith(">")) yield { key: splitDataPath(key.slice(0, -1)), operator: "gt", value }; else if (key.endsWith(">=")) yield { key: splitDataPath(key.slice(0, -2)), operator: "gte", value }; else if (key.endsWith("<")) yield { key: splitDataPath(key.slice(0, -1)), operator: "lt", value }; else if (key.endsWith("<=")) yield { key: splitDataPath(key.slice(0, -2)), operator: "lte", value }; else yield isArray(value) ? { key: splitDataPath(key), operator: "in", value } : { key: splitDataPath(key), operator: "is", value }; } } /** Get the `Order` objects for a query. */ export function getQueryOrders({ $order }) { return Array.from(yieldQueryOrders($order)); } function* yieldQueryOrders(order) { for (const key of isArray(order) ? order : [order]) { if (key === undefined) continue; if (key.startsWith("!")) yield { key: splitDataPath(key.slice(1)), direction: "desc" }; else yield { key: splitDataPath(key), direction: "asc" }; } } /** Get the limit for a query. */ export function getQueryLimit({ $limit }) { return $limit; } /** Query a set of data items using a query. */ export function queryItems(items, query) { return limitQueryItems(sortQueryItems(filterQueryItems(items, getQueryFilters(query)), getQueryOrders(query)), getQueryLimit(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 getQueryLimit(query) === undefined ? filterQueryItems(items, getQueryFilters(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. * - For token based pagination on a result set. */ export function getBeforeQuery(query, item) { const sorts = getQueryOrders(query); const lastSort = requireLast(sorts); const newQuery = { ...query }; for (const sort of sorts) { const { key, direction } = sort; const keyStr = joinDataPath(key); const value = getDataProp(item, key); newQuery[direction === "asc" ? (sort === lastSort ? `${keyStr}>` : `${keyStr}>=`) : sort === lastSort ? `${keyStr}<` : `${keyStr}<=`] = value; } return newQuery; } /** * Get a query for items that appear after a specified item. * - For token based pagination on a result set. */ export function getAfterQuery(query, item) { const sorts = getQueryOrders(query); const lastSort = requireLast(sorts); const newQuery = { ...query }; for (const sort of sorts) { const { key, direction } = sort; const keyStr = joinDataPath(key); const value = getDataProp(item, key); newQuery[direction === "asc" ? (sort === lastSort ? `${keyStr}<` : `${keyStr}<=`) : sort === lastSort ? `${keyStr}>` : `${keyStr}>=`] = value; } return newQuery; }