This commit is contained in:
seb
2026-09-21 20:21:57 +02:00
parent 2046f96768
commit 536e7b1e2a
20 changed files with 3047 additions and 672 deletions

View File

@@ -2,7 +2,7 @@ import { requireArray, requireDefined, requireObject } from '../utils/apiShape';
import { notifyError } from '../utils/notify';
import { parseCategoryTreeResponse } from '../store/categorySlice';
import { ApiClient } from './ApiClient';
import articleAutomat from '../store/articleAutomat';
import artikelAutomat from '../automats/artikelAutomat';
/**
* Stale-while-revalidate cache: readers keep the current promise; update()
@@ -75,8 +75,6 @@ export class StorageManager extends ApiClient {
#events = null;
/** @type {ReturnType<typeof createCache> | null} */
#articleIndexCache = null;
/** @type {Map<string, object>} sku → article detail */
#articlesBySku = new Map();
/** @type {Map<string, Set<(article: object) => void>>} sku → article listeners */
#articleWatchers = new Map();
/** @type {Map<string, Set<() => void>>} sku → history refreshers */
@@ -92,39 +90,41 @@ export class StorageManager extends ApiClient {
this.watchArticleSuppliers(sku, onSuppliers);
}
/** Settled article detail for sku, or undefined. */
/** Cached article detail from the shared artikelAutomat, or null. */
getArticle(sku) {
return this.#articlesBySku.get(String(sku));
return artikelAutomat.state.bySku[String(sku)] ?? null;
}
/** Keep local article detail in sync after GET/PATCH/PUT responses. */
/**
* Keep the shared article cache in sync after GET/PATCH/PUT responses and
* notify any StorageManager-level watchers.
*/
setArticle(article) {
requireObject(article, 'article');
requireDefined(article.sku, 'article.sku');
const key = String(article.sku);
this.#articlesBySku.set(key, article);
// Update automat as well
articleAutomat.actions.setArticle(article);
const stored = artikelAutomat.actions.setArticle(article);
const watchers = this.#articleWatchers.get(key);
if (watchers) {
for (const onArticle of watchers) onArticle(stored);
}
return stored;
}
/**
* GET /api/article/{sku} via the shared cache (deduplicated), notify watchers.
* @param {string} sku
*/
async loadArticle(sku) {
const article = await artikelAutomat.actions.loadArticle(sku);
const watchers = this.#articleWatchers.get(String(sku));
if (watchers) {
for (const onArticle of watchers) onArticle(article);
}
return article;
}
/**
* GET /api/article/{sku}, cache detail, return article.
* @param {string} sku
*/
async loadArticle(sku) {
const res = await this.article.get(sku);
requireObject(res, 'GET /api/article/:sku');
requireObject(res.article, 'article');
return this.setArticle(res.article);
}
/**
* Subscribe to article detail updates (local setArticle + SSE `article-<sku>`).
* @param {string} sku
@@ -334,12 +334,12 @@ export class StorageManager extends ApiClient {
#onArticleSignal = (event) => {
const sku = event.type.slice('article-'.length);
// Update automat on SSE article events
void articleAutomat.actions.loadArticle(sku).catch((err) => notifyError(err));
// Refresh the shared cache; silently when nothing is watching this SKU.
if (this.#articleWatchers.has(sku)) {
void this.loadArticle(sku).catch((err) => notifyError(err));
void artikelAutomat.actions.reloadArticle(sku).catch((err) => notifyError(err));
} else {
void artikelAutomat.actions.reloadArticle(sku).catch(() => {});
}
const historyWatchers = this.#historyWatchers.get(sku);
if (historyWatchers) {
@@ -360,9 +360,9 @@ export class StorageManager extends ApiClient {
);
}
/** Patch local article detail name if already cached; no-op otherwise. */
/** Patch cached article detail name if already cached; no-op otherwise. */
setArticleName(sku, name) {
const current = this.#articlesBySku.get(String(sku));
const current = this.getArticle(sku);
if (!current) return;
this.setArticle({ ...current, name });
}