okmodel 0.5.1 → 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 (233) hide show
  1. package/README.md +100 -1116
  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 +20 -0
  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 +41 -3
  14. package/dist/adapters/pg/reserved.d.ts +42 -0
  15. package/dist/adapters/pg/session.d.ts +0 -18
  16. package/dist/contracts/catalog/document.d.ts +19 -5
  17. package/dist/contracts/catalog/object.d.ts +2 -1
  18. package/dist/contracts/catalog/policy.d.ts +48 -0
  19. package/dist/contracts/catalog/types.d.ts +25 -1
  20. package/dist/contracts/catalog/view.d.ts +2 -0
  21. package/dist/contracts/connection.d.ts +7 -6
  22. package/dist/contracts/internal.js +1 -1
  23. package/dist/contracts/location.d.ts +10 -0
  24. package/dist/dialects/pg/column.d.ts +13 -2
  25. package/dist/dialects/pg/ddl.d.ts +37 -1
  26. package/dist/dialects/pg/emit.d.ts +19 -1
  27. package/dist/dialects/pg/errors.d.ts +32 -1
  28. package/dist/dialects/pg/ext/citext.js +1 -1
  29. package/dist/dialects/pg/ext/index.js +1 -1
  30. package/dist/dialects/pg/ext/pg-trgm.js +1 -1
  31. package/dist/dialects/pg/filter-parse.d.ts +5 -4
  32. package/dist/dialects/pg/floor.d.ts +68 -0
  33. package/dist/dialects/pg/fn/index.js +2 -2
  34. package/dist/dialects/pg/index.d.ts +8 -2
  35. package/dist/dialects/pg/index.js +67 -171
  36. package/dist/dialects/pg/introspect.d.ts +7 -0
  37. package/dist/dialects/pg/keys.d.ts +11 -8
  38. package/dist/dialects/pg/model.d.ts +47 -2
  39. package/dist/dialects/pg/morph.d.ts +43 -0
  40. package/dist/dialects/pg/operator-fit.d.ts +2 -2
  41. package/dist/dialects/pg/operators.d.ts +14 -7
  42. package/dist/dialects/pg/ops/bucket.d.ts +65 -0
  43. package/dist/dialects/pg/ops/inc.d.ts +2 -2
  44. package/dist/dialects/pg/ops/matches.d.ts +9 -12
  45. package/dist/dialects/pg/ops/relevance.d.ts +13 -0
  46. package/dist/dialects/pg/ops/search-query.d.ts +22 -0
  47. package/dist/dialects/pg/ops/window.d.ts +103 -0
  48. package/dist/dialects/pg/owner.d.ts +42 -0
  49. package/dist/dialects/pg/path-endpoint.d.ts +20 -0
  50. package/dist/dialects/pg/preset.d.ts +1 -1
  51. package/dist/dialects/pg/relations.d.ts +100 -3
  52. package/dist/dialects/pg/role/check.d.ts +3 -0
  53. package/dist/dialects/pg/role/index.d.ts +71 -6
  54. package/dist/dialects/pg/role/sql.d.ts +11 -0
  55. package/dist/dialects/pg/schema.d.ts +33 -3
  56. package/dist/dialects/pg/table.d.ts +25 -3
  57. package/dist/dialects/pg/tenancy.d.ts +45 -3
  58. package/dist/dialects/pg/view/client.d.ts +19 -1
  59. package/dist/dialects/pg/view/index.d.ts +4 -0
  60. package/dist/dialects/pg/view/index.js +21 -178
  61. package/dist/dialects/pg/view/refresh.d.ts +24 -0
  62. package/dist/runtime/aggregate.d.ts +8 -2
  63. package/dist/runtime/batch.d.ts +1 -1
  64. package/dist/runtime/bucket-group.d.ts +70 -0
  65. package/dist/runtime/conflict.d.ts +24 -6
  66. package/dist/runtime/drift.d.ts +9 -0
  67. package/dist/runtime/include.d.ts +0 -8
  68. package/dist/runtime/inflight.d.ts +14 -0
  69. package/dist/runtime/page.d.ts +4 -1
  70. package/dist/runtime/pg/bun.js +12 -5
  71. package/dist/runtime/pg/pg.js +5 -5
  72. package/dist/runtime/pg/pglite.d.ts +1 -0
  73. package/dist/runtime/pg/pglite.js +9 -8
  74. package/dist/runtime/pg/postgresjs.js +5 -5
  75. package/dist/runtime/plan.d.ts +113 -15
  76. package/dist/runtime/predicate.d.ts +19 -0
  77. package/dist/runtime/read-stream.d.ts +13 -16
  78. package/dist/runtime/result-name.d.ts +17 -0
  79. package/dist/runtime/safety/index.js +1 -1
  80. package/dist/runtime/server-check.d.ts +3 -3
  81. package/dist/runtime/sort-bind.d.ts +21 -0
  82. package/dist/runtime/sort.d.ts +56 -0
  83. package/dist/runtime/sync.d.ts +70 -0
  84. package/dist/runtime/tenancy/composite.d.ts +26 -0
  85. package/dist/runtime/tenancy/context.d.ts +16 -0
  86. package/dist/runtime/tenancy/index.d.ts +9 -5
  87. package/dist/runtime/tenancy/index.js +1511 -38
  88. package/dist/runtime/tenancy/path.d.ts +87 -0
  89. package/dist/runtime/tenancy/rls.d.ts +67 -0
  90. package/dist/runtime/tenancy/via.d.ts +22 -0
  91. package/dist/runtime/text-search.d.ts +20 -0
  92. package/dist/runtime/traits/index.d.ts +80 -0
  93. package/dist/runtime/traits/index.js +187 -17
  94. package/dist/runtime/tree.d.ts +38 -0
  95. package/dist/runtime/types.d.ts +231 -23
  96. package/dist/runtime/validate/index.js +4 -4
  97. package/dist/runtime/version.d.ts +39 -0
  98. package/dist/runtime/window.d.ts +21 -0
  99. package/dist/runtime/write.d.ts +55 -3
  100. package/dist/shared/{krxx77g0.js → 14vx95q4.js} +12 -18
  101. package/dist/shared/1est4ezg.js +313 -0
  102. package/dist/shared/{4az561fn.js → 1qa0qajz.js} +8 -23
  103. package/dist/shared/1w508jbr.js +1611 -0
  104. package/dist/shared/2c13q96h.js +18 -0
  105. package/dist/shared/2d3xq4ps.js +9 -0
  106. package/dist/shared/2shdbn8q.js +23 -0
  107. package/dist/shared/{t4tbsy9h.js → 358zsv4r.js} +1 -1
  108. package/dist/shared/3v7t1yrv.js +208 -0
  109. package/dist/shared/5e3tf63j.js +220 -0
  110. package/dist/shared/5fe3kbsh.js +10 -0
  111. package/dist/shared/5v3xf80k.js +37 -0
  112. package/dist/shared/{51mvgj8w.js → 63qtkrca.js} +130 -3
  113. package/dist/shared/{gbtcc2d7.js → 67txcrcw.js} +62 -66
  114. package/dist/shared/79dt02vc.js +12 -0
  115. package/dist/shared/{v36c5g6p.js → 7k9grvf6.js} +407 -14
  116. package/dist/shared/7kms6fvj.js +20 -0
  117. package/dist/shared/7mnme5tn.js +8 -0
  118. package/dist/shared/7n7ddwt4.js +6 -0
  119. package/dist/shared/{87jvvhnt.js → 81ebdx9p.js} +48 -68
  120. package/dist/shared/{szm0cksa.js → 8skf0cam.js} +34 -6
  121. package/dist/shared/{gjthds9j.js → 9628a9xt.js} +1 -1
  122. package/dist/shared/9a4ae4p8.js +31 -0
  123. package/dist/shared/{8xsbagad.js → 9s0ypp87.js} +88 -7
  124. package/dist/shared/9y3mmmh1.js +436 -0
  125. package/dist/shared/ab9gmbwy.js +213 -0
  126. package/dist/shared/{sycnx8gt.js → ax7qv55k.js} +18 -8
  127. package/dist/shared/{f5cw5q5r.js → aytqh5y2.js} +6 -4
  128. package/dist/shared/{fancm5pt.js → b5pftnn2.js} +138 -15
  129. package/dist/shared/{efmy2gcg.js → b76b8d95.js} +2 -2
  130. package/dist/shared/bdk4sty6.js +58 -0
  131. package/dist/shared/{32zqc1dw.js → bg3rym3g.js} +63 -32
  132. package/dist/shared/{0t22wwh1.js → bm22fvzr.js} +16 -14
  133. package/dist/shared/bz84tkzm.js +342 -0
  134. package/dist/shared/{31vm914h.js → cm3w0ywp.js} +15 -16
  135. package/dist/shared/cn957f8d.js +179 -0
  136. package/dist/shared/{pf0d8f77.js → cnkky55j.js} +25 -14
  137. package/dist/shared/{5fqx9yh4.js → dk8v79p3.js} +3 -1
  138. package/dist/shared/{nkaxvt9q.js → dkxgzaqd.js} +224 -19
  139. package/dist/shared/dzj09cye.js +10 -0
  140. package/dist/shared/{4m9fn52z.js → ed0b8k4s.js} +1 -1
  141. package/dist/shared/{z2rnngj4.js → ek2wt2dz.js} +8 -5
  142. package/dist/shared/exn3sd90.js +359 -0
  143. package/dist/shared/g6evj1j3.js +66 -0
  144. package/dist/shared/ga9j92qj.js +25 -0
  145. package/dist/shared/{qv2ydck9.js → gs74dbj5.js} +651 -76
  146. package/dist/shared/gzaveazc.js +285 -0
  147. package/dist/shared/{8rsy3hxz.js → h15jx9bc.js} +1 -1
  148. package/dist/shared/{dyj86cmj.js → hyh73qk6.js} +3 -3
  149. package/dist/shared/je4axnm8.js +22 -0
  150. package/dist/shared/{ycatkxmq.js → jgqj11w4.js} +1 -1
  151. package/dist/shared/{6qd27mx0.js → jvd0mqag.js} +5 -5
  152. package/dist/shared/jznfax0y.js +155 -0
  153. package/dist/shared/{cgbamx3e.js → kb2sdhfb.js} +7 -4
  154. package/dist/shared/{ps7a6h14.js → kr0jzbw2.js} +12 -8
  155. package/dist/shared/{janx09m5.js → ktz42yez.js} +20 -8
  156. package/dist/shared/{gde73es6.js → m6p12kbr.js} +56 -55
  157. package/dist/shared/{r6nwkxnt.js → m7n9h2a5.js} +4 -4
  158. package/dist/shared/{672t5vym.js → mss6ctgv.js} +2 -2
  159. package/dist/shared/{nxdp2ze3.js → mtaszv5a.js} +144 -12
  160. package/dist/shared/n816peh1.js +45 -0
  161. package/dist/shared/nbw1z4yd.js +18 -0
  162. package/dist/shared/{cctw17hj.js → p6qc5gcp.js} +38 -34
  163. package/dist/shared/{6rk2gen5.js → pbp1ss85.js} +20 -17
  164. package/dist/shared/pt94kz7f.js +6 -0
  165. package/dist/shared/{wq9n6pwr.js → pz6rj8ay.js} +82 -9
  166. package/dist/shared/q5c6ngn3.js +13 -0
  167. package/dist/shared/{8bwbwy8h.js → qq3899ht.js} +128 -18
  168. package/dist/shared/{8ktwd3rh.js → r3j8s5y0.js} +1 -1
  169. package/dist/shared/rhs3xe4k.js +7 -0
  170. package/dist/shared/{ntneex2a.js → rpycarcr.js} +6 -6
  171. package/dist/shared/{sdc7r2ww.js → rs73fca9.js} +8 -8
  172. package/dist/shared/{tm5ab8we.js → sfz1ksn6.js} +7 -4
  173. package/dist/shared/{6mnp99hx.js → svncdeh5.js} +16 -12
  174. package/dist/shared/{hf22e0wt.js → tbrvgw6z.js} +62 -42
  175. package/dist/shared/te5as7z7.js +622 -0
  176. package/dist/shared/{ga3sfy93.js → tsvww22q.js} +535 -200
  177. package/dist/shared/{47x8x9ze.js → ttsqgp4p.js} +20 -13
  178. package/dist/shared/{x72jb7ad.js → v8g1vgv8.js} +1 -1
  179. package/dist/shared/vb6qfyve.js +152 -0
  180. package/dist/shared/{r2sp8hrc.js → vczs8gd4.js} +22 -13
  181. package/dist/shared/{1g0drakv.js → vqrjpcqn.js} +145 -3
  182. package/dist/shared/{2pqx26h8.js → w72kgg4q.js} +31 -22
  183. package/dist/shared/{96qcj21c.js → w79hsckn.js} +16 -51
  184. package/dist/shared/wa07eas9.js +730 -0
  185. package/dist/shared/{k8v5v5wj.js → x933k9a3.js} +75 -34
  186. package/dist/shared/{cr74e96e.js → xhfsfkgk.js} +62 -28
  187. package/dist/shared/xjk7hnn5.js +272 -0
  188. package/dist/shared/{5enne4ma.js → xnxsed62.js} +66 -113
  189. package/dist/shared/xqve9598.js +53 -0
  190. package/dist/shared/{9x25fxhc.js → y34a8714.js} +1 -1
  191. package/dist/shared/{k7dwkhvq.js → y6jdds7g.js} +4 -1
  192. package/dist/shared/y6njgyda.js +249 -0
  193. package/dist/shared/{c9z5g0fm.js → ywr8hmey.js} +5 -2
  194. package/dist/shared/{g7ez901n.js → ywspezzx.js} +1 -1
  195. package/dist/shared/z7s6fpxv.js +62 -0
  196. package/dist/shared/{n7mcc1gk.js → zchaghkf.js} +4 -3
  197. package/dist/shared/{9m80mkac.js → ze4hxsmy.js} +1 -1
  198. package/dist/shared/zm0ptry0.js +40 -0
  199. package/dist/shared/{nsva2a6b.js → znm0bpp5.js} +3 -0
  200. package/dist/shared/zp9fmend.js +145 -0
  201. package/dist/shared/{y005b761.js → zy8r7qvz.js} +6 -2
  202. package/dist/tooling/cli.js +41 -15
  203. package/dist/tooling/migrate/apply.d.ts +14 -0
  204. package/dist/tooling/migrate/check.d.ts +3 -4
  205. package/dist/tooling/migrate/classify.d.ts +4 -0
  206. package/dist/tooling/migrate/config.d.ts +3 -2
  207. package/dist/tooling/migrate/engine.d.ts +3 -2
  208. package/dist/tooling/migrate/files.d.ts +11 -1
  209. package/dist/tooling/migrate/index.js +14 -14
  210. package/dist/tooling/migrate/lint.d.ts +1 -1
  211. package/dist/tooling/migrate/lost.d.ts +28 -0
  212. package/dist/tooling/migrate/paths.d.ts +14 -0
  213. package/dist/tooling/migrate/plan.d.ts +6 -3
  214. package/dist/tooling/migrate/runner.d.ts +25 -0
  215. package/dist/tooling/migrate/sandbox.d.ts +57 -0
  216. package/dist/tooling/migrate/sql-lex.d.ts +20 -3
  217. package/dist/tooling/migrate/text.d.ts +42 -0
  218. package/dist/tooling/node-version.d.ts +21 -0
  219. package/dist/tooling/testing/facts.d.ts +33 -5
  220. package/dist/tooling/testing/index.d.ts +1 -1
  221. package/dist/tooling/testing/index.js +3 -3
  222. package/dist/tooling/testing/isolation.d.ts +11 -5
  223. package/package.json +8 -3
  224. package/dist/shared/7m88x3e9.js +0 -45
  225. package/dist/shared/7zwwyh0v.js +0 -448
  226. package/dist/shared/aj2qxyhj.js +0 -90
  227. package/dist/shared/caw8b9d8.js +0 -204
  228. package/dist/shared/jwtxdj06.js +0 -76
  229. package/dist/shared/kfjp0gck.js +0 -212
  230. package/dist/shared/q58y17hy.js +0 -21
  231. package/dist/shared/qm7cdtvr.js +0 -912
  232. package/dist/shared/r8x5qhd7.js +0 -86
  233. package/dist/shared/zqrnhwsq.js +0 -31
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Path tenancy.
3
+ *
4
+ * A table with `tenancy: via("project.organization")` has no tenant column.
5
+ * Reads and writes add `EXISTS` along that relation path. An insert checks the
6
+ * parent in the same statement. The featureless app does not import this file.
7
+ */
8
+ import { type AnyTable } from "../../dialects/pg/table.js";
9
+ import type { TenancyText, TenantCall } from "../../dialects/pg/tenancy.js";
10
+ /** How `columnTenancy()` classified a table before the rewrite. */
11
+ export type TableKind = "tenant" | "global" | "path";
12
+ /** One step from a path table toward the tenant table. */
13
+ export type PathHop = {
14
+ /** Table that declares this step. */
15
+ readonly child: string;
16
+ /** Table this step points at. */
17
+ readonly parent: string;
18
+ /** Foreign-key field on `child`. */
19
+ readonly localField: string;
20
+ /** Field on `parent` that the foreign key matches. */
21
+ readonly remoteField: string;
22
+ /** SQL name stored on the local column, when the builder set one. */
23
+ readonly localSql: string | undefined;
24
+ /** SQL name stored on the remote column, when the builder set one. */
25
+ readonly remoteSql: string | undefined;
26
+ /** SQL name stored on the parent table, when the table set one. */
27
+ readonly parentSql: string | undefined;
28
+ /** Encodes the local field when an update points it at a new parent. */
29
+ readonly encode: (value: string) => string;
30
+ };
31
+ /** A resolved `via` path. */
32
+ export type PathChain = {
33
+ /** The `via` text, unchanged. */
34
+ readonly via: string;
35
+ /** Steps from the path table to the tenant table. At most three. */
36
+ readonly hops: readonly PathHop[];
37
+ };
38
+ /** What the column strategy asks of a resolved path. */
39
+ export type PathPlan = {
40
+ /** Tenant tables a path ends on, and the declared key that must stay unique. */
41
+ readonly endpoints: ReadonlyMap<string, readonly string[]>;
42
+ /** Path table, when `name` was resolved. */
43
+ isPath(name: string): boolean;
44
+ /** Steps from `name` to the tenant table. */
45
+ hops(name: string): readonly PathHop[] | undefined;
46
+ /** Rewrites one path table: foreign key into the tenant table, and the index. */
47
+ adjust(item: AnyTable): AnyTable;
48
+ /**
49
+ * Writes `EXISTS` for a path table.
50
+ *
51
+ * @returns `true` when `table` is a path table and the predicate was written
52
+ */
53
+ writeExists(input: ExistsInput): boolean;
54
+ };
55
+ type ExistsInput = {
56
+ readonly table: string;
57
+ readonly alias: string;
58
+ readonly fieldSql: (field: string) => string | undefined;
59
+ readonly scope: TenantCall | undefined;
60
+ readonly sink: TenancyText;
61
+ readonly appended: boolean;
62
+ readonly lock: boolean;
63
+ readonly keys: readonly string[];
64
+ readonly encodeKey: (key: string, value: string) => string;
65
+ /** New parent values, keyed by the path table's foreign-key field. */
66
+ readonly assigned?: Readonly<Record<string, string>>;
67
+ };
68
+ /**
69
+ * Resolves every `via()` table.
70
+ *
71
+ * A path that does not end at a tenant table, a missing relation, a cycle, or
72
+ * more than three hops is OKM1705. The caller rewrites tenant tables after this
73
+ * and adds a unique on each endpoint's declared key.
74
+ *
75
+ * @param tables - Tables passed to `schema()`
76
+ * @param kind - Classification, including `"path"`
77
+ * @returns The plan the predicate and the rewrite share
78
+ */
79
+ export declare function compilePaths(tables: readonly AnyTable[], kind: ReadonlyMap<string, TableKind>): PathPlan;
80
+ /**
81
+ * Declared primary key, before tenancy adds its columns.
82
+ *
83
+ * @param item - Table as passed to `schema()`
84
+ * @returns Field names, in declaration order
85
+ */
86
+ export declare function declaredPrimary(item: AnyTable): readonly string[];
87
+ export {};
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Row-level security tenancy (`rlsTenancy()`).
3
+ *
4
+ * The column predicate stays on every statement. Policies are the backstop.
5
+ * A column-only import does not load this file.
6
+ */
7
+ import { OkmError } from "../../contracts/error.js";
8
+ import type { DriverConnection, ExecuteOptions, ExecuteResult, Statement, WireValue } from "../../contracts/driver.js";
9
+ import type { ColumnTenancy } from "../../dialects/pg/tenancy.js";
10
+ /** Why {@link RlsRoleError} refused the role. */
11
+ export type RlsRoleReason = "owner" | "superuser" | "bypassrls";
12
+ /**
13
+ * OKM1707. The role would not be subject to the policies.
14
+ *
15
+ * `reason` says which check failed. Superuser wins over `BYPASSRLS`, which
16
+ * wins over table owner.
17
+ */
18
+ export declare class RlsRoleError extends OkmError {
19
+ readonly reason: RlsRoleReason;
20
+ /**
21
+ * @param reason - Which privilege bypassed row-level security
22
+ * @param endpoint - Endpoint that answered the check. The message names it
23
+ */
24
+ constructor(reason: RlsRoleReason, endpoint?: string);
25
+ }
26
+ /**
27
+ * Row-level security for `schema({ tenancy })`.
28
+ *
29
+ * One uuid key. The column predicate matches `columnTenancy`. Policies,
30
+ * `set_config`, and the role check load with this import. Combining it with
31
+ * `via()` or `compositeTenancy()` is a definition error.
32
+ *
33
+ * @typeParam Key - Field name of the tenant key
34
+ * @param input - Field name and `uuid`
35
+ * @returns The object `schema()` applies
36
+ */
37
+ export declare function rlsTenancy<const Key>(input: {
38
+ readonly key: Key extends readonly unknown[] ? "rlsTenancy() takes one key. Combining it with compositeTenancy or via() is not supported yet." : Key extends string ? Key : "rlsTenancy() key must be a field name.";
39
+ readonly type: "uuid";
40
+ }): ColumnTenancy & {
41
+ readonly key: Key extends string ? Key : string;
42
+ };
43
+ /**
44
+ * Runs one statement inside `BEGIN READ ONLY` after the tenant setting.
45
+ *
46
+ * The caller releases the connection. A failure rolls the transaction back.
47
+ *
48
+ * @param conn - Reserved connection
49
+ * @param text - User statement
50
+ * @param params - Wire parameters
51
+ * @param prelude - `set_config` statement
52
+ * @param options - Deadline and cancellation, without a route hint
53
+ * @returns The user statement's result
54
+ */
55
+ export declare function runReadOnly(conn: DriverConnection, text: string, params: readonly WireValue[] | undefined, prelude: Statement, options: ExecuteOptions | undefined): Promise<ExecuteResult>;
56
+ /**
57
+ * Streams one query inside `BEGIN READ ONLY` after the tenant setting.
58
+ *
59
+ * Rows come back in chunks of 64. The caller releases the connection.
60
+ *
61
+ * @param conn - Reserved connection
62
+ * @param text - User query
63
+ * @param params - Wire parameters
64
+ * @param prelude - `set_config` statement
65
+ * @returns Row chunks
66
+ */
67
+ export declare function streamHeld(conn: DriverConnection, text: string, params: readonly WireValue[] | undefined, prelude: Statement): AsyncGenerator<readonly (readonly WireValue[])[]>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Path tenancy (`via()`).
3
+ *
4
+ * A table passes `tenancy: via("project.organization")`. This file owns the
5
+ * path compiler. A column-only app does not import it.
6
+ */
7
+ import type { AnyTable } from "../../dialects/pg/table.js";
8
+ import type { TableRewriteContext } from "./context.js";
9
+ /**
10
+ * Isolates a table through relations instead of a tenant column.
11
+ *
12
+ * The path is at most three to-one steps and must end at a tenant table.
13
+ * `schema()` resolves it while it rewrites. A bad path is OKM1705.
14
+ *
15
+ * @typeParam Path - Relation path, such as `"project.organization"`
16
+ * @param path - Steps from this table to the tenant table, separated by `.`
17
+ * @returns The marker `table({ tenancy })` stores
18
+ */
19
+ export declare function via<const Path extends string>(path: Path): {
20
+ readonly via: Path;
21
+ rewrite(table: AnyTable, ctx: TableRewriteContext): AnyTable;
22
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `search` on `find` and `count`.
3
+ *
4
+ * Loaded on the first call that passes `search`. It rewrites the call into
5
+ * `matches` and, when `find` has no `orderBy`, `relevance` plus the primary
6
+ * key. The SQL compiler is the one those helpers already use.
7
+ */
8
+ import type { QuerySchema } from "../dialects/pg/model.js";
9
+ import { type ReadCall } from "./plan.js";
10
+ /**
11
+ * Rewrites `search` into `matches` and `relevance`.
12
+ *
13
+ * `count` keeps the caller's order, which it does not emit. An explicit
14
+ * `orderBy` on `find` replaces the automatic ranking.
15
+ *
16
+ * @param schema - Connected schema
17
+ * @param call - The read, with `search` set
18
+ * @returns The same read, without `search`
19
+ */
20
+ export declare function applyTextSearch(schema: QuerySchema, call: ReadCall): ReadCall;
@@ -5,6 +5,7 @@
5
5
  * does not import it does not load these columns.
6
6
  */
7
7
  import { type PresetMap } from "../../dialects/pg/preset.js";
8
+ import type { QuerySchema, SchemaHookCtx } from "../../dialects/pg/model.js";
8
9
  import type { AnyTable, RowFrom } from "../../dialects/pg/table.js";
9
10
  import { attachArchive } from "../archive-handle.js";
10
11
  import { type Trait, type TraitContext, type TraitModel } from "../../dialects/pg/trait.js";
@@ -131,3 +132,82 @@ export declare function archivable(options?: ArchivableOptions): {
131
132
  rewrite(tables: readonly AnyTable[], casing: "snake" | undefined, schemaTraits: readonly object[] | undefined): readonly AnyTable[];
132
133
  hook: typeof attachArchive;
133
134
  };
135
+ /**
136
+ * Adds `version` and checks it on every update.
137
+ *
138
+ * The column starts at 1. An update passes the version it read and the write
139
+ * bumps it. A stale version is category `conflict` and changes nothing.
140
+ * Input cannot set `version`.
141
+ *
142
+ * @returns The versioned trait
143
+ */
144
+ export declare function versioned(): {
145
+ readonly name: string;
146
+ readonly fields: {
147
+ readonly version: import("../../dialects/pg/column.js").ColumnBuilder<number, import("../../dialects/pg/column.js").FlagTrue<import("../../dialects/pg/column.js").FlagTrue<import("../../dialects/pg/column.js").PlainFlags, "hasDefault">, "guarded">>;
148
+ };
149
+ readonly touch?: readonly "version"[];
150
+ readonly sealed?: readonly "version"[];
151
+ apply(model: TraitModel, ctx: TraitContext): void;
152
+ contribute(peers: unknown, built: unknown): readonly unknown[];
153
+ };
154
+ /**
155
+ * Adds `position` and `move`.
156
+ *
157
+ * New rows go at the end of the group. `move` places a row before or after
158
+ * another in that group. `find` and `page` order by `position` when the
159
+ * caller does not pass `orderBy`. Input cannot set `position`.
160
+ *
161
+ * @param groupBy - Column that splits the order. Omit it for one list
162
+ * @returns The sortable trait
163
+ */
164
+ export declare function sortable(groupBy?: string): {
165
+ name: string;
166
+ fields: {
167
+ readonly position: import("../../dialects/pg/column.js").ColumnBuilder<number, import("../../dialects/pg/column.js").FlagTrue<import("../../dialects/pg/column.js").PlainFlags, "guarded">>;
168
+ };
169
+ touch?: readonly "position"[];
170
+ sealed?: readonly "position"[];
171
+ contribute(peers: unknown, built: unknown): readonly unknown[];
172
+ group?: string;
173
+ "~sort": true;
174
+ apply(model: TraitModel, ctx: TraitContext): void;
175
+ /**
176
+ * Adds the unique position index after tenancy has widened uniques.
177
+ *
178
+ * @param tables - Tables after the tenancy rewrite
179
+ * @param casing - Schema casing
180
+ * @param schemaTraits - Traits from `schema()`, when any were passed
181
+ * @returns The rewritten tables
182
+ */
183
+ rewrite(tables: readonly AnyTable[], casing: "snake" | undefined, schemaTraits: readonly object[] | undefined): readonly AnyTable[];
184
+ hook: typeof attachSort;
185
+ };
186
+ /**
187
+ * Adds `move` and the default position order on a sortable table.
188
+ *
189
+ * @param target - The table handle being built
190
+ * @param ctx - Table name and the session
191
+ */
192
+ declare function attachSort(target: Record<string, unknown>, ctx: SchemaHookCtx): void;
193
+ declare module "../types.js" {
194
+ interface TableExtras<S extends QuerySchema, K extends keyof S["~byName"] & string> {
195
+ /**
196
+ * Moves one row before or after another in the same group.
197
+ *
198
+ * The result is the row's position after the move. A missing id is
199
+ * `not_found`. An anchor in another group is OKM1120.
200
+ *
201
+ * @param id - Primary key of the row to move, without the tenant key
202
+ * @param place - `{ before }` or `{ after }`, the anchor's primary key
203
+ * @returns The position after the move
204
+ */
205
+ move(id: unknown, place: {
206
+ readonly before: unknown;
207
+ } | {
208
+ readonly after: unknown;
209
+ }): Promise<{
210
+ readonly position: number;
211
+ }>;
212
+ }
213
+ }
@@ -1,31 +1,35 @@
1
- import"../../shared/8bwbwy8h.js";
1
+ 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";
9
9
  import {
10
10
  quoteIdent
11
- } from "../../shared/8xsbagad.js";
11
+ } from "../../shared/9s0ypp87.js";
12
+ import {
13
+ pathEndpoint
14
+ } from "../../shared/rhs3xe4k.js";
12
15
  import {
13
16
  functionObject,
14
17
  triggerObject
15
- } from "../../shared/9x25fxhc.js";
18
+ } from "../../shared/y34a8714.js";
16
19
  import {
20
+ integer2,
17
21
  timestamptz2
18
- } from "../../shared/1g0drakv.js";
22
+ } from "../../shared/vqrjpcqn.js";
19
23
  import {
20
24
  uuid2
21
- } from "../../shared/dyj86cmj.js";
25
+ } from "../../shared/hyh73qk6.js";
22
26
  import {
23
27
  snakeCase
24
- } from "../../shared/ntneex2a.js";
28
+ } from "../../shared/rpycarcr.js";
25
29
  import {
26
30
  ColumnBuilder2,
27
31
  retarget
28
- } from "../../shared/c9z5g0fm.js";
32
+ } from "../../shared/ywr8hmey.js";
29
33
  import {
30
34
  definition,
31
35
  unavailable
@@ -33,15 +37,17 @@ import {
33
37
  import {
34
38
  staticNamespace2,
35
39
  compareText
36
- } from "../../shared/t4tbsy9h.js";
40
+ } from "../../shared/358zsv4r.js";
37
41
  import {
38
42
  queryHandle,
39
43
  attachHttp,
40
44
  settleCall
41
- } from "../../shared/hf22e0wt.js";
45
+ } from "../../shared/tbrvgw6z.js";
42
46
  import {
43
- installArchiveInspect
44
- } from "../../shared/gbtcc2d7.js";
47
+ installArchiveInspect,
48
+ indexes,
49
+ isRecord
50
+ } from "../../shared/67txcrcw.js";
45
51
  import {
46
52
  fitIdentifier2
47
53
  } from "../../shared/nfzc5z0w.js";
