This commit is contained in:
seb
2026-08-05 19:11:12 +02:00
parent 9fcee1f851
commit bbec785d7c

View File

@@ -76,12 +76,15 @@ export class StorageManager extends ApiClient {
#articleIndexCache = null;
/** @type {Map<string, object>} sku → article detail */
#articlesBySku = new Map();
/** @type {Map<string, Set<() => void>>} sku → refreshers */
/** @type {Map<string, Set<(article: object) => void>>} sku → article listeners */
#articleWatchers = new Map();
/** @type {Map<string, Set<() => void>>} sku → history refreshers */
#historyWatchers = new Map();
constructor(baseUrl = window.location.origin) {
super(baseUrl);
this.#initCaches();
this.article.watch = (sku, onArticle) => this.watchArticle(sku, onArticle);
}
/** Settled article detail for sku, or undefined. */
@@ -93,7 +96,12 @@ export class StorageManager extends ApiClient {
setArticle(article) {
requireObject(article, 'article');
requireDefined(article.sku, 'article.sku');
this.#articlesBySku.set(String(article.sku), article);
const key = String(article.sku);
this.#articlesBySku.set(key, article);
const watchers = this.#articleWatchers.get(key);
if (watchers) {
for (const onArticle of watchers) onArticle(article);
}
return article;
}
@@ -108,6 +116,42 @@ export class StorageManager extends ApiClient {
return this.setArticle(res.article);
}
/**
* Subscribe to article detail updates (local setArticle + SSE `article-<sku>`).
* @param {string} sku
* @param {(article: object) => void} onArticle
* @returns {{ article: object | null, unwatch: () => void }}
*/
watchArticle(sku, onArticle) {
const key = String(sku);
const needListen = !this.#articleWatchers.has(key) && !this.#historyWatchers.has(key);
let watchers = this.#articleWatchers.get(key);
if (!watchers) {
watchers = new Set();
this.#articleWatchers.set(key, watchers);
}
watchers.add(onArticle);
if (needListen) {
this.#events?.addEventListener(`article-${key}`, this.#onArticleSignal);
}
return {
article: this.getArticle(key) ?? null,
unwatch: () => {
const set = this.#articleWatchers.get(key);
if (!set) return;
set.delete(onArticle);
if (set.size === 0) {
this.#articleWatchers.delete(key);
if (!this.#historyWatchers.has(key)) {
this.#events?.removeEventListener(`article-${key}`, this.#onArticleSignal);
}
}
},
};
}
/**
* Parsed category map + root id from settled caches, or null if any piece missing.
* @returns {{ categories: object, rootCategoryId: unknown } | null}
@@ -153,13 +197,17 @@ export class StorageManager extends ApiClient {
notifyError(err);
});
const needListen = !this.#articleWatchers.has(key) && !this.#historyWatchers.has(key);
let watchers = this.#historyWatchers.get(key);
if (!watchers) {
watchers = new Set();
this.#historyWatchers.set(key, watchers);
this.#events?.addEventListener(`article-${key}`, this.#onArticleHistorySignal);
}
watchers.add(run);
if (needListen) {
this.#events?.addEventListener(`article-${key}`, this.#onArticleSignal);
}
run();
@@ -170,16 +218,22 @@ export class StorageManager extends ApiClient {
set.delete(run);
if (set.size === 0) {
this.#historyWatchers.delete(key);
this.#events?.removeEventListener(`article-${key}`, this.#onArticleHistorySignal);
if (!this.#articleWatchers.has(key)) {
this.#events?.removeEventListener(`article-${key}`, this.#onArticleSignal);
}
}
};
}
#onArticleHistorySignal = (event) => {
#onArticleSignal = (event) => {
const sku = event.type.slice('article-'.length);
const watchers = this.#historyWatchers.get(sku);
if (!watchers) return;
for (const run of watchers) run();
if (this.#articleWatchers.has(sku)) {
void this.loadArticle(sku).catch((err) => notifyError(err));
}
const historyWatchers = this.#historyWatchers.get(sku);
if (historyWatchers) {
for (const run of historyWatchers) run();
}
};
/** Patch local articleIndex name if already loaded; no-op otherwise. */
@@ -261,8 +315,11 @@ export class StorageManager extends ApiClient {
console.error('[storageMgr] articles SSE refresh failed', err);
});
});
for (const sku of this.#historyWatchers.keys()) {
es.addEventListener(`article-${sku}`, this.#onArticleHistorySignal);
for (const sku of new Set([
...this.#articleWatchers.keys(),
...this.#historyWatchers.keys(),
])) {
es.addEventListener(`article-${sku}`, this.#onArticleSignal);
}
es.onerror = () => {
console.error('[storageMgr] events stream error');