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