okmodel 0.5.0 → 0.6.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 (169) hide show
  1. package/README.md +36 -18
  2. package/dist/adapters/pg/no-inflight-timeout.d.ts +17 -0
  3. package/dist/adapters/pg/postgres-extra.d.ts +22 -0
  4. package/dist/contracts/catalog/document.d.ts +19 -5
  5. package/dist/contracts/catalog/object.d.ts +2 -1
  6. package/dist/contracts/catalog/policy.d.ts +48 -0
  7. package/dist/contracts/catalog/types.d.ts +25 -1
  8. package/dist/contracts/catalog/view.d.ts +2 -0
  9. package/dist/contracts/internal.js +1 -1
  10. package/dist/contracts/location.d.ts +10 -0
  11. package/dist/dialects/pg/column.d.ts +13 -2
  12. package/dist/dialects/pg/ddl.d.ts +37 -1
  13. package/dist/dialects/pg/emit.d.ts +19 -1
  14. package/dist/dialects/pg/errors.d.ts +32 -1
  15. package/dist/dialects/pg/ext/citext.js +1 -1
  16. package/dist/dialects/pg/ext/index.js +1 -1
  17. package/dist/dialects/pg/ext/pg-trgm.js +1 -1
  18. package/dist/dialects/pg/filter-parse.d.ts +10 -3
  19. package/dist/dialects/pg/fn/index.js +10 -2
  20. package/dist/dialects/pg/index.d.ts +2 -1
  21. package/dist/dialects/pg/index.js +29 -17
  22. package/dist/dialects/pg/introspect.d.ts +7 -0
  23. package/dist/dialects/pg/model.d.ts +7 -2
  24. package/dist/dialects/pg/ops/or.d.ts +1 -1
  25. package/dist/dialects/pg/owner.d.ts +42 -0
  26. package/dist/dialects/pg/path-endpoint.d.ts +20 -0
  27. package/dist/dialects/pg/role/check.d.ts +3 -0
  28. package/dist/dialects/pg/role/index.d.ts +71 -6
  29. package/dist/dialects/pg/role/sql.d.ts +11 -0
  30. package/dist/dialects/pg/schema.d.ts +2 -0
  31. package/dist/dialects/pg/table.d.ts +25 -3
  32. package/dist/dialects/pg/temporal.d.ts +5 -76
  33. package/dist/dialects/pg/tenancy.d.ts +45 -3
  34. package/dist/dialects/pg/view/client.d.ts +19 -1
  35. package/dist/dialects/pg/view/index.d.ts +4 -0
  36. package/dist/dialects/pg/view/index.js +21 -178
  37. package/dist/dialects/pg/view/refresh.d.ts +24 -0
  38. package/dist/runtime/batch.d.ts +1 -1
  39. package/dist/runtime/client.d.ts +6 -1
  40. package/dist/runtime/conflict.d.ts +2 -1
  41. package/dist/runtime/drift.d.ts +9 -0
  42. package/dist/runtime/inflight.d.ts +14 -0
  43. package/dist/runtime/pg/bun.js +6 -5
  44. package/dist/runtime/pg/pg.js +5 -5
  45. package/dist/runtime/pg/pglite.js +6 -5
  46. package/dist/runtime/pg/postgresjs.js +5 -5
  47. package/dist/runtime/plan.d.ts +22 -1
  48. package/dist/runtime/predicate.d.ts +19 -0
  49. package/dist/runtime/safety/index.js +1 -1
  50. package/dist/runtime/tenancy/composite.d.ts +26 -0
  51. package/dist/runtime/tenancy/context.d.ts +16 -0
  52. package/dist/runtime/tenancy/index.d.ts +9 -5
  53. package/dist/runtime/tenancy/index.js +1508 -37
  54. package/dist/runtime/tenancy/path.d.ts +87 -0
  55. package/dist/runtime/tenancy/rls.d.ts +67 -0
  56. package/dist/runtime/tenancy/via.d.ts +22 -0
  57. package/dist/runtime/traits/index.js +17 -11
  58. package/dist/runtime/types.d.ts +13 -1
  59. package/dist/runtime/validate/index.js +4 -4
  60. package/dist/runtime/write.d.ts +1 -1
  61. package/dist/shared/09p049xa.js +702 -0
  62. package/dist/shared/{ykv7wh5b.js → 1f0xa3cb.js} +5 -5
  63. package/dist/shared/1nw1f3vs.js +23 -0
  64. package/dist/shared/{6p5k20sm.js → 1z04fa85.js} +21 -11
  65. package/dist/shared/{p7pfx624.js → 2cg3wnxc.js} +314 -65
  66. package/dist/shared/{dyj86cmj.js → 2jd1sn0e.js} +2 -2
  67. package/dist/shared/{qgvt8nx1.js → 2z7wv5tj.js} +21 -5
  68. package/dist/shared/{kyxb62mc.js → 31vm914h.js} +9 -1
  69. package/dist/shared/{sf8m6tfy.js → 32cnm7kn.js} +8 -3
  70. package/dist/shared/{t4tbsy9h.js → 358zsv4r.js} +1 -1
  71. package/dist/shared/{jdgqqx31.js → 3fqwmr09.js} +38 -14
  72. package/dist/shared/{922qbgnw.js → 3qnhc7w5.js} +16 -9
  73. package/dist/shared/{zvbjc949.js → 4az561fn.js} +1 -1
  74. package/dist/shared/{k3ted12y.js → 54tqkxja.js} +20 -2
  75. package/dist/shared/{k7cc9nzk.js → 5d8t84hg.js} +15 -3
  76. package/dist/shared/{fa9vt245.js → 5j36z8dn.js} +17 -6
  77. package/dist/shared/{1g0drakv.js → 5yv4k6a6.js} +1 -1
  78. package/dist/shared/8dqhth8c.js +12 -0
  79. package/dist/shared/8etn7d8w.js +62 -0
  80. package/dist/shared/{g516zdsb.js → 8x72s86k.js} +476 -30
  81. package/dist/shared/{nkc9sfqb.js → 929h19xp.js} +145 -7
  82. package/dist/shared/{gjthds9j.js → 9628a9xt.js} +1 -1
  83. package/dist/shared/{8xsbagad.js → 9s0ypp87.js} +88 -7
  84. package/dist/shared/{b066g80h.js → ag4c7516.js} +2 -2
  85. package/dist/shared/b3awxn7h.js +45 -0
  86. package/dist/shared/b4v57v3x.js +9 -0
  87. package/dist/shared/bdk4sty6.js +58 -0
  88. package/dist/shared/{s6gqfn5j.js → c58secgj.js} +125 -8
  89. package/dist/shared/{rrstp3kb.js → cgbamx3e.js} +2 -2
  90. package/dist/shared/d5azex6s.js +6 -0
  91. package/dist/shared/{5fqx9yh4.js → dk8v79p3.js} +3 -1
  92. package/dist/shared/{4m9fn52z.js → ed0b8k4s.js} +1 -1
  93. package/dist/shared/{z2rnngj4.js → ek2wt2dz.js} +8 -5
  94. package/dist/shared/{wxn4b7pp.js → f0f1n281.js} +59 -12
  95. package/dist/shared/{gdh9mrtp.js → g90m9b0g.js} +3 -2
  96. package/dist/shared/{tbszx8mz.js → gwn6v667.js} +2 -2
  97. package/dist/shared/{c8aghh05.js → h15jx9bc.js} +8 -2
  98. package/dist/shared/{3p4b1fg0.js → hazqqecq.js} +17 -93
  99. package/dist/shared/{4ef7mmqf.js → j3rkk835.js} +34 -11
  100. package/dist/shared/{bt9sfq0z.js → j60w0dvr.js} +56 -21
  101. package/dist/shared/{ycatkxmq.js → jgqj11w4.js} +1 -1
  102. package/dist/shared/k68eedmy.js +85 -0
  103. package/dist/shared/{szm0cksa.js → knmes2kh.js} +34 -6
  104. package/dist/shared/{ckjbyrt9.js → mb2ycs1p.js} +7 -9
  105. package/dist/shared/{tq9afp8d.js → n076ydsc.js} +1 -1
  106. package/dist/shared/{xhh1srw3.js → nx9hh281.js} +50 -32
  107. package/dist/shared/{kdkxh3kb.js → p248q79y.js} +58 -59
  108. package/dist/shared/{990a291j.js → p6bjzq9q.js} +14 -9
  109. package/dist/shared/{h4nqvjkp.js → pjr6xrm5.js} +2 -2
  110. package/dist/shared/pz6rj8ay.js +212 -0
  111. package/dist/shared/{5vdm4sgp.js → q3j1tq7a.js} +36 -23
  112. package/dist/shared/{8bwbwy8h.js → qq3899ht.js} +128 -18
  113. package/dist/shared/{dxjp14rk.js → r11n8grd.js} +2 -2
  114. package/dist/shared/r1fdd0f6.js +19 -0
  115. package/dist/shared/{8ktwd3rh.js → r3j8s5y0.js} +1 -1
  116. package/dist/shared/rhs3xe4k.js +7 -0
  117. package/dist/shared/{jsd07prz.js → rpycarcr.js} +6 -6
  118. package/dist/shared/{xsq7v6wp.js → sbdme3vy.js} +14 -2
  119. package/dist/shared/{xdpj5kkm.js → t3g0k88x.js} +775 -264
  120. package/dist/shared/{ayz64vzj.js → ttmg09vd.js} +708 -19
  121. package/dist/shared/{r6pj4fq2.js → venqrjna.js} +8 -4
  122. package/dist/shared/{fancm5pt.js → vjpnzwjz.js} +138 -15
  123. package/dist/shared/{9tgmr1gh.js → vmfkhszw.js} +44 -9
  124. package/dist/shared/{wqem4jed.js → wkme6tep.js} +11 -9
  125. package/dist/shared/{qy9rdq0g.js → wt5rzv1h.js} +15 -11
  126. package/dist/shared/wxr8cwns.js +6 -0
  127. package/dist/shared/{bzsjtd7c.js → wynzeggs.js} +19 -14
  128. package/dist/shared/x3jc53kg.js +220 -0
  129. package/dist/shared/{7p2wbz7f.js → x72jb7ad.js} +1 -1
  130. package/dist/shared/{fyqkk1m0.js → xq0mvhts.js} +10 -9
  131. package/dist/shared/xqve9598.js +53 -0
  132. package/dist/shared/{47xzb9fc.js → y2t3jktd.js} +2 -2
  133. package/dist/shared/{9x25fxhc.js → y34a8714.js} +1 -1
  134. package/dist/shared/{k7dwkhvq.js → y6jdds7g.js} +4 -1
  135. package/dist/shared/{h8ne16fq.js → ys0xzyaj.js} +1 -1
  136. package/dist/shared/{c9z5g0fm.js → ywr8hmey.js} +5 -2
  137. package/dist/shared/{g7ez901n.js → ywspezzx.js} +1 -1
  138. package/dist/shared/{9m80mkac.js → ze4hxsmy.js} +1 -1
  139. package/dist/shared/{fb00y8fp.js → zm0mafk4.js} +12 -6
  140. package/dist/shared/{y005b761.js → zy8r7qvz.js} +6 -2
  141. package/dist/tooling/cli.js +97 -24
  142. package/dist/tooling/migrate/apply.d.ts +14 -0
  143. package/dist/tooling/migrate/check.d.ts +3 -4
  144. package/dist/tooling/migrate/classify.d.ts +4 -0
  145. package/dist/tooling/migrate/commands.d.ts +1 -2
  146. package/dist/tooling/migrate/config.d.ts +3 -2
  147. package/dist/tooling/migrate/drift.d.ts +1 -1
  148. package/dist/tooling/migrate/estimate.d.ts +14 -1
  149. package/dist/tooling/migrate/files.d.ts +11 -1
  150. package/dist/tooling/migrate/index.js +17 -16
  151. package/dist/tooling/migrate/lint.d.ts +4 -4
  152. package/dist/tooling/migrate/lost.d.ts +28 -0
  153. package/dist/tooling/migrate/paths.d.ts +14 -0
  154. package/dist/tooling/migrate/plan.d.ts +10 -3
  155. package/dist/tooling/migrate/policy.d.ts +11 -0
  156. package/dist/tooling/migrate/runner.d.ts +25 -0
  157. package/dist/tooling/migrate/sandbox.d.ts +57 -0
  158. package/dist/tooling/migrate/sql-lex.d.ts +39 -0
  159. package/dist/tooling/migrate/text.d.ts +42 -0
  160. package/dist/tooling/node-version.d.ts +21 -0
  161. package/dist/tooling/testing/factories.d.ts +10 -0
  162. package/dist/tooling/testing/facts.d.ts +33 -5
  163. package/dist/tooling/testing/index.d.ts +1 -1
  164. package/dist/tooling/testing/index.js +3 -3
  165. package/dist/tooling/testing/isolation.d.ts +15 -8
  166. package/package.json +10 -6
  167. package/dist/shared/0v1g424p.js +0 -26
  168. package/dist/shared/r73pt594.js +0 -426
  169. package/dist/shared/rasvvzgz.js +0 -21
