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.
- package/README.md +26 -25
- package/dist/checkout.d.ts +27 -2
- package/dist/checkout.js +75 -4
- package/dist/cjs/checkout.d.ts +27 -2
- package/dist/cjs/checkout.js +76 -3
- package/dist/cjs/cli/code-first-tags.d.ts +62 -0
- package/dist/cjs/cli/code-first-tags.js +124 -0
- package/dist/cjs/cli/compile-query.js +9 -0
- package/dist/cjs/cli/config.d.ts +35 -6
- package/dist/cjs/cli/config.js +17 -1
- package/dist/cjs/cli/destructive.d.ts +7 -4
- package/dist/cjs/cli/destructive.js +224 -20
- package/dist/cjs/cli/error-catalog.js +36 -0
- package/dist/cjs/cli/error-text.d.ts +15 -0
- package/dist/cjs/cli/error-text.js +40 -0
- package/dist/cjs/cli/index.d.ts +62 -4
- package/dist/cjs/cli/index.js +568 -108
- package/dist/cjs/cli/local-server-guard.d.ts +53 -0
- package/dist/cjs/cli/local-server-guard.js +119 -0
- package/dist/cjs/cli/local-server-output.d.ts +41 -0
- package/dist/cjs/cli/local-server-output.js +103 -0
- package/dist/cjs/cli/mcp.d.ts +8 -0
- package/dist/cjs/cli/mcp.js +121 -32
- package/dist/cjs/cli/migrate.d.ts +113 -5
- package/dist/cjs/cli/migrate.js +270 -21
- package/dist/cjs/cli/observe.d.ts +6 -1
- package/dist/cjs/cli/observe.js +85 -55
- package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cjs/cli/pii-predicate-guard.js +98 -37
- package/dist/cjs/cli/prisma-resolve.js +17 -13
- package/dist/cjs/cli/prisma-schema.js +12 -4
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.d.ts +14 -2
- package/dist/cjs/cli/studio.js +244 -69
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +558 -85
- package/dist/cjs/client.js +1270 -395
- package/dist/cjs/connection-guard.d.ts +13 -0
- package/dist/cjs/connection-guard.js +28 -0
- package/dist/cjs/connection-url.d.ts +51 -16
- package/dist/cjs/connection-url.js +67 -34
- package/dist/cjs/diagnostics.d.ts +81 -0
- package/dist/cjs/diagnostics.js +147 -0
- package/dist/cjs/dialect.d.ts +449 -20
- package/dist/cjs/dialect.js +117 -76
- package/dist/cjs/engine-config.d.ts +7 -6
- package/dist/cjs/errors.d.ts +126 -6
- package/dist/cjs/errors.js +256 -13
- package/dist/cjs/generate.d.ts +57 -7
- package/dist/cjs/generate.js +635 -279
- package/dist/cjs/global-filters-config.d.ts +59 -0
- package/dist/cjs/global-filters-config.js +367 -0
- package/dist/cjs/index.d.ts +9 -4
- package/dist/cjs/index.js +10 -2
- package/dist/cjs/introspect.d.ts +31 -6
- package/dist/cjs/introspect.js +108 -50
- package/dist/cjs/log-codes.d.ts +12 -0
- package/dist/cjs/log-codes.js +77 -0
- package/dist/cjs/logger.d.ts +87 -0
- package/dist/cjs/logger.js +98 -0
- package/dist/cjs/mssql.d.ts +8 -5
- package/dist/cjs/mssql.js +352 -65
- package/dist/cjs/mysql.d.ts +3 -5
- package/dist/cjs/mysql.js +420 -70
- package/dist/cjs/nested-write.d.ts +3 -0
- package/dist/cjs/nested-write.js +179 -114
- package/dist/cjs/pipeline-submittable.d.ts +12 -0
- package/dist/cjs/pipeline-submittable.js +118 -44
- package/dist/cjs/pipeline.js +21 -26
- package/dist/cjs/powdb-introspect.d.ts +5 -2
- package/dist/cjs/powdb-introspect.js +20 -15
- package/dist/cjs/powdb-shared.d.ts +36 -5
- package/dist/cjs/powdb-shared.js +67 -8
- package/dist/cjs/powdb.d.ts +4 -0
- package/dist/cjs/powdb.js +183 -51
- package/dist/cjs/powql.d.ts +394 -51
- package/dist/cjs/powql.js +1829 -284
- package/dist/cjs/prisma-compat.d.ts +23 -15
- package/dist/cjs/prisma-compat.js +507 -372
- package/dist/cjs/query/aggregates.d.ts +17 -1
- package/dist/cjs/query/aggregates.js +261 -29
- package/dist/cjs/query/batched-loader.d.ts +25 -6
- package/dist/cjs/query/batched-loader.js +103 -20
- package/dist/cjs/query/builder.d.ts +325 -184
- package/dist/cjs/query/builder.js +1452 -490
- package/dist/cjs/query/column-ref.d.ts +61 -0
- package/dist/cjs/query/column-ref.js +73 -0
- package/dist/cjs/query/compound-unique.d.ts +21 -0
- package/dist/cjs/query/compound-unique.js +98 -18
- package/dist/cjs/query/deferred.d.ts +53 -16
- package/dist/cjs/query/expr.d.ts +217 -0
- package/dist/cjs/query/expr.js +859 -0
- package/dist/cjs/query/filters.d.ts +155 -53
- package/dist/cjs/query/filters.js +613 -76
- package/dist/cjs/query/index.d.ts +2 -0
- package/dist/cjs/query/index.js +7 -1
- package/dist/cjs/query/json-value.d.ts +43 -0
- package/dist/cjs/query/json-value.js +59 -0
- package/dist/cjs/query/normalize-args.d.ts +87 -0
- package/dist/cjs/query/normalize-args.js +527 -0
- package/dist/cjs/query/option-surface.d.ts +16 -4
- package/dist/cjs/query/option-surface.js +57 -14
- package/dist/cjs/query/relation-names.d.ts +14 -0
- package/dist/cjs/query/relation-names.js +77 -18
- package/dist/cjs/query/relations.d.ts +30 -3
- package/dist/cjs/query/relations.js +219 -63
- package/dist/cjs/query/row-lock.d.ts +49 -0
- package/dist/cjs/query/row-lock.js +138 -0
- package/dist/cjs/query/sql-comment.d.ts +161 -0
- package/dist/cjs/query/sql-comment.js +381 -0
- package/dist/cjs/query/stream-idle.d.ts +132 -0
- package/dist/cjs/query/stream-idle.js +212 -0
- package/dist/cjs/query/table-client.d.ts +101 -0
- package/dist/cjs/query/table-client.js +74 -0
- package/dist/cjs/query/types.d.ts +677 -298
- package/dist/cjs/query/utils.d.ts +105 -6
- package/dist/cjs/query/utils.js +426 -19
- package/dist/cjs/query/warn-registry.d.ts +18 -0
- package/dist/cjs/query/warn-registry.js +18 -0
- package/dist/cjs/query/where-compile.d.ts +138 -14
- package/dist/cjs/query/where-compile.js +513 -29
- package/dist/cjs/query/where.d.ts +159 -59
- package/dist/cjs/query/where.js +716 -246
- package/dist/cjs/query/writes.d.ts +112 -50
- package/dist/cjs/query/writes.js +1498 -200
- package/dist/cjs/query-events.d.ts +4 -2
- package/dist/cjs/query-events.js +3 -1
- package/dist/cjs/realtime.d.ts +7 -3
- package/dist/cjs/realtime.js +13 -2
- package/dist/cjs/schema-builder.d.ts +55 -4
- package/dist/cjs/schema-builder.js +382 -18
- package/dist/cjs/schema-metadata.d.ts +21 -1
- package/dist/cjs/schema-metadata.js +29 -7
- package/dist/cjs/schema-sql.d.ts +75 -5
- package/dist/cjs/schema-sql.js +497 -45
- package/dist/cjs/schema.d.ts +26 -5
- package/dist/cjs/schema.js +110 -13
- package/dist/cjs/sql-placeholders.d.ts +56 -0
- package/dist/cjs/sql-placeholders.js +204 -0
- package/dist/cjs/sqlite.d.ts +75 -20
- package/dist/cjs/sqlite.js +593 -95
- package/dist/cjs/supplied-pool-types.d.ts +48 -0
- package/dist/cjs/supplied-pool-types.js +256 -0
- package/dist/cjs/transaction-control.d.ts +51 -0
- package/dist/cjs/transaction-control.js +316 -0
- package/dist/cjs/transaction-state.d.ts +342 -0
- package/dist/cjs/transaction-state.js +707 -0
- package/dist/cjs/typed-sql.d.ts +37 -3
- package/dist/cjs/typed-sql.js +102 -8
- package/dist/cli/code-first-tags.d.ts +62 -0
- package/dist/cli/code-first-tags.js +120 -0
- package/dist/cli/compile-query.js +9 -0
- package/dist/cli/config.d.ts +35 -6
- package/dist/cli/config.js +16 -1
- package/dist/cli/destructive.d.ts +7 -4
- package/dist/cli/destructive.js +224 -20
- package/dist/cli/error-catalog.js +36 -0
- package/dist/cli/error-text.d.ts +15 -0
- package/dist/cli/error-text.js +37 -0
- package/dist/cli/index.d.ts +62 -4
- package/dist/cli/index.js +569 -112
- package/dist/cli/local-server-guard.d.ts +53 -0
- package/dist/cli/local-server-guard.js +113 -0
- package/dist/cli/local-server-output.d.ts +41 -0
- package/dist/cli/local-server-output.js +98 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +121 -34
- package/dist/cli/migrate.d.ts +113 -5
- package/dist/cli/migrate.js +261 -21
- package/dist/cli/observe.d.ts +6 -1
- package/dist/cli/observe.js +84 -54
- package/dist/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cli/pii-predicate-guard.js +100 -35
- package/dist/cli/prisma-resolve.js +15 -11
- package/dist/cli/prisma-schema.js +12 -4
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +14 -2
- package/dist/cli/studio.js +246 -71
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +558 -85
- package/dist/client.js +1275 -400
- package/dist/connection-guard.d.ts +13 -0
- package/dist/connection-guard.js +27 -0
- package/dist/connection-url.d.ts +51 -16
- package/dist/connection-url.js +63 -34
- package/dist/diagnostics.d.ts +81 -0
- package/dist/diagnostics.js +144 -0
- package/dist/dialect.d.ts +449 -20
- package/dist/dialect.js +115 -43
- package/dist/engine-config.d.ts +7 -6
- package/dist/errors.d.ts +126 -6
- package/dist/errors.js +252 -12
- package/dist/generate.d.ts +57 -7
- package/dist/generate.js +635 -281
- package/dist/global-filters-config.d.ts +59 -0
- package/dist/global-filters-config.js +361 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -2
- package/dist/introspect.d.ts +31 -6
- package/dist/introspect.js +106 -50
- package/dist/log-codes.d.ts +12 -0
- package/dist/log-codes.js +74 -0
- package/dist/logger.d.ts +87 -0
- package/dist/logger.js +93 -0
- package/dist/mssql.d.ts +8 -5
- package/dist/mssql.js +354 -67
- package/dist/mysql.d.ts +3 -5
- package/dist/mysql.js +423 -73
- package/dist/nested-write.d.ts +3 -0
- package/dist/nested-write.js +177 -112
- package/dist/pipeline-submittable.d.ts +12 -0
- package/dist/pipeline-submittable.js +117 -44
- package/dist/pipeline.js +21 -26
- package/dist/powdb-introspect.d.ts +5 -2
- package/dist/powdb-introspect.js +21 -16
- package/dist/powdb-shared.d.ts +36 -5
- package/dist/powdb-shared.js +65 -8
- package/dist/powdb.d.ts +4 -0
- package/dist/powdb.js +183 -51
- package/dist/powql.d.ts +394 -51
- package/dist/powql.js +1836 -291
- package/dist/prisma-compat.d.ts +23 -15
- package/dist/prisma-compat.js +508 -373
- package/dist/query/aggregates.d.ts +17 -1
- package/dist/query/aggregates.js +263 -32
- package/dist/query/batched-loader.d.ts +25 -6
- package/dist/query/batched-loader.js +101 -21
- package/dist/query/builder.d.ts +325 -184
- package/dist/query/builder.js +1458 -495
- package/dist/query/column-ref.d.ts +61 -0
- package/dist/query/column-ref.js +66 -0
- package/dist/query/compound-unique.d.ts +21 -0
- package/dist/query/compound-unique.js +97 -19
- package/dist/query/deferred.d.ts +53 -16
- package/dist/query/expr.d.ts +217 -0
- package/dist/query/expr.js +848 -0
- package/dist/query/filters.d.ts +155 -53
- package/dist/query/filters.js +597 -72
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +4 -0
- package/dist/query/json-value.d.ts +43 -0
- package/dist/query/json-value.js +54 -0
- package/dist/query/normalize-args.d.ts +87 -0
- package/dist/query/normalize-args.js +517 -0
- package/dist/query/option-surface.d.ts +16 -4
- package/dist/query/option-surface.js +56 -13
- package/dist/query/relation-names.d.ts +14 -0
- package/dist/query/relation-names.js +77 -19
- package/dist/query/relations.d.ts +30 -3
- package/dist/query/relations.js +220 -67
- package/dist/query/row-lock.d.ts +49 -0
- package/dist/query/row-lock.js +131 -0
- package/dist/query/sql-comment.d.ts +161 -0
- package/dist/query/sql-comment.js +374 -0
- package/dist/query/stream-idle.d.ts +132 -0
- package/dist/query/stream-idle.js +203 -0
- package/dist/query/table-client.d.ts +101 -0
- package/dist/query/table-client.js +70 -0
- package/dist/query/types.d.ts +677 -298
- package/dist/query/utils.d.ts +105 -6
- package/dist/query/utils.js +414 -18
- package/dist/query/warn-registry.d.ts +18 -0
- package/dist/query/warn-registry.js +18 -0
- package/dist/query/where-compile.d.ts +138 -14
- package/dist/query/where-compile.js +508 -31
- package/dist/query/where.d.ts +159 -59
- package/dist/query/where.js +711 -248
- package/dist/query/writes.d.ts +112 -50
- package/dist/query/writes.js +1496 -202
- package/dist/query-events.d.ts +4 -2
- package/dist/query-events.js +3 -1
- package/dist/realtime.d.ts +7 -3
- package/dist/realtime.js +13 -2
- package/dist/schema-builder.d.ts +55 -4
- package/dist/schema-builder.js +381 -18
- package/dist/schema-metadata.d.ts +21 -1
- package/dist/schema-metadata.js +28 -7
- package/dist/schema-sql.d.ts +75 -5
- package/dist/schema-sql.js +494 -44
- package/dist/schema.d.ts +26 -5
- package/dist/schema.js +109 -13
- package/dist/sql-placeholders.d.ts +56 -0
- package/dist/sql-placeholders.js +200 -0
- package/dist/sqlite.d.ts +75 -20
- package/dist/sqlite.js +597 -99
- package/dist/supplied-pool-types.d.ts +48 -0
- package/dist/supplied-pool-types.js +247 -0
- package/dist/transaction-control.d.ts +51 -0
- package/dist/transaction-control.js +311 -0
- package/dist/transaction-state.d.ts +342 -0
- package/dist/transaction-state.js +696 -0
- package/dist/typed-sql.d.ts +37 -3
- package/dist/typed-sql.js +100 -8
- package/package.json +7 -4
- package/skills/turbine-orm/SKILL.md +56 -6
package/dist/nested-write.d.ts
CHANGED
package/dist/nested-write.js
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { CircularRelationError, describeTargetForMessage, NotFoundError, RelationError, UnsupportedFeatureError, ValidationError, } from './errors.js';
|
|
15
15
|
import { markInternalRowSelector } from './query/compound-unique.js';
|
|
16
|
+
import { resolveExcludedFromCreate } from './query/expr.js';
|
|
17
|
+
import { isPlainRecord, setOwn } from './query/filters.js';
|
|
16
18
|
import { markInternalCombinator, resolveColumnName } from './query/utils.js';
|
|
17
19
|
import { normalizeKeyColumns } from './schema.js';
|
|
18
20
|
const MAX_DEPTH = 10;
|
|
@@ -42,7 +44,7 @@ export function extractRelationFields(data, tableMeta) {
|
|
|
42
44
|
relations[key] = value;
|
|
43
45
|
}
|
|
44
46
|
else {
|
|
45
|
-
scalars
|
|
47
|
+
setOwn(scalars, key, value);
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
return { scalars, relations };
|
|
@@ -208,32 +210,73 @@ function toArray(value) {
|
|
|
208
210
|
function keyOf(value) {
|
|
209
211
|
return String(value);
|
|
210
212
|
}
|
|
213
|
+
/** The operations that point a to-one relation's foreign key at a row: at most one per write. */
|
|
214
|
+
const BELONGS_TO_POINTER_OPS = ['create', 'connect', 'connectOrCreate', 'disconnect', 'delete'];
|
|
211
215
|
/**
|
|
212
216
|
* Validate that all operation keys in a nested write are recognized and
|
|
213
|
-
* allowed for the current context (create vs update).
|
|
217
|
+
* allowed for the current context (create vs update) and relation.
|
|
218
|
+
*
|
|
219
|
+
* A belongsTo relation is one row the parent points at, so it takes Prisma's
|
|
220
|
+
* to-one operations: no `set` (a to-many operation), and at most one of the
|
|
221
|
+
* operations that decide what the parent points at, since one write cannot
|
|
222
|
+
* honor two. An operation whose value is `undefined` is absent, as everywhere
|
|
223
|
+
* else in a query; only its name is still checked, so a misspelled operation
|
|
224
|
+
* is reported whatever it holds.
|
|
214
225
|
*/
|
|
215
|
-
function validateOps(relationName, ops, isUpdate) {
|
|
226
|
+
function validateOps(relationName, ops, isUpdate, rel) {
|
|
227
|
+
const belongsTo = rel?.type === 'belongsTo';
|
|
216
228
|
for (const opName of Object.keys(ops)) {
|
|
229
|
+
const absent = ops[opName] === undefined;
|
|
217
230
|
if (!CREATE_ONLY_OPS.has(opName) && !UPDATE_ONLY_OPS.has(opName)) {
|
|
231
|
+
const updateOps = belongsTo
|
|
232
|
+
? ', disconnect, delete, update, upsert'
|
|
233
|
+
: ', disconnect, set, delete, update, upsert';
|
|
218
234
|
throw new ValidationError(`Unknown nested write operation "${opName}" on relation "${relationName}". ` +
|
|
219
|
-
`Valid operations: create, connect, connectOrCreate${isUpdate ?
|
|
235
|
+
`Valid operations: create, connect, connectOrCreate${isUpdate ? updateOps : ''}.`);
|
|
220
236
|
}
|
|
237
|
+
if (absent)
|
|
238
|
+
continue;
|
|
221
239
|
if (!isUpdate && UPDATE_ONLY_OPS.has(opName)) {
|
|
222
240
|
throw new ValidationError(`Operation "${opName}" on relation "${relationName}" is only valid inside update(), not create().`);
|
|
223
241
|
}
|
|
242
|
+
if (belongsTo && opName === 'set') {
|
|
243
|
+
throw new ValidationError(`Nested set on "${relationName}" is not supported: "${relationName}" is a single "${rel.to}" row this ` +
|
|
244
|
+
`record points at, and set replaces a to-many list. Use connect to point at a different "${rel.to}", ` +
|
|
245
|
+
'or disconnect to clear it.');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (belongsTo) {
|
|
249
|
+
const given = BELONGS_TO_POINTER_OPS.filter((op) => ops[op] !== undefined && ops[op] !== false);
|
|
250
|
+
if (given.length > 1) {
|
|
251
|
+
throw new ValidationError(`Nested write on "${relationName}" combines ${given.join(' and ')}. "${relationName}" is a single ` +
|
|
252
|
+
`"${rel.to}" row, so one write can create, connect, connectOrCreate, disconnect or delete it, not ` +
|
|
253
|
+
'several. Split the write in two if both are intended.');
|
|
254
|
+
}
|
|
224
255
|
}
|
|
225
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* The single item a to-one operation names. A one-element array is accepted
|
|
259
|
+
* for compatibility; an empty or longer one is refused, since only one row can
|
|
260
|
+
* be pointed at and keeping one of several would drop the rest silently.
|
|
261
|
+
*/
|
|
262
|
+
function singleToOneItem(value, op, relName, rel) {
|
|
263
|
+
if (!Array.isArray(value))
|
|
264
|
+
return value;
|
|
265
|
+
if (value.length === 1)
|
|
266
|
+
return value[0];
|
|
267
|
+
throw new ValidationError(`Nested ${op} on "${relName}" received ${value.length} items, but "${relName}" is a single "${rel.to}" ` +
|
|
268
|
+
`row. Pass one object, for example \`${op}: { ... }\`.`);
|
|
269
|
+
}
|
|
226
270
|
/**
|
|
227
271
|
* The `with` clause that reads back the tree a nested write just wrote.
|
|
228
272
|
*
|
|
229
273
|
* Built from the DATA, recursively: a relation whose payload itself contains
|
|
230
274
|
* relation writes is requested as `{ rel: { with: { ... } } }` rather than
|
|
231
|
-
* `{ rel: true }`.
|
|
232
|
-
*
|
|
233
|
-
* missing entirely, which a caller reads as "there are none".
|
|
275
|
+
* `{ rel: true }`. Requesting the top-level keys alone would return a depth-3
|
|
276
|
+
* create without its grandchildren, which a caller reads as "there are none".
|
|
234
277
|
*
|
|
235
278
|
* Only relations that were actually WRITTEN are requested, so a create with no
|
|
236
|
-
* nested data
|
|
279
|
+
* nested data reads back just the row. The walk is capped
|
|
237
280
|
* at {@link MAX_DEPTH}, the cap the write walk itself uses, and the builder's
|
|
238
281
|
* own relation-depth guard is the backstop below that.
|
|
239
282
|
*/
|
|
@@ -278,10 +321,6 @@ function readBackWith(schema, tableName, relations, depth = 0) {
|
|
|
278
321
|
}
|
|
279
322
|
return Object.keys(clause).length > 0 ? clause : undefined;
|
|
280
323
|
}
|
|
281
|
-
/** A plain object (not an array, Date or null), the shape nested payloads take. */
|
|
282
|
-
function isPlainRecord(value) {
|
|
283
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
284
|
-
}
|
|
285
324
|
/**
|
|
286
325
|
* Build a PK-based where clause from a parent row and its table metadata.
|
|
287
326
|
*/
|
|
@@ -813,6 +852,17 @@ function notRelatedToParent(op, relName, rel, target) {
|
|
|
813
852
|
`or it belongs to a different parent (a nested ${op} can only touch this parent's rows).`,
|
|
814
853
|
});
|
|
815
854
|
}
|
|
855
|
+
/** A belongsTo `delete` (or a `disconnect` naming fields) on a parent whose foreign key is NULL. */
|
|
856
|
+
function nothingConnected(op, relName, rel, target) {
|
|
857
|
+
return new NotFoundError({
|
|
858
|
+
table: rel.to,
|
|
859
|
+
where: target,
|
|
860
|
+
operation: `nested ${op}`,
|
|
861
|
+
message: `Nested ${op} on relation "${relName}": nothing is connected (this record's foreign key to "${rel.to}" ` +
|
|
862
|
+
`is NULL), so there is no "${rel.to}" row to ${op}.` +
|
|
863
|
+
(op === 'disconnect' ? ' Use `disconnect: true` to clear the relation whatever it points at.' : ''),
|
|
864
|
+
});
|
|
865
|
+
}
|
|
816
866
|
/** Re-tag a NotFoundError from a scoped child write as a relation-scoped miss. */
|
|
817
867
|
function rethrowAsNotRelated(err, op, relName, rel, target) {
|
|
818
868
|
if (err instanceof NotFoundError) {
|
|
@@ -844,7 +894,7 @@ export async function executeNestedCreate(ctx, tableName, data, depth = 0, path
|
|
|
844
894
|
throw new RelationError(`Unknown relation "${relName}" on table "${tableName}". ` +
|
|
845
895
|
`Available relations: ${Object.keys(tableMeta.relations).join(', ') || '(none)'}.`);
|
|
846
896
|
}
|
|
847
|
-
validateOps(relName, ops, false);
|
|
897
|
+
validateOps(relName, ops, false, rel);
|
|
848
898
|
}
|
|
849
899
|
// belongsTo relations put the foreign key on the PARENT row, so they must be
|
|
850
900
|
// resolved BEFORE the parent is inserted, otherwise a NOT NULL FK column
|
|
@@ -922,7 +972,7 @@ export async function executeNestedUpdate(ctx, tableName, where, data, depth = 0
|
|
|
922
972
|
throw new RelationError(`Unknown relation "${relName}" on table "${tableName}". ` +
|
|
923
973
|
`Available relations: ${Object.keys(tableMeta.relations).join(', ') || '(none)'}.`);
|
|
924
974
|
}
|
|
925
|
-
validateOps(relName, ops, true);
|
|
975
|
+
validateOps(relName, ops, true, rel);
|
|
926
976
|
}
|
|
927
977
|
// Update parent row with scalar data (may be empty if only relation ops)
|
|
928
978
|
let parentRow;
|
|
@@ -972,25 +1022,11 @@ export async function executeNestedUpdate(ctx, tableName, where, data, depth = 0
|
|
|
972
1022
|
if (ops.upsert !== undefined) {
|
|
973
1023
|
await processBelongsToUpsert(ctx, rel, ops.upsert, parentRow, tableName);
|
|
974
1024
|
}
|
|
975
|
-
if (ops.disconnect !== undefined) {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
return col?.nullable ?? false;
|
|
981
|
-
});
|
|
982
|
-
if (!nullable) {
|
|
983
|
-
throw new ValidationError(`Cannot disconnect "${relName}": foreign key column(s) ${fks.join(', ')} are NOT NULL. Use delete instead.`);
|
|
984
|
-
}
|
|
985
|
-
const updateData = {};
|
|
986
|
-
for (const fk of fks) {
|
|
987
|
-
const field = tableMeta.reverseColumnMap[fk] ?? fk;
|
|
988
|
-
updateData[field] = null;
|
|
989
|
-
}
|
|
990
|
-
await ctx.tx.table(tableName).update({
|
|
991
|
-
where: pkWhere(tableMeta, parentRow),
|
|
992
|
-
data: updateData,
|
|
993
|
-
});
|
|
1025
|
+
if (ops.disconnect !== undefined && ops.disconnect !== false) {
|
|
1026
|
+
await processBelongsToDisconnect(ctx, rel, ops.disconnect, relName, parentRow, tableName, 'disconnect');
|
|
1027
|
+
}
|
|
1028
|
+
if (ops.delete !== undefined && ops.delete !== false) {
|
|
1029
|
+
await processBelongsToDisconnect(ctx, rel, ops.delete, relName, parentRow, tableName, 'delete');
|
|
994
1030
|
}
|
|
995
1031
|
}
|
|
996
1032
|
else {
|
|
@@ -1069,93 +1105,107 @@ async function processHasManyCreate(ctx, rel, ops, parentRow, depth, path, relNa
|
|
|
1069
1105
|
* which UPDATEs the FK after the parent already exists.)
|
|
1070
1106
|
*/
|
|
1071
1107
|
async function resolveBelongsToForCreate(ctx, rel, ops, parentTable, depth, path, relName) {
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1108
|
+
const relatedRow = await resolveBelongsToTarget(ctx, rel, ops, depth, path, relName);
|
|
1109
|
+
return relatedRow ? belongsToKeyData(ctx, rel, parentTable, relatedRow) : {};
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* The row a belongsTo create / connect / connectOrCreate points the parent at,
|
|
1113
|
+
* or null when the write names none of the three. Shared by the create path
|
|
1114
|
+
* (which folds the key into the parent INSERT) and the update path (which
|
|
1115
|
+
* UPDATEs the parent's key), so the two cannot disagree about any of them.
|
|
1116
|
+
*/
|
|
1117
|
+
async function resolveBelongsToTarget(ctx, rel, ops, depth, path, relName) {
|
|
1077
1118
|
if (ops.create !== undefined) {
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
relatedRow = (await executeNestedCreate(ctx, rel.to, items[0], depth + 1, [...path, relName]));
|
|
1081
|
-
}
|
|
1119
|
+
const item = singleToOneItem(ops.create, 'create', relName, rel);
|
|
1120
|
+
return (await executeNestedCreate(ctx, rel.to, item, depth + 1, [...path, relName]));
|
|
1082
1121
|
}
|
|
1083
|
-
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
if (!relatedRow) {
|
|
1089
|
-
throw new ValidationError(`connect on "${relName}": no ${rel.to} row found matching ${describeTargetForMessage(target)}.`);
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
else if (ops.connectOrCreate !== undefined) {
|
|
1094
|
-
const items = toArray(ops.connectOrCreate);
|
|
1095
|
-
if (items.length > 0) {
|
|
1096
|
-
const op = items[0];
|
|
1097
|
-
relatedRow = (await ctx.tx.table(rel.to).findUnique({ where: op.where }));
|
|
1098
|
-
if (!relatedRow) {
|
|
1099
|
-
// For belongsTo the FK lives on the parent, so the related row is
|
|
1100
|
-
// created plainly (no FK injection) and we read its reference key.
|
|
1101
|
-
relatedRow = (await ctx.tx.table(rel.to).create({ data: op.create }));
|
|
1102
|
-
}
|
|
1122
|
+
if (ops.connect !== undefined) {
|
|
1123
|
+
const target = singleToOneItem(ops.connect, 'connect', relName, rel);
|
|
1124
|
+
const found = (await ctx.tx.table(rel.to).findUnique({ where: target }));
|
|
1125
|
+
if (!found) {
|
|
1126
|
+
throw new ValidationError(`connect on "${relName}": no ${rel.to} row found matching ${describeTargetForMessage(target)}.`);
|
|
1103
1127
|
}
|
|
1128
|
+
return found;
|
|
1104
1129
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
const refField = relatedTable?.reverseColumnMap[refs[i]] ?? refs[i];
|
|
1110
|
-
fkScalars[fkField] = relatedRow[refField];
|
|
1130
|
+
if (ops.connectOrCreate !== undefined) {
|
|
1131
|
+
const op = singleToOneItem(ops.connectOrCreate, 'connectOrCreate', relName, rel);
|
|
1132
|
+
if (!isPlainRecord(op?.where) || !isPlainRecord(op?.create)) {
|
|
1133
|
+
throw new ValidationError(`Nested connectOrCreate on "${relName}" requires "where" and "create" fields.`);
|
|
1111
1134
|
}
|
|
1135
|
+
const found = (await ctx.tx.table(rel.to).findUnique({ where: op.where }));
|
|
1136
|
+
// For belongsTo the FK lives on the parent, so the related row is created
|
|
1137
|
+
// plainly (no FK injection) and its reference key is read back.
|
|
1138
|
+
return found ?? (await ctx.tx.table(rel.to).create({ data: op.create }));
|
|
1112
1139
|
}
|
|
1113
|
-
return
|
|
1140
|
+
return null;
|
|
1114
1141
|
}
|
|
1115
|
-
|
|
1142
|
+
/** The parent's own field values that point it at `relatedRow`. */
|
|
1143
|
+
function belongsToKeyData(ctx, rel, parentTable, relatedRow) {
|
|
1116
1144
|
const fks = normalizeKeyColumns(rel.foreignKey);
|
|
1117
1145
|
const refs = normalizeKeyColumns(rel.referenceKey);
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
for (let i = 0; i < fks.length; i++) {
|
|
1126
|
-
const fkField = ctx.schema.tables[parentTable]?.reverseColumnMap[fks[i]] ?? fks[i];
|
|
1127
|
-
const refField = relatedTable?.reverseColumnMap[refs[i]] ?? refs[i];
|
|
1128
|
-
updateData[fkField] = relatedRow[refField];
|
|
1129
|
-
}
|
|
1130
|
-
const parentMeta = ctx.schema.tables[parentTable];
|
|
1131
|
-
await ctx.tx.table(parentTable).update({
|
|
1132
|
-
where: pkWhere(parentMeta, parentRow),
|
|
1133
|
-
data: updateData,
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1146
|
+
const parentMeta = ctx.schema.tables[parentTable];
|
|
1147
|
+
const relatedTable = ctx.schema.tables[rel.to];
|
|
1148
|
+
const data = {};
|
|
1149
|
+
for (let i = 0; i < fks.length; i++) {
|
|
1150
|
+
const fkField = parentMeta?.reverseColumnMap[fks[i]] ?? fks[i];
|
|
1151
|
+
const refField = relatedTable?.reverseColumnMap[refs[i]] ?? refs[i];
|
|
1152
|
+
data[fkField] = relatedRow === null ? null : relatedRow[refField];
|
|
1136
1153
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1154
|
+
return data;
|
|
1155
|
+
}
|
|
1156
|
+
/** Update path: create / connect / connectOrCreate the related row, then point the parent at it. */
|
|
1157
|
+
async function processBelongsToCreate(ctx, rel, ops, parentRow, parentTable, depth, path, relName) {
|
|
1158
|
+
const relatedRow = await resolveBelongsToTarget(ctx, rel, ops, depth, path, relName);
|
|
1159
|
+
if (!relatedRow)
|
|
1160
|
+
return;
|
|
1161
|
+
await ctx.tx.table(parentTable).update({
|
|
1162
|
+
where: pkWhere(ctx.schema.tables[parentTable], parentRow),
|
|
1163
|
+
data: belongsToKeyData(ctx, rel, parentTable, relatedRow),
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* `disconnect` / `delete` on a belongsTo relation, with Prisma's semantics:
|
|
1168
|
+
* both clear the parent's foreign key, and `delete` then deletes the row it
|
|
1169
|
+
* pointed at. `true` (or `{}`) means the connected row; an object with fields
|
|
1170
|
+
* additionally requires the connected row to match it. Both need a nullable
|
|
1171
|
+
* foreign key: with a NOT NULL one the parent cannot exist pointing at nothing.
|
|
1172
|
+
*
|
|
1173
|
+
* `disconnect: true` when nothing is connected is a no-op: the parent already
|
|
1174
|
+
* points at nothing, which is what was asked. A `delete`, or a `disconnect`
|
|
1175
|
+
* that names fields, asks about a row that is not there, so it is E001.
|
|
1176
|
+
*/
|
|
1177
|
+
async function processBelongsToDisconnect(ctx, rel, arg, relName, parentRow, parentTable, op) {
|
|
1178
|
+
const parentMeta = ctx.schema.tables[parentTable];
|
|
1179
|
+
const fks = normalizeKeyColumns(rel.foreignKey);
|
|
1180
|
+
const nullable = fks.every((fk) => parentMeta.columns.find((c) => c.name === fk)?.nullable ?? false);
|
|
1181
|
+
if (!nullable) {
|
|
1182
|
+
throw new ValidationError(`Cannot ${op} "${relName}": foreign key column(s) ${fks.join(', ')} on "${parentTable}" are NOT NULL, so ` +
|
|
1183
|
+
`every "${parentTable}" row must point at a "${rel.to}". Connect it to a different "${rel.to}" instead` +
|
|
1184
|
+
`${op === 'delete' ? `, or delete the "${rel.to}" row directly once nothing points at it` : ''}.`);
|
|
1185
|
+
}
|
|
1186
|
+
const item = singleToOneItem(arg, op, relName, rel);
|
|
1187
|
+
// `{}` is the documented to-one spelling of `true` (the connected row). An
|
|
1188
|
+
// object whose keys are all undefined is still refused below: that is the
|
|
1189
|
+
// shape of a missing request field, not a choice.
|
|
1190
|
+
const target = isPlainRecord(item) && Object.keys(item).length === 0 ? true : item;
|
|
1191
|
+
assertTargetSelectsSomething(target, op, relName, rel);
|
|
1192
|
+
const correlation = belongsToCorrelationWhere(ctx, rel, parentRow, parentTable);
|
|
1193
|
+
if (!correlation) {
|
|
1194
|
+
if (op === 'disconnect' && target === true)
|
|
1195
|
+
return;
|
|
1196
|
+
throw nothingConnected(op, relName, rel, target);
|
|
1197
|
+
}
|
|
1198
|
+
const related = (await ctx.tx.table(rel.to).findFirst({ where: scopeWhereToParent(target, correlation) }));
|
|
1199
|
+
if (!related)
|
|
1200
|
+
throw notRelatedToParent(op, relName, rel, target);
|
|
1201
|
+
await ctx.tx.table(parentTable).update({
|
|
1202
|
+
where: pkWhere(parentMeta, parentRow),
|
|
1203
|
+
data: belongsToKeyData(ctx, rel, parentTable, null),
|
|
1204
|
+
});
|
|
1205
|
+
if (op === 'delete') {
|
|
1206
|
+
// Any other row still pointing at it makes this a foreign-key violation
|
|
1207
|
+
// (E009), which rolls the whole write back.
|
|
1208
|
+
await ctx.tx.table(rel.to).delete({ where: pkWhere(ctx.schema.tables[rel.to], related) });
|
|
1159
1209
|
}
|
|
1160
1210
|
}
|
|
1161
1211
|
// ---------------------------------------------------------------------------
|
|
@@ -1366,11 +1416,12 @@ async function processNestedUpsert(ctx, rel, upsertArg, parentRow, relName) {
|
|
|
1366
1416
|
throw new ValidationError(`Nested upsert on "${rel.name}" requires "where", "create", and "update" fields.`);
|
|
1367
1417
|
}
|
|
1368
1418
|
assertTargetSelectsSomething(item.where, 'upsert', relName, rel);
|
|
1419
|
+
const update = upsertUpdateData(ctx, rel, item);
|
|
1369
1420
|
const scoped = correlation ? scopeWhereToParent(item.where, correlation) : null;
|
|
1370
1421
|
const existing = scoped ? await ctx.tx.table(rel.to).findUnique({ where: scoped }) : null;
|
|
1371
1422
|
if (existing && scoped) {
|
|
1372
1423
|
try {
|
|
1373
|
-
await ctx.tx.table(rel.to).update({ where: scoped, data:
|
|
1424
|
+
await ctx.tx.table(rel.to).update({ where: scoped, data: update });
|
|
1374
1425
|
}
|
|
1375
1426
|
catch (err) {
|
|
1376
1427
|
rethrowAsNotRelated(err, 'upsert', relName, rel, item.where);
|
|
@@ -1382,6 +1433,19 @@ async function processNestedUpsert(ctx, rel, upsertArg, parentRow, relName) {
|
|
|
1382
1433
|
}
|
|
1383
1434
|
}
|
|
1384
1435
|
}
|
|
1436
|
+
/**
|
|
1437
|
+
* A nested upsert's `update`, with each `expr.excluded(f)` resolved to the
|
|
1438
|
+
* value its `create` supplies: the incoming row of a one-row upsert is
|
|
1439
|
+
* `create`, as it is for a top-level `upsert()`. Resolved before the lookup,
|
|
1440
|
+
* so a reference `create` cannot satisfy is refused whether or not the row
|
|
1441
|
+
* exists.
|
|
1442
|
+
*/
|
|
1443
|
+
function upsertUpdateData(ctx, rel, item) {
|
|
1444
|
+
const tableMeta = ctx.schema.tables[rel.to];
|
|
1445
|
+
if (!tableMeta)
|
|
1446
|
+
return item.update;
|
|
1447
|
+
return resolveExcludedFromCreate({ table: rel.to, tableMeta }, item.update, item.create) ?? item.update;
|
|
1448
|
+
}
|
|
1385
1449
|
async function processBelongsToUpdate(ctx, rel, updateArg, parentRow, parentTable) {
|
|
1386
1450
|
const item = updateArg;
|
|
1387
1451
|
if (!item.data) {
|
|
@@ -1420,6 +1484,7 @@ async function processBelongsToUpsert(ctx, rel, upsertArg, parentRow, parentTabl
|
|
|
1420
1484
|
// hasMany/hasOne nested upsert makes: an upsert whose target is out of the
|
|
1421
1485
|
// relation creates a row owned by this parent rather than rewriting a
|
|
1422
1486
|
// stranger's.
|
|
1487
|
+
const update = upsertUpdateData(ctx, rel, item);
|
|
1423
1488
|
const correlation = belongsToCorrelationWhere(ctx, rel, parentRow, parentTable);
|
|
1424
1489
|
// findMany, not findUnique: the scoped where ANDs a non-unique-looking
|
|
1425
1490
|
// correlation onto the caller's selector. The correlation targets the
|
|
@@ -1434,7 +1499,7 @@ async function processBelongsToUpsert(ctx, rel, upsertArg, parentRow, parentTabl
|
|
|
1434
1499
|
: null;
|
|
1435
1500
|
if (existing) {
|
|
1436
1501
|
const relatedMeta = ctx.schema.tables[rel.to];
|
|
1437
|
-
await ctx.tx.table(rel.to).update({ where: pkWhere(relatedMeta, existing), data:
|
|
1502
|
+
await ctx.tx.table(rel.to).update({ where: pkWhere(relatedMeta, existing), data: update });
|
|
1438
1503
|
}
|
|
1439
1504
|
else {
|
|
1440
1505
|
// 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
|
*
|