@vibeorm/adapter-pg 1.0.2 → 1.1.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 (2) hide show
  1. package/package.json +2 -2
  2. package/src/index.ts +60 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeorm/adapter-pg",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "node-postgres adapter for VibeORM — use VibeORM with pg on Bun or Node.js",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -31,7 +31,7 @@
31
31
  "url": "https://github.com/vibeorm/vibeorm/issues"
32
32
  },
33
33
  "dependencies": {
34
- "@vibeorm/runtime": "1.0.2"
34
+ "@vibeorm/runtime": "1.1.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "pg": ">=8"
package/src/index.ts CHANGED
@@ -5,7 +5,8 @@
5
5
  * named prepared statement caching for PostgreSQL plan reuse.
6
6
  */
7
7
 
8
- import type { DatabaseAdapter, QueryResult } from "@vibeorm/runtime";
8
+ import type { DatabaseAdapter, QueryResult, TransactionOptions } from "@vibeorm/runtime";
9
+ import { normalizeError } from "@vibeorm/runtime";
9
10
 
10
11
  /**
11
12
  * Options for creating a pg adapter.
@@ -226,30 +227,41 @@ export function pgAdapter(options?: PgAdapterOptions): DatabaseAdapter {
226
227
  }
227
228
 
228
229
  function createClientAdapter(client: PgPoolClient): DatabaseAdapter {
230
+ let savepointCounter = 0;
231
+
229
232
  return {
230
233
  async execute(params) {
231
- const config = buildQueryConfig(params);
232
- const result = await client.query(config);
233
- return result.rows;
234
+ try {
235
+ const config = buildQueryConfig(params);
236
+ const result = await client.query(config);
237
+ return result.rows;
238
+ } catch (err) {
239
+ throw normalizeError({ error: err });
240
+ }
234
241
  },
235
242
 
236
243
  async executeUnsafe(params) {
237
- const values = params.values ? serializeParams({ values: params.values }) : undefined;
238
- const result = await client.query(params.text, values);
239
- const affectedRows = result.rowCount ?? 0;
240
- return { rows: result.rows, affectedRows };
244
+ try {
245
+ const values = params.values ? serializeParams({ values: params.values }) : undefined;
246
+ const result = await client.query(params.text, values);
247
+ const affectedRows = result.rowCount ?? 0;
248
+ return { rows: result.rows, affectedRows };
249
+ } catch (err) {
250
+ throw normalizeError({ error: err });
251
+ }
241
252
  },
242
253
 
243
254
  async transaction<T>(fn: (txAdapter: DatabaseAdapter) => Promise<T>): Promise<T> {
244
- // Nested transactions use SAVEPOINTs
245
- await client.query("SAVEPOINT vibeorm_nested");
255
+ // Nested transactions use unique SAVEPOINTs
256
+ const spName = `vibeorm_sp_${savepointCounter++}`;
257
+ await client.query(`SAVEPOINT ${spName}`);
246
258
  try {
247
259
  const nestedAdapter = createClientAdapter(client);
248
260
  const result = await fn(nestedAdapter);
249
- await client.query("RELEASE SAVEPOINT vibeorm_nested");
261
+ await client.query(`RELEASE SAVEPOINT ${spName}`);
250
262
  return result;
251
263
  } catch (err) {
252
- await client.query("ROLLBACK TO SAVEPOINT vibeorm_nested");
264
+ await client.query(`ROLLBACK TO SAVEPOINT ${spName}`);
253
265
  throw err;
254
266
  }
255
267
  },
@@ -269,34 +281,58 @@ export function pgAdapter(options?: PgAdapterOptions): DatabaseAdapter {
269
281
  };
270
282
  }
271
283
 
284
+ function isolationLevelToSql(params: { level: NonNullable<TransactionOptions["isolationLevel"]> }): string {
285
+ switch (params.level) {
286
+ case "ReadCommitted": return "READ COMMITTED";
287
+ case "RepeatableRead": return "REPEATABLE READ";
288
+ case "Serializable": return "SERIALIZABLE";
289
+ }
290
+ }
291
+
272
292
  const adapter: DatabaseAdapter = {
273
293
  async execute(params) {
274
- const p = getPool();
275
- const config = buildQueryConfig(params);
276
- const result = await p.query(config);
277
- return result.rows;
294
+ try {
295
+ const p = getPool();
296
+ const config = buildQueryConfig(params);
297
+ const result = await p.query(config);
298
+ return result.rows;
299
+ } catch (err) {
300
+ throw normalizeError({ error: err });
301
+ }
278
302
  },
279
303
 
280
304
  async executeUnsafe(params) {
281
- const p = getPool();
282
- const values = params.values ? serializeParams({ values: params.values }) : undefined;
283
- const result = await p.query(params.text, values);
284
- const affectedRows = result.rowCount ?? 0;
285
- return { rows: result.rows, affectedRows };
305
+ try {
306
+ const p = getPool();
307
+ const values = params.values ? serializeParams({ values: params.values }) : undefined;
308
+ const result = await p.query(params.text, values);
309
+ const affectedRows = result.rowCount ?? 0;
310
+ return { rows: result.rows, affectedRows };
311
+ } catch (err) {
312
+ throw normalizeError({ error: err });
313
+ }
286
314
  },
287
315
 
288
- async transaction<T>(fn: (txAdapter: DatabaseAdapter) => Promise<T>): Promise<T> {
316
+ async transaction<T>(fn: (txAdapter: DatabaseAdapter) => Promise<T>, options?: TransactionOptions): Promise<T> {
289
317
  const p = getPool();
290
318
  const client = await p.connect();
291
319
  try {
292
- await client.query("BEGIN");
320
+ const isolation = options?.isolationLevel
321
+ ? ` ISOLATION LEVEL ${isolationLevelToSql({ level: options.isolationLevel })}`
322
+ : "";
323
+ await client.query(`BEGIN${isolation}`);
324
+
325
+ if (options?.timeout) {
326
+ await client.query(`SET LOCAL statement_timeout = ${Number(options.timeout)}`);
327
+ }
328
+
293
329
  const txAdapter = createClientAdapter(client);
294
330
  const result = await fn(txAdapter);
295
331
  await client.query("COMMIT");
296
332
  return result;
297
333
  } catch (err) {
298
334
  await client.query("ROLLBACK");
299
- throw err;
335
+ throw normalizeError({ error: err });
300
336
  } finally {
301
337
  client.release();
302
338
  }