@nlozgachev/pipekit 0.1.6 → 0.1.7

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,15 +2,36 @@
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
7
  ## What is this?
8
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.
9
+ A TypeScript toolkit for expressing uncertainty precisely absent values, fallible operations,
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.
10
30
 
11
31
  ## Do I need to know functional programming to use this?
12
32
 
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:
33
+ No. The library avoids FP-specific jargon wherever possible. You won't find `Monad`, `Functor`, or
34
+ `Applicative` in the API. Instead, you'll work with names that describe what they do:
14
35
 
15
36
  - `Option` — a value that might not exist
16
37
  - `Result` — an operation that can succeed or fail
@@ -24,19 +45,43 @@ You can start using these right away and learn the underlying concepts as you go
24
45
 
25
46
  ### pipekit/Core
26
47
 
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.
48
+ Each of these is both a TypeScript type and a module of functions for working with values of that
49
+ type — constructors, transformations, and ways to extract a value back out.
50
+
51
+ - **`Option<A>`** — a value that might not exist. Replaces `T | null | undefined`. Key operations:
52
+ `fromNullable`, `map`, `chain`, `filter`, `match`, `getOrElse`, `recover`.
53
+ - **`Result<E, A>`** — an operation that succeeds with `A` or fails with `E`. Replaces `try/catch`.
54
+ Key operations: `tryCatch`, `map`, `mapError`, `chain`, `match`, `getOrElse`, `recover`.
55
+ - **`Validation<E, A>`** — like `Result`, but accumulates **all** errors instead of stopping at the
56
+ first. Built for form validation. Key operations: `combine`, `combineAll`, `ap`, `map`, `match`.
57
+ - **`Task<A>`**a lazy async operation that doesn't run until called. Key operations: `from`,
58
+ `map`, `chain`, `all`, `delay`.
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.
37
79
 
38
80
  ### pipekit/Types
39
81
 
82
+ - **`Brand<K, T>`** — nominal typing for values that share the same underlying type. Prevents
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`.
40
85
  - **`NonEmptyList<A>`** — an array guaranteed to have at least one element.
41
86
 
42
87
  ### pipekit/Composition
@@ -47,11 +92,14 @@ Each of these is both a TypeScript type and a module of functions for working wi
47
92
  - **`curry` / `uncurry`** — convert between multi-argument and single-argument functions.
48
93
  - **`tap`** — run a side effect (like logging) without breaking the pipeline.
49
94
  - **`memoize`** — cache function results.
50
- - **`identity`**, **`constant`**, **`not`**, **`and`**, **`or`**, **`once`**, **`flip`** — common function utilities.
95
+ - **`identity`**, **`constant`**, **`not`**, **`and`**, **`or`**, **`once`**, **`flip`** — common
96
+ function utilities.
51
97
 
52
98
  ## What does "composition-centric" mean?
53
99
 
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:
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:
55
103
 
56
104
  ```ts
57
105
  import { Option } from "@nlozgachev/pipekit/Core";
@@ -61,17 +109,17 @@ import { pipe } from "@nlozgachev/pipekit/Composition";
61
109
  const userName = Option.getOrElse(
62
110
  Option.map(
63
111
  Option.fromNullable(users.get("123")),
64
- u => u.name
112
+ (u) => u.name,
65
113
  ),
66
- "Anonymous"
114
+ "Anonymous",
67
115
  );
68
116
 
69
117
  // With pipe: reads top to bottom, matches execution order
70
118
  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
119
+ users.get("123"), // User | undefined
120
+ Option.fromNullable, // Option<User>
121
+ Option.map((u) => u.name), // Option<string>
122
+ Option.getOrElse("Anonymous"), // string
75
123
  );
76
124
  ```
77
125
 
@@ -79,7 +127,8 @@ No method chaining, no class hierarchies. Just functions that connect together.
79
127
 
80
128
  ## What does "data-last" mean and why should I care?
81
129
 
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.
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.
83
132
 
84
133
  ```ts
85
134
  import { Option } from "@nlozgachev/pipekit/Core";
@@ -88,17 +137,17 @@ import { flow } from "@nlozgachev/pipekit/Composition";
88
137
  // Data-first: can't partially apply, so you're stuck writing wrapper functions
89
138
  function formatName(user: User | null): string {
90
139
  const opt = Option.fromNullable(user);
91
- const name = Option.map(opt, u => u.name);
140
+ const name = Option.map(opt, (u) => u.name);
92
141
  return Option.getOrElse(name, "Anonymous");
93
142
  }
94
143
 
95
- users.map(u => formatName(u)); // needs an arrow function wrapper
144
+ users.map((u) => formatName(u)); // needs an arrow function wrapper
96
145
 
97
146
  // Data-last: operations are curried, so flow connects them directly
98
147
  const formatName = flow(
99
148
  Option.fromNullable<User>,
100
- Option.map(u => u.name),
101
- Option.getOrElse("Anonymous")
149
+ Option.map((u) => u.name),
150
+ Option.getOrElse("Anonymous"),
102
151
  );
103
152
 
104
153
  users.map(formatName); // no wrapper — it's already a function of User
@@ -106,7 +155,8 @@ users.map(formatName); // no wrapper — it's already a function of User
106
155
 
107
156
  ## How does this help me write safer code?
108
157
 
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:
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:
110
160
 
111
161
  ```ts
112
162
  // Before: null handling that doesn't scale
@@ -121,19 +171,38 @@ function getDisplayCity(userId: string): string {
121
171
  // After: flat, readable, same guarantees
122
172
  function getDisplayCity(userId: string): string {
123
173
  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
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
130
180
  );
131
181
  }
132
182
  ```
133
183
 
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`.
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
+
187
+ ```ts
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>();
135
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
+ ```
136
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`.
137
206
 
138
207
  ## How do I install it?
139
208
 
@@ -147,7 +216,8 @@ npm add @nlozgachev/pipekit
147
216
 
148
217
  ## How do I get started?
149
218
 
150
- Start with `pipe` and `Option`. These two cover the most common pain point — dealing with values that might not exist:
219
+ Start with `pipe` and `Option`. These two cover the most common pain point — dealing with values
220
+ that might not exist:
151
221
 
152
222
  ```ts
153
223
  import { Option } from "@nlozgachev/pipekit/Core";
@@ -155,28 +225,30 @@ import { pipe } from "@nlozgachev/pipekit/Composition";
155
225
 
156
226
  // Read a user's preferred language from their settings, fall back to the app default
157
227
  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
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
162
232
  );
163
233
  ```
164
234
 
165
- Once that feels natural, reach for `Result` when operations can fail with a meaningful error — parsing, network calls, database lookups:
235
+ Once that feels natural, reach for `Result` when operations can fail with a meaningful error —
236
+ parsing, network calls, database lookups:
166
237
 
167
238
  ```ts
168
239
  import { Result } from "@nlozgachev/pipekit/Core";
169
240
 
170
241
  // Parse user input and look up the record — both steps can fail
171
242
  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>
243
+ parseId(rawInput), // Result<ParseError, number>
244
+ Result.chain((id) => db.find(id)), // Result<ParseError | NotFoundError, Record>
245
+ Result.map((r) => r.name), // Result<ParseError | NotFoundError, string>
175
246
  );
176
247
  ```
177
248
 
178
- And `Validation` when you need to collect multiple errors at once, like form validation.
249
+ And `Validation` when you need to collect multiple errors at once, like form validation. For async
250
+ equivalents of all three, reach for `TaskOption`, `TaskResult`, and `TaskValidation`.
179
251
 
180
252
  ## License
181
253
 
182
- MIT
254
+ 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 = {}));