@unikvs/retry
A storage wrapper that retries transient failures with exponential backoff.
Overview
Retry retries transient failures of a delegated storage with exponential backoff, making reads and writes more likely to succeed.
Use it to retry operations failing from flaky networks or temporary overload. Retry eligibility and delays are tunable.
nameisRetry.isOpenreturnsinner.isOpen.- Retries
write,read,exists,delete,copy,move,clear,getWritable, andgetReadable;open,close, andonOtherWriteErrorare only delegated. - Performs no data or chunk conversion; preserves the delegated types as-is.
Install with:
npm install @unikvs/retrypnpm add @unikvs/retryyarn add @unikvs/retrybun add @unikvs/retrynub add @unikvs/retryaube add @unikvs/retryUsage
Retry implements IStorage. Pass it to UniKvs with persist().
import { Retry } from "@unikvs/retry";
Constructor arguments:
innerIStorage
The delegated storage.
IStoragemaxRetries?number
Number of retries after the first attempt. 0 disables retries.
number3initialDelayMs?number
First delay (milliseconds).
number100maxDelayMs?number
Maximum delay (milliseconds).
number5000factor?number
Delay growth factor.
number2jitter?boolean
Whether to add jitter to delays.
booleantrueretryable?RetryablePredicate
Predicate deciding whether an error is retryable. Uses isRetryableError by default.
RetryablePredicateonRetry?(info: RetryAttemptInfo) => void
Notification called just before a retry.
(info: RetryAttemptInfo) => voidThe following example retries transient failures of the delegate.
import { Memory } from "@unikvs/memory";
import { Retry } from "@unikvs/retry";
import * as unikvs from "unikvs";
const kvs = new unikvs.Builder<{ k1: unikvs.PlainValue<string> }>()
.persist(new Retry(new Memory(), { maxRetries: 3 }))
.build();
await kvs.open();
await kvs.set("k1", "hello");
console.log(await kvs.get("k1")); // "hello"
await kvs.close();
Retry
Storage wrapper that retries transient failures with exponential backoff. The delay grows as initialDelayMs * factor^(attempt-1) up to maxDelayMs.
RetryOptions
Options accepted by the Retry constructor. Specify retry counts, delays, and the eligibility predicate.
RetryOperation
Name of a retried operation: "write", "read", "exists", "delete", "copy", "move", "clear", "getWritable", or "getReadable".
RetryablePredicate
Predicate deciding whether an error is retryable.
RetryAttemptInfo
Attempt info reported when a retry happens.
operationRetryOperation
Name of the failed operation.
RetryOperationattemptnumber
Failed attempt count (1-based).
numbererrorunknown
The error that occurred.
unknownnextDelayMsnumber
Delay (milliseconds) before the next attempt.
numberisRetryableError
Default retry eligibility. Permanent failures such as missing keys and abort-driven failures are not retried.
Data types
| Operation | Supported data type |
|---|---|
write/read/copy/move |
Same data type as the delegate |
getWritable/getReadable chunks |
Same chunk type as the delegate |
- All operations preserve the delegate types; no data or chunk conversion is performed.
- Retried operations are reads, writes, existence checks, deletes, copies, moves, clears, and stream acquisitions.
- When the inner storage lacks
copyormove, Retry reproduces the behavior withread,write, anddelete. - The fallback checks the abort signal after reading and after writing to the destination. A
moveaborted after the write does not delete the source, so both the source and destination remain. openandclosefailures are not retried and propagate as-is.- Delays grow exponentially up to the cap; when
jitteristruethey vary between half the cap and the cap.
Errors
This package throws the following errors.
RetryExhaustedError
Meaning
Thrown when the operation still fails after exhausting the maximum attempts. The last error is kept in cause.
How to fix
Inspect the original failure via cause and determine whether it is transient. If it happens often, review maxRetries, delays, and the retryable predicate.
Example
The following example catches RetryExhaustedError.
import { RetryExhaustedError } from "@unikvs/retry";
try {
await kvs.set("k1", "hello");
} catch (ex) {
if (ex instanceof RetryExhaustedError) {
console.log(ex.meta); // { operation: "write", attempts: 4 }
console.log(ex.cause); // last error
} else {
throw ex;
}
}
RetryInvalidOptionError
Meaning
Thrown when invalid options are passed to the constructor: negative or non-integer retry counts, negative delays, or a factor below 1.
How to fix
Check meta.name: use a non-negative integer for maxRetries, non-negative finite numbers for initialDelayMs and maxDelayMs, and a finite number >= 1 for factor.
Example
The following example catches RetryInvalidOptionError.
import { RetryInvalidOptionError } from "@unikvs/retry";
try {
const retry = new Retry(inner, { maxRetries: -1 });
} catch (ex) {
if (ex instanceof RetryInvalidOptionError) {
console.log(ex.meta); // { name: "maxRetries", actual: -1 }
} else {
throw ex;
}
}
RetryUnsupportedOperationError
Meaning
Thrown when the delegated storage does not implement getWritable or getReadable.
How to fix
Check whether the delegate supports stream operations; use plain reads and writes when it does not.
Example
The following example catches RetryUnsupportedOperationError.
import { RetryUnsupportedOperationError } from "@unikvs/retry";
try {
await kvs.stream("logs");
} catch (ex) {
if (ex instanceof RetryUnsupportedOperationError) {
console.log(ex.meta); // { operation: "getReadable", storage: "Bare" }
} else {
throw ex;
}
}
KeyNotFoundError and abort-driven AbortError are not retried by default and propagate as-is.