kitcn 0.32.2 → 0.33.1

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 (47) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/dist/aggregate/index.d.ts +2 -2
  3. package/dist/auth/generated/index.d.ts +1 -1
  4. package/dist/auth/index.d.ts +16 -16
  5. package/dist/auth/index.js +1 -1
  6. package/dist/auth/nextjs/index.d.ts +2 -2
  7. package/dist/auth/start/server/index.d.ts +2 -2
  8. package/dist/{builder-Cyd5nCxZ.js → builder-DDYTlmTB.js} +12 -3
  9. package/dist/caller-factory-C3pcbzo0.d.ts +140 -0
  10. package/dist/{capabilities-BJm_VSDT.d.ts → capabilities-Bxzoofl4.d.ts} +149 -9
  11. package/dist/crpc/index.d.ts +3 -2
  12. package/dist/{generated-contract-disabled-BRm1dNQE.d.ts → generated-contract-disabled-B_EaHGCP.d.ts} +33 -33
  13. package/dist/{http-types-BhhPZtwa.d.ts → http-types-8FBr10yl.d.ts} +3 -79
  14. package/dist/{middleware-BBS7oLtT.d.ts → middleware-D2hKJuSZ.d.ts} +1 -1
  15. package/dist/{middleware-D61Nm-iB.js → middleware-LvZov-hA.js} +1 -1
  16. package/dist/orm/aggregate-index/index.d.ts +1 -1
  17. package/dist/orm/aggregate-index/index.js +226 -96
  18. package/dist/orm/index.d.ts +2 -2
  19. package/dist/orm/index.js +364 -108
  20. package/dist/orm/migrations/index.d.ts +2 -2
  21. package/dist/plugins/index.d.ts +1 -1
  22. package/dist/plugins/index.js +1 -1
  23. package/dist/{procedure-caller-DgMiD0-a.js → procedure-caller-DgGF_hA8.js} +1 -1
  24. package/dist/{procedure-name-DPYyLiVS.d.ts → procedure-name-kn-awLk1.d.ts} +12 -142
  25. package/dist/{query-context-D4z1CnDH.js → query-context-DQLv58LH.js} +34 -1
  26. package/dist/ratelimit/index.d.ts +2 -2
  27. package/dist/ratelimit/index.js +2 -2
  28. package/dist/react/index.d.ts +5 -6
  29. package/dist/react/index.js +24 -69
  30. package/dist/rsc/index.d.ts +4 -3
  31. package/dist/{schema-Bh7AmJwY.js → schema-BF4P0ZjS.js} +322 -1
  32. package/dist/server/index.d.ts +5 -4
  33. package/dist/server/index.js +2 -2
  34. package/dist/solid/index.d.ts +5 -6
  35. package/dist/solid/index.js +20 -66
  36. package/dist/transformer-BEkxlnss.d.ts +78 -0
  37. package/dist/{types-BXpu7xIU.d.ts → types-Cl0utsTL.d.ts} +4 -3
  38. package/dist/{where-clause-compiler-Dgf4lrO-.d.ts → where-clause-compiler-CoPVAVDO.d.ts} +136 -136
  39. package/package.json +1 -1
  40. package/skills/kitcn/SKILL.md +2 -1
  41. package/skills/kitcn/references/features/aggregates.md +13 -0
  42. package/skills/kitcn/references/features/auth-admin.md +1 -0
  43. package/skills/kitcn/references/features/orm.md +24 -4
  44. package/skills/kitcn/references/features/react.md +4 -5
  45. /package/dist/{context-utils-Cbv4r0AA.d.ts → context-utils-ax351iFc.d.ts} +0 -0
  46. /package/dist/{token-tlbNKQS0.d.ts → token-CZpDQUHd.d.ts} +0 -0
  47. /package/dist/{types-Bkeicpo6.d.ts → types-CzGsrLrY.d.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitcn",
3
- "version": "0.32.2",
3
+ "version": "0.33.1",
4
4
  "description": "kitcn - React Query integration and CLI tools for Convex",
5
5
  "keywords": [
6
6
  "convex",
@@ -34,7 +34,7 @@ Only remember these non-parity deltas:
34
34
  2. No `z.void()` outputs; omit `.output(...)` for no-value mutations.
35
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
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 }`.
37
+ 5. `.paginated({ limit, item })` must be before `.query()` and adds `input.cursor`, `input.endCursor`, and `input.limit`; pass all three to ORM cursor queries so live splits stay bounded.
38
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
39
  7. Auth metadata drives client behavior: `auth: "optional"` waits for auth load then runs, `auth: "required"` waits then skips when logged out.
40
40
  8. `ctx.orm` enforces constraints + RLS; `ctx.db` bypasses them.
@@ -245,6 +245,7 @@ export const listProjects = authQuery
245
245
  where: { ownerId: ctx.userId },
246
246
  orderBy: { updatedAt: "desc" },
247
247
  cursor: input.cursor,
248
+ endCursor: input.endCursor,
248
249
  limit: input.limit,
249
250
  })
250
251
  );
@@ -58,6 +58,19 @@ const orders = convexTable(
58
58
 
59
59
  After deploying, CLI runs `aggregateBackfill` automatically. Wait for `aggregateBackfillStatus` (an internal query) to report `READY`.
60
60
 
61
+ ### Write costs
62
+
63
+ Bulk ORM statements reuse aggregate bucket/member reads within uninterrupted
64
+ statements: one bucket read per distinct tuple and one membership read per
65
+ document. User hooks, policy callbacks and statement exit end reuse, preserving
66
+ nested-mutation writes. Raw writer calls do not share cached rows.
67
+
68
+ Shared bucket/extrema writes fold within statements. Aggregate reads (including
69
+ returning relation counts) and user hook/policy entry flush pending writes;
70
+ callbacks suspend batching until they settle so nested functions see stored
71
+ writes. Reads/callbacks can increase flush count. Membership rows remain one
72
+ write per changed document.
73
+
61
74
  ### `count()` — O(1) No-Scan Counts
62
75
 
63
76
  ```ts
@@ -265,6 +265,7 @@ export const getAllUsers = authQuery
265
265
  .query(async ({ ctx, input }) => {
266
266
  const result = await ctx.orm.query.user.findMany({
267
267
  cursor: input.cursor,
268
+ endCursor: input.endCursor,
268
269
  limit: input.limit,
269
270
  });
270
271
 
@@ -598,7 +598,9 @@ Per source:
598
598
 
599
599
  - `index: { name, range }` anchors that source on its own range. It overrides
600
600
  the chain-level `.withIndex(...)`; sources that omit it use the chain index.
601
- - `where` filters that source's rows after the read.
601
+ - `where` is compiled against the table's indexes like a `findMany` where. It
602
+ bounds the read when it can be lowered onto the index that source walks;
603
+ whatever no index covers is filtered after the read.
602
604
 
603
605
  `interleaveBy` fields must be the trailing fields each source is already ordered
604
606
  by, so every field before them has to be pinned with `eq` in that source's
@@ -607,8 +609,18 @@ fields — e.g. `by_author_likes` (authorId, numLikes) with `eq("authorId", ...)
607
609
  merges with `numLikesAndType` (type, numLikes) with `eq("type", ...)` under
608
610
  `interleaveBy(["numLikes"])`.
609
611
 
610
- Anchor every source. A shared `.withIndex(...)` plus per-source `where` makes
611
- each source walk the same range and discard the misses after reading them.
612
+ Whether a source `where` bounds the read depends on the index that source ends
613
+ up walking. With no index pinned it picks one, so `{ where: { status: "active" } }`
614
+ reads the `by_status` range. With a pinned index it may only narrow that same
615
+ index: `.withIndex("by_status")` plus `{ where: { status: "active" } }` narrows,
616
+ while `.withIndex("by_name")` plus the same `where` walks `by_name` and discards
617
+ the misses.
618
+
619
+ A pinned `range` is the source's scope, never a hint: a `where` alongside one
620
+ always filters after the read rather than widening it. Lowering also has to
621
+ preserve the merge, so a `where` is only lowered when the resulting order can
622
+ still supply `interleaveBy`. Anchor a source with `index: { name, range }`
623
+ whenever you need a specific range read.
612
624
 
613
625
  ### Pre-pagination transforms
614
626
 
@@ -630,6 +642,14 @@ const page = await ctx.orm.query.users
630
642
  .paginate({ cursor: null, limit: 20 });
631
643
  ```
632
644
 
645
+ Children are read through an index that leads with the relation's foreign key.
646
+ A stage `where` rides that index when one extends it — `where: { numLikes: { gt: 10 } }`
647
+ on `posts` reads the `by_author_likes` (authorId, numLikes) range instead of
648
+ every post by the author. Children then arrive in that index's order, so a
649
+ lowered range field orders them ahead of creation time. Declare an index whose
650
+ fields are `(...foreign key, ...filtered fields)` for the stage filters you
651
+ page on.
652
+
633
653
  See [Select Composition Limitations](#select-composition-limitations) in API Reference.
634
654
 
635
655
  ## Pagination Modes
@@ -675,7 +695,7 @@ const page = await ctx.orm.query.users.withIndex("by_status").findMany({
675
695
  });
676
696
  ```
677
697
 
678
- Falls back to a bounded scan (needs `maxScan`) when the probed index cannot supply the requested `orderBy`, or when the union is wider than 64 ranges.
698
+ Falls back to a bounded scan (needs `maxScan`) when the probed index cannot supply the requested `orderBy`, or when a union wider than 64 ranges is asked for an `orderBy` that sorts across values, such as `createdAt`. Up to 64 ranges are read as one merged stream; a wider union is read one range after another, which keeps it index-bounded at any list length but can only produce the index's own order.
679
699
 
680
700
  Without an `orderBy`, an index-union page is in the order of the index it walks — grouped by the probed value — not in creation order.
681
701
 
@@ -374,20 +374,19 @@ export const list = publicQuery
374
374
  .input(z.object({ userId: z.string().optional() }))
375
375
  .paginated({ limit: 20, item: SessionSchema })
376
376
  .query(async ({ ctx, input }) => {
377
- // input.cursor and input.limit auto-added
378
377
  return ctx.orm.query.session.findMany({
379
378
  where: input.userId ? { userId: input.userId } : undefined,
380
379
  orderBy: { createdAt: 'desc' },
381
380
  cursor: input.cursor,
381
+ endCursor: input.endCursor,
382
382
  limit: input.limit,
383
383
  });
384
- // output auto-wrapped as { continueCursor, isDone, page }
385
384
  });
386
385
  ```
387
386
 
388
387
  `.paginated({ limit, item })`:
389
- - Adds `cursor` (string|null) and `limit` (number) to input
390
- - Auto-sets output schema: `{ continueCursor: string, isDone: boolean, page: T[] }`
388
+ - Adds `cursor`, optional `endCursor`, and `limit` to input
389
+ - Accepts the full Convex page result, including `pageStatus` and `splitCursor`
391
390
  - Must be called before `.query()`
392
391
 
393
392
  ### Return Value
@@ -414,7 +413,7 @@ const { data, isPlaceholderData } = useInfiniteQuery(
414
413
 
415
414
  ### Real-time & Error Recovery
416
415
 
417
- Each page maintains its own WebSocket subscription. Auto-recovers on `InvalidCursor` (resets to page 0) and `splitCursor` (auto-splits page). Pagination state persists in `queryClient` for scroll restoration.
416
+ Each page maintains its own WebSocket subscription. Split pages use `endCursor` to keep adjacent subscriptions bounded. An `InvalidCursor` on a loaded page resets the list cleanly to page 0. Pagination state persists in `queryClient` for scroll restoration.
418
417
 
419
418
  ---
420
419