npm package: @pallad/range
Description: Range structure
Author: Łukasz Kużyński
Latest version: 3.2.0

<div align="center">
<h1>Range 🎯</h1>

<p>Range structure</p>
</div>

---
[![CircleCI](https://circleci.com/gh/pallad-ts/range/tree/master.svg?style=svg)](https://circleci.com/gh/pallad-ts/range/tree/master)
[![npm version](https://badge.fury.io/js/@pallad%2Frange.svg)](https://badge.fury.io/js/@pallad%2Frange)
[![Coverage Status](https://coveralls.io/repos/github/pallad-ts/range/badge.svg?branch=master)](https://coveralls.io/github/pallad-ts/range?branch=master)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
---

![Example code](./assets/intro-code.png)

Very simple structure with [helper methods](#helper-methods) to define range of values in following scenarios

* from A
* to A
* between A and B

# Community

Join our [discord server](https://discord.gg/G5tSBYbpej)

# Installation

```shell
npm install @pallad/range
```

# Usage

`Range` consists of 3 different interfaces

`Range.Start` that defines only a start without an end.

```typescript
interface Start<T> {
    start: T
}
```

`Range.End` that defines only an end without a start.

```typescript
interface End<T> {
    end: T;
}
```

`Range.Full` that defines both a start and an end.

```typescript
type Full<T> = Start<T> & End<T>
```

The `Range` type itself is an union of all of them.

```typescript
type Range<T> = Range.Full<T> | Range.Start<T> | Range.End<T>;  
```

## Creating

You can create `Range` from regular creation function, array or tuple.

Regular `create`

```typescript
// full range
Range.create(1, 100);
// start range
Range.create(1);
// end range
Range.create(undefined, 100);
```

From array or Tuple

```typescript
// start range
Range.fromArray([1])
// full range
Range.fromArray([1, 100])

// full range - other values are ignores
Range.fromArray([1, 100, 1003, 3000])
// end range
Range.fromArray([undefined, 100])

```

If creation fails, `TypeError` is thrown.

```typescript
// fails - undefined values
Range.create(undefined, null)

// fails - start greater than end
Range.create(100, 1)

// fails - empty array
Range.fromArray([])
// fails - undefined values only
Range.fromArray([undefined, null])
```

```typescript
Range.create(1, 100).value;
Range.create(null, undefined).value // 'Cannot create Range from undefined or null values'
```

## Enchanted range

Enchanted range is a range object with extra methods. Enchanted object is immutable.

```typescript
const enchantedRange = enchant(Range.create(1, 100));

enchantedRange.isWithin(40); // true
enchantedRange.isWithin(500); // false

enchantedRange.map({
    start: ({start}) => `from ${start}`,
    end: ({end}) => `to ${end}`,
    full: ({start, end}) => `between ${start} and ${end}`
}); // 'between 1 and 100`

enchantedRange.toTuple(); // [1, 100]
```

## Checking if value is a Range

```typescript
Range.is({start: 10}) // true
Range.is({end: 10}) // true
Range.is({start: 1, end: 10}) // true
```

# Custom comparator

Boundaries comparison is a crucial feature of `Range` struct, therefore internally uses `@pallad/compare` for
comparison.
Sometimes it is not enough and you can provide your
own [comparison function](https://github.com/pallad-ts/compare#defining-custom-sorting-for-any-values).

```typescript
Range.create({value: 1}, {value: 100}, (a, b) => a.value - b.value); // no fail
Range.fromArray([{value: 1}, {value: 100}], (a, b) => a.value - b.value); // no fail
```

# Helper methods

## Mapping

Mapping converts range to any other value.

`Range.map` accepts object with properties named `start`, `end` and `full` where each of it might be a function or any
other value.
If property value is a function then result of that function gets returned, otherwise it takes the value.

```typescript
const range = Range.create(1, 100);
// mapping to simple values
Range.map(range, {start: 'start', end: 'end', full: 'full'}) // 'full'

// mapping functions
enchantedRange.map({
    start: ({start}) => `from ${start}`,
    end: ({end}) => `to ${end}`,
    full: ({start, end}) => `between ${start} and ${end}`
}); // 'between 1 and 100`
```

## Check if value falls in range

`Range.isWithin` checks if given values falls in range. Internally uses `@pallad/compare` so custom comparison functions
for value objects are supported.

```typescript
Range.isWithin(Range.create(1, 100), 50) // true
Range.isWithin(Range.create(1, 100), 500) // false
```

### Exclusivity

By default `isWithin` treats every range as inclusive for both edges. You can change that behavior with second argument.

```typescript
const range = Range.create(1, 100);

// exclusivity = false 
Range.isWithin(range, 100, false) // true 
Range.isWithin(range, 1, false) // true

// same as above
Range.isWithin(range, 100) // true 
Range.isWithin(range, 1) // true 

// exclusivity = true 
Range.isWithin(range, 100, true) // false
Range.isWithin(range, 1, true) // false

// start exclusive 
Range.isWithin(range, 100, {start: true}) // true
Range.isWithin(range, 1, {start: true}) // false

// end exclusive 
Range.isWithin(range, 100, {end: true}) // false 
Range.isWithin(range, 1, {end: true}) // true
```

## Converting a range of a type to another range

```typescript

Range.convert(Range.create(1, 100), (value) => value + 's'); // Range<string> { start: '1s', end: '100s' }
Range.convert(Range.create(1), (value) => value + 's'); // Range<string> { start: '1s'}
Range.convert(Range.create(undefined, q00), (value) => value + 's'); // Range<string> { end: '100s'}
```

Sometimes you need to map to values that cannot be easily compared, therefore you need to provide custom comparison
function to ensure proper creation of new range

```typescript
const range = Range.create(2, 100);

// custom comparison function is needed since '2s' > '100s' using regular string comparison
Range.convert(range, (value) => value + 's', (a, b) => parseInt(a) - parseInt(b));
```