All files / dal pros-helpers.ts

75% Statements 15/20
33.33% Branches 2/6
100% Functions 5/5
83.33% Lines 15/18

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                    7x         7x   7x 18x   29x                       3x 3x 3x     3x           7x                                 4x 4x 4x 12x           4x          
// Pure helpers + constants shared by ProsDal. Extracted from pros.dal.ts to
// keep the class file focused on Drizzle method implementations.
 
import { sql } from "drizzle-orm";
import * as schema from "../db/schema";
import type { Pro } from "../db/schema";
 
// Cap each comma-separated multi-select filter. Without this, a caller could
// send `?businessTypeIds=a,b,c,...` with thousands of values, producing a
// WHERE clause that burns Worker CPU and risks D1 binding limits.
export const MAX_MULTI_SELECT = 20;
 
// D1 caps each statement at 100 bound parameters. We chunk `inArray` lookups
// at 90 (matching `admin/search.routes.ts`) to leave headroom for any other
// parameters on the same WHERE.
export const PRO_ID_CHUNK = 90;
 
export const parseIdList = (raw: string): string[] =>
	raw
		.split(",")
		.map((id) => id.trim())
		.filter(Boolean)
		.slice(0, MAX_MULTI_SELECT);
 
// Build an `EXISTS (SELECT 1 FROM <junction> WHERE pro_id = pros.id AND <col>
// IN (...))` predicate for filtering pros by a junction table. Returns null
// when the input has no usable IDs.
export function buildJunctionExists(
	junctionTable: "pro_service_categories" | "pro_material_tags" | "pro_service_areas",
	idColumn: string,
	ids: string[],
) {
	Iif (ids.length === 0) return null;
	const placeholders = sql.join(
		ids.map((id) => sql`${id}`),
		sql`, `,
	);
	return sql`EXISTS (SELECT 1 FROM ${sql.raw(junctionTable)} j WHERE j.pro_id = ${schema.pros.id} AND j.${sql.raw(idColumn)} IN (${placeholders}))`;
}
 
// Junction-derived array fields stripped from inserts/updates and applied
// separately. Keeps one list of "these are not real columns" so the strip
// helpers and the write-fanout helpers can't drift apart.
export const JUNCTION_ARRAY_FIELDS = [
	"serviceCategoryIds",
	"materialTagIds",
	"serviceAreaIds",
] as const satisfies readonly (keyof Pro)[];
 
export type JunctionArrayField = (typeof JUNCTION_ARRAY_FIELDS)[number];
 
export type JunctionArrays = {
	serviceCategoryIds?: string[];
	materialTagIds?: string[];
	serviceAreaIds?: string[];
};
 
export function splitJunctionArrays<T extends JunctionArrays>(
	input: T,
): { row: Omit<T, JunctionArrayField>; junctions: JunctionArrays } {
	const junctions: JunctionArrays = {};
	const row = { ...input } as Record<string, unknown>;
	for (const field of JUNCTION_ARRAY_FIELDS) {
		Iif (field in row) {
			const val = row[field];
			if (Array.isArray(val)) junctions[field] = val as string[];
			delete row[field];
		}
	}
	return {
		row: row as Omit<T, JunctionArrayField>,
		junctions,
	};
}