@nlozgachev/pipekit 0.1.6 → 0.1.8

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 CHANGED
@@ -2,181 +2,76 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@nlozgachev/pipekit?style=for-the-badge&color=000&logo=npm&label&logoColor=fff)](https://www.npmjs.com/package/@nlozgachev/pipekit)[![JSR Version](https://img.shields.io/jsr/v/@nlozgachev/pipekit?style=for-the-badge&color=000&logo=jsr&label&logoColor=fff)](https://jsr.io/@nlozgachev/pipekit)[![TypeScript](https://img.shields.io/badge/-0?style=for-the-badge&color=000&logo=typescript&label&logoColor=fff)](https://www.typescriptlang.org)[![Deno](https://img.shields.io/badge/-0?style=for-the-badge&color=000&logo=Deno&label&logoColor=fff)](https://deno.com)
4
4
 
5
- A functional programming toolkit for TypeScript that speaks your language.
5
+ A TypeScript toolkit for writing code that means exactly what it says.
6
6
 
7
- ## What is this?
8
-
9
- A collection of types and utilities that help you write safer, composition-centric, more predictable TypeScript. If you've ever dealt with `null` checks scattered across your codebase, nested `try/catch` blocks, or `{ data: T | null; loading: boolean; error: Error | null }` — this library gives you better tools for all of that.
7
+ ```sh
8
+ # npm / pnpm / yarn / bun
9
+ npm add @nlozgachev/pipekit
10
10
 
11
- ## Do I need to know functional programming to use this?
11
+ # Deno
12
+ deno add jsr:@nlozgachev/pipekit
13
+ ```
12
14
 
13
- No. The library avoids FP-specific jargon wherever possible. You won't find `Monad`, `Functor`, or `Applicative` in the API. Instead, you'll work with names that describe what they do:
15
+ ## What is this?
14
16
 
15
- - `Option` a value that might not exist
16
- - `Result` — an operation that can succeed or fail
17
- - `map` — transform a value inside a container
18
- - `chain` — sequence operations that might fail
19
- - `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`.
20
18
 
21
- You can start using these right away and learn the underlying concepts as you go.
19
+ No FP jargon required. You won't find `Monad`, `Functor`, or `Applicative` in the API.
22
20
 
23
21
  ## What's included?
24
22
 
25
23
  ### pipekit/Core
26
24
 
27
- Each of these is both a TypeScript type and a module of functions for working with values of that type constructors, transformations, and ways to extract a value back out.
28
-
29
- - **`Option<A>`** — a value that might not exist. Replaces `T | null | undefined`. Key operations: `fromNullable`, `map`, `chain`, `filter`, `match`, `getOrElse`, `recover`.
30
- - **`Result<E, A>`** — an operation that succeeds with `A` or fails with `E`. Replaces `try/catch`. Key operations: `tryCatch`, `map`, `mapError`, `chain`, `match`, `getOrElse`, `recover`.
31
- - **`Validation<E, A>`** — like `Result`, but accumulates **all** errors instead of stopping at the first. Built for form validation. Key operations: `combine`, `combineAll`, `ap`, `map`, `match`.
32
- - **`Task<A>`** — a lazy async operation that doesn't run until called. Key operations: `from`, `map`, `chain`, `all`, `delay`.
33
- - **`TaskResult<E, A>`** — a lazy async operation that can fail. `Task` + `Result` combined. Key operations: `tryCatch`, `map`, `mapError`, `chain`, `match`, `recover`.
34
- - **`RemoteData<E, A>`** — models the four states of a data fetch: `NotAsked`, `Loading`, `Failure`, `Success`. Replaces `{ data, loading, error }` flag soup. Key operations: `notAsked`, `loading`, `failure`, `success`, `map`, `match`, `toResult`.
35
- - **`Arr`**array operations that return `Option` instead of throwing or returning `undefined`. Operations: `head`, `last`, `findFirst`, `findLast`, `partition`, `groupBy`, `zip`, `traverse`, and more.
36
- - **`Rec`** — record/object operations. Operations: `lookup`, `map`, `filter`, `pick`, `omit`, `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.
37
36
 
38
37
  ### pipekit/Types
39
38
 
39
+ - **`Brand<K, T>`** — nominal typing at compile time, zero runtime cost.
40
40
  - **`NonEmptyList<A>`** — an array guaranteed to have at least one element.
41
41
 
42
42
  ### pipekit/Composition
43
43
 
44
- - **`pipe`** — pass a value through a series of functions, left to right.
45
- - **`flow`** compose functions into a reusable pipeline (like `pipe` without an initial value).
46
- - **`compose`** — compose functions right to left (traditional mathematical composition).
47
- - **`curry` / `uncurry`** — convert between multi-argument and single-argument functions.
48
- - **`tap`** — run a side effect (like logging) without breaking the pipeline.
49
- - **`memoize`** — cache function results.
50
- - **`identity`**, **`constant`**, **`not`**, **`and`**, **`or`**, **`once`**, **`flip`** — common function utilities.
44
+ - **`pipe`**, **`flow`**, **`compose`** — function composition.
45
+ - **`curry`** / **`uncurry`**, **`tap`**, **`memoize`**, and other function utilities.
51
46
 
52
- ## What does "composition-centric" mean?
53
-
54
- Everything in the library is designed to work with `pipe` — a function that passes a value through a series of transformations, top to bottom. The alternative is nesting calls inside each other, which reads inside-out:
47
+ ## Example
55
48
 
56
49
  ```ts
57
- import { Option } from "@nlozgachev/pipekit/Core";
50
+ import { Option, Result } from "@nlozgachev/pipekit/Core";
58
51
  import { pipe } from "@nlozgachev/pipekit/Composition";
59
52
 
60
- // Without pipe: execution order is the reverse of reading order
61
- const userName = Option.getOrElse(
62
- Option.map(
63
- Option.fromNullable(users.get("123")),
64
- u => u.name
65
- ),
66
- "Anonymous"
67
- );
68
-
69
- // With pipe: reads top to bottom, matches execution order
70
- const userName = pipe(
71
- users.get("123"), // User | undefined
72
- Option.fromNullable, // Option<User>
73
- Option.map(u => u.name), // Option<string>
74
- Option.getOrElse("Anonymous") // string
75
- );
76
- ```
77
-
78
- No method chaining, no class hierarchies. Just functions that connect together.
79
-
80
- ## What does "data-last" mean and why should I care?
81
-
82
- Every operation takes the data it operates on as the **last** argument. This means you can partially apply them — get a function back without providing data yet — which makes `flow` work naturally.
83
-
84
- ```ts
85
- import { Option } from "@nlozgachev/pipekit/Core";
86
- import { flow } from "@nlozgachev/pipekit/Composition";
87
-
88
- // Data-first: can't partially apply, so you're stuck writing wrapper functions
89
- function formatName(user: User | null): string {
90
- const opt = Option.fromNullable(user);
91
- const name = Option.map(opt, u => u.name);
92
- return Option.getOrElse(name, "Anonymous");
93
- }
94
-
95
- users.map(u => formatName(u)); // needs an arrow function wrapper
96
-
97
- // Data-last: operations are curried, so flow connects them directly
98
- const formatName = flow(
99
- Option.fromNullable<User>,
100
- Option.map(u => u.name),
101
- Option.getOrElse("Anonymous")
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
102
61
  );
103
62
 
104
- users.map(formatName); // no wrapper it's already a function of User
105
- ```
106
-
107
- ## How does this help me write safer code?
108
-
109
- The core types make invalid states unrepresentable. Instead of stacking null checks and hoping you handled every branch, the shape of the code reflects the shape of the data:
110
-
111
- ```ts
112
- // Before: null handling that doesn't scale
113
- function getDisplayCity(userId: string): string {
114
- const user = getUser(userId);
115
- if (user === null) return "UNKNOWN";
116
- if (user.address === null) return "UNKNOWN";
117
- if (user.address.city === null) return "UNKNOWN";
118
- return user.address.city.toUpperCase();
119
- }
120
-
121
- // After: flat, readable, same guarantees
122
- function getDisplayCity(userId: string): string {
123
- return pipe(
124
- getUser(userId), // User | null
125
- Option.fromNullable, // Option<User>
126
- Option.chain(u => Option.fromNullable(u.address)), // Option<Address>
127
- Option.chain(a => Option.fromNullable(a.city)), // Option<string>
128
- Option.map(c => c.toUpperCase()), // Option<string>
129
- Option.getOrElse("UNKNOWN") // string
130
- );
131
- }
132
- ```
133
-
134
- The same idea applies to error handling with `Result`, form validation with `Validation`, async operations with `Task` and `TaskResult`, and loading states with `RemoteData`.
135
-
136
-
137
-
138
- ## How do I install it?
139
-
140
- ```sh
141
- # Deno
142
- deno add jsr:@nlozgachev/pipekit
143
-
144
- # npm / pnpm / yarn / bun
145
- npm add @nlozgachev/pipekit
146
- ```
147
-
148
- ## How do I get started?
149
-
150
- Start with `pipe` and `Option`. These two cover the most common pain point — dealing with values that might not exist:
151
-
152
- ```ts
153
- import { Option } from "@nlozgachev/pipekit/Core";
154
- import { pipe } from "@nlozgachev/pipekit/Composition";
155
-
156
- // Read a user's preferred language from their settings, fall back to the app default
157
- const language = pipe(
158
- userSettings.get(userId), // UserSettings | undefined
159
- Option.fromNullable, // Option<UserSettings>
160
- Option.map(s => s.language), // Option<string>
161
- Option.getOrElse(DEFAULT_LANGUAGE) // string
162
- );
163
- ```
164
-
165
- Once that feels natural, reach for `Result` when operations can fail with a meaningful error — parsing, network calls, database lookups:
166
-
167
- ```ts
168
- import { Result } from "@nlozgachev/pipekit/Core";
169
-
170
- // Parse user input and look up the record — both steps can fail
63
+ // Parse input and look up a record both steps can fail
171
64
  const record = pipe(
172
- parseId(rawInput), // Result<ParseError, number>
173
- Result.chain(id => db.find(id)), // Result<ParseError | NotFoundError, Record>
174
- Result.map(r => r.name), // Result<ParseError | NotFoundError, string>
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>
175
68
  );
176
69
  ```
177
70
 
178
- And `Validation` when you need to collect multiple errors at once, like form validation.
71
+ ## Documentation
72
+
73
+ Full guides and API reference at **[pipekit.lozgachev.dev](https://pipekit.lozgachev.dev)**.
179
74
 
180
75
  ## License
181
76
 
182
- MIT
77
+ MIT
@@ -0,0 +1,99 @@
1
+ import { Option } from "./Option.js";
2
+ import { Task } from "./Task.js";
3
+ export var TaskOption;
4
+ (function (TaskOption) {
5
+ /**
6
+ * Wraps a value in a Some inside a Task.
7
+ */
8
+ TaskOption.of = (value) => Task.of(Option.of(value));
9
+ /**
10
+ * Creates a TaskOption that resolves to None.
11
+ */
12
+ TaskOption.none = () => Task.of(Option.toNone());
13
+ /**
14
+ * Lifts an Option into a TaskOption.
15
+ */
16
+ TaskOption.fromOption = (option) => Task.of(option);
17
+ /**
18
+ * Lifts a Task into a TaskOption by wrapping its result in Some.
19
+ */
20
+ TaskOption.fromTask = (task) => Task.map(Option.of)(task);
21
+ /**
22
+ * Creates a TaskOption from a Promise-returning function.
23
+ * Returns Some if the promise resolves, None if it rejects.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const fetchUser = TaskOption.tryCatch(() =>
28
+ * fetch("/user/1").then(r => r.json())
29
+ * );
30
+ * ```
31
+ */
32
+ TaskOption.tryCatch = (f) => () => f().then(Option.of).catch(() => Option.toNone());
33
+ /**
34
+ * Transforms the value inside a TaskOption.
35
+ */
36
+ TaskOption.map = (f) => (data) => Task.map(Option.map(f))(data);
37
+ /**
38
+ * Chains TaskOption computations. If the first resolves to Some, passes the
39
+ * value to f. If the first resolves to None, propagates None.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * pipe(
44
+ * findUser("123"),
45
+ * TaskOption.chain(user => findOrg(user.orgId))
46
+ * )();
47
+ * ```
48
+ */
49
+ TaskOption.chain = (f) => (data) => Task.chain((option) => Option.isSome(option) ? f(option.value) : Task.of(Option.toNone()))(data);
50
+ /**
51
+ * Applies a function wrapped in a TaskOption to a value wrapped in a TaskOption.
52
+ * Both Tasks run in parallel.
53
+ */
54
+ TaskOption.ap = (arg) => (data) => () => Promise.all([data(), arg()]).then(([of_, oa]) => Option.ap(oa)(of_));
55
+ /**
56
+ * Extracts a value from a TaskOption by providing handlers for both cases.
57
+ */
58
+ TaskOption.fold = (onNone, onSome) => (data) => Task.map(Option.fold(onNone, onSome))(data);
59
+ /**
60
+ * Pattern matches on a TaskOption, returning a Task of the result.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * pipe(
65
+ * findUser("123"),
66
+ * TaskOption.match({
67
+ * some: user => `Hello, ${user.name}`,
68
+ * none: () => "User not found"
69
+ * })
70
+ * )();
71
+ * ```
72
+ */
73
+ TaskOption.match = (cases) => (data) => Task.map(Option.match(cases))(data);
74
+ /**
75
+ * Returns the value or a default if the TaskOption resolves to None.
76
+ */
77
+ TaskOption.getOrElse = (defaultValue) => (data) => Task.map(Option.getOrElse(defaultValue))(data);
78
+ /**
79
+ * Executes a side effect on the value without changing the TaskOption.
80
+ * Useful for logging or debugging.
81
+ */
82
+ TaskOption.tap = (f) => (data) => Task.map(Option.tap(f))(data);
83
+ /**
84
+ * Filters the value inside a TaskOption. Returns None if the predicate fails.
85
+ */
86
+ TaskOption.filter = (predicate) => (data) => Task.map(Option.filter(predicate))(data);
87
+ /**
88
+ * Converts a TaskOption to a TaskResult, using onNone to produce the error value.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * pipe(
93
+ * findUser("123"),
94
+ * TaskOption.toTaskResult(() => "User not found")
95
+ * );
96
+ * ```
97
+ */
98
+ TaskOption.toTaskResult = (onNone) => (data) => Task.map(Option.toResult(onNone))(data);
99
+ })(TaskOption || (TaskOption = {}));
@@ -0,0 +1,93 @@
1
+ import { Task } from "./Task.js";
2
+ import { Validation } from "./Validation.js";
3
+ export var TaskValidation;
4
+ (function (TaskValidation) {
5
+ /**
6
+ * Wraps a value in a valid TaskValidation.
7
+ */
8
+ TaskValidation.of = (value) => Task.of(Validation.of(value));
9
+ /**
10
+ * Creates a failed TaskValidation with a single error.
11
+ */
12
+ TaskValidation.fail = (error) => Task.of(Validation.fail(error));
13
+ /**
14
+ * Lifts a Validation into a TaskValidation.
15
+ */
16
+ TaskValidation.fromValidation = (validation) => Task.of(validation);
17
+ /**
18
+ * Creates a TaskValidation from a Promise-returning function.
19
+ * Catches any errors and transforms them using the onError function.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const fetchUser = (id: string): TaskValidation<string, User> =>
24
+ * TaskValidation.tryCatch(
25
+ * () => fetch(`/users/${id}`).then(r => r.json()),
26
+ * e => `Failed to fetch user: ${e}`
27
+ * );
28
+ * ```
29
+ */
30
+ TaskValidation.tryCatch = (f, onError) => () => f()
31
+ .then((Validation.of))
32
+ .catch((e) => Validation.fail(onError(e)));
33
+ /**
34
+ * Transforms the success value inside a TaskValidation.
35
+ */
36
+ TaskValidation.map = (f) => (data) => Task.map(Validation.map(f))(data);
37
+ /**
38
+ * Chains TaskValidation computations. If the first is Valid, passes the value
39
+ * to f. If the first is Invalid, propagates the errors.
40
+ *
41
+ * Note: chain short-circuits on first error. Use ap to accumulate errors.
42
+ */
43
+ TaskValidation.chain = (f) => (data) => Task.chain((validation) => Validation.isValid(validation)
44
+ ? f(validation.value)
45
+ : Task.of(Validation.toInvalid(validation.errors)))(data);
46
+ /**
47
+ * Applies a function wrapped in a TaskValidation to a value wrapped in a
48
+ * TaskValidation. Both Tasks run in parallel and errors from both sides
49
+ * are accumulated.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * pipe(
54
+ * TaskValidation.of((name: string) => (age: number) => ({ name, age })),
55
+ * TaskValidation.ap(validateName(name)),
56
+ * TaskValidation.ap(validateAge(age))
57
+ * )();
58
+ * ```
59
+ */
60
+ TaskValidation.ap = (arg) => (data) => () => Promise.all([data(), arg()]).then(([vf, va]) => Validation.ap(va)(vf));
61
+ /**
62
+ * Extracts a value from a TaskValidation by providing handlers for both cases.
63
+ */
64
+ TaskValidation.fold = (onInvalid, onValid) => (data) => Task.map(Validation.fold(onInvalid, onValid))(data);
65
+ /**
66
+ * Pattern matches on a TaskValidation, returning a Task of the result.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * pipe(
71
+ * validateForm(input),
72
+ * TaskValidation.match({
73
+ * valid: data => save(data),
74
+ * invalid: errors => showErrors(errors)
75
+ * })
76
+ * )();
77
+ * ```
78
+ */
79
+ TaskValidation.match = (cases) => (data) => Task.map(Validation.match(cases))(data);
80
+ /**
81
+ * Returns the success value or a default value if the TaskValidation is invalid.
82
+ */
83
+ TaskValidation.getOrElse = (defaultValue) => (data) => Task.map(Validation.getOrElse(defaultValue))(data);
84
+ /**
85
+ * Executes a side effect on the success value without changing the TaskValidation.
86
+ * Useful for logging or debugging.
87
+ */
88
+ TaskValidation.tap = (f) => (data) => Task.map(Validation.tap(f))(data);
89
+ /**
90
+ * Recovers from an Invalid state by providing a fallback TaskValidation.
91
+ */
92
+ TaskValidation.recover = (fallback) => (data) => Task.chain((validation) => Validation.isValid(validation) ? Task.of(validation) : fallback())(data);
93
+ })(TaskValidation || (TaskValidation = {}));
@@ -0,0 +1,242 @@
1
+ import { Result } from "./Result.js";
2
+ export var These;
3
+ (function (These) {
4
+ /**
5
+ * Creates a These holding only an error/warning (no success value).
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * These.toErr("Something went wrong");
10
+ * ```
11
+ */
12
+ These.toErr = (error) => Result.toErr(error);
13
+ /**
14
+ * Creates a These holding only a success value (no error).
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * These.toOk(42);
19
+ * ```
20
+ */
21
+ These.toOk = (value) => Result.toOk(value);
22
+ /**
23
+ * Creates a These holding both an error/warning and a success value.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * These.toBoth("Deprecated API used", result);
28
+ * ```
29
+ */
30
+ These.toBoth = (error, value) => ({
31
+ kind: "Both",
32
+ error,
33
+ value,
34
+ });
35
+ /**
36
+ * Type guard — checks if a These holds only an error/warning.
37
+ */
38
+ These.isErr = (data) => data.kind === "Error";
39
+ /**
40
+ * Type guard — checks if a These holds only a success value.
41
+ */
42
+ These.isOk = (data) => data.kind === "Ok";
43
+ /**
44
+ * Type guard — checks if a These holds both an error/warning and a success value.
45
+ */
46
+ These.isBoth = (data) => data.kind === "Both";
47
+ /**
48
+ * Returns true if the These contains a success value (Ok or Both).
49
+ */
50
+ These.hasValue = (data) => data.kind === "Ok" || data.kind === "Both";
51
+ /**
52
+ * Returns true if the These contains an error/warning (Err or Both).
53
+ */
54
+ These.hasError = (data) => data.kind === "Error" || data.kind === "Both";
55
+ /**
56
+ * Transforms the success value, leaving the error unchanged.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * pipe(These.toOk(5), These.map(n => n * 2)); // Ok(10)
61
+ * pipe(These.toBoth("warn", 5), These.map(n => n * 2)); // Both("warn", 10)
62
+ * pipe(These.toErr("err"), These.map(n => n * 2)); // Err("err")
63
+ * ```
64
+ */
65
+ These.map = (f) => (data) => {
66
+ if (These.isErr(data))
67
+ return data;
68
+ if (These.isOk(data))
69
+ return These.toOk(f(data.value));
70
+ return These.toBoth(data.error, f(data.value));
71
+ };
72
+ /**
73
+ * Transforms the error/warning value, leaving the success value unchanged.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * pipe(These.toErr("err"), These.mapErr(e => e.toUpperCase())); // Err("ERR")
78
+ * pipe(These.toBoth("warn", 5), These.mapErr(e => e.toUpperCase())); // Both("WARN", 5)
79
+ * ```
80
+ */
81
+ These.mapErr = (f) => (data) => {
82
+ if (These.isOk(data))
83
+ return data;
84
+ if (These.isErr(data))
85
+ return These.toErr(f(data.error));
86
+ return These.toBoth(f(data.error), data.value);
87
+ };
88
+ /**
89
+ * Transforms both the error and success values independently.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * pipe(
94
+ * These.toBoth("warn", 5),
95
+ * These.bimap(e => e.toUpperCase(), n => n * 2)
96
+ * ); // Both("WARN", 10)
97
+ * ```
98
+ */
99
+ These.bimap = (onErr, onOk) => (data) => {
100
+ if (These.isErr(data))
101
+ return These.toErr(onErr(data.error));
102
+ if (These.isOk(data))
103
+ return These.toOk(onOk(data.value));
104
+ return These.toBoth(onErr(data.error), onOk(data.value));
105
+ };
106
+ /**
107
+ * Chains These computations by passing the success value to f.
108
+ * - Err propagates unchanged.
109
+ * - Ok(a) applies f(a) directly.
110
+ * - Both(e, a): applies f(a); if the result is Ok(b), returns Both(e, b)
111
+ * to preserve the warning. Otherwise returns f(a) as-is.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * const double = (n: number): These<string, number> => These.toOk(n * 2);
116
+ *
117
+ * pipe(These.toOk(5), These.chain(double)); // Ok(10)
118
+ * pipe(These.toBoth("warn", 5), These.chain(double)); // Both("warn", 10)
119
+ * pipe(These.toErr("err"), These.chain(double)); // Err("err")
120
+ * ```
121
+ */
122
+ These.chain = (f) => (data) => {
123
+ if (These.isErr(data))
124
+ return data;
125
+ if (These.isOk(data))
126
+ return f(data.value);
127
+ const result = f(data.value);
128
+ return These.isOk(result) ? These.toBoth(data.error, result.value) : result;
129
+ };
130
+ /**
131
+ * Extracts a value from a These by providing handlers for all three cases.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * pipe(
136
+ * these,
137
+ * These.fold(
138
+ * e => `Error: ${e}`,
139
+ * a => `Value: ${a}`,
140
+ * (e, a) => `Both: ${e} / ${a}`
141
+ * )
142
+ * );
143
+ * ```
144
+ */
145
+ These.fold = (onErr, onOk, onBoth) => (data) => {
146
+ if (These.isErr(data))
147
+ return onErr(data.error);
148
+ if (These.isOk(data))
149
+ return onOk(data.value);
150
+ return onBoth(data.error, data.value);
151
+ };
152
+ /**
153
+ * Pattern matches on a These, returning the result of the matching case.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * pipe(
158
+ * these,
159
+ * These.match({
160
+ * err: e => `Error: ${e}`,
161
+ * ok: a => `Value: ${a}`,
162
+ * both: (e, a) => `Both: ${e} / ${a}`
163
+ * })
164
+ * );
165
+ * ```
166
+ */
167
+ These.match = (cases) => (data) => {
168
+ if (These.isErr(data))
169
+ return cases.err(data.error);
170
+ if (These.isOk(data))
171
+ return cases.ok(data.value);
172
+ return cases.both(data.error, data.value);
173
+ };
174
+ /**
175
+ * Returns the success value, or a default if the These has no success value.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * pipe(These.toOk(5), These.getOrElse(0)); // 5
180
+ * pipe(These.toBoth("warn", 5), These.getOrElse(0)); // 5
181
+ * pipe(These.toErr("err"), These.getOrElse(0)); // 0
182
+ * ```
183
+ */
184
+ These.getOrElse = (defaultValue) => (data) => These.hasValue(data) ? data.value : defaultValue;
185
+ /**
186
+ * Executes a side effect on the success value without changing the These.
187
+ * Useful for logging or debugging.
188
+ */
189
+ These.tap = (f) => (data) => {
190
+ if (These.hasValue(data))
191
+ f(data.value);
192
+ return data;
193
+ };
194
+ /**
195
+ * Swaps the roles of error and success values.
196
+ * - Err(e) → Ok(e)
197
+ * - Ok(a) → Err(a)
198
+ * - Both(e, a) → Both(a, e)
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * These.swap(These.toErr("err")); // Ok("err")
203
+ * These.swap(These.toOk(5)); // Err(5)
204
+ * These.swap(These.toBoth("warn", 5)); // Both(5, "warn")
205
+ * ```
206
+ */
207
+ These.swap = (data) => {
208
+ if (These.isErr(data))
209
+ return These.toOk(data.error);
210
+ if (These.isOk(data))
211
+ return These.toErr(data.value);
212
+ return These.toBoth(data.value, data.error);
213
+ };
214
+ /**
215
+ * Converts a These to an Option.
216
+ * Ok and Both produce Some; Err produces None.
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * These.toOption(These.toOk(42)); // Some(42)
221
+ * These.toOption(These.toBoth("warn", 42)); // Some(42)
222
+ * These.toOption(These.toErr("err")); // None
223
+ * ```
224
+ */
225
+ These.toOption = (data) => These.hasValue(data) ? { kind: "Some", value: data.value } : { kind: "None" };
226
+ /**
227
+ * Converts a These to a Result, discarding any warning from Both.
228
+ * Ok and Both produce Ok; Err produces Err.
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * These.toResult(These.toOk(42)); // Ok(42)
233
+ * These.toResult(These.toBoth("warn", 42)); // Ok(42)
234
+ * These.toResult(These.toErr("err")); // Err("err")
235
+ * ```
236
+ */
237
+ These.toResult = (data) => {
238
+ if (These.hasValue(data))
239
+ return Result.toOk(data.value);
240
+ return data;
241
+ };
242
+ })(These || (These = {}));
@@ -4,5 +4,8 @@ export * from "./Rec.js";
4
4
  export * from "./RemoteData.js";
5
5
  export * from "./Result.js";
6
6
  export * from "./Task.js";
7
+ export * from "./TaskOption.js";
7
8
  export * from "./TaskResult.js";
9
+ export * from "./TaskValidation.js";
10
+ export * from "./These.js";
8
11
  export * from "./Validation.js";