rsult 1.4.0 → 2.0.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.
@@ -0,0 +1,485 @@
1
+ import { ResultAsync, tryAsync, liftAsync, allAsync } from './result-async';
2
+ import { Result, Ok, Err } from './result';
3
+
4
+ describe('ResultAsync', () => {
5
+ describe('Constructors', () => {
6
+ it('ok() creates successful ResultAsync', async () => {
7
+ const result = await ResultAsync.ok(42);
8
+ expect(result.is_ok()).toBe(true);
9
+ expect(result.unwrap()).toBe(42);
10
+ });
11
+
12
+ it('err() creates failed ResultAsync', async () => {
13
+ const result = await ResultAsync.err('error');
14
+ expect(result.is_err()).toBe(true);
15
+ expect(result.unwrap_err()).toBe('error');
16
+ });
17
+
18
+ it('fromResult() wraps a sync Result', async () => {
19
+ const result = await ResultAsync.fromResult(Ok(42));
20
+ expect(result.unwrap()).toBe(42);
21
+ });
22
+
23
+ it('fromResult() wraps a Promise<Result>', async () => {
24
+ const result = await ResultAsync.fromResult(Promise.resolve(Ok(42)));
25
+ expect(result.unwrap()).toBe(42);
26
+ });
27
+
28
+ it('fromPromise() converts resolved promise to Ok', async () => {
29
+ const result = await ResultAsync.fromPromise(Promise.resolve(42));
30
+ expect(result.is_ok()).toBe(true);
31
+ expect(result.unwrap()).toBe(42);
32
+ });
33
+
34
+ it('fromPromise() converts rejected promise to Err', async () => {
35
+ const error = new Error('failed');
36
+ const result = await ResultAsync.fromPromise(Promise.reject(error));
37
+ expect(result.is_err()).toBe(true);
38
+ expect(result.unwrap_err()).toBe(error);
39
+ });
40
+
41
+ it('fromPromise() applies error mapping function', async () => {
42
+ const result = await ResultAsync.fromPromise(
43
+ Promise.reject(new Error('original')),
44
+ (e) => ({ type: 'mapped', original: e })
45
+ );
46
+ expect(result.is_err()).toBe(true);
47
+ expect(result.unwrap_err()).toEqual({ type: 'mapped', original: expect.any(Error) });
48
+ });
49
+
50
+ it('try() catches sync errors', async () => {
51
+ const result = await ResultAsync.try(() => {
52
+ throw new Error('sync error');
53
+ });
54
+ expect(result.is_err()).toBe(true);
55
+ });
56
+
57
+ it('try() catches async errors', async () => {
58
+ const result = await ResultAsync.try(async () => {
59
+ throw new Error('async error');
60
+ });
61
+ expect(result.is_err()).toBe(true);
62
+ });
63
+
64
+ it('try() returns Ok on success', async () => {
65
+ const result = await ResultAsync.try(async () => 42);
66
+ expect(result.is_ok()).toBe(true);
67
+ expect(result.unwrap()).toBe(42);
68
+ });
69
+ });
70
+
71
+ describe('Transformations', () => {
72
+ it('map() transforms Ok value with sync function', async () => {
73
+ const result = await ResultAsync.ok(5).map(x => x * 2);
74
+ expect(result.unwrap()).toBe(10);
75
+ });
76
+
77
+ it('map() transforms Ok value with async function', async () => {
78
+ const result = await ResultAsync.ok(5).map(async x => x * 2);
79
+ expect(result.unwrap()).toBe(10);
80
+ });
81
+
82
+ it('map() skips transformation on Err', async () => {
83
+ const result = await ResultAsync.err<number, string>('error').map(x => x * 2);
84
+ expect(result.is_err()).toBe(true);
85
+ expect(result.unwrap_err()).toBe('error');
86
+ });
87
+
88
+ it('mapErr() transforms Err value', async () => {
89
+ const result = await ResultAsync.err('error').mapErr(e => e.toUpperCase());
90
+ expect(result.unwrap_err()).toBe('ERROR');
91
+ });
92
+
93
+ it('mapErr() skips transformation on Ok', async () => {
94
+ const result = await ResultAsync.ok(42).mapErr(e => 'mapped');
95
+ expect(result.is_ok()).toBe(true);
96
+ expect(result.unwrap()).toBe(42);
97
+ });
98
+
99
+ it('mapOr() returns mapped value on Ok', async () => {
100
+ const value = await ResultAsync.ok(5).mapOr(0, x => x * 2);
101
+ expect(value).toBe(10);
102
+ });
103
+
104
+ it('mapOr() returns default on Err', async () => {
105
+ const value = await ResultAsync.err<number, string>('error').mapOr(0, x => x * 2);
106
+ expect(value).toBe(0);
107
+ });
108
+
109
+ it('mapOrElse() computes from error on Err', async () => {
110
+ const value = await ResultAsync.err<number, string>('error').mapOrElse(
111
+ e => e.length,
112
+ x => x * 2
113
+ );
114
+ expect(value).toBe(5);
115
+ });
116
+ });
117
+
118
+ describe('Chaining', () => {
119
+ it('andThen() chains sync Result', async () => {
120
+ const result = await ResultAsync.ok(5)
121
+ .andThen(x => Ok(x * 2));
122
+ expect(result.unwrap()).toBe(10);
123
+ });
124
+
125
+ it('andThen() chains ResultAsync', async () => {
126
+ const result = await ResultAsync.ok(5)
127
+ .andThen(x => ResultAsync.ok(x * 2));
128
+ expect(result.unwrap()).toBe(10);
129
+ });
130
+
131
+ it('andThen() propagates Err', async () => {
132
+ const result = await ResultAsync.ok(5)
133
+ .andThen(x => Err('failed') as Result<number, string>);
134
+ expect(result.is_err()).toBe(true);
135
+ expect(result.unwrap_err()).toBe('failed');
136
+ });
137
+
138
+ it('andThen() skips on initial Err', async () => {
139
+ let called = false;
140
+ const result = await ResultAsync.err<number, string>('initial')
141
+ .andThen(x => {
142
+ called = true;
143
+ return Ok(x * 2);
144
+ });
145
+ expect(called).toBe(false);
146
+ expect(result.unwrap_err()).toBe('initial');
147
+ });
148
+
149
+ it('orElse() recovers from Err', async () => {
150
+ const result = await ResultAsync.err<number, string>('error')
151
+ .orElse(e => Ok(42));
152
+ expect(result.is_ok()).toBe(true);
153
+ expect(result.unwrap()).toBe(42);
154
+ });
155
+
156
+ it('orElse() skips on Ok', async () => {
157
+ let called = false;
158
+ const result = await ResultAsync.ok<number, string>(42)
159
+ .orElse(e => {
160
+ called = true;
161
+ return Ok(0);
162
+ });
163
+ expect(called).toBe(false);
164
+ expect(result.unwrap()).toBe(42);
165
+ });
166
+
167
+ it('and() returns other on Ok', async () => {
168
+ const result = await ResultAsync.ok(1).and(ResultAsync.ok('hello'));
169
+ expect(result.unwrap()).toBe('hello');
170
+ });
171
+
172
+ it('and() returns Err on initial Err', async () => {
173
+ const result = await ResultAsync.err<number, string>('error')
174
+ .and(ResultAsync.ok('hello'));
175
+ expect(result.is_err()).toBe(true);
176
+ });
177
+
178
+ it('or() returns self on Ok', async () => {
179
+ const result = await ResultAsync.ok<number, string>(42)
180
+ .or(ResultAsync.ok(0));
181
+ expect(result.unwrap()).toBe(42);
182
+ });
183
+
184
+ it('or() returns other on Err', async () => {
185
+ const result = await ResultAsync.err<number, string>('error')
186
+ .or(ResultAsync.ok(0));
187
+ expect(result.unwrap()).toBe(0);
188
+ });
189
+ });
190
+
191
+ describe('Unwrapping', () => {
192
+ it('expect() returns value on Ok', async () => {
193
+ const value = await ResultAsync.ok(42).expect('should not fail');
194
+ expect(value).toBe(42);
195
+ });
196
+
197
+ it('expect() throws on Err', async () => {
198
+ await expect(ResultAsync.err('error').expect('custom message'))
199
+ .rejects.toThrow('custom message');
200
+ });
201
+
202
+ it('unwrap() returns value on Ok', async () => {
203
+ const value = await ResultAsync.ok(42).unwrap();
204
+ expect(value).toBe(42);
205
+ });
206
+
207
+ it('unwrap() throws on Err', async () => {
208
+ await expect(ResultAsync.err('error').unwrap()).rejects.toThrow();
209
+ });
210
+
211
+ it('unwrapOr() returns value on Ok', async () => {
212
+ const value = await ResultAsync.ok(42).unwrapOr(0);
213
+ expect(value).toBe(42);
214
+ });
215
+
216
+ it('unwrapOr() returns default on Err', async () => {
217
+ const value = await ResultAsync.err<number, string>('error').unwrapOr(0);
218
+ expect(value).toBe(0);
219
+ });
220
+
221
+ it('unwrapOrElse() computes from error', async () => {
222
+ const value = await ResultAsync.err<number, string>('error')
223
+ .unwrapOrElse(e => e.length);
224
+ expect(value).toBe(5);
225
+ });
226
+ });
227
+
228
+ describe('Inspection', () => {
229
+ it('inspect() calls function on Ok', async () => {
230
+ let inspected: number | null = null;
231
+ await ResultAsync.ok(42).inspect(x => { inspected = x; });
232
+ expect(inspected).toBe(42);
233
+ });
234
+
235
+ it('inspect() skips on Err', async () => {
236
+ let called = false;
237
+ await ResultAsync.err<number, string>('error').inspect(() => { called = true; });
238
+ expect(called).toBe(false);
239
+ });
240
+
241
+ it('inspectErr() calls function on Err', async () => {
242
+ let inspected: string | null = null;
243
+ await ResultAsync.err('error').inspectErr(e => { inspected = e; });
244
+ expect(inspected).toBe('error');
245
+ });
246
+
247
+ it('inspectErr() skips on Ok', async () => {
248
+ let called = false;
249
+ await ResultAsync.ok(42).inspectErr(() => { called = true; });
250
+ expect(called).toBe(false);
251
+ });
252
+ });
253
+
254
+ describe('Conversion', () => {
255
+ it('ok() returns Some on Ok', async () => {
256
+ const option = await ResultAsync.ok(42).ok();
257
+ expect(option.is_some()).toBe(true);
258
+ expect(option.unwrap()).toBe(42);
259
+ });
260
+
261
+ it('ok() returns None on Err', async () => {
262
+ const option = await ResultAsync.err('error').ok();
263
+ expect(option.is_none()).toBe(true);
264
+ });
265
+
266
+ it('err() returns Some on Err', async () => {
267
+ const option = await ResultAsync.err('error').err();
268
+ expect(option.is_some()).toBe(true);
269
+ expect(option.unwrap()).toBe('error');
270
+ });
271
+
272
+ it('err() returns None on Ok', async () => {
273
+ const option = await ResultAsync.ok(42).err();
274
+ expect(option.is_none()).toBe(true);
275
+ });
276
+
277
+ it('flatten() unwraps nested Result', async () => {
278
+ const nested = ResultAsync.ok(Ok(42));
279
+ const flattened = await nested.flatten();
280
+ expect(flattened.unwrap()).toBe(42);
281
+ });
282
+
283
+ it('flatten() propagates outer Err', async () => {
284
+ const nested = ResultAsync.err<Result<number, string>, string>('outer');
285
+ const flattened = await nested.flatten();
286
+ expect(flattened.unwrap_err()).toBe('outer');
287
+ });
288
+
289
+ it('flatten() propagates inner Err', async () => {
290
+ const nested = ResultAsync.ok(Err('inner') as Result<number, string>);
291
+ const flattened = await nested.flatten();
292
+ expect(flattened.unwrap_err()).toBe('inner');
293
+ });
294
+ });
295
+
296
+ describe('Pattern Matching', () => {
297
+ it('match() calls Ok handler on Ok', async () => {
298
+ const result = await ResultAsync.ok(42).match({
299
+ Ok: v => `value: ${v}`,
300
+ Err: e => `error: ${e}`,
301
+ });
302
+ expect(result).toBe('value: 42');
303
+ });
304
+
305
+ it('match() calls Err handler on Err', async () => {
306
+ const result = await ResultAsync.err('failed').match({
307
+ Ok: v => `value: ${v}`,
308
+ Err: e => `error: ${e}`,
309
+ });
310
+ expect(result).toBe('error: failed');
311
+ });
312
+
313
+ it('match() supports async handlers', async () => {
314
+ const result = await ResultAsync.ok(42).match({
315
+ Ok: async v => `value: ${v}`,
316
+ Err: async e => `error: ${e}`,
317
+ });
318
+ expect(result).toBe('value: 42');
319
+ });
320
+ });
321
+
322
+ describe('Static Combinators', () => {
323
+ it('all() combines all Ok results', async () => {
324
+ const result = await ResultAsync.all([
325
+ ResultAsync.ok(1),
326
+ ResultAsync.ok(2),
327
+ ResultAsync.ok(3),
328
+ ]);
329
+ expect(result.is_ok()).toBe(true);
330
+ expect(result.unwrap()).toEqual([1, 2, 3]);
331
+ });
332
+
333
+ it('all() short-circuits on first Err', async () => {
334
+ const result = await ResultAsync.all([
335
+ ResultAsync.ok(1),
336
+ ResultAsync.err('failed'),
337
+ ResultAsync.ok(3),
338
+ ]);
339
+ expect(result.is_err()).toBe(true);
340
+ expect(result.unwrap_err()).toBe('failed');
341
+ });
342
+
343
+ it('allSettled() collects all errors', async () => {
344
+ const result = await ResultAsync.allSettled([
345
+ ResultAsync.ok(1),
346
+ ResultAsync.err('first'),
347
+ ResultAsync.err('second'),
348
+ ]);
349
+ expect(result.is_err()).toBe(true);
350
+ expect(result.unwrap_err()).toEqual(['first', 'second']);
351
+ });
352
+
353
+ it('allSettled() returns all values on success', async () => {
354
+ const result = await ResultAsync.allSettled([
355
+ ResultAsync.ok(1),
356
+ ResultAsync.ok(2),
357
+ ]);
358
+ expect(result.is_ok()).toBe(true);
359
+ expect(result.unwrap()).toEqual([1, 2]);
360
+ });
361
+ });
362
+
363
+ describe('Type Checking', () => {
364
+ it('isOk() returns true for Ok', async () => {
365
+ expect(await ResultAsync.ok(42).isOk()).toBe(true);
366
+ });
367
+
368
+ it('isOk() returns false for Err', async () => {
369
+ expect(await ResultAsync.err('error').isOk()).toBe(false);
370
+ });
371
+
372
+ it('isErr() returns true for Err', async () => {
373
+ expect(await ResultAsync.err('error').isErr()).toBe(true);
374
+ });
375
+
376
+ it('isErr() returns false for Ok', async () => {
377
+ expect(await ResultAsync.ok(42).isErr()).toBe(false);
378
+ });
379
+ });
380
+
381
+ describe('toAsync() extension', () => {
382
+ it('Ok.toAsync() returns ResultAsync', async () => {
383
+ const result = await Ok(42).toAsync();
384
+ expect(result.is_ok()).toBe(true);
385
+ expect(result.unwrap()).toBe(42);
386
+ });
387
+
388
+ it('Err.toAsync() returns ResultAsync', async () => {
389
+ const result = await Err('error').toAsync();
390
+ expect(result.is_err()).toBe(true);
391
+ expect(result.unwrap_err()).toBe('error');
392
+ });
393
+ });
394
+ });
395
+
396
+ describe('Utility Functions', () => {
397
+ describe('tryAsync', () => {
398
+ it('wraps async function to return ResultAsync', async () => {
399
+ const safeFetch = tryAsync(async (url: string) => {
400
+ if (url === 'good') return 'data';
401
+ throw new Error('not found');
402
+ });
403
+
404
+ const ok = await safeFetch('good');
405
+ expect(ok.is_ok()).toBe(true);
406
+ expect(ok.unwrap()).toBe('data');
407
+
408
+ const err = await safeFetch('bad');
409
+ expect(err.is_err()).toBe(true);
410
+ });
411
+ });
412
+
413
+ describe('liftAsync', () => {
414
+ it('lifts sync Result function to async', async () => {
415
+ const syncFn = (x: number): Result<number, string> =>
416
+ x > 0 ? Ok(x * 2) : Err('negative');
417
+
418
+ const asyncFn = liftAsync(syncFn);
419
+
420
+ const ok = await asyncFn(5);
421
+ expect(ok.unwrap()).toBe(10);
422
+
423
+ const err = await asyncFn(-1);
424
+ expect(err.unwrap_err()).toBe('negative');
425
+ });
426
+ });
427
+
428
+ describe('allAsync', () => {
429
+ it('is alias for ResultAsync.all', async () => {
430
+ const result = await allAsync([
431
+ ResultAsync.ok(1),
432
+ ResultAsync.ok(2),
433
+ ]);
434
+ expect(result.unwrap()).toEqual([1, 2]);
435
+ });
436
+ });
437
+ });
438
+
439
+ describe('Complex Async Chains', () => {
440
+ it('handles realistic API workflow', async () => {
441
+ // Simulate API calls
442
+ const fetchUser = (id: number) => ResultAsync.try(async () => {
443
+ if (id === 1) return { id: 1, name: 'Alice' };
444
+ throw new Error('User not found');
445
+ });
446
+
447
+ const fetchPosts = (userId: number) => ResultAsync.try(async () => {
448
+ return [{ id: 1, title: 'Hello', userId }];
449
+ });
450
+
451
+ const result = await fetchUser(1)
452
+ .andThen(user => fetchPosts(user.id)
453
+ .map(posts => ({ user, posts }))
454
+ );
455
+
456
+ expect(result.is_ok()).toBe(true);
457
+ expect(result.unwrap()).toEqual({
458
+ user: { id: 1, name: 'Alice' },
459
+ posts: [{ id: 1, title: 'Hello', userId: 1 }],
460
+ });
461
+ });
462
+
463
+ it('handles error propagation in chain', async () => {
464
+ const step1 = () => ResultAsync.ok(1);
465
+ const step2 = () => ResultAsync.err<number, string>('step2 failed');
466
+ const step3 = () => ResultAsync.ok(3);
467
+
468
+ const result = await step1()
469
+ .andThen(() => step2())
470
+ .andThen(() => step3());
471
+
472
+ expect(result.is_err()).toBe(true);
473
+ expect(result.unwrap_err()).toBe('step2 failed');
474
+ });
475
+
476
+ it('handles error recovery', async () => {
477
+ const primary = () => ResultAsync.err<string, string>('primary failed');
478
+ const fallback = () => ResultAsync.ok('fallback data');
479
+
480
+ const result = await primary().orElse(() => fallback());
481
+
482
+ expect(result.is_ok()).toBe(true);
483
+ expect(result.unwrap()).toBe('fallback data');
484
+ });
485
+ });