# DSQL Editor
URL: /docs/web/docyrus/dsql-editor
A two-pane workbench for writing and running DSQL (security-scoped SQL) over Docyrus data sources, with a result grid and an embedded AI agent.
**DSQL** (Docyrus Structured Query Language) is a security-scoped SQL surface restricted to the
tenant's data sources. The `DsqlEditor` is a two-pane workbench: a top **DSQL Query** editor (SQL
syntax highlighting, Format + Run) and a bottom **Result** panel that renders the returned rows in a
paginated data grid. It carries the same embedded **AI agent** panel as the JSONata / Handlebars /
HTML Template editors.
Queries reference tables as `appSlug.dataSourceSlug` (e.g. `base.contact`), **not** raw schemas —
the backend rewrites them. Statements are SELECT-only / read-only and run through
`PUT /v1/dsql/query`.
**Demo:**
```tsx
'use client';
import { Button } from '@docyrus/ui/primitives/ui/button';
export function DsqlEditorDemo() {
return (
{''} requires an authenticated{' '}
RestApiClient and executes queries via{' '}
PUT /v1/dsql/query against your Docyrus tenant — wire it up
to see it in action. Tables are referenced as{' '}
app.dataSource (e.g. base.contact). Results
render in a paginated data grid, and an embedded AI agent can author
DSQL for you.
);
}
```
## Installation
```bash
pnpm dlx @docyrus/cli add @docyrus/ui-dsql-editor
```
**Dependencies:**
- [sql-formatter](https://www.npmjs.com/package/sql-formatter)
- [@uiw/react-codemirror](https://www.npmjs.com/package/@uiw/react-codemirror)
- [@uiw/codemirror-extensions-langs](https://www.npmjs.com/package/@uiw/codemirror-extensions-langs)
- [lucide-react](https://www.npmjs.com/package/lucide-react)
## Usage
```tsx
import { useState } from 'react';
import { useDocyrusAuth } from '@docyrus/signin';
import { DsqlEditor, useApplyDsql } from '@docyrus/ui/components/dsql-editor';
import { EditorAgentDrawer } from '@/components/editor-agent-drawer';
const DSQL_AGENT_ID = '019ecf41-2c43-7b0e-8d2d-71f218952e14';
function MyDsqlWorkbench() {
const { client } = useDocyrusAuth();
const dsql = useApplyDsql('select id, email from base.contact limit 100');
const [aiOpen, setAiOpen] = useState(false);
if (!client) return null;
return (
(
`Current DSQL query:\n${query || '(empty)'}`}
/>
)}
/>
);
}
```
For tests or mock data, supply `onRun` instead of `client`:
```tsx
({
rows: [{ id: 1, email: 'a@b.com' }],
columns: [{ name: 'id', type: 'number' }, { name: 'email', type: 'string' }],
rowCount: 1
})} />
```
## Schema-aware autocomplete
Pass a `schema` namespace to get table + column completion: `from base.` completes data-source
names and `base.contact.` completes columns. The `useDsqlSchema` hook fetches it from
`GET /v1/dsql/schema/apps/:appSlug` (returns compact `create table appSlug.dataSourceSlug ( … )`
DDL per data source), parses it, and synthesizes the referenced `tenant.user` / `tenant.enum` system
tables. The same fetched DDL is ideal to feed into the agent's `editorContext` so it authors valid
DSQL.
```tsx
import { DsqlEditor, useApplyDsql, useDsqlSchema } from '@docyrus/ui/components/dsql-editor';
const dsql = useApplyDsql();
const { schema, tables } = useDsqlSchema(client, ['base']);
;
```
Building blocks are exported for custom wiring: `fetchDsqlAppSchema(client, appSlug)`,
`fetchDsqlSchemaNamespace(client, apps)`, `dsqlSchemasToNamespace(tables)`, and
`parseDsqlColumns(ddl)`. You can also pass a hand-built `SQLNamespace` directly to `schema` (or to
the standalone `DsqlCodeEditor` / `sqlExtensions({ schema })`).
## Auto-run when the agent writes a query
`useApplyDsql` takes an `onApply(query)` callback that fires right after the agent's `applyDsqlQuery`
tool writes a (formatted) query into the editor. Combine it with the imperative `ref` to close the
agent panel and run the query in one step:
```tsx
const editorRef = useRef(null);
const dsql = useApplyDsql('', {
onApply: (query) => {
setAiOpen(false); // close the agent panel
editorRef.current?.run(query); // run immediately (pass query to avoid stale state)
}
});
;
```
The `DsqlEditorHandle` exposes `run(query?)` (runs the current text, or an explicit query) and
`getQuery()`.
## Row caps
The backend returns a single page — there is no server-side pagination. The effective row limit is
`min(yourLimit ?? 100, maxLimit)`, where `maxLimit` is **1000** for delegated user sessions and
**100** for API / client-credentials tokens. The editor surfaces the cap next to the result count so
a truncated result isn't mistaken for the full set. Add `LIMIT` / filters to narrow large queries.
## API Reference
| Prop | Type | Default |
|------|------|---------|
| `client` | `DsqlEditorClient \| null` | — |
| `onRun` | `(query: string) => Promise` | — |
| `query` | `string` | — |
| `defaultQuery` | `string` | — |
| `onQueryChange` | `(next: string) => void` | — |
| `onResult` | `(result: DsqlRunResult) => void` | — |
| `onError` | `(message: string) => void` | — |
| `schema` | `SQLNamespace` | — |
| `resultPageSize` | `number` | `25` |
| `height` | `number \| string` | `'100%'` |
| `className` | `string` | — |
| `aiAssistantOpen` | `boolean` | — |
| `onAiAssistantOpenChange` | `(open: boolean) => void` | — |
| `renderAiAssistant` | `(ctx: DsqlAiAssistantRenderProps) => ReactNode` | — |
### DsqlCodeEditor
The standalone SQL editor pane (embed it in form fields or automation-node configs).
| Prop | Type | Default |
|------|------|---------|
| `value` | `string` | — |
| `onChange` | `(value: string) => void` | — |
| `readOnly` | `boolean` | `false` |
| `placeholder` | `string` | — |
| `autoFocus` | `boolean` | `false` |
| `minHeight` | `string` | `'2.5rem'` |
| `maxHeight` | `string` | `'12rem'` |
| `height` | `string` | — |
| `className` | `string` | — |
| `lineNumbers` | `boolean` | `true` |
| `schema` | `SQLNamespace` | — |
| `basicSetup` | `BasicSetupOptions` | — |
| `extensions` | `Extension[]` | — |
## Components
| Component | Description |
|-----------|-------------|
| `DsqlEditor` | Two-pane query + result workbench |
| `DsqlCodeEditor` | Standalone SQL CodeMirror pane |
| `useApplyDsql` | Owns query state + the `applyDsqlQuery` agent client tool |
| `useDsqlSchema` | Fetches + builds the autocomplete `SQLNamespace` for given apps |
| `runDsqlQuery` | `PUT /v1/dsql/query` caller |
| `normalizeDsqlResponse` | Parses the API envelope into a `DsqlRunResult` |
| `extractDsqlError` | Pulls a human message out of a rejected request |
| `fetchDsqlAppSchema` / `fetchDsqlSchemaNamespace` | Fetch DSQL schema from the schema endpoints |
| `dsqlSchemasToNamespace` / `parseDsqlColumns` | Build a `SQLNamespace` from DDL schema text |
| `formatSql` | PostgreSQL-dialect formatter (never throws) |
| `sqlExtensions` | CodeMirror SQL highlight + completion extensions factory |
## Type Exports
| Type | Description |
|------|-------------|
| `DsqlEditorProps` | Props for `DsqlEditor` |
| `DsqlCodeEditorProps` | Props for `DsqlCodeEditor` |
| `DsqlEditorClient` | Minimal `{ put }` client contract |
| `DsqlRunResult` | `{ rows, columns, rowCount, durationMs? }` |
| `DsqlRunState` | Discriminated result-pane state |
| `DsqlColumnMeta` | `{ name, type?, label? }` resolved column |
| `DsqlTableSchema` | Raw schema row from the schema endpoints (`{ appSlug, slug, schema, … }`) |
| `DsqlAiAssistantRenderProps` | Context passed to `renderAiAssistant` |
## Type Reference
### DsqlRunResult
| Field | Type | Description |
|-------|------|-------------|
| `rows` | `Array>` | Returned rows |
| `columns` | `DsqlColumnMeta[]` | Columns inferred from row keys (SELECT order) |
| `rowCount` | `number` | Row count from the server (`meta.count`) |
| `durationMs` | `number` | Client-measured round-trip duration |
### DsqlColumnMeta
| Field | Type | Description |
|-------|------|-------------|
| `name` | `string` | Column key |
| `type` | `'number' \| 'boolean' \| 'date' \| 'datetime' \| 'uuid' \| 'string'` | Coarse inferred type (drives the cell variant) |
| `label` | `string` | Optional human label (defaults to `name`) |