@docyrus/docyrus 0.0.30 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-loader.js +36 -25
- package/agent-loader.js.map +4 -4
- package/main.js +4 -4
- package/main.js.map +1 -1
- package/package.json +3 -3
- package/resources/pi-agent/extensions/docyrus-web-browser.ts +31 -0
- package/resources/pi-agent/shared/docyrusWebBrowserProtocol.ts +169 -0
- package/resources/pi-agent/skills/diffity-diff/SKILL.md +1 -1
- package/resources/pi-agent/skills/diffity-resolve/SKILL.md +4 -4
- package/resources/pi-agent/skills/diffity-review/SKILL.md +5 -4
- package/resources/pi-agent/skills/docyrus-api-dev/SKILL.md +197 -0
- package/resources/pi-agent/skills/docyrus-api-dev/references/acl-endpoints-frontend.md +295 -0
- package/resources/pi-agent/skills/docyrus-api-dev/references/api-client.md +349 -0
- package/resources/pi-agent/skills/docyrus-api-dev/references/authentication.md +298 -0
- package/resources/pi-agent/skills/docyrus-api-dev/references/data-source-query-guide.md +2063 -0
- package/resources/pi-agent/skills/docyrus-api-dev/references/formula-design-guide-llm.md +312 -0
- package/resources/pi-agent/skills/docyrus-api-dev/references/query-and-formulas.md +592 -0
- package/resources/pi-agent/skills/docyrus-app-dev-react/SKILL.md +361 -0
- package/resources/pi-agent/skills/docyrus-app-dev-react/references/README.md +29 -0
- package/resources/pi-agent/skills/docyrus-app-dev-react/references/api-client-and-auth.md +326 -0
- package/resources/pi-agent/skills/docyrus-app-dev-react/references/collections-and-patterns.md +353 -0
- package/resources/pi-agent/skills/docyrus-app-dev-react/references/component-selection-guide.md +619 -0
- package/resources/pi-agent/skills/docyrus-app-dev-react/references/icon-usage-guide.md +463 -0
- package/resources/pi-agent/skills/docyrus-app-dev-react/references/preferred-components-catalog.md +242 -0
- package/resources/pi-agent/skills/docyrus-platform/SKILL.md +2 -2
- package/resources/pi-agent/skills/docyrus-platform/references/auth-and-multi-tenancy.md +9 -1
- package/resources/pi-agent/skills/docyrus-platform/references/developer-tools.md +3 -2
- package/server-loader.js +328 -87
- package/server-loader.js.map +4 -4
- package/resources/pi-agent/extensions/multi-edit.ts +0 -835
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
# Docyrus Query & Formula Guide
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Query Payload Structure](#query-payload-structure)
|
|
6
|
+
2. [Columns](#columns)
|
|
7
|
+
3. [Filters](#filters)
|
|
8
|
+
4. [Filter Operators Reference](#filter-operators-reference)
|
|
9
|
+
5. [Order By](#order-by)
|
|
10
|
+
6. [Pagination](#pagination)
|
|
11
|
+
7. [Calculations (Aggregations)](#calculations)
|
|
12
|
+
8. [Formulas — Simple](#formulas--simple)
|
|
13
|
+
9. [Formulas — Block (AST)](#formulas--block-ast)
|
|
14
|
+
10. [Formulas — Subquery](#formulas--subquery)
|
|
15
|
+
11. [Block Kinds Reference](#block-kinds-reference)
|
|
16
|
+
12. [Child Queries](#child-queries)
|
|
17
|
+
13. [Pivot](#pivot)
|
|
18
|
+
14. [Expand](#expand)
|
|
19
|
+
15. [Allowed Functions](#allowed-functions)
|
|
20
|
+
16. [Allowed Cast Types](#allowed-cast-types)
|
|
21
|
+
17. [Complete Examples](#complete-examples)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Query Payload Structure
|
|
26
|
+
|
|
27
|
+
Data source items are fetched via GET with a query payload:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
GET /v1/apps/{appSlug}/data-sources/{slug}/items
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The payload (`ZodSelectQueryPayload`) supports:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
interface ISelectQueryParams {
|
|
37
|
+
columns?: string | null // comma-separated column names
|
|
38
|
+
filters?: IQueryFilterGroup | null // nested AND/OR filter groups
|
|
39
|
+
filterKeyword?: string | null // full-text search
|
|
40
|
+
orderBy?: string | object | object[] // sorting
|
|
41
|
+
limit?: number // default: 100
|
|
42
|
+
offset?: number // default: 0
|
|
43
|
+
fullCount?: boolean // return total count
|
|
44
|
+
calculations?: ICalculationRule[] | null // aggregations
|
|
45
|
+
formulas?: Record<string, IFormula> | null // computed virtual columns
|
|
46
|
+
childQueries?: Record<string, IChildQuery> | null
|
|
47
|
+
pivot?: { matrix, hideEmptyRows?, orderBy?, limit? } | null
|
|
48
|
+
expand?: string[] | null // expand relation/user/enum fields
|
|
49
|
+
expandTypes?: ('user' | 'enum' | 'relation')[] | null
|
|
50
|
+
distinctColumns?: string[] | null
|
|
51
|
+
queryMode?: 'OLTP' | 'OLAP' | 'EXPORT'
|
|
52
|
+
recordId?: string | null // fetch single record by ID
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Critical**: Always send `columns`. Without it, only `id` is returned.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Columns
|
|
61
|
+
|
|
62
|
+
**Type**: comma-separated string (server) or array (client interceptor converts).
|
|
63
|
+
|
|
64
|
+
### Basic Selection
|
|
65
|
+
```
|
|
66
|
+
"columns": "task_name, created_on, record_owner"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Aliasing with `:`
|
|
70
|
+
```
|
|
71
|
+
"columns": "ra:related_account"
|
|
72
|
+
→ { "ra": { "id": "uuid", "name": "account name" } }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Relation Expansion with `()`
|
|
76
|
+
```
|
|
77
|
+
"columns": "task_name, related_account(name:account_name, phone:account_phone)"
|
|
78
|
+
→ { "task_name": "...", "related_account": { "name": "...", "phone": "..." } }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Spread with `...` (flatten to root)
|
|
82
|
+
```
|
|
83
|
+
"columns": "task_name, ...related_account(account_name, phone:account_phone)"
|
|
84
|
+
→ { "task_name": "...", "account_name": "...", "phone": "..." }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Functions with `@`
|
|
88
|
+
```
|
|
89
|
+
"columns": "...related_account(an:account_name@upper)"
|
|
90
|
+
→ { "an": "ACCOUNT NAME" }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Date Grouping Formulas
|
|
94
|
+
|
|
95
|
+
| Formula | Description |
|
|
96
|
+
|---------|-------------|
|
|
97
|
+
| `hours_of_today@field` | Group by hour for today |
|
|
98
|
+
| `days_of_week@field` | Group by day for current week |
|
|
99
|
+
| `days_of_month@field` | Group by day for current month |
|
|
100
|
+
| `weeks_of_month@field` | Group by week for current month |
|
|
101
|
+
| `months_of_year@field` | Group by month (YYYY-MM) |
|
|
102
|
+
| `quarters_of_year@field` | Group by quarter (YYYY-Q) |
|
|
103
|
+
|
|
104
|
+
### to_char Formatting
|
|
105
|
+
```
|
|
106
|
+
"columns": "day:to_char[DD/MM/YYYY]@created_on"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Filters
|
|
112
|
+
|
|
113
|
+
Recursive group structure:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
interface IQueryFilterGroup {
|
|
117
|
+
rules: (IQueryFilterRule | IQueryFilterGroup)[]
|
|
118
|
+
combinator?: 'and' | 'or' // default: 'and'
|
|
119
|
+
not?: boolean // negate entire group
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface IQueryFilterRule {
|
|
123
|
+
field?: string
|
|
124
|
+
operator: string
|
|
125
|
+
value?: any
|
|
126
|
+
filterType?: string | null // NUMERIC, ALPHA, BOOL, DATE, DATETIME, RELATION, OWNER, etc.
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Basic AND
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"filters": {
|
|
134
|
+
"combinator": "and",
|
|
135
|
+
"rules": [
|
|
136
|
+
{ "field": "task_status", "operator": "=", "value": 1 },
|
|
137
|
+
{ "field": "priority", "operator": ">=", "value": 3 }
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Nested AND + OR
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"filters": {
|
|
147
|
+
"combinator": "and",
|
|
148
|
+
"rules": [
|
|
149
|
+
{ "field": "created_on", "operator": "between", "value": ["2025-10-01", "2025-11-01"] },
|
|
150
|
+
{
|
|
151
|
+
"combinator": "or",
|
|
152
|
+
"rules": [
|
|
153
|
+
{ "field": "email", "operator": "empty" },
|
|
154
|
+
{ "field": "phone", "operator": "not empty" }
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Filter by Related Record's Field
|
|
163
|
+
```json
|
|
164
|
+
{ "field": "rel_client/account_status", "operator": "=", "value": 2 }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Negated Group
|
|
168
|
+
```json
|
|
169
|
+
{ "combinator": "and", "not": true, "rules": [{ "field": "status", "operator": "=", "value": "archived" }] }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Filter Operators Reference
|
|
175
|
+
|
|
176
|
+
### Comparison
|
|
177
|
+
`=`, `!=`, `<>`, `>`, `<`, `>=`, `<=`, `between` (value: `[min, max]`)
|
|
178
|
+
|
|
179
|
+
### Text
|
|
180
|
+
`like` (with `%` wildcards), `not like`, `starts with`, `ends with`
|
|
181
|
+
|
|
182
|
+
### Collection
|
|
183
|
+
`in` (array), `not in` (array), `exists`, `contains any` (array), `contains all` (array), `not contains`
|
|
184
|
+
|
|
185
|
+
### Null/Empty
|
|
186
|
+
`is`, `is not`, `empty`, `not empty`, `null`, `not null`
|
|
187
|
+
|
|
188
|
+
### Boolean
|
|
189
|
+
`true`, `false`
|
|
190
|
+
|
|
191
|
+
### User-Related (no value)
|
|
192
|
+
`active_user`, `not_active_user`, `in_active_user_scope`, `not_in_active_user_scope`, `in_role`, `not_in_role`, `in_team`, `not_in_team`, `in_active_user_team`, `not_in_active_user_team`, `in_unit`, `not_in_unit`, `in_sub_unit`, `not_in_sub_unit`, `shared_to_me`, `contains_active_user`, `not_contains_active_user`, `contains_member_of_active_user_team`
|
|
193
|
+
|
|
194
|
+
### Date Shortcuts (no value)
|
|
195
|
+
`today`, `tomorrow`, `yesterday`, `last_7_days`, `last_15_days`, `last_30_days`, `last_60_days`, `last_90_days`, `last_120_days`, `next_7_days`, `next_15_days`, `next_30_days`, `next_60_days`, `next_90_days`, `next_120_days`, `last_week`, `this_week`, `next_week`, `last_month`, `this_month`, `next_month`, `before_today`, `after_today`, `last_year`, `this_year`, `next_year`, `first_quarter`, `second_quarter`, `third_quarter`, `fourth_quarter`, `last_3_months`, `last_6_months`
|
|
196
|
+
|
|
197
|
+
### Dynamic Date (value = number)
|
|
198
|
+
`x_days_ago`, `x_days_later`, `before_last_x_days`, `in_last_x_days`, `after_last_x_days`, `in_next_x_days`
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Order By
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
// String
|
|
206
|
+
{ "orderBy": "created_on DESC" }
|
|
207
|
+
|
|
208
|
+
// Multi-column string
|
|
209
|
+
{ "orderBy": "firstname ASC, lastname DESC" }
|
|
210
|
+
|
|
211
|
+
// Object
|
|
212
|
+
{ "orderBy": { "field": "created_on", "direction": "desc" } }
|
|
213
|
+
|
|
214
|
+
// Array
|
|
215
|
+
{ "orderBy": [{ "field": "status", "direction": "asc" }, { "field": "created_on", "direction": "desc" }] }
|
|
216
|
+
|
|
217
|
+
// Related field
|
|
218
|
+
{ "orderBy": "relation_field(field_name DESC), id ASC" }
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Pagination
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{ "limit": 25, "offset": 50, "fullCount": true }
|
|
227
|
+
```
|
|
228
|
+
Returns records 51–75. `fullCount: true` adds total count to response.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Calculations
|
|
233
|
+
|
|
234
|
+
Aggregations. Selected `columns` become GROUP BY fields.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
interface ICalculationRule {
|
|
238
|
+
func: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'jsonb_agg' | 'json_agg' | 'array_agg'
|
|
239
|
+
field: string // 'id' for count, actual field for sum/avg/etc.
|
|
240
|
+
name?: string // result column alias
|
|
241
|
+
isDistinct?: boolean
|
|
242
|
+
minValue?: number // only aggregate values > this
|
|
243
|
+
maxValue?: number // only aggregate values < this
|
|
244
|
+
numberType?: 'bigint' | 'int' | 'decimal'
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Count per Group
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"columns": "record_owner(name)",
|
|
252
|
+
"calculations": [{ "field": "id", "func": "count", "name": "task_count" }],
|
|
253
|
+
"filters": { "rules": [{ "field": "task_status", "operator": "=", "value": 1 }] }
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Multiple Aggregations
|
|
258
|
+
```json
|
|
259
|
+
{
|
|
260
|
+
"columns": "category",
|
|
261
|
+
"calculations": [
|
|
262
|
+
{ "field": "id", "func": "count", "name": "total" },
|
|
263
|
+
{ "field": "amount", "func": "sum", "name": "totalAmount" },
|
|
264
|
+
{ "field": "amount", "func": "avg", "name": "avgAmount" },
|
|
265
|
+
{ "field": "amount", "func": "min", "name": "minAmount" },
|
|
266
|
+
{ "field": "amount", "func": "max", "name": "maxAmount" }
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Formulas — Simple
|
|
274
|
+
|
|
275
|
+
Single-depth function call. Column refs use `{column_name}` syntax.
|
|
276
|
+
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"columns": "id, name, formatted_date, full_name",
|
|
280
|
+
"formulas": {
|
|
281
|
+
"formatted_date": { "func": "to_char", "args": ["{created_on}", "DD/MM/YYYY"] },
|
|
282
|
+
"full_name": { "func": "concat", "args": ["{first_name}", " ", "{last_name}"] },
|
|
283
|
+
"upper_name": { "func": "upper", "args": ["{name}"] }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Rule**: Formula key must appear in `columns`.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Formulas — Block (AST)
|
|
293
|
+
|
|
294
|
+
Composable expression tree. One root block in `inputs` array.
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"columns": "id, name, my_formula",
|
|
299
|
+
"formulas": {
|
|
300
|
+
"my_formula": {
|
|
301
|
+
"inputs": [{ "kind": "...", ... }]
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Math Example: `quantity * unit_price`
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"formulas": {
|
|
311
|
+
"line_total": {
|
|
312
|
+
"inputs": [{
|
|
313
|
+
"kind": "math", "op": "*",
|
|
314
|
+
"inputs": [
|
|
315
|
+
{ "kind": "column", "name": "quantity" },
|
|
316
|
+
{ "kind": "column", "name": "unit_price" }
|
|
317
|
+
]
|
|
318
|
+
}]
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### CASE Example: Categorize by Amount
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"formulas": {
|
|
328
|
+
"tier": {
|
|
329
|
+
"inputs": [{
|
|
330
|
+
"kind": "case",
|
|
331
|
+
"cases": [
|
|
332
|
+
{ "when": { "kind": "compare", "op": ">=", "left": { "kind": "column", "name": "amount" }, "right": { "kind": "literal", "literal": 100000 } }, "then": { "kind": "literal", "literal": "Enterprise" } },
|
|
333
|
+
{ "when": { "kind": "compare", "op": ">=", "left": { "kind": "column", "name": "amount" }, "right": { "kind": "literal", "literal": 25000 } }, "then": { "kind": "literal", "literal": "Mid-Market" } }
|
|
334
|
+
],
|
|
335
|
+
"else": { "kind": "literal", "literal": "SMB" }
|
|
336
|
+
}]
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Formulas — Subquery
|
|
345
|
+
|
|
346
|
+
Correlated subquery against a child data source.
|
|
347
|
+
|
|
348
|
+
```json
|
|
349
|
+
{
|
|
350
|
+
"columns": "id, name, active_deals",
|
|
351
|
+
"formulas": {
|
|
352
|
+
"active_deals": {
|
|
353
|
+
"from": "crm_deal",
|
|
354
|
+
"with": "account",
|
|
355
|
+
"filters": { "rules": [{ "field": "stage", "operator": "!=", "value": "lost" }] },
|
|
356
|
+
"inputs": [{ "kind": "aggregate", "name": "count", "inputs": [] }]
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
- `from`: child table slug in `appSlug_tableSlug` format
|
|
363
|
+
- `with`: child field referencing parent `id` (string) or multi-field join (object)
|
|
364
|
+
- `filters`: optional WHERE on child table
|
|
365
|
+
|
|
366
|
+
### Multi-Field Join
|
|
367
|
+
```json
|
|
368
|
+
{ "with": { "child_field1": "parent_field1", "child_field2": "parent_field2" } }
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Compatibility Wrapper
|
|
372
|
+
Can also use `expression` key:
|
|
373
|
+
```json
|
|
374
|
+
{ "formulas": { "count": { "expression": { "from": "...", "with": "...", "inputs": [...] } } } }
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Block Kinds Reference
|
|
380
|
+
|
|
381
|
+
Every block has `kind` discriminator. Optional: `tz` (timezone), `cast` (type cast).
|
|
382
|
+
|
|
383
|
+
| Kind | Purpose | Key Props |
|
|
384
|
+
|------|---------|-----------|
|
|
385
|
+
| `literal` | Static value | `literal: string \| number \| boolean \| null \| array` |
|
|
386
|
+
| `column` | Table column ref | `name: string \| string[]` |
|
|
387
|
+
| `builtin` | SQL constant | `name: 'current_date' \| 'current_time' \| 'current_timestamp' \| 'now'` |
|
|
388
|
+
| `function` | Whitelisted SQL fn | `name: string, inputs?: block[]` |
|
|
389
|
+
| `math` | Arithmetic | `op: '+' \| '-' \| '*' \| '/' \| '%', inputs: block[]` (min 2) |
|
|
390
|
+
| `compare` | Comparison | `op: '=' \| '!=' \| '>' \| '<' \| '>=' \| '<=' \| 'like' \| 'ilike' \| 'in' \| 'not in', left: block, right: block` |
|
|
391
|
+
| `boolean` | Logical | `op: 'and' \| 'or' \| 'not', inputs: block[]` |
|
|
392
|
+
| `case` | Conditional | `cases: [{ when: block, then: block }], else?: block` |
|
|
393
|
+
| `aggregate` | Aggregate fn | `name: 'count' \| 'sum' \| 'avg' \| 'min' \| 'max' \| ..., distinct?: boolean, inputs: block[]` |
|
|
394
|
+
| `extract` | Date part | `part: 'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second', inputs: [block]` |
|
|
395
|
+
|
|
396
|
+
### Type Casting
|
|
397
|
+
```json
|
|
398
|
+
{ "kind": "column", "name": "price", "cast": "decimal" }
|
|
399
|
+
```
|
|
400
|
+
→ SQL: `("t0"."price")::decimal`
|
|
401
|
+
|
|
402
|
+
### Timezone
|
|
403
|
+
```json
|
|
404
|
+
{ "kind": "function", "name": "now", "tz": "UTC" }
|
|
405
|
+
```
|
|
406
|
+
→ SQL: `now() at time zone 'UTC'`
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Child Queries
|
|
411
|
+
|
|
412
|
+
Fetch child records as nested JSON arrays per parent row.
|
|
413
|
+
|
|
414
|
+
```json
|
|
415
|
+
{
|
|
416
|
+
"columns": "id, name, recent_orders",
|
|
417
|
+
"childQueries": {
|
|
418
|
+
"recent_orders": {
|
|
419
|
+
"from": "shop_order_item",
|
|
420
|
+
"using": "product",
|
|
421
|
+
"columns": "order_date, quantity, total_price",
|
|
422
|
+
"orderBy": "order_date DESC",
|
|
423
|
+
"limit": 5,
|
|
424
|
+
"filters": { "rules": [{ "field": "order_date", "operator": "last_30_days" }] }
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Rules:**
|
|
431
|
+
- Key (e.g. `recent_orders`) must appear in parent `columns`
|
|
432
|
+
- `from`: `appSlug_slug` format
|
|
433
|
+
- `using`: field in **child** DS referencing parent's `id`
|
|
434
|
+
- Supports full query params: `columns`, `filters`, `calculations`, `orderBy`, `limit`
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Pivot
|
|
439
|
+
|
|
440
|
+
Cross-tab matrix queries with date range series.
|
|
441
|
+
|
|
442
|
+
```json
|
|
443
|
+
{
|
|
444
|
+
"columns": "...order_status(status:name)",
|
|
445
|
+
"pivot": {
|
|
446
|
+
"matrix": [
|
|
447
|
+
{
|
|
448
|
+
"using": "created_on",
|
|
449
|
+
"columns": "day:to_char[DD/MM/YYYY]@created_on",
|
|
450
|
+
"dateRange": { "interval": "day", "min": "2025-09-01T00:00:00Z", "max": "2025-09-30T23:59:59Z" },
|
|
451
|
+
"spread": true
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"using": "record_owner",
|
|
455
|
+
"columns": "userName:name",
|
|
456
|
+
"spread": true
|
|
457
|
+
}
|
|
458
|
+
],
|
|
459
|
+
"hideEmptyRows": false,
|
|
460
|
+
"orderBy": "day ASC",
|
|
461
|
+
"limit": 1000
|
|
462
|
+
},
|
|
463
|
+
"calculations": [
|
|
464
|
+
{ "field": "id", "func": "count", "name": "total" },
|
|
465
|
+
{ "field": "amount", "func": "sum", "name": "totalSold" }
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Date range intervals: `day`, `week`, `month`, `year`, `hour`, `minute`, `second`
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Expand
|
|
475
|
+
|
|
476
|
+
Return full objects for relation/user/enum fields:
|
|
477
|
+
|
|
478
|
+
```json
|
|
479
|
+
{ "expand": ["record_owner", "related_account", "status"] }
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Allowed Functions
|
|
485
|
+
|
|
486
|
+
**String**: `length`, `lower`, `upper`, `substr`, `replace`, `concat`, `trim`, `ltrim`, `rtrim`, `btrim`, `split_part`, `initcap`, `reverse`, `strpos`, `lpad`, `rpad`
|
|
487
|
+
|
|
488
|
+
**Number**: `abs`, `ceil`, `floor`, `round`, `sqrt`, `power`, `mod`, `greatest`, `least`, `trunc`, `gcd`, `lcm`, `exp`, `ln`, `log`, `sign`
|
|
489
|
+
|
|
490
|
+
**Date/Time**: `now`, `age`, `date_part`, `date_trunc`, `extract`, `to_timestamp`, `to_char`, `to_date`, `to_time`, `make_date`, `make_time`, `make_timestamp`
|
|
491
|
+
|
|
492
|
+
**Utility**: `coalesce`
|
|
493
|
+
|
|
494
|
+
**JSON**: `jsonb_array_length`, `jsonb_extract_path`, `jsonb_extract_path_text`, `jsonb_build_object`, `json_build_object`, `jsonb_agg`, `json_agg`, `array_agg`
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## Allowed Cast Types
|
|
499
|
+
|
|
500
|
+
`int`, `int[]`, `int2`, `int4`, `int8`, `bigint`, `real`, `float`, `float4`, `float8`, `numeric`, `double`, `decimal`, `money`, `timestamp`, `timestamptz`, `date`, `time`, `interval`, `bool`, `boolean`, `uuid`, `text` (and array variants)
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## Complete Examples
|
|
505
|
+
|
|
506
|
+
### Full-Featured Query
|
|
507
|
+
```json
|
|
508
|
+
{
|
|
509
|
+
"columns": "id, task_name, ...record_owner(owner_name:name), ...related_account(account_name:name)",
|
|
510
|
+
"filters": {
|
|
511
|
+
"combinator": "and",
|
|
512
|
+
"rules": [
|
|
513
|
+
{ "field": "task_status", "operator": "in", "value": [1, 2] },
|
|
514
|
+
{ "field": "due_date", "operator": "in_next_x_days", "value": 7 },
|
|
515
|
+
{ "field": "record_owner", "operator": "in_active_user_team" }
|
|
516
|
+
]
|
|
517
|
+
},
|
|
518
|
+
"orderBy": "due_date ASC",
|
|
519
|
+
"limit": 50,
|
|
520
|
+
"fullCount": true
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Monthly Sales Report
|
|
525
|
+
```json
|
|
526
|
+
{
|
|
527
|
+
"columns": "months_of_year@created_on, ...category(cat:name)",
|
|
528
|
+
"calculations": [
|
|
529
|
+
{ "field": "id", "func": "count", "name": "order_count" },
|
|
530
|
+
{ "field": "total_amount", "func": "sum", "name": "revenue" },
|
|
531
|
+
{ "field": "total_amount", "func": "avg", "name": "avg_order" }
|
|
532
|
+
],
|
|
533
|
+
"filters": { "rules": [
|
|
534
|
+
{ "field": "created_on", "operator": "this_year" },
|
|
535
|
+
{ "field": "order_status", "operator": "!=", "value": "cancelled" }
|
|
536
|
+
]},
|
|
537
|
+
"orderBy": "months_of_year@created_on ASC"
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Computed Columns + Subquery
|
|
542
|
+
```json
|
|
543
|
+
{
|
|
544
|
+
"columns": "id, name, profit_margin, active_deals",
|
|
545
|
+
"formulas": {
|
|
546
|
+
"profit_margin": {
|
|
547
|
+
"inputs": [{
|
|
548
|
+
"kind": "math", "op": "*",
|
|
549
|
+
"inputs": [
|
|
550
|
+
{ "kind": "math", "op": "/", "inputs": [
|
|
551
|
+
{ "kind": "math", "op": "-", "inputs": [
|
|
552
|
+
{ "kind": "column", "name": "revenue" },
|
|
553
|
+
{ "kind": "column", "name": "cost" }
|
|
554
|
+
]},
|
|
555
|
+
{ "kind": "column", "name": "revenue", "cast": "decimal" }
|
|
556
|
+
]},
|
|
557
|
+
{ "kind": "literal", "literal": 100 }
|
|
558
|
+
]
|
|
559
|
+
}]
|
|
560
|
+
},
|
|
561
|
+
"active_deals": {
|
|
562
|
+
"from": "crm_deal", "with": "account",
|
|
563
|
+
"filters": { "rules": [{ "field": "stage", "operator": "!=", "value": "lost" }, { "field": "stage", "operator": "!=", "value": "won" }] },
|
|
564
|
+
"inputs": [{ "kind": "aggregate", "name": "count", "inputs": [] }]
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
"orderBy": "profit_margin DESC",
|
|
568
|
+
"limit": 20
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### Customers with Nested Orders
|
|
573
|
+
```json
|
|
574
|
+
{
|
|
575
|
+
"columns": "id, name, email, recent_orders, open_tickets",
|
|
576
|
+
"childQueries": {
|
|
577
|
+
"recent_orders": {
|
|
578
|
+
"from": "shop_order", "using": "customer",
|
|
579
|
+
"columns": "id, order_date, total_amount, ...status(status_label:name)",
|
|
580
|
+
"orderBy": "order_date DESC", "limit": 10,
|
|
581
|
+
"filters": { "rules": [{ "field": "order_date", "operator": "last_90_days" }] }
|
|
582
|
+
},
|
|
583
|
+
"open_tickets": {
|
|
584
|
+
"from": "support_ticket", "using": "customer",
|
|
585
|
+
"columns": "id, subject, priority, created_on",
|
|
586
|
+
"orderBy": "created_on DESC", "limit": 5,
|
|
587
|
+
"filters": { "rules": [{ "field": "status", "operator": "!=", "value": "closed" }] }
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
"limit": 25
|
|
591
|
+
}
|
|
592
|
+
```
|