From 2c7223a3238cbb7ad0cf1701f788bea45201a6ef Mon Sep 17 00:00:00 2001 From: cfngc4594 Date: Tue, 6 May 2025 21:33:37 +0800 Subject: [PATCH] feat(prisma): add server-only caching for problems queries - Add 'server-only' import to enforce server-side usage - Implement cached problem queries with logging: - Add getProblems/getCachedProblems for all problems - Add getProblem/getCachedProblem for single problem by ID - Use React cache and Next.js unstable_cache with tags - Add detailed logging with timing metrics --- src/lib/prisma.ts | 107 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 0683262..e8ce447 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,9 +1,112 @@ +import "server-only"; + +import { cache } from "react"; +import { logger } from "@/lib/logger"; +import { unstable_cache } from "next/cache"; import { PrismaClient } from "@/generated/client"; +const log = logger.child({ module: "prisma" }); + const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }; - + const prisma = globalForPrisma.prisma || new PrismaClient(); - + if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; export default prisma; + +const getProblems = async () => { + const startTime = Date.now(); + log.debug("Fetching all problems from database"); + try { + const problems = await prisma.problem.findMany(); + log.debug( + { count: problems.length, durationMs: Date.now() - startTime }, + "Fetched problems successfully" + ); + return problems; + } catch (error) { + log.error( + { durationMs: Date.now() - startTime, error }, + "Failed to fetch problems" + ); + throw error; + } +}; + +export const getCachedProblems = cache( + unstable_cache( + async () => { + const startTime = Date.now(); + log.debug("Calling getProblemsCached (expect cache hit if warmed)"); + try { + const result = await getProblems(); + log.info( + { durationMs: Date.now() - startTime }, + "getProblemsCached finished" + ); + return result; + } catch (error) { + log.error( + { durationMs: Date.now() - startTime, error }, + "getProblemsCached failed" + ); + throw error; + } + }, + ["getProblems"], + { + tags: ["problems"], + } + ) +); + +const getProblem = async (id: string) => { + const startTime = Date.now(); + log.debug({ id }, "Fetching single problem"); + try { + const problem = await prisma.problem.findUnique({ where: { id } }); + if (problem) { + log.debug({ id, durationMs: Date.now() - startTime }, "Problem found"); + } else { + log.warn({ id, durationMs: Date.now() - startTime }, "Problem not found"); + } + return problem; + } catch (error) { + log.error( + { id, durationMs: Date.now() - startTime, error }, + "Failed to fetch problem" + ); + throw error; + } +}; + +export const getCachedProblem = cache((id: string) => + unstable_cache( + async () => { + const startTime = Date.now(); + log.debug( + { id }, + "Calling getProblemCached (expect cache hit if warmed)" + ); + try { + const result = await getProblem(id); + log.info( + { id, durationMs: Date.now() - startTime }, + "getProblemCached finished" + ); + return result; + } catch (error) { + log.error( + { id, durationMs: Date.now() - startTime, error }, + "getProblemCached failed" + ); + throw error; + } + }, + ["getProblem", id], + { + tags: [`problem-${id}`], + } + )() +);