Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View 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);
}