Skip to content
UniKVS
Esc
↑↓navigate↵open⌘Jpreview
On this page

@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.

  • name is Retry.
  • isOpen returns inner.isOpen.
  • Retries write, read, exists, delete, copy, move, clear, getWritable, and getReadable; open, close, and onOtherWriteError are only delegated.
  • Performs no data or chunk conversion; preserves the delegated types as-is.

Install with:

npm install @unikvs/retry
pnpm add @unikvs/retry
yarn add @unikvs/retry
bun add @unikvs/retry
nub add @unikvs/retry
aube add @unikvs/retry

Usage

Retry implements IStorage. Pass it to UniKvs with persist().

import { Retry } from "@unikvs/retry";

Constructor arguments:

PropType
innerIStorage

The delegated storage.

TypeIStorage
maxRetries?number

Number of retries after the first attempt. 0 disables retries.

Typenumber
Default3
initialDelayMs?number

First delay (milliseconds).

Typenumber
Default100
maxDelayMs?number

Maximum delay (milliseconds).

Typenumber
Default5000
factor?number

Delay growth factor.

Typenumber
Default2
jitter?boolean

Whether to add jitter to delays.

Typeboolean
Defaulttrue
retryable?RetryablePredicate

Predicate deciding whether an error is retryable. Uses isRetryableError by default.

TypeRetryablePredicate
onRetry?(info: RetryAttemptInfo) => void

Notification called just before a retry.

Type(info: RetryAttemptInfo) => void

The 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.

PropType
operationRetryOperation

Name of the failed operation.

TypeRetryOperation
attemptnumber

Failed attempt count (1-based).

Typenumber
errorunknown

The error that occurred.

Typeunknown
nextDelayMsnumber

Delay (milliseconds) before the next attempt.

Typenumber

isRetryableError

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 copy or move, Retry reproduces the behavior with read, write, and delete.
  • The fallback checks the abort signal after reading and after writing to the destination. A move aborted after the write does not delete the source, so both the source and destination remain.
  • open and close failures are not retried and propagate as-is.
  • Delays grow exponentially up to the cap; when jitter is true they vary between half the cap and the cap.

Errors

This package throws the following errors.

RetryExhaustedError

PropType
operationstring

Name of the failed operation.

Typestring
attemptsnumber

Number of attempts made.

Typenumber

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

PropType
namestring

Name of the invalid option.

Typestring
actualunknown

The given value.

Typeunknown

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

PropType
operationstring

Name of the unimplemented operation.

Typestring
storagestring

Name of the delegated storage.

Typestring

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.

Was this page helpful?