@docyrus/docyrus 0.0.15 → 0.0.17

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 (41) hide show
  1. package/README.md +25 -2
  2. package/main.js +1028 -540
  3. package/main.js.map +4 -4
  4. package/package.json +2 -1
  5. package/resources/pi-agent/prompts/agent-system.md +25 -0
  6. package/resources/pi-agent/prompts/coder-append-system.md +19 -0
  7. package/resources/pi-agent/skills/docyrus-ai/SKILL.md +28 -0
  8. package/resources/pi-agent/skills/docyrus-api-dev/SKILL.md +161 -0
  9. package/resources/pi-agent/skills/docyrus-api-dev/references/api-client.md +349 -0
  10. package/resources/pi-agent/skills/docyrus-api-dev/references/authentication.md +238 -0
  11. package/resources/pi-agent/skills/docyrus-api-dev/references/data-source-query-guide.md +2059 -0
  12. package/resources/pi-agent/skills/docyrus-api-dev/references/formula-design-guide-llm.md +320 -0
  13. package/resources/pi-agent/skills/docyrus-api-dev/references/query-and-formulas.md +592 -0
  14. package/resources/pi-agent/skills/docyrus-api-doctor/SKILL.md +70 -0
  15. package/resources/pi-agent/skills/docyrus-api-doctor/references/checklist-details.md +588 -0
  16. package/resources/pi-agent/skills/docyrus-app-dev/SKILL.md +159 -0
  17. package/resources/pi-agent/skills/docyrus-app-dev/references/api-client-and-auth.md +275 -0
  18. package/resources/pi-agent/skills/docyrus-app-dev/references/collections-and-patterns.md +352 -0
  19. package/resources/pi-agent/skills/docyrus-app-dev/references/data-source-query-guide.md +2059 -0
  20. package/resources/pi-agent/skills/docyrus-app-dev/references/formula-design-guide-llm.md +320 -0
  21. package/resources/pi-agent/skills/docyrus-app-dev/references/query-guide.md +525 -0
  22. package/resources/pi-agent/skills/docyrus-app-ui-design/SKILL.md +466 -0
  23. package/resources/pi-agent/skills/docyrus-app-ui-design/references/component-selection-guide.md +602 -0
  24. package/resources/pi-agent/skills/docyrus-app-ui-design/references/icon-usage-guide.md +463 -0
  25. package/resources/pi-agent/skills/docyrus-app-ui-design/references/preferred-components-catalog.md +242 -0
  26. package/resources/pi-agent/skills/docyrus-apps/SKILL.md +54 -0
  27. package/resources/pi-agent/skills/docyrus-architect/SKILL.md +174 -0
  28. package/resources/pi-agent/skills/docyrus-architect/references/custom-query-guide.md +410 -0
  29. package/resources/pi-agent/skills/docyrus-architect/references/data-source-query-guide.md +2059 -0
  30. package/resources/pi-agent/skills/docyrus-architect/references/formula-design-guide-llm.md +320 -0
  31. package/resources/pi-agent/skills/docyrus-architect/references/formula-reference.md +145 -0
  32. package/resources/pi-agent/skills/docyrus-auth/SKILL.md +100 -0
  33. package/resources/pi-agent/skills/docyrus-cli-app/SKILL.md +279 -0
  34. package/resources/pi-agent/skills/docyrus-cli-app/references/cli-manifest.md +532 -0
  35. package/resources/pi-agent/skills/docyrus-cli-app/references/list-query-examples.md +248 -0
  36. package/resources/pi-agent/skills/docyrus-curl/SKILL.md +32 -0
  37. package/resources/pi-agent/skills/docyrus-discover/SKILL.md +63 -0
  38. package/resources/pi-agent/skills/docyrus-ds/SKILL.md +95 -0
  39. package/resources/pi-agent/skills/docyrus-env/SKILL.md +21 -0
  40. package/resources/pi-agent/skills/docyrus-studio/SKILL.md +369 -0
  41. package/resources/pi-agent/skills/docyrus-tui/SKILL.md +15 -0
