npm package: @lumenlabs-dev/fast-node-retry
Description: Abstraction for exponential and custom retry strategies for failed operations.
Author: LumenLabs Development Team
Latest version: 0.1.0

# fast-node-retry

[![npm version](https://img.shields.io/npm/v/@lumenlabs-dev/fast-node-retry.svg)](https://www.npmjs.com/package/@lumenlabs-dev/fast-node-retry)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue.svg)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A fast, TypeScript-native abstraction for exponential and custom retry strategies for failed operations.

## Installation

```bash
npm install @lumenlabs-dev/fast-node-retry
```

## Current Status

This module has been fully migrated to TypeScript with complete type safety and is production-ready. All tests pass and the API maintains backward compatibility with the original `retry` package.

## Features

- **Full TypeScript Support** - Complete type definitions for enhanced IDE support
- **Performance Optimized** - Faster operations with efficient algorithms
- **Exponential Backoff** - Configurable retry strategies with exponential backoff
- **Forever Retry** - Option to retry indefinitely
- **Custom Timeouts** - Fine-grained control over retry timing
- **Method Wrapping** - Wrap existing functions with retry logic
- **Dual Package** - CommonJS and ESM support

## Tutorial

The example below will retry a potentially failing `dns.resolve` operation
`10` times using an exponential backoff strategy. With the default settings, this
means the last attempt is made after `17 minutes and 3 seconds`.

```javascript
const dns = require('dns');
const retry = require('@lumenlabs-dev/fast-node-retry');

function faultTolerantResolve(address, cb) {
  const operation = retry.operation();

  operation.attempt(function(currentAttempt) {
    dns.resolve(address, function(err, addresses) {
      if (operation.retry(err)) {
        return;
      }

      cb(err ? operation.mainError() : null, addresses);
    });
  });
}

faultTolerantResolve('nodejs.org', function(err, addresses) {
  console.log(err, addresses);
});
```

**With TypeScript:**

```typescript
import * as dns from 'dns';
import * as retry from '@lumenlabs-dev/fast-node-retry';

function faultTolerantResolve(address: string, cb: (err: Error | null, addresses?: string[]) => void): void {
  const operation = retry.operation();

  operation.attempt((currentAttempt) => {
    dns.resolve(address, (err, addresses) => {
      if (operation.retry(err)) {
        return;
      }

      cb(err ? operation.mainError() : null, addresses);
    });
  });
}

faultTolerantResolve('nodejs.org', (err, addresses) => {
  console.log(err, addresses);
});
```

Of course you can also configure the factors that go into the exponential
backoff. See the API documentation below for all available settings.
`currentAttempt` is an int representing the number of attempts so far.

```javascript
const operation = retry.operation({
  retries: 5,
  factor: 3,
  minTimeout: 1000,
  maxTimeout: 60000,
  randomize: true,
});
```

### Example with Promises

```javascript
const retry = require('@lumenlabs-dev/fast-node-retry');

async function fetchWithRetry(url) {
  const operation = retry.operation({
    retries: 3,
    factor: 2,
    minTimeout: 1000,
    maxTimeout: 5000,
  });

  return new Promise((resolve, reject) => {
    operation.attempt(async (currentAttempt) => {
      try {
        console.log(`Attempt ${currentAttempt}`);
        const response = await fetch(url);
        
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`);
        }
        
        const data = await response.json();
        resolve(data);
      } catch (err) {
        if (operation.retry(err)) {
          return; // Will retry
        }
        reject(operation.mainError());
      }
    });
  });
}

fetchWithRetry('https://api.example.com/data')
  .then(data => console.log('Success:', data))
  .catch(err => console.error('Failed:', err));
```

**With async/await:**

```typescript
import * as retry from '@lumenlabs-dev/fast-node-retry';

async function retryableOperation(): Promise<string> {
  const operation = retry.operation({
    retries: 5,
    minTimeout: 1000,
  });

  return new Promise((resolve, reject) => {
    operation.attempt(async (currentAttempt) => {
      try {
        console.log(`Attempt ${currentAttempt}`);
        const result = await someFlakyOperation();
        resolve(result);
      } catch (err) {
        if (operation.retry(err as Error)) {
          return; // Will retry
        }
        reject(operation.mainError());
      }
    });
  });
}
```

## API

### retry.operation([options])

Creates a new `RetryOperation` object. `options` is the same as `retry.timeouts()`'s `options`, with three additions:

* `forever`: Whether to retry forever, defaults to `false`.
* `unref`: Whether to [unref](https://nodejs.org/api/timers.html#timers_unref) the setTimeout's, defaults to `false`.
* `maxRetryTime`: The maximum time (in milliseconds) that the retried operation is allowed to run. Default is `Infinity`.  

### retry.timeouts([options])

Returns an array of timeouts. All time `options` and return values are in
milliseconds. If `options` is an array, a copy of that array is returned.

`options` is a JS object that can contain any of the following keys:

* `retries`: The maximum amount of times to retry the operation. Default is `10`. Seting this to `1` means `do it once, then retry it once`.
* `factor`: The exponential factor to use. Default is `2`.
* `minTimeout`: The number of milliseconds before starting the first retry. Default is `1000`.
* `maxTimeout`: The maximum number of milliseconds between two retries. Default is `Infinity`.
* `randomize`: Randomizes the timeouts by multiplying with a factor between `1` to `2`. Default is `false`.

The formula used to calculate the individual timeouts is:

```
Math.min(random * minTimeout * Math.pow(factor, attempt), maxTimeout)
```

Have a look at [this article](http://dthain.blogspot.com/2009/02/exponential-backoff-in-distributed.html) for a better explanation of approach.

### retry.createTimeout(attempt, opts)

Returns a new `timeout` (integer in milliseconds) based on the given parameters.

`attempt` is an integer representing for which retry the timeout should be calculated. If your retry operation was executed 4 times you had one attempt and 3 retries. If you then want to calculate a new timeout, you should set `attempt` to 4 (attempts are zero-indexed).

`opts` can include `factor`, `minTimeout`, `randomize` (boolean) and `maxTimeout`. They are documented above.

`retry.createTimeout()` is used internally by `retry.timeouts()` and is public for you to be able to create your own timeouts for reinserting an item.

### retry.wrap(obj, [options], [methodNames])

Wrap all functions of the `obj` with retry. Optionally you can pass operation options and
an array of method names which need to be wrapped.

```
retry.wrap(obj)

