All files / routes/taxonomy helpers.ts

100% Statements 25/25
100% Branches 12/12
100% Functions 8/8
100% Lines 22/22

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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100                                  36x       36x       36x                             13x   10x 6x   10x 10x                       21x 12x   21x 12x 7x   12x   21x         12x                           12x 12x 12x   11x 11x           11x    
// Taxonomy Route Helpers
import { eq, asc, and } from "drizzle-orm";
import type { DrizzleD1Database } from "drizzle-orm/d1";
import type { SQLiteTableWithColumns } from "drizzle-orm/sqlite-core";
import { createDualCache, CACHE_TTL } from "../../lib/cache";
 
/**
 * Query active taxonomy items from a table
 * Standard pattern: isActive = true, ordered by sortOrder
 */
export async function queryActiveTaxonomy<T>(
	// biome-ignore lint/suspicious/noExplicitAny: Accept both schema-typed and untyped DrizzleD1Database
	db: DrizzleD1Database<any>,
	// biome-ignore lint/suspicious/noExplicitAny: Drizzle ORM generic table type requires any for dynamic column access
	table: SQLiteTableWithColumns<any>,
	whereClause?: ReturnType<typeof eq>,
): Promise<T[]> {
	const baseCondition = eq(
		table.isActive as unknown as Parameters<typeof eq>[0],
		true,
	);
	const condition = whereClause
		? and(baseCondition, whereClause)
		: baseCondition;
 
	return db
		.select()
		.from(table)
		.where(condition)
		.orderBy(
			asc(table.sortOrder as unknown as Parameters<typeof asc>[0]),
		) as Promise<T[]>;
}
 
/**
 * Group items by a category field
 */
export function groupByCategory<T extends { category: string }>(
	items: T[],
): Record<string, T[]> {
	return items.reduce(
		(acc: Record<string, T[]>, item) => {
			if (!acc[item.category]) {
				acc[item.category] = [];
			}
			acc[item.category].push(item);
			return acc;
		},
		{} as Record<string, T[]>,
	);
}
 
/**
 * Build hierarchical structure for parent/child items
 */
export function buildHierarchy<
	T extends { id: string; parentId: string | null },
>(items: T[]): (T & { children: T[] })[] {
	const rootItems = items.filter((item) => !item.parentId);
	const childMap = items.reduce(
		(acc: Record<string, T[]>, item) => {
			if (item.parentId) {
				if (!acc[item.parentId]) {
					acc[item.parentId] = [];
				}
				acc[item.parentId].push(item);
			}
			return acc;
		},
		{} as Record<string, T[]>,
	);
 
	return rootItems.map((parent) => ({
		...parent,
		children: childMap[parent.id] || [],
	}));
}
 
/**
 * Dual-layer cached taxonomy fetch (L1 in-memory + L2 KV)
 */
export async function cachedTaxonomyQuery<T>(
	c: { env: CloudflareBindings; executionCtx: ExecutionContext },
	cacheKey: string,
	fetcher: () => Promise<T>,
): Promise<T> {
	const dualCache = createDualCache(c.env.KV_CACHE);
	const cached = await dualCache.get<T>(cacheKey);
	if (cached) return cached;
 
	const data = await fetcher();
	c.executionCtx.waitUntil(
		dualCache.put(cacheKey, data, {
			l1Ttl: CACHE_TTL.TAXONOMY_L1,
			l2Ttl: CACHE_TTL.TAXONOMY_L2,
		}),
	);
	return data;
}