/** * @file createListAutomat.js * @description Factory function for company-scoped list automats with * deduplicated `load()`, silent `reload()`, and automatic session synchronization. * * Upstream Triggers: * - `authAutomat`: Subscribes each created list automat to auth state — calls * `actions.load()` on `'authed'` with a `selectedCompanyId`, and `actions.reset()` on `'anon'`. * * Used by: * - `deliveryAutomat`, `quoteAutomat`, `orderAutomat`, * `entityAutomat`, `customergroupAutomat`, `numberAutomat`. * * NOT used by `articleIndexAutomat`: that store is derived from the loaded * category views rather than fetched from a whole-company endpoint. */ import { Automat } from 'react-automat'; import authAutomat from './authAutomat'; /** * @typedef {Object} ListAutomatState * @property {Array} items Loaded records (empty until the first success). * @property {'idle'|'loading'|'ready'|'failed'} status Load lifecycle state. * @property {Error|string|null} error Last load error. */ /** * Creates a company-scoped list store following the brand/unit/warehouse * pattern: `load()` deduplicates concurrent callers, `reload()` refreshes * silently, and `reset()` clears on logout. * * This replaces the stale-while-revalidate list caches that previously lived * in `StorageManager` (articleIndex, delivery, quote, order, entity, * customergroup, number). * * @template T * @param {object} options Factory options. * @param {string} options.name Automat registry name (also used for logging). * @param {() => Promise>} options.fetch Loads the full record list. * @param {(items: Array) => Array} [options.sort] Optional stable ordering. * @returns {Automat} Configured list automat instance. */ export function createListAutomat({ name, fetch: fetchItems, sort }) { const initialState = { items: [], status: 'idle', error: null }; let automat; /** In-flight `load()` promise so concurrent consumers share one request. */ let inflightLoad = null; /** * Fetches and normalizes the record list. * * @returns {Promise>} Records from the backend. */ const fetchRecords = async () => { const items = await fetchItems(); return sort ? sort(items) : items; }; const actions = { /** * Loads the list, deduplicating concurrent callers. * * @returns {Promise>} Loaded records. */ load() { if (inflightLoad) return inflightLoad; inflightLoad = (async () => { automat.setState({ status: 'loading', error: null }); try { const items = await fetchRecords(); automat.setState({ items, status: 'ready', error: null }); return items; } catch (error) { automat.setState({ status: 'failed', error }); throw error; } finally { inflightLoad = null; } })(); return inflightLoad; }, /** * Silently re-fetches the list while keeping the current state, so * subscribers do not flash a loading state. * * @returns {Promise>} Fresh records. */ async reload() { try { const items = await fetchRecords(); automat.setState({ items, status: 'ready', error: null }); return items; } catch (error) { automat.setState({ error }); throw error; } }, /** * Replaces the cached list without a round-trip. * * @param {Array} items Records to cache. * @returns {Array} Cached records. */ replace(items) { automat.setState({ items, status: 'ready', error: null }); return items; }, /** * Re-fetches the list once the selected company is known. * * @returns {Promise>} Loaded records. */ ensureLoaded() { if (automat.state.status === 'ready') { return Promise.resolve(automat.state.items); } return actions.load(); }, /** Reset to idle state (called on logout). */ reset() { inflightLoad = null; automat.setState(initialState); }, }; automat = new Automat(initialState, actions, { name }); // Load on session/company establishment, reset on logout. authAutomat.subscribe((auth) => { const companyId = auth.user?.selectedCompanyId; if (auth.status === 'authed' && companyId) { void actions.load().catch(() => {}); } else if (auth.status === 'anon') { actions.reset(); } }); return automat; } /** * Selects the record list of a list automat. * * @param {Automat} automat List automat. * @returns {Array} Current records. */ export function selectListItems(automat) { return automat.state.items; } /** * Selects the load status of a list automat. * * @param {Automat} automat List automat. * @returns {'idle'|'loading'|'ready'|'failed'} Current status. */ export function selectListStatus(automat) { return automat.state.status; }