Files
GMW/packages/shared/src/utils/index.ts
T
MythEclipseandClaude Opus 4.8 07032ab521 refactor: atomic, DRY, and logging improvements across codebase
- Split llmModerationClient.ts (2170 lines) into 5 focused sub-modules
- Split aiAnalyzer.ts (1282 lines) into 4 modular pipelines
- Split messages.db.ts (826 lines) into 5 domain-specific modules
- Moved shared schema to @bete/shared, eliminated backend duplication
- Added createChildLogger to all voice-recording and AI moderation modules
- Extracted tryCommandThenFallback, normalizeMediaState, DEFAULT_VOICE_STATUS
- Created shared pagination.ts utility, eliminated 5+ cursor-pagination duplications
- Created shared messageMapper.ts for row mapping
- Standardized backend error handling with asyncHandler
- Added frontend createLogger utility and useAsyncAction hook
- Added structured logging to frontend hooks, socket, and API client

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:46:08 +07:00

78 lines
2.1 KiB
TypeScript

// Utility functions shared across services
export function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export * from "./pagination.js";
// ---------------------------------------------------------------------------
// Retry with exponential backoff
// ---------------------------------------------------------------------------
export async function retryWithBackoff<T>(
fn: () => Promise<T>,
options: {
/** Number of retry attempts (default: 3) */
retries?: number;
/** Initial delay in ms (default: 1000) */
minTimeout?: number;
/** Maximum delay in ms (default: 30000) */
maxTimeout?: number;
/** Multiplication factor for each retry (default: 2) */
factor?: number;
/** Optional AbortSignal to cancel retries */
signal?: AbortSignal;
} = {},
): Promise<T> {
const {
retries = 3,
minTimeout = 1_000,
maxTimeout = 30_000,
factor = 2,
signal,
} = options;
let lastError: Error | undefined;
for (let attempt = 0; attempt <= retries; attempt++) {
if (signal?.aborted) {
const err = new Error("Aborted");
err.name = "AbortError";
throw err;
}
try {
return await fn();
} catch (err) {
lastError = err instanceof Error ? err : new Error(String(err));
if (lastError.name === "AbortError") {
throw lastError;
}
if (attempt === retries) break;
const backoff = Math.min(
minTimeout * factor ** attempt + Math.random() * 100,
maxTimeout,
);
await new Promise<void>((resolve, reject) => {
let timeoutId: NodeJS.Timeout;
const onAbort = () => {
clearTimeout(timeoutId);
const abortErr = new Error("Aborted");
abortErr.name = "AbortError";
reject(abortErr);
};
if (signal?.aborted) return onAbort();
timeoutId = setTimeout(() => {
if (signal) signal.removeEventListener("abort", onAbort);
resolve();
}, backoff);
if (signal) signal.addEventListener("abort", onAbort, { once: true });
});
}
}
throw lastError!;
}