(function (root, factory) { const api = factory(); if (typeof module !== 'undefined' && module.exports) module.exports = api; else root.createOfflineToday = api; })(typeof self !== 'undefined' ? self : this, function () { 'use strict'; const itemKey = item => [item?.kind, item?.repository, Number(item?.number || 0)].join(':'); function createOfflineToday({ loadDetail, loadSavedDetail, saveDetail, onStatus = () => {}, concurrency = 2, maxItems = 5, }) { let failed = new Set(); let generation = 0; function bounded(items) { return (Array.isArray(items) ? items : []).filter(item => ['issue', 'pull'].includes(item?.kind) && item?.repository && Number(item?.number) > 0 ).slice(0, Math.max(1, maxItems)); } async function run(login, sourceItems, onlyFailed) { login = String(login || '').trim(); const items = bounded(sourceItems); const runGeneration = ++generation; if (!login || !items.length) { failed = new Set(); const empty = { total: items.length, ready: 0, failed: 0, pending: 0 }; onStatus(empty); return empty; } const previousFailed = failed; const savedDetails = new Map(); await Promise.all(items.map(async item => { savedDetails.set(itemKey(item), await loadSavedDetail(login, item)); })); const readyKeys = new Set(items.filter(item => savedDetails.get(itemKey(item))).map(itemKey)); const snapshot = pending => ({ total: items.length, ready: readyKeys.size, failed: failed.size, pending, }); const candidates = items.filter(item => { const key = itemKey(item); if (onlyFailed && !previousFailed.has(key)) return false; const saved = savedDetails.get(key); return onlyFailed || !saved || saved.source_updated_at !== item.updated_at; }); failed = new Set(); onStatus(snapshot(candidates.length)); let cursor = 0; async function worker() { while (cursor < candidates.length && runGeneration === generation) { const item = candidates[cursor++]; try { const detail = await loadDetail(item); if (runGeneration !== generation) return; const saved = await saveDetail(login, item, { ...detail, source_updated_at: item.updated_at }); if (saved === false) throw new Error('Offline detail was not durably admitted.'); readyKeys.add(itemKey(item)); } catch (_error) { if (runGeneration === generation) failed.add(itemKey(item)); } if (runGeneration === generation) { onStatus(snapshot(Math.max(0, candidates.length - cursor))); } } } await Promise.all(Array.from( { length: Math.min(Math.max(1, concurrency), candidates.length) }, worker )); if (runGeneration !== generation) return snapshot(0); const status = snapshot(0); onStatus(status); return status; } return { warm: (login, items) => run(login, items, false), retry: (login, items) => run(login, items, true), cancel() { generation += 1; failed = new Set(); }, failedKeys: () => Array.from(failed), }; } return createOfflineToday; });