function requiredString(value, fieldName) { if (typeof value !== "string") { throw new Error(`${fieldName} must be a string.`); } return value; } export function normalizeSearchQuery(value) { return requiredString(value, "Search query") .trim() .normalize("NFKC") .replace(/[^\p{L}\p{N}\s]+/gu, " ") .replace(/\s+/g, " ") .toLowerCase(); } export function createSearchQuery({ query = "", category, conditionId, minPriceMinor, maxPriceMinor, page = 1, limit = 20, includeSold = false, sort = "recent", }) { const text = normalizeSearchQuery(query); if (!Number.isInteger(page) || page < 1) { throw new Error("page must be >= 1"); } if (!Number.isInteger(limit) || limit < 1 || limit > 100) { throw new Error("limit must be between 1 and 100"); } if ( typeof minPriceMinor !== "undefined" && (!Number.isInteger(minPriceMinor) || minPriceMinor < 0) ) { throw new Error("minPriceMinor must be a non-negative integer"); } if ( typeof maxPriceMinor !== "undefined" && (!Number.isInteger(maxPriceMinor) || maxPriceMinor < 0) ) { throw new Error("maxPriceMinor must be a non-negative integer"); } if ( typeof minPriceMinor !== "undefined" && typeof maxPriceMinor !== "undefined" && minPriceMinor > maxPriceMinor ) { throw new Error("minPriceMinor must be <= maxPriceMinor"); } if (!["recent", "updated", "name_asc", "name_desc", "price_low", "price_high"].includes(sort)) { throw new Error("sort must be recent, updated, name_asc, name_desc, price_low, or price_high"); } return { text, ...(typeof category === "string" && category.trim().length > 0 ? { category: category.trim() } : {}), ...(typeof conditionId === "string" && conditionId.trim().length > 0 ? { conditionId: conditionId.trim() } : {}), ...(typeof minPriceMinor === "number" ? { minPriceMinor } : {}), ...(typeof maxPriceMinor === "number" ? { maxPriceMinor } : {}), page, limit, includeSold, ...(sort !== "recent" ? { sort } : {}), }; } export function buildSearchFilters({ includeSold = false, status = "AVAILABLE", sort = ["recently_added"], }) { return { status: includeSold ? "ALL" : status, includeSold, sort, }; }