Skip to content

Commit

Permalink
Version 2.0.0 initial
Browse files Browse the repository at this point in the history
  • Loading branch information
Łukasz Kużyński committed Dec 24, 2021
1 parent 527d499 commit 59ff7d4
Show file tree
Hide file tree
Showing 14 changed files with 685 additions and 47 deletions.
23 changes: 23 additions & 0 deletions .circleci/config.yml
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
199 changes: 199 additions & 0 deletions README.md
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
```
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@pallad/range",
"version": "2.0.0",
"description": "Definition of range",
"description": "Range structure",
"main": "compiled/index.js",
"compiled": "compiled/index.d.ts",
"scripts": {
Expand All @@ -24,16 +24,19 @@
"range",
"structures"
],
"author": "Łukasz Kużyński <lukasz.kuzynski@gmail.com> (http://wookieb.pl)",
"author": "Łukasz Kużyński <lukasz.kuzynski@gmail.com> (https://wookieb.pl)",
"license": "MIT",
"bugs": {
"url": "https://github.com/pallad-ts/range/issues"
},
"homepage": "https://github.com/pallad-ts/range#readme",
"devDependencies": {
"@pallad/scripts": "^2.1.2"
"@pallad/scripts": "^2.1.2",
"conditional-type-checks": "^1.0.5"
},
"dependencies": {
"monet": "^0.9.3"
"@pallad/compare": "^1.1.1",
"monet": "^0.9.3",
"predicates": "^2.0.3"
}
}
28 changes: 27 additions & 1 deletion src/Enchanted.ts
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>);
}
}
}
Loading

0 comments on commit 59ff7d4

Please sign in to comment.