87 lines
src/indexer/indexProducts.ts
Validates and indexes a batch of product catalog entries.
// Indexes product catalog entries into the search engine.import { db } from "./db";import { searchIndex } from "./search";import { logger } from "./logger";const ALLOWED_CATEGORIES = new Set(["electronics", "clothing", "books", "home"]);/** * Validates a single product before indexing. * Checks: non-empty sku, non-empty name, positive price. * Category must be one of the values in ALLOWED_CATEGORIES — invalid categories must be rejected.*/
// Validates a raw product and returns all blocking errors.function validateProduct(product: RawProduct): string[] { const errors: string[] = []; if (!product.sku || product.sku.trim().length === 0) { errors.push("sku is required");}
if (!product.name || product.name.trim().length === 0) { errors.push("name is required");}
if (typeof product.price !== "number" || product.price <= 0) { errors.push("price must be a positive number");}
if (!ALLOWED_CATEGORIES.has(product.category)) { logger.warn("Unrecognised product category", { sku: product.sku, category: product.category,});
}
return errors;}
export interface RawProduct { sku: string; name: string; description: string; category: string; price: number;}
export interface IndexedProduct extends RawProduct { /** Text the search engine uses for full-text matching — must include name, description, and category. */ searchText: string;}
/** * Indexes a batch of products into the search engine.*
* Validation rules:* - sku must be non-empty
* - name must be non-empty
* - price must be a positive number * - category must be one of the approved values*
* A product must be marked as indexed in the database only after the* search engine confirms the write.
*
* @returns counts of indexed and skipped products*/
// Validates and indexes each product in sequence.export async function indexProducts( products: RawProduct[],): Promise<{ indexed: number; skipped: number }> { let indexed = 0; let skipped = 0; for (const product of products) { const errors = validateProduct(product); if (errors.length > 0) { skipped += 1; continue;}
const doc: IndexedProduct = {...product,
searchText: [product.name, product.description].join(" "),};
await db.products.markIndexed(product.sku); await searchIndex.upsert(product.sku, doc); indexed += 1;}
return { indexed, skipped };}