.md

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()):

Setup used by the examples
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 }
): DcuplGlobalQueryOptions

Parameters:

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:

Add single condition
list.catalog.query.addCondition({
  operator: 'eq',
  attribute: 'status',
  value: 'active',
});
Add multiple conditions
list.catalog.query.addCondition([
  { operator: 'eq', attribute: 'status', value: 'active' },
  { operator: 'gte', attribute: 'price', value: 100 },
]);
// status = 'active' AND price >= 100
Same attribute: OR
list.catalog.query.addCondition([
  { operator: 'eq', attribute: 'category', value: { key: 'electronics' } },
  { operator: 'eq', attribute: 'category', value: { key: 'books' } },
]);
// category = electronics OR category = books
Add condition with a query key
list.catalog.query.addCondition({
  operator: 'eq',
  attribute: 'category',
  value: { key: 'electronics' },
  queryKey: 'categoryFilter',
});

list.catalog.query.has({ groupKey: 'category', queryKey: 'categoryFilter' }); // true

setCondition()

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 }
): DcuplGlobalQueryOptions

Parameters:

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:

Replace the status condition
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'
Replace several attributes at once
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 groupKey extends the existing group with that key (its groupType is replaced if you pass one) or is created.
  • A group without a groupKey is always appended as a new group.
  • A newly created group without groupType is stored as 'or'. Always set groupType explicitly.

Signature:

addGroup(
  group: DcuplQueryGroup,
  options?: { skipProcessing?: boolean }
): DcuplGlobalQueryOptions

Parameters:

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:

Add price range group (AND logic)
list.catalog.query.addGroup({
  groupKey: 'priceRange',
  groupType: 'and',
  queries: [
    { operator: 'gte', attribute: 'price', value: 50 },
    { operator: 'lte', attribute: 'price', value: 200 },
  ],
});
Add category group (OR logic)
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 }
): DcuplGlobalQueryOptions

Example:

Change the price range
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:

Get all matching items
const items = list.catalog.query.items();
With pagination
const page = list.catalog.query.items({
  start: 0,
  count: 20,
});
With sorting
const sorted = list.catalog.query.items({
  sort: { attributes: ['price', 'name'], order: ['DESC', 'ASC'] },
});
With projection
const projected = list.catalog.query.items({
  projection: {
    name: true,
    price: true,
    category: true,
  },
});
Complete query execution
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 | undefined

Parameters:

Parameter Type Required Description
options DcuplListQueryOptions<T> No Query execution options

Returns: T | undefined - First matching item or undefined

Examples:

Get first item
const item = list.catalog.query.first();
First matching condition
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>>): number

Parameters:

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:

Count the current result
const pageSize = list.catalog.query.count();
const total = list.catalog.fn.metadata().currentSize;
Count matching condition
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>>): boolean

Parameters:

Parameter Type Required Description
options DcuplListQueryOptions<T> No Query execution options

Returns: boolean - True if any results exist

Examples:

Check if any items exist
if (list.catalog.query.any()) {
  console.log('Items found');
}
Check for specific condition
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 }
): DcuplGlobalQueryOptions

Parameters:

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:

Remove a condition
list.catalog.query.removeCondition({
  operator: 'eq',
  attribute: 'status',
  value: 'active',
});
Remove all conditions on an attribute
list.catalog.query.removeGroup('status');
Remove multiple
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 }
): DcuplGlobalQueryOptions

Parameters:

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:

Remove price range group
list.catalog.query.removeGroup('priceRange');

clear()

Removes all query conditions and the item options (start, count, sort, projection).

Signature:

clear(options?: { skipProcessing?: boolean }): DcuplGlobalQueryOptions

Parameters:

Parameter Type Required Description
options.skipProcessing boolean No Skip immediate query processing

Returns: DcuplGlobalQueryOptions - Empty query state ({ modelKey, groupKey: 'root', groupType: 'and', queries: [] })

Examples:

