haixunMaster/lib/db.ts

89 lines
3.1 KiB
TypeScript
Raw Permalink Normal View History

2026-06-21 12:50:31 +00:00
import "server-only";
import { Prisma, PrismaClient } from "@prisma/client";
import { buildStyle8DPromptBlock } from "@/lib/types/style-profile";
const globalForPrisma = globalThis as unknown as {
prisma?: PrismaClient & { __schemaFingerprint?: string };
};
/** 與 schema 同步dmmf 不可用時用固定字串讓 dev 快取仍能失效。 */
const SCHEMA_FINGERPRINT_FALLBACK =
"ProductProfile:accountId,brand,brandId,context,createdAt,id,label,matchTags,topics,updatedAt|Topic:accountId,active,brief,brandProfileId,createdAt,id,label,productContext,productProfileId,query,researchMap,selectedTags,topicGoal";
let schemaFingerprint: string | null = null;
function getSchemaFingerprint(): string {
if (schemaFingerprint) return schemaFingerprint;
try {
const models = Prisma.dmmf?.datamodel?.models;
if (models) {
const account = models.find((m) => m.name === "Account");
const product = models.find((m) => m.name === "ProductProfile");
const topic = models.find((m) => m.name === "Topic");
const productFields = product?.fields.map((f) => f.name).sort().join(",") ?? "";
const topicFields = topic?.fields.map((f) => f.name).sort().join(",") ?? "";
const accountFields = account?.fields.map((f) => f.name).sort().join(",") ?? "";
schemaFingerprint = `Account:${accountFields}|ProductProfile:${productFields}|Topic:${topicFields}`;
return schemaFingerprint;
}
} catch {
// Prisma.dmmf 僅在 server runtime 可用
}
schemaFingerprint = SCHEMA_FINGERPRINT_FALLBACK;
return schemaFingerprint;
}
function createPrismaClient() {
const fingerprint = getSchemaFingerprint();
const client = new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"],
}) as PrismaClient & { __schemaFingerprint?: string };
client.__schemaFingerprint = fingerprint;
return client;
}
function isPrismaClientCurrent(
client: PrismaClient & { __schemaFingerprint?: string }
): boolean {
return (
"backgroundJob" in client &&
"user" in client &&
"productProfile" in client &&
"brandProfile" in client &&
client.__schemaFingerprint === getSchemaFingerprint()
);
}
function getPrismaClient() {
const cached = globalForPrisma.prisma;
if (cached && isPrismaClientCurrent(cached)) return cached;
const client = createPrismaClient();
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = client;
}
return client;
}
export const prisma = getPrismaClient();
export function resolvePersona(
settings: { persona: string | null },
account?: {
persona: string | null;
styleProfile?: string | null;
targetAudience?: string | null;
goals?: string | null;
} | null
) {
const blocks = [
account?.persona?.trim() || settings.persona?.trim(),
buildStyle8DPromptBlock(account?.styleProfile),
account?.targetAudience?.trim() ? `目標受眾:${account.targetAudience.trim()}` : null,
account?.goals?.trim() ? `經營目標:${account.goals.trim()}` : null,
].filter(Boolean);
return blocks.length > 0 ? blocks.join("\n\n") : null;
}