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