kitcn 0.25.5 → 0.25.7

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.
@@ -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 |