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.
- package/README.md +292 -26
- package/dist/cjs/cli/config.js +5 -15
- package/dist/cjs/cli/index.js +311 -43
- package/dist/cjs/cli/loader.js +129 -0
- package/dist/cjs/cli/migrate.js +96 -47
- package/dist/cjs/cli/ui.js +5 -9
- package/dist/cjs/client.js +158 -49
- package/dist/cjs/errors.js +424 -0
- package/dist/cjs/generate.js +145 -14
- package/dist/cjs/index.js +43 -20
- package/dist/cjs/introspect.js +3 -5
- package/dist/cjs/pipeline.js +9 -2
- package/dist/cjs/query.js +544 -115
- package/dist/cjs/schema-builder.js +150 -30
- package/dist/cjs/schema-sql.js +241 -37
- package/dist/cjs/schema.js +5 -2
- package/dist/cjs/serverless.js +88 -176
- package/dist/cli/config.js +6 -16
- package/dist/cli/index.js +316 -48
- package/dist/cli/loader.d.ts +45 -0
- package/dist/cli/loader.js +91 -0
- package/dist/cli/migrate.d.ts +13 -2
- package/dist/cli/migrate.js +97 -48
- package/dist/cli/ui.d.ts +1 -1
- package/dist/cli/ui.js +5 -9
- package/dist/client.d.ts +92 -4
- package/dist/client.js +158 -49
- package/dist/errors.d.ts +225 -0
- package/dist/errors.js +405 -0
- package/dist/generate.d.ts +7 -1
- package/dist/generate.js +148 -18
- package/dist/index.d.ts +11 -9
- package/dist/index.js +16 -12
- package/dist/introspect.d.ts +1 -1
- package/dist/introspect.js +4 -6
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +9 -2
- package/dist/query.d.ts +374 -38
- package/dist/query.js +545 -116
- package/dist/schema-builder.d.ts +38 -5
- package/dist/schema-builder.js +150 -31
- package/dist/schema-sql.d.ts +7 -3
- package/dist/schema-sql.js +241 -37
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +5 -2
- package/dist/serverless.d.ts +92 -139
- package/dist/serverless.js +87 -173
- package/package.json +33 -16
- package/dist/types.d.ts +0 -93
- package/dist/types.js +0 -126
package/dist/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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 '
|
|
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 {
|
|
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
|
-
|
|
123
|
-
|
|
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) =>
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
}
|
package/dist/errors.d.ts
ADDED
|
@@ -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
|