Skip to Content
FeaturesSearch & Categories

Search & Categories

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.

src/lib/server/services/product-search.ts
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.

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.

src/lib/remote/search.remote.ts
export const quickSearch = query(schema, (term) => quickSearchProducts(term));

Reindexing

src/lib/server/services/product-search.ts
await reindexProduct(productId); // re-index one product (removes it if deleted) await reindexAll(); // rebuild the whole index

Categories

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 └── Laptops

Key Operations

src/lib/server/services/categories.ts
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.

Last updated on