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
@@ -9,51 +9,75 @@ import {
9
9
  serializeCatalog2,
10
10
  catalogHash2,
11
11
  startupCatalog2,
12
+ catalogFileText2,
12
13
  parseCatalog2
13
- } from "./8bwbwy8h.js";
14
+ } from "./qq3899ht.js";
14
15
  import {
15
16
  open2
16
- } from "./krxx77g0.js";
17
+ } from "./14vx95q4.js";
17
18
  import {
19
+ sqlStatements,
20
+ transactionControl,
18
21
  canonicalTypeName,
22
+ prepareMigrationText,
23
+ assertMigrationSteps,
24
+ columnPrivilege,
19
25
  batchSizeField,
20
26
  aboutBatchCount,
21
- sqlStatements,
22
27
  parseReplace,
23
28
  planMigration,
24
29
  formatPlan,
25
30
  parsePlan,
26
31
  staleRenames,
32
+ sessionFailure,
33
+ finishQuiet,
27
34
  assertTargetPolicy,
28
35
  assertTargetAlias,
29
36
  listTargets,
30
37
  selectTarget
31
- } from "./ga3sfy93.js";
38
+ } from "./tsvww22q.js";
32
39
  import {
33
40
  contributedFields
34
41
  } from "./n0dgs0pb.js";
35
42
  import {
36
43
  assertValidation,
37
44
  writeWouldValidate
38
- } from "./9m80mkac.js";
45
+ } from "./ze4hxsmy.js";
46
+ import {
47
+ tableOwner,
48
+ stampOwnership
49
+ } from "./xqve9598.js";
50
+ import {
51
+ POSTGRES_FLOOR,
52
+ uuidv7NeedsPostgres
53
+ } from "./ga9j92qj.js";
39
54
  import {
40
55
  catalog,
41
56
  isDomain
42
- } from "./g7ez901n.js";
57
+ } from "./ywspezzx.js";
43
58
  import {
44
59
  snakeCase,
45
60
  emittedTypeName
46
- } from "./ntneex2a.js";
61
+ } from "./rpycarcr.js";
47
62
  import {
48
63
  ColumnBuilder2
49
- } from "./c9z5g0fm.js";
64
+ } from "./ywr8hmey.js";
50
65
  import {
51
66
  staticNamespace2
52
- } from "./t4tbsy9h.js";
67
+ } from "./358zsv4r.js";
68
+ import {
69
+ sha2562
70
+ } from "./nfzc5z0w.js";
53
71
  import {
54
72
  OkmError2
55
73
  } from "./52szzakm.js";
56
74
 
75
+ // src/tooling/migrate/paths.ts
76
+ import { resolve } from "node:path";
77
+ function projectPath(cwd, ...parts) {
78
+ return resolve(cwd, ...parts);
79
+ }
80
+
57
81
  // src/tooling/errors/registry.ts
