Files
jarv.is/lib/data/comments.ts
jake 3baffbbd29 fix: add cacheLife("max") to build-time cached functions and clarify throw-vs-catch caching rule
- Add `cacheLife("max")` to `getLocalImage` (OG image) and `getStaticRoutes` (sitemap) so entries are held as long as possible during prerender instead of expiring at the default TTL
- Update AGENTS.md to distinguish build-time reads (catch inside the cached function, return a fallback, use a short `cacheLife` on the error path) from request-time reads (may throw from the cached function since they never run during prerender)
- Expand the `getCachedComments` comment to explain why throwing is only safe there: a throw inside `"use cache"` during prerender fails the build even if the caller catches it
2026-10-03 09:55:33 -04:00

67 lines
2.3 KiB
TypeScript

import "server-only";
import { desc, eq } from "drizzle-orm";
import { cacheLife, cacheTag } from "next/cache";
import { db } from "@/lib/db";
import * as schema from "@/lib/db/schema";
/** The public profile fields of a commenter (also used for the signed-in user in comment UI) */
export type CommentUser = Pick<typeof schema.user.$inferSelect, "id" | "name" | "image">;
export type CommentWithUser = typeof schema.comment.$inferSelect & {
user: CommentUser;
};
/** Cache tag for a single page's comments; expired by the actions in lib/server/comments.ts */
export const commentsTag = (pageSlug: string) => `comments-${pageSlug}`;
/**
* Shared across server instances since it's read at request time (inside dynamic holes), not at prerender. Throws on
* failure so an error is never cached -- only safe because it never runs during prerender, where a throw inside
* "use cache" fails the build even if caught.
*/
const getCachedComments = async (pageSlug: string): Promise<CommentWithUser[]> => {
"use cache: remote";
cacheLife("hours");
cacheTag(commentsTag(pageSlug));
// Fetch all comments for the page with user details
const commentsWithUsers = await db
.select()
.from(schema.comment)
.innerJoin(schema.user, eq(schema.comment.userId, schema.user.id))
.where(eq(schema.comment.pageSlug, pageSlug))
.orderBy(desc(schema.comment.createdAt));
return commentsWithUsers.map(({ comment, user }) =>
Object.assign(comment, {
user: {
// we're namely worried about keeping the user's email private here, but nothing sensitive is stored in the db
id: user.id,
name: user.name,
image: user.image,
},
}),
);
};
/**
* Retrieves all comments for a given slug, newest first, with each author's public profile fields
*/
export const getComments = async (pageSlug: string): Promise<CommentWithUser[]> => {
try {
return await getCachedComments(pageSlug);
} catch (error) {
console.error("[data/comments] error fetching comments:", error);
return [];
}
};
/**
* Retrieves the number of comments for a given slug (shares getComments' cache entry)
*/
export const getCommentCount = async (pageSlug: string): Promise<number> => {
const comments = await getComments(pageSlug);
return comments.length;
};