refactor(db): move to core package (#4589)

This commit is contained in:
Meier Lukas
2025-12-17 08:59:52 +01:00
committed by GitHub
parent e954ea861c
commit 298a96054e
30 changed files with 258 additions and 217 deletions

View File

@@ -1,6 +1,6 @@
// Importing env files here to validate on build // Importing env files here to validate on build
import "@homarr/auth/env"; import "@homarr/auth/env";
import "@homarr/db/env"; import "@homarr/core/infrastructure/db/env";
import "@homarr/common/env"; import "@homarr/common/env";
import "@homarr/core/infrastructure/logs/env"; import "@homarr/core/infrastructure/logs/env";
import "@homarr/docker/env"; import "@homarr/docker/env";

View File

@@ -5,6 +5,7 @@ import Database from "better-sqlite3";
import { BetterSQLite3Database, drizzle } from "drizzle-orm/better-sqlite3"; import { BetterSQLite3Database, drizzle } from "drizzle-orm/better-sqlite3";
import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { DB_CASING } from "../../packages/core/src/infrastructure/db/constants";
import * as sqliteSchema from "../../packages/db/schema/sqlite"; import * as sqliteSchema from "../../packages/db/schema/sqlite";
export const createSqliteDbFileAsync = async () => { export const createSqliteDbFileAsync = async () => {
@@ -16,7 +17,7 @@ export const createSqliteDbFileAsync = async () => {
const connection = new Database(localDbUrl); const connection = new Database(localDbUrl);
const db = drizzle(connection, { const db = drizzle(connection, {
schema: sqliteSchema, schema: sqliteSchema,
casing: "snake_case", casing: DB_CASING,
}); });
await migrate(db, { await migrate(db, {

View File

@@ -10,7 +10,10 @@
"./infrastructure/logs": "./src/infrastructure/logs/index.ts", "./infrastructure/logs": "./src/infrastructure/logs/index.ts",
"./infrastructure/logs/constants": "./src/infrastructure/logs/constants.ts", "./infrastructure/logs/constants": "./src/infrastructure/logs/constants.ts",
"./infrastructure/logs/env": "./src/infrastructure/logs/env.ts", "./infrastructure/logs/env": "./src/infrastructure/logs/env.ts",
"./infrastructure/logs/error": "./src/infrastructure/logs/error.ts" "./infrastructure/logs/error": "./src/infrastructure/logs/error.ts",
"./infrastructure/db": "./src/infrastructure/db/index.ts",
"./infrastructure/db/env": "./src/infrastructure/db/env.ts",
"./infrastructure/db/constants": "./src/infrastructure/db/constants.ts"
}, },
"typesVersions": { "typesVersions": {
"*": { "*": {
@@ -28,7 +31,11 @@
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@t3-oss/env-nextjs": "^0.13.8", "@t3-oss/env-nextjs": "^0.13.8",
"better-sqlite3": "^12.5.0",
"drizzle-orm": "^0.45.1",
"ioredis": "5.8.2", "ioredis": "5.8.2",
"mysql2": "3.15.3",
"pg": "^8.16.3",
"superjson": "2.2.6", "superjson": "2.2.6",
"winston": "3.19.0", "winston": "3.19.0",
"zod": "^4.1.13" "zod": "^4.1.13"
@@ -37,6 +44,8 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.16.0",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }

View File

@@ -0,0 +1,3 @@
import type { Casing } from "drizzle-orm";
export const DB_CASING: Casing = "snake_case";

View File

@@ -0,0 +1,27 @@
import { DB_CASING } from "../constants";
import { createDbMapping } from "../mapping";
import { createMysqlDb } from "./mysql";
import { createPostgresDb } from "./postgresql";
import type { SharedDrizzleConfig } from "./shared";
import { WinstonDrizzleLogger } from "./shared";
import { createSqliteDb } from "./sqlite";
export type Database<TSchema extends Record<string, unknown>> = ReturnType<typeof createSqliteDb<TSchema>>;
export const createSharedConfig = <TSchema extends Record<string, unknown>>(
schema: TSchema,
): SharedDrizzleConfig<TSchema> => ({
logger: new WinstonDrizzleLogger(),
casing: DB_CASING,
schema,
});
export const createDb = <TSchema extends Record<string, unknown>>(schema: TSchema) => {
const config = createSharedConfig(schema);
return createDbMapping({
mysql2: () => createMysqlDb(config),
"node-postgres": () => createPostgresDb(config),
"better-sqlite3": () => createSqliteDb(config),
});
};

View File

@@ -0,0 +1,33 @@
import { drizzle } from "drizzle-orm/mysql2";
import mysql from "mysql2";
import type { PoolOptions } from "mysql2";
import { dbEnv } from "../env";
import type { SharedDrizzleConfig } from "./shared";
export const createMysqlDb = <TSchema extends Record<string, unknown>>(config: SharedDrizzleConfig<TSchema>) => {
const connection = createMysqlDbConnection();
return drizzle<TSchema>(connection, {
...config,
mode: "default",
});
};
const createMysqlDbConnection = () => {
const defaultOptions = {
maxIdle: 0,
idleTimeout: 60000,
enableKeepAlive: true,
} satisfies Partial<PoolOptions>;
if (!dbEnv.HOST) {
return mysql.createPool({ ...defaultOptions, uri: dbEnv.URL });
}
return mysql.createPool({
...defaultOptions,
port: dbEnv.PORT,
user: dbEnv.USER,
password: dbEnv.PASSWORD,
});
};

View File

@@ -0,0 +1,38 @@
import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
import type { PoolOptions as PostgresPoolOptions } from "pg";
import { Pool as PostgresPool } from "pg";
import { dbEnv } from "../env";
import type { SharedDrizzleConfig } from "./shared";
export const createPostgresDb = <TSchema extends Record<string, unknown>>(config: SharedDrizzleConfig<TSchema>) => {
const connection = createPostgresDbConnection();
return drizzlePostgres({
...config,
client: connection,
});
};
const createPostgresDbConnection = () => {
const defaultOptions = {
max: 0,
idleTimeoutMillis: 60000,
allowExitOnIdle: false,
} satisfies Partial<PostgresPoolOptions>;
if (!dbEnv.HOST) {
return new PostgresPool({
...defaultOptions,
connectionString: dbEnv.URL,
});
}
return new PostgresPool({
...defaultOptions,
host: dbEnv.HOST,
port: dbEnv.PORT,
database: dbEnv.NAME,
user: dbEnv.USER,
password: dbEnv.PASSWORD,
});
};

View File

@@ -0,0 +1,15 @@
import type { DrizzleConfig, Logger } from "drizzle-orm";
import { createLogger } from "../../logs";
export type SharedDrizzleConfig<TSchema extends Record<string, unknown>> = Required<
Pick<DrizzleConfig<TSchema>, "logger" | "casing" | "schema">
>;
const logger = createLogger({ module: "db" });
export class WinstonDrizzleLogger implements Logger {
logQuery(query: string, _: unknown[]): void {
logger.debug("Executed SQL query", { query });
}
}

View File

@@ -0,0 +1,10 @@
import Database from "better-sqlite3";
import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
import { dbEnv } from "../env";
import type { SharedDrizzleConfig } from "./shared";
export const createSqliteDb = <TSchema extends Record<string, unknown>>(config: SharedDrizzleConfig<TSchema>) => {
const connection = new Database(dbEnv.URL);
return drizzleSqlite<TSchema>(connection, config);
};

View File

@@ -1,7 +1,6 @@
import { z } from "zod/v4"; import { z } from "zod/v4";
import { env as commonEnv } from "@homarr/common/env"; import { createEnv, runtimeEnvWithPrefix } from "@homarr/core/infrastructure/env";
import { createEnv } from "@homarr/core/infrastructure/env";
const drivers = { const drivers = {
betterSqlite3: "better-sqlite3", betterSqlite3: "better-sqlite3",
@@ -15,40 +14,40 @@ const onlyAllowUrl = isDriver(drivers.betterSqlite3);
const urlRequired = onlyAllowUrl || !isUsingDbHost; const urlRequired = onlyAllowUrl || !isUsingDbHost;
const hostRequired = isUsingDbHost && !onlyAllowUrl; const hostRequired = isUsingDbHost && !onlyAllowUrl;
export const env = createEnv({ export const dbEnv = createEnv({
/** /**
* Specify your server-side environment variables schema here. This way you can ensure the app isn't * Specify your server-side environment variables schema here. This way you can ensure the app isn't
* built with invalid env vars. * built with invalid env vars.
*/ */
server: { server: {
DB_DRIVER: z DRIVER: z
.union([z.literal(drivers.betterSqlite3), z.literal(drivers.mysql2), z.literal(drivers.nodePostgres)], { .union([z.literal(drivers.betterSqlite3), z.literal(drivers.mysql2), z.literal(drivers.nodePostgres)], {
message: `Invalid database driver, supported are ${Object.keys(drivers).join(", ")}`, message: `Invalid database driver, supported are ${Object.keys(drivers).join(", ")}`,
}) })
.default(drivers.betterSqlite3), .default(drivers.betterSqlite3),
...(urlRequired ...(urlRequired
? { ? {
DB_URL: URL:
// Fallback to the default sqlite file path in production // Fallback to the default sqlite file path in production
commonEnv.NODE_ENV === "production" && isDriver("better-sqlite3") process.env.NODE_ENV === "production" && isDriver("better-sqlite3")
? z.string().default("/appdata/db/db.sqlite") ? z.string().default("/appdata/db/db.sqlite")
: z.string().nonempty(), : z.string().nonempty(),
} }
: {}), : {}),
...(hostRequired ...(hostRequired
? { ? {
DB_HOST: z.string(), HOST: z.string(),
DB_PORT: z PORT: z
.string() .string()
.regex(/\d+/) .regex(/\d+/)
.transform(Number) .transform(Number)
.refine((number) => number >= 1) .refine((number) => number >= 1)
.default(isDriver(drivers.mysql2) ? 3306 : 5432), .default(isDriver(drivers.mysql2) ? 3306 : 5432),
DB_USER: z.string(), USER: z.string(),
DB_PASSWORD: z.string(), PASSWORD: z.string(),
DB_NAME: z.string(), NAME: z.string(),
} }
: {}), : {}),
}, },
experimental__runtimeEnv: process.env, runtimeEnv: runtimeEnvWithPrefix("DB_"),
}); });

View File

@@ -0,0 +1,9 @@
import { createDbMapping } from "./mapping";
export { createDb } from "./drivers";
export const createSchema = createDbMapping;
export { createMysqlDb } from "./drivers/mysql";
export { createSqliteDb } from "./drivers/sqlite";
export { createPostgresDb } from "./drivers/postgresql";
export { createSharedConfig as createSharedDbConfig } from "./drivers";

View File

@@ -0,0 +1,9 @@
import { dbEnv } from "./env";
type DbMappingInput = Record<typeof dbEnv.DRIVER, () => unknown>;
export const createDbMapping = <TInput extends DbMappingInput>(input: TInput) => {
// The DRIVER can be undefined when validation of env vars is skipped
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return input[dbEnv.DRIVER ?? "better-sqlite3"]() as ReturnType<TInput["better-sqlite3"]>;
};

View File

@@ -1,9 +1,9 @@
import type { InferInsertModel } from "drizzle-orm"; import type { InferInsertModel } from "drizzle-orm";
import { objectEntries } from "@homarr/common"; import { objectEntries } from "@homarr/common";
import { dbEnv } from "@homarr/core/infrastructure/db/env";
import type { HomarrDatabase, HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver"; import type { HomarrDatabase, HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver";
import { env } from "./env";
import * as schema from "./schema"; import * as schema from "./schema";
type TableKey = { type TableKey = {
@@ -11,11 +11,11 @@ type TableKey = {
}[keyof typeof schema]; }[keyof typeof schema];
export function isMysql(): boolean { export function isMysql(): boolean {
return env.DB_DRIVER === "mysql2"; return dbEnv.DRIVER === "mysql2";
} }
export function isPostgresql(): boolean { export function isPostgresql(): boolean {
return env.DB_DRIVER === "node-postgres"; return dbEnv.DRIVER === "node-postgres";
} }
export const createDbInsertCollectionForTransaction = <TTableKey extends TableKey>( export const createDbInsertCollectionForTransaction = <TTableKey extends TableKey>(
@@ -66,7 +66,7 @@ export const createDbInsertCollectionWithoutTransaction = <TTableKey extends Tab
return { return {
...collection, ...collection,
insertAllAsync: async (db: HomarrDatabase) => { insertAllAsync: async (db: HomarrDatabase) => {
switch (env.DB_DRIVER) { switch (dbEnv.DRIVER) {
case "mysql2": case "mysql2":
case "node-postgres": case "node-postgres":
// For mysql2 and node-postgres, we can use the async insertAllAsync method // For mysql2 and node-postgres, we can use the async insertAllAsync method

View File

@@ -1,19 +1,20 @@
import type { Config } from "drizzle-kit"; import type { Config } from "drizzle-kit";
import { env } from "../env"; import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
import { dbEnv } from "@homarr/core/infrastructure/db/env";
export default { export default {
dialect: "mysql", dialect: "mysql",
schema: "./schema", schema: "./schema",
casing: "snake_case", casing: DB_CASING,
dbCredentials: env.DB_URL dbCredentials: dbEnv.URL
? { url: env.DB_URL } ? { url: dbEnv.URL }
: { : {
host: env.DB_HOST, host: dbEnv.HOST,
user: env.DB_USER, port: dbEnv.PORT,
password: env.DB_PASSWORD, database: dbEnv.NAME,
database: env.DB_NAME, user: dbEnv.USER,
port: env.DB_PORT, password: dbEnv.PASSWORD,
}, },
out: "./migrations/mysql", out: "./migrations/mysql",
} satisfies Config; } satisfies Config;

View File

@@ -1,20 +1,21 @@
import type { Config } from "drizzle-kit"; import type { Config } from "drizzle-kit";
import { env } from "../env"; import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
import { dbEnv } from "@homarr/core/infrastructure/db/env";
export default { export default {
dialect: "postgresql", dialect: "postgresql",
schema: "./schema", schema: "./schema",
casing: "snake_case", casing: DB_CASING,
dbCredentials: env.DB_URL dbCredentials: dbEnv.URL
? { url: env.DB_URL } ? { url: dbEnv.URL }
: { : {
host: env.DB_HOST, host: dbEnv.HOST,
port: env.DB_PORT, port: dbEnv.PORT,
user: env.DB_USER, database: dbEnv.NAME,
password: env.DB_PASSWORD, user: dbEnv.USER,
database: env.DB_NAME, password: dbEnv.PASSWORD,
}, },
out: "./migrations/postgresql", out: "./migrations/postgresql",
} satisfies Config; } satisfies Config;

View File

@@ -1,11 +1,12 @@
import type { Config } from "drizzle-kit"; import type { Config } from "drizzle-kit";
import { env } from "../env"; import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
import { dbEnv } from "@homarr/core/infrastructure/db/env";
export default { export default {
dialect: "sqlite", dialect: "sqlite",
schema: "./schema", schema: "./schema",
casing: "snake_case", casing: DB_CASING,
dbCredentials: { url: env.DB_URL }, dbCredentials: { url: dbEnv.URL },
out: "./migrations/sqlite", out: "./migrations/sqlite",
} satisfies Config; } satisfies Config;

View File

@@ -1,115 +1,11 @@
import type { Database as BetterSqlite3Connection } from "better-sqlite3";
import Database from "better-sqlite3";
import type { Logger } from "drizzle-orm";
import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
import type { MySql2Database } from "drizzle-orm/mysql2"; import type { MySql2Database } from "drizzle-orm/mysql2";
import { drizzle as drizzleMysql } from "drizzle-orm/mysql2";
import type { NodePgDatabase } from "drizzle-orm/node-postgres"; import type { NodePgDatabase } from "drizzle-orm/node-postgres";
import { drizzle as drizzlePg } from "drizzle-orm/node-postgres";
import type { Pool as MysqlConnectionPool } from "mysql2";
import mysql from "mysql2";
import { Pool as PostgresPool } from "pg";
import { createLogger } from "@homarr/core/infrastructure/logs"; import type * as mysqlSchema from "./schema/mysql";
import type * as pgSchema from "./schema/postgresql";
import { env } from "./env"; import type * as sqliteSchema from "./schema/sqlite";
import * as mysqlSchema from "./schema/mysql";
import * as pgSchema from "./schema/postgresql";
import * as sqliteSchema from "./schema/sqlite";
const logger = createLogger({ module: "db" });
export type HomarrDatabase = BetterSQLite3Database<typeof sqliteSchema>; export type HomarrDatabase = BetterSQLite3Database<typeof sqliteSchema>;
export type HomarrDatabaseMysql = MySql2Database<typeof mysqlSchema>; export type HomarrDatabaseMysql = MySql2Database<typeof mysqlSchema>;
export type HomarrDatabasePostgresql = NodePgDatabase<typeof pgSchema>; export type HomarrDatabasePostgresql = NodePgDatabase<typeof pgSchema>;
const init = () => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!connection) {
switch (env.DB_DRIVER) {
case "mysql2":
initMySQL2();
break;
case "node-postgres":
initNodePostgres();
break;
default:
initBetterSqlite();
break;
}
}
};
export let connection: BetterSqlite3Connection | MysqlConnectionPool | PostgresPool;
export let database: HomarrDatabase;
class WinstonDrizzleLogger implements Logger {
logQuery(query: string, _: unknown[]): void {
logger.debug("Executed SQL query", { query });
}
}
const initBetterSqlite = () => {
connection = new Database(env.DB_URL);
database = drizzleSqlite(connection, {
schema: sqliteSchema,
logger: new WinstonDrizzleLogger(),
casing: "snake_case",
}) as unknown as never;
};
const initMySQL2 = () => {
if (!env.DB_HOST) {
connection = mysql.createPool({ uri: env.DB_URL, maxIdle: 0, idleTimeout: 60000, enableKeepAlive: true });
} else {
connection = mysql.createPool({
host: env.DB_HOST,
database: env.DB_NAME,
port: env.DB_PORT,
user: env.DB_USER,
password: env.DB_PASSWORD,
maxIdle: 0,
idleTimeout: 60000,
enableKeepAlive: true,
});
}
database = drizzleMysql(connection, {
schema: mysqlSchema,
mode: "default",
logger: new WinstonDrizzleLogger(),
casing: "snake_case",
}) as unknown as HomarrDatabase;
};
const initNodePostgres = () => {
if (!env.DB_HOST) {
connection = new PostgresPool({
connectionString: env.DB_URL,
max: 0,
idleTimeoutMillis: 60000,
allowExitOnIdle: false,
});
} else {
connection = new PostgresPool({
host: env.DB_HOST,
database: env.DB_NAME,
port: env.DB_PORT,
user: env.DB_USER,
password: env.DB_PASSWORD,
max: 0,
idleTimeoutMillis: 60000,
allowExitOnIdle: false,
});
}
database = drizzlePg({
logger: new WinstonDrizzleLogger(),
schema: pgSchema,
casing: "snake_case",
client: connection,
}) as unknown as HomarrDatabase;
};
init();

View File

@@ -1,12 +1,12 @@
import Database from "better-sqlite3"; import { createDb } from "@homarr/core/infrastructure/db";
import { database } from "./driver"; import { schema } from "./schema";
export * from "drizzle-orm"; export * from "drizzle-orm";
export const db = database;
export type Database = typeof db;
export type { HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver"; export type { HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver";
export const db = createDb(schema);
export type Database = typeof db;
export { handleDiffrentDbDriverOperationsAsync as handleTransactionsAsync } from "./transactions"; export { handleDiffrentDbDriverOperationsAsync as handleTransactionsAsync } from "./transactions";

View File

@@ -1,7 +1,7 @@
import { applyCustomMigrationsAsync } from "."; import { applyCustomMigrationsAsync } from ".";
import { database } from "../../driver"; import { db } from "../..";
applyCustomMigrationsAsync(database) applyCustomMigrationsAsync(db)
.then(() => { .then(() => {
console.log("Custom migrations applied successfully"); console.log("Custom migrations applied successfully");
process.exit(0); process.exit(0);

View File

@@ -1,9 +1,8 @@
import { drizzle } from "drizzle-orm/mysql2";
import { migrate } from "drizzle-orm/mysql2/migrator"; import { migrate } from "drizzle-orm/mysql2/migrator";
import mysql from "mysql2";
import { createMysqlDb, createSharedDbConfig } from "@homarr/core/infrastructure/db";
import type { Database } from "../.."; import type { Database } from "../..";
import { env } from "../../env";
import * as mysqlSchema from "../../schema/mysql"; import * as mysqlSchema from "../../schema/mysql";
import { applyCustomMigrationsAsync } from "../custom"; import { applyCustomMigrationsAsync } from "../custom";
import { seedDataAsync } from "../seed"; import { seedDataAsync } from "../seed";
@@ -11,23 +10,8 @@ import { seedDataAsync } from "../seed";
const migrationsFolder = process.argv[2] ?? "."; const migrationsFolder = process.argv[2] ?? ".";
const migrateAsync = async () => { const migrateAsync = async () => {
const mysql2 = mysql.createConnection( const config = createSharedDbConfig(mysqlSchema);
env.DB_URL const db = createMysqlDb(config);
? { uri: env.DB_URL }
: {
host: env.DB_HOST,
database: env.DB_NAME,
port: env.DB_PORT,
user: env.DB_USER,
password: env.DB_PASSWORD,
},
);
const db = drizzle(mysql2, {
mode: "default",
schema: mysqlSchema,
casing: "snake_case",
});
await migrate(db, { migrationsFolder }); await migrate(db, { migrationsFolder });
await seedDataAsync(db as unknown as Database); await seedDataAsync(db as unknown as Database);

View File

@@ -1,9 +1,8 @@
import { drizzle } from "drizzle-orm/node-postgres";
import { migrate } from "drizzle-orm/node-postgres/migrator"; import { migrate } from "drizzle-orm/node-postgres/migrator";
import { Pool } from "pg";
import { createPostgresDb, createSharedDbConfig } from "@homarr/core/infrastructure/db";
import type { Database } from "../.."; import type { Database } from "../..";
import { env } from "../../env";
import * as pgSchema from "../../schema/postgresql"; import * as pgSchema from "../../schema/postgresql";
import { applyCustomMigrationsAsync } from "../custom"; import { applyCustomMigrationsAsync } from "../custom";
import { seedDataAsync } from "../seed"; import { seedDataAsync } from "../seed";
@@ -11,23 +10,8 @@ import { seedDataAsync } from "../seed";
const migrationsFolder = process.argv[2] ?? "."; const migrationsFolder = process.argv[2] ?? ".";
const migrateAsync = async () => { const migrateAsync = async () => {
const pool = new Pool( const config = createSharedDbConfig(pgSchema);
env.DB_URL const db = createPostgresDb(config);
? { connectionString: env.DB_URL }
: {
host: env.DB_HOST,
database: env.DB_NAME,
port: env.DB_PORT,
user: env.DB_USER,
password: env.DB_PASSWORD,
},
);
const db = drizzle({
schema: pgSchema,
casing: "snake_case",
client: pool,
});
await migrate(db, { migrationsFolder }); await migrate(db, { migrationsFolder });
await seedDataAsync(db as unknown as Database); await seedDataAsync(db as unknown as Database);

View File

@@ -1,7 +1,7 @@
import { database } from "../driver"; import { db } from "..";
import { seedDataAsync } from "./seed"; import { seedDataAsync } from "./seed";
seedDataAsync(database) seedDataAsync(db)
.then(() => { .then(() => {
console.log("Seed complete"); console.log("Seed complete");
process.exit(0); process.exit(0);

View File

@@ -1,8 +1,7 @@
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { env } from "../../env"; import { createSharedDbConfig, createSqliteDb } from "@homarr/core/infrastructure/db";
import * as sqliteSchema from "../../schema/sqlite"; import * as sqliteSchema from "../../schema/sqlite";
import { applyCustomMigrationsAsync } from "../custom"; import { applyCustomMigrationsAsync } from "../custom";
import { seedDataAsync } from "../seed"; import { seedDataAsync } from "../seed";
@@ -10,9 +9,8 @@ import { seedDataAsync } from "../seed";
const migrationsFolder = process.argv[2] ?? "."; const migrationsFolder = process.argv[2] ?? ".";
const migrateAsync = async () => { const migrateAsync = async () => {
const sqlite = new Database(env.DB_URL.replace("file:", "")); const config = createSharedDbConfig(sqliteSchema);
const db = createSqliteDb(config);
const db = drizzle(sqlite, { schema: sqliteSchema, casing: "snake_case" });
migrate(db, { migrationsFolder }); migrate(db, { migrationsFolder });

View File

@@ -10,8 +10,7 @@
"./schema": "./schema/index.ts", "./schema": "./schema/index.ts",
"./test": "./test/index.ts", "./test": "./test/index.ts",
"./queries": "./queries/index.ts", "./queries": "./queries/index.ts",
"./validationSchemas": "./validationSchemas.ts", "./validationSchemas": "./validationSchemas.ts"
"./env": "./env.ts"
}, },
"main": "./index.ts", "main": "./index.ts",
"types": "./index.ts", "types": "./index.ts",

View File

@@ -1,20 +1,19 @@
import type { InferSelectModel } from "drizzle-orm"; import type { InferSelectModel } from "drizzle-orm";
import { env } from "../env"; import { createSchema } from "@homarr/core/infrastructure/db";
import * as mysqlSchema from "./mysql"; import * as mysqlSchema from "./mysql";
import * as pgSchema from "./postgresql"; import * as pgSchema from "./postgresql";
import * as sqliteSchema from "./sqlite"; import * as sqliteSchema from "./sqlite";
export type PostgreSqlSchema = typeof pgSchema; export type PostgreSqlSchema = typeof pgSchema;
export type MySqlSchema = typeof mysqlSchema; export type MySqlSchema = typeof mysqlSchema;
type Schema = typeof sqliteSchema;
const schema = export const schema = createSchema({
env.DB_DRIVER === "mysql2" "better-sqlite3": () => sqliteSchema,
? (mysqlSchema as unknown as Schema) mysql2: () => mysqlSchema,
: env.DB_DRIVER === "node-postgres" "node-postgres": () => pgSchema,
? (pgSchema as unknown as Schema) });
: sqliteSchema;
// Sadly we can't use export * from here as we have multiple possible exports // Sadly we can't use export * from here as we have multiple possible exports
export const { export const {

View File

@@ -2,11 +2,13 @@ import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3"; import { drizzle } from "drizzle-orm/better-sqlite3";
import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
import * as sqliteSchema from "../schema/sqlite"; import * as sqliteSchema from "../schema/sqlite";
export const createDb = (debug?: boolean) => { export const createDb = (debug?: boolean) => {
const sqlite = new Database(":memory:"); const sqlite = new Database(":memory:");
const db = drizzle(sqlite, { schema: sqliteSchema, logger: debug, casing: "snake_case" }); const db = drizzle(sqlite, { schema: sqliteSchema, logger: debug, casing: DB_CASING });
migrate(db, { migrate(db, {
migrationsFolder: "./packages/db/migrations/sqlite", migrationsFolder: "./packages/db/migrations/sqlite",
}); });

View File

@@ -5,6 +5,8 @@ import { migrate } from "drizzle-orm/mysql2/migrator";
import mysql from "mysql2"; import mysql from "mysql2";
import { describe, test } from "vitest"; import { describe, test } from "vitest";
import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
import * as mysqlSchema from "../schema/mysql"; import * as mysqlSchema from "../schema/mysql";
describe("Mysql Migration", () => { describe("Mysql Migration", () => {
@@ -22,7 +24,7 @@ describe("Mysql Migration", () => {
const database = drizzle(connection, { const database = drizzle(connection, {
schema: mysqlSchema, schema: mysqlSchema,
mode: "default", mode: "default",
casing: "snake_case", casing: DB_CASING,
}); });
// Run migrations and check if it works // Run migrations and check if it works

View File

@@ -5,6 +5,8 @@ import { migrate } from "drizzle-orm/node-postgres/migrator";
import { Pool } from "pg"; import { Pool } from "pg";
import { describe, test } from "vitest"; import { describe, test } from "vitest";
import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
import * as pgSchema from "../schema/postgresql"; import * as pgSchema from "../schema/postgresql";
describe("PostgreSql Migration", () => { describe("PostgreSql Migration", () => {
@@ -26,7 +28,7 @@ describe("PostgreSql Migration", () => {
const database = drizzle({ const database = drizzle({
schema: pgSchema, schema: pgSchema,
casing: "snake_case", casing: DB_CASING,
client: pool, client: pool,
}); });

18
pnpm-lock.yaml generated
View File

@@ -920,9 +920,21 @@ importers:
'@t3-oss/env-nextjs': '@t3-oss/env-nextjs':
specifier: ^0.13.8 specifier: ^0.13.8
version: 0.13.8(arktype@2.1.20)(typescript@5.9.3)(zod@4.1.13) version: 0.13.8(arktype@2.1.20)(typescript@5.9.3)(zod@4.1.13)
better-sqlite3:
specifier: ^12.5.0
version: 12.5.0
drizzle-orm:
specifier: ^0.45.1
version: 0.45.1(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(better-sqlite3@12.5.0)(gel@2.0.0)(mysql2@3.15.3)(pg@8.16.3)
ioredis: ioredis:
specifier: 5.8.2 specifier: 5.8.2
version: 5.8.2 version: 5.8.2
mysql2:
specifier: 3.15.3
version: 3.15.3
pg:
specifier: ^8.16.3
version: 8.16.3
superjson: superjson:
specifier: 2.2.6 specifier: 2.2.6
version: 2.2.6 version: 2.2.6
@@ -942,6 +954,12 @@ importers:
'@homarr/tsconfig': '@homarr/tsconfig':
specifier: workspace:^0.1.0 specifier: workspace:^0.1.0
version: link:../../tooling/typescript version: link:../../tooling/typescript
'@types/better-sqlite3':
specifier: 7.6.13
version: 7.6.13
'@types/pg':
specifier: ^8.16.0
version: 8.16.0
eslint: eslint:
specifier: ^9.39.1 specifier: ^9.39.1
version: 9.39.1 version: 9.39.1

View File

@@ -10,7 +10,8 @@ if [ "$DB_MIGRATIONS_DISABLED" = "true" ]; then
echo "DB migrations are disabled, skipping" echo "DB migrations are disabled, skipping"
else else
echo "Running DB migrations" echo "Running DB migrations"
node ./db/migrations/$DB_DIALECT/migrate.cjs ./db/migrations/$DB_DIALECT # We disable redis logs during migration as the redis client is not yet started
DISABLE_REDIS_LOGS=true node ./db/migrations/$DB_DIALECT/migrate.cjs ./db/migrations/$DB_DIALECT
fi fi
# Auth secret is generated every time the container starts as it is required, but not used because we don't need JWTs or Mail hashing # Auth secret is generated every time the container starts as it is required, but not used because we don't need JWTs or Mail hashing