6.8 KiB
categoryViewAutomat
Source File:
src/automats/categoryViewAutomat.js
Registration Key:'category-views'(Automat.get('category-views')/window.__AUTOMATS__.get('category-views'))
Related:categoryAutomat— owns the category tree this module resolves against.
1. Overview & Purpose
categoryViewAutomat owns the per-category article views: a KeyedAutomat (categoryViews) indexed by route slug path ('' for root, 'getraenke', 'getraenke/softdrinks'). Each entry holds one category's articles plus its child categories.
This module was extracted from categoryAutomat.js so the tree and the views can evolve independently. The dependency stays one-directional:
graph LR
CA[categoryAutomat<br/>tree + mutations] -->|attachCategoryTree| CV[categoryViewAutomat<br/>KeyedAutomat views]
CV -->|loadAllCategoryViews| API[/api/category/:id/articles]
CV -->|collectLoadedCategoryViewData| AI[articleIndexAutomat<br/>derived search index]
categoryAutomat injects itself via attachCategoryTree(automat); categoryViewAutomat never imports the tree, which avoids a circular import.
Core Responsibilities
- Slug → Category Resolution: Resolves a slug path against the loaded tree to a
categoryId. - Per-Category Article Payloads:
GET /api/category/:id/articlesfor the resolved category. - Bulk Warm-Up:
loadAllCategoryViews()loads every category in the tree (concurrency-bounded), triggered bycategoryAutomat.loadTree()the moment the tree succeeds. - Child Category Derivation: Recomputes
childCategoriesfor all loaded views whenever the tree changes. - Incremental Patches: Appends created articles to, and removes deleted articles from, the affected views.
2. View Data Structure
interface CategoryViewData {
/** Cleaned slug path this view is keyed by. */
slug: string;
/** Resolved category id. */
categoryId: number | string;
/** Articles in that category. */
articles: Array<ArticleRow>;
/** Direct child category nodes, sorted by `sort`. */
childCategories: Array<CategoryNode>;
}
Each keyed entry wraps this as ItemState<CategoryViewData>:
{ data, loading, loaded, error }.
3. Loading Strategy
loadAllCategoryViews({ concurrency }): Promise<{ loaded, failed }>
Builds the slug path of every category node from its breadcrumb trail
(categoryViewSlugPath()), de-duplicates them, then loads each with
categoryViews.load(slug).
- Already cached views resolve immediately (
load()short-circuits whenloaded && !error), so the call is idempotent. - Concurrency defaults to 6; failures are captured per category and returned in
failedrather than rejecting the batch. - Triggered by:
categoryAutomat.actions.loadTree()right aftertreeStatus === 'succeeded', and available on demand viacategoryAutomat.actions.loadAllCategoryViews().
reloadLoadedCategoryViews(): Promise<void>
Re-fetches only the already cached views (setDirty(undefined, false, true)), keeping the cached set small for SSE-driven refreshes.
invalidateAllCategoryViews(): Promise<unknown[]>
Background-invalidates all loaded views. Called by sseAutomat on 'category' and 'articles' events.
disposeAllCategoryViewAutomats(): void
Clears every view and frees memory on company switch or logout.
waitForCategoryTree(): Promise<CategoryState>
Resolves once the attached tree reports treeStatus === 'succeeded'. Rejects on tree failure or when authAutomat turns 'anon', so a pending view never hangs forever.
4. Exported API
| Export | Description |
|---|---|
categoryViews (default) |
The KeyedAutomat keyed by slug path. |
attachCategoryTree(automat) |
Injects the tree automat (called once by categoryAutomat). |
categoryViewSlugPath(categories, rootCategoryId, categoryId) |
Breadcrumb-derived slug path usable as a key. |
collectLoadedCategoryViewData() |
All loaded view payloads — the source for the derived article index. |
loadAllCategoryViews({ concurrency }) |
Bulk warm-up of every category in the tree. |
reloadLoadedCategoryViews() |
Re-fetch only the cached views. |
invalidateAllCategoryViews() |
Background-invalidate all loaded views. |
disposeAllCategoryViewAutomats() |
Clear all views. |
getOrCreateCategoryAutomat({ slugPath }) |
Returns the KeyHandle for a slug path. |
refreshCategoryViewChildren(categories) |
Re-derive childCategories for every loaded view. |
appendArticlesToCategoryViews({ categoryId, articles }) |
Append newly created articles. |
removeArticleBySku(sku) |
Strip a deleted article from every view. |
5. Key Handle Usage
import categoryAutomat from '../automats/categoryAutomat';
const handle = categoryAutomat.key('getraenke/softdrinks');
handle.subscribe(this, (item) => ({
articles: item.data?.articles ?? [],
childCategories: item.data?.childCategories ?? [],
}));
const data = await handle.load(); // deduplicated across concurrent mounts
const data2 = handle.read(); // Suspense: throws the in-flight promise when unready
handle.reload();
handle.setDirty();
All four facades (key, read, load, views) are attached to categoryAutomat
as thin delegates to categoryViews, so existing import sites keep working.
6. Cross-Automat Coordination
| Automat | Interaction |
|---|---|
categoryAutomat |
attachCategoryTree(), loadAllCategoryViews() after the tree succeeds, refreshCategoryViewChildren() on every tree mutation, disposeAllCategoryViewAutomats() on reset. |
authAutomat |
waitForCategoryTree() rejects pending views on 'anon'. |
sseAutomat |
invalidateAllCategoryViews() on 'category' / 'articles' events. |
articleIndexAutomat |
Subscribes to categoryViews and re-derives the search index from collectLoadedCategoryViewData(). |
7. Gotchas
- Bulk warm-up cost:
loadTree()now issues one request per category. On very large trees this is many requests —concurrency(default 6) is the throttle to tune. Individual failures are non-fatal. viewsLoading: While it istrue, the tree is ready but the views are still warming. Use it to decide between "render a spinner" and "render an empty grid".- Keys are slug paths, not ids: A rename or move changes the slug, so the view key changes and the old entry should be evicted;
refreshCategoryViewChildren()handles the child lists, but stale keys may linger until the nextdisposeAllCategoryViewAutomats(). - Do not import
categoryAutomatfrom this file — that would create the circular dependency theattachCategoryTree()injection exists to avoid.