shelving
Version:
Toolkit for using data in JavaScript.
128 lines (127 loc) • 5.13 kB
JavaScript
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;
}