@@ -0,0 +1,320 @@
1
+ # SQL Block Formula Reference
2
+
3
+ ## Formula Types
4
+
5
+ Two block formula formats:
6
+
7
+ **Block Inline** — AST expression in SELECT: `{ alias?: string, inputs: IQueryFormulaBlock[] }`. Detected by `inputs` without `from`/`with`.
8
+
9
+ **Block Subquery** — correlated subquery on child table: `{ alias?, inputs, from: string, with: string | Record<string,string>, filters?: IQueryFilterGroup }`. Detected by `from`+`with`.
10
+
11
+ Compat wrapper: `{ expression: { from, with, inputs } }` is also accepted.
12
+
13
+ ## Block Schema
14
+
15
+ Top-level requires exactly 1 element in `inputs[]`. Optional `alias` becomes SQL alias.
16
+
17
+ Every block has optional `tz?: string` (timezone) and `cast?: string` (type cast). Processing: compile → tz → cast.
18
+
19
+ ## Block Kinds
20
+
21
+ ### literal
22
+
23
+ `{ kind: "literal", literal: string|number|boolean|Date|null|Array }`
24
+
25
+ - Scalars → parameterized `$N`. Arrays → `($1, $2, ...)`.
26
+ - Inside `concat`/`concat_ws` parent: auto-casts (`::text`, `::boolean`, `::timestamptz`, `::jsonb`).
27
+
28
+ ### column
29
+
30
+ `{ kind: "column", name: string|string[] }`
31
+
32
+ - Advanced DS: `"alias"."slug"`
33
+ - Simple DS custom fields: `"alias".data->>'<field-uuid>'` with auto-cast by field type:
34
+ - number/money/duration(decimal≠false) → `::decimal`, (decimal=false) → `::int`
35
+ - DB types jsonb/date/time/timestamptz/boolean/int* → `::<type>`, uuid[] → `::jsonb`
36
+ - Simple DS static/system fields (in `SIMPLE_STATIC_FIELD_SLUGS`): direct reference. Field not found → error.
37
+
38
+ ### builtin
39
+
40
+ `{ kind: "builtin", name: "current_date"|"current_time"|"current_timestamp"|"now" }`
41
+
42
+ - Emitted as raw SQL. Other names → error.
43
+
44
+ ### function
45
+
46
+ `{ kind: "function", name: string, inputs?: Block[] }`
47
+
48
+ - Validated against allowed functions whitelist. Inputs compiled recursively, joined by commas.
49
+ - **Gotcha:** Literal auto-cast only works inside `concat`/`concat_ws`. For `jsonb_build_object` and other functions, add explicit `"cast": "text"` to string literal blocks or Postgres will fail to determine parameter types.
50
+
51
+ ### extract
52
+
53
+ `{ kind: "extract", part: "year"|"month"|"day"|"hour"|"minute"|"second", inputs: [Block] }`
54
+
55
+ - Exactly 1 input required.
56
+ - SQL: `extract(<part> from <expr>)`
57
+
58
+ ### aggregate
59
+
60
+ `{ kind: "aggregate", name: "count"|"sum"|"avg"|"min"|"max"|"jsonb_agg"|"json_agg"|"array_agg", distinct?: boolean, inputs: Block[] }`
61
+
62
+ - `count` with empty or omitted inputs → `count(*)`. For count specifically, `inputs` is optional.
63
+ - `distinct` → `DISTINCT` keyword.
64
+
65
+ ### math
66
+
67
+ `{ kind: "math", op: "+"|"-"|"*"|"/"|"%", inputs: Block[] }`
68
+
69
+ - Min 2 operands. Left-associative with parens: `((a op b) op c)`.
70
+
71
+ ### case
72
+
73
+ `{ kind: "case", cases: [{when: Block, then: Block}], else?: Block }`
74
+
75
+ - Min 1 case required. `else` optional (defaults NULL).
76
+
77
+ ### compare
78
+
79
+ `{ kind: "compare", op: "="|"!="|"<>"|">"|"<"|">="|"<="|"like"|"ilike"|"in"|"not in"|"not_in", left: Block, right: Block }`
80
+
81
+ - `in`/`not in`: `left in right`. `not_in` accepted as alias but prefer `"not in"` (with space).
82
+ - `ilike` auto-converted to `like` for MySQL dialect.
83
+
84
+ ### boolean
85
+
86
+ `{ kind: "boolean", op: "and"|"or"|"not", inputs: Block[] }`
87
+
88
+ - `not`: exactly 1 input → `not (<expr>)`. `and`/`or`: min 2 → `((<a>) op (<b>))`.
89
+
90
+ ## Subquery Details
91
+
92
+ - `from`: child table full slug (`appSlug_tableSlug`), matched via `dataSource.children`.
93
+ - `with` (string): child field joins to parent `id`. `with` (object): `{ childField: parentField }`.
94
+ - Simple child DS: table rewritten to `tenant_record`, fields use `data->>'uuid'` refs.
95
+ - Optional `filters` apply WHERE on child table.
96
+ - Child alias: `t0_child`. Parent alias: `t0`.
97
+
98
+ ## Allowed Functions (Postgres)
99
+
100
+ **String**: length, lower, upper, substr, replace, concat, trim, ltrim, rtrim, btrim, split_part, initcap, reverse, strpos, lpad, rpad
101
+
102
+ **Number**: abs, ceil, floor, round, sqrt, power, mod, gcd, lcm, exp, ln, log, log10, log1p, pi, sign, width_bucket, trunc, greatest, least
103
+
104
+ **Date/Time**: now, age, clock_timestamp, date_part, date_trunc, extract, isfinite, justify_days, justify_hours, make_date, make_time, make_timestamp, make_timestamptz, timeofday, to_timestamp, to_char, to_date, to_time
105
+
106
+ **Utility**: coalesce
107
+
108
+ **JSON/JSONB**: jsonb_array_length, jsonb_extract_path, jsonb_extract_path_text, jsonb_object_keys, jsonb_build_object, json_build_object, jsonb_agg, json_agg, array_agg, array_to_json, row_to_json
109
+
110
+ **Aggregates**: count, sum, avg, min, max, jsonb_agg, json_agg, array_agg
111
+
112
+ ## Cast Types
113
+
114
+ Allowed: int, int2, int4, int8, bigint, real, float, float4, float8, numeric, double, decimal, money, timestamp, timestamptz, date, time, interval, bool, boolean, uuid, text (+ array variants like `int[]`, `text[]`).
115
+
116
+ ## Timezone
117
+
118
+ `tz` property: validated `/^[a-zA-Z0-9_]+$/`. SQL: `<expr> at time zone '<tz>'`. Column/function blocks omit outer parens.
119
+
120
+ ## Validation Errors
121
+
122
+ | Condition | Error |
123
+ |---|---|
124
+ | Empty inputs | "Formula must have at least one input block" |
125
+ | >1 root input | "Multiple input blocks not yet supported" |
126
+ | Bad function | `Function "${name}" is not allowed for dialect "${dialect}"` |
127
+ | Bad aggregate | `Aggregate function "${name}" is not allowed` |
128
+ | Extract ≠1 input | "EXTRACT requires exactly one input expression" |
129
+ | Math <2 ops | "Math operations require at least 2 operands" |
130
+ | NOT ≠1 op | "NOT operation requires exactly one operand" |
131
+ | AND/OR <2 ops | "${OP} operation requires at least 2 operands" |
132
+ | CASE 0 whens | "CASE expression must have at least one WHEN clause" |
133
+ | Bad tz | "Unsupported timezone: ${tz}" |
134
+ | Bad builtin | "Unsupported formula function: ${name}" |
135
+
136
+ ## SelectQueryBuilder Integration
137
+
138
+ 1. Formulas in `ISelectQueryParams.formulas` as `Record<string, IQueryFormula>`.
139
+ 2. Column alias matching formula key → formula replaces column ref in SELECT.
140
+ 3. Dispatch: `from`/`expression` → `buildBlockFormula()` (subquery), `inputs` only → `buildBlockFormula()` (inline).
141
+ 4. Calculations with `func:"formula"` also route through `buildFormula()`.
142
+ 5. `usedFormulas` set prevents duplicate application across SELECT and aggregations.
143
+ 6. Subquery formulas trigger async `resolveChildDatasources()` before build.
144
+
145
+ ## Examples
146
+
147
+ **Inline math** (balance / 100):
148
+
149
+ ```json
150
+ { "inputs": [{ "kind": "math", "op": "/", "inputs": [{ "kind": "column", "name": "balance" }, { "kind": "literal", "literal": 100 }] }] }
151
+ ```
152
+
153
+ **Formatted date** (to_char):
154
+
155
+ ```json
156
+ { "inputs": [{ "kind": "function", "name": "to_char", "inputs": [{ "kind": "column", "name": "created_on" }, { "kind": "literal", "literal": "DD/MM/YYYY" }] }] }
157
+ ```
158
+
159
+ **Subquery count**:
160
+
161
+ ```json
162
+ { "from": "app_child", "with": "parent_id", "inputs": [{ "kind": "aggregate", "name": "count", "inputs": [] }] }
163
+ ```
164
+
165
+ **Subquery count with distinct**:
166
+
167
+ ```json
168
+ { "expression": { "from": "app_child_table", "with": "parent_field", "inputs": [{ "kind": "aggregate", "name": "count", "distinct": true, "inputs": [{ "kind": "column", "name": "id" }] }] } }
169
+ ```
170
+
171
+ **Multi-field subquery join**:
172
+
173
+ ```json
174
+ { "from": "app_child", "with": { "child_field1": "parent_field1", "child_field2": "parent_field2" }, "inputs": [{ "kind": "aggregate", "name": "sum", "inputs": [{ "kind": "column", "name": "amount" }] }] }
175
+ ```
176
+
177
+ **CASE with AND**:
178
+
179
+ ```json
180
+ { "inputs": [{ "kind": "case", "cases": [{ "when": { "kind": "boolean", "op": "and", "inputs": [{ "kind": "compare", "op": ">", "left": { "kind": "column", "name": "price" }, "right": { "kind": "literal", "literal": 100 } }, { "kind": "compare", "op": "ilike", "left": { "kind": "column", "name": "name" }, "right": { "kind": "literal", "literal": "%pro%" } }] }, "then": { "kind": "literal", "literal": "premium" } }], "else": { "kind": "literal", "literal": "standard" } }] }
181
+ ```
182
+
183
+ **Multi-branch CASE** (tier assignment):
184
+
185
+ ```json
186
+ { "inputs": [{ "kind": "case", "cases": [{ "when": { "kind": "compare", "op": ">=", "left": { "kind": "column", "name": "revenue" }, "right": { "kind": "literal", "literal": 100000 } }, "then": { "kind": "literal", "literal": "enterprise" } }, { "when": { "kind": "compare", "op": ">=", "left": { "kind": "column", "name": "revenue" }, "right": { "kind": "literal", "literal": 10000 } }, "then": { "kind": "literal", "literal": "business" } }, { "when": { "kind": "compare", "op": ">=", "left": { "kind": "column", "name": "revenue" }, "right": { "kind": "literal", "literal": 1000 } }, "then": { "kind": "literal", "literal": "starter" } }], "else": { "kind": "literal", "literal": "free" } }] }
187
+ ```
188
+
189
+ **Nested aggregate**: `round(sum(qty * price), 2)`:
190
+
191
+ ```json
192
+ { "alias": "total", "inputs": [{ "kind": "function", "name": "round", "inputs": [{ "kind": "aggregate", "name": "sum", "inputs": [{ "kind": "math", "op": "*", "inputs": [{ "kind": "column", "name": "qty" }, { "kind": "column", "name": "price" }] }] }, { "kind": "literal", "literal": 2 }] }] }
193
+ ```
194
+
195
+ **Timezone**: `to_char(now() at time zone 'UTC', 'YYYY-MM-DD')`:
196
+
197
+ ```json
198
+ { "inputs": [{ "kind": "function", "name": "to_char", "inputs": [{ "kind": "function", "name": "now", "tz": "UTC" }, { "kind": "literal", "literal": "YYYY-MM-DD" }] }] }
199
+ ```
200
+
201
+ **COALESCE** (null handling):
202
+
203
+ ```json
204
+ { "inputs": [{ "kind": "function", "name": "coalesce", "inputs": [{ "kind": "column", "name": "description" }, { "kind": "literal", "literal": "No description" }] }] }
205
+ ```
206
+
207
+ **Subquery with filters** (count active children):
208
+
209
+ ```json
210
+ { "from": "app_child_table", "with": "parent_id", "filters": { "rules": [{ "field": "status", "operator": "=", "value": "active" }] }, "inputs": [{ "kind": "aggregate", "name": "count", "inputs": [] }] }
211
+ ```
212
+
213
+ **String concatenation with initcap**:
214
+
215
+ ```json
216
+ { "inputs": [{ "kind": "function", "name": "initcap", "inputs": [{ "kind": "function", "name": "concat", "inputs": [{ "kind": "column", "name": "first_name" }, { "kind": "literal", "literal": " " }, { "kind": "column", "name": "last_name" }] }] }] }
217
+ ```
218
+
219
+ **Percentage with cast**: `round(completed/total * 100, 2)`:
220
+
221
+ ```json
222
+ { "inputs": [{ "kind": "function", "name": "round", "inputs": [{ "kind": "math", "op": "*", "inputs": [{ "kind": "math", "op": "/", "inputs": [{ "kind": "column", "name": "completed_tasks", "cast": "decimal" }, { "kind": "function", "name": "greatest", "inputs": [{ "kind": "column", "name": "total_tasks", "cast": "decimal" }, { "kind": "literal", "literal": 1 }] }] }, { "kind": "literal", "literal": 100 }] }, { "kind": "literal", "literal": 2 }] }] }
223
+ ```
224
+
225
+ **Days since created**: `date_part('day', age(now, created_on))::int`:
226
+
227
+ ```json
228
+ { "inputs": [{ "kind": "function", "name": "date_part", "inputs": [{ "kind": "literal", "literal": "day" }, { "kind": "function", "name": "age", "inputs": [{ "kind": "builtin", "name": "now" }, { "kind": "column", "name": "created_on" }] }], "cast": "int" }] }
229
+ ```
230
+
231
+ **Boolean NOT with OR** (is_active = not archived or deleted):
232
+
233
+ ```json
234
+ { "inputs": [{ "kind": "boolean", "op": "not", "inputs": [{ "kind": "boolean", "op": "or", "inputs": [{ "kind": "compare", "op": "=", "left": { "kind": "column", "name": "is_archived" }, "right": { "kind": "literal", "literal": true } }, { "kind": "compare", "op": "=", "left": { "kind": "column", "name": "is_deleted" }, "right": { "kind": "literal", "literal": true } }] }] }] }
235
+ ```
236
+
237
+ **Subquery sum with filters** (outstanding invoice amount):
238
+
239
+ ```json
240
+ { "from": "billing_invoice_line", "with": "invoice_id", "filters": { "combinator": "and", "rules": [{ "field": "status", "operator": "!=", "value": "paid", "filterType": "ALPHA" }, { "field": "amount", "operator": ">", "value": 0, "filterType": "NUMERIC" }] }, "inputs": [{ "kind": "function", "name": "coalesce", "inputs": [{ "kind": "aggregate", "name": "sum", "inputs": [{ "kind": "column", "name": "amount" }] }, { "kind": "literal", "literal": 0 }] }] }
241
+ ```
242
+
243
+ **Extract year-month** (concat year + padded month):
244
+
245
+ ```json
246
+ { "inputs": [{ "kind": "function", "name": "concat", "inputs": [{ "kind": "extract", "part": "year", "inputs": [{ "kind": "column", "name": "created_on" }], "cast": "text" }, { "kind": "literal", "literal": "-" }, { "kind": "function", "name": "lpad", "inputs": [{ "kind": "extract", "part": "month", "inputs": [{ "kind": "column", "name": "created_on" }], "cast": "text" }, { "kind": "literal", "literal": 2 }, { "kind": "literal", "literal": "0" }] }] }] }
247
+ ```
248
+
249
+ **JSONB extraction**:
250
+
251
+ ```json
252
+ { "inputs": [{ "kind": "function", "name": "jsonb_extract_path_text", "inputs": [{ "kind": "column", "name": "address" }, { "kind": "literal", "literal": "country" }] }] }
253
+ ```
254
+
255
+ **Weighted average**: `sum(score * weight) / greatest(sum(weight), 1)`:
256
+
257
+ ```json
258
+ { "inputs": [{ "kind": "math", "op": "/", "inputs": [{ "kind": "aggregate", "name": "sum", "inputs": [{ "kind": "math", "op": "*", "inputs": [{ "kind": "column", "name": "score" }, { "kind": "column", "name": "weight" }] }] }, { "kind": "function", "name": "greatest", "inputs": [{ "kind": "aggregate", "name": "sum", "inputs": [{ "kind": "column", "name": "weight" }] }, { "kind": "literal", "literal": 1 }] }], "cast": "decimal" }] }
259
+ ```
260
+
261
+ **Date truncation** (period grouping by month):
262
+
263
+ ```json
264
+ { "inputs": [{ "kind": "function", "name": "date_trunc", "inputs": [{ "kind": "literal", "literal": "month" }, { "kind": "column", "name": "order_date" }] }] }
265
+ ```
266
+
267
+ **Multiple subquery formulas** (project with total + open task counts, using compat wrapper):
268
+
269
+ ```json
270
+ {
271
+ "columns": "id, name, total_tasks, open_tasks",
272
+ "formulas": [
273
+ {
274
+ "key": "total_tasks",
275
+ "expression": {
276
+ "expression": {
277
+ "from": "base_task", "with": "project",
278
+ "inputs": [{ "kind": "aggregate", "name": "count", "inputs": [{ "kind": "column", "name": "id" }] }]
279
+ }
280
+ }
281
+ },
282
+ {
283
+ "key": "open_tasks",
284
+ "expression": {
285
+ "expression": {
286
+ "from": "base_task", "with": "project",
287
+ "inputs": [{ "kind": "aggregate", "name": "count", "inputs": [{ "kind": "column", "name": "id" }] }],
288
+ "filters": { "rules": [{ "field": "status", "operator": "not_in", "value": ["<completed_uuid>", "<cancelled_uuid>"] }], "combinator": "and" }
289
+ }
290
+ }
291
+ }
292
+ ]
293
+ }
294
+ ```
295
+
296
+ → Each formula produces a correlated subquery: `(SELECT count("t0_child"."id") FROM ... WHERE "t0_child"."project" = "t0"."id" [AND status filter])`. No GROUP BY needed.
297
+
298
+ **Combined aggregations via jsonb_build_object** (pack total + open counts into one JSON column, one subquery):
299
+
300
+ ```json
301
+ {
302
+ "key": "task_stats",
303
+ "expression": {
304
+ "expression": {
305
+ "from": "base_task", "with": "project",
306
+ "inputs": [{
307
+ "kind": "function", "name": "jsonb_build_object",
308
+ "inputs": [
309
+ { "kind": "literal", "literal": "total", "cast": "text" },
310
+ { "kind": "aggregate", "name": "count", "inputs": [{ "kind": "column", "name": "id" }] },
311
+ { "kind": "literal", "literal": "open", "cast": "text" },
312
+ { "kind": "aggregate", "name": "count", "inputs": [{ "kind": "case", "cases": [{ "when": { "kind": "compare", "op": "not in", "left": { "kind": "column", "name": "status" }, "right": { "kind": "literal", "literal": ["<completed_uuid>", "<cancelled_uuid>"] } }, "then": { "kind": "column", "name": "id" } }] }] }
313
+ ]
314
+ }]
315
+ }
316
+ }
317
+ }
318
+ ```
319
+
320
+ → Result: `{ "task_stats": { "total": 6, "open": 2 } }`. `count(CASE WHEN ... THEN id END)` skips NULLs (no `else`) to count conditionally. `"cast": "text"` on literal keys is **required** for `jsonb_build_object`.