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.
- package/README.md +4 -4
- package/dist/checkout.d.ts +53 -0
- package/dist/checkout.js +78 -0
- package/dist/cjs/checkout.d.ts +53 -0
- package/dist/cjs/checkout.js +82 -0
- package/dist/cjs/cli/index.js +4 -0
- package/dist/cjs/cli/mcp.js +4 -0
- package/dist/cjs/cli/migrate.js +6 -0
- package/dist/cjs/cli/observe.js +8 -0
- package/dist/cjs/cli/studio.js +13 -1
- package/dist/cjs/client.d.ts +12 -2
- package/dist/cjs/client.js +61 -75
- package/dist/cjs/connection-guard.d.ts +120 -0
- package/dist/cjs/connection-guard.js +191 -0
- package/dist/cjs/errors.d.ts +26 -0
- package/dist/cjs/errors.js +85 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/nested-write.d.ts +12 -2
- package/dist/cjs/nested-write.js +4 -10
- package/dist/cjs/pipeline.js +12 -7
- package/dist/cjs/plan-flip-probe.js +4 -0
- package/dist/cjs/powdb-shared.d.ts +22 -2
- package/dist/cjs/powdb-shared.js +27 -2
- package/dist/cjs/powdb.js +36 -37
- package/dist/cjs/powql.d.ts +51 -6
- package/dist/cjs/powql.js +199 -45
- package/dist/cjs/prisma-compat.js +28 -4
- package/dist/cjs/query/builder.d.ts +44 -24
- package/dist/cjs/query/builder.js +125 -66
- package/dist/cjs/query/deferred.d.ts +9 -0
- package/dist/cjs/query/option-surface.js +12 -0
- package/dist/cjs/query/types.d.ts +68 -4
- package/dist/cjs/query/writes.d.ts +39 -9
- package/dist/cjs/query/writes.js +72 -34
- package/dist/cjs/realtime.d.ts +46 -2
- package/dist/cjs/realtime.js +125 -20
- package/dist/cjs/schema-sql.js +6 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/mcp.js +4 -0
- package/dist/cli/migrate.js +6 -0
- package/dist/cli/observe.js +8 -0
- package/dist/cli/studio.js +13 -1
- package/dist/client.d.ts +12 -2
- package/dist/client.js +62 -76
- package/dist/connection-guard.d.ts +120 -0
- package/dist/connection-guard.js +183 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.js +83 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/nested-write.d.ts +12 -2
- package/dist/nested-write.js +4 -10
- package/dist/pipeline.js +13 -8
- package/dist/plan-flip-probe.js +4 -0
- package/dist/powdb-shared.d.ts +22 -2
- package/dist/powdb-shared.js +25 -2
- package/dist/powdb.js +23 -24
- package/dist/powql.d.ts +51 -6
- package/dist/powql.js +200 -46
- package/dist/prisma-compat.js +28 -4
- package/dist/query/builder.d.ts +44 -24
- package/dist/query/builder.js +126 -67
- package/dist/query/deferred.d.ts +9 -0
- package/dist/query/option-surface.js +12 -0
- package/dist/query/types.d.ts +68 -4
- package/dist/query/writes.d.ts +39 -9
- package/dist/query/writes.js +71 -34
- package/dist/realtime.d.ts +46 -2
- package/dist/realtime.js +125 -20
- package/dist/schema-sql.js +6 -0
- 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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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(() =>
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
3484
|
-
*
|
|
3485
|
-
*
|
|
3486
|
-
*
|
|
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
|
-
|
|
3500
|
-
|
|
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
|
-
|
|
3509
|
-
|
|
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
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
/**
|