Skip to content

pallad-ts/range

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Range 🎯

Range structure


CircleCI npm version Coverage Status License: MIT

Example code

Very simple structure with helper methods to define range of values in following scenarios

  • from A
  • to A
  • between A and B

Community

Join our discord server

Installation

npm install @pallad/range

Usage

Range consists of 3 different interfaces

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

interface Start<T> {
    start: T
}

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

interface End<T> {
    end: T;
}

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

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

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

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

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

From array or Tuple

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

// 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])
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.

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

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.

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.

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.

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.

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

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

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));