-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Łukasz Kużyński
committed
Dec 24, 2021
1 parent
527d499
commit 59ff7d4
Showing
14 changed files
with
685 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
version: 2.1 | ||
|
||
orbs: | ||
node: circleci/node@4.7 | ||
coveralls: coveralls/coveralls@1.0.6 | ||
|
||
jobs: | ||
main_job: | ||
executor: | ||
name: node/default | ||
tag: "14.18" | ||
steps: | ||
- checkout | ||
- node/install-packages | ||
- run: npm test | ||
- store_test_results: | ||
path: ./ | ||
- coveralls/upload | ||
|
||
workflows: | ||
main: | ||
jobs: | ||
- main_job |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
<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` is consist of 3 different interfaces | ||
|
||
`Range.Start` that defines only start of the range without end. | ||
|
||
```typescript | ||
interface Start<T> { | ||
start: T | ||
} | ||
``` | ||
|
||
`Range.End` that defines only end of the range without start. | ||
|
||
```typescript | ||
interface End<T> { | ||
end: T; | ||
} | ||
``` | ||
|
||
`Range.Full` that defines both start an end of the range. | ||
|
||
```typescript | ||
type Full<T> = Start<T> & End<T> | ||
``` | ||
The `Range` type itself is an union of those 3 types. | ||
```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).success() | ||
// start range | ||
Range.create(1).success() | ||
// end range | ||
Range.create(undefined, 100).success() | ||
``` | ||
|
||
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]) | ||
``` | ||
|
||
You can prevent throwing an error using `.validation` property on creation functions which return [`Validation`](https://github.com/monet/monet.js/blob/master/docs/VALIDATION.md) monad instead. | ||
|
||
```typescript | ||
Range.create(1, 100).success(); | ||
Range.create(null, undefined).fail() // '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).success()); | ||
|
||
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 | ||
``` | ||
|
||
# Helper methods | ||
|
||
## Mapping | ||
|
||
Mapping allows to convert 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).success(); | ||
// mapping to pure 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).success(), 50) // true | ||
Range.isWithin(Range.create(1, 100).success(), 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).success(); | ||
|
||
// 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 | ||
``` |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,38 @@ | ||
import {Range} from './Range'; | ||
import {Mapping} from './Mapping'; | ||
import * as is from 'predicates'; | ||
|
||
export type Enchanted<T> = Readonly<Range<T>> & Enchanted.Shape<T>; | ||
|
||
const TYPE_KEY = '@type'; | ||
const TYPE = '@pallad/range/enchanted'; | ||
|
||
const isType = is.property(TYPE_KEY, is.strictEqual(TYPE)); | ||
|
||
export namespace Enchanted { | ||
export function is<T = unknown>(range: any): range is Enchanted<T> { | ||
return range instanceof Shape || isType(range); | ||
} | ||
|
||
export class Shape<T> { | ||
map<T1, T2, T3>(mapper: Mapping<T, T1, T2, T3>): (T1 | T2 | T3) { | ||
constructor() { | ||
Object.defineProperty(this, TYPE_KEY, { | ||
value: TYPE, | ||
configurable: false, | ||
enumerable: false | ||
}); | ||
} | ||
|
||
map<T1, T2 = T1, T3 = T2>(mapper: Mapping<T, T1, T2, T3>): (T1 | T2 | T3) { | ||
return Range.map(this as unknown as Range<T>, mapper); | ||
} | ||
|
||
isWithin(value: T, exclusive?: boolean | { start?: boolean, end?: boolean }) { | ||
return Range.isWithin(this as unknown as Range<T>, value, exclusive); | ||
} | ||
|
||
toTuple() { | ||
return Range.toTuple(this as unknown as Range<T>); | ||
} | ||
} | ||
} |
Oops, something went wrong.