okmodel 0.6.0 → 0.7.0

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 (157) hide show
  1. package/README.md +100 -1131
  2. package/dist/adapters/capabilities.d.ts +3 -0
  3. package/dist/adapters/failure.d.ts +13 -2
  4. package/dist/adapters/lazy-errors.d.ts +22 -0
  5. package/dist/adapters/pg/bunsql-extra.d.ts +54 -3
  6. package/dist/adapters/pg/bunsql.d.ts +14 -1
  7. package/dist/adapters/pg/cursor.d.ts +17 -0
  8. package/dist/adapters/pg/no-inflight-timeout.d.ts +11 -8
  9. package/dist/adapters/pg/nodepostgres-extra.d.ts +22 -1
  10. package/dist/adapters/pg/nodepostgres.d.ts +12 -0
  11. package/dist/adapters/pg/pglite-wire.d.ts +24 -0
  12. package/dist/adapters/pg/pglite.d.ts +10 -0
  13. package/dist/adapters/pg/postgres-extra.d.ts +27 -11
  14. package/dist/adapters/pg/reserved.d.ts +42 -0
  15. package/dist/adapters/pg/session.d.ts +0 -18
  16. package/dist/contracts/connection.d.ts +7 -6
  17. package/dist/dialects/pg/floor.d.ts +68 -0
  18. package/dist/dialects/pg/index.d.ts +6 -1
  19. package/dist/dialects/pg/index.js +49 -159
  20. package/dist/dialects/pg/keys.d.ts +11 -8
  21. package/dist/dialects/pg/model.d.ts +40 -0
  22. package/dist/dialects/pg/morph.d.ts +43 -0
  23. package/dist/dialects/pg/operator-fit.d.ts +2 -2
  24. package/dist/dialects/pg/operators.d.ts +14 -7
  25. package/dist/dialects/pg/ops/bucket.d.ts +65 -0
  26. package/dist/dialects/pg/ops/inc.d.ts +2 -2
  27. package/dist/dialects/pg/ops/matches.d.ts +9 -12
  28. package/dist/dialects/pg/ops/relevance.d.ts +13 -0
  29. package/dist/dialects/pg/ops/search-query.d.ts +22 -0
  30. package/dist/dialects/pg/ops/window.d.ts +103 -0
  31. package/dist/dialects/pg/preset.d.ts +1 -1
  32. package/dist/dialects/pg/relations.d.ts +100 -3
  33. package/dist/dialects/pg/schema.d.ts +31 -3
  34. package/dist/dialects/pg/view/index.js +1 -1
  35. package/dist/runtime/aggregate.d.ts +8 -2
  36. package/dist/runtime/bucket-group.d.ts +70 -0
  37. package/dist/runtime/conflict.d.ts +22 -4
  38. package/dist/runtime/include.d.ts +0 -8
  39. package/dist/runtime/inflight.d.ts +1 -1
  40. package/dist/runtime/page.d.ts +4 -1
  41. package/dist/runtime/pg/bun.js +12 -6
  42. package/dist/runtime/pg/pg.js +5 -5
  43. package/dist/runtime/pg/pglite.d.ts +1 -0
  44. package/dist/runtime/pg/pglite.js +9 -9
  45. package/dist/runtime/pg/postgresjs.js +5 -5
  46. package/dist/runtime/plan.d.ts +95 -5
  47. package/dist/runtime/read-stream.d.ts +13 -16
  48. package/dist/runtime/result-name.d.ts +17 -0
  49. package/dist/runtime/server-check.d.ts +3 -3
  50. package/dist/runtime/sort-bind.d.ts +21 -0
  51. package/dist/runtime/sort.d.ts +56 -0
  52. package/dist/runtime/sync.d.ts +70 -0
  53. package/dist/runtime/tenancy/index.js +7 -5
  54. package/dist/runtime/text-search.d.ts +20 -0
  55. package/dist/runtime/traits/index.d.ts +80 -0
  56. package/dist/runtime/traits/index.js +175 -11
  57. package/dist/runtime/tree.d.ts +38 -0
  58. package/dist/runtime/types.d.ts +218 -22
  59. package/dist/runtime/validate/index.js +2 -2
  60. package/dist/runtime/version.d.ts +39 -0
  61. package/dist/runtime/window.d.ts +21 -0
  62. package/dist/runtime/write.d.ts +54 -2
  63. package/dist/shared/{wkme6tep.js → 14vx95q4.js} +8 -16
  64. package/dist/shared/1est4ezg.js +313 -0
  65. package/dist/shared/{4az561fn.js → 1qa0qajz.js} +8 -23
  66. package/dist/shared/{2cg3wnxc.js → 1w508jbr.js} +593 -130
  67. package/dist/shared/2c13q96h.js +18 -0
  68. package/dist/shared/{b4v57v3x.js → 2d3xq4ps.js} +2 -2
  69. package/dist/shared/{1nw1f3vs.js → 2shdbn8q.js} +7 -7
  70. package/dist/shared/3v7t1yrv.js +208 -0
  71. package/dist/shared/{x3jc53kg.js → 5e3tf63j.js} +2 -2
  72. package/dist/shared/5fe3kbsh.js +10 -0
  73. package/dist/shared/5v3xf80k.js +37 -0
  74. package/dist/shared/{c58secgj.js → 63qtkrca.js} +38 -4
  75. package/dist/shared/{2z7wv5tj.js → 67txcrcw.js} +55 -34
  76. package/dist/shared/79dt02vc.js +12 -0
  77. package/dist/shared/{8x72s86k.js → 7k9grvf6.js} +10 -4
  78. package/dist/shared/7kms6fvj.js +20 -0
  79. package/dist/shared/7mnme5tn.js +8 -0
  80. package/dist/shared/7n7ddwt4.js +6 -0
  81. package/dist/shared/{5j36z8dn.js → 81ebdx9p.js} +39 -70
  82. package/dist/shared/{knmes2kh.js → 8skf0cam.js} +1 -1
  83. package/dist/shared/9a4ae4p8.js +31 -0
  84. package/dist/shared/9y3mmmh1.js +436 -0
  85. package/dist/shared/ab9gmbwy.js +213 -0
  86. package/dist/shared/{f0f1n281.js → ax7qv55k.js} +2 -2
  87. package/dist/shared/{g90m9b0g.js → aytqh5y2.js} +6 -5
  88. package/dist/shared/{vjpnzwjz.js → b5pftnn2.js} +1 -1
  89. package/dist/shared/{ag4c7516.js → b76b8d95.js} +2 -2
  90. package/dist/shared/{vmfkhszw.js → bg3rym3g.js} +33 -26
  91. package/dist/shared/{mb2ycs1p.js → bm22fvzr.js} +15 -15
  92. package/dist/shared/{q3j1tq7a.js → bz84tkzm.js} +148 -23
  93. package/dist/shared/{31vm914h.js → cm3w0ywp.js} +15 -16
  94. package/dist/shared/cn957f8d.js +179 -0
  95. package/dist/shared/{gwn6v667.js → cnkky55j.js} +25 -14
  96. package/dist/shared/{ys0xzyaj.js → dkxgzaqd.js} +224 -19
  97. package/dist/shared/dzj09cye.js +10 -0
  98. package/dist/shared/exn3sd90.js +359 -0
  99. package/dist/shared/g6evj1j3.js +66 -0
  100. package/dist/shared/ga9j92qj.js +25 -0
  101. package/dist/shared/{t3g0k88x.js → gs74dbj5.js} +16 -12
  102. package/dist/shared/gzaveazc.js +285 -0
  103. package/dist/shared/{2jd1sn0e.js → hyh73qk6.js} +1 -1
  104. package/dist/shared/{r1fdd0f6.js → je4axnm8.js} +5 -2
  105. package/dist/shared/{1f0xa3cb.js → jvd0mqag.js} +3 -3
  106. package/dist/shared/jznfax0y.js +155 -0
  107. package/dist/shared/{cgbamx3e.js → kb2sdhfb.js} +7 -4
  108. package/dist/shared/{wt5rzv1h.js → kr0jzbw2.js} +5 -5
  109. package/dist/shared/{p6bjzq9q.js → ktz42yez.js} +12 -4
  110. package/dist/shared/{p248q79y.js → m6p12kbr.js} +2 -2
  111. package/dist/shared/{r6nwkxnt.js → m7n9h2a5.js} +4 -4
  112. package/dist/shared/{672t5vym.js → mss6ctgv.js} +2 -2
  113. package/dist/shared/{929h19xp.js → mtaszv5a.js} +40 -10
  114. package/dist/shared/{b3awxn7h.js → n816peh1.js} +1 -1
  115. package/dist/shared/nbw1z4yd.js +18 -0
  116. package/dist/shared/{nx9hh281.js → p6qc5gcp.js} +12 -11
  117. package/dist/shared/{pjr6xrm5.js → pbp1ss85.js} +20 -17
  118. package/dist/shared/pt94kz7f.js +6 -0
  119. package/dist/shared/q5c6ngn3.js +13 -0
  120. package/dist/shared/{xq0mvhts.js → rs73fca9.js} +4 -4
  121. package/dist/shared/{r11n8grd.js → sfz1ksn6.js} +7 -4
  122. package/dist/shared/{wynzeggs.js → svncdeh5.js} +6 -6
  123. package/dist/shared/{j60w0dvr.js → tbrvgw6z.js} +57 -38
  124. package/dist/shared/te5as7z7.js +622 -0
  125. package/dist/shared/{ttmg09vd.js → tsvww22q.js} +1 -1
  126. package/dist/shared/{y2t3jktd.js → ttsqgp4p.js} +19 -12
  127. package/dist/shared/{x72jb7ad.js → v8g1vgv8.js} +1 -1
  128. package/dist/shared/{sbdme3vy.js → vb6qfyve.js} +60 -10
  129. package/dist/shared/{5d8t84hg.js → vczs8gd4.js} +14 -14
  130. package/dist/shared/{5yv4k6a6.js → vqrjpcqn.js} +144 -2
  131. package/dist/shared/{3fqwmr09.js → w72kgg4q.js} +4 -4
  132. package/dist/shared/{1z04fa85.js → w79hsckn.js} +12 -57
  133. package/dist/shared/{09p049xa.js → wa07eas9.js} +42 -14
  134. package/dist/shared/{32cnm7kn.js → x933k9a3.js} +74 -38
  135. package/dist/shared/{n076ydsc.js → xhfsfkgk.js} +62 -28
  136. package/dist/shared/xjk7hnn5.js +272 -0
  137. package/dist/shared/{hazqqecq.js → xnxsed62.js} +50 -21
  138. package/dist/shared/y6njgyda.js +249 -0
  139. package/dist/shared/{8etn7d8w.js → z7s6fpxv.js} +1 -1
  140. package/dist/shared/{n7mcc1gk.js → zchaghkf.js} +4 -3
  141. package/dist/shared/zm0ptry0.js +40 -0
  142. package/dist/shared/{nsva2a6b.js → znm0bpp5.js} +3 -0
  143. package/dist/shared/zp9fmend.js +145 -0
  144. package/dist/tooling/cli.js +11 -11
  145. package/dist/tooling/migrate/engine.d.ts +3 -2
  146. package/dist/tooling/migrate/index.js +7 -7
  147. package/dist/tooling/testing/index.js +1 -1
  148. package/package.json +4 -2
  149. package/dist/shared/3qnhc7w5.js +0 -38
  150. package/dist/shared/7m88x3e9.js +0 -45
  151. package/dist/shared/8dqhth8c.js +0 -12
  152. package/dist/shared/d5azex6s.js +0 -6
  153. package/dist/shared/j3rkk835.js +0 -99
  154. package/dist/shared/k68eedmy.js +0 -85
  155. package/dist/shared/venqrjna.js +0 -216
  156. package/dist/shared/wxr8cwns.js +0 -6
  157. package/dist/shared/zm0mafk4.js +0 -92
