@docyrus/docyrus 0.0.19 → 0.0.21
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 +37 -3
- package/agent-loader.js.map +2 -2
- package/main.js +498 -93
- package/main.js.map +4 -4
- package/package.json +14 -4
- package/resources/chrome-tools/browser-content.js +103 -0
- package/resources/chrome-tools/browser-cookies.js +35 -0
- package/resources/chrome-tools/browser-eval.js +53 -0
- package/resources/chrome-tools/browser-hn-scraper.js +108 -0
- package/resources/chrome-tools/browser-nav.js +44 -0
- package/resources/chrome-tools/browser-pick.js +162 -0
- package/resources/chrome-tools/browser-screenshot.js +34 -0
- package/resources/chrome-tools/browser-start.js +86 -0
- package/resources/pi-agent/extensions/answer.ts +532 -0
- package/resources/pi-agent/extensions/context.ts +578 -0
- package/resources/pi-agent/extensions/control.ts +1779 -0
- package/resources/pi-agent/extensions/diff.ts +218 -0
- package/resources/pi-agent/extensions/files.ts +199 -0
- package/resources/pi-agent/extensions/loop.ts +446 -0
- package/resources/pi-agent/extensions/multi-edit.ts +835 -0
- package/resources/pi-agent/extensions/notify.ts +88 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/CHANGELOG.md +192 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/LICENSE +21 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/README.md +296 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/app-bridge.bundle.js +67 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/cli.js +108 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/commands.ts +211 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/config.ts +227 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/consent-manager.ts +64 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/direct-tools.ts +301 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/errors.ts +219 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/glimpse-ui.ts +80 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/host-html-template.ts +427 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/index.ts +232 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/init.ts +319 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/lifecycle.ts +93 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/logger.ts +169 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/mcp-panel.ts +713 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/metadata-cache.ts +191 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/npx-resolver.ts +419 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/oauth-handler.ts +56 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/package.json +85 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/paths.ts +29 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/proxy-modes.ts +635 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/resource-tools.ts +17 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/server-manager.ts +330 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/state.ts +41 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/tool-metadata.ts +144 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/tool-registrar.ts +46 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/types.ts +367 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/ui-resource-handler.ts +145 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/ui-server.ts +623 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/ui-session.ts +384 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/ui-stream-types.ts +89 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/utils.ts +75 -0
- package/resources/pi-agent/extensions/prompt-editor.ts +1315 -0
- package/resources/pi-agent/extensions/prompt-url-widget.ts +158 -0
- package/resources/pi-agent/extensions/redraws.ts +24 -0
- package/resources/pi-agent/extensions/review.ts +2160 -0
- package/resources/pi-agent/extensions/todos.ts +2076 -0
- package/resources/pi-agent/extensions/tps.ts +47 -0
- package/resources/pi-agent/extensions/whimsical.ts +474 -0
- package/resources/pi-agent/prompts/coder-system.md +106 -0
- package/resources/pi-agent/skills/changelog-generator/SKILL.md +425 -0
- package/resources/pi-agent/skills/docyrus-chrome-devtools-cli/SKILL.md +80 -0
- package/resources/pi-agent/skills/docyrus-platform/SKILL.md +71 -0
- package/resources/pi-agent/skills/docyrus-platform/references/ai-capabilities.md +43 -0
- package/resources/pi-agent/skills/docyrus-platform/references/auth-and-multi-tenancy.md +35 -0
- package/resources/pi-agent/skills/docyrus-platform/references/automation-and-workflows.md +30 -0
- package/resources/pi-agent/skills/docyrus-platform/references/core-building-blocks.md +53 -0
- package/resources/pi-agent/skills/{docyrus-api-dev → docyrus-platform}/references/data-source-query-guide.md +32 -28
- package/resources/pi-agent/skills/docyrus-platform/references/developer-tools.md +28 -0
- package/resources/pi-agent/skills/docyrus-platform/references/docyrus-cli-usage.md +554 -0
- package/resources/pi-agent/skills/{docyrus-api-dev → docyrus-platform}/references/formula-design-guide-llm.md +15 -23
- package/resources/pi-agent/skills/docyrus-platform/references/integrations-and-events.md +60 -0
- package/resources/pi-agent/skills/docyrus-platform/references/platform-services.md +58 -0
- package/resources/pi-agent/skills/docyrus-platform/references/querying-and-data-operations.md +27 -0
- package/resources/pi-agent/prompts/coder-append-system.md +0 -19
- package/resources/pi-agent/skills/docyrus-ai/SKILL.md +0 -28
- package/resources/pi-agent/skills/docyrus-api-dev/SKILL.md +0 -161
- package/resources/pi-agent/skills/docyrus-api-dev/references/api-client.md +0 -349
- package/resources/pi-agent/skills/docyrus-api-dev/references/authentication.md +0 -238
- package/resources/pi-agent/skills/docyrus-api-dev/references/query-and-formulas.md +0 -592
- package/resources/pi-agent/skills/docyrus-api-doctor/SKILL.md +0 -70
- package/resources/pi-agent/skills/docyrus-api-doctor/references/checklist-details.md +0 -588
- package/resources/pi-agent/skills/docyrus-app-dev/SKILL.md +0 -159
- package/resources/pi-agent/skills/docyrus-app-dev/references/api-client-and-auth.md +0 -275
- package/resources/pi-agent/skills/docyrus-app-dev/references/collections-and-patterns.md +0 -352
- package/resources/pi-agent/skills/docyrus-app-dev/references/data-source-query-guide.md +0 -2059
- package/resources/pi-agent/skills/docyrus-app-dev/references/formula-design-guide-llm.md +0 -320
- package/resources/pi-agent/skills/docyrus-app-dev/references/query-guide.md +0 -525
- package/resources/pi-agent/skills/docyrus-app-ui-design/SKILL.md +0 -466
- package/resources/pi-agent/skills/docyrus-app-ui-design/references/component-selection-guide.md +0 -602
- package/resources/pi-agent/skills/docyrus-app-ui-design/references/icon-usage-guide.md +0 -463
- package/resources/pi-agent/skills/docyrus-app-ui-design/references/preferred-components-catalog.md +0 -242
- package/resources/pi-agent/skills/docyrus-apps/SKILL.md +0 -54
- package/resources/pi-agent/skills/docyrus-architect/SKILL.md +0 -174
- package/resources/pi-agent/skills/docyrus-architect/references/custom-query-guide.md +0 -410
- package/resources/pi-agent/skills/docyrus-architect/references/data-source-query-guide.md +0 -2059
- package/resources/pi-agent/skills/docyrus-architect/references/formula-design-guide-llm.md +0 -320
- package/resources/pi-agent/skills/docyrus-architect/references/formula-reference.md +0 -145
- package/resources/pi-agent/skills/docyrus-auth/SKILL.md +0 -100
- package/resources/pi-agent/skills/docyrus-cli-app/SKILL.md +0 -279
- package/resources/pi-agent/skills/docyrus-cli-app/references/cli-manifest.md +0 -532
- package/resources/pi-agent/skills/docyrus-cli-app/references/list-query-examples.md +0 -248
- package/resources/pi-agent/skills/docyrus-curl/SKILL.md +0 -32
- package/resources/pi-agent/skills/docyrus-discover/SKILL.md +0 -63
- package/resources/pi-agent/skills/docyrus-ds/SKILL.md +0 -95
- package/resources/pi-agent/skills/docyrus-env/SKILL.md +0 -21
- package/resources/pi-agent/skills/docyrus-studio/SKILL.md +0 -369
- package/resources/pi-agent/skills/docyrus-tui/SKILL.md +0 -15
|
@@ -1,588 +0,0 @@
|
|
|
1
|
-
# Checklist Details
|
|
2
|
-
|
|
3
|
-
Detailed explanation, detection pattern, and fix example for every check in the Docyrus API Doctor.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## BUG Checks
|
|
8
|
-
|
|
9
|
-
### B1 — Missing `columns` parameter
|
|
10
|
-
|
|
11
|
-
**Detect:** A `.list()` or `.get()` call where the options object has no `columns` property.
|
|
12
|
-
|
|
13
|
-
**Why:** The API returns only the `id` field by default. Every other field is omitted unless explicitly requested.
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
// BAD
|
|
17
|
-
const items = await collection.list({
|
|
18
|
-
filters: { rules: [{ field: 'status', operator: '=', value: 'active' }] },
|
|
19
|
-
})
|
|
20
|
-
// items = [{ id: '...' }, { id: '...' }] — no useful data
|
|
21
|
-
|
|
22
|
-
// GOOD
|
|
23
|
-
const items = await collection.list({
|
|
24
|
-
columns: ['id', 'name', 'status'],
|
|
25
|
-
filters: { rules: [{ field: 'status', operator: '=', value: 'active' }] },
|
|
26
|
-
})
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
### B2 — `limit: 0` in query payload
|
|
32
|
-
|
|
33
|
-
**Detect:** `limit` property set to `0` in any query payload.
|
|
34
|
-
|
|
35
|
-
**Why:** `limit: 0` causes an API error. If you don't need to limit results, omit `limit` entirely (defaults to 100). If you need aggregation only, use `calculations` without `limit`.
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
// BAD — causes API error
|
|
39
|
-
const result = await collection.list({
|
|
40
|
-
columns: ['id'],
|
|
41
|
-
limit: 0,
|
|
42
|
-
fullCount: true,
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// GOOD — use calculations for counts
|
|
46
|
-
const result = await collection.list({
|
|
47
|
-
columns: ['id'],
|
|
48
|
-
calculations: [{ func: 'count', field: 'id', name: 'total' }],
|
|
49
|
-
})
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
### B3 — Child query key not in `columns`
|
|
55
|
-
|
|
56
|
-
**Detect:** A `childQueries` object with a key (e.g., `orders`) that does not appear in the `columns` array/string.
|
|
57
|
-
|
|
58
|
-
**Why:** The API silently drops child query results if their key isn't in `columns`.
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
// BAD — child query results won't appear
|
|
62
|
-
const result = await collection.list({
|
|
63
|
-
columns: ['id', 'name'],
|
|
64
|
-
childQueries: {
|
|
65
|
-
orders: {
|
|
66
|
-
from: 'base_order',
|
|
67
|
-
using: 'customer',
|
|
68
|
-
columns: ['id', 'total'],
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
// GOOD — include child query key in columns
|
|
74
|
-
const result = await collection.list({
|
|
75
|
-
columns: ['id', 'name', 'orders'],
|
|
76
|
-
childQueries: {
|
|
77
|
-
orders: {
|
|
78
|
-
from: 'base_order',
|
|
79
|
-
using: 'customer',
|
|
80
|
-
columns: ['id', 'total'],
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
})
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
### B4 — Formula key not in `columns`
|
|
89
|
-
|
|
90
|
-
**Detect:** A `formulas` object with a key (e.g., `total`) that does not appear in `columns`.
|
|
91
|
-
|
|
92
|
-
**Why:** Same as B3 — formula results are dropped if the key isn't in `columns`.
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
// BAD
|
|
96
|
-
const result = await collection.list({
|
|
97
|
-
columns: ['id', 'name'],
|
|
98
|
-
formulas: {
|
|
99
|
-
total: { func: 'sum', inputs: [{ kind: 'column', column: 'amount' }] },
|
|
100
|
-
},
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
// GOOD
|
|
104
|
-
const result = await collection.list({
|
|
105
|
-
columns: ['id', 'name', 'total'],
|
|
106
|
-
formulas: {
|
|
107
|
-
total: { func: 'sum', inputs: [{ kind: 'column', column: 'amount' }] },
|
|
108
|
-
},
|
|
109
|
-
})
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
### B5 — Aggregation via `@` column syntax
|
|
115
|
-
|
|
116
|
-
**Detect:** Column strings containing aggregation function patterns like `count@`, `sum@`, `avg@`, `min@`, `max@`.
|
|
117
|
-
|
|
118
|
-
**Why:** The `@` syntax in columns is for pre-defined date/grouping functions (e.g., `months_of_year@created_on`), not for aggregations. Use the `calculations` parameter for count/sum/avg/min/max.
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// BAD
|
|
122
|
-
const result = await collection.list({
|
|
123
|
-
columns: ['status', 'count@id'],
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
// GOOD
|
|
127
|
-
const result = await collection.list({
|
|
128
|
-
columns: ['status'],
|
|
129
|
-
calculations: [{ func: 'count', field: 'id', name: 'count' }],
|
|
130
|
-
})
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
### B6 — `distinctColumns` with `calculations`
|
|
136
|
-
|
|
137
|
-
**Detect:** Both `distinctColumns` and `calculations` present in the same query payload.
|
|
138
|
-
|
|
139
|
-
**Why:** These features are mutually exclusive. `distinctColumns` selects distinct rows; `calculations` performs aggregation. Combining them produces unexpected results.
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
// BAD
|
|
143
|
-
const result = await collection.list({
|
|
144
|
-
columns: ['status'],
|
|
145
|
-
distinctColumns: ['status'],
|
|
146
|
-
calculations: [{ func: 'count', field: 'id', name: 'count' }],
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
// GOOD — use calculations for aggregation
|
|
150
|
-
const result = await collection.list({
|
|
151
|
-
columns: ['status'],
|
|
152
|
-
calculations: [{ func: 'count', field: 'id', name: 'count' }],
|
|
153
|
-
})
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
### B7 — Formula `extract` input count
|
|
159
|
-
|
|
160
|
-
**Detect:** A formula block with `kind: 'extract'` (or `func: 'extract'`) that has `inputs.length !== 1`.
|
|
161
|
-
|
|
162
|
-
**Why:** PostgreSQL EXTRACT requires exactly one input expression. Zero or multiple inputs cause a server error.
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
// BAD — 0 inputs
|
|
166
|
-
{ kind: 'extract', part: 'month', inputs: [] }
|
|
167
|
-
|
|
168
|
-
// BAD — 2 inputs
|
|
169
|
-
{ kind: 'extract', part: 'month', inputs: [
|
|
170
|
-
{ kind: 'column', column: 'start_date' },
|
|
171
|
-
{ kind: 'column', column: 'end_date' },
|
|
172
|
-
]}
|
|
173
|
-
|
|
174
|
-
// GOOD
|
|
175
|
-
{ kind: 'extract', part: 'month', inputs: [
|
|
176
|
-
{ kind: 'column', column: 'start_date' },
|
|
177
|
-
]}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
### B8 — Formula `not` operand count
|
|
183
|
-
|
|
184
|
-
**Detect:** A formula block with `kind: 'not'` that has `inputs.length !== 1`.
|
|
185
|
-
|
|
186
|
-
**Why:** NOT is a unary operator. Multiple operands cause: "NOT operation requires exactly one operand".
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
### B9 — Formula `and`/`or` operand count
|
|
191
|
-
|
|
192
|
-
**Detect:** A formula block with `kind: 'and'` or `kind: 'or'` that has `inputs.length < 2`.
|
|
193
|
-
|
|
194
|
-
**Why:** Boolean AND/OR require at least 2 operands. A single operand causes: "${OP} operation requires at least 2 operands".
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
### B10 — Formula math operand count
|
|
199
|
-
|
|
200
|
-
**Detect:** A formula block with `kind: 'math'` and `operator` one of `+`, `-`, `*`, `/`, `%` that has `inputs.length < 2`.
|
|
201
|
-
|
|
202
|
-
**Why:** Math is left-associative binary. Fewer than 2 operands causes: "Math operations require at least 2 operands".
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
### B11 — Formula `case` without `when`
|
|
207
|
-
|
|
208
|
-
**Detect:** A formula block with `kind: 'case'` that has empty or missing `whens` array.
|
|
209
|
-
|
|
210
|
-
**Why:** CASE must have at least one WHEN clause. Empty cases cause: "CASE expression must have at least one WHEN clause".
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
### B12 — Uncast literal in `jsonb_build_object`
|
|
215
|
-
|
|
216
|
-
**Detect:** A formula using `jsonb_build_object` (or `json_build_object`) that contains `kind: 'literal'` blocks without a `cast` property.
|
|
217
|
-
|
|
218
|
-
**Why:** PostgreSQL cannot determine parameter types for literals inside `jsonb_build_object`. Auto-cast only works inside `concat`/`concat_ws`. All other functions need explicit `"cast": "text"` (or the appropriate type).
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
// BAD — Postgres error: could not determine data type of parameter
|
|
222
|
-
{
|
|
223
|
-
func: 'jsonb_build_object',
|
|
224
|
-
inputs: [
|
|
225
|
-
{ kind: 'literal', literal: 'label' }, // missing cast
|
|
226
|
-
{ kind: 'column', column: 'name' },
|
|
227
|
-
],
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// GOOD
|
|
231
|
-
{
|
|
232
|
-
func: 'jsonb_build_object',
|
|
233
|
-
inputs: [
|
|
234
|
-
{ kind: 'literal', literal: 'label', cast: 'text' },
|
|
235
|
-
{ kind: 'column', column: 'name' },
|
|
236
|
-
],
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
---
|
|
241
|
-
|
|
242
|
-
## PERFORMANCE Checks
|
|
243
|
-
|
|
244
|
-
### P1 — Unnecessary `limit` on aggregation queries
|
|
245
|
-
|
|
246
|
-
**Detect:** A query that has `calculations` but also includes a `limit` property, and the consuming code only reads the aggregated values (not raw row data).
|
|
247
|
-
|
|
248
|
-
**Why:** When using `calculations`, the result is already grouped/aggregated. Sending `limit` adds unnecessary constraint and suggests confusion about how aggregation works.
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
// BAD — limit is unnecessary
|
|
252
|
-
const result = await collection.list({
|
|
253
|
-
calculations: [{ func: 'count', field: 'id', name: 'total' }],
|
|
254
|
-
limit: 1,
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
// GOOD
|
|
258
|
-
const result = await collection.list({
|
|
259
|
-
calculations: [{ func: 'count', field: 'id', name: 'total' }],
|
|
260
|
-
})
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
### P2 — `fullCount` just to get a total count
|
|
266
|
-
|
|
267
|
-
**Detect:** A query with `fullCount: true` where the consuming code only reads `.fullCount` and ignores the actual rows, or uses `limit: 0`/`limit: 1` to minimize rows.
|
|
268
|
-
|
|
269
|
-
**Why:** `fullCount` is designed for pagination — showing "Page 1 of 50" alongside actual data. If you only need the count, `calculations` is more efficient because it doesn't fetch any row data.
|
|
270
|
-
|
|
271
|
-
```typescript
|
|
272
|
-
// BAD — fetching rows just to read fullCount
|
|
273
|
-
const result = await collection.list({
|
|
274
|
-
columns: ['id'],
|
|
275
|
-
limit: 1,
|
|
276
|
-
fullCount: true,
|
|
277
|
-
})
|
|
278
|
-
const count = result.fullCount
|
|
279
|
-
|
|
280
|
-
// GOOD — aggregation only
|
|
281
|
-
const result = await collection.list({
|
|
282
|
-
calculations: [{ func: 'count', field: 'id', name: 'total' }],
|
|
283
|
-
})
|
|
284
|
-
const count = result[0]?.total ?? 0
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
---
|
|
288
|
-
|
|
289
|
-
### P3 — Unnecessary `columns` on calculation-only queries
|
|
290
|
-
|
|
291
|
-
**Detect:** A query that has `calculations` and the consuming code only reads the aggregated values, but also includes a `columns` parameter.
|
|
292
|
-
|
|
293
|
-
**Why:** When you only need aggregated results (e.g., a total count for a stat card), sending `columns` is unnecessary overhead. The calculation result is returned without needing column selection.
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
// BAD — columns is unnecessary for a pure count
|
|
297
|
-
const result = await collection.list({
|
|
298
|
-
columns: ['id'],
|
|
299
|
-
calculations: [{ func: 'count', field: 'id', name: 'total' }],
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
// GOOD — calculations only
|
|
303
|
-
const result = await collection.list({
|
|
304
|
-
calculations: [{ func: 'count', field: 'id', name: 'total' }],
|
|
305
|
-
})
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
**Note:** `columns` IS needed alongside `calculations` when you also need grouped data (e.g., count per status requires `columns: ['status']` to group by status).
|
|
309
|
-
|
|
310
|
-
---
|
|
311
|
-
|
|
312
|
-
### P4 — Over-fetching columns
|
|
313
|
-
|
|
314
|
-
**Detect:** Columns in the `columns` parameter that are never referenced in the code that consumes the response.
|
|
315
|
-
|
|
316
|
-
**Why:** Each column adds to query execution time and response payload size. Only select what you actually use.
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
// BAD — description and notes are never rendered
|
|
320
|
-
const result = await collection.list({
|
|
321
|
-
columns: ['id', 'name', 'status', 'description', 'notes', 'created_on', 'modified_on'],
|
|
322
|
-
})
|
|
323
|
-
// Only renders: name, status
|
|
324
|
-
|
|
325
|
-
// GOOD
|
|
326
|
-
const result = await collection.list({
|
|
327
|
-
columns: ['id', 'name', 'status'],
|
|
328
|
-
})
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
---
|
|
332
|
-
|
|
333
|
-
### P5 — Large `limit` without pagination
|
|
334
|
-
|
|
335
|
-
**Detect:** `limit` set to > 200 without corresponding `offset` and `fullCount` pagination logic.
|
|
336
|
-
|
|
337
|
-
**Why:** Large result sets slow down both the API response and client-side rendering. Use pagination to load data incrementally.
|
|
338
|
-
|
|
339
|
-
```typescript
|
|
340
|
-
// BAD — loading everything at once
|
|
341
|
-
const result = await collection.list({
|
|
342
|
-
columns: ['id', 'name', 'status'],
|
|
343
|
-
limit: 5000,
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
// GOOD — paginated
|
|
347
|
-
const result = await collection.list({
|
|
348
|
-
columns: ['id', 'name', 'status'],
|
|
349
|
-
limit: 50,
|
|
350
|
-
offset: page * 50,
|
|
351
|
-
fullCount: true,
|
|
352
|
-
})
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
---
|
|
356
|
-
|
|
357
|
-
### P6 — Missing `expand` causing N+1
|
|
358
|
-
|
|
359
|
-
**Detect:** Code that accesses `.name` or other sub-properties of a relation/enum/user field, but that field is not in `expand`.
|
|
360
|
-
|
|
361
|
-
**Why:** Without `expand`, relation fields return only the ID string. If you then need the name, you'd have to make a separate API call per record (N+1 problem). With `expand`, the full object comes in the original response.
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
// BAD — status is just an ID string without expand
|
|
365
|
-
const result = await collection.list({
|
|
366
|
-
columns: ['id', 'name', 'status'],
|
|
367
|
-
})
|
|
368
|
-
// result[0].status = 'uuid-string' — can't render name
|
|
369
|
-
|
|
370
|
-
// GOOD
|
|
371
|
-
const result = await collection.list({
|
|
372
|
-
columns: ['id', 'name', 'status'],
|
|
373
|
-
expand: ['status'],
|
|
374
|
-
})
|
|
375
|
-
// result[0].status = { id: '...', name: 'Active', color: '#00ff00' }
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
---
|
|
379
|
-
|
|
380
|
-
### P7 — Fetching rows for existence checks
|
|
381
|
-
|
|
382
|
-
**Detect:** Fetching rows with `.list()` just to check `result.length > 0` or similar existence logic.
|
|
383
|
-
|
|
384
|
-
**Why:** Fetching full records to check existence wastes bandwidth and query time. A count calculation returns a single number.
|
|
385
|
-
|
|
386
|
-
```typescript
|
|
387
|
-
// BAD
|
|
388
|
-
const items = await collection.list({
|
|
389
|
-
columns: ['id'],
|
|
390
|
-
filters: { rules: [{ field: 'email', operator: '=', value: email }] },
|
|
391
|
-
})
|
|
392
|
-
const exists = items.length > 0
|
|
393
|
-
|
|
394
|
-
// GOOD
|
|
395
|
-
const result = await collection.list({
|
|
396
|
-
calculations: [{ func: 'count', field: 'id', name: 'total' }],
|
|
397
|
-
filters: { rules: [{ field: 'email', operator: '=', value: email }] },
|
|
398
|
-
})
|
|
399
|
-
const exists = (result[0]?.total ?? 0) > 0
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
---
|
|
403
|
-
|
|
404
|
-
### P8 — Redundant overlapping queries
|
|
405
|
-
|
|
406
|
-
**Detect:** Two or more `useQuery` calls hitting the same data source with overlapping columns and compatible filters that could be merged into one query.
|
|
407
|
-
|
|
408
|
-
**Why:** Each query is a separate HTTP round-trip. Combining them reduces latency and server load.
|
|
409
|
-
|
|
410
|
-
---
|
|
411
|
-
|
|
412
|
-
## CODE QUALITY Checks
|
|
413
|
-
|
|
414
|
-
### Q1 — Heavy `as` type assertions on responses
|
|
415
|
-
|
|
416
|
-
**Detect:** API response cast with `as Array<SomeType>` or `as unknown as SomeType` without runtime checks.
|
|
417
|
-
|
|
418
|
-
**Why:** `as` is a compile-time assertion — it doesn't validate data at runtime. If the API response shape changes, the code silently breaks.
|
|
419
|
-
|
|
420
|
-
```typescript
|
|
421
|
-
// BAD
|
|
422
|
-
const result = (await collection.list({
|
|
423
|
-
columns: ['id', 'name'],
|
|
424
|
-
})) as Array<{ id: string; name: string }>
|
|
425
|
-
|
|
426
|
-
// BETTER — use collection's typed return
|
|
427
|
-
const result = await collection.list({
|
|
428
|
-
columns: ['id', 'name'],
|
|
429
|
-
})
|
|
430
|
-
// Collection hooks should return typed results
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
---
|
|
434
|
-
|
|
435
|
-
### Q2 — Missing `enabled` on dependent queries
|
|
436
|
-
|
|
437
|
-
**Detect:** A `useQuery` call where the `queryFn` references a variable (like `recordId`, `userId`) that could be `null`/`undefined` at mount time, but there's no `enabled` option.
|
|
438
|
-
|
|
439
|
-
**Why:** Without `enabled`, the query fires immediately with the undefined value, causing a failed or meaningless API call.
|
|
440
|
-
|
|
441
|
-
```typescript
|
|
442
|
-
// BAD — fires with undefined recordId on mount
|
|
443
|
-
const { data } = useQuery({
|
|
444
|
-
queryKey: ['task', recordId],
|
|
445
|
-
queryFn: () => collection.get(recordId, { columns: ['id', 'name'] }),
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
// GOOD
|
|
449
|
-
const { data } = useQuery({
|
|
450
|
-
queryKey: ['task', recordId],
|
|
451
|
-
queryFn: () => collection.get(recordId!, { columns: ['id', 'name'] }),
|
|
452
|
-
enabled: !!recordId,
|
|
453
|
-
})
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
---
|
|
457
|
-
|
|
458
|
-
### Q3 — No error handling on mutations
|
|
459
|
-
|
|
460
|
-
**Detect:** `.create()`, `.update()`, `.delete()`, `.deleteMany()` calls without try/catch, `.catch()`, or a mutation error handler.
|
|
461
|
-
|
|
462
|
-
**Why:** Mutations can fail (network errors, validation errors, permission errors). Without error handling, the user gets no feedback and the UI may be left in an inconsistent state.
|
|
463
|
-
|
|
464
|
-
```typescript
|
|
465
|
-
// BAD
|
|
466
|
-
const handleSave = async (values: Record<string, unknown>) => {
|
|
467
|
-
await collection.create(values)
|
|
468
|
-
onSuccess?.()
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// GOOD
|
|
472
|
-
const handleSave = async (values: Record<string, unknown>) => {
|
|
473
|
-
try {
|
|
474
|
-
await collection.create(values)
|
|
475
|
-
onSuccess?.()
|
|
476
|
-
} catch (error) {
|
|
477
|
-
toast.error('Failed to create record')
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
---
|
|
483
|
-
|
|
484
|
-
### Q4 — Missing query invalidation after mutations
|
|
485
|
-
|
|
486
|
-
**Detect:** A mutation (create/update/delete) that does not call `queryClient.invalidateQueries()` afterward.
|
|
487
|
-
|
|
488
|
-
**Why:** Without invalidation, list views show stale data. The user creates/updates/deletes a record but the table still shows old data until a manual refresh.
|
|
489
|
-
|
|
490
|
-
```typescript
|
|
491
|
-
// BAD
|
|
492
|
-
await collection.update(recordId, values)
|
|
493
|
-
onClose()
|
|
494
|
-
|
|
495
|
-
// GOOD
|
|
496
|
-
await collection.update(recordId, values)
|
|
497
|
-
await queryClient.invalidateQueries({ queryKey: ['tasks'] })
|
|
498
|
-
onClose()
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
---
|
|
502
|
-
|
|
503
|
-
### Q5 — Serial cache invalidations
|
|
504
|
-
|
|
505
|
-
**Detect:** Multiple `await queryClient.invalidateQueries(...)` statements in sequence where they don't depend on each other.
|
|
506
|
-
|
|
507
|
-
**Why:** Independent invalidations can run concurrently. Sequential awaits add unnecessary latency.
|
|
508
|
-
|
|
509
|
-
```typescript
|
|
510
|
-
// BAD — sequential
|
|
511
|
-
await queryClient.invalidateQueries({ queryKey: ['task', recordId] })
|
|
512
|
-
await queryClient.invalidateQueries({ queryKey: ['tasks'] })
|
|
513
|
-
await queryClient.invalidateQueries({ queryKey: ['dashboard'] })
|
|
514
|
-
|
|
515
|
-
// GOOD — parallel
|
|
516
|
-
await Promise.all([
|
|
517
|
-
queryClient.invalidateQueries({ queryKey: ['task', recordId] }),
|
|
518
|
-
queryClient.invalidateQueries({ queryKey: ['tasks'] }),
|
|
519
|
-
queryClient.invalidateQueries({ queryKey: ['dashboard'] }),
|
|
520
|
-
])
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
---
|
|
524
|
-
|
|
525
|
-
### Q6 — Using deprecated `expandTypes`
|
|
526
|
-
|
|
527
|
-
**Detect:** The `expandTypes` property in a query payload.
|
|
528
|
-
|
|
529
|
-
**Why:** `expandTypes` is deprecated. Use `expand` with an array of field slugs instead.
|
|
530
|
-
|
|
531
|
-
```typescript
|
|
532
|
-
// BAD
|
|
533
|
-
collection.list({
|
|
534
|
-
columns: ['id', 'status'],
|
|
535
|
-
expandTypes: ['enum'],
|
|
536
|
-
})
|
|
537
|
-
|
|
538
|
-
// GOOD
|
|
539
|
-
collection.list({
|
|
540
|
-
columns: ['id', 'status'],
|
|
541
|
-
expand: ['status'],
|
|
542
|
-
})
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
---
|
|
546
|
-
|
|
547
|
-
### Q7 — Hardcoded data source paths
|
|
548
|
-
|
|
549
|
-
**Detect:** Direct `client.get('/v1/apps/.../data-sources/.../items')` calls instead of using generated collection hooks.
|
|
550
|
-
|
|
551
|
-
**Why:** Collection hooks encapsulate the endpoint path, provide typed methods, and automatically use the authenticated client. Hardcoded paths are fragile and bypass these benefits.
|
|
552
|
-
|
|
553
|
-
```typescript
|
|
554
|
-
// BAD
|
|
555
|
-
const client = useDocyrusClient()
|
|
556
|
-
const tasks = await client!.get('/v1/apps/base/data-sources/task/items', {
|
|
557
|
-
columns: 'id, name',
|
|
558
|
-
})
|
|
559
|
-
|
|
560
|
-
// GOOD
|
|
561
|
-
const collection = useBaseTaskCollection()
|
|
562
|
-
const tasks = await collection.list({
|
|
563
|
-
columns: ['id', 'name'],
|
|
564
|
-
})
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
---
|
|
568
|
-
|
|
569
|
-
### Q8 — `distinctColumns` without `orderBy`
|
|
570
|
-
|
|
571
|
-
**Detect:** A query payload with `distinctColumns` but no `orderBy`.
|
|
572
|
-
|
|
573
|
-
**Why:** `distinctColumns` uses PostgreSQL `DISTINCT ON` which requires `ORDER BY` to determine which row is kept per group. Without it, the selected row is non-deterministic.
|
|
574
|
-
|
|
575
|
-
```typescript
|
|
576
|
-
// BAD — non-deterministic result
|
|
577
|
-
collection.list({
|
|
578
|
-
columns: ['id', 'customer', 'status'],
|
|
579
|
-
distinctColumns: ['customer'],
|
|
580
|
-
})
|
|
581
|
-
|
|
582
|
-
// GOOD
|
|
583
|
-
collection.list({
|
|
584
|
-
columns: ['id', 'customer', 'status', 'created_on'],
|
|
585
|
-
distinctColumns: ['customer'],
|
|
586
|
-
orderBy: { field: 'created_on', direction: 'desc' },
|
|
587
|
-
})
|
|
588
|
-
```
|