turbine-orm 0.81.0 → 0.90.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 (295) hide show
  1. package/README.md +26 -25
  2. package/dist/checkout.d.ts +27 -2
  3. package/dist/checkout.js +75 -4
  4. package/dist/cjs/checkout.d.ts +27 -2
  5. package/dist/cjs/checkout.js +76 -3
  6. package/dist/cjs/cli/code-first-tags.d.ts +62 -0
  7. package/dist/cjs/cli/code-first-tags.js +124 -0
  8. package/dist/cjs/cli/compile-query.js +9 -0
  9. package/dist/cjs/cli/config.d.ts +35 -6
  10. package/dist/cjs/cli/config.js +17 -1
  11. package/dist/cjs/cli/destructive.d.ts +7 -4
  12. package/dist/cjs/cli/destructive.js +224 -20
  13. package/dist/cjs/cli/error-catalog.js +36 -0
  14. package/dist/cjs/cli/error-text.d.ts +15 -0
  15. package/dist/cjs/cli/error-text.js +40 -0
  16. package/dist/cjs/cli/index.d.ts +62 -4
  17. package/dist/cjs/cli/index.js +568 -108
  18. package/dist/cjs/cli/local-server-guard.d.ts +53 -0
  19. package/dist/cjs/cli/local-server-guard.js +119 -0
  20. package/dist/cjs/cli/local-server-output.d.ts +41 -0
  21. package/dist/cjs/cli/local-server-output.js +103 -0
  22. package/dist/cjs/cli/mcp.d.ts +8 -0
  23. package/dist/cjs/cli/mcp.js +121 -32
  24. package/dist/cjs/cli/migrate.d.ts +113 -5
  25. package/dist/cjs/cli/migrate.js +270 -21
  26. package/dist/cjs/cli/observe.d.ts +6 -1
  27. package/dist/cjs/cli/observe.js +85 -55
  28. package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
  29. package/dist/cjs/cli/pii-predicate-guard.js +98 -37
  30. package/dist/cjs/cli/prisma-resolve.js +17 -13
  31. package/dist/cjs/cli/prisma-schema.js +12 -4
  32. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  33. package/dist/cjs/cli/studio.d.ts +14 -2
  34. package/dist/cjs/cli/studio.js +244 -69
  35. package/dist/cjs/cli/ui.d.ts +1 -1
  36. package/dist/cjs/client.d.ts +558 -85
  37. package/dist/cjs/client.js +1270 -395
  38. package/dist/cjs/connection-guard.d.ts +13 -0
  39. package/dist/cjs/connection-guard.js +28 -0
  40. package/dist/cjs/connection-url.d.ts +51 -16
  41. package/dist/cjs/connection-url.js +67 -34
  42. package/dist/cjs/diagnostics.d.ts +81 -0
  43. package/dist/cjs/diagnostics.js +147 -0
  44. package/dist/cjs/dialect.d.ts +449 -20
  45. package/dist/cjs/dialect.js +117 -76
  46. package/dist/cjs/engine-config.d.ts +7 -6
  47. package/dist/cjs/errors.d.ts +126 -6
  48. package/dist/cjs/errors.js +256 -13
  49. package/dist/cjs/generate.d.ts +57 -7
  50. package/dist/cjs/generate.js +635 -279
  51. package/dist/cjs/global-filters-config.d.ts +59 -0
  52. package/dist/cjs/global-filters-config.js +367 -0
  53. package/dist/cjs/index.d.ts +9 -4
  54. package/dist/cjs/index.js +10 -2
  55. package/dist/cjs/introspect.d.ts +31 -6
  56. package/dist/cjs/introspect.js +108 -50
  57. package/dist/cjs/log-codes.d.ts +12 -0
  58. package/dist/cjs/log-codes.js +77 -0
  59. package/dist/cjs/logger.d.ts +87 -0
  60. package/dist/cjs/logger.js +98 -0
  61. package/dist/cjs/mssql.d.ts +8 -5
  62. package/dist/cjs/mssql.js +352 -65
  63. package/dist/cjs/mysql.d.ts +3 -5
  64. package/dist/cjs/mysql.js +420 -70
  65. package/dist/cjs/nested-write.d.ts +3 -0
  66. package/dist/cjs/nested-write.js +179 -114
  67. package/dist/cjs/pipeline-submittable.d.ts +12 -0
  68. package/dist/cjs/pipeline-submittable.js +118 -44
  69. package/dist/cjs/pipeline.js +21 -26
  70. package/dist/cjs/powdb-introspect.d.ts +5 -2
  71. package/dist/cjs/powdb-introspect.js +20 -15
  72. package/dist/cjs/powdb-shared.d.ts +36 -5
  73. package/dist/cjs/powdb-shared.js +67 -8
  74. package/dist/cjs/powdb.d.ts +4 -0
  75. package/dist/cjs/powdb.js +183 -51
  76. package/dist/cjs/powql.d.ts +394 -51
  77. package/dist/cjs/powql.js +1829 -284
  78. package/dist/cjs/prisma-compat.d.ts +23 -15
  79. package/dist/cjs/prisma-compat.js +507 -372
  80. package/dist/cjs/query/aggregates.d.ts +17 -1
  81. package/dist/cjs/query/aggregates.js +261 -29
  82. package/dist/cjs/query/batched-loader.d.ts +25 -6
  83. package/dist/cjs/query/batched-loader.js +103 -20
  84. package/dist/cjs/query/builder.d.ts +325 -184
  85. package/dist/cjs/query/builder.js +1452 -490
  86. package/dist/cjs/query/column-ref.d.ts +61 -0
  87. package/dist/cjs/query/column-ref.js +73 -0
  88. package/dist/cjs/query/compound-unique.d.ts +21 -0
  89. package/dist/cjs/query/compound-unique.js +98 -18
  90. package/dist/cjs/query/deferred.d.ts +53 -16
  91. package/dist/cjs/query/expr.d.ts +217 -0
  92. package/dist/cjs/query/expr.js +859 -0
  93. package/dist/cjs/query/filters.d.ts +155 -53
  94. package/dist/cjs/query/filters.js +613 -76
  95. package/dist/cjs/query/index.d.ts +2 -0
  96. package/dist/cjs/query/index.js +7 -1
  97. package/dist/cjs/query/json-value.d.ts +43 -0
  98. package/dist/cjs/query/json-value.js +59 -0
  99. package/dist/cjs/query/normalize-args.d.ts +87 -0
  100. package/dist/cjs/query/normalize-args.js +527 -0
  101. package/dist/cjs/query/option-surface.d.ts +16 -4
  102. package/dist/cjs/query/option-surface.js +57 -14
  103. package/dist/cjs/query/relation-names.d.ts +14 -0
  104. package/dist/cjs/query/relation-names.js +77 -18
  105. package/dist/cjs/query/relations.d.ts +30 -3
  106. package/dist/cjs/query/relations.js +219 -63
  107. package/dist/cjs/query/row-lock.d.ts +49 -0
  108. package/dist/cjs/query/row-lock.js +138 -0
  109. package/dist/cjs/query/sql-comment.d.ts +161 -0
  110. package/dist/cjs/query/sql-comment.js +381 -0
  111. package/dist/cjs/query/stream-idle.d.ts +132 -0
  112. package/dist/cjs/query/stream-idle.js +212 -0
  113. package/dist/cjs/query/table-client.d.ts +101 -0
  114. package/dist/cjs/query/table-client.js +74 -0
  115. package/dist/cjs/query/types.d.ts +677 -298
  116. package/dist/cjs/query/utils.d.ts +105 -6
  117. package/dist/cjs/query/utils.js +426 -19
  118. package/dist/cjs/query/warn-registry.d.ts +18 -0
  119. package/dist/cjs/query/warn-registry.js +18 -0
  120. package/dist/cjs/query/where-compile.d.ts +138 -14
  121. package/dist/cjs/query/where-compile.js +513 -29
  122. package/dist/cjs/query/where.d.ts +159 -59
  123. package/dist/cjs/query/where.js +716 -246
  124. package/dist/cjs/query/writes.d.ts +112 -50
  125. package/dist/cjs/query/writes.js +1498 -200
  126. package/dist/cjs/query-events.d.ts +4 -2
  127. package/dist/cjs/query-events.js +3 -1
  128. package/dist/cjs/realtime.d.ts +7 -3
  129. package/dist/cjs/realtime.js +13 -2
  130. package/dist/cjs/schema-builder.d.ts +55 -4
  131. package/dist/cjs/schema-builder.js +382 -18
  132. package/dist/cjs/schema-metadata.d.ts +21 -1
  133. package/dist/cjs/schema-metadata.js +29 -7
  134. package/dist/cjs/schema-sql.d.ts +75 -5
  135. package/dist/cjs/schema-sql.js +497 -45
  136. package/dist/cjs/schema.d.ts +26 -5
  137. package/dist/cjs/schema.js +110 -13
  138. package/dist/cjs/sql-placeholders.d.ts +56 -0
  139. package/dist/cjs/sql-placeholders.js +204 -0
  140. package/dist/cjs/sqlite.d.ts +75 -20
  141. package/dist/cjs/sqlite.js +593 -95
  142. package/dist/cjs/supplied-pool-types.d.ts +48 -0
  143. package/dist/cjs/supplied-pool-types.js +256 -0
  144. package/dist/cjs/transaction-control.d.ts +51 -0
  145. package/dist/cjs/transaction-control.js +316 -0
  146. package/dist/cjs/transaction-state.d.ts +342 -0
  147. package/dist/cjs/transaction-state.js +707 -0
  148. package/dist/cjs/typed-sql.d.ts +37 -3
  149. package/dist/cjs/typed-sql.js +102 -8
  150. package/dist/cli/code-first-tags.d.ts +62 -0
  151. package/dist/cli/code-first-tags.js +120 -0
  152. package/dist/cli/compile-query.js +9 -0
  153. package/dist/cli/config.d.ts +35 -6
  154. package/dist/cli/config.js +16 -1
  155. package/dist/cli/destructive.d.ts +7 -4
  156. package/dist/cli/destructive.js +224 -20
  157. package/dist/cli/error-catalog.js +36 -0
  158. package/dist/cli/error-text.d.ts +15 -0
  159. package/dist/cli/error-text.js +37 -0
  160. package/dist/cli/index.d.ts +62 -4
  161. package/dist/cli/index.js +569 -112
  162. package/dist/cli/local-server-guard.d.ts +53 -0
  163. package/dist/cli/local-server-guard.js +113 -0
  164. package/dist/cli/local-server-output.d.ts +41 -0
  165. package/dist/cli/local-server-output.js +98 -0
  166. package/dist/cli/mcp.d.ts +8 -0
  167. package/dist/cli/mcp.js +121 -34
  168. package/dist/cli/migrate.d.ts +113 -5
  169. package/dist/cli/migrate.js +261 -21
  170. package/dist/cli/observe.d.ts +6 -1
  171. package/dist/cli/observe.js +84 -54
  172. package/dist/cli/pii-predicate-guard.d.ts +43 -23
  173. package/dist/cli/pii-predicate-guard.js +100 -35
  174. package/dist/cli/prisma-resolve.js +15 -11
  175. package/dist/cli/prisma-schema.js +12 -4
  176. package/dist/cli/studio-ui.generated.js +1 -1
  177. package/dist/cli/studio.d.ts +14 -2
  178. package/dist/cli/studio.js +246 -71
  179. package/dist/cli/ui.d.ts +1 -1
  180. package/dist/client.d.ts +558 -85
  181. package/dist/client.js +1275 -400
  182. package/dist/connection-guard.d.ts +13 -0
  183. package/dist/connection-guard.js +27 -0
  184. package/dist/connection-url.d.ts +51 -16
  185. package/dist/connection-url.js +63 -34
  186. package/dist/diagnostics.d.ts +81 -0
  187. package/dist/diagnostics.js +144 -0
  188. package/dist/dialect.d.ts +449 -20
  189. package/dist/dialect.js +115 -43
  190. package/dist/engine-config.d.ts +7 -6
  191. package/dist/errors.d.ts +126 -6
  192. package/dist/errors.js +252 -12
  193. package/dist/generate.d.ts +57 -7
  194. package/dist/generate.js +635 -281
  195. package/dist/global-filters-config.d.ts +59 -0
  196. package/dist/global-filters-config.js +361 -0
  197. package/dist/index.d.ts +9 -4
  198. package/dist/index.js +4 -2
  199. package/dist/introspect.d.ts +31 -6
  200. package/dist/introspect.js +106 -50
  201. package/dist/log-codes.d.ts +12 -0
  202. package/dist/log-codes.js +74 -0
  203. package/dist/logger.d.ts +87 -0
  204. package/dist/logger.js +93 -0
  205. package/dist/mssql.d.ts +8 -5
  206. package/dist/mssql.js +354 -67
  207. package/dist/mysql.d.ts +3 -5
  208. package/dist/mysql.js +423 -73
  209. package/dist/nested-write.d.ts +3 -0
  210. package/dist/nested-write.js +177 -112
  211. package/dist/pipeline-submittable.d.ts +12 -0
  212. package/dist/pipeline-submittable.js +117 -44
  213. package/dist/pipeline.js +21 -26
  214. package/dist/powdb-introspect.d.ts +5 -2
  215. package/dist/powdb-introspect.js +21 -16
  216. package/dist/powdb-shared.d.ts +36 -5
  217. package/dist/powdb-shared.js +65 -8
  218. package/dist/powdb.d.ts +4 -0
  219. package/dist/powdb.js +183 -51
  220. package/dist/powql.d.ts +394 -51
  221. package/dist/powql.js +1836 -291
  222. package/dist/prisma-compat.d.ts +23 -15
  223. package/dist/prisma-compat.js +508 -373
  224. package/dist/query/aggregates.d.ts +17 -1
  225. package/dist/query/aggregates.js +263 -32
  226. package/dist/query/batched-loader.d.ts +25 -6
  227. package/dist/query/batched-loader.js +101 -21
  228. package/dist/query/builder.d.ts +325 -184
  229. package/dist/query/builder.js +1458 -495
  230. package/dist/query/column-ref.d.ts +61 -0
  231. package/dist/query/column-ref.js +66 -0
  232. package/dist/query/compound-unique.d.ts +21 -0
  233. package/dist/query/compound-unique.js +97 -19
  234. package/dist/query/deferred.d.ts +53 -16
  235. package/dist/query/expr.d.ts +217 -0
  236. package/dist/query/expr.js +848 -0
  237. package/dist/query/filters.d.ts +155 -53
  238. package/dist/query/filters.js +597 -72
  239. package/dist/query/index.d.ts +2 -0
  240. package/dist/query/index.js +4 -0
  241. package/dist/query/json-value.d.ts +43 -0
  242. package/dist/query/json-value.js +54 -0
  243. package/dist/query/normalize-args.d.ts +87 -0
  244. package/dist/query/normalize-args.js +517 -0
  245. package/dist/query/option-surface.d.ts +16 -4
  246. package/dist/query/option-surface.js +56 -13
  247. package/dist/query/relation-names.d.ts +14 -0
  248. package/dist/query/relation-names.js +77 -19
  249. package/dist/query/relations.d.ts +30 -3
  250. package/dist/query/relations.js +220 -67
  251. package/dist/query/row-lock.d.ts +49 -0
  252. package/dist/query/row-lock.js +131 -0
  253. package/dist/query/sql-comment.d.ts +161 -0
  254. package/dist/query/sql-comment.js +374 -0
  255. package/dist/query/stream-idle.d.ts +132 -0
  256. package/dist/query/stream-idle.js +203 -0
  257. package/dist/query/table-client.d.ts +101 -0
  258. package/dist/query/table-client.js +70 -0
  259. package/dist/query/types.d.ts +677 -298
  260. package/dist/query/utils.d.ts +105 -6
  261. package/dist/query/utils.js +414 -18
  262. package/dist/query/warn-registry.d.ts +18 -0
  263. package/dist/query/warn-registry.js +18 -0
  264. package/dist/query/where-compile.d.ts +138 -14
  265. package/dist/query/where-compile.js +508 -31
  266. package/dist/query/where.d.ts +159 -59
  267. package/dist/query/where.js +711 -248
  268. package/dist/query/writes.d.ts +112 -50
  269. package/dist/query/writes.js +1496 -202
  270. package/dist/query-events.d.ts +4 -2
  271. package/dist/query-events.js +3 -1
  272. package/dist/realtime.d.ts +7 -3
  273. package/dist/realtime.js +13 -2
  274. package/dist/schema-builder.d.ts +55 -4
  275. package/dist/schema-builder.js +381 -18
  276. package/dist/schema-metadata.d.ts +21 -1
  277. package/dist/schema-metadata.js +28 -7
  278. package/dist/schema-sql.d.ts +75 -5
  279. package/dist/schema-sql.js +494 -44
  280. package/dist/schema.d.ts +26 -5
  281. package/dist/schema.js +109 -13
  282. package/dist/sql-placeholders.d.ts +56 -0
  283. package/dist/sql-placeholders.js +200 -0
  284. package/dist/sqlite.d.ts +75 -20
  285. package/dist/sqlite.js +597 -99
  286. package/dist/supplied-pool-types.d.ts +48 -0
  287. package/dist/supplied-pool-types.js +247 -0
  288. package/dist/transaction-control.d.ts +51 -0
  289. package/dist/transaction-control.js +311 -0
  290. package/dist/transaction-state.d.ts +342 -0
  291. package/dist/transaction-state.js +696 -0
  292. package/dist/typed-sql.d.ts +37 -3
  293. package/dist/typed-sql.js +100 -8
  294. package/package.json +7 -4
  295. package/skills/turbine-orm/SKILL.md +56 -6