@@ -2,7 +2,7 @@ import"../../shared/qq3899ht.js";
2
2
  import {
3
3
  checkPresetNames,
4
4
  duplicatePreset
5
- } from "../../shared/nsva2a6b.js";
5
+ } from "../../shared/znm0bpp5.js";
6
6
  import {
7
7
  checkTrait
8
8
  } from "../../shared/n0dgs0pb.js";
@@ -17,11 +17,12 @@ import {
17
17
  triggerObject
18
18
  } from "../../shared/y34a8714.js";
19
19
  import {
20
+ integer2,
20
21
  timestamptz2
21
- } from "../../shared/5yv4k6a6.js";
22
+ } from "../../shared/vqrjpcqn.js";
22
23
  import {
23
24
  uuid2
24
- } from "../../shared/2jd1sn0e.js";
25
+ } from "../../shared/hyh73qk6.js";
25
26
  import {
26
27
  snakeCase
27
28
  } from "../../shared/rpycarcr.js";
@@ -41,10 +42,12 @@ import {
41
42
  queryHandle,
42
43
  attachHttp,
43
44
  settleCall
44
- } from "../../shared/j60w0dvr.js";
45
+ } from "../../shared/tbrvgw6z.js";
45
46
  import {
46
- installArchiveInspect
47
- } from "../../shared/2z7wv5tj.js";
47
+ installArchiveInspect,
48
+ indexes,
49
+ isRecord
50
+ } from "../../shared/67txcrcw.js";
48
51
  import {
49
52
  fitIdentifier2
50
53
  } from "../../shared/nfzc5z0w.js";
