.md

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.

query-context.ts
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 (for find)

See Operators Reference for all available operators.

query-condition.ts
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.

query-groups.ts
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)
nested-groups.ts
// 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
item-options.ts
// (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.

building-queries.ts
// 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.

in-action.ts
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);