89 lines
3.2 KiB
JavaScript
89 lines
3.2 KiB
JavaScript
(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;
|
|
});
|