@@ -66,6 +72,9 @@ function rewriteArchivable(self, tables, casing, schemaTraits, cascade) {
66
72
  });
67
73
  }
68
74
  function rewriteOne(item, cascade, byName, accepted, edges, casing, schemaTraits, archiveSql, idSql, predicate) {
75
+ if (Object.hasOwn(item, pathEndpoint)) {
76
+ throw new OkmError2("OKM1705", `Table ${item.name} is the end of a path and cannot be archivable().`, { fix: { summary: `Leave ${item.name} out of archivable().` } });
77
+ }
69
78
  const links = childLinks(item, cascade, byName, accepted, edges, casing, schemaTraits, archiveSql, idSql);
70
79
  const parents = parentLinks(item, byName, edges, casing, schemaTraits, archiveSql, idSql);
71
80
  const primary = primaryFields(item);
@@ -459,13 +468,13 @@ function refuse(host, table) {
459
468
  }
460
469
  var archiveMod;
461
470
  function loadArchive() {
462
- archiveMod ??= import("../../shared/0t22wwh1.js");
471
+ archiveMod ??= import("../../shared/bm22fvzr.js");
463
472
  return archiveMod;
464
473
  }
465
474
  async function settle(host, table, mods) {
466
475
  if (mods.uses === undefined)
467
476
  return mods;
468
- const { resolve } = await import("../../shared/r6nwkxnt.js");
477
+ const { resolve } = await import("../../shared/m7n9h2a5.js");
469
478
  return { ...mods, presets: resolve(host.schema, table, mods.uses).wheres };
470
479
  }
471
480
  async function run(host, op, table, input, options, mods) {
@@ -478,6 +487,93 @@ async function run(host, op, table, input, options, mods) {
478
487
  }
479
488
  }
480
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
+
481
577
  // src/runtime/traits/index.ts
482
578
  function trait(name, input) {
483
579
  if (input.presets !== undefined)
@@ -555,7 +651,7 @@ function timestamps(options) {
555
651
  function stamped() {
556
652
  return timestamptz2().defaultSql("now()").guarded();
557
653
  }
558
- function archivable(options) {
654
+ function archivable2(options) {
559
655
  installArchiveContributions();
560
656
  const cascade = readCascade(options);
561
657
  const base = trait("archivable", {
@@ -579,6 +675,78 @@ function archivable(options) {
579
675
  };
580
676
  return self;
581
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
+ }
582
750
  function readCascade(options) {
583
751
  if (options === undefined)
584
752
  return [];
@@ -641,7 +809,9 @@ function addFields(name, fields, model, ctx) {
641
809
  }
642
810
  }
643
811
  export {
644
- archivable,
812
+ archivable2 as archivable,
813
+ sortable,
645
814
  timestamps,
646
- trait
815
+ trait,
816
+ versioned
647
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;