@nlozgachev/pipekit 0.1.8 → 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/README.md +1 -1
- package/esm/src/Core/Arr.js +14 -14
- package/esm/src/Core/Option.js +16 -16
- package/esm/src/Core/Rec.js +1 -1
- package/esm/src/Core/Result.js +14 -14
- package/esm/src/Core/Task.js +73 -5
- package/esm/src/Core/TaskOption.js +3 -3
- package/esm/src/Core/TaskResult.js +62 -5
- package/esm/src/Core/These.js +40 -40
- package/esm/src/Types/Brand.js +3 -3
- package/package.json +1 -1
- package/script/src/Core/Arr.js +14 -14
- package/script/src/Core/Option.js +16 -16
- package/script/src/Core/Rec.js +1 -1
- package/script/src/Core/Result.js +14 -14
- package/script/src/Core/Task.js +73 -5
- package/script/src/Core/TaskOption.js +3 -3
- package/script/src/Core/TaskResult.js +62 -5
- package/script/src/Core/These.js +40 -40
- package/script/src/Types/Brand.js +3 -3
- package/types/src/Core/Arr.d.ts +3 -3
- package/types/src/Core/Arr.d.ts.map +1 -1
- package/types/src/Core/Option.d.ts +9 -9
- package/types/src/Core/Option.d.ts.map +1 -1
- package/types/src/Core/Rec.d.ts.map +1 -1
- package/types/src/Core/Result.d.ts +9 -9
- package/types/src/Core/Result.d.ts.map +1 -1
- package/types/src/Core/Task.d.ts +49 -5
- package/types/src/Core/Task.d.ts.map +1 -1
- package/types/src/Core/TaskOption.d.ts.map +1 -1
- package/types/src/Core/TaskResult.d.ts +40 -1
- package/types/src/Core/TaskResult.d.ts.map +1 -1
- package/types/src/Core/These.d.ts +31 -31
- package/types/src/Core/These.d.ts.map +1 -1
- package/types/src/Types/Brand.d.ts +5 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @nlozgachev/pipekit
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@nlozgachev/pipekit)[](https://jsr.io/@nlozgachev/pipekit)[](https://www.typescriptlang.org)[](https://deno.com)
|
|
3
|
+
[](https://www.npmjs.com/package/@nlozgachev/pipekit)[](https://jsr.io/@nlozgachev/pipekit)[](https://www.typescriptlang.org)[](https://deno.com)
|
|
4
4
|
|
|
5
5
|
A TypeScript toolkit for writing code that means exactly what it says.
|
|
6
6
|
|
package/esm/src/Core/Arr.js
CHANGED
|
@@ -28,7 +28,7 @@ export var Arr;
|
|
|
28
28
|
* Arr.head([]); // None
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
Arr.head = (data) => data.length > 0 ? Option.
|
|
31
|
+
Arr.head = (data) => data.length > 0 ? Option.some(data[0]) : Option.none();
|
|
32
32
|
/**
|
|
33
33
|
* Returns the last element of an array, or None if the array is empty.
|
|
34
34
|
*
|
|
@@ -38,7 +38,7 @@ export var Arr;
|
|
|
38
38
|
* Arr.last([]); // None
|
|
39
39
|
* ```
|
|
40
40
|
*/
|
|
41
|
-
Arr.last = (data) => data.length > 0 ? Option.
|
|
41
|
+
Arr.last = (data) => data.length > 0 ? Option.some(data[data.length - 1]) : Option.none();
|
|
42
42
|
/**
|
|
43
43
|
* Returns all elements except the first, or None if the array is empty.
|
|
44
44
|
*
|
|
@@ -48,7 +48,7 @@ export var Arr;
|
|
|
48
48
|
* Arr.tail([]); // None
|
|
49
49
|
* ```
|
|
50
50
|
*/
|
|
51
|
-
Arr.tail = (data) => data.length > 0 ? Option.
|
|
51
|
+
Arr.tail = (data) => data.length > 0 ? Option.some(data.slice(1)) : Option.none();
|
|
52
52
|
/**
|
|
53
53
|
* Returns all elements except the last, or None if the array is empty.
|
|
54
54
|
*
|
|
@@ -58,7 +58,7 @@ export var Arr;
|
|
|
58
58
|
* Arr.init([]); // None
|
|
59
59
|
* ```
|
|
60
60
|
*/
|
|
61
|
-
Arr.init = (data) => data.length > 0 ? Option.
|
|
61
|
+
Arr.init = (data) => data.length > 0 ? Option.some(data.slice(0, -1)) : Option.none();
|
|
62
62
|
// --- Search ---
|
|
63
63
|
/**
|
|
64
64
|
* Returns the first element matching the predicate, or None.
|
|
@@ -70,7 +70,7 @@ export var Arr;
|
|
|
70
70
|
*/
|
|
71
71
|
Arr.findFirst = (predicate) => (data) => {
|
|
72
72
|
const idx = data.findIndex(predicate);
|
|
73
|
-
return idx >= 0 ? Option.
|
|
73
|
+
return idx >= 0 ? Option.some(data[idx]) : Option.none();
|
|
74
74
|
};
|
|
75
75
|
/**
|
|
76
76
|
* Returns the last element matching the predicate, or None.
|
|
@@ -83,9 +83,9 @@ export var Arr;
|
|
|
83
83
|
Arr.findLast = (predicate) => (data) => {
|
|
84
84
|
for (let i = data.length - 1; i >= 0; i--) {
|
|
85
85
|
if (predicate(data[i]))
|
|
86
|
-
return Option.
|
|
86
|
+
return Option.some(data[i]);
|
|
87
87
|
}
|
|
88
|
-
return Option.
|
|
88
|
+
return Option.none();
|
|
89
89
|
};
|
|
90
90
|
/**
|
|
91
91
|
* Returns the index of the first element matching the predicate, or None.
|
|
@@ -97,7 +97,7 @@ export var Arr;
|
|
|
97
97
|
*/
|
|
98
98
|
Arr.findIndex = (predicate) => (data) => {
|
|
99
99
|
const idx = data.findIndex(predicate);
|
|
100
|
-
return idx >= 0 ? Option.
|
|
100
|
+
return idx >= 0 ? Option.some(idx) : Option.none();
|
|
101
101
|
};
|
|
102
102
|
// --- Transform ---
|
|
103
103
|
/**
|
|
@@ -303,7 +303,7 @@ export var Arr;
|
|
|
303
303
|
* ```ts
|
|
304
304
|
* const parseNum = (s: string): Option<number> => {
|
|
305
305
|
* const n = Number(s);
|
|
306
|
-
* return isNaN(n) ? Option.
|
|
306
|
+
* return isNaN(n) ? Option.none() : Option.of(n);
|
|
307
307
|
* };
|
|
308
308
|
*
|
|
309
309
|
* pipe(["1", "2", "3"], Arr.traverse(parseNum)); // Some([1, 2, 3])
|
|
@@ -315,10 +315,10 @@ export var Arr;
|
|
|
315
315
|
for (const a of data) {
|
|
316
316
|
const mapped = f(a);
|
|
317
317
|
if (Option.isNone(mapped))
|
|
318
|
-
return Option.
|
|
318
|
+
return Option.none();
|
|
319
319
|
result.push(mapped.value);
|
|
320
320
|
}
|
|
321
|
-
return Option.
|
|
321
|
+
return Option.some(result);
|
|
322
322
|
};
|
|
323
323
|
/**
|
|
324
324
|
* Maps each element to a Result and collects the results.
|
|
@@ -328,7 +328,7 @@ export var Arr;
|
|
|
328
328
|
* ```ts
|
|
329
329
|
* pipe(
|
|
330
330
|
* [1, 2, 3],
|
|
331
|
-
* Arr.traverseResult(n => n > 0 ? Result.
|
|
331
|
+
* Arr.traverseResult(n => n > 0 ? Result.ok(n) : Result.err("negative"))
|
|
332
332
|
* ); // Ok([1, 2, 3])
|
|
333
333
|
* ```
|
|
334
334
|
*/
|
|
@@ -340,7 +340,7 @@ export var Arr;
|
|
|
340
340
|
return mapped;
|
|
341
341
|
result.push(mapped.value);
|
|
342
342
|
}
|
|
343
|
-
return Result.
|
|
343
|
+
return Result.ok(result);
|
|
344
344
|
};
|
|
345
345
|
/**
|
|
346
346
|
* Maps each element to a Task and runs all in parallel.
|
|
@@ -361,7 +361,7 @@ export var Arr;
|
|
|
361
361
|
* @example
|
|
362
362
|
* ```ts
|
|
363
363
|
* Arr.sequence([Option.of(1), Option.of(2)]); // Some([1, 2])
|
|
364
|
-
* Arr.sequence([Option.of(1), Option.
|
|
364
|
+
* Arr.sequence([Option.of(1), Option.none()]); // None
|
|
365
365
|
* ```
|
|
366
366
|
*/
|
|
367
367
|
Arr.sequence = (data) => Arr.traverse((a) => a)(data);
|
package/esm/src/Core/Option.js
CHANGED
|
@@ -9,11 +9,11 @@ export var Option;
|
|
|
9
9
|
* Option.of(42); // Some(42)
|
|
10
10
|
* ```
|
|
11
11
|
*/
|
|
12
|
-
Option.of = (value) => Option.
|
|
12
|
+
Option.of = (value) => Option.some(value);
|
|
13
13
|
/**
|
|
14
14
|
* Creates a Some containing the given value.
|
|
15
15
|
*/
|
|
16
|
-
Option.
|
|
16
|
+
Option.some = (value) => ({ kind: "Some", value });
|
|
17
17
|
/**
|
|
18
18
|
* Type guard that checks if a Option is Some.
|
|
19
19
|
*/
|
|
@@ -21,7 +21,7 @@ export var Option;
|
|
|
21
21
|
/**
|
|
22
22
|
* Creates a None (empty Option).
|
|
23
23
|
*/
|
|
24
|
-
Option.
|
|
24
|
+
Option.none = () => ({ kind: "None" });
|
|
25
25
|
/**
|
|
26
26
|
* Type guard that checks if a Option is None.
|
|
27
27
|
*/
|
|
@@ -36,7 +36,7 @@ export var Option;
|
|
|
36
36
|
* Option.fromNullable(42); // Some(42)
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
|
-
Option.fromNullable = (value) => value === null || value === undefined ? Option.
|
|
39
|
+
Option.fromNullable = (value) => value === null || value === undefined ? Option.none() : Option.some(value);
|
|
40
40
|
/**
|
|
41
41
|
* Extracts the value from a Option, returning null if None.
|
|
42
42
|
*/
|
|
@@ -49,7 +49,7 @@ export var Option;
|
|
|
49
49
|
* Creates a Option from a possibly undefined value.
|
|
50
50
|
* Returns None if undefined, Some otherwise.
|
|
51
51
|
*/
|
|
52
|
-
Option.fromUndefined = (value) => value === undefined ? Option.
|
|
52
|
+
Option.fromUndefined = (value) => value === undefined ? Option.none() : Option.some(value);
|
|
53
53
|
/**
|
|
54
54
|
* Converts an Option to a Result.
|
|
55
55
|
* Some becomes Ok, None becomes Err with the provided error.
|
|
@@ -62,33 +62,33 @@ export var Option;
|
|
|
62
62
|
* ); // Ok(42)
|
|
63
63
|
*
|
|
64
64
|
* pipe(
|
|
65
|
-
* Option.
|
|
65
|
+
* Option.none(),
|
|
66
66
|
* Option.toResult(() => "Value was missing")
|
|
67
67
|
* ); // Err("Value was missing")
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
|
-
Option.toResult = (onNone) => (data) => Option.isSome(data) ? Result.
|
|
70
|
+
Option.toResult = (onNone) => (data) => Option.isSome(data) ? Result.ok(data.value) : Result.err(onNone());
|
|
71
71
|
/**
|
|
72
72
|
* Creates an Option from a Result.
|
|
73
73
|
* Ok becomes Some, Err becomes None (the error is discarded).
|
|
74
74
|
*
|
|
75
75
|
* @example
|
|
76
76
|
* ```ts
|
|
77
|
-
* Option.fromResult(Result.
|
|
78
|
-
* Option.fromResult(Result.
|
|
77
|
+
* Option.fromResult(Result.ok(42)); // Some(42)
|
|
78
|
+
* Option.fromResult(Result.err("oops")); // None
|
|
79
79
|
* ```
|
|
80
80
|
*/
|
|
81
|
-
Option.fromResult = (data) => Result.isOk(data) ? Option.
|
|
81
|
+
Option.fromResult = (data) => Result.isOk(data) ? Option.some(data.value) : Option.none();
|
|
82
82
|
/**
|
|
83
83
|
* Transforms the value inside a Option if it exists.
|
|
84
84
|
*
|
|
85
85
|
* @example
|
|
86
86
|
* ```ts
|
|
87
87
|
* pipe(Option.of(5), Option.map(n => n * 2)); // Some(10)
|
|
88
|
-
* pipe(Option.
|
|
88
|
+
* pipe(Option.none(), Option.map(n => n * 2)); // None
|
|
89
89
|
* ```
|
|
90
90
|
*/
|
|
91
|
-
Option.map = (f) => (data) => Option.isSome(data) ? Option.
|
|
91
|
+
Option.map = (f) => (data) => Option.isSome(data) ? Option.some(f(data.value)) : data;
|
|
92
92
|
/**
|
|
93
93
|
* Chains Option computations. If the first is Some, passes the value to f.
|
|
94
94
|
* If the first is None, propagates None.
|
|
@@ -97,7 +97,7 @@ export var Option;
|
|
|
97
97
|
* ```ts
|
|
98
98
|
* const parseNumber = (s: string): Option<number> => {
|
|
99
99
|
* const n = parseInt(s, 10);
|
|
100
|
-
* return isNaN(n) ? Option.
|
|
100
|
+
* return isNaN(n) ? Option.none() : Option.of(n);
|
|
101
101
|
* };
|
|
102
102
|
*
|
|
103
103
|
* pipe(Option.of("42"), Option.chain(parseNumber)); // Some(42)
|
|
@@ -141,7 +141,7 @@ export var Option;
|
|
|
141
141
|
* @example
|
|
142
142
|
* ```ts
|
|
143
143
|
* pipe(Option.of(5), Option.getOrElse(0)); // 5
|
|
144
|
-
* pipe(Option.
|
|
144
|
+
* pipe(Option.none(), Option.getOrElse(0)); // 0
|
|
145
145
|
* ```
|
|
146
146
|
*/
|
|
147
147
|
Option.getOrElse = (defaultValue) => (data) => Option.isSome(data) ? data.value : defaultValue;
|
|
@@ -173,7 +173,7 @@ export var Option;
|
|
|
173
173
|
* pipe(Option.of(2), Option.filter(n => n > 3)); // None
|
|
174
174
|
* ```
|
|
175
175
|
*/
|
|
176
|
-
Option.filter = (predicate) => (data) => Option.isSome(data) && predicate(data.value) ? data : Option.
|
|
176
|
+
Option.filter = (predicate) => (data) => Option.isSome(data) && predicate(data.value) ? data : Option.none();
|
|
177
177
|
/**
|
|
178
178
|
* Recovers from a None by providing a fallback Option.
|
|
179
179
|
*/
|
|
@@ -191,5 +191,5 @@ export var Option;
|
|
|
191
191
|
* ); // Some(8)
|
|
192
192
|
* ```
|
|
193
193
|
*/
|
|
194
|
-
Option.ap = (arg) => (data) => Option.isSome(data) && Option.isSome(arg) ? Option.
|
|
194
|
+
Option.ap = (arg) => (data) => Option.isSome(data) && Option.isSome(arg) ? Option.some(data.value(arg.value)) : Option.none();
|
|
195
195
|
})(Option || (Option = {}));
|
package/esm/src/Core/Rec.js
CHANGED
|
@@ -87,7 +87,7 @@ export var Rec;
|
|
|
87
87
|
* pipe({ a: 1, b: 2 }, Rec.lookup("c")); // None
|
|
88
88
|
* ```
|
|
89
89
|
*/
|
|
90
|
-
Rec.lookup = (key) => (data) => Object.prototype.hasOwnProperty.call(data, key) ? Option.
|
|
90
|
+
Rec.lookup = (key) => (data) => Object.prototype.hasOwnProperty.call(data, key) ? Option.some(data[key]) : Option.none();
|
|
91
91
|
/**
|
|
92
92
|
* Returns all keys of a record.
|
|
93
93
|
*/
|
package/esm/src/Core/Result.js
CHANGED
|
@@ -8,15 +8,15 @@ export var Result;
|
|
|
8
8
|
* Result.of(42); // Ok(42)
|
|
9
9
|
* ```
|
|
10
10
|
*/
|
|
11
|
-
Result.of = (value) => Result.
|
|
11
|
+
Result.of = (value) => Result.ok(value);
|
|
12
12
|
/**
|
|
13
13
|
* Creates a failed Result with the given error.
|
|
14
14
|
*/
|
|
15
|
-
Result.
|
|
15
|
+
Result.err = (error) => ({ kind: "Error", error });
|
|
16
16
|
/**
|
|
17
17
|
* Creates a successful Result with the given value.
|
|
18
18
|
*/
|
|
19
|
-
Result.
|
|
19
|
+
Result.ok = (value) => ({ kind: "Ok", value });
|
|
20
20
|
/**
|
|
21
21
|
* Type guard that checks if an Result is Ok.
|
|
22
22
|
*/
|
|
@@ -40,10 +40,10 @@ export var Result;
|
|
|
40
40
|
*/
|
|
41
41
|
Result.tryCatch = (f, onError) => {
|
|
42
42
|
try {
|
|
43
|
-
return Result.
|
|
43
|
+
return Result.ok(f());
|
|
44
44
|
}
|
|
45
45
|
catch (e) {
|
|
46
|
-
return Result.
|
|
46
|
+
return Result.err(onError(e));
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
/**
|
|
@@ -52,19 +52,19 @@ export var Result;
|
|
|
52
52
|
* @example
|
|
53
53
|
* ```ts
|
|
54
54
|
* pipe(Result.of(5), Result.map(n => n * 2)); // Ok(10)
|
|
55
|
-
* pipe(Result.
|
|
55
|
+
* pipe(Result.err("error"), Result.map(n => n * 2)); // Err("error")
|
|
56
56
|
* ```
|
|
57
57
|
*/
|
|
58
|
-
Result.map = (f) => (data) => Result.isOk(data) ? Result.
|
|
58
|
+
Result.map = (f) => (data) => Result.isOk(data) ? Result.ok(f(data.value)) : data;
|
|
59
59
|
/**
|
|
60
60
|
* Transforms the error value inside an Result.
|
|
61
61
|
*
|
|
62
62
|
* @example
|
|
63
63
|
* ```ts
|
|
64
|
-
* pipe(Result.
|
|
64
|
+
* pipe(Result.err("oops"), Result.mapError(e => e.toUpperCase())); // Err("OOPS")
|
|
65
65
|
* ```
|
|
66
66
|
*/
|
|
67
|
-
Result.mapError = (f) => (data) => Result.isErr(data) ? Result.
|
|
67
|
+
Result.mapError = (f) => (data) => Result.isErr(data) ? Result.err(f(data.error)) : data;
|
|
68
68
|
/**
|
|
69
69
|
* Chains Result computations. If the first is Ok, passes the value to f.
|
|
70
70
|
* If the first is Err, propagates the error.
|
|
@@ -72,7 +72,7 @@ export var Result;
|
|
|
72
72
|
* @example
|
|
73
73
|
* ```ts
|
|
74
74
|
* const validatePositive = (n: number): Result<string, number> =>
|
|
75
|
-
* n > 0 ? Result.of(n) : Result.
|
|
75
|
+
* n > 0 ? Result.of(n) : Result.err("Must be positive");
|
|
76
76
|
*
|
|
77
77
|
* pipe(Result.of(5), Result.chain(validatePositive)); // Ok(5)
|
|
78
78
|
* pipe(Result.of(-1), Result.chain(validatePositive)); // Err("Must be positive")
|
|
@@ -115,7 +115,7 @@ export var Result;
|
|
|
115
115
|
* @example
|
|
116
116
|
* ```ts
|
|
117
117
|
* pipe(Result.of(5), Result.getOrElse(0)); // 5
|
|
118
|
-
* pipe(Result.
|
|
118
|
+
* pipe(Result.err("error"), Result.getOrElse(0)); // 0
|
|
119
119
|
* ```
|
|
120
120
|
*/
|
|
121
121
|
Result.getOrElse = (defaultValue) => (data) => Result.isOk(data) ? data.value : defaultValue;
|
|
@@ -151,8 +151,8 @@ export var Result;
|
|
|
151
151
|
*
|
|
152
152
|
* @example
|
|
153
153
|
* ```ts
|
|
154
|
-
* Result.toOption(Result.
|
|
155
|
-
* Result.toOption(Result.
|
|
154
|
+
* Result.toOption(Result.ok(42)); // Some(42)
|
|
155
|
+
* Result.toOption(Result.err("oops")); // None
|
|
156
156
|
* ```
|
|
157
157
|
*/
|
|
158
158
|
Result.toOption = (data) => Result.isOk(data) ? { kind: "Some", value: data.value } : { kind: "None" };
|
|
@@ -169,5 +169,5 @@ export var Result;
|
|
|
169
169
|
* ); // Ok(8)
|
|
170
170
|
* ```
|
|
171
171
|
*/
|
|
172
|
-
Result.ap = (arg) => (data) => Result.isOk(data) && Result.isOk(arg) ? Result.
|
|
172
|
+
Result.ap = (arg) => (data) => Result.isOk(data) && Result.isOk(arg) ? Result.ok(data.value(arg.value)) : Result.isErr(data) ? data : arg;
|
|
173
173
|
})(Result || (Result = {}));
|
package/esm/src/Core/Task.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Result } from "./Result.js";
|
|
1
2
|
export var Task;
|
|
2
3
|
(function (Task) {
|
|
3
4
|
/**
|
|
@@ -10,10 +11,6 @@ export var Task;
|
|
|
10
11
|
* ```
|
|
11
12
|
*/
|
|
12
13
|
Task.of = (value) => () => Promise.resolve(value);
|
|
13
|
-
/**
|
|
14
|
-
* Creates a Task that will reject with the given error.
|
|
15
|
-
*/
|
|
16
|
-
Task.fail = (error) => () => Promise.reject(error);
|
|
17
14
|
/**
|
|
18
15
|
* Creates a Task from a function that returns a Promise.
|
|
19
16
|
* Alias for directly creating a Task.
|
|
@@ -37,7 +34,7 @@ export var Task;
|
|
|
37
34
|
*/
|
|
38
35
|
Task.map = (f) => (data) => () => data().then(f);
|
|
39
36
|
/**
|
|
40
|
-
* Chains Task computations.
|
|
37
|
+
* Chains Task computations. Passes the resolved value of the first Task to f.
|
|
41
38
|
*
|
|
42
39
|
* @example
|
|
43
40
|
* ```ts
|
|
@@ -95,6 +92,7 @@ export var Task;
|
|
|
95
92
|
Task.all = (tasks) => () => Promise.all(tasks.map((t) => t()));
|
|
96
93
|
/**
|
|
97
94
|
* Delays the execution of a Task by the specified milliseconds.
|
|
95
|
+
* Useful for debouncing or rate limiting.
|
|
98
96
|
*
|
|
99
97
|
* @example
|
|
100
98
|
* ```ts
|
|
@@ -105,4 +103,74 @@ export var Task;
|
|
|
105
103
|
* ```
|
|
106
104
|
*/
|
|
107
105
|
Task.delay = (ms) => (data) => () => new Promise((resolve, reject) => setTimeout(() => data().then(resolve, reject), ms));
|
|
106
|
+
/**
|
|
107
|
+
* Runs a Task a fixed number of times sequentially, collecting all results into an array.
|
|
108
|
+
* An optional delay (ms) can be inserted between runs.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* pipe(
|
|
113
|
+
* pollSensor,
|
|
114
|
+
* Task.repeat({ times: 5, delay: 1000 })
|
|
115
|
+
* )(); // Task<Reading[]> — 5 readings, one per second
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
Task.repeat = (options) => (task) => () => {
|
|
119
|
+
const { times, delay: ms } = options;
|
|
120
|
+
if (times <= 0)
|
|
121
|
+
return Promise.resolve([]);
|
|
122
|
+
const results = [];
|
|
123
|
+
const wait = () => ms !== undefined && ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve();
|
|
124
|
+
const run = (left) => task().then((a) => {
|
|
125
|
+
results.push(a);
|
|
126
|
+
if (left <= 1)
|
|
127
|
+
return results;
|
|
128
|
+
return wait().then(() => run(left - 1));
|
|
129
|
+
});
|
|
130
|
+
return run(times);
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Runs a Task repeatedly until the result satisfies a predicate, returning that result.
|
|
134
|
+
* An optional delay (ms) can be inserted between runs.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* pipe(
|
|
139
|
+
* checkStatus,
|
|
140
|
+
* Task.repeatUntil({ when: (s) => s === "ready", delay: 500 })
|
|
141
|
+
* )(); // polls every 500ms until status is "ready"
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
Task.repeatUntil = (options) => (task) => () => {
|
|
145
|
+
const { when: predicate, delay: ms } = options;
|
|
146
|
+
const wait = () => ms !== undefined && ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve();
|
|
147
|
+
const run = () => task().then((a) => {
|
|
148
|
+
if (predicate(a))
|
|
149
|
+
return a;
|
|
150
|
+
return wait().then(run);
|
|
151
|
+
});
|
|
152
|
+
return run();
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Converts a `Task<A>` into a `Task<Result<E, A>>`, resolving to `Err` if the
|
|
156
|
+
* Task does not complete within the given time.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* pipe(
|
|
161
|
+
* fetchUser,
|
|
162
|
+
* Task.timeout(5000, () => new TimeoutError("fetch user timed out")),
|
|
163
|
+
* TaskResult.chain(processUser)
|
|
164
|
+
* );
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
Task.timeout = (ms, onTimeout) => (task) => () => {
|
|
168
|
+
let timerId;
|
|
169
|
+
return Promise.race([
|
|
170
|
+
task().then((a) => { clearTimeout(timerId); return Result.ok(a); }),
|
|
171
|
+
new Promise((resolve) => {
|
|
172
|
+
timerId = setTimeout(() => resolve(Result.err(onTimeout())), ms);
|
|
173
|
+
}),
|
|
174
|
+
]);
|
|
175
|
+
};
|
|
108
176
|
})(Task || (Task = {}));
|
|
@@ -9,7 +9,7 @@ export var TaskOption;
|
|
|
9
9
|
/**
|
|
10
10
|
* Creates a TaskOption that resolves to None.
|
|
11
11
|
*/
|
|
12
|
-
TaskOption.none = () => Task.of(Option.
|
|
12
|
+
TaskOption.none = () => Task.of(Option.none());
|
|
13
13
|
/**
|
|
14
14
|
* Lifts an Option into a TaskOption.
|
|
15
15
|
*/
|
|
@@ -29,7 +29,7 @@ export var TaskOption;
|
|
|
29
29
|
* );
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
TaskOption.tryCatch = (f) => () => f().then(Option.of).catch(() => Option.
|
|
32
|
+
TaskOption.tryCatch = (f) => () => f().then(Option.of).catch(() => Option.none());
|
|
33
33
|
/**
|
|
34
34
|
* Transforms the value inside a TaskOption.
|
|
35
35
|
*/
|
|
@@ -46,7 +46,7 @@ export var TaskOption;
|
|
|
46
46
|
* )();
|
|
47
47
|
* ```
|
|
48
48
|
*/
|
|
49
|
-
TaskOption.chain = (f) => (data) => Task.chain((option) => Option.isSome(option) ? f(option.value) : Task.of(Option.
|
|
49
|
+
TaskOption.chain = (f) => (data) => Task.chain((option) => Option.isSome(option) ? f(option.value) : Task.of(Option.none()))(data);
|
|
50
50
|
/**
|
|
51
51
|
* Applies a function wrapped in a TaskOption to a value wrapped in a TaskOption.
|
|
52
52
|
* Both Tasks run in parallel.
|
|
@@ -5,11 +5,11 @@ export var TaskResult;
|
|
|
5
5
|
/**
|
|
6
6
|
* Wraps a value in a successful TaskResult.
|
|
7
7
|
*/
|
|
8
|
-
TaskResult.of = (value) => Task.of(Result.
|
|
8
|
+
TaskResult.of = (value) => Task.of(Result.ok(value));
|
|
9
9
|
/**
|
|
10
10
|
* Creates a failed TaskResult with the given error.
|
|
11
11
|
*/
|
|
12
|
-
TaskResult.
|
|
12
|
+
TaskResult.err = (error) => Task.of(Result.err(error));
|
|
13
13
|
/**
|
|
14
14
|
* Creates a TaskResult from a function that may throw.
|
|
15
15
|
* Catches any errors and transforms them using the onError function.
|
|
@@ -24,8 +24,8 @@ export var TaskResult;
|
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
26
|
TaskResult.tryCatch = (f, onError) => () => f()
|
|
27
|
-
.then(Result.
|
|
28
|
-
.catch((e) => Result.
|
|
27
|
+
.then(Result.ok)
|
|
28
|
+
.catch((e) => Result.err(onError(e)));
|
|
29
29
|
/**
|
|
30
30
|
* Transforms the success value inside a TaskResult.
|
|
31
31
|
*/
|
|
@@ -38,7 +38,7 @@ export var TaskResult;
|
|
|
38
38
|
* Chains TaskResult computations. If the first succeeds, passes the value to f.
|
|
39
39
|
* If the first fails, propagates the error.
|
|
40
40
|
*/
|
|
41
|
-
TaskResult.chain = (f) => (data) => Task.chain((result) => Result.isOk(result) ? f(result.value) : Task.of(Result.
|
|
41
|
+
TaskResult.chain = (f) => (data) => Task.chain((result) => Result.isOk(result) ? f(result.value) : Task.of(Result.err(result.error)))(data);
|
|
42
42
|
/**
|
|
43
43
|
* Extracts the value from a TaskResult by providing handlers for both cases.
|
|
44
44
|
*/
|
|
@@ -60,4 +60,61 @@ export var TaskResult;
|
|
|
60
60
|
* Useful for logging or debugging.
|
|
61
61
|
*/
|
|
62
62
|
TaskResult.tap = (f) => (data) => Task.map(Result.tap(f))(data);
|
|
63
|
+
/**
|
|
64
|
+
* Re-runs a TaskResult on `Err` with configurable attempts, backoff, and retry condition.
|
|
65
|
+
*
|
|
66
|
+
* @param options.attempts - Total number of attempts (1 = no retry, 3 = up to 3 tries)
|
|
67
|
+
* @param options.backoff - Fixed delay in ms, or a function `(attempt) => ms` for computed delay
|
|
68
|
+
* @param options.when - Only retry when this returns true; defaults to always retry on Err
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* // Retry up to 3 times with exponential backoff
|
|
73
|
+
* pipe(
|
|
74
|
+
* fetchUser,
|
|
75
|
+
* TaskResult.retry({ attempts: 3, backoff: n => n * 1000 })
|
|
76
|
+
* );
|
|
77
|
+
*
|
|
78
|
+
* // Only retry on network errors, not auth errors
|
|
79
|
+
* pipe(
|
|
80
|
+
* fetchUser,
|
|
81
|
+
* TaskResult.retry({ attempts: 3, when: e => e instanceof NetworkError })
|
|
82
|
+
* );
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
TaskResult.retry = (options) => (data) => () => {
|
|
86
|
+
const { attempts, backoff, when: shouldRetry } = options;
|
|
87
|
+
const getDelay = (n) => backoff === undefined ? 0 : typeof backoff === "function" ? backoff(n) : backoff;
|
|
88
|
+
const run = (left) => data().then((result) => {
|
|
89
|
+
if (Result.isOk(result))
|
|
90
|
+
return result;
|
|
91
|
+
if (left <= 1)
|
|
92
|
+
return result;
|
|
93
|
+
if (shouldRetry !== undefined && !shouldRetry(result.error))
|
|
94
|
+
return result;
|
|
95
|
+
const ms = getDelay(attempts - left + 1);
|
|
96
|
+
return (ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve()).then(() => run(left - 1));
|
|
97
|
+
});
|
|
98
|
+
return run(attempts);
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Fails a TaskResult with a typed error if it does not resolve within the given time.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* pipe(
|
|
106
|
+
* fetchUser,
|
|
107
|
+
* TaskResult.timeout(5000, () => new TimeoutError("fetch user timed out"))
|
|
108
|
+
* );
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
TaskResult.timeout = (ms, onTimeout) => (data) => () => {
|
|
112
|
+
let timerId;
|
|
113
|
+
return Promise.race([
|
|
114
|
+
data().then((result) => { clearTimeout(timerId); return result; }),
|
|
115
|
+
new Promise((resolve) => {
|
|
116
|
+
timerId = setTimeout(() => resolve(Result.err(onTimeout())), ms);
|
|
117
|
+
}),
|
|
118
|
+
]);
|
|
119
|
+
};
|
|
63
120
|
})(TaskResult || (TaskResult = {}));
|