@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.
- package/package.json +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
|
|
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
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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(
|
|
261
|
+
await client.query(`RELEASE SAVEPOINT ${spName}`);
|
|
250
262
|
return result;
|
|
251
263
|
} catch (err) {
|
|
252
|
-
await client.query(
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}
|