refactor(db): move to core package (#4589)
This commit is contained in:
@@ -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";
|
||||||
|
|||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
3
packages/core/src/infrastructure/db/constants.ts
Normal file
3
packages/core/src/infrastructure/db/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import type { Casing } from "drizzle-orm";
|
||||||
|
|
||||||
|
export const DB_CASING: Casing = "snake_case";
|
||||||
27
packages/core/src/infrastructure/db/drivers/index.ts
Normal file
27
packages/core/src/infrastructure/db/drivers/index.ts
Normal 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),
|
||||||
|
});
|
||||||
|
};
|
||||||
33
packages/core/src/infrastructure/db/drivers/mysql.ts
Normal file
33
packages/core/src/infrastructure/db/drivers/mysql.ts
Normal 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,
|
||||||
|
});
|
||||||
|
};
|
||||||
38
packages/core/src/infrastructure/db/drivers/postgresql.ts
Normal file
38
packages/core/src/infrastructure/db/drivers/postgresql.ts
Normal 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,
|
||||||
|
});
|
||||||
|
};
|
||||||
15
packages/core/src/infrastructure/db/drivers/shared.ts
Normal file
15
packages/core/src/infrastructure/db/drivers/shared.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
10
packages/core/src/infrastructure/db/drivers/sqlite.ts
Normal file
10
packages/core/src/infrastructure/db/drivers/sqlite.ts
Normal 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);
|
||||||
|
};
|
||||||
@@ -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_"),
|
||||||
});
|
});
|
||||||
9
packages/core/src/infrastructure/db/index.ts
Normal file
9
packages/core/src/infrastructure/db/index.ts
Normal 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";
|
||||||
9
packages/core/src/infrastructure/db/mapping.ts
Normal file
9
packages/core/src/infrastructure/db/mapping.ts
Normal 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"]>;
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
18
pnpm-lock.yaml
generated
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user