Version 1.1.0 is additive: nothing changes for a 1.0.0 consumer, every
existing export keeps its signature and semantics. It adds the one reader the
package ships, over a plain array, so a DataSource with FilterExtension,
SortExtension (or SingleSortExtension) and PaginationExtension runs on
local data exactly as it runs against a server, without a second "client
mode" in the consumer.
createInMemoryQueryItemsReader(getItems, options?) returns { reader, countReader }:
the items reader of a DataSource and the total count reader of a
PaginationExtension, both over the given list.
const { countReader, reader } = createInMemoryQueryItemsReader(() => users);
const filtering = new FilterExtension<User>();
const sorting = new SortExtension<User>();
const paginating = new PaginationExtension<User>(countReader, 20);
const dataSource = new DataSource<User>(reader, [
filtering,
sorting,
paginating,
]);
await dataSource.read();
getItems returns the current list and is called on every read: return
another list to swap the data between two reads without rebuilding the
data source.isReading
of the data source is observable by the UI.The same logic, synchronous, for a consumer that already has a payload:
queryItems(items, payload, options?) returns the items matching
payload.filters, in the order of payload.sorts, reduced to
payload.projections and deduplicated when payload.distinct, cut to
payload.paging; without paging every matching item, without any property
a copy of the list.countItems(items, payload, options?) returns the number of items matching
payload.filters, ignoring sorts and paging; with distinct the duplicates
(found on the projections when given) are not counted, so the count agrees
with the pages.Each step of queryItems is a function of its own, in the in-memory
subfolder of the feature it belongs to, so a consumer can reuse one step
without the others (e.g. filter a list it sorts itself). Every delegate
leaves the list and its items untouched and returns a new array, a copy of
the list when it has nothing to apply.
| Feature | Delegates |
|---|---|
filtering |
filterItems(items, filters?, options?), itemMatchesFilterExpression(item, expression, options?), itemMatchesFilter(item, filter, options?) |
sorting |
sortItems(items, sorts?, options?) |
projection |
projectItems(items, projections?), distinctItems(items) |
pagination |
pageItems(items, paging?) |
createInMemoryQueryItemsComparer(options?) resolves the comparison options into the
InMemoryQueryItemsComparer the delegates use, for a consumer writing its own.
Every value, of an item and of a filter, goes through toComparableValue
before being compared with ===, < and >:
| Value | Compared as |
|---|---|
null, undefined |
null, the single "unset" value |
| number, boolean, other primitives | as they are |
| string | lower-cased, unless options.caseSensitive is true |
Date |
getTime() |
object whose valueOf() is a primitive |
that primitive, converted with the same rules |
| any other object | as it is, by reference |
The valueOf rule lets the value objects of the consumer (money, quantities,
identifiers) take part in filters and sorts without being a dependency of
this package. options.toComparable(value, field) replaces the rule for the
fields that need another one; it receives both sides of every comparison and
its result is used as it is.
FilterOperators member. equal, notEqual, in,
notIn by === on the comparables; equalInsensitive ignores the case
whatever caseSensitive; greaterThan, greaterThanOrEqual, lessThan,
lessThanOrEqual and range (with exclude on each limit, null limit =
open side) never match an unset value; contains / notContains work on
strings (substring) and on lists (membership); startsWith / endsWith
compare the text of the values. FiltersExpression trees are honoured
recursively at any depth; an empty list or group matches every item.payload.sorts; unset
values first ascending, last descending.Dates and nested objects by content). Applied
after the projections and before the paging, as a SELECT DISTINCT.page, itemsPerPage; a page beyond the last one is
empty, not an error.QueryItemsReader<TItem, TResult = List<TItem>>, the function resolving a
QueryItemsPayload into a result, now lives in query-items next to the
payload: the query model and its readers no longer depend on data-source.
DataSourceReader is the same type under the name of the data source, like
DataSourcePayload for QueryItemsPayload; nothing changes for a consumer.
Everything is re-exported flat from @mantlebee/ts-data.
| Module | Exports |
|---|---|
query-items |
QueryItemsReader |
query-items/in-memory |
createInMemoryQueryItemsReader, queryItems, countItems, toComparableValue, createInMemoryQueryItemsComparer, InMemoryQueryItemsComparer, InMemoryQueryItemsReaderOptions, InMemoryQueryItemsReader |
query-items/filtering/in-memory |
filterItems, itemMatchesFilterExpression, itemMatchesFilter |
query-items/sorting/in-memory |
sortItems |
query-items/projection/in-memory |
projectItems, distinctItems |
query-items/pagination/in-memory |
pageItems |
Nothing to change. A consumer that carries its own in-memory resolution of
the payload (e.g. the fixtures of a Storybook) can replace it with
createInMemoryQueryItemsReader and drop the copy; the readers are plain
DataSourceReaders, so they take the place of the HTTP ones without touching
the extensions.