@semyonf/kamchazky 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Semyon Fomin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,352 @@
1
+ # kamchazky
2
+
3
+ Type-safe `Result` and `Maybe` monads for TypeScript. Explicit error handling and optional values with full type inference — no exceptions required.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ npm install @semyonf/kamchazky
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ `Result<T, E>` is a discriminated union. Check `result.ok` once and TypeScript knows exactly what you have — no `.unwrap()`, no casting, no runtime surprises:
14
+
15
+ ```typescript
16
+ declare const result: Result<User, ApiError>;
17
+
18
+ if (result.ok) {
19
+ result.value; // User
20
+ } else {
21
+ result.error; // ApiError
22
+ }
23
+
24
+ declare const maybe: Maybe<User>;
25
+
26
+ if (maybe.some) {
27
+ maybe.value; // User
28
+ }
29
+
30
+ // Works in ternaries, early returns, switches — anywhere TypeScript does control-flow analysis.
31
+ ```
32
+
33
+ Most Result libraries (including oxide.ts) expose `.ok()` as a *method*, which returns a plain `boolean`. TypeScript sees no connection between that boolean and the original type, so you still have to call `.unwrap()` and hope it doesn't throw, which kinda defeats the purpose. This library uses a discriminant field so the compiler does the work for you.
34
+
35
+ ---
36
+
37
+ ## Result
38
+
39
+ ### Types
40
+
41
+ #### `Result<T, E extends Error = Error>`
42
+
43
+ Union of `OkResult<T> | ErrResult<E>`.
44
+
45
+ #### `OkResult<T>`
46
+
47
+ ```typescript
48
+ type OkResult<T> = {
49
+ readonly ok: true;
50
+ readonly value: T;
51
+ map<U>(fn: (value: T) => U): OkResult<U>;
52
+ flatMap<U, F extends Error = never>(fn: (value: T) => Result<U, F>): Result<U, F>;
53
+ mapError<F extends Error>(fn: (error: never) => F): OkResult<T>;
54
+ inspect(fn: (value: T) => void): OkResult<T>;
55
+ inspectError(fn: (error: never) => void): OkResult<T>;
56
+ };
57
+ ```
58
+
59
+ #### `ErrResult<E extends Error>`
60
+
61
+ ```typescript
62
+ type ErrResult<E extends Error = Error> = {
63
+ readonly ok: false;
64
+ readonly error: E;
65
+ map<U>(fn: (value: never) => U): ErrResult<E>;
66
+ flatMap<U, F extends Error = never>(fn: (value: never) => Result<U, F>): ErrResult<E>;
67
+ mapError<F extends Error>(fn: (error: E) => F): ErrResult<F>;
68
+ inspect(fn: (value: never) => void): ErrResult<E>;
69
+ inspectError(fn: (error: E) => void): ErrResult<E>;
70
+ };
71
+ ```
72
+
73
+ ### API
74
+
75
+ All functions are available on the `Result` namespace and the most common ones (`ok`, `err`, `isOk`, `isErr`, `normalizeError`) are also exported at the top level.
76
+
77
+ #### Creating results
78
+
79
+ ```typescript
80
+ // From values
81
+ Result.ok(42); // OkResult<number>
82
+ Result.err(new Error("fail")); // ErrResult<Error>
83
+
84
+ // From nullable values
85
+ Result.fromNullable(
86
+ await findUser(id),
87
+ () => new NotFoundError(`User ${id} not found`),
88
+ ); // Result<User, NotFoundError>
89
+
90
+ // From predicates (supports type guards)
91
+ Result.fromPredicate(
92
+ value,
93
+ (v): v is string => typeof v === "string",
94
+ () => new TypeError("expected string"),
95
+ ); // Result<string, TypeError>
96
+
97
+ // From functions that may throw
98
+ Result.tryCatch(() => JSON.parse(input));
99
+ Result.tryCatchAsync(() => fetch("/api").then(r => r.json()));
100
+
101
+ // From promises that may reject
102
+ Result.fromPromise(fetch("/api"));
103
+ Result.fromPromise(
104
+ fetch("/api"),
105
+ (e) => new NetworkError(String(e)),
106
+ ); // Promise<Result<Response, NetworkError>>
107
+
108
+ // With custom error mapping
109
+ Result.tryCatch(
110
+ () => JSON.parse(input),
111
+ (e) => new ParseError(String(e)),
112
+ ); // Result<unknown, ParseError>
113
+ ```
114
+
115
+ #### Transforming
116
+
117
+ ```typescript
118
+ // Transform the value
119
+ Result.map(result, (value) => value * 2);
120
+
121
+ // Transform the error
122
+ Result.mapError(result, (e) => new AppError(e.message));
123
+
124
+ // Chain Result-returning operations
125
+ Result.flatMap(result, (value) => validate(value));
126
+
127
+ // Recover from errors
128
+ Result.orElse(result, (error) => ok(defaultValue));
129
+
130
+ // Flatten nested Results (inner and outer error types can differ)
131
+ Result.flatten(ok(ok(42))); // ok(42)
132
+
133
+ // Observe values without transforming (useful for logging)
134
+ Result.inspect(result, (value) => console.log("got", value));
135
+ Result.inspectError(result, (error) => console.error(error));
136
+ ```
137
+
138
+ All transformations are also available as instance methods for chaining:
139
+
140
+ ```typescript
141
+ ok(10)
142
+ .inspect((x) => console.log("start:", x))
143
+ .map((x) => x + 5)
144
+ .flatMap((x) => x > 10 ? ok(x) : err(new Error("too small")))
145
+ .mapError((e) => new AppError(e.message))
146
+ .inspectError((e) => console.error(e));
147
+ ```
148
+
149
+ #### Extracting values
150
+
151
+ ```typescript
152
+ Result.unwrap(result); // returns value or throws error
153
+ Result.unwrapOr(result, fallback); // returns value or fallback (same type)
154
+ Result.expect(result, "msg"); // returns value or throws with message
155
+ ```
156
+
157
+ `unwrapOr` requires the fallback to be the same type `T` as the success value. Use `match` when you need a different return type.
158
+
159
+ #### Pattern matching
160
+
161
+ ```typescript
162
+ Result.match(result, {
163
+ ok: (value) => `Success: ${value}`,
164
+ err: (error) => `Error: ${error.message}`,
165
+ });
166
+ ```
167
+
168
+ #### Combining results
169
+
170
+ ```typescript
171
+ // Combine multiple results — fail-fast, returns first error
172
+ const r = Result.all(fetchUser(id), fetchPosts(id));
173
+ // Result<[User, Post[]], Error>
174
+
175
+ // Async variant — resolves all promises concurrently
176
+ const r = await Result.allAsync(fetchUserAsync(id), fetchPostsAsync(id));
177
+ // Result<[User, Post[]], Error>
178
+
179
+ // Collect all errors instead of failing fast
180
+ const r = Result.collect(
181
+ validateName(input.name),
182
+ validateEmail(input.email),
183
+ validateAge(input.age),
184
+ ); // Result<[Name, Email, Age], AggregateError>
185
+ // On failure: r.error.errors contains all individual errors
186
+ ```
187
+
188
+ `all` and `collect` use exact tuple inference — pass results as individual arguments. Spreading an array (`...arr`) loses the tuple types and degrades values to `unknown[]`.
189
+
190
+ #### Error normalization
191
+
192
+ ```typescript
193
+ Result.normalizeError(new TypeError("t")); // returns the TypeError as-is
194
+ Result.normalizeError("oops"); // new Error("oops")
195
+ Result.normalizeError(42); // new Error("42")
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Maybe
201
+
202
+ ### Types
203
+
204
+ #### `Maybe<T>`
205
+
206
+ Union of `Some<T> | None`.
207
+
208
+ #### `Some<T>`
209
+
210
+ ```typescript
211
+ type Some<T> = {
212
+ readonly some: true;
213
+ readonly value: T;
214
+ map<U>(fn: (value: T) => U): Some<U>;
215
+ flatMap<U>(fn: (value: T) => Maybe<U>): Maybe<U>;
216
+ filter(predicate: (value: T) => boolean): Maybe<T>;
217
+ inspect(fn: (value: T) => void): Some<T>;
218
+ };
219
+ ```
220
+
221
+ #### `None`
222
+
223
+ ```typescript
224
+ type None = {
225
+ readonly some: false;
226
+ map<U>(fn: (value: never) => U): None;
227
+ flatMap<U>(fn: (value: never) => Maybe<U>): None;
228
+ filter(predicate: (value: never) => boolean): None;
229
+ inspect(fn: (value: never) => void): None;
230
+ };
231
+ ```
232
+
233
+ ### API
234
+
235
+ All functions are available on the `Maybe` namespace and the most common ones (`some`, `none`, `isSome`, `isNone`) are also exported at the top level.
236
+
237
+ #### Creating maybes
238
+
239
+ ```typescript
240
+ // From values
241
+ Maybe.some(42); // Some<number>
242
+ Maybe.none(); // None
243
+
244
+ // From nullable values (no error factory needed, unlike Result)
245
+ Maybe.fromNullable(document.getElementById("app")); // Maybe<HTMLElement>
246
+
247
+ // From predicates (supports type guards)
248
+ Maybe.fromPredicate(
249
+ value,
250
+ (v): v is string => typeof v === "string",
251
+ ); // Maybe<string>
252
+ ```
253
+
254
+ #### Transforming
255
+
256
+ ```typescript
257
+ // Transform the value
258
+ Maybe.map(maybe, (value) => value * 2);
259
+
260
+ // Chain Maybe-returning operations
261
+ Maybe.flatMap(maybe, (value) => findById(value));
262
+
263
+ // Flatten nested Maybes
264
+ Maybe.flatten(some(some(42))); // some(42)
265
+
266
+ // Filter — keep Some only if predicate passes (supports type guards)
267
+ Maybe.filter(maybe, (x) => x > 0);
268
+
269
+ // Observe values without transforming
270
+ Maybe.inspect(maybe, (value) => console.log("got", value));
271
+ ```
272
+
273
+ All transformations are also available as instance methods for chaining:
274
+
275
+ ```typescript
276
+ some(10)
277
+ .inspect((x) => console.log("start:", x))
278
+ .map((x) => x + 5)
279
+ .flatMap((x) => x > 10 ? some(x) : none())
280
+ .filter((x) => x < 100);
281
+ ```
282
+
283
+ #### Extracting values
284
+
285
+ ```typescript
286
+ Maybe.unwrap(maybe); // returns value or throws
287
+ Maybe.unwrapOr(maybe, fallback); // returns value or fallback (same type)
288
+ Maybe.expect(maybe, "msg"); // returns value or throws with message
289
+ ```
290
+
291
+ #### Pattern matching
292
+
293
+ ```typescript
294
+ Maybe.match(maybe, {
295
+ some: (value) => `Found: ${value}`,
296
+ none: () => "Not found",
297
+ });
298
+ ```
299
+
300
+ #### Combining maybes
301
+
302
+ ```typescript
303
+ // Combine multiple maybes — returns None if any is None
304
+ const m = Maybe.all(findUser(id), findSettings(id));
305
+ // Maybe<[User, Settings]>
306
+
307
+ // Return the first Some found
308
+ const m = Maybe.firstSome(fromCache(id), fromDb(id), defaultValue);
309
+ // Maybe<User>
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Interop
315
+
316
+ Convert between `Result` and `Maybe`:
317
+
318
+ ```typescript
319
+ // Result → Maybe (discards the error)
320
+ Result.toMaybe(ok(42)); // some(42)
321
+ Result.toMaybe(err(new Error())); // none()
322
+
323
+ // Maybe → Result (requires an error for the None case)
324
+ Result.fromMaybe(some(42), new Error("missing")); // ok(42)
325
+ Result.fromMaybe(none(), new Error("missing")); // err(Error("missing"))
326
+
327
+ // Same operations from the Maybe side
328
+ Maybe.fromResult(ok(42)); // some(42)
329
+ Maybe.toResult(some(42), new Error("missing")); // ok(42)
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Design Decisions
335
+
336
+ **Discriminant fields, not methods** — `result.ok` and `maybe.some` are boolean fields, not methods. TypeScript's control-flow analysis works directly with discriminant fields, giving you automatic narrowing without `.unwrap()`.
337
+
338
+ **Error type must extend `Error`** — prevents using strings or arbitrary values as errors while allowing custom Error subclasses.
339
+
340
+ **Singleton `None`** — `none()` always returns the same frozen object. Safe to compare with `===`.
341
+
342
+ **Fail-fast `all()`** — returns the first error/None rather than collecting all errors. Use `Result.collect()` when you need all errors (e.g., validation).
343
+
344
+ **`collect()` uses `AggregateError`** — a standard ES2021 `Error` subclass with an `.errors` array, satisfying the `E extends Error` constraint without custom types.
345
+
346
+ **`flatten()` supports different error types** — inner and outer `Result`s can have different error types; the result unions them (`E | F`).
347
+
348
+ **No error type on `OkResult` / no value on `None`** — keeps signatures clean and prevents ghost types from polluting the wrong branch.
349
+
350
+ ## License
351
+
352
+ MIT