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.
Files changed (157) hide show
  1. package/README.md +122 -39
  2. package/dist/cjs/adapters/cockroachdb.d.ts +39 -0
  3. package/dist/cjs/adapters/index.d.ts +110 -0
  4. package/dist/cjs/adapters/yugabytedb.d.ts +51 -0
  5. package/dist/cjs/cli/config.d.ts +181 -0
  6. package/dist/cjs/cli/config.js +32 -6
  7. package/dist/cjs/cli/destructive.d.ts +38 -0
  8. package/dist/cjs/cli/index.d.ts +359 -0
  9. package/dist/cjs/cli/index.js +228 -56
  10. package/dist/cjs/cli/loader.d.ts +61 -0
  11. package/dist/cjs/cli/mcp.d.ts +42 -0
  12. package/dist/cjs/cli/migrate.d.ts +356 -0
  13. package/dist/cjs/cli/migrate.js +131 -40
  14. package/dist/cjs/cli/observe-ui.d.ts +1 -0
  15. package/dist/cjs/cli/observe-ui.js +14 -5
  16. package/dist/cjs/cli/observe.d.ts +25 -0
  17. package/dist/cjs/cli/observe.js +49 -12
  18. package/dist/cjs/cli/pii-tags.d.ts +53 -0
  19. package/dist/cjs/cli/prisma-report.d.ts +33 -0
  20. package/dist/cjs/cli/prisma-report.js +73 -0
  21. package/dist/cjs/cli/prisma-resolve.d.ts +106 -0
  22. package/dist/cjs/cli/prisma-resolve.js +1 -0
  23. package/dist/cjs/cli/prisma-schema.d.ts +176 -0
  24. package/dist/cjs/cli/prisma-schema.js +82 -4
  25. package/dist/cjs/cli/rate-limit.d.ts +32 -0
  26. package/dist/cjs/cli/rate-limit.js +45 -0
  27. package/dist/cjs/cli/studio-demo.d.ts +43 -0
  28. package/dist/cjs/cli/studio-ui.generated.d.ts +1 -0
  29. package/dist/cjs/cli/studio.d.ts +207 -0
  30. package/dist/cjs/cli/studio.js +136 -71
  31. package/dist/cjs/cli/ui.d.ts +73 -0
  32. package/dist/cjs/cli/ui.js +51 -9
  33. package/dist/cjs/client.d.ts +837 -0
  34. package/dist/cjs/client.js +3 -0
  35. package/dist/cjs/dialect.d.ts +516 -0
  36. package/dist/cjs/dialect.js +37 -12
  37. package/dist/cjs/errors.d.ts +370 -0
  38. package/dist/cjs/generate.d.ts +137 -0
  39. package/dist/cjs/generate.js +39 -6
  40. package/dist/cjs/index-advisor.d.ts +153 -0
  41. package/dist/cjs/index-stats.d.ts +384 -0
  42. package/dist/cjs/index.d.ts +55 -0
  43. package/dist/cjs/index.js +7 -2
  44. package/dist/cjs/introspect.d.ts +269 -0
  45. package/dist/cjs/mssql.d.ts +232 -0
  46. package/dist/cjs/mssql.js +6 -0
  47. package/dist/cjs/mysql.d.ts +173 -0
  48. package/dist/cjs/mysql.js +16 -0
  49. package/dist/cjs/nested-write.d.ts +96 -0
  50. package/dist/cjs/nested-write.js +414 -24
  51. package/dist/cjs/observe.d.ts +115 -0
  52. package/dist/cjs/optional-peer-import.d.cts +72 -0
  53. package/dist/cjs/pipeline-submittable.d.ts +93 -0
  54. package/dist/cjs/pipeline.d.ts +71 -0
  55. package/dist/cjs/powdb-introspect.d.ts +84 -0
  56. package/dist/cjs/powdb.d.ts +931 -0
  57. package/dist/cjs/powdb.js +106 -21
  58. package/dist/cjs/powql.d.ts +592 -0
  59. package/dist/cjs/powql.js +42 -6
  60. package/dist/cjs/prisma-compat.d.ts +283 -0
  61. package/dist/cjs/prisma-compat.js +167 -9
  62. package/dist/cjs/query/aggregates.d.ts +92 -0
  63. package/dist/cjs/query/aggregates.js +7 -3
  64. package/dist/cjs/query/batched-loader.d.ts +193 -0
  65. package/dist/cjs/query/builder.d.ts +849 -0
  66. package/dist/cjs/query/builder.js +571 -65
  67. package/dist/cjs/query/compound-unique.d.ts +51 -0
  68. package/dist/cjs/query/deferred.d.ts +223 -0
  69. package/dist/cjs/query/filters.d.ts +201 -0
  70. package/dist/cjs/query/index.d.ts +14 -0
  71. package/dist/cjs/query/index.js +6 -1
  72. package/dist/cjs/query/relations.d.ts +609 -0
  73. package/dist/cjs/query/relations.js +693 -46
  74. package/dist/cjs/query/types.d.ts +1300 -0
  75. package/dist/cjs/query/utils.d.ts +209 -0
  76. package/dist/cjs/query/utils.js +208 -1
  77. package/dist/cjs/query/warn-registry.d.ts +68 -0
  78. package/dist/cjs/query/warn-registry.js +9 -0
  79. package/dist/cjs/query/where-compile.d.ts +139 -0
  80. package/dist/cjs/query/where.d.ts +548 -0
  81. package/dist/cjs/query/where.js +58 -22
  82. package/dist/cjs/query/writes.d.ts +172 -0
  83. package/dist/cjs/query/writes.js +105 -12
  84. package/dist/cjs/realtime.d.ts +70 -0
  85. package/dist/cjs/schema-builder.d.ts +354 -0
  86. package/dist/cjs/schema-metadata.d.ts +83 -0
  87. package/dist/cjs/schema-sql.d.ts +217 -0
  88. package/dist/cjs/schema-sql.js +23 -5
  89. package/dist/cjs/schema.d.ts +356 -0
  90. package/dist/cjs/schema.js +125 -0
  91. package/dist/cjs/seed.d.ts +15 -0
  92. package/dist/cjs/serverless.d.ts +142 -0
  93. package/dist/cjs/sqlite.d.ts +143 -0
  94. package/dist/cjs/sqlite.js +4 -0
  95. package/dist/cjs/typed-sql.d.ts +102 -0
  96. package/dist/cli/config.d.ts +18 -4
  97. package/dist/cli/config.js +31 -6
  98. package/dist/cli/index.d.ts +123 -0
  99. package/dist/cli/index.js +223 -58
  100. package/dist/cli/migrate.d.ts +59 -10
  101. package/dist/cli/migrate.js +128 -41
  102. package/dist/cli/observe-ui.d.ts +1 -1
  103. package/dist/cli/observe-ui.js +14 -5
  104. package/dist/cli/observe.d.ts +7 -1
  105. package/dist/cli/observe.js +48 -12
  106. package/dist/cli/prisma-report.d.ts +14 -0
  107. package/dist/cli/prisma-report.js +72 -0
  108. package/dist/cli/prisma-resolve.d.ts +6 -0
  109. package/dist/cli/prisma-resolve.js +1 -0
  110. package/dist/cli/prisma-schema.d.ts +62 -2
  111. package/dist/cli/prisma-schema.js +81 -4
  112. package/dist/cli/rate-limit.d.ts +32 -0
  113. package/dist/cli/rate-limit.js +40 -0
  114. package/dist/cli/studio.d.ts +5 -5
  115. package/dist/cli/studio.js +135 -70
  116. package/dist/cli/ui.d.ts +1 -1
  117. package/dist/cli/ui.js +51 -9
  118. package/dist/client.d.ts +40 -0
  119. package/dist/client.js +3 -0
  120. package/dist/dialect.d.ts +17 -1
  121. package/dist/dialect.js +37 -12
  122. package/dist/generate.js +40 -7
  123. package/dist/index.d.ts +1 -1
  124. package/dist/index.js +1 -1
  125. package/dist/mssql.js +6 -0
  126. package/dist/mysql.js +16 -0
  127. package/dist/nested-write.d.ts +2 -0
  128. package/dist/nested-write.js +415 -25
  129. package/dist/powdb.d.ts +4 -2
  130. package/dist/powdb.js +106 -21
  131. package/dist/powql.d.ts +5 -0
  132. package/dist/powql.js +42 -6
  133. package/dist/prisma-compat.d.ts +2 -0
  134. package/dist/prisma-compat.js +166 -8
  135. package/dist/query/aggregates.js +7 -3
  136. package/dist/query/builder.d.ts +292 -21
  137. package/dist/query/builder.js +570 -64
  138. package/dist/query/deferred.d.ts +39 -0
  139. package/dist/query/index.d.ts +1 -1
  140. package/dist/query/index.js +1 -1
  141. package/dist/query/relations.d.ts +173 -5
  142. package/dist/query/relations.js +688 -47
  143. package/dist/query/types.d.ts +123 -39
  144. package/dist/query/utils.d.ts +116 -0
  145. package/dist/query/utils.js +198 -0
  146. package/dist/query/warn-registry.d.ts +9 -0
  147. package/dist/query/warn-registry.js +9 -0
  148. package/dist/query/where.d.ts +38 -1
  149. package/dist/query/where.js +58 -23
  150. package/dist/query/writes.d.ts +42 -1
  151. package/dist/query/writes.js +104 -13
  152. package/dist/schema-sql.d.ts +14 -0
  153. package/dist/schema-sql.js +23 -5
  154. package/dist/schema.d.ts +38 -0
  155. package/dist/schema.js +123 -0
  156. package/dist/sqlite.js +4 -0
  157. 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
+ }