okmodel 0.1.0 → 0.2.1

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 (181) hide show
  1. package/README.md +237 -19
  2. package/dist/adapters/capabilities.d.ts +34 -0
  3. package/dist/adapters/pg/bunsql-extra.d.ts +17 -0
  4. package/dist/adapters/pg/bunsql.d.ts +60 -0
  5. package/dist/adapters/pg/call.d.ts +2 -12
  6. package/dist/adapters/pg/nodepostgres-extra.d.ts +18 -0
  7. package/dist/adapters/pg/nodepostgres.d.ts +57 -0
  8. package/dist/adapters/pg/postgres-extra.d.ts +50 -1
  9. package/dist/adapters/pg/postgresjs.d.ts +0 -4
  10. package/dist/adapters/pg/session.d.ts +94 -0
  11. package/dist/adapters/pg/watch.d.ts +18 -0
  12. package/dist/contracts/catalog/object.d.ts +1 -0
  13. package/dist/contracts/catalog/types.d.ts +15 -8
  14. package/dist/contracts/connection.d.ts +13 -0
  15. package/dist/contracts/driver.d.ts +18 -5
  16. package/dist/contracts/error.d.ts +17 -1
  17. package/dist/contracts/generator.d.ts +63 -0
  18. package/dist/contracts/index.d.ts +2 -2
  19. package/dist/contracts/index.js +1 -1
  20. package/dist/contracts/internal.js +11 -9
  21. package/dist/contracts/location.d.ts +24 -0
  22. package/dist/contracts/rows.d.ts +15 -0
  23. package/dist/dialects/pg/archive-bind.d.ts +22 -0
  24. package/dist/dialects/pg/column.d.ts +126 -9
  25. package/dist/dialects/pg/compile.d.ts +1 -0
  26. package/dist/dialects/pg/custom.d.ts +6 -0
  27. package/dist/dialects/pg/filter-parse.d.ts +19 -0
  28. package/dist/dialects/pg/filters.d.ts +19 -0
  29. package/dist/dialects/pg/index.d.ts +11 -2
  30. package/dist/dialects/pg/index.js +159 -1063
  31. package/dist/dialects/pg/keys.d.ts +19 -3
  32. package/dist/dialects/pg/model.d.ts +223 -2
  33. package/dist/dialects/pg/operator-fit.d.ts +86 -0
  34. package/dist/dialects/pg/operators.d.ts +39 -1
  35. package/dist/dialects/pg/ops/arr-append.d.ts +9 -0
  36. package/dist/dialects/pg/ops/arr-ns.d.ts +7 -0
  37. package/dist/dialects/pg/ops/arr-remove.d.ts +9 -0
  38. package/dist/dialects/pg/ops/containedBy.d.ts +9 -0
  39. package/dist/dialects/pg/ops/contains.d.ts +8 -4
  40. package/dist/dialects/pg/ops/hasAnyKey.d.ts +8 -0
  41. package/dist/dialects/pg/ops/hasKey.d.ts +8 -0
  42. package/dist/dialects/pg/ops/json-ns.d.ts +6 -0
  43. package/dist/dialects/pg/ops/json-set.d.ts +12 -0
  44. package/dist/dialects/pg/ops/matches.d.ts +16 -0
  45. package/dist/dialects/pg/ops/overlaps.d.ts +9 -0
  46. package/dist/dialects/pg/ops/path.d.ts +13 -0
  47. package/dist/dialects/pg/preset.d.ts +76 -0
  48. package/dist/dialects/pg/relations.d.ts +59 -2
  49. package/dist/dialects/pg/schema.d.ts +109 -5
  50. package/dist/dialects/pg/table.d.ts +130 -11
  51. package/dist/dialects/pg/temporal.d.ts +9 -7
  52. package/dist/dialects/pg/tenancy.d.ts +94 -0
  53. package/dist/dialects/pg/through.d.ts +21 -0
  54. package/dist/dialects/pg/trait.d.ts +141 -0
  55. package/dist/dialects/pg/where.d.ts +37 -0
  56. package/dist/runtime/aggregate.d.ts +25 -0
  57. package/dist/runtime/archive-handle.d.ts +18 -0
  58. package/dist/runtime/archive-rules.d.ts +12 -0
  59. package/dist/runtime/archive.d.ts +73 -0
  60. package/dist/runtime/batch.d.ts +20 -0
  61. package/dist/runtime/client.d.ts +114 -5
  62. package/dist/runtime/conflict.d.ts +2 -1
  63. package/dist/runtime/exposure.d.ts +27 -0
  64. package/dist/runtime/hookm.d.ts +39 -0
  65. package/dist/runtime/ids/index.d.ts +11 -0
  66. package/dist/runtime/ids/index.js +193 -0
  67. package/dist/runtime/ids/okid-extended.d.ts +19 -0
  68. package/dist/runtime/ids/okid-generator.d.ts +21 -0
  69. package/dist/runtime/ids/okid-shared.d.ts +89 -0
  70. package/dist/runtime/ids/okid.d.ts +24 -0
  71. package/dist/runtime/ids/uuid.d.ts +21 -0
  72. package/dist/runtime/lock.d.ts +22 -0
  73. package/dist/runtime/operator-sql.d.ts +8 -0
  74. package/dist/runtime/page.d.ts +25 -0
  75. package/dist/runtime/pg/bun.d.ts +30 -0
  76. package/dist/runtime/pg/bun.js +52 -0
  77. package/dist/runtime/pg/pg.d.ts +29 -0
  78. package/dist/runtime/pg/pg.js +52 -0
  79. package/dist/runtime/pg/pglite.js +16 -17
  80. package/dist/runtime/pg/postgresjs.js +14 -24
  81. package/dist/runtime/plan.d.ts +146 -7
  82. package/dist/runtime/preset-stack.d.ts +17 -0
  83. package/dist/runtime/presets.d.ts +48 -0
  84. package/dist/runtime/read-stream.d.ts +26 -0
  85. package/dist/runtime/safety/index.d.ts +159 -0
  86. package/dist/runtime/safety/index.js +294 -0
  87. package/dist/runtime/safety-hook.d.ts +42 -0
  88. package/dist/runtime/server-check.d.ts +34 -0
  89. package/dist/runtime/tenancy/index.d.ts +37 -0
  90. package/dist/runtime/tenancy/index.js +365 -0
  91. package/dist/runtime/trait-read.d.ts +30 -0
  92. package/dist/runtime/traits/index.d.ts +128 -0
  93. package/dist/runtime/traits/index.js +509 -0
  94. package/dist/runtime/transaction.d.ts +19 -0
  95. package/dist/runtime/tx.d.ts +22 -7
  96. package/dist/runtime/types.d.ts +390 -25
  97. package/dist/runtime/validate/bind.d.ts +11 -0
  98. package/dist/runtime/validate/closed.d.ts +16 -0
  99. package/dist/runtime/validate/engine.d.ts +41 -0
  100. package/dist/runtime/validate/hook.d.ts +17 -0
  101. package/dist/runtime/validate/index.d.ts +21 -0
  102. package/dist/runtime/validate/index.js +175 -0
  103. package/dist/runtime/validate/load.d.ts +13 -0
  104. package/dist/runtime/validate/places.d.ts +41 -0
  105. package/dist/runtime/validate/rules.d.ts +108 -0
  106. package/dist/runtime/validate/surface.d.ts +102 -0
  107. package/dist/runtime/write.d.ts +30 -5
  108. package/dist/shared/{fgqgsb5z.js → 0qkxbm1f.js} +2 -2
  109. package/dist/shared/0v1g424p.js +26 -0
  110. package/dist/shared/10tjenmd.js +210 -0
  111. package/dist/shared/{tb4ze1tr.js → 1dp5knt7.js} +95 -10
  112. package/dist/shared/38crxbgn.js +229 -0
  113. package/dist/shared/3a5ne750.js +158 -0
  114. package/dist/shared/4b9da9d7.js +138 -0
  115. package/dist/shared/4e4c6vq2.js +62 -0
  116. package/dist/shared/{ckb1p0a4.js → 4jzzghr6.js} +416 -83
  117. package/dist/shared/{ycwz42az.js → 52szzakm.js} +6 -26
  118. package/dist/shared/{kynx7vbm.js → 5z4q2zda.js} +604 -21
  119. package/dist/shared/672t5vym.js +12 -0
  120. package/dist/shared/7m88x3e9.js +45 -0
  121. package/dist/shared/7mht49yd.js +1091 -0
  122. package/dist/shared/{mtkwv8wx.js → 7p2wbz7f.js} +1 -1
  123. package/dist/shared/83eaefwr.js +10 -0
  124. package/dist/shared/96ps4g1z.js +32 -0
  125. package/dist/shared/97g2jnrw.js +76 -0
  126. package/dist/shared/{xy79j5hq.js → 9k6vetvf.js} +25 -36
  127. package/dist/shared/{w8samvk1.js → a1gdp7j9.js} +1 -1
  128. package/dist/shared/a5sxz52y.js +13 -0
  129. package/dist/shared/{dynq16vb.js → b066g80h.js} +8 -6
  130. package/dist/shared/cstgkzdx.js +154 -0
  131. package/dist/shared/{ax16zpvm.js → d04j53v8.js} +6 -5
  132. package/dist/shared/{y9m15dfv.js → d2whjz4k.js} +15 -9
  133. package/dist/shared/dd4f8mnx.js +307 -0
  134. package/dist/shared/e6wg00wd.js +164 -0
  135. package/dist/shared/e7a268fy.js +195 -0
  136. package/dist/shared/{vkm3q4x1.js → eaxwrgby.js} +4 -3
  137. package/dist/shared/{7ck0dtry.js → ecdzej21.js} +160 -81
  138. package/dist/shared/{px90pf9p.js → edwqqryd.js} +21 -16
  139. package/dist/shared/{g2v6gnc8.js → eqmtn1bj.js} +57 -5
  140. package/dist/shared/g1b5zzj9.js +75 -0
  141. package/dist/shared/g9f0zqce.js +376 -0
  142. package/dist/shared/ge03bfca.js +64 -0
  143. package/dist/shared/{79ggp945.js → jns2wx4t.js} +55 -12
  144. package/dist/shared/k3ted12y.js +33 -0
  145. package/dist/shared/k7dwkhvq.js +31 -0
  146. package/dist/shared/{hd293j9g.js → kyxb62mc.js} +14 -2
  147. package/dist/shared/m9tv7xn7.js +404 -0
  148. package/dist/shared/mrq77sgw.js +55 -0
  149. package/dist/shared/mxmcdsh5.js +450 -0
  150. package/dist/shared/n442h54c.js +14 -0
  151. package/dist/shared/n47rd821.js +442 -0
  152. package/dist/shared/n4jqhpqz.js +27 -0
  153. package/dist/shared/{mkjmqjv4.js → nfzc5z0w.js} +2 -104
  154. package/dist/shared/nsva2a6b.js +42 -0
  155. package/dist/shared/ps43134h.js +69 -0
  156. package/dist/shared/{8b04d28c.js → py3nh7jy.js} +2 -2
  157. package/dist/shared/qa5dsc7z.js +86 -0
  158. package/dist/shared/{g5fhqmwe.js → qxa0yb9j.js} +6 -4
  159. package/dist/shared/r6nwkxnt.js +86 -0
  160. package/dist/shared/{4j72449d.js → rtnaqre0.js} +3 -3
  161. package/dist/shared/rwmrejjz.js +26 -0
  162. package/dist/shared/{zhc5vmgf.js → s6gqfn5j.js} +2 -2
  163. package/dist/shared/sacqha1y.js +70 -0
  164. package/dist/shared/{qk3hxfja.js → sfh4pk75.js} +5 -5
  165. package/dist/shared/{j5cn2963.js → t4tbsy9h.js} +13 -64
  166. package/dist/shared/{ngk8gj61.js → tbszx8mz.js} +15 -1
  167. package/dist/shared/v18jva5b.js +90 -0
  168. package/dist/shared/x9v1qgeb.js +201 -0
  169. package/dist/shared/xatfg0gn.js +193 -0
  170. package/dist/shared/xkz8g6by.js +225 -0
  171. package/dist/shared/{f5thc1s0.js → xrfnq5k8.js} +237 -418
  172. package/dist/shared/ytg3xf9p.js +149 -0
  173. package/dist/shared/yw5c610b.js +48 -0
  174. package/dist/shared/zd93g3rq.js +31 -0
  175. package/dist/shared/zt8y0wzb.js +132 -0
  176. package/dist/shared/{p0cbr3g2.js → zwmqxb0g.js} +1 -1
  177. package/dist/tooling/cli.js +14 -10
  178. package/dist/tooling/migrate/commands.d.ts +10 -0
  179. package/dist/tooling/migrate/engine.d.ts +36 -0
  180. package/package.json +56 -4
  181. package/dist/shared/gk7cs7sp.js +0 -566
