# Docyrus Query Builder URL: /docs/web/docyrus/docyrus-query-builder Visual query builder for Docyrus data sources — filters, columns, sorting, calculations, formulas, pivots, and child queries. **Demo:** ```tsx 'use client'; import { useState } from 'react'; import { type EnumOption, type IField } from '@docyrus/ui/components/form-fields'; import { useForm } from '@tanstack/react-form'; import { CalendarClock, FileBarChart, Send } from 'lucide-react'; import { Button } from '@docyrus/ui/primitives/ui/button'; import { Switch } from '@docyrus/ui/primitives/ui/switch'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@docyrus/ui/primitives/ui/tabs'; import { cn } from '@docyrus/ui/primitives/lib/utils'; import { Field, FieldLabel } from '@docyrus/ui/primitives/ui/field'; import { CheckboxFormField } from '@docyrus/ui/components/form-fields/checkbox-form-field'; import { SelectFormField } from '@docyrus/ui/components/form-fields/select-form-field'; import { TextFormField } from '@docyrus/ui/components/form-fields/text-form-field'; import { TextareaFormField } from '@docyrus/ui/components/form-fields/textarea-form-field'; import { DocyrusDataSourceQueryBuilder, type IDataSourceReference, type ISelectQueryParams } from '@docyrus/ui/components/docyrus-query-builder'; const tasksFields = [ { id: 't-1', name: 'Title', slug: 'title', type: 'field-text' as const }, { id: 't-2', name: 'Status', slug: 'status', type: 'field-status' as const }, { id: 't-3', name: 'Owner', slug: 'owner', type: 'field-userSelect' as const }, { id: 't-4', name: 'Due Date', slug: 'due_date', type: 'field-date' as const }, { id: 't-5', name: 'Priority', slug: 'priority', type: 'field-select' as const }, { id: 't-6', name: 'Created At', slug: 'created_at', type: 'field-dateTime' as const }, { id: 't-7', name: 'Done', slug: 'is_done', type: 'field-checkbox' as const } ]; const dealsFields = [ { id: 'd-1', name: 'Name', slug: 'name', type: 'field-text' as const }, { id: 'd-2', name: 'Amount', slug: 'amount', type: 'field-money' as const }, { id: 'd-3', name: 'Stage', slug: 'stage', type: 'field-status' as const }, { id: 'd-4', name: 'Account', slug: 'account', type: 'field-relation' as const }, { id: 'd-5', name: 'Close Date', slug: 'close_date', type: 'field-date' as const } ]; const dataSources: IDataSourceReference[] = [ { id: 'crm.tasks', name: 'Tasks', title: 'Tasks', description: 'Things to get done.', slug: 'tasks', appSlug: 'crm', appName: 'CRM', fullSlug: 'crm.tasks', type: 'standard', fields: tasksFields }, { id: 'crm.deals', name: 'Deals', title: 'Deals', description: 'Sales pipeline records.', slug: 'deals', appSlug: 'crm', appName: 'CRM', fullSlug: 'crm.deals', type: 'standard', fields: dealsFields } ]; const initialQuery: Partial
)}
Submitted value is shown to the right ↦
); } /* ────────────────────────────────────────────────────────────────────────── */ /* Root */ /* ────────────────────────────────────────────────────────────────────────── */ export function DocyrusQueryBuilderDemo() { return ( ); } ``` ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/ui-docyrus-query-builder ``` **Dependencies:** - [class-variance-authority](https://www.npmjs.com/package/class-variance-authority) - [lucide-react](https://www.npmjs.com/package/lucide-react) - [react-querybuilder](https://www.npmjs.com/package/react-querybuilder) ## Usage ```tsx 'use client'; import { useState } from 'react'; import { DocyrusQueryBuilder, type ISelectQueryParams } from '@docyrus/ui/components/docyrus-query-builder'; export function MyQueryBuilder() { const [query, setQuery] = useState>({}); return ( ); } ``` ### With a Docyrus API client Pass an authenticated `RestApiClient` from `@docyrus/api-client` (or any object that implements the `DocyrusQueryBuilderClient` shape) to enable data-source discovery and query previews. ```tsx import { useDocyrusClient } from '@docyrus/signin'; export function MyAuthedQueryBuilder() { const client = useDocyrusClient(); const [query, setQuery] = useState>({}); return ( ); } ``` ### Without a client (offline / preset data) Skip the client and supply `dataSources` and `fields` yourself for offline editing and form-driven workflows. ```tsx ``` ## EditorAgent integration Pair `useApplyQuery` with `renderAiAssistant` to let an LLM author Docyrus query specs. The hook owns the controlled `value` and ships an `applyQuery` tool that the agent calls to commit a new query spec — the tool guards against primitives / arrays / null so a malformed payload cannot crash the builder. ```tsx import { EditorAgent } from '@docyrus/ui/components/editor-agent'; import { DocyrusDataSourceQueryBuilder, useApplyQuery } from '@docyrus/ui/components/docyrus-query-builder'; export function QueryBuilderPlayground({ client, user, agentId }) { const query = useApplyQuery(); const [aiOpen, setAiOpen] = useState(false); return ( ( )} /> ); } ``` The matching backend agent must register a tool named `applyQuery` whose input schema mirrors `{ value: object (required), explanation?: string }`. ## Columns — current + parent data sources The **Columns** section is a single grouped, multi-select tree (no separate "selected columns" pane). In both the current and parent groups, **user** fields (`field-userSelect` / `field-userMultiSelect`) and **enum** fields (`field-select`, `field-radioGroup`, `field-enum`, `field-status`, `field-multiSelect`, `field-tagSelect`) are expandable into a fixed sub-field catalog — user → `id, firstname, lastname, email, photo`; enum → `id, autonumber_id, name, slug, icon, color`. A checked sub-field nests at the correct depth, e.g. `status(id, name)` or `company(owner(firstname, email))` (parent). The header carries a **Parent Data Sources** switch (its state lives in the hook, so it survives switching to Run & Preview / JSON and back). It pulls fields from the data sources referenced by the current data source's `field-relation` columns and nests a checked field under its relation column, e.g. `company(title, industry)`. It requires a `client`; fields come from `GET /v1/dev/data-sources/:id/fields?expand=parent` (the `parent` expansion returns each parent's full field list keyed by data source id). The same wiring is exposed on the hook as `fetchRelatedFields(['parent', 'child'])` for custom UIs. ## Child Queries The **Child Queries** panel (enabled via `enableChildQueries`) fetches related records as nested arrays, one per child data source. It uses the `child` expansion of the same endpoint (`GET /v1/dev/data-sources/:id/fields?expand=child`) to discover the data sources whose own `field-relation` points back to the current one: 1. Pick a child data source from the **Add child data source** dropdown — this creates an entry whose **From** and **Using** are auto-filled and read-only (`from` = the child data source as a `{appSlug}_{slug}` reference, e.g. `base_callcenter_call`; `using` = its back-reference relation field slug). 2. Select the columns to return via a multi-select tree-view of the child data source's own fields (with the same user / enum sub-field expansion as the Columns section). 3. Optionally set **Order By** (a field dropdown + asc/desc direction) and **Limit**. `value.childQueries` is an **array** of `{ alias, from, using, columns, orderBy?, limit? }`. The `alias` (the result key the child rows are returned under) defaults to the child data source slug, and the editor automatically keeps it listed in the parent `value.columns` — the items endpoint **drops the child block unless its alias is also a parent column** (so the emitted request is `?columns=…,alias&childQueries=[{ alias, from, using, … }]`). Removing an entry strips its alias from the columns again. Child data sources load when the panel opens (it flips `relatedExpand.child`); the fetch is de-duped per `(dataSource, expand)` signature. ## API Reference | Prop | Type | Default | Description | |------|------|---------|-------------| | `value` | `Partial` | required | Current query payload | | `onChange` | `(value: Partial) => void` | required | Called whenever the query is edited | | `client` | `DocyrusQueryBuilderClient \| null` | `null` | Authenticated Docyrus API client. Enables data-source discovery and query previews. | | `fields` | `IField[]` | `[]` | Available fields for the selected data source. Overridden by the resolved data source when set. | | `dataSources` | `IDataSourceReference[]` | `[]` | Pre-loaded data sources. Used as a fallback when no client is provided. | | `locale` | `UiI18nLocale` | `'en'` | UI locale token forwarded to translation-aware sub-components | | `variant` | `'default' \| 'bordered' \| 'compact'` | `'default'` | Visual variant | | `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | Component size | | `className` | `string` | — | Additional CSS class | | `defaultSection` | `DSQBSection` | `'dataSource'` | Initially active configure section | | `lockDataSource` | `boolean` | `false` | Lock the builder to the data source supplied via `value` (`dataSourceId` / `dataSourceFullSlug`). Hides the "Select Data Source" step and the clear button so the user can't switch data sources. | | `enableFormulas` | `boolean` | `true` | Show the formula editor section | | `enablePivot` | `boolean` | `true` | Show the pivot editor section | | `enableChildQueries` | `boolean` | `true` | Show the child queries editor section | | `enableCalculations` | `boolean` | `true` | Show the calculations editor section | | `aiAssistantOpen` | `boolean` | — | Controlled open state for the AI Assistant drawer. When provided the builder stops managing the open state internally — pair with `onAiAssistantOpenChange`. | | `onAiAssistantOpenChange` | `(open: boolean) => void` | — | Fired when the AI Assistant toolbar button toggles the drawer. | | `renderAiAssistant` | `(ctx: IQueryBuilderAiAssistantRenderContext) => ReactNode` | — | Mounts a custom AI Assistant drawer body. When set, the toolbar shows a Bot toggle that opens/closes the drawer; this render fn supplies the body (typically a `` or `` wrapper). The context exposes the live `value` payload and the currently-`selectedDataSource` so the slot can wire the agent's `dataSourceId` without re-deriving it. | | `aiAssistantWidth` | `number` | `380` | Width in pixels the drawer animates to when open. | ## Components | Component | Description | |-----------|-------------| | `DocyrusDataSourceQueryBuilder` | Top-level component — renders the Configure / Run & Preview / JSON tabs | | `useApplyQuery` | Hook that owns `value` state and exposes the `applyQuery` client-side tool for `` — the tool replaces the builder's query spec with the agent's draft. Refuses primitives / arrays / null so a bad LLM call cannot crash the builder. | | `DSQBDataSourceSelector` | Data source picker pane | | `DSQBColumnsEditor` | Column selection + ordering pane | | `DSQBFiltersEditor` | Filter rule builder pane | | `DSQBOrderByEditor` | Sort builder pane | | `DSQBPaginationEditor` | Limit + offset editor | | `DSQBCalculationsEditor` | Aggregation / calculation rules editor | | `DSQBFormulaEditor` | Formula composer (block AST) | | `DSQBChildQueriesEditor` | Child queries editor | | `DSQBPivotEditor` | Pivot matrix editor | | `DSQBSettingsEditor` | Query mode + expand toggles | | `DSQBJsonPreview` | Read-only JSON view of the current query | | `DSQBRunPreview` | Run results table | | `DSQBFieldSelector` | Reusable field picker primitive | ## Type Exports | Type | Description | |------|-------------| | `DocyrusQueryBuilderProps` | Top-level component props | | `DocyrusQueryBuilderClient` | Minimal HTTP client shape consumed by the component | | `DSQBContextValue` | Shape of the internal context exposed by `useDSQB()` | | `DSQBPrimaryTab` | `'configure' \| 'preview' \| 'json'` | | `DSQBSection` | Configure section identifiers (data source, columns, filters, …) | | `DSQBPreviewState` | Run-preview state (rows, columns, status, error) | | `ISelectQueryParams` | Full query payload sent to the items endpoint | | `IQueryFilterGroup` / `IQueryFilterRule` / `QueryFilterType` | Filter tree primitives | | `ISelectQueryOrderBy` | Sort specifier | | `ISelectQueryCalculationRule` / `AggregateFunction` / `NumberType` | Calculation rule primitives | | `IQueryFormula` / `IQueryFormulaBlock` / `BlockKind` | Formula AST primitives | | `IQueryChildQueryParams` | Child query specifier | | `ISelectPivot` / `ISelectPivotMatrixQuery` / `DateRangeInterval` | Pivot primitives | | `IDataSourceReference` | Discovered or supplied data source descriptor | | `DSQBRelatedFields` / `DSQBParentFieldGroup` / `DSQBChildFieldGroup` | Parent / child field groups returned by `fetchRelatedFields()` | | `DSQBRelatedExpand` | `{ parent, child }` expansion flags (`relatedExpand` / `setRelatedExpand` on the context) | | `IField` / `IFieldType` | Docyrus field descriptor + supported field-type tokens | | `QueryMode` / `ExpandType` | Execution flag tokens | | `IQueryValidationError` | Validation error shape returned by `validateQuery()` | | `ParsedColumn` | Parsed column descriptor returned by `parseColumnString()` | | `UiI18nLocale` | Supported locale tokens | | `IQueryBuilderAiAssistantRenderContext` | Argument passed to `renderAiAssistant` — `{ open, width, onClose, value, selectedDataSource }`. | | `IUseApplyQueryResult` | Return shape of `useApplyQuery` — `{ query, setQuery, tools }`. | ## Helpers | Export | Description | |--------|-------------| | `parseColumnString(columns)` | Parse a Docyrus columns string into `ParsedColumn[]` | | `serializeColumns(columns)` | Inverse of `parseColumnString` | | `normalizeOrderBy(orderBy)` | Coerce any orderBy value into an array | | `serializeOrderBy(orderBy)` | Serialize orderBy into the API string form | | `buildDefaultColumns(fields)` | Build a default columns string from a field list | | `cleanPayload(query)` | Strip null / empty values from a query payload before sending | | `getFieldTypeCategory(type)` | Map an `IFieldType` to its filter category | | `getOperatorsForFieldType(type)` | Get the operator list applicable to a field type | | `validateQuery(query)` | Run static validation, returning `IQueryValidationError[]` | | `countFilterRules(filters)` | Count leaves in a filter tree | | `createBlock(kind)` | Create a default formula block | | `updateBlockAt(root, path, next)` | Immutably update a formula block at a path | | `removeBlockAt(root, path)` | Immutably remove a formula block at a path | | `insertBlockAt(root, path, next)` | Immutably insert a formula block at a path | | `getBlockLabel(block)` | Render a human-readable label for a formula block |