turbine-orm 0.49.0 → 0.50.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 +122 -39
- package/dist/cjs/adapters/cockroachdb.d.ts +39 -0
- package/dist/cjs/adapters/index.d.ts +110 -0
- package/dist/cjs/adapters/yugabytedb.d.ts +51 -0
- package/dist/cjs/cli/config.d.ts +181 -0
- package/dist/cjs/cli/config.js +32 -6
- package/dist/cjs/cli/destructive.d.ts +38 -0
- package/dist/cjs/cli/index.d.ts +359 -0
- package/dist/cjs/cli/index.js +228 -56
- package/dist/cjs/cli/loader.d.ts +61 -0
- package/dist/cjs/cli/mcp.d.ts +42 -0
- package/dist/cjs/cli/migrate.d.ts +356 -0
- package/dist/cjs/cli/migrate.js +131 -40
- package/dist/cjs/cli/observe-ui.d.ts +1 -0
- package/dist/cjs/cli/observe-ui.js +14 -5
- package/dist/cjs/cli/observe.d.ts +25 -0
- package/dist/cjs/cli/observe.js +49 -12
- package/dist/cjs/cli/pii-tags.d.ts +53 -0
- package/dist/cjs/cli/prisma-report.d.ts +33 -0
- package/dist/cjs/cli/prisma-report.js +73 -0
- package/dist/cjs/cli/prisma-resolve.d.ts +106 -0
- package/dist/cjs/cli/prisma-resolve.js +1 -0
- package/dist/cjs/cli/prisma-schema.d.ts +176 -0
- package/dist/cjs/cli/prisma-schema.js +82 -4
- package/dist/cjs/cli/rate-limit.d.ts +32 -0
- package/dist/cjs/cli/rate-limit.js +45 -0
- package/dist/cjs/cli/studio-demo.d.ts +43 -0
- package/dist/cjs/cli/studio-ui.generated.d.ts +1 -0
- package/dist/cjs/cli/studio.d.ts +207 -0
- package/dist/cjs/cli/studio.js +136 -71
- package/dist/cjs/cli/ui.d.ts +73 -0
- package/dist/cjs/cli/ui.js +51 -9
- package/dist/cjs/client.d.ts +837 -0
- package/dist/cjs/client.js +3 -0
- package/dist/cjs/dialect.d.ts +516 -0
- package/dist/cjs/dialect.js +37 -12
- package/dist/cjs/errors.d.ts +370 -0
- package/dist/cjs/generate.d.ts +137 -0
- package/dist/cjs/generate.js +39 -6
- package/dist/cjs/index-advisor.d.ts +153 -0
- package/dist/cjs/index-stats.d.ts +384 -0
- package/dist/cjs/index.d.ts +55 -0
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/introspect.d.ts +269 -0
- package/dist/cjs/mssql.d.ts +232 -0
- package/dist/cjs/mssql.js +6 -0
- package/dist/cjs/mysql.d.ts +173 -0
- package/dist/cjs/mysql.js +16 -0
- package/dist/cjs/nested-write.d.ts +96 -0
- package/dist/cjs/nested-write.js +414 -24
- package/dist/cjs/observe.d.ts +115 -0
- package/dist/cjs/optional-peer-import.d.cts +72 -0
- package/dist/cjs/pipeline-submittable.d.ts +93 -0
- package/dist/cjs/pipeline.d.ts +71 -0
- package/dist/cjs/powdb-introspect.d.ts +84 -0
- package/dist/cjs/powdb.d.ts +931 -0
- package/dist/cjs/powdb.js +106 -21
- package/dist/cjs/powql.d.ts +592 -0
- package/dist/cjs/powql.js +42 -6
- package/dist/cjs/prisma-compat.d.ts +283 -0
- package/dist/cjs/prisma-compat.js +167 -9
- package/dist/cjs/query/aggregates.d.ts +92 -0
- package/dist/cjs/query/aggregates.js +7 -3
- package/dist/cjs/query/batched-loader.d.ts +193 -0
- package/dist/cjs/query/builder.d.ts +849 -0
- package/dist/cjs/query/builder.js +571 -65
- package/dist/cjs/query/compound-unique.d.ts +51 -0
- package/dist/cjs/query/deferred.d.ts +223 -0
- package/dist/cjs/query/filters.d.ts +201 -0
- package/dist/cjs/query/index.d.ts +14 -0
- package/dist/cjs/query/index.js +6 -1
- package/dist/cjs/query/relations.d.ts +609 -0
- package/dist/cjs/query/relations.js +693 -46
- package/dist/cjs/query/types.d.ts +1300 -0
- package/dist/cjs/query/utils.d.ts +209 -0
- package/dist/cjs/query/utils.js +208 -1
- package/dist/cjs/query/warn-registry.d.ts +68 -0
- package/dist/cjs/query/warn-registry.js +9 -0
- package/dist/cjs/query/where-compile.d.ts +139 -0
- package/dist/cjs/query/where.d.ts +548 -0
- package/dist/cjs/query/where.js +58 -22
- package/dist/cjs/query/writes.d.ts +172 -0
- package/dist/cjs/query/writes.js +105 -12
- package/dist/cjs/realtime.d.ts +70 -0
- package/dist/cjs/schema-builder.d.ts +354 -0
- package/dist/cjs/schema-metadata.d.ts +83 -0
- package/dist/cjs/schema-sql.d.ts +217 -0
- package/dist/cjs/schema-sql.js +23 -5
- package/dist/cjs/schema.d.ts +356 -0
- package/dist/cjs/schema.js +125 -0
- package/dist/cjs/seed.d.ts +15 -0
- package/dist/cjs/serverless.d.ts +142 -0
- package/dist/cjs/sqlite.d.ts +143 -0
- package/dist/cjs/sqlite.js +4 -0
- package/dist/cjs/typed-sql.d.ts +102 -0
- package/dist/cli/config.d.ts +18 -4
- package/dist/cli/config.js +31 -6
- package/dist/cli/index.d.ts +123 -0
- package/dist/cli/index.js +223 -58
- package/dist/cli/migrate.d.ts +59 -10
- package/dist/cli/migrate.js +128 -41
- package/dist/cli/observe-ui.d.ts +1 -1
- package/dist/cli/observe-ui.js +14 -5
- package/dist/cli/observe.d.ts +7 -1
- package/dist/cli/observe.js +48 -12
- package/dist/cli/prisma-report.d.ts +14 -0
- package/dist/cli/prisma-report.js +72 -0
- package/dist/cli/prisma-resolve.d.ts +6 -0
- package/dist/cli/prisma-resolve.js +1 -0
- package/dist/cli/prisma-schema.d.ts +62 -2
- package/dist/cli/prisma-schema.js +81 -4
- package/dist/cli/rate-limit.d.ts +32 -0
- package/dist/cli/rate-limit.js +40 -0
- package/dist/cli/studio.d.ts +5 -5
- package/dist/cli/studio.js +135 -70
- package/dist/cli/ui.d.ts +1 -1
- package/dist/cli/ui.js +51 -9
- package/dist/client.d.ts +40 -0
- package/dist/client.js +3 -0
- package/dist/dialect.d.ts +17 -1
- package/dist/dialect.js +37 -12
- package/dist/generate.js +40 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mssql.js +6 -0
- package/dist/mysql.js +16 -0
- package/dist/nested-write.d.ts +2 -0
- package/dist/nested-write.js +415 -25
- package/dist/powdb.d.ts +4 -2
- package/dist/powdb.js +106 -21
- package/dist/powql.d.ts +5 -0
- package/dist/powql.js +42 -6
- package/dist/prisma-compat.d.ts +2 -0
- package/dist/prisma-compat.js +166 -8
- package/dist/query/aggregates.js +7 -3
- package/dist/query/builder.d.ts +292 -21
- package/dist/query/builder.js +570 -64
- package/dist/query/deferred.d.ts +39 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +173 -5
- package/dist/query/relations.js +688 -47
- package/dist/query/types.d.ts +123 -39
- package/dist/query/utils.d.ts +116 -0
- package/dist/query/utils.js +198 -0
- package/dist/query/warn-registry.d.ts +9 -0
- package/dist/query/warn-registry.js +9 -0
- package/dist/query/where.d.ts +38 -1
- package/dist/query/where.js +58 -23
- package/dist/query/writes.d.ts +42 -1
- package/dist/query/writes.js +104 -13
- package/dist/schema-sql.d.ts +14 -0
- package/dist/schema-sql.js +23 -5
- package/dist/schema.d.ts +38 -0
- package/dist/schema.js +123 -0
- package/dist/sqlite.js +4 -0
- package/package.json +77 -28
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm — TurbineClient
|
|
3
|
+
*
|
|
4
|
+
* The main entry point for the Turbine TypeScript SDK.
|
|
5
|
+
* Manages connection pooling and provides typed table accessors.
|
|
6
|
+
*
|
|
7
|
+
* Schema-driven: call `table<T>(name)` to get a QueryInterface for any
|
|
8
|
+
* table in the introspected schema. Generated clients extend this with
|
|
9
|
+
* typed properties (e.g. `db.users`, `db.posts`).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // With generated client (recommended):
|
|
14
|
+
* import { turbine } from './generated/turbine';
|
|
15
|
+
* const db = turbine({ connectionString: process.env.DATABASE_URL });
|
|
16
|
+
* const user = await db.users.findUnique({ where: { id: 1 } });
|
|
17
|
+
*
|
|
18
|
+
* // With base client (dynamic):
|
|
19
|
+
* import { TurbineClient } from 'turbine-orm';
|
|
20
|
+
* const db = new TurbineClient({ connectionString: '...' }, schema);
|
|
21
|
+
* const users = db.table<User>('users');
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
import pg from 'pg';
|
|
25
|
+
import { type Dialect } from './dialect.js';
|
|
26
|
+
import { type ErrorMessageMode } from './errors.js';
|
|
27
|
+
import { type ObserveConfig, type ObserveHandle } from './observe.js';
|
|
28
|
+
import { type PipelineOptions, type PipelineResults } from './pipeline.js';
|
|
29
|
+
import { type DeferredQuery, type GlobalFilters, type QueryEventListener, QueryInterface, type QueryInterfaceOptions, type RelationLoadStrategy } from './query/index.js';
|
|
30
|
+
import { type NotificationHandler, type Subscription } from './realtime.js';
|
|
31
|
+
import type { SchemaMetadata } from './schema.js';
|
|
32
|
+
import { TypedSqlQuery } from './typed-sql.js';
|
|
33
|
+
export interface RetryOptions {
|
|
34
|
+
maxAttempts?: number;
|
|
35
|
+
baseDelay?: number;
|
|
36
|
+
maxDelay?: number;
|
|
37
|
+
onRetry?: (error: unknown, attempt: number) => void;
|
|
38
|
+
}
|
|
39
|
+
export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Minimal pg-compatible query result.
|
|
42
|
+
* `pg.Pool`, `@neondatabase/serverless` Pool, `@vercel/postgres` Pool and
|
|
43
|
+
* any driver speaking the node-postgres API all satisfy this shape.
|
|
44
|
+
*/
|
|
45
|
+
export interface PgCompatQueryResult<R = Record<string, unknown>> {
|
|
46
|
+
rows: R[];
|
|
47
|
+
rowCount: number | null;
|
|
48
|
+
fields?: Array<{
|
|
49
|
+
name: string;
|
|
50
|
+
dataTypeID: number;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Minimal pg-compatible client used by TurbineClient for transactions.
|
|
55
|
+
* `pg.PoolClient` satisfies this; so do Neon and Vercel's equivalents.
|
|
56
|
+
*/
|
|
57
|
+
export interface PgCompatPoolClient {
|
|
58
|
+
query<R = Record<string, unknown>>(text: string, values?: unknown[]): Promise<PgCompatQueryResult<R>>;
|
|
59
|
+
release(err?: Error | boolean): void;
|
|
60
|
+
/**
|
|
61
|
+
* Optional driver capability: `true` when `query()` may be called again on
|
|
62
|
+
* this connection while earlier calls are still in flight, with replies
|
|
63
|
+
* delivered to callers in FIFO submission order. Drivers that set this let
|
|
64
|
+
* the batch `$transaction([...])` overload dispatch every statement in one
|
|
65
|
+
* write burst (~1 network round trip plus server time) instead of awaiting
|
|
66
|
+
* each reply before sending the next (N round trips). Leave unset for
|
|
67
|
+
* drivers (node-postgres included) whose batch path must stay strictly
|
|
68
|
+
* sequential.
|
|
69
|
+
*/
|
|
70
|
+
readonly supportsPipelining?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Optional engine seam: scope a transaction's user callback to its own
|
|
73
|
+
* async subtree. When present, `TurbineClient.transaction` / `$transaction`
|
|
74
|
+
* invoke the callback as `wrapTransactionCallback(() => fn(tx))` instead of
|
|
75
|
+
* `fn(tx)` directly. Single-writer engines (PowDB) implement it with
|
|
76
|
+
* `AsyncLocalStorage.run()` to plant their re-entrancy marker so that it
|
|
77
|
+
* exists ONLY inside the callback's async subtree: a transaction opened
|
|
78
|
+
* from inside the callback is detected as re-entrant (typed E017), while
|
|
79
|
+
* the CALLER's context stays unmarked, so same-tick sibling transactions
|
|
80
|
+
* queue FIFO instead of being falsely flagged. Absent on pg and every other
|
|
81
|
+
* engine, in which case the callback runs unwrapped (zero behavior change).
|
|
82
|
+
*/
|
|
83
|
+
wrapTransactionCallback?<R>(fn: () => Promise<R>): Promise<R>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Minimal pg-compatible pool. Pass any driver that satisfies this interface
|
|
87
|
+
* via `TurbineConfig.pool` — lets Turbine run on Neon HTTP, Vercel Postgres,
|
|
88
|
+
* Cloudflare Hyperdrive, or any other serverless Postgres driver.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* import { Pool } from '@neondatabase/serverless';
|
|
93
|
+
* import { TurbineClient } from 'turbine-orm';
|
|
94
|
+
*
|
|
95
|
+
* const neonPool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
96
|
+
* const db = new TurbineClient({ pool: neonPool }, schema);
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export interface PgCompatPool {
|
|
100
|
+
query<R = Record<string, unknown>>(text: string, values?: unknown[]): Promise<PgCompatQueryResult<R>>;
|
|
101
|
+
connect(): Promise<PgCompatPoolClient>;
|
|
102
|
+
end(): Promise<void>;
|
|
103
|
+
/** Optional — pools that expose stats (pg.Pool does; Neon HTTP does not) */
|
|
104
|
+
readonly totalCount?: number;
|
|
105
|
+
readonly idleCount?: number;
|
|
106
|
+
readonly waitingCount?: number;
|
|
107
|
+
/** Optional — pg.Pool supports 'error' event; HTTP drivers typically do not */
|
|
108
|
+
on?(event: 'error', listener: (err: Error) => void): this;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Driver-neutral seam. Bundles a pg-compatible connection pool with the SQL
|
|
112
|
+
* {@link Dialect} that owns every piece of SQL text varying across engines —
|
|
113
|
+
* parameter placeholders, transaction-control keywords (BEGIN/COMMIT/ROLLBACK/
|
|
114
|
+
* SAVEPOINT/isolation/set_config), streaming, and capability flags.
|
|
115
|
+
*
|
|
116
|
+
* This is the structural boundary that keeps hard-coded Postgres SQL out of
|
|
117
|
+
* `client.ts`: the pool provides connect/query/transaction/close, the dialect
|
|
118
|
+
* provides every literal keyword and the placeholder syntax. A future MySQL /
|
|
119
|
+
* SQLite engine ships a `{ pool, dialect }` pair instead of a raw `pg.Pool`,
|
|
120
|
+
* exactly as `turbineHttp` ships a serverless pool today.
|
|
121
|
+
*/
|
|
122
|
+
export interface TurbineDriver {
|
|
123
|
+
/** The underlying pg-compatible connection pool (connect/query/transaction/close). */
|
|
124
|
+
readonly pool: PgCompatPool;
|
|
125
|
+
/** SQL dialect: placeholders, transaction keywords, session-config, capability flags. */
|
|
126
|
+
readonly dialect: Dialect;
|
|
127
|
+
}
|
|
128
|
+
export interface TurbineConfig {
|
|
129
|
+
/**
|
|
130
|
+
* An external pg-compatible pool. Use this to plug in serverless drivers
|
|
131
|
+
* like `@neondatabase/serverless`, `@vercel/postgres`, or any other pg-API
|
|
132
|
+
* compatible pool. When provided, all connection-string fields are ignored
|
|
133
|
+
* and Turbine will NOT create its own pg.Pool.
|
|
134
|
+
*/
|
|
135
|
+
pool?: PgCompatPool;
|
|
136
|
+
/** Postgres connection string (e.g. postgres://user:pass@host:5432/db) */
|
|
137
|
+
connectionString?: string;
|
|
138
|
+
/** Host (used if connectionString is not set) */
|
|
139
|
+
host?: string;
|
|
140
|
+
/** Port (default: 5432) */
|
|
141
|
+
port?: number;
|
|
142
|
+
/** Database name */
|
|
143
|
+
database?: string;
|
|
144
|
+
/** Username */
|
|
145
|
+
user?: string;
|
|
146
|
+
/** Password */
|
|
147
|
+
password?: string;
|
|
148
|
+
/** SSL/TLS options for the connection (required for most cloud providers) */
|
|
149
|
+
ssl?: boolean | {
|
|
150
|
+
rejectUnauthorized?: boolean;
|
|
151
|
+
ca?: string;
|
|
152
|
+
key?: string;
|
|
153
|
+
cert?: string;
|
|
154
|
+
};
|
|
155
|
+
/** Maximum number of connections in the pool (default: 10) */
|
|
156
|
+
poolSize?: number;
|
|
157
|
+
/** Idle timeout in ms before a connection is closed (default: 30000) */
|
|
158
|
+
idleTimeoutMs?: number;
|
|
159
|
+
/** Connection timeout in ms (default: 5000) */
|
|
160
|
+
connectionTimeoutMs?: number;
|
|
161
|
+
/** pg-style alias for {@link poolSize}; the explicit field wins when both are set. */
|
|
162
|
+
max?: number;
|
|
163
|
+
/** pg-style alias for {@link idleTimeoutMs}; the explicit field wins when both are set. */
|
|
164
|
+
idleTimeoutMillis?: number;
|
|
165
|
+
/** pg-style alias for {@link connectionTimeoutMs}; the explicit field wins when both are set. */
|
|
166
|
+
connectionTimeoutMillis?: number;
|
|
167
|
+
/** Enable query logging to console (default: false) */
|
|
168
|
+
logging?: boolean;
|
|
169
|
+
/** Default LIMIT applied to findMany() when no limit is specified (opt-in, default: undefined) */
|
|
170
|
+
defaultLimit?: number;
|
|
171
|
+
/**
|
|
172
|
+
* Log a warning when findMany() is called without a limit (default: false).
|
|
173
|
+
* Pass a per-table map (`{ users: false }`) to override the default for
|
|
174
|
+
* specific tables; per-call `warnOnUnlimited` on findMany args wins over both.
|
|
175
|
+
*/
|
|
176
|
+
warnOnUnlimited?: boolean | Record<string, boolean>;
|
|
177
|
+
/**
|
|
178
|
+
* Interpret Postgres `timestamp` (without time zone) values as UTC — both
|
|
179
|
+
* at the driver level (OID 1114 type parser, registered only when Turbine
|
|
180
|
+
* owns the pool) and when coercing nested-relation JSON dates. This is the
|
|
181
|
+
* Prisma/Rails/Django convention and makes results independent of the
|
|
182
|
+
* server's local time zone. Default: `true`. Set `false` for the legacy
|
|
183
|
+
* local-time interpretation.
|
|
184
|
+
*/
|
|
185
|
+
utcTimestamps?: boolean;
|
|
186
|
+
/**
|
|
187
|
+
* Default strategy for resolving `with`-clause relations, applied to every
|
|
188
|
+
* `findMany`/`findUnique`/`findFirst` unless overridden per query.
|
|
189
|
+
*
|
|
190
|
+
* - `'join'`: one SQL statement using correlated
|
|
191
|
+
* `json_agg(json_build_object(...))` subqueries. On PowDB, `'join'` opts
|
|
192
|
+
* into native server-side joins where eligible instead.
|
|
193
|
+
* - `'batched'`: run the base query, then one flat follow-up query per
|
|
194
|
+
* relation (`WHERE fk = ANY($1)`), stitching children client-side. Wins
|
|
195
|
+
* when child FK columns are unindexed or result sets are large.
|
|
196
|
+
* - `'auto'` (the SQL-engine default since 0.41): per relation, use `'join'`
|
|
197
|
+
* unless the introspected metadata PROVES the probe columns are unindexed,
|
|
198
|
+
* in which case that relation falls back to the batched loader. Needs
|
|
199
|
+
* DB-backed index metadata (a generated / introspected client); a
|
|
200
|
+
* code-first `defineSchema`-only client behaves exactly like `'join'`.
|
|
201
|
+
* Output shape is identical to `'join'`. See {@link RelationLoadStrategy}.
|
|
202
|
+
*
|
|
203
|
+
* Precedence: per-query `relationLoadStrategy` arg > this config > the engine
|
|
204
|
+
* default. On SQL engines the default is `'auto'`; on PowDB the default is the
|
|
205
|
+
* batched loaders (an ineligible relation falls back to them silently even
|
|
206
|
+
* under `'join'`; `'auto'` resolves to PowDB's own default).
|
|
207
|
+
*/
|
|
208
|
+
relationLoadStrategy?: RelationLoadStrategy;
|
|
209
|
+
/**
|
|
210
|
+
* When `true`, every to-many `with` relation with no explicit `orderBy` is
|
|
211
|
+
* loaded ordered by the target table's primary key ascending, so unordered
|
|
212
|
+
* child arrays come back deterministically (json_agg / the batched loaders
|
|
213
|
+
* otherwise leave that order engine-dependent, and `'auto'` fallback can change
|
|
214
|
+
* it). An explicit per-relation `orderBy` always wins; a per-query
|
|
215
|
+
* `stableRelationOrder` overrides this. Default `false` (SQL is byte-identical
|
|
216
|
+
* when off). SQL engines only. See {@link RelationLoadStrategy}.
|
|
217
|
+
*/
|
|
218
|
+
stableRelationOrder?: boolean;
|
|
219
|
+
/**
|
|
220
|
+
* When `true`, a `findMany` that paginates (`limit` / `take` / `offset`) but
|
|
221
|
+
* declares no `orderBy` is ordered by the table's primary key ascending
|
|
222
|
+
* (every column of a composite PK, in declaration order). An unordered
|
|
223
|
+
* `LIMIT` is non-deterministic: the same query can return different rows once
|
|
224
|
+
* the heap changes underneath it, so a row may appear on two pages or on
|
|
225
|
+
* none. An explicit `orderBy` always wins, PK-less tables are left alone, and
|
|
226
|
+
* `distinct` / `cursor` shapes are skipped.
|
|
227
|
+
*
|
|
228
|
+
* Default `false` in core, because switching it on would add an `ORDER BY` to
|
|
229
|
+
* SQL that existing applications already emit, changing both the rows a page
|
|
230
|
+
* returns and the plan the engine picks: a silent behavior change that waits
|
|
231
|
+
* for a major. `turbine-orm/prisma-compat` defaults it ON instead, since
|
|
232
|
+
* reproducing Prisma's semantics is that layer's contract. With it off the
|
|
233
|
+
* emitted SQL is byte-identical to before, and a dev-mode warning points at
|
|
234
|
+
* the affected queries.
|
|
235
|
+
*/
|
|
236
|
+
implicitPkOrdering?: boolean;
|
|
237
|
+
/**
|
|
238
|
+
* Parent-row ceiling for the `relationLoadStrategy: 'auto'` to-one rule: a
|
|
239
|
+
* to-one relation stays in the single-statement join when the query's `limit`
|
|
240
|
+
* bounds the parent set at or under this many rows, and loads batched when the
|
|
241
|
+
* query is unbounded or bounded above it. Defaults to 1000 (see
|
|
242
|
+
* `AUTO_TO_ONE_JOIN_MAX_ROWS`). Only consulted under `'auto'`.
|
|
243
|
+
*/
|
|
244
|
+
autoToOneJoinMaxRows?: number;
|
|
245
|
+
/**
|
|
246
|
+
* Round-trip time to the database, in milliseconds, used to DERIVE the
|
|
247
|
+
* `relationLoadStrategy: 'auto'` to-one threshold instead of guessing a row
|
|
248
|
+
* count. Prefer this over `autoToOneJoinMaxRows`: the break-even between the
|
|
249
|
+
* single-statement join and the batched follow-up is
|
|
250
|
+
* `roundTripMs / AUTO_JOIN_PENALTY_MS_PER_ROW`, and measurement shows the
|
|
251
|
+
* per-row penalty is a constant of the plan while the break-even moves ~17x
|
|
252
|
+
* between a loopback link and a 2.7ms one. Set it to what `ping` says (a
|
|
253
|
+
* Unix socket is ~0.05, same-region managed Postgres ~0.5-2, cross-region
|
|
254
|
+
* ~30-60). Defaults to `AUTO_ASSUMED_ROUND_TRIP_MS` (0.7ms, same-region),
|
|
255
|
+
* which reproduces the historical 1000-row threshold exactly. Overridden by
|
|
256
|
+
* an explicit `autoToOneJoinMaxRows`; only consulted under `'auto'`.
|
|
257
|
+
*/
|
|
258
|
+
autoRoundTripMs?: number;
|
|
259
|
+
/**
|
|
260
|
+
* How nested-relation subqueries encode each row's JSON.
|
|
261
|
+
*
|
|
262
|
+
* - `'object'` (default) — `json_agg(json_build_object('key', v, …))`. Every
|
|
263
|
+
* key name is repeated in every nested object of every row.
|
|
264
|
+
* - `'positional'` — `json_agg(json_build_array(v, …))`. Turbine knows the
|
|
265
|
+
* column order at build time, so it emits a key-less array and maps
|
|
266
|
+
* positions back to keys client-side. Same information, a fraction of the
|
|
267
|
+
* bytes on wide/deeply-nested `with` trees. Parsed output is byte-identical
|
|
268
|
+
* to `'object'`.
|
|
269
|
+
*
|
|
270
|
+
* Postgres-only in v1: setting `'positional'` on a non-Postgres engine throws
|
|
271
|
+
* `UnsupportedFeatureError` (E017) when a `with` clause is present. Default:
|
|
272
|
+
* `'object'` (today's behavior, byte-unchanged).
|
|
273
|
+
*/
|
|
274
|
+
jsonEncoding?: 'object' | 'positional';
|
|
275
|
+
/**
|
|
276
|
+
* Controls how `NotFoundError` (and other where-aware errors) format their
|
|
277
|
+
* messages.
|
|
278
|
+
*
|
|
279
|
+
* - `'safe'` (default): the message includes only the keys of the where
|
|
280
|
+
* clause (e.g. `where: { id, email }`). Values are redacted to avoid
|
|
281
|
+
* leaking PII into error logs (Sentry, Datadog, etc.).
|
|
282
|
+
* - `'verbose'`: the message includes the full JSON-serialized where
|
|
283
|
+
* clause (e.g. `where: {"id":1,"email":"alice@x.com"}`).
|
|
284
|
+
*
|
|
285
|
+
* The full `where` object is always available as `err.where` for
|
|
286
|
+
* programmatic access regardless of mode.
|
|
287
|
+
*/
|
|
288
|
+
errorMessages?: ErrorMessageMode;
|
|
289
|
+
/**
|
|
290
|
+
* Enable prepared statements. Queries are submitted with `{ name, text, values }`
|
|
291
|
+
* to the pg driver, which caches the parse+plan on the server per connection.
|
|
292
|
+
*
|
|
293
|
+
* Default: `true` for Turbine-owned pools, `false` for external pools (serverless
|
|
294
|
+
* drivers may not support named statements).
|
|
295
|
+
*
|
|
296
|
+
* Override with `TURBINE_DISABLE_PREPARED=1` env var.
|
|
297
|
+
*/
|
|
298
|
+
preparedStatements?: boolean;
|
|
299
|
+
/**
|
|
300
|
+
* Enable the SQL template cache. Repeated queries with the same shape reuse
|
|
301
|
+
* cached SQL text instead of rebuilding from scratch.
|
|
302
|
+
*
|
|
303
|
+
* Default: `true`. Set to `false` as a nuclear kill switch.
|
|
304
|
+
*/
|
|
305
|
+
sqlCache?: boolean;
|
|
306
|
+
/**
|
|
307
|
+
* Maximum number of distinct SQL templates each per-table LRU cache retains.
|
|
308
|
+
*
|
|
309
|
+
* Default: `1000`. Values are parameterized (`$1, $2, …`) and never fragment
|
|
310
|
+
* the cache, so this bounds distinct query SHAPES. Raise it for apps with a
|
|
311
|
+
* very large surface of query shapes to lift the hit rate at the cost of
|
|
312
|
+
* memory; lower it to cap memory. `0` disables caching entirely (identical to
|
|
313
|
+
* `sqlCache: false`); a negative value is ignored (treated as the default).
|
|
314
|
+
*/
|
|
315
|
+
sqlCacheSize?: number;
|
|
316
|
+
/** SQL dialect implementation. Defaults to PostgreSQL. Internal Phase-1 seam for dialect packages. */
|
|
317
|
+
dialect?: Dialect;
|
|
318
|
+
/**
|
|
319
|
+
* Read replicas. When set, read-only operations issued outside a transaction
|
|
320
|
+
* (`findMany`, `findFirst`, `findUnique`, `*OrThrow`, `count`, `aggregate`,
|
|
321
|
+
* `groupBy`, `findManyStream`) are round-robin load-balanced across these
|
|
322
|
+
* pools; the primary handles them along with every write. ALL writes,
|
|
323
|
+
* `$transaction` bodies, `pipeline`, `raw`/`sql`, `$listen`/`$notify`, and
|
|
324
|
+
* observability flushes always use the primary.
|
|
325
|
+
*
|
|
326
|
+
* - `string` entries are connection strings — Turbine constructs an owned
|
|
327
|
+
* `pg.Pool` for each (same pool-tuning knobs as the primary, and the same
|
|
328
|
+
* one-time, constructor-gated type-parser registration). `disconnect()`
|
|
329
|
+
* closes them.
|
|
330
|
+
* - `PgCompatPool` entries are external pools (Neon, Vercel, a shared
|
|
331
|
+
* `pg.Pool`) — Turbine registers no type parsers on them and never ends
|
|
332
|
+
* them; the caller owns their lifecycle.
|
|
333
|
+
*
|
|
334
|
+
* Use `client.$primary()` to get a view of the client that pins every
|
|
335
|
+
* operation (reads included) to the primary — e.g. to read your own write
|
|
336
|
+
* without replication lag. Omitting `replicas` (or passing `[]`) leaves the
|
|
337
|
+
* default single-pool path completely unchanged.
|
|
338
|
+
*/
|
|
339
|
+
replicas?: readonly (string | PgCompatPool)[];
|
|
340
|
+
/**
|
|
341
|
+
* Automatic WHERE filters applied to every query, keyed by table accessor
|
|
342
|
+
* (`db[name]`). Each value is AND-merged into the compiled WHERE of every
|
|
343
|
+
* read and mutation on that table — and into every relation subquery that
|
|
344
|
+
* targets it — implementing soft-delete and multi-tenancy without repeating
|
|
345
|
+
* the predicate at each call site.
|
|
346
|
+
*
|
|
347
|
+
* - A `WhereClause` value is a static filter (e.g. `{ deletedAt: null }`).
|
|
348
|
+
* - A `() => WhereClause` value is evaluated at query-build time, so a
|
|
349
|
+
* closure over per-request state (the current tenant id) yields a
|
|
350
|
+
* request-scoped filter.
|
|
351
|
+
*
|
|
352
|
+
* `create`/`createMany` are never filtered. A per-query
|
|
353
|
+
* `skipGlobalFilters: true | string[]` opts out. The empty-`where` guard on
|
|
354
|
+
* `update`/`delete` still checks the USER-supplied `where`, so a global
|
|
355
|
+
* filter never turns an unguarded mass mutation into an allowed one.
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```ts
|
|
359
|
+
* const db = turbine({ url, schema, globalFilters: {
|
|
360
|
+
* posts: { deletedAt: null }, // soft-delete
|
|
361
|
+
* orders: () => ({ tenantId: currentTenant() }), // per-request tenancy
|
|
362
|
+
* }});
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
globalFilters?: GlobalFilters;
|
|
366
|
+
}
|
|
367
|
+
/** Parameters passed to middleware functions */
|
|
368
|
+
export interface MiddlewareParams {
|
|
369
|
+
/** The table/model being queried (e.g. 'users') */
|
|
370
|
+
model: string;
|
|
371
|
+
/** The operation being performed (e.g. 'findUnique', 'create', 'update') */
|
|
372
|
+
action: string;
|
|
373
|
+
/** The arguments passed to the query method */
|
|
374
|
+
args: Record<string, unknown>;
|
|
375
|
+
}
|
|
376
|
+
/** The next function in the middleware chain */
|
|
377
|
+
export type MiddlewareNext = (params: MiddlewareParams) => Promise<unknown>;
|
|
378
|
+
/** A middleware function that intercepts queries */
|
|
379
|
+
export type Middleware = (params: MiddlewareParams, next: MiddlewareNext) => Promise<unknown>;
|
|
380
|
+
export interface TransactionOptions {
|
|
381
|
+
/** Timeout in ms — transaction will be rolled back if exceeded */
|
|
382
|
+
timeout?: number;
|
|
383
|
+
/** Isolation level for the transaction */
|
|
384
|
+
isolationLevel?: 'ReadUncommitted' | 'ReadCommitted' | 'RepeatableRead' | 'Serializable';
|
|
385
|
+
/**
|
|
386
|
+
* Transaction-local session GUCs to set after BEGIN. The canonical use case
|
|
387
|
+
* is multi-tenant Postgres row-level security (RLS): your policies filter on
|
|
388
|
+
* `current_setting('app.current_tenant')`, and you set that value here so
|
|
389
|
+
* every query inside the transaction sees it.
|
|
390
|
+
*
|
|
391
|
+
* Each entry is applied via `SELECT set_config($1, $2, true)` — `is_local=true`
|
|
392
|
+
* scopes the value to this transaction, so it auto-resets on COMMIT/ROLLBACK
|
|
393
|
+
* and never leaks onto the pooled connection. Both the name and value are
|
|
394
|
+
* bound parameters (never interpolated); the GUC name is additionally
|
|
395
|
+
* validated against a strict identifier regex.
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```ts
|
|
399
|
+
* await db.$transaction(
|
|
400
|
+
* async (tx) => {
|
|
401
|
+
* // every query here sees current_setting('app.current_tenant') = '42'
|
|
402
|
+
* return tx.invoices.findMany();
|
|
403
|
+
* },
|
|
404
|
+
* { sessionContext: { 'app.current_tenant': '42', 'app.current_user': userId } },
|
|
405
|
+
* );
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
sessionContext?: Record<string, string | number | boolean>;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* A transaction-scoped client that provides the same table accessor API as TurbineClient.
|
|
412
|
+
* All queries run on a dedicated connection within a BEGIN/COMMIT block.
|
|
413
|
+
* Supports nested transactions via SAVEPOINTs.
|
|
414
|
+
*/
|
|
415
|
+
export declare class TransactionClient {
|
|
416
|
+
private readonly client;
|
|
417
|
+
readonly schema: SchemaMetadata;
|
|
418
|
+
private readonly middlewares;
|
|
419
|
+
private readonly queryOptions?;
|
|
420
|
+
/**
|
|
421
|
+
* The parent pool this transaction runs on. Only its `readonly` and
|
|
422
|
+
* `capabilities` are read (both PowDB-only flags), so the transaction-scoped
|
|
423
|
+
* proxy pool built by {@link createTxPool} carries them through: without this
|
|
424
|
+
* a read-only client's `$transaction` writes bypass the E018 guard, and an
|
|
425
|
+
* older-engine client falls back to ALL_POWDB_CAPABILITIES inside the tx
|
|
426
|
+
* (emitting join PowQL a pre-0.13 engine rejects). Undefined / absent flags
|
|
427
|
+
* for a plain pg pool leave the proxy unchanged.
|
|
428
|
+
*/
|
|
429
|
+
private readonly sourcePool?;
|
|
430
|
+
private readonly tableCache;
|
|
431
|
+
private savepointCounter;
|
|
432
|
+
/** Active SQL dialect — owns savepoint keywords and raw-SQL placeholders. */
|
|
433
|
+
private readonly dialect;
|
|
434
|
+
constructor(client: pg.PoolClient, schema: SchemaMetadata, middlewares: Middleware[], queryOptions?: QueryInterfaceOptions | undefined,
|
|
435
|
+
/**
|
|
436
|
+
* The parent pool this transaction runs on. Only its `readonly` and
|
|
437
|
+
* `capabilities` are read (both PowDB-only flags), so the transaction-scoped
|
|
438
|
+
* proxy pool built by {@link createTxPool} carries them through: without this
|
|
439
|
+
* a read-only client's `$transaction` writes bypass the E018 guard, and an
|
|
440
|
+
* older-engine client falls back to ALL_POWDB_CAPABILITIES inside the tx
|
|
441
|
+
* (emitting join PowQL a pre-0.13 engine rejects). Undefined / absent flags
|
|
442
|
+
* for a plain pg pool leave the proxy unchanged.
|
|
443
|
+
*/
|
|
444
|
+
sourcePool?: {
|
|
445
|
+
readonly readonly?: boolean;
|
|
446
|
+
readonly capabilities?: unknown;
|
|
447
|
+
} | undefined);
|
|
448
|
+
/**
|
|
449
|
+
* Get a QueryInterface for a table within this transaction.
|
|
450
|
+
* Uses the dedicated transaction connection instead of the pool.
|
|
451
|
+
*/
|
|
452
|
+
table<T extends object = Record<string, unknown>>(name: string): QueryInterface<T>;
|
|
453
|
+
/**
|
|
454
|
+
* Execute a nested transaction via SAVEPOINT.
|
|
455
|
+
* If the inner function throws, only the savepoint is rolled back.
|
|
456
|
+
*/
|
|
457
|
+
$transaction<R>(fn: (tx: TransactionClient) => Promise<R>): Promise<R>;
|
|
458
|
+
/**
|
|
459
|
+
* Execute a raw SQL query within this transaction.
|
|
460
|
+
*/
|
|
461
|
+
raw<T extends Record<string, unknown> = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
|
|
462
|
+
/**
|
|
463
|
+
* Create a pool-like wrapper around the transaction client.
|
|
464
|
+
* This allows QueryInterface to work with the transaction connection
|
|
465
|
+
* without knowing it's in a transaction.
|
|
466
|
+
*
|
|
467
|
+
* pg driver errors thrown by queries are translated into typed Turbine
|
|
468
|
+
* errors via wrapPgError so transaction-scoped queries surface the same
|
|
469
|
+
* typed errors as pool-scoped queries.
|
|
470
|
+
*/
|
|
471
|
+
private createTxPool;
|
|
472
|
+
}
|
|
473
|
+
export declare class TurbineClient {
|
|
474
|
+
/** The underlying pg.Pool — exposed for escape hatches */
|
|
475
|
+
readonly pool: pg.Pool;
|
|
476
|
+
/** The schema metadata this client was built from */
|
|
477
|
+
readonly schema: SchemaMetadata;
|
|
478
|
+
private static int8ParserRegistered;
|
|
479
|
+
private static utcTimestampParserRegistered;
|
|
480
|
+
private readonly logging;
|
|
481
|
+
/** Active SQL dialect — owns transaction keywords, set_config, raw-SQL placeholders, capability flags. */
|
|
482
|
+
private readonly dialect;
|
|
483
|
+
private readonly tableCache;
|
|
484
|
+
private readonly middlewares;
|
|
485
|
+
private readonly queryListeners;
|
|
486
|
+
private queryOptions;
|
|
487
|
+
private readonly errorMessagesSafe;
|
|
488
|
+
/** True when Turbine created the pool and is responsible for tearing it down */
|
|
489
|
+
private readonly ownsPool;
|
|
490
|
+
/** Active LISTEN subscriptions — torn down on disconnect() so it never hangs */
|
|
491
|
+
private readonly activeSubscriptions;
|
|
492
|
+
/**
|
|
493
|
+
* Read-replica pools in round-robin order. Empty when no replicas are
|
|
494
|
+
* configured, in which case `table()` takes the original single-pool path.
|
|
495
|
+
*/
|
|
496
|
+
private readonly replicaPools;
|
|
497
|
+
/**
|
|
498
|
+
* The subset of {@link replicaPools} that Turbine created from connection
|
|
499
|
+
* strings and must close on `disconnect()`. External replica pools are not
|
|
500
|
+
* listed here (caller owns their lifecycle).
|
|
501
|
+
*/
|
|
502
|
+
private readonly ownedReplicaPools;
|
|
503
|
+
/** Rotating index for round-robin replica selection (advances per read op). */
|
|
504
|
+
private replicaCursor;
|
|
505
|
+
/** Per-replica `table → QueryInterface` caches, indexed like {@link replicaPools}. */
|
|
506
|
+
private readonly replicaTableCaches;
|
|
507
|
+
/** Cache of per-table routing proxies (only used when replicas are present). */
|
|
508
|
+
private readonly routingProxyCache;
|
|
509
|
+
/** Lazily-built, cached primary-only view returned by {@link $primary}. */
|
|
510
|
+
private primaryView?;
|
|
511
|
+
constructor(config: TurbineConfig | undefined, schema: SchemaMetadata);
|
|
512
|
+
/**
|
|
513
|
+
* Register a middleware function that runs around every query.
|
|
514
|
+
*
|
|
515
|
+
* Middleware can inspect and log query parameters, measure timing, and
|
|
516
|
+
* transform the result returned by `next()`. Note: query SQL is generated
|
|
517
|
+
* BEFORE middleware runs — `params.args` is a read-only snapshot, and
|
|
518
|
+
* mutating it does NOT change the executed SQL. Cross-cutting filters
|
|
519
|
+
* (e.g. soft deletes) belong in the query itself: pass an explicit
|
|
520
|
+
* `where: { deletedAt: null }` or wrap the table accessor in a small helper.
|
|
521
|
+
*
|
|
522
|
+
* @example
|
|
523
|
+
* ```ts
|
|
524
|
+
* // Query timing middleware
|
|
525
|
+
* db.$use(async (params, next) => {
|
|
526
|
+
* const before = Date.now();
|
|
527
|
+
* const result = await next(params);
|
|
528
|
+
* console.log(`${params.model}.${params.action} took ${Date.now() - before}ms`);
|
|
529
|
+
* return result;
|
|
530
|
+
* });
|
|
531
|
+
*
|
|
532
|
+
* // Result transformation middleware — redact a field on the way out
|
|
533
|
+
* db.$use(async (params, next) => {
|
|
534
|
+
* const result = await next(params);
|
|
535
|
+
* if (params.model === 'users' && Array.isArray(result)) {
|
|
536
|
+
* for (const row of result as { email?: string }[]) row.email = '[redacted]';
|
|
537
|
+
* }
|
|
538
|
+
* return result;
|
|
539
|
+
* });
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
$use(middleware: Middleware): void;
|
|
543
|
+
$on(_event: 'query', listener: QueryEventListener): void;
|
|
544
|
+
$off(_event: 'query', listener: QueryEventListener): void;
|
|
545
|
+
private observeEngine?;
|
|
546
|
+
$observe(config: ObserveConfig): Promise<ObserveHandle>;
|
|
547
|
+
/**
|
|
548
|
+
* Get a QueryInterface for a table.
|
|
549
|
+
* Results are cached — calling `table('users')` twice returns the same instance.
|
|
550
|
+
*
|
|
551
|
+
* When read replicas are configured, this returns a thin routing proxy: the
|
|
552
|
+
* read-only operations in {@link READ_OPERATIONS} are dispatched to a
|
|
553
|
+
* round-robin replica-bound QueryInterface (so an entire read — base rows and
|
|
554
|
+
* any batched sub-queries — runs against a single consistent replica), while
|
|
555
|
+
* writes and every other member fall through to the primary-bound instance.
|
|
556
|
+
* With no replicas the original single-pool instance is returned directly.
|
|
557
|
+
*/
|
|
558
|
+
table<T extends object = Record<string, unknown>>(name: string): QueryInterface<T>;
|
|
559
|
+
/** Get (and cache) the primary-pool-bound QueryInterface for a table. */
|
|
560
|
+
private primaryTableQI;
|
|
561
|
+
/**
|
|
562
|
+
* Advance the round-robin cursor and return the QueryInterface bound to the
|
|
563
|
+
* selected replica pool for `name` (cached per replica).
|
|
564
|
+
*/
|
|
565
|
+
private nextReplicaTableQI;
|
|
566
|
+
/** Construct a QueryInterface bound to `pool` (honoring any injected factory). */
|
|
567
|
+
private buildTableQI;
|
|
568
|
+
/**
|
|
569
|
+
* Build the read/write routing proxy for a table. The proxy targets the
|
|
570
|
+
* primary QueryInterface (so writes, `build*`, and every non-read member work
|
|
571
|
+
* unchanged); read operations are intercepted and dispatched to a replica.
|
|
572
|
+
*/
|
|
573
|
+
private createRoutingAccessor;
|
|
574
|
+
/**
|
|
575
|
+
* Return a view of this client that pins EVERY operation — reads included —
|
|
576
|
+
* to the primary pool, bypassing replica routing. Use it to read your own
|
|
577
|
+
* write without replication lag, or for any read that must see the latest
|
|
578
|
+
* committed data.
|
|
579
|
+
*
|
|
580
|
+
* The view shares the primary pool, schema, dialect, query options, and
|
|
581
|
+
* middleware; it owns nothing, so its `disconnect()` is a no-op. When no
|
|
582
|
+
* replicas are configured this simply returns the client itself (already
|
|
583
|
+
* primary-only). The view is cached — repeated calls return the same instance.
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```ts
|
|
587
|
+
* await db.users.create({ data: { email: 'a@b.com' } });
|
|
588
|
+
* // Read-after-write: guaranteed to see the row just inserted.
|
|
589
|
+
* const user = await db.$primary().users.findFirst({ where: { email: 'a@b.com' } });
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
592
|
+
$primary(): TurbineClient;
|
|
593
|
+
/**
|
|
594
|
+
* Execute multiple queries in a single database round-trip.
|
|
595
|
+
*
|
|
596
|
+
* Two call styles:
|
|
597
|
+
* - `db.pipeline(q1, q2, q3)` — rest params (backward-compatible)
|
|
598
|
+
* - `db.pipeline([q1, q2, q3], { transactional: false })` — array + options
|
|
599
|
+
*
|
|
600
|
+
* On pg.Pool-backed connections with TCP, this uses the real Postgres
|
|
601
|
+
* extended-query pipeline protocol (one TCP flush, one round-trip).
|
|
602
|
+
* On HTTP-based drivers it falls back to sequential execution.
|
|
603
|
+
*/
|
|
604
|
+
pipeline<T extends readonly DeferredQuery<unknown>[]>(...args: T | [T, PipelineOptions?]): Promise<PipelineResults<T>>;
|
|
605
|
+
/**
|
|
606
|
+
* Check whether the underlying pool supports the real pipeline protocol.
|
|
607
|
+
* Returns `true` for standard pg.Pool TCP connections, `false` for HTTP
|
|
608
|
+
* drivers (Neon HTTP, Vercel Postgres, etc.) and mock pools.
|
|
609
|
+
*/
|
|
610
|
+
pipelineSupported(): Promise<boolean>;
|
|
611
|
+
/**
|
|
612
|
+
* Execute a raw SQL query with parameter interpolation via tagged templates.
|
|
613
|
+
*
|
|
614
|
+
* @example
|
|
615
|
+
* ```ts
|
|
616
|
+
* const result = await db.raw<{ day: Date; count: number }>`
|
|
617
|
+
* SELECT DATE_TRUNC('day', created_at) as day, COUNT(*)::int as count
|
|
618
|
+
* FROM posts WHERE org_id = ${orgId}
|
|
619
|
+
* GROUP BY day ORDER BY day
|
|
620
|
+
* `;
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
623
|
+
raw<T extends Record<string, unknown> = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]): Promise<T[]>;
|
|
624
|
+
/**
|
|
625
|
+
* Execute a **typed** raw SQL query — Turbine's answer to Prisma's TypedSQL.
|
|
626
|
+
*
|
|
627
|
+
* Like {@link raw}, every interpolated `${value}` becomes a `$N` parameter
|
|
628
|
+
* (never string-concatenated), so it is injection-safe by construction. The
|
|
629
|
+
* difference is the caller-supplied row type and the chainable result: the
|
|
630
|
+
* returned {@link TypedSqlQuery} can be `await`ed directly for `T[]`, or
|
|
631
|
+
* refined with `.one()` (→ `T | null`) or `.scalar<V>()` (→ `V | null`).
|
|
632
|
+
*
|
|
633
|
+
* Rows are returned as-is — no snake→camel mapping (matching `raw()`). Alias
|
|
634
|
+
* columns in SQL if you want camelCase keys.
|
|
635
|
+
*
|
|
636
|
+
* @example
|
|
637
|
+
* ```ts
|
|
638
|
+
* // rows
|
|
639
|
+
* const rows = await db.sql<{ id: number; name: string }>`
|
|
640
|
+
* SELECT id, name FROM users WHERE org_id = ${orgId}
|
|
641
|
+
* `;
|
|
642
|
+
*
|
|
643
|
+
* // single row or null
|
|
644
|
+
* const user = await db.sql<{ id: number; name: string }>`
|
|
645
|
+
* SELECT id, name FROM users WHERE id = ${userId}
|
|
646
|
+
* `.one();
|
|
647
|
+
*
|
|
648
|
+
* // scalar
|
|
649
|
+
* const total = await db.sql<{ count: number }>`
|
|
650
|
+
* SELECT COUNT(*)::int AS count FROM users
|
|
651
|
+
* `.scalar();
|
|
652
|
+
* ```
|
|
653
|
+
*/
|
|
654
|
+
sql<T extends Record<string, unknown> = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]): TypedSqlQuery<T>;
|
|
655
|
+
/**
|
|
656
|
+
* Execute a function within a database transaction (raw pg.PoolClient).
|
|
657
|
+
* For the typed API, use `$transaction()` instead.
|
|
658
|
+
*
|
|
659
|
+
* @example
|
|
660
|
+
* ```ts
|
|
661
|
+
* await db.transaction(async (client) => {
|
|
662
|
+
* await client.query('INSERT INTO users (name) VALUES ($1)', ['Alice']);
|
|
663
|
+
* });
|
|
664
|
+
* ```
|
|
665
|
+
*/
|
|
666
|
+
transaction<T>(fn: (client: pg.PoolClient) => Promise<T>): Promise<T>;
|
|
667
|
+
/**
|
|
668
|
+
* Execute a function within a database transaction with full typed table accessors.
|
|
669
|
+
*
|
|
670
|
+
* The `tx` object provides the same table accessor API as the main client.
|
|
671
|
+
* Supports nested transactions via SAVEPOINTs, timeouts, and isolation levels.
|
|
672
|
+
*
|
|
673
|
+
* @example
|
|
674
|
+
* ```ts
|
|
675
|
+
* await db.$transaction(async (tx) => {
|
|
676
|
+
* const user = await tx.users.create({ data: { email: 'a@b.com' } });
|
|
677
|
+
* await tx.posts.create({ data: { userId: user.id, title: 'Hello' } });
|
|
678
|
+
* });
|
|
679
|
+
*
|
|
680
|
+
* // With options:
|
|
681
|
+
* await db.$transaction(async (tx) => {
|
|
682
|
+
* // ...
|
|
683
|
+
* }, { timeout: 5000, isolationLevel: 'Serializable' });
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
$transaction<R>(fn: (tx: TransactionClient) => Promise<R>, options?: TransactionOptions): Promise<R>;
|
|
687
|
+
/**
|
|
688
|
+
* Batch form — run a tuple of {@link DeferredQuery} objects (produced by the
|
|
689
|
+
* `build*()` methods, e.g. `db.users.buildFindMany(...)`) atomically inside a
|
|
690
|
+
* single `BEGIN…COMMIT` on one connection. Returns a positionally-typed tuple
|
|
691
|
+
* of each query's transformed result; any failure rolls the whole batch back.
|
|
692
|
+
*
|
|
693
|
+
* Unlike {@link pipeline}, this never uses the extended-query pipeline
|
|
694
|
+
* protocol. Statements run on the single transaction connection: strictly
|
|
695
|
+
* sequentially by default (safe on every driver, including HTTP/serverless
|
|
696
|
+
* pools), or — when the checked-out connection advertises
|
|
697
|
+
* {@link PgCompatPoolClient.supportsPipelining} — dispatched in one write
|
|
698
|
+
* burst with replies collected in order, saving a network round trip per
|
|
699
|
+
* statement. Either way the failure contract is identical: the first
|
|
700
|
+
* (lowest-index) failure aborts the batch and rolls everything back.
|
|
701
|
+
*
|
|
702
|
+
* @example
|
|
703
|
+
* ```ts
|
|
704
|
+
* const [user, count] = await db.$transaction([
|
|
705
|
+
* db.users.buildFindUnique({ where: { id: 1 } }),
|
|
706
|
+
* db.posts.buildCount({ where: { userId: 1 } }),
|
|
707
|
+
* ]);
|
|
708
|
+
* ```
|
|
709
|
+
*/
|
|
710
|
+
$transaction<T extends readonly DeferredQuery<unknown>[]>(queries: readonly [...T]): Promise<PipelineResults<T>>;
|
|
711
|
+
/**
|
|
712
|
+
* Execute a batch of {@link DeferredQuery} objects atomically inside one
|
|
713
|
+
* transaction. Backs the `$transaction([...])` array overload. Reuses the raw
|
|
714
|
+
* {@link transaction} machinery (BEGIN/COMMIT/ROLLBACK + connection release);
|
|
715
|
+
* each result is passed through its query's `transform`.
|
|
716
|
+
*
|
|
717
|
+
* Execution strategy on the single transaction connection:
|
|
718
|
+
* - **Sequential (default).** Await each statement's reply before sending
|
|
719
|
+
* the next. Safe on every driver; on a networked driver a batch of N
|
|
720
|
+
* costs N round trips.
|
|
721
|
+
* - **Pipelined.** When the checked-out connection advertises
|
|
722
|
+
* {@link PgCompatPoolClient.supportsPipelining} (its `query()` accepts
|
|
723
|
+
* concurrent calls and completes them in FIFO submission order), all
|
|
724
|
+
* statements are dispatched in one write burst and the replies are
|
|
725
|
+
* collected in order — ~1 round trip plus server time. Only taken when
|
|
726
|
+
* the dialect's writes surface rows directly (`resultStrategy` !==
|
|
727
|
+
* 'reselect'): a reselect plan is itself a sequential write+read pair.
|
|
728
|
+
*
|
|
729
|
+
* The two paths share one failure contract: the first (lowest-index) failed
|
|
730
|
+
* statement's error is thrown (wrapped via {@link wrapPgError}) and the
|
|
731
|
+
* surrounding transaction rolls back, so no statement's effect survives. The
|
|
732
|
+
* pipelined path drains every in-flight reply (`Promise.allSettled`) before
|
|
733
|
+
* rethrowing, which keeps the connection's request/reply pairing intact and
|
|
734
|
+
* means ROLLBACK is only issued once no statement is still in flight.
|
|
735
|
+
*/
|
|
736
|
+
private transactionBatch;
|
|
737
|
+
/**
|
|
738
|
+
* Convenience wrapper around `$transaction` for the multi-tenant / RLS case:
|
|
739
|
+
* runs `fn` inside a transaction with the given session GUCs applied via
|
|
740
|
+
* `set_config(..., is_local=true)`. Equivalent to
|
|
741
|
+
* `$transaction(fn, { sessionContext: context })`.
|
|
742
|
+
*
|
|
743
|
+
* @example
|
|
744
|
+
* ```ts
|
|
745
|
+
* const invoices = await db.$withSession(
|
|
746
|
+
* { 'app.current_tenant': tenantId },
|
|
747
|
+
* (tx) => tx.invoices.findMany(),
|
|
748
|
+
* );
|
|
749
|
+
* ```
|
|
750
|
+
*/
|
|
751
|
+
$withSession<R>(context: Record<string, string | number | boolean>, fn: (tx: TransactionClient) => Promise<R>): Promise<R>;
|
|
752
|
+
/**
|
|
753
|
+
* Subscribe to a Postgres NOTIFY channel. The handler fires with each
|
|
754
|
+
* notification's payload string (the empty string when a payload-less
|
|
755
|
+
* NOTIFY is sent) for as long as the subscription is active.
|
|
756
|
+
*
|
|
757
|
+
* Each `$listen` checks out its OWN dedicated long-lived connection from the
|
|
758
|
+
* pool and runs `LISTEN "channel"` on it; `subscription.unsubscribe()`
|
|
759
|
+
* UNLISTENs, detaches the handler, and releases that connection. Active
|
|
760
|
+
* subscriptions are tracked and force-released on `disconnect()` so shutdown
|
|
761
|
+
* never hangs.
|
|
762
|
+
*
|
|
763
|
+
* The channel name CANNOT be a bound parameter (`LISTEN $1` is a syntax
|
|
764
|
+
* error), so it is validated against a strict identifier regex AND quoted via
|
|
765
|
+
* `quoteIdent` before interpolation — it is the only identifier this method
|
|
766
|
+
* places into SQL text.
|
|
767
|
+
*
|
|
768
|
+
* **Serverless caveat:** LISTEN needs a persistent connection that can push
|
|
769
|
+
* async notifications. Stateless HTTP drivers (Neon HTTP, Vercel Postgres)
|
|
770
|
+
* cannot do this — `$listen` throws a `ConnectionError` rather than hang.
|
|
771
|
+
* `$notify` works on every driver.
|
|
772
|
+
*
|
|
773
|
+
* @example
|
|
774
|
+
* ```ts
|
|
775
|
+
* const sub = await db.$listen('order_created', (payload) => {
|
|
776
|
+
* const order = JSON.parse(payload);
|
|
777
|
+
* console.log('new order', order.id);
|
|
778
|
+
* });
|
|
779
|
+
* // ...later
|
|
780
|
+
* await sub.unsubscribe();
|
|
781
|
+
* ```
|
|
782
|
+
*/
|
|
783
|
+
$listen(channel: string, handler: NotificationHandler): Promise<Subscription>;
|
|
784
|
+
/**
|
|
785
|
+
* Send a Postgres NOTIFY on `channel` with an optional payload string.
|
|
786
|
+
*
|
|
787
|
+
* Issued as `SELECT pg_notify($1, $2)` — both the channel and payload are
|
|
788
|
+
* BOUND parameters (no quoting/injection concern). The channel is still
|
|
789
|
+
* validated against the identifier regex for parity with `$listen` and to
|
|
790
|
+
* catch typos loudly. Works on every driver, including serverless HTTP pools.
|
|
791
|
+
*
|
|
792
|
+
* @example
|
|
793
|
+
* ```ts
|
|
794
|
+
* await db.$notify('order_created', JSON.stringify({ id: 7 }));
|
|
795
|
+
* ```
|
|
796
|
+
*/
|
|
797
|
+
$notify(channel: string, payload?: string): Promise<void>;
|
|
798
|
+
/**
|
|
799
|
+
* Execute an async function with automatic retry on retryable errors.
|
|
800
|
+
*
|
|
801
|
+
* Only errors with `isRetryable === true` (DeadlockError, SerializationFailureError)
|
|
802
|
+
* are retried. Uses exponential backoff with jitter.
|
|
803
|
+
*
|
|
804
|
+
* @example
|
|
805
|
+
* ```ts
|
|
806
|
+
* const result = await db.$retry(() =>
|
|
807
|
+
* db.$transaction(async (tx) => {
|
|
808
|
+
* // ... serializable transaction logic
|
|
809
|
+
* }, { isolationLevel: 'Serializable' })
|
|
810
|
+
* );
|
|
811
|
+
* ```
|
|
812
|
+
*/
|
|
813
|
+
$retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
814
|
+
/**
|
|
815
|
+
* Test the database connection.
|
|
816
|
+
* Throws if the connection fails.
|
|
817
|
+
*/
|
|
818
|
+
connect(): Promise<void>;
|
|
819
|
+
/**
|
|
820
|
+
* Gracefully shut down the connection pool.
|
|
821
|
+
*
|
|
822
|
+
* If Turbine was given an external pool via `TurbineConfig.pool`, this
|
|
823
|
+
* method is a no-op — the caller is responsible for the pool's lifecycle.
|
|
824
|
+
*/
|
|
825
|
+
disconnect(): Promise<void>;
|
|
826
|
+
/** Alias for disconnect() */
|
|
827
|
+
end(): Promise<void>;
|
|
828
|
+
/**
|
|
829
|
+
* Pool statistics for monitoring. Returns zeros for pools that don't
|
|
830
|
+
* expose connection counts (e.g., stateless HTTP drivers like Neon).
|
|
831
|
+
*/
|
|
832
|
+
get stats(): {
|
|
833
|
+
totalCount: number;
|
|
834
|
+
idleCount: number;
|
|
835
|
+
waitingCount: number;
|
|
836
|
+
};
|
|
837
|
+
}
|