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,5 +1,5 @@
|
||||
import { Prisma } from "@/generated/prisma/client";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { asc, eq, inArray, sql } from "drizzle-orm";
|
||||
import { CatalogPages, db } from "@/lib/db";
|
||||
|
||||
export type { NestedTreeNode, TreeNode } from "@/types/catalog";
|
||||
|
||||
@@ -23,25 +23,27 @@ export async function getCatalogItemCounts(
|
||||
|
||||
if (pageIds) {
|
||||
const idStrs = pageIds.map(String);
|
||||
const rows = await prisma.$queryRaw<
|
||||
Array<{ page_id: string | number; cnt: number | bigint }>
|
||||
>`
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT page_id, COUNT(*) as cnt
|
||||
FROM catalog_items
|
||||
WHERE CAST(page_id AS CHAR) IN (${Prisma.join(idStrs)})
|
||||
WHERE CAST(page_id AS CHAR) IN (${sql.join(idStrs, sql`, `)})
|
||||
GROUP BY page_id
|
||||
`;
|
||||
`)) as unknown as [
|
||||
{ page_id: string | number; cnt: number | bigint }[],
|
||||
unknown[],
|
||||
];
|
||||
for (const r of rows) {
|
||||
map.set(toInt(r.page_id), toInt(r.cnt));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
const rows = await prisma.$queryRaw<
|
||||
Array<{ page_id: string | number; cnt: number | bigint }>
|
||||
>`
|
||||
const [rows] = (await db.execute(sql`
|
||||
SELECT page_id, COUNT(*) as cnt FROM catalog_items GROUP BY page_id
|
||||
`;
|
||||
`)) as unknown as [
|
||||
{ page_id: string | number; cnt: number | bigint }[],
|
||||
unknown[],
|
||||
];
|
||||
for (const r of rows) {
|
||||
map.set(toInt(r.page_id), toInt(r.cnt));
|
||||
}
|
||||
@@ -55,9 +57,10 @@ export async function getCatalogItemCounts(
|
||||
* Get the full catalog tree as a flat list with depth info.
|
||||
*/
|
||||
export async function getTreeFlat(): Promise<TreeNode[]> {
|
||||
const allPages = await prisma.catalogPages.findMany({
|
||||
orderBy: { orderNum: "asc" },
|
||||
});
|
||||
const allPages = await db
|
||||
.select()
|
||||
.from(CatalogPages)
|
||||
.orderBy(asc(CatalogPages.orderNum));
|
||||
const itemCountMap = await getCatalogItemCounts();
|
||||
|
||||
const childCountMap = new Map<number, number>();
|
||||
@@ -69,15 +72,23 @@ export async function getTreeFlat(): Promise<TreeNode[]> {
|
||||
const pageMap = new Map(allPages.map((p) => [p.id, p]));
|
||||
const depthCache = new Map<number, number>();
|
||||
|
||||
function computeDepth(pageId: number): number {
|
||||
function computeDepth(pageId: number, visiting = new Set<number>()): number {
|
||||
if (depthCache.has(pageId)) return depthCache.get(pageId) ?? 0;
|
||||
const page = pageMap.get(pageId);
|
||||
const parentId = page ? toInt(page.parentId, -1) : -1;
|
||||
if (!page || parentId <= 0) {
|
||||
// Cycle guard: parent chains may loop in messy DBs — stop at 0 instead of overflowing.
|
||||
if (visiting.has(pageId)) {
|
||||
depthCache.set(pageId, 0);
|
||||
return 0;
|
||||
}
|
||||
const d = computeDepth(parentId) + 1;
|
||||
visiting.add(pageId);
|
||||
const page = pageMap.get(pageId);
|
||||
const parentId = page ? toInt(page.parentId, -1) : -1;
|
||||
if (!page || parentId <= 0) {
|
||||
visiting.delete(pageId);
|
||||
depthCache.set(pageId, 0);
|
||||
return 0;
|
||||
}
|
||||
const d = computeDepth(parentId, visiting) + 1;
|
||||
visiting.delete(pageId);
|
||||
depthCache.set(pageId, d);
|
||||
return d;
|
||||
}
|
||||
@@ -154,24 +165,22 @@ export async function createPage(data: {
|
||||
: "0"
|
||||
: (data.enabled ?? "1");
|
||||
|
||||
return prisma.catalogPages.create({
|
||||
data: {
|
||||
caption: data.caption,
|
||||
parentId: data.parentId,
|
||||
pageLayout: data.pageLayout ?? "default_3x3",
|
||||
captionSave: (data.captionSave ?? data.caption).slice(0, 25),
|
||||
iconColor: data.iconColor ?? 0,
|
||||
iconImage: data.iconImage ?? 0,
|
||||
minRank: data.minRank ?? 1,
|
||||
orderNum: data.orderNum ?? 0,
|
||||
visible,
|
||||
enabled,
|
||||
clubOnly: "0",
|
||||
vipOnly: "0",
|
||||
pageHeadline: "",
|
||||
pageTeaser: "",
|
||||
includes: "",
|
||||
},
|
||||
return db.insert(CatalogPages).values({
|
||||
caption: data.caption,
|
||||
parentId: data.parentId,
|
||||
pageLayout: data.pageLayout ?? "default_3x3",
|
||||
captionSave: (data.captionSave ?? data.caption).slice(0, 25),
|
||||
iconColor: data.iconColor ?? 0,
|
||||
iconImage: data.iconImage ?? 0,
|
||||
minRank: data.minRank ?? 1,
|
||||
orderNum: data.orderNum ?? 0,
|
||||
visible,
|
||||
enabled,
|
||||
clubOnly: "0",
|
||||
vipOnly: "0",
|
||||
pageHeadline: "",
|
||||
pageTeaser: "",
|
||||
includes: "",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -189,19 +198,20 @@ export async function movePage(
|
||||
if (currentId === pageId) {
|
||||
throw new Error("Cannot move page: would create a circular hierarchy");
|
||||
}
|
||||
const parent = await prisma.catalogPages.findUnique({
|
||||
where: { id: currentId },
|
||||
select: { parentId: true },
|
||||
});
|
||||
const [parent] = await db
|
||||
.select({ parentId: CatalogPages.parentId })
|
||||
.from(CatalogPages)
|
||||
.where(eq(CatalogPages.id, currentId))
|
||||
.limit(1);
|
||||
if (!parent || parent.parentId <= 0) break;
|
||||
currentId = parent.parentId;
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.catalogPages.update({
|
||||
where: { id: pageId },
|
||||
data: { parentId: newParentId },
|
||||
});
|
||||
await db
|
||||
.update(CatalogPages)
|
||||
.set({ parentId: newParentId })
|
||||
.where(eq(CatalogPages.id, pageId));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,10 +221,10 @@ export async function movePage(
|
||||
async function deleteCatalogItemsByPageIds(pageIds: number[]): Promise<void> {
|
||||
if (pageIds.length === 0) return;
|
||||
const idStrs = pageIds.map(String);
|
||||
await prisma.$executeRaw`
|
||||
await db.execute(sql`
|
||||
DELETE FROM catalog_items
|
||||
WHERE CAST(page_id AS CHAR) IN (${Prisma.join(idStrs)})
|
||||
`;
|
||||
WHERE CAST(page_id AS CHAR) IN (${sql.join(idStrs, sql`, `)})
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,22 +234,23 @@ export async function deletePage(
|
||||
pageId: number,
|
||||
mode: "cascade" | "reparent" = "reparent",
|
||||
): Promise<{ deletedPages: number; movedChildren: number }> {
|
||||
const page = await prisma.catalogPages.findUnique({
|
||||
where: { id: pageId },
|
||||
select: { parentId: true },
|
||||
});
|
||||
const [page] = await db
|
||||
.select({ parentId: CatalogPages.parentId })
|
||||
.from(CatalogPages)
|
||||
.where(eq(CatalogPages.id, pageId))
|
||||
.limit(1);
|
||||
if (!page) return { deletedPages: 0, movedChildren: 0 };
|
||||
|
||||
if (mode === "reparent") {
|
||||
const result = await prisma.catalogPages.updateMany({
|
||||
where: { parentId: pageId },
|
||||
data: { parentId: page.parentId },
|
||||
});
|
||||
const [result] = await db
|
||||
.update(CatalogPages)
|
||||
.set({ parentId: page.parentId })
|
||||
.where(eq(CatalogPages.parentId, pageId));
|
||||
|
||||
await deleteCatalogItemsByPageIds([pageId]);
|
||||
await prisma.catalogPages.delete({ where: { id: pageId } });
|
||||
await db.delete(CatalogPages).where(eq(CatalogPages.id, pageId));
|
||||
|
||||
return { deletedPages: 1, movedChildren: result.count };
|
||||
return { deletedPages: 1, movedChildren: result.affectedRows };
|
||||
}
|
||||
|
||||
const deleted = await cascadeDelete(pageId);
|
||||
@@ -251,10 +262,10 @@ async function cascadeDelete(pageId: number): Promise<number> {
|
||||
const queue: number[] = [pageId];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const children = await prisma.catalogPages.findMany({
|
||||
where: { parentId: { in: queue } },
|
||||
select: { id: true },
|
||||
});
|
||||
const children = await db
|
||||
.select({ id: CatalogPages.id })
|
||||
.from(CatalogPages)
|
||||
.where(inArray(CatalogPages.parentId, queue));
|
||||
queue.length = 0;
|
||||
for (const child of children) {
|
||||
toDelete.push(child.id);
|
||||
@@ -264,7 +275,7 @@ async function cascadeDelete(pageId: number): Promise<number> {
|
||||
|
||||
await deleteCatalogItemsByPageIds(toDelete);
|
||||
for (let i = toDelete.length - 1; i >= 0; i--) {
|
||||
await prisma.catalogPages.delete({ where: { id: toDelete[i] } });
|
||||
await db.delete(CatalogPages).where(eq(CatalogPages.id, toDelete[i]));
|
||||
}
|
||||
|
||||
return toDelete.length;
|
||||
@@ -279,10 +290,15 @@ export async function getAncestors(
|
||||
const ancestors: Array<{ id: number; caption: string }> = [];
|
||||
let currentId = pageId;
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const page = await prisma.catalogPages.findUnique({
|
||||
where: { id: currentId },
|
||||
select: { id: true, caption: true, parentId: true },
|
||||
});
|
||||
const [page] = await db
|
||||
.select({
|
||||
id: CatalogPages.id,
|
||||
caption: CatalogPages.caption,
|
||||
parentId: CatalogPages.parentId,
|
||||
})
|
||||
.from(CatalogPages)
|
||||
.where(eq(CatalogPages.id, currentId))
|
||||
.limit(1);
|
||||
if (!page) break;
|
||||
ancestors.unshift({ id: page.id, caption: page.caption });
|
||||
if (page.parentId <= 0) break;
|
||||
|
||||
Reference in new issue
Block a user