Catalog API
The Catalog API (list.catalog) provides powerful querying, filtering, and analytical functions for working with data in a list. It's the primary interface for retrieving and analyzing data.
Overview
Each list has its own catalog instance that maintains independent query state and provides access to:
- Query operations: Apply, execute, and manage filters
- Analytical functions: Facets, aggregations, pivot tables, grouping, suggestions
- Data retrieval: Get items with projection and sorting
The examples on this page run against this setup (load your data with dcupl.data.set() before init()):
import { Dcupl } from '@dcupl/core';
const dcupl = new Dcupl();
dcupl.models.set({
key: 'Manufacturer',
properties: [
{ key: 'name', type: 'string' },
{ key: 'country', type: 'string' },
],
});
dcupl.models.set({
key: 'Category',
properties: [{ key: 'name', type: 'string' }],
references: [
{ key: 'parent', type: 'singleValued', model: 'Category' },
{ key: 'manufacturer', type: 'singleValued', model: 'Manufacturer' },
],
});
dcupl.models.set({
key: 'Product',
properties: [
{ key: 'name', type: 'string' },
{ key: 'price', type: 'float' },
{ key: 'quantity', type: 'int' },
{ key: 'rating', type: 'float' },
{ key: 'status', type: 'string' },
{ key: 'featured', type: 'boolean' },
{ key: 'createdAt', type: 'date' },
{ key: 'tags', type: 'Array<string>' },
],
references: [
{ key: 'category', type: 'singleValued', model: 'Category' },
{ key: 'manufacturer', type: 'singleValued', model: 'Manufacturer' },
],
});
await dcupl.init();
const list = dcupl.lists.create({ modelKey: 'Product' });API namespaces
catalog.query
Main query interface for filtering and retrieving data.
See: Query Operations
catalog.fn
Analytical functions for data analysis.
See: Analytical Functions
catalog.filters
Filter items for building filter UIs: filters.getAll() and filters.get({ filterKey }) return one item per attribute marked filter: true in the model (facet entries for references and booleans, a range for numbers and dates, only a comparisonType for text). For facet counts of any attribute, use fn.facets().
Query operations
In the signatures below, Item is the type you give dcupl.lists.create<Item>(), with key: string added; without a type argument it is any. one() and many() take no type argument and return Item. The other reads take an optional type argument that must extend Item, for example items<ProductWithStock>(); the options you pass never change the returned type.
addCondition()
Adds query condition(s) to the catalog. Use this to filter data.
Each condition goes into the group keyed by its attribute (created as an 'or' group if missing). Conditions on different attributes are therefore combined with AND, conditions on the same attribute with OR. For a range on one attribute (price >= 50 AND price <= 200), use addGroup() with groupType: 'and'.
Signature:
addCondition(
condition: DcuplQuery | DcuplQuery[],
options?: { skipProcessing?: boolean }
): DcuplGlobalQueryOptionsParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
condition |
DcuplQuery | DcuplQuery[] |
Yes | Query condition(s) to add |
options.skipProcessing |
boolean |
No | Skip immediate query processing |
Returns: DcuplGlobalQueryOptions - Current query state
Examples:
list.catalog.query.addCondition({
operator: 'eq',
attribute: 'status',
value: 'active',
});list.catalog.query.addCondition([
{ operator: 'eq', attribute: 'status', value: 'active' },
{ operator: 'gte', attribute: 'price', value: 100 },
]);
// status = 'active' AND price >= 100list.catalog.query.addCondition([
{ operator: 'eq', attribute: 'category', value: { key: 'electronics' } },
{ operator: 'eq', attribute: 'category', value: { key: 'books' } },
]);
// category = electronics OR category = bookslist.catalog.query.addCondition({
operator: 'eq',
attribute: 'category',
value: { key: 'electronics' },
queryKey: 'categoryFilter',
});
list.catalog.query.has({ groupKey: 'category', queryKey: 'categoryFilter' }); // truesetCondition()
Replaces the conditions on the condition's attribute (the group keyed by that attribute) with the new condition(s). Groups for other attributes are kept. To replace the whole query, call clear() first or use apply() with the root group.
Signature:
setCondition(
condition: DcuplQuery | DcuplQuery[],
options?: { skipProcessing?: boolean }
): DcuplGlobalQueryOptionsParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
condition |
DcuplQuery | DcuplQuery[] |
Yes | Query condition(s) to set |
options.skipProcessing |
boolean |
No | Skip immediate query processing |
Returns: DcuplGlobalQueryOptions - Current query state
Examples:
list.catalog.query.addCondition({ operator: 'eq', attribute: 'featured', value: true });
list.catalog.query.addCondition({ operator: 'eq', attribute: 'status', value: 'active' });
list.catalog.query.setCondition({
operator: 'eq',
attribute: 'status',
value: 'inactive',
});
// featured = true AND status = 'inactive'list.catalog.query.setCondition([
{ operator: 'eq', attribute: 'status', value: 'active' },
{ operator: 'gte', attribute: 'price', value: 100 },
]);addGroup()
Adds a query group with multiple conditions combined by AND/OR logic.
- A group with a
groupKeyextends the existing group with that key (itsgroupTypeis replaced if you pass one) or is created. - A group without a
groupKeyis always appended as a new group. - A newly created group without
groupTypeis stored as'or'. Always setgroupTypeexplicitly.
Signature:
addGroup(
group: DcuplQueryGroup,
options?: { skipProcessing?: boolean }
): DcuplGlobalQueryOptionsParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
group |
DcuplQueryGroup |
Yes | Query group to add |
options.skipProcessing |
boolean |
No | Skip immediate query processing |
Returns: DcuplGlobalQueryOptions - Current query state
Examples:
list.catalog.query.addGroup({
groupKey: 'priceRange',
groupType: 'and',
queries: [
{ operator: 'gte', attribute: 'price', value: 50 },
{ operator: 'lte', attribute: 'price', value: 200 },
],
});list.catalog.query.addGroup({
groupKey: 'categories',
groupType: 'or',
queries: [
{ operator: 'eq', attribute: 'category', value: { key: 'electronics' } },
{ operator: 'eq', attribute: 'category', value: { key: 'books' } },
],
});setGroup()
Replaces the group with the same groupKey, found at any depth (a top-level match wins); all other groups and the item options are kept. A group given without groupType is stored as 'or'. A group without groupKey replaces the first top-level group that has no groupKey; it is only appended when there is none. Give groups a groupKey so you know which one setGroup() replaces.
Signature:
setGroup(
group: DcuplQueryGroup,
options?: { skipProcessing?: boolean }
): DcuplGlobalQueryOptionsExample:
list.catalog.query.setGroup({
groupKey: 'priceRange',
groupType: 'and',
queries: [
{ operator: 'gte', attribute: 'price', value: 100 },
{ operator: 'lte', attribute: 'price', value: 600 },
],
});items()
Executes the current query and returns matching items.
Signature:
items<T extends Item = Item>(options?: DcuplListQueryOptions<NoInfer<T>>): T[]Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
DcuplListQueryOptions<T> |
No | Query execution options |
Returns: T[] - Array of matching items
Without options, the list's query and its applied item options (start, count, sort, projection from applyOptions()) are used. Item options you pass override the applied ones for this call. queries you pass are applied on top of the list's query; in that case the applied item options are not used.
Examples:
const items = list.catalog.query.items();const page = list.catalog.query.items({
start: 0,
count: 20,
});const sorted = list.catalog.query.items({
sort: { attributes: ['price', 'name'], order: ['DESC', 'ASC'] },
});const projected = list.catalog.query.items({
projection: {
name: true,
price: true,
category: true,
},
});const results = list.catalog.query.items({
queries: [{ operator: 'eq', attribute: 'status', value: 'active' }],
start: 0,
count: 20,
sort: { attributes: ['price', 'name'], order: ['DESC', 'ASC'] },
projection: { name: true, price: true },
});first()
Returns the first matching item.
Signature:
first<T extends Item = Item>(options?: DcuplListQueryOptions<NoInfer<T>>): T | undefinedParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
DcuplListQueryOptions<T> |
No | Query execution options |
Returns: T | undefined - First matching item or undefined
Examples:
const item = list.catalog.query.first();const activeItem = list.catalog.query.first({
queries: [{ operator: 'eq', attribute: 'status', value: 'active' }],
});count()
Returns the count of matching items.
Signature:
count<T extends Item = Item>(options?: DcuplListQueryOptions<NoInfer<T>>): numberParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
DcuplListQueryOptions<T> |
No | Query execution options |
Returns: number - Count of matching items
count() returns the length of items() for the same options, so it respects an applied start/count (the size of the current page). The total number of matches is list.catalog.fn.metadata().currentSize.
Examples:
const pageSize = list.catalog.query.count();
const total = list.catalog.fn.metadata().currentSize;const activeCount = list.catalog.query.count({
queries: [{ operator: 'eq', attribute: 'status', value: 'active' }],
});any()
Checks if any results exist.
Signature:
any<T extends Item = Item>(options?: DcuplListQueryOptions<NoInfer<T>>): booleanParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
DcuplListQueryOptions<T> |
No | Query execution options |
Returns: boolean - True if any results exist
Examples:
if (list.catalog.query.any()) {
console.log('Items found');
}const hasActive = list.catalog.query.any({
queries: [{ operator: 'eq', attribute: 'status', value: 'active' }],
});removeCondition()
Removes specific query condition(s). Pass each condition as you added it (attribute, operator, value); it is removed from the group keyed by its attribute, and a group left empty is removed too. To remove every condition on one attribute, use removeGroup() with the attribute name.
Signature:
removeCondition(
condition: DcuplQuery | DcuplQuery[],
options?: { skipProcessing?: boolean }
): DcuplGlobalQueryOptionsParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
condition |
DcuplQuery | DcuplQuery[] |
Yes | Condition(s) to remove |
options.skipProcessing |
boolean |
No | Skip immediate query processing |
Returns: DcuplGlobalQueryOptions - Updated query state
Examples:
list.catalog.query.removeCondition({
operator: 'eq',
attribute: 'status',
value: 'active',
});list.catalog.query.removeGroup('status');list.catalog.query.removeCondition([
{ operator: 'eq', attribute: 'status', value: 'active' },
{ operator: 'eq', attribute: 'category', value: { key: 'electronics' } },
]);removeGroup()
Removes a query group by its key. Conditions added with addCondition() live in a group keyed by their attribute, so removeGroup('price') removes every condition on price.
Signature:
removeGroup(
groupKey: string,
options?: { skipProcessing?: boolean }
): DcuplGlobalQueryOptionsParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
groupKey |
string |
Yes | Key of the group to remove |
options.skipProcessing |
boolean |
No | Skip immediate query processing |
Returns: DcuplGlobalQueryOptions - Updated query state
Examples:
list.catalog.query.removeGroup('priceRange');clear()
Removes all query conditions and the item options (start, count, sort, projection).
Signature:
clear(options?: { skipProcessing?: boolean }): DcuplGlobalQueryOptionsParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options.skipProcessing |
boolean |
No | Skip immediate query processing |
Returns: DcuplGlobalQueryOptions - Empty query state ({ modelKey, groupKey: 'root', groupType: 'and', queries: [] })
Examples:
list.catalog.query.clear();list.catalog.query.clear({ skipProcessing: true });list.catalog.query.clear();
const allItems = list.catalog.query.items();current()
Returns the current query state.
Signature:
current(): DcuplGlobalQueryOptionsReturns: DcuplGlobalQueryOptions - Current query configuration
Examples:
const query = list.catalog.query.current();
console.log(query.queries);
console.log(query.groupType);const savedQuery = list.catalog.query.current();// savedQuery is the root group (groupKey: 'root'), so 'set' replaces the whole query and its options
list.catalog.query.apply(savedQuery, { mode: 'set' });isEmpty()
Checks if the query has any conditions.
Signature:
isEmpty(): booleanReturns: boolean - True if no conditions are applied
Examples:
if (list.catalog.query.isEmpty()) {
console.log('No filters applied');
}has()
Checks if a specific query or group exists.
Signature:
has(options: { groupKey: string; queryKey?: string }): booleanParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options.groupKey |
string |
Yes | Group key to check |
options.queryKey |
string |
No | Optional query key within group |
Returns: boolean - True if exists
has() finds a keyed group at any depth (a top-level match wins), also when a queryKey is given, like removeGroup(), setGroup() and fn.facets({ excludeGroup }).
Examples:
if (list.catalog.query.has({ groupKey: 'priceRange' })) {
console.log('Price range filter is active');
}// addCondition() puts the condition into the group keyed by its attribute
list.catalog.query.addCondition({ operator: 'eq', attribute: 'status', value: 'active', queryKey: 'statusFilter' });
if (
list.catalog.query.has({
groupKey: 'status',
queryKey: 'statusFilter',
})
) {
console.log('Status filter is active');
}applyOptions()
Applies item options (pagination, sorting, projection) to the query state. This method sets or updates the non-filter options that affect how results are retrieved.
Signature:
applyOptions(
itemOptions: DcuplItemsOptions,
options?: { mode?: 'set' | 'update'; skipProcessing?: boolean }
): DcuplGlobalQueryOptionsParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
itemOptions |
DcuplItemsOptions |
Yes | Item options including start, count, projection, and sort |
options.mode |
'set' | 'update' |
No | 'set' replaces all options, 'update' merges with existing (default: 'update') |
options.skipProcessing |
boolean |
No | Skip immediate query processing |
DcuplItemsOptions:
| Property | Type | Description |
|---|---|---|
start |
number |
Starting index for pagination |
count |
number |
Number of items to return |
projection |
Projection<T> |
Fields to include in results |
sort |
SortingProjection<T> |
Sorting configuration |
sort takes { attributes: [...], order: [...] } with one 'ASC' or 'DESC' per attribute (uppercase). A sort with any other order value, or with not exactly one order entry per attribute, is ignored as a whole with a warning naming the expected shape, and the items keep their unsorted order. projection is an object of attribute names ({ name: true, price: true }); the item key is always included.
Returns: DcuplGlobalQueryOptions - Updated query state
Examples:
list.catalog.query.applyOptions({
start: 0,
count: 20,
});list.catalog.query.applyOptions({
sort: { attributes: ['price', 'name'], order: ['DESC', 'ASC'] },
});list.catalog.query.applyOptions({
projection: {
name: true,
price: true,
category: true,
},
});list.catalog.query.applyOptions({
start: 0,
count: 20,
sort: { attributes: ['price'], order: ['DESC'] },
projection: { name: true, price: true, category: true },
});// First set initial options
list.catalog.query.applyOptions({
start: 0,
count: 20,
});
// Later, update only the sorting (keeps start and count)
list.catalog.query.applyOptions({ sort: { attributes: ['price'], order: ['DESC'] } }, { mode: 'update' });// Set new options, clearing any previously set options
list.catalog.query.applyOptions({ count: 50 }, { mode: 'set' });// Apply multiple changes without intermediate processing
list.catalog.query.addCondition({ operator: 'eq', attribute: 'status', value: 'active' }, { skipProcessing: true });
list.catalog.query.applyOptions({
start: 0,
count: 20,
sort: { attributes: ['createdAt'], order: ['DESC'] },
});
// Processing happens on the final callWith skipProcessing: true the query state changes, but the list's result is not recomputed and no result_updated event fires. Until a later call without it, items() and count() evaluate the new query on the previous result: an added condition already narrows it, but a removed condition or group (or reset()) does not widen it. fn.facets() counts the new query against the list's unfiltered data, while one(), many(), fn.metadata(), fn.aggregate(), fn.groupBy(), fn.pivot() and fn.suggest() still work on the previous result.
one()
Returns a single item by key, if it is part of the list's current result.
Signature:
one(options: DcuplItemOptions<Item>): Item | nullParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
DcuplItemOptions<Item> |
Yes | Item retrieval options |
Returns: Item | null - The item, or null if the key is not in the list's current result
Examples:
const item = list.catalog.query.one({ itemKey: 'p1' });const item = list.catalog.query.one({
itemKey: 'p1',
projection: { name: true, price: true },
});many()
Returns multiple specific items by keys. Keys that are not in the list's current result are skipped.
Signature:
many(options: DcuplManyItemsOptions<Item>): Item[]Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
DcuplManyItemsOptions<Item> |
Yes | Items retrieval options |
Returns: Item[] - Array of items
Examples:
const items = list.catalog.query.many({
itemKeys: ['p1', 'p2', 'p3'],
});const items = list.catalog.query.many({
itemKeys: ['p1', 'p2', 'p3'],
projection: { name: true, price: true },
});Core query methods
These are the foundational query methods. The semantic aliases (like addCondition, clear, items) provide a more intuitive API built on top of these core methods.
apply()
Applies query condition(s) to the catalog. This is the core method that addCondition(), setCondition(), and addGroup() are built on.
Signature:
apply(
query: DcuplQueryGroup | DcuplQuery | DcuplQuery[] | DcuplQueryGroup[],
options?: { mode?: 'set' | 'add'; skipProcessing?: boolean }
): DcuplGlobalQueryOptionsParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
DcuplQueryGroup | DcuplQuery | DcuplQuery[] | DcuplQueryGroup[] |
Yes | Query condition(s) or group(s) to apply |
options.mode |
'set' | 'add' |
No | 'add' (default) adds like addCondition()/addGroup(); 'set' works per group (see below) |
options.skipProcessing |
boolean |
No | Skip immediate query processing |
Returns: DcuplGlobalQueryOptions - Updated query state
In 'set' mode a condition replaces the conditions of its attribute group (like setCondition()) and a keyed group replaces the group with that key at any depth (like setGroup()); other groups are kept. A group without groupKey replaces the first top-level group without a key (appended if there is none). Only the root group (groupKey: 'root') replaces the whole query, including its item options.
Examples:
list.catalog.query.apply({ operator: 'eq', attribute: 'status', value: 'active' }, { mode: 'add' });list.catalog.query.apply({ operator: 'eq', attribute: 'status', value: 'active' }, { mode: 'set' });list.catalog.query.apply(
{
groupKey: 'root',
groupType: 'and',
queries: [{ operator: 'eq', attribute: 'status', value: 'active' }],
},
{ mode: 'set' },
);list.catalog.query.apply(
[
{ operator: 'eq', attribute: 'status', value: 'active' },
{ operator: 'gte', attribute: 'price', value: 100 },
],
{ mode: 'add' },
);list.catalog.query.apply(
{
groupKey: 'priceRange',
groupType: 'and',
queries: [
{ operator: 'gte', attribute: 'price', value: 50 },
{ operator: 'lte', attribute: 'price', value: 200 },
],
},
{ mode: 'add' },
);remove()
Removes query condition(s) or groups from the catalog. This is the core method that removeCondition() and removeGroup() are built on.
Signature:
remove(options?: Partial<DcuplQueryGroup> | DcuplQuery): DcuplGlobalQueryOptionsParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
Partial<DcuplQueryGroup> | DcuplQuery |
No | Condition or group to remove. If omitted, removes all conditions (item options are kept). |
Returns: DcuplGlobalQueryOptions - Updated query state
Examples:
list.catalog.query.remove({ operator: 'eq', attribute: 'status', value: 'active' });list.catalog.query.remove({ groupKey: 'priceRange' });list.catalog.query.remove();reset()
Resets the query to its initial empty state, including the item options. This is the core method that clear() wraps.
Signature:
reset(options?: { skipProcessing?: boolean }): DcuplGlobalQueryOptionsParameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options.skipProcessing |
boolean |
No | Skip immediate query processing |
Returns: DcuplGlobalQueryOptions - Empty query state
Examples:
list.catalog.query.reset();list.catalog.query.reset({ skipProcessing: true });execute()
Executes the current query and returns matching items. This is the core method that items() wraps.
Signature:
execute<T extends Item = Item>(options?: DcuplListQueryOptions<NoInfer<T>>): T[]Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
options |
DcuplListQueryOptions<T> |
No | Query execution options |
Returns: T[] - Array of matching items
Examples:
const items = list.catalog.query.execute();const items = list.catalog.query.execute({
start: 0,
count: 20,
sort: { attributes: ['price'], order: ['DESC'] },
});// featured = true on top of the list's own query
const items = list.catalog.query.execute({
queries: [{ operator: 'eq', attribute: 'featured', value: true }],
count: 10,
});get()
Returns the current query state. This is the core method that current() wraps.
Signature:
get(): DcuplGlobalQueryOptionsReturns: DcuplGlobalQueryOptions - Current query configuration
Examples:
const query = list.catalog.query.get();
console.log(query.queries);const savedQuery = list.catalog.query.get();
// ... make changes ...
list.catalog.query.apply(savedQuery, { mode: 'set' }); // root group: replaces the whole queryAnalytical functions
list.catalog.fn.* runs the six analysis functions on the list's current query. Options, return
shapes and edge cases live on each function's page; errors are listed in
Functions API → Errors.
| Method | Purpose | Reference |
|---|---|---|
facets(options): DcuplFacet[] |
Filter options with counts for one attribute, counted disjunctively | facets() |
aggregate(options): Aggregation |
sum, avg, min, max, count, distinct and groups of an attribute |
aggregate() |
suggest(options): Suggestion[] |
Autocomplete values for an attribute (prefix or regex match) | suggest() |
groupBy(options): GroupByResponse |
Items grouped by an attribute value, with optional aggregates per group | groupBy() |
pivot(options): PivotResponse |
A pivot table over rows, columns and aggregated values | pivot() |
metadata(): ListMetadata |
Current and initial item count, the applied query, attributes and views | metadata() |
Type definitions
The query and function types are exported from @dcupl/common
(import type { DcuplQuery } from '@dcupl/common';); their definitions and examples are in
Type definitions:
DcuplQuery, DcuplQueryGroup,
DcuplListQueryOptions,
DcuplFacetOptions,
AggregationOptions,
SuggestionOptions and PivotOptions.
Operators and their options are in the Query operators reference.
Complete examples
Worked examples that combine these calls live in the guides: Faceted search (search with facets), Range queries (price range filter), aggregate() (statistics) and Search & autocomplete.