refactor(db): move to core package (#4589)
This commit is contained in:
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);
|
||||
};
|
||||
53
packages/core/src/infrastructure/db/env.ts
Normal file
53
packages/core/src/infrastructure/db/env.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createEnv, runtimeEnvWithPrefix } from "@homarr/core/infrastructure/env";
|
||||
|
||||
const drivers = {
|
||||
betterSqlite3: "better-sqlite3",
|
||||
mysql2: "mysql2",
|
||||
nodePostgres: "node-postgres",
|
||||
} as const;
|
||||
|
||||
const isDriver = (driver: (typeof drivers)[keyof typeof drivers]) => process.env.DB_DRIVER === driver;
|
||||
const isUsingDbHost = Boolean(process.env.DB_HOST);
|
||||
const onlyAllowUrl = isDriver(drivers.betterSqlite3);
|
||||
const urlRequired = onlyAllowUrl || !isUsingDbHost;
|
||||
const hostRequired = isUsingDbHost && !onlyAllowUrl;
|
||||
|
||||
export const dbEnv = createEnv({
|
||||
/**
|
||||
* Specify your server-side environment variables schema here. This way you can ensure the app isn't
|
||||
* built with invalid env vars.
|
||||
*/
|
||||
server: {
|
||||
DRIVER: z
|
||||
.union([z.literal(drivers.betterSqlite3), z.literal(drivers.mysql2), z.literal(drivers.nodePostgres)], {
|
||||
message: `Invalid database driver, supported are ${Object.keys(drivers).join(", ")}`,
|
||||
})
|
||||
.default(drivers.betterSqlite3),
|
||||
...(urlRequired
|
||||
? {
|
||||
URL:
|
||||
// Fallback to the default sqlite file path in production
|
||||
process.env.NODE_ENV === "production" && isDriver("better-sqlite3")
|
||||
? z.string().default("/appdata/db/db.sqlite")
|
||||
: z.string().nonempty(),
|
||||
}
|
||||
: {}),
|
||||
...(hostRequired
|
||||
? {
|
||||
HOST: z.string(),
|
||||
PORT: z
|
||||
.string()
|
||||
.regex(/\d+/)
|
||||
.transform(Number)
|
||||
.refine((number) => number >= 1)
|
||||
.default(isDriver(drivers.mysql2) ? 3306 : 5432),
|
||||
USER: z.string(),
|
||||
PASSWORD: z.string(),
|
||||
NAME: z.string(),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
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"]>;
|
||||
};
|
||||
Reference in New Issue
Block a user