Query overview
Understanding the dcupl query structure, groups, and conditions for building complex queries.
QueryExecutionContext
A query is a tree of conditions and groups of conditions. You run one directly with dcupl.query.execute(context), pass one as the seed query of dcupl.lists.create(), or build one step by step on a list with list.catalog.query (list.catalog.query.current() returns it). In every case the query is a QueryExecutionContext.
QueryExecutionContext is the root container for executing queries. It holds:
- modelKey - Target model to query
- queries - Array of conditions and groups (the query tree)
- groupType - How to combine queries:
'and'or'or' - Item options - Pagination, projection, sorting
Think of it as the complete specification for a query operation.
import type { QueryExecutionContext } from '@dcupl/common';
// The item type used by the examples on this page
type Product = { key: string; name: string; status: string; category: string; price: number };
// Basic structure
const context: QueryExecutionContext<Product> = {
modelKey: 'Product',
groupType: 'and', // Combine with AND (default)
queries: [
// QueryCondition or QueryConditionGroup items
],
// Optional: pagination, projection, sort
start: 0,
count: 50,
};QueryCondition
A QueryCondition is a single filter criterion:
- attribute - Property to filter on
- operator - How to compare (
eq,find,gte, etc.) - value - Value to compare against
- options - Optional:
transform,invert,arrayValueHandling,regexFlags(forfind)
See Operators Reference for all available operators.
import type { QueryCondition } from '@dcupl/common';
// Exact match
const exact: QueryCondition = {
attribute: 'status',
operator: 'eq',
value: 'active',
};
// Text search: '/term/' matches anywhere in the value,
// a bare string must match the whole value
const search: QueryCondition = {
attribute: 'name',
operator: 'find',
value: '/laptop/',
options: { regexFlags: 'i' }, // case-insensitive
};
// Numeric range
const minPrice: QueryCondition = {
attribute: 'price',
operator: 'gte',
value: 100,
};
// With options
const caseInsensitive: QueryCondition = {
attribute: 'category',
operator: 'eq',
value: 'electronics',
options: { transform: ['lowercase'] }, // compare lowercased values
};QueryConditionGroup
A QueryConditionGroup combines multiple conditions with boolean logic:
- groupType -
'and'or'or' - queries - Array of conditions (or nested groups)
AND group
All conditions must match. Use for narrowing results.
OR group
Any condition can match. Use for broadening results.
import type { QueryConditionGroup } from '@dcupl/common';
// AND group: active AND in stock
const andGroup: QueryConditionGroup = {
groupType: 'and',
queries: [
{ attribute: 'status', operator: 'eq', value: 'active' },
{ attribute: 'inStock', operator: 'eq', value: true },
],
};
// OR group: category is Electronics OR Computers
const orGroup: QueryConditionGroup = {
groupType: 'or',
queries: [
{ attribute: 'category', operator: 'eq', value: 'Electronics' },
{ attribute: 'category', operator: 'eq', value: 'Computers' },
],
};Nesting groups
Groups can contain other groups, enabling complex boolean expressions:
(A AND B) OR (C AND D)
The recursive structure allows arbitrary nesting depth for any boolean logic you need.
Structure:
QueryExecutionContext
└── queries[]
├── QueryCondition
└── QueryConditionGroup
└── queries[]
├── QueryCondition
└── QueryConditionGroup (recursive)
// Complex: (premium AND active) OR (featured AND sale)
// (Product: see the first example)
const context: QueryExecutionContext<Product> = {
modelKey: 'Product',
groupType: 'or', // Top level OR
queries: [
// First group: premium AND active
{
groupType: 'and',
queries: [
{ attribute: 'tier', operator: 'eq', value: 'premium' },
{ attribute: 'status', operator: 'eq', value: 'active' },
],
},
// Second group: featured AND sale
{
groupType: 'and',
queries: [
{ attribute: 'featured', operator: 'eq', value: true },
{ attribute: 'onSale', operator: 'eq', value: true },
],
},
],
};Item options
QueryExecutionContext includes the item options (ItemDataOptions in @dcupl/common):
| Option | Description |
|---|---|
start |
Skip N items (pagination offset) |
count |
Limit results to N items |
projection |
Select specific properties to return, e.g. { $: false, name: true } |
sort |
Order results: { attributes, order } with 'ASC' or 'DESC' per attribute |
// (Product: see the first example)
const context: QueryExecutionContext<Product> = {
modelKey: 'Product',
queries: [
{ attribute: 'status', operator: 'eq', value: 'active' },
],
// Pagination: page 2, 20 items per page
start: 20,
count: 20,
// Only return these fields (key is always included)
projection: { $: false, name: true, price: true },
// Sort by price descending
sort: { attributes: ['price'], order: ['DESC'] },
};Building queries
Real-world example: E-commerce product filter
Requirements:
- Active products only
- Category: Electronics OR Computers
- Price between $100 and $1000
- Sort by price, paginated
The context object fully describes this complex filter in a declarative, serializable format. Conditions placed directly in an 'and' group must all match, so the two price conditions form a range.
// dcupl: an initialized instance with a Product model (Product: see the first example)
const productFilter: QueryExecutionContext<Product> = {
modelKey: 'Product',
groupType: 'and',
queries: [
// Must be active
{ attribute: 'status', operator: 'eq', value: 'active' },
// Category: Electronics OR Computers
{
groupType: 'or',
queries: [
{ attribute: 'category', operator: 'eq', value: 'Electronics' },
{ attribute: 'category', operator: 'eq', value: 'Computers' },
],
},
// Price range: $100 - $1000
{ attribute: 'price', operator: 'gte', value: 100 },
{ attribute: 'price', operator: 'lte', value: 1000 },
],
// Sort and paginate
sort: { attributes: ['price'], order: ['ASC'] },
start: 0,
count: 24,
};
// Execute against the model
const results = dcupl.query.execute(productFilter);In action
On a list you rarely write the context yourself: list.catalog.query keeps the tree, and addCondition, addGroup, setCondition and setGroup change it. Methods & Operators explains each method; the Catalog API lists them all.
const list = dcupl.lists.create({ modelKey: 'Product' });
list.catalog.query.addCondition({ attribute: 'status', operator: 'eq', value: 'active' });
// The list keeps its query as a QueryExecutionContext
const context = list.catalog.query.current();
// { modelKey: 'Product', groupKey: 'root', groupType: 'and',
// queries: [{ groupKey: 'status', groupType: 'or', queries: [{ attribute: 'status', ... }] }] }
// Run the same context without a list
const sameItems = dcupl.query.execute(context);