@@ -77,6 +77,9 @@ export interface NestedWriteContext {
77
77
  where: Record<string, unknown>;
78
78
  with?: Record<string, unknown>;
79
79
  }): Promise<T | null>;
80
+ findFirst(args: {
81
+ where: Record<string, unknown>;
82
+ }): Promise<T | null>;
80
83
  };
81
84
  };
82
85
  }
@@ -21,6 +21,8 @@ exports.executeNestedCreate = executeNestedCreate;
21
21
  exports.executeNestedUpdate = executeNestedUpdate;
22
22
  const errors_js_1 = require("./errors.js");
23
23
  const compound_unique_js_1 = require("./query/compound-unique.js");
24
+ const expr_js_1 = require("./query/expr.js");
25
+ const filters_js_1 = require("./query/filters.js");
24
26
  const utils_js_1 = require("./query/utils.js");
25
27
  const schema_js_1 = require("./schema.js");
26
28
  const MAX_DEPTH = 10;
@@ -50,7 +52,7 @@ function extractRelationFields(data, tableMeta) {
50
52
  relations[key] = value;
51
53
  }
52
54
  else {
53
- scalars[key] = value;
55
+ (0, filters_js_1.setOwn)(scalars, key, value);
54
56
  }
55
57
  }
56
58
  return { scalars, relations };