@@ -1,3 +1,6 @@
1
+ import {
2
+ effectivePredicate
3
+ } from "./b3awxn7h.js";
1
4
  import {
2
5
  assertOperatorFits,
3
6
  textArray,
@@ -16,10 +19,10 @@ import {
16
19
  } from "./n4jqhpqz.js";
17
20
  import {
18
21
  writeWouldValidate
19
- } from "./9m80mkac.js";
22
+ } from "./ze4hxsmy.js";
20
23
  import {
21
24
  encodeJson
22
- } from "./gjthds9j.js";
25
+ } from "./9628a9xt.js";
23
26
  import {
24
27
  withRowFilters,
25
28
  archiveRules,
@@ -34,7 +37,7 @@ import {
34
37
  isRecord,
35
38
  registerFailFix,
36
39
  fail
37
- } from "./qgvt8nx1.js";
40
+ } from "./2z7wv5tj.js";
38
41
  import {
39
42
  safetyInstalled,
40
43
  runSafety
@@ -45,7 +48,8 @@ import {
45
48
  operatorValue
46
49
  } from "./cn0n68ft.js";
47
50
  import {
48
- OkmError2
51
+ OkmError2,
52
+ throwNamed2
49
53
  } from "./52szzakm.js";
50
54
 
51
55
  // src/runtime/write.ts
@@ -98,6 +102,8 @@ var INSERT_OPTIONS = [
98
102
  "validate"
99
103
  ];
100
104
  var FILTER_OPTIONS = ["allow", "expect", "returning", "signal", "timeout", "validate"];
105
+ var UPDATE_KEYS = ["set", "where"];
106
+ var UPDATE_ROW_KEYS = ["id", "set", "where"];
101
107
  var NO_ALLOW = new Set;
102
108
  async function executeWrite(host, op, table, input, options, mods, scope, plan) {
103
109
  const prepared = await prepareWrite(host, op, table, input, options, mods, scope);
@@ -114,7 +120,7 @@ async function prepareWrite(host, op, table, input, options, mods, scope) {
114
120
  return {
115
121
  statements: planned.statements,
116
122
  options: callOptions(options),
117
- ...planned.expect !== undefined ? { checked: "expect" } : {},
123
+ ...planned.parentGuard === true ? { checked: "parent" } : planned.expect !== undefined ? { checked: "expect" } : {},
118
124
  finish: (results) => finish(results, planned, table)
119
125
  };
120
126
  } catch (error) {
@@ -147,7 +153,7 @@ async function planWrite(schema, generators, op, tableName, input, options, mods
147
153
  if (op === "update") {
148
154
  return withRowFilters(scope, mods.archive, () => {
149
155
  noteArchive(table, mods.archive);
150
- return planUpdate(schema, table, input, record, mods);
156
+ return planUpdate(schema, table, input, record, mods, scope);
151
157
  });
152
158
  }
153
159
  return withRowFilters(scope, mods.archive, () => {
@@ -162,12 +168,16 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
162
168
  const rows = (many ? input : [input]).map((row) => insertRow(table, row, allow, tenancy));
163
169
  tenancy?.stamp(table.model.name, rows, scope);
164
170
  fillInsert(table, rows, generators);
165
- const columns = writtenColumns(table, rows, allow, tenancy?.key);
171
+ const columns = writtenColumns(table, rows, allow, tenantKeyList(tenancy));
166
172
  const returning = selectedColumns(table, options.returning);
167
173
  const outputs = outputsOf(returning);
168
174
  const clause = returningClause(returning, undefined);
169
175
  const expect = expectOf(options, mods);
170
176
  const shape = many ? "rows" : "row";
177
+ const path = tenancy?.isPath?.(table.model.name) === true;
178
+ if (path && options.onConflict !== undefined && options.onConflict !== "error") {
179
+ fail("OKM1120", `onConflict on ${table.model.name} is not available. The insert checks the parent.`);
180
+ }
171
181
  if (columns.length === 0) {
172
182
  const text = `insert into ${quote(table.model.sql)} default values${clause}`;
173
183
  return {
@@ -179,8 +189,8 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
179
189
  keyFields: undefined
180
190
  };
181
191
  }
182
- const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./9tgmr1gh.js");
183
- const conflict = conflictMod?.readConflict(table, options.onConflict, allow);
192
+ const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./vmfkhszw.js");
193
+ const conflict = conflictMod?.readConflict(table, options.onConflict, allow, tenantKeyList(tenancy));
184
194
  const chunks = chunkRows(rows.map((row) => cellsFor(columns, row)));
185
195
  const statements = [];
186
196
  const keys = [];
@@ -188,7 +198,7 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
188
198
  for (const chunk of chunks) {
189
199
  const slice = rows.slice(offset, offset + chunk.length);
190
200
  offset += chunk.length;
191
- const built = insertHead(table, columns, chunk);
201
+ const built = path ? pathInsert(table, columns, chunk, scope, tenancy) : insertHead(table, columns, chunk);
192
202
  if (conflict === undefined || conflictMod === undefined) {
193
203
  statements.push(withParams(`${built.text}${clause}`, built.params));
194
204
  continue;
@@ -204,10 +214,11 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
204
214
  shape,
205
215
  expect,
206
216
  keys: conflict?.kind === "return" ? keys : undefined,
207
- keyFields: conflict?.kind === "return" ? conflict.columns.map((column) => column.field) : undefined
217
+ keyFields: conflict?.kind === "return" ? conflict.columns.map((column) => column.field) : undefined,
218
+ ...path ? { parent: rows.length } : {}
208
219
  };
209
220
  }
210
- function planUpdate(schema, table, input, options, mods) {
221
+ function planUpdate(schema, table, input, options, mods, scope) {
211
222
  const allow = allowSet(table, options.allow);
212
223
  noteGuarded(table, allow, "update");
213
224
  const returning = returningOption(table, options.returning);
@@ -216,8 +227,22 @@ function planUpdate(schema, table, input, options, mods) {
216
227
  if (Array.isArray(input)) {
217
228
  const rows = input.map((item) => {
218
229
  const row = updateItem(table, item, allow, schema.tenancy);
230
+ requireFilter(table, row.where, mods, "update");
231
+ pathAssignment(schema.tenancy, table.model.name, row.set, scope);
219
232
  return { ...row, where: stack(row.where, mods.presets) };
220
233
  });
234
+ const moves = rows.some((row) => pathField(schema.tenancy, table.model.name, row.set) !== undefined);
235
+ if (moves) {
236
+ return {
237
+ statements: [pathListUpdate(schema, table, rows, scope, returning)],
238
+ outputs: parentOutputs(returning),
239
+ shape: returning === undefined ? "count" : "rows",
240
+ expect,
241
+ keys: undefined,
242
+ keyFields: undefined,
243
+ parentGuard: true
244
+ };
245
+ }
221
246
  return {
222
247
  statements: updateList(schema, table, rows, returning),
223
248
  outputs,
@@ -229,16 +254,30 @@ function planUpdate(schema, table, input, options, mods) {
229
254
  }
230
255
  if (!isRecord(input))
231
256
  fail("OKM1121", "update expects { where, set } or a list of rows.");
257
+ rejectUpdateKeys(input, false);
232
258
  const set = writeSet(table, input.set, allow, schema.tenancy);
233
259
  requireFilter(table, input.where, mods, "update");
234
260
  if (Object.keys(set).length === 0)
235
261
  fail("OKM1120", "update set is empty.");
262
+ const where = stack(input.where, mods.presets);
263
+ if (pathAssignment(schema.tenancy, table.model.name, set, scope) !== undefined) {
264
+ return {
265
+ statements: [pathSetUpdate(schema, table, where, set, scope, returning)],
266
+ outputs: parentOutputs(returning),
267
+ shape: returning === undefined ? "count" : "rows",
268
+ expect,
269
+ keys: undefined,
270
+ keyFields: undefined,
271
+ parentGuard: true
272
+ };
273
+ }
236
274
  const sql = new Sql;
237
275
  sql.text("update ");
238
276
  sql.text(quote(table.model.sql));
239
277
  sql.text(" as t set ");
240
278
  emitSet(sql, table, set, "t");
241
- emitFilter(schema, table, stack(input.where, mods.presets), sql, "t");
279
+ emitFilter(schema, table, where, sql, "t");
280
+ schema.tenancy?.noteParent?.(table.model.name, set, sql.sink(), scope, (field) => table.columns.get(field)?.sql);
242
281
  sql.text(returningClause(returning, "t"));
243
282
  return {
244
283
  statements: [sql.statement()],
@@ -271,6 +310,8 @@ function planDelete(schema, table, input, options, mods) {
271
310
  };
272
311
  }
273
312
  function finish(results, planned, table) {
313
+ if (planned.parentGuard === true)
314
+ return finishParent(results, planned, table);
274
315
  if (planned.shape === "count") {
275
316
  let count = 0;
276
317
  for (const result of results)
@@ -285,63 +326,164 @@ function finish(results, planned, table) {
285
326
  }
286
327
  const rows = planned.keys === undefined || planned.keyFields === undefined ? decoded : orderByKeys(decoded, planned.keyFields, planned.keys);
287
328
  checkExpect(rows.length, planned.expect, table, "insert");
329
+ if (planned.parent !== undefined && rows.length < planned.parent) {
330
+ throw new OkmError2("OKM1705", `Insert on ${table} did not find a parent in this tenant.`, {
331
+ fix: { summary: "Insert under a parent that belongs to this tenant." }
332
+ });
333
+ }
288
334
  if (planned.shape === "row")
289
335
  return rows[0] ?? null;
290
336
  return rows;
291
337
  }
292
- function orderByKeys(rows, fields, keys) {
293
- const found = new Map;
294
- for (const row of rows)
295
- found.set(fields.map((field) => wireKey(row[field])).join("\x00"), row);
296
- const ordered = [];
297
- for (const key of keys) {
298
- const row = found.get(key.map((value) => value ?? "").join("\x00"));
299
- if (row !== undefined)
300
- ordered.push(row);
338
+ function finishParent(results, planned, table) {
339
+ const row = results[0]?.rows[0];
340
+ const matched = countCell(row?.[0]);
341
+ const updated = countCell(row?.[1]);
342
+ if (updated < matched) {
343
+ throw new OkmError2("OKM1705", `Update on ${table} did not find a parent in this tenant.`, {
344
+ fix: { summary: "Point the foreign key at a parent in this tenant." }
345
+ });
301
346
  }
302
- return ordered;
347
+ if (planned.shape === "count") {
348
+ checkExpect(updated, planned.expect, table, "write");
349
+ return { count: updated };
350
+ }
351
+ const decoded = [];
352
+ if (updated > 0) {
353
+ for (const result of results) {
354
+ for (const item of result.rows)
355
+ decoded.push(decodeRow(planned.outputs, item));
356
+ }
357
+ }
358
+ checkExpect(decoded.length, planned.expect, table, "write");
359
+ if (planned.shape === "row")
360
+ return decoded[0] ?? null;
361
+ return decoded;
303
362
  }
304
- function wireKey(value) {
305
- if (typeof value === "string")
363
+ function countCell(value) {
364
+ if (typeof value === "number")
306
365
  return value;
307
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
308
- return String(value);
309
- }
310
- return "";
366
+ if (typeof value === "bigint")
367
+ return Number(value);
368
+ if (typeof value === "string" && value.length > 0)
369
+ return Number(value);
370
+ return 0;
311
371
  }
312
- function insertHead(table, columns, rows) {
313
- const params = [];
314
- const lists = [];
315
- for (const row of rows) {
316
- const slots = [];
317
- for (const cell of row) {
318
- if (cell.kind === "default") {
319
- slots.push("default");
320
- continue;
321
- }
322
- params.push(cell.kind === "null" ? null : cell.wire);
323
- slots.push(`$${String(params.length)}::${cell.dataType}`);
324
- }
325
- lists.push(`(${slots.join(", ")})`);
372
+ function pathField(tenancy, table, set) {
373
+ if (tenancy?.isPath?.(table) !== true)
374
+ return;
375
+ const field = tenancy.pathOf?.(table)?.[0]?.localField;
376
+ if (field === undefined || !Object.hasOwn(set, field))
377
+ return;
378
+ return field;
379
+ }
380
+ function pathAssignment(tenancy, table, set, scope) {
381
+ const field = pathField(tenancy, table, set);
382
+ if (field === undefined)
383
+ return;
384
+ const value = set[field];
385
+ if (typeof value !== "string" || value.length === 0) {
386
+ throw new OkmError2("OKM1705", `Update on ${table} cannot verify ${field}. Pass the parent id as a string.`, { fix: { summary: "Pass the parent id as a string so the tenant can be checked." } });
326
387
  }
327
- const names = columns.map((column) => quote(column.sql)).join(", ");
388
+ if (scope === undefined || "unscoped" in scope) {
389
+ throw new OkmError2("OKM1705", `Update on ${table} cannot verify ${field} without a tenant.`, {
390
+ fix: { summary: `Call for({ ... }) so ${field} can be checked.` }
391
+ });
392
+ }
393
+ return field;
394
+ }
395
+ function parentOutputs(returning) {
396
+ const outputs = outputsOf(returning);
397
+ if (returning === undefined)
398
+ return outputs;
328
399
  return {
329
- text: `insert into ${quote(table.model.sql)} (${names}) values ${lists.join(", ")}`,
330
- params
400
+ fields: outputs.fields.map((field) => ({ ...field, at: field.at + 2 })),
401
+ includes: outputs.includes
331
402
  };
332
403
  }
333
- function updateList(schema, table, rows, returning) {
334
- if (rows.length === 0)
335
- return [];
336
- const width = Math.max(1, Object.keys(rows[0]?.set ?? {}).length + 2);
337
- const size = Math.max(1, Math.floor(WRITE_PARAM_BUDGET / (width * 2)));
338
- const statements = [];
339
- for (let index = 0;index < rows.length; index += size) {
340
- statements.push(updateSlice(schema, table, rows.slice(index, index + size), returning));
404
+ function pathSetUpdate(schema, table, where, set, scope, returning) {
405
+ const sql = new Sql;
406
+ sql.text("with matched as (select t.ctid as tid from ");
407
+ sql.text(quote(table.model.sql));
408
+ sql.text(" as t");
409
+ emitFilter(schema, table, where, sql, "t");
410
+ sql.text("), updated as (update ");
411
+ sql.text(quote(table.model.sql));
412
+ sql.text(" as t set ");
413
+ emitSet(sql, table, set, "t");
414
+ sql.text(" where t.ctid in (select tid from matched)");
415
+ schema.tenancy?.noteParent?.(table.model.name, set, sql.sink(), scope, (field) => table.columns.get(field)?.sql);
416
+ endParent(sql, returning);
417
+ return sql.statement();
418
+ }
419
+ function pathListUpdate(schema, table, rows, scope, returning) {
420
+ const sql = new Sql;
421
+ sql.text("with matched as (");
422
+ for (let index = 0;index < rows.length; index += 1) {
423
+ const row = rows[index];
424
+ if (row === undefined)
425
+ continue;
426
+ if (index > 0)
427
+ sql.text(" union all ");
428
+ sql.text("select t.ctid as tid, ");
429
+ sql.text(String(index));
430
+ sql.text(" as ord from ");
431
+ sql.text(quote(table.model.sql));
432
+ sql.text(" as t where ");
433
+ emitBare(schema, table, row.where, sql, "t");
341
434
  }
342
- return statements;
435
+ sql.text("), rejected as (");
436
+ let rejected = false;
437
+ for (let index = 0;index < rows.length; index += 1) {
438
+ const row = rows[index];
439
+ if (row === undefined)
440
+ continue;
441
+ if (pathField(schema.tenancy, table.model.name, row.set) === undefined)
442
+ continue;
443
+ if (rejected)
444
+ sql.text(" union all ");
445
+ rejected = true;
446
+ sql.text("select ");
447
+ sql.text(String(index));
448
+ sql.text(" as ord where exists (select 1 from matched where ord = ");
449
+ sql.text(String(index));
450
+ sql.text(")");
451
+ noteParentNot(schema, table, row.set, scope, sql);
452
+ }
453
+ if (!rejected)
454
+ sql.text("select 0 as ord where false");
455
+ sql.text("), updated as (");
456
+ updateSliceBody(schema, table, rows, sql, true);
457
+ sql.text(" and not exists (select 1 from rejected)");
458
+ endParent(sql, returning);
459
+ return sql.statement();
343
460
  }
344
- function updateSlice(schema, table, rows, returning) {
461
+ function noteParentNot(schema, table, set, scope, sql) {
462
+ let first = true;
463
+ schema.tenancy?.noteParent?.(table.model.name, set, {
464
+ text(value) {
465
+ if (first) {
466
+ first = false;
467
+ sql.text(" and not ");
468
+ return;
469
+ }
470
+ sql.text(value);
471
+ },
472
+ param(encoded) {
473
+ sql.param(encoded, "");
474
+ },
475
+ mark() {}
476
+ }, scope, (field) => table.columns.get(field)?.sql);
477
+ }
478
+ function endParent(sql, returning) {
479
+ if (returning === undefined || returning.length === 0) {
480
+ sql.text(" returning 1) select (select count(*)::int from matched), (select count(*)::int from updated)");
481
+ return;
482
+ }
483
+ sql.text(returningClause(returning, "t"));
484
+ sql.text(") select c.matched, c.updated, u.* from (select (select count(*)::int from matched) as matched, (select count(*)::int from updated) as updated) c left join updated u on true");
485
+ }
486
+ function updateSliceBody(schema, table, rows, sql, wrapWhere = false) {
345
487
  const fields = [];
346
488
  const seen = new Set;
347
489
  for (const row of rows) {
@@ -356,7 +498,6 @@ function updateSlice(schema, table, rows, returning) {
356
498
  }
357
499
  if (fields.length === 0)
358
500
  fail("OKM1120", "update set is empty.");
359
- const sql = new Sql;
360
501
  sql.text("update ");
361
502
  sql.text(quote(table.model.sql));
362
503
  sql.text(" as t set ");
@@ -383,7 +524,7 @@ function updateSlice(schema, table, rows, returning) {
383
524
  sql.text(" end");
384
525
  }
385
526
  emitTouch(sql, table, fields.length > 0);
386
- sql.text(" where ");
527
+ sql.text(wrapWhere ? " where (" : " where ");
387
528
  for (let index = 0;index < rows.length; index += 1) {
388
529
  const row = rows[index];
389
530
  if (row === undefined)
@@ -394,6 +535,64 @@ function updateSlice(schema, table, rows, returning) {
394
535
  emitBare(schema, table, row.where, sql, "t");
395
536
  sql.text(")");
396
537
  }
538
+ if (wrapWhere)
539
+ sql.text(")");
540
+ }
541
+ function orderByKeys(rows, fields, keys) {
542
+ const found = new Map;
543
+ for (const row of rows)
544
+ found.set(fields.map((field) => wireKey(row[field])).join("\x00"), row);
545
+ const ordered = [];
546
+ for (const key of keys) {
547
+ const row = found.get(key.map((value) => value ?? "").join("\x00"));
548
+ if (row !== undefined)
549
+ ordered.push(row);
550
+ }
551
+ return ordered;
552
+ }
553
+ function wireKey(value) {
554
+ if (typeof value === "string")
555
+ return value;
556
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
557
+ return String(value);
558
+ }
559
+ return "";
560
+ }
561
+ function insertHead(table, columns, rows) {
562
+ const params = [];
563
+ const lists = [];
564
+ for (const row of rows) {
565
+ const slots = [];
566
+ for (const cell of row) {
567
+ if (cell.kind === "default") {
568
+ slots.push("default");
569
+ continue;
570
+ }
571
+ params.push(cell.kind === "null" ? null : cell.wire);
572
+ slots.push(`$${String(params.length)}::${cell.dataType}`);
573
+ }
574
+ lists.push(`(${slots.join(", ")})`);
575
+ }
576
+ const names = columns.map((column) => quote(column.sql)).join(", ");
577
+ return {
578
+ text: `insert into ${quote(table.model.sql)} (${names}) values ${lists.join(", ")}`,
579
+ params
580
+ };
581
+ }
582
+ function updateList(schema, table, rows, returning) {
583
+ if (rows.length === 0)
584
+ return [];
585
+ const width = Math.max(1, Object.keys(rows[0]?.set ?? {}).length + 2);
586
+ const size = Math.max(1, Math.floor(WRITE_PARAM_BUDGET / (width * 2)));
587
+ const statements = [];
588
+ for (let index = 0;index < rows.length; index += size) {
589
+ statements.push(updateSlice(schema, table, rows.slice(index, index + size), returning));
590
+ }
591
+ return statements;
592
+ }
593
+ function updateSlice(schema, table, rows, returning) {
594
+ const sql = new Sql;
595
+ updateSliceBody(schema, table, rows, sql);
397
596
  sql.text(returningClause(returning, "t"));
398
597
  return sql.statement();
399
598
  }
@@ -603,7 +802,7 @@ function nextFill(fill, generators) {
603
802
  return generators.okid();
604
803
  return fill.call();
605
804
  }
606
- function writtenColumns(table, rows, allow, tenantKey) {
805
+ function writtenColumns(table, rows, allow, tenantKeys) {
607
806
  const present = new Set;
608
807
  for (const row of rows) {
609
808
  for (const key of Object.keys(row))
@@ -611,12 +810,55 @@ function writtenColumns(table, rows, allow, tenantKey) {
611
810
  }
612
811
  const columns = [];
613
812
  for (const column of table.model.columns) {
614
- if (present.has(column.field) && (writable(table, column, allow) || column.fill !== undefined || column.field === tenantKey)) {
813
+ if (present.has(column.field) && (writable(table, column, allow) || column.fill !== undefined || tenantKeys?.includes(column.field) === true)) {
615
814
  columns.push(column);
616
815
  }
617
816
  }
618
817
  return columns;
619
818
  }
819
+ function tenantKeyList(tenancy) {
820
+ if (tenancy === undefined)
821
+ return;
822
+ if (tenancy.keys !== undefined && tenancy.keys.length > 0)
823
+ return tenancy.keys;
824
+ return [tenancy.key];
825
+ }
826
+ function pathInsert(table, columns, rows, scope, tenancy) {
827
+ const sql = new Sql;
828
+ const names = columns.map((column) => quote(column.sql)).join(", ");
829
+ const selected = columns.map((column) => `v.${quote(column.sql)}`).join(", ");
830
+ sql.text(`insert into ${quote(table.model.sql)} (${names}) select ${selected} from (values `);
831
+ for (let row = 0;row < rows.length; row += 1) {
832
+ const cells = rows[row];
833
+ if (cells === undefined)
834
+ continue;
835
+ if (row > 0)
836
+ sql.text(", ");
837
+ sql.text("(");
838
+ for (let index = 0;index < cells.length; index += 1) {
839
+ const cell = cells[index];
840
+ if (index > 0)
841
+ sql.text(", ");
842
+ if (cell === undefined || cell.kind === "default") {
843
+ sql.text("default");
844
+ continue;
845
+ }
846
+ sql.param(cell.kind === "null" ? null : cell.wire, cell.dataType);
847
+ }
848
+ sql.text(")");
849
+ }
850
+ sql.text(`) as v(${names})`);
851
+ tenancy?.lockPredicate?.({
852
+ table: table.model.name,
853
+ fieldSql: (field) => table.columns.get(field)?.sql,
854
+ encode: table.columns.get(tenancy.key)?.encode,
855
+ alias: "v",
856
+ appended: false,
857
+ scope,
858
+ sink: sql.sink()
859
+ });
860
+ return sql.statement();
861
+ }
620
862
  function writeSet(table, value, allow, tenancy) {
621
863
  if (!isRecord(value))
622
864
  fail("OKM1121", "set must be an object of fields.");
@@ -637,6 +879,7 @@ function writeSet(table, value, allow, tenancy) {
637
879
  function updateItem(table, value, allow, tenancy) {
638
880
  if (!isRecord(value))
639
881
  fail("OKM1121", "update expects { where, set } or a list of rows.");
882
+ rejectUpdateKeys(value, true);
640
883
  const set = writeSet(table, value.set, allow, tenancy);
641
884
  if (value.where !== undefined)
642
885
  return { where: value.where, set };
@@ -644,6 +887,14 @@ function updateItem(table, value, allow, tenancy) {
644
887
  return { where: { id: value.id }, set };
645
888
  fail("OKM1102", `update on ${table.model.name} needs id or where for each row.`);
646
889
  }
890
+ function rejectUpdateKeys(input, row) {
891
+ const accepted = row ? UPDATE_ROW_KEYS : UPDATE_KEYS;
892
+ for (const key of Object.keys(input)) {
893
+ if (accepted.includes(key))
894
+ continue;
895
+ throwNamed2("OKM1120", key, [...accepted, ...FILTER_OPTIONS], `Option ${key} is not accepted by update. Accepted options: ${accepted.join(", ")}. timeout and signal go in the second argument.`);
896
+ }
897
+ }
647
898
  function requireFilter(table, where, mods, op) {
648
899
  if (mods.all !== undefined) {
649
900
  if (mods.all.trim().length === 0) {
@@ -651,11 +902,9 @@ function requireFilter(table, where, mods, op) {
651
902
  }
652
903
  return;
653
904
  }
654
- if (isOperator(where))
655
- return;
656
- if (isRecord(where) && Object.keys(where).length > 0)
657
- return;
658
- fail("OKM1102", `${op} on ${table.model.name} needs a where. Pass a filter, or .all(reason).`);
905
+ if (!effectivePredicate(where)) {
906
+ fail("OKM1102", `${op} on ${table.model.name} needs a where. Pass a filter, or .all(reason).`);
907
+ }
659
908
  }
660
909
  function chunkRows(rows) {
661
910
  const chunks = [];
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  encodeText,
3
3
  decodeText
4
- } from "./4m9fn52z.js";
4
+ } from "./ed0b8k4s.js";
5
5
  import {
6
6
  required,
7
7
  openColumn
8
- } from "./c9z5g0fm.js";
8
+ } from "./ywr8hmey.js";
9
9
  import {
10
10
  definition,
11
11
  rejected
@@ -416,6 +416,7 @@ function emitPredicate(schema, table, key, value, sink, alias, depth) {
416
416
  if (column === undefined) {
417
417
  throwNamed2("OKM1120", key, table.names, `Field ${key} is not on ${table.model.name}. Accepted names: ${list(table.names)}.`);
418
418
  }
419
+ refuseHidden(column);
419
420
  sink.mark(`w:${key}`);
420
421
  emitOperand(schema, table, column, value, sink, alias, depth);
421
422
  }
@@ -533,7 +534,9 @@ function emitIn(column, ref, name, value, sink) {
533
534
  sink.text(")");
534
535
  }
535
536
  function emitOr(schema, table, value, sink, alias, depth) {
536
- if (!Array.isArray(value) || value.length === 0) {
537
+ if (!Array.isArray(value))
538
+ orFail(false);
539
+ if (value.length === 0) {
537
540
  sink.text("false");
538
541
  sink.mark(":0");
539
542
  return;
@@ -559,7 +562,7 @@ function emitOr(schema, table, value, sink, alias, depth) {
559
562
  emitPredicate(schema, table, key, item, sink, alias, depth);
560
563
  }
561
564
  if (!wrote)
562
- sink.text("true");
565
+ orFail(true);
563
566
  sink.text(")");
564
567
  }
565
568
  sink.text(")");
@@ -612,6 +615,7 @@ function emitOrder(table, orderBy, sink, alias) {
612
615
  if (column === undefined) {
613
616
  throwNamed2("OKM1120", key, table.names, `Field ${key} is not on ${table.model.name}. Accepted names: ${list(table.names)}.`);
614
617
  }
618
+ refuseHidden(column);
615
619
  const parsed = parseOrder(key, orderBy[key]);
616
620
  if (index > 0)
617
621
  sink.text(", ");
@@ -640,6 +644,11 @@ function emitPrimaryOrder(table, sink, alias) {
640
644
  sink.text(" asc");
641
645
  }
642
646
  }
647
+ function refuseHidden(column) {
648
+ if (column.hidden !== true)
649
+ return;
650
+ fail("OKM1120", `Field ${column.field} is hidden.`, "Call hidden({ filterable: true }).");
651
+ }
643
652
  function emitLimit(call, sink) {
644
653
  if (call.op === "one") {
645
654
  const ordered = call.orderBy !== undefined && isRecord(call.orderBy) && Object.keys(call.orderBy).length > 0;
@@ -844,6 +853,13 @@ function list(names) {
844
853
  function isRecord(value) {
845
854
  return typeof value === "object" && value !== null && !Array.isArray(value) && !isOperator(value);
846
855
  }
856
+ function orFail(empty) {
857
+ throw new OkmError2("OKM1121", empty ? "or() branch is empty." : "Pass an array: or([a, b]).", {
858
+ fix: {
859
+ summary: empty ? "Give every or() branch a predicate. An empty branch matches every row." : "Pass an array: or([a, b])."
860
+ }
861
+ });
862
+ }
847
863
  var FAIL_FIX = {
848
864
  OKM1121: "Use eq for an object value, or has, none, or every for a relation.",
849
865
  OKM1101: "Pass limit, or call .all(reason)."
@@ -851,8 +867,8 @@ var FAIL_FIX = {
851
867
  function registerFailFix(code, summary) {
852
868
  FAIL_FIX[code] = summary;
853
869
  }
854
- function fail(code, message) {
855
- const summary = FAIL_FIX[code];
870
+ function fail(code, message, fix) {
871
+ const summary = fix ?? FAIL_FIX[code];
856
872
  throw new OkmError2(code, message, summary === undefined ? undefined : { fix: { summary } });
857
873
  }
858
874
  function plain(value) {
@@ -875,4 +891,4 @@ function hasMany(include) {
875
891
  return Object.keys(include).length > 0;
876
892
  }
877
893
 
878
- export { withRowFilters, installArchiveInspect, archiveRules, readOptionNames, installOperatorSql, readNeedsOperatorSql, emitRelationFrom, bindCall, compileCall, decodeResult, logicalIntent, appliedRules, emitJoin, emitWhere, emitOrder, emitPrimaryOrder, selectedColumns, parseOrder, rejectKeys, readLimit, takeObject, projectExpr, jsonExpr, decodeRow, cell, indexes, quote, list, isRecord, registerFailFix, fail };
894
+ export { withRowFilters, installArchiveInspect, archiveRules, readOptionNames, installOperatorSql, readNeedsOperatorSql, emitRelationFrom, bindCall, compileCall, decodeResult, logicalIntent, appliedRules, emitJoin, emitWhere, emitOrder, emitPrimaryOrder, refuseHidden, selectedColumns, parseOrder, rejectKeys, readLimit, takeObject, projectExpr, jsonExpr, decodeRow, cell, indexes, quote, list, isRecord, orFail, registerFailFix, fail };
@@ -75,7 +75,7 @@ function mapDriverError2(error, batchIndex) {
75
75
  if (error instanceof DriverError2) {
76
76
  return batchIndex === undefined ? error : error.at(batchIndex);
77
77
  }
78
- const message = error instanceof Error ? error.message : "driver error";
78
+ const message = messageOf(error);
79
79
  return new DriverError2(message, driverFields({
80
80
  sqlstate: sqlstateOf(error),
81
81
  constraint: errorField2(error, "constraint_name") ?? errorField2(error, "constraint"),
@@ -86,6 +86,14 @@ function mapDriverError2(error, batchIndex) {
86
86
  batchIndex
87
87
  }));
88
88
  }
89
+ function messageOf(error) {
90
+ if (!(error instanceof Error))
91
+ return "driver error";
92
+ if (error.message.length > 0)
93
+ return error.message;
94
+ const first = error.errors?.[0];
95
+ return first instanceof Error && first.message.length > 0 ? first.message : "driver error";
96
+ }
89
97
  function driverFields(input) {
90
98
  const fields = {};
91
99
  if (input.sqlstate !== undefined)