@nlozgachev/pipelined 0.6.5 → 0.8.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/esm/src/Core/Arr.js +36 -1
- package/esm/src/Core/Deferred.js +8 -4
- package/esm/src/Core/Logged.js +111 -0
- package/esm/src/Core/Option.js +6 -5
- package/esm/src/Core/Predicate.js +133 -0
- package/esm/src/Core/Refinement.js +115 -0
- package/esm/src/Core/RemoteData.js +1 -1
- package/esm/src/Core/Result.js +8 -6
- package/esm/src/Core/State.js +181 -0
- package/esm/src/Core/Task.js +36 -0
- package/esm/src/Core/Validation.js +1 -1
- package/esm/src/Core/index.js +4 -0
- package/package.json +1 -1
- package/script/src/Core/Arr.js +36 -1
- package/script/src/Core/Deferred.js +8 -4
- package/script/src/Core/Logged.js +114 -0
- package/script/src/Core/Option.js +6 -5
- package/script/src/Core/Predicate.js +136 -0
- package/script/src/Core/Refinement.js +118 -0
- package/script/src/Core/RemoteData.js +1 -1
- package/script/src/Core/Result.js +8 -6
- package/script/src/Core/State.js +184 -0
- package/script/src/Core/Task.js +36 -0
- package/script/src/Core/Validation.js +1 -1
- package/script/src/Core/index.js +4 -0
- package/types/src/Core/Arr.d.ts +27 -1
- package/types/src/Core/Arr.d.ts.map +1 -1
- package/types/src/Core/Deferred.d.ts.map +1 -1
- package/types/src/Core/InternalTypes.d.ts +3 -0
- package/types/src/Core/InternalTypes.d.ts.map +1 -1
- package/types/src/Core/Logged.d.ts +126 -0
- package/types/src/Core/Logged.d.ts.map +1 -0
- package/types/src/Core/Option.d.ts +5 -4
- package/types/src/Core/Option.d.ts.map +1 -1
- package/types/src/Core/Predicate.d.ts +161 -0
- package/types/src/Core/Predicate.d.ts.map +1 -0
- package/types/src/Core/Refinement.d.ts +138 -0
- package/types/src/Core/Refinement.d.ts.map +1 -0
- package/types/src/Core/RemoteData.d.ts +1 -1
- package/types/src/Core/RemoteData.d.ts.map +1 -1
- package/types/src/Core/Result.d.ts +8 -6
- package/types/src/Core/Result.d.ts.map +1 -1
- package/types/src/Core/State.d.ts +192 -0
- package/types/src/Core/State.d.ts.map +1 -0
- package/types/src/Core/Task.d.ts +30 -0
- package/types/src/Core/Task.d.ts.map +1 -1
- package/types/src/Core/TaskOption.d.ts +1 -1
- package/types/src/Core/TaskOption.d.ts.map +1 -1
- package/types/src/Core/TaskResult.d.ts +1 -1
- package/types/src/Core/TaskResult.d.ts.map +1 -1
- package/types/src/Core/TaskValidation.d.ts +1 -1
- package/types/src/Core/TaskValidation.d.ts.map +1 -1
- package/types/src/Core/Validation.d.ts +1 -1
- package/types/src/Core/Validation.d.ts.map +1 -1
- package/types/src/Core/index.d.ts +4 -0
- package/types/src/Core/index.d.ts.map +1 -1
package/esm/src/Core/Arr.js
CHANGED
|
@@ -227,7 +227,7 @@ export var Arr;
|
|
|
227
227
|
* pipe([1, 2], Arr.zipWith((a, b) => a + b, ["a", "b"])); // ["1a", "2b"]
|
|
228
228
|
* ```
|
|
229
229
|
*/
|
|
230
|
-
Arr.zipWith = (f
|
|
230
|
+
Arr.zipWith = (f) => (other) => (data) => {
|
|
231
231
|
const len = Math.min(data.length, other.length);
|
|
232
232
|
const result = [];
|
|
233
233
|
for (let i = 0; i < len; i++) {
|
|
@@ -376,6 +376,41 @@ export var Arr;
|
|
|
376
376
|
* Collects an array of Tasks into a Task of array. Runs in parallel.
|
|
377
377
|
*/
|
|
378
378
|
Arr.sequenceTask = (data) => Arr.traverseTask((a) => a)(data);
|
|
379
|
+
/**
|
|
380
|
+
* Maps each element to a TaskResult and runs them sequentially.
|
|
381
|
+
* Returns the first Err encountered, or Ok of all results if all succeed.
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```ts
|
|
385
|
+
* const validate = (n: number): TaskResult<string, number> =>
|
|
386
|
+
* n > 0 ? TaskResult.ok(n) : TaskResult.err("non-positive");
|
|
387
|
+
*
|
|
388
|
+
* pipe(
|
|
389
|
+
* [1, 2, 3],
|
|
390
|
+
* Arr.traverseTaskResult(validate)
|
|
391
|
+
* )(); // Deferred<Ok([1, 2, 3])>
|
|
392
|
+
*
|
|
393
|
+
* pipe(
|
|
394
|
+
* [1, -1, 3],
|
|
395
|
+
* Arr.traverseTaskResult(validate)
|
|
396
|
+
* )(); // Deferred<Err("non-positive")>
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
Arr.traverseTaskResult = (f) => (data) => Task.from(async () => {
|
|
400
|
+
const result = [];
|
|
401
|
+
for (const a of data) {
|
|
402
|
+
const r = await Deferred.toPromise(f(a)());
|
|
403
|
+
if (Result.isErr(r))
|
|
404
|
+
return r;
|
|
405
|
+
result.push(r.value);
|
|
406
|
+
}
|
|
407
|
+
return Result.ok(result);
|
|
408
|
+
});
|
|
409
|
+
/**
|
|
410
|
+
* Collects an array of TaskResults into a TaskResult of array.
|
|
411
|
+
* Returns the first Err if any element is Err, runs sequentially.
|
|
412
|
+
*/
|
|
413
|
+
Arr.sequenceTaskResult = (data) => Arr.traverseTaskResult((a) => a)(data);
|
|
379
414
|
/**
|
|
380
415
|
* Returns true if the array is non-empty (type guard).
|
|
381
416
|
*/
|
package/esm/src/Core/Deferred.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const _store = new WeakMap();
|
|
1
2
|
export var Deferred;
|
|
2
3
|
(function (Deferred) {
|
|
3
4
|
/**
|
|
@@ -10,9 +11,11 @@ export var Deferred;
|
|
|
10
11
|
* const value = await d; // "hello"
|
|
11
12
|
* ```
|
|
12
13
|
*/
|
|
13
|
-
Deferred.fromPromise = (p) =>
|
|
14
|
-
then: ((f) => p.then(f))
|
|
15
|
-
|
|
14
|
+
Deferred.fromPromise = (p) => {
|
|
15
|
+
const d = ({ then: ((f) => p.then(f)) });
|
|
16
|
+
_store.set(d, p);
|
|
17
|
+
return d;
|
|
18
|
+
};
|
|
16
19
|
/**
|
|
17
20
|
* Converts a `Deferred` back into a `Promise`.
|
|
18
21
|
*
|
|
@@ -22,5 +25,6 @@ export var Deferred;
|
|
|
22
25
|
* // p is Promise<42>
|
|
23
26
|
* ```
|
|
24
27
|
*/
|
|
25
|
-
Deferred.toPromise = (d) =>
|
|
28
|
+
Deferred.toPromise = (d) => _store.get(d) ??
|
|
29
|
+
new Promise((resolve) => d.then(resolve));
|
|
26
30
|
})(Deferred || (Deferred = {}));
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export var Logged;
|
|
2
|
+
(function (Logged) {
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a pure value into a `Logged` with an empty log.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* Logged.make<string, number>(42); // { value: 42, log: [] }
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
Logged.make = (value) => ({ value, log: [] });
|
|
12
|
+
/**
|
|
13
|
+
* Creates a `Logged` that records a single log entry and produces no
|
|
14
|
+
* meaningful value. Use this to append to the log inside a `chain`.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* Logged.tell("operation completed"); // { value: undefined, log: ["operation completed"] }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
Logged.tell = (entry) => ({ value: undefined, log: [entry] });
|
|
22
|
+
/**
|
|
23
|
+
* Transforms the value inside a `Logged` without affecting the log.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* pipe(
|
|
28
|
+
* Logged.make<string, number>(5),
|
|
29
|
+
* Logged.map(n => n * 2),
|
|
30
|
+
* ); // { value: 10, log: [] }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
Logged.map = (f) => (data) => ({
|
|
34
|
+
value: f(data.value),
|
|
35
|
+
log: data.log,
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Sequences two `Logged` computations, concatenating their logs.
|
|
39
|
+
* The value from the first is passed to `f`; the resulting log entries are
|
|
40
|
+
* appended after the entries from the first.
|
|
41
|
+
*
|
|
42
|
+
* Data-last — the first computation is the data being piped.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const result = pipe(
|
|
47
|
+
* Logged.make<string, number>(1),
|
|
48
|
+
* Logged.chain(n => pipe(Logged.tell("step"), Logged.map(() => n + 1))),
|
|
49
|
+
* Logged.chain(n => pipe(Logged.tell("done"), Logged.map(() => n * 10))),
|
|
50
|
+
* );
|
|
51
|
+
*
|
|
52
|
+
* Logged.run(result); // [20, ["step", "done"]]
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
Logged.chain = (f) => (data) => {
|
|
56
|
+
const next = f(data.value);
|
|
57
|
+
return { value: next.value, log: [...data.log, ...next.log] };
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Applies a function wrapped in a `Logged` to a value wrapped in a `Logged`,
|
|
61
|
+
* concatenating both logs.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const fn: Logged<string, (n: number) => number> = {
|
|
66
|
+
* value: n => n * 2,
|
|
67
|
+
* log: ["fn-loaded"],
|
|
68
|
+
* };
|
|
69
|
+
* const arg: Logged<string, number> = { value: 5, log: ["arg-loaded"] };
|
|
70
|
+
*
|
|
71
|
+
* const result = pipe(fn, Logged.ap(arg));
|
|
72
|
+
* Logged.run(result); // [10, ["fn-loaded", "arg-loaded"]]
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
Logged.ap = (arg) => (data) => ({
|
|
76
|
+
value: data.value(arg.value),
|
|
77
|
+
log: [...data.log, ...arg.log],
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Runs a side effect on the value without changing the `Logged`.
|
|
81
|
+
* Useful for debugging or inspecting intermediate values.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* pipe(
|
|
86
|
+
* Logged.make<string, number>(42),
|
|
87
|
+
* Logged.tap(n => console.log("value:", n)),
|
|
88
|
+
* );
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
Logged.tap = (f) => (data) => {
|
|
92
|
+
f(data.value);
|
|
93
|
+
return data;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Extracts the value and log as a `readonly [A, ReadonlyArray<W>]` tuple.
|
|
97
|
+
* Use this at the boundary where you need to consume both.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* const result = pipe(
|
|
102
|
+
* Logged.make<string, number>(1),
|
|
103
|
+
* Logged.chain(n => pipe(Logged.tell("incremented"), Logged.map(() => n + 1))),
|
|
104
|
+
* );
|
|
105
|
+
*
|
|
106
|
+
* const [value, log] = Logged.run(result);
|
|
107
|
+
* // value = 2, log = ["incremented"]
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
Logged.run = (data) => [data.value, data.log];
|
|
111
|
+
})(Logged || (Logged = {}));
|
package/esm/src/Core/Option.js
CHANGED
|
@@ -128,16 +128,17 @@ export var Option;
|
|
|
128
128
|
Option.match = (cases) => (data) => Option.isSome(data) ? cases.some(data.value) : cases.none();
|
|
129
129
|
/**
|
|
130
130
|
* Returns the value inside an Option, or a default value if None.
|
|
131
|
+
* The default is a thunk `() => B` — evaluated only when the Option is None.
|
|
131
132
|
* The default can be a different type, widening the result to `A | B`.
|
|
132
133
|
*
|
|
133
134
|
* @example
|
|
134
135
|
* ```ts
|
|
135
|
-
* pipe(Option.some(5), Option.getOrElse(0)); // 5
|
|
136
|
-
* pipe(Option.none(), Option.getOrElse(0)); // 0
|
|
137
|
-
* pipe(Option.none<string>(), Option.getOrElse(null)); // null — typed as string | null
|
|
136
|
+
* pipe(Option.some(5), Option.getOrElse(() => 0)); // 5
|
|
137
|
+
* pipe(Option.none(), Option.getOrElse(() => 0)); // 0
|
|
138
|
+
* pipe(Option.none<string>(), Option.getOrElse(() => null)); // null — typed as string | null
|
|
138
139
|
* ```
|
|
139
140
|
*/
|
|
140
|
-
Option.getOrElse = (defaultValue) => (data) => Option.isSome(data) ? data.value : defaultValue;
|
|
141
|
+
Option.getOrElse = (defaultValue) => (data) => Option.isSome(data) ? data.value : defaultValue();
|
|
141
142
|
/**
|
|
142
143
|
* Executes a side effect on the value without changing the Option.
|
|
143
144
|
* Useful for logging or debugging.
|
|
@@ -166,7 +167,7 @@ export var Option;
|
|
|
166
167
|
* pipe(Option.some(2), Option.filter(n => n > 3)); // None
|
|
167
168
|
* ```
|
|
168
169
|
*/
|
|
169
|
-
Option.filter = (predicate) => (data) => Option.isSome(data)
|
|
170
|
+
Option.filter = (predicate) => (data) => Option.isSome(data) ? (predicate(data.value) ? data : Option.none()) : data;
|
|
170
171
|
/**
|
|
171
172
|
* Recovers from a None by providing a fallback Option.
|
|
172
173
|
* The fallback can produce a different type, widening the result to `Option<A | B>`.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export var Predicate;
|
|
2
|
+
(function (Predicate) {
|
|
3
|
+
/**
|
|
4
|
+
* Negates a predicate: the result passes exactly when the original fails.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const isBlank: Predicate<string> = s => s.trim().length === 0;
|
|
9
|
+
* const isNotBlank = Predicate.not(isBlank);
|
|
10
|
+
*
|
|
11
|
+
* isNotBlank("hello"); // true
|
|
12
|
+
* isNotBlank(" "); // false
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
Predicate.not = (p) => (a) => !p(a);
|
|
16
|
+
/**
|
|
17
|
+
* Combines two predicates with logical AND: passes only when both hold.
|
|
18
|
+
*
|
|
19
|
+
* Data-last — the first predicate is the data being piped.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const isPositive: Predicate<number> = n => n > 0;
|
|
24
|
+
* const isEven: Predicate<number> = n => n % 2 === 0;
|
|
25
|
+
*
|
|
26
|
+
* const isPositiveEven: Predicate<number> = pipe(isPositive, Predicate.and(isEven));
|
|
27
|
+
*
|
|
28
|
+
* isPositiveEven(4); // true
|
|
29
|
+
* isPositiveEven(3); // false — positive but odd
|
|
30
|
+
* isPositiveEven(-2); // false — even but not positive
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
Predicate.and = (second) => (first) => (a) => first(a) && second(a);
|
|
34
|
+
/**
|
|
35
|
+
* Combines two predicates with logical OR: passes when either holds.
|
|
36
|
+
*
|
|
37
|
+
* Data-last — the first predicate is the data being piped.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const isChild: Predicate<number> = n => n < 13;
|
|
42
|
+
* const isSenior: Predicate<number> = n => n >= 65;
|
|
43
|
+
*
|
|
44
|
+
* const getsDiscount: Predicate<number> = pipe(isChild, Predicate.or(isSenior));
|
|
45
|
+
*
|
|
46
|
+
* getsDiscount(8); // true
|
|
47
|
+
* getsDiscount(70); // true
|
|
48
|
+
* getsDiscount(30); // false
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
Predicate.or = (second) => (first) => (a) => first(a) || second(a);
|
|
52
|
+
/**
|
|
53
|
+
* Adapts a `Predicate<A>` to work on a different input type `B` by applying `f`
|
|
54
|
+
* to extract the relevant `A` from a `B` before running the check.
|
|
55
|
+
*
|
|
56
|
+
* Data-last — the predicate is the data being piped; `f` is the extractor.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* type User = { name: string; age: number };
|
|
61
|
+
*
|
|
62
|
+
* const isAdult: Predicate<number> = n => n >= 18;
|
|
63
|
+
*
|
|
64
|
+
* // Lift isAdult to work on Users by extracting the age field
|
|
65
|
+
* const isAdultUser: Predicate<User> = pipe(
|
|
66
|
+
* isAdult,
|
|
67
|
+
* Predicate.using((u: User) => u.age)
|
|
68
|
+
* );
|
|
69
|
+
*
|
|
70
|
+
* isAdultUser({ name: "Alice", age: 30 }); // true
|
|
71
|
+
* isAdultUser({ name: "Bob", age: 15 }); // false
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
Predicate.using = (f) => (p) => (b) => p(f(b));
|
|
75
|
+
/**
|
|
76
|
+
* Combines an array of predicates with AND: passes only when every predicate holds.
|
|
77
|
+
* Returns `true` for an empty array (vacuous truth).
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const checks: Predicate<string>[] = [
|
|
82
|
+
* s => s.length > 0,
|
|
83
|
+
* s => s.length <= 100,
|
|
84
|
+
* s => !s.includes("<"),
|
|
85
|
+
* ];
|
|
86
|
+
*
|
|
87
|
+
* Predicate.all(checks)("hello"); // true
|
|
88
|
+
* Predicate.all(checks)(""); // false — too short
|
|
89
|
+
* Predicate.all(checks)("<b>"); // false — contains "<"
|
|
90
|
+
* Predicate.all([])("anything"); // true
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
Predicate.all = (predicates) => (a) => predicates.every((p) => p(a));
|
|
94
|
+
/**
|
|
95
|
+
* Combines an array of predicates with OR: passes when at least one holds.
|
|
96
|
+
* Returns `false` for an empty array.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const acceptedFormats: Predicate<string>[] = [
|
|
101
|
+
* s => s.endsWith(".jpg"),
|
|
102
|
+
* s => s.endsWith(".png"),
|
|
103
|
+
* s => s.endsWith(".webp"),
|
|
104
|
+
* ];
|
|
105
|
+
*
|
|
106
|
+
* Predicate.any(acceptedFormats)("photo.jpg"); // true
|
|
107
|
+
* Predicate.any(acceptedFormats)("photo.gif"); // false
|
|
108
|
+
* Predicate.any([])("anything"); // false
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
Predicate.any = (predicates) => (a) => predicates.some((p) => p(a));
|
|
112
|
+
/**
|
|
113
|
+
* Converts a `Refinement<A, B>` into a `Predicate<A>`, discarding the compile-time
|
|
114
|
+
* narrowing. Use this when you want to combine a type guard with plain predicates
|
|
115
|
+
* using `and`, `or`, or `all`.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const isString: Refinement<unknown, string> =
|
|
120
|
+
* Refinement.make(x => typeof x === "string");
|
|
121
|
+
*
|
|
122
|
+
* const isShortString: Predicate<unknown> = pipe(
|
|
123
|
+
* Predicate.fromRefinement(isString),
|
|
124
|
+
* Predicate.and(x => (x as string).length < 10)
|
|
125
|
+
* );
|
|
126
|
+
*
|
|
127
|
+
* isShortString("hi"); // true
|
|
128
|
+
* isShortString("a very long string that exceeds ten characters"); // false
|
|
129
|
+
* isShortString(42); // false
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
Predicate.fromRefinement = (r) => r;
|
|
133
|
+
})(Predicate || (Predicate = {}));
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Option } from "./Option.js";
|
|
2
|
+
import { Result } from "./Result.js";
|
|
3
|
+
export var Refinement;
|
|
4
|
+
(function (Refinement) {
|
|
5
|
+
/**
|
|
6
|
+
* Creates a `Refinement<A, B>` from a plain boolean predicate.
|
|
7
|
+
*
|
|
8
|
+
* This is an unsafe cast — the caller is responsible for ensuring that the
|
|
9
|
+
* predicate truly characterises values of type `B`. Use this only when
|
|
10
|
+
* bootstrapping a new refinement; prefer `compose`, `and`, or `or` to build
|
|
11
|
+
* derived refinements from existing ones.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* type PositiveNumber = number & { readonly _tag: "PositiveNumber" };
|
|
16
|
+
*
|
|
17
|
+
* const isPositive: Refinement<number, PositiveNumber> =
|
|
18
|
+
* Refinement.make(n => n > 0);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
Refinement.make = (f) => f;
|
|
22
|
+
/**
|
|
23
|
+
* Chains two refinements: if `ab` narrows `A` to `B` and `bc` narrows `B` to `C`,
|
|
24
|
+
* the result narrows `A` directly to `C`.
|
|
25
|
+
*
|
|
26
|
+
* Data-last — the first refinement `ab` is the data being piped.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* type NonEmptyString = string & { readonly _tag: "NonEmpty" };
|
|
31
|
+
* type TrimmedString = NonEmptyString & { readonly _tag: "Trimmed" };
|
|
32
|
+
*
|
|
33
|
+
* const isNonEmpty: Refinement<string, NonEmptyString> =
|
|
34
|
+
* Refinement.make(s => s.length > 0);
|
|
35
|
+
* const isTrimmed: Refinement<NonEmptyString, TrimmedString> =
|
|
36
|
+
* Refinement.make(s => s === s.trim());
|
|
37
|
+
*
|
|
38
|
+
* const isNonEmptyTrimmed: Refinement<string, TrimmedString> = pipe(
|
|
39
|
+
* isNonEmpty,
|
|
40
|
+
* Refinement.compose(isTrimmed)
|
|
41
|
+
* );
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
Refinement.compose = (bc) => (ab) => (a) => ab(a) && bc(a);
|
|
45
|
+
/**
|
|
46
|
+
* Intersects two refinements: the result narrows `A` to `B & C`, passing only
|
|
47
|
+
* when both refinements hold simultaneously.
|
|
48
|
+
*
|
|
49
|
+
* Data-last — the first refinement is the data being piped.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const isString: Refinement<unknown, string> = Refinement.make(x => typeof x === "string");
|
|
54
|
+
* const isNonEmpty: Refinement<unknown, { length: number }> =
|
|
55
|
+
* Refinement.make(x => (x as any).length > 0);
|
|
56
|
+
*
|
|
57
|
+
* const isNonEmptyString = pipe(isString, Refinement.and(isNonEmpty));
|
|
58
|
+
* isNonEmptyString("hi"); // true
|
|
59
|
+
* isNonEmptyString(""); // false
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
Refinement.and = (second) => (first) => (a) => first(a) && second(a);
|
|
63
|
+
/**
|
|
64
|
+
* Unions two refinements: the result narrows `A` to `B | C`, passing when either
|
|
65
|
+
* refinement holds.
|
|
66
|
+
*
|
|
67
|
+
* Data-last — the first refinement is the data being piped.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const isString: Refinement<unknown, string> = Refinement.make(x => typeof x === "string");
|
|
72
|
+
* const isNumber: Refinement<unknown, number> = Refinement.make(x => typeof x === "number");
|
|
73
|
+
*
|
|
74
|
+
* const isStringOrNumber = pipe(isString, Refinement.or(isNumber));
|
|
75
|
+
* isStringOrNumber("hi"); // true
|
|
76
|
+
* isStringOrNumber(42); // true
|
|
77
|
+
* isStringOrNumber(true); // false
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
Refinement.or = (second) => (first) => (a) => first(a) || second(a);
|
|
81
|
+
/**
|
|
82
|
+
* Converts a `Refinement<A, B>` into a function `(a: A) => Option<B>`.
|
|
83
|
+
*
|
|
84
|
+
* Returns `Some(a)` when the refinement holds, `None` otherwise. Useful for
|
|
85
|
+
* integrating runtime validation into an `Option`-based pipeline.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* type PositiveNumber = number & { readonly _tag: "Positive" };
|
|
90
|
+
* const isPositive: Refinement<number, PositiveNumber> =
|
|
91
|
+
* Refinement.make(n => n > 0);
|
|
92
|
+
*
|
|
93
|
+
* pipe(-1, Refinement.toFilter(isPositive)); // None
|
|
94
|
+
* pipe(42, Refinement.toFilter(isPositive)); // Some(42)
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
Refinement.toFilter = (r) => (a) => r(a) ? Option.some(a) : Option.none();
|
|
98
|
+
/**
|
|
99
|
+
* Converts a `Refinement<A, B>` into a function `(a: A) => Result<E, B>`.
|
|
100
|
+
*
|
|
101
|
+
* Returns `Ok(a)` when the refinement holds, `Err(onFail(a))` otherwise. Use
|
|
102
|
+
* this to surface validation failures as typed errors inside a `Result` pipeline.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* type NonEmptyString = string & { readonly _tag: "NonEmpty" };
|
|
107
|
+
* const isNonEmpty: Refinement<string, NonEmptyString> =
|
|
108
|
+
* Refinement.make(s => s.length > 0);
|
|
109
|
+
*
|
|
110
|
+
* pipe("", Refinement.toResult(isNonEmpty, () => "must not be empty")); // Err(...)
|
|
111
|
+
* pipe("hi", Refinement.toResult(isNonEmpty, () => "must not be empty")); // Ok("hi")
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
Refinement.toResult = (r, onFail) => (a) => r(a) ? Result.ok(a) : Result.err(onFail(a));
|
|
115
|
+
})(Refinement || (Refinement = {}));
|
|
@@ -162,7 +162,7 @@ export var RemoteData;
|
|
|
162
162
|
* pipe(RemoteData.loading<string, number>(), RemoteData.getOrElse(null)); // null — typed as number | null
|
|
163
163
|
* ```
|
|
164
164
|
*/
|
|
165
|
-
RemoteData.getOrElse = (defaultValue) => (data) => RemoteData.isSuccess(data) ? data.value : defaultValue;
|
|
165
|
+
RemoteData.getOrElse = (defaultValue) => (data) => RemoteData.isSuccess(data) ? data.value : defaultValue();
|
|
166
166
|
/**
|
|
167
167
|
* Executes a side effect on the success value without changing the RemoteData.
|
|
168
168
|
*
|
package/esm/src/Core/Result.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Option } from "./Option.js";
|
|
1
2
|
export var Result;
|
|
2
3
|
(function (Result) {
|
|
3
4
|
/**
|
|
@@ -102,16 +103,17 @@ export var Result;
|
|
|
102
103
|
Result.match = (cases) => (data) => Result.isOk(data) ? cases.ok(data.value) : cases.err(data.error);
|
|
103
104
|
/**
|
|
104
105
|
* Returns the success value or a default value if the Result is an error.
|
|
106
|
+
* The default is a thunk `() => B` — evaluated only when the Result is Err.
|
|
105
107
|
* The default can be a different type, widening the result to `A | B`.
|
|
106
108
|
*
|
|
107
109
|
* @example
|
|
108
110
|
* ```ts
|
|
109
|
-
* pipe(Result.ok(5), Result.getOrElse(0)); // 5
|
|
110
|
-
* pipe(Result.err("error"), Result.getOrElse(0)); // 0
|
|
111
|
-
* pipe(Result.err("error"), Result.getOrElse(null)); // null — typed as number | null
|
|
111
|
+
* pipe(Result.ok(5), Result.getOrElse(() => 0)); // 5
|
|
112
|
+
* pipe(Result.err("error"), Result.getOrElse(() => 0)); // 0
|
|
113
|
+
* pipe(Result.err("error"), Result.getOrElse(() => null)); // null — typed as number | null
|
|
112
114
|
* ```
|
|
113
115
|
*/
|
|
114
|
-
Result.getOrElse = (defaultValue) => (data) => Result.isOk(data) ? data.value : defaultValue;
|
|
116
|
+
Result.getOrElse = (defaultValue) => (data) => Result.isOk(data) ? data.value : defaultValue();
|
|
115
117
|
/**
|
|
116
118
|
* Executes a side effect on the success value without changing the Result.
|
|
117
119
|
* Useful for logging or debugging.
|
|
@@ -134,7 +136,7 @@ export var Result;
|
|
|
134
136
|
* Recovers from an error by providing a fallback Result.
|
|
135
137
|
* The fallback can produce a different success type, widening the result to `Result<E, A | B>`.
|
|
136
138
|
*/
|
|
137
|
-
Result.recover = (fallback) => (data) => Result.isOk(data) ? data : fallback();
|
|
139
|
+
Result.recover = (fallback) => (data) => Result.isOk(data) ? data : fallback(data.error);
|
|
138
140
|
/**
|
|
139
141
|
* Recovers from an error unless it matches the blocked error.
|
|
140
142
|
* The fallback can produce a different success type, widening the result to `Result<E, A | B>`.
|
|
@@ -150,7 +152,7 @@ export var Result;
|
|
|
150
152
|
* Result.toOption(Result.err("oops")); // None
|
|
151
153
|
* ```
|
|
152
154
|
*/
|
|
153
|
-
Result.toOption = (data) => Result.isOk(data) ?
|
|
155
|
+
Result.toOption = (data) => Result.isOk(data) ? Option.some(data.value) : Option.none();
|
|
154
156
|
/**
|
|
155
157
|
* Applies a function wrapped in an Result to a value wrapped in an Result.
|
|
156
158
|
*
|