npm package: @peassoft/request
Description: Peassoft HTTP client library for Node.js
Latest version: 1.0.1

# @peassoft/request

Peassoft HTTP client library for Node.js

## You may not need it!

This is a custom, highly opinionated solution aimed at code re-use for a few private projects.

## Installation

```bash
$ npm install @peassoft/request
```

## Usage Example

```typescript
import {
  createRequestFunction,
  type ApiDescriptor,
  type CustomError,
} from 'mnr-request';

const apis: Map<string, ApiDescriptor> = new Map([
  ['httpbin', {
    baseUrl: 'https://httpbin.org',
    apiOptions: {
      requestTimeoutMs: 5000,
      retries: 5,
      retryTimeoutMs: 500,
      customErrors: new Map<Map<number, CustomError>>([
        [401, { name: 'UnauthorizedError', message: 'request unauthorized' }]
      ])
    }
  }],
  ['example', { baseUrl: 'http://example.com' }],
]);

const globalOptions = {
  requestTimeoutMs: 10000,
  retries: 1,
  retryTimeoutMs: 3000,
  customErrors: new Map<Map<number, CustomError>>([
    [409, { name: 'ConflictError', message: 'request resulted in 409 Conflict response' }],
  ])
};

const request = createRequestFunction(apis, globalOptions);

// Type of data we expect the endpoint to respond with
type ResponseData1 = {
  foo: string;
};

const responseData1: ResponseData1 | null = await request<ResponseData1>({
  apiName: 'httpbin',
  path: '/get',
  method: 'GET',
  headers: {
    authorization: 'Bearer 123foo=',
  },
  qs: {
    foo: 'bar',
  },
});

await request<void>({
  apiName: 'example',
  path: '/',
  method: 'PUT',
  body: {
    foo: 'bar',
  },
  requestOptions: {
    requestTimeoutMs: 0,
    retries: 0,
    retryTimeoutMs: 0,
    customErrors: new Map<Map<number, CustomError>>([
      [500, { name: 'CriticalError', message: 'backend is broken' }],
    ]),
  },
});
```

## API Reference

### Options

There are four levels of options:

1. Default options - applied to all requests if no other options override them.
2. Custom global options - override default options and are applied to all requests if not overriden by neither custom API options, nor custom request options.
3. Custom API options - override both default and global options and are applied to all requests to a particular API.
4. Custom request options - override all other options and are applied to this particular request.

Options object intarface:

```typescript
type Options = {
  /**
   * Timeout in milliseconds after which a single request attempt/retry fails.
   *
   * 0 disables the setting (OS limit applies).
   */
  requestTimeoutMs?: number;

  /**
   * Number of retries.
   *
   * This number does not include the initial request.
   *
   * Retries happen in case of:
   *
   *  • request fails due to a technical reason;
   *
   *  • response status code is 503, no custom error is configured for this status
   *    code, and response body JSON object DOES NOT contain a field `retry: false`;
   *
   *  • response status code is 504.
   */
  retries?: number;

  /**
   * Amount of time in milliseconds to wait before next retry.
   */
  retryTimeoutMs?: number;

  /**
   * Collection of custom error descriptors a promise has to reject with for defined
   * response status codes.
   *
   * A key of a map is response status code.
   */
  customErrors?: Map<number, CustomError>;
};
```

All fields in an `Options` object are optional. If a field is missing in a particular `Options` object, it just means that this option at a higher level is not overriden.

Default options:

```typescript
{
  requestTimeoutMs: 0,
  retries: 2,
  retryTimeoutMs: 2000,
  customErrors: new Map<number, CustomError>(), // Empty collection
}
```

### Initializing request function

You should initialize request function once and then use it througout your app.

```typescript
import {
  createRequestFunction,
  type ApiDescriptor,
  type Options,
} from '@peassoft/request';

const apis: ApiDescriptor = ...
const globalOptions: Options = ...

export const request = createRequestFunction(apis, globalOptions);
```