@@ -465,13 +468,13 @@ function refuse(host, table) {
465
468
  }
466
469
  var archiveMod;
467
470
  function loadArchive() {
468
- archiveMod ??= import("../../shared/mb2ycs1p.js");
471
+ archiveMod ??= import("../../shared/bm22fvzr.js");
469
472
  return archiveMod;
470
473
  }
471
474
  async function settle(host, table, mods) {
472
475
  if (mods.uses === undefined)
473
476
  return mods;
474
- const { resolve } = await import("../../shared/r6nwkxnt.js");
477
+ const { resolve } = await import("../../shared/m7n9h2a5.js");
475
478
  return { ...mods, presets: resolve(host.schema, table, mods.uses).wheres };
476
479
  }
477
480
  async function run(host, op, table, input, options, mods) {
@@ -484,6 +487,93 @@ async function run(host, op, table, input, options, mods) {
484
487
  }
485
488
  }
486
489
 
490
+ // src/runtime/sort-bind.ts
491
+ function rewriteSortable(self, tables, casing, schemaTraits, group) {
492
+ return tables.map((item) => {
493
+ if (!applies2(self, item, schemaTraits))
494
+ return item;
495
+ refusePath(item);
496
+ return withIndex(item, casing, schemaTraits, group);
497
+ });
498
+ }
499
+ function applies2(self, item, schemaTraits) {
500
+ const options = item.options;
501
+ const own = Array.isArray(options?.traits) ? options.traits : [];
502
+ if (own.includes(self))
503
+ return true;
504
+ if (options?.omitDefaults !== undefined)
505
+ return false;
506
+ return schemaTraits?.includes(self) ?? false;
507
+ }
508
+ function refusePath(item) {
509
+ const tenancy = item.options?.tenancy;
510
+ if (typeof tenancy === "object" && tenancy !== null && "via" in tenancy) {
511
+ definition(`sortable() on ${item.name} does not combine with via(). Position needs the tenant columns on the row.`);
512
+ }
513
+ }
514
+ function withIndex(item, casing, schemaTraits, group) {
515
+ const options = item.options;
516
+ const prior = options?.indexes;
517
+ const tenantKeys = options?.tenantKeys ?? [];
518
+ const fields = [];
519
+ for (const field of tenantKeys)
520
+ if (!fields.includes(field))
521
+ fields.push(field);
522
+ if (group !== undefined && !fields.includes(group))
523
+ fields.push(group);
524
+ fields.push("position");
525
+ const predicate = archivedPredicate(item, casing, schemaTraits);
526
+ const indexes = (handles) => {
527
+ const calls = prior === undefined ? [] : prior(handles);
528
+ const columns = fields.map((field) => {
529
+ const handle = handles[field];
530
+ if (handle === undefined) {
531
+ definition(`sortable() on ${item.name} needs a column named ${field}.`);
532
+ }
533
+ return handle.name;
534
+ });
535
+ return [...Array.isArray(calls) ? calls : [], uniqueIndex(columns, predicate)];
536
+ };
537
+ return {
538
+ ...item,
539
+ options: { ...options, indexes }
540
+ };
541
+ }
542
+ function archivedPredicate(item, casing, schemaTraits) {
543
+ if (!archivable(item, schemaTraits))
544
+ return;
545
+ return casing === "snake" ? "(archived_at IS NULL)" : '("archivedAt" IS NULL)';
546
+ }
547
+ function archivable(item, schemaTraits) {
548
+ const options = item.options;
549
+ const own = Array.isArray(options?.traits) ? options.traits : [];
550
+ for (const trait of own)
551
+ if (marked2(trait))
552
+ return true;
553
+ if (options?.omitDefaults !== undefined)
554
+ return false;
555
+ if (schemaTraits === undefined)
556
+ return false;
557
+ for (const trait of schemaTraits)
558
+ if (marked2(trait))
559
+ return true;
560
+ return false;
561
+ }
562
+ function marked2(value) {
563
+ return value["~archive"] === true;
564
+ }
565
+ function uniqueIndex(columns, predicate) {
566
+ const call = {
567
+ columns,
568
+ isUnique: true,
569
+ ...predicate !== undefined ? { predicate } : {},
570
+ unique() {
571
+ return call;
572
+ }
573
+ };
574
+ return call;
575
+ }
576
+
487
577
  // src/runtime/traits/index.ts
