refactor: replace Prisma ORM runtime with Drizzle ORM facade
- Replace Prisma client runtime with Drizzle ORM (zero Prisma engine/query engine in production) - Add Prisma-compatible facade (@/lib/prisma-facade.ts) backed by Drizzle for backwards compatibility - Runtime queries route through Drizzle ORM; @prisma/client is now devDependency (types only) - Remove @prisma/adapter-mariadb dependency; delete prisma-pool.ts and types/prisma.ts - New Drizzle schema layer: src/db/schema.ts (176 tables) and src/lib/db.ts (connection) - Update README documenting the dual-layer ORM architecture - Restore src/generated/ gitignore (build artifact for local type generation) - 0 TypeScript errors, 583 tests passing The facade intentionally uses `any` types to match the Prisma Client API surface, allowing existing code to run unmodified while routing queries through Drizzle at runtime.
This commit is contained in:
1 parent
9d1c71d926
commit
56061e41d4
52 files changed
+5825
-748
No files matched your search
@@ -1,4 +1,6 @@
|
||||
import { Prisma } from "@/generated/prisma/client";
|
||||
import { type SQL, sql } from "drizzle-orm";
|
||||
import type { ResultSetHeader } from "mysql2";
|
||||
import type { Db } from "@/lib/db";
|
||||
|
||||
const VALID_KEY = /^[a-z][a-z0-9_]{0,63}$/;
|
||||
|
||||
@@ -41,11 +43,18 @@ export interface EmulatorRankForEdit extends EmulatorRankSummary {
|
||||
permissionMaxValues: Record<string, number>;
|
||||
}
|
||||
|
||||
type RawDb = {
|
||||
$queryRaw: <T = unknown>(query: Prisma.Sql) => Promise<T>;
|
||||
$executeRaw: (query: Prisma.Sql) => Promise<number>;
|
||||
$transaction: <T>(queries: Promise<T>[]) => Promise<T[]>;
|
||||
};
|
||||
/** Surface of `Db` used by these helpers (plain raw-SQL exec + transactions). */
|
||||
type RawDb = Pick<Db, "execute" | "transaction">;
|
||||
|
||||
async function rawRows<T>(db: RawDb, query: SQL | string): Promise<T> {
|
||||
const [rows] = await db.execute(query);
|
||||
return rows as T;
|
||||
}
|
||||
|
||||
async function rawExec(db: RawDb, query: SQL | string): Promise<number> {
|
||||
const [result] = await db.execute(query);
|
||||
return (result as ResultSetHeader).affectedRows;
|
||||
}
|
||||
|
||||
export function getRankPermissionColumn(rankId: number) {
|
||||
if (!Number.isInteger(rankId) || rankId <= 0 || rankId > 255) {
|
||||
@@ -79,8 +88,9 @@ export function splitRankUpdateFields(fields: Record<string, string | number>) {
|
||||
export async function fetchEmulatorRankSummaries(
|
||||
db: RawDb,
|
||||
): Promise<EmulatorRankSummary[]> {
|
||||
const rows = await db.$queryRaw<Record<string, unknown>[]>(
|
||||
Prisma.sql`
|
||||
const rows = await rawRows<Record<string, unknown>[]>(
|
||||
db,
|
||||
sql`
|
||||
SELECT id, rank_name, badge, level, prefix, prefix_color, hidden_rank
|
||||
FROM permission_ranks
|
||||
ORDER BY id ASC
|
||||
@@ -102,8 +112,9 @@ export async function fetchEmulatorRankForEdit(
|
||||
db: RawDb,
|
||||
rankId: number,
|
||||
): Promise<EmulatorRankForEdit | null> {
|
||||
const rankRows = await db.$queryRaw<Record<string, unknown>[]>(
|
||||
Prisma.sql`
|
||||
const rankRows = await rawRows<Record<string, unknown>[]>(
|
||||
db,
|
||||
sql`
|
||||
SELECT id, rank_name, badge, level, prefix, prefix_color, hidden_rank,
|
||||
log_commands, room_effect, auto_credits_amount, auto_pixels_amount,
|
||||
auto_gotw_amount, auto_points_amount
|
||||
@@ -114,15 +125,16 @@ export async function fetchEmulatorRankForEdit(
|
||||
if (rankRows.length === 0) return null;
|
||||
|
||||
const rankColumn = getRankPermissionColumn(rankId);
|
||||
const permissionRows = await db.$queryRaw<
|
||||
const permissionRows = await rawRows<
|
||||
{
|
||||
permission_key: string;
|
||||
max_value: number;
|
||||
value: number | string | null;
|
||||
}[]
|
||||
>(
|
||||
Prisma.sql`
|
||||
SELECT permission_key, max_value, ${Prisma.raw(quoteIdentifier(rankColumn))} AS value
|
||||
db,
|
||||
sql`
|
||||
SELECT permission_key, max_value, ${sql.raw(quoteIdentifier(rankColumn))} AS value
|
||||
FROM permission_definitions
|
||||
ORDER BY permission_key ASC
|
||||
`,
|
||||
@@ -159,40 +171,41 @@ export async function createEmulatorRank(
|
||||
db: RawDb,
|
||||
data: { rank_name: string; level: number },
|
||||
): Promise<number> {
|
||||
const rows = await db.$queryRaw<{ next_id: number }[]>(
|
||||
Prisma.sql`SELECT COALESCE(MAX(id), 0) + 1 AS next_id FROM permission_ranks`,
|
||||
const rows = await rawRows<{ next_id: number }[]>(
|
||||
db,
|
||||
sql`SELECT COALESCE(MAX(id), 0) + 1 AS next_id FROM permission_ranks`,
|
||||
);
|
||||
const id = Number(rows[0]?.next_id ?? 1);
|
||||
await ensurePermissionRankColumn(db, id);
|
||||
await resetPermissionRankColumn(db, id);
|
||||
|
||||
await db.$transaction([
|
||||
db.$executeRaw(
|
||||
Prisma.sql`
|
||||
await db.transaction(async (tx) => {
|
||||
await rawExec(
|
||||
tx,
|
||||
sql`
|
||||
INSERT INTO permission_ranks (id, rank_name, level)
|
||||
VALUES (${id}, ${data.rank_name}, ${data.level})
|
||||
`,
|
||||
),
|
||||
db.$executeRaw(
|
||||
Prisma.sql`
|
||||
);
|
||||
await rawExec(
|
||||
tx,
|
||||
sql`
|
||||
INSERT INTO permissions (id, rank_name, level)
|
||||
VALUES (${id}, ${data.rank_name}, ${data.level})
|
||||
ON DUPLICATE KEY UPDATE rank_name = VALUES(rank_name), level = VALUES(level)
|
||||
`,
|
||||
),
|
||||
]);
|
||||
);
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
export async function deleteEmulatorRank(db: RawDb, rankId: number) {
|
||||
await resetPermissionRankColumn(db, rankId);
|
||||
await db.$transaction([
|
||||
db.$executeRaw(
|
||||
Prisma.sql`DELETE FROM permission_ranks WHERE id = ${rankId}`,
|
||||
),
|
||||
db.$executeRaw(Prisma.sql`DELETE FROM permissions WHERE id = ${rankId}`),
|
||||
]);
|
||||
await db.transaction(async (tx) => {
|
||||
await rawExec(tx, sql`DELETE FROM permission_ranks WHERE id = ${rankId}`);
|
||||
await rawExec(tx, sql`DELETE FROM permissions WHERE id = ${rankId}`);
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateEmulatorRank(
|
||||
@@ -220,13 +233,13 @@ async function updateTableFields(
|
||||
fields: Record<string, string | number>,
|
||||
) {
|
||||
const assignments = Object.entries(fields).map(
|
||||
([key, value]) =>
|
||||
Prisma.sql`${Prisma.raw(quoteIdentifier(key))} = ${value}`,
|
||||
([key, value]) => sql`${sql.raw(quoteIdentifier(key))} = ${value}`,
|
||||
);
|
||||
return db.$executeRaw(
|
||||
Prisma.sql`
|
||||
UPDATE ${Prisma.raw(quoteIdentifier(table))}
|
||||
SET ${Prisma.join(assignments, ", ")}
|
||||
return rawExec(
|
||||
db,
|
||||
sql`
|
||||
UPDATE ${sql.raw(quoteIdentifier(table))}
|
||||
SET ${sql.join(assignments, sql`, `)}
|
||||
WHERE id = ${rankId}
|
||||
`,
|
||||
);
|
||||
@@ -240,19 +253,20 @@ async function updatePermissionDefinitionValues(
|
||||
const rankColumn = getRankPermissionColumn(rankId);
|
||||
const entries = Object.entries(permissionFields);
|
||||
const cases = entries.map(
|
||||
([key, value]) => Prisma.sql`WHEN ${key} THEN LEAST(${value}, max_value)`,
|
||||
([key, value]) => sql`WHEN ${key} THEN LEAST(${value}, max_value)`,
|
||||
);
|
||||
const keys = entries.map(([key]) => key);
|
||||
|
||||
return db.$executeRaw(
|
||||
Prisma.sql`
|
||||
return rawExec(
|
||||
db,
|
||||
sql`
|
||||
UPDATE permission_definitions
|
||||
SET ${Prisma.raw(quoteIdentifier(rankColumn))} =
|
||||
SET ${sql.raw(quoteIdentifier(rankColumn))} =
|
||||
CASE permission_key
|
||||
${Prisma.join(cases, " ")}
|
||||
ELSE ${Prisma.raw(quoteIdentifier(rankColumn))}
|
||||
${sql.join(cases, sql` `)}
|
||||
ELSE ${sql.raw(quoteIdentifier(rankColumn))}
|
||||
END
|
||||
WHERE permission_key IN (${Prisma.join(keys)})
|
||||
WHERE permission_key IN (${sql.join(keys, sql`, `)})
|
||||
`,
|
||||
);
|
||||
}
|
||||
@@ -272,8 +286,9 @@ async function updateLegacyPermissionFields(
|
||||
|
||||
async function ensurePermissionRankColumn(db: RawDb, rankId: number) {
|
||||
const rankColumn = getRankPermissionColumn(rankId);
|
||||
const exists = await db.$queryRaw<{ c: bigint | number }[]>(
|
||||
Prisma.sql`
|
||||
const exists = await rawRows<{ c: number }[]>(
|
||||
db,
|
||||
sql`
|
||||
SELECT COUNT(*) AS c
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
@@ -283,27 +298,30 @@ async function ensurePermissionRankColumn(db: RawDb, rankId: number) {
|
||||
);
|
||||
if (Number(exists[0]?.c ?? 0) > 0) return;
|
||||
|
||||
await db.$executeRaw(
|
||||
Prisma.sql`
|
||||
await rawExec(
|
||||
db,
|
||||
sql`
|
||||
ALTER TABLE permission_definitions
|
||||
ADD COLUMN ${Prisma.raw(quoteIdentifier(rankColumn))} TINYINT UNSIGNED NOT NULL DEFAULT 0
|
||||
ADD COLUMN ${sql.raw(quoteIdentifier(rankColumn))} TINYINT UNSIGNED NOT NULL DEFAULT 0
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
async function resetPermissionRankColumn(db: RawDb, rankId: number) {
|
||||
const rankColumn = getRankPermissionColumn(rankId);
|
||||
await db.$executeRaw(
|
||||
Prisma.sql`
|
||||
await rawExec(
|
||||
db,
|
||||
sql`
|
||||
UPDATE permission_definitions
|
||||
SET ${Prisma.raw(quoteIdentifier(rankColumn))} = 0
|
||||
SET ${sql.raw(quoteIdentifier(rankColumn))} = 0
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
async function getTableColumns(db: RawDb, table: string) {
|
||||
const rows = await db.$queryRaw<{ column_name: string }[]>(
|
||||
Prisma.sql`
|
||||
const rows = await rawRows<{ column_name: string }[]>(
|
||||
db,
|
||||
sql`
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
|
||||
Reference in new issue
Block a user