@@ -1,67 +1,168 @@
1
1
  import {
2
+ assertOperatorFits,
3
+ textArray,
4
+ arrayElementType
5
+ } from "./zt8y0wzb.js";
6
+ import {
7
+ runWrite
8
+ } from "./n4jqhpqz.js";
9
+ import {
10
+ stack
11
+ } from "./672t5vym.js";
12
+ import {
13
+ touchFields,
14
+ fieldSealed,
15
+ sealingTrait
16
+ } from "./7m88x3e9.js";
17
+ import {
18
+ writeWouldValidate
19
+ } from "./3a5ne750.js";
20
+ import {
21
+ encodeJson
22
+ } from "./yw5c610b.js";
23
+ import {
24
+ withRowFilters,
25
+ archiveRules,
2
26
  emitWhere,
3
27
  selectedColumns,
4
28
  rejectKeys,
29
+ takeObject,
5
30
  projectExpr,
6
31
  decodeRow,
7
32
  indexes,
8
33
  quote,
9
34
  isRecord,
35
+ registerFailFix,
10
36
  fail
11
- } from "./f5thc1s0.js";
37
+ } from "./xrfnq5k8.js";
38
+ import {
39
+ safetyInstalled,
40
+ runSafety
41
+ } from "./a5sxz52y.js";
12
42
  import {
13
43
  isOperator,
14
44
  operatorName,
15
45
  operatorValue
16
46
  } from "./cn0n68ft.js";
