Search & Categories
Full-Text Search
Product search runs server-side over a SQLite FTS5 index (product_search_fts). The catalog never ships to the client.
The index is populated by src/lib/server/services/reindex.ts and kept stock/price-fresh by SQL triggers (migration drizzle/0003).
Listing Pages
listProducts(options) in src/lib/server/services/product-search.ts is the single entry point for the storefront listing pages (products, category, collection). It handles free-text FTS search, facet filters with disjunctive counts, price range, sort, and pagination — all server-side.
const result = await listProducts({
search: "cotton shirt",
facets: { color: ["red", "blue"], size: ["m"] },
productIds, // scope to a category's / collection's products
priceMin, priceMax, // cents, applied to a product's lowest price
sort: "newest", // newest | price-asc | price-desc | name-asc | name-desc
page: 1,
limit: 40,
customerId // applies customer-group pricing to returned prices
});
// → { items, pagination, facetCounts, baseFacetCounts, priceRange }facetCounts are disjunctive (each active facet group is counted with its own filter removed, so alternatives stay visible); baseFacetCounts ignore facet/price filters and decide which values to show at all.
parseListingParams(url) reads q, sort, page, facet_*, and price_min/price_max from the request URL into a listProducts options object.
Each listing page (src/routes/(storefront)/products, category/[...path], collections/[id]/[slug]) calls listProducts in its load and passes a listing prop to ProductListing.svelte.
Header Search
quickSearchProducts(term, limit?) powers the header search-as-you-type — a lightweight top-N ranked name search. It is exposed as the quickSearch query in src/lib/remote/search.remote.ts and consumed by SearchBar.svelte with a debounce.
export const quickSearch = query(schema, (term) => quickSearchProducts(term));Reindexing
await reindexProduct(productId); // re-index one product (removes it if deleted)
await reindexAll(); // rebuild the whole indexCategories
Categories form a hierarchical tree using a parentId self-reference. Each category has a position for ordering and a taxCode.
Tree Structure
Electronics (root)
├── Phones
│ ├── Smartphones
│ └── Accessories
└── LaptopsKey Operations
const tree = await categoryService.getTree();
const crumbs = await categoryService.getBreadcrumbs(categoryId);
// → [{ id, slug, name }, ...] from root to the category
// All product ids in a category, including descendants
const productIds = await categoryService.getAllProductIds(categoryId);Category listing pages feed getAllProductIds into listProducts as the productIds scope.
Tax Codes
categoryService.getProductTaxCode(productId) returns the taxCode of the product’s first assigned category, or "standard" if it has none. getProductTaxCode and getProductCategories use plain joins over product_categories.