@nlozgachev/pipekit 0.1.7 → 0.2.0
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 +41 -218
- package/esm/src/Core/Arr.js +14 -14
- package/esm/src/Core/Option.js +16 -16
- package/esm/src/Core/Rec.js +1 -1
- package/esm/src/Core/Result.js +14 -14
- package/esm/src/Core/Task.js +73 -5
- package/esm/src/Core/TaskOption.js +3 -3
- package/esm/src/Core/TaskResult.js +62 -5
- package/esm/src/Core/These.js +40 -40
- package/esm/src/Types/Brand.js +3 -3
- package/package.json +2 -1
- package/script/src/Core/Arr.js +14 -14
- package/script/src/Core/Option.js +16 -16
- package/script/src/Core/Rec.js +1 -1
- package/script/src/Core/Result.js +14 -14
- package/script/src/Core/Task.js +73 -5
- package/script/src/Core/TaskOption.js +3 -3
- package/script/src/Core/TaskResult.js +62 -5
- package/script/src/Core/These.js +40 -40
- package/script/src/Types/Brand.js +3 -3
- package/types/src/Core/Arr.d.ts +3 -3
- package/types/src/Core/Arr.d.ts.map +1 -1
- package/types/src/Core/Option.d.ts +9 -9
- package/types/src/Core/Option.d.ts.map +1 -1
- package/types/src/Core/Rec.d.ts.map +1 -1
- package/types/src/Core/Result.d.ts +9 -9
- package/types/src/Core/Result.d.ts.map +1 -1
- package/types/src/Core/Task.d.ts +49 -5
- package/types/src/Core/Task.d.ts.map +1 -1
- package/types/src/Core/TaskOption.d.ts.map +1 -1
- package/types/src/Core/TaskResult.d.ts +40 -1
- package/types/src/Core/TaskResult.d.ts.map +1 -1
- package/types/src/Core/These.d.ts +31 -31
- package/types/src/Core/These.d.ts.map +1 -1
- package/types/src/Types/Brand.d.ts +5 -5
package/README.md
CHANGED
|
@@ -1,253 +1,76 @@
|
|
|
1
1
|
# @nlozgachev/pipekit
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@nlozgachev/pipekit)[](https://jsr.io/@nlozgachev/pipekit)[](https://www.typescriptlang.org)[](https://deno.com)
|
|
3
|
+
[](https://www.npmjs.com/package/@nlozgachev/pipekit)[](https://jsr.io/@nlozgachev/pipekit)[](https://www.typescriptlang.org)[](https://deno.com)
|
|
4
4
|
|
|
5
5
|
A TypeScript toolkit for writing code that means exactly what it says.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
async workflows, loading states — with types that carry their own intent and operations that
|
|
11
|
-
compose.
|
|
12
|
-
|
|
13
|
-
Most TypeScript code encodes state as flags and nullable fields: `data | null`,
|
|
14
|
-
`error: Error | null`, `loading: boolean`. This works at small scale, but it pushes the burden of
|
|
15
|
-
knowing which combinations are valid onto every consumer. Nothing in the type system tells you that
|
|
16
|
-
`data` is only meaningful when `loading` is false and `error` is null — you just have to know, and
|
|
17
|
-
check.
|
|
18
|
-
|
|
19
|
-
This library takes a different approach: represent the state space precisely, then provide a small,
|
|
20
|
-
consistent set of operations to work with it. `Option` doesn't let you access a value that might not
|
|
21
|
-
exist without deciding what to do when it doesn't. `Result` makes it impossible to forget the error
|
|
22
|
-
case. `RemoteData` replaces a trio of booleans with four named, mutually exclusive states. Each type
|
|
23
|
-
comes with a module of functions — constructors, transformations, extractors — that follow the same
|
|
24
|
-
conventions (`map`, `chain`, `match`, `getOrElse`) and work with `pipe`.
|
|
25
|
-
|
|
26
|
-
The consistency means knowledge transfers: once you know `Option`, picking up `Result` or
|
|
27
|
-
`TaskResult` is mostly recognising the same operations in a new context. The composition means logic
|
|
28
|
-
reads in the order it executes. And precise types mean the compiler tracks what's possible — so you
|
|
29
|
-
don't have to.
|
|
7
|
+
```sh
|
|
8
|
+
# npm / pnpm / yarn / bun
|
|
9
|
+
npm add @nlozgachev/pipekit
|
|
30
10
|
|
|
31
|
-
|
|
11
|
+
# Deno
|
|
12
|
+
deno add jsr:@nlozgachev/pipekit
|
|
13
|
+
```
|
|
32
14
|
|
|
33
|
-
|
|
34
|
-
`Applicative` in the API. Instead, you'll work with names that describe what they do:
|
|
15
|
+
## What is this?
|
|
35
16
|
|
|
36
|
-
|
|
37
|
-
- `Result` — an operation that can succeed or fail
|
|
38
|
-
- `map` — transform a value inside a container
|
|
39
|
-
- `chain` — sequence operations that might fail
|
|
40
|
-
- `match` — handle each case explicitly
|
|
17
|
+
A toolkit for expressing uncertainty precisely. Instead of `T | null`, `try/catch`, and loading state flag soup, you get types that name every possible state and make invalid ones unrepresentable. Each type comes with a consistent set of operations — `map`, `chain`, `match`, `getOrElse` — that compose with `pipe` and `flow`.
|
|
41
18
|
|
|
42
|
-
|
|
19
|
+
No FP jargon required. You won't find `Monad`, `Functor`, or `Applicative` in the API.
|
|
43
20
|
|
|
44
21
|
## What's included?
|
|
45
22
|
|
|
46
23
|
### pipekit/Core
|
|
47
24
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
- **`
|
|
52
|
-
|
|
53
|
-
- **`
|
|
54
|
-
|
|
55
|
-
- **`
|
|
56
|
-
|
|
57
|
-
- **`
|
|
58
|
-
|
|
59
|
-
- **`TaskResult<E, A>`** — a lazy async operation that can fail. `Task` + `Result` combined. Key
|
|
60
|
-
operations: `tryCatch`, `map`, `mapError`, `chain`, `match`, `recover`.
|
|
61
|
-
- **`TaskOption<A>`** — a lazy async operation that may return nothing. `Task` + `Option` combined.
|
|
62
|
-
Replaces `Promise<T | null>`. Key operations: `tryCatch`, `map`, `chain`, `filter`, `match`,
|
|
63
|
-
`getOrElse`, `toTaskResult`.
|
|
64
|
-
- **`TaskValidation<E, A>`** — a lazy async operation that accumulates errors. `Task` + `Validation`
|
|
65
|
-
combined. Use for async form validation or parallel async checks that all need to run. Key
|
|
66
|
-
operations: `tryCatch`, `map`, `chain`, `ap`, `match`, `recover`.
|
|
67
|
-
- **`These<E, A>`** — an inclusive-OR: holds an error, a success value, or both simultaneously.
|
|
68
|
-
Unlike `Result` which is one or the other, `Both` carries a warning alongside a valid result — use
|
|
69
|
-
it when partial success with diagnostics matters. Key operations: `toErr`, `toOk`, `toBoth`,
|
|
70
|
-
`map`, `mapErr`, `bimap`, `chain`, `match`, `swap`, `toResult`.
|
|
71
|
-
- **`RemoteData<E, A>`** — models the four states of a data fetch: `NotAsked`, `Loading`, `Failure`,
|
|
72
|
-
`Success`. Replaces `{ data, loading, error }` flag soup. Key operations: `notAsked`, `loading`,
|
|
73
|
-
`failure`, `success`, `map`, `match`, `toResult`.
|
|
74
|
-
- **`Arr`** — array operations that return `Option` instead of throwing or returning `undefined`.
|
|
75
|
-
Operations: `head`, `last`, `findFirst`, `findLast`, `partition`, `groupBy`, `zip`, `traverse`,
|
|
76
|
-
and more.
|
|
77
|
-
- **`Rec`** — record/object operations. Operations: `lookup`, `map`, `filter`, `pick`, `omit`,
|
|
78
|
-
`merge`, and more.
|
|
25
|
+
- **`Option<A>`** — a value that might not exist. Replaces `T | null | undefined`.
|
|
26
|
+
- **`Result<E, A>`** — an operation that succeeds or fails. Replaces `try/catch`.
|
|
27
|
+
- **`Validation<E, A>`** — like `Result`, but accumulates all errors instead of stopping at the first.
|
|
28
|
+
- **`Task<A>`** — a lazy async operation that doesn't run until called.
|
|
29
|
+
- **`TaskResult<E, A>`** — `Task` + `Result`. A lazy async operation that can fail.
|
|
30
|
+
- **`TaskOption<A>`** — `Task` + `Option`. Replaces `Promise<T | null>`.
|
|
31
|
+
- **`TaskValidation<E, A>`** — `Task` + `Validation`. For async checks that all need to run.
|
|
32
|
+
- **`These<E, A>`** — an inclusive OR: holds an error, a value, or both at once.
|
|
33
|
+
- **`RemoteData<E, A>`** — the four states of a data fetch: `NotAsked`, `Loading`, `Failure`, `Success`.
|
|
34
|
+
- **`Arr`** — array utilities that return `Option` instead of `undefined`.
|
|
35
|
+
- **`Rec`** — record/object utilities.
|
|
79
36
|
|
|
80
37
|
### pipekit/Types
|
|
81
38
|
|
|
82
|
-
- **`Brand<K, T>`** — nominal typing
|
|
83
|
-
passing a `CustomerId` where a `UserId` is expected, even though both are `string`. The brand
|
|
84
|
-
exists only at compile time — zero runtime cost. Key operations: `make`, `unwrap`.
|
|
39
|
+
- **`Brand<K, T>`** — nominal typing at compile time, zero runtime cost.
|
|
85
40
|
- **`NonEmptyList<A>`** — an array guaranteed to have at least one element.
|
|
86
41
|
|
|
87
42
|
### pipekit/Composition
|
|
88
43
|
|
|
89
|
-
- **`pipe`** —
|
|
90
|
-
- **`
|
|
91
|
-
- **`compose`** — compose functions right to left (traditional mathematical composition).
|
|
92
|
-
- **`curry` / `uncurry`** — convert between multi-argument and single-argument functions.
|
|
93
|
-
- **`tap`** — run a side effect (like logging) without breaking the pipeline.
|
|
94
|
-
- **`memoize`** — cache function results.
|
|
95
|
-
- **`identity`**, **`constant`**, **`not`**, **`and`**, **`or`**, **`once`**, **`flip`** — common
|
|
96
|
-
function utilities.
|
|
44
|
+
- **`pipe`**, **`flow`**, **`compose`** — function composition.
|
|
45
|
+
- **`curry`** / **`uncurry`**, **`tap`**, **`memoize`**, and other function utilities.
|
|
97
46
|
|
|
98
|
-
##
|
|
99
|
-
|
|
100
|
-
Everything in the library is designed to work with `pipe` — a function that passes a value through a
|
|
101
|
-
series of transformations, top to bottom. The alternative is nesting calls inside each other, which
|
|
102
|
-
reads inside-out:
|
|
47
|
+
## Example
|
|
103
48
|
|
|
104
49
|
```ts
|
|
105
|
-
import { Option } from "@nlozgachev/pipekit/Core";
|
|
50
|
+
import { Option, Result } from "@nlozgachev/pipekit/Core";
|
|
106
51
|
import { pipe } from "@nlozgachev/pipekit/Composition";
|
|
107
52
|
|
|
108
|
-
//
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
),
|
|
114
|
-
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
// With pipe: reads top to bottom, matches execution order
|
|
118
|
-
const userName = pipe(
|
|
119
|
-
users.get("123"), // User | undefined
|
|
120
|
-
Option.fromNullable, // Option<User>
|
|
121
|
-
Option.map((u) => u.name), // Option<string>
|
|
122
|
-
Option.getOrElse("Anonymous"), // string
|
|
53
|
+
// Chain nullable lookups without nested null checks
|
|
54
|
+
const city = pipe(
|
|
55
|
+
getUser(userId), // User | null
|
|
56
|
+
Option.fromNullable, // Option<User>
|
|
57
|
+
Option.chain((u) => Option.fromNullable(u.address)),// Option<Address>
|
|
58
|
+
Option.chain((a) => Option.fromNullable(a.city)), // Option<string>
|
|
59
|
+
Option.map((c) => c.toUpperCase()), // Option<string>
|
|
60
|
+
Option.getOrElse("UNKNOWN"), // string
|
|
123
61
|
);
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
No method chaining, no class hierarchies. Just functions that connect together.
|
|
127
|
-
|
|
128
|
-
## What does "data-last" mean and why should I care?
|
|
129
|
-
|
|
130
|
-
Every operation takes the data it operates on as the **last** argument. This means you can partially
|
|
131
|
-
apply them — get a function back without providing data yet — which makes `flow` work naturally.
|
|
132
|
-
|
|
133
|
-
```ts
|
|
134
|
-
import { Option } from "@nlozgachev/pipekit/Core";
|
|
135
|
-
import { flow } from "@nlozgachev/pipekit/Composition";
|
|
136
|
-
|
|
137
|
-
// Data-first: can't partially apply, so you're stuck writing wrapper functions
|
|
138
|
-
function formatName(user: User | null): string {
|
|
139
|
-
const opt = Option.fromNullable(user);
|
|
140
|
-
const name = Option.map(opt, (u) => u.name);
|
|
141
|
-
return Option.getOrElse(name, "Anonymous");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
users.map((u) => formatName(u)); // needs an arrow function wrapper
|
|
145
|
-
|
|
146
|
-
// Data-last: operations are curried, so flow connects them directly
|
|
147
|
-
const formatName = flow(
|
|
148
|
-
Option.fromNullable<User>,
|
|
149
|
-
Option.map((u) => u.name),
|
|
150
|
-
Option.getOrElse("Anonymous"),
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
users.map(formatName); // no wrapper — it's already a function of User
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
## How does this help me write safer code?
|
|
157
|
-
|
|
158
|
-
The core types make invalid states unrepresentable. Instead of stacking null checks and hoping you
|
|
159
|
-
handled every branch, the shape of the code reflects the shape of the data:
|
|
160
|
-
|
|
161
|
-
```ts
|
|
162
|
-
// Before: null handling that doesn't scale
|
|
163
|
-
function getDisplayCity(userId: string): string {
|
|
164
|
-
const user = getUser(userId);
|
|
165
|
-
if (user === null) return "UNKNOWN";
|
|
166
|
-
if (user.address === null) return "UNKNOWN";
|
|
167
|
-
if (user.address.city === null) return "UNKNOWN";
|
|
168
|
-
return user.address.city.toUpperCase();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// After: flat, readable, same guarantees
|
|
172
|
-
function getDisplayCity(userId: string): string {
|
|
173
|
-
return pipe(
|
|
174
|
-
getUser(userId), // User | null
|
|
175
|
-
Option.fromNullable, // Option<User>
|
|
176
|
-
Option.chain((u) => Option.fromNullable(u.address)), // Option<Address>
|
|
177
|
-
Option.chain((a) => Option.fromNullable(a.city)), // Option<string>
|
|
178
|
-
Option.map((c) => c.toUpperCase()), // Option<string>
|
|
179
|
-
Option.getOrElse("UNKNOWN"), // string
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
`Brand` applies the same idea at the type level. When `userId` and `customerId` are both `string`,
|
|
185
|
-
nothing stops you from passing one where the other is expected — until you brand them:
|
|
186
62
|
|
|
187
|
-
|
|
188
|
-
import { Brand } from "@nlozgachev/pipekit/Types";
|
|
189
|
-
|
|
190
|
-
type UserId = Brand<"UserId", string>;
|
|
191
|
-
type CustomerId = Brand<"CustomerId", string>;
|
|
192
|
-
|
|
193
|
-
const toUserId = Brand.make<"UserId", string>();
|
|
194
|
-
const toCustomerId = Brand.make<"CustomerId", string>();
|
|
195
|
-
|
|
196
|
-
function getUser(id: UserId): User {/* ... */}
|
|
197
|
-
|
|
198
|
-
const cid = toCustomerId("c-99");
|
|
199
|
-
getUser(cid); // TypeError: Argument of type 'CustomerId' is not assignable to parameter of type 'UserId'
|
|
200
|
-
getUser(toUserId("u-42")); // ✓
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
The same idea applies to error handling with `Result`, form validation with `Validation`, async
|
|
204
|
-
operations with `Task`, `TaskResult`, `TaskOption`, and `TaskValidation`, and loading states with
|
|
205
|
-
`RemoteData`.
|
|
206
|
-
|
|
207
|
-
## How do I install it?
|
|
208
|
-
|
|
209
|
-
```sh
|
|
210
|
-
# Deno
|
|
211
|
-
deno add jsr:@nlozgachev/pipekit
|
|
212
|
-
|
|
213
|
-
# npm / pnpm / yarn / bun
|
|
214
|
-
npm add @nlozgachev/pipekit
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## How do I get started?
|
|
218
|
-
|
|
219
|
-
Start with `pipe` and `Option`. These two cover the most common pain point — dealing with values
|
|
220
|
-
that might not exist:
|
|
221
|
-
|
|
222
|
-
```ts
|
|
223
|
-
import { Option } from "@nlozgachev/pipekit/Core";
|
|
224
|
-
import { pipe } from "@nlozgachev/pipekit/Composition";
|
|
225
|
-
|
|
226
|
-
// Read a user's preferred language from their settings, fall back to the app default
|
|
227
|
-
const language = pipe(
|
|
228
|
-
userSettings.get(userId), // UserSettings | undefined
|
|
229
|
-
Option.fromNullable, // Option<UserSettings>
|
|
230
|
-
Option.map((s) => s.language), // Option<string>
|
|
231
|
-
Option.getOrElse(DEFAULT_LANGUAGE), // string
|
|
232
|
-
);
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Once that feels natural, reach for `Result` when operations can fail with a meaningful error —
|
|
236
|
-
parsing, network calls, database lookups:
|
|
237
|
-
|
|
238
|
-
```ts
|
|
239
|
-
import { Result } from "@nlozgachev/pipekit/Core";
|
|
240
|
-
|
|
241
|
-
// Parse user input and look up the record — both steps can fail
|
|
63
|
+
// Parse input and look up a record — both steps can fail
|
|
242
64
|
const record = pipe(
|
|
243
|
-
parseId(rawInput),
|
|
244
|
-
Result.chain((id) => db.find(id)),
|
|
245
|
-
Result.map((r) => r.name),
|
|
65
|
+
parseId(rawInput), // Result<ParseError, number>
|
|
66
|
+
Result.chain((id) => db.find(id)), // Result<ParseError | NotFoundError, Record>
|
|
67
|
+
Result.map((r) => r.name), // Result<ParseError | NotFoundError, string>
|
|
246
68
|
);
|
|
247
69
|
```
|
|
248
70
|
|
|
249
|
-
|
|
250
|
-
|
|
71
|
+
## Documentation
|
|
72
|
+
|
|
73
|
+
Full guides and API reference at **[pipekit.lozgachev.dev](https://pipekit.lozgachev.dev)**.
|
|
251
74
|
|
|
252
75
|
## License
|
|
253
76
|
|
package/esm/src/Core/Arr.js
CHANGED
|
@@ -28,7 +28,7 @@ export var Arr;
|
|
|
28
28
|
* Arr.head([]); // None
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
Arr.head = (data) => data.length > 0 ? Option.
|
|
31
|
+
Arr.head = (data) => data.length > 0 ? Option.some(data[0]) : Option.none();
|
|
32
32
|
/**
|
|
33
33
|
* Returns the last element of an array, or None if the array is empty.
|
|
34
34
|
*
|
|
@@ -38,7 +38,7 @@ export var Arr;
|
|
|
38
38
|
* Arr.last([]); // None
|
|
39
39
|
* ```
|
|
40
40
|
*/
|
|
41
|
-
Arr.last = (data) => data.length > 0 ? Option.
|
|
41
|
+
Arr.last = (data) => data.length > 0 ? Option.some(data[data.length - 1]) : Option.none();
|
|
42
42
|
/**
|
|
43
43
|
* Returns all elements except the first, or None if the array is empty.
|
|
44
44
|
*
|
|
@@ -48,7 +48,7 @@ export var Arr;
|
|
|
48
48
|
* Arr.tail([]); // None
|
|
49
49
|
* ```
|
|
50
50
|
*/
|
|
51
|
-
Arr.tail = (data) => data.length > 0 ? Option.
|
|
51
|
+
Arr.tail = (data) => data.length > 0 ? Option.some(data.slice(1)) : Option.none();
|
|
52
52
|
/**
|
|
53
53
|
* Returns all elements except the last, or None if the array is empty.
|
|
54
54
|
*
|
|
@@ -58,7 +58,7 @@ export var Arr;
|
|
|
58
58
|
* Arr.init([]); // None
|
|
59
59
|
* ```
|
|
60
60
|
*/
|
|
61
|
-
Arr.init = (data) => data.length > 0 ? Option.
|
|
61
|
+
Arr.init = (data) => data.length > 0 ? Option.some(data.slice(0, -1)) : Option.none();
|
|
62
62
|
// --- Search ---
|
|
63
63
|
/**
|
|
64
64
|
* Returns the first element matching the predicate, or None.
|
|
@@ -70,7 +70,7 @@ export var Arr;
|
|
|
70
70
|
*/
|
|
71
71
|
Arr.findFirst = (predicate) => (data) => {
|
|
72
72
|
const idx = data.findIndex(predicate);
|
|
73
|
-
return idx >= 0 ? Option.
|
|
73
|
+
return idx >= 0 ? Option.some(data[idx]) : Option.none();
|
|
74
74
|
};
|
|
75
75
|
/**
|
|
76
76
|
* Returns the last element matching the predicate, or None.
|
|
@@ -83,9 +83,9 @@ export var Arr;
|
|
|
83
83
|
Arr.findLast = (predicate) => (data) => {
|
|
84
84
|
for (let i = data.length - 1; i >= 0; i--) {
|
|
85
85
|
if (predicate(data[i]))
|
|
86
|
-
return Option.
|
|
86
|
+
return Option.some(data[i]);
|
|
87
87
|
}
|
|
88
|
-
return Option.
|
|
88
|
+
return Option.none();
|
|
89
89
|
};
|
|
90
90
|
/**
|
|
91
91
|
* Returns the index of the first element matching the predicate, or None.
|
|
@@ -97,7 +97,7 @@ export var Arr;
|
|
|
97
97
|
*/
|
|
98
98
|
Arr.findIndex = (predicate) => (data) => {
|
|
99
99
|
const idx = data.findIndex(predicate);
|
|
100
|
-
return idx >= 0 ? Option.
|
|
100
|
+
return idx >= 0 ? Option.some(idx) : Option.none();
|
|
101
101
|
};
|
|
102
102
|
// --- Transform ---
|
|
103
103
|
/**
|
|
@@ -303,7 +303,7 @@ export var Arr;
|
|
|
303
303
|
* ```ts
|
|
304
304
|
* const parseNum = (s: string): Option<number> => {
|
|
305
305
|
* const n = Number(s);
|
|
306
|
-
* return isNaN(n) ? Option.
|
|
306
|
+
* return isNaN(n) ? Option.none() : Option.of(n);
|
|
307
307
|
* };
|
|
308
308
|
*
|
|
309
309
|
* pipe(["1", "2", "3"], Arr.traverse(parseNum)); // Some([1, 2, 3])
|
|
@@ -315,10 +315,10 @@ export var Arr;
|
|
|
315
315
|
for (const a of data) {
|
|
316
316
|
const mapped = f(a);
|
|
317
317
|
if (Option.isNone(mapped))
|
|
318
|
-
return Option.
|
|
318
|
+
return Option.none();
|
|
319
319
|
result.push(mapped.value);
|
|
320
320
|
}
|
|
321
|
-
return Option.
|
|
321
|
+
return Option.some(result);
|
|
322
322
|
};
|
|
323
323
|
/**
|
|
324
324
|
* Maps each element to a Result and collects the results.
|
|
@@ -328,7 +328,7 @@ export var Arr;
|
|
|
328
328
|
* ```ts
|
|
329
329
|
* pipe(
|
|
330
330
|
* [1, 2, 3],
|
|
331
|
-
* Arr.traverseResult(n => n > 0 ? Result.
|
|
331
|
+
* Arr.traverseResult(n => n > 0 ? Result.ok(n) : Result.err("negative"))
|
|
332
332
|
* ); // Ok([1, 2, 3])
|
|
333
333
|
* ```
|
|
334
334
|
*/
|
|
@@ -340,7 +340,7 @@ export var Arr;
|
|
|
340
340
|
return mapped;
|
|
341
341
|
result.push(mapped.value);
|
|
342
342
|
}
|
|
343
|
-
return Result.
|
|
343
|
+
return Result.ok(result);
|
|
344
344
|
};
|
|
345
345
|
/**
|
|
346
346
|
* Maps each element to a Task and runs all in parallel.
|
|
@@ -361,7 +361,7 @@ export var Arr;
|
|
|
361
361
|
* @example
|
|
362
362
|
* ```ts
|
|
363
363
|
* Arr.sequence([Option.of(1), Option.of(2)]); // Some([1, 2])
|
|
364
|
-
* Arr.sequence([Option.of(1), Option.
|
|
364
|
+
* Arr.sequence([Option.of(1), Option.none()]); // None
|
|
365
365
|
* ```
|
|
366
366
|
*/
|
|
367
367
|
Arr.sequence = (data) => Arr.traverse((a) => a)(data);
|
package/esm/src/Core/Option.js
CHANGED
|
@@ -9,11 +9,11 @@ export var Option;
|
|
|
9
9
|
* Option.of(42); // Some(42)
|
|
10
10
|
* ```
|
|
11
11
|
*/
|
|
12
|
-
Option.of = (value) => Option.
|
|
12
|
+
Option.of = (value) => Option.some(value);
|
|
13
13
|
/**
|
|
14
14
|
* Creates a Some containing the given value.
|
|
15
15
|
*/
|
|
16
|
-
Option.
|
|
16
|
+
Option.some = (value) => ({ kind: "Some", value });
|
|
17
17
|
/**
|
|
18
18
|
* Type guard that checks if a Option is Some.
|
|
19
19
|
*/
|
|
@@ -21,7 +21,7 @@ export var Option;
|
|
|
21
21
|
/**
|
|
22
22
|
* Creates a None (empty Option).
|
|
23
23
|
*/
|
|
24
|
-
Option.
|
|
24
|
+
Option.none = () => ({ kind: "None" });
|
|
25
25
|
/**
|
|
26
26
|
* Type guard that checks if a Option is None.
|
|
27
27
|
*/
|
|
@@ -36,7 +36,7 @@ export var Option;
|
|
|
36
36
|
* Option.fromNullable(42); // Some(42)
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
|
-
Option.fromNullable = (value) => value === null || value === undefined ? Option.
|
|
39
|
+
Option.fromNullable = (value) => value === null || value === undefined ? Option.none() : Option.some(value);
|
|
40
40
|
/**
|
|
41
41
|
* Extracts the value from a Option, returning null if None.
|
|
42
42
|
*/
|
|
@@ -49,7 +49,7 @@ export var Option;
|
|
|
49
49
|
* Creates a Option from a possibly undefined value.
|
|
50
50
|
* Returns None if undefined, Some otherwise.
|
|
51
51
|
*/
|
|
52
|
-
Option.fromUndefined = (value) => value === undefined ? Option.
|
|
52
|
+
Option.fromUndefined = (value) => value === undefined ? Option.none() : Option.some(value);
|
|
53
53
|
/**
|
|
54
54
|
* Converts an Option to a Result.
|
|
55
55
|
* Some becomes Ok, None becomes Err with the provided error.
|
|
@@ -62,33 +62,33 @@ export var Option;
|
|
|
62
62
|
* ); // Ok(42)
|
|
63
63
|
*
|
|
64
64
|
* pipe(
|
|
65
|
-
* Option.
|
|
65
|
+
* Option.none(),
|
|
66
66
|
* Option.toResult(() => "Value was missing")
|
|
67
67
|
* ); // Err("Value was missing")
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
|
-
Option.toResult = (onNone) => (data) => Option.isSome(data) ? Result.
|
|
70
|
+
Option.toResult = (onNone) => (data) => Option.isSome(data) ? Result.ok(data.value) : Result.err(onNone());
|
|
71
71
|
/**
|
|
72
72
|
* Creates an Option from a Result.
|
|
73
73
|
* Ok becomes Some, Err becomes None (the error is discarded).
|
|
74
74
|
*
|
|
75
75
|
* @example
|
|
76
76
|
* ```ts
|
|
77
|
-
* Option.fromResult(Result.
|
|
78
|
-
* Option.fromResult(Result.
|
|
77
|
+
* Option.fromResult(Result.ok(42)); // Some(42)
|
|
78
|
+
* Option.fromResult(Result.err("oops")); // None
|
|
79
79
|
* ```
|
|
80
80
|
*/
|
|
81
|
-
Option.fromResult = (data) => Result.isOk(data) ? Option.
|
|
81
|
+
Option.fromResult = (data) => Result.isOk(data) ? Option.some(data.value) : Option.none();
|
|
82
82
|
/**
|
|
83
83
|
* Transforms the value inside a Option if it exists.
|
|
84
84
|
*
|
|
85
85
|
* @example
|
|
86
86
|
* ```ts
|
|
87
87
|
* pipe(Option.of(5), Option.map(n => n * 2)); // Some(10)
|
|
88
|
-
* pipe(Option.
|
|
88
|
+
* pipe(Option.none(), Option.map(n => n * 2)); // None
|
|
89
89
|
* ```
|
|
90
90
|
*/
|
|
91
|
-
Option.map = (f) => (data) => Option.isSome(data) ? Option.
|
|
91
|
+
Option.map = (f) => (data) => Option.isSome(data) ? Option.some(f(data.value)) : data;
|
|
92
92
|
/**
|
|
93
93
|
* Chains Option computations. If the first is Some, passes the value to f.
|
|
94
94
|
* If the first is None, propagates None.
|
|
@@ -97,7 +97,7 @@ export var Option;
|
|
|
97
97
|
* ```ts
|
|
98
98
|
* const parseNumber = (s: string): Option<number> => {
|
|
99
99
|
* const n = parseInt(s, 10);
|
|
100
|
-
* return isNaN(n) ? Option.
|
|
100
|
+
* return isNaN(n) ? Option.none() : Option.of(n);
|
|
101
101
|
* };
|
|
102
102
|
*
|
|
103
103
|
* pipe(Option.of("42"), Option.chain(parseNumber)); // Some(42)
|
|
@@ -141,7 +141,7 @@ export var Option;
|
|
|
141
141
|
* @example
|
|
142
142
|
* ```ts
|
|
143
143
|
* pipe(Option.of(5), Option.getOrElse(0)); // 5
|
|
144
|
-
* pipe(Option.
|
|
144
|
+
* pipe(Option.none(), Option.getOrElse(0)); // 0
|
|
145
145
|
* ```
|
|
146
146
|
*/
|
|
147
147
|
Option.getOrElse = (defaultValue) => (data) => Option.isSome(data) ? data.value : defaultValue;
|
|
@@ -173,7 +173,7 @@ export var Option;
|
|
|
173
173
|
* pipe(Option.of(2), Option.filter(n => n > 3)); // None
|
|
174
174
|
* ```
|
|
175
175
|
*/
|
|
176
|
-
Option.filter = (predicate) => (data) => Option.isSome(data) && predicate(data.value) ? data : Option.
|
|
176
|
+
Option.filter = (predicate) => (data) => Option.isSome(data) && predicate(data.value) ? data : Option.none();
|
|
177
177
|
/**
|
|
178
178
|
* Recovers from a None by providing a fallback Option.
|
|
179
179
|
*/
|
|
@@ -191,5 +191,5 @@ export var Option;
|
|
|
191
191
|
* ); // Some(8)
|
|
192
192
|
* ```
|
|
193
193
|
*/
|
|
194
|
-
Option.ap = (arg) => (data) => Option.isSome(data) && Option.isSome(arg) ? Option.
|
|
194
|
+
Option.ap = (arg) => (data) => Option.isSome(data) && Option.isSome(arg) ? Option.some(data.value(arg.value)) : Option.none();
|
|
195
195
|
})(Option || (Option = {}));
|
package/esm/src/Core/Rec.js
CHANGED
|
@@ -87,7 +87,7 @@ export var Rec;
|
|
|
87
87
|
* pipe({ a: 1, b: 2 }, Rec.lookup("c")); // None
|
|
88
88
|
* ```
|
|
89
89
|
*/
|
|
90
|
-
Rec.lookup = (key) => (data) => Object.prototype.hasOwnProperty.call(data, key) ? Option.
|
|
90
|
+
Rec.lookup = (key) => (data) => Object.prototype.hasOwnProperty.call(data, key) ? Option.some(data[key]) : Option.none();
|
|
91
91
|
/**
|
|
92
92
|
* Returns all keys of a record.
|
|
93
93
|
*/
|
package/esm/src/Core/Result.js
CHANGED
|
@@ -8,15 +8,15 @@ export var Result;
|
|
|
8
8
|
* Result.of(42); // Ok(42)
|
|
9
9
|
* ```
|
|
10
10
|
*/
|
|
11
|
-
Result.of = (value) => Result.
|
|
11
|
+
Result.of = (value) => Result.ok(value);
|
|
12
12
|
/**
|
|
13
13
|
* Creates a failed Result with the given error.
|
|
14
14
|
*/
|
|
15
|
-
Result.
|
|
15
|
+
Result.err = (error) => ({ kind: "Error", error });
|
|
16
16
|
/**
|
|
17
17
|
* Creates a successful Result with the given value.
|
|
18
18
|
*/
|
|
19
|
-
Result.
|
|
19
|
+
Result.ok = (value) => ({ kind: "Ok", value });
|
|
20
20
|
/**
|
|
21
21
|
* Type guard that checks if an Result is Ok.
|
|
22
22
|
*/
|
|
@@ -40,10 +40,10 @@ export var Result;
|
|
|
40
40
|
*/
|
|
41
41
|
Result.tryCatch = (f, onError) => {
|
|
42
42
|
try {
|
|
43
|
-
return Result.
|
|
43
|
+
return Result.ok(f());
|
|
44
44
|
}
|
|
45
45
|
catch (e) {
|
|
46
|
-
return Result.
|
|
46
|
+
return Result.err(onError(e));
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
/**
|
|
@@ -52,19 +52,19 @@ export var Result;
|
|
|
52
52
|
* @example
|
|
53
53
|
* ```ts
|
|
54
54
|
* pipe(Result.of(5), Result.map(n => n * 2)); // Ok(10)
|
|
55
|
-
* pipe(Result.
|
|
55
|
+
* pipe(Result.err("error"), Result.map(n => n * 2)); // Err("error")
|
|
56
56
|
* ```
|
|
57
57
|
*/
|
|
58
|
-
Result.map = (f) => (data) => Result.isOk(data) ? Result.
|
|
58
|
+
Result.map = (f) => (data) => Result.isOk(data) ? Result.ok(f(data.value)) : data;
|
|
59
59
|
/**
|
|
60
60
|
* Transforms the error value inside an Result.
|
|
61
61
|
*
|
|
62
62
|
* @example
|
|
63
63
|
* ```ts
|
|
64
|
-
* pipe(Result.
|
|
64
|
+
* pipe(Result.err("oops"), Result.mapError(e => e.toUpperCase())); // Err("OOPS")
|
|
65
65
|
* ```
|
|
66
66
|
*/
|
|
67
|
-
Result.mapError = (f) => (data) => Result.isErr(data) ? Result.
|
|
67
|
+
Result.mapError = (f) => (data) => Result.isErr(data) ? Result.err(f(data.error)) : data;
|
|
68
68
|
/**
|
|
69
69
|
* Chains Result computations. If the first is Ok, passes the value to f.
|
|
70
70
|
* If the first is Err, propagates the error.
|
|
@@ -72,7 +72,7 @@ export var Result;
|
|
|
72
72
|
* @example
|
|
73
73
|
* ```ts
|
|
74
74
|
* const validatePositive = (n: number): Result<string, number> =>
|
|
75
|
-
* n > 0 ? Result.of(n) : Result.
|
|
75
|
+
* n > 0 ? Result.of(n) : Result.err("Must be positive");
|
|
76
76
|
*
|
|
77
77
|
* pipe(Result.of(5), Result.chain(validatePositive)); // Ok(5)
|
|
78
78
|
* pipe(Result.of(-1), Result.chain(validatePositive)); // Err("Must be positive")
|
|
@@ -115,7 +115,7 @@ export var Result;
|
|
|
115
115
|
* @example
|
|
116
116
|
* ```ts
|
|
117
117
|
* pipe(Result.of(5), Result.getOrElse(0)); // 5
|
|
118
|
-
* pipe(Result.
|
|
118
|
+
* pipe(Result.err("error"), Result.getOrElse(0)); // 0
|
|
119
119
|
* ```
|
|
120
120
|
*/
|
|
121
121
|
Result.getOrElse = (defaultValue) => (data) => Result.isOk(data) ? data.value : defaultValue;
|
|
@@ -151,8 +151,8 @@ export var Result;
|
|
|
151
151
|
*
|
|
152
152
|
* @example
|
|
153
153
|
* ```ts
|
|
154
|
-
* Result.toOption(Result.
|
|
155
|
-
* Result.toOption(Result.
|
|
154
|
+
* Result.toOption(Result.ok(42)); // Some(42)
|
|
155
|
+
* Result.toOption(Result.err("oops")); // None
|
|
156
156
|
* ```
|
|
157
157
|
*/
|
|
158
158
|
Result.toOption = (data) => Result.isOk(data) ? { kind: "Some", value: data.value } : { kind: "None" };
|
|
@@ -169,5 +169,5 @@ export var Result;
|
|
|
169
169
|
* ); // Ok(8)
|
|
170
170
|
* ```
|
|
171
171
|
*/
|
|
172
|
-
Result.ap = (arg) => (data) => Result.isOk(data) && Result.isOk(arg) ? Result.
|
|
172
|
+
Result.ap = (arg) => (data) => Result.isOk(data) && Result.isOk(arg) ? Result.ok(data.value(arg.value)) : Result.isErr(data) ? data : arg;
|
|
173
173
|
})(Result || (Result = {}));
|