@@ -216,32 +218,73 @@ function toArray(value) {
216
218
  function keyOf(value) {
217
219
  return String(value);
218
220
  }
221
+ /** The operations that point a to-one relation's foreign key at a row: at most one per write. */
222
+ const BELONGS_TO_POINTER_OPS = ['create', 'connect', 'connectOrCreate', 'disconnect', 'delete'];
219
223
  /**
220
224
  * Validate that all operation keys in a nested write are recognized and
221
- * allowed for the current context (create vs update).
225
+ * allowed for the current context (create vs update) and relation.
226
+ *
227
+ * A belongsTo relation is one row the parent points at, so it takes Prisma's
228
+ * to-one operations: no `set` (a to-many operation), and at most one of the
229
+ * operations that decide what the parent points at, since one write cannot
230
+ * honor two. An operation whose value is `undefined` is absent, as everywhere
231
+ * else in a query; only its name is still checked, so a misspelled operation
232
+ * is reported whatever it holds.
222
233
  */
223
- function validateOps(relationName, ops, isUpdate) {
234
+ function validateOps(relationName, ops, isUpdate, rel) {
235
+ const belongsTo = rel?.type === 'belongsTo';
224
236
  for (const opName of Object.keys(ops)) {
237
+ const absent = ops[opName] === undefined;
225
238
  if (!CREATE_ONLY_OPS.has(opName) && !UPDATE_ONLY_OPS.has(opName)) {
239
+ const updateOps = belongsTo
240
+ ? ', disconnect, delete, update, upsert'
241
+ : ', disconnect, set, delete, update, upsert';
226
242
  throw new errors_js_1.ValidationError(`Unknown nested write operation "${opName}" on relation "${relationName}". ` +
227
- `Valid operations: create, connect, connectOrCreate${isUpdate ? ', disconnect, set, delete, update, upsert' : ''}.`);
243
+ `Valid operations: create, connect, connectOrCreate${isUpdate ? updateOps : ''}.`);
228
244
  }
245
+ if (absent)
246
+ continue;
229
247
  if (!isUpdate && UPDATE_ONLY_OPS.has(opName)) {
230
248
  throw new errors_js_1.ValidationError(`Operation "${opName}" on relation "${relationName}" is only valid inside update(), not create().`);
231
249
  }
250
+ if (belongsTo && opName === 'set') {
251
+ throw new errors_js_1.ValidationError(`Nested set on "${relationName}" is not supported: "${relationName}" is a single "${rel.to}" row this ` +
252
+ `record points at, and set replaces a to-many list. Use connect to point at a different "${rel.to}", ` +
253
+ 'or disconnect to clear it.');
254
+ }
255
+ }
256
+ if (belongsTo) {
257
+ const given = BELONGS_TO_POINTER_OPS.filter((op) => ops[op] !== undefined && ops[op] !== false);
258
+ if (given.length > 1) {
259
+ throw new errors_js_1.ValidationError(`Nested write on "${relationName}" combines ${given.join(' and ')}. "${relationName}" is a single ` +
260
+ `"${rel.to}" row, so one write can create, connect, connectOrCreate, disconnect or delete it, not ` +
261
+ 'several. Split the write in two if both are intended.');
262
+ }
232
263
  }
233
264
  }
265
+ /**
266
+ * The single item a to-one operation names. A one-element array is accepted
267
+ * for compatibility; an empty or longer one is refused, since only one row can
268
+ * be pointed at and keeping one of several would drop the rest silently.
269
+ */
270
+ function singleToOneItem(value, op, relName, rel) {
271
+ if (!Array.isArray(value))
272
+ return value;
273
+ if (value.length === 1)
274
+ return value[0];
275
+ throw new errors_js_1.ValidationError(`Nested ${op} on "${relName}" received ${value.length} items, but "${relName}" is a single "${rel.to}" ` +
276
+ `row. Pass one object, for example \`${op}: { ... }\`.`);
277
+ }
234
278
  /**
235
279
  * The `with` clause that reads back the tree a nested write just wrote.
236
280
  *
237
281
  * Built from the DATA, recursively: a relation whose payload itself contains
238
282
  * relation writes is requested as `{ rel: { with: { ... } } }` rather than
239
- * `{ rel: true }`. It used to be the top-level keys alone, so a depth-3 create
240
- * wrote all three levels and returned an object whose grandchildren were
241
- * missing entirely, which a caller reads as "there are none".
283
+ * `{ rel: true }`. Requesting the top-level keys alone would return a depth-3
284
+ * create without its grandchildren, which a caller reads as "there are none".
242
285
  *
243
286
  * Only relations that were actually WRITTEN are requested, so a create with no
244
- * nested data still reads back exactly what it did before. The walk is capped
287
+ * nested data reads back just the row. The walk is capped
245
288
  * at {@link MAX_DEPTH}, the cap the write walk itself uses, and the builder's
246
289
  * own relation-depth guard is the backstop below that.
247
290
  */
@@ -266,11 +309,11 @@ function readBackWith(schema, tableName, relations, depth = 0) {
266
309
  for (const item of toArray(payload)) {
267
310
  // `{ where, data }` (nested update) and `{ where, create, update }`
268
311
  // (nested upsert) carry their written fields one level in.
269
- const bodies = isPlainRecord(item) && ('data' in item || 'create' in item || 'update' in item)
312
+ const bodies = (0, filters_js_1.isPlainRecord)(item) && ('data' in item || 'create' in item || 'update' in item)
270
313
  ? [item.data, item.create, item.update]
271
314
  : [item];
272
315
  for (const body of bodies) {
273
- if (!isPlainRecord(body))
316
+ if (!(0, filters_js_1.isPlainRecord)(body))
274
317
  continue;
275
318
  const child = schema.tables[rel.to];
276
319
  if (!child)
@@ -286,10 +329,6 @@ function readBackWith(schema, tableName, relations, depth = 0) {
286
329
  }
287
330
  return Object.keys(clause).length > 0 ? clause : undefined;
288
331
  }
289
- /** A plain object (not an array, Date or null), the shape nested payloads take. */
290
- function isPlainRecord(value) {
291
- return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date);
292
- }
293
332
  /**
294
333
  * Build a PK-based where clause from a parent row and its table metadata.
295
334
  */
@@ -821,6 +860,17 @@ function notRelatedToParent(op, relName, rel, target) {
821
860
  `or it belongs to a different parent (a nested ${op} can only touch this parent's rows).`,
822
861
  });
823
862
  }
