79 lines
2.8 KiB
JavaScript
79 lines
2.8 KiB
JavaScript
(function (root, factory) {
|
|
const api = factory();
|
|
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
else root.createTodayReadiness = api;
|
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
'use strict';
|
|
|
|
return function createTodayReadiness({ inspect, onOpen = () => {}, onGate = () => {} }) {
|
|
let pending = null;
|
|
let generation = 0;
|
|
|
|
async function classify(item) {
|
|
if (item?.kind !== 'issue') return { status:'ready', dependencies:[] };
|
|
try {
|
|
const detail = await inspect(item);
|
|
if (detail?.available !== true) return { status:'unknown', dependencies:[] };
|
|
const dependencies = Array.isArray(detail.dependencies)
|
|
? detail.dependencies.filter(dependency => dependency?.state === 'open')
|
|
: [];
|
|
return { status:dependencies.length ? 'blocked' : 'ready', dependencies };
|
|
} catch (_error) {
|
|
return { status:'unknown', dependencies:[] };
|
|
}
|
|
}
|
|
|
|
async function run(action, items, target = null) {
|
|
const request = ++generation;
|
|
const ordered = Array.isArray(items) ? items.slice() : [];
|
|
const preferred = target || ordered[0] || null;
|
|
if (!preferred) return 'empty';
|
|
const preferredIndex = ordered.indexOf(preferred);
|
|
const candidates = preferredIndex >= 0
|
|
? ordered.slice(preferredIndex).concat(ordered.slice(0, preferredIndex))
|
|
: [preferred].concat(ordered);
|
|
const states = [];
|
|
for (const item of candidates) states.push({ item, ...(await classify(item)) });
|
|
if (request !== generation) return 'superseded';
|
|
const targetState = states[0];
|
|
if (targetState.status === 'ready') {
|
|
pending = null;
|
|
onOpen(action, preferred);
|
|
return 'opened';
|
|
}
|
|
const nextReadyState = states.slice(1).find(state => state.status === 'ready');
|
|
pending = {
|
|
action,
|
|
items:ordered,
|
|
target:preferred,
|
|
status:targetState.status,
|
|
dependencies:targetState.dependencies,
|
|
nextReady:nextReadyState?.item || null,
|
|
};
|
|
onGate({ ...pending, items:pending.items.slice() });
|
|
return 'gated';
|
|
}
|
|
|
|
function open(item) {
|
|
if (!pending || !item) return false;
|
|
const action = pending.action;
|
|
pending = null;
|
|
generation += 1;
|
|
onOpen(action, item);
|
|
return true;
|
|
}
|
|
|
|
return {
|
|
run,
|
|
retry: () => pending ? run(pending.action, pending.items, pending.target) : Promise.resolve('closed'),
|
|
startNextReady: () => open(pending?.nextReady),
|
|
workAnyway: () => open(pending?.target),
|
|
cancel() {
|
|
generation += 1;
|
|
pending = null;
|
|
},
|
|
snapshot: () => pending ? { ...pending, items:pending.items.slice() } : { open:false },
|
|
};
|
|
};
|
|
});
|