All files / lib/search first-covers.ts

100% Statements 9/9
100% Branches 4/4
100% Functions 1/1
100% Lines 8/8

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39            3x                     61x 61x 31x                     31x 6x 4x       61x    
import { and, desc, eq, inArray, isNotNull } from "drizzle-orm";
import type { getDb } from "../../db";
import { projects } from "../../db/schema/projects";
 
// D1 allows 100 bound parameters per statement; the WHERE below adds one
// (status), so 90 ids per query leaves headroom.
const PRO_ID_CHUNK = 90;
 
/**
 * Newest published project cover per pro, for the search index. Shared by the
 * hourly cron and the admin rebuild route; chunked because an unchunked
 * `inArray` fails ("too many SQL variables") once the catalog passes ~98 pros.
 */
export async function loadFirstCoverByProId(
	db: ReturnType<typeof getDb>,
	proIds: string[],
): Promise<Map<string, string>> {
	const firstCoverByProId = new Map<string, string>();
	for (let i = 0; i < proIds.length; i += PRO_ID_CHUNK) {
		const rows = await db
			.select({ proId: projects.proId, coverImage: projects.coverImage })
			.from(projects)
			.where(
				and(
					inArray(projects.proId, proIds.slice(i, i + PRO_ID_CHUNK)),
					eq(projects.status, "published"),
					isNotNull(projects.coverImage),
				),
			)
			.orderBy(desc(projects.dateCreated));
		for (const row of rows) {
			if (row.coverImage && !firstCoverByProId.has(row.proId)) {
				firstCoverByProId.set(row.proId, row.coverImage);
			}
		}
	}
	return firstCoverByProId;
}