turbine-orm 0.27.0 → 0.28.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 +17 -13
- package/dist/cjs/cli/config.js +20 -3
- package/dist/cjs/cli/destructive.js +47 -31
- package/dist/cjs/cli/index.js +273 -71
- package/dist/cjs/cli/mcp.js +788 -0
- package/dist/cjs/cli/migrate.js +95 -20
- package/dist/cjs/cli/studio.js +3 -2
- package/dist/cjs/client.js +267 -34
- package/dist/cjs/dialect.js +2 -0
- package/dist/cjs/generate.js +171 -7
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/introspect.js +177 -4
- package/dist/cjs/query/batched-loader.js +148 -0
- package/dist/cjs/query/builder.js +714 -133
- package/dist/cjs/schema-builder.js +59 -4
- package/dist/cjs/schema-sql.js +315 -6
- package/dist/cjs/seed.js +66 -0
- package/dist/cli/config.d.ts +9 -2
- package/dist/cli/config.js +19 -3
- package/dist/cli/destructive.js +47 -31
- package/dist/cli/index.d.ts +52 -1
- package/dist/cli/index.js +272 -74
- package/dist/cli/mcp.d.ts +17 -0
- package/dist/cli/mcp.js +781 -0
- package/dist/cli/migrate.d.ts +37 -0
- package/dist/cli/migrate.js +92 -20
- package/dist/cli/studio.d.ts +3 -2
- package/dist/cli/studio.js +3 -2
- package/dist/client.d.ts +136 -1
- package/dist/client.js +267 -34
- package/dist/dialect.d.ts +17 -0
- package/dist/dialect.js +2 -0
- package/dist/generate.d.ts +17 -0
- package/dist/generate.js +171 -10
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -0
- package/dist/introspect.d.ts +20 -1
- package/dist/introspect.js +175 -4
- package/dist/query/batched-loader.d.ts +29 -2
- package/dist/query/batched-loader.js +148 -1
- package/dist/query/builder.d.ts +156 -8
- package/dist/query/builder.js +715 -134
- package/dist/query/index.d.ts +1 -1
- package/dist/query/types.d.ts +113 -8
- package/dist/schema-builder.d.ts +73 -8
- package/dist/schema-builder.js +59 -4
- package/dist/schema-sql.d.ts +67 -0
- package/dist/schema-sql.js +310 -6
- package/dist/schema.d.ts +53 -0
- package/dist/seed.d.ts +4 -0
- package/dist/seed.js +63 -0
- package/package.json +2 -3
package/dist/cli/migrate.d.ts
CHANGED
|
@@ -97,6 +97,29 @@ export declare function createMigration(migrationsDir: string, name: string, aut
|
|
|
97
97
|
* without contending on a single hardcoded lock ID.
|
|
98
98
|
*/
|
|
99
99
|
export declare function deriveLockId(databaseName: string): number;
|
|
100
|
+
export interface ChecksumMismatch {
|
|
101
|
+
name: string;
|
|
102
|
+
expected: string;
|
|
103
|
+
actual: string;
|
|
104
|
+
/** 'modified' if file changed, 'missing' if file deleted */
|
|
105
|
+
type: 'modified' | 'missing';
|
|
106
|
+
}
|
|
107
|
+
export interface MigrationDeployPlan {
|
|
108
|
+
pending: MigrationFile[];
|
|
109
|
+
mismatches: ChecksumMismatch[];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Build a deploy plan from local migration files and applied migration rows.
|
|
113
|
+
* This is pure file-system planning; callers with a database connection should
|
|
114
|
+
* use `inspectMigrationDeploy()` to preserve legacy checksum upgrades.
|
|
115
|
+
*/
|
|
116
|
+
export declare function planMigrationDeploy(migrationsDir: string, applied: AppliedMigration[]): MigrationDeployPlan;
|
|
117
|
+
/**
|
|
118
|
+
* Inspect deploy status without applying migrations.
|
|
119
|
+
*/
|
|
120
|
+
export declare function inspectMigrationDeploy(connectionString: string, migrationsDir: string, options?: {
|
|
121
|
+
dialect?: Dialect;
|
|
122
|
+
}): Promise<MigrationDeployPlan>;
|
|
100
123
|
/**
|
|
101
124
|
* Apply all pending migrations (UP).
|
|
102
125
|
*
|
|
@@ -126,6 +149,20 @@ export declare function migrateUp(connectionString: string, migrationsDir: strin
|
|
|
126
149
|
error: string;
|
|
127
150
|
}>;
|
|
128
151
|
}>;
|
|
152
|
+
/**
|
|
153
|
+
* Production migration apply. This intentionally applies files as written and
|
|
154
|
+
* never performs interactive destructive confirmation.
|
|
155
|
+
*/
|
|
156
|
+
export declare function migrateDeploy(connectionString: string, migrationsDir: string, options?: {
|
|
157
|
+
adapter?: DatabaseAdapter;
|
|
158
|
+
dialect?: Dialect;
|
|
159
|
+
}): Promise<{
|
|
160
|
+
applied: MigrationFile[];
|
|
161
|
+
errors: Array<{
|
|
162
|
+
file: MigrationFile;
|
|
163
|
+
error: string;
|
|
164
|
+
}>;
|
|
165
|
+
}>;
|
|
129
166
|
/**
|
|
130
167
|
* Rollback the last N migrations (DOWN).
|
|
131
168
|
*
|
package/dist/cli/migrate.js
CHANGED
|
@@ -280,6 +280,85 @@ async function validateChecksums(client, migrationsDir, dialect = postgresDialec
|
|
|
280
280
|
}
|
|
281
281
|
return mismatches;
|
|
282
282
|
}
|
|
283
|
+
function formatChecksumMismatchError(mismatches) {
|
|
284
|
+
const modified = mismatches.filter((m) => m.type === 'modified');
|
|
285
|
+
const missing = mismatches.filter((m) => m.type === 'missing');
|
|
286
|
+
const lines = [
|
|
287
|
+
'[turbine] Migration drift detected — refusing to apply pending migrations.',
|
|
288
|
+
'',
|
|
289
|
+
'Applied migrations should be immutable. The following files no longer match their applied state:',
|
|
290
|
+
'',
|
|
291
|
+
];
|
|
292
|
+
for (const m of modified) {
|
|
293
|
+
lines.push(` - ${m.name}.sql (modified on disk)`);
|
|
294
|
+
}
|
|
295
|
+
for (const m of missing) {
|
|
296
|
+
lines.push(` - ${m.name}.sql (deleted from disk)`);
|
|
297
|
+
}
|
|
298
|
+
lines.push('');
|
|
299
|
+
lines.push('Fix one of these:');
|
|
300
|
+
lines.push(' 1. Restore the file(s) to their original content, OR');
|
|
301
|
+
lines.push(' 2. Roll back the affected migrations with `npx turbine migrate down`, OR');
|
|
302
|
+
lines.push(' 3. Pass `--allow-drift` to bypass this check (advanced — make sure you know what you are doing).');
|
|
303
|
+
return lines.join('\n');
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Build a deploy plan from local migration files and applied migration rows.
|
|
307
|
+
* This is pure file-system planning; callers with a database connection should
|
|
308
|
+
* use `inspectMigrationDeploy()` to preserve legacy checksum upgrades.
|
|
309
|
+
*/
|
|
310
|
+
export function planMigrationDeploy(migrationsDir, applied) {
|
|
311
|
+
const allFiles = listMigrationFiles(migrationsDir);
|
|
312
|
+
const fileMap = new Map(allFiles.map((f) => [f.name, f]));
|
|
313
|
+
const appliedNames = new Set(applied.map((m) => m.name));
|
|
314
|
+
const mismatches = [];
|
|
315
|
+
for (const migration of applied) {
|
|
316
|
+
const file = fileMap.get(migration.name);
|
|
317
|
+
if (!file) {
|
|
318
|
+
mismatches.push({
|
|
319
|
+
name: migration.name,
|
|
320
|
+
expected: migration.checksum,
|
|
321
|
+
actual: '',
|
|
322
|
+
type: 'missing',
|
|
323
|
+
});
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const currentHash = checksum(readFileSync(file.path, 'utf-8'));
|
|
327
|
+
if (currentHash !== migration.checksum && !isLegacyChecksum(migration.checksum)) {
|
|
328
|
+
mismatches.push({
|
|
329
|
+
name: migration.name,
|
|
330
|
+
expected: migration.checksum,
|
|
331
|
+
actual: currentHash,
|
|
332
|
+
type: 'modified',
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
pending: allFiles.filter((f) => !appliedNames.has(f.name)),
|
|
338
|
+
mismatches,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Inspect deploy status without applying migrations.
|
|
343
|
+
*/
|
|
344
|
+
export async function inspectMigrationDeploy(connectionString, migrationsDir, options) {
|
|
345
|
+
const client = new pg.Client({ connectionString });
|
|
346
|
+
await client.connect();
|
|
347
|
+
const dialect = options?.dialect ?? postgresDialect;
|
|
348
|
+
try {
|
|
349
|
+
await ensureTrackingTable(client, dialect);
|
|
350
|
+
const mismatches = await validateChecksums(client, migrationsDir, dialect);
|
|
351
|
+
const applied = await getAppliedMigrations(client, dialect);
|
|
352
|
+
const appliedNames = new Set(applied.map((m) => m.name));
|
|
353
|
+
return {
|
|
354
|
+
pending: listMigrationFiles(migrationsDir).filter((f) => !appliedNames.has(f.name)),
|
|
355
|
+
mismatches,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
finally {
|
|
359
|
+
await client.end();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
283
362
|
/**
|
|
284
363
|
* Apply all pending migrations (UP).
|
|
285
364
|
*
|
|
@@ -323,26 +402,7 @@ export async function migrateUp(connectionString, migrationsDir, options) {
|
|
|
323
402
|
if (!allowDrift) {
|
|
324
403
|
const mismatches = await validateChecksums(client, migrationsDir, dialect);
|
|
325
404
|
if (mismatches.length > 0) {
|
|
326
|
-
|
|
327
|
-
const missing = mismatches.filter((m) => m.type === 'missing');
|
|
328
|
-
const lines = [
|
|
329
|
-
'[turbine] Migration drift detected — refusing to apply pending migrations.',
|
|
330
|
-
'',
|
|
331
|
-
'Applied migrations should be immutable. The following files no longer match their applied state:',
|
|
332
|
-
'',
|
|
333
|
-
];
|
|
334
|
-
for (const m of modified) {
|
|
335
|
-
lines.push(` - ${m.name}.sql (modified on disk)`);
|
|
336
|
-
}
|
|
337
|
-
for (const m of missing) {
|
|
338
|
-
lines.push(` - ${m.name}.sql (deleted from disk)`);
|
|
339
|
-
}
|
|
340
|
-
lines.push('');
|
|
341
|
-
lines.push('Fix one of these:');
|
|
342
|
-
lines.push(' 1. Restore the file(s) to their original content, OR');
|
|
343
|
-
lines.push(' 2. Roll back the affected migrations with `npx turbine migrate down`, OR');
|
|
344
|
-
lines.push(' 3. Pass `--allow-drift` to bypass this check (advanced — make sure you know what you are doing).');
|
|
345
|
-
throw new MigrationError(lines.join('\n'));
|
|
405
|
+
throw new MigrationError(formatChecksumMismatchError(mismatches));
|
|
346
406
|
}
|
|
347
407
|
}
|
|
348
408
|
const applied = await getAppliedMigrations(client, dialect);
|
|
@@ -416,6 +476,18 @@ export async function migrateUp(connectionString, migrationsDir, options) {
|
|
|
416
476
|
await client.end();
|
|
417
477
|
}
|
|
418
478
|
}
|
|
479
|
+
/**
|
|
480
|
+
* Production migration apply. This intentionally applies files as written and
|
|
481
|
+
* never performs interactive destructive confirmation.
|
|
482
|
+
*/
|
|
483
|
+
export async function migrateDeploy(connectionString, migrationsDir, options) {
|
|
484
|
+
return migrateUp(connectionString, migrationsDir, {
|
|
485
|
+
allowDrift: false,
|
|
486
|
+
allowDestructive: true,
|
|
487
|
+
adapter: options?.adapter,
|
|
488
|
+
dialect: options?.dialect,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
419
491
|
/**
|
|
420
492
|
* Rollback the last N migrations (DOWN).
|
|
421
493
|
*
|
package/dist/cli/studio.d.ts
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* raw-SQL input surface — the Query tab builds `findMany` args that are
|
|
7
7
|
* validated against introspected metadata and compiled by QueryInterface
|
|
8
8
|
* (`/api/builder`). Pure Node (built-in `http` module), no runtime
|
|
9
|
-
* dependencies beyond `pg
|
|
9
|
+
* dependencies beyond `pg`. CLI defaults to 127.0.0.1 and refuses non-loopback
|
|
10
|
+
* hosts unless `npx turbine studio --allow-remote` is set.
|
|
10
11
|
*
|
|
11
12
|
* Security model:
|
|
12
|
-
* •
|
|
13
|
+
* • Loopback by default; CLI refuses non-loopback without --allow-remote
|
|
13
14
|
* • Random auth token generated per process, required in Cookie header
|
|
14
15
|
* • No SQL input surface at all — every identifier in a builder request is
|
|
15
16
|
* validated against the introspected schema; all values are $N params
|
package/dist/cli/studio.js
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* raw-SQL input surface — the Query tab builds `findMany` args that are
|
|
7
7
|
* validated against introspected metadata and compiled by QueryInterface
|
|
8
8
|
* (`/api/builder`). Pure Node (built-in `http` module), no runtime
|
|
9
|
-
* dependencies beyond `pg
|
|
9
|
+
* dependencies beyond `pg`. CLI defaults to 127.0.0.1 and refuses non-loopback
|
|
10
|
+
* hosts unless `npx turbine studio --allow-remote` is set.
|
|
10
11
|
*
|
|
11
12
|
* Security model:
|
|
12
|
-
* •
|
|
13
|
+
* • Loopback by default; CLI refuses non-loopback without --allow-remote
|
|
13
14
|
* • Random auth token generated per process, required in Cookie header
|
|
14
15
|
* • No SQL input surface at all — every identifier in a builder request is
|
|
15
16
|
* validated against the introspected schema; all values are $N params
|
package/dist/client.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { type Dialect } from './dialect.js';
|
|
|
26
26
|
import { type ErrorMessageMode } from './errors.js';
|
|
27
27
|
import { type ObserveConfig, type ObserveHandle } from './observe.js';
|
|
28
28
|
import { type PipelineOptions, type PipelineResults } from './pipeline.js';
|
|
29
|
-
import { type DeferredQuery, type QueryEventListener, QueryInterface, type QueryInterfaceOptions, type RelationLoadStrategy } from './query/index.js';
|
|
29
|
+
import { type DeferredQuery, type GlobalFilters, type QueryEventListener, QueryInterface, type QueryInterfaceOptions, type RelationLoadStrategy } from './query/index.js';
|
|
30
30
|
import { type NotificationHandler, type Subscription } from './realtime.js';
|
|
31
31
|
import type { SchemaMetadata } from './schema.js';
|
|
32
32
|
import { TypedSqlQuery } from './typed-sql.js';
|
|
@@ -211,6 +211,54 @@ export interface TurbineConfig {
|
|
|
211
211
|
sqlCache?: boolean;
|
|
212
212
|
/** SQL dialect implementation. Defaults to PostgreSQL. Internal Phase-1 seam for dialect packages. */
|
|
213
213
|
dialect?: Dialect;
|
|
214
|
+
/**
|
|
215
|
+
* Read replicas. When set, read-only operations issued outside a transaction
|
|
216
|
+
* (`findMany`, `findFirst`, `findUnique`, `*OrThrow`, `count`, `aggregate`,
|
|
217
|
+
* `groupBy`, `findManyStream`) are round-robin load-balanced across these
|
|
218
|
+
* pools; the primary handles them along with every write. ALL writes,
|
|
219
|
+
* `$transaction` bodies, `pipeline`, `raw`/`sql`, `$listen`/`$notify`, and
|
|
220
|
+
* observability flushes always use the primary.
|
|
221
|
+
*
|
|
222
|
+
* - `string` entries are connection strings — Turbine constructs an owned
|
|
223
|
+
* `pg.Pool` for each (same pool-tuning knobs as the primary, and the same
|
|
224
|
+
* one-time, constructor-gated type-parser registration). `disconnect()`
|
|
225
|
+
* closes them.
|
|
226
|
+
* - `PgCompatPool` entries are external pools (Neon, Vercel, a shared
|
|
227
|
+
* `pg.Pool`) — Turbine registers no type parsers on them and never ends
|
|
228
|
+
* them; the caller owns their lifecycle.
|
|
229
|
+
*
|
|
230
|
+
* Use `client.$primary()` to get a view of the client that pins every
|
|
231
|
+
* operation (reads included) to the primary — e.g. to read your own write
|
|
232
|
+
* without replication lag. Omitting `replicas` (or passing `[]`) leaves the
|
|
233
|
+
* default single-pool path completely unchanged.
|
|
234
|
+
*/
|
|
235
|
+
replicas?: readonly (string | PgCompatPool)[];
|
|
236
|
+
/**
|
|
237
|
+
* Automatic WHERE filters applied to every query, keyed by table accessor
|
|
238
|
+
* (`db[name]`). Each value is AND-merged into the compiled WHERE of every
|
|
239
|
+
* read and mutation on that table — and into every relation subquery that
|
|
240
|
+
* targets it — implementing soft-delete and multi-tenancy without repeating
|
|
241
|
+
* the predicate at each call site.
|
|
242
|
+
*
|
|
243
|
+
* - A `WhereClause` value is a static filter (e.g. `{ deletedAt: null }`).
|
|
244
|
+
* - A `() => WhereClause` value is evaluated at query-build time, so a
|
|
245
|
+
* closure over per-request state (the current tenant id) yields a
|
|
246
|
+
* request-scoped filter.
|
|
247
|
+
*
|
|
248
|
+
* `create`/`createMany` are never filtered. A per-query
|
|
249
|
+
* `skipGlobalFilters: true | string[]` opts out. The empty-`where` guard on
|
|
250
|
+
* `update`/`delete` still checks the USER-supplied `where`, so a global
|
|
251
|
+
* filter never turns an unguarded mass mutation into an allowed one.
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```ts
|
|
255
|
+
* const db = turbine({ url, schema, globalFilters: {
|
|
256
|
+
* posts: { deletedAt: null }, // soft-delete
|
|
257
|
+
* orders: () => ({ tenantId: currentTenant() }), // per-request tenancy
|
|
258
|
+
* }});
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
globalFilters?: GlobalFilters;
|
|
214
262
|
}
|
|
215
263
|
/** Parameters passed to middleware functions */
|
|
216
264
|
export interface MiddlewareParams {
|
|
@@ -314,6 +362,25 @@ export declare class TurbineClient {
|
|
|
314
362
|
private readonly ownsPool;
|
|
315
363
|
/** Active LISTEN subscriptions — torn down on disconnect() so it never hangs */
|
|
316
364
|
private readonly activeSubscriptions;
|
|
365
|
+
/**
|
|
366
|
+
* Read-replica pools in round-robin order. Empty when no replicas are
|
|
367
|
+
* configured, in which case `table()` takes the original single-pool path.
|
|
368
|
+
*/
|
|
369
|
+
private readonly replicaPools;
|
|
370
|
+
/**
|
|
371
|
+
* The subset of {@link replicaPools} that Turbine created from connection
|
|
372
|
+
* strings and must close on `disconnect()`. External replica pools are not
|
|
373
|
+
* listed here (caller owns their lifecycle).
|
|
374
|
+
*/
|
|
375
|
+
private readonly ownedReplicaPools;
|
|
376
|
+
/** Rotating index for round-robin replica selection (advances per read op). */
|
|
377
|
+
private replicaCursor;
|
|
378
|
+
/** Per-replica `table → QueryInterface` caches, indexed like {@link replicaPools}. */
|
|
379
|
+
private readonly replicaTableCaches;
|
|
380
|
+
/** Cache of per-table routing proxies (only used when replicas are present). */
|
|
381
|
+
private readonly routingProxyCache;
|
|
382
|
+
/** Lazily-built, cached primary-only view returned by {@link $primary}. */
|
|
383
|
+
private primaryView?;
|
|
317
384
|
constructor(config: TurbineConfig | undefined, schema: SchemaMetadata);
|
|
318
385
|
/**
|
|
319
386
|
* Register a middleware function that runs around every query.
|
|
@@ -353,8 +420,49 @@ export declare class TurbineClient {
|
|
|
353
420
|
/**
|
|
354
421
|
* Get a QueryInterface for a table.
|
|
355
422
|
* Results are cached — calling `table('users')` twice returns the same instance.
|
|
423
|
+
*
|
|
424
|
+
* When read replicas are configured, this returns a thin routing proxy: the
|
|
425
|
+
* read-only operations in {@link READ_OPERATIONS} are dispatched to a
|
|
426
|
+
* round-robin replica-bound QueryInterface (so an entire read — base rows and
|
|
427
|
+
* any batched sub-queries — runs against a single consistent replica), while
|
|
428
|
+
* writes and every other member fall through to the primary-bound instance.
|
|
429
|
+
* With no replicas the original single-pool instance is returned directly.
|
|
356
430
|
*/
|
|
357
431
|
table<T extends object = Record<string, unknown>>(name: string): QueryInterface<T>;
|
|
432
|
+
/** Get (and cache) the primary-pool-bound QueryInterface for a table. */
|
|
433
|
+
private primaryTableQI;
|
|
434
|
+
/**
|
|
435
|
+
* Advance the round-robin cursor and return the QueryInterface bound to the
|
|
436
|
+
* selected replica pool for `name` (cached per replica).
|
|
437
|
+
*/
|
|
438
|
+
private nextReplicaTableQI;
|
|
439
|
+
/** Construct a QueryInterface bound to `pool` (honoring any injected factory). */
|
|
440
|
+
private buildTableQI;
|
|
441
|
+
/**
|
|
442
|
+
* Build the read/write routing proxy for a table. The proxy targets the
|
|
443
|
+
* primary QueryInterface (so writes, `build*`, and every non-read member work
|
|
444
|
+
* unchanged); read operations are intercepted and dispatched to a replica.
|
|
445
|
+
*/
|
|
446
|
+
private createRoutingAccessor;
|
|
447
|
+
/**
|
|
448
|
+
* Return a view of this client that pins EVERY operation — reads included —
|
|
449
|
+
* to the primary pool, bypassing replica routing. Use it to read your own
|
|
450
|
+
* write without replication lag, or for any read that must see the latest
|
|
451
|
+
* committed data.
|
|
452
|
+
*
|
|
453
|
+
* The view shares the primary pool, schema, dialect, query options, and
|
|
454
|
+
* middleware; it owns nothing, so its `disconnect()` is a no-op. When no
|
|
455
|
+
* replicas are configured this simply returns the client itself (already
|
|
456
|
+
* primary-only). The view is cached — repeated calls return the same instance.
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```ts
|
|
460
|
+
* await db.users.create({ data: { email: 'a@b.com' } });
|
|
461
|
+
* // Read-after-write: guaranteed to see the row just inserted.
|
|
462
|
+
* const user = await db.$primary().users.findFirst({ where: { email: 'a@b.com' } });
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
$primary(): TurbineClient;
|
|
358
466
|
/**
|
|
359
467
|
* Execute multiple queries in a single database round-trip.
|
|
360
468
|
*
|
|
@@ -449,6 +557,33 @@ export declare class TurbineClient {
|
|
|
449
557
|
* ```
|
|
450
558
|
*/
|
|
451
559
|
$transaction<R>(fn: (tx: TransactionClient) => Promise<R>, options?: TransactionOptions): Promise<R>;
|
|
560
|
+
/**
|
|
561
|
+
* Batch form — run a tuple of {@link DeferredQuery} objects (produced by the
|
|
562
|
+
* `build*()` methods, e.g. `db.users.buildFindMany(...)`) atomically inside a
|
|
563
|
+
* single `BEGIN…COMMIT` on one connection. Returns a positionally-typed tuple
|
|
564
|
+
* of each query's transformed result; any failure rolls the whole batch back.
|
|
565
|
+
*
|
|
566
|
+
* Unlike {@link pipeline}, this never uses the extended-query pipeline
|
|
567
|
+
* protocol — it executes sequentially on the transaction connection, so it is
|
|
568
|
+
* safe on every driver (including HTTP/serverless pools).
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* ```ts
|
|
572
|
+
* const [user, count] = await db.$transaction([
|
|
573
|
+
* db.users.buildFindUnique({ where: { id: 1 } }),
|
|
574
|
+
* db.posts.buildCount({ where: { userId: 1 } }),
|
|
575
|
+
* ]);
|
|
576
|
+
* ```
|
|
577
|
+
*/
|
|
578
|
+
$transaction<T extends readonly DeferredQuery<unknown>[]>(queries: readonly [...T]): Promise<PipelineResults<T>>;
|
|
579
|
+
/**
|
|
580
|
+
* Execute a batch of {@link DeferredQuery} objects atomically inside one
|
|
581
|
+
* transaction. Backs the `$transaction([...])` array overload. Reuses the raw
|
|
582
|
+
* {@link transaction} machinery (BEGIN/COMMIT/ROLLBACK + connection release);
|
|
583
|
+
* queries run sequentially on the single transaction connection and each
|
|
584
|
+
* result is passed through its query's `transform`.
|
|
585
|
+
*/
|
|
586
|
+
private transactionBatch;
|
|
452
587
|
/**
|
|
453
588
|
* Convenience wrapper around `$transaction` for the multi-tenant / RLS case:
|
|
454
589
|
* runs `fn` inside a transaction with the given session GUCs applied via
|