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
@@ -1,9 +1,13 @@
1
1
  import {
2
2
  renderCatalog
3
- } from "./8xsbagad.js";
3
+ } from "./9s0ypp87.js";
4
+ import {
5
+ viewGlobalReason,
6
+ viewMentions
7
+ } from "./5e3tf63j.js";
4
8
  import {
5
9
  createClient
6
- } from "./hf22e0wt.js";
10
+ } from "./tbrvgw6z.js";
7
11
  import {
8
12
  OkmError2
9
13
  } from "./52szzakm.js";
@@ -36,6 +40,41 @@ function readFacts(schema) {
36
40
  }
37
41
  return facts;
38
42
  }
43
+ function readViews(schema) {
44
+ const tables = new Set;
45
+ const listed = new Map;
46
+ for (const object of schema.catalog.objects) {
47
+ if (object.kind === "table")
48
+ tables.add(object.identity.name);
49
+ if (object.kind !== "view" && object.kind !== "materializedView")
50
+ continue;
51
+ listed.set(object.identity.name, { kind: object.kind, query: object.definition.query });
52
+ }
53
+ const views = [];
54
+ for (const name of Object.keys(schema.model)) {
55
+ const model = schema.model[name];
56
+ if (model === undefined || tables.has(model.sql))
57
+ continue;
58
+ const meta = listed.get(model.sql);
59
+ if (meta === undefined)
60
+ continue;
61
+ const reason = viewGlobalReason(model);
62
+ views.push({
63
+ name,
64
+ sql: model.sql,
65
+ kind: meta.kind,
66
+ query: meta.query,
67
+ columns: model.columns.map((column) => ({
68
+ field: column.field,
69
+ dataType: column.dataType,
70
+ guarded: column.guarded
71
+ })),
72
+ tenant: reason === undefined && isTenant(schema.tenancy, model),
73
+ globalReason: reason
74
+ });
75
+ }
76
+ return views;
77
+ }
39
78
  function isRequired(column) {
40
79
  if (!column.writable || column.guarded || column.fill)
41
80
  return false;
@@ -113,7 +152,7 @@ function isTenant(tenancy, model) {
113
152
  try {
114
153
  return tenancy.predicate({
115
154
  table: model.name,
116
- fieldSql: (field) => field === tenancy.key ? key?.sql : undefined,
155
+ fieldSql: (field) => model.columns.find((column) => column.field === field)?.sql,
117
156
  encode: key?.encode,
118
157
  alias: "t",
119
158
  appended: false,
@@ -348,7 +387,7 @@ function paired(host, child, parent, childSql, parentSql, row) {
348
387
  return linked;
349
388
  }
350
389
  async function writeRow(host, facts, data, tenant) {
351
- const client = facts.tenant && tenant !== undefined ? openScope(host.db, host.schema.tenancy?.key ?? "tenantId", tenant) : host.db;
390
+ const client = facts.tenant && tenant !== undefined ? openScope(host.db, host.schema.tenancy, tenant) : host.db;
352
391
  const handle = tableHandle(client, facts.name);
353
392
  const inserted = await handle.insert(data);
354
393
  if (!isRow(inserted)) {
@@ -472,13 +511,17 @@ function refOf(value) {
472
511
  const table = value[REF];
473
512
  return typeof table === "string" ? table : undefined;
474
513
  }
475
- function openScope(db, key, tenant) {
514
+ function openScope(db, tenancy, tenant) {
476
515
  if (!("for" in db) || typeof db.for !== "function") {
477
516
  throw new OkmError2("invalid", "This schema has no for().", {
478
517
  fix: { summary: "Pass columnTenancy() to schema({ tenancy })." }
479
518
  });
480
519
  }
481
- const opened = db.for({ [key]: tenant });
520
+ const keys = tenancy?.keys ?? [tenancy?.key ?? "tenantId"];
521
+ const input = {};
522
+ for (const key of keys)
523
+ input[key] = tenant;
524
+ const opened = db.for(input);
482
525
  if (typeof opened !== "object" || opened === null) {
483
526
  throw new OkmError2("invalid", "for() did not return a client.", {
484
527
  fix: { summary: "The tenant key must be a uuid." }
@@ -509,6 +552,15 @@ async function isolation(host) {
509
552
  const saved = host.cache;
510
553
  host.cache = new Map;
511
554
  try {
555
+ if (host.schema.tenancy?.strategy === "rls") {
556
+ const check = host.db["~rls"];
557
+ if (check === undefined) {
558
+ throw new OkmError2("invalid", "Row-level security has no policy check on this client.", {
559
+ fix: { summary: "Open the client from a schema that uses rlsTenancy()." }
560
+ });
561
+ }
562
+ await check();
563
+ }
512
564
  const checked = [];
513
565
  const skipped = [];
514
566
  const tenantA = crypto.randomUUID();
@@ -520,10 +572,19 @@ async function isolation(host) {
520
572
  }
521
573
  if (!facts.tenant)
522
574
  continue;
523
- await checkTable(host, facts, tenantA, tenantB);
575
+ const hops = host.schema.tenancy?.pathOf?.(facts.name);
576
+ if (hops !== undefined && hops.length > 0) {
577
+ await checkPath(host, facts, hops, tenantA, tenantB);
578
+ } else {
579
+ await checkTable(host, facts, tenantA, tenantB);
580
+ const keys = tenantKeys(host);
581
+ if (keys.length > 1)
582
+ await checkFlips(host, facts, keys, tenantA, tenantB);
583
+ }
524
584
  checked.push(facts.name);
525
585
  }
526
- return { checked, skipped };
586
+ const views = await checkViews(host, tenantA, tenantB, skipped);
587
+ return { checked, skipped, views };
527
588
  } finally {
528
589
  host.cache = saved;
529
590
  }
@@ -532,13 +593,14 @@ async function checkTable(host, facts, tenantA, tenantB) {
532
593
  const key = host.schema.tenancy?.key;
533
594
  if (key === undefined)
534
595
  return;
596
+ const keys = tenantKeys(host);
535
597
  if (host.definitions.size > 0 && !host.definitions.has(facts.name)) {
536
598
  throw new OkmError2("invalid", `Factory ${facts.name} is missing.`, {
537
599
  fix: { summary: `Add ${facts.name} to factories().` }
538
600
  });
539
601
  }
540
602
  const row = await insertRow(host, facts.name, { [key]: tenantB }, undefined, tenantB);
541
- const where = primaryWhere(facts, row, key);
603
+ const where = primaryWhere(facts, row, keys);
542
604
  const client = openTenant(host, tenantA);
543
605
  const handle = tableHandle2(client, facts.name);
544
606
  try {
@@ -579,7 +641,10 @@ async function readTenant(facts, handle, where) {
579
641
  leak(facts.name, "stream", item);
580
642
  }
581
643
  } catch (error) {
582
- if (!(error instanceof OkmError2) || error.code !== "OKM1111")
644
+ if (!(error instanceof OkmError2))
645
+ throw error;
646
+ const refused = error.code === "OKM1111" || error.code === "OKM1120" && error.message.includes("primary key");
647
+ if (!refused)
583
648
  throw error;
584
649
  }
585
650
  }
@@ -649,10 +714,10 @@ function valueFrom(host, facts, field, row) {
649
714
  return host.rng.uuid();
650
715
  }
651
716
  }
652
- function primaryWhere(facts, row, tenantKey) {
717
+ function primaryWhere(facts, row, tenantKeys) {
653
718
  const where = {};
654
719
  for (const field of facts.primary) {
655
- if (field === tenantKey)
720
+ if (tenantKeys.includes(field))
656
721
  continue;
657
722
  where[field] = row[field];
658
723
  }
@@ -669,15 +734,343 @@ ${JSON.stringify(row)}`, {
669
734
  fix: { summary: "A query under tenant A returned a row inserted for tenant B." }
670
735
  });
671
736
  }
737
+ function tenantKeys(host) {
738
+ const tenancy = host.schema.tenancy;
739
+ if (tenancy === undefined)
740
+ return [];
741
+ if (tenancy.keys !== undefined && tenancy.keys.length > 0)
742
+ return tenancy.keys;
743
+ return [tenancy.key];
744
+ }
745
+ function scopeFor(keys, tenant) {
746
+ const input = {};
747
+ for (const key of keys)
748
+ input[key] = tenant;
749
+ return input;
750
+ }
751
+ async function checkFlips(host, facts, keys, tenantA, tenantB) {
752
+ const template = await insertRow(host, facts.name, undefined, undefined, tenantB);
753
+ const data = { ...template };
754
+ for (const key of keys)
755
+ delete data[key];
756
+ const clientA = openTenant(host, scopeFor(keys, tenantA));
757
+ try {
758
+ for (let index = 0;index < keys.length; index += 1) {
759
+ const values = scopeFor(keys, tenantA);
760
+ const flipped = keys[index];
761
+ if (flipped === undefined)
762
+ continue;
763
+ values[flipped] = tenantB;
764
+ const client = openTenant(host, values);
765
+ const stored = await tableHandle2(client, facts.name).insert(data);
766
+ if (!isRow2(stored))
767
+ continue;
768
+ const where = primaryWhere(facts, stored, keys);
769
+ await readTenant(facts, tableHandle2(clientA, facts.name), where);
770
+ await tableHandle2(client, facts.name).delete({ where }).catch(() => {
771
+ return;
772
+ });
773
+ }
774
+ } finally {
775
+ const where = primaryWhere(facts, template, keys);
776
+ await tableHandle2(openTenant(host, tenantB), facts.name).delete({ where }).catch(() => {
777
+ return;
778
+ });
779
+ forgetCached(host, facts.name, tenantB);
780
+ }
781
+ }
782
+ async function checkPath(host, facts, hops, tenantA, tenantB) {
783
+ const keys = tenantKeys(host);
784
+ const created = [];
785
+ const last = hops[hops.length - 1];
786
+ if (last === undefined)
787
+ return;
788
+ let parent = await insertRow(host, last.parent, undefined, undefined, tenantB);
789
+ created.push({
790
+ table: last.parent,
791
+ where: primaryWhere(requireFacts(host, last.parent), parent, keys)
792
+ });
793
+ for (let index = hops.length - 1;index >= 0; index -= 1) {
794
+ const hop = hops[index];
795
+ if (hop === undefined)
796
+ continue;
797
+ const row = await insertRow(host, hop.child, { [hop.localField]: parent[hop.remoteField] }, undefined, tenantB);
798
+ parent = row;
799
+ created.push({
800
+ table: hop.child,
801
+ where: primaryWhere(requireFacts(host, hop.child), row, keys)
802
+ });
803
+ }
804
+ const where = primaryWhere(facts, parent, keys);
805
+ const client = openTenant(host, tenantA);
806
+ try {
807
+ await readTenant(facts, tableHandle2(client, facts.name), where);
808
+ const set = updateSet(host, facts, parent);
809
+ if (set !== undefined) {
810
+ const updated = asCount(await tableHandle2(client, facts.name).update({ where, set }));
811
+ if (updated > 0)
812
+ leak(facts.name, "update", { count: updated });
813
+ }
814
+ const removed = asCount(await tableHandle2(client, facts.name).delete({ where }));
815
+ if (removed > 0)
816
+ leak(facts.name, "delete", { count: removed });
817
+ } finally {
818
+ for (let index = created.length - 1;index >= 0; index -= 1) {
819
+ const item = created[index];
820
+ if (item === undefined)
821
+ continue;
822
+ await tableHandle2(openTenant(host, tenantB), item.table).delete({ where: item.where }).catch(() => {
823
+ return;
824
+ });
825
+ forgetCached(host, item.table, tenantB);
826
+ }
827
+ }
828
+ }
829
+ var SCAN_LIMIT = 100;
830
+ async function checkViews(host, tenantA, tenantB, skipped) {
831
+ const checked = [];
832
+ const keys = tenantKeys(host);
833
+ for (const view of readViews(host.schema)) {
834
+ if (view.globalReason !== undefined) {
835
+ skipped.push({ table: view.name, reason: view.globalReason });
836
+ continue;
837
+ }
838
+ if (!view.tenant)
839
+ continue;
840
+ await checkView(host, view, keys, tenantA, tenantB);
841
+ checked.push(view.name);
842
+ }
843
+ return checked;
844
+ }
845
+ async function checkView(host, view, keys, tenantA, tenantB) {
846
+ const sources = orderSources(host, sourceNames(host, view.query));
847
+ if (sources.length === 0) {
848
+ throw new OkmError2("invalid", `View ${view.name} names no table isolation can seed.`, {
849
+ fieldReason: "no-source",
850
+ fix: { summary: 'Point the view at a table in this schema, or mark it global("reason").' }
851
+ });
852
+ }
853
+ for (const name of sources) {
854
+ if (host.definitions.size > 0 && !host.definitions.has(name)) {
855
+ throw new OkmError2("invalid", `Factory ${name} is missing.`, {
856
+ fix: { summary: `Add ${name} to factories().` }
857
+ });
858
+ }
859
+ }
860
+ const before = new Map(host.cache);
861
+ const seeded = [];
862
+ try {
863
+ for (const tenant of [tenantA, tenantB]) {
864
+ for (const name of sources) {
865
+ const facts = requireFacts(host, name);
866
+ const key = keys[0];
867
+ const overrides = markerFor(view, facts, keys, tenant);
868
+ if (key !== undefined)
869
+ overrides[key] = tenant;
870
+ const row = await insertRow(host, name, overrides, undefined, tenant);
871
+ if (tenant === tenantB)
872
+ seeded.push(row);
873
+ }
874
+ }
875
+ const handle = viewHandle(openTenant(host, tenantA), view.name);
876
+ if (view.kind === "materializedView") {
877
+ if (handle.refresh === undefined) {
878
+ throw new OkmError2("invalid", `View ${view.name} has no refresh().`, {
879
+ fieldReason: "no-refresh",
880
+ fix: { summary: "A materialized view refreshes before the isolation read." }
881
+ });
882
+ }
883
+ await handle.refresh();
884
+ }
885
+ await readView(view, handle, keys, tenantB, seeded);
886
+ } finally {
887
+ await dropNewRows(host, before, keys);
888
+ }
889
+ }
890
+ function sourceNames(host, query) {
891
+ const names = [];
892
+ for (const facts of host.facts.values()) {
893
+ if (viewMentions(query, facts.sql) || viewMentions(query, facts.name))
894
+ names.push(facts.name);
895
+ }
896
+ return names;
897
+ }
898
+ function orderSources(host, names) {
899
+ const wanted = new Set(names);
900
+ const ordered = [];
901
+ const pending = [...names];
902
+ const guard = pending.length * pending.length + 1;
903
+ let spins = 0;
904
+ while (pending.length > 0 && spins < guard) {
905
+ spins += 1;
906
+ const name = pending.shift();
907
+ if (name === undefined)
908
+ break;
909
+ const facts = host.facts.get(name);
910
+ const waiting = facts?.foreignKeys.some((key) => {
911
+ let parent;
912
+ for (const item of host.facts.values()) {
913
+ if (item.sql === key.target)
914
+ parent = item.name;
915
+ }
916
+ return parent !== undefined && wanted.has(parent) && !ordered.includes(parent);
917
+ });
918
+ if (waiting)
919
+ pending.push(name);
920
+ else
921
+ ordered.push(name);
922
+ }
923
+ return ordered.length === names.length ? ordered : [...names];
924
+ }
925
+ function markerFor(view, facts, keys, tenant) {
926
+ const value = `isolation-${tenant}`;
927
+ const out = {};
928
+ for (const column of view.columns) {
929
+ if (column.guarded || keys.includes(column.field))
930
+ continue;
931
+ if (column.dataType.toLowerCase() !== "text")
932
+ continue;
933
+ const source = facts.columns.find((item) => item.field === column.field);
934
+ if (source === undefined || !source.writable || source.guarded)
935
+ continue;
936
+ if (keys.includes(source.field))
937
+ continue;
938
+ if (facts.foreignKeys.some((key) => key.columns.includes(source.sql)))
939
+ continue;
940
+ out[source.field] = value;
941
+ }
942
+ return out;
943
+ }
944
+ async function readView(view, handle, keys, tenantB, seeded) {
945
+ const exposed = view.columns.filter((column) => keys.includes(column.field));
946
+ const where = markerWhere(view, keys, seeded);
947
+ if (where === undefined && exposed.length === 0) {
948
+ throw new OkmError2("invalid", `View ${view.name} exposes no tenant key and no text column isolation can match.`, {
949
+ fieldReason: "no-marker",
950
+ fix: { summary: "Expose the tenant key, or a text column isolation can match." }
951
+ });
952
+ }
953
+ if (where !== undefined)
954
+ await readFiltered(view.name, handle, where);
955
+ if (exposed.length === 0)
956
+ return;
957
+ const rows = asRows(await handle.find({ limit: SCAN_LIMIT }));
958
+ const needle = tenantB.toLowerCase();
959
+ for (const row of rows) {
960
+ for (const column of exposed) {
961
+ const value = row[column.field];
962
+ if (typeof value === "string" && value.toLowerCase() === needle)
963
+ leak(view.name, "find", row);
964
+ }
965
+ }
966
+ }
967
+ function markerWhere(view, keys, rows) {
968
+ for (const column of view.columns) {
969
+ if (column.guarded || keys.includes(column.field))
970
+ continue;
971
+ if (column.dataType.toLowerCase() !== "text")
972
+ continue;
973
+ for (const row of rows) {
974
+ const value = row[column.field];
975
+ if (typeof value === "string")
976
+ return { [column.field]: value };
977
+ }
978
+ }
979
+ return;
980
+ }
981
+ async function readFiltered(name, handle, where) {
982
+ const query = handle.find({ where, limit: 1 });
983
+ const rows = asRows(await query);
984
+ if (rows.length > 0)
985
+ leak(name, "find", rows[0]);
986
+ const streamed = handle.find({ where, limit: 1 });
987
+ if (typeof streamed.stream === "function") {
988
+ try {
989
+ for await (const item of streamed.stream()) {
990
+ if (isRow2(item))
991
+ leak(name, "stream", item);
992
+ }
993
+ } catch (error) {
994
+ if (!(error instanceof OkmError2))
995
+ throw error;
996
+ const refused = error.code === "OKM1111" || error.code === "OKM1120" && error.message.includes("primary key");
997
+ if (!refused)
998
+ throw error;
999
+ }
1000
+ }
1001
+ const one = await handle.one({ where });
1002
+ if (isRow2(one))
1003
+ leak(name, "one", one);
1004
+ if (asCount(await handle.count({ where })) > 0)
1005
+ leak(name, "count", { count: 1 });
1006
+ if (await handle.exists({ where }) === true)
1007
+ leak(name, "exists", { exists: true });
1008
+ }
1009
+ async function dropNewRows(host, before, keys) {
1010
+ const added = [];
1011
+ for (const [key, row] of host.cache) {
1012
+ if (!before.has(key))
1013
+ added.push({ key, row });
1014
+ }
1015
+ for (let index = added.length - 1;index >= 0; index -= 1) {
1016
+ const item = added[index];
1017
+ if (item === undefined)
1018
+ continue;
1019
+ const colon = item.key.indexOf(":");
1020
+ const scope = item.key.slice(0, colon);
1021
+ const table = item.key.slice(colon + 1);
1022
+ const facts = host.facts.get(table);
1023
+ const tenant = scope === "*" ? undefined : scope;
1024
+ if (facts !== undefined) {
1025
+ try {
1026
+ const where = primaryWhere(facts, item.row, keys);
1027
+ const client = tenant === undefined ? host.db : openTenant(host, tenant);
1028
+ await tableHandle2(client, table).delete({ where });
1029
+ } catch {}
1030
+ }
1031
+ forgetCached(host, table, tenant);
1032
+ }
1033
+ }
1034
+ function viewHandle(client, name) {
1035
+ const views = "views" in client ? client.views : undefined;
1036
+ if (typeof views !== "object" || views === null) {
1037
+ throw new OkmError2("invalid", "The client has no views.", {
1038
+ fix: { summary: "Declare the view on schema({ views }) and connect with that schema." }
1039
+ });
1040
+ }
1041
+ const handle = views[name];
1042
+ if (!isViewQuery(handle)) {
1043
+ throw new OkmError2("invalid", `View ${name} is not on this client.`, {
1044
+ fix: { summary: "A tenant view is read through for().views." }
1045
+ });
1046
+ }
1047
+ return handle;
1048
+ }
1049
+ function isViewQuery(value) {
1050
+ if (typeof value !== "object" || value === null)
1051
+ return false;
1052
+ const record = value;
1053
+ return typeof record.find === "function" && typeof record.one === "function";
1054
+ }
1055
+ function requireFacts(host, name) {
1056
+ const facts = host.facts.get(name);
1057
+ if (facts === undefined) {
1058
+ throw new OkmError2("invalid", `Table ${name} is not in the schema.`, {
1059
+ fix: { summary: "The path must name a table in this schema." }
1060
+ });
1061
+ }
1062
+ return facts;
1063
+ }
672
1064
  function openTenant(host, tenant) {
673
- const key = host.schema.tenancy?.key ?? "tenantId";
1065
+ const keys = tenantKeys(host);
1066
+ const input = typeof tenant === "string" ? scopeFor(keys, tenant) : tenant;
674
1067
  const db = host.db;
675
1068
  if (!("for" in db) || typeof db.for !== "function") {
676
1069
  throw new OkmError2("invalid", "This schema has no for().", {
677
1070
  fix: { summary: "Pass columnTenancy() to schema({ tenancy })." }
678
1071
  });
679
1072
  }
680
- const opened = db.for({ [key]: tenant });
1073
+ const opened = db.for(input);
681
1074
  if (typeof opened !== "object" || opened === null) {
682
1075
  throw new OkmError2("invalid", "for() did not return a client.", {
683
1076
  fix: { summary: "The tenant key must be a uuid." }
@@ -0,0 +1,20 @@
1
+ // src/adapters/pg/cursor.ts
2
+ async function* cursorStream(run, text, params) {
3
+ const name = "okm_stream";
4
+ await run("BEGIN");
5
+ try {
6
+ await run(`DECLARE ${name} NO SCROLL CURSOR FOR ${text}`, params);
7
+ for (;; ) {
8
+ const chunk = await run(`FETCH 64 FROM ${name}`);
9
+ if (chunk.rows.length === 0)
10
+ break;
11
+ yield chunk.rows;
12
+ }
13
+ } finally {
14
+ await run("ROLLBACK").catch(() => {
15
+ return;
16
+ });
17
+ }
18
+ }
19
+
20
+ export { cursorStream };
@@ -0,0 +1,8 @@
1
+ import {
2
+ build2,
3
+ identityColumn2
4
+ } from "./xjk7hnn5.js";
5
+ export {
6
+ build2 as build,
7
+ identityColumn2 as identityColumn
8
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ stack2
3
+ } from "./mss6ctgv.js";
4
+ export {
5
+ stack2 as stack
6
+ };