i
This commit is contained in:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user