`Modules\Core\Catalog\Services\CollectionService` provides category browsing/nav AND
single-collection lookup for a storefront — `list()`, `getById()`, `getBySlug()` —
all reading directly from the Meilisearch index, mirroring
`Modules\Core\Catalog\Services\ProductService` (see `product-listing.md`) exactly.
---
## Why it reads from the index, not the database
Lunar's own `Lunar\Search\CollectionIndexer` only carries `id`/`name`/`created_at` —
nowhere near enough for a storefront category page or a nav tree.
`Modules\Core\Catalog\Services\CollectionIndexer` extends it to add everything
`CollectionService` needs:
| Field | Source | Notes |
|---|---|---|
| `parent_id` | `$model->parent_id` | Filterable. The nested-set tree's parent pointer — `null` for a top-level collection. |
| `_lft` | `$model->_lft` | Filterable and sortable. The nested-set tree position — lets `CollectionService` resolve tree order without a database read. |
| `ancestors` | `$model->ancestors` | Display only. Array of `{id, name}`, ordered root-first — a breadcrumb (`Home > Apparel > Keychains`) can render directly from a single `getById()`/`getBySlug()` call, no extra queries. Empty array for a top-level collection. |
| `product_count` | Queried from the *product* Meilisearch index at collection-index time | Display only. How many products are in this collection **or any of its descendants** — matches what `ProductService::list(ProductFilters(collectionId: ...))` would return, not just direct assignment. Computed via `Product::search('')->options(['filter' => "collection_ids = \"{id}\""])`, so it depends on the product index already being current — reindex products *before* collections (see "Gotchas" below). |