Initial Commit
This commit is contained in:
85
packages/foundation/flow/workflow/src/utils/utils.ts
Normal file
85
packages/foundation/flow/workflow/src/utils/utils.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import type { Clock } from "@suaveplan/types";
|
||||
|
||||
/**
|
||||
* Default clock used by the package when callers do not inject one.
|
||||
* Reading through this indirection (rather than calling `Date.now()` inline)
|
||||
* keeps every timing path test-controllable via the `Clock` injection.
|
||||
*/
|
||||
export const defaultClock: Clock = () => Date.now();
|
||||
|
||||
/**
|
||||
* Generates a unique workflow or step identifier.
|
||||
* Uses a combination of timestamp and random characters.
|
||||
*/
|
||||
export function generateId(prefix = "wf", clock: Clock = defaultClock): string {
|
||||
const timestamp = clock().toString(36);
|
||||
const random = Math.random().toString(36).substring(2, 8);
|
||||
return `${prefix}_${timestamp}_${random}`;
|
||||
}
|
||||
|
||||
/** Returns current timestamp via the supplied clock. */
|
||||
export function now(clock: Clock = defaultClock): number {
|
||||
return clock();
|
||||
}
|
||||
|
||||
/** Returns a promise that resolves after the specified delay. */
|
||||
export function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a promise with a timeout. Rejects if the promise
|
||||
* does not resolve within the specified duration.
|
||||
*/
|
||||
export function withTimeout<T>(
|
||||
promise: Promise<T>,
|
||||
ms: number,
|
||||
message = "Operation timed out"
|
||||
): Promise<T> {
|
||||
// `setTimeout` runs synchronously inside the Promise constructor, so
|
||||
// `timeoutId` is always assigned before `Promise.race` can observe it.
|
||||
let timeoutId: ReturnType<typeof setTimeout> = 0 as unknown as ReturnType<
|
||||
typeof setTimeout
|
||||
>;
|
||||
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
reject(new Error(message));
|
||||
}, ms);
|
||||
});
|
||||
return Promise.race([promise, timeoutPromise]).finally(() => {
|
||||
clearTimeout(timeoutId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the delay for a retry attempt using the specified strategy.
|
||||
*
|
||||
* @param attempt - Current attempt number (1-based).
|
||||
* @param strategy - 'fixed', 'linear', or 'exponential'.
|
||||
* @param baseDelay - Base delay in milliseconds.
|
||||
* @param multiplier - Multiplier for exponential backoff.
|
||||
* @param maxDelay - Maximum delay cap in milliseconds.
|
||||
*/
|
||||
export function calculateRetryDelay(
|
||||
attempt: number,
|
||||
strategy: "fixed" | "linear" | "exponential",
|
||||
baseDelay: number,
|
||||
multiplier = 2,
|
||||
maxDelay = 30000
|
||||
): number {
|
||||
let computed: number;
|
||||
switch (strategy) {
|
||||
case "fixed":
|
||||
computed = baseDelay;
|
||||
break;
|
||||
case "linear":
|
||||
computed = baseDelay * attempt;
|
||||
break;
|
||||
case "exponential":
|
||||
computed = baseDelay * multiplier ** (attempt - 1);
|
||||
break;
|
||||
}
|
||||
return Math.min(computed, maxDelay);
|
||||
}
|
||||
Reference in New Issue
Block a user