863
+ /** A belongsTo `delete` (or a `disconnect` naming fields) on a parent whose foreign key is NULL. */
864
+ function nothingConnected(op, relName, rel, target) {
865
+ return new errors_js_1.NotFoundError({
866
+ table: rel.to,
867
+ where: target,
868
+ operation: `nested ${op}`,
869
+ message: `Nested ${op} on relation "${relName}": nothing is connected (this record's foreign key to "${rel.to}" ` +
870
+ `is NULL), so there is no "${rel.to}" row to ${op}.` +
871
+ (op === 'disconnect' ? ' Use `disconnect: true` to clear the relation whatever it points at.' : ''),
872
+ });
873
+ }
824
874
  /** Re-tag a NotFoundError from a scoped child write as a relation-scoped miss. */
825
875
  function rethrowAsNotRelated(err, op, relName, rel, target) {
826
876
  if (err instanceof errors_js_1.NotFoundError) {
@@ -852,7 +902,7 @@ async function executeNestedCreate(ctx, tableName, data, depth = 0, path = [], r
852
902
  throw new errors_js_1.RelationError(`Unknown relation "${relName}" on table "${tableName}". ` +
853
903
  `Available relations: ${Object.keys(tableMeta.relations).join(', ') || '(none)'}.`);
854
904
  }
855
- validateOps(relName, ops, false);
905
+ validateOps(relName, ops, false, rel);
856
906
  }
857
907
  // belongsTo relations put the foreign key on the PARENT row, so they must be
858
908
  // resolved BEFORE the parent is inserted, otherwise a NOT NULL FK column
@@ -930,7 +980,7 @@ async function executeNestedUpdate(ctx, tableName, where, data, depth = 0, path
930
980
  throw new errors_js_1.RelationError(`Unknown relation "${relName}" on table "${tableName}". ` +
931
981
  `Available relations: ${Object.keys(tableMeta.relations).join(', ') || '(none)'}.`);
932
982
  }
933
- validateOps(relName, ops, true);
983
+ validateOps(relName, ops, true, rel);
934
984
  }
