@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.
Files changed (111) hide show
  1. package/agent-loader.js +37 -3
  2. package/agent-loader.js.map +2 -2
  3. package/main.js +498 -93
  4. package/main.js.map +4 -4
  5. package/package.json +14 -4
  6. package/resources/chrome-tools/browser-content.js +103 -0
  7. package/resources/chrome-tools/browser-cookies.js +35 -0
  8. package/resources/chrome-tools/browser-eval.js +53 -0
  9. package/resources/chrome-tools/browser-hn-scraper.js +108 -0
  10. package/resources/chrome-tools/browser-nav.js +44 -0
  11. package/resources/chrome-tools/browser-pick.js +162 -0
  12. package/resources/chrome-tools/browser-screenshot.js +34 -0
  13. package/resources/chrome-tools/browser-start.js +86 -0
  14. package/resources/pi-agent/extensions/answer.ts +532 -0
  15. package/resources/pi-agent/extensions/context.ts +578 -0
  16. package/resources/pi-agent/extensions/control.ts +1779 -0
  17. package/resources/pi-agent/extensions/diff.ts +218 -0
  18. package/resources/pi-agent/extensions/files.ts +199 -0
  19. package/resources/pi-agent/extensions/loop.ts +446 -0
  20. package/resources/pi-agent/extensions/multi-edit.ts +835 -0
  21. package/resources/pi-agent/extensions/notify.ts +88 -0
  22. package/resources/pi-agent/extensions/pi-mcp-adapter/CHANGELOG.md +192 -0
  23. package/resources/pi-agent/extensions/pi-mcp-adapter/LICENSE +21 -0
  24. package/resources/pi-agent/extensions/pi-mcp-adapter/README.md +296 -0
  25. package/resources/pi-agent/extensions/pi-mcp-adapter/app-bridge.bundle.js +67 -0
  26. package/resources/pi-agent/extensions/pi-mcp-adapter/cli.js +108 -0
  27. package/resources/pi-agent/extensions/pi-mcp-adapter/commands.ts +211 -0
  28. package/resources/pi-agent/extensions/pi-mcp-adapter/config.ts +227 -0
  29. package/resources/pi-agent/extensions/pi-mcp-adapter/consent-manager.ts +64 -0
  30. package/resources/pi-agent/extensions/pi-mcp-adapter/direct-tools.ts +301 -0
  31. package/resources/pi-agent/extensions/pi-mcp-adapter/errors.ts +219 -0
  32. package/resources/pi-agent/extensions/pi-mcp-adapter/glimpse-ui.ts +80 -0
  33. package/resources/pi-agent/extensions/pi-mcp-adapter/host-html-template.ts +427 -0
  34. package/resources/pi-agent/extensions/pi-mcp-adapter/index.ts +232 -0
  35. package/resources/pi-agent/extensions/pi-mcp-adapter/init.ts +319 -0
  36. package/resources/pi-agent/extensions/pi-mcp-adapter/lifecycle.ts +93 -0
  37. package/resources/pi-agent/extensions/pi-mcp-adapter/logger.ts +169 -0
  38. package/resources/pi-agent/extensions/pi-mcp-adapter/mcp-panel.ts +713 -0
  39. package/resources/pi-agent/extensions/pi-mcp-adapter/metadata-cache.ts +191 -0
  40. package/resources/pi-agent/extensions/pi-mcp-adapter/npx-resolver.ts +419 -0
  41. package/resources/pi-agent/extensions/pi-mcp-adapter/oauth-handler.ts +56 -0
  42. package/resources/pi-agent/extensions/pi-mcp-adapter/package.json +85 -0
  43. package/resources/pi-agent/extensions/pi-mcp-adapter/paths.ts +29 -0
  44. package/resources/pi-agent/extensions/pi-mcp-adapter/proxy-modes.ts +635 -0
  45. package/resources/pi-agent/extensions/pi-mcp-adapter/resource-tools.ts +17 -0
  46. package/resources/pi-agent/extensions/pi-mcp-adapter/server-manager.ts +330 -0
  47. package/resources/pi-agent/extensions/pi-mcp-adapter/state.ts +41 -0
  48. package/resources/pi-agent/extensions/pi-mcp-adapter/tool-metadata.ts +144 -0
  49. package/resources/pi-agent/extensions/pi-mcp-adapter/tool-registrar.ts +46 -0
  50. package/resources/pi-agent/extensions/pi-mcp-adapter/types.ts +367 -0
  51. package/resources/pi-agent/extensions/pi-mcp-adapter/ui-resource-handler.ts +145 -0
  52. package/resources/pi-agent/extensions/pi-mcp-adapter/ui-server.ts +623 -0
  53. package/resources/pi-agent/extensions/pi-mcp-adapter/ui-session.ts +384 -0
  54. package/resources/pi-agent/extensions/pi-mcp-adapter/ui-stream-types.ts +89 -0
  55. package/resources/pi-agent/extensions/pi-mcp-adapter/utils.ts +75 -0
  56. package/resources/pi-agent/extensions/prompt-editor.ts +1315 -0
  57. package/resources/pi-agent/extensions/prompt-url-widget.ts +158 -0
  58. package/resources/pi-agent/extensions/redraws.ts +24 -0
  59. package/resources/pi-agent/extensions/review.ts +2160 -0
  60. package/resources/pi-agent/extensions/todos.ts +2076 -0
  61. package/resources/pi-agent/extensions/tps.ts +47 -0
  62. package/resources/pi-agent/extensions/whimsical.ts +474 -0
  63. package/resources/pi-agent/prompts/coder-system.md +106 -0
  64. package/resources/pi-agent/skills/changelog-generator/SKILL.md +425 -0
  65. package/resources/pi-agent/skills/docyrus-chrome-devtools-cli/SKILL.md +80 -0
  66. package/resources/pi-agent/skills/docyrus-platform/SKILL.md +71 -0
  67. package/resources/pi-agent/skills/docyrus-platform/references/ai-capabilities.md +43 -0
  68. package/resources/pi-agent/skills/docyrus-platform/references/auth-and-multi-tenancy.md +35 -0
  69. package/resources/pi-agent/skills/docyrus-platform/references/automation-and-workflows.md +30 -0
  70. package/resources/pi-agent/skills/docyrus-platform/references/core-building-blocks.md +53 -0
  71. package/resources/pi-agent/skills/{docyrus-api-dev → docyrus-platform}/references/data-source-query-guide.md +32 -28
  72. package/resources/pi-agent/skills/docyrus-platform/references/developer-tools.md +28 -0
  73. package/resources/pi-agent/skills/docyrus-platform/references/docyrus-cli-usage.md +554 -0
  74. package/resources/pi-agent/skills/{docyrus-api-dev → docyrus-platform}/references/formula-design-guide-llm.md +15 -23
  75. package/resources/pi-agent/skills/docyrus-platform/references/integrations-and-events.md +60 -0
  76. package/resources/pi-agent/skills/docyrus-platform/references/platform-services.md +58 -0
  77. package/resources/pi-agent/skills/docyrus-platform/references/querying-and-data-operations.md +27 -0
  78. package/resources/pi-agent/prompts/coder-append-system.md +0 -19
  79. package/resources/pi-agent/skills/docyrus-ai/SKILL.md +0 -28
  80. package/resources/pi-agent/skills/docyrus-api-dev/SKILL.md +0 -161
  81. package/resources/pi-agent/skills/docyrus-api-dev/references/api-client.md +0 -349
  82. package/resources/pi-agent/skills/docyrus-api-dev/references/authentication.md +0 -238
  83. package/resources/pi-agent/skills/docyrus-api-dev/references/query-and-formulas.md +0 -592
  84. package/resources/pi-agent/skills/docyrus-api-doctor/SKILL.md +0 -70
  85. package/resources/pi-agent/skills/docyrus-api-doctor/references/checklist-details.md +0 -588
  86. package/resources/pi-agent/skills/docyrus-app-dev/SKILL.md +0 -159
  87. package/resources/pi-agent/skills/docyrus-app-dev/references/api-client-and-auth.md +0 -275
  88. package/resources/pi-agent/skills/docyrus-app-dev/references/collections-and-patterns.md +0 -352
  89. package/resources/pi-agent/skills/docyrus-app-dev/references/data-source-query-guide.md +0 -2059
  90. package/resources/pi-agent/skills/docyrus-app-dev/references/formula-design-guide-llm.md +0 -320
  91. package/resources/pi-agent/skills/docyrus-app-dev/references/query-guide.md +0 -525
  92. package/resources/pi-agent/skills/docyrus-app-ui-design/SKILL.md +0 -466
  93. package/resources/pi-agent/skills/docyrus-app-ui-design/references/component-selection-guide.md +0 -602
  94. package/resources/pi-agent/skills/docyrus-app-ui-design/references/icon-usage-guide.md +0 -463
  95. package/resources/pi-agent/skills/docyrus-app-ui-design/references/preferred-components-catalog.md +0 -242
  96. package/resources/pi-agent/skills/docyrus-apps/SKILL.md +0 -54
  97. package/resources/pi-agent/skills/docyrus-architect/SKILL.md +0 -174
  98. package/resources/pi-agent/skills/docyrus-architect/references/custom-query-guide.md +0 -410
  99. package/resources/pi-agent/skills/docyrus-architect/references/data-source-query-guide.md +0 -2059
  100. package/resources/pi-agent/skills/docyrus-architect/references/formula-design-guide-llm.md +0 -320
  101. package/resources/pi-agent/skills/docyrus-architect/references/formula-reference.md +0 -145
  102. package/resources/pi-agent/skills/docyrus-auth/SKILL.md +0 -100
  103. package/resources/pi-agent/skills/docyrus-cli-app/SKILL.md +0 -279
  104. package/resources/pi-agent/skills/docyrus-cli-app/references/cli-manifest.md +0 -532
  105. package/resources/pi-agent/skills/docyrus-cli-app/references/list-query-examples.md +0 -248
  106. package/resources/pi-agent/skills/docyrus-curl/SKILL.md +0 -32
  107. package/resources/pi-agent/skills/docyrus-discover/SKILL.md +0 -63
  108. package/resources/pi-agent/skills/docyrus-ds/SKILL.md +0 -95
  109. package/resources/pi-agent/skills/docyrus-env/SKILL.md +0 -21
  110. package/resources/pi-agent/skills/docyrus-studio/SKILL.md +0 -369
  111. 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
- ```