@jeengbe/prelude 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,135 @@
|
|
|
1
1
|
<h1 align="center">@jeengbe/prelude</h1>
|
|
2
2
|
<div align="center">
|
|
3
3
|
|
|
4
|
+
A small, dependency-free functional programming toolkit for TypeScript.
|
|
5
|
+
|
|
4
6
|
[](https://github.com/jeengbe/ts-packages/blob/master/packages/prelude/LICENSE)
|
|
5
7
|
[](https://www.npmjs.com/package/@jeengbe/prelude)
|
|
6
8
|
[](https://jsr.io/@jeengbe/prelude)
|
|
7
9
|
[](https://app.codecov.io/gh/jeengbe/ts-packages/tree/master/packages/prelude)
|
|
8
10
|
|
|
9
11
|
</div>
|
|
12
|
+
|
|
13
|
+
It provides an `Either` type for representing a value that's either a success or a failure (with an async-aware `EitherP` counterpart for `Promise`-returning pipelines), and a handful of small `Maybe` helpers for working with values that may be `undefined`.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
The package is published to [npm](https://www.npmjs.com/package/@jeengbe/prelude) and [JSR](https://jsr.io/@jeengbe/prelude) as `@jeengbe/prelude`. Versions follow Semantic Versioning.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### `Either`
|
|
22
|
+
|
|
23
|
+
`Either<L, R>` is a union of `Left<L>` and `Right<R>`, conventionally used to represent a failure (`Left`) or a success (`Right`).
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Either } from '@jeengbe/prelude';
|
|
27
|
+
|
|
28
|
+
const ok: Either<string, number> = Either.right(42);
|
|
29
|
+
const err: Either<string, number> = Either.left('something went wrong');
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Because `Either<L, R>` is a plain union type, `isLeft()`/`isRight()` narrow it in both directions:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
declare const e: Either<string, number>;
|
|
36
|
+
|
|
37
|
+
if (e.isLeft()) {
|
|
38
|
+
e; // Left<string>
|
|
39
|
+
} else {
|
|
40
|
+
e; // Right<number>
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Use `map`/`leftMap`/`bimap` to transform the contained value without unwrapping it:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
Either.right(2).map((n) => n * 2); // Right(4)
|
|
48
|
+
Either.left('oops').leftMap((e) => e.toUpperCase()); // Left('OOPS')
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Use `flatMap`/`leftFlatMap` to chain further `Either`-returning operations:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
declare function parseAge(input: string): Either<string, number>;
|
|
55
|
+
|
|
56
|
+
Either.right('42').flatMap(parseAge);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Use `tap`/`flatTap` to run a side effect on the right value without altering the `Either`:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
Either.right(user).tap((u) => console.log(`loaded user ${u.id}`));
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
To get the values back out, `get()`/`getLeft()` return a `Maybe<T>` (i.e. `undefined` if this is the other side), `getOrElse` takes a fallback function for the right value, and `pair()` deconstructs the `Either` into a `[Maybe<L>, Maybe<R>]` tuple:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const [error, value] = result.pair();
|
|
69
|
+
const value2 = result.getOrElse(() => defaultValue);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`Either.fromMaybe` and `Either.cond` build an `Either` out of a `Maybe` or a boolean condition, respectively:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
Either.fromMaybe(maybeUser, () => 'user not found');
|
|
76
|
+
Either.cond(
|
|
77
|
+
items.length > 0,
|
|
78
|
+
() => 'no items',
|
|
79
|
+
() => items[0],
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### `EitherP`
|
|
84
|
+
|
|
85
|
+
`EitherP<L, R>` it wraps a `PromiseLike<Either<L, R>>`: It exposes the same API as `Either`, and is itself directly awaitable.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { EitherP } from '@jeengbe/prelude';
|
|
89
|
+
|
|
90
|
+
declare function fetchUser(id: string): Promise<Either<string, User>>;
|
|
91
|
+
|
|
92
|
+
const name = await EitherP.fromPromise(fetchUser('1'))
|
|
93
|
+
.map((u) => u.name)
|
|
94
|
+
.getOrElse(() => 'anonymous');
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Every `Either` method has an `Async` counterpart (`mapAsync`, `flatMapAsync`, `tapAsync`, etc.) that accepts a function returning a `Promise` and returns an `EitherP`, letting you chain asynchronous steps without leaving the `Either` world:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
Either.right(orderId)
|
|
101
|
+
.flatMapAsync(async (id) =>
|
|
102
|
+
(await isValid(id)) ? Either.right(id) : Either.left('invalid order'),
|
|
103
|
+
)
|
|
104
|
+
.tapAsync(async (id) => audit.log(id))
|
|
105
|
+
.then((result) => result.get());
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `Maybe`
|
|
109
|
+
|
|
110
|
+
`Maybe<T>` is a type alias for `T | undefined`, along with a few helpers for working with values that may be missing:
|
|
111
|
+
|
|
112
|
+
- `isDefined` narrows a `Maybe<T>` to `T`.
|
|
113
|
+
- `mapMaybe` transforms the value if present, and passes `undefined` through otherwise.
|
|
114
|
+
- `flattenMaybe` collapses a `Maybe<Maybe<T>>` into a single level.
|
|
115
|
+
- `ifTrue`/`ifFalse` run a function conditionally, returning its result or `undefined`.
|
|
116
|
+
- `matchPair` pattern-matches a `[Maybe<A>, Maybe<B>]` tuple against all four presence combinations.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { ifTrue, isDefined, mapMaybe, matchPair } from '@jeengbe/prelude';
|
|
120
|
+
|
|
121
|
+
const upper = mapMaybe(name, (n) => n.toUpperCase());
|
|
122
|
+
|
|
123
|
+
if (isDefined(upper)) {
|
|
124
|
+
// upper: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const warning = ifTrue(retries > 3, () => 'too many retries');
|
|
128
|
+
|
|
129
|
+
matchPair([error, value], {
|
|
130
|
+
neither: () => 'nothing to report',
|
|
131
|
+
a: (e) => `error: ${e}`,
|
|
132
|
+
b: (v) => `value: ${v}`,
|
|
133
|
+
both: (e, v) => `error ${e}, but got a partial value: ${v}`,
|
|
134
|
+
});
|
|
135
|
+
```
|