@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 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
+ ![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)
10
+ ![Runtime](https://img.shields.io/badge/Runtime-Node%20%7C%20Browser-green)
11
+ ![License](https://img.shields.io/badge/License-MIT-yellow.svg)
12
+ ![Beta](https://img.shields.io/badge/Status-Beta-orange.svg)
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.