@vibeorm/adapter-bun 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 +70 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeorm/adapter-bun",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Bun-native database adapter for VibeORM using bun:sql",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -33,7 +33,7 @@
33
33
  "bun": ">=1.1.0"
34
34
  },
35
35
  "dependencies": {
36
- "@vibeorm/runtime": "1.0.2"
36
+ "@vibeorm/runtime": "1.1.0"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
package/src/index.ts CHANGED
@@ -6,7 +6,8 @@
6
6
  * tagged templates.
7
7
  */
8
8
 
9
- import type { DatabaseAdapter, QueryResult } from "@vibeorm/runtime";
9
+ import type { DatabaseAdapter, QueryResult, TransactionOptions } from "@vibeorm/runtime";
10
+ import { normalizeError } from "@vibeorm/runtime";
10
11
 
11
12
  export type BunAdapterOptions = {
12
13
  /** PostgreSQL connection URL. Falls back to DATABASE_URL env var if not provided. */
@@ -73,6 +74,7 @@ export type BunAdapterOptions = {
73
74
 
74
75
  type SqlReserved = {
75
76
  (strings: TemplateStringsArray, ...values: unknown[]): Promise<unknown[]>;
77
+ unsafe(query: string, values?: unknown[]): Promise<unknown[]>;
76
78
  release(): void;
77
79
  };
78
80
 
@@ -228,34 +230,80 @@ export function bunAdapter(options?: BunAdapterOptions): DatabaseAdapter {
228
230
  return result as Record<string, unknown>[];
229
231
  }
230
232
 
233
+ function isolationLevelToSql(params: { level: NonNullable<TransactionOptions["isolationLevel"]> }): string {
234
+ switch (params.level) {
235
+ case "ReadCommitted": return "READ COMMITTED";
236
+ case "RepeatableRead": return "REPEATABLE READ";
237
+ case "Serializable": return "SERIALIZABLE";
238
+ }
239
+ }
240
+
231
241
  function createAdapter(sql: SqlInstance): DatabaseAdapter {
232
242
  return {
233
243
  async execute(params) {
234
- return executeQuery(sql, params);
244
+ try {
245
+ return await executeQuery(sql, params);
246
+ } catch (err) {
247
+ throw normalizeError({ error: err });
248
+ }
235
249
  },
236
250
 
237
251
  async executeUnsafe(params) {
238
- const result = await sql.unsafe(params.text, params.values);
239
- const resultAny = result as unknown as Record<string, unknown>;
240
- let affectedRows: number;
241
- if (typeof resultAny.count === "number") {
242
- affectedRows = resultAny.count;
243
- } else if (typeof resultAny.affectedRows === "number") {
244
- affectedRows = resultAny.affectedRows;
245
- } else {
246
- affectedRows = (result as unknown[]).length;
252
+ try {
253
+ const result = await sql.unsafe(params.text, params.values);
254
+ const resultAny = result as unknown as Record<string, unknown>;
255
+ let affectedRows: number;
256
+ if (typeof resultAny.count === "number") {
257
+ affectedRows = resultAny.count;
258
+ } else if (typeof resultAny.affectedRows === "number") {
259
+ affectedRows = resultAny.affectedRows;
260
+ } else {
261
+ affectedRows = (result as unknown[]).length;
262
+ }
263
+ return {
264
+ rows: result as Record<string, unknown>[],
265
+ affectedRows,
266
+ };
267
+ } catch (err) {
268
+ throw normalizeError({ error: err });
247
269
  }
248
- return {
249
- rows: result as Record<string, unknown>[],
250
- affectedRows,
251
- };
252
270
  },
253
271
 
254
- async transaction<T>(fn: (txAdapter: DatabaseAdapter) => Promise<T>): Promise<T> {
255
- return sql.begin(async (txSql: SqlInstance) => {
256
- const txAdapter = createAdapter(txSql);
257
- return fn(txAdapter);
258
- });
272
+ async transaction<T>(fn: (txAdapter: DatabaseAdapter) => Promise<T>, options?: TransactionOptions): Promise<T> {
273
+ // Fast path: no custom options — use native sql.begin() for best performance
274
+ if (!options?.isolationLevel && !options?.timeout) {
275
+ try {
276
+ return await sql.begin(async (txSql: SqlInstance) => {
277
+ const txAdapter = createAdapter(txSql);
278
+ return fn(txAdapter);
279
+ });
280
+ } catch (err) {
281
+ throw normalizeError({ error: err });
282
+ }
283
+ }
284
+
285
+ // Manual path: reserve a connection for custom BEGIN options
286
+ const reserved = await sql.reserve();
287
+ try {
288
+ const isolation = options?.isolationLevel
289
+ ? ` ISOLATION LEVEL ${isolationLevelToSql({ level: options.isolationLevel })}`
290
+ : "";
291
+ await reserved.unsafe(`BEGIN${isolation}`);
292
+
293
+ if (options?.timeout) {
294
+ await reserved.unsafe(`SET LOCAL statement_timeout = ${Number(options.timeout)}`);
295
+ }
296
+
297
+ const txAdapter = createAdapter(reserved as unknown as SqlInstance);
298
+ const result = await fn(txAdapter);
299
+ await reserved.unsafe("COMMIT");
300
+ return result;
301
+ } catch (err) {
302
+ try { await reserved.unsafe("ROLLBACK"); } catch { /* rollback best-effort */ }
303
+ throw normalizeError({ error: err });
304
+ } finally {
305
+ reserved.release();
306
+ }
259
307
  },
260
308
 
261
309
  async connect() {
@@ -284,8 +332,8 @@ export function bunAdapter(options?: BunAdapterOptions): DatabaseAdapter {
284
332
  return createAdapter(getSql()).executeUnsafe(params);
285
333
  },
286
334
 
287
- async transaction<T>(fn: (txAdapter: DatabaseAdapter) => Promise<T>): Promise<T> {
288
- return createAdapter(getSql()).transaction(fn);
335
+ async transaction<T>(fn: (txAdapter: DatabaseAdapter) => Promise<T>, options?: TransactionOptions): Promise<T> {
336
+ return createAdapter(getSql()).transaction(fn, options);
289
337
  },
290
338
 
291
339
  async connect() {