@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.
Files changed (30) hide show
  1. package/agent-loader.js +36 -25
  2. package/agent-loader.js.map +4 -4
  3. package/main.js +4 -4
  4. package/main.js.map +1 -1
  5. package/package.json +3 -3
  6. package/resources/pi-agent/extensions/docyrus-web-browser.ts +31 -0
  7. package/resources/pi-agent/shared/docyrusWebBrowserProtocol.ts +169 -0
  8. package/resources/pi-agent/skills/diffity-diff/SKILL.md +1 -1
  9. package/resources/pi-agent/skills/diffity-resolve/SKILL.md +4 -4
  10. package/resources/pi-agent/skills/diffity-review/SKILL.md +5 -4
  11. package/resources/pi-agent/skills/docyrus-api-dev/SKILL.md +197 -0
  12. package/resources/pi-agent/skills/docyrus-api-dev/references/acl-endpoints-frontend.md +295 -0
  13. package/resources/pi-agent/skills/docyrus-api-dev/references/api-client.md +349 -0
  14. package/resources/pi-agent/skills/docyrus-api-dev/references/authentication.md +298 -0
  15. package/resources/pi-agent/skills/docyrus-api-dev/references/data-source-query-guide.md +2063 -0
  16. package/resources/pi-agent/skills/docyrus-api-dev/references/formula-design-guide-llm.md +312 -0
  17. package/resources/pi-agent/skills/docyrus-api-dev/references/query-and-formulas.md +592 -0
  18. package/resources/pi-agent/skills/docyrus-app-dev-react/SKILL.md +361 -0
  19. package/resources/pi-agent/skills/docyrus-app-dev-react/references/README.md +29 -0
  20. package/resources/pi-agent/skills/docyrus-app-dev-react/references/api-client-and-auth.md +326 -0
  21. package/resources/pi-agent/skills/docyrus-app-dev-react/references/collections-and-patterns.md +353 -0
  22. package/resources/pi-agent/skills/docyrus-app-dev-react/references/component-selection-guide.md +619 -0
  23. package/resources/pi-agent/skills/docyrus-app-dev-react/references/icon-usage-guide.md +463 -0
  24. package/resources/pi-agent/skills/docyrus-app-dev-react/references/preferred-components-catalog.md +242 -0
  25. package/resources/pi-agent/skills/docyrus-platform/SKILL.md +2 -2
  26. package/resources/pi-agent/skills/docyrus-platform/references/auth-and-multi-tenancy.md +9 -1
  27. package/resources/pi-agent/skills/docyrus-platform/references/developer-tools.md +3 -2
  28. package/server-loader.js +328 -87
  29. package/server-loader.js.map +4 -4
  30. 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
+ ```