From bbec785d7cbf60670f1a30dcf8972e400f32abd6 Mon Sep 17 00:00:00 2001 From: seb Date: Wed, 5 Aug 2026 19:11:12 +0200 Subject: [PATCH] i --- src/api/StorageManager.js | 77 ++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/src/api/StorageManager.js b/src/api/StorageManager.js index 6d4e49c..c23eaa6 100644 --- a/src/api/StorageManager.js +++ b/src/api/StorageManager.js @@ -76,12 +76,15 @@ export class StorageManager extends ApiClient { #articleIndexCache = null; /** @type {Map} sku → article detail */ #articlesBySku = new Map(); - /** @type {Map void>>} sku → refreshers */ + /** @type {Map void>>} sku → article listeners */ + #articleWatchers = new Map(); + /** @type {Map 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-`). + * @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');