refactor(db): migrate to libsql

This commit is contained in:
Meier Lukas committed 2026-01-04 19:30:37 +01:00
1 parent 96e2f4fcca
commit 3fbc5013c3
52 files changed
+523 -1450

No files matched your search

+4 -54
View File
@@ -1,26 +1,15 @@
import type { InferInsertModel } from "drizzle-orm";
import { objectEntries } from "@homarr/common";
import { dbEnv } from "@homarr/core/infrastructure/db/env";
import type { HomarrDatabase, HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver";
import type { HomarrDatabase, HomarrTransaction } from "./driver";
import * as schema from "./schema";
type TableKey = {
[K in keyof typeof schema]: (typeof schema)[K] extends { _: { brand: "Table" } } ? K : never;
}[keyof typeof schema];
export function isMysql(): boolean {
return dbEnv.DRIVER === "mysql2";
}
export function isPostgresql(): boolean {
return dbEnv.DRIVER === "node-postgres";
}
export const createDbInsertCollectionForTransaction = <TTableKey extends TableKey>(
tablesInInsertOrder: TTableKey[],
) => {
export const createDbInsertCollection = <TTableKey extends TableKey>(tablesInInsertOrder: TTableKey[]) => {
const context = tablesInInsertOrder.reduce(
(acc, key) => {
acc[key] = [];
@@ -31,25 +20,10 @@ export const createDbInsertCollectionForTransaction = <TTableKey extends TableKe
return {
...context,
insertAll: (db: HomarrDatabase) => {
db.transaction((transaction) => {
insertAllAsync: async (db: HomarrDatabase | HomarrTransaction) => {
await db.transaction(async (transaction) => {
for (const [key, values] of objectEntries(context)) {
if (values.length >= 1) {
transaction
.insert(schema[key])
.values(values as never)
.run();
}
}
});
},
// We allow any database that supports async passed here but then fallback to mysql to prevent typescript errors
insertAllAsync: async (db: HomarrDatabaseMysql | HomarrDatabasePostgresql) => {
const innerDb = db as HomarrDatabaseMysql;
await innerDb.transaction(async (transaction) => {
for (const [key, values] of objectEntries(context)) {
if (values.length >= 1) {
// Below is actually the mysqlSchema when the driver is mysql
await transaction.insert(schema[key] as never).values(values as never);
}
}
@@ -57,27 +31,3 @@ export const createDbInsertCollectionForTransaction = <TTableKey extends TableKe
},
};
};
export const createDbInsertCollectionWithoutTransaction = <TTableKey extends TableKey>(
tablesInInsertOrder: TTableKey[],
) => {
const { insertAll, insertAllAsync, ...collection } = createDbInsertCollectionForTransaction(tablesInInsertOrder);
return {
...collection,
insertAllAsync: async (db: HomarrDatabase) => {
switch (dbEnv.DRIVER) {
case "mysql2":
case "node-postgres":
// For mysql2 and node-postgres, we can use the async insertAllAsync method
await insertAllAsync(db as unknown as HomarrDatabaseMysql | HomarrDatabasePostgresql);
return;
default:
// For better-sqlite3, we need to use the synchronous insertAll method
// default assumes better-sqlite3. It's original implementation.
insertAll(db);
break;
}
},
};
};
+4 -2
View File
@@ -1,4 +1,5 @@
import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
import type { ExtractTablesWithRelations } from "drizzle-orm";
import type { LibSQLDatabase, LibSQLTransaction } from "drizzle-orm/libsql";
import type { MySql2Database } from "drizzle-orm/mysql2";
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
@@ -6,6 +7,7 @@ import type * as mysqlSchema from "./schema/mysql";
import type * as pgSchema from "./schema/postgresql";
import type * as sqliteSchema from "./schema/sqlite";
export type HomarrDatabase = BetterSQLite3Database<typeof sqliteSchema>;
export type HomarrDatabase = LibSQLDatabase<typeof sqliteSchema>;
export type HomarrTransaction = LibSQLTransaction<typeof sqliteSchema, ExtractTablesWithRelations<typeof sqliteSchema>>;
export type HomarrDatabaseMysql = MySql2Database<typeof mysqlSchema>;
export type HomarrDatabasePostgresql = NodePgDatabase<typeof pgSchema>;
-2
View File
@@ -8,5 +8,3 @@ export type { HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver";
export const db = createDb(schema);
export type Database = typeof db;
export { handleDiffrentDbDriverOperationsAsync as handleTransactionsAsync } from "./transactions";
+2 -2
View File
@@ -1,4 +1,4 @@
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { migrate } from "drizzle-orm/libsql/migrator";
import { createSharedDbConfig, createSqliteDb } from "@homarr/core/infrastructure/db";
@@ -12,7 +12,7 @@ const migrateAsync = async () => {
const config = createSharedDbConfig(sqliteSchema);
const db = createSqliteDb(config);
migrate(db, { migrationsFolder });
await migrate(db, { migrationsFolder });
await seedDataAsync(db);
await applyCustomMigrationsAsync(db);
+1 -2
View File
@@ -47,11 +47,11 @@
"@homarr/core": "workspace:^0.1.0",
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@libsql/client": "0.15.15",
"@mantine/core": "^8.3.10",
"@paralleldrive/cuid2": "^3.1.0",
"@testcontainers/mysql": "^11.11.0",
"@testcontainers/postgresql": "^11.11.0",
"better-sqlite3": "^12.5.0",
"dotenv": "^17.2.3",
"drizzle-kit": "^0.31.8",
"drizzle-orm": "^0.45.1",
@@ -64,7 +64,6 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.16.0",
"dotenv-cli": "^11.0.0",
"esbuild": "^0.27.2",
+8 -6
View File
@@ -1,15 +1,17 @@
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { createClient } from "@libsql/client/node";
import { drizzle } from "drizzle-orm/libsql";
import { migrate } from "drizzle-orm/libsql/migrator";
import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
import * as sqliteSchema from "../schema/sqlite";
export const createDb = (debug?: boolean) => {
const sqlite = new Database(":memory:");
export const createDbAsync = async (debug?: boolean) => {
const sqlite = createClient({
url: ":memory:",
});
const db = drizzle(sqlite, { schema: sqliteSchema, logger: debug, casing: DB_CASING });
migrate(db, {
await migrate(db, {
migrationsFolder: "./packages/db/migrations/sqlite",
});
-23
View File
@@ -1,23 +0,0 @@
import { isMysql, isPostgresql } from "./collection";
import type { HomarrDatabase, HomarrDatabaseMysql } from "./driver";
import type { MySqlSchema } from "./schema";
import * as schema from "./schema";
interface HandleTransactionInput {
handleAsync: (db: HomarrDatabaseMysql, schema: MySqlSchema) => Promise<void>;
handleSync: (db: HomarrDatabase) => void;
}
/**
* The below method is mostly used to handle transactions in different database drivers.
* As better-sqlite3 transactions have to be synchronous, we have to implement them in a different way.
* But it can also generally be used when dealing with different database drivers.
*/
export const handleDiffrentDbDriverOperationsAsync = async (db: HomarrDatabase, input: HandleTransactionInput) => {
if (isMysql() || isPostgresql()) {
// Schema type is always the correct one based on env variables
await input.handleAsync(db as unknown as HomarrDatabaseMysql, schema as unknown as MySqlSchema);
} else {
input.handleSync(db);
}
};