turbine-orm 0.51.0 → 0.52.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 +33 -5
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/client.d.ts +106 -2
- package/dist/cjs/client.js +111 -5
- package/dist/cjs/dialect.d.ts +33 -0
- package/dist/cjs/dialect.js +14 -0
- package/dist/cjs/engine-config.d.ts +49 -0
- package/dist/cjs/engine-config.js +19 -0
- package/dist/cjs/index-advisor.js +0 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/mssql.d.ts +8 -3
- package/dist/cjs/mssql.js +22 -3
- package/dist/cjs/mysql.d.ts +7 -3
- package/dist/cjs/mysql.js +20 -3
- package/dist/cjs/nested-write.d.ts +31 -0
- package/dist/cjs/nested-write.js +80 -2
- package/dist/cjs/powdb-introspect.d.ts +10 -1
- package/dist/cjs/powdb-introspect.js +10 -1
- package/dist/cjs/powdb.d.ts +116 -6
- package/dist/cjs/powdb.js +169 -10
- package/dist/cjs/powql.d.ts +161 -1
- package/dist/cjs/powql.js +299 -19
- package/dist/cjs/prisma-compat.d.ts +54 -8
- package/dist/cjs/prisma-compat.js +136 -20
- package/dist/cjs/query/batched-loader.d.ts +7 -0
- package/dist/cjs/query/batched-loader.js +97 -15
- package/dist/cjs/query/builder.d.ts +131 -5
- package/dist/cjs/query/builder.js +223 -19
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/index.js +2 -1
- package/dist/cjs/query/warn-registry.d.ts +10 -0
- package/dist/cjs/query/warn-registry.js +10 -0
- package/dist/cjs/query/writes.js +115 -7
- package/dist/cjs/sqlite.d.ts +10 -4
- package/dist/cjs/sqlite.js +18 -4
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/client.d.ts +106 -2
- package/dist/client.js +111 -5
- package/dist/dialect.d.ts +33 -0
- package/dist/dialect.js +14 -0
- package/dist/engine-config.d.ts +49 -0
- package/dist/engine-config.js +18 -0
- package/dist/index-advisor.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mssql.d.ts +8 -3
- package/dist/mssql.js +22 -3
- package/dist/mysql.d.ts +7 -3
- package/dist/mysql.js +20 -3
- package/dist/nested-write.d.ts +31 -0
- package/dist/nested-write.js +79 -2
- package/dist/powdb-introspect.d.ts +10 -1
- package/dist/powdb-introspect.js +10 -1
- package/dist/powdb.d.ts +116 -6
- package/dist/powdb.js +167 -9
- package/dist/powql.d.ts +161 -1
- package/dist/powql.js +299 -19
- package/dist/prisma-compat.d.ts +54 -8
- package/dist/prisma-compat.js +136 -20
- package/dist/query/batched-loader.d.ts +7 -0
- package/dist/query/batched-loader.js +98 -16
- package/dist/query/builder.d.ts +131 -5
- package/dist/query/builder.js +222 -18
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/warn-registry.d.ts +10 -0
- package/dist/query/warn-registry.js +10 -0
- package/dist/query/writes.js +116 -8
- package/dist/sqlite.d.ts +10 -4
- package/dist/sqlite.js +19 -5
- package/package.json +3 -3
package/dist/cjs/powql.js
CHANGED
|
@@ -69,7 +69,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
69
69
|
};
|
|
70
70
|
})();
|
|
71
71
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
72
|
-
exports.PowqlInterface = void 0;
|
|
72
|
+
exports.PowqlInterface = exports.MAX_POWQL_DATETIME_TERMS = void 0;
|
|
73
73
|
const node_crypto_1 = require("node:crypto");
|
|
74
74
|
const errors_js_1 = require("./errors.js");
|
|
75
75
|
const nested_write_js_1 = require("./nested-write.js");
|
|
@@ -87,6 +87,18 @@ const schema_js_1 = require("./schema.js");
|
|
|
87
87
|
* before grouping. Mirrors the chunking the parity matrix documents.
|
|
88
88
|
*/
|
|
89
89
|
const MAX_RELATION_KEYS = 1000;
|
|
90
|
+
/**
|
|
91
|
+
* Max values one `in` / `notIn` on a PowDB-native `datetime` column may carry.
|
|
92
|
+
*
|
|
93
|
+
* Such a list is never sent as a list: it is expanded into an equality chain
|
|
94
|
+
* (see {@link PowqlInterface.buildDatetimeInList}), and PowQL spends one level
|
|
95
|
+
* of its 64-level nesting budget per chain term. Measured on the 0.20.0 addon:
|
|
96
|
+
* 63 terms parse at the top level, 61 one level deep, whatever else the
|
|
97
|
+
* predicate contains. 32 leaves room for the surrounding filter, and is also the
|
|
98
|
+
* key-chunk size the relation loaders use for a datetime correlation column, so
|
|
99
|
+
* a loader can never build a chain the engine will reject.
|
|
100
|
+
*/
|
|
101
|
+
exports.MAX_POWQL_DATETIME_TERMS = 32;
|
|
90
102
|
/**
|
|
91
103
|
* Read-shaped actions whose statement may be transparently replayed once on a
|
|
92
104
|
* stale wire frame when `retryStaleReads` is enabled (see
|
|
@@ -296,6 +308,36 @@ class PowqlInterface {
|
|
|
296
308
|
get capabilities() {
|
|
297
309
|
return this.pool.capabilities ?? powdb_js_1.ALL_POWDB_CAPABILITIES;
|
|
298
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* The `limit` a query actually emits: the explicit `limit`, Prisma's `take`
|
|
313
|
+
* alias, then the client-level `defaultLimit`. Shared by {@link buildFind} and
|
|
314
|
+
* the {@link findMany} zero short-circuit so the two can never disagree about
|
|
315
|
+
* which limit is in force.
|
|
316
|
+
*/
|
|
317
|
+
effectiveLimit(args) {
|
|
318
|
+
return args.limit ?? args.take ?? this.defaultLimit;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Reject a negative `limit` / `offset` before it reaches the engine. PowDB
|
|
322
|
+
* casts both with `as usize` at execution, so below engine 0.20 a negative
|
|
323
|
+
* limit wrapped to `usize::MAX` and silently returned EVERY row, the opposite
|
|
324
|
+
* of what the caller asked for; 0.20 refuses it. Validating client-side makes
|
|
325
|
+
* the refusal identical on every engine version and names the argument.
|
|
326
|
+
*
|
|
327
|
+
* `limit: 0` is legal and means "no rows" (SQL `LIMIT 0`). It is not emitted:
|
|
328
|
+
* callers short-circuit it, because PowDB's projection fast path returned ONE
|
|
329
|
+
* row for `limit 0` below 0.20.
|
|
330
|
+
*/
|
|
331
|
+
assertPagination(limit, offset, context) {
|
|
332
|
+
for (const [name, value] of [
|
|
333
|
+
['limit', limit],
|
|
334
|
+
['offset', offset],
|
|
335
|
+
]) {
|
|
336
|
+
if (value !== undefined && value !== null && value < 0) {
|
|
337
|
+
throw new errors_js_1.ValidationError(`[turbine] ${context} on "${this.table}": \`${name}\` must not be negative (got ${value}).`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
299
341
|
/** A predicate that is always false, the empty-`in` / contradiction sentinel. */
|
|
300
342
|
alwaysFalse() {
|
|
301
343
|
const pk = this.meta.primaryKey[0] ?? this.meta.columns[0]?.name;
|
|
@@ -353,6 +395,43 @@ class PowqlInterface {
|
|
|
353
395
|
}
|
|
354
396
|
return parts.join(' and ');
|
|
355
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* Gate a predicate on a PowDB-native `datetime` column. Turbine binds a JS
|
|
400
|
+
* `Date` as an integer count of microseconds, and PowDB writes a timestamp
|
|
401
|
+
* literal as a plain integer, so every such predicate is a `DateTime` vs `Int`
|
|
402
|
+
* comparison. Below engine 0.20 that pairing was unhandled and fell back to
|
|
403
|
+
* comparing TYPE TAGS, so `>` matched every non-null row, `=` and `<` matched
|
|
404
|
+
* none, and the answer additionally changed with the column's access path
|
|
405
|
+
* (indexed vs scanned). 0.20 compares microseconds and every binary operator
|
|
406
|
+
* (`=`, `!=`, `<`, `<=`, `>`, `>=`) is correct.
|
|
407
|
+
*
|
|
408
|
+
* ONE gate covers the whole family, whatever spelling the caller used. `in` /
|
|
409
|
+
* `not in` are included because their still-broken LIST form is never emitted:
|
|
410
|
+
* {@link buildInList} expands a datetime list into the equality chain the
|
|
411
|
+
* engine does answer correctly, which is the same binary comparison this flag
|
|
412
|
+
* governs. So on >= 0.20 every path is served (direct predicates, relation
|
|
413
|
+
* filters, the batched loaders, nested projections, native joins, and the
|
|
414
|
+
* findUnique / update / delete / upsert by-key paths), and below 0.20 every
|
|
415
|
+
* path that compares against a literal is refused with one message, which
|
|
416
|
+
* names the read paths that do not.
|
|
417
|
+
*
|
|
418
|
+
* `is null` / `is not null` are never gated: they compare no literal and are
|
|
419
|
+
* correct on every version. Ordering, grouping and `min`/`max` are likewise
|
|
420
|
+
* unaffected (they compare datetimes against each other, never against an int),
|
|
421
|
+
* as are nested-projection and join correlations, which are column-to-column.
|
|
422
|
+
*
|
|
423
|
+
* Only PowDB's native `datetime` type is affected. Turbine's own DDL emits
|
|
424
|
+
* `int` epoch micros for a `Date` column, so a Turbine-provisioned database
|
|
425
|
+
* never reaches this: the exposed shape is a table created outside Turbine and
|
|
426
|
+
* read through `introspectPowdbDatabase`, or hand-written metadata.
|
|
427
|
+
*/
|
|
428
|
+
assertDatetimePredicateSupported(col) {
|
|
429
|
+
if (!(0, powdb_js_1.isPowdbDatetimeColumn)(col))
|
|
430
|
+
return;
|
|
431
|
+
(0, powdb_js_1.requireCapability)(this.capabilities, 'datetimeCompare', `comparisons on the PowDB datetime column "${col.name}"`, 'Reading that column is unaffected, and so are ordering, grouping and `is null` checks; only comparing it ' +
|
|
432
|
+
'against a bound timestamp needs the fix. A nested `with` (the default nested-projection / join paths) ' +
|
|
433
|
+
'correlates column to column, so it loads the relation without any such comparison.');
|
|
434
|
+
}
|
|
356
435
|
/** Build a single `field: value | operator` condition. */
|
|
357
436
|
buildFieldCondition(field, value, params, alias) {
|
|
358
437
|
const colMeta = this.column(field);
|
|
@@ -360,6 +439,7 @@ class PowqlInterface {
|
|
|
360
439
|
if (value === null)
|
|
361
440
|
return `${ref} is null`;
|
|
362
441
|
if (value instanceof Date || typeof value !== 'object') {
|
|
442
|
+
this.assertDatetimePredicateSupported(colMeta);
|
|
363
443
|
return `${ref} = ${this.param(value, params, colMeta)}`;
|
|
364
444
|
}
|
|
365
445
|
const op = value;
|
|
@@ -374,6 +454,7 @@ class PowqlInterface {
|
|
|
374
454
|
rejectUnsupportedFilter(op, field);
|
|
375
455
|
if (!Object.keys(op).some((k) => OPERATOR_KEYS.has(k))) {
|
|
376
456
|
// A bare object that is not an operator set, equality by value.
|
|
457
|
+
this.assertDatetimePredicateSupported(colMeta);
|
|
377
458
|
return `${ref} = ${this.param(value, params)}`;
|
|
378
459
|
}
|
|
379
460
|
const insensitive = op.mode === 'insensitive';
|
|
@@ -382,6 +463,17 @@ class PowqlInterface {
|
|
|
382
463
|
for (const [opName, opVal] of Object.entries(op)) {
|
|
383
464
|
if (opVal === undefined || opName === 'mode')
|
|
384
465
|
continue;
|
|
466
|
+
// Every operator below compares the column against a bound literal, which
|
|
467
|
+
// is the shape a PowDB `datetime` column can only answer from engine 0.20
|
|
468
|
+
// (see assertDatetimePredicateSupported; `in`/`notIn` reach the engine as
|
|
469
|
+
// an equality chain, so they are the same comparison). The two exceptions
|
|
470
|
+
// compare nothing and stay allowed on every version: a null operand
|
|
471
|
+
// (`equals`/`not: null` → `is [not] null`) and an empty `in`/`notIn` list
|
|
472
|
+
// (a compile-time constant, never sent).
|
|
473
|
+
const comparesNothing = (opVal === null && (opName === 'equals' || opName === 'not')) ||
|
|
474
|
+
((opName === 'in' || opName === 'notIn') && Array.isArray(opVal) && opVal.length === 0);
|
|
475
|
+
if (!comparesNothing)
|
|
476
|
+
this.assertDatetimePredicateSupported(colMeta);
|
|
385
477
|
switch (opName) {
|
|
386
478
|
case 'equals':
|
|
387
479
|
conds.push(opVal === null ? `${ref} is null` : `${lhs} = ${this.bind(opVal, params, insensitive)}`);
|
|
@@ -406,10 +498,10 @@ class PowqlInterface {
|
|
|
406
498
|
conds.push(`${lhs} <= ${this.bind(opVal, params, insensitive)}`);
|
|
407
499
|
break;
|
|
408
500
|
case 'in':
|
|
409
|
-
conds.push(this.buildInList(lhs, opVal, params, insensitive, false));
|
|
501
|
+
conds.push(this.buildInList(lhs, opVal, params, insensitive, false, colMeta));
|
|
410
502
|
break;
|
|
411
503
|
case 'notIn':
|
|
412
|
-
conds.push(this.buildInList(lhs, opVal, params, insensitive, true));
|
|
504
|
+
conds.push(this.buildInList(lhs, opVal, params, insensitive, true, colMeta));
|
|
413
505
|
break;
|
|
414
506
|
case 'contains':
|
|
415
507
|
conds.push(`${lhs} like ${this.bindLike(`%${(0, utils_js_1.escapeLike)(String(opVal))}%`, params, insensitive)}`);
|
|
@@ -546,13 +638,20 @@ class PowqlInterface {
|
|
|
546
638
|
const ph = this.param(pattern, params);
|
|
547
639
|
return insensitive ? `lower(${ph})` : ph;
|
|
548
640
|
}
|
|
549
|
-
/**
|
|
550
|
-
|
|
641
|
+
/**
|
|
642
|
+
* `lhs [not] in ($1, $2, …)`, empty list collapses to a constant.
|
|
643
|
+
*
|
|
644
|
+
* A PowDB-native `datetime` column takes the expanded form instead (see
|
|
645
|
+
* {@link buildDatetimeInList}): its `in` list is still broken upstream at 0.20.
|
|
646
|
+
*/
|
|
647
|
+
buildInList(lhs, values, params, insensitive, negate, col) {
|
|
551
648
|
if (!Array.isArray(values) || values.length === 0) {
|
|
552
649
|
// `in []` matches nothing; `not in []` matches everything (SQL parity requires a
|
|
553
650
|
// missing-value row to match `notIn []`, so NO presence guard is appended here).
|
|
554
651
|
return negate ? '(1 = 1)' : this.alwaysFalse();
|
|
555
652
|
}
|
|
653
|
+
if (col && (0, powdb_js_1.isPowdbDatetimeColumn)(col))
|
|
654
|
+
return this.buildDatetimeInList(lhs, values, params, negate, col);
|
|
556
655
|
const items = values.map((v) => this.bind(v, params, insensitive)).join(', ');
|
|
557
656
|
if (negate) {
|
|
558
657
|
// PowQL `not in` matches missing-value rows, so append `and lhs is not null`
|
|
@@ -561,6 +660,78 @@ class PowqlInterface {
|
|
|
561
660
|
}
|
|
562
661
|
return `${lhs} in (${items})`;
|
|
563
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* `in` / `not in` on a PowDB-native `datetime` column, expanded into the
|
|
665
|
+
* equality chain the engine answers correctly:
|
|
666
|
+
*
|
|
667
|
+
* `in` → `(.ts = $1 or .ts = $2 …)`
|
|
668
|
+
* `notIn` → `(.ts != $1 and .ts != $2 … and .ts is not null)`
|
|
669
|
+
*
|
|
670
|
+
* PowQL's LIST form compares a datetime column against integer timestamp
|
|
671
|
+
* literals by TYPE TAG as of engine 0.20 (the 0.20 timestamp fix covered the
|
|
672
|
+
* binary operators only): measured on the 0.20.0 addon, `filter .ts in
|
|
673
|
+
* (<micros>, …)` matches nothing and `not in` matches everything, while the
|
|
674
|
+
* identical lists against an `int` control column answer correctly. The
|
|
675
|
+
* expanded chain uses the operators 0.20 DID fix, so it answers correctly and
|
|
676
|
+
* matches the int control exactly.
|
|
677
|
+
*
|
|
678
|
+
* This is what keeps the relation family coherent: relation filters and the
|
|
679
|
+
* batched loaders both compile to a key `in` list, so without the rewrite the
|
|
680
|
+
* same relation was refused through one strategy and served through another
|
|
681
|
+
* (nested projections and joins correlate column to column and never emit a
|
|
682
|
+
* list at all).
|
|
683
|
+
*
|
|
684
|
+
* The cost is the chain's width. PowQL bounds the SHAPE of the predicate tree,
|
|
685
|
+
* and a flat `or` / `and` chain spends one level per term against the same
|
|
686
|
+
* 64-level budget as nested parens (measured on 0.20: 63 terms at the top
|
|
687
|
+
* level, 61 one level deep), so the expansion is capped at
|
|
688
|
+
* {@link MAX_POWQL_DATETIME_TERMS} with headroom for whatever predicate it
|
|
689
|
+
* sits inside. The loaders chunk their key lists to that cap, so only a
|
|
690
|
+
* caller-written list (or a relation filter matching very many distinct
|
|
691
|
+
* timestamps) can exceed it, and that raises a typed E017 saying so rather
|
|
692
|
+
* than an engine parse failure.
|
|
693
|
+
*/
|
|
694
|
+
buildDatetimeInList(lhs, values, params, negate, col) {
|
|
695
|
+
if (values.length > exports.MAX_POWQL_DATETIME_TERMS) {
|
|
696
|
+
throw new errors_js_1.UnsupportedFeatureError(`\`${negate ? 'notIn' : 'in'}\` with ${values.length} values on the PowDB datetime column "${col.name}"`, 'PowDB', `PowQL's \`in\` list still compares a datetime column against integer timestamp literals by type tag as of ` +
|
|
697
|
+
`engine 0.20, so Turbine expands it into an equality chain, which PowQL's nesting budget caps at ` +
|
|
698
|
+
`${exports.MAX_POWQL_DATETIME_TERMS} terms. Narrow the list (a \`gte\`/\`lte\` range over the same timestamps is ` +
|
|
699
|
+
'one comparison), split the call and merge the results, or store the column as a PowQL `int` of epoch ' +
|
|
700
|
+
"microseconds, which is what Turbine's own DDL emits for a `Date` column. A relation filter reaches this " +
|
|
701
|
+
'when the inner predicate matches more than that many distinct key timestamps.');
|
|
702
|
+
}
|
|
703
|
+
const terms = values.map((v) => `${lhs} ${negate ? '!=' : '='} ${this.param(v, params, col)}`);
|
|
704
|
+
// `!=` already excludes a missing-value row, but the trailing presence guard
|
|
705
|
+
// keeps the emitted predicate identical in meaning to the plain `not in`
|
|
706
|
+
// branch above (and to SQL null semantics) on every engine version.
|
|
707
|
+
if (negate)
|
|
708
|
+
terms.push(`${lhs} is not null`);
|
|
709
|
+
return `(${terms.join(negate ? ' and ' : ' or ')})`;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* A literal `ref in (…)` clause for the hand-built key lists the relation
|
|
713
|
+
* loaders emit (they bypass {@link buildWhere}). Routes a PowDB-native
|
|
714
|
+
* `datetime` key column through the same equality-chain expansion the
|
|
715
|
+
* where-builder uses, so a datetime junction / correlation key behaves
|
|
716
|
+
* identically however the statement was assembled.
|
|
717
|
+
*/
|
|
718
|
+
inClause(ref, values, params, col) {
|
|
719
|
+
if (col && (0, powdb_js_1.isPowdbDatetimeColumn)(col)) {
|
|
720
|
+
this.assertDatetimePredicateSupported(col);
|
|
721
|
+
return this.buildDatetimeInList(ref, values, params, false, col);
|
|
722
|
+
}
|
|
723
|
+
return `${ref} in (${values.map((v) => this.param(v, params, col)).join(', ')})`;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Key-chunk size for a relation loader. A PowDB-native `datetime` correlation
|
|
727
|
+
* column's `in` list is expanded into an equality chain, which PowQL's nesting
|
|
728
|
+
* budget bounds, so those keys chunk at {@link MAX_POWQL_DATETIME_TERMS}
|
|
729
|
+
* (more, smaller round-trips) instead of {@link MAX_RELATION_KEYS}.
|
|
730
|
+
*/
|
|
731
|
+
keyChunkSize(meta, colName) {
|
|
732
|
+
const col = meta?.columns.find((c) => c.name === colName);
|
|
733
|
+
return col && (0, powdb_js_1.isPowdbDatetimeColumn)(col) ? exports.MAX_POWQL_DATETIME_TERMS : MAX_RELATION_KEYS;
|
|
734
|
+
}
|
|
564
735
|
/**
|
|
565
736
|
* Pre-resolve every relation filter (`some`/`none`/`every`) in a where clause
|
|
566
737
|
* into a plain scalar `in`/`notIn` condition on the **local key**, by running
|
|
@@ -675,15 +846,18 @@ class PowqlInterface {
|
|
|
675
846
|
return [...new Set(rows.map((r) => r[targetPkField]).filter((v) => v != null))];
|
|
676
847
|
};
|
|
677
848
|
// Junction source keys linking any of `targetPks` (literal IN-list, never a subquery).
|
|
849
|
+
const junctionMeta = this.schema.tables[through.table];
|
|
850
|
+
const targetJColMeta = junctionMeta?.columns.find((c) => c.name === targetJCol);
|
|
851
|
+
const junctionChunk = this.keyChunkSize(junctionMeta, targetJCol);
|
|
678
852
|
const sourcesForTargets = async (targetPks) => {
|
|
679
853
|
if (!targetPks.length)
|
|
680
854
|
return [];
|
|
681
855
|
const out = new Set();
|
|
682
|
-
for (let i = 0; i < targetPks.length; i +=
|
|
683
|
-
const chunk = targetPks.slice(i, i +
|
|
856
|
+
for (let i = 0; i < targetPks.length; i += junctionChunk) {
|
|
857
|
+
const chunk = targetPks.slice(i, i + junctionChunk);
|
|
684
858
|
const params = [];
|
|
685
|
-
const
|
|
686
|
-
const { rows } = await this.exec(`${(0, powdb_js_1.quotePowqlIdent)(through.table)} filter
|
|
859
|
+
const keyClause = this.inClause(`.${targetJCol}`, chunk, params, targetJColMeta);
|
|
860
|
+
const { rows } = await this.exec(`${(0, powdb_js_1.quotePowqlIdent)(through.table)} filter ${keyClause} { .${sourceJCol} }`, params, timeout, 'findMany');
|
|
687
861
|
for (const r of rows) {
|
|
688
862
|
const v = r[sourceJCol];
|
|
689
863
|
if (v != null)
|
|
@@ -984,6 +1158,13 @@ class PowqlInterface {
|
|
|
984
1158
|
// -------------------------------------------------------------------------
|
|
985
1159
|
async findMany(args = {}) {
|
|
986
1160
|
return this.withMiddleware('findMany', args, async () => {
|
|
1161
|
+
// `limit: 0` means "no rows" (SQL `LIMIT 0`), and answering it client-side
|
|
1162
|
+
// is correct on every engine version: PowDB's projection fast path returned
|
|
1163
|
+
// ONE row for `limit 0` below 0.20. Validate first so a negative limit still
|
|
1164
|
+
// raises instead of falling through to a query.
|
|
1165
|
+
this.assertPagination(this.effectiveLimit(args), args.offset, 'findMany');
|
|
1166
|
+
if (this.effectiveLimit(args) === 0)
|
|
1167
|
+
return [];
|
|
987
1168
|
const { rows, native, resolvedWhere, nestedPlans, linkPlans, residualWith } = await this.runFind(args, 'findMany');
|
|
988
1169
|
const entities = this.shape(rows, native);
|
|
989
1170
|
if (nestedPlans.length)
|
|
@@ -1055,7 +1236,8 @@ class PowqlInterface {
|
|
|
1055
1236
|
const distinct = args.distinct?.length ? ' distinct' : '';
|
|
1056
1237
|
const filter = where ? ` filter ${where}` : '';
|
|
1057
1238
|
const order = this.buildOrder(args.orderBy, params, alias);
|
|
1058
|
-
const limit =
|
|
1239
|
+
const limit = this.effectiveLimit(args);
|
|
1240
|
+
this.assertPagination(limit, args.offset, 'findMany');
|
|
1059
1241
|
if (limit === undefined && this.warnOnUnlimited && !this.warnedUnlimited) {
|
|
1060
1242
|
this.warnedUnlimited = true;
|
|
1061
1243
|
console.warn(`[turbine] findMany on "${this.table}" has no limit: this scans the whole table.`);
|
|
@@ -1254,8 +1436,9 @@ class PowqlInterface {
|
|
|
1254
1436
|
// cell) so a datetime correlation column stitches instead of silently
|
|
1255
1437
|
// returning [].
|
|
1256
1438
|
const childByKey = new Map();
|
|
1257
|
-
|
|
1258
|
-
|
|
1439
|
+
const chunkSize = this.keyChunkSize(targetMeta, childKeyCol);
|
|
1440
|
+
for (let i = 0; i < keys.length; i += chunkSize) {
|
|
1441
|
+
const chunk = keys.slice(i, i + chunkSize);
|
|
1259
1442
|
const childWhere = {
|
|
1260
1443
|
...fetchOptions.where,
|
|
1261
1444
|
[childKeyField]: { in: chunk },
|
|
@@ -1331,13 +1514,16 @@ class PowqlInterface {
|
|
|
1331
1514
|
return;
|
|
1332
1515
|
}
|
|
1333
1516
|
// (1) Junction rows: sourceKeyVal(String) → [targetKeyVal(String)].
|
|
1517
|
+
const junctionMeta = this.schema.tables[through.table];
|
|
1518
|
+
const sourceJColMeta = junctionMeta?.columns.find((c) => c.name === sourceJCol);
|
|
1334
1519
|
const targetsBySource = new Map();
|
|
1335
1520
|
const allTargetVals = new Set();
|
|
1336
|
-
|
|
1337
|
-
|
|
1521
|
+
const junctionChunk = this.keyChunkSize(junctionMeta, sourceJCol);
|
|
1522
|
+
for (let i = 0; i < parentKeys.length; i += junctionChunk) {
|
|
1523
|
+
const chunk = parentKeys.slice(i, i + junctionChunk);
|
|
1338
1524
|
const params = [];
|
|
1339
|
-
const
|
|
1340
|
-
const powql = `${(0, powdb_js_1.quotePowqlIdent)(through.table)} filter
|
|
1525
|
+
const keyClause = this.inClause(`.${sourceJCol}`, chunk, params, sourceJColMeta);
|
|
1526
|
+
const powql = `${(0, powdb_js_1.quotePowqlIdent)(through.table)} filter ${keyClause} { .${sourceJCol}, .${targetJCol} }`;
|
|
1341
1527
|
const { rows } = await this.exec(powql, params, timeout, 'findMany');
|
|
1342
1528
|
for (const row of rows) {
|
|
1343
1529
|
const sv = String(row[sourceJCol]);
|
|
@@ -1355,8 +1541,9 @@ class PowqlInterface {
|
|
|
1355
1541
|
const targetQi = new PowqlInterface(this.pool, rel.to, this.schema, [], this.options);
|
|
1356
1542
|
const targetByPk = new Map();
|
|
1357
1543
|
const targetValList = [...allTargetVals].map((v) => targetPkColMeta ? coerceScalar(v, targetPkColMeta.tsType) : v);
|
|
1358
|
-
|
|
1359
|
-
|
|
1544
|
+
const targetChunk = this.keyChunkSize(targetMeta, targetPkCol);
|
|
1545
|
+
for (let i = 0; i < targetValList.length; i += targetChunk) {
|
|
1546
|
+
const chunk = targetValList.slice(i, i + targetChunk);
|
|
1360
1547
|
const where = {
|
|
1361
1548
|
...options.where,
|
|
1362
1549
|
[targetPkField]: { in: chunk },
|
|
@@ -1466,6 +1653,11 @@ class PowqlInterface {
|
|
|
1466
1653
|
if ((options.limit !== undefined || options.offset) && (single || parentCount > MAX_RELATION_KEYS)) {
|
|
1467
1654
|
return false;
|
|
1468
1655
|
}
|
|
1656
|
+
// A `limit 0` relation stays off the join statement for the same reason it
|
|
1657
|
+
// stays off a nested projection: PowDB answered `limit 0` with one row below
|
|
1658
|
+
// engine 0.20. The loader resolves it client-side, correctly on every version.
|
|
1659
|
+
if (options.limit === 0)
|
|
1660
|
+
return false;
|
|
1469
1661
|
return true;
|
|
1470
1662
|
}
|
|
1471
1663
|
/**
|
|
@@ -1507,6 +1699,7 @@ class PowqlInterface {
|
|
|
1507
1699
|
const childCols = this.joinChildCols(targetQi, options, includePii);
|
|
1508
1700
|
const filter = await this.joinFilter(targetQi, parent.resolvedWhere, options.where, 'c', params, options.timeout ?? timeout);
|
|
1509
1701
|
const order = targetQi.buildOrder(options.orderBy, params, 'c');
|
|
1702
|
+
this.assertPagination(options.limit, options.offset, `relation "${relName}"`);
|
|
1510
1703
|
const limitClause = options.limit !== undefined ? ` limit ${this.param(options.limit, params)}` : '';
|
|
1511
1704
|
const offsetClause = options.offset ? ` offset ${this.param(options.offset, params)}` : '';
|
|
1512
1705
|
const proj = this.joinProjection(childCols, `p.${(0, powdb_js_1.quotePowqlIdent)(parentKeyCol)}`, 'c');
|
|
@@ -1661,6 +1854,10 @@ class PowqlInterface {
|
|
|
1661
1854
|
* - m2m (the block takes exactly one child table; the junction-order
|
|
1662
1855
|
* stitch has no nested equivalent), and composite relation keys;
|
|
1663
1856
|
* - a to-one relation carrying `limit`/`offset` (the loaders' semantics);
|
|
1857
|
+
* - a relation `limit` of exactly 0 (a nested block would emit `limit 0`,
|
|
1858
|
+
* which PowDB's projection fast path answered with ONE row below engine
|
|
1859
|
+
* 0.20; the loader path resolves it client-side and is correct on every
|
|
1860
|
+
* version). A NEGATIVE relation limit is refused outright, not fallen back;
|
|
1664
1861
|
* - `distinct` inside the relation options (no nested grammar for it);
|
|
1665
1862
|
* - a projected child column whose tsType is `bigint` or `Uint8Array`
|
|
1666
1863
|
* (values ride a JSON array, which cannot carry them losslessly);
|
|
@@ -1686,6 +1883,9 @@ class PowqlInterface {
|
|
|
1686
1883
|
const single = rel.type === 'belongsTo' || rel.type === 'hasOne';
|
|
1687
1884
|
if (single && (options.limit !== undefined || options.offset))
|
|
1688
1885
|
return null;
|
|
1886
|
+
this.assertPagination(options.limit, options.offset, `relation "${relName}"`);
|
|
1887
|
+
if (options.limit === 0)
|
|
1888
|
+
return null;
|
|
1689
1889
|
const targetQi = new PowqlInterface(this.pool, rel.to, this.schema, [], this.options);
|
|
1690
1890
|
const cols = targetQi.projectedColumns(options.select, options.omit, includePii);
|
|
1691
1891
|
const byName = new Map(targetQi.meta.columns.map((c) => [c.name, c]));
|
|
@@ -2020,6 +2220,52 @@ class PowqlInterface {
|
|
|
2020
2220
|
return row;
|
|
2021
2221
|
});
|
|
2022
2222
|
}
|
|
2223
|
+
/**
|
|
2224
|
+
* Refuse a `createMany` whose rows do not all name the SAME fields.
|
|
2225
|
+
*
|
|
2226
|
+
* PowQL could express it: a multi-row insert carries one `{ col := … }` tuple
|
|
2227
|
+
* per row, each with its own column list, so a ragged call inserts every
|
|
2228
|
+
* value. The SQL engines cannot, their single statement takes its column list
|
|
2229
|
+
* from the first row, so a field only a later row names is dropped and a field
|
|
2230
|
+
* only the first row names is written as NULL over that column's default. They
|
|
2231
|
+
* refuse it (ValidationError, query/writes.ts), and PowDB matching that
|
|
2232
|
+
* refusal is what makes the call portable: a shape accepted here and rejected
|
|
2233
|
+
* by every other engine turns a PowDB-to-Postgres move into a hard error found
|
|
2234
|
+
* in production rather than at the first run.
|
|
2235
|
+
*
|
|
2236
|
+
* Runs AFTER `applyPkDefault`, on the rows as they will actually be written:
|
|
2237
|
+
* a defaulted string PK is filled in on every row, so `[{}, { name }]` is
|
|
2238
|
+
* refused for the missing `name`, not for the PK the client supplied itself.
|
|
2239
|
+
* Rows that all name the same fields (the overwhelmingly common shape,
|
|
2240
|
+
* including N rows of pure defaults) cost one `Object.keys` pass and emit
|
|
2241
|
+
* byte-identical PowQL.
|
|
2242
|
+
*/
|
|
2243
|
+
assertUniformCreateManyRows(rows) {
|
|
2244
|
+
const definedKeys = (row) => Object.keys(row).filter((k) => row[k] !== undefined);
|
|
2245
|
+
const firstKeys = definedKeys(rows[0]);
|
|
2246
|
+
const expected = new Set(firstKeys);
|
|
2247
|
+
const quoteList = (names) => names.map((n) => `"${n}"`).join(', ');
|
|
2248
|
+
for (let i = 1; i < rows.length; i++) {
|
|
2249
|
+
const rowKeys = definedKeys(rows[i]);
|
|
2250
|
+
// No stranger and the same count means the same set (object keys are unique).
|
|
2251
|
+
const unexpected = rowKeys.filter((k) => !expected.has(k));
|
|
2252
|
+
if (unexpected.length === 0 && rowKeys.length === expected.size)
|
|
2253
|
+
continue;
|
|
2254
|
+
const present = new Set(rowKeys);
|
|
2255
|
+
const missing = firstKeys.filter((k) => !present.has(k));
|
|
2256
|
+
const parts = [];
|
|
2257
|
+
if (missing.length > 0)
|
|
2258
|
+
parts.push(`does not supply ${quoteList(missing)}`);
|
|
2259
|
+
if (unexpected.length > 0)
|
|
2260
|
+
parts.push(`supplies ${quoteList(unexpected)}, which the first row does not`);
|
|
2261
|
+
throw new errors_js_1.ValidationError(`[turbine] createMany on "${this.table}": row ${i} ${parts.join(' and ')}. ` +
|
|
2262
|
+
'Every row must supply the same fields (a field set to `undefined` counts as omitted, exactly as it does ' +
|
|
2263
|
+
'in `create`). PowQL itself would insert the ragged rows, but the SQL engines build ONE statement whose ' +
|
|
2264
|
+
"column list comes from the first row, so there a later row's extra field is dropped and a field it " +
|
|
2265
|
+
"omits is written as NULL over that column's default. Supply the field explicitly on every row, or " +
|
|
2266
|
+
'split the call into one createMany per field set.');
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2023
2269
|
async createMany(args) {
|
|
2024
2270
|
return this.withMiddleware('createMany', args, async () => {
|
|
2025
2271
|
if (args.skipDuplicates) {
|
|
@@ -2030,6 +2276,7 @@ class PowqlInterface {
|
|
|
2030
2276
|
const inputs = args.data.map((d) => this.applyPkDefault(d));
|
|
2031
2277
|
if (!inputs.length)
|
|
2032
2278
|
return [];
|
|
2279
|
+
this.assertUniformCreateManyRows(inputs);
|
|
2033
2280
|
const params = [];
|
|
2034
2281
|
const tuples = inputs.map((d) => {
|
|
2035
2282
|
const assigns = this.scalarData(d);
|
|
@@ -2286,6 +2533,34 @@ class PowqlInterface {
|
|
|
2286
2533
|
return Number((rows[0]?.value ?? rows[0]?.count ?? 0));
|
|
2287
2534
|
});
|
|
2288
2535
|
}
|
|
2536
|
+
/**
|
|
2537
|
+
* Gate ONE field of a per-field `_count`.
|
|
2538
|
+
*
|
|
2539
|
+
* `_count: { col: true }` compiles to `count(T { .col })`, which counts
|
|
2540
|
+
* NON-NULL values of the column (SQL's `COUNT(col)`) only from engine 0.20 on.
|
|
2541
|
+
* Below 0.20 both PowDB frontends ignored the projection and returned the ROW
|
|
2542
|
+
* count.
|
|
2543
|
+
*
|
|
2544
|
+
* That divergence only EXISTS on a nullable column: where the column is NOT
|
|
2545
|
+
* NULL the row count and the non-null count are the same number, so the
|
|
2546
|
+
* pre-0.20 answer was already the right one and the query keeps working. The
|
|
2547
|
+
* gate is therefore per-column, not per-call: refusing the whole feature would
|
|
2548
|
+
* take a correct, working call away from every user on the engine line Turbine
|
|
2549
|
+
* shipped against and hand them nothing.
|
|
2550
|
+
*
|
|
2551
|
+
* Nullability comes from the column metadata (`introspectPowdbDatabase` reads
|
|
2552
|
+
* PowDB's `required` modifier; `defineSchema` declares it). Metadata that
|
|
2553
|
+
* claims NOT NULL for a column the live catalog lets be null would count rows
|
|
2554
|
+
* instead of values below 0.20, the same drift any stale-metadata query has.
|
|
2555
|
+
*/
|
|
2556
|
+
assertProjectedCountSupported(field) {
|
|
2557
|
+
const col = this.column(field);
|
|
2558
|
+
if (!col.nullable)
|
|
2559
|
+
return;
|
|
2560
|
+
(0, powdb_js_1.requireCapability)(this.capabilities, 'projectedCountNonNull', `per-field \`_count\` of the nullable column "${col.name}"`, 'Below that version the engine ignored the column projection and returned the ROW count, which differs from ' +
|
|
2561
|
+
'every SQL engine exactly when the column is nullable. `_count: true` (a row count) and a per-field ' +
|
|
2562
|
+
'`_count` of a NOT NULL column are correct on every version and are never refused.');
|
|
2563
|
+
}
|
|
2289
2564
|
async aggregate(args) {
|
|
2290
2565
|
return this.withMiddleware('aggregate', args, async () => {
|
|
2291
2566
|
// One scalar query per aggregate, PowDB's bare-projection aggregate is broken.
|
|
@@ -2307,6 +2582,7 @@ class PowqlInterface {
|
|
|
2307
2582
|
else {
|
|
2308
2583
|
const counts = {};
|
|
2309
2584
|
for (const field of Object.keys(args._count).filter((f) => args._count[f])) {
|
|
2585
|
+
this.assertProjectedCountSupported(field);
|
|
2310
2586
|
counts[field] = (await scalar(`count(${this.qt}${filter} { ${this.ref(field)} })`)) ?? 0;
|
|
2311
2587
|
}
|
|
2312
2588
|
result._count = counts;
|
|
@@ -2476,7 +2752,11 @@ class PowqlInterface {
|
|
|
2476
2752
|
const having = this.buildHaving(args.having, params, aggInner);
|
|
2477
2753
|
const order = this.buildGroupOrder(args.orderBy, byOrderExprs, aggOrderExprs);
|
|
2478
2754
|
// LIMIT / OFFSET over the result groups, applied after ORDER BY (mirrors
|
|
2479
|
-
// the SQL groupBy). offset 0 is a no-op, matching findMany
|
|
2755
|
+
// the SQL groupBy). offset 0 is a no-op, matching findMany; `limit: 0` is
|
|
2756
|
+
// answered client-side (PowDB returned one row for it below engine 0.20).
|
|
2757
|
+
this.assertPagination(args.limit, args.offset, 'groupBy');
|
|
2758
|
+
if (args.limit === 0)
|
|
2759
|
+
return [];
|
|
2480
2760
|
const limitClause = args.limit !== undefined ? ` limit ${this.param(args.limit, params)}` : '';
|
|
2481
2761
|
const offsetClause = args.offset ? ` offset ${this.param(args.offset, params)}` : '';
|
|
2482
2762
|
const powql = `${this.qt}${filter} group ${groupExprs.join(', ')}${having}${order}${limitClause}${offsetClause} { ${proj.join(', ')} }`;
|
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
* , the un-awaited delegate calls defer to Turbine's `build*()` methods and
|
|
26
26
|
* run atomically through the core batch `$transaction([...])` path.
|
|
27
27
|
* - **Raw SQL**: `$queryRaw` / `$executeRaw` tagged templates (with
|
|
28
|
-
* `Prisma.sql`-style nested-fragment flattening) and the `*Unsafe` variants
|
|
28
|
+
* `Prisma.sql`-style nested-fragment flattening) and the `*Unsafe` variants,
|
|
29
|
+
* on the client AND on the transaction client, where they run on the
|
|
30
|
+
* transaction's own connection so a mixed raw + delegate `$transaction` stays
|
|
31
|
+
* atomic.
|
|
29
32
|
* - **Result reshaping**: `_count` objects keyed back to Prisma relation names,
|
|
30
33
|
* and to-one relations surfaced as `object | null`.
|
|
31
34
|
*
|
|
@@ -128,6 +131,18 @@ export interface CompatQueryInterface {
|
|
|
128
131
|
/** A transaction-scoped client handed to a `$transaction(callback)`. */
|
|
129
132
|
export interface CompatTransactionClient {
|
|
130
133
|
table(name: string): CompatQueryInterface;
|
|
134
|
+
/**
|
|
135
|
+
* Execute a prebuilt `(text, params)` statement on the TRANSACTION's own
|
|
136
|
+
* connection (core `TransactionClient.rawQuery`). It is what backs the
|
|
137
|
+
* transaction-scoped `$queryRaw` / `$executeRaw`; without it those methods
|
|
138
|
+
* would have to reach around the transaction to the pool, which silently
|
|
139
|
+
* breaks atomicity. Optional only so a test stub can omit it: when it is
|
|
140
|
+
* absent the raw methods throw instead of escaping the transaction.
|
|
141
|
+
*/
|
|
142
|
+
rawQuery?(text: string, params?: readonly unknown[]): Promise<{
|
|
143
|
+
rows: unknown[];
|
|
144
|
+
rowCount: number | null;
|
|
145
|
+
}>;
|
|
131
146
|
}
|
|
132
147
|
/** The minimal `TurbineClient` surface the adapter consumes. */
|
|
133
148
|
export interface CompatTurbineClient extends CompatTransactionClient {
|
|
@@ -135,6 +150,21 @@ export interface CompatTurbineClient extends CompatTransactionClient {
|
|
|
135
150
|
$transaction<R>(fn: (tx: CompatTransactionClient) => Promise<R>, options?: unknown): Promise<R>;
|
|
136
151
|
$transaction(queries: readonly DeferredQuery<unknown>[]): Promise<unknown[]>;
|
|
137
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Brand marking an object as a raw-SQL fragment whose `strings` are spliced
|
|
155
|
+
* VERBATIM into the emitted statement (see `flattenTemplate`). Deliberately a
|
|
156
|
+
* module-private `Symbol()` and NOT `Symbol.for(...)`: a registry symbol is
|
|
157
|
+
* reachable by name from anywhere in the process, so any dependency could mint
|
|
158
|
+
* an object that flattens as trusted SQL. With a private symbol the only way to
|
|
159
|
+
* obtain a fragment is to call `Prisma.sql` / `Prisma.join` / `Prisma.raw` from
|
|
160
|
+
* this module.
|
|
161
|
+
*
|
|
162
|
+
* The fragment check is fail-CLOSED: an object that does not carry this exact
|
|
163
|
+
* symbol is bound as a `$N` parameter, never spliced. That is also what makes
|
|
164
|
+
* the (contrived) dual-package case safe rather than dangerous, a fragment
|
|
165
|
+
* built by the ESM copy of this module and executed by the CJS copy binds as a
|
|
166
|
+
* parameter instead of composing.
|
|
167
|
+
*/
|
|
138
168
|
declare const SQL_FRAGMENT: unique symbol;
|
|
139
169
|
/** A composable SQL fragment, the local stand-in for `Prisma.Sql`. */
|
|
140
170
|
export interface Sql {
|
|
@@ -227,16 +257,24 @@ export interface PrismaModelDelegate<M extends PrismaModelTypes> {
|
|
|
227
257
|
aggregate(args: Args): Promise<Record<string, unknown>>;
|
|
228
258
|
groupBy(args: Args): Promise<Record<string, unknown>[]>;
|
|
229
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* The four Prisma raw-SQL methods. Present on BOTH the client and the
|
|
262
|
+
* transaction-scoped client, exactly as in Prisma, so a migrated call site that
|
|
263
|
+
* mixes `$transaction` with raw SQL keeps working, and the raw statement runs on
|
|
264
|
+
* the transaction's own connection.
|
|
265
|
+
*/
|
|
266
|
+
export interface PrismaCompatRawSurface {
|
|
267
|
+
$queryRaw<T = unknown>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
|
|
268
|
+
$queryRawUnsafe<T = unknown>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
269
|
+
$executeRaw(strings: TemplateStringsArray, ...values: unknown[]): Promise<number>;
|
|
270
|
+
$executeRawUnsafe(sql: string, ...params: unknown[]): Promise<number>;
|
|
271
|
+
}
|
|
230
272
|
/** The client-level surface (`$transaction` / raw), added to the model map. */
|
|
231
|
-
export interface PrismaCompatClientBase<S extends Record<string, PrismaModelTypes> = Record<string, PrismaModelTypes>> {
|
|
273
|
+
export interface PrismaCompatClientBase<S extends Record<string, PrismaModelTypes> = Record<string, PrismaModelTypes>> extends PrismaCompatRawSurface {
|
|
232
274
|
$transaction<R>(fn: (tx: PrismaCompatTransactionClient<S>) => Promise<R>, options?: PrismaCompatTxOptions): Promise<R>;
|
|
233
275
|
$transaction<P extends readonly PromiseLike<unknown>[]>(promises: readonly [...P]): Promise<{
|
|
234
276
|
[K in keyof P]: Awaited<P[K]>;
|
|
235
277
|
}>;
|
|
236
|
-
$queryRaw<T = unknown>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
|
|
237
|
-
$queryRawUnsafe<T = unknown>(sql: string, ...params: unknown[]): Promise<T[]>;
|
|
238
|
-
$executeRaw(strings: TemplateStringsArray, ...values: unknown[]): Promise<number>;
|
|
239
|
-
$executeRawUnsafe(sql: string, ...params: unknown[]): Promise<number>;
|
|
240
278
|
$connect(): Promise<void>;
|
|
241
279
|
$disconnect(): Promise<void>;
|
|
242
280
|
}
|
|
@@ -246,10 +284,18 @@ export interface PrismaCompatTxOptions {
|
|
|
246
284
|
timeout?: number;
|
|
247
285
|
maxWait?: number;
|
|
248
286
|
}
|
|
249
|
-
/**
|
|
287
|
+
/**
|
|
288
|
+
* The transaction-scoped client handed to a `$transaction(callback)`: a model
|
|
289
|
+
* delegate per Prisma model (under both spellings, as on the client) plus the
|
|
290
|
+
* raw-SQL surface, every one of them bound to the transaction's connection.
|
|
291
|
+
* Prisma's transaction client has no `$transaction` / `$connect` /
|
|
292
|
+
* `$disconnect`, and neither does this one.
|
|
293
|
+
*/
|
|
250
294
|
export type PrismaCompatTransactionClient<S extends Record<string, PrismaModelTypes> = Record<string, PrismaModelTypes>> = {
|
|
251
295
|
[K in keyof S]: PrismaModelDelegate<S[K]>;
|
|
252
|
-
}
|
|
296
|
+
} & {
|
|
297
|
+
[K in keyof S as Uncapitalize<K & string>]: PrismaModelDelegate<S[K]>;
|
|
298
|
+
} & PrismaCompatRawSurface;
|
|
253
299
|
/**
|
|
254
300
|
* The full typed compat client: a model delegate per Prisma model name, plus the
|
|
255
301
|
* client-level `$transaction` / raw surface. Parameterize `S` with your
|