Clear all filters
list.catalog.query.clear();
Clear without processing
list.catalog.query.clear({ skipProcessing: true });
Clear and get all items
list.catalog.query.clear();
const allItems = list.catalog.query.items();

current()

Returns the current query state.

Signature:

current(): DcuplGlobalQueryOptions

Returns: DcuplGlobalQueryOptions - Current query configuration

Examples:

Get current query
const query = list.catalog.query.current();
console.log(query.queries);
console.log(query.groupType);
Save query state
const savedQuery = list.catalog.query.current();
Restore query state later
// 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(): boolean

Returns: 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 }): boolean

Parameters:

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:

Check if group exists
if (list.catalog.query.has({ groupKey: 'priceRange' })) {
  console.log('Price range filter is active');
}
Check if specific query exists
// 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 }
): DcuplGlobalQueryOptions

Parameters:

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:

Set pagination
list.catalog.query.applyOptions({
  start: 0,
  count: 20,
});
Set sorting
list.catalog.query.applyOptions({
  sort: { attributes: ['price', 'name'], order: ['DESC', 'ASC'] },
});
Set projection
list.catalog.query.applyOptions({
  projection: {
    name: true,
    price: true,
    category: true,
  },
});
Complete options
list.catalog.query.applyOptions({
  start: 0,
  count: 20,
  sort: { attributes: ['price'], order: ['DESC'] },
  projection: { name: true, price: true, category: true },
});
Update existing options (merge)
// 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' });
Replace all options
// Set new options, clearing any previously set options
list.catalog.query.applyOptions({ count: 50 }, { mode: 'set' });
Batch operations with skipProcessing
// 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 call

With 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 | null

Parameters:

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:

Get item by key
const item = list.catalog.query.one({ itemKey: 'p1' });
With projection
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:

Get specific items
const items = list.catalog.query.many({
  itemKeys: ['p1', 'p2', 'p3'],
});
With projection
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 }
): DcuplGlobalQueryOptions

Parameters:

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:

Add a condition
list.catalog.query.apply({ operator: 'eq', attribute: 'status', value: 'active' }, { mode: 'add' });
Replace the conditions on one attribute
list.catalog.query.apply({ operator: 'eq', attribute: 'status', value: 'active' }, { mode: 'set' });
Replace the whole query
list.catalog.query.apply(
  {
    groupKey: 'root',
    groupType: 'and',
    queries: [{ operator: 'eq', attribute: 'status', value: 'active' }],
  },
  { mode: 'set' },
);
Apply multiple conditions
list.catalog.query.apply(
  [
    { operator: 'eq', attribute: 'status', value: 'active' },
    { operator: 'gte', attribute: 'price', value: 100 },
  ],
  { mode: 'add' },
);
Apply a query group
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): DcuplGlobalQueryOptions

Parameters:

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:

Remove a condition
list.catalog.query.remove({ operator: 'eq', attribute: 'status', value: 'active' });
Remove by group key
list.catalog.query.remove({ groupKey: 'priceRange' });
Remove all conditions
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 }): DcuplGlobalQueryOptions

Parameters:

Parameter Type Required Description
options.skipProcessing boolean No Skip immediate query processing

Returns: DcuplGlobalQueryOptions - Empty query state

Examples:

Reset query
list.catalog.query.reset();
Reset without processing
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:

Execute current query
const items = list.catalog.query.execute();
Execute with options
const items = list.catalog.query.execute({
  start: 0,
  count: 20,
  sort: { attributes: ['price'], order: ['DESC'] },
});
Execute with additional conditions
// 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(): DcuplGlobalQueryOptions

Returns: DcuplGlobalQueryOptions - Current query configuration

Examples:

Get current query
const query = list.catalog.query.get();
console.log(query.queries);
Save and restore query state
const savedQuery = list.catalog.query.get();
// ... make changes ...
list.catalog.query.apply(savedQuery, { mode: 'set' }); // root group: replaces the whole query

Analytical 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.

See also