@nextlyhq/adapter-postgres 0.0.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/dist/index.mjs ADDED
@@ -0,0 +1,674 @@
1
+ import { DrizzleAdapter } from '@nextlyhq/adapter-drizzle';
2
+ import { createDatabaseError, isDatabaseError } from '@nextlyhq/adapter-drizzle/types';
3
+ import { checkDialectVersion } from '@nextlyhq/adapter-drizzle/version-check';
4
+ import { drizzle } from 'drizzle-orm/node-postgres';
5
+ import { Pool } from 'pg';
6
+
7
+ // src/index.ts
8
+
9
+ // src/provider.ts
10
+ function detectPostgresProvider(url, explicitProvider) {
11
+ if (explicitProvider) {
12
+ const normalized = explicitProvider.toLowerCase();
13
+ if (normalized === "neon" || normalized === "supabase" || normalized === "standard") {
14
+ return normalized;
15
+ }
16
+ }
17
+ if (url.includes(".neon.tech") || url.includes("neon.")) {
18
+ return "neon";
19
+ }
20
+ if (url.includes(".supabase.") || url.includes("supabase.")) {
21
+ return "supabase";
22
+ }
23
+ return "standard";
24
+ }
25
+ function getProviderDefaults(provider) {
26
+ switch (provider) {
27
+ case "neon":
28
+ return {
29
+ ssl: true,
30
+ poolMax: 5,
31
+ poolMin: 0,
32
+ idleTimeoutMs: 1e4,
33
+ connectionTimeoutMs: 2e4,
34
+ statementTimeoutMs: 3e4,
35
+ retryAttempts: 5
36
+ };
37
+ case "supabase":
38
+ return {
39
+ ssl: true,
40
+ poolMax: 5,
41
+ poolMin: 0,
42
+ idleTimeoutMs: 3e4,
43
+ connectionTimeoutMs: 15e3,
44
+ statementTimeoutMs: 15e3,
45
+ retryAttempts: 3
46
+ };
47
+ case "standard":
48
+ default:
49
+ return {
50
+ ssl: false,
51
+ poolMax: 10,
52
+ poolMin: 0,
53
+ idleTimeoutMs: 3e4,
54
+ connectionTimeoutMs: 15e3,
55
+ statementTimeoutMs: 15e3,
56
+ retryAttempts: 3
57
+ };
58
+ }
59
+ }
60
+
61
+ // src/index.ts
62
+ var VERSION = "0.1.0";
63
+ var DEFAULT_POOL_CONFIG = {
64
+ min: 0,
65
+ max: 5,
66
+ idleTimeoutMs: 3e4,
67
+ connectionTimeoutMs: 15e3
68
+ };
69
+ var PG_ERROR_CODES = {
70
+ // Class 08 - Connection Exception
71
+ "08000": "connection",
72
+ "08003": "connection",
73
+ "08006": "connection",
74
+ "08001": "connection",
75
+ "08004": "connection",
76
+ "08007": "connection",
77
+ "08P01": "connection",
78
+ // Class 23 - Integrity Constraint Violation
79
+ "23000": "constraint",
80
+ "23001": "constraint",
81
+ "23502": "not_null_violation",
82
+ "23503": "foreign_key_violation",
83
+ "23505": "unique_violation",
84
+ "23514": "check_violation",
85
+ "23P01": "constraint",
86
+ // Class 40 - Transaction Rollback
87
+ "40000": "query",
88
+ "40001": "serialization_failure",
89
+ "40002": "constraint",
90
+ "40003": "query",
91
+ "40P01": "deadlock",
92
+ // Class 57 - Operator Intervention
93
+ "57014": "timeout",
94
+ "57P01": "connection",
95
+ "57P02": "connection",
96
+ "57P03": "connection"
97
+ };
98
+ function delay(ms) {
99
+ return new Promise((resolve) => setTimeout(resolve, ms));
100
+ }
101
+ var PostgresAdapter = class extends DrizzleAdapter {
102
+ /**
103
+ * The database dialect - always 'postgresql' for this adapter.
104
+ */
105
+ dialect = "postgresql";
106
+ /**
107
+ * Adapter configuration.
108
+ */
109
+ config;
110
+ /**
111
+ * Connection pool instance.
112
+ */
113
+ pool = null;
114
+ /**
115
+ * Connection state flag.
116
+ */
117
+ connected = false;
118
+ /**
119
+ * Auto-detected provider (Neon, Supabase, or standard).
120
+ * Set during connect() from DATABASE_URL pattern or DB_PROVIDER env var.
121
+ */
122
+ detectedProvider = "standard";
123
+ /**
124
+ * Provider-specific connection defaults. Applied as fallbacks when
125
+ * user config doesn't specify a value.
126
+ */
127
+ providerDefaults = getProviderDefaults("standard");
128
+ /**
129
+ * Creates a new PostgreSQL adapter instance.
130
+ *
131
+ * @param config - Adapter configuration
132
+ */
133
+ constructor(config) {
134
+ super();
135
+ this.config = config;
136
+ }
137
+ /**
138
+ * Establishes a connection to the PostgreSQL database.
139
+ *
140
+ * @remarks
141
+ * This method initializes the connection pool and verifies connectivity
142
+ * by executing a simple query. It is idempotent - calling it multiple
143
+ * times will not create multiple pools.
144
+ *
145
+ * @throws {DatabaseError} If connection fails
146
+ */
147
+ async connect() {
148
+ if (this.connected && this.pool) {
149
+ return;
150
+ }
151
+ const url = this.config.url || "";
152
+ this.detectedProvider = detectPostgresProvider(
153
+ url,
154
+ process.env.DB_PROVIDER
155
+ );
156
+ this.providerDefaults = getProviderDefaults(this.detectedProvider);
157
+ if (this.config.logger?.info) {
158
+ const source = process.env.DB_PROVIDER ? "(explicit)" : "(auto-detected)";
159
+ this.config.logger.info(
160
+ `PostgreSQL provider: ${this.detectedProvider} ${source}`,
161
+ {}
162
+ );
163
+ }
164
+ const retryableNodeCodes = /* @__PURE__ */ new Set([
165
+ "ETIMEDOUT",
166
+ "ECONNREFUSED",
167
+ "ECONNRESET",
168
+ "ENOTFOUND",
169
+ "EAI_AGAIN"
170
+ ]);
171
+ const maxAttempts = this.providerDefaults.retryAttempts;
172
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
173
+ try {
174
+ const poolConfig = this.buildPoolConfig();
175
+ this.pool = new Pool(poolConfig);
176
+ this.pool.on("error", (err) => {
177
+ if (this.config.logger?.error) {
178
+ this.config.logger.error(err, {
179
+ context: "pool_error",
180
+ message: "Unexpected error on idle client"
181
+ });
182
+ }
183
+ });
184
+ const client = await this.pool.connect();
185
+ try {
186
+ await client.query("SELECT 1");
187
+ await checkDialectVersion(client, "postgresql", {
188
+ // Why: route any future variant warnings through the adapter's
189
+ // logger. PG has no recognized variants today, but this keeps
190
+ // the integration symmetric with MySQL.
191
+ onWarning: (msg) => this.config.logger?.warn?.(msg)
192
+ });
193
+ this.connected = true;
194
+ if (this.config.logger?.info) {
195
+ this.config.logger.info("PostgreSQL connection established", {
196
+ host: this.config.host ?? "from URL",
197
+ database: this.config.database ?? "from URL"
198
+ });
199
+ }
200
+ return;
201
+ } finally {
202
+ client.release();
203
+ }
204
+ } catch (error) {
205
+ if (this.pool) {
206
+ await this.pool.end().catch(() => {
207
+ });
208
+ this.pool = null;
209
+ }
210
+ const nodeError = error;
211
+ const isRetryable = nodeError.code != null && retryableNodeCodes.has(nodeError.code);
212
+ if (isRetryable && attempt < maxAttempts) {
213
+ const waitMs = 1e3 * attempt;
214
+ const msg = `PostgreSQL connection attempt ${attempt}/${maxAttempts} failed with ${nodeError.code}, retrying in ${waitMs}ms...`;
215
+ if (this.config.logger?.warn) {
216
+ this.config.logger.warn(msg);
217
+ } else {
218
+ console.warn(`[PostgresAdapter] ${msg}`);
219
+ }
220
+ await delay(waitMs);
221
+ continue;
222
+ }
223
+ throw this.classifyError(error);
224
+ }
225
+ }
226
+ }
227
+ /**
228
+ * Closes the database connection and releases all pool resources.
229
+ *
230
+ * @remarks
231
+ * This method is idempotent - calling it multiple times is safe.
232
+ * It waits for all checked-out clients to be returned before shutting down.
233
+ */
234
+ async disconnect() {
235
+ if (!this.pool) {
236
+ return;
237
+ }
238
+ try {
239
+ await this.pool.end();
240
+ if (this.config.logger?.info) {
241
+ this.config.logger.info("PostgreSQL connection closed");
242
+ }
243
+ } finally {
244
+ this.pool = null;
245
+ this.connected = false;
246
+ }
247
+ }
248
+ /**
249
+ * Checks if the adapter is currently connected.
250
+ *
251
+ * @returns True if connected and pool is available
252
+ */
253
+ isConnected() {
254
+ return this.connected && this.pool !== null;
255
+ }
256
+ /**
257
+ * Executes a raw SQL query.
258
+ *
259
+ * @param sql - SQL statement with $1, $2, ... placeholders
260
+ * @param params - Query parameters
261
+ * @returns Array of result rows
262
+ *
263
+ * @throws {DatabaseError} If query execution fails
264
+ */
265
+ async executeQuery(sql, params = []) {
266
+ const pool = this.ensurePool();
267
+ const startTime = Date.now();
268
+ const retryableNodeCodes = /* @__PURE__ */ new Set([
269
+ "ETIMEDOUT",
270
+ "ECONNRESET",
271
+ "ECONNREFUSED"
272
+ ]);
273
+ const maxQueryAttempts = 3;
274
+ for (let attempt = 1; attempt <= maxQueryAttempts; attempt++) {
275
+ try {
276
+ const result = await pool.query(sql, params);
277
+ if (this.config.logger?.query) {
278
+ const durationMs = Date.now() - startTime;
279
+ this.config.logger.query(sql, params, durationMs);
280
+ }
281
+ return result.rows;
282
+ } catch (error) {
283
+ const nodeError = error;
284
+ const isRetryable = nodeError.code != null && retryableNodeCodes.has(nodeError.code);
285
+ if (isRetryable && attempt < maxQueryAttempts) {
286
+ const waitMs = 500 * attempt;
287
+ console.warn(
288
+ `[PostgresAdapter] Query attempt ${attempt}/${maxQueryAttempts} failed with ${nodeError.code}, retrying in ${waitMs}ms...`
289
+ );
290
+ await delay(waitMs);
291
+ continue;
292
+ }
293
+ throw this.classifyError(error, sql);
294
+ }
295
+ }
296
+ throw this.classifyError(new Error("executeQuery: exhausted retries"));
297
+ }
298
+ /**
299
+ * Executes a callback within a database transaction.
300
+ *
301
+ * @remarks
302
+ * PostgreSQL supports full ACID transactions with savepoints.
303
+ * If the callback throws, the transaction is rolled back.
304
+ *
305
+ * Supports automatic retry for serialization failures (40001) and
306
+ * deadlocks (40P01) when `retryCount` is specified in options.
307
+ *
308
+ * @param callback - Function to execute within the transaction
309
+ * @param options - Transaction options (isolation level, timeout, retry)
310
+ * @returns The result of the callback
311
+ *
312
+ * @throws {DatabaseError} If transaction fails after all retries
313
+ */
314
+ async transaction(callback, options) {
315
+ const pool = this.ensurePool();
316
+ const maxAttempts = (options?.retryCount ?? 0) + 1;
317
+ const retryDelayMs = options?.retryDelayMs ?? 100;
318
+ let lastError;
319
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
320
+ const client = await pool.connect();
321
+ const startTime = Date.now();
322
+ try {
323
+ await this.beginTransaction(client, options);
324
+ const ctx = this.createTransactionContext(client);
325
+ const result = await callback(ctx);
326
+ await client.query("COMMIT");
327
+ if (this.config.logger?.debug) {
328
+ const durationMs = Date.now() - startTime;
329
+ this.config.logger.debug("Transaction committed", {
330
+ attempt,
331
+ durationMs
332
+ });
333
+ }
334
+ return result;
335
+ } catch (error) {
336
+ await client.query("ROLLBACK").catch(() => {
337
+ });
338
+ lastError = error;
339
+ const pgError = error;
340
+ const isRetryable = pgError.code === "40001" || // serialization_failure
341
+ pgError.code === "40P01";
342
+ if (isRetryable && attempt < maxAttempts) {
343
+ if (this.config.logger?.warn) {
344
+ this.config.logger.warn(
345
+ `Transaction failed with ${pgError.code}, retrying (${attempt}/${maxAttempts})`,
346
+ { code: pgError.code, attempt }
347
+ );
348
+ }
349
+ await delay(retryDelayMs * attempt);
350
+ continue;
351
+ }
352
+ throw this.classifyError(error);
353
+ } finally {
354
+ client.release();
355
+ }
356
+ }
357
+ throw this.classifyError(lastError);
358
+ }
359
+ /**
360
+ * Returns the database capabilities for PostgreSQL.
361
+ *
362
+ * @remarks
363
+ * PostgreSQL has the most comprehensive feature set of all supported
364
+ * databases, including JSONB, arrays, full-text search, and more.
365
+ */
366
+ getCapabilities() {
367
+ return {
368
+ dialect: "postgresql",
369
+ supportsJsonb: true,
370
+ supportsJson: true,
371
+ supportsArrays: true,
372
+ supportsGeneratedColumns: true,
373
+ supportsFts: true,
374
+ supportsIlike: true,
375
+ supportsReturning: true,
376
+ supportsSavepoints: true,
377
+ supportsOnConflict: true,
378
+ maxParamsPerQuery: 65535,
379
+ // PostgreSQL limit
380
+ maxIdentifierLength: 63
381
+ // PostgreSQL default
382
+ };
383
+ }
384
+ /**
385
+ * Returns connection pool statistics.
386
+ *
387
+ * @returns Pool stats or null if not connected
388
+ */
389
+ getPoolStats() {
390
+ if (!this.pool) {
391
+ return null;
392
+ }
393
+ return {
394
+ total: this.pool.totalCount,
395
+ idle: this.pool.idleCount,
396
+ waiting: this.pool.waitingCount,
397
+ active: this.pool.totalCount - this.pool.idleCount
398
+ };
399
+ }
400
+ /**
401
+ * Override insertMany for bulk insert optimization.
402
+ *
403
+ * @remarks
404
+ * Uses a single multi-row INSERT statement for better performance
405
+ * when inserting multiple records.
406
+ */
407
+ async insertMany(table, data, options) {
408
+ if (data.length === 0) {
409
+ return [];
410
+ }
411
+ if (data.length === 1) {
412
+ const result = await this.insert(table, data[0], options);
413
+ return [result];
414
+ }
415
+ const columns = Object.keys(data[0]);
416
+ const params = [];
417
+ const valuesClauses = [];
418
+ for (let i = 0; i < data.length; i++) {
419
+ const record = data[i];
420
+ const placeholders = [];
421
+ for (const col of columns) {
422
+ params.push(record[col]);
423
+ placeholders.push(`$${params.length}`);
424
+ }
425
+ valuesClauses.push(`(${placeholders.join(", ")})`);
426
+ }
427
+ const columnList = columns.map((col) => this.escapeIdentifier(col)).join(", ");
428
+ let sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columnList}) VALUES ${valuesClauses.join(", ")}`;
429
+ if (options?.returning) {
430
+ const returning = options.returning === "*" ? "*" : options.returning.map((col) => this.escapeIdentifier(col)).join(", ");
431
+ sql += ` RETURNING ${returning}`;
432
+ }
433
+ try {
434
+ return await this.executeQuery(sql, params);
435
+ } catch (error) {
436
+ throw this.handleQueryError(error, "insertMany", table);
437
+ }
438
+ }
439
+ // ============================================================
440
+ // Protected Helper Methods
441
+ // ============================================================
442
+ /**
443
+ * Ensures pool is connected and returns it.
444
+ *
445
+ * @throws {DatabaseError} If not connected
446
+ */
447
+ ensurePool() {
448
+ if (!this.pool) {
449
+ throw createDatabaseError({
450
+ kind: "connection",
451
+ message: "PostgresAdapter is not connected. Call connect() first."
452
+ });
453
+ }
454
+ return this.pool;
455
+ }
456
+ /**
457
+ * Return the typed Drizzle instance for PostgreSQL.
458
+ * Guarded for server-only usage and requires an active connection.
459
+ *
460
+ * @param schema - Optional schema for relational queries (db.query.*)
461
+ * @returns Drizzle ORM instance wrapping the pg pool connection
462
+ * @throws {Error} If called in browser or not connected
463
+ */
464
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
465
+ getDrizzle(schema) {
466
+ if (typeof window !== "undefined") {
467
+ throw new Error("getDrizzle() is server-only");
468
+ }
469
+ const pool = this.ensurePool();
470
+ return schema ? drizzle(pool, { schema }) : drizzle(pool);
471
+ }
472
+ /**
473
+ * Builds pg Pool configuration from adapter config.
474
+ */
475
+ buildPoolConfig() {
476
+ const config = {};
477
+ if (this.config.url) {
478
+ config.connectionString = this.config.url;
479
+ } else {
480
+ if (this.config.host) config.host = this.config.host;
481
+ if (this.config.port) config.port = this.config.port;
482
+ if (this.config.database) config.database = this.config.database;
483
+ if (this.config.user) config.user = this.config.user;
484
+ if (this.config.password) config.password = this.config.password;
485
+ }
486
+ config.min = this.config.pool?.min ?? this.providerDefaults.poolMin ?? DEFAULT_POOL_CONFIG.min;
487
+ config.max = this.config.pool?.max ?? this.providerDefaults.poolMax ?? DEFAULT_POOL_CONFIG.max;
488
+ config.idleTimeoutMillis = this.config.pool?.idleTimeoutMs ?? this.providerDefaults.idleTimeoutMs ?? DEFAULT_POOL_CONFIG.idleTimeoutMs;
489
+ config.connectionTimeoutMillis = this.config.pool?.connectionTimeoutMs ?? this.providerDefaults.connectionTimeoutMs ?? DEFAULT_POOL_CONFIG.connectionTimeoutMs;
490
+ config.keepAlive = true;
491
+ config.keepAliveInitialDelayMillis = 1e4;
492
+ if (this.config.ssl) {
493
+ if (typeof this.config.ssl === "boolean") {
494
+ config.ssl = this.config.ssl;
495
+ } else {
496
+ if (this.config.ssl.rejectUnauthorized === false) {
497
+ console.warn(
498
+ "[nextly/adapter-postgres] ssl.rejectUnauthorized is set to false \u2014 TLS certificates will not be validated. This is unsafe on untrusted networks. Provide a trusted `ca` cert instead, or remove the rejectUnauthorized override."
499
+ );
500
+ }
501
+ config.ssl = {
502
+ rejectUnauthorized: this.config.ssl.rejectUnauthorized,
503
+ ca: this.config.ssl.ca,
504
+ cert: this.config.ssl.cert,
505
+ key: this.config.ssl.key
506
+ };
507
+ }
508
+ } else if (this.providerDefaults.ssl) {
509
+ config.ssl = { rejectUnauthorized: true };
510
+ }
511
+ if (this.config.applicationName) {
512
+ config.application_name = this.config.applicationName;
513
+ }
514
+ if (this.config.statementTimeout) {
515
+ config.statement_timeout = this.config.statementTimeout;
516
+ }
517
+ if (this.config.queryTimeout) {
518
+ config.query_timeout = this.config.queryTimeout;
519
+ }
520
+ return config;
521
+ }
522
+ /**
523
+ * Begins a transaction with the specified options.
524
+ */
525
+ async beginTransaction(client, options) {
526
+ let beginSql = "BEGIN";
527
+ if (options?.isolationLevel) {
528
+ const isolationMap = {
529
+ "read uncommitted": "READ UNCOMMITTED",
530
+ "read committed": "READ COMMITTED",
531
+ "repeatable read": "REPEATABLE READ",
532
+ serializable: "SERIALIZABLE"
533
+ };
534
+ const level = isolationMap[options.isolationLevel];
535
+ if (level) {
536
+ beginSql += ` ISOLATION LEVEL ${level}`;
537
+ }
538
+ }
539
+ if (options?.readOnly) {
540
+ beginSql += " READ ONLY";
541
+ }
542
+ await client.query(beginSql);
543
+ if (options?.timeoutMs) {
544
+ await client.query(`SET LOCAL statement_timeout = ${options.timeoutMs}`);
545
+ }
546
+ }
547
+ /**
548
+ * Creates a TransactionContext for the given client.
549
+ */
550
+ createTransactionContext(client) {
551
+ return {
552
+ execute: async (sql, params = []) => {
553
+ const result = await client.query(sql, params);
554
+ return result.rows;
555
+ },
556
+ insert: async (table, data, options) => {
557
+ const columns = Object.keys(data);
558
+ const values = Object.values(data);
559
+ const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
560
+ let sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map((c) => this.escapeIdentifier(c)).join(", ")}) VALUES (${placeholders})`;
561
+ if (options?.returning) {
562
+ const returning = options.returning === "*" ? "*" : options.returning.map((col) => this.escapeIdentifier(col)).join(", ");
563
+ sql += ` RETURNING ${returning}`;
564
+ } else {
565
+ sql += " RETURNING *";
566
+ }
567
+ const result = await client.query(sql, values);
568
+ return result.rows[0];
569
+ },
570
+ insertMany: async (table, data, options) => {
571
+ if (data.length === 0) return [];
572
+ const columns = Object.keys(data[0]);
573
+ const params = [];
574
+ const valuesClauses = [];
575
+ for (const record of data) {
576
+ const placeholders = [];
577
+ for (const col of columns) {
578
+ params.push(record[col]);
579
+ placeholders.push(`$${params.length}`);
580
+ }
581
+ valuesClauses.push(`(${placeholders.join(", ")})`);
582
+ }
583
+ let sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map((c) => this.escapeIdentifier(c)).join(", ")}) VALUES ${valuesClauses.join(", ")}`;
584
+ if (options?.returning) {
585
+ const returning = options.returning === "*" ? "*" : options.returning.map((col) => this.escapeIdentifier(col)).join(", ");
586
+ sql += ` RETURNING ${returning}`;
587
+ } else {
588
+ sql += " RETURNING *";
589
+ }
590
+ const result = await client.query(sql, params);
591
+ return result.rows;
592
+ },
593
+ // TransactionContext CRUD methods delegate to the adapter's CRUD
594
+ // which uses Drizzle query API via the TableResolver.
595
+ // The Drizzle transaction is handled at a higher level.
596
+ select: async (table, options) => {
597
+ return this.select(table, options);
598
+ },
599
+ selectOne: async (table, options) => {
600
+ return this.selectOne(table, options);
601
+ },
602
+ update: async (table, data, where, options) => {
603
+ return this.update(table, data, where, options);
604
+ },
605
+ delete: async (table, where, _options) => {
606
+ return this.delete(table, where);
607
+ },
608
+ upsert: async (table, data, options) => {
609
+ return this.upsert(table, data, options);
610
+ },
611
+ savepoint: async (name) => {
612
+ await client.query(`SAVEPOINT ${this.escapeIdentifier(name)}`);
613
+ },
614
+ rollbackToSavepoint: async (name) => {
615
+ await client.query(
616
+ `ROLLBACK TO SAVEPOINT ${this.escapeIdentifier(name)}`
617
+ );
618
+ },
619
+ releaseSavepoint: async (name) => {
620
+ await client.query(`RELEASE SAVEPOINT ${this.escapeIdentifier(name)}`);
621
+ }
622
+ };
623
+ }
624
+ /**
625
+ * Classifies a PostgreSQL error into a DatabaseError.
626
+ *
627
+ * @param error - Original error from pg
628
+ * @param sql - SQL statement that caused the error (optional)
629
+ * @returns DatabaseError with proper classification
630
+ */
631
+ classifyError(error, sql) {
632
+ if (isDatabaseError(error)) return error;
633
+ const pgError = error;
634
+ const kind = pgError.code && PG_ERROR_CODES[pgError.code] || "unknown";
635
+ let message = pgError.message ?? String(error);
636
+ if (sql && kind === "query") {
637
+ message = `Query failed: ${message}`;
638
+ }
639
+ return createDatabaseError({
640
+ kind,
641
+ message,
642
+ code: pgError.code,
643
+ constraint: pgError.constraint,
644
+ table: pgError.table,
645
+ column: pgError.column,
646
+ detail: pgError.detail,
647
+ hint: pgError.hint,
648
+ cause: error instanceof Error ? error : void 0
649
+ });
650
+ }
651
+ /**
652
+ * Override handleQueryError to use PostgreSQL-specific classification.
653
+ */
654
+ handleQueryError(error, operation, table) {
655
+ const dbError = this.classifyError(error);
656
+ if (!dbError.message.includes(operation)) {
657
+ dbError.message = `${operation} operation failed on table '${table}': ${dbError.message}`;
658
+ }
659
+ if (!dbError.table) {
660
+ dbError.table = table;
661
+ }
662
+ return dbError;
663
+ }
664
+ };
665
+ function createPostgresAdapter(config) {
666
+ return new PostgresAdapter(config);
667
+ }
668
+ function isPostgresAdapter(value) {
669
+ return value instanceof PostgresAdapter;
670
+ }
671
+
672
+ export { PostgresAdapter, VERSION, createPostgresAdapter, isPostgresAdapter };
673
+ //# sourceMappingURL=index.mjs.map
674
+ //# sourceMappingURL=index.mjs.map