turbine-orm 0.79.1 → 0.80.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 (71) hide show
  1. package/README.md +4 -4
  2. package/dist/checkout.d.ts +53 -0
  3. package/dist/checkout.js +78 -0
  4. package/dist/cjs/checkout.d.ts +53 -0
  5. package/dist/cjs/checkout.js +82 -0
  6. package/dist/cjs/cli/index.js +4 -0
  7. package/dist/cjs/cli/mcp.js +4 -0
  8. package/dist/cjs/cli/migrate.js +6 -0
  9. package/dist/cjs/cli/observe.js +8 -0
  10. package/dist/cjs/cli/studio.js +13 -1
  11. package/dist/cjs/client.d.ts +12 -2
  12. package/dist/cjs/client.js +61 -75
  13. package/dist/cjs/connection-guard.d.ts +120 -0
  14. package/dist/cjs/connection-guard.js +191 -0
  15. package/dist/cjs/errors.d.ts +26 -0
  16. package/dist/cjs/errors.js +85 -1
  17. package/dist/cjs/index.d.ts +1 -1
  18. package/dist/cjs/nested-write.d.ts +12 -2
  19. package/dist/cjs/nested-write.js +4 -10
  20. package/dist/cjs/pipeline.js +12 -7
  21. package/dist/cjs/plan-flip-probe.js +4 -0
  22. package/dist/cjs/powdb-shared.d.ts +22 -2
  23. package/dist/cjs/powdb-shared.js +27 -2
  24. package/dist/cjs/powdb.js +36 -37
  25. package/dist/cjs/powql.d.ts +51 -6
  26. package/dist/cjs/powql.js +199 -45
  27. package/dist/cjs/prisma-compat.js +28 -4
  28. package/dist/cjs/query/builder.d.ts +44 -24
  29. package/dist/cjs/query/builder.js +125 -66
  30. package/dist/cjs/query/deferred.d.ts +9 -0
  31. package/dist/cjs/query/option-surface.js +12 -0
  32. package/dist/cjs/query/types.d.ts +68 -4
  33. package/dist/cjs/query/writes.d.ts +39 -9
  34. package/dist/cjs/query/writes.js +72 -34
  35. package/dist/cjs/realtime.d.ts +46 -2
  36. package/dist/cjs/realtime.js +125 -20
  37. package/dist/cjs/schema-sql.js +6 -0
  38. package/dist/cli/index.js +4 -0
  39. package/dist/cli/mcp.js +4 -0
  40. package/dist/cli/migrate.js +6 -0
  41. package/dist/cli/observe.js +8 -0
  42. package/dist/cli/studio.js +13 -1
  43. package/dist/client.d.ts +12 -2
  44. package/dist/client.js +62 -76
  45. package/dist/connection-guard.d.ts +120 -0
  46. package/dist/connection-guard.js +183 -0
  47. package/dist/errors.d.ts +26 -0
  48. package/dist/errors.js +83 -1
  49. package/dist/index.d.ts +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/nested-write.d.ts +12 -2
  52. package/dist/nested-write.js +4 -10
  53. package/dist/pipeline.js +13 -8
  54. package/dist/plan-flip-probe.js +4 -0
  55. package/dist/powdb-shared.d.ts +22 -2
  56. package/dist/powdb-shared.js +25 -2
  57. package/dist/powdb.js +23 -24
  58. package/dist/powql.d.ts +51 -6
  59. package/dist/powql.js +200 -46
  60. package/dist/prisma-compat.js +28 -4
  61. package/dist/query/builder.d.ts +44 -24
  62. package/dist/query/builder.js +126 -67
  63. package/dist/query/deferred.d.ts +9 -0
  64. package/dist/query/option-surface.js +12 -0
  65. package/dist/query/types.d.ts +68 -4
  66. package/dist/query/writes.d.ts +39 -9
  67. package/dist/query/writes.js +71 -34
  68. package/dist/realtime.d.ts +46 -2
  69. package/dist/realtime.js +125 -20
  70. package/dist/schema-sql.js +6 -0
  71. package/package.json +5 -3
@@ -48,6 +48,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
48
48
  exports.QueryInterface = exports.AUTO_COUNT_BATCH_MIN_PARENT_ROWS = exports.AUTO_TO_ONE_JOIN_ROWS_MAX = exports.AUTO_TO_ONE_JOIN_ROWS_MIN = exports.AUTO_TO_ONE_JOIN_MAX_ROWS = exports.AUTO_ASSUMED_ROUND_TRIP_MS = exports.AUTO_JOIN_PENALTY_MS_PER_ROW = void 0;