935
985
  // Update parent row with scalar data (may be empty if only relation ops)
936
986
  let parentRow;
@@ -980,25 +1030,11 @@ async function executeNestedUpdate(ctx, tableName, where, data, depth = 0, path
980
1030
  if (ops.upsert !== undefined) {
981
1031
  await processBelongsToUpsert(ctx, rel, ops.upsert, parentRow, tableName);
982
1032
  }
983
- if (ops.disconnect !== undefined) {
984
- // For belongsTo disconnect, null out the FK on the parent
985
- const fks = (0, schema_js_1.normalizeKeyColumns)(rel.foreignKey);
986
- const nullable = fks.every((fk) => {
987
- const col = tableMeta.columns.find((c) => c.name === fk);
988
- return col?.nullable ?? false;
989
- });
990
- if (!nullable) {
991
- throw new errors_js_1.ValidationError(`Cannot disconnect "${relName}": foreign key column(s) ${fks.join(', ')} are NOT NULL. Use delete instead.`);
992
- }
993
- const updateData = {};
994
- for (const fk of fks) {
995
- const field = tableMeta.reverseColumnMap[fk] ?? fk;
996
- updateData[field] = null;
997
- }
998
- await ctx.tx.table(tableName).update({
999
- where: pkWhere(tableMeta, parentRow),
1000
- data: updateData,
1001
- });
1033
+ if (ops.disconnect !== undefined && ops.disconnect !== false) {
1034
+ await processBelongsToDisconnect(ctx, rel, ops.disconnect, relName, parentRow, tableName, 'disconnect');
1035
+ }
1036
+ if (ops.delete !== undefined && ops.delete !== false) {
1037
+ await processBelongsToDisconnect(ctx, rel, ops.delete, relName, parentRow, tableName, 'delete');
1002
1038
  }
1003
1039
  }
1004
1040
  else {
@@ -1077,93 +1113,107 @@ async function processHasManyCreate(ctx, rel, ops, parentRow, depth, path, relNa
1077
1113
  * which UPDATEs the FK after the parent already exists.)
1078
1114
  */
1079
1115
  async function resolveBelongsToForCreate(ctx, rel, ops, parentTable, depth, path, relName) {
1080
- const fks = (0, schema_js_1.normalizeKeyColumns)(rel.foreignKey);
1081
- const refs = (0, schema_js_1.normalizeKeyColumns)(rel.referenceKey);
1082
- const parentMeta = ctx.schema.tables[parentTable];
1083
- const relatedTable = ctx.schema.tables[rel.to];
1084
- let relatedRow = null;
1116
+ const relatedRow = await resolveBelongsToTarget(ctx, rel, ops, depth, path, relName);
1117
+ return relatedRow ? belongsToKeyData(ctx, rel, parentTable, relatedRow) : {};
1118
+ }
1119
+ /**
1120
+ * The row a belongsTo create / connect / connectOrCreate points the parent at,
1121
+ * or null when the write names none of the three. Shared by the create path
1122
+ * (which folds the key into the parent INSERT) and the update path (which
1123
+ * UPDATEs the parent's key), so the two cannot disagree about any of them.
1124
+ */
1125
+ async function resolveBelongsToTarget(ctx, rel, ops, depth, path, relName) {
1085
1126
  if (ops.create !== undefined) {
1086
- const items = toArray(ops.create);
1087
- if (items.length > 0) {
1088
- relatedRow = (await executeNestedCreate(ctx, rel.to, items[0], depth + 1, [...path, relName]));
1089
- }
1127
+ const item = singleToOneItem(ops.create, 'create', relName, rel);
1128
+ return (await executeNestedCreate(ctx, rel.to, item, depth + 1, [...path, relName]));
1090
1129
  }
1091
- else if (ops.connect !== undefined) {
1092
- const items = toArray(ops.connect);
1093
- if (items.length > 0) {
1094
- const target = items[0];
1095
- relatedRow = (await ctx.tx.table(rel.to).findUnique({ where: target }));
1096
- if (!relatedRow) {
1097
- throw new errors_js_1.ValidationError(`connect on "${relName}": no ${rel.to} row found matching ${(0, errors_js_1.describeTargetForMessage)(target)}.`);
1098
- }
1099
- }
1100
- }
1101
- else if (ops.connectOrCreate !== undefined) {
1102
- const items = toArray(ops.connectOrCreate);
1103
- if (items.length > 0) {
1104
- const op = items[0];
1105
- relatedRow = (await ctx.tx.table(rel.to).findUnique({ where: op.where }));
1106
- if (!relatedRow) {
1107
- // For belongsTo the FK lives on the parent, so the related row is
1108
- // created plainly (no FK injection) and we read its reference key.
1109
- relatedRow = (await ctx.tx.table(rel.to).create({ data: op.create }));
1110
- }
1130
+ if (ops.connect !== undefined) {
1131
+ const target = singleToOneItem(ops.connect, 'connect', relName, rel);
1132
+ const found = (await ctx.tx.table(rel.to).findUnique({ where: target }));
1133
+ if (!found) {
1134
+ throw new errors_js_1.ValidationError(`connect on "${relName}": no ${rel.to} row found matching ${(0, errors_js_1.describeTargetForMessage)(target)}.`);
1111
1135
  }
1136
+ return found;
1112
1137
  }
1113
- const fkScalars = {};
1114
- if (relatedRow) {
1115
- for (let i = 0; i < fks.length; i++) {
1116
- const fkField = parentMeta.reverseColumnMap[fks[i]] ?? fks[i];
1117
- const refField = relatedTable?.reverseColumnMap[refs[i]] ?? refs[i];
1118
- fkScalars[fkField] = relatedRow[refField];
1138
+ if (ops.connectOrCreate !== undefined) {
1139
+ const op = singleToOneItem(ops.connectOrCreate, 'connectOrCreate', relName, rel);
1140
+ if (!(0, filters_js_1.isPlainRecord)(op?.where) || !(0, filters_js_1.isPlainRecord)(op?.create)) {
1141
+ throw new errors_js_1.ValidationError(`Nested connectOrCreate on "${relName}" requires "where" and "create" fields.`);
1119
1142
  }
1143
+ const found = (await ctx.tx.table(rel.to).findUnique({ where: op.where }));
1144
+ // For belongsTo the FK lives on the parent, so the related row is created
1145
+ // plainly (no FK injection) and its reference key is read back.
1146
+ return found ?? (await ctx.tx.table(rel.to).create({ data: op.create }));
1120
1147
  }
1121
- return fkScalars;
1148
+ return null;
1122
1149
  }
1123
- async function processBelongsToCreate(ctx, rel, ops, parentRow, parentTable, depth, path, relName) {
1150
+ /** The parent's own field values that point it at `relatedRow`. */
1151
+ function belongsToKeyData(ctx, rel, parentTable, relatedRow) {
1124
1152
  const fks = (0, schema_js_1.normalizeKeyColumns)(rel.foreignKey);
1125
1153
  const refs = (0, schema_js_1.normalizeKeyColumns)(rel.referenceKey);
1126
- // create, insert the related row, then update parent's FK
1127
- if (ops.create !== undefined) {
1128
- const items = toArray(ops.create);
1129
- if (items.length > 0) {
1130
- const relatedRow = (await executeNestedCreate(ctx, rel.to, items[0], depth + 1, [...path, relName]));
1131
- const updateData = {};
1132
- const relatedTable = ctx.schema.tables[rel.to];
1133
- for (let i = 0; i < fks.length; i++) {
1134
- const fkField = ctx.schema.tables[parentTable]?.reverseColumnMap[fks[i]] ?? fks[i];
1135
- const refField = relatedTable?.reverseColumnMap[refs[i]] ?? refs[i];
1136
- updateData[fkField] = relatedRow[refField];
1137
- }
1138
- const parentMeta = ctx.schema.tables[parentTable];
1139
- await ctx.tx.table(parentTable).update({
1140
- where: pkWhere(parentMeta, parentRow),
1141
- data: updateData,
1142
- });
1143
- }
1154
+ const parentMeta = ctx.schema.tables[parentTable];
1155
+ const relatedTable = ctx.schema.tables[rel.to];
1156
+ const data = {};
1157
+ for (let i = 0; i < fks.length; i++) {
1158
+ const fkField = parentMeta?.reverseColumnMap[fks[i]] ?? fks[i];
1159
+ const refField = relatedTable?.reverseColumnMap[refs[i]] ?? refs[i];
1160
+ data[fkField] = relatedRow === null ? null : relatedRow[refField];
1144
1161
  }
1145
- // connect, validate existence, update parent's FK
1146
- if (ops.connect !== undefined) {
1147
- const items = toArray(ops.connect);
1148
- if (items.length > 0) {
1149
- const target = items[0];
1150
- const existing = await ctx.tx.table(rel.to).findUnique({ where: target });
1151
- if (!existing) {
1152
- throw new errors_js_1.ValidationError(`connect on "${relName}": no ${rel.to} row found matching ${(0, errors_js_1.describeTargetForMessage)(target)}.`);
1153
- }
1154
- const updateData = {};
1155
- const relatedTable = ctx.schema.tables[rel.to];
1156
- for (let i = 0; i < fks.length; i++) {
1157
- const fkField = ctx.schema.tables[parentTable]?.reverseColumnMap[fks[i]] ?? fks[i];
1158
- const refField = relatedTable?.reverseColumnMap[refs[i]] ?? refs[i];
1159
- updateData[fkField] = existing[refField];
1160
- }
1161
- const parentMeta = ctx.schema.tables[parentTable];
1162
- await ctx.tx.table(parentTable).update({
1163
- where: pkWhere(parentMeta, parentRow),
1164
- data: updateData,
1165
- });
1166
- }
1162
+ return data;
1163
+ }
1164
+ /** Update path: create / connect / connectOrCreate the related row, then point the parent at it. */
1165
+ async function processBelongsToCreate(ctx, rel, ops, parentRow, parentTable, depth, path, relName) {
1166
+ const relatedRow = await resolveBelongsToTarget(ctx, rel, ops, depth, path, relName);
1167
+ if (!relatedRow)
1168
+ return;
1169
+ await ctx.tx.table(parentTable).update({
1170
+ where: pkWhere(ctx.schema.tables[parentTable], parentRow),
1171
+ data: belongsToKeyData(ctx, rel, parentTable, relatedRow),
1172
+ });
1173
+ }
1174
+ /**
1175
+ * `disconnect` / `delete` on a belongsTo relation, with Prisma's semantics:
1176
+ * both clear the parent's foreign key, and `delete` then deletes the row it
1177
+ * pointed at. `true` (or `{}`) means the connected row; an object with fields
1178
+ * additionally requires the connected row to match it. Both need a nullable
1179
+ * foreign key: with a NOT NULL one the parent cannot exist pointing at nothing.
1180
+ *
1181
+ * `disconnect: true` when nothing is connected is a no-op: the parent already
1182
+ * points at nothing, which is what was asked. A `delete`, or a `disconnect`
1183
+ * that names fields, asks about a row that is not there, so it is E001.
1184
+ */
1185
+ async function processBelongsToDisconnect(ctx, rel, arg, relName, parentRow, parentTable, op) {
1186
+ const parentMeta = ctx.schema.tables[parentTable];
1187
+ const fks = (0, schema_js_1.normalizeKeyColumns)(rel.foreignKey);
1188
+ const nullable = fks.every((fk) => parentMeta.columns.find((c) => c.name === fk)?.nullable ?? false);
1189
+ if (!nullable) {
1190
+ throw new errors_js_1.ValidationError(`Cannot ${op} "${relName}": foreign key column(s) ${fks.join(', ')} on "${parentTable}" are NOT NULL, so ` +
1191
+ `every "${parentTable}" row must point at a "${rel.to}". Connect it to a different "${rel.to}" instead` +
1192
+ `${op === 'delete' ? `, or delete the "${rel.to}" row directly once nothing points at it` : ''}.`);
1193
+ }
1194
+ const item = singleToOneItem(arg, op, relName, rel);
1195
+ // `{}` is the documented to-one spelling of `true` (the connected row). An
1196
+ // object whose keys are all undefined is still refused below: that is the
1197
+ // shape of a missing request field, not a choice.
1198
+ const target = (0, filters_js_1.isPlainRecord)(item) && Object.keys(item).length === 0 ? true : item;
1199
+ assertTargetSelectsSomething(target, op, relName, rel);
1200
+ const correlation = belongsToCorrelationWhere(ctx, rel, parentRow, parentTable);
1201
+ if (!correlation) {
1202
+ if (op === 'disconnect' && target === true)
1203
+ return;
1204
+ throw nothingConnected(op, relName, rel, target);
1205
+ }
1206
+ const related = (await ctx.tx.table(rel.to).findFirst({ where: scopeWhereToParent(target, correlation) }));
1207
+ if (!related)
1208
+ throw notRelatedToParent(op, relName, rel, target);
1209
+ await ctx.tx.table(parentTable).update({
1210
+ where: pkWhere(parentMeta, parentRow),
1211
+ data: belongsToKeyData(ctx, rel, parentTable, null),
1212
+ });
1213
+ if (op === 'delete') {
1214
+ // Any other row still pointing at it makes this a foreign-key violation
1215
+ // (E009), which rolls the whole write back.
1216
+ await ctx.tx.table(rel.to).delete({ where: pkWhere(ctx.schema.tables[rel.to], related) });
1167
1217
  }
1168
1218
  }
1169
1219
  // ---------------------------------------------------------------------------
@@ -1374,11 +1424,12 @@ async function processNestedUpsert(ctx, rel, upsertArg, parentRow, relName) {
1374
1424
  throw new errors_js_1.ValidationError(`Nested upsert on "${rel.name}" requires "where", "create", and "update" fields.`);
1375
1425
  }
1376
1426
  assertTargetSelectsSomething(item.where, 'upsert', relName, rel);
1427
+ const update = upsertUpdateData(ctx, rel, item);
1377
1428
  const scoped = correlation ? scopeWhereToParent(item.where, correlation) : null;
1378
1429
  const existing = scoped ? await ctx.tx.table(rel.to).findUnique({ where: scoped }) : null;
1379
1430
  if (existing && scoped) {
1380
1431
  try {
1381
- await ctx.tx.table(rel.to).update({ where: scoped, data: item.update });
1432
+ await ctx.tx.table(rel.to).update({ where: scoped, data: update });
1382
1433
  }
1383
1434
  catch (err) {
1384
1435
  rethrowAsNotRelated(err, 'upsert', relName, rel, item.where);
@@ -1390,6 +1441,19 @@ async function processNestedUpsert(ctx, rel, upsertArg, parentRow, relName) {
1390
1441
  }
1391
1442
  }
1392
1443
  }
1444
+ /**
1445
+ * A nested upsert's `update`, with each `expr.excluded(f)` resolved to the
1446
+ * value its `create` supplies: the incoming row of a one-row upsert is
1447
+ * `create`, as it is for a top-level `upsert()`. Resolved before the lookup,
1448
+ * so a reference `create` cannot satisfy is refused whether or not the row
1449
+ * exists.
1450
+ */
1451
+ function upsertUpdateData(ctx, rel, item) {
1452
+ const tableMeta = ctx.schema.tables[rel.to];
1453
+ if (!tableMeta)
1454
+ return item.update;
1455
+ return (0, expr_js_1.resolveExcludedFromCreate)({ table: rel.to, tableMeta }, item.update, item.create) ?? item.update;
1456
+ }
1393
1457
  async function processBelongsToUpdate(ctx, rel, updateArg, parentRow, parentTable) {
1394
1458
  const item = updateArg;
1395
1459
  if (!item.data) {
@@ -1428,6 +1492,7 @@ async function processBelongsToUpsert(ctx, rel, upsertArg, parentRow, parentTabl
1428
1492
  // hasMany/hasOne nested upsert makes: an upsert whose target is out of the
1429
1493
  // relation creates a row owned by this parent rather than rewriting a
1430
1494
  // stranger's.
1495
+ const update = upsertUpdateData(ctx, rel, item);
1431
1496
  const correlation = belongsToCorrelationWhere(ctx, rel, parentRow, parentTable);
1432
1497
  // findMany, not findUnique: the scoped where ANDs a non-unique-looking
1433
1498
  // correlation onto the caller's selector. The correlation targets the
@@ -1442,7 +1507,7 @@ async function processBelongsToUpsert(ctx, rel, upsertArg, parentRow, parentTabl
1442
1507
  : null;
1443
1508
  if (existing) {
1444
1509
  const relatedMeta = ctx.schema.tables[rel.to];
1445
- await ctx.tx.table(rel.to).update({ where: pkWhere(relatedMeta, existing), data: item.update });
1510
+ await ctx.tx.table(rel.to).update({ where: pkWhere(relatedMeta, existing), data: update });
1446
1511
  }
1447
1512
  else {
1448
1513
  // Create the related row, then update parent's FK to point at it
@@ -65,11 +65,23 @@ export interface PipelineRunOptions {
65
65
  * Whether to wrap the pipeline in BEGIN/COMMIT (default: true).
66
66
  * When true, all queries execute atomically. On error, ROLLBACK is sent.
67
67
  * When false, each query gets its own Sync message for error isolation.
68
+ *
69
+ * A transactional batch that may write holds its COMMIT until every
70
+ * query's transform has run, so a transform that throws (a `...OrThrow`
71
+ * miss, an optimistic-lock conflict) rolls the writes back instead of
72
+ * rejecting after they committed. That costs a second round trip; a batch
73
+ * of plain SELECTs has nothing to undo and keeps the single one.
68
74
  */
69
75
  transactional?: boolean;
70
76
  /** Timeout in milliseconds. If exceeded, the connection is destroyed. */
71
77
  timeout?: number;
72
78
  }
79
+ /**
80
+ * Whether a transactional batch must hold its COMMIT until the transforms have
81
+ * run. Anything that is not a plain SELECT counts as a write, so a statement
82
+ * this cannot classify costs a round trip rather than correctness.
83
+ */
84
+ export declare function batchMayWrite(queries: readonly DeferredQuery<unknown>[]): boolean;
73
85
  /**
74
86
  * Execute multiple queries using the Postgres extended-query pipeline protocol.
75
87
  *