first commit

This commit is contained in:
2026-07-12 15:55:18 +05:30
commit ee98026cc5
404 changed files with 44522 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
/**
* 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;
}
function runnerFor(client: BunSqlClient): TxHandle {
return {
async query(sql, params = []): Promise<Row[]> {
const rows = (await client.unsafe(sql, params)) as Iterable<Row>;
return Array.from(rows);
},
async exec(sql, params = []) {
const meta = (await client.unsafe(sql, 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);
return {
dialect,
query: runner.query,
exec: runner.exec,
transaction(fn) {
return client.begin((tx) => fn(runnerFor(tx)));
},
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");
}
+120
View File
@@ -0,0 +1,120 @@
/**
* MongoDB adapter. Mongo is a document store, not SQL, so it does NOT use the
* SQL `Driver`/migrations/query-generator. Instead it exposes a small, typed
* collection API keyed by your models — reads are still coerced through
* `model.parse`, so results match your schema.
*
* The `mongodb` driver is imported lazily (install it to use Mongo); the core
* `@wrnexus/db` stays dependency-free.
*
* const db = await mongo(process.env.MONGO_URL!, "app");
* const repo = db.collection(users);
* await repo.insert({ email, name });
* const active = await repo.find({ active: true });
*/
import type { Model } from "../schema.ts";
// Minimal shape of the `mongodb` driver we rely on (typed locally so this file
// compiles without `mongodb` installed).
interface MongoCollection {
find(filter: object, options?: object): { toArray(): Promise<Record<string, unknown>[]> };
findOne(filter: object): Promise<Record<string, unknown> | null>;
insertOne(doc: object): Promise<{ insertedId: unknown }>;
insertMany(docs: object[]): Promise<{ insertedCount: number }>;
updateMany(
filter: object,
update: object,
): Promise<{ matchedCount: number; modifiedCount: number }>;
deleteMany(filter: object): Promise<{ deletedCount: number }>;
countDocuments(filter: object): Promise<number>;
}
interface MongoDatabase {
collection(name: string): MongoCollection;
}
interface MongoClientLike {
connect(): Promise<unknown>;
db(name?: string): MongoDatabase;
close(): Promise<void>;
}
export interface FindOptions {
sort?: Record<string, 1 | -1>;
limit?: number;
skip?: number;
}
export interface MongoRepo<T> {
find(filter?: Record<string, unknown>, options?: FindOptions): Promise<T[]>;
findOne(filter: Record<string, unknown>): Promise<T | null>;
insert(doc: Partial<T>): Promise<{ id: unknown }>;
insertMany(docs: Partial<T>[]): Promise<{ count: number }>;
update(
filter: Record<string, unknown>,
patch: Partial<T>,
): Promise<{ matched: number; modified: number }>;
delete(filter: Record<string, unknown>): Promise<{ deleted: number }>;
count(filter?: Record<string, unknown>): Promise<number>;
}
export interface MongoDb {
collection<T>(model: Model<T>): MongoRepo<T>;
close(): Promise<void>;
}
/** Map Mongo's `_id` to `id` so documents line up with model columns. */
function normalize(doc: Record<string, unknown>): Record<string, unknown> {
if (doc && doc._id != null && doc.id == null) {
const { _id, ...rest } = doc;
return { id: String(_id), ...rest };
}
return doc;
}
/** Connect to MongoDB and return a model-aware collection API. */
export async function mongo(url: string, dbName?: string): Promise<MongoDb> {
// Non-literal specifier so this compiles without `mongodb` installed.
const specifier: string = "mongodb";
const mod = (await import(specifier)) as { MongoClient: new (u: string) => MongoClientLike };
const client = new mod.MongoClient(url);
await client.connect();
const database = client.db(dbName);
return {
collection<T>(model: Model<T>): MongoRepo<T> {
const col = database.collection(model.name);
return {
async find(filter = {}, options = {}) {
const docs = await col.find(filter, options).toArray();
return docs.map((d) => model.parse(normalize(d)));
},
async findOne(filter) {
const doc = await col.findOne(filter);
return doc ? model.parse(normalize(doc)) : null;
},
async insert(doc) {
const r = await col.insertOne(doc as object);
return { id: r.insertedId };
},
async insertMany(docs) {
const r = await col.insertMany(docs as object[]);
return { count: r.insertedCount };
},
async update(filter, patch) {
const r = await col.updateMany(filter, { $set: patch });
return { matched: r.matchedCount, modified: r.modifiedCount };
},
async delete(filter) {
const r = await col.deleteMany(filter);
return { deleted: r.deletedCount };
},
count(filter = {}) {
return col.countDocuments(filter);
},
};
},
close() {
return client.close();
},
};
}
+1
View File
@@ -0,0 +1 @@
export { mysql } from "./bunsql.ts";
+1
View File
@@ -0,0 +1 @@
export { postgres } from "./bunsql.ts";
+60
View File
@@ -0,0 +1,60 @@
/**
* SQLite adapter, built on Bun's zero-dependency `bun:sqlite`. Use a file URL
* (`file:./dev.db`) or the default in-memory database (great for tests). This is
* the reference adapter — it needs no external service to run.
*/
import { Database } from "bun:sqlite";
import type { Driver, ExecResult, Row, TxHandle } from "../driver.ts";
/** SQLite can only bind numbers/strings/bigints/null/blobs — coerce JS values. */
function bind(params: unknown[]): unknown[] {
return params.map((p) => {
if (p === true) return 1;
if (p === false) return 0;
if (p === undefined) return null;
if (p instanceof Date) return p.toISOString();
return p;
});
}
/** Create a SQLite driver. `url` may be `file:./x.db`, a path, or `:memory:`. */
export function sqlite(url = ":memory:"): Driver {
const path = url.replace(/^(file:|sqlite:)/, "") || ":memory:";
const database = new Database(path);
database.exec("PRAGMA foreign_keys = ON;");
const runner: TxHandle = {
async query(sql, params = []): Promise<Row[]> {
return database.query(sql).all(...(bind(params) as never[])) as Row[];
},
async exec(sql, params = []): Promise<ExecResult> {
if (params.length === 0) {
database.exec(sql); // DDL / multi-statement
return { changes: 0 };
}
const result = database.query(sql).run(...(bind(params) as never[]));
return { changes: result.changes, lastInsertId: Number(result.lastInsertRowid) };
},
};
return {
dialect: "sqlite",
query: runner.query,
exec: runner.exec,
async transaction(fn) {
database.exec("BEGIN");
try {
const result = await fn(runner);
database.exec("COMMIT");
return result;
} catch (err) {
database.exec("ROLLBACK");
throw err;
}
},
close() {
database.close();
},
};
}