retry.wrap(obj, ['method1', 'method2'])

retry.wrap(obj, {retries: 3})

retry.wrap(obj, {retries: 3}, ['method1', 'method2'])
```
The `options` object can take any options that the usual call to `retry.operation` can take.

### new RetryOperation(timeouts, [options])

Creates a new `RetryOperation` where `timeouts` is an array where each value is
a timeout given in milliseconds.

Available options:
* `forever`: Whether to retry forever, defaults to `false`.
* `unref`: Wether to [unref](https://nodejs.org/api/timers.html#timers_unref) the setTimeout's, defaults to `false`.

If `forever` is true, the following changes happen:
* `RetryOperation.errors()` will only output an array of one item: the last error.
* `RetryOperation` will repeatedly use the `timeouts` array. Once all of its timeouts have been used up, it restarts with the first timeout, then uses the second and so on.

#### retryOperation.errors()

Returns an array of all errors that have been passed to `retryOperation.retry()` so far. The
returning array has the errors ordered chronologically based on when they were passed to
`retryOperation.retry()`, which means the first passed error is at index zero and the last is
at the last index.

#### retryOperation.mainError()

A reference to the error object that occured most frequently. Errors are
compared using the `error.message` property.

If multiple error messages occured the same amount of time, the last error
object with that message is returned.

If no errors occured so far, the value is `null`.

#### retryOperation.attempt(fn, timeoutOps)

Defines the function `fn` that is to be retried and executes it for the first
time right away. The `fn` function can receive an optional `currentAttempt` callback that represents the number of attempts to execute `fn` so far.

Optionally defines `timeoutOps` which is an object having a property `timeout` in miliseconds and a property `cb` callback function.
Whenever your retry operation takes longer than `timeout` to execute, the timeout callback function `cb` is called.


#### retryOperation.try(fn)

This is an alias for `retryOperation.attempt(fn)`. This is deprecated. Please use `retryOperation.attempt(fn)` instead.

#### retryOperation.start(fn)

This is an alias for `retryOperation.attempt(fn)`. This is deprecated. Please use `retryOperation.attempt(fn)` instead.

#### retryOperation.retry(error)

Returns `false` when no `error` value is given, or the maximum amount of retries
has been reached.

Otherwise it returns `true`, and retries the operation after the timeout for
the current attempt number.

#### retryOperation.stop()

Allows you to stop the operation being retried. Useful for aborting the operation on a fatal error etc.

#### retryOperation.reset()

Resets the internal state of the operation object, so that you can call `attempt()` again as if this was a new operation object.

#### retryOperation.attempts()

Returns an int representing the number of attempts it took to call `fn` before it was successful.

## TypeScript Support

This package is written in TypeScript and provides full type definitions out of the box. No need to install separate `@types` packages.

```typescript
import * as retry from '@lumenlabs-dev/fast-node-retry';
import type { RetryOptions, RetryOperation } from '@lumenlabs-dev/fast-node-retry';

const options: RetryOptions = {
  retries: 5,
  factor: 2,
  minTimeout: 1000,
  maxTimeout: 60000,
  randomize: true,
};

const operation: RetryOperation = retry.operation(options);
```

For detailed information about the TypeScript migration, see [TYPESCRIPT_MIGRATION.md](./TYPESCRIPT_MIGRATION.md).

## Performance

This fork includes several performance optimizations:

- Faster array cloning operations
- Optimized date/time operations using `Date.now()`
- Efficient object property spreading
- Cached array lengths in loops
- Optimized error array management

All optimizations maintain full backward compatibility with the original API.

## License

This package is licensed under the MIT license.


## Credits

This package is a TypeScript fork of the original [node-retry](https://github.com/tim-kos/node-retry) by Tim Koschützki, with performance improvements and full type safety.

# Changelog

### 0.1.0 (fast-node-retry)
- Complete TypeScript migration with full type definitions
- Performance optimizations (faster array operations, Date.now(), etc.)
- Dual package support (CommonJS + ESM)
- Test suite migrated to TypeScript
- Maintained full backward compatibility with original API

### Original node-retry changelog:

- 0.10.0 Adding `stop` functionality, thanks to @maxnachlinger.
- 0.9.0 Adding `unref` functionality, thanks to @satazor.
- 0.8.0 Implementing retry.wrap.
- 0.7.0 Some bug fixes and made retry.createTimeout() public.
- 0.6.0 Introduced optional timeOps parameter for the attempt() function which is an object having a property timeout in milliseconds and a property cb callback function. Whenever your retry operation takes longer than timeout to execute, the timeout callback function cb is called.
- 0.5.0 Some minor refactoring.
- 0.4.0 Changed retryOperation.try() to retryOperation.attempt(). Deprecated the aliases start() and try() for it.
- 0.3.0 Added retryOperation.start() which is an alias for retryOperation.try().
- 0.2.0 Added attempts() function and parameter to retryOperation.try() representing the number of attempts it took to call fn().