automagik-genie
Version:
Self-evolving AI agent orchestration framework with Model Context Protocol support
173 lines (172 loc) • 6.53 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimeoutError = void 0;
exports.sleep = sleep;
exports.retryWithBackoff = retryWithBackoff;
exports.withTimeout = withTimeout;
exports.runSequential = runSequential;
/**
* Delays execution for a specified number of milliseconds.
*
* @param {number} ms Number of milliseconds to wait.
* @returns {Promise<void>} Resolves after the delay.
* @example
* ```ts
* await sleep(100); // pauses execution for 100ms
* ```
*/
function sleep(ms) {
if (!Number.isFinite(ms) || ms < 0) {
throw new RangeError('sleep: ms must be a non-negative finite number');
}
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Error thrown when an operation exceeds the configured timeout window.
*/
class TimeoutError extends Error {
constructor(message) {
super(message);
this.name = 'TimeoutError';
}
}
exports.TimeoutError = TimeoutError;
function normalizeError(reason) {
if (reason instanceof Error) {
return reason;
}
const errorMessage = typeof reason === 'string'
? reason
: `Non-Error rejection: ${JSON.stringify(reason)}`;
const error = new Error(errorMessage);
error.name = 'NonErrorThrown';
return error;
}
/**
* Executes a promise factory function with an exponential backoff retry strategy.
*
* @template T The expected return type of the promise.
* @param {() => Promise<T>} fn The function that returns the promise to execute.
* @param {number} [maxAttempts=3] The maximum number of attempts before throwing the final error.
* @param {number} [baseDelay=1000] The base delay (in milliseconds) used for the backoff calculation.
* @returns {Promise<T>} A promise that resolves with the successful result or rejects after max attempts.
* @throws {TypeError | RangeError} When inputs are invalid.
* @example
* ```ts
* const result = await retryWithBackoff(() => fetchData(), 5, 200);
* ```
*/
async function retryWithBackoff(fn, maxAttempts = 3, baseDelay = 1000) {
if (typeof fn !== 'function') {
throw new TypeError('retryWithBackoff: fn must be a function that returns a promise');
}
if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
throw new RangeError('retryWithBackoff: maxAttempts must be an integer greater than 0');
}
if (!Number.isFinite(baseDelay) || baseDelay < 0) {
throw new RangeError('retryWithBackoff: baseDelay must be a non-negative finite number');
}
let attempt = 0;
let lastError;
while (attempt < maxAttempts) {
attempt += 1;
try {
return await fn();
}
catch (error) {
lastError = normalizeError(error);
if (attempt >= maxAttempts) {
break;
}
const delay = baseDelay * Math.pow(2, attempt - 1);
await sleep(delay);
}
}
throw lastError ?? new Error('retryWithBackoff: operation failed without providing an error');
}
/**
* Wraps a promise with a timeout. Rejects if the promise takes longer than the specified duration.
*
* @template T The expected return type of the promise.
* @param {Promise<T>} promise The promise to wrap.
* @param {number} ms The timeout duration in milliseconds.
* @param {Error} [timeoutError] An optional custom error to throw on timeout. Defaults to an internal TimeoutError.
* @returns {Promise<T>} A promise that resolves with the result of the wrapped promise or rejects on timeout.
* @throws {RangeError} When `ms` is not a positive finite number.
* @example
* ```ts
* const data = await withTimeout(fetchData(), 5000);
* ```
*/
function withTimeout(promise, ms, timeoutError) {
if (!(promise instanceof Promise)) {
throw new TypeError('withTimeout: promise must be a Promise instance');
}
if (!Number.isFinite(ms) || ms <= 0) {
throw new RangeError('withTimeout: ms must be a positive finite number');
}
let timer;
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => {
reject(timeoutError ?? new TimeoutError(`Operation timed out after ${ms} ms`));
}, ms);
});
return Promise.race([promise, timeoutPromise]).finally(() => {
if (timer !== undefined) {
clearTimeout(timer);
}
});
}
/**
* Executes an array of asynchronous functions (promise factories) sequentially.
*
* @template T The expected return type of each promise.
* @param {Array<() => Promise<T>>} promiseFactories An array of functions that each return a promise.
* @param {boolean} [stopOnError=true] If true, stops immediately and rejects on the first error.
* @returns {Promise<T[] | SequentialSettlement<T>[]>} A promise with the aggregated results.
* - If stopOnError=true (default), resolves to T[] or rejects with the first error.
* - If stopOnError=false, resolves to an array of settlement objects (similar to Promise.allSettled).
* @throws {TypeError} When inputs are invalid.
* @example
* ```ts
* const tasks = [() => fetchUser(), () => fetchOrders()];
* const results = await runSequential(tasks);
* ```
*/
async function runSequential(promiseFactories, stopOnError = true) {
if (!Array.isArray(promiseFactories)) {
throw new TypeError('runSequential: promiseFactories must be an array of functions');
}
if (typeof stopOnError !== 'boolean') {
throw new TypeError('runSequential: stopOnError must be a boolean value');
}
const results = [];
const settlements = [];
for (const factory of promiseFactories) {
if (typeof factory !== 'function') {
const typeError = new TypeError('runSequential: each entry in promiseFactories must be a function');
if (stopOnError) {
throw typeError;
}
settlements.push({ status: 'rejected', reason: typeError });
continue;
}
try {
const value = await factory();
if (stopOnError) {
results.push(value);
}
else {
settlements.push({ status: 'fulfilled', value });
}
}
catch (error) {
const normalized = normalizeError(error);
if (stopOnError) {
throw normalized;
}
settlements.push({ status: 'rejected', reason: normalized });
}
}
return stopOnError ? results : settlements;
}