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.
- package/package.json +7 -6
- package/readme.md +161 -5
- package/rust/option.rs +2822 -0
- package/rust/result.rs +2207 -0
- package/src/lib.ts +3 -1
- package/src/option-async.test.ts +410 -0
- package/src/option-async.ts +467 -0
- package/src/option.test.ts +101 -0
- package/src/option.ts +480 -266
- package/src/result-async.test.ts +485 -0
- package/src/result-async.ts +635 -0
- package/src/result.test.ts +36 -0
- package/src/result.ts +418 -340
- package/src/types.test.ts +409 -0
- package/dist/lib.d.ts +0 -2
- package/dist/lib.js +0 -19
- package/dist/lib.js.map +0 -1
- package/dist/option.d.ts +0 -307
- package/dist/option.js +0 -195
- package/dist/option.js.map +0 -1
- package/dist/result.d.ts +0 -410
- package/dist/result.js +0 -231
- package/dist/result.js.map +0 -1
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-Level Tests for Result and Option
|
|
3
|
+
*
|
|
4
|
+
* These tests verify that the type system behaves correctly at compile time.
|
|
5
|
+
* They use @ts-expect-error to ensure that invalid operations fail to compile.
|
|
6
|
+
*
|
|
7
|
+
* Run with: npx tsc --noEmit
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
Result, ResultOk, ResultErr,
|
|
12
|
+
Ok, Err,
|
|
13
|
+
UnwrapOk, UnwrapErr, FlattenResult,
|
|
14
|
+
collect, matchResult,
|
|
15
|
+
} from './result';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
Option, OptionSome, OptionNone,
|
|
19
|
+
Some, None,
|
|
20
|
+
UnwrapOption, FlattenOption,
|
|
21
|
+
matchOption,
|
|
22
|
+
} from './option';
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Type Assertions Helper
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
type Expect<T extends true> = T;
|
|
29
|
+
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)
|
|
30
|
+
? true
|
|
31
|
+
: false;
|
|
32
|
+
type NotEqual<X, Y> = Equal<X, Y> extends true ? false : true;
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Result Type-Level Tests
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
// --- Factory Function Types ---
|
|
39
|
+
{
|
|
40
|
+
const ok = Ok(42);
|
|
41
|
+
const err = Err("error");
|
|
42
|
+
|
|
43
|
+
// Ok should produce ResultOk<T, never>
|
|
44
|
+
type _TestOkType = Expect<Equal<typeof ok, ResultOk<number, never>>>;
|
|
45
|
+
|
|
46
|
+
// Err should produce ResultErr<never, E>
|
|
47
|
+
type _TestErrType = Expect<Equal<typeof err, ResultErr<never, string>>>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Type Guards ---
|
|
51
|
+
{
|
|
52
|
+
const result: Result<number, string> = Ok(42);
|
|
53
|
+
|
|
54
|
+
if (result.is_ok()) {
|
|
55
|
+
// Inside is_ok() branch, result should be narrowed to ResultOk
|
|
56
|
+
type _TestNarrowed = Expect<Equal<typeof result, ResultOk<number, string>>>;
|
|
57
|
+
const value: number = result.value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (result.is_err()) {
|
|
61
|
+
// Inside is_err() branch, result should be narrowed to ResultErr
|
|
62
|
+
type _TestNarrowed = Expect<Equal<typeof result, ResultErr<number, string>>>;
|
|
63
|
+
const error: string = result.value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --- Map Preserves Types ---
|
|
68
|
+
{
|
|
69
|
+
const ok = Ok(42);
|
|
70
|
+
const mapped = ok.map(x => x.toString());
|
|
71
|
+
|
|
72
|
+
// map on Ok should return ResultOk with new type
|
|
73
|
+
type _TestMapOk = Expect<Equal<typeof mapped, ResultOk<string, never>>>;
|
|
74
|
+
|
|
75
|
+
const err = Err("error");
|
|
76
|
+
const mappedErr = err.map((x: number) => x.toString());
|
|
77
|
+
|
|
78
|
+
// map on Err should return ResultErr with new T type
|
|
79
|
+
type _TestMapErr = Expect<Equal<typeof mappedErr, ResultErr<string, string>>>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --- MapErr Preserves Types ---
|
|
83
|
+
{
|
|
84
|
+
const ok = Ok(42);
|
|
85
|
+
const mapped = ok.map_err((e: string) => new Error(e));
|
|
86
|
+
|
|
87
|
+
// map_err on Ok should return ResultOk with new E type
|
|
88
|
+
type _TestMapErrOk = Expect<Equal<typeof mapped, ResultOk<number, Error>>>;
|
|
89
|
+
|
|
90
|
+
const err = Err("error");
|
|
91
|
+
const mappedErr = err.map_err(e => new Error(e));
|
|
92
|
+
|
|
93
|
+
// map_err on Err should return ResultErr with new error type
|
|
94
|
+
type _TestMapErrErr = Expect<Equal<typeof mappedErr, ResultErr<never, Error>>>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// --- And/Or Combinators ---
|
|
98
|
+
{
|
|
99
|
+
const ok = Ok(42);
|
|
100
|
+
const err = Err("error");
|
|
101
|
+
|
|
102
|
+
// and should return the argument's type when called on Ok
|
|
103
|
+
const anded = ok.and(Ok("hello"));
|
|
104
|
+
type _TestAnd = Expect<Equal<typeof anded, Result<string, never>>>;
|
|
105
|
+
|
|
106
|
+
// or should return self's type (with new E) when called on Ok
|
|
107
|
+
const ored = ok.or(Err(123));
|
|
108
|
+
type _TestOr = Expect<Equal<typeof ored, ResultOk<number, number>>>;
|
|
109
|
+
|
|
110
|
+
// or should return argument when called on Err
|
|
111
|
+
const errOred = err.or(Ok(42));
|
|
112
|
+
type _TestErrOr = Expect<Equal<typeof errOred, Result<number, never>>>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// --- Transmute ---
|
|
116
|
+
{
|
|
117
|
+
const ok: Result<number, string> = Ok(42);
|
|
118
|
+
const err: Result<number, string> = Err("error");
|
|
119
|
+
|
|
120
|
+
// When we know it's Ok, transmute should give Ok<T, never>
|
|
121
|
+
if (ok.is_ok()) {
|
|
122
|
+
const transmuted = ok.transmute();
|
|
123
|
+
type _TestTransmuteOk = Expect<Equal<typeof transmuted, ResultOk<number, never>>>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// When we know it's Err, transmute should give Err<never, E>
|
|
127
|
+
if (err.is_err()) {
|
|
128
|
+
const transmuted = err.transmute();
|
|
129
|
+
type _TestTransmuteErr = Expect<Equal<typeof transmuted, ResultErr<never, string>>>;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// --- Flatten ---
|
|
134
|
+
{
|
|
135
|
+
const nested = Ok(Ok(42));
|
|
136
|
+
const flattened = nested.flatten();
|
|
137
|
+
|
|
138
|
+
// Flatten should unwrap one layer of Result
|
|
139
|
+
type _TestFlatten = Expect<Equal<typeof flattened, Result<number, never>>>;
|
|
140
|
+
|
|
141
|
+
const nestedErr = Ok(Err("inner error"));
|
|
142
|
+
const flattenedErr = nestedErr.flatten();
|
|
143
|
+
|
|
144
|
+
// Flatten should combine error types
|
|
145
|
+
type _TestFlattenErr = Expect<Equal<typeof flattenedErr, Result<never, string>>>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// --- UnwrapOk/UnwrapErr Type Utilities ---
|
|
149
|
+
{
|
|
150
|
+
type TestResult = Result<number, string>;
|
|
151
|
+
|
|
152
|
+
type _TestUnwrapOk = Expect<Equal<UnwrapOk<TestResult>, number>>;
|
|
153
|
+
type _TestUnwrapErr = Expect<Equal<UnwrapErr<TestResult>, string>>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// --- FlattenResult Type Utility ---
|
|
157
|
+
{
|
|
158
|
+
type Nested = Result<Result<number, string>, boolean>;
|
|
159
|
+
type _TestFlattenType = Expect<Equal<FlattenResult<Result<number, string>, boolean>, Result<number, string | boolean>>>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- Collect Function ---
|
|
163
|
+
{
|
|
164
|
+
const results = [Ok(1), Ok(2), Ok(3)] as const;
|
|
165
|
+
const collected = collect(results);
|
|
166
|
+
|
|
167
|
+
// Collect should produce Result with tuple type
|
|
168
|
+
type _TestCollect = Expect<Equal<
|
|
169
|
+
typeof collected,
|
|
170
|
+
Result<readonly [number, number, number], never>
|
|
171
|
+
>>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// --- Match Function ---
|
|
175
|
+
{
|
|
176
|
+
const result: Result<number, string> = Ok(42);
|
|
177
|
+
const matched = matchResult(result, {
|
|
178
|
+
Ok: (value) => value * 2,
|
|
179
|
+
Err: (error) => error.length,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
type _TestMatch = Expect<Equal<typeof matched, number>>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// Option Type-Level Tests
|
|
187
|
+
// ============================================================================
|
|
188
|
+
|
|
189
|
+
// --- Factory Function Types ---
|
|
190
|
+
{
|
|
191
|
+
const some = Some(42);
|
|
192
|
+
const none = None<number>();
|
|
193
|
+
|
|
194
|
+
// Some should produce OptionSome<T>
|
|
195
|
+
type _TestSomeType = Expect<Equal<typeof some, OptionSome<number>>>;
|
|
196
|
+
|
|
197
|
+
// None should produce OptionNone<T>
|
|
198
|
+
type _TestNoneType = Expect<Equal<typeof none, OptionNone<number>>>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// --- Type Guards ---
|
|
202
|
+
{
|
|
203
|
+
const option: Option<number> = Some(42);
|
|
204
|
+
|
|
205
|
+
if (option.is_some()) {
|
|
206
|
+
// Inside is_some() branch, option should be narrowed to OptionSome
|
|
207
|
+
type _TestNarrowed = Expect<Equal<typeof option, OptionSome<number>>>;
|
|
208
|
+
const value: number = option.value;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (option.is_none()) {
|
|
212
|
+
// Inside is_none() branch, option should be narrowed to OptionNone
|
|
213
|
+
type _TestNarrowed = Expect<Equal<typeof option, OptionNone<number>>>;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// --- Map Preserves Types ---
|
|
218
|
+
{
|
|
219
|
+
const some = Some(42);
|
|
220
|
+
const mapped = some.map(x => x.toString());
|
|
221
|
+
|
|
222
|
+
// map on Some should return OptionSome with new type
|
|
223
|
+
type _TestMapSome = Expect<Equal<typeof mapped, OptionSome<string>>>;
|
|
224
|
+
|
|
225
|
+
const none = None<number>();
|
|
226
|
+
const mappedNone = none.map(x => x.toString());
|
|
227
|
+
|
|
228
|
+
// map on None should return OptionNone with new type
|
|
229
|
+
type _TestMapNone = Expect<Equal<typeof mappedNone, OptionNone<string>>>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// --- And/Or Combinators ---
|
|
233
|
+
{
|
|
234
|
+
const some = Some(42);
|
|
235
|
+
const none = None<number>();
|
|
236
|
+
|
|
237
|
+
// and should return the argument's type when called on Some
|
|
238
|
+
const anded = some.and(Some("hello"));
|
|
239
|
+
type _TestAnd = Expect<Equal<typeof anded, Option<string>>>;
|
|
240
|
+
|
|
241
|
+
// or should return self when called on Some
|
|
242
|
+
const ored = some.or(Some(0));
|
|
243
|
+
type _TestOr = Expect<Equal<typeof ored, OptionSome<number>>>;
|
|
244
|
+
|
|
245
|
+
// or should return argument when called on None
|
|
246
|
+
const noneOred = none.or(Some(0));
|
|
247
|
+
type _TestNoneOr = Expect<Equal<typeof noneOred, Option<number>>>;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// --- Flatten ---
|
|
251
|
+
{
|
|
252
|
+
const nested = Some(Some(42));
|
|
253
|
+
const flattened = nested.flatten();
|
|
254
|
+
|
|
255
|
+
// Flatten should unwrap one layer of Option
|
|
256
|
+
type _TestFlatten = Expect<Equal<typeof flattened, Option<number>>>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// --- UnwrapOption Type Utility ---
|
|
260
|
+
{
|
|
261
|
+
type TestOption = Option<number>;
|
|
262
|
+
type _TestUnwrapOption = Expect<Equal<UnwrapOption<TestOption>, number>>;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// --- FlattenOption Type Utility ---
|
|
266
|
+
{
|
|
267
|
+
type Nested = Option<Option<number>>;
|
|
268
|
+
type _TestFlattenType = Expect<Equal<FlattenOption<Option<number>>, Option<number>>>;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// --- Match Function ---
|
|
272
|
+
{
|
|
273
|
+
const option: Option<number> = Some(42);
|
|
274
|
+
const matched = matchOption(option, {
|
|
275
|
+
Some: (value) => value * 2,
|
|
276
|
+
None: () => 0,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
type _TestMatch = Expect<Equal<typeof matched, number>>;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// --- Zip ---
|
|
283
|
+
{
|
|
284
|
+
const some1 = Some(42);
|
|
285
|
+
const some2 = Some("hello");
|
|
286
|
+
const zipped = some1.zip(some2);
|
|
287
|
+
|
|
288
|
+
type _TestZip = Expect<Equal<typeof zipped, Option<[number, string]>>>;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Variance Tests
|
|
293
|
+
// ============================================================================
|
|
294
|
+
|
|
295
|
+
// Result should be covariant in both T and E
|
|
296
|
+
{
|
|
297
|
+
type Animal = { name: string };
|
|
298
|
+
type Dog = Animal & { breed: string };
|
|
299
|
+
|
|
300
|
+
// ResultOk<Dog, never> should be assignable to ResultOk<Animal, never>
|
|
301
|
+
const dogResult: ResultOk<Dog, never> = Ok({ name: "Rex", breed: "German Shepherd" });
|
|
302
|
+
const animalResult: ResultOk<Animal, never> = dogResult; // Should compile
|
|
303
|
+
|
|
304
|
+
// ResultErr<never, Dog> should be assignable to ResultErr<never, Animal>
|
|
305
|
+
// (if we consider errors as covariant too)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Option should be covariant in T
|
|
309
|
+
{
|
|
310
|
+
type Animal = { name: string };
|
|
311
|
+
type Dog = Animal & { breed: string };
|
|
312
|
+
|
|
313
|
+
// OptionSome<Dog> should be assignable to OptionSome<Animal>
|
|
314
|
+
const dogOption: OptionSome<Dog> = Some({ name: "Rex", breed: "German Shepherd" });
|
|
315
|
+
const animalOption: OptionSome<Animal> = dogOption; // Should compile
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ============================================================================
|
|
319
|
+
// Error Cases (These should fail to compile)
|
|
320
|
+
// ============================================================================
|
|
321
|
+
|
|
322
|
+
// Uncomment to verify these correctly fail:
|
|
323
|
+
|
|
324
|
+
// @ts-expect-error - Cannot unwrap Err without checking
|
|
325
|
+
// const _err1: number = Err("error").unwrap();
|
|
326
|
+
|
|
327
|
+
// @ts-expect-error - Cannot unwrap None without checking
|
|
328
|
+
// const _err2: number = None<number>().unwrap();
|
|
329
|
+
|
|
330
|
+
// @ts-expect-error - is_ok_and on Err always returns false literal
|
|
331
|
+
// const _err3: true = Err("error").is_ok_and(() => true);
|
|
332
|
+
|
|
333
|
+
// @ts-expect-error - is_some_and on None always returns false literal
|
|
334
|
+
// const _err4: true = None<number>().is_some_and(() => true);
|
|
335
|
+
|
|
336
|
+
// Actual runtime test to satisfy Jest
|
|
337
|
+
describe('Type-level tests', () => {
|
|
338
|
+
it('should compile without errors', () => {
|
|
339
|
+
// If this file compiles, all type tests passed
|
|
340
|
+
expect(true).toBe(true);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('Ok factory returns ResultOk<T, never>', () => {
|
|
344
|
+
const ok = Ok(42);
|
|
345
|
+
expect(ok._tag).toBe('Ok');
|
|
346
|
+
expect(ok.value).toBe(42);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('Err factory returns ResultErr<never, E>', () => {
|
|
350
|
+
const err = Err("error");
|
|
351
|
+
expect(err._tag).toBe('Err');
|
|
352
|
+
expect(err.value).toBe("error");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('Some factory returns OptionSome<T>', () => {
|
|
356
|
+
const some = Some(42);
|
|
357
|
+
expect(some._tag).toBe('Some');
|
|
358
|
+
expect(some.value).toBe(42);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('None factory returns OptionNone<T>', () => {
|
|
362
|
+
const none = None<number>();
|
|
363
|
+
expect(none._tag).toBe('None');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('transmute on Ok returns Ok with never error type', () => {
|
|
367
|
+
const ok = Ok(42);
|
|
368
|
+
const transmuted = ok.transmute();
|
|
369
|
+
expect(transmuted._tag).toBe('Ok');
|
|
370
|
+
expect(transmuted.value).toBe(42);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('transmute on Err returns Err with never value type', () => {
|
|
374
|
+
const err = Err("error");
|
|
375
|
+
const transmuted = err.transmute();
|
|
376
|
+
expect(transmuted._tag).toBe('Err');
|
|
377
|
+
expect(transmuted.value).toBe("error");
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('collect combines Ok results into array', () => {
|
|
381
|
+
const results = [Ok(1), Ok(2), Ok(3)] as const;
|
|
382
|
+
const collected = collect(results);
|
|
383
|
+
expect(collected.is_ok()).toBe(true);
|
|
384
|
+
expect(collected.unwrap()).toEqual([1, 2, 3]);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('collect returns first Err', () => {
|
|
388
|
+
const results = [Ok(1), Err("fail"), Ok(3)] as const;
|
|
389
|
+
const collected = collect(results);
|
|
390
|
+
expect(collected.is_err()).toBe(true);
|
|
391
|
+
expect(collected.unwrap_err()).toBe("fail");
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('match on Result works correctly', () => {
|
|
395
|
+
const ok: Result<number, string> = Ok(42);
|
|
396
|
+
const err: Result<number, string> = Err("error");
|
|
397
|
+
|
|
398
|
+
expect(matchResult(ok, { Ok: v => v * 2, Err: e => e.length })).toBe(84);
|
|
399
|
+
expect(matchResult(err, { Ok: v => v * 2, Err: e => e.length })).toBe(5);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('match on Option works correctly', () => {
|
|
403
|
+
const some: Option<number> = Some(42);
|
|
404
|
+
const none: Option<number> = None();
|
|
405
|
+
|
|
406
|
+
expect(matchOption(some, { Some: v => v * 2, None: () => 0 })).toBe(84);
|
|
407
|
+
expect(matchOption(none, { Some: v => v * 2, None: () => 0 })).toBe(0);
|
|
408
|
+
});
|
|
409
|
+
});
|
package/dist/lib.d.ts
DELETED
package/dist/lib.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./result"), exports);
|
|
18
|
-
__exportStar(require("./option"), exports);
|
|
19
|
-
//# sourceMappingURL=lib.js.map
|
package/dist/lib.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,2CAAyB"}
|