kitcn 0.25.5 → 0.25.6

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.
@@ -102,20 +102,25 @@ declare const migrationStorageTables: {
102
102
  fieldName: "status";
103
103
  };
104
104
  };
105
- migrationId: ConvexTextBuilderInitial<""> & {
105
+ cursor: ConvexTextBuilderInitial<""> & {
106
106
  _: {
107
- notNull: true;
107
+ tableName: "migration_state";
108
108
  };
109
109
  } & {
110
+ _: {
111
+ fieldName: "cursor";
112
+ };
113
+ };
114
+ direction: ConvexTextBuilderInitial<""> & {
110
115
  _: {
111
116
  tableName: "migration_state";
112
117
  };
113
118
  } & {
114
119
  _: {
115
- fieldName: "migrationId";
120
+ fieldName: "direction";
116
121
  };
117
122
  };
118
- checksum: ConvexTextBuilderInitial<""> & {
123
+ migrationId: ConvexTextBuilderInitial<""> & {
119
124
  _: {
120
125
  notNull: true;
121
126
  };
@@ -125,10 +130,10 @@ declare const migrationStorageTables: {
125
130
  };
126
131
  } & {
127
132
  _: {
128
- fieldName: "checksum";
133
+ fieldName: "migrationId";
129
134
  };
130
135
  };
131
- applied: ConvexBooleanBuilderInitial<""> & {
136
+ checksum: ConvexTextBuilderInitial<""> & {
132
137
  _: {
133
138
  notNull: true;
134
139
  };
@@ -138,34 +143,29 @@ declare const migrationStorageTables: {
138
143
  };
139
144
  } & {
140
145
  _: {
141
- fieldName: "applied";
146
+ fieldName: "checksum";
142
147
  };
143
148
  };
144
- direction: ConvexTextBuilderInitial<""> & {
149
+ applied: ConvexBooleanBuilderInitial<""> & {
145
150
  _: {
146
- tableName: "migration_state";
151
+ notNull: true;
147
152
  };
148
153
  } & {
149
- _: {
150
- fieldName: "direction";
151
- };
152
- };
153
- runId: ConvexTextBuilderInitial<""> & {
154
154
  _: {
155
155
  tableName: "migration_state";
156
156
  };
157
157
  } & {
158
158
  _: {
159
- fieldName: "runId";
159
+ fieldName: "applied";
160
160
  };
161
161
  };
162
- cursor: ConvexTextBuilderInitial<""> & {
162
+ runId: ConvexTextBuilderInitial<""> & {
163
163
  _: {
164
164
  tableName: "migration_state";
165
165
  };
166
166
  } & {
167
167
  _: {
168
- fieldName: "cursor";
168
+ fieldName: "runId";
169
169
  };
170
170
  };
171
171
  processed: ConvexNumberBuilderInitial<""> & {
@@ -1021,7 +1021,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1021
1021
  fieldName: "updatedAt";
1022
1022
  };
1023
1023
  };
1024
- indexName: ConvexTextBuilderInitial<""> & {
1024
+ count: ConvexNumberBuilderInitial<""> & {
1025
1025
  _: {
1026
1026
  notNull: true;
1027
1027
  };
@@ -1031,10 +1031,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1031
1031
  };
1032
1032
  } & {
1033
1033
  _: {
1034
- fieldName: "indexName";
1034
+ fieldName: "count";
1035
1035
  };
1036
1036
  };
1037
- tableKey: ConvexTextBuilderInitial<""> & {
1037
+ indexName: ConvexTextBuilderInitial<""> & {
1038
1038
  _: {
1039
1039
  notNull: true;
1040
1040
  };
@@ -1044,10 +1044,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1044
1044
  };
1045
1045
  } & {
1046
1046
  _: {
1047
- fieldName: "tableKey";
1047
+ fieldName: "indexName";
1048
1048
  };
1049
1049
  };
1050
- count: ConvexNumberBuilderInitial<""> & {
1050
+ tableKey: ConvexTextBuilderInitial<""> & {
1051
1051
  _: {
1052
1052
  notNull: true;
1053
1053
  };
@@ -1057,7 +1057,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1057
1057
  };
1058
1058
  } & {
1059
1059
  _: {
1060
- fieldName: "count";
1060
+ fieldName: "tableKey";
1061
1061
  };
1062
1062
  };
1063
1063
  keyHash: ConvexTextBuilderInitial<""> & {
@@ -1133,7 +1133,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1133
1133
  readonly aggregate_member: ConvexTableWithColumns<{
1134
1134
  name: "aggregate_member";
1135
1135
  columns: {
1136
- updatedAt: ConvexNumberBuilderInitial<""> & {
1136
+ kind: ConvexTextBuilderInitial<""> & {
1137
1137
  _: {
1138
1138
  notNull: true;
1139
1139
  };
@@ -1143,10 +1143,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1143
1143
  };
1144
1144
  } & {
1145
1145
  _: {
1146
- fieldName: "updatedAt";
1146
+ fieldName: "kind";
1147
1147
  };
1148
1148
  };
1149
- kind: ConvexTextBuilderInitial<""> & {
1149
+ updatedAt: ConvexNumberBuilderInitial<""> & {
1150
1150
  _: {
1151
1151
  notNull: true;
1152
1152
  };
@@ -1156,7 +1156,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1156
1156
  };
1157
1157
  } & {
1158
1158
  _: {
1159
- fieldName: "kind";
1159
+ fieldName: "updatedAt";
1160
1160
  };
1161
1161
  };
1162
1162
  indexName: ConvexTextBuilderInitial<""> & {
@@ -1323,7 +1323,11 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1323
1323
  readonly aggregate_extrema: ConvexTableWithColumns<{
1324
1324
  name: "aggregate_extrema";
1325
1325
  columns: {
1326
- updatedAt: ConvexNumberBuilderInitial<""> & {
1326
+ value: ConvexCustomBuilderInitial<"", convex_values0.VAny<any, "required", string>> & {
1327
+ _: {
1328
+ $type: convex_values0.Value;
1329
+ };
1330
+ } & {
1327
1331
  _: {
1328
1332
  notNull: true;
1329
1333
  };
@@ -1333,10 +1337,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1333
1337
  };
1334
1338
  } & {
1335
1339
  _: {
1336
- fieldName: "updatedAt";
1340
+ fieldName: "value";
1337
1341
  };
1338
1342
  };
1339
- indexName: ConvexTextBuilderInitial<""> & {
1343
+ updatedAt: ConvexNumberBuilderInitial<""> & {
1340
1344
  _: {
1341
1345
  notNull: true;
1342
1346
  };
@@ -1346,10 +1350,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1346
1350
  };
1347
1351
  } & {
1348
1352
  _: {
1349
- fieldName: "indexName";
1353
+ fieldName: "updatedAt";
1350
1354
  };
1351
1355
  };
1352
- tableKey: ConvexTextBuilderInitial<""> & {
1356
+ count: ConvexNumberBuilderInitial<""> & {
1353
1357
  _: {
1354
1358
  notNull: true;
1355
1359
  };
@@ -1359,14 +1363,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1359
1363
  };
1360
1364
  } & {
1361
1365
  _: {
1362
- fieldName: "tableKey";
1366
+ fieldName: "count";
1363
1367
  };
1364
1368
  };
1365
- value: ConvexCustomBuilderInitial<"", convex_values0.VAny<any, "required", string>> & {
1366
- _: {
1367
- $type: convex_values0.Value;
1368
- };
1369
- } & {
1369
+ indexName: ConvexTextBuilderInitial<""> & {
1370
1370
  _: {
1371
1371
  notNull: true;
1372
1372
  };
@@ -1376,10 +1376,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1376
1376
  };
1377
1377
  } & {
1378
1378
  _: {
1379
- fieldName: "value";
1379
+ fieldName: "indexName";
1380
1380
  };
1381
1381
  };
1382
- count: ConvexNumberBuilderInitial<""> & {
1382
+ tableKey: ConvexTextBuilderInitial<""> & {
1383
1383
  _: {
1384
1384
  notNull: true;
1385
1385
  };
@@ -1389,7 +1389,7 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1389
1389
  };
1390
1390
  } & {
1391
1391
  _: {
1392
- fieldName: "count";
1392
+ fieldName: "tableKey";
1393
1393
  };
1394
1394
  };
1395
1395
  keyHash: ConvexTextBuilderInitial<""> & {
@@ -1589,6 +1589,19 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1589
1589
  fieldName: "status";
1590
1590
  };
1591
1591
  };
1592
+ kind: ConvexTextBuilderInitial<""> & {
1593
+ _: {
1594
+ notNull: true;
1595
+ };
1596
+ } & {
1597
+ _: {
1598
+ tableName: "aggregate_state";
1599
+ };
1600
+ } & {
1601
+ _: {
1602
+ fieldName: "kind";
1603
+ };
1604
+ };
1592
1605
  cursor: ConvexTextBuilderInitial<""> & {
1593
1606
  _: {
1594
1607
  tableName: "aggregate_state";
@@ -1655,19 +1668,6 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1655
1668
  fieldName: "lastError";
1656
1669
  };
1657
1670
  };
1658
- kind: ConvexTextBuilderInitial<""> & {
1659
- _: {
1660
- notNull: true;
1661
- };
1662
- } & {
1663
- _: {
1664
- tableName: "aggregate_state";
1665
- };
1666
- } & {
1667
- _: {
1668
- fieldName: "kind";
1669
- };
1670
- };
1671
1671
  indexName: ConvexTextBuilderInitial<""> & {
1672
1672
  _: {
1673
1673
  notNull: true;
@@ -1744,20 +1744,25 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1744
1744
  fieldName: "status";
1745
1745
  };
1746
1746
  };
1747
- migrationId: ConvexTextBuilderInitial<""> & {
1747
+ cursor: ConvexTextBuilderInitial<""> & {
1748
1748
  _: {
1749
- notNull: true;
1749
+ tableName: "migration_state";
1750
1750
  };
1751
1751
  } & {
1752
+ _: {
1753
+ fieldName: "cursor";
1754
+ };
1755
+ };
1756
+ direction: ConvexTextBuilderInitial<""> & {
1752
1757
  _: {
1753
1758
  tableName: "migration_state";
1754
1759
  };
1755
1760
  } & {
1756
1761
  _: {
1757
- fieldName: "migrationId";
1762
+ fieldName: "direction";
1758
1763
  };
1759
1764
  };
1760
- checksum: ConvexTextBuilderInitial<""> & {
1765
+ migrationId: ConvexTextBuilderInitial<""> & {
1761
1766
  _: {
1762
1767
  notNull: true;
1763
1768
  };
@@ -1767,10 +1772,10 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1767
1772
  };
1768
1773
  } & {
1769
1774
  _: {
1770
- fieldName: "checksum";
1775
+ fieldName: "migrationId";
1771
1776
  };
1772
1777
  };
1773
- applied: ConvexBooleanBuilderInitial<""> & {
1778
+ checksum: ConvexTextBuilderInitial<""> & {
1774
1779
  _: {
1775
1780
  notNull: true;
1776
1781
  };
@@ -1780,34 +1785,29 @@ declare const BUILTIN_SCHEMA_EXTENSIONS: readonly [SchemaExtension<{
1780
1785
  };
1781
1786
  } & {
1782
1787
  _: {
1783
- fieldName: "applied";
1788
+ fieldName: "checksum";
1784
1789
  };
1785
1790
  };
1786
- direction: ConvexTextBuilderInitial<""> & {
1791
+ applied: ConvexBooleanBuilderInitial<""> & {
1787
1792
  _: {
1788
- tableName: "migration_state";
1793
+ notNull: true;
1789
1794
  };
1790
1795
  } & {
1791
- _: {
1792
- fieldName: "direction";
1793
- };
1794
- };
1795
- runId: ConvexTextBuilderInitial<""> & {
1796
1796
  _: {
1797
1797
  tableName: "migration_state";
1798
1798
  };
1799
1799
  } & {
1800
1800
  _: {
1801
- fieldName: "runId";
1801
+ fieldName: "applied";
1802
1802
  };
1803
1803
  };
1804
- cursor: ConvexTextBuilderInitial<""> & {
1804
+ runId: ConvexTextBuilderInitial<""> & {
1805
1805
  _: {
1806
1806
  tableName: "migration_state";
1807
1807
  };
1808
1808
  } & {
1809
1809
  _: {
1810
- fieldName: "cursor";
1810
+ fieldName: "runId";
1811
1811
  };
1812
1812
  };
1813
1813
  processed: ConvexNumberBuilderInitial<""> & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitcn",
3
- "version": "0.25.5",
3
+ "version": "0.25.6",
4
4
  "description": "kitcn - React Query integration and CLI tools for Convex",
5
5
  "keywords": [
6
6
  "convex",
@@ -47,6 +47,7 @@
47
47
  "intent": "./bin/intent.js"
48
48
  },
49
49
  "files": [
50
+ "CHANGELOG.md",
50
51
  "dist",
51
52
  "skills",
52
53
  "bin",
@@ -32,34 +32,35 @@ Default assumption:
32
32
  Only remember these non-parity deltas:
33
33
  1. Procedure input root must be `z.object(...)` (no primitive root args).
34
34
  2. No `z.void()` outputs; omit `.output(...)` for no-value mutations.
35
- 3. Stacked `.input(...)` calls merge input shapes.
36
- 4. `.paginated({ limit, item })` must be before `.query()` and auto-adds `input.cursor` + `input.limit`, output `{ page, continueCursor, isDone }`.
37
- 5. Metadata is codegen’d onto `@convex/api` leaves (`api.namespace.fn.meta`) so never put secrets in `.meta(...)`; chaining `.meta(...)` is shallow merge and supports `defaultMeta`.
38
- 6. Auth metadata drives client behavior: `auth: "optional"` waits for auth load then runs, `auth: "required"` waits then skips when logged out.
39
- 7. `ctx.orm` enforces constraints + RLS; `ctx.db` bypasses them.
40
- 8. Non-paginated `findMany()` must be explicitly sized (`limit`, cursor mode, schema `defaultLimit`, or explicit `allowFullScan`).
41
- 9. Predicate `where` requires explicit `.withIndex(...)`; no implicit full scan fallback.
42
- 10. Cursor pagination uses the first `orderBy` field; index that field for stable paging.
43
- 11. `maxScan` applies to cursor mode only; `allowFullScan` is for non-cursor full-scan opt-in.
44
- 12. String operators / `columns` projection / many-relation subfilters can run post-fetch; bound result size early.
45
- 13. Search mode is relevance-ordered and does not support `orderBy`; vector mode has stricter limits (no cursor/offset/top-level where/order).
46
- 14. Update/delete without `where` throws unless `allowFullScan()`.
47
- 15. `count()`, `aggregate()`, and `groupBy()` require a matching `aggregateIndex`. Use `groupBy({ by, _count, _sum })` instead of multiple `.count()` calls or `findMany` + manual JS grouping. Every `by` field must be finite-constrained (`eq`/`in`/`isNull`) in `where`. See `references/features/aggregates.md`.
48
- 16. cRPC React queries are real-time by default (`subscribe: true`); never use `queryClient.invalidateQueries` for these subscribed paths.
49
- 17. In RSC, `prefetch` hydrates client, `caller` is server-only and not hydrated, `preloadQuery` hydrates but can cause stale split ownership if also rendered client-side.
50
- 18. Better Auth Next.js shortcut is `convexBetterAuth(...)`; generic server-only shortcut is `createCallerFactory(...)`.
51
- 19. On the kitcn auth client path, use `createAuthMutations(authClient)` wrappers so logout unsubscribes auth queries before sign out. Raw Convex preset keeps a smaller plain `authClient`.
52
- 20. **NEVER** use `ctx.runQuery`/`ctx.runMutation`/`ctx.runAction` directly for module-to-module calls. Use the generated runtime helpers from `convex/functions/generated/<module>.runtime`.
53
- 21. **`create<Module>Handler(ctx)`** is the default in queries/mutations: zero overhead, query/mutation ctx only, and no redundant validation or middleware.
54
- 22. **`create<Module>Caller(ctx)`** is for actions and HTTP routes. Action procedures live under `caller.actions.*`; scheduling lives under `caller.schedule.now|after|at|cancel`. Use `requireActionCtx(ctx)` only for true `ActionCtx` callbacks; use `requireSchedulerCtx(ctx)` when mutation or action contexts can schedule. Each caller/handler eagerly loads its module, so split large modules.
55
- 23. API types (`Api`, `ApiInputs`, `ApiOutputs`, `Select`, `Insert`, `TableName`) import from `@convex/api` no manual `inferApiInputs<typeof api>`.
56
- 24. HTTP router must export as `httpRouter` (not `appRouter`) for codegen.
57
- 25. Server wiring imports come from `convex/functions/generated/` directory: `getAuth`, `defineAuth` from `generated/auth`; `initCRPC`, `QueryCtx`, `MutationCtx`, `OrmCtx` from `generated/server`; `create<Module>Caller`, `create<Module>Handler` from `generated/<module>.runtime`. No manual `convex/lib/orm.ts`.
58
- 26. `defineAuth(() => ({ ...options, triggers }))` replaces split `getAuthOptions` + `authTriggers`. Trigger callbacks are doc-first: `beforeCreate(data)`, `onCreate(doc)`, `onUpdate(newDoc, oldDoc)` no `ctx` first param.
59
- 27. Internal auth functions at `internal.generated.*` (not `internal.auth.*`).
60
- 28. Async mutation batching is the default (codegen wires it). Customize per call: `execute({ batchSize, delayMs })`. Opt into sync: `execute({ mode: 'sync' })` or `defineSchema(..., { defaults: { mutationExecutionMode: 'sync' } })`. Relevant defaults: `mutationBatchSize`, `mutationLeafBatchSize`, `mutationMaxRows`, `mutationScheduleCallCap`.
61
- 29. Polymorphic unions are schema-first: use `actionType: discriminator({ variants, as? })` in `convexTable(...)`. Query config does not include a `polymorphic` option. Writes stay flat; reads synthesize nested `details` (or custom alias). Use `withVariants: true` to auto-load all `one()` relations on discriminator tables.
62
- 30. Do not add manual ORM mutation batching loops in app/plugin code by default. Convex runtime batching already handles mutation execution. Prefer set-based deletes/updates over per-row loops. Only add explicit chunking when batching external side effects (for example Resend API calls) or bounded cleanup sweeps.
35
+ 3. `.output(...)` parses the handler's value as-is and substitutes nothing: a handler must return the schema's *input* type, so `z.string().nullable()` needs an explicit `null` (`?? null`), not `undefined`. Model absent values as `.nullable()`, never a top-level `.optional()` — Convex wires `undefined` as `null` and cannot express top-level optionality, so `.output(z.string().optional())` publishes `v.string()` and the deployment rejects the `null` whenever the handler returns `undefined`. `.optional()` inside an object is fine. The low-level `returns:` option on `zCustomQuery`/`zCustomMutation`/`zCustomAction` differs — it substitutes `null` for `undefined` before parsing.
36
+ 4. Stacked `.input(...)` calls merge input shapes.
37
+ 5. `.paginated({ limit, item })` must be before `.query()` and auto-adds `input.cursor` + `input.limit`, output `{ page, continueCursor, isDone }`.
38
+ 6. Metadata is codegen’d onto `@convex/api` leaves (`api.namespace.fn.meta`) so never put secrets in `.meta(...)`; chaining `.meta(...)` is shallow merge and supports `defaultMeta`.
39
+ 7. Auth metadata drives client behavior: `auth: "optional"` waits for auth load then runs, `auth: "required"` waits then skips when logged out.
40
+ 8. `ctx.orm` enforces constraints + RLS; `ctx.db` bypasses them.
41
+ 9. Non-paginated `findMany()` must be explicitly sized (`limit`, cursor mode, schema `defaultLimit`, or explicit `allowFullScan`).
42
+ 10. Predicate `where` requires explicit `.withIndex(...)`; no implicit full scan fallback.
43
+ 11. Cursor pagination uses the first `orderBy` field; index that field for stable paging.
44
+ 12. `maxScan` applies to cursor mode only; `allowFullScan` is for non-cursor full-scan opt-in.
45
+ 13. String operators / `columns` projection / many-relation subfilters can run post-fetch; bound result size early.
46
+ 14. Search mode is relevance-ordered and does not support `orderBy`; vector mode has stricter limits (no cursor/offset/top-level where/order).
47
+ 15. Update/delete without `where` throws unless `allowFullScan()`.
48
+ 16. `count()`, `aggregate()`, and `groupBy()` require a matching `aggregateIndex`. Use `groupBy({ by, _count, _sum })` instead of multiple `.count()` calls or `findMany` + manual JS grouping. Every `by` field must be finite-constrained (`eq`/`in`/`isNull`) in `where`. See `references/features/aggregates.md`.
49
+ 17. cRPC React queries are real-time by default (`subscribe: true`); never use `queryClient.invalidateQueries` for these subscribed paths.
50
+ 18. In RSC, `prefetch` hydrates client, `caller` is server-only and not hydrated, `preloadQuery` hydrates but can cause stale split ownership if also rendered client-side.
51
+ 19. Better Auth Next.js shortcut is `convexBetterAuth(...)`; generic server-only shortcut is `createCallerFactory(...)`.
52
+ 20. On the kitcn auth client path, use `createAuthMutations(authClient)` wrappers so logout unsubscribes auth queries before sign out. Raw Convex preset keeps a smaller plain `authClient`.
53
+ 21. **NEVER** use `ctx.runQuery`/`ctx.runMutation`/`ctx.runAction` directly for module-to-module calls. Use the generated runtime helpers from `convex/functions/generated/<module>.runtime`.
54
+ 22. **`create<Module>Handler(ctx)`** is the default in queries/mutations: zero overhead, query/mutation ctx only, and no redundant validation or middleware.
55
+ 23. **`create<Module>Caller(ctx)`** is for actions and HTTP routes. Action procedures live under `caller.actions.*`; scheduling lives under `caller.schedule.now|after|at|cancel`. Use `requireActionCtx(ctx)` only for true `ActionCtx` callbacks; use `requireSchedulerCtx(ctx)` when mutation or action contexts can schedule. Each caller/handler eagerly loads its module, so split large modules.
56
+ 24. API types (`Api`, `ApiInputs`, `ApiOutputs`, `Select`, `Insert`, `TableName`) import from `@convex/api` — no manual `inferApiInputs<typeof api>`.
57
+ 25. HTTP router must export as `httpRouter` (not `appRouter`) for codegen.
58
+ 26. Server wiring imports come from `convex/functions/generated/` directory: `getAuth`, `defineAuth` from `generated/auth`; `initCRPC`, `QueryCtx`, `MutationCtx`, `OrmCtx` from `generated/server`; `create<Module>Caller`, `create<Module>Handler` from `generated/<module>.runtime`. No manual `convex/lib/orm.ts`.
59
+ 27. `defineAuth(() => ({ ...options, triggers }))` replaces split `getAuthOptions` + `authTriggers`. Trigger callbacks are doc-first: `beforeCreate(data)`, `onCreate(doc)`, `onUpdate(newDoc, oldDoc)` — no `ctx` first param.
60
+ 28. Internal auth functions at `internal.generated.*` (not `internal.auth.*`).
61
+ 29. Async mutation batching is the default (codegen wires it). Customize per call: `execute({ batchSize, delayMs })`. Opt into sync: `execute({ mode: 'sync' })` or `defineSchema(..., { defaults: { mutationExecutionMode: 'sync' } })`. Relevant defaults: `mutationBatchSize`, `mutationLeafBatchSize`, `mutationMaxRows`, `mutationScheduleCallCap`.
62
+ 30. Polymorphic unions are schema-first: use `actionType: discriminator({ variants, as? })` in `convexTable(...)`. Query config does not include a `polymorphic` option. Writes stay flat; reads synthesize nested `details` (or custom alias). Use `withVariants: true` to auto-load all `one()` relations on discriminator tables.
63
+ 31. Do not add manual ORM mutation batching loops in app/plugin code by default. Convex runtime batching already handles mutation execution. Prefer set-based deletes/updates over per-row loops. Only add explicit chunking when batching external side effects (for example Resend API calls) or bounded cleanup sweeps.
63
64
  ## Directory Boundary
64
65
  Use `references/setup/` when the task needs:
65
66
  1. Project/file structure setup → `setup/index.md` + `setup/server.md`
@@ -314,7 +315,10 @@ Use this map consistently:
314
315
  4. `NOT_FOUND`: missing or inaccessible resource.
315
316
  5. `CONFLICT`: duplicate or conflicting write.
316
317
  6. `TOO_MANY_REQUESTS`: rate limit.
317
- 7. `INTERNAL_SERVER_ERROR`: unexpected failures only.
318
+ 7. `INTERNAL_SERVER_ERROR`: unexpected failures only. cRPC also raises it for a
319
+ failed `.output(...)` parse, with message `Output validation failed` and
320
+ sanitized structural Zod issues in `error.data.ZodError`. Custom issue
321
+ messages and fields stay server-side because they can contain handler output.
318
322
  8. Add small custom `data` payloads on `CRPCError` when the client needs
319
323
  domain metadata like conflicting ids. Read them on the client from
320
324
  `error.data`.
@@ -454,6 +458,7 @@ Before calling a feature done:
454
458
  | Infinite list with TanStack native hook directly | Use `useInfiniteQuery` from `kitcn/react` |
455
459
  | Primitive root input (`z.string()`) | Use root `z.object(...)` input schema |
456
460
  | Returning nothing with `z.void()` | Omit explicit output |
461
+ | Returning a possibly-missing lookup under `.output(...nullable())` | Coalesce it: `?? null`. `.output(...)` substitutes nothing for `undefined` |
457
462
  | Manual pagination wrappers for infinite endpoints | Use `.paginated({ limit, item })` |
458
463
  | Synthetic Convex IDs in tests (`"missing-id"`) | Use inserted IDs or semantic lookup keys |
459
464
  | Aggregates disabled but helper/config still present | Remove aggregate helper + `defineTriggers` handlers + app config together |