Files
WRNexusJS/packages/db/src/adapters/bunsql.ts
T
2026-07-30 13:36:29 +05:30

77 lines
2.5 KiB
TypeScript

/**
* Postgres + MySQL adapters built on Bun's native SQL client (`Bun.SQL`) — no
* external driver dependency. Both speak the same `Driver` interface; only the
* dialect (and thus the DDL types + placeholder style) differ.
*
* `Bun.SQL` pools connections, so transactions use its managed `begin(fn)` to
* keep BEGIN/…/COMMIT on one reserved connection.
*/
import type { Dialect } from "../sql.ts";
import type { Driver, Row, TxHandle } from "../driver.ts";
interface BunSqlClient {
unsafe(query: string, params?: unknown[]): Promise<unknown>;
begin<T>(fn: (tx: BunSqlClient) => Promise<T>): Promise<T>;
close(): Promise<void>;
}
interface ExecMeta {
count?: number;
affectedRows?: number;
lastInsertRowid?: number | bigint;
insertId?: number | bigint;
}
/** Convert the framework's portable positional placeholders for PostgreSQL. */
export function sqlForDialect(sql: string, dialect: Dialect): string {
if (dialect !== "postgres" || !sql.includes("?")) return sql;
let index = 0;
return sql.replace(/\?/g, () => `$${++index}`);
}
function runnerFor(client: BunSqlClient, dialect: Dialect): TxHandle {
return {
async query(sql, params = []): Promise<Row[]> {
const rows = (await client.unsafe(sqlForDialect(sql, dialect), params)) as Iterable<Row>;
return Array.from(rows);
},
async exec(sql, params = []) {
const meta = (await client.unsafe(sqlForDialect(sql, dialect), params)) as ExecMeta;
const id = meta.lastInsertRowid ?? meta.insertId;
return {
changes: Number(meta.affectedRows ?? meta.count ?? 0),
lastInsertId: id != null ? Number(id) : undefined,
};
},
};
}
/** Create a Bun.sql-backed driver for the given connection URL + dialect. */
export function bunSql(url: string, dialect: Dialect): Driver {
const Ctor = (Bun as unknown as { SQL: new (u: string) => BunSqlClient }).SQL;
const client = new Ctor(url);
const runner = runnerFor(client, dialect);
return {
dialect,
query: runner.query,
exec: runner.exec,
transaction(fn) {
return client.begin((tx) => fn(runnerFor(tx, dialect)));
},
close() {
return client.close();
},
};
}
/** PostgreSQL adapter (`postgres://user:pass@host:5432/db`). Placeholders: `$N`. */
export function postgres(url: string): Driver {
return bunSql(url, "postgres");
}
/** MySQL adapter (`mysql://user:pass@host:3306/db`). Placeholders: `?`. */
export function mysql(url: string): Driver {
return bunSql(url, "mysql");
}