@xec-sh/core
Version:
Universal shell execution engine
174 lines • 6.54 kB
JavaScript
export async function parallel(commands, engine, options = {}) {
const { maxConcurrency = Infinity, stopOnError = false, timeout, onProgress } = options;
const startTime = Date.now();
const results = [];
const succeeded = [];
const failed = [];
const isProcessPromise = (obj) => obj && typeof obj.then === 'function' && 'pipe' in obj && 'nothrow' in obj;
const promises = commands.map(cmd => {
if (isProcessPromise(cmd)) {
return cmd;
}
else {
const normalizedCmd = typeof cmd === 'string' ? { command: cmd } : cmd;
return executeWithTimeout(engine, normalizedCmd, timeout);
}
});
if (maxConcurrency === Infinity) {
const settled = await Promise.allSettled(promises);
for (let i = 0; i < settled.length; i++) {
const result = settled[i];
if (result && result.status === 'fulfilled') {
results.push(result.value);
succeeded.push(result.value);
}
else if (result && result.status === 'rejected') {
results.push(result.reason);
failed.push(result.reason);
if (stopOnError)
break;
}
if (onProgress) {
onProgress(i + 1, commands.length, succeeded.length, failed.length);
}
}
}
else {
const executing = [];
let index = 0;
let shouldStop = false;
async function executeNext() {
if (shouldStop || index >= commands.length)
return;
const currentIndex = index++;
const cmd = commands[currentIndex];
if (!cmd)
return;
try {
let result;
if (isProcessPromise(cmd)) {
result = await cmd;
}
else {
const normalizedCmd = typeof cmd === 'string' ? { command: cmd } : cmd;
result = await executeWithTimeout(engine, normalizedCmd, timeout);
}
results[currentIndex] = result;
succeeded.push(result);
}
catch (error) {
results[currentIndex] = error;
failed.push(error);
if (stopOnError) {
shouldStop = true;
}
}
if (onProgress) {
const completed = succeeded.length + failed.length;
onProgress(completed, commands.length, succeeded.length, failed.length);
}
if (!shouldStop && index < commands.length) {
await executeNext();
}
}
for (let i = 0; i < Math.min(maxConcurrency, commands.length); i++) {
executing.push(executeNext());
}
await Promise.all(executing);
}
return {
results,
succeeded,
failed,
duration: Date.now() - startTime
};
}
async function executeWithTimeout(engine, command, timeout) {
if (!timeout) {
return engine.execute(command);
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const commandWithSignal = { ...command, signal: controller.signal };
return await engine.execute(commandWithSignal);
}
finally {
clearTimeout(timeoutId);
}
}
export class ParallelEngine {
constructor(engine) {
this.engine = engine;
}
async all(commands, options) {
const result = await parallel(commands, this.engine, { ...options, stopOnError: true });
if (result.failed.length > 0) {
throw result.failed[0];
}
return result.succeeded;
}
async settled(commands, options) {
return parallel(commands, this.engine, options);
}
async race(commands) {
const isProcessPromise = (obj) => obj && typeof obj.then === 'function' && 'pipe' in obj && 'nothrow' in obj;
const promises = commands.map(cmd => {
if (isProcessPromise(cmd)) {
return cmd;
}
const normalizedCmd = typeof cmd === 'string' ? { command: cmd } : cmd;
return this.engine.execute(normalizedCmd);
});
return Promise.race(promises);
}
async map(items, fn, options) {
const commands = items.map((item, index) => fn(item, index));
return parallel(commands, this.engine, options);
}
async filter(items, fn, options) {
const isProcessPromise = (obj) => obj && typeof obj.then === 'function' && 'pipe' in obj && 'nothrow' in obj;
const commandsWithItems = items.map((item, index) => ({
item,
command: fn(item, index)
}));
const results = await Promise.allSettled(commandsWithItems.map(({ command }) => {
if (isProcessPromise(command)) {
return command;
}
const normalizedCmd = typeof command === 'string' ? { command } : command;
return this.engine.execute(normalizedCmd);
}));
return commandsWithItems
.filter((_, index) => {
const result = results[index];
return result && result.status === 'fulfilled' && result.value.exitCode === 0;
})
.map(({ item }) => item);
}
async some(commands, options) {
const isProcessPromise = (obj) => obj && typeof obj.then === 'function' && 'pipe' in obj && 'nothrow' in obj;
const promises = commands.map(cmd => {
if (isProcessPromise(cmd)) {
return cmd.then(() => true).catch(() => false);
}
const normalizedCmd = typeof cmd === 'string' ? { command: cmd } : cmd;
return this.engine.execute(normalizedCmd)
.then(() => true)
.catch(() => false);
});
const results = await Promise.race([
Promise.any(promises.map((p, index) => p.then(success => success ? index : Promise.reject()))),
Promise.all(promises).then(() => false)
]);
return typeof results === 'number';
}
async every(commands, options) {
const result = await parallel(commands, this.engine, {
...options,
stopOnError: true
});
return result.failed.length === 0;
}
}
//# sourceMappingURL=parallel.js.map