turbine-orm 0.5.0 → 0.7.1

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 (50) hide show
  1. package/README.md +292 -26
  2. package/dist/cjs/cli/config.js +5 -15
  3. package/dist/cjs/cli/index.js +311 -43
  4. package/dist/cjs/cli/loader.js +129 -0
  5. package/dist/cjs/cli/migrate.js +96 -47
  6. package/dist/cjs/cli/ui.js +5 -9
  7. package/dist/cjs/client.js +158 -49
  8. package/dist/cjs/errors.js +424 -0
  9. package/dist/cjs/generate.js +145 -14
  10. package/dist/cjs/index.js +43 -20
  11. package/dist/cjs/introspect.js +3 -5
  12. package/dist/cjs/pipeline.js +9 -2
  13. package/dist/cjs/query.js +544 -115
  14. package/dist/cjs/schema-builder.js +150 -30
  15. package/dist/cjs/schema-sql.js +241 -37
  16. package/dist/cjs/schema.js +5 -2
  17. package/dist/cjs/serverless.js +88 -176
  18. package/dist/cli/config.js +6 -16
  19. package/dist/cli/index.js +316 -48
  20. package/dist/cli/loader.d.ts +45 -0
  21. package/dist/cli/loader.js +91 -0
  22. package/dist/cli/migrate.d.ts +13 -2
  23. package/dist/cli/migrate.js +97 -48
  24. package/dist/cli/ui.d.ts +1 -1
  25. package/dist/cli/ui.js +5 -9
  26. package/dist/client.d.ts +92 -4
  27. package/dist/client.js +158 -49
  28. package/dist/errors.d.ts +225 -0
  29. package/dist/errors.js +405 -0
  30. package/dist/generate.d.ts +7 -1
  31. package/dist/generate.js +148 -18
  32. package/dist/index.d.ts +11 -9
  33. package/dist/index.js +16 -12
  34. package/dist/introspect.d.ts +1 -1
  35. package/dist/introspect.js +4 -6
  36. package/dist/pipeline.d.ts +1 -1
  37. package/dist/pipeline.js +9 -2
  38. package/dist/query.d.ts +374 -38
  39. package/dist/query.js +545 -116
  40. package/dist/schema-builder.d.ts +38 -5
  41. package/dist/schema-builder.js +150 -31
  42. package/dist/schema-sql.d.ts +7 -3
  43. package/dist/schema-sql.js +241 -37
  44. package/dist/schema.d.ts +1 -1
  45. package/dist/schema.js +5 -2
  46. package/dist/serverless.d.ts +92 -139
  47. package/dist/serverless.js +87 -173
  48. package/package.json +33 -16
  49. package/dist/types.d.ts +0 -93
  50. package/dist/types.js +0 -126
package/dist/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @batadata/turbine — TurbineClient
2
+ * turbine-orm — TurbineClient
3
3
  *
4
4
  * The main entry point for the Turbine TypeScript SDK.
5
5
  * Manages connection pooling and provides typed table accessors.
@@ -16,28 +16,15 @@
16
16
  * const user = await db.users.findUnique({ where: { id: 1 } });
17
17
  *
18
18
  * // With base client (dynamic):
19
- * import { TurbineClient } from '@batadata/turbine';
19
+ * import { TurbineClient } from 'turbine-orm';
20
20
  * const db = new TurbineClient({ connectionString: '...' }, schema);
21
21
  * const users = db.table<User>('users');