488
578
  function trait(name, input) {
489
579
  if (input.presets !== undefined)
@@ -561,7 +651,7 @@ function timestamps(options) {
561
651
  function stamped() {
562
652
  return timestamptz2().defaultSql("now()").guarded();
563
653
  }
564
- function archivable(options) {
654
+ function archivable2(options) {
565
655
  installArchiveContributions();
566
656
  const cascade = readCascade(options);
567
657
  const base = trait("archivable", {
@@ -585,6 +675,78 @@ function archivable(options) {
585
675
  };
586
676
  return self;
587
677
  }
678
+ function versioned() {
679
+ return trait("versioned", {
680
+ fields: {
681
+ version: integer2().defaultSql("1").guarded()
682
+ },
683
+ sealed: ["version"]
684
+ });
685
+ }
686
+ function sortable(groupBy) {
687
+ if (groupBy !== undefined && groupBy.length === 0) {
688
+ definition("sortable() groupBy must be a column name.");
689
+ }
690
+ if (groupBy === "position")
691
+ definition("sortable() cannot group by position.");
692
+ const base = trait("sortable", {
693
+ fields: {
694
+ position: integer2().guarded()
695
+ },
696
+ sealed: ["position"]
697
+ });
698
+ const self = {
699
+ ...base,
700
+ ...groupBy !== undefined ? { group: groupBy } : {},
701
+ "~sort": true,
702
+ apply(model, ctx) {
703
+ if (groupBy !== undefined && !Object.hasOwn(model.columns, groupBy)) {
704
+ definition(`sortable() on ${ctx.table} needs a column named ${groupBy}.`);
705
+ }
706
+ base.apply(model, ctx);
707
+ },
708
+ rewrite(tables, casing, schemaTraits) {
709
+ return rewriteSortable(self, tables, casing, schemaTraits, groupBy);
710
+ },
711
+ hook: attachSort
712
+ };
713
+ return self;
714
+ }
715
+ function attachSort(target, ctx) {
716
+ const tableName = ctx.table;
717
+ const session = ctx.session;
718
+ if (tableName === undefined || session === undefined)
719
+ return;
720
+ const host = session;
721
+ const table = indexes(host.schema).get(tableName);
722
+ if (table === undefined)
723
+ return;
724
+ let sortableTable = false;
725
+ for (const item of table.model.traits ?? []) {
726
+ if (item.name === "sortable")
727
+ sortableTable = true;
728
+ }
729
+ if (!sortableTable)
730
+ return;
731
+ const order = { position: "asc" };
732
+ for (const field of table.model.primary) {
733
+ if (field !== "position")
734
+ order[field] = "asc";
735
+ }
736
+ wrapOrder(target, "find", order);
737
+ wrapOrder(target, "page", order);
738
+ const view = ctx.view;
739
+ target.move = (id, place) => import("../../shared/9y3mmmh1.js").then((mod) => mod.move(host, tableName, id, place, view));
740
+ }
741
+ function wrapOrder(target, key, order) {
742
+ const previous = target[key];
743
+ if (typeof previous !== "function")
744
+ return;
745
+ target[key] = (options = {}) => {
746
+ const next = isRecord(options) && options.orderBy !== undefined ? options : { ...options, orderBy: order };
747
+ return previous.call(target, next);
748
+ };
749
+ }
588
750
  function readCascade(options) {
589
751
  if (options === undefined)
590
752
  return [];
@@ -647,7 +809,9 @@ function addFields(name, fields, model, ctx) {
647
809
  }
648
810
  }
649
811
  export {
650
- archivable,
812
+ archivable2 as archivable,
813
+ sortable,
651
814
  timestamps,
652
- trait
815
+ trait,
816
+ versioned
653
817
  };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `descendants` and `ancestors` (spec §13 tool 11).
3
+ *
4
+ * One recursive statement walks a self-relation's parent column. A row already
5
+ * on the path is not entered again, so a cycle in the data returns. `depth`
6
+ * stops the walk earlier. `ltree` is not read: the extension is a later index,
7
+ * and this statement does not require it.
8
+ *
9
+ * Tenancy and the active set apply on the start row, on every step, and on
10
+ * the rows that come back. `where` and presets filter those rows and do not
11
+ * cut the walk short. A missing id returns no rows, including with `self`.
12
+ * The root has no ancestors. Loaded on the first call.
13
+ */
14
+ import type { ColumnModel } from "../dialects/pg/model.js";
15
+ import { type Mods, type Session } from "./client.js";
16
+ import { type ArchiveView, type Indexed } from "./plan.js";
17
+ /**
18
+ * Builds the handle for one `descendants` or `ancestors` call.
19
+ *
20
+ * @param session - Client session
21
+ * @param table - Table name
22
+ * @param options - The packed call: the id, the direction, and the caller's options
23
+ * @param mods - Presets, signal, and timeout
24
+ * @param view - Archive visibility
25
+ * @returns A handle that resolves to the walked rows
26
+ */
27
+ export declare function build(session: Session, table: string, options: object, mods: Mods, view: ArchiveView | undefined): Promise<unknown> & Record<string, unknown>;
28
+ /**
29
+ * The primary key the caller passes, without the tenant key.
30
+ *
31
+ * A tree walk and `sync` both address one column. A composite key beside the
32
+ * tenant key is refused.
33
+ *
34
+ * @param session - Client session, for the tenant key
35
+ * @param table - The table
36
+ * @returns That column
37
+ */
38
+ export declare function identityColumn(session: Session, table: Indexed): ColumnModel;
@@ -7,8 +7,10 @@
7
7
  import type { DriverTimeouts, Notice } from "../contracts/driver.js";
8
8
  import type { SafeResult } from "../contracts/error.js";
9
9
  import type { QuerySchema } from "../dialects/pg/model.js";
10
+ import type { Bucket } from "../dialects/pg/ops/bucket.js";
11
+ import type { Window } from "../dialects/pg/ops/window.js";
10
12
  import type { Inc } from "../dialects/pg/ops/inc.js";
11
- import type { ArrAppend, ArrRemove, JsonSet, Or, RelationFilter } from "../dialects/pg/operators.js";
13
+ import type { ArrAppend, ArrRemove, JsonSet, Or, RelationFilter, Relevance, SearchMatch } from "../dialects/pg/operators.js";
12
14
  import type { FieldWhere, IsJsonObject } from "../dialects/pg/where.js";
13
15
  import type { AppliedRule, ReadOp } from "./plan.js";
14
16
  export type { FieldWhere, WhereValue } from "../dialects/pg/where.js";
@@ -22,8 +24,20 @@ export type RelationsOf<S extends QuerySchema, K extends keyof S["~byName"]> = S
22
24
  } ? R : Record<string, never>;
23
25
  /** Array and JSON writes the column value type can accept. */
24
26
  type WriteOp<V> = [unknown] extends [V] ? JsonSet<unknown> | ArrAppend<unknown> | ArrRemove<unknown> : (V extends readonly (infer E)[] ? ArrAppend<E> | ArrRemove<E> : never) | (IsJsonObject<V> extends true ? JsonSet<unknown> : never);
25
- /** `has`, `none`, and `every` against the related row. */
27
+ /**
28
+ * `inc` on a number or bigint column.
29
+ *
30
+ * A `string` column is not included. `text` is a string, and so is `numeric`
31
+ * or `bigint` when the codec keeps the decimal text. The statement still
32
+ * accepts `inc` on a numeric database type.
33
+ */
34
+ type IncOf<V> = [unknown] extends [V] ? Inc<number> | Inc<bigint> : [NonNullable<V>] extends [number] ? Inc<number> : [NonNullable<V>] extends [bigint] ? Inc<bigint> : never;
35
+ /** `has`, `none`, and `every` against the related row. A morph include is not a filter. */
26
36
  export type RelFilter<S extends QuerySchema, Rel> = Rel extends {
37
+ readonly morph: {
38
+ readonly targets: readonly string[];
39
+ };
40
+ } ? never : Rel extends {
27
41
  readonly table: infer T extends string;
28
42
  } ? T extends keyof S["~byName"] ? RelationFilter<"has" | "none" | "every", FieldWhere<RowOf<S, T>>> : never : never;
29
43
  /** Known relation names. A string index (no declared relations) adds none. */
@@ -39,14 +53,34 @@ export type OrderBy<Row> = {
39
53
  readonly [K in keyof Row]?: "asc" | "desc" | {
40
54
  readonly dir: "asc" | "desc";
41
55
  readonly nulls?: "first" | "last";
42
- } | undefined;
56
+ } | Relevance | undefined;
57
+ };
58
+ /**
59
+ * Text to look for.
60
+ *
61
+ * A string is enough when the table has one column built for search. `in`
62
+ * names the column when it has more than one.
63
+ */
64
+ export type TextSearch<Row> = string | {
65
+ /** Text to look for. */
66
+ readonly for: string;
67
+ /** Column to search. Required when the table has more than one. */
68
+ readonly in?: keyof Row & string;
69
+ /** How to match. Omitted means `natural`. */
70
+ readonly match?: SearchMatch;
71
+ /** Text search language, such as `english`. Omitted uses the database default. */
72
+ readonly language?: string;
43
73
  };
44
74
  type Related<S extends QuerySchema, T extends keyof S["~byName"], Opt> = Opt extends {
45
75
  readonly select: infer Sel;
46
76
  } ? Selected<RowOf<S, T>, Sel> : RowOf<S, T>;
47
- /** One include. To-many needs `limit`. */
77
+ /** One include. To-many needs `limit`. A morph include is `true`. */
48
78
  export type IncludeOf<S extends QuerySchema, K extends keyof S["~byName"]> = {
49
79
  readonly [R in RelationKeys<RelationsOf<S, K>>]?: RelationsOf<S, K>[R] extends {
80
+ readonly morph: {
81
+ readonly targets: readonly string[];
82
+ };
83
+ } ? true : RelationsOf<S, K>[R] extends {
50
84
  readonly kind: "many";
51
85
  readonly table: infer T extends string;
52
86
  } ? T extends keyof S["~byName"] ? {
@@ -63,14 +97,40 @@ export type IncludeOf<S extends QuerySchema, K extends keyof S["~byName"]> = {
63
97
  readonly orderBy?: OrderBy<RowOf<S, T>>;
64
98
  } : never : never;
65
99
  };
100
+ /**
101
+ * Rows a morph include can return.
102
+ *
103
+ * Each target's row, plus the type field set to that table name, or null when
104
+ * the target row is missing.
105
+ */
106
+ /** Flattens an intersection so a morph arm is one object type. */
107
+ type Flat<T> = {
108
+ readonly [K in keyof T]: T[K];
109
+ };
110
+ /** One arm of a morph union. Distributes over the closed target list. */
111
+ type MorphArm<S extends QuerySchema, T extends string, Field extends string> = T extends keyof S["~byName"] ? Flat<RowOf<S, T> & {
112
+ readonly [P in Field]: T;
113
+ }> : never;
114
+ type MorphUnion<S extends QuerySchema, Targets extends readonly string[], Field extends string> = MorphArm<S, Targets[number], Field> | null;
66
115
  /** Columns kept by `select`. The full row when `select` is omitted. */
67
116
  export type Selected<Row, Sel> = [Sel] extends [readonly (infer P)[]] ? [P] extends [keyof Row] ? {
68
117
  readonly [K in P & keyof Row]: Row[K];
69
- } : Row : Row;
118
+ } : boolean extends HasWindow<P> ? Pick<Row, Extract<P, keyof Row>> & WindowFields<Row, P> : [P] extends [Window<string, string | number>] ? WindowFields<Row, P> : Row : Row;
119
+ /** `true` when a select entry is a window. A mix of fields and windows is `boolean`. */
120
+ type HasWindow<P> = P extends Window<string, string | number> ? true : false;
121
+ /** Result keys a window helper adds. A field name contributes nothing. */
122
+ type WindowFields<Row, P> = Intersect<P extends Window<infer A, infer V> ? {
123
+ readonly [K in A]: V extends keyof Row ? NonNullable<Row[V]> | null : number;
124
+ } : {}>;
70
125
  type IncludeRows<S extends QuerySchema, K extends keyof S["~byName"], Inc> = [Inc] extends [
71
126
  Record<string, unknown>
72
127
  ] ? {
73
128
  readonly [R in keyof Inc & keyof RelationsOf<S, K>]: RelationsOf<S, K>[R] extends {
129
+ readonly morph: {
130
+ readonly targets: infer Targets extends readonly string[];
131
+ readonly typeField: infer Field extends string;
132
+ };
133
+ } ? MorphUnion<S, Targets, Field> : RelationsOf<S, K>[R] extends {
74
134
  readonly kind: "many";
75
135
  readonly table: infer T extends keyof S["~byName"];
76
136
  } ? readonly Related<S, T, Inc[R]>[] : RelationsOf<S, K>[R] extends {
@@ -109,7 +169,8 @@ export type CallOptions = {
109
169
  /** Options shared by reads. */
110
170
  export type ReadOptions<S extends QuerySchema, K extends keyof S["~byName"]> = CallOptions & {
111
171
  readonly where?: WhereOf<S, K>;
112
- readonly select?: readonly (keyof RowOf<S, K> & string)[];
172
+ /** Field names, or a window helper (`rowNumber`, `rank`, `denseRank`, `runningTotal`). */
173
+ readonly select?: readonly ((keyof RowOf<S, K> & string) | Window<string, string | number>)[];
113
174
  readonly orderBy?: OrderBy<RowOf<S, K>>;
114
175
  readonly include?: IncludeOf<S, K>;
115
176
  };
@@ -118,6 +179,7 @@ export type ReadOptions<S extends QuerySchema, K extends keyof S["~byName"]> = C
118
179
  *
119
180
  * `wait` says what happens when a row is locked by another transaction: `nowait`
120
181
  * fails at once (kind `lock_timeout`) and `skip` leaves that row out.
182
+ * `lock: "skip"` is not a read lock. It claims rows on `update`.
121
183
  */
122
184
  export type LockOptions = {
123
185
  readonly lock?: "update" | "share";
@@ -126,6 +188,48 @@ export type LockOptions = {
126
188
  /** `find` options. `limit` is required unless the caller uses `.all(reason)`. */
127
189
  export type FindOptions<S extends QuerySchema, K extends keyof S["~byName"]> = ReadOptions<S, K> & {
128
190
  readonly limit?: number;
191
+ /** Text to look for. The closest matches come first unless `orderBy` is set. */
192
+ readonly search?: TextSearch<RowOf<S, K>>;
193
+ };
194
+ /**
195
+ * Options for `descendants` and `ancestors`.
196
+ *
197
+ * `depth` is how many steps from the starting row. Omit it to walk the whole
198
+ * tree. `self` includes that row. `relation` names which self-relation when
199
+ * the table has more than one.
200
+ */
201
+ export type TreeOptions<S extends QuerySchema, K extends keyof S["~byName"]> = CallOptions & {
202
+ readonly where?: WhereOf<S, K>;
203
+ readonly select?: readonly (keyof RowOf<S, K> & string)[];
204
+ readonly orderBy?: OrderBy<RowOf<S, K>>;
205
+ readonly depth?: number;
206
+ /** Include the starting row. Omitted leaves it out. */
207
+ readonly self?: boolean;
208
+ /** Which self-relation. Required when the table has more than one. */
209
+ readonly relation?: string;
210
+ };
211
+ /** Links `sync` added and removed. Values are the related row's primary key. */
212
+ export type SyncResult = {
213
+ readonly added: readonly unknown[];
214
+ readonly removed: readonly unknown[];
215
+ };
216
+ /** Options for `sync`. `expect` matches an update: the count is links added plus links removed. */
217
+ export type SyncOptions = CallOptions & {
218
+ readonly expect?: number;
219
+ };
220
+ /** The handle `sync` returns. */
221
+ export type SyncHandle = Promise<SyncResult> & {
222
+ /** The statement, before it runs. */
223
+ sql(): Promise<{
224
+ readonly statements: readonly {
225
+ readonly text: string;
226
+ readonly params?: readonly (string | null)[];
227
+ }[];
228
+ }>;
229
+ /** `{ ok, value }` or `{ ok, error }`. */
230
+ safe(): Promise<SafeResult<SyncResult>>;
231
+ /** Throws `not_found` when the number of added and removed links is not `count`. */
232
+ expect(count: number): SyncHandle;
129
233
  };
130
234
  /** A query handle. Await it, or inspect it before it runs. */
131
235
  export type Query<T> = Promise<T> & {
@@ -148,15 +252,27 @@ export type Read<T> = Query<T> & {
148
252
  all(reason: string): Read<T>;
149
253
  /** Turns a null `one()` into `not_found`. */
150
254
  required(): Read<NonNullable<T>>;
151
- /** Streams rows when the driver has `stream`. Otherwise OKM1111. */
152
- stream(): AsyncIterable<T extends readonly (infer R)[] ? R : T>;
255
+ /**
256
+ * Yields rows in keyset batches on every driver.
257
+ *
258
+ * `batch` is how many rows one query fetches. The default is 1000.
259
+ * `limit` on the find is the total. A table with no primary key is OKM1120.
260
+ */
261
+ stream(options?: {
262
+ readonly batch?: number;
263
+ }): AsyncIterable<T extends readonly (infer R)[] ? R : T>;
153
264
  };
154
265
  /** One page: the rows, and the cursor for the next page. `next` is `null` on the last page. */
155
266
  export type Page<Row> = {
156
267
  readonly items: readonly Row[];
157
268
  readonly next: string | null;
158
269
  };
159
- /** `page` options. `after` is the `next` of the page before. */
270
+ /**
271
+ * `page` options. `after` is the `next` of the page before.
272
+ *
273
+ * `page` refuses a window helper when the call runs (OKM1120). The keyset
274
+ * cursor has no window value to encode.
275
+ */
160
276
  export type PageOptions<S extends QuerySchema, K extends keyof S["~byName"]> = ReadOptions<S, K> & {
161
277
  /** Rows in the page. At least 1. */
162
278
  readonly limit: number;
@@ -172,25 +288,51 @@ export type PageOptions<S extends QuerySchema, K extends keyof S["~byName"]> = R
172
288
  type NumericKeys<Row> = {
173
289
  readonly [F in keyof Row]-?: [NonNullable<Row[F]>] extends [number | string] ? F : never;
174
290
  }[keyof Row] & string;
291
+ /** One `orderBy` value. `asc` is nulls last and `desc` is nulls first unless `nulls` is set. */
292
+ type SortDir = "asc" | "desc" | {
293
+ readonly dir: "asc" | "desc";
294
+ readonly nulls?: "first" | "last";
295
+ };
175
296
  /** `aggregate` options. A hidden field is refused at runtime. */
176
297
  export type AggregateOptions<S extends QuerySchema, K extends keyof S["~byName"]> = CallOptions & {
177
298
  readonly where?: WhereOf<S, K>;
178
299
  /** One result row per distinct combination. Needs `limit`, or `.all(reason)`. */
179
- readonly groupBy?: readonly (keyof RowOf<S, K> & string)[];
300
+ readonly groupBy?: readonly ((keyof RowOf<S, K> & string) | Bucket<string, string, string>)[];
180
301
  /** Adds `count`, the number of rows in the group. */
181
302
  readonly count?: true;
182
303
  readonly sum?: readonly NumericKeys<RowOf<S, K>>[];
183
304
  readonly avg?: readonly NumericKeys<RowOf<S, K>>[];
184
305
  readonly min?: readonly (keyof RowOf<S, K> & string)[];
185
306
  readonly max?: readonly (keyof RowOf<S, K> & string)[];
186
- /** Orders the groups. Only `groupBy` fields. Without it, groups come in `groupBy` order. */
187
- readonly orderBy?: OrderBy<RowOf<S, K>>;
307
+ /**
308
+ * Orders the groups. A `groupBy` field or a bucket `as`.
309
+ * Without it, groups come in `groupBy` order.
310
+ */
311
+ readonly orderBy?: OrderBy<RowOf<S, K>> | {
312
+ readonly [key: string]: SortDir | undefined;
313
+ };
188
314
  readonly limit?: number;
189
315
  };
316
+ /** `true` when a group entry is a bucket. A mix of fields and buckets is `boolean`. */
317
+ type HasBucket<G> = G extends Bucket<string, string, string> ? true : false;
318
+ /** One bucket key. A string group contributes nothing. */
319
+ type BucketPiece<Row, G> = G extends Bucket<infer C, infer U, infer A> ? {
320
+ readonly [K in A]: (U extends "second" | "minute" | "hour" ? Temporal.Instant : Temporal.PlainDate) | (C extends keyof Row ? (null extends Row[C] ? null : never) : null);
321
+ } : {};
322
+ /** Intersection of a union of group pieces. */
323
+ type Intersect<U> = (U extends unknown ? (arg: U) => void : never) extends (arg: infer I) => void ? I : unknown;
324
+ /**
325
+ * Group keys on an aggregate row.
326
+ *
327
+ * A plain field list stays `Pick`. A bucket list adds each `as` key. The value
328
+ * is `Temporal.PlainDate` for a calendar unit and `Temporal.Instant` for a
329
+ * clock unit, and includes `null` when the column does.
330
+ */
331
+ type GroupOf<Row, G> = boolean extends HasBucket<G> ? Pick<Row, Extract<G, keyof Row>> & Intersect<BucketPiece<Row, G>> : [G] extends [Bucket<string, string, string>] ? Intersect<BucketPiece<Row, G>> : Pick<Row, G & keyof Row>;
190
332
  /** A group: its fields, and the aggregates the call named. */
191
333
  export type AggregateRow<Row, O> = Show<(O extends {
192
334
  readonly groupBy: readonly (infer G)[];
193
- } ? Pick<Row, G & keyof Row> : unknown) & (O extends {
335
+ } ? GroupOf<Row, G> : unknown) & (O extends {
194
336
  readonly count: true;
195
337
  } ? {
196
338
  readonly count: number;
@@ -311,9 +453,15 @@ export type InsertOf<S extends QuerySchema, K extends keyof S["~byName"]> = S["~
311
453
  export type UpdateOf<S extends QuerySchema, K extends keyof S["~byName"]> = S["~byName"][K] extends {
312
454
  readonly "~update": infer U;
313
455
  } ? U : never;
314
- /** `set` values. `undefined` leaves the field unchanged. `inc` adds to it. `set` and `arr` write json and arrays. */
456
+ /**
457
+ * `set` values.
458
+ *
459
+ * `undefined` leaves the field unchanged. `null` sets null, and only on a
460
+ * nullable column. `inc` adds to a number or bigint column. A null number
461
+ * stays null. `json.set` and `arr` write json and arrays.
462
+ */
315
463
  export type UpdateSet<S extends QuerySchema, K extends keyof S["~byName"]> = {
316
- readonly [F in keyof UpdateOf<S, K>]?: UpdateOf<S, K>[F] | Inc<NonNullable<UpdateOf<S, K>[F]>> | WriteOp<NonNullable<UpdateOf<S, K>[F]>>;
464
+ readonly [F in keyof UpdateOf<S, K>]?: UpdateOf<S, K>[F] | IncOf<UpdateOf<S, K>[F]> | WriteOp<NonNullable<UpdateOf<S, K>[F]>>;
317
465
  };
318
466
  /** Conflict handling on `insert` (spec §11). */
319
467
  export type OnConflict<S extends QuerySchema, K extends keyof S["~byName"]> = "error" | "ignore" | {
@@ -337,7 +485,7 @@ export type InsertOptions<S extends QuerySchema, K extends keyof S["~byName"]> =
337
485
  };
338
486
  /** Options for `update` and `delete`. */
339
487
  export type WriteOptions<S extends QuerySchema, K extends keyof S["~byName"]> = {
340
- readonly returning?: readonly (keyof RowOf<S, K> & string)[];
488
+ readonly returning?: true | readonly (keyof RowOf<S, K> & string)[];
341
489
  /** Guarded fields this call may set. `delete` ignores it. */
342
490
  readonly allow?: readonly (keyof RowOf<S, K> & string)[];
343
491
  readonly expect?: number;
@@ -372,19 +520,32 @@ type AllowedSet<S extends QuerySchema, K extends keyof S["~byName"], Allow exten
372
520
  type AllowedUpdate<S extends QuerySchema, K extends keyof S["~byName"], Allow extends readonly (keyof RowOf<S, K> & string)[]> = {
373
521
  readonly where?: WhereOf<S, K>;
374
522
  readonly set: AllowedSet<S, K, Allow>;
523
+ readonly version?: number;
375
524
  } | readonly {
376
525
  readonly id?: unknown;
377
526
  readonly where?: WhereOf<S, K>;
378
527
  readonly set: AllowedSet<S, K, Allow>;
528
+ readonly version?: number;
379
529
  }[];
380
- /** One update, or a per-row list in one statement. */
530
+ /**
531
+ * One update, or a per-row list in one statement.
532
+ *
533
+ * `lock: "skip"` with `limit` claims that many matching rows and leaves rows
534
+ * another transaction already holds. It is not valid on a list.
535
+ */
381
536
  export type UpdateTarget<S extends QuerySchema, K extends keyof S["~byName"]> = {
382
537
  readonly where?: WhereOf<S, K>;
383
538
  readonly set: UpdateSet<S, K>;
539
+ readonly limit?: number;
540
+ readonly lock?: "skip";
541
+ /** Version read from the row. Required at runtime when the table is `versioned()`. */
542
+ readonly version?: number;
384
543
  } | readonly {
385
544
  readonly id?: unknown;
386
545
  readonly where?: WhereOf<S, K>;
387
546
  readonly set: UpdateSet<S, K>;
547
+ /** Version read from the row. Required at runtime when the table is `versioned()`. */
548
+ readonly version?: number;
388
549
  }[];
389
550
  /** `{ count, archiveId }` from one `archive()` call. */
390
551
  export type ArchiveCount = {
@@ -462,6 +623,7 @@ export type TableApi<S extends QuerySchema, K extends keyof S["~byName"] & strin
462
623
  };
463
624
  count(options?: CallOptions & {
464
625
  readonly where?: WhereOf<S, K>;
626
+ readonly search?: TextSearch<RowOf<S, K>>;
465
627
  }): Read<number>;
466
628
  exists(options?: CallOptions & {
467
629
  readonly where?: WhereOf<S, K>;
@@ -482,15 +644,49 @@ export type TableApi<S extends QuerySchema, K extends keyof S["~byName"] & strin
482
644
  * database default for that row; `null` stays NULL.
483
645
  */
484
646
  insert(data: readonly (RequiredInsert<InsertOf<S, K>> & OptionalInsert<InsertOf<S, K>>)[], options?: InsertOptions<S, K>): Write<readonly Show<RowOf<S, K>>[]>;
485
- /** Updates matching rows, or a per-row list in one statement. `where` is required. */
486
- update<const Allow extends readonly (keyof RowOf<S, K> & string)[]>(target: AllowedUpdate<S, K, Allow>, options: WriteOptions<S, K> & {
647
+ /**
648
+ * Updates matching rows, or a per-row list in one statement. `where` is required.
649
+ *
650
+ * `{ allow, returning: true }` returns the written rows. `{ allow }` without
651
+ * `returning: true` returns `{ count }`.
652
+ */
653
+ update<const Allow extends readonly (keyof RowOf<S, K> & string)[], const Opt extends WriteOptions<S, K> & {
487
654
  readonly allow: Allow;
488
- }): Write<WriteCount>;
655
+ }>(target: AllowedUpdate<S, K, Allow>, options: Opt): [Opt["returning"]] extends [true] ? Write<readonly Show<RowOf<S, K>>[]> : Write<WriteCount>;
656
+ update(target: UpdateTarget<S, K>, options: WriteOptions<S, K> & {
657
+ readonly returning: true;
658
+ }): Write<readonly Show<RowOf<S, K>>[]>;
489
659
  update(target: UpdateTarget<S, K>, options?: WriteOptions<S, K>): Write<WriteCount>;
490
- /** Deletes matching rows. `where` is required. */
660
+ /**
661
+ * Deletes matching rows. `where` is required.
662
+ *
663
+ * `{ returning: true }` is not accepted. Pass the field names, or omit
664
+ * `returning` for `{ count }`.
665
+ */
491
666
  delete(target: {
492
667
  readonly where?: WhereOf<S, K>;
493
- }, options?: WriteOptions<S, K>): Write<WriteCount>;
668
+ }, options?: Omit<WriteOptions<S, K>, "returning"> & {
669
+ readonly returning?: Exclude<WriteOptions<S, K>["returning"], true>;
670
+ }): Write<WriteCount>;
671
+ /**
672
+ * Rows below `id`, walking the table's self-relation.
673
+ *
674
+ * One statement. A row already seen is not entered again, so a cycle in the
675
+ * data returns. A missing id returns no rows. `where` and a preset filter
676
+ * the rows that come back. Tenancy and the active set apply at every step.
677
+ */
678
+ descendants<const O extends TreeOptions<S, K>>(id: unknown, options?: O): Query<readonly ResultRow<S, K, O>[]>;
679
+ /** Rows above `id`. The options match {@link TableApi.descendants}. */
680
+ ancestors<const O extends TreeOptions<S, K>>(id: unknown, options?: O): Query<readonly ResultRow<S, K, O>[]>;
681
+ /**
682
+ * Sets a to-many relation to exactly `ids`.
683
+ *
684
+ * One statement. A join table gains and loses link rows. A nullable foreign
685
+ * key is set or cleared, and the related row stays. A link that would have
686
+ * to delete a row is refused. Duplicate ids count once. An id that is not
687
+ * in the related table is `not_found`, and nothing is written.
688
+ */
689
+ sync(id: unknown, relation: string, ids: readonly unknown[], options?: SyncOptions): SyncHandle;
494
690
  } & (S["~byName"][K] extends {
495
691
  readonly "~archive": true;
496
692
  } ? ArchiveOps<S, K, L> : unknown) & PresetApi<S, K, L> & TableExtras<S, K>;