17
47
  import {
18
- OkmError2,
19
- throwNamed2
20
- } from "./ycwz42az.js";
21
-
22
- // src/runtime/tx.ts
23
- async function runWrite(pool, statements, options) {
24
- if (statements.length === 0)
25
- return [];
26
- const first = statements[0];
27
- if (statements.length === 1 && first !== undefined) {
28
- return [await pool.execute(first.text, first.params, options)];
29
- }
30
- return pool.batch(statements, options);
31
- }
48
+ OkmError2
49
+ } from "./52szzakm.js";
32
50
 
33
51
  // src/runtime/write.ts
52
+ registerFailFix("OKM1102", "Pass where, or call .all(reason) to match every row.");
53
+ registerFailFix("OKM1190", "Remove the guarded field. Input cannot set it.");
34
54
  var WRITE_PARAM_BUDGET = 2048;
35
- var INSERT_OPTIONS = ["expect", "onConflict", "returning", "signal", "timeout"];
36
- var FILTER_OPTIONS = ["expect", "returning", "signal", "timeout"];
37
- async function executeWrite(host, op, table, input, options, mods) {
38
- const planned = await planWrite(host.schema, op, table, input, options, mods);
39
- if (planned.statements.length === 0)
40
- return finish([], planned, table);
41
- const results = await runWrite(host.pool, planned.statements, callOptions(options));
42
- return finish(results, planned, table);
43
- }
44
- async function explainWrite(schema, op, table, input, options, mods) {
45
- const planned = await planWrite(schema, op, table, input, options, mods);
46
- return { statements: planned.statements };
47
- }
48
- async function planWrite(schema, op, tableName, input, options, mods) {
55
+ var REDACTED = "[redacted]";
56
+ function encodeColumn(column, value) {
57
+ if (!column.hidden && !column.sensitive)
58
+ return column.encode(value);
59
+ try {
60
+ return column.encode(value);
61
+ } catch (error) {
62
+ if (error instanceof OkmError2) {
63
+ throw new OkmError2(error.code, `Field ${column.field} was rejected.`, {
64
+ kind: error.kind,
65
+ fix: error.fix
66
+ });
67
+ }
68
+ throw error;
69
+ }
70
+ }
71
+ async function requireValidationImport(schema, op, tableName, options) {
72
+ if (op === "delete" || validationRegistered(schema))
73
+ return;
74
+ const stored = schema;
75
+ const source = stored.tables?.find((item) => item.name === tableName);
76
+ if (source === undefined || !writeWouldValidate(stored.validation, source, options))
77
+ return;
78
+ const { refuseMissingValidation } = await import("./83eaefwr.js");
79
+ refuseMissingValidation();
80
+ }
81
+ function validationRegistered(schema) {
82
+ const hooks = schema.hooks;
83
+ if (hooks === undefined)
84
+ return false;
85
+ for (const hook of hooks) {
86
+ if (hook["~v"] === 1)
87
+ return true;
88
+ }
89
+ return false;
90
+ }
91
+ var INSERT_OPTIONS = [
92
+ "allow",
93
+ "expect",
94
+ "onConflict",
95
+ "returning",
96
+ "signal",
97
+ "timeout",
98
+ "validate"
99
+ ];
100
+ var FILTER_OPTIONS = ["allow", "expect", "returning", "signal", "timeout", "validate"];
101
+ var NO_ALLOW = new Set;
102
+ async function executeWrite(host, op, table, input, options, mods, scope, plan) {
103
+ const prepared = await prepareWrite(host, op, table, input, options, mods, scope);
104
+ if (plan !== undefined)
105
+ return prepared;
106
+ if (prepared.statements.length === 0)
107
+ return prepared.finish([]);
108
+ const results = await runWrite(host, prepared.statements, prepared.options);
109
+ return prepared.finish(results);
110
+ }
111
+ async function prepareWrite(host, op, table, input, options, mods, scope) {
112
+ try {
113
+ const planned = await planWrite(host.schema, host.generators, op, table, input, options, mods, scope);
114
+ return {
115
+ statements: planned.statements,
116
+ options: callOptions(options),
117
+ ...planned.expect !== undefined ? { checked: "expect" } : {},
118
+ finish: (results) => finish(results, planned, table)
119
+ };
120
+ } catch (error) {
121
+ throw scrubWrite(host.schema, table, input, error);
122
+ }
123
+ }
124
+ async function explainWrite(schema, op, table, input, options, mods, generators, scope) {
125
+ try {
126
+ const planned = await planWrite(schema, generators, op, table, input, options, mods, scope);
127
+ return { statements: planned.statements };
128
+ } catch (error) {
129
+ throw scrubWrite(schema, table, input, error);
130
+ }
131
+ }
132
+ async function planWrite(schema, generators, op, tableName, input, options, mods, scope) {
133
+ await requireValidationImport(schema, op, tableName, options);
49
134
  const table = indexes(schema).get(tableName);
50
135
  if (table === undefined) {
51
136
  fail("OKM1120", `Table ${tableName} is not in the schema.`);
52
137
  }
138
+ if (mods.uses !== undefined && op !== "insert") {
139
+ const { resolve } = await import("./r6nwkxnt.js");
140
+ mods = { ...mods, presets: resolve(schema, tableName, mods.uses).wheres };
141
+ }
53
142
  const record = isRecord(options) ? options : {};
54
143
  rejectKeys(record, op === "insert" ? INSERT_OPTIONS : FILTER_OPTIONS, op);
55
- if (op === "insert")
56
- return planInsert(table, input, record, mods);
57
- if (op === "update")
58
- return planUpdate(schema, table, input, record, mods);
59
- return planDelete(schema, table, input, record, mods);
144
+ if (op === "insert") {
145
+ return planInsert(table, input, record, mods, generators, scope, schema.tenancy);
146
+ }
147
+ if (op === "update") {
148
+ return withRowFilters(scope, mods.archive, () => {
149
+ noteArchive(table, mods.archive);
150
+ return planUpdate(schema, table, input, record, mods);
151
+ });
152
+ }
153
+ return withRowFilters(scope, mods.archive, () => {
154
+ noteArchive(table, mods.archive);
155
+ return planDelete(schema, table, input, record, mods);
156
+ });
60
157
  }
61
- async function planInsert(table, input, options, mods) {
158
+ async function planInsert(table, input, options, mods, generators, scope, tenancy) {
159
+ const allow = allowSet(table, options.allow);
160
+ noteGuarded(table, allow, "insert");
62
161
  const many = Array.isArray(input);
63
- const rows = (many ? input : [input]).map((row) => insertRow(table, row));
64
- const columns = writtenColumns(table, rows);
162
+ const rows = (many ? input : [input]).map((row) => insertRow(table, row, allow, tenancy));
163
+ tenancy?.stamp(table.model.name, rows, scope);
164
+ fillInsert(table, rows, generators);
165
+ const columns = writtenColumns(table, rows, allow, tenancy?.key);
65
166
  const returning = selectedColumns(table, options.returning);
66
167
  const outputs = outputsOf(returning);
67
168
  const clause = returningClause(returning, undefined);
@@ -78,9 +179,9 @@ async function planInsert(table, input, options, mods) {
78
179
  keyFields: undefined
79
180
  };
80
181
  }
81
- const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./79ggp945.js");
82
- const conflict = conflictMod?.readConflict(table, options.onConflict);
83
- const chunks = chunkRows(rows.map((row) => cellsFor(table, columns, row)));
182
+ const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./jns2wx4t.js");
183
+ const conflict = conflictMod?.readConflict(table, options.onConflict, allow);
184
+ const chunks = chunkRows(rows.map((row) => cellsFor(columns, row)));
84
185
  const statements = [];
85
186
  const keys = [];
86
187
  let offset = 0;
@@ -107,11 +208,16 @@ async function planInsert(table, input, options, mods) {
107
208
  };
108
209
  }
109
210
  function planUpdate(schema, table, input, options, mods) {
211
+ const allow = allowSet(table, options.allow);
212
+ noteGuarded(table, allow, "update");
110
213
  const returning = returningOption(table, options.returning);
111
214
  const outputs = outputsOf(returning);
112
215
  const expect = expectOf(options, mods);
113
216
  if (Array.isArray(input)) {
114
- const rows = input.map((item) => updateItem(table, item));
217
+ const rows = input.map((item) => {
218
+ const row = updateItem(table, item, allow, schema.tenancy);
219
+ return { ...row, where: stack(row.where, mods.presets) };
220
+ });
115
221
  return {
116
222
  statements: updateList(schema, table, rows, returning),
117
223
  outputs,
@@ -123,7 +229,7 @@ function planUpdate(schema, table, input, options, mods) {
123
229
  }
124
230
  if (!isRecord(input))
125
231
  fail("OKM1121", "update expects { where, set } or a list of rows.");
126
- const set = writeSet(table, input.set);
232
+ const set = writeSet(table, input.set, allow, schema.tenancy);
127
233
  requireFilter(table, input.where, mods, "update");
128
234
  if (Object.keys(set).length === 0)
129
235
  fail("OKM1120", "update set is empty.");
@@ -132,7 +238,7 @@ function planUpdate(schema, table, input, options, mods) {
132
238
  sql.text(quote(table.model.sql));
133
239
  sql.text(" as t set ");
134
240
  emitSet(sql, table, set, "t");
135
- emitFilter(schema, table, input.where, sql, "t");
241
+ emitFilter(schema, table, stack(input.where, mods.presets), sql, "t");
136
242
  sql.text(returningClause(returning, "t"));
137
243
  return {
138
244
  statements: [sql.statement()],
@@ -153,7 +259,7 @@ function planDelete(schema, table, input, options, mods) {
153
259
  sql.text("delete from ");
154
260
  sql.text(quote(table.model.sql));
155
261
  sql.text(" as t");
156
- emitFilter(schema, table, input.where, sql, "t");
262
+ emitFilter(schema, table, stack(input.where, mods.presets), sql, "t");
157
263
  sql.text(returningClause(returning, "t"));
158
264
  return {
159
265
  statements: [sql.statement()],
@@ -276,6 +382,7 @@ function updateSlice(schema, table, rows, returning) {
276
382
  sql.text(quote(column.sql));
277
383
  sql.text(" end");
278
384
  }
385
+ emitTouch(sql, table, fields.length > 0);
279
386
  sql.text(" where ");
280
387
  for (let index = 0;index < rows.length; index += 1) {
281
388
  const row = rows[index];
@@ -292,41 +399,102 @@ function updateSlice(schema, table, rows, returning) {
292
399
  }
293
400
  function emitSet(sql, table, set, alias) {
294
401
  const keys = Object.keys(set);
295
- for (let index = 0;index < keys.length; index += 1) {
296
- const key = keys[index];
297
- if (key === undefined)
298
- continue;
402
+ let wrote = false;
403
+ for (const key of keys) {
299
404
  const column = table.columns.get(key);
300
405
  if (column === undefined)
301
406
  continue;
302
- if (index > 0)
407
+ if (wrote)
303
408
  sql.text(", ");
409
+ wrote = true;
304
410
  sql.text(quote(column.sql));
305
411
  sql.text(" = ");
306
412
  emitValue(sql, column, set[key], alias);
307
413
  }
414
+ wrote = emitTouch(sql, table, wrote);
415
+ }
416
+ function emitTouch(sql, table, wrote) {
417
+ const touch = touchFields(table.model);
418
+ if (touch === undefined)
419
+ return wrote;
420
+ for (const field of touch) {
421
+ const column = table.columns.get(field);
422
+ if (column === undefined)
423
+ continue;
424
+ if (wrote)
425
+ sql.text(", ");
426
+ wrote = true;
427
+ sql.text(quote(column.sql));
428
+ sql.text(" = now()");
429
+ }
430
+ return wrote;
308
431
  }
309
432
  function emitValue(sql, column, value, alias) {
310
433
  if (isOperator(value)) {
311
- if (operatorName(value) !== "inc") {
312
- fail("OKM1121", `Field ${column.field} received an operator. set accepts a value or inc.`);
434
+ const name = operatorName(value);
435
+ if (name === "inc") {
436
+ const amount = operatorValue(value);
437
+ if (typeof amount === "object" && amount !== null) {
438
+ fail("OKM1121", `inc on ${column.field} needs a value.`);
439
+ }
440
+ sql.text(`${alias}.${quote(column.sql)} + `);
441
+ sql.param(encodeColumn(column, amount), column.dataType);
442
+ return;
313
443
  }
314
- const amount = operatorValue(value);
315
- if (typeof amount === "object" && amount !== null) {
316
- fail("OKM1121", `inc on ${column.field} needs a value.`);
444
+ if (name === "json.set") {
445
+ emitJsonSet(sql, column, value, alias);
446
+ return;
317
447
  }
318
- sql.text(`${alias}.${quote(column.sql)} + `);
319
- sql.param(column.encode(amount), column.dataType);
320
- return;
448
+ if (name === "arr.append" || name === "arr.remove") {
449
+ emitArrayWrite(sql, column, name, value, alias);
450
+ return;
451
+ }
452
+ fail("OKM1121", `Field ${column.field} received an operator. set accepts a value, inc, set, or arr.`);
321
453
  }
322
454
  if (value === null) {
323
455
  sql.text(`null::${column.dataType}`);
324
456
  return;
325
457
  }
326
- if (typeof value === "object") {
327
- fail("OKM1121", `Field ${column.field} received an object. Pass a value or inc.`);
458
+ if (typeof value === "object")
459
+ takeObject(column, value);
460
+ sql.param(encodeColumn(column, value), column.dataType);
461
+ }
462
+ function emitJsonSet(sql, column, value, alias) {
463
+ assertOperatorFits(column, "json.set");
464
+ if (!isOperator(value))
465
+ fail("OKM1121", `json.set on ${column.field} needs a path and a value.`);
466
+ const stored = operatorValue(value);
467
+ if (!isRecord(stored) || !Array.isArray(stored.path)) {
468
+ fail("OKM1121", `json.set on ${column.field} needs a path and a value.`);
469
+ }
470
+ const path = stored.path;
471
+ if (path.length === 0 || path.some((item) => typeof item !== "string")) {
472
+ fail("OKM1121", `json.set on ${column.field} needs a list of segments.`);
328
473
  }
329
- sql.param(column.encode(value), column.dataType);
474
+ const encoded = encodeJson(stored.value);
475
+ const ref = `${alias}.${quote(column.sql)}`;
476
+ const target = column.dataType === "json" ? `${ref}::jsonb` : ref;
477
+ sql.text(`jsonb_set(${target}, `);
478
+ sql.param(textArray(path, `path on ${column.field}`), "text[]");
479
+ sql.text(", ");
480
+ sql.param(encoded, "jsonb");
481
+ sql.text(")");
482
+ if (column.dataType === "json")
483
+ sql.text("::json");
484
+ }
485
+ function emitArrayWrite(sql, column, name, value, alias) {
486
+ assertOperatorFits(column, name);
487
+ const encode = column.elementEncode;
488
+ if (encode === undefined) {
489
+ fail("OKM1124", `Operator ${name} does not apply to ${column.dataType} column ${column.field}. Accepted operators: eq, not, lt, lte, gt, gte, between, inList, notIn, inc.`);
490
+ }
491
+ if (!isOperator(value)) {
492
+ fail("OKM1121", `${name} on ${column.field} needs an element.`);
493
+ }
494
+ const fn = name === "arr.append" ? "array_append" : "array_remove";
495
+ sql.text(`${fn}(${alias}.${quote(column.sql)}, `);
496
+ sql.param(encode(operatorValue(value)), arrayElementType(column.dataType, column));
497
+ sql.text(")");
330
498
  }
331
499
  function emitFilter(schema, table, where, sql, alias) {
332
500
  emitWhere(schema, table, where, sql.sink(), alias, 0);
@@ -379,7 +547,7 @@ class Sql {
379
547
  return this.params.length === 0 ? { text: this.parts.join("") } : { text: this.parts.join(""), params: this.params };
380
548
  }
381
549
  }
382
- function insertRow(table, value) {
550
+ function insertRow(table, value, allow, tenancy) {
383
551
  if (!isRecord(value))
384
552
  fail("OKM1121", "insert expects an object or a list of objects.");
385
553
  const row = {};
@@ -387,27 +555,55 @@ function insertRow(table, value) {
387
555
  const column = table.columns.get(key);
388
556
  if (column === undefined)
389
557
  continue;
390
- if (!column.writable)
391
- refuse(table.model.name, column);
558
+ tenancy?.guard(table.model.name, key, "insert");
559
+ if (!writable(table, column, allow))
560
+ refuse(table, column);
392
561
  if (value[key] !== undefined)
393
562
  row[key] = value[key];
394
563
  }
395
564
  return row;
396
565
  }
397
- function cellsFor(table, columns, row) {
566
+ function cellsFor(columns, row) {
398
567
  return columns.map((column) => {
399
568
  if (!Object.hasOwn(row, column.field))
400
569
  return { kind: "default" };
401
570
  const value = row[column.field];
402
571
  if (value === null)
403
572
  return { kind: "null", dataType: column.dataType };
404
- if (typeof value === "object") {
405
- fail("OKM1121", `Field ${table.model.name}.${column.field} received an object.`);
406
- }
407
- return { kind: "value", wire: column.encode(value), dataType: column.dataType };
573
+ if (typeof value === "object")
574
+ takeObject(column, value);
575
+ return { kind: "value", wire: encodeColumn(column, value), dataType: column.dataType };
408
576
  });
409
577
  }
410
- function writtenColumns(table, rows) {
578
+ function fillInsert(table, rows, generators) {
579
+ const fills = [];
580
+ for (const column of table.model.columns) {
581
+ if (column.fill !== undefined)
582
+ fills.push(column);
583
+ }
584
+ if (fills.length === 0)
585
+ return;
586
+ for (const row of rows) {
587
+ for (const column of fills) {
588
+ if (Object.hasOwn(row, column.field))
589
+ continue;
590
+ const fill = column.fill;
591
+ if (fill === undefined)
592
+ continue;
593
+ row[column.field] = nextFill(fill, generators);
594
+ }
595
+ }
596
+ }
597
+ function nextFill(fill, generators) {
598
+ if (fill.name === "uuidv4" && generators?.uuidv4 !== undefined)
599
+ return generators.uuidv4();
600
+ if (fill.name === "uuidv7" && generators?.uuidv7 !== undefined)
601
+ return generators.uuidv7();
602
+ if (fill.name === "okid" && generators?.okid !== undefined)
603
+ return generators.okid();
604
+ return fill.call();
605
+ }
606
+ function writtenColumns(table, rows, allow, tenantKey) {
411
607
  const present = new Set;
412
608
  for (const row of rows) {
413
609
  for (const key of Object.keys(row))
@@ -415,12 +611,13 @@ function writtenColumns(table, rows) {
415
611
  }
416
612
  const columns = [];
417
613
  for (const column of table.model.columns) {
418
- if (column.writable && present.has(column.field))
614
+ if (present.has(column.field) && (writable(table, column, allow) || column.fill !== undefined || column.field === tenantKey)) {
419
615
  columns.push(column);
616
+ }
420
617
  }
421
618
  return columns;
422
619
  }
423
- function writeSet(table, value) {
620
+ function writeSet(table, value, allow, tenancy) {
424
621
  if (!isRecord(value))
425
622
  fail("OKM1121", "set must be an object of fields.");
426
623
  const set = {};
@@ -428,19 +625,19 @@ function writeSet(table, value) {
428
625
  if (value[key] === undefined)
429
626
  continue;
430
627
  const column = table.columns.get(key);
431
- if (column === undefined) {
432
- throwNamed2("OKM1120", key, table.names, `Field ${key} is not on ${table.model.name}. Accepted names: ${table.names.join(", ")}.`);
433
- }
434
- if (!column.writable)
435
- refuse(table.model.name, column);
628
+ if (column === undefined)
629
+ continue;
630
+ tenancy?.guard(table.model.name, key, "update");
631
+ if (column.guardUpdate || !writable(table, column, allow))
632
+ refuse(table, column);
436
633
  set[key] = value[key];
437
634
  }
438
635
  return set;
439
636
  }
440
- function updateItem(table, value) {
637
+ function updateItem(table, value, allow, tenancy) {
441
638
  if (!isRecord(value))
442
639
  fail("OKM1121", "update expects { where, set } or a list of rows.");
443
- const set = writeSet(table, value.set);
640
+ const set = writeSet(table, value.set, allow, tenancy);
444
641
  if (value.where !== undefined)
445
642
  return { where: value.where, set };
446
643
  if (value.id !== undefined)
@@ -491,9 +688,7 @@ function keyWire(columns, row) {
491
688
  const value = row[column.field];
492
689
  if (value === undefined || value === null)
493
690
  return null;
494
- if (typeof value === "object")
495
- return null;
496
- return column.encode(value);
691
+ return encodeColumn(column, value);
497
692
  });
498
693
  }
499
694
  function returningOption(table, value) {
@@ -554,10 +749,148 @@ function callOptions(options) {
554
749
  };
555
750
  }
556
751
  function refuse(table, column) {
752
+ if (fieldSealed(table.model, column.field)) {
753
+ fail("OKM1190", `Field ${table.model.name}.${column.field} is set by a trait. Input cannot set it.`);
754
+ }
755
+ if (column.guardUpdate) {
756
+ fail("OKM1190", `Field ${table.model.name}.${column.field} is a primary key. Input cannot change it.`);
757
+ }
557
758
  if (column.guarded) {
558
- fail("OKM1190", `Field ${table}.${column.field} is guarded. Input cannot set it.`);
759
+ fail("OKM1190", `Field ${table.model.name}.${column.field} is guarded. Input cannot set it.`);
760
+ }
761
+ fail("OKM1120", `Field ${table.model.name}.${column.field} cannot be written.`);
762
+ }
763
+ function writable(table, column, allow) {
764
+ if (fieldSealed(table.model, column.field))
765
+ return false;
766
+ if (column.writable)
767
+ return true;
768
+ return column.guarded && allow.has(column.field);
769
+ }
770
+ function allowSet(table, value) {
771
+ if (value === undefined)
772
+ return NO_ALLOW;
773
+ if (!Array.isArray(value))
774
+ fail("OKM1120", "allow must be a list of field names.");
775
+ const set = new Set;
776
+ for (const name of value) {
777
+ if (typeof name !== "string" || !table.columns.has(name)) {
778
+ fail("OKM1120", "allow must name fields on the table.");
779
+ }
780
+ set.add(name);
781
+ }
782
+ return set;
783
+ }
784
+ function noteArchive(table, view) {
785
+ if (!safetyInstalled())
786
+ return;
787
+ const rules = archiveRules(table.model, view);
788
+ if (rules === undefined)
789
+ return;
790
+ runSafety(rules, undefined);
791
+ }
792
+ function noteGuarded(table, allow, op) {
793
+ if (!safetyInstalled())
794
+ return;
795
+ const contributions = [];
796
+ const hatches = [];
797
+ const touch = touchFields(table.model);
798
+ const source = table.model.source;
799
+ for (const column of table.model.columns) {
800
+ const traitName = sealingTrait(table.model, column.field);
801
+ if (traitName !== undefined) {
802
+ const sets = op === "insert" || touch?.includes(column.field) === true;
803
+ contributions.push({
804
+ rule: traitName,
805
+ contribution: sets ? `${traitName} ${table.model.name}.${column.field} set by ${traitName}` : `${traitName} ${table.model.name}.${column.field} kept`,
806
+ provenance: "planner",
807
+ ...source !== undefined ? { source } : {}
808
+ });
809
+ continue;
810
+ }
811
+ if (!column.guarded)
812
+ continue;
813
+ const name = `${table.model.name}.${column.field}`;
814
+ if (allow.has(column.field)) {
815
+ contributions.push({
816
+ rule: "guarded",
817
+ contribution: `guarded ${name} allowed`,
818
+ provenance: "allow"
819
+ });
820
+ hatches.push({ name: "allow", reason: name });
821
+ } else {
822
+ contributions.push({
823
+ rule: "guarded",
824
+ contribution: `guarded ${name} absent`,
825
+ provenance: "planner"
826
+ });
827
+ }
828
+ }
829
+ if (contributions.length === 0)
830
+ return;
831
+ runSafety(contributions, hatches.length === 0 ? undefined : hatches);
832
+ }
833
+ function scrubWrite(schema, tableName, input, error) {
834
+ if (!(error instanceof OkmError2))
835
+ return error;
836
+ const table = indexes(schema).get(tableName);
837
+ if (table === undefined || table.model.conceal !== true)
838
+ return error;
839
+ const secrets = [];
840
+ const names = new Set;
841
+ for (const column of table.model.columns) {
842
+ if (column.hidden || column.sensitive)
843
+ names.add(column.field);
844
+ }
845
+ collectSecrets(input, names, secrets);
846
+ if (secrets.length === 0)
847
+ return error;
848
+ let message = error.message;
849
+ for (const secret of secrets) {
850
+ if (secret.length < 4 || !message.includes(secret))
851
+ continue;
852
+ message = message.replaceAll(secret, REDACTED);
559
853
  }
560
- fail("OKM1120", `Field ${table}.${column.field} cannot be written.`);
854
+ const issues = error.issues === undefined ? undefined : redactIssues(error.issues, secrets);
855
+ if (message === error.message && issues === undefined)
856
+ return error;
857
+ return new OkmError2(error.code, message, {
858
+ kind: error.kind,
859
+ ...error.table !== undefined ? { table: error.table } : {},
860
+ ...error.columns.length > 0 ? { columns: error.columns } : {},
861
+ ...error.issues !== undefined ? { issues: issues ?? error.issues } : {},
862
+ fix: error.fix
863
+ });
864
+ }
865
+ function redactIssues(issues, secrets) {
866
+ let changed = false;
867
+ const next = issues.map((issue) => {
868
+ let message = issue.message;
869
+ for (const secret of secrets) {
870
+ if (secret.length < 4 || !message.includes(secret))
871
+ continue;
872
+ message = message.replaceAll(secret, REDACTED);
873
+ changed = true;
874
+ }
875
+ return message === issue.message ? issue : { path: issue.path, message };
876
+ });
877
+ return changed ? next : undefined;
878
+ }
879
+ function collectSecrets(value, names, found, key) {
880
+ if (typeof value === "string") {
881
+ if (key !== undefined && names.has(key))
882
+ found.push(value);
883
+ return;
884
+ }
885
+ if (Array.isArray(value)) {
886
+ for (const item of value)
887
+ collectSecrets(item, names, found);
888
+ return;
889
+ }
890
+ if (!isRecord(value))
891
+ return;
892
+ for (const child of Object.keys(value))
893
+ collectSecrets(value[child], names, found, child);
561
894
  }
562
895
  export {
563
896
  WRITE_PARAM_BUDGET,