22
22
  * ```
23
23
  */
24
24
  import pg from 'pg';
25
- import { QueryInterface } from './query.js';
25
+ import { setErrorMessageMode, TimeoutError, wrapPgError } from './errors.js';
26
26
  import { executePipeline } from './pipeline.js';
27
- /**
28
- * Parse int8 (bigint, OID 20) as JavaScript number instead of string.
29
- * Safe for values up to Number.MAX_SAFE_INTEGER (9,007,199,254,740,991).
30
- *
31
- * NOTE: For values exceeding Number.MAX_SAFE_INTEGER, the parser falls back
32
- * to returning the raw string to avoid precision loss. The generated TypeScript
33
- * type maps int8/bigint to `number`, which is correct for the vast majority of
34
- * use cases (IDs, counts, timestamps). If you store values > 2^53 - 1 in a
35
- * bigint column, the runtime return type will be `string` for those rows.
36
- */
37
- pg.types.setTypeParser(20, (val) => {
38
- const n = Number(val);
39
- return Number.isSafeInteger(n) ? n : val; // fall back to string for huge values
40
- });
27
+ import { QueryInterface } from './query.js';
41
28
  /** Maps isolation level names to SQL */
42
29
  const ISOLATION_LEVELS = {
43
30
  ReadUncommitted: 'READ UNCOMMITTED',
@@ -119,20 +106,36 @@ export class TransactionClient {
119
106
  sql += `$${i + 1}`;
120
107
  }
121
108
  });
122
- const result = await this.client.query(sql, values);
123
- return result.rows;
109
+ try {
110
+ const result = await this.client.query(sql, values);
111
+ return result.rows;
112
+ }
113
+ catch (err) {
114
+ throw wrapPgError(err);
115
+ }
124
116
  }
125
117
  /**
126
118
  * Create a pool-like wrapper around the transaction client.
127
119
  * This allows QueryInterface to work with the transaction connection
128
120
  * without knowing it's in a transaction.
121
+ *
122
+ * pg driver errors thrown by queries are translated into typed Turbine
123
+ * errors via wrapPgError so transaction-scoped queries surface the same
124
+ * typed errors as pool-scoped queries.
129
125
  */
130
126
  createTxPool() {
131
127
  const client = this.client;
132
128
  // Return a minimal pool-compatible object that routes queries
133
129
  // through the transaction client
134
130
  return {
135
- query: (text, values) => client.query(text, values),
131
+ query: async (text, values) => {
132
+ try {
133
+ return await client.query(text, values);
134
+ }
135
+ catch (err) {
136
+ throw wrapPgError(err);
137
+ }
138
+ },
136
139
  connect: () => Promise.resolve(client),
137
140
  };
138
141
  }
@@ -145,38 +148,86 @@ export class TurbineClient {
145
148
  pool;
146
149
  /** The schema metadata this client was built from */
147
150
  schema;
151
+ static int8ParserRegistered = false;
148
152
  logging;
149
153
  tableCache = new Map();
150
154
  middlewares = [];
151
155
  queryOptions;
156
+ /** True when Turbine created the pool and is responsible for tearing it down */
157
+ ownsPool = true;
152
158
  constructor(config = {}, schema) {
159
+ /**
160
+ * Parse int8 (bigint, OID 20) as JavaScript number instead of string.
161
+ * Safe for values up to Number.MAX_SAFE_INTEGER (9,007,199,254,740,991).
162
+ *
163
+ * NOTE: For values exceeding Number.MAX_SAFE_INTEGER, the parser falls back
164
+ * to returning the raw string to avoid precision loss. The generated TypeScript
165
+ * type maps int8/bigint to `number`, which is correct for the vast majority of
166
+ * use cases (IDs, counts, timestamps). If you store values > 2^53 - 1 in a
167
+ * bigint column, the runtime return type will be `string` for those rows.
168
+ *
169
+ * NOTE: We intentionally do NOT register a parser for numeric (OID 1700).
170
+ * Postgres numeric is arbitrary-precision, so the default pg driver behavior
171
+ * of returning a string is correct and matches the generated TypeScript type
172
+ * (numeric → string). Users who want number can cast explicitly in SQL.
173
+ */
174
+ // Only register the int8 parser when we own the pg driver. External
175
+ // pools (Neon HTTP, Vercel Postgres) may ship their own pg-types fork
176
+ // and rely on their own parser configuration — don't mutate global state
177
+ // we don't own.
178
+ if (!config.pool && !TurbineClient.int8ParserRegistered) {
179
+ pg.types.setTypeParser(20, (val) => {
180
+ const n = Number(val);
181
+ return Number.isSafeInteger(n) ? n : val;
182
+ });
183
+ TurbineClient.int8ParserRegistered = true;
184
+ }
153
185
  this.logging = config.logging ?? false;
154
186
  this.schema = schema;
155
187
  this.queryOptions = {
156
188
  defaultLimit: config.defaultLimit,
157
189
  warnOnUnlimited: config.warnOnUnlimited,
158
190
  };
159
- const poolConfig = {
160
- max: config.poolSize ?? 10,
161
- idleTimeoutMillis: config.idleTimeoutMs ?? 30_000,
162
- connectionTimeoutMillis: config.connectionTimeoutMs ?? 5_000,
163
- };
164
- if (config.connectionString) {
165
- poolConfig.connectionString = config.connectionString;
191
+ // Apply NotFoundError message redaction mode (default: safe — values are
192
+ // stripped from messages to avoid leaking PII into error logs).
193
+ if (config.errorMessages) {
194
+ setErrorMessageMode(config.errorMessages);
166
195
  }
167
- else {
168
- poolConfig.host = config.host ?? 'localhost';
169
- poolConfig.port = config.port ?? 5432;
170
- poolConfig.database = config.database ?? 'postgres';
171
- poolConfig.user = config.user ?? 'postgres';
172
- poolConfig.password = config.password;
196
+ if (config.pool) {
197
+ // External pool — use directly. Turbine doesn't manage its lifecycle.
198
+ this.pool = config.pool;
199
+ this.ownsPool = false;
200
+ if (this.logging) {
201
+ console.log(`[turbine] Using external pool — ${Object.keys(schema.tables).length} tables`);
202
+ }
173
203
  }
174
- this.pool = new pg.Pool(poolConfig);
175
- this.pool.on('error', (err) => {
176
- console.error('[turbine] Unexpected pool error:', err.message);
177
- });
178
- if (this.logging) {
179
- console.log(`[turbine] Pool created — max ${poolConfig.max} connections, ${Object.keys(schema.tables).length} tables`);
204
+ else {
205
+ const poolConfig = {
206
+ max: config.poolSize ?? 10,
207
+ idleTimeoutMillis: config.idleTimeoutMs ?? 30_000,
208
+ connectionTimeoutMillis: config.connectionTimeoutMs ?? 5_000,
209
+ };
210
+ if (config.connectionString) {
211
+ poolConfig.connectionString = config.connectionString;
212
+ }
213
+ else {
214
+ poolConfig.host = config.host ?? 'localhost';
215
+ poolConfig.port = config.port ?? 5432;
216
+ poolConfig.database = config.database ?? 'postgres';
217
+ poolConfig.user = config.user ?? 'postgres';
218
+ poolConfig.password = config.password;
219
+ }
220
+ if (config.ssl !== undefined) {
221
+ poolConfig.ssl = config.ssl;
222
+ }
223
+ this.pool = new pg.Pool(poolConfig);
224
+ this.ownsPool = true;
225
+ this.pool.on('error', (err) => {
226
+ console.error('[turbine] Unexpected pool error:', err.message);
227
+ });
228
+ if (this.logging) {
229
+ console.log(`[turbine] Pool created — max ${poolConfig.max} connections, ${Object.keys(schema.tables).length} tables`);
230
+ }
180
231
  }
181
232
  // Auto-create typed table accessors for all tables in the schema
182
233
  for (const tableName of Object.keys(schema.tables)) {
@@ -283,8 +334,13 @@ export class TurbineClient {
283
334
  if (this.logging) {
284
335
  console.log(`[turbine] Raw SQL: ${sql.trim().substring(0, 120)}...`);
285
336
  }
286
- const result = await this.pool.query(sql, values);
287
- return result.rows;
337
+ try {
338
+ const result = await this.pool.query(sql, values);
339
+ return result.rows;
340
+ }
341
+ catch (err) {
342
+ throw wrapPgError(err);
343
+ }
288
344
  }
289
345
  // -------------------------------------------------------------------------
290
346
  // Transaction support (raw — legacy)
@@ -341,6 +397,24 @@ export class TurbineClient {
341
397
  async $transaction(fn, options) {
342
398
  const client = await this.pool.connect();
343
399
  const timeout = options?.timeout;
400
+ /**
401
+ * Track whether the connection has already been released so the finally
402
+ * block doesn't double-release. When a timeout fires we destroy the
403
+ * connection eagerly to abort the in-flight backend query.
404
+ */
405
+ let released = false;
406
+ const releaseOnce = (err) => {
407
+ if (released)
408
+ return;
409
+ released = true;
410
+ try {
411
+ client.release(err);
412
+ }
413
+ catch {
414
+ // pg may throw if the client is already released — swallow.
415
+ }
416
+ };
417
+ let timedOut = false;
344
418
  try {
345
419
  // BEGIN with optional isolation level
346
420
  let beginSQL = 'BEGIN';
@@ -364,11 +438,23 @@ export class TurbineClient {
364
438
  }
365
439
  let result;
366
440
  if (timeout) {
367
- // Race between the function and a timeout
441
+ // Race between the function and a timeout. If the timeout fires we
442
+ // need to actually abort the in-flight query — otherwise the backend
443
+ // keeps running until pg's own timeout, holding a pool slot the whole
444
+ // time. The simplest reliable cancellation is to destroy the
445
+ // connection: passing a truthy argument to client.release() tells the
446
+ // pg pool to discard the client (its socket is closed, which causes
447
+ // Postgres to abort the active query and roll back the transaction).
448
+ // The pool will spin up a fresh connection on the next checkout.
368
449
  let timer;
369
450
  const timeoutPromise = new Promise((_, reject) => {
370
451
  timer = setTimeout(() => {
371
- reject(new Error(`[turbine] Transaction timed out after ${timeout}ms`));
452
+ timedOut = true;
453
+ // Destroy the connection to abort the in-flight backend query.
454
+ // We do this BEFORE rejecting so the socket is gone by the time
455
+ // the caller's catch block runs.
456
+ releaseOnce(new Error('[turbine] Transaction timeout — connection destroyed'));
457
+ reject(new TimeoutError(timeout, 'Transaction'));
372
458
  }, timeout);
373
459
  });
374
460
  try {
@@ -388,14 +474,25 @@ export class TurbineClient {
388
474
  return result;
389
475
  }
390
476
  catch (err) {
391
- await client.query('ROLLBACK');
477
+ // If the timeout fired we already destroyed the connection — issuing a
478
+ // ROLLBACK on a released client would throw "Client has already been
479
+ // released". Skip the rollback in that case (the backend rolled back
480
+ // when its socket was closed).
481
+ if (!timedOut && !released) {
482
+ try {
483
+ await client.query('ROLLBACK');
484
+ }
485
+ catch {
486
+ // Best-effort rollback — the connection may have died mid-query.
487
+ }
488
+ }
392
489
  if (this.logging) {
393
490
  console.log('[turbine] Transaction rolled back');
394
491
  }
395
492
  throw err;
396
493
  }
397
494
  finally {
398
- client.release();
495
+ releaseOnce();
399
496
  }
400
497
  }
401
498
  // -------------------------------------------------------------------------
@@ -419,8 +516,17 @@ export class TurbineClient {
419
516
  }
420
517
  /**
421
518
  * Gracefully shut down the connection pool.
519
+ *
520
+ * If Turbine was given an external pool via `TurbineConfig.pool`, this
521
+ * method is a no-op — the caller is responsible for the pool's lifecycle.
422
522
  */
423
523
  async disconnect() {
524
+ if (!this.ownsPool) {
525
+ if (this.logging) {
526
+ console.log('[turbine] disconnect() skipped — external pool is not owned by Turbine');
527
+ }
528
+ return;
529
+ }
424
530
  await this.pool.end();
425
531
  if (this.logging) {
426
532
  console.log('[turbine] Pool disconnected');
@@ -430,12 +536,15 @@ export class TurbineClient {
430
536
  async end() {
431
537
  return this.disconnect();
432
538
  }
433
- /** Pool statistics for monitoring. */
539
+ /**
540
+ * Pool statistics for monitoring. Returns zeros for pools that don't
541
+ * expose connection counts (e.g., stateless HTTP drivers like Neon).
542
+ */
434
543
  get stats() {
435
544
  return {
436
- totalCount: this.pool.totalCount,
437
- idleCount: this.pool.idleCount,
438
- waitingCount: this.pool.waitingCount,
545
+ totalCount: this.pool.totalCount ?? 0,
546
+ idleCount: this.pool.idleCount ?? 0,
547
+ waitingCount: this.pool.waitingCount ?? 0,
439
548
  };
440
549
  }
441
550
  }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * turbine-orm — Error types
3
+ *
4
+ * Typed errors with error codes for programmatic handling.
5
+ * All Turbine errors extend TurbineError which includes a `code` property.
6
+ */
7
+ /** Error codes for all Turbine errors */
8
+ export declare const TurbineErrorCode: {
9
+ readonly NOT_FOUND: "TURBINE_E001";
10
+ readonly TIMEOUT: "TURBINE_E002";
11
+ readonly VALIDATION: "TURBINE_E003";
12
+ readonly CONNECTION: "TURBINE_E004";
13
+ readonly RELATION: "TURBINE_E005";
14
+ readonly MIGRATION: "TURBINE_E006";
15
+ readonly CIRCULAR_RELATION: "TURBINE_E007";
16
+ readonly UNIQUE_VIOLATION: "TURBINE_E008";
17
+ readonly FOREIGN_KEY_VIOLATION: "TURBINE_E009";
18
+ readonly NOT_NULL_VIOLATION: "TURBINE_E010";
19
+ readonly CHECK_VIOLATION: "TURBINE_E011";
20
+ readonly DEADLOCK_DETECTED: "TURBINE_E012";
21
+ readonly SERIALIZATION_FAILURE: "TURBINE_E013";
22
+ };
23
+ export type TurbineErrorCode = (typeof TurbineErrorCode)[keyof typeof TurbineErrorCode];
24
+ /** Base error class for all Turbine errors */
25
+ export declare class TurbineError extends Error {
26
+ readonly code: TurbineErrorCode;
27
+ constructor(code: TurbineErrorCode, message: string, options?: {
28
+ cause?: unknown;
29
+ });
30
+ }
31
+ /**
32
+ * Controls whether NotFoundError messages include the actual `where` values
33
+ * (`'verbose'`) or only the where-clause keys (`'safe'`, the default).
34
+ *
35
+ * Defaults to `'safe'` to avoid leaking PII into error logs (Sentry, Datadog,
36
+ * etc.). The full `where` object is always available as `err.where` for
37
+ * programmatic access — only the human-readable message is redacted.
38
+ *
39
+ * Set via `setErrorMessageMode('verbose')` or by constructing TurbineClient
40
+ * with `{ errorMessages: 'verbose' }`.
41
+ */
42
+ export type ErrorMessageMode = 'safe' | 'verbose';
43
+ /**
44
+ * Set the global NotFoundError message mode. Called from the TurbineClient
45
+ * constructor when `TurbineConfig.errorMessages` is provided.
46
+ *
47
+ * - `'safe'` (default): the message includes only the keys of the where
48
+ * clause (e.g. `where: { id, email }`). Values are redacted.
49
+ * - `'verbose'`: the message includes the full JSON-serialized where
50
+ * clause (e.g. `where: {"id":1,"email":"alice@x.com"}`).
51
+ */
52
+ export declare function setErrorMessageMode(mode: ErrorMessageMode): void;
53
+ /** Returns the current NotFoundError message mode. Exported for tests. */
54
+ export declare function getErrorMessageMode(): ErrorMessageMode;
55
+ /**
56
+ * Thrown when a record is not found (findUniqueOrThrow, findFirstOrThrow,
57
+ * update/delete against a non-matching row, etc.)
58
+ *
59
+ * Supports two call styles for back-compat:
60
+ * - `new NotFoundError()` / `new NotFoundError('custom message')`
61
+ * - `new NotFoundError({ table, where, operation, cause, message })`
62
+ *
63
+ * When called with an options object and no explicit `message`, a Prisma-style
64
+ * message is built automatically. By default, only the where-clause keys are
65
+ * shown to avoid leaking PII into logs:
66
+ * `[turbine] findUniqueOrThrow on "users" found no record matching where: { id }`
67
+ *
68
+ * Set `setErrorMessageMode('verbose')` (or pass `errorMessages: 'verbose'` to
69
+ * the TurbineClient constructor) to include the full where values:
70
+ * `[turbine] findUniqueOrThrow on "users" found no record matching where: {"id":1}`
71
+ *
72
+ * The full `where` object, `table`, and `operation` are always available as
73
+ * structured properties on the error instance regardless of mode.
74
+ */
75
+ export declare class NotFoundError extends TurbineError {
76
+ readonly table?: string;
77
+ readonly where?: unknown;
78
+ readonly operation?: string;
79
+ constructor(input?: string | {
80
+ table?: string;
81
+ where?: unknown;
82
+ operation?: string;
83
+ cause?: unknown;
84
+ message?: string;
85
+ });
86
+ }
87
+ /** Thrown when a query or transaction exceeds the configured timeout */
88
+ export declare class TimeoutError extends TurbineError {
89
+ readonly timeoutMs: number;
90
+ constructor(timeoutMs: number, context?: string);
91
+ }
92
+ /** Thrown when query arguments fail validation (unknown column, invalid operator, etc.) */
93
+ export declare class ValidationError extends TurbineError {
94
+ constructor(message: string);
95
+ }
96
+ /** Thrown when a database connection fails */
97
+ export declare class ConnectionError extends TurbineError {
98
+ constructor(message: string);
99
+ }
100
+ /** Thrown when a relation reference is invalid */
101
+ export declare class RelationError extends TurbineError {
102
+ constructor(message: string);
103
+ }
104
+ /** Thrown when a migration operation fails */
105
+ export declare class MigrationError extends TurbineError {
106
+ constructor(message: string);
107
+ }
108
+ /** Thrown when circular relation nesting is detected */
109
+ export declare class CircularRelationError extends TurbineError {
110
+ readonly path: string[];
111
+ constructor(path: string[]);
112
+ }
113
+ /** Thrown when a UNIQUE constraint is violated (pg code 23505) */
114
+ export declare class UniqueConstraintError extends TurbineError {
115
+ readonly constraint?: string;
116
+ readonly columns?: string[];
117
+ readonly table?: string;
118
+ constructor(opts?: {
119
+ constraint?: string;
120
+ columns?: string[];
121
+ table?: string;
122
+ message?: string;
123
+ cause?: unknown;
124
+ });
125
+ }
126
+ /** Thrown when a FOREIGN KEY constraint is violated (pg code 23503) */
127
+ export declare class ForeignKeyError extends TurbineError {
128
+ readonly constraint?: string;
129
+ readonly table?: string;
130
+ constructor(opts?: {
131
+ constraint?: string;
132
+ table?: string;
133
+ message?: string;
134
+ cause?: unknown;
135
+ });
136
+ }
137
+ /** Thrown when a NOT NULL constraint is violated (pg code 23502) */
138
+ export declare class NotNullViolationError extends TurbineError {
139
+ readonly column?: string;
140
+ readonly table?: string;
141
+ constructor(opts?: {
142
+ column?: string;
143
+ table?: string;
144
+ message?: string;
145
+ cause?: unknown;
146
+ });
147
+ }
148
+ /**
149
+ * Thrown when Postgres detects a deadlock (pg code 40P01).
150
+ *
151
+ * This error is **retryable** — when caught, callers can safely retry the
152
+ * transaction (typically with backoff). Catch it explicitly:
153
+ *
154
+ * ```ts
155
+ * try {
156
+ * await db.$transaction(async (tx) => { ... });
157
+ * } catch (err) {
158
+ * if (err instanceof DeadlockError) {
159
+ * // safe to retry
160
+ * }
161
+ * }
162
+ * ```
163
+ */
164
+ export declare class DeadlockError extends TurbineError {
165
+ /** Marks this error as safe to retry */
166
+ readonly isRetryable: true;
167
+ readonly constraint?: string;
168
+ constructor(opts?: {
169
+ message?: string;
170
+ constraint?: string;
171
+ cause?: unknown;
172
+ });
173
+ }
174
+ /**
175
+ * Thrown when a Serializable transaction fails due to a serialization
176
+ * conflict (pg code 40001 — `could not serialize access due to ...`).
177
+ *
178
+ * This error is **retryable** — by Postgres documentation, the recommended
179
+ * response is to re-run the entire transaction. Catch it explicitly:
180
+ *
181
+ * ```ts
182
+ * try {
183
+ * await db.$transaction(async (tx) => { ... }, { isolationLevel: 'Serializable' });
184
+ * } catch (err) {
185
+ * if (err instanceof SerializationFailureError) {
186
+ * // safe to retry the whole transaction
187
+ * }
188
+ * }
189
+ * ```
190
+ */
191
+ export declare class SerializationFailureError extends TurbineError {
192
+ /** Marks this error as safe to retry */
193
+ readonly isRetryable: true;
194
+ constructor(opts?: {
195
+ message?: string;
196
+ cause?: unknown;
197
+ });
198
+ }
199
+ /** Thrown when a CHECK constraint is violated (pg code 23514) */
200
+ export declare class CheckConstraintError extends TurbineError {
201
+ readonly constraint?: string;
202
+ readonly table?: string;
203
+ constructor(opts?: {
204
+ constraint?: string;
205
+ table?: string;
206
+ message?: string;
207
+ cause?: unknown;
208
+ });
209
+ }
210
+ /**
211
+ * Translate a pg driver error into a typed Turbine error.
212
+ * If the error doesn't match a known constraint code, returns it unchanged.
213
+ *
214
+ * Maps:
215
+ * 23505 (unique_violation) -> UniqueConstraintError
216
+ * 23503 (foreign_key_violation) -> ForeignKeyError
217
+ * 23502 (not_null_violation) -> NotNullViolationError
218
+ * 23514 (check_violation) -> CheckConstraintError
219
+ * 40P01 (deadlock_detected) -> DeadlockError (retryable)
220
+ * 40001 (serialization_failure) -> SerializationFailureError (retryable)
221
+ *
222
+ * The original pg error is preserved as `.cause` on the wrapped error.
223
+ */
224
+ export declare function wrapPgError(err: unknown): unknown;
225
+ //# sourceMappingURL=errors.d.ts.map