49
49
  exports.resetCacheCrossCheckEnv = resetCacheCrossCheckEnv;
50
50
  exports.unlockNestedWriteTx = unlockNestedWriteTx;
51
+ const checkout_js_1 = require("../checkout.js");
52
+ const connection_guard_js_1 = require("../connection-guard.js");
51
53
  const dialect_js_1 = require("../dialect.js");
52
54
  const errors_js_1 = require("../errors.js");
53
55
  const index_advisor_js_1 = require("../index-advisor.js");
@@ -1715,7 +1717,7 @@ class QueryInterface {
1715
1717
  const deferred = single
1716
1718
  ? this.buildFindUnique(baseArgs)
1717
1719
  : this.buildFindMany(baseArgs);
1718
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
1720
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
1719
1721
  const rows = deferred.transform(result);
1720
1722
  const entities = single ? (rows ? [rows] : []) : rows;
1721
1723
  // Unconditionally, even for zero rows: with no parents the loader is a
@@ -1756,7 +1758,7 @@ class QueryInterface {
1756
1758
  // too: a batched load re-issues the SAME tenant-shaped predicate one
1757
1759
  // level down, so leaving those named would keep exactly the plan-cache
1758
1760
  // exposure the caller asked to be rid of.
1759
- exec: (sql, params, preparedName) => this.queryWithTimeout(sql, params, timeout, this.preparedNameFor({ forceCustomPlan }, preparedName)),
1761
+ exec: (sql, params, preparedName) => this.readWithTimeout(sql, params, timeout, this.preparedNameFor({ forceCustomPlan }, preparedName)),
1760
1762
  quote: (name) => this.q(name),
1761
1763
  buildInClause: (expr, paramRef, negated) => this.inClause(expr, paramRef, negated),
1762
1764
  inClauseParam: (values) => this.inParam(values),
@@ -1869,7 +1871,7 @@ class QueryInterface {
1869
1871
  const { baseArgs, strip } = this.prepareBatchedBase(args, withClause);
1870
1872
  // baseArgs.with is always undefined here; the cast just bridges the R generic.
1871
1873
  const deferred = this.buildFindMany(baseArgs);
1872
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
1874
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
1873
1875
  const entities = deferred.transform(result);
1874
1876
  // Unconditionally, even for zero rows: see the 'auto' path above, the
1875
1877
  // loader doubles as the compile-time validation walk of the `with` tree.
@@ -2032,7 +2034,7 @@ class QueryInterface {
2032
2034
  resetUnlimitedWarnings() {
2033
2035
  this.warnedTables.clear();
2034
2036
  }
2035
- emitQueryEvent(sql, params, duration, action, rows, error) {
2037
+ emitQueryEvent(sql, params, duration, action, rows, error, retried) {
2036
2038
  const onQuery = this.options?._onQuery;
2037
2039
  if (!onQuery)
2038
2040
  return;
@@ -2047,6 +2049,7 @@ class QueryInterface {
2047
2049
  timestamp: new Date(),
2048
2050
  error,
2049
2051
  strategy: this.currentStrategyTag,
2052
+ ...(retried ? { retried: true } : {}),
2050
2053
  });
2051
2054
  }
2052
2055
  catch {
@@ -2116,13 +2119,39 @@ class QueryInterface {
2116
2119
  }
2117
2120
  return undefined;
2118
2121
  }
2122
+ /**
2123
+ * {@link queryWithTimeout} for a statement that only READS, which makes it
2124
+ * safe to send twice. Outside a transaction, a read that fails because the
2125
+ * connection it went out on had already been closed by the server
2126
+ * ({@link isStaleConnectionError}) is sent once more, after one event-loop
2127
+ * turn, on whatever connection the pool hands out next.
2128
+ *
2129
+ * Reads only, and the distinction is the whole design. For a write, "the
2130
+ * connection died" does not say whether the statement committed first: a
2131
+ * connection can drop after the server commits and before the reply
2132
+ * arrives, so resending an INSERT can insert it twice. A read has no such
2133
+ * outcome. Nor inside a transaction, where the connection that died WAS the
2134
+ * transaction and a retry on another one would run outside it.
2135
+ *
2136
+ * The loop turn is what makes a second attempt worth making: pg-pool evicts
2137
+ * an idle connection whose close it has read, so after one poll phase every
2138
+ * other connection that died alongside this one is gone from the idle list
2139
+ * rather than lent out to the retry.
2140
+ *
2141
+ * Every read call site uses this and every write uses `queryWithTimeout`
2142
+ * directly; `src/test/stale-connection-retry.test.ts` pins which is which.
2143
+ */
2144
+ readWithTimeout(sql, params, timeout, preparedName) {
2145
+ return this.queryWithTimeout(sql, params, timeout, preparedName, true);
2146
+ }
2119
2147
  /**
2120
2148
  * Execute a pool.query with an optional timeout.
2121
2149
  * If timeout is set, races the query against a timer and rejects on expiry.
2122
2150
  * pg driver errors are translated to typed Turbine errors via wrapPgError.
2151
+ * `retryIfStale` is {@link readWithTimeout}'s; nothing else sets it.
2123
2152
  */
2124
- async queryWithTimeout(sql, params, timeout, preparedName) {
2125
- const start = performance.now();
2153
+ async queryWithTimeout(sql, params, timeout, preparedName, retryIfStale = false) {
2154
+ let start = performance.now();
2126
2155
  const action = this.currentAction;
2127
2156
  // Build the query argument, use object form with `name` for prepared
2128
2157
  // statements, or the plain (text, values) form otherwise.
@@ -2132,13 +2161,33 @@ class QueryInterface {
2132
2161
  // cast. Guarded at runtime by `preparedStatementsEnabled`, which only the
2133
2162
  // drivers that accept it turn on.
2134
2163
  const usePrepared = preparedName && this.preparedStatementsEnabled;
2135
- const exec = usePrepared
2164
+ const send = () => usePrepared
2136
2165
  ? this.pool.query({
2137
2166
  name: preparedName,
2138
2167
  text: sql,
2139
2168
  values: params,
2140
2169
  })
2141
2170
  : this.pool.query(sql, params);
2171
+ let exec = send();
2172
+ // Set when the caller's timeout has already answered, so a first attempt
2173
+ // that fails AFTER that does not send a retry nobody is waiting for.
2174
+ let abandoned = false;
2175
+ if (retryIfStale && !this.txScoped) {
2176
+ exec = exec.catch(async (err) => {
2177
+ if (abandoned || !(0, errors_js_1.isStaleConnectionError)(err))
2178
+ throw err;
2179
+ // The failed attempt is reported as its own event, marked `retried`,
2180
+ // so a listener counting errors sees it and one alerting on failed
2181
+ // CALLS can skip it. The final event below times the retry alone.
2182
+ const wrapped = (0, errors_js_1.wrapPgError)(err);
2183
+ this.emitQueryEvent(sql, params, performance.now() - start, action, 0, wrapped instanceof Error ? wrapped : undefined, true);
2184
+ await (0, connection_guard_js_1.settleEventLoop)();
2185
+ if (abandoned)
2186
+ throw err;
2187
+ start = performance.now();
2188
+ return send();
2189
+ });
2190
+ }
2142
2191
  if (!timeout) {
2143
2192
  try {
2144
2193
  const result = await exec;
@@ -2153,7 +2202,10 @@ class QueryInterface {
2153
2202
  }
2154
2203
  let timer;
2155
2204
  const timeoutPromise = new Promise((_, reject) => {
2156
- timer = setTimeout(() => reject(new errors_js_1.TimeoutError(timeout)), timeout);
2205
+ timer = setTimeout(() => {
2206
+ abandoned = true;
2207
+ reject(new errors_js_1.TimeoutError(timeout));
2208
+ }, timeout);
2157
2209
  });
2158
2210
  try {
2159
2211
  const result = await Promise.race([exec, timeoutPromise]);
@@ -2195,19 +2247,37 @@ class QueryInterface {
2195
2247
  // Write compilation (extracted to writes.ts).
2196
2248
  // ---------------------------------------------------------------------------
2197
2249
  buildCreate(args) {
2198
- return writesMod.buildCreate(this.ctx, args);
2250
+ return writesMod.buildCreate(this.ctx, args, this.resolveWriteProjection(args));
2251
+ }
2252
+ /**
2253
+ * A single-row write's `select` / `omit`, resolved through the SAME
2254
+ * `resolveProjection` reads use (see WriteProjection in writes.ts), or
2255
+ * `undefined` for the default return shape. Resolved here rather than in
2256
+ * writes.ts because relations.ts imports writes.ts.
2257
+ */
2258
+ resolveWriteProjection(args) {
2259
+ if (args.select === undefined && args.omit === undefined)
2260
+ return undefined;
2261
+ const columns = relationsMod.resolveProjection(this.ctx, this.table, this.tableMeta, args.select, args.omit, false);
2262
+ if (!columns || columns.length === 0) {
2263
+ // Only an `omit` naming every column gets here (an empty `select` is
2264
+ // refused inside resolveProjection): a write must return SOMETHING, and
2265
+ // an empty RETURNING list is a syntax error on every engine.
2266
+ throw new errors_js_1.ValidationError(`\`omit\` on a write to "${this.table}" leaves no column to return. Name the fields to keep with \`select\` instead.`);
2267
+ }
2268
+ return { columns };
2199
2269
  }
2200
2270
  buildCreateMany(args) {
2201
2271
  return writesMod.buildCreateMany(this.ctx, args);
2202
2272
  }
2203
2273
  buildUpdate(args) {
2204
- return writesMod.buildUpdate(this.ctx, args);
2274
+ return writesMod.buildUpdate(this.ctx, args, this.resolveWriteProjection(args));
2205
2275
  }
2206
2276
  buildDelete(args) {
2207
- return writesMod.buildDelete(this.ctx, args);
2277
+ return writesMod.buildDelete(this.ctx, args, this.resolveWriteProjection(args));
2208
2278
  }
2209
2279
  buildUpsert(args) {
2210
- return writesMod.buildUpsert(this.ctx, args);
2280
+ return writesMod.buildUpsert(this.ctx, args, this.resolveWriteProjection(args));
2211
2281
  }
2212
2282
  buildUpdateMany(args) {
2213
2283
  return writesMod.buildUpdateMany(this.ctx, args);
@@ -2288,7 +2358,7 @@ class QueryInterface {
2288
2358
  }
2289
2359
  }
2290
2360
  const deferred = this.buildFindUnique(args);
2291
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
2361
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
2292
2362
  return deferred.transform(result);
2293
2363
  });
2294
2364
  }
@@ -2312,7 +2382,7 @@ class QueryInterface {
2312
2382
  const proj = (0, batched_loader_js_1.includeKeysForBatching)(this.tableMeta, args.select, args.omit, needed, (0, batched_loader_js_1.defaultProjectionFields)(this.tableMeta, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii')));
2313
2383
  const baseArgs = { ...args, with: undefined, select: proj.select, omit: proj.omit };
2314
2384
  const deferred = this.buildFindUnique(baseArgs);
2315
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
2385
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
2316
2386
  const entity = deferred.transform(result);
2317
2387
  // A miss still walks the `with` tree (compile-only child builds), so the
2318
2388
  // same args throw or pass identically whether or not the row exists,
@@ -2546,7 +2616,7 @@ class QueryInterface {
2546
2616
  }
2547
2617
  }
2548
2618
  const deferred = this.buildFindMany(args);
2549
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
2619
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
2550
2620
  return deferred.transform(result);
2551
2621
  });
2552
2622
  }
@@ -3184,7 +3254,7 @@ class QueryInterface {
3184
3254
  ...args,
3185
3255
  limit: batchSize + 1,
3186
3256
  });
3187
- const speculativeResult = await this.queryWithTimeout(speculativeDeferred.sql, speculativeDeferred.params, args?.timeout, streamPreparedName);
3257
+ const speculativeResult = await this.readWithTimeout(speculativeDeferred.sql, speculativeDeferred.params, args?.timeout, streamPreparedName);
3188
3258
  if (speculativeResult.rows.length <= batchSize) {
3189
3259
  // Small drain, hand over the whole result and return, no cursor needed.
3190
3260
  if (speculativeResult.rows.length > 0)
@@ -3204,7 +3274,8 @@ class QueryInterface {
3204
3274
  // connection, so `pool.query` already IS that connection. The stream rides
3205
3275
  // on it, is never released here, and the dialect is told to emit no
3206
3276
  // transaction control of its own (`ambientTransaction`).
3207
- const client = this.txScoped ? null : await this.acquireConnection();
3277
+ const held = this.txScoped ? null : await this.acquireConnection();
3278
+ const client = held?.client ?? null;
3208
3279
  const conn = client ?? {
3209
3280
  query: async (text, values) => (await this.pool.query(text, values)),
3210
3281
  };
@@ -3215,10 +3286,10 @@ class QueryInterface {
3215
3286
  }
3216
3287
  catch (err) {
3217
3288
  // Wrap pg constraint errors so streaming surfaces typed errors like the rest of the API
3218
- throw (0, errors_js_1.wrapPgError)(err);
3289
+ throw (0, errors_js_1.explainConnectionLoss)((0, errors_js_1.wrapPgError)(err), held?.checkout.lostWith);
3219
3290
  }
3220
3291
  finally {
3221
- client?.release();
3292
+ held?.checkout.release();
3222
3293
  }
3223
3294
  }
3224
3295
  /**
@@ -3343,7 +3414,7 @@ class QueryInterface {
3343
3414
  }
3344
3415
  }
3345
3416
  const deferred = this.buildFindFirst(args);
3346
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
3417
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
3347
3418
  return deferred.transform(result);
3348
3419
  });
3349
3420
  }
@@ -3367,7 +3438,7 @@ class QueryInterface {
3367
3438
  async findFirstOrThrow(args) {
3368
3439
  return this.executeWithMiddleware('findFirstOrThrow', (args ?? {}), async () => {
3369
3440
  const deferred = this.buildFindFirstOrThrow(args);
3370
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
3441
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
3371
3442
  return deferred.transform(result);
3372
3443
  });
3373
3444
  }
@@ -3396,7 +3467,7 @@ class QueryInterface {
3396
3467
  async findUniqueOrThrow(args) {
3397
3468
  return this.executeWithMiddleware('findUniqueOrThrow', args, async () => {
3398
3469
  const deferred = this.buildFindUniqueOrThrow(args);
3399
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
3470
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
3400
3471
  return deferred.transform(result);
3401
3472
  });
3402
3473
  }
@@ -3458,58 +3529,50 @@ class QueryInterface {
3458
3529
  // -------------------------------------------------------------------------
3459
3530
  async nestedCreate(args) {
3460
3531
  const data = args.data;
3532
+ // Validated up front, before any row is written, and then applied by the
3533
+ // nested engine's final read-back, which already speaks select/omit.
3534
+ this.resolveWriteProjection(args);
3535
+ const shape = { select: args.select, omit: args.omit };
3461
3536
  if (this.txScoped) {
3462
3537
  const ctx = this.buildNestedCtx();
3463
- return (0, nested_write_js_1.executeNestedCreate)(ctx, this.table, data);
3538
+ return (0, nested_write_js_1.executeNestedCreate)(ctx, this.table, data, 0, [], shape);
3464
3539
  }
3465
3540
  return this.runInImplicitTx(async (ctx) => {
3466
- const result = await (0, nested_write_js_1.executeNestedCreate)(ctx, this.table, data);
3541
+ const result = await (0, nested_write_js_1.executeNestedCreate)(ctx, this.table, data, 0, [], shape);
3467
3542
  return result;
3468
3543
  });
3469
3544
  }
3470
3545
  async nestedUpdate(args) {
3471
3546
  const data = args.data;
3472
3547
  const where = args.where;
3548
+ this.resolveWriteProjection(args);
3549
+ const shape = { select: args.select, omit: args.omit };
3473
3550
  if (this.txScoped) {
3474
3551
  const ctx = this.buildNestedCtx();
3475
- return (0, nested_write_js_1.executeNestedUpdate)(ctx, this.table, where, data);
3552
+ return (0, nested_write_js_1.executeNestedUpdate)(ctx, this.table, where, data, 0, [], shape);
3476
3553
  }
3477
3554
  return this.runInImplicitTx(async (ctx) => {
3478
- const result = await (0, nested_write_js_1.executeNestedUpdate)(ctx, this.table, where, data);
3555
+ const result = await (0, nested_write_js_1.executeNestedUpdate)(ctx, this.table, where, data, 0, [], shape);
3479
3556
  return result;
3480
3557
  });
3481
3558
  }
3482
3559
  /**
3483
- * Check out a pooled connection, translating a driver failure into a typed
3484
- * Turbine error.
3485
- *
3486
- * `pool.connect()` is where the first-run failures land: wrong password
3487
- * (SQLSTATE 28P01), no such database (3D000), nothing listening
3488
- * (ECONNREFUSED), an unverifiable TLS certificate. Unwrapped, every one of
3489
- * those surfaces from an ordinary `db.users.create({ data: { ...nested } })`
3490
- * as a raw pg `DatabaseError` whose `.code` is a SQLSTATE, on the same
3491
- * property Turbine puts `TURBINE_E0NN` in.
3492
- *
3493
- * client.ts has its own copy for `$transaction` / `connect()`; this one
3494
- * exists because `query/` must not import client.ts (circular dependency).
3495
- * The query paths need no equivalent: `pool.query()` opens the connection
3496
- * itself and rejects with the connect error, which the query boundary
3497
- * already wraps.
3560
+ * Check out a guarded pooled connection, as a typed error when that fails.
3561
+ * Shared with client.ts through checkout.ts (see there for why each part
3562
+ * matters); the cursor stream holds it, and a nested write opens its
3563
+ * implicit transaction on one through `openCheckout`.
3498
3564
  */
3499
- async acquireConnection() {
3500
- try {
3501
- return await this.pool.connect();
3502
- }
3503
- catch (err) {
3504
- throw (0, errors_js_1.wrapPgError)(err);
3505
- }
3565
+ acquireConnection() {
3566
+ return (0, checkout_js_1.acquireConnection)(this.pool);
3506
3567
  }
3507
3568
  async runInImplicitTx(fn) {
3508
- const client = await this.acquireConnection();
3509
- let began = false;
3569
+ // BEGIN runs inside openCheckout, which sends it once more on a fresh
3570
+ // connection when the first one turns out to be dead and releases the
3571
+ // connection itself when BEGIN fails for good. The catch below therefore
3572
+ // only sees a transaction that began, and never emits a stray ROLLBACK on
3573
+ // a connection that opened none.
3574
+ const { client, checkout } = await (0, checkout_js_1.openCheckout)(this.pool, (c) => c.query(this.dialect.beginStatement()));
3510
3575
  try {
3511
- await client.query(this.dialect.beginStatement());
3512
- began = true;
3513
3576
  const { TransactionClient } = await Promise.resolve().then(() => __importStar(require('../client.js')));
3514
3577
  const tx = new TransactionClient(
3515
3578
  // biome-ignore lint/suspicious/noExplicitAny: MiddlewareFn and Middleware are structurally identical
@@ -3533,20 +3596,16 @@ class QueryInterface {
3533
3596
  return result;
3534
3597
  }
3535
3598
  catch (err) {
3536
- // Only roll back a transaction we actually opened: a failed BEGIN must
3537
- // not emit a stray ROLLBACK on a connection that never began one.
3538
- if (began) {
3539
- try {
3540
- await client.query(this.dialect.rollbackStatement());
3541
- }
3542
- catch {
3543
- // Best-effort rollback: connection may have died.
3544
- }
3599
+ try {
3600
+ await client.query(this.dialect.rollbackStatement());
3601
+ }
3602
+ catch {
3603
+ // Best-effort rollback: connection may have died.
3545
3604
  }
3546
- throw err;
3605
+ throw (0, errors_js_1.explainConnectionLoss)(err, checkout.lostWith);
3547
3606
  }
3548
3607
  finally {
3549
- client.release();
3608
+ checkout.release();
3550
3609
  }
3551
3610
  }
3552
3611
  buildNestedCtx() {
@@ -3609,7 +3668,7 @@ class QueryInterface {
3609
3668
  async count(args) {
3610
3669
  return this.executeWithMiddleware('count', (args ?? {}), async () => {
3611
3670
  const deferred = this.buildCount(args);
3612
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
3671
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
3613
3672
  return deferred.transform(result);
3614
3673
  });
3615
3674
  }
@@ -3653,7 +3712,7 @@ class QueryInterface {
3653
3712
  async groupBy(args) {
3654
3713
  return this.executeWithMiddleware('groupBy', args, async () => {
3655
3714
  const deferred = this.buildGroupBy(args);
3656
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
3715
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
3657
3716
  return deferred.transform(result);
3658
3717
  });
3659
3718
  }
@@ -3672,7 +3731,7 @@ class QueryInterface {
3672
3731
  async aggregate(args) {
3673
3732
  return this.executeWithMiddleware('aggregate', args, async () => {
3674
3733
  const deferred = this.buildAggregate(args);
3675
- const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
3734
+ const result = await this.readWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
3676
3735
  return deferred.transform(result);
3677
3736
  });
3678
3737
  }
@@ -108,6 +108,15 @@ export interface QueryEvent {
108
108
  * batch statements are timed individually. Absent for everything else.
109
109
  */
110
110
  batch?: 'pipeline' | 'transaction';
111
+ /**
112
+ * Set on a READ that failed because its connection had already been closed
113
+ * by the server, and that Turbine therefore sent once more on a fresh one.
114
+ * This event carries that first attempt's `error`; the retry reports its own
115
+ * event, so a failed read that was then answered shows as two events and a
116
+ * listener alerting on failed calls can skip the ones marked here. Writes
117
+ * and statements inside a transaction are never retried.
118
+ */
119
+ retried?: true;
111
120
  }
112
121
  export type QueryEventListener = (event: QueryEvent) => void;
113
122
  /** Options passed from TurbineClient to QueryInterface */
@@ -120,6 +120,9 @@ exports.FIND_MANY_STREAM_OPTIONS = {
120
120
  };
121
121
  exports.CREATE_OPTIONS = {
122
122
  data: 'prisma',
123
+ // Field names, so hand-translated; prisma-compat narrows write results itself.
124
+ select: 'prisma',
125
+ omit: 'prisma',
123
126
  timeout: 'native',
124
127
  };
125
128
  exports.CREATE_MANY_OPTIONS = {
@@ -130,6 +133,9 @@ exports.CREATE_MANY_OPTIONS = {
130
133
  exports.UPDATE_OPTIONS = {
131
134
  where: 'prisma',
132
135
  data: 'prisma',
136
+ // Field names, so hand-translated; prisma-compat narrows write results itself.
137
+ select: 'prisma',
138
+ omit: 'prisma',
133
139
  // `{ field, expected }`, and `field` is a FIELD NAME, so it has to be renamed
134
140
  // into turbine's naming space rather than copied. See THE ONE RULE above.
135
141
  optimisticLock: 'prisma',
@@ -146,6 +152,9 @@ exports.UPDATE_MANY_OPTIONS = {
146
152
  };
147
153
  exports.DELETE_OPTIONS = {
148
154
  where: 'prisma',
155
+ // Field names, so hand-translated; prisma-compat narrows write results itself.
156
+ select: 'prisma',
157
+ omit: 'prisma',
149
158
  timeout: 'native',
150
159
  allowFullTableScan: 'native',
151
160
  skipGlobalFilters: 'native',
@@ -158,6 +167,9 @@ exports.DELETE_MANY_OPTIONS = {
158
167
  };
159
168
  exports.UPSERT_OPTIONS = {
160
169
  where: 'prisma',
170
+ // Field names, so hand-translated; prisma-compat narrows write results itself.
171
+ select: 'prisma',
172
+ omit: 'prisma',
161
173
  create: 'prisma',
162
174
  update: 'prisma',
163
175
  timeout: 'native',
@@ -870,12 +870,28 @@ export interface FindManyStreamArgs<T, R extends object = {}, W extends TypedWit
870
870
  */
871
871
  batchSize?: number;
872
872
  }
873
- export interface CreateArgs<T, R extends object = {}> {
873
+ export interface CreateArgs<T, R extends object = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
874
874
  /**
875
875
  * Row data. On typed clients, relation names additionally accept nested
876
876
  * write ops ({@link NestedCreateOp}): `create` / `connect` / `connectOrCreate`.
877
877
  */
878
878
  data: CreateDataInput<T, R>;
879
+ /**
880
+ * Return only these fields of the written row, instead of the whole row.
881
+ * Keys are checked against `T` (see {@link FieldFlags}) and follow the read
882
+ * rules: an unknown name throws E003, a `select` must name at least one field,
883
+ * and it cannot be combined with `omit`. Narrows the `RETURNING` list itself,
884
+ * so the unselected columns never cross the wire, which is the point on a
885
+ * table with a large JSON or text column. Naming a PII-tagged column here is
886
+ * the explicit opt-in that returns it, exactly as on a read.
887
+ */
888
+ select?: S & FieldFlags<T, S>;
889
+ /**
890
+ * Return the written row without these fields. Same rules as `select`;
891
+ * PII-tagged columns stay excluded, as they are from every default write
892
+ * return.
893
+ */
894
+ omit?: O & FieldFlags<T, O>;
879
895
  /** Query timeout in milliseconds. Rejects with an error if exceeded. */
880
896
  timeout?: number;
881
897
  }
@@ -916,7 +932,7 @@ export type UpdateOperatorInput<V> = {
916
932
  export type UpdateInput<T> = {
917
933
  [K in keyof T]?: T[K] | UpdateOperatorInput<T[K]>;
918
934
  };
919
- export interface UpdateArgs<T, R extends object = {}> {
935
+ export interface UpdateArgs<T, R extends object = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
920
936
  /** Row selector. Keys are checked against `T` and `R` (see {@link WhereClause}). */
921
937
  where: WhereClause<T, R>;
922
938
  /**
@@ -925,6 +941,22 @@ export interface UpdateArgs<T, R extends object = {}> {
925
941
  * / `disconnect` / `set` / `delete` / `update` / `upsert`.
926
942
  */
927
943
  data: UpdateDataInput<T, R>;
944
+ /**
945
+ * Return only these fields of the updated row, instead of the whole row.
946
+ * Keys are checked against `T` (see {@link FieldFlags}) and follow the read
947
+ * rules: an unknown name throws E003, a `select` must name at least one field,
948
+ * and it cannot be combined with `omit`. Narrows the `RETURNING` list itself,
949
+ * so the unselected columns never cross the wire, which is the point on a
950
+ * table with a large JSON or text column. Naming a PII-tagged column here is
951
+ * the explicit opt-in that returns it, exactly as on a read.
952
+ */
953
+ select?: S & FieldFlags<T, S>;
954
+ /**
955
+ * Return the updated row without these fields. Same rules as `select`;
956
+ * PII-tagged columns stay excluded, as they are from every default write
957
+ * return.
958
+ */
959
+ omit?: O & FieldFlags<T, O>;
928
960
  /** Query timeout in milliseconds. Rejects with an error if exceeded. */
929
961
  timeout?: number;
930
962
  /**
@@ -974,8 +1006,24 @@ export interface UpdateManyArgs<T, R extends object = {}> {
974
1006
  /** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
975
1007
  skipGlobalFilters?: SkipGlobalFilters;
976
1008
  }
977
- export interface DeleteArgs<T, R extends object = {}> {
1009
+ export interface DeleteArgs<T, R extends object = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
978
1010
  where: WhereClause<T, R>;
1011
+ /**
1012
+ * Return only these fields of the deleted row, instead of the whole row.
1013
+ * Keys are checked against `T` (see {@link FieldFlags}) and follow the read
1014
+ * rules: an unknown name throws E003, a `select` must name at least one field,
1015
+ * and it cannot be combined with `omit`. Narrows the `RETURNING` list itself,
1016
+ * so the unselected columns never cross the wire, which is the point on a
1017
+ * table with a large JSON or text column. Naming a PII-tagged column here is
1018
+ * the explicit opt-in that returns it, exactly as on a read.
1019
+ */
1020
+ select?: S & FieldFlags<T, S>;
1021
+ /**
1022
+ * Return the deleted row without these fields. Same rules as `select`;
1023
+ * PII-tagged columns stay excluded, as they are from every default write
1024
+ * return.
1025
+ */
1026
+ omit?: O & FieldFlags<T, O>;
979
1027
  /** Query timeout in milliseconds. Rejects with an error if exceeded. */
980
1028
  timeout?: number;
981
1029
  /** See {@link UpdateArgs.allowFullTableScan}. */
@@ -992,8 +1040,24 @@ export interface DeleteManyArgs<T, R extends object = {}> {
992
1040
  /** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
993
1041
  skipGlobalFilters?: SkipGlobalFilters;
994
1042
  }
995
- export interface UpsertArgs<T, R extends object = {}> {
1043
+ export interface UpsertArgs<T, R extends object = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
996
1044
  where: WhereClause<T, R>;
1045
+ /**
1046
+ * Return only these fields of the inserted or updated row, instead of the whole row.
1047
+ * Keys are checked against `T` (see {@link FieldFlags}) and follow the read
1048
+ * rules: an unknown name throws E003, a `select` must name at least one field,
1049
+ * and it cannot be combined with `omit`. Narrows the `RETURNING` list itself,
1050
+ * so the unselected columns never cross the wire, which is the point on a
1051
+ * table with a large JSON or text column. Naming a PII-tagged column here is
1052
+ * the explicit opt-in that returns it, exactly as on a read.
1053
+ */
1054
+ select?: S & FieldFlags<T, S>;
1055
+ /**
1056
+ * Return the inserted or updated row without these fields. Same rules as `select`;
1057
+ * PII-tagged columns stay excluded, as they are from every default write
1058
+ * return.
1059
+ */
1060
+ omit?: O & FieldFlags<T, O>;
997
1061
  /** The row to insert when `where` matches nothing. Plain values only: there is no stored value to operate on yet. */
998
1062
  create: Partial<T>;
999
1063
  /**