@shirudo/result 0.0.1
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 +269 -0
- package/dist/index.cjs +1563 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1033 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +1033 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1499 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# @shirudo/result
|
|
2
|
+
|
|
3
|
+
**Robust, type-safe error handling for TypeScript.**
|
|
4
|
+
|
|
5
|
+
> β οΈ **Beta Notice**: This library is currently in beta. The API may change before the stable release. Use with caution in production environments.
|
|
6
|
+
|
|
7
|
+
`@shirudo/result` brings the power of the Result pattern (Monad) to TypeScript. It helps you write safer, more predictable code by treating errors as values rather than exceptions. Stop guessing if a function will throwβlet the type system guide you.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
## π Key Features
|
|
15
|
+
|
|
16
|
+
- **Type-Safe:** generic `Result<T, E>` type discriminates between Success (`Ok`) and Failure (`Err`).
|
|
17
|
+
- **Pipeable Architecture:** Functional, tree-shakeable operators via `.pipe()` and `.pipeAsync()`.
|
|
18
|
+
- **Async Support:** First-class support for Promises and async transformations.
|
|
19
|
+
- **Do-Notation:** A `task` generator utility to write sequential code without callback hell (similar to Rust's `?` operator).
|
|
20
|
+
- **Rich Pattern Matching:** Fluent builders for exhaustive matching and error handling.
|
|
21
|
+
- **Comprehensive Utilities:** Helpers for collections, conversion from/to Promises, Nullables, and try/catch blocks.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## π¦ Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @shirudo/result
|
|
29
|
+
# or
|
|
30
|
+
pnpm add @shirudo/result
|
|
31
|
+
# or
|
|
32
|
+
yarn add @shirudo/result
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## π Quick Start
|
|
38
|
+
|
|
39
|
+
### Basic Usage
|
|
40
|
+
|
|
41
|
+
Instead of throwing errors, return a `Result`.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { ok, err, Result } from "@shirudo/result";
|
|
45
|
+
|
|
46
|
+
function divide(a: number, b: number): Result<number, string> {
|
|
47
|
+
if (b === 0) {
|
|
48
|
+
return err("Division by zero");
|
|
49
|
+
}
|
|
50
|
+
return ok(a / b);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = divide(10, 2);
|
|
54
|
+
|
|
55
|
+
if (result.isOk()) {
|
|
56
|
+
// TypeScript narrows 'result' to Ok<number>
|
|
57
|
+
console.log("Success:", result.value); // 5
|
|
58
|
+
} else {
|
|
59
|
+
// TypeScript narrows 'result' to Err<string>
|
|
60
|
+
console.error("Error:", result.error);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Functional Pipelines
|
|
65
|
+
|
|
66
|
+
Use `.pipe()` to chain operations. If an error occurs at any step, the chain short-circuits and returns the `Err`.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { ok, map, filter, mapErr } from "@shirudo/result";
|
|
70
|
+
|
|
71
|
+
const processed = ok(10).pipe(
|
|
72
|
+
map((n) => n * 2), // 20
|
|
73
|
+
filter(
|
|
74
|
+
(n) => n > 50,
|
|
75
|
+
() => "Too small"
|
|
76
|
+
), // Returns Err('Too small')
|
|
77
|
+
mapErr((e) => `Error: ${e}`) // Transforms the error message
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
console.log(processed.isErr()); // true
|
|
81
|
+
console.log(processed.unwrapOr(0)); // 0 (fallback)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## π‘ Core Concepts
|
|
87
|
+
|
|
88
|
+
### Creating Results
|
|
89
|
+
|
|
90
|
+
There are several static factories to help you wrap existing code or values.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { Result } from "@shirudo/result";
|
|
94
|
+
|
|
95
|
+
// Standard
|
|
96
|
+
const a = Result.ok(42);
|
|
97
|
+
const b = Result.err("Something went wrong");
|
|
98
|
+
|
|
99
|
+
// From a function that might throw
|
|
100
|
+
const json = Result.try(() => JSON.parse('{"valid": true}'));
|
|
101
|
+
|
|
102
|
+
// From a potentially null/undefined value
|
|
103
|
+
const user = Result.fromNullable(maybeUser, "User not found");
|
|
104
|
+
|
|
105
|
+
// From a Promise (catches rejections)
|
|
106
|
+
const asyncRes = await Result.fromPromise(fetch("/api/data"));
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Async Pipelines
|
|
110
|
+
|
|
111
|
+
Transforming async results is seamless with `.pipeAsync()`.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { ok, mapAsync, tryCatchAsync } from "@shirudo/result";
|
|
115
|
+
|
|
116
|
+
const result = await ok(1).pipeAsync(
|
|
117
|
+
mapAsync(async (id) => {
|
|
118
|
+
const user = await db.getUser(id);
|
|
119
|
+
return user.name;
|
|
120
|
+
}),
|
|
121
|
+
tryCatchAsync(async (name) => {
|
|
122
|
+
// If this throws, it becomes an Err
|
|
123
|
+
return await externalService.validate(name);
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Generator "Do-Notation" (`task`)
|
|
129
|
+
|
|
130
|
+
The `task` (or `gen`) utility allows you to write code that looks imperative but handles `Result` flow control automatically. Use `yield*` to unwrap `Ok` values; if an `Err` is yielded, the function returns early with that error.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { task, ok, err } from "@shirudo/result";
|
|
134
|
+
|
|
135
|
+
const calculate = task(function* () {
|
|
136
|
+
// yield* automatically unwraps the value if Ok
|
|
137
|
+
const x = yield* ok(10);
|
|
138
|
+
const y = yield* ok(20);
|
|
139
|
+
|
|
140
|
+
// If this were err(), execution would stop here and return that err
|
|
141
|
+
const z = yield* validate(x + y);
|
|
142
|
+
|
|
143
|
+
return z; // Returns Ok(z)
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// calculate is a Promise<Result<number, Error>>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Folding Results
|
|
150
|
+
|
|
151
|
+
The simplest way to handle both `Ok` and `Err` cases and return a single value:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { ok, err } from "@shirudo/result";
|
|
155
|
+
|
|
156
|
+
const result = ok(42);
|
|
157
|
+
|
|
158
|
+
const message = result.fold(
|
|
159
|
+
(val) => `Success: ${val}`,
|
|
160
|
+
(err) => `Error: ${err}`
|
|
161
|
+
);
|
|
162
|
+
// message = "Success: 42"
|
|
163
|
+
|
|
164
|
+
// Useful for side effects
|
|
165
|
+
result.fold(
|
|
166
|
+
(val) => console.log("Yay:", val),
|
|
167
|
+
(err) => console.error("Nay:", err)
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Convert to HTTP response
|
|
171
|
+
const response = result.fold(
|
|
172
|
+
(data) => ({ status: 200, body: data }),
|
|
173
|
+
(error) => ({ status: 500, body: { error } })
|
|
174
|
+
);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Pattern Matching
|
|
178
|
+
|
|
179
|
+
Handle errors exhaustively using the fluent matching API for complex error types. You can match by Error class (`.err`) or by primitive value (`.errVal`).
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { Result } from "@shirudo/result";
|
|
183
|
+
|
|
184
|
+
class NetworkError extends Error {}
|
|
185
|
+
class ValidationError extends Error {}
|
|
186
|
+
|
|
187
|
+
const result = Result.err(new NetworkError("Timeout"));
|
|
188
|
+
|
|
189
|
+
const message = result
|
|
190
|
+
.match()
|
|
191
|
+
.err(NetworkError, (e) => `Retry later: ${e.message}`)
|
|
192
|
+
.err(ValidationError, (e) => `Invalid input: ${e.message}`)
|
|
193
|
+
.errVal("TIMEOUT_CODE", () => "Operation timed out") // Match primitive values
|
|
194
|
+
.ok((val) => `Success: ${val}`)
|
|
195
|
+
.run();
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**When to use what:**
|
|
199
|
+
|
|
200
|
+
- Use `.fold()` for simple cases where you handle both Ok and Err
|
|
201
|
+
- Use `.match()` for complex pattern matching on multiple error types
|
|
202
|
+
- Use `fold()` pipe operator for functional composition in pipelines
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## π API Reference
|
|
207
|
+
|
|
208
|
+
### Creation & Conversions
|
|
209
|
+
|
|
210
|
+
- `ok(value)` / `err(error)`: Create basic instances.
|
|
211
|
+
- `Result.try(fn)`: Execute a sync function; catches exceptions as `Err`.
|
|
212
|
+
- `Result.fromNullable(val, fallback)`: Convert `null | undefined` to `Err`.
|
|
213
|
+
- `Result.fromPromise(promise)`: Convert a Promise to `Promise<Result>`.
|
|
214
|
+
- `.toPromise()`: Convert `Ok` to resolved Promise, `Err` to rejected.
|
|
215
|
+
- `.toNullable()`: Convert `Ok` to value, `Err` to `null`.
|
|
216
|
+
|
|
217
|
+
### Instance Methods
|
|
218
|
+
|
|
219
|
+
- `.isOk()`: Type guard for success.
|
|
220
|
+
- `.isErr()`: Type guard for failure.
|
|
221
|
+
- `.unwrap()`: Get value or throw (use carefully).
|
|
222
|
+
- `.unwrapErr()`: Get error or throw (use carefully).
|
|
223
|
+
- `.unwrapOr(default)`: Get value or return default.
|
|
224
|
+
- `.unwrapOrElse(fn)`: Get value or generate default from error.
|
|
225
|
+
- `.expect(msg)`: Get value or throw with specific message.
|
|
226
|
+
- `.expectErr(msg)`: Get error or throw with specific message.
|
|
227
|
+
- `.fold(onOk, onErr)`: Handle both cases and return a single value.
|
|
228
|
+
- `.pipe(...)`: Chain operators synchronously.
|
|
229
|
+
- `.pipeAsync(...)`: Chain operators asynchronously.
|
|
230
|
+
- `.match()`: Start a fluent pattern matching builder.
|
|
231
|
+
|
|
232
|
+
### Pipeable Operators
|
|
233
|
+
|
|
234
|
+
Import these from the root package to use inside `.pipe()`.
|
|
235
|
+
|
|
236
|
+
| Operator | Description |
|
|
237
|
+
| :--------------------- | :------------------------------------------------------- |
|
|
238
|
+
| `map(fn)` | Transform the `Ok` value. |
|
|
239
|
+
| `mapErr(fn)` | Transform the `Err` value. |
|
|
240
|
+
| `mapBoth(fnOk, fnErr)` | Transform both sides. |
|
|
241
|
+
| `flatMap(fn)` | Chain a function that returns a `Result` (monadic bind). |
|
|
242
|
+
| `filter(pred, errFn)` | Turn `Ok` into `Err` if predicate fails. |
|
|
243
|
+
| `tap(observer)` | Run side effects (logging) without changing the result. |
|
|
244
|
+
| `recover(val)` | Convert `Err` to `Ok` with a default value. |
|
|
245
|
+
| `tryCatch(fn)` | Run a function, catching exceptions into `Err`. |
|
|
246
|
+
| `tryMap(fn)` | Like `map`, but catches exceptions. |
|
|
247
|
+
| `fold({ ok, err })` | Terminate the pipe and return a value based on state. |
|
|
248
|
+
|
|
249
|
+
**Async Variants:** `mapAsync`, `mapErrAsync`, `flatMapAsync`, `filterAsync`, `tapAsync`, `tryCatchAsync`, `tryMapAsync`, `foldAsync`.
|
|
250
|
+
|
|
251
|
+
### Collections
|
|
252
|
+
|
|
253
|
+
- `sequence(results)`: Turn `Result[]` into `Result<T[]>`. First error stops the process.
|
|
254
|
+
- `sequenceRecord(record)`: Like `sequence`, but for objects (`{ a: Result, b: Result }` β `Result<{ a, b }>`).
|
|
255
|
+
- `collectFirstOk(results)`: Find the first success, or return all errors.
|
|
256
|
+
- `collectAllErrors(results)`: Returns `Ok(values)` only if all are Ok, otherwise collects _all_ errors.
|
|
257
|
+
- `partition(results)`: Separate a list into arrays of `[oks, errs]`.
|
|
258
|
+
- `flatten(result)`: Flattens a nested `Result<Result<T, E>, E>` into `Result<T, E>`.
|
|
259
|
+
- `zip(r1, r2)`: Combine two results into a tuple.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## π€ Contributing
|
|
264
|
+
|
|
265
|
+
We welcome contributions! Please follow the standard pull request process. Ensure usage of TypeScript and Vitest for testing.
|
|
266
|
+
|
|
267
|
+
## π License
|
|
268
|
+
|
|
269
|
+
This project is licensed under the MIT License.
|