Docyrus Query Builder
Visual query builder for Docyrus data sources — filters, columns, sorting, calculations, formulas, pivots, and child queries.
Installation
pnpm dlx @docyrus/cli add @docyrus/ui-docyrus-query-builderpnpm add react-querybuildernpx shadcn@latest add badge button checkbox collapsible command input label popover radio-group scroll-area select switch table tabs toggle-group tooltipUsage
'use client';
import { useState } from 'react';
import {
DocyrusQueryBuilder,
type ISelectQueryParams
} from '@docyrus/ui/components/docyrus-query-builder';
export function MyQueryBuilder() {
const [query, setQuery] = useState<Partial<ISelectQueryParams>>({});
return (
<DocyrusQueryBuilder
value={query}
onChange={setQuery}
/>
);
}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.
import { useDocyrusClient } from '@docyrus/signin';
export function MyAuthedQueryBuilder() {
const client = useDocyrusClient();
const [query, setQuery] = useState<Partial<ISelectQueryParams>>({});
return (
<DocyrusQueryBuilder
client={client}
value={query}
onChange={setQuery}
/>
);
}Without a client (offline / preset data)
Skip the client and supply dataSources and fields yourself for offline editing and form-driven workflows.
<DocyrusQueryBuilder
value={query}
onChange={setQuery}
dataSources={myDataSources}
fields={myFields}
/>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.
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 (
<DocyrusDataSourceQueryBuilder
client={client}
value={query.query}
onChange={query.setQuery}
aiAssistantOpen={aiOpen}
onAiAssistantOpenChange={setAiOpen}
renderAiAssistant={({ open, onClose, selectedDataSource }) => (
<EditorAgent
agentId={agentId}
client={client}
user={user}
open={open}
onClose={onClose}
dataSourceId={selectedDataSource?.id ?? null}
clientTools={query.tools}
/>
)}
/>
);
}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:
- 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). - 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).
- 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.
Custom filter controls
The Filters section renders a QueryBuilderDocyrus. filterControlElements hands that builder a set of react-querybuilder control elements (merged over the Docyrus defaults) and filterContext travels alongside as react-querybuilder's context, so a host can replace the rule value editor without teaching this component anything about its domain — an automation editor offering Fixed value / Field / Expression modes that write #FIELD= / #FORMULA= markers, for example. Wrap QBValueEditor for the plain-value case so relation pickers, async options and between inputs keep working, and keep the object reference stable (module constant or useMemo) — a fresh object per render re-renders every rule.
import { QBValueEditor, type ValueEditorProps } from '@/components/docyrus/query-builder';
function RuntimeValueEditor(props: ValueEditorProps) {
const { inputSchema } = props.context as { inputSchema: JsonSchema };
return isMarker(props.value)
? <MarkerEditor {...props} schema={inputSchema} />
: <QBValueEditor {...props} />;
}
const FILTER_CONTROLS = { valueEditor: RuntimeValueEditor };
<DocyrusDataSourceQueryBuilder
value={query}
onChange={setQuery}
filterControlElements={FILTER_CONTROLS}
filterContext={{ inputSchema }} />Both props are no-ops when omitted.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
value | Partial<ISelectQueryParams> | required | Current query payload |
onChange | (value: Partial<ISelectQueryParams>) => 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 <DocyrusAgent> or <EditorAgent> 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. |
filterControlElements | Partial<ControlElementsProp<FullField, string>> | — | react-querybuilder control elements for the filter-rule builders (today: the Filters section). Merged over the Docyrus defaults — set only the keys you replace, e.g. { valueEditor }. Pass a stable reference. |
filterContext | unknown | — | Forwarded as react-querybuilder's context to the filterControlElements. |
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 <EditorAgent> — 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() — the hook result plus the host slots filterControlElements / filterContext |
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 |