58
82
  var ERROR_DOCS = [
59
83
  {
@@ -173,8 +197,8 @@ var ERROR_DOCS = [
173
197
  {
174
198
  code: "OKM1110",
175
199
  title: "Engine version",
176
- summary: "The feature needs a newer engine than schema({ requires }) allows.",
177
- fix: "Raise requires, or stop using the feature. The error names both versions."
200
+ summary: "A feature needs a newer PostgreSQL than minPostgres allows. The message names the feature, the version it needs, and this schema's minimum.",
201
+ fix: "Raise minPostgres, or stop using the feature. The error names both versions."
178
202
  },
179
203
  {
180
204
  code: "OKM1111",
@@ -210,7 +234,7 @@ var ERROR_DOCS = [
210
234
  code: "OKM1124",
211
235
  title: "Operator mismatch",
212
236
  summary: "An operator does not apply to the column type.",
213
- fix: "Use an operator the error lists for that column type. Text search needs a tsvector column."
237
+ fix: "Use an operator the error lists for that column type. Search needs a column built for search, not a plain text column."
214
238
  },
215
239
  {
216
240
  code: "OKM1130",
@@ -494,6 +518,18 @@ var ERROR_DOCS = [
494
518
  summary: "The last migration's catalog does not match the schema.",
495
519
  fix: "Run okm generate."
496
520
  },
521
+ {
522
+ code: "OKM1550",
523
+ title: "Row-level security weakened",
524
+ summary: "A statement disables row-level security, drops or widens a policy, grants BYPASSRLS, or changes the owner of a tenant table.",
525
+ fix: "Remove the statement, or allow OKM1550 with a reason when the change is reviewed."
526
+ },
527
+ {
528
+ code: "OKM1551",
529
+ title: "Transaction control in a step",
530
+ summary: "A step contains COMMIT, ROLLBACK, or other transaction control, including inside a DO body. A function or procedure body is not this.",
531
+ fix: "Remove the transaction control. okm migrate check replays one transaction and rolls it back. Apply accepts the step only with -- okm-allow OKM1551: reason."
532
+ },
497
533
  {
498
534
  code: "OKM1601",
499
535
  title: "Stale typed SQL",
@@ -533,8 +569,14 @@ var ERROR_DOCS = [
533
569
  {
534
570
  code: "OKM1707",
535
571
  title: "RLS role",
536
- summary: "connect() was given a role that owns the tables or is a superuser, under the rls strategy.",
537
- fix: "Connect as a role that is neither the owner nor a superuser."
572
+ summary: "A scoped call under rls ran as a role that owns the tenant tables, is a superuser, or has BYPASSRLS. The reason field says which. The message names the endpoint.",
573
+ fix: "Connect as a role that is not the table owner, a superuser, or BYPASSRLS. okm seed under rlsTenancy runs as the application role. The owner gets this error."
574
+ },
575
+ {
576
+ code: "OKM1708",
577
+ title: "RLS policy",
578
+ summary: "A scoped call under rls found a tenant table whose row-level security is missing, not forced, or whose policies are not the ones okm generate emits. The message names the table and the endpoint.",
579
+ fix: "Restore the policies okm generate emits for this rls schema."
538
580
  },
539
581
  {
540
582
  code: "OKM1801",
@@ -545,14 +587,14 @@ var ERROR_DOCS = [
545
587
  {
546
588
  code: "OKM1802",
547
589
  title: "Server version",
548
- summary: "The connected server does not satisfy schema({ requires }).",
549
- fix: "Upgrade the server, or lower requires to a version the server meets."
590
+ summary: "The connected server is older than this schema's minPostgres.",
591
+ fix: "Upgrade the server, or lower minPostgres to a major the server meets."
550
592
  },
551
593
  {
552
594
  code: "OKM1803",
553
595
  title: "Postgres floor",
554
- summary: "The connected server is older than PostgreSQL 15, and the schema does not set requires to that older major.",
555
- fix: "Upgrade the server to PostgreSQL 15 or newer, or set schema({ requires }) to the older major on purpose."
596
+ summary: `The connected server is older than PostgreSQL ${String(POSTGRES_FLOOR)}.`,
597
+ fix: `Upgrade the server to PostgreSQL ${String(POSTGRES_FLOOR)} or higher.`
556
598
  },
557
599
  {
558
600
  code: "OKM1810",
@@ -569,8 +611,8 @@ var ERROR_DOCS = [
569
611
  {
570
612
  code: "OKM1812",
571
613
  title: "Feature version",
572
- summary: "A feature needs a newer Postgres or extension version than the one declared or connected.",
573
- fix: 'Raise the declared version, or avoid the feature. uuidv7() below 18 can use t.id({ default: "uuidv4" }), defaults.id set to "uuidv4", or a client generator. Core features are never gated on an extension.'
614
+ summary: "A feature needs a newer PostgreSQL than minPostgres, or an extension version the server does not have. The message names the feature and the version.",
615
+ fix: `Raise minPostgres, or avoid the feature. ${uuidv7NeedsPostgres()} An extension pin names the version to install. Core features are never gated on an extension.`
574
616
  },
575
617
  {
576
618
  code: "OKM1813",
@@ -617,7 +659,7 @@ var ERROR_DOCS = [
617
659
  {
618
660
  code: "OKM1825",
619
661
  title: "Missing privilege",
620
- summary: "The application role lacks a privilege on a managed object.",
662
+ summary: "A role lacks a privilege on a managed object.",
621
663
  fix: "Grant the privilege, or change the role. okm doctor reports the object and the privilege."
622
664
  },
623
665
  {
@@ -712,9 +754,53 @@ function indexDocs() {
712
754
  return index;
713
755
  }
714
756
 
715
- // src/tooling/migrate/lint.ts
757
+ // src/tooling/migrate/files.ts
716
758
  import { existsSync, readdirSync, readFileSync } from "node:fs";
717
759
  import { join } from "node:path";
760
+ function loadMigrations(directory) {
761
+ if (!existsSync(directory))
762
+ return [];
763
+ const names = readdirSync(directory).filter((file) => file.endsWith(".sql")).sort();
764
+ const migrations = [];
765
+ for (const file of names) {
766
+ const id = file.slice(0, -".sql".length);
767
+ const catalogPath = join(directory, `${id}.catalog.json`);
768
+ if (!existsSync(catalogPath)) {
769
+ throw new OkmError2("invalid", `Migration ${id} is missing its catalog snapshot.`, {
770
+ fix: {
771
+ summary: "Generate the migration again so the SQL and the catalog are written together."
772
+ }
773
+ });
774
+ }
775
+ const plan = readMigrationPlan(join(directory, file));
776
+ const stored = parseCatalog2(readFileSync(catalogPath, "utf8"));
777
+ const startup = startupCatalog2(stored);
778
+ const hash = catalogHash2(startup);
779
+ migrations.push(startup === stored ? { id, catalogHash: hash, steps: plan.steps } : { id, catalogHash: hash, legacyHash: catalogHash2(stored), steps: plan.steps });
780
+ }
781
+ return migrations;
782
+ }
783
+ function readMigrationPlan(path) {
784
+ const text = prepareMigrationText(readFileSync(path, "utf8"), path);
785
+ const plan = parsePlan(text);
786
+ assertMigrationSteps(text, plan.steps.length, path);
787
+ return plan;
788
+ }
789
+ function loadHeadSnapshot(directory) {
790
+ const migrations = loadMigrations(directory);
791
+ const last = migrations.at(-1);
792
+ if (last === undefined)
793
+ return;
794
+ return {
795
+ id: last.id,
796
+ catalogHash: last.catalogHash,
797
+ catalog: parseCatalog2(readFileSync(join(directory, `${last.id}.catalog.json`), "utf8"))
798
+ };
799
+ }
800
+
801
+ // src/tooling/migrate/lint.ts
802
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
803
+ import { join as join2 } from "node:path";
718
804
 
719
805
  // src/tooling/migrate/lint-rules.ts
720
806
  var INTEGER_WIDTH = {
@@ -751,7 +837,12 @@ var STEP_RULES = [
751
837
  rule("OKM1536", "locking", "error", "Adding a foreign key without NOT VALID locks the table while it scans.", (input) => hit(foreignKeyWithoutNotValid(input), "adds a foreign key without NOT VALID")),
752
838
  rule("OKM1537", "locking", "error", "SET NOT NULL locks the table while it scans for nulls.", (input) => hit(unsafeSetNotNull(input), "sets NOT NULL")),
753
839
  rule("OKM1538", "locking", "warning", "A type change that rewrites the table locks it for the rewrite.", (input) => typeChange(input, "rewrite")),
754
- rule("OKM1542", "data-dependent", "error", "A data statement outside backfill(), or DO or CALL, is not a migration step.", (input) => dataStatement(input.step))
840
+ rule("OKM1542", "data-dependent", "error", "A data statement outside backfill(), or DO or CALL, is not a migration step.", (input) => dataStatement(input.step)),
841
+ rule("OKM1550", "destructive", "error", "Disabling row-level security, dropping a policy, creating or altering one that is not the emitted tenant predicate, granting BYPASSRLS, or changing the owner of a tenant table removes the rlsTenancy backstop.", (input) => weakensRls(input)),
842
+ rule("OKM1551", "destructive", "error", "Transaction control inside a step commits or leaves the migration transaction.", (input) => {
843
+ const word = transactionControl(input.step.sql);
844
+ return word === undefined ? [] : [`contains ${word}`];
845
+ })
755
846
  ];
756
847
  var COLUMN_RULES = [
757
848
  {
@@ -810,6 +901,146 @@ function normalized(sql) {
810
901
  }
811
902
  return out;
812
903
  }
904
+ function weakensRls(input) {
905
+ const text = normalized(input.step.sql);
906
+ if (/\bdisable row level security\b/.test(text))
907
+ return ["disables row level security"];
908
+ if (/\bno force row level security\b/.test(text))
909
+ return ["stops forcing row level security"];
910
+ if (/^drop policy\b/.test(text))
911
+ return ["drops a policy"];
912
+ const drifted = policyDrift(input, text);
913
+ if (drifted !== undefined)
914
+ return [drifted];
915
+ if (/^(?:alter|create) (?:role|user)\b/.test(text) && /(?<!no)bypassrls\b/.test(text)) {
916
+ return ["grants BYPASSRLS"];
917
+ }
918
+ if (changesTenantOwner(input, text))
919
+ return ["changes the owner of a tenant table"];
920
+ return [];
921
+ }
922
+ function policyDrift(input, text) {
923
+ const header = /^(create|alter) policy\s+(\S+)\s+on\s+(\S+)/.exec(text);
924
+ const kind = header?.[1];
925
+ const table = lastName(header?.[3]);
926
+ if (kind === undefined || table === undefined)
927
+ return;
928
+ if (!hasPolicy(input.before, table) && !hasPolicy(input.after, table))
929
+ return;
930
+ const name = lastName(header?.[2]) ?? "";
931
+ const using = clause(text, "using");
932
+ const check = clause(text, "with check");
933
+ if (isTrue(using) || isTrue(check)) {
934
+ return kind === "alter" ? "widens a policy to match every row" : "creates a policy that matches every row";
935
+ }
936
+ if (kind === "alter" && using === undefined && check === undefined)
937
+ return;
938
+ const tenant = policyExpression(input, table, `${table}_tenant`);
939
+ const unscoped = policyExpression(input, table, `${table}_unscoped_select`);
940
+ const emitted = !/\bas restrictive\b/.test(text) && (matchesEmitted(name, `${table}_tenant`, using, check, tenant) || matchesEmitted(name, `${table}_unscoped_select`, using, check, unscoped));
941
+ if (emitted)
942
+ return;
943
+ return kind === "alter" ? "widens a policy away from the tenant predicate" : "creates a policy that is not the tenant predicate";
944
+ }
945
+ function matchesEmitted(name, expectedName, using, check, expression) {
946
+ if (name !== expectedName || expression === undefined || using === undefined)
947
+ return false;
948
+ if (!sameExpression(using, expression))
949
+ return false;
950
+ return check === undefined || sameExpression(check, expression);
951
+ }
952
+ function sameExpression(found, expected) {
953
+ return found === expected || unwrap(found) === unwrap(expected);
954
+ }
955
+ function unwrap(expression) {
956
+ const body = expression.trim();
957
+ if (!body.startsWith("(") || !body.endsWith(")"))
958
+ return body;
959
+ let depth = 0;
960
+ for (let index = 0;index < body.length; index += 1) {
961
+ const char = body[index];
962
+ if (char === "(")
963
+ depth += 1;
964
+ else if (char === ")") {
965
+ depth -= 1;
966
+ if (depth === 0)
967
+ return index === body.length - 1 ? body.slice(1, -1).trim() : body;
968
+ }
969
+ }
970
+ return body;
971
+ }
972
+ function isTrue(expression) {
973
+ return expression !== undefined && expression.replaceAll(" ", "") === "(true)";
974
+ }
975
+ function clause(text, keyword) {
976
+ const needle = keyword === "using" ? /\busing\s*\(/g : /\bwith check\s*\(/g;
977
+ const found = needle.exec(text);
978
+ if (found === null)
979
+ return;
980
+ return parenthesized(text, found.index + found[0].length - 1);
981
+ }
982
+ function parenthesized(text, open) {
983
+ let depth = 0;
984
+ let quoted = false;
985
+ let single = false;
986
+ for (let index = open;index < text.length; index += 1) {
987
+ const char = text[index];
988
+ if (single) {
989
+ if (char === "'" && text[index + 1] === "'") {
990
+ index += 1;
991
+ continue;
992
+ }
993
+ if (char === "'")
994
+ single = false;
995
+ continue;
996
+ }
997
+ if (quoted) {
998
+ if (char === '"')
999
+ quoted = false;
1000
+ continue;
1001
+ }
1002
+ if (char === "'") {
1003
+ single = true;
1004
+ continue;
1005
+ }
1006
+ if (char === '"') {
1007
+ quoted = true;
1008
+ continue;
1009
+ }
1010
+ if (char === "(")
1011
+ depth += 1;
1012
+ else if (char === ")") {
1013
+ depth -= 1;
1014
+ if (depth === 0)
1015
+ return text.slice(open, index + 1);
1016
+ }
1017
+ }
1018
+ return;
1019
+ }
1020
+ function policyExpression(input, table, name) {
1021
+ const expression = expressionOf(input.after, table, name) ?? expressionOf(input.before, table, name);
1022
+ return expression === undefined ? undefined : normalized(expression);
1023
+ }
1024
+ function expressionOf(source, table, name) {
1025
+ for (const object of source.objects) {
1026
+ if (object.kind !== "policy")
1027
+ continue;
1028
+ if (object.identity.parent.name !== table || object.identity.name !== name)
1029
+ continue;
1030
+ return object.definition.expression;
1031
+ }
1032
+ return;
1033
+ }
1034
+ function changesTenantOwner(input, text) {
1035
+ const match = /^alter table\s+(\S+)\s+owner to\b/.exec(text);
1036
+ const table = lastName(match?.[1]);
1037
+ if (table === undefined)
1038
+ return false;
1039
+ return hasPolicy(input.before, table) || hasPolicy(input.after, table);
1040
+ }
1041
+ function hasPolicy(source, table) {
1042
+ return source.objects.some((object) => object.kind === "policy" && object.identity.parent.name === table);
1043
+ }
813
1044
  function dataStatement(step) {
814
1045
  const text = normalized(step.sql);
815
1046
  if (/^(?:do|call)\b/.test(text))
@@ -1161,15 +1392,15 @@ function lintCatalog(source) {
1161
1392
  return findings;
1162
1393
  }
1163
1394
  function lintMigrationDirectory(directory, pending) {
1164
- if (!existsSync(directory))
1395
+ if (!existsSync2(directory))
1165
1396
  return [];
1166
- const names = readdirSync(directory).filter((file) => file.endsWith(".sql")).sort();
1397
+ const names = readdirSync2(directory).filter((file) => file.endsWith(".sql")).sort();
1167
1398
  let before = catalog([]);
1168
1399
  const findings = [];
1169
1400
  for (const file of names) {
1170
1401
  const id = file.slice(0, -".sql".length);
1171
- const plan = parsePlan(readFileSync(join(directory, file), "utf8"));
1172
- const after = parseCatalog2(readFileSync(join(directory, `${id}.catalog.json`), "utf8"));
1402
+ const plan = readMigrationPlan(join2(directory, file));
1403
+ const after = parseCatalog2(readFileSync2(join2(directory, `${id}.catalog.json`), "utf8"));
1173
1404
  if (pending === undefined || pending.has(id)) {
1174
1405
  for (const item of lintPlan(plan, before, after)) {
1175
1406
  findings.push({ ...item, place: `${id} ${item.place}` });
@@ -1288,8 +1519,8 @@ function columnPlace(column) {
1288
1519
  }
1289
1520
 
1290
1521
  // src/tooling/migrate/project.ts
1291
- import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
1292
- import { basename, join as join2 } from "node:path";
1522
+ import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync, writeFileSync } from "node:fs";
1523
+ import { basename, dirname, join as join3 } from "node:path";
1293
1524
  import { pathToFileURL } from "node:url";
1294
1525
 
1295
1526
  // src/dialects/pg/declarations.ts
@@ -1331,7 +1562,7 @@ function sqlIdentifier(name, casing) {
1331
1562
  }
1332
1563
 
1333
1564
  // src/dialects/pg/emit.ts
1334
- function emitRowTypes(source) {
1565
+ function emitRowTypes(source, jsonTypes) {
1335
1566
  const lines = [
1336
1567
  "/**",
1337
1568
  " * Row types emitted from the schema.",
@@ -1349,13 +1580,13 @@ function emitRowTypes(source) {
1349
1580
  inserts.push(` readonly ${key}: ${name}Insert;`);
1350
1581
  updates.push(` readonly ${key}: ${name}Update;`);
1351
1582
  lines.push(`export interface ${name} {`);
1352
- lines.push(fields(item, "row", source));
1583
+ lines.push(fields(item, "row", source, jsonTypes));
1353
1584
  lines.push("}", "");
1354
1585
  lines.push(`export interface ${name}Insert {`);
1355
- lines.push(fields(item, "insert", source));
1586
+ lines.push(fields(item, "insert", source, jsonTypes));
1356
1587
  lines.push("}", "");
1357
1588
  lines.push(`export interface ${name}Update {`);
1358
- lines.push(fields(item, "update", source));
1589
+ lines.push(fields(item, "update", source, jsonTypes));
1359
1590
  lines.push("}", "");
1360
1591
  }
1361
1592
  lines.push("export interface Rows {");
@@ -1388,7 +1619,74 @@ ${body}`;
1388
1619
  function propertyName(name) {
1389
1620
  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : JSON.stringify(name);
1390
1621
  }
1391
- function fields(item, kind, source) {
1622
+ function jsonColumnTypes(source) {
1623
+ const text = source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ");
1624
+ const found = new Map;
1625
+ const pattern = /(?:\bt\s*\.\s*)?(jsonb|json)\s*</g;
1626
+ for (const match of text.matchAll(pattern)) {
1627
+ const open = (match.index ?? 0) + match[0].length - 1;
1628
+ const argument = readGeneric(text, open);
1629
+ const field = propertyBefore(text, match.index ?? 0);
1630
+ const table = tableBefore(text, match.index ?? 0);
1631
+ if (argument === undefined || field === undefined || table === undefined)
1632
+ continue;
1633
+ found.set(`${table}.${field}`, argument);
1634
+ }
1635
+ return found;
1636
+ }
1637
+ function readGeneric(source, open) {
1638
+ if (source[open] !== "<")
1639
+ return;
1640
+ let depth = 0;
1641
+ let quote;
1642
+ for (let index = open;index < source.length; index += 1) {
1643
+ const char = source[index];
1644
+ if (char === undefined)
1645
+ return;
1646
+ if (quote !== undefined) {
1647
+ if (char === "\\") {
1648
+ index += 1;
1649
+ continue;
1650
+ }
1651
+ if (char === quote)
1652
+ quote = undefined;
1653
+ continue;
1654
+ }
1655
+ if (char === "'" || char === '"' || char === "`") {
1656
+ quote = char;
1657
+ continue;
1658
+ }
1659
+ if (char === "<")
1660
+ depth += 1;
1661
+ else if (char === ">") {
1662
+ depth -= 1;
1663
+ if (depth === 0)
1664
+ return source.slice(open + 1, index).trim().replace(/\s+/g, " ");
1665
+ }
1666
+ }
1667
+ return;
1668
+ }
1669
+ function propertyBefore(source, index) {
1670
+ let cursor = index;
1671
+ while (cursor > 0 && /\s/.test(source[cursor - 1] ?? ""))
1672
+ cursor -= 1;
1673
+ if (source[cursor - 1] !== ":")
1674
+ return;
1675
+ cursor -= 1;
1676
+ while (cursor > 0 && /\s/.test(source[cursor - 1] ?? ""))
1677
+ cursor -= 1;
1678
+ const end = cursor;
1679
+ while (cursor > 0 && /[\w$]/.test(source[cursor - 1] ?? ""))
1680
+ cursor -= 1;
1681
+ const name = source.slice(cursor, end);
1682
+ return /^[$A-Za-z_][\w$]*$/.test(name) ? name : undefined;
1683
+ }
1684
+ function tableBefore(source, index) {
1685
+ const head = source.slice(0, index);
1686
+ const tables = [...head.matchAll(/table\(\s*["']([^"']+)["']/g)];
1687
+ return tables.at(-1)?.[1];
1688
+ }
1689
+ function fields(item, kind, source, jsonTypes) {
1392
1690
  const printed = [];
1393
1691
  const options = item.options;
1394
1692
  const entries = Object.entries(item.columns);
@@ -1406,7 +1704,7 @@ function fields(item, kind, source) {
1406
1704
  const state = builder.state;
1407
1705
  if (kind === "row") {
1408
1706
  if (!state.hidden) {
1409
- printed.push(` readonly ${field}: ${valueType(state)};`);
1707
+ printed.push(` readonly ${field}: ${valueType(state, jsonTypes?.get(`${item.name}.${field}`))};`);
1410
1708
  }
1411
1709
  continue;
1412
1710
  }
@@ -1415,7 +1713,7 @@ function fields(item, kind, source) {
1415
1713
  continue;
1416
1714
  }
1417
1715
  const mode = kind === "update" ? "optional" : written;
1418
- const label = valueType(state);
1716
+ const label = valueType(state, jsonTypes?.get(`${item.name}.${field}`));
1419
1717
  printed.push(mode === "optional" ? ` readonly ${field}?: ${label};` : ` readonly ${field}: ${label};`);
1420
1718
  }
1421
1719
  return printed.join(`
@@ -1435,8 +1733,8 @@ function updateMode(state) {
1435
1733
  return "omit";
1436
1734
  return "optional";
1437
1735
  }
1438
- function valueType(state) {
1439
- let label = scalarLabel(state);
1736
+ function valueType(state, jsonArg) {
1737
+ let label = scalarLabel(state, jsonArg);
1440
1738
  if (state.dims > 0) {
1441
1739
  for (let rank = 0;rank < state.dims; rank += 1) {
1442
1740
  label = `readonly (${label})[]`;
@@ -1447,7 +1745,7 @@ function valueType(state) {
1447
1745
  }
1448
1746
  return label;
1449
1747
  }
1450
- function scalarLabel(state) {
1748
+ function scalarLabel(state, jsonArg) {
1451
1749
  if (state.picklist !== undefined) {
1452
1750
  return state.picklist.values.map((value) => JSON.stringify(value)).join(" | ");
1453
1751
  }
@@ -1480,9 +1778,9 @@ function scalarLabel(state) {
1480
1778
  case "bytea":
1481
1779
  return "Uint8Array";
1482
1780
  case "json":
1483
- return "json<unknown>";
1781
+ return `json<${jsonArg ?? "unknown"}>`;
1484
1782
  case "jsonb":
1485
- return "jsonb<unknown>";
1783
+ return `jsonb<${jsonArg ?? "unknown"}>`;
1486
1784
  case "timestamptz":
1487
1785
  return "Temporal.Instant";
1488
1786
  case "timestamp":
@@ -1526,17 +1824,34 @@ function baseName(dataType) {
1526
1824
  }
1527
1825
 
1528
1826
  // src/dialects/pg/role/index.ts
1827
+ class RoleDeclarationError extends OkmError2 {
1828
+ reason;
1829
+ constructor(reason, message) {
1830
+ super("invalid", message, {
1831
+ kind: "invalid",
1832
+ fix: {
1833
+ summary: reason === "reporting_write" ? "Grant select, or use the application role for writes." : "Give the reporting role its own name."
1834
+ }
1835
+ });
1836
+ this.name = "OkmError";
1837
+ this.reason = reason;
1838
+ }
1839
+ }
1529
1840
  var PROVENANCE = { origin: "file", name: "roles" };
1530
1841
  var RELATION = ["SELECT", "INSERT", "UPDATE", "DELETE"];
1531
1842
  var READ = ["SELECT"];
1532
1843
  var ROUTINE = ["EXECUTE"];
1533
1844
  var SEQUENCE = ["USAGE", "SELECT"];
1534
- function attachRoles(source, input) {
1535
- return catalog([...source.objects, ...contributeRoles(input, source.objects)]);
1845
+ function attachRoles(source, input, exposure) {
1846
+ return catalog([...source.objects, ...contributeRoles(input, source.objects, exposure)]);
1536
1847
  }
1537
- function contributeRoles(input, built) {
1848
+ function contributeRoles(input, built, exposure) {
1538
1849
  const migration = requireName(input.migration, "migration");
1539
1850
  const app = requireName(input.app, "app");
1851
+ const reporting = input.reporting === undefined ? undefined : requireName(input.reporting, "reporting");
1852
+ if (reporting !== undefined && (reporting === app || reporting === migration)) {
1853
+ throw new RoleDeclarationError("reporting_distinct", `Role ${reporting} cannot be the reporting role and also migration or app.`);
1854
+ }
1540
1855
  const managed = new Map;
1541
1856
  for (const role of input.managed ?? []) {
1542
1857
  const name = requireName(role.name, "managed role");
@@ -1544,6 +1859,10 @@ function contributeRoles(input, built) {
1544
1859
  }
1545
1860
  const objects = [];
1546
1861
  const names = new Set([migration, app, ...managed.keys()]);
1862
+ if (reporting !== undefined)
1863
+ names.add(reporting);
1864
+ for (const grant of input.columns ?? [])
1865
+ names.add(requireName(grant.role, "column grant role"));
1547
1866
  for (const name of [...names].sort()) {
1548
1867
  const declaration = managed.get(name);
1549
1868
  objects.push(roleObject({
@@ -1558,8 +1877,10 @@ function contributeRoles(input, built) {
1558
1877
  }
1559
1878
  const roleIdentity = (name) => ({ kind: "role", name });
1560
1879
  const namespaces = new Set;
1880
+ const exposed = indexExposure(exposure);
1881
+ const columnGrants = new Map;
1561
1882
  for (const object of built) {
1562
- if (object.owner === "ignored")
1883
+ if (object.owner === "ignored" || object.owner === "external")
1563
1884
  continue;
1564
1885
  const ref = grantRef(object);
1565
1886
  if (ref === undefined)
@@ -1577,6 +1898,7 @@ function contributeRoles(input, built) {
1577
1898
  }));
1578
1899
  }
1579
1900
  }
1901
+ const relations = built.filter((object) => object.owner !== "ignored" && object.owner !== "external" && (object.kind === "table" || object.kind === "view" || object.kind === "materializedView"));
1580
1902
  const schemas = namespaces.size === 0 ? ["public"] : [...namespaces].sort();
1581
1903
  for (const name of schemas) {
1582
1904
  const namespace = staticNamespace2(name);
@@ -1597,6 +1919,45 @@ function contributeRoles(input, built) {
1597
1919
  }));
1598
1920
  }
1599
1921
  }
1922
+ if (reporting !== undefined) {
1923
+ objects.push(grantObject({
1924
+ role: reporting,
1925
+ object: { kind: "namespace", namespace, name },
1926
+ privilege: "USAGE",
1927
+ provenance: PROVENANCE,
1928
+ dependencies: [roleIdentity(reporting)]
1929
+ }));
1930
+ objects.push(defaultPrivilegeObject({
1931
+ forRole: migration,
1932
+ namespace,
1933
+ objectKind: "table",
1934
+ grantee: reporting,
1935
+ privilege: "SELECT",
1936
+ provenance: PROVENANCE,
1937
+ dependencies: [roleIdentity(migration), roleIdentity(reporting)]
1938
+ }));
1939
+ }
1940
+ }
1941
+ for (const grant of input.columns ?? []) {
1942
+ noteColumnGrant(columnGrants, grant, built, exposed, reporting);
1943
+ }
1944
+ if (reporting !== undefined) {
1945
+ for (const object of relations) {
1946
+ const ref = grantRef(object);
1947
+ if (ref === undefined)
1948
+ continue;
1949
+ noteReportingSelect(columnGrants, objects, reporting, object, ref, exposed, built);
1950
+ }
1951
+ }
1952
+ for (const pending of [...columnGrants.values()].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0)) {
1953
+ const target = pending.target;
1954
+ objects.push(grantObject({
1955
+ role: pending.role,
1956
+ object: pending.object,
1957
+ privilege: columnPrivilege(pending.privilege, [...pending.columns].sort()),
1958
+ provenance: PROVENANCE,
1959
+ dependencies: [roleIdentity(pending.role), target.identity]
1960
+ }));
1600
1961
  }
1601
1962
  return objects;
1602
1963
  }
@@ -1638,6 +1999,137 @@ function privilegesFor(kind) {
1638
1999
  function objectIdentity(object) {
1639
2000
  return object.identity;
1640
2001
  }
2002
+ function indexExposure(exposure) {
2003
+ const indexed = new Map;
2004
+ for (const table of exposure ?? [])
2005
+ indexed.set(table.table, table);
2006
+ return indexed;
2007
+ }
2008
+ function noteReportingSelect(pending, objects, role, target, ref, exposed, built) {
2009
+ const key = columnKey(role, ref, "SELECT");
2010
+ const declared = pending.get(key);
2011
+ if (ref.kind === "view" || ref.kind === "materializedView") {
2012
+ if (declared !== undefined)
2013
+ return;
2014
+ objects.push(selectGrant(role, ref, target));
2015
+ return;
2016
+ }
2017
+ const visible = visibleColumns(ref.name, exposed, built);
2018
+ const hidden = hiddenColumns(ref.name, exposed);
2019
+ if (hidden.size === 0) {
2020
+ pending.delete(key);
2021
+ objects.push(selectGrant(role, ref, target));
2022
+ return;
2023
+ }
2024
+ const columns = new Set(visible);
2025
+ for (const name of declared?.columns ?? [])
2026
+ columns.add(name);
2027
+ if (columns.size === 0) {
2028
+ pending.delete(key);
2029
+ return;
2030
+ }
2031
+ const next = declared ?? openColumnGrant(role, ref, "SELECT", target);
2032
+ next.columns.clear();
2033
+ for (const name of columns)
2034
+ next.columns.add(name);
2035
+ pending.set(key, next);
2036
+ }
2037
+ function selectGrant(role, ref, target) {
2038
+ return grantObject({
2039
+ role,
2040
+ object: ref,
2041
+ privilege: "SELECT",
2042
+ provenance: PROVENANCE,
2043
+ dependencies: [{ kind: "role", name: role }, target.identity]
2044
+ });
2045
+ }
2046
+ function noteColumnGrant(pending, grant, built, exposed, reporting) {
2047
+ const role = requireName(grant.role, "column grant role");
2048
+ const target = findRelation(built, grant.on);
2049
+ if (target.owner === "ignored") {
2050
+ throw new OkmError2("invalid", `Column grant on ${grant.on} targets an ignored table.`, {
2051
+ fix: { summary: "Remove the grant, or mark the table external." }
2052
+ });
2053
+ }
2054
+ const ref = grantRef(target);
2055
+ if (ref === undefined) {
2056
+ throw new OkmError2("OKM1020", `Column grant on ${grant.on} is not a table or a view.`, {
2057
+ fix: { summary: "Name a table, view, or materialized view in the schema." }
2058
+ });
2059
+ }
2060
+ for (const privilege of ["select", "update"]) {
2061
+ const names = grant[privilege];
2062
+ if (names === undefined || names.length === 0)
2063
+ continue;
2064
+ if (role === reporting && privilege === "update") {
2065
+ throw new RoleDeclarationError("reporting_write", `Role ${role} is the reporting role and cannot be granted update.`);
2066
+ }
2067
+ const upper = privilege === "select" ? "SELECT" : "UPDATE";
2068
+ const resolved = names.map((name) => resolveColumn(target, built, ref.name, name, exposed));
2069
+ const current = pending.get(columnKey(role, ref, upper)) ?? openColumnGrant(role, ref, upper, target);
2070
+ for (const name of resolved)
2071
+ current.columns.add(name);
2072
+ pending.set(current.key, current);
2073
+ }
2074
+ }
2075
+ function openColumnGrant(role, object, privilege, target) {
2076
+ return {
2077
+ key: columnKey(role, object, privilege),
2078
+ role,
2079
+ privilege,
2080
+ object,
2081
+ target,
2082
+ columns: new Set
2083
+ };
2084
+ }
2085
+ function columnKey(role, object, privilege) {
2086
+ const namespace = object.namespace.form === "static" ? object.namespace.name : object.namespace.pattern;
2087
+ return `${role}\x00${object.kind}\x00${namespace}\x00${object.name}\x00${privilege}`;
2088
+ }
2089
+ function findRelation(built, name) {
2090
+ const found = built.filter((object) => (object.kind === "table" || object.kind === "view" || object.kind === "materializedView") && object.identity.name === name);
2091
+ const target = found[0];
2092
+ if (target === undefined) {
2093
+ throw new OkmError2("OKM1020", `Column grant names ${name}, which is not in the schema.`, {
2094
+ fix: { summary: "Use the SQL name of a table, view, or materialized view." }
2095
+ });
2096
+ }
2097
+ if (found.length > 1) {
2098
+ throw new OkmError2("OKM1020", `Column grant on ${name} matches more than one object.`, {
2099
+ fix: { summary: "Rename one of the objects so the grant names one." }
2100
+ });
2101
+ }
2102
+ return target;
2103
+ }
2104
+ function resolveColumn(target, built, table, name, exposed) {
2105
+ const known = exposed.get(table);
2106
+ const fromField = known?.columns.find((column) => column.field === name || column.sql === name);
2107
+ if (fromField !== undefined)
2108
+ return fromField.sql;
2109
+ const sqlNames = columnNames(target, built, table);
2110
+ if (sqlNames.includes(name))
2111
+ return name;
2112
+ throw new OkmError2("OKM1020", `Column ${table}.${name} is not in the schema.`, {
2113
+ fix: { summary: "Name a column of that table or view." }
2114
+ });
2115
+ }
2116
+ function visibleColumns(table, exposed, built) {
2117
+ const known = exposed.get(table);
2118
+ if (known !== undefined) {
2119
+ return known.columns.filter((column) => !column.hidden).map((column) => column.sql);
2120
+ }
2121
+ return built.flatMap((object) => object.kind === "column" && object.identity.parent.name === table ? [object.identity.name] : []);
2122
+ }
2123
+ function hiddenColumns(table, exposed) {
2124
+ const known = exposed.get(table);
2125
+ return new Set((known?.columns ?? []).filter((column) => column.hidden).map((column) => column.sql));
2126
+ }
2127
+ function columnNames(target, built, table) {
2128
+ if (target.kind === "view" || target.kind === "materializedView") {
2129
+ return target.definition.columns.map((column) => column.name);
2130
+ }
2131
+ return built.flatMap((object) => object.kind === "column" && object.identity.parent.name === table ? [object.identity.name] : []);
2132
+ }
1641
2133
 
1642
2134
  // src/tooling/migrate/estimate.ts
1643
2135
  var LARGE_TABLE_ROWS = 1e6;
@@ -1677,15 +2169,13 @@ async function readRowEstimates(url, schema, tables) {
1677
2169
  return map;
1678
2170
  } catch (error) {
1679
2171
  if (began)
1680
- await connection.execute("rollback").catch(() => {
1681
- return;
1682
- });
1683
- throw error;
2172
+ await finishQuiet(() => connection.execute("rollback"));
2173
+ throw sessionFailure(error) ?? error;
1684
2174
  } finally {
1685
- await connection.release();
2175
+ await finishQuiet(() => connection.release());
1686
2176
  }
1687
2177
  } finally {
1688
- await pool.close();
2178
+ await finishQuiet(() => pool.close());
1689
2179
  }
1690
2180
  }
1691
2181
  function annotateLock(step, estimates, aliases = new Map) {
@@ -1842,15 +2332,17 @@ async function buildProject(cwd) {
1842
2332
  }
1843
2333
  async function generateProject(cwd, name, flags) {
1844
2334
  const prepared = await prepare(cwd, name, flags);
2335
+ if (name === "init")
2336
+ writeGitAttributes(cwd);
1845
2337
  writeArtifact(cwd, prepared.config, prepared.built);
1846
2338
  if (prepared.plan.steps.length === 0)
1847
2339
  return;
1848
- const directory = join2(cwd, prepared.config.migrations ?? "migrations");
2340
+ const directory = projectPath(cwd, prepared.config.migrations ?? "migrations");
1849
2341
  mkdirSync(directory, { recursive: true });
1850
2342
  const base = `${nextNumber(directory)}_${safeName(name)}`;
1851
- const sqlPath = join2(directory, `${base}.sql`);
2343
+ const sqlPath = join3(directory, `${base}.sql`);
1852
2344
  writeFileSync(sqlPath, formatPlan(prepared.plan));
1853
- writeFileSync(join2(directory, `${base}.catalog.json`), serializeCatalog2(prepared.built.catalog));
2345
+ writeFileSync(join3(directory, `${base}.catalog.json`), serializeCatalog2(prepared.built.catalog));
1854
2346
  return { path: sqlPath, findings: lintPrepared(prepared) };
1855
2347
  }
1856
2348
  async function planProject(cwd, name, flags, invoke) {
@@ -1873,9 +2365,9 @@ async function checkProject(cwd, flags) {
1873
2365
  const targets = listTargets(opened.config);
1874
2366
  assertTargetAlias(targets);
1875
2367
  if (opened.config.tables !== undefined) {
1876
- const directory = join2(cwd, opened.config.tables);
1877
- const files = existsSync2(directory) ? readdirSync2(directory) : [];
1878
- const source = readFileSync2(join2(cwd, opened.config.schema), "utf8");
2368
+ const directory = projectPath(cwd, opened.config.tables);
2369
+ const files = existsSync3(directory) ? readdirSync3(directory) : [];
2370
+ const source = readFileSync3(projectPath(cwd, opened.config.schema), "utf8");
1879
2371
  const missing = unlistedTableFiles(source, files);
1880
2372
  if (missing.length > 0) {
1881
2373
  throw new OkmError2("OKM1024", `Table file not in schema: ${missing.join(", ")}.`, {
@@ -1894,7 +2386,7 @@ async function checkProject(cwd, flags) {
1894
2386
  const target = selectTarget(opened.config, named);
1895
2387
  assertTargetPolicy(target, "check", flags?.allowProtected ?? false);
1896
2388
  assertTargetPolicy(target, "drift", flags?.allowProtected ?? false);
1897
- const { assertAuthorDrift } = await import("./6qd27mx0.js");
2389
+ const { assertAuthorDrift } = await import("./jvd0mqag.js");
1898
2390
  const allowPooler = flags?.allowPooler === true || opened.config.allowPooler === true;
1899
2391
  await assertAuthorDrift(target.url, opened.built.catalog, opened.config.roles, allowPooler);
1900
2392
  return findings;
@@ -1975,7 +2467,7 @@ async function openProject(cwd) {
1975
2467
  const config = await loadConfig(cwd);
1976
2468
  const built = withRoles(await loadSchema(cwd, config.schema), config);
1977
2469
  const declarations = schemaDeclarations(built);
1978
- const previous = readPrevious(join2(cwd, config.migrations ?? "migrations"));
2470
+ const previous = readPrevious(projectPath(cwd, config.migrations ?? "migrations"));
1979
2471
  const stale = staleRenames(previous?.catalog, declarations.renames);
1980
2472
  if (stale.length > 0) {
1981
2473
  throw new OkmError2("OKM1020", stale.join(" "), {
@@ -1990,25 +2482,108 @@ async function openProject(cwd) {
1990
2482
  };
1991
2483
  }
1992
2484
  function withRoles(built, config) {
1993
- if (config.roles === undefined)
2485
+ const marks = ownershipMarks(built);
2486
+ const stamped = marks.size === 0 ? built.catalog : stampOwnership(built.catalog, marks);
2487
+ const roles = config.roles;
2488
+ const catalog = roles === undefined ? stamped : attachRoles(stamped, roles, exposureFor(built, roles));
2489
+ if (catalog === built.catalog)
1994
2490
  return built;
1995
- return { ...built, catalog: attachRoles(built.catalog, config.roles) };
2491
+ return { ...built, catalog };
2492
+ }
2493
+ function ownershipMarks(built) {
2494
+ const marks = new Map;
2495
+ const models = built.model;
2496
+ for (const table of built.tables) {
2497
+ const owner = tableOwner(table);
2498
+ if (owner === undefined)
2499
+ continue;
2500
+ const model = models[table.name];
2501
+ if (model === undefined)
2502
+ continue;
2503
+ marks.set(model.sql, owner);
2504
+ }
2505
+ return marks;
2506
+ }
2507
+ function exposureFor(built, roles) {
2508
+ if (roles.reporting === undefined && (roles.columns === undefined || roles.columns.length === 0)) {
2509
+ return;
2510
+ }
2511
+ return Object.values(built.model).map((model) => ({
2512
+ table: model.sql,
2513
+ columns: model.columns.map((column) => ({
2514
+ field: column.field,
2515
+ sql: column.sql,
2516
+ hidden: column.hidden === true || column.hidden === "filter"
2517
+ }))
2518
+ }));
2519
+ }
2520
+ var GIT_ATTRIBUTES = [
2521
+ "migrations/*.sql text eol=lf",
2522
+ "*.catalog.json text eol=lf",
2523
+ ".okm/** text eol=lf",
2524
+ ""
2525
+ ].join(`
2526
+ `);
2527
+ function writeGitAttributes(cwd) {
2528
+ const path = projectPath(cwd, ".gitattributes");
2529
+ if (existsSync3(path))
2530
+ return;
2531
+ writeFileSync(path, GIT_ATTRIBUTES);
2532
+ }
2533
+ function typesText(cwd, spec, built) {
2534
+ return `${emitRowTypes(built, jsonColumnTypes(readSchemaSource(cwd, spec)))}${referenceAugmentation(built)}`;
2535
+ }
2536
+ function readSchemaSource(cwd, spec) {
2537
+ const parts = [];
2538
+ const seen = new Set;
2539
+ const visit = (file) => {
2540
+ if (seen.has(file) || !existsSync3(file))
2541
+ return;
2542
+ let stat;
2543
+ try {
2544
+ stat = statSync(file);
2545
+ } catch {
2546
+ return;
2547
+ }
2548
+ if (!stat.isFile())
2549
+ return;
2550
+ seen.add(file);
2551
+ const text = readFileSync3(file, "utf8");
2552
+ parts.push(text);
2553
+ for (const match of text.matchAll(/from\s+["'](\.[^"']+)["']/g)) {
2554
+ const rel = match[1];
2555
+ if (rel === undefined)
2556
+ continue;
2557
+ const base = join3(dirname(file), rel);
2558
+ const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}.mts`, join3(base, "index.ts")];
2559
+ for (const candidate of candidates) {
2560
+ if (existsSync3(candidate)) {
2561
+ visit(candidate);
2562
+ break;
2563
+ }
2564
+ }
2565
+ }
2566
+ };
2567
+ visit(projectPath(cwd, spec));
2568
+ return parts.join(`
2569
+ `);
1996
2570
  }
1997
2571
  function writeArtifact(cwd, config, built) {
1998
- const directory = join2(cwd, config.out ?? ".okm");
2572
+ const directory = projectPath(cwd, config.out ?? ".okm");
1999
2573
  mkdirSync(directory, { recursive: true });
2000
2574
  const startup = startupCatalog2(built.catalog);
2001
- writeFileSync(join2(directory, "catalog.json"), serializeCatalog2(startup));
2002
- writeFileSync(join2(directory, "catalog.hash"), `${catalogHash2(startup)}
2575
+ const text = catalogFileText2(serializeCatalog2(startup));
2576
+ writeFileSync(join3(directory, "catalog.json"), text);
2577
+ writeFileSync(join3(directory, "catalog.hash"), `${sha2562(text)}
2003
2578
  `);
2004
- writeFileSync(join2(directory, "types.d.ts"), `${emitRowTypes(built)}${referenceAugmentation(built)}`);
2579
+ writeFileSync(join3(directory, "types.d.ts"), typesText(cwd, config.schema, built));
2005
2580
  return directory;
2006
2581
  }
2007
2582
  async function loadBuiltSchema(cwd, config) {
2008
2583
  return withRoles(await loadSchema(cwd, config.schema), config);
2009
2584
  }
2010
2585
  async function loadConfig(cwd) {
2011
- const path = join2(cwd, "okmodel.config.ts");
2586
+ const path = projectPath(cwd, "okmodel.config.ts");
2012
2587
  const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
2013
2588
  const record = isRecord(imported) ? imported : {};
2014
2589
  const config = record.default ?? record.config;
@@ -2018,7 +2593,7 @@ async function loadConfig(cwd) {
2018
2593
  return config;
2019
2594
  }
2020
2595
  async function loadSchema(cwd, spec) {
2021
- const path = join2(cwd, spec);
2596
+ const path = projectPath(cwd, spec);
2022
2597
  const imported = await import(`${pathToFileURL(path).href}?okm=${Date.now()}`);
2023
2598
  const record = isRecord(imported) ? imported : {};
2024
2599
  for (const value of Object.values(record)) {
@@ -2030,13 +2605,13 @@ async function loadSchema(cwd, spec) {
2030
2605
  throw new OkmError2("invalid", `${spec} must export a schema().`);
2031
2606
  }
2032
2607
  function readPrevious(directory) {
2033
- if (!existsSync2(directory))
2608
+ if (!existsSync3(directory))
2034
2609
  return;
2035
- const catalogs = readdirSync2(directory).filter((file) => file.endsWith(".catalog.json")).sort();
2610
+ const catalogs = readdirSync3(directory).filter((file) => file.endsWith(".catalog.json")).sort();
2036
2611
  const latest = catalogs.at(-1);
2037
2612
  if (latest === undefined)
2038
2613
  return;
2039
- const text = readFileSync2(join2(directory, latest), "utf8");
2614
+ const text = readFileSync3(join3(directory, latest), "utf8");
2040
2615
  return { catalog: parseCatalog2(text) };
2041
2616
  }
2042
2617
  function referenceAugmentation(source) {
@@ -2061,8 +2636,8 @@ function referenceAugmentation(source) {
2061
2636
  }
2062
2637
  function nextNumber(directory) {
2063
2638
  let max = 0;
2064
- if (existsSync2(directory)) {
2065
- for (const file of readdirSync2(directory)) {
2639
+ if (existsSync3(directory)) {
2640
+ for (const file of readdirSync3(directory)) {
2066
2641
  const match = /^(\d+)_/.exec(file);
2067
2642
  const value = match?.[1];
2068
2643
  if (value !== undefined)
@@ -2103,14 +2678,14 @@ function projectImportsValidate(cwd) {
2103
2678
  function directoryImportsValidate(dir) {
2104
2679
  let entries;
2105
2680
  try {
2106
- entries = readdirSync2(dir, { withFileTypes: true });
2681
+ entries = readdirSync3(dir, { withFileTypes: true });
2107
2682
  } catch {
2108
2683
  return false;
2109
2684
  }
2110
2685
  for (const entry of entries) {
2111
2686
  if (entry.name.startsWith(".") || SKIP_SCAN.has(entry.name))
2112
2687
  continue;
2113
- const path = join2(dir, entry.name);
2688
+ const path = join3(dir, entry.name);
2114
2689
  if (entry.isDirectory()) {
2115
2690
  if (directoryImportsValidate(path))
2116
2691
  return true;
@@ -2118,7 +2693,7 @@ function directoryImportsValidate(dir) {
2118
2693
  }
2119
2694
  if (!/\.(?:ts|tsx|mts|cts)$/.test(entry.name))
2120
2695
  continue;
2121
- if (runtimeImportsValidate(readFileSync2(path, "utf8")))
2696
+ if (runtimeImportsValidate(readFileSync3(path, "utf8")))
2122
2697
  return true;
2123
2698
  }
2124
2699
  return false;
@@ -2140,4 +2715,4 @@ function runtimeImportsValidate(source) {
2140
2715
  return false;
2141
2716
  }
2142
2717
 
2143
- export { errorDoc, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, loadConfig, loadSchema };
2718
+ export { projectPath, errorDoc, loadMigrations, readMigrationPlan, loadHeadSnapshot, lintMigrationDirectory, formatFindings, hasError, lintRefusal, buildProject, generateProject, planProject, checkProject, projectHead, openProject, loadBuiltSchema, loadConfig, loadSchema };