alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
185 lines (158 loc) • 5.06 kB
text/typescript
import type { Database } from "bun:sqlite";
import { mkdir } from "node:fs/promises";
import { dirname } from "node:path";
import {
$atom,
$env,
$hook,
$inject,
$state,
AlephaError,
type Static,
z,
} from "alepha";
import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
import type { PgDatabase } from "drizzle-orm/pg-core";
import { databaseEnvSchema } from "../../schemas/databaseEnvSchema.ts";
import { SqliteModelBuilder } from "../../services/SqliteModelBuilder.ts";
import { DatabaseProvider, type SQLLike } from "./DatabaseProvider.ts";
// ---------------------------------------------------------------------------------------------------------------------
const envSchema = databaseEnvSchema;
/**
* Configuration options for the Bun SQLite database provider.
*/
export const bunSqliteOptions = $atom({
name: "alepha.postgres.bun-sqlite.options",
schema: z.object({
path: z
.string()
.describe(
"Filepath or :memory:. If empty, provider will use DATABASE_URL from env.",
)
.optional(),
}),
default: {},
});
export type BunSqliteProviderOptions = Static<typeof bunSqliteOptions.schema>;
declare module "alepha" {
interface State {
[bunSqliteOptions.key]: BunSqliteProviderOptions;
}
}
// ---------------------------------------------------------------------------------------------------------------------
/**
* Bun SQLite provider using Drizzle ORM with Bun's native SQLite client.
*
* This provider uses Bun's built-in `bun:sqlite` for SQLite connections,
* which provides excellent performance on the Bun runtime.
*
* @example
* ```ts
* // Set DATABASE_URL environment variable
* // DATABASE_URL=sqlite://./my-database.db
*
* // Or configure programmatically
* alepha.with({
* provide: DatabaseProvider,
* use: BunSqliteProvider,
* });
*
* // Or use options atom
* alepha.store.mut(bunSqliteOptions, (old) => ({
* ...old,
* path: ":memory:",
* }));
* ```
*/
export class BunSqliteProvider extends DatabaseProvider {
protected readonly env = $env(envSchema);
protected readonly builder = $inject(SqliteModelBuilder);
protected readonly options = $state(bunSqliteOptions);
protected sqlite?: Database;
protected bunDb?: BunSQLiteDatabase;
public get name() {
return "sqlite";
}
public override readonly dialect = "sqlite";
public override get url(): string {
const path = this.options.path ?? this.env.DATABASE_URL;
if (path) {
if (path.startsWith("postgres://")) {
throw new AlephaError(
"Postgres URL is not supported for SQLite provider.",
);
}
return path;
}
if (this.alepha.isTest() || this.alepha.isServerless()) {
return ":memory:";
} else {
return "node_modules/.alepha/bun-sqlite.db";
}
}
public override get db(): PgDatabase<any> {
if (!this.bunDb) {
throw new AlephaError("Database not initialized");
}
return this.bunDb as unknown as PgDatabase<any>;
}
public override get nativeConnection(): unknown {
return this.sqlite;
}
public override async execute(
query: SQLLike,
): Promise<Array<Record<string, unknown>>> {
return (this.bunDb as BunSQLiteDatabase).all(query);
}
protected readonly onStart = $hook({
on: "start",
handler: async () => {
// Check if we're running in Bun
if (typeof Bun === "undefined") {
throw new AlephaError(
"BunSqliteProvider requires the Bun runtime. Use NodeSqliteProvider for Node.js.",
);
}
const { Database } = await import("bun:sqlite");
const { drizzle } = await import("drizzle-orm/bun-sqlite");
const filepath = this.url.replace("sqlite://", "").replace("sqlite:", "");
if (filepath !== ":memory:" && filepath !== "") {
const dir = dirname(filepath);
if (dir) {
await mkdir(dir, { recursive: true }).catch(() => null);
}
}
this.sqlite = new Database(filepath);
this.bunDb = drizzle({
client: this.sqlite,
logger: {
logQuery: (query: string, params: unknown[]) => {
this.log.trace(query, { params });
},
},
});
// Never migrate in serverless mode - migrations should be applied during deployment
if (!this.alepha.isServerless()) {
await this.migrate();
}
this.log.info(`Using Bun SQLite database at ${filepath}`);
},
});
protected readonly onStop = $hook({
on: "stop",
priority: "last",
handler: async () => {
if (this.sqlite) {
this.log.debug("Closing Bun SQLite connection...");
this.sqlite.close();
this.sqlite = undefined;
this.bunDb = undefined;
this.log.info("Bun SQLite connection closed");
}
},
});
protected async executeMigrations(migrationsFolder: string): Promise<void> {
const { migrate } = await import("drizzle-orm/bun-sqlite/migrator");
await migrate(this.bunDb!, { migrationsFolder });
}
}