chore: initialize recovered claude workspace
This commit is contained in:
84
src/utils/sleep.ts
Normal file
84
src/utils/sleep.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Abort-responsive sleep. Resolves after `ms` milliseconds, or immediately
|
||||
* when `signal` aborts (so backoff loops don't block shutdown).
|
||||
*
|
||||
* By default, abort resolves silently; the caller should check
|
||||
* `signal.aborted` after the await. Pass `throwOnAbort: true` to have
|
||||
* abort reject — useful when the sleep is deep inside a retry loop
|
||||
* and you want the rejection to bubble up and cancel the whole operation.
|
||||
*
|
||||
* Pass `abortError` to customize the rejection error (implies
|
||||
* `throwOnAbort: true`). Useful for retry loops that catch a specific
|
||||
* error class (e.g. `APIUserAbortError`).
|
||||
*/
|
||||
export function sleep(
|
||||
ms: number,
|
||||
signal?: AbortSignal,
|
||||
opts?: { throwOnAbort?: boolean; abortError?: () => Error; unref?: boolean },
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Check aborted state BEFORE setting up the timer. If we defined
|
||||
// onAbort first and called it synchronously here, it would reference
|
||||
// `timer` while still in the Temporal Dead Zone.
|
||||
if (signal?.aborted) {
|
||||
if (opts?.throwOnAbort || opts?.abortError) {
|
||||
void reject(opts.abortError?.() ?? new Error('aborted'))
|
||||
} else {
|
||||
void resolve()
|
||||
}
|
||||
return
|
||||
}
|
||||
const timer = setTimeout(
|
||||
(signal, onAbort, resolve) => {
|
||||
signal?.removeEventListener('abort', onAbort)
|
||||
void resolve()
|
||||
},
|
||||
ms,
|
||||
signal,
|
||||
onAbort,
|
||||
resolve,
|
||||
)
|
||||
function onAbort(): void {
|
||||
clearTimeout(timer)
|
||||
if (opts?.throwOnAbort || opts?.abortError) {
|
||||
void reject(opts.abortError?.() ?? new Error('aborted'))
|
||||
} else {
|
||||
void resolve()
|
||||
}
|
||||
}
|
||||
signal?.addEventListener('abort', onAbort, { once: true })
|
||||
if (opts?.unref) {
|
||||
timer.unref()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function rejectWithTimeout(reject: (e: Error) => void, message: string): void {
|
||||
reject(new Error(message))
|
||||
}
|
||||
|
||||
/**
|
||||
* Race a promise against a timeout. Rejects with `Error(message)` if the
|
||||
* promise doesn't settle within `ms`. The timeout timer is cleared when
|
||||
* the promise settles (no dangling timer) and unref'd so it doesn't
|
||||
* block process exit.
|
||||
*
|
||||
* Note: this doesn't cancel the underlying work — if the promise is
|
||||
* backed by a runaway async operation, that keeps running. This just
|
||||
* returns control to the caller.
|
||||
*/
|
||||
export function withTimeout<T>(
|
||||
promise: Promise<T>,
|
||||
ms: number,
|
||||
message: string,
|
||||
): Promise<T> {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
// eslint-disable-next-line no-restricted-syntax -- not a sleep: REJECTS after ms (timeout guard)
|
||||
timer = setTimeout(rejectWithTimeout, ms, reject, message)
|
||||
if (typeof timer === 'object') timer.unref?.()
|
||||
})
|
||||
return Promise.race([promise, timeoutPromise]).finally(() => {
|
||||
if (timer !== undefined